@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,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ADR-116 D4 + D10 — pure state machine + identity replacement table.
|
|
3
|
+
*
|
|
4
|
+
* The transition function is pure : input snapshot + event → output
|
|
5
|
+
* snapshot. No carriers, no postMessage, no timers, no DOM. The
|
|
6
|
+
* resolver (`resolver.ts`) wraps this with side effects.
|
|
7
|
+
*
|
|
8
|
+
* Per ADR-116 D10 the identity replacement table dispatches on
|
|
9
|
+
* (current identity, incoming identity) pairs. The full table is
|
|
10
|
+
* encoded in `transition()` below ; deviating from it produces ad-hoc
|
|
11
|
+
* bugs across boot / re-mint / cross-project / expired-storage edges.
|
|
12
|
+
*/
|
|
13
|
+
import type { IdentitySnapshot, PreviewIdentity, ProvisionalIdentity } from './identity-types.js';
|
|
14
|
+
/**
|
|
15
|
+
* Maximum recovery attempts on `disconnected → resolving-identity`.
|
|
16
|
+
* Each retry MUST be paced by external backoff (the resolver applies
|
|
17
|
+
* exponential delay between attempts). After the budget is exhausted
|
|
18
|
+
* the resolver moves to `error`.
|
|
19
|
+
*/
|
|
20
|
+
export declare const MAX_RECOVERY_RETRIES = 3;
|
|
21
|
+
export declare const INITIAL_SNAPSHOT: IdentitySnapshot;
|
|
22
|
+
/**
|
|
23
|
+
* State machine events. Every event is dispatched by the resolver in
|
|
24
|
+
* response to either an external trigger (start, carrier hit, broadcast)
|
|
25
|
+
* or an internal one (timeout fire).
|
|
26
|
+
*/
|
|
27
|
+
export type IdentityEvent = {
|
|
28
|
+
type: 'start';
|
|
29
|
+
} | {
|
|
30
|
+
type: 'carrier-hit';
|
|
31
|
+
/** Source carrier so the resolver tags the resolved identity. */
|
|
32
|
+
source: 'storage' | 'url';
|
|
33
|
+
identity: ProvisionalIdentity;
|
|
34
|
+
now: number;
|
|
35
|
+
} | {
|
|
36
|
+
type: 'broadcast-received';
|
|
37
|
+
identity: ProvisionalIdentity;
|
|
38
|
+
now: number;
|
|
39
|
+
} | {
|
|
40
|
+
type: 'carrier-timeout';
|
|
41
|
+
} | {
|
|
42
|
+
type: 'auth-succeeded';
|
|
43
|
+
} | {
|
|
44
|
+
type: 'auth-failed';
|
|
45
|
+
code: string;
|
|
46
|
+
message: string;
|
|
47
|
+
} | {
|
|
48
|
+
type: 'disconnect';
|
|
49
|
+
code?: string;
|
|
50
|
+
message?: string;
|
|
51
|
+
} | {
|
|
52
|
+
type: 'recovery-begin';
|
|
53
|
+
} | {
|
|
54
|
+
type: 'recovery-exhausted';
|
|
55
|
+
code: string;
|
|
56
|
+
message: string;
|
|
57
|
+
} | {
|
|
58
|
+
type: 'reset';
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* Pure transition. Illegal transitions are returned unchanged (the
|
|
62
|
+
* machine is defensive against misordered events from external
|
|
63
|
+
* sources). Throwing here would propagate to React render cycles ;
|
|
64
|
+
* the no-op pattern matches the existing PreviewAuthState reducer.
|
|
65
|
+
*/
|
|
66
|
+
export declare function transition(snap: IdentitySnapshot, event: IdentityEvent): IdentitySnapshot;
|
|
67
|
+
/** True if a broadcast with the same identity is idempotent vs current. */
|
|
68
|
+
export declare function isIdempotentBroadcast(current: PreviewIdentity | null, incoming: ProvisionalIdentity): boolean;
|
|
69
|
+
/** True if a broadcast would be rejected by the projectId tie-breaker. */
|
|
70
|
+
export declare function isRejectedBroadcast(current: PreviewIdentity | null, incoming: ProvisionalIdentity): boolean;
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ADR-116 D4 + D10 — pure state machine + identity replacement table.
|
|
3
|
+
*
|
|
4
|
+
* The transition function is pure : input snapshot + event → output
|
|
5
|
+
* snapshot. No carriers, no postMessage, no timers, no DOM. The
|
|
6
|
+
* resolver (`resolver.ts`) wraps this with side effects.
|
|
7
|
+
*
|
|
8
|
+
* Per ADR-116 D10 the identity replacement table dispatches on
|
|
9
|
+
* (current identity, incoming identity) pairs. The full table is
|
|
10
|
+
* encoded in `transition()` below ; deviating from it produces ad-hoc
|
|
11
|
+
* bugs across boot / re-mint / cross-project / expired-storage edges.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Maximum recovery attempts on `disconnected → resolving-identity`.
|
|
15
|
+
* Each retry MUST be paced by external backoff (the resolver applies
|
|
16
|
+
* exponential delay between attempts). After the budget is exhausted
|
|
17
|
+
* the resolver moves to `error`.
|
|
18
|
+
*/
|
|
19
|
+
export const MAX_RECOVERY_RETRIES = 3;
|
|
20
|
+
export const INITIAL_SNAPSHOT = Object.freeze({
|
|
21
|
+
state: 'idle',
|
|
22
|
+
identity: null,
|
|
23
|
+
error: null,
|
|
24
|
+
retryCount: 0,
|
|
25
|
+
});
|
|
26
|
+
/**
|
|
27
|
+
* Pure transition. Illegal transitions are returned unchanged (the
|
|
28
|
+
* machine is defensive against misordered events from external
|
|
29
|
+
* sources). Throwing here would propagate to React render cycles ;
|
|
30
|
+
* the no-op pattern matches the existing PreviewAuthState reducer.
|
|
31
|
+
*/
|
|
32
|
+
export function transition(snap, event) {
|
|
33
|
+
// `error` is terminal — only `reset` clears it.
|
|
34
|
+
if (snap.state === 'error' && event.type !== 'reset')
|
|
35
|
+
return snap;
|
|
36
|
+
switch (event.type) {
|
|
37
|
+
case 'start': {
|
|
38
|
+
if (snap.state !== 'idle' && snap.state !== 'disconnected')
|
|
39
|
+
return snap;
|
|
40
|
+
return { ...snap, state: 'resolving-identity' };
|
|
41
|
+
}
|
|
42
|
+
case 'reset': {
|
|
43
|
+
return { ...INITIAL_SNAPSHOT };
|
|
44
|
+
}
|
|
45
|
+
case 'carrier-hit': {
|
|
46
|
+
if (snap.state !== 'resolving-identity')
|
|
47
|
+
return snap;
|
|
48
|
+
const identity = {
|
|
49
|
+
...event.identity,
|
|
50
|
+
source: event.source,
|
|
51
|
+
epoch: 0,
|
|
52
|
+
resolvedAt: event.now,
|
|
53
|
+
carrierAttempts: Object.freeze([
|
|
54
|
+
{ carrier: event.source, result: 'hit' },
|
|
55
|
+
]),
|
|
56
|
+
};
|
|
57
|
+
return {
|
|
58
|
+
...snap,
|
|
59
|
+
state: 'handshaking',
|
|
60
|
+
identity,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
case 'broadcast-received': {
|
|
64
|
+
return applyBroadcast(snap, event.identity, event.now);
|
|
65
|
+
}
|
|
66
|
+
case 'carrier-timeout': {
|
|
67
|
+
// Only meaningful while still resolving. If a carrier hit fired
|
|
68
|
+
// before the timeout, the state already moved to handshaking and
|
|
69
|
+
// the timeout no-ops.
|
|
70
|
+
if (snap.state !== 'resolving-identity')
|
|
71
|
+
return snap;
|
|
72
|
+
return {
|
|
73
|
+
...snap,
|
|
74
|
+
state: 'error',
|
|
75
|
+
error: {
|
|
76
|
+
code: 'identity_carrier_timeout',
|
|
77
|
+
message: 'Preview identity could not be established. Reopen the document in Storystick Studio to retry.',
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
case 'auth-succeeded': {
|
|
82
|
+
if (snap.state !== 'handshaking')
|
|
83
|
+
return snap;
|
|
84
|
+
return { ...snap, state: 'authenticated', retryCount: 0 };
|
|
85
|
+
}
|
|
86
|
+
case 'auth-failed': {
|
|
87
|
+
if (snap.state !== 'handshaking')
|
|
88
|
+
return snap;
|
|
89
|
+
return {
|
|
90
|
+
...snap,
|
|
91
|
+
state: 'error',
|
|
92
|
+
error: { code: event.code, message: event.message },
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
case 'disconnect': {
|
|
96
|
+
// The resolver may decide to recover (transition recovery-begin).
|
|
97
|
+
// From a connected-ish state the disconnect always lands.
|
|
98
|
+
if (snap.state !== 'authenticated' &&
|
|
99
|
+
snap.state !== 'handshaking' &&
|
|
100
|
+
snap.state !== 'disconnected') {
|
|
101
|
+
return snap;
|
|
102
|
+
}
|
|
103
|
+
return {
|
|
104
|
+
...snap,
|
|
105
|
+
state: 'disconnected',
|
|
106
|
+
error: event.code
|
|
107
|
+
? { code: event.code, message: event.message ?? event.code }
|
|
108
|
+
: snap.error,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
case 'recovery-begin': {
|
|
112
|
+
if (snap.state !== 'disconnected')
|
|
113
|
+
return snap;
|
|
114
|
+
if (snap.retryCount >= MAX_RECOVERY_RETRIES)
|
|
115
|
+
return snap;
|
|
116
|
+
return {
|
|
117
|
+
...snap,
|
|
118
|
+
state: 'resolving-identity',
|
|
119
|
+
retryCount: snap.retryCount + 1,
|
|
120
|
+
error: null,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
case 'recovery-exhausted': {
|
|
124
|
+
if (snap.state !== 'disconnected')
|
|
125
|
+
return snap;
|
|
126
|
+
return {
|
|
127
|
+
...snap,
|
|
128
|
+
state: 'error',
|
|
129
|
+
error: { code: event.code, message: event.message },
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
default: {
|
|
133
|
+
// Exhaustiveness — TypeScript flags missing cases.
|
|
134
|
+
return snap;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* ADR-116 D10 — identity replacement table for incoming Studio
|
|
140
|
+
* broadcasts. Encoded as a single function so the table is testable
|
|
141
|
+
* end-to-end and impossible to bypass.
|
|
142
|
+
*
|
|
143
|
+
* | current identity | incoming | action |
|
|
144
|
+
* |----------------------------------------|--------------------------------|-------------|
|
|
145
|
+
* | null (resolving-identity) | broadcast | confirm |
|
|
146
|
+
* | matches sessionId + projectId | broadcast | no-op |
|
|
147
|
+
* | same projectId, different sessionId | broadcast | replace ++ |
|
|
148
|
+
* | different projectId | broadcast | reject |
|
|
149
|
+
* | provisional storage/url, broadcast same| broadcast confirms | promote |
|
|
150
|
+
*
|
|
151
|
+
* Atomicity (codex R6) : each call mutates the snapshot atomically.
|
|
152
|
+
* No intermediate state where `identity.sessionId` is mismatched with
|
|
153
|
+
* `state` is visible to subscribers.
|
|
154
|
+
*/
|
|
155
|
+
function applyBroadcast(snap, incoming, now) {
|
|
156
|
+
// Reject on idle/error (state machine is closed to broadcasts there).
|
|
157
|
+
if (snap.state === 'idle' || snap.state === 'error')
|
|
158
|
+
return snap;
|
|
159
|
+
const current = snap.identity;
|
|
160
|
+
if (current === null) {
|
|
161
|
+
// Confirm — first identity to arrive.
|
|
162
|
+
const identity = {
|
|
163
|
+
...incoming,
|
|
164
|
+
source: 'broadcast',
|
|
165
|
+
epoch: 0,
|
|
166
|
+
resolvedAt: now,
|
|
167
|
+
carrierAttempts: Object.freeze([
|
|
168
|
+
{ carrier: 'broadcast', result: 'hit' },
|
|
169
|
+
]),
|
|
170
|
+
};
|
|
171
|
+
return {
|
|
172
|
+
...snap,
|
|
173
|
+
// From `resolving-identity` → handshaking ; from disconnected
|
|
174
|
+
// (mid-recovery) → handshaking too.
|
|
175
|
+
state: 'handshaking',
|
|
176
|
+
identity,
|
|
177
|
+
error: null,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
// Different projectId — reject. The `<Storystick projectId>` prop is
|
|
181
|
+
// authoritative ; a divergent projectId indicates either a Studio bug
|
|
182
|
+
// or a hostile origin that passed the allowlist. Keep current
|
|
183
|
+
// identity unchanged.
|
|
184
|
+
if (current.projectId !== incoming.projectId) {
|
|
185
|
+
return snap;
|
|
186
|
+
}
|
|
187
|
+
// Same sessionId + same projectId — idempotent no-op. The resolver
|
|
188
|
+
// refreshes the storage TTL externally (carrier-hit side effect).
|
|
189
|
+
if (current.sessionId === incoming.sessionId) {
|
|
190
|
+
// Promote provisional → broadcast-sourced for the audit trail. The
|
|
191
|
+
// tuple itself is unchanged so subscribers do not need to re-render
|
|
192
|
+
// identity-derived state. But if parentOrigin was null (came from
|
|
193
|
+
// storage/url) and the broadcast carries one, populate it.
|
|
194
|
+
if (current.source !== 'broadcast' ||
|
|
195
|
+
(current.parentOrigin === null && incoming.parentOrigin !== null)) {
|
|
196
|
+
const promoted = {
|
|
197
|
+
...current,
|
|
198
|
+
parentOrigin: incoming.parentOrigin ?? current.parentOrigin,
|
|
199
|
+
source: 'broadcast',
|
|
200
|
+
};
|
|
201
|
+
return {
|
|
202
|
+
...snap,
|
|
203
|
+
// Stay in current state ; promotion is a tag-only mutation
|
|
204
|
+
// when already past resolving-identity. If still resolving-
|
|
205
|
+
// identity (rare — provisional carrier hit then matching
|
|
206
|
+
// broadcast in the same boot), advance to handshaking.
|
|
207
|
+
state: snap.state === 'resolving-identity' ? 'handshaking' : snap.state,
|
|
208
|
+
identity: promoted,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
return snap;
|
|
212
|
+
}
|
|
213
|
+
// Different sessionId, same projectId — REPLACE (new epoch).
|
|
214
|
+
const nextEpoch = {
|
|
215
|
+
...incoming,
|
|
216
|
+
source: 'broadcast',
|
|
217
|
+
epoch: current.epoch + 1,
|
|
218
|
+
resolvedAt: now,
|
|
219
|
+
carrierAttempts: Object.freeze([
|
|
220
|
+
{ carrier: 'broadcast', result: 'hit' },
|
|
221
|
+
]),
|
|
222
|
+
};
|
|
223
|
+
return {
|
|
224
|
+
...snap,
|
|
225
|
+
// Replace transitions the channel back to handshaking — the auth
|
|
226
|
+
// handshake must rerun against the new sessionId.
|
|
227
|
+
state: 'handshaking',
|
|
228
|
+
identity: nextEpoch,
|
|
229
|
+
error: null,
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
/** True if a broadcast with the same identity is idempotent vs current. */
|
|
233
|
+
export function isIdempotentBroadcast(current, incoming) {
|
|
234
|
+
if (current === null)
|
|
235
|
+
return false;
|
|
236
|
+
return (current.sessionId === incoming.sessionId &&
|
|
237
|
+
current.projectId === incoming.projectId);
|
|
238
|
+
}
|
|
239
|
+
/** True if a broadcast would be rejected by the projectId tie-breaker. */
|
|
240
|
+
export function isRejectedBroadcast(current, incoming) {
|
|
241
|
+
if (current === null)
|
|
242
|
+
return false;
|
|
243
|
+
return current.projectId !== incoming.projectId;
|
|
244
|
+
}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ADR-116 — Preview Identity Foundation v2.
|
|
3
|
+
*
|
|
4
|
+
* Framework-agnostic identity primitive consumed by every framework
|
|
5
|
+
* adapter. The URL marker `_ss_session` becomes a boot-bootstrap signal
|
|
6
|
+
* only ; identity itself is resolved via a carrier chain (live Studio
|
|
7
|
+
* broadcast > sessionStorage > URL marker) and persists for the iframe
|
|
8
|
+
* lifecycle.
|
|
9
|
+
*
|
|
10
|
+
* This module is pure : it imports no React, no Next, no Convex, no
|
|
11
|
+
* DOM. The carriers and resolver build on these types. The framework
|
|
12
|
+
* adapter contract (`FrameworkAdapter`) is intentionally brutally
|
|
13
|
+
* small — three methods — so future adapters (Astro, SvelteKit, Vue,
|
|
14
|
+
* plain HTML) can ship without rewriting the resolver.
|
|
15
|
+
*
|
|
16
|
+
* Why core, not adapter : every framework adapter needs the same
|
|
17
|
+
* answer to *"am I in preview + what is my session?"*. Putting the
|
|
18
|
+
* resolver here means future adapters consume the same answer with no
|
|
19
|
+
* duplication and the URL-marker assumption (load-bearing across 7
|
|
20
|
+
* sites pre-ADR-116) is eliminated by construction.
|
|
21
|
+
*/
|
|
22
|
+
/**
|
|
23
|
+
* Resolved preview identity for an iframe boot epoch.
|
|
24
|
+
*
|
|
25
|
+
* IMMUTABLE per `sessionId` (ADR-116 D1, refined per codex R6 #1). A
|
|
26
|
+
* new `sessionId` from Studio produces a new epoch with an incremented
|
|
27
|
+
* `epoch` counter. The identity is the single source of truth for the
|
|
28
|
+
* iframe lifetime — every consumer reads the same tuple.
|
|
29
|
+
*/
|
|
30
|
+
export interface PreviewIdentity {
|
|
31
|
+
/** Studio-minted preview session id (uuid-ish). */
|
|
32
|
+
sessionId: string;
|
|
33
|
+
/** Project shortId. Authoritative tie-breaker per D8. */
|
|
34
|
+
projectId: string;
|
|
35
|
+
/**
|
|
36
|
+
* Parent Studio origin. `null` during the bootstrap window when the
|
|
37
|
+
* identity came from the URL or sessionStorage carrier (carriers
|
|
38
|
+
* cannot know the parent's origin without postMessage exchange).
|
|
39
|
+
* Populated once a `session-broadcast` envelope arrives and the
|
|
40
|
+
* iframe locks the parent origin. The split between provisional vs
|
|
41
|
+
* connected channel surfaces here (codex R6 #1).
|
|
42
|
+
*/
|
|
43
|
+
parentOrigin: string | null;
|
|
44
|
+
/** Which carrier produced this identity. */
|
|
45
|
+
source: 'broadcast' | 'request' | 'storage' | 'url';
|
|
46
|
+
/**
|
|
47
|
+
* Epoch counter, incremented on every Replace transition (D10).
|
|
48
|
+
* Forward-compat with the ADR-108 epoch follow-up — Studio
|
|
49
|
+
* dispatchers can correlate envelopes against the latest known
|
|
50
|
+
* epoch without protocol changes when that ADR lands.
|
|
51
|
+
*/
|
|
52
|
+
epoch: number;
|
|
53
|
+
/** Wall-clock instant the identity was resolved (debug + audit). */
|
|
54
|
+
resolvedAt: number;
|
|
55
|
+
/**
|
|
56
|
+
* Per-carrier attempt outcomes from the resolver's read pass.
|
|
57
|
+
* Frozen at resolve time ; subsequent broadcasts do NOT mutate it
|
|
58
|
+
* (the audit trail is the boot story, not the full history).
|
|
59
|
+
*/
|
|
60
|
+
carrierAttempts: ReadonlyArray<CarrierAttempt>;
|
|
61
|
+
}
|
|
62
|
+
export interface CarrierAttempt {
|
|
63
|
+
carrier: 'broadcast' | 'storage' | 'url';
|
|
64
|
+
result: 'hit' | 'miss' | 'error';
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* State machine values (ADR-116 D4). The runtime cell on `globalThis`
|
|
68
|
+
* (PR4 wiring) holds the latest state plus the immutable identity.
|
|
69
|
+
*
|
|
70
|
+
* - `idle` — initial, no resolution attempted.
|
|
71
|
+
* - `resolving-identity` — carriers being consulted ; envelope
|
|
72
|
+
* listeners attached ; the carrier-miss timeout is armed.
|
|
73
|
+
* - `handshaking` — identity resolved (provisional or live) ; auth
|
|
74
|
+
* handshake in progress. PR4 wires `notifyAuthSucceeded()` to
|
|
75
|
+
* transition to `authenticated`.
|
|
76
|
+
* - `authenticated` — channel live ; ADR-108 envelope flow takes
|
|
77
|
+
* over.
|
|
78
|
+
* - `disconnected` — operational signal indicates parent connection
|
|
79
|
+
* lost ; recovery attempts re-handshake (rate-limited).
|
|
80
|
+
* - `error` — terminal ; recoverable only by full reload.
|
|
81
|
+
*/
|
|
82
|
+
export type IdentityResolverState = 'idle' | 'resolving-identity' | 'handshaking' | 'authenticated' | 'disconnected' | 'error';
|
|
83
|
+
/**
|
|
84
|
+
* Resolver snapshot. Subscribers receive the full snapshot on every
|
|
85
|
+
* transition so they can render any state without separate lookups.
|
|
86
|
+
*/
|
|
87
|
+
export interface IdentitySnapshot {
|
|
88
|
+
state: IdentityResolverState;
|
|
89
|
+
identity: PreviewIdentity | null;
|
|
90
|
+
/** Populated only when `state === 'error'`. */
|
|
91
|
+
error: {
|
|
92
|
+
code: string;
|
|
93
|
+
message: string;
|
|
94
|
+
} | null;
|
|
95
|
+
/**
|
|
96
|
+
* Recovery retry budget consumed (0 on cold boot, up to 3). Visible
|
|
97
|
+
* for tests and the eventual UI surface ("preview reconnecting…").
|
|
98
|
+
*/
|
|
99
|
+
retryCount: number;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Context handed to carriers when the resolver consults them. Pure
|
|
103
|
+
* values only — carriers don't reach into the resolver.
|
|
104
|
+
*/
|
|
105
|
+
export interface CarrierContext {
|
|
106
|
+
/**
|
|
107
|
+
* Authoritative projectId from the `<Storystick projectId>` prop.
|
|
108
|
+
* Carriers MUST reject identity for any other project. D8 tie-
|
|
109
|
+
* breaker.
|
|
110
|
+
*/
|
|
111
|
+
expectedProjectId: string;
|
|
112
|
+
/** Wall clock — injectable for tests. */
|
|
113
|
+
now: () => number;
|
|
114
|
+
}
|
|
115
|
+
/** Result of a synchronous carrier read. */
|
|
116
|
+
export type CarrierResult = {
|
|
117
|
+
kind: 'hit';
|
|
118
|
+
identity: ProvisionalIdentity;
|
|
119
|
+
} | {
|
|
120
|
+
kind: 'miss';
|
|
121
|
+
} | {
|
|
122
|
+
kind: 'error';
|
|
123
|
+
reason: string;
|
|
124
|
+
};
|
|
125
|
+
/**
|
|
126
|
+
* Identity shape carriers produce (no `epoch`, no `resolvedAt`, no
|
|
127
|
+
* `carrierAttempts` — those are stamped by the resolver).
|
|
128
|
+
*/
|
|
129
|
+
export interface ProvisionalIdentity {
|
|
130
|
+
sessionId: string;
|
|
131
|
+
projectId: string;
|
|
132
|
+
/** Carriers cannot know the parent origin until a broadcast lands. */
|
|
133
|
+
parentOrigin: string | null;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* A carrier reads identity from one substrate (URL, sessionStorage,
|
|
137
|
+
* postMessage). The resolver consults them in strict precedence order
|
|
138
|
+
* (D3). The interface is synchronous because all known substrates are
|
|
139
|
+
* synchronous reads ; the resolver's async surface comes from the
|
|
140
|
+
* timeout window for broadcast arrival, not from carrier reads.
|
|
141
|
+
*/
|
|
142
|
+
export interface Carrier {
|
|
143
|
+
readonly name: 'broadcast' | 'storage' | 'url';
|
|
144
|
+
read(ctx: CarrierContext): CarrierResult;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Snapshot of the current location surface in framework-agnostic form.
|
|
148
|
+
* Used by the framework adapter contract.
|
|
149
|
+
*/
|
|
150
|
+
export interface FrameworkLocation {
|
|
151
|
+
readonly pathname: string;
|
|
152
|
+
readonly search: string;
|
|
153
|
+
readonly hash: string;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Framework adapter contract (ADR-116 D6). Brutally small — three
|
|
157
|
+
* methods — so future adapters (Astro / SvelteKit / Vue / plain HTML)
|
|
158
|
+
* can ship in 1–2 days each from the settled contract. The Next
|
|
159
|
+
* adapter implements this in PR4. The core test-adapter
|
|
160
|
+
* (`__test-adapter.ts`) implements it for unit tests.
|
|
161
|
+
*
|
|
162
|
+
* Adapters do NOT need a separate `dispose()` method (plan-eng-review
|
|
163
|
+
* A5) : the accumulated unsubscribes from `subscribeToLocation` plus
|
|
164
|
+
* the state machine's `disconnected` event are sufficient teardown
|
|
165
|
+
* signals.
|
|
166
|
+
*/
|
|
167
|
+
export interface FrameworkAdapter {
|
|
168
|
+
/**
|
|
169
|
+
* Same-origin absolute path. App Router → `router.replace` ; Pages
|
|
170
|
+
* Router → `router.replace` ; plain HTML → `location.assign` as
|
|
171
|
+
* fallback. `opts.refresh === true` invalidates the App Router
|
|
172
|
+
* client Router Cache via `router.refresh()` ; ignored by routers
|
|
173
|
+
* without an equivalent surface.
|
|
174
|
+
*/
|
|
175
|
+
navigateTo(path: string, opts?: {
|
|
176
|
+
refresh?: boolean;
|
|
177
|
+
}): void;
|
|
178
|
+
/**
|
|
179
|
+
* Subscribe to client-side route changes. Returns an unsubscribe
|
|
180
|
+
* function. The resolver invokes the unsubscribe on teardown
|
|
181
|
+
* (transition to `disconnected`, runtime `dispose()`, framework HMR
|
|
182
|
+
* re-mount).
|
|
183
|
+
*/
|
|
184
|
+
subscribeToLocation(cb: (loc: FrameworkLocation) => void): () => void;
|
|
185
|
+
/**
|
|
186
|
+
* Snapshot read for boot-time emission + reverse-channel echo
|
|
187
|
+
* classification.
|
|
188
|
+
*/
|
|
189
|
+
getCurrentLocation(): FrameworkLocation;
|
|
190
|
+
}
|
|
191
|
+
/** Listener attached to the resolver via `subscribe`. */
|
|
192
|
+
export type IdentityListener = (snapshot: IdentitySnapshot) => void;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ADR-116 — Preview Identity Foundation v2.
|
|
3
|
+
*
|
|
4
|
+
* Framework-agnostic identity primitive consumed by every framework
|
|
5
|
+
* adapter. The URL marker `_ss_session` becomes a boot-bootstrap signal
|
|
6
|
+
* only ; identity itself is resolved via a carrier chain (live Studio
|
|
7
|
+
* broadcast > sessionStorage > URL marker) and persists for the iframe
|
|
8
|
+
* lifecycle.
|
|
9
|
+
*
|
|
10
|
+
* This module is pure : it imports no React, no Next, no Convex, no
|
|
11
|
+
* DOM. The carriers and resolver build on these types. The framework
|
|
12
|
+
* adapter contract (`FrameworkAdapter`) is intentionally brutally
|
|
13
|
+
* small — three methods — so future adapters (Astro, SvelteKit, Vue,
|
|
14
|
+
* plain HTML) can ship without rewriting the resolver.
|
|
15
|
+
*
|
|
16
|
+
* Why core, not adapter : every framework adapter needs the same
|
|
17
|
+
* answer to *"am I in preview + what is my session?"*. Putting the
|
|
18
|
+
* resolver here means future adapters consume the same answer with no
|
|
19
|
+
* duplication and the URL-marker assumption (load-bearing across 7
|
|
20
|
+
* sites pre-ADR-116) is eliminated by construction.
|
|
21
|
+
*/
|
|
22
|
+
export {};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ADR-116 — preview identity foundation barrel.
|
|
3
|
+
*
|
|
4
|
+
* Re-exports the public surface so consumers (PR4 `<Storystick>` + PR5
|
|
5
|
+
* adapter migrations + future framework adapters) import from one
|
|
6
|
+
* place.
|
|
7
|
+
*/
|
|
8
|
+
export type { PreviewIdentity, ProvisionalIdentity, CarrierAttempt, Carrier, CarrierContext, CarrierResult, IdentityResolverState, IdentitySnapshot, IdentityListener, FrameworkAdapter, FrameworkLocation, } from './identity-types.js';
|
|
9
|
+
export { INITIAL_SNAPSHOT, MAX_RECOVERY_RETRIES, transition, isIdempotentBroadcast, isRejectedBroadcast, type IdentityEvent, } from './identity-state-machine.js';
|
|
10
|
+
export { createIdentityResolver, DEFAULT_CARRIER_TIMEOUT_MS, RECOVERY_BACKOFF_MS, STORAGE_KEY, type CreateResolverOpts, type IdentityResolver, } from './resolver.js';
|
|
11
|
+
export { createUrlCarrier, URL_SESSION_PARAM } from './carriers/url.js';
|
|
12
|
+
export { createStorageCarrier, readStored, writeStored, clearStored, STORAGE_TTL_MS, type StoredIdentityV1, } from './carriers/storage.js';
|
|
13
|
+
export { buildSessionRequest, parseInboundBroadcast, attachBroadcastListener, postSessionRequest, } from './carriers/postmessage.js';
|
|
14
|
+
export { createTestAdapter } from './__test-adapter.js';
|
|
15
|
+
export type { TestAdapterPair, TestAdapterHarness } from './__test-adapter.js';
|
|
16
|
+
export { PREVIEW_SESSION_QUERY_PARAM, PREVIEW_VARIANT_QUERY_PARAM, PREVIEW_RELEASE_QUERY_PARAM, hasPreviewSessionParam, detectPreviewSessionFromWindow, isPreviewIdentity, readPreviewIdentity, readPreviewIdentitySessionId, readPreviewIdentitySessionIdFromSource, readPreviewSessionIdFromSearch, readPreviewVariantIdFromSearch, normalizePreviewVariantId, readPreviewReleaseIdFromSearch, normalizePreviewReleaseId, type PreviewSessionDetection, type PreviewIdentitySource, } from './preview-identity.js';
|
|
17
|
+
export { PreviewScopeConflictError, createPreviewScope, previewScopeKey, readPreviewScopeFromSearch, writePreviewScopeToSearchParams, type PreviewScope, } from './preview-scope.js';
|
|
18
|
+
export { PREVIEW_DRAFT_QUERY_PARAM, readPreviewDraftDocumentId, previewDraftHref } from './preview-draft.js';
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ADR-116 — preview identity foundation barrel.
|
|
3
|
+
*
|
|
4
|
+
* Re-exports the public surface so consumers (PR4 `<Storystick>` + PR5
|
|
5
|
+
* adapter migrations + future framework adapters) import from one
|
|
6
|
+
* place.
|
|
7
|
+
*/
|
|
8
|
+
export { INITIAL_SNAPSHOT, MAX_RECOVERY_RETRIES, transition, isIdempotentBroadcast, isRejectedBroadcast, } from './identity-state-machine.js';
|
|
9
|
+
export { createIdentityResolver, DEFAULT_CARRIER_TIMEOUT_MS, RECOVERY_BACKOFF_MS, STORAGE_KEY, } from './resolver.js';
|
|
10
|
+
export { createUrlCarrier, URL_SESSION_PARAM } from './carriers/url.js';
|
|
11
|
+
export { createStorageCarrier, readStored, writeStored, clearStored, STORAGE_TTL_MS, } from './carriers/storage.js';
|
|
12
|
+
export { buildSessionRequest, parseInboundBroadcast, attachBroadcastListener, postSessionRequest, } from './carriers/postmessage.js';
|
|
13
|
+
export { createTestAdapter } from './__test-adapter.js';
|
|
14
|
+
export { PREVIEW_SESSION_QUERY_PARAM, PREVIEW_VARIANT_QUERY_PARAM, PREVIEW_RELEASE_QUERY_PARAM, hasPreviewSessionParam, detectPreviewSessionFromWindow, isPreviewIdentity, readPreviewIdentity, readPreviewIdentitySessionId, readPreviewIdentitySessionIdFromSource, readPreviewSessionIdFromSearch, readPreviewVariantIdFromSearch, normalizePreviewVariantId, readPreviewReleaseIdFromSearch, normalizePreviewReleaseId, } from './preview-identity.js';
|
|
15
|
+
export { PreviewScopeConflictError, createPreviewScope, previewScopeKey, readPreviewScopeFromSearch, writePreviewScopeToSearchParams, } from './preview-scope.js';
|
|
16
|
+
export { PREVIEW_DRAFT_QUERY_PARAM, readPreviewDraftDocumentId, previewDraftHref } from './preview-draft.js';
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Public names only. Neither the name nor the presence signal grants access. */
|
|
2
|
+
export declare function scopedPreviewCookieName(prefix: string, scope: {
|
|
3
|
+
projectId: string;
|
|
4
|
+
requestOrigin: string;
|
|
5
|
+
}): string;
|
|
6
|
+
export declare function previewPresenceCookieName(scope: {
|
|
7
|
+
projectId: string;
|
|
8
|
+
requestOrigin: string;
|
|
9
|
+
}): string;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** Public names only. Neither the name nor the presence signal grants access. */
|
|
2
|
+
export function scopedPreviewCookieName(prefix, scope) {
|
|
3
|
+
// Browser cookies do not partition ports; the name must do so explicitly.
|
|
4
|
+
const bytes = new TextEncoder().encode(JSON.stringify([scope.projectId, new URL(scope.requestOrigin).origin]));
|
|
5
|
+
let binary = '';
|
|
6
|
+
for (const byte of bytes)
|
|
7
|
+
binary += String.fromCharCode(byte);
|
|
8
|
+
return `${prefix}_${btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '')}`;
|
|
9
|
+
}
|
|
10
|
+
export function previewPresenceCookieName(scope) {
|
|
11
|
+
return scopedPreviewCookieName('__storystick_preview_present', scope);
|
|
12
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Public request destination, never an authorization or a sticky view setting.
|
|
2
|
+
* Native links omit it; Back/Forward and reload retain the selected document. */
|
|
3
|
+
export declare const PREVIEW_DRAFT_QUERY_PARAM = "_ss_draft";
|
|
4
|
+
export declare function readPreviewDraftDocumentId(search: string): string | null;
|
|
5
|
+
/** Sets the destination explicitly; never inherits the current page's draft. */
|
|
6
|
+
export declare function previewDraftHref(pathname: string, documentId?: string | null): string;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** Public request destination, never an authorization or a sticky view setting.
|
|
2
|
+
* Native links omit it; Back/Forward and reload retain the selected document. */
|
|
3
|
+
export const PREVIEW_DRAFT_QUERY_PARAM = '_ss_draft';
|
|
4
|
+
export function readPreviewDraftDocumentId(search) {
|
|
5
|
+
const values = new URLSearchParams(search).getAll(PREVIEW_DRAFT_QUERY_PARAM);
|
|
6
|
+
if (values.length === 0)
|
|
7
|
+
return null;
|
|
8
|
+
if (values.length !== 1 || !/^[A-Za-z0-9_-]{1,180}$/.test(values[0])) {
|
|
9
|
+
throw new Error('PREVIEW_DRAFT_DESTINATION_INVALID');
|
|
10
|
+
}
|
|
11
|
+
return values[0];
|
|
12
|
+
}
|
|
13
|
+
/** Sets the destination explicitly; never inherits the current page's draft. */
|
|
14
|
+
export function previewDraftHref(pathname, documentId) {
|
|
15
|
+
const url = new URL(pathname, 'https://preview.invalid');
|
|
16
|
+
if (!pathname.startsWith('/') || url.origin !== 'https://preview.invalid') {
|
|
17
|
+
throw new Error('PREVIEW_DESTINATION_INVALID');
|
|
18
|
+
}
|
|
19
|
+
url.searchParams.delete(PREVIEW_DRAFT_QUERY_PARAM);
|
|
20
|
+
if (documentId != null) {
|
|
21
|
+
url.searchParams.set(PREVIEW_DRAFT_QUERY_PARAM, documentId);
|
|
22
|
+
readPreviewDraftDocumentId(url.search);
|
|
23
|
+
}
|
|
24
|
+
return url.pathname + url.search + url.hash;
|
|
25
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export { PREVIEW_SESSION_QUERY_PARAM, readPreviewSessionIdFromSearch, hasPreviewSessionParam } from './session-query.js';
|
|
2
|
+
import type { PreviewIdentity } from './identity-types.js';
|
|
3
|
+
export declare const PREVIEW_VARIANT_QUERY_PARAM = "_ss_variant";
|
|
4
|
+
export declare const PREVIEW_RELEASE_QUERY_PARAM = "_ss_release";
|
|
5
|
+
export interface PreviewIdentitySource {
|
|
6
|
+
getIdentity?: () => PreviewIdentity | null | undefined;
|
|
7
|
+
}
|
|
8
|
+
export interface PreviewSessionDetection {
|
|
9
|
+
active: boolean;
|
|
10
|
+
sessionId: string | null;
|
|
11
|
+
variantId: string | null;
|
|
12
|
+
releaseId: string | null;
|
|
13
|
+
}
|
|
14
|
+
type PreviewWindowLike = {
|
|
15
|
+
parent: unknown;
|
|
16
|
+
location?: {
|
|
17
|
+
search?: string;
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
export declare function isPreviewIdentity(value: unknown): value is PreviewIdentity;
|
|
21
|
+
export declare function readPreviewIdentitySessionId(identity: PreviewIdentity | null | undefined): string | null;
|
|
22
|
+
export declare function readPreviewIdentity(source: PreviewIdentitySource | null | undefined): PreviewIdentity | null;
|
|
23
|
+
export declare function readPreviewIdentitySessionIdFromSource(source: PreviewIdentitySource | null | undefined): string | null;
|
|
24
|
+
export declare function readPreviewVariantIdFromSearch(search: string, paramName?: string): string | null;
|
|
25
|
+
export declare function normalizePreviewVariantId(value: string | null | undefined): string | null;
|
|
26
|
+
export declare function readPreviewReleaseIdFromSearch(search: string, paramName?: string): string | null;
|
|
27
|
+
export declare function normalizePreviewReleaseId(value: string | null | undefined): string | null;
|
|
28
|
+
export declare function detectPreviewSessionFromWindow(win?: PreviewWindowLike | null | undefined, paramName?: string): PreviewSessionDetection;
|