Звонки: фиксы session fallback/registry, аналитика ICE/TURN, авточек UI-версии и перенос кнопки разработчика
This commit is contained in:
parent
e3377a48b3
commit
27bd47dbe0
@ -1,2 +1,2 @@
|
|||||||
client.version=1.2.25
|
client.version=1.2.28
|
||||||
server.version=1.2.25
|
server.version=1.2.26
|
||||||
|
|||||||
@ -108,6 +108,7 @@ const toolbarEl = document.getElementById('toolbar-slot');
|
|||||||
const appShellEl = document.querySelector('.app-shell');
|
const appShellEl = document.querySelector('.app-shell');
|
||||||
|
|
||||||
const CONNECTION_CHECK_INTERVAL_MS = 20 * 1000;
|
const CONNECTION_CHECK_INTERVAL_MS = 20 * 1000;
|
||||||
|
const UI_VERSION_PERIODIC_CHECK_MS = 5 * 60 * 1000;
|
||||||
const CURRENT_BUILD_HASH = String(window.__SHINE_BUILD_HASH__ || '').trim();
|
const CURRENT_BUILD_HASH = String(window.__SHINE_BUILD_HASH__ || '').trim();
|
||||||
const UI_BUILD_HASH_PATTERN = /window\.__SHINE_BUILD_HASH__\s*=\s*'([^']+)'/;
|
const UI_BUILD_HASH_PATTERN = /window\.__SHINE_BUILD_HASH__\s*=\s*'([^']+)'/;
|
||||||
|
|
||||||
@ -125,6 +126,7 @@ let wsSessionRestoreInFlight = null;
|
|||||||
let uiUpdateReloadScheduled = false;
|
let uiUpdateReloadScheduled = false;
|
||||||
let pwaUpdateCheckAttempted = false;
|
let pwaUpdateCheckAttempted = false;
|
||||||
let uiVersionCheckInFlight = false;
|
let uiVersionCheckInFlight = false;
|
||||||
|
let uiVersionPeriodicIntervalId = null;
|
||||||
|
|
||||||
setClientErrorTransport((payload) => authService.reportClientError(payload));
|
setClientErrorTransport((payload) => authService.reportClientError(payload));
|
||||||
initPwaInstallPromptHandling();
|
initPwaInstallPromptHandling();
|
||||||
@ -335,6 +337,32 @@ async function tryUpdatePwaOnFirstConnectedPing() {
|
|||||||
await refreshServiceWorkers({ activateWaitingWorker: false });
|
await refreshServiceWorkers({ activateWaitingWorker: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function runPeriodicUiVersionCheck() {
|
||||||
|
if (uiUpdateReloadScheduled) return;
|
||||||
|
if (uiVersionCheckInFlight) return;
|
||||||
|
try {
|
||||||
|
const latestHostHash = await fetchCurrentHostUiBuildHash();
|
||||||
|
if (!latestHostHash || !CURRENT_BUILD_HASH || latestHostHash === CURRENT_BUILD_HASH) return;
|
||||||
|
scheduleUiReload({
|
||||||
|
source: 'ui-periodic-version-check',
|
||||||
|
message: `Найдена новая версия UI: ${CURRENT_BUILD_HASH} -> ${latestHostHash}`,
|
||||||
|
delayMs: 600,
|
||||||
|
activateWaitingWorker: true,
|
||||||
|
});
|
||||||
|
} catch {
|
||||||
|
// ignore periodic check errors
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function startPeriodicUiVersionCheck() {
|
||||||
|
if (uiVersionPeriodicIntervalId) return;
|
||||||
|
// ВРЕМЕННО: частая проверка обновления UI (каждые 5 минут) для диагностики проблем с обновлением клиента.
|
||||||
|
// Позже интервал нужно увеличить или вернуть проверку только по ручному действию.
|
||||||
|
uiVersionPeriodicIntervalId = window.setInterval(() => {
|
||||||
|
void runPeriodicUiVersionCheck();
|
||||||
|
}, UI_VERSION_PERIODIC_CHECK_MS);
|
||||||
|
}
|
||||||
|
|
||||||
async function checkConnectionHealth() {
|
async function checkConnectionHealth() {
|
||||||
if (connectionCheckInFlight) return;
|
if (connectionCheckInFlight) return;
|
||||||
connectionCheckInFlight = true;
|
connectionCheckInFlight = true;
|
||||||
@ -887,6 +915,7 @@ async function init() {
|
|||||||
await tryAutoLogin();
|
await tryAutoLogin();
|
||||||
await hydrateMessagesFromStore();
|
await hydrateMessagesFromStore();
|
||||||
startConnectionMonitor();
|
startConnectionMonitor();
|
||||||
|
startPeriodicUiVersionCheck();
|
||||||
await ensureSessionRuntimeStarted();
|
await ensureSessionRuntimeStarted();
|
||||||
|
|
||||||
if (!window.location.hash) {
|
if (!window.location.hash) {
|
||||||
|
|||||||
@ -43,14 +43,12 @@ export function render({ navigate }) {
|
|||||||
<button class="text-btn" type="button" id="settings-device">Устройства</button>
|
<button class="text-btn" type="button" id="settings-device">Устройства</button>
|
||||||
<button class="text-btn" type="button" id="settings-servers">Настройки серверов</button>
|
<button class="text-btn" type="button" id="settings-servers">Настройки серверов</button>
|
||||||
<button class="text-btn" type="button" id="settings-language">Язык / Language</button>
|
<button class="text-btn" type="button" id="settings-language">Язык / Language</button>
|
||||||
<button class="text-btn" type="button" id="settings-developer">Настройки разработчика</button>
|
|
||||||
<button class="text-btn" type="button" id="settings-signout">Завершить текущий сеанс</button>
|
<button class="text-btn" type="button" id="settings-signout">Завершить текущий сеанс</button>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
card.querySelector('#settings-device').addEventListener('click', () => navigate('device-view'));
|
card.querySelector('#settings-device').addEventListener('click', () => navigate('device-view'));
|
||||||
card.querySelector('#settings-servers').addEventListener('click', () => navigate('server-settings-view'));
|
card.querySelector('#settings-servers').addEventListener('click', () => navigate('server-settings-view'));
|
||||||
card.querySelector('#settings-language').addEventListener('click', () => navigate('language-view'));
|
card.querySelector('#settings-language').addEventListener('click', () => navigate('language-view'));
|
||||||
card.querySelector('#settings-developer').addEventListener('click', () => navigate('developer-settings-view'));
|
|
||||||
|
|
||||||
const signOutBtn = card.querySelector('#settings-signout');
|
const signOutBtn = card.querySelector('#settings-signout');
|
||||||
signOutBtn.addEventListener('click', async () => {
|
signOutBtn.addEventListener('click', async () => {
|
||||||
@ -95,6 +93,13 @@ export function render({ navigate }) {
|
|||||||
|
|
||||||
versionCard.append(title, clientVersion, uiBuild, serverVersion);
|
versionCard.append(title, clientVersion, uiBuild, serverVersion);
|
||||||
|
|
||||||
|
const developerCard = document.createElement('div');
|
||||||
|
developerCard.className = 'card stack';
|
||||||
|
developerCard.innerHTML = `
|
||||||
|
<button class="text-btn" type="button" id="settings-developer">Настройки разработчика</button>
|
||||||
|
`;
|
||||||
|
developerCard.querySelector('#settings-developer').addEventListener('click', () => navigate('developer-settings-view'));
|
||||||
|
|
||||||
void (async () => {
|
void (async () => {
|
||||||
try {
|
try {
|
||||||
let value = '';
|
let value = '';
|
||||||
@ -122,5 +127,6 @@ export function render({ navigate }) {
|
|||||||
};
|
};
|
||||||
screen.append(card);
|
screen.append(card);
|
||||||
screen.append(versionCard);
|
screen.append(versionCard);
|
||||||
|
screen.append(developerCard);
|
||||||
return screen;
|
return screen;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -86,6 +86,23 @@ function uniqueUrls(urls = []) {
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function parseTurnHostFromUrl(rawUrl) {
|
||||||
|
const value = String(rawUrl || '').trim();
|
||||||
|
if (!value) return '';
|
||||||
|
const noProto = value.replace(/^turns?:/i, '');
|
||||||
|
const noQuery = noProto.split('?')[0];
|
||||||
|
const noPath = noQuery.split('/')[0];
|
||||||
|
const hostPort = noPath.replace(/^\/\//, '').trim();
|
||||||
|
if (!hostPort) return '';
|
||||||
|
if (hostPort.startsWith('[')) {
|
||||||
|
const end = hostPort.indexOf(']');
|
||||||
|
if (end > 1) return hostPort.slice(1, end);
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
const idx = hostPort.indexOf(':');
|
||||||
|
return (idx >= 0 ? hostPort.slice(0, idx) : hostPort).trim();
|
||||||
|
}
|
||||||
|
|
||||||
async function resolveIceServers(call) {
|
async function resolveIceServers(call) {
|
||||||
try {
|
try {
|
||||||
const payload = await authService.getCallIceConfig();
|
const payload = await authService.getCallIceConfig();
|
||||||
@ -95,6 +112,7 @@ async function resolveIceServers(call) {
|
|||||||
const turnPassword = String(payload?.turnPassword || '').trim();
|
const turnPassword = String(payload?.turnPassword || '').trim();
|
||||||
const turnServers = Array.isArray(payload?.turnServers) ? payload.turnServers : [];
|
const turnServers = Array.isArray(payload?.turnServers) ? payload.turnServers : [];
|
||||||
|
|
||||||
|
const turnHostSet = new Set();
|
||||||
const iceServers = [];
|
const iceServers = [];
|
||||||
if (stunUrls.length > 0) {
|
if (stunUrls.length > 0) {
|
||||||
iceServers.push({ urls: stunUrls.length === 1 ? stunUrls[0] : stunUrls });
|
iceServers.push({ urls: stunUrls.length === 1 ? stunUrls[0] : stunUrls });
|
||||||
@ -105,6 +123,10 @@ async function resolveIceServers(call) {
|
|||||||
const username = String(item?.username || '').trim();
|
const username = String(item?.username || '').trim();
|
||||||
const password = String(item?.password || '').trim();
|
const password = String(item?.password || '').trim();
|
||||||
if (urls.length === 0 || !username || !password) return;
|
if (urls.length === 0 || !username || !password) return;
|
||||||
|
urls.forEach((url) => {
|
||||||
|
const host = parseTurnHostFromUrl(url);
|
||||||
|
if (host) turnHostSet.add(host);
|
||||||
|
});
|
||||||
iceServers.push({
|
iceServers.push({
|
||||||
urls: urls.length === 1 ? urls[0] : urls,
|
urls: urls.length === 1 ? urls[0] : urls,
|
||||||
username,
|
username,
|
||||||
@ -112,6 +134,10 @@ async function resolveIceServers(call) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
} else if (turnUrls.length > 0 && turnUsername && turnPassword) {
|
} else if (turnUrls.length > 0 && turnUsername && turnPassword) {
|
||||||
|
turnUrls.forEach((url) => {
|
||||||
|
const host = parseTurnHostFromUrl(url);
|
||||||
|
if (host) turnHostSet.add(host);
|
||||||
|
});
|
||||||
iceServers.push({
|
iceServers.push({
|
||||||
urls: turnUrls.length === 1 ? turnUrls[0] : turnUrls,
|
urls: turnUrls.length === 1 ? turnUrls[0] : turnUrls,
|
||||||
username: turnUsername,
|
username: turnUsername,
|
||||||
@ -123,6 +149,9 @@ async function resolveIceServers(call) {
|
|||||||
await emitDebug(call, 'warn', 'call_ice_empty_from_server', 'using_default_stun');
|
await emitDebug(call, 'warn', 'call_ice_empty_from_server', 'using_default_stun');
|
||||||
return cloneDefaultIceServers();
|
return cloneDefaultIceServers();
|
||||||
}
|
}
|
||||||
|
if (call) {
|
||||||
|
call.turnHostsConfigured = Array.from(turnHostSet);
|
||||||
|
}
|
||||||
await emitDebug(call, 'info', 'call_ice_loaded_from_server', `stun=${stunUrls.length}; turnEntries=${Math.max(0, iceServers.length - (stunUrls.length > 0 ? 1 : 0))}`);
|
await emitDebug(call, 'info', 'call_ice_loaded_from_server', `stun=${stunUrls.length}; turnEntries=${Math.max(0, iceServers.length - (stunUrls.length > 0 ? 1 : 0))}`);
|
||||||
return iceServers;
|
return iceServers;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -131,6 +160,97 @@ async function resolveIceServers(call) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function collectIceCandidateAnalytics(call) {
|
||||||
|
const pc = call?.pc;
|
||||||
|
if (!pc || typeof pc.getStats !== 'function') return {};
|
||||||
|
try {
|
||||||
|
const stats = await pc.getStats();
|
||||||
|
const localCounts = { host: 0, srflx: 0, relay: 0, prflx: 0, other: 0 };
|
||||||
|
const remoteCounts = { host: 0, srflx: 0, relay: 0, prflx: 0, other: 0 };
|
||||||
|
const relayLocalAddresses = new Set();
|
||||||
|
const relayRemoteAddresses = new Set();
|
||||||
|
const configuredHosts = new Set((call?.turnHostsConfigured || []).map((v) => String(v || '').trim()).filter(Boolean));
|
||||||
|
const matchedConfiguredHosts = new Set();
|
||||||
|
let succeededPairsCount = 0;
|
||||||
|
let succeededPairsWithRelayCount = 0;
|
||||||
|
|
||||||
|
const bump = (bucket, rawType) => {
|
||||||
|
const type = String(rawType || '').trim().toLowerCase();
|
||||||
|
if (!type) bucket.other += 1;
|
||||||
|
else if (Object.prototype.hasOwnProperty.call(bucket, type)) bucket[type] += 1;
|
||||||
|
else bucket.other += 1;
|
||||||
|
};
|
||||||
|
const hostOf = (candidate) => {
|
||||||
|
const raw = String(candidate?.ip || candidate?.address || '').trim();
|
||||||
|
if (!raw) return '';
|
||||||
|
if (raw.startsWith('[')) {
|
||||||
|
const end = raw.indexOf(']');
|
||||||
|
if (end > 1) return raw.slice(1, end);
|
||||||
|
}
|
||||||
|
return raw;
|
||||||
|
};
|
||||||
|
|
||||||
|
stats.forEach((report) => {
|
||||||
|
if (!report || report.type !== 'local-candidate') return;
|
||||||
|
bump(localCounts, report.candidateType);
|
||||||
|
if (String(report.candidateType || '').toLowerCase() === 'relay') {
|
||||||
|
const addr = String(report.ip || report.address || '');
|
||||||
|
const port = String(report.port || '');
|
||||||
|
relayLocalAddresses.add(port ? `${addr}:${port}` : addr);
|
||||||
|
const host = hostOf(report);
|
||||||
|
if (host && configuredHosts.has(host)) matchedConfiguredHosts.add(host);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
stats.forEach((report) => {
|
||||||
|
if (!report || report.type !== 'remote-candidate') return;
|
||||||
|
bump(remoteCounts, report.candidateType);
|
||||||
|
if (String(report.candidateType || '').toLowerCase() === 'relay') {
|
||||||
|
const addr = String(report.ip || report.address || '');
|
||||||
|
const port = String(report.port || '');
|
||||||
|
relayRemoteAddresses.add(port ? `${addr}:${port}` : addr);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
stats.forEach((report) => {
|
||||||
|
if (!report || report.type !== 'candidate-pair') return;
|
||||||
|
const state = String(report.state || '').toLowerCase();
|
||||||
|
const ok = report.selected === true || (report.nominated === true && state === 'succeeded') || state === 'succeeded';
|
||||||
|
if (!ok) return;
|
||||||
|
succeededPairsCount += 1;
|
||||||
|
const local = report.localCandidateId && typeof stats.get === 'function' ? stats.get(report.localCandidateId) : null;
|
||||||
|
const remote = report.remoteCandidateId && typeof stats.get === 'function' ? stats.get(report.remoteCandidateId) : null;
|
||||||
|
const lt = String(local?.candidateType || '').toLowerCase();
|
||||||
|
const rt = String(remote?.candidateType || '').toLowerCase();
|
||||||
|
if (lt === 'relay' || rt === 'relay') succeededPairsWithRelayCount += 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
localCandidatesHost: localCounts.host,
|
||||||
|
localCandidatesSrflx: localCounts.srflx,
|
||||||
|
localCandidatesRelay: localCounts.relay,
|
||||||
|
localCandidatesPrflx: localCounts.prflx,
|
||||||
|
localCandidatesOther: localCounts.other,
|
||||||
|
remoteCandidatesHost: remoteCounts.host,
|
||||||
|
remoteCandidatesSrflx: remoteCounts.srflx,
|
||||||
|
remoteCandidatesRelay: remoteCounts.relay,
|
||||||
|
remoteCandidatesPrflx: remoteCounts.prflx,
|
||||||
|
remoteCandidatesOther: remoteCounts.other,
|
||||||
|
relayLocalCandidatesFound: relayLocalAddresses.size,
|
||||||
|
relayRemoteCandidatesFound: relayRemoteAddresses.size,
|
||||||
|
relayLocalCandidatesAddresses: Array.from(relayLocalAddresses).join('|'),
|
||||||
|
relayRemoteCandidatesAddresses: Array.from(relayRemoteAddresses).join('|'),
|
||||||
|
configuredTurnHosts: Array.from(configuredHosts).join('|'),
|
||||||
|
configuredTurnHostsCount: configuredHosts.size,
|
||||||
|
reachableTurnHostsCount: matchedConfiguredHosts.size,
|
||||||
|
reachableTurnHosts: Array.from(matchedConfiguredHosts).join('|'),
|
||||||
|
turnConfiguredButNotReachedHosts: Array.from(configuredHosts).filter((host) => !matchedConfiguredHosts.has(host)).join('|'),
|
||||||
|
succeededCandidatePairsCount: succeededPairsCount,
|
||||||
|
succeededCandidatePairsWithRelayCount: succeededPairsWithRelayCount,
|
||||||
|
};
|
||||||
|
} catch {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function ensureAudioContext() {
|
function ensureAudioContext() {
|
||||||
if (audioContext) return audioContext;
|
if (audioContext) return audioContext;
|
||||||
const Ctx = window.AudioContext || window.webkitAudioContext;
|
const Ctx = window.AudioContext || window.webkitAudioContext;
|
||||||
@ -633,14 +753,23 @@ async function finalizeCall(call, {
|
|||||||
stopReconnectFlow(call);
|
stopReconnectFlow(call);
|
||||||
stopTone();
|
stopTone();
|
||||||
|
|
||||||
if (notifyRemoteHangup && call.remoteSessionId) {
|
const shouldNotifyRemoteFailure =
|
||||||
|
!notifyRemoteHangup
|
||||||
|
&& Boolean(call.remoteSessionId)
|
||||||
|
&& String(localReasonCode || '') !== 'completed'
|
||||||
|
&& String(debugReason || '') !== 'remote_hangup';
|
||||||
|
|
||||||
|
if ((notifyRemoteHangup || shouldNotifyRemoteFailure) && call.remoteSessionId) {
|
||||||
try {
|
try {
|
||||||
|
const dataValue = notifyRemoteHangup
|
||||||
|
? ''
|
||||||
|
: `setup_failed:${String(localReasonCode || 'error')}:${String(debugReason || '').slice(0, 80)}`;
|
||||||
await authService.callSignalToSession({
|
await authService.callSignalToSession({
|
||||||
toLogin: call.peerLogin,
|
toLogin: call.peerLogin,
|
||||||
targetSessionId: call.remoteSessionId,
|
targetSessionId: call.remoteSessionId,
|
||||||
callId: call.callId,
|
callId: call.callId,
|
||||||
type: TYPES.HANGUP,
|
type: TYPES.HANGUP,
|
||||||
data: '',
|
data: dataValue,
|
||||||
});
|
});
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
@ -769,6 +898,7 @@ async function ensurePeerConnection(call) {
|
|||||||
call.connectionSuccessReported = true;
|
call.connectionSuccessReported = true;
|
||||||
void (async () => {
|
void (async () => {
|
||||||
const route = await detectConnectionRoute(call);
|
const route = await detectConnectionRoute(call);
|
||||||
|
const candidateAnalytics = await collectIceCandidateAnalytics(call);
|
||||||
if (route?.label) {
|
if (route?.label) {
|
||||||
call.connectionRouteLabel = route.label;
|
call.connectionRouteLabel = route.label;
|
||||||
}
|
}
|
||||||
@ -785,6 +915,7 @@ async function ensurePeerConnection(call) {
|
|||||||
localIp: route?.localIp || '',
|
localIp: route?.localIp || '',
|
||||||
remoteIp: route?.remoteIp || '',
|
remoteIp: route?.remoteIp || '',
|
||||||
turnCandidateAddress: route?.turnCandidateAddress || '',
|
turnCandidateAddress: route?.turnCandidateAddress || '',
|
||||||
|
...candidateAnalytics,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
})();
|
})();
|
||||||
|
|||||||
@ -81,10 +81,12 @@ public final class ActiveConnectionsRegistry {
|
|||||||
String login = ctx.getLogin();
|
String login = ctx.getLogin();
|
||||||
|
|
||||||
if (sessionId != null && !sessionId.isBlank()) {
|
if (sessionId != null && !sessionId.isBlank()) {
|
||||||
ConnectionContext removed = bySessionId.remove(sessionId);
|
// Удаляем только если под ключом всё ещё лежит именно этот ctx.
|
||||||
|
// Иначе это старое соединение после re-register, и удалять новый ctx нельзя.
|
||||||
|
boolean removedCurrent = bySessionId.remove(sessionId, ctx);
|
||||||
|
|
||||||
// Если в мапе лежал другой ctx под тем же sessionId — не трогаем его byLogin
|
// Если в мапе уже другой ctx под тем же sessionId — не трогаем byLogin.
|
||||||
if (removed != null && removed != ctx) {
|
if (!removedCurrent) {
|
||||||
log.debug("remove(ctx): sessionId mapped to another ctx, skip byLogin cleanup (sessionId={})", sessionId);
|
log.debug("remove(ctx): sessionId mapped to another ctx, skip byLogin cleanup (sessionId={})", sessionId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,6 +16,8 @@ import server.logic.ws_protocol.WireCodes;
|
|||||||
import shine.db.dao.SolanaUsersDAO;
|
import shine.db.dao.SolanaUsersDAO;
|
||||||
import shine.db.entities.SolanaUserEntry;
|
import shine.db.entities.SolanaUserEntry;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
public class Net_CallSignalToSession_Handler implements JsonMessageHandler {
|
public class Net_CallSignalToSession_Handler implements JsonMessageHandler {
|
||||||
private static final ObjectMapper MAPPER = new ObjectMapper();
|
private static final ObjectMapper MAPPER = new ObjectMapper();
|
||||||
|
|
||||||
@ -44,8 +46,15 @@ public class Net_CallSignalToSession_Handler implements JsonMessageHandler {
|
|||||||
|
|
||||||
ConnectionContext targetCtx = ActiveConnectionsRegistry.getInstance().getBySessionId(targetSessionId);
|
ConnectionContext targetCtx = ActiveConnectionsRegistry.getInstance().getBySessionId(targetSessionId);
|
||||||
if (targetCtx == null || !to.equalsIgnoreCase(targetCtx.getLogin())) {
|
if (targetCtx == null || !to.equalsIgnoreCase(targetCtx.getLogin())) {
|
||||||
|
// Fallback: если точной сессии уже нет (переподключение), но у пользователя ровно 1 активная сессия,
|
||||||
|
// отправляем в неё, чтобы не ронять звонок из-за устаревшего targetSessionId.
|
||||||
|
Set<ConnectionContext> activeForLogin = ActiveConnectionsRegistry.getInstance().getByLogin(to);
|
||||||
|
if (activeForLogin.size() == 1) {
|
||||||
|
targetCtx = activeForLogin.iterator().next();
|
||||||
|
} else {
|
||||||
return NetExceptionResponseFactory.error(req, 404, "SESSION_NOT_FOUND", "Целевая сессия не найдена");
|
return NetExceptionResponseFactory.error(req, 404, "SESSION_NOT_FOUND", "Целевая сессия не найдена");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
String eventId = NetIdGenerator.eventId("evt");
|
String eventId = NetIdGenerator.eventId("evt");
|
||||||
ObjectNode payload = MAPPER.createObjectNode();
|
ObjectNode payload = MAPPER.createObjectNode();
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user