@notionhq/custom-blocks-host 0.1.8 → 0.1.10

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 (33) hide show
  1. package/README.md +43 -24
  2. package/dist/createCustomBlockHost.d.ts +13 -11
  3. package/dist/createCustomBlockHost.d.ts.map +1 -1
  4. package/dist/createCustomBlockHost.js +35 -106
  5. package/dist/lifecycle/initializationController.d.ts +9 -4
  6. package/dist/lifecycle/initializationController.d.ts.map +1 -1
  7. package/dist/lifecycle/initializationController.js +34 -26
  8. package/dist/lifecycle/types.d.ts +5 -1
  9. package/dist/lifecycle/types.d.ts.map +1 -1
  10. package/dist/messages/invalidSandboxMessage.js +1 -1
  11. package/dist/messages/sandboxMessageRouter.d.ts +35 -0
  12. package/dist/messages/sandboxMessageRouter.d.ts.map +1 -0
  13. package/dist/messages/sandboxMessageRouter.js +107 -0
  14. package/dist/messages/types.d.ts +1 -1
  15. package/dist/messages/types.d.ts.map +1 -1
  16. package/dist/protocol/index.d.ts +3 -2
  17. package/dist/protocol/index.js +3 -2
  18. package/dist/protocol/messages/isInitHandshakeMessage.d.ts +4 -0
  19. package/dist/protocol/messages/isInitHandshakeMessage.js +11 -0
  20. package/dist/protocol/{messagePayload.d.ts → messages/messagePayload.d.ts} +1 -1
  21. package/dist/queries/types.d.ts +1 -1
  22. package/dist/queries/types.d.ts.map +1 -1
  23. package/package.json +1 -1
  24. package/src/createCustomBlockHost.ts +49 -134
  25. package/src/lifecycle/initializationController.ts +54 -33
  26. package/src/lifecycle/types.ts +5 -1
  27. package/src/messages/invalidSandboxMessage.ts +1 -1
  28. package/src/messages/sandboxMessageRouter.ts +176 -0
  29. package/src/messages/types.ts +1 -1
  30. package/src/queries/types.ts +1 -1
  31. /package/dist/protocol/{incomingType.d.ts → messages/incomingType.d.ts} +0 -0
  32. /package/dist/protocol/{incomingType.js → messages/incomingType.js} +0 -0
  33. /package/dist/protocol/{messagePayload.js → messages/messagePayload.js} +0 -0
@@ -34,7 +34,11 @@ export type CustomBlockHostInitialState = {
34
34
  blockId: NotionBlockId
35
35
  parent: NotionParent
36
36
  page: CustomBlockPage
37
- manifest: CustomBlockManifest
37
+ /**
38
+ * The host's persisted manifest, when one exists. If omitted, the host
39
+ * requires the sandbox to provide a manifest in `connect`.
40
+ */
41
+ manifest?: CustomBlockManifest
38
42
  dataSources: CustomBlockHostInitialDataSources
39
43
  currentUser: NotionUser
40
44
  }
@@ -1,4 +1,4 @@
1
- import { readIncomingType } from "@notionhq/custom-blocks-protocol/incomingType.js"
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
3
 
4
4
  export type InvalidSandboxMessageResponse = {
@@ -0,0 +1,176 @@
1
+ import type { ConnectMessage } from "@notionhq/custom-blocks-protocol/messages/connect.js"
2
+ import type { CreatePageMessage } from "@notionhq/custom-blocks-protocol/messages/createPage.js"
3
+ import type { GetPageMessage } from "@notionhq/custom-blocks-protocol/messages/getPage.js"
4
+ import type { GetUserMessage } from "@notionhq/custom-blocks-protocol/messages/getUser.js"
5
+ import type { HostToSandboxMessage } from "@notionhq/custom-blocks-protocol/messages/hostToSandbox.js"
6
+ import type { InitResultMessage } from "@notionhq/custom-blocks-protocol/messages/initResult.js"
7
+ import { isInitHandshakeMessage } from "@notionhq/custom-blocks-protocol/messages/isInitHandshakeMessage.js"
8
+ import type { ListUsersMessage } from "@notionhq/custom-blocks-protocol/messages/listUsers.js"
9
+ import type { QueryDataSourceMessage } from "@notionhq/custom-blocks-protocol/messages/queryDataSource.js"
10
+ import type { ResizeMessage } from "@notionhq/custom-blocks-protocol/messages/resize.js"
11
+ import { sandboxToHostMessageSchema } from "@notionhq/custom-blocks-protocol/messages/sandboxToHost.js"
12
+ import type { UnsubscribeDataSourceQueryMessage } from "@notionhq/custom-blocks-protocol/messages/unsubscribeDataSourceQuery.js"
13
+ import type { UpdatePageMessage } from "@notionhq/custom-blocks-protocol/messages/updatePage.js"
14
+ import * as v from "valibot"
15
+ import { initErrorForFailureReason } from "../lifecycle/initErrors.js"
16
+ import type { InitializationController } from "../lifecycle/initializationController.js"
17
+ import type { InitStatus } from "../lifecycle/types.js"
18
+ import { unreachable } from "../utils.js"
19
+ import { getInvalidSandboxMessageResponse } from "./invalidSandboxMessage.js"
20
+ import type { CustomBlockHostLogDirection } from "./types.js"
21
+
22
+ const initializationIdentitySchema = v.object({
23
+ initializationId: v.string(),
24
+ })
25
+
26
+ export type SandboxMessageRouterOptions = {
27
+ iframe: HTMLIFrameElement
28
+ sandboxOrigin: string
29
+ getInitStatus: () => InitStatus
30
+ emit: (direction: CustomBlockHostLogDirection, payload: unknown) => void
31
+ post: (message: HostToSandboxMessage) => void
32
+ onInvalidConnect: InitializationController["handleInvalidConnect"]
33
+ onInvalidInitResult: InitializationController["handleInvalidInitResult"]
34
+ onConnect: (message: ConnectMessage) => void
35
+ onInitResult: (message: InitResultMessage) => void
36
+ onQueryDataSource: (message: QueryDataSourceMessage) => void | Promise<void>
37
+ onUnsubscribeDataSourceQuery: (
38
+ message: UnsubscribeDataSourceQueryMessage,
39
+ ) => void
40
+ onCreatePage: (message: CreatePageMessage) => void | Promise<void>
41
+ onGetPage: (message: GetPageMessage) => void | Promise<void>
42
+ onGetUser: (message: GetUserMessage) => void | Promise<void>
43
+ onListUsers: (message: ListUsersMessage) => void | Promise<void>
44
+ onUpdatePage: (message: UpdatePageMessage) => void | Promise<void>
45
+ onResize: (message: ResizeMessage) => void
46
+ }
47
+
48
+ export function createSandboxMessageRouter(
49
+ options: SandboxMessageRouterOptions,
50
+ ): (event: MessageEvent) => void {
51
+ const {
52
+ iframe,
53
+ sandboxOrigin,
54
+ getInitStatus,
55
+ emit,
56
+ post,
57
+ onInvalidConnect,
58
+ onInvalidInitResult,
59
+ onConnect,
60
+ onInitResult,
61
+ onQueryDataSource,
62
+ onUnsubscribeDataSourceQuery,
63
+ onCreatePage,
64
+ onGetPage,
65
+ onGetUser,
66
+ onListUsers,
67
+ onUpdatePage,
68
+ onResize,
69
+ } = options
70
+
71
+ function onInvalidSandboxMessage(
72
+ data: unknown,
73
+ issues: readonly v.BaseIssue<unknown>[],
74
+ ) {
75
+ emit("warn", {
76
+ message: "Ignored unsupported sandbox message",
77
+ payload: data,
78
+ issues,
79
+ })
80
+ const response = getInvalidSandboxMessageResponse(data)
81
+
82
+ if (response.incomingType === "connect") {
83
+ const parsedIdentity = v.safeParse(initializationIdentitySchema, data)
84
+ if (parsedIdentity.success) {
85
+ const responseType = onInvalidConnect({
86
+ initializationId: parsedIdentity.output.initializationId,
87
+ error: initErrorForFailureReason("invalid_connect_payload"),
88
+ })
89
+ if (responseType === "initErrorSent") {
90
+ // The controller already sent `init.error`, so no need to also send a NACK.
91
+ return
92
+ }
93
+ }
94
+ }
95
+
96
+ if (response.incomingType === "initResult") {
97
+ const parsedIdentity = v.safeParse(initializationIdentitySchema, data)
98
+ onInvalidInitResult({
99
+ initializationId: parsedIdentity.success
100
+ ? parsedIdentity.output.initializationId
101
+ : undefined,
102
+ error: initErrorForFailureReason("invalid_init_result_payload"),
103
+ })
104
+ }
105
+
106
+ // Other messages get NACKed indicating they were invalid and skipped
107
+ if (response.nack !== undefined) {
108
+ post(response.nack)
109
+ }
110
+ }
111
+
112
+ return function onMessage(event: MessageEvent) {
113
+ // Verify the sender is this host's iframe, not another window with the same origin.
114
+ const isExpectedSource = event.source === iframe.contentWindow
115
+ // Verify the sender's origin, since the iframe can navigate to another origin.
116
+ const isExpectedOrigin = event.origin === sandboxOrigin
117
+ if (!isExpectedSource || !isExpectedOrigin) {
118
+ return
119
+ }
120
+
121
+ // Parse and validate the message from the sandbox.
122
+ const parsed = v.safeParse(sandboxToHostMessageSchema, event.data)
123
+ if (!parsed.success) {
124
+ onInvalidSandboxMessage(event.data, parsed.issues)
125
+ return
126
+ }
127
+
128
+ const message = parsed.output
129
+ if (!isInitHandshakeMessage(message) && getInitStatus() !== "success") {
130
+ emit("warn", {
131
+ message: `Ignored ${message.type} before initialization completed`,
132
+ payload: message,
133
+ })
134
+ return
135
+ }
136
+
137
+ emit("in", message)
138
+ switch (message.type) {
139
+ case "connect":
140
+ onConnect(message)
141
+ return
142
+ case "initResult":
143
+ onInitResult(message)
144
+ return
145
+ case "queryDataSource":
146
+ void onQueryDataSource(message)
147
+ return
148
+ case "unsubscribeDataSourceQuery":
149
+ onUnsubscribeDataSourceQuery(message)
150
+ return
151
+ case "createPage":
152
+ void onCreatePage(message)
153
+ return
154
+ case "getPage":
155
+ void onGetPage(message)
156
+ return
157
+ case "getUser":
158
+ void onGetUser(message)
159
+ return
160
+ case "listUsers":
161
+ void onListUsers(message)
162
+ return
163
+ case "updatePage":
164
+ void onUpdatePage(message)
165
+ return
166
+ case "resize":
167
+ onResize(message)
168
+ return
169
+ case "invalidHostMessage":
170
+ // The inbound log records this rejection. Do not reply, to avoid an error-response loop.
171
+ return
172
+ default:
173
+ unreachable(message)
174
+ }
175
+ }
176
+ }
@@ -1,4 +1,3 @@
1
- import type { BridgeMessagePayload } from "@notionhq/custom-blocks-protocol/messagePayload.js"
2
1
  import type { CreatePageMessage } from "@notionhq/custom-blocks-protocol/messages/createPage.js"
3
2
  import type {
4
3
  CreatePageResultMessage,
@@ -21,6 +20,7 @@ import type {
21
20
  ListUsersMessage,
22
21
  ListUsersResultMessage,
23
22
  } from "@notionhq/custom-blocks-protocol/messages/listUsers.js"
23
+ import type { BridgeMessagePayload } from "@notionhq/custom-blocks-protocol/messages/messagePayload.js"
24
24
  import type { QueryDataSourceMessage } from "@notionhq/custom-blocks-protocol/messages/queryDataSource.js"
25
25
  import type { ResizeMessage } from "@notionhq/custom-blocks-protocol/messages/resize.js"
26
26
  import type { UpdatePageMessage } from "@notionhq/custom-blocks-protocol/messages/updatePage.js"
@@ -1,4 +1,4 @@
1
- import type { BridgeMessagePayload } from "@notionhq/custom-blocks-protocol/messagePayload.js"
1
+ import type { BridgeMessagePayload } from "@notionhq/custom-blocks-protocol/messages/messagePayload.js"
2
2
  import type {
3
3
  CustomBlockQueryDataSourceErrorInfo,
4
4
  QueryDataSourceResultMessage,