@notionhq/custom-blocks 0.0.77 → 0.0.78

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 (92) hide show
  1. package/HOST.md +60 -33
  2. package/bin/src/bridge/manifest.js +3 -3
  3. package/dist/bridge/SandboxBridge.d.ts +7 -12
  4. package/dist/bridge/SandboxBridge.d.ts.map +1 -1
  5. package/dist/bridge/SandboxBridge.js +109 -38
  6. package/dist/bridge/dataSources/resolve.d.ts +3 -4
  7. package/dist/bridge/dataSources/resolve.d.ts.map +1 -1
  8. package/dist/bridge/dataSources/resolve.js +2 -12
  9. package/dist/bridge/hostState.d.ts +2 -0
  10. package/dist/bridge/hostState.d.ts.map +1 -1
  11. package/dist/bridge/loadManifest.d.ts +4 -4
  12. package/dist/bridge/loadManifest.d.ts.map +1 -1
  13. package/dist/bridge/loadManifest.js +2 -2
  14. package/dist/bridge/manifest.d.ts +3 -3
  15. package/dist/bridge/manifest.js +3 -3
  16. package/dist/bridge/messages/{ready.d.ts → connect.d.ts} +13 -11
  17. package/dist/bridge/messages/connect.d.ts.map +1 -0
  18. package/dist/bridge/messages/{ready.js → connect.js} +14 -13
  19. package/dist/bridge/messages/hostToSandbox.d.ts +21 -0
  20. package/dist/bridge/messages/hostToSandbox.d.ts.map +1 -1
  21. package/dist/bridge/messages/init.d.ts +35 -6
  22. package/dist/bridge/messages/init.d.ts.map +1 -1
  23. package/dist/bridge/messages/init.js +8 -4
  24. package/dist/bridge/messages/initResult.d.ts +29 -0
  25. package/dist/bridge/messages/initResult.d.ts.map +1 -0
  26. package/dist/bridge/messages/initResult.js +25 -0
  27. package/dist/bridge/messages/sandboxToHost.d.ts +20 -5
  28. package/dist/bridge/messages/sandboxToHost.d.ts.map +1 -1
  29. package/dist/bridge/messages/sandboxToHost.js +4 -2
  30. package/dist/bridge/sandboxClient.d.ts +2 -4
  31. package/dist/bridge/sandboxClient.d.ts.map +1 -1
  32. package/dist/bridge/sandboxClient.js +2 -5
  33. package/dist/customBlock.d.ts +1 -1
  34. package/dist/customBlock.d.ts.map +1 -1
  35. package/dist/customBlock.js +1 -1
  36. package/dist/host/createCustomBlockHost.d.ts +6 -3
  37. package/dist/host/createCustomBlockHost.d.ts.map +1 -1
  38. package/dist/host/createCustomBlockHost.js +202 -38
  39. package/dist/host/lifecycle/initErrors.d.ts +1 -1
  40. package/dist/host/lifecycle/initErrors.d.ts.map +1 -1
  41. package/dist/host/lifecycle/initErrors.js +25 -7
  42. package/dist/host/lifecycle/types.d.ts +6 -4
  43. package/dist/host/lifecycle/types.d.ts.map +1 -1
  44. package/dist/host/messages/invalidSandboxMessage.js +0 -3
  45. package/dist/host.d.ts +5 -3
  46. package/dist/host.d.ts.map +1 -1
  47. package/dist/host.js +2 -1
  48. package/dist/index.d.ts +2 -2
  49. package/dist/index.d.ts.map +1 -1
  50. package/dist/index.js +1 -1
  51. package/dist/init.d.ts +7 -7
  52. package/dist/init.d.ts.map +1 -1
  53. package/dist/init.js +70 -50
  54. package/dist/react/index.d.ts +1 -1
  55. package/dist/react/index.d.ts.map +1 -1
  56. package/dist/react/standalonePreview.d.ts.map +1 -1
  57. package/dist/react/standalonePreview.js +2 -0
  58. package/dist/react/useCustomBlockInit.d.ts +5 -6
  59. package/dist/react/useCustomBlockInit.d.ts.map +1 -1
  60. package/dist/react/useCustomBlockInit.js +3 -19
  61. package/dist/react/useRuntimeState.d.ts +4 -3
  62. package/dist/react/useRuntimeState.d.ts.map +1 -1
  63. package/dist/react/useRuntimeState.js +3 -2
  64. package/dist/version.js +1 -1
  65. package/docs/data-sources.md +7 -5
  66. package/docs/errors.md +5 -1
  67. package/docs/lifecycle.md +19 -31
  68. package/docs/manifest.md +1 -1
  69. package/package.json +1 -1
  70. package/src/bridge/SandboxBridge.ts +137 -43
  71. package/src/bridge/dataSources/resolve.ts +3 -14
  72. package/src/bridge/hostState.ts +2 -0
  73. package/src/bridge/loadManifest.ts +6 -6
  74. package/src/bridge/manifest.ts +3 -3
  75. package/src/bridge/messages/{ready.ts → connect.ts} +15 -14
  76. package/src/bridge/messages/init.ts +24 -10
  77. package/src/bridge/messages/initResult.ts +37 -0
  78. package/src/bridge/messages/sandboxToHost.ts +4 -2
  79. package/src/bridge/sandboxClient.ts +3 -8
  80. package/src/customBlock.ts +2 -2
  81. package/src/host/createCustomBlockHost.ts +242 -42
  82. package/src/host/lifecycle/initErrors.ts +25 -7
  83. package/src/host/lifecycle/types.ts +13 -4
  84. package/src/host/messages/invalidSandboxMessage.ts +0 -3
  85. package/src/host.ts +13 -2
  86. package/src/index.ts +3 -1
  87. package/src/init.ts +73 -60
  88. package/src/react/index.ts +0 -1
  89. package/src/react/standalonePreview.ts +2 -0
  90. package/src/react/useCustomBlockInit.ts +6 -27
  91. package/src/react/useRuntimeState.ts +4 -3
  92. package/dist/bridge/messages/ready.d.ts.map +0 -1
@@ -17,7 +17,6 @@ import {
17
17
  getDataSourceQueryView as getDataSourceQueryViewWithBridge,
18
18
  } from "./hostState.js"
19
19
  import type { ManifestLoadResult } from "./loadManifest.js"
20
- import type { CustomBlockManifest } from "./manifest.js"
21
20
  import type { InitMessage } from "./messages/init.js"
22
21
  import { type MessageLogEntry, SandboxBridge } from "./SandboxBridge.js"
23
22
 
@@ -26,11 +25,11 @@ export type { MessageLogEntry }
26
25
  const bridge = new SandboxBridge()
27
26
 
28
27
  export const customBlockHost = {
29
- sendReady: (manifestResult: ManifestLoadResult) => {
30
- bridge.sendReady(manifestResult)
28
+ sendConnect: (manifestResult: ManifestLoadResult) => {
29
+ bridge.sendConnect(manifestResult)
31
30
  },
32
31
 
33
- awaitInit: (signal?: AbortSignal): Promise<InitMessage> => {
32
+ awaitInit: (signal?: AbortSignal): Promise<void> => {
34
33
  return bridge.awaitInit(signal)
35
34
  },
36
35
 
@@ -42,10 +41,6 @@ export const customBlockHost = {
42
41
  return bridge.getHostState()
43
42
  },
44
43
 
45
- getManifest: (): CustomBlockManifest | null => {
46
- return bridge.getManifest()
47
- },
48
-
49
44
  /**
50
45
  * Apply an `init` payload directly, bypassing the postMessage handshake for
51
46
  * standalone preview state.
@@ -57,8 +57,8 @@ export const customBlock = {
57
57
  return getInitializedHostState("getPage").page
58
58
  },
59
59
 
60
- getManifest(): CustomBlockManifest | null {
61
- return customBlockHost.getManifest()
60
+ getManifest(): CustomBlockManifest {
61
+ return getInitializedHostState("getManifest").manifest
62
62
  },
63
63
 
64
64
  autoResize,
@@ -1,5 +1,7 @@
1
1
  import * as v from "valibot"
2
2
  import type { NotionDataSourceId } from "../bridge/ids.js"
3
+ import type { CustomBlockManifest } from "../bridge/manifest.js"
4
+ import type { ConnectMessage } from "../bridge/messages/connect.js"
3
5
  import type { CreatePageMessage } from "../bridge/messages/createPage.js"
4
6
  import type { CreatePageResultMessage } from "../bridge/messages/createPageResult.js"
5
7
  import type { DataSourcesChangedMessage } from "../bridge/messages/dataSourcesChanged.js"
@@ -13,9 +15,11 @@ import type {
13
15
  } from "../bridge/messages/getUser.js"
14
16
  import type { HostToSandboxMessage } from "../bridge/messages/hostToSandbox.js"
15
17
  import type {
18
+ CustomBlockInitErrorCode,
16
19
  CustomBlockInitErrorInfo,
17
20
  InitMessage,
18
21
  } from "../bridge/messages/init.js"
22
+ import type { InitResultMessage } from "../bridge/messages/initResult.js"
19
23
  import type {
20
24
  ListUsersMessage,
21
25
  ListUsersResultMessage,
@@ -24,7 +28,6 @@ import type { PageChangedMessage } from "../bridge/messages/pageChanged.js"
24
28
  import type { ParentChangedMessage } from "../bridge/messages/parentChanged.js"
25
29
  import type { QueryDataSourceMessage } from "../bridge/messages/queryDataSource.js"
26
30
  import type { QueryDataSourceResultMessage } from "../bridge/messages/queryDataSourceResult.js"
27
- import type { ReadyMessage } from "../bridge/messages/ready.js"
28
31
  import { sandboxToHostMessageSchema } from "../bridge/messages/sandboxToHost.js"
29
32
  import type { ThemeChangedMessage } from "../bridge/messages/themeChanged.js"
30
33
  import type { UpdatePageMessage } from "../bridge/messages/updatePage.js"
@@ -37,10 +40,10 @@ import { unreachable } from "../utils.js"
37
40
  import { initErrorForFailureReason } from "./lifecycle/initErrors.js"
38
41
  import { isValidBridgeProtocolVersion } from "./lifecycle/ready.js"
39
42
  import type {
43
+ ConnectSuccessMessage,
40
44
  CustomBlockHostDataSourcesPayload,
41
45
  CustomBlockHostInitialState,
42
46
  InitStatus,
43
- ReadySuccessMessage,
44
47
  } from "./lifecycle/types.js"
45
48
  import { getInvalidSandboxMessageResponse } from "./messages/invalidSandboxMessage.js"
46
49
  import type {
@@ -54,10 +57,12 @@ export type CustomBlockHostOptions = {
54
57
  iframe: HTMLIFrameElement
55
58
  targetOrigin?: string
56
59
  minBridgeProtocolVersion?: number
57
- noReadyTimeoutMs?: number
60
+ noConnectTimeoutMs?: number
61
+ noInitResultTimeoutMs?: number
58
62
  initialState: CustomBlockHostInitialState
59
63
  handlers: CustomBlockHostHandlers
60
- onReady?: (message: ReadySuccessMessage) => void
64
+ onConnect?: (message: ConnectSuccessMessage) => void
65
+ onInitResult?: (message: InitResultMessage) => void
61
66
  onLog?: (direction: CustomBlockHostLogDirection, payload: unknown) => void
62
67
  }
63
68
 
@@ -75,7 +80,8 @@ export type CustomBlockHostHandle = {
75
80
  }) => void
76
81
  }
77
82
 
78
- const DEFAULT_NO_READY_TIMEOUT_MS = 5_000
83
+ const DEFAULT_NO_CONNECT_TIMEOUT_MS = 5_000
84
+ const DEFAULT_NO_INIT_RESULT_TIMEOUT_MS = 5_000
79
85
  const DEFAULT_MIN_BRIDGE_PROTOCOL_VERSION = 2
80
86
 
81
87
  export function createCustomBlockHost(
@@ -85,20 +91,31 @@ export function createCustomBlockHost(
85
91
  iframe,
86
92
  initialState,
87
93
  handlers,
88
- onReady,
94
+ onConnect,
95
+ onInitResult,
89
96
  onLog,
90
97
  targetOrigin = "*",
91
98
  minBridgeProtocolVersion = DEFAULT_MIN_BRIDGE_PROTOCOL_VERSION,
92
- noReadyTimeoutMs = DEFAULT_NO_READY_TIMEOUT_MS,
99
+ noConnectTimeoutMs = DEFAULT_NO_CONNECT_TIMEOUT_MS,
100
+ noInitResultTimeoutMs = DEFAULT_NO_INIT_RESULT_TIMEOUT_MS,
93
101
  } = options
94
102
 
95
103
  let theme = initialState.theme
96
104
  let parent = initialState.parent
97
105
  let page = initialState.page
98
106
  let dataSources = initialState.dataSources
107
+ let manifest = initialState.manifest
99
108
  let currentUser = initialState.currentUser
100
- let initStatus: InitStatus = "pending"
101
- let noReadyTimeoutId: number | undefined
109
+ let initStatus: InitStatus = "waitingForConnect"
110
+ let noConnectTimeoutId: number | undefined
111
+ let noInitResultTimeoutId: number | undefined
112
+ let activeInitializationId: string | undefined
113
+ let connectedSandbox: ConnectSuccessMessage | undefined
114
+ let pendingThemeChange = false
115
+ let pendingParentChange = false
116
+ let pendingPageChange = false
117
+ let pendingDataSourcesChange: CustomBlockHostDataSourcesPayload | undefined
118
+ let pendingCurrentUserChange = false
102
119
  const querySubscriptions = new QuerySubscriptions()
103
120
 
104
121
  function emit(direction: CustomBlockHostLogDirection, payload: unknown) {
@@ -132,58 +149,104 @@ export function createCustomBlockHost(
132
149
  post(message)
133
150
  }
134
151
 
135
- function clearNoReadyTimeout() {
136
- if (noReadyTimeoutId !== undefined) {
137
- window.clearTimeout(noReadyTimeoutId)
138
- noReadyTimeoutId = undefined
152
+ function clearNoConnectTimeout() {
153
+ if (noConnectTimeoutId !== undefined) {
154
+ window.clearTimeout(noConnectTimeoutId)
155
+ noConnectTimeoutId = undefined
156
+ }
157
+ }
158
+
159
+ function clearNoInitResultTimeout() {
160
+ if (noInitResultTimeoutId !== undefined) {
161
+ window.clearTimeout(noInitResultTimeoutId)
162
+ noInitResultTimeoutId = undefined
139
163
  }
140
164
  }
141
165
 
142
166
  function resolveDataSourcesPayload(
143
- ready: ReadySuccessMessage,
167
+ connect: ConnectSuccessMessage,
144
168
  ): CustomBlockHostDataSourcesPayload {
145
- return typeof dataSources === "function" ? dataSources(ready) : dataSources
169
+ return typeof dataSources === "function"
170
+ ? dataSources(connect)
171
+ : dataSources
146
172
  }
147
173
 
148
- function sendInit(ready: ReadySuccessMessage): boolean {
149
- if (initStatus !== "pending") {
174
+ function sendInit(connect: ConnectSuccessMessage): boolean {
175
+ if (initStatus !== "waitingForConnect") {
150
176
  return false
151
177
  }
152
- initStatus = "success"
153
- clearNoReadyTimeout()
178
+ onConnect?.(connect)
179
+ manifest = connect.manifest ?? initialState.manifest
180
+ const resolvedDataSources = resolveDataSourcesPayload(connect)
181
+ const bindingErrorCode = getBindingErrorCode(manifest, resolvedDataSources)
182
+ if (bindingErrorCode !== undefined) {
183
+ sendInitError(
184
+ connect.initializationId,
185
+ initErrorForFailureReason(bindingErrorCode),
186
+ )
187
+ return false
188
+ }
189
+ initStatus = "waitingForInitResult"
190
+ activeInitializationId = connect.initializationId
191
+ clearNoConnectTimeout()
154
192
  const message: InitMessage = {
155
193
  type: "init",
194
+ initializationId: connect.initializationId,
156
195
  status: "success",
157
196
  theme,
158
197
  blockId: initialState.blockId,
159
198
  parent,
160
199
  page,
161
- dataSources: resolveDataSourcesPayload(ready),
200
+ manifest,
201
+ dataSources: resolvedDataSources,
162
202
  currentUser,
163
203
  }
164
204
  post(message)
205
+ noInitResultTimeoutId = window.setTimeout(() => {
206
+ if (initStatus !== "waitingForInitResult") {
207
+ return
208
+ }
209
+ initStatus = "error"
210
+ emit("warn", {
211
+ message: "Sandbox did not send initResult",
212
+ initializationId: connect.initializationId,
213
+ })
214
+ }, noInitResultTimeoutMs)
165
215
  return true
166
216
  }
167
217
 
168
- function sendInitError(error: CustomBlockInitErrorInfo) {
169
- if (initStatus !== "pending") {
218
+ function sendInitError(
219
+ initializationId: string,
220
+ error: CustomBlockInitErrorInfo,
221
+ ) {
222
+ if (initStatus !== "waitingForConnect") {
170
223
  return
171
224
  }
172
225
  initStatus = "error"
173
- clearNoReadyTimeout()
226
+ activeInitializationId = initializationId
227
+ clearNoConnectTimeout()
174
228
  post({
175
229
  type: "init",
230
+ initializationId,
176
231
  status: "error",
177
232
  error,
178
233
  })
179
234
  }
180
235
 
181
236
  function onIframeLoad() {
182
- clearNoReadyTimeout()
183
- noReadyTimeoutId = window.setTimeout(() => {
184
- const initError = initErrorForFailureReason("no_ready")
185
- sendInitError(initError)
186
- }, noReadyTimeoutMs)
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
+ emit("warn", initErrorForFailureReason("no_connect"))
249
+ }, noConnectTimeoutMs)
187
250
  }
188
251
 
189
252
  async function handleQuery(message: QueryDataSourceMessage) {
@@ -263,13 +326,13 @@ export function createCustomBlockHost(
263
326
  post(result)
264
327
  }
265
328
 
266
- function handleReady(message: ReadyMessage) {
329
+ function handleConnect(message: ConnectMessage) {
267
330
  // Verify the bridge protocol version is valid.
268
331
  if (!isValidBridgeProtocolVersion(message.bridgeProtocolVersion)) {
269
332
  const initError = initErrorForFailureReason("invalid_protocol_version", {
270
333
  currentBridgeProtocolVersion: message.bridgeProtocolVersion,
271
334
  })
272
- sendInitError(initError)
335
+ sendInitError(message.initializationId, initError)
273
336
  return
274
337
  }
275
338
  if (message.bridgeProtocolVersion < minBridgeProtocolVersion) {
@@ -280,21 +343,70 @@ export function createCustomBlockHost(
280
343
  minBridgeProtocolVersion,
281
344
  },
282
345
  )
283
- sendInitError(initError)
346
+ sendInitError(message.initializationId, initError)
284
347
  return
285
348
  }
286
349
 
287
- // If the sandbox reported an error during `ready`, return it as an `init` error.
350
+ // If the sandbox reported an error during `connect`, return it as an `init` error.
288
351
  if (message.status === "error") {
289
- sendInitError(message.error)
352
+ sendInitError(message.initializationId, message.error)
290
353
  return
291
354
  }
292
355
 
293
- if (sendInit(message)) {
294
- onReady?.(message)
356
+ if (!sendInit(message)) {
357
+ return
358
+ }
359
+ connectedSandbox = message
360
+ }
361
+
362
+ /**
363
+ * Sends any pending host state changes to the sandbox. It is possible for these to change
364
+ * between when the host sends `init` and when the sandbox responds with `initResult`. The host
365
+ * does send any pending state changes until hearing back from the sandbox.
366
+ */
367
+ function flushPendingHostStateChanges() {
368
+ if (pendingThemeChange) {
369
+ pendingThemeChange = false
370
+ post({ type: "themeChanged", theme })
371
+ }
372
+ if (pendingParentChange) {
373
+ pendingParentChange = false
374
+ post({ type: "parentChanged", parent })
375
+ }
376
+ if (pendingPageChange) {
377
+ pendingPageChange = false
378
+ post({ type: "pageChanged", page })
379
+ }
380
+ if (pendingDataSourcesChange !== undefined) {
381
+ const nextDataSources = pendingDataSourcesChange
382
+ pendingDataSourcesChange = undefined
383
+ post({ type: "dataSourcesChanged", dataSources: nextDataSources })
384
+ }
385
+ if (pendingCurrentUserChange) {
386
+ pendingCurrentUserChange = false
387
+ post({ type: "currentUserChanged", currentUser })
295
388
  }
296
389
  }
297
390
 
391
+ function handleInitResult(message: InitResultMessage) {
392
+ if (
393
+ initStatus !== "waitingForInitResult" ||
394
+ message.initializationId !== activeInitializationId
395
+ ) {
396
+ emit("warn", {
397
+ message: "Ignored stale or unexpected initResult",
398
+ payload: message,
399
+ })
400
+ return
401
+ }
402
+ clearNoInitResultTimeout()
403
+ initStatus = message.status === "success" ? "success" : "error"
404
+ if (initStatus === "success") {
405
+ flushPendingHostStateChanges()
406
+ }
407
+ onInitResult?.(message)
408
+ }
409
+
298
410
  function onInvalidSandboxMessage(
299
411
  data: unknown,
300
412
  issues: readonly v.BaseIssue<unknown>[],
@@ -306,10 +418,7 @@ export function createCustomBlockHost(
306
418
  })
307
419
  const response = getInvalidSandboxMessageResponse(data)
308
420
 
309
- if (initStatus === "pending" && response.incomingType === "ready") {
310
- const initError = initErrorForFailureReason("invalid_ready")
311
- sendInitError(initError)
312
- } else if (response.nack !== undefined) {
421
+ if (response.nack !== undefined) {
313
422
  post(response.nack)
314
423
  }
315
424
  }
@@ -328,9 +437,24 @@ export function createCustomBlockHost(
328
437
  const message = parsed.output
329
438
  emit("in", message)
330
439
 
440
+ if (
441
+ message.type !== "connect" &&
442
+ message.type !== "initResult" &&
443
+ initStatus !== "success"
444
+ ) {
445
+ emit("warn", {
446
+ message: `Ignored ${message.type} before initialization completed`,
447
+ payload: message,
448
+ })
449
+ return
450
+ }
451
+
331
452
  switch (message.type) {
332
- case "ready":
333
- handleReady(message)
453
+ case "connect":
454
+ handleConnect(message)
455
+ return
456
+ case "initResult":
457
+ handleInitResult(message)
334
458
  return
335
459
  case "queryDataSource":
336
460
  void handleQuery(message)
@@ -367,27 +491,62 @@ export function createCustomBlockHost(
367
491
  stop() {
368
492
  window.removeEventListener("message", onMessage)
369
493
  iframe.removeEventListener("load", onIframeLoad)
370
- clearNoReadyTimeout()
494
+ clearNoConnectTimeout()
495
+ clearNoInitResultTimeout()
371
496
  querySubscriptions.clear()
372
497
  },
373
498
  post,
374
499
  setTheme(nextTheme) {
375
500
  theme = nextTheme
501
+ if (initStatus !== "success") {
502
+ if (initStatus === "waitingForInitResult") {
503
+ pendingThemeChange = true
504
+ }
505
+ return
506
+ }
376
507
  const message: ThemeChangedMessage = { type: "themeChanged", theme }
377
508
  post(message)
378
509
  },
379
510
  setParent(nextParent) {
380
511
  parent = nextParent
512
+ if (initStatus !== "success") {
513
+ if (initStatus === "waitingForInitResult") {
514
+ pendingParentChange = true
515
+ }
516
+ return
517
+ }
381
518
  const message: ParentChangedMessage = { type: "parentChanged", parent }
382
519
  post(message)
383
520
  },
384
521
  setPage(nextPage) {
385
522
  page = nextPage
523
+ if (initStatus !== "success") {
524
+ if (initStatus === "waitingForInitResult") {
525
+ pendingPageChange = true
526
+ }
527
+ return
528
+ }
386
529
  const message: PageChangedMessage = { type: "pageChanged", page }
387
530
  post(message)
388
531
  },
389
532
  setDataSources(nextDataSources) {
533
+ if (
534
+ connectedSandbox !== undefined &&
535
+ getBindingErrorCode(manifest, nextDataSources) !== undefined
536
+ ) {
537
+ emit("warn", {
538
+ message: "Ignored invalid data source bindings update",
539
+ payload: nextDataSources,
540
+ })
541
+ return
542
+ }
390
543
  dataSources = nextDataSources
544
+ if (initStatus !== "success") {
545
+ if (initStatus === "waitingForInitResult") {
546
+ pendingDataSourcesChange = nextDataSources
547
+ }
548
+ return
549
+ }
391
550
  const message: DataSourcesChangedMessage = {
392
551
  type: "dataSourcesChanged",
393
552
  dataSources: nextDataSources,
@@ -396,9 +555,18 @@ export function createCustomBlockHost(
396
555
  },
397
556
  setCurrentUser(nextCurrentUser) {
398
557
  currentUser = nextCurrentUser
558
+ if (initStatus !== "success") {
559
+ if (initStatus === "waitingForInitResult") {
560
+ pendingCurrentUserChange = true
561
+ }
562
+ return
563
+ }
399
564
  post({ type: "currentUserChanged", currentUser })
400
565
  },
401
566
  refreshQuery({ dataSourceId, response }) {
567
+ if (initStatus !== "success") {
568
+ return
569
+ }
402
570
  const refreshes = querySubscriptions.resolveRefreshes({
403
571
  dataSourceId,
404
572
  response,
@@ -413,3 +581,35 @@ export function createCustomBlockHost(
413
581
  },
414
582
  }
415
583
  }
584
+
585
+ function getBindingErrorCode(
586
+ manifest: CustomBlockManifest,
587
+ dataSources: CustomBlockHostDataSourcesPayload,
588
+ ): CustomBlockInitErrorCode | undefined {
589
+ for (const [dataSourceKey, manifestDataSource] of Object.entries(
590
+ manifest.dataSources,
591
+ )) {
592
+ const binding = dataSources.bindings[dataSourceKey]
593
+ if (
594
+ binding?.collectionPointer === undefined ||
595
+ binding.collectionSchema === undefined
596
+ ) {
597
+ return "missing_data_source_binding"
598
+ }
599
+ for (const [propertyKey, manifestProperty] of Object.entries(
600
+ manifestDataSource.properties ?? {},
601
+ )) {
602
+ const propertyId = binding.propertyIdsByKey?.[propertyKey]
603
+ if (propertyId === undefined) {
604
+ return "missing_property_binding"
605
+ }
606
+ if (
607
+ binding.collectionSchema.propertiesById[propertyId]?.type !==
608
+ manifestProperty.type
609
+ ) {
610
+ return "invalid_property_binding"
611
+ }
612
+ }
613
+ }
614
+ return undefined
615
+ }
@@ -10,7 +10,7 @@ const UNKNOWN_ERROR_MESSAGE =
10
10
  * Returns the error info for a given `init` failure code.
11
11
  *
12
12
  * `init` handshakes are one-shot: after an `init` error, this host instance will not answer another
13
- * `ready` message. `isRetryable` means a fresh iframe / `init` attempt could plausibly succeed
13
+ * `connect` message. `isRetryable` means a fresh iframe / `init` attempt could plausibly succeed
14
14
  * without changing block code or host configuration.
15
15
  */
16
16
  export function initErrorForFailureReason(
@@ -21,23 +21,23 @@ export function initErrorForFailureReason(
21
21
  } = {},
22
22
  ): CustomBlockInitErrorInfo {
23
23
  switch (reason) {
24
- case "no_ready":
24
+ case "no_connect":
25
25
  return {
26
26
  code: reason,
27
- message: "Sandbox did not send a ready message.",
27
+ message: "Sandbox did not send a connect message.",
28
28
  isRetryable: true,
29
29
  }
30
- case "invalid_ready":
30
+ case "invalid_connect":
31
31
  return {
32
32
  code: reason,
33
- message: "Sandbox sent an invalid ready message.",
34
- // The sandbox must be updated to send a valid ready message.
33
+ message: "Sandbox sent an invalid connect message.",
34
+ // The sandbox must be updated to send a valid connect message.
35
35
  isRetryable: false,
36
36
  }
37
37
  case "manifest_unavailable":
38
38
  return {
39
39
  code: reason,
40
- message: "Sandbox could not load its manifest.",
40
+ message: "The custom block manifest is unavailable.",
41
41
  isRetryable: true,
42
42
  }
43
43
  case "manifest_invalid":
@@ -115,6 +115,24 @@ export function initErrorForFailureReason(
115
115
  // a property with a compatible schema.
116
116
  isRetryable: false,
117
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":
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
+ }
118
136
  case "not_in_iframe":
119
137
  return {
120
138
  code: reason,
@@ -1,14 +1,22 @@
1
1
  import type { NotionDataSourceBindings } from "../../bridge/dataSources/dataSource.js"
2
2
  import type { NotionBlockId } from "../../bridge/ids.js"
3
- import type { ReadyMessage } from "../../bridge/messages/ready.js"
3
+ import type { CustomBlockManifest } from "../../bridge/manifest.js"
4
+ import type { ConnectMessage } from "../../bridge/messages/connect.js"
4
5
  import type { CustomBlockPage } from "../../bridge/pages/page.js"
5
6
  import type { NotionParent } from "../../bridge/parent.js"
6
7
  import type { NotionTheme } from "../../bridge/theme.js"
7
8
  import type { NotionUser } from "../../bridge/users/user.js"
8
9
 
9
- export type ReadySuccessMessage = Extract<ReadyMessage, { status: "success" }>
10
+ export type ConnectSuccessMessage = Extract<
11
+ ConnectMessage,
12
+ { status: "success" }
13
+ >
10
14
 
11
- export type InitStatus = "pending" | "success" | "error"
15
+ export type InitStatus =
16
+ | "waitingForConnect"
17
+ | "waitingForInitResult"
18
+ | "success"
19
+ | "error"
12
20
 
13
21
  export type CustomBlockHostDataSourcesPayload = {
14
22
  bindings: NotionDataSourceBindings
@@ -16,13 +24,14 @@ export type CustomBlockHostDataSourcesPayload = {
16
24
 
17
25
  export type CustomBlockHostInitialDataSources =
18
26
  | CustomBlockHostDataSourcesPayload
19
- | ((ready: ReadySuccessMessage) => CustomBlockHostDataSourcesPayload)
27
+ | ((connect: ConnectSuccessMessage) => CustomBlockHostDataSourcesPayload)
20
28
 
21
29
  export type CustomBlockHostInitialState = {
22
30
  theme: NotionTheme
23
31
  blockId: NotionBlockId
24
32
  parent: NotionParent
25
33
  page: CustomBlockPage
34
+ manifest: CustomBlockManifest
26
35
  dataSources: CustomBlockHostInitialDataSources
27
36
  currentUser: NotionUser
28
37
  }
@@ -28,9 +28,6 @@ export function getInvalidSandboxMessageResponse(
28
28
  function nackReasonForInvalidSandboxMessage(
29
29
  incomingType: string | undefined,
30
30
  ): string {
31
- if (incomingType === "ready") {
32
- return "Invalid manifest"
33
- }
34
31
  if (incomingType !== undefined) {
35
32
  return `Unsupported sandbox message of type "${incomingType}"`
36
33
  }
package/src/host.ts CHANGED
@@ -38,6 +38,8 @@ export {
38
38
  notionPageIdSchema,
39
39
  } from "./bridge/ids.js"
40
40
  export { readIncomingType } from "./bridge/incomingType.js"
41
+ export type { ConnectMessage } from "./bridge/messages/connect.js"
42
+ export { connectMessageSchema } from "./bridge/messages/connect.js"
41
43
  export type { CreatePageMessage } from "./bridge/messages/createPage.js"
42
44
  export { createPageMessageSchema } from "./bridge/messages/createPage.js"
43
45
  export type {
@@ -90,6 +92,16 @@ export {
90
92
  customBlockInitErrorInfoSchema,
91
93
  initMessageSchema,
92
94
  } from "./bridge/messages/init.js"
95
+ export type {
96
+ CustomBlockInitResultErrorCode,
97
+ CustomBlockInitResultErrorInfo,
98
+ InitResultMessage,
99
+ } from "./bridge/messages/initResult.js"
100
+ export {
101
+ customBlockInitResultErrorCodeSchema,
102
+ customBlockInitResultErrorInfoSchema,
103
+ initResultMessageSchema,
104
+ } from "./bridge/messages/initResult.js"
93
105
  export type { InvalidHostMessage } from "./bridge/messages/invalidHostMessage.js"
94
106
  export { invalidHostMessageSchema } from "./bridge/messages/invalidHostMessage.js"
95
107
  export type { InvalidSandboxMessage } from "./bridge/messages/invalidSandboxMessage.js"
@@ -122,8 +134,6 @@ export {
122
134
  customBlockQueryDataSourceErrorInfoSchema,
123
135
  queryDataSourceResultMessageSchema,
124
136
  } from "./bridge/messages/queryDataSourceResult.js"
125
- export type { ReadyMessage } from "./bridge/messages/ready.js"
126
- export { readyMessageSchema } from "./bridge/messages/ready.js"
127
137
  export type { ResizeMessage } from "./bridge/messages/resize.js"
128
138
  export { resizeMessageSchema } from "./bridge/messages/resize.js"
129
139
  export type { SandboxToHostMessage } from "./bridge/messages/sandboxToHost.js"
@@ -151,6 +161,7 @@ export {
151
161
  createCustomBlockHost,
152
162
  } from "./host/createCustomBlockHost.js"
153
163
  export type {
164
+ ConnectSuccessMessage,
154
165
  CustomBlockHostDataSourcesPayload,
155
166
  CustomBlockHostInitialDataSources,
156
167
  CustomBlockHostInitialState,
package/src/index.ts CHANGED
@@ -79,8 +79,10 @@ export type {
79
79
  export type {
80
80
  CustomBlockInitErrorCode,
81
81
  CustomBlockInitErrorInfo,
82
+ CustomBlockInitializationErrorCode,
83
+ CustomBlockInitializationErrorInfo,
82
84
  } from "./bridge/messages/init.js"
83
- export { CustomBlockInitError } from "./bridge/messages/init.js"
85
+ export { CustomBlockInitializationError } from "./bridge/messages/init.js"
84
86
  export type {
85
87
  CustomBlockListUsersErrorCode,
86
88
  CustomBlockListUsersErrorInfo,