import { Status, createClient, hasStoredSession } from './client.js'; import { game } from '$lib/stores/game.svelte.js'; import { settings } from '$lib/stores/settings.svelte.js'; /** * One socket for the whole app. * * The client itself is framework-agnostic and injectable, which is what makes * it testable; this module is the small reactive shell that binds that one * instance to the two stores and to the component tree. */ const state = $state({ status: Status.CLOSED }); /** @type {ReturnType | null} */ let client = null; /** Opens the socket if it is not already open. Safe to call from any route. */ export function connect() { if (client) return; client = createClient({ nickname: () => settings.state.nickname, onMessage: (msg) => game.apply(msg), onStatus: (status) => { state.status = status; } }); client.connect(); } /** * Sends a message, reporting whether it actually went out. * * Deliberately does not open the socket: a caller that has not connected yet * has nothing queued to resume, and auto-connecting here would reopen the * connection during teardown. * @param {any} msg - a ClientMessage * @returns {boolean} */ export function send(msg) { return client?.send(msg) ?? false; } /** * Retries the connection immediately instead of waiting out the backoff. * * For the player looking at a "mất kết nối" banner with a turn timer running: * the schedule is tuned for a client nobody is watching, and this is the case * where somebody is. * @returns {boolean} whether an attempt was actually started */ export function reconnectNow() { return client?.reconnectNow() ?? false; } /** * The server's clock as this client estimates it. The countdown is drawn * against this rather than Date.now(), so a device with a wrong clock still * shows the right remaining time. */ export function serverNow() { return client?.serverNow() ?? Date.now(); } /** * Drops the resume token without touching the connection. * * Used when a resume was refused: the token is spent, but the socket is fine * and the player may well want to start something new on it. */ export function forgetSession() { client?.forgetSession(); } /** * Closes the socket and forgets the session. * * Called when the player leaves the board. Keeping the socket open across * routes would mean the next game is announced to the server under whatever * nickname the previous `Hello` carried, and the resume token would offer the * abandoned seat back to a connection that no longer wants it. */ export function disconnect() { client?.forgetSession(); client?.close(); client = null; state.status = Status.CLOSED; } export const connection = state; export { Status, hasStoredSession };