@notionhq/custom-blocks-host 0.0.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 (142) hide show
  1. package/README.md +204 -0
  2. package/dist/autoBindProperties.d.ts +21 -0
  3. package/dist/autoBindProperties.d.ts.map +1 -0
  4. package/dist/autoBindProperties.js +38 -0
  5. package/dist/createCustomBlockHost.d.ts +46 -0
  6. package/dist/createCustomBlockHost.d.ts.map +1 -0
  7. package/dist/createCustomBlockHost.js +433 -0
  8. package/dist/index.d.ts +6 -0
  9. package/dist/index.d.ts.map +1 -0
  10. package/dist/index.js +2 -0
  11. package/dist/lifecycle/initErrors.d.ts +13 -0
  12. package/dist/lifecycle/initErrors.d.ts.map +1 -0
  13. package/dist/lifecycle/initErrors.js +152 -0
  14. package/dist/lifecycle/initializationController.d.ts +54 -0
  15. package/dist/lifecycle/initializationController.d.ts.map +1 -0
  16. package/dist/lifecycle/initializationController.js +182 -0
  17. package/dist/lifecycle/protocolVersion.d.ts +2 -0
  18. package/dist/lifecycle/protocolVersion.d.ts.map +1 -0
  19. package/dist/lifecycle/protocolVersion.js +3 -0
  20. package/dist/lifecycle/types.d.ts +28 -0
  21. package/dist/lifecycle/types.d.ts.map +1 -0
  22. package/dist/lifecycle/types.js +1 -0
  23. package/dist/messages/invalidSandboxMessage.d.ts +7 -0
  24. package/dist/messages/invalidSandboxMessage.d.ts.map +1 -0
  25. package/dist/messages/invalidSandboxMessage.js +21 -0
  26. package/dist/messages/types.d.ts +29 -0
  27. package/dist/messages/types.d.ts.map +1 -0
  28. package/dist/messages/types.js +1 -0
  29. package/dist/protocol/contrast.d.ts +4 -0
  30. package/dist/protocol/contrast.js +3 -0
  31. package/dist/protocol/dataSources/dataSource.d.ts +731 -0
  32. package/dist/protocol/dataSources/dataSource.js +37 -0
  33. package/dist/protocol/dataSources/dataSourcePage.d.ts +72 -0
  34. package/dist/protocol/dataSources/dataSourcePage.js +12 -0
  35. package/dist/protocol/dataSources/dataSourceValue.d.ts +66 -0
  36. package/dist/protocol/dataSources/dataSourceValue.js +14 -0
  37. package/dist/protocol/dataSources/dateValue.d.ts +157 -0
  38. package/dist/protocol/dataSources/dateValue.js +59 -0
  39. package/dist/protocol/dataSources/errors.d.ts +8 -0
  40. package/dist/protocol/dataSources/errors.js +7 -0
  41. package/dist/protocol/dataSources/propertySchema.d.ts +197 -0
  42. package/dist/protocol/dataSources/propertySchema.js +165 -0
  43. package/dist/protocol/dataSources/recordPointer.d.ts +9 -0
  44. package/dist/protocol/dataSources/recordPointer.js +8 -0
  45. package/dist/protocol/errors.d.ts +12 -0
  46. package/dist/protocol/errors.js +6 -0
  47. package/dist/protocol/ids.d.ts +31 -0
  48. package/dist/protocol/ids.js +26 -0
  49. package/dist/protocol/incomingType.d.ts +9 -0
  50. package/dist/protocol/incomingType.js +17 -0
  51. package/dist/protocol/index.d.ts +48 -0
  52. package/dist/protocol/index.js +48 -0
  53. package/dist/protocol/manifest.d.ts +60 -0
  54. package/dist/protocol/manifest.js +32 -0
  55. package/dist/protocol/messagePayload.d.ts +19 -0
  56. package/dist/protocol/messagePayload.js +1 -0
  57. package/dist/protocol/messages/connect.d.ts +47 -0
  58. package/dist/protocol/messages/connect.js +38 -0
  59. package/dist/protocol/messages/contrastModeChanged.d.ts +7 -0
  60. package/dist/protocol/messages/contrastModeChanged.js +7 -0
  61. package/dist/protocol/messages/createPage.d.ts +200 -0
  62. package/dist/protocol/messages/createPage.js +52 -0
  63. package/dist/protocol/messages/createPageResult.d.ts +213 -0
  64. package/dist/protocol/messages/createPageResult.js +28 -0
  65. package/dist/protocol/messages/currentUserChanged.d.ts +18 -0
  66. package/dist/protocol/messages/currentUserChanged.js +9 -0
  67. package/dist/protocol/messages/dataSourcesChanged.d.ts +157 -0
  68. package/dist/protocol/messages/dataSourcesChanged.js +13 -0
  69. package/dist/protocol/messages/getPage.d.ts +218 -0
  70. package/dist/protocol/messages/getPage.js +33 -0
  71. package/dist/protocol/messages/getUser.d.ts +44 -0
  72. package/dist/protocol/messages/getUser.js +32 -0
  73. package/dist/protocol/messages/hostToSandbox.d.ts +1167 -0
  74. package/dist/protocol/messages/hostToSandbox.js +34 -0
  75. package/dist/protocol/messages/init.d.ts +259 -0
  76. package/dist/protocol/messages/init.js +56 -0
  77. package/dist/protocol/messages/initResult.d.ts +37 -0
  78. package/dist/protocol/messages/initResult.js +28 -0
  79. package/dist/protocol/messages/invalidHostMessage.d.ts +14 -0
  80. package/dist/protocol/messages/invalidHostMessage.js +13 -0
  81. package/dist/protocol/messages/invalidSandboxMessage.d.ts +14 -0
  82. package/dist/protocol/messages/invalidSandboxMessage.js +13 -0
  83. package/dist/protocol/messages/listUsers.d.ts +52 -0
  84. package/dist/protocol/messages/listUsers.js +33 -0
  85. package/dist/protocol/messages/pageChanged.d.ts +27 -0
  86. package/dist/protocol/messages/pageChanged.js +9 -0
  87. package/dist/protocol/messages/parentChanged.d.ts +24 -0
  88. package/dist/protocol/messages/parentChanged.js +9 -0
  89. package/dist/protocol/messages/queryDataSource.d.ts +985 -0
  90. package/dist/protocol/messages/queryDataSource.js +123 -0
  91. package/dist/protocol/messages/queryDataSourceResult.d.ts +93 -0
  92. package/dist/protocol/messages/queryDataSourceResult.js +28 -0
  93. package/dist/protocol/messages/resize.d.ts +11 -0
  94. package/dist/protocol/messages/resize.js +10 -0
  95. package/dist/protocol/messages/sandboxToHost.d.ts +744 -0
  96. package/dist/protocol/messages/sandboxToHost.js +27 -0
  97. package/dist/protocol/messages/themeChanged.d.ts +10 -0
  98. package/dist/protocol/messages/themeChanged.js +10 -0
  99. package/dist/protocol/messages/updatePage.d.ts +180 -0
  100. package/dist/protocol/messages/updatePage.js +22 -0
  101. package/dist/protocol/messages/updatePageResult.d.ts +212 -0
  102. package/dist/protocol/messages/updatePageResult.js +27 -0
  103. package/dist/protocol/pages/page.d.ts +597 -0
  104. package/dist/protocol/pages/page.js +212 -0
  105. package/dist/protocol/parent.d.ts +28 -0
  106. package/dist/protocol/parent.js +12 -0
  107. package/dist/protocol/protocolVersion.d.ts +6 -0
  108. package/dist/protocol/protocolVersion.js +6 -0
  109. package/dist/protocol/theme.d.ts +3 -0
  110. package/dist/protocol/theme.js +2 -0
  111. package/dist/protocol/users/user.d.ts +32 -0
  112. package/dist/protocol/users/user.js +20 -0
  113. package/dist/queries/querySubscriptions.d.ts +17 -0
  114. package/dist/queries/querySubscriptions.d.ts.map +1 -0
  115. package/dist/queries/querySubscriptions.js +38 -0
  116. package/dist/queries/types.d.ts +4 -0
  117. package/dist/queries/types.d.ts.map +1 -0
  118. package/dist/queries/types.js +1 -0
  119. package/dist/queries/validateQueryDataSourceFilter.d.ts +5 -0
  120. package/dist/queries/validateQueryDataSourceFilter.d.ts.map +1 -0
  121. package/dist/queries/validateQueryDataSourceFilter.js +72 -0
  122. package/dist/queries/validateQueryDataSourceSorts.d.ts +8 -0
  123. package/dist/queries/validateQueryDataSourceSorts.d.ts.map +1 -0
  124. package/dist/queries/validateQueryDataSourceSorts.js +47 -0
  125. package/dist/utils.d.ts +5 -0
  126. package/dist/utils.d.ts.map +1 -0
  127. package/dist/utils.js +6 -0
  128. package/package.json +26 -0
  129. package/src/autoBindProperties.ts +63 -0
  130. package/src/createCustomBlockHost.ts +605 -0
  131. package/src/index.ts +25 -0
  132. package/src/lifecycle/initErrors.ts +165 -0
  133. package/src/lifecycle/initializationController.ts +290 -0
  134. package/src/lifecycle/protocolVersion.ts +3 -0
  135. package/src/lifecycle/types.ts +40 -0
  136. package/src/messages/invalidSandboxMessage.ts +35 -0
  137. package/src/messages/types.ts +80 -0
  138. package/src/queries/querySubscriptions.ts +57 -0
  139. package/src/queries/types.ts +10 -0
  140. package/src/queries/validateQueryDataSourceFilter.ts +101 -0
  141. package/src/queries/validateQueryDataSourceSorts.ts +69 -0
  142. package/src/utils.ts +8 -0
@@ -0,0 +1,165 @@
1
+ import type {
2
+ CustomBlockInitErrorCode,
3
+ CustomBlockInitErrorInfo,
4
+ } from "@notionhq/custom-blocks-protocol/messages/init.js"
5
+
6
+ const UNKNOWN_ERROR_MESSAGE =
7
+ "Unexpected error while initializing custom block."
8
+
9
+ /**
10
+ * Returns the error info for a given `init` failure code.
11
+ *
12
+ * `init` handshakes are one-shot: after an `init` error, this host instance will not answer another
13
+ * `connect` message. `isRetryable` means a fresh iframe / `init` attempt could plausibly succeed
14
+ * without changing block code or host configuration.
15
+ */
16
+ export function initErrorForFailureReason(
17
+ reason: CustomBlockInitErrorCode,
18
+ data: {
19
+ minBridgeProtocolVersion?: number
20
+ currentBridgeProtocolVersion?: number
21
+ } = {},
22
+ ): CustomBlockInitErrorInfo {
23
+ switch (reason) {
24
+ case "no_connect":
25
+ return {
26
+ code: reason,
27
+ message: "Sandbox did not send a connect message.",
28
+ isRetryable: true,
29
+ }
30
+ case "invalid_connect_payload":
31
+ return {
32
+ code: reason,
33
+ message: "Sandbox sent an invalid connect message.",
34
+ // The sandbox must be updated to send a valid connect message.
35
+ isRetryable: false,
36
+ }
37
+ case "manifest_unavailable":
38
+ return {
39
+ code: reason,
40
+ message: "The custom block manifest is unavailable.",
41
+ isRetryable: true,
42
+ }
43
+ case "manifest_invalid":
44
+ return {
45
+ code: reason,
46
+ message: "Sandbox sent an invalid manifest.",
47
+ // The sandbox must be updated to a valid manifest.
48
+ isRetryable: false,
49
+ }
50
+ case "invalid_protocol_version": {
51
+ const versionText = data.currentBridgeProtocolVersion
52
+ ? `: ${data.currentBridgeProtocolVersion}`
53
+ : "."
54
+ return {
55
+ code: reason,
56
+ message: `Sandbox reported an invalid bridge protocol version ${versionText}`,
57
+ // The sandbox must be updated to a supported protocol version.
58
+ isRetryable: false,
59
+ }
60
+ }
61
+ case "unsupported_protocol_version": {
62
+ const currentVersionText = data.currentBridgeProtocolVersion
63
+ ? `${data.currentBridgeProtocolVersion} `
64
+ : ""
65
+ const minVersionText = data.minBridgeProtocolVersion
66
+ ? ` Minimum supported version is ${data.minBridgeProtocolVersion}.`
67
+ : ""
68
+ return {
69
+ code: reason,
70
+ message: `Sandbox bridge protocol version ${currentVersionText}is not supported.${minVersionText}`,
71
+ // The sandbox must be updated to a supported protocol version.
72
+ isRetryable: false,
73
+ }
74
+ }
75
+ case "context_unavailable":
76
+ return {
77
+ code: reason,
78
+ message: "Host page context is unavailable.",
79
+ isRetryable: true,
80
+ }
81
+ case "current_user_unavailable":
82
+ return {
83
+ code: reason,
84
+ message: "Current user is unavailable.",
85
+ isRetryable: true,
86
+ }
87
+ case "missing_data_source_binding":
88
+ return {
89
+ code: reason,
90
+ message: "A required data source is not mapped.",
91
+ // Host configuration must be changed before this block can initialize.
92
+ isRetryable: false,
93
+ }
94
+ case "data_source_unavailable":
95
+ return {
96
+ code: reason,
97
+ message: "A mapped data source is unavailable.",
98
+ // This error combines some retryable errors (e.g. data source failed to load) with
99
+ // some non-retryable errors (e.g. data source does not exist or user does not have
100
+ // access). Fallback to assuming the error is retryable.
101
+ isRetryable: true,
102
+ }
103
+ case "missing_property_binding":
104
+ return {
105
+ code: reason,
106
+ message: "A required data source property is not mapped.",
107
+ // Host configuration must be changed before this block can initialize.
108
+ isRetryable: false,
109
+ }
110
+ case "invalid_property_binding":
111
+ return {
112
+ code: reason,
113
+ message: "A mapped data source property is missing or incompatible.",
114
+ // The bound property's schema must be updated or the binding must be updated to
115
+ // a property with a compatible schema.
116
+ isRetryable: false,
117
+ }
118
+ case "no_init_result":
119
+ return {
120
+ code: reason,
121
+ message: "Sandbox did not acknowledge initialization.",
122
+ isRetryable: true,
123
+ }
124
+ case "invalid_init_result_payload":
125
+ return {
126
+ code: reason,
127
+ message: "Sandbox sent an invalid initialization result.",
128
+ isRetryable: false,
129
+ }
130
+ case "invalid_init_bindings":
131
+ return {
132
+ code: reason,
133
+ message: "Sandbox rejected the host-provided data source bindings.",
134
+ isRetryable: false,
135
+ }
136
+ case "not_in_iframe":
137
+ return {
138
+ code: reason,
139
+ message: "<NotionCustomBlock> only works inside an iframe.",
140
+ // The block must be embedded in an iframe.
141
+ isRetryable: false,
142
+ }
143
+ case "init_timeout":
144
+ return {
145
+ code: reason,
146
+ message: "Host did not respond to init before the timeout.",
147
+ isRetryable: true,
148
+ }
149
+ case "unknown_error":
150
+ return {
151
+ code: reason,
152
+ message: UNKNOWN_ERROR_MESSAGE,
153
+ // Treat unknown errors as non-retryable.
154
+ isRetryable: false,
155
+ }
156
+ default:
157
+ console.warn(`Unexpected custom block init error: ${reason}`)
158
+ return {
159
+ code: reason,
160
+ message: UNKNOWN_ERROR_MESSAGE,
161
+ // Treat unknown error codes as non-retryable.
162
+ isRetryable: false,
163
+ }
164
+ }
165
+ }
@@ -0,0 +1,290 @@
1
+ import type { NotionContrastMode } from "@notionhq/custom-blocks-protocol/contrast.js"
2
+ import type { NotionBlockId } from "@notionhq/custom-blocks-protocol/ids.js"
3
+ import type { CustomBlockManifest } from "@notionhq/custom-blocks-protocol/manifest.js"
4
+ import type { ConnectMessage } from "@notionhq/custom-blocks-protocol/messages/connect.js"
5
+ import type { HostToSandboxMessage } from "@notionhq/custom-blocks-protocol/messages/hostToSandbox.js"
6
+ import type {
7
+ CustomBlockInitErrorInfo,
8
+ InitMessage,
9
+ } from "@notionhq/custom-blocks-protocol/messages/init.js"
10
+ import type { InitResultMessage } from "@notionhq/custom-blocks-protocol/messages/initResult.js"
11
+ import type { CustomBlockPage } from "@notionhq/custom-blocks-protocol/pages/page.js"
12
+ import type { NotionParent } from "@notionhq/custom-blocks-protocol/parent.js"
13
+ import type { NotionTheme } from "@notionhq/custom-blocks-protocol/theme.js"
14
+ import type { NotionUser } from "@notionhq/custom-blocks-protocol/users/user.js"
15
+ import { initErrorForFailureReason } from "./initErrors.js"
16
+ import { isValidBridgeProtocolVersion } from "./protocolVersion.js"
17
+ import type {
18
+ ConnectSuccessMessage,
19
+ CustomBlockHostDataSourcesPayload,
20
+ InitStatus,
21
+ } from "./types.js"
22
+
23
+ const DEFAULT_NO_CONNECT_TIMEOUT_MS = 5_000
24
+ const DEFAULT_NO_INIT_RESULT_TIMEOUT_MS = 5_000
25
+
26
+ type InitializationState = {
27
+ theme: NotionTheme
28
+ contrastMode: NotionContrastMode
29
+ parent: NotionParent
30
+ page: CustomBlockPage
31
+ currentUser: NotionUser
32
+ }
33
+
34
+ type InitializationPreparation =
35
+ | {
36
+ status: "success"
37
+ manifest: CustomBlockManifest
38
+ dataSources: CustomBlockHostDataSourcesPayload
39
+ }
40
+ | {
41
+ status: "error"
42
+ error: CustomBlockInitErrorInfo
43
+ }
44
+
45
+ export type InitializationControllerOptions = {
46
+ blockId: NotionBlockId
47
+ getState: () => InitializationState
48
+ prepareInit: (connect: ConnectSuccessMessage) => InitializationPreparation
49
+ post: (message: HostToSandboxMessage) => void
50
+ warn: (payload: unknown) => void
51
+ flushPendingHostStateChanges: () => void
52
+ minBridgeProtocolVersion: number
53
+ noConnectTimeoutMs?: number
54
+ noInitResultTimeoutMs?: number
55
+ onConnect?: (message: ConnectSuccessMessage) => void
56
+ onInitResult?: (message: InitResultMessage) => void
57
+ onIgnoredConnect?: (message: ConnectMessage) => void
58
+ }
59
+
60
+ export type InitializationController = {
61
+ readonly status: InitStatus
62
+ readonly hasConnectedSandbox: boolean
63
+ handleConnect: (message: ConnectMessage) => void
64
+ handleInitResult: (message: InitResultMessage) => void
65
+ handleInvalidConnect: (initializationId: string) => boolean
66
+ handleInvalidInitResult: () => boolean
67
+ onIframeLoad: () => void
68
+ stop: () => void
69
+ }
70
+
71
+ export function createInitializationController(
72
+ options: InitializationControllerOptions,
73
+ ): InitializationController {
74
+ const {
75
+ blockId,
76
+ getState,
77
+ prepareInit,
78
+ post,
79
+ warn,
80
+ flushPendingHostStateChanges,
81
+ minBridgeProtocolVersion,
82
+ noConnectTimeoutMs = DEFAULT_NO_CONNECT_TIMEOUT_MS,
83
+ noInitResultTimeoutMs = DEFAULT_NO_INIT_RESULT_TIMEOUT_MS,
84
+ onConnect,
85
+ onInitResult,
86
+ onIgnoredConnect,
87
+ } = options
88
+
89
+ let initStatus: InitStatus = "waitingForConnect"
90
+ let noConnectTimeoutId: number | undefined
91
+ let noInitResultTimeoutId: number | undefined
92
+ let activeInitializationId: string | undefined
93
+ let hasConnectedSandbox = false
94
+
95
+ function clearNoConnectTimeout() {
96
+ if (noConnectTimeoutId !== undefined) {
97
+ window.clearTimeout(noConnectTimeoutId)
98
+ noConnectTimeoutId = undefined
99
+ }
100
+ }
101
+
102
+ function clearNoInitResultTimeout() {
103
+ if (noInitResultTimeoutId !== undefined) {
104
+ window.clearTimeout(noInitResultTimeoutId)
105
+ noInitResultTimeoutId = undefined
106
+ }
107
+ }
108
+
109
+ function sendInitError(
110
+ initializationId: string,
111
+ error: CustomBlockInitErrorInfo,
112
+ ) {
113
+ if (initStatus !== "waitingForConnect") {
114
+ return
115
+ }
116
+ initStatus = "error"
117
+ activeInitializationId = initializationId
118
+ clearNoConnectTimeout()
119
+ post({
120
+ type: "init",
121
+ initializationId,
122
+ status: "error",
123
+ error,
124
+ })
125
+ }
126
+
127
+ function sendInit(connect: ConnectSuccessMessage): boolean {
128
+ if (initStatus !== "waitingForConnect") {
129
+ return false
130
+ }
131
+ onConnect?.(connect)
132
+ const preparation = prepareInit(connect)
133
+ if (preparation.status === "error") {
134
+ sendInitError(connect.initializationId, preparation.error)
135
+ return false
136
+ }
137
+ initStatus = "waitingForInitResult"
138
+ activeInitializationId = connect.initializationId
139
+ clearNoConnectTimeout()
140
+ const state = getState()
141
+ const message: InitMessage = {
142
+ type: "init",
143
+ initializationId: connect.initializationId,
144
+ status: "success",
145
+ theme: state.theme,
146
+ contrastMode: state.contrastMode,
147
+ blockId,
148
+ parent: state.parent,
149
+ page: state.page,
150
+ manifest: preparation.manifest,
151
+ dataSources: preparation.dataSources,
152
+ currentUser: state.currentUser,
153
+ }
154
+ post(message)
155
+ noInitResultTimeoutId = window.setTimeout(() => {
156
+ if (initStatus !== "waitingForInitResult") {
157
+ return
158
+ }
159
+ initStatus = "error"
160
+ warn({
161
+ message: "Sandbox did not send initResult",
162
+ initializationId: connect.initializationId,
163
+ })
164
+ }, noInitResultTimeoutMs)
165
+ return true
166
+ }
167
+
168
+ function handleConnect(message: ConnectMessage) {
169
+ if (initStatus !== "waitingForConnect") {
170
+ // A sandbox document sends `connect` exactly once, so this connect
171
+ // belongs to a replacement document — or arrived after this host
172
+ // already gave up waiting. The host is single-initialization; the
173
+ // embedder must recreate it together with the iframe for the new
174
+ // document to initialize.
175
+ warn({
176
+ message:
177
+ "Ignored connect after initialization settled; recreate the host and iframe to re-initialize",
178
+ initializationId: message.initializationId,
179
+ })
180
+ onIgnoredConnect?.(message)
181
+ return
182
+ }
183
+
184
+ // Verify the bridge protocol version is valid.
185
+ if (!isValidBridgeProtocolVersion(message.bridgeProtocolVersion)) {
186
+ const initError = initErrorForFailureReason("invalid_protocol_version", {
187
+ currentBridgeProtocolVersion: message.bridgeProtocolVersion,
188
+ })
189
+ sendInitError(message.initializationId, initError)
190
+ return
191
+ }
192
+ if (message.bridgeProtocolVersion < minBridgeProtocolVersion) {
193
+ const initError = initErrorForFailureReason(
194
+ "unsupported_protocol_version",
195
+ {
196
+ currentBridgeProtocolVersion: message.bridgeProtocolVersion,
197
+ minBridgeProtocolVersion,
198
+ },
199
+ )
200
+ sendInitError(message.initializationId, initError)
201
+ return
202
+ }
203
+
204
+ // If the sandbox reported an error during `connect`, return it as an `init` error.
205
+ if (message.status === "error") {
206
+ sendInitError(message.initializationId, message.error)
207
+ return
208
+ }
209
+
210
+ if (sendInit(message)) {
211
+ hasConnectedSandbox = true
212
+ }
213
+ }
214
+
215
+ function handleInvalidConnect(initializationId: string): boolean {
216
+ if (initStatus !== "waitingForConnect") {
217
+ return false
218
+ }
219
+ sendInitError(
220
+ initializationId,
221
+ initErrorForFailureReason("invalid_connect_payload"),
222
+ )
223
+ return true
224
+ }
225
+
226
+ function handleInvalidInitResult(): boolean {
227
+ if (initStatus !== "waitingForInitResult") {
228
+ return false
229
+ }
230
+ clearNoInitResultTimeout()
231
+ initStatus = "error"
232
+ warn(initErrorForFailureReason("invalid_init_result_payload"))
233
+ return true
234
+ }
235
+
236
+ function onIframeLoad() {
237
+ clearNoConnectTimeout()
238
+ if (initStatus !== "waitingForConnect") {
239
+ // Only set the timeout if the iframe loaded before the `connect` message was sent.
240
+ return
241
+ }
242
+ noConnectTimeoutId = window.setTimeout(() => {
243
+ if (initStatus !== "waitingForConnect") {
244
+ // Only do anything if the iframe loaded before the `connect` message was sent.
245
+ return
246
+ }
247
+ initStatus = "error"
248
+ warn(initErrorForFailureReason("no_connect"))
249
+ }, noConnectTimeoutMs)
250
+ }
251
+
252
+ function handleInitResult(message: InitResultMessage) {
253
+ if (
254
+ initStatus !== "waitingForInitResult" ||
255
+ message.initializationId !== activeInitializationId
256
+ ) {
257
+ warn({
258
+ message: "Ignored stale or unexpected initResult",
259
+ payload: message,
260
+ })
261
+ return
262
+ }
263
+ clearNoInitResultTimeout()
264
+ initStatus = message.status === "success" ? "success" : "error"
265
+ if (initStatus === "success") {
266
+ flushPendingHostStateChanges()
267
+ }
268
+ onInitResult?.(message)
269
+ }
270
+
271
+ function stop() {
272
+ clearNoConnectTimeout()
273
+ clearNoInitResultTimeout()
274
+ }
275
+
276
+ return {
277
+ get status() {
278
+ return initStatus
279
+ },
280
+ get hasConnectedSandbox() {
281
+ return hasConnectedSandbox
282
+ },
283
+ handleConnect,
284
+ handleInitResult,
285
+ handleInvalidConnect,
286
+ handleInvalidInitResult,
287
+ onIframeLoad,
288
+ stop,
289
+ }
290
+ }
@@ -0,0 +1,3 @@
1
+ export function isValidBridgeProtocolVersion(version: number): boolean {
2
+ return Number.isFinite(version) && Number.isInteger(version) && version >= 1
3
+ }
@@ -0,0 +1,40 @@
1
+ import type { NotionContrastMode } from "@notionhq/custom-blocks-protocol/contrast.js"
2
+ import type { NotionDataSourceBindings } from "@notionhq/custom-blocks-protocol/dataSources/dataSource.js"
3
+ import type { NotionBlockId } from "@notionhq/custom-blocks-protocol/ids.js"
4
+ import type { CustomBlockManifest } from "@notionhq/custom-blocks-protocol/manifest.js"
5
+ import type { ConnectMessage } from "@notionhq/custom-blocks-protocol/messages/connect.js"
6
+ import type { CustomBlockPage } from "@notionhq/custom-blocks-protocol/pages/page.js"
7
+ import type { NotionParent } from "@notionhq/custom-blocks-protocol/parent.js"
8
+ import type { NotionTheme } from "@notionhq/custom-blocks-protocol/theme.js"
9
+ import type { NotionUser } from "@notionhq/custom-blocks-protocol/users/user.js"
10
+
11
+ export type ConnectSuccessMessage = Extract<
12
+ ConnectMessage,
13
+ { status: "success" }
14
+ >
15
+
16
+ export type InitStatus =
17
+ | "waitingForConnect"
18
+ | "waitingForInitResult"
19
+ | "success"
20
+ | "error"
21
+
22
+ export type CustomBlockHostDataSourcesPayload = {
23
+ bindings: NotionDataSourceBindings
24
+ }
25
+
26
+ export type CustomBlockHostInitialDataSources =
27
+ | CustomBlockHostDataSourcesPayload
28
+ | ((connect: ConnectSuccessMessage) => CustomBlockHostDataSourcesPayload)
29
+
30
+ export type CustomBlockHostInitialState = {
31
+ theme: NotionTheme
32
+ // TODO(custom-blocks): Make this required when bumping bridge protocol version 4.
33
+ contrastMode?: NotionContrastMode
34
+ blockId: NotionBlockId
35
+ parent: NotionParent
36
+ page: CustomBlockPage
37
+ manifest: CustomBlockManifest
38
+ dataSources: CustomBlockHostInitialDataSources
39
+ currentUser: NotionUser
40
+ }
@@ -0,0 +1,35 @@
1
+ import { readIncomingType } from "@notionhq/custom-blocks-protocol/incomingType.js"
2
+ import type { InvalidSandboxMessage } from "@notionhq/custom-blocks-protocol/messages/invalidSandboxMessage.js"
3
+
4
+ export type InvalidSandboxMessageResponse = {
5
+ incomingType: string | undefined
6
+ nack?: InvalidSandboxMessage
7
+ }
8
+
9
+ export function getInvalidSandboxMessageResponse(
10
+ data: unknown,
11
+ ): InvalidSandboxMessageResponse {
12
+ const incomingType = readIncomingType(data)
13
+ if (
14
+ incomingType === "invalidHostMessage" ||
15
+ incomingType === "invalidSandboxMessage"
16
+ ) {
17
+ return { incomingType }
18
+ }
19
+ return {
20
+ incomingType,
21
+ nack: {
22
+ type: "invalidSandboxMessage",
23
+ reason: nackReasonForInvalidSandboxMessage(incomingType),
24
+ },
25
+ }
26
+ }
27
+
28
+ function nackReasonForInvalidSandboxMessage(
29
+ incomingType: string | undefined,
30
+ ): string {
31
+ if (incomingType !== undefined) {
32
+ return `Unsupported sandbox message of type "${incomingType}"`
33
+ }
34
+ return "Unsupported sandbox message (no `type` field or non-object payload)"
35
+ }
@@ -0,0 +1,80 @@
1
+ import type { BridgeMessagePayload } from "@notionhq/custom-blocks-protocol/messagePayload.js"
2
+ import type { CreatePageMessage } from "@notionhq/custom-blocks-protocol/messages/createPage.js"
3
+ import type {
4
+ CreatePageResultMessage,
5
+ CustomBlockCreatePageErrorInfo,
6
+ } from "@notionhq/custom-blocks-protocol/messages/createPageResult.js"
7
+ import type {
8
+ CustomBlockGetPageErrorInfo,
9
+ GetPageMessage,
10
+ GetPageResultMessage,
11
+ } from "@notionhq/custom-blocks-protocol/messages/getPage.js"
12
+ import type {
13
+ CustomBlockGetUserErrorInfo,
14
+ GetUserMessage,
15
+ GetUserResultMessage,
16
+ } from "@notionhq/custom-blocks-protocol/messages/getUser.js"
17
+ import type {
18
+ CustomBlockListUsersErrorInfo,
19
+ ListUsersMessage,
20
+ ListUsersResultMessage,
21
+ } from "@notionhq/custom-blocks-protocol/messages/listUsers.js"
22
+ import type { QueryDataSourceMessage } from "@notionhq/custom-blocks-protocol/messages/queryDataSource.js"
23
+ import type { ResizeMessage } from "@notionhq/custom-blocks-protocol/messages/resize.js"
24
+ import type { UpdatePageMessage } from "@notionhq/custom-blocks-protocol/messages/updatePage.js"
25
+ import type {
26
+ CustomBlockUpdatePageErrorInfo,
27
+ UpdatePageResultMessage,
28
+ } from "@notionhq/custom-blocks-protocol/messages/updatePageResult.js"
29
+ import type { CustomBlockHostQueryDataSourceResult } from "../queries/types.js"
30
+
31
+ type MaybePromise<T> = T | Promise<T>
32
+
33
+ export type CustomBlockHostCreatePageResult = BridgeMessagePayload<
34
+ CreatePageResultMessage,
35
+ CustomBlockCreatePageErrorInfo
36
+ >
37
+
38
+ export type CustomBlockHostGetPageResult = BridgeMessagePayload<
39
+ GetPageResultMessage,
40
+ CustomBlockGetPageErrorInfo
41
+ >
42
+
43
+ export type CustomBlockHostUpdatePageResult = BridgeMessagePayload<
44
+ UpdatePageResultMessage,
45
+ CustomBlockUpdatePageErrorInfo
46
+ >
47
+
48
+ export type CustomBlockHostGetUserResult = BridgeMessagePayload<
49
+ GetUserResultMessage,
50
+ CustomBlockGetUserErrorInfo
51
+ >
52
+
53
+ export type CustomBlockHostListUsersResult = BridgeMessagePayload<
54
+ ListUsersResultMessage,
55
+ CustomBlockListUsersErrorInfo
56
+ >
57
+
58
+ export type CustomBlockHostHandlers = {
59
+ queryDataSource: (
60
+ message: QueryDataSourceMessage,
61
+ ) => MaybePromise<CustomBlockHostQueryDataSourceResult>
62
+ createPage?: (
63
+ message: CreatePageMessage,
64
+ ) => MaybePromise<CustomBlockHostCreatePageResult>
65
+ getPage?: (
66
+ message: GetPageMessage,
67
+ ) => MaybePromise<CustomBlockHostGetPageResult>
68
+ getUser?: (
69
+ message: GetUserMessage,
70
+ ) => MaybePromise<CustomBlockHostGetUserResult>
71
+ listUsers?: (
72
+ message: ListUsersMessage,
73
+ ) => MaybePromise<CustomBlockHostListUsersResult>
74
+ updatePage?: (
75
+ message: UpdatePageMessage,
76
+ ) => MaybePromise<CustomBlockHostUpdatePageResult>
77
+ resize?: (message: ResizeMessage) => void
78
+ }
79
+
80
+ export type CustomBlockHostLogDirection = "in" | "out" | "warn"
@@ -0,0 +1,57 @@
1
+ import type { NotionDataSourceId } from "@notionhq/custom-blocks-protocol/ids.js"
2
+ import type { QueryDataSourceMessage } from "@notionhq/custom-blocks-protocol/messages/queryDataSource.js"
3
+
4
+ export type QueryRefresh = {
5
+ message: QueryDataSourceMessage
6
+ token: QuerySubscriptionToken
7
+ }
8
+
9
+ export type QuerySubscriptionToken = {
10
+ readonly _tag: "QuerySubscriptionToken"
11
+ }
12
+
13
+ type QuerySubscriptionState = {
14
+ message: QueryDataSourceMessage
15
+ token: QuerySubscriptionToken
16
+ }
17
+
18
+ export class QuerySubscriptions {
19
+ private readonly bySubscriptionId = new Map<string, QuerySubscriptionState>()
20
+
21
+ track(message: QueryDataSourceMessage): QuerySubscriptionToken {
22
+ const token: QuerySubscriptionToken = {
23
+ _tag: "QuerySubscriptionToken",
24
+ }
25
+ this.bySubscriptionId.set(message.subscriptionId, {
26
+ message,
27
+ token,
28
+ })
29
+ return token
30
+ }
31
+
32
+ isCurrent(subscriptionId: string, token: QuerySubscriptionToken): boolean {
33
+ return this.bySubscriptionId.get(subscriptionId)?.token === token
34
+ }
35
+
36
+ refresh(dataSourceId: NotionDataSourceId): QueryRefresh[] {
37
+ const refreshes: QueryRefresh[] = []
38
+ for (const queryState of this.bySubscriptionId.values()) {
39
+ if (queryState.message.dataSourceId !== dataSourceId) {
40
+ continue
41
+ }
42
+ const token: QuerySubscriptionToken = {
43
+ _tag: "QuerySubscriptionToken",
44
+ }
45
+ queryState.token = token
46
+ refreshes.push({
47
+ message: queryState.message,
48
+ token,
49
+ })
50
+ }
51
+ return refreshes
52
+ }
53
+
54
+ clear() {
55
+ this.bySubscriptionId.clear()
56
+ }
57
+ }
@@ -0,0 +1,10 @@
1
+ import type { BridgeMessagePayload } from "@notionhq/custom-blocks-protocol/messagePayload.js"
2
+ import type {
3
+ CustomBlockQueryDataSourceErrorInfo,
4
+ QueryDataSourceResultMessage,
5
+ } from "@notionhq/custom-blocks-protocol/messages/queryDataSourceResult.js"
6
+
7
+ export type CustomBlockHostQueryDataSourceResult = BridgeMessagePayload<
8
+ QueryDataSourceResultMessage,
9
+ CustomBlockQueryDataSourceErrorInfo
10
+ >