bcd5a1d02d
The additive groundwork for the native offline-first experience (ANDROID_PLAN.md §D). Inert until the native cold-boot lands: on the web these paths never fire, so web / VK / Telegram behaviour is unchanged. - ui/scripts/bundle-dicts.mjs copies the scrabble-dictionary release DAWGs into dist/dict/<variant>@<version>.dawg for the native pipeline (run after `pnpm build`, before `cap sync`). - The dict loader gains a bundled tier between the IndexedDB and network tiers, attempted only on a native channel (a packaged app serves ./dict/<key>.dawg from its assets). Native-gated rather than the plan's "404 on the web" because the relative path would otherwise hit the gateway's own session-gated /dict/ route. - __DICT_VERSION__ Vite define (from VITE_DICT_VERSION, default "dev") + its ambient declaration; the offline vs_ai / hotseat creates in NewGame fall back to it when a device-local guest has no profile-advertised version. - New lib/localguest.ts: a persisted device-local guest id (no DB row); the offline vs_ai human seat uses it (and the localized common.guest name) when there is no server session. svelte-check clean, vitest green, native + web builds clean. The cold-boot rewrite, reconciliation, the Profile soft-sign-in gating and the offline-first e2e follow.
47 lines
2.1 KiB
TypeScript
47 lines
2.1 KiB
TypeScript
// The device-local play identity: a persisted id that exists from the very first launch with no
|
|
// network and no DB row. It fills the human seat's accountId in a local vs_ai game when there is no
|
|
// server session yet (so the seat is recognised as "you" across reloads); hotseat seats keep their
|
|
// own independent local identities. A purely-offline user never mints a server guest and consumes no
|
|
// server resources — when the app first reaches the network the reconciliation (app.svelte.ts) mints
|
|
// a server guest and adopts its session, but the local games created under this id stay device-only.
|
|
//
|
|
// The display name is not persisted here — it is a UI concern the caller localises (t('common.guest')),
|
|
// so this module stays free of i18n and unit-tests in the node env alongside the other localgame libs.
|
|
|
|
const ID_KEY = 'scrabble.localGuestId';
|
|
|
|
/** LOCAL_GUEST_PREFIX marks an account id as a device-local guest (no DB row). */
|
|
export const LOCAL_GUEST_PREFIX = 'localguest:';
|
|
|
|
function mintId(): string {
|
|
// No crypto API, so it also works on the older engines the SPA still supports (mirrors
|
|
// localgame/id.ts newLocalGameId).
|
|
return `${LOCAL_GUEST_PREFIX}${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
|
|
}
|
|
|
|
/**
|
|
* localGuestId returns the persisted device-local guest id, minting and storing one on first use.
|
|
* Best-effort persistence: where storage is unavailable it returns a fresh ephemeral id each call (a
|
|
* device that cannot persist still plays, it just does not carry a stable local identity across
|
|
* reloads).
|
|
*/
|
|
export function localGuestId(): string {
|
|
try {
|
|
if (typeof localStorage !== 'undefined') {
|
|
const existing = localStorage.getItem(ID_KEY);
|
|
if (existing) return existing;
|
|
const minted = mintId();
|
|
localStorage.setItem(ID_KEY, minted);
|
|
return minted;
|
|
}
|
|
} catch {
|
|
/* storage unavailable — fall through to an ephemeral id */
|
|
}
|
|
return mintId();
|
|
}
|
|
|
|
/** isLocalGuestId reports whether an account id is a device-local guest id. */
|
|
export function isLocalGuestId(id: string): boolean {
|
|
return id.startsWith(LOCAL_GUEST_PREFIX);
|
|
}
|