Defer peer negotiation until post-media setup and queue early signals
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
// Maintainer-controlled web client version.
|
// Maintainer-controlled web client version.
|
||||||
// Format: YYYY.MM.DD Rn (example: 2026.02.20 R2)
|
// Format: YYYY.MM.DD Rn (example: 2026.02.20 R2)
|
||||||
window.CHGRID_WEB_VERSION = "2026.02.25 R266";
|
window.CHGRID_WEB_VERSION = "2026.02.25 R267";
|
||||||
// Optional display timezone for timestamps. Falls back to America/Detroit if unset/invalid.
|
// Optional display timezone for timestamps. Falls back to America/Detroit if unset/invalid.
|
||||||
window.CHGRID_TIME_ZONE = "America/Detroit";
|
window.CHGRID_TIME_ZONE = "America/Detroit";
|
||||||
|
|||||||
@@ -257,6 +257,8 @@ let heartbeatAwaitingPong = false;
|
|||||||
let reconnectInFlight = false;
|
let reconnectInFlight = false;
|
||||||
let activeServerInstanceId: string | null = null;
|
let activeServerInstanceId: string | null = null;
|
||||||
let reloadScheduledForVersionMismatch = false;
|
let reloadScheduledForVersionMismatch = false;
|
||||||
|
let peerNegotiationReady = false;
|
||||||
|
let pendingSignalMessages: Array<Extract<IncomingMessage, { type: 'signal' }>> = [];
|
||||||
let peerListenGainByNickname = settings.loadPeerListenGains();
|
let peerListenGainByNickname = settings.loadPeerListenGains();
|
||||||
let audioLayers: AudioLayerState = {
|
let audioLayers: AudioLayerState = {
|
||||||
voice: true,
|
voice: true,
|
||||||
@@ -1571,9 +1573,27 @@ function disconnect(): void {
|
|||||||
lastSubscriptionRefreshTileY = Math.round(state.player.y);
|
lastSubscriptionRefreshTileY = Math.round(state.player.y);
|
||||||
stopTeleportLoopAudio();
|
stopTeleportLoopAudio();
|
||||||
activeTeleport = null;
|
activeTeleport = null;
|
||||||
|
peerNegotiationReady = false;
|
||||||
|
pendingSignalMessages = [];
|
||||||
itemBehaviorRegistry.cleanup();
|
itemBehaviorRegistry.cleanup();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Starts peer negotiation only after welcome + media setup sequencing is complete. */
|
||||||
|
async function activatePeerNegotiation(): Promise<void> {
|
||||||
|
if (!state.running) return;
|
||||||
|
if (peerNegotiationReady) return;
|
||||||
|
peerNegotiationReady = true;
|
||||||
|
for (const peer of state.peers.values()) {
|
||||||
|
await peerManager.createOrGetPeer(peer.id, true, peer);
|
||||||
|
}
|
||||||
|
if (pendingSignalMessages.length === 0) return;
|
||||||
|
const queued = pendingSignalMessages;
|
||||||
|
pendingSignalMessages = [];
|
||||||
|
for (const signal of queued) {
|
||||||
|
await onAppMessage(signal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const onAppMessage = createOnMessageHandler({
|
const onAppMessage = createOnMessageHandler({
|
||||||
getWorldGridSize: () => worldGridSize,
|
getWorldGridSize: () => worldGridSize,
|
||||||
setWorldGridSize: (size) => {
|
setWorldGridSize: (size) => {
|
||||||
@@ -1640,6 +1660,13 @@ const onAppMessage = createOnMessageHandler({
|
|||||||
},
|
},
|
||||||
handleAuthRequired,
|
handleAuthRequired,
|
||||||
handleAuthResult,
|
handleAuthResult,
|
||||||
|
isPeerNegotiationReady: () => peerNegotiationReady,
|
||||||
|
enqueuePendingSignal: (message) => {
|
||||||
|
pendingSignalMessages.push(message);
|
||||||
|
if (pendingSignalMessages.length > 500) {
|
||||||
|
pendingSignalMessages.splice(0, pendingSignalMessages.length - 500);
|
||||||
|
}
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
/** Handles signaling packets with heartbeat/restart metadata before app-level dispatch. */
|
/** Handles signaling packets with heartbeat/restart metadata before app-level dispatch. */
|
||||||
@@ -1701,12 +1728,14 @@ async function setupMediaAfterAuth(): Promise<void> {
|
|||||||
const canProceed = await checkMicPermission();
|
const canProceed = await checkMicPermission();
|
||||||
if (!canProceed) {
|
if (!canProceed) {
|
||||||
setConnectionStatus('Microphone access is required.');
|
setConnectionStatus('Microphone access is required.');
|
||||||
|
await activatePeerNegotiation();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await populateAudioDevices();
|
await populateAudioDevices();
|
||||||
if (dom.audioInputSelect.options.length === 0) {
|
if (dom.audioInputSelect.options.length === 0) {
|
||||||
setConnectionStatus('No audio input device found. Open Audio setup or connect a microphone.');
|
setConnectionStatus('No audio input device found. Open Audio setup or connect a microphone.');
|
||||||
|
await activatePeerNegotiation();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const inputDeviceId = dom.audioInputSelect.value || mediaSession.getPreferredInputDeviceId();
|
const inputDeviceId = dom.audioInputSelect.value || mediaSession.getPreferredInputDeviceId();
|
||||||
@@ -1714,6 +1743,8 @@ async function setupMediaAfterAuth(): Promise<void> {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
setConnectionStatus(describeMediaError(error));
|
setConnectionStatus(describeMediaError(error));
|
||||||
|
} finally {
|
||||||
|
await activatePeerNegotiation();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -71,6 +71,8 @@ type MessageHandlerDeps = {
|
|||||||
playIncomingItemUseSound: (url: string, x: number, y: number) => void;
|
playIncomingItemUseSound: (url: string, x: number, y: number) => void;
|
||||||
handleAuthRequired: (message: Extract<IncomingMessage, { type: 'auth_required' }>) => void;
|
handleAuthRequired: (message: Extract<IncomingMessage, { type: 'auth_required' }>) => void;
|
||||||
handleAuthResult: (message: Extract<IncomingMessage, { type: 'auth_result' }>) => Promise<void>;
|
handleAuthResult: (message: Extract<IncomingMessage, { type: 'auth_result' }>) => Promise<void>;
|
||||||
|
isPeerNegotiationReady: () => boolean;
|
||||||
|
enqueuePendingSignal: (message: Extract<IncomingMessage, { type: 'signal' }>) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -117,7 +119,6 @@ export function createOnMessageHandler(deps: MessageHandlerDeps): (message: Inco
|
|||||||
|
|
||||||
for (const user of message.users) {
|
for (const user of message.users) {
|
||||||
deps.state.peers.set(user.id, { ...user });
|
deps.state.peers.set(user.id, { ...user });
|
||||||
await deps.peerManager.createOrGetPeer(user.id, true, user);
|
|
||||||
}
|
}
|
||||||
deps.state.items.clear();
|
deps.state.items.clear();
|
||||||
for (const item of message.items || []) {
|
for (const item of message.items || []) {
|
||||||
@@ -132,6 +133,18 @@ export function createOnMessageHandler(deps: MessageHandlerDeps): (message: Inco
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 'signal': {
|
case 'signal': {
|
||||||
|
if (!deps.isPeerNegotiationReady()) {
|
||||||
|
deps.enqueuePendingSignal(message);
|
||||||
|
if (!deps.state.peers.has(message.senderId)) {
|
||||||
|
deps.state.peers.set(message.senderId, {
|
||||||
|
id: message.senderId,
|
||||||
|
nickname: deps.sanitizeName(message.senderNickname || 'user...') || 'user...',
|
||||||
|
x: Number.isFinite(message.x) ? message.x : 20,
|
||||||
|
y: Number.isFinite(message.y) ? message.y : 20,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
const peer = await deps.peerManager.handleSignal(message);
|
const peer = await deps.peerManager.handleSignal(message);
|
||||||
if (!deps.state.peers.has(peer.id)) {
|
if (!deps.state.peers.has(peer.id)) {
|
||||||
deps.state.peers.set(peer.id, {
|
deps.state.peers.set(peer.id, {
|
||||||
|
|||||||
Reference in New Issue
Block a user