mirror of
https://github.com/tiennm99/noitu.git
synced 2026-10-11 03:13:45 +00:00
A /rules page says, in one place, what nothing in the app said before: the chain rule, the clock, what a dead end costs, elimination and the last player standing, how a word is scored and the reconnect window. Linked from the landing page and from the board and lobby headers. The chat control moves above the chain as a pill with the unread count, where it can be reached on a phone mid-game, and the lobby's folded chat now carries an unread badge too. ESLint with the Svelte and JSDoc plugins, run in CI; the real findings it turned up (missing each keys, untyped timer handles) are fixed.
91 lines
2.7 KiB
JavaScript
91 lines
2.7 KiB
JavaScript
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<typeof createClient> | 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 };
|