@notionhq/custom-blocks-host 0.1.7 → 0.1.9

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
@@ -72,29 +72,42 @@ const host = createCustomBlockHost({
72
72
  listUsers: async (message) => listUsers(message),
73
73
  resize: (message) => resizeIframe(message.height),
74
74
  },
75
- onConnect: (message) => {
76
- console.log("Connected SDK", message.sdkVersion);
75
+ onManifest: (manifest) => {
76
+ console.log("Selected manifest", manifest);
77
77
  },
78
- onIgnoredConnect: () => {
79
- remountHostAndIframe();
78
+ onInitialized: (state) => {
79
+ console.log("Initialized manifest", state.manifest);
80
80
  },
81
81
  onInitializationFailure: (error) => {
82
82
  showInitializationError(error);
83
83
  },
84
+ onRestartRequired: () => {
85
+ remountHostAndIframe();
86
+ },
84
87
  onLog: (direction, payload) => {
85
88
  console.log(direction, payload);
86
89
  },
87
90
  });
88
91
  ```
89
92
 
90
- The host runs one initialization per instance. `onIgnoredConnect` fires when a
91
- `connect` arrives that the settled host cannot serve: the iframe document was
92
- replaced, or the document's first `connect` came after the no-connect timeout.
93
- Recreate the host and the iframe together so the document runs a fresh
94
- handshake against current host state.
93
+ Each host instance initializes once:
95
94
 
96
- `onInitializationFailure` receives structured errors from failed `init`
97
- messages, failed `initResult` messages, and initialization timeouts.
95
+ - `onManifest` provides the selected manifest as soon as it is available after
96
+ `connect`, before binding validation and `onInitialized`. Use it to configure
97
+ bindings even when initialization cannot yet succeed.
98
+
99
+ - `onInitialized` reports success after the sandbox accepts initialization. It
100
+ provides the selected `manifest` and the sandbox's optional `initialHeight`.
101
+ - `onInitializationFailure` reports an initialization error or timeout.
102
+ - `onRestartRequired` reports a connection attempt after the host accepts its
103
+ first connection or stops waiting. Recreate the host and iframe together
104
+ to accept a new connection.
105
+ This can occur after the iframe document changes or a connection arrives
106
+ after a timeout.
107
+
108
+ The host uses the manifest from `connect`, or `initialState.manifest` if
109
+ `connect` omits it. If neither exists, initialization fails with
110
+ `manifest_unavailable`.
98
111
 
99
112
  `queryDataSource` is required. Other request handlers are optional. Every
100
113
  request handler may return its result synchronously or as a promise. Results
@@ -157,7 +170,7 @@ failure. It logs and ignores a valid `initResult` with a stale
157
170
  `initializationId` or an unexpected state.
158
171
 
159
172
  Hosts do not retry malformed messages. A host that receives a `connect` after
160
- initialization has settled must use `onIgnoredConnect` to recreate the host
173
+ initialization has settled must use `onRestartRequired` to recreate the host
161
174
  and iframe together.
162
175
 
163
176
  ## Updating live state
@@ -179,6 +192,11 @@ held until initialization completes. `refreshQuery()` re-runs
179
192
  `queryDataSource` for every active subscription with the supplied data source
180
193
  ID, preserving each subscription's filter, sorts, and limit.
181
194
 
195
+ The runtime removes a subscription when it receives
196
+ `unsubscribeDataSourceQuery`. Repeated unsubscribe messages are safe. The
197
+ runtime also ignores results from a query that was unsubscribed while its
198
+ handler was running.
199
+
182
200
  Call `stop()` when the iframe is removed. It detaches listeners, clears
183
201
  timeouts, and discards active query subscriptions.
184
202
 
@@ -1,13 +1,12 @@
1
1
  import { type NotionContrastMode } from "./protocol/contrast.js";
2
2
  import type { NotionDataSourceId } from "./protocol/ids.js";
3
- import type { ConnectMessage } from "./protocol/messages/connect.js";
3
+ import type { CustomBlockManifest } from "./protocol/manifest.js";
4
4
  import type { HostToSandboxMessage } from "./protocol/messages/hostToSandbox.js";
5
- import type { InitResultMessage } from "./protocol/messages/initResult.js";
6
5
  import type { CustomBlockPage } from "./protocol/pages/page.js";
7
6
  import type { NotionParent } from "./protocol/parent.js";
8
7
  import type { NotionTheme } from "./protocol/theme.js";
9
8
  import type { NotionUser } from "./protocol/users/user.js";
10
- import type { ConnectSuccessMessage, CustomBlockHostDataSourcesPayload, CustomBlockHostInitialState } from "./lifecycle/types.js";
9
+ import type { CustomBlockHostDataSourcesPayload, CustomBlockHostInitialState } from "./lifecycle/types.js";
11
10
  import type { CustomBlockHostHandlers, CustomBlockHostInitializationFailure, CustomBlockHostLogDirection } from "./messages/types.js";
12
11
  export type CustomBlockHostOptions = {
13
12
  iframe: HTMLIFrameElement;
@@ -17,17 +16,20 @@ export type CustomBlockHostOptions = {
17
16
  noInitResultTimeoutMs?: number;
18
17
  initialState: CustomBlockHostInitialState;
19
18
  handlers: CustomBlockHostHandlers;
20
- onConnect?: (message: ConnectSuccessMessage) => void;
21
- onInitResult?: (message: InitResultMessage) => void;
19
+ /** Called as soon as the `manifest` is available after `connect`, before `onInitialized` and binding validation. */
20
+ onManifest?: (manifest: CustomBlockManifest) => void;
21
+ /** Callback fired when the initialization handshake with the sandbox completed successfully. */
22
+ onInitialized?: (state: {
23
+ manifest: CustomBlockManifest;
24
+ initialHeight?: number;
25
+ }) => void;
26
+ /** Callback fired when the initialization handshake with the sandbox failed or timed out. */
22
27
  onInitializationFailure?: (error: CustomBlockHostInitializationFailure) => void;
23
28
  /**
24
- * A `connect` arrived after this host's initialization settled the
25
- * document was replaced, or its first `connect` came after the host
26
- * stopped waiting. Either way the host cannot serve it: the embedder
27
- * should recreate host and iframe together so the document can run a
28
- * fresh handshake.
29
+ * Callback fired when the current host and iframe must restart.
30
+ * Recreate both to continue.
29
31
  */
30
- onIgnoredConnect?: (message: ConnectMessage) => void;
32
+ onRestartRequired?: () => void;
31
33
  onLog?: (direction: CustomBlockHostLogDirection, payload: unknown) => void;
32
34
  };
33
35
  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;AACrD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yCAAyC,CAAA;AAEjF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sDAAsD,CAAA;AAa1F,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,4DAA4D,CAAA;AAEtG,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yDAAyD,CAAA;AAahG,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,qBAAqB,EACrB,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,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,qBAAqB,KAAK,IAAI,CAAA;IACpD,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE,iBAAiB,KAAK,IAAI,CAAA;IACnD,uBAAuB,CAAC,EAAE,CACzB,KAAK,EAAE,oCAAoC,KACvC,IAAI,CAAA;IACT;;;;;;OAMG;IACH,gBAAgB,CAAC,EAAE,CAAC,OAAO,EAAE,cAAc,KAAK,IAAI,CAAA;IACpD,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,CAidvB"}
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"}
@@ -13,7 +13,7 @@ const initializationIdentitySchema = v.object({
13
13
  initializationId: v.string(),
14
14
  });
15
15
  export function createCustomBlockHost(options) {
16
- const { iframe, initialState, handlers, onConnect, onInitResult, onInitializationFailure, onIgnoredConnect, onLog, sandboxOrigin, minBridgeProtocolVersion: configuredMinBridgeProtocolVersion, noConnectTimeoutMs, noInitResultTimeoutMs, } = options;
16
+ const { iframe, initialState, handlers, onManifest, onInitialized, onInitializationFailure, onRestartRequired, onLog, sandboxOrigin, minBridgeProtocolVersion: configuredMinBridgeProtocolVersion, noConnectTimeoutMs, noInitResultTimeoutMs, } = options;
17
17
  const minBridgeProtocolVersion = Math.max(configuredMinBridgeProtocolVersion ?? CUSTOM_BLOCK_BRIDGE_PROTOCOL_VERSION, CUSTOM_BLOCK_BRIDGE_PROTOCOL_VERSION);
18
18
  let theme = initialState.theme;
19
19
  let contrastMode = initialState.contrastMode ?? DEFAULT_CONTRAST_MODE;
@@ -54,7 +54,7 @@ export function createCustomBlockHost(options) {
54
54
  };
55
55
  post(message);
56
56
  }
57
- async function handleQuery(message, token = querySubscriptions.track(message)) {
57
+ async function handleQuery(message, token = querySubscriptions.subscribe(message)) {
58
58
  const collectionSchema = getCollectionSchema(activeDataSources, message.dataSourceId);
59
59
  const filterError = validateQueryDataSourceFilter(message.filter, collectionSchema);
60
60
  if (filterError !== undefined) {
@@ -188,6 +188,13 @@ export function createCustomBlockHost(options) {
188
188
  }),
189
189
  prepareInit(connect) {
190
190
  manifest = connect.manifest ?? initialState.manifest;
191
+ if (manifest === undefined) {
192
+ return {
193
+ status: "error",
194
+ error: initErrorForFailureReason("manifest_unavailable"),
195
+ };
196
+ }
197
+ onManifest?.(manifest);
191
198
  const resolvedDataSources = typeof dataSources === "function" ? dataSources(connect) : dataSources;
192
199
  const bindingErrorCode = getBindingErrorCode(manifest, resolvedDataSources);
193
200
  if (bindingErrorCode !== undefined) {
@@ -209,9 +216,12 @@ export function createCustomBlockHost(options) {
209
216
  minBridgeProtocolVersion,
210
217
  noConnectTimeoutMs,
211
218
  noInitResultTimeoutMs,
212
- onConnect,
213
- onInitResult,
214
- onIgnoredConnect,
219
+ onInitResult(message) {
220
+ if (message.status === "success" && manifest !== undefined) {
221
+ onInitialized?.({ manifest, initialHeight: message.initialHeight });
222
+ }
223
+ },
224
+ onRestartRequired,
215
225
  onInitializationFailure,
216
226
  });
217
227
  function onInvalidSandboxMessage(data, issues) {
@@ -282,6 +292,9 @@ export function createCustomBlockHost(options) {
282
292
  case "queryDataSource":
283
293
  void handleQuery(message);
284
294
  return;
295
+ case "unsubscribeDataSourceQuery":
296
+ querySubscriptions.unsubscribe(message.subscriptionId);
297
+ return;
285
298
  case "createPage":
286
299
  void handleCreatePage(message);
287
300
  return;
@@ -313,7 +326,7 @@ export function createCustomBlockHost(options) {
313
326
  window.removeEventListener("message", onMessage);
314
327
  iframe.removeEventListener("load", initializationController.onIframeLoad);
315
328
  initializationController.stop();
316
- querySubscriptions.clear();
329
+ querySubscriptions.unsubscribeAll();
317
330
  },
318
331
  post,
319
332
  setTheme(nextTheme) {
@@ -365,6 +378,7 @@ export function createCustomBlockHost(options) {
365
378
  },
366
379
  setDataSources(nextDataSources) {
367
380
  if (initializationController.hasConnectedSandbox &&
381
+ manifest !== undefined &&
368
382
  getBindingErrorCode(manifest, nextDataSources) !== undefined) {
369
383
  emit("warn", {
370
384
  message: "Ignored invalid data source bindings update",
@@ -36,9 +36,8 @@ export type InitializationControllerOptions = {
36
36
  minBridgeProtocolVersion: number;
37
37
  noConnectTimeoutMs?: number;
38
38
  noInitResultTimeoutMs?: number;
39
- onConnect?: (message: ConnectSuccessMessage) => void;
40
39
  onInitResult?: (message: InitResultMessage) => void;
41
- onIgnoredConnect?: (message: ConnectMessage) => void;
40
+ onRestartRequired?: () => void;
42
41
  onInitializationFailure?: (error: CustomBlockHostInitializationFailure) => void;
43
42
  };
44
43
  export type InitializationController = {
@@ -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":"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"}
@@ -3,7 +3,7 @@ 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, onConnect, onInitResult, onIgnoredConnect, onInitializationFailure, } = 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;
7
7
  let initStatus = "waitingForConnect";
8
8
  let noConnectTimeoutId;
9
9
  let noInitResultTimeoutId;
@@ -40,7 +40,6 @@ export function createInitializationController(options) {
40
40
  if (initStatus !== "waitingForConnect") {
41
41
  return false;
42
42
  }
43
- onConnect?.(connect);
44
43
  const preparation = prepareInit(connect);
45
44
  if (preparation.status === "error") {
46
45
  sendInitError(connect.initializationId, preparation.error);
@@ -89,7 +88,7 @@ export function createInitializationController(options) {
89
88
  message: "Ignored connect after initialization settled; recreate the host and iframe to re-initialize",
90
89
  initializationId: message.initializationId,
91
90
  });
92
- onIgnoredConnect?.(message);
91
+ onRestartRequired?.();
93
92
  return;
94
93
  }
95
94
  // Verify the bridge protocol version is valid.
@@ -21,7 +21,11 @@ export type CustomBlockHostInitialState = {
21
21
  blockId: NotionBlockId;
22
22
  parent: NotionParent;
23
23
  page: CustomBlockPage;
24
- manifest: CustomBlockManifest;
24
+ /**
25
+ * The host's persisted manifest, when one exists. If omitted, the host
26
+ * requires the sandbox to provide a manifest in `connect`.
27
+ */
28
+ manifest?: CustomBlockManifest;
25
29
  dataSources: CustomBlockHostInitialDataSources;
26
30
  currentUser: NotionUser;
27
31
  };
@@ -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;IAElB,YAAY,CAAC,EAAE,kBAAkB,CAAA;IACjC,OAAO,EAAE,aAAa,CAAA;IACtB,MAAM,EAAE,YAAY,CAAA;IACpB,IAAI,EAAE,eAAe,CAAA;IACrB,QAAQ,EAAE,mBAAmB,CAAA;IAC7B,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,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;IAElB,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"}
@@ -39,6 +39,7 @@ export * from "./messages/queryDataSourceResult.js";
39
39
  export * from "./messages/resize.js";
40
40
  export * from "./messages/sandboxToHost.js";
41
41
  export * from "./messages/themeChanged.js";
42
+ export * from "./messages/unsubscribeDataSourceQuery.js";
42
43
  export * from "./messages/updatePage.js";
43
44
  export * from "./messages/updatePageResult.js";
44
45
  export * from "./pages/page.js";
@@ -39,6 +39,7 @@ export * from "./messages/queryDataSourceResult.js";
39
39
  export * from "./messages/resize.js";
40
40
  export * from "./messages/sandboxToHost.js";
41
41
  export * from "./messages/themeChanged.js";
42
+ export * from "./messages/unsubscribeDataSourceQuery.js";
42
43
  export * from "./messages/updatePage.js";
43
44
  export * from "./messages/updatePageResult.js";
44
45
  export * from "./pages/page.js";
@@ -411,6 +411,9 @@ export declare const sandboxToHostMessageSchema: v.VariantSchema<"type", [v.Vari
411
411
  readonly propertyId: v.StringSchema<undefined>;
412
412
  readonly direction: v.PicklistSchema<["ascending", "descending"], undefined>;
413
413
  }, undefined>, undefined>, undefined>;
414
+ }, undefined>, v.ObjectSchema<{
415
+ readonly type: v.LiteralSchema<"unsubscribeDataSourceQuery", undefined>;
416
+ readonly subscriptionId: v.StringSchema<undefined>;
414
417
  }, undefined>, v.ObjectSchema<{
415
418
  readonly type: v.LiteralSchema<"createPage", undefined>;
416
419
  readonly requestId: v.StringSchema<undefined>;
@@ -8,6 +8,7 @@ import { invalidHostMessageSchema } from "./invalidHostMessage.js";
8
8
  import { listUsersMessageSchema } from "./listUsers.js";
9
9
  import { queryDataSourceMessageSchema } from "./queryDataSource.js";
10
10
  import { resizeMessageSchema } from "./resize.js";
11
+ import { unsubscribeDataSourceQueryMessageSchema } from "./unsubscribeDataSourceQuery.js";
11
12
  import { updatePageMessageSchema } from "./updatePage.js";
12
13
  /**
13
14
  * Discriminated union of every message the sandbox is allowed to send the host. Useful for hosts
@@ -17,6 +18,7 @@ export const sandboxToHostMessageSchema = v.variant("type", [
17
18
  connectMessageSchema,
18
19
  initResultMessageSchema,
19
20
  queryDataSourceMessageSchema,
21
+ unsubscribeDataSourceQueryMessageSchema,
20
22
  createPageMessageSchema,
21
23
  getPageMessageSchema,
22
24
  getUserMessageSchema,
@@ -0,0 +1,10 @@
1
+ import * as v from "valibot";
2
+ /**
3
+ * Tells the host to stop an active data source query subscription.
4
+ */
5
+ export declare const unsubscribeDataSourceQueryMessageSchema: v.ObjectSchema<{
6
+ readonly type: v.LiteralSchema<"unsubscribeDataSourceQuery", undefined>;
7
+ /** The subscription to remove from the host. */
8
+ readonly subscriptionId: v.StringSchema<undefined>;
9
+ }, undefined>;
10
+ export type UnsubscribeDataSourceQueryMessage = v.InferOutput<typeof unsubscribeDataSourceQueryMessageSchema>;
@@ -0,0 +1,9 @@
1
+ import * as v from "valibot";
2
+ /**
3
+ * Tells the host to stop an active data source query subscription.
4
+ */
5
+ export const unsubscribeDataSourceQueryMessageSchema = v.object({
6
+ type: v.literal("unsubscribeDataSourceQuery"),
7
+ /** The subscription to remove from the host. */
8
+ subscriptionId: v.string(),
9
+ });
@@ -9,9 +9,10 @@ export type QuerySubscriptionToken = {
9
9
  };
10
10
  export declare class QuerySubscriptions {
11
11
  private readonly bySubscriptionId;
12
- track(message: QueryDataSourceMessage): QuerySubscriptionToken;
12
+ subscribe(message: QueryDataSourceMessage): QuerySubscriptionToken;
13
13
  isCurrent(subscriptionId: string, token: QuerySubscriptionToken): boolean;
14
+ unsubscribe(subscriptionId: string): void;
14
15
  refresh(dataSourceId: NotionDataSourceId): QueryRefresh[];
15
- clear(): void;
16
+ unsubscribeAll(): void;
16
17
  }
17
18
  //# sourceMappingURL=querySubscriptions.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"querySubscriptions.d.ts","sourceRoot":"","sources":["../../src/queries/querySubscriptions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yCAAyC,CAAA;AACjF,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,8DAA8D,CAAA;AAE1G,MAAM,MAAM,YAAY,GAAG;IAC1B,OAAO,EAAE,sBAAsB,CAAA;IAC/B,KAAK,EAAE,sBAAsB,CAAA;CAC7B,CAAA;AAED,MAAM,MAAM,sBAAsB,GAAG;IACpC,QAAQ,CAAC,IAAI,EAAE,wBAAwB,CAAA;CACvC,CAAA;AAOD,qBAAa,kBAAkB;IAC9B,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAA4C;IAE7E,KAAK,CAAC,OAAO,EAAE,sBAAsB,GAAG,sBAAsB,CAS7D;IAED,SAAS,CAAC,cAAc,EAAE,MAAM,EAAE,KAAK,EAAE,sBAAsB,GAAG,OAAO,CAExE;IAED,OAAO,CAAC,YAAY,EAAE,kBAAkB,GAAG,YAAY,EAAE,CAgBxD;IAED,KAAK,SAEJ;CACD"}
1
+ {"version":3,"file":"querySubscriptions.d.ts","sourceRoot":"","sources":["../../src/queries/querySubscriptions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yCAAyC,CAAA;AACjF,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,8DAA8D,CAAA;AAE1G,MAAM,MAAM,YAAY,GAAG;IAC1B,OAAO,EAAE,sBAAsB,CAAA;IAC/B,KAAK,EAAE,sBAAsB,CAAA;CAC7B,CAAA;AAED,MAAM,MAAM,sBAAsB,GAAG;IACpC,QAAQ,CAAC,IAAI,EAAE,wBAAwB,CAAA;CACvC,CAAA;AAOD,qBAAa,kBAAkB;IAC9B,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAA4C;IAE7E,SAAS,CAAC,OAAO,EAAE,sBAAsB,GAAG,sBAAsB,CASjE;IAED,SAAS,CAAC,cAAc,EAAE,MAAM,EAAE,KAAK,EAAE,sBAAsB,GAAG,OAAO,CAExE;IAED,WAAW,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI,CAExC;IAED,OAAO,CAAC,YAAY,EAAE,kBAAkB,GAAG,YAAY,EAAE,CAgBxD;IAED,cAAc,SAEb;CACD"}
@@ -1,6 +1,6 @@
1
1
  export class QuerySubscriptions {
2
2
  bySubscriptionId = new Map();
3
- track(message) {
3
+ subscribe(message) {
4
4
  const token = {
5
5
  _tag: "QuerySubscriptionToken",
6
6
  };
@@ -13,6 +13,9 @@ export class QuerySubscriptions {
13
13
  isCurrent(subscriptionId, token) {
14
14
  return this.bySubscriptionId.get(subscriptionId)?.token === token;
15
15
  }
16
+ unsubscribe(subscriptionId) {
17
+ this.bySubscriptionId.delete(subscriptionId);
18
+ }
16
19
  refresh(dataSourceId) {
17
20
  const refreshes = [];
18
21
  for (const queryState of this.bySubscriptionId.values()) {
@@ -30,7 +33,7 @@ export class QuerySubscriptions {
30
33
  }
31
34
  return refreshes;
32
35
  }
33
- clear() {
36
+ unsubscribeAll() {
34
37
  this.bySubscriptionId.clear();
35
38
  }
36
39
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@notionhq/custom-blocks-host",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "description": "Reusable host runtime for Notion custom blocks.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -4,7 +4,6 @@ import {
4
4
  } from "@notionhq/custom-blocks-protocol/contrast.js"
5
5
  import type { NotionDataSourceId } from "@notionhq/custom-blocks-protocol/ids.js"
6
6
  import type { CustomBlockManifest } from "@notionhq/custom-blocks-protocol/manifest.js"
7
- import type { ConnectMessage } from "@notionhq/custom-blocks-protocol/messages/connect.js"
8
7
  import type { ContrastModeChangedMessage } from "@notionhq/custom-blocks-protocol/messages/contrastModeChanged.js"
9
8
  import type { CreatePageMessage } from "@notionhq/custom-blocks-protocol/messages/createPage.js"
10
9
  import type { CreatePageResultMessage } from "@notionhq/custom-blocks-protocol/messages/createPageResult.js"
@@ -19,7 +18,6 @@ import type {
19
18
  } from "@notionhq/custom-blocks-protocol/messages/getUser.js"
20
19
  import type { HostToSandboxMessage } from "@notionhq/custom-blocks-protocol/messages/hostToSandbox.js"
21
20
  import type { CustomBlockInitErrorCode } from "@notionhq/custom-blocks-protocol/messages/init.js"
22
- import type { InitResultMessage } from "@notionhq/custom-blocks-protocol/messages/initResult.js"
23
21
  import type {
24
22
  ListUsersMessage,
25
23
  ListUsersResultMessage,
@@ -41,7 +39,6 @@ import * as v from "valibot"
41
39
  import { initErrorForFailureReason } from "./lifecycle/initErrors.js"
42
40
  import { createInitializationController } from "./lifecycle/initializationController.js"
43
41
  import type {
44
- ConnectSuccessMessage,
45
42
  CustomBlockHostDataSourcesPayload,
46
43
  CustomBlockHostInitialState,
47
44
  } from "./lifecycle/types.js"
@@ -68,19 +65,22 @@ export type CustomBlockHostOptions = {
68
65
  noInitResultTimeoutMs?: number
69
66
  initialState: CustomBlockHostInitialState
70
67
  handlers: CustomBlockHostHandlers
71
- onConnect?: (message: ConnectSuccessMessage) => void
72
- onInitResult?: (message: InitResultMessage) => void
68
+ /** Called as soon as the `manifest` is available after `connect`, before `onInitialized` and binding validation. */
69
+ onManifest?: (manifest: CustomBlockManifest) => void
70
+ /** Callback fired when the initialization handshake with the sandbox completed successfully. */
71
+ onInitialized?: (state: {
72
+ manifest: CustomBlockManifest
73
+ initialHeight?: number
74
+ }) => void
75
+ /** Callback fired when the initialization handshake with the sandbox failed or timed out. */
73
76
  onInitializationFailure?: (
74
77
  error: CustomBlockHostInitializationFailure,
75
78
  ) => void
76
79
  /**
77
- * A `connect` arrived after this host's initialization settled the
78
- * document was replaced, or its first `connect` came after the host
79
- * stopped waiting. Either way the host cannot serve it: the embedder
80
- * should recreate host and iframe together so the document can run a
81
- * fresh handshake.
80
+ * Callback fired when the current host and iframe must restart.
81
+ * Recreate both to continue.
82
82
  */
83
- onIgnoredConnect?: (message: ConnectMessage) => void
83
+ onRestartRequired?: () => void
84
84
  onLog?: (direction: CustomBlockHostLogDirection, payload: unknown) => void
85
85
  }
86
86
 
@@ -107,10 +107,10 @@ export function createCustomBlockHost(
107
107
  iframe,
108
108
  initialState,
109
109
  handlers,
110
- onConnect,
111
- onInitResult,
110
+ onManifest,
111
+ onInitialized,
112
112
  onInitializationFailure,
113
- onIgnoredConnect,
113
+ onRestartRequired,
114
114
  onLog,
115
115
  sandboxOrigin,
116
116
  minBridgeProtocolVersion: configuredMinBridgeProtocolVersion,
@@ -171,7 +171,7 @@ export function createCustomBlockHost(
171
171
 
172
172
  async function handleQuery(
173
173
  message: QueryDataSourceMessage,
174
- token: QuerySubscriptionToken = querySubscriptions.track(message),
174
+ token: QuerySubscriptionToken = querySubscriptions.subscribe(message),
175
175
  ) {
176
176
  const collectionSchema = getCollectionSchema(
177
177
  activeDataSources,
@@ -322,6 +322,13 @@ export function createCustomBlockHost(
322
322
  }),
323
323
  prepareInit(connect) {
324
324
  manifest = connect.manifest ?? initialState.manifest
325
+ if (manifest === undefined) {
326
+ return {
327
+ status: "error",
328
+ error: initErrorForFailureReason("manifest_unavailable"),
329
+ }
330
+ }
331
+ onManifest?.(manifest)
325
332
  const resolvedDataSources =
326
333
  typeof dataSources === "function" ? dataSources(connect) : dataSources
327
334
  const bindingErrorCode = getBindingErrorCode(
@@ -347,9 +354,12 @@ export function createCustomBlockHost(
347
354
  minBridgeProtocolVersion,
348
355
  noConnectTimeoutMs,
349
356
  noInitResultTimeoutMs,
350
- onConnect,
351
- onInitResult,
352
- onIgnoredConnect,
357
+ onInitResult(message) {
358
+ if (message.status === "success" && manifest !== undefined) {
359
+ onInitialized?.({ manifest, initialHeight: message.initialHeight })
360
+ }
361
+ },
362
+ onRestartRequired,
353
363
  onInitializationFailure,
354
364
  })
355
365
 
@@ -435,6 +445,9 @@ export function createCustomBlockHost(
435
445
  case "queryDataSource":
436
446
  void handleQuery(message)
437
447
  return
448
+ case "unsubscribeDataSourceQuery":
449
+ querySubscriptions.unsubscribe(message.subscriptionId)
450
+ return
438
451
  case "createPage":
439
452
  void handleCreatePage(message)
440
453
  return
@@ -468,7 +481,7 @@ export function createCustomBlockHost(
468
481
  window.removeEventListener("message", onMessage)
469
482
  iframe.removeEventListener("load", initializationController.onIframeLoad)
470
483
  initializationController.stop()
471
- querySubscriptions.clear()
484
+ querySubscriptions.unsubscribeAll()
472
485
  },
473
486
  post,
474
487
  setTheme(nextTheme) {
@@ -521,6 +534,7 @@ export function createCustomBlockHost(
521
534
  setDataSources(nextDataSources) {
522
535
  if (
523
536
  initializationController.hasConnectedSandbox &&
537
+ manifest !== undefined &&
524
538
  getBindingErrorCode(manifest, nextDataSources) !== undefined
525
539
  ) {
526
540
  emit("warn", {
@@ -53,9 +53,8 @@ export type InitializationControllerOptions = {
53
53
  minBridgeProtocolVersion: number
54
54
  noConnectTimeoutMs?: number
55
55
  noInitResultTimeoutMs?: number
56
- onConnect?: (message: ConnectSuccessMessage) => void
57
56
  onInitResult?: (message: InitResultMessage) => void
58
- onIgnoredConnect?: (message: ConnectMessage) => void
57
+ onRestartRequired?: () => void
59
58
  onInitializationFailure?: (
60
59
  error: CustomBlockHostInitializationFailure,
61
60
  ) => void
@@ -85,9 +84,8 @@ export function createInitializationController(
85
84
  minBridgeProtocolVersion,
86
85
  noConnectTimeoutMs = DEFAULT_NO_CONNECT_TIMEOUT_MS,
87
86
  noInitResultTimeoutMs = DEFAULT_NO_INIT_RESULT_TIMEOUT_MS,
88
- onConnect,
89
87
  onInitResult,
90
- onIgnoredConnect,
88
+ onRestartRequired,
91
89
  onInitializationFailure,
92
90
  } = options
93
91
 
@@ -134,7 +132,6 @@ export function createInitializationController(
134
132
  if (initStatus !== "waitingForConnect") {
135
133
  return false
136
134
  }
137
- onConnect?.(connect)
138
135
  const preparation = prepareInit(connect)
139
136
  if (preparation.status === "error") {
140
137
  sendInitError(connect.initializationId, preparation.error)
@@ -185,7 +182,7 @@ export function createInitializationController(
185
182
  "Ignored connect after initialization settled; recreate the host and iframe to re-initialize",
186
183
  initializationId: message.initializationId,
187
184
  })
188
- onIgnoredConnect?.(message)
185
+ onRestartRequired?.()
189
186
  return
190
187
  }
191
188
 
@@ -34,7 +34,11 @@ export type CustomBlockHostInitialState = {
34
34
  blockId: NotionBlockId
35
35
  parent: NotionParent
36
36
  page: CustomBlockPage
37
- manifest: CustomBlockManifest
37
+ /**
38
+ * The host's persisted manifest, when one exists. If omitted, the host
39
+ * requires the sandbox to provide a manifest in `connect`.
40
+ */
41
+ manifest?: CustomBlockManifest
38
42
  dataSources: CustomBlockHostInitialDataSources
39
43
  currentUser: NotionUser
40
44
  }
@@ -18,7 +18,7 @@ type QuerySubscriptionState = {
18
18
  export class QuerySubscriptions {
19
19
  private readonly bySubscriptionId = new Map<string, QuerySubscriptionState>()
20
20
 
21
- track(message: QueryDataSourceMessage): QuerySubscriptionToken {
21
+ subscribe(message: QueryDataSourceMessage): QuerySubscriptionToken {
22
22
  const token: QuerySubscriptionToken = {
23
23
  _tag: "QuerySubscriptionToken",
24
24
  }
@@ -33,6 +33,10 @@ export class QuerySubscriptions {
33
33
  return this.bySubscriptionId.get(subscriptionId)?.token === token
34
34
  }
35
35
 
36
+ unsubscribe(subscriptionId: string): void {
37
+ this.bySubscriptionId.delete(subscriptionId)
38
+ }
39
+
36
40
  refresh(dataSourceId: NotionDataSourceId): QueryRefresh[] {
37
41
  const refreshes: QueryRefresh[] = []
38
42
  for (const queryState of this.bySubscriptionId.values()) {
@@ -51,7 +55,7 @@ export class QuerySubscriptions {
51
55
  return refreshes
52
56
  }
53
57
 
54
- clear() {
58
+ unsubscribeAll() {
55
59
  this.bySubscriptionId.clear()
56
60
  }
57
61
  }