camox 0.59.0 → 0.61.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.
Files changed (55) hide show
  1. package/dist/core/createApp.d.ts +8 -8
  2. package/dist/core/createBlock.d.ts +1 -2
  3. package/dist/core/createLayout.d.ts +10 -2
  4. package/dist/core/createLayout.js +1 -3
  5. package/dist/core/editing/BlockEditingRuntime.d.ts +1 -0
  6. package/dist/core/editing/CompleteBlockEditingRuntime.js +12 -9
  7. package/dist/core/editing/createEditableBlock.d.ts +4 -4
  8. package/dist/core/editing/createEditableBlock.js +20 -26
  9. package/dist/core/lib/artifactData.js +2 -2
  10. package/dist/core/lib/contentType.js +5 -4
  11. package/dist/features/preview/components/SelectionEditor.js +1 -1
  12. package/dist/features/renderer/RendererApp.js +187 -103
  13. package/dist/features/renderer/rendererHost.js +23 -16
  14. package/dist/features/renderer/studioConnection.js +8 -4
  15. package/dist/features/runtime/loopbackFetch.d.ts +8 -0
  16. package/dist/features/runtime/publicCache.d.ts +21 -0
  17. package/dist/features/runtime/publishingDraft.d.ts +4 -0
  18. package/dist/features/runtime/runtime.js +54 -3
  19. package/dist/features/runtime/worker.js +1 -4
  20. package/dist/features/runtime/workerRequest.d.ts +8 -0
  21. package/dist/features/runtime/workerRuntime.d.ts +5 -0
  22. package/dist/features/runtime/workerRuntime.js +6 -0
  23. package/dist/features/sdkBundle/buildSdkBundle.d.ts +40 -0
  24. package/dist/features/sdkBundle/buildSdkBundle.js +323 -0
  25. package/dist/features/sdkBundle/localSdkBundle.d.ts +44 -0
  26. package/dist/features/sdkBundle/localSdkBundle.js +162 -0
  27. package/dist/features/sdkBundle/sdkBundle.d.ts +3 -0
  28. package/dist/features/sdkBundle/sdkBundle.js +4 -0
  29. package/dist/features/sdkBundle/sdkBundleName.d.ts +59 -0
  30. package/dist/features/sdkBundle/sdkBundleName.js +164 -0
  31. package/dist/features/sdkBundle/specifiers.d.ts +34 -0
  32. package/dist/features/sdkBundle/specifiers.js +146 -0
  33. package/dist/features/settings/SocialProfilesSettings.js +284 -151
  34. package/dist/features/settings/useProjectMutation.js +6 -5
  35. package/dist/features/studio/FollowAgentProvider.js +71 -12
  36. package/dist/features/studio/components/ChatWidget.js +85 -43
  37. package/dist/features/studio/components/Patrol.js +311 -208
  38. package/dist/features/studio/components/Squareling.js +164 -66
  39. package/dist/features/studio/components/ThreadComposer.js +139 -69
  40. package/dist/features/studio/components/ThreadMessages.js +115 -45
  41. package/dist/features/studio/editorWhereContext.js +11 -0
  42. package/dist/features/studio/useEditorSelection.js +135 -0
  43. package/dist/features/vite/hostedChecks.js +1 -1
  44. package/dist/features/vite/runtimeDev.js +2 -1
  45. package/dist/features/vite/sdkBundleBuild.js +74 -0
  46. package/dist/features/vite/staticAssetHeaders.js +7 -4
  47. package/dist/features/vite/vite.d.ts +0 -2
  48. package/dist/features/vite/vite.js +33 -3
  49. package/dist/lib/auth.js +24 -58
  50. package/dist/lib/server-auth-cookie.js +12 -2
  51. package/dist/lib/stored-auth-cookies.js +76 -0
  52. package/dist/lib/use-thread.js +9 -5
  53. package/dist/package.js +1 -1
  54. package/dist/studio.css +1 -1
  55. package/package.json +17 -4
@@ -113,9 +113,10 @@ function RendererApp(t0) {
113
113
  }
114
114
  /** A block's thumbnail, rendered by the draft's code into its frame: nothing for a block it lacks. */
115
115
  function RendererThumbnail(t0) {
116
- const $ = c(9);
116
+ const $ = c(17);
117
117
  const { slot, blockType, camoxApp } = t0;
118
118
  const frameWindow = slot.ownerDocument.defaultView;
119
+ const left = useLeftInSlot(slot);
119
120
  const t1 = frameWindow?.frameElement ?? null;
120
121
  let t2;
121
122
  if ($[0] !== frameWindow || $[1] !== t1) {
@@ -130,32 +131,55 @@ function RendererThumbnail(t0) {
130
131
  const frameContext = t2;
131
132
  let t3;
132
133
  if ($[3] !== blockType || $[4] !== camoxApp) {
133
- t3 = /* @__PURE__ */ jsx(ThumbnailErrorBoundary, { children: /* @__PURE__ */ jsx(React.Suspense, {
134
- fallback: null,
135
- children: /* @__PURE__ */ jsx(ThumbnailBlock, {
136
- blockType,
137
- camoxApp
138
- })
139
- }) });
134
+ t3 = /* @__PURE__ */ jsx(ThumbnailBlock, {
135
+ blockType,
136
+ camoxApp
137
+ });
140
138
  $[3] = blockType;
141
139
  $[4] = camoxApp;
142
140
  $[5] = t3;
143
141
  } else t3 = $[5];
144
142
  let t4;
145
- if ($[6] !== frameContext || $[7] !== t3) {
146
- t4 = /* @__PURE__ */ jsx(NavigationProvider, {
143
+ if ($[6] !== left) {
144
+ t4 = /* @__PURE__ */ jsx(RemovesLeft, { nodes: left });
145
+ $[6] = left;
146
+ $[7] = t4;
147
+ } else t4 = $[7];
148
+ let t5;
149
+ if ($[8] !== t3 || $[9] !== t4) {
150
+ t5 = /* @__PURE__ */ jsxs(React.Suspense, {
151
+ fallback: null,
152
+ children: [t3, t4]
153
+ });
154
+ $[8] = t3;
155
+ $[9] = t4;
156
+ $[10] = t5;
157
+ } else t5 = $[10];
158
+ let t6;
159
+ if ($[11] !== left || $[12] !== t5) {
160
+ t6 = /* @__PURE__ */ jsx(ThumbnailErrorBoundary, {
161
+ left,
162
+ children: t5
163
+ });
164
+ $[11] = left;
165
+ $[12] = t5;
166
+ $[13] = t6;
167
+ } else t6 = $[13];
168
+ let t7;
169
+ if ($[14] !== frameContext || $[15] !== t6) {
170
+ t7 = /* @__PURE__ */ jsx(NavigationProvider, {
147
171
  location: THUMBNAIL_LOCATION,
148
172
  navigate: noNavigation,
149
173
  children: /* @__PURE__ */ jsx(FrameContext.Provider, {
150
174
  value: frameContext,
151
- children: t3
175
+ children: t6
152
176
  })
153
177
  });
154
- $[6] = frameContext;
155
- $[7] = t3;
156
- $[8] = t4;
157
- } else t4 = $[8];
158
- return t4;
178
+ $[14] = frameContext;
179
+ $[15] = t6;
180
+ $[16] = t7;
181
+ } else t7 = $[16];
182
+ return t7;
159
183
  }
160
184
  const THUMBNAIL_LOCATION = {
161
185
  href: "",
@@ -173,7 +197,7 @@ var ThumbnailErrorBoundary = class extends React.Component {
173
197
  console.error("Camox: a block's thumbnail failed to render.", error);
174
198
  }
175
199
  render() {
176
- return this.state.failed ? null : this.props.children;
200
+ return this.state.failed ? /* @__PURE__ */ jsx(RemovesLeft, { nodes: this.props.left }) : this.props.children;
177
201
  }
178
202
  };
179
203
  function ThumbnailBlock(t0) {
@@ -188,6 +212,46 @@ function ThumbnailBlock(t0) {
188
212
  } else t1 = $[1];
189
213
  return t1;
190
214
  }
215
+ /**
216
+ * What another renderer document left in `slot`, read before this one renders into it. Each new
217
+ * revision loads another renderer document, and removing the last one's frame never unmounts
218
+ * what it rendered into the studio's frames, so its page stays there.
219
+ */
220
+ function useLeftInSlot(slot) {
221
+ const $ = c(2);
222
+ let t0;
223
+ if ($[0] !== slot) {
224
+ t0 = () => [...slot.childNodes];
225
+ $[0] = slot;
226
+ $[1] = t0;
227
+ } else t0 = $[1];
228
+ const [left] = React.useState(t0);
229
+ return left;
230
+ }
231
+ /**
232
+ * Removes the nodes another renderer left, once what replaces them commits: until then, the
233
+ * frame keeps showing the last page rather than nothing.
234
+ */
235
+ function RemovesLeft(t0) {
236
+ const $ = c(3);
237
+ const { nodes } = t0;
238
+ let t1;
239
+ let t2;
240
+ if ($[0] !== nodes) {
241
+ t1 = () => {
242
+ for (const node of nodes) node.remove();
243
+ };
244
+ t2 = [nodes];
245
+ $[0] = nodes;
246
+ $[1] = t1;
247
+ $[2] = t2;
248
+ } else {
249
+ t1 = $[1];
250
+ t2 = $[2];
251
+ }
252
+ React.useLayoutEffect(t1, t2);
253
+ return null;
254
+ }
191
255
  function locationOf(href) {
192
256
  const url = new URL(href);
193
257
  return {
@@ -199,10 +263,11 @@ function locationOf(href) {
199
263
  }
200
264
  /** One frame's page, with what the studio says of the frame around it. */
201
265
  function RendererFrameContent(t0) {
202
- const $ = c(59);
266
+ const $ = c(65);
203
267
  const { frame: t1, connection, camoxApp, codeLoaded, projectUuid, runtimeBasePath } = t0;
204
268
  const { frameId, slot, target } = t1;
205
269
  const frameWindow = slot.ownerDocument.defaultView;
270
+ const left = useLeftInSlot(slot);
206
271
  const frameElement = frameWindow?.frameElement ?? null;
207
272
  let t2;
208
273
  if ($[0] !== target.href) {
@@ -293,17 +358,14 @@ function RendererFrameContent(t0) {
293
358
  } else t9 = $[18];
294
359
  let t10;
295
360
  if ($[19] !== camoxApp || $[20] !== codeLoaded || $[21] !== projectUuid || $[22] !== rendered || $[23] !== runtimeBasePath || $[24] !== target.derivedLayoutId || $[25] !== target.pathname) {
296
- t10 = /* @__PURE__ */ jsx(React.Suspense, {
297
- fallback: t9,
298
- children: /* @__PURE__ */ jsx(FramePage, {
299
- camoxApp,
300
- codeLoaded,
301
- projectUuid,
302
- runtimeBasePath,
303
- pathname: target.pathname,
304
- derivedLayoutId: target.derivedLayoutId,
305
- onRendered: rendered
306
- })
361
+ t10 = /* @__PURE__ */ jsx(FramePage, {
362
+ camoxApp,
363
+ codeLoaded,
364
+ projectUuid,
365
+ runtimeBasePath,
366
+ pathname: target.pathname,
367
+ derivedLayoutId: target.derivedLayoutId,
368
+ onRendered: rendered
307
369
  });
308
370
  $[19] = camoxApp;
309
371
  $[20] = codeLoaded;
@@ -315,110 +377,128 @@ function RendererFrameContent(t0) {
315
377
  $[26] = t10;
316
378
  } else t10 = $[26];
317
379
  let t11;
318
- if ($[27] !== failed || $[28] !== t10 || $[29] !== target.pathname) {
319
- t11 = /* @__PURE__ */ jsx(FrameErrorBoundary, {
320
- onError: failed,
321
- children: t10
322
- }, target.pathname);
323
- $[27] = failed;
324
- $[28] = t10;
325
- $[29] = target.pathname;
326
- $[30] = t11;
327
- } else t11 = $[30];
380
+ if ($[27] !== left) {
381
+ t11 = /* @__PURE__ */ jsx(RemovesLeft, { nodes: left });
382
+ $[27] = left;
383
+ $[28] = t11;
384
+ } else t11 = $[28];
328
385
  let t12;
329
- if ($[31] !== t11 || $[32] !== t8) {
330
- t12 = /* @__PURE__ */ jsx(React.Activity, {
331
- mode: t8,
332
- children: t11
386
+ if ($[29] !== t10 || $[30] !== t11) {
387
+ t12 = /* @__PURE__ */ jsxs(React.Suspense, {
388
+ fallback: t9,
389
+ children: [t10, t11]
333
390
  });
334
- $[31] = t11;
335
- $[32] = t8;
336
- $[33] = t12;
337
- } else t12 = $[33];
391
+ $[29] = t10;
392
+ $[30] = t11;
393
+ $[31] = t12;
394
+ } else t12 = $[31];
338
395
  let t13;
339
- if ($[34] !== t12 || $[35] !== target.artifactStudio) {
340
- t13 = /* @__PURE__ */ jsx(ArtifactStudioContext, {
341
- value: target.artifactStudio,
396
+ if ($[32] !== failed || $[33] !== left || $[34] !== t12 || $[35] !== target.pathname) {
397
+ t13 = /* @__PURE__ */ jsx(FrameErrorBoundary, {
398
+ onError: failed,
399
+ left,
342
400
  children: t12
343
- });
401
+ }, target.pathname);
402
+ $[32] = failed;
403
+ $[33] = left;
344
404
  $[34] = t12;
345
- $[35] = target.artifactStudio;
405
+ $[35] = target.pathname;
346
406
  $[36] = t13;
347
407
  } else t13 = $[36];
348
408
  let t14;
349
- if ($[37] !== t13 || $[38] !== target.artifactsRun) {
350
- t14 = /* @__PURE__ */ jsx(ArtifactsRunContext, {
351
- value: target.artifactsRun,
409
+ if ($[37] !== t13 || $[38] !== t8) {
410
+ t14 = /* @__PURE__ */ jsx(React.Activity, {
411
+ mode: t8,
352
412
  children: t13
353
413
  });
354
414
  $[37] = t13;
355
- $[38] = target.artifactsRun;
415
+ $[38] = t8;
356
416
  $[39] = t14;
357
417
  } else t14 = $[39];
358
418
  let t15;
359
- if ($[40] !== t14 || $[41] !== target.editable) {
360
- t15 = /* @__PURE__ */ jsx(EditableSurfaceContext, {
361
- value: target.editable,
419
+ if ($[40] !== t14 || $[41] !== target.artifactStudio) {
420
+ t15 = /* @__PURE__ */ jsx(ArtifactStudioContext, {
421
+ value: target.artifactStudio,
362
422
  children: t14
363
423
  });
364
424
  $[40] = t14;
365
- $[41] = target.editable;
425
+ $[41] = target.artifactStudio;
366
426
  $[42] = t15;
367
427
  } else t15 = $[42];
368
428
  let t16;
369
- if ($[43] !== t15 || $[44] !== workspace) {
370
- t16 = /* @__PURE__ */ jsx(CanvasWorkspaceContext, {
371
- value: workspace,
429
+ if ($[43] !== t15 || $[44] !== target.artifactsRun) {
430
+ t16 = /* @__PURE__ */ jsx(ArtifactsRunContext, {
431
+ value: target.artifactsRun,
372
432
  children: t15
373
433
  });
374
434
  $[43] = t15;
375
- $[44] = workspace;
435
+ $[44] = target.artifactsRun;
376
436
  $[45] = t16;
377
437
  } else t16 = $[45];
378
438
  let t17;
379
- if ($[46] !== t16 || $[47] !== target.owner) {
380
- t17 = /* @__PURE__ */ jsx(PreviewEditingOwnerContext, {
381
- value: target.owner,
439
+ if ($[46] !== t16 || $[47] !== target.editable) {
440
+ t17 = /* @__PURE__ */ jsx(EditableSurfaceContext, {
441
+ value: target.editable,
382
442
  children: t16
383
443
  });
384
444
  $[46] = t16;
385
- $[47] = target.owner;
445
+ $[47] = target.editable;
386
446
  $[48] = t17;
387
447
  } else t17 = $[48];
388
448
  let t18;
389
- if ($[49] !== frameContext || $[50] !== t17) {
390
- t18 = /* @__PURE__ */ jsx(FrameContext.Provider, {
391
- value: frameContext,
449
+ if ($[49] !== t17 || $[50] !== workspace) {
450
+ t18 = /* @__PURE__ */ jsx(CanvasWorkspaceContext, {
451
+ value: workspace,
392
452
  children: t17
393
453
  });
394
- $[49] = frameContext;
395
- $[50] = t17;
454
+ $[49] = t17;
455
+ $[50] = workspace;
396
456
  $[51] = t18;
397
457
  } else t18 = $[51];
398
458
  let t19;
399
- if ($[52] !== location || $[53] !== navigate || $[54] !== t18) {
400
- t19 = /* @__PURE__ */ jsx(NavigationProvider, {
401
- location,
402
- navigate,
459
+ if ($[52] !== t18 || $[53] !== target.owner) {
460
+ t19 = /* @__PURE__ */ jsx(PreviewEditingOwnerContext, {
461
+ value: target.owner,
403
462
  children: t18
404
463
  });
405
- $[52] = location;
406
- $[53] = navigate;
407
- $[54] = t18;
408
- $[55] = t19;
409
- } else t19 = $[55];
410
- const content = t19;
464
+ $[52] = t18;
465
+ $[53] = target.owner;
466
+ $[54] = t19;
467
+ } else t19 = $[54];
411
468
  let t20;
412
- if ($[56] !== content || $[57] !== target.locale) {
413
- t20 = target.locale ? /* @__PURE__ */ jsx(ViewLocaleContext, {
469
+ if ($[55] !== frameContext || $[56] !== t19) {
470
+ t20 = /* @__PURE__ */ jsx(FrameContext.Provider, {
471
+ value: frameContext,
472
+ children: t19
473
+ });
474
+ $[55] = frameContext;
475
+ $[56] = t19;
476
+ $[57] = t20;
477
+ } else t20 = $[57];
478
+ let t21;
479
+ if ($[58] !== location || $[59] !== navigate || $[60] !== t20) {
480
+ t21 = /* @__PURE__ */ jsx(NavigationProvider, {
481
+ location,
482
+ navigate,
483
+ children: t20
484
+ });
485
+ $[58] = location;
486
+ $[59] = navigate;
487
+ $[60] = t20;
488
+ $[61] = t21;
489
+ } else t21 = $[61];
490
+ const content = t21;
491
+ let t22;
492
+ if ($[62] !== content || $[63] !== target.locale) {
493
+ t22 = target.locale ? /* @__PURE__ */ jsx(ViewLocaleContext, {
414
494
  value: target.locale,
415
495
  children: content
416
496
  }) : content;
417
- $[56] = content;
418
- $[57] = target.locale;
419
- $[58] = t20;
420
- } else t20 = $[58];
421
- return t20;
497
+ $[62] = content;
498
+ $[63] = target.locale;
499
+ $[64] = t22;
500
+ } else t22 = $[64];
501
+ return t22;
422
502
  }
423
503
  function _temp2() {
424
504
  return _temp;
@@ -539,19 +619,23 @@ var FrameErrorBoundary = class extends React.Component {
539
619
  role: "alert",
540
620
  "data-camox-renderer-error": true,
541
621
  style: ERROR_STYLE,
542
- children: [/* @__PURE__ */ jsx("p", {
543
- style: {
544
- margin: 0,
545
- fontWeight: 600
546
- },
547
- children: "This page couldn’t render."
548
- }), /* @__PURE__ */ jsx("p", {
549
- style: {
550
- margin: "8px 0 0",
551
- fontFamily: "ui-monospace, monospace"
552
- },
553
- children: this.state.error
554
- })]
622
+ children: [
623
+ /* @__PURE__ */ jsx(RemovesLeft, { nodes: this.props.left }),
624
+ /* @__PURE__ */ jsx("p", {
625
+ style: {
626
+ margin: 0,
627
+ fontWeight: 600
628
+ },
629
+ children: "This page couldn’t render."
630
+ }),
631
+ /* @__PURE__ */ jsx("p", {
632
+ style: {
633
+ margin: "8px 0 0",
634
+ fontFamily: "ui-monospace, monospace"
635
+ },
636
+ children: this.state.error
637
+ })
638
+ ]
555
639
  });
556
640
  }
557
641
  };
@@ -6,14 +6,30 @@ import { createContentEditor } from "../../core/editing/contentEdits.js";
6
6
  import { LIVE_KEY_SCOPE, scopeOfKey } from "@camox/api-contract/query-keys";
7
7
 
8
8
  //#region src/features/renderer/rendererHost.ts
9
- const mirrored = {
10
- preview: previewStore,
11
- canvas: canvasStore
9
+ /** The studio's state of each store the renderer mirrors. */
10
+ const storeMessages = {
11
+ preview: () => ({
12
+ type: "store",
13
+ store: "preview",
14
+ context: previewStore.getSnapshot().context
15
+ }),
16
+ canvas: () => ({
17
+ type: "store",
18
+ store: "canvas",
19
+ context: canvasStore.getSnapshot().context
20
+ })
12
21
  };
22
+ /** The stores the renderer forwards its events to, as the host applies them. */
13
23
  const forwarded = {
14
24
  preview: previewStore,
15
25
  referencePickerFocus
16
26
  };
27
+ /** Applies an event the renderer forwarded to the studio's store. */
28
+ const applyStoreEvent = (message) => {
29
+ const store = forwarded[message.store];
30
+ if (!store) return;
31
+ store.send(message.event);
32
+ };
17
33
  /**
18
34
  * The host of the studio whose cache is `studioCache`, viewing the draft `draftScope` names, which
19
35
  * the renderer shows too. Without them, the renderer hears nothing of the studio's cache.
@@ -45,12 +61,8 @@ function createRendererHost({ studioCache, draftScope, queryClient, notify } = {
45
61
  const post = (message) => {
46
62
  if (welcomed) renderer?.(message);
47
63
  };
48
- const postStore = (store) => post({
49
- type: "store",
50
- store,
51
- context: mirrored[store].getSnapshot().context
52
- });
53
- const subscriptions = Object.keys(mirrored).map((store) => mirrored[store].subscribe(() => postStore(store)));
64
+ const postStore = (store) => post(storeMessages[store]());
65
+ const subscriptions = [previewStore.subscribe(() => postStore("preview")), canvasStore.subscribe(() => postStore("canvas"))];
54
66
  const shownScope = () => draftScope?.current() ?? LIVE_KEY_SCOPE;
55
67
  const shown = (queryKey) => scopeOfKey(queryKey) === shownScope();
56
68
  /** Whether a project room invalidation reaches the reads of the draft the renderer shows. */
@@ -103,7 +115,7 @@ function createRendererHost({ studioCache, draftScope, queryClient, notify } = {
103
115
  if (message.sdkVersion !== SDK_VERSION) return setStatus("updating");
104
116
  welcomed = true;
105
117
  setStatus("connected");
106
- for (const store of Object.keys(mirrored)) postStore(store);
118
+ for (const store of Object.keys(storeMessages)) postStore(store);
107
119
  for (const [frameId, { slot, target }] of frames) post({
108
120
  type: "frame",
109
121
  frameId,
@@ -123,12 +135,7 @@ function createRendererHost({ studioCache, draftScope, queryClient, notify } = {
123
135
  });
124
136
  for (const listener of connectionListeners) listener();
125
137
  return;
126
- case "storeEvent": {
127
- const store = forwarded[message.store];
128
- if (!store) return;
129
- store.send(message.event);
130
- return;
131
- }
138
+ case "storeEvent": return applyStoreEvent(message);
132
139
  case "rendered": return frames.get(message.frameId)?.hears.rendered?.();
133
140
  case "error": return frames.get(message.frameId)?.hears.failed?.(message.message);
134
141
  case "navigate": return frames.get(message.frameId)?.hears.navigate?.(message.to);
@@ -5,14 +5,21 @@ import { SDK_VERSION, deferredInbox } from "./protocol.js";
5
5
  import { answerFromCode } from "./definitionAnswers.js";
6
6
 
7
7
  //#region src/features/renderer/studioConnection.ts
8
+ /** The stores of this document that take the studio's state as it is (`mirror`). */
8
9
  const mirrors = {
9
10
  preview: previewStore,
10
11
  canvas: canvasStore
11
12
  };
13
+ /** The stores of this document whose events are forwarded to the studio's. */
12
14
  const forwards = {
13
15
  preview: previewStore,
14
16
  referencePickerFocus
15
17
  };
18
+ /** Has this document's copy of a mirrored store take the studio's state. */
19
+ const mirror = (message) => mirrors[message.store]?.send({
20
+ type: "mirror",
21
+ context: message.context
22
+ });
16
23
  /**
17
24
  * Connects the renderer document to the studio through its handle. This document's stores then
18
25
  * mirror the studio's, its own events are forwarded to the studio, which owns their truth, and
@@ -64,10 +71,7 @@ function connectToStudio(handle, { queryClient, studioCache, draftScope, camoxAp
64
71
  setFrames(frames.filter(({ frameId }) => frameId !== message.frameId));
65
72
  return;
66
73
  case "store":
67
- mirrors[message.store]?.send({
68
- type: "mirror",
69
- context: message.context
70
- });
74
+ mirror(message);
71
75
  return;
72
76
  case "draftStarted":
73
77
  draftScope.enterStarted(message.draftId);
@@ -0,0 +1,8 @@
1
+ //#region src/features/runtime/loopbackFetch.d.ts
2
+ /**
3
+ * Browsers resolve any `*.localhost` name to loopback, but the Workers runtime asks the
4
+ * OS, which need not. Local dev servers (like the per-checkout API) use such names, so
5
+ * the dev worker reaches them on 127.0.0.1 instead.
6
+ */
7
+ export declare function fetchLocalhostOnLoopback(fetch: typeof globalThis.fetch): typeof globalThis.fetch;
8
+ //#endregion
@@ -0,0 +1,21 @@
1
+ //#region src/features/runtime/publicCache.d.ts
2
+ /** Where a website's public renders are kept, and what they're kept under. */
3
+ export interface PublicCache {
4
+ /** Opens Cloudflare's cache in the request's data center: `PUBLIC_CACHE_NAME`. */
5
+ cache: () => Promise<Cache>;
6
+ /** The project's Live cache key, which changes whenever Live does; null when unreadable. */
7
+ liveCacheKey: () => Promise<string | null>;
8
+ /** The website's build, so a release never serves pages naming the last release's assets. */
9
+ build: string;
10
+ waitUntil: (promise: Promise<unknown>) => void;
11
+ }
12
+ /** The named cache public renders are kept in. */
13
+ export declare const PUBLIC_CACHE_NAME = "camox-public-renders";
14
+ /** The project's Live cache key, as the API serves it from its edge cache. */
15
+ export declare function readLiveCacheKey(apiUrl: string, projectUuid: string): Promise<string | null>;
16
+ /**
17
+ * Answers a public request from the cache, or renders it and keeps the render when it may be
18
+ * shared. Without a Live cache key, it renders without the cache: nothing tells when Live changed.
19
+ */
20
+ export declare function respondThroughPublicCache(request: Request, publicCache: PublicCache, render: () => Promise<Response | null>): Promise<Response | null>;
21
+ //#endregion
@@ -0,0 +1,4 @@
1
+ //#region src/features/runtime/publishingDraft.d.ts
2
+ /** Keeps the publishing draft a Worker's environment names, if it names one. */
3
+ export declare function rememberPublishingDraft(env: unknown): void;
4
+ //#endregion
@@ -2,7 +2,8 @@ import { serializeJsonForHtml } from "../../lib/html.js";
2
2
  import { createRequestContext } from "../../core/requestContext.js";
3
3
  import { UNNAMED_DEFAULT_LOCALE, localizedPath, parseLocalePath } from "../../lib/locale-path.js";
4
4
  import { draftNamedBy } from "../../lib/draft-scope.js";
5
- import { buildClearServerAuthCookieHeader, getServerAuthCookieHeader } from "../../lib/server-auth-cookie.js";
5
+ import { buildClearServerAuthCookieHeader, buildServerAuthCookieHeader, getServerAuthCookieHeader } from "../../lib/server-auth-cookie.js";
6
+ import { getCookie, getSetCookie } from "../../lib/stored-auth-cookies.js";
6
7
  import { ARTIFACTS_PATH_PREFIX, RUNTIME_ROUTE_PATHS, matchRuntimeOwnedRoute } from "./runtimeRoutes.js";
7
8
  import { reads } from "../../lib/studio-reads.js";
8
9
  import { asCollectionItem, recordMarkdownTemplate, recordPageValues } from "../../core/recordItem.js";
@@ -79,7 +80,7 @@ const PUBLIC_ROUTE_KINDS = /* @__PURE__ */ new Set([
79
80
  */
80
81
  function isPublicRequest(request, options = {}) {
81
82
  if (request.method !== "GET" && request.method !== "HEAD") return false;
82
- if (isPrivateRender(request) || isPreviewHandoff(request, options)) return false;
83
+ if (isPrivateRender(request) || new URL(request.url).searchParams.has("ott")) return false;
83
84
  const pathname = getRuntimePathname(request.url, options.runtimeBasePath);
84
85
  return pathname !== null && PUBLIC_ROUTE_KINDS.has(matchRuntimeRoute(pathname).kind);
85
86
  }
@@ -559,6 +560,51 @@ function isPreviewHandoff(request, options) {
559
560
  return searchParams.has("camox-preview") && searchParams.has("ott") && !!options.apiUrl && !!options.authenticationUrl && !!options.projectUuid && !!options.renderPage;
560
561
  }
561
562
  /**
563
+ * Whether a request lands from the studio's sign-in with its one-time token, which the Worker
564
+ * exchanges itself. A preview link's handoff checks its target in the browser first.
565
+ */
566
+ function isSignInHandoff(request) {
567
+ const { searchParams } = new URL(request.url);
568
+ return request.method === "GET" && searchParams.has("ott") && !searchParams.has("camox-preview");
569
+ }
570
+ /**
571
+ * Exchanges a sign-in's one-time token for the studio's session, sets the cookie that mirrors it,
572
+ * and sends the browser back to the URL without the token. The page then renders for the member
573
+ * at once, rather than for a visitor, then again once the browser signed in. Null when the
574
+ * exchange fails: the page loads with the token, and the browser tries it and says what failed.
575
+ */
576
+ async function createSignInResponse(request, apiUrl) {
577
+ const url = new URL(request.url);
578
+ let setCookie;
579
+ try {
580
+ const response = await fetch(`${apiUrl.replace(/\/+$/, "")}/api/auth/one-time-token/verify`, {
581
+ method: "POST",
582
+ headers: {
583
+ "Content-Type": "application/json",
584
+ "Better-Auth-Cookie": ""
585
+ },
586
+ body: JSON.stringify({ token: url.searchParams.get("ott") })
587
+ });
588
+ if (!response.ok) return null;
589
+ setCookie = response.headers.get("Set-Better-Auth-Cookie");
590
+ } catch (error) {
591
+ console.error("[camox] Sign-in token exchange failed.", error);
592
+ return null;
593
+ }
594
+ const session = setCookie ? getCookie(getSetCookie(setCookie)) : "";
595
+ if (!session) return null;
596
+ url.searchParams.delete("ott");
597
+ return new Response(null, {
598
+ status: 302,
599
+ headers: {
600
+ Location: `${url.pathname}${url.search}`,
601
+ "Cache-Control": "private, no-store",
602
+ "Referrer-Policy": "no-referrer",
603
+ "Set-Cookie": buildServerAuthCookieHeader(session, { secure: url.protocol === "https:" })
604
+ }
605
+ });
606
+ }
607
+ /**
562
608
  * The alternates of the page a request renders, where it exists in each of the scope's locales,
563
609
  * and its head with their `hreflang` links. Without a head, there is none to add them to.
564
610
  */
@@ -1257,7 +1303,8 @@ async function createStudioHtmlResponse({ match, options, pathname, located, req
1257
1303
  routeKind: match.kind,
1258
1304
  runtimeBasePath: normalizeRuntimeBasePath(options.runtimeBasePath),
1259
1305
  locale: located.locale,
1260
- locales: located.locales
1306
+ locales: located.locales,
1307
+ draftId: project ? getRequestDraftId(request) : void 0
1261
1308
  };
1262
1309
  if (dataOnly) return Response.json(studioRenderInput, { headers: {
1263
1310
  "Cache-Control": "private, no-store",
@@ -1429,6 +1476,10 @@ async function handleCamoxRequest(request, options = {}) {
1429
1476
  pathname,
1430
1477
  request
1431
1478
  });
1479
+ if (options.apiUrl && isSignInHandoff(request)) {
1480
+ const signedIn = await createSignInResponse(request, options.apiUrl);
1481
+ if (signedIn) return signedIn;
1482
+ }
1432
1483
  const { apiUrl, projectUuid } = options;
1433
1484
  const markdownPathname = parseMarkdownPath(pathname);
1434
1485
  if (apiUrl && projectUuid && (markdownPathname || prefersMarkdown(request.headers.get("Accept") ?? ""))) {
@@ -1,7 +1,4 @@
1
- import { rememberPublishingDraft } from "./publishingDraft.js";
2
- import { fetchLocalhostOnLoopback } from "./loopbackFetch.js";
3
- import { PUBLIC_CACHE_NAME, readLiveCacheKey, respondThroughPublicCache } from "./publicCache.js";
4
- import { shouldHandleRequest, withDevHtml } from "./workerRequest.js";
1
+ import { PUBLIC_CACHE_NAME, fetchLocalhostOnLoopback, readLiveCacheKey, rememberPublishingDraft, respondThroughPublicCache, shouldHandleRequest, withDevHtml } from "camox/_internal/workerRuntime";
5
2
  import { WorkerEntrypoint } from "cloudflare:workers";
6
3
  import { devHtml, handleCamoxRequest, isPublicRequest, publicCacheSource, readDefinitionSet, runtimeBasePath } from "virtual:camox/server";
7
4
 
@@ -0,0 +1,8 @@
1
+ //#region src/features/runtime/workerRequest.d.ts
2
+ export declare function shouldHandleRequest(request: Request, basePath: string): boolean;
3
+ /** Adds what Vite injects into HTML in dev, such as its client and React Refresh. */
4
+ export declare function withDevHtml(response: Response, html: {
5
+ head: string;
6
+ body: string;
7
+ } | null): Promise<Response>;
8
+ //#endregion
@@ -0,0 +1,5 @@
1
+ import { fetchLocalhostOnLoopback } from "./loopbackFetch.js";
2
+ import { PUBLIC_CACHE_NAME, readLiveCacheKey, respondThroughPublicCache } from "./publicCache.js";
3
+ import { rememberPublishingDraft } from "./publishingDraft.js";
4
+ import { shouldHandleRequest, withDevHtml } from "./workerRequest.js";
5
+ export { PUBLIC_CACHE_NAME, fetchLocalhostOnLoopback, readLiveCacheKey, rememberPublishingDraft, respondThroughPublicCache, shouldHandleRequest, withDevHtml };
@@ -0,0 +1,6 @@
1
+ import { rememberPublishingDraft } from "./publishingDraft.js";
2
+ import { fetchLocalhostOnLoopback } from "./loopbackFetch.js";
3
+ import { PUBLIC_CACHE_NAME, readLiveCacheKey, respondThroughPublicCache } from "./publicCache.js";
4
+ import { shouldHandleRequest, withDevHtml } from "./workerRequest.js";
5
+
6
+ export { PUBLIC_CACHE_NAME, fetchLocalhostOnLoopback, readLiveCacheKey, rememberPublishingDraft, respondThroughPublicCache, shouldHandleRequest, withDevHtml };