@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/CHANGELOG.md
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# @diffenderfer-games/hub
|
|
2
|
+
|
|
3
|
+
## 2.0.0-next.10
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 5d68e5f: Adventure Mode (the world, full screen at `/world`):
|
|
8
|
+
|
|
9
|
+
- `world.say` takes `{ quick }` or `{ text }`: typed text is moderated like all chat and delivered only to the mutual friends who can see you (strangers get quick chat only); the `world.say` event's `quick` is absent for typed text.
|
|
10
|
+
- `world.find { userId }` (new): where a friend is (world, instance, their cell when they share yours) and the portals to walk through; `friend_not_in_world` for anyone who isn't a friend showing you their presence.
|
|
11
|
+
- `world.enter`'s `world` is optional (your last world, else the Hall) and both it and `world.teleport` take `friend` to join that friend's instance; `WorldPlace.chat` says whether you may type.
|
|
12
|
+
- `@diffenderfer-games/world`: `portalEdges` and `planWorldRoute`, the fewest-portals route between worlds.
|
|
13
|
+
- `@diffenderfer-games/hub`: a page the host injected without a slug (the catalog, Adventure Mode) has no game (`hub.slug` is `null`).
|
|
14
|
+
- 5deeb66: `TurnGame`'s quiet-refresh safety net now also runs while the game is a lobby (waiting for seats or a start), so a page waiting in a lobby fetches the game again if it missed a live event.
|
|
15
|
+
- Updated dependencies [5d68e5f]
|
|
16
|
+
- @diffenderfer-games/hub-contract@2.0.0-next.10
|
|
17
|
+
- @diffenderfer-games/world@2.0.0-next.10
|
|
18
|
+
|
|
19
|
+
## 2.0.0-next.9
|
|
20
|
+
|
|
21
|
+
### Minor Changes
|
|
22
|
+
|
|
23
|
+
- c464aae: Avatars get a world-scale figure: `figure: 'walker'` (on `RenderOptions` for full frames, on `createAtlas` and on `createCrowd`) draws a small full-body character with legs, arms, a shirt, trousers and shoes and a smaller head, for maps and crowds. It walks (8-step cycles that swing the legs and arms along the facing), runs, sits (the origin is the seat; the feet hang `WALKER_SEAT_DROP` units below it), idles, waves and cheers, and reads clearly from behind. Head and bust frames always draw the usual figure, and `blob` stays the default. Crowd members also take `tagLift` to move a name tag up, so a world can stack tags that would overlap, and a crowd member whose look changes keeps showing its old frame while the new one bakes (no flash to the silhouette). New exports: the `Figure` type, `WALKER_HEAD_SCALE` and `WALKER_SEAT_DROP`.
|
|
24
|
+
|
|
25
|
+
### Patch Changes
|
|
26
|
+
|
|
27
|
+
- e28707d: `createCrowd` (`/avatar/pixi`) takes two optional settings for worlds: `overlays`, a container for name tags and bubbles (so they can sit above a lighting layer), and `displayScale`, which draws atlas avatars at that scale (bake the atlas at `scale × devicePixelRatio` and pass `1 / devicePixelRatio` for crisp avatars on high-density screens). Both default to the old behaviour.
|
|
28
|
+
- 4eefc6c: `createCrowd` (`/avatar/pixi`): a member whose look has not baked yet shows the silhouette at its spot and at the crowd's `displayScale`, like a baked frame (it was drawn at full size with its corner at the feet, so on high-density screens it showed twice as big, below and to the right of its name tag).
|
|
29
|
+
- ef850e8: The world, ready to publish and live on the hub.
|
|
30
|
+
|
|
31
|
+
- `@diffenderfer-games/world` (first release): the `dg-world@1` map format with compressed `"encoding": "rle"` layers, a map `revision`, the generator's `provenance.generatorVersion` (the same seed and needs at the same version always give the same map), strict validation with readable paths, and size limits (`WORLD_LIMITS`, `parseWorldMapText`). New: `serializeWorldMap`, `encodeWorldMap`, `mapFingerprint`, `generatorVersionOf`, the Tiled converter (`worldMapToTiled`, `worldMapFromTiled`: layers, tile properties, objects, zones and lights, both ways) and the `dg-world` CLI (`check`, `from-tiled`, `to-tiled`, `generate`).
|
|
32
|
+
- `@diffenderfer-games/hub-contract`: `game.world` in the manifest (`map` and/or `generate: { type, seed, needs }`), the `world.*` socket ops (`enter`, `leave`, `move`, `sit`, `stand`, `teleport`, `say`) and events (`world.state`, `world.say`), `GET /world/maps/:key`, the world error codes, and `world` on a friend's presence.
|
|
33
|
+
- `@diffenderfer-games/hub`: `hub-doctor` checks a `game.world.map` file (it exists, is a valid map and is within the size limit) and warns about an unknown world type; the realtime API's optional `world` (feature version 6).
|
|
34
|
+
- Updated dependencies [ef850e8]
|
|
35
|
+
- @diffenderfer-games/world@2.0.0-next.9
|
|
36
|
+
- @diffenderfer-games/hub-contract@2.0.0-next.9
|
|
37
|
+
|
|
38
|
+
## 2.0.0-next.8
|
|
39
|
+
|
|
40
|
+
### Minor Changes
|
|
41
|
+
|
|
42
|
+
- ac6b1cd: Online games (`hub.turns`): `TurnGame.mayActNow` says this player may move now out of turn (the rules' `mayAct`: offer the jump-in, the UNO call, the catch), and `TurnGame.tournament` names the tournament match a game is. A `move()` answer that arrives after live events already brought newer moves no longer rolls the page back (nor does an older live event). `letComputerPlay()` documents the hand-over case. The rules kit `checkTurnRules` now moves every seat in `toAct` (not only the first) and seats in `mayAct` through `autoReact` (or the new `reactMove` option), fails on an illegal `autoReact` or a seat that does not exist, and reports `outOfTurn`.
|
|
43
|
+
- c12868a: Online games: new `@diffenderfer-games/hub/turns-rules` entry for a game's rules module (`defineTurnRules`, the rules types and the RNG, nothing else: the bundle stays a few KB instead of about 1 MB; `/turns` re-exports it with the kit). `TurnGame.canLetComputerPlay(now?)` says when "Let the computer play" may work (from `summary.handOverAt`, the seats' new `connected` and the deadline); `turn` now also fires when a seated player comes or goes or `handOverAt` changes. `refresh()` (and every other fetch) no longer rolls the game back when its answer is older than a live move that arrived meanwhile, and never replays that move twice. The rules kit `checkTurnRules` gains `revealed(state, marker)` (a planted card the rules rightly reveal, such as one played face up, stops being checked) and `shuffleHidden(state, seat, rng)` (the "twin table": shuffling what a seat can't see must not change its view).
|
|
44
|
+
|
|
45
|
+
`TurnGame.move()` fetches the game again after a 500 `rules_error` or 503 `rules_busy` (the hub now makes a move all or nothing, so such a move was not made).
|
|
46
|
+
|
|
47
|
+
The "your turn" buzz: every online game's page gives a short vibration when the player becomes due on a live update (never for their own next step, a move out of turn, a game just opened or a catch-up; never when they turned it off, ask for less motion, or the device can't vibrate). `hub.turns.setYourTurnBuzz(false)` and `TurnGame.setYourTurnBuzz(false)` opt a page or game out; `hub.turns.playerYourTurnBuzz` is the player's own switch on this device (the menu's Settings show it), with `readYourTurnBuzz`, `saveYourTurnBuzz`, `isNewlyDue`, `YOUR_TURN_BUZZ` and `YOUR_TURN_BUZZ_KEY` exported. A visible page waiting on another player's move that hears nothing for `TURN_QUIET_MS` (15 s) fetches the game again, so games need no polling of their own.
|
|
48
|
+
|
|
49
|
+
### Patch Changes
|
|
50
|
+
|
|
51
|
+
- Updated dependencies [3a67590]
|
|
52
|
+
- Updated dependencies [70dcfa1]
|
|
53
|
+
- Updated dependencies [c77830f]
|
|
54
|
+
- Updated dependencies [ac6b1cd]
|
|
55
|
+
- Updated dependencies [c12868a]
|
|
56
|
+
- @diffenderfer-games/hub-contract@2.0.0-next.8
|
|
57
|
+
|
|
58
|
+
## 2.0.0-next.7
|
|
59
|
+
|
|
60
|
+
### Minor Changes
|
|
61
|
+
|
|
62
|
+
- 99d0932: Avatars: long hair now hangs behind the body. The parts of a hair style below the shoulders are drawn before the body (in SVG, Canvas, `<dg-avatar>`, the Pixi atlas and the three.js adapter) while the fringe and anything over the face stay over the head; back views keep the hair over the back. The renderer draws the eight new glasses in style C with any frame colour (light frames, and frames close to the skin, get a dark edge) and tinted lenses for sunglasses, sport and hearts (a mirror finish for `p:lens-mirror`); winks and sleepy eyes show through the lenses. `describe()` names them. The `/avatar` entry re-exports `FRAME_PRESETS`, `isTinted` and `SECOND_COLOUR_CODE`.
|
|
63
|
+
|
|
64
|
+
### Patch Changes
|
|
65
|
+
|
|
66
|
+
- Updated dependencies [99d0932]
|
|
67
|
+
- @diffenderfer-games/hub-contract@2.0.0-next.7
|
|
68
|
+
|
|
69
|
+
## 2.0.0-next.6
|
|
70
|
+
|
|
71
|
+
### Minor Changes
|
|
72
|
+
|
|
73
|
+
- 537272a: `hub.avatar`: players' avatars without the renderer. `mine()` / `mineSync()` (works offline from the device cache), `of(userId)` and `many(ids)` (batched and cached), `from(seat)` (any payload with `look`, no request), `bot(seed)` for computer players, `save(look)` (queued offline), `on('change')`, `openDesigner()` ("Make your avatar" over the game) and `load()` (the `/avatar` renderer with its art). A DOM-free `@diffenderfer-games/hub/avatar/svg` entry renders avatars as SVG on servers and in workers.
|
|
74
|
+
- fb0a9f5: `hub.userId()`: your user id, or `null`, synchronously. It follows the account as the hub changes it (the menu's new-guest account, sign-in, sign-out, the live connection's hello) with no `hub.me()` call.
|
|
75
|
+
|
|
76
|
+
`hub.avatar` follows the account too: when your account appears or changes, your look is fetched and `change` fires with your `userId`; before it exists, `mine()` resolves at once with a default look. Each time the live connection (re)connects, the looks this page asked about are checked again and `change` fires for any that changed while it was down. `change` for your own look fires only when it differs from the one the page last had.
|
|
77
|
+
- 849874f: Avatars: draw players' faces in any game. New entries: `@diffenderfer-games/hub/avatar` (`ready()`, `render.svg/element/canvas/canvasAt/bitmap/dataUrl`, `<dg-avatar>` via `defineAvatarElement()`, the `animator` with 16 expressions, gaze, blinks, talking, reactions and `feel()`, `layout()` anchors, `facingFrom`, `lookToward`, `describe`, `avatarEmoji`, overlays and `createAtlas`), `@diffenderfer-games/hub/avatar/pixi` (`pixiAvatar`, `avatarTexture`, `createCrowd`) and `@diffenderfer-games/hub/avatar/three` (`avatarCanvasTexture`, `avatarSprite`). The adapters take your PIXI or THREE namespace; no new dependencies. See "Avatars" in docs/hub.md.
|
|
78
|
+
- 5109a42: The avatar renderer draws the new `beard.short` (a short, neat beard with a moustache) in style C, with the same beard and moustache layers and tonal outline as the full beard, in SVG, `<dg-avatar>` and the Pixi and three.js adapters. `describe()` calls it "a short beard".
|
|
79
|
+
- e86bfb4: "Your turn" alerts and quiet hours. The contract adds the `your_turn` notice and preference kind, `NotifyPrefs.yourTurn`/`yourTurnOff`/`yourTurnAsked`/`quiet`, `QuietHours` and `PUT /social/notify/quiet` (`setQuietHours`), plus `POST /turns/:id/share` (`shareTurnGame`) and `autoStart` on `createTurnGame`. The client adds `TurnGame.share({ link?, public? })`, `hub.turns.create({ autoStart })`, `TURN_RESYNC_EVENT` (an open online game fetches what it missed when the live connection comes back; `turn` also fires on a `chatMode` or `link` change), and `hub.saved.onOpen(handler, { kinds })`. A second Rematch joins the first one's game.
|
|
80
|
+
- e0e38da: Daily challenges in progress. A started day is now a saved game: starting it again (the calendar, a `?daily=` link, Your games) picks it up where the player left it, at any age, and winning it any time later gives that day's credit. `hub.daily` adds `save({ state, progress, title?, version? })`, `resume(instanceId)`, `discard()` and `inProgress()`; `challenge.saved`/`savedVersion`/`instanceId`/`resumed` in `play(challenge)`; the `hub:daily-resumed`, `hub:daily-paused` and `hub:daily-discarded` events. `hub.daily.cancel()` now pauses the day (it stays in progress) instead of dropping it. `hub.resume.save()` during a daily saves to the day instead of being ignored (`blockedBy` no longer reports `'daily'`), so games that already use `hub.resume` get mid-daily saves for free; `hub.resume.clear()` during a daily restarts the day. Works offline (a day started offline lands as the one saved game for that day). The contract adds `StartedDailyChallenge` (start answers `instanceId`, `saved`, `savedVersion`, `resumed`), `POST /daily/instances/:id/resume`, `inProgress` days in `GET /daily` and `GET /games/:slug/daily`, and 409 `too_many_dailies` (60 per game).
|
|
81
|
+
- ce80c33: Saved games (foundations). New `hub.saved`: `list()` (this game's games in progress: daily challenges, the saved run, online games), `listAll()`, `opened` (the instance the page was opened for, `?hub_open=<id>&hub_kind=<kind>`), `onOpen(handler)` (switch to a game picked from the hub menu without a reload), `open(id)`, `discard(id)`, `counts()` and `on('change', cb)`. Works offline from the device's copies. The contract adds `api/saved.ts` (`GET /saved`, `GET /saved/:id`, `PUT /saved/:id/state`, `POST /saved/:id/discard`, `POST /games/:slug/saved/discard`), the `saved` socket event, `TurnSummary`, the error codes `instance_closed` and `too_many_dailies`, and the pure `compareSaved`/`countSaved`. `hub.resume` is unchanged for games; saved runs never expire any more (`RESUME_RETENTION_MS` is deprecated and unused).
|
|
82
|
+
- 0c5c833: Online games played over days. A game declares `game.turns` (`rules`, `players`, `bots`, `modes`, `maxApplyMs`) and ships its rules as one self-contained ES module (the `TurnRules` contract: `setup`, `view`, `toAct`, `apply`, `eventView`, `outcome`, `progress`, `autoMove`, `migrate`); the hub runs it on the server, so hands and decks never reach another player. New `hub.turns` (`create`, `join`, `open`, `list`, `openLobbies`, `link`) and `TurnGame` (`summary`, `view`, `mySeat`, `outcome`, `chat`, `chatMode`, `link`, `start`, `move`, `history`, `leave`, `nudge`, `setNotify`, `rematch`, `invite`, `letComputerPlay`, `say`, `refresh`, `close`, events `move`, `turn`, `ended`, `chat`). `@diffenderfer-games/hub/turns` exports the rules types, `defineTurnRules`, the seeded RNG and the `checkTurnRules` conformance kit for a game's own tests. `HubError.rule` carries a rules refusal's code; `hub.resume.blockedBy` can be `'turns'`. The contract adds `api/turns.ts`, the `turn` and `turn.chat` events, the turn error codes, `ErrorBody.rule`, Play online's `yourTurn`/`turns`/`openTurns`/`turnGames`, and the `turn_chat` report kind.
|
|
83
|
+
|
|
84
|
+
### Patch Changes
|
|
85
|
+
|
|
86
|
+
- 4a03d21: The avatar adapters take the engines' real types and give them back: `pixiAvatar`, `avatarTexture` and `createCrowd` (`/avatar/pixi`) accept `import * as PIXI from 'pixi.js'` or an object of the named imports, and `face.view` is pixi's own `Sprite` (it goes into a `Container` with no cast). `avatarSprite` and `avatarCanvasTexture` (`/avatar/three`) do the same for three.js, returning three's `Sprite` and `CanvasTexture`; `avatarCanvasTexture` needs only `CanvasTexture` (the new `ThreeTextureNs`). `PixiNs`, `PixiSprite`, `PixiContainer`, `PixiAvatar`, `CrowdOptions`, `ThreeNs` and `ThreeAvatarTexture` are generic over the game's classes, with defaults, so existing annotations keep compiling. No runtime change.
|
|
87
|
+
- bcb10f1: `hub.resume.clear()` during a daily challenge now reaches the hub (the day starts over from its seed on every device instead of the old state coming back), and `hub.resume.get()`/`load()` dropping an older-version saved run no longer restarts the daily being played.
|
|
88
|
+
- ac2884e: `hub.input.autoNavigateTarget()` returns the game's current `autoNavigate` target (its query and back action), so hub dialogs that borrow the gamepad give the game's menu navigation back when they close. Fixes gamepad menu navigation being lost after a social dialog (a confirm, the share sheet) closed.
|
|
89
|
+
- b2e9e64: Saved runs (`hub.resume`) are now saved games of their own kind and never expire; the API is unchanged. The menu's Reset game also offers to discard the game's daily challenges in progress.
|
|
90
|
+
- 93af853: `hub.input.setInsets` also publishes the insets as `--hub-inset-top` (and right, bottom, left) on the page, so the hub's toasts sit below a game's top bar (without it, the hub looks for a top bar in the page itself).
|
|
91
|
+
- 06f827c: `TurnGame` fires `turn` when a seated player changes their avatar (the seat's `look`), not only when statuses or players change.
|
|
92
|
+
- Updated dependencies [91ca7f9]
|
|
93
|
+
- Updated dependencies [46fa584]
|
|
94
|
+
- Updated dependencies [5109a42]
|
|
95
|
+
- Updated dependencies [bf6445c]
|
|
96
|
+
- Updated dependencies [508531a]
|
|
97
|
+
- Updated dependencies [e86bfb4]
|
|
98
|
+
- Updated dependencies [e0e38da]
|
|
99
|
+
- Updated dependencies [ce80c33]
|
|
100
|
+
- Updated dependencies [0c5c833]
|
|
101
|
+
- Updated dependencies [17cf0af]
|
|
102
|
+
- @diffenderfer-games/hub-contract@2.0.0-next.6
|
|
103
|
+
|
|
104
|
+
## 2.0.0-next.5
|
|
105
|
+
|
|
106
|
+
### Patch Changes
|
|
107
|
+
|
|
108
|
+
- 7585522: Saves offline no longer risk the cloud copy. Offline, `getSave(slot)` rejects with a `HubError` whose `code` is `save_unknown` (new export `SAVE_UNKNOWN_CODE`; `isOffline` is `true`) when the device has never seen the hub's copy of that slot, instead of answering `save: null`; it still answers from the device's copy after a snapshot (`prefetchAll`), an online read, a landed write, or for a slot the device wrote and has queued. A `putSave` replayed from the offline queue now sends `base`, the slot's `updatedAt` on the hub as the device last saw it, and the hub refuses it with 409 `save_conflict` when another device saved since (dropped and reported in the `hub:sync` conflicts); writes sent at once while online are unchanged. The client also remembers the signed-in user from `login`, `signup`, `claim` and `ensureGuest` replies, and forgets it on `logout`. Contract: `writeSave` takes an optional `base` and may answer 409; new error code `save_conflict`. Older clients (no `base`) behave as before. See docs/hub.md "Saves offline".
|
|
109
|
+
- Updated dependencies [7585522]
|
|
110
|
+
- @diffenderfer-games/hub-contract@2.0.0-next.5
|
|
111
|
+
|
|
112
|
+
## 2.0.0-next.4
|
|
113
|
+
|
|
114
|
+
### Minor Changes
|
|
115
|
+
|
|
116
|
+
- 9f993ab: Resume saved games. New `hub.resume` (`save`, `flush`, `clear`, `load`, `get`, `requested`, `blockedBy`, `list`, `discard`): one auto-saved run per player per game that the home page's new Resume card lists, so players pick long single-player runs up again on any device. Saves are kept on the device at once, debounced (3 s), sent when the page hides, and replay offline through the new `hub.resume` outbox kind; they are ignored during races, daily challenges and hub rooms. `hub.resume.requested` is a synchronous `true` when the page was opened from the card (`?hub_resume=1`, taken out of the URL). Games opt in with `game.resumable: true` in the manifest. Contract: `resumeEndpoints` (`getResumeSlot`, `saveResumeSlot`, `clearResumeSlot`, `listMyResumeSlots`), `ResumeSlot`, `ResumeSummary`, `ResumeProgress`, `ResumeWrite`, `RESUME_MAX_STATE_BYTES` (512 KB), `RESUME_TEXT_MAX`, `RESUME_RETENTION_MS` (60 days), `RESUME_REQUEST_PARAM`; `saveResumeSlot` and `clearResumeSlot` join `OFFLINE_REPLAY_ENDPOINTS`. See docs/hub.md "Resume saved games". Daily: `hub.daily.complete()` now ends the daily run at the call (`hub.daily.active` is `null` at once, even if the hub refuses the completion); games must call `hub.daily.cancel()` when the player leaves a daily unfinished, or `hub.resume.save` stays off (it warns once).
|
|
117
|
+
|
|
118
|
+
### Patch Changes
|
|
119
|
+
|
|
120
|
+
- Updated dependencies [9f993ab]
|
|
121
|
+
- @diffenderfer-games/hub-contract@2.0.0-next.4
|
|
122
|
+
|
|
123
|
+
## 2.0.0-next.3
|
|
124
|
+
|
|
125
|
+
### Patch Changes
|
|
126
|
+
|
|
127
|
+
- ce98e79: Focus rings only for keyboard and gamepad players. The input system tracks the last input modality on `<html data-hub-input="keys|pointer|touch">`: the navigation highlight and the catalog's `gp-focus` show only after a key or gamepad press, never after a tap or click, and touch screens show no focus ring on buttons at all (the browser's own included). The ring is new: a two-tone box-shadow (dark gap, light ring) that follows the element's own corner radius instead of an orange rectangle, tunable with `--hub-focus-color`, `--hub-focus-inner`, `--hub-focus-width` and `--hub-focus-gap`. New exports from `@diffenderfer-games/hub/input`: `FOCUS_RING_SHADOW`, `MODALITY_ATTRIBUTE`, `trackInputModality` and the `InputModality` type.
|
|
128
|
+
- @diffenderfer-games/hub-contract@2.0.0-next.3
|
|
129
|
+
|
|
130
|
+
## 2.0.0-next.2
|
|
131
|
+
|
|
132
|
+
### Minor Changes
|
|
133
|
+
|
|
134
|
+
- 3944819: `game.labels`: say what kind of game yours is, from a fixed vocabulary (`GAME_LABELS`, with display names in `GAME_LABEL_TITLES`, grouped in `GAME_LABEL_GROUPS`): genre (`cards`, `board`, `puzzle`, `arcade`, `platformer`, `real-time-strategy`, `physics`, `typing`, `word`, `sports`, `racing`), look (`2d`, `3d`), where it plays (`phone`, `controller`, `desktop`) and players (`multiplayer`, `single-player`). The home page filters by them. An unknown label is a `hub-doctor` warning, not an error.
|
|
135
|
+
|
|
136
|
+
New endpoints: `GET /catalog/players` (how many accounts and guests have each game open right now: counts only) and `GET /games/:slug/me/activity` (the signed-in player's plays and minutes in a game). The `lobbyOpen` notification preference now means "tell me when people want to play": an open public lobby or race, or a tournament opening.
|
|
137
|
+
- 937d61e: Explicit offline replay. Writes made offline now go into a typed operation outbox in `localStorage`: each op has a kind whose handler performs it (several dependent calls can be one op), ops replay in order with backoff when the hub is back and after a reload, one tab replays at a time, and the outbox is capped. Saves, stats, scores, the profile, daily completions, inventory grants and house exchanges, and starring a game all queue offline. Games add their own kinds with `hub.offline.define(kind, handler)`, queue them with `hub.offline.enqueue(kind, payload)`, list them with `hub.offline.pending()`, and hear `queued`, `replayed` and `failed` through `hub.offline.on(...)` (and `hub:offline-*` window events). An offline `hub.daily.complete()` resolves `null`, fires `hub:daily-queued`, and replays later. The contract marks the endpoints that may be replayed (`OFFLINE_REPLAY_ENDPOINTS`); each replayed call carries an operation key (`x-hub-op`) and the hub runs it once per key. `SyncConflict.op` is now the dropped outbox op.
|
|
138
|
+
- ed68402: Invite links and resilient online games (batch 2026-10, R13 and R14).
|
|
139
|
+
|
|
140
|
+
- **Invite links:** `hub.mp.inviteLink()` makes (or reuses) a shareable link to the room the player is in; `hub.mp.revokeInviteLink(token)` revokes it. Opening `/<slug>/<lobby>?hub_link=<token>` arrives as an ordinary `join` or `watch` launch with `via: 'link'`. Contract: `POST /mp/links`, `GET /mp/links`, `DELETE /mp/links/:token`, `POST /mp/links/:token/open`, the `InviteLink` shape and the `link_invalid`, `link_room_gone` and `too_many_links` error codes.
|
|
141
|
+
- **Resilient rooms:** the new `room.seat` event (`room.on('seat', …)`: `away` with `graceEndsAt`, `back`, `gone` with `reason`), `room.seats()`, `hub.rooms.reclaim()` (socket op `room.reclaim`), seats' `graceEndsAt`, and `Launch.via` (`'link' | 'rejoin'`). The host role now passes on instead of the room closing with `host_left`: once the host's seat is freed by default, or as soon as the host drops with `hub.rooms.create({ hostMigration: 'drop' })`.
|
|
142
|
+
- The realtime feature version is 3; on an older runtime the new methods reject with `OfflineError` asking for a reload.
|
|
143
|
+
- 5b68831: Tournaments (docs/multiplayer.md §15). `game.multiplayer.tournaments: true` says a game plays 1v1 tournament matches and reports their winner; race games need nothing. A ready match reaches the game as an ordinary `host`/`guest` launch with the new `launch.tournament` (`{ id, title, match, opponent }`); a hub-room game ends it with `room.end({ winnerId })`, an own-server game posts results with `tournament: { id, match }`.
|
|
144
|
+
|
|
145
|
+
New endpoints: `GET /play-online` (lobbies waiting for a player, invites, my tournaments, open ones coming up), `GET|POST /tournaments`, `GET|DELETE /tournaments/:id`, `POST /tournaments/:id/join|leave|start|invites`, `PUT|DELETE /tournaments/:id/follow` and `POST /tournaments/:id/matches/:match/launch`; the `tournament` socket event; the `tournament` notice kind; `GameInfo.tournaments`; `TOURNAMENT_LIMITS`. `hub.social.openPlayOnline()` opens the hub's Play online window (realtime feature version 4).
|
|
146
|
+
|
|
147
|
+
### Patch Changes
|
|
148
|
+
|
|
149
|
+
- `hub.input.axis(name)` reads a vertical-only axis (`axes: { paddle: { y: ['up', 'down'] } }`); it used to read 0.
|
|
150
|
+
- A save, profile or score meta with an optional field set to `undefined` is stored and sent as the JSON it stands for (the field left out); with the new outbox it was refused as "Malformed offline operation payload" and never reached the hub.
|
|
151
|
+
- Updated dependencies [3944819]
|
|
152
|
+
- Updated dependencies [937d61e]
|
|
153
|
+
- Updated dependencies [ed68402]
|
|
154
|
+
- Updated dependencies [5b68831]
|
|
155
|
+
- @diffenderfer-games/hub-contract@2.0.0-next.2
|
|
156
|
+
|
|
157
|
+
## 2.0.0-next.1
|
|
158
|
+
|
|
159
|
+
### Patch Changes
|
|
160
|
+
|
|
161
|
+
- Follow-ups from the Bubble Pop pilot. `hub-doctor` gives up after `--timeout` ms (default 10 s) and fails with `hub unreachable at <url>` instead of hanging. A v1 vendored singleton adopted on the page is now recorded in `window.__DG__` as `{ version: 'v1' }`. The README covers exact pins for prereleases, sharing the page with the host's `/_hub/hub.js`, and testing against the hub.
|
|
162
|
+
- Updated dependencies
|
|
163
|
+
- @diffenderfer-games/hub-contract@2.0.0-next.1
|
package/LICENSE
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 Philip Diffenderfer
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Philip Diffenderfer
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,139 +1,203 @@
|
|
|
1
|
-
# @diffenderfer-games/hub
|
|
2
|
-
|
|
3
|
-
The client library for games on [diffenderfer.games](https://diffenderfer.games):
|
|
4
|
-
accounts, saves, stats, leaderboards, inventory and trades, daily challenges,
|
|
5
|
-
the input system (keyboard, touch and gamepad), and the social, multiplayer,
|
|
6
|
-
rooms and race namespaces. It is typed from the hub's wire contract
|
|
7
|
-
(`@diffenderfer-games/hub-contract`), ships ES modules with `.d.ts` files, and
|
|
8
|
-
carries no zod at runtime.
|
|
9
|
-
|
|
10
|
-
## Install
|
|
11
|
-
|
|
12
|
-
```sh
|
|
13
|
-
npm install @diffenderfer-games/hub
|
|
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
|
-
|
|
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
|
-
hub
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
1
|
+
# @diffenderfer-games/hub
|
|
2
|
+
|
|
3
|
+
The client library for games on [diffenderfer.games](https://diffenderfer.games):
|
|
4
|
+
accounts, saves, stats, leaderboards, inventory and trades, daily challenges,
|
|
5
|
+
the input system (keyboard, touch and gamepad), and the social, multiplayer,
|
|
6
|
+
rooms and race namespaces. It is typed from the hub's wire contract
|
|
7
|
+
(`@diffenderfer-games/hub-contract`), ships ES modules with `.d.ts` files, and
|
|
8
|
+
carries no zod at runtime.
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```sh
|
|
13
|
+
npm install @diffenderfer-games/hub
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
**Prereleases: pin the exact version.** While the package is on `2.0.0-next.N`,
|
|
17
|
+
a caret range does not move between prereleases the way you might expect, so
|
|
18
|
+
install with `--save-exact` and bump on purpose:
|
|
19
|
+
|
|
20
|
+
```sh
|
|
21
|
+
npm install --save-exact @diffenderfer-games/hub@2.0.0-next.1
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
The `next` dist-tag always names the newest prerelease. `latest` points at the
|
|
25
|
+
same prerelease for now. It moves to `2.0.0` with the first stable release,
|
|
26
|
+
after which ordinary `^2.0.0` ranges are fine.
|
|
27
|
+
|
|
28
|
+
ESM only, for browsers (ES2020: Safari 14+, current Chrome, Edge and Firefox).
|
|
29
|
+
Node 20+ for the `hub-doctor` command.
|
|
30
|
+
|
|
31
|
+
## Use
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
import { hub } from '@diffenderfer-games/hub';
|
|
35
|
+
|
|
36
|
+
const { user } = await hub.me();
|
|
37
|
+
await hub.ensureGuest(); // a guest account, so progress saves without a sign-in
|
|
38
|
+
await hub.putSave('auto', { level: 7, gold: 120 });
|
|
39
|
+
await hub.submitScore('highscore', 9001, { title: 'High Score' });
|
|
40
|
+
hub.recordPlay();
|
|
41
|
+
|
|
42
|
+
hub.input.define({ groups: { play: { inputs: { jump: { keys: [' '], gamepad: { button: 0 } } } } } });
|
|
43
|
+
hub.input.enable('play');
|
|
44
|
+
hub.daily.define({ play: (challenge) => startLevel(challenge.seed) });
|
|
45
|
+
hub.overlay.autoPause({ pause, resume });
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
In a game the host serves, the slug and the API base are read from the page.
|
|
49
|
+
For anything else, create a client yourself:
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
import { createHub } from '@diffenderfer-games/hub';
|
|
53
|
+
|
|
54
|
+
const demo = createHub({ slug: 'my-game', apiBase: '/_api', offline: 'auto' });
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
A game's own JSON is typed by the game: `hub.getSave<MySave>('auto')`,
|
|
58
|
+
`hub.putSave<MySave>('auto', save)`, `hub.getProfile<MyProfile>()`.
|
|
59
|
+
|
|
60
|
+
## Entry points
|
|
61
|
+
|
|
62
|
+
| Import | What it has |
|
|
63
|
+
|---|---|
|
|
64
|
+
| `@diffenderfer-games/hub` | `hub`, `createHub`, `Hub`, the errors, every client type, and the input and daily exports |
|
|
65
|
+
| `@diffenderfer-games/hub/input` | `getInput`, `InputSystem`, the on-screen control layout, `getUiStack` |
|
|
66
|
+
| `@diffenderfer-games/hub/daily` | `getDaily`, `DailySystem` |
|
|
67
|
+
| `@diffenderfer-games/hub/race`, `/social`, `/mp`, `/rooms` | one namespace each (`race`, `social` with `presence` and `notify`, `mp`, `rooms`) |
|
|
68
|
+
| `@diffenderfer-games/hub/types` | type-only: every contract type (requests, replies, entities, error codes, socket ops and events, the `game.*` manifest) |
|
|
69
|
+
| `@diffenderfer-games/hub/auto` | everything above, and it sets `window.HubSDK` (what `/_hub/hub.js` is built from) |
|
|
70
|
+
|
|
71
|
+
The namespaces also hang off `hub` (`hub.input`, `hub.daily`, `hub.race`,
|
|
72
|
+
`hub.social`, `hub.presence`, `hub.notify`, `hub.mp`, `hub.rooms`,
|
|
73
|
+
`hub.overlay`, `hub.menu`), created on first use.
|
|
74
|
+
|
|
75
|
+
## Errors
|
|
76
|
+
|
|
77
|
+
Every call rejects with a `HubError` when it fails. `status` is the HTTP
|
|
78
|
+
status, or `undefined` when the hub could not be reached (`isOffline`).
|
|
79
|
+
`code`, `reason`, `hint` and `retryAfterMs` come from the hub's error body.
|
|
80
|
+
|
|
81
|
+
- `ContentRejectedError`: the safety filter refused some text (HTTP 422).
|
|
82
|
+
Keep the player's draft and show `hint`.
|
|
83
|
+
- `OfflineError`: an action that needs the hub was tried offline.
|
|
84
|
+
- `code: 'unsupported_by_hub'`: the hub does not have this call yet (the game
|
|
85
|
+
uses a newer client than the deployed hub). Deploy the hub first.
|
|
86
|
+
|
|
87
|
+
The analytics calls (`recordPlay`, `heartbeat`, `event`) never throw.
|
|
88
|
+
|
|
89
|
+
## Offline
|
|
90
|
+
|
|
91
|
+
With `offline: 'auto'` (the default), reads fall back to a cache in
|
|
92
|
+
`localStorage` when the hub can't be reached, and writes (saves, stats,
|
|
93
|
+
scores, profile, daily completions, inventory grants and exchanges, stars)
|
|
94
|
+
apply to the cache at once and are queued in an outbox. The outbox replays
|
|
95
|
+
in order when the connection returns (and after a reload), one tab at a
|
|
96
|
+
time, and the hub applies each replayed call once. `hub.sync()` replays it
|
|
97
|
+
on demand, `hub.pending()` counts what waits, and `hub.onStatus(listener)`
|
|
98
|
+
reports changes. `hub.prefetchAll()` warms the cache for every game in one
|
|
99
|
+
request. `offline: 'always'` never touches the network (develop without a
|
|
100
|
+
hub); `offline: 'never'` always throws instead.
|
|
101
|
+
|
|
102
|
+
Queue your own operations, with a handler that may make several dependent
|
|
103
|
+
calls:
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
hub.offline.define<{ score: number }>('my-game.finish', async (run, context) => {
|
|
107
|
+
const { rank } = await context.call('submitScore', { slug: 'my-game', key: 'runs', score: run.score });
|
|
108
|
+
await context.call('updateGameStat', { slug: 'my-game', key: 'best-rank', value: rank ?? 0, mode: 'max' });
|
|
109
|
+
});
|
|
110
|
+
hub.offline.enqueue('my-game.finish', { score: 4200 });
|
|
111
|
+
hub.offline.on('failed', ({ op, error }) => console.warn(op.kind, error));
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
The full rules (what replays, ordering, retries, the size cap) are in the
|
|
115
|
+
hub docs' "Offline and replay" section. The cache keeps the old vendored
|
|
116
|
+
client's `localStorage` keys, and its queue moves into the outbox, so a game
|
|
117
|
+
that switches to this package keeps its players' queued progress.
|
|
118
|
+
|
|
119
|
+
## Games without a build step
|
|
120
|
+
|
|
121
|
+
Keep `import('/_hub/hub.js')` or `window.HubSDK.hub`: the host serves a
|
|
122
|
+
bundle of this package's `./auto` entry, at the version the host was built
|
|
123
|
+
with. Install the package as a dev dependency only if you want editor types:
|
|
124
|
+
|
|
125
|
+
```js
|
|
126
|
+
/** @type {import('@diffenderfer-games/hub').Hub} */
|
|
127
|
+
const hub = window.HubSDK.hub;
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Sharing the page with the host's `/_hub/hub.js`
|
|
131
|
+
|
|
132
|
+
Every game page also gets the host's menu and social runtime, which use the
|
|
133
|
+
host's own copy of this client (`/_hub/hub.js`, built from the version the host
|
|
134
|
+
was deployed with). Your bundled copy and the host's cooperate. The page-wide
|
|
135
|
+
pieces (the input system, daily challenges, the realtime API) are registered
|
|
136
|
+
once on `window.__DG__`, each with the version that created it:
|
|
137
|
+
|
|
138
|
+
- **Same shape** (the usual case): the first one wins and both copies share
|
|
139
|
+
it, so an older patch version on either side is harmless.
|
|
140
|
+
- **A different shape** (a major change): your game's copy owns input and
|
|
141
|
+
daily; the host's runtime owns realtime. The mismatch is logged once in the
|
|
142
|
+
console with the fix: `npm update @diffenderfer-games/hub`.
|
|
143
|
+
- **A v1 vendored copy** already on the page is shared too, and recorded as
|
|
144
|
+
`{ version: 'v1' }`.
|
|
145
|
+
|
|
146
|
+
The v1 globals (`window.__HUB_INPUT__`, `__HUB_DAILY__`, `__HUB_RT__`) stay as
|
|
147
|
+
aliases. Don't create your own singletons; `hub.input`, `hub.daily` and the
|
|
148
|
+
forwarders always return the shared ones.
|
|
149
|
+
|
|
150
|
+
## Versions
|
|
151
|
+
|
|
152
|
+
The client sends its version as `x-hub-client`. `hub.hubVersion()` reads the
|
|
153
|
+
hub's `GET /_api/version` (`{ server, contract, supported, deprecated }`) once
|
|
154
|
+
per page, and `hub.clientSupport()` says whether this client is `supported`,
|
|
155
|
+
`deprecated` or `unsupported` (`unknown` on a hub from before versioning).
|
|
156
|
+
|
|
157
|
+
The hub supports the current major and the previous one. Update with
|
|
158
|
+
`npm update @diffenderfer-games/hub`.
|
|
159
|
+
|
|
160
|
+
## hub-doctor
|
|
161
|
+
|
|
162
|
+
Run it in a game's folder, in CI and before pushing:
|
|
163
|
+
|
|
164
|
+
```sh
|
|
165
|
+
npx hub-doctor # checks against https://diffenderfer.games
|
|
166
|
+
npx hub-doctor --offline # skip the version check
|
|
167
|
+
npx hub-doctor --timeout 5000 # wait at most 5 s for the hub (default 10 s)
|
|
168
|
+
npx hub-doctor --hub http://localhost:8080 --dir ../my-game --json
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
An unreachable or slow hub is an error (`hub unreachable at <url> (...)`) and
|
|
172
|
+
exits non-zero, so CI notices. Use `--offline` where CI has no network.
|
|
173
|
+
|
|
174
|
+
It fails when the installed client is outside the hub's `supported` range or
|
|
175
|
+
the `package.json` `game.*` block does not match the manifest schema, and it
|
|
176
|
+
warns when the client is deprecated or a vendored `src/hub/` copy (or
|
|
177
|
+
`scripts/sync-hub.mjs`) is still around.
|
|
178
|
+
|
|
179
|
+
## Testing your game against the hub
|
|
180
|
+
|
|
181
|
+
The hub's test kit lives in the host repo (`test/helpers/`). Point your
|
|
182
|
+
game's tests at a checkout of it with `DG_HOST`; the default is
|
|
183
|
+
`../diffenderfer-games`. It boots a real hub on a free port for each test
|
|
184
|
+
file (the same hub and host production runs):
|
|
185
|
+
|
|
186
|
+
```sh
|
|
187
|
+
DG_HOST=../diffenderfer-games npm run test:hub
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
The checkout needs `npm ci` at its root.
|
|
191
|
+
|
|
192
|
+
## Moving from the vendored copy
|
|
193
|
+
|
|
194
|
+
1. `npm install @diffenderfer-games/hub`.
|
|
195
|
+
2. Change `from './hub/hub'` (or `'./hub'`) to `from '@diffenderfer-games/hub'`.
|
|
196
|
+
3. Delete `src/hub/` and `scripts/sync-hub.mjs`.
|
|
197
|
+
4. Add `npx hub-doctor` to the game's CI.
|
|
198
|
+
|
|
199
|
+
Every method name is unchanged.
|
|
200
|
+
|
|
201
|
+
## License
|
|
202
|
+
|
|
203
|
+
MIT
|