@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.
Files changed (293) hide show
  1. package/CHANGELOG.md +163 -0
  2. package/LICENSE +21 -21
  3. package/README.md +203 -139
  4. package/dist/auto.d.ts +8 -8
  5. package/dist/auto.js +3 -3
  6. package/dist/avatar-pixi.d.ts +165 -0
  7. package/dist/avatar-pixi.d.ts.map +1 -0
  8. package/dist/avatar-pixi.js +299 -0
  9. package/dist/avatar-pixi.js.map +1 -0
  10. package/dist/avatar-svg.d.ts +4 -0
  11. package/dist/avatar-svg.js +5 -0
  12. package/dist/avatar-three.d.ts +69 -0
  13. package/dist/avatar-three.d.ts.map +1 -0
  14. package/dist/avatar-three.js +49 -0
  15. package/dist/avatar-three.js.map +1 -0
  16. package/dist/avatar.d.ts +7 -0
  17. package/dist/avatar.js +500 -0
  18. package/dist/avatar.js.map +1 -0
  19. package/dist/daily.d.ts +2 -2
  20. package/dist/doctor/cli.mjs +119 -28
  21. package/dist/doctor/cli.mjs.map +1 -1
  22. package/dist/index.d.ts +8 -8
  23. package/dist/index.js +3 -3
  24. package/dist/input.d.ts +2 -2
  25. package/dist/input.js +2 -2
  26. package/dist/mp.d.ts +3 -3
  27. package/dist/race.d.ts +1 -1
  28. package/dist/rooms.d.ts +3 -3
  29. package/dist/rooms.js +1 -1
  30. package/dist/shared/animator.d.ts +104 -0
  31. package/dist/shared/animator.d.ts.map +1 -0
  32. package/dist/shared/art.js +1946 -0
  33. package/dist/shared/art.js.map +1 -0
  34. package/dist/shared/atlas.d.ts +138 -0
  35. package/dist/shared/atlas.d.ts.map +1 -0
  36. package/dist/shared/avatar.d.ts +123 -0
  37. package/dist/shared/avatar.d.ts.map +1 -0
  38. package/dist/shared/bridge.js +18 -4
  39. package/dist/shared/bridge.js.map +1 -1
  40. package/dist/shared/client-version.gen.js +1 -1
  41. package/dist/shared/daily.d.ts +145 -19
  42. package/dist/shared/daily.d.ts.map +1 -1
  43. package/dist/shared/daily.js +138 -25
  44. package/dist/shared/daily.js.map +1 -1
  45. package/dist/shared/defineProperty.js +39 -0
  46. package/dist/shared/describe.d.ts +32 -0
  47. package/dist/shared/describe.d.ts.map +1 -0
  48. package/dist/shared/describe.js +109 -0
  49. package/dist/shared/describe.js.map +1 -0
  50. package/dist/shared/globals.js +9 -42
  51. package/dist/shared/globals.js.map +1 -1
  52. package/dist/shared/index.d.ts +1122 -46
  53. package/dist/shared/index.d.ts.map +1 -1
  54. package/dist/shared/input.d.ts +74 -14
  55. package/dist/shared/input.d.ts.map +1 -1
  56. package/dist/shared/input.js +179 -25
  57. package/dist/shared/input.js.map +1 -1
  58. package/dist/shared/json-guards.js +48 -0
  59. package/dist/shared/json-guards.js.map +1 -0
  60. package/dist/shared/latest-state.js +1 -1
  61. package/dist/shared/live-canvas.js +62 -0
  62. package/dist/shared/live-canvas.js.map +1 -0
  63. package/dist/shared/mp.js +10 -2
  64. package/dist/shared/mp.js.map +1 -1
  65. package/dist/shared/multiplayer-types.d.ts +15 -2
  66. package/dist/shared/multiplayer-types.d.ts.map +1 -1
  67. package/dist/shared/overlays.js +800 -0
  68. package/dist/shared/overlays.js.map +1 -0
  69. package/dist/shared/page-stack.js +2 -1
  70. package/dist/shared/page-stack.js.map +1 -1
  71. package/dist/shared/pose-key.js +249 -0
  72. package/dist/shared/pose-key.js.map +1 -0
  73. package/dist/shared/race-types.d.ts +1 -1
  74. package/dist/shared/race.js +1 -1
  75. package/dist/shared/room-types.d.ts +28 -3
  76. package/dist/shared/room-types.d.ts.map +1 -1
  77. package/dist/shared/rooms.d.ts.map +1 -1
  78. package/dist/shared/rooms.js +23 -4
  79. package/dist/shared/rooms.js.map +1 -1
  80. package/dist/shared/runtime-opener.js +1 -1
  81. package/dist/shared/scene.d.ts +90 -0
  82. package/dist/shared/scene.d.ts.map +1 -0
  83. package/dist/shared/social.d.ts +90 -9
  84. package/dist/shared/social.d.ts.map +1 -1
  85. package/dist/shared/social.js +6 -1
  86. package/dist/shared/social.js.map +1 -1
  87. package/dist/shared/src.js +4563 -667
  88. package/dist/shared/src.js.map +1 -1
  89. package/dist/shared/svg-markup.js +2354 -0
  90. package/dist/shared/svg-markup.js.map +1 -0
  91. package/dist/shared/svg.js +27 -0
  92. package/dist/shared/svg.js.map +1 -0
  93. package/dist/shared/types.d.ts +174 -0
  94. package/dist/shared/types.d.ts.map +1 -0
  95. package/dist/shared/wire-types.d.ts +6 -2
  96. package/dist/shared/wire-types.d.ts.map +1 -1
  97. package/dist/social.d.ts +5 -5
  98. package/dist/social.js +2 -2
  99. package/dist/turns-rules.d.ts +2 -0
  100. package/dist/turns-rules.js +1 -0
  101. package/dist/turns.d.ts +61 -0
  102. package/dist/turns.d.ts.map +1 -0
  103. package/dist/turns.js +183 -0
  104. package/dist/turns.js.map +1 -0
  105. package/package.json +125 -89
  106. package/src/auto.ts +39 -39
  107. package/src/avatar/adapters/pixi-avatar.ts +73 -0
  108. package/src/avatar/adapters/pixi-crowd.ts +329 -0
  109. package/src/avatar/adapters/pixi-types.ts +84 -0
  110. package/src/avatar/adapters/three.ts +126 -0
  111. package/src/avatar/animator/animator.ts +285 -0
  112. package/src/avatar/animator/reactions.ts +44 -0
  113. package/src/avatar/animator/schedule.ts +73 -0
  114. package/src/avatar/art/beard.ts +49 -0
  115. package/src/avatar/art/glasses.ts +67 -0
  116. package/src/avatar/art/hair.ts +254 -0
  117. package/src/avatar/art/headwear.ts +75 -0
  118. package/src/avatar/art/index.ts +21 -0
  119. package/src/avatar/art/shape.ts +87 -0
  120. package/src/avatar/art-store.ts +36 -0
  121. package/src/avatar/atlas.ts +246 -0
  122. package/src/avatar/catalog-view.ts +14 -0
  123. package/src/avatar/colour.ts +109 -0
  124. package/src/avatar/describe.ts +79 -0
  125. package/src/avatar/face.ts +187 -0
  126. package/src/avatar/geom.ts +173 -0
  127. package/src/avatar/layout.ts +47 -0
  128. package/src/avatar/look-like.ts +52 -0
  129. package/src/avatar/outputs/bitmap.ts +75 -0
  130. package/src/avatar/outputs/canvas-paint.ts +177 -0
  131. package/src/avatar/outputs/canvas.ts +62 -0
  132. package/src/avatar/outputs/custom-element.ts +140 -0
  133. package/src/avatar/outputs/element.ts +58 -0
  134. package/src/avatar/outputs/live-canvas.ts +57 -0
  135. package/src/avatar/outputs/overlay-svg.ts +22 -0
  136. package/src/avatar/outputs/pose-key.ts +30 -0
  137. package/src/avatar/outputs/svg-markup.ts +113 -0
  138. package/src/avatar/outputs/svg.ts +41 -0
  139. package/src/avatar/overlays.ts +128 -0
  140. package/src/avatar/render/body.ts +116 -0
  141. package/src/avatar/render/colours.ts +81 -0
  142. package/src/avatar/render/compose.ts +320 -0
  143. package/src/avatar/render/context.ts +117 -0
  144. package/src/avatar/render/eyes.ts +78 -0
  145. package/src/avatar/render/eyewear.ts +118 -0
  146. package/src/avatar/render/frame.ts +73 -0
  147. package/src/avatar/render/head.ts +81 -0
  148. package/src/avatar/render/mouth.ts +86 -0
  149. package/src/avatar/render/props.ts +91 -0
  150. package/src/avatar/render/types.ts +93 -0
  151. package/src/avatar/render/walker-parts.ts +121 -0
  152. package/src/avatar/render/walker.ts +199 -0
  153. package/src/avatar/scene.ts +119 -0
  154. package/src/avatar-pixi.ts +20 -0
  155. package/src/avatar-svg.ts +21 -0
  156. package/src/avatar-three.ts +18 -0
  157. package/src/avatar.ts +63 -0
  158. package/src/core/avatar/avatar-cache.ts +95 -0
  159. package/src/core/avatar/avatar-ref.ts +34 -0
  160. package/src/core/avatar/avatar-types.ts +43 -0
  161. package/src/core/avatar/hub-avatar.ts +308 -0
  162. package/src/core/connection.ts +408 -299
  163. package/src/core/errors.ts +160 -147
  164. package/src/core/game-data.ts +40 -38
  165. package/src/core/hub-economy.ts +216 -179
  166. package/src/core/hub-progress.ts +269 -205
  167. package/src/core/hub.ts +352 -131
  168. package/src/core/menu-control.ts +61 -61
  169. package/src/core/offline/cloud-saves.ts +62 -0
  170. package/src/core/offline/events.ts +50 -50
  171. package/src/core/offline/favorites.ts +21 -21
  172. package/src/core/offline/hub-kinds/avatar-kind.ts +42 -0
  173. package/src/core/offline/hub-kinds/daily-kind.ts +68 -0
  174. package/src/core/offline/hub-kinds/economy-kinds.ts +86 -0
  175. package/src/core/offline/hub-kinds/game-fields.ts +2 -0
  176. package/src/core/offline/hub-kinds/index.ts +52 -0
  177. package/src/core/offline/hub-kinds/legacy-ops.ts +37 -0
  178. package/src/core/offline/hub-kinds/progress-kinds.ts +165 -0
  179. package/src/core/offline/hub-kinds/resume-kind.ts +108 -0
  180. package/src/core/offline/hub-kinds/saved-kind.ts +93 -0
  181. package/src/core/offline/kind-adapters.ts +48 -0
  182. package/src/core/offline/local-inventory.ts +64 -0
  183. package/src/core/offline/local-store.ts +304 -261
  184. package/src/core/offline/offline-api.ts +65 -0
  185. package/src/core/offline/outbox-entry.ts +59 -0
  186. package/src/core/offline/outbox-kinds.ts +78 -0
  187. package/src/core/offline/outbox-merge.ts +39 -0
  188. package/src/core/offline/outbox-replayer.ts +141 -0
  189. package/src/core/offline/outbox-store.ts +85 -0
  190. package/src/core/offline/outbox.ts +232 -0
  191. package/src/core/offline/queue.ts +126 -171
  192. package/src/core/offline/replay-lease.ts +87 -0
  193. package/src/core/offline/replay-policy.ts +52 -0
  194. package/src/core/page-account.ts +121 -0
  195. package/src/core/page.ts +87 -74
  196. package/src/core/rest/endpoint-types.ts +22 -22
  197. package/src/core/rest/rest-client.ts +163 -152
  198. package/src/core/rest/routes.gen.ts +144 -88
  199. package/src/core/resume/hub-resume.ts +286 -0
  200. package/src/core/resume/local-resume.ts +130 -0
  201. package/src/core/resume/resume-list.ts +65 -0
  202. package/src/core/resume/resume-request.ts +52 -0
  203. package/src/core/resume/resume-types.ts +64 -0
  204. package/src/core/saved/daily-saves.ts +196 -0
  205. package/src/core/saved/daily-seed.ts +16 -0
  206. package/src/core/saved/hub-saved.ts +221 -0
  207. package/src/core/saved/local-saved.ts +122 -0
  208. package/src/core/saved/saved-list.ts +129 -0
  209. package/src/core/saved/saved-open.ts +104 -0
  210. package/src/core/saved/saved-order.ts +49 -0
  211. package/src/core/saved/saved-types.ts +24 -0
  212. package/src/core/storage/json-guards.ts +80 -74
  213. package/src/core/storage/key-value.ts +66 -66
  214. package/src/core/ticket.ts +43 -43
  215. package/src/core/turns/hub-turns.ts +161 -0
  216. package/src/core/turns/turn-game.ts +510 -0
  217. package/src/core/turns/your-turn-buzz.ts +69 -0
  218. package/src/core/types.ts +134 -134
  219. package/src/core/version/client-version.gen.ts +2 -2
  220. package/src/core/version/hub-version.ts +62 -62
  221. package/src/core/version/semver.ts +97 -97
  222. package/src/daily/daily-system.ts +276 -161
  223. package/src/daily/page-daily.ts +36 -36
  224. package/src/daily/types.ts +105 -67
  225. package/src/daily.ts +10 -10
  226. package/src/doctor/checks.ts +86 -76
  227. package/src/doctor/cli.ts +101 -106
  228. package/src/doctor/hub-version-fetch.ts +43 -0
  229. package/src/doctor/tsconfig.json +12 -12
  230. package/src/doctor/world-check.ts +61 -0
  231. package/src/globals.ts +115 -110
  232. package/src/index.ts +68 -50
  233. package/src/input/device-state.ts +146 -146
  234. package/src/input/edges.ts +55 -55
  235. package/src/input/focus-ring.ts +13 -0
  236. package/src/input/gamepad.ts +61 -61
  237. package/src/input/input-events.ts +32 -32
  238. package/src/input/input-registry.ts +171 -164
  239. package/src/input/input-system.ts +406 -393
  240. package/src/input/legacy-controls.ts +64 -64
  241. package/src/input/modality.ts +122 -0
  242. package/src/input/nav-state.ts +200 -191
  243. package/src/input/navigator.ts +182 -171
  244. package/src/input/on-screen-controls.ts +101 -101
  245. package/src/input/page-control.ts +43 -43
  246. package/src/input/page-input.ts +193 -193
  247. package/src/input/player-remaps.ts +77 -77
  248. package/src/input/source-values.ts +116 -116
  249. package/src/input/spatial.ts +47 -47
  250. package/src/input/types.ts +331 -331
  251. package/src/input/zero-config.ts +164 -177
  252. package/src/input.ts +26 -22
  253. package/src/mp.ts +10 -10
  254. package/src/overlay/controls-panel.ts +163 -163
  255. package/src/overlay/dom.ts +46 -46
  256. package/src/overlay/layout-clusters.ts +162 -162
  257. package/src/overlay/layout-geometry.ts +136 -136
  258. package/src/overlay/layout.ts +203 -203
  259. package/src/overlay/listener-scope.ts +23 -23
  260. package/src/overlay/on-screen-keyboard.ts +137 -137
  261. package/src/overlay/overlay.ts +234 -208
  262. package/src/overlay/styles.ts +13 -12
  263. package/src/overlay/virtual-controls.ts +262 -262
  264. package/src/race.ts +10 -10
  265. package/src/realtime/bridge.ts +126 -121
  266. package/src/realtime/cross-bundle.ts +29 -29
  267. package/src/realtime/fallback-notice.ts +48 -48
  268. package/src/realtime/hub-link.ts +65 -65
  269. package/src/realtime/latest-state.ts +33 -33
  270. package/src/realtime/multiplayer-forwarder.ts +40 -28
  271. package/src/realtime/multiplayer-types.ts +66 -52
  272. package/src/realtime/page-runtime.ts +60 -60
  273. package/src/realtime/race-forwarder.ts +59 -59
  274. package/src/realtime/race-types.ts +132 -132
  275. package/src/realtime/room-types.ts +151 -125
  276. package/src/realtime/rooms-forwarder.ts +104 -79
  277. package/src/realtime/runtime-api.ts +74 -49
  278. package/src/realtime/runtime-opener.ts +61 -61
  279. package/src/realtime/social-forwarder.ts +77 -72
  280. package/src/realtime/social-types.ts +86 -78
  281. package/src/realtime/types.ts +54 -53
  282. package/src/realtime/wire-types.ts +79 -72
  283. package/src/realtime/world-types.ts +20 -0
  284. package/src/rooms.ts +10 -10
  285. package/src/social.ts +19 -18
  286. package/src/turns-kit/check-rules.ts +281 -0
  287. package/src/turns-rules.ts +12 -0
  288. package/src/turns.ts +14 -0
  289. package/src/types.ts +13 -13
  290. package/src/uistack/page-stack.ts +45 -45
  291. package/src/uistack/play-suspension.ts +82 -82
  292. package/src/uistack/types.ts +76 -76
  293. package/src/uistack/ui-stack.ts +144 -144
@@ -1,28 +1,40 @@
1
- /**
2
- * `hub.mp`: invites, launches, the leave guard and own-server tickets,
3
- * forwarded to the social runtime.
4
- */
5
- import { getUiStack } from '../uistack/page-stack';
6
- import { pageBridge } from './bridge';
7
- import { hubLink } from './hub-link';
8
- import { latestState } from './latest-state';
9
- import type { HubMpApi } from './multiplayer-types';
10
-
11
- /** `hub.mp`: the multiplayer lifecycle. */
12
- export const multiplayerForwarder: HubMpApi = {
13
- onLaunch: (listener) => pageBridge.subscribe((realtime) => realtime.mp.onLaunch(listener)),
14
- onJoinInfo: (listener) => pageBridge.subscribe((realtime) => realtime.mp.onJoinInfo(listener)),
15
- invite: (userId, options) => pageBridge.call((realtime) => realtime.mp.invite(userId, options)),
16
- setBusy(isBusy, options) {
17
- const busyOptions = options?.label === undefined ? {} : { label: options.label };
18
- latestState.busy = { isBusy, options: busyOptions };
19
- // The overlay stack is local, so "can't pause" works even without the runtime.
20
- getUiStack().setCanPause(!isBusy);
21
- pageBridge.current()?.mp.setBusy(isBusy, busyOptions);
22
- },
23
- // A direct request, not a runtime call: "Play online" must not depend on the
24
- // social runtime having loaded (an old cached menu may never load it).
25
- ticket: () => hubLink().getGameServerTicket(),
26
- setJoinInfo: (partyId, joinInfo) => pageBridge.call((realtime) => realtime.mp.setJoinInfo(partyId, joinInfo)),
27
- party: () => pageBridge.current()?.mp.party() ?? null,
28
- };
1
+ /**
2
+ * `hub.mp`: invites, launches, the leave guard and own-server tickets,
3
+ * forwarded to the social runtime.
4
+ */
5
+ import { getUiStack } from '../uistack/page-stack';
6
+ import { outdatedRuntime, pageBridge } from './bridge';
7
+ import { hubLink } from './hub-link';
8
+ import { latestState } from './latest-state';
9
+ import type { HubMpApi } from './multiplayer-types';
10
+
11
+ /** `hub.mp`: the multiplayer lifecycle. */
12
+ export const multiplayerForwarder: HubMpApi = {
13
+ onLaunch: (listener) => pageBridge.subscribe((realtime) => realtime.mp.onLaunch(listener)),
14
+ onJoinInfo: (listener) => pageBridge.subscribe((realtime) => realtime.mp.onJoinInfo(listener)),
15
+ invite: (userId, options) => pageBridge.call((realtime) => realtime.mp.invite(userId, options)),
16
+ setBusy(isBusy, options) {
17
+ const busyOptions = options?.label === undefined ? {} : { label: options.label };
18
+ latestState.busy = { isBusy, options: busyOptions };
19
+ // The overlay stack is local, so "can't pause" works even without the runtime.
20
+ getUiStack().setCanPause(!isBusy);
21
+ pageBridge.current()?.mp.setBusy(isBusy, busyOptions);
22
+ },
23
+ // A direct request, not a runtime call: "Play online" must not depend on the
24
+ // social runtime having loaded (an old cached menu may never load it).
25
+ ticket: () => hubLink().getGameServerTicket(),
26
+ setJoinInfo: (partyId, joinInfo) => pageBridge.call((realtime) => realtime.mp.setJoinInfo(partyId, joinInfo)),
27
+ party: () => pageBridge.current()?.mp.party() ?? null,
28
+ inviteLink: () => pageBridge.call((realtime) => {
29
+ if (realtime.mp.inviteLink === undefined) {
30
+ throw outdatedRuntime('Invite links');
31
+ }
32
+ return realtime.mp.inviteLink();
33
+ }),
34
+ revokeInviteLink: (token) => pageBridge.call((realtime) => {
35
+ if (realtime.mp.revokeInviteLink === undefined) {
36
+ throw outdatedRuntime('Invite links');
37
+ }
38
+ return realtime.mp.revokeInviteLink(token);
39
+ }),
40
+ };
@@ -1,52 +1,66 @@
1
- /**
2
- * The multiplayer lifecycle as games see it through `hub.mp`: invites,
3
- * launches, the leave guard and own-server tickets.
4
- */
5
- import type { Invite, JoinInfo, Launch, Party } from '@diffenderfer-games/hub-contract';
6
-
7
- /** `hub.mp.onJoinInfo` payload: the party leader published where to connect. */
8
- export interface JoinInfoEvent {
9
- /** The party it is for. */
10
- partyId: string;
11
- /** Opaque connection details (a room code, a server address…). */
12
- info: JoinInfo;
13
- }
14
-
15
- /** What `hub.mp.invite` resolves with. */
16
- export interface InviteResult {
17
- /** The invite as sent. */
18
- invite: Invite;
19
- }
20
-
21
- /** Options for `hub.mp.invite`. */
22
- export interface InviteOptions {
23
- /** The game mode to invite to. */
24
- mode?: string;
25
- }
26
-
27
- /** Options for `hub.mp.setBusy`. */
28
- export interface BusyOptions {
29
- /** What the leave-confirmation calls the match ("a duel"). */
30
- label?: string;
31
- }
32
-
33
- /** `hub.mp`: the multiplayer lifecycle (docs/multiplayer.md §7). */
34
- export interface HubMpApi {
35
- /** Invite, join and watch arrivals. Call at boot; launches that arrived earlier are buffered. */
36
- onLaunch(listener: (launch: Launch) => void): () => void;
37
- /** The party leader published join info (e.g. a room code). */
38
- onJoinInfo(listener: (event: JoinInfoEvent) => void): () => void;
39
- /** Invite a friend to this game. Rejects with `HubError` or `OfflineError`. */
40
- invite(userId: number, options?: InviteOptions): Promise<InviteResult>;
41
- /**
42
- * A live match is in progress: the hub confirms before navigating away and
43
- * overlay events carry `canPause: false`.
44
- */
45
- setBusy(isBusy: boolean, options?: BusyOptions): void;
46
- /** A 60-second identity ticket for your own game server. */
47
- ticket(): Promise<string>;
48
- /** Host side, own-server games: tell the party where to connect. */
49
- setJoinInfo(partyId: string, joinInfo: JoinInfo): Promise<void>;
50
- /** My current party, or `null` (also `null` until the runtime is up). */
51
- party(): Party | null;
52
- }
1
+ /**
2
+ * The multiplayer lifecycle as games see it through `hub.mp`: invites,
3
+ * launches, the leave guard and own-server tickets.
4
+ */
5
+ import type { Invite, JoinInfo, Launch, Party } from '@diffenderfer-games/hub-contract';
6
+ import type { InviteLink } from './wire-types';
7
+
8
+ /** `hub.mp.onJoinInfo` payload: the party leader published where to connect. */
9
+ export interface JoinInfoEvent {
10
+ /** The party it is for. */
11
+ partyId: string;
12
+ /** Opaque connection details (a room code, a server address…). */
13
+ info: JoinInfo;
14
+ }
15
+
16
+ /** What `hub.mp.invite` resolves with. */
17
+ export interface InviteResult {
18
+ /** The invite as sent. */
19
+ invite: Invite;
20
+ }
21
+
22
+ /** Options for `hub.mp.invite`. */
23
+ export interface InviteOptions {
24
+ /** The game mode to invite to. */
25
+ mode?: string;
26
+ }
27
+
28
+ /** Options for `hub.mp.setBusy`. */
29
+ export interface BusyOptions {
30
+ /** What the leave-confirmation calls the match ("a duel"). */
31
+ label?: string;
32
+ }
33
+
34
+ /** What feature version 3 added to `hub.mp`: invite links. */
35
+ export interface InviteLinkMethods {
36
+ /**
37
+ * A shareable link to the room I'm in (my live one when it exists): anyone
38
+ * who opens it joins while a seat is free, otherwise watches. It expires
39
+ * after 24 h. Claimed accounts only; rejects with `HubError`
40
+ * (`not_in_room`, `too_many_links`, `rate_limited`) or `OfflineError`.
41
+ */
42
+ inviteLink(): Promise<InviteLink>;
43
+ /** Revoke a link I made, or one into a room I host. */
44
+ revokeInviteLink(token: string): Promise<void>;
45
+ }
46
+
47
+ /** `hub.mp`: the multiplayer lifecycle (docs/multiplayer.md §7). */
48
+ export interface HubMpApi extends InviteLinkMethods {
49
+ /** Invite, join and watch arrivals. Call at boot; launches that arrived earlier are buffered. */
50
+ onLaunch(listener: (launch: Launch) => void): () => void;
51
+ /** The party leader published join info (e.g. a room code). */
52
+ onJoinInfo(listener: (event: JoinInfoEvent) => void): () => void;
53
+ /** Invite a friend to this game. Rejects with `HubError` or `OfflineError`. */
54
+ invite(userId: number, options?: InviteOptions): Promise<InviteResult>;
55
+ /**
56
+ * A live match is in progress: the hub confirms before navigating away and
57
+ * overlay events carry `canPause: false`.
58
+ */
59
+ setBusy(isBusy: boolean, options?: BusyOptions): void;
60
+ /** A 60-second identity ticket for your own game server. */
61
+ ticket(): Promise<string>;
62
+ /** Host side, own-server games: tell the party where to connect. */
63
+ setJoinInfo(partyId: string, joinInfo: JoinInfo): Promise<void>;
64
+ /** My current party, or `null` (also `null` until the runtime is up). */
65
+ party(): Party | null;
66
+ }
@@ -1,60 +1,60 @@
1
- /**
2
- * Finding the social runtime's realtime API on the page. The runtime owns the
3
- * `rt` singleton (plan §4.4): it registers on `window.__DG__.rt`, and older
4
- * runtimes only set the v1 name `window.__HUB_RT__`.
5
- */
6
- import { LEGACY_GLOBAL_NAMES } from '../globals';
7
- import { hasMethods, isObject, readField } from './cross-bundle';
8
- import type { RealtimeApi } from './runtime-api';
9
- import { REALTIME_SINGLETON_API } from './runtime-api';
10
-
11
- /** Whether `candidate` looks like a {@link RealtimeApi} (any bundle's). */
12
- // eslint-disable-next-line no-restricted-syntax -- trust boundary: the runtime is another bundle
13
- export function isRealtimeApi(candidate: unknown): candidate is RealtimeApi {
14
- return typeof readField(candidate, 'version') === 'number'
15
- && hasMethods(readField(candidate, 'presence'), ['set', 'clear'])
16
- && hasMethods(readField(candidate, 'social'), ['openPlayer', 'friends', 'counts', 'chatMode', 'on'])
17
- && hasMethods(readField(candidate, 'mp'), ['onLaunch', 'onJoinInfo', 'invite', 'setBusy', 'party'])
18
- && hasMethods(readField(candidate, 'rooms'), ['create', 'join', 'list'])
19
- && hasMethods(readField(candidate, 'notify'), ['setQuiet']);
20
- }
21
-
22
- let hasWarnedAboutApi = false;
23
-
24
- /** Warn once when the runtime registered a different shape number than this client knows. */
25
- function warnOnApiMismatch(registeredApi: number): void {
26
- if (registeredApi === REALTIME_SINGLETON_API || hasWarnedAboutApi) {
27
- return;
28
- }
29
- hasWarnedAboutApi = true;
30
- // eslint-disable-next-line no-console -- the plan's one-time warning to the game developer, with the fix
31
- console.warn(`[hub] this page's social runtime speaks realtime api ${String(registeredApi)}, this client api ${String(REALTIME_SINGLETON_API)}. Fix: npm update @diffenderfer-games/hub`);
32
- }
33
-
34
- /** The page's realtime API if the runtime has loaded, else `null`. */
35
- export function findRealtimeApi(): RealtimeApi | null {
36
- if (typeof window === 'undefined') {
37
- return null;
38
- }
39
- const registered = window.__DG__?.rt;
40
- if (registered !== undefined && isRealtimeApi(registered.instance)) {
41
- warnOnApiMismatch(registered.api);
42
- return registered.instance;
43
- }
44
- const legacy = window[LEGACY_GLOBAL_NAMES.rt];
45
- return isRealtimeApi(legacy) ? legacy : null;
46
- }
47
-
48
- /** Whether the hub injected this page (`window.__HUB__`); without it the runtime can never arrive. */
49
- export function isHubInjectedPage(): boolean {
50
- return typeof window !== 'undefined' && isObject(readField(window, '__HUB__'));
51
- }
52
-
53
- /** Why the social runtime failed to load on this page (set by the menu), or `''`. */
54
- export function readRuntimeFailure(): string {
55
- if (typeof window === 'undefined') {
56
- return '';
57
- }
58
- const failure = readField(window, '__HUB_RT_ERROR__');
59
- return typeof failure === 'string' ? failure.slice(0, 120) : '';
60
- }
1
+ /**
2
+ * Finding the social runtime's realtime API on the page. The runtime owns the
3
+ * `rt` singleton (plan §4.4): it registers on `window.__DG__.rt`, and older
4
+ * runtimes only set the v1 name `window.__HUB_RT__`.
5
+ */
6
+ import { LEGACY_GLOBAL_NAMES } from '../globals';
7
+ import { hasMethods, isObject, readField } from './cross-bundle';
8
+ import type { RealtimeApi } from './runtime-api';
9
+ import { REALTIME_SINGLETON_API } from './runtime-api';
10
+
11
+ /** Whether `candidate` looks like a {@link RealtimeApi} (any bundle's). */
12
+ // eslint-disable-next-line no-restricted-syntax -- trust boundary: the runtime is another bundle
13
+ export function isRealtimeApi(candidate: unknown): candidate is RealtimeApi {
14
+ return typeof readField(candidate, 'version') === 'number'
15
+ && hasMethods(readField(candidate, 'presence'), ['set', 'clear'])
16
+ && hasMethods(readField(candidate, 'social'), ['openPlayer', 'friends', 'counts', 'chatMode', 'on'])
17
+ && hasMethods(readField(candidate, 'mp'), ['onLaunch', 'onJoinInfo', 'invite', 'setBusy', 'party'])
18
+ && hasMethods(readField(candidate, 'rooms'), ['create', 'join', 'list'])
19
+ && hasMethods(readField(candidate, 'notify'), ['setQuiet']);
20
+ }
21
+
22
+ let hasWarnedAboutApi = false;
23
+
24
+ /** Warn once when the runtime registered a different shape number than this client knows. */
25
+ function warnOnApiMismatch(registeredApi: number): void {
26
+ if (registeredApi === REALTIME_SINGLETON_API || hasWarnedAboutApi) {
27
+ return;
28
+ }
29
+ hasWarnedAboutApi = true;
30
+ // eslint-disable-next-line no-console -- the plan's one-time warning to the game developer, with the fix
31
+ console.warn(`[hub] this page's social runtime speaks realtime api ${String(registeredApi)}, this client api ${String(REALTIME_SINGLETON_API)}. Fix: npm update @diffenderfer-games/hub`);
32
+ }
33
+
34
+ /** The page's realtime API if the runtime has loaded, else `null`. */
35
+ export function findRealtimeApi(): RealtimeApi | null {
36
+ if (typeof window === 'undefined') {
37
+ return null;
38
+ }
39
+ const registered = window.__DG__?.rt;
40
+ if (registered !== undefined && isRealtimeApi(registered.instance)) {
41
+ warnOnApiMismatch(registered.api);
42
+ return registered.instance;
43
+ }
44
+ const legacy = window[LEGACY_GLOBAL_NAMES.rt];
45
+ return isRealtimeApi(legacy) ? legacy : null;
46
+ }
47
+
48
+ /** Whether the hub injected this page (`window.__HUB__`); without it the runtime can never arrive. */
49
+ export function isHubInjectedPage(): boolean {
50
+ return typeof window !== 'undefined' && isObject(readField(window, '__HUB__'));
51
+ }
52
+
53
+ /** Why the social runtime failed to load on this page (set by the menu), or `''`. */
54
+ export function readRuntimeFailure(): string {
55
+ if (typeof window === 'undefined') {
56
+ return '';
57
+ }
58
+ const failure = readField(window, '__HUB_RT_ERROR__');
59
+ return typeof failure === 'string' ? failure.slice(0, 120) : '';
60
+ }
@@ -1,59 +1,59 @@
1
- /**
2
- * `hub.race`: races, forwarded to the social runtime. An old cached runtime
3
- * (feature version 1) has no races, which reads as "not available" rather
4
- * than a crash.
5
- */
6
- import { pageBridge } from './bridge';
7
- import { hubLink } from './hub-link';
8
- import type { HubRaceApi, RaceDefinition } from './race-types';
9
- import type { RealtimeApi } from './runtime-api';
10
- import { createRuntimeOpener } from './runtime-opener';
11
-
12
- const openWithRuntime = createRuntimeOpener(pageBridge);
13
-
14
- let raceDefinition: RaceDefinition | null = null;
15
- pageBridge.onReady((realtime) => {
16
- if (raceDefinition !== null) {
17
- realtime.race?.define(raceDefinition);
18
- }
19
- });
20
-
21
- /** The runtime's race section, or an `OfflineError` for a runtime too old to race. */
22
- function requireRaces(realtime: RealtimeApi): HubRaceApi {
23
- if (realtime.race === undefined) {
24
- throw hubLink().createOfflineError('Races need the latest hub — reload the page.');
25
- }
26
- return realtime.race;
27
- }
28
-
29
- /** `hub.race`: races. */
30
- export const raceForwarder: HubRaceApi = {
31
- define(definition) {
32
- raceDefinition = definition;
33
- pageBridge.current()?.race?.define(definition);
34
- },
35
- create: (options) => pageBridge.call((realtime) => requireRaces(realtime).create(options)),
36
- join: (code, options) => pageBridge.call((realtime) => requireRaces(realtime).join(code, options)),
37
- list: () => pageBridge.call((realtime) => requireRaces(realtime).list()),
38
- browse: () => {
39
- openWithRuntime((realtime) => {
40
- realtime.race?.browse();
41
- });
42
- },
43
- openHistory: (options) => {
44
- openWithRuntime((realtime) => {
45
- realtime.race?.openHistory(options);
46
- });
47
- },
48
- history: (query) => pageBridge.call((realtime) => requireRaces(realtime).history(query)),
49
- status: (stats, progress) => {
50
- pageBridge.current()?.race?.status(stats, progress);
51
- },
52
- finish: (stats) => pageBridge.call((realtime) => requireRaces(realtime).finish(stats)),
53
- lose: (stats) => pageBridge.call((realtime) => requireRaces(realtime).lose(stats)),
54
- forfeit: () => pageBridge.call((realtime) => requireRaces(realtime).forfeit()),
55
- leave: () => pageBridge.call((realtime) => requireRaces(realtime).leave()),
56
- rematch: (params) => pageBridge.call((realtime) => requireRaces(realtime).rematch(params)),
57
- current: () => pageBridge.current()?.race?.current() ?? null,
58
- on: (event, listener) => pageBridge.subscribe((realtime) => realtime.race?.on(event, listener) ?? (() => undefined)),
59
- };
1
+ /**
2
+ * `hub.race`: races, forwarded to the social runtime. An old cached runtime
3
+ * (feature version 1) has no races, which reads as "not available" rather
4
+ * than a crash.
5
+ */
6
+ import { pageBridge } from './bridge';
7
+ import { hubLink } from './hub-link';
8
+ import type { HubRaceApi, RaceDefinition } from './race-types';
9
+ import type { RealtimeApi } from './runtime-api';
10
+ import { createRuntimeOpener } from './runtime-opener';
11
+
12
+ const openWithRuntime = createRuntimeOpener(pageBridge);
13
+
14
+ let raceDefinition: RaceDefinition | null = null;
15
+ pageBridge.onReady((realtime) => {
16
+ if (raceDefinition !== null) {
17
+ realtime.race?.define(raceDefinition);
18
+ }
19
+ });
20
+
21
+ /** The runtime's race section, or an `OfflineError` for a runtime too old to race. */
22
+ function requireRaces(realtime: RealtimeApi): HubRaceApi {
23
+ if (realtime.race === undefined) {
24
+ throw hubLink().createOfflineError('Races need the latest hub — reload the page.');
25
+ }
26
+ return realtime.race;
27
+ }
28
+
29
+ /** `hub.race`: races. */
30
+ export const raceForwarder: HubRaceApi = {
31
+ define(definition) {
32
+ raceDefinition = definition;
33
+ pageBridge.current()?.race?.define(definition);
34
+ },
35
+ create: (options) => pageBridge.call((realtime) => requireRaces(realtime).create(options)),
36
+ join: (code, options) => pageBridge.call((realtime) => requireRaces(realtime).join(code, options)),
37
+ list: () => pageBridge.call((realtime) => requireRaces(realtime).list()),
38
+ browse: () => {
39
+ openWithRuntime((realtime) => {
40
+ realtime.race?.browse();
41
+ });
42
+ },
43
+ openHistory: (options) => {
44
+ openWithRuntime((realtime) => {
45
+ realtime.race?.openHistory(options);
46
+ });
47
+ },
48
+ history: (query) => pageBridge.call((realtime) => requireRaces(realtime).history(query)),
49
+ status: (stats, progress) => {
50
+ pageBridge.current()?.race?.status(stats, progress);
51
+ },
52
+ finish: (stats) => pageBridge.call((realtime) => requireRaces(realtime).finish(stats)),
53
+ lose: (stats) => pageBridge.call((realtime) => requireRaces(realtime).lose(stats)),
54
+ forfeit: () => pageBridge.call((realtime) => requireRaces(realtime).forfeit()),
55
+ leave: () => pageBridge.call((realtime) => requireRaces(realtime).leave()),
56
+ rematch: (params) => pageBridge.call((realtime) => requireRaces(realtime).rematch(params)),
57
+ current: () => pageBridge.current()?.race?.current() ?? null,
58
+ on: (event, listener) => pageBridge.subscribe((realtime) => realtime.race?.on(event, listener) ?? (() => undefined)),
59
+ };