openvibers/openvibe.games Commits
Client: the first connect gives up after 10 s, and a refused or timed-out connect waits for the server and reloads, as a dropped connection already did (WS-P task 3 game-shard proof: an upgrade sent during a restart can wait with no answer, so a page opened in that 2 s gap stayed on "connecting…")
Changes
@@@ -369,12 +369,8 @@ async function start(): Promise<void> {}}}-- connection.onClose = () => {-- if (rejected) return-- // Deploys restart the server; come back on our own instead of-- // freezing on a dead socket. Reload = full clean resync.-- hud.setStatus('connection lost — reconnecting automatically…')-- hud.toast('Disconnected — reconnecting…', true)++ // Deploys restart the server: wait until it answers /healthz, then reload (a full clean resync).++ const reloadWhenServerIsBack = (): void => {const poll = setInterval(() => {fetch('/healthz').then((r) => {@@@ -386,12 +382,21 @@ async function start(): Promise<void> {.catch(() => {})}, 2000)}++ connection.onClose = () => {++ if (rejected) return++ // Come back on our own instead of freezing on a dead socket.++ hud.setStatus('connection lost — reconnecting automatically…')++ hud.toast('Disconnected — reconnecting…', true)++ reloadWhenServerIsBack()++ }hud.setStatus('connecting…')try {await connection.connect(gameSocketUrl(), identity.token, name, appearance, slot, sso)} catch {-- hud.setStatus('could not reach server — is it running?')++ // Refused or timed out (a page opened during a restart): wait for the server, then reload.++ hud.setStatus('could not reach the server — retrying automatically…')++ reloadWhenServerIsBack()return}@@@ -7,6 +7,9 @@ import {type ServerMessage,} from '@openvibe/protocol'++/** How long the first WebSocket connect may take before the page falls back to waiting for the server. */++export const CONNECT_TIMEOUT_MS = 10_000++/*** WebSocket connection to the dedicated server. Message handling is a* callback so the network layer stays independent of game/state code.@@@ -27,8 +30,14 @@ export class Connection {const ws = new WebSocket(url)this.ws = wsawait new Promise<void>((resolve, reject) => {-- ws.onopen = () => resolve()-- ws.onerror = () => reject(new Error('connection failed'))++ // An upgrade sent while the server restarts can wait with no answer (the proxy holds it):++ // give up after CONNECT_TIMEOUT_MS so the page can wait for the server and reload instead.++ const timer = setTimeout(() => {++ try { ws.close() } catch { /* already closing */ }++ reject(new Error('connection timed out'))++ }, CONNECT_TIMEOUT_MS)++ ws.onopen = () => { clearTimeout(timer); resolve() }++ ws.onerror = () => { clearTimeout(timer); reject(new Error('connection failed')) }})ws.onmessage = (event) => {const msg = decodeServerMessage(String(event.data))
@@ -369,12 +369,8 @@ async function start(): Promise<void> {
@@ -369,12 +369,8 @@ async function start(): Promise<void> {
}
}
}
}
}
}
- connection.onClose = () => {
- if (rejected) return
- // Deploys restart the server; come back on our own instead of
- // freezing on a dead socket. Reload = full clean resync.
- hud.setStatus('connection lost — reconnecting automatically…')
- hud.toast('Disconnected — reconnecting…', true)
+ // Deploys restart the server: wait until it answers /healthz, then reload (a full clean resync).
+ const reloadWhenServerIsBack = (): void => {
const poll = setInterval(() => {
const poll = setInterval(() => {
fetch('/healthz')
fetch('/healthz')
.then((r) => {
.then((r) => {
@@ -386,12 +382,21 @@ async function start(): Promise<void> {
@@ -386,12 +382,21 @@ async function start(): Promise<void> {
.catch(() => {})
.catch(() => {})
}, 2000)
}, 2000)
}
}
+ connection.onClose = () => {
+ if (rejected) return
+ // Come back on our own instead of freezing on a dead socket.
+ hud.setStatus('connection lost — reconnecting automatically…')
+ hud.toast('Disconnected — reconnecting…', true)
+ reloadWhenServerIsBack()
+ }
hud.setStatus('connecting…')
hud.setStatus('connecting…')
try {
try {
await connection.connect(gameSocketUrl(), identity.token, name, appearance, slot, sso)
await connection.connect(gameSocketUrl(), identity.token, name, appearance, slot, sso)
} catch {
} catch {
- hud.setStatus('could not reach server — is it running?')
+ // Refused or timed out (a page opened during a restart): wait for the server, then reload.
+ hud.setStatus('could not reach the server — retrying automatically…')
+ reloadWhenServerIsBack()
return
return
}
}
@@ -7,6 +7,9 @@ import {
@@ -7,6 +7,9 @@ import {
type ServerMessage,
type ServerMessage,
} from '@openvibe/protocol'
} from '@openvibe/protocol'
+/** How long the first WebSocket connect may take before the page falls back to waiting for the server. */
+export const CONNECT_TIMEOUT_MS = 10_000
+
/**
/**
* WebSocket connection to the dedicated server. Message handling is a
* WebSocket connection to the dedicated server. Message handling is a
* callback so the network layer stays independent of game/state code.
* callback so the network layer stays independent of game/state code.
@@ -27,8 +30,14 @@ export class Connection {
@@ -27,8 +30,14 @@ export class Connection {
const ws = new WebSocket(url)
const ws = new WebSocket(url)
this.ws = ws
this.ws = ws
await new Promise<void>((resolve, reject) => {
await new Promise<void>((resolve, reject) => {
- ws.onopen = () => resolve()
- ws.onerror = () => reject(new Error('connection failed'))
+ // An upgrade sent while the server restarts can wait with no answer (the proxy holds it):
+ // give up after CONNECT_TIMEOUT_MS so the page can wait for the server and reload instead.
+ const timer = setTimeout(() => {
+ try { ws.close() } catch { /* already closing */ }
+ reject(new Error('connection timed out'))
+ }, CONNECT_TIMEOUT_MS)
+ ws.onopen = () => { clearTimeout(timer); resolve() }
+ ws.onerror = () => { clearTimeout(timer); reject(new Error('connection failed')) }
})
})
ws.onmessage = (event) => {
ws.onmessage = (event) => {
const msg = decodeServerMessage(String(event.data))
const msg = decodeServerMessage(String(event.data))