@getuserfeedback/protocol 0.10.1 → 1.0.0

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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webview-transport.d.ts","sourceRoot":"","sources":["../src/webview-transport.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,155 @@
1
+ import * as z from "zod/mini";
2
+ import { appEventPayloadSchema } from "./app-event.js";
3
+ import { clientMetaSchema } from "./client-meta.js";
4
+ import { HANDLE_INVALIDATED_REASON_CODES, HANDLE_INVALIDATED_SOURCES, } from "./host/host-event-contract.js";
5
+ import { publicCommandPayloadSchema } from "./widget-commands.js";
6
+ const nonEmptyStringSchema = z.string().check(z.trim(), z.minLength(1));
7
+ const transportMessageIdSchema = nonEmptyStringSchema;
8
+ const commandErrorSchema = z.strictObject({
9
+ message: nonEmptyStringSchema,
10
+ code: z.optional(nonEmptyStringSchema),
11
+ });
12
+ export const webViewCommandEnvelopeSchema = z.object({
13
+ version: z.literal("1"),
14
+ instanceId: nonEmptyStringSchema,
15
+ requestId: nonEmptyStringSchema,
16
+ idempotencyKey: nonEmptyStringSchema,
17
+ clientMeta: z.optional(clientMetaSchema),
18
+ command: publicCommandPayloadSchema,
19
+ });
20
+ const commandSettledDetailBase = {
21
+ requestId: nonEmptyStringSchema,
22
+ instanceId: z.nullable(nonEmptyStringSchema),
23
+ kind: nonEmptyStringSchema,
24
+ };
25
+ const commandSettledDetailSchema = z.discriminatedUnion("ok", [
26
+ z.strictObject({
27
+ ...commandSettledDetailBase,
28
+ ok: z.literal(true),
29
+ result: z.optional(z.unknown()),
30
+ }),
31
+ z.strictObject({
32
+ ...commandSettledDetailBase,
33
+ ok: z.literal(false),
34
+ error: commandErrorSchema,
35
+ }),
36
+ ]);
37
+ const flowStateBase = {
38
+ instanceId: nonEmptyStringSchema,
39
+ isOpen: z.boolean(),
40
+ isLoading: z.boolean(),
41
+ width: z.optional(z.number()),
42
+ height: z.optional(z.number()),
43
+ };
44
+ const flowStateChangedDetailSchema = z.strictObject({
45
+ ...flowStateBase,
46
+ flowHandleId: nonEmptyStringSchema,
47
+ });
48
+ const instanceFlowStateChangedDetailSchema = z.strictObject(flowStateBase);
49
+ const openRequestedDetailSchema = z.strictObject({
50
+ instanceId: nonEmptyStringSchema,
51
+ source: z.enum(["command", "targeting"]),
52
+ flowId: nonEmptyStringSchema,
53
+ flowHandleId: z.optional(nonEmptyStringSchema),
54
+ hideCloseButton: z.optional(z.boolean()),
55
+ });
56
+ const handleInvalidatedDetailSchema = z.strictObject({
57
+ instanceId: nonEmptyStringSchema,
58
+ handleKind: nonEmptyStringSchema,
59
+ handleId: nonEmptyStringSchema,
60
+ reasonCode: z.enum(HANDLE_INVALIDATED_REASON_CODES),
61
+ reasonMessage: z.optional(nonEmptyStringSchema),
62
+ relatedRequestId: z.optional(nonEmptyStringSchema),
63
+ source: z.enum(HANDLE_INVALIDATED_SOURCES),
64
+ at: z.number(),
65
+ });
66
+ const unsupportedCommandDetailSchema = z.strictObject({
67
+ requestId: nonEmptyStringSchema,
68
+ instanceId: z.nullable(nonEmptyStringSchema),
69
+ kind: nonEmptyStringSchema,
70
+ code: z.literal("UNSUPPORTED_COMMAND"),
71
+ message: nonEmptyStringSchema,
72
+ issues: z.optional(z.array(z.string())),
73
+ });
74
+ const instanceErrorDetailSchema = z.strictObject({
75
+ instanceId: nonEmptyStringSchema,
76
+ code: nonEmptyStringSchema,
77
+ details: z.optional(z.unknown()),
78
+ recovery: z.optional(z.unknown()),
79
+ at: z.number(),
80
+ });
81
+ const loaderErrorDetailSchema = z.strictObject({
82
+ code: nonEmptyStringSchema,
83
+ details: z.optional(z.unknown()),
84
+ recovery: z.optional(z.unknown()),
85
+ at: z.number(),
86
+ });
87
+ const appEventDetailSchema = z.strictObject({
88
+ instanceId: nonEmptyStringSchema,
89
+ payload: appEventPayloadSchema,
90
+ });
91
+ export const webViewTransportNativeMessageSchema = z.discriminatedUnion("kind", [
92
+ z.strictObject({
93
+ kind: z.literal("enqueueCommand"),
94
+ id: transportMessageIdSchema,
95
+ envelope: webViewCommandEnvelopeSchema,
96
+ }),
97
+ ]);
98
+ export const webViewTransportHostEventSchema = z.discriminatedUnion("name", [
99
+ z.strictObject({
100
+ name: z.literal("instance:command:settled"),
101
+ detail: commandSettledDetailSchema,
102
+ }),
103
+ z.strictObject({
104
+ name: z.literal("instance:flow:state-changed"),
105
+ detail: flowStateChangedDetailSchema,
106
+ }),
107
+ z.strictObject({
108
+ name: z.literal("instance:flow:state-changed:instance"),
109
+ detail: instanceFlowStateChangedDetailSchema,
110
+ }),
111
+ z.strictObject({
112
+ name: z.literal("instance:open:requested"),
113
+ detail: openRequestedDetailSchema,
114
+ }),
115
+ z.strictObject({
116
+ name: z.literal("instance:handle:invalidated"),
117
+ detail: handleInvalidatedDetailSchema,
118
+ }),
119
+ z.strictObject({
120
+ name: z.literal("instance:command:unsupported"),
121
+ detail: unsupportedCommandDetailSchema,
122
+ }),
123
+ z.strictObject({
124
+ name: z.literal("instance:error"),
125
+ detail: instanceErrorDetailSchema,
126
+ }),
127
+ z.strictObject({
128
+ name: z.literal("instance:app-event"),
129
+ detail: appEventDetailSchema,
130
+ }),
131
+ z.strictObject({
132
+ name: z.literal("loader:error"),
133
+ detail: loaderErrorDetailSchema,
134
+ }),
135
+ ]);
136
+ export const webViewTransportWebMessageSchema = z.discriminatedUnion("kind", [
137
+ z.strictObject({
138
+ kind: z.literal("ready"),
139
+ instanceId: z.optional(nonEmptyStringSchema),
140
+ }),
141
+ z.strictObject({
142
+ kind: z.literal("hostEvent"),
143
+ event: webViewTransportHostEventSchema,
144
+ }),
145
+ z.strictObject({
146
+ kind: z.literal("error"),
147
+ error: commandErrorSchema,
148
+ }),
149
+ ]);
150
+ export function parseWebViewTransportNativeMessage(input) {
151
+ return webViewTransportNativeMessageSchema.parse(input);
152
+ }
153
+ export function parseWebViewTransportWebMessage(input) {
154
+ return webViewTransportWebMessageSchema.parse(input);
155
+ }
@@ -2,9 +2,11 @@ import * as z from "zod/mini";
2
2
  import { type ConfigureOptions, type InitOptions } from "./widget-config.js";
3
3
  export declare const publicWidgetCommandKindSchema: z.ZodMiniEnum<{
4
4
  open: "open";
5
- prefetch: "prefetch";
6
5
  prerender: "prerender";
6
+ prefetch: "prefetch";
7
7
  identify: "identify";
8
+ updateHostContext: "updateHostContext";
9
+ emitHostSignal: "emitHostSignal";
8
10
  setContainer: "setContainer";
9
11
  setDefaultContainerPolicy: "setDefaultContainerPolicy";
10
12
  configure: "configure";
@@ -55,6 +57,22 @@ export declare const publicCommandPayloadSchema: z.ZodMiniUnion<readonly [z.ZodM
55
57
  flowId: z.ZodMiniString<string>;
56
58
  flowHandleId: z.ZodMiniOptional<z.ZodMiniString<string>>;
57
59
  hideCloseButton: z.ZodMiniOptional<z.ZodMiniBoolean<boolean>>;
60
+ }, z.core.$strict>, z.ZodMiniObject<{
61
+ kind: z.ZodMiniLiteral<"updateHostContext">;
62
+ context: z.ZodMiniObject<{
63
+ url: z.ZodMiniString<string>;
64
+ env: z.ZodMiniOptional<z.ZodMiniRecord<z.ZodMiniString<string>, z.ZodMiniString<string>>>;
65
+ storage: z.ZodMiniOptional<z.ZodMiniRecord<z.ZodMiniString<string>, z.ZodMiniString<string>>>;
66
+ page: z.ZodMiniOptional<z.ZodMiniObject<{
67
+ path: z.ZodMiniOptional<z.ZodMiniString<string>>;
68
+ referrer: z.ZodMiniOptional<z.ZodMiniString<string>>;
69
+ search: z.ZodMiniOptional<z.ZodMiniString<string>>;
70
+ }, z.core.$strict>>;
71
+ }, z.core.$strict>;
72
+ }, z.core.$strict>, z.ZodMiniObject<{
73
+ kind: z.ZodMiniLiteral<"emitHostSignal">;
74
+ name: z.ZodMiniString<string>;
75
+ data: z.ZodMiniOptional<z.ZodMiniUnknown>;
58
76
  }, z.core.$strict>, z.ZodMiniObject<{
59
77
  kind: z.ZodMiniLiteral<"setContainer">;
60
78
  flowHandleId: z.ZodMiniString<string>;
@@ -1 +1 @@
1
- {"version":3,"file":"widget-commands.d.ts","sourceRoot":"","sources":["../src/widget-commands.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,UAAU,CAAC;AAK9B,OAAO,EACN,KAAK,gBAAgB,EAErB,KAAK,WAAW,EAEhB,MAAM,oBAAoB,CAAC;AAoB5B,eAAO,MAAM,6BAA6B;;;;;;;;;;;;EAAmC,CAAC;AAgD9E,eAAO,MAAM,qBAAqB;;;;;;;;;4BAShC,CAAC;AAeH,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;sBA2CrC,CAAC;AAEH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAE/E,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAErE,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,WAAW,CAE5D;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,GAAG,gBAAgB,CAEtE;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,oBAAoB,CAEvE"}
1
+ {"version":3,"file":"widget-commands.d.ts","sourceRoot":"","sources":["../src/widget-commands.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,UAAU,CAAC;AAK9B,OAAO,EACN,KAAK,gBAAgB,EAErB,KAAK,WAAW,EAEhB,MAAM,oBAAoB,CAAC;AAsB5B,eAAO,MAAM,6BAA6B;;;;;;;;;;;;;;EAAmC,CAAC;AA6D9E,eAAO,MAAM,qBAAqB;;;;;;;;;4BAShC,CAAC;AAeH,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;sBAoDrC,CAAC;AAEH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAE/E,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAErE,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,WAAW,CAE5D;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,GAAG,gBAAgB,CAEtE;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,oBAAoB,CAEvE"}
@@ -9,6 +9,8 @@ const publicWidgetCommandKinds = [
9
9
  "prefetch",
10
10
  "prerender",
11
11
  "identify",
12
+ "updateHostContext",
13
+ "emitHostSignal",
12
14
  "setContainer",
13
15
  "setDefaultContainerPolicy",
14
16
  "configure",
@@ -52,6 +54,16 @@ const identifyPayloadSchema = z.union([
52
54
  options: z.optional(identifyOptionsSchema),
53
55
  }),
54
56
  ]);
57
+ const hostContextSchema = z.strictObject({
58
+ url: z.string().check(z.url()),
59
+ env: z.optional(z.record(z.string(), z.string())),
60
+ storage: z.optional(z.record(z.string(), z.string())),
61
+ page: z.optional(z.strictObject({
62
+ path: z.optional(z.string()),
63
+ referrer: z.optional(z.string()),
64
+ search: z.optional(z.string()),
65
+ })),
66
+ });
55
67
  // TODO(migration): After telemetry confirms no remaining `open.containerRequirement` in host command queues, remove the optional field and transform so unknown keys fail under `.strict()` again.
56
68
  // [from=legacy-open-container-requirement] [to=strict-public-open-only] [scope=slice] [priority=low] [impact=medium] [risk=low]
57
69
  const legacyOpenContainerRequirementSchema = z.enum(["any", "hostOnly"]);
@@ -88,6 +100,15 @@ export const publicCommandPayloadSchema = z.union([
88
100
  flowHandleId: z.optional(flowHandleIdSchema),
89
101
  hideCloseButton: z.optional(z.boolean()),
90
102
  }),
103
+ z.strictObject({
104
+ kind: z.literal("updateHostContext"),
105
+ context: hostContextSchema,
106
+ }),
107
+ z.strictObject({
108
+ kind: z.literal("emitHostSignal"),
109
+ name: z.string().check(z.trim(), z.minLength(1)),
110
+ data: z.optional(z.unknown()),
111
+ }),
91
112
  z.strictObject({
92
113
  kind: z.literal("setContainer"),
93
114
  flowHandleId: flowHandleIdSchema,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getuserfeedback/protocol",
3
- "version": "0.10.1",
3
+ "version": "1.0.0",
4
4
  "description": "getuserfeedback widget protocol — host surface and (later) wire protocol",
5
5
  "keywords": [
6
6
  "getuserfeedback",
@@ -88,6 +88,12 @@
88
88
  "import": "./dist/host/lazy-handle-client.js",
89
89
  "default": "./dist/host/lazy-handle-client.js"
90
90
  },
91
+ "./webview-transport": {
92
+ "types": "./dist/webview-transport.d.ts",
93
+ "bun": "./src/webview-transport.ts",
94
+ "import": "./dist/webview-transport.js",
95
+ "default": "./dist/webview-transport.js"
96
+ },
91
97
  "./version-resolution": {
92
98
  "types": "./dist/version-resolution.d.ts",
93
99
  "bun": "./src/version-resolution.ts",
package/src/index.ts CHANGED
@@ -100,6 +100,18 @@ export {
100
100
  type ThemeVersionResolution,
101
101
  themeVersionResolutionSchema,
102
102
  } from "./version-resolution.js";
103
+ export {
104
+ parseWebViewTransportNativeMessage,
105
+ parseWebViewTransportWebMessage,
106
+ type WebViewCommandEnvelope,
107
+ type WebViewTransportHostEvent,
108
+ type WebViewTransportNativeMessage,
109
+ type WebViewTransportWebMessage,
110
+ webViewCommandEnvelopeSchema,
111
+ webViewTransportHostEventSchema,
112
+ webViewTransportNativeMessageSchema,
113
+ webViewTransportWebMessageSchema,
114
+ } from "./webview-transport.js";
103
115
  export {
104
116
  type PublicCommandPayload,
105
117
  parseConfigureOptions,
@@ -1,6 +1,7 @@
1
1
  import { describe, expect, it } from "bun:test";
2
2
  import type { FlowDismissedPayload, FlowViewedPayload } from "./host";
3
3
  import { createCommandEnvelope } from "./host";
4
+ import type { WebViewTransportWebMessage } from "./index";
4
5
  import {
5
6
  appEventCapabilitySchema,
6
7
  appEventFlagSchema,
@@ -18,6 +19,8 @@ import {
18
19
  parseConfigureOptions,
19
20
  parseInitOptions,
20
21
  parsePublicCommand,
22
+ parseWebViewTransportNativeMessage,
23
+ parseWebViewTransportWebMessage,
21
24
  publicGrantScopeIdSchema,
22
25
  publicWidgetCommandKindSchema,
23
26
  SCOPE_IDS,
@@ -161,6 +164,40 @@ describe("@getuserfeedback/protocol root contract", () => {
161
164
  });
162
165
  });
163
166
 
167
+ it("parses public embedded host context and signal commands from the root contract", () => {
168
+ expect(
169
+ parsePublicCommand({
170
+ kind: "updateHostContext",
171
+ context: {
172
+ url: "app://screen/checkout",
173
+ page: { path: "/checkout" },
174
+ env: { locale: "en-US" },
175
+ storage: { cartId: "cart_123" },
176
+ },
177
+ }),
178
+ ).toEqual({
179
+ kind: "updateHostContext",
180
+ context: {
181
+ url: "app://screen/checkout",
182
+ page: { path: "/checkout" },
183
+ env: { locale: "en-US" },
184
+ storage: { cartId: "cart_123" },
185
+ },
186
+ });
187
+
188
+ expect(
189
+ parsePublicCommand({
190
+ kind: "emitHostSignal",
191
+ name: "screenFocused",
192
+ data: { path: "/checkout" },
193
+ }),
194
+ ).toEqual({
195
+ kind: "emitHostSignal",
196
+ name: "screenFocused",
197
+ data: { path: "/checkout" },
198
+ });
199
+ });
200
+
164
201
  it("parses theme resolution payloads with optional runtime artifacts", () => {
165
202
  expect(
166
203
  themeVersionResolutionSchema.parse({
@@ -304,6 +341,192 @@ describe("@getuserfeedback/protocol root contract", () => {
304
341
  ).toThrow();
305
342
  });
306
343
 
344
+ it("parses WebView transport queue and host-event messages from the root contract", () => {
345
+ const envelope = {
346
+ version: "1",
347
+ requestId: "request_123",
348
+ idempotencyKey: "request_123",
349
+ instanceId: "instance_123",
350
+ clientMeta: {
351
+ loader: "custom",
352
+ clientName: "@getuserfeedback/react-native",
353
+ transport: "loader",
354
+ },
355
+ command: {
356
+ kind: "updateHostContext",
357
+ context: { url: "app://screen/checkout" },
358
+ },
359
+ } as const;
360
+
361
+ expect(
362
+ parseWebViewTransportNativeMessage({
363
+ kind: "enqueueCommand",
364
+ id: "webview_message_1",
365
+ envelope,
366
+ }),
367
+ ).toEqual({
368
+ kind: "enqueueCommand",
369
+ id: "webview_message_1",
370
+ envelope,
371
+ });
372
+
373
+ expect(
374
+ parseWebViewTransportWebMessage({
375
+ kind: "hostEvent",
376
+ event: {
377
+ name: "instance:flow:state-changed",
378
+ detail: {
379
+ instanceId: "instance_123",
380
+ isOpen: true,
381
+ isLoading: false,
382
+ width: 320,
383
+ height: 480,
384
+ flowHandleId: "handle_123",
385
+ },
386
+ },
387
+ }),
388
+ ).toEqual({
389
+ kind: "hostEvent",
390
+ event: {
391
+ name: "instance:flow:state-changed",
392
+ detail: {
393
+ instanceId: "instance_123",
394
+ isOpen: true,
395
+ isLoading: false,
396
+ width: 320,
397
+ height: 480,
398
+ flowHandleId: "handle_123",
399
+ },
400
+ },
401
+ });
402
+
403
+ expect(
404
+ parseWebViewTransportWebMessage({
405
+ kind: "hostEvent",
406
+ event: {
407
+ name: "instance:command:settled",
408
+ detail: {
409
+ requestId: "request_123",
410
+ instanceId: "instance_123",
411
+ kind: "updateHostContext",
412
+ ok: true,
413
+ },
414
+ },
415
+ }),
416
+ ).toEqual({
417
+ kind: "hostEvent",
418
+ event: {
419
+ name: "instance:command:settled",
420
+ detail: {
421
+ requestId: "request_123",
422
+ instanceId: "instance_123",
423
+ kind: "updateHostContext",
424
+ ok: true,
425
+ },
426
+ },
427
+ });
428
+
429
+ expect(
430
+ parseWebViewTransportWebMessage({
431
+ kind: "hostEvent",
432
+ event: {
433
+ name: "instance:command:settled",
434
+ detail: {
435
+ requestId: "request_456",
436
+ instanceId: "instance_123",
437
+ kind: "open",
438
+ ok: false,
439
+ error: {
440
+ message: "Command failed",
441
+ code: "COMMAND_FAILED",
442
+ },
443
+ },
444
+ },
445
+ }),
446
+ ).toEqual({
447
+ kind: "hostEvent",
448
+ event: {
449
+ name: "instance:command:settled",
450
+ detail: {
451
+ requestId: "request_456",
452
+ instanceId: "instance_123",
453
+ kind: "open",
454
+ ok: false,
455
+ error: {
456
+ message: "Command failed",
457
+ code: "COMMAND_FAILED",
458
+ },
459
+ },
460
+ },
461
+ });
462
+ });
463
+
464
+ it("rejects WebView transport commands and settlements with invalid state", () => {
465
+ expect(() =>
466
+ parseWebViewTransportNativeMessage({
467
+ kind: "enqueueCommand",
468
+ id: "webview_message_1",
469
+ envelope: {
470
+ version: "1",
471
+ requestId: "request_123",
472
+ idempotencyKey: "request_123",
473
+ command: { kind: "close" },
474
+ },
475
+ }),
476
+ ).toThrow();
477
+
478
+ expect(() =>
479
+ parseWebViewTransportWebMessage({
480
+ kind: "hostEvent",
481
+ event: {
482
+ name: "instance:command:settled",
483
+ detail: {
484
+ requestId: "request_123",
485
+ instanceId: "instance_123",
486
+ kind: "open",
487
+ ok: false,
488
+ },
489
+ },
490
+ }),
491
+ ).toThrow();
492
+
493
+ expect(() =>
494
+ parseWebViewTransportWebMessage({
495
+ kind: "hostEvent",
496
+ event: {
497
+ name: "instance:command:settled",
498
+ detail: {
499
+ requestId: "request_456",
500
+ instanceId: "instance_123",
501
+ kind: "open",
502
+ ok: true,
503
+ error: {
504
+ message: "Should not be present",
505
+ },
506
+ },
507
+ },
508
+ }),
509
+ ).toThrow();
510
+
511
+ const blankReasonMessage = {
512
+ kind: "hostEvent",
513
+ event: {
514
+ name: "instance:handle:invalidated",
515
+ detail: {
516
+ instanceId: "instance_123",
517
+ handleKind: "survey",
518
+ handleId: "handle_123",
519
+ reasonCode: "STALE_HANDLE",
520
+ reasonMessage: " ",
521
+ source: "loader",
522
+ at: 1_735_689_600_000,
523
+ },
524
+ },
525
+ } satisfies WebViewTransportWebMessage;
526
+
527
+ expect(() => parseWebViewTransportWebMessage(blankReasonMessage)).toThrow();
528
+ });
529
+
307
530
  it("parses public API flow and theme version resolution payloads", () => {
308
531
  expect(
309
532
  flowVersionResolutionSchema.parse({ flowVersionId: "fv_abc" }),