@notionhq/custom-blocks-host 0.1.19 → 0.1.20
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 +120 -141
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,25 +1,25 @@
|
|
|
1
1
|
# @notionhq/custom-blocks-host
|
|
2
2
|
|
|
3
|
-
Host runtime for the custom block bridge. This package owns the generic
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
Host runtime for the custom block bridge. This package owns the generic `postMessage` protocol loop
|
|
4
|
+
used by hosts such as the local dev shell. It does not own iframe policy, permissions, persistence,
|
|
5
|
+
analytics, or host UI state. Host implementations such as the dev shell and `notion-next` depend on
|
|
6
|
+
this package.
|
|
7
7
|
|
|
8
8
|
## `getAutoBoundPropertyIdsByKey()`
|
|
9
9
|
|
|
10
|
-
`getAutoBoundPropertyIdsByKey()` matches manifest properties to schema
|
|
11
|
-
|
|
10
|
+
`getAutoBoundPropertyIdsByKey()` matches manifest properties to schema properties for initial
|
|
11
|
+
property binding. For each manifest property:
|
|
12
12
|
|
|
13
13
|
1. Consider only schema properties of the same type.
|
|
14
|
-
2. A schema property is a candidate if its ID equals the manifest key
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
2. A schema property is a candidate if its ID equals the manifest key (exact string: no trim, no
|
|
15
|
+
case folding) or its display name equals the manifest display name after `trim()` and
|
|
16
|
+
lowercasing.
|
|
17
17
|
3. Bind when there is exactly one candidate. Otherwise leave it unmapped.
|
|
18
18
|
|
|
19
19
|
Wrong-type properties never enter the candidate set.
|
|
20
20
|
|
|
21
21
|
```ts
|
|
22
|
-
import { getAutoBoundPropertyIdsByKey } from "@notionhq/custom-blocks-host"
|
|
22
|
+
import { getAutoBoundPropertyIdsByKey } from "@notionhq/custom-blocks-host"
|
|
23
23
|
|
|
24
24
|
const propertyIdsByKey = getAutoBoundPropertyIdsByKey({
|
|
25
25
|
manifestProperties: {
|
|
@@ -30,20 +30,19 @@ const propertyIdsByKey = getAutoBoundPropertyIdsByKey({
|
|
|
30
30
|
title: { name: "Different label", type: "title" },
|
|
31
31
|
"property-1": { name: " estimate ", type: "number" },
|
|
32
32
|
},
|
|
33
|
-
})
|
|
33
|
+
})
|
|
34
34
|
|
|
35
35
|
// { title: "title", estimate: "property-1" }
|
|
36
36
|
```
|
|
37
37
|
|
|
38
38
|
## `createCustomBlockHost()`
|
|
39
39
|
|
|
40
|
-
`createCustomBlockHost()` attaches a message listener, performs the
|
|
41
|
-
`
|
|
42
|
-
|
|
43
|
-
updates.
|
|
40
|
+
`createCustomBlockHost()` attaches a message listener, performs the `connect` / `init` /
|
|
41
|
+
`initResult` handshake, validates sandbox messages, routes requests to handlers, manages query
|
|
42
|
+
subscriptions, and emits live state updates.
|
|
44
43
|
|
|
45
44
|
```ts
|
|
46
|
-
import { createCustomBlockHost } from "@notionhq/custom-blocks-host"
|
|
45
|
+
import { createCustomBlockHost } from "@notionhq/custom-blocks-host"
|
|
47
46
|
|
|
48
47
|
const host = createCustomBlockHost({
|
|
49
48
|
iframe,
|
|
@@ -59,99 +58,90 @@ const host = createCustomBlockHost({
|
|
|
59
58
|
dataSources: { bindings },
|
|
60
59
|
},
|
|
61
60
|
handlers: {
|
|
62
|
-
queryDataSource: async
|
|
61
|
+
queryDataSource: async message => ({
|
|
63
62
|
status: "success",
|
|
64
63
|
items: await queryRows(message),
|
|
65
64
|
hasMore: false,
|
|
66
65
|
}),
|
|
67
|
-
createPage: async
|
|
68
|
-
getPage: async
|
|
69
|
-
openPage: async
|
|
70
|
-
updatePage: async
|
|
71
|
-
getUser: async
|
|
72
|
-
listUsers: async
|
|
73
|
-
resize:
|
|
66
|
+
createPage: async message => createPage(message),
|
|
67
|
+
getPage: async message => getPage(message),
|
|
68
|
+
openPage: async message => openPage(message),
|
|
69
|
+
updatePage: async message => updatePage(message),
|
|
70
|
+
getUser: async message => getUser(message),
|
|
71
|
+
listUsers: async message => listUsers(message),
|
|
72
|
+
resize: message => resizeIframe(message.height),
|
|
74
73
|
},
|
|
75
|
-
onManifest:
|
|
76
|
-
console.log("Selected manifest", manifest)
|
|
74
|
+
onManifest: manifest => {
|
|
75
|
+
console.log("Selected manifest", manifest)
|
|
77
76
|
},
|
|
78
|
-
onInitialized:
|
|
79
|
-
console.log("Initialized manifest", state.manifest)
|
|
77
|
+
onInitialized: state => {
|
|
78
|
+
console.log("Initialized manifest", state.manifest)
|
|
80
79
|
},
|
|
81
|
-
onInitializationFailure:
|
|
82
|
-
showInitializationError(error)
|
|
80
|
+
onInitializationFailure: error => {
|
|
81
|
+
showInitializationError(error)
|
|
83
82
|
},
|
|
84
83
|
onRestartRequired: () => {
|
|
85
|
-
remountHostAndIframe()
|
|
84
|
+
remountHostAndIframe()
|
|
86
85
|
},
|
|
87
86
|
onLog: (direction, payload) => {
|
|
88
|
-
console.log(direction, payload)
|
|
87
|
+
console.log(direction, payload)
|
|
89
88
|
},
|
|
90
|
-
})
|
|
89
|
+
})
|
|
91
90
|
```
|
|
92
91
|
|
|
93
|
-
`minBridgeProtocolVersion` is optional and defaults to the oldest protocol version the host runtime
|
|
94
|
-
Set a higher value to retire older SDKs. Lower values cannot enable versions the runtime
|
|
95
|
-
The host does not enforce a maximum protocol version.
|
|
92
|
+
`minBridgeProtocolVersion` is optional and defaults to the oldest protocol version the host runtime
|
|
93
|
+
supports. Set a higher value to retire older SDKs. Lower values cannot enable versions the runtime
|
|
94
|
+
no longer supports. The host does not enforce a maximum protocol version.
|
|
96
95
|
|
|
97
96
|
Each host instance initializes once:
|
|
98
97
|
|
|
99
|
-
- `onManifest` provides the selected manifest as soon as it is available after
|
|
100
|
-
|
|
101
|
-
|
|
98
|
+
- `onManifest` provides the selected manifest as soon as it is available after `connect`, before
|
|
99
|
+
binding validation and `onInitialized`. Use it to configure bindings even when initialization
|
|
100
|
+
cannot yet succeed.
|
|
102
101
|
|
|
103
|
-
- `onInitialized` reports success after the sandbox accepts initialization. It
|
|
104
|
-
|
|
102
|
+
- `onInitialized` reports success after the sandbox accepts initialization. It provides the selected
|
|
103
|
+
`manifest` and the sandbox's optional `initialHeight`.
|
|
105
104
|
- `onInitializationFailure` reports an initialization error or timeout.
|
|
106
|
-
- `onRestartRequired` reports a connection attempt after the host accepts its
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
This can occur after the iframe document changes or a connection arrives
|
|
110
|
-
after a timeout.
|
|
105
|
+
- `onRestartRequired` reports a connection attempt after the host accepts its first connection or
|
|
106
|
+
stops waiting. Recreate the host and iframe together to accept a new connection. This can occur
|
|
107
|
+
after the iframe document changes or a connection arrives after a timeout.
|
|
111
108
|
|
|
112
|
-
The host uses the manifest from `connect`, or `initialState.manifest` if
|
|
113
|
-
|
|
114
|
-
`manifest_unavailable`.
|
|
109
|
+
The host uses the manifest from `connect`, or `initialState.manifest` if `connect` omits it. If
|
|
110
|
+
neither exists, initialization fails with `manifest_unavailable`.
|
|
115
111
|
|
|
116
|
-
`queryDataSource` is required. Other request handlers are optional. Every
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
while errors carry `{ code, message, isRetryable }`.
|
|
112
|
+
`queryDataSource` is required. Other request handlers are optional. Every request handler may return
|
|
113
|
+
its result synchronously or as a promise. Results use a `status` discriminator: successful results
|
|
114
|
+
carry their response payload, while errors carry `{ code, message, isRetryable }`.
|
|
120
115
|
|
|
121
116
|
### Request types
|
|
122
117
|
|
|
123
|
-
TypeScript infers request types for handlers passed to `createCustomBlockHost()`.
|
|
124
|
-
|
|
125
|
-
|
|
118
|
+
TypeScript infers request types for handlers passed to `createCustomBlockHost()`. For separately
|
|
119
|
+
defined handlers and helpers, import the corresponding request and page-property types directly from
|
|
120
|
+
`@notionhq/custom-blocks-host`.
|
|
126
121
|
|
|
127
|
-
Request types such as `GetPageMessage` describe validated messages that handlers
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
access.
|
|
122
|
+
Request types such as `GetPageMessage` describe validated messages that handlers receive. The
|
|
123
|
+
corresponding `*MessageInput` types accept plain string IDs for adapters and test fixtures that
|
|
124
|
+
construct requests. These types do not validate values at runtime. The host runtime validates
|
|
125
|
+
incoming messages before it calls handlers. Your implementation remains responsible for permissions
|
|
126
|
+
and record access.
|
|
133
127
|
|
|
134
128
|
### Query requests
|
|
135
129
|
|
|
136
|
-
The `queryDataSource` handler receives raw data source and property IDs. Its
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
collection schema when available, and applies valid sorts in the order provided
|
|
141
|
-
by the array.
|
|
130
|
+
The `queryDataSource` handler receives raw data source and property IDs. Its optional `filter` and
|
|
131
|
+
`sorts` fields have already been resolved by the SDK. The host validates that sorts contain at most
|
|
132
|
+
10 unique property IDs, checks property IDs and currently supported property types against the bound
|
|
133
|
+
collection schema when available, and applies valid sorts in the order provided by the array.
|
|
142
134
|
|
|
143
|
-
When a data source binding includes `collectionSchema`, the runtime validates
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
handlers remain responsible for validating option names.
|
|
135
|
+
When a data source binding includes `collectionSchema`, the runtime validates select, multi-select,
|
|
136
|
+
and status option names before invoking `queryDataSource`. Bindings without a collection schema
|
|
137
|
+
cannot perform this check, so their query handlers remain responsible for validating option names.
|
|
147
138
|
|
|
148
|
-
`initialState.dataSources` may also be a function of the successful `connect`
|
|
149
|
-
|
|
139
|
+
`initialState.dataSources` may also be a function of the successful `connect` message. This lets a
|
|
140
|
+
host derive bindings from the sandbox-provided manifest.
|
|
150
141
|
|
|
151
|
-
Set `sandboxOrigin` to the iframe's exact origin. The runtime targets outbound
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
default to five seconds.
|
|
142
|
+
Set `sandboxOrigin` to the iframe's exact origin. The runtime targets outbound messages to that
|
|
143
|
+
origin and rejects inbound messages from any other origin. The runtime also accepts
|
|
144
|
+
`noConnectTimeoutMs` and `noInitResultTimeoutMs`; both default to five seconds.
|
|
155
145
|
|
|
156
146
|
### Initialization and malformed messages
|
|
157
147
|
|
|
@@ -161,42 +151,38 @@ default to five seconds.
|
|
|
161
151
|
sandbox connect → host init → sandbox initResult
|
|
162
152
|
```
|
|
163
153
|
|
|
164
|
-
The sandbox creates the `initializationId`. The host copies that ID into `init`.
|
|
165
|
-
|
|
166
|
-
|
|
154
|
+
The sandbox creates the `initializationId`. The host copies that ID into `init`. The sandbox copies
|
|
155
|
+
it into `initResult`. The host sends live state changes only after it receives a matching
|
|
156
|
+
`initResult.success`.
|
|
167
157
|
|
|
168
|
-
The host must check that the `initializationId` in `initResult` exactly matches
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
initialization ends with `no_init_result`.
|
|
158
|
+
The host must check that the `initializationId` in `initResult` exactly matches the ID in the `init`
|
|
159
|
+
message. The host ignores a result with a different ID. If no result arrives, or only a stale valid
|
|
160
|
+
result arrives, before the timeout, initialization ends with `no_init_result`.
|
|
172
161
|
|
|
173
|
-
The host validates each inbound sandbox message with `sandboxToHostMessageSchema`
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
162
|
+
The host validates each inbound sandbox message with `sandboxToHostMessageSchema` before it handles
|
|
163
|
+
the message. If parsing fails, the host uses `readIncomingType()` to identify the attempted message.
|
|
164
|
+
It replies with `invalidSandboxMessage`, a negative acknowledgment (NACK). The host never sends a
|
|
165
|
+
NACK in response to a NACK.
|
|
177
166
|
|
|
178
|
-
Malformed sandbox messages do not change the host's initialization state or
|
|
179
|
-
|
|
167
|
+
Malformed sandbox messages do not change the host's initialization state or timeout, except in these
|
|
168
|
+
two cases:
|
|
180
169
|
|
|
181
|
-
- The host expects `connect` and receives a malformed `connect` with a readable
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
- The host expects `initResult` and receives a malformed `initResult` with the
|
|
185
|
-
|
|
186
|
-
`invalid_init_result_payload`. This error code describes a local host failure.
|
|
187
|
-
|
|
170
|
+
- The host expects `connect` and receives a malformed `connect` with a readable `initializationId`.
|
|
171
|
+
The host sends `init.error` with `invalid_connect_payload` instead of a NACK, then fails
|
|
172
|
+
initialization.
|
|
173
|
+
- The host expects `initResult` and receives a malformed `initResult` with the active
|
|
174
|
+
`initializationId`. The host sends a NACK and fails initialization with
|
|
175
|
+
`invalid_init_result_payload`. This error code describes a local host failure. The host does not
|
|
176
|
+
send it in `initResult.error`.
|
|
188
177
|
|
|
189
|
-
A malformed `initResult` with a missing or stale ID does not end initialization
|
|
190
|
-
|
|
191
|
-
it succeeds or fails.
|
|
178
|
+
A malformed `initResult` with a missing or stale ID does not end initialization or reset its
|
|
179
|
+
timeout. Malformed messages do not restart initialization after it succeeds or fails.
|
|
192
180
|
|
|
193
|
-
The host accepts a valid `initResult.error` as a terminal initialization
|
|
194
|
-
|
|
195
|
-
`initializationId` or an unexpected state.
|
|
181
|
+
The host accepts a valid `initResult.error` as a terminal initialization failure. It logs and
|
|
182
|
+
ignores a valid `initResult` with a stale `initializationId` or an unexpected state.
|
|
196
183
|
|
|
197
|
-
Hosts do not retry malformed messages. A host that receives a `connect` after
|
|
198
|
-
|
|
199
|
-
and iframe together.
|
|
184
|
+
Hosts do not retry malformed messages. A host that receives a `connect` after initialization has
|
|
185
|
+
settled must use `onRestartRequired` to recreate the host and iframe together.
|
|
200
186
|
|
|
201
187
|
## Updating live state
|
|
202
188
|
|
|
@@ -212,42 +198,35 @@ The returned handle exposes:
|
|
|
212
198
|
- `post()`
|
|
213
199
|
- `stop()`
|
|
214
200
|
|
|
215
|
-
State updates are sent after `initResult.success`. Updates made earlier are
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
ID, preserving each subscription's filter, sorts, and limit.
|
|
201
|
+
State updates are sent after `initResult.success`. Updates made earlier are held until
|
|
202
|
+
initialization completes. `refreshQuery()` re-runs `queryDataSource` for every active subscription
|
|
203
|
+
with the supplied data source ID, preserving each subscription's filter, sorts, and limit.
|
|
219
204
|
|
|
220
|
-
The runtime removes a subscription when it receives
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
handler was running.
|
|
205
|
+
The runtime removes a subscription when it receives `unsubscribeDataSourceQuery`. Repeated
|
|
206
|
+
unsubscribe messages are safe. The runtime also ignores results from a query that was unsubscribed
|
|
207
|
+
while its handler was running.
|
|
224
208
|
|
|
225
|
-
Call `stop()` when the iframe is removed. It detaches listeners, clears
|
|
226
|
-
|
|
209
|
+
Call `stop()` when the iframe is removed. It detaches listeners, clears timeouts, and discards
|
|
210
|
+
active query subscriptions.
|
|
227
211
|
|
|
228
212
|
## Wire protocol
|
|
229
213
|
|
|
230
|
-
`createCustomBlockHost()` implements the protocol: it validates inbound
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
`
|
|
249
|
-
|
|
250
|
-
Error payloads use `{ code, message, isRetryable }`. The runtime accepts any
|
|
251
|
-
string for `code`, including codes that this package does not know yet. Host
|
|
252
|
-
code should preserve unknown codes and use `isRetryable` to decide whether a
|
|
253
|
-
retry is appropriate.
|
|
214
|
+
`createCustomBlockHost()` implements the protocol: it validates inbound messages, runs the
|
|
215
|
+
initialization handshake, correlates requests, manages query subscriptions, and emits bridge NACKs.
|
|
216
|
+
Use its state-update methods and handlers instead of posting bridge messages directly.
|
|
217
|
+
|
|
218
|
+
The bridge protocol is maintained in this repository's `protocol/` package. The published host
|
|
219
|
+
package includes a compiled copy of that protocol and does not expose it as a public subpath.
|
|
220
|
+
Consumers should use the host APIs above instead of importing protocol implementation files.
|
|
221
|
+
|
|
222
|
+
Host source in this repository imports protocol schemas and types from the workspace package. The
|
|
223
|
+
publish build rewrites those imports to the bundled copy.
|
|
224
|
+
|
|
225
|
+
Handler implementations must preserve message identity: one-shot operations return one result with
|
|
226
|
+
the request's `requestId`, while `queryDataSource` is a long-lived subscription keyed by
|
|
227
|
+
`subscriptionId`. The runtime handles the `connect` → `init` → `initResult` lifecycle, including
|
|
228
|
+
waiting for `initResult.success` before live updates.
|
|
229
|
+
|
|
230
|
+
Error payloads use `{ code, message, isRetryable }`. The runtime accepts any string for `code`,
|
|
231
|
+
including codes that this package does not know yet. Host code should preserve unknown codes and use
|
|
232
|
+
`isRetryable` to decide whether a retry is appropriate.
|