@storystick/preview 0.0.0-stage → 0.2.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (247) hide show
  1. package/LICENSE +112 -0
  2. package/PUBLIC-API.md +56 -0
  3. package/README.md +42 -2
  4. package/dist/.source-hash +1 -0
  5. package/dist/authoring/runtime-contract.d.ts +92 -0
  6. package/dist/authoring/runtime-contract.js +28 -0
  7. package/dist/channel-taxonomy.d.ts +17 -0
  8. package/dist/channel-taxonomy.js +46 -0
  9. package/dist/context-command.d.ts +18 -0
  10. package/dist/context-command.js +38 -0
  11. package/dist/dev/suspicious-codeword-detector.d.ts +15 -0
  12. package/dist/dev/suspicious-codeword-detector.js +220 -0
  13. package/dist/dev/suspicious-codeword-detector.noop.d.ts +3 -0
  14. package/dist/dev/suspicious-codeword-detector.noop.js +4 -0
  15. package/dist/framework/browser-context.d.ts +2 -0
  16. package/dist/framework/browser-context.js +2 -0
  17. package/dist/framework/generation-refresh.d.ts +23 -0
  18. package/dist/framework/generation-refresh.js +82 -0
  19. package/dist/framework/generation-signal.d.ts +8 -0
  20. package/dist/framework/generation-signal.js +17 -0
  21. package/dist/framework/projection-generation-watch.d.ts +5 -0
  22. package/dist/framework/projection-generation-watch.js +53 -0
  23. package/dist/framework/render-receipt.d.ts +8 -0
  24. package/dist/framework/render-receipt.js +10 -0
  25. package/dist/framework/runtime-adapter-store.d.ts +30 -0
  26. package/dist/framework/runtime-adapter-store.js +81 -0
  27. package/dist/grant-coordinator.d.ts +53 -0
  28. package/dist/grant-coordinator.js +329 -0
  29. package/dist/grant-lifecycle.d.ts +52 -0
  30. package/dist/grant-lifecycle.js +74 -0
  31. package/dist/identity/__test-adapter.d.ts +34 -0
  32. package/dist/identity/__test-adapter.js +59 -0
  33. package/dist/identity/browser-context-gate.d.ts +4 -0
  34. package/dist/identity/browser-context-gate.js +22 -0
  35. package/dist/identity/browser-context.d.ts +13 -0
  36. package/dist/identity/browser-context.js +22 -0
  37. package/dist/identity/carriers/postmessage.d.ts +84 -0
  38. package/dist/identity/carriers/postmessage.js +177 -0
  39. package/dist/identity/carriers/storage-state.d.ts +41 -0
  40. package/dist/identity/carriers/storage-state.js +99 -0
  41. package/dist/identity/carriers/storage.d.ts +39 -0
  42. package/dist/identity/carriers/storage.js +182 -0
  43. package/dist/identity/carriers/url.d.ts +19 -0
  44. package/dist/identity/carriers/url.js +38 -0
  45. package/dist/identity/identity-state-machine.d.ts +70 -0
  46. package/dist/identity/identity-state-machine.js +244 -0
  47. package/dist/identity/identity-types.d.ts +192 -0
  48. package/dist/identity/identity-types.js +22 -0
  49. package/dist/identity/index.d.ts +18 -0
  50. package/dist/identity/index.js +16 -0
  51. package/dist/identity/preview-cookie.d.ts +9 -0
  52. package/dist/identity/preview-cookie.js +12 -0
  53. package/dist/identity/preview-draft.d.ts +6 -0
  54. package/dist/identity/preview-draft.js +25 -0
  55. package/dist/identity/preview-identity.d.ts +28 -0
  56. package/dist/identity/preview-identity.js +84 -0
  57. package/dist/identity/preview-scope.d.ts +28 -0
  58. package/dist/identity/preview-scope.js +47 -0
  59. package/dist/identity/resolver.d.ts +110 -0
  60. package/dist/identity/resolver.js +334 -0
  61. package/dist/identity/session-query.d.ts +4 -0
  62. package/dist/identity/session-query.js +19 -0
  63. package/dist/index.d.ts +90 -0
  64. package/dist/index.js +788 -0
  65. package/dist/interaction-mode.d.ts +58 -0
  66. package/dist/interaction-mode.js +93 -0
  67. package/dist/nav-handler.d.ts +95 -0
  68. package/dist/nav-handler.js +84 -0
  69. package/dist/overlay-label.d.ts +79 -0
  70. package/dist/overlay-label.js +73 -0
  71. package/dist/page-presentation.d.ts +24 -0
  72. package/dist/page-presentation.js +32 -0
  73. package/dist/provenance/anchor-map.d.ts +95 -0
  74. package/dist/provenance/anchor-map.js +177 -0
  75. package/dist/provenance/carrier-plan.d.ts +51 -0
  76. package/dist/provenance/carrier-plan.js +90 -0
  77. package/dist/provenance/coded-text.d.ts +20 -0
  78. package/dist/provenance/coded-text.js +302 -0
  79. package/dist/provenance/direct-edit-locator.d.ts +29 -0
  80. package/dist/provenance/direct-edit-locator.js +218 -0
  81. package/dist/provenance/editable-leaf.d.ts +134 -0
  82. package/dist/provenance/editable-leaf.js +483 -0
  83. package/dist/provenance/editor-target.d.ts +29 -0
  84. package/dist/provenance/editor-target.js +46 -0
  85. package/dist/provenance/field-leaf-walker.d.ts +21 -0
  86. package/dist/provenance/field-leaf-walker.js +98 -0
  87. package/dist/provenance/frame-dependencies.d.ts +212 -0
  88. package/dist/provenance/frame-dependencies.js +550 -0
  89. package/dist/provenance/index.d.ts +26 -0
  90. package/dist/provenance/index.js +26 -0
  91. package/dist/provenance/local-events.d.ts +1 -0
  92. package/dist/provenance/local-events.js +1 -0
  93. package/dist/provenance/markers.d.ts +21 -0
  94. package/dist/provenance/markers.js +70 -0
  95. package/dist/provenance/multi-frame-anchor-map.d.ts +54 -0
  96. package/dist/provenance/multi-frame-anchor-map.js +109 -0
  97. package/dist/provenance/normalize.d.ts +2 -0
  98. package/dist/provenance/normalize.js +19 -0
  99. package/dist/provenance/overlay-focus-echo.d.ts +17 -0
  100. package/dist/provenance/overlay-focus-echo.js +16 -0
  101. package/dist/provenance/overlay-projection.d.ts +65 -0
  102. package/dist/provenance/overlay-projection.js +201 -0
  103. package/dist/provenance/owner-map.d.ts +55 -0
  104. package/dist/provenance/owner-map.js +239 -0
  105. package/dist/provenance/read-carrier.d.ts +22 -0
  106. package/dist/provenance/read-carrier.js +92 -0
  107. package/dist/provenance/recorder.d.ts +38 -0
  108. package/dist/provenance/recorder.js +82 -0
  109. package/dist/provenance/render-context.d.ts +106 -0
  110. package/dist/provenance/render-context.js +338 -0
  111. package/dist/provenance/reveal-contract.d.ts +44 -0
  112. package/dist/provenance/reveal-contract.js +1 -0
  113. package/dist/provenance/richtext-slice-embed-address.d.ts +20 -0
  114. package/dist/provenance/richtext-slice-embed-address.js +35 -0
  115. package/dist/provenance/runtime-script.d.ts +1 -0
  116. package/dist/provenance/runtime-script.js +2217 -0
  117. package/dist/provenance/sidecar-store.d.ts +63 -0
  118. package/dist/provenance/sidecar-store.js +151 -0
  119. package/dist/provenance/sidecar.d.ts +34 -0
  120. package/dist/provenance/sidecar.js +227 -0
  121. package/dist/provenance/slice-model.d.ts +28 -0
  122. package/dist/provenance/slice-model.js +86 -0
  123. package/dist/provenance/source-identity.d.ts +3 -0
  124. package/dist/provenance/source-identity.js +7 -0
  125. package/dist/provenance/source-map-materializer.d.ts +76 -0
  126. package/dist/provenance/source-map-materializer.js +286 -0
  127. package/dist/provenance/source-registry.d.ts +103 -0
  128. package/dist/provenance/source-registry.js +562 -0
  129. package/dist/provenance/transformed-output-gate.d.ts +161 -0
  130. package/dist/provenance/transformed-output-gate.js +171 -0
  131. package/dist/provenance/types.d.ts +102 -0
  132. package/dist/provenance/types.js +1 -0
  133. package/dist/refresh-policy.d.ts +11 -0
  134. package/dist/refresh-policy.js +14 -0
  135. package/dist/reveal-target.d.ts +8 -0
  136. package/dist/reveal-target.js +47 -0
  137. package/dist/route-paths.d.ts +11 -0
  138. package/dist/route-paths.js +14 -0
  139. package/dist/router-subscriber.d.ts +92 -0
  140. package/dist/router-subscriber.js +44 -0
  141. package/dist/runtime/anchor-types.d.ts +5 -0
  142. package/dist/runtime/anchor-types.js +1 -0
  143. package/dist/runtime/authority-store.d.ts +31 -0
  144. package/dist/runtime/authority-store.js +56 -0
  145. package/dist/runtime/boot.d.ts +52 -0
  146. package/dist/runtime/boot.js +379 -0
  147. package/dist/runtime/content-refresh.d.ts +30 -0
  148. package/dist/runtime/content-refresh.js +68 -0
  149. package/dist/runtime/emit-runtime-error.d.ts +51 -0
  150. package/dist/runtime/emit-runtime-error.js +64 -0
  151. package/dist/runtime/field-proxy.d.ts +112 -0
  152. package/dist/runtime/field-proxy.js +471 -0
  153. package/dist/runtime/form-contract-reporter.d.ts +99 -0
  154. package/dist/runtime/form-contract-reporter.js +391 -0
  155. package/dist/runtime/invalidation-adapter.d.ts +17 -0
  156. package/dist/runtime/invalidation-adapter.js +42 -0
  157. package/dist/runtime/live-query-overlay.d.ts +39 -0
  158. package/dist/runtime/live-query-overlay.js +345 -0
  159. package/dist/runtime/momentary-navigate.d.ts +16 -0
  160. package/dist/runtime/momentary-navigate.js +106 -0
  161. package/dist/runtime/observation-scheduler.d.ts +15 -0
  162. package/dist/runtime/observation-scheduler.js +42 -0
  163. package/dist/runtime/perf-metrics.d.ts +28 -0
  164. package/dist/runtime/perf-metrics.js +90 -0
  165. package/dist/runtime/preview-interaction-policy.d.ts +1 -0
  166. package/dist/runtime/preview-interaction-policy.js +103 -0
  167. package/dist/runtime/preview-overlay-painter.d.ts +27 -0
  168. package/dist/runtime/preview-overlay-painter.js +1732 -0
  169. package/dist/runtime/preview-overlay-sidecar.d.ts +26 -0
  170. package/dist/runtime/preview-overlay-sidecar.js +45 -0
  171. package/dist/runtime/preview-perspective-alignment.d.ts +15 -0
  172. package/dist/runtime/preview-perspective-alignment.js +40 -0
  173. package/dist/runtime/preview-perspective-intent-state.d.ts +23 -0
  174. package/dist/runtime/preview-perspective-intent-state.js +5 -0
  175. package/dist/runtime/preview-transition-state.d.ts +142 -0
  176. package/dist/runtime/preview-transition-state.js +257 -0
  177. package/dist/runtime/render-mode-state.d.ts +51 -0
  178. package/dist/runtime/render-mode-state.js +76 -0
  179. package/dist/runtime/request-id.d.ts +4 -0
  180. package/dist/runtime/request-id.js +8 -0
  181. package/dist/runtime/resolve-schema-field.d.ts +99 -0
  182. package/dist/runtime/resolve-schema-field.js +356 -0
  183. package/dist/runtime/runtime-error-dispatch.d.ts +23 -0
  184. package/dist/runtime/runtime-error-dispatch.js +75 -0
  185. package/dist/runtime/schema-store.d.ts +121 -0
  186. package/dist/runtime/schema-store.js +667 -0
  187. package/dist/runtime/snapshot-assembly.d.ts +104 -0
  188. package/dist/runtime/snapshot-assembly.js +553 -0
  189. package/dist/runtime/snapshot-reporter.d.ts +55 -0
  190. package/dist/runtime/snapshot-reporter.js +375 -0
  191. package/dist/runtime/standalone-overlay-controller.d.ts +33 -0
  192. package/dist/runtime/standalone-overlay-controller.js +281 -0
  193. package/dist/runtime/store.d.ts +307 -0
  194. package/dist/runtime/store.js +1026 -0
  195. package/dist/runtime/windowed-minimap-projection.d.ts +28 -0
  196. package/dist/runtime/windowed-minimap-projection.js +189 -0
  197. package/dist/runtime/workspace-shortcuts.d.ts +30 -0
  198. package/dist/runtime/workspace-shortcuts.js +188 -0
  199. package/dist/sdk-error-copy.d.ts +48 -0
  200. package/dist/sdk-error-copy.js +182 -0
  201. package/dist/select-cursor.d.ts +2 -0
  202. package/dist/select-cursor.js +23 -0
  203. package/dist/server/admission-cookie.d.ts +38 -0
  204. package/dist/server/admission-cookie.js +122 -0
  205. package/dist/server/control-client.d.ts +29 -0
  206. package/dist/server/control-client.js +22 -0
  207. package/dist/server/control-errors.d.ts +22 -0
  208. package/dist/server/control-errors.js +182 -0
  209. package/dist/server/draft-route.d.ts +12 -0
  210. package/dist/server/draft-route.js +45 -0
  211. package/dist/server/extract-token.d.ts +30 -0
  212. package/dist/server/extract-token.js +98 -0
  213. package/dist/server/grant.d.ts +128 -0
  214. package/dist/server/grant.js +395 -0
  215. package/dist/server/ordinary-read-provenance.d.ts +11 -0
  216. package/dist/server/ordinary-read-provenance.js +124 -0
  217. package/dist/server/preview-read-metadata.d.ts +10 -0
  218. package/dist/server/preview-read-metadata.js +27 -0
  219. package/dist/server/provenance-frame-builder.d.ts +53 -0
  220. package/dist/server/provenance-frame-builder.js +102 -0
  221. package/dist/server/provenance-runtime.d.ts +70 -0
  222. package/dist/server/provenance-runtime.js +46 -0
  223. package/dist/server/read-context-command.d.ts +11 -0
  224. package/dist/server/read-context-command.js +53 -0
  225. package/dist/server/read-identity.d.ts +3 -0
  226. package/dist/server/read-identity.js +63 -0
  227. package/dist/server/read-lifecycle.d.ts +17 -0
  228. package/dist/server/read-lifecycle.js +52 -0
  229. package/dist/server/render-frame-provenance.d.ts +133 -0
  230. package/dist/server/render-frame-provenance.js +676 -0
  231. package/dist/server/render-id.d.ts +15 -0
  232. package/dist/server/render-id.js +22 -0
  233. package/dist/server/route-document-provenance.d.ts +77 -0
  234. package/dist/server/route-document-provenance.js +1327 -0
  235. package/dist/server/route-session-cookie.d.ts +33 -0
  236. package/dist/server/route-session-cookie.js +54 -0
  237. package/dist/session-projections.d.ts +33 -0
  238. package/dist/session-projections.js +43 -0
  239. package/dist/session-subscriber.d.ts +124 -0
  240. package/dist/session-subscriber.js +79 -0
  241. package/dist/sha256-fallback.d.ts +30 -0
  242. package/dist/sha256-fallback.js +126 -0
  243. package/dist/types.d.ts +446 -0
  244. package/dist/types.js +85 -0
  245. package/dist/validators.d.ts +61 -0
  246. package/dist/validators.js +110 -0
  247. package/package.json +210 -3
@@ -0,0 +1,33 @@
1
+ import { type PreviewGrantActivationResponse, type PreviewGrantCookie } from './grant.js';
2
+ import { type AdmissionScope, type PreviewAdmission, type PreviewCookieReader } from './admission-cookie.js';
3
+ type CookieWritableResponse = {
4
+ cookies: {
5
+ set: (name: string, value: string, options: {
6
+ httpOnly?: boolean;
7
+ secure?: boolean;
8
+ sameSite?: 'none' | 'lax' | 'strict' | boolean;
9
+ partitioned?: boolean;
10
+ path?: string;
11
+ maxAge?: number;
12
+ }) => void;
13
+ };
14
+ };
15
+ export declare function buildPreviewGrantCookieFromActivation(activation: PreviewGrantActivationResponse, now?: number, applicationBuildId?: string): PreviewGrantCookie;
16
+ type CookieWriteOptions = {
17
+ response: CookieWritableResponse;
18
+ scope: AdmissionScope;
19
+ reader: PreviewCookieReader;
20
+ requestUrl: string;
21
+ basePath: string;
22
+ now?: number;
23
+ };
24
+ /** Deletes only exact names observed by this request, never a shared current slot. */
25
+ export declare function prunePreviewAdmissionCookies(args: CookieWriteOptions, sequence: number): void;
26
+ export declare function setPreviewGrantCookieOnResponse(args: CookieWriteOptions & {
27
+ cookie: PreviewGrantCookie;
28
+ sequence: number;
29
+ }): Promise<void>;
30
+ export declare function closePreviewAdmissionOnResponse(args: CookieWriteOptions & {
31
+ admission: PreviewAdmission;
32
+ }): Promise<void>;
33
+ export {};
@@ -0,0 +1,54 @@
1
+ import { previewPresenceCookieName } from '@storystick/preview/identity/preview-cookie';
2
+ import { previewCookieAttributes, previewCapabilityCookieName } from './grant.js';
3
+ import { admissionCookieName, admissionPresenceName, encodePreviewAdmission, invalidAdmissionNames, PREVIEW_CLOSURE_TTL_MS } from './admission-cookie.js';
4
+ export function buildPreviewGrantCookieFromActivation(activation, now = Date.now(), applicationBuildId) {
5
+ return { v: 2, grantId: activation.grantId, grantToken: activation.grantToken,
6
+ grantExpiresAt: activation.grantExpiresAt, projectId: activation.projectId, previewOrigin: activation.previewOrigin,
7
+ accessToken: activation.token, accessExpiresAt: now + activation.tokenExpiresIn * 1000,
8
+ contentShapeCapability: activation.contentShapeCapability, ...(applicationBuildId ? { applicationBuildId } : {}) };
9
+ }
10
+ async function writeAdmission(args, admission) {
11
+ const attrs = previewCookieAttributes({ expiresIn: Math.ceil((admission.expiresAt - (args.now ?? Date.now())) / 1000),
12
+ requestUrl: args.requestUrl, basePath: args.basePath });
13
+ args.response.cookies.set(await admissionCookieName(args.scope, admission), await encodePreviewAdmission(admission, args.scope), attrs);
14
+ args.response.cookies.set(await admissionPresenceName(args.scope, admission), '1', { ...attrs, httpOnly: false });
15
+ prunePreviewAdmissionCookies(args, admission.sequence);
16
+ }
17
+ /** Deletes only exact names observed by this request, never a shared current slot. */
18
+ export function prunePreviewAdmissionCookies(args, sequence) {
19
+ const prefixes = [previewCapabilityCookieName(args.scope), previewPresenceCookieName(args.scope)];
20
+ const attrs = previewCookieAttributes({ expiresIn: 0, requestUrl: args.requestUrl, basePath: args.basePath });
21
+ for (const { name } of args.reader.getAll()) {
22
+ for (const prefix of prefixes) {
23
+ const suffix = name.startsWith(prefix + '_') ? name.slice(prefix.length) : '';
24
+ const match = /^_[ax]([1-9]\d*)_[A-Za-z0-9_-]{22}$/.exec(suffix);
25
+ // Retired fixed cookies cannot override admissions, and may be removed.
26
+ if (name === prefix || (match && Number(match[1]) < sequence)) {
27
+ args.response.cookies.set(name, '', { ...attrs, httpOnly: prefix === prefixes[0] });
28
+ }
29
+ }
30
+ }
31
+ }
32
+ export async function setPreviewGrantCookieOnResponse(args) {
33
+ const attrs = previewCookieAttributes({ expiresIn: 0, requestUrl: args.requestUrl, basePath: args.basePath });
34
+ for (const name of await invalidAdmissionNames(args.reader, args.scope)) {
35
+ args.response.cookies.set(name, '', attrs);
36
+ args.response.cookies.set(name.replace(previewCapabilityCookieName(args.scope), previewPresenceCookieName(args.scope)), '', { ...attrs, httpOnly: false });
37
+ }
38
+ await writeAdmission(args, { kind: 'active', sequence: args.sequence, expiresAt: args.cookie.grantExpiresAt, grant: args.cookie });
39
+ }
40
+ export async function closePreviewAdmissionOnResponse(args) {
41
+ // A late activation response for this same grant cannot overwrite its closure:
42
+ // active and closed have different names, and closed wins at equal sequence.
43
+ await writeAdmission(args, { kind: 'closed', sequence: args.admission.sequence,
44
+ expiresAt: Math.max(args.admission.expiresAt, (args.now ?? Date.now()) + PREVIEW_CLOSURE_TTL_MS) });
45
+ // Keep the small closure, not its credentials. A delayed same-rank active
46
+ // response remains suppressed by the distinct closure name.
47
+ const active = { kind: 'active', sequence: args.admission.sequence };
48
+ const names = [await admissionCookieName(args.scope, active), await admissionPresenceName(args.scope, active)];
49
+ const attrs = previewCookieAttributes({ expiresIn: 0, requestUrl: args.requestUrl, basePath: args.basePath });
50
+ for (const { name } of args.reader.getAll())
51
+ if (names.includes(name)) {
52
+ args.response.cookies.set(name, '', { ...attrs, httpOnly: name === names[0] });
53
+ }
54
+ }
@@ -0,0 +1,33 @@
1
+ import type { PreviewSessionData } from './session-subscriber.js';
2
+ import { type PreviewContentRefreshDecision } from './provenance/index.js';
3
+ export interface PreviewSessionRoutingState {
4
+ sessionId: string;
5
+ previewedDocumentId: string;
6
+ pathname: string;
7
+ activeVariantId: string | null;
8
+ activeReleaseId: string | null;
9
+ parentOrigin: string;
10
+ }
11
+ export interface PreviewSessionDraftState {
12
+ draftDocumentId: string | null;
13
+ draftTarget: PreviewSessionData['draftTarget'];
14
+ }
15
+ export interface PreviewSessionRevisionState {
16
+ contentRevision: number;
17
+ contentRefreshDecision: PreviewContentRefreshDecision;
18
+ }
19
+ export interface PreviewSessionIdentityState {
20
+ sessionId: string;
21
+ parentOrigin: string;
22
+ }
23
+ export interface PreviewSessionProjections {
24
+ identity: PreviewSessionIdentityState;
25
+ routing: PreviewSessionRoutingState;
26
+ draft: PreviewSessionDraftState;
27
+ revision: PreviewSessionRevisionState;
28
+ }
29
+ export declare function projectPreviewSessionIdentity(session: PreviewSessionData): PreviewSessionIdentityState;
30
+ export declare function projectPreviewSessionRouting(session: PreviewSessionData): PreviewSessionRoutingState;
31
+ export declare function projectPreviewSessionDraft(session: PreviewSessionData): PreviewSessionDraftState;
32
+ export declare function projectPreviewSessionRevision(session: PreviewSessionData): PreviewSessionRevisionState;
33
+ export declare function projectPreviewSession(session: PreviewSessionData): PreviewSessionProjections;
@@ -0,0 +1,43 @@
1
+ import { createPreviewContentRefreshDecision, isPreviewContentRefreshDecision, } from './provenance/index.js';
2
+ export function projectPreviewSessionIdentity(session) {
3
+ return {
4
+ sessionId: session.sessionId,
5
+ parentOrigin: session.parentOrigin,
6
+ };
7
+ }
8
+ export function projectPreviewSessionRouting(session) {
9
+ return {
10
+ sessionId: session.sessionId,
11
+ previewedDocumentId: session.previewedDocumentId,
12
+ pathname: session.pathname,
13
+ activeVariantId: session.activeVariantId,
14
+ activeReleaseId: session.activeReleaseId ?? null,
15
+ parentOrigin: session.parentOrigin,
16
+ };
17
+ }
18
+ export function projectPreviewSessionDraft(session) {
19
+ return {
20
+ draftDocumentId: session.draftDocumentId,
21
+ draftTarget: session.draftTarget ?? null,
22
+ };
23
+ }
24
+ export function projectPreviewSessionRevision(session) {
25
+ return {
26
+ contentRevision: session.contentRevision,
27
+ contentRefreshDecision: isPreviewContentRefreshDecision(session.contentRefreshDecision)
28
+ ? session.contentRefreshDecision
29
+ : createPreviewContentRefreshDecision({
30
+ refresh: true,
31
+ affectedFrameIds: [],
32
+ reason: 'legacy-refresh',
33
+ }),
34
+ };
35
+ }
36
+ export function projectPreviewSession(session) {
37
+ return {
38
+ identity: projectPreviewSessionIdentity(session),
39
+ routing: projectPreviewSessionRouting(session),
40
+ draft: projectPreviewSessionDraft(session),
41
+ revision: projectPreviewSessionRevision(session),
42
+ };
43
+ }
@@ -0,0 +1,124 @@
1
+ /**
2
+ * `getPreviewSession` Convex sub wrapper (ADR-087 §3.4).
3
+ *
4
+ * Once the iframe has acquired a PreviewGrant-backed access JWT and created
5
+ * its Convex client, the SDK subscribes to
6
+ * `sdkQueries:getPreviewSession({ sessionId })` to learn the live
7
+ * preview-routing state : which document is currently being edited
8
+ * (`previewedDocumentId`), the active variant (`activeVariantId`),
9
+ * the pathname Studio wants the iframe to be on (`pathname`), and
10
+ * the parent origin recorded server-side as defense-in-depth
11
+ * (`parentOrigin`, CSO-PREVIEW-4).
12
+ *
13
+ * PreviewGrant/context failures are terminal. Unstructured failures (network,
14
+ * relay restarts, access-token rollover) map to `UNKNOWN` so the coordinator
15
+ * can reconnect while preserving the last rendered frame.
16
+ *
17
+ * The wrapper accepts an opaque `subscribe` callable so tests don't
18
+ * need a real `ConvexReactClient`. In production the callable wraps
19
+ * the documented `client.watchQuery(query, args).onUpdate(callback)`
20
+ * pattern from `convex/react` — the callback receives no args, and
21
+ * the wrapper pulls the current value via `localQueryResult()` (a
22
+ * thrown ConvexError there propagates as an `onError` call).
23
+ */
24
+ import type { PreviewContentRefreshDecision } from './provenance/index.js';
25
+ import type { PreviewQueryTargetingSignalV1 } from '@storystick/types/preview-query-targeting';
26
+ /** Shape of the session row returned by `getPreviewSession`. */
27
+ export interface PreviewSessionData {
28
+ sessionId: string;
29
+ readContext: import('@storystick/types/preview-query-targeting').PreviewReadContext;
30
+ readContextStatus: 'active' | 'ended';
31
+ readContextNotice?: 'release-published' | 'release-archived' | null;
32
+ previewedDocumentId: string;
33
+ pathname: string;
34
+ activeVariantId: string | null;
35
+ activeReleaseId: string | null;
36
+ parentOrigin: string;
37
+ /** Frozen, validated Studio opening destination; not the currently browsed page. */
38
+ standaloneStudioUrl?: string | null;
39
+ /**
40
+ * ADR-096: monotonic counter bumped from Studio's autosave path on
41
+ * each successful document write. The Next adapter's
42
+ * `<StorystickPreview>` observes this field via the Provider's context
43
+ * and fires `router.refresh()` on forward jumps. Defaults to 0 for
44
+ * legacy rows (pre-ADR-096) so the SDK contract is total.
45
+ */
46
+ contentRevision: number;
47
+ contentRefreshDecision?: PreviewContentRefreshDecision;
48
+ /**
49
+ * ADR-099 D11: Cas A draft signal. `null` when not in Cas A — the
50
+ * customer's layout falls through to its normal route. `string` when
51
+ * Cas A is active — `resolvePreviewDraft()` (`@storystick/next/server`)
52
+ * fetches the doc by id and the layout substitutes children with the
53
+ * customer's `<Renderer>`. Total contract (`string | null`, never
54
+ * `undefined`).
55
+ */
56
+ draftDocumentId: string | null;
57
+ draftTarget?: {
58
+ kind: 'document';
59
+ id: string;
60
+ } | {
61
+ kind: 'template';
62
+ id: string;
63
+ } | null;
64
+ }
65
+ export interface PreviewContentSignalData {
66
+ sessionId: string;
67
+ contentRevision: number;
68
+ contentRefreshDecision?: PreviewContentRefreshDecision;
69
+ /** Content Plane source sequence this invalidation must observe. */
70
+ requiredProjectedThroughSeq?: number;
71
+ queryTargeting?: PreviewQueryTargetingSignalV1;
72
+ }
73
+ /** Error codes the wrapper maps from `ConvexError.data.code`. */
74
+ export type PreviewSessionErrorCode = 'PREVIEW_REQUIRED' | 'PREVIEW_GRANT_REQUIRED' | 'PREVIEW_GRANT_INVALID' | 'PREVIEW_GRANT_REVOKED' | 'PREVIEW_ACCESS_REVOKED' | 'PREVIEW_GRANT_EXPIRED' | 'PREVIEW_GRANT_FORBIDDEN' | 'PREVIEW_CONTEXT_INVALID' | 'PREVIEW_VIEW_UNAVAILABLE' | 'PREVIEW_ADMISSION_SUPERSEDED' | 'INVALID_IDENTITY_CLAIM' | 'UNKNOWN';
75
+ export type TerminalPreviewSessionErrorCode = Exclude<PreviewSessionErrorCode, 'UNKNOWN' | 'PREVIEW_ADMISSION_SUPERSEDED'>;
76
+ export declare function isTerminalPreviewSessionErrorCode(code: PreviewSessionErrorCode): code is TerminalPreviewSessionErrorCode;
77
+ /**
78
+ * Minimal subscribe callable shape (matches what `ConvexReactClient`
79
+ * exposes). The caller owns the full client ; this module is just
80
+ * the thin layer that maps Convex errors to our internal error
81
+ * codes.
82
+ */
83
+ export type SubscribeCallable<TArgs, TResult> = (args: TArgs, onUpdate: (value: TResult) => void, onError?: (err: unknown) => void) => () => void;
84
+ export interface SubscribeOptions {
85
+ sessionId: string;
86
+ mode?: 'routing-only';
87
+ /** Called on every successful update. */
88
+ onUpdate: (data: PreviewSessionData) => void;
89
+ /** Called on a typed Convex error mapped to our code. */
90
+ onError: (code: PreviewSessionErrorCode, message: string) => void;
91
+ /**
92
+ * The Convex subscribe callable. Bound to the
93
+ * `sdkQueries:getPreviewSession` UDF by the caller.
94
+ */
95
+ subscribe: SubscribeCallable<{
96
+ sessionId: string;
97
+ mode?: 'routing-only';
98
+ }, PreviewSessionData>;
99
+ }
100
+ export interface SubscribeContentSignalOptions {
101
+ sessionId: string;
102
+ onUpdate: (data: PreviewContentSignalData) => void;
103
+ onError: (code: PreviewSessionErrorCode, message: string) => void;
104
+ subscribe: SubscribeCallable<{
105
+ sessionId: string;
106
+ }, PreviewContentSignalData>;
107
+ }
108
+ /**
109
+ * Map an unknown thrown value to one of our typed codes. ConvexError
110
+ * carries a `data` payload that survives the wire ; we look for a
111
+ * `code` field with the PreviewGrant boundary values, falling back to
112
+ * `UNKNOWN` for transport/runtime failures.
113
+ */
114
+ export declare function mapPreviewSessionError(err: unknown): {
115
+ code: PreviewSessionErrorCode;
116
+ message: string;
117
+ };
118
+ /**
119
+ * Subscribe to the live `getPreviewSession` row. Returns an
120
+ * unsubscribe function. The unsubscribe is synchronous and idempotent
121
+ * (calling it twice is a no-op).
122
+ */
123
+ export declare function subscribePreviewSession(opts: SubscribeOptions): () => void;
124
+ export declare function subscribePreviewContentSignal(opts: SubscribeContentSignalOptions): () => void;
@@ -0,0 +1,79 @@
1
+ import { isPreviewReadContext } from '@storystick/types/preview-query-targeting';
2
+ export function isTerminalPreviewSessionErrorCode(code) {
3
+ return code !== 'UNKNOWN' && code !== 'PREVIEW_ADMISSION_SUPERSEDED';
4
+ }
5
+ /**
6
+ * Map an unknown thrown value to one of our typed codes. ConvexError
7
+ * carries a `data` payload that survives the wire ; we look for a
8
+ * `code` field with the PreviewGrant boundary values, falling back to
9
+ * `UNKNOWN` for transport/runtime failures.
10
+ */
11
+ export function mapPreviewSessionError(err) {
12
+ if (err && typeof err === 'object') {
13
+ const e = err;
14
+ const code = e.data?.code;
15
+ if (code === 'PREVIEW_REQUIRED' ||
16
+ code === 'PREVIEW_GRANT_REQUIRED' ||
17
+ code === 'PREVIEW_GRANT_INVALID' ||
18
+ code === 'PREVIEW_GRANT_REVOKED' ||
19
+ code === 'PREVIEW_ACCESS_REVOKED' ||
20
+ code === 'PREVIEW_GRANT_EXPIRED' ||
21
+ code === 'PREVIEW_GRANT_FORBIDDEN' ||
22
+ code === 'PREVIEW_CONTEXT_INVALID' ||
23
+ code === 'PREVIEW_VIEW_UNAVAILABLE' ||
24
+ code === 'PREVIEW_ADMISSION_SUPERSEDED' ||
25
+ code === 'INVALID_IDENTITY_CLAIM') {
26
+ return {
27
+ code,
28
+ message: typeof e.message === 'string' ? e.message : code,
29
+ };
30
+ }
31
+ if (typeof e.message === 'string') {
32
+ return { code: 'UNKNOWN', message: e.message };
33
+ }
34
+ }
35
+ return { code: 'UNKNOWN', message: String(err) };
36
+ }
37
+ /**
38
+ * Subscribe to the live `getPreviewSession` row. Returns an
39
+ * unsubscribe function. The unsubscribe is synchronous and idempotent
40
+ * (calling it twice is a no-op).
41
+ */
42
+ export function subscribePreviewSession(opts) {
43
+ const unsubscribe = opts.subscribe({
44
+ sessionId: opts.sessionId,
45
+ ...(opts.mode ? { mode: opts.mode } : {}),
46
+ }, (value) => {
47
+ if (!value || !isPreviewReadContext(value.readContext) ||
48
+ (value.readContextStatus !== 'active' && value.readContextStatus !== 'ended')) {
49
+ opts.onError('PREVIEW_CONTEXT_INVALID', 'Preview returned an invalid read context.');
50
+ return;
51
+ }
52
+ opts.onUpdate(value);
53
+ }, (err) => {
54
+ const { code, message } = mapPreviewSessionError(err);
55
+ opts.onError(code, message);
56
+ });
57
+ let stopped = false;
58
+ return () => {
59
+ if (stopped)
60
+ return;
61
+ stopped = true;
62
+ unsubscribe();
63
+ };
64
+ }
65
+ export function subscribePreviewContentSignal(opts) {
66
+ const unsubscribe = opts.subscribe({ sessionId: opts.sessionId }, (value) => {
67
+ opts.onUpdate(value);
68
+ }, (err) => {
69
+ const { code, message } = mapPreviewSessionError(err);
70
+ opts.onError(code, message);
71
+ });
72
+ let stopped = false;
73
+ return () => {
74
+ if (stopped)
75
+ return;
76
+ stopped = true;
77
+ unsubscribe();
78
+ };
79
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Pure-JS SHA-256 fallback for `computeRouteHash`.
3
+ *
4
+ * Web Crypto requires a SECURE CONTEXT, and a cross-origin iframe
5
+ * inherits its embedder chain's secure-context state. In dev:
6
+ *
7
+ * - Studio runs on `http://storystick.dojo:3000` — `.dojo` is not
8
+ * `localhost`, so this origin is NOT a potentially-trustworthy
9
+ * URL, so the embedder chain is not secure.
10
+ * - The iframe at `http://localhost:5176` would be a secure context
11
+ * on its own, but per W3C Secure Contexts §4.3 the embedder chain
12
+ * must also be secure → the iframe inherits non-secure →
13
+ * `crypto.subtle` is `undefined`.
14
+ *
15
+ * In production the chain is HTTPS↔HTTPS so Web Crypto is available
16
+ * and this fallback never runs.
17
+ *
18
+ * Correctness invariant : output MUST byte-for-byte match Web Crypto's
19
+ * `SHA-256` hex output for the same input. Verified by unit test —
20
+ * `tests/sha256-fallback.test.ts` cross-checks against Node's
21
+ * webcrypto for the same string inputs.
22
+ *
23
+ * Implementation : direct transcription of FIPS PUB 180-4 §6.2 SHA-256.
24
+ * The constants `K` are §4.2.2 ; the message schedule + compression
25
+ * loop is §6.2.2. Operating on big-endian 32-bit words.
26
+ */
27
+ /**
28
+ * Compute SHA-256 of a UTF-8 string. Returns lowercase hex.
29
+ */
30
+ export declare function sha256HexFallback(input: string): string;
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Pure-JS SHA-256 fallback for `computeRouteHash`.
3
+ *
4
+ * Web Crypto requires a SECURE CONTEXT, and a cross-origin iframe
5
+ * inherits its embedder chain's secure-context state. In dev:
6
+ *
7
+ * - Studio runs on `http://storystick.dojo:3000` — `.dojo` is not
8
+ * `localhost`, so this origin is NOT a potentially-trustworthy
9
+ * URL, so the embedder chain is not secure.
10
+ * - The iframe at `http://localhost:5176` would be a secure context
11
+ * on its own, but per W3C Secure Contexts §4.3 the embedder chain
12
+ * must also be secure → the iframe inherits non-secure →
13
+ * `crypto.subtle` is `undefined`.
14
+ *
15
+ * In production the chain is HTTPS↔HTTPS so Web Crypto is available
16
+ * and this fallback never runs.
17
+ *
18
+ * Correctness invariant : output MUST byte-for-byte match Web Crypto's
19
+ * `SHA-256` hex output for the same input. Verified by unit test —
20
+ * `tests/sha256-fallback.test.ts` cross-checks against Node's
21
+ * webcrypto for the same string inputs.
22
+ *
23
+ * Implementation : direct transcription of FIPS PUB 180-4 §6.2 SHA-256.
24
+ * The constants `K` are §4.2.2 ; the message schedule + compression
25
+ * loop is §6.2.2. Operating on big-endian 32-bit words.
26
+ */
27
+ const K = new Uint32Array([
28
+ 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1,
29
+ 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
30
+ 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786,
31
+ 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
32
+ 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147,
33
+ 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
34
+ 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b,
35
+ 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
36
+ 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a,
37
+ 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
38
+ 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
39
+ ]);
40
+ function rotr(x, n) {
41
+ return ((x >>> n) | (x << (32 - n))) >>> 0;
42
+ }
43
+ function toHex(bytes) {
44
+ let out = '';
45
+ for (let i = 0; i < bytes.length; i++) {
46
+ out += bytes[i].toString(16).padStart(2, '0');
47
+ }
48
+ return out;
49
+ }
50
+ /**
51
+ * Compute SHA-256 of a UTF-8 string. Returns lowercase hex.
52
+ */
53
+ export function sha256HexFallback(input) {
54
+ const utf8 = new TextEncoder().encode(input);
55
+ return sha256BytesHex(utf8);
56
+ }
57
+ function sha256BytesHex(bytes) {
58
+ // ── Pad message per §5.1.1 ──────────────────────────────────────
59
+ const L = bytes.length;
60
+ // Append 0x80 + 0x00s + 64-bit big-endian length-in-bits.
61
+ const totalLen = ((L + 9 + 63) >>> 6) << 6;
62
+ const padded = new Uint8Array(totalLen);
63
+ padded.set(bytes);
64
+ padded[L] = 0x80;
65
+ const bitLenHi = Math.floor(L / 0x20000000); // (L * 8) / 2^32
66
+ const bitLenLo = (L * 8) >>> 0;
67
+ const view = new DataView(padded.buffer);
68
+ view.setUint32(totalLen - 8, bitLenHi);
69
+ view.setUint32(totalLen - 4, bitLenLo);
70
+ // ── Initial hash values §5.3.3 ──────────────────────────────────
71
+ const H = new Uint32Array([
72
+ 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c,
73
+ 0x1f83d9ab, 0x5be0cd19,
74
+ ]);
75
+ const W = new Uint32Array(64);
76
+ for (let block = 0; block < totalLen; block += 64) {
77
+ // ── Prepare message schedule §6.2.2 step 1 ────────────────────
78
+ for (let t = 0; t < 16; t++) {
79
+ W[t] = view.getUint32(block + t * 4);
80
+ }
81
+ for (let t = 16; t < 64; t++) {
82
+ const wt15 = W[t - 15];
83
+ const wt2 = W[t - 2];
84
+ const s0 = rotr(wt15, 7) ^ rotr(wt15, 18) ^ (wt15 >>> 3);
85
+ const s1 = rotr(wt2, 17) ^ rotr(wt2, 19) ^ (wt2 >>> 10);
86
+ W[t] = (W[t - 16] + s0 + W[t - 7] + s1) >>> 0;
87
+ }
88
+ // ── Init working vars §6.2.2 step 2 ──────────────────────────
89
+ let a = H[0], b = H[1], c = H[2], d = H[3], e = H[4], f = H[5], g = H[6], h = H[7];
90
+ // ── Main loop §6.2.2 step 3 ──────────────────────────────────
91
+ for (let t = 0; t < 64; t++) {
92
+ const S1 = rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25);
93
+ const ch = (e & f) ^ (~e & g);
94
+ const T1 = (h + S1 + ch + K[t] + W[t]) >>> 0;
95
+ const S0 = rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22);
96
+ const mj = (a & b) ^ (a & c) ^ (b & c);
97
+ const T2 = (S0 + mj) >>> 0;
98
+ h = g;
99
+ g = f;
100
+ f = e;
101
+ e = (d + T1) >>> 0;
102
+ d = c;
103
+ c = b;
104
+ b = a;
105
+ a = (T1 + T2) >>> 0;
106
+ }
107
+ // ── §6.2.2 step 4 ────────────────────────────────────────────
108
+ H[0] = (H[0] + a) >>> 0;
109
+ H[1] = (H[1] + b) >>> 0;
110
+ H[2] = (H[2] + c) >>> 0;
111
+ H[3] = (H[3] + d) >>> 0;
112
+ H[4] = (H[4] + e) >>> 0;
113
+ H[5] = (H[5] + f) >>> 0;
114
+ H[6] = (H[6] + g) >>> 0;
115
+ H[7] = (H[7] + h) >>> 0;
116
+ }
117
+ // ── Output as big-endian bytes → hex ────────────────────────────
118
+ const out = new Uint8Array(32);
119
+ for (let i = 0; i < 8; i++) {
120
+ out[i * 4 + 0] = (H[i] >>> 24) & 0xff;
121
+ out[i * 4 + 1] = (H[i] >>> 16) & 0xff;
122
+ out[i * 4 + 2] = (H[i] >>> 8) & 0xff;
123
+ out[i * 4 + 3] = H[i] & 0xff;
124
+ }
125
+ return toHex(out);
126
+ }