@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,63 @@
|
|
|
1
|
+
import type { PublicProvenanceSidecar } from './sidecar.js';
|
|
2
|
+
export declare const PREVIEW_PROVENANCE_PUBLISHER_TYPE: "storystick.preview.provenance-publisher";
|
|
3
|
+
export declare const PREVIEW_PROVENANCE_SNAPSHOT_TYPE: "storystick.preview.provenance-sidecar-snapshot";
|
|
4
|
+
export declare const PREVIEW_PROVENANCE_STORE_KEY: "__ssPreviewProvenanceSidecarStoreV2";
|
|
5
|
+
export interface PreviewProvenanceTransportFrame {
|
|
6
|
+
readonly frameId: string;
|
|
7
|
+
readonly sidecar: PublicProvenanceSidecar;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Transient server-rendered carrier. The external preview runtime consumes
|
|
11
|
+
* this record, removes the carrier, and publishes an atomic snapshot through
|
|
12
|
+
* Studio. It is deliberately not an executable or persistent inline script.
|
|
13
|
+
*/
|
|
14
|
+
export interface PreviewProvenancePublisher {
|
|
15
|
+
readonly type: typeof PREVIEW_PROVENANCE_PUBLISHER_TYPE;
|
|
16
|
+
readonly version: 2;
|
|
17
|
+
readonly sessionId?: string;
|
|
18
|
+
readonly frame: PreviewProvenanceTransportFrame;
|
|
19
|
+
}
|
|
20
|
+
export interface PreviewProvenanceSidecarSnapshot {
|
|
21
|
+
readonly type: typeof PREVIEW_PROVENANCE_SNAPSHOT_TYPE;
|
|
22
|
+
readonly version: 2;
|
|
23
|
+
readonly sessionId: string;
|
|
24
|
+
readonly routeHash: string;
|
|
25
|
+
readonly codeVersion: string;
|
|
26
|
+
readonly variantId: string | null;
|
|
27
|
+
readonly renderId: string;
|
|
28
|
+
readonly renderNonce: string;
|
|
29
|
+
readonly generation: number;
|
|
30
|
+
readonly emissionRevision: number;
|
|
31
|
+
readonly operation: 'replace' | 'tombstone';
|
|
32
|
+
readonly complete: boolean;
|
|
33
|
+
readonly expectedFrameIds: readonly string[];
|
|
34
|
+
readonly frames: readonly PreviewProvenanceTransportFrame[];
|
|
35
|
+
}
|
|
36
|
+
export interface PreviewProvenanceSidecarStore {
|
|
37
|
+
readonly version: 2;
|
|
38
|
+
accept(snapshot: PreviewProvenanceSidecarSnapshot): boolean;
|
|
39
|
+
readSnapshot(): PreviewProvenanceSidecarSnapshot | null;
|
|
40
|
+
readFrames(): readonly PreviewProvenanceTransportFrame[];
|
|
41
|
+
}
|
|
42
|
+
export declare function installPreviewProvenanceSidecarStore(win: Window): PreviewProvenanceSidecarStore;
|
|
43
|
+
export declare function readPreviewProvenanceSidecarStore(win: Window | null | undefined): PreviewProvenanceSidecarStore | null;
|
|
44
|
+
export declare function isPreviewProvenanceSidecarSnapshot(value: unknown): value is PreviewProvenanceSidecarSnapshot;
|
|
45
|
+
export declare function comparePreviewProvenanceSnapshotOrder(left: Pick<PreviewProvenanceSidecarSnapshot, 'generation' | 'emissionRevision'>, right: Pick<PreviewProvenanceSidecarSnapshot, 'generation' | 'emissionRevision'>): number;
|
|
46
|
+
/**
|
|
47
|
+
* Studio keeps one watermark while an iframe WindowProxy survives customer
|
|
48
|
+
* full-document navigations. A fresh document restarts its local generation
|
|
49
|
+
* at one, so generation ordering alone cannot distinguish it from a stale
|
|
50
|
+
* replay belonging to the previous document.
|
|
51
|
+
*
|
|
52
|
+
* `emissionRevision` is wall-clock based and the newly rendered document's
|
|
53
|
+
* revision is later in this race. A lower-generation snapshot may therefore
|
|
54
|
+
* cross that document boundary only when both independent identity signals
|
|
55
|
+
* agree: a new render nonce and a strictly newer revision. If clock order is
|
|
56
|
+
* unavailable the comparison fails closed; same-render replays and nonce
|
|
57
|
+
* collisions remain governed by the ordinary generation-first order.
|
|
58
|
+
*
|
|
59
|
+
* The iframe-local sidecar store intentionally keeps using the stricter
|
|
60
|
+
* generation comparator above: it is recreated per document and has no
|
|
61
|
+
* cross-document WindowProxy lifecycle to reconcile.
|
|
62
|
+
*/
|
|
63
|
+
export declare function shouldAcceptPreviewProvenanceSnapshotAfterWatermark(next: Pick<PreviewProvenanceSidecarSnapshot, 'generation' | 'emissionRevision' | 'renderNonce'>, watermark: Pick<PreviewProvenanceSidecarSnapshot, 'generation' | 'emissionRevision' | 'renderNonce'>): boolean;
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
export const PREVIEW_PROVENANCE_PUBLISHER_TYPE = 'storystick.preview.provenance-publisher';
|
|
2
|
+
export const PREVIEW_PROVENANCE_SNAPSHOT_TYPE = 'storystick.preview.provenance-sidecar-snapshot';
|
|
3
|
+
export const PREVIEW_PROVENANCE_STORE_KEY = '__ssPreviewProvenanceSidecarStoreV2';
|
|
4
|
+
export function installPreviewProvenanceSidecarStore(win) {
|
|
5
|
+
const target = win;
|
|
6
|
+
const existing = target[PREVIEW_PROVENANCE_STORE_KEY];
|
|
7
|
+
if (existing?.version === 2)
|
|
8
|
+
return existing;
|
|
9
|
+
let active = null;
|
|
10
|
+
let latestAccepted = null;
|
|
11
|
+
const store = {
|
|
12
|
+
version: 2,
|
|
13
|
+
accept(snapshot) {
|
|
14
|
+
if (!isPreviewProvenanceSidecarSnapshot(snapshot))
|
|
15
|
+
return false;
|
|
16
|
+
if (latestAccepted !== null) {
|
|
17
|
+
const order = comparePreviewProvenanceSnapshotOrder(snapshot, latestAccepted);
|
|
18
|
+
if (order < 0)
|
|
19
|
+
return false;
|
|
20
|
+
if (order === 0) {
|
|
21
|
+
if (snapshot.renderNonce !== latestAccepted.renderNonce)
|
|
22
|
+
return false;
|
|
23
|
+
if (snapshot.operation === 'tombstone')
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
latestAccepted = snapshot;
|
|
28
|
+
if (snapshot.operation === 'tombstone') {
|
|
29
|
+
active = null;
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
active = snapshot;
|
|
33
|
+
return true;
|
|
34
|
+
},
|
|
35
|
+
readSnapshot: () => active,
|
|
36
|
+
readFrames: () => active?.frames ?? [],
|
|
37
|
+
};
|
|
38
|
+
target[PREVIEW_PROVENANCE_STORE_KEY] = store;
|
|
39
|
+
return store;
|
|
40
|
+
}
|
|
41
|
+
export function readPreviewProvenanceSidecarStore(win) {
|
|
42
|
+
if (!win)
|
|
43
|
+
return null;
|
|
44
|
+
const store = win[PREVIEW_PROVENANCE_STORE_KEY];
|
|
45
|
+
return store?.version === 2 ? store : null;
|
|
46
|
+
}
|
|
47
|
+
export function isPreviewProvenanceSidecarSnapshot(value) {
|
|
48
|
+
if (!isRecord(value))
|
|
49
|
+
return false;
|
|
50
|
+
if (value.type !== PREVIEW_PROVENANCE_SNAPSHOT_TYPE ||
|
|
51
|
+
value.version !== 2 ||
|
|
52
|
+
typeof value.sessionId !== 'string' ||
|
|
53
|
+
value.sessionId.length === 0 ||
|
|
54
|
+
typeof value.routeHash !== 'string' ||
|
|
55
|
+
typeof value.codeVersion !== 'string' ||
|
|
56
|
+
!(value.variantId === null || typeof value.variantId === 'string') ||
|
|
57
|
+
typeof value.renderId !== 'string' ||
|
|
58
|
+
typeof value.renderNonce !== 'string' ||
|
|
59
|
+
value.renderNonce.length === 0 ||
|
|
60
|
+
!isPositiveFinite(value.generation) ||
|
|
61
|
+
!isPositiveFinite(value.emissionRevision) ||
|
|
62
|
+
!(value.operation === 'replace' || value.operation === 'tombstone') ||
|
|
63
|
+
typeof value.complete !== 'boolean' ||
|
|
64
|
+
!isStringArray(value.expectedFrameIds) ||
|
|
65
|
+
!Array.isArray(value.frames)) {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
if (value.operation === 'tombstone') {
|
|
69
|
+
return value.complete && value.expectedFrameIds.length === 0 && value.frames.length === 0;
|
|
70
|
+
}
|
|
71
|
+
if (!value.complete || value.routeHash.length === 0 || value.codeVersion.length === 0) {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
const expected = new Set(value.expectedFrameIds);
|
|
75
|
+
if (expected.size !== value.expectedFrameIds.length || !expected.has('route'))
|
|
76
|
+
return false;
|
|
77
|
+
const received = new Set();
|
|
78
|
+
for (const frame of value.frames) {
|
|
79
|
+
if (!isRecord(frame) || typeof frame.frameId !== 'string' || frame.frameId.length === 0) {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
if (!isPublicSidecar(frame.sidecar) || received.has(frame.frameId))
|
|
83
|
+
return false;
|
|
84
|
+
if (frame.sidecar.codeVersion !== value.codeVersion ||
|
|
85
|
+
(frame.frameId === 'route' &&
|
|
86
|
+
(frame.sidecar.routeHash !== value.routeHash ||
|
|
87
|
+
frame.sidecar.renderId !== value.renderId ||
|
|
88
|
+
frame.sidecar.renderNonce !== value.renderNonce))) {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
received.add(frame.frameId);
|
|
92
|
+
}
|
|
93
|
+
return received.size === expected.size && [...expected].every((id) => received.has(id));
|
|
94
|
+
}
|
|
95
|
+
export function comparePreviewProvenanceSnapshotOrder(left, right) {
|
|
96
|
+
if (left.generation !== right.generation) {
|
|
97
|
+
return left.generation < right.generation ? -1 : 1;
|
|
98
|
+
}
|
|
99
|
+
if (left.emissionRevision === right.emissionRevision)
|
|
100
|
+
return 0;
|
|
101
|
+
return left.emissionRevision < right.emissionRevision ? -1 : 1;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Studio keeps one watermark while an iframe WindowProxy survives customer
|
|
105
|
+
* full-document navigations. A fresh document restarts its local generation
|
|
106
|
+
* at one, so generation ordering alone cannot distinguish it from a stale
|
|
107
|
+
* replay belonging to the previous document.
|
|
108
|
+
*
|
|
109
|
+
* `emissionRevision` is wall-clock based and the newly rendered document's
|
|
110
|
+
* revision is later in this race. A lower-generation snapshot may therefore
|
|
111
|
+
* cross that document boundary only when both independent identity signals
|
|
112
|
+
* agree: a new render nonce and a strictly newer revision. If clock order is
|
|
113
|
+
* unavailable the comparison fails closed; same-render replays and nonce
|
|
114
|
+
* collisions remain governed by the ordinary generation-first order.
|
|
115
|
+
*
|
|
116
|
+
* The iframe-local sidecar store intentionally keeps using the stricter
|
|
117
|
+
* generation comparator above: it is recreated per document and has no
|
|
118
|
+
* cross-document WindowProxy lifecycle to reconcile.
|
|
119
|
+
*/
|
|
120
|
+
export function shouldAcceptPreviewProvenanceSnapshotAfterWatermark(next, watermark) {
|
|
121
|
+
const order = comparePreviewProvenanceSnapshotOrder(next, watermark);
|
|
122
|
+
if (order > 0)
|
|
123
|
+
return true;
|
|
124
|
+
if (order === 0)
|
|
125
|
+
return next.renderNonce === watermark.renderNonce;
|
|
126
|
+
return (next.renderNonce !== watermark.renderNonce &&
|
|
127
|
+
next.emissionRevision > watermark.emissionRevision);
|
|
128
|
+
}
|
|
129
|
+
function isPublicSidecar(value) {
|
|
130
|
+
if (!isRecord(value))
|
|
131
|
+
return false;
|
|
132
|
+
return value.type === 'storystick.preview.public-sidecar' &&
|
|
133
|
+
value.version === 1 &&
|
|
134
|
+
typeof value.routeHash === 'string' &&
|
|
135
|
+
typeof value.codeVersion === 'string' &&
|
|
136
|
+
typeof value.renderId === 'string' &&
|
|
137
|
+
typeof value.renderNonce === 'string' &&
|
|
138
|
+
value.renderNonce.length > 0 &&
|
|
139
|
+
isPositiveFinite(value.generation) &&
|
|
140
|
+
isPositiveFinite(value.emissionRevision) &&
|
|
141
|
+
Array.isArray(value.markers);
|
|
142
|
+
}
|
|
143
|
+
function isPositiveFinite(value) {
|
|
144
|
+
return typeof value === 'number' && Number.isFinite(value) && value > 0;
|
|
145
|
+
}
|
|
146
|
+
function isStringArray(value) {
|
|
147
|
+
return Array.isArray(value) && value.every((item) => typeof item === 'string' && item.length > 0);
|
|
148
|
+
}
|
|
149
|
+
function isRecord(value) {
|
|
150
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
151
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { type PreviewReadScopeEcho } from '@storystick/client/internal';
|
|
2
|
+
import type { MarkerRecord, PreviewProvenanceCarrierPlan, ProvenanceFrame } from './types.js';
|
|
3
|
+
import { type DirectEditLocator } from './direct-edit-locator.js';
|
|
4
|
+
import { type PreviewFrameDependencySet } from './frame-dependencies.js';
|
|
5
|
+
export interface PublicSidecarMarker {
|
|
6
|
+
readonly markerId: string;
|
|
7
|
+
/** Public edit address; presentation/occurrence identity stays on the frame. */
|
|
8
|
+
readonly editLocator?: DirectEditLocator;
|
|
9
|
+
readonly modality: MarkerRecord['modality'];
|
|
10
|
+
readonly anchorKind: MarkerRecord['anchorKind'];
|
|
11
|
+
readonly carrierPlans?: readonly PreviewProvenanceCarrierPlan[];
|
|
12
|
+
readonly anchorMultiplicity?: MarkerRecord['anchorMultiplicity'];
|
|
13
|
+
readonly presence?: MarkerRecord['presence'];
|
|
14
|
+
readonly renderedTextFingerprint?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface PublicProvenanceSidecar {
|
|
17
|
+
readonly type: 'storystick.preview.public-sidecar';
|
|
18
|
+
readonly version: 1;
|
|
19
|
+
readonly routeHash: string;
|
|
20
|
+
readonly codeVersion: string;
|
|
21
|
+
readonly variantId?: string;
|
|
22
|
+
readonly previewScope?: PreviewReadScopeEcho;
|
|
23
|
+
readonly renderId: string;
|
|
24
|
+
readonly generation: number;
|
|
25
|
+
readonly renderNonce?: string;
|
|
26
|
+
/** Parent route render nonce for streamed descendant frames. */
|
|
27
|
+
readonly routeRenderNonce?: string;
|
|
28
|
+
readonly emissionRevision?: number;
|
|
29
|
+
/** Public render-read identity used to project the active refresh watches. */
|
|
30
|
+
readonly dependencySet?: PreviewFrameDependencySet;
|
|
31
|
+
readonly markers: readonly PublicSidecarMarker[];
|
|
32
|
+
}
|
|
33
|
+
export declare function createPublicSidecar(frame: ProvenanceFrame, dependencySet?: PreviewFrameDependencySet): PublicProvenanceSidecar;
|
|
34
|
+
export declare function parsePublicSidecar(text: string | null | undefined): PublicProvenanceSidecar;
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import { normalizePreviewVariantId } from '../identity/preview-identity.js';
|
|
2
|
+
import { directEditLocatorForSourceSet, normalizeDirectEditLocator, normalizePreviewReadScopeEcho, } from './direct-edit-locator.js';
|
|
3
|
+
import { isPreviewFrameDependencySet, } from './frame-dependencies.js';
|
|
4
|
+
export function createPublicSidecar(frame, dependencySet) {
|
|
5
|
+
const sourceSetById = new Map(frame.sourceSets.map((sourceSet) => [sourceSet.id, sourceSet]));
|
|
6
|
+
return {
|
|
7
|
+
type: 'storystick.preview.public-sidecar',
|
|
8
|
+
version: 1,
|
|
9
|
+
routeHash: frame.routeHash,
|
|
10
|
+
codeVersion: frame.codeVersion,
|
|
11
|
+
...(normalizePreviewVariantId(frame.variantId) !== null
|
|
12
|
+
? { variantId: normalizePreviewVariantId(frame.variantId) }
|
|
13
|
+
: {}),
|
|
14
|
+
...(frame.previewScope !== undefined
|
|
15
|
+
? { previewScope: frame.previewScope }
|
|
16
|
+
: {}),
|
|
17
|
+
renderId: frame.renderId,
|
|
18
|
+
generation: frame.generation,
|
|
19
|
+
...(frame.renderNonce !== undefined ? { renderNonce: frame.renderNonce } : {}),
|
|
20
|
+
...(frame.emissionRevision !== undefined
|
|
21
|
+
? { emissionRevision: frame.emissionRevision }
|
|
22
|
+
: {}),
|
|
23
|
+
...(dependencySet !== undefined ? { dependencySet } : {}),
|
|
24
|
+
markers: frame.markers.map((marker) => {
|
|
25
|
+
const editLocator = directEditLocatorForSourceSet(sourceSetById.get(marker.sourceSetId));
|
|
26
|
+
return {
|
|
27
|
+
markerId: marker.markerId,
|
|
28
|
+
...(editLocator !== null ? { editLocator } : {}),
|
|
29
|
+
modality: marker.modality,
|
|
30
|
+
anchorKind: marker.anchorKind,
|
|
31
|
+
carrierPlans: marker.carrierPlans,
|
|
32
|
+
anchorMultiplicity: marker.anchorMultiplicity,
|
|
33
|
+
// Expected remains non-fatal in anchor validation, while retaining the
|
|
34
|
+
// information needed for owner diagnostics in the public frame.
|
|
35
|
+
presence: marker.presence,
|
|
36
|
+
renderedTextFingerprint: marker.renderedTextFingerprint,
|
|
37
|
+
};
|
|
38
|
+
}),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
export function parsePublicSidecar(text) {
|
|
42
|
+
if (text === null || text === undefined || text.trim().length === 0)
|
|
43
|
+
return emptyPublicSidecar();
|
|
44
|
+
try {
|
|
45
|
+
const parsed = JSON.parse(text);
|
|
46
|
+
return {
|
|
47
|
+
type: 'storystick.preview.public-sidecar',
|
|
48
|
+
version: 1,
|
|
49
|
+
routeHash: typeof parsed.routeHash === 'string' ? parsed.routeHash : '',
|
|
50
|
+
codeVersion: typeof parsed.codeVersion === 'string' ? parsed.codeVersion : '',
|
|
51
|
+
...(normalizePreviewVariantId(parsed.variantId) !== null
|
|
52
|
+
? { variantId: normalizePreviewVariantId(parsed.variantId) }
|
|
53
|
+
: {}),
|
|
54
|
+
...(normalizePreviewReadScopeEcho(parsed.previewScope) !== null
|
|
55
|
+
? { previewScope: normalizePreviewReadScopeEcho(parsed.previewScope) }
|
|
56
|
+
: {}),
|
|
57
|
+
renderId: typeof parsed.renderId === 'string' ? parsed.renderId : '',
|
|
58
|
+
generation: typeof parsed.generation === 'number' ? parsed.generation : 0,
|
|
59
|
+
...(typeof parsed.renderNonce === 'string' && parsed.renderNonce.length > 0
|
|
60
|
+
? { renderNonce: parsed.renderNonce }
|
|
61
|
+
: {}),
|
|
62
|
+
...(typeof parsed.routeRenderNonce === 'string' &&
|
|
63
|
+
parsed.routeRenderNonce.length > 0
|
|
64
|
+
? { routeRenderNonce: parsed.routeRenderNonce }
|
|
65
|
+
: {}),
|
|
66
|
+
...(typeof parsed.emissionRevision === 'number' &&
|
|
67
|
+
Number.isFinite(parsed.emissionRevision) &&
|
|
68
|
+
parsed.emissionRevision > 0
|
|
69
|
+
? { emissionRevision: parsed.emissionRevision }
|
|
70
|
+
: {}),
|
|
71
|
+
...(isPreviewFrameDependencySet(parsed.dependencySet)
|
|
72
|
+
? { dependencySet: parsed.dependencySet }
|
|
73
|
+
: {}),
|
|
74
|
+
markers: Array.isArray(parsed.markers)
|
|
75
|
+
? parsed.markers.flatMap((marker) => normalizeMarker(marker))
|
|
76
|
+
: [],
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
return emptyPublicSidecar();
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
function normalizeMarker(marker) {
|
|
84
|
+
if (typeof marker !== 'object' || marker === null)
|
|
85
|
+
return [];
|
|
86
|
+
const candidate = marker;
|
|
87
|
+
if (typeof candidate.markerId !== 'string' || candidate.markerId.length === 0)
|
|
88
|
+
return [];
|
|
89
|
+
if (!isModality(candidate.modality) || !isAnchorKind(candidate.anchorKind))
|
|
90
|
+
return [];
|
|
91
|
+
return [{
|
|
92
|
+
markerId: candidate.markerId,
|
|
93
|
+
...(normalizeDirectEditLocator(candidate.editLocator) !== null
|
|
94
|
+
? { editLocator: normalizeDirectEditLocator(candidate.editLocator) }
|
|
95
|
+
: {}),
|
|
96
|
+
modality: candidate.modality,
|
|
97
|
+
anchorKind: candidate.anchorKind,
|
|
98
|
+
carrierPlans: normalizeCarrierPlans(candidate.carrierPlans),
|
|
99
|
+
anchorMultiplicity: candidate.anchorMultiplicity === 'multi' ? 'multi' : undefined,
|
|
100
|
+
presence: candidate.presence === 'optional' || candidate.presence === 'expected'
|
|
101
|
+
? candidate.presence
|
|
102
|
+
: undefined,
|
|
103
|
+
renderedTextFingerprint: typeof candidate.renderedTextFingerprint === 'string'
|
|
104
|
+
? candidate.renderedTextFingerprint
|
|
105
|
+
: undefined,
|
|
106
|
+
}];
|
|
107
|
+
}
|
|
108
|
+
function emptyPublicSidecar() {
|
|
109
|
+
return {
|
|
110
|
+
type: 'storystick.preview.public-sidecar',
|
|
111
|
+
version: 1,
|
|
112
|
+
routeHash: '',
|
|
113
|
+
codeVersion: '',
|
|
114
|
+
renderId: '',
|
|
115
|
+
generation: 0,
|
|
116
|
+
markers: [],
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
function isModality(value) {
|
|
120
|
+
return value === 'text' ||
|
|
121
|
+
value === 'attr:href' ||
|
|
122
|
+
value === 'attr:src' ||
|
|
123
|
+
value === 'attr:alt' ||
|
|
124
|
+
value === 'attr:title' ||
|
|
125
|
+
value === 'attr:aria-label';
|
|
126
|
+
}
|
|
127
|
+
function isAnchorKind(value) {
|
|
128
|
+
return value === 'element' || value === 'attribute' || value === 'text-range' || value === 'scope';
|
|
129
|
+
}
|
|
130
|
+
function normalizeCarrierPlans(value) {
|
|
131
|
+
if (!Array.isArray(value))
|
|
132
|
+
return undefined;
|
|
133
|
+
const plans = value.flatMap((plan) => normalizeCarrierPlan(plan));
|
|
134
|
+
return plans.length > 0 ? plans : undefined;
|
|
135
|
+
}
|
|
136
|
+
function normalizeCarrierPlan(value) {
|
|
137
|
+
if (typeof value !== 'object' || value === null)
|
|
138
|
+
return [];
|
|
139
|
+
const candidate = value;
|
|
140
|
+
if (candidate.previewOnly !== true ||
|
|
141
|
+
candidate.markerPayload !== 'opaque-marker-id') {
|
|
142
|
+
return [];
|
|
143
|
+
}
|
|
144
|
+
if (candidate.kind === 'attribute' &&
|
|
145
|
+
(candidate.carrier === 'data-ss-p' || candidate.carrier === 'data-ss-e') &&
|
|
146
|
+
candidate.status === 'emitted' &&
|
|
147
|
+
isModality(candidate.modality) &&
|
|
148
|
+
isAnchorKind(candidate.anchorKind)) {
|
|
149
|
+
return [{
|
|
150
|
+
kind: 'attribute',
|
|
151
|
+
carrier: candidate.carrier,
|
|
152
|
+
modality: candidate.modality,
|
|
153
|
+
anchorKind: candidate.anchorKind,
|
|
154
|
+
status: 'emitted',
|
|
155
|
+
previewOnly: true,
|
|
156
|
+
markerPayload: 'opaque-marker-id',
|
|
157
|
+
}];
|
|
158
|
+
}
|
|
159
|
+
if (candidate.kind === 'text' &&
|
|
160
|
+
candidate.carrier === 'coded-text-unicode-tags' &&
|
|
161
|
+
candidate.status === 'emitted' &&
|
|
162
|
+
candidate.modality === 'text' &&
|
|
163
|
+
candidate.anchorKind === 'element' &&
|
|
164
|
+
isCodedTextSurface(candidate.surface)) {
|
|
165
|
+
return [{
|
|
166
|
+
kind: 'text',
|
|
167
|
+
carrier: 'coded-text-unicode-tags',
|
|
168
|
+
modality: 'text',
|
|
169
|
+
anchorKind: 'element',
|
|
170
|
+
status: 'emitted',
|
|
171
|
+
previewOnly: true,
|
|
172
|
+
markerPayload: 'opaque-marker-id',
|
|
173
|
+
surface: candidate.surface,
|
|
174
|
+
}];
|
|
175
|
+
}
|
|
176
|
+
if (candidate.kind === 'text-range' &&
|
|
177
|
+
candidate.carrier === 'coded-text-unicode-tags' &&
|
|
178
|
+
candidate.status === 'emitted' &&
|
|
179
|
+
candidate.modality === 'text' &&
|
|
180
|
+
candidate.anchorKind === 'text-range' &&
|
|
181
|
+
candidate.surface === 'non-promoted-text-range') {
|
|
182
|
+
return [{
|
|
183
|
+
kind: 'text-range',
|
|
184
|
+
carrier: 'coded-text-unicode-tags',
|
|
185
|
+
modality: 'text',
|
|
186
|
+
anchorKind: 'text-range',
|
|
187
|
+
status: 'emitted',
|
|
188
|
+
previewOnly: true,
|
|
189
|
+
markerPayload: 'opaque-marker-id',
|
|
190
|
+
surface: 'non-promoted-text-range',
|
|
191
|
+
}];
|
|
192
|
+
}
|
|
193
|
+
if ((candidate.kind === 'future-attribute' ||
|
|
194
|
+
candidate.kind === 'future-media' ||
|
|
195
|
+
candidate.kind === 'future-scope') &&
|
|
196
|
+
candidate.status === 'planned' &&
|
|
197
|
+
isFutureCarrier(candidate.carrier) &&
|
|
198
|
+
isFutureModality(candidate.modality) &&
|
|
199
|
+
isAnchorKind(candidate.anchorKind) &&
|
|
200
|
+
typeof candidate.reason === 'string' &&
|
|
201
|
+
candidate.reason.length > 0) {
|
|
202
|
+
return [{
|
|
203
|
+
kind: candidate.kind,
|
|
204
|
+
carrier: candidate.carrier,
|
|
205
|
+
modality: candidate.modality,
|
|
206
|
+
anchorKind: candidate.anchorKind,
|
|
207
|
+
status: 'planned',
|
|
208
|
+
previewOnly: true,
|
|
209
|
+
markerPayload: 'opaque-marker-id',
|
|
210
|
+
reason: candidate.reason,
|
|
211
|
+
}];
|
|
212
|
+
}
|
|
213
|
+
return [];
|
|
214
|
+
}
|
|
215
|
+
function isCodedTextSurface(value) {
|
|
216
|
+
return value === 'direct-editorial-text' ||
|
|
217
|
+
value === 'rich-text-text-node' ||
|
|
218
|
+
value === 'fallback-text-carrier';
|
|
219
|
+
}
|
|
220
|
+
function isFutureCarrier(value) {
|
|
221
|
+
return value === 'attribute-output-carrier' ||
|
|
222
|
+
value === 'media-carrier' ||
|
|
223
|
+
value === 'scope-carrier';
|
|
224
|
+
}
|
|
225
|
+
function isFutureModality(value) {
|
|
226
|
+
return isModality(value) || value === 'media' || value === 'scope';
|
|
227
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export interface SliceAnchor {
|
|
2
|
+
pathPrefix: string;
|
|
3
|
+
key: string;
|
|
4
|
+
}
|
|
5
|
+
export interface SliceModel {
|
|
6
|
+
v: 129;
|
|
7
|
+
renderId: string;
|
|
8
|
+
doc: string;
|
|
9
|
+
i: number;
|
|
10
|
+
type: string;
|
|
11
|
+
fields: Record<string, unknown>;
|
|
12
|
+
zoneField: string;
|
|
13
|
+
zoneFieldSource: 'explicit' | 'default';
|
|
14
|
+
parentAnchors: ReadonlyArray<SliceAnchor>;
|
|
15
|
+
source?: {
|
|
16
|
+
path: string;
|
|
17
|
+
anchors: ReadonlyArray<SliceAnchor>;
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
export declare function getPreviewDocumentId(context: unknown): string | null;
|
|
21
|
+
export declare function isPublishedSnapshotContext(context: unknown): boolean;
|
|
22
|
+
export declare function extractSliceFields(slice: object): Record<string, unknown>;
|
|
23
|
+
export declare function makeSliceRenderId(input: {
|
|
24
|
+
readonly scopeDocId: string;
|
|
25
|
+
readonly slices: readonly unknown[];
|
|
26
|
+
readonly scopeKey?: string;
|
|
27
|
+
}): string;
|
|
28
|
+
export declare function serializeSliceModel(model: SliceModel): string | null;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
const PREVIEW_OBJECT_MARKER_KEY = '_ssMarker';
|
|
2
|
+
const SLICE_META_KEYS = new Set([
|
|
3
|
+
'_type',
|
|
4
|
+
'_key',
|
|
5
|
+
'_variation',
|
|
6
|
+
'_model',
|
|
7
|
+
'_ssPreview',
|
|
8
|
+
'__ssContent',
|
|
9
|
+
]);
|
|
10
|
+
export function getPreviewDocumentId(context) {
|
|
11
|
+
if (!context || typeof context !== 'object')
|
|
12
|
+
return null;
|
|
13
|
+
const c = context;
|
|
14
|
+
return typeof c.documentId === 'string' && c.documentId.length > 0
|
|
15
|
+
? c.documentId
|
|
16
|
+
: null;
|
|
17
|
+
}
|
|
18
|
+
export function isPublishedSnapshotContext(context) {
|
|
19
|
+
if (!context || typeof context !== 'object')
|
|
20
|
+
return false;
|
|
21
|
+
const c = context;
|
|
22
|
+
return c.publishedAt != null || c.snapshotId != null;
|
|
23
|
+
}
|
|
24
|
+
export function extractSliceFields(slice) {
|
|
25
|
+
const storedSlice = slice;
|
|
26
|
+
const fields = {};
|
|
27
|
+
for (const key of Object.keys(slice)) {
|
|
28
|
+
if (!SLICE_META_KEYS.has(key))
|
|
29
|
+
fields[key] = storedSlice[key];
|
|
30
|
+
}
|
|
31
|
+
return fields;
|
|
32
|
+
}
|
|
33
|
+
export function makeSliceRenderId(input) {
|
|
34
|
+
let hash = 0x811c9dc5;
|
|
35
|
+
hash = hashString(hash, input.scopeDocId);
|
|
36
|
+
if (input.scopeKey !== undefined && input.scopeKey.length > 0) {
|
|
37
|
+
hash = hashString(hash, `@${input.scopeKey}`);
|
|
38
|
+
}
|
|
39
|
+
hash = hashString(hash, `#${input.slices.length}`);
|
|
40
|
+
for (let index = 0; index < input.slices.length; index += 1) {
|
|
41
|
+
const slice = input.slices[index];
|
|
42
|
+
hash = hashString(hash, `|${index}:`);
|
|
43
|
+
hash = hashString(hash, readSliceIdentity(slice, '_type'));
|
|
44
|
+
hash = hashString(hash, ':');
|
|
45
|
+
hash = hashString(hash, readSliceIdentity(slice, '_key'));
|
|
46
|
+
}
|
|
47
|
+
return `r${hash.toString(36)}`;
|
|
48
|
+
}
|
|
49
|
+
function readSliceIdentity(slice, key) {
|
|
50
|
+
if (slice === null || typeof slice !== 'object')
|
|
51
|
+
return '';
|
|
52
|
+
const value = slice[key];
|
|
53
|
+
return typeof value === 'string' ? value : '';
|
|
54
|
+
}
|
|
55
|
+
export function serializeSliceModel(model) {
|
|
56
|
+
let json;
|
|
57
|
+
try {
|
|
58
|
+
const ancestors = [];
|
|
59
|
+
json = JSON.stringify(model, function (key, val) {
|
|
60
|
+
if (key === PREVIEW_OBJECT_MARKER_KEY || key === '__ssContent' || key === '_ssPreview')
|
|
61
|
+
return undefined;
|
|
62
|
+
if (typeof val === 'bigint')
|
|
63
|
+
return String(val);
|
|
64
|
+
if (val !== null && typeof val === 'object') {
|
|
65
|
+
while (ancestors.length && ancestors.at(-1) !== this)
|
|
66
|
+
ancestors.pop();
|
|
67
|
+
if (ancestors.includes(val))
|
|
68
|
+
return undefined;
|
|
69
|
+
ancestors.push(val);
|
|
70
|
+
}
|
|
71
|
+
return val;
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
return json || null;
|
|
78
|
+
}
|
|
79
|
+
function hashString(seed, value) {
|
|
80
|
+
let hash = seed >>> 0;
|
|
81
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
82
|
+
hash ^= value.charCodeAt(index);
|
|
83
|
+
hash = Math.imul(hash, 0x01000193) >>> 0;
|
|
84
|
+
}
|
|
85
|
+
return hash >>> 0;
|
|
86
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { normalizePreviewVariantId } from '../identity/preview-identity.js';
|
|
2
|
+
/** Compare only the source's own read echo, never the presentation frame. */
|
|
3
|
+
export function hasCoherentPreviewProvenanceSourceIdentity(source) {
|
|
4
|
+
return source.readScope === undefined ||
|
|
5
|
+
normalizePreviewVariantId(source.variantId) ===
|
|
6
|
+
normalizePreviewVariantId(source.readScope.appliedVariantId);
|
|
7
|
+
}
|