@notionhq/custom-blocks 0.0.63 → 0.0.65

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/HOST.md +16 -16
  2. package/README.md +2 -2
  3. package/bin/cli/create.js +3 -3
  4. package/dist/bridge/SandboxBridge.js +17 -17
  5. package/dist/bridge/loadManifest.d.ts +2 -7
  6. package/dist/bridge/loadManifest.d.ts.map +1 -1
  7. package/dist/bridge/loadManifest.js +36 -18
  8. package/dist/bridge/messages/ready.d.ts +1 -0
  9. package/dist/bridge/messages/ready.d.ts.map +1 -1
  10. package/dist/bridge/messages/ready.js +8 -2
  11. package/dist/bridge/messages/sandboxToHost.d.ts +1 -0
  12. package/dist/bridge/messages/sandboxToHost.d.ts.map +1 -1
  13. package/dist/host/createCustomBlockHost.d.ts.map +1 -1
  14. package/dist/host/createCustomBlockHost.js +24 -13
  15. package/dist/host/lifecycle/initErrors.d.ts +4 -1
  16. package/dist/host/lifecycle/initErrors.d.ts.map +1 -1
  17. package/dist/host/lifecycle/initErrors.js +16 -5
  18. package/dist/host/lifecycle/ready.d.ts +1 -7
  19. package/dist/host/lifecycle/ready.d.ts.map +1 -1
  20. package/dist/host/lifecycle/ready.js +1 -22
  21. package/dist/init.js +1 -1
  22. package/dist/react/NotionCustomBlock.css +47 -47
  23. package/dist/react/NotionCustomBlock.js +4 -4
  24. package/dist/utils.js +1 -1
  25. package/dist/version.d.ts +1 -1
  26. package/dist/version.d.ts.map +1 -1
  27. package/dist/version.js +1 -1
  28. package/docs/block-location.md +1 -1
  29. package/docs/manifest.md +2 -2
  30. package/docs/pages.md +1 -1
  31. package/package.json +1 -1
  32. package/src/bridge/SandboxBridge.ts +17 -17
  33. package/src/bridge/loadManifest.ts +44 -23
  34. package/src/bridge/messages/ready.ts +9 -2
  35. package/src/host/createCustomBlockHost.ts +30 -14
  36. package/src/host/lifecycle/initErrors.ts +19 -4
  37. package/src/host/lifecycle/ready.ts +1 -34
  38. package/src/init.ts +1 -1
  39. package/src/react/NotionCustomBlock.css +47 -47
  40. package/src/react/NotionCustomBlock.tsx +14 -10
  41. package/src/utils.ts +1 -1
  42. package/src/version.ts +1 -1
@@ -1,27 +1,27 @@
1
- .ncblock-standalone-banner {
2
- --ncblock-standalone-banner-background: #fff8e1;
3
- --ncblock-standalone-banner-border: #f0d77b;
4
- --ncblock-standalone-banner-text: #5d4200;
1
+ .custom-blocks-standalone-banner {
2
+ --custom-blocks-standalone-banner-background: #fff8e1;
3
+ --custom-blocks-standalone-banner-border: #f0d77b;
4
+ --custom-blocks-standalone-banner-text: #5d4200;
5
5
 
6
6
  padding: 8px 12px;
7
- background: var(--ncblock-standalone-banner-background);
8
- color: var(--ncblock-standalone-banner-text);
9
- border-bottom: 1px solid var(--ncblock-standalone-banner-border);
7
+ background: var(--custom-blocks-standalone-banner-background);
8
+ color: var(--custom-blocks-standalone-banner-text);
9
+ border-bottom: 1px solid var(--custom-blocks-standalone-banner-border);
10
10
  font-size: 13px;
11
11
  font-family: system-ui, sans-serif;
12
12
  line-height: 1.4;
13
13
  }
14
14
 
15
- .ncblock-default-init-error {
16
- --ncblock-init-error-background: #f7f7f5;
17
- --ncblock-init-error-card-background: #ffffff;
18
- --ncblock-init-error-border: #e5e5e1;
19
- --ncblock-init-error-text: #1f1f1d;
20
- --ncblock-init-error-muted-text: #5f5e5b;
21
- --ncblock-init-error-eyebrow-text: #787774;
22
- --ncblock-init-error-pre-background: #f1f1ef;
23
- --ncblock-init-error-pre-text: #37352f;
24
- --ncblock-init-error-shadow: rgba(0, 0, 0, 0.08);
15
+ .custom-blocks-default-init-error {
16
+ --custom-blocks-init-error-background: #f7f7f5;
17
+ --custom-blocks-init-error-card-background: #ffffff;
18
+ --custom-blocks-init-error-border: #e5e5e1;
19
+ --custom-blocks-init-error-text: #1f1f1d;
20
+ --custom-blocks-init-error-muted-text: #5f5e5b;
21
+ --custom-blocks-init-error-eyebrow-text: #787774;
22
+ --custom-blocks-init-error-pre-background: #f1f1ef;
23
+ --custom-blocks-init-error-pre-text: #37352f;
24
+ --custom-blocks-init-error-shadow: rgba(0, 0, 0, 0.08);
25
25
 
26
26
  box-sizing: border-box;
27
27
  min-height: 240px;
@@ -29,81 +29,81 @@
29
29
  align-items: center;
30
30
  justify-content: center;
31
31
  padding: 24px;
32
- background: var(--ncblock-init-error-background);
33
- color: var(--ncblock-init-error-text);
32
+ background: var(--custom-blocks-init-error-background);
33
+ color: var(--custom-blocks-init-error-text);
34
34
  font-family: system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
35
35
  color-scheme: light;
36
36
  }
37
37
 
38
- .ncblock-default-init-error__card {
38
+ .custom-blocks-default-init-error__card {
39
39
  box-sizing: border-box;
40
40
  width: 100%;
41
41
  max-width: 420px;
42
42
  padding: 20px;
43
- border: 1px solid var(--ncblock-init-error-border);
43
+ border: 1px solid var(--custom-blocks-init-error-border);
44
44
  border-radius: 12px;
45
- background: var(--ncblock-init-error-card-background);
46
- box-shadow: 0 8px 24px var(--ncblock-init-error-shadow);
45
+ background: var(--custom-blocks-init-error-card-background);
46
+ box-shadow: 0 8px 24px var(--custom-blocks-init-error-shadow);
47
47
  }
48
48
 
49
- .ncblock-default-init-error__eyebrow {
49
+ .custom-blocks-default-init-error__eyebrow {
50
50
  margin-bottom: 8px;
51
51
  font-size: 11px;
52
52
  font-weight: 600;
53
53
  letter-spacing: 0.08em;
54
54
  text-transform: uppercase;
55
- color: var(--ncblock-init-error-eyebrow-text);
55
+ color: var(--custom-blocks-init-error-eyebrow-text);
56
56
  }
57
57
 
58
- .ncblock-default-init-error__title {
58
+ .custom-blocks-default-init-error__title {
59
59
  margin: 0;
60
60
  font-size: 18px;
61
61
  line-height: 1.3;
62
62
  font-weight: 650;
63
63
  }
64
64
 
65
- .ncblock-default-init-error__body {
65
+ .custom-blocks-default-init-error__body {
66
66
  margin: 8px 0 0;
67
67
  font-size: 14px;
68
68
  line-height: 1.5;
69
- color: var(--ncblock-init-error-muted-text);
69
+ color: var(--custom-blocks-init-error-muted-text);
70
70
  }
71
71
 
72
- .ncblock-default-init-error__details {
72
+ .custom-blocks-default-init-error__details {
73
73
  margin-top: 14px;
74
74
  font-size: 12px;
75
- color: var(--ncblock-init-error-muted-text);
75
+ color: var(--custom-blocks-init-error-muted-text);
76
76
  }
77
77
 
78
- .ncblock-default-init-error__pre {
78
+ .custom-blocks-default-init-error__pre {
79
79
  margin: 8px 0 0;
80
80
  padding: 10px;
81
81
  overflow: auto;
82
82
  border-radius: 8px;
83
- background: var(--ncblock-init-error-pre-background);
84
- color: var(--ncblock-init-error-pre-text);
83
+ background: var(--custom-blocks-init-error-pre-background);
84
+ color: var(--custom-blocks-init-error-pre-text);
85
85
  font-size: 12px;
86
86
  line-height: 1.4;
87
87
  white-space: pre-wrap;
88
88
  }
89
89
 
90
90
  @media (prefers-color-scheme: dark) {
91
- .ncblock-standalone-banner {
92
- --ncblock-standalone-banner-background: #3b2f0b;
93
- --ncblock-standalone-banner-border: #6f5713;
94
- --ncblock-standalone-banner-text: #f5d36c;
91
+ .custom-blocks-standalone-banner {
92
+ --custom-blocks-standalone-banner-background: #3b2f0b;
93
+ --custom-blocks-standalone-banner-border: #6f5713;
94
+ --custom-blocks-standalone-banner-text: #f5d36c;
95
95
  }
96
96
 
97
- .ncblock-default-init-error {
98
- --ncblock-init-error-background: #191918;
99
- --ncblock-init-error-card-background: #20201f;
100
- --ncblock-init-error-border: #373633;
101
- --ncblock-init-error-text: #f1f1ef;
102
- --ncblock-init-error-muted-text: #b9b8b3;
103
- --ncblock-init-error-eyebrow-text: #9b9a97;
104
- --ncblock-init-error-pre-background: #2a2a28;
105
- --ncblock-init-error-pre-text: #e8e8e4;
106
- --ncblock-init-error-shadow: rgba(0, 0, 0, 0.35);
97
+ .custom-blocks-default-init-error {
98
+ --custom-blocks-init-error-background: #191918;
99
+ --custom-blocks-init-error-card-background: #20201f;
100
+ --custom-blocks-init-error-border: #373633;
101
+ --custom-blocks-init-error-text: #f1f1ef;
102
+ --custom-blocks-init-error-muted-text: #b9b8b3;
103
+ --custom-blocks-init-error-eyebrow-text: #9b9a97;
104
+ --custom-blocks-init-error-pre-background: #2a2a28;
105
+ --custom-blocks-init-error-pre-text: #e8e8e4;
106
+ --custom-blocks-init-error-shadow: rgba(0, 0, 0, 0.35);
107
107
 
108
108
  color-scheme: dark;
109
109
  }
@@ -45,7 +45,7 @@ export function NotionCustomBlock({ children, timeoutMs, fallback = null, errorF
45
45
  if (!isStandalone) {
46
46
  return;
47
47
  }
48
- console.warn(`[notion-custom-sdk] ${init.error?.message}`);
48
+ console.warn(`[custom-blocks-sdk] ${init.error?.message}`);
49
49
  seedStandalonePreviewState();
50
50
  }, [isStandalone, init.error]);
51
51
  if (debugOpen) {
@@ -65,7 +65,7 @@ export function NotionCustomBlock({ children, timeoutMs, fallback = null, errorF
65
65
  if (host.status !== "initialized") {
66
66
  return _jsx(_Fragment, { children: fallback });
67
67
  }
68
- return (_jsxs(_Fragment, { children: [_jsx("div", { role: "status", className: "ncblock-standalone-banner", children: "Notion host not detected \u2014 running in standalone preview. SDK hooks return placeholder values until embedded in Notion." }), children] }));
68
+ return (_jsxs(_Fragment, { children: [_jsx("div", { role: "status", className: "custom-blocks-standalone-banner", children: "Notion host not detected \u2014 running in standalone preview. SDK hooks return placeholder values until embedded in Notion." }), children] }));
69
69
  }
70
70
  if (!init.isLoaded) {
71
71
  return _jsx(_Fragment, { children: fallback });
@@ -75,7 +75,7 @@ export function NotionCustomBlock({ children, timeoutMs, fallback = null, errorF
75
75
  // TODO(custom-blocks): Align the styling of this with the host-side error states.
76
76
  function DefaultInitErrorFallback({ error }) {
77
77
  useEffect(() => {
78
- console.warn(`[notion-custom-sdk] Custom block init failed: ${error.message}`);
78
+ console.warn(`[custom-blocks-sdk] Custom block init failed: ${error.message}`);
79
79
  }, [error]);
80
- return (_jsx("div", { role: "alert", className: "ncblock-default-init-error", children: _jsxs("div", { className: "ncblock-default-init-error__card", children: [_jsx("div", { className: "ncblock-default-init-error__eyebrow", children: "Custom block" }), _jsx("h2", { className: "ncblock-default-init-error__title", children: "Couldn't connect to Notion" }), _jsx("p", { className: "ncblock-default-init-error__body", children: "This custom block loaded, but the setup handshake didn't finish. Try again from Notion. If this keeps happening, check the browser console for SDK details." }), _jsxs("details", { className: "ncblock-default-init-error__details", children: [_jsx("summary", { children: "Developer details" }), _jsx("pre", { className: "ncblock-default-init-error__pre", children: error.message })] })] }) }));
80
+ return (_jsx("div", { role: "alert", className: "custom-blocks-default-init-error", children: _jsxs("div", { className: "custom-blocks-default-init-error__card", children: [_jsx("div", { className: "custom-blocks-default-init-error__eyebrow", children: "Custom block" }), _jsx("h2", { className: "custom-blocks-default-init-error__title", children: "Couldn't connect to Notion" }), _jsx("p", { className: "custom-blocks-default-init-error__body", children: "This custom block loaded, but the setup handshake didn't finish. Try again from Notion. If this keeps happening, check the browser console for SDK details." }), _jsxs("details", { className: "custom-blocks-default-init-error__details", children: [_jsx("summary", { children: "Developer details" }), _jsx("pre", { className: "custom-blocks-default-init-error__pre", children: error.message })] })] }) }));
81
81
  }
package/dist/utils.js CHANGED
@@ -6,5 +6,5 @@
6
6
  * are covered when using discriminated unions.
7
7
  */
8
8
  export function unreachable(value) {
9
- throw new Error(`[notion-custom-sdk] Unexpected value encountered: ${JSON.stringify(value)}`);
9
+ throw new Error(`[custom-blocks-sdk] Unexpected value encountered: ${JSON.stringify(value)}`);
10
10
  }
package/dist/version.d.ts CHANGED
@@ -5,5 +5,5 @@
5
5
  * This checked-in value intentionally stays generic for local development. The SDK publish build
6
6
  * overwrites the compiled runtime module with package.json's semver before publishing.
7
7
  */
8
- export declare const NCBLOCK_SDK_VERSION: string;
8
+ export declare const CUSTOM_BLOCKS_SDK_VERSION: string;
9
9
  //# sourceMappingURL=version.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../../../home/runner/work/custom-blocks/custom-blocks/sdk/src/version.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,EAAE,MAAgB,CAAA"}
1
+ {"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../../../home/runner/work/custom-blocks/custom-blocks/sdk/src/version.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,eAAO,MAAM,yBAAyB,EAAE,MAAgB,CAAA"}
package/dist/version.js CHANGED
@@ -4,4 +4,4 @@
4
4
  *
5
5
  * WARNING: Generated during SDK publish. Do not edit in the published package.
6
6
  */
7
- export const NCBLOCK_SDK_VERSION = "0.0.63"
7
+ export const CUSTOM_BLOCKS_SDK_VERSION = "0.0.65"
@@ -46,7 +46,7 @@ For non-React renderers, use `customBlock.getParent()` after `initCustomBlock()`
46
46
  To identify custom blocks in agent-owned instruction content:
47
47
 
48
48
  ```tsx
49
- import { useParent } from "ncblock"
49
+ import { useParent } from "@notionhq/custom-blocks"
50
50
 
51
51
  export function AgentInstructionBadge() {
52
52
  const parent = useParent()
package/docs/manifest.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Manifest
2
2
 
3
- A custom view declares its required data sources in `custom_blocks.json` at the project root. Notion uses the manifest to know what semantic keys the block expects, what shape each property should be, and what to show when an admin is configuring the block.
3
+ A custom block declares its required data sources in `custom_blocks.json` at the project root. Notion uses the manifest to know what semantic keys the block expects, what shape each property should be, and what to show when an admin is configuring the block.
4
4
 
5
5
  ```json
6
6
  {
@@ -18,7 +18,7 @@ A custom view declares its required data sources in `custom_blocks.json` at the
18
18
  }
19
19
  ```
20
20
 
21
- `initCustomBlock()` fetches `custom_blocks.json` and forwards it with `ready`. The `notionCustomBlock()` Vite plugin from `@notionhq/custom-blocks/vite` serves it in dev and emits it into `dist/` on build. If the file is missing, the SDK sends `ready` with `status: "success"` and `manifest: null`, which means the block has no declared data requirements. If the file is unavailable for another reason or invalid, the SDK sends `ready` with `status: "error"` and an `error` payload, and hosts should reject init by echoing the sandbox-reported error code back.
21
+ `initCustomBlock()` fetches the manifest and forwards it with `ready`. The `notionCustomBlock()` Vite plugin from `@notionhq/custom-blocks/vite` serves it in dev and emits it into `dist/` on build. If the file is missing, the SDK sends `ready` with `status: "success"` and `manifest: null`, which means the block has no declared data requirements. If the file is unavailable for another reason or invalid, the SDK sends `ready` with `status: "error"` and an `error` payload. Hosts should reject `init` by forwarding that full error payload unchanged.
22
22
 
23
23
  ## Vite plugin
24
24
 
package/docs/pages.md CHANGED
@@ -53,7 +53,7 @@ type CreatePageParent =
53
53
  | { type: "data_source_key"; key: string };
54
54
  ```
55
55
 
56
- `type: "data_source_key"` is the recommended form inside a custom view. Pass the semantic key you declared in `custom_blocks.json` (e.g. `"tasks"`) and the SDK looks up the corresponding data source for you. The other two variants exist for the rarer case where you already have a raw Notion ID in hand.
56
+ `type: "data_source_key"` is the recommended form inside a custom block. Pass the semantic key you declared in `custom_blocks.json` (e.g. `"tasks"`) and the SDK looks up the corresponding data source for you. The other two variants exist for the rarer case where you already have a raw Notion ID in hand.
57
57
 
58
58
  ### Property keys
59
59
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@notionhq/custom-blocks",
3
- "version": "0.0.63",
3
+ "version": "0.0.65",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -13,7 +13,7 @@ import type {
13
13
  UseDataSourceOptions,
14
14
  } from "../types.js"
15
15
  import { unreachable } from "../utils.js"
16
- import { NCBLOCK_SDK_VERSION } from "../version.js"
16
+ import { CUSTOM_BLOCKS_SDK_VERSION } from "../version.js"
17
17
  import type {
18
18
  NotionDataSource,
19
19
  NotionDataSourceBindings,
@@ -167,7 +167,7 @@ export class SandboxBridge {
167
167
  return
168
168
  }
169
169
  if (this.hasSentReady) {
170
- console.warn("[notion-custom-sdk] ignoring duplicate ready message")
170
+ console.warn("[custom-blocks-sdk] ignoring duplicate ready message")
171
171
  return
172
172
  }
173
173
  const { manifest, error } = manifestResult
@@ -179,21 +179,21 @@ export class SandboxBridge {
179
179
  type: "ready",
180
180
  status: "error",
181
181
  bridgeProtocolVersion: CUSTOM_BLOCK_BRIDGE_PROTOCOL_VERSION,
182
- sdkVersion: NCBLOCK_SDK_VERSION,
182
+ sdkVersion: CUSTOM_BLOCKS_SDK_VERSION,
183
183
  error,
184
184
  }
185
185
  : {
186
186
  type: "ready",
187
187
  status: "success",
188
188
  bridgeProtocolVersion: CUSTOM_BLOCK_BRIDGE_PROTOCOL_VERSION,
189
- sdkVersion: NCBLOCK_SDK_VERSION,
189
+ sdkVersion: CUSTOM_BLOCKS_SDK_VERSION,
190
190
  manifest,
191
191
  }
192
192
  this.postToHost(readyMessage)
193
193
  }
194
194
 
195
195
  private postToHost(message: unknown) {
196
- console.debug("[notion-custom-sdk] outbound postMessage", message)
196
+ console.debug("[custom-blocks-sdk] outbound postMessage", message)
197
197
  this.logMessage("sent", message)
198
198
  window.parent.postMessage(message, "*")
199
199
  }
@@ -205,7 +205,7 @@ export class SandboxBridge {
205
205
  }
206
206
 
207
207
  private handleMessage = (event: MessageEvent) => {
208
- console.debug("[notion-custom-sdk] incoming postMessage", {
208
+ console.debug("[custom-blocks-sdk] incoming postMessage", {
209
209
  data: event.data,
210
210
  fromParent: event.source === window.parent,
211
211
  })
@@ -217,7 +217,7 @@ export class SandboxBridge {
217
217
  const parsed = v.safeParse(hostToSandboxMessageSchema, event.data)
218
218
  if (!parsed.success) {
219
219
  console.warn(
220
- "[notion-custom-sdk] ignoring malformed host message",
220
+ "[custom-blocks-sdk] ignoring malformed host message",
221
221
  parsed.issues,
222
222
  )
223
223
  const incomingType = readIncomingType(event.data)
@@ -245,7 +245,7 @@ export class SandboxBridge {
245
245
  // failure on its own.
246
246
  if (message.type === "invalidSandboxMessage") {
247
247
  console.warn(
248
- "[notion-custom-sdk] host reported invalid sandbox message:",
248
+ "[custom-blocks-sdk] host reported invalid sandbox message:",
249
249
  message.reason,
250
250
  )
251
251
  return
@@ -263,7 +263,7 @@ export class SandboxBridge {
263
263
  const hostState = this.hostState
264
264
 
265
265
  if (hostState.status !== "initialized") {
266
- console.warn(`[notion-custom-sdk] ignoring ${message.type} before init`)
266
+ console.warn(`[custom-blocks-sdk] ignoring ${message.type} before init`)
267
267
  return
268
268
  }
269
269
 
@@ -340,7 +340,7 @@ export class SandboxBridge {
340
340
  : { status: "error", error: message.error }
341
341
  if (!this.pendingCreatePage.resolve(message.requestId, result)) {
342
342
  console.warn(
343
- `[notion-custom-sdk] createPageResult for unknown requestId ${message.requestId}`,
343
+ `[custom-blocks-sdk] createPageResult for unknown requestId ${message.requestId}`,
344
344
  )
345
345
  }
346
346
  return
@@ -353,7 +353,7 @@ export class SandboxBridge {
353
353
  : { status: "error", error: message.error }
354
354
  if (!this.pendingGetPage.resolve(message.requestId, result)) {
355
355
  console.warn(
356
- `[notion-custom-sdk] getPageResult for unknown requestId ${message.requestId}`,
356
+ `[custom-blocks-sdk] getPageResult for unknown requestId ${message.requestId}`,
357
357
  )
358
358
  }
359
359
  return
@@ -366,7 +366,7 @@ export class SandboxBridge {
366
366
  : { status: "error", error: message.error }
367
367
  if (!this.pendingGetUser.resolve(message.requestId, result)) {
368
368
  console.warn(
369
- `[notion-custom-sdk] getUserResult for unknown requestId ${message.requestId}`,
369
+ `[custom-blocks-sdk] getUserResult for unknown requestId ${message.requestId}`,
370
370
  )
371
371
  }
372
372
  return
@@ -379,7 +379,7 @@ export class SandboxBridge {
379
379
  : { status: "error", error: message.error }
380
380
  if (!this.pendingListUsers.resolve(message.requestId, result)) {
381
381
  console.warn(
382
- `[notion-custom-sdk] listUsersResult for unknown requestId ${message.requestId}`,
382
+ `[custom-blocks-sdk] listUsersResult for unknown requestId ${message.requestId}`,
383
383
  )
384
384
  }
385
385
  return
@@ -392,7 +392,7 @@ export class SandboxBridge {
392
392
  : { status: "error", error: message.error }
393
393
  if (!this.pendingUpdatePage.resolve(message.requestId, result)) {
394
394
  console.warn(
395
- `[notion-custom-sdk] updatePageResult for unknown requestId ${message.requestId}`,
395
+ `[custom-blocks-sdk] updatePageResult for unknown requestId ${message.requestId}`,
396
396
  )
397
397
  }
398
398
  return
@@ -492,7 +492,7 @@ export class SandboxBridge {
492
492
  // the browser console either way. We deliberately don't NACK the host or post anything
493
493
  // back over the bridge since the host has already given up.
494
494
  console.error(
495
- `[notion-custom-sdk] host reported init error (${message.error.code}): ${message.error.message}`,
495
+ `[custom-blocks-sdk] host reported init error (${message.error.code}): ${message.error.message}`,
496
496
  )
497
497
  if (this.rejectInit) {
498
498
  this.rejectInit(new CustomBlockInitError(message.error))
@@ -900,13 +900,13 @@ function resolveDataSourceQueryLimit(limit: number | undefined): number {
900
900
  }
901
901
  if (!Number.isFinite(limit) || !Number.isInteger(limit) || limit < 1) {
902
902
  console.warn(
903
- `[notion-custom-sdk] useDataSource limit must be a positive integer; using ${DEFAULT_DATA_SOURCE_QUERY_LIMIT}.`,
903
+ `[custom-blocks-sdk] useDataSource limit must be a positive integer; using ${DEFAULT_DATA_SOURCE_QUERY_LIMIT}.`,
904
904
  )
905
905
  return DEFAULT_DATA_SOURCE_QUERY_LIMIT
906
906
  }
907
907
  if (limit > MAX_DATA_SOURCE_QUERY_LIMIT) {
908
908
  console.warn(
909
- `[notion-custom-sdk] useDataSource limit is capped at ${MAX_DATA_SOURCE_QUERY_LIMIT}.`,
909
+ `[custom-blocks-sdk] useDataSource limit is capped at ${MAX_DATA_SOURCE_QUERY_LIMIT}.`,
910
910
  )
911
911
  return MAX_DATA_SOURCE_QUERY_LIMIT
912
912
  }
@@ -1,25 +1,16 @@
1
1
  import * as v from "valibot"
2
+ import type { CustomBlockErrorInfo } from "./errors.js"
2
3
  import { type CustomBlockManifest, manifestSchema } from "./manifest.js"
3
4
 
4
5
  const MANIFEST_URL = "custom_blocks.json"
5
6
 
6
- // The schema accepts any string code. The type lists known codes for
7
- // autocomplete, with an open string fallback for newer senders.
8
- export const customBlockReadyErrorCodeSchema = v.string()
9
-
10
7
  export type CustomBlockReadyErrorCode =
11
8
  | "manifest_unavailable"
12
9
  | "manifest_invalid"
13
10
  | (string & {})
14
11
 
15
- export const customBlockReadyErrorSchema = v.object({
16
- code: customBlockReadyErrorCodeSchema,
17
- message: v.string(),
18
- })
19
-
20
- export type CustomBlockReadyError = v.InferOutput<
21
- typeof customBlockReadyErrorSchema
22
- >
12
+ export type CustomBlockReadyError =
13
+ CustomBlockErrorInfo<CustomBlockReadyErrorCode>
23
14
 
24
15
  export type ManifestLoadResult =
25
16
  | {
@@ -41,10 +32,10 @@ export type ManifestLoadResult =
41
32
  export async function loadManifest(): Promise<ManifestLoadResult> {
42
33
  if (typeof fetch !== "function") {
43
34
  const message = `No fetch API available; cannot load ${MANIFEST_URL}.`
44
- console.warn(`[notion-custom-sdk] ${message}`)
35
+ console.warn(`[custom-blocks-sdk] ${message}`)
45
36
  return {
46
37
  manifest: null,
47
- error: { code: "manifest_unavailable", message },
38
+ error: { code: "manifest_unavailable", message, isRetryable: true },
48
39
  }
49
40
  }
50
41
  let response: Response
@@ -52,38 +43,68 @@ export async function loadManifest(): Promise<ManifestLoadResult> {
52
43
  response = await fetch(MANIFEST_URL, { credentials: "omit" })
53
44
  } catch (error) {
54
45
  const message = `Could not fetch ${MANIFEST_URL}.`
55
- console.warn(`[notion-custom-sdk] ${message}`, error)
46
+ console.warn(`[custom-blocks-sdk] ${message}`, error)
56
47
  return {
57
48
  manifest: null,
58
- error: { code: "manifest_unavailable", message },
49
+ // Consider this retryable because it may have just been a network issue. Because `init`
50
+ // errors are considered terminal, the host should "retry" by reloading the iframe and
51
+ // sending another `ready` message.
52
+ error: { code: "manifest_unavailable", message, isRetryable: true },
59
53
  }
60
54
  }
61
55
  if (response.status === 404) {
62
56
  const message = `No manifest found at ${MANIFEST_URL} (status ${response.status}).`
63
- console.warn(`[notion-custom-sdk] ${message}`)
57
+ console.warn(`[custom-blocks-sdk] ${message}`)
64
58
  return { manifest: null }
65
59
  }
66
60
  if (!response.ok) {
67
61
  const message = `Could not fetch ${MANIFEST_URL} (status ${response.status}).`
68
- console.warn(`[notion-custom-sdk] ${message}`)
62
+ console.warn(`[custom-blocks-sdk] ${message}`)
69
63
  return {
70
64
  manifest: null,
71
- error: { code: "manifest_unavailable", message },
65
+ error: {
66
+ code: "manifest_unavailable",
67
+ message,
68
+ isRetryable: isRetryableManifestHttpStatus(response.status),
69
+ },
72
70
  }
73
71
  }
72
+
74
73
  let json: unknown
75
74
  try {
76
75
  json = await response.json()
77
76
  } catch (error) {
78
77
  const message = `Manifest at ${MANIFEST_URL} was not valid JSON.`
79
- console.warn(`[notion-custom-sdk] ${message}`, error)
80
- return { manifest: null, error: { code: "manifest_invalid", message } }
78
+ console.warn(`[custom-blocks-sdk] ${message}`, error)
79
+ return {
80
+ manifest: null,
81
+ // Consider invalid manifests as unrecoverable errors.
82
+ error: { code: "manifest_invalid", message, isRetryable: false },
83
+ }
81
84
  }
85
+
82
86
  const parsed = v.safeParse(manifestSchema, json)
83
87
  if (!parsed.success) {
84
88
  const message = `Manifest at ${MANIFEST_URL} did not match schema.`
85
- console.warn(`[notion-custom-sdk] ${message}`, parsed.issues)
86
- return { manifest: null, error: { code: "manifest_invalid", message } }
89
+ console.warn(`[custom-blocks-sdk] ${message}`, parsed.issues)
90
+ return {
91
+ manifest: null,
92
+ // Consider invalid manifests as unrecoverable errors.
93
+ error: { code: "manifest_invalid", message, isRetryable: false },
94
+ }
87
95
  }
96
+
88
97
  return { manifest: parsed.output }
89
98
  }
99
+
100
+ function isRetryableManifestHttpStatus(status: number): boolean {
101
+ // Consider these statuses retryable because they may reflect transient failures.
102
+ return (
103
+ status === 408 || // Request Timeout
104
+ status === 425 || // Too Early
105
+ status === 500 || // Internal Server Error
106
+ status === 502 || // Bad Gateway
107
+ status === 503 || // Service Unavailable
108
+ status === 504 // Gateway Timeout
109
+ )
110
+ }
@@ -1,5 +1,4 @@
1
1
  import * as v from "valibot"
2
- import { customBlockReadyErrorSchema } from "../loadManifest.js"
3
2
  import { manifestSchema } from "../manifest.js"
4
3
 
5
4
  const readyMessageCommonEntries = {
@@ -17,6 +16,14 @@ const readyMessageCommonEntries = {
17
16
  sdkVersion: v.string(),
18
17
  }
19
18
 
19
+ // TODO(custom-blocks): Replace with `customBlockErrorInfoSchema` which makes `isRetryable` required
20
+ // once min supported bridge protocol version is bumped to 3.
21
+ const readyErrorSchema = v.object({
22
+ code: v.string(),
23
+ message: v.string(),
24
+ isRetryable: v.optional(v.boolean(), false),
25
+ })
26
+
20
27
  /**
21
28
  * First message the sandbox sends after mount, kicking off the bridge handshake. The host replies
22
29
  * with `init`.
@@ -33,7 +40,7 @@ export const readyMessageSchema = v.union([
33
40
  }),
34
41
  v.object({
35
42
  status: v.literal("error"),
36
- error: customBlockReadyErrorSchema,
43
+ error: readyErrorSchema,
37
44
  ...readyMessageCommonEntries,
38
45
  }),
39
46
  ])
@@ -13,7 +13,7 @@ import type {
13
13
  } from "../bridge/messages/getUser.js"
14
14
  import type { HostToSandboxMessage } from "../bridge/messages/hostToSandbox.js"
15
15
  import type {
16
- CustomBlockInitErrorCode,
16
+ CustomBlockInitErrorInfo,
17
17
  InitMessage,
18
18
  } from "../bridge/messages/init.js"
19
19
  import type {
@@ -35,7 +35,7 @@ import type { NotionTheme } from "../bridge/theme.js"
35
35
  import type { NotionUser } from "../bridge/users/user.js"
36
36
  import { unreachable } from "../utils.js"
37
37
  import { initErrorForFailureReason } from "./lifecycle/initErrors.js"
38
- import { getReadyInitFailure } from "./lifecycle/ready.js"
38
+ import { isValidBridgeProtocolVersion } from "./lifecycle/ready.js"
39
39
  import type {
40
40
  CustomBlockHostDataSourcesPayload,
41
41
  CustomBlockHostInitialState,
@@ -161,27 +161,24 @@ export function createCustomBlockHost(
161
161
  return true
162
162
  }
163
163
 
164
- function sendInitError(code: CustomBlockInitErrorCode, message?: string) {
164
+ function sendInitError(error: CustomBlockInitErrorInfo) {
165
165
  if (initStatus !== "pending") {
166
166
  return
167
167
  }
168
168
  initStatus = "error"
169
169
  clearNoReadyTimeout()
170
- const error = initErrorForFailureReason(code)
171
170
  post({
172
171
  type: "init",
173
172
  status: "error",
174
- error: {
175
- ...error,
176
- message: message ?? error.message,
177
- },
173
+ error,
178
174
  })
179
175
  }
180
176
 
181
177
  function onIframeLoad() {
182
178
  clearNoReadyTimeout()
183
179
  noReadyTimeoutId = window.setTimeout(() => {
184
- sendInitError("no_ready")
180
+ const initError = initErrorForFailureReason("no_ready")
181
+ sendInitError(initError)
185
182
  }, noReadyTimeoutMs)
186
183
  }
187
184
 
@@ -261,14 +258,32 @@ export function createCustomBlockHost(
261
258
  }
262
259
 
263
260
  function handleReady(message: ReadyMessage) {
264
- const failure = getReadyInitFailure(message, minBridgeProtocolVersion)
265
- if (failure !== undefined) {
266
- sendInitError(failure.code, failure.message)
261
+ // Verify the bridge protocol version is valid.
262
+ if (!isValidBridgeProtocolVersion(message.bridgeProtocolVersion)) {
263
+ const initError = initErrorForFailureReason("invalid_protocol_version", {
264
+ currentBridgeProtocolVersion: message.bridgeProtocolVersion,
265
+ })
266
+ sendInitError(initError)
267
267
  return
268
268
  }
269
- if (message.status !== "success") {
269
+ if (message.bridgeProtocolVersion < minBridgeProtocolVersion) {
270
+ const initError = initErrorForFailureReason(
271
+ "unsupported_protocol_version",
272
+ {
273
+ currentBridgeProtocolVersion: message.bridgeProtocolVersion,
274
+ minBridgeProtocolVersion,
275
+ },
276
+ )
277
+ sendInitError(initError)
270
278
  return
271
279
  }
280
+
281
+ // If the sandbox reported an error during `ready`, return it as an `init` error.
282
+ if (message.status === "error") {
283
+ sendInitError(message.error)
284
+ return
285
+ }
286
+
272
287
  if (sendInit(message)) {
273
288
  onReady?.(message)
274
289
  }
@@ -286,7 +301,8 @@ export function createCustomBlockHost(
286
301
  const response = getInvalidSandboxMessageResponse(data)
287
302
 
288
303
  if (initStatus === "pending" && response.incomingType === "ready") {
289
- sendInitError("invalid_ready")
304
+ const initError = initErrorForFailureReason("invalid_ready")
305
+ sendInitError(initError)
290
306
  } else if (response.nack !== undefined) {
291
307
  post(response.nack)
292
308
  }