{"_id":"@capsium/swsws","_rev":"2-829cdeccfb52e18b9718adeb8bb09829","name":"@capsium/swsws","dist-tags":{"latest":"0.2.1"},"versions":{"0.2.0":{"name":"@capsium/swsws","version":"0.2.0","license":"MIT","_id":"@capsium/swsws@0.2.0","maintainers":[{"name":"ronaldtse","email":"ronald.tse@ribose.com"}],"homepage":"https://github.com/capsiums/capsium-js#readme","bugs":{"url":"https://github.com/capsiums/capsium-js/issues"},"dist":{"shasum":"0f73a383776665157c98c90f7d142284bfae145a","tarball":"https://registry.npmjs.org/@capsium/swsws/-/swsws-0.2.0.tgz","fileCount":7,"integrity":"sha512-/HIbMAaGYalbUyY+lhwz1xUAJ5x+RjgUpv43VeR0nUq9mDmmZcIs84RriL15GfJW/v2rfcyeE+qDg5oFyv2uVw==","signatures":[{"sig":"MEYCIQDZTOKfa2uJ5dfIO0eVPUKVUKAUO6p7gmMdioo3TeS1MAIhANofmRwqCfnsqEA1vRggPeEFzSeFAcJD8AyGSLTih7c9","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":2395629},"type":"module","engines":{"node":">=20"},"exports":{".":{"types":"./src/index.ts","import":"./src/index.ts"}},"gitHead":"98ba558a28e591407103c166230b5eee1448d640","scripts":{"lint":"eslint .","test":"vitest run","build":"tsup","typecheck":"tsc --noEmit"},"_npmUser":{"name":"ronaldtse","email":"ronald.tse@ribose.com"},"repository":{"url":"git+https://github.com/capsiums/capsium-js.git","type":"git","directory":"packages/swsws"},"_npmVersion":"11.9.0","description":"Service worker static website server: browser Capsium reactor serving .cap packages per routes.json (layered storage, composite packages, JS handler routes, §4b auth, §7 introspection).","directories":{},"_nodeVersion":"24.14.0","dependencies":{"fflate":"^0.8.3","bcryptjs":"^3.0.3","@capsium/core":"workspace:^","@types/bcryptjs":"^3.0.0"},"publishConfig":{"access":"public","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}}},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/swsws_0.2.0_1784651527991_0.37781492368659264","host":"s3://npm-registry-packages-npm-production"}},"0.2.1":{"name":"@capsium/swsws","version":"0.2.1","description":"Service worker static website server: browser Capsium reactor serving .cap packages per routes.json (layered storage, composite packages, JS handler routes, §4b auth, §7 introspection).","license":"MIT","type":"module","engines":{"node":">=20"},"repository":{"type":"git","url":"git+https://github.com/capsiums/capsium-js.git","directory":"packages/swsws"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"publishConfig":{"access":"public","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}}},"scripts":{"build":"tsup","test":"vitest run","lint":"eslint .","typecheck":"tsc --noEmit"},"dependencies":{"@capsium/core":"^0.2.1","@types/bcryptjs":"^3.0.0","bcryptjs":"^3.0.3","fflate":"^0.8.3"},"_id":"@capsium/swsws@0.2.1","bugs":{"url":"https://github.com/capsiums/capsium-js/issues"},"homepage":"https://github.com/capsiums/capsium-js#readme","_integrity":"sha512-oOS59XjmomnbI4Coxg53Fu5AuymDOc0vjAwUzKBEK4x0UsApYo7zNEjVuFsNq9jGRDqiZiNCwsK6fEsI20wYEQ==","_resolved":"/home/runner/work/capsium-js/capsium-js/packages/swsws/package.tgz","_from":"file:package.tgz","_nodeVersion":"24.18.0","_npmVersion":"11.16.0","dist":{"integrity":"sha512-oOS59XjmomnbI4Coxg53Fu5AuymDOc0vjAwUzKBEK4x0UsApYo7zNEjVuFsNq9jGRDqiZiNCwsK6fEsI20wYEQ==","shasum":"c744586733254a5543ed82092cd48c6194d47919","tarball":"https://registry.npmjs.org/@capsium/swsws/-/swsws-0.2.1.tgz","fileCount":7,"unpackedSize":2395627,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@capsium%2fswsws@0.2.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCICMXSPKoKywVlddqaoOrytHNV6PT5sGSbIGNNA+ho4+IAiEA3EtUzqQRXKLrqNIq4igQgxE1dY6KaiRbwSue+eLAuR8="}]},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:9e798cf1-de44-4dac-97e2-448276bb3bd6"}},"directories":{},"maintainers":[{"name":"ronaldtse","email":"ronald.tse@ribose.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/swsws_0.2.1_1784652258993_0.7440939094380414"},"_hasShrinkwrap":false}},"time":{"created":"2026-07-21T16:32:07.711Z","modified":"2026-07-21T16:44:19.560Z","0.2.0":"2026-07-21T16:32:08.158Z","0.2.1":"2026-07-21T16:44:19.192Z"},"bugs":{"url":"https://github.com/capsiums/capsium-js/issues"},"license":"MIT","homepage":"https://github.com/capsiums/capsium-js#readme","repository":{"type":"git","url":"git+https://github.com/capsiums/capsium-js.git","directory":"packages/swsws"},"description":"Service worker static website server: browser Capsium reactor serving .cap packages per routes.json (layered storage, composite packages, JS handler routes, §4b auth, §7 introspection).","maintainers":[{"name":"ronaldtse","email":"ronald.tse@ribose.com"}],"readme":"# swsws — service worker static website server\n\nThe browser Capsium reactor (`@capsium/swsws`): a dependency-light, hand-rolled\nservice worker that serves `.cap` packages straight from the zip.\n\n> **Status: 0.2.0** — published to npm under the `@capsium` org (see the\n> repo-root [release docs](../../README.md#releasing)).\n\n## Install\n\n```sh\nnpm install @capsium/swsws\n```\n\nThe published package ships the reactor building blocks as an ESM library\n(`dist/index.js`: `handleRequest`, `PackageStore`, scope helpers, WebCrypto\nproviders) and the self-contained service-worker script as `dist/sw.js`\n(IIFE, all deps bundled — register or copy it as-is).\n\n- Accepts a `.cap` blob from the page via `postMessage`, verifies SHA-256\n  checksums against `security.json` (WebCrypto) and rejects tampered packages.\n- Persists the verified blob in the Cache API and serves requests per\n  `routes.json` (auto-generated routes included), unpacking entries with fflate.\n- Answers the reactor introspection API under `/api/v1/introspect/`\n  (`metadata`, `routes`, `content-hashes`, `content-validity`).\n- Verifies RSA-SHA256 digital signatures (§6a, WebCrypto\n  RSASSA-PKCS1-v1_5) when `security.json` declares them and rejects\n  packages whose signature does not verify.\n- Executes JS handler routes (§4a) as ES modules — see below.\n- Serves §5a layered storage (top → bottom, `.capsium-tombstones` → 404).\n- Serves §4a composite packages (dependency resources + route inheritance).\n- Gates routes with §4b authentication (basicAuth / OAuth2 PKCE).\n\n## Handler routes (§4a)\n\nRoutes declared as `{ \"path\": \"/api/v1/echo\", \"method\": \"POST\", \"handler\":\n\"content/handlers/echo.js\" }` execute in the service worker: the handler\nsource is read from the package, imported as an ES module through a blob:\nURL, and its default export (or named `fetch` export) is called with the\nfetch-style `Request`; it must produce the `Response`:\n\n```js\nexport default async (request) => new Response('echo');\n```\n\n- The route matches on both path and method; other methods get\n  `405 Method Not Allowed` (with an `Allow` header).\n- Sync and async handlers are supported; GET/POST/PUT/DELETE etc. all work\n  (the original `Request`, including its body, is passed through).\n- Error mapping: missing module / import failure / no callable entry /\n  non-Response return → `502` with a clear body; a handler exception →\n  `500`.\n- Non-JS handlers (e.g. `.lua`) are **not** executed: this reactor answers\n  `501` (§4a: JS handlers execute only in JS-capable reactors).\n- Modules are cached per installed package (ES module semantics).\n\n### Sandboxing caveats\n\nA service worker has **no real sandbox**: handler code runs with the\nworker's full privileges — same-origin `fetch`, Cache API, the installed\npackage bytes. Only install `.cap` packages from sources you trust, prefer\nsigned packages (§6a), and deploy a Content Security Policy on the page,\ne.g. `script-src 'self' blob:; connect-src 'self'`, to constrain module\nloading and exfiltration. Handler modules are imported from in-memory\nblob: URLs only — never from the network.\n\n## Layered storage (§5a)\n\nPackages with `storage.layers` serve a merged view: layers are\npackage-relative directories mirroring the package tree, resolved **top →\nbottom** (first hit wins); packages without a `layers` config behave as a\nsingle implicit root layer. Deletions recorded in a layer's\n`.capsium-tombstones` file (JSON array of merged-view paths) answer `404`\neven when a lower layer still has the file. `visibility: private` layers\nare served to the package itself but never exposed to dependent packages.\n\n## Composite packages (§4a)\n\nA package with `metadata.dependencies` (guid → semver range) can inherit\ndependency content. The browser reactor has no store directory, so the\ndependency `.cap` blobs are **supplied explicitly alongside the main\npackage** (e.g. the page's multi-file picker posts them with the install\nmessage); they are verified and persisted like the main package.\n\n- Resource routes may reference dependencies:\n  `{ \"path\": \"/vendor/app.js\", \"resource\": \"capsium://<guid>/content/app.js\" }`.\n  Only `exported` manifest resources are visible — referencing a\n  dependency's `private` resource is rejected with a clear 404.\n- Route inheritance attributes are honored at serve time: `remap`\n  (the route is served at the remapped path), `responseRewrite`\n  (`body` replacement, `headers` override), `responseHeaders`\n  (merged over the served headers), and `requestHeaders`\n  (supplanted before forwarding to an inherited handler).\n\n## Authentication (§4b)\n\n`authentication.json` gates package routes (the introspection API stays\nopen):\n\n- **basicAuth** — `401` + `WWW-Authenticate` challenge; the package's\n  htpasswd file is verified in pure TS. Supported hash types: **bcrypt**\n  (`$2a$`/`$2b$`/`$2y$`) and **apr1-MD5** (`$apr1$`). Anything else\n  (sha-crypt, plaintext, ...) answers `501` with a precise body, as does\n  a missing `passwdFile`.\n- **oauth2** — browser-native authorization-code + **PKCE** (S256) flow.\n  Provider config (clientId, authorization/token/userinfo URLs,\n  `redirectPath`, scopes) comes from the package; the session-cookie\n  signing secret comes from a **deploy-time config message** (`{type:\n  'deploy-config', config: {sessionSecret}}`) — never from the package.\n  OAuth2 without the deploy secret answers `501`. Sessions are\n  HMAC-SHA256-signed cookies; PKCE pending state lives in memory, so a\n  worker restart mid-login restarts the flow.\n- Route-level `accessControl` on dataset routes is enforced after\n  authentication (`401` unauthenticated, `403` unauthorized). htpasswd\n  has no roles, so basic-auth principals are role-less; roles come from\n  the OAuth2 userinfo profile.\n\n## Files\n\n- `src/sw.ts` — service worker entry (built to `dist/sw.js` as an IIFE).\n- `src/resolver.ts` — pure routes.json path+method resolution (unit-tested).\n- `src/fetch-handler.ts` — request pipeline (unit-tested with a mocked store).\n- `src/handler-executor.ts` — §4a ES-module handler execution.\n- `src/package-store.ts` — verification + Cache-API persistence (incl.\n  composite dependencies).\n- `src/auth/` — §4b: htpasswd (pure-TS MD5/apr1 + bcryptjs), PKCE flow,\n  signed session cookies, the authentication gate.\n- `index.html` — demo page with a `.cap` file picker.\n\n## Try it\n\n```sh\ncorepack yarn build          # produces dist/sw.js\nnpx serve packages/swsws     # any static server; serve the package dir\n```\n\nOpen the page, pick a `.cap` (build one with `@capsium/packager`), then\nbrowse `/`.\n","readmeFilename":"README.md"}