@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/daily/types.ts
CHANGED
|
@@ -1,67 +1,105 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Daily challenge shapes: what a game's `play` receives, and the hub's
|
|
3
|
-
* answers (typed from the contract's daily endpoints).
|
|
4
|
-
*/
|
|
5
|
-
import type { dailyEndpoints, Json } from '@diffenderfer-games/hub-contract';
|
|
6
|
-
import type {
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
/** The
|
|
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
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Daily challenge shapes: what a game's `play` receives, and the hub's
|
|
3
|
+
* answers (typed from the contract's daily endpoints).
|
|
4
|
+
*/
|
|
5
|
+
import type { dailyEndpoints, Json } from '@diffenderfer-games/hub-contract';
|
|
6
|
+
import type { GamePayload } from '../core/game-data';
|
|
7
|
+
import type { SavedEntry } from '../core/saved/saved-types';
|
|
8
|
+
import type { SchemaOutput } from '../realtime/wire-types';
|
|
9
|
+
|
|
10
|
+
/** The response type of a daily endpoint. */
|
|
11
|
+
type DailyResponse<Name extends keyof typeof dailyEndpoints> = SchemaOutput<(typeof dailyEndpoints)[Name]['cfg']['response']>;
|
|
12
|
+
|
|
13
|
+
/** This game's daily window, the player's completions and days in progress (`GET /games/:slug/daily`). */
|
|
14
|
+
export type DailyStatus = DailyResponse<'getGameDailyChallenges'>;
|
|
15
|
+
|
|
16
|
+
/** Every daily game and the global standings (`GET /daily`). */
|
|
17
|
+
export type DailyAll = DailyResponse<'listDailyChallengesForAllGames'>;
|
|
18
|
+
|
|
19
|
+
/** One game's row in {@link DailyAll}. */
|
|
20
|
+
export type DailyGameStatus = DailyAll['games'][number];
|
|
21
|
+
|
|
22
|
+
/** The answer to starting (or resuming) a day: its seed, the signed token to present on completion, and its saved game. */
|
|
23
|
+
export type DailyStartResponse = DailyResponse<'startDailyChallenge'>;
|
|
24
|
+
|
|
25
|
+
/** The answer to completing a day: the player's new completion count and rank. */
|
|
26
|
+
export type DailyCompleteResponse = DailyResponse<'completeDailyChallenge'>;
|
|
27
|
+
|
|
28
|
+
/** The challenge a game's `play` receives: set up and start exactly this day (derive the level from `seed`). */
|
|
29
|
+
export interface DailyChallenge {
|
|
30
|
+
/** The day, `YYYY-MM-DD` (a UTC day key). */
|
|
31
|
+
day: string;
|
|
32
|
+
/** Deterministic per game and day. */
|
|
33
|
+
seed: number;
|
|
34
|
+
/** The signed token `complete` presents (empty for a day started offline: it is fetched when the hub is back). */
|
|
35
|
+
token: string;
|
|
36
|
+
/** The player already completed this day (a replay for fun: nothing is saved or recorded). */
|
|
37
|
+
alreadyCompleted?: boolean;
|
|
38
|
+
/** The day's saved game; `null` for a replay of a completed day. */
|
|
39
|
+
instanceId: string | null;
|
|
40
|
+
/**
|
|
41
|
+
* The state last saved for this day (`hub.daily.save`, or `hub.resume.save`
|
|
42
|
+
* during the daily), or `null` when the day starts fresh. A game that
|
|
43
|
+
* saves restores from it: `if (challenge.saved) startFrom(challenge.saved)`.
|
|
44
|
+
*/
|
|
45
|
+
saved: GamePayload | null;
|
|
46
|
+
/** The game's state version of `saved` (0 when none). */
|
|
47
|
+
savedVersion: number;
|
|
48
|
+
/** The day was already in progress and was picked up again. */
|
|
49
|
+
resumed?: boolean;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** What `hub.daily.define` takes. */
|
|
53
|
+
export interface DailyDefinition {
|
|
54
|
+
/** Start this challenge now (restore `challenge.saved` when it has one). */
|
|
55
|
+
play: (challenge: DailyChallenge) => void;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Metadata a game records with a completion (`{ score, difficulty, timeMs, … }`). */
|
|
59
|
+
export type DailyCompletionMeta = Record<string, Json>;
|
|
60
|
+
|
|
61
|
+
/** A daily state save (the same shape as `hub.resume.save`). */
|
|
62
|
+
export interface DailySaveInput {
|
|
63
|
+
/** Everything needed to rebuild the day's run (up to 512 KB as JSON). */
|
|
64
|
+
readonly state: GamePayload;
|
|
65
|
+
/** A short line ("34 cards left") and an optional 0 to 1 fraction. */
|
|
66
|
+
readonly progress: { readonly label: string; readonly fraction?: number | null };
|
|
67
|
+
readonly title?: string | null;
|
|
68
|
+
/** The game's state version (default 0). */
|
|
69
|
+
readonly version?: number;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** What the daily system needs from the hub client: the REST calls, the device copy and its window events. */
|
|
73
|
+
export interface DailyHost {
|
|
74
|
+
/** This page's game slug. */
|
|
75
|
+
readonly slug: string | null;
|
|
76
|
+
/** `GET /games/:slug/daily`. */
|
|
77
|
+
status(): Promise<DailyStatus>;
|
|
78
|
+
/** `GET /daily`. */
|
|
79
|
+
all(): Promise<DailyAll>;
|
|
80
|
+
/** Start (or pick up) a day: `POST /games/:slug/daily/:day/start`, or a device-only start while offline. */
|
|
81
|
+
start(day: string): Promise<DailyStartResponse>;
|
|
82
|
+
/** Pick a day in progress up again by its saved game's id. */
|
|
83
|
+
resume(instanceId: string): Promise<DailyStartResponse>;
|
|
84
|
+
/** `POST /games/:slug/daily/:day/complete`; `null` when it was queued offline, to replay when the hub is back. */
|
|
85
|
+
complete(day: string, token: string, meta: DailyCompletionMeta): Promise<DailyCompleteResponse | null>;
|
|
86
|
+
/** Keep the day's state on the device at once and send it soon (works offline); `false` when it can't be stored. */
|
|
87
|
+
save(challenge: DailyChallenge, input: DailySaveInput): boolean;
|
|
88
|
+
/** Send a waiting save now. */
|
|
89
|
+
flush(): Promise<void>;
|
|
90
|
+
/** Give the day up: discard its saved game (works offline). */
|
|
91
|
+
discard(challenge: DailyChallenge): Promise<void>;
|
|
92
|
+
/** This game's days in progress (the saved games of kind daily). */
|
|
93
|
+
inProgress(): Promise<SavedEntry[]>;
|
|
94
|
+
/** Fire a `hub:*` window event (the menu listens). */
|
|
95
|
+
emit(name: string, detail: DailyEventDetail): void;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** The detail of the `hub:daily-*` window events. */
|
|
99
|
+
export type DailyEventDetail =
|
|
100
|
+
| { day: string; seed: number; alreadyCompleted: boolean; resumed?: boolean }
|
|
101
|
+
| { day: string; count: number; rank: number | null; meta: DailyCompletionMeta }
|
|
102
|
+
| { day: string; queued: true; meta: DailyCompletionMeta }
|
|
103
|
+
| { day: string; paused: true }
|
|
104
|
+
| { day: string; discarded: true }
|
|
105
|
+
| { day: string; error: string };
|
package/src/daily.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `@diffenderfer-games/hub/daily`: daily challenges (`hub.daily`): the
|
|
3
|
-
* {@link DailySystem} and the page-wide {@link getDaily}.
|
|
4
|
-
*
|
|
5
|
-
* @packageDocumentation
|
|
6
|
-
*/
|
|
7
|
-
export { DAILY_START_EVENT, DailySystem } from './daily/daily-system';
|
|
8
|
-
export { DAILY_SINGLETON_API, getDaily, isDailySystem } from './daily/page-daily';
|
|
9
|
-
export type * from './daily/types';
|
|
10
|
-
export type { DailyCompletion, DailyCompletions } from '@diffenderfer-games/hub-contract';
|
|
1
|
+
/**
|
|
2
|
+
* `@diffenderfer-games/hub/daily`: daily challenges (`hub.daily`): the
|
|
3
|
+
* {@link DailySystem} and the page-wide {@link getDaily}.
|
|
4
|
+
*
|
|
5
|
+
* @packageDocumentation
|
|
6
|
+
*/
|
|
7
|
+
export { DAILY_START_EVENT, DailySystem } from './daily/daily-system';
|
|
8
|
+
export { DAILY_SINGLETON_API, getDaily, isDailySystem } from './daily/page-daily';
|
|
9
|
+
export type * from './daily/types';
|
|
10
|
+
export type { DailyCompletion, DailyCompletions } from '@diffenderfer-games/hub-contract';
|
package/src/doctor/checks.ts
CHANGED
|
@@ -1,76 +1,86 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The checks `hub-doctor` runs on a game (plan §4.3.7). Each returns
|
|
3
|
-
* findings; an `error` finding fails the run (and the game's CI).
|
|
4
|
-
*/
|
|
5
|
-
import { existsSync } from 'node:fs';
|
|
6
|
-
import { join } from 'node:path';
|
|
7
|
-
import { GamePackageJson, type HubVersion } from '@diffenderfer-games/hub-contract';
|
|
8
|
-
import { clientSupport } from '../core/version/hub-version';
|
|
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
|
-
case '
|
|
39
|
-
return [{ level: '
|
|
40
|
-
case '
|
|
41
|
-
return [{ level: '
|
|
42
|
-
case '
|
|
43
|
-
return [{ level: 'info', check: 'version', message:
|
|
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
|
-
|
|
1
|
+
/**
|
|
2
|
+
* The checks `hub-doctor` runs on a game (plan §4.3.7). Each returns
|
|
3
|
+
* findings; an `error` finding fails the run (and the game's CI).
|
|
4
|
+
*/
|
|
5
|
+
import { existsSync } from 'node:fs';
|
|
6
|
+
import { join } from 'node:path';
|
|
7
|
+
import { GAME_LABELS, GamePackageJson, splitGameLabels, type HubVersion } from '@diffenderfer-games/hub-contract';
|
|
8
|
+
import { clientSupport } from '../core/version/hub-version';
|
|
9
|
+
import { isHubUnreachable, type HubUnreachable } from './hub-version-fetch';
|
|
10
|
+
|
|
11
|
+
/** How serious a finding is: `error` fails the run, `warning` and `info` only report. */
|
|
12
|
+
export type FindingLevel = 'error' | 'warning' | 'info';
|
|
13
|
+
|
|
14
|
+
/** One thing the doctor found. */
|
|
15
|
+
export interface Finding {
|
|
16
|
+
/** How serious it is. */
|
|
17
|
+
readonly level: FindingLevel;
|
|
18
|
+
/** Which check found it: `version`, `vendored`, `manifest` or `world`. */
|
|
19
|
+
readonly check: 'version' | 'vendored' | 'manifest' | 'world';
|
|
20
|
+
/** What to tell the developer, with the fix. */
|
|
21
|
+
readonly message: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** The fix for an old or unsupported client. */
|
|
25
|
+
const UPDATE_FIX = 'run `npm update @diffenderfer-games/hub`';
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The installed client against the hub's `/_api/version`: outside
|
|
29
|
+
* `supported` is an error, `deprecated` a warning, `null` (a hub from before
|
|
30
|
+
* versioning) is only reported. An unreachable or slow hub is an error, so a
|
|
31
|
+
* game's CI notices (`--offline` skips the check).
|
|
32
|
+
*/
|
|
33
|
+
export function checkClientVersion(installedVersion: string, hubVersion: HubVersion | null | HubUnreachable): Finding[] {
|
|
34
|
+
if (isHubUnreachable(hubVersion)) {
|
|
35
|
+
return [{ level: 'error', check: 'version', message: `hub unreachable at ${hubVersion.unreachableUrl} (${hubVersion.reason}); check --hub, or pass --offline to skip the version check` }];
|
|
36
|
+
}
|
|
37
|
+
switch (clientSupport(hubVersion, installedVersion)) {
|
|
38
|
+
case 'unsupported':
|
|
39
|
+
return [{ level: 'error', check: 'version', message: `@diffenderfer-games/hub ${installedVersion} is not supported by the hub (supported: ${hubVersion?.supported ?? '?'}): ${UPDATE_FIX}` }];
|
|
40
|
+
case 'deprecated':
|
|
41
|
+
return [{ level: 'warning', check: 'version', message: `@diffenderfer-games/hub ${installedVersion} is deprecated (deprecated: ${hubVersion?.deprecated ?? '?'}): ${UPDATE_FIX}` }];
|
|
42
|
+
case 'unknown':
|
|
43
|
+
return [{ level: 'info', check: 'version', message: 'the hub predates versioning (no /_api/version); the client version was not checked' }];
|
|
44
|
+
case 'supported':
|
|
45
|
+
return [{ level: 'info', check: 'version', message: `@diffenderfer-games/hub ${installedVersion} is supported (hub ${hubVersion?.server ?? '?'})` }];
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Leftovers of the v1 vendored client, relative to the game's folder. */
|
|
50
|
+
const VENDORED_LEFTOVERS = [
|
|
51
|
+
'src/hub/hub.ts',
|
|
52
|
+
'src/hub/input.ts',
|
|
53
|
+
'client/src/hub/hub.ts',
|
|
54
|
+
'scripts/sync-hub.mjs',
|
|
55
|
+
] as const;
|
|
56
|
+
|
|
57
|
+
/** A stale vendored `src/hub/` copy or its sync script (the package replaces both). */
|
|
58
|
+
export function checkVendoredCopies(gameDir: string): Finding[] {
|
|
59
|
+
return VENDORED_LEFTOVERS.filter((leftover) => existsSync(join(gameDir, leftover))).map((leftover) => ({
|
|
60
|
+
level: 'warning',
|
|
61
|
+
check: 'vendored',
|
|
62
|
+
message: `${leftover} is a vendored copy of the v1 client: import from '@diffenderfer-games/hub' instead and delete it`,
|
|
63
|
+
}));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** The `package.json` `game.*` block against the contract's manifest schema. */
|
|
67
|
+
// eslint-disable-next-line no-restricted-syntax -- trust boundary: the game's package.json, parsed by GamePackageJson next
|
|
68
|
+
export function checkManifest(packageJson: unknown): Finding[] {
|
|
69
|
+
const parsed = GamePackageJson.safeParse(packageJson);
|
|
70
|
+
if (parsed.success) {
|
|
71
|
+
const unknownLabels = splitGameLabels(parsed.data.game.labels ?? []).unknown;
|
|
72
|
+
return [
|
|
73
|
+
{ level: 'info', check: 'manifest', message: `the game.* block is valid (${parsed.data.game.type})` },
|
|
74
|
+
...unknownLabels.map((label): Finding => ({
|
|
75
|
+
level: 'warning',
|
|
76
|
+
check: 'manifest',
|
|
77
|
+
message: `game.labels: "${label}" is not a known label (the catalog ignores it); use any of ${GAME_LABELS.join(', ')}`,
|
|
78
|
+
})),
|
|
79
|
+
];
|
|
80
|
+
}
|
|
81
|
+
return parsed.error.issues.map((issue) => ({
|
|
82
|
+
level: 'error',
|
|
83
|
+
check: 'manifest',
|
|
84
|
+
message: `${issue.path.length === 0 ? '(package.json)' : issue.path.join('.')}: ${issue.message}`,
|
|
85
|
+
}));
|
|
86
|
+
}
|
package/src/doctor/cli.ts
CHANGED
|
@@ -1,106 +1,101 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* `hub-doctor`: checks a game against the hub (plan §4.3.7). Run it in a
|
|
4
|
-
* game's folder, in CI and before pushing:
|
|
5
|
-
*
|
|
6
|
-
* ```sh
|
|
7
|
-
* npx hub-doctor [--dir <game folder>] [--hub <url>] [--offline] [--json]
|
|
8
|
-
* ```
|
|
9
|
-
*
|
|
10
|
-
* - the installed `@diffenderfer-games/hub` against the hub's
|
|
11
|
-
* `/_api/version` (`--hub`, default `https://diffenderfer.games`): outside
|
|
12
|
-
* `supported` fails, `deprecated` warns (`--offline` skips this check);
|
|
13
|
-
* - a stale vendored `src/hub/` copy or `scripts/sync-hub.mjs` warns;
|
|
14
|
-
* - the `package.json` `game.*` block must match the contract's schema
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
import { HUB_CLIENT_VERSION } from '../core/version/client-version.gen';
|
|
23
|
-
import { checkClientVersion, checkManifest, checkVendoredCopies, type Finding } from './checks';
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
readonly
|
|
32
|
-
readonly
|
|
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
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
for (const finding of findings) {
|
|
103
|
-
process.stdout.write(`${finding.level.padEnd(7)} ${finding.check}: ${finding.message}\n`);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
process.exitCode = findings.some((finding) => finding.level === 'error') ? 1 : 0;
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* `hub-doctor`: checks a game against the hub (plan §4.3.7). Run it in a
|
|
4
|
+
* game's folder, in CI and before pushing:
|
|
5
|
+
*
|
|
6
|
+
* ```sh
|
|
7
|
+
* npx hub-doctor [--dir <game folder>] [--hub <url>] [--timeout <ms>] [--offline] [--json]
|
|
8
|
+
* ```
|
|
9
|
+
*
|
|
10
|
+
* - the installed `@diffenderfer-games/hub` against the hub's
|
|
11
|
+
* `/_api/version` (`--hub`, default `https://diffenderfer.games`): outside
|
|
12
|
+
* `supported` fails, `deprecated` warns (`--offline` skips this check);
|
|
13
|
+
* - a stale vendored `src/hub/` copy or `scripts/sync-hub.mjs` warns;
|
|
14
|
+
* - the `package.json` `game.*` block must match the contract's schema;
|
|
15
|
+
* - a `game.world.map` file must exist and be a valid `dg-world@1` map within the size limit.
|
|
16
|
+
*
|
|
17
|
+
* Exits 1 when any check fails.
|
|
18
|
+
*/
|
|
19
|
+
import { readFileSync } from 'node:fs';
|
|
20
|
+
import { join, resolve } from 'node:path';
|
|
21
|
+
import { parseArgs } from 'node:util';
|
|
22
|
+
import { HUB_CLIENT_VERSION } from '../core/version/client-version.gen';
|
|
23
|
+
import { checkClientVersion, checkManifest, checkVendoredCopies, type Finding } from './checks';
|
|
24
|
+
import { DEFAULT_HUB_TIMEOUT_MS, fetchHubVersion } from './hub-version-fetch';
|
|
25
|
+
import { checkWorld } from './world-check';
|
|
26
|
+
|
|
27
|
+
const DEFAULT_HUB_URL = 'https://diffenderfer.games';
|
|
28
|
+
|
|
29
|
+
/** The command line, parsed. */
|
|
30
|
+
interface DoctorOptions {
|
|
31
|
+
readonly gameDir: string;
|
|
32
|
+
readonly hubUrl: string;
|
|
33
|
+
/** How long to wait for the hub, ms (`--timeout`). */
|
|
34
|
+
readonly timeoutMs: number;
|
|
35
|
+
readonly isOffline: boolean;
|
|
36
|
+
readonly isJson: boolean;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function readOptions(): DoctorOptions {
|
|
40
|
+
const { values } = parseArgs({
|
|
41
|
+
options: {
|
|
42
|
+
dir: { type: 'string' },
|
|
43
|
+
hub: { type: 'string' },
|
|
44
|
+
timeout: { type: 'string' },
|
|
45
|
+
offline: { type: 'boolean', default: false },
|
|
46
|
+
json: { type: 'boolean', default: false },
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
const timeoutMs = values.timeout === undefined ? DEFAULT_HUB_TIMEOUT_MS : Number(values.timeout);
|
|
50
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
|
|
51
|
+
process.stderr.write('hub-doctor: --timeout must be a positive number of milliseconds\n');
|
|
52
|
+
process.exit(2);
|
|
53
|
+
}
|
|
54
|
+
return { gameDir: resolve(values.dir ?? '.'), hubUrl: values.hub ?? DEFAULT_HUB_URL, timeoutMs, isOffline: values.offline, isJson: values.json };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// eslint-disable-next-line no-restricted-syntax -- trust boundary: a JSON file from disk, parsed by the caller's schema
|
|
58
|
+
function readJsonFile(path: string): unknown {
|
|
59
|
+
// eslint-disable-next-line no-restricted-syntax -- trust boundary: JSON.parse's result, parsed by the caller's schema
|
|
60
|
+
const parsed: unknown = JSON.parse(readFileSync(path, 'utf8'));
|
|
61
|
+
return parsed;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** The client version the game has installed (its `node_modules` copy), else this doctor's own. */
|
|
65
|
+
function installedClientVersion(gameDir: string): string {
|
|
66
|
+
try {
|
|
67
|
+
const installed = readJsonFile(join(gameDir, 'node_modules', '@diffenderfer-games', 'hub', 'package.json'));
|
|
68
|
+
if (typeof installed === 'object' && installed !== null && 'version' in installed && typeof installed.version === 'string') {
|
|
69
|
+
return installed.version;
|
|
70
|
+
}
|
|
71
|
+
} catch {
|
|
72
|
+
// Not installed in the game: report this doctor's own version.
|
|
73
|
+
}
|
|
74
|
+
return HUB_CLIENT_VERSION;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async function runDoctor(options: DoctorOptions): Promise<Finding[]> {
|
|
78
|
+
const findings: Finding[] = [];
|
|
79
|
+
if (!options.isOffline) {
|
|
80
|
+
findings.push(...checkClientVersion(installedClientVersion(options.gameDir), await fetchHubVersion(options.hubUrl, options.timeoutMs)));
|
|
81
|
+
}
|
|
82
|
+
findings.push(...checkVendoredCopies(options.gameDir));
|
|
83
|
+
try {
|
|
84
|
+
const packageJson = readJsonFile(join(options.gameDir, 'package.json'));
|
|
85
|
+
findings.push(...checkManifest(packageJson), ...checkWorld(options.gameDir, packageJson));
|
|
86
|
+
} catch (failure) {
|
|
87
|
+
findings.push({ level: 'error', check: 'manifest', message: `could not read package.json: ${failure instanceof Error ? failure.message : String(failure)}` });
|
|
88
|
+
}
|
|
89
|
+
return findings;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const options = readOptions();
|
|
93
|
+
const findings = await runDoctor(options);
|
|
94
|
+
if (options.isJson) {
|
|
95
|
+
process.stdout.write(`${JSON.stringify({ findings }, null, 2)}\n`);
|
|
96
|
+
} else {
|
|
97
|
+
for (const finding of findings) {
|
|
98
|
+
process.stdout.write(`${finding.level.padEnd(7)} ${finding.check}: ${finding.message}\n`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
process.exitCode = findings.some((finding) => finding.level === 'error') ? 1 : 0;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reading the hub's `GET /_api/version` for `hub-doctor`, with a timeout so a
|
|
3
|
+
* hung or unreachable hub fails a game's CI quickly instead of stalling it.
|
|
4
|
+
*/
|
|
5
|
+
import { HubVersion } from '@diffenderfer-games/hub-contract';
|
|
6
|
+
|
|
7
|
+
/** How long the doctor waits for the hub by default, ms. */
|
|
8
|
+
export const DEFAULT_HUB_TIMEOUT_MS = 10_000;
|
|
9
|
+
|
|
10
|
+
/** The hub could not be asked: down, slow, or answering with an error. */
|
|
11
|
+
export interface HubUnreachable {
|
|
12
|
+
/** The URL that was asked. */
|
|
13
|
+
readonly unreachableUrl: string;
|
|
14
|
+
/** Why, for the message (`timed out after 10000 ms`, `HTTP 502`, a network error). */
|
|
15
|
+
readonly reason: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Whether a version lookup failed. */
|
|
19
|
+
export function isHubUnreachable(lookup: HubVersion | null | HubUnreachable): lookup is HubUnreachable {
|
|
20
|
+
return lookup !== null && 'unreachableUrl' in lookup;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* The hub's version: `null` for a hub from before versioning (404), a
|
|
25
|
+
* {@link HubUnreachable} when it can't be read within `timeoutMs`.
|
|
26
|
+
*/
|
|
27
|
+
export async function fetchHubVersion(hubUrl: string, timeoutMs: number = DEFAULT_HUB_TIMEOUT_MS): Promise<HubVersion | null | HubUnreachable> {
|
|
28
|
+
const url = new URL('/_api/version', hubUrl).toString();
|
|
29
|
+
try {
|
|
30
|
+
const response = await fetch(url, { signal: AbortSignal.timeout(timeoutMs) });
|
|
31
|
+
if (response.status === 404) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
if (!response.ok) {
|
|
35
|
+
return { unreachableUrl: url, reason: `HTTP ${String(response.status)}` };
|
|
36
|
+
}
|
|
37
|
+
return HubVersion.parse(await response.json());
|
|
38
|
+
} catch (failure) {
|
|
39
|
+
const isTimeout = failure instanceof Error && (failure.name === 'TimeoutError' || failure.name === 'AbortError');
|
|
40
|
+
const reason = isTimeout ? `timed out after ${String(timeoutMs)} ms` : failure instanceof Error ? failure.message : String(failure);
|
|
41
|
+
return { unreachableUrl: url, reason };
|
|
42
|
+
}
|
|
43
|
+
}
|