@dreamboard-games/sdk 0.4.0-alpha.1 → 0.4.0-alpha.2

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 (78) hide show
  1. package/REFERENCE.md +1215 -125
  2. package/dist/{ResourceCounter-BTXklCEZ.d.ts → ResourceCounter-BuiNstXs.d.ts} +5 -1
  3. package/dist/{ThemeProvider-DvMl17Ny.d.ts → ThemeProvider-B1sIoBlX.d.ts} +1 -1
  4. package/dist/{attributes-BrZtP9az.d.ts → attributes-DbvyMbXw.d.ts} +17 -2
  5. package/dist/authoring/index.d.ts +66 -0
  6. package/dist/authoring/index.js +560 -0
  7. package/dist/authoring/index.js.map +1 -0
  8. package/dist/browser-interaction.d.ts +205 -5
  9. package/dist/browser-interaction.js +14 -2
  10. package/dist/{chunk-7W6TQO3X.js → chunk-45FI6XII.js} +613 -366
  11. package/dist/chunk-45FI6XII.js.map +1 -0
  12. package/dist/{chunk-3K362ZKR.js → chunk-6JQFGSLD.js} +202 -38
  13. package/dist/chunk-6JQFGSLD.js.map +1 -0
  14. package/dist/{chunk-XXBSIPJX.js → chunk-CP35NPPY.js} +131 -59
  15. package/dist/chunk-CP35NPPY.js.map +1 -0
  16. package/dist/{chunk-XJYUSD5F.js → chunk-FWVV2X74.js} +2 -2
  17. package/dist/{chunk-IUGKW2GS.js → chunk-HWJS3VOJ.js} +6 -2
  18. package/dist/chunk-HWJS3VOJ.js.map +1 -0
  19. package/dist/chunk-K5ZRJTZZ.js +328 -0
  20. package/dist/chunk-K5ZRJTZZ.js.map +1 -0
  21. package/dist/{chunk-7LZZEI25.js → chunk-N6ZDIMRB.js} +1 -1
  22. package/dist/chunk-N6ZDIMRB.js.map +1 -0
  23. package/dist/chunk-O6M6PN4E.js +1225 -0
  24. package/dist/chunk-O6M6PN4E.js.map +1 -0
  25. package/dist/{chunk-AEUXCFGM.js → chunk-PF7L4BMG.js} +1 -8
  26. package/dist/{chunk-AEUXCFGM.js.map → chunk-PF7L4BMG.js.map} +1 -1
  27. package/dist/{chunk-CZMFAS3E.js → chunk-WOWFONLZ.js} +517 -1274
  28. package/dist/chunk-WOWFONLZ.js.map +1 -0
  29. package/dist/{chunk-MALRKLRR.js → chunk-WUIVY2KE.js} +301 -12
  30. package/dist/chunk-WUIVY2KE.js.map +1 -0
  31. package/dist/chunk-ZAS2SENW.js +12 -0
  32. package/dist/chunk-ZAS2SENW.js.map +1 -0
  33. package/dist/codegen.d.ts +11 -3
  34. package/dist/codegen.js +1 -1
  35. package/dist/{components-Dqr_LuZJ.d.ts → components-BbWPnZCF.d.ts} +37 -6
  36. package/dist/{hex-board-view-UCUjMALd.d.ts → hex-board-view-Kz9Q_zsv.d.ts} +1 -1
  37. package/dist/index.js +2 -1
  38. package/dist/package-set.d.ts +2 -2
  39. package/dist/package-set.js +2 -1
  40. package/dist/player-state-Cqpyeql0.d.ts +371 -0
  41. package/dist/protocol-BP193lva.d.ts +267 -0
  42. package/dist/reducer/advanced.js +1 -1
  43. package/dist/reducer-contract.js +1 -1
  44. package/dist/reducer.js +2 -2
  45. package/dist/reducer.js.map +1 -1
  46. package/dist/runtime/primitives.d.ts +10 -10
  47. package/dist/runtime/primitives.js +9 -7
  48. package/dist/runtime/runtime-api.d.ts +2 -2
  49. package/dist/runtime/workspace-contract.d.ts +23 -12
  50. package/dist/runtime/workspace-contract.js +7 -6
  51. package/dist/runtime-api-BJharVaj.d.ts +198 -0
  52. package/dist/runtime-api-C6aWo9c0.d.ts +95 -0
  53. package/dist/runtime.d.ts +35 -31
  54. package/dist/runtime.js +87 -797
  55. package/dist/runtime.js.map +1 -1
  56. package/dist/testing.d.ts +254 -140
  57. package/dist/testing.js +964 -149
  58. package/dist/testing.js.map +1 -1
  59. package/dist/types-CYeN0wLK.d.ts +29 -0
  60. package/dist/{types-BqzHkNMA.d.ts → types-DJj5MJkl.d.ts} +32 -5
  61. package/dist/types.d.ts +13 -8
  62. package/dist/ui/components.d.ts +4 -5
  63. package/dist/ui/components.js +19 -1
  64. package/dist/ui/player-state.d.ts +2 -365
  65. package/dist/{ui-contract-W2-kxObe.d.ts → ui-contract-CY7cxsCC.d.ts} +16 -26
  66. package/dist/ui.d.ts +7 -8
  67. package/dist/ui.js +20 -2
  68. package/package.json +25 -19
  69. package/dist/chunk-3K362ZKR.js.map +0 -1
  70. package/dist/chunk-7LZZEI25.js.map +0 -1
  71. package/dist/chunk-7W6TQO3X.js.map +0 -1
  72. package/dist/chunk-CZMFAS3E.js.map +0 -1
  73. package/dist/chunk-IUGKW2GS.js.map +0 -1
  74. package/dist/chunk-MALRKLRR.js.map +0 -1
  75. package/dist/chunk-XXBSIPJX.js.map +0 -1
  76. package/dist/hex-color-MhOyuY-o.d.ts +0 -8
  77. package/dist/runtime-api-BXd70c2e.d.ts +0 -490
  78. /package/dist/{chunk-XJYUSD5F.js.map → chunk-FWVV2X74.js.map} +0 -0
package/dist/runtime.js CHANGED
@@ -1,844 +1,140 @@
1
1
  import {
2
2
  createWorkspaceUIContract
3
- } from "./chunk-3K362ZKR.js";
4
- import {
5
- InteractionUiProvider,
6
- RuntimeProvider,
7
- RuntimeSemanticProjectionMarker,
8
- usePluginSession
9
- } from "./chunk-CZMFAS3E.js";
10
- import "./chunk-XJYUSD5F.js";
3
+ } from "./chunk-6JQFGSLD.js";
4
+ import "./chunk-WOWFONLZ.js";
5
+ import "./chunk-FWVV2X74.js";
11
6
  import "./chunk-T3ZKNUZ7.js";
12
7
  import {
13
8
  GameSkeleton
14
- } from "./chunk-7W6TQO3X.js";
9
+ } from "./chunk-45FI6XII.js";
15
10
  import "./chunk-VDXOF4FW.js";
16
- import "./chunk-MALRKLRR.js";
17
11
  import {
18
- RuntimeJsonSchema,
19
- TRANSPORT_JSON_LIMITS,
20
- assertJsonWithinLimits
21
- } from "./chunk-AEUXCFGM.js";
12
+ PluginRuntimeBoundary,
13
+ createPluginRuntimeClient
14
+ } from "./chunk-K5ZRJTZZ.js";
15
+ import {
16
+ DREAMBOARD_PLUGIN_PROTOCOL,
17
+ DREAMBOARD_PLUGIN_PROTOCOL_VERSION,
18
+ HostToPluginEnvelopeSchema
19
+ } from "./chunk-O6M6PN4E.js";
20
+ import "./chunk-WUIVY2KE.js";
21
+ import "./chunk-PF7L4BMG.js";
22
22
  import "./chunk-PZ5AY32C.js";
23
23
 
24
24
  // src/runtime/hooks/usePluginRuntime.ts
25
25
  import { useState, useEffect, useRef } from "react";
26
26
 
27
- // src/runtime/plugin-protocol.ts
28
- import { z } from "zod";
29
- var DREAMBOARD_PLUGIN_PROTOCOL = "dreamboard-plugin";
30
- var DREAMBOARD_PLUGIN_PROTOCOL_VERSION = 2;
31
- var PluginEnvelopeBaseSchema = z.object({
32
- protocol: z.literal(DREAMBOARD_PLUGIN_PROTOCOL),
33
- version: z.literal(DREAMBOARD_PLUGIN_PROTOCOL_VERSION),
34
- channelId: z.string().min(32).max(256)
35
- }).strict();
36
- var PlayerIdSchema = z.string();
37
- var RuntimeRecordSchema = z.record(z.string(), RuntimeJsonSchema);
38
- var NullableRuntimeJsonSchema = RuntimeJsonSchema.nullable();
39
- var SeatAssignmentSchema = z.object({
40
- playerId: PlayerIdSchema,
41
- controllerUserId: z.string().optional(),
42
- displayName: z.string(),
43
- playerColor: z.string().optional(),
44
- isHost: z.boolean().optional()
45
- }).strict();
46
- var LobbyStateSchema = z.object({
47
- seats: z.array(SeatAssignmentSchema),
48
- canStart: z.boolean(),
49
- hostUserId: z.string()
50
- }).strict();
51
- var NotificationPayloadSchema = z.discriminatedUnion("type", [
52
- z.object({
53
- type: z.literal("YOUR_TURN"),
54
- activePlayers: z.array(PlayerIdSchema)
55
- }).strict(),
56
- z.object({
57
- type: z.literal("PROMPT_OPENED"),
58
- promptId: z.string(),
59
- promptInstanceId: z.string(),
60
- targetPlayer: PlayerIdSchema,
61
- title: z.string().optional()
62
- }).strict(),
63
- z.object({
64
- type: z.literal("ACTION_EXECUTED"),
65
- playerId: PlayerIdSchema,
66
- actionType: z.string()
67
- }).strict(),
68
- z.object({
69
- type: z.literal("ACTION_REJECTED"),
70
- reason: z.string(),
71
- targetPlayer: z.string().optional()
72
- }).strict(),
73
- z.object({
74
- type: z.literal("TURN_CHANGED"),
75
- previousPlayers: z.array(PlayerIdSchema),
76
- currentPlayers: z.array(PlayerIdSchema)
77
- }).strict(),
78
- z.object({
79
- type: z.literal("STATE_CHANGED"),
80
- newState: z.string()
81
- }).strict(),
82
- z.object({
83
- type: z.literal("GAME_ENDED"),
84
- winner: z.string().optional(),
85
- finalScores: z.record(z.string(), z.number().finite()),
86
- reason: z.string()
87
- }).strict(),
88
- z.object({
89
- type: z.literal("ERROR"),
90
- message: z.string(),
91
- code: z.string().optional()
92
- }).strict()
93
- ]);
94
- var NotificationSchema = z.object({
95
- id: z.string(),
96
- type: z.enum([
97
- "YOUR_TURN",
98
- "PROMPT_OPENED",
99
- "ACTION_EXECUTED",
100
- "ACTION_REJECTED",
101
- "TURN_CHANGED",
102
- "STATE_CHANGED",
103
- "GAME_ENDED",
104
- "ERROR"
105
- ]),
106
- payload: NotificationPayloadSchema,
107
- timestamp: z.number().finite(),
108
- read: z.boolean()
109
- }).strict();
110
- var PluginSessionStateSchema = z.object({
111
- sessionId: z.string().nullable(),
112
- controllablePlayerIds: z.array(PlayerIdSchema),
113
- controllingPlayerId: PlayerIdSchema.nullable(),
114
- userId: z.string().nullable()
115
- }).strict();
116
- var HistoryEntrySummarySchema = z.object({
117
- id: z.string(),
118
- version: z.number().int(),
119
- timestamp: z.string(),
120
- description: z.string(),
121
- playerId: PlayerIdSchema.optional(),
122
- actionType: z.string().optional(),
123
- isCurrent: z.boolean()
124
- }).strict();
125
- var HistoryStateSchema = z.object({
126
- entries: z.array(HistoryEntrySummarySchema),
127
- currentIndex: z.number().int(),
128
- canGoBack: z.boolean(),
129
- canGoForward: z.boolean()
130
- }).strict();
131
- var SimultaneousPhaseSchema = z.object({
132
- phaseName: z.string(),
133
- interactionId: z.string(),
134
- actorIds: z.array(PlayerIdSchema),
135
- sealedPlayerIds: z.array(PlayerIdSchema),
136
- pendingPlayerIds: z.array(PlayerIdSchema)
137
- }).strict();
138
- var InteractionCommitPolicySchema = z.discriminatedUnion("mode", [
139
- z.object({ mode: z.literal("manual") }).strict(),
140
- z.object({ mode: z.literal("autoWhenReady") }).strict()
141
- ]);
142
- var InputSelectionSchema = z.discriminatedUnion("mode", [
143
- z.object({ mode: z.literal("single") }).strict(),
144
- z.object({
145
- mode: z.literal("many"),
146
- min: z.number().int(),
147
- max: z.number().int().optional(),
148
- distinct: z.boolean().optional()
149
- }).strict()
150
- ]);
151
- var InputDomainDependencyCaseSchema = z.object({
152
- when: z.record(z.string(), z.string()),
153
- domain: RuntimeRecordSchema
154
- }).strict();
155
- var InputDomainResolverSchema = z.discriminatedUnion("mode", [
156
- z.object({
157
- mode: z.literal("eager"),
158
- dependentCases: z.array(InputDomainDependencyCaseSchema)
159
- }).strict(),
160
- z.object({
161
- mode: z.literal("lazy"),
162
- dependsOn: z.array(z.string()),
163
- resolver: z.object({
164
- interactionKey: z.string().optional(),
165
- inputKey: z.string()
166
- }).strict()
167
- }).strict()
168
- ]);
169
- var InputDomainSchema = z.object({
170
- type: z.string(),
171
- selection: InputSelectionSchema.optional(),
172
- dependencies: InputDomainResolverSchema.optional()
173
- }).catchall(RuntimeJsonSchema);
174
- var InteractionInputDescriptorSchema = z.object({
175
- key: z.string(),
176
- kind: z.string(),
177
- domain: InputDomainSchema,
178
- defaultValue: RuntimeJsonSchema.optional()
179
- }).strict();
180
- var InteractionAvailabilitySchema = z.discriminatedUnion("status", [
181
- z.object({ status: z.literal("available") }).strict(),
182
- z.object({ status: z.literal("notYourTurn"), reason: z.string() }).strict(),
183
- z.object({
184
- status: z.literal("insufficientResources"),
185
- reason: z.string(),
186
- missingResources: z.record(z.string(), z.number().finite())
187
- }).strict(),
188
- z.object({
189
- status: z.literal("blocked"),
190
- reason: z.string(),
191
- code: z.string().optional()
192
- }).strict()
193
- ]);
194
- var InteractionBaseSchema = z.object({
195
- phaseName: z.string(),
196
- interactionKey: z.string(),
197
- interactionId: z.string(),
198
- commit: InteractionCommitPolicySchema,
199
- descriptorDigest: z.string().optional(),
200
- actorSeat: z.number().int().optional(),
201
- draftDigest: z.string().optional(),
202
- zoneId: z.string().optional(),
203
- zoneIds: z.array(z.string()).optional(),
204
- inputs: z.array(InteractionInputDescriptorSchema),
205
- cost: RuntimeRecordSchema.optional(),
206
- currentResources: RuntimeRecordSchema.optional(),
207
- availability: InteractionAvailabilitySchema,
208
- reasons: z.array(
209
- z.object({
210
- ruleId: z.string(),
211
- errorCode: z.string()
212
- }).strict()
213
- ).optional()
214
- }).strict();
215
- var InteractionDescriptorSchema = z.discriminatedUnion("kind", [
216
- InteractionBaseSchema.extend({ kind: z.literal("action") }).strict(),
217
- InteractionBaseSchema.extend({
218
- kind: z.literal("prompt"),
219
- context: z.object({
220
- to: z.string(),
221
- title: z.string().optional(),
222
- payload: RuntimeRecordSchema.optional(),
223
- options: z.array(
224
- z.object({
225
- id: z.string(),
226
- label: z.string().optional()
227
- }).strict()
228
- ).optional()
229
- }).strict()
230
- }).strict()
231
- ]);
232
- var ZoneHandlesSnapshotSchema = z.object({
233
- cardIds: z.array(z.string()),
234
- cardViewsById: z.record(z.string(), z.string()),
235
- playableByCardId: z.record(
236
- z.string(),
237
- z.array(InteractionDescriptorSchema)
238
- )
239
- }).strict();
240
- var GameplaySnapshotSchema = z.object({
241
- currentPhase: z.string().nullable(),
242
- currentStage: z.string().nullable(),
243
- activePlayers: z.array(PlayerIdSchema),
244
- simultaneousPhase: SimultaneousPhaseSchema.nullable().optional(),
245
- availableInteractions: z.array(InteractionDescriptorSchema),
246
- zones: z.record(z.string(), ZoneHandlesSnapshotSchema)
247
- }).strict();
248
- var PluginStateSnapshotSchema = z.object({
249
- view: NullableRuntimeJsonSchema,
250
- gameplay: GameplaySnapshotSchema,
251
- lobby: LobbyStateSchema.nullable(),
252
- notifications: z.array(NotificationSchema),
253
- session: PluginSessionStateSchema,
254
- history: HistoryStateSchema.nullable(),
255
- syncId: z.number().int()
256
- }).strict();
257
- var InitPayloadSchema = z.object({
258
- type: z.literal("init"),
259
- sessionId: z.string(),
260
- controllablePlayerIds: z.array(z.string()),
261
- controllingPlayerId: z.string(),
262
- userId: z.string().nullable(),
263
- state: PluginStateSnapshotSchema.optional()
264
- }).strict();
265
- var PingPayloadSchema = z.object({ type: z.literal("ping") }).strict();
266
- var StateSyncPayloadSchema = z.object({
267
- type: z.literal("state-sync"),
268
- syncId: z.number().int(),
269
- state: PluginStateSnapshotSchema
270
- }).strict();
271
- var ValidateInteractionResultPayloadSchema = z.object({
272
- type: z.literal("validate-interaction-result"),
273
- messageId: z.string(),
274
- result: z.object({
275
- valid: z.boolean(),
276
- errorCode: z.string().nullable().optional(),
277
- message: z.string().nullable().optional()
278
- }).strict()
279
- }).strict();
280
- var SubmitResultPayloadSchema = z.object({
281
- type: z.literal("submit-result"),
282
- messageId: z.string(),
283
- accepted: z.boolean(),
284
- errorCode: z.string().nullable().optional(),
285
- message: z.string().nullable().optional()
286
- }).strict();
287
- var HostToPluginPayloadSchema = z.discriminatedUnion("type", [
288
- InitPayloadSchema,
289
- PingPayloadSchema,
290
- StateSyncPayloadSchema,
291
- ValidateInteractionResultPayloadSchema,
292
- SubmitResultPayloadSchema
293
- ]);
294
- var HostToPluginEnvelopeSchema = PluginEnvelopeBaseSchema.extend({
295
- payload: HostToPluginPayloadSchema
296
- }).strict();
297
- var PluginInitEnvelopeSchema = PluginEnvelopeBaseSchema.extend({
298
- payload: InitPayloadSchema
299
- }).strict();
300
- function assertTransportEnvelopeWithinLimits(value) {
301
- assertJsonWithinLimits(value, TRANSPORT_JSON_LIMITS, "Plugin message");
302
- }
303
- function createPluginEnvelope(payload, channel) {
304
- return {
305
- protocol: DREAMBOARD_PLUGIN_PROTOCOL,
306
- version: DREAMBOARD_PLUGIN_PROTOCOL_VERSION,
307
- channelId: channel.channelId,
308
- payload
309
- };
310
- }
311
-
312
- // src/runtime/api/createPluginRuntimeAPI.ts
313
- var mintClientActionId = () => {
314
- const cryptoLike = globalThis.crypto;
315
- if (cryptoLike?.randomUUID) {
316
- try {
317
- return cryptoLike.randomUUID();
318
- } catch {
319
- }
320
- }
321
- const rand = Math.random().toString(16).slice(2);
322
- return `cid-${Date.now().toString(16)}-${rand}`;
323
- };
324
- var PLUGIN_RUNTIME_SINGLETON_KEY = "__dreamboardPluginRuntimeApi";
325
- function createPluginRuntimeAPI(options = {}) {
326
- const existingRuntime = globalThis[PLUGIN_RUNTIME_SINGLETON_KEY];
327
- if (existingRuntime) {
328
- existingRuntime.setDiagnosticHandler?.(options.onDiagnostic);
329
- return existingRuntime;
330
- }
331
- let onDiagnostic = options.onDiagnostic;
27
+ // src/runtime/browser/post-message-transport.ts
28
+ function createPostMessagePluginTransport(options = {}) {
29
+ const targetWindow = options.targetWindow ?? window;
30
+ const parentWindow = options.parentWindow ?? targetWindow.parent;
332
31
  let channel = null;
333
- let disconnected = false;
334
- const emitDiagnostic = (event) => {
335
- if (onDiagnostic) {
336
- try {
337
- onDiagnostic(event);
338
- return;
339
- } catch {
340
- }
341
- }
342
- switch (event.type) {
343
- case "runtimeLog": {
344
- const args = [event.message, ...event.details ?? []];
345
- if (event.level === "warn") {
346
- console.warn(...args);
347
- } else if (event.level === "error") {
348
- console.error(...args);
349
- } else {
350
- console.log(...args);
351
- }
352
- break;
353
- }
354
- case "internalError":
355
- console.error(
356
- `[Plugin RuntimeAPI] ${event.code}:`,
357
- event.message,
358
- event.stack ?? ""
359
- );
360
- break;
361
- default: {
362
- const _exhaustive = event;
363
- return _exhaustive;
364
- }
365
- }
366
- };
367
- let currentStateSnapshot = null;
368
- const stateListeners = /* @__PURE__ */ new Set();
369
- const sessionState = {
370
- status: "loading",
371
- sessionId: null,
372
- controllablePlayerIds: [],
373
- controllingPlayerId: null,
374
- userId: null
375
- };
376
- const sessionStateListeners = /* @__PURE__ */ new Set();
377
- const pendingValidations = /* @__PURE__ */ new Map();
378
- let validationIdCounter = 0;
379
- const pendingSubmissions = /* @__PURE__ */ new Map();
380
- let submitIdCounter = 0;
381
- const postToHost = (payload) => {
382
- if (channel === null || disconnected) {
383
- throw new Error("Plugin runtime is not initialized.");
384
- }
385
- channel.hostWindow.postMessage(
386
- createPluginEnvelope(payload, channel),
387
- channel.hostOrigin
388
- );
389
- };
390
- const rejectPendingRequests = () => {
391
- pendingValidations.forEach((resolve) => {
392
- resolve({
393
- valid: false,
394
- errorCode: "runtime-disconnected",
395
- message: "Plugin runtime disconnected"
396
- });
397
- });
398
- pendingValidations.clear();
399
- pendingSubmissions.forEach((pending) => {
400
- pending.reject(
401
- createSubmissionError(
402
- "runtime-disconnected",
403
- "Plugin runtime disconnected"
404
- )
405
- );
406
- });
407
- pendingSubmissions.clear();
408
- };
409
- const notifySessionStateChange = () => {
410
- sessionStateListeners.forEach((listener) => {
411
- try {
412
- listener({ ...sessionState });
413
- } catch {
414
- }
415
- });
416
- };
417
- const notifyStateListeners = () => {
418
- if (!currentStateSnapshot) return;
419
- const snapshot = currentStateSnapshot;
420
- stateListeners.forEach((listener) => {
421
- try {
422
- listener(snapshot);
423
- } catch {
424
- }
425
- });
426
- };
427
- const createSubmissionError = (errorCode, message) => {
428
- const error = new Error(message ?? "Submission failed");
429
- error.name = "SubmissionError";
430
- error.errorCode = errorCode;
431
- return error;
432
- };
433
- const submitViaParent = (payload) => new Promise((resolve, reject) => {
434
- if (channel === null || disconnected) {
435
- reject(
436
- createSubmissionError(
437
- "runtime-not-initialized",
438
- "Plugin runtime is not initialized."
439
- )
440
- );
441
- return;
442
- }
443
- const messageId = `submit-${++submitIdCounter}`;
444
- pendingSubmissions.set(messageId, { resolve, reject });
445
- const clientSubmittedAtMs = Date.now();
446
- if (payload.clientActionId && typeof performance !== "undefined") {
447
- try {
448
- performance.mark(`dreamboard.t0_click.${payload.clientActionId}`, {
449
- detail: { clientActionId: payload.clientActionId }
450
- });
451
- } catch {
452
- }
453
- }
454
- try {
455
- postToHost({ ...payload, messageId, clientSubmittedAtMs });
456
- } catch (error) {
457
- pendingSubmissions.delete(messageId);
458
- reject(
459
- createSubmissionError(
460
- "runtime-not-initialized",
461
- error instanceof Error ? error.message : "Plugin runtime is not initialized."
462
- )
463
- );
464
- return;
465
- }
466
- setTimeout(() => {
467
- const pending = pendingSubmissions.get(messageId);
468
- if (!pending) {
469
- return;
470
- }
471
- pendingSubmissions.delete(messageId);
472
- pending.reject(
473
- createSubmissionError(
474
- "submission-timeout",
475
- "Submission request timed out"
476
- )
477
- );
478
- }, 1e4);
479
- });
480
- const applyInitMessage = (message) => {
481
- emitDiagnostic({
482
- type: "runtimeLog",
483
- level: "log",
484
- message: "[Plugin RuntimeAPI] Received init message"
485
- });
486
- sessionState.status = "ready";
487
- sessionState.sessionId = message.sessionId;
488
- sessionState.controllablePlayerIds = message.controllablePlayerIds;
489
- sessionState.controllingPlayerId = message.controllingPlayerId;
490
- sessionState.userId = message.userId;
491
- notifySessionStateChange();
492
- if (message.state) {
493
- applyStateSyncMessage({
494
- type: "state-sync",
495
- syncId: message.state.syncId,
496
- state: message.state
497
- });
498
- }
499
- };
500
- const applyStateSyncMessage = (message) => {
501
- emitDiagnostic({
502
- type: "runtimeLog",
503
- level: "log",
504
- message: "[Plugin RuntimeAPI] Received state-sync, syncId:",
505
- details: [message.syncId]
506
- });
507
- const clientReceivedAtMs = Date.now();
508
- if (typeof performance !== "undefined") {
509
- try {
510
- performance.mark(
511
- `dreamboard.t7_state_sync_received.sync-${message.syncId}`,
512
- { detail: { syncId: message.syncId } }
513
- );
514
- } catch {
515
- }
516
- }
517
- currentStateSnapshot = message.state;
518
- if (message.state.session) {
519
- sessionState.sessionId = message.state.session.sessionId;
520
- sessionState.controllablePlayerIds = message.state.session.controllablePlayerIds;
521
- sessionState.controllingPlayerId = message.state.session.controllingPlayerId;
522
- sessionState.userId = message.state.session.userId;
523
- sessionState.status = "ready";
524
- notifySessionStateChange();
525
- }
526
- notifyStateListeners();
527
- postToHost({
528
- type: "state-ack",
529
- syncId: message.syncId,
530
- clientReceivedAtMs
531
- });
532
- const schedulePostRender = () => {
533
- const send = () => {
534
- const clientRenderedAtMs = Date.now();
535
- if (typeof performance !== "undefined") {
536
- try {
537
- performance.mark(
538
- `dreamboard.t8_render_commit.sync-${message.syncId}`,
539
- {
540
- detail: { syncId: message.syncId }
541
- }
542
- );
543
- } catch {
544
- }
545
- }
546
- if (!disconnected && channel !== null) {
547
- postToHost({
548
- type: "state-rendered",
549
- syncId: message.syncId,
550
- clientReceivedAtMs,
551
- clientRenderedAtMs
552
- });
553
- }
554
- };
555
- if (typeof requestAnimationFrame === "function") {
556
- requestAnimationFrame(send);
557
- } else {
558
- queueMicrotask(send);
559
- }
560
- };
561
- queueMicrotask(schedulePostRender);
562
- };
563
- const handleMessage = (event) => {
564
- if (disconnected || event.source !== window.parent) {
565
- return;
566
- }
567
- try {
568
- assertTransportEnvelopeWithinLimits(event.data);
569
- } catch (error) {
570
- emitDiagnostic({
571
- type: "runtimeLog",
572
- level: "warn",
573
- message: "[Plugin RuntimeAPI] Rejected oversized or non-JSON message:",
574
- details: [error instanceof Error ? error.name : "unknown"]
575
- });
576
- return;
577
- }
578
- if (channel === null) {
579
- const initResult = PluginInitEnvelopeSchema.safeParse(event.data);
580
- if (!initResult.success) {
581
- return;
582
- }
583
- channel = {
584
- channelId: initResult.data.channelId,
585
- hostOrigin: event.origin,
586
- hostWindow: window.parent
587
- };
588
- applyInitMessage(initResult.data.payload);
589
- postToHost({ type: "ready" });
590
- return;
591
- }
592
- if (event.source !== channel.hostWindow || event.origin !== channel.hostOrigin) {
593
- return;
594
- }
595
- const parseResult = HostToPluginEnvelopeSchema.safeParse(event.data);
596
- if (!parseResult.success || parseResult.data.channelId !== channel.channelId) {
597
- const rawPayload = event.data?.payload;
598
- if (typeof rawPayload?.type === "string") {
599
- emitDiagnostic({
600
- type: "runtimeLog",
601
- level: "warn",
602
- message: "[Plugin RuntimeAPI] Invalid message received:",
603
- details: [rawPayload.type]
604
- });
605
- }
606
- return;
607
- }
608
- const message = parseResult.data.payload;
609
- switch (message.type) {
610
- case "init": {
611
- break;
612
- }
613
- case "ping": {
614
- postToHost({ type: "pong" });
615
- break;
616
- }
617
- case "state-sync": {
618
- applyStateSyncMessage(message);
619
- break;
620
- }
621
- case "validate-interaction-result": {
622
- const resolver = pendingValidations.get(message.messageId);
623
- if (resolver) {
624
- pendingValidations.delete(message.messageId);
625
- resolver({
626
- valid: message.result.valid,
627
- errorCode: message.result.errorCode ?? void 0,
628
- message: message.result.message ?? void 0
629
- });
32
+ let outboundSequence = 0;
33
+ return {
34
+ start(onMessage) {
35
+ const handleMessage = (event) => {
36
+ if (event.source !== parentWindow) {
37
+ return;
630
38
  }
631
- break;
632
- }
633
- case "submit-result": {
634
- const pending = pendingSubmissions.get(message.messageId);
635
- if (!pending) {
636
- break;
39
+ const parseResult = HostToPluginEnvelopeSchema.safeParse(event.data);
40
+ if (!parseResult.success) {
41
+ options.onInvalidMessage?.("invalid-envelope", event.data);
42
+ return;
637
43
  }
638
- if (typeof performance !== "undefined") {
639
- try {
640
- performance.mark(`dreamboard.t3b_ack.${message.messageId}`, {
641
- detail: { messageId: message.messageId }
642
- });
643
- } catch {
44
+ const envelope = parseResult.data;
45
+ if (!channel) {
46
+ if (envelope.payload.type !== "runtime.init") {
47
+ return;
644
48
  }
49
+ channel = {
50
+ channelId: envelope.channelId,
51
+ hostOrigin: event.origin,
52
+ hostWindow: event.source
53
+ };
645
54
  }
646
- pendingSubmissions.delete(message.messageId);
647
- if (message.accepted) {
648
- pending.resolve();
649
- } else {
650
- pending.reject(
651
- createSubmissionError(
652
- message.errorCode ?? void 0,
653
- message.message ?? void 0
654
- )
655
- );
656
- }
657
- break;
658
- }
659
- }
660
- };
661
- window.addEventListener("message", handleMessage);
662
- const sendErrorToParent = (message, code, stack) => {
663
- emitDiagnostic({
664
- type: "internalError",
665
- code,
666
- message,
667
- ...stack ? { stack } : {}
668
- });
669
- if (channel !== null && !disconnected) {
670
- postToHost({
671
- type: "error",
672
- message: stack ? `${message}
673
- ${stack}` : message,
674
- code
675
- });
676
- }
677
- };
678
- window.onerror = (message, source, lineno, colno, error) => {
679
- const errorMessage = typeof message === "string" ? message : error?.message || "Unknown error";
680
- const location = source ? ` at ${source}:${lineno}:${colno}` : "";
681
- sendErrorToParent(errorMessage + location, "UNCAUGHT_ERROR", error?.stack);
682
- return false;
683
- };
684
- window.onunhandledrejection = (event) => {
685
- const reason = event.reason;
686
- const message = reason instanceof Error ? reason.message : typeof reason === "string" ? reason : JSON.stringify(reason);
687
- const stack = reason instanceof Error ? reason.stack : void 0;
688
- sendErrorToParent(message, "UNHANDLED_REJECTION", stack);
689
- };
690
- emitDiagnostic({
691
- type: "runtimeLog",
692
- level: "log",
693
- message: "[Plugin RuntimeAPI] Initialized (state-sync architecture)"
694
- });
695
- const runtime = {
696
- // State-sync methods
697
- getSnapshot: () => currentStateSnapshot,
698
- subscribeToState: (listener) => {
699
- stateListeners.add(listener);
700
- if (currentStateSnapshot) {
701
- try {
702
- listener(currentStateSnapshot);
703
- } catch {
704
- }
705
- }
706
- return () => {
707
- stateListeners.delete(listener);
708
- };
709
- },
710
- validateInteraction: async (playerId, interactionId, params) => {
711
- return new Promise((resolve) => {
712
- if (channel === null || disconnected) {
713
- resolve({
714
- valid: false,
715
- errorCode: "runtime-not-initialized",
716
- message: "Plugin runtime is not initialized."
717
- });
55
+ if (envelope.channelId !== channel.channelId || event.origin !== channel.hostOrigin || event.source !== channel.hostWindow) {
56
+ options.onInvalidMessage?.("channel-mismatch", event.data);
718
57
  return;
719
58
  }
720
- const messageId = `validate-${++validationIdCounter}`;
721
- pendingValidations.set(messageId, resolve);
722
- try {
723
- postToHost({
724
- type: "validate-interaction",
725
- playerId,
726
- interactionId,
727
- params,
728
- messageId
729
- });
730
- } catch {
731
- pendingValidations.delete(messageId);
732
- resolve({
733
- valid: false,
734
- errorCode: "runtime-not-initialized",
735
- message: "Plugin runtime is not initialized."
736
- });
737
- return;
738
- }
739
- setTimeout(() => {
740
- if (pendingValidations.has(messageId)) {
741
- pendingValidations.delete(messageId);
742
- resolve({
743
- valid: false,
744
- errorCode: "validation-timeout",
745
- message: "Validation request timed out"
746
- });
747
- }
748
- }, 1e4);
749
- });
750
- },
751
- submitInteraction: async (playerId, interactionId, params) => submitViaParent({
752
- type: "interaction",
753
- playerId,
754
- interactionId,
755
- params,
756
- clientActionId: mintClientActionId()
757
- }),
758
- getSessionState: () => ({ ...sessionState }),
759
- disconnect: () => {
760
- window.removeEventListener("message", handleMessage);
761
- window.onerror = null;
762
- window.onunhandledrejection = null;
763
- disconnected = true;
764
- channel = null;
765
- sessionStateListeners.clear();
766
- stateListeners.clear();
767
- rejectPendingRequests();
768
- currentStateSnapshot = null;
769
- },
770
- switchPlayer: (playerId) => {
771
- postToHost({ type: "switch-player", playerId });
772
- },
773
- restoreHistory: (entryId) => {
774
- postToHost({ type: "restore-history", entryId });
775
- },
776
- markNotificationRead: (notificationId) => {
777
- postToHost({ type: "mark-notification-read", notificationId });
778
- },
779
- setDiagnosticHandler: (handler) => {
780
- onDiagnostic = handler;
781
- },
782
- emitDiagnostic,
783
- _subscribeToSessionState: (listener) => {
784
- sessionStateListeners.add(listener);
785
- listener({ ...sessionState });
59
+ onMessage(envelope);
60
+ };
61
+ targetWindow.addEventListener("message", handleMessage);
786
62
  return () => {
787
- sessionStateListeners.delete(listener);
63
+ targetWindow.removeEventListener("message", handleMessage);
64
+ channel = null;
788
65
  };
66
+ },
67
+ send(payload) {
68
+ if (!channel) {
69
+ throw new Error("Plugin transport is not initialized.");
70
+ }
71
+ channel.hostWindow.postMessage(
72
+ {
73
+ protocol: DREAMBOARD_PLUGIN_PROTOCOL,
74
+ version: DREAMBOARD_PLUGIN_PROTOCOL_VERSION,
75
+ channelId: channel.channelId,
76
+ sequence: ++outboundSequence,
77
+ payload
78
+ },
79
+ channel.hostOrigin
80
+ );
789
81
  }
790
82
  };
791
- globalThis[PLUGIN_RUNTIME_SINGLETON_KEY] = runtime;
792
- return runtime;
793
83
  }
794
84
 
795
85
  // src/runtime/hooks/usePluginRuntime.ts
796
- function hasProjectedView(snapshot) {
797
- return snapshot !== null && snapshot !== void 0 && snapshot.view !== null && snapshot.view !== void 0;
86
+ function hasProjectedView(runtime) {
87
+ const frame = runtime.getFrame();
88
+ return frame !== null && frame.view !== null && typeof frame.view !== "undefined";
798
89
  }
799
90
  function usePluginRuntime(options = {}) {
800
91
  const { timeout = 1e4, onDiagnostic } = options;
801
92
  const [runtime] = useState(
802
- () => createPluginRuntimeAPI({ onDiagnostic })
93
+ () => createPluginRuntimeClient({
94
+ transport: createPostMessagePluginTransport({
95
+ onInvalidMessage: (reason, value) => onDiagnostic?.({
96
+ type: "runtimeLog",
97
+ level: "warn",
98
+ message: `[PluginRuntime] Ignored invalid host message: ${reason}`,
99
+ details: [value]
100
+ })
101
+ })
102
+ })
803
103
  );
804
- const [isReady, setIsReady] = useState(() => {
805
- const snapshot = runtime.getSnapshot?.();
806
- return hasProjectedView(snapshot);
807
- });
104
+ const [isReady, setIsReady] = useState(() => hasProjectedView(runtime));
808
105
  const [error, setError] = useState(null);
809
106
  const hasBeenReadyRef = useRef(false);
810
107
  if (isReady) hasBeenReadyRef.current = true;
811
108
  useEffect(() => {
812
- runtime.setDiagnosticHandler?.(onDiagnostic);
813
- }, [runtime, onDiagnostic]);
109
+ void onDiagnostic;
110
+ }, [onDiagnostic]);
814
111
  useEffect(() => {
815
- const markReadyFromSnapshot = () => {
816
- const currentSnapshot = runtime.getSnapshot?.();
817
- if (!hasProjectedView(currentSnapshot)) {
112
+ const markReadyFromFrame = () => {
113
+ if (!hasProjectedView(runtime)) {
818
114
  return false;
819
115
  }
820
116
  setError(null);
821
117
  setIsReady(true);
822
118
  return true;
823
119
  };
824
- if (markReadyFromSnapshot()) {
120
+ if (markReadyFromFrame()) {
825
121
  return;
826
122
  }
827
123
  const timeoutId = setTimeout(() => {
828
- if (!markReadyFromSnapshot()) {
124
+ if (!markReadyFromFrame()) {
829
125
  setError(
830
- `Timed out waiting for the initial projected view after ${timeout}ms. Ensure the host sends a reducer-native state-sync with a seat view.`
126
+ `Timed out waiting for the initial projected view after ${timeout}ms. Ensure the host sends a gameplay frame with a seat view.`
831
127
  );
832
128
  }
833
129
  }, timeout);
834
130
  const pollId = setInterval(() => {
835
- if (markReadyFromSnapshot()) {
131
+ if (markReadyFromFrame()) {
836
132
  clearInterval(pollId);
837
133
  clearTimeout(timeoutId);
838
134
  }
839
135
  }, 100);
840
- const unsubscribe = runtime.subscribeToState?.((state) => {
841
- if (!hasProjectedView(state)) {
136
+ const unsubscribe = runtime.subscribeFrame(() => {
137
+ if (!hasProjectedView(runtime)) {
842
138
  setIsReady(false);
843
139
  return;
844
140
  }
@@ -894,19 +190,13 @@ function PluginRuntime({
894
190
  }
895
191
  );
896
192
  }
897
- return /* @__PURE__ */ jsx(RuntimeProvider, { runtime, children: /* @__PURE__ */ jsx(SessionScopedInteractionUiProvider, { children }) });
898
- }
899
- function SessionScopedInteractionUiProvider({
900
- children
901
- }) {
902
- const { controllingPlayerId } = usePluginSession();
903
- return /* @__PURE__ */ jsxs(InteractionUiProvider, { children: [
904
- /* @__PURE__ */ jsx(RuntimeSemanticProjectionMarker, {}),
905
- children
906
- ] }, controllingPlayerId ?? "__no_player__");
193
+ return /* @__PURE__ */ jsx(PluginRuntimeBoundary, { runtime, children });
907
194
  }
908
195
  export {
909
196
  PluginRuntime,
197
+ PluginRuntimeBoundary,
198
+ createPluginRuntimeClient,
199
+ createPostMessagePluginTransport,
910
200
  createWorkspaceUIContract
911
201
  };
912
202
  //# sourceMappingURL=runtime.js.map