@notionhq/custom-blocks-host 0.5.1 → 0.6.0

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 (76) hide show
  1. package/README.md +9 -0
  2. package/dist/browser.js +1 -1
  3. package/dist/compatibility/normalizeHostToSandboxMessage.d.ts.map +1 -1
  4. package/dist/compatibility/normalizeHostToSandboxMessage.js +5 -1
  5. package/dist/compatibility/normalizeSandboxToHostMessage.d.ts +88 -1
  6. package/dist/compatibility/normalizeSandboxToHostMessage.d.ts.map +1 -1
  7. package/dist/compatibility/pages/toIconForProtocol3To8.d.ts +4 -0
  8. package/dist/compatibility/pages/toIconForProtocol3To8.d.ts.map +1 -0
  9. package/dist/compatibility/pages/toIconForProtocol3To8.js +13 -0
  10. package/dist/compatibility/protocol3To5/toPageForProtocol3To5.d.ts.map +1 -1
  11. package/dist/compatibility/protocol3To5/toPageForProtocol3To5.js +2 -1
  12. package/dist/compatibility/protocol3To5/toQueryPageForProtocol3To5.d.ts +8 -8
  13. package/dist/compatibility/protocol6/normalizePageResultsForProtocol6.d.ts.map +1 -1
  14. package/dist/compatibility/protocol6/normalizePageResultsForProtocol6.js +9 -2
  15. package/dist/compatibility/protocol7To8/normalizePageResultsForProtocol7To8.d.ts +4 -0
  16. package/dist/compatibility/protocol7To8/normalizePageResultsForProtocol7To8.d.ts.map +1 -0
  17. package/dist/compatibility/protocol7To8/normalizePageResultsForProtocol7To8.js +16 -0
  18. package/dist/createCustomBlockHost.d.ts.map +1 -1
  19. package/dist/createCustomBlockHost.js +50 -34
  20. package/dist/index.d.ts +1 -1
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/messages/createSandboxMessageRouter.d.ts +3 -2
  23. package/dist/messages/createSandboxMessageRouter.d.ts.map +1 -1
  24. package/dist/messages/createSandboxMessageRouter.js +7 -1
  25. package/dist/messages/getInvalidSandboxMessageResponse.d.ts.map +1 -1
  26. package/dist/messages/getInvalidSandboxMessageResponse.js +3 -0
  27. package/dist/messages/invalidNativeIconColor.d.ts +5 -0
  28. package/dist/messages/invalidNativeIconColor.d.ts.map +1 -0
  29. package/dist/messages/invalidNativeIconColor.js +34 -0
  30. package/dist/pages/types.d.ts +2 -0
  31. package/dist/pages/types.d.ts.map +1 -1
  32. package/dist/protocol/compatibility/protocol3To5/bridgePage.d.ts +7 -1
  33. package/dist/protocol/dataSources/messages/queryDataSource.d.ts +14 -2
  34. package/dist/protocol/icons.d.ts +17 -0
  35. package/dist/protocol/icons.js +27 -0
  36. package/dist/protocol/index.d.ts +5 -3
  37. package/dist/protocol/index.js +4 -2
  38. package/dist/protocol/messages/hostToSandbox.d.ts +79 -11
  39. package/dist/protocol/messages/invalidHostMessage.d.ts +2 -0
  40. package/dist/protocol/messages/invalidHostMessage.js +2 -0
  41. package/dist/protocol/messages/invalidSandboxMessage.d.ts +2 -0
  42. package/dist/protocol/messages/invalidSandboxMessage.js +2 -0
  43. package/dist/protocol/messages/sandboxToHost.d.ts +88 -1
  44. package/dist/protocol/navigation/openExternalUrl.d.ts +2 -2
  45. package/dist/protocol/pages/bridgePage.d.ts +7 -1
  46. package/dist/protocol/pages/messages/createPage.d.ts +103 -5
  47. package/dist/protocol/pages/messages/createPage.js +3 -1
  48. package/dist/protocol/pages/messages/getPage.d.ts +23 -5
  49. package/dist/protocol/pages/messages/openPage.d.ts +2 -2
  50. package/dist/protocol/pages/messages/updatePage.d.ts +30 -6
  51. package/dist/protocol/pages/pageAppearance.d.ts +36 -1
  52. package/dist/protocol/pages/pageAppearance.js +9 -0
  53. package/dist/protocol/users/messages/getUser.d.ts +2 -2
  54. package/dist/protocol/users/messages/listUsers.d.ts +2 -2
  55. package/dist/protocol/utils/errors.d.ts +2 -0
  56. package/dist/protocol/utils/protocolVersion.d.ts +2 -1
  57. package/dist/protocol/utils/protocolVersion.js +3 -1
  58. package/dist/protocol/utils/readRequestId.d.ts +2 -0
  59. package/dist/protocol/utils/readRequestId.js +7 -0
  60. package/docs/bridge/changelog.md +19 -0
  61. package/docs/bridge/protocol.md +30 -1
  62. package/docs/bridge/versioning.md +1 -0
  63. package/docs/compatibility.md +15 -7
  64. package/docs/handlers.md +6 -3
  65. package/package.json +1 -1
  66. package/src/compatibility/normalizeHostToSandboxMessage.ts +5 -0
  67. package/src/compatibility/pages/toIconForProtocol3To8.ts +17 -0
  68. package/src/compatibility/protocol3To5/toPageForProtocol3To5.ts +2 -1
  69. package/src/compatibility/protocol6/normalizePageResultsForProtocol6.ts +11 -2
  70. package/src/compatibility/protocol7To8/normalizePageResultsForProtocol7To8.ts +22 -0
  71. package/src/createCustomBlockHost.ts +57 -37
  72. package/src/index.ts +1 -0
  73. package/src/messages/createSandboxMessageRouter.ts +18 -2
  74. package/src/messages/getInvalidSandboxMessageResponse.ts +3 -0
  75. package/src/messages/invalidNativeIconColor.ts +43 -0
  76. package/src/pages/types.ts +3 -0
@@ -148,6 +148,8 @@ export type CustomBlockHostHandle = {
148
148
  */
149
149
  type OneShotRequest = Extract<SandboxToHostMessage, { requestId: string }>
150
150
 
151
+ type RequestError = { status: "error"; error: CustomBlockErrorInfo }
152
+
151
153
  export function createCustomBlockHost(
152
154
  options: CustomBlockHostOptions,
153
155
  ): CustomBlockHostHandle {
@@ -247,22 +249,39 @@ export function createCustomBlockHost(
247
249
  * Invokes a one-shot handler and posts its result with the request's ID.
248
250
  * Each result type is named `<request type>Result` by the bridge protocol.
249
251
  */
250
- async function respondToRequest<
252
+ async function handleRequest<
251
253
  Request extends OneShotRequest,
252
254
  Response extends object,
253
255
  >(
254
256
  message: Request,
255
257
  handler: ((message: Request) => Response | Promise<Response>) | undefined,
256
258
  ) {
257
- if (handler === undefined) {
258
- return
259
+ try {
260
+ const response =
261
+ handler === undefined
262
+ ? unsupportedOperation(message.type)
263
+ : await handler(message)
264
+ sendInitializedMessage({
265
+ ...response,
266
+ type: `${message.type}Result`,
267
+ requestId: message.requestId,
268
+ })
269
+ } catch (error) {
270
+ if (initializationController.status !== "initialized") {
271
+ return
272
+ }
273
+ emit("warn", error)
274
+ sendInitializedMessage({
275
+ type: `${message.type}Result`,
276
+ requestId: message.requestId,
277
+ status: "error",
278
+ error: {
279
+ code: "unknown_error",
280
+ message: `The host could not complete ${message.type}.`,
281
+ isRetryable: false,
282
+ },
283
+ })
259
284
  }
260
- const response = await handler(message)
261
- sendInitializedMessage({
262
- type: `${message.type}Result` as const,
263
- requestId: message.requestId,
264
- ...response,
265
- })
266
285
  }
267
286
 
268
287
  function postQueryDataSourceResult(args: {
@@ -338,10 +357,12 @@ export function createCustomBlockHost(
338
357
  }
339
358
 
340
359
  async function handleOpenExternalUrl(message: OpenExternalUrlMessage) {
341
- if (
342
- handlers.openExternalUrl === undefined ||
343
- navigationAbortController.signal.aborted
344
- ) {
360
+ if (navigationAbortController.signal.aborted) {
361
+ return
362
+ }
363
+ const handler = handlers.openExternalUrl
364
+ if (handler === undefined) {
365
+ await handleRequest(message, undefined)
345
366
  return
346
367
  }
347
368
  if (pendingOpenExternalUrlRequestId === message.requestId) {
@@ -380,25 +401,12 @@ export function createCustomBlockHost(
380
401
 
381
402
  pendingOpenExternalUrlRequestId = message.requestId
382
403
  try {
383
- const response = await handlers.openExternalUrl(
384
- { ...message, url },
385
- {
386
- signal: navigationAbortController.signal,
387
- },
404
+ await handleRequest(message, () =>
405
+ handler(
406
+ { ...message, url },
407
+ { signal: navigationAbortController.signal },
408
+ ),
388
409
  )
389
- if (navigationAbortController.signal.aborted) {
390
- return
391
- }
392
- const result: OpenExternalUrlResultMessage = {
393
- type: "openExternalUrlResult",
394
- requestId: message.requestId,
395
- ...response,
396
- }
397
- sendInitializedMessage(result)
398
- } catch (error) {
399
- if (!navigationAbortController.signal.aborted) {
400
- throw error
401
- }
402
410
  } finally {
403
411
  if (pendingOpenExternalUrlRequestId === message.requestId) {
404
412
  pendingOpenExternalUrlRequestId = undefined
@@ -595,6 +603,7 @@ export function createCustomBlockHost(
595
603
  getInitStatus: () => initializationController.status,
596
604
  emit,
597
605
  sendNack,
606
+ sendPageMutationError: postToSandbox,
598
607
  onInvalidConnect: initializationController.handleInvalidConnect,
599
608
  onInvalidInitResult: initializationController.handleInvalidInitResult,
600
609
  onConnect: initializationController.handleConnect,
@@ -603,12 +612,12 @@ export function createCustomBlockHost(
603
612
  onUnsubscribeDataSourceQuery: message =>
604
613
  querySubscriptions.unsubscribe(message.subscriptionId),
605
614
  onOpenExternalUrl: handleOpenExternalUrl,
606
- onCreatePage: message => respondToRequest(message, handlers.createPage),
607
- onGetPage: message => respondToRequest(message, handlers.getPage),
608
- onOpenPage: message => respondToRequest(message, handlers.openPage),
609
- onGetUser: message => respondToRequest(message, handlers.getUser),
610
- onListUsers: message => respondToRequest(message, handlers.listUsers),
611
- onUpdatePage: message => respondToRequest(message, handlers.updatePage),
615
+ onCreatePage: message => handleRequest(message, handlers.createPage),
616
+ onGetPage: message => handleRequest(message, handlers.getPage),
617
+ onOpenPage: message => handleRequest(message, handlers.openPage),
618
+ onGetUser: message => handleRequest(message, handlers.getUser),
619
+ onListUsers: message => handleRequest(message, handlers.listUsers),
620
+ onUpdatePage: message => handleRequest(message, handlers.updatePage),
612
621
  onResize: message => handlers.resize?.(message),
613
622
  })
614
623
 
@@ -793,6 +802,17 @@ function getBindingError(
793
802
  return undefined
794
803
  }
795
804
 
805
+ function unsupportedOperation(type: string): RequestError {
806
+ return {
807
+ status: "error",
808
+ error: {
809
+ code: "unsupported_operation",
810
+ message: `The host does not support ${type}.`,
811
+ isRetryable: false,
812
+ },
813
+ }
814
+ }
815
+
796
816
  function getQueryValidationError(
797
817
  message: QueryDataSourceMessage,
798
818
  collectionSchema: NotionCollectionSchema | undefined,
package/src/index.ts CHANGED
@@ -44,6 +44,7 @@ export type {
44
44
  export type {
45
45
  CreatePageMessageInput,
46
46
  CustomBlockHostOpenPageResult,
47
+ CustomBlockHostPageIcon,
47
48
  CustomBlockHostPageResult,
48
49
  GetPageMessageInput,
49
50
  OpenPageMessageInput,
@@ -6,10 +6,16 @@ import type { InitResultMessage } from "@notionhq/custom-blocks-protocol/handsha
6
6
  import { isInitHandshakeMessage } from "@notionhq/custom-blocks-protocol/handshake/isInitHandshakeMessage.js"
7
7
  import type { InvalidSandboxMessage } from "@notionhq/custom-blocks-protocol/messages/invalidSandboxMessage.js"
8
8
  import type { OpenExternalUrlMessage } from "@notionhq/custom-blocks-protocol/navigation/openExternalUrl.js"
9
- import type { CreatePageMessage } from "@notionhq/custom-blocks-protocol/pages/messages/createPage.js"
9
+ import type {
10
+ CreatePageMessage,
11
+ CreatePageResultMessage,
12
+ } from "@notionhq/custom-blocks-protocol/pages/messages/createPage.js"
10
13
  import type { GetPageMessage } from "@notionhq/custom-blocks-protocol/pages/messages/getPage.js"
11
14
  import type { OpenPageMessage } from "@notionhq/custom-blocks-protocol/pages/messages/openPage.js"
12
- import type { UpdatePageMessage } from "@notionhq/custom-blocks-protocol/pages/messages/updatePage.js"
15
+ import type {
16
+ UpdatePageMessage,
17
+ UpdatePageResultMessage,
18
+ } from "@notionhq/custom-blocks-protocol/pages/messages/updatePage.js"
13
19
  import type { GetUserMessage } from "@notionhq/custom-blocks-protocol/users/messages/getUser.js"
14
20
  import type { ListUsersMessage } from "@notionhq/custom-blocks-protocol/users/messages/listUsers.js"
15
21
  import * as v from "valibot"
@@ -20,6 +26,7 @@ import { invalidInitResultPayloadError } from "../lifecycle/errors/invalidInitRe
20
26
  import type { InitStatus } from "../lifecycle/types.js"
21
27
  import { unreachable } from "../utils/unreachable.js"
22
28
  import { getInvalidSandboxMessageResponse } from "./getInvalidSandboxMessageResponse.js"
29
+ import { getInvalidNativeIconColorResponse } from "./invalidNativeIconColor.js"
23
30
  import type { CustomBlockHostLogDirection } from "./logging.js"
24
31
 
25
32
  const initializationIdentitySchema = v.object({
@@ -38,6 +45,9 @@ export type SandboxMessageRouterOptions = {
38
45
  getInitStatus: () => InitStatus
39
46
  emit: (direction: CustomBlockHostLogDirection, payload: unknown) => void
40
47
  sendNack: (message: InvalidSandboxMessage) => void
48
+ sendPageMutationError: (
49
+ message: UpdatePageResultMessage | CreatePageResultMessage,
50
+ ) => void
41
51
  onInvalidConnect: InitializationController["handleInvalidConnect"]
42
52
  onInvalidInitResult: InitializationController["handleInvalidInitResult"]
43
53
  onConnect: (message: ConnectMessage) => void
@@ -65,6 +75,7 @@ export function createSandboxMessageRouter(
65
75
  getInitStatus,
66
76
  emit,
67
77
  sendNack,
78
+ sendPageMutationError,
68
79
  onInvalidConnect,
69
80
  onInvalidInitResult,
70
81
  onConnect,
@@ -85,6 +96,11 @@ export function createSandboxMessageRouter(
85
96
  data: unknown,
86
97
  issues: readonly v.BaseIssue<unknown>[],
87
98
  ) {
99
+ const iconError = getInvalidNativeIconColorResponse(data)
100
+ if (iconError !== undefined && getInitStatus() === "initialized") {
101
+ sendPageMutationError(iconError)
102
+ return
103
+ }
88
104
  emit("warn", {
89
105
  message: "Ignored unsupported sandbox message",
90
106
  payload: redactExternalUrl(data),
@@ -1,5 +1,6 @@
1
1
  import { readIncomingType } from "@notionhq/custom-blocks-protocol/messages/incomingType.js"
2
2
  import type { InvalidSandboxMessage } from "@notionhq/custom-blocks-protocol/messages/invalidSandboxMessage.js"
3
+ import { readRequestId } from "@notionhq/custom-blocks-protocol/utils/readRequestId.js"
3
4
 
4
5
  export type InvalidSandboxMessageResponse = {
5
6
  incomingType: string | undefined
@@ -16,10 +17,12 @@ export function getInvalidSandboxMessageResponse(
16
17
  ) {
17
18
  return { incomingType }
18
19
  }
20
+ const requestId = readRequestId(data)
19
21
  return {
20
22
  incomingType,
21
23
  nack: {
22
24
  type: "invalidSandboxMessage",
25
+ ...(requestId !== undefined ? { requestId } : {}),
23
26
  reason: nackReasonForInvalidSandboxMessage(incomingType),
24
27
  },
25
28
  }
@@ -0,0 +1,43 @@
1
+ import { notionIconColorSchema } from "@notionhq/custom-blocks-protocol/icons.js"
2
+ import type { CreatePageResultMessage } from "@notionhq/custom-blocks-protocol/pages/messages/createPage.js"
3
+ import type { UpdatePageResultMessage } from "@notionhq/custom-blocks-protocol/pages/messages/updatePage.js"
4
+ import * as v from "valibot"
5
+
6
+ const nativeIconMutationSchema = v.object({
7
+ type: v.picklist(["updatePage", "createPage"]),
8
+ requestId: v.string(),
9
+ icon: v.object({
10
+ type: v.literal("icon"),
11
+ icon: v.object({ color: v.unknown() }),
12
+ }),
13
+ })
14
+
15
+ /** Reject malformed colors with a correlated result before invoking a page mutation handler. */
16
+ export function getInvalidNativeIconColorResponse(
17
+ data: unknown,
18
+ ): UpdatePageResultMessage | CreatePageResultMessage | undefined {
19
+ const parsed = v.safeParse(nativeIconMutationSchema, data)
20
+ if (!parsed.success) {
21
+ return
22
+ }
23
+ const color = parsed.output.icon.icon.color
24
+ if (
25
+ color === undefined ||
26
+ v.safeParse(notionIconColorSchema, color).success
27
+ ) {
28
+ return
29
+ }
30
+ return {
31
+ type:
32
+ parsed.output.type === "createPage"
33
+ ? "createPageResult"
34
+ : "updatePageResult",
35
+ requestId: parsed.output.requestId,
36
+ status: "error",
37
+ error: {
38
+ code: "invalid_icon_color",
39
+ message: "Use a supported Notion icon color.",
40
+ isRetryable: false,
41
+ },
42
+ }
43
+ }
@@ -3,6 +3,7 @@ import type { createPageMessageSchema } from "@notionhq/custom-blocks-protocol/p
3
3
  import type { getPageMessageSchema } from "@notionhq/custom-blocks-protocol/pages/messages/getPage.js"
4
4
  import type { openPageMessageSchema } from "@notionhq/custom-blocks-protocol/pages/messages/openPage.js"
5
5
  import type { updatePageMessageSchema } from "@notionhq/custom-blocks-protocol/pages/messages/updatePage.js"
6
+ import type { notionPageIconSchema } from "@notionhq/custom-blocks-protocol/pages/pageAppearance.js"
6
7
  import type { InferInput } from "valibot"
7
8
  import type { CustomBlockHostResult } from "../messages/result.js"
8
9
 
@@ -20,3 +21,5 @@ export type CustomBlockHostPageResult = CustomBlockHostResult<{
20
21
  export type CustomBlockHostOpenPageResult = CustomBlockHostResult<{
21
22
  pageId: string
22
23
  }>
24
+
25
+ export type CustomBlockHostPageIcon = InferInput<typeof notionPageIconSchema>