Звонки: preflight сессии перед вызовом и retry; таймаут вынесен в настройки

This commit is contained in:
AidarKC
2026-05-05 18:11:55 +03:00
parent ef0cd2cb7d
commit 6774c26ea1
5 changed files with 116 additions and 3 deletions
+66 -1
View File
@@ -1,4 +1,4 @@
import { addSystemChatMessage, authService } from '../state.js';
import { addSystemChatMessage, authService, authorizeSession, state } from '../state.js';
const TYPES = {
INVITE: 100,
@@ -31,6 +31,47 @@ function nowMs() {
return Date.now();
}
function resolveCallPreflightTimeoutMs() {
const configured = Number(state?.entrySettings?.callPreflightTimeoutMs || 6000);
return Math.max(1000, Math.min(20000, Number.isFinite(configured) ? configured : 6000));
}
function isSessionReadyForCall() {
const wsOpen = Boolean(authService?.ws?.ws && authService.ws.ws.readyState === WebSocket.OPEN);
const hasSession = Boolean(state?.session?.isAuthorized && state?.session?.login && state?.session?.sessionId);
return wsOpen && hasSession;
}
async function withTimeout(promise, timeoutMs, timeoutMessage = 'timeout') {
let timerId = 0;
try {
return await Promise.race([
promise,
new Promise((_, reject) => {
timerId = window.setTimeout(() => reject(new Error(timeoutMessage)), timeoutMs);
}),
]);
} finally {
if (timerId) window.clearTimeout(timerId);
}
}
async function ensureSessionForCall({ timeoutMs, force = false } = {}) {
if (!force && isSessionReadyForCall()) return true;
const login = String(state?.session?.login || '').trim();
const sessionId = String(state?.session?.sessionId || '').trim();
if (!login || !sessionId) return false;
try {
await withTimeout(authService.ws.open(), timeoutMs, 'call_preflight_ws_timeout');
const resumed = await withTimeout(authService.resumeSession(login, sessionId), timeoutMs, 'call_preflight_resume_timeout');
authorizeSession(resumed);
return true;
} catch {
return false;
}
}
function makeCallId() {
return `${Date.now()}${Math.floor(Math.random() * 1_000_000_000)}`;
}
@@ -1314,6 +1355,12 @@ export async function startOutgoingCall(peerLogin) {
}
const callId = makeCallId();
const preflightTimeoutMs = resolveCallPreflightTimeoutMs();
const preflightOk = await ensureSessionForCall({ timeoutMs: preflightTimeoutMs, force: false });
if (!preflightOk) {
throw new Error('Сервер временно недоступен');
}
const call = {
callId,
peerLogin: cleanPeer,
@@ -1359,6 +1406,19 @@ export async function startOutgoingCall(peerLogin) {
try {
await authService.callInviteBroadcast({ toLogin: cleanPeer, callId, type: TYPES.INVITE });
} catch (error) {
const text = String(error?.message || '').toUpperCase();
const isNotAuth = text.includes('NOT_AUTHENTICATED');
if (isNotAuth) {
const recovered = await ensureSessionForCall({ timeoutMs: preflightTimeoutMs, force: true });
if (recovered) {
try {
await authService.callInviteBroadcast({ toLogin: cleanPeer, callId, type: TYPES.INVITE });
return;
} catch {}
}
await finalizeCall(call, { localReasonCode: 'error', debugReason: 'invite_failed:not_authenticated_after_retry' });
throw new Error('Сервер временно недоступен');
}
await finalizeCall(call, { localReasonCode: 'error', debugReason: `invite_failed:${toErrorText(error)}` });
throw error;
}
@@ -1587,6 +1647,11 @@ export async function handleCallPushAction(action, payload = {}) {
const normalized = String(action || '').trim().toLowerCase();
if (normalized !== 'accept' && normalized !== 'decline') return;
if (!isIncomingCallPushFresh(payload)) return;
const timeoutMs = resolveCallPreflightTimeoutMs();
const ok = await ensureSessionForCall({ timeoutMs, force: false });
if (!ok) {
throw new Error('Не удалось подключиться, вызов завершён');
}
await handleIncomingCallPush(payload);
if (normalized === 'accept') {
await acceptIncomingCall();