@camada/sveltekit 0.1.2 → 0.1.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/README.md +33 -1
- package/dist/index.cjs +39 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +40 -4
- package/dist/index.js.map +1 -1
- package/package.json +11 -6
package/README.md
CHANGED
|
@@ -68,7 +68,20 @@ if camada were not installed.
|
|
|
68
68
|
`st: 403` and `blk: <reason>` so the analyst counts SDK blocks apart from your own 403s.
|
|
69
69
|
5. **Skip** → a skip rule or the allow list wins over a wider block.
|
|
70
70
|
6. **Challenge** → a `403` proof-of-work page (HTML navigations) or `403 {"error":"challenge_required"}`
|
|
71
|
-
(
|
|
71
|
+
(API and `fetch()` calls); `POST /__camada/challenge` verifies the solution, sets `_cch` and 302s back.
|
|
72
|
+
The solve is a form POST, so SvelteKit's own cross-site form check (`csrf.checkOrigin`) applies
|
|
73
|
+
to it: on **adapter-node served over plain http** (a local run, or a proxy that terminates TLS
|
|
74
|
+
without forwarding the scheme) set the `ORIGIN` env var to the public origin
|
|
75
|
+
(`ORIGIN=http://localhost:3000 node build`), or `PROTOCOL_HEADER` / `HOST_HEADER` (e.g. `PROTOCOL_HEADER=x-forwarded-proto HOST_HEADER=x-forwarded-host`), or
|
|
76
|
+
SvelteKit rejects the POST with "Cross-site POST form submissions are forbidden" before camada
|
|
77
|
+
sees it and the visitor cannot pass.
|
|
78
|
+
A client-side navigation (SvelteKit fetching `<path>/__data.json` for an `<a>` to a page with a
|
|
79
|
+
server `load`) gets a data redirect to `/_cam/reload?to=<page>` instead, beside the beacon
|
|
80
|
+
(`scriptPath`'s directory). No app route matches that URL, so the client reloads it as a
|
|
81
|
+
document, and camada 302s that request to the page, which shows the challenge. Your error
|
|
82
|
+
page never renders a 403 for it. The bounce answers only while camada is active (a key, and no
|
|
83
|
+
`CAMADA_DISABLED=1`); otherwise the path is your app's. See [The bounce path](#the-bounce-path-_camreload)
|
|
84
|
+
if you have a root catch-all route.
|
|
72
85
|
7. Otherwise `resolve(event)` runs, and the settled response ships one batched, redacted event
|
|
73
86
|
with its real status (Authorization and Cookie values never leave the process; credential-looking
|
|
74
87
|
query values are scrubbed — see `@camada/core`).
|
|
@@ -77,6 +90,25 @@ A first visit gets the shared `_sfp` session cookie through `event.cookies.set()
|
|
|
77
90
|
adds the header to whatever `resolve()` returns, so a `redirect()` or a raw `Response` passes
|
|
78
91
|
through untouched.
|
|
79
92
|
|
|
93
|
+
## The bounce path (`/_cam/reload`)
|
|
94
|
+
|
|
95
|
+
`GET /_cam/reload?to=<page>` (in `scriptPath`'s directory) is camada's: it 302s to `to` (a
|
|
96
|
+
same-site path only, anything else goes to `/`, `no-store`). It is how a challenged client-side
|
|
97
|
+
navigation turns into a document request (see step 6). It only works while no app route matches
|
|
98
|
+
`/_cam/reload`, since SvelteKit's client navigates client-side to any URL a route matches.
|
|
99
|
+
|
|
100
|
+
A root catch-all (`src/routes/[...rest]`) matches it, and the visitor would see that route
|
|
101
|
+
instead of the challenge. Keep the catch-all off camada's directory with a param matcher:
|
|
102
|
+
|
|
103
|
+
```ts
|
|
104
|
+
// src/params/notcam.ts
|
|
105
|
+
import type { ParamMatcher } from '@sveltejs/kit';
|
|
106
|
+
export const match = ((param) => !param.startsWith('_cam/')) satisfies ParamMatcher;
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
and rename the route directory `src/routes/[...rest]` → `src/routes/[...rest=notcam]`. With a
|
|
110
|
+
custom `scriptPath`, test for its directory instead (`/api/cam/b.js` → `api/cam/`).
|
|
111
|
+
|
|
80
112
|
## Options
|
|
81
113
|
|
|
82
114
|
| option | default | meaning |
|
package/dist/index.cjs
CHANGED
|
@@ -44,7 +44,7 @@ var import_iife_string = __toESM(require("@camada/browser/iife-string"), 1);
|
|
|
44
44
|
|
|
45
45
|
// package.json
|
|
46
46
|
var name = "@camada/sveltekit";
|
|
47
|
-
var version = "0.1.
|
|
47
|
+
var version = "0.1.4";
|
|
48
48
|
|
|
49
49
|
// src/version.ts
|
|
50
50
|
var SDK_ID = `${name}/${version}`;
|
|
@@ -74,14 +74,50 @@ function contextOf(event) {
|
|
|
74
74
|
httpVersion: cf?.httpProtocol ? cf.httpProtocol.replace(/^HTTP\//i, "") : null
|
|
75
75
|
};
|
|
76
76
|
}
|
|
77
|
+
var DATA_SUFFIX = "/__data.json";
|
|
78
|
+
var HTML_DATA_SUFFIX = ".html__data.json";
|
|
79
|
+
function dataRequestPage(url) {
|
|
80
|
+
let path;
|
|
81
|
+
if (url.pathname.endsWith(HTML_DATA_SUFFIX)) path = url.pathname.slice(0, -HTML_DATA_SUFFIX.length) + ".html";
|
|
82
|
+
else if (url.pathname.endsWith(DATA_SUFFIX)) path = url.pathname.slice(0, -DATA_SUFFIX.length) || "/";
|
|
83
|
+
else return null;
|
|
84
|
+
const q = new URLSearchParams(url.search);
|
|
85
|
+
if (q.get("x-sveltekit-trailing-slash") === "1" && !path.endsWith("/")) path += "/";
|
|
86
|
+
q.delete("x-sveltekit-trailing-slash");
|
|
87
|
+
q.delete("x-sveltekit-invalidated");
|
|
88
|
+
const search = q.toString();
|
|
89
|
+
return path + (search ? "?" + search : "");
|
|
90
|
+
}
|
|
91
|
+
function forNavigation(req, res, bouncePath) {
|
|
92
|
+
return (0, import_core.guarded)(() => {
|
|
93
|
+
if (res.status !== 403 || res.headers.get("x-camada-challenge") !== "1") return res;
|
|
94
|
+
const page = dataRequestPage(new URL(req.url));
|
|
95
|
+
if (page === null) return res;
|
|
96
|
+
void res.body?.cancel().catch(() => {
|
|
97
|
+
});
|
|
98
|
+
return new Response(JSON.stringify({ type: "redirect", location: `${bouncePath}?to=${encodeURIComponent(page)}` }), {
|
|
99
|
+
status: 200,
|
|
100
|
+
headers: { "content-type": "application/json", "cache-control": "no-store", "x-camada-challenge": "1" }
|
|
101
|
+
});
|
|
102
|
+
}, res);
|
|
103
|
+
}
|
|
77
104
|
function camada(opts = {}) {
|
|
78
105
|
const cam = (0, import_fetch.createFetchCamada)({ tap: import_core.TAP_SVELTEKIT, sdk: SDK_ID, iife: import_iife_string.default }, opts);
|
|
79
106
|
instances.add(cam);
|
|
107
|
+
const bouncePath = (opts.scriptPath ?? "/_cam/b.js").replace(/[^/]*$/, "reload");
|
|
80
108
|
return async ({ event, resolve }) => {
|
|
81
109
|
if (event.isSubRequest) return resolve(event);
|
|
82
|
-
const
|
|
110
|
+
const ctx = contextOf(event);
|
|
111
|
+
const bounce = (0, import_core.guarded)(() => {
|
|
112
|
+
const url = new URL(event.request.url);
|
|
113
|
+
if (event.request.method !== "GET" || url.pathname !== bouncePath) return null;
|
|
114
|
+
if (!cam.active(ctx)) return null;
|
|
115
|
+
return new Response(null, { status: 302, headers: { location: (0, import_core.safeReturnTo)(url.searchParams.get("to")), "cache-control": "no-store" } });
|
|
116
|
+
}, null);
|
|
117
|
+
if (bounce) return bounce;
|
|
118
|
+
const r = await cam.before(event.request, ctx);
|
|
83
119
|
if (!r) return resolve(event);
|
|
84
|
-
if (r.response) return r.response;
|
|
120
|
+
if (r.response) return forNavigation(event.request, r.response, bouncePath);
|
|
85
121
|
const vars = r.vars;
|
|
86
122
|
(0, import_core.guarded)(() => {
|
|
87
123
|
event.locals[VAR] = vars;
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/camada.ts","../package.json","../src/version.ts"],"sourcesContent":["// @camada/sveltekit — the one-line install for a SvelteKit app, in src/hooks.server.ts:\n// export const handle = camada(); // env: CAMADA_KEY (+ CAMADA_INGEST_URL / CAMADA_SNAPSHOT_URL in dev)\n// scriptTag(event) // the first-party beacon, returned from a load() into the page\n// track(event, 'login_failed', { user }) // an outcome the wire cannot show\nexport { camada, track, scriptTag, resetCamada, type CamadaSvelteKitOptions, type CamadaSvelteKitVars } from './camada.js';\n","// @camada/sveltekit — the `handle` hook that binds @camada/core/fetch to a SvelteKit app. The\n// pipeline (verdict, block, challenge, beacon, the event) lives in core; this file only says what\n// SvelteKit knows: the adapter-resolved client address, the per-request env and execution context\n// an edge adapter hands over, the cookie API, and `event.locals` as the per-request slot.\nimport type { Handle, RequestEvent } from '@sveltejs/kit';\nimport { guarded, TAP_SVELTEKIT } from '@camada/core';\nimport { createFetchCamada, withRid, withSetCookie, 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 CamadaSvelteKitOptions = FetchCamadaOptions;\nexport type CamadaSvelteKitVars = FetchVars;\n\nconst VAR = '__camada'; // private: track() and scriptTag() are the API, not event.locals\nconst instances = new Set<FetchCamada>();\n\ntype Env = Record<string, string | undefined>;\n\n/** What adapter-cloudflare puts on `event.platform`; every field is optional because other adapters put nothing there. */\ninterface CfPlatform {\n env?: Env;\n context?: { waitUntil(p: Promise<unknown>): void };\n cf?: { asn?: number; country?: string; tlsClientExtensionsSha1?: string; httpProtocol?: string };\n}\n\nconst slotOf = (event: RequestEvent): FetchVars | undefined =>\n guarded(() => (event.locals as Record<string, unknown>)[VAR] as FetchVars | undefined, undefined);\n\n/** Everything the runtime vouches for. Nothing here is read from a client header. */\nfunction contextOf(event: RequestEvent): FetchRequestContext {\n const platform = event.platform as CfPlatform | undefined;\n // process.env on adapter-node (and nodejs_compat Workers), the bindings on adapter-cloudflare;\n // a binding can be a KV namespace, so the merge is guarded and core reads only CAMADA_* keys.\n const env = guarded<Env>(() => ({ ...(globalThis as { process?: { env?: Env } }).process?.env, ...platform?.env }), {});\n let peer: string | null = null;\n try { peer = event.getClientAddress(); } catch { /* prerendering, or an adapter with no socket: nothing vouched for */ }\n const exec = platform?.context;\n const cf = platform?.cf;\n return {\n peer, env,\n waitUntil: exec ? (p) => exec.waitUntil(p) : undefined,\n asn: cf?.asn ?? null,\n country: cf?.country ?? null,\n tlsx: cf?.tlsClientExtensionsSha1 ?? null,\n // The connection terminates at Cloudflare, so `cf.httpProtocol` is the visitor's own hop.\n httpVersion: cf?.httpProtocol ? cf.httpProtocol.replace(/^HTTP\\//i, '') : null,\n };\n}\n\n/**\n * The `handle` for `src/hooks.server.ts`: `export const handle = camada()`, or first in a\n * `sequence()`. Reads `CAMADA_KEY` (+ `CAMADA_INGEST_URL` / `CAMADA_SNAPSHOT_URL`) from\n * `process.env` or the Cloudflare bindings; inert without a key. One pipeline per call.\n */\nexport function camada(opts: CamadaSvelteKitOptions = {}): Handle {\n const cam = createFetchCamada({ tap: TAP_SVELTEKIT, sdk: SDK_ID, iife }, opts); // mode defaults to lazy in core: the handle may run on edge/serverless\n instances.add(cam);\n return async ({ event, resolve }) => {\n // A server-side `event.fetch` to the app's own routes runs the handle again on a fresh event\n // with no user-agent or accept: one page view is one event, and the page-level verdict must\n // not be re-applied to the server's own load().\n if (event.isSubRequest) return resolve(event);\n const r = await cam.before(event.request, contextOf(event));\n if (!r) return resolve(event);\n if (r.response) return r.response;\n const vars = r.vars;\n guarded(() => { (event.locals as Record<string, unknown>)[VAR] = vars; }, undefined);\n // The _sfp cookie rides the response, not `event.cookies.set`: SvelteKit appends those in place\n // after resolve(), which throws on an immutable Response (a `fetch()` result, `Response.redirect()`).\n // withSetCookie copies such a response; the hook sees it, so the 500 never happens.\n const res = await resolve(event);\n // SvelteKit swaps a 200 carrying an etag for a bodiless 304 after this hook when the client's\n // If-None-Match matches, dropping the body unread: waiting on it would never ship, so an\n // etagged response ships now even when it is an event stream. (That 304 is built by SvelteKit\n // from a fixed header allow-list, so it carries no x-rid; the hook cannot change that.)\n const out = guarded(() => (vars.sessionCookie ? withSetCookie(res, vars.sessionCookie) : res), res);\n if (out.headers.has('etag')) { cam.after(event.request, vars, out.status); return guarded(() => withRid(out, vars), out); }\n return cam.finish(event.request, vars, out); // an SSE body ships once it has gone out; anything else ships now, untouched\n };\n}\n\n/**\n * Records an outcome the app knows and the wire cannot show (`login_failed`, `signup`,\n * `payment_failed`, ...), joined to this request's event by rid and session. The user identifier\n * is HMAC-hashed in-process. Never throws; a no-op where the handle did not run for this event.\n */\nexport const track = (event: RequestEvent, et: string, data?: { user?: string }): Promise<void> => coreTrack(slotOf(event), et, data);\n\n/** The beacon `<script>` tag for this request's page; `''` where the handle did not run or the tenant turned the beacon off. */\nexport const scriptTag = (event: RequestEvent): string => coreScriptTag(slotOf(event));\n\n/** Test/reset hook: stops and drops every engine of every handle this module created. */\nexport function resetCamada(): void {\n for (const cam of instances) cam.reset();\n}\n","{\n \"name\": \"@camada/sveltekit\",\n \"version\": \"0.1.2\",\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 SvelteKit (hooks.server.ts handle): snapshot enforcement, first-party challenge and beacon, track() outcomes, waitUntil-flushed event shipping on Cloudflare\",\n \"exports\": {\n \".\": {\n \"types\": \"./dist/index.d.ts\",\n \"import\": \"./dist/index.js\",\n \"require\": \"./dist/index.cjs\"\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 \"@sveltejs/kit\": \">=2\"\n },\n \"devDependencies\": {\n \"@sveltejs/kit\": \"^2.0.0\",\n \"@types/node\": \"^22.10.0\",\n \"svelte\": \"^5.0.0\",\n \"tsup\": \"^8.3.5\",\n \"typescript\": \"^5.7.2\",\n \"vite\": \"^8.0.0\",\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"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACKA,kBAAuC;AACvC,mBAA+L;AAC/L,yBAAiB;;;ACNf,WAAQ;AACR,cAAW;;;ACCN,IAAM,SAAS,GAAG,IAAI,IAAI,OAAO;;;AFUxC,IAAM,MAAM;AACZ,IAAM,YAAY,oBAAI,IAAiB;AAWvC,IAAM,SAAS,CAAC,cACd,qBAAQ,MAAO,MAAM,OAAmC,GAAG,GAA4B,MAAS;AAGlG,SAAS,UAAU,OAA0C;AAC3D,QAAM,WAAW,MAAM;AAGvB,QAAM,UAAM,qBAAa,OAAO,EAAE,GAAI,WAA2C,SAAS,KAAK,GAAG,UAAU,IAAI,IAAI,CAAC,CAAC;AACtH,MAAI,OAAsB;AAC1B,MAAI;AAAE,WAAO,MAAM,iBAAiB;AAAA,EAAG,QAAQ;AAAA,EAAwE;AACvH,QAAM,OAAO,UAAU;AACvB,QAAM,KAAK,UAAU;AACrB,SAAO;AAAA,IACL;AAAA,IAAM;AAAA,IACN,WAAW,OAAO,CAAC,MAAM,KAAK,UAAU,CAAC,IAAI;AAAA,IAC7C,KAAK,IAAI,OAAO;AAAA,IAChB,SAAS,IAAI,WAAW;AAAA,IACxB,MAAM,IAAI,2BAA2B;AAAA;AAAA,IAErC,aAAa,IAAI,eAAe,GAAG,aAAa,QAAQ,YAAY,EAAE,IAAI;AAAA,EAC5E;AACF;AAOO,SAAS,OAAO,OAA+B,CAAC,GAAW;AAChE,QAAM,UAAM,gCAAkB,EAAE,KAAK,2BAAe,KAAK,QAAQ,yBAAAA,QAAK,GAAG,IAAI;AAC7E,YAAU,IAAI,GAAG;AACjB,SAAO,OAAO,EAAE,OAAO,QAAQ,MAAM;AAInC,QAAI,MAAM,aAAc,QAAO,QAAQ,KAAK;AAC5C,UAAM,IAAI,MAAM,IAAI,OAAO,MAAM,SAAS,UAAU,KAAK,CAAC;AAC1D,QAAI,CAAC,EAAG,QAAO,QAAQ,KAAK;AAC5B,QAAI,EAAE,SAAU,QAAO,EAAE;AACzB,UAAM,OAAO,EAAE;AACf,6BAAQ,MAAM;AAAE,MAAC,MAAM,OAAmC,GAAG,IAAI;AAAA,IAAM,GAAG,MAAS;AAInF,UAAM,MAAM,MAAM,QAAQ,KAAK;AAK/B,UAAM,UAAM,qBAAQ,MAAO,KAAK,oBAAgB,4BAAc,KAAK,KAAK,aAAa,IAAI,KAAM,GAAG;AAClG,QAAI,IAAI,QAAQ,IAAI,MAAM,GAAG;AAAE,UAAI,MAAM,MAAM,SAAS,MAAM,IAAI,MAAM;AAAG,iBAAO,qBAAQ,UAAM,sBAAQ,KAAK,IAAI,GAAG,GAAG;AAAA,IAAG;AAC1H,WAAO,IAAI,OAAO,MAAM,SAAS,MAAM,GAAG;AAAA,EAC5C;AACF;AAOO,IAAM,QAAQ,CAAC,OAAqB,IAAY,aAA4C,aAAAC,OAAU,OAAO,KAAK,GAAG,IAAI,IAAI;AAG7H,IAAM,YAAY,CAAC,cAAgC,aAAAC,WAAc,OAAO,KAAK,CAAC;AAG9E,SAAS,cAAoB;AAClC,aAAW,OAAO,UAAW,KAAI,MAAM;AACzC;","names":["iife","coreTrack","coreScriptTag"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/camada.ts","../package.json","../src/version.ts"],"sourcesContent":["// @camada/sveltekit — the one-line install for a SvelteKit app, in src/hooks.server.ts:\n// export const handle = camada(); // env: CAMADA_KEY (+ CAMADA_INGEST_URL / CAMADA_SNAPSHOT_URL in dev)\n// scriptTag(event) // the first-party beacon, returned from a load() into the page\n// track(event, 'login_failed', { user }) // an outcome the wire cannot show\nexport { camada, track, scriptTag, resetCamada, type CamadaSvelteKitOptions, type CamadaSvelteKitVars } from './camada.js';\n","// @camada/sveltekit — the `handle` hook that binds @camada/core/fetch to a SvelteKit app. The\n// pipeline (verdict, block, challenge, beacon, the event) lives in core; this file only says what\n// SvelteKit knows: the adapter-resolved client address, the per-request env and execution context\n// an edge adapter hands over, the cookie API, and `event.locals` as the per-request slot.\nimport type { Handle, RequestEvent } from '@sveltejs/kit';\nimport { guarded, safeReturnTo, TAP_SVELTEKIT } from '@camada/core';\nimport { createFetchCamada, withRid, withSetCookie, 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 CamadaSvelteKitOptions = FetchCamadaOptions;\nexport type CamadaSvelteKitVars = FetchVars;\n\nconst VAR = '__camada'; // private: track() and scriptTag() are the API, not event.locals\nconst instances = new Set<FetchCamada>();\n\ntype Env = Record<string, string | undefined>;\n\n/** What adapter-cloudflare puts on `event.platform`; every field is optional because other adapters put nothing there. */\ninterface CfPlatform {\n env?: Env;\n context?: { waitUntil(p: Promise<unknown>): void };\n cf?: { asn?: number; country?: string; tlsClientExtensionsSha1?: string; httpProtocol?: string };\n}\n\nconst slotOf = (event: RequestEvent): FetchVars | undefined =>\n guarded(() => (event.locals as Record<string, unknown>)[VAR] as FetchVars | undefined, undefined);\n\n/** Everything the runtime vouches for. Nothing here is read from a client header. */\nfunction contextOf(event: RequestEvent): FetchRequestContext {\n const platform = event.platform as CfPlatform | undefined;\n // process.env on adapter-node (and nodejs_compat Workers), the bindings on adapter-cloudflare;\n // a binding can be a KV namespace, so the merge is guarded and core reads only CAMADA_* keys.\n const env = guarded<Env>(() => ({ ...(globalThis as { process?: { env?: Env } }).process?.env, ...platform?.env }), {});\n let peer: string | null = null;\n try { peer = event.getClientAddress(); } catch { /* prerendering, or an adapter with no socket: nothing vouched for */ }\n const exec = platform?.context;\n const cf = platform?.cf;\n return {\n peer, env,\n waitUntil: exec ? (p) => exec.waitUntil(p) : undefined,\n asn: cf?.asn ?? null,\n country: cf?.country ?? null,\n tlsx: cf?.tlsClientExtensionsSha1 ?? null,\n // The connection terminates at Cloudflare, so `cf.httpProtocol` is the visitor's own hop.\n httpVersion: cf?.httpProtocol ? cf.httpProtocol.replace(/^HTTP\\//i, '') : null,\n };\n}\n\nconst DATA_SUFFIX = '/__data.json';\nconst HTML_DATA_SUFFIX = '.html__data.json';\n\n/** The page a SvelteKit `__data.json` request loads data for, or null when `url` is not one\n * (kit's own strip_data_suffix, plus its trailing-slash and invalidation params). */\nfunction dataRequestPage(url: URL): string | null {\n let path: string;\n if (url.pathname.endsWith(HTML_DATA_SUFFIX)) path = url.pathname.slice(0, -HTML_DATA_SUFFIX.length) + '.html';\n else if (url.pathname.endsWith(DATA_SUFFIX)) path = url.pathname.slice(0, -DATA_SUFFIX.length) || '/';\n else return null;\n const q = new URLSearchParams(url.search);\n if (q.get('x-sveltekit-trailing-slash') === '1' && !path.endsWith('/')) path += '/';\n q.delete('x-sveltekit-trailing-slash');\n q.delete('x-sveltekit-invalidated');\n const search = q.toString();\n return path + (search ? '?' + search : '');\n}\n\n/**\n * A challenged client-side navigation (camada-all-wjln). SvelteKit's client fetches\n * `<path>/__data.json` and renders the root error page for a 403 there, so the visitor would never\n * meet the challenge. A data `{\"type\":\"redirect\"}` to the page itself does not help: the client\n * follows it client-side, fetches `__data.json` again and loops into its 20-redirect error. So the\n * redirect points at camada's bounce path (`<scriptPath dir>/reload?to=<page>`). No app route\n * matches it, so the client falls back to a native navigation (`location.href`), and the bounce\n * then 302s that document request to the page, which gets the HTML challenge. Every other answer,\n * and any request that is not a `__data.json` fetch (an API call from app code), is left as it is.\n */\nfunction forNavigation(req: Request, res: Response, bouncePath: string): Response {\n return guarded(() => {\n if (res.status !== 403 || res.headers.get('x-camada-challenge') !== '1') return res;\n const page = dataRequestPage(new URL(req.url));\n if (page === null) return res;\n void res.body?.cancel().catch(() => {});\n return new Response(JSON.stringify({ type: 'redirect', location: `${bouncePath}?to=${encodeURIComponent(page)}` }), {\n status: 200, headers: { 'content-type': 'application/json', 'cache-control': 'no-store', 'x-camada-challenge': '1' },\n });\n }, res);\n}\n\n/**\n * The `handle` for `src/hooks.server.ts`: `export const handle = camada()`, or first in a\n * `sequence()`. Reads `CAMADA_KEY` (+ `CAMADA_INGEST_URL` / `CAMADA_SNAPSHOT_URL`) from\n * `process.env` or the Cloudflare bindings; inert without a key. One pipeline per call.\n */\nexport function camada(opts: CamadaSvelteKitOptions = {}): Handle {\n const cam = createFetchCamada({ tap: TAP_SVELTEKIT, sdk: SDK_ID, iife }, opts); // mode defaults to lazy in core: the handle may run on edge/serverless\n instances.add(cam);\n const bouncePath = (opts.scriptPath ?? '/_cam/b.js').replace(/[^/]*$/, 'reload'); // beside the beacon: camada's own directory\n return async ({ event, resolve }) => {\n // A server-side `event.fetch` to the app's own routes runs the handle again on a fresh event\n // with no user-agent or accept: one page view is one event, and the page-level verdict must\n // not be re-applied to the server's own load().\n if (event.isSubRequest) return resolve(event);\n const ctx = contextOf(event);\n // The bounce a challenged data request sends the client through (see forNavigation): a\n // same-site 302 back to the page, which then meets the verdict as a document request. Only\n // while camada is active (core's own inert test: a key, and no CAMADA_DISABLED=1); otherwise\n // the path is the app's.\n const bounce = guarded(() => {\n const url = new URL(event.request.url);\n if (event.request.method !== 'GET' || url.pathname !== bouncePath) return null;\n if (!cam.active(ctx)) return null;\n return new Response(null, { status: 302, headers: { location: safeReturnTo(url.searchParams.get('to')), 'cache-control': 'no-store' } });\n }, null);\n if (bounce) return bounce;\n const r = await cam.before(event.request, ctx);\n if (!r) return resolve(event);\n if (r.response) return forNavigation(event.request, r.response, bouncePath);\n const vars = r.vars;\n guarded(() => { (event.locals as Record<string, unknown>)[VAR] = vars; }, undefined);\n // The _sfp cookie rides the response, not `event.cookies.set`: SvelteKit appends those in place\n // after resolve(), which throws on an immutable Response (a `fetch()` result, `Response.redirect()`).\n // withSetCookie copies such a response; the hook sees it, so the 500 never happens.\n const res = await resolve(event);\n // SvelteKit swaps a 200 carrying an etag for a bodiless 304 after this hook when the client's\n // If-None-Match matches, dropping the body unread: waiting on it would never ship, so an\n // etagged response ships now even when it is an event stream. (That 304 is built by SvelteKit\n // from a fixed header allow-list, so it carries no x-rid; the hook cannot change that.)\n const out = guarded(() => (vars.sessionCookie ? withSetCookie(res, vars.sessionCookie) : res), res);\n if (out.headers.has('etag')) { cam.after(event.request, vars, out.status); return guarded(() => withRid(out, vars), out); }\n return cam.finish(event.request, vars, out); // an SSE body ships once it has gone out; anything else ships now, untouched\n };\n}\n\n/**\n * Records an outcome the app knows and the wire cannot show (`login_failed`, `signup`,\n * `payment_failed`, ...), joined to this request's event by rid and session. The user identifier\n * is HMAC-hashed in-process. Never throws; a no-op where the handle did not run for this event.\n */\nexport const track = (event: RequestEvent, et: string, data?: { user?: string }): Promise<void> => coreTrack(slotOf(event), et, data);\n\n/** The beacon `<script>` tag for this request's page; `''` where the handle did not run or the tenant turned the beacon off. */\nexport const scriptTag = (event: RequestEvent): string => coreScriptTag(slotOf(event));\n\n/** Test/reset hook: stops and drops every engine of every handle this module created. */\nexport function resetCamada(): void {\n for (const cam of instances) cam.reset();\n}\n","{\n \"name\": \"@camada/sveltekit\",\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 SvelteKit (hooks.server.ts handle): snapshot enforcement, first-party challenge and beacon, track() outcomes, waitUntil-flushed event shipping on Cloudflare\",\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 \"@sveltejs/kit\": \">=2\"\n },\n \"devDependencies\": {\n \"@sveltejs/kit\": \"^2.0.0\",\n \"@types/node\": \"^22.10.0\",\n \"svelte\": \"^5.0.0\",\n \"tsup\": \"^8.3.5\",\n \"typescript\": \"^5.7.2\",\n \"vite\": \"^8.0.0\",\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"],"mappings":"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","names":["iife","coreTrack","coreScriptTag"]}
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
// src/camada.ts
|
|
2
|
-
import { guarded, TAP_SVELTEKIT } from "@camada/core";
|
|
2
|
+
import { guarded, safeReturnTo, TAP_SVELTEKIT } from "@camada/core";
|
|
3
3
|
import { createFetchCamada, withRid, withSetCookie, track as coreTrack, scriptTag as coreScriptTag } from "@camada/core/fetch";
|
|
4
4
|
import iife from "@camada/browser/iife-string";
|
|
5
5
|
|
|
6
6
|
// package.json
|
|
7
7
|
var name = "@camada/sveltekit";
|
|
8
|
-
var version = "0.1.
|
|
8
|
+
var version = "0.1.4";
|
|
9
9
|
|
|
10
10
|
// src/version.ts
|
|
11
11
|
var SDK_ID = `${name}/${version}`;
|
|
@@ -35,14 +35,50 @@ function contextOf(event) {
|
|
|
35
35
|
httpVersion: cf?.httpProtocol ? cf.httpProtocol.replace(/^HTTP\//i, "") : null
|
|
36
36
|
};
|
|
37
37
|
}
|
|
38
|
+
var DATA_SUFFIX = "/__data.json";
|
|
39
|
+
var HTML_DATA_SUFFIX = ".html__data.json";
|
|
40
|
+
function dataRequestPage(url) {
|
|
41
|
+
let path;
|
|
42
|
+
if (url.pathname.endsWith(HTML_DATA_SUFFIX)) path = url.pathname.slice(0, -HTML_DATA_SUFFIX.length) + ".html";
|
|
43
|
+
else if (url.pathname.endsWith(DATA_SUFFIX)) path = url.pathname.slice(0, -DATA_SUFFIX.length) || "/";
|
|
44
|
+
else return null;
|
|
45
|
+
const q = new URLSearchParams(url.search);
|
|
46
|
+
if (q.get("x-sveltekit-trailing-slash") === "1" && !path.endsWith("/")) path += "/";
|
|
47
|
+
q.delete("x-sveltekit-trailing-slash");
|
|
48
|
+
q.delete("x-sveltekit-invalidated");
|
|
49
|
+
const search = q.toString();
|
|
50
|
+
return path + (search ? "?" + search : "");
|
|
51
|
+
}
|
|
52
|
+
function forNavigation(req, res, bouncePath) {
|
|
53
|
+
return guarded(() => {
|
|
54
|
+
if (res.status !== 403 || res.headers.get("x-camada-challenge") !== "1") return res;
|
|
55
|
+
const page = dataRequestPage(new URL(req.url));
|
|
56
|
+
if (page === null) return res;
|
|
57
|
+
void res.body?.cancel().catch(() => {
|
|
58
|
+
});
|
|
59
|
+
return new Response(JSON.stringify({ type: "redirect", location: `${bouncePath}?to=${encodeURIComponent(page)}` }), {
|
|
60
|
+
status: 200,
|
|
61
|
+
headers: { "content-type": "application/json", "cache-control": "no-store", "x-camada-challenge": "1" }
|
|
62
|
+
});
|
|
63
|
+
}, res);
|
|
64
|
+
}
|
|
38
65
|
function camada(opts = {}) {
|
|
39
66
|
const cam = createFetchCamada({ tap: TAP_SVELTEKIT, sdk: SDK_ID, iife }, opts);
|
|
40
67
|
instances.add(cam);
|
|
68
|
+
const bouncePath = (opts.scriptPath ?? "/_cam/b.js").replace(/[^/]*$/, "reload");
|
|
41
69
|
return async ({ event, resolve }) => {
|
|
42
70
|
if (event.isSubRequest) return resolve(event);
|
|
43
|
-
const
|
|
71
|
+
const ctx = contextOf(event);
|
|
72
|
+
const bounce = guarded(() => {
|
|
73
|
+
const url = new URL(event.request.url);
|
|
74
|
+
if (event.request.method !== "GET" || url.pathname !== bouncePath) return null;
|
|
75
|
+
if (!cam.active(ctx)) return null;
|
|
76
|
+
return new Response(null, { status: 302, headers: { location: safeReturnTo(url.searchParams.get("to")), "cache-control": "no-store" } });
|
|
77
|
+
}, null);
|
|
78
|
+
if (bounce) return bounce;
|
|
79
|
+
const r = await cam.before(event.request, ctx);
|
|
44
80
|
if (!r) return resolve(event);
|
|
45
|
-
if (r.response) return r.response;
|
|
81
|
+
if (r.response) return forNavigation(event.request, r.response, bouncePath);
|
|
46
82
|
const vars = r.vars;
|
|
47
83
|
guarded(() => {
|
|
48
84
|
event.locals[VAR] = vars;
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/camada.ts","../package.json","../src/version.ts"],"sourcesContent":["// @camada/sveltekit — the `handle` hook that binds @camada/core/fetch to a SvelteKit app. The\n// pipeline (verdict, block, challenge, beacon, the event) lives in core; this file only says what\n// SvelteKit knows: the adapter-resolved client address, the per-request env and execution context\n// an edge adapter hands over, the cookie API, and `event.locals` as the per-request slot.\nimport type { Handle, RequestEvent } from '@sveltejs/kit';\nimport { guarded, TAP_SVELTEKIT } from '@camada/core';\nimport { createFetchCamada, withRid, withSetCookie, 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 CamadaSvelteKitOptions = FetchCamadaOptions;\nexport type CamadaSvelteKitVars = FetchVars;\n\nconst VAR = '__camada'; // private: track() and scriptTag() are the API, not event.locals\nconst instances = new Set<FetchCamada>();\n\ntype Env = Record<string, string | undefined>;\n\n/** What adapter-cloudflare puts on `event.platform`; every field is optional because other adapters put nothing there. */\ninterface CfPlatform {\n env?: Env;\n context?: { waitUntil(p: Promise<unknown>): void };\n cf?: { asn?: number; country?: string; tlsClientExtensionsSha1?: string; httpProtocol?: string };\n}\n\nconst slotOf = (event: RequestEvent): FetchVars | undefined =>\n guarded(() => (event.locals as Record<string, unknown>)[VAR] as FetchVars | undefined, undefined);\n\n/** Everything the runtime vouches for. Nothing here is read from a client header. */\nfunction contextOf(event: RequestEvent): FetchRequestContext {\n const platform = event.platform as CfPlatform | undefined;\n // process.env on adapter-node (and nodejs_compat Workers), the bindings on adapter-cloudflare;\n // a binding can be a KV namespace, so the merge is guarded and core reads only CAMADA_* keys.\n const env = guarded<Env>(() => ({ ...(globalThis as { process?: { env?: Env } }).process?.env, ...platform?.env }), {});\n let peer: string | null = null;\n try { peer = event.getClientAddress(); } catch { /* prerendering, or an adapter with no socket: nothing vouched for */ }\n const exec = platform?.context;\n const cf = platform?.cf;\n return {\n peer, env,\n waitUntil: exec ? (p) => exec.waitUntil(p) : undefined,\n asn: cf?.asn ?? null,\n country: cf?.country ?? null,\n tlsx: cf?.tlsClientExtensionsSha1 ?? null,\n // The connection terminates at Cloudflare, so `cf.httpProtocol` is the visitor's own hop.\n httpVersion: cf?.httpProtocol ? cf.httpProtocol.replace(/^HTTP\\//i, '') : null,\n };\n}\n\n/**\n * The `handle` for `src/hooks.server.ts`: `export const handle = camada()`, or first in a\n * `sequence()`. Reads `CAMADA_KEY` (+ `CAMADA_INGEST_URL` / `CAMADA_SNAPSHOT_URL`) from\n * `process.env` or the Cloudflare bindings; inert without a key. One pipeline per call.\n */\nexport function camada(opts: CamadaSvelteKitOptions = {}): Handle {\n const cam = createFetchCamada({ tap: TAP_SVELTEKIT, sdk: SDK_ID, iife }, opts); // mode defaults to lazy in core: the handle may run on edge/serverless\n instances.add(cam);\n return async ({ event, resolve }) => {\n // A server-side `event.fetch` to the app's own routes runs the handle again on a fresh event\n // with no user-agent or accept: one page view is one event, and the page-level verdict must\n // not be re-applied to the server's own load().\n if (event.isSubRequest) return resolve(event);\n const r = await cam.before(event.request, contextOf(event));\n if (!r) return resolve(event);\n if (r.response) return r.response;\n const vars = r.vars;\n guarded(() => { (event.locals as Record<string, unknown>)[VAR] = vars; }, undefined);\n // The _sfp cookie rides the response, not `event.cookies.set`: SvelteKit appends those in place\n // after resolve(), which throws on an immutable Response (a `fetch()` result, `Response.redirect()`).\n // withSetCookie copies such a response; the hook sees it, so the 500 never happens.\n const res = await resolve(event);\n // SvelteKit swaps a 200 carrying an etag for a bodiless 304 after this hook when the client's\n // If-None-Match matches, dropping the body unread: waiting on it would never ship, so an\n // etagged response ships now even when it is an event stream. (That 304 is built by SvelteKit\n // from a fixed header allow-list, so it carries no x-rid; the hook cannot change that.)\n const out = guarded(() => (vars.sessionCookie ? withSetCookie(res, vars.sessionCookie) : res), res);\n if (out.headers.has('etag')) { cam.after(event.request, vars, out.status); return guarded(() => withRid(out, vars), out); }\n return cam.finish(event.request, vars, out); // an SSE body ships once it has gone out; anything else ships now, untouched\n };\n}\n\n/**\n * Records an outcome the app knows and the wire cannot show (`login_failed`, `signup`,\n * `payment_failed`, ...), joined to this request's event by rid and session. The user identifier\n * is HMAC-hashed in-process. Never throws; a no-op where the handle did not run for this event.\n */\nexport const track = (event: RequestEvent, et: string, data?: { user?: string }): Promise<void> => coreTrack(slotOf(event), et, data);\n\n/** The beacon `<script>` tag for this request's page; `''` where the handle did not run or the tenant turned the beacon off. */\nexport const scriptTag = (event: RequestEvent): string => coreScriptTag(slotOf(event));\n\n/** Test/reset hook: stops and drops every engine of every handle this module created. */\nexport function resetCamada(): void {\n for (const cam of instances) cam.reset();\n}\n","{\n \"name\": \"@camada/sveltekit\",\n \"version\": \"0.1.2\",\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 SvelteKit (hooks.server.ts handle): snapshot enforcement, first-party challenge and beacon, track() outcomes, waitUntil-flushed event shipping on Cloudflare\",\n \"exports\": {\n \".\": {\n \"types\": \"./dist/index.d.ts\",\n \"import\": \"./dist/index.js\",\n \"require\": \"./dist/index.cjs\"\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 \"@sveltejs/kit\": \">=2\"\n },\n \"devDependencies\": {\n \"@sveltejs/kit\": \"^2.0.0\",\n \"@types/node\": \"^22.10.0\",\n \"svelte\": \"^5.0.0\",\n \"tsup\": \"^8.3.5\",\n \"typescript\": \"^5.7.2\",\n \"vite\": \"^8.0.0\",\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"],"mappings":";AAKA,SAAS,SAAS,qBAAqB;AACvC,SAAS,mBAAmB,SAAS,eAAe,SAAS,WAAW,aAAa,qBAA0G;AAC/L,OAAO,UAAU;;;ACNf,WAAQ;AACR,cAAW;;;ACCN,IAAM,SAAS,GAAG,IAAI,IAAI,OAAO;;;AFUxC,IAAM,MAAM;AACZ,IAAM,YAAY,oBAAI,IAAiB;AAWvC,IAAM,SAAS,CAAC,UACd,QAAQ,MAAO,MAAM,OAAmC,GAAG,GAA4B,MAAS;AAGlG,SAAS,UAAU,OAA0C;AAC3D,QAAM,WAAW,MAAM;AAGvB,QAAM,MAAM,QAAa,OAAO,EAAE,GAAI,WAA2C,SAAS,KAAK,GAAG,UAAU,IAAI,IAAI,CAAC,CAAC;AACtH,MAAI,OAAsB;AAC1B,MAAI;AAAE,WAAO,MAAM,iBAAiB;AAAA,EAAG,QAAQ;AAAA,EAAwE;AACvH,QAAM,OAAO,UAAU;AACvB,QAAM,KAAK,UAAU;AACrB,SAAO;AAAA,IACL;AAAA,IAAM;AAAA,IACN,WAAW,OAAO,CAAC,MAAM,KAAK,UAAU,CAAC,IAAI;AAAA,IAC7C,KAAK,IAAI,OAAO;AAAA,IAChB,SAAS,IAAI,WAAW;AAAA,IACxB,MAAM,IAAI,2BAA2B;AAAA;AAAA,IAErC,aAAa,IAAI,eAAe,GAAG,aAAa,QAAQ,YAAY,EAAE,IAAI;AAAA,EAC5E;AACF;AAOO,SAAS,OAAO,OAA+B,CAAC,GAAW;AAChE,QAAM,MAAM,kBAAkB,EAAE,KAAK,eAAe,KAAK,QAAQ,KAAK,GAAG,IAAI;AAC7E,YAAU,IAAI,GAAG;AACjB,SAAO,OAAO,EAAE,OAAO,QAAQ,MAAM;AAInC,QAAI,MAAM,aAAc,QAAO,QAAQ,KAAK;AAC5C,UAAM,IAAI,MAAM,IAAI,OAAO,MAAM,SAAS,UAAU,KAAK,CAAC;AAC1D,QAAI,CAAC,EAAG,QAAO,QAAQ,KAAK;AAC5B,QAAI,EAAE,SAAU,QAAO,EAAE;AACzB,UAAM,OAAO,EAAE;AACf,YAAQ,MAAM;AAAE,MAAC,MAAM,OAAmC,GAAG,IAAI;AAAA,IAAM,GAAG,MAAS;AAInF,UAAM,MAAM,MAAM,QAAQ,KAAK;AAK/B,UAAM,MAAM,QAAQ,MAAO,KAAK,gBAAgB,cAAc,KAAK,KAAK,aAAa,IAAI,KAAM,GAAG;AAClG,QAAI,IAAI,QAAQ,IAAI,MAAM,GAAG;AAAE,UAAI,MAAM,MAAM,SAAS,MAAM,IAAI,MAAM;AAAG,aAAO,QAAQ,MAAM,QAAQ,KAAK,IAAI,GAAG,GAAG;AAAA,IAAG;AAC1H,WAAO,IAAI,OAAO,MAAM,SAAS,MAAM,GAAG;AAAA,EAC5C;AACF;AAOO,IAAM,QAAQ,CAAC,OAAqB,IAAY,SAA4C,UAAU,OAAO,KAAK,GAAG,IAAI,IAAI;AAG7H,IAAM,YAAY,CAAC,UAAgC,cAAc,OAAO,KAAK,CAAC;AAG9E,SAAS,cAAoB;AAClC,aAAW,OAAO,UAAW,KAAI,MAAM;AACzC;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/camada.ts","../package.json","../src/version.ts"],"sourcesContent":["// @camada/sveltekit — the `handle` hook that binds @camada/core/fetch to a SvelteKit app. The\n// pipeline (verdict, block, challenge, beacon, the event) lives in core; this file only says what\n// SvelteKit knows: the adapter-resolved client address, the per-request env and execution context\n// an edge adapter hands over, the cookie API, and `event.locals` as the per-request slot.\nimport type { Handle, RequestEvent } from '@sveltejs/kit';\nimport { guarded, safeReturnTo, TAP_SVELTEKIT } from '@camada/core';\nimport { createFetchCamada, withRid, withSetCookie, 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 CamadaSvelteKitOptions = FetchCamadaOptions;\nexport type CamadaSvelteKitVars = FetchVars;\n\nconst VAR = '__camada'; // private: track() and scriptTag() are the API, not event.locals\nconst instances = new Set<FetchCamada>();\n\ntype Env = Record<string, string | undefined>;\n\n/** What adapter-cloudflare puts on `event.platform`; every field is optional because other adapters put nothing there. */\ninterface CfPlatform {\n env?: Env;\n context?: { waitUntil(p: Promise<unknown>): void };\n cf?: { asn?: number; country?: string; tlsClientExtensionsSha1?: string; httpProtocol?: string };\n}\n\nconst slotOf = (event: RequestEvent): FetchVars | undefined =>\n guarded(() => (event.locals as Record<string, unknown>)[VAR] as FetchVars | undefined, undefined);\n\n/** Everything the runtime vouches for. Nothing here is read from a client header. */\nfunction contextOf(event: RequestEvent): FetchRequestContext {\n const platform = event.platform as CfPlatform | undefined;\n // process.env on adapter-node (and nodejs_compat Workers), the bindings on adapter-cloudflare;\n // a binding can be a KV namespace, so the merge is guarded and core reads only CAMADA_* keys.\n const env = guarded<Env>(() => ({ ...(globalThis as { process?: { env?: Env } }).process?.env, ...platform?.env }), {});\n let peer: string | null = null;\n try { peer = event.getClientAddress(); } catch { /* prerendering, or an adapter with no socket: nothing vouched for */ }\n const exec = platform?.context;\n const cf = platform?.cf;\n return {\n peer, env,\n waitUntil: exec ? (p) => exec.waitUntil(p) : undefined,\n asn: cf?.asn ?? null,\n country: cf?.country ?? null,\n tlsx: cf?.tlsClientExtensionsSha1 ?? null,\n // The connection terminates at Cloudflare, so `cf.httpProtocol` is the visitor's own hop.\n httpVersion: cf?.httpProtocol ? cf.httpProtocol.replace(/^HTTP\\//i, '') : null,\n };\n}\n\nconst DATA_SUFFIX = '/__data.json';\nconst HTML_DATA_SUFFIX = '.html__data.json';\n\n/** The page a SvelteKit `__data.json` request loads data for, or null when `url` is not one\n * (kit's own strip_data_suffix, plus its trailing-slash and invalidation params). */\nfunction dataRequestPage(url: URL): string | null {\n let path: string;\n if (url.pathname.endsWith(HTML_DATA_SUFFIX)) path = url.pathname.slice(0, -HTML_DATA_SUFFIX.length) + '.html';\n else if (url.pathname.endsWith(DATA_SUFFIX)) path = url.pathname.slice(0, -DATA_SUFFIX.length) || '/';\n else return null;\n const q = new URLSearchParams(url.search);\n if (q.get('x-sveltekit-trailing-slash') === '1' && !path.endsWith('/')) path += '/';\n q.delete('x-sveltekit-trailing-slash');\n q.delete('x-sveltekit-invalidated');\n const search = q.toString();\n return path + (search ? '?' + search : '');\n}\n\n/**\n * A challenged client-side navigation (camada-all-wjln). SvelteKit's client fetches\n * `<path>/__data.json` and renders the root error page for a 403 there, so the visitor would never\n * meet the challenge. A data `{\"type\":\"redirect\"}` to the page itself does not help: the client\n * follows it client-side, fetches `__data.json` again and loops into its 20-redirect error. So the\n * redirect points at camada's bounce path (`<scriptPath dir>/reload?to=<page>`). No app route\n * matches it, so the client falls back to a native navigation (`location.href`), and the bounce\n * then 302s that document request to the page, which gets the HTML challenge. Every other answer,\n * and any request that is not a `__data.json` fetch (an API call from app code), is left as it is.\n */\nfunction forNavigation(req: Request, res: Response, bouncePath: string): Response {\n return guarded(() => {\n if (res.status !== 403 || res.headers.get('x-camada-challenge') !== '1') return res;\n const page = dataRequestPage(new URL(req.url));\n if (page === null) return res;\n void res.body?.cancel().catch(() => {});\n return new Response(JSON.stringify({ type: 'redirect', location: `${bouncePath}?to=${encodeURIComponent(page)}` }), {\n status: 200, headers: { 'content-type': 'application/json', 'cache-control': 'no-store', 'x-camada-challenge': '1' },\n });\n }, res);\n}\n\n/**\n * The `handle` for `src/hooks.server.ts`: `export const handle = camada()`, or first in a\n * `sequence()`. Reads `CAMADA_KEY` (+ `CAMADA_INGEST_URL` / `CAMADA_SNAPSHOT_URL`) from\n * `process.env` or the Cloudflare bindings; inert without a key. One pipeline per call.\n */\nexport function camada(opts: CamadaSvelteKitOptions = {}): Handle {\n const cam = createFetchCamada({ tap: TAP_SVELTEKIT, sdk: SDK_ID, iife }, opts); // mode defaults to lazy in core: the handle may run on edge/serverless\n instances.add(cam);\n const bouncePath = (opts.scriptPath ?? '/_cam/b.js').replace(/[^/]*$/, 'reload'); // beside the beacon: camada's own directory\n return async ({ event, resolve }) => {\n // A server-side `event.fetch` to the app's own routes runs the handle again on a fresh event\n // with no user-agent or accept: one page view is one event, and the page-level verdict must\n // not be re-applied to the server's own load().\n if (event.isSubRequest) return resolve(event);\n const ctx = contextOf(event);\n // The bounce a challenged data request sends the client through (see forNavigation): a\n // same-site 302 back to the page, which then meets the verdict as a document request. Only\n // while camada is active (core's own inert test: a key, and no CAMADA_DISABLED=1); otherwise\n // the path is the app's.\n const bounce = guarded(() => {\n const url = new URL(event.request.url);\n if (event.request.method !== 'GET' || url.pathname !== bouncePath) return null;\n if (!cam.active(ctx)) return null;\n return new Response(null, { status: 302, headers: { location: safeReturnTo(url.searchParams.get('to')), 'cache-control': 'no-store' } });\n }, null);\n if (bounce) return bounce;\n const r = await cam.before(event.request, ctx);\n if (!r) return resolve(event);\n if (r.response) return forNavigation(event.request, r.response, bouncePath);\n const vars = r.vars;\n guarded(() => { (event.locals as Record<string, unknown>)[VAR] = vars; }, undefined);\n // The _sfp cookie rides the response, not `event.cookies.set`: SvelteKit appends those in place\n // after resolve(), which throws on an immutable Response (a `fetch()` result, `Response.redirect()`).\n // withSetCookie copies such a response; the hook sees it, so the 500 never happens.\n const res = await resolve(event);\n // SvelteKit swaps a 200 carrying an etag for a bodiless 304 after this hook when the client's\n // If-None-Match matches, dropping the body unread: waiting on it would never ship, so an\n // etagged response ships now even when it is an event stream. (That 304 is built by SvelteKit\n // from a fixed header allow-list, so it carries no x-rid; the hook cannot change that.)\n const out = guarded(() => (vars.sessionCookie ? withSetCookie(res, vars.sessionCookie) : res), res);\n if (out.headers.has('etag')) { cam.after(event.request, vars, out.status); return guarded(() => withRid(out, vars), out); }\n return cam.finish(event.request, vars, out); // an SSE body ships once it has gone out; anything else ships now, untouched\n };\n}\n\n/**\n * Records an outcome the app knows and the wire cannot show (`login_failed`, `signup`,\n * `payment_failed`, ...), joined to this request's event by rid and session. The user identifier\n * is HMAC-hashed in-process. Never throws; a no-op where the handle did not run for this event.\n */\nexport const track = (event: RequestEvent, et: string, data?: { user?: string }): Promise<void> => coreTrack(slotOf(event), et, data);\n\n/** The beacon `<script>` tag for this request's page; `''` where the handle did not run or the tenant turned the beacon off. */\nexport const scriptTag = (event: RequestEvent): string => coreScriptTag(slotOf(event));\n\n/** Test/reset hook: stops and drops every engine of every handle this module created. */\nexport function resetCamada(): void {\n for (const cam of instances) cam.reset();\n}\n","{\n \"name\": \"@camada/sveltekit\",\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 SvelteKit (hooks.server.ts handle): snapshot enforcement, first-party challenge and beacon, track() outcomes, waitUntil-flushed event shipping on Cloudflare\",\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 \"@sveltejs/kit\": \">=2\"\n },\n \"devDependencies\": {\n \"@sveltejs/kit\": \"^2.0.0\",\n \"@types/node\": \"^22.10.0\",\n \"svelte\": \"^5.0.0\",\n \"tsup\": \"^8.3.5\",\n \"typescript\": \"^5.7.2\",\n \"vite\": \"^8.0.0\",\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"],"mappings":";AAKA,SAAS,SAAS,cAAc,qBAAqB;AACrD,SAAS,mBAAmB,SAAS,eAAe,SAAS,WAAW,aAAa,qBAA0G;AAC/L,OAAO,UAAU;;;ACNf,WAAQ;AACR,cAAW;;;ACCN,IAAM,SAAS,GAAG,IAAI,IAAI,OAAO;;;AFUxC,IAAM,MAAM;AACZ,IAAM,YAAY,oBAAI,IAAiB;AAWvC,IAAM,SAAS,CAAC,UACd,QAAQ,MAAO,MAAM,OAAmC,GAAG,GAA4B,MAAS;AAGlG,SAAS,UAAU,OAA0C;AAC3D,QAAM,WAAW,MAAM;AAGvB,QAAM,MAAM,QAAa,OAAO,EAAE,GAAI,WAA2C,SAAS,KAAK,GAAG,UAAU,IAAI,IAAI,CAAC,CAAC;AACtH,MAAI,OAAsB;AAC1B,MAAI;AAAE,WAAO,MAAM,iBAAiB;AAAA,EAAG,QAAQ;AAAA,EAAwE;AACvH,QAAM,OAAO,UAAU;AACvB,QAAM,KAAK,UAAU;AACrB,SAAO;AAAA,IACL;AAAA,IAAM;AAAA,IACN,WAAW,OAAO,CAAC,MAAM,KAAK,UAAU,CAAC,IAAI;AAAA,IAC7C,KAAK,IAAI,OAAO;AAAA,IAChB,SAAS,IAAI,WAAW;AAAA,IACxB,MAAM,IAAI,2BAA2B;AAAA;AAAA,IAErC,aAAa,IAAI,eAAe,GAAG,aAAa,QAAQ,YAAY,EAAE,IAAI;AAAA,EAC5E;AACF;AAEA,IAAM,cAAc;AACpB,IAAM,mBAAmB;AAIzB,SAAS,gBAAgB,KAAyB;AAChD,MAAI;AACJ,MAAI,IAAI,SAAS,SAAS,gBAAgB,EAAG,QAAO,IAAI,SAAS,MAAM,GAAG,CAAC,iBAAiB,MAAM,IAAI;AAAA,WAC7F,IAAI,SAAS,SAAS,WAAW,EAAG,QAAO,IAAI,SAAS,MAAM,GAAG,CAAC,YAAY,MAAM,KAAK;AAAA,MAC7F,QAAO;AACZ,QAAM,IAAI,IAAI,gBAAgB,IAAI,MAAM;AACxC,MAAI,EAAE,IAAI,4BAA4B,MAAM,OAAO,CAAC,KAAK,SAAS,GAAG,EAAG,SAAQ;AAChF,IAAE,OAAO,4BAA4B;AACrC,IAAE,OAAO,yBAAyB;AAClC,QAAM,SAAS,EAAE,SAAS;AAC1B,SAAO,QAAQ,SAAS,MAAM,SAAS;AACzC;AAYA,SAAS,cAAc,KAAc,KAAe,YAA8B;AAChF,SAAO,QAAQ,MAAM;AACnB,QAAI,IAAI,WAAW,OAAO,IAAI,QAAQ,IAAI,oBAAoB,MAAM,IAAK,QAAO;AAChF,UAAM,OAAO,gBAAgB,IAAI,IAAI,IAAI,GAAG,CAAC;AAC7C,QAAI,SAAS,KAAM,QAAO;AAC1B,SAAK,IAAI,MAAM,OAAO,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AACtC,WAAO,IAAI,SAAS,KAAK,UAAU,EAAE,MAAM,YAAY,UAAU,GAAG,UAAU,OAAO,mBAAmB,IAAI,CAAC,GAAG,CAAC,GAAG;AAAA,MAClH,QAAQ;AAAA,MAAK,SAAS,EAAE,gBAAgB,oBAAoB,iBAAiB,YAAY,sBAAsB,IAAI;AAAA,IACrH,CAAC;AAAA,EACH,GAAG,GAAG;AACR;AAOO,SAAS,OAAO,OAA+B,CAAC,GAAW;AAChE,QAAM,MAAM,kBAAkB,EAAE,KAAK,eAAe,KAAK,QAAQ,KAAK,GAAG,IAAI;AAC7E,YAAU,IAAI,GAAG;AACjB,QAAM,cAAc,KAAK,cAAc,cAAc,QAAQ,UAAU,QAAQ;AAC/E,SAAO,OAAO,EAAE,OAAO,QAAQ,MAAM;AAInC,QAAI,MAAM,aAAc,QAAO,QAAQ,KAAK;AAC5C,UAAM,MAAM,UAAU,KAAK;AAK3B,UAAM,SAAS,QAAQ,MAAM;AAC3B,YAAM,MAAM,IAAI,IAAI,MAAM,QAAQ,GAAG;AACrC,UAAI,MAAM,QAAQ,WAAW,SAAS,IAAI,aAAa,WAAY,QAAO;AAC1E,UAAI,CAAC,IAAI,OAAO,GAAG,EAAG,QAAO;AAC7B,aAAO,IAAI,SAAS,MAAM,EAAE,QAAQ,KAAK,SAAS,EAAE,UAAU,aAAa,IAAI,aAAa,IAAI,IAAI,CAAC,GAAG,iBAAiB,WAAW,EAAE,CAAC;AAAA,IACzI,GAAG,IAAI;AACP,QAAI,OAAQ,QAAO;AACnB,UAAM,IAAI,MAAM,IAAI,OAAO,MAAM,SAAS,GAAG;AAC7C,QAAI,CAAC,EAAG,QAAO,QAAQ,KAAK;AAC5B,QAAI,EAAE,SAAU,QAAO,cAAc,MAAM,SAAS,EAAE,UAAU,UAAU;AAC1E,UAAM,OAAO,EAAE;AACf,YAAQ,MAAM;AAAE,MAAC,MAAM,OAAmC,GAAG,IAAI;AAAA,IAAM,GAAG,MAAS;AAInF,UAAM,MAAM,MAAM,QAAQ,KAAK;AAK/B,UAAM,MAAM,QAAQ,MAAO,KAAK,gBAAgB,cAAc,KAAK,KAAK,aAAa,IAAI,KAAM,GAAG;AAClG,QAAI,IAAI,QAAQ,IAAI,MAAM,GAAG;AAAE,UAAI,MAAM,MAAM,SAAS,MAAM,IAAI,MAAM;AAAG,aAAO,QAAQ,MAAM,QAAQ,KAAK,IAAI,GAAG,GAAG;AAAA,IAAG;AAC1H,WAAO,IAAI,OAAO,MAAM,SAAS,MAAM,GAAG;AAAA,EAC5C;AACF;AAOO,IAAM,QAAQ,CAAC,OAAqB,IAAY,SAA4C,UAAU,OAAO,KAAK,GAAG,IAAI,IAAI;AAG7H,IAAM,YAAY,CAAC,UAAgC,cAAc,OAAO,KAAK,CAAC;AAG9E,SAAS,cAAoB;AAClC,aAAW,OAAO,UAAW,KAAI,MAAM;AACzC;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camada/sveltekit",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -14,17 +14,22 @@
|
|
|
14
14
|
"description": "camada SDK for SvelteKit (hooks.server.ts handle): snapshot enforcement, first-party challenge and beacon, track() outcomes, waitUntil-flushed event shipping on Cloudflare",
|
|
15
15
|
"exports": {
|
|
16
16
|
".": {
|
|
17
|
-
"
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
"import": {
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"default": "./dist/index.js"
|
|
20
|
+
},
|
|
21
|
+
"require": {
|
|
22
|
+
"types": "./dist/index.d.cts",
|
|
23
|
+
"default": "./dist/index.cjs"
|
|
24
|
+
}
|
|
20
25
|
}
|
|
21
26
|
},
|
|
22
27
|
"main": "./dist/index.cjs",
|
|
23
28
|
"module": "./dist/index.js",
|
|
24
29
|
"types": "./dist/index.d.ts",
|
|
25
30
|
"dependencies": {
|
|
26
|
-
"@camada/core": "^0.5.
|
|
27
|
-
"@camada/browser": "^0.3.
|
|
31
|
+
"@camada/core": "^0.5.2",
|
|
32
|
+
"@camada/browser": "^0.3.2"
|
|
28
33
|
},
|
|
29
34
|
"peerDependencies": {
|
|
30
35
|
"@sveltejs/kit": ">=2"
|