@notionhq/custom-blocks-host 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. package/README.md +204 -0
  2. package/dist/autoBindProperties.d.ts +21 -0
  3. package/dist/autoBindProperties.d.ts.map +1 -0
  4. package/dist/autoBindProperties.js +38 -0
  5. package/dist/createCustomBlockHost.d.ts +46 -0
  6. package/dist/createCustomBlockHost.d.ts.map +1 -0
  7. package/dist/createCustomBlockHost.js +433 -0
  8. package/dist/index.d.ts +6 -0
  9. package/dist/index.d.ts.map +1 -0
  10. package/dist/index.js +2 -0
  11. package/dist/lifecycle/initErrors.d.ts +13 -0
  12. package/dist/lifecycle/initErrors.d.ts.map +1 -0
  13. package/dist/lifecycle/initErrors.js +152 -0
  14. package/dist/lifecycle/initializationController.d.ts +54 -0
  15. package/dist/lifecycle/initializationController.d.ts.map +1 -0
  16. package/dist/lifecycle/initializationController.js +182 -0
  17. package/dist/lifecycle/protocolVersion.d.ts +2 -0
  18. package/dist/lifecycle/protocolVersion.d.ts.map +1 -0
  19. package/dist/lifecycle/protocolVersion.js +3 -0
  20. package/dist/lifecycle/types.d.ts +28 -0
  21. package/dist/lifecycle/types.d.ts.map +1 -0
  22. package/dist/lifecycle/types.js +1 -0
  23. package/dist/messages/invalidSandboxMessage.d.ts +7 -0
  24. package/dist/messages/invalidSandboxMessage.d.ts.map +1 -0
  25. package/dist/messages/invalidSandboxMessage.js +21 -0
  26. package/dist/messages/types.d.ts +29 -0
  27. package/dist/messages/types.d.ts.map +1 -0
  28. package/dist/messages/types.js +1 -0
  29. package/dist/protocol/contrast.d.ts +4 -0
  30. package/dist/protocol/contrast.js +3 -0
  31. package/dist/protocol/dataSources/dataSource.d.ts +731 -0
  32. package/dist/protocol/dataSources/dataSource.js +37 -0
  33. package/dist/protocol/dataSources/dataSourcePage.d.ts +72 -0
  34. package/dist/protocol/dataSources/dataSourcePage.js +12 -0
  35. package/dist/protocol/dataSources/dataSourceValue.d.ts +66 -0
  36. package/dist/protocol/dataSources/dataSourceValue.js +14 -0
  37. package/dist/protocol/dataSources/dateValue.d.ts +157 -0
  38. package/dist/protocol/dataSources/dateValue.js +59 -0
  39. package/dist/protocol/dataSources/errors.d.ts +8 -0
  40. package/dist/protocol/dataSources/errors.js +7 -0
  41. package/dist/protocol/dataSources/propertySchema.d.ts +197 -0
  42. package/dist/protocol/dataSources/propertySchema.js +165 -0
  43. package/dist/protocol/dataSources/recordPointer.d.ts +9 -0
  44. package/dist/protocol/dataSources/recordPointer.js +8 -0
  45. package/dist/protocol/errors.d.ts +12 -0
  46. package/dist/protocol/errors.js +6 -0
  47. package/dist/protocol/ids.d.ts +31 -0
  48. package/dist/protocol/ids.js +26 -0
  49. package/dist/protocol/incomingType.d.ts +9 -0
  50. package/dist/protocol/incomingType.js +17 -0
  51. package/dist/protocol/index.d.ts +48 -0
  52. package/dist/protocol/index.js +48 -0
  53. package/dist/protocol/manifest.d.ts +60 -0
  54. package/dist/protocol/manifest.js +32 -0
  55. package/dist/protocol/messagePayload.d.ts +19 -0
  56. package/dist/protocol/messagePayload.js +1 -0
  57. package/dist/protocol/messages/connect.d.ts +47 -0
  58. package/dist/protocol/messages/connect.js +38 -0
  59. package/dist/protocol/messages/contrastModeChanged.d.ts +7 -0
  60. package/dist/protocol/messages/contrastModeChanged.js +7 -0
  61. package/dist/protocol/messages/createPage.d.ts +200 -0
  62. package/dist/protocol/messages/createPage.js +52 -0
  63. package/dist/protocol/messages/createPageResult.d.ts +213 -0
  64. package/dist/protocol/messages/createPageResult.js +28 -0
  65. package/dist/protocol/messages/currentUserChanged.d.ts +18 -0
  66. package/dist/protocol/messages/currentUserChanged.js +9 -0
  67. package/dist/protocol/messages/dataSourcesChanged.d.ts +157 -0
  68. package/dist/protocol/messages/dataSourcesChanged.js +13 -0
  69. package/dist/protocol/messages/getPage.d.ts +218 -0
  70. package/dist/protocol/messages/getPage.js +33 -0
  71. package/dist/protocol/messages/getUser.d.ts +44 -0
  72. package/dist/protocol/messages/getUser.js +32 -0
  73. package/dist/protocol/messages/hostToSandbox.d.ts +1167 -0
  74. package/dist/protocol/messages/hostToSandbox.js +34 -0
  75. package/dist/protocol/messages/init.d.ts +259 -0
  76. package/dist/protocol/messages/init.js +56 -0
  77. package/dist/protocol/messages/initResult.d.ts +37 -0
  78. package/dist/protocol/messages/initResult.js +28 -0
  79. package/dist/protocol/messages/invalidHostMessage.d.ts +14 -0
  80. package/dist/protocol/messages/invalidHostMessage.js +13 -0
  81. package/dist/protocol/messages/invalidSandboxMessage.d.ts +14 -0
  82. package/dist/protocol/messages/invalidSandboxMessage.js +13 -0
  83. package/dist/protocol/messages/listUsers.d.ts +52 -0
  84. package/dist/protocol/messages/listUsers.js +33 -0
  85. package/dist/protocol/messages/pageChanged.d.ts +27 -0
  86. package/dist/protocol/messages/pageChanged.js +9 -0
  87. package/dist/protocol/messages/parentChanged.d.ts +24 -0
  88. package/dist/protocol/messages/parentChanged.js +9 -0
  89. package/dist/protocol/messages/queryDataSource.d.ts +985 -0
  90. package/dist/protocol/messages/queryDataSource.js +123 -0
  91. package/dist/protocol/messages/queryDataSourceResult.d.ts +93 -0
  92. package/dist/protocol/messages/queryDataSourceResult.js +28 -0
  93. package/dist/protocol/messages/resize.d.ts +11 -0
  94. package/dist/protocol/messages/resize.js +10 -0
  95. package/dist/protocol/messages/sandboxToHost.d.ts +744 -0
  96. package/dist/protocol/messages/sandboxToHost.js +27 -0
  97. package/dist/protocol/messages/themeChanged.d.ts +10 -0
  98. package/dist/protocol/messages/themeChanged.js +10 -0
  99. package/dist/protocol/messages/updatePage.d.ts +180 -0
  100. package/dist/protocol/messages/updatePage.js +22 -0
  101. package/dist/protocol/messages/updatePageResult.d.ts +212 -0
  102. package/dist/protocol/messages/updatePageResult.js +27 -0
  103. package/dist/protocol/pages/page.d.ts +597 -0
  104. package/dist/protocol/pages/page.js +212 -0
  105. package/dist/protocol/parent.d.ts +28 -0
  106. package/dist/protocol/parent.js +12 -0
  107. package/dist/protocol/protocolVersion.d.ts +6 -0
  108. package/dist/protocol/protocolVersion.js +6 -0
  109. package/dist/protocol/theme.d.ts +3 -0
  110. package/dist/protocol/theme.js +2 -0
  111. package/dist/protocol/users/user.d.ts +32 -0
  112. package/dist/protocol/users/user.js +20 -0
  113. package/dist/queries/querySubscriptions.d.ts +17 -0
  114. package/dist/queries/querySubscriptions.d.ts.map +1 -0
  115. package/dist/queries/querySubscriptions.js +38 -0
  116. package/dist/queries/types.d.ts +4 -0
  117. package/dist/queries/types.d.ts.map +1 -0
  118. package/dist/queries/types.js +1 -0
  119. package/dist/queries/validateQueryDataSourceFilter.d.ts +5 -0
  120. package/dist/queries/validateQueryDataSourceFilter.d.ts.map +1 -0
  121. package/dist/queries/validateQueryDataSourceFilter.js +72 -0
  122. package/dist/queries/validateQueryDataSourceSorts.d.ts +8 -0
  123. package/dist/queries/validateQueryDataSourceSorts.d.ts.map +1 -0
  124. package/dist/queries/validateQueryDataSourceSorts.js +47 -0
  125. package/dist/utils.d.ts +5 -0
  126. package/dist/utils.d.ts.map +1 -0
  127. package/dist/utils.js +6 -0
  128. package/package.json +26 -0
  129. package/src/autoBindProperties.ts +63 -0
  130. package/src/createCustomBlockHost.ts +605 -0
  131. package/src/index.ts +25 -0
  132. package/src/lifecycle/initErrors.ts +165 -0
  133. package/src/lifecycle/initializationController.ts +290 -0
  134. package/src/lifecycle/protocolVersion.ts +3 -0
  135. package/src/lifecycle/types.ts +40 -0
  136. package/src/messages/invalidSandboxMessage.ts +35 -0
  137. package/src/messages/types.ts +80 -0
  138. package/src/queries/querySubscriptions.ts +57 -0
  139. package/src/queries/types.ts +10 -0
  140. package/src/queries/validateQueryDataSourceFilter.ts +101 -0
  141. package/src/queries/validateQueryDataSourceSorts.ts +69 -0
  142. package/src/utils.ts +8 -0
@@ -0,0 +1,605 @@
1
+ import {
2
+ DEFAULT_CONTRAST_MODE,
3
+ type NotionContrastMode,
4
+ } from "@notionhq/custom-blocks-protocol/contrast.js"
5
+ import type { NotionDataSourceId } from "@notionhq/custom-blocks-protocol/ids.js"
6
+ import type { CustomBlockManifest } from "@notionhq/custom-blocks-protocol/manifest.js"
7
+ import type { ConnectMessage } from "@notionhq/custom-blocks-protocol/messages/connect.js"
8
+ import type { ContrastModeChangedMessage } from "@notionhq/custom-blocks-protocol/messages/contrastModeChanged.js"
9
+ import type { CreatePageMessage } from "@notionhq/custom-blocks-protocol/messages/createPage.js"
10
+ import type { CreatePageResultMessage } from "@notionhq/custom-blocks-protocol/messages/createPageResult.js"
11
+ import type { DataSourcesChangedMessage } from "@notionhq/custom-blocks-protocol/messages/dataSourcesChanged.js"
12
+ import type {
13
+ GetPageMessage,
14
+ GetPageResultMessage,
15
+ } from "@notionhq/custom-blocks-protocol/messages/getPage.js"
16
+ import type {
17
+ GetUserMessage,
18
+ GetUserResultMessage,
19
+ } from "@notionhq/custom-blocks-protocol/messages/getUser.js"
20
+ import type { HostToSandboxMessage } from "@notionhq/custom-blocks-protocol/messages/hostToSandbox.js"
21
+ import type { CustomBlockInitErrorCode } from "@notionhq/custom-blocks-protocol/messages/init.js"
22
+ import type { InitResultMessage } from "@notionhq/custom-blocks-protocol/messages/initResult.js"
23
+ import type {
24
+ ListUsersMessage,
25
+ ListUsersResultMessage,
26
+ } from "@notionhq/custom-blocks-protocol/messages/listUsers.js"
27
+ import type { PageChangedMessage } from "@notionhq/custom-blocks-protocol/messages/pageChanged.js"
28
+ import type { ParentChangedMessage } from "@notionhq/custom-blocks-protocol/messages/parentChanged.js"
29
+ import type { QueryDataSourceMessage } from "@notionhq/custom-blocks-protocol/messages/queryDataSource.js"
30
+ import type { QueryDataSourceResultMessage } from "@notionhq/custom-blocks-protocol/messages/queryDataSourceResult.js"
31
+ import { sandboxToHostMessageSchema } from "@notionhq/custom-blocks-protocol/messages/sandboxToHost.js"
32
+ import type { ThemeChangedMessage } from "@notionhq/custom-blocks-protocol/messages/themeChanged.js"
33
+ import type { UpdatePageMessage } from "@notionhq/custom-blocks-protocol/messages/updatePage.js"
34
+ import type { UpdatePageResultMessage } from "@notionhq/custom-blocks-protocol/messages/updatePageResult.js"
35
+ import type { CustomBlockPage } from "@notionhq/custom-blocks-protocol/pages/page.js"
36
+ import type { NotionParent } from "@notionhq/custom-blocks-protocol/parent.js"
37
+ import { CUSTOM_BLOCK_BRIDGE_PROTOCOL_VERSION } from "@notionhq/custom-blocks-protocol/protocolVersion.js"
38
+ import type { NotionTheme } from "@notionhq/custom-blocks-protocol/theme.js"
39
+ import type { NotionUser } from "@notionhq/custom-blocks-protocol/users/user.js"
40
+ import * as v from "valibot"
41
+ import { initErrorForFailureReason } from "./lifecycle/initErrors.js"
42
+ import { createInitializationController } from "./lifecycle/initializationController.js"
43
+ import type {
44
+ ConnectSuccessMessage,
45
+ CustomBlockHostDataSourcesPayload,
46
+ CustomBlockHostInitialState,
47
+ } from "./lifecycle/types.js"
48
+ import { getInvalidSandboxMessageResponse } from "./messages/invalidSandboxMessage.js"
49
+ import type {
50
+ CustomBlockHostHandlers,
51
+ CustomBlockHostLogDirection,
52
+ } from "./messages/types.js"
53
+ import {
54
+ QuerySubscriptions,
55
+ type QuerySubscriptionToken,
56
+ } from "./queries/querySubscriptions.js"
57
+ import type { CustomBlockHostQueryDataSourceResult } from "./queries/types.js"
58
+ import { validateQueryDataSourceFilter } from "./queries/validateQueryDataSourceFilter.js"
59
+ import { validateQueryDataSourceSorts } from "./queries/validateQueryDataSourceSorts.js"
60
+ import { unreachable } from "./utils.js"
61
+
62
+ export type CustomBlockHostOptions = {
63
+ iframe: HTMLIFrameElement
64
+ sandboxOrigin: string
65
+ minBridgeProtocolVersion: number
66
+ noConnectTimeoutMs?: number
67
+ noInitResultTimeoutMs?: number
68
+ initialState: CustomBlockHostInitialState
69
+ handlers: CustomBlockHostHandlers
70
+ onConnect?: (message: ConnectSuccessMessage) => void
71
+ onInitResult?: (message: InitResultMessage) => void
72
+ /**
73
+ * A `connect` arrived after this host's initialization settled — the
74
+ * document was replaced, or its first `connect` came after the host
75
+ * stopped waiting. Either way the host cannot serve it: the embedder
76
+ * should recreate host and iframe together so the document can run a
77
+ * fresh handshake.
78
+ */
79
+ onIgnoredConnect?: (message: ConnectMessage) => void
80
+ onLog?: (direction: CustomBlockHostLogDirection, payload: unknown) => void
81
+ }
82
+
83
+ export type CustomBlockHostHandle = {
84
+ stop: () => void
85
+ post: (message: HostToSandboxMessage) => void
86
+ setTheme: (theme: NotionTheme) => void
87
+ setContrastMode: (contrastMode: NotionContrastMode) => void
88
+ setParent: (parent: NotionParent) => void
89
+ setPage: (page: CustomBlockPage) => void
90
+ setDataSources: (dataSources: CustomBlockHostDataSourcesPayload) => void
91
+ setCurrentUser: (currentUser: NotionUser) => void
92
+ refreshQuery: (args: { dataSourceId: NotionDataSourceId }) => void
93
+ }
94
+
95
+ const initializationIdentitySchema = v.object({
96
+ initializationId: v.string(),
97
+ })
98
+
99
+ export function createCustomBlockHost(
100
+ options: CustomBlockHostOptions,
101
+ ): CustomBlockHostHandle {
102
+ const {
103
+ iframe,
104
+ initialState,
105
+ handlers,
106
+ onConnect,
107
+ onInitResult,
108
+ onIgnoredConnect,
109
+ onLog,
110
+ sandboxOrigin,
111
+ minBridgeProtocolVersion: configuredMinBridgeProtocolVersion,
112
+ noConnectTimeoutMs,
113
+ noInitResultTimeoutMs,
114
+ } = options
115
+ const minBridgeProtocolVersion = Math.max(
116
+ configuredMinBridgeProtocolVersion ?? CUSTOM_BLOCK_BRIDGE_PROTOCOL_VERSION,
117
+ CUSTOM_BLOCK_BRIDGE_PROTOCOL_VERSION,
118
+ )
119
+
120
+ let theme = initialState.theme
121
+ let contrastMode = initialState.contrastMode ?? DEFAULT_CONTRAST_MODE
122
+ let parent = initialState.parent
123
+ let page = initialState.page
124
+ let dataSources = initialState.dataSources
125
+ let activeDataSources: CustomBlockHostDataSourcesPayload | undefined
126
+ let manifest = initialState.manifest
127
+ let currentUser = initialState.currentUser
128
+ let pendingThemeChange = false
129
+ let pendingContrastModeChange = false
130
+ let pendingParentChange = false
131
+ let pendingPageChange = false
132
+ let pendingDataSourcesChange: CustomBlockHostDataSourcesPayload | undefined
133
+ let pendingCurrentUserChange = false
134
+ const querySubscriptions = new QuerySubscriptions()
135
+
136
+ function emit(direction: CustomBlockHostLogDirection, payload: unknown) {
137
+ onLog?.(direction, payload)
138
+ }
139
+
140
+ function post(message: HostToSandboxMessage) {
141
+ emit("out", message)
142
+ iframe.contentWindow?.postMessage(message, sandboxOrigin)
143
+ }
144
+
145
+ function postQueryDataSourceResult(args: {
146
+ subscriptionId: string
147
+ response: CustomBlockHostQueryDataSourceResult
148
+ }) {
149
+ const message: QueryDataSourceResultMessage =
150
+ args.response.status === "error"
151
+ ? {
152
+ type: "queryDataSourceResult",
153
+ subscriptionId: args.subscriptionId,
154
+ status: "error",
155
+ error: args.response.error,
156
+ }
157
+ : {
158
+ type: "queryDataSourceResult",
159
+ subscriptionId: args.subscriptionId,
160
+ status: "success",
161
+ items: args.response.items,
162
+ hasMore: args.response.hasMore,
163
+ }
164
+ post(message)
165
+ }
166
+
167
+ async function handleQuery(
168
+ message: QueryDataSourceMessage,
169
+ token: QuerySubscriptionToken = querySubscriptions.track(message),
170
+ ) {
171
+ const collectionSchema = getCollectionSchema(
172
+ activeDataSources,
173
+ message.dataSourceId,
174
+ )
175
+ const filterError = validateQueryDataSourceFilter(
176
+ message.filter,
177
+ collectionSchema,
178
+ )
179
+ if (filterError !== undefined) {
180
+ if (querySubscriptions.isCurrent(message.subscriptionId, token)) {
181
+ postQueryDataSourceResult({
182
+ subscriptionId: message.subscriptionId,
183
+ response: { status: "error", error: filterError },
184
+ })
185
+ }
186
+ return
187
+ }
188
+ const sortError = validateQueryDataSourceSorts(
189
+ message.sorts,
190
+ collectionSchema,
191
+ )
192
+ if (sortError !== undefined) {
193
+ if (querySubscriptions.isCurrent(message.subscriptionId, token)) {
194
+ postQueryDataSourceResult({
195
+ subscriptionId: message.subscriptionId,
196
+ response: { status: "error", error: sortError },
197
+ })
198
+ }
199
+ return
200
+ }
201
+ const response = await handlers.queryDataSource(message)
202
+ if (!querySubscriptions.isCurrent(message.subscriptionId, token)) {
203
+ return
204
+ }
205
+ postQueryDataSourceResult({
206
+ subscriptionId: message.subscriptionId,
207
+ response,
208
+ })
209
+ }
210
+
211
+ async function handleCreatePage(message: CreatePageMessage) {
212
+ if (handlers.createPage === undefined) {
213
+ return
214
+ }
215
+ const response = await handlers.createPage(message)
216
+ const result: CreatePageResultMessage = {
217
+ type: "createPageResult",
218
+ requestId: message.requestId,
219
+ ...response,
220
+ }
221
+ post(result)
222
+ }
223
+
224
+ async function handleGetPage(message: GetPageMessage) {
225
+ if (handlers.getPage === undefined) {
226
+ return
227
+ }
228
+ const response = await handlers.getPage(message)
229
+ const result: GetPageResultMessage = {
230
+ type: "getPageResult",
231
+ requestId: message.requestId,
232
+ ...response,
233
+ }
234
+ post(result)
235
+ }
236
+
237
+ async function handleGetUser(message: GetUserMessage) {
238
+ if (handlers.getUser === undefined) {
239
+ return
240
+ }
241
+ const response = await handlers.getUser(message)
242
+ const result: GetUserResultMessage = {
243
+ type: "getUserResult",
244
+ requestId: message.requestId,
245
+ ...response,
246
+ }
247
+ post(result)
248
+ }
249
+
250
+ async function handleListUsers(message: ListUsersMessage) {
251
+ if (handlers.listUsers === undefined) {
252
+ return
253
+ }
254
+ const response = await handlers.listUsers(message)
255
+ const result: ListUsersResultMessage = {
256
+ type: "listUsersResult",
257
+ requestId: message.requestId,
258
+ ...response,
259
+ }
260
+ post(result)
261
+ }
262
+
263
+ async function handleUpdatePage(message: UpdatePageMessage) {
264
+ if (handlers.updatePage === undefined) {
265
+ return
266
+ }
267
+ const response = await handlers.updatePage(message)
268
+ const result: UpdatePageResultMessage = {
269
+ type: "updatePageResult",
270
+ requestId: message.requestId,
271
+ ...response,
272
+ }
273
+ post(result)
274
+ }
275
+
276
+ /**
277
+ * Sends any pending host state changes to the sandbox. It is possible for these to change
278
+ * between when the host sends `init` and when the sandbox responds with `initResult`. The host
279
+ * does send any pending state changes until hearing back from the sandbox.
280
+ */
281
+ function flushPendingHostStateChanges() {
282
+ if (pendingThemeChange) {
283
+ pendingThemeChange = false
284
+ post({ type: "themeChanged", theme })
285
+ }
286
+ if (pendingContrastModeChange) {
287
+ pendingContrastModeChange = false
288
+ post({ type: "contrastModeChanged", contrastMode })
289
+ }
290
+ if (pendingParentChange) {
291
+ pendingParentChange = false
292
+ post({ type: "parentChanged", parent })
293
+ }
294
+ if (pendingPageChange) {
295
+ pendingPageChange = false
296
+ post({ type: "pageChanged", page })
297
+ }
298
+ if (pendingDataSourcesChange !== undefined) {
299
+ const nextDataSources = pendingDataSourcesChange
300
+ pendingDataSourcesChange = undefined
301
+ post({ type: "dataSourcesChanged", dataSources: nextDataSources })
302
+ }
303
+ if (pendingCurrentUserChange) {
304
+ pendingCurrentUserChange = false
305
+ post({ type: "currentUserChanged", currentUser })
306
+ }
307
+ }
308
+
309
+ const initializationController = createInitializationController({
310
+ blockId: initialState.blockId,
311
+ getState: () => ({
312
+ theme,
313
+ contrastMode,
314
+ parent,
315
+ page,
316
+ currentUser,
317
+ }),
318
+ prepareInit(connect) {
319
+ manifest = connect.manifest ?? initialState.manifest
320
+ const resolvedDataSources =
321
+ typeof dataSources === "function" ? dataSources(connect) : dataSources
322
+ const bindingErrorCode = getBindingErrorCode(
323
+ manifest,
324
+ resolvedDataSources,
325
+ )
326
+ if (bindingErrorCode !== undefined) {
327
+ return {
328
+ status: "error",
329
+ error: initErrorForFailureReason(bindingErrorCode),
330
+ }
331
+ }
332
+ activeDataSources = resolvedDataSources
333
+ return {
334
+ status: "success",
335
+ manifest,
336
+ dataSources: resolvedDataSources,
337
+ }
338
+ },
339
+ post,
340
+ warn: payload => emit("warn", payload),
341
+ flushPendingHostStateChanges,
342
+ minBridgeProtocolVersion,
343
+ noConnectTimeoutMs,
344
+ noInitResultTimeoutMs,
345
+ onConnect,
346
+ onInitResult,
347
+ onIgnoredConnect,
348
+ })
349
+
350
+ function onInvalidSandboxMessage(
351
+ data: unknown,
352
+ issues: readonly v.BaseIssue<unknown>[],
353
+ ) {
354
+ emit("warn", {
355
+ message: "Ignored unsupported sandbox message",
356
+ payload: data,
357
+ issues,
358
+ })
359
+ const response = getInvalidSandboxMessageResponse(data)
360
+
361
+ const parsedIdentity = v.safeParse(initializationIdentitySchema, data)
362
+ const initializationId = parsedIdentity.success
363
+ ? parsedIdentity.output.initializationId
364
+ : undefined
365
+ if (
366
+ response.incomingType === "connect" &&
367
+ initializationId !== undefined &&
368
+ initializationController.handleInvalidConnect(initializationId)
369
+ ) {
370
+ return
371
+ }
372
+ if (
373
+ response.incomingType === "initResult" &&
374
+ initializationController.handleInvalidInitResult()
375
+ ) {
376
+ if (response.nack !== undefined) {
377
+ post(response.nack)
378
+ }
379
+ return
380
+ }
381
+ if (response.nack !== undefined) {
382
+ post(response.nack)
383
+ }
384
+ }
385
+
386
+ function onMessage(event: MessageEvent) {
387
+ // Ignore messages from windows other than this host's iframe.
388
+ if (event.source !== iframe.contentWindow) {
389
+ return
390
+ }
391
+
392
+ // Ignore messages that do not come from the configured sandbox origin.
393
+ if (event.origin !== sandboxOrigin) {
394
+ return
395
+ }
396
+
397
+ const parsed = v.safeParse(sandboxToHostMessageSchema, event.data)
398
+ if (!parsed.success) {
399
+ onInvalidSandboxMessage(event.data, parsed.issues)
400
+ return
401
+ }
402
+
403
+ const message = parsed.output
404
+ emit("in", message)
405
+
406
+ if (
407
+ message.type !== "connect" &&
408
+ message.type !== "initResult" &&
409
+ initializationController.status !== "success"
410
+ ) {
411
+ // The SDK reports resize as soon as it renders, before the
412
+ // handshake completes, so an early resize is routine.
413
+ if (message.type !== "resize") {
414
+ emit("warn", {
415
+ message: `Ignored ${message.type} before initialization completed`,
416
+ payload: message,
417
+ })
418
+ }
419
+ return
420
+ }
421
+
422
+ switch (message.type) {
423
+ case "connect":
424
+ initializationController.handleConnect(message)
425
+ return
426
+ case "initResult":
427
+ initializationController.handleInitResult(message)
428
+ return
429
+ case "queryDataSource":
430
+ void handleQuery(message)
431
+ return
432
+ case "createPage":
433
+ void handleCreatePage(message)
434
+ return
435
+ case "getPage":
436
+ void handleGetPage(message)
437
+ return
438
+ case "getUser":
439
+ void handleGetUser(message)
440
+ return
441
+ case "listUsers":
442
+ void handleListUsers(message)
443
+ return
444
+ case "updatePage":
445
+ void handleUpdatePage(message)
446
+ return
447
+ case "resize":
448
+ handlers.resize?.(message)
449
+ return
450
+ case "invalidHostMessage":
451
+ return
452
+ default:
453
+ unreachable(message)
454
+ }
455
+ }
456
+
457
+ window.addEventListener("message", onMessage)
458
+ iframe.addEventListener("load", initializationController.onIframeLoad)
459
+
460
+ return {
461
+ stop() {
462
+ window.removeEventListener("message", onMessage)
463
+ iframe.removeEventListener("load", initializationController.onIframeLoad)
464
+ initializationController.stop()
465
+ querySubscriptions.clear()
466
+ },
467
+ post,
468
+ setTheme(nextTheme) {
469
+ theme = nextTheme
470
+ if (initializationController.status !== "success") {
471
+ if (initializationController.status === "waitingForInitResult") {
472
+ pendingThemeChange = true
473
+ }
474
+ return
475
+ }
476
+ const message: ThemeChangedMessage = { type: "themeChanged", theme }
477
+ post(message)
478
+ },
479
+ setContrastMode(nextContrastMode) {
480
+ contrastMode = nextContrastMode
481
+ if (initializationController.status !== "success") {
482
+ if (initializationController.status === "waitingForInitResult") {
483
+ pendingContrastModeChange = true
484
+ }
485
+ return
486
+ }
487
+ const message: ContrastModeChangedMessage = {
488
+ type: "contrastModeChanged",
489
+ contrastMode,
490
+ }
491
+ post(message)
492
+ },
493
+ setParent(nextParent) {
494
+ parent = nextParent
495
+ if (initializationController.status !== "success") {
496
+ if (initializationController.status === "waitingForInitResult") {
497
+ pendingParentChange = true
498
+ }
499
+ return
500
+ }
501
+ const message: ParentChangedMessage = { type: "parentChanged", parent }
502
+ post(message)
503
+ },
504
+ setPage(nextPage) {
505
+ page = nextPage
506
+ if (initializationController.status !== "success") {
507
+ if (initializationController.status === "waitingForInitResult") {
508
+ pendingPageChange = true
509
+ }
510
+ return
511
+ }
512
+ const message: PageChangedMessage = { type: "pageChanged", page }
513
+ post(message)
514
+ },
515
+ setDataSources(nextDataSources) {
516
+ if (
517
+ initializationController.hasConnectedSandbox &&
518
+ getBindingErrorCode(manifest, nextDataSources) !== undefined
519
+ ) {
520
+ emit("warn", {
521
+ message: "Ignored invalid data source bindings update",
522
+ payload: nextDataSources,
523
+ })
524
+ return
525
+ }
526
+ dataSources = nextDataSources
527
+ activeDataSources = nextDataSources
528
+ if (initializationController.status !== "success") {
529
+ if (initializationController.status === "waitingForInitResult") {
530
+ pendingDataSourcesChange = nextDataSources
531
+ }
532
+ return
533
+ }
534
+ const message: DataSourcesChangedMessage = {
535
+ type: "dataSourcesChanged",
536
+ dataSources: nextDataSources,
537
+ }
538
+ post(message)
539
+ },
540
+ setCurrentUser(nextCurrentUser) {
541
+ currentUser = nextCurrentUser
542
+ if (initializationController.status !== "success") {
543
+ if (initializationController.status === "waitingForInitResult") {
544
+ pendingCurrentUserChange = true
545
+ }
546
+ return
547
+ }
548
+ post({ type: "currentUserChanged", currentUser })
549
+ },
550
+ refreshQuery({ dataSourceId }) {
551
+ if (initializationController.status !== "success") {
552
+ return
553
+ }
554
+ const refreshes = querySubscriptions.refresh(dataSourceId)
555
+ if (refreshes.length === 0) {
556
+ emit("warn", `No active query exists for data source "${dataSourceId}"`)
557
+ return
558
+ }
559
+ for (const refresh of refreshes) {
560
+ void handleQuery(refresh.message, refresh.token)
561
+ }
562
+ },
563
+ }
564
+ }
565
+
566
+ function getCollectionSchema(
567
+ dataSources: CustomBlockHostDataSourcesPayload | undefined,
568
+ dataSourceId: NotionDataSourceId,
569
+ ) {
570
+ return Object.values(dataSources?.bindings ?? {}).find(
571
+ binding => binding.collectionPointer?.id === dataSourceId,
572
+ )?.collectionSchema
573
+ }
574
+
575
+ function getBindingErrorCode(
576
+ manifest: CustomBlockManifest,
577
+ dataSources: CustomBlockHostDataSourcesPayload,
578
+ ): CustomBlockInitErrorCode | undefined {
579
+ for (const [dataSourceKey, manifestDataSource] of Object.entries(
580
+ manifest.dataSources,
581
+ )) {
582
+ const binding = dataSources.bindings[dataSourceKey]
583
+ if (
584
+ binding?.collectionPointer === undefined ||
585
+ binding.collectionSchema === undefined
586
+ ) {
587
+ return "missing_data_source_binding"
588
+ }
589
+ for (const [propertyKey, manifestProperty] of Object.entries(
590
+ manifestDataSource.properties ?? {},
591
+ )) {
592
+ const propertyId = binding.propertyIdsByKey?.[propertyKey]
593
+ if (propertyId === undefined) {
594
+ return "missing_property_binding"
595
+ }
596
+ if (
597
+ binding.collectionSchema.propertiesById[propertyId]?.type !==
598
+ manifestProperty.type
599
+ ) {
600
+ return "invalid_property_binding"
601
+ }
602
+ }
603
+ }
604
+ return undefined
605
+ }
package/src/index.ts ADDED
@@ -0,0 +1,25 @@
1
+ export {
2
+ type AutoBindableProperty,
3
+ getAutoBoundPropertyIdsByKey,
4
+ } from "./autoBindProperties.js"
5
+ export {
6
+ type CustomBlockHostHandle,
7
+ type CustomBlockHostOptions,
8
+ createCustomBlockHost,
9
+ } from "./createCustomBlockHost.js"
10
+ export type {
11
+ ConnectSuccessMessage,
12
+ CustomBlockHostDataSourcesPayload,
13
+ CustomBlockHostInitialDataSources,
14
+ CustomBlockHostInitialState,
15
+ } from "./lifecycle/types.js"
16
+ export type {
17
+ CustomBlockHostCreatePageResult,
18
+ CustomBlockHostGetPageResult,
19
+ CustomBlockHostGetUserResult,
20
+ CustomBlockHostHandlers,
21
+ CustomBlockHostListUsersResult,
22
+ CustomBlockHostLogDirection,
23
+ CustomBlockHostUpdatePageResult,
24
+ } from "./messages/types.js"
25
+ export type { CustomBlockHostQueryDataSourceResult } from "./queries/types.js"