@dreamboard-games/cli 0.1.30-alpha.0 → 0.1.30-alpha.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 (156) hide show
  1. package/README.md +179 -22
  2. package/dist/agent-verifier/agent-workspace-verifier.mjs +31 -30
  3. package/dist/agent-verifier/agent-workspace-verifier.mjs.map +1 -0
  4. package/dist/agent-verifier/{chunk-2NZNKIND.mjs → chunk-3IJBOLGT.mjs} +4 -12
  5. package/dist/agent-verifier/chunk-3IJBOLGT.mjs.map +1 -0
  6. package/dist/agent-verifier/{chunk-6A5HRJMQ.mjs → chunk-4GU3PCHV.mjs} +62 -99
  7. package/dist/agent-verifier/chunk-4GU3PCHV.mjs.map +1 -0
  8. package/dist/agent-verifier/{chunk-SYPLYRGB.mjs → chunk-6XRC5PWB.mjs} +119 -310
  9. package/dist/agent-verifier/chunk-6XRC5PWB.mjs.map +1 -0
  10. package/dist/agent-verifier/{chunk-BVVNBJM4.mjs → chunk-COB56ESI.mjs} +2 -1
  11. package/dist/agent-verifier/chunk-COB56ESI.mjs.map +1 -0
  12. package/dist/agent-verifier/{chunk-2GBBP27W.mjs → chunk-F2DIOJJZ.mjs} +1 -0
  13. package/dist/agent-verifier/chunk-F2DIOJJZ.mjs.map +1 -0
  14. package/dist/agent-verifier/{chunk-CFU5EWIC.mjs → chunk-G42BGGG2.mjs} +7 -6
  15. package/dist/agent-verifier/chunk-G42BGGG2.mjs.map +1 -0
  16. package/dist/agent-verifier/{chunk-XYDL7GY6.mjs → chunk-H6XDQJ3N.mjs} +1 -0
  17. package/dist/agent-verifier/{chunk-LM3OZLZG.mjs → chunk-IAYRNVUC.mjs} +1 -0
  18. package/dist/agent-verifier/chunk-IAYRNVUC.mjs.map +1 -0
  19. package/dist/agent-verifier/{chunk-2QMNAVV4.mjs → chunk-JZTH3EMV.mjs} +2 -1
  20. package/dist/agent-verifier/chunk-JZTH3EMV.mjs.map +1 -0
  21. package/dist/agent-verifier/chunk-KK47X7RV.mjs +14 -0
  22. package/dist/agent-verifier/chunk-KK47X7RV.mjs.map +1 -0
  23. package/dist/agent-verifier/{chunk-SHUMAVAP.mjs → chunk-M7UVBANQ.mjs} +8 -9
  24. package/dist/agent-verifier/chunk-M7UVBANQ.mjs.map +1 -0
  25. package/dist/agent-verifier/{chunk-RJBLBYHX.mjs → chunk-MGXX4WFR.mjs} +87 -22
  26. package/dist/agent-verifier/chunk-MGXX4WFR.mjs.map +1 -0
  27. package/dist/agent-verifier/{chunk-2E5P5NWG.mjs → chunk-NAK77WXW.mjs} +58 -126
  28. package/dist/agent-verifier/chunk-NAK77WXW.mjs.map +1 -0
  29. package/dist/agent-verifier/{chunk-CEQ2VJWN.mjs → chunk-POBFNXD4.mjs} +2 -1
  30. package/dist/agent-verifier/chunk-POBFNXD4.mjs.map +1 -0
  31. package/dist/agent-verifier/{chunk-6UUJEYDV.mjs → chunk-QBAF7EYR.mjs} +1 -0
  32. package/dist/agent-verifier/chunk-QBAF7EYR.mjs.map +1 -0
  33. package/dist/agent-verifier/{chunk-7653FPGJ.mjs → chunk-RHI6S4SU.mjs} +3 -2
  34. package/dist/agent-verifier/chunk-RHI6S4SU.mjs.map +1 -0
  35. package/dist/agent-verifier/{chunk-MINCYHXN.mjs → chunk-TAEQKBJB.mjs} +1 -0
  36. package/dist/agent-verifier/chunk-TAEQKBJB.mjs.map +1 -0
  37. package/dist/agent-verifier/{chunk-PM3SVG6R.mjs → chunk-TLYGTHXU.mjs} +3 -2
  38. package/dist/agent-verifier/chunk-TLYGTHXU.mjs.map +1 -0
  39. package/dist/agent-verifier/{chunk-EIQWDQWJ.mjs → chunk-UWJIZML3.mjs} +13 -14
  40. package/dist/agent-verifier/chunk-UWJIZML3.mjs.map +1 -0
  41. package/dist/agent-verifier/{chunk-DTMJCPS4.mjs → chunk-VLOIZDR6.mjs} +15 -31
  42. package/dist/agent-verifier/chunk-VLOIZDR6.mjs.map +1 -0
  43. package/dist/agent-verifier/{chunk-HJFQDSTU.mjs → chunk-W2MDP5ZN.mjs} +6 -5
  44. package/dist/agent-verifier/chunk-W2MDP5ZN.mjs.map +1 -0
  45. package/dist/agent-verifier/{chunk-CEDUHGNH.mjs → chunk-XKCJBIRY.mjs} +2 -1
  46. package/dist/agent-verifier/chunk-XKCJBIRY.mjs.map +1 -0
  47. package/dist/agent-verifier/{chunk-VYJTHSYR.mjs → chunk-YDIOW2BO.mjs} +2 -1
  48. package/dist/agent-verifier/chunk-YDIOW2BO.mjs.map +1 -0
  49. package/dist/agent-verifier/{chunk-MRCUP5SW.mjs → chunk-YE7UAO3T.mjs} +1 -0
  50. package/dist/agent-verifier/chunk-YE7UAO3T.mjs.map +1 -0
  51. package/dist/agent-verifier/{chunk-EOQIV6PS.mjs → chunk-YR664DJX.mjs} +111 -116
  52. package/dist/agent-verifier/chunk-YR664DJX.mjs.map +1 -0
  53. package/dist/agent-verifier/{chunk-2SZHMP6F.mjs → chunk-Z6OZWUIZ.mjs} +6 -9
  54. package/dist/agent-verifier/chunk-Z6OZWUIZ.mjs.map +1 -0
  55. package/dist/agent-verifier/{chunk-RBDDIIPM.mjs → chunk-ZEELHSY3.mjs} +1 -0
  56. package/dist/agent-verifier/chunk-ZEELHSY3.mjs.map +1 -0
  57. package/dist/agent-verifier/{compile-WNCQQVOF.mjs → compile-C2VIP6VC.mjs} +27 -27
  58. package/dist/agent-verifier/compile-C2VIP6VC.mjs.map +1 -0
  59. package/dist/agent-verifier/{global-config-WX3ZZIVU.mjs → global-config-XHL7BCKN.mjs} +6 -5
  60. package/dist/agent-verifier/global-config-XHL7BCKN.mjs.map +1 -0
  61. package/dist/agent-verifier/{keychain-backend-TNOPQV3Z.mjs → keychain-backend-A3MRWLPF.mjs} +2 -1
  62. package/dist/agent-verifier/keychain-backend-A3MRWLPF.mjs.map +1 -0
  63. package/dist/agent-verifier/{local-files-MTPLP62S.mjs → local-files-ZW52HSVT.mjs} +10 -11
  64. package/dist/agent-verifier/local-files-ZW52HSVT.mjs.map +1 -0
  65. package/dist/agent-verifier/local-typecheck-3JXL2NMG.mjs +10 -0
  66. package/dist/agent-verifier/local-typecheck-3JXL2NMG.mjs.map +1 -0
  67. package/dist/agent-verifier/{materialize-workspace-EWGZIVOY.mjs → materialize-workspace-BKZLLFI4.mjs} +20 -20
  68. package/dist/agent-verifier/materialize-workspace-BKZLLFI4.mjs.map +1 -0
  69. package/dist/agent-verifier/{project-state-7GR6BQTQ.mjs → project-state-XKUSCFSV.mjs} +3 -2
  70. package/dist/agent-verifier/project-state-XKUSCFSV.mjs.map +1 -0
  71. package/dist/agent-verifier/{prompt-3BAINGAQ.mjs → prompt-VKHMCQT6.mjs} +2 -1
  72. package/dist/agent-verifier/prompt-VKHMCQT6.mjs.map +1 -0
  73. package/dist/agent-verifier/{reducer-bundle-preflight-C73LEXI2.mjs → reducer-bundle-preflight-7NYZF5ZT.mjs} +6 -9
  74. package/dist/agent-verifier/reducer-bundle-preflight-7NYZF5ZT.mjs.map +1 -0
  75. package/dist/agent-verifier/reducer-contract-preflight-COD2CO22.mjs +11 -0
  76. package/dist/agent-verifier/reducer-contract-preflight-COD2CO22.mjs.map +1 -0
  77. package/dist/agent-verifier/{reducer-native-test-harness-GMWBUISX.mjs → reducer-native-test-harness-D4VWPIAC.mjs} +14 -17
  78. package/dist/agent-verifier/reducer-native-test-harness-D4VWPIAC.mjs.map +1 -0
  79. package/dist/agent-verifier/static-scaffold-JCRBDKEH.mjs +26 -0
  80. package/dist/agent-verifier/static-scaffold-JCRBDKEH.mjs.map +1 -0
  81. package/dist/agent-verifier/{sync-LOQAH4RC.mjs → sync-UTL2IIZV.mjs} +35 -39
  82. package/dist/agent-verifier/sync-UTL2IIZV.mjs.map +1 -0
  83. package/dist/agent-verifier/{test-YOJERVHN.mjs → test-H26XCBFA.mjs} +29 -31
  84. package/dist/agent-verifier/test-H26XCBFA.mjs.map +1 -0
  85. package/dist/agent-verifier/workspace-codegen-WPZHMATU.mjs +10 -0
  86. package/dist/agent-verifier/workspace-codegen-WPZHMATU.mjs.map +1 -0
  87. package/dist/agent-verifier/{workspace-dependencies-HZ6VVS4G.mjs → workspace-dependencies-ULZZZPNX.mjs} +5 -4
  88. package/dist/agent-verifier/workspace-dependencies-ULZZZPNX.mjs.map +1 -0
  89. package/dist/{chunk-TSJVWTJO.js → chunk-GXM7RRZJ.js} +14 -11
  90. package/dist/chunk-GXM7RRZJ.js.map +1 -0
  91. package/dist/{chunk-3XNJT3RK.js → chunk-P5TITCD3.js} +808 -17878
  92. package/dist/chunk-P5TITCD3.js.map +1 -0
  93. package/dist/{global-config-UKSWNDTX.js → global-config-WPJRXVDO.js} +2 -2
  94. package/dist/global-config-WPJRXVDO.js.map +1 -0
  95. package/dist/index.js +987 -255
  96. package/dist/index.js.map +1 -1
  97. package/dist/internal.js +2 -3
  98. package/package.json +8 -7
  99. package/skills/dreamboard/references/building-your-first-game.md +510 -0
  100. package/skills/dreamboard/references/cli.md +104 -0
  101. package/skills/dreamboard/references/game-interface.md +548 -0
  102. package/skills/dreamboard/references/manifest-authoring.md +597 -0
  103. package/skills/dreamboard/references/quickstart.md +66 -0
  104. package/skills/dreamboard/references/reducer.md +864 -0
  105. package/skills/dreamboard/references/rule-authoring.md +147 -0
  106. package/skills/dreamboard/references/testing.md +249 -0
  107. package/skills/dreamboard/scripts/events-extract.mjs +218 -0
  108. package/dist/agent-verifier/chunk-54TAYXUD.mjs +0 -12
  109. package/dist/agent-verifier/chunk-HBNDKQT5.mjs +0 -8381
  110. package/dist/agent-verifier/chunk-LI3ZR3BI.mjs +0 -41
  111. package/dist/agent-verifier/chunk-U6OJN7XS.mjs +0 -8092
  112. package/dist/agent-verifier/local-typecheck-QFYYAZOK.mjs +0 -9
  113. package/dist/agent-verifier/reducer-contract-preflight-22X7DSZW.mjs +0 -10
  114. package/dist/agent-verifier/static-scaffold-4YEQME5N.mjs +0 -28
  115. package/dist/agent-verifier/testing-5K2BJYF2.mjs +0 -674
  116. package/dist/agent-verifier/workspace-codegen-JDZJRSDV.mjs +0 -11
  117. package/dist/chunk-3XNJT3RK.js.map +0 -1
  118. package/dist/chunk-7FOO4AJI.js +0 -50
  119. package/dist/chunk-7FOO4AJI.js.map +0 -1
  120. package/dist/chunk-TSJVWTJO.js.map +0 -1
  121. package/dist/internal.d.ts +0 -311
  122. package/dist/runtime-packages/ui-host-runtime/src/actor-principal.ts +0 -71
  123. package/dist/runtime-packages/ui-host-runtime/src/browser-interaction.ts +0 -139
  124. package/dist/runtime-packages/ui-host-runtime/src/components/host-controls.tsx +0 -374
  125. package/dist/runtime-packages/ui-host-runtime/src/components/host-feedback-toaster.tsx +0 -266
  126. package/dist/runtime-packages/ui-host-runtime/src/components/host-feedback.tsx +0 -212
  127. package/dist/runtime-packages/ui-host-runtime/src/components/host-primitives.tsx +0 -271
  128. package/dist/runtime-packages/ui-host-runtime/src/components/host-session-metadata.tsx +0 -135
  129. package/dist/runtime-packages/ui-host-runtime/src/components/index.ts +0 -5
  130. package/dist/runtime-packages/ui-host-runtime/src/components/perf-overlay.tsx +0 -194
  131. package/dist/runtime-packages/ui-host-runtime/src/gameplay-authority-transport.ts +0 -626
  132. package/dist/runtime-packages/ui-host-runtime/src/host-controls.tsx +0 -1
  133. package/dist/runtime-packages/ui-host-runtime/src/host-feedback.tsx +0 -1
  134. package/dist/runtime-packages/ui-host-runtime/src/host-session-transport.ts +0 -294
  135. package/dist/runtime-packages/ui-host-runtime/src/index.ts +0 -3
  136. package/dist/runtime-packages/ui-host-runtime/src/logger.ts +0 -11
  137. package/dist/runtime-packages/ui-host-runtime/src/perf.ts +0 -324
  138. package/dist/runtime-packages/ui-host-runtime/src/plugin-bridge.ts +0 -195
  139. package/dist/runtime-packages/ui-host-runtime/src/plugin-health-check.ts +0 -138
  140. package/dist/runtime-packages/ui-host-runtime/src/plugin-messages.ts +0 -159
  141. package/dist/runtime-packages/ui-host-runtime/src/plugin-session-gateway.ts +0 -551
  142. package/dist/runtime-packages/ui-host-runtime/src/runtime/index.ts +0 -13
  143. package/dist/runtime-packages/ui-host-runtime/src/screenshot/projection-to-snapshot.ts +0 -122
  144. package/dist/runtime-packages/ui-host-runtime/src/screenshot/static-store-api.ts +0 -26
  145. package/dist/runtime-packages/ui-host-runtime/src/session-ingress-controller.ts +0 -583
  146. package/dist/runtime-packages/ui-host-runtime/src/session-ingress.ts +0 -219
  147. package/dist/runtime-packages/ui-host-runtime/src/session-live-runtime.ts +0 -117
  148. package/dist/runtime-packages/ui-host-runtime/src/session-model.ts +0 -431
  149. package/dist/runtime-packages/ui-host-runtime/src/session-projection.ts +0 -211
  150. package/dist/runtime-packages/ui-host-runtime/src/session-recovery.ts +0 -80
  151. package/dist/runtime-packages/ui-host-runtime/src/session-state-reducer.ts +0 -1034
  152. package/dist/runtime-packages/ui-host-runtime/src/sse-manager.ts +0 -416
  153. package/dist/runtime-packages/ui-host-runtime/src/unified-session-store.ts +0 -184
  154. package/dist/testing-KLSV6CPJ.js +0 -674
  155. package/dist/testing-KLSV6CPJ.js.map +0 -1
  156. /package/dist/{global-config-UKSWNDTX.js.map → agent-verifier/chunk-H6XDQJ3N.mjs.map} +0 -0
@@ -1,416 +0,0 @@
1
- import type { HostSessionTransport } from "./host-session-transport.js";
2
- import { defaultHostSessionTransport } from "./host-session-transport.js";
3
- import type { LoggerLike } from "./logger.js";
4
- import { consoleLogger } from "./logger.js";
5
- import { PERF_MARK_NAMES, findActionIdByVersion, recordMark } from "./perf.js";
6
- import type { HostSessionWireEvent } from "./session-ingress.js";
7
- import {
8
- getSessionRecoveryDetails,
9
- type SessionRecoveryDetails,
10
- } from "./session-recovery.js";
11
-
12
- type HostSessionEventType = HostSessionWireEvent["type"];
13
-
14
- type ExtractHostSessionEvent<T extends HostSessionEventType> = Extract<
15
- HostSessionWireEvent,
16
- { type: T }
17
- >;
18
-
19
- type HostSessionEventHandler<
20
- T extends HostSessionEventType = HostSessionEventType,
21
- > = (data: ExtractHostSessionEvent<T>) => void;
22
- type ConnectionEventHandler = () => void;
23
- type ErrorEventHandler = (error: unknown) => void;
24
- type RecoveryEventHandler = (details: LongPollRecoveryEvent) => void;
25
- type AnyHostSessionEventHandler = (data: HostSessionWireEvent) => void;
26
- type EventHandler =
27
- | AnyHostSessionEventHandler
28
- | ConnectionEventHandler
29
- | ErrorEventHandler
30
- | RecoveryEventHandler;
31
-
32
- export interface LongPollRecoveryEvent extends SessionRecoveryDetails {
33
- attempt: number;
34
- }
35
-
36
- export type SSERecoveryEvent = LongPollRecoveryEvent;
37
-
38
- export interface LongPollSessionManagerOptions {
39
- clientId?: string;
40
- logger?: LoggerLike;
41
- recoveryRetryMaxAttempts?: number;
42
- recoveryRetrySleep?: (ms: number) => Promise<void>;
43
- transport?: Pick<
44
- HostSessionTransport,
45
- "subscribeToSessionEvents" | "disconnectSessionEvents"
46
- >;
47
- }
48
-
49
- export type SSEManagerOptions = LongPollSessionManagerOptions;
50
-
51
- export interface SSEConnectOptions {
52
- source?: string;
53
- playerId?: string;
54
- }
55
-
56
- interface ActiveConnection {
57
- connectionAttemptId: string;
58
- sessionId: string;
59
- playerId?: string;
60
- source?: string;
61
- }
62
-
63
- const TAB_ID_SESSION_STORAGE_KEY = "dreamboard.sse.tab-id";
64
-
65
- function randomId(): string {
66
- if (
67
- typeof crypto !== "undefined" &&
68
- typeof crypto.randomUUID === "function"
69
- ) {
70
- return crypto.randomUUID();
71
- }
72
- return `sse-${Date.now()}-${Math.random().toString(36).slice(2)}`;
73
- }
74
-
75
- function getOrCreateTabId(): string {
76
- if (typeof window === "undefined") {
77
- return randomId();
78
- }
79
-
80
- try {
81
- const existing = window.sessionStorage.getItem(TAB_ID_SESSION_STORAGE_KEY);
82
- if (existing) {
83
- return existing;
84
- }
85
-
86
- const created = randomId();
87
- window.sessionStorage.setItem(TAB_ID_SESSION_STORAGE_KEY, created);
88
- return created;
89
- } catch {
90
- return randomId();
91
- }
92
- }
93
-
94
- function isAbortLikeError(error: unknown): boolean {
95
- return error instanceof Error && error.name === "AbortError";
96
- }
97
-
98
- export class LongPollSessionManager {
99
- private abortController: AbortController | null = null;
100
- private activeConnection: ActiveConnection | null = null;
101
- private readonly clientId: string;
102
- private readonly transport: Pick<
103
- HostSessionTransport,
104
- "subscribeToSessionEvents" | "disconnectSessionEvents"
105
- >;
106
- private handlers: Map<string, Set<EventHandler>> = new Map();
107
- private anyMessageHandlers: Set<AnyHostSessionEventHandler> = new Set();
108
- private isConnected = false;
109
- private messageBuffer: HostSessionWireEvent[] = [];
110
- private logger: LoggerLike;
111
- private pageHideHandler: (() => void) | null = null;
112
- private readonly recoveryRetryMaxAttempts: number;
113
- private readonly recoveryRetrySleep: (ms: number) => Promise<void>;
114
-
115
- constructor(options: LongPollSessionManagerOptions = {}) {
116
- this.clientId = options.clientId ?? getOrCreateTabId();
117
- this.logger = options.logger ?? consoleLogger;
118
- this.transport = options.transport ?? defaultHostSessionTransport;
119
- this.recoveryRetryMaxAttempts = options.recoveryRetryMaxAttempts ?? 60;
120
- this.recoveryRetrySleep =
121
- options.recoveryRetrySleep ??
122
- ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
123
- }
124
-
125
- async connect(sessionId: string, options: SSEConnectOptions) {
126
- if (this.abortController) {
127
- this.disconnect();
128
- }
129
-
130
- this.abortController = new AbortController();
131
- const connection: ActiveConnection = {
132
- sessionId,
133
- playerId: options.playerId,
134
- connectionAttemptId: randomId(),
135
- source: options.source,
136
- };
137
- this.activeConnection = connection;
138
- this.registerPageHideHandler();
139
-
140
- try {
141
- let recoveryAttempt = 0;
142
-
143
- while (!this.abortController.signal.aborted) {
144
- let connected = false;
145
- let streamError: unknown = null;
146
- let stream: AsyncIterable<HostSessionWireEvent | null | undefined>;
147
-
148
- try {
149
- stream = (
150
- await this.transport.subscribeToSessionEvents({
151
- sessionId,
152
- clientId: this.clientId,
153
- connectionAttemptId: connection.connectionAttemptId,
154
- clientSource: options.source,
155
- playerId: connection.playerId,
156
- signal: this.abortController.signal,
157
- onSseError: (error) => {
158
- if (
159
- this.abortController?.signal.aborted ||
160
- isAbortLikeError(error)
161
- ) {
162
- return;
163
- }
164
- streamError = error;
165
- },
166
- })
167
- ).stream;
168
- } catch (error) {
169
- const recovered = await this.retryRecovery(error, ++recoveryAttempt);
170
- if (recovered) {
171
- continue;
172
- }
173
- throw error;
174
- }
175
-
176
- try {
177
- for await (const event of stream) {
178
- if (!event) continue;
179
- if (!connected) {
180
- connected = true;
181
- recoveryAttempt = 0;
182
- this.isConnected = true;
183
- this.emit("connected", null);
184
- }
185
-
186
- const eventType = event.type;
187
-
188
- if (eventType) {
189
- // Tier-0 perf: record `t4_sse_received` the moment the SSE
190
- // stream yields a gameplay.updated for a version we submitted.
191
- // Stitching is done via the version -> actionId map populated
192
- // at t3 in runtime-api.
193
- if (eventType === "session.gameplayUpdated") {
194
- const version = event.gameplay?.version;
195
- if (typeof version === "number") {
196
- const actionId = findActionIdByVersion(version);
197
- if (actionId) {
198
- recordMark(actionId, PERF_MARK_NAMES.T4_SSE_RECEIVED, {
199
- extra: { version },
200
- });
201
- }
202
- }
203
- }
204
- this.emit(eventType, event);
205
- } else {
206
- this.logger.error(
207
- "[LongPollSessionManager] Received event without type:",
208
- event,
209
- );
210
- }
211
- }
212
- } catch (error) {
213
- streamError = streamError ?? error;
214
- }
215
-
216
- if (streamError) {
217
- const recovered = await this.retryRecovery(
218
- streamError,
219
- ++recoveryAttempt,
220
- );
221
- if (recovered) {
222
- continue;
223
- }
224
- this.logger.error(
225
- "[LongPollSessionManager] stream error:",
226
- streamError,
227
- );
228
- this.emit("error", streamError);
229
- this.isConnected = false;
230
- this.emit("disconnected", null);
231
- return;
232
- }
233
-
234
- this.isConnected = false;
235
- this.emit("disconnected", null);
236
- return;
237
- }
238
-
239
- this.isConnected = false;
240
- this.emit("disconnected", null);
241
- } catch (error) {
242
- if (isAbortLikeError(error)) {
243
- return;
244
- }
245
-
246
- this.logger.error("[LongPollSessionManager] stream error:", error);
247
- this.emit("error", error);
248
- this.isConnected = false;
249
- this.emit("disconnected", null);
250
- } finally {
251
- if (
252
- this.activeConnection?.connectionAttemptId ===
253
- connection.connectionAttemptId
254
- ) {
255
- this.activeConnection = null;
256
- this.unregisterPageHideHandler();
257
- }
258
- }
259
- }
260
-
261
- on<T extends HostSessionEventType>(
262
- eventType: T,
263
- handler: HostSessionEventHandler<T>,
264
- ): () => void;
265
- on(
266
- eventType: "connected" | "disconnected",
267
- handler: ConnectionEventHandler,
268
- ): () => void;
269
- on(eventType: "error", handler: ErrorEventHandler): () => void;
270
- on(eventType: "recovering", handler: RecoveryEventHandler): () => void;
271
- on(eventType: string, handler: EventHandler): () => void {
272
- if (!this.handlers.has(eventType)) {
273
- this.handlers.set(eventType, new Set());
274
- }
275
- const handlers = this.handlers.get(eventType);
276
- if (handlers) {
277
- handlers.add(handler);
278
- }
279
-
280
- return () => {
281
- this.handlers.get(eventType)?.delete(handler);
282
- };
283
- }
284
-
285
- onAnyMessage(handler: AnyHostSessionEventHandler): () => void {
286
- this.anyMessageHandlers.add(handler);
287
-
288
- if (this.messageBuffer.length > 0) {
289
- this.messageBuffer.forEach((message) => {
290
- handler(message);
291
- });
292
- }
293
-
294
- return () => {
295
- this.anyMessageHandlers.delete(handler);
296
- };
297
- }
298
-
299
- private emit(eventType: string, data: HostSessionWireEvent): void;
300
- private emit(eventType: "connected" | "disconnected", data: null): void;
301
- private emit(eventType: "error", error: unknown): void;
302
- private emit(eventType: "recovering", data: LongPollRecoveryEvent): void;
303
- private emit(
304
- eventType: string,
305
- data: HostSessionWireEvent | null | unknown,
306
- ): void {
307
- const handlers = this.handlers.get(eventType);
308
- if (handlers) {
309
- handlers.forEach((handler) => {
310
- if (eventType === "connected" || eventType === "disconnected") {
311
- (handler as ConnectionEventHandler)();
312
- } else if (eventType === "error") {
313
- (handler as ErrorEventHandler)(data);
314
- } else if (eventType === "recovering") {
315
- (handler as RecoveryEventHandler)(data as LongPollRecoveryEvent);
316
- } else {
317
- (handler as AnyHostSessionEventHandler)(data as HostSessionWireEvent);
318
- }
319
- });
320
- }
321
-
322
- if (
323
- eventType !== "connected" &&
324
- eventType !== "disconnected" &&
325
- eventType !== "error" &&
326
- eventType !== "recovering"
327
- ) {
328
- this.messageBuffer.push(data as HostSessionWireEvent);
329
-
330
- this.anyMessageHandlers.forEach((handler) => {
331
- handler(data as HostSessionWireEvent);
332
- });
333
- }
334
- }
335
-
336
- disconnect() {
337
- const activeConnection = this.activeConnection;
338
- if (activeConnection) {
339
- this.notifyDisconnect(activeConnection, true);
340
- }
341
-
342
- if (this.abortController) {
343
- this.abortController.abort();
344
- this.abortController = null;
345
- }
346
- this.activeConnection = null;
347
- this.unregisterPageHideHandler();
348
- this.isConnected = false;
349
- this.handlers.clear();
350
- this.anyMessageHandlers.clear();
351
- this.messageBuffer = [];
352
- }
353
-
354
- isStreamConnected(): boolean {
355
- return this.isConnected;
356
- }
357
-
358
- private async retryRecovery(
359
- error: unknown,
360
- attempt: number,
361
- ): Promise<boolean> {
362
- const recovery = getSessionRecoveryDetails(error);
363
- if (!recovery || attempt > this.recoveryRetryMaxAttempts) {
364
- return false;
365
- }
366
- this.isConnected = false;
367
- this.emit("disconnected", null);
368
- this.emit("recovering", { ...recovery, attempt });
369
- await this.recoveryRetrySleep(recovery.retryAfterMs);
370
- return this.abortController?.signal.aborted !== true;
371
- }
372
-
373
- private notifyDisconnect(
374
- connection: ActiveConnection,
375
- _keepalive: boolean,
376
- ): void {
377
- const request = this.transport.disconnectSessionEvents({
378
- sessionId: connection.sessionId,
379
- clientId: this.clientId,
380
- connectionAttemptId: connection.connectionAttemptId,
381
- playerId: connection.playerId,
382
- });
383
-
384
- void request.catch((error) => {
385
- this.logger.error(
386
- "[LongPollSessionManager] Failed to notify disconnect:",
387
- error,
388
- );
389
- });
390
- }
391
-
392
- private registerPageHideHandler(): void {
393
- if (typeof window === "undefined" || this.pageHideHandler) {
394
- return;
395
- }
396
-
397
- this.pageHideHandler = () => {
398
- if (this.activeConnection) {
399
- this.notifyDisconnect(this.activeConnection, true);
400
- }
401
- };
402
-
403
- window.addEventListener("pagehide", this.pageHideHandler);
404
- }
405
-
406
- private unregisterPageHideHandler(): void {
407
- if (typeof window === "undefined" || !this.pageHideHandler) {
408
- return;
409
- }
410
-
411
- window.removeEventListener("pagehide", this.pageHideHandler);
412
- this.pageHideHandler = null;
413
- }
414
- }
415
-
416
- export { LongPollSessionManager as SSEManager };
@@ -1,184 +0,0 @@
1
- import { createStore } from "zustand/vanilla";
2
- import { subscribeWithSelector } from "zustand/middleware";
3
- import type { StoreApi } from "zustand/vanilla";
4
- import type { PluginStateSnapshot as UiSdkPluginStateSnapshot } from "@dreamboard-games/sdk/runtime/runtime-api";
5
- import type { LoggerLike } from "./logger.js";
6
- import { consoleLogger } from "./logger.js";
7
- import type { HostSessionTransport } from "./host-session-transport.js";
8
- import { defaultHostSessionTransport } from "./host-session-transport.js";
9
- import type { GameplayViewport } from "./session-model.js";
10
- import {
11
- getGameplayViewport,
12
- getSessionContext,
13
- resolveControllablePlayerIds,
14
- } from "./session-model.js";
15
- import type { SSEManagerLike } from "./session-live-runtime.js";
16
- import { createSessionIngressController } from "./session-ingress-controller.js";
17
- import type { SessionIngressControllerActions } from "./session-ingress-controller.js";
18
- import {
19
- createInitialUnifiedSessionState,
20
- type UnifiedSessionState,
21
- } from "./session-state-reducer.js";
22
- import {
23
- selectBootstrapStatus,
24
- selectGameplayViewModel,
25
- selectGameplayViewport,
26
- selectHistory,
27
- selectLobbyViewModel,
28
- selectPluginSnapshot,
29
- selectSessionContext,
30
- selectSessionError,
31
- selectSessionType,
32
- } from "./session-projection.js";
33
-
34
- export type {
35
- GameplayViewport,
36
- HistoryState,
37
- SessionContext,
38
- SessionIdentity,
39
- SessionPhase,
40
- UnifiedSessionModel,
41
- } from "./session-model.js";
42
- export type {
43
- BootstrapStatus,
44
- GameplayViewModel,
45
- LobbyViewModel,
46
- } from "./session-projection.js";
47
- export type { SSEManagerLike } from "./session-live-runtime.js";
48
- export type {
49
- ActivityState,
50
- ConnectionState,
51
- ConnectionRecoveryState,
52
- DebugState,
53
- HostFeedback,
54
- HostFeedbackPayload,
55
- HostFeedbackType,
56
- Notification,
57
- NotificationPayload,
58
- NotificationType,
59
- SSEEventEntry,
60
- UnifiedSessionState,
61
- } from "./session-state-reducer.js";
62
-
63
- export type PluginStateSnapshot = UiSdkPluginStateSnapshot;
64
- export type GameplayState = GameplayViewport;
65
-
66
- export interface UnifiedSessionActions extends SessionIngressControllerActions {
67
- getPluginSnapshot: () => PluginStateSnapshot;
68
- getRenderableGameplay: () => GameplayViewport | null;
69
- }
70
-
71
- export type UnifiedSessionStore = UnifiedSessionState & UnifiedSessionActions;
72
-
73
- export interface CreateUnifiedSessionStoreOptions {
74
- createSseManager: () => SSEManagerLike;
75
- transport?: HostSessionTransport;
76
- logger?: LoggerLike;
77
- fallbackToAllSeatsWhenUserIdMissing?: boolean;
78
- }
79
-
80
- let sseEventIdCounter = 0;
81
- let notificationIdCounter = 0;
82
- const EMPTY_ARRAY: never[] = [];
83
-
84
- function generateNotificationId(): string {
85
- return `notif-${++notificationIdCounter}-${Date.now()}`;
86
- }
87
-
88
- export function createUnifiedSessionStore(
89
- options: CreateUnifiedSessionStoreOptions,
90
- ): StoreApi<UnifiedSessionStore> {
91
- const logger = options.logger ?? consoleLogger;
92
- const transport = options.transport ?? defaultHostSessionTransport;
93
- const fallbackToAllSeatsWhenUserIdMissing =
94
- options.fallbackToAllSeatsWhenUserIdMissing ?? false;
95
-
96
- return createStore<UnifiedSessionStore>()(
97
- subscribeWithSelector((_, get, store) => {
98
- const controller = createSessionIngressController({
99
- store,
100
- createSseManager: options.createSseManager,
101
- transport,
102
- logger,
103
- fallbackToAllSeatsWhenUserIdMissing,
104
- reducerEnvironment: {
105
- fallbackToAllSeatsWhenUserIdMissing,
106
- nextEventId: () => ++sseEventIdCounter,
107
- nextNotificationId: generateNotificationId,
108
- nowMs: () => Date.now(),
109
- nowIso: () => new Date().toISOString(),
110
- },
111
- });
112
-
113
- return {
114
- ...createInitialUnifiedSessionState(),
115
- ...controller,
116
- getPluginSnapshot: () =>
117
- selectPluginSnapshot(get(), fallbackToAllSeatsWhenUserIdMissing),
118
- getRenderableGameplay: () => getGameplayViewport(get().session),
119
- };
120
- }),
121
- );
122
- }
123
-
124
- export const unifiedSessionSelectors = {
125
- sessionType: selectSessionType,
126
- bootstrapStatus: selectBootstrapStatus,
127
- sessionContext: selectSessionContext,
128
- lobby: selectLobbyViewModel,
129
- gameplay: selectGameplayViewModel,
130
- gameplayViewport: selectGameplayViewport,
131
- hasGameplayPayload: (s: UnifiedSessionStore) =>
132
- getGameplayViewport(s.session) !== null,
133
- history: selectHistory,
134
- isConnected: (s: UnifiedSessionStore) => s.connection.isConnected,
135
- connectionError: (s: UnifiedSessionStore) => s.connection.error,
136
- connectionRecovery: (s: UnifiedSessionStore) => s.connection.recovery,
137
- isLoading: (s: UnifiedSessionStore) =>
138
- s.session.type === "loading" ||
139
- s.session.type === "gameplayLoading" ||
140
- s.connection.recovery.active,
141
- error: selectSessionError,
142
- sessionId: (s: UnifiedSessionStore) =>
143
- getSessionContext(s.session)?.identity.sessionId ?? null,
144
- shortCode: (s: UnifiedSessionStore) =>
145
- getSessionContext(s.session)?.identity.shortCode ?? "",
146
- gameId: (s: UnifiedSessionStore) =>
147
- getSessionContext(s.session)?.identity.gameId ?? null,
148
- seats: (s: UnifiedSessionStore) =>
149
- getSessionContext(s.session)?.seats ?? EMPTY_ARRAY,
150
- canStart: (s: UnifiedSessionStore) =>
151
- getSessionContext(s.session)?.canStart ?? false,
152
- hostActor: (s: UnifiedSessionStore) =>
153
- getSessionContext(s.session)?.hostActor ?? null,
154
- isSessionHost: (s: UnifiedSessionStore) =>
155
- selectLobbyViewModel(s)?.isSessionHost ?? false,
156
- controllablePlayerIds: (s: UnifiedSessionStore) => {
157
- const context = getSessionContext(s.session);
158
- return context
159
- ? resolveControllablePlayerIds(
160
- context.switchablePlayerIds,
161
- context.seats,
162
- context.userId,
163
- )
164
- : EMPTY_ARRAY;
165
- },
166
- controllingPlayerId: (s: UnifiedSessionStore) =>
167
- s.session.type === "gameplay" ? s.session.perspective.playerId : null,
168
- currentPlayerId: (s: UnifiedSessionStore) =>
169
- s.session.type === "gameplay"
170
- ? s.session.perspective.playerId
171
- : s.session.type === "gameplayLoading"
172
- ? s.session.requestedPlayerId
173
- : s.session.type === "lobby"
174
- ? s.session.preferredPlayerId
175
- : null,
176
- userId: (s: UnifiedSessionStore) =>
177
- getSessionContext(s.session)?.userId ?? null,
178
- notifications: (s: UnifiedSessionStore) => s.activity.notifications,
179
- hostFeedback: (s: UnifiedSessionStore) => s.activity.hostFeedback,
180
- sseEvents: (s: UnifiedSessionStore) => s.debug.sseEvents,
181
- syncId: (s: UnifiedSessionStore) => s.activity.syncId,
182
- activity: (s: UnifiedSessionStore) => s.activity,
183
- debug: (s: UnifiedSessionStore) => s.debug,
184
- };