@dreamboard-games/cli 0.1.30-alpha.1 → 0.1.30-alpha.11
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/README.md +179 -22
- package/dist/agent-verifier/agent-workspace-verifier.mjs +31 -30
- package/dist/agent-verifier/agent-workspace-verifier.mjs.map +1 -0
- package/dist/agent-verifier/{chunk-4WD3YU2E.mjs → chunk-3IJBOLGT.mjs} +4 -12
- package/dist/agent-verifier/chunk-3IJBOLGT.mjs.map +1 -0
- package/dist/agent-verifier/{chunk-6A5HRJMQ.mjs → chunk-4GU3PCHV.mjs} +62 -99
- package/dist/agent-verifier/chunk-4GU3PCHV.mjs.map +1 -0
- package/dist/agent-verifier/{chunk-SYPLYRGB.mjs → chunk-6XRC5PWB.mjs} +119 -310
- package/dist/agent-verifier/chunk-6XRC5PWB.mjs.map +1 -0
- package/dist/agent-verifier/{chunk-BVVNBJM4.mjs → chunk-COB56ESI.mjs} +2 -1
- package/dist/agent-verifier/chunk-COB56ESI.mjs.map +1 -0
- package/dist/agent-verifier/{chunk-2GBBP27W.mjs → chunk-F2DIOJJZ.mjs} +1 -0
- package/dist/agent-verifier/chunk-F2DIOJJZ.mjs.map +1 -0
- package/dist/agent-verifier/{chunk-CFU5EWIC.mjs → chunk-G42BGGG2.mjs} +7 -6
- package/dist/agent-verifier/chunk-G42BGGG2.mjs.map +1 -0
- package/dist/agent-verifier/{chunk-XYDL7GY6.mjs → chunk-H6XDQJ3N.mjs} +1 -0
- package/dist/agent-verifier/{chunk-LM3OZLZG.mjs → chunk-IAYRNVUC.mjs} +1 -0
- package/dist/agent-verifier/chunk-IAYRNVUC.mjs.map +1 -0
- package/dist/agent-verifier/{chunk-2QMNAVV4.mjs → chunk-JZTH3EMV.mjs} +2 -1
- package/dist/agent-verifier/chunk-JZTH3EMV.mjs.map +1 -0
- package/dist/agent-verifier/chunk-KK47X7RV.mjs +14 -0
- package/dist/agent-verifier/chunk-KK47X7RV.mjs.map +1 -0
- package/dist/agent-verifier/{chunk-SHUMAVAP.mjs → chunk-M7UVBANQ.mjs} +8 -9
- package/dist/agent-verifier/chunk-M7UVBANQ.mjs.map +1 -0
- package/dist/agent-verifier/{chunk-2E5P5NWG.mjs → chunk-NAK77WXW.mjs} +58 -126
- package/dist/agent-verifier/chunk-NAK77WXW.mjs.map +1 -0
- package/dist/agent-verifier/{chunk-CEQ2VJWN.mjs → chunk-POBFNXD4.mjs} +2 -1
- package/dist/agent-verifier/chunk-POBFNXD4.mjs.map +1 -0
- package/dist/agent-verifier/{chunk-6UUJEYDV.mjs → chunk-QBAF7EYR.mjs} +1 -0
- package/dist/agent-verifier/chunk-QBAF7EYR.mjs.map +1 -0
- package/dist/agent-verifier/{chunk-7653FPGJ.mjs → chunk-RHI6S4SU.mjs} +3 -2
- package/dist/agent-verifier/chunk-RHI6S4SU.mjs.map +1 -0
- package/dist/agent-verifier/{chunk-MINCYHXN.mjs → chunk-TAEQKBJB.mjs} +1 -0
- package/dist/agent-verifier/chunk-TAEQKBJB.mjs.map +1 -0
- package/dist/agent-verifier/{chunk-7E65UQLY.mjs → chunk-TLYGTHXU.mjs} +3 -2
- package/dist/agent-verifier/chunk-TLYGTHXU.mjs.map +1 -0
- package/dist/agent-verifier/{chunk-JH22JNYD.mjs → chunk-UIJ2NDG6.mjs} +93 -24
- package/dist/agent-verifier/chunk-UIJ2NDG6.mjs.map +1 -0
- package/dist/agent-verifier/{chunk-EIQWDQWJ.mjs → chunk-UWJIZML3.mjs} +13 -14
- package/dist/agent-verifier/chunk-UWJIZML3.mjs.map +1 -0
- package/dist/agent-verifier/{chunk-CJEEA6NJ.mjs → chunk-VLOIZDR6.mjs} +15 -31
- package/dist/agent-verifier/chunk-VLOIZDR6.mjs.map +1 -0
- package/dist/agent-verifier/{chunk-HJFQDSTU.mjs → chunk-W2MDP5ZN.mjs} +6 -5
- package/dist/agent-verifier/chunk-W2MDP5ZN.mjs.map +1 -0
- package/dist/agent-verifier/{chunk-CEDUHGNH.mjs → chunk-XKCJBIRY.mjs} +2 -1
- package/dist/agent-verifier/chunk-XKCJBIRY.mjs.map +1 -0
- package/dist/agent-verifier/{chunk-VYJTHSYR.mjs → chunk-YDIOW2BO.mjs} +2 -1
- package/dist/agent-verifier/chunk-YDIOW2BO.mjs.map +1 -0
- package/dist/agent-verifier/{chunk-MRCUP5SW.mjs → chunk-YE7UAO3T.mjs} +1 -0
- package/dist/agent-verifier/chunk-YE7UAO3T.mjs.map +1 -0
- package/dist/agent-verifier/{chunk-EOQIV6PS.mjs → chunk-YR664DJX.mjs} +111 -116
- package/dist/agent-verifier/chunk-YR664DJX.mjs.map +1 -0
- package/dist/agent-verifier/{chunk-2SZHMP6F.mjs → chunk-Z6OZWUIZ.mjs} +6 -9
- package/dist/agent-verifier/chunk-Z6OZWUIZ.mjs.map +1 -0
- package/dist/agent-verifier/{chunk-RBDDIIPM.mjs → chunk-ZEELHSY3.mjs} +1 -0
- package/dist/agent-verifier/chunk-ZEELHSY3.mjs.map +1 -0
- package/dist/agent-verifier/{compile-5QSPIOUT.mjs → compile-WZ7X6I2A.mjs} +27 -27
- package/dist/agent-verifier/compile-WZ7X6I2A.mjs.map +1 -0
- package/dist/agent-verifier/{global-config-WX3ZZIVU.mjs → global-config-XHL7BCKN.mjs} +6 -5
- package/dist/agent-verifier/global-config-XHL7BCKN.mjs.map +1 -0
- package/dist/agent-verifier/{keychain-backend-TNOPQV3Z.mjs → keychain-backend-A3MRWLPF.mjs} +2 -1
- package/dist/agent-verifier/keychain-backend-A3MRWLPF.mjs.map +1 -0
- package/dist/agent-verifier/{local-files-MTPLP62S.mjs → local-files-ZW52HSVT.mjs} +10 -11
- package/dist/agent-verifier/local-files-ZW52HSVT.mjs.map +1 -0
- package/dist/agent-verifier/local-typecheck-3JXL2NMG.mjs +10 -0
- package/dist/agent-verifier/local-typecheck-3JXL2NMG.mjs.map +1 -0
- package/dist/agent-verifier/{materialize-workspace-FKALAE2T.mjs → materialize-workspace-BKZLLFI4.mjs} +20 -20
- package/dist/agent-verifier/materialize-workspace-BKZLLFI4.mjs.map +1 -0
- package/dist/agent-verifier/{project-state-7GR6BQTQ.mjs → project-state-XKUSCFSV.mjs} +3 -2
- package/dist/agent-verifier/project-state-XKUSCFSV.mjs.map +1 -0
- package/dist/agent-verifier/{prompt-3BAINGAQ.mjs → prompt-VKHMCQT6.mjs} +2 -1
- package/dist/agent-verifier/prompt-VKHMCQT6.mjs.map +1 -0
- package/dist/agent-verifier/{reducer-bundle-preflight-C73LEXI2.mjs → reducer-bundle-preflight-7NYZF5ZT.mjs} +6 -9
- package/dist/agent-verifier/reducer-bundle-preflight-7NYZF5ZT.mjs.map +1 -0
- package/dist/agent-verifier/reducer-contract-preflight-COD2CO22.mjs +11 -0
- package/dist/agent-verifier/reducer-contract-preflight-COD2CO22.mjs.map +1 -0
- package/dist/agent-verifier/{reducer-native-test-harness-GMWBUISX.mjs → reducer-native-test-harness-D4VWPIAC.mjs} +14 -17
- package/dist/agent-verifier/reducer-native-test-harness-D4VWPIAC.mjs.map +1 -0
- package/dist/agent-verifier/static-scaffold-JCRBDKEH.mjs +26 -0
- package/dist/agent-verifier/static-scaffold-JCRBDKEH.mjs.map +1 -0
- package/dist/agent-verifier/{sync-3DUQH32H.mjs → sync-ELLJEWMB.mjs} +41 -39
- package/dist/agent-verifier/sync-ELLJEWMB.mjs.map +1 -0
- package/dist/agent-verifier/{test-P4U5INTD.mjs → test-OSXBPLSP.mjs} +29 -31
- package/dist/agent-verifier/test-OSXBPLSP.mjs.map +1 -0
- package/dist/agent-verifier/workspace-codegen-WPZHMATU.mjs +10 -0
- package/dist/agent-verifier/workspace-codegen-WPZHMATU.mjs.map +1 -0
- package/dist/agent-verifier/{workspace-dependencies-HZ6VVS4G.mjs → workspace-dependencies-ULZZZPNX.mjs} +5 -4
- package/dist/agent-verifier/workspace-dependencies-ULZZZPNX.mjs.map +1 -0
- package/dist/{chunk-C6UAT6EH.js → chunk-GXM7RRZJ.js} +9 -11
- package/dist/chunk-GXM7RRZJ.js.map +1 -0
- package/dist/{chunk-RS7UXJZV.js → chunk-P5TITCD3.js} +790 -17875
- package/dist/chunk-P5TITCD3.js.map +1 -0
- package/dist/{global-config-AGFBDFYD.js → global-config-WPJRXVDO.js} +2 -2
- package/dist/global-config-WPJRXVDO.js.map +1 -0
- package/dist/index.js +455 -54
- package/dist/index.js.map +1 -1
- package/dist/internal.js +2 -3
- package/package.json +8 -7
- package/skills/dreamboard/references/building-your-first-game.md +510 -0
- package/skills/dreamboard/references/cli.md +104 -0
- package/skills/dreamboard/references/game-interface.md +548 -0
- package/skills/dreamboard/references/manifest-authoring.md +597 -0
- package/skills/dreamboard/references/quickstart.md +66 -0
- package/skills/dreamboard/references/reducer.md +864 -0
- package/skills/dreamboard/references/rule-authoring.md +147 -0
- package/skills/dreamboard/references/testing.md +249 -0
- package/skills/dreamboard/scripts/events-extract.mjs +218 -0
- package/dist/agent-verifier/chunk-54TAYXUD.mjs +0 -12
- package/dist/agent-verifier/chunk-HBNDKQT5.mjs +0 -8381
- package/dist/agent-verifier/chunk-LI3ZR3BI.mjs +0 -41
- package/dist/agent-verifier/chunk-U6OJN7XS.mjs +0 -8092
- package/dist/agent-verifier/local-typecheck-QFYYAZOK.mjs +0 -9
- package/dist/agent-verifier/reducer-contract-preflight-22X7DSZW.mjs +0 -10
- package/dist/agent-verifier/static-scaffold-AJMZZQWS.mjs +0 -28
- package/dist/agent-verifier/testing-5K2BJYF2.mjs +0 -674
- package/dist/agent-verifier/workspace-codegen-JDZJRSDV.mjs +0 -11
- package/dist/chunk-7FOO4AJI.js +0 -50
- package/dist/chunk-7FOO4AJI.js.map +0 -1
- package/dist/chunk-C6UAT6EH.js.map +0 -1
- package/dist/chunk-RS7UXJZV.js.map +0 -1
- package/dist/internal.d.ts +0 -311
- package/dist/runtime-packages/ui-host-runtime/src/actor-principal.ts +0 -71
- package/dist/runtime-packages/ui-host-runtime/src/browser-interaction.ts +0 -139
- package/dist/runtime-packages/ui-host-runtime/src/components/host-controls.tsx +0 -374
- package/dist/runtime-packages/ui-host-runtime/src/components/host-feedback-toaster.tsx +0 -266
- package/dist/runtime-packages/ui-host-runtime/src/components/host-feedback.tsx +0 -212
- package/dist/runtime-packages/ui-host-runtime/src/components/host-primitives.tsx +0 -271
- package/dist/runtime-packages/ui-host-runtime/src/components/host-session-metadata.tsx +0 -135
- package/dist/runtime-packages/ui-host-runtime/src/components/index.ts +0 -5
- package/dist/runtime-packages/ui-host-runtime/src/components/perf-overlay.tsx +0 -194
- package/dist/runtime-packages/ui-host-runtime/src/gameplay-authority-transport.ts +0 -626
- package/dist/runtime-packages/ui-host-runtime/src/host-controls.tsx +0 -1
- package/dist/runtime-packages/ui-host-runtime/src/host-feedback.tsx +0 -1
- package/dist/runtime-packages/ui-host-runtime/src/host-session-transport.ts +0 -294
- package/dist/runtime-packages/ui-host-runtime/src/index.ts +0 -3
- package/dist/runtime-packages/ui-host-runtime/src/logger.ts +0 -11
- package/dist/runtime-packages/ui-host-runtime/src/perf.ts +0 -324
- package/dist/runtime-packages/ui-host-runtime/src/plugin-bridge.ts +0 -195
- package/dist/runtime-packages/ui-host-runtime/src/plugin-health-check.ts +0 -138
- package/dist/runtime-packages/ui-host-runtime/src/plugin-messages.ts +0 -159
- package/dist/runtime-packages/ui-host-runtime/src/plugin-session-gateway.ts +0 -551
- package/dist/runtime-packages/ui-host-runtime/src/runtime/index.ts +0 -13
- package/dist/runtime-packages/ui-host-runtime/src/screenshot/projection-to-snapshot.ts +0 -122
- package/dist/runtime-packages/ui-host-runtime/src/screenshot/static-store-api.ts +0 -26
- package/dist/runtime-packages/ui-host-runtime/src/session-ingress-controller.ts +0 -583
- package/dist/runtime-packages/ui-host-runtime/src/session-ingress.ts +0 -219
- package/dist/runtime-packages/ui-host-runtime/src/session-live-runtime.ts +0 -117
- package/dist/runtime-packages/ui-host-runtime/src/session-model.ts +0 -431
- package/dist/runtime-packages/ui-host-runtime/src/session-projection.ts +0 -211
- package/dist/runtime-packages/ui-host-runtime/src/session-recovery.ts +0 -80
- package/dist/runtime-packages/ui-host-runtime/src/session-state-reducer.ts +0 -1034
- package/dist/runtime-packages/ui-host-runtime/src/sse-manager.ts +0 -416
- package/dist/runtime-packages/ui-host-runtime/src/unified-session-store.ts +0 -184
- package/dist/testing-KLSV6CPJ.js +0 -674
- package/dist/testing-KLSV6CPJ.js.map +0 -1
- /package/dist/{global-config-AGFBDFYD.js.map → agent-verifier/chunk-H6XDQJ3N.mjs.map} +0 -0
|
@@ -1,551 +0,0 @@
|
|
|
1
|
-
import type { PluginStateSnapshot } from "@dreamboard-games/sdk/runtime/runtime-api";
|
|
2
|
-
import type { ValidationResult } from "@dreamboard-games/sdk/runtime/runtime-api";
|
|
3
|
-
import { PluginBridge } from "./plugin-bridge.js";
|
|
4
|
-
import { PluginHealthCheck } from "./plugin-health-check.js";
|
|
5
|
-
import type { LoggerLike } from "./logger.js";
|
|
6
|
-
import { consoleLogger } from "./logger.js";
|
|
7
|
-
import { PERF_MARK_NAMES, findActionIdBySyncId, recordMark } from "./perf.js";
|
|
8
|
-
|
|
9
|
-
export interface GameSessionStoreApi {
|
|
10
|
-
getStateSnapshot: () => PluginStateSnapshot;
|
|
11
|
-
subscribe: (callback: () => void) => () => void;
|
|
12
|
-
onStateAck: (syncId: number) => void;
|
|
13
|
-
markNotificationRead: (notificationId: string) => void;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export type GatewayState = "loading" | "handshaking" | "connected" | "error";
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Per-interaction metadata the gateway forwards from the plugin down to
|
|
20
|
-
* `onInteraction`. Currently carries just the client-minted correlation id
|
|
21
|
-
* used by Tier-0 input-latency observability; keeping it as a bag lets
|
|
22
|
-
* future perf/debug fields (e.g. plugin-side timestamps) ride along
|
|
23
|
-
* without another signature change.
|
|
24
|
-
*/
|
|
25
|
-
export interface InteractionMeta {
|
|
26
|
-
clientActionId?: string;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export interface PluginSessionGatewayConfig {
|
|
30
|
-
iframe: HTMLIFrameElement;
|
|
31
|
-
sessionId: string;
|
|
32
|
-
controllablePlayerIds: string[];
|
|
33
|
-
controllingPlayerId: string;
|
|
34
|
-
userId: string | null;
|
|
35
|
-
onReady: () => void;
|
|
36
|
-
onError: (error: Error) => void;
|
|
37
|
-
onInteraction: (
|
|
38
|
-
playerId: string,
|
|
39
|
-
interactionId: string,
|
|
40
|
-
params: unknown,
|
|
41
|
-
meta?: InteractionMeta,
|
|
42
|
-
) => void | Promise<void>;
|
|
43
|
-
onValidateInteraction: (
|
|
44
|
-
playerId: string,
|
|
45
|
-
interactionId: string,
|
|
46
|
-
params: unknown,
|
|
47
|
-
) => Promise<ValidationResult>;
|
|
48
|
-
onSwitchPlayer?: (playerId: string) => void;
|
|
49
|
-
onRestoreHistory?: (entryId: string) => void;
|
|
50
|
-
logger?: LoggerLike;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export class PluginSessionGateway {
|
|
54
|
-
private bridge: PluginBridge | null = null;
|
|
55
|
-
private healthCheck: PluginHealthCheck | null = null;
|
|
56
|
-
private state: GatewayState = "loading";
|
|
57
|
-
private unsubscribeHandlers: Array<() => void> = [];
|
|
58
|
-
private config: PluginSessionGatewayConfig;
|
|
59
|
-
private readyTimeout: ReturnType<typeof setTimeout> | null = null;
|
|
60
|
-
private initStartTimeout: ReturnType<typeof setTimeout> | null = null;
|
|
61
|
-
private initRetryInterval: ReturnType<typeof setInterval> | null = null;
|
|
62
|
-
private storeUnsubscribe: (() => void) | null = null;
|
|
63
|
-
private gameSessionStore: GameSessionStoreApi | null = null;
|
|
64
|
-
private lastSentSnapshotKey: string | null = null;
|
|
65
|
-
private logger: LoggerLike;
|
|
66
|
-
|
|
67
|
-
constructor(config: PluginSessionGatewayConfig) {
|
|
68
|
-
this.config = config;
|
|
69
|
-
this.logger = config.logger ?? consoleLogger;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
connect(): void {
|
|
73
|
-
if (this.bridge) {
|
|
74
|
-
this.logger.warn("[Gateway] Already connected");
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
this.state = "loading";
|
|
79
|
-
|
|
80
|
-
const targetOrigin = "*";
|
|
81
|
-
this.bridge = new PluginBridge(this.config.iframe, targetOrigin, {
|
|
82
|
-
logger: this.logger,
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
this.readyTimeout = setTimeout(() => {
|
|
86
|
-
if (this.state !== "connected") {
|
|
87
|
-
this.handleError(
|
|
88
|
-
new Error("Plugin failed to send ready message within 10 seconds"),
|
|
89
|
-
);
|
|
90
|
-
}
|
|
91
|
-
}, 10000);
|
|
92
|
-
|
|
93
|
-
this.setupInteractionHandler();
|
|
94
|
-
this.setupValidateInteractionHandler();
|
|
95
|
-
this.setupReadyHandler();
|
|
96
|
-
this.setupErrorHandler();
|
|
97
|
-
this.setupSwitchPlayerHandler();
|
|
98
|
-
this.setupRestoreHistoryHandler();
|
|
99
|
-
this.setupStateAckHandler();
|
|
100
|
-
this.setupStateRenderedHandler();
|
|
101
|
-
this.setupMarkNotificationReadHandler();
|
|
102
|
-
|
|
103
|
-
this.state = "handshaking";
|
|
104
|
-
|
|
105
|
-
this.initStartTimeout = setTimeout(() => {
|
|
106
|
-
this.initStartTimeout = null;
|
|
107
|
-
if (this.state === "error") {
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
this.sendInit();
|
|
111
|
-
this.initRetryInterval = setInterval(() => {
|
|
112
|
-
if (this.state === "connected") {
|
|
113
|
-
this.clearInitRetryInterval();
|
|
114
|
-
return;
|
|
115
|
-
}
|
|
116
|
-
this.sendInit();
|
|
117
|
-
}, 250);
|
|
118
|
-
}, 50);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
attachStore(store: GameSessionStoreApi): void {
|
|
122
|
-
if (this.state !== "connected") {
|
|
123
|
-
this.logger.warn(
|
|
124
|
-
"[Gateway] Cannot attach store - plugin not ready yet (state: " +
|
|
125
|
-
this.state +
|
|
126
|
-
")",
|
|
127
|
-
);
|
|
128
|
-
return;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
if (this.storeUnsubscribe) {
|
|
132
|
-
this.logger.warn("[Gateway] Store already attached");
|
|
133
|
-
return;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
this.gameSessionStore = store;
|
|
137
|
-
|
|
138
|
-
this.flushStateSync();
|
|
139
|
-
|
|
140
|
-
this.storeUnsubscribe = store.subscribe(() => {
|
|
141
|
-
this.flushStateSync();
|
|
142
|
-
});
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
flushStateSync(): void {
|
|
146
|
-
if (!this.bridge || !this.gameSessionStore) return;
|
|
147
|
-
|
|
148
|
-
const snapshot = this.gameSessionStore.getStateSnapshot();
|
|
149
|
-
const snapshotKey = this.snapshotDeliveryKey(snapshot);
|
|
150
|
-
|
|
151
|
-
if (snapshotKey !== this.lastSentSnapshotKey) {
|
|
152
|
-
this.lastSentSnapshotKey = snapshotKey;
|
|
153
|
-
this.logger.log("[Gateway] State snapshot view present:", {
|
|
154
|
-
syncId: snapshot.syncId,
|
|
155
|
-
hasView: snapshot.view !== null,
|
|
156
|
-
controllingPlayerId: snapshot.session.controllingPlayerId,
|
|
157
|
-
controllablePlayerIds: snapshot.session.controllablePlayerIds,
|
|
158
|
-
});
|
|
159
|
-
this.bridge.sendStateSync(snapshot.syncId, snapshot);
|
|
160
|
-
// Tier-0 perf: stitch the outgoing state-sync to the action
|
|
161
|
-
// that caused it (via the syncId -> actionId map populated
|
|
162
|
-
// at `t5_store_applied`). For non-action-driven syncs (lobby
|
|
163
|
-
// updates etc.) the lookup returns undefined and the mark is
|
|
164
|
-
// a no-op.
|
|
165
|
-
const actionId = findActionIdBySyncId(snapshot.syncId);
|
|
166
|
-
if (actionId) {
|
|
167
|
-
recordMark(actionId, PERF_MARK_NAMES.T6_STATE_SYNC_POSTED, {
|
|
168
|
-
extra: { syncId: snapshot.syncId },
|
|
169
|
-
});
|
|
170
|
-
}
|
|
171
|
-
this.logger.log("[Gateway] Sent state-sync, syncId:", snapshot.syncId);
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
getState(): GatewayState {
|
|
176
|
-
return this.state;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
disconnect(): void {
|
|
180
|
-
if (this.readyTimeout) {
|
|
181
|
-
clearTimeout(this.readyTimeout);
|
|
182
|
-
this.readyTimeout = null;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
if (this.initStartTimeout) {
|
|
186
|
-
clearTimeout(this.initStartTimeout);
|
|
187
|
-
this.initStartTimeout = null;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
this.clearInitRetryInterval();
|
|
191
|
-
|
|
192
|
-
if (this.healthCheck) {
|
|
193
|
-
this.healthCheck.stop();
|
|
194
|
-
this.healthCheck = null;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
if (this.bridge) {
|
|
198
|
-
this.bridge.disconnect();
|
|
199
|
-
this.bridge = null;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
if (this.storeUnsubscribe) {
|
|
203
|
-
this.storeUnsubscribe();
|
|
204
|
-
this.storeUnsubscribe = null;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
this.unsubscribeHandlers.forEach((unsubscribe) => unsubscribe());
|
|
208
|
-
this.unsubscribeHandlers = [];
|
|
209
|
-
|
|
210
|
-
this.state = "loading";
|
|
211
|
-
this.lastSentSnapshotKey = null;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
private snapshotDeliveryKey(snapshot: PluginStateSnapshot): string {
|
|
215
|
-
return JSON.stringify({
|
|
216
|
-
syncId: snapshot.syncId,
|
|
217
|
-
sessionId: snapshot.session.sessionId,
|
|
218
|
-
controllingPlayerId: snapshot.session.controllingPlayerId,
|
|
219
|
-
controllablePlayerIds: snapshot.session.controllablePlayerIds,
|
|
220
|
-
userId: snapshot.session.userId,
|
|
221
|
-
});
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
private setupReadyHandler(): void {
|
|
225
|
-
if (!this.bridge) return;
|
|
226
|
-
|
|
227
|
-
const unsubscribe = this.bridge.onPluginMessage("ready", () => {
|
|
228
|
-
if (this.readyTimeout) {
|
|
229
|
-
clearTimeout(this.readyTimeout);
|
|
230
|
-
this.readyTimeout = null;
|
|
231
|
-
}
|
|
232
|
-
this.clearInitRetryInterval();
|
|
233
|
-
|
|
234
|
-
this.state = "connected";
|
|
235
|
-
|
|
236
|
-
if (this.bridge) {
|
|
237
|
-
this.healthCheck = new PluginHealthCheck(this.bridge, {
|
|
238
|
-
onUnhealthy: () => {
|
|
239
|
-
this.handleError(new Error("Plugin iframe is unresponsive"));
|
|
240
|
-
},
|
|
241
|
-
logger: this.logger,
|
|
242
|
-
});
|
|
243
|
-
this.healthCheck.start();
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
this.config.onReady();
|
|
247
|
-
});
|
|
248
|
-
|
|
249
|
-
this.unsubscribeHandlers.push(unsubscribe);
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
private sendInit(): void {
|
|
253
|
-
if (!this.bridge) {
|
|
254
|
-
return;
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
this.bridge.sendInit(
|
|
258
|
-
this.config.sessionId,
|
|
259
|
-
this.config.controllablePlayerIds,
|
|
260
|
-
this.config.controllingPlayerId,
|
|
261
|
-
this.config.userId,
|
|
262
|
-
);
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
private clearInitRetryInterval(): void {
|
|
266
|
-
if (this.initRetryInterval) {
|
|
267
|
-
clearInterval(this.initRetryInterval);
|
|
268
|
-
this.initRetryInterval = null;
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
private setupInteractionHandler(): void {
|
|
273
|
-
if (!this.bridge) return;
|
|
274
|
-
|
|
275
|
-
const unsubscribe = this.bridge.onPluginMessage(
|
|
276
|
-
"interaction",
|
|
277
|
-
async (message) => {
|
|
278
|
-
// Tier-0 perf: record plugin-supplied `t0_click` and our own
|
|
279
|
-
// `t1_host_received` against the client-minted actionId so the
|
|
280
|
-
// rest of the pipeline (http submit, SSE, store apply) can
|
|
281
|
-
// attach their marks to the same entry.
|
|
282
|
-
if (message.clientActionId) {
|
|
283
|
-
if (typeof message.clientSubmittedAtMs === "number") {
|
|
284
|
-
recordMark(message.clientActionId, PERF_MARK_NAMES.T0_CLICK, {
|
|
285
|
-
timestampMs: message.clientSubmittedAtMs,
|
|
286
|
-
extra: { source: "plugin" },
|
|
287
|
-
});
|
|
288
|
-
}
|
|
289
|
-
recordMark(message.clientActionId, PERF_MARK_NAMES.T1_HOST_RECEIVED, {
|
|
290
|
-
extra: { messageId: message.messageId },
|
|
291
|
-
});
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
try {
|
|
295
|
-
await this.config.onInteraction(
|
|
296
|
-
message.playerId,
|
|
297
|
-
message.interactionId,
|
|
298
|
-
message.params,
|
|
299
|
-
message.clientActionId
|
|
300
|
-
? { clientActionId: message.clientActionId }
|
|
301
|
-
: undefined,
|
|
302
|
-
);
|
|
303
|
-
this.sendSubmitResult(message.messageId, { accepted: true });
|
|
304
|
-
} catch (error) {
|
|
305
|
-
this.logger.error("[Gateway] Interaction submission error:", error);
|
|
306
|
-
this.sendSubmitResult(
|
|
307
|
-
message.messageId,
|
|
308
|
-
this.describeSubmissionFailure(error, "Interaction rejected"),
|
|
309
|
-
);
|
|
310
|
-
}
|
|
311
|
-
},
|
|
312
|
-
);
|
|
313
|
-
|
|
314
|
-
this.unsubscribeHandlers.push(unsubscribe);
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
private setupValidateInteractionHandler(): void {
|
|
318
|
-
if (!this.bridge) return;
|
|
319
|
-
|
|
320
|
-
const unsubscribe = this.bridge.onPluginMessage(
|
|
321
|
-
"validate-interaction",
|
|
322
|
-
async (message) => {
|
|
323
|
-
try {
|
|
324
|
-
const result = await this.config.onValidateInteraction(
|
|
325
|
-
message.playerId,
|
|
326
|
-
message.interactionId,
|
|
327
|
-
message.params,
|
|
328
|
-
);
|
|
329
|
-
|
|
330
|
-
this.bridge?.send({
|
|
331
|
-
type: "validate-interaction-result",
|
|
332
|
-
messageId: message.messageId,
|
|
333
|
-
result,
|
|
334
|
-
});
|
|
335
|
-
} catch (error) {
|
|
336
|
-
this.logger.error("[Gateway] Validate interaction error:", error);
|
|
337
|
-
|
|
338
|
-
this.bridge?.send({
|
|
339
|
-
type: "validate-interaction-result",
|
|
340
|
-
messageId: message.messageId,
|
|
341
|
-
result: {
|
|
342
|
-
valid: false,
|
|
343
|
-
errorCode: "validation-error",
|
|
344
|
-
message:
|
|
345
|
-
error instanceof Error ? error.message : "Validation failed",
|
|
346
|
-
},
|
|
347
|
-
});
|
|
348
|
-
}
|
|
349
|
-
},
|
|
350
|
-
);
|
|
351
|
-
|
|
352
|
-
this.unsubscribeHandlers.push(unsubscribe);
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
private setupErrorHandler(): void {
|
|
356
|
-
if (!this.bridge) return;
|
|
357
|
-
|
|
358
|
-
const unsubscribe = this.bridge.onPluginMessage("error", (message) => {
|
|
359
|
-
if (this.isRecoverablePluginError(message)) {
|
|
360
|
-
this.logger.warn(
|
|
361
|
-
"[Gateway] Recoverable plugin submission timeout ignored.",
|
|
362
|
-
);
|
|
363
|
-
return;
|
|
364
|
-
}
|
|
365
|
-
this.logger.error("[Gateway] Plugin error:", message.message);
|
|
366
|
-
if (message.code) {
|
|
367
|
-
this.logger.error("[Gateway] Error code:", message.code);
|
|
368
|
-
}
|
|
369
|
-
const error = new Error(
|
|
370
|
-
message.code ? `${message.code}: ${message.message}` : message.message,
|
|
371
|
-
);
|
|
372
|
-
error.name = "PluginRuntimeError";
|
|
373
|
-
this.handleError(error);
|
|
374
|
-
});
|
|
375
|
-
|
|
376
|
-
this.unsubscribeHandlers.push(unsubscribe);
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
private setupSwitchPlayerHandler(): void {
|
|
380
|
-
if (!this.bridge) return;
|
|
381
|
-
|
|
382
|
-
const unsubscribe = this.bridge.onPluginMessage(
|
|
383
|
-
"switch-player",
|
|
384
|
-
(message) => {
|
|
385
|
-
this.logger.log("[Gateway] Switch player request:", message.playerId);
|
|
386
|
-
if (this.config.onSwitchPlayer) {
|
|
387
|
-
this.config.onSwitchPlayer(message.playerId);
|
|
388
|
-
}
|
|
389
|
-
},
|
|
390
|
-
);
|
|
391
|
-
|
|
392
|
-
this.unsubscribeHandlers.push(unsubscribe);
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
private isRecoverablePluginError(message: {
|
|
396
|
-
code?: string | null;
|
|
397
|
-
message: string;
|
|
398
|
-
}): boolean {
|
|
399
|
-
return (
|
|
400
|
-
message.code === "UNHANDLED_REJECTION" &&
|
|
401
|
-
message.message.includes("Submission request timed out")
|
|
402
|
-
);
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
private setupRestoreHistoryHandler(): void {
|
|
406
|
-
if (!this.bridge) return;
|
|
407
|
-
|
|
408
|
-
const unsubscribe = this.bridge.onPluginMessage(
|
|
409
|
-
"restore-history",
|
|
410
|
-
(message) => {
|
|
411
|
-
this.logger.log("[Gateway] Restore history request:", message.entryId);
|
|
412
|
-
if (this.config.onRestoreHistory) {
|
|
413
|
-
this.config.onRestoreHistory(message.entryId);
|
|
414
|
-
}
|
|
415
|
-
},
|
|
416
|
-
);
|
|
417
|
-
|
|
418
|
-
this.unsubscribeHandlers.push(unsubscribe);
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
private setupStateAckHandler(): void {
|
|
422
|
-
if (!this.bridge) return;
|
|
423
|
-
|
|
424
|
-
const unsubscribe = this.bridge.onPluginMessage("state-ack", (message) => {
|
|
425
|
-
this.logger.log("[Gateway] Received state-ack, syncId:", message.syncId);
|
|
426
|
-
// Tier-0 perf: mark `t7_state_sync_received` using the plugin's
|
|
427
|
-
// own `Date.now()` captured at state-sync receipt. Uses the
|
|
428
|
-
// syncId -> actionId map populated at t5.
|
|
429
|
-
if (typeof message.clientReceivedAtMs === "number") {
|
|
430
|
-
const actionId = findActionIdBySyncId(message.syncId);
|
|
431
|
-
if (actionId) {
|
|
432
|
-
recordMark(actionId, PERF_MARK_NAMES.T7_STATE_SYNC_RECEIVED, {
|
|
433
|
-
timestampMs: message.clientReceivedAtMs,
|
|
434
|
-
extra: { syncId: message.syncId, source: "plugin" },
|
|
435
|
-
});
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
this.gameSessionStore?.onStateAck(message.syncId);
|
|
439
|
-
});
|
|
440
|
-
|
|
441
|
-
this.unsubscribeHandlers.push(unsubscribe);
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
private setupStateRenderedHandler(): void {
|
|
445
|
-
if (!this.bridge) return;
|
|
446
|
-
|
|
447
|
-
const unsubscribe = this.bridge.onPluginMessage(
|
|
448
|
-
"state-rendered",
|
|
449
|
-
(message) => {
|
|
450
|
-
// Tier-0 perf only: the plugin sends this after its post-sync
|
|
451
|
-
// microtask/rAF completes so the host HUD can show t8 - t0
|
|
452
|
-
// ("total" click-to-render). No functional side effects.
|
|
453
|
-
const actionId = findActionIdBySyncId(message.syncId);
|
|
454
|
-
if (!actionId) return;
|
|
455
|
-
recordMark(actionId, PERF_MARK_NAMES.T7_STATE_SYNC_RECEIVED, {
|
|
456
|
-
timestampMs: message.clientReceivedAtMs,
|
|
457
|
-
extra: { syncId: message.syncId, source: "plugin" },
|
|
458
|
-
});
|
|
459
|
-
recordMark(actionId, PERF_MARK_NAMES.T8_RENDER_COMMIT, {
|
|
460
|
-
timestampMs: message.clientRenderedAtMs,
|
|
461
|
-
extra: { syncId: message.syncId, source: "plugin" },
|
|
462
|
-
});
|
|
463
|
-
},
|
|
464
|
-
);
|
|
465
|
-
|
|
466
|
-
this.unsubscribeHandlers.push(unsubscribe);
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
private setupMarkNotificationReadHandler(): void {
|
|
470
|
-
if (!this.bridge) return;
|
|
471
|
-
|
|
472
|
-
const unsubscribe = this.bridge.onPluginMessage(
|
|
473
|
-
"mark-notification-read",
|
|
474
|
-
(message) => {
|
|
475
|
-
this.logger.log(
|
|
476
|
-
"[Gateway] Mark notification read:",
|
|
477
|
-
message.notificationId,
|
|
478
|
-
);
|
|
479
|
-
this.gameSessionStore?.markNotificationRead(message.notificationId);
|
|
480
|
-
},
|
|
481
|
-
);
|
|
482
|
-
|
|
483
|
-
this.unsubscribeHandlers.push(unsubscribe);
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
private handleError(error: Error): void {
|
|
487
|
-
if (this.state === "error") {
|
|
488
|
-
return;
|
|
489
|
-
}
|
|
490
|
-
if (this.readyTimeout) {
|
|
491
|
-
clearTimeout(this.readyTimeout);
|
|
492
|
-
this.readyTimeout = null;
|
|
493
|
-
}
|
|
494
|
-
if (this.initStartTimeout) {
|
|
495
|
-
clearTimeout(this.initStartTimeout);
|
|
496
|
-
this.initStartTimeout = null;
|
|
497
|
-
}
|
|
498
|
-
this.clearInitRetryInterval();
|
|
499
|
-
if (this.healthCheck) {
|
|
500
|
-
this.healthCheck.stop();
|
|
501
|
-
this.healthCheck = null;
|
|
502
|
-
}
|
|
503
|
-
this.logger.error("[Gateway] Error:", error);
|
|
504
|
-
this.state = "error";
|
|
505
|
-
this.config.onError(error);
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
private sendSubmitResult(
|
|
509
|
-
messageId: string,
|
|
510
|
-
result: {
|
|
511
|
-
accepted: boolean;
|
|
512
|
-
errorCode?: string;
|
|
513
|
-
message?: string;
|
|
514
|
-
},
|
|
515
|
-
): void {
|
|
516
|
-
this.bridge?.sendSubmitResult({
|
|
517
|
-
messageId,
|
|
518
|
-
accepted: result.accepted,
|
|
519
|
-
errorCode: result.errorCode,
|
|
520
|
-
message: result.message,
|
|
521
|
-
});
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
private describeSubmissionFailure(
|
|
525
|
-
error: unknown,
|
|
526
|
-
fallbackMessage: string,
|
|
527
|
-
): {
|
|
528
|
-
accepted: false;
|
|
529
|
-
errorCode?: string;
|
|
530
|
-
message: string;
|
|
531
|
-
} {
|
|
532
|
-
const errorCode =
|
|
533
|
-
typeof error === "object" &&
|
|
534
|
-
error !== null &&
|
|
535
|
-
"errorCode" in error &&
|
|
536
|
-
typeof error.errorCode === "string"
|
|
537
|
-
? error.errorCode
|
|
538
|
-
: undefined;
|
|
539
|
-
const message =
|
|
540
|
-
error instanceof Error
|
|
541
|
-
? error.message
|
|
542
|
-
: typeof error === "string"
|
|
543
|
-
? error
|
|
544
|
-
: fallbackMessage;
|
|
545
|
-
return {
|
|
546
|
-
accepted: false,
|
|
547
|
-
errorCode,
|
|
548
|
-
message,
|
|
549
|
-
};
|
|
550
|
-
}
|
|
551
|
-
}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
export * from "../actor-principal.js";
|
|
2
|
-
export * from "../gameplay-authority-transport.js";
|
|
3
|
-
export * from "../logger.js";
|
|
4
|
-
export * from "../host-session-transport.js";
|
|
5
|
-
export * from "../perf.js";
|
|
6
|
-
export * from "../plugin-bridge.js";
|
|
7
|
-
export * from "../plugin-health-check.js";
|
|
8
|
-
export * from "../plugin-messages.js";
|
|
9
|
-
export * from "../plugin-session-gateway.js";
|
|
10
|
-
export * from "../screenshot/projection-to-snapshot.js";
|
|
11
|
-
export * from "../screenshot/static-store-api.js";
|
|
12
|
-
export * from "../sse-manager.js";
|
|
13
|
-
export * from "../unified-session-store.js";
|
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
import type { SeatAssignment } from "@dreamboard-games/api-client";
|
|
2
|
-
import type { PluginStateSnapshot } from "@dreamboard-games/sdk/runtime/runtime-api";
|
|
3
|
-
import { seatsForPluginSnapshot } from "../actor-principal.js";
|
|
4
|
-
|
|
5
|
-
const DEFAULT_PLAYER_IDS = [
|
|
6
|
-
"player-1",
|
|
7
|
-
"player-2",
|
|
8
|
-
"player-3",
|
|
9
|
-
"player-4",
|
|
10
|
-
] as const;
|
|
11
|
-
|
|
12
|
-
const PLAYER_COLORS = [
|
|
13
|
-
"#f97316",
|
|
14
|
-
"#0ea5e9",
|
|
15
|
-
"#22c55e",
|
|
16
|
-
"#a855f7",
|
|
17
|
-
"#ef4444",
|
|
18
|
-
"#eab308",
|
|
19
|
-
] as const;
|
|
20
|
-
|
|
21
|
-
export interface ScreenshotProjection {
|
|
22
|
-
currentPhase?: string | null;
|
|
23
|
-
currentStage?: string | null;
|
|
24
|
-
stageSeats?: string[];
|
|
25
|
-
simultaneousPhase?: PluginStateSnapshot["gameplay"]["simultaneousPhase"];
|
|
26
|
-
view?: unknown;
|
|
27
|
-
availableInteractions?: PluginStateSnapshot["gameplay"]["availableInteractions"];
|
|
28
|
-
zones?: PluginStateSnapshot["gameplay"]["zones"];
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export interface ProjectionToSnapshotOptions {
|
|
32
|
-
sessionId?: string | null;
|
|
33
|
-
userId?: string | null;
|
|
34
|
-
controllingPlayerId?: string;
|
|
35
|
-
playerIds?: string[];
|
|
36
|
-
syncId?: number;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export function projectionToSnapshot(
|
|
40
|
-
projection: ScreenshotProjection,
|
|
41
|
-
options: ProjectionToSnapshotOptions = {},
|
|
42
|
-
): PluginStateSnapshot {
|
|
43
|
-
const controllingPlayerId =
|
|
44
|
-
options.controllingPlayerId ??
|
|
45
|
-
projection.stageSeats?.[0] ??
|
|
46
|
-
DEFAULT_PLAYER_IDS[0];
|
|
47
|
-
const playerIds = normalizePlayerIds(options.playerIds, controllingPlayerId);
|
|
48
|
-
const userId = options.userId ?? "screenshot-user";
|
|
49
|
-
const seats = buildSeatAssignments(playerIds, userId);
|
|
50
|
-
|
|
51
|
-
return {
|
|
52
|
-
view: (projection.view ?? null) as PluginStateSnapshot["view"],
|
|
53
|
-
gameplay: {
|
|
54
|
-
currentPhase:
|
|
55
|
-
projection.currentPhase ??
|
|
56
|
-
viewCurrentPhase(projection.view) ??
|
|
57
|
-
projection.currentStage ??
|
|
58
|
-
null,
|
|
59
|
-
currentStage: projection.currentStage ?? null,
|
|
60
|
-
activePlayers:
|
|
61
|
-
projection.stageSeats && projection.stageSeats.length > 0
|
|
62
|
-
? projection.stageSeats
|
|
63
|
-
: [controllingPlayerId],
|
|
64
|
-
simultaneousPhase: projection.simultaneousPhase ?? null,
|
|
65
|
-
availableInteractions: projection.availableInteractions ?? [],
|
|
66
|
-
zones: projection.zones ?? {},
|
|
67
|
-
},
|
|
68
|
-
lobby: {
|
|
69
|
-
seats: seatsForPluginSnapshot(seats),
|
|
70
|
-
canStart: true,
|
|
71
|
-
hostUserId: userId,
|
|
72
|
-
},
|
|
73
|
-
notifications: [],
|
|
74
|
-
session: {
|
|
75
|
-
sessionId: options.sessionId ?? null,
|
|
76
|
-
controllablePlayerIds: [controllingPlayerId],
|
|
77
|
-
controllingPlayerId,
|
|
78
|
-
userId,
|
|
79
|
-
},
|
|
80
|
-
history: null,
|
|
81
|
-
syncId: options.syncId ?? 1,
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
function viewCurrentPhase(view: unknown): string | null {
|
|
86
|
-
if (!view || typeof view !== "object" || !("currentPhase" in view)) {
|
|
87
|
-
return null;
|
|
88
|
-
}
|
|
89
|
-
const value = (view as { currentPhase?: unknown }).currentPhase;
|
|
90
|
-
return typeof value === "string" && value.length > 0 ? value : null;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
function normalizePlayerIds(
|
|
94
|
-
playerIds: string[] | undefined,
|
|
95
|
-
controllingPlayerId: string,
|
|
96
|
-
): string[] {
|
|
97
|
-
const normalized = (playerIds?.length ? playerIds : [...DEFAULT_PLAYER_IDS])
|
|
98
|
-
.map((playerId) => playerId.trim())
|
|
99
|
-
.filter(Boolean);
|
|
100
|
-
return Array.from(new Set([controllingPlayerId, ...normalized]));
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
function buildSeatAssignments(
|
|
104
|
-
playerIds: string[],
|
|
105
|
-
userId: string,
|
|
106
|
-
): SeatAssignment[] {
|
|
107
|
-
return playerIds.map((playerId, index) => ({
|
|
108
|
-
playerId,
|
|
109
|
-
controllerActor: {
|
|
110
|
-
kind: "DEMO_GUEST",
|
|
111
|
-
demoActorSessionId: userId,
|
|
112
|
-
},
|
|
113
|
-
displayName: displayNameForPlayer(playerId),
|
|
114
|
-
playerColor: PLAYER_COLORS[index % PLAYER_COLORS.length],
|
|
115
|
-
isHost: index === 0,
|
|
116
|
-
}));
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
function displayNameForPlayer(playerId: string): string {
|
|
120
|
-
const match = /^player-(\d+)$/.exec(playerId);
|
|
121
|
-
return match ? `Player ${match[1]}` : playerId;
|
|
122
|
-
}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import type { PluginStateSnapshot } from "@dreamboard-games/sdk/runtime/runtime-api";
|
|
2
|
-
import type { GameSessionStoreApi } from "../plugin-session-gateway.js";
|
|
3
|
-
|
|
4
|
-
export function createStaticStoreApi(
|
|
5
|
-
snapshot: PluginStateSnapshot,
|
|
6
|
-
): GameSessionStoreApi {
|
|
7
|
-
const frozenSnapshot = deepFreeze(snapshot);
|
|
8
|
-
return {
|
|
9
|
-
getStateSnapshot: () => frozenSnapshot,
|
|
10
|
-
subscribe: () => () => undefined,
|
|
11
|
-
onStateAck: () => undefined,
|
|
12
|
-
markNotificationRead: () => undefined,
|
|
13
|
-
};
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function deepFreeze<T>(value: T): T {
|
|
17
|
-
if (!value || typeof value !== "object" || Object.isFrozen(value)) {
|
|
18
|
-
return value;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
for (const nested of Object.values(value as Record<string, unknown>)) {
|
|
22
|
-
deepFreeze(nested);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
return Object.freeze(value);
|
|
26
|
-
}
|