@notionhq/custom-blocks-host 0.1.31 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,11 +1,15 @@
1
+ import { DEFAULT_CONTRAST_MODE } from "../protocol/contrast.js";
2
+ import { unreachable } from "../utils.js";
1
3
  import { initErrorForFailureReason } from "./initErrors.js";
2
4
  import { isValidBridgeProtocolVersion } from "./protocolVersion.js";
3
5
  const DEFAULT_NO_CONNECT_TIMEOUT_MS = 5_000;
6
+ const DEFAULT_INITIAL_STATE_TIMEOUT_MS = 5_000;
4
7
  const DEFAULT_NO_INIT_RESULT_TIMEOUT_MS = 5_000;
5
8
  export function createInitializationController(options) {
6
- const { blockId, getState, prepareInit, sendInitMessage, warn, flushPendingHostStateChanges, minBridgeProtocolVersion, noConnectTimeoutMs = DEFAULT_NO_CONNECT_TIMEOUT_MS, noInitResultTimeoutMs = DEFAULT_NO_INIT_RESULT_TIMEOUT_MS, stopHostWithFailure, onInitResult, onFailure, } = options;
9
+ const { prepareInit, sendInitMessage, warn, flushPendingHostStateChanges, minBridgeProtocolVersion, noConnectTimeoutMs = DEFAULT_NO_CONNECT_TIMEOUT_MS, initialStateTimeoutMs = DEFAULT_INITIAL_STATE_TIMEOUT_MS, noInitResultTimeoutMs = DEFAULT_NO_INIT_RESULT_TIMEOUT_MS, stopHostWithFailure, onInitResult, onFailure, } = options;
7
10
  let initStatus = "waitingForConnect";
8
11
  let noConnectTimeoutId;
12
+ let initialStateTimeoutId;
9
13
  let noInitResultTimeoutId;
10
14
  let activeInitializationId;
11
15
  let hasObservedIframeLoad = false;
@@ -16,6 +20,12 @@ export function createInitializationController(options) {
16
20
  noConnectTimeoutId = undefined;
17
21
  }
18
22
  }
23
+ function clearInitialStateTimeout() {
24
+ if (initialStateTimeoutId !== undefined) {
25
+ window.clearTimeout(initialStateTimeoutId);
26
+ initialStateTimeoutId = undefined;
27
+ }
28
+ }
19
29
  function clearNoInitResultTimeout() {
20
30
  if (noInitResultTimeoutId !== undefined) {
21
31
  window.clearTimeout(noInitResultTimeoutId);
@@ -24,12 +34,22 @@ export function createInitializationController(options) {
24
34
  }
25
35
  function sendInitError(args) {
26
36
  const { initializationId, error } = args;
27
- if (initStatus !== "waitingForConnect") {
28
- return;
37
+ switch (initStatus) {
38
+ case "waitingForConnect":
39
+ case "preparingInit":
40
+ break;
41
+ case "waitingForInitResult":
42
+ case "initialized":
43
+ case "error":
44
+ case "stopped":
45
+ return;
46
+ default:
47
+ return unreachable(initStatus);
29
48
  }
30
49
  initStatus = "error";
31
50
  activeInitializationId = initializationId;
32
51
  clearNoConnectTimeout();
52
+ clearInitialStateTimeout();
33
53
  sendInitMessage({
34
54
  type: "init",
35
55
  initializationId,
@@ -38,54 +58,78 @@ export function createInitializationController(options) {
38
58
  });
39
59
  onFailure?.(error);
40
60
  }
41
- function prepareAndSendInit(connect) {
42
- if (initStatus !== "waitingForConnect") {
43
- return false;
44
- }
45
- const preparation = prepareInit(connect);
46
- // The embedder can stop the host when it receives the manifest.
47
- // If that happens, do not continue initialization or start its timer.
61
+ async function prepareAndSendInit(connect) {
48
62
  if (initStatus !== "waitingForConnect") {
49
- return false;
63
+ return;
50
64
  }
51
- if (preparation.status === "error") {
52
- sendInitError({
65
+ initStatus = "preparingInit";
66
+ activeInitializationId = connect.initializationId;
67
+ clearNoConnectTimeout();
68
+ initialStateTimeoutId = window.setTimeout(() => {
69
+ if (initStatus !== "preparingInit") {
70
+ return;
71
+ }
72
+ const error = initErrorForFailureReason("initial_state_timeout");
73
+ warn({
74
+ ...error,
53
75
  initializationId: connect.initializationId,
54
- error: preparation.error,
55
76
  });
56
- return false;
77
+ sendInitError({ initializationId: connect.initializationId, error });
78
+ }, initialStateTimeoutMs);
79
+ try {
80
+ const preparation = await prepareInit(connect);
81
+ if (initStatus !== "preparingInit") {
82
+ return;
83
+ }
84
+ clearInitialStateTimeout();
85
+ if (preparation.status === "error") {
86
+ sendInitError({
87
+ initializationId: connect.initializationId,
88
+ error: preparation.error,
89
+ });
90
+ return;
91
+ }
92
+ initStatus = "waitingForInitResult";
93
+ const message = {
94
+ type: "init",
95
+ initializationId: connect.initializationId,
96
+ status: "success",
97
+ theme: preparation.initialState.theme,
98
+ contrastMode: preparation.initialState.contrastMode ?? DEFAULT_CONTRAST_MODE,
99
+ blockId: preparation.initialState.blockId,
100
+ parent: preparation.initialState.parent,
101
+ page: preparation.initialState.page,
102
+ currentUser: preparation.initialState.currentUser,
103
+ manifest: preparation.initialState.manifest,
104
+ dataSources: preparation.initialState.dataSources,
105
+ };
106
+ sendInitMessage(message);
107
+ noInitResultTimeoutId = window.setTimeout(() => {
108
+ if (initStatus !== "waitingForInitResult") {
109
+ return;
110
+ }
111
+ initStatus = "error";
112
+ const error = initErrorForFailureReason("no_init_result");
113
+ onFailure?.(error);
114
+ warn({
115
+ ...error,
116
+ initializationId: connect.initializationId,
117
+ });
118
+ }, noInitResultTimeoutMs);
57
119
  }
58
- initStatus = "waitingForInitResult";
59
- activeInitializationId = connect.initializationId;
60
- clearNoConnectTimeout();
61
- const state = getState();
62
- const message = {
63
- type: "init",
64
- initializationId: connect.initializationId,
65
- status: "success",
66
- theme: state.theme,
67
- contrastMode: state.contrastMode,
68
- blockId,
69
- parent: state.parent,
70
- page: state.page,
71
- manifest: preparation.manifest,
72
- dataSources: preparation.dataSources,
73
- currentUser: state.currentUser,
74
- };
75
- sendInitMessage(message);
76
- noInitResultTimeoutId = window.setTimeout(() => {
77
- if (initStatus !== "waitingForInitResult") {
120
+ catch (error) {
121
+ if (initStatus !== "preparingInit") {
78
122
  return;
79
123
  }
80
- initStatus = "error";
81
- const error = initErrorForFailureReason("no_init_result");
82
124
  warn({
83
- message: "Sandbox did not send initResult",
125
+ message: "Failed to prepare custom block initialization",
126
+ error,
127
+ });
128
+ sendInitError({
84
129
  initializationId: connect.initializationId,
130
+ error: initErrorForFailureReason("unknown_error"),
85
131
  });
86
- onFailure?.(error);
87
- }, noInitResultTimeoutMs);
88
- return true;
132
+ }
89
133
  }
90
134
  function handleConnect(message) {
91
135
  if (initStatus === "stopped") {
@@ -124,9 +168,8 @@ export function createInitializationController(options) {
124
168
  });
125
169
  return;
126
170
  }
127
- if (prepareAndSendInit(message)) {
128
- hasConnectedSandbox = true;
129
- }
171
+ hasConnectedSandbox = true;
172
+ void prepareAndSendInit(message);
130
173
  }
131
174
  function handleInvalidConnect(args) {
132
175
  if (initStatus === "stopped") {
@@ -208,12 +251,11 @@ export function createInitializationController(options) {
208
251
  return;
209
252
  }
210
253
  clearNoInitResultTimeout();
254
+ initStatus = message.status === "success" ? "initialized" : "error";
211
255
  if (message.status === "success") {
212
- initStatus = "initialized";
213
256
  flushPendingHostStateChanges();
214
257
  }
215
258
  else {
216
- initStatus = "error";
217
259
  onFailure?.(message.error);
218
260
  }
219
261
  onInitResult?.(message);
@@ -221,6 +263,7 @@ export function createInitializationController(options) {
221
263
  function stop() {
222
264
  initStatus = "stopped";
223
265
  clearNoConnectTimeout();
266
+ clearInitialStateTimeout();
224
267
  clearNoInitResultTimeout();
225
268
  }
226
269
  return {
@@ -3,6 +3,7 @@ import type { NotionDataSourceBindings } from "../protocol/dataSources/dataSourc
3
3
  import type { NotionBlockId } from "../protocol/ids.js";
4
4
  import type { CustomBlockManifest } from "../protocol/manifest.js";
5
5
  import type { ConnectMessage } from "../protocol/messages/connect.js";
6
+ import type { CustomBlockInitErrorInfo } from "../protocol/messages/init.js";
6
7
  import type { CustomBlockPage } from "../protocol/pages/page.js";
7
8
  import type { NotionParent } from "../protocol/parent.js";
8
9
  import type { NotionTheme } from "../protocol/theme.js";
@@ -14,6 +15,8 @@ export type ConnectSuccessMessage = Extract<ConnectMessage, {
14
15
  export type InitStatus =
15
16
  /** The host is waiting for the sandbox's first `connect` message. */
16
17
  "waitingForConnect"
18
+ /** The host is waiting for `getInitialState` to prepare `init`. */
19
+ | "preparingInit"
17
20
  /** The host is waiting for a matching `initResult` after it sends `init`. */
18
21
  | "waitingForInitResult"
19
22
  /** The host is initialized and can send live updates. */
@@ -25,7 +28,6 @@ export type InitStatus =
25
28
  export type CustomBlockHostDataSourcesPayload = {
26
29
  bindings: NotionDataSourceBindings;
27
30
  };
28
- export type CustomBlockHostInitialDataSources = CustomBlockHostDataSourcesPayload | ((connect: ConnectSuccessMessage) => CustomBlockHostDataSourcesPayload);
29
31
  export type CustomBlockHostInitialState = {
30
32
  theme: NotionTheme;
31
33
  /**
@@ -36,12 +38,16 @@ export type CustomBlockHostInitialState = {
36
38
  blockId: NotionBlockId;
37
39
  parent: NotionParent;
38
40
  page: CustomBlockPage;
39
- /**
40
- * The host's persisted manifest, when one exists. If omitted, the host
41
- * requires the sandbox to provide a manifest in `connect`.
42
- */
43
- manifest?: CustomBlockManifest;
44
- dataSources: CustomBlockHostInitialDataSources;
41
+ manifest: CustomBlockManifest;
42
+ dataSources: CustomBlockHostDataSourcesPayload;
45
43
  currentUser: NotionUser;
46
44
  };
45
+ export type CustomBlockHostInitialStateResult = {
46
+ status: "success";
47
+ initialState: CustomBlockHostInitialState;
48
+ } | {
49
+ status: "error";
50
+ error: CustomBlockInitErrorInfo;
51
+ };
52
+ export type CustomBlockHostGetInitialState = (connect: ConnectSuccessMessage) => Promise<CustomBlockHostInitialStateResult>;
47
53
  //# sourceMappingURL=types.d.ts.map
@@ -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,gEAAgE;AAChE,MAAM,MAAM,UAAU;AACrB,qEAAqE;AACnE,mBAAmB;AACrB,6EAA6E;GAC3E,sBAAsB;AACxB,yDAAyD;GACvD,aAAa;AACf,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"}
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,wBAAwB,EAAE,MAAM,mDAAmD,CAAA;AACjG,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,mEAAmE;GACjE,eAAe;AACjB,6EAA6E;GAC3E,sBAAsB;AACxB,yDAAyD;GACvD,aAAa;AACf,gFAAgF;GAC9E,OAAO;AACT,2EAA2E;GACzE,SAAS,CAAA;AAEZ,MAAM,MAAM,iCAAiC,GAAG;IAC/C,QAAQ,EAAE,wBAAwB,CAAA;CAClC,CAAA;AAED,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,QAAQ,EAAE,mBAAmB,CAAA;IAC7B,WAAW,EAAE,iCAAiC,CAAA;IAC9C,WAAW,EAAE,UAAU,CAAA;CACvB,CAAA;AAED,MAAM,MAAM,iCAAiC,GAC1C;IACA,MAAM,EAAE,SAAS,CAAA;IACjB,YAAY,EAAE,2BAA2B,CAAA;CACxC,GACD;IACA,MAAM,EAAE,OAAO,CAAA;IACf,KAAK,EAAE,wBAAwB,CAAA;CAC9B,CAAA;AAEJ,MAAM,MAAM,8BAA8B,GAAG,CAC5C,OAAO,EAAE,qBAAqB,KAC1B,OAAO,CAAC,iCAAiC,CAAC,CAAA"}
@@ -2,7 +2,7 @@ import * as v from "valibot";
2
2
  import type { CustomBlockErrorCode, CustomBlockErrorInfo } from "../errors.js";
3
3
  import type { CustomBlockInitResultErrorCode } from "./initResult.js";
4
4
  export declare const customBlockInitErrorCodeSchema: v.StringSchema<undefined>;
5
- export type CustomBlockInitErrorCode = CustomBlockErrorCode<"manifest_unavailable" | "manifest_invalid" | "invalid_connect_payload" | "invalid_protocol_version" | "unsupported_protocol_version" | "init_timeout" | "context_unavailable" | "current_user_unavailable" | "missing_data_source_binding" | "data_source_unavailable" | "missing_property_binding" | "invalid_property_binding">;
5
+ export type CustomBlockInitErrorCode = CustomBlockErrorCode<"manifest_unavailable" | "manifest_invalid" | "invalid_connect_payload" | "invalid_protocol_version" | "unsupported_protocol_version" | "init_timeout" | "context_unavailable" | "current_user_unavailable" | "initial_state_timeout" | "missing_data_source_binding" | "data_source_unavailable" | "missing_property_binding" | "invalid_property_binding">;
6
6
  export type CustomBlockInitErrorInfo = CustomBlockErrorInfo<CustomBlockInitErrorCode>;
7
7
  /**
8
8
  * Every initialization failure visible to block code. This combines errors
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@notionhq/custom-blocks-host",
3
- "version": "0.1.31",
3
+ "version": "0.2.0",
4
4
  "description": "Reusable host runtime for Notion custom blocks.",
5
5
  "license": "MIT",
6
6
  "type": "module",