@notionhq/custom-blocks-host 0.3.11 → 0.4.1

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 (126) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +143 -236
  3. package/THIRD_PARTY_LICENSES.md +9 -0
  4. package/dist/autoBindProperties.d.ts +5 -2
  5. package/dist/autoBindProperties.d.ts.map +1 -1
  6. package/dist/autoBindProperties.js +5 -2
  7. package/dist/browser.js +1 -1
  8. package/dist/compatibility/addMetadataReadSchemasForProtocol3To4.d.ts +1215 -0
  9. package/dist/compatibility/addMetadataReadSchemasForProtocol3To4.d.ts.map +1 -0
  10. package/dist/compatibility/addMetadataReadSchemasForProtocol3To4.js +27 -0
  11. package/dist/compatibility/normalizeHostToSandboxMessage.d.ts +8 -0
  12. package/dist/compatibility/normalizeHostToSandboxMessage.d.ts.map +1 -0
  13. package/dist/compatibility/normalizeHostToSandboxMessage.js +39 -0
  14. package/dist/compatibility/normalizePageResultsForProtocol3To5.d.ts +11 -0
  15. package/dist/compatibility/normalizePageResultsForProtocol3To5.d.ts.map +1 -0
  16. package/dist/compatibility/normalizePageResultsForProtocol3To5.js +18 -0
  17. package/dist/compatibility/normalizePageResultsForProtocol6.d.ts +9 -0
  18. package/dist/compatibility/normalizePageResultsForProtocol6.d.ts.map +1 -0
  19. package/dist/compatibility/normalizePageResultsForProtocol6.js +18 -0
  20. package/dist/compatibility/normalizeSandboxToHostMessage.d.ts +1099 -0
  21. package/dist/compatibility/normalizeSandboxToHostMessage.d.ts.map +1 -0
  22. package/dist/compatibility/normalizeSandboxToHostMessage.js +9 -0
  23. package/dist/compatibility/pageForProtocol3To5.d.ts +159 -0
  24. package/dist/compatibility/pageForProtocol3To5.d.ts.map +1 -0
  25. package/dist/{lifecycle/legacyPage.js → compatibility/pageForProtocol3To5.js} +50 -9
  26. package/dist/compatibility/pageResult.d.ts +11 -0
  27. package/dist/compatibility/pageResult.d.ts.map +1 -0
  28. package/dist/compatibility/pageWithNullTextFallbackProperties.d.ts +9 -0
  29. package/dist/compatibility/pageWithNullTextFallbackProperties.d.ts.map +1 -0
  30. package/dist/compatibility/pageWithNullTextFallbackProperties.js +46 -0
  31. package/dist/createCustomBlockHost.d.ts +18 -19
  32. package/dist/createCustomBlockHost.d.ts.map +1 -1
  33. package/dist/createCustomBlockHost.js +103 -38
  34. package/dist/index.d.ts +2 -19
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +1 -2
  37. package/dist/{localBlockUrl.d.ts → isAllowedLocalPreviewUrl.d.ts} +2 -2
  38. package/dist/isAllowedLocalPreviewUrl.d.ts.map +1 -0
  39. package/dist/{localBlockUrl.js → isAllowedLocalPreviewUrl.js} +3 -7
  40. package/dist/lifecycle/initErrors.d.ts +1 -0
  41. package/dist/lifecycle/initErrors.d.ts.map +1 -1
  42. package/dist/lifecycle/initErrors.js +7 -0
  43. package/dist/lifecycle/initializationController.d.ts +6 -4
  44. package/dist/lifecycle/initializationController.d.ts.map +1 -1
  45. package/dist/lifecycle/initializationController.js +24 -15
  46. package/dist/lifecycle/resolveManifest.d.ts +13 -0
  47. package/dist/lifecycle/resolveManifest.d.ts.map +1 -0
  48. package/dist/lifecycle/resolveManifest.js +21 -0
  49. package/dist/lifecycle/types.d.ts +1637 -17
  50. package/dist/lifecycle/types.d.ts.map +1 -1
  51. package/dist/lifecycle/types.js +18 -0
  52. package/dist/messages/sandboxMessageRouter.d.ts.map +1 -1
  53. package/dist/messages/sandboxMessageRouter.js +2 -2
  54. package/dist/protocol/dataSources/dataSourcePage.d.ts +3 -4
  55. package/dist/protocol/dataSources/dataSourcePage.js +2 -3
  56. package/dist/protocol/index.d.ts +1 -0
  57. package/dist/protocol/index.js +1 -0
  58. package/dist/protocol/isLocalBlockServerUrl.d.ts +2 -0
  59. package/dist/protocol/isLocalBlockServerUrl.js +9 -0
  60. package/dist/protocol/messages/connect.d.ts +2 -2
  61. package/dist/protocol/messages/connect.js +2 -2
  62. package/dist/protocol/messages/createPageResult.d.ts +2 -2
  63. package/dist/protocol/messages/createPageResult.js +3 -3
  64. package/dist/protocol/messages/getPage.d.ts +2 -2
  65. package/dist/protocol/messages/getPage.js +3 -3
  66. package/dist/protocol/messages/hostToSandbox.d.ts +3 -3
  67. package/dist/protocol/messages/hostToSandbox.js +10 -10
  68. package/dist/protocol/messages/init.d.ts +1 -1
  69. package/dist/protocol/messages/queryDataSource.d.ts +24 -0
  70. package/dist/protocol/messages/queryDataSource.js +2 -0
  71. package/dist/protocol/messages/queryDataSourceResult.d.ts +3 -3
  72. package/dist/protocol/messages/queryDataSourceResult.js +6 -3
  73. package/dist/protocol/messages/sandboxToHost.d.ts +8 -0
  74. package/dist/protocol/messages/updatePageResult.d.ts +2 -2
  75. package/dist/protocol/messages/updatePageResult.js +3 -3
  76. package/dist/protocol/pages/page.d.ts +2 -2
  77. package/dist/protocol/pages/page.js +1 -1
  78. package/dist/protocol/protocolVersion.d.ts +2 -2
  79. package/dist/protocol/protocolVersion.js +2 -2
  80. package/dist/protocol.d.ts +9 -0
  81. package/dist/protocol.d.ts.map +1 -0
  82. package/dist/protocol.js +8 -0
  83. package/docs/bridge/changelog.md +67 -0
  84. package/docs/bridge/protocol.md +189 -0
  85. package/docs/bridge/versioning.md +38 -0
  86. package/docs/compatibility.md +68 -0
  87. package/docs/handlers.md +199 -0
  88. package/docs/lifecycle.md +117 -0
  89. package/package.json +10 -2
  90. package/src/autoBindProperties.ts +5 -2
  91. package/src/compatibility/addMetadataReadSchemasForProtocol3To4.ts +36 -0
  92. package/src/compatibility/normalizeHostToSandboxMessage.ts +54 -0
  93. package/src/compatibility/normalizePageResultsForProtocol3To5.ts +28 -0
  94. package/src/compatibility/normalizePageResultsForProtocol6.ts +23 -0
  95. package/src/compatibility/normalizeSandboxToHostMessage.ts +10 -0
  96. package/src/{lifecycle/legacyPage.ts → compatibility/pageForProtocol3To5.ts} +52 -11
  97. package/src/compatibility/pageResult.ts +18 -0
  98. package/src/compatibility/pageWithNullTextFallbackProperties.ts +53 -0
  99. package/src/createCustomBlockHost.ts +152 -62
  100. package/src/index.ts +4 -51
  101. package/src/{localBlockUrl.ts → isAllowedLocalPreviewUrl.ts} +4 -7
  102. package/src/lifecycle/initErrors.ts +9 -0
  103. package/src/lifecycle/initializationController.ts +34 -19
  104. package/src/lifecycle/resolveManifest.ts +31 -0
  105. package/src/lifecycle/types.ts +84 -16
  106. package/src/messages/sandboxMessageRouter.ts +2 -2
  107. package/src/protocol.ts +8 -0
  108. package/dist/lifecycle/legacyPage.d.ts +0 -70
  109. package/dist/lifecycle/legacyPage.d.ts.map +0 -1
  110. package/dist/lifecycle/loadCustomBlockManifest.d.ts +0 -16
  111. package/dist/lifecycle/loadCustomBlockManifest.d.ts.map +0 -1
  112. package/dist/lifecycle/loadCustomBlockManifest.js +0 -65
  113. package/dist/lifecycle/manifestOptions.d.ts +0 -17
  114. package/dist/lifecycle/manifestOptions.d.ts.map +0 -1
  115. package/dist/lifecycle/normalizeHostToSandboxMessage.d.ts +0 -6
  116. package/dist/lifecycle/normalizeHostToSandboxMessage.d.ts.map +0 -1
  117. package/dist/lifecycle/normalizeHostToSandboxMessage.js +0 -14
  118. package/dist/lifecycle/normalizePage.d.ts +0 -7
  119. package/dist/lifecycle/normalizePage.d.ts.map +0 -1
  120. package/dist/lifecycle/normalizePage.js +0 -47
  121. package/dist/localBlockUrl.d.ts.map +0 -1
  122. package/src/lifecycle/loadCustomBlockManifest.ts +0 -89
  123. package/src/lifecycle/manifestOptions.ts +0 -19
  124. package/src/lifecycle/normalizeHostToSandboxMessage.ts +0 -27
  125. package/src/lifecycle/normalizePage.ts +0 -71
  126. /package/dist/{lifecycle/manifestOptions.js → compatibility/pageResult.js} +0 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) Notion Labs, Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,270 +1,177 @@
1
1
  # @notionhq/custom-blocks-host
2
2
 
3
- The shared host connects an embedding application to a custom block sandbox. It loads the manifest,
4
- prepares initialization, validates messages, routes requests, manages query subscriptions, and
5
- cleans up the connection.
3
+ The host manages the bridge between your application and a custom block iframe. Your application
4
+ owns the iframe, permissions, data access, persistence, analytics, and UI. Block authors use
5
+ `@notionhq/custom-blocks`.
6
6
 
7
- The embedding application creates the iframe and supplies permissions, record access, bindings,
8
- persistence, analytics, and UI. Both Notion and the dev shell use this package. Block authors use
9
- `@notionhq/custom-blocks` instead; they do not need to create a host.
7
+ Create the host before navigating the iframe. Pass the full block URL as `sandboxUrl`, and call
8
+ `stop()` when removing the iframe.
10
9
 
11
- ## `createCustomBlockHost()`
10
+ For configuration and lifecycle details, see [Initialization and lifecycle](docs/lifecycle.md).
12
11
 
13
- Create a host before navigating its iframe to the block URL. Set `sandboxOrigin` to that URL's exact
14
- origin. The host accepts messages only from that iframe and origin, and targets outbound messages to
15
- the same origin.
12
+ ## Create a host
13
+
14
+ Install `@notionhq/custom-blocks-host` in your embedding application. Run this TypeScript through
15
+ your browser bundler after the document body exists.
16
+
17
+ This example embeds a block with no data sources and supplies fixed demo context. Replace
18
+ `https://blocks.example.com/hello` with your block's URL. Serve the block from a different origin
19
+ than the embedding application.
16
20
 
17
21
  ```ts
18
22
  import { createCustomBlockHost } from "@notionhq/custom-blocks-host"
19
23
 
20
- const host = createCustomBlockHost({
21
- iframe,
22
- sandboxOrigin: "https://example.com",
23
- manifestSource: "persisted",
24
- getPersistedManifest: async ({ signal }) => loadSavedManifest(signal),
25
- prepareInitialState: async ({ manifest, signal }) => ({
26
- status: "success",
27
- initialState: {
28
- theme,
29
- contrastMode,
30
- blockId,
31
- parent,
32
- page,
33
- currentUser,
34
- dataSources: {
35
- bindings: await loadBindings(manifest, signal),
36
- },
37
- },
38
- }),
39
- handlers: {
40
- queryDataSource: async message => ({
24
+ function mountCustomBlock(container: HTMLElement, blockUrl: URL): () => void {
25
+ if (
26
+ !["http:", "https:"].includes(blockUrl.protocol) ||
27
+ blockUrl.origin === window.location.origin
28
+ ) {
29
+ throw new Error("The block needs an HTTP or HTTPS URL on a separate origin")
30
+ }
31
+
32
+ const wrapper = document.createElement("section")
33
+ const status = document.createElement("p")
34
+ status.setAttribute("role", "status")
35
+ status.textContent = "Loading block…"
36
+ const iframe = document.createElement("iframe")
37
+ iframe.title = "Hello custom block"
38
+ iframe.setAttribute("sandbox", "allow-scripts allow-same-origin")
39
+ iframe.allow = "clipboard-write"
40
+ iframe.style.width = "100%"
41
+ iframe.style.height = "300px"
42
+ iframe.style.visibility = "hidden"
43
+ wrapper.append(status, iframe)
44
+ container.append(wrapper)
45
+
46
+ function setHeight(height: number) {
47
+ iframe.style.height = `${Math.min(1200, Math.max(120, height))}px`
48
+ }
49
+
50
+ const host = createCustomBlockHost({
51
+ iframe,
52
+ sandboxUrl: blockUrl.href,
53
+ manifestSource: "host",
54
+ getPersistedManifest: async () => ({
41
55
  status: "success",
42
- items: await queryRows(message),
43
- hasMore: false,
56
+ manifest: { version: 1, dataSources: {} },
44
57
  }),
45
- resize: message => resizeIframe(message.height),
46
- },
47
- onInitialized: state => showBlock(state),
48
- onConnectionFailure: error => showHostError(error),
49
- onQueryUnsubscribed: undefined,
50
- onLog: (direction, payload) => console.log(direction, payload),
51
- })
52
- ```
53
-
54
- ### Manifest and initial state
55
-
56
- The host starts loading the manifest when it is created. `manifestSource` determines where the
57
- manifest comes from:
58
-
59
- - `"persisted"` requires `getPersistedManifest({ signal })`. The callback supplies a saved block
60
- definition or a dev-shell worker registry manifest.
61
- - `"local"` fetches `/manifest` from `sandboxOrigin`. The URL must pass
62
- [`isLocalCustomBlockUrl({ url, hostOrigin })`](#local-block-urls), including its origin-isolation
63
- check. Requests omit credentials, reject redirects, bypass caches, and time out after five
64
- seconds. This source has no callback or persisted fallback. Local refers to the block server, not
65
- the host's environment.
66
-
67
- A manifest declares data sources by semantic key, with property schemas and optional block metadata
68
- (`name`, `description`, `slashCommand`, and emoji `icon`). If `dataSources` is omitted, it defaults
69
- to `{}`. Each data source's `properties` also defaults to `{}` when omitted. Hosts and SDKs use the
70
- same manifest shape. The host validates and normalizes the manifest, then calls
71
- `prepareInitialState({ manifest, signal })` once. The embedding application uses that manifest to
72
- prepare context and bindings, and returns them in `initialState`. The host includes the manifest
73
- with that state in `init`.
74
-
75
- Manifest loading and state preparation do not wait for `connect`. The host listens concurrently and
76
- sends `init` after both preparation and connection succeed. If preparation fails before `connect`,
77
- the host retains the failure and sends `init.error` when the sandbox connects.
78
-
79
- Manifest failures produce `manifest_unavailable`. Exceptions during state preparation produce
80
- `unknown_error`. `onLog("warn", payload)` receives diagnostic details. User-facing manifest errors
81
- remain generic. Stopping the host cancels preparation without logging a cancellation warning.
82
-
83
- The embedding application can load saved definitions separately during local preview, for example to
84
- restore saved bindings after preview ends. Local initialization does not wait for those reads.
85
-
86
- ### Property compatibility
87
-
88
- The host selects page and query property shapes using `connect.bridgeProtocolVersion`. Protocol 7
89
- receives unique ID, button, formula, location, rollup, and verification property objects with text
90
- fallbacks. Protocol 6 receives `null` for those properties while retaining supported properties.
91
- This also applies to recent SDKs that briefly supported the richer values while still reporting
92
- protocol 6; those blocks must upgrade their SDK and rebuild to regain the values. Protocols 3–5
93
- retain flat query values and omit unsupported property objects from page responses. The host
94
- continues accepting older protocols; `sdkVersion` remains analytics metadata.
58
+ prepareInitialState: async () => ({
59
+ status: "success",
60
+ initialState: {
61
+ theme: "light",
62
+ contrastMode: "standard",
63
+ blockId: "11111111-1111-4111-8111-111111111111",
64
+ parent: {
65
+ type: "page_id",
66
+ page_id: "22222222-2222-4222-8222-222222222222",
67
+ },
68
+ page: {
69
+ type: "page",
70
+ id: "22222222-2222-4222-8222-222222222222",
71
+ parent: { type: "workspace", workspace: true },
72
+ },
73
+ currentUser: {
74
+ object: "user",
75
+ id: "33333333-3333-4333-8333-333333333333",
76
+ name: "Demo viewer",
77
+ avatar_url: null,
78
+ type: "person",
79
+ person: { email: "viewer@example.com" },
80
+ },
81
+ dataSources: { bindings: {} },
82
+ },
83
+ }),
84
+ handlers: {
85
+ queryDataSource: () => ({
86
+ status: "error",
87
+ error: {
88
+ code: "unknown_data_source_key",
89
+ message: "This demo has no data sources.",
90
+ isRetryable: false,
91
+ },
92
+ }),
93
+ resize: message => setHeight(message.height),
94
+ },
95
+ onStatusChange: state => {
96
+ if (state.status === "initialized") {
97
+ setHeight(state.initialHeight ?? 300)
98
+ status.hidden = true
99
+ iframe.style.visibility = "visible"
100
+ } else if (state.status === "failed") {
101
+ iframe.style.visibility = "hidden"
102
+ status.hidden = false
103
+ status.textContent = `Block failed: ${state.error.code}`
104
+ }
105
+ },
106
+ onQueryUnsubscribed: undefined,
107
+ })
95
108
 
96
- ### Connection lifecycle
109
+ // The host must listen before the block starts its handshake.
110
+ iframe.src = blockUrl.href
97
111
 
98
- Each host handles one handshake with one iframe:
112
+ return () => {
113
+ host.stop()
114
+ wrapper.remove()
115
+ }
116
+ }
99
117
 
100
- ```text
101
- sandbox connect → host init → sandbox initResult
118
+ const dispose = mountCustomBlock(document.body, new URL("https://blocks.example.com/hello"))
119
+ window.addEventListener("pagehide", dispose, { once: true })
102
120
  ```
103
121
 
104
- The host accepts the first `connect` attempt. Later attempts do not restart initialization. Live
105
- updates wait for a matching `initResult.success`.
106
-
107
- The first observed iframe `load` is allowed. A second load stops the host and reports retryable
108
- `iframe_reloaded`. Create a new host and iframe to retry initialization or replace the block URL.
109
-
110
- Stopping removes listeners, cancels preparation, clears timers, ends query subscriptions, and
111
- prevents further outbound messages, including pending responses. Call `stop()` when the iframe is
112
- removed.
113
-
114
- Load-event cleanup does not isolate documents before that event. A replacement document at the same
115
- origin can receive messages and invoke handlers until the host stops.
116
-
117
- All handshake timeouts default to five seconds:
118
-
119
- | Option | Waits for | Failure code |
120
- | ----------------------- | --------------------------------------- | ----------------------- |
121
- | `noConnectTimeoutMs` | `connect` after the iframe's first load | `no_connect` |
122
- | `initialStateTimeoutMs` | State preparation after `connect` | `initial_state_timeout` |
123
- | `noInitResultTimeoutMs` | `initResult` after `init` | `no_init_result` |
124
-
125
- The local manifest fetch has its own five-second timeout, starting when the host is created. The SDK
126
- waits for `init` without its own timeout. Handshake failures abort preparation and suppress late
127
- results. Asynchronous callbacks must respect their `signal`.
128
-
129
- `minBridgeProtocolVersion` defaults to the runtime's minimum supported version. A higher value
130
- rejects SDKs below that minimum. A lower value cannot enable unsupported versions. The host does not
131
- enforce a maximum protocol version.
122
+ You should see “Loading block…” until the block completes initialization, then the block iframe. A
123
+ connection failure displays its error code. The example limits the iframe height to 120–1,200 CSS
124
+ pixels.
132
125
 
133
- Unavailable page context requires protocol 5. With protocols 3 and 4, an unavailable page produces
134
- `context_unavailable` during initialization or ends an active connection.
126
+ Call `dispose()` when your application removes the embed. To retry a failed connection or change the
127
+ block URL, dispose of the current embed and call `mountCustomBlock()` again.
135
128
 
136
- ### Callbacks
129
+ For a production integration, replace the demo context and manifest with your application's values.
130
+ Supply data-source bindings and handlers for the APIs your block uses. Your application must enforce
131
+ permissions and record access.
137
132
 
138
- - `onInitialized({ manifest, initialHeight })` runs after the sandbox accepts initialization. The
139
- height is optional.
140
- - `onConnectionFailure(error)` reports a handshake failure, timeout, unavailable page context, or
141
- iframe reload. The error contains `{ phase, source, code, message, isRetryable }`. `phase` is
142
- `connect`, `init`, `initResult`, or `runtime`. `source` identifies the host or sandbox that
143
- reported the failure. This callback can run after initialization succeeds.
144
- - `onQueryUnsubscribed(subscriptionId)` releases application resources when a subscription ends. The
145
- host removes the subscription itself. The callback runs for unsubscribe, replacement, terminal
146
- error, or host stop, once per ended subscription.
147
- - `onLog(direction, payload)` receives bridge messages and warnings. The embedding application
148
- chooses where to record them.
133
+ To connect workspace data, see [Connect a data source](docs/handlers.md#connect-a-data-source).
149
134
 
150
- ### Request types
135
+ ## Guides and reference
151
136
 
152
- `queryDataSource` is required. Other request handlers are optional. Handlers return a result
153
- synchronously or as a promise. Successful results carry their response payload. Errors carry
154
- `{ code, message, isRetryable }`; error codes are open strings.
137
+ - [Initialization and lifecycle](docs/lifecycle.md): manifests, context, timeouts, live state, and
138
+ cleanup.
139
+ - [Handlers and data](docs/handlers.md): application handlers, data-source integration, queries, and
140
+ navigation.
141
+ - [Host compatibility](docs/compatibility.md): supported versions and response conversions.
155
142
 
156
- TypeScript infers handler request types. Separately defined handlers can import request and
157
- page-property types from `@notionhq/custom-blocks-host`.
143
+ The runtime handles the wire handshake and message validation. The shared protocol references cover
144
+ payload semantics, version rules, and contract history:
158
145
 
159
- Request types such as `GetPageMessage` describe validated messages. Corresponding `*MessageInput`
160
- types accept plain string IDs for callers that construct requests. Initial state, live setters, and
161
- handler results also accept plain string IDs. The runtime validates these values and applies schema
162
- defaults. ID brands distinguish ID kinds in TypeScript; they do not check record existence or
163
- access. The embedding application enforces permissions and record access.
146
+ - [Wire protocol](docs/bridge/protocol.md)
147
+ - [Protocol versioning](docs/bridge/versioning.md)
148
+ - [Protocol changelog](docs/bridge/changelog.md)
164
149
 
165
- ### Query requests
150
+ ## Protocol schemas and types
166
151
 
167
- The SDK resolves semantic data-source and property keys to raw IDs before sending requests. The host
168
- validates filters and sorts against actual database properties in `collectionSchema.propertiesById`
169
- before invoking `queryDataSource`. Status group names and option names must match the bound schema.
170
- Sorts retain their order and contain at most ten unique properties.
152
+ The `/protocol` entrypoint exposes the protocol definitions bundled with this host release. It
153
+ provides message schemas and types, data shapes, IDs, error definitions, and protocol-version
154
+ constants.
171
155
 
172
- ### External navigation
173
-
174
- The `openExternalUrl` handler receives an `AbortSignal` and a normalized, absolute HTTP or HTTPS URL
175
- without credentials, limited to 2,048 JavaScript code units before and after normalization. Adapters
176
- display the exact URL, obtain approval, and navigate only after approval. They may apply additional
177
- URL restrictions and should dismiss confirmation UI when the signal aborts on stop or iframe reload.
178
-
179
- Each iframe permits one pending external-navigation request. A different request receives
180
- `pending_navigation`; a duplicate of the active request is ignored. Every completed request is
181
- forgotten, so a subsequent request requires fresh approval. Results are suppressed after stop. The
182
- runtime redacts `openExternalUrl.url` from `onLog` payloads.
183
-
184
- ### Initialization and malformed messages
185
-
186
- The host validates inbound messages, correlates handshake IDs, and rejects malformed requests.
187
- Malformed messages do not restart initialization. The host logs and ignores stale valid `initResult`
188
- messages.
189
-
190
- The runtime owns the wire handshake and message validation. Integrators use the callbacks and
191
- handlers above; they do not need to construct or validate bridge messages themselves.
192
-
193
- ## Updating live state
194
-
195
- The returned handle exposes `setTheme()`, `setContrastMode()`, `setParent()`, `setPage()`,
196
- `setDataSources()`, `setCurrentUser()`, `refreshQuery()`, and `stop()`.
197
-
198
- State updates made during initialization are held until `initResult.success`. Updates after that
199
- point are sent to the sandbox.
200
-
201
- `refreshQuery()` re-runs `queryDataSource` for each active subscription with the supplied data
202
- source ID, preserving its filter, sorts, and limit. Overlapping refreshes queue one follow-up query.
203
- The running query delivers its result before the follow-up starts.
204
-
205
- Unsubscribe, replacement, and host stop discard queued refreshes and suppress late results.
206
- Non-retryable query errors also end the subscription. Retryable errors retain it. A failed query can
207
- end its subscription while the connection stays active.
208
-
209
- ## Local block URLs
210
-
211
- `isLocalCustomBlockUrl({ url, hostOrigin }: { url: URL; hostOrigin: string })` accepts HTTP(S) URLs
212
- on `localhost`, any `.localhost` subdomain, or `127.0.0.1`, without a username or password. It
213
- rejects URLs on the embedding page's origin and requires a nonempty `hostOrigin`. Pass the embedding
214
- page's serialized origin, such as `window.location.origin`.
215
-
216
- Both local manifest fetching and the dev shell use this policy. Existing callers must add the
217
- required `hostOrigin` argument when adopting this version.
218
-
219
- ## `getAutoBoundPropertyIdsByKey()`
220
-
221
- `getAutoBoundPropertyIdsByKey()` matches manifest properties to database properties for initial
222
- bindings. A candidate must have the same property type and either:
223
-
224
- - An ID that exactly equals the manifest key, without trimming or case folding.
225
- - A display name that equals the manifest display name after trimming and lowercasing.
226
-
227
- Exactly one candidate produces a binding. Zero or multiple candidates leave the property unmapped.
156
+ Import these definitions through the host package to use the protocol version shipped with that
157
+ host:
228
158
 
229
159
  ```ts
230
- import { getAutoBoundPropertyIdsByKey } from "@notionhq/custom-blocks-host"
231
-
232
- const propertyIdsByKey = getAutoBoundPropertyIdsByKey({
233
- manifestProperties: {
234
- title: { name: "Task", type: "title" },
235
- estimate: { name: "Estimate", type: "number" },
236
- },
237
- propertySchemasById: {
238
- title: { name: "Different label", type: "title" },
239
- "property-1": { name: " estimate ", type: "number" },
240
- },
241
- })
242
-
243
- // { title: "title", estimate: "property-1" }
160
+ import {
161
+ sandboxToHostMessageSchema,
162
+ type SandboxToHostMessage,
163
+ } from "@notionhq/custom-blocks-host/protocol"
244
164
  ```
245
165
 
246
- ## Standalone browser module
247
-
248
- The published package includes `dist/browser.js`, a single ES module with all dependencies. Load
249
- this file directly when the browser must not fetch additional modules, such as under an exact-URL
250
- Content Security Policy.
251
-
252
- ## Wire protocol
166
+ `LATEST_BRIDGE_PROTOCOL_VERSION` identifies the latest bridge protocol version defined by the
167
+ bundled protocol.
253
168
 
254
- The host package bundles the wire protocol and exposes its public types. Use the host APIs instead
255
- of posting bridge messages directly or importing protocol implementation files. The protocol is
256
- included in the package; no separate protocol dependency is needed.
169
+ Host APIs, configuration, handler results, and `*MessageInput` types are available from
170
+ `@notionhq/custom-blocks-host`.
257
171
 
258
- ## Date conversion
172
+ These schemas validate wire payloads. Your application still enforces permissions.
259
173
 
260
- `toNotionDateValue()` converts `NotionDateInput` into the four date variants in
261
- `CustomBlockDateValue`, including reminders. Conversion failures throw `InvalidNotionDateError`.
262
-
263
- ## Other exports
264
-
265
- - `LATEST_BRIDGE_PROTOCOL_VERSION`: the latest bridge protocol version bundled with this host
266
- package.
267
- - `isLocalCustomBlockUrl()`: checks whether a URL can serve a local custom block over HTTP or HTTPS.
174
+ ## Standalone browser module
268
175
 
269
- The package also exports types for host configuration, request handlers, messages, and page
270
- properties.
176
+ `dist/browser.js` is one ES module with all dependencies. Use it when the browser must not fetch
177
+ additional modules, such as under an exact-URL Content Security Policy.
@@ -0,0 +1,9 @@
1
+ MIT License
2
+
3
+ Copyright (c) Fabian Hiller
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -4,8 +4,8 @@ export type AutoBindableProperty = {
4
4
  type: NotionPropertyType;
5
5
  };
6
6
  /**
7
- * Finds property IDs for a manifest's properties without changing the
8
- * manifest's semantic keys.
7
+ * Returns a map from manifest property keys to database property IDs.
8
+ * Unmapped keys are omitted.
9
9
  *
10
10
  * For each manifest property:
11
11
  * 1. Consider only schema properties of the same type.
@@ -13,6 +13,9 @@ export type AutoBindableProperty = {
13
13
  * (exact string: no trim, no case folding) or its display name equals
14
14
  * the manifest display name after `trim()` and lowercasing.
15
15
  * 3. Bind when there is exactly one candidate. Otherwise leave it unmapped.
16
+ *
17
+ * An ID match has no priority over a display name match. If they identify two
18
+ * different properties of the same type, the manifest property stays unmapped.
16
19
  */
17
20
  export declare function getAutoBoundPropertyIdsByKey(args: {
18
21
  manifestProperties: Readonly<Record<string, AutoBindableProperty>>;
@@ -1 +1 @@
1
- {"version":3,"file":"autoBindProperties.d.ts","sourceRoot":"","sources":["../src/autoBindProperties.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,gEAAgE,CAAA;AAExG,MAAM,MAAM,oBAAoB,GAAG;IAClC,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,kBAAkB,CAAA;CACxB,CAAA;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,4BAA4B,CAAC,IAAI,EAAE;IAClD,kBAAkB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAC,CAAA;IAClE,mBAAmB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAC,CAAA;CACnE,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAkBzB"}
1
+ {"version":3,"file":"autoBindProperties.d.ts","sourceRoot":"","sources":["../src/autoBindProperties.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,gEAAgE,CAAA;AAExG,MAAM,MAAM,oBAAoB,GAAG;IAClC,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,kBAAkB,CAAA;CACxB,CAAA;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,4BAA4B,CAAC,IAAI,EAAE;IAClD,kBAAkB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAC,CAAA;IAClE,mBAAmB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAC,CAAA;CACnE,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAkBzB"}
@@ -1,6 +1,6 @@
1
1
  /**
2
- * Finds property IDs for a manifest's properties without changing the
3
- * manifest's semantic keys.
2
+ * Returns a map from manifest property keys to database property IDs.
3
+ * Unmapped keys are omitted.
4
4
  *
5
5
  * For each manifest property:
6
6
  * 1. Consider only schema properties of the same type.
@@ -8,6 +8,9 @@
8
8
  * (exact string: no trim, no case folding) or its display name equals
9
9
  * the manifest display name after `trim()` and lowercasing.
10
10
  * 3. Bind when there is exactly one candidate. Otherwise leave it unmapped.
11
+ *
12
+ * An ID match has no priority over a display name match. If they identify two
13
+ * different properties of the same type, the manifest property stays unmapped.
11
14
  */
12
15
  export function getAutoBoundPropertyIdsByKey(args) {
13
16
  const { manifestProperties, propertySchemasById } = args;