@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/src/core/errors.ts
CHANGED
|
@@ -1,147 +1,160 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The errors every hub call throws: {@link HubError} and its two kinds,
|
|
3
|
-
* {@link OfflineError} and {@link ContentRejectedError}. The fields come from
|
|
4
|
-
* the contract's error body (`{ error, code, reason, hint, retryAfterMs, until }`).
|
|
5
|
-
*/
|
|
6
|
-
import type { ErrorCode, RejectReason } from '@diffenderfer-games/hub-contract';
|
|
7
|
-
import { isRecord, optionalNumber, optionalString } from './storage/json-guards';
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* The code a client gives a call the hub does not have yet (a newer client on
|
|
11
|
-
* an older hub, plan §4.3.3). Deploy the hub first, or use an older client.
|
|
12
|
-
*/
|
|
13
|
-
export const UNSUPPORTED_BY_HUB = 'unsupported_by_hub';
|
|
14
|
-
|
|
15
|
-
/** The code the social runtime gives a call it could not send (no connection). */
|
|
16
|
-
export const OFFLINE_CODE = 'offline';
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
*
|
|
20
|
-
* (
|
|
21
|
-
*
|
|
22
|
-
*/
|
|
23
|
-
export
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
*/
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
return
|
|
124
|
-
|
|
125
|
-
:
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
1
|
+
/**
|
|
2
|
+
* The errors every hub call throws: {@link HubError} and its two kinds,
|
|
3
|
+
* {@link OfflineError} and {@link ContentRejectedError}. The fields come from
|
|
4
|
+
* the contract's error body (`{ error, code, reason, hint, retryAfterMs, until }`).
|
|
5
|
+
*/
|
|
6
|
+
import type { ErrorCode, RejectReason } from '@diffenderfer-games/hub-contract';
|
|
7
|
+
import { isRecord, optionalNumber, optionalString } from './storage/json-guards';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* The code a client gives a call the hub does not have yet (a newer client on
|
|
11
|
+
* an older hub, plan §4.3.3). Deploy the hub first, or use an older client.
|
|
12
|
+
*/
|
|
13
|
+
export const UNSUPPORTED_BY_HUB = 'unsupported_by_hub';
|
|
14
|
+
|
|
15
|
+
/** The code the social runtime gives a call it could not send (no connection). */
|
|
16
|
+
export const OFFLINE_CODE = 'offline';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The code `getSave` rejects with when the hub can't be reached and this
|
|
20
|
+
* device has no copy of the slot it can trust (a fresh device or cleared
|
|
21
|
+
* browser): the slot may well hold a save, so it is not reported empty.
|
|
22
|
+
*/
|
|
23
|
+
export const SAVE_UNKNOWN_CODE = 'save_unknown';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* A machine-readable error code: one of the contract's public codes
|
|
27
|
+
* (`content_rejected`, `rate_limited`, `suspended`, `claim_required`, ...),
|
|
28
|
+
* {@link UNSUPPORTED_BY_HUB}, or a code a newer hub added.
|
|
29
|
+
*/
|
|
30
|
+
export type HubErrorCode = ErrorCode | typeof UNSUPPORTED_BY_HUB | typeof OFFLINE_CODE | typeof SAVE_UNKNOWN_CODE | (string & Record<never, never>);
|
|
31
|
+
|
|
32
|
+
/** The machine-readable extras the hub attaches to an error body. */
|
|
33
|
+
export interface HubErrorDetails {
|
|
34
|
+
/** A stable error code, for code to branch on. */
|
|
35
|
+
readonly code?: HubErrorCode | undefined;
|
|
36
|
+
/** Why content was refused (`link`, `language`, `muted`, `chat_off`, ...). */
|
|
37
|
+
readonly reason?: RejectReason | (string & Record<never, never>) | undefined;
|
|
38
|
+
/** A kid-friendly sentence to show the player. */
|
|
39
|
+
readonly hint?: string | undefined;
|
|
40
|
+
/** How long to wait before retrying, in ms (rate limits, mutes). */
|
|
41
|
+
readonly retryAfterMs?: number | undefined;
|
|
42
|
+
/** When a suspension or mute ends (epoch ms). */
|
|
43
|
+
readonly until?: number | undefined;
|
|
44
|
+
/** An online game's rules refused a move (`illegal`): the rules' own short code. */
|
|
45
|
+
readonly rule?: string | undefined;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Thrown by every hub call that fails. `status` is the HTTP status when the
|
|
50
|
+
* hub answered (401 = not signed in, 404 = unknown game or board, 413 = too
|
|
51
|
+
* large), or `undefined` for a transport failure (offline, no host). Social
|
|
52
|
+
* and multiplayer errors also carry {@link HubErrorDetails}.
|
|
53
|
+
*/
|
|
54
|
+
export class HubError extends Error {
|
|
55
|
+
/** The HTTP status, or `undefined` when the hub could not be reached. */
|
|
56
|
+
readonly status: number | undefined;
|
|
57
|
+
/** A stable error code (see {@link HubErrorCode}). */
|
|
58
|
+
readonly code: HubErrorCode | undefined;
|
|
59
|
+
/** Why content was refused. */
|
|
60
|
+
readonly reason: string | undefined;
|
|
61
|
+
/** A kid-friendly sentence to show the player. */
|
|
62
|
+
readonly hint: string | undefined;
|
|
63
|
+
/** How long to wait before retrying, in ms. */
|
|
64
|
+
readonly retryAfterMs: number | undefined;
|
|
65
|
+
/** When a suspension or mute ends (epoch ms). */
|
|
66
|
+
readonly until: number | undefined;
|
|
67
|
+
/** An online game's rules refused a move (`code` is `illegal`): the rules' own short code, such as `cell_taken`. */
|
|
68
|
+
readonly rule: string | undefined;
|
|
69
|
+
|
|
70
|
+
/** An error with a player-safe `message`, the HTTP `status` (if any) and the body's extras. */
|
|
71
|
+
constructor(message: string, status?: number, details: HubErrorDetails = {}) {
|
|
72
|
+
super(message);
|
|
73
|
+
this.name = 'HubError';
|
|
74
|
+
this.status = status;
|
|
75
|
+
this.code = details.code;
|
|
76
|
+
this.reason = details.reason;
|
|
77
|
+
this.hint = details.hint;
|
|
78
|
+
this.retryAfterMs = details.retryAfterMs;
|
|
79
|
+
this.until = details.until;
|
|
80
|
+
this.rule = details.rule;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** `true` when the failure was a network or transport error, not an HTTP status. */
|
|
84
|
+
get isOffline(): boolean {
|
|
85
|
+
return this.status === undefined;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Thrown when an action that genuinely needs the hub is attempted offline. */
|
|
90
|
+
export class OfflineError extends HubError {
|
|
91
|
+
/** An offline error; the default message is "This needs a connection.". */
|
|
92
|
+
constructor(message = 'This needs a connection.') {
|
|
93
|
+
super(message);
|
|
94
|
+
this.name = 'OfflineError';
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Text was refused by the safety filter (HTTP 422, or a socket ack with
|
|
100
|
+
* `code: 'content_rejected'`). Keep the player's draft and show `hint`; when
|
|
101
|
+
* `reason` is `muted` or `rate_limited`, `retryAfterMs` says how long to wait.
|
|
102
|
+
*/
|
|
103
|
+
export class ContentRejectedError extends HubError {
|
|
104
|
+
/** A content reject with the hub's message, status and extras. */
|
|
105
|
+
constructor(message: string, status: number | undefined, details: HubErrorDetails) {
|
|
106
|
+
super(message, status, details);
|
|
107
|
+
this.name = 'ContentRejectedError';
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** An error body's fields, read without trusting its shape. */
|
|
112
|
+
export interface ErrorBodyFields extends HubErrorDetails {
|
|
113
|
+
/** The player-safe sentence, when the body had one. */
|
|
114
|
+
readonly error?: string | undefined;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Read the fields this client understands from an error body (or any thrown object). */
|
|
118
|
+
// eslint-disable-next-line no-restricted-syntax -- trust boundary: an error body from the hub or an error from another bundle, read field by field
|
|
119
|
+
export function readErrorFields(body: unknown): ErrorBodyFields {
|
|
120
|
+
if (!isRecord(body)) {
|
|
121
|
+
return {};
|
|
122
|
+
}
|
|
123
|
+
return {
|
|
124
|
+
error: optionalString(body.error),
|
|
125
|
+
code: optionalString(body.code),
|
|
126
|
+
reason: optionalString(body.reason),
|
|
127
|
+
hint: optionalString(body.hint),
|
|
128
|
+
retryAfterMs: optionalNumber(body.retryAfterMs),
|
|
129
|
+
until: optionalNumber(body.until),
|
|
130
|
+
rule: optionalString(body.rule),
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** The right error class for a failure: a 422 or `content_rejected` is a {@link ContentRejectedError}. */
|
|
135
|
+
export function errorFor(message: string, status: number | undefined, details: HubErrorDetails): HubError {
|
|
136
|
+
return (status === 422 && details.code !== 'illegal') || details.code === 'content_rejected'
|
|
137
|
+
? new ContentRejectedError(message, status, details)
|
|
138
|
+
: new HubError(message, status, details);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Re-create an error from another bundle (the social runtime, or another
|
|
143
|
+
* client copy on the page) as this bundle's class, so `instanceof` works in
|
|
144
|
+
* games.
|
|
145
|
+
*/
|
|
146
|
+
// eslint-disable-next-line no-restricted-syntax -- trust boundary: whatever another bundle threw
|
|
147
|
+
export function toHubError(thrown: unknown): HubError {
|
|
148
|
+
if (thrown instanceof HubError) {
|
|
149
|
+
return thrown;
|
|
150
|
+
}
|
|
151
|
+
if (!isRecord(thrown)) {
|
|
152
|
+
return new HubError(String(thrown));
|
|
153
|
+
}
|
|
154
|
+
const fields = readErrorFields(thrown);
|
|
155
|
+
const message = optionalString(thrown.message) ?? 'Request failed';
|
|
156
|
+
if (fields.code === OFFLINE_CODE) {
|
|
157
|
+
return new OfflineError(message);
|
|
158
|
+
}
|
|
159
|
+
return errorFor(message, optionalNumber(thrown.status), fields);
|
|
160
|
+
}
|
package/src/core/game-data.ts
CHANGED
|
@@ -1,38 +1,40 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* A game's own JSON (save payloads, profile data, score metadata) is typed by
|
|
3
|
-
* the game: `hub.putSave<MySave>(...)`, `hub.getSave<MySave>(...)`. The hub
|
|
4
|
-
* stores it as opaque JSON, so neither the contract nor this client can check
|
|
5
|
-
* it against `MySave`: the game vouches for its own type, exactly as with the
|
|
6
|
-
* v1 client. These two functions are the only places that hand-over happens.
|
|
7
|
-
*/
|
|
8
|
-
import type { Json } from '@diffenderfer-games/hub-contract';
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* A game's own JSON (save payloads, profile data, score metadata) is typed by
|
|
3
|
+
* the game: `hub.putSave<MySave>(...)`, `hub.getSave<MySave>(...)`. The hub
|
|
4
|
+
* stores it as opaque JSON, so neither the contract nor this client can check
|
|
5
|
+
* it against `MySave`: the game vouches for its own type, exactly as with the
|
|
6
|
+
* v1 client. These two functions are the only places that hand-over happens.
|
|
7
|
+
*/
|
|
8
|
+
import type { Json } from '@diffenderfer-games/hub-contract';
|
|
9
|
+
import { isJson, parseJsonText } from './storage/json-guards';
|
|
10
|
+
|
|
11
|
+
/** Anything a game may store as JSON: an object (interfaces included), an array, text, a number, a boolean or `null`. */
|
|
12
|
+
export type GamePayload = object | string | number | boolean | null;
|
|
13
|
+
|
|
14
|
+
/** Always true: JSON the game stored is read back as the game's type. */
|
|
15
|
+
function isGameOwnData<TData>(_stored: Json): _stored is Json & TData {
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* A game's payload as the JSON the hub stores: a copy of exactly what
|
|
21
|
+
* `JSON.stringify` sends, so an optional field left `undefined` is simply
|
|
22
|
+
* absent (as on the wire) and the outbox's payload checks accept it, and a
|
|
23
|
+
* later change to the game's object can't alter a queued write.
|
|
24
|
+
*/
|
|
25
|
+
export function toStoredJson(payload: GamePayload): Json {
|
|
26
|
+
const stored = parseJsonText(JSON.stringify(payload));
|
|
27
|
+
if (isJson(stored)) {
|
|
28
|
+
return stored;
|
|
29
|
+
}
|
|
30
|
+
throw new TypeError('This value cannot be stored as JSON.');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** JSON the game stored, typed as the game's own type. */
|
|
34
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters -- the caller names the game's own type; the game vouches for it (see above)
|
|
35
|
+
export function toGameData<TData>(stored: Json): TData {
|
|
36
|
+
if (isGameOwnData<TData>(stored)) {
|
|
37
|
+
return stored;
|
|
38
|
+
}
|
|
39
|
+
throw new TypeError('unreachable: stored game data is the game\'s own');
|
|
40
|
+
}
|