@storystick/preview 0.0.0-stage → 0.2.0-rc.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.
- package/LICENSE +112 -0
- package/PUBLIC-API.md +55 -0
- package/README.md +42 -2
- package/dist/.source-hash +1 -0
- package/dist/authoring/runtime-contract.d.ts +89 -0
- package/dist/authoring/runtime-contract.js +28 -0
- package/dist/channel-taxonomy.d.ts +17 -0
- package/dist/channel-taxonomy.js +46 -0
- package/dist/context-command.d.ts +18 -0
- package/dist/context-command.js +38 -0
- package/dist/dev/suspicious-codeword-detector.d.ts +15 -0
- package/dist/dev/suspicious-codeword-detector.js +220 -0
- package/dist/dev/suspicious-codeword-detector.noop.d.ts +3 -0
- package/dist/dev/suspicious-codeword-detector.noop.js +4 -0
- package/dist/framework/browser-context.d.ts +2 -0
- package/dist/framework/browser-context.js +2 -0
- package/dist/framework/generation-refresh.d.ts +23 -0
- package/dist/framework/generation-refresh.js +82 -0
- package/dist/framework/generation-signal.d.ts +8 -0
- package/dist/framework/generation-signal.js +17 -0
- package/dist/framework/projection-generation-watch.d.ts +5 -0
- package/dist/framework/projection-generation-watch.js +53 -0
- package/dist/framework/render-receipt.d.ts +8 -0
- package/dist/framework/render-receipt.js +10 -0
- package/dist/framework/runtime-adapter-store.d.ts +30 -0
- package/dist/framework/runtime-adapter-store.js +81 -0
- package/dist/grant-coordinator.d.ts +53 -0
- package/dist/grant-coordinator.js +329 -0
- package/dist/grant-lifecycle.d.ts +52 -0
- package/dist/grant-lifecycle.js +74 -0
- package/dist/identity/__test-adapter.d.ts +34 -0
- package/dist/identity/__test-adapter.js +59 -0
- package/dist/identity/browser-context-gate.d.ts +4 -0
- package/dist/identity/browser-context-gate.js +22 -0
- package/dist/identity/browser-context.d.ts +13 -0
- package/dist/identity/browser-context.js +22 -0
- package/dist/identity/carriers/postmessage.d.ts +84 -0
- package/dist/identity/carriers/postmessage.js +177 -0
- package/dist/identity/carriers/storage-state.d.ts +41 -0
- package/dist/identity/carriers/storage-state.js +99 -0
- package/dist/identity/carriers/storage.d.ts +39 -0
- package/dist/identity/carriers/storage.js +182 -0
- package/dist/identity/carriers/url.d.ts +19 -0
- package/dist/identity/carriers/url.js +38 -0
- package/dist/identity/identity-state-machine.d.ts +70 -0
- package/dist/identity/identity-state-machine.js +244 -0
- package/dist/identity/identity-types.d.ts +192 -0
- package/dist/identity/identity-types.js +22 -0
- package/dist/identity/index.d.ts +18 -0
- package/dist/identity/index.js +16 -0
- package/dist/identity/preview-cookie.d.ts +9 -0
- package/dist/identity/preview-cookie.js +12 -0
- package/dist/identity/preview-draft.d.ts +6 -0
- package/dist/identity/preview-draft.js +25 -0
- package/dist/identity/preview-identity.d.ts +28 -0
- package/dist/identity/preview-identity.js +84 -0
- package/dist/identity/preview-scope.d.ts +28 -0
- package/dist/identity/preview-scope.js +47 -0
- package/dist/identity/resolver.d.ts +110 -0
- package/dist/identity/resolver.js +334 -0
- package/dist/identity/session-query.d.ts +4 -0
- package/dist/identity/session-query.js +19 -0
- package/dist/index.d.ts +90 -0
- package/dist/index.js +788 -0
- package/dist/interaction-mode.d.ts +58 -0
- package/dist/interaction-mode.js +93 -0
- package/dist/nav-handler.d.ts +95 -0
- package/dist/nav-handler.js +84 -0
- package/dist/overlay-label.d.ts +79 -0
- package/dist/overlay-label.js +73 -0
- package/dist/provenance/anchor-map.d.ts +95 -0
- package/dist/provenance/anchor-map.js +177 -0
- package/dist/provenance/carrier-plan.d.ts +51 -0
- package/dist/provenance/carrier-plan.js +90 -0
- package/dist/provenance/coded-text.d.ts +20 -0
- package/dist/provenance/coded-text.js +302 -0
- package/dist/provenance/direct-edit-locator.d.ts +29 -0
- package/dist/provenance/direct-edit-locator.js +218 -0
- package/dist/provenance/editable-leaf.d.ts +134 -0
- package/dist/provenance/editable-leaf.js +483 -0
- package/dist/provenance/editor-target.d.ts +29 -0
- package/dist/provenance/editor-target.js +46 -0
- package/dist/provenance/field-leaf-walker.d.ts +21 -0
- package/dist/provenance/field-leaf-walker.js +98 -0
- package/dist/provenance/frame-dependencies.d.ts +212 -0
- package/dist/provenance/frame-dependencies.js +550 -0
- package/dist/provenance/index.d.ts +26 -0
- package/dist/provenance/index.js +26 -0
- package/dist/provenance/local-events.d.ts +1 -0
- package/dist/provenance/local-events.js +1 -0
- package/dist/provenance/markers.d.ts +21 -0
- package/dist/provenance/markers.js +70 -0
- package/dist/provenance/multi-frame-anchor-map.d.ts +54 -0
- package/dist/provenance/multi-frame-anchor-map.js +109 -0
- package/dist/provenance/normalize.d.ts +2 -0
- package/dist/provenance/normalize.js +19 -0
- package/dist/provenance/overlay-focus-echo.d.ts +17 -0
- package/dist/provenance/overlay-focus-echo.js +16 -0
- package/dist/provenance/overlay-projection.d.ts +65 -0
- package/dist/provenance/overlay-projection.js +201 -0
- package/dist/provenance/owner-map.d.ts +55 -0
- package/dist/provenance/owner-map.js +239 -0
- package/dist/provenance/read-carrier.d.ts +22 -0
- package/dist/provenance/read-carrier.js +92 -0
- package/dist/provenance/recorder.d.ts +38 -0
- package/dist/provenance/recorder.js +82 -0
- package/dist/provenance/render-context.d.ts +106 -0
- package/dist/provenance/render-context.js +338 -0
- package/dist/provenance/reveal-contract.d.ts +44 -0
- package/dist/provenance/reveal-contract.js +1 -0
- package/dist/provenance/richtext-slice-embed-address.d.ts +20 -0
- package/dist/provenance/richtext-slice-embed-address.js +35 -0
- package/dist/provenance/runtime-script.d.ts +1 -0
- package/dist/provenance/runtime-script.js +2217 -0
- package/dist/provenance/sidecar-store.d.ts +63 -0
- package/dist/provenance/sidecar-store.js +151 -0
- package/dist/provenance/sidecar.d.ts +34 -0
- package/dist/provenance/sidecar.js +227 -0
- package/dist/provenance/slice-model.d.ts +28 -0
- package/dist/provenance/slice-model.js +86 -0
- package/dist/provenance/source-identity.d.ts +3 -0
- package/dist/provenance/source-identity.js +7 -0
- package/dist/provenance/source-map-materializer.d.ts +76 -0
- package/dist/provenance/source-map-materializer.js +286 -0
- package/dist/provenance/source-registry.d.ts +103 -0
- package/dist/provenance/source-registry.js +562 -0
- package/dist/provenance/transformed-output-gate.d.ts +161 -0
- package/dist/provenance/transformed-output-gate.js +171 -0
- package/dist/provenance/types.d.ts +102 -0
- package/dist/provenance/types.js +1 -0
- package/dist/refresh-policy.d.ts +11 -0
- package/dist/refresh-policy.js +14 -0
- package/dist/reveal-target.d.ts +8 -0
- package/dist/reveal-target.js +47 -0
- package/dist/route-paths.d.ts +11 -0
- package/dist/route-paths.js +14 -0
- package/dist/router-subscriber.d.ts +92 -0
- package/dist/router-subscriber.js +44 -0
- package/dist/runtime/anchor-types.d.ts +5 -0
- package/dist/runtime/anchor-types.js +1 -0
- package/dist/runtime/authority-store.d.ts +31 -0
- package/dist/runtime/authority-store.js +56 -0
- package/dist/runtime/boot.d.ts +52 -0
- package/dist/runtime/boot.js +379 -0
- package/dist/runtime/content-refresh.d.ts +30 -0
- package/dist/runtime/content-refresh.js +68 -0
- package/dist/runtime/emit-runtime-error.d.ts +51 -0
- package/dist/runtime/emit-runtime-error.js +64 -0
- package/dist/runtime/field-proxy.d.ts +112 -0
- package/dist/runtime/field-proxy.js +471 -0
- package/dist/runtime/form-contract-reporter.d.ts +99 -0
- package/dist/runtime/form-contract-reporter.js +391 -0
- package/dist/runtime/invalidation-adapter.d.ts +17 -0
- package/dist/runtime/invalidation-adapter.js +42 -0
- package/dist/runtime/live-query-overlay.d.ts +39 -0
- package/dist/runtime/live-query-overlay.js +345 -0
- package/dist/runtime/momentary-navigate.d.ts +16 -0
- package/dist/runtime/momentary-navigate.js +106 -0
- package/dist/runtime/observation-scheduler.d.ts +15 -0
- package/dist/runtime/observation-scheduler.js +42 -0
- package/dist/runtime/perf-metrics.d.ts +28 -0
- package/dist/runtime/perf-metrics.js +90 -0
- package/dist/runtime/preview-interaction-policy.d.ts +1 -0
- package/dist/runtime/preview-interaction-policy.js +103 -0
- package/dist/runtime/preview-overlay-painter.d.ts +27 -0
- package/dist/runtime/preview-overlay-painter.js +1732 -0
- package/dist/runtime/preview-overlay-sidecar.d.ts +26 -0
- package/dist/runtime/preview-overlay-sidecar.js +45 -0
- package/dist/runtime/preview-perspective-alignment.d.ts +15 -0
- package/dist/runtime/preview-perspective-alignment.js +40 -0
- package/dist/runtime/preview-perspective-intent-state.d.ts +23 -0
- package/dist/runtime/preview-perspective-intent-state.js +5 -0
- package/dist/runtime/preview-transition-state.d.ts +142 -0
- package/dist/runtime/preview-transition-state.js +257 -0
- package/dist/runtime/render-mode-state.d.ts +51 -0
- package/dist/runtime/render-mode-state.js +76 -0
- package/dist/runtime/request-id.d.ts +4 -0
- package/dist/runtime/request-id.js +8 -0
- package/dist/runtime/resolve-schema-field.d.ts +99 -0
- package/dist/runtime/resolve-schema-field.js +356 -0
- package/dist/runtime/runtime-error-dispatch.d.ts +23 -0
- package/dist/runtime/runtime-error-dispatch.js +75 -0
- package/dist/runtime/schema-store.d.ts +121 -0
- package/dist/runtime/schema-store.js +667 -0
- package/dist/runtime/snapshot-assembly.d.ts +104 -0
- package/dist/runtime/snapshot-assembly.js +553 -0
- package/dist/runtime/snapshot-reporter.d.ts +55 -0
- package/dist/runtime/snapshot-reporter.js +375 -0
- package/dist/runtime/standalone-overlay-controller.d.ts +33 -0
- package/dist/runtime/standalone-overlay-controller.js +281 -0
- package/dist/runtime/store.d.ts +307 -0
- package/dist/runtime/store.js +1026 -0
- package/dist/runtime/windowed-minimap-projection.d.ts +28 -0
- package/dist/runtime/windowed-minimap-projection.js +189 -0
- package/dist/runtime/workspace-shortcuts.d.ts +30 -0
- package/dist/runtime/workspace-shortcuts.js +188 -0
- package/dist/sdk-error-copy.d.ts +48 -0
- package/dist/sdk-error-copy.js +182 -0
- package/dist/select-cursor.d.ts +2 -0
- package/dist/select-cursor.js +23 -0
- package/dist/server/admission-cookie.d.ts +38 -0
- package/dist/server/admission-cookie.js +122 -0
- package/dist/server/control-client.d.ts +29 -0
- package/dist/server/control-client.js +22 -0
- package/dist/server/control-errors.d.ts +22 -0
- package/dist/server/control-errors.js +165 -0
- package/dist/server/draft-route.d.ts +12 -0
- package/dist/server/draft-route.js +45 -0
- package/dist/server/extract-token.d.ts +30 -0
- package/dist/server/extract-token.js +98 -0
- package/dist/server/grant.d.ts +128 -0
- package/dist/server/grant.js +395 -0
- package/dist/server/ordinary-read-provenance.d.ts +11 -0
- package/dist/server/ordinary-read-provenance.js +124 -0
- package/dist/server/preview-read-metadata.d.ts +10 -0
- package/dist/server/preview-read-metadata.js +27 -0
- package/dist/server/provenance-frame-builder.d.ts +53 -0
- package/dist/server/provenance-frame-builder.js +102 -0
- package/dist/server/provenance-runtime.d.ts +70 -0
- package/dist/server/provenance-runtime.js +46 -0
- package/dist/server/read-context-command.d.ts +11 -0
- package/dist/server/read-context-command.js +53 -0
- package/dist/server/read-identity.d.ts +3 -0
- package/dist/server/read-identity.js +63 -0
- package/dist/server/read-lifecycle.d.ts +17 -0
- package/dist/server/read-lifecycle.js +52 -0
- package/dist/server/render-frame-provenance.d.ts +133 -0
- package/dist/server/render-frame-provenance.js +676 -0
- package/dist/server/render-id.d.ts +15 -0
- package/dist/server/render-id.js +22 -0
- package/dist/server/route-document-provenance.d.ts +77 -0
- package/dist/server/route-document-provenance.js +1327 -0
- package/dist/server/route-session-cookie.d.ts +33 -0
- package/dist/server/route-session-cookie.js +54 -0
- package/dist/session-projections.d.ts +33 -0
- package/dist/session-projections.js +43 -0
- package/dist/session-subscriber.d.ts +124 -0
- package/dist/session-subscriber.js +79 -0
- package/dist/sha256-fallback.d.ts +30 -0
- package/dist/sha256-fallback.js +126 -0
- package/dist/types.d.ts +446 -0
- package/dist/types.js +85 -0
- package/dist/validators.d.ts +61 -0
- package/dist/validators.js +110 -0
- package/package.json +206 -3
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { type PreviewAuthoringAdmission } from '../authoring/runtime-contract.js';
|
|
2
|
+
export declare const PREVIEW_CAPABILITY_COOKIE = "__storystick_preview";
|
|
3
|
+
export declare const PREVIEW_ACTIVATION_QUERY_PARAM = "_ss_activation";
|
|
4
|
+
export type PreviewGrantCookie = {
|
|
5
|
+
v: 2;
|
|
6
|
+
grantId: string;
|
|
7
|
+
grantToken: string;
|
|
8
|
+
grantExpiresAt: number;
|
|
9
|
+
projectId: string;
|
|
10
|
+
previewOrigin: string;
|
|
11
|
+
accessToken: string;
|
|
12
|
+
accessExpiresAt: number;
|
|
13
|
+
/** First server-observed epoch, retained for the grant lifetime. */
|
|
14
|
+
contentShapeCapability?: string;
|
|
15
|
+
/** Build that minted this grant's access token; a redeploy must reopen Preview. */
|
|
16
|
+
applicationBuildId?: string;
|
|
17
|
+
};
|
|
18
|
+
export type PreviewGrantActivationResponse = {
|
|
19
|
+
admissionSequence: number;
|
|
20
|
+
grantToken: string;
|
|
21
|
+
grantId: string;
|
|
22
|
+
grantExpiresAt: number;
|
|
23
|
+
token: string;
|
|
24
|
+
tokenExpiresIn: number;
|
|
25
|
+
sessionId: string;
|
|
26
|
+
projectId: string;
|
|
27
|
+
previewOrigin: string;
|
|
28
|
+
contentShapeCapability: string;
|
|
29
|
+
};
|
|
30
|
+
export type PreviewGrantExchangeResponse = {
|
|
31
|
+
admissionSequence?: number;
|
|
32
|
+
token: string;
|
|
33
|
+
tokenExpiresIn: number;
|
|
34
|
+
projectId: string;
|
|
35
|
+
previewOrigin: string;
|
|
36
|
+
grantId?: string;
|
|
37
|
+
grantExpiresAt?: number;
|
|
38
|
+
contentShapeCapability: string;
|
|
39
|
+
};
|
|
40
|
+
export type PreviewCookieAttributes = {
|
|
41
|
+
httpOnly: true;
|
|
42
|
+
secure: boolean;
|
|
43
|
+
sameSite: 'none' | 'lax';
|
|
44
|
+
partitioned?: true;
|
|
45
|
+
path: string;
|
|
46
|
+
maxAge: number;
|
|
47
|
+
};
|
|
48
|
+
type ErrorCategory = 'system' | 'auth' | 'validation' | 'conflict' | 'cancel';
|
|
49
|
+
export declare class PreviewGrantExchangeError extends Error {
|
|
50
|
+
readonly code: string;
|
|
51
|
+
readonly ref: string | undefined;
|
|
52
|
+
readonly category: ErrorCategory;
|
|
53
|
+
readonly retryable: boolean;
|
|
54
|
+
readonly status: number | undefined;
|
|
55
|
+
constructor(message: string, opts: {
|
|
56
|
+
code: string;
|
|
57
|
+
ref?: string;
|
|
58
|
+
category: ErrorCategory;
|
|
59
|
+
retryable: boolean;
|
|
60
|
+
status?: number;
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
export declare class PreviewRedirectError extends Error {
|
|
64
|
+
constructor(message: string);
|
|
65
|
+
}
|
|
66
|
+
export declare class PreviewActivationError extends Error {
|
|
67
|
+
constructor(message: string);
|
|
68
|
+
}
|
|
69
|
+
export type PreviewGrantOperation = 'activate' | 'exchange' | 'authoring';
|
|
70
|
+
export type PreviewAdapterError = {
|
|
71
|
+
status: number;
|
|
72
|
+
code: string;
|
|
73
|
+
message: string;
|
|
74
|
+
category: ErrorCategory;
|
|
75
|
+
recoverable: boolean;
|
|
76
|
+
retryable: boolean;
|
|
77
|
+
upstreamCode: string;
|
|
78
|
+
upstreamRef?: string;
|
|
79
|
+
};
|
|
80
|
+
export declare function classifyPreviewGrantError(err: PreviewGrantExchangeError, operation: PreviewGrantOperation): PreviewAdapterError;
|
|
81
|
+
type FetchLike = (input: string, init?: RequestInit) => Promise<Response>;
|
|
82
|
+
export declare function activatePreviewGrantForServer(opts: {
|
|
83
|
+
endpoint: string;
|
|
84
|
+
apiKey: string;
|
|
85
|
+
applicationBuildId?: string;
|
|
86
|
+
grantId: string;
|
|
87
|
+
activationSecret: string;
|
|
88
|
+
sessionId: string;
|
|
89
|
+
requestOrigin: string;
|
|
90
|
+
fetchImpl?: FetchLike;
|
|
91
|
+
}): Promise<PreviewGrantActivationResponse>;
|
|
92
|
+
export declare function exchangePreviewGrantForServer(opts: {
|
|
93
|
+
endpoint: string;
|
|
94
|
+
apiKey: string;
|
|
95
|
+
applicationBuildId?: string;
|
|
96
|
+
grantToken: string;
|
|
97
|
+
requestOrigin: string;
|
|
98
|
+
contentShapeCapability: string;
|
|
99
|
+
fetchImpl?: FetchLike;
|
|
100
|
+
}): Promise<PreviewGrantExchangeResponse>;
|
|
101
|
+
export declare function exchangePreviewAuthoringAdmissionForServer(opts: {
|
|
102
|
+
endpoint: string;
|
|
103
|
+
apiKey: string;
|
|
104
|
+
applicationBuildId?: string;
|
|
105
|
+
grantToken: string;
|
|
106
|
+
requestOrigin: string;
|
|
107
|
+
contentShapeCapability: string;
|
|
108
|
+
admissionId?: string;
|
|
109
|
+
fileDelegationId?: string;
|
|
110
|
+
fetchImpl?: FetchLike;
|
|
111
|
+
}): Promise<PreviewAuthoringAdmission>;
|
|
112
|
+
export declare function parsePreviewActivation(value: string | null): {
|
|
113
|
+
grantId: string;
|
|
114
|
+
activationSecret: string;
|
|
115
|
+
};
|
|
116
|
+
export declare function isSafePreviewSessionId(value: unknown): value is string;
|
|
117
|
+
export declare function previewCapabilityCookieName(scope: {
|
|
118
|
+
projectId: string;
|
|
119
|
+
requestOrigin: string;
|
|
120
|
+
}): string;
|
|
121
|
+
export declare function previewCookieAttributes({ expiresIn, requestUrl, basePath, }: {
|
|
122
|
+
expiresIn: number;
|
|
123
|
+
requestUrl: string;
|
|
124
|
+
basePath?: string;
|
|
125
|
+
}): PreviewCookieAttributes;
|
|
126
|
+
export declare function assertSafePreviewRedirect(path: string, basePath?: string): string;
|
|
127
|
+
export declare function previewNoStoreFetchOptions(): RequestInit;
|
|
128
|
+
export {};
|
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
import { scopedPreviewCookieName } from '@storystick/preview/identity/preview-cookie';
|
|
2
|
+
/** Framework-neutral primitives for the durable PreviewGrant lifecycle. */
|
|
3
|
+
import { contentShapeCapabilityHeaders } from '@storystick/types';
|
|
4
|
+
import { isPreviewAuthoringAdmission } from '../authoring/runtime-contract.js';
|
|
5
|
+
export const PREVIEW_CAPABILITY_COOKIE = '__storystick_preview';
|
|
6
|
+
export const PREVIEW_ACTIVATION_QUERY_PARAM = '_ss_activation';
|
|
7
|
+
const PREVIEW_SESSION_ID_RE = /^[A-Za-z0-9_-]{1,180}$/;
|
|
8
|
+
export class PreviewGrantExchangeError extends Error {
|
|
9
|
+
code;
|
|
10
|
+
ref;
|
|
11
|
+
category;
|
|
12
|
+
retryable;
|
|
13
|
+
status;
|
|
14
|
+
constructor(message, opts) {
|
|
15
|
+
super(message);
|
|
16
|
+
this.name = 'PreviewGrantExchangeError';
|
|
17
|
+
this.code = opts.code;
|
|
18
|
+
this.ref = opts.ref;
|
|
19
|
+
this.category = opts.category;
|
|
20
|
+
this.retryable = opts.retryable;
|
|
21
|
+
this.status = opts.status;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
export class PreviewRedirectError extends Error {
|
|
25
|
+
constructor(message) {
|
|
26
|
+
super(message);
|
|
27
|
+
this.name = 'PreviewRedirectError';
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export class PreviewActivationError extends Error {
|
|
31
|
+
constructor(message) {
|
|
32
|
+
super(message);
|
|
33
|
+
this.name = 'PreviewActivationError';
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
const VALID_CATEGORIES = new Set([
|
|
37
|
+
'system',
|
|
38
|
+
'auth',
|
|
39
|
+
'validation',
|
|
40
|
+
'conflict',
|
|
41
|
+
'cancel',
|
|
42
|
+
]);
|
|
43
|
+
function asCategory(value) {
|
|
44
|
+
return typeof value === 'string' && VALID_CATEGORIES.has(value)
|
|
45
|
+
? value
|
|
46
|
+
: 'system';
|
|
47
|
+
}
|
|
48
|
+
function bareErrorCode(code) {
|
|
49
|
+
return code.startsWith('storystick/') ? code.slice('storystick/'.length) : code;
|
|
50
|
+
}
|
|
51
|
+
function isNonEmptyString(value) {
|
|
52
|
+
return typeof value === 'string' && value.length > 0;
|
|
53
|
+
}
|
|
54
|
+
const HARD_GRANT_FAILURES = new Set([
|
|
55
|
+
'PREVIEW_ACTIVATION_INVALID',
|
|
56
|
+
'GRANT_INVALID',
|
|
57
|
+
'PREVIEW_GRANT_INVALID',
|
|
58
|
+
'GRANT_EXPIRED',
|
|
59
|
+
'PREVIEW_GRANT_EXPIRED',
|
|
60
|
+
'GRANT_REVOKED',
|
|
61
|
+
'PREVIEW_GRANT_REVOKED',
|
|
62
|
+
'PREVIEW_ACCESS_REVOKED',
|
|
63
|
+
'PREVIEW_GRANT_FORBIDDEN',
|
|
64
|
+
'MEMBERSHIP_REVOKED',
|
|
65
|
+
'ORIGIN_MISMATCH',
|
|
66
|
+
'PROJECT_MISMATCH',
|
|
67
|
+
]);
|
|
68
|
+
export function classifyPreviewGrantError(err, operation) {
|
|
69
|
+
const upstreamCode = bareErrorCode(err.code);
|
|
70
|
+
if (upstreamCode === 'PREVIEW_CONTEXT_INVALID') {
|
|
71
|
+
return {
|
|
72
|
+
status: 409,
|
|
73
|
+
code: upstreamCode,
|
|
74
|
+
message: 'This preview context or perspective is no longer available. Choose an available perspective in Studio and reopen the preview.',
|
|
75
|
+
category: 'conflict', recoverable: true, retryable: false,
|
|
76
|
+
upstreamCode: err.code,
|
|
77
|
+
...(err.ref !== undefined && { upstreamRef: err.ref }),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
if (upstreamCode === 'PREVIEW_PROJECT_MISMATCH') {
|
|
81
|
+
return { status: 403, code: upstreamCode,
|
|
82
|
+
message: 'This site is configured for a different Storystick project. Check the Preview URL in Studio and the site’s project connection.',
|
|
83
|
+
category: 'auth', recoverable: true, retryable: false, upstreamCode: err.code };
|
|
84
|
+
}
|
|
85
|
+
if (upstreamCode === 'CONTENT_SHAPE_RELOAD_REQUIRED') {
|
|
86
|
+
return {
|
|
87
|
+
status: 409,
|
|
88
|
+
code: upstreamCode,
|
|
89
|
+
message: 'Open Preview again from Studio to load the current content shape.',
|
|
90
|
+
category: 'conflict',
|
|
91
|
+
recoverable: true,
|
|
92
|
+
retryable: false,
|
|
93
|
+
upstreamCode: err.code,
|
|
94
|
+
...(err.ref !== undefined && { upstreamRef: err.ref }),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
const prefix = operation === 'activate'
|
|
98
|
+
? 'PREVIEW_GRANT_ACTIVATION'
|
|
99
|
+
: operation === 'authoring'
|
|
100
|
+
? 'PREVIEW_AUTHORING_ADMISSION'
|
|
101
|
+
: 'PREVIEW_GRANT_ACCESS';
|
|
102
|
+
if (upstreamCode === 'RATE_LIMITED' || err.status === 429) {
|
|
103
|
+
return {
|
|
104
|
+
status: 429,
|
|
105
|
+
code: `${prefix}_RATE_LIMITED`,
|
|
106
|
+
message: 'preview grant temporarily rate limited',
|
|
107
|
+
category: 'system',
|
|
108
|
+
recoverable: true,
|
|
109
|
+
retryable: true,
|
|
110
|
+
upstreamCode: err.code,
|
|
111
|
+
...(err.ref !== undefined && { upstreamRef: err.ref }),
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
if (err.code === 'PREVIEW_GRANT_NETWORK') {
|
|
115
|
+
return {
|
|
116
|
+
status: 503,
|
|
117
|
+
code: `${prefix}_UNAVAILABLE`,
|
|
118
|
+
message: 'preview grant service unavailable',
|
|
119
|
+
category: 'system',
|
|
120
|
+
recoverable: true,
|
|
121
|
+
retryable: true,
|
|
122
|
+
upstreamCode: err.code,
|
|
123
|
+
...(err.ref !== undefined && { upstreamRef: err.ref }),
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
if (HARD_GRANT_FAILURES.has(upstreamCode)) {
|
|
127
|
+
return {
|
|
128
|
+
status: err.status !== undefined && err.status >= 400 && err.status < 500
|
|
129
|
+
? err.status
|
|
130
|
+
: 401,
|
|
131
|
+
code: `PREVIEW_${upstreamCode.replace(/^PREVIEW_/, '')}`,
|
|
132
|
+
message: 'preview grant is no longer valid',
|
|
133
|
+
category: 'auth',
|
|
134
|
+
recoverable: true,
|
|
135
|
+
retryable: false,
|
|
136
|
+
upstreamCode: err.code,
|
|
137
|
+
...(err.ref !== undefined && { upstreamRef: err.ref }),
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
if (err.category === 'auth' || err.status === 401 || err.status === 403) {
|
|
141
|
+
return {
|
|
142
|
+
status: err.status === 403 ? 403 : 401,
|
|
143
|
+
code: `${prefix}_AUTH_FAILED`,
|
|
144
|
+
message: 'preview grant authentication failed',
|
|
145
|
+
category: 'auth',
|
|
146
|
+
recoverable: true,
|
|
147
|
+
retryable: false,
|
|
148
|
+
upstreamCode: err.code,
|
|
149
|
+
...(err.ref !== undefined && { upstreamRef: err.ref }),
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
return {
|
|
153
|
+
status: err.status && err.status >= 400 ? err.status : 502,
|
|
154
|
+
code: `${prefix}_FAILED`,
|
|
155
|
+
message: 'preview grant exchange failed',
|
|
156
|
+
category: err.category,
|
|
157
|
+
recoverable: true,
|
|
158
|
+
retryable: err.retryable,
|
|
159
|
+
upstreamCode: err.code,
|
|
160
|
+
...(err.ref !== undefined && { upstreamRef: err.ref }),
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
async function postGrantOperation(path, opts) {
|
|
164
|
+
const fetchImpl = opts.fetchImpl ?? fetch;
|
|
165
|
+
let response;
|
|
166
|
+
try {
|
|
167
|
+
response = await fetchImpl(`${opts.endpoint}${path}`, {
|
|
168
|
+
method: 'POST',
|
|
169
|
+
// A shared in-flight exchange cannot retain future callers indefinitely.
|
|
170
|
+
...(path.endsWith('/exchange-for-server') || path.endsWith('/authoring-for-server')
|
|
171
|
+
? { signal: AbortSignal.timeout(10_000) }
|
|
172
|
+
: {}),
|
|
173
|
+
headers: {
|
|
174
|
+
Authorization: `Bearer ${opts.apiKey}`,
|
|
175
|
+
'Content-Type': 'application/json',
|
|
176
|
+
'X-Storystick-Client': 'sdk',
|
|
177
|
+
...contentShapeCapabilityHeaders('api', opts.contentShapeCapability, opts.applicationBuildId),
|
|
178
|
+
},
|
|
179
|
+
body: JSON.stringify(opts.body),
|
|
180
|
+
cache: 'no-store',
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
catch (error) {
|
|
184
|
+
throw new PreviewGrantExchangeError(`Preview grant network error: ${error instanceof Error ? error.message : String(error)}`, {
|
|
185
|
+
code: 'PREVIEW_GRANT_NETWORK',
|
|
186
|
+
category: 'system',
|
|
187
|
+
retryable: true,
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
if (!response.ok) {
|
|
191
|
+
let code = `HTTP_${response.status}`;
|
|
192
|
+
let message = response.statusText;
|
|
193
|
+
let ref;
|
|
194
|
+
let category = 'system';
|
|
195
|
+
let retryable = response.status === 429 || response.status >= 500;
|
|
196
|
+
try {
|
|
197
|
+
const body = (await response.json());
|
|
198
|
+
const wrapped = body.error ?? body;
|
|
199
|
+
code = wrapped.code ?? code;
|
|
200
|
+
message = wrapped.message ?? message;
|
|
201
|
+
ref = wrapped.ref;
|
|
202
|
+
category = asCategory(wrapped.category);
|
|
203
|
+
retryable = wrapped.retryable ?? retryable;
|
|
204
|
+
}
|
|
205
|
+
catch {
|
|
206
|
+
// Keep the coarse HTTP fallback when Central did not return JSON.
|
|
207
|
+
}
|
|
208
|
+
throw new PreviewGrantExchangeError(`${code} — ${message}`, {
|
|
209
|
+
code,
|
|
210
|
+
ref,
|
|
211
|
+
category,
|
|
212
|
+
retryable,
|
|
213
|
+
status: response.status,
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
return response.json();
|
|
217
|
+
}
|
|
218
|
+
export async function activatePreviewGrantForServer(opts) {
|
|
219
|
+
const data = (await postGrantOperation('/api/preview-grants/activate-for-server', {
|
|
220
|
+
endpoint: opts.endpoint,
|
|
221
|
+
apiKey: opts.apiKey,
|
|
222
|
+
applicationBuildId: opts.applicationBuildId,
|
|
223
|
+
body: {
|
|
224
|
+
grantId: opts.grantId,
|
|
225
|
+
activationSecret: opts.activationSecret,
|
|
226
|
+
sessionId: opts.sessionId,
|
|
227
|
+
requestOrigin: opts.requestOrigin,
|
|
228
|
+
},
|
|
229
|
+
fetchImpl: opts.fetchImpl,
|
|
230
|
+
}));
|
|
231
|
+
if (!Number.isSafeInteger(data.admissionSequence) || (data.admissionSequence ?? 0) <= 0 ||
|
|
232
|
+
!isNonEmptyString(data.grantToken) ||
|
|
233
|
+
!isSafePreviewSessionId(data.grantId) ||
|
|
234
|
+
typeof data.grantExpiresAt !== 'number' ||
|
|
235
|
+
!Number.isFinite(data.grantExpiresAt) ||
|
|
236
|
+
!isNonEmptyString(data.token) ||
|
|
237
|
+
typeof data.tokenExpiresIn !== 'number' ||
|
|
238
|
+
!Number.isFinite(data.tokenExpiresIn) ||
|
|
239
|
+
data.tokenExpiresIn <= 0 ||
|
|
240
|
+
!isSafePreviewSessionId(data.sessionId) ||
|
|
241
|
+
!isNonEmptyString(data.projectId) ||
|
|
242
|
+
!isNonEmptyString(data.previewOrigin) ||
|
|
243
|
+
!isNonEmptyString(data.contentShapeCapability)) {
|
|
244
|
+
throw new PreviewGrantExchangeError('Preview grant activation response is malformed', {
|
|
245
|
+
code: 'PREVIEW_GRANT_INVALID_RESPONSE',
|
|
246
|
+
category: 'system',
|
|
247
|
+
retryable: false,
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
return data;
|
|
251
|
+
}
|
|
252
|
+
export async function exchangePreviewGrantForServer(opts) {
|
|
253
|
+
const data = (await postGrantOperation('/api/preview-grants/exchange-for-server', {
|
|
254
|
+
endpoint: opts.endpoint,
|
|
255
|
+
apiKey: opts.apiKey,
|
|
256
|
+
applicationBuildId: opts.applicationBuildId,
|
|
257
|
+
body: {
|
|
258
|
+
grantToken: opts.grantToken,
|
|
259
|
+
requestOrigin: opts.requestOrigin,
|
|
260
|
+
},
|
|
261
|
+
contentShapeCapability: opts.contentShapeCapability,
|
|
262
|
+
fetchImpl: opts.fetchImpl,
|
|
263
|
+
}));
|
|
264
|
+
if (!isNonEmptyString(data.token) ||
|
|
265
|
+
typeof data.tokenExpiresIn !== 'number' ||
|
|
266
|
+
!Number.isFinite(data.tokenExpiresIn) ||
|
|
267
|
+
data.tokenExpiresIn <= 0 ||
|
|
268
|
+
!isNonEmptyString(data.projectId) ||
|
|
269
|
+
!isNonEmptyString(data.previewOrigin) ||
|
|
270
|
+
!isNonEmptyString(data.contentShapeCapability) ||
|
|
271
|
+
(data.grantId !== undefined && !isSafePreviewSessionId(data.grantId)) ||
|
|
272
|
+
(data.grantExpiresAt !== undefined &&
|
|
273
|
+
(typeof data.grantExpiresAt !== 'number' ||
|
|
274
|
+
!Number.isFinite(data.grantExpiresAt)))) {
|
|
275
|
+
throw new PreviewGrantExchangeError('Preview grant access response is malformed', {
|
|
276
|
+
code: 'PREVIEW_GRANT_INVALID_RESPONSE',
|
|
277
|
+
category: 'system',
|
|
278
|
+
retryable: false,
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
return data;
|
|
282
|
+
}
|
|
283
|
+
export async function exchangePreviewAuthoringAdmissionForServer(opts) {
|
|
284
|
+
const data = (await postGrantOperation('/api/preview-grants/authoring-for-server', {
|
|
285
|
+
endpoint: opts.endpoint,
|
|
286
|
+
apiKey: opts.apiKey,
|
|
287
|
+
applicationBuildId: opts.applicationBuildId,
|
|
288
|
+
body: {
|
|
289
|
+
grantToken: opts.grantToken,
|
|
290
|
+
requestOrigin: opts.requestOrigin,
|
|
291
|
+
...(opts.admissionId && opts.fileDelegationId
|
|
292
|
+
? {
|
|
293
|
+
admissionId: opts.admissionId,
|
|
294
|
+
fileDelegationId: opts.fileDelegationId,
|
|
295
|
+
}
|
|
296
|
+
: {}),
|
|
297
|
+
},
|
|
298
|
+
contentShapeCapability: opts.contentShapeCapability,
|
|
299
|
+
fetchImpl: opts.fetchImpl,
|
|
300
|
+
}));
|
|
301
|
+
if (!isPreviewAuthoringAdmission(data)) {
|
|
302
|
+
throw new PreviewGrantExchangeError('Preview authoring admission response is malformed', {
|
|
303
|
+
code: 'PREVIEW_GRANT_INVALID_RESPONSE',
|
|
304
|
+
category: 'system',
|
|
305
|
+
retryable: false,
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
return data;
|
|
309
|
+
}
|
|
310
|
+
export function parsePreviewActivation(value) {
|
|
311
|
+
if (!value)
|
|
312
|
+
throw new PreviewActivationError('preview activation is required');
|
|
313
|
+
const separator = value.indexOf('.');
|
|
314
|
+
if (separator <= 0 || separator === value.length - 1) {
|
|
315
|
+
throw new PreviewActivationError('preview activation is malformed');
|
|
316
|
+
}
|
|
317
|
+
const grantId = value.slice(0, separator);
|
|
318
|
+
const activationSecret = value.slice(separator + 1);
|
|
319
|
+
if (!isSafePreviewSessionId(grantId) ||
|
|
320
|
+
!/^[A-Za-z0-9_-]{16,512}$/.test(activationSecret)) {
|
|
321
|
+
throw new PreviewActivationError('preview activation is malformed');
|
|
322
|
+
}
|
|
323
|
+
return { grantId, activationSecret };
|
|
324
|
+
}
|
|
325
|
+
export function isSafePreviewSessionId(value) {
|
|
326
|
+
return typeof value === 'string' && PREVIEW_SESSION_ID_RE.test(value);
|
|
327
|
+
}
|
|
328
|
+
export function previewCapabilityCookieName(scope) {
|
|
329
|
+
// Ports do not partition browser cookies. Scope the name, retaining the native Path.
|
|
330
|
+
return scopedPreviewCookieName(PREVIEW_CAPABILITY_COOKIE, scope);
|
|
331
|
+
}
|
|
332
|
+
const LOOPBACK_HOSTNAMES = new Set(['localhost', '127.0.0.1', '[::1]', '::1']);
|
|
333
|
+
export function previewCookieAttributes({ expiresIn, requestUrl, basePath = '', }) {
|
|
334
|
+
const url = new URL(requestUrl);
|
|
335
|
+
const maxAge = Math.max(0, Math.floor(expiresIn));
|
|
336
|
+
const isHttpNonLoopback = url.protocol === 'http:' && !LOOPBACK_HOSTNAMES.has(url.hostname);
|
|
337
|
+
if (isHttpNonLoopback) {
|
|
338
|
+
return {
|
|
339
|
+
httpOnly: true,
|
|
340
|
+
secure: false,
|
|
341
|
+
sameSite: 'lax',
|
|
342
|
+
path: basePath || '/',
|
|
343
|
+
maxAge,
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
return {
|
|
347
|
+
httpOnly: true,
|
|
348
|
+
secure: true,
|
|
349
|
+
sameSite: 'none',
|
|
350
|
+
partitioned: true,
|
|
351
|
+
path: basePath || '/',
|
|
352
|
+
maxAge,
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
const CONTROL_CHAR_RE = /[\x00-\x1f]/;
|
|
356
|
+
export function assertSafePreviewRedirect(path, basePath = '') {
|
|
357
|
+
const input = path || '/';
|
|
358
|
+
if (CONTROL_CHAR_RE.test(input)) {
|
|
359
|
+
throw new PreviewRedirectError('Unsafe redirect path: control characters');
|
|
360
|
+
}
|
|
361
|
+
let decoded;
|
|
362
|
+
try {
|
|
363
|
+
decoded = decodeURIComponent(input);
|
|
364
|
+
}
|
|
365
|
+
catch {
|
|
366
|
+
throw new PreviewRedirectError('Unsafe redirect path: malformed encoding');
|
|
367
|
+
}
|
|
368
|
+
if (decoded.startsWith('//') ||
|
|
369
|
+
/^[a-zA-Z][a-zA-Z0-9+\-.]*:/.test(decoded)) {
|
|
370
|
+
throw new PreviewRedirectError('Unsafe redirect path: external target');
|
|
371
|
+
}
|
|
372
|
+
let parsed;
|
|
373
|
+
try {
|
|
374
|
+
parsed = new URL(input, `http://placeholder.invalid${basePath || '/'}`);
|
|
375
|
+
}
|
|
376
|
+
catch {
|
|
377
|
+
throw new PreviewRedirectError('Unsafe redirect path: unparseable');
|
|
378
|
+
}
|
|
379
|
+
if (parsed.hostname !== 'placeholder.invalid') {
|
|
380
|
+
throw new PreviewRedirectError('Unsafe redirect path: external host');
|
|
381
|
+
}
|
|
382
|
+
const normalized = parsed.pathname;
|
|
383
|
+
if (basePath &&
|
|
384
|
+
normalized !== basePath &&
|
|
385
|
+
!normalized.startsWith(`${basePath}/`)) {
|
|
386
|
+
throw new PreviewRedirectError('Unsafe redirect path: outside basePath');
|
|
387
|
+
}
|
|
388
|
+
return normalized + parsed.search + parsed.hash;
|
|
389
|
+
}
|
|
390
|
+
export function previewNoStoreFetchOptions() {
|
|
391
|
+
return {
|
|
392
|
+
cache: 'no-store',
|
|
393
|
+
headers: { 'Cache-Control': 'no-store' },
|
|
394
|
+
};
|
|
395
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { PreviewPrimaryPage } from '@storystick/preview/provenance';
|
|
2
|
+
import { type PreviewProvenanceRuntime } from './provenance-runtime.js';
|
|
3
|
+
/** Encode after the raw request cache. Reads stay native and need no render publisher. */
|
|
4
|
+
export declare function prepareOrdinaryPreviewRead<T>(raw: T, runtime: PreviewProvenanceRuntime, options?: {
|
|
5
|
+
sessionId?: string | null;
|
|
6
|
+
pathname?: string;
|
|
7
|
+
primaryPage?: {
|
|
8
|
+
document: object;
|
|
9
|
+
identity: PreviewPrimaryPage;
|
|
10
|
+
};
|
|
11
|
+
}): Promise<T>;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { isContentRenderPlan, mergeContentFields, selectedContentFields } from '@storystick/types';
|
|
2
|
+
import { attachPreviewReadObservation, getPreviewReadObservation, attachPreviewQueryReceipt, getPreviewQueryReceiptId } from '@storystick/client/internal';
|
|
3
|
+
import { attachPreviewFrameDependencies, collectPreviewFrameDependencies, attachPreviewProvenanceSourceMap, getPreviewProvenanceSourceMap, decodePreviewProvenanceText, encodePreviewProvenanceText, encodePreviewReadCarrier, } from '@storystick/preview/provenance';
|
|
4
|
+
import { withPreviewProvenanceContext } from '../provenance/render-context.js';
|
|
5
|
+
import { unstable_attachPreviewRouteRenderContext, unstable_buildPreviewRouteDocumentFrameWithRuntime } from './route-document-provenance.js';
|
|
6
|
+
import { bindPreviewRuntimeToReads } from './provenance-runtime.js';
|
|
7
|
+
/** Encode after the raw request cache. Reads stay native and need no render publisher. */
|
|
8
|
+
export async function prepareOrdinaryPreviewRead(raw, runtime, options = {}) {
|
|
9
|
+
if (!runtime.config.isEnabled())
|
|
10
|
+
return raw;
|
|
11
|
+
const manifest = await runtime.config.resolveSemanticManifest();
|
|
12
|
+
async function visit(value) {
|
|
13
|
+
if (!value || typeof value !== 'object')
|
|
14
|
+
return value;
|
|
15
|
+
if ('__ssPreviewRouteRender' in value)
|
|
16
|
+
return value;
|
|
17
|
+
let coded;
|
|
18
|
+
if ('documentId' in value && 'storyType' in value && 'fields' in value) {
|
|
19
|
+
const document = value;
|
|
20
|
+
const observation = getPreviewReadObservation(value);
|
|
21
|
+
const bytes = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(JSON.stringify([
|
|
22
|
+
document.documentId, observation?.receiptId ?? null,
|
|
23
|
+
observation?.readContext ?? null, document._locale ?? null,
|
|
24
|
+
])));
|
|
25
|
+
const frameId = 'read_' + Array.from(new Uint8Array(bytes), byte => byte.toString(16).padStart(2, '0')).join('').slice(0, 24);
|
|
26
|
+
const plan = document._render;
|
|
27
|
+
const dependencies = isContentRenderPlan(plan) ? plan.dependencies : undefined;
|
|
28
|
+
// Hidden render dependencies participate in coding but remain outside the
|
|
29
|
+
// caller's selected root fields. This preserves binding source identity.
|
|
30
|
+
const planningDocument = dependencies ? { ...document, fields: mergeContentFields(dependencies, document.fields) } : document;
|
|
31
|
+
if (observation)
|
|
32
|
+
attachPreviewReadObservation(planningDocument, observation);
|
|
33
|
+
if (planningDocument !== document) {
|
|
34
|
+
attachPreviewQueryReceipt(planningDocument, getPreviewQueryReceiptId(document) ?? undefined);
|
|
35
|
+
attachPreviewFrameDependencies(planningDocument, collectPreviewFrameDependencies(document));
|
|
36
|
+
const sourceMap = getPreviewProvenanceSourceMap(document);
|
|
37
|
+
if (sourceMap)
|
|
38
|
+
attachPreviewProvenanceSourceMap(planningDocument, sourceMap);
|
|
39
|
+
}
|
|
40
|
+
const frame = await unstable_buildPreviewRouteDocumentFrameWithRuntime({
|
|
41
|
+
document: planningDocument, manifest, frameId, routeHash: 'occurrence:' + frameId,
|
|
42
|
+
renderId: frameId, pathname: options.pathname ?? '/', sessionId: options.sessionId,
|
|
43
|
+
}, runtime);
|
|
44
|
+
if (!frame?.validationOk || !frame.publicSidecar || !frame.codedDocument)
|
|
45
|
+
return value;
|
|
46
|
+
const carriers = new Map(frame.publicSidecar.markers.map(marker => [marker.markerId, encodePreviewReadCarrier(frameId, frame.publicSidecar, marker, options.primaryPage?.document === value ? options.primaryPage.identity : undefined)]));
|
|
47
|
+
const codedDocument = frame.codedDocument;
|
|
48
|
+
if (dependencies && isContentRenderPlan(plan)) {
|
|
49
|
+
const codedFields = codedDocument.fields;
|
|
50
|
+
codedDocument._render = { ...plan, dependencies: Object.fromEntries(Object.keys(dependencies)
|
|
51
|
+
.map(key => [key, codedFields[key]])) };
|
|
52
|
+
codedDocument.fields = selectedContentFields(codedFields, document.fields);
|
|
53
|
+
}
|
|
54
|
+
const documentWithCarriers = rewriteCarriers(frame.codedDocument, carriers);
|
|
55
|
+
const attrsByKey = Object.fromEntries(Object.entries(frame.attrsByKey).map(([key, attrs]) => [key, Object.fromEntries(Object.entries(attrs).map(([name, id]) => [name, typeof id === 'string' ? carriers.get(id) ?? id : id]))]));
|
|
56
|
+
const contextual = withPreviewProvenanceContext(documentWithCarriers, attrsByKey);
|
|
57
|
+
// Renderers retain their attribute/slice context. The values themselves
|
|
58
|
+
// publish provenance; no adjacent React publisher or projection boundary.
|
|
59
|
+
coded = unstable_attachPreviewRouteRenderContext(contextual, {
|
|
60
|
+
...frame, codedDocument: contextual, attrsByKey, publicSidecar: null,
|
|
61
|
+
});
|
|
62
|
+
const sourceMap = getPreviewProvenanceSourceMap(document);
|
|
63
|
+
if (sourceMap)
|
|
64
|
+
attachPreviewProvenanceSourceMap(coded, sourceMap);
|
|
65
|
+
}
|
|
66
|
+
else if (Array.isArray(value)) {
|
|
67
|
+
coded = await Promise.all(value.map(visit));
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
coded = Object.fromEntries(await Promise.all(Object.entries(value)
|
|
71
|
+
.map(async ([key, child]) => [key, await visit(child)])));
|
|
72
|
+
}
|
|
73
|
+
const observation = getPreviewReadObservation(value);
|
|
74
|
+
if (observation)
|
|
75
|
+
attachPreviewReadObservation(coded, observation);
|
|
76
|
+
attachPreviewQueryReceipt(coded, getPreviewQueryReceiptId(value) ?? undefined);
|
|
77
|
+
attachPreviewFrameDependencies(coded, collectPreviewFrameDependencies(value));
|
|
78
|
+
return coded;
|
|
79
|
+
}
|
|
80
|
+
const coded = await visit(raw);
|
|
81
|
+
bindPreviewRuntimeToReads(coded, runtime);
|
|
82
|
+
return coded;
|
|
83
|
+
}
|
|
84
|
+
function rewriteCarriers(value, carriers) {
|
|
85
|
+
if (typeof value === 'string') {
|
|
86
|
+
// Object markers and attribute IDs also remain plain serializable strings.
|
|
87
|
+
if (carriers.has(value))
|
|
88
|
+
return carriers.get(value);
|
|
89
|
+
const decoded = decodePreviewProvenanceText(value);
|
|
90
|
+
let rewritten = value;
|
|
91
|
+
if (decoded.segments.length) {
|
|
92
|
+
let cursor = 0;
|
|
93
|
+
let coded = '';
|
|
94
|
+
for (const segment of decoded.segments) {
|
|
95
|
+
coded += value.slice(cursor, segment.markerStart) + encodePreviewProvenanceText(carriers.get(segment.markerId) ?? segment.markerId, segment.text);
|
|
96
|
+
cursor = segment.markerEnd;
|
|
97
|
+
}
|
|
98
|
+
rewritten = coded + value.slice(cursor);
|
|
99
|
+
}
|
|
100
|
+
return rewriteSerializedRichTextCarriers(rewritten, carriers);
|
|
101
|
+
}
|
|
102
|
+
if (Array.isArray(value))
|
|
103
|
+
return value.map(child => rewriteCarriers(child, carriers));
|
|
104
|
+
if (value && typeof value === 'object')
|
|
105
|
+
return Object.fromEntries(Object.entries(value)
|
|
106
|
+
.map(([key, child]) => [key, rewriteCarriers(child, carriers)]));
|
|
107
|
+
return value;
|
|
108
|
+
}
|
|
109
|
+
function rewriteSerializedRichTextCarriers(value, carriers) {
|
|
110
|
+
if (value[0] !== '{' || !value.includes('"type"') || !value.includes('"content"'))
|
|
111
|
+
return value;
|
|
112
|
+
try {
|
|
113
|
+
const parsed = JSON.parse(value);
|
|
114
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
|
|
115
|
+
return value;
|
|
116
|
+
const document = parsed;
|
|
117
|
+
if (document.type !== 'doc' || !Array.isArray(document.content))
|
|
118
|
+
return value;
|
|
119
|
+
return JSON.stringify(rewriteCarriers(parsed, carriers));
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
return value;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { PreviewReadContext } from '@storystick/types/preview-query-targeting';
|
|
2
|
+
import { type PreviewReadObservation } from '@storystick/client/internal';
|
|
3
|
+
export interface PreviewFrameReadMetadata {
|
|
4
|
+
readonly readContext?: PreviewReadContext;
|
|
5
|
+
readonly receiptIds: readonly string[];
|
|
6
|
+
readonly observationId: string;
|
|
7
|
+
readonly renderWatermark: number;
|
|
8
|
+
}
|
|
9
|
+
export declare function resolvePreviewFrameReadMetadata(codeVersion: string, ...roots: readonly unknown[]): PreviewFrameReadMetadata | null;
|
|
10
|
+
export declare function resolvePreviewFrameReadMetadataFromObservations(codeVersion: string, observations: readonly PreviewReadObservation[]): PreviewFrameReadMetadata | null;
|