@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
|
@@ -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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
invite
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
mode
|
|
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
|
-
party(
|
|
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
|
+
};
|