@avocadostudio-ai/site-sdk 0.11.2 → 0.11.4

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.
@@ -9,6 +9,7 @@
9
9
  * site's draft content out of its own browser. Now the development default is
10
10
  * only assumed in development, and a deployment says what it allows.
11
11
  */
12
+ import { withLoopbackSpellings } from "@avocadostudio-ai/shared";
12
13
  /*
13
14
  * Both spellings of the same loopback address, because the product uses both.
14
15
  *
@@ -19,7 +20,10 @@
19
20
  * and the symptom is a blank iframe with a console message naming an origin
20
21
  * that looks like it should have matched.
21
22
  *
22
- * Development only, as below: a deployment still has to name its editor.
23
+ * This is the development default. A deployment still has to name its editor —
24
+ * but whichever spelling it names, `withLoopbackSpellings` now grants the
25
+ * other, which is what this constant had been quietly doing for development
26
+ * only. See below.
23
27
  */
24
28
  const DEV_EDITOR_ORIGINS = ["http://localhost:4100", "http://127.0.0.1:4100"];
25
29
  let cachedOrigins;
@@ -41,7 +45,23 @@ export function getEditorCorsOrigins() {
41
45
  const declaredEditor = parseOrigins(process.env.NEXT_PUBLIC_EDITOR_ORIGIN);
42
46
  const isProduction = process.env.NODE_ENV === "production";
43
47
  const defaults = isProduction ? [] : DEV_EDITOR_ORIGINS;
44
- cachedOrigins = new Set([...defaults, ...declaredEditor, ...configured]);
48
+ /*
49
+ * Expand loopback spellings, which is what this function claimed to do and
50
+ * did not.
51
+ *
52
+ * `next-config.mjs`'s `avocadoEditorOrigins` — the `frame-ancestors` half of
53
+ * the same decision — says in its own comment that it *mirrors* this
54
+ * function, and it has run every origin through the same expansion since it
55
+ * was written. This one applied it to the development constant above and
56
+ * nowhere else, so a production deployment that wrote
57
+ * `EDITOR_CORS_ORIGINS=http://localhost:4100` allowed the frame from
58
+ * `127.0.0.1:4100` and refused every API call from it: the editor paints and
59
+ * is inert, which is strictly worse than either half failing alone.
60
+ *
61
+ * `editor-origin-parity.test.ts` now asserts the two produce the same set,
62
+ * so the next person to change one is told about the other.
63
+ */
64
+ cachedOrigins = new Set(withLoopbackSpellings([...defaults, ...declaredEditor, ...configured]));
45
65
  return cachedOrigins;
46
66
  }
47
67
  /** Test seam — the cache is a module global, and env changes must be able to take effect. */
@@ -59,7 +79,29 @@ export function applyEditorCors(response, requestOrigin) {
59
79
  return response;
60
80
  response.headers.set("Access-Control-Allow-Origin", requestOrigin);
61
81
  response.headers.set("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
62
- response.headers.set("Access-Control-Allow-Headers", "Content-Type, Authorization");
82
+ /*
83
+ * `x-access-token` is not optional here, and leaving it out shut the editor.
84
+ *
85
+ * The editor installs a global `fetch` wrapper that stamps its access token on
86
+ * every request whose *origin* matches the orchestrator's — not whose path
87
+ * does. In library mode the orchestrator is mounted inside the site, so
88
+ * `/api/avocado/*` and `/api/editor/*` share an origin and both get the
89
+ * header. The orchestrator's own CORS lists it; this list did not, so once a
90
+ * production gate was configured and a token minted, the browser refused the
91
+ * preflight for `/api/editor/blocks` and `/api/editor/pages` — the component
92
+ * manifest the whole editing surface is derived from — with the requests
93
+ * never leaving the browser. The server saw nothing and logged nothing.
94
+ *
95
+ * It only bites in production with a gate on, which is why `curl` and every
96
+ * development run looked fine: in development no token exists to stamp.
97
+ *
98
+ * These names must stay in step with the `access-control-allow-headers` the
99
+ * orchestrator's own preflight sends, in
100
+ * `orchestrator-core/src/handler/create-orchestrator.ts`. A header one group accepts
101
+ * and the other rejects is indistinguishable, from the browser, from the
102
+ * server being down.
103
+ */
104
+ response.headers.set("Access-Control-Allow-Headers", "Content-Type, Authorization, x-access-token");
63
105
  return response;
64
106
  }
65
107
  export function createEditorCorsOptionsHandler() {
package/next-config.mjs CHANGED
@@ -427,6 +427,17 @@ function builtOrchestratorCore(from, linked) {
427
427
  * *intersection* — putting `frame-ancestors 'self'` back and blocking the
428
428
  * frame it just allowed.
429
429
  */
430
+ /*
431
+ * A deliberate copy of `bothLoopbackSpellings` from
432
+ * `@avocadostudio-ai/shared`, not an import.
433
+ *
434
+ * This file is shipped as raw ESM and evaluated inside `next.config.mjs`,
435
+ * before anything is built or resolved, and a helper that can fail to import
436
+ * there breaks a user's build for a header. So the rule is duplicated and
437
+ * `packages/site-sdk/src/editor-origin-parity.test.ts` asserts the two agree
438
+ * on every input either can see. That test exists because this very expansion
439
+ * was applied here and not to CORS, and the disagreement shipped.
440
+ */
430
441
  function bothLoopbackSpellings(origin) {
431
442
  if (origin.includes("localhost")) return [origin, origin.replace("localhost", "127.0.0.1")]
432
443
  if (origin.includes("127.0.0.1")) return [origin, origin.replace("127.0.0.1", "localhost")]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@avocadostudio-ai/site-sdk",
3
- "version": "0.11.2",
3
+ "version": "0.11.4",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -147,17 +147,17 @@
147
147
  ],
148
148
  "dependencies": {
149
149
  "zod": "^4.3.6",
150
- "@avocadostudio-ai/blocks": "^0.11.2",
151
- "@avocadostudio-ai/preview-adapter": "^0.11.2",
152
- "@avocadostudio-ai/shared": "^0.11.2",
153
- "@avocadostudio-ai/richtext": "^0.11.2"
150
+ "@avocadostudio-ai/blocks": "^0.11.4",
151
+ "@avocadostudio-ai/preview-adapter": "^0.11.4",
152
+ "@avocadostudio-ai/shared": "^0.11.4",
153
+ "@avocadostudio-ai/richtext": "^0.11.4"
154
154
  },
155
155
  "peerDependencies": {
156
156
  "next": ">=15.0.0",
157
157
  "react": ">=19.0.0",
158
158
  "react-dom": ">=19.0.0",
159
159
  "better-sqlite3": ">=12.0.0",
160
- "@avocadostudio-ai/orchestrator-core": "^0.11.2"
160
+ "@avocadostudio-ai/orchestrator-core": "^0.11.4"
161
161
  },
162
162
  "peerDependenciesMeta": {
163
163
  "@avocadostudio-ai/orchestrator-core": {