@notionhq/custom-blocks-host 0.1.26 → 0.1.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -77,11 +77,8 @@ const host = createCustomBlockHost({
77
77
  onInitialized: state => {
78
78
  console.log("Initialized manifest", state.manifest)
79
79
  },
80
- onInitializationFailure: error => {
81
- showInitializationError(error)
82
- },
83
- onRestartRequired: () => {
84
- remountHostAndIframe()
80
+ onFailure: error => {
81
+ showHostError(error)
85
82
  },
86
83
  onLog: (direction, payload) => {
87
84
  console.log(direction, payload)
@@ -93,7 +90,28 @@ const host = createCustomBlockHost({
93
90
  supports. Set a higher value to retire older SDKs. Lower values cannot enable versions the runtime
94
91
  no longer supports. The host does not enforce a maximum protocol version.
95
92
 
96
- Each host instance initializes once:
93
+ Each host handles one initialization handshake with one sandbox iframe:
94
+
95
+ - Create the host before navigating the iframe to the sandbox URL.
96
+ - The host accepts the first `connect` attempt and ignores subsequent `connect` messages, including
97
+ malformed ones. A connection after an initialization timeout does not restart it.
98
+ - The first observed iframe `load` is allowed, including when initialization completes before it.
99
+ - A second observed `load` stops the host and reports retryable `iframe_reloaded` through
100
+ `onFailure`. A reload or navigation can replace the sandbox document, so the host ends the
101
+ connection instead of continuing to serve the replacement.
102
+ - Stopping removes message and load listeners, clears initialization timers, ends query
103
+ subscriptions, and prevents further outbound messages, including pending responses.
104
+ - To retry or apply a deployment or configuration change, the embedder creates a new host and
105
+ iframe. The host does not restart automatically.
106
+
107
+ The stop takes effect at the second observed load. It does not prevent navigation or isolate the
108
+ original document before that event: a replacement document at the same origin can receive messages
109
+ and invoke handlers until the host stops.
110
+
111
+ SDKs using protocol 5 or later support unavailable page context. For older SDKs, an unavailable
112
+ initial page fails initialization with `context_unavailable`. If page context becomes unavailable
113
+ after initialization, the host stops and reports the same error instead of sending an unsupported
114
+ `pageChanged` message.
97
115
 
98
116
  - `onManifest` provides the selected manifest as soon as it is available after `connect`, before
99
117
  binding validation and `onInitialized`. Use it to configure bindings even when initialization
@@ -101,10 +119,8 @@ Each host instance initializes once:
101
119
 
102
120
  - `onInitialized` reports success after the sandbox accepts initialization. It provides the selected
103
121
  `manifest` and the sandbox's optional `initialHeight`.
104
- - `onInitializationFailure` reports an initialization error or timeout.
105
- - `onRestartRequired` reports a connection attempt after the host accepts its first connection or
106
- stops waiting. Recreate the host and iframe together to accept a new connection. This can occur
107
- after the iframe document changes or a connection arrives after a timeout.
122
+ - `onFailure` reports a handshake failure, timeout, unavailable page context, or iframe reload. It
123
+ can run after initialization succeeds.
108
124
 
109
125
  The host uses the manifest from `connect`, or `initialState.manifest` if `connect` omits it. If
110
126
  neither exists, initialization fails with `manifest_unavailable`.
@@ -145,7 +161,7 @@ filter types fail protocol validation.
145
161
  host derive bindings from the sandbox-provided manifest.
146
162
 
147
163
  Set `sandboxOrigin` to the iframe's exact origin. The runtime targets outbound messages to that
148
- origin and rejects inbound messages from any other origin. The runtime also accepts
164
+ origin and accepts inbound messages only from that iframe and origin. The runtime also accepts
149
165
  `noConnectTimeoutMs` and `noInitResultTimeoutMs`; both default to five seconds.
150
166
 
151
167
  ### Initialization and malformed messages
@@ -186,8 +202,7 @@ timeout. Malformed messages do not restart initialization after it succeeds or f
186
202
  The host accepts a valid `initResult.error` as a terminal initialization failure. It logs and
187
203
  ignores a valid `initResult` with a stale `initializationId` or an unexpected state.
188
204
 
189
- Hosts do not retry malformed messages. A host that receives a `connect` after initialization has
190
- settled must use `onRestartRequired` to recreate the host and iframe together.
205
+ The host does not retry malformed messages.
191
206
 
192
207
  ## Updating live state
193
208
 
@@ -207,9 +222,10 @@ State updates are sent after `initResult.success`. Updates made earlier are held
207
222
  initialization completes. `refreshQuery()` re-runs `queryDataSource` for every active subscription
208
223
  with the supplied data source ID, preserving each subscription's filter, sorts, and limit.
209
224
 
210
- The runtime removes a subscription when it receives `unsubscribeDataSourceQuery`. Repeated
211
- unsubscribe messages are safe. The runtime also ignores results from a query that was unsubscribed
212
- while its handler was running.
225
+ After `stop()`, the host drops outbound messages, including pending request responses. The runtime
226
+ removes a subscription when it receives `unsubscribeDataSourceQuery`. Repeated unsubscribe messages
227
+ are safe. The runtime also ignores results from a query that was unsubscribed while its handler was
228
+ running.
213
229
 
214
230
  Call `stop()` when the iframe is removed. It detaches listeners, clears timeouts, and discards
215
231
  active query subscriptions.
@@ -7,7 +7,7 @@ import type { NotionParent } from "./protocol/parent.js";
7
7
  import type { NotionTheme } from "./protocol/theme.js";
8
8
  import type { NotionUser } from "./protocol/users/user.js";
9
9
  import type { CustomBlockHostDataSourcesPayload, CustomBlockHostInitialState } from "./lifecycle/types.js";
10
- import type { CustomBlockHostHandlers, CustomBlockHostInitializationFailure, CustomBlockHostLogDirection } from "./messages/types.js";
10
+ import type { CustomBlockHostFailure, CustomBlockHostHandlers, CustomBlockHostLogDirection } from "./messages/types.js";
11
11
  export type CustomBlockHostOptions = {
12
12
  iframe: HTMLIFrameElement;
13
13
  sandboxOrigin: string;
@@ -31,12 +31,13 @@ export type CustomBlockHostOptions = {
31
31
  manifest: CustomBlockManifest;
32
32
  initialHeight?: number;
33
33
  }) => void;
34
- /** Callback fired when the initialization handshake with the sandbox failed or timed out. */
35
- onInitializationFailure?: (error: CustomBlockHostInitializationFailure) => void;
36
34
  /**
37
- * Callback fired when the current host and iframe must restart. Recreate both to continue.
35
+ * Reports a handshake failure, timeout, unavailable page context, or iframe reload.
36
+ * Iframe reloads and unavailable page context for older SDKs stop the host
37
+ * before this callback runs.
38
+ * This callback can run after initialization succeeds.
38
39
  */
39
- onRestartRequired?: () => void;
40
+ onFailure?: (error: CustomBlockHostFailure) => void;
40
41
  onLog?: (direction: CustomBlockHostLogDirection, payload: unknown) => void;
41
42
  };
42
43
  export type CustomBlockHostHandle = {
@@ -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;AAErD,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;AAoBtG,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;;;;OAIG;IACH,wBAAwB,CAAC,EAAE,MAAM,CAAA;IACjC,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,qBAAqB,CAAC,EAAE,MAAM,CAAA;IAC9B,YAAY,EAAE,2BAA2B,CAAA;IACzC,QAAQ,EAAE,uBAAuB,CAAA;IACjC;;;OAGG;IACH,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;;OAEG;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,CAgZvB"}
1
+ {"version":3,"file":"createCustomBlockHost.d.ts","sourceRoot":"","sources":["../src/createCustomBlockHost.ts"],"names":[],"mappings":"AAAA,OAAO,EAEN,KAAK,kBAAkB,EACvB,MAAM,8CAA8C,CAAA;AAErD,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;AAoBtG,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,sBAAsB,EACtB,uBAAuB,EACvB,2BAA2B,EAC3B,MAAM,qBAAqB,CAAA;AAc5B,MAAM,MAAM,sBAAsB,GAAG;IACpC,MAAM,EAAE,iBAAiB,CAAA;IACzB,aAAa,EAAE,MAAM,CAAA;IACrB;;;;OAIG;IACH,wBAAwB,CAAC,EAAE,MAAM,CAAA;IACjC,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,qBAAqB,CAAC,EAAE,MAAM,CAAA;IAC9B,YAAY,EAAE,2BAA2B,CAAA;IACzC,QAAQ,EAAE,uBAAuB,CAAA;IACjC;;;OAGG;IACH,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;;;;;OAKG;IACH,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,sBAAsB,KAAK,IAAI,CAAA;IACnD,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,CA+avB"}
@@ -8,8 +8,11 @@ import { MIN_HOST_SUPPORTED_BRIDGE_PROTOCOL_VERSION } from "./protocolVersion.js
8
8
  import { QuerySubscriptions, } from "./queries/querySubscriptions.js";
9
9
  import { validateQueryDataSourceFilter } from "./queries/validateQueryDataSourceFilter.js";
10
10
  import { validateQueryDataSourceSorts } from "./queries/validateQueryDataSourceSorts.js";
11
+ // Bridge protocol version that introduced page: { type: "unavailable" }.
12
+ // Earlier SDKs cannot represent this context, so the host reports context_unavailable.
13
+ const BRIDGE_PROTOCOL_VERSION_INTRODUCING_UNAVAILABLE_PAGE = 5;
11
14
  export function createCustomBlockHost(options) {
12
- const { iframe, initialState, handlers, onManifest, onInitialized, onInitializationFailure, onRestartRequired, onLog, sandboxOrigin, minBridgeProtocolVersion: configuredMinBridgeProtocolVersion, noConnectTimeoutMs, noInitResultTimeoutMs, } = options;
15
+ const { iframe, initialState, handlers, onManifest, onInitialized, onFailure, onLog, sandboxOrigin, minBridgeProtocolVersion: configuredMinBridgeProtocolVersion, noConnectTimeoutMs, noInitResultTimeoutMs, } = options;
13
16
  // Callers can retire older SDKs, but cannot restore support for protocols the runtime no longer
14
17
  // implements. Omission uses the runtime minimum.
15
18
  const minBridgeProtocolVersion = Math.max(configuredMinBridgeProtocolVersion ??
@@ -34,6 +37,10 @@ export function createCustomBlockHost(options) {
34
37
  onLog?.(direction, payload);
35
38
  }
36
39
  function post(message) {
40
+ if (initializationController.status === "stopped") {
41
+ // The parent page can stop this host. Do not send messages after the host stops.
42
+ return;
43
+ }
37
44
  const sandboxMessage = normalizeHostToSandboxMessage(message, sandboxBridgeProtocolVersion);
38
45
  emit("out", sandboxMessage);
39
46
  iframe.contentWindow?.postMessage(sandboxMessage, sandboxOrigin);
@@ -194,6 +201,14 @@ export function createCustomBlockHost(options) {
194
201
  }),
195
202
  prepareInit(connect) {
196
203
  sandboxBridgeProtocolVersion = connect.bridgeProtocolVersion;
204
+ if (sandboxBridgeProtocolVersion <
205
+ BRIDGE_PROTOCOL_VERSION_INTRODUCING_UNAVAILABLE_PAGE &&
206
+ page.type === "unavailable") {
207
+ return {
208
+ status: "error",
209
+ error: initErrorForFailureReason("context_unavailable"),
210
+ };
211
+ }
197
212
  manifest = connect.manifest ?? initialState.manifest;
198
213
  if (manifest === undefined) {
199
214
  return {
@@ -223,13 +238,13 @@ export function createCustomBlockHost(options) {
223
238
  minBridgeProtocolVersion,
224
239
  noConnectTimeoutMs,
225
240
  noInitResultTimeoutMs,
241
+ stopHostWithFailure: stopWithFailure,
226
242
  onInitResult(message) {
227
243
  if (message.status === "success" && manifest !== undefined) {
228
244
  onInitialized?.({ manifest, initialHeight: message.initialHeight });
229
245
  }
230
246
  },
231
- onRestartRequired,
232
- onInitializationFailure,
247
+ onFailure,
233
248
  });
234
249
  const onMessage = createSandboxMessageRouter({
235
250
  iframe,
@@ -253,13 +268,19 @@ export function createCustomBlockHost(options) {
253
268
  });
254
269
  window.addEventListener("message", onMessage);
255
270
  iframe.addEventListener("load", initializationController.onIframeLoad);
271
+ function stop() {
272
+ window.removeEventListener("message", onMessage);
273
+ iframe.removeEventListener("load", initializationController.onIframeLoad);
274
+ initializationController.stop();
275
+ querySubscriptions.unsubscribeAll();
276
+ }
277
+ function stopWithFailure(error) {
278
+ stop();
279
+ emit("warn", error);
280
+ onFailure?.(error);
281
+ }
256
282
  return {
257
- stop() {
258
- window.removeEventListener("message", onMessage);
259
- iframe.removeEventListener("load", initializationController.onIframeLoad);
260
- initializationController.stop();
261
- querySubscriptions.unsubscribeAll();
262
- },
283
+ stop,
263
284
  post,
264
285
  setTheme(nextTheme) {
265
286
  theme = nextTheme;
@@ -299,6 +320,14 @@ export function createCustomBlockHost(options) {
299
320
  },
300
321
  setPage(nextPage) {
301
322
  page = nextPage;
323
+ if (nextPage.type === "unavailable" &&
324
+ sandboxBridgeProtocolVersion <
325
+ BRIDGE_PROTOCOL_VERSION_INTRODUCING_UNAVAILABLE_PAGE &&
326
+ (initializationController.status === "waitingForInitResult" ||
327
+ initializationController.status === "success")) {
328
+ stopWithFailure(initErrorForFailureReason("context_unavailable"));
329
+ return;
330
+ }
302
331
  if (initializationController.status !== "success") {
303
332
  if (initializationController.status === "waitingForInitResult") {
304
333
  pendingPageChange = true;
package/dist/index.d.ts CHANGED
@@ -10,6 +10,6 @@ export type { NotionPagePropertyValue, NotionPagePropertyWriteMap, NotionRichTex
10
10
  export { type AutoBindableProperty, getAutoBoundPropertyIdsByKey, } from "./autoBindProperties.js";
11
11
  export { type CustomBlockHostHandle, type CustomBlockHostOptions, createCustomBlockHost, } from "./createCustomBlockHost.js";
12
12
  export type { ConnectSuccessMessage, CustomBlockHostDataSourcesPayload, CustomBlockHostInitialDataSources, CustomBlockHostInitialState, } from "./lifecycle/types.js";
13
- export type { CreatePageMessageInput, CustomBlockHostCreatePageResult, CustomBlockHostGetPageResult, CustomBlockHostGetUserResult, CustomBlockHostHandlers, CustomBlockHostInitializationFailure, CustomBlockHostListUsersResult, CustomBlockHostLogDirection, CustomBlockHostOpenPageResult, CustomBlockHostUpdatePageResult, GetPageMessageInput, GetUserMessageInput, ListUsersMessageInput, OpenPageMessageInput, QueryDataSourceMessageInput, ResizeMessageInput, UpdatePageMessageInput, } from "./messages/types.js";
13
+ export type { CreatePageMessageInput, CustomBlockHostCreatePageResult, CustomBlockHostFailure, CustomBlockHostGetPageResult, CustomBlockHostGetUserResult, CustomBlockHostHandlers, CustomBlockHostListUsersResult, CustomBlockHostLogDirection, CustomBlockHostOpenPageResult, CustomBlockHostUpdatePageResult, GetPageMessageInput, GetUserMessageInput, ListUsersMessageInput, OpenPageMessageInput, QueryDataSourceMessageInput, ResizeMessageInput, UpdatePageMessageInput, } from "./messages/types.js";
14
14
  export type { CustomBlockHostQueryDataSourceResult } from "./queries/types.js";
15
15
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,iBAAiB,EAAE,MAAM,yDAAyD,CAAA;AAChG,YAAY,EAAE,cAAc,EAAE,MAAM,sDAAsD,CAAA;AAC1F,YAAY,EAAE,cAAc,EAAE,MAAM,sDAAsD,CAAA;AAC1F,YAAY,EAAE,gBAAgB,EAAE,MAAM,wDAAwD,CAAA;AAC9F,YAAY,EAAE,eAAe,EAAE,MAAM,uDAAuD,CAAA;AAC5F,YAAY,EAAE,sBAAsB,EAAE,MAAM,8DAA8D,CAAA;AAC1G,YAAY,EAAE,aAAa,EAAE,MAAM,qDAAqD,CAAA;AACxF,YAAY,EAAE,iBAAiB,EAAE,MAAM,yDAAyD,CAAA;AAChG,YAAY,EACX,uBAAuB,EACvB,0BAA0B,EAC1B,kBAAkB,GAClB,MAAM,gDAAgD,CAAA;AACvD,OAAO,EACN,KAAK,oBAAoB,EACzB,4BAA4B,GAC5B,MAAM,yBAAyB,CAAA;AAChC,OAAO,EACN,KAAK,qBAAqB,EAC1B,KAAK,sBAAsB,EAC3B,qBAAqB,GACrB,MAAM,4BAA4B,CAAA;AACnC,YAAY,EACX,qBAAqB,EACrB,iCAAiC,EACjC,iCAAiC,EACjC,2BAA2B,GAC3B,MAAM,sBAAsB,CAAA;AAC7B,YAAY,EACX,sBAAsB,EACtB,+BAA+B,EAC/B,4BAA4B,EAC5B,4BAA4B,EAC5B,uBAAuB,EACvB,oCAAoC,EACpC,8BAA8B,EAC9B,2BAA2B,EAC3B,6BAA6B,EAC7B,+BAA+B,EAC/B,mBAAmB,EACnB,mBAAmB,EACnB,qBAAqB,EACrB,oBAAoB,EACpB,2BAA2B,EAC3B,kBAAkB,EAClB,sBAAsB,GACtB,MAAM,qBAAqB,CAAA;AAC5B,YAAY,EAAE,oCAAoC,EAAE,MAAM,oBAAoB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,iBAAiB,EAAE,MAAM,yDAAyD,CAAA;AAChG,YAAY,EAAE,cAAc,EAAE,MAAM,sDAAsD,CAAA;AAC1F,YAAY,EAAE,cAAc,EAAE,MAAM,sDAAsD,CAAA;AAC1F,YAAY,EAAE,gBAAgB,EAAE,MAAM,wDAAwD,CAAA;AAC9F,YAAY,EAAE,eAAe,EAAE,MAAM,uDAAuD,CAAA;AAC5F,YAAY,EAAE,sBAAsB,EAAE,MAAM,8DAA8D,CAAA;AAC1G,YAAY,EAAE,aAAa,EAAE,MAAM,qDAAqD,CAAA;AACxF,YAAY,EAAE,iBAAiB,EAAE,MAAM,yDAAyD,CAAA;AAChG,YAAY,EACX,uBAAuB,EACvB,0BAA0B,EAC1B,kBAAkB,GAClB,MAAM,gDAAgD,CAAA;AACvD,OAAO,EACN,KAAK,oBAAoB,EACzB,4BAA4B,GAC5B,MAAM,yBAAyB,CAAA;AAChC,OAAO,EACN,KAAK,qBAAqB,EAC1B,KAAK,sBAAsB,EAC3B,qBAAqB,GACrB,MAAM,4BAA4B,CAAA;AACnC,YAAY,EACX,qBAAqB,EACrB,iCAAiC,EACjC,iCAAiC,EACjC,2BAA2B,GAC3B,MAAM,sBAAsB,CAAA;AAC7B,YAAY,EACX,sBAAsB,EACtB,+BAA+B,EAC/B,sBAAsB,EACtB,4BAA4B,EAC5B,4BAA4B,EAC5B,uBAAuB,EACvB,8BAA8B,EAC9B,2BAA2B,EAC3B,6BAA6B,EAC7B,+BAA+B,EAC/B,mBAAmB,EACnB,mBAAmB,EACnB,qBAAqB,EACrB,oBAAoB,EACpB,2BAA2B,EAC3B,kBAAkB,EAClB,sBAAsB,GACtB,MAAM,qBAAqB,CAAA;AAC5B,YAAY,EAAE,oCAAoC,EAAE,MAAM,oBAAoB,CAAA"}
@@ -9,7 +9,7 @@ import type { CustomBlockPage } from "../protocol/pages/page.js";
9
9
  import type { NotionParent } from "../protocol/parent.js";
10
10
  import type { NotionTheme } from "../protocol/theme.js";
11
11
  import type { NotionUser } from "../protocol/users/user.js";
12
- import type { CustomBlockHostInitializationFailure } from "../messages/types.js";
12
+ import type { CustomBlockHostFailure } from "../messages/types.js";
13
13
  import type { ConnectSuccessMessage, CustomBlockHostDataSourcesPayload, InitStatus } from "./types.js";
14
14
  type InitializationState = {
15
15
  theme: NotionTheme;
@@ -36,9 +36,9 @@ export type InitializationControllerOptions = {
36
36
  minBridgeProtocolVersion: number;
37
37
  noConnectTimeoutMs?: number;
38
38
  noInitResultTimeoutMs?: number;
39
+ stopHostWithFailure: (error: CustomBlockHostFailure) => void;
39
40
  onInitResult?: (message: InitResultMessage) => void;
40
- onRestartRequired?: () => void;
41
- onInitializationFailure?: (error: CustomBlockHostInitializationFailure) => void;
41
+ onFailure?: (error: CustomBlockHostFailure) => void;
42
42
  };
43
43
  export type InitializationController = {
44
44
  readonly status: InitStatus;
@@ -46,10 +46,10 @@ export type InitializationController = {
46
46
  handleConnect: (message: ConnectMessage) => void;
47
47
  handleInitResult: (message: InitResultMessage) => void;
48
48
  handleInvalidConnect: (args: {
49
- initializationId: string;
49
+ initializationId: string | undefined;
50
50
  error: CustomBlockInitErrorInfo;
51
51
  bridgeProtocolVersion: number | undefined;
52
- }) => "initErrorSent" | "nack";
52
+ }) => "handled" | "nack";
53
53
  handleInvalidInitResult: (args: {
54
54
  initializationId: string | undefined;
55
55
  error: CustomBlockInitErrorInfo;
@@ -1 +1 @@
1
- {"version":3,"file":"initializationController.d.ts","sourceRoot":"","sources":["../../src/lifecycle/initializationController.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"initializationController.d.ts","sourceRoot":"","sources":["../../src/lifecycle/initializationController.ts"],"names":[],"mappings":"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"}
@@ -3,11 +3,12 @@ import { isValidBridgeProtocolVersion } from "./protocolVersion.js";
3
3
  const DEFAULT_NO_CONNECT_TIMEOUT_MS = 5_000;
4
4
  const DEFAULT_NO_INIT_RESULT_TIMEOUT_MS = 5_000;
5
5
  export function createInitializationController(options) {
6
- const { blockId, getState, prepareInit, post, warn, flushPendingHostStateChanges, minBridgeProtocolVersion, noConnectTimeoutMs = DEFAULT_NO_CONNECT_TIMEOUT_MS, noInitResultTimeoutMs = DEFAULT_NO_INIT_RESULT_TIMEOUT_MS, onInitResult, onRestartRequired, onInitializationFailure, } = options;
6
+ const { blockId, getState, prepareInit, post, warn, flushPendingHostStateChanges, minBridgeProtocolVersion, noConnectTimeoutMs = DEFAULT_NO_CONNECT_TIMEOUT_MS, noInitResultTimeoutMs = DEFAULT_NO_INIT_RESULT_TIMEOUT_MS, stopHostWithFailure, onInitResult, onFailure, } = options;
7
7
  let initStatus = "waitingForConnect";
8
8
  let noConnectTimeoutId;
9
9
  let noInitResultTimeoutId;
10
10
  let activeInitializationId;
11
+ let hasObservedIframeLoad = false;
11
12
  let hasConnectedSandbox = false;
12
13
  function clearNoConnectTimeout() {
13
14
  if (noConnectTimeoutId !== undefined) {
@@ -35,7 +36,7 @@ export function createInitializationController(options) {
35
36
  status: "error",
36
37
  error,
37
38
  });
38
- onInitializationFailure?.(error);
39
+ onFailure?.(error);
39
40
  }
40
41
  function sendInit(connect) {
41
42
  if (initStatus !== "waitingForConnect") {
@@ -77,22 +78,15 @@ export function createInitializationController(options) {
77
78
  message: "Sandbox did not send initResult",
78
79
  initializationId: connect.initializationId,
79
80
  });
80
- onInitializationFailure?.(error);
81
+ onFailure?.(error);
81
82
  }, noInitResultTimeoutMs);
82
83
  return true;
83
84
  }
84
85
  function handleConnect(message) {
86
+ if (initStatus === "stopped") {
87
+ return;
88
+ }
85
89
  if (initStatus !== "waitingForConnect") {
86
- // A sandbox document sends `connect` exactly once, so this connect
87
- // belongs to a replacement document — or arrived after this host
88
- // already gave up waiting. The host is single-initialization; the
89
- // embedder must recreate it together with the iframe for the new
90
- // document to initialize.
91
- warn({
92
- message: "Ignored connect after initialization settled; recreate the host and iframe to re-initialize",
93
- initializationId: message.initializationId,
94
- });
95
- onRestartRequired?.();
96
90
  return;
97
91
  }
98
92
  // Verify the bridge protocol version is valid.
@@ -130,11 +124,16 @@ export function createInitializationController(options) {
130
124
  }
131
125
  }
132
126
  function handleInvalidConnect(args) {
127
+ if (initStatus === "stopped") {
128
+ return "handled";
129
+ }
133
130
  if (initStatus !== "waitingForConnect") {
134
- // `connect` messages received when not waiting for one are NACKed like generic invalid messages.
135
- return "nack";
131
+ return "handled";
136
132
  }
137
133
  const { initializationId, bridgeProtocolVersion } = args;
134
+ if (initializationId === undefined) {
135
+ return "nack";
136
+ }
138
137
  let error = args.error;
139
138
  // Prefer a specific protocol version error over the generic connect payload error
140
139
  // when the malformed message includes a numeric version.
@@ -150,7 +149,7 @@ export function createInitializationController(options) {
150
149
  }
151
150
  }
152
151
  sendInitError({ initializationId, error });
153
- return "initErrorSent";
152
+ return "handled";
154
153
  }
155
154
  function handleInvalidInitResult(args) {
156
155
  const { initializationId, error } = args;
@@ -163,9 +162,21 @@ export function createInitializationController(options) {
163
162
  clearNoInitResultTimeout();
164
163
  initStatus = "error";
165
164
  warn(error);
166
- onInitializationFailure?.(error);
165
+ onFailure?.(error);
167
166
  }
168
167
  function onIframeLoad() {
168
+ if (initStatus === "stopped") {
169
+ return;
170
+ }
171
+ if (hasObservedIframeLoad) {
172
+ stopHostWithFailure({
173
+ code: "iframe_reloaded",
174
+ message: "The iframe reloaded. Create a new host and iframe to reconnect.",
175
+ isRetryable: true,
176
+ });
177
+ return;
178
+ }
179
+ hasObservedIframeLoad = true;
169
180
  clearNoConnectTimeout();
170
181
  if (initStatus !== "waitingForConnect") {
171
182
  // Only set the timeout if the iframe loaded before the `connect` message was sent.
@@ -179,7 +190,7 @@ export function createInitializationController(options) {
179
190
  initStatus = "error";
180
191
  const error = initErrorForFailureReason("no_connect");
181
192
  warn(error);
182
- onInitializationFailure?.(error);
193
+ onFailure?.(error);
183
194
  }, noConnectTimeoutMs);
184
195
  }
185
196
  function handleInitResult(message) {
@@ -198,11 +209,12 @@ export function createInitializationController(options) {
198
209
  }
199
210
  else {
200
211
  initStatus = "error";
201
- onInitializationFailure?.(message.error);
212
+ onFailure?.(message.error);
202
213
  }
203
214
  onInitResult?.(message);
204
215
  }
205
216
  function stop() {
217
+ initStatus = "stopped";
206
218
  clearNoConnectTimeout();
207
219
  clearNoInitResultTimeout();
208
220
  }
@@ -10,7 +10,18 @@ import type { NotionUser } from "../protocol/users/user.js";
10
10
  export type ConnectSuccessMessage = Extract<ConnectMessage, {
11
11
  status: "success";
12
12
  }>;
13
- export type InitStatus = "waitingForConnect" | "waitingForInitResult" | "success" | "error";
13
+ /** Tracks initialization and lifetime for one host instance. */
14
+ export type InitStatus =
15
+ /** The host is waiting for the sandbox's first `connect` message. */
16
+ "waitingForConnect"
17
+ /** The host is waiting for a matching `initResult` after it sends `init`. */
18
+ | "waitingForInitResult"
19
+ /** The host is initialized and can send live updates. */
20
+ | "success"
21
+ /** The host is in an error state after an initialization failure or timeout. */
22
+ | "error"
23
+ /** The host is stopped and no longer sends or accepts sandbox messages. */
24
+ | "stopped";
14
25
  export type CustomBlockHostDataSourcesPayload = {
15
26
  bindings: NotionDataSourceBindings;
16
27
  };
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/lifecycle/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,8CAA8C,CAAA;AACtF,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,4DAA4D,CAAA;AAC1G,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,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;AAEhF,MAAM,MAAM,qBAAqB,GAAG,OAAO,CAC1C,cAAc,EACd;IAAE,MAAM,EAAE,SAAS,CAAA;CAAE,CACrB,CAAA;AAED,MAAM,MAAM,UAAU,GACnB,mBAAmB,GACnB,sBAAsB,GACtB,SAAS,GACT,OAAO,CAAA;AAEV,MAAM,MAAM,iCAAiC,GAAG;IAC/C,QAAQ,EAAE,wBAAwB,CAAA;CAClC,CAAA;AAED,MAAM,MAAM,iCAAiC,GAC1C,iCAAiC,GACjC,CAAC,CAAC,OAAO,EAAE,qBAAqB,KAAK,iCAAiC,CAAC,CAAA;AAE1E,MAAM,MAAM,2BAA2B,GAAG;IACzC,KAAK,EAAE,WAAW,CAAA;IAClB;;;OAGG;IACH,YAAY,CAAC,EAAE,kBAAkB,CAAA;IACjC,OAAO,EAAE,aAAa,CAAA;IACtB,MAAM,EAAE,YAAY,CAAA;IACpB,IAAI,EAAE,eAAe,CAAA;IACrB;;;OAGG;IACH,QAAQ,CAAC,EAAE,mBAAmB,CAAA;IAC9B,WAAW,EAAE,iCAAiC,CAAA;IAC9C,WAAW,EAAE,UAAU,CAAA;CACvB,CAAA"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/lifecycle/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,8CAA8C,CAAA;AACtF,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,4DAA4D,CAAA;AAC1G,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,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;AAEhF,MAAM,MAAM,qBAAqB,GAAG,OAAO,CAC1C,cAAc,EACd;IAAE,MAAM,EAAE,SAAS,CAAA;CAAE,CACrB,CAAA;AAED,gEAAgE;AAChE,MAAM,MAAM,UAAU;AACrB,qEAAqE;AACnE,mBAAmB;AACrB,6EAA6E;GAC3E,sBAAsB;AACxB,yDAAyD;GACvD,SAAS;AACX,gFAAgF;GAC9E,OAAO;AACT,2EAA2E;GACzE,SAAS,CAAA;AAEZ,MAAM,MAAM,iCAAiC,GAAG;IAC/C,QAAQ,EAAE,wBAAwB,CAAA;CAClC,CAAA;AAED,MAAM,MAAM,iCAAiC,GAC1C,iCAAiC,GACjC,CAAC,CAAC,OAAO,EAAE,qBAAqB,KAAK,iCAAiC,CAAC,CAAA;AAE1E,MAAM,MAAM,2BAA2B,GAAG;IACzC,KAAK,EAAE,WAAW,CAAA;IAClB;;;OAGG;IACH,YAAY,CAAC,EAAE,kBAAkB,CAAA;IACjC,OAAO,EAAE,aAAa,CAAA;IACtB,MAAM,EAAE,YAAY,CAAA;IACpB,IAAI,EAAE,eAAe,CAAA;IACrB;;;OAGG;IACH,QAAQ,CAAC,EAAE,mBAAmB,CAAA;IAC9B,WAAW,EAAE,iCAAiC,CAAA;IAC9C,WAAW,EAAE,UAAU,CAAA;CACvB,CAAA"}
@@ -23,19 +23,19 @@ export function createSandboxMessageRouter(options) {
23
23
  const response = getInvalidSandboxMessageResponse(data);
24
24
  if (response.incomingType === "connect") {
25
25
  const parsedIdentity = v.safeParse(initializationIdentitySchema, data);
26
- if (parsedIdentity.success) {
27
- const parsedVersion = v.safeParse(bridgeProtocolVersionSchema, data);
28
- const responseType = onInvalidConnect({
29
- initializationId: parsedIdentity.output.initializationId,
30
- error: initErrorForFailureReason("invalid_connect_payload"),
31
- bridgeProtocolVersion: parsedVersion.success
32
- ? parsedVersion.output.bridgeProtocolVersion
33
- : undefined,
34
- });
35
- if (responseType === "initErrorSent") {
36
- // The controller already sent `init.error`, so no need to also send a NACK.
37
- return;
38
- }
26
+ const parsedVersion = v.safeParse(bridgeProtocolVersionSchema, data);
27
+ const responseType = onInvalidConnect({
28
+ initializationId: parsedIdentity.success
29
+ ? parsedIdentity.output.initializationId
30
+ : undefined,
31
+ error: initErrorForFailureReason("invalid_connect_payload"),
32
+ bridgeProtocolVersion: parsedVersion.success
33
+ ? parsedVersion.output.bridgeProtocolVersion
34
+ : undefined,
35
+ });
36
+ if (responseType === "handled") {
37
+ // The controller sent init.error or stopped the host; neither needs a NACK.
38
+ return;
39
39
  }
40
40
  }
41
41
  if (response.incomingType === "initResult") {
@@ -30,7 +30,7 @@ export type CustomBlockHostHandlers = {
30
30
  updatePage?: (message: UpdatePageMessage) => MaybePromise<CustomBlockHostUpdatePageResult>;
31
31
  resize?: (message: ResizeMessage) => void;
32
32
  };
33
- export type CustomBlockHostInitializationFailure = CustomBlockInitErrorInfo | CustomBlockInitResultErrorInfo;
33
+ export type CustomBlockHostFailure = CustomBlockInitErrorInfo | CustomBlockInitResultErrorInfo;
34
34
  export type CustomBlockHostLogDirection = "in" | "out" | "warn";
35
35
  export type CreatePageMessageInput = InferInput<typeof createPageMessageSchema>;
36
36
  export type GetPageMessageInput = InferInput<typeof getPageMessageSchema>;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/messages/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/messages/types.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@notionhq/custom-blocks-host",
3
- "version": "0.1.26",
3
+ "version": "0.1.28",
4
4
  "description": "Reusable host runtime for Notion custom blocks.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -51,8 +51,8 @@ import type {
51
51
  } from "./lifecycle/types.js"
52
52
  import { createSandboxMessageRouter } from "./messages/sandboxMessageRouter.js"
53
53
  import type {
54
+ CustomBlockHostFailure,
54
55
  CustomBlockHostHandlers,
55
- CustomBlockHostInitializationFailure,
56
56
  CustomBlockHostLogDirection,
57
57
  } from "./messages/types.js"
58
58
  import { MIN_HOST_SUPPORTED_BRIDGE_PROTOCOL_VERSION } from "./protocolVersion.js"
@@ -64,6 +64,10 @@ import type { CustomBlockHostQueryDataSourceResult } from "./queries/types.js"
64
64
  import { validateQueryDataSourceFilter } from "./queries/validateQueryDataSourceFilter.js"
65
65
  import { validateQueryDataSourceSorts } from "./queries/validateQueryDataSourceSorts.js"
66
66
 
67
+ // Bridge protocol version that introduced page: { type: "unavailable" }.
68
+ // Earlier SDKs cannot represent this context, so the host reports context_unavailable.
69
+ const BRIDGE_PROTOCOL_VERSION_INTRODUCING_UNAVAILABLE_PAGE = 5
70
+
67
71
  export type CustomBlockHostOptions = {
68
72
  iframe: HTMLIFrameElement
69
73
  sandboxOrigin: string
@@ -87,14 +91,13 @@ export type CustomBlockHostOptions = {
87
91
  manifest: CustomBlockManifest
88
92
  initialHeight?: number
89
93
  }) => void
90
- /** Callback fired when the initialization handshake with the sandbox failed or timed out. */
91
- onInitializationFailure?: (
92
- error: CustomBlockHostInitializationFailure,
93
- ) => void
94
94
  /**
95
- * Callback fired when the current host and iframe must restart. Recreate both to continue.
95
+ * Reports a handshake failure, timeout, unavailable page context, or iframe reload.
96
+ * Iframe reloads and unavailable page context for older SDKs stop the host
97
+ * before this callback runs.
98
+ * This callback can run after initialization succeeds.
96
99
  */
97
- onRestartRequired?: () => void
100
+ onFailure?: (error: CustomBlockHostFailure) => void
98
101
  onLog?: (direction: CustomBlockHostLogDirection, payload: unknown) => void
99
102
  }
100
103
 
@@ -119,8 +122,7 @@ export function createCustomBlockHost(
119
122
  handlers,
120
123
  onManifest,
121
124
  onInitialized,
122
- onInitializationFailure,
123
- onRestartRequired,
125
+ onFailure,
124
126
  onLog,
125
127
  sandboxOrigin,
126
128
  minBridgeProtocolVersion: configuredMinBridgeProtocolVersion,
@@ -157,6 +159,10 @@ export function createCustomBlockHost(
157
159
  }
158
160
 
159
161
  function post(message: HostToSandboxMessage) {
162
+ if (initializationController.status === "stopped") {
163
+ // The parent page can stop this host. Do not send messages after the host stops.
164
+ return
165
+ }
160
166
  const sandboxMessage = normalizeHostToSandboxMessage(
161
167
  message,
162
168
  sandboxBridgeProtocolVersion,
@@ -340,6 +346,16 @@ export function createCustomBlockHost(
340
346
  }),
341
347
  prepareInit(connect) {
342
348
  sandboxBridgeProtocolVersion = connect.bridgeProtocolVersion
349
+ if (
350
+ sandboxBridgeProtocolVersion <
351
+ BRIDGE_PROTOCOL_VERSION_INTRODUCING_UNAVAILABLE_PAGE &&
352
+ page.type === "unavailable"
353
+ ) {
354
+ return {
355
+ status: "error",
356
+ error: initErrorForFailureReason("context_unavailable"),
357
+ }
358
+ }
343
359
  manifest = connect.manifest ?? initialState.manifest
344
360
  if (manifest === undefined) {
345
361
  return {
@@ -373,13 +389,13 @@ export function createCustomBlockHost(
373
389
  minBridgeProtocolVersion,
374
390
  noConnectTimeoutMs,
375
391
  noInitResultTimeoutMs,
392
+ stopHostWithFailure: stopWithFailure,
376
393
  onInitResult(message) {
377
394
  if (message.status === "success" && manifest !== undefined) {
378
395
  onInitialized?.({ manifest, initialHeight: message.initialHeight })
379
396
  }
380
397
  },
381
- onRestartRequired,
382
- onInitializationFailure,
398
+ onFailure,
383
399
  })
384
400
 
385
401
  const onMessage = createSandboxMessageRouter({
@@ -407,13 +423,21 @@ export function createCustomBlockHost(
407
423
  window.addEventListener("message", onMessage)
408
424
  iframe.addEventListener("load", initializationController.onIframeLoad)
409
425
 
426
+ function stop() {
427
+ window.removeEventListener("message", onMessage)
428
+ iframe.removeEventListener("load", initializationController.onIframeLoad)
429
+ initializationController.stop()
430
+ querySubscriptions.unsubscribeAll()
431
+ }
432
+
433
+ function stopWithFailure(error: CustomBlockHostFailure) {
434
+ stop()
435
+ emit("warn", error)
436
+ onFailure?.(error)
437
+ }
438
+
410
439
  return {
411
- stop() {
412
- window.removeEventListener("message", onMessage)
413
- iframe.removeEventListener("load", initializationController.onIframeLoad)
414
- initializationController.stop()
415
- querySubscriptions.unsubscribeAll()
416
- },
440
+ stop,
417
441
  post,
418
442
  setTheme(nextTheme) {
419
443
  theme = nextTheme
@@ -453,6 +477,16 @@ export function createCustomBlockHost(
453
477
  },
454
478
  setPage(nextPage) {
455
479
  page = nextPage
480
+ if (
481
+ nextPage.type === "unavailable" &&
482
+ sandboxBridgeProtocolVersion <
483
+ BRIDGE_PROTOCOL_VERSION_INTRODUCING_UNAVAILABLE_PAGE &&
484
+ (initializationController.status === "waitingForInitResult" ||
485
+ initializationController.status === "success")
486
+ ) {
487
+ stopWithFailure(initErrorForFailureReason("context_unavailable"))
488
+ return
489
+ }
456
490
  if (initializationController.status !== "success") {
457
491
  if (initializationController.status === "waitingForInitResult") {
458
492
  pendingPageChange = true
package/src/index.ts CHANGED
@@ -29,10 +29,10 @@ export type {
29
29
  export type {
30
30
  CreatePageMessageInput,
31
31
  CustomBlockHostCreatePageResult,
32
+ CustomBlockHostFailure,
32
33
  CustomBlockHostGetPageResult,
33
34
  CustomBlockHostGetUserResult,
34
35
  CustomBlockHostHandlers,
35
- CustomBlockHostInitializationFailure,
36
36
  CustomBlockHostListUsersResult,
37
37
  CustomBlockHostLogDirection,
38
38
  CustomBlockHostOpenPageResult,
@@ -12,7 +12,7 @@ import type { CustomBlockPage } from "@notionhq/custom-blocks-protocol/pages/pag
12
12
  import type { NotionParent } from "@notionhq/custom-blocks-protocol/parent.js"
13
13
  import type { NotionTheme } from "@notionhq/custom-blocks-protocol/theme.js"
14
14
  import type { NotionUser } from "@notionhq/custom-blocks-protocol/users/user.js"
15
- import type { CustomBlockHostInitializationFailure } from "../messages/types.js"
15
+ import type { CustomBlockHostFailure } from "../messages/types.js"
16
16
  import { initErrorForFailureReason } from "./initErrors.js"
17
17
  import { isValidBridgeProtocolVersion } from "./protocolVersion.js"
18
18
  import type {
@@ -53,11 +53,9 @@ export type InitializationControllerOptions = {
53
53
  minBridgeProtocolVersion: number
54
54
  noConnectTimeoutMs?: number
55
55
  noInitResultTimeoutMs?: number
56
+ stopHostWithFailure: (error: CustomBlockHostFailure) => void
56
57
  onInitResult?: (message: InitResultMessage) => void
57
- onRestartRequired?: () => void
58
- onInitializationFailure?: (
59
- error: CustomBlockHostInitializationFailure,
60
- ) => void
58
+ onFailure?: (error: CustomBlockHostFailure) => void
61
59
  }
62
60
 
63
61
  export type InitializationController = {
@@ -66,10 +64,10 @@ export type InitializationController = {
66
64
  handleConnect: (message: ConnectMessage) => void
67
65
  handleInitResult: (message: InitResultMessage) => void
68
66
  handleInvalidConnect: (args: {
69
- initializationId: string
67
+ initializationId: string | undefined
70
68
  error: CustomBlockInitErrorInfo
71
69
  bridgeProtocolVersion: number | undefined
72
- }) => "initErrorSent" | "nack"
70
+ }) => "handled" | "nack"
73
71
  handleInvalidInitResult: (args: {
74
72
  initializationId: string | undefined
75
73
  error: CustomBlockInitErrorInfo
@@ -91,15 +89,16 @@ export function createInitializationController(
91
89
  minBridgeProtocolVersion,
92
90
  noConnectTimeoutMs = DEFAULT_NO_CONNECT_TIMEOUT_MS,
93
91
  noInitResultTimeoutMs = DEFAULT_NO_INIT_RESULT_TIMEOUT_MS,
92
+ stopHostWithFailure,
94
93
  onInitResult,
95
- onRestartRequired,
96
- onInitializationFailure,
94
+ onFailure,
97
95
  } = options
98
96
 
99
97
  let initStatus: InitStatus = "waitingForConnect"
100
98
  let noConnectTimeoutId: number | undefined
101
99
  let noInitResultTimeoutId: number | undefined
102
100
  let activeInitializationId: string | undefined
101
+ let hasObservedIframeLoad = false
103
102
  let hasConnectedSandbox = false
104
103
 
105
104
  function clearNoConnectTimeout() {
@@ -133,7 +132,7 @@ export function createInitializationController(
133
132
  status: "error",
134
133
  error,
135
134
  })
136
- onInitializationFailure?.(error)
135
+ onFailure?.(error)
137
136
  }
138
137
 
139
138
  function sendInit(connect: ConnectSuccessMessage): boolean {
@@ -176,24 +175,16 @@ export function createInitializationController(
176
175
  message: "Sandbox did not send initResult",
177
176
  initializationId: connect.initializationId,
178
177
  })
179
- onInitializationFailure?.(error)
178
+ onFailure?.(error)
180
179
  }, noInitResultTimeoutMs)
181
180
  return true
182
181
  }
183
182
 
184
183
  function handleConnect(message: ConnectMessage) {
184
+ if (initStatus === "stopped") {
185
+ return
186
+ }
185
187
  if (initStatus !== "waitingForConnect") {
186
- // A sandbox document sends `connect` exactly once, so this connect
187
- // belongs to a replacement document — or arrived after this host
188
- // already gave up waiting. The host is single-initialization; the
189
- // embedder must recreate it together with the iframe for the new
190
- // document to initialize.
191
- warn({
192
- message:
193
- "Ignored connect after initialization settled; recreate the host and iframe to re-initialize",
194
- initializationId: message.initializationId,
195
- })
196
- onRestartRequired?.()
197
188
  return
198
189
  }
199
190
 
@@ -238,16 +229,21 @@ export function createInitializationController(
238
229
  }
239
230
 
240
231
  function handleInvalidConnect(args: {
241
- initializationId: string
232
+ initializationId: string | undefined
242
233
  error: CustomBlockInitErrorInfo
243
234
  bridgeProtocolVersion: number | undefined
244
- }): "initErrorSent" | "nack" {
235
+ }): "handled" | "nack" {
236
+ if (initStatus === "stopped") {
237
+ return "handled"
238
+ }
245
239
  if (initStatus !== "waitingForConnect") {
246
- // `connect` messages received when not waiting for one are NACKed like generic invalid messages.
247
- return "nack"
240
+ return "handled"
248
241
  }
249
242
 
250
243
  const { initializationId, bridgeProtocolVersion } = args
244
+ if (initializationId === undefined) {
245
+ return "nack"
246
+ }
251
247
  let error = args.error
252
248
  // Prefer a specific protocol version error over the generic connect payload error
253
249
  // when the malformed message includes a numeric version.
@@ -262,7 +258,7 @@ export function createInitializationController(
262
258
  }
263
259
  }
264
260
  sendInitError({ initializationId, error })
265
- return "initErrorSent"
261
+ return "handled"
266
262
  }
267
263
 
268
264
  function handleInvalidInitResult(args: {
@@ -281,10 +277,23 @@ export function createInitializationController(
281
277
  clearNoInitResultTimeout()
282
278
  initStatus = "error"
283
279
  warn(error)
284
- onInitializationFailure?.(error)
280
+ onFailure?.(error)
285
281
  }
286
282
 
287
283
  function onIframeLoad() {
284
+ if (initStatus === "stopped") {
285
+ return
286
+ }
287
+ if (hasObservedIframeLoad) {
288
+ stopHostWithFailure({
289
+ code: "iframe_reloaded",
290
+ message:
291
+ "The iframe reloaded. Create a new host and iframe to reconnect.",
292
+ isRetryable: true,
293
+ })
294
+ return
295
+ }
296
+ hasObservedIframeLoad = true
288
297
  clearNoConnectTimeout()
289
298
  if (initStatus !== "waitingForConnect") {
290
299
  // Only set the timeout if the iframe loaded before the `connect` message was sent.
@@ -298,7 +307,7 @@ export function createInitializationController(
298
307
  initStatus = "error"
299
308
  const error = initErrorForFailureReason("no_connect")
300
309
  warn(error)
301
- onInitializationFailure?.(error)
310
+ onFailure?.(error)
302
311
  }, noConnectTimeoutMs)
303
312
  }
304
313
 
@@ -319,12 +328,13 @@ export function createInitializationController(
319
328
  flushPendingHostStateChanges()
320
329
  } else {
321
330
  initStatus = "error"
322
- onInitializationFailure?.(message.error)
331
+ onFailure?.(message.error)
323
332
  }
324
333
  onInitResult?.(message)
325
334
  }
326
335
 
327
336
  function stop() {
337
+ initStatus = "stopped"
328
338
  clearNoConnectTimeout()
329
339
  clearNoInitResultTimeout()
330
340
  }
@@ -13,11 +13,18 @@ export type ConnectSuccessMessage = Extract<
13
13
  { status: "success" }
14
14
  >
15
15
 
16
+ /** Tracks initialization and lifetime for one host instance. */
16
17
  export type InitStatus =
18
+ /** The host is waiting for the sandbox's first `connect` message. */
17
19
  | "waitingForConnect"
20
+ /** The host is waiting for a matching `initResult` after it sends `init`. */
18
21
  | "waitingForInitResult"
22
+ /** The host is initialized and can send live updates. */
19
23
  | "success"
24
+ /** The host is in an error state after an initialization failure or timeout. */
20
25
  | "error"
26
+ /** The host is stopped and no longer sends or accepts sandbox messages. */
27
+ | "stopped"
21
28
 
22
29
  export type CustomBlockHostDataSourcesPayload = {
23
30
  bindings: NotionDataSourceBindings
@@ -90,19 +90,19 @@ export function createSandboxMessageRouter(
90
90
 
91
91
  if (response.incomingType === "connect") {
92
92
  const parsedIdentity = v.safeParse(initializationIdentitySchema, data)
93
- if (parsedIdentity.success) {
94
- const parsedVersion = v.safeParse(bridgeProtocolVersionSchema, data)
95
- const responseType = onInvalidConnect({
96
- initializationId: parsedIdentity.output.initializationId,
97
- error: initErrorForFailureReason("invalid_connect_payload"),
98
- bridgeProtocolVersion: parsedVersion.success
99
- ? parsedVersion.output.bridgeProtocolVersion
100
- : undefined,
101
- })
102
- if (responseType === "initErrorSent") {
103
- // The controller already sent `init.error`, so no need to also send a NACK.
104
- return
105
- }
93
+ const parsedVersion = v.safeParse(bridgeProtocolVersionSchema, data)
94
+ const responseType = onInvalidConnect({
95
+ initializationId: parsedIdentity.success
96
+ ? parsedIdentity.output.initializationId
97
+ : undefined,
98
+ error: initErrorForFailureReason("invalid_connect_payload"),
99
+ bridgeProtocolVersion: parsedVersion.success
100
+ ? parsedVersion.output.bridgeProtocolVersion
101
+ : undefined,
102
+ })
103
+ if (responseType === "handled") {
104
+ // The controller sent init.error or stopped the host; neither needs a NACK.
105
+ return
106
106
  }
107
107
  }
108
108
 
@@ -109,7 +109,7 @@ export type CustomBlockHostHandlers = {
109
109
  resize?: (message: ResizeMessage) => void
110
110
  }
111
111
 
112
- export type CustomBlockHostInitializationFailure =
112
+ export type CustomBlockHostFailure =
113
113
  | CustomBlockInitErrorInfo
114
114
  | CustomBlockInitResultErrorInfo
115
115