@notionhq/custom-blocks-host 0.1.9 → 0.1.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 (29) hide show
  1. package/README.md +19 -13
  2. package/dist/createCustomBlockHost.d.ts.map +1 -1
  3. package/dist/createCustomBlockHost.js +20 -102
  4. package/dist/lifecycle/initializationController.d.ts +8 -2
  5. package/dist/lifecycle/initializationController.d.ts.map +1 -1
  6. package/dist/lifecycle/initializationController.js +32 -23
  7. package/dist/messages/invalidSandboxMessage.js +1 -1
  8. package/dist/messages/sandboxMessageRouter.d.ts +35 -0
  9. package/dist/messages/sandboxMessageRouter.d.ts.map +1 -0
  10. package/dist/messages/sandboxMessageRouter.js +107 -0
  11. package/dist/messages/types.d.ts +1 -1
  12. package/dist/messages/types.d.ts.map +1 -1
  13. package/dist/protocol/index.d.ts +3 -2
  14. package/dist/protocol/index.js +3 -2
  15. package/dist/protocol/messages/isInitHandshakeMessage.d.ts +4 -0
  16. package/dist/protocol/messages/isInitHandshakeMessage.js +11 -0
  17. package/dist/protocol/{messagePayload.d.ts → messages/messagePayload.d.ts} +1 -1
  18. package/dist/queries/types.d.ts +1 -1
  19. package/dist/queries/types.d.ts.map +1 -1
  20. package/package.json +1 -1
  21. package/src/createCustomBlockHost.ts +21 -117
  22. package/src/lifecycle/initializationController.ts +51 -27
  23. package/src/messages/invalidSandboxMessage.ts +1 -1
  24. package/src/messages/sandboxMessageRouter.ts +176 -0
  25. package/src/messages/types.ts +1 -1
  26. package/src/queries/types.ts +1 -1
  27. /package/dist/protocol/{incomingType.d.ts → messages/incomingType.d.ts} +0 -0
  28. /package/dist/protocol/{incomingType.js → messages/incomingType.js} +0 -0
  29. /package/dist/protocol/{messagePayload.js → messages/messagePayload.js} +0 -0
package/README.md CHANGED
@@ -151,19 +151,25 @@ the ID in the `init` message. The host ignores a result with a different ID.
151
151
  If no result arrives, or only a stale valid result arrives, before the timeout,
152
152
  initialization ends with `no_init_result`.
153
153
 
154
- Hosts validate each inbound sandbox message with `sandboxToHostMessageSchema`
155
- before acting on it. If validation fails, the host uses `readIncomingType()` to
156
- identify the message and applies these rules:
157
-
158
- - A malformed `connect` with a readable `initializationId` gets `init.error`
159
- with code `invalid_connect_payload`.
160
- - A malformed `initResult` gets one `invalidSandboxMessage` NACK. The host then
161
- cancels the handshake and fails with the host-only `invalid_init_result_payload`
162
- error. The host does not wait for the `no_init_result` timeout.
163
- - Any other malformed sandbox message gets one `invalidSandboxMessage` NACK.
164
- The sandbox logs this NACK and does not reply.
165
- - Never reply to a sandbox-sent NACK with a host-sent NACK. The host should
166
- not respond to `invalidHostMessage` or `invalidSandboxMessage` messages.
154
+ The host validates each inbound sandbox message with `sandboxToHostMessageSchema`
155
+ before it handles the message. If parsing fails, the host uses `readIncomingType()`
156
+ to identify the attempted message. It replies with `invalidSandboxMessage`, a
157
+ negative acknowledgment (NACK). The host never sends a NACK in response to a NACK.
158
+
159
+ Malformed sandbox messages do not change the host's initialization state or
160
+ timeout, except in these two cases:
161
+
162
+ - The host expects `connect` and receives a malformed `connect` with a readable
163
+ `initializationId`. The host sends `init.error` with `invalid_connect_payload`
164
+ instead of a NACK, then fails initialization.
165
+ - The host expects `initResult` and receives a malformed `initResult` with the
166
+ active `initializationId`. The host sends a NACK and fails initialization with
167
+ `invalid_init_result_payload`. This error code describes a local host failure.
168
+ The host does not send it in `initResult.error`.
169
+
170
+ A malformed `initResult` with a missing or stale ID does not end initialization
171
+ or reset its timeout. Malformed messages do not restart initialization after
172
+ it succeeds or fails.
167
173
 
168
174
  The host accepts a valid `initResult.error` as a terminal initialization
169
175
  failure. It logs and ignores a valid `initResult` with a stale
@@ -1 +1 @@
1
- {"version":3,"file":"createCustomBlockHost.d.ts","sourceRoot":"","sources":["../src/createCustomBlockHost.ts"],"names":[],"mappings":"AAAA,OAAO,EAEN,KAAK,kBAAkB,EACvB,MAAM,8CAA8C,CAAA;AACrD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yCAAyC,CAAA;AACjF,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,8CAA8C,CAAA;AAavF,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,4DAA4D,CAAA;AActG,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gDAAgD,CAAA;AACrF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4CAA4C,CAAA;AAE9E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2CAA2C,CAAA;AAC5E,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gDAAgD,CAAA;AAIhF,OAAO,KAAK,EACX,iCAAiC,EACjC,2BAA2B,EAC3B,MAAM,sBAAsB,CAAA;AAE7B,OAAO,KAAK,EACX,uBAAuB,EACvB,oCAAoC,EACpC,2BAA2B,EAC3B,MAAM,qBAAqB,CAAA;AAU5B,MAAM,MAAM,sBAAsB,GAAG;IACpC,MAAM,EAAE,iBAAiB,CAAA;IACzB,aAAa,EAAE,MAAM,CAAA;IACrB,wBAAwB,EAAE,MAAM,CAAA;IAChC,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,qBAAqB,CAAC,EAAE,MAAM,CAAA;IAC9B,YAAY,EAAE,2BAA2B,CAAA;IACzC,QAAQ,EAAE,uBAAuB,CAAA;IACjC,oHAAoH;IACpH,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,mBAAmB,KAAK,IAAI,CAAA;IACpD,gGAAgG;IAChG,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE;QACvB,QAAQ,EAAE,mBAAmB,CAAA;QAC7B,aAAa,CAAC,EAAE,MAAM,CAAA;KACtB,KAAK,IAAI,CAAA;IACV,6FAA6F;IAC7F,uBAAuB,CAAC,EAAE,CACzB,KAAK,EAAE,oCAAoC,KACvC,IAAI,CAAA;IACT;;;OAGG;IACH,iBAAiB,CAAC,EAAE,MAAM,IAAI,CAAA;IAC9B,KAAK,CAAC,EAAE,CAAC,SAAS,EAAE,2BAA2B,EAAE,OAAO,EAAE,OAAO,KAAK,IAAI,CAAA;CAC1E,CAAA;AAED,MAAM,MAAM,qBAAqB,GAAG;IACnC,IAAI,EAAE,MAAM,IAAI,CAAA;IAChB,IAAI,EAAE,CAAC,OAAO,EAAE,oBAAoB,KAAK,IAAI,CAAA;IAC7C,QAAQ,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAAA;IACtC,eAAe,EAAE,CAAC,YAAY,EAAE,kBAAkB,KAAK,IAAI,CAAA;IAC3D,SAAS,EAAE,CAAC,MAAM,EAAE,YAAY,KAAK,IAAI,CAAA;IACzC,OAAO,EAAE,CAAC,IAAI,EAAE,eAAe,KAAK,IAAI,CAAA;IACxC,cAAc,EAAE,CAAC,WAAW,EAAE,iCAAiC,KAAK,IAAI,CAAA;IACxE,cAAc,EAAE,CAAC,WAAW,EAAE,UAAU,KAAK,IAAI,CAAA;IACjD,YAAY,EAAE,CAAC,IAAI,EAAE;QAAE,YAAY,EAAE,kBAAkB,CAAA;KAAE,KAAK,IAAI,CAAA;CAClE,CAAA;AAMD,wBAAgB,qBAAqB,CACpC,OAAO,EAAE,sBAAsB,GAC7B,qBAAqB,CA+dvB"}
1
+ {"version":3,"file":"createCustomBlockHost.d.ts","sourceRoot":"","sources":["../src/createCustomBlockHost.ts"],"names":[],"mappings":"AAAA,OAAO,EAEN,KAAK,kBAAkB,EACvB,MAAM,8CAA8C,CAAA;AACrD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yCAAyC,CAAA;AACjF,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,8CAA8C,CAAA;AAavF,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,4DAA4D,CAAA;AAatG,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gDAAgD,CAAA;AACrF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4CAA4C,CAAA;AAE9E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2CAA2C,CAAA;AAC5E,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gDAAgD,CAAA;AAGhF,OAAO,KAAK,EACX,iCAAiC,EACjC,2BAA2B,EAC3B,MAAM,sBAAsB,CAAA;AAE7B,OAAO,KAAK,EACX,uBAAuB,EACvB,oCAAoC,EACpC,2BAA2B,EAC3B,MAAM,qBAAqB,CAAA;AAS5B,MAAM,MAAM,sBAAsB,GAAG;IACpC,MAAM,EAAE,iBAAiB,CAAA;IACzB,aAAa,EAAE,MAAM,CAAA;IACrB,wBAAwB,EAAE,MAAM,CAAA;IAChC,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,qBAAqB,CAAC,EAAE,MAAM,CAAA;IAC9B,YAAY,EAAE,2BAA2B,CAAA;IACzC,QAAQ,EAAE,uBAAuB,CAAA;IACjC,oHAAoH;IACpH,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,mBAAmB,KAAK,IAAI,CAAA;IACpD,gGAAgG;IAChG,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE;QACvB,QAAQ,EAAE,mBAAmB,CAAA;QAC7B,aAAa,CAAC,EAAE,MAAM,CAAA;KACtB,KAAK,IAAI,CAAA;IACV,6FAA6F;IAC7F,uBAAuB,CAAC,EAAE,CACzB,KAAK,EAAE,oCAAoC,KACvC,IAAI,CAAA;IACT;;;OAGG;IACH,iBAAiB,CAAC,EAAE,MAAM,IAAI,CAAA;IAC9B,KAAK,CAAC,EAAE,CAAC,SAAS,EAAE,2BAA2B,EAAE,OAAO,EAAE,OAAO,KAAK,IAAI,CAAA;CAC1E,CAAA;AAED,MAAM,MAAM,qBAAqB,GAAG;IACnC,IAAI,EAAE,MAAM,IAAI,CAAA;IAChB,IAAI,EAAE,CAAC,OAAO,EAAE,oBAAoB,KAAK,IAAI,CAAA;IAC7C,QAAQ,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAAA;IACtC,eAAe,EAAE,CAAC,YAAY,EAAE,kBAAkB,KAAK,IAAI,CAAA;IAC3D,SAAS,EAAE,CAAC,MAAM,EAAE,YAAY,KAAK,IAAI,CAAA;IACzC,OAAO,EAAE,CAAC,IAAI,EAAE,eAAe,KAAK,IAAI,CAAA;IACxC,cAAc,EAAE,CAAC,WAAW,EAAE,iCAAiC,KAAK,IAAI,CAAA;IACxE,cAAc,EAAE,CAAC,WAAW,EAAE,UAAU,KAAK,IAAI,CAAA;IACjD,YAAY,EAAE,CAAC,IAAI,EAAE;QAAE,YAAY,EAAE,kBAAkB,CAAA;KAAE,KAAK,IAAI,CAAA;CAClE,CAAA;AAED,wBAAgB,qBAAqB,CACpC,OAAO,EAAE,sBAAsB,GAC7B,qBAAqB,CAsYvB"}
@@ -1,17 +1,11 @@
1
1
  import { DEFAULT_CONTRAST_MODE, } from "./protocol/contrast.js";
2
- import { sandboxToHostMessageSchema } from "./protocol/messages/sandboxToHost.js";
3
2
  import { CUSTOM_BLOCK_BRIDGE_PROTOCOL_VERSION } from "./protocol/protocolVersion.js";
4
- import * as v from "valibot";
5
3
  import { initErrorForFailureReason } from "./lifecycle/initErrors.js";
6
4
  import { createInitializationController } from "./lifecycle/initializationController.js";
7
- import { getInvalidSandboxMessageResponse } from "./messages/invalidSandboxMessage.js";
5
+ import { createSandboxMessageRouter } from "./messages/sandboxMessageRouter.js";
8
6
  import { QuerySubscriptions, } from "./queries/querySubscriptions.js";
9
7
  import { validateQueryDataSourceFilter } from "./queries/validateQueryDataSourceFilter.js";
10
8
  import { validateQueryDataSourceSorts } from "./queries/validateQueryDataSourceSorts.js";
11
- import { unreachable } from "./utils.js";
12
- const initializationIdentitySchema = v.object({
13
- initializationId: v.string(),
14
- });
15
9
  export function createCustomBlockHost(options) {
16
10
  const { iframe, initialState, handlers, onManifest, onInitialized, onInitializationFailure, onRestartRequired, onLog, sandboxOrigin, minBridgeProtocolVersion: configuredMinBridgeProtocolVersion, noConnectTimeoutMs, noInitResultTimeoutMs, } = options;
17
11
  const minBridgeProtocolVersion = Math.max(configuredMinBridgeProtocolVersion ?? CUSTOM_BLOCK_BRIDGE_PROTOCOL_VERSION, CUSTOM_BLOCK_BRIDGE_PROTOCOL_VERSION);
@@ -224,101 +218,25 @@ export function createCustomBlockHost(options) {
224
218
  onRestartRequired,
225
219
  onInitializationFailure,
226
220
  });
227
- function onInvalidSandboxMessage(data, issues) {
228
- emit("warn", {
229
- message: "Ignored unsupported sandbox message",
230
- payload: data,
231
- issues,
232
- });
233
- const response = getInvalidSandboxMessageResponse(data);
234
- const parsedIdentity = v.safeParse(initializationIdentitySchema, data);
235
- const initializationId = parsedIdentity.success
236
- ? parsedIdentity.output.initializationId
237
- : undefined;
238
- if (response.incomingType === "connect") {
239
- if (initializationController.handleInvalidConnect(initializationId)) {
240
- if (initializationId === undefined && response.nack !== undefined) {
241
- post(response.nack);
242
- }
243
- return;
244
- }
245
- }
246
- if (response.incomingType === "initResult" &&
247
- initializationController.handleInvalidInitResult()) {
248
- if (response.nack !== undefined) {
249
- post(response.nack);
250
- }
251
- return;
252
- }
253
- if (response.nack !== undefined) {
254
- post(response.nack);
255
- }
256
- }
257
- function onMessage(event) {
258
- // Ignore messages from windows other than this host's iframe.
259
- if (event.source !== iframe.contentWindow) {
260
- return;
261
- }
262
- // Ignore messages that do not come from the configured sandbox origin.
263
- if (event.origin !== sandboxOrigin) {
264
- return;
265
- }
266
- const parsed = v.safeParse(sandboxToHostMessageSchema, event.data);
267
- if (!parsed.success) {
268
- onInvalidSandboxMessage(event.data, parsed.issues);
269
- return;
270
- }
271
- const message = parsed.output;
272
- emit("in", message);
273
- if (message.type !== "connect" &&
274
- message.type !== "initResult" &&
275
- initializationController.status !== "success") {
276
- // Ignore all non-handshake messages until initialization succeeds.
277
- if (message.type !== "resize") {
278
- emit("warn", {
279
- message: `Ignored ${message.type} before initialization completed`,
280
- payload: message,
281
- });
282
- }
283
- return;
284
- }
285
- switch (message.type) {
286
- case "connect":
287
- initializationController.handleConnect(message);
288
- return;
289
- case "initResult":
290
- initializationController.handleInitResult(message);
291
- return;
292
- case "queryDataSource":
293
- void handleQuery(message);
294
- return;
295
- case "unsubscribeDataSourceQuery":
296
- querySubscriptions.unsubscribe(message.subscriptionId);
297
- return;
298
- case "createPage":
299
- void handleCreatePage(message);
300
- return;
301
- case "getPage":
302
- void handleGetPage(message);
303
- return;
304
- case "getUser":
305
- void handleGetUser(message);
306
- return;
307
- case "listUsers":
308
- void handleListUsers(message);
309
- return;
310
- case "updatePage":
311
- void handleUpdatePage(message);
312
- return;
313
- case "resize":
314
- handlers.resize?.(message);
315
- return;
316
- case "invalidHostMessage":
317
- return;
318
- default:
319
- unreachable(message);
320
- }
321
- }
221
+ const onMessage = createSandboxMessageRouter({
222
+ iframe,
223
+ sandboxOrigin,
224
+ getInitStatus: () => initializationController.status,
225
+ emit,
226
+ post,
227
+ onInvalidConnect: initializationController.handleInvalidConnect,
228
+ onInvalidInitResult: initializationController.handleInvalidInitResult,
229
+ onConnect: initializationController.handleConnect,
230
+ onInitResult: initializationController.handleInitResult,
231
+ onQueryDataSource: handleQuery,
232
+ onUnsubscribeDataSourceQuery: message => querySubscriptions.unsubscribe(message.subscriptionId),
233
+ onCreatePage: handleCreatePage,
234
+ onGetPage: handleGetPage,
235
+ onGetUser: handleGetUser,
236
+ onListUsers: handleListUsers,
237
+ onUpdatePage: handleUpdatePage,
238
+ onResize: message => handlers.resize?.(message),
239
+ });
322
240
  window.addEventListener("message", onMessage);
323
241
  iframe.addEventListener("load", initializationController.onIframeLoad);
324
242
  return {
@@ -45,8 +45,14 @@ export type InitializationController = {
45
45
  readonly hasConnectedSandbox: boolean;
46
46
  handleConnect: (message: ConnectMessage) => void;
47
47
  handleInitResult: (message: InitResultMessage) => void;
48
- handleInvalidConnect: (initializationId?: string) => boolean;
49
- handleInvalidInitResult: () => boolean;
48
+ handleInvalidConnect: (args: {
49
+ initializationId: string;
50
+ error: CustomBlockInitErrorInfo;
51
+ }) => "initErrorSent" | "nack";
52
+ handleInvalidInitResult: (args: {
53
+ initializationId: string | undefined;
54
+ error: CustomBlockInitErrorInfo;
55
+ }) => void;
50
56
  onIframeLoad: () => void;
51
57
  stop: () => void;
52
58
  };
@@ -1 +1 @@
1
- {"version":3,"file":"initializationController.d.ts","sourceRoot":"","sources":["../../src/lifecycle/initializationController.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,8CAA8C,CAAA;AACtF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yCAAyC,CAAA;AAC5E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,8CAA8C,CAAA;AACvF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sDAAsD,CAAA;AAC1F,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,4DAA4D,CAAA;AACtG,OAAO,KAAK,EACX,wBAAwB,EAExB,MAAM,mDAAmD,CAAA;AAC1D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yDAAyD,CAAA;AAChG,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gDAAgD,CAAA;AACrF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4CAA4C,CAAA;AAC9E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2CAA2C,CAAA;AAC5E,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gDAAgD,CAAA;AAChF,OAAO,KAAK,EAAE,oCAAoC,EAAE,MAAM,sBAAsB,CAAA;AAGhF,OAAO,KAAK,EACX,qBAAqB,EACrB,iCAAiC,EACjC,UAAU,EACV,MAAM,YAAY,CAAA;AAKnB,KAAK,mBAAmB,GAAG;IAC1B,KAAK,EAAE,WAAW,CAAA;IAClB,YAAY,EAAE,kBAAkB,CAAA;IAChC,MAAM,EAAE,YAAY,CAAA;IACpB,IAAI,EAAE,eAAe,CAAA;IACrB,WAAW,EAAE,UAAU,CAAA;CACvB,CAAA;AAED,KAAK,yBAAyB,GAC3B;IACA,MAAM,EAAE,SAAS,CAAA;IACjB,QAAQ,EAAE,mBAAmB,CAAA;IAC7B,WAAW,EAAE,iCAAiC,CAAA;CAC7C,GACD;IACA,MAAM,EAAE,OAAO,CAAA;IACf,KAAK,EAAE,wBAAwB,CAAA;CAC9B,CAAA;AAEJ,MAAM,MAAM,+BAA+B,GAAG;IAC7C,OAAO,EAAE,aAAa,CAAA;IACtB,QAAQ,EAAE,MAAM,mBAAmB,CAAA;IACnC,WAAW,EAAE,CAAC,OAAO,EAAE,qBAAqB,KAAK,yBAAyB,CAAA;IAC1E,IAAI,EAAE,CAAC,OAAO,EAAE,oBAAoB,KAAK,IAAI,CAAA;IAC7C,IAAI,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAA;IAChC,4BAA4B,EAAE,MAAM,IAAI,CAAA;IACxC,wBAAwB,EAAE,MAAM,CAAA;IAChC,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,qBAAqB,CAAC,EAAE,MAAM,CAAA;IAC9B,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE,iBAAiB,KAAK,IAAI,CAAA;IACnD,iBAAiB,CAAC,EAAE,MAAM,IAAI,CAAA;IAC9B,uBAAuB,CAAC,EAAE,CACzB,KAAK,EAAE,oCAAoC,KACvC,IAAI,CAAA;CACT,CAAA;AAED,MAAM,MAAM,wBAAwB,GAAG;IACtC,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAA;IAC3B,QAAQ,CAAC,mBAAmB,EAAE,OAAO,CAAA;IACrC,aAAa,EAAE,CAAC,OAAO,EAAE,cAAc,KAAK,IAAI,CAAA;IAChD,gBAAgB,EAAE,CAAC,OAAO,EAAE,iBAAiB,KAAK,IAAI,CAAA;IACtD,oBAAoB,EAAE,CAAC,gBAAgB,CAAC,EAAE,MAAM,KAAK,OAAO,CAAA;IAC5D,uBAAuB,EAAE,MAAM,OAAO,CAAA;IACtC,YAAY,EAAE,MAAM,IAAI,CAAA;IACxB,IAAI,EAAE,MAAM,IAAI,CAAA;CAChB,CAAA;AAED,wBAAgB,8BAA8B,CAC7C,OAAO,EAAE,+BAA+B,GACtC,wBAAwB,CAuO1B"}
1
+ {"version":3,"file":"initializationController.d.ts","sourceRoot":"","sources":["../../src/lifecycle/initializationController.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,8CAA8C,CAAA;AACtF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yCAAyC,CAAA;AAC5E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,8CAA8C,CAAA;AACvF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sDAAsD,CAAA;AAC1F,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,4DAA4D,CAAA;AACtG,OAAO,KAAK,EACX,wBAAwB,EAExB,MAAM,mDAAmD,CAAA;AAC1D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yDAAyD,CAAA;AAChG,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gDAAgD,CAAA;AACrF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4CAA4C,CAAA;AAC9E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2CAA2C,CAAA;AAC5E,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gDAAgD,CAAA;AAChF,OAAO,KAAK,EAAE,oCAAoC,EAAE,MAAM,sBAAsB,CAAA;AAGhF,OAAO,KAAK,EACX,qBAAqB,EACrB,iCAAiC,EACjC,UAAU,EACV,MAAM,YAAY,CAAA;AAKnB,KAAK,mBAAmB,GAAG;IAC1B,KAAK,EAAE,WAAW,CAAA;IAClB,YAAY,EAAE,kBAAkB,CAAA;IAChC,MAAM,EAAE,YAAY,CAAA;IACpB,IAAI,EAAE,eAAe,CAAA;IACrB,WAAW,EAAE,UAAU,CAAA;CACvB,CAAA;AAED,KAAK,yBAAyB,GAC3B;IACA,MAAM,EAAE,SAAS,CAAA;IACjB,QAAQ,EAAE,mBAAmB,CAAA;IAC7B,WAAW,EAAE,iCAAiC,CAAA;CAC7C,GACD;IACA,MAAM,EAAE,OAAO,CAAA;IACf,KAAK,EAAE,wBAAwB,CAAA;CAC9B,CAAA;AAEJ,MAAM,MAAM,+BAA+B,GAAG;IAC7C,OAAO,EAAE,aAAa,CAAA;IACtB,QAAQ,EAAE,MAAM,mBAAmB,CAAA;IACnC,WAAW,EAAE,CAAC,OAAO,EAAE,qBAAqB,KAAK,yBAAyB,CAAA;IAC1E,IAAI,EAAE,CAAC,OAAO,EAAE,oBAAoB,KAAK,IAAI,CAAA;IAC7C,IAAI,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAA;IAChC,4BAA4B,EAAE,MAAM,IAAI,CAAA;IACxC,wBAAwB,EAAE,MAAM,CAAA;IAChC,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,qBAAqB,CAAC,EAAE,MAAM,CAAA;IAC9B,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE,iBAAiB,KAAK,IAAI,CAAA;IACnD,iBAAiB,CAAC,EAAE,MAAM,IAAI,CAAA;IAC9B,uBAAuB,CAAC,EAAE,CACzB,KAAK,EAAE,oCAAoC,KACvC,IAAI,CAAA;CACT,CAAA;AAED,MAAM,MAAM,wBAAwB,GAAG;IACtC,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAA;IAC3B,QAAQ,CAAC,mBAAmB,EAAE,OAAO,CAAA;IACrC,aAAa,EAAE,CAAC,OAAO,EAAE,cAAc,KAAK,IAAI,CAAA;IAChD,gBAAgB,EAAE,CAAC,OAAO,EAAE,iBAAiB,KAAK,IAAI,CAAA;IACtD,oBAAoB,EAAE,CAAC,IAAI,EAAE;QAC5B,gBAAgB,EAAE,MAAM,CAAA;QACxB,KAAK,EAAE,wBAAwB,CAAA;KAC/B,KAAK,eAAe,GAAG,MAAM,CAAA;IAC9B,uBAAuB,EAAE,CAAC,IAAI,EAAE;QAC/B,gBAAgB,EAAE,MAAM,GAAG,SAAS,CAAA;QACpC,KAAK,EAAE,wBAAwB,CAAA;KAC/B,KAAK,IAAI,CAAA;IACV,YAAY,EAAE,MAAM,IAAI,CAAA;IACxB,IAAI,EAAE,MAAM,IAAI,CAAA;CAChB,CAAA;AAED,wBAAgB,8BAA8B,CAC7C,OAAO,EAAE,+BAA+B,GACtC,wBAAwB,CAyP1B"}
@@ -21,7 +21,8 @@ export function createInitializationController(options) {
21
21
  noInitResultTimeoutId = undefined;
22
22
  }
23
23
  }
24
- function sendInitError(initializationId, error) {
24
+ function sendInitError(args) {
25
+ const { initializationId, error } = args;
25
26
  if (initStatus !== "waitingForConnect") {
26
27
  return;
27
28
  }
@@ -42,7 +43,10 @@ export function createInitializationController(options) {
42
43
  }
43
44
  const preparation = prepareInit(connect);
44
45
  if (preparation.status === "error") {
45
- sendInitError(connect.initializationId, preparation.error);
46
+ sendInitError({
47
+ initializationId: connect.initializationId,
48
+ error: preparation.error,
49
+ });
46
50
  return false;
47
51
  }
48
52
  initStatus = "waitingForInitResult";
@@ -96,7 +100,10 @@ export function createInitializationController(options) {
96
100
  const initError = initErrorForFailureReason("invalid_protocol_version", {
97
101
  currentBridgeProtocolVersion: message.bridgeProtocolVersion,
98
102
  });
99
- sendInitError(message.initializationId, initError);
103
+ sendInitError({
104
+ initializationId: message.initializationId,
105
+ error: initError,
106
+ });
100
107
  return;
101
108
  }
102
109
  if (message.bridgeProtocolVersion < minBridgeProtocolVersion) {
@@ -104,44 +111,46 @@ export function createInitializationController(options) {
104
111
  currentBridgeProtocolVersion: message.bridgeProtocolVersion,
105
112
  minBridgeProtocolVersion,
106
113
  });
107
- sendInitError(message.initializationId, initError);
114
+ sendInitError({
115
+ initializationId: message.initializationId,
116
+ error: initError,
117
+ });
108
118
  return;
109
119
  }
110
120
  // If the sandbox reported an error during `connect`, return it as an `init` error.
111
121
  if (message.status === "error") {
112
- sendInitError(message.initializationId, message.error);
122
+ sendInitError({
123
+ initializationId: message.initializationId,
124
+ error: message.error,
125
+ });
113
126
  return;
114
127
  }
115
128
  if (sendInit(message)) {
116
129
  hasConnectedSandbox = true;
117
130
  }
118
131
  }
119
- function handleInvalidConnect(initializationId) {
132
+ function handleInvalidConnect(args) {
133
+ const { initializationId, error } = args;
120
134
  if (initStatus !== "waitingForConnect") {
121
- return false;
135
+ // `connect` messages received when not waiting for one are NACKed like generic invalid messages.
136
+ return "nack";
122
137
  }
123
- const error = initErrorForFailureReason("invalid_connect_payload");
124
- if (initializationId === undefined) {
125
- initStatus = "error";
126
- clearNoConnectTimeout();
127
- warn(error);
128
- onInitializationFailure?.(error);
129
- }
130
- else {
131
- sendInitError(initializationId, error);
132
- }
133
- return true;
138
+ // The sandbox awaits `init`. Reply with `init.error` using its initialization ID.
139
+ sendInitError({ initializationId, error });
140
+ return "initErrorSent";
134
141
  }
135
- function handleInvalidInitResult() {
136
- if (initStatus !== "waitingForInitResult") {
137
- return false;
142
+ function handleInvalidInitResult(args) {
143
+ const { initializationId, error } = args;
144
+ if (initStatus !== "waitingForInitResult" ||
145
+ initializationId === undefined ||
146
+ initializationId !== activeInitializationId) {
147
+ return;
138
148
  }
149
+ clearNoConnectTimeout();
139
150
  clearNoInitResultTimeout();
140
151
  initStatus = "error";
141
- const error = initErrorForFailureReason("invalid_init_result_payload");
142
152
  warn(error);
143
153
  onInitializationFailure?.(error);
144
- return true;
145
154
  }
146
155
  function onIframeLoad() {
147
156
  clearNoConnectTimeout();
@@ -1,4 +1,4 @@
1
- import { readIncomingType } from "../protocol/incomingType.js";
1
+ import { readIncomingType } from "../protocol/messages/incomingType.js";
2
2
  export function getInvalidSandboxMessageResponse(data) {
3
3
  const incomingType = readIncomingType(data);
4
4
  if (incomingType === "invalidHostMessage" ||
@@ -0,0 +1,35 @@
1
+ import type { ConnectMessage } from "../protocol/messages/connect.js";
2
+ import type { CreatePageMessage } from "../protocol/messages/createPage.js";
3
+ import type { GetPageMessage } from "../protocol/messages/getPage.js";
4
+ import type { GetUserMessage } from "../protocol/messages/getUser.js";
5
+ import type { HostToSandboxMessage } from "../protocol/messages/hostToSandbox.js";
6
+ import type { InitResultMessage } from "../protocol/messages/initResult.js";
7
+ import type { ListUsersMessage } from "../protocol/messages/listUsers.js";
8
+ import type { QueryDataSourceMessage } from "../protocol/messages/queryDataSource.js";
9
+ import type { ResizeMessage } from "../protocol/messages/resize.js";
10
+ import type { UnsubscribeDataSourceQueryMessage } from "../protocol/messages/unsubscribeDataSourceQuery.js";
11
+ import type { UpdatePageMessage } from "../protocol/messages/updatePage.js";
12
+ import type { InitializationController } from "../lifecycle/initializationController.js";
13
+ import type { InitStatus } from "../lifecycle/types.js";
14
+ import type { CustomBlockHostLogDirection } from "./types.js";
15
+ export type SandboxMessageRouterOptions = {
16
+ iframe: HTMLIFrameElement;
17
+ sandboxOrigin: string;
18
+ getInitStatus: () => InitStatus;
19
+ emit: (direction: CustomBlockHostLogDirection, payload: unknown) => void;
20
+ post: (message: HostToSandboxMessage) => void;
21
+ onInvalidConnect: InitializationController["handleInvalidConnect"];
22
+ onInvalidInitResult: InitializationController["handleInvalidInitResult"];
23
+ onConnect: (message: ConnectMessage) => void;
24
+ onInitResult: (message: InitResultMessage) => void;
25
+ onQueryDataSource: (message: QueryDataSourceMessage) => void | Promise<void>;
26
+ onUnsubscribeDataSourceQuery: (message: UnsubscribeDataSourceQueryMessage) => void;
27
+ onCreatePage: (message: CreatePageMessage) => void | Promise<void>;
28
+ onGetPage: (message: GetPageMessage) => void | Promise<void>;
29
+ onGetUser: (message: GetUserMessage) => void | Promise<void>;
30
+ onListUsers: (message: ListUsersMessage) => void | Promise<void>;
31
+ onUpdatePage: (message: UpdatePageMessage) => void | Promise<void>;
32
+ onResize: (message: ResizeMessage) => void;
33
+ };
34
+ export declare function createSandboxMessageRouter(options: SandboxMessageRouterOptions): (event: MessageEvent) => void;
35
+ //# sourceMappingURL=sandboxMessageRouter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sandboxMessageRouter.d.ts","sourceRoot":"","sources":["../../src/messages/sandboxMessageRouter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sDAAsD,CAAA;AAC1F,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yDAAyD,CAAA;AAChG,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sDAAsD,CAAA;AAC1F,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sDAAsD,CAAA;AAC1F,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,4DAA4D,CAAA;AACtG,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yDAAyD,CAAA;AAEhG,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wDAAwD,CAAA;AAC9F,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,8DAA8D,CAAA;AAC1G,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qDAAqD,CAAA;AAExF,OAAO,KAAK,EAAE,iCAAiC,EAAE,MAAM,yEAAyE,CAAA;AAChI,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yDAAyD,CAAA;AAGhG,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,0CAA0C,CAAA;AACxF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAA;AAGvD,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,YAAY,CAAA;AAM7D,MAAM,MAAM,2BAA2B,GAAG;IACzC,MAAM,EAAE,iBAAiB,CAAA;IACzB,aAAa,EAAE,MAAM,CAAA;IACrB,aAAa,EAAE,MAAM,UAAU,CAAA;IAC/B,IAAI,EAAE,CAAC,SAAS,EAAE,2BAA2B,EAAE,OAAO,EAAE,OAAO,KAAK,IAAI,CAAA;IACxE,IAAI,EAAE,CAAC,OAAO,EAAE,oBAAoB,KAAK,IAAI,CAAA;IAC7C,gBAAgB,EAAE,wBAAwB,CAAC,sBAAsB,CAAC,CAAA;IAClE,mBAAmB,EAAE,wBAAwB,CAAC,yBAAyB,CAAC,CAAA;IACxE,SAAS,EAAE,CAAC,OAAO,EAAE,cAAc,KAAK,IAAI,CAAA;IAC5C,YAAY,EAAE,CAAC,OAAO,EAAE,iBAAiB,KAAK,IAAI,CAAA;IAClD,iBAAiB,EAAE,CAAC,OAAO,EAAE,sBAAsB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC5E,4BAA4B,EAAE,CAC7B,OAAO,EAAE,iCAAiC,KACtC,IAAI,CAAA;IACT,YAAY,EAAE,CAAC,OAAO,EAAE,iBAAiB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAClE,SAAS,EAAE,CAAC,OAAO,EAAE,cAAc,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC5D,SAAS,EAAE,CAAC,OAAO,EAAE,cAAc,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC5D,WAAW,EAAE,CAAC,OAAO,EAAE,gBAAgB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAChE,YAAY,EAAE,CAAC,OAAO,EAAE,iBAAiB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAClE,QAAQ,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,IAAI,CAAA;CAC1C,CAAA;AAED,wBAAgB,0BAA0B,CACzC,OAAO,EAAE,2BAA2B,GAClC,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CA8H/B"}
@@ -0,0 +1,107 @@
1
+ import { isInitHandshakeMessage } from "../protocol/messages/isInitHandshakeMessage.js";
2
+ import { sandboxToHostMessageSchema } from "../protocol/messages/sandboxToHost.js";
3
+ import * as v from "valibot";
4
+ import { initErrorForFailureReason } from "../lifecycle/initErrors.js";
5
+ import { unreachable } from "../utils.js";
6
+ import { getInvalidSandboxMessageResponse } from "./invalidSandboxMessage.js";
7
+ const initializationIdentitySchema = v.object({
8
+ initializationId: v.string(),
9
+ });
10
+ export function createSandboxMessageRouter(options) {
11
+ const { iframe, sandboxOrigin, getInitStatus, emit, post, onInvalidConnect, onInvalidInitResult, onConnect, onInitResult, onQueryDataSource, onUnsubscribeDataSourceQuery, onCreatePage, onGetPage, onGetUser, onListUsers, onUpdatePage, onResize, } = options;
12
+ function onInvalidSandboxMessage(data, issues) {
13
+ emit("warn", {
14
+ message: "Ignored unsupported sandbox message",
15
+ payload: data,
16
+ issues,
17
+ });
18
+ const response = getInvalidSandboxMessageResponse(data);
19
+ if (response.incomingType === "connect") {
20
+ const parsedIdentity = v.safeParse(initializationIdentitySchema, data);
21
+ if (parsedIdentity.success) {
22
+ const responseType = onInvalidConnect({
23
+ initializationId: parsedIdentity.output.initializationId,
24
+ error: initErrorForFailureReason("invalid_connect_payload"),
25
+ });
26
+ if (responseType === "initErrorSent") {
27
+ // The controller already sent `init.error`, so no need to also send a NACK.
28
+ return;
29
+ }
30
+ }
31
+ }
32
+ if (response.incomingType === "initResult") {
33
+ const parsedIdentity = v.safeParse(initializationIdentitySchema, data);
34
+ onInvalidInitResult({
35
+ initializationId: parsedIdentity.success
36
+ ? parsedIdentity.output.initializationId
37
+ : undefined,
38
+ error: initErrorForFailureReason("invalid_init_result_payload"),
39
+ });
40
+ }
41
+ // Other messages get NACKed indicating they were invalid and skipped
42
+ if (response.nack !== undefined) {
43
+ post(response.nack);
44
+ }
45
+ }
46
+ return function onMessage(event) {
47
+ // Verify the sender is this host's iframe, not another window with the same origin.
48
+ const isExpectedSource = event.source === iframe.contentWindow;
49
+ // Verify the sender's origin, since the iframe can navigate to another origin.
50
+ const isExpectedOrigin = event.origin === sandboxOrigin;
51
+ if (!isExpectedSource || !isExpectedOrigin) {
52
+ return;
53
+ }
54
+ // Parse and validate the message from the sandbox.
55
+ const parsed = v.safeParse(sandboxToHostMessageSchema, event.data);
56
+ if (!parsed.success) {
57
+ onInvalidSandboxMessage(event.data, parsed.issues);
58
+ return;
59
+ }
60
+ const message = parsed.output;
61
+ if (!isInitHandshakeMessage(message) && getInitStatus() !== "success") {
62
+ emit("warn", {
63
+ message: `Ignored ${message.type} before initialization completed`,
64
+ payload: message,
65
+ });
66
+ return;
67
+ }
68
+ emit("in", message);
69
+ switch (message.type) {
70
+ case "connect":
71
+ onConnect(message);
72
+ return;
73
+ case "initResult":
74
+ onInitResult(message);
75
+ return;
76
+ case "queryDataSource":
77
+ void onQueryDataSource(message);
78
+ return;
79
+ case "unsubscribeDataSourceQuery":
80
+ onUnsubscribeDataSourceQuery(message);
81
+ return;
82
+ case "createPage":
83
+ void onCreatePage(message);
84
+ return;
85
+ case "getPage":
86
+ void onGetPage(message);
87
+ return;
88
+ case "getUser":
89
+ void onGetUser(message);
90
+ return;
91
+ case "listUsers":
92
+ void onListUsers(message);
93
+ return;
94
+ case "updatePage":
95
+ void onUpdatePage(message);
96
+ return;
97
+ case "resize":
98
+ onResize(message);
99
+ return;
100
+ case "invalidHostMessage":
101
+ // The inbound log records this rejection. Do not reply, to avoid an error-response loop.
102
+ return;
103
+ default:
104
+ unreachable(message);
105
+ }
106
+ };
107
+ }
@@ -1,4 +1,3 @@
1
- import type { BridgeMessagePayload } from "../protocol/messagePayload.js";
2
1
  import type { CreatePageMessage } from "../protocol/messages/createPage.js";
3
2
  import type { CreatePageResultMessage, CustomBlockCreatePageErrorInfo } from "../protocol/messages/createPageResult.js";
4
3
  import type { CustomBlockGetPageErrorInfo, GetPageMessage, GetPageResultMessage } from "../protocol/messages/getPage.js";
@@ -6,6 +5,7 @@ import type { CustomBlockGetUserErrorInfo, GetUserMessage, GetUserResultMessage
6
5
  import type { CustomBlockInitErrorInfo } from "../protocol/messages/init.js";
7
6
  import type { CustomBlockInitResultErrorInfo } from "../protocol/messages/initResult.js";
8
7
  import type { CustomBlockListUsersErrorInfo, ListUsersMessage, ListUsersResultMessage } from "../protocol/messages/listUsers.js";
8
+ import type { BridgeMessagePayload } from "../protocol/messages/messagePayload.js";
9
9
  import type { QueryDataSourceMessage } from "../protocol/messages/queryDataSource.js";
10
10
  import type { ResizeMessage } from "../protocol/messages/resize.js";
11
11
  import type { UpdatePageMessage } from "../protocol/messages/updatePage.js";
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/messages/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,oDAAoD,CAAA;AAC9F,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yDAAyD,CAAA;AAChG,OAAO,KAAK,EACX,uBAAuB,EACvB,8BAA8B,EAC9B,MAAM,+DAA+D,CAAA;AACtE,OAAO,KAAK,EACX,2BAA2B,EAC3B,cAAc,EACd,oBAAoB,EACpB,MAAM,sDAAsD,CAAA;AAC7D,OAAO,KAAK,EACX,2BAA2B,EAC3B,cAAc,EACd,oBAAoB,EACpB,MAAM,sDAAsD,CAAA;AAC7D,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,mDAAmD,CAAA;AACjG,OAAO,KAAK,EAAE,8BAA8B,EAAE,MAAM,yDAAyD,CAAA;AAC7G,OAAO,KAAK,EACX,6BAA6B,EAC7B,gBAAgB,EAChB,sBAAsB,EACtB,MAAM,wDAAwD,CAAA;AAC/D,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,8DAA8D,CAAA;AAC1G,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qDAAqD,CAAA;AACxF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yDAAyD,CAAA;AAChG,OAAO,KAAK,EACX,8BAA8B,EAC9B,uBAAuB,EACvB,MAAM,+DAA+D,CAAA;AACtE,OAAO,KAAK,EAAE,oCAAoC,EAAE,MAAM,qBAAqB,CAAA;AAE/E,KAAK,YAAY,CAAC,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAA;AAErC,MAAM,MAAM,+BAA+B,GAAG,oBAAoB,CACjE,uBAAuB,EACvB,8BAA8B,CAC9B,CAAA;AAED,MAAM,MAAM,4BAA4B,GAAG,oBAAoB,CAC9D,oBAAoB,EACpB,2BAA2B,CAC3B,CAAA;AAED,MAAM,MAAM,+BAA+B,GAAG,oBAAoB,CACjE,uBAAuB,EACvB,8BAA8B,CAC9B,CAAA;AAED,MAAM,MAAM,4BAA4B,GAAG,oBAAoB,CAC9D,oBAAoB,EACpB,2BAA2B,CAC3B,CAAA;AAED,MAAM,MAAM,8BAA8B,GAAG,oBAAoB,CAChE,sBAAsB,EACtB,6BAA6B,CAC7B,CAAA;AAED,MAAM,MAAM,uBAAuB,GAAG;IACrC,eAAe,EAAE,CAChB,OAAO,EAAE,sBAAsB,KAC3B,YAAY,CAAC,oCAAoC,CAAC,CAAA;IACvD,UAAU,CAAC,EAAE,CACZ,OAAO,EAAE,iBAAiB,KACtB,YAAY,CAAC,+BAA+B,CAAC,CAAA;IAClD,OAAO,CAAC,EAAE,CACT,OAAO,EAAE,cAAc,KACnB,YAAY,CAAC,4BAA4B,CAAC,CAAA;IAC/C,OAAO,CAAC,EAAE,CACT,OAAO,EAAE,cAAc,KACnB,YAAY,CAAC,4BAA4B,CAAC,CAAA;IAC/C,SAAS,CAAC,EAAE,CACX,OAAO,EAAE,gBAAgB,KACrB,YAAY,CAAC,8BAA8B,CAAC,CAAA;IACjD,UAAU,CAAC,EAAE,CACZ,OAAO,EAAE,iBAAiB,KACtB,YAAY,CAAC,+BAA+B,CAAC,CAAA;IAClD,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,IAAI,CAAA;CACzC,CAAA;AAED,MAAM,MAAM,oCAAoC,GAC7C,wBAAwB,GACxB,8BAA8B,CAAA;AAEjC,MAAM,MAAM,2BAA2B,GAAG,IAAI,GAAG,KAAK,GAAG,MAAM,CAAA"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/messages/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yDAAyD,CAAA;AAChG,OAAO,KAAK,EACX,uBAAuB,EACvB,8BAA8B,EAC9B,MAAM,+DAA+D,CAAA;AACtE,OAAO,KAAK,EACX,2BAA2B,EAC3B,cAAc,EACd,oBAAoB,EACpB,MAAM,sDAAsD,CAAA;AAC7D,OAAO,KAAK,EACX,2BAA2B,EAC3B,cAAc,EACd,oBAAoB,EACpB,MAAM,sDAAsD,CAAA;AAC7D,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,mDAAmD,CAAA;AACjG,OAAO,KAAK,EAAE,8BAA8B,EAAE,MAAM,yDAAyD,CAAA;AAC7G,OAAO,KAAK,EACX,6BAA6B,EAC7B,gBAAgB,EAChB,sBAAsB,EACtB,MAAM,wDAAwD,CAAA;AAC/D,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,6DAA6D,CAAA;AACvG,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,8DAA8D,CAAA;AAC1G,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qDAAqD,CAAA;AACxF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yDAAyD,CAAA;AAChG,OAAO,KAAK,EACX,8BAA8B,EAC9B,uBAAuB,EACvB,MAAM,+DAA+D,CAAA;AACtE,OAAO,KAAK,EAAE,oCAAoC,EAAE,MAAM,qBAAqB,CAAA;AAE/E,KAAK,YAAY,CAAC,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAA;AAErC,MAAM,MAAM,+BAA+B,GAAG,oBAAoB,CACjE,uBAAuB,EACvB,8BAA8B,CAC9B,CAAA;AAED,MAAM,MAAM,4BAA4B,GAAG,oBAAoB,CAC9D,oBAAoB,EACpB,2BAA2B,CAC3B,CAAA;AAED,MAAM,MAAM,+BAA+B,GAAG,oBAAoB,CACjE,uBAAuB,EACvB,8BAA8B,CAC9B,CAAA;AAED,MAAM,MAAM,4BAA4B,GAAG,oBAAoB,CAC9D,oBAAoB,EACpB,2BAA2B,CAC3B,CAAA;AAED,MAAM,MAAM,8BAA8B,GAAG,oBAAoB,CAChE,sBAAsB,EACtB,6BAA6B,CAC7B,CAAA;AAED,MAAM,MAAM,uBAAuB,GAAG;IACrC,eAAe,EAAE,CAChB,OAAO,EAAE,sBAAsB,KAC3B,YAAY,CAAC,oCAAoC,CAAC,CAAA;IACvD,UAAU,CAAC,EAAE,CACZ,OAAO,EAAE,iBAAiB,KACtB,YAAY,CAAC,+BAA+B,CAAC,CAAA;IAClD,OAAO,CAAC,EAAE,CACT,OAAO,EAAE,cAAc,KACnB,YAAY,CAAC,4BAA4B,CAAC,CAAA;IAC/C,OAAO,CAAC,EAAE,CACT,OAAO,EAAE,cAAc,KACnB,YAAY,CAAC,4BAA4B,CAAC,CAAA;IAC/C,SAAS,CAAC,EAAE,CACX,OAAO,EAAE,gBAAgB,KACrB,YAAY,CAAC,8BAA8B,CAAC,CAAA;IACjD,UAAU,CAAC,EAAE,CACZ,OAAO,EAAE,iBAAiB,KACtB,YAAY,CAAC,+BAA+B,CAAC,CAAA;IAClD,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,IAAI,CAAA;CACzC,CAAA;AAED,MAAM,MAAM,oCAAoC,GAC7C,wBAAwB,GACxB,8BAA8B,CAAA;AAEjC,MAAM,MAAM,2BAA2B,GAAG,IAAI,GAAG,KAAK,GAAG,MAAM,CAAA"}
@@ -15,9 +15,7 @@ export * from "./dataSources/propertySchema.js";
15
15
  export * from "./dataSources/recordPointer.js";
16
16
  export * from "./errors.js";
17
17
  export * from "./ids.js";
18
- export * from "./incomingType.js";
19
18
  export * from "./manifest.js";
20
- export * from "./messagePayload.js";
21
19
  export * from "./messages/connect.js";
22
20
  export * from "./messages/contrastModeChanged.js";
23
21
  export * from "./messages/createPage.js";
@@ -27,11 +25,14 @@ export * from "./messages/dataSourcesChanged.js";
27
25
  export * from "./messages/getPage.js";
28
26
  export * from "./messages/getUser.js";
29
27
  export * from "./messages/hostToSandbox.js";
28
+ export * from "./messages/incomingType.js";
30
29
  export * from "./messages/init.js";
31
30
  export * from "./messages/initResult.js";
32
31
  export * from "./messages/invalidHostMessage.js";
33
32
  export * from "./messages/invalidSandboxMessage.js";
33
+ export * from "./messages/isInitHandshakeMessage.js";
34
34
  export * from "./messages/listUsers.js";
35
+ export * from "./messages/messagePayload.js";
35
36
  export * from "./messages/pageChanged.js";
36
37
  export * from "./messages/parentChanged.js";
37
38
  export * from "./messages/queryDataSource.js";
@@ -15,9 +15,7 @@ export * from "./dataSources/propertySchema.js";
15
15
  export * from "./dataSources/recordPointer.js";
16
16
  export * from "./errors.js";
17
17
  export * from "./ids.js";
18
- export * from "./incomingType.js";
19
18
  export * from "./manifest.js";
20
- export * from "./messagePayload.js";
21
19
  export * from "./messages/connect.js";
22
20
  export * from "./messages/contrastModeChanged.js";
23
21
  export * from "./messages/createPage.js";
@@ -27,11 +25,14 @@ export * from "./messages/dataSourcesChanged.js";
27
25
  export * from "./messages/getPage.js";
28
26
  export * from "./messages/getUser.js";
29
27
  export * from "./messages/hostToSandbox.js";
28
+ export * from "./messages/incomingType.js";
30
29
  export * from "./messages/init.js";
31
30
  export * from "./messages/initResult.js";
32
31
  export * from "./messages/invalidHostMessage.js";
33
32
  export * from "./messages/invalidSandboxMessage.js";
33
+ export * from "./messages/isInitHandshakeMessage.js";
34
34
  export * from "./messages/listUsers.js";
35
+ export * from "./messages/messagePayload.js";
35
36
  export * from "./messages/pageChanged.js";
36
37
  export * from "./messages/parentChanged.js";
37
38
  export * from "./messages/queryDataSource.js";
@@ -0,0 +1,4 @@
1
+ import type { HostToSandboxMessage } from "./hostToSandbox.js";
2
+ import type { SandboxToHostMessage } from "./sandboxToHost.js";
3
+ /** Returns whether a message belongs to the initialization handshake. */
4
+ export declare function isInitHandshakeMessage(message: SandboxToHostMessage | HostToSandboxMessage): boolean;
@@ -0,0 +1,11 @@
1
+ /** Returns whether a message belongs to the initialization handshake. */
2
+ export function isInitHandshakeMessage(message) {
3
+ switch (message.type) {
4
+ case "connect":
5
+ case "init":
6
+ case "initResult":
7
+ return true;
8
+ default:
9
+ return false;
10
+ }
11
+ }
@@ -1,4 +1,4 @@
1
- import type { CustomBlockErrorInfo } from "./errors.js";
1
+ import type { CustomBlockErrorInfo } from "../errors.js";
2
2
  type WithTypedErrorInfo<T, TErrorInfo extends CustomBlockErrorInfo | undefined> = TErrorInfo extends CustomBlockErrorInfo ? T extends {
3
3
  error: CustomBlockErrorInfo;
4
4
  } ? Omit<T, "error"> & {
@@ -1,4 +1,4 @@
1
- import type { BridgeMessagePayload } from "../protocol/messagePayload.js";
1
+ import type { BridgeMessagePayload } from "../protocol/messages/messagePayload.js";
2
2
  import type { CustomBlockQueryDataSourceErrorInfo, QueryDataSourceResultMessage } from "../protocol/messages/queryDataSourceResult.js";
3
3
  export type CustomBlockHostQueryDataSourceResult = BridgeMessagePayload<QueryDataSourceResultMessage, CustomBlockQueryDataSourceErrorInfo>;
4
4
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/queries/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,oDAAoD,CAAA;AAC9F,OAAO,KAAK,EACX,mCAAmC,EACnC,4BAA4B,EAC5B,MAAM,oEAAoE,CAAA;AAE3E,MAAM,MAAM,oCAAoC,GAAG,oBAAoB,CACtE,4BAA4B,EAC5B,mCAAmC,CACnC,CAAA"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/queries/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,6DAA6D,CAAA;AACvG,OAAO,KAAK,EACX,mCAAmC,EACnC,4BAA4B,EAC5B,MAAM,oEAAoE,CAAA;AAE3E,MAAM,MAAM,oCAAoC,GAAG,oBAAoB,CACtE,4BAA4B,EAC5B,mCAAmC,CACnC,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@notionhq/custom-blocks-host",
3
- "version": "0.1.9",
3
+ "version": "0.1.10",
4
4
  "description": "Reusable host runtime for Notion custom blocks.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -26,7 +26,6 @@ import type { PageChangedMessage } from "@notionhq/custom-blocks-protocol/messag
26
26
  import type { ParentChangedMessage } from "@notionhq/custom-blocks-protocol/messages/parentChanged.js"
27
27
  import type { QueryDataSourceMessage } from "@notionhq/custom-blocks-protocol/messages/queryDataSource.js"
28
28
  import type { QueryDataSourceResultMessage } from "@notionhq/custom-blocks-protocol/messages/queryDataSourceResult.js"
29
- import { sandboxToHostMessageSchema } from "@notionhq/custom-blocks-protocol/messages/sandboxToHost.js"
30
29
  import type { ThemeChangedMessage } from "@notionhq/custom-blocks-protocol/messages/themeChanged.js"
31
30
  import type { UpdatePageMessage } from "@notionhq/custom-blocks-protocol/messages/updatePage.js"
32
31
  import type { UpdatePageResultMessage } from "@notionhq/custom-blocks-protocol/messages/updatePageResult.js"
@@ -35,14 +34,13 @@ import type { NotionParent } from "@notionhq/custom-blocks-protocol/parent.js"
35
34
  import { CUSTOM_BLOCK_BRIDGE_PROTOCOL_VERSION } from "@notionhq/custom-blocks-protocol/protocolVersion.js"
36
35
  import type { NotionTheme } from "@notionhq/custom-blocks-protocol/theme.js"
37
36
  import type { NotionUser } from "@notionhq/custom-blocks-protocol/users/user.js"
38
- import * as v from "valibot"
39
37
  import { initErrorForFailureReason } from "./lifecycle/initErrors.js"
40
38
  import { createInitializationController } from "./lifecycle/initializationController.js"
41
39
  import type {
42
40
  CustomBlockHostDataSourcesPayload,
43
41
  CustomBlockHostInitialState,
44
42
  } from "./lifecycle/types.js"
45
- import { getInvalidSandboxMessageResponse } from "./messages/invalidSandboxMessage.js"
43
+ import { createSandboxMessageRouter } from "./messages/sandboxMessageRouter.js"
46
44
  import type {
47
45
  CustomBlockHostHandlers,
48
46
  CustomBlockHostInitializationFailure,
@@ -55,7 +53,6 @@ import {
55
53
  import type { CustomBlockHostQueryDataSourceResult } from "./queries/types.js"
56
54
  import { validateQueryDataSourceFilter } from "./queries/validateQueryDataSourceFilter.js"
57
55
  import { validateQueryDataSourceSorts } from "./queries/validateQueryDataSourceSorts.js"
58
- import { unreachable } from "./utils.js"
59
56
 
60
57
  export type CustomBlockHostOptions = {
61
58
  iframe: HTMLIFrameElement
@@ -96,10 +93,6 @@ export type CustomBlockHostHandle = {
96
93
  refreshQuery: (args: { dataSourceId: NotionDataSourceId }) => void
97
94
  }
98
95
 
99
- const initializationIdentitySchema = v.object({
100
- initializationId: v.string(),
101
- })
102
-
103
96
  export function createCustomBlockHost(
104
97
  options: CustomBlockHostOptions,
105
98
  ): CustomBlockHostHandle {
@@ -363,115 +356,26 @@ export function createCustomBlockHost(
363
356
  onInitializationFailure,
364
357
  })
365
358
 
366
- function onInvalidSandboxMessage(
367
- data: unknown,
368
- issues: readonly v.BaseIssue<unknown>[],
369
- ) {
370
- emit("warn", {
371
- message: "Ignored unsupported sandbox message",
372
- payload: data,
373
- issues,
374
- })
375
- const response = getInvalidSandboxMessageResponse(data)
376
-
377
- const parsedIdentity = v.safeParse(initializationIdentitySchema, data)
378
- const initializationId = parsedIdentity.success
379
- ? parsedIdentity.output.initializationId
380
- : undefined
381
- if (response.incomingType === "connect") {
382
- if (initializationController.handleInvalidConnect(initializationId)) {
383
- if (initializationId === undefined && response.nack !== undefined) {
384
- post(response.nack)
385
- }
386
- return
387
- }
388
- }
389
- if (
390
- response.incomingType === "initResult" &&
391
- initializationController.handleInvalidInitResult()
392
- ) {
393
- if (response.nack !== undefined) {
394
- post(response.nack)
395
- }
396
- return
397
- }
398
- if (response.nack !== undefined) {
399
- post(response.nack)
400
- }
401
- }
402
-
403
- function onMessage(event: MessageEvent) {
404
- // Ignore messages from windows other than this host's iframe.
405
- if (event.source !== iframe.contentWindow) {
406
- return
407
- }
408
-
409
- // Ignore messages that do not come from the configured sandbox origin.
410
- if (event.origin !== sandboxOrigin) {
411
- return
412
- }
413
-
414
- const parsed = v.safeParse(sandboxToHostMessageSchema, event.data)
415
- if (!parsed.success) {
416
- onInvalidSandboxMessage(event.data, parsed.issues)
417
- return
418
- }
419
-
420
- const message = parsed.output
421
- emit("in", message)
422
-
423
- if (
424
- message.type !== "connect" &&
425
- message.type !== "initResult" &&
426
- initializationController.status !== "success"
427
- ) {
428
- // Ignore all non-handshake messages until initialization succeeds.
429
- if (message.type !== "resize") {
430
- emit("warn", {
431
- message: `Ignored ${message.type} before initialization completed`,
432
- payload: message,
433
- })
434
- }
435
- return
436
- }
437
-
438
- switch (message.type) {
439
- case "connect":
440
- initializationController.handleConnect(message)
441
- return
442
- case "initResult":
443
- initializationController.handleInitResult(message)
444
- return
445
- case "queryDataSource":
446
- void handleQuery(message)
447
- return
448
- case "unsubscribeDataSourceQuery":
449
- querySubscriptions.unsubscribe(message.subscriptionId)
450
- return
451
- case "createPage":
452
- void handleCreatePage(message)
453
- return
454
- case "getPage":
455
- void handleGetPage(message)
456
- return
457
- case "getUser":
458
- void handleGetUser(message)
459
- return
460
- case "listUsers":
461
- void handleListUsers(message)
462
- return
463
- case "updatePage":
464
- void handleUpdatePage(message)
465
- return
466
- case "resize":
467
- handlers.resize?.(message)
468
- return
469
- case "invalidHostMessage":
470
- return
471
- default:
472
- unreachable(message)
473
- }
474
- }
359
+ const onMessage = createSandboxMessageRouter({
360
+ iframe,
361
+ sandboxOrigin,
362
+ getInitStatus: () => initializationController.status,
363
+ emit,
364
+ post,
365
+ onInvalidConnect: initializationController.handleInvalidConnect,
366
+ onInvalidInitResult: initializationController.handleInvalidInitResult,
367
+ onConnect: initializationController.handleConnect,
368
+ onInitResult: initializationController.handleInitResult,
369
+ onQueryDataSource: handleQuery,
370
+ onUnsubscribeDataSourceQuery: message =>
371
+ querySubscriptions.unsubscribe(message.subscriptionId),
372
+ onCreatePage: handleCreatePage,
373
+ onGetPage: handleGetPage,
374
+ onGetUser: handleGetUser,
375
+ onListUsers: handleListUsers,
376
+ onUpdatePage: handleUpdatePage,
377
+ onResize: message => handlers.resize?.(message),
378
+ })
475
379
 
476
380
  window.addEventListener("message", onMessage)
477
381
  iframe.addEventListener("load", initializationController.onIframeLoad)
@@ -65,8 +65,14 @@ export type InitializationController = {
65
65
  readonly hasConnectedSandbox: boolean
66
66
  handleConnect: (message: ConnectMessage) => void
67
67
  handleInitResult: (message: InitResultMessage) => void
68
- handleInvalidConnect: (initializationId?: string) => boolean
69
- handleInvalidInitResult: () => boolean
68
+ handleInvalidConnect: (args: {
69
+ initializationId: string
70
+ error: CustomBlockInitErrorInfo
71
+ }) => "initErrorSent" | "nack"
72
+ handleInvalidInitResult: (args: {
73
+ initializationId: string | undefined
74
+ error: CustomBlockInitErrorInfo
75
+ }) => void
70
76
  onIframeLoad: () => void
71
77
  stop: () => void
72
78
  }
@@ -109,10 +115,11 @@ export function createInitializationController(
109
115
  }
110
116
  }
111
117
 
112
- function sendInitError(
113
- initializationId: string,
114
- error: CustomBlockInitErrorInfo,
115
- ) {
118
+ function sendInitError(args: {
119
+ initializationId: string
120
+ error: CustomBlockInitErrorInfo
121
+ }) {
122
+ const { initializationId, error } = args
116
123
  if (initStatus !== "waitingForConnect") {
117
124
  return
118
125
  }
@@ -134,7 +141,10 @@ export function createInitializationController(
134
141
  }
135
142
  const preparation = prepareInit(connect)
136
143
  if (preparation.status === "error") {
137
- sendInitError(connect.initializationId, preparation.error)
144
+ sendInitError({
145
+ initializationId: connect.initializationId,
146
+ error: preparation.error,
147
+ })
138
148
  return false
139
149
  }
140
150
  initStatus = "waitingForInitResult"
@@ -191,7 +201,10 @@ export function createInitializationController(
191
201
  const initError = initErrorForFailureReason("invalid_protocol_version", {
192
202
  currentBridgeProtocolVersion: message.bridgeProtocolVersion,
193
203
  })
194
- sendInitError(message.initializationId, initError)
204
+ sendInitError({
205
+ initializationId: message.initializationId,
206
+ error: initError,
207
+ })
195
208
  return
196
209
  }
197
210
  if (message.bridgeProtocolVersion < minBridgeProtocolVersion) {
@@ -202,13 +215,19 @@ export function createInitializationController(
202
215
  minBridgeProtocolVersion,
203
216
  },
204
217
  )
205
- sendInitError(message.initializationId, initError)
218
+ sendInitError({
219
+ initializationId: message.initializationId,
220
+ error: initError,
221
+ })
206
222
  return
207
223
  }
208
224
 
209
225
  // If the sandbox reported an error during `connect`, return it as an `init` error.
210
226
  if (message.status === "error") {
211
- sendInitError(message.initializationId, message.error)
227
+ sendInitError({
228
+ initializationId: message.initializationId,
229
+ error: message.error,
230
+ })
212
231
  return
213
232
  }
214
233
 
@@ -217,32 +236,37 @@ export function createInitializationController(
217
236
  }
218
237
  }
219
238
 
220
- function handleInvalidConnect(initializationId?: string): boolean {
239
+ function handleInvalidConnect(args: {
240
+ initializationId: string
241
+ error: CustomBlockInitErrorInfo
242
+ }): "initErrorSent" | "nack" {
243
+ const { initializationId, error } = args
221
244
  if (initStatus !== "waitingForConnect") {
222
- return false
223
- }
224
- const error = initErrorForFailureReason("invalid_connect_payload")
225
- if (initializationId === undefined) {
226
- initStatus = "error"
227
- clearNoConnectTimeout()
228
- warn(error)
229
- onInitializationFailure?.(error)
230
- } else {
231
- sendInitError(initializationId, error)
245
+ // `connect` messages received when not waiting for one are NACKed like generic invalid messages.
246
+ return "nack"
232
247
  }
233
- return true
248
+ // The sandbox awaits `init`. Reply with `init.error` using its initialization ID.
249
+ sendInitError({ initializationId, error })
250
+ return "initErrorSent"
234
251
  }
235
252
 
236
- function handleInvalidInitResult(): boolean {
237
- if (initStatus !== "waitingForInitResult") {
238
- return false
253
+ function handleInvalidInitResult(args: {
254
+ initializationId: string | undefined
255
+ error: CustomBlockInitErrorInfo
256
+ }): void {
257
+ const { initializationId, error } = args
258
+ if (
259
+ initStatus !== "waitingForInitResult" ||
260
+ initializationId === undefined ||
261
+ initializationId !== activeInitializationId
262
+ ) {
263
+ return
239
264
  }
265
+ clearNoConnectTimeout()
240
266
  clearNoInitResultTimeout()
241
267
  initStatus = "error"
242
- const error = initErrorForFailureReason("invalid_init_result_payload")
243
268
  warn(error)
244
269
  onInitializationFailure?.(error)
245
- return true
246
270
  }
247
271
 
248
272
  function onIframeLoad() {
@@ -1,4 +1,4 @@
1
- import { readIncomingType } from "@notionhq/custom-blocks-protocol/incomingType.js"
1
+ import { readIncomingType } from "@notionhq/custom-blocks-protocol/messages/incomingType.js"
2
2
  import type { InvalidSandboxMessage } from "@notionhq/custom-blocks-protocol/messages/invalidSandboxMessage.js"
3
3
 
4
4
  export type InvalidSandboxMessageResponse = {
@@ -0,0 +1,176 @@
1
+ import type { ConnectMessage } from "@notionhq/custom-blocks-protocol/messages/connect.js"
2
+ import type { CreatePageMessage } from "@notionhq/custom-blocks-protocol/messages/createPage.js"
3
+ import type { GetPageMessage } from "@notionhq/custom-blocks-protocol/messages/getPage.js"
4
+ import type { GetUserMessage } from "@notionhq/custom-blocks-protocol/messages/getUser.js"
5
+ import type { HostToSandboxMessage } from "@notionhq/custom-blocks-protocol/messages/hostToSandbox.js"
6
+ import type { InitResultMessage } from "@notionhq/custom-blocks-protocol/messages/initResult.js"
7
+ import { isInitHandshakeMessage } from "@notionhq/custom-blocks-protocol/messages/isInitHandshakeMessage.js"
8
+ import type { ListUsersMessage } from "@notionhq/custom-blocks-protocol/messages/listUsers.js"
9
+ import type { QueryDataSourceMessage } from "@notionhq/custom-blocks-protocol/messages/queryDataSource.js"
10
+ import type { ResizeMessage } from "@notionhq/custom-blocks-protocol/messages/resize.js"
11
+ import { sandboxToHostMessageSchema } from "@notionhq/custom-blocks-protocol/messages/sandboxToHost.js"
12
+ import type { UnsubscribeDataSourceQueryMessage } from "@notionhq/custom-blocks-protocol/messages/unsubscribeDataSourceQuery.js"
13
+ import type { UpdatePageMessage } from "@notionhq/custom-blocks-protocol/messages/updatePage.js"
14
+ import * as v from "valibot"
15
+ import { initErrorForFailureReason } from "../lifecycle/initErrors.js"
16
+ import type { InitializationController } from "../lifecycle/initializationController.js"
17
+ import type { InitStatus } from "../lifecycle/types.js"
18
+ import { unreachable } from "../utils.js"
19
+ import { getInvalidSandboxMessageResponse } from "./invalidSandboxMessage.js"
20
+ import type { CustomBlockHostLogDirection } from "./types.js"
21
+
22
+ const initializationIdentitySchema = v.object({
23
+ initializationId: v.string(),
24
+ })
25
+
26
+ export type SandboxMessageRouterOptions = {
27
+ iframe: HTMLIFrameElement
28
+ sandboxOrigin: string
29
+ getInitStatus: () => InitStatus
30
+ emit: (direction: CustomBlockHostLogDirection, payload: unknown) => void
31
+ post: (message: HostToSandboxMessage) => void
32
+ onInvalidConnect: InitializationController["handleInvalidConnect"]
33
+ onInvalidInitResult: InitializationController["handleInvalidInitResult"]
34
+ onConnect: (message: ConnectMessage) => void
35
+ onInitResult: (message: InitResultMessage) => void
36
+ onQueryDataSource: (message: QueryDataSourceMessage) => void | Promise<void>
37
+ onUnsubscribeDataSourceQuery: (
38
+ message: UnsubscribeDataSourceQueryMessage,
39
+ ) => void
40
+ onCreatePage: (message: CreatePageMessage) => void | Promise<void>
41
+ onGetPage: (message: GetPageMessage) => void | Promise<void>
42
+ onGetUser: (message: GetUserMessage) => void | Promise<void>
43
+ onListUsers: (message: ListUsersMessage) => void | Promise<void>
44
+ onUpdatePage: (message: UpdatePageMessage) => void | Promise<void>
45
+ onResize: (message: ResizeMessage) => void
46
+ }
47
+
48
+ export function createSandboxMessageRouter(
49
+ options: SandboxMessageRouterOptions,
50
+ ): (event: MessageEvent) => void {
51
+ const {
52
+ iframe,
53
+ sandboxOrigin,
54
+ getInitStatus,
55
+ emit,
56
+ post,
57
+ onInvalidConnect,
58
+ onInvalidInitResult,
59
+ onConnect,
60
+ onInitResult,
61
+ onQueryDataSource,
62
+ onUnsubscribeDataSourceQuery,
63
+ onCreatePage,
64
+ onGetPage,
65
+ onGetUser,
66
+ onListUsers,
67
+ onUpdatePage,
68
+ onResize,
69
+ } = options
70
+
71
+ function onInvalidSandboxMessage(
72
+ data: unknown,
73
+ issues: readonly v.BaseIssue<unknown>[],
74
+ ) {
75
+ emit("warn", {
76
+ message: "Ignored unsupported sandbox message",
77
+ payload: data,
78
+ issues,
79
+ })
80
+ const response = getInvalidSandboxMessageResponse(data)
81
+
82
+ if (response.incomingType === "connect") {
83
+ const parsedIdentity = v.safeParse(initializationIdentitySchema, data)
84
+ if (parsedIdentity.success) {
85
+ const responseType = onInvalidConnect({
86
+ initializationId: parsedIdentity.output.initializationId,
87
+ error: initErrorForFailureReason("invalid_connect_payload"),
88
+ })
89
+ if (responseType === "initErrorSent") {
90
+ // The controller already sent `init.error`, so no need to also send a NACK.
91
+ return
92
+ }
93
+ }
94
+ }
95
+
96
+ if (response.incomingType === "initResult") {
97
+ const parsedIdentity = v.safeParse(initializationIdentitySchema, data)
98
+ onInvalidInitResult({
99
+ initializationId: parsedIdentity.success
100
+ ? parsedIdentity.output.initializationId
101
+ : undefined,
102
+ error: initErrorForFailureReason("invalid_init_result_payload"),
103
+ })
104
+ }
105
+
106
+ // Other messages get NACKed indicating they were invalid and skipped
107
+ if (response.nack !== undefined) {
108
+ post(response.nack)
109
+ }
110
+ }
111
+
112
+ return function onMessage(event: MessageEvent) {
113
+ // Verify the sender is this host's iframe, not another window with the same origin.
114
+ const isExpectedSource = event.source === iframe.contentWindow
115
+ // Verify the sender's origin, since the iframe can navigate to another origin.
116
+ const isExpectedOrigin = event.origin === sandboxOrigin
117
+ if (!isExpectedSource || !isExpectedOrigin) {
118
+ return
119
+ }
120
+
121
+ // Parse and validate the message from the sandbox.
122
+ const parsed = v.safeParse(sandboxToHostMessageSchema, event.data)
123
+ if (!parsed.success) {
124
+ onInvalidSandboxMessage(event.data, parsed.issues)
125
+ return
126
+ }
127
+
128
+ const message = parsed.output
129
+ if (!isInitHandshakeMessage(message) && getInitStatus() !== "success") {
130
+ emit("warn", {
131
+ message: `Ignored ${message.type} before initialization completed`,
132
+ payload: message,
133
+ })
134
+ return
135
+ }
136
+
137
+ emit("in", message)
138
+ switch (message.type) {
139
+ case "connect":
140
+ onConnect(message)
141
+ return
142
+ case "initResult":
143
+ onInitResult(message)
144
+ return
145
+ case "queryDataSource":
146
+ void onQueryDataSource(message)
147
+ return
148
+ case "unsubscribeDataSourceQuery":
149
+ onUnsubscribeDataSourceQuery(message)
150
+ return
151
+ case "createPage":
152
+ void onCreatePage(message)
153
+ return
154
+ case "getPage":
155
+ void onGetPage(message)
156
+ return
157
+ case "getUser":
158
+ void onGetUser(message)
159
+ return
160
+ case "listUsers":
161
+ void onListUsers(message)
162
+ return
163
+ case "updatePage":
164
+ void onUpdatePage(message)
165
+ return
166
+ case "resize":
167
+ onResize(message)
168
+ return
169
+ case "invalidHostMessage":
170
+ // The inbound log records this rejection. Do not reply, to avoid an error-response loop.
171
+ return
172
+ default:
173
+ unreachable(message)
174
+ }
175
+ }
176
+ }
@@ -1,4 +1,3 @@
1
- import type { BridgeMessagePayload } from "@notionhq/custom-blocks-protocol/messagePayload.js"
2
1
  import type { CreatePageMessage } from "@notionhq/custom-blocks-protocol/messages/createPage.js"
3
2
  import type {
4
3
  CreatePageResultMessage,
@@ -21,6 +20,7 @@ import type {
21
20
  ListUsersMessage,
22
21
  ListUsersResultMessage,
23
22
  } from "@notionhq/custom-blocks-protocol/messages/listUsers.js"
23
+ import type { BridgeMessagePayload } from "@notionhq/custom-blocks-protocol/messages/messagePayload.js"
24
24
  import type { QueryDataSourceMessage } from "@notionhq/custom-blocks-protocol/messages/queryDataSource.js"
25
25
  import type { ResizeMessage } from "@notionhq/custom-blocks-protocol/messages/resize.js"
26
26
  import type { UpdatePageMessage } from "@notionhq/custom-blocks-protocol/messages/updatePage.js"
@@ -1,4 +1,4 @@
1
- import type { BridgeMessagePayload } from "@notionhq/custom-blocks-protocol/messagePayload.js"
1
+ import type { BridgeMessagePayload } from "@notionhq/custom-blocks-protocol/messages/messagePayload.js"
2
2
  import type {
3
3
  CustomBlockQueryDataSourceErrorInfo,
4
4
  QueryDataSourceResultMessage,