Defer peer negotiation until post-media setup and queue early signals

This commit is contained in:
Jage9
2026-02-25 02:40:33 -05:00
parent 4bef1eddb8
commit 97ed7b4b61
3 changed files with 46 additions and 2 deletions

View File

@@ -1,5 +1,5 @@
// Maintainer-controlled web client version.
// 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.
window.CHGRID_TIME_ZONE = "America/Detroit";

View File

@@ -257,6 +257,8 @@ let heartbeatAwaitingPong = false;
let reconnectInFlight = false;
let activeServerInstanceId: string | null = null;
let reloadScheduledForVersionMismatch = false;
let peerNegotiationReady = false;
let pendingSignalMessages: Array<Extract<IncomingMessage, { type: 'signal' }>> = [];
let peerListenGainByNickname = settings.loadPeerListenGains();
let audioLayers: AudioLayerState = {
voice: true,
@@ -1571,9 +1573,27 @@ function disconnect(): void {
lastSubscriptionRefreshTileY = Math.round(state.player.y);
stopTeleportLoopAudio();
activeTeleport = null;
peerNegotiationReady = false;
pendingSignalMessages = [];
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({
getWorldGridSize: () => worldGridSize,
setWorldGridSize: (size) => {
@@ -1640,6 +1660,13 @@ const onAppMessage = createOnMessageHandler({
},
handleAuthRequired,
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. */
@@ -1701,12 +1728,14 @@ async function setupMediaAfterAuth(): Promise<void> {
const canProceed = await checkMicPermission();
if (!canProceed) {
setConnectionStatus('Microphone access is required.');
await activatePeerNegotiation();
return;
}
try {
await populateAudioDevices();
if (dom.audioInputSelect.options.length === 0) {
setConnectionStatus('No audio input device found. Open Audio setup or connect a microphone.');
await activatePeerNegotiation();
return;
}
const inputDeviceId = dom.audioInputSelect.value || mediaSession.getPreferredInputDeviceId();
@@ -1714,6 +1743,8 @@ async function setupMediaAfterAuth(): Promise<void> {
} catch (error) {
console.error(error);
setConnectionStatus(describeMediaError(error));
} finally {
await activatePeerNegotiation();
}
}

View File

@@ -71,6 +71,8 @@ type MessageHandlerDeps = {
playIncomingItemUseSound: (url: string, x: number, y: number) => void;
handleAuthRequired: (message: Extract<IncomingMessage, { type: 'auth_required' }>) => 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) {
deps.state.peers.set(user.id, { ...user });
await deps.peerManager.createOrGetPeer(user.id, true, user);
}
deps.state.items.clear();
for (const item of message.items || []) {
@@ -132,6 +133,18 @@ export function createOnMessageHandler(deps: MessageHandlerDeps): (message: Inco
break;
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);
if (!deps.state.peers.has(peer.id)) {
deps.state.peers.set(peer.id, {