@notionhq/custom-blocks-host 0.3.11 → 0.4.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.
- package/LICENSE +21 -0
- package/README.md +143 -236
- package/THIRD_PARTY_LICENSES.md +9 -0
- package/dist/autoBindProperties.d.ts +5 -2
- package/dist/autoBindProperties.d.ts.map +1 -1
- package/dist/autoBindProperties.js +5 -2
- package/dist/browser.js +1 -1
- package/dist/compatibility/addMetadataReadSchemasForProtocol3To4.d.ts +1215 -0
- package/dist/compatibility/addMetadataReadSchemasForProtocol3To4.d.ts.map +1 -0
- package/dist/compatibility/addMetadataReadSchemasForProtocol3To4.js +27 -0
- package/dist/compatibility/normalizeHostToSandboxMessage.d.ts +8 -0
- package/dist/compatibility/normalizeHostToSandboxMessage.d.ts.map +1 -0
- package/dist/compatibility/normalizeHostToSandboxMessage.js +39 -0
- package/dist/compatibility/normalizePageResultsForProtocol3To5.d.ts +11 -0
- package/dist/compatibility/normalizePageResultsForProtocol3To5.d.ts.map +1 -0
- package/dist/compatibility/normalizePageResultsForProtocol3To5.js +18 -0
- package/dist/compatibility/normalizePageResultsForProtocol6.d.ts +9 -0
- package/dist/compatibility/normalizePageResultsForProtocol6.d.ts.map +1 -0
- package/dist/compatibility/normalizePageResultsForProtocol6.js +18 -0
- package/dist/compatibility/normalizeSandboxToHostMessage.d.ts +1099 -0
- package/dist/compatibility/normalizeSandboxToHostMessage.d.ts.map +1 -0
- package/dist/compatibility/normalizeSandboxToHostMessage.js +9 -0
- package/dist/compatibility/pageForProtocol3To5.d.ts +159 -0
- package/dist/compatibility/pageForProtocol3To5.d.ts.map +1 -0
- package/dist/{lifecycle/legacyPage.js → compatibility/pageForProtocol3To5.js} +50 -9
- package/dist/compatibility/pageResult.d.ts +11 -0
- package/dist/compatibility/pageResult.d.ts.map +1 -0
- package/dist/compatibility/pageWithNullTextFallbackProperties.d.ts +9 -0
- package/dist/compatibility/pageWithNullTextFallbackProperties.d.ts.map +1 -0
- package/dist/compatibility/pageWithNullTextFallbackProperties.js +46 -0
- package/dist/createCustomBlockHost.d.ts +18 -19
- package/dist/createCustomBlockHost.d.ts.map +1 -1
- package/dist/createCustomBlockHost.js +103 -38
- package/dist/index.d.ts +2 -19
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -2
- package/dist/{localBlockUrl.d.ts → isAllowedLocalPreviewUrl.d.ts} +2 -2
- package/dist/isAllowedLocalPreviewUrl.d.ts.map +1 -0
- package/dist/{localBlockUrl.js → isAllowedLocalPreviewUrl.js} +3 -7
- package/dist/lifecycle/initErrors.d.ts +1 -0
- package/dist/lifecycle/initErrors.d.ts.map +1 -1
- package/dist/lifecycle/initErrors.js +7 -0
- package/dist/lifecycle/initializationController.d.ts +6 -4
- package/dist/lifecycle/initializationController.d.ts.map +1 -1
- package/dist/lifecycle/initializationController.js +24 -15
- package/dist/lifecycle/resolveManifest.d.ts +13 -0
- package/dist/lifecycle/resolveManifest.d.ts.map +1 -0
- package/dist/lifecycle/resolveManifest.js +21 -0
- package/dist/lifecycle/types.d.ts +1637 -17
- package/dist/lifecycle/types.d.ts.map +1 -1
- package/dist/lifecycle/types.js +18 -0
- package/dist/messages/sandboxMessageRouter.d.ts.map +1 -1
- package/dist/messages/sandboxMessageRouter.js +2 -2
- package/dist/protocol/dataSources/dataSourcePage.d.ts +3 -4
- package/dist/protocol/dataSources/dataSourcePage.js +2 -3
- package/dist/protocol/index.d.ts +1 -0
- package/dist/protocol/index.js +1 -0
- package/dist/protocol/isLocalBlockServerUrl.d.ts +2 -0
- package/dist/protocol/isLocalBlockServerUrl.js +9 -0
- package/dist/protocol/messages/connect.d.ts +2 -2
- package/dist/protocol/messages/connect.js +2 -2
- package/dist/protocol/messages/createPageResult.d.ts +2 -2
- package/dist/protocol/messages/createPageResult.js +3 -3
- package/dist/protocol/messages/getPage.d.ts +2 -2
- package/dist/protocol/messages/getPage.js +3 -3
- package/dist/protocol/messages/hostToSandbox.d.ts +3 -3
- package/dist/protocol/messages/hostToSandbox.js +10 -10
- package/dist/protocol/messages/init.d.ts +1 -1
- package/dist/protocol/messages/queryDataSource.d.ts +24 -0
- package/dist/protocol/messages/queryDataSource.js +2 -0
- package/dist/protocol/messages/queryDataSourceResult.d.ts +3 -3
- package/dist/protocol/messages/queryDataSourceResult.js +6 -3
- package/dist/protocol/messages/sandboxToHost.d.ts +8 -0
- package/dist/protocol/messages/updatePageResult.d.ts +2 -2
- package/dist/protocol/messages/updatePageResult.js +3 -3
- package/dist/protocol/pages/page.d.ts +2 -2
- package/dist/protocol/pages/page.js +1 -1
- package/dist/protocol/protocolVersion.d.ts +2 -2
- package/dist/protocol/protocolVersion.js +2 -2
- package/dist/protocol.d.ts +9 -0
- package/dist/protocol.d.ts.map +1 -0
- package/dist/protocol.js +8 -0
- package/docs/bridge/changelog.md +67 -0
- package/docs/bridge/protocol.md +189 -0
- package/docs/bridge/versioning.md +38 -0
- package/docs/compatibility.md +68 -0
- package/docs/handlers.md +199 -0
- package/docs/lifecycle.md +117 -0
- package/package.json +10 -2
- package/src/autoBindProperties.ts +5 -2
- package/src/compatibility/addMetadataReadSchemasForProtocol3To4.ts +36 -0
- package/src/compatibility/normalizeHostToSandboxMessage.ts +54 -0
- package/src/compatibility/normalizePageResultsForProtocol3To5.ts +28 -0
- package/src/compatibility/normalizePageResultsForProtocol6.ts +23 -0
- package/src/compatibility/normalizeSandboxToHostMessage.ts +10 -0
- package/src/{lifecycle/legacyPage.ts → compatibility/pageForProtocol3To5.ts} +52 -11
- package/src/compatibility/pageResult.ts +18 -0
- package/src/compatibility/pageWithNullTextFallbackProperties.ts +53 -0
- package/src/createCustomBlockHost.ts +152 -62
- package/src/index.ts +4 -51
- package/src/{localBlockUrl.ts → isAllowedLocalPreviewUrl.ts} +4 -7
- package/src/lifecycle/initErrors.ts +9 -0
- package/src/lifecycle/initializationController.ts +34 -19
- package/src/lifecycle/resolveManifest.ts +31 -0
- package/src/lifecycle/types.ts +84 -16
- package/src/messages/sandboxMessageRouter.ts +2 -2
- package/src/protocol.ts +8 -0
- package/dist/lifecycle/legacyPage.d.ts +0 -70
- package/dist/lifecycle/legacyPage.d.ts.map +0 -1
- package/dist/lifecycle/loadCustomBlockManifest.d.ts +0 -16
- package/dist/lifecycle/loadCustomBlockManifest.d.ts.map +0 -1
- package/dist/lifecycle/loadCustomBlockManifest.js +0 -65
- package/dist/lifecycle/manifestOptions.d.ts +0 -17
- package/dist/lifecycle/manifestOptions.d.ts.map +0 -1
- package/dist/lifecycle/normalizeHostToSandboxMessage.d.ts +0 -6
- package/dist/lifecycle/normalizeHostToSandboxMessage.d.ts.map +0 -1
- package/dist/lifecycle/normalizeHostToSandboxMessage.js +0 -14
- package/dist/lifecycle/normalizePage.d.ts +0 -7
- package/dist/lifecycle/normalizePage.d.ts.map +0 -1
- package/dist/lifecycle/normalizePage.js +0 -47
- package/dist/localBlockUrl.d.ts.map +0 -1
- package/src/lifecycle/loadCustomBlockManifest.ts +0 -89
- package/src/lifecycle/manifestOptions.ts +0 -19
- package/src/lifecycle/normalizeHostToSandboxMessage.ts +0 -27
- package/src/lifecycle/normalizePage.ts +0 -71
- /package/dist/{lifecycle/manifestOptions.js → compatibility/pageResult.js} +0 -0
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
} from "@notionhq/custom-blocks-protocol/messages/hostToSandbox.js"
|
|
11
11
|
import {
|
|
12
12
|
type CustomBlockInitErrorInfo,
|
|
13
|
+
CustomBlockInitializationError,
|
|
13
14
|
initMessageSchema,
|
|
14
15
|
} from "@notionhq/custom-blocks-protocol/messages/init.js"
|
|
15
16
|
import type { InvalidSandboxMessage } from "@notionhq/custom-blocks-protocol/messages/invalidSandboxMessage.js"
|
|
@@ -30,25 +31,34 @@ import type { notionThemeSchema } from "@notionhq/custom-blocks-protocol/theme.j
|
|
|
30
31
|
import type { notionUserSchema } from "@notionhq/custom-blocks-protocol/users/user.js"
|
|
31
32
|
import type { InferInput } from "valibot"
|
|
32
33
|
import * as v from "valibot"
|
|
34
|
+
import { normalizeHostToSandboxMessage } from "./compatibility/normalizeHostToSandboxMessage.js"
|
|
35
|
+
import { isAllowedLocalPreviewUrl } from "./isAllowedLocalPreviewUrl.js"
|
|
33
36
|
import {
|
|
34
37
|
contextUnavailableError,
|
|
38
|
+
invalidLocalPreviewUrlError,
|
|
35
39
|
invalidPropertyBindingError,
|
|
36
40
|
missingDataSourceBindingError,
|
|
37
41
|
missingPropertyBindingError,
|
|
38
42
|
unknownInitializationError,
|
|
39
43
|
} from "./lifecycle/initErrors.js"
|
|
40
44
|
import { createInitializationController } from "./lifecycle/initializationController.js"
|
|
41
|
-
import {
|
|
42
|
-
import type { CustomBlockHostManifestOptions } from "./lifecycle/manifestOptions.js"
|
|
43
|
-
import { normalizeHostToSandboxMessage } from "./lifecycle/normalizeHostToSandboxMessage.js"
|
|
45
|
+
import { resolveManifest } from "./lifecycle/resolveManifest.js"
|
|
44
46
|
import type {
|
|
45
47
|
CustomBlockHostDataSourcesPayload,
|
|
46
48
|
CustomBlockHostInitialState,
|
|
47
49
|
CustomBlockHostInitialStateResult,
|
|
50
|
+
CustomBlockHostManifestResult,
|
|
48
51
|
CustomBlockHostNormalizedDataSourcesPayload,
|
|
52
|
+
CustomBlockHostNormalizedManifestResult,
|
|
49
53
|
CustomBlockHostPrepareInitialState,
|
|
54
|
+
CustomBlockHostStatus,
|
|
55
|
+
CustomBlockManifestSource,
|
|
56
|
+
} from "./lifecycle/types.js"
|
|
57
|
+
import {
|
|
58
|
+
customBlockHostInitialStateSchema,
|
|
59
|
+
customBlockHostManifestResultSchema,
|
|
60
|
+
customBlockHostPreparedStateResultSchema,
|
|
50
61
|
} from "./lifecycle/types.js"
|
|
51
|
-
import { customBlockHostInitialStateSchema } from "./lifecycle/types.js"
|
|
52
62
|
import { parseExternalUrl } from "./messages/parseExternalUrl.js"
|
|
53
63
|
import { createSandboxMessageRouter } from "./messages/sandboxMessageRouter.js"
|
|
54
64
|
import type {
|
|
@@ -70,9 +80,20 @@ import { unreachable } from "./utils.js"
|
|
|
70
80
|
// Earlier SDKs cannot represent this context, so the host reports context_unavailable.
|
|
71
81
|
const BRIDGE_PROTOCOL_VERSION_INTRODUCING_UNAVAILABLE_PAGE = 5
|
|
72
82
|
|
|
73
|
-
export type CustomBlockHostOptions =
|
|
83
|
+
export type CustomBlockHostOptions = {
|
|
84
|
+
manifestSource: CustomBlockManifestSource
|
|
85
|
+
/**
|
|
86
|
+
* Returns success with a persisted manifest or an explicit loading error, never null or
|
|
87
|
+
* undefined. The host validates the result and applies manifest defaults. It calls this
|
|
88
|
+
* when created, regardless of manifestSource. A returned error prevents initialization
|
|
89
|
+
* only when manifestSource is "host".
|
|
90
|
+
*/
|
|
91
|
+
getPersistedManifest: (args: {
|
|
92
|
+
signal: AbortSignal
|
|
93
|
+
}) => Promise<CustomBlockHostManifestResult>
|
|
74
94
|
iframe: HTMLIFrameElement
|
|
75
|
-
|
|
95
|
+
/** Full block URL. Its origin is used to authenticate bridge messages. */
|
|
96
|
+
sandboxUrl: string
|
|
76
97
|
/**
|
|
77
98
|
* Minimum sandbox protocol version to accept. Defaults to the host runtime
|
|
78
99
|
* minimum. Higher values retire older SDKs; lower values cannot enable
|
|
@@ -82,22 +103,12 @@ export type CustomBlockHostOptions = CustomBlockHostManifestOptions & {
|
|
|
82
103
|
noConnectTimeoutMs?: number
|
|
83
104
|
noInitResultTimeoutMs?: number
|
|
84
105
|
handlers: CustomBlockHostHandlers
|
|
85
|
-
/**
|
|
86
|
-
|
|
87
|
-
manifest: CustomBlockManifest
|
|
88
|
-
initialHeight?: number
|
|
89
|
-
}) => void
|
|
90
|
-
/**
|
|
91
|
-
* Reports a handshake failure, timeout, unavailable page context, or iframe reload.
|
|
92
|
-
* Iframe reloads and unavailable page context for older SDKs stop the host
|
|
93
|
-
* before this callback runs.
|
|
94
|
-
* This callback can run after initialization succeeds.
|
|
95
|
-
*/
|
|
96
|
-
onConnectionFailure: ((error: CustomBlockHostFailure) => void) | undefined
|
|
106
|
+
/** Reports accepted lifecycle transitions, starting synchronously with waitingForConnect. */
|
|
107
|
+
onStatusChange: (state: CustomBlockHostStatus) => void
|
|
97
108
|
/** Releases adapter resources when a query ends, including replacement and terminal errors. */
|
|
98
109
|
onQueryUnsubscribed: ((subscriptionId: string) => void) | undefined
|
|
99
110
|
onLog?: (direction: CustomBlockHostLogDirection, payload: unknown) => void
|
|
100
|
-
/** Runs once,
|
|
111
|
+
/** Runs once, after the selected manifest is validated. */
|
|
101
112
|
prepareInitialState: CustomBlockHostPrepareInitialState
|
|
102
113
|
initialStateTimeoutMs?: number
|
|
103
114
|
}
|
|
@@ -130,16 +141,16 @@ export function createCustomBlockHost(
|
|
|
130
141
|
iframe,
|
|
131
142
|
prepareInitialState: prepareAdapterState,
|
|
132
143
|
handlers,
|
|
133
|
-
|
|
134
|
-
onConnectionFailure,
|
|
144
|
+
onStatusChange,
|
|
135
145
|
onLog,
|
|
136
146
|
onQueryUnsubscribed,
|
|
137
|
-
sandboxOrigin,
|
|
138
147
|
minBridgeProtocolVersion: configuredMinBridgeProtocolVersion,
|
|
139
148
|
noConnectTimeoutMs,
|
|
140
149
|
initialStateTimeoutMs,
|
|
141
150
|
noInitResultTimeoutMs,
|
|
142
151
|
} = options
|
|
152
|
+
const sandboxUrl = parseSandboxUrl(options.sandboxUrl, options.manifestSource)
|
|
153
|
+
const sandboxOrigin = sandboxUrl.origin
|
|
143
154
|
// Callers can retire older SDKs, but cannot restore support for protocols
|
|
144
155
|
// the runtime no longer implements. Omission uses the runtime minimum.
|
|
145
156
|
const minBridgeProtocolVersion = Math.max(
|
|
@@ -175,7 +186,7 @@ export function createCustomBlockHost(
|
|
|
175
186
|
case "preparingInit":
|
|
176
187
|
case "waitingForInitResult":
|
|
177
188
|
case "initialized":
|
|
178
|
-
case "
|
|
189
|
+
case "failed":
|
|
179
190
|
break
|
|
180
191
|
// A stopped host must not send messages, including pending responses.
|
|
181
192
|
// Return from the function to skip delivery below.
|
|
@@ -210,7 +221,7 @@ export function createCustomBlockHost(
|
|
|
210
221
|
case "waitingForConnect":
|
|
211
222
|
case "preparingInit":
|
|
212
223
|
case "waitingForInitResult":
|
|
213
|
-
case "
|
|
224
|
+
case "failed":
|
|
214
225
|
case "stopped":
|
|
215
226
|
return
|
|
216
227
|
default:
|
|
@@ -304,7 +315,7 @@ export function createCustomBlockHost(
|
|
|
304
315
|
function updateHostState(update: HostStateUpdates) {
|
|
305
316
|
if (
|
|
306
317
|
initializationController.status === "stopped" ||
|
|
307
|
-
initializationController.status === "
|
|
318
|
+
initializationController.status === "failed"
|
|
308
319
|
) {
|
|
309
320
|
return
|
|
310
321
|
}
|
|
@@ -419,15 +430,30 @@ export function createCustomBlockHost(
|
|
|
419
430
|
}
|
|
420
431
|
|
|
421
432
|
const initializationController = createInitializationController({
|
|
433
|
+
manifestSource: options.manifestSource,
|
|
422
434
|
async prepareInit(connect) {
|
|
435
|
+
const connectManifest =
|
|
436
|
+
connect.status === "success" ? connect.manifest : undefined
|
|
423
437
|
sandboxBridgeProtocolVersion = connect.bridgeProtocolVersion
|
|
424
|
-
|
|
438
|
+
onStatusChange({
|
|
439
|
+
status: "preparingInit",
|
|
440
|
+
localManifest: isAllowedLocalPreviewUrl({
|
|
441
|
+
url: sandboxUrl,
|
|
442
|
+
hostOrigin: window.location.origin,
|
|
443
|
+
})
|
|
444
|
+
? connectManifest
|
|
445
|
+
: undefined,
|
|
446
|
+
bridgeProtocolVersion: connect.bridgeProtocolVersion,
|
|
447
|
+
sdkVersion: connect.sdkVersion,
|
|
448
|
+
})
|
|
449
|
+
const result = await (preparation ?? prepare(connectManifest))
|
|
425
450
|
if (initializationController.status !== "preparingInit") {
|
|
426
451
|
return result
|
|
427
452
|
}
|
|
428
453
|
return finalizeInitialState(result)
|
|
429
454
|
},
|
|
430
455
|
sendInitMessage,
|
|
456
|
+
onInitSent: () => onStatusChange({ status: "waitingForInitResult" }),
|
|
431
457
|
warn: payload => emit("warn", payload),
|
|
432
458
|
flushPendingHostStateChanges,
|
|
433
459
|
minBridgeProtocolVersion,
|
|
@@ -437,12 +463,16 @@ export function createCustomBlockHost(
|
|
|
437
463
|
stopHostWithFailure: stopWithFailure,
|
|
438
464
|
onInitResult(message) {
|
|
439
465
|
if (message.status === "success" && manifest !== undefined) {
|
|
440
|
-
|
|
466
|
+
onStatusChange({
|
|
467
|
+
status: "initialized",
|
|
468
|
+
resolvedManifest: manifest,
|
|
469
|
+
initialHeight: message.initialHeight,
|
|
470
|
+
})
|
|
441
471
|
}
|
|
442
472
|
},
|
|
443
473
|
onConnectionFailure: failure => {
|
|
444
474
|
preparationController.abort()
|
|
445
|
-
|
|
475
|
+
onStatusChange({ status: "failed", error: failure })
|
|
446
476
|
},
|
|
447
477
|
})
|
|
448
478
|
|
|
@@ -484,46 +514,64 @@ export function createCustomBlockHost(
|
|
|
484
514
|
return { status: "success", initialState }
|
|
485
515
|
}
|
|
486
516
|
|
|
487
|
-
//
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
})
|
|
495
|
-
}
|
|
496
|
-
return { status: "error", error: unknownInitializationError() }
|
|
497
|
-
},
|
|
498
|
-
)
|
|
517
|
+
// Neither manifest source waits for the other. Observe rejections immediately,
|
|
518
|
+
// including failures from a host manifest that is not selected.
|
|
519
|
+
const hostManifest = readHostManifest().catch(handlePreparationError)
|
|
520
|
+
const preparation =
|
|
521
|
+
options.manifestSource === "host"
|
|
522
|
+
? prepare(undefined).catch(handlePreparationError)
|
|
523
|
+
: undefined
|
|
499
524
|
|
|
500
|
-
async function
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
sandboxOrigin,
|
|
525
|
+
async function readHostManifest(): Promise<CustomBlockHostNormalizedManifestResult> {
|
|
526
|
+
preparationController.signal.throwIfAborted()
|
|
527
|
+
const result = await options.getPersistedManifest({
|
|
504
528
|
signal: preparationController.signal,
|
|
505
|
-
onError: error => {
|
|
506
|
-
emit("warn", {
|
|
507
|
-
message: "Failed to load custom block manifest",
|
|
508
|
-
error,
|
|
509
|
-
})
|
|
510
|
-
},
|
|
511
529
|
})
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
530
|
+
preparationController.signal.throwIfAborted()
|
|
531
|
+
return v.parse(customBlockHostManifestResultSchema, result)
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
function handlePreparationError(error: unknown): {
|
|
535
|
+
status: "error"
|
|
536
|
+
error: CustomBlockInitErrorInfo
|
|
537
|
+
} {
|
|
538
|
+
if (!preparationController.signal.aborted) {
|
|
539
|
+
emit("warn", {
|
|
540
|
+
message: "Failed to prepare custom block initialization",
|
|
541
|
+
error,
|
|
542
|
+
})
|
|
517
543
|
}
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
544
|
+
return { status: "error", error: unknownInitializationError() }
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
async function prepare(
|
|
548
|
+
connectManifest: CustomBlockManifest | undefined,
|
|
549
|
+
): Promise<CustomBlockHostInitialStateResult> {
|
|
550
|
+
preparationController.signal.throwIfAborted()
|
|
551
|
+
const manifestResult = await resolveManifest({
|
|
552
|
+
manifestSource: options.manifestSource,
|
|
553
|
+
hostManifest,
|
|
554
|
+
connectManifest,
|
|
521
555
|
})
|
|
556
|
+
preparationController.signal.throwIfAborted()
|
|
557
|
+
if (manifestResult.status === "error") {
|
|
558
|
+
return manifestResult
|
|
559
|
+
}
|
|
560
|
+
const result = v.parse(
|
|
561
|
+
customBlockHostPreparedStateResultSchema,
|
|
562
|
+
await prepareAdapterState({
|
|
563
|
+
manifest: manifestResult.manifest,
|
|
564
|
+
signal: preparationController.signal,
|
|
565
|
+
}),
|
|
566
|
+
)
|
|
522
567
|
return result.status === "error"
|
|
523
568
|
? result
|
|
524
569
|
: {
|
|
525
570
|
status: "success",
|
|
526
|
-
initialState: {
|
|
571
|
+
initialState: {
|
|
572
|
+
...result.initialState,
|
|
573
|
+
manifest: manifestResult.manifest,
|
|
574
|
+
},
|
|
527
575
|
}
|
|
528
576
|
}
|
|
529
577
|
|
|
@@ -552,7 +600,7 @@ export function createCustomBlockHost(
|
|
|
552
600
|
|
|
553
601
|
window.addEventListener("message", onMessage)
|
|
554
602
|
iframe.addEventListener("load", initializationController.onIframeLoad)
|
|
555
|
-
function
|
|
603
|
+
function releaseResources() {
|
|
556
604
|
navigationAbortController.abort()
|
|
557
605
|
preparationController.abort()
|
|
558
606
|
window.removeEventListener("message", onMessage)
|
|
@@ -561,12 +609,29 @@ export function createCustomBlockHost(
|
|
|
561
609
|
querySubscriptions.unsubscribeAll()
|
|
562
610
|
}
|
|
563
611
|
|
|
612
|
+
function stop() {
|
|
613
|
+
const previousStatus = initializationController.status
|
|
614
|
+
releaseResources()
|
|
615
|
+
if (previousStatus !== "stopped" && previousStatus !== "failed") {
|
|
616
|
+
onStatusChange({ status: "stopped" })
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
|
|
564
620
|
function stopWithFailure(error: CustomBlockHostFailure) {
|
|
565
|
-
|
|
621
|
+
if (initializationController.status === "stopped") {
|
|
622
|
+
return
|
|
623
|
+
}
|
|
624
|
+
if (initializationController.status === "failed") {
|
|
625
|
+
releaseResources()
|
|
626
|
+
return
|
|
627
|
+
}
|
|
628
|
+
releaseResources()
|
|
566
629
|
emit("warn", error)
|
|
567
|
-
|
|
630
|
+
onStatusChange({ status: "failed", error })
|
|
568
631
|
}
|
|
569
632
|
|
|
633
|
+
onStatusChange({ status: "waitingForConnect" })
|
|
634
|
+
|
|
570
635
|
return {
|
|
571
636
|
stop,
|
|
572
637
|
setTheme(theme) {
|
|
@@ -642,6 +707,31 @@ export function createCustomBlockHost(
|
|
|
642
707
|
}
|
|
643
708
|
}
|
|
644
709
|
|
|
710
|
+
function parseSandboxUrl(
|
|
711
|
+
url: string,
|
|
712
|
+
manifestSource: CustomBlockManifestSource,
|
|
713
|
+
): URL {
|
|
714
|
+
let parsed: URL
|
|
715
|
+
try {
|
|
716
|
+
parsed = new URL(url)
|
|
717
|
+
} catch (error) {
|
|
718
|
+
if (manifestSource === "host") {
|
|
719
|
+
throw error
|
|
720
|
+
}
|
|
721
|
+
throw new CustomBlockInitializationError(invalidLocalPreviewUrlError())
|
|
722
|
+
}
|
|
723
|
+
if (
|
|
724
|
+
manifestSource === "sandbox" &&
|
|
725
|
+
!isAllowedLocalPreviewUrl({
|
|
726
|
+
url: parsed,
|
|
727
|
+
hostOrigin: window.location.origin,
|
|
728
|
+
})
|
|
729
|
+
) {
|
|
730
|
+
throw new CustomBlockInitializationError(invalidLocalPreviewUrlError())
|
|
731
|
+
}
|
|
732
|
+
return parsed
|
|
733
|
+
}
|
|
734
|
+
|
|
645
735
|
function getCollectionSchema(
|
|
646
736
|
dataSources: CustomBlockHostNormalizedDataSourcesPayload | undefined,
|
|
647
737
|
dataSourceId: NotionDataSourceId,
|
package/src/index.ts
CHANGED
|
@@ -1,51 +1,3 @@
|
|
|
1
|
-
export type { LegacyNotionDataSourcePageBridge } from "@notionhq/custom-blocks-protocol/dataSources/dataSourcePage.js"
|
|
2
|
-
export type { NotionPropertySchemasById } from "@notionhq/custom-blocks-protocol/dataSources/propertySchema.js"
|
|
3
|
-
export type { CustomBlockErrorInfo } from "@notionhq/custom-blocks-protocol/errors.js"
|
|
4
|
-
export type { NotionPageId } from "@notionhq/custom-blocks-protocol/ids.js"
|
|
5
|
-
export type { CustomBlockManifest } from "@notionhq/custom-blocks-protocol/manifest.js"
|
|
6
|
-
export type { CreatePageMessage } from "@notionhq/custom-blocks-protocol/messages/createPage.js"
|
|
7
|
-
export type { GetPageMessage } from "@notionhq/custom-blocks-protocol/messages/getPage.js"
|
|
8
|
-
export type { GetUserMessage } from "@notionhq/custom-blocks-protocol/messages/getUser.js"
|
|
9
|
-
export type { ListUsersMessage } from "@notionhq/custom-blocks-protocol/messages/listUsers.js"
|
|
10
|
-
export type { OpenExternalUrlMessage } from "@notionhq/custom-blocks-protocol/messages/openExternalUrl.js"
|
|
11
|
-
export type { OpenPageMessage } from "@notionhq/custom-blocks-protocol/messages/openPage.js"
|
|
12
|
-
export type {
|
|
13
|
-
CustomBlockCheckboxFilterOperator,
|
|
14
|
-
CustomBlockContainsFilterOperator,
|
|
15
|
-
CustomBlockCreatedByFilterOperator,
|
|
16
|
-
CustomBlockDateFilterOperator,
|
|
17
|
-
CustomBlockNumberFilterOperator,
|
|
18
|
-
CustomBlockOptionFilterOperator,
|
|
19
|
-
CustomBlockPeopleFilterOperator,
|
|
20
|
-
CustomBlockPersonFilterValue,
|
|
21
|
-
CustomBlockRelationFilterOperator,
|
|
22
|
-
CustomBlockStatusFilterOperator,
|
|
23
|
-
CustomBlockStatusGroupFilterOperator,
|
|
24
|
-
CustomBlockStatusGroupName,
|
|
25
|
-
CustomBlockTextFilterOperator,
|
|
26
|
-
QueryDataSourceMessage,
|
|
27
|
-
ResolvedCustomBlockDataSourceFilter,
|
|
28
|
-
ResolvedCustomBlockDataSourcePropertyFilter,
|
|
29
|
-
ResolvedCustomBlockDataSourceSort,
|
|
30
|
-
} from "@notionhq/custom-blocks-protocol/messages/queryDataSource.js"
|
|
31
|
-
export type { ResizeMessage } from "@notionhq/custom-blocks-protocol/messages/resize.js"
|
|
32
|
-
export type { UpdatePageMessage } from "@notionhq/custom-blocks-protocol/messages/updatePage.js"
|
|
33
|
-
export type {
|
|
34
|
-
NotionDateInput,
|
|
35
|
-
NotionFileInput,
|
|
36
|
-
NotionPageCover,
|
|
37
|
-
NotionPageData,
|
|
38
|
-
NotionPageIcon,
|
|
39
|
-
NotionPagePropertyReadMap,
|
|
40
|
-
NotionPagePropertyValue,
|
|
41
|
-
NotionPagePropertyWriteMap,
|
|
42
|
-
NotionRichTextItem,
|
|
43
|
-
NotionSelectOptionInput,
|
|
44
|
-
} from "@notionhq/custom-blocks-protocol/pages/page.js"
|
|
45
|
-
export {
|
|
46
|
-
LATEST_BRIDGE_PROTOCOL_VERSION,
|
|
47
|
-
LEGACY_PROPERTY_SHAPE_PROTOCOL_VERSION,
|
|
48
|
-
} from "@notionhq/custom-blocks-protocol/protocolVersion.js"
|
|
49
1
|
export {
|
|
50
2
|
type AutoBindableProperty,
|
|
51
3
|
getAutoBoundPropertyIdsByKey,
|
|
@@ -61,16 +13,17 @@ export {
|
|
|
61
13
|
toNotionDateValue,
|
|
62
14
|
} from "./dateValue.js"
|
|
63
15
|
export { toNotionPageId } from "./ids.js"
|
|
64
|
-
export
|
|
16
|
+
export { isAllowedLocalPreviewUrl } from "./isAllowedLocalPreviewUrl.js"
|
|
65
17
|
export type {
|
|
66
|
-
ConnectSuccessMessage,
|
|
67
18
|
CustomBlockHostDataSourcesPayload,
|
|
68
19
|
CustomBlockHostInitialState,
|
|
69
20
|
CustomBlockHostInitialStateResult,
|
|
21
|
+
CustomBlockHostManifestResult,
|
|
70
22
|
CustomBlockHostPreparedStateResult,
|
|
71
23
|
CustomBlockHostPrepareInitialState,
|
|
24
|
+
CustomBlockHostStatus,
|
|
25
|
+
CustomBlockManifestSource,
|
|
72
26
|
} from "./lifecycle/types.js"
|
|
73
|
-
export { isLocalCustomBlockUrl } from "./localBlockUrl.js"
|
|
74
27
|
export type {
|
|
75
28
|
CreatePageMessageInput,
|
|
76
29
|
CustomBlockHostCreatePageResult,
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
+
import { isLocalBlockServerUrl } from "@notionhq/custom-blocks-protocol/isLocalBlockServerUrl.js"
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Checks whether a URL can serve a local custom block over HTTP or HTTPS.
|
|
3
5
|
* Accepts localhost, any .localhost subdomain, and 127.0.0.1 without credentials.
|
|
4
6
|
* Local refers to the block server, not the environment where the host runs.
|
|
5
7
|
* Requires the embedding page's origin and rejects URLs that share that origin.
|
|
6
8
|
*/
|
|
7
|
-
export function
|
|
9
|
+
export function isAllowedLocalPreviewUrl({
|
|
8
10
|
url,
|
|
9
11
|
hostOrigin,
|
|
10
12
|
}: {
|
|
@@ -15,11 +17,6 @@ export function isLocalCustomBlockUrl({
|
|
|
15
17
|
typeof hostOrigin === "string" &&
|
|
16
18
|
hostOrigin !== "" &&
|
|
17
19
|
url.origin !== hostOrigin &&
|
|
18
|
-
(url
|
|
19
|
-
(url.hostname === "localhost" ||
|
|
20
|
-
url.hostname === "127.0.0.1" ||
|
|
21
|
-
url.hostname.endsWith(".localhost")) &&
|
|
22
|
-
url.username === "" &&
|
|
23
|
-
url.password === ""
|
|
20
|
+
isLocalBlockServerUrl(url)
|
|
24
21
|
)
|
|
25
22
|
}
|
|
@@ -120,3 +120,12 @@ export function unknownInitializationError(): CustomBlockErrorInfo<"unknown_erro
|
|
|
120
120
|
isRetryable: false,
|
|
121
121
|
}
|
|
122
122
|
}
|
|
123
|
+
|
|
124
|
+
export function invalidLocalPreviewUrlError(): CustomBlockErrorInfo<"invalid_local_preview_url"> {
|
|
125
|
+
return {
|
|
126
|
+
code: "invalid_local_preview_url",
|
|
127
|
+
message:
|
|
128
|
+
"Local custom blocks require an HTTP(S) URL on localhost, a .localhost subdomain, or 127.0.0.1, without credentials and on a different origin from the host.",
|
|
129
|
+
isRetryable: false,
|
|
130
|
+
}
|
|
131
|
+
}
|
|
@@ -18,8 +18,8 @@ import {
|
|
|
18
18
|
} from "./initErrors.js"
|
|
19
19
|
import { isValidBridgeProtocolVersion } from "./protocolVersion.js"
|
|
20
20
|
import type {
|
|
21
|
-
ConnectSuccessMessage,
|
|
22
21
|
CustomBlockHostInitialStateResult,
|
|
22
|
+
CustomBlockManifestSource,
|
|
23
23
|
InitStatus,
|
|
24
24
|
} from "./types.js"
|
|
25
25
|
|
|
@@ -28,8 +28,9 @@ const DEFAULT_INITIAL_STATE_TIMEOUT_MS = 5_000
|
|
|
28
28
|
const DEFAULT_NO_INIT_RESULT_TIMEOUT_MS = 5_000
|
|
29
29
|
|
|
30
30
|
export type InitializationControllerOptions = {
|
|
31
|
+
manifestSource: CustomBlockManifestSource
|
|
31
32
|
prepareInit: (
|
|
32
|
-
connect:
|
|
33
|
+
connect: ConnectMessage,
|
|
33
34
|
) => Promise<CustomBlockHostInitialStateResult>
|
|
34
35
|
sendInitMessage: (message: InferInput<typeof initMessageSchema>) => void
|
|
35
36
|
warn: (payload: unknown) => void
|
|
@@ -39,8 +40,9 @@ export type InitializationControllerOptions = {
|
|
|
39
40
|
initialStateTimeoutMs?: number
|
|
40
41
|
noInitResultTimeoutMs?: number
|
|
41
42
|
stopHostWithFailure: (error: CustomBlockHostFailure) => void
|
|
42
|
-
onInitResult
|
|
43
|
-
|
|
43
|
+
onInitResult: (message: InitResultMessage) => void
|
|
44
|
+
onInitSent: () => void
|
|
45
|
+
onConnectionFailure: (error: CustomBlockHostFailure) => void
|
|
44
46
|
}
|
|
45
47
|
|
|
46
48
|
export type InitializationController = {
|
|
@@ -65,6 +67,7 @@ export function createInitializationController(
|
|
|
65
67
|
options: InitializationControllerOptions,
|
|
66
68
|
): InitializationController {
|
|
67
69
|
const {
|
|
70
|
+
manifestSource,
|
|
68
71
|
prepareInit,
|
|
69
72
|
sendInitMessage,
|
|
70
73
|
warn,
|
|
@@ -75,6 +78,7 @@ export function createInitializationController(
|
|
|
75
78
|
noInitResultTimeoutMs = DEFAULT_NO_INIT_RESULT_TIMEOUT_MS,
|
|
76
79
|
stopHostWithFailure,
|
|
77
80
|
onInitResult,
|
|
81
|
+
onInitSent,
|
|
78
82
|
onConnectionFailure,
|
|
79
83
|
} = options
|
|
80
84
|
|
|
@@ -120,13 +124,13 @@ export function createInitializationController(
|
|
|
120
124
|
break
|
|
121
125
|
case "waitingForInitResult":
|
|
122
126
|
case "initialized":
|
|
123
|
-
case "
|
|
127
|
+
case "failed":
|
|
124
128
|
case "stopped":
|
|
125
129
|
return
|
|
126
130
|
default:
|
|
127
131
|
return unreachable(initStatus)
|
|
128
132
|
}
|
|
129
|
-
initStatus = "
|
|
133
|
+
initStatus = "failed"
|
|
130
134
|
activeInitializationId = initializationId
|
|
131
135
|
clearNoConnectTimeout()
|
|
132
136
|
clearInitialStateTimeout()
|
|
@@ -136,10 +140,10 @@ export function createInitializationController(
|
|
|
136
140
|
status: "error",
|
|
137
141
|
error,
|
|
138
142
|
})
|
|
139
|
-
onConnectionFailure
|
|
143
|
+
onConnectionFailure({ ...error, phase, source })
|
|
140
144
|
}
|
|
141
145
|
|
|
142
|
-
async function prepareAndSendInit(connect:
|
|
146
|
+
async function prepareAndSendInit(connect: ConnectMessage) {
|
|
143
147
|
if (initStatus !== "waitingForConnect") {
|
|
144
148
|
return
|
|
145
149
|
}
|
|
@@ -194,13 +198,20 @@ export function createInitializationController(
|
|
|
194
198
|
dataSources: preparation.initialState.dataSources,
|
|
195
199
|
}
|
|
196
200
|
sendInitMessage(message)
|
|
201
|
+
if (initStatus !== "waitingForInitResult") {
|
|
202
|
+
return
|
|
203
|
+
}
|
|
204
|
+
onInitSent()
|
|
205
|
+
if (initStatus !== "waitingForInitResult") {
|
|
206
|
+
return
|
|
207
|
+
}
|
|
197
208
|
noInitResultTimeoutId = window.setTimeout(() => {
|
|
198
209
|
if (initStatus !== "waitingForInitResult") {
|
|
199
210
|
return
|
|
200
211
|
}
|
|
201
|
-
initStatus = "
|
|
212
|
+
initStatus = "failed"
|
|
202
213
|
const error = noInitResultError()
|
|
203
|
-
onConnectionFailure
|
|
214
|
+
onConnectionFailure({ ...error, phase: "initResult", source: "host" })
|
|
204
215
|
warn({
|
|
205
216
|
...error,
|
|
206
217
|
initializationId: connect.initializationId,
|
|
@@ -258,8 +269,12 @@ export function createInitializationController(
|
|
|
258
269
|
return
|
|
259
270
|
}
|
|
260
271
|
|
|
261
|
-
//
|
|
262
|
-
if (
|
|
272
|
+
// Invalid sandbox metadata does not prevent initialization from a host manifest.
|
|
273
|
+
if (
|
|
274
|
+
message.status === "error" &&
|
|
275
|
+
(manifestSource === "sandbox" ||
|
|
276
|
+
message.error.code !== "manifest_invalid")
|
|
277
|
+
) {
|
|
263
278
|
sendInitError({
|
|
264
279
|
initializationId: message.initializationId,
|
|
265
280
|
error: message.error,
|
|
@@ -322,9 +337,9 @@ export function createInitializationController(
|
|
|
322
337
|
}
|
|
323
338
|
clearNoConnectTimeout()
|
|
324
339
|
clearNoInitResultTimeout()
|
|
325
|
-
initStatus = "
|
|
340
|
+
initStatus = "failed"
|
|
326
341
|
warn(error)
|
|
327
|
-
onConnectionFailure
|
|
342
|
+
onConnectionFailure({ ...error, phase: "initResult", source: "host" })
|
|
328
343
|
}
|
|
329
344
|
|
|
330
345
|
function onIframeLoad() {
|
|
@@ -353,10 +368,10 @@ export function createInitializationController(
|
|
|
353
368
|
// Only do anything if the iframe loaded before the `connect` message was sent.
|
|
354
369
|
return
|
|
355
370
|
}
|
|
356
|
-
initStatus = "
|
|
371
|
+
initStatus = "failed"
|
|
357
372
|
const error = noConnectError()
|
|
358
373
|
warn(error)
|
|
359
|
-
onConnectionFailure
|
|
374
|
+
onConnectionFailure({ ...error, phase: "connect", source: "host" })
|
|
360
375
|
}, noConnectTimeoutMs)
|
|
361
376
|
}
|
|
362
377
|
|
|
@@ -372,17 +387,17 @@ export function createInitializationController(
|
|
|
372
387
|
return
|
|
373
388
|
}
|
|
374
389
|
clearNoInitResultTimeout()
|
|
375
|
-
initStatus = message.status === "success" ? "initialized" : "
|
|
390
|
+
initStatus = message.status === "success" ? "initialized" : "failed"
|
|
376
391
|
if (message.status === "success") {
|
|
377
392
|
flushPendingHostStateChanges()
|
|
378
393
|
} else {
|
|
379
|
-
onConnectionFailure
|
|
394
|
+
onConnectionFailure({
|
|
380
395
|
...message.error,
|
|
381
396
|
phase: "initResult",
|
|
382
397
|
source: "sandbox",
|
|
383
398
|
})
|
|
384
399
|
}
|
|
385
|
-
onInitResult
|
|
400
|
+
onInitResult(message)
|
|
386
401
|
}
|
|
387
402
|
|
|
388
403
|
function stop() {
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { CustomBlockManifest } from "@notionhq/custom-blocks-protocol/manifest.js"
|
|
2
|
+
import type {
|
|
3
|
+
CustomBlockHostNormalizedManifestResult,
|
|
4
|
+
CustomBlockManifestSource,
|
|
5
|
+
} from "./types.js"
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Select the host's manifest result or the sandbox's connect declaration. Both
|
|
9
|
+
* sources are already validated. Forward host errors unchanged, and report
|
|
10
|
+
* manifest_unavailable if the sandbox omits its declaration.
|
|
11
|
+
*/
|
|
12
|
+
export async function resolveManifest(args: {
|
|
13
|
+
manifestSource: CustomBlockManifestSource
|
|
14
|
+
hostManifest: Promise<CustomBlockHostNormalizedManifestResult>
|
|
15
|
+
connectManifest: CustomBlockManifest | undefined
|
|
16
|
+
}): Promise<CustomBlockHostNormalizedManifestResult> {
|
|
17
|
+
if (args.manifestSource === "host") {
|
|
18
|
+
return args.hostManifest
|
|
19
|
+
}
|
|
20
|
+
if (args.connectManifest !== undefined) {
|
|
21
|
+
return { status: "success", manifest: args.connectManifest }
|
|
22
|
+
}
|
|
23
|
+
return {
|
|
24
|
+
status: "error",
|
|
25
|
+
error: {
|
|
26
|
+
code: "manifest_unavailable",
|
|
27
|
+
message: "The sandbox did not provide a manifest in connect.",
|
|
28
|
+
isRetryable: false,
|
|
29
|
+
},
|
|
30
|
+
}
|
|
31
|
+
}
|