@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/doctor/tsconfig.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
{
|
|
2
|
-
"extends": "../../tsconfig.json",
|
|
3
|
-
"compilerOptions": {
|
|
4
|
-
"types": [
|
|
5
|
-
"node"
|
|
6
|
-
]
|
|
7
|
-
},
|
|
8
|
-
"include": [
|
|
9
|
-
"."
|
|
10
|
-
],
|
|
11
|
-
"exclude": []
|
|
12
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"extends": "../../tsconfig.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"types": [
|
|
5
|
+
"node"
|
|
6
|
+
]
|
|
7
|
+
},
|
|
8
|
+
"include": [
|
|
9
|
+
"."
|
|
10
|
+
],
|
|
11
|
+
"exclude": []
|
|
12
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `hub-doctor`'s world check (docs/plans/world.md §14.6): a `game.world.map`
|
|
3
|
+
* file must exist inside the game, be within the size limit and be a valid
|
|
4
|
+
* `dg-world@1` map (the same `parseWorldMapText` the hub runs at load); a
|
|
5
|
+
* `game.world.generate.type` the hub doesn't know is a warning.
|
|
6
|
+
*/
|
|
7
|
+
import { existsSync, readFileSync, statSync } from 'node:fs';
|
|
8
|
+
import { join } from 'node:path';
|
|
9
|
+
import { GamePackageJson } from '@diffenderfer-games/hub-contract';
|
|
10
|
+
import { BUILT_IN_WORLD_TYPES, WORLD_LIMITS, parseWorldMapText } from '@diffenderfer-games/world';
|
|
11
|
+
import type { Finding } from './checks';
|
|
12
|
+
|
|
13
|
+
/** The most map problems listed (the rest are counted). */
|
|
14
|
+
const MAX_LISTED_PROBLEMS = 20;
|
|
15
|
+
|
|
16
|
+
/** Checks the map file and world type `game.world` names. A manifest that doesn't parse is left to `checkManifest`. */
|
|
17
|
+
// eslint-disable-next-line no-restricted-syntax -- trust boundary: the game's package.json, parsed by GamePackageJson next
|
|
18
|
+
export function checkWorld(gameDir: string, packageJson: unknown): Finding[] {
|
|
19
|
+
const parsed = GamePackageJson.safeParse(packageJson);
|
|
20
|
+
const world = parsed.success ? parsed.data.game.world : undefined;
|
|
21
|
+
if (world === undefined) {
|
|
22
|
+
return [];
|
|
23
|
+
}
|
|
24
|
+
const findings: Finding[] = [];
|
|
25
|
+
const type = world.generate?.type;
|
|
26
|
+
if (type !== undefined && !BUILT_IN_WORLD_TYPES.some((known) => known.id === type)) {
|
|
27
|
+
findings.push({ level: 'warning', check: 'world', message: `game.world.generate.type: the hub has no world type "${type}"; use one of ${BUILT_IN_WORLD_TYPES.map((known) => known.id).join(', ')}` });
|
|
28
|
+
}
|
|
29
|
+
return world.map === undefined ? findings : [...findings, ...checkMapFile(gameDir, world.map)];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** One map file's findings. */
|
|
33
|
+
export function checkMapFile(gameDir: string, mapFile: string): Finding[] {
|
|
34
|
+
const path = join(gameDir, mapFile);
|
|
35
|
+
const error = (message: string): Finding => ({ level: 'error', check: 'world', message: `game.world.map (${mapFile}): ${message}` });
|
|
36
|
+
if (!existsSync(path) || !statSync(path).isFile()) {
|
|
37
|
+
return [error('the file does not exist (build or commit it)')];
|
|
38
|
+
}
|
|
39
|
+
const bytes = statSync(path).size;
|
|
40
|
+
if (bytes > WORLD_LIMITS.bytes) {
|
|
41
|
+
return [error(`${String(bytes)} bytes is over the ${String(WORLD_LIMITS.bytes)}-byte limit (save layers as "rle")`)];
|
|
42
|
+
}
|
|
43
|
+
const result = parseWorldMapText(readFileSync(path, 'utf8'));
|
|
44
|
+
const listed = result.problems.slice(0, MAX_LISTED_PROBLEMS).map((problem): Finding => ({
|
|
45
|
+
level: problem.severity === 'error' ? 'error' : 'warning',
|
|
46
|
+
check: 'world',
|
|
47
|
+
message: `game.world.map (${mapFile}): ${problem.message}`,
|
|
48
|
+
}));
|
|
49
|
+
const more = result.problems.length - listed.length;
|
|
50
|
+
if (more > 0) {
|
|
51
|
+
listed.push(error(`and ${String(more)} more problems`));
|
|
52
|
+
}
|
|
53
|
+
if (!result.ok) {
|
|
54
|
+
return listed;
|
|
55
|
+
}
|
|
56
|
+
const { map } = result;
|
|
57
|
+
return [...listed, {
|
|
58
|
+
level: 'info', check: 'world',
|
|
59
|
+
message: `game.world.map (${mapFile}) is a valid dg-world@1 map: ${map.title}, ${String(map.width)} × ${String(map.height)}, ${String(map.objects.length)} objects, ${String(bytes)} bytes`,
|
|
60
|
+
}];
|
|
61
|
+
}
|
package/src/globals.ts
CHANGED
|
@@ -1,110 +1,115 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Page-wide singletons across client versions (plan §4.4). One page often
|
|
3
|
-
* runs two client builds: the game's bundled copy and the host's injected
|
|
4
|
-
* `/_hub/hub.js`. The input system, daily challenges and the realtime API
|
|
5
|
-
* must exist once per page, so each is registered here with the version that
|
|
6
|
-
* created it and a small `api` number that changes only when the
|
|
7
|
-
* cross-bundle shape does.
|
|
8
|
-
*
|
|
9
|
-
* - Same `api`: the first creator wins and everyone shares it.
|
|
10
|
-
* - Different `api`: the caller decides who owns the page (see
|
|
11
|
-
* {@link claimSingleton}'s `onMismatch`); the mismatch is logged once with the fix.
|
|
12
|
-
* - The v1 names (`window.__HUB_INPUT__`, `__HUB_DAILY__`, `__HUB_RT__`) stay as
|
|
13
|
-
* aliases while vendored v1 copies are still around.
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
/** A page-wide singleton's name. */
|
|
17
|
-
export type SingletonName = 'input' | 'daily' | 'rt';
|
|
18
|
-
|
|
19
|
-
/** One registered singleton. */
|
|
20
|
-
export interface SingletonRecord {
|
|
21
|
-
/** The client version that created it. */
|
|
22
|
-
readonly version: string;
|
|
23
|
-
/** Its cross-bundle shape number. */
|
|
24
|
-
readonly api: number;
|
|
25
|
-
/** The instance itself; another bundle's object, so callers check it before use. */
|
|
26
|
-
// eslint-disable-next-line no-restricted-syntax -- trust boundary: the instance may come from another bundle on the page
|
|
27
|
-
readonly instance: unknown;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/** The registry on `window.__DG__`. */
|
|
31
|
-
export type DgGlobals = { [K in SingletonName]?: SingletonRecord };
|
|
32
|
-
|
|
33
|
-
/** The v1
|
|
34
|
-
export const
|
|
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
|
-
readonly
|
|
60
|
-
/**
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
page
|
|
76
|
-
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
if (
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Page-wide singletons across client versions (plan §4.4). One page often
|
|
3
|
+
* runs two client builds: the game's bundled copy and the host's injected
|
|
4
|
+
* `/_hub/hub.js`. The input system, daily challenges and the realtime API
|
|
5
|
+
* must exist once per page, so each is registered here with the version that
|
|
6
|
+
* created it and a small `api` number that changes only when the
|
|
7
|
+
* cross-bundle shape does.
|
|
8
|
+
*
|
|
9
|
+
* - Same `api`: the first creator wins and everyone shares it.
|
|
10
|
+
* - Different `api`: the caller decides who owns the page (see
|
|
11
|
+
* {@link claimSingleton}'s `onMismatch`); the mismatch is logged once with the fix.
|
|
12
|
+
* - The v1 names (`window.__HUB_INPUT__`, `__HUB_DAILY__`, `__HUB_RT__`) stay as
|
|
13
|
+
* aliases while vendored v1 copies are still around.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/** A page-wide singleton's name. */
|
|
17
|
+
export type SingletonName = 'input' | 'daily' | 'rt';
|
|
18
|
+
|
|
19
|
+
/** One registered singleton. */
|
|
20
|
+
export interface SingletonRecord {
|
|
21
|
+
/** The client version that created it. */
|
|
22
|
+
readonly version: string;
|
|
23
|
+
/** Its cross-bundle shape number. */
|
|
24
|
+
readonly api: number;
|
|
25
|
+
/** The instance itself; another bundle's object, so callers check it before use. */
|
|
26
|
+
// eslint-disable-next-line no-restricted-syntax -- trust boundary: the instance may come from another bundle on the page
|
|
27
|
+
readonly instance: unknown;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** The registry on `window.__DG__`. */
|
|
31
|
+
export type DgGlobals = { [K in SingletonName]?: SingletonRecord };
|
|
32
|
+
|
|
33
|
+
/** The version recorded for a singleton adopted from a v1 (vendored) client, which predates the registry. */
|
|
34
|
+
export const V1_CLIENT_VERSION = 'v1';
|
|
35
|
+
|
|
36
|
+
/** The v1 window names, kept as aliases. */
|
|
37
|
+
export const LEGACY_GLOBAL_NAMES = { input: '__HUB_INPUT__', daily: '__HUB_DAILY__', rt: '__HUB_RT__' } as const satisfies Record<SingletonName, string>;
|
|
38
|
+
|
|
39
|
+
declare global {
|
|
40
|
+
interface Window {
|
|
41
|
+
__DG__?: DgGlobals;
|
|
42
|
+
// eslint-disable-next-line no-restricted-syntax -- trust boundary: set by any bundle on the page, including v1 copies
|
|
43
|
+
__HUB_INPUT__?: unknown;
|
|
44
|
+
// eslint-disable-next-line no-restricted-syntax -- trust boundary: set by any bundle on the page, including v1 copies
|
|
45
|
+
__HUB_DAILY__?: unknown;
|
|
46
|
+
// eslint-disable-next-line no-restricted-syntax -- trust boundary: set by any bundle on the page, including v1 copies
|
|
47
|
+
__HUB_RT__?: unknown;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** How to claim a singleton. */
|
|
52
|
+
export interface SingletonClaim<T> {
|
|
53
|
+
readonly name: SingletonName;
|
|
54
|
+
/** This bundle's client version. */
|
|
55
|
+
readonly version: string;
|
|
56
|
+
/** This bundle's shape number for the singleton. */
|
|
57
|
+
readonly api: number;
|
|
58
|
+
/** Make the instance when nobody has yet. */
|
|
59
|
+
readonly create: () => T;
|
|
60
|
+
/** Whether a registered instance is usable as `T` (it may come from another bundle). */
|
|
61
|
+
// eslint-disable-next-line no-restricted-syntax -- trust boundary: checks another bundle's object
|
|
62
|
+
readonly isInstance: (candidate: unknown) => candidate is T;
|
|
63
|
+
/**
|
|
64
|
+
* The registered one has a different `api`: return `true` to replace it
|
|
65
|
+
* with this bundle's instance, `false` to keep it (then `create` makes a
|
|
66
|
+
* private instance for this bundle only). Default: keep it.
|
|
67
|
+
*/
|
|
68
|
+
readonly onMismatch?: (registered: SingletonRecord) => boolean;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const warnedMismatches = new Set<SingletonName>();
|
|
72
|
+
|
|
73
|
+
/** The page registry, created on first use (`undefined` outside a browser). */
|
|
74
|
+
function registryOf(page: Window | undefined): DgGlobals | undefined {
|
|
75
|
+
if (page === undefined) {
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
page.__DG__ ??= {};
|
|
79
|
+
return page.__DG__;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Register (or join) a page-wide singleton and return the instance this bundle should use. */
|
|
83
|
+
export function claimSingleton<T>(claim: SingletonClaim<T>): T {
|
|
84
|
+
const page = typeof window === 'undefined' ? undefined : window;
|
|
85
|
+
const registry = registryOf(page);
|
|
86
|
+
if (page === undefined || registry === undefined) {
|
|
87
|
+
return claim.create();
|
|
88
|
+
}
|
|
89
|
+
const registered = registry[claim.name];
|
|
90
|
+
if (registered !== undefined && claim.isInstance(registered.instance)) {
|
|
91
|
+
if (registered.api === claim.api) {
|
|
92
|
+
return registered.instance;
|
|
93
|
+
}
|
|
94
|
+
if (!warnedMismatches.has(claim.name)) {
|
|
95
|
+
warnedMismatches.add(claim.name);
|
|
96
|
+
// eslint-disable-next-line no-console -- the plan's one-time warning to the game developer, with the fix
|
|
97
|
+
console.warn(`[hub] two clients on this page disagree on "${claim.name}" (v${registered.version} api ${String(registered.api)}, v${claim.version} api ${String(claim.api)}). Fix: npm update @diffenderfer-games/hub`);
|
|
98
|
+
}
|
|
99
|
+
if (claim.onMismatch?.(registered) !== true) {
|
|
100
|
+
return claim.create();
|
|
101
|
+
}
|
|
102
|
+
} else {
|
|
103
|
+
const legacy = page[LEGACY_GLOBAL_NAMES[claim.name]];
|
|
104
|
+
if (registered === undefined && claim.isInstance(legacy)) {
|
|
105
|
+
// A v1 vendored copy got there first: share it, as v1 bundles did, and
|
|
106
|
+
// record it so later bundles (and diagnostics) see which client owns it.
|
|
107
|
+
registry[claim.name] = { version: V1_CLIENT_VERSION, api: claim.api, instance: legacy };
|
|
108
|
+
return legacy;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
const instance = claim.create();
|
|
112
|
+
registry[claim.name] = { version: claim.version, api: claim.api, instance };
|
|
113
|
+
page[LEGACY_GLOBAL_NAMES[claim.name]] = instance;
|
|
114
|
+
return instance;
|
|
115
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,50 +1,68 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `@diffenderfer-games/hub`: the hub client for games on diffenderfer.games.
|
|
3
|
-
*
|
|
4
|
-
* ```ts
|
|
5
|
-
* import { hub } from '@diffenderfer-games/hub';
|
|
6
|
-
*
|
|
7
|
-
* const { user } = await hub.me();
|
|
8
|
-
* await hub.putSave('auto', { level: 7, gold: 120 });
|
|
9
|
-
* await hub.submitScore('highscore', 9001, { title: 'High Score' });
|
|
10
|
-
* ```
|
|
11
|
-
*
|
|
12
|
-
* In a hosted game the slug and API base are detected from the page; use
|
|
13
|
-
* {@link createHub} for others. Every call rejects with a {@link HubError}
|
|
14
|
-
* when it fails, except the fire-and-forget analytics (`recordPlay`,
|
|
15
|
-
* `heartbeat`, `event`). When the hub can't be reached, reads come from an
|
|
16
|
-
* offline cache and writes are queued until it is back
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
export {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
export type {
|
|
37
|
-
export type {
|
|
38
|
-
export type {
|
|
39
|
-
export type {
|
|
40
|
-
export
|
|
41
|
-
export {
|
|
42
|
-
export type {
|
|
43
|
-
export {
|
|
44
|
-
export type
|
|
45
|
-
export
|
|
46
|
-
export
|
|
47
|
-
export type * from './
|
|
48
|
-
export type
|
|
49
|
-
export
|
|
50
|
-
export type
|
|
1
|
+
/**
|
|
2
|
+
* `@diffenderfer-games/hub`: the hub client for games on diffenderfer.games.
|
|
3
|
+
*
|
|
4
|
+
* ```ts
|
|
5
|
+
* import { hub } from '@diffenderfer-games/hub';
|
|
6
|
+
*
|
|
7
|
+
* const { user } = await hub.me();
|
|
8
|
+
* await hub.putSave('auto', { level: 7, gold: 120 });
|
|
9
|
+
* await hub.submitScore('highscore', 9001, { title: 'High Score' });
|
|
10
|
+
* ```
|
|
11
|
+
*
|
|
12
|
+
* In a hosted game the slug and API base are detected from the page; use
|
|
13
|
+
* {@link createHub} for others. Every call rejects with a {@link HubError}
|
|
14
|
+
* when it fails, except the fire-and-forget analytics (`recordPlay`,
|
|
15
|
+
* `heartbeat`, `event`). When the hub can't be reached, reads come from an
|
|
16
|
+
* offline cache and writes are queued in an outbox until it is back; games
|
|
17
|
+
* queue their own operations with `hub.offline` (docs: "Offline and replay").
|
|
18
|
+
*
|
|
19
|
+
* The page-wide namespaces hang off `hub` as lazy getters (`hub.input`,
|
|
20
|
+
* `hub.daily`, `hub.resume`, `hub.saved`, `hub.social`, `hub.mp`, `hub.rooms`, `hub.race`), and each has
|
|
21
|
+
* its own entry (`@diffenderfer-games/hub/input`, ...) for games that want
|
|
22
|
+
* only that part.
|
|
23
|
+
*
|
|
24
|
+
* @packageDocumentation
|
|
25
|
+
*/
|
|
26
|
+
export { createHub, Hub, hub } from './core/hub';
|
|
27
|
+
export {
|
|
28
|
+
ContentRejectedError,
|
|
29
|
+
HubError,
|
|
30
|
+
OfflineError,
|
|
31
|
+
SAVE_UNKNOWN_CODE,
|
|
32
|
+
UNSUPPORTED_BY_HUB,
|
|
33
|
+
type HubErrorCode,
|
|
34
|
+
type HubErrorDetails,
|
|
35
|
+
} from './core/errors';
|
|
36
|
+
export type { SyncConflict, SyncResult } from './core/connection';
|
|
37
|
+
export type { AuthResponse, ProfileUpdate, StatResult } from './core/hub-progress';
|
|
38
|
+
export type { GrantBundle, TradeBundle, TradeId, TradeListOptions, TradeNoteOptions } from './core/hub-economy';
|
|
39
|
+
export type { HubMenuControl } from './core/menu-control';
|
|
40
|
+
export { HubResume, RESUME_DEBOUNCE_MS, RESUME_MAX_STATE_BYTES } from './core/resume/hub-resume';
|
|
41
|
+
export { HubAvatar, type AvatarHost, type AvatarModule } from './core/avatar/hub-avatar';
|
|
42
|
+
export type { AvatarChange, AvatarRef, DesignerResult, OpenDesignerOptions, RuntimeAvatarApi } from './core/avatar/avatar-types';
|
|
43
|
+
export type { CarriesLook } from './core/avatar/avatar-ref';
|
|
44
|
+
export type { ResumeBlocker, ResumeEntry, ResumeLoadOptions, ResumeProgress, ResumeRun, ResumeSaveInput } from './core/resume/resume-types';
|
|
45
|
+
export { HubSaved, SAVED_CHANGE_QUIET_MS, type SavedChangeListener, type SavedOpenDetail, type SavedOpenHandler } from './core/saved/hub-saved';
|
|
46
|
+
export { SAVED_CHANGE_EVENT, SAVED_KIND_PARAM, SAVED_OPEN_EVENT, SAVED_OPEN_PARAM, savedGameUrl } from './core/saved/saved-open';
|
|
47
|
+
export type * from './core/saved/saved-types';
|
|
48
|
+
export { HubTurns, TURN_LINK_PARAM, type TurnCreateOptions } from './core/turns/hub-turns';
|
|
49
|
+
export { isNewlyDue, readYourTurnBuzz, saveYourTurnBuzz, YOUR_TURN_BUZZ, YOUR_TURN_BUZZ_KEY } from './core/turns/your-turn-buzz';
|
|
50
|
+
export { TURN_CHAT_EVENT, TURN_EVENT, TURN_POLL_MS, TURN_QUIET_MS, TURN_RESYNC_EVENT, TurnGame, type TurnGameEvents, type TurnGameOptions, type TurnMoveEvent, type TurnOutcome } from './core/turns/turn-game';
|
|
51
|
+
export type { OpenTurnGame, TurnChatLine, TurnDeadline, TurnGameView } from '@diffenderfer-games/hub-contract';
|
|
52
|
+
export type { QueueOp, QueueOpInput, QueuedScoreOptions, ProfilePatch } from './core/offline/queue';
|
|
53
|
+
export { OfflineApi } from './core/offline/offline-api';
|
|
54
|
+
export { OUTBOX_FULL_CODE, type OfflineEventDetails, type OfflineEventName, type OfflineListener, type OfflineOp } from './core/offline/outbox';
|
|
55
|
+
export type { OfflineHandler, OfflineKindDefinition, ReplayContext } from './core/offline/outbox-kinds';
|
|
56
|
+
export { OUTBOX_MAX_CHARS, OUTBOX_MAX_ENTRIES } from './core/offline/outbox-store';
|
|
57
|
+
export type { BufferedEvent } from './core/offline/events';
|
|
58
|
+
export type { EndpointFields, EndpointName, EndpointReply } from './core/rest/endpoint-types';
|
|
59
|
+
export { CLIENT_VERSION_HEADER } from './core/rest/rest-client';
|
|
60
|
+
export type { ClientSupport } from './core/version/hub-version';
|
|
61
|
+
export { HUB_CLIENT_VERSION } from './core/version/client-version.gen';
|
|
62
|
+
export type * from './core/types';
|
|
63
|
+
export * from './daily';
|
|
64
|
+
export * from './input';
|
|
65
|
+
export type * from './social';
|
|
66
|
+
export type * from './mp';
|
|
67
|
+
export type * from './race';
|
|
68
|
+
export type * from './rooms';
|