@notionhq/custom-blocks 0.0.76 → 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 (109) hide show
  1. package/HOST.md +68 -37
  2. package/bin/src/bridge/manifest.js +3 -3
  3. package/dist/bridge/SandboxBridge.d.ts +6 -12
  4. package/dist/bridge/SandboxBridge.d.ts.map +1 -1
  5. package/dist/bridge/SandboxBridge.js +123 -71
  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 +3 -2
  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 +24 -79
  20. package/dist/bridge/messages/hostToSandbox.d.ts.map +1 -1
  21. package/dist/bridge/messages/hostToSandbox.js +2 -3
  22. package/dist/bridge/messages/init.d.ts +35 -6
  23. package/dist/bridge/messages/init.d.ts.map +1 -1
  24. package/dist/bridge/messages/init.js +8 -4
  25. package/dist/bridge/messages/initResult.d.ts +29 -0
  26. package/dist/bridge/messages/initResult.d.ts.map +1 -0
  27. package/dist/bridge/messages/initResult.js +25 -0
  28. package/dist/bridge/messages/queryDataSource.d.ts +3 -7
  29. package/dist/bridge/messages/queryDataSource.d.ts.map +1 -1
  30. package/dist/bridge/messages/queryDataSource.js +3 -7
  31. package/dist/bridge/messages/queryDataSourceResult.d.ts +4 -240
  32. package/dist/bridge/messages/queryDataSourceResult.d.ts.map +1 -1
  33. package/dist/bridge/messages/queryDataSourceResult.js +4 -22
  34. package/dist/bridge/messages/sandboxToHost.d.ts +21 -7
  35. package/dist/bridge/messages/sandboxToHost.d.ts.map +1 -1
  36. package/dist/bridge/messages/sandboxToHost.js +4 -2
  37. package/dist/bridge/sandboxClient.d.ts +2 -4
  38. package/dist/bridge/sandboxClient.d.ts.map +1 -1
  39. package/dist/bridge/sandboxClient.js +2 -5
  40. package/dist/customBlock.d.ts +1 -1
  41. package/dist/customBlock.d.ts.map +1 -1
  42. package/dist/customBlock.js +1 -1
  43. package/dist/host/createCustomBlockHost.d.ts +6 -3
  44. package/dist/host/createCustomBlockHost.d.ts.map +1 -1
  45. package/dist/host/createCustomBlockHost.js +227 -58
  46. package/dist/host/lifecycle/initErrors.d.ts +1 -1
  47. package/dist/host/lifecycle/initErrors.d.ts.map +1 -1
  48. package/dist/host/lifecycle/initErrors.js +25 -7
  49. package/dist/host/lifecycle/types.d.ts +6 -4
  50. package/dist/host/lifecycle/types.d.ts.map +1 -1
  51. package/dist/host/messages/invalidSandboxMessage.js +0 -3
  52. package/dist/host/queries/querySubscriptions.d.ts +10 -10
  53. package/dist/host/queries/querySubscriptions.d.ts.map +1 -1
  54. package/dist/host/queries/querySubscriptions.js +35 -19
  55. package/dist/host/queries/types.d.ts +5 -4
  56. package/dist/host/queries/types.d.ts.map +1 -1
  57. package/dist/host.d.ts +5 -3
  58. package/dist/host.d.ts.map +1 -1
  59. package/dist/host.js +2 -1
  60. package/dist/index.d.ts +2 -2
  61. package/dist/index.d.ts.map +1 -1
  62. package/dist/index.js +1 -1
  63. package/dist/init.d.ts +7 -7
  64. package/dist/init.d.ts.map +1 -1
  65. package/dist/init.js +70 -50
  66. package/dist/react/index.d.ts +1 -1
  67. package/dist/react/index.d.ts.map +1 -1
  68. package/dist/react/standalonePreview.d.ts.map +1 -1
  69. package/dist/react/standalonePreview.js +2 -0
  70. package/dist/react/useCustomBlockInit.d.ts +5 -6
  71. package/dist/react/useCustomBlockInit.d.ts.map +1 -1
  72. package/dist/react/useCustomBlockInit.js +3 -19
  73. package/dist/react/useRuntimeState.d.ts +4 -3
  74. package/dist/react/useRuntimeState.d.ts.map +1 -1
  75. package/dist/react/useRuntimeState.js +3 -2
  76. package/dist/version.js +1 -1
  77. package/docs/data-sources.md +7 -5
  78. package/docs/errors.md +5 -1
  79. package/docs/lifecycle.md +19 -31
  80. package/docs/manifest.md +1 -1
  81. package/package.json +1 -1
  82. package/src/bridge/SandboxBridge.ts +151 -77
  83. package/src/bridge/dataSources/resolve.ts +3 -14
  84. package/src/bridge/hostState.ts +3 -2
  85. package/src/bridge/loadManifest.ts +6 -6
  86. package/src/bridge/manifest.ts +3 -3
  87. package/src/bridge/messages/{ready.ts → connect.ts} +15 -14
  88. package/src/bridge/messages/hostToSandbox.ts +2 -6
  89. package/src/bridge/messages/init.ts +24 -10
  90. package/src/bridge/messages/initResult.ts +37 -0
  91. package/src/bridge/messages/queryDataSource.ts +3 -7
  92. package/src/bridge/messages/queryDataSourceResult.ts +4 -24
  93. package/src/bridge/messages/sandboxToHost.ts +4 -2
  94. package/src/bridge/sandboxClient.ts +3 -8
  95. package/src/customBlock.ts +2 -2
  96. package/src/host/createCustomBlockHost.ts +269 -64
  97. package/src/host/lifecycle/initErrors.ts +25 -7
  98. package/src/host/lifecycle/types.ts +13 -4
  99. package/src/host/messages/invalidSandboxMessage.ts +0 -3
  100. package/src/host/queries/querySubscriptions.ts +58 -39
  101. package/src/host/queries/types.ts +10 -11
  102. package/src/host.ts +13 -2
  103. package/src/index.ts +3 -1
  104. package/src/init.ts +73 -60
  105. package/src/react/index.ts +0 -1
  106. package/src/react/standalonePreview.ts +2 -0
  107. package/src/react/useCustomBlockInit.ts +6 -27
  108. package/src/react/useRuntimeState.ts +4 -3
  109. package/dist/bridge/messages/ready.d.ts.map +0 -1
@@ -5,17 +5,27 @@ import { initErrorForFailureReason } from "./lifecycle/initErrors.js";
5
5
  import { isValidBridgeProtocolVersion } from "./lifecycle/ready.js";
6
6
  import { getInvalidSandboxMessageResponse } from "./messages/invalidSandboxMessage.js";
7
7
  import { QuerySubscriptions } from "./queries/querySubscriptions.js";
8
- const DEFAULT_NO_READY_TIMEOUT_MS = 5000;
8
+ const DEFAULT_NO_CONNECT_TIMEOUT_MS = 5000;
9
+ const DEFAULT_NO_INIT_RESULT_TIMEOUT_MS = 5000;
9
10
  const DEFAULT_MIN_BRIDGE_PROTOCOL_VERSION = 2;
10
11
  export function createCustomBlockHost(options) {
11
- const { iframe, initialState, handlers, onReady, onLog, targetOrigin = "*", minBridgeProtocolVersion = DEFAULT_MIN_BRIDGE_PROTOCOL_VERSION, noReadyTimeoutMs = DEFAULT_NO_READY_TIMEOUT_MS, } = options;
12
+ const { iframe, initialState, handlers, onConnect, onInitResult, onLog, targetOrigin = "*", minBridgeProtocolVersion = DEFAULT_MIN_BRIDGE_PROTOCOL_VERSION, noConnectTimeoutMs = DEFAULT_NO_CONNECT_TIMEOUT_MS, noInitResultTimeoutMs = DEFAULT_NO_INIT_RESULT_TIMEOUT_MS, } = options;
12
13
  let theme = initialState.theme;
13
14
  let parent = initialState.parent;
14
15
  let page = initialState.page;
15
16
  let dataSources = initialState.dataSources;
17
+ let manifest = initialState.manifest;
16
18
  let currentUser = initialState.currentUser;
17
- let initStatus = "pending";
18
- let noReadyTimeoutId;
19
+ let initStatus = "waitingForConnect";
20
+ let noConnectTimeoutId;
21
+ let noInitResultTimeoutId;
22
+ let activeInitializationId;
23
+ let connectedSandbox;
24
+ let pendingThemeChange = false;
25
+ let pendingParentChange = false;
26
+ let pendingPageChange = false;
27
+ let pendingDataSourcesChange;
28
+ let pendingCurrentUserChange = false;
19
29
  const querySubscriptions = new QuerySubscriptions();
20
30
  function emit(direction, payload) {
21
31
  onLog?.(direction, payload);
@@ -25,71 +35,116 @@ export function createCustomBlockHost(options) {
25
35
  iframe.contentWindow?.postMessage(message, targetOrigin);
26
36
  }
27
37
  function postQueryDataSourceResult(args) {
28
- // TODO(custom-blocks): Update when bumping bridge protocol version to 3.
29
- // Protocol v2 sandboxes require the legacy optional-error result shape.
30
- const message = {
31
- type: "queryDataSourceResult",
32
- requestId: args.requestId,
33
- snapshotId: args.snapshotId,
34
- items: args.response.items,
35
- hasMore: args.response.hasMore ?? false,
36
- error: args.response.error,
37
- };
38
+ const message = args.response.status === "error"
39
+ ? {
40
+ type: "queryDataSourceResult",
41
+ subscriptionId: args.subscriptionId,
42
+ status: "error",
43
+ error: args.response.error,
44
+ }
45
+ : {
46
+ type: "queryDataSourceResult",
47
+ subscriptionId: args.subscriptionId,
48
+ status: "success",
49
+ items: args.response.items,
50
+ hasMore: args.response.hasMore,
51
+ };
38
52
  post(message);
39
53
  }
40
- function clearNoReadyTimeout() {
41
- if (noReadyTimeoutId !== undefined) {
42
- window.clearTimeout(noReadyTimeoutId);
43
- noReadyTimeoutId = undefined;
54
+ function clearNoConnectTimeout() {
55
+ if (noConnectTimeoutId !== undefined) {
56
+ window.clearTimeout(noConnectTimeoutId);
57
+ noConnectTimeoutId = undefined;
44
58
  }
45
59
  }
46
- function resolveDataSourcesPayload(ready) {
47
- return typeof dataSources === "function" ? dataSources(ready) : dataSources;
60
+ function clearNoInitResultTimeout() {
61
+ if (noInitResultTimeoutId !== undefined) {
62
+ window.clearTimeout(noInitResultTimeoutId);
63
+ noInitResultTimeoutId = undefined;
64
+ }
65
+ }
66
+ function resolveDataSourcesPayload(connect) {
67
+ return typeof dataSources === "function"
68
+ ? dataSources(connect)
69
+ : dataSources;
48
70
  }
49
- function sendInit(ready) {
50
- if (initStatus !== "pending") {
71
+ function sendInit(connect) {
72
+ if (initStatus !== "waitingForConnect") {
73
+ return false;
74
+ }
75
+ onConnect?.(connect);
76
+ manifest = connect.manifest ?? initialState.manifest;
77
+ const resolvedDataSources = resolveDataSourcesPayload(connect);
78
+ const bindingErrorCode = getBindingErrorCode(manifest, resolvedDataSources);
79
+ if (bindingErrorCode !== undefined) {
80
+ sendInitError(connect.initializationId, initErrorForFailureReason(bindingErrorCode));
51
81
  return false;
52
82
  }
53
- initStatus = "success";
54
- clearNoReadyTimeout();
83
+ initStatus = "waitingForInitResult";
84
+ activeInitializationId = connect.initializationId;
85
+ clearNoConnectTimeout();
55
86
  const message = {
56
87
  type: "init",
88
+ initializationId: connect.initializationId,
57
89
  status: "success",
58
90
  theme,
59
91
  blockId: initialState.blockId,
60
92
  parent,
61
93
  page,
62
- dataSources: resolveDataSourcesPayload(ready),
94
+ manifest,
95
+ dataSources: resolvedDataSources,
63
96
  currentUser,
64
97
  };
65
98
  post(message);
99
+ noInitResultTimeoutId = window.setTimeout(() => {
100
+ if (initStatus !== "waitingForInitResult") {
101
+ return;
102
+ }
103
+ initStatus = "error";
104
+ emit("warn", {
105
+ message: "Sandbox did not send initResult",
106
+ initializationId: connect.initializationId,
107
+ });
108
+ }, noInitResultTimeoutMs);
66
109
  return true;
67
110
  }
68
- function sendInitError(error) {
69
- if (initStatus !== "pending") {
111
+ function sendInitError(initializationId, error) {
112
+ if (initStatus !== "waitingForConnect") {
70
113
  return;
71
114
  }
72
115
  initStatus = "error";
73
- clearNoReadyTimeout();
116
+ activeInitializationId = initializationId;
117
+ clearNoConnectTimeout();
74
118
  post({
75
119
  type: "init",
120
+ initializationId,
76
121
  status: "error",
77
122
  error,
78
123
  });
79
124
  }
80
125
  function onIframeLoad() {
81
- clearNoReadyTimeout();
82
- noReadyTimeoutId = window.setTimeout(() => {
83
- const initError = initErrorForFailureReason("no_ready");
84
- sendInitError(initError);
85
- }, noReadyTimeoutMs);
126
+ clearNoConnectTimeout();
127
+ if (initStatus !== "waitingForConnect") {
128
+ // Only set the timeout if the iframe loaded before the `connect` message was sent.
129
+ return;
130
+ }
131
+ noConnectTimeoutId = window.setTimeout(() => {
132
+ if (initStatus !== "waitingForConnect") {
133
+ // Only do anything if the iframe loaded before the `connect` message was sent.
134
+ return;
135
+ }
136
+ initStatus = "error";
137
+ emit("warn", initErrorForFailureReason("no_connect"));
138
+ }, noConnectTimeoutMs);
86
139
  }
87
140
  async function handleQuery(message) {
88
- querySubscriptions.track(message);
141
+ const token = querySubscriptions.track(message);
89
142
  const response = await handlers.queryDataSource(message);
143
+ if (!querySubscriptions.isCurrent(message.subscriptionId, token)) {
144
+ return;
145
+ }
90
146
  postQueryDataSourceResult({
91
- requestId: message.requestId,
92
- snapshotId: message.snapshotId,
147
+ subscriptionId: message.subscriptionId,
93
148
  response,
94
149
  });
95
150
  }
@@ -153,13 +208,13 @@ export function createCustomBlockHost(options) {
153
208
  };
154
209
  post(result);
155
210
  }
156
- function handleReady(message) {
211
+ function handleConnect(message) {
157
212
  // Verify the bridge protocol version is valid.
158
213
  if (!isValidBridgeProtocolVersion(message.bridgeProtocolVersion)) {
159
214
  const initError = initErrorForFailureReason("invalid_protocol_version", {
160
215
  currentBridgeProtocolVersion: message.bridgeProtocolVersion,
161
216
  });
162
- sendInitError(initError);
217
+ sendInitError(message.initializationId, initError);
163
218
  return;
164
219
  }
165
220
  if (message.bridgeProtocolVersion < minBridgeProtocolVersion) {
@@ -167,17 +222,62 @@ export function createCustomBlockHost(options) {
167
222
  currentBridgeProtocolVersion: message.bridgeProtocolVersion,
168
223
  minBridgeProtocolVersion,
169
224
  });
170
- sendInitError(initError);
225
+ sendInitError(message.initializationId, initError);
171
226
  return;
172
227
  }
173
- // If the sandbox reported an error during `ready`, return it as an `init` error.
228
+ // If the sandbox reported an error during `connect`, return it as an `init` error.
174
229
  if (message.status === "error") {
175
- sendInitError(message.error);
230
+ sendInitError(message.initializationId, message.error);
231
+ return;
232
+ }
233
+ if (!sendInit(message)) {
234
+ return;
235
+ }
236
+ connectedSandbox = message;
237
+ }
238
+ /**
239
+ * Sends any pending host state changes to the sandbox. It is possible for these to change
240
+ * between when the host sends `init` and when the sandbox responds with `initResult`. The host
241
+ * does send any pending state changes until hearing back from the sandbox.
242
+ */
243
+ function flushPendingHostStateChanges() {
244
+ if (pendingThemeChange) {
245
+ pendingThemeChange = false;
246
+ post({ type: "themeChanged", theme });
247
+ }
248
+ if (pendingParentChange) {
249
+ pendingParentChange = false;
250
+ post({ type: "parentChanged", parent });
251
+ }
252
+ if (pendingPageChange) {
253
+ pendingPageChange = false;
254
+ post({ type: "pageChanged", page });
255
+ }
256
+ if (pendingDataSourcesChange !== undefined) {
257
+ const nextDataSources = pendingDataSourcesChange;
258
+ pendingDataSourcesChange = undefined;
259
+ post({ type: "dataSourcesChanged", dataSources: nextDataSources });
260
+ }
261
+ if (pendingCurrentUserChange) {
262
+ pendingCurrentUserChange = false;
263
+ post({ type: "currentUserChanged", currentUser });
264
+ }
265
+ }
266
+ function handleInitResult(message) {
267
+ if (initStatus !== "waitingForInitResult" ||
268
+ message.initializationId !== activeInitializationId) {
269
+ emit("warn", {
270
+ message: "Ignored stale or unexpected initResult",
271
+ payload: message,
272
+ });
176
273
  return;
177
274
  }
178
- if (sendInit(message)) {
179
- onReady?.(message);
275
+ clearNoInitResultTimeout();
276
+ initStatus = message.status === "success" ? "success" : "error";
277
+ if (initStatus === "success") {
278
+ flushPendingHostStateChanges();
180
279
  }
280
+ onInitResult?.(message);
181
281
  }
182
282
  function onInvalidSandboxMessage(data, issues) {
183
283
  emit("warn", {
@@ -186,11 +286,7 @@ export function createCustomBlockHost(options) {
186
286
  issues,
187
287
  });
188
288
  const response = getInvalidSandboxMessageResponse(data);
189
- if (initStatus === "pending" && response.incomingType === "ready") {
190
- const initError = initErrorForFailureReason("invalid_ready");
191
- sendInitError(initError);
192
- }
193
- else if (response.nack !== undefined) {
289
+ if (response.nack !== undefined) {
194
290
  post(response.nack);
195
291
  }
196
292
  }
@@ -205,9 +301,21 @@ export function createCustomBlockHost(options) {
205
301
  }
206
302
  const message = parsed.output;
207
303
  emit("in", message);
304
+ if (message.type !== "connect" &&
305
+ message.type !== "initResult" &&
306
+ initStatus !== "success") {
307
+ emit("warn", {
308
+ message: `Ignored ${message.type} before initialization completed`,
309
+ payload: message,
310
+ });
311
+ return;
312
+ }
208
313
  switch (message.type) {
209
- case "ready":
210
- handleReady(message);
314
+ case "connect":
315
+ handleConnect(message);
316
+ return;
317
+ case "initResult":
318
+ handleInitResult(message);
211
319
  return;
212
320
  case "queryDataSource":
213
321
  void handleQuery(message);
@@ -242,26 +350,60 @@ export function createCustomBlockHost(options) {
242
350
  stop() {
243
351
  window.removeEventListener("message", onMessage);
244
352
  iframe.removeEventListener("load", onIframeLoad);
245
- clearNoReadyTimeout();
353
+ clearNoConnectTimeout();
354
+ clearNoInitResultTimeout();
355
+ querySubscriptions.clear();
246
356
  },
247
357
  post,
248
358
  setTheme(nextTheme) {
249
359
  theme = nextTheme;
360
+ if (initStatus !== "success") {
361
+ if (initStatus === "waitingForInitResult") {
362
+ pendingThemeChange = true;
363
+ }
364
+ return;
365
+ }
250
366
  const message = { type: "themeChanged", theme };
251
367
  post(message);
252
368
  },
253
369
  setParent(nextParent) {
254
370
  parent = nextParent;
371
+ if (initStatus !== "success") {
372
+ if (initStatus === "waitingForInitResult") {
373
+ pendingParentChange = true;
374
+ }
375
+ return;
376
+ }
255
377
  const message = { type: "parentChanged", parent };
256
378
  post(message);
257
379
  },
258
380
  setPage(nextPage) {
259
381
  page = nextPage;
382
+ if (initStatus !== "success") {
383
+ if (initStatus === "waitingForInitResult") {
384
+ pendingPageChange = true;
385
+ }
386
+ return;
387
+ }
260
388
  const message = { type: "pageChanged", page };
261
389
  post(message);
262
390
  },
263
391
  setDataSources(nextDataSources) {
392
+ if (connectedSandbox !== undefined &&
393
+ getBindingErrorCode(manifest, nextDataSources) !== undefined) {
394
+ emit("warn", {
395
+ message: "Ignored invalid data source bindings update",
396
+ payload: nextDataSources,
397
+ });
398
+ return;
399
+ }
264
400
  dataSources = nextDataSources;
401
+ if (initStatus !== "success") {
402
+ if (initStatus === "waitingForInitResult") {
403
+ pendingDataSourcesChange = nextDataSources;
404
+ }
405
+ return;
406
+ }
265
407
  const message = {
266
408
  type: "dataSourcesChanged",
267
409
  dataSources: nextDataSources,
@@ -270,22 +412,49 @@ export function createCustomBlockHost(options) {
270
412
  },
271
413
  setCurrentUser(nextCurrentUser) {
272
414
  currentUser = nextCurrentUser;
415
+ if (initStatus !== "success") {
416
+ if (initStatus === "waitingForInitResult") {
417
+ pendingCurrentUserChange = true;
418
+ }
419
+ return;
420
+ }
273
421
  post({ type: "currentUserChanged", currentUser });
274
422
  },
275
423
  refreshQuery({ dataSourceId, response }) {
276
- const refresh = querySubscriptions.resolveRefresh({
424
+ if (initStatus !== "success") {
425
+ return;
426
+ }
427
+ const refreshes = querySubscriptions.resolveRefreshes({
277
428
  dataSourceId,
278
429
  response,
279
430
  });
280
- if (refresh.status === "missing") {
431
+ if (refreshes.length === 0) {
281
432
  emit("warn", `No active query exists for data source "${dataSourceId}"`);
282
433
  return;
283
434
  }
284
- postQueryDataSourceResult({
285
- requestId: refresh.requestId,
286
- snapshotId: refresh.snapshotId,
287
- response: refresh.response,
288
- });
435
+ for (const refresh of refreshes) {
436
+ postQueryDataSourceResult(refresh);
437
+ }
289
438
  },
290
439
  };
291
440
  }
441
+ function getBindingErrorCode(manifest, dataSources) {
442
+ for (const [dataSourceKey, manifestDataSource] of Object.entries(manifest.dataSources)) {
443
+ const binding = dataSources.bindings[dataSourceKey];
444
+ if (binding?.collectionPointer === undefined ||
445
+ binding.collectionSchema === undefined) {
446
+ return "missing_data_source_binding";
447
+ }
448
+ for (const [propertyKey, manifestProperty] of Object.entries(manifestDataSource.properties ?? {})) {
449
+ const propertyId = binding.propertyIdsByKey?.[propertyKey];
450
+ if (propertyId === undefined) {
451
+ return "missing_property_binding";
452
+ }
453
+ if (binding.collectionSchema.propertiesById[propertyId]?.type !==
454
+ manifestProperty.type) {
455
+ return "invalid_property_binding";
456
+ }
457
+ }
458
+ }
459
+ return undefined;
460
+ }
@@ -3,7 +3,7 @@ import type { CustomBlockInitErrorCode, CustomBlockInitErrorInfo } from "../../b
3
3
  * Returns the error info for a given `init` failure code.
4
4
  *
5
5
  * `init` handshakes are one-shot: after an `init` error, this host instance will not answer another
6
- * `ready` message. `isRetryable` means a fresh iframe / `init` attempt could plausibly succeed
6
+ * `connect` message. `isRetryable` means a fresh iframe / `init` attempt could plausibly succeed
7
7
  * without changing block code or host configuration.
8
8
  */
9
9
  export declare function initErrorForFailureReason(reason: CustomBlockInitErrorCode, data?: {
@@ -1 +1 @@
1
- {"version":3,"file":"initErrors.d.ts","sourceRoot":"","sources":["../../../../../home/runner/work/custom-blocks/custom-blocks/sdk/src/host/lifecycle/initErrors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACX,wBAAwB,EACxB,wBAAwB,EACxB,MAAM,+BAA+B,CAAA;AAKtC;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CACxC,MAAM,EAAE,wBAAwB,EAChC,IAAI,GAAE;IACL,wBAAwB,CAAC,EAAE,MAAM,CAAA;IACjC,4BAA4B,CAAC,EAAE,MAAM,CAAA;CAChC,GACJ,wBAAwB,CA6H1B"}
1
+ {"version":3,"file":"initErrors.d.ts","sourceRoot":"","sources":["../../../../../home/runner/work/custom-blocks/custom-blocks/sdk/src/host/lifecycle/initErrors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACX,wBAAwB,EACxB,wBAAwB,EACxB,MAAM,+BAA+B,CAAA;AAKtC;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CACxC,MAAM,EAAE,wBAAwB,EAChC,IAAI,GAAE;IACL,wBAAwB,CAAC,EAAE,MAAM,CAAA;IACjC,4BAA4B,CAAC,EAAE,MAAM,CAAA;CAChC,GACJ,wBAAwB,CA+I1B"}
@@ -3,28 +3,28 @@ const UNKNOWN_ERROR_MESSAGE = "Unexpected error while initializing custom block.
3
3
  * Returns the error info for a given `init` failure code.
4
4
  *
5
5
  * `init` handshakes are one-shot: after an `init` error, this host instance will not answer another
6
- * `ready` message. `isRetryable` means a fresh iframe / `init` attempt could plausibly succeed
6
+ * `connect` message. `isRetryable` means a fresh iframe / `init` attempt could plausibly succeed
7
7
  * without changing block code or host configuration.
8
8
  */
9
9
  export function initErrorForFailureReason(reason, data = {}) {
10
10
  switch (reason) {
11
- case "no_ready":
11
+ case "no_connect":
12
12
  return {
13
13
  code: reason,
14
- message: "Sandbox did not send a ready message.",
14
+ message: "Sandbox did not send a connect message.",
15
15
  isRetryable: true,
16
16
  };
17
- case "invalid_ready":
17
+ case "invalid_connect":
18
18
  return {
19
19
  code: reason,
20
- message: "Sandbox sent an invalid ready message.",
21
- // The sandbox must be updated to send a valid ready message.
20
+ message: "Sandbox sent an invalid connect message.",
21
+ // The sandbox must be updated to send a valid connect message.
22
22
  isRetryable: false,
23
23
  };
24
24
  case "manifest_unavailable":
25
25
  return {
26
26
  code: reason,
27
- message: "Sandbox could not load its manifest.",
27
+ message: "The custom block manifest is unavailable.",
28
28
  isRetryable: true,
29
29
  };
30
30
  case "manifest_invalid":
@@ -102,6 +102,24 @@ export function initErrorForFailureReason(reason, data = {}) {
102
102
  // a property with a compatible schema.
103
103
  isRetryable: false,
104
104
  };
105
+ case "no_init_result":
106
+ return {
107
+ code: reason,
108
+ message: "Sandbox did not acknowledge initialization.",
109
+ isRetryable: true,
110
+ };
111
+ case "invalid_init_result":
112
+ return {
113
+ code: reason,
114
+ message: "Sandbox sent an invalid initialization result.",
115
+ isRetryable: false,
116
+ };
117
+ case "invalid_init_bindings":
118
+ return {
119
+ code: reason,
120
+ message: "Sandbox rejected the host-provided data source bindings.",
121
+ isRetryable: false,
122
+ };
105
123
  case "not_in_iframe":
106
124
  return {
107
125
  code: reason,
@@ -1,23 +1,25 @@
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
- export type ReadySuccessMessage = Extract<ReadyMessage, {
9
+ export type ConnectSuccessMessage = Extract<ConnectMessage, {
9
10
  status: "success";
10
11
  }>;
11
- export type InitStatus = "pending" | "success" | "error";
12
+ export type InitStatus = "waitingForConnect" | "waitingForInitResult" | "success" | "error";
12
13
  export type CustomBlockHostDataSourcesPayload = {
13
14
  bindings: NotionDataSourceBindings;
14
15
  };
15
- export type CustomBlockHostInitialDataSources = CustomBlockHostDataSourcesPayload | ((ready: ReadySuccessMessage) => CustomBlockHostDataSourcesPayload);
16
+ export type CustomBlockHostInitialDataSources = CustomBlockHostDataSourcesPayload | ((connect: ConnectSuccessMessage) => CustomBlockHostDataSourcesPayload);
16
17
  export type CustomBlockHostInitialState = {
17
18
  theme: NotionTheme;
18
19
  blockId: NotionBlockId;
19
20
  parent: NotionParent;
20
21
  page: CustomBlockPage;
22
+ manifest: CustomBlockManifest;
21
23
  dataSources: CustomBlockHostInitialDataSources;
22
24
  currentUser: NotionUser;
23
25
  };
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../../home/runner/work/custom-blocks/custom-blocks/sdk/src/host/lifecycle/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,wCAAwC,CAAA;AACtF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AACxD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAA;AAClE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAA;AACjE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAA;AAC1D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AACxD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAA;AAE5D,MAAM,MAAM,mBAAmB,GAAG,OAAO,CAAC,YAAY,EAAE;IAAE,MAAM,EAAE,SAAS,CAAA;CAAE,CAAC,CAAA;AAE9E,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,CAAA;AAExD,MAAM,MAAM,iCAAiC,GAAG;IAC/C,QAAQ,EAAE,wBAAwB,CAAA;CAClC,CAAA;AAED,MAAM,MAAM,iCAAiC,GAC1C,iCAAiC,GACjC,CAAC,CAAC,KAAK,EAAE,mBAAmB,KAAK,iCAAiC,CAAC,CAAA;AAEtE,MAAM,MAAM,2BAA2B,GAAG;IACzC,KAAK,EAAE,WAAW,CAAA;IAClB,OAAO,EAAE,aAAa,CAAA;IACtB,MAAM,EAAE,YAAY,CAAA;IACpB,IAAI,EAAE,eAAe,CAAA;IACrB,WAAW,EAAE,iCAAiC,CAAA;IAC9C,WAAW,EAAE,UAAU,CAAA;CACvB,CAAA"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../../home/runner/work/custom-blocks/custom-blocks/sdk/src/host/lifecycle/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,wCAAwC,CAAA;AACtF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AACxD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAA;AACnE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kCAAkC,CAAA;AACtE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAA;AACjE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAA;AAC1D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AACxD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAA;AAE5D,MAAM,MAAM,qBAAqB,GAAG,OAAO,CAC1C,cAAc,EACd;IAAE,MAAM,EAAE,SAAS,CAAA;CAAE,CACrB,CAAA;AAED,MAAM,MAAM,UAAU,GACnB,mBAAmB,GACnB,sBAAsB,GACtB,SAAS,GACT,OAAO,CAAA;AAEV,MAAM,MAAM,iCAAiC,GAAG;IAC/C,QAAQ,EAAE,wBAAwB,CAAA;CAClC,CAAA;AAED,MAAM,MAAM,iCAAiC,GAC1C,iCAAiC,GACjC,CAAC,CAAC,OAAO,EAAE,qBAAqB,KAAK,iCAAiC,CAAC,CAAA;AAE1E,MAAM,MAAM,2BAA2B,GAAG;IACzC,KAAK,EAAE,WAAW,CAAA;IAClB,OAAO,EAAE,aAAa,CAAA;IACtB,MAAM,EAAE,YAAY,CAAA;IACpB,IAAI,EAAE,eAAe,CAAA;IACrB,QAAQ,EAAE,mBAAmB,CAAA;IAC7B,WAAW,EAAE,iCAAiC,CAAA;IAC9C,WAAW,EAAE,UAAU,CAAA;CACvB,CAAA"}
@@ -14,9 +14,6 @@ export function getInvalidSandboxMessageResponse(data) {
14
14
  };
15
15
  }
16
16
  function nackReasonForInvalidSandboxMessage(incomingType) {
17
- if (incomingType === "ready") {
18
- return "Invalid manifest";
19
- }
20
17
  if (incomingType !== undefined) {
21
18
  return `Unsupported sandbox message of type "${incomingType}"`;
22
19
  }
@@ -1,20 +1,20 @@
1
1
  import type { NotionDataSourceId } from "../../bridge/ids.js";
2
2
  import type { QueryDataSourceMessage } from "../../bridge/messages/queryDataSource.js";
3
3
  import type { CustomBlockHostQueryDataSourceResult } from "./types.js";
4
- export type QueryRefreshResult = {
5
- status: "success";
6
- requestId: string;
7
- snapshotId: string;
4
+ export type QueryRefresh = {
5
+ subscriptionId: string;
8
6
  response: CustomBlockHostQueryDataSourceResult;
9
- } | {
10
- status: "missing";
11
7
  };
8
+ type QuerySubscriptionToken = object;
12
9
  export declare class QuerySubscriptions {
13
- private readonly latestByDataSourceId;
14
- track(message: QueryDataSourceMessage): void;
15
- resolveRefresh(args: {
10
+ private readonly bySubscriptionId;
11
+ track(message: QueryDataSourceMessage): QuerySubscriptionToken;
12
+ isCurrent(subscriptionId: string, token: QuerySubscriptionToken): boolean;
13
+ resolveRefreshes(args: {
16
14
  dataSourceId: NotionDataSourceId;
17
15
  response: CustomBlockHostQueryDataSourceResult;
18
- }): QueryRefreshResult;
16
+ }): QueryRefresh[];
17
+ clear(): void;
19
18
  }
19
+ export {};
20
20
  //# sourceMappingURL=querySubscriptions.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"querySubscriptions.d.ts","sourceRoot":"","sources":["../../../../../home/runner/work/custom-blocks/custom-blocks/sdk/src/host/queries/querySubscriptions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAA;AAC7D,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,0CAA0C,CAAA;AACtF,OAAO,KAAK,EACX,oCAAoC,EAEpC,MAAM,YAAY,CAAA;AAEnB,MAAM,MAAM,kBAAkB,GAC3B;IACA,MAAM,EAAE,SAAS,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAE,oCAAoC,CAAA;CAC7C,GACD;IAAE,MAAM,EAAE,SAAS,CAAA;CAAE,CAAA;AAExB,qBAAa,kBAAkB;IAC9B,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAGlC;IAEH,KAAK,CAAC,OAAO,EAAE,sBAAsB;IAQrC,cAAc,CAAC,IAAI,EAAE;QACpB,YAAY,EAAE,kBAAkB,CAAA;QAChC,QAAQ,EAAE,oCAAoC,CAAA;KAC9C,GAAG,kBAAkB;CAkBtB"}
1
+ {"version":3,"file":"querySubscriptions.d.ts","sourceRoot":"","sources":["../../../../../home/runner/work/custom-blocks/custom-blocks/sdk/src/host/queries/querySubscriptions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAA;AAC7D,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,0CAA0C,CAAA;AACtF,OAAO,KAAK,EAAE,oCAAoC,EAAE,MAAM,YAAY,CAAA;AAEtE,MAAM,MAAM,YAAY,GAAG;IAC1B,cAAc,EAAE,MAAM,CAAA;IACtB,QAAQ,EAAE,oCAAoC,CAAA;CAC9C,CAAA;AAED,KAAK,sBAAsB,GAAG,MAAM,CAAA;AAQpC,qBAAa,kBAAkB;IAC9B,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAA4C;IAE7E,KAAK,CAAC,OAAO,EAAE,sBAAsB,GAAG,sBAAsB;IAU9D,SAAS,CAAC,cAAc,EAAE,MAAM,EAAE,KAAK,EAAE,sBAAsB,GAAG,OAAO;IAIzE,gBAAgB,CAAC,IAAI,EAAE;QACtB,YAAY,EAAE,kBAAkB,CAAA;QAChC,QAAQ,EAAE,oCAAoC,CAAA;KAC9C,GAAG,YAAY,EAAE;IAelB,KAAK;CAGL"}
@@ -1,29 +1,45 @@
1
1
  export class QuerySubscriptions {
2
2
  constructor() {
3
- this.latestByDataSourceId = new Map();
3
+ this.bySubscriptionId = new Map();
4
4
  }
5
5
  track(message) {
6
- this.latestByDataSourceId.set(message.dataSourceId, {
7
- requestId: message.requestId,
8
- snapshotId: message.snapshotId,
6
+ const token = {};
7
+ this.bySubscriptionId.set(message.subscriptionId, {
8
+ dataSourceId: message.dataSourceId,
9
9
  limit: message.limit,
10
+ token,
10
11
  });
12
+ return token;
11
13
  }
12
- resolveRefresh(args) {
13
- const queryState = this.latestByDataSourceId.get(args.dataSourceId);
14
- if (!queryState) {
15
- return { status: "missing" };
14
+ isCurrent(subscriptionId, token) {
15
+ return this.bySubscriptionId.get(subscriptionId)?.token === token;
16
+ }
17
+ resolveRefreshes(args) {
18
+ const refreshes = [];
19
+ for (const [subscriptionId, queryState] of this.bySubscriptionId) {
20
+ if (queryState.dataSourceId !== args.dataSourceId) {
21
+ continue;
22
+ }
23
+ queryState.token = {};
24
+ refreshes.push({
25
+ subscriptionId,
26
+ response: limitResponse(args.response, queryState.limit),
27
+ });
16
28
  }
17
- const items = args.response.items.slice(0, queryState.limit);
18
- return {
19
- status: "success",
20
- requestId: queryState.requestId,
21
- snapshotId: queryState.snapshotId,
22
- response: {
23
- items,
24
- hasMore: args.response.hasMore ?? args.response.items.length > items.length,
25
- error: args.response.error,
26
- },
27
- };
29
+ return refreshes;
30
+ }
31
+ clear() {
32
+ this.bySubscriptionId.clear();
33
+ }
34
+ }
35
+ function limitResponse(response, limit) {
36
+ if (response.status === "error") {
37
+ return response;
28
38
  }
39
+ const items = response.items.slice(0, limit);
40
+ return {
41
+ status: "success",
42
+ items,
43
+ hasMore: response.hasMore || response.items.length > items.length,
44
+ };
29
45
  }