@notionhq/custom-blocks 0.0.62 → 0.0.64
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/HOST.md +4 -4
- package/README.md +2 -2
- package/bin/cli/create.js +3 -3
- package/dist/bridge/SandboxBridge.js +17 -17
- package/dist/bridge/loadManifest.js +6 -6
- package/dist/bridge/messages/hostToSandbox.d.ts +32 -0
- package/dist/bridge/messages/hostToSandbox.d.ts.map +1 -1
- package/dist/bridge/messages/init.d.ts +16 -0
- package/dist/bridge/messages/init.d.ts.map +1 -1
- package/dist/bridge/messages/pageChanged.d.ts +16 -0
- package/dist/bridge/messages/pageChanged.d.ts.map +1 -1
- package/dist/bridge/pages/page.d.ts +24 -0
- package/dist/bridge/pages/page.d.ts.map +1 -1
- package/dist/bridge/pages/page.js +9 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/init.d.ts +2 -2
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +1 -1
- package/dist/react/NotionCustomBlock.css +47 -47
- package/dist/react/NotionCustomBlock.js +4 -4
- package/dist/react/standalonePreview.js +1 -1
- package/dist/react/useCustomBlockInit.d.ts +2 -2
- package/dist/react/useCustomBlockInit.d.ts.map +1 -1
- package/dist/utils.js +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +1 -1
- package/docs/block-location.md +17 -3
- package/docs/lifecycle.md +3 -3
- package/docs/manifest.md +1 -1
- package/docs/pages.md +1 -1
- package/package.json +1 -1
- package/src/bridge/SandboxBridge.ts +17 -17
- package/src/bridge/loadManifest.ts +6 -6
- package/src/bridge/pages/page.ts +9 -0
- package/src/index.ts +1 -1
- package/src/init.ts +4 -4
- package/src/react/NotionCustomBlock.css +47 -47
- package/src/react/NotionCustomBlock.tsx +14 -10
- package/src/react/standalonePreview.ts +1 -1
- package/src/react/useCustomBlockInit.ts +2 -2
- package/src/utils.ts +1 -1
- package/src/version.ts +1 -1
|
@@ -1,27 +1,27 @@
|
|
|
1
|
-
.
|
|
2
|
-
--
|
|
3
|
-
--
|
|
4
|
-
--
|
|
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(--
|
|
8
|
-
color: var(--
|
|
9
|
-
border-bottom: 1px solid var(--
|
|
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
|
-
.
|
|
16
|
-
--
|
|
17
|
-
--
|
|
18
|
-
--
|
|
19
|
-
--
|
|
20
|
-
--
|
|
21
|
-
--
|
|
22
|
-
--
|
|
23
|
-
--
|
|
24
|
-
--
|
|
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(--
|
|
33
|
-
color: var(--
|
|
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
|
-
.
|
|
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(--
|
|
43
|
+
border: 1px solid var(--custom-blocks-init-error-border);
|
|
44
44
|
border-radius: 12px;
|
|
45
|
-
background: var(--
|
|
46
|
-
box-shadow: 0 8px 24px var(--
|
|
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
|
-
.
|
|
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(--
|
|
55
|
+
color: var(--custom-blocks-init-error-eyebrow-text);
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
.
|
|
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
|
-
.
|
|
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(--
|
|
69
|
+
color: var(--custom-blocks-init-error-muted-text);
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
.
|
|
72
|
+
.custom-blocks-default-init-error__details {
|
|
73
73
|
margin-top: 14px;
|
|
74
74
|
font-size: 12px;
|
|
75
|
-
color: var(--
|
|
75
|
+
color: var(--custom-blocks-init-error-muted-text);
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
.
|
|
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(--
|
|
84
|
-
color: var(--
|
|
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
|
-
.
|
|
92
|
-
--
|
|
93
|
-
--
|
|
94
|
-
--
|
|
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
|
-
.
|
|
98
|
-
--
|
|
99
|
-
--
|
|
100
|
-
--
|
|
101
|
-
--
|
|
102
|
-
--
|
|
103
|
-
--
|
|
104
|
-
--
|
|
105
|
-
--
|
|
106
|
-
--
|
|
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(`[
|
|
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: "
|
|
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(`[
|
|
78
|
+
console.warn(`[custom-blocks-sdk] Custom block init failed: ${error.message}`);
|
|
79
79
|
}, [error]);
|
|
80
|
-
return (_jsx("div", { role: "alert", className: "
|
|
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
|
}
|
|
@@ -10,7 +10,7 @@ export function seedStandalonePreviewState() {
|
|
|
10
10
|
theme: "light",
|
|
11
11
|
blockId: previewBlockId,
|
|
12
12
|
parent: { type: "page_id", page_id: previewPageId },
|
|
13
|
-
page: { id: previewPageId },
|
|
13
|
+
page: { id: previewPageId, parent: { type: "workspace", workspace: true } },
|
|
14
14
|
dataSources: { bindings: {} },
|
|
15
15
|
currentUser: {
|
|
16
16
|
object: "user",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { CustomBlockInitError } from "../bridge/messages/init.js";
|
|
2
|
-
import { type
|
|
2
|
+
import { type CustomBlockInitPayload, type InitCustomBlockOptions, NotInIframeError } from "../init.js";
|
|
3
3
|
export type CustomBlockInitFailure = CustomBlockInitError | NotInIframeError;
|
|
4
4
|
/**
|
|
5
5
|
* Discriminated state returned by {@link useCustomBlockInit}.
|
|
@@ -20,7 +20,7 @@ export type UseCustomBlockInitResult = {
|
|
|
20
20
|
} | {
|
|
21
21
|
isLoaded: true;
|
|
22
22
|
error: undefined;
|
|
23
|
-
initial:
|
|
23
|
+
initial: CustomBlockInitPayload;
|
|
24
24
|
};
|
|
25
25
|
/**
|
|
26
26
|
* React wrapper around {@link initCustomBlock}. Kicks off the SDK ↔ host
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useCustomBlockInit.d.ts","sourceRoot":"","sources":["../../../../home/runner/work/custom-blocks/custom-blocks/sdk/src/react/useCustomBlockInit.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAA;AACjE,OAAO,EACN,KAAK,
|
|
1
|
+
{"version":3,"file":"useCustomBlockInit.d.ts","sourceRoot":"","sources":["../../../../home/runner/work/custom-blocks/custom-blocks/sdk/src/react/useCustomBlockInit.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAA;AACjE,OAAO,EACN,KAAK,sBAAsB,EAC3B,KAAK,sBAAsB,EAE3B,gBAAgB,EAChB,MAAM,YAAY,CAAA;AAEnB,MAAM,MAAM,sBAAsB,GAAG,oBAAoB,GAAG,gBAAgB,CAAA;AAE5E;;;;;;;;;GASG;AACH,MAAM,MAAM,wBAAwB,GACjC;IAAE,QAAQ,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,SAAS,CAAA;CAAE,GACrC;IAAE,QAAQ,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,sBAAsB,CAAA;CAAE,GAClD;IAAE,QAAQ,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,SAAS,CAAC;IAAC,OAAO,EAAE,sBAAsB,CAAA;CAAE,CAAA;AAExE;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,kBAAkB,CACjC,IAAI,CAAC,EAAE,sBAAsB,GAC3B,wBAAwB,CA8B1B"}
|
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(`[
|
|
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
|
|
8
|
+
export declare const CUSTOM_BLOCKS_SDK_VERSION: string;
|
|
9
9
|
//# sourceMappingURL=version.d.ts.map
|
package/dist/version.d.ts.map
CHANGED
|
@@ -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,
|
|
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
package/docs/block-location.md
CHANGED
|
@@ -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 "
|
|
49
|
+
import { useParent } from "@notionhq/custom-blocks"
|
|
50
50
|
|
|
51
51
|
export function AgentInstructionBadge() {
|
|
52
52
|
const parent = useParent()
|
|
@@ -63,12 +63,26 @@ export function AgentInstructionBadge() {
|
|
|
63
63
|
|
|
64
64
|
Returns the nearest enclosing `page` / `collection_view_page` ancestor.
|
|
65
65
|
|
|
66
|
-
Re-renders when the host sends a block location update.
|
|
66
|
+
Re-renders when the host sends a block location update — including when the containing page itself moves (its `parent` changes) without the page ID changing.
|
|
67
67
|
|
|
68
68
|
```ts
|
|
69
|
-
function usePage():
|
|
69
|
+
function usePage(): CustomBlockPage;
|
|
70
|
+
|
|
71
|
+
type CustomBlockPage = {
|
|
72
|
+
id: NotionPageId;
|
|
73
|
+
parent: NotionParent; // the containing page's own parent
|
|
74
|
+
};
|
|
70
75
|
```
|
|
71
76
|
|
|
77
|
+
`page.parent` allows determining if the custom block is in a freestanding page or part of a database. Don't confuse it with `useParent()`, which is the custom block's own parent.
|
|
78
|
+
|
|
79
|
+
```tsx
|
|
80
|
+
const page = usePage();
|
|
81
|
+
const isInsideDatabaseRow = page.parent.type === "data_source_id";
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
For anything beyond location — title, icon, properties — fetch the page through the pages API: `pages.get(page.id)`.
|
|
85
|
+
|
|
72
86
|
For non-React renderers, use `customBlock.getPage()` after `initCustomBlock()` resolves.
|
|
73
87
|
|
|
74
88
|
### `useTheme()`
|
package/docs/lifecycle.md
CHANGED
|
@@ -4,7 +4,7 @@ The SDK ↔ host handshake, the React wrapper that runs it, and the auto-resize
|
|
|
4
4
|
|
|
5
5
|
## Handshake
|
|
6
6
|
|
|
7
|
-
`initCustomBlock()` posts `ready` to `window.parent` and awaits the host's `init` (theme, block ID/parent/page ID, current user, and `dataSources: { bindings }` keyed by semantic data-source key, which the SDK resolves against the manifest). The promise resolves with the normalized initial state, captured in `
|
|
7
|
+
`initCustomBlock()` posts `ready` to `window.parent` and awaits the host's `init` (theme, block ID/parent/page ID, current user, and `dataSources: { bindings }` keyed by semantic data-source key, which the SDK resolves against the manifest). The promise resolves with the normalized initial state, captured in `CustomBlockInitPayload` — `await` it before mounting React so hooks always see populated state.
|
|
8
8
|
|
|
9
9
|
- Default `timeoutMs` is 15000; rejects with `CustomBlockInitError` code `init_timeout` if the host doesn't respond.
|
|
10
10
|
- In a top-level browser tab (no parent frame), rejects with `NotInIframeError` code `not_in_iframe`. `<NotionCustomBlock>` catches this, seeds placeholders, and renders `children` behind a warning banner so dev-time previews still work.
|
|
@@ -45,7 +45,7 @@ function useCustomBlockInit(
|
|
|
45
45
|
type UseCustomBlockInitResult =
|
|
46
46
|
| { isLoaded: false; error: undefined }
|
|
47
47
|
| { isLoaded: false; error: CustomBlockInitFailure }
|
|
48
|
-
| { isLoaded: true; error: undefined; initial:
|
|
48
|
+
| { isLoaded: true; error: undefined; initial: CustomBlockInitPayload };
|
|
49
49
|
```
|
|
50
50
|
|
|
51
51
|
React wrapper around `initCustomBlock` for templates that prefer not to use top-level `await`. Multiple components calling it share the same handshake.
|
|
@@ -64,7 +64,7 @@ function Root() {
|
|
|
64
64
|
```ts
|
|
65
65
|
function initCustomBlock(
|
|
66
66
|
opts?: InitCustomBlockOptions,
|
|
67
|
-
): Promise<
|
|
67
|
+
): Promise<CustomBlockInitPayload>;
|
|
68
68
|
|
|
69
69
|
type InitCustomBlockOptions = { timeoutMs?: number };
|
|
70
70
|
```
|
package/docs/manifest.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Manifest
|
|
2
2
|
|
|
3
|
-
A custom
|
|
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
|
{
|
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
|
|
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
|
@@ -13,7 +13,7 @@ import type {
|
|
|
13
13
|
UseDataSourceOptions,
|
|
14
14
|
} from "../types.js"
|
|
15
15
|
import { unreachable } from "../utils.js"
|
|
16
|
-
import {
|
|
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("[
|
|
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:
|
|
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:
|
|
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("[
|
|
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("[
|
|
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
|
-
"[
|
|
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
|
-
"[
|
|
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(`[
|
|
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
|
-
`[
|
|
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
|
-
`[
|
|
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
|
-
`[
|
|
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
|
-
`[
|
|
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
|
-
`[
|
|
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
|
-
`[
|
|
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
|
-
`[
|
|
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
|
-
`[
|
|
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
|
}
|
|
@@ -41,7 +41,7 @@ export type ManifestLoadResult =
|
|
|
41
41
|
export async function loadManifest(): Promise<ManifestLoadResult> {
|
|
42
42
|
if (typeof fetch !== "function") {
|
|
43
43
|
const message = `No fetch API available; cannot load ${MANIFEST_URL}.`
|
|
44
|
-
console.warn(`[
|
|
44
|
+
console.warn(`[custom-blocks-sdk] ${message}`)
|
|
45
45
|
return {
|
|
46
46
|
manifest: null,
|
|
47
47
|
error: { code: "manifest_unavailable", message },
|
|
@@ -52,7 +52,7 @@ export async function loadManifest(): Promise<ManifestLoadResult> {
|
|
|
52
52
|
response = await fetch(MANIFEST_URL, { credentials: "omit" })
|
|
53
53
|
} catch (error) {
|
|
54
54
|
const message = `Could not fetch ${MANIFEST_URL}.`
|
|
55
|
-
console.warn(`[
|
|
55
|
+
console.warn(`[custom-blocks-sdk] ${message}`, error)
|
|
56
56
|
return {
|
|
57
57
|
manifest: null,
|
|
58
58
|
error: { code: "manifest_unavailable", message },
|
|
@@ -60,12 +60,12 @@ export async function loadManifest(): Promise<ManifestLoadResult> {
|
|
|
60
60
|
}
|
|
61
61
|
if (response.status === 404) {
|
|
62
62
|
const message = `No manifest found at ${MANIFEST_URL} (status ${response.status}).`
|
|
63
|
-
console.warn(`[
|
|
63
|
+
console.warn(`[custom-blocks-sdk] ${message}`)
|
|
64
64
|
return { manifest: null }
|
|
65
65
|
}
|
|
66
66
|
if (!response.ok) {
|
|
67
67
|
const message = `Could not fetch ${MANIFEST_URL} (status ${response.status}).`
|
|
68
|
-
console.warn(`[
|
|
68
|
+
console.warn(`[custom-blocks-sdk] ${message}`)
|
|
69
69
|
return {
|
|
70
70
|
manifest: null,
|
|
71
71
|
error: { code: "manifest_unavailable", message },
|
|
@@ -76,13 +76,13 @@ export async function loadManifest(): Promise<ManifestLoadResult> {
|
|
|
76
76
|
json = await response.json()
|
|
77
77
|
} catch (error) {
|
|
78
78
|
const message = `Manifest at ${MANIFEST_URL} was not valid JSON.`
|
|
79
|
-
console.warn(`[
|
|
79
|
+
console.warn(`[custom-blocks-sdk] ${message}`, error)
|
|
80
80
|
return { manifest: null, error: { code: "manifest_invalid", message } }
|
|
81
81
|
}
|
|
82
82
|
const parsed = v.safeParse(manifestSchema, json)
|
|
83
83
|
if (!parsed.success) {
|
|
84
84
|
const message = `Manifest at ${MANIFEST_URL} did not match schema.`
|
|
85
|
-
console.warn(`[
|
|
85
|
+
console.warn(`[custom-blocks-sdk] ${message}`, parsed.issues)
|
|
86
86
|
return { manifest: null, error: { code: "manifest_invalid", message } }
|
|
87
87
|
}
|
|
88
88
|
return { manifest: parsed.output }
|
package/src/bridge/pages/page.ts
CHANGED
|
@@ -11,8 +11,17 @@ import { notionParentSchema } from "../parent.js"
|
|
|
11
11
|
|
|
12
12
|
export type { NotionPageId } from "../ids.js"
|
|
13
13
|
|
|
14
|
+
/**
|
|
15
|
+
* The custom block's nearest enclosing page ancestor, carried in `init.page` and `pageChanged`.
|
|
16
|
+
*/
|
|
14
17
|
export const customBlockPageSchema = v.object({
|
|
15
18
|
id: notionPageIdSchema,
|
|
19
|
+
/**
|
|
20
|
+
* The containing page's own parent. It lets a block distinguish a freestanding page from a
|
|
21
|
+
* database row without a round trip. Not to be confused with the top-level `parent` runtime
|
|
22
|
+
* value, which is the custom block's own parent.
|
|
23
|
+
*/
|
|
24
|
+
parent: notionParentSchema,
|
|
16
25
|
})
|
|
17
26
|
|
|
18
27
|
export type CustomBlockPage = v.InferOutput<typeof customBlockPageSchema>
|
package/src/index.ts
CHANGED
|
@@ -108,7 +108,7 @@ export { pages, users } from "./bridge/sandboxClient.js"
|
|
|
108
108
|
export type { NotionTheme } from "./bridge/theme.js"
|
|
109
109
|
export { type CustomBlockState, customBlock } from "./customBlock.js"
|
|
110
110
|
export {
|
|
111
|
-
type
|
|
111
|
+
type CustomBlockInitPayload,
|
|
112
112
|
type InitCustomBlockOptions,
|
|
113
113
|
initCustomBlock,
|
|
114
114
|
NotInIframeError,
|
package/src/init.ts
CHANGED
|
@@ -15,7 +15,7 @@ import type { NotionUser } from "./bridge/users/user.js"
|
|
|
15
15
|
/**
|
|
16
16
|
* The payload sent by the host in the `init` message in response to the sandbox's `ready` message.
|
|
17
17
|
*/
|
|
18
|
-
export type
|
|
18
|
+
export type CustomBlockInitPayload = {
|
|
19
19
|
theme: NotionTheme
|
|
20
20
|
blockId: NotionBlockId
|
|
21
21
|
parent: NotionParent
|
|
@@ -60,7 +60,7 @@ const DEFAULT_INIT_TIMEOUT_MS = 15_000
|
|
|
60
60
|
const NOT_IN_IFRAME_MESSAGE =
|
|
61
61
|
"<NotionCustomBlock> only works inside an iframe — use the dev shell or deploy to Notion."
|
|
62
62
|
|
|
63
|
-
let initPromise: Promise<
|
|
63
|
+
let initPromise: Promise<CustomBlockInitPayload> | undefined
|
|
64
64
|
|
|
65
65
|
/**
|
|
66
66
|
* Performs the SDK <-> host handshake: loads `custom_blocks.json`, posts
|
|
@@ -74,7 +74,7 @@ let initPromise: Promise<CustomBlockInitial> | undefined
|
|
|
74
74
|
*/
|
|
75
75
|
export function initCustomBlock(
|
|
76
76
|
opts: InitCustomBlockOptions = {},
|
|
77
|
-
): Promise<
|
|
77
|
+
): Promise<CustomBlockInitPayload> {
|
|
78
78
|
if (initPromise === undefined) {
|
|
79
79
|
initPromise = (async () => {
|
|
80
80
|
// Fail fast with a typed error when rendered as a standalone tab and not in a parent frame.
|
|
@@ -106,7 +106,7 @@ export function initCustomBlock(
|
|
|
106
106
|
}
|
|
107
107
|
if (message.status === "error") {
|
|
108
108
|
console.error(
|
|
109
|
-
`[
|
|
109
|
+
`[custom-blocks-sdk] host reported init error (${message.error.code}): ${message.error.message}`,
|
|
110
110
|
)
|
|
111
111
|
throw new CustomBlockInitError(message.error)
|
|
112
112
|
}
|