Fix reconnect/media failure paths and harden signaling parse

This commit is contained in:
Jage9
2026-02-20 16:30:54 -05:00
parent b246c9a7fd
commit 76a5c1186a
4 changed files with 38 additions and 17 deletions

View File

@@ -1,3 +1,3 @@
// Maintainer-controlled web client version.
// Format: YYYY.MM.DD Rn (example: 2026.02.20 R2)
window.CHGRID_WEB_VERSION = "2026.02.20 R57";
window.CHGRID_WEB_VERSION = "2026.02.20 R58";

View File

@@ -577,9 +577,7 @@ async function checkMicPermission(): Promise<boolean> {
}
async function setupLocalMedia(audioDeviceId = ''): Promise<void> {
if (localStream) {
localStream.getTracks().forEach((track) => track.stop());
}
stopLocalMedia();
await audio.ensureContext();
@@ -604,6 +602,14 @@ async function setupLocalMedia(audioDeviceId = ''): Promise<void> {
await peerManager.replaceOutgoingTrack(outboundStream);
}
function stopLocalMedia(): void {
if (localStream) {
localStream.getTracks().forEach((track) => track.stop());
localStream = null;
}
outboundStream = null;
}
function describeMediaError(error: unknown): string {
if (error instanceof DOMException) {
if (error.name === 'NotAllowedError') return 'Microphone blocked. Allow mic access in browser site settings.';
@@ -638,14 +644,23 @@ async function connect(): Promise<void> {
return;
}
state.player.x = Math.floor(Math.random() * GRID_SIZE);
state.player.y = Math.floor(Math.random() * GRID_SIZE);
const storedPosition = localStorage.getItem('spatialChatPosition');
if (storedPosition) {
const parsed = JSON.parse(storedPosition) as { x: number; y: number };
state.player.x = parsed.x;
state.player.y = parsed.y;
} else {
state.player.x = Math.floor(Math.random() * GRID_SIZE);
state.player.y = Math.floor(Math.random() * GRID_SIZE);
try {
const parsed = JSON.parse(storedPosition) as { x?: number; y?: number };
if (Number.isFinite(parsed.x) && Number.isFinite(parsed.y)) {
const x = Math.floor(parsed.x as number);
const y = Math.floor(parsed.y as number);
if (x >= 0 && x < GRID_SIZE && y >= 0 && y < GRID_SIZE) {
state.player.x = x;
state.player.y = y;
}
}
} catch {
// Ignore malformed saved positions and keep randomized defaults.
}
}
try {
@@ -670,6 +685,7 @@ async function connect(): Promise<void> {
await signaling.connect(onMessage);
} catch (error) {
console.error(error);
stopLocalMedia();
updateStatus('Connect failed. Signaling server may be offline or unreachable.');
connecting = false;
updateConnectAvailability();
@@ -686,11 +702,7 @@ function disconnect(): void {
}
signaling.disconnect();
if (localStream) {
localStream.getTracks().forEach((track) => track.stop());
localStream = null;
}
outboundStream = null;
stopLocalMedia();
peerManager.cleanupAll();
cleanupAllRadioRuntimes();
@@ -1627,8 +1639,9 @@ async function populateAudioDevices(): Promise<void> {
return;
}
let temporaryStream: MediaStream | null = null;
try {
await navigator.mediaDevices.getUserMedia({ audio: true });
temporaryStream = await navigator.mediaDevices.getUserMedia({ audio: true });
const devices = await navigator.mediaDevices.enumerateDevices();
dom.audioInputSelect.innerHTML = '';
@@ -1665,6 +1678,8 @@ async function populateAudioDevices(): Promise<void> {
updateDeviceSummary();
} catch {
updateStatus('Could not list devices.');
} finally {
temporaryStream?.getTracks().forEach((track) => track.stop());
}
}

View File

@@ -38,7 +38,12 @@ export class SignalingClient {
};
this.ws.onmessage = async (event) => {
const parsed = JSON.parse(String(event.data));
let parsed: unknown;
try {
parsed = JSON.parse(String(event.data));
} catch {
return;
}
const validated = incomingMessageSchema.safeParse(parsed);
if (!validated.success) return;
await onMessage(validated.data);