isotone 1.0.3 → 1.0.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.
package/dist/serve.js CHANGED
@@ -11,7 +11,7 @@ import {
11
11
  import {
12
12
  UPDATE_ENDPOINT,
13
13
  startUpdateCheck
14
- } from "./chunk-QDOV7KYU.js";
14
+ } from "./chunk-3AYKY7NS.js";
15
15
  import {
16
16
  NOTES_ENDPOINT,
17
17
  ensureFileIgnored,
package/dist/server.d.ts CHANGED
@@ -92,12 +92,21 @@ declare function variantsResponse(opts: VariantsStoreOptions, req: NotesRequest)
92
92
  * keep this subpath framework-free.
93
93
  */
94
94
 
95
+ interface ServeRequestOptions {
96
+ /**
97
+ * Accept requests from any loopback page (localhost, 127.0.0.1, [::1]) on
98
+ * any port. Needed when the app's dev server and this API listen on
99
+ * different ports, since the page's origin then never matches the API host.
100
+ * Off by default: only the API's own origin is accepted.
101
+ */
102
+ loopbackOrigins?: boolean;
103
+ }
95
104
  /**
96
105
  * A convenience Next-compatible adapter: given a `Request`/`NextRequest` and the
97
106
  * `file`, return a `NextResponse`-shaped `{ status, headers, body }`. Frameworks
98
107
  * that expose a `(req, res)` pair call `notesResponse` and render it directly.
99
108
  */
100
- declare function serveNotesRequest(file: string, request: Request): Promise<{
109
+ declare function serveNotesRequest(file: string, request: Request, options?: ServeRequestOptions): Promise<{
101
110
  status: number;
102
111
  headers: Record<string, string>;
103
112
  body: unknown;
@@ -108,10 +117,10 @@ declare function serveNotesRequest(file: string, request: Request): Promise<{
108
117
  * such as `.../active`), strip the mount prefix before handing the rest to
109
118
  * `variantsResponse`, which routes on the path relative to the mount.
110
119
  */
111
- declare function serveVariantsRequest(opts: VariantsStoreOptions, request: Request): Promise<{
120
+ declare function serveVariantsRequest(opts: VariantsStoreOptions, request: Request, options?: ServeRequestOptions): Promise<{
112
121
  status: number;
113
122
  headers: Record<string, string>;
114
123
  body: unknown;
115
124
  }>;
116
125
 
117
- export { ACTIVE_FILE, DEFAULT_VARIANTS_DIR, NOTES_ENDPOINT, type NotesRequest, type NotesResponse, PAYLOAD_MAX, type StoredNote, type VariantsStoreOptions, ensureFileIgnored, listVariants, notesResponse, readActive, readNotes, serveNotesRequest, serveVariantsRequest, updateNotes, variantsResponse, writeActive, writeNotes };
126
+ export { ACTIVE_FILE, DEFAULT_VARIANTS_DIR, NOTES_ENDPOINT, type NotesRequest, type NotesResponse, PAYLOAD_MAX, type ServeRequestOptions, type StoredNote, type VariantsStoreOptions, ensureFileIgnored, listVariants, notesResponse, readActive, readNotes, serveNotesRequest, serveVariantsRequest, updateNotes, variantsResponse, writeActive, writeNotes };
package/dist/server.js CHANGED
@@ -26,10 +26,10 @@ import {
26
26
  import "./chunk-A4DX2V4C.js";
27
27
 
28
28
  // src/server/index.ts
29
- async function serveNotesRequest(file, request) {
29
+ async function serveNotesRequest(file, request, options = {}) {
30
30
  try {
31
31
  const url = new URL(request.url);
32
- checkOrigin(request.headers.get("origin"), url.host);
32
+ checkOrigin(request.headers.get("origin"), url.host, options.loopbackOrigins);
33
33
  const body = request.method === "POST" ? await readWebJson(request) : void 0;
34
34
  return notesResponse(file, {
35
35
  method: request.method,
@@ -40,10 +40,10 @@ async function serveNotesRequest(file, request) {
40
40
  return httpFailure(error);
41
41
  }
42
42
  }
43
- async function serveVariantsRequest(opts, request) {
43
+ async function serveVariantsRequest(opts, request, options = {}) {
44
44
  try {
45
45
  const url = new URL(request.url);
46
- checkOrigin(request.headers.get("origin"), url.host);
46
+ checkOrigin(request.headers.get("origin"), url.host, options.loopbackOrigins);
47
47
  const body = request.method === "POST" ? await readWebJson(request) : void 0;
48
48
  const mounted = url.pathname.startsWith(VARIANTS_ENDPOINT) ? url.pathname.slice(VARIANTS_ENDPOINT.length) : url.pathname;
49
49
  return variantsResponse(opts, {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/server/index.ts"],"sourcesContent":["import { checkOrigin, httpFailure, readWebJson } from './http';\n/**\n * isotone — server.\n *\n * The transport-agnostic side of the loop: the file store and a pure request\n * handler that any dev runtime (Vite middleware, Next route handler, Bun.serve,\n * Vercel function, plain `node:http`) can render. No React and no Vite here —\n * keep this subpath framework-free.\n */\n\nexport {\n ensureFileIgnored,\n notesResponse,\n readNotes,\n writeNotes,\n updateNotes,\n NOTES_ENDPOINT,\n PAYLOAD_MAX,\n} from '../store';\nimport { notesResponse } from '../store';\nexport type {\n NotesRequest,\n NotesResponse,\n StoredNote,\n} from '../store';\n\nexport {\n ACTIVE_FILE,\n DEFAULT_VARIANTS_DIR,\n listVariants,\n readActive,\n variantsResponse,\n writeActive,\n} from '../variants-store';\nimport { variantsResponse } from '../variants-store';\nimport type { VariantsStoreOptions } from '../variants-store';\nexport type { VariantsStoreOptions } from '../variants-store';\nexport { VARIANTS_ENDPOINT } from '../core/variants';\nimport { VARIANTS_ENDPOINT } from '../core/variants';\n\n/**\n * A convenience Next-compatible adapter: given a `Request`/`NextRequest` and the\n * `file`, return a `NextResponse`-shaped `{ status, headers, body }`. Frameworks\n * that expose a `(req, res)` pair call `notesResponse` and render it directly.\n */\nexport async function serveNotesRequest(\n file: string,\n request: Request,\n): Promise<{ status: number; headers: Record<string, string>; body: unknown }> {\n try {\n const url = new URL(request.url);\n checkOrigin(request.headers.get('origin'), url.host);\n const body = request.method === 'POST' ? await readWebJson(request) : undefined;\n return notesResponse(file, {\n method: request.method,\n url: `${url.pathname}?${url.searchParams.toString()}`,\n body,\n });\n } catch (error) {\n return httpFailure(error);\n }\n}\n\n/**\n * Like {@link serveNotesRequest}, for the variants endpoint: given a\n * `Request` whose path ends with `VARIANTS_ENDPOINT` (or a sub-path of it,\n * such as `.../active`), strip the mount prefix before handing the rest to\n * `variantsResponse`, which routes on the path relative to the mount.\n */\nexport async function serveVariantsRequest(\n opts: VariantsStoreOptions,\n request: Request,\n): Promise<{ status: number; headers: Record<string, string>; body: unknown }> {\n try {\n const url = new URL(request.url);\n checkOrigin(request.headers.get('origin'), url.host);\n const body = request.method === 'POST' ? await readWebJson(request) : undefined;\n const mounted = url.pathname.startsWith(VARIANTS_ENDPOINT)\n ? url.pathname.slice(VARIANTS_ENDPOINT.length)\n : url.pathname;\n return variantsResponse(opts, {\n method: request.method,\n url: `${mounted || '/'}?${url.searchParams.toString()}`,\n body,\n });\n } catch (error) {\n return httpFailure(error);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6CA,eAAsB,kBACpB,MACA,SAC6E;AAC7E,MAAI;AACF,UAAM,MAAM,IAAI,IAAI,QAAQ,GAAG;AAC/B,gBAAY,QAAQ,QAAQ,IAAI,QAAQ,GAAG,IAAI,IAAI;AACnD,UAAM,OAAO,QAAQ,WAAW,SAAS,MAAM,YAAY,OAAO,IAAI;AACtE,WAAO,cAAc,MAAM;AAAA,MACzB,QAAQ,QAAQ;AAAA,MAChB,KAAK,GAAG,IAAI,QAAQ,IAAI,IAAI,aAAa,SAAS,CAAC;AAAA,MACnD;AAAA,IACF,CAAC;AAAA,EACH,SAAS,OAAO;AACd,WAAO,YAAY,KAAK;AAAA,EAC1B;AACF;AAQA,eAAsB,qBACpB,MACA,SAC6E;AAC7E,MAAI;AACF,UAAM,MAAM,IAAI,IAAI,QAAQ,GAAG;AAC/B,gBAAY,QAAQ,QAAQ,IAAI,QAAQ,GAAG,IAAI,IAAI;AACnD,UAAM,OAAO,QAAQ,WAAW,SAAS,MAAM,YAAY,OAAO,IAAI;AACtE,UAAM,UAAU,IAAI,SAAS,WAAW,iBAAiB,IACrD,IAAI,SAAS,MAAM,kBAAkB,MAAM,IAC3C,IAAI;AACR,WAAO,iBAAiB,MAAM;AAAA,MAC5B,QAAQ,QAAQ;AAAA,MAChB,KAAK,GAAG,WAAW,GAAG,IAAI,IAAI,aAAa,SAAS,CAAC;AAAA,MACrD;AAAA,IACF,CAAC;AAAA,EACH,SAAS,OAAO;AACd,WAAO,YAAY,KAAK;AAAA,EAC1B;AACF;","names":[]}
1
+ {"version":3,"sources":["../src/server/index.ts"],"sourcesContent":["import { checkOrigin, httpFailure, readWebJson } from './http';\n/**\n * isotone — server.\n *\n * The transport-agnostic side of the loop: the file store and a pure request\n * handler that any dev runtime (Vite middleware, Next route handler, Bun.serve,\n * Vercel function, plain `node:http`) can render. No React and no Vite here —\n * keep this subpath framework-free.\n */\n\nexport {\n ensureFileIgnored,\n notesResponse,\n readNotes,\n writeNotes,\n updateNotes,\n NOTES_ENDPOINT,\n PAYLOAD_MAX,\n} from '../store';\nimport { notesResponse } from '../store';\nexport type {\n NotesRequest,\n NotesResponse,\n StoredNote,\n} from '../store';\n\nexport {\n ACTIVE_FILE,\n DEFAULT_VARIANTS_DIR,\n listVariants,\n readActive,\n variantsResponse,\n writeActive,\n} from '../variants-store';\nimport { variantsResponse } from '../variants-store';\nimport type { VariantsStoreOptions } from '../variants-store';\nexport type { VariantsStoreOptions } from '../variants-store';\nexport { VARIANTS_ENDPOINT } from '../core/variants';\nimport { VARIANTS_ENDPOINT } from '../core/variants';\n\nexport interface ServeRequestOptions {\n /**\n * Accept requests from any loopback page (localhost, 127.0.0.1, [::1]) on\n * any port. Needed when the app's dev server and this API listen on\n * different ports, since the page's origin then never matches the API host.\n * Off by default: only the API's own origin is accepted.\n */\n loopbackOrigins?: boolean;\n}\n\n/**\n * A convenience Next-compatible adapter: given a `Request`/`NextRequest` and the\n * `file`, return a `NextResponse`-shaped `{ status, headers, body }`. Frameworks\n * that expose a `(req, res)` pair call `notesResponse` and render it directly.\n */\nexport async function serveNotesRequest(\n file: string,\n request: Request,\n options: ServeRequestOptions = {},\n): Promise<{ status: number; headers: Record<string, string>; body: unknown }> {\n try {\n const url = new URL(request.url);\n checkOrigin(request.headers.get('origin'), url.host, options.loopbackOrigins);\n const body = request.method === 'POST' ? await readWebJson(request) : undefined;\n return notesResponse(file, {\n method: request.method,\n url: `${url.pathname}?${url.searchParams.toString()}`,\n body,\n });\n } catch (error) {\n return httpFailure(error);\n }\n}\n\n/**\n * Like {@link serveNotesRequest}, for the variants endpoint: given a\n * `Request` whose path ends with `VARIANTS_ENDPOINT` (or a sub-path of it,\n * such as `.../active`), strip the mount prefix before handing the rest to\n * `variantsResponse`, which routes on the path relative to the mount.\n */\nexport async function serveVariantsRequest(\n opts: VariantsStoreOptions,\n request: Request,\n options: ServeRequestOptions = {},\n): Promise<{ status: number; headers: Record<string, string>; body: unknown }> {\n try {\n const url = new URL(request.url);\n checkOrigin(request.headers.get('origin'), url.host, options.loopbackOrigins);\n const body = request.method === 'POST' ? await readWebJson(request) : undefined;\n const mounted = url.pathname.startsWith(VARIANTS_ENDPOINT)\n ? url.pathname.slice(VARIANTS_ENDPOINT.length)\n : url.pathname;\n return variantsResponse(opts, {\n method: request.method,\n url: `${mounted || '/'}?${url.searchParams.toString()}`,\n body,\n });\n } catch (error) {\n return httpFailure(error);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuDA,eAAsB,kBACpB,MACA,SACA,UAA+B,CAAC,GAC6C;AAC7E,MAAI;AACF,UAAM,MAAM,IAAI,IAAI,QAAQ,GAAG;AAC/B,gBAAY,QAAQ,QAAQ,IAAI,QAAQ,GAAG,IAAI,MAAM,QAAQ,eAAe;AAC5E,UAAM,OAAO,QAAQ,WAAW,SAAS,MAAM,YAAY,OAAO,IAAI;AACtE,WAAO,cAAc,MAAM;AAAA,MACzB,QAAQ,QAAQ;AAAA,MAChB,KAAK,GAAG,IAAI,QAAQ,IAAI,IAAI,aAAa,SAAS,CAAC;AAAA,MACnD;AAAA,IACF,CAAC;AAAA,EACH,SAAS,OAAO;AACd,WAAO,YAAY,KAAK;AAAA,EAC1B;AACF;AAQA,eAAsB,qBACpB,MACA,SACA,UAA+B,CAAC,GAC6C;AAC7E,MAAI;AACF,UAAM,MAAM,IAAI,IAAI,QAAQ,GAAG;AAC/B,gBAAY,QAAQ,QAAQ,IAAI,QAAQ,GAAG,IAAI,MAAM,QAAQ,eAAe;AAC5E,UAAM,OAAO,QAAQ,WAAW,SAAS,MAAM,YAAY,OAAO,IAAI;AACtE,UAAM,UAAU,IAAI,SAAS,WAAW,iBAAiB,IACrD,IAAI,SAAS,MAAM,kBAAkB,MAAM,IAC3C,IAAI;AACR,WAAO,iBAAiB,MAAM;AAAA,MAC5B,QAAQ,QAAQ;AAAA,MAChB,KAAK,GAAG,WAAW,GAAG,IAAI,IAAI,aAAa,SAAS,CAAC;AAAA,MACrD;AAAA,IACF,CAAC;AAAA,EACH,SAAS,OAAO;AACd,WAAO,YAAY,KAAK;AAAA,EAC1B;AACF;","names":[]}
@@ -22,7 +22,7 @@ import {
22
22
  import {
23
23
  UPDATE_ENDPOINT,
24
24
  startUpdateCheck
25
- } from "./chunk-QDOV7KYU.js";
25
+ } from "./chunk-3AYKY7NS.js";
26
26
  import {
27
27
  NOTES_ENDPOINT,
28
28
  ensureDirIgnored,
@@ -94,6 +94,13 @@ const handle = async (request: Request) => {
94
94
  export { handle as GET, handle as POST, handle as DELETE };
95
95
  ```
96
96
 
97
+ When the page and this API listen on different ports, for example a Next dev
98
+ server and a separate local API, the browser's origin never matches the API's
99
+ host and every request gets a 403 `origin not allowed`. Pass
100
+ `{ loopbackOrigins: true }` as the third argument of `serveNotesRequest` (or
101
+ `serveVariantsRequest`) to accept pages served from localhost, 127.0.0.1 or
102
+ [::1] on any port. Non-loopback origins are still rejected.
103
+
97
104
  For this manual route, add `.iteration-feedback.json` to the project's `.gitignore`.
98
105
  The Vite plugin and standalone server ignore their notes file automatically; the
99
106
  transport-only request adapter does not modify Git configuration.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "isotone",
3
- "version": "1.0.3",
3
+ "version": "1.0.4",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "Local feedback and design variants for your running web app. Pin elements, leave notes, and give your coding agent the context to make the change.",