@camada/nuxt 0.1.4 → 0.2.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/README.md CHANGED
@@ -56,9 +56,10 @@ so an unprovisioned environment behaves exactly as if camada were not installed.
56
56
  3. Runs your ordered custom rules, then the allow, block and challenge lists.
57
57
  4. **Block** → `403` with `x-block-reason` before your route; the event still ships, with
58
58
  `st: 403` and `blk: <reason>` so the analyst counts SDK blocks apart from your own 403s.
59
- 5. **Challenge** → a `403` proof-of-work page for HTML navigations, `403 {"error":
60
- "challenge_required"}` for anything else; the solution posts to `/__camada/challenge`,
61
- which sets `_cch` and 302s back.
59
+ 5. **Challenge** → a `403` proof-of-work page for HTML navigations; anything else (API
60
+ calls, `fetch()`, Nuxt's `_payload.json`) passes to your app with `x-camada-challenge: 1` and
61
+ `no-store` on `cache-control` and the CDN cache headers on the response (and `chs: 1` on the event) so your frontend can
62
+ choose to challenge. The solution posts to `/__camada/challenge`, which sets `_cch` and 302s back.
62
63
  6. Otherwise the request falls through to your routes and pages. The event ships on the
63
64
  response's `finish` with the real `statusCode` — a Node response, or the mock a web preset
64
65
  provides; `st` is null only for an event without a response object at all. A client that
package/dist/index.cjs CHANGED
@@ -46,7 +46,7 @@ var import_iife_string = __toESM(require("@camada/browser/iife-string"), 1);
46
46
 
47
47
  // package.json
48
48
  var name = "@camada/nuxt";
49
- var version = "0.1.4";
49
+ var version = "0.2.0";
50
50
 
51
51
  // src/version.ts
52
52
  var SDK_ID = `${name}/${version}`;
@@ -60,6 +60,30 @@ var slotOf = (event) => (0, import_core.guarded)(() => {
60
60
  const outer = event.node?.req?.__unenv__;
61
61
  return outer?.[VAR];
62
62
  }, void 0);
63
+ var NO_STORE_HEADERS = ["cache-control", "cdn-cache-control", "surrogate-control", "cloudflare-cdn-cache-control", "vercel-cdn-cache-control", "netlify-cdn-cache-control"];
64
+ function signal(res) {
65
+ const set = res.setHeader, writeHead = res.writeHead;
66
+ const isNoStore = (n) => typeof n === "string" && NO_STORE_HEADERS.includes(n.toLowerCase());
67
+ const strip = (h) => {
68
+ if (Array.isArray(h)) {
69
+ if (h.length && Array.isArray(h[0])) return h.filter((p) => !isNoStore(p[0]));
70
+ const out = [];
71
+ for (let i = 0; i < h.length; i += 2) if (!isNoStore(h[i])) out.push(h[i], h[i + 1]);
72
+ return out;
73
+ }
74
+ if (h && typeof h === "object") return Object.fromEntries(Object.entries(h).filter(([k]) => !isNoStore(k)));
75
+ return h;
76
+ };
77
+ res.setHeader = function(name2, value) {
78
+ return isNoStore(name2) ? this : set.call(this, name2, value);
79
+ };
80
+ res.writeHead = function(status, ...rest) {
81
+ if (!this.headersSent) for (const h of NO_STORE_HEADERS) set.call(this, h, "no-store");
82
+ return writeHead.call(this, status, ...rest.map(strip));
83
+ };
84
+ res.setHeader("x-camada-challenge", "1");
85
+ for (const h of NO_STORE_HEADERS) set.call(res, h, "no-store");
86
+ }
63
87
  function camada(opts = {}) {
64
88
  const cam = (0, import_fetch.createFetchCamada)({ tap: import_core.TAP_NUXT, sdk: SDK_ID, iife: import_iife_string.default }, opts);
65
89
  instances.add(cam);
@@ -100,6 +124,7 @@ function camada(opts = {}) {
100
124
  }), void 0);
101
125
  }
102
126
  if (res) (0, import_core.guarded)(() => (0, import_h3.setResponseHeader)(event, "x-rid", vars.rid), void 0);
127
+ if (vars.signalled && res) (0, import_core.guarded)(() => signal(res), void 0);
103
128
  if (typeof res?.once === "function") {
104
129
  let shipped = false;
105
130
  const ship = () => {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/camada.ts","../package.json","../src/version.ts","../src/nitro.ts"],"sourcesContent":["// @camada/nuxt — the two-file install for a Nuxt app (any Nitro preset with an h3 event):\n// server/middleware/camada.ts: export default camada(); // env: CAMADA_KEY (+ CAMADA_INGEST_URL / CAMADA_SNAPSHOT_URL in dev)\n// server/plugins/camada.ts: export default camadaNitroPlugin(); // the first-party beacon in every rendered <head>\n// track(event, 'login_failed', { user }) // an outcome the wire cannot show, from a server route\nexport { camada, track, scriptTag, resetCamada, type CamadaNuxtOptions, type CamadaNuxtVars } from './camada.js';\nexport { camadaNitroPlugin, type NitroAppLike } from './nitro.js';\n","// @camada/nuxt — the h3 binding over @camada/core/fetch. The pipeline (verdict, block,\n// challenge, beacon endpoints, the wire event) lives in core; this file only says what h3 knows:\n// the Web Request, the socket peer, the process env, and when the response has settled.\nimport { defineEventHandler, getRequestIP, setCookie, setResponseHeader, toWebRequest, type EventHandler, type H3Event } from 'h3';\nimport { guarded, TAP_NUXT } from '@camada/core';\nimport { createFetchCamada, SESSION_COOKIE, SESSION_MAX_AGE, track as coreTrack, scriptTag as coreScriptTag, type FetchCamada, type FetchCamadaOptions, type FetchRequestContext, type FetchVars } from '@camada/core/fetch';\nimport iife from '@camada/browser/iife-string';\nimport { SDK_ID } from './version.js';\n\nexport type CamadaNuxtOptions = FetchCamadaOptions;\nexport type CamadaNuxtVars = FetchVars;\n\nconst VAR = '__camada'; // private: track() and scriptTag() are the API, not event.context\n\n// Every instance this module built, so a test or a hot reload can stop them all at once.\nconst instances = new Set<FetchCamada>();\n\n/** Reads the slot without trusting the event shape: a stub or a foreign event must not throw.\n * An inner event Nitro made for `event.$fetch` / SSR `useFetch` carries the outer event's context\n * on its mock request (`req.__unenv__`; Nitro copies only `_platform` and `waitUntil` into the\n * inner context), so the outer request's vars are found there — the page's own rid and session. */\nconst slotOf = (event: H3Event): FetchVars | undefined =>\n guarded(() => {\n const own = event.context[VAR] as FetchVars | undefined;\n if (own) return own;\n const outer = (event.node?.req as { __unenv__?: Record<string, unknown> } | undefined)?.__unenv__;\n return outer?.[VAR] as FetchVars | undefined;\n }, undefined);\n\n/**\n * The server middleware: `export default camada()` in `server/middleware/camada.ts`. Reads\n * `CAMADA_KEY` / `CAMADA_INGEST_URL` / `CAMADA_SNAPSHOT_URL` from `process.env`; without a key\n * it is inert. Returns a web Response where camada answers (block, challenge, the beacon\n * endpoints) and falls through otherwise.\n */\nexport function camada(opts: CamadaNuxtOptions = {}): EventHandler {\n const cam = createFetchCamada({ tap: TAP_NUXT, sdk: SDK_ID, iife }, opts); // mode defaults to lazy in core: the middleware may run on an edge preset\n instances.add(cam);\n return defineEventHandler(async (event) => {\n // Nitro's own `event.$fetch` / SSR `useFetch` re-enters the h3 app for the same page view,\n // already captured on the outer request (a bare `$fetch` with no event is a request of its own).\n if (slotOf(event)) return;\n const req = guarded(() => toWebRequest(event), null);\n if (!req) return; // an event h3 cannot express as a Request is not ours to break\n // Building the Request puts a Node request body into a web stream; hand that stream back to\n // h3 so the app's readBody() reads the same bytes rather than an already-drained socket.\n if (req.body) event._requestBody = req.body;\n const ctx: FetchRequestContext = {\n // The socket address (or what the host preset stamped as clientAddress); never\n // X-Forwarded-For here, core resolves that under the trusted-proxy rules.\n peer: guarded(() => getRequestIP(event) ?? null, null),\n env: globalThis.process?.env,\n };\n const r = await cam.before(req, ctx);\n if (!r) return;\n if (r.response) {\n // h3 keeps headers an earlier middleware put on node.res and lays the Response's over them: the\n // challenge page's own CSP replaces an app policy, but an app's Report-Only policy would still ride\n // along and report the solver as a violation. Drop both first (camada-all-mwpo).\n const out = r.response;\n if (out.headers.has('content-security-policy')) {\n guarded(() => {\n event.node?.res?.removeHeader('content-security-policy');\n event.node?.res?.removeHeader('content-security-policy-report-only');\n }, undefined);\n }\n return out;\n }\n const vars = r.vars;\n event.context[VAR] = vars;\n const res = event.node?.res;\n // Set before the app runs so a handler's own redirect carries the session too; core only\n // hands a cookie for a new session, so an existing `_sfp` is never overwritten. h3 1.x\n // writes cookies through node.res, so an event without one gets no session, quietly.\n if (vars.sessionCookie && vars.sid && res) {\n const sid = vars.sid;\n guarded(() => setCookie(event, SESSION_COOKIE, sid, {\n path: '/', maxAge: SESSION_MAX_AGE, httpOnly: true, sameSite: 'lax', secure: new URL(req.url).protocol === 'https:',\n }), undefined);\n }\n // x-rid, set up front like the cookie: h3 keeps node.res headers through the app's answer,\n // a returned web Response included. Camada's own answers returned above never get it.\n if (res) guarded(() => setResponseHeader(event, 'x-rid', vars.rid), undefined);\n // Ship with the real status once the response has settled — a Node response, or the mock a\n // web preset provides. 'close' alone means the client left mid-response (an aborted SSE):\n // the status set so far and dur up to the abort, the fetch adapters' cancel semantics; it\n // also follows every 'finish', so a guard keeps it to one event. An event with no response\n // object at all ships now, with st and dur null: nothing here can see when its response\n // settles, and a dur taken now would be ~0.\n if (typeof res?.once === 'function') {\n let shipped = false;\n const ship = () => { if (!shipped) { shipped = true; cam.after(req, vars, res.statusCode); } };\n res.once('finish', ship);\n res.once('close', ship);\n } else cam.after(req, vars, null);\n });\n}\n\n/**\n * Records an outcome the app knows and the wire cannot show: `login_failed`, `login_succeeded`,\n * `signup`, `password_reset`, `mfa_failed`, `payment_failed`, `payment_succeeded`, `coupon_failed`\n * (free-form; that vocabulary is what the analyst's rules read). Joined to this request's event\n * through its rid and session; the user identifier is HMAC-hashed in-process. Never throws, and\n * a silent no-op where the middleware did not run.\n */\nexport const track = (event: H3Event, et: string, data?: { user?: string }): Promise<void> => coreTrack(slotOf(event), et, data);\n\n/** The `<script>` tag for an HTML response — `''` where the middleware did not run or the tenant turned the beacon off. */\nexport const scriptTag = (event: H3Event): string => coreScriptTag(slotOf(event));\n\n/** Test/reset hook: stops and drops every engine behind every `camada()` this module created. */\nexport function resetCamada(): void {\n for (const c of instances) c.reset();\n}\n","{\n \"name\": \"@camada/nuxt\",\n \"version\": \"0.1.4\",\n \"license\": \"MIT\",\n \"type\": \"module\",\n \"engines\": {\n \"node\": \">=20\"\n },\n \"files\": [\n \"dist\",\n \"README.md\",\n \"LICENSE\"\n ],\n \"scripts\": {\n \"build\": \"tsup\",\n \"dev\": \"tsup --watch\",\n \"test\": \"vitest run\",\n \"check\": \"tsc --noEmit\"\n },\n \"description\": \"camada SDK for Nuxt (h3 server middleware): snapshot enforcement, first-party challenge and beacon, track() outcomes, events shipped with the settled status\",\n \"exports\": {\n \".\": {\n \"import\": {\n \"types\": \"./dist/index.d.ts\",\n \"default\": \"./dist/index.js\"\n },\n \"require\": {\n \"types\": \"./dist/index.d.cts\",\n \"default\": \"./dist/index.cjs\"\n }\n }\n },\n \"main\": \"./dist/index.cjs\",\n \"module\": \"./dist/index.js\",\n \"types\": \"./dist/index.d.ts\",\n \"dependencies\": {\n \"@camada/core\": \"file:../camada-core\",\n \"@camada/browser\": \"file:../camada-browser\"\n },\n \"peerDependencies\": {\n \"h3\": \"^1.12.0\"\n },\n \"devDependencies\": {\n \"@types/node\": \"^22.10.0\",\n \"h3\": \"^1.15.0\",\n \"tsup\": \"^8.3.5\",\n \"typescript\": \"^5.7.2\",\n \"vitest\": \"^3.0.0\"\n }\n}\n","// The SDK's wire identity (SDK-03): x-camada-sdk: <package>/<version>. Named JSON imports are\n// inlined and tree-shaken by tsup at build, so dist carries two literals — no runtime fs read.\nimport { name, version } from '../package.json';\nexport const SDK_ID = `${name}/${version}`;\n","// The Nitro side of the beacon: Nuxt renders `<head>` through the `render:html` hook, so the\n// tag goes in there rather than in every page. Typed structurally so this package never\n// depends on nitropack — the plugin file is `export default camadaNitroPlugin()`.\nimport type { H3Event } from 'h3';\nimport { scriptTag } from './camada.js';\n\nexport interface NitroAppLike { hooks: { hook(name: string, fn: (...a: any[]) => void): void } }\ninterface RenderHtml { head: string[] }\n\n/** `server/plugins/camada.ts`: pushes this request's beacon tag into the rendered `<head>`; nothing where the middleware did not run. */\nexport function camadaNitroPlugin(): (nitroApp: NitroAppLike) => void {\n return (nitroApp) => {\n nitroApp.hooks.hook('render:html', (html: RenderHtml, { event }: { event: H3Event }) => {\n const tag = scriptTag(event);\n if (tag) html.head.push(tag);\n });\n };\n}\n"],"mappings":"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","names":["iife","coreTrack","coreScriptTag"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/camada.ts","../package.json","../src/version.ts","../src/nitro.ts"],"sourcesContent":["// @camada/nuxt — the two-file install for a Nuxt app (any Nitro preset with an h3 event):\n// server/middleware/camada.ts: export default camada(); // env: CAMADA_KEY (+ CAMADA_INGEST_URL / CAMADA_SNAPSHOT_URL in dev)\n// server/plugins/camada.ts: export default camadaNitroPlugin(); // the first-party beacon in every rendered <head>\n// track(event, 'login_failed', { user }) // an outcome the wire cannot show, from a server route\nexport { camada, track, scriptTag, resetCamada, type CamadaNuxtOptions, type CamadaNuxtVars } from './camada.js';\nexport { camadaNitroPlugin, type NitroAppLike } from './nitro.js';\n","// @camada/nuxt — the h3 binding over @camada/core/fetch. The pipeline (verdict, block,\n// challenge, beacon endpoints, the wire event) lives in core; this file only says what h3 knows:\n// the Web Request, the socket peer, the process env, and when the response has settled.\nimport type { ServerResponse } from 'node:http';\nimport { defineEventHandler, getRequestIP, setCookie, setResponseHeader, toWebRequest, type EventHandler, type H3Event } from 'h3';\nimport { guarded, TAP_NUXT } from '@camada/core';\nimport { createFetchCamada, SESSION_COOKIE, SESSION_MAX_AGE, track as coreTrack, scriptTag as coreScriptTag, type FetchCamada, type FetchCamadaOptions, type FetchRequestContext, type FetchVars } from '@camada/core/fetch';\nimport iife from '@camada/browser/iife-string';\nimport { SDK_ID } from './version.js';\n\nexport type CamadaNuxtOptions = FetchCamadaOptions;\nexport type CamadaNuxtVars = FetchVars;\n\nconst VAR = '__camada'; // private: track() and scriptTag() are the API, not event.context\n\n// Every instance this module built, so a test or a hot reload can stop them all at once.\nconst instances = new Set<FetchCamada>();\n\n/** Reads the slot without trusting the event shape: a stub or a foreign event must not throw.\n * An inner event Nitro made for `event.$fetch` / SSR `useFetch` carries the outer event's context\n * on its mock request (`req.__unenv__`; Nitro copies only `_platform` and `waitUntil` into the\n * inner context), so the outer request's vars are found there — the page's own rid and session. */\nconst slotOf = (event: H3Event): FetchVars | undefined =>\n guarded(() => {\n const own = event.context[VAR] as FetchVars | undefined;\n if (own) return own;\n const outer = (event.node?.req as { __unenv__?: Record<string, unknown> } | undefined)?.__unenv__;\n return outer?.[VAR] as FetchVars | undefined;\n }, undefined);\n\n// CDNs read their own cache header ahead of cache-control, so all of these end up no-store.\nconst NO_STORE_HEADERS = ['cache-control', 'cdn-cache-control', 'surrogate-control', 'cloudflare-cdn-cache-control', 'vercel-cdn-cache-control', 'netlify-cdn-cache-control'];\n\n/** Marks a signalled response: `x-camada-challenge: 1` and no-store on every NO_STORE_HEADERS name. The header is\n * per client, so no shared cache may keep the copy, whatever the app sets later. Two choke points:\n * `res.setHeader` ignores those names (setHeaders, h3's setResponseHeader and sendWebResponse all\n * land there, and it is the only one node-mock-http's response uses under web/edge presets, whose\n * end() never calls writeHead); `res.writeHead` drops those names from the headers it is\n * given (object, flat array, tuple array) and re-sets no-store, on real Node where those bypass\n * setHeader. Both use the original setHeader for no-store (camada-all-60f8). */\nfunction signal(res: ServerResponse): void {\n const set = res.setHeader, writeHead = res.writeHead;\n const isNoStore = (n: unknown) => typeof n === 'string' && NO_STORE_HEADERS.includes(n.toLowerCase());\n const strip = (h: unknown): unknown => {\n if (Array.isArray(h)) {\n if (h.length && Array.isArray(h[0])) return h.filter((p) => !isNoStore(p[0]));\n const out: unknown[] = [];\n for (let i = 0; i < h.length; i += 2) if (!isNoStore(h[i])) out.push(h[i], h[i + 1]);\n return out;\n }\n if (h && typeof h === 'object') return Object.fromEntries(Object.entries(h).filter(([k]) => !isNoStore(k)));\n return h;\n };\n res.setHeader = function (this: ServerResponse, name: string, value: unknown) {\n return isNoStore(name) ? this : (set as (...a: unknown[]) => ServerResponse).call(this, name, value);\n } as ServerResponse['setHeader'];\n res.writeHead = function (this: ServerResponse, status: number, ...rest: unknown[]) {\n if (!this.headersSent) for (const h of NO_STORE_HEADERS) set.call(this, h, 'no-store');\n return (writeHead as (...a: unknown[]) => ServerResponse).call(this, status, ...rest.map(strip));\n } as ServerResponse['writeHead'];\n res.setHeader('x-camada-challenge', '1');\n for (const h of NO_STORE_HEADERS) set.call(res, h, 'no-store');\n}\n\n/**\n * The server middleware: `export default camada()` in `server/middleware/camada.ts`. Reads\n * `CAMADA_KEY` / `CAMADA_INGEST_URL` / `CAMADA_SNAPSHOT_URL` from `process.env`; without a key\n * it is inert. Returns a web Response where camada answers (block, challenge, the beacon\n * endpoints) and falls through otherwise.\n */\nexport function camada(opts: CamadaNuxtOptions = {}): EventHandler {\n const cam = createFetchCamada({ tap: TAP_NUXT, sdk: SDK_ID, iife }, opts); // mode defaults to lazy in core: the middleware may run on an edge preset\n instances.add(cam);\n return defineEventHandler(async (event) => {\n // Nitro's own `event.$fetch` / SSR `useFetch` re-enters the h3 app for the same page view,\n // already captured on the outer request (a bare `$fetch` with no event is a request of its own).\n if (slotOf(event)) return;\n const req = guarded(() => toWebRequest(event), null);\n if (!req) return; // an event h3 cannot express as a Request is not ours to break\n // Building the Request puts a Node request body into a web stream; hand that stream back to\n // h3 so the app's readBody() reads the same bytes rather than an already-drained socket.\n if (req.body) event._requestBody = req.body;\n const ctx: FetchRequestContext = {\n // The socket address (or what the host preset stamped as clientAddress); never\n // X-Forwarded-For here, core resolves that under the trusted-proxy rules.\n peer: guarded(() => getRequestIP(event) ?? null, null),\n env: globalThis.process?.env,\n };\n const r = await cam.before(req, ctx);\n if (!r) return;\n if (r.response) {\n // h3 keeps headers an earlier middleware put on node.res and lays the Response's over them: the\n // challenge page's own CSP replaces an app policy, but an app's Report-Only policy would still ride\n // along and report the solver as a violation. Drop both first (camada-all-mwpo).\n const out = r.response;\n if (out.headers.has('content-security-policy')) {\n guarded(() => {\n event.node?.res?.removeHeader('content-security-policy');\n event.node?.res?.removeHeader('content-security-policy-report-only');\n }, undefined);\n }\n return out;\n }\n const vars = r.vars;\n event.context[VAR] = vars;\n const res = event.node?.res;\n // Set before the app runs so a handler's own redirect carries the session too; core only\n // hands a cookie for a new session, so an existing `_sfp` is never overwritten. h3 1.x\n // writes cookies through node.res, so an event without one gets no session, quietly.\n if (vars.sessionCookie && vars.sid && res) {\n const sid = vars.sid;\n guarded(() => setCookie(event, SESSION_COOKIE, sid, {\n path: '/', maxAge: SESSION_MAX_AGE, httpOnly: true, sameSite: 'lax', secure: new URL(req.url).protocol === 'https:',\n }), undefined);\n }\n // x-rid, set up front like the cookie: h3 keeps node.res headers through the app's answer,\n // a returned web Response included. Camada's own answers returned above never get it.\n if (res) guarded(() => setResponseHeader(event, 'x-rid', vars.rid), undefined);\n // A non-HTML request under a challenge verdict reaches the app; mark its response so the\n // frontend can challenge, and keep it out of shared caches (camada-all-60f8).\n if (vars.signalled && res) guarded(() => signal(res), undefined);\n // Ship with the real status once the response has settled — a Node response, or the mock a\n // web preset provides. 'close' alone means the client left mid-response (an aborted SSE):\n // the status set so far and dur up to the abort, the fetch adapters' cancel semantics; it\n // also follows every 'finish', so a guard keeps it to one event. An event with no response\n // object at all ships now, with st and dur null: nothing here can see when its response\n // settles, and a dur taken now would be ~0.\n if (typeof res?.once === 'function') {\n let shipped = false;\n const ship = () => { if (!shipped) { shipped = true; cam.after(req, vars, res.statusCode); } };\n res.once('finish', ship);\n res.once('close', ship);\n } else cam.after(req, vars, null);\n });\n}\n\n/**\n * Records an outcome the app knows and the wire cannot show: `login_failed`, `login_succeeded`,\n * `signup`, `password_reset`, `mfa_failed`, `payment_failed`, `payment_succeeded`, `coupon_failed`\n * (free-form; that vocabulary is what the analyst's rules read). Joined to this request's event\n * through its rid and session; the user identifier is HMAC-hashed in-process. Never throws, and\n * a silent no-op where the middleware did not run.\n */\nexport const track = (event: H3Event, et: string, data?: { user?: string }): Promise<void> => coreTrack(slotOf(event), et, data);\n\n/** The `<script>` tag for an HTML response — `''` where the middleware did not run or the tenant turned the beacon off. */\nexport const scriptTag = (event: H3Event): string => coreScriptTag(slotOf(event));\n\n/** Test/reset hook: stops and drops every engine behind every `camada()` this module created. */\nexport function resetCamada(): void {\n for (const c of instances) c.reset();\n}\n","{\n \"name\": \"@camada/nuxt\",\n \"version\": \"0.2.0\",\n \"license\": \"MIT\",\n \"type\": \"module\",\n \"engines\": {\n \"node\": \">=20\"\n },\n \"files\": [\n \"dist\",\n \"README.md\",\n \"LICENSE\"\n ],\n \"scripts\": {\n \"build\": \"tsup\",\n \"dev\": \"tsup --watch\",\n \"test\": \"vitest run\",\n \"check\": \"tsc --noEmit\"\n },\n \"description\": \"camada SDK for Nuxt (h3 server middleware): snapshot enforcement, first-party challenge and beacon, track() outcomes, events shipped with the settled status\",\n \"exports\": {\n \".\": {\n \"import\": {\n \"types\": \"./dist/index.d.ts\",\n \"default\": \"./dist/index.js\"\n },\n \"require\": {\n \"types\": \"./dist/index.d.cts\",\n \"default\": \"./dist/index.cjs\"\n }\n }\n },\n \"main\": \"./dist/index.cjs\",\n \"module\": \"./dist/index.js\",\n \"types\": \"./dist/index.d.ts\",\n \"dependencies\": {\n \"@camada/core\": \"file:../camada-core\",\n \"@camada/browser\": \"file:../camada-browser\"\n },\n \"peerDependencies\": {\n \"h3\": \"^1.12.0\"\n },\n \"devDependencies\": {\n \"@types/node\": \"^22.10.0\",\n \"h3\": \"^1.15.0\",\n \"tsup\": \"^8.3.5\",\n \"typescript\": \"^5.7.2\",\n \"vitest\": \"^3.0.0\"\n }\n}\n","// The SDK's wire identity (SDK-03): x-camada-sdk: <package>/<version>. Named JSON imports are\n// inlined and tree-shaken by tsup at build, so dist carries two literals — no runtime fs read.\nimport { name, version } from '../package.json';\nexport const SDK_ID = `${name}/${version}`;\n","// The Nitro side of the beacon: Nuxt renders `<head>` through the `render:html` hook, so the\n// tag goes in there rather than in every page. Typed structurally so this package never\n// depends on nitropack — the plugin file is `export default camadaNitroPlugin()`.\nimport type { H3Event } from 'h3';\nimport { scriptTag } from './camada.js';\n\nexport interface NitroAppLike { hooks: { hook(name: string, fn: (...a: any[]) => void): void } }\ninterface RenderHtml { head: string[] }\n\n/** `server/plugins/camada.ts`: pushes this request's beacon tag into the rendered `<head>`; nothing where the middleware did not run. */\nexport function camadaNitroPlugin(): (nitroApp: NitroAppLike) => void {\n return (nitroApp) => {\n nitroApp.hooks.hook('render:html', (html: RenderHtml, { event }: { event: H3Event }) => {\n const tag = scriptTag(event);\n if (tag) html.head.push(tag);\n });\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACIA,gBAA8H;AAC9H,kBAAkC;AAClC,mBAAwM;AACxM,yBAAiB;;;ACNf,WAAQ;AACR,cAAW;;;ACCN,IAAM,SAAS,GAAG,IAAI,IAAI,OAAO;;;AFUxC,IAAM,MAAM;AAGZ,IAAM,YAAY,oBAAI,IAAiB;AAMvC,IAAM,SAAS,CAAC,cACd,qBAAQ,MAAM;AACZ,QAAM,MAAM,MAAM,QAAQ,GAAG;AAC7B,MAAI,IAAK,QAAO;AAChB,QAAM,QAAS,MAAM,MAAM,KAA6D;AACxF,SAAO,QAAQ,GAAG;AACpB,GAAG,MAAS;AAGd,IAAM,mBAAmB,CAAC,iBAAiB,qBAAqB,qBAAqB,gCAAgC,4BAA4B,2BAA2B;AAS5K,SAAS,OAAO,KAA2B;AACzC,QAAM,MAAM,IAAI,WAAW,YAAY,IAAI;AAC3C,QAAM,YAAY,CAAC,MAAe,OAAO,MAAM,YAAY,iBAAiB,SAAS,EAAE,YAAY,CAAC;AACpG,QAAM,QAAQ,CAAC,MAAwB;AACrC,QAAI,MAAM,QAAQ,CAAC,GAAG;AACpB,UAAI,EAAE,UAAU,MAAM,QAAQ,EAAE,CAAC,CAAC,EAAG,QAAO,EAAE,OAAO,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC;AAC5E,YAAM,MAAiB,CAAC;AACxB,eAAS,IAAI,GAAG,IAAI,EAAE,QAAQ,KAAK,EAAG,KAAI,CAAC,UAAU,EAAE,CAAC,CAAC,EAAG,KAAI,KAAK,EAAE,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;AACnF,aAAO;AAAA,IACT;AACA,QAAI,KAAK,OAAO,MAAM,SAAU,QAAO,OAAO,YAAY,OAAO,QAAQ,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC;AAC1G,WAAO;AAAA,EACT;AACA,MAAI,YAAY,SAAgCA,OAAc,OAAgB;AAC5E,WAAO,UAAUA,KAAI,IAAI,OAAQ,IAA4C,KAAK,MAAMA,OAAM,KAAK;AAAA,EACrG;AACA,MAAI,YAAY,SAAgC,WAAmB,MAAiB;AAClF,QAAI,CAAC,KAAK,YAAa,YAAW,KAAK,iBAAkB,KAAI,KAAK,MAAM,GAAG,UAAU;AACrF,WAAQ,UAAkD,KAAK,MAAM,QAAQ,GAAG,KAAK,IAAI,KAAK,CAAC;AAAA,EACjG;AACA,MAAI,UAAU,sBAAsB,GAAG;AACvC,aAAW,KAAK,iBAAkB,KAAI,KAAK,KAAK,GAAG,UAAU;AAC/D;AAQO,SAAS,OAAO,OAA0B,CAAC,GAAiB;AACjE,QAAM,UAAM,gCAAkB,EAAE,KAAK,sBAAU,KAAK,QAAQ,yBAAAC,QAAK,GAAG,IAAI;AACxE,YAAU,IAAI,GAAG;AACjB,aAAO,8BAAmB,OAAO,UAAU;AAGzC,QAAI,OAAO,KAAK,EAAG;AACnB,UAAM,UAAM,qBAAQ,UAAM,wBAAa,KAAK,GAAG,IAAI;AACnD,QAAI,CAAC,IAAK;AAGV,QAAI,IAAI,KAAM,OAAM,eAAe,IAAI;AACvC,UAAM,MAA2B;AAAA;AAAA;AAAA,MAG/B,UAAM,qBAAQ,UAAM,wBAAa,KAAK,KAAK,MAAM,IAAI;AAAA,MACrD,KAAK,WAAW,SAAS;AAAA,IAC3B;AACA,UAAM,IAAI,MAAM,IAAI,OAAO,KAAK,GAAG;AACnC,QAAI,CAAC,EAAG;AACR,QAAI,EAAE,UAAU;AAId,YAAM,MAAM,EAAE;AACd,UAAI,IAAI,QAAQ,IAAI,yBAAyB,GAAG;AAC9C,iCAAQ,MAAM;AACZ,gBAAM,MAAM,KAAK,aAAa,yBAAyB;AACvD,gBAAM,MAAM,KAAK,aAAa,qCAAqC;AAAA,QACrE,GAAG,MAAS;AAAA,MACd;AACA,aAAO;AAAA,IACT;AACA,UAAM,OAAO,EAAE;AACf,UAAM,QAAQ,GAAG,IAAI;AACrB,UAAM,MAAM,MAAM,MAAM;AAIxB,QAAI,KAAK,iBAAiB,KAAK,OAAO,KAAK;AACzC,YAAM,MAAM,KAAK;AACjB,+BAAQ,UAAM,qBAAU,OAAO,6BAAgB,KAAK;AAAA,QAClD,MAAM;AAAA,QAAK,QAAQ;AAAA,QAAiB,UAAU;AAAA,QAAM,UAAU;AAAA,QAAO,QAAQ,IAAI,IAAI,IAAI,GAAG,EAAE,aAAa;AAAA,MAC7G,CAAC,GAAG,MAAS;AAAA,IACf;AAGA,QAAI,IAAK,0BAAQ,UAAM,6BAAkB,OAAO,SAAS,KAAK,GAAG,GAAG,MAAS;AAG7E,QAAI,KAAK,aAAa,IAAK,0BAAQ,MAAM,OAAO,GAAG,GAAG,MAAS;AAO/D,QAAI,OAAO,KAAK,SAAS,YAAY;AACnC,UAAI,UAAU;AACd,YAAM,OAAO,MAAM;AAAE,YAAI,CAAC,SAAS;AAAE,oBAAU;AAAM,cAAI,MAAM,KAAK,MAAM,IAAI,UAAU;AAAA,QAAG;AAAA,MAAE;AAC7F,UAAI,KAAK,UAAU,IAAI;AACvB,UAAI,KAAK,SAAS,IAAI;AAAA,IACxB,MAAO,KAAI,MAAM,KAAK,MAAM,IAAI;AAAA,EAClC,CAAC;AACH;AASO,IAAM,QAAQ,CAAC,OAAgB,IAAY,aAA4C,aAAAC,OAAU,OAAO,KAAK,GAAG,IAAI,IAAI;AAGxH,IAAM,YAAY,CAAC,cAA2B,aAAAC,WAAc,OAAO,KAAK,CAAC;AAGzE,SAAS,cAAoB;AAClC,aAAW,KAAK,UAAW,GAAE,MAAM;AACrC;;;AG7IO,SAAS,oBAAsD;AACpE,SAAO,CAAC,aAAa;AACnB,aAAS,MAAM,KAAK,eAAe,CAAC,MAAkB,EAAE,MAAM,MAA0B;AACtF,YAAM,MAAM,UAAU,KAAK;AAC3B,UAAI,IAAK,MAAK,KAAK,KAAK,GAAG;AAAA,IAC7B,CAAC;AAAA,EACH;AACF;","names":["name","iife","coreTrack","coreScriptTag"]}
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@ import iife from "@camada/browser/iife-string";
6
6
 
7
7
  // package.json
8
8
  var name = "@camada/nuxt";
9
- var version = "0.1.4";
9
+ var version = "0.2.0";
10
10
 
11
11
  // src/version.ts
12
12
  var SDK_ID = `${name}/${version}`;
@@ -20,6 +20,30 @@ var slotOf = (event) => guarded(() => {
20
20
  const outer = event.node?.req?.__unenv__;
21
21
  return outer?.[VAR];
22
22
  }, void 0);
23
+ var NO_STORE_HEADERS = ["cache-control", "cdn-cache-control", "surrogate-control", "cloudflare-cdn-cache-control", "vercel-cdn-cache-control", "netlify-cdn-cache-control"];
24
+ function signal(res) {
25
+ const set = res.setHeader, writeHead = res.writeHead;
26
+ const isNoStore = (n) => typeof n === "string" && NO_STORE_HEADERS.includes(n.toLowerCase());
27
+ const strip = (h) => {
28
+ if (Array.isArray(h)) {
29
+ if (h.length && Array.isArray(h[0])) return h.filter((p) => !isNoStore(p[0]));
30
+ const out = [];
31
+ for (let i = 0; i < h.length; i += 2) if (!isNoStore(h[i])) out.push(h[i], h[i + 1]);
32
+ return out;
33
+ }
34
+ if (h && typeof h === "object") return Object.fromEntries(Object.entries(h).filter(([k]) => !isNoStore(k)));
35
+ return h;
36
+ };
37
+ res.setHeader = function(name2, value) {
38
+ return isNoStore(name2) ? this : set.call(this, name2, value);
39
+ };
40
+ res.writeHead = function(status, ...rest) {
41
+ if (!this.headersSent) for (const h of NO_STORE_HEADERS) set.call(this, h, "no-store");
42
+ return writeHead.call(this, status, ...rest.map(strip));
43
+ };
44
+ res.setHeader("x-camada-challenge", "1");
45
+ for (const h of NO_STORE_HEADERS) set.call(res, h, "no-store");
46
+ }
23
47
  function camada(opts = {}) {
24
48
  const cam = createFetchCamada({ tap: TAP_NUXT, sdk: SDK_ID, iife }, opts);
25
49
  instances.add(cam);
@@ -60,6 +84,7 @@ function camada(opts = {}) {
60
84
  }), void 0);
61
85
  }
62
86
  if (res) guarded(() => setResponseHeader(event, "x-rid", vars.rid), void 0);
87
+ if (vars.signalled && res) guarded(() => signal(res), void 0);
63
88
  if (typeof res?.once === "function") {
64
89
  let shipped = false;
65
90
  const ship = () => {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/camada.ts","../package.json","../src/version.ts","../src/nitro.ts"],"sourcesContent":["// @camada/nuxt — the h3 binding over @camada/core/fetch. The pipeline (verdict, block,\n// challenge, beacon endpoints, the wire event) lives in core; this file only says what h3 knows:\n// the Web Request, the socket peer, the process env, and when the response has settled.\nimport { defineEventHandler, getRequestIP, setCookie, setResponseHeader, toWebRequest, type EventHandler, type H3Event } from 'h3';\nimport { guarded, TAP_NUXT } from '@camada/core';\nimport { createFetchCamada, SESSION_COOKIE, SESSION_MAX_AGE, track as coreTrack, scriptTag as coreScriptTag, type FetchCamada, type FetchCamadaOptions, type FetchRequestContext, type FetchVars } from '@camada/core/fetch';\nimport iife from '@camada/browser/iife-string';\nimport { SDK_ID } from './version.js';\n\nexport type CamadaNuxtOptions = FetchCamadaOptions;\nexport type CamadaNuxtVars = FetchVars;\n\nconst VAR = '__camada'; // private: track() and scriptTag() are the API, not event.context\n\n// Every instance this module built, so a test or a hot reload can stop them all at once.\nconst instances = new Set<FetchCamada>();\n\n/** Reads the slot without trusting the event shape: a stub or a foreign event must not throw.\n * An inner event Nitro made for `event.$fetch` / SSR `useFetch` carries the outer event's context\n * on its mock request (`req.__unenv__`; Nitro copies only `_platform` and `waitUntil` into the\n * inner context), so the outer request's vars are found there — the page's own rid and session. */\nconst slotOf = (event: H3Event): FetchVars | undefined =>\n guarded(() => {\n const own = event.context[VAR] as FetchVars | undefined;\n if (own) return own;\n const outer = (event.node?.req as { __unenv__?: Record<string, unknown> } | undefined)?.__unenv__;\n return outer?.[VAR] as FetchVars | undefined;\n }, undefined);\n\n/**\n * The server middleware: `export default camada()` in `server/middleware/camada.ts`. Reads\n * `CAMADA_KEY` / `CAMADA_INGEST_URL` / `CAMADA_SNAPSHOT_URL` from `process.env`; without a key\n * it is inert. Returns a web Response where camada answers (block, challenge, the beacon\n * endpoints) and falls through otherwise.\n */\nexport function camada(opts: CamadaNuxtOptions = {}): EventHandler {\n const cam = createFetchCamada({ tap: TAP_NUXT, sdk: SDK_ID, iife }, opts); // mode defaults to lazy in core: the middleware may run on an edge preset\n instances.add(cam);\n return defineEventHandler(async (event) => {\n // Nitro's own `event.$fetch` / SSR `useFetch` re-enters the h3 app for the same page view,\n // already captured on the outer request (a bare `$fetch` with no event is a request of its own).\n if (slotOf(event)) return;\n const req = guarded(() => toWebRequest(event), null);\n if (!req) return; // an event h3 cannot express as a Request is not ours to break\n // Building the Request puts a Node request body into a web stream; hand that stream back to\n // h3 so the app's readBody() reads the same bytes rather than an already-drained socket.\n if (req.body) event._requestBody = req.body;\n const ctx: FetchRequestContext = {\n // The socket address (or what the host preset stamped as clientAddress); never\n // X-Forwarded-For here, core resolves that under the trusted-proxy rules.\n peer: guarded(() => getRequestIP(event) ?? null, null),\n env: globalThis.process?.env,\n };\n const r = await cam.before(req, ctx);\n if (!r) return;\n if (r.response) {\n // h3 keeps headers an earlier middleware put on node.res and lays the Response's over them: the\n // challenge page's own CSP replaces an app policy, but an app's Report-Only policy would still ride\n // along and report the solver as a violation. Drop both first (camada-all-mwpo).\n const out = r.response;\n if (out.headers.has('content-security-policy')) {\n guarded(() => {\n event.node?.res?.removeHeader('content-security-policy');\n event.node?.res?.removeHeader('content-security-policy-report-only');\n }, undefined);\n }\n return out;\n }\n const vars = r.vars;\n event.context[VAR] = vars;\n const res = event.node?.res;\n // Set before the app runs so a handler's own redirect carries the session too; core only\n // hands a cookie for a new session, so an existing `_sfp` is never overwritten. h3 1.x\n // writes cookies through node.res, so an event without one gets no session, quietly.\n if (vars.sessionCookie && vars.sid && res) {\n const sid = vars.sid;\n guarded(() => setCookie(event, SESSION_COOKIE, sid, {\n path: '/', maxAge: SESSION_MAX_AGE, httpOnly: true, sameSite: 'lax', secure: new URL(req.url).protocol === 'https:',\n }), undefined);\n }\n // x-rid, set up front like the cookie: h3 keeps node.res headers through the app's answer,\n // a returned web Response included. Camada's own answers returned above never get it.\n if (res) guarded(() => setResponseHeader(event, 'x-rid', vars.rid), undefined);\n // Ship with the real status once the response has settled — a Node response, or the mock a\n // web preset provides. 'close' alone means the client left mid-response (an aborted SSE):\n // the status set so far and dur up to the abort, the fetch adapters' cancel semantics; it\n // also follows every 'finish', so a guard keeps it to one event. An event with no response\n // object at all ships now, with st and dur null: nothing here can see when its response\n // settles, and a dur taken now would be ~0.\n if (typeof res?.once === 'function') {\n let shipped = false;\n const ship = () => { if (!shipped) { shipped = true; cam.after(req, vars, res.statusCode); } };\n res.once('finish', ship);\n res.once('close', ship);\n } else cam.after(req, vars, null);\n });\n}\n\n/**\n * Records an outcome the app knows and the wire cannot show: `login_failed`, `login_succeeded`,\n * `signup`, `password_reset`, `mfa_failed`, `payment_failed`, `payment_succeeded`, `coupon_failed`\n * (free-form; that vocabulary is what the analyst's rules read). Joined to this request's event\n * through its rid and session; the user identifier is HMAC-hashed in-process. Never throws, and\n * a silent no-op where the middleware did not run.\n */\nexport const track = (event: H3Event, et: string, data?: { user?: string }): Promise<void> => coreTrack(slotOf(event), et, data);\n\n/** The `<script>` tag for an HTML response — `''` where the middleware did not run or the tenant turned the beacon off. */\nexport const scriptTag = (event: H3Event): string => coreScriptTag(slotOf(event));\n\n/** Test/reset hook: stops and drops every engine behind every `camada()` this module created. */\nexport function resetCamada(): void {\n for (const c of instances) c.reset();\n}\n","{\n \"name\": \"@camada/nuxt\",\n \"version\": \"0.1.4\",\n \"license\": \"MIT\",\n \"type\": \"module\",\n \"engines\": {\n \"node\": \">=20\"\n },\n \"files\": [\n \"dist\",\n \"README.md\",\n \"LICENSE\"\n ],\n \"scripts\": {\n \"build\": \"tsup\",\n \"dev\": \"tsup --watch\",\n \"test\": \"vitest run\",\n \"check\": \"tsc --noEmit\"\n },\n \"description\": \"camada SDK for Nuxt (h3 server middleware): snapshot enforcement, first-party challenge and beacon, track() outcomes, events shipped with the settled status\",\n \"exports\": {\n \".\": {\n \"import\": {\n \"types\": \"./dist/index.d.ts\",\n \"default\": \"./dist/index.js\"\n },\n \"require\": {\n \"types\": \"./dist/index.d.cts\",\n \"default\": \"./dist/index.cjs\"\n }\n }\n },\n \"main\": \"./dist/index.cjs\",\n \"module\": \"./dist/index.js\",\n \"types\": \"./dist/index.d.ts\",\n \"dependencies\": {\n \"@camada/core\": \"file:../camada-core\",\n \"@camada/browser\": \"file:../camada-browser\"\n },\n \"peerDependencies\": {\n \"h3\": \"^1.12.0\"\n },\n \"devDependencies\": {\n \"@types/node\": \"^22.10.0\",\n \"h3\": \"^1.15.0\",\n \"tsup\": \"^8.3.5\",\n \"typescript\": \"^5.7.2\",\n \"vitest\": \"^3.0.0\"\n }\n}\n","// The SDK's wire identity (SDK-03): x-camada-sdk: <package>/<version>. Named JSON imports are\n// inlined and tree-shaken by tsup at build, so dist carries two literals — no runtime fs read.\nimport { name, version } from '../package.json';\nexport const SDK_ID = `${name}/${version}`;\n","// The Nitro side of the beacon: Nuxt renders `<head>` through the `render:html` hook, so the\n// tag goes in there rather than in every page. Typed structurally so this package never\n// depends on nitropack — the plugin file is `export default camadaNitroPlugin()`.\nimport type { H3Event } from 'h3';\nimport { scriptTag } from './camada.js';\n\nexport interface NitroAppLike { hooks: { hook(name: string, fn: (...a: any[]) => void): void } }\ninterface RenderHtml { head: string[] }\n\n/** `server/plugins/camada.ts`: pushes this request's beacon tag into the rendered `<head>`; nothing where the middleware did not run. */\nexport function camadaNitroPlugin(): (nitroApp: NitroAppLike) => void {\n return (nitroApp) => {\n nitroApp.hooks.hook('render:html', (html: RenderHtml, { event }: { event: H3Event }) => {\n const tag = scriptTag(event);\n if (tag) html.head.push(tag);\n });\n };\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/camada.ts","../package.json","../src/version.ts","../src/nitro.ts"],"sourcesContent":["// @camada/nuxt — the h3 binding over @camada/core/fetch. The pipeline (verdict, block,\n// challenge, beacon endpoints, the wire event) lives in core; this file only says what h3 knows:\n// the Web Request, the socket peer, the process env, and when the response has settled.\nimport type { ServerResponse } from 'node:http';\nimport { defineEventHandler, getRequestIP, setCookie, setResponseHeader, toWebRequest, type EventHandler, type H3Event } from 'h3';\nimport { guarded, TAP_NUXT } from '@camada/core';\nimport { createFetchCamada, SESSION_COOKIE, SESSION_MAX_AGE, track as coreTrack, scriptTag as coreScriptTag, type FetchCamada, type FetchCamadaOptions, type FetchRequestContext, type FetchVars } from '@camada/core/fetch';\nimport iife from '@camada/browser/iife-string';\nimport { SDK_ID } from './version.js';\n\nexport type CamadaNuxtOptions = FetchCamadaOptions;\nexport type CamadaNuxtVars = FetchVars;\n\nconst VAR = '__camada'; // private: track() and scriptTag() are the API, not event.context\n\n// Every instance this module built, so a test or a hot reload can stop them all at once.\nconst instances = new Set<FetchCamada>();\n\n/** Reads the slot without trusting the event shape: a stub or a foreign event must not throw.\n * An inner event Nitro made for `event.$fetch` / SSR `useFetch` carries the outer event's context\n * on its mock request (`req.__unenv__`; Nitro copies only `_platform` and `waitUntil` into the\n * inner context), so the outer request's vars are found there — the page's own rid and session. */\nconst slotOf = (event: H3Event): FetchVars | undefined =>\n guarded(() => {\n const own = event.context[VAR] as FetchVars | undefined;\n if (own) return own;\n const outer = (event.node?.req as { __unenv__?: Record<string, unknown> } | undefined)?.__unenv__;\n return outer?.[VAR] as FetchVars | undefined;\n }, undefined);\n\n// CDNs read their own cache header ahead of cache-control, so all of these end up no-store.\nconst NO_STORE_HEADERS = ['cache-control', 'cdn-cache-control', 'surrogate-control', 'cloudflare-cdn-cache-control', 'vercel-cdn-cache-control', 'netlify-cdn-cache-control'];\n\n/** Marks a signalled response: `x-camada-challenge: 1` and no-store on every NO_STORE_HEADERS name. The header is\n * per client, so no shared cache may keep the copy, whatever the app sets later. Two choke points:\n * `res.setHeader` ignores those names (setHeaders, h3's setResponseHeader and sendWebResponse all\n * land there, and it is the only one node-mock-http's response uses under web/edge presets, whose\n * end() never calls writeHead); `res.writeHead` drops those names from the headers it is\n * given (object, flat array, tuple array) and re-sets no-store, on real Node where those bypass\n * setHeader. Both use the original setHeader for no-store (camada-all-60f8). */\nfunction signal(res: ServerResponse): void {\n const set = res.setHeader, writeHead = res.writeHead;\n const isNoStore = (n: unknown) => typeof n === 'string' && NO_STORE_HEADERS.includes(n.toLowerCase());\n const strip = (h: unknown): unknown => {\n if (Array.isArray(h)) {\n if (h.length && Array.isArray(h[0])) return h.filter((p) => !isNoStore(p[0]));\n const out: unknown[] = [];\n for (let i = 0; i < h.length; i += 2) if (!isNoStore(h[i])) out.push(h[i], h[i + 1]);\n return out;\n }\n if (h && typeof h === 'object') return Object.fromEntries(Object.entries(h).filter(([k]) => !isNoStore(k)));\n return h;\n };\n res.setHeader = function (this: ServerResponse, name: string, value: unknown) {\n return isNoStore(name) ? this : (set as (...a: unknown[]) => ServerResponse).call(this, name, value);\n } as ServerResponse['setHeader'];\n res.writeHead = function (this: ServerResponse, status: number, ...rest: unknown[]) {\n if (!this.headersSent) for (const h of NO_STORE_HEADERS) set.call(this, h, 'no-store');\n return (writeHead as (...a: unknown[]) => ServerResponse).call(this, status, ...rest.map(strip));\n } as ServerResponse['writeHead'];\n res.setHeader('x-camada-challenge', '1');\n for (const h of NO_STORE_HEADERS) set.call(res, h, 'no-store');\n}\n\n/**\n * The server middleware: `export default camada()` in `server/middleware/camada.ts`. Reads\n * `CAMADA_KEY` / `CAMADA_INGEST_URL` / `CAMADA_SNAPSHOT_URL` from `process.env`; without a key\n * it is inert. Returns a web Response where camada answers (block, challenge, the beacon\n * endpoints) and falls through otherwise.\n */\nexport function camada(opts: CamadaNuxtOptions = {}): EventHandler {\n const cam = createFetchCamada({ tap: TAP_NUXT, sdk: SDK_ID, iife }, opts); // mode defaults to lazy in core: the middleware may run on an edge preset\n instances.add(cam);\n return defineEventHandler(async (event) => {\n // Nitro's own `event.$fetch` / SSR `useFetch` re-enters the h3 app for the same page view,\n // already captured on the outer request (a bare `$fetch` with no event is a request of its own).\n if (slotOf(event)) return;\n const req = guarded(() => toWebRequest(event), null);\n if (!req) return; // an event h3 cannot express as a Request is not ours to break\n // Building the Request puts a Node request body into a web stream; hand that stream back to\n // h3 so the app's readBody() reads the same bytes rather than an already-drained socket.\n if (req.body) event._requestBody = req.body;\n const ctx: FetchRequestContext = {\n // The socket address (or what the host preset stamped as clientAddress); never\n // X-Forwarded-For here, core resolves that under the trusted-proxy rules.\n peer: guarded(() => getRequestIP(event) ?? null, null),\n env: globalThis.process?.env,\n };\n const r = await cam.before(req, ctx);\n if (!r) return;\n if (r.response) {\n // h3 keeps headers an earlier middleware put on node.res and lays the Response's over them: the\n // challenge page's own CSP replaces an app policy, but an app's Report-Only policy would still ride\n // along and report the solver as a violation. Drop both first (camada-all-mwpo).\n const out = r.response;\n if (out.headers.has('content-security-policy')) {\n guarded(() => {\n event.node?.res?.removeHeader('content-security-policy');\n event.node?.res?.removeHeader('content-security-policy-report-only');\n }, undefined);\n }\n return out;\n }\n const vars = r.vars;\n event.context[VAR] = vars;\n const res = event.node?.res;\n // Set before the app runs so a handler's own redirect carries the session too; core only\n // hands a cookie for a new session, so an existing `_sfp` is never overwritten. h3 1.x\n // writes cookies through node.res, so an event without one gets no session, quietly.\n if (vars.sessionCookie && vars.sid && res) {\n const sid = vars.sid;\n guarded(() => setCookie(event, SESSION_COOKIE, sid, {\n path: '/', maxAge: SESSION_MAX_AGE, httpOnly: true, sameSite: 'lax', secure: new URL(req.url).protocol === 'https:',\n }), undefined);\n }\n // x-rid, set up front like the cookie: h3 keeps node.res headers through the app's answer,\n // a returned web Response included. Camada's own answers returned above never get it.\n if (res) guarded(() => setResponseHeader(event, 'x-rid', vars.rid), undefined);\n // A non-HTML request under a challenge verdict reaches the app; mark its response so the\n // frontend can challenge, and keep it out of shared caches (camada-all-60f8).\n if (vars.signalled && res) guarded(() => signal(res), undefined);\n // Ship with the real status once the response has settled — a Node response, or the mock a\n // web preset provides. 'close' alone means the client left mid-response (an aborted SSE):\n // the status set so far and dur up to the abort, the fetch adapters' cancel semantics; it\n // also follows every 'finish', so a guard keeps it to one event. An event with no response\n // object at all ships now, with st and dur null: nothing here can see when its response\n // settles, and a dur taken now would be ~0.\n if (typeof res?.once === 'function') {\n let shipped = false;\n const ship = () => { if (!shipped) { shipped = true; cam.after(req, vars, res.statusCode); } };\n res.once('finish', ship);\n res.once('close', ship);\n } else cam.after(req, vars, null);\n });\n}\n\n/**\n * Records an outcome the app knows and the wire cannot show: `login_failed`, `login_succeeded`,\n * `signup`, `password_reset`, `mfa_failed`, `payment_failed`, `payment_succeeded`, `coupon_failed`\n * (free-form; that vocabulary is what the analyst's rules read). Joined to this request's event\n * through its rid and session; the user identifier is HMAC-hashed in-process. Never throws, and\n * a silent no-op where the middleware did not run.\n */\nexport const track = (event: H3Event, et: string, data?: { user?: string }): Promise<void> => coreTrack(slotOf(event), et, data);\n\n/** The `<script>` tag for an HTML response — `''` where the middleware did not run or the tenant turned the beacon off. */\nexport const scriptTag = (event: H3Event): string => coreScriptTag(slotOf(event));\n\n/** Test/reset hook: stops and drops every engine behind every `camada()` this module created. */\nexport function resetCamada(): void {\n for (const c of instances) c.reset();\n}\n","{\n \"name\": \"@camada/nuxt\",\n \"version\": \"0.2.0\",\n \"license\": \"MIT\",\n \"type\": \"module\",\n \"engines\": {\n \"node\": \">=20\"\n },\n \"files\": [\n \"dist\",\n \"README.md\",\n \"LICENSE\"\n ],\n \"scripts\": {\n \"build\": \"tsup\",\n \"dev\": \"tsup --watch\",\n \"test\": \"vitest run\",\n \"check\": \"tsc --noEmit\"\n },\n \"description\": \"camada SDK for Nuxt (h3 server middleware): snapshot enforcement, first-party challenge and beacon, track() outcomes, events shipped with the settled status\",\n \"exports\": {\n \".\": {\n \"import\": {\n \"types\": \"./dist/index.d.ts\",\n \"default\": \"./dist/index.js\"\n },\n \"require\": {\n \"types\": \"./dist/index.d.cts\",\n \"default\": \"./dist/index.cjs\"\n }\n }\n },\n \"main\": \"./dist/index.cjs\",\n \"module\": \"./dist/index.js\",\n \"types\": \"./dist/index.d.ts\",\n \"dependencies\": {\n \"@camada/core\": \"file:../camada-core\",\n \"@camada/browser\": \"file:../camada-browser\"\n },\n \"peerDependencies\": {\n \"h3\": \"^1.12.0\"\n },\n \"devDependencies\": {\n \"@types/node\": \"^22.10.0\",\n \"h3\": \"^1.15.0\",\n \"tsup\": \"^8.3.5\",\n \"typescript\": \"^5.7.2\",\n \"vitest\": \"^3.0.0\"\n }\n}\n","// The SDK's wire identity (SDK-03): x-camada-sdk: <package>/<version>. Named JSON imports are\n// inlined and tree-shaken by tsup at build, so dist carries two literals — no runtime fs read.\nimport { name, version } from '../package.json';\nexport const SDK_ID = `${name}/${version}`;\n","// The Nitro side of the beacon: Nuxt renders `<head>` through the `render:html` hook, so the\n// tag goes in there rather than in every page. Typed structurally so this package never\n// depends on nitropack — the plugin file is `export default camadaNitroPlugin()`.\nimport type { H3Event } from 'h3';\nimport { scriptTag } from './camada.js';\n\nexport interface NitroAppLike { hooks: { hook(name: string, fn: (...a: any[]) => void): void } }\ninterface RenderHtml { head: string[] }\n\n/** `server/plugins/camada.ts`: pushes this request's beacon tag into the rendered `<head>`; nothing where the middleware did not run. */\nexport function camadaNitroPlugin(): (nitroApp: NitroAppLike) => void {\n return (nitroApp) => {\n nitroApp.hooks.hook('render:html', (html: RenderHtml, { event }: { event: H3Event }) => {\n const tag = scriptTag(event);\n if (tag) html.head.push(tag);\n });\n };\n}\n"],"mappings":"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","names":["name"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camada/nuxt",
3
- "version": "0.1.4",
3
+ "version": "0.2.0",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "engines": {
@@ -28,8 +28,8 @@
28
28
  "module": "./dist/index.js",
29
29
  "types": "./dist/index.d.ts",
30
30
  "dependencies": {
31
- "@camada/core": "^0.5.2",
32
- "@camada/browser": "^0.3.2"
31
+ "@camada/core": "^0.6.0",
32
+ "@camada/browser": "^0.4.0"
33
33
  },
34
34
  "peerDependencies": {
35
35
  "h3": "^1.12.0"