@diffenderfer-games/hub 2.0.0-next.0 → 2.0.0-next.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +163 -0
- package/LICENSE +21 -21
- package/README.md +203 -139
- package/dist/auto.d.ts +8 -8
- package/dist/auto.js +3 -3
- package/dist/avatar-pixi.d.ts +165 -0
- package/dist/avatar-pixi.d.ts.map +1 -0
- package/dist/avatar-pixi.js +299 -0
- package/dist/avatar-pixi.js.map +1 -0
- package/dist/avatar-svg.d.ts +4 -0
- package/dist/avatar-svg.js +5 -0
- package/dist/avatar-three.d.ts +69 -0
- package/dist/avatar-three.d.ts.map +1 -0
- package/dist/avatar-three.js +49 -0
- package/dist/avatar-three.js.map +1 -0
- package/dist/avatar.d.ts +7 -0
- package/dist/avatar.js +500 -0
- package/dist/avatar.js.map +1 -0
- package/dist/daily.d.ts +2 -2
- package/dist/doctor/cli.mjs +119 -28
- package/dist/doctor/cli.mjs.map +1 -1
- package/dist/index.d.ts +8 -8
- package/dist/index.js +3 -3
- package/dist/input.d.ts +2 -2
- package/dist/input.js +2 -2
- package/dist/mp.d.ts +3 -3
- package/dist/race.d.ts +1 -1
- package/dist/rooms.d.ts +3 -3
- package/dist/rooms.js +1 -1
- package/dist/shared/animator.d.ts +104 -0
- package/dist/shared/animator.d.ts.map +1 -0
- package/dist/shared/art.js +1946 -0
- package/dist/shared/art.js.map +1 -0
- package/dist/shared/atlas.d.ts +138 -0
- package/dist/shared/atlas.d.ts.map +1 -0
- package/dist/shared/avatar.d.ts +123 -0
- package/dist/shared/avatar.d.ts.map +1 -0
- package/dist/shared/bridge.js +18 -4
- package/dist/shared/bridge.js.map +1 -1
- package/dist/shared/client-version.gen.js +1 -1
- package/dist/shared/daily.d.ts +145 -19
- package/dist/shared/daily.d.ts.map +1 -1
- package/dist/shared/daily.js +138 -25
- package/dist/shared/daily.js.map +1 -1
- package/dist/shared/defineProperty.js +39 -0
- package/dist/shared/describe.d.ts +32 -0
- package/dist/shared/describe.d.ts.map +1 -0
- package/dist/shared/describe.js +109 -0
- package/dist/shared/describe.js.map +1 -0
- package/dist/shared/globals.js +9 -42
- package/dist/shared/globals.js.map +1 -1
- package/dist/shared/index.d.ts +1122 -46
- package/dist/shared/index.d.ts.map +1 -1
- package/dist/shared/input.d.ts +74 -14
- package/dist/shared/input.d.ts.map +1 -1
- package/dist/shared/input.js +179 -25
- package/dist/shared/input.js.map +1 -1
- package/dist/shared/json-guards.js +48 -0
- package/dist/shared/json-guards.js.map +1 -0
- package/dist/shared/latest-state.js +1 -1
- package/dist/shared/live-canvas.js +62 -0
- package/dist/shared/live-canvas.js.map +1 -0
- package/dist/shared/mp.js +10 -2
- package/dist/shared/mp.js.map +1 -1
- package/dist/shared/multiplayer-types.d.ts +15 -2
- package/dist/shared/multiplayer-types.d.ts.map +1 -1
- package/dist/shared/overlays.js +800 -0
- package/dist/shared/overlays.js.map +1 -0
- package/dist/shared/page-stack.js +2 -1
- package/dist/shared/page-stack.js.map +1 -1
- package/dist/shared/pose-key.js +249 -0
- package/dist/shared/pose-key.js.map +1 -0
- package/dist/shared/race-types.d.ts +1 -1
- package/dist/shared/race.js +1 -1
- package/dist/shared/room-types.d.ts +28 -3
- package/dist/shared/room-types.d.ts.map +1 -1
- package/dist/shared/rooms.d.ts.map +1 -1
- package/dist/shared/rooms.js +23 -4
- package/dist/shared/rooms.js.map +1 -1
- package/dist/shared/runtime-opener.js +1 -1
- package/dist/shared/scene.d.ts +90 -0
- package/dist/shared/scene.d.ts.map +1 -0
- package/dist/shared/social.d.ts +90 -9
- package/dist/shared/social.d.ts.map +1 -1
- package/dist/shared/social.js +6 -1
- package/dist/shared/social.js.map +1 -1
- package/dist/shared/src.js +4563 -667
- package/dist/shared/src.js.map +1 -1
- package/dist/shared/svg-markup.js +2354 -0
- package/dist/shared/svg-markup.js.map +1 -0
- package/dist/shared/svg.js +27 -0
- package/dist/shared/svg.js.map +1 -0
- package/dist/shared/types.d.ts +174 -0
- package/dist/shared/types.d.ts.map +1 -0
- package/dist/shared/wire-types.d.ts +6 -2
- package/dist/shared/wire-types.d.ts.map +1 -1
- package/dist/social.d.ts +5 -5
- package/dist/social.js +2 -2
- package/dist/turns-rules.d.ts +2 -0
- package/dist/turns-rules.js +1 -0
- package/dist/turns.d.ts +61 -0
- package/dist/turns.d.ts.map +1 -0
- package/dist/turns.js +183 -0
- package/dist/turns.js.map +1 -0
- package/package.json +125 -89
- package/src/auto.ts +39 -39
- package/src/avatar/adapters/pixi-avatar.ts +73 -0
- package/src/avatar/adapters/pixi-crowd.ts +329 -0
- package/src/avatar/adapters/pixi-types.ts +84 -0
- package/src/avatar/adapters/three.ts +126 -0
- package/src/avatar/animator/animator.ts +285 -0
- package/src/avatar/animator/reactions.ts +44 -0
- package/src/avatar/animator/schedule.ts +73 -0
- package/src/avatar/art/beard.ts +49 -0
- package/src/avatar/art/glasses.ts +67 -0
- package/src/avatar/art/hair.ts +254 -0
- package/src/avatar/art/headwear.ts +75 -0
- package/src/avatar/art/index.ts +21 -0
- package/src/avatar/art/shape.ts +87 -0
- package/src/avatar/art-store.ts +36 -0
- package/src/avatar/atlas.ts +246 -0
- package/src/avatar/catalog-view.ts +14 -0
- package/src/avatar/colour.ts +109 -0
- package/src/avatar/describe.ts +79 -0
- package/src/avatar/face.ts +187 -0
- package/src/avatar/geom.ts +173 -0
- package/src/avatar/layout.ts +47 -0
- package/src/avatar/look-like.ts +52 -0
- package/src/avatar/outputs/bitmap.ts +75 -0
- package/src/avatar/outputs/canvas-paint.ts +177 -0
- package/src/avatar/outputs/canvas.ts +62 -0
- package/src/avatar/outputs/custom-element.ts +140 -0
- package/src/avatar/outputs/element.ts +58 -0
- package/src/avatar/outputs/live-canvas.ts +57 -0
- package/src/avatar/outputs/overlay-svg.ts +22 -0
- package/src/avatar/outputs/pose-key.ts +30 -0
- package/src/avatar/outputs/svg-markup.ts +113 -0
- package/src/avatar/outputs/svg.ts +41 -0
- package/src/avatar/overlays.ts +128 -0
- package/src/avatar/render/body.ts +116 -0
- package/src/avatar/render/colours.ts +81 -0
- package/src/avatar/render/compose.ts +320 -0
- package/src/avatar/render/context.ts +117 -0
- package/src/avatar/render/eyes.ts +78 -0
- package/src/avatar/render/eyewear.ts +118 -0
- package/src/avatar/render/frame.ts +73 -0
- package/src/avatar/render/head.ts +81 -0
- package/src/avatar/render/mouth.ts +86 -0
- package/src/avatar/render/props.ts +91 -0
- package/src/avatar/render/types.ts +93 -0
- package/src/avatar/render/walker-parts.ts +121 -0
- package/src/avatar/render/walker.ts +199 -0
- package/src/avatar/scene.ts +119 -0
- package/src/avatar-pixi.ts +20 -0
- package/src/avatar-svg.ts +21 -0
- package/src/avatar-three.ts +18 -0
- package/src/avatar.ts +63 -0
- package/src/core/avatar/avatar-cache.ts +95 -0
- package/src/core/avatar/avatar-ref.ts +34 -0
- package/src/core/avatar/avatar-types.ts +43 -0
- package/src/core/avatar/hub-avatar.ts +308 -0
- package/src/core/connection.ts +408 -299
- package/src/core/errors.ts +160 -147
- package/src/core/game-data.ts +40 -38
- package/src/core/hub-economy.ts +216 -179
- package/src/core/hub-progress.ts +269 -205
- package/src/core/hub.ts +352 -131
- package/src/core/menu-control.ts +61 -61
- package/src/core/offline/cloud-saves.ts +62 -0
- package/src/core/offline/events.ts +50 -50
- package/src/core/offline/favorites.ts +21 -21
- package/src/core/offline/hub-kinds/avatar-kind.ts +42 -0
- package/src/core/offline/hub-kinds/daily-kind.ts +68 -0
- package/src/core/offline/hub-kinds/economy-kinds.ts +86 -0
- package/src/core/offline/hub-kinds/game-fields.ts +2 -0
- package/src/core/offline/hub-kinds/index.ts +52 -0
- package/src/core/offline/hub-kinds/legacy-ops.ts +37 -0
- package/src/core/offline/hub-kinds/progress-kinds.ts +165 -0
- package/src/core/offline/hub-kinds/resume-kind.ts +108 -0
- package/src/core/offline/hub-kinds/saved-kind.ts +93 -0
- package/src/core/offline/kind-adapters.ts +48 -0
- package/src/core/offline/local-inventory.ts +64 -0
- package/src/core/offline/local-store.ts +304 -261
- package/src/core/offline/offline-api.ts +65 -0
- package/src/core/offline/outbox-entry.ts +59 -0
- package/src/core/offline/outbox-kinds.ts +78 -0
- package/src/core/offline/outbox-merge.ts +39 -0
- package/src/core/offline/outbox-replayer.ts +141 -0
- package/src/core/offline/outbox-store.ts +85 -0
- package/src/core/offline/outbox.ts +232 -0
- package/src/core/offline/queue.ts +126 -171
- package/src/core/offline/replay-lease.ts +87 -0
- package/src/core/offline/replay-policy.ts +52 -0
- package/src/core/page-account.ts +121 -0
- package/src/core/page.ts +87 -74
- package/src/core/rest/endpoint-types.ts +22 -22
- package/src/core/rest/rest-client.ts +163 -152
- package/src/core/rest/routes.gen.ts +144 -88
- package/src/core/resume/hub-resume.ts +286 -0
- package/src/core/resume/local-resume.ts +130 -0
- package/src/core/resume/resume-list.ts +65 -0
- package/src/core/resume/resume-request.ts +52 -0
- package/src/core/resume/resume-types.ts +64 -0
- package/src/core/saved/daily-saves.ts +196 -0
- package/src/core/saved/daily-seed.ts +16 -0
- package/src/core/saved/hub-saved.ts +221 -0
- package/src/core/saved/local-saved.ts +122 -0
- package/src/core/saved/saved-list.ts +129 -0
- package/src/core/saved/saved-open.ts +104 -0
- package/src/core/saved/saved-order.ts +49 -0
- package/src/core/saved/saved-types.ts +24 -0
- package/src/core/storage/json-guards.ts +80 -74
- package/src/core/storage/key-value.ts +66 -66
- package/src/core/ticket.ts +43 -43
- package/src/core/turns/hub-turns.ts +161 -0
- package/src/core/turns/turn-game.ts +510 -0
- package/src/core/turns/your-turn-buzz.ts +69 -0
- package/src/core/types.ts +134 -134
- package/src/core/version/client-version.gen.ts +2 -2
- package/src/core/version/hub-version.ts +62 -62
- package/src/core/version/semver.ts +97 -97
- package/src/daily/daily-system.ts +276 -161
- package/src/daily/page-daily.ts +36 -36
- package/src/daily/types.ts +105 -67
- package/src/daily.ts +10 -10
- package/src/doctor/checks.ts +86 -76
- package/src/doctor/cli.ts +101 -106
- package/src/doctor/hub-version-fetch.ts +43 -0
- package/src/doctor/tsconfig.json +12 -12
- package/src/doctor/world-check.ts +61 -0
- package/src/globals.ts +115 -110
- package/src/index.ts +68 -50
- package/src/input/device-state.ts +146 -146
- package/src/input/edges.ts +55 -55
- package/src/input/focus-ring.ts +13 -0
- package/src/input/gamepad.ts +61 -61
- package/src/input/input-events.ts +32 -32
- package/src/input/input-registry.ts +171 -164
- package/src/input/input-system.ts +406 -393
- package/src/input/legacy-controls.ts +64 -64
- package/src/input/modality.ts +122 -0
- package/src/input/nav-state.ts +200 -191
- package/src/input/navigator.ts +182 -171
- package/src/input/on-screen-controls.ts +101 -101
- package/src/input/page-control.ts +43 -43
- package/src/input/page-input.ts +193 -193
- package/src/input/player-remaps.ts +77 -77
- package/src/input/source-values.ts +116 -116
- package/src/input/spatial.ts +47 -47
- package/src/input/types.ts +331 -331
- package/src/input/zero-config.ts +164 -177
- package/src/input.ts +26 -22
- package/src/mp.ts +10 -10
- package/src/overlay/controls-panel.ts +163 -163
- package/src/overlay/dom.ts +46 -46
- package/src/overlay/layout-clusters.ts +162 -162
- package/src/overlay/layout-geometry.ts +136 -136
- package/src/overlay/layout.ts +203 -203
- package/src/overlay/listener-scope.ts +23 -23
- package/src/overlay/on-screen-keyboard.ts +137 -137
- package/src/overlay/overlay.ts +234 -208
- package/src/overlay/styles.ts +13 -12
- package/src/overlay/virtual-controls.ts +262 -262
- package/src/race.ts +10 -10
- package/src/realtime/bridge.ts +126 -121
- package/src/realtime/cross-bundle.ts +29 -29
- package/src/realtime/fallback-notice.ts +48 -48
- package/src/realtime/hub-link.ts +65 -65
- package/src/realtime/latest-state.ts +33 -33
- package/src/realtime/multiplayer-forwarder.ts +40 -28
- package/src/realtime/multiplayer-types.ts +66 -52
- package/src/realtime/page-runtime.ts +60 -60
- package/src/realtime/race-forwarder.ts +59 -59
- package/src/realtime/race-types.ts +132 -132
- package/src/realtime/room-types.ts +151 -125
- package/src/realtime/rooms-forwarder.ts +104 -79
- package/src/realtime/runtime-api.ts +74 -49
- package/src/realtime/runtime-opener.ts +61 -61
- package/src/realtime/social-forwarder.ts +77 -72
- package/src/realtime/social-types.ts +86 -78
- package/src/realtime/types.ts +54 -53
- package/src/realtime/wire-types.ts +79 -72
- package/src/realtime/world-types.ts +20 -0
- package/src/rooms.ts +10 -10
- package/src/social.ts +19 -18
- package/src/turns-kit/check-rules.ts +281 -0
- package/src/turns-rules.ts +12 -0
- package/src/turns.ts +14 -0
- package/src/types.ts +13 -13
- package/src/uistack/page-stack.ts +45 -45
- package/src/uistack/play-suspension.ts +82 -82
- package/src/uistack/types.ts +76 -76
- package/src/uistack/ui-stack.ts +144 -144
package/dist/shared/index.d.ts
CHANGED
|
@@ -1,11 +1,129 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { S as avatar_d_exports } from "./avatar.js";
|
|
2
|
+
import { M as SavedOpened, S as SavedEntry, s as DailySystem, w as SavedListOptions, y as GamePayload } from "./daily.js";
|
|
3
|
+
import { O as InputSystem, n as getUiStack } from "./input.js";
|
|
3
4
|
import { o as multiplayerForwarder } from "./mp.js";
|
|
4
5
|
import { f as raceForwarder } from "./race.js";
|
|
5
6
|
import { n as roomsForwarder } from "./rooms.js";
|
|
6
|
-
import { Q as
|
|
7
|
-
import {
|
|
7
|
+
import { $ as AvatarRef, Q as AvatarChange, at as notifyForwarder, et as DesignerResult, ot as presenceForwarder, st as socialForwarder, tt as OpenDesignerOptions } from "./social.js";
|
|
8
|
+
import { AvatarLook } from "@diffenderfer-games/hub-contract/avatar";
|
|
9
|
+
import { Board, Board as Board$1, CreateTurnGame, ErrorCode, GameInfo, GameLeaderboards, GameRecords, HubVersion, Inventory, Inventory as Inventory$1, Json, Json as Json$1, LeaderboardEntry, Metrics, Metrics as Metrics$1, MyAvatar, OpenTurnGame, OpenTurnGame as OpenTurnGame$1, PublicEndpoints, RejectReason, ResumeSlot, ResumeSummary, SavedCounts, SessionUser, SortDir, SortDir as SortDir$1, Stats, Stats as Stats$1, TurnChatLine, TurnChatLine as TurnChatLine$1, TurnDeadline, TurnDeadline as TurnDeadline$1, TurnGameView, TurnGameView as TurnGameView$1, TurnSummary, TurnTournament } from "@diffenderfer-games/hub-contract";
|
|
8
10
|
import { CallReturn, ClientData } from "@ayepi/core/client";
|
|
11
|
+
//#region src/core/page-account.d.ts
|
|
12
|
+
/** The signed-in player as the page heard it. */
|
|
13
|
+
interface PageUser {
|
|
14
|
+
readonly id: number;
|
|
15
|
+
/** Their look code when the source carried it, else `null`. */
|
|
16
|
+
readonly look: string | null;
|
|
17
|
+
}
|
|
18
|
+
/** Where the news came from: an account event, or a socket hello (a time to catch up on what was missed). */
|
|
19
|
+
type AccountSource = 'account' | 'session';
|
|
20
|
+
/** Hears every report of who is signed in. */
|
|
21
|
+
type AccountListener = (user: PageUser | null, source: AccountSource) => void;
|
|
22
|
+
/** The page's account, as last heard. */
|
|
23
|
+
declare class PageAccount {
|
|
24
|
+
/** `undefined` until something said; `null` when signed out. */
|
|
25
|
+
private heard;
|
|
26
|
+
private readonly listeners;
|
|
27
|
+
private isListening;
|
|
28
|
+
constructor();
|
|
29
|
+
/** The signed-in player, `null` when signed out, `undefined` before anything said. */
|
|
30
|
+
get user(): PageUser | null | undefined;
|
|
31
|
+
/** Record who is signed in (this client's own sign-in or sign-out) and tell the listeners. */
|
|
32
|
+
note(user: PageUser | null, source?: AccountSource): void;
|
|
33
|
+
/** Hear every report; returns the unsubscribe function. */
|
|
34
|
+
on(listener: AccountListener): () => void;
|
|
35
|
+
/** Listen to the window events (at once in a page; made before a window existed, on first use). */
|
|
36
|
+
private listen;
|
|
37
|
+
private onEvent;
|
|
38
|
+
}
|
|
39
|
+
//#endregion
|
|
40
|
+
//#region src/core/avatar/avatar-ref.d.ts
|
|
41
|
+
/** Anything that carries a player's look: a seat, a `PlayerLite`, a racer, a ticket user. */
|
|
42
|
+
interface CarriesLook {
|
|
43
|
+
readonly look?: string;
|
|
44
|
+
readonly userId?: number;
|
|
45
|
+
readonly id?: number;
|
|
46
|
+
readonly uid?: number;
|
|
47
|
+
}
|
|
48
|
+
//#endregion
|
|
49
|
+
//#region src/core/avatar/hub-avatar.d.ts
|
|
50
|
+
/** What `hub.avatar` needs from its client. */
|
|
51
|
+
interface AvatarHost {
|
|
52
|
+
/** The signed-in user's id, or `null` (`hub.userId()`). */
|
|
53
|
+
readonly currentUserId: () => number | null;
|
|
54
|
+
/** Hear every report of who is signed in (the page's account). */
|
|
55
|
+
readonly onAccount: (listener: AccountListener) => () => void;
|
|
56
|
+
/** `GET /avatar/me`; rejects when offline. */
|
|
57
|
+
readonly fetchMine: () => Promise<MyAvatar>;
|
|
58
|
+
/** `POST /avatar/batch`. */
|
|
59
|
+
readonly fetchCodes: (ids: readonly number[]) => Promise<Record<string, string>>;
|
|
60
|
+
/** Queue a save of your look (sent now when online, replayed later when not). */
|
|
61
|
+
readonly queueSave: (code: string, savedAt: number) => Promise<void>;
|
|
62
|
+
}
|
|
63
|
+
/** The renderer module `load()` resolves with. */
|
|
64
|
+
type AvatarModule = typeof avatar_d_exports;
|
|
65
|
+
type ChangeListener = (change: AvatarChange) => void;
|
|
66
|
+
/** `hub.avatar`. */
|
|
67
|
+
declare class HubAvatar {
|
|
68
|
+
private readonly host;
|
|
69
|
+
private readonly cache;
|
|
70
|
+
private readonly listeners;
|
|
71
|
+
private pending;
|
|
72
|
+
private flushQueued;
|
|
73
|
+
private detachRuntime;
|
|
74
|
+
/** The account the last report named. */
|
|
75
|
+
private lastUserId;
|
|
76
|
+
/** The code of your look the page was last given (`mine`, `mineSync`, `change`). */
|
|
77
|
+
private mineShown;
|
|
78
|
+
/** Players this page asked about or was handed (re-checked on each socket hello). */
|
|
79
|
+
private readonly seen;
|
|
80
|
+
constructor(host: AvatarHost);
|
|
81
|
+
/**
|
|
82
|
+
* Your look (your default if you never designed one). Works offline from
|
|
83
|
+
* the device cache. Before your account exists (a new guest's is made just
|
|
84
|
+
* after the game starts) it resolves at once with a default look, and
|
|
85
|
+
* `change` brings yours when the account is there.
|
|
86
|
+
*/
|
|
87
|
+
mine(): Promise<AvatarRef>;
|
|
88
|
+
/** Your cached look, or your default; `null` only while nobody is signed in. */
|
|
89
|
+
mineSync(): AvatarRef | null;
|
|
90
|
+
/** Any player's look: from the cache, else batched with other requests in the same tick. */
|
|
91
|
+
of(userId: number): Promise<AvatarRef>;
|
|
92
|
+
/** Many players' looks at once. */
|
|
93
|
+
many(userIds: readonly number[]): Promise<Map<number, AvatarRef>>;
|
|
94
|
+
/** The look a payload already carries (a `RoomSeat`, `PlayerLite`, racer or ticket user). No request. */
|
|
95
|
+
from(carrier: CarriesLook): AvatarRef;
|
|
96
|
+
/** A seeded default for bots, previews and local pass-and-play seats: `bot('local:2')`. */
|
|
97
|
+
bot(seed: string | number): AvatarRef;
|
|
98
|
+
/** Save your look (designer code paths; queued offline, latest wins). */
|
|
99
|
+
save(look: AvatarLook): Promise<AvatarRef>;
|
|
100
|
+
/**
|
|
101
|
+
* Hear about look changes (yours, friends', your room's, and yours when
|
|
102
|
+
* your account appears). Returns the unsubscribe function. Wait for this
|
|
103
|
+
* rather than polling `mine()`.
|
|
104
|
+
*/
|
|
105
|
+
on(_event: 'change', listener: ChangeListener): () => void;
|
|
106
|
+
/** Open "Make your avatar" over the game; resolves when it closes. */
|
|
107
|
+
openDesigner(options?: OpenDesignerOptions): Promise<DesignerResult>;
|
|
108
|
+
/** Loads the renderer and its art; resolves when rendering is synchronous. */
|
|
109
|
+
load(): Promise<AvatarModule>;
|
|
110
|
+
/** Remember the ref the page was given for your look. */
|
|
111
|
+
private showMine;
|
|
112
|
+
/** Cache your look; `change` fires when it differs from the one the page last had. */
|
|
113
|
+
private noteMine;
|
|
114
|
+
/** Someone said who is signed in: fetch your look when the account is new or its look isn't the cached one. */
|
|
115
|
+
private onAccount;
|
|
116
|
+
private refreshMine;
|
|
117
|
+
/** The socket (re)connected: a change sent while it was down never arrived, so ask again. */
|
|
118
|
+
private recheckSeen;
|
|
119
|
+
private emit;
|
|
120
|
+
private attachRuntime;
|
|
121
|
+
private scheduleFlush;
|
|
122
|
+
private flush;
|
|
123
|
+
/** Players' codes, in batches; offline or failing, the missing ones are left out (they get their default). */
|
|
124
|
+
private fetchCodes;
|
|
125
|
+
}
|
|
126
|
+
//#endregion
|
|
9
127
|
//#region src/core/storage/key-value.d.ts
|
|
10
128
|
/**
|
|
11
129
|
* The string store behind the offline cache and queues: `localStorage` when
|
|
@@ -25,10 +143,6 @@ interface KeyValueStore {
|
|
|
25
143
|
keys(): string[];
|
|
26
144
|
}
|
|
27
145
|
//#endregion
|
|
28
|
-
//#region src/core/game-data.d.ts
|
|
29
|
-
/** Anything a game may store as JSON: an object (interfaces included), an array, text, a number, a boolean or `null`. */
|
|
30
|
-
type GamePayload = object | string | number | boolean | null;
|
|
31
|
-
//#endregion
|
|
32
146
|
//#region src/core/rest/endpoint-types.d.ts
|
|
33
147
|
/** The name of a public REST endpoint (`getCurrentUser`, `writeSave`, ...). */
|
|
34
148
|
type EndpointName = keyof PublicEndpoints;
|
|
@@ -234,7 +348,6 @@ type StatUpdateMode = 'set' | 'incr' | 'max';
|
|
|
234
348
|
declare class LocalStore {
|
|
235
349
|
private readonly prefix;
|
|
236
350
|
private readonly store;
|
|
237
|
-
private sequence;
|
|
238
351
|
/** The cache for game `slug` in `store` (default: `localStorage`, or memory without it). */
|
|
239
352
|
constructor(slug: string, store?: KeyValueStore);
|
|
240
353
|
private readStored;
|
|
@@ -257,6 +370,16 @@ declare class LocalStore {
|
|
|
257
370
|
putSave(slot: string, saveData: Json, label: string | null): number;
|
|
258
371
|
/** Forget a save. */
|
|
259
372
|
deleteSave(slot: string): void;
|
|
373
|
+
/**
|
|
374
|
+
* The hub's `updatedAt` for `slot` as the current user last saw it (`null`:
|
|
375
|
+
* the slot was empty), or `undefined` when this device doesn't know.
|
|
376
|
+
*/
|
|
377
|
+
cloudSave(slot: string): number | null | undefined;
|
|
378
|
+
/** Note the hub's copy of `slot` (just read or written) for the current user: its `updatedAt`, or `null` when empty. */
|
|
379
|
+
rememberCloudSave(slot: string, updatedAt: number | null): void;
|
|
380
|
+
/** Cache a slot as read from the hub (forgetting it when the hub has none), and note the hub's copy. */
|
|
381
|
+
cacheCloudSave(slot: string, save: Save | null): void;
|
|
382
|
+
private readCloudSaves;
|
|
260
383
|
/** This game's cached stats. */
|
|
261
384
|
getStats(): Stats;
|
|
262
385
|
/** Apply one stat update and return the stat's new value. */
|
|
@@ -267,21 +390,24 @@ declare class LocalStore {
|
|
|
267
390
|
getBoard(key: string): LocalBoard | null;
|
|
268
391
|
/** Record a score locally, keeping the best one; returns the board and best. */
|
|
269
392
|
submitScore(key: string, score: number, options: QueuedScoreOptions): LocalBoard;
|
|
270
|
-
/** The queued writes,
|
|
271
|
-
|
|
272
|
-
/**
|
|
273
|
-
|
|
274
|
-
/**
|
|
275
|
-
|
|
276
|
-
/** Queue a write, collapsing it with a compatible queued one. */
|
|
277
|
-
enqueue(input: QueueOpInput): void;
|
|
393
|
+
/** The v1 client's queued writes, removed from storage (the caller moves them into the outbox). */
|
|
394
|
+
takeLegacyQueue(): QueueOp[];
|
|
395
|
+
/** This game's cached inventory (empty when none is cached). */
|
|
396
|
+
getInventory(): Inventory;
|
|
397
|
+
/** Store this game's inventory and return it. */
|
|
398
|
+
setInventory(inventory: Inventory): Inventory;
|
|
278
399
|
/** The buffered analytics events, oldest first. */
|
|
279
400
|
getEvents(): BufferedEvent[];
|
|
280
401
|
/** Replace the event buffer. */
|
|
281
402
|
setEvents(events: readonly BufferedEvent[]): void;
|
|
282
403
|
/** Buffer one event (capped; heartbeats are dropped first). */
|
|
283
404
|
bufferEvent(type: string): void;
|
|
284
|
-
/**
|
|
405
|
+
/**
|
|
406
|
+
* Fan a `/me/snapshot` reply out across every game's cache keys. It lists
|
|
407
|
+
* every save slot of every game the player has played, so each game's
|
|
408
|
+
* slots on the hub become known (and this game's are all empty when it
|
|
409
|
+
* isn't listed).
|
|
410
|
+
*/
|
|
285
411
|
hydrate(snapshot: SnapshotResponse): void;
|
|
286
412
|
}
|
|
287
413
|
//#endregion
|
|
@@ -296,21 +422,348 @@ interface RestClient {
|
|
|
296
422
|
readonly apiBase: string;
|
|
297
423
|
/** Call an endpoint; resolves with its reply body, rejects with a {@link HubError}. */
|
|
298
424
|
call<K extends EndpointName>(name: K, ...fieldsArgument: CallArguments<K>): Promise<EndpointReply<K>>;
|
|
425
|
+
/**
|
|
426
|
+
* Call an endpoint as part of an offline operation: an endpoint marked for
|
|
427
|
+
* offline replay gets `opKey` in the operation header, so the hub runs it
|
|
428
|
+
* once however often it is sent; any other endpoint is called as usual.
|
|
429
|
+
*/
|
|
430
|
+
callOnce<K extends EndpointName>(opKey: string, name: K, ...fieldsArgument: CallArguments<K>): Promise<EndpointReply<K>>;
|
|
431
|
+
}
|
|
432
|
+
//#endregion
|
|
433
|
+
//#region src/core/offline/outbox-entry.d.ts
|
|
434
|
+
/** A queued operation. */
|
|
435
|
+
interface OutboxEntry {
|
|
436
|
+
/** Unique and sortable by queue time: replay runs in `id` order. Also the base of its operation keys. */
|
|
437
|
+
readonly id: string;
|
|
438
|
+
/** Which handler performs it (`hub.save`, `hub.stat`, or a game's own kind). */
|
|
439
|
+
readonly kind: string;
|
|
440
|
+
/** The handler's input. */
|
|
441
|
+
readonly payload: Json;
|
|
442
|
+
/** When it was queued (epoch ms). */
|
|
443
|
+
readonly createdAt: number;
|
|
444
|
+
/** How many times replay has started it. A started entry never absorbs a later one. */
|
|
445
|
+
readonly attempts: number;
|
|
446
|
+
/** Not before this time (epoch ms); 0 means as soon as possible. */
|
|
447
|
+
readonly retryAt: number;
|
|
448
|
+
/** The last failure's message, when it has failed before. */
|
|
449
|
+
readonly lastError?: string;
|
|
450
|
+
}
|
|
451
|
+
//#endregion
|
|
452
|
+
//#region src/core/offline/outbox-kinds.d.ts
|
|
453
|
+
/** What a kind's handler gets besides its payload. */
|
|
454
|
+
interface ReplayContext {
|
|
455
|
+
/** The operation's id (stable across retries). */
|
|
456
|
+
readonly opId: string;
|
|
457
|
+
/** Which try this is, from 1. */
|
|
458
|
+
readonly attempt: number;
|
|
459
|
+
/** `false` when the op runs at once (online, nothing waiting); `true` when it runs from the queue. */
|
|
460
|
+
readonly isReplay: boolean;
|
|
461
|
+
/**
|
|
462
|
+
* Call a hub endpoint. Calls to endpoints marked for offline replay carry
|
|
463
|
+
* an operation key made from the op id and the call's position in the
|
|
464
|
+
* handler, so a retried op whose earlier calls already landed gets their
|
|
465
|
+
* remembered replies instead of running them twice. Make the same calls in
|
|
466
|
+
* the same order on every try.
|
|
467
|
+
*/
|
|
468
|
+
call<K extends EndpointName>(name: K, ...fieldsArgument: CallArguments<K>): Promise<EndpointReply<K>>;
|
|
469
|
+
/** A key for `step` of this op that is the same on every try: send it to a game server to deduplicate there. */
|
|
470
|
+
key(step: string): string;
|
|
471
|
+
}
|
|
472
|
+
/** Performs one operation of a kind. Throw to fail it (see the docs for which failures retry). */
|
|
473
|
+
type OfflineHandler<Payload> = (payload: Payload, context: ReplayContext) => Promise<Json | undefined> | Json | undefined;
|
|
474
|
+
/** A kind, with optional rules for folding new operations into waiting ones. */
|
|
475
|
+
interface OfflineKindDefinition<Payload> {
|
|
476
|
+
/** Perform one operation. */
|
|
477
|
+
readonly run: OfflineHandler<Payload>;
|
|
478
|
+
/**
|
|
479
|
+
* Operations with the same merge key may fold together. Without one, every
|
|
480
|
+
* operation replays on its own.
|
|
481
|
+
*/
|
|
482
|
+
readonly mergeKey?: (payload: Payload) => string;
|
|
483
|
+
/**
|
|
484
|
+
* Fold `incoming` into the newest waiting operation with the same merge key
|
|
485
|
+
* (in its place in the queue); `undefined` to queue it separately.
|
|
486
|
+
*/
|
|
487
|
+
readonly merge?: (waiting: Payload, incoming: Payload) => Payload | undefined;
|
|
488
|
+
/** `true`: a new operation replaces every waiting one with the same merge key and goes last (a save slot's last write wins). */
|
|
489
|
+
readonly replaces?: boolean;
|
|
490
|
+
}
|
|
491
|
+
/** A kind as the outbox runs it: payloads are the stored JSON. */
|
|
492
|
+
interface RegisteredKind {
|
|
493
|
+
/** Perform one operation (malformed payloads are refused before this). */
|
|
494
|
+
readonly run: (payload: Json, context: ReplayContext) => Promise<Json | undefined> | Json | undefined;
|
|
495
|
+
/** Whether a stored payload is well formed for this kind. */
|
|
496
|
+
readonly accepts: (payload: Json) => boolean;
|
|
497
|
+
readonly mergeKey?: (payload: Json) => string;
|
|
498
|
+
readonly merge?: (waiting: Json, incoming: Json) => Json | undefined;
|
|
499
|
+
readonly replaces: boolean;
|
|
500
|
+
}
|
|
501
|
+
/** The registered kinds of one client. */
|
|
502
|
+
declare class KindRegistry {
|
|
503
|
+
private readonly kinds;
|
|
504
|
+
/** Register (or replace) a kind. */
|
|
505
|
+
register(kind: string, definition: RegisteredKind): void;
|
|
506
|
+
/** A registered kind, or `undefined`. */
|
|
507
|
+
get(kind: string): RegisteredKind | undefined;
|
|
508
|
+
}
|
|
509
|
+
//#endregion
|
|
510
|
+
//#region src/core/offline/outbox-store.d.ts
|
|
511
|
+
/** The most entries one game's outbox holds. */
|
|
512
|
+
declare const OUTBOX_MAX_ENTRIES = 500;
|
|
513
|
+
/** The most stored characters one game's outbox holds (about 2 MB of `localStorage`). */
|
|
514
|
+
declare const OUTBOX_MAX_CHARS = 1000000;
|
|
515
|
+
/** One game's outbox. */
|
|
516
|
+
declare class OutboxStore {
|
|
517
|
+
/** The key prefix of this game's entries. */
|
|
518
|
+
readonly prefix: string;
|
|
519
|
+
private readonly store;
|
|
520
|
+
/** The outbox for game `slug` in `store`. */
|
|
521
|
+
constructor(slug: string, store: KeyValueStore);
|
|
522
|
+
/** Every well-formed entry, oldest first. Malformed ones are removed. */
|
|
523
|
+
list(): OutboxEntry[];
|
|
524
|
+
/** How many entries are queued. */
|
|
525
|
+
count(): number;
|
|
526
|
+
/** One entry, or `undefined` once it is gone (replayed, or removed by another tab). */
|
|
527
|
+
get(id: string): OutboxEntry | undefined;
|
|
528
|
+
/** Whether `extra` more stored characters in one more entry still fit under the caps. */
|
|
529
|
+
fits(extraChars: number): boolean;
|
|
530
|
+
/** Store an entry (new or updated); `false` when the storage refused it (full or blocked). */
|
|
531
|
+
put(entry: OutboxEntry): boolean;
|
|
532
|
+
/** Forget an entry. */
|
|
533
|
+
remove(id: string): void;
|
|
534
|
+
/** Whether a storage key belongs to this outbox (for cross-tab `storage` events). */
|
|
535
|
+
owns(key: string | null): boolean;
|
|
536
|
+
private entryKeys;
|
|
537
|
+
}
|
|
538
|
+
//#endregion
|
|
539
|
+
//#region src/core/offline/outbox-replayer.d.ts
|
|
540
|
+
/** An op dropped during replay, with why. */
|
|
541
|
+
interface ReplayFailure {
|
|
542
|
+
/** The dropped op. */
|
|
543
|
+
readonly entry: OutboxEntry;
|
|
544
|
+
/** Why it was dropped (the hub's message, or the handler's). */
|
|
545
|
+
readonly error: string;
|
|
546
|
+
}
|
|
547
|
+
/** Why a replay stopped before the outbox was empty. */
|
|
548
|
+
type ReplayStop = 'connection' | 'backoff' | 'unknown-kind' | 'lease';
|
|
549
|
+
/** What one replay did. */
|
|
550
|
+
interface ReplayReport {
|
|
551
|
+
/** The ops that landed, in order. */
|
|
552
|
+
readonly replayed: OutboxEntry[];
|
|
553
|
+
/** The ops dropped. */
|
|
554
|
+
readonly failed: ReplayFailure[];
|
|
555
|
+
/** Why it stopped early, or `undefined` when the outbox is empty. */
|
|
556
|
+
readonly stoppedBy?: ReplayStop;
|
|
557
|
+
}
|
|
558
|
+
//#endregion
|
|
559
|
+
//#region src/core/offline/outbox.d.ts
|
|
560
|
+
/** A queued operation, as `hub.offline` reports it. */
|
|
561
|
+
type OfflineOp = OutboxEntry;
|
|
562
|
+
/** Each outbox event's detail. */
|
|
563
|
+
interface OfflineEventDetails {
|
|
564
|
+
/** An op was queued (or folded into a waiting one, which is then the `op`). */
|
|
565
|
+
queued: {
|
|
566
|
+
readonly op: OfflineOp;
|
|
567
|
+
};
|
|
568
|
+
/** An op landed; `result` is what its handler returned. */
|
|
569
|
+
replayed: {
|
|
570
|
+
readonly op: OfflineOp;
|
|
571
|
+
readonly result?: Json;
|
|
572
|
+
};
|
|
573
|
+
/** An op was dropped: the hub refused it, or it kept failing. */
|
|
574
|
+
failed: {
|
|
575
|
+
readonly op: OfflineOp;
|
|
576
|
+
readonly error: string;
|
|
577
|
+
};
|
|
578
|
+
}
|
|
579
|
+
/** An outbox event's name. */
|
|
580
|
+
type OfflineEventName = keyof OfflineEventDetails;
|
|
581
|
+
/** An outbox event listener. */
|
|
582
|
+
type OfflineListener<Name extends OfflineEventName> = (detail: OfflineEventDetails[Name]) => void;
|
|
583
|
+
/** The code of the error `add` throws when the outbox is full. */
|
|
584
|
+
declare const OUTBOX_FULL_CODE = "offline_queue_full";
|
|
585
|
+
/** What the outbox needs from its client. */
|
|
586
|
+
interface OutboxOptions {
|
|
587
|
+
/** The game whose outbox it is. */
|
|
588
|
+
readonly slug: string;
|
|
589
|
+
/** Where it lives (`localStorage`, or memory). */
|
|
590
|
+
readonly store: KeyValueStore;
|
|
591
|
+
/** The REST client its handlers call through. */
|
|
592
|
+
readonly rest: RestClient;
|
|
593
|
+
/** Fire a window event (the menu listens). */
|
|
594
|
+
readonly emit: (name: string, detail: OfflineEventDetails[OfflineEventName]) => void;
|
|
595
|
+
/** The hub couldn't be reached during a replay. */
|
|
596
|
+
readonly onUnreachable: () => void;
|
|
597
|
+
/** The queue length may have changed. */
|
|
598
|
+
readonly onChange: () => void;
|
|
599
|
+
/** A retry is due: replay again. */
|
|
600
|
+
readonly onRetryDue: () => void;
|
|
601
|
+
readonly now?: () => number;
|
|
602
|
+
}
|
|
603
|
+
/** One game's outbox. */
|
|
604
|
+
declare class Outbox {
|
|
605
|
+
/** The kinds this client can perform. */
|
|
606
|
+
readonly kinds: KindRegistry;
|
|
607
|
+
/** The stored entries. */
|
|
608
|
+
readonly entries: OutboxStore;
|
|
609
|
+
private readonly options;
|
|
610
|
+
private readonly replayer;
|
|
611
|
+
private readonly now;
|
|
612
|
+
private sequence;
|
|
613
|
+
private retryTimer;
|
|
614
|
+
private retryAt;
|
|
615
|
+
private readonly listeners;
|
|
616
|
+
/** The outbox for `options.slug`. */
|
|
617
|
+
constructor(options: OutboxOptions);
|
|
618
|
+
/** Register a kind. */
|
|
619
|
+
define(kind: string, definition: RegisteredKind): void;
|
|
620
|
+
/** A new, not yet stored, op of `kind`. */
|
|
621
|
+
stamp(kind: string, payload: Json): OutboxEntry;
|
|
622
|
+
/**
|
|
623
|
+
* Queue an op, folding it into a waiting one where its kind says so.
|
|
624
|
+
* Throws a {@link HubError} (code {@link OUTBOX_FULL_CODE}) when the
|
|
625
|
+
* outbox is full or the storage refuses it.
|
|
626
|
+
*/
|
|
627
|
+
add(op: OutboxEntry): void;
|
|
628
|
+
/** Perform an op now, without queueing it (the online path); throws what its handler throws. */
|
|
629
|
+
runNow(op: OutboxEntry): Promise<Json | undefined>;
|
|
630
|
+
/** Replay what is due, in order. `isEager`: the connection just came back, so ops waiting for it go at once. */
|
|
631
|
+
replay(isEager?: boolean): Promise<ReplayReport>;
|
|
632
|
+
/** The waiting ops, oldest first. */
|
|
633
|
+
pending(): OfflineOp[];
|
|
634
|
+
/** How many ops are waiting. */
|
|
635
|
+
count(): number;
|
|
636
|
+
/** Listen for an event; returns the unsubscribe function. */
|
|
637
|
+
on<Name extends OfflineEventName>(name: Name, listener: OfflineListener<Name>): () => void;
|
|
638
|
+
private fire;
|
|
639
|
+
private contextFor;
|
|
640
|
+
private scheduleRetry;
|
|
641
|
+
private clearRetry;
|
|
642
|
+
}
|
|
643
|
+
//#endregion
|
|
644
|
+
//#region src/core/offline/offline-api.d.ts
|
|
645
|
+
/** What `hub.offline` drives. */
|
|
646
|
+
interface OfflineHost {
|
|
647
|
+
/** The game's outbox. */
|
|
648
|
+
readonly outbox: Outbox;
|
|
649
|
+
/** Replay now when the hub is reachable (and nothing else is replaying). */
|
|
650
|
+
requestSync(): void;
|
|
651
|
+
}
|
|
652
|
+
/** The game-facing outbox API. */
|
|
653
|
+
declare class OfflineApi {
|
|
654
|
+
private readonly host;
|
|
655
|
+
/** The API over `host`. */
|
|
656
|
+
constructor(host: OfflineHost);
|
|
657
|
+
/**
|
|
658
|
+
* Define what an operation of `kind` does when it replays. Define every
|
|
659
|
+
* kind at load, before or after enqueueing: waiting ops of an undefined
|
|
660
|
+
* kind hold the queue until it is defined. `hub.*` kinds are the hub's own.
|
|
661
|
+
*/
|
|
662
|
+
define<Payload extends GamePayload>(kind: string, definition: OfflineHandler<Payload> | OfflineKindDefinition<Payload>): void;
|
|
663
|
+
/**
|
|
664
|
+
* Queue an operation of `kind` and replay it as soon as the hub is
|
|
665
|
+
* reachable (at once when online). Resolves the queued op; its outcome
|
|
666
|
+
* arrives as a `replayed` or `failed` event. Throws a `HubError` (code
|
|
667
|
+
* `offline_queue_full`) when the outbox is full.
|
|
668
|
+
*/
|
|
669
|
+
enqueue(kind: string, payload: GamePayload): OfflineOp;
|
|
670
|
+
/** The waiting operations (the hub's and the game's), oldest first. */
|
|
671
|
+
pending(): OfflineOp[];
|
|
672
|
+
/** Listen for `queued`, `replayed` or `failed`; returns the unsubscribe function. */
|
|
673
|
+
on<Name extends OfflineEventName>(name: Name, listener: OfflineListener<Name>): () => void;
|
|
674
|
+
}
|
|
675
|
+
//#endregion
|
|
676
|
+
//#region src/core/resume/local-resume.d.ts
|
|
677
|
+
/** A run as this device saved it. */
|
|
678
|
+
interface LocalRun {
|
|
679
|
+
readonly status: 'saved';
|
|
680
|
+
readonly userId: number | null;
|
|
681
|
+
/** When the game saved it, epoch ms. */
|
|
682
|
+
readonly at: number;
|
|
683
|
+
readonly label: string;
|
|
684
|
+
readonly fraction: number | null;
|
|
685
|
+
readonly title: string | null;
|
|
686
|
+
readonly version: number;
|
|
687
|
+
/** The game's title, for the Resume card before the hub knows the run. */
|
|
688
|
+
readonly gameTitle: string;
|
|
689
|
+
readonly state: Json;
|
|
690
|
+
readonly isSynced: boolean;
|
|
691
|
+
}
|
|
692
|
+
/** A run this device cleared (kept until the hub has the clear). */
|
|
693
|
+
interface LocalClear {
|
|
694
|
+
readonly status: 'cleared';
|
|
695
|
+
readonly userId: number | null;
|
|
696
|
+
readonly at: number;
|
|
697
|
+
readonly isSynced: boolean;
|
|
698
|
+
}
|
|
699
|
+
/** A device copy of one game's slot. */
|
|
700
|
+
type LocalResume = LocalRun | LocalClear;
|
|
701
|
+
/** The device copies of every game's slot. */
|
|
702
|
+
declare class LocalResumeStore {
|
|
703
|
+
private readonly store;
|
|
704
|
+
constructor(store: KeyValueStore);
|
|
705
|
+
/** `slug`'s copy, or `null`. */
|
|
706
|
+
read(slug: string): LocalResume | null;
|
|
707
|
+
/** Store `slug`'s copy; answers whether it stuck (a full storage refuses a large state). */
|
|
708
|
+
write(slug: string, record: LocalResume): boolean;
|
|
709
|
+
/** Forget `slug`'s copy. */
|
|
710
|
+
remove(slug: string): void;
|
|
711
|
+
/** The hub now has `slug`'s save or clear made at `at`: mark it synced (a clear is simply forgotten). */
|
|
712
|
+
markSynced(slug: string, at: number): void;
|
|
713
|
+
/** Every game's copy, by slug. */
|
|
714
|
+
all(): Map<string, LocalResume>;
|
|
715
|
+
}
|
|
716
|
+
//#endregion
|
|
717
|
+
//#region src/core/saved/local-saved.d.ts
|
|
718
|
+
/** A daily challenge as this device last saw it. */
|
|
719
|
+
interface LocalDaily {
|
|
720
|
+
readonly id: string;
|
|
721
|
+
readonly game: string;
|
|
722
|
+
readonly day: string;
|
|
723
|
+
readonly seed: number;
|
|
724
|
+
/** `active` in progress; `finished` (won) or `discarded`, kept until the hub has it. */
|
|
725
|
+
readonly status: 'active' | 'finished' | 'discarded';
|
|
726
|
+
readonly userId: number | null;
|
|
727
|
+
/** When it last changed here, epoch ms. */
|
|
728
|
+
readonly at: number;
|
|
729
|
+
readonly label: string;
|
|
730
|
+
readonly fraction: number | null;
|
|
731
|
+
readonly title: string | null;
|
|
732
|
+
readonly version: number;
|
|
733
|
+
readonly gameTitle: string;
|
|
734
|
+
/** The state the game last saved, `null` before its first save. */
|
|
735
|
+
readonly state: Json;
|
|
736
|
+
readonly isSynced: boolean;
|
|
737
|
+
}
|
|
738
|
+
/** The device copies of daily challenges. */
|
|
739
|
+
declare class LocalDailyStore {
|
|
740
|
+
private readonly store;
|
|
741
|
+
constructor(store: KeyValueStore);
|
|
742
|
+
/** The copy of `game`'s `day`, or `null`. */
|
|
743
|
+
read(game: string, day: string): LocalDaily | null;
|
|
744
|
+
/** Store a copy; answers whether it stuck (a full storage refuses a large state). */
|
|
745
|
+
write(copy: LocalDaily): boolean;
|
|
746
|
+
/** Forget a copy. */
|
|
747
|
+
remove(game: string, day: string): void;
|
|
748
|
+
/** The hub has the change made at `at` (and calls the instance `id`): mark it synced; a closed copy is forgotten. */
|
|
749
|
+
markSynced(game: string, day: string, at: number, id?: string): void;
|
|
750
|
+
/** Every copy of `userId`'s, any game. */
|
|
751
|
+
all(userId: number | null): LocalDaily[];
|
|
299
752
|
}
|
|
300
753
|
//#endregion
|
|
301
754
|
//#region src/core/connection.d.ts
|
|
302
|
-
/** A queued
|
|
755
|
+
/** A queued op the hub refused during a sync (or that kept failing): dropped, with the reason. */
|
|
303
756
|
interface SyncConflict {
|
|
304
|
-
/** The
|
|
305
|
-
readonly op:
|
|
306
|
-
/** The hub's message. */
|
|
757
|
+
/** The dropped op. */
|
|
758
|
+
readonly op: OfflineOp;
|
|
759
|
+
/** The hub's (or the handler's) message. */
|
|
307
760
|
readonly error?: string;
|
|
308
761
|
}
|
|
309
762
|
/** What one `sync()` did. */
|
|
310
763
|
interface SyncResult {
|
|
311
|
-
/** Queued
|
|
764
|
+
/** Queued ops that landed. */
|
|
312
765
|
readonly flushed: number;
|
|
313
|
-
/** Queued
|
|
766
|
+
/** Queued ops that were dropped. */
|
|
314
767
|
readonly conflicts: SyncConflict[];
|
|
315
768
|
}
|
|
316
769
|
/** The game slug's path field, or a {@link HubError} when the slug is unknown. */
|
|
@@ -326,12 +779,22 @@ declare class HubConnection {
|
|
|
326
779
|
readonly apiBase: string;
|
|
327
780
|
/** Typed REST calls to {@link apiBase}. */
|
|
328
781
|
protected readonly rest: RestClient;
|
|
329
|
-
/** This game's offline cache
|
|
782
|
+
/** This game's offline cache and event buffer. */
|
|
330
783
|
protected readonly store: LocalStore;
|
|
784
|
+
/** This game's queued operations. */
|
|
785
|
+
protected readonly outbox: Outbox;
|
|
786
|
+
/** The device copies of every game's resume slot (`hub.resume`). */
|
|
787
|
+
protected readonly resumeStore: LocalResumeStore;
|
|
788
|
+
/** The device copies of daily challenges in progress (every game's). */
|
|
789
|
+
protected readonly dailyStore: LocalDailyStore;
|
|
331
790
|
private readonly offlineMode;
|
|
332
791
|
private isReachable;
|
|
333
|
-
private
|
|
792
|
+
private runningSync;
|
|
793
|
+
private followingSync;
|
|
794
|
+
private isFollowingEager;
|
|
795
|
+
private directSend;
|
|
334
796
|
private lastPrefetchAt;
|
|
797
|
+
private offlineApi;
|
|
335
798
|
private readonly statusListeners;
|
|
336
799
|
/** A connection for `options` (slug and API base default to the page's). */
|
|
337
800
|
constructor(options?: HubOptions);
|
|
@@ -339,13 +802,18 @@ declare class HubConnection {
|
|
|
339
802
|
get isOffline(): boolean;
|
|
340
803
|
/** `true` while the hub is reachable. */
|
|
341
804
|
get isOnline(): boolean;
|
|
342
|
-
/**
|
|
805
|
+
/**
|
|
806
|
+
* The offline outbox: define your own kinds of operation, queue them, and
|
|
807
|
+
* hear how they replay (`hub.offline.define`, `enqueue`, `pending`, `on`).
|
|
808
|
+
*/
|
|
809
|
+
get offline(): OfflineApi;
|
|
810
|
+
/** Queued operations plus buffered analytics events waiting to sync. */
|
|
343
811
|
pending(): number;
|
|
344
812
|
/** Subscribe to connection and sync status changes; returns the unsubscribe function. */
|
|
345
813
|
onStatus(listener: (status: HubStatus) => void): () => void;
|
|
346
814
|
/** Tell `onStatus` listeners and the page (`hub:status`) the current status. */
|
|
347
815
|
protected notifyStatus(): void;
|
|
348
|
-
/** Record reachability; coming back online
|
|
816
|
+
/** Record reachability; coming back online replays the outbox and re-warms the cache. */
|
|
349
817
|
protected setReachable(isReachableNow: boolean): void;
|
|
350
818
|
/** `{ slug }` for this game's endpoints; throws when the slug is unknown. */
|
|
351
819
|
protected gameFields(): GameFields;
|
|
@@ -355,23 +823,38 @@ declare class HubConnection {
|
|
|
355
823
|
* status) always reach the caller.
|
|
356
824
|
*/
|
|
357
825
|
protected withFallback<Reply>(networkCall: () => Promise<Reply>, offlineAnswer: () => Reply): Promise<Reply>;
|
|
358
|
-
/** Send one queued write. */
|
|
359
|
-
private sendOp;
|
|
360
826
|
/**
|
|
361
|
-
* Apply a write to the offline cache at once, then
|
|
362
|
-
*
|
|
363
|
-
*
|
|
364
|
-
*
|
|
827
|
+
* Apply a write to the offline cache at once, then perform it as an op of
|
|
828
|
+
* `kind`: sent now when online with nothing waiting, else queued behind
|
|
829
|
+
* the others. Resolves with the local answer (or, sent now, with
|
|
830
|
+
* `replyAnswer`'s reading of the hub's reply). A real API error on the
|
|
831
|
+
* direct path reaches the caller and is not queued; a transport failure
|
|
832
|
+
* queues the op under the same id, so if the request did land, its replay
|
|
833
|
+
* is recognised by the hub and not applied twice.
|
|
365
834
|
*/
|
|
366
|
-
protected
|
|
835
|
+
protected mutateOffline<Reply>(kind: string, payload: Json, localAnswer: () => Reply, replyAnswer?: (result: Json | undefined) => Reply | undefined): Promise<Reply>;
|
|
367
836
|
/**
|
|
368
|
-
* Replay the
|
|
369
|
-
* transport failure (offline again: the rest stays queued);
|
|
370
|
-
* refuses is dropped and reported as a conflict.
|
|
837
|
+
* Replay the outbox, then the buffered events, in order. Stops at the
|
|
838
|
+
* first transport failure (offline again: the rest stays queued); an op
|
|
839
|
+
* the hub refuses is dropped and reported as a conflict.
|
|
371
840
|
*/
|
|
372
841
|
sync(): Promise<SyncResult>;
|
|
842
|
+
/** A retry came due: try again even when the hub was last unreachable (the try is the probe), unless the browser itself is offline. */
|
|
843
|
+
private retrySync;
|
|
844
|
+
/**
|
|
845
|
+
* One replay at a time per client. A call during one queues a single
|
|
846
|
+
* follow-up replay (eager if any caller asked for that), so an op queued
|
|
847
|
+
* or a reconnect that happens mid-replay is never missed.
|
|
848
|
+
*/
|
|
849
|
+
private replayAll;
|
|
850
|
+
private startReplay;
|
|
851
|
+
private runReplay;
|
|
373
852
|
/** Send the buffered analytics events; a refused one is dropped (telemetry never blocks). */
|
|
374
853
|
private flushEvents;
|
|
854
|
+
/** Move a v1 copy's queued writes into the outbox, in their order. */
|
|
855
|
+
private importLegacyQueue;
|
|
856
|
+
/** Follow the browser's connection, other tabs' outbox changes, and replay what an earlier page left queued. */
|
|
857
|
+
private listenToPage;
|
|
375
858
|
/**
|
|
376
859
|
* Warm the offline cache for every game the player has touched, in one
|
|
377
860
|
* request, so going offline later still shows complete progress.
|
|
@@ -402,6 +885,16 @@ interface ProfileUpdate<TData extends GamePayload = Json> {
|
|
|
402
885
|
}
|
|
403
886
|
/** Accounts, profile, saves, stats and leaderboards. */
|
|
404
887
|
declare class HubProgress extends HubConnection {
|
|
888
|
+
/** Who the page is signed in as, as the hub's events and this client's sign-ins tell it. */
|
|
889
|
+
protected readonly account: PageAccount;
|
|
890
|
+
/**
|
|
891
|
+
* Your user id, or `null` when nobody is signed in (also before an offline
|
|
892
|
+
* first visit has an account). Synchronous, and it follows the account as
|
|
893
|
+
* the hub changes it (a new guest's account made by the menu, sign-in,
|
|
894
|
+
* sign-out) with no `hub.me()` needed. Compare it with a seat's `userId`
|
|
895
|
+
* to find yours.
|
|
896
|
+
*/
|
|
897
|
+
userId(): number | null;
|
|
405
898
|
/** The current user (or `null`) and the games they have a profile in. */
|
|
406
899
|
me(): Promise<MeResponse>;
|
|
407
900
|
/**
|
|
@@ -425,6 +918,11 @@ declare class HubProgress extends HubConnection {
|
|
|
425
918
|
ok: true;
|
|
426
919
|
}>;
|
|
427
920
|
private signInLocally;
|
|
921
|
+
/** Remember who the hub signed in, so the cache's notes about the hub's copies are read for the right player. */
|
|
922
|
+
private signedIn;
|
|
923
|
+
/** The cache and the page (`hub.userId()`, `hub.avatar`) learn who is signed in. */
|
|
924
|
+
private remember;
|
|
925
|
+
private signedOut;
|
|
428
926
|
/** This game's profile for the current user, or `profile: null`. `TData` is the game's own data type. */
|
|
429
927
|
getProfile<TData = Json>(): Promise<{
|
|
430
928
|
profile: Profile<TData> | null;
|
|
@@ -437,10 +935,18 @@ declare class HubProgress extends HubConnection {
|
|
|
437
935
|
listSaves(): Promise<{
|
|
438
936
|
saves: SaveMeta[];
|
|
439
937
|
}>;
|
|
440
|
-
/**
|
|
938
|
+
/**
|
|
939
|
+
* One slot's contents, or `save: null` when the slot is empty. `TData` is
|
|
940
|
+
* the game's own save type. Offline it answers from the device's copy, but
|
|
941
|
+
* only when the device knows the hub's copy of the slot; otherwise (a fresh
|
|
942
|
+
* device or cleared browser) it rejects with a {@link HubError} whose
|
|
943
|
+
* `code` is `save_unknown` (`isOffline` is `true`), never a false "empty".
|
|
944
|
+
*/
|
|
441
945
|
getSave<TData = Json>(slot: string): Promise<{
|
|
442
946
|
save: Save<TData> | null;
|
|
443
947
|
}>;
|
|
948
|
+
/** The device's copy of `slot`, when it can be trusted offline (see {@link getSave}). */
|
|
949
|
+
private offlineSave;
|
|
444
950
|
/** Create or overwrite a save slot. `saveData` is any JSON up to 64 KB. */
|
|
445
951
|
putSave<TData extends GamePayload = Json>(slot: string, saveData: TData, label?: string): Promise<{
|
|
446
952
|
ok: true;
|
|
@@ -509,7 +1015,7 @@ declare class HubEconomy extends HubProgress {
|
|
|
509
1015
|
favorites(): Promise<{
|
|
510
1016
|
games: string[];
|
|
511
1017
|
}>;
|
|
512
|
-
/** Star (`isOn = true`) or unstar a game.
|
|
1018
|
+
/** Star (`isOn = true`) or unstar a game. Offline, the local list changes at once and the hub's when it is back. */
|
|
513
1019
|
setFavorite(slug: string, isOn: boolean): Promise<{
|
|
514
1020
|
games: string[];
|
|
515
1021
|
}>;
|
|
@@ -519,9 +1025,16 @@ declare class HubEconomy extends HubProgress {
|
|
|
519
1025
|
getInventory(): Promise<InventorySnapshot>;
|
|
520
1026
|
/** Another player's available holdings (to compose a trade). */
|
|
521
1027
|
playerInventory(userId: number): Promise<InventorySnapshot>;
|
|
522
|
-
/**
|
|
1028
|
+
/**
|
|
1029
|
+
* Add goods to the player (gameplay rewards); answers the new holdings.
|
|
1030
|
+
* Offline, the cached holdings change at once and the grant replays later.
|
|
1031
|
+
*/
|
|
523
1032
|
grant(bundle: GrantBundle): Promise<EndpointReply<'grantInventoryItems'>>;
|
|
524
|
-
/**
|
|
1033
|
+
/**
|
|
1034
|
+
* An atomic swap with the house: remove `take`, add `give`. Offline it is
|
|
1035
|
+
* checked against the cached holdings (409 when they can't cover `take`),
|
|
1036
|
+
* applied to them at once, and replayed later; the hub has the last word.
|
|
1037
|
+
*/
|
|
525
1038
|
exchange(take?: TradeBundle, give?: TradeBundle): Promise<EndpointReply<'exchangeInventoryItems'>>;
|
|
526
1039
|
/** Sell items to the house for coins. */
|
|
527
1040
|
sell(items: Record<string, number>, coins: number): Promise<EndpointReply<'exchangeInventoryItems'>>;
|
|
@@ -572,6 +1085,506 @@ interface HubMenuControl {
|
|
|
572
1085
|
readonly visible: boolean;
|
|
573
1086
|
}
|
|
574
1087
|
//#endregion
|
|
1088
|
+
//#region src/core/offline/hub-kinds/resume-kind.d.ts
|
|
1089
|
+
/** Send the run saved at `savedAt`; `state` only when the device copy couldn't hold it. */
|
|
1090
|
+
type ResumeSavePayload = {
|
|
1091
|
+
readonly action: 'save';
|
|
1092
|
+
readonly savedAt: number;
|
|
1093
|
+
readonly label: string;
|
|
1094
|
+
readonly fraction: number | null;
|
|
1095
|
+
readonly title: string | null;
|
|
1096
|
+
readonly version: number;
|
|
1097
|
+
readonly state?: Json;
|
|
1098
|
+
};
|
|
1099
|
+
/** Clear the run, unless one was saved after `before`. */
|
|
1100
|
+
type ResumeClearPayload = {
|
|
1101
|
+
readonly action: 'clear';
|
|
1102
|
+
readonly before: number;
|
|
1103
|
+
};
|
|
1104
|
+
/** One resume op. */
|
|
1105
|
+
type ResumePayload = ResumeSavePayload | ResumeClearPayload;
|
|
1106
|
+
//#endregion
|
|
1107
|
+
//#region src/core/resume/resume-types.d.ts
|
|
1108
|
+
/** How far along a run is. */
|
|
1109
|
+
interface ResumeProgress {
|
|
1110
|
+
/** A short line the game writes, such as "Level 12" or "Round 3 of 5" (up to 60 characters). */
|
|
1111
|
+
readonly label: string;
|
|
1112
|
+
/** 0 to 1, drawn as a progress bar; leave it out when the game can't say. */
|
|
1113
|
+
readonly fraction?: number | null;
|
|
1114
|
+
}
|
|
1115
|
+
/** A run to save. `TState` is the game's own JSON-safe state type. */
|
|
1116
|
+
interface ResumeSaveInput<TState extends GamePayload> {
|
|
1117
|
+
/** Everything the game needs to pick the run up again (up to 512 KB as JSON). */
|
|
1118
|
+
readonly state: TState;
|
|
1119
|
+
readonly progress: ResumeProgress;
|
|
1120
|
+
/** An optional name for the run ("Klondike, draw 3"), shown under the game's name. */
|
|
1121
|
+
readonly title?: string | null;
|
|
1122
|
+
/** The game's state version (default 0). Bump it when `state` changes shape; see `load({ version })`. */
|
|
1123
|
+
readonly version?: number;
|
|
1124
|
+
}
|
|
1125
|
+
/** A saved run, read back. */
|
|
1126
|
+
interface ResumeRun<TState> {
|
|
1127
|
+
readonly state: TState;
|
|
1128
|
+
readonly progress: {
|
|
1129
|
+
readonly label: string;
|
|
1130
|
+
readonly fraction: number | null;
|
|
1131
|
+
};
|
|
1132
|
+
readonly title: string | null;
|
|
1133
|
+
readonly version: number;
|
|
1134
|
+
/** When the game saved it, epoch ms. */
|
|
1135
|
+
readonly savedAt: number;
|
|
1136
|
+
}
|
|
1137
|
+
/** Options for reading the run back. */
|
|
1138
|
+
interface ResumeLoadOptions {
|
|
1139
|
+
/**
|
|
1140
|
+
* The state version this code reads. A run saved with an older version is
|
|
1141
|
+
* dropped (cleared) and reads as `null`; a newer one (from a newer copy of
|
|
1142
|
+
* the game in another tab) reads as `null` but is kept.
|
|
1143
|
+
*/
|
|
1144
|
+
readonly version?: number;
|
|
1145
|
+
}
|
|
1146
|
+
/** One saved run in the player's list (the home page's Resume card). */
|
|
1147
|
+
interface ResumeEntry {
|
|
1148
|
+
/** The game's slug. */
|
|
1149
|
+
readonly slug: string;
|
|
1150
|
+
readonly gameTitle: string;
|
|
1151
|
+
/** The game's URL path, like `/solitaire`. */
|
|
1152
|
+
readonly path: string;
|
|
1153
|
+
/** The game has a tile image (`/_tiles/<slug>`). */
|
|
1154
|
+
readonly hasImage: boolean;
|
|
1155
|
+
readonly progress: {
|
|
1156
|
+
readonly label: string;
|
|
1157
|
+
readonly fraction: number | null;
|
|
1158
|
+
};
|
|
1159
|
+
readonly title: string | null;
|
|
1160
|
+
/** When it was saved, epoch ms. */
|
|
1161
|
+
readonly savedAt: number;
|
|
1162
|
+
/** Saved on this device and not yet sent to the hub (offline, or the tab closed first). */
|
|
1163
|
+
readonly isLocalOnly: boolean;
|
|
1164
|
+
}
|
|
1165
|
+
/** Why a save was ignored: the page is in a race or an online room. (`'daily'` is no longer returned: a save during a daily goes to the day.) */
|
|
1166
|
+
type ResumeBlocker = 'race' | 'daily' | 'room' | 'turns';
|
|
1167
|
+
//#endregion
|
|
1168
|
+
//#region src/core/resume/hub-resume.d.ts
|
|
1169
|
+
/** How long saves must be quiet before the latest is sent, ms. */
|
|
1170
|
+
declare const RESUME_DEBOUNCE_MS = 3000;
|
|
1171
|
+
/** Largest state, bytes of JSON (the contract's `RESUME_MAX_STATE_BYTES`). */
|
|
1172
|
+
declare const RESUME_MAX_STATE_BYTES: number;
|
|
1173
|
+
/** What `hub.resume` needs from its client. */
|
|
1174
|
+
interface ResumeHost {
|
|
1175
|
+
/** This game's slug; `null` on the home page. */
|
|
1176
|
+
readonly slug: string | null;
|
|
1177
|
+
readonly local: LocalResumeStore;
|
|
1178
|
+
/** The signed-in player's id as the device knows it, or `null`. */
|
|
1179
|
+
currentUserId(): number | null;
|
|
1180
|
+
/** The game's display title. */
|
|
1181
|
+
gameTitle(): string;
|
|
1182
|
+
/** Why saving is off right now, or `null`. */
|
|
1183
|
+
blocker(): ResumeBlocker | null;
|
|
1184
|
+
/** The page's daily challenge while one is being played (saves go to the day instead of the run), else `null`. */
|
|
1185
|
+
daily?(): DailyRoute | null;
|
|
1186
|
+
/** Perform a resume op: at once when online with nothing waiting, else queued. */
|
|
1187
|
+
send(payload: ResumePayload): Promise<void>;
|
|
1188
|
+
/** This game's run on the hub, or `undefined` when the hub can't be reached. */
|
|
1189
|
+
fetchSlot(): Promise<ResumeSlot | null | undefined>;
|
|
1190
|
+
/** The player's runs on the hub, or `undefined` when it can't be reached. */
|
|
1191
|
+
fetchList(): Promise<ResumeSummary[] | undefined>;
|
|
1192
|
+
/** Clear another game's run on the hub (the home page's discard); throws offline. */
|
|
1193
|
+
clearOther(slug: string): Promise<void>;
|
|
1194
|
+
/** The run changed here (saved or cleared): `hub.saved` listeners hear it. */
|
|
1195
|
+
onChange?(): void;
|
|
1196
|
+
}
|
|
1197
|
+
/** Where a save goes during a daily challenge (`hub.daily`). */
|
|
1198
|
+
interface DailyRoute {
|
|
1199
|
+
save(input: ResumeSaveInput<GamePayload>): boolean;
|
|
1200
|
+
}
|
|
1201
|
+
/** `hub.resume`. */
|
|
1202
|
+
declare class HubResume {
|
|
1203
|
+
private readonly host;
|
|
1204
|
+
private timer;
|
|
1205
|
+
private waiting;
|
|
1206
|
+
private isListening;
|
|
1207
|
+
private readonly warnedBlockers;
|
|
1208
|
+
constructor(host: ResumeHost);
|
|
1209
|
+
/**
|
|
1210
|
+
* `true` when the page was opened from the Resume card (`?hub_resume=1`,
|
|
1211
|
+
* taken out of the address bar). Synchronous, so a game can restore before
|
|
1212
|
+
* it routes or draws its title screen.
|
|
1213
|
+
*/
|
|
1214
|
+
get requested(): boolean;
|
|
1215
|
+
/** Why a save would be ignored right now (`race`, `daily`, `room`), or `null`. */
|
|
1216
|
+
get blockedBy(): ResumeBlocker | null;
|
|
1217
|
+
/**
|
|
1218
|
+
* Save the run (the latest save wins). Stored on the device at once; sent
|
|
1219
|
+
* after {@link RESUME_DEBOUNCE_MS} of quiet, or when the page is hidden.
|
|
1220
|
+
* Returns `false` (and saves nothing) in a race or an online room; during
|
|
1221
|
+
* a daily challenge the save goes to the day (`hub.daily.save`). Throws a
|
|
1222
|
+
* `HubError` 413 over 512 KB, 400 without a label.
|
|
1223
|
+
*/
|
|
1224
|
+
save<TState extends GamePayload>(run: ResumeSaveInput<TState>): boolean;
|
|
1225
|
+
/** Send a waiting save now (the page does this itself when hidden or closed). */
|
|
1226
|
+
flush(): Promise<void>;
|
|
1227
|
+
/** Clear the run: call it when the run ends (won, lost or quit). Works offline. */
|
|
1228
|
+
clear(): Promise<void>;
|
|
1229
|
+
/** Delete the game's saved run (never the day being played). */
|
|
1230
|
+
private clearRun;
|
|
1231
|
+
/** The saved run with its progress and time, or `null`. The newer of the device's copy and the hub's wins. */
|
|
1232
|
+
get<TState = GamePayload>(options?: ResumeLoadOptions): Promise<ResumeRun<TState> | null>;
|
|
1233
|
+
/** The saved state, or `null` (see {@link get} for the version rule). */
|
|
1234
|
+
load<TState = GamePayload>(options?: ResumeLoadOptions): Promise<TState | null>;
|
|
1235
|
+
/** The player's saved runs in every resumable game, newest first (the home page's Resume card). */
|
|
1236
|
+
list(): Promise<ResumeEntry[]>;
|
|
1237
|
+
/** Discard another game's run from the home page (online only: throws a `HubError` offline). */
|
|
1238
|
+
discard(slug: string): Promise<void>;
|
|
1239
|
+
/** Say once per blocker why saves are ignored, so a game that forgot `hub.daily.cancel()` finds out. */
|
|
1240
|
+
private warnBlocked;
|
|
1241
|
+
private requireSlug;
|
|
1242
|
+
private schedule;
|
|
1243
|
+
/** Send a waiting save as soon as the page is hidden or closed. */
|
|
1244
|
+
private listenToPage;
|
|
1245
|
+
}
|
|
1246
|
+
//#endregion
|
|
1247
|
+
//#region src/core/saved/saved-list.d.ts
|
|
1248
|
+
/** What the device holds, for one player. */
|
|
1249
|
+
interface DeviceCopies {
|
|
1250
|
+
/** Run copies by slug (`hub.resume`). */
|
|
1251
|
+
readonly runs: ReadonlyMap<string, LocalResume>;
|
|
1252
|
+
/** Daily copies (`hub.daily`), any game. */
|
|
1253
|
+
readonly dailies: readonly LocalDaily[];
|
|
1254
|
+
}
|
|
1255
|
+
//#endregion
|
|
1256
|
+
//#region src/core/saved/hub-saved.d.ts
|
|
1257
|
+
/** How long changes must be quiet before `on('change')` listeners hear the new list, ms. */
|
|
1258
|
+
declare const SAVED_CHANGE_QUIET_MS = 300;
|
|
1259
|
+
/** What `hub.saved` needs from its client. */
|
|
1260
|
+
interface SavedHost {
|
|
1261
|
+
/** This game's slug; `null` on the home page. */
|
|
1262
|
+
readonly slug: string | null;
|
|
1263
|
+
/** The hub's list (one game, or all), or `undefined` when it can't be reached. */
|
|
1264
|
+
fetchList(game: string | null): Promise<readonly SavedEntry[] | undefined>;
|
|
1265
|
+
/** What this device holds for the signed-in player. */
|
|
1266
|
+
deviceCopies(): DeviceCopies;
|
|
1267
|
+
/** Discard a daily or run (works offline where it can). */
|
|
1268
|
+
discard(entry: SavedEntry): Promise<void>;
|
|
1269
|
+
/** Go to a URL (`location.assign`). */
|
|
1270
|
+
navigate(url: string): void;
|
|
1271
|
+
}
|
|
1272
|
+
/** A change listener: this game's games in progress. */
|
|
1273
|
+
type SavedChangeListener = (games: SavedEntry[]) => void;
|
|
1274
|
+
/** An `onOpen` handler: switch to this instance without a reload. */
|
|
1275
|
+
type SavedOpenHandler = (instance: SavedEntry) => void | Promise<void>;
|
|
1276
|
+
/** The detail of {@link SAVED_OPEN_EVENT}. */
|
|
1277
|
+
interface SavedOpenDetail {
|
|
1278
|
+
readonly entry: SavedEntry;
|
|
1279
|
+
}
|
|
1280
|
+
/** `hub.saved`. */
|
|
1281
|
+
declare class HubSaved {
|
|
1282
|
+
private readonly host;
|
|
1283
|
+
/** Each handler with the kinds it takes (every kind when `undefined`). */
|
|
1284
|
+
private readonly openHandlers;
|
|
1285
|
+
private readonly changeListeners;
|
|
1286
|
+
private isListeningForOpen;
|
|
1287
|
+
private isListeningForChange;
|
|
1288
|
+
private changeTimer;
|
|
1289
|
+
constructor(host: SavedHost);
|
|
1290
|
+
/**
|
|
1291
|
+
* The instance this page was opened for (`?hub_open=<id>&hub_kind=<kind>`,
|
|
1292
|
+
* taken out of the address bar), or `null`. Synchronous: read it before
|
|
1293
|
+
* routing or drawing a title screen.
|
|
1294
|
+
*/
|
|
1295
|
+
get opened(): SavedOpened | null;
|
|
1296
|
+
/** This game's games in progress: online games waiting on the player first, then the newest. Works offline. */
|
|
1297
|
+
list(options?: SavedListOptions): Promise<SavedEntry[]>;
|
|
1298
|
+
/** Every game's games in progress (the home page and the menu). Online, plus this device's copies. */
|
|
1299
|
+
listAll(): Promise<SavedEntry[]>;
|
|
1300
|
+
/** The badge counts across every game. */
|
|
1301
|
+
counts(): Promise<SavedCounts>;
|
|
1302
|
+
/**
|
|
1303
|
+
* Switch to an instance of THIS game when the player picks it from the
|
|
1304
|
+
* hub menu, without a reload. Without a handler the page reloads with
|
|
1305
|
+
* `?hub_open=<id>`. With `{ kinds }` the handler takes only those kinds;
|
|
1306
|
+
* the others still reload the page (with `?hub_resume=1` or `?daily=` for
|
|
1307
|
+
* a saved run or a daily). Returns a function that removes the handler.
|
|
1308
|
+
*/
|
|
1309
|
+
onOpen(handler: SavedOpenHandler, options?: SavedListOptions): () => void;
|
|
1310
|
+
/** Open an instance: in this game through `onOpen` (or a reload), in another game by going there. */
|
|
1311
|
+
open(id: string): Promise<void>;
|
|
1312
|
+
/** Open an entry already in hand (the hub's lists). */
|
|
1313
|
+
openEntry(entry: SavedEntry): void;
|
|
1314
|
+
/**
|
|
1315
|
+
* Discard a daily challenge or a saved run (the caller asks first; the
|
|
1316
|
+
* hub's own lists ask for themselves). Online games are left with
|
|
1317
|
+
* `hub.turns`, never discarded.
|
|
1318
|
+
*/
|
|
1319
|
+
discard(id: string): Promise<void>;
|
|
1320
|
+
/** Hear when this game's list changes; returns a function that stops listening. */
|
|
1321
|
+
on(_eventName: 'change', listener: SavedChangeListener): () => void;
|
|
1322
|
+
/** Something of this game's changed (a save here, an event from the hub): tell the listeners, once per quiet spell. */
|
|
1323
|
+
changed(): void;
|
|
1324
|
+
private listenForOpen;
|
|
1325
|
+
private listenForChange;
|
|
1326
|
+
}
|
|
1327
|
+
//#endregion
|
|
1328
|
+
//#region src/core/turns/turn-game.d.ts
|
|
1329
|
+
/** The window event the social runtime fires for each `ev:turn`. */
|
|
1330
|
+
declare const TURN_EVENT = "hub:turn";
|
|
1331
|
+
/** The runtime's live connection (re)opened: anything sent while it was down is fetched again. */
|
|
1332
|
+
declare const TURN_RESYNC_EVENT = "hub:turn-resync";
|
|
1333
|
+
/** The window event the social runtime fires for each `ev:turn.chat`. */
|
|
1334
|
+
declare const TURN_CHAT_EVENT = "hub:turn-chat";
|
|
1335
|
+
/** How often a page without the live runtime looks for moves, ms. */
|
|
1336
|
+
declare const TURN_POLL_MS = 20000;
|
|
1337
|
+
/** How long a visible page waiting on another player's move (or in a lobby) may hear nothing before it fetches the game again, ms. */
|
|
1338
|
+
declare const TURN_QUIET_MS = 15000;
|
|
1339
|
+
/** A game's end: the winning seats, and optionally places and scores by seat. */
|
|
1340
|
+
type TurnOutcome = NonNullable<TurnGameView['outcome']>;
|
|
1341
|
+
/** One move as this seat saw it. */
|
|
1342
|
+
interface TurnMoveEvent<View, Ev> {
|
|
1343
|
+
readonly seq: number;
|
|
1344
|
+
readonly seat: number;
|
|
1345
|
+
/** The view after the move (for a replayed move, the view now). */
|
|
1346
|
+
readonly view: View;
|
|
1347
|
+
readonly events: readonly Ev[];
|
|
1348
|
+
}
|
|
1349
|
+
/** What a game's listeners hear. */
|
|
1350
|
+
interface TurnGameEvents<View, Ev> {
|
|
1351
|
+
move: TurnMoveEvent<View, Ev>;
|
|
1352
|
+
turn: TurnSummary;
|
|
1353
|
+
ended: {
|
|
1354
|
+
readonly outcome: TurnOutcome | null;
|
|
1355
|
+
readonly view: View;
|
|
1356
|
+
};
|
|
1357
|
+
chat: TurnChatLine;
|
|
1358
|
+
}
|
|
1359
|
+
/** What a game needs from its client: the REST calls, by name. */
|
|
1360
|
+
interface TurnGameHost {
|
|
1361
|
+
get(id: string): Promise<TurnGameView>;
|
|
1362
|
+
start(id: string): Promise<TurnGameView>;
|
|
1363
|
+
move(id: string, seq: number, move: Json): Promise<{
|
|
1364
|
+
game: TurnGameView;
|
|
1365
|
+
events: Json[];
|
|
1366
|
+
}>;
|
|
1367
|
+
history(id: string, since: number): Promise<{
|
|
1368
|
+
seq: number;
|
|
1369
|
+
seat: number;
|
|
1370
|
+
events: Json[];
|
|
1371
|
+
}[]>;
|
|
1372
|
+
leave(id: string): Promise<void>;
|
|
1373
|
+
nudge(id: string): Promise<void>;
|
|
1374
|
+
setNotify(id: string, mode: 'on' | 'off'): Promise<void>;
|
|
1375
|
+
rematch(id: string): Promise<TurnGameView>;
|
|
1376
|
+
invite(id: string, userIds: readonly number[]): Promise<TurnGameView>;
|
|
1377
|
+
share(id: string, share: {
|
|
1378
|
+
readonly link?: boolean;
|
|
1379
|
+
readonly public?: boolean;
|
|
1380
|
+
}): Promise<TurnGameView>;
|
|
1381
|
+
autoPlay(id: string): Promise<TurnGameView>;
|
|
1382
|
+
chat(id: string, send: {
|
|
1383
|
+
readonly text?: string;
|
|
1384
|
+
readonly quick?: string;
|
|
1385
|
+
}): Promise<TurnChatLine>;
|
|
1386
|
+
/** The page has a game open (saves of a run are off while it does). */
|
|
1387
|
+
opened(game: object): void;
|
|
1388
|
+
closed(game: object): void;
|
|
1389
|
+
/** Whether the live runtime is on the page (else the game polls). */
|
|
1390
|
+
isLive(): boolean;
|
|
1391
|
+
}
|
|
1392
|
+
type Listener<Payload> = (payload: Payload) => void;
|
|
1393
|
+
/** How `hub.turns` sets a game up. */
|
|
1394
|
+
interface TurnGameOptions {
|
|
1395
|
+
/** Whether this page wants the "your turn" buzz (`hub.turns.setYourTurnBuzz`); read at each buzz. Default on. */
|
|
1396
|
+
readonly isBuzzOn?: () => boolean;
|
|
1397
|
+
}
|
|
1398
|
+
/** One online game. `View`, `Move` and `Ev` (its events) are the game's own JSON types from its rules. */
|
|
1399
|
+
declare class TurnGame<View = GamePayload, Move extends GamePayload = GamePayload, Ev = GamePayload> {
|
|
1400
|
+
readonly id: string;
|
|
1401
|
+
private state;
|
|
1402
|
+
private readonly host;
|
|
1403
|
+
private readonly listeners;
|
|
1404
|
+
private readonly stopListening;
|
|
1405
|
+
private isClosed;
|
|
1406
|
+
private catchingUp;
|
|
1407
|
+
/** The move count this page's own move in flight will make (its live echo is not another player's move). */
|
|
1408
|
+
private ownSeq;
|
|
1409
|
+
/** When the page last heard from the hub about this game (a live event or an answer), epoch ms. */
|
|
1410
|
+
private heardAt;
|
|
1411
|
+
private readonly options;
|
|
1412
|
+
/** This game's own "your turn" buzz switch (`setYourTurnBuzz`). */
|
|
1413
|
+
private isBuzzOn;
|
|
1414
|
+
constructor(host: TurnGameHost, initial: TurnGameView, options?: TurnGameOptions);
|
|
1415
|
+
/** Whose move it is, the seats and the deadline. */
|
|
1416
|
+
get summary(): TurnSummary;
|
|
1417
|
+
/** This seat's view, from the game's rules (`null` before the game starts). */
|
|
1418
|
+
get view(): View;
|
|
1419
|
+
/** This player's seat, or `null` (they left). */
|
|
1420
|
+
get mySeat(): number | null;
|
|
1421
|
+
/** The end, once the game is over. */
|
|
1422
|
+
get outcome(): TurnOutcome | null;
|
|
1423
|
+
/** The last chat lines, oldest first. */
|
|
1424
|
+
get chat(): readonly TurnChatLine[];
|
|
1425
|
+
/** `on`: friends may type (moderated); `quick`: quick-chat and emotes only; `off`. */
|
|
1426
|
+
get chatMode(): TurnGameView['chatMode'];
|
|
1427
|
+
/** The link others can take a seat with (the creator's, while seats are open), else `null`. */
|
|
1428
|
+
get link(): string | null;
|
|
1429
|
+
/** Whether this player may move now without it being their turn (the rules' `mayAct`: a jump-in, calling or catching "UNO"): offer that action. */
|
|
1430
|
+
get mayActNow(): boolean;
|
|
1431
|
+
/** The tournament match this game is, or `null` (show it if you like: "Saturday Cup: your match"). */
|
|
1432
|
+
get tournament(): TurnTournament | null;
|
|
1433
|
+
/**
|
|
1434
|
+
* The "your turn" buzz for this game: a short vibration when this player
|
|
1435
|
+
* becomes due on a live update while the page is visible (never for the
|
|
1436
|
+
* next step of their own turn, a move out of turn, a game just opened or a
|
|
1437
|
+
* catch-up; never when the player turned it off or asks for less motion;
|
|
1438
|
+
* silently nothing where the device can't vibrate). On by default.
|
|
1439
|
+
*/
|
|
1440
|
+
setYourTurnBuzz(isOn: boolean): void;
|
|
1441
|
+
/** Creator: start the game (empty seats go to the computer when the game allows). */
|
|
1442
|
+
start(): Promise<void>;
|
|
1443
|
+
/**
|
|
1444
|
+
* Make a move: when it is your turn (`summary.isMyTurn`), or out of turn
|
|
1445
|
+
* when the rules allow it (`mayActNow`). Rejects with a `HubError`: 403
|
|
1446
|
+
* `not_your_turn`, 409 `stale` (the game moved on, or another player's
|
|
1447
|
+
* move got there first: the view is refreshed first), 422 `illegal` with
|
|
1448
|
+
* the rules' own code in `rule`, 500 `rules_error` or 503 `rules_busy`
|
|
1449
|
+
* (the move was not made; the game is fetched again). The answer's `view` is the newest this
|
|
1450
|
+
* page has: moves that arrived live while the answer was on its way (the
|
|
1451
|
+
* computer's, another player's) are never rolled back.
|
|
1452
|
+
*/
|
|
1453
|
+
move(move: Move): Promise<{
|
|
1454
|
+
seq: number;
|
|
1455
|
+
view: View;
|
|
1456
|
+
events: Ev[];
|
|
1457
|
+
}>;
|
|
1458
|
+
/** The moves after `sinceSeq`, with their events as this seat may see them ("what happened while you were away"). */
|
|
1459
|
+
history(sinceSeq: number): Promise<{
|
|
1460
|
+
seq: number;
|
|
1461
|
+
seat: number;
|
|
1462
|
+
events: Ev[];
|
|
1463
|
+
}[]>;
|
|
1464
|
+
/** Leave: the computer plays the seat (or, without bots, the others win). The player can't come back. */
|
|
1465
|
+
leave(): Promise<void>;
|
|
1466
|
+
/** Remind whoever must move (once a day). */
|
|
1467
|
+
nudge(): Promise<void>;
|
|
1468
|
+
/** This game's "your turn" alerts. */
|
|
1469
|
+
setNotify(mode: 'on' | 'off'): Promise<void>;
|
|
1470
|
+
/** A finished game: a new one with the same friends (they get an invite). */
|
|
1471
|
+
rematch(): Promise<TurnGame<View, Move, Ev>>;
|
|
1472
|
+
/** Creator, before the start: add an invite link (`link: true`, then read `link`) or list the open seats for anyone (`public`). */
|
|
1473
|
+
share(share: {
|
|
1474
|
+
readonly link?: boolean;
|
|
1475
|
+
readonly public?: boolean;
|
|
1476
|
+
}): Promise<void>;
|
|
1477
|
+
/** Invite friends to free seats (before the start). */
|
|
1478
|
+
invite(userIds: readonly number[]): Promise<void>;
|
|
1479
|
+
/**
|
|
1480
|
+
* Whether "Let the computer play for <name>" may work now: another
|
|
1481
|
+
* player is due, the hub's hand-over wait (`summary.handOverAt`) has
|
|
1482
|
+
* passed and a due player isn't connected (`TurnSeat.connected` false
|
|
1483
|
+
* or unknown), or the turn's `deadline` passed. `false` in a
|
|
1484
|
+
* tournament match, and from a hub too old to say. Re-check it on each
|
|
1485
|
+
* `turn` event (a player coming or going fires one) and when
|
|
1486
|
+
* `handOverAt` comes.
|
|
1487
|
+
*/
|
|
1488
|
+
canLetComputerPlay(now?: number): boolean;
|
|
1489
|
+
/**
|
|
1490
|
+
* Let the computer play for whoever is due: a turn whose deadline passed
|
|
1491
|
+
* (or whose player is suspended) is played once; otherwise, when that
|
|
1492
|
+
* player has been due a while (2 minutes) and isn't connected at all, the
|
|
1493
|
+
* computer plays their seat for the rest of the game (it shows as `bot`,
|
|
1494
|
+
* named with who chose it) until they open the game again. Rejects with
|
|
1495
|
+
* 409 `deadline_not_passed` when neither (it never says whether they are
|
|
1496
|
+
* online), and 409 `not_supported` in a tournament match.
|
|
1497
|
+
*/
|
|
1498
|
+
letComputerPlay(): Promise<void>;
|
|
1499
|
+
/** Say something: `{ text }` between friends (moderated), `{ quick }` (a quick-chat phrase id) with anyone. */
|
|
1500
|
+
say(send: {
|
|
1501
|
+
readonly text?: string;
|
|
1502
|
+
readonly quick?: string;
|
|
1503
|
+
}): Promise<TurnChatLine>;
|
|
1504
|
+
/**
|
|
1505
|
+
* Fetch the game again, and replay moves missed since the last one this
|
|
1506
|
+
* page saw. An answer older than what the page has by then (live moves
|
|
1507
|
+
* arrived while it was on its way) is dropped, never rolled back to, and
|
|
1508
|
+
* a move that arrived live is never replayed twice.
|
|
1509
|
+
*/
|
|
1510
|
+
refresh(): Promise<void>;
|
|
1511
|
+
/** Hear moves, turn changes, the end and chat; returns a function that stops listening. */
|
|
1512
|
+
on<K extends keyof TurnGameEvents<View, Ev>>(eventName: K, listener: Listener<TurnGameEvents<View, Ev>[K]>): () => void;
|
|
1513
|
+
/** Stop live updates for this page (the game itself goes on). */
|
|
1514
|
+
close(): void;
|
|
1515
|
+
/** Take a fresh answer from the hub (one older than what the page has is dropped: no rollback); fire `turn` and `ended` when they changed. */
|
|
1516
|
+
private update;
|
|
1517
|
+
private emit;
|
|
1518
|
+
/** Whether the page is waiting on someone: another player's move, or a lobby. */
|
|
1519
|
+
private isWaitingOnOthers;
|
|
1520
|
+
/** A live `hub:turn` for this game: a move (or a missed run of them), or another change. */
|
|
1521
|
+
private onLive;
|
|
1522
|
+
/** Replay what was missed (one catch-up at a time). */
|
|
1523
|
+
private catchUp;
|
|
1524
|
+
private listen;
|
|
1525
|
+
}
|
|
1526
|
+
//#endregion
|
|
1527
|
+
//#region src/core/turns/hub-turns.d.ts
|
|
1528
|
+
/** The query parameter an invite link carries (`/<slug>/?hub_turn_link=<key>`); the client strips it. */
|
|
1529
|
+
declare const TURN_LINK_PARAM = "hub_turn_link";
|
|
1530
|
+
/** How to make a game. */
|
|
1531
|
+
interface TurnCreateOptions {
|
|
1532
|
+
readonly mode?: string;
|
|
1533
|
+
/** The game's own setup choices (at most 2 KB of JSON). */
|
|
1534
|
+
readonly options?: GamePayload;
|
|
1535
|
+
/** Friends to invite (their seats are held). */
|
|
1536
|
+
readonly invite?: readonly number[];
|
|
1537
|
+
/** Make a link others can take a seat with (`game.link`). */
|
|
1538
|
+
readonly link?: boolean;
|
|
1539
|
+
/** List the open seats in Play online for anyone to take. */
|
|
1540
|
+
readonly public?: boolean;
|
|
1541
|
+
/** Seats in all (within `game.turns.players`). */
|
|
1542
|
+
readonly seats?: number;
|
|
1543
|
+
/** Let the computer fill seats nobody took (default true, when the game has bots). */
|
|
1544
|
+
readonly bots?: boolean;
|
|
1545
|
+
/** How long a turn may wait before the others may let the computer play it (default none). */
|
|
1546
|
+
readonly deadline?: TurnDeadline;
|
|
1547
|
+
/** Start by itself once every seat is taken (the creator needn't come back). */
|
|
1548
|
+
readonly autoStart?: boolean;
|
|
1549
|
+
}
|
|
1550
|
+
/** What `hub.turns` needs from its client. */
|
|
1551
|
+
interface TurnsHost extends TurnGameHost {
|
|
1552
|
+
readonly slug: string | null;
|
|
1553
|
+
create(body: CreateTurnGame): ReturnType<TurnGameHost['get']>;
|
|
1554
|
+
join(id: string): ReturnType<TurnGameHost['get']>;
|
|
1555
|
+
joinByLink(key: string): ReturnType<TurnGameHost['get']>;
|
|
1556
|
+
listOpen(game: string | null): Promise<OpenTurnGame[]>;
|
|
1557
|
+
list(): Promise<SavedEntry[]>;
|
|
1558
|
+
}
|
|
1559
|
+
/** `hub.turns`. */
|
|
1560
|
+
declare class HubTurns {
|
|
1561
|
+
private readonly host;
|
|
1562
|
+
private isBuzzOn;
|
|
1563
|
+
private readonly gameOptions;
|
|
1564
|
+
constructor(host: TurnsHost);
|
|
1565
|
+
/**
|
|
1566
|
+
* This page's "your turn" buzz (a short vibration when the player becomes
|
|
1567
|
+
* due in an open game, see `TurnGame.setYourTurnBuzz`): pass `false` if
|
|
1568
|
+
* your game gives its own signal. On by default.
|
|
1569
|
+
*/
|
|
1570
|
+
setYourTurnBuzz(isOn: boolean): void;
|
|
1571
|
+
/** The player's own "Buzz when it's my turn" switch on this device (the menu's Settings show it too). */
|
|
1572
|
+
get playerYourTurnBuzz(): boolean;
|
|
1573
|
+
set playerYourTurnBuzz(isOn: boolean);
|
|
1574
|
+
/** The invite link key this page was opened with (`?hub_turn_link=`), taken out of the address bar; `null` when none. Synchronous. */
|
|
1575
|
+
get link(): string | null;
|
|
1576
|
+
/** Make a game in this page's game (its lobby): the player takes the first seat. */
|
|
1577
|
+
create<View = GamePayload, Move extends GamePayload = GamePayload, Event = GamePayload>(options?: TurnCreateOptions): Promise<TurnGame<View, Move, Event>>;
|
|
1578
|
+
/** Take a seat: by the game's id (an invite, an open seat) or by an invite link's key. */
|
|
1579
|
+
join<View = GamePayload, Move extends GamePayload = GamePayload, Event = GamePayload>(idOrLinkKey: string): Promise<TurnGame<View, Move, Event>>;
|
|
1580
|
+
/** Open a game the player sits in (from `hub.saved.opened`, the Continue list, an invite). */
|
|
1581
|
+
open<View = GamePayload, Move extends GamePayload = GamePayload, Event = GamePayload>(id: string): Promise<TurnGame<View, Move, Event>>;
|
|
1582
|
+
/** This game's online games the player sits in (the same rows as `hub.saved.list({ kinds: ['turns'] })`). */
|
|
1583
|
+
list(): Promise<SavedEntry[]>;
|
|
1584
|
+
/** Open-seat games of this game anyone may join (none across a block). */
|
|
1585
|
+
openLobbies(): Promise<OpenTurnGame[]>;
|
|
1586
|
+
}
|
|
1587
|
+
//#endregion
|
|
575
1588
|
//#region src/core/version/hub-version.d.ts
|
|
576
1589
|
/** How a hub regards a client version. */
|
|
577
1590
|
type ClientSupport =
|
|
@@ -589,6 +1602,13 @@ type ClientSupport =
|
|
|
589
1602
|
declare class Hub extends HubEconomy {
|
|
590
1603
|
/** This client's package version (sent as `x-hub-client`). */
|
|
591
1604
|
readonly clientVersion: string;
|
|
1605
|
+
private resumeApi;
|
|
1606
|
+
private avatarApi;
|
|
1607
|
+
private savedApi;
|
|
1608
|
+
private dailySavesApi;
|
|
1609
|
+
private turnsApi;
|
|
1610
|
+
/** The online games open on this page (a run isn't saved while one is). */
|
|
1611
|
+
private readonly openTurnGames;
|
|
592
1612
|
/**
|
|
593
1613
|
* The hub's version and the client versions it supports (`GET /_api/version`),
|
|
594
1614
|
* read once per page. `null` for a hub from before versioning.
|
|
@@ -624,7 +1644,41 @@ declare class Hub extends HubEconomy {
|
|
|
624
1644
|
get notify(): typeof notifyForwarder;
|
|
625
1645
|
/** Races: a single-player game becomes multiplayer by racing. */
|
|
626
1646
|
get race(): typeof raceForwarder;
|
|
627
|
-
/**
|
|
1647
|
+
/**
|
|
1648
|
+
* Resume a saved game: `hub.resume.save({ state, progress })` during a long
|
|
1649
|
+
* single-player run, `clear()` when it ends, `load()` and `requested` to
|
|
1650
|
+
* pick it up again (docs: "Resume saved games"). Needs `game.resumable: true`.
|
|
1651
|
+
*/
|
|
1652
|
+
get resume(): HubResume;
|
|
1653
|
+
/**
|
|
1654
|
+
* Players' avatars: `hub.avatar.mine()`, `of(userId)`, `from(seat)`,
|
|
1655
|
+
* `bot(seed)`, `on('change')`, `openDesigner()` and `load()` for the renderer
|
|
1656
|
+
* (docs: "Avatars"). No art or renderer until `load()`.
|
|
1657
|
+
*/
|
|
1658
|
+
get avatar(): HubAvatar;
|
|
1659
|
+
/**
|
|
1660
|
+
* Every game the player has in progress, of every kind: `hub.saved.list()`
|
|
1661
|
+
* for this game's "Continue" list, `opened` and `onOpen` to open the one
|
|
1662
|
+
* the player picked (docs: "Saved games").
|
|
1663
|
+
*/
|
|
1664
|
+
get saved(): HubSaved;
|
|
1665
|
+
/**
|
|
1666
|
+
* Online games played over minutes or days, run by the hub from the
|
|
1667
|
+
* game's rules module (`game.turns`): `hub.turns.create`, `join`, `open`
|
|
1668
|
+
* (docs/multiplayer.md "Turn games").
|
|
1669
|
+
*/
|
|
1670
|
+
get turns(): HubTurns;
|
|
1671
|
+
/** What `hub.turns` needs from this client: its REST calls. */
|
|
1672
|
+
private turnsHost;
|
|
1673
|
+
/** What `hub.saved` needs from this client. */
|
|
1674
|
+
private savedHost;
|
|
1675
|
+
/** Discard a daily or run: this game's run through `hub.resume` (works offline), others on the hub. */
|
|
1676
|
+
private discardSaved;
|
|
1677
|
+
/** What `hub.resume` needs from this client. */
|
|
1678
|
+
private resumeHost;
|
|
1679
|
+
/** This game's daily saves (device copies and their sends). */
|
|
1680
|
+
private dailySaves;
|
|
1681
|
+
/** What the daily system needs from this client: its REST calls, the device copies and window events. */
|
|
628
1682
|
private dailyHost;
|
|
629
1683
|
}
|
|
630
1684
|
/** A ready-to-use client, with the slug and API base detected from the page. */
|
|
@@ -640,12 +1694,18 @@ declare function createHub(options?: HubOptions): Hub;
|
|
|
640
1694
|
declare const UNSUPPORTED_BY_HUB = "unsupported_by_hub";
|
|
641
1695
|
/** The code the social runtime gives a call it could not send (no connection). */
|
|
642
1696
|
declare const OFFLINE_CODE = "offline";
|
|
1697
|
+
/**
|
|
1698
|
+
* The code `getSave` rejects with when the hub can't be reached and this
|
|
1699
|
+
* device has no copy of the slot it can trust (a fresh device or cleared
|
|
1700
|
+
* browser): the slot may well hold a save, so it is not reported empty.
|
|
1701
|
+
*/
|
|
1702
|
+
declare const SAVE_UNKNOWN_CODE = "save_unknown";
|
|
643
1703
|
/**
|
|
644
1704
|
* A machine-readable error code: one of the contract's public codes
|
|
645
1705
|
* (`content_rejected`, `rate_limited`, `suspended`, `claim_required`, ...),
|
|
646
1706
|
* {@link UNSUPPORTED_BY_HUB}, or a code a newer hub added.
|
|
647
1707
|
*/
|
|
648
|
-
type HubErrorCode = ErrorCode | typeof UNSUPPORTED_BY_HUB | typeof OFFLINE_CODE | (string & Record<never, never>);
|
|
1708
|
+
type HubErrorCode = ErrorCode | typeof UNSUPPORTED_BY_HUB | typeof OFFLINE_CODE | typeof SAVE_UNKNOWN_CODE | (string & Record<never, never>);
|
|
649
1709
|
/** The machine-readable extras the hub attaches to an error body. */
|
|
650
1710
|
interface HubErrorDetails {
|
|
651
1711
|
/** A stable error code, for code to branch on. */
|
|
@@ -658,6 +1718,8 @@ interface HubErrorDetails {
|
|
|
658
1718
|
readonly retryAfterMs?: number | undefined;
|
|
659
1719
|
/** When a suspension or mute ends (epoch ms). */
|
|
660
1720
|
readonly until?: number | undefined;
|
|
1721
|
+
/** An online game's rules refused a move (`illegal`): the rules' own short code. */
|
|
1722
|
+
readonly rule?: string | undefined;
|
|
661
1723
|
}
|
|
662
1724
|
/**
|
|
663
1725
|
* Thrown by every hub call that fails. `status` is the HTTP status when the
|
|
@@ -678,6 +1740,8 @@ declare class HubError extends Error {
|
|
|
678
1740
|
readonly retryAfterMs: number | undefined;
|
|
679
1741
|
/** When a suspension or mute ends (epoch ms). */
|
|
680
1742
|
readonly until: number | undefined;
|
|
1743
|
+
/** An online game's rules refused a move (`code` is `illegal`): the rules' own short code, such as `cell_taken`. */
|
|
1744
|
+
readonly rule: string | undefined;
|
|
681
1745
|
/** An error with a player-safe `message`, the HTTP `status` (if any) and the body's extras. */
|
|
682
1746
|
constructor(message: string, status?: number, details?: HubErrorDetails);
|
|
683
1747
|
/** `true` when the failure was a network or transport error, not an HTTP status. */
|
|
@@ -698,9 +1762,21 @@ declare class ContentRejectedError extends HubError {
|
|
|
698
1762
|
constructor(message: string, status: number | undefined, details: HubErrorDetails);
|
|
699
1763
|
}
|
|
700
1764
|
//#endregion
|
|
1765
|
+
//#region src/core/turns/your-turn-buzz.d.ts
|
|
1766
|
+
/** The pattern: two short pulses, ms. */
|
|
1767
|
+
declare const YOUR_TURN_BUZZ: readonly number[];
|
|
1768
|
+
/** Where this device keeps the player's choice (`off` turns it off). */
|
|
1769
|
+
declare const YOUR_TURN_BUZZ_KEY = "dg:your-turn-buzz";
|
|
1770
|
+
/** Whether going from `previous` to `next` makes this player newly due (the moment to buzz). */
|
|
1771
|
+
declare function isNewlyDue(previous: Pick<TurnSummary, 'phase' | 'isMyTurn'>, next: Pick<TurnSummary, 'phase' | 'isMyTurn' | 'isMuted'>): boolean;
|
|
1772
|
+
/** The player's own switch on this device (on unless they turned it off). */
|
|
1773
|
+
declare function readYourTurnBuzz(): boolean;
|
|
1774
|
+
/** Save the player's switch on this device. */
|
|
1775
|
+
declare function saveYourTurnBuzz(isOn: boolean): void;
|
|
1776
|
+
//#endregion
|
|
701
1777
|
//#region src/core/version/client-version.gen.d.ts
|
|
702
1778
|
/** This client's package version, sent as `x-hub-client` on REST calls and `client` in `hello`. */
|
|
703
|
-
declare const HUB_CLIENT_VERSION = "2.0.0-next.
|
|
1779
|
+
declare const HUB_CLIENT_VERSION = "2.0.0-next.10";
|
|
704
1780
|
//#endregion
|
|
705
|
-
export {
|
|
1781
|
+
export { TradeListOptions as $, AvatarModule as $t, TurnGame as A, HubOptions as At, HubResume as B, ProfileSummary as Bt, TURN_LINK_PARAM as C, Board$1 as Ct, TURN_POLL_MS as D, GameLeaderboards as Dt, TURN_EVENT as E, GameInfo$1 as Et, HubSaved as F, LeaderboardEntry as Ft, ResumeLoadOptions as G, Stats$1 as Gt, RESUME_MAX_STATE_BYTES as H, SaveMeta as Ht, SAVED_CHANGE_QUIET_MS as I, LeaderboardResult as It, ResumeSaveInput as J, User as Jt, ResumeProgress as K, SubmitScoreOptions as Kt, SavedChangeListener as L, MeResponse as Lt, TurnGameOptions as M, Inventory$1 as Mt, TurnMoveEvent as N, InventorySnapshot as Nt, TURN_QUIET_MS as O, GameRecords as Ot, TurnOutcome as P, Json$1 as Pt, TradeId as Q, AvatarHost as Qt, SavedOpenDetail as R, Metrics$1 as Rt, HubTurns as S, BufferedEvent as St, TURN_CHAT_EVENT as T, GameAnalytics as Tt, ResumeBlocker as U, SnapshotResponse as Ut, RESUME_DEBOUNCE_MS as V, Save as Vt, ResumeEntry as W, SortDir$1 as Wt, GrantBundle as X, EndpointName as Xt, HubMenuControl as Y, EndpointFields as Yt, TradeBundle as Z, EndpointReply as Zt, UNSUPPORTED_BY_HUB as _, CLIENT_VERSION_HEADER as _t, HUB_CLIENT_VERSION as a, SyncResult as at, hub as b, QueueOpInput as bt, isNewlyDue as c, OfflineEventDetails as ct, ContentRejectedError as d, OfflineOp as dt, HubAvatar as en, TradeNoteOptions as et, HubError as f, OUTBOX_MAX_CHARS as ft, SAVE_UNKNOWN_CODE as g, ReplayContext as gt, OfflineError as h, OfflineKindDefinition as ht, TurnGameView$1 as i, SyncConflict as it, TurnGameEvents as j, HubStatus as jt, TURN_RESYNC_EVENT as k, GlobalAnalytics as kt, readYourTurnBuzz as l, OfflineEventName as lt, HubErrorDetails as m, OfflineHandler as mt, TurnChatLine$1 as n, ProfileUpdate as nt, YOUR_TURN_BUZZ as o, OfflineApi as ot, HubErrorCode as p, OUTBOX_MAX_ENTRIES as pt, ResumeRun as q, SubmitScoreResult as qt, TurnDeadline$1 as r, StatResult as rt, YOUR_TURN_BUZZ_KEY as s, OUTBOX_FULL_CODE as st, OpenTurnGame$1 as t, CarriesLook as tn, AuthResponse as tt, saveYourTurnBuzz as u, OfflineListener as ut, Hub as v, ProfilePatch as vt, TurnCreateOptions as w, BoardWithTop as wt, ClientSupport as x, QueuedScoreOptions as xt, createHub as y, QueueOp as yt, SavedOpenHandler as z, Profile as zt };
|
|
706
1782
|
//# sourceMappingURL=index.d.ts.map
|