SHA256
Перевести звонки на SendSignal с client key
This commit is contained in:
@@ -10,6 +10,7 @@ import {
|
||||
handleIncomingCallInvite,
|
||||
handleIncomingCallPush,
|
||||
handleIncomingCallSignal,
|
||||
handleIncomingCallSignalViaSendSignal,
|
||||
handleStopCallPush,
|
||||
setCallDebugReporter,
|
||||
startDebugConnectionAsInitiator,
|
||||
@@ -1313,6 +1314,10 @@ async function init() {
|
||||
try { await handleIncomingCallSignal(evt); } catch {}
|
||||
});
|
||||
|
||||
authService.onEvent('IncomingSignal', async (evt) => {
|
||||
try { await handleIncomingCallSignalViaSendSignal(evt); } catch {}
|
||||
});
|
||||
|
||||
authService.onEvent('DebugConnectPrepareResponder', async (evt) => {
|
||||
try {
|
||||
const p = evt?.payload || {};
|
||||
|
||||
@@ -2631,8 +2631,8 @@ export class AuthService {
|
||||
}
|
||||
|
||||
|
||||
async callInviteBroadcast({ toLogin, callId, type = 100 }) {
|
||||
const response = await this.ws.request('CallInviteBroadcast', { toLogin, callId, type });
|
||||
async callInviteBroadcast({ toLogin, callId, type = 100, data = '' }) {
|
||||
const response = await this.ws.request('CallInviteBroadcast', { toLogin, callId, type, data });
|
||||
if (response.status !== 200) throw opError('CallInviteBroadcast', response);
|
||||
return response.payload || {};
|
||||
}
|
||||
|
||||
@@ -8,10 +8,25 @@ const TYPES = {
|
||||
DECLINE_BUSY: 130,
|
||||
TIMEOUT: 140,
|
||||
HANGUP: 150,
|
||||
CONNECT_START: 170,
|
||||
OFFER: 200,
|
||||
ANSWER: 210,
|
||||
ICE: 220,
|
||||
};
|
||||
const SIGNAL_TARGET_SINGLE = 'single_session';
|
||||
const SIGNAL_TARGET_ALL = 'all_sessions';
|
||||
const CALL_SIGNAL_TYPES = Object.freeze({
|
||||
[TYPES.INVITE]: 'call_invite',
|
||||
[TYPES.RINGING]: 'call_ringing',
|
||||
[TYPES.ACCEPT]: 'call_accept',
|
||||
[TYPES.DECLINE_BUSY]: 'call_decline_busy',
|
||||
[TYPES.TIMEOUT]: 'call_timeout',
|
||||
[TYPES.HANGUP]: 'call_hangup',
|
||||
[TYPES.CONNECT_START]: 'call_connect_start',
|
||||
[TYPES.OFFER]: 'call_offer',
|
||||
[TYPES.ANSWER]: 'call_answer',
|
||||
[TYPES.ICE]: 'call_ice',
|
||||
});
|
||||
|
||||
const calls = new Map();
|
||||
const callStateListeners = new Set();
|
||||
@@ -71,12 +86,54 @@ function getSignalTypeName(type) {
|
||||
if (normalized === TYPES.DECLINE_BUSY) return 'decline_busy';
|
||||
if (normalized === TYPES.TIMEOUT) return 'timeout';
|
||||
if (normalized === TYPES.HANGUP) return 'hangup';
|
||||
if (normalized === TYPES.CONNECT_START) return 'connect_start';
|
||||
if (normalized === TYPES.OFFER) return 'offer';
|
||||
if (normalized === TYPES.ANSWER) return 'answer';
|
||||
if (normalized === TYPES.ICE) return 'ice';
|
||||
return `type_${String(type || '')}`;
|
||||
}
|
||||
|
||||
function buildConnectStartData(winnerSessionId) {
|
||||
return `session=${String(winnerSessionId || '').trim()}`;
|
||||
}
|
||||
|
||||
function parseConnectStartWinnerSessionId(data = '') {
|
||||
const raw = String(data || '').trim();
|
||||
if (!raw) return '';
|
||||
if (raw.startsWith('session=')) return raw.slice('session='.length).trim();
|
||||
if (raw.startsWith('winnerSessionId=')) return raw.slice('winnerSessionId='.length).trim();
|
||||
return '';
|
||||
}
|
||||
|
||||
function getCallSignalType(type) {
|
||||
return CALL_SIGNAL_TYPES[Number(type)] || '';
|
||||
}
|
||||
|
||||
function encodeCallSignalData(callId, type, data = '') {
|
||||
return JSON.stringify({
|
||||
callId: String(callId || '').trim(),
|
||||
type: Number(type),
|
||||
data: String(data || ''),
|
||||
});
|
||||
}
|
||||
|
||||
function decodeCallSignalData(raw) {
|
||||
try {
|
||||
const parsed = JSON.parse(String(raw || '{}'));
|
||||
return {
|
||||
callId: String(parsed?.callId || '').trim(),
|
||||
type: Number(parsed?.type),
|
||||
data: String(parsed?.data || ''),
|
||||
};
|
||||
} catch {
|
||||
return {
|
||||
callId: '',
|
||||
type: Number.NaN,
|
||||
data: '',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function ensureCallTimeline(call) {
|
||||
if (!call) return;
|
||||
if (!Array.isArray(call.timelineEvents)) call.timelineEvents = [];
|
||||
@@ -1168,13 +1225,7 @@ async function finalizeCall(call, {
|
||||
const dataValue = notifyRemoteHangup
|
||||
? ''
|
||||
: `setup_failed:${String(localReasonCode || 'error')}:${String(debugReason || '').slice(0, 80)}`;
|
||||
await authService.callSignalToSession({
|
||||
toLogin: call.peerLogin,
|
||||
targetSessionId: call.remoteSessionId,
|
||||
callId: call.callId,
|
||||
type: TYPES.HANGUP,
|
||||
data: dataValue,
|
||||
});
|
||||
await sendSignal(call, TYPES.HANGUP, dataValue);
|
||||
} catch {}
|
||||
}
|
||||
|
||||
@@ -1255,13 +1306,22 @@ async function emitDebug(call, level, message, details = '') {
|
||||
async function sendSignal(call, type, data = '') {
|
||||
if (!call.remoteSessionId) return;
|
||||
const signalName = getSignalTypeName(type);
|
||||
const signalType = getCallSignalType(type);
|
||||
const login = String(state?.session?.login || '').trim();
|
||||
const storagePwd = String(state?.session?.storagePwdInMemory || '').trim();
|
||||
if (!login || !storagePwd || !signalType) {
|
||||
throw new Error('Call SendSignal: missing login/storagePwd/signalType');
|
||||
}
|
||||
try {
|
||||
await authService.callSignalToSession({
|
||||
await authService.sendSignal({
|
||||
toLogin: call.peerLogin,
|
||||
targetMode: SIGNAL_TARGET_SINGLE,
|
||||
targetSessionId: call.remoteSessionId,
|
||||
callId: call.callId,
|
||||
type,
|
||||
data,
|
||||
signalType,
|
||||
signalRequestId: call.callId,
|
||||
data: encodeCallSignalData(call.callId, type, data),
|
||||
storagePwd,
|
||||
includeClientSignature: true,
|
||||
});
|
||||
recordCallTimeline(call, `signal_out_${signalName}`, `toSession=${call.remoteSessionId}; len=${String(data || '').length}`);
|
||||
await emitDebug(call, 'info', `signal_sent_${type}`, `len=${String(data || '').length}`);
|
||||
@@ -1280,6 +1340,50 @@ async function sendSignal(call, type, data = '') {
|
||||
}
|
||||
}
|
||||
|
||||
async function broadcastSignal(call, type, data = '') {
|
||||
const signalName = getSignalTypeName(type);
|
||||
const signalType = getCallSignalType(type);
|
||||
const login = String(state?.session?.login || '').trim();
|
||||
const storagePwd = String(state?.session?.storagePwdInMemory || '').trim();
|
||||
if (!call?.peerLogin || !login || !storagePwd || !signalType) {
|
||||
throw new Error('Call SendSignal broadcast: missing peer/login/storagePwd/signalType');
|
||||
}
|
||||
try {
|
||||
const response = await authService.sendSignal({
|
||||
toLogin: call.peerLogin,
|
||||
targetMode: SIGNAL_TARGET_ALL,
|
||||
signalType,
|
||||
signalRequestId: call.callId,
|
||||
data: encodeCallSignalData(call.callId, type, data),
|
||||
storagePwd,
|
||||
includeClientSignature: true,
|
||||
});
|
||||
recordCallTimeline(
|
||||
call,
|
||||
`signal_broadcast_${signalName}`,
|
||||
`ws=${Number(response?.deliveredWsSessions || response?.deliveredCount || 0)}; push=${Number(response?.deliveredWebPushSessions || response?.deliveredFcmSessions || 0)}`,
|
||||
);
|
||||
return response || {};
|
||||
} catch (error) {
|
||||
recordCallTimeline(call, `signal_broadcast_${signalName}_failed`, toErrorText(error), 'warn');
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async function notifyPeerSessionsAboutConnectStart(call) {
|
||||
const winnerSessionId = String(call?.remoteSessionId || '').trim();
|
||||
if (!call || !winnerSessionId) return;
|
||||
const data = buildConnectStartData(winnerSessionId);
|
||||
try {
|
||||
await broadcastSignal(call, TYPES.CONNECT_START, data);
|
||||
recordCallTimeline(call, 'connect_start_broadcast_sent', `winnerSession=${winnerSessionId}`);
|
||||
await emitDebug(call, 'info', 'connect_start_broadcast_sent', `winnerSession=${winnerSessionId}`);
|
||||
} catch (error) {
|
||||
recordCallTimeline(call, 'connect_start_broadcast_failed', `winnerSession=${winnerSessionId}; error=${toErrorText(error)}`, 'warn');
|
||||
await emitDebug(call, 'warn', 'connect_start_broadcast_failed', toErrorText(error));
|
||||
}
|
||||
}
|
||||
|
||||
async function ensurePeerConnection(call) {
|
||||
if (call.pc) return call.pc;
|
||||
|
||||
@@ -1451,6 +1555,7 @@ async function onAccept(call) {
|
||||
cleanupTimers(call);
|
||||
setStatus(call, 'Соединяем…', 'connecting');
|
||||
try {
|
||||
await notifyPeerSessionsAboutConnectStart(call);
|
||||
const pc = await ensurePeerConnection(call);
|
||||
const offer = await pc.createOffer();
|
||||
await pc.setLocalDescription(offer);
|
||||
@@ -1496,16 +1601,40 @@ async function handleIncomingInvitePayload(payload, { source = 'ws' } = {}) {
|
||||
const callId = String(payload?.callId || '').trim();
|
||||
const fromLogin = String(payload?.fromLogin || '').trim();
|
||||
const fromSessionId = String(payload?.fromSessionId || '').trim();
|
||||
const type = Number(payload?.type || TYPES.INVITE);
|
||||
if (type === TYPES.CONNECT_START) {
|
||||
const call = getCall(callId);
|
||||
if (!call || call.direction !== 'in' || call.phase === 'ended') return call;
|
||||
const winnerSessionId = parseConnectStartWinnerSessionId(payload?.data || '');
|
||||
const currentSessionId = String(state?.session?.sessionId || '').trim();
|
||||
recordCallTimeline(call, 'connect_start_broadcast_received', `winnerSession=${winnerSessionId || '-'}; currentSession=${currentSessionId || '-'}`);
|
||||
if (winnerSessionId && currentSessionId && winnerSessionId === currentSessionId) {
|
||||
await emitDebug(call, 'info', 'connect_start_kept_on_winner_session', winnerSessionId);
|
||||
return call;
|
||||
}
|
||||
dismissCallUiLocally(call);
|
||||
await finalizeCall(call, {
|
||||
localReasonCode: 'completed',
|
||||
debugReason: 'answered_elsewhere_session_selected',
|
||||
suppressRemoteSignal: true,
|
||||
suppressReports: true,
|
||||
suppressSummary: true,
|
||||
});
|
||||
return call;
|
||||
}
|
||||
if (!callId || !fromLogin || !fromSessionId) return null;
|
||||
|
||||
if (activeCallId && activeCallId !== callId) {
|
||||
try {
|
||||
await authService.callSignalToSession({
|
||||
await authService.sendSignal({
|
||||
toLogin: fromLogin,
|
||||
targetMode: SIGNAL_TARGET_SINGLE,
|
||||
targetSessionId: fromSessionId,
|
||||
callId,
|
||||
type: TYPES.DECLINE_BUSY,
|
||||
data: 'busy',
|
||||
signalType: getCallSignalType(TYPES.DECLINE_BUSY),
|
||||
signalRequestId: callId,
|
||||
data: encodeCallSignalData(callId, TYPES.DECLINE_BUSY, 'busy'),
|
||||
storagePwd: state?.session?.storagePwdInMemory || '',
|
||||
includeClientSignature: true,
|
||||
});
|
||||
} catch {}
|
||||
return null;
|
||||
@@ -1804,7 +1933,7 @@ export async function startOutgoingCall(peerLogin) {
|
||||
}, 35000);
|
||||
|
||||
try {
|
||||
call.inviteDelivery = await authService.callInviteBroadcast({ toLogin: cleanPeer, callId, type: TYPES.INVITE });
|
||||
call.inviteDelivery = await broadcastSignal(call, TYPES.INVITE);
|
||||
recordCallTimeline(
|
||||
call,
|
||||
'invite_broadcast_ok',
|
||||
@@ -1822,7 +1951,7 @@ export async function startOutgoingCall(peerLogin) {
|
||||
const recovered = await ensureSessionForCall({ timeoutMs: preflightTimeoutMs, force: true });
|
||||
if (recovered) {
|
||||
try {
|
||||
call.inviteDelivery = await authService.callInviteBroadcast({ toLogin: cleanPeer, callId, type: TYPES.INVITE });
|
||||
call.inviteDelivery = await broadcastSignal(call, TYPES.INVITE);
|
||||
if (getInviteDeliveredCount(call) > 0 && call.phase === 'searching') {
|
||||
setStatus(call, 'Вызываем…', 'ringing');
|
||||
}
|
||||
@@ -1842,6 +1971,33 @@ export async function handleIncomingCallInvite(evt) {
|
||||
await handleIncomingInvitePayload(evt?.payload || {}, { source: 'ws' });
|
||||
}
|
||||
|
||||
export async function handleIncomingCallSignalViaSendSignal(evt) {
|
||||
const payload = evt?.payload || {};
|
||||
const signalType = String(payload?.signalType || '').trim();
|
||||
if (!signalType.startsWith('call_')) return false;
|
||||
|
||||
const decoded = decodeCallSignalData(payload?.data || '');
|
||||
if (!Number.isFinite(decoded.type)) return true;
|
||||
|
||||
const normalizedEvent = {
|
||||
payload: {
|
||||
callId: decoded.callId || String(payload?.signalRequestId || '').trim(),
|
||||
fromLogin: payload?.fromLogin,
|
||||
fromSessionId: payload?.fromSessionId,
|
||||
type: decoded.type,
|
||||
data: decoded.data,
|
||||
},
|
||||
};
|
||||
|
||||
if (decoded.type === TYPES.INVITE || decoded.type === TYPES.CONNECT_START) {
|
||||
await handleIncomingInvitePayload(normalizedEvent.payload, { source: 'send_signal' });
|
||||
return true;
|
||||
}
|
||||
|
||||
await handleIncomingCallSignal(normalizedEvent);
|
||||
return true;
|
||||
}
|
||||
|
||||
export async function acceptIncomingCall() {
|
||||
const call = getActiveCall();
|
||||
if (!call || call.direction !== 'in' || call.phase !== 'incoming') return;
|
||||
@@ -1885,23 +2041,11 @@ export async function handleIncomingCallSignal(evt) {
|
||||
recordCallTimeline(call, `signal_in_${signalName}`, `from=${fromSessionId || '-'}; len=${data.length}`);
|
||||
if (call.direction === 'out') {
|
||||
if (type === TYPES.RINGING) {
|
||||
if (!call.remoteSessionId && fromSessionId) {
|
||||
setRemoteSessionId(call, fromSessionId, 'ringing');
|
||||
}
|
||||
if (call.remoteSessionId && fromSessionId && call.remoteSessionId !== fromSessionId) {
|
||||
recordCallTimeline(call, 'signal_in_ringing_ignored', `selected=${call.remoteSessionId}; from=${fromSessionId}`);
|
||||
await emitDebug(
|
||||
call,
|
||||
'info',
|
||||
'ringing_from_non_selected_session_ignored',
|
||||
`selected=${call.remoteSessionId}; from=${fromSessionId}`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
recordCallTimeline(call, 'ringing_received', `from=${fromSessionId || '-'}`);
|
||||
} else if (type === TYPES.ACCEPT) {
|
||||
if (fromSessionId) {
|
||||
if (!call.remoteSessionId || !call.initialOfferSent) {
|
||||
setRemoteSessionId(call, fromSessionId, 'accept_before_offer_lock');
|
||||
if (!call.remoteSessionId) {
|
||||
setRemoteSessionId(call, fromSessionId, 'accept');
|
||||
} else if (call.remoteSessionId !== fromSessionId) {
|
||||
recordCallTimeline(call, 'signal_in_accept_ignored', `selected=${call.remoteSessionId}; from=${fromSessionId}`, 'warn');
|
||||
await emitDebug(
|
||||
@@ -1961,6 +2105,12 @@ export async function handleIncomingCallSignal(evt) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (type === TYPES.CONNECT_START) {
|
||||
const winnerSessionId = parseConnectStartWinnerSessionId(data);
|
||||
recordCallTimeline(call, 'connect_start_received', `winnerSession=${winnerSessionId || '-'}; currentRemoteSession=${call.remoteSessionId || '-'}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (type === TYPES.ACCEPT) {
|
||||
if (call.direction !== 'out') {
|
||||
await emitDebug(call, 'warn', 'accept_ignored_for_non_outgoing_call', `direction=${call.direction || ''}`);
|
||||
|
||||
Reference in New Issue
Block a user