tempest-react-sdk 0.67.0 → 0.68.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 +97 -93
- package/dist/auth/create-tempest-auth.cjs +1 -1
- package/dist/auth/create-tempest-auth.cjs.map +1 -1
- package/dist/auth/create-tempest-auth.js +41 -40
- package/dist/auth/create-tempest-auth.js.map +1 -1
- package/dist/components/BottomSheet/BottomSheet.cjs +1 -1
- package/dist/components/BottomSheet/BottomSheet.cjs.map +1 -1
- package/dist/components/BottomSheet/BottomSheet.js +34 -30
- package/dist/components/BottomSheet/BottomSheet.js.map +1 -1
- package/dist/components/Card/Card.module.cjs.map +1 -1
- package/dist/components/Card/Card.module.js.map +1 -1
- package/dist/components/Combobox/Combobox.cjs +1 -1
- package/dist/components/Combobox/Combobox.cjs.map +1 -1
- package/dist/components/Combobox/Combobox.js +78 -77
- package/dist/components/Combobox/Combobox.js.map +1 -1
- package/dist/components/Combobox/Combobox.module.cjs.map +1 -1
- package/dist/components/Combobox/Combobox.module.js.map +1 -1
- package/dist/components/Command/Command.cjs +1 -1
- package/dist/components/Command/Command.js +4 -4
- package/dist/components/DataTable/DataTable.cjs +1 -1
- package/dist/components/DataTable/DataTable.cjs.map +1 -1
- package/dist/components/DataTable/DataTable.js +138 -127
- package/dist/components/DataTable/DataTable.js.map +1 -1
- package/dist/components/DataTable/use-dev-warnings.cjs +1 -1
- package/dist/components/DataTable/use-dev-warnings.cjs.map +1 -1
- package/dist/components/DataTable/use-dev-warnings.js +5 -3
- package/dist/components/DataTable/use-dev-warnings.js.map +1 -1
- package/dist/components/Drawer/Drawer.cjs +1 -1
- package/dist/components/Drawer/Drawer.cjs.map +1 -1
- package/dist/components/Drawer/Drawer.js +43 -40
- package/dist/components/Drawer/Drawer.js.map +1 -1
- package/dist/components/FilterPanel/FilterPanel.cjs +2 -0
- package/dist/components/FilterPanel/FilterPanel.cjs.map +1 -0
- package/dist/components/FilterPanel/FilterPanel.js +75 -0
- package/dist/components/FilterPanel/FilterPanel.js.map +1 -0
- package/dist/components/FilterPanel/FilterPanel.module.cjs +3 -0
- package/dist/components/FilterPanel/FilterPanel.module.cjs.map +1 -0
- package/dist/components/FilterPanel/FilterPanel.module.js +12 -0
- package/dist/components/FilterPanel/FilterPanel.module.js.map +1 -0
- package/dist/components/Lightbox/Lightbox.cjs +1 -1
- package/dist/components/Lightbox/Lightbox.js +5 -5
- package/dist/components/Modal/Modal.cjs +1 -1
- package/dist/components/Modal/Modal.cjs.map +1 -1
- package/dist/components/Modal/Modal.js +44 -41
- package/dist/components/Modal/Modal.js.map +1 -1
- package/dist/components/Pagination/Pagination.cjs +1 -1
- package/dist/components/Pagination/Pagination.cjs.map +1 -1
- package/dist/components/Pagination/Pagination.js +31 -30
- package/dist/components/Pagination/Pagination.js.map +1 -1
- package/dist/components/Portal/tab-bridge.cjs +1 -1
- package/dist/components/Portal/tab-bridge.cjs.map +1 -1
- package/dist/components/Portal/tab-bridge.js +21 -23
- package/dist/components/Portal/tab-bridge.js.map +1 -1
- package/dist/components/Select/CaretIcon.cjs +2 -0
- package/dist/components/Select/CaretIcon.cjs.map +1 -0
- package/dist/components/Select/CaretIcon.js +21 -0
- package/dist/components/Select/CaretIcon.js.map +1 -0
- package/dist/components/Select/Select.cjs +1 -1
- package/dist/components/Select/Select.cjs.map +1 -1
- package/dist/components/Select/Select.js +33 -47
- package/dist/components/Select/Select.js.map +1 -1
- package/dist/components/Select/Select.module.cjs +1 -1
- package/dist/components/Select/Select.module.cjs.map +1 -1
- package/dist/components/Select/Select.module.js +4 -3
- package/dist/components/Select/Select.module.js.map +1 -1
- package/dist/components/Select/options.cjs +2 -0
- package/dist/components/Select/options.cjs.map +1 -0
- package/dist/components/Select/options.js +25 -0
- package/dist/components/Select/options.js.map +1 -0
- package/dist/components/Sidebar/Sidebar.cjs +1 -1
- package/dist/components/Sidebar/Sidebar.cjs.map +1 -1
- package/dist/components/Sidebar/Sidebar.js +49 -43
- package/dist/components/Sidebar/Sidebar.js.map +1 -1
- package/dist/components/Sidebar/active-nav-key.cjs +2 -0
- package/dist/components/Sidebar/active-nav-key.cjs.map +1 -0
- package/dist/components/Sidebar/active-nav-key.js +22 -0
- package/dist/components/Sidebar/active-nav-key.js.map +1 -0
- package/dist/hooks/tab-sequence.cjs +2 -0
- package/dist/hooks/tab-sequence.cjs.map +1 -0
- package/dist/hooks/tab-sequence.js +53 -0
- package/dist/hooks/tab-sequence.js.map +1 -0
- package/dist/hooks/use-deep-memo.cjs +1 -1
- package/dist/hooks/use-deep-memo.cjs.map +1 -1
- package/dist/hooks/use-deep-memo.js +1 -1
- package/dist/hooks/use-deep-memo.js.map +1 -1
- package/dist/hooks/use-draft-filters.cjs +2 -0
- package/dist/hooks/use-draft-filters.cjs.map +1 -0
- package/dist/hooks/use-draft-filters.js +49 -0
- package/dist/hooks/use-draft-filters.js.map +1 -0
- package/dist/hooks/use-focus-trap.cjs +1 -1
- package/dist/hooks/use-focus-trap.cjs.map +1 -1
- package/dist/hooks/use-focus-trap.js +31 -34
- package/dist/hooks/use-focus-trap.js.map +1 -1
- package/dist/http/api-client.cjs +1 -1
- package/dist/http/api-client.cjs.map +1 -1
- package/dist/http/api-client.js +92 -80
- package/dist/http/api-client.js.map +1 -1
- package/dist/http/credential-scope.cjs +1 -1
- package/dist/http/credential-scope.cjs.map +1 -1
- package/dist/http/credential-scope.js +5 -3
- package/dist/http/credential-scope.js.map +1 -1
- package/dist/http/csrf.cjs +2 -0
- package/dist/http/csrf.cjs.map +1 -0
- package/dist/http/csrf.js +46 -0
- package/dist/http/csrf.js.map +1 -0
- package/dist/http/resumable-upload.cjs +1 -1
- package/dist/http/resumable-upload.cjs.map +1 -1
- package/dist/http/resumable-upload.js +131 -123
- package/dist/http/resumable-upload.js.map +1 -1
- package/dist/http/upload-with-progress.cjs +1 -1
- package/dist/http/upload-with-progress.cjs.map +1 -1
- package/dist/http/upload-with-progress.js +41 -33
- package/dist/http/upload-with-progress.js.map +1 -1
- package/dist/query/use-paginated-query.cjs +1 -1
- package/dist/query/use-paginated-query.cjs.map +1 -1
- package/dist/query/use-paginated-query.js +47 -30
- package/dist/query/use-paginated-query.js.map +1 -1
- package/dist/styles/FilterPanel.css +2 -0
- package/dist/styles/component/Card.css +1 -1
- package/dist/styles/component/Combobox.css +4 -3
- package/dist/styles/component/FilterPanel.css +5 -0
- package/dist/styles/component/Select.css +13 -6
- package/dist/styles/core.css +4 -4
- package/dist/styles/data.css +4 -0
- package/dist/styles/forms.css +17 -9
- package/dist/styles/identity.css +1 -1
- package/dist/styles/manifest.json +6 -0
- package/dist/styles/tokens.css +4 -4
- package/dist/styles.css +1 -1
- package/dist/tempest-react-sdk.cjs +1 -1
- package/dist/tempest-react-sdk.d.ts +658 -22
- package/dist/tempest-react-sdk.js +347 -342
- package/dist/vite/tempest-csp.cjs +3 -0
- package/dist/vite/tempest-csp.cjs.map +1 -0
- package/dist/vite/tempest-csp.js +189 -0
- package/dist/vite/tempest-csp.js.map +1 -0
- package/dist/vite.cjs +1 -1
- package/dist/vite.d.ts +145 -0
- package/dist/vite.js +2 -1
- package/package.json +2 -2
- package/template/vite.config.ts +8 -1
- package/template-pwa/vite.config.ts +6 -0
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
let e=require("node:crypto");var t=new Set([`frame-ancestors`,`report-uri`,`sandbox`]),n=[{module:/[\\/]forms[\\/]use-viacep\.(?:[cm]?js|tsx?)$/,origin:`https://viacep.com.br`,from:`tempest-react-sdk useViaCEP`}],r=/^(?:https?|wss?):\/\//i,i=/^((?:https?|wss?):\/\/\*\.[a-z0-9.-]+(?::\d+)?)(?:[/?#].*)?$/i;function a(e,t){let n=e.trim();if(n.startsWith(`/`)&&!n.startsWith(`//`))return;let a=i.exec(n);if(a)return a[1].toLowerCase();if(r.test(n))try{let e=new URL(n);return`${e.protocol}//${e.host}`}catch{throw Error(`tempestCsp: ${t} is not a valid URL: "${e}"`)}throw Error(`tempestCsp: ${t} must be an absolute http(s)/ws(s) URL or a root-relative path, got "${e}"`)}function o(e){let t={};for(let n of e.split(`;`)){let[e,...r]=n.trim().split(/\s+/);if(!e)continue;let i=e.toLowerCase();i in t||(t[i]=r)}return t}function s(e,n){return Object.entries(e).filter(([e])=>n===`header`||!t.has(e)).map(([e,t])=>[e,...t].join(` `)).join(`; `)}function c(e,t){let n=e.filter(e=>e!==`'none'`||t.length===0);for(let e of t)n.includes(e)||n.push(e);return n.length===0?[`'none'`]:n}function l(t){return`'sha256-${(0,e.createHash)(`sha256`).update(t,`utf8`).digest(`base64`)}'`}function u(e){return e.replace(/'|'|'/gi,`'`).replace(/"/gi,`"`).replace(/&/gi,`&`)}function d(e){let t={};for(let n of e.matchAll(/([^\s=<>/]+)\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]+))/g))t[n[1].toLowerCase()]=u(n[2]??n[3]??n[4]??``);return t}function f(e){let t=[...e.matchAll(/<meta\b[^>]*>/gi)].filter(e=>d(e[0])[`http-equiv`]?.toLowerCase()===`content-security-policy`);if(t.length===0)return{html:e};if(t.length>1)throw Error(`tempestCsp: index.html declares more than one Content-Security-Policy <meta>; merge them into one`);let n=t[0];return{html:e.slice(0,n.index)+e.slice(n.index+n[0].length).replace(/^[ \t]*\r?\n/,``),declared:o(d(n[0]).content??``)}}function p(e,t){let n=RegExp(`<${t}\\b([^>]*)>([\\s\\S]*?)</${t}>`,`gi`),r=[];for(let i of e.matchAll(n)){let e=d(`<x ${i[1]}>`);if(t===`script`&&`src`in e||i[2].length===0)continue;let n=l(i[2]);r.includes(n)||r.push(n)}return r}function m(e,t){let n=`<meta http-equiv="Content-Security-Policy" content="${t.replace(/&/g,`&`).replace(/"/g,`"`)}" />`,r=/<meta\b[^>]*\bcharset\s*=[^>]*>/i.exec(e),i=/<head\b[^>]*>/i.exec(e),a=r!==null&&i!==null&&/<(?:script|link|style)\b/i.test(e.slice(i.index,r.index)),o=r&&i&&r.index>i.index&&!a?r:i;if(!o)return`${n}\n${e}`;let s=o.index+o[0].length;return`${e.slice(0,s)}\n ${n}${e.slice(s)}`}function h(e){let t=[];for(let n of Object.values(e.bundle??{}))n.type===`chunk`&&t.push(...n.moduleIds??Object.keys(n.modules??{}));return t}function g(e={}){let{connect:t=[],env:i=!0,sockets:o=!0,wasm:l=!0,inlineStyles:u=!0,directives:d={},dev:g=!0,log:_=!0,onPolicy:v}=e,y,b=[],x=``;function S(e,t,n){t===void 0||e.some(e=>e.origin===t)||e.push({origin:t,from:n})}function C(e){let n=[],o=e.env??{},s=[e.envPrefix??`VITE_`].flat();if(i===!0)for(let[e,t]of Object.entries(o))s.some(t=>e.startsWith(t))&&typeof t==`string`&&r.test(t.trim())&&S(n,a(t,`env ${e}`),`env ${e}`);else if(Array.isArray(i))for(let t of i){let r=o[t];if(typeof r!=`string`||r.trim()===``){e.logger.warn(`tempestCsp: env ${t} is empty — nothing added to connect-src`);continue}S(n,a(r,`env ${t}`),`env ${t}`)}return t.forEach((t,r)=>{if(!t||t.trim()===``){e.logger.warn(`tempestCsp: connect[${r}] is empty — skipped, nothing added to connect-src`);return}S(n,a(t,`connect[${r}]`),`connect[${r}]`)}),n}function w(e){let t=[...e];if(!o)return t;for(let{origin:n}of e){let e=n.replace(/^http(s?):/i,`ws$1:`);e!==n&&S(t,e,`sockets (${n})`)}return t}function T(e,t){let n=e?[`'self'`,e]:[`'self'`],r={"default-src":[...n],"script-src":l?[...n,`'wasm-unsafe-eval'`]:[...n],"style-src":u||t?[...n,`'unsafe-inline'`]:[...n],"img-src":[...n,`data:`,`blob:`],"font-src":[...n,`data:`],"media-src":[...n,`blob:`],"connect-src":[`'self'`,`blob:`,`data:`],"worker-src":[...n,`blob:`],"manifest-src":[...n],"object-src":[`'none'`],"base-uri":[`'self'`],"form-action":[`'self'`]};for(let[e,t]of Object.entries(d)){let n=e.toLowerCase();t===!1?delete r[n]:r[n]=c(r[n]??[],t)}return r}return{name:`tempest-csp`,configResolved(e){y=e,b=C(e)},transformIndexHtml:{order:`post`,handler(e,t){let i=y?.command===`serve`;if(i&&!g)return e;let o=w(b),l=y?.base??`/`,u=r.test(l)?a(l,`base`):void 0;u&&S(o,u,`base`);let d=i?[]:h(t);for(let e of n)(i||d.some(t=>e.module.test(t)))&&S(o,e.origin,e.from);let C=o,{html:E,declared:D={}}=f(e),O=T(u,i);`script-src`in D||(O[`script-src`]=c(O[`script-src`]??[],p(E,`script`))),!(`style-src`in D)&&!O[`style-src`]?.includes(`'unsafe-inline'`)&&(O[`style-src`]=c(O[`style-src`]??[],p(E,`style`)));let k={...O};for(let[e,t]of Object.entries(D))k[e]=t;k[`connect-src`]=c(k[`connect-src`]??[`'self'`],C.map(e=>e.origin));let A={directives:k,meta:s(k,`meta`),header:s(k,`header`),connect:C,command:i?`serve`:`build`};if(_&&y&&A.header!==x){x=A.header;let e=C.map(e=>` ${e.origin} ← ${e.from}`);y.logger.info([`tempest-csp: connect-src ${k[`connect-src`].join(` `)}`,...e.length>0?e:[` (no cross-origin endpoint: only 'self')`]].join(`
|
|
2
|
+
`))}return v?.(A),m(E,A.meta)}}}}exports.cspOrigin=a,exports.parseCsp=o,exports.serializeCsp=s,exports.tempestCsp=g;
|
|
3
|
+
//# sourceMappingURL=tempest-csp.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tempest-csp.cjs","names":[],"sources":["../../src/vite/tempest-csp.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport type { IndexHtmlTransformContext, Plugin, ResolvedConfig } from \"vite\";\n\nimport type { TempestVitePlugin } from \"./tempest-pwa-manifest\";\n\n/** Options for {@link tempestCsp}. */\nexport interface TempestCspOptions {\n /**\n * Endpoints the app calls at runtime, as full URLs. Only the origin\n * (`scheme://host[:port]`) enters `connect-src`: a source expression with a path\n * matches that path alone, so `https://idp/realms/x` would still block\n * `https://idp/realms/x/protocol/openid-connect/token`.\n *\n * Empty entries (`undefined`, `null`, `false`, `\"\"`) are skipped with a build\n * warning instead of leaving a hole in the directive, so passing an env var\n * that is not set is safe. A root-relative path (`/api`) is same-origin and\n * already covered by `'self'`. Anything else that is not an absolute\n * `http(s)`/`ws(s)` URL throws at config time — failing the build, not\n * production. A wildcard host (`https://*.example.com`) is kept as written.\n */\n connect?: ReadonlyArray<string | null | undefined | false>;\n /**\n * Read origins from the same environment the runtime reads. `true` (the\n * default) adds every variable Vite exposes to `import.meta.env` (the\n * `envPrefix` ones, `VITE_` by default) whose value is an absolute\n * `http(s)`/`ws(s)` URL. This is the structural bridge: an origin added to\n * `.env` for a new runtime call lands in the policy with no second list to\n * keep in sync. Pass a list of names to include exactly those — a name that\n * resolves to nothing is reported as a build warning — or `false` to rely on\n * {@link TempestCspOptions.connect} alone.\n */\n env?: boolean | readonly string[];\n /**\n * Also allow the WebSocket twin of every `http(s)` origin (`https://api` →\n * `wss://api`). Measured in Chromium (23/09/2026): `connect-src https://api`\n * blocks `new WebSocket(\"wss://api\")`, while `'self'` does cover a same-origin\n * `ws:` — so an app that derives its socket URL from the API URL breaks\n * without this. Default `true`.\n */\n sockets?: boolean;\n /**\n * Add `'wasm-unsafe-eval'` to `script-src`, which `onnxruntime-web` (the\n * `/vision` and `/tabular` subpaths) needs to compile its WebAssembly. Without\n * it `WebAssembly.compile` throws a `CompileError` naming `script-src`.\n * It grants WebAssembly compilation only, not `eval`. Default `true`.\n */\n wasm?: boolean;\n /**\n * Add `'unsafe-inline'` to `style-src`. React's `style` prop does **not** need\n * it — it writes through the CSSOM, which CSP does not govern (measured in\n * Chromium, 23/09/2026). What needs it is a `<style>` element or a `style=\"\"`\n * attribute, and the SDK's own `applyTheme` injects a `<style>`. Pass `false`\n * when the app never calls `applyTheme`; inline `<style>` blocks in\n * `index.html` are then hashed instead. Always on under `vite dev`, whose CSS\n * injection is a `<style>` element. Default `true`.\n */\n inlineStyles?: boolean;\n /**\n * Extra sources per directive, merged into the defaults (`{ \"img-src\":\n * [\"https://tiles.example.com\"] }`), or `false` to drop a directive.\n * `frame-ancestors`, `report-uri` and `sandbox` are ignored by browsers when\n * delivered by `<meta>`, so they reach {@link TempestCspPolicy.header} only.\n */\n directives?: Readonly<Record<string, readonly string[] | false>>;\n /**\n * Apply the policy under `vite dev` too. Default `true`: the failure this\n * plugin exists for is \"works in dev, blocked in production\", and a policy\n * enforced in dev moves every missing origin to the first page load on the\n * developer's machine. Dev differs from build only in `style-src\n * 'unsafe-inline'` and in allowing every SDK-known origin (see\n * {@link TempestCspPolicy.connect}), because the dev server cannot know which\n * modules the page will load.\n */\n dev?: boolean;\n /** Print the resolved `connect-src` and where each origin came from. Default `true`. */\n log?: boolean;\n /**\n * Receive the resolved policy — for asserting it in a test, or for writing the\n * same policy as an HTTP header (the only channel for `frame-ancestors`).\n */\n onPolicy?: (policy: TempestCspPolicy) => void;\n}\n\n/** One origin in `connect-src` and the input that put it there. */\nexport interface TempestCspSource {\n /** The source expression, e.g. `https://api.example.com`. */\n origin: string;\n /** Where it came from: `env VITE_API_URL`, `connect[0]`, `sockets`, `base` or an SDK module. */\n from: string;\n}\n\n/** The policy {@link tempestCsp} resolved for one `index.html`. */\nexport interface TempestCspPolicy {\n /** Every directive and its sources, including the header-only ones. */\n directives: Record<string, string[]>;\n /** The policy as written into the `<meta http-equiv>` tag. */\n meta: string;\n /** The full policy for a `Content-Security-Policy` response header. */\n header: string;\n /** Every origin added to `connect-src`, with its provenance. */\n connect: TempestCspSource[];\n /** `\"serve\"` under `vite dev`, `\"build\"` under `vite build`. */\n command: \"serve\" | \"build\";\n}\n\n/**\n * Directives a browser ignores when the policy arrives by `<meta>` (CSP3 §3.3),\n * printing a console warning for each.\n */\nconst HEADER_ONLY_DIRECTIVES: ReadonlySet<string> = new Set([\n \"frame-ancestors\",\n \"report-uri\",\n \"sandbox\",\n]);\n\n/**\n * Origins the SDK itself calls, keyed by the module that calls them. In a build\n * the origin enters only when the module is in the bundle; in dev always.\n */\nconst SDK_ORIGINS: ReadonlyArray<{ module: RegExp; origin: string; from: string }> = [\n {\n module: /[\\\\/]forms[\\\\/]use-viacep\\.(?:[cm]?js|tsx?)$/,\n origin: \"https://viacep.com.br\",\n from: \"tempest-react-sdk useViaCEP\",\n },\n];\n\nconst ABSOLUTE_URL = /^(?:https?|wss?):\\/\\//i;\nconst WILDCARD_SOURCE = /^((?:https?|wss?):\\/\\/\\*\\.[a-z0-9.-]+(?::\\d+)?)(?:[/?#].*)?$/i;\n\n/**\n * Reduce a URL to the origin a CSP source expression should carry.\n *\n * @param value - The URL as the app configured it.\n * @param from - Its provenance, quoted in the error.\n * @returns The origin, or `undefined` for a root-relative path (covered by `'self'`).\n * @throws {Error} When the value is neither an absolute `http(s)`/`ws(s)` URL nor a path.\n */\nexport function cspOrigin(value: string, from: string): string | undefined {\n const trimmed = value.trim();\n if (trimmed.startsWith(\"/\") && !trimmed.startsWith(\"//\")) return undefined;\n const wildcard = WILDCARD_SOURCE.exec(trimmed);\n if (wildcard) return wildcard[1]!.toLowerCase();\n if (ABSOLUTE_URL.test(trimmed)) {\n try {\n const url = new URL(trimmed);\n return `${url.protocol}//${url.host}`;\n } catch {\n throw new Error(`tempestCsp: ${from} is not a valid URL: \"${value}\"`);\n }\n }\n throw new Error(\n `tempestCsp: ${from} must be an absolute http(s)/ws(s) URL or a root-relative path, got \"${value}\"`,\n );\n}\n\n/**\n * Parse a serialized policy into directives, lowercasing names. A repeated\n * directive keeps its first occurrence, as browsers do.\n *\n * @param policy - A `Content-Security-Policy` value.\n * @returns Directive name → sources.\n */\nexport function parseCsp(policy: string): Record<string, string[]> {\n const out: Record<string, string[]> = {};\n for (const part of policy.split(\";\")) {\n const [name, ...sources] = part.trim().split(/\\s+/);\n if (!name) continue;\n const key = name.toLowerCase();\n if (!(key in out)) out[key] = sources;\n }\n return out;\n}\n\n/**\n * Serialize directives into a policy string, dropping header-only directives\n * when the target is a `<meta>` tag.\n *\n * @param directives - Directive name → sources.\n * @param target - `\"meta\"` or `\"header\"`.\n * @returns The policy value.\n */\nexport function serializeCsp(\n directives: Readonly<Record<string, readonly string[]>>,\n target: \"meta\" | \"header\",\n): string {\n return Object.entries(directives)\n .filter(([name]) => target === \"header\" || !HEADER_ONLY_DIRECTIVES.has(name))\n .map(([name, sources]) => [name, ...sources].join(\" \"))\n .join(\"; \");\n}\n\nfunction union(base: readonly string[], extra: readonly string[]): string[] {\n const out = base.filter((source) => source !== \"'none'\" || extra.length === 0);\n for (const source of extra) if (!out.includes(source)) out.push(source);\n return out.length === 0 ? [\"'none'\"] : out;\n}\n\nfunction sha256(content: string): string {\n return `'sha256-${createHash(\"sha256\").update(content, \"utf8\").digest(\"base64\")}'`;\n}\n\nfunction decodeEntities(value: string): string {\n return value\n .replace(/'|'|'/gi, \"'\")\n .replace(/"/gi, '\"')\n .replace(/&/gi, \"&\");\n}\n\nfunction readAttributes(tag: string): Record<string, string> {\n const out: Record<string, string> = {};\n const pattern = /([^\\s=<>/]+)\\s*=\\s*(?:\"([^\"]*)\"|'([^']*)'|([^\\s>]+))/g;\n for (const match of tag.matchAll(pattern)) {\n out[match[1]!.toLowerCase()] = decodeEntities(match[2] ?? match[3] ?? match[4] ?? \"\");\n }\n return out;\n}\n\n/**\n * Find the policy `index.html` already declares and remove its tag, so the merged\n * policy replaces it instead of stacking a second one (two policies are\n * intersected by the browser, so the stricter would win silently).\n *\n * @throws {Error} When the page declares more than one CSP `<meta>`: merging an\n * intersection into a union would weaken it, so the plugin refuses.\n */\nfunction extractMetaPolicy(html: string): { html: string; declared?: Record<string, string[]> } {\n const tags = [...html.matchAll(/<meta\\b[^>]*>/gi)].filter(\n (match) =>\n readAttributes(match[0])[\"http-equiv\"]?.toLowerCase() === \"content-security-policy\",\n );\n if (tags.length === 0) return { html };\n if (tags.length > 1) {\n throw new Error(\n \"tempestCsp: index.html declares more than one Content-Security-Policy <meta>; merge them into one\",\n );\n }\n const tag = tags[0]!;\n return {\n html:\n html.slice(0, tag.index) +\n html.slice(tag.index! + tag[0].length).replace(/^[ \\t]*\\r?\\n/, \"\"),\n declared: parseCsp(readAttributes(tag[0]).content ?? \"\"),\n };\n}\n\nfunction inlineHashes(html: string, element: \"script\" | \"style\"): string[] {\n const pattern = new RegExp(`<${element}\\\\b([^>]*)>([\\\\s\\\\S]*?)</${element}>`, \"gi\");\n const hashes: string[] = [];\n for (const match of html.matchAll(pattern)) {\n const attributes = readAttributes(`<x ${match[1]}>`);\n if (element === \"script\" && \"src\" in attributes) continue;\n if (match[2]!.length === 0) continue;\n const hash = sha256(match[2]!);\n if (!hashes.includes(hash)) hashes.push(hash);\n }\n return hashes;\n}\n\n/**\n * Write the policy tag where it governs the whole page. A `<meta>` CSP applies\n * only to what follows it, so it goes right after `<meta charset>` — unless a\n * resource precedes the charset, as the React Refresh preamble does under\n * `vite dev` (`head-prepend`), in which case it goes right after `<head>`.\n */\nfunction insertMeta(html: string, policy: string): string {\n const escaped = policy.replace(/&/g, \"&\").replace(/\"/g, \""\");\n const tag = `<meta http-equiv=\"Content-Security-Policy\" content=\"${escaped}\" />`;\n const charset = /<meta\\b[^>]*\\bcharset\\s*=[^>]*>/i.exec(html);\n const head = /<head\\b[^>]*>/i.exec(html);\n const resourceFirst =\n charset !== null &&\n head !== null &&\n /<(?:script|link|style)\\b/i.test(html.slice(head.index, charset.index));\n const anchor = charset && head && charset.index > head.index && !resourceFirst ? charset : head;\n if (!anchor) return `${tag}\\n${html}`;\n const at = anchor.index + anchor[0].length;\n return `${html.slice(0, at)}\\n ${tag}${html.slice(at)}`;\n}\n\nfunction bundleModuleIds(ctx: IndexHtmlTransformContext): string[] {\n const ids: string[] = [];\n for (const output of Object.values(ctx.bundle ?? {})) {\n if (output.type !== \"chunk\") continue;\n ids.push(...(output.moduleIds ?? Object.keys(output.modules ?? {})));\n }\n return ids;\n}\n\n/**\n * Vite plugin that writes a Content-Security-Policy `<meta>` into `index.html`\n * and derives `connect-src` from the origins the app actually calls.\n *\n * It exists because of how a missing origin fails: the request is blocked\n * before a connection opens, so there is no entry in the Network tab, nothing in\n * the server log, `curl` succeeds, and the app sees `TypeError: Failed to\n * fetch` — indistinguishable from offline, DNS or CORS. And a hand-written CSP\n * plugin is usually `apply: \"build\"`, so it only fails in production. This\n * plugin closes both ends:\n *\n * - origins come from the same `import.meta.env` the runtime reads\n * ({@link TempestCspOptions.env}), reduced to `scheme://host[:port]`, with\n * empty values skipped — no second list to drift;\n * - the policy is enforced under `vite dev` as well ({@link TempestCspOptions.dev});\n * - the build prints every origin and its provenance.\n *\n * Defaults cover what the SDK's stack needs and nothing more: `'self'`\n * everywhere, `'wasm-unsafe-eval'` for ONNX Runtime, `blob:` for workers, object\n * URLs and media, `data:` for images and fonts, `object-src 'none'`,\n * `base-uri 'self'`, `form-action 'self'`. Inline `<script>` blocks left in\n * `index.html` (a `themeInitScript()` anti-flash script, say) are allowed by\n * SHA-256 hash, never by `'unsafe-inline'`.\n *\n * An existing CSP `<meta>` in `index.html` is merged, not overwritten: every\n * directive it declares is kept verbatim — the plugin never loosens a choice the\n * app wrote — except `connect-src`, which gains the resolved origins, since\n * that is the list the plugin exists to maintain. Directives it does not declare\n * come from the defaults.\n *\n * @param options - See {@link TempestCspOptions}.\n * @returns A Vite plugin, to be placed in `plugins`. It runs as a `post` HTML\n * transform, so a plugin that rewrites inline scripts after it would\n * invalidate the hashes.\n *\n * @example\n * // vite.config.ts\n * import { createViteConfig, tempestCsp } from \"tempest-react-sdk/vite\";\n *\n * export default createViteConfig({\n * plugins: [tempestCsp({ connect: [\"https://sso.example.com/realms/app\"] })],\n * });\n */\nexport function tempestCsp(options: TempestCspOptions = {}): TempestVitePlugin {\n const {\n connect = [],\n env = true,\n sockets = true,\n wasm = true,\n inlineStyles = true,\n directives: extra = {},\n dev = true,\n log = true,\n onPolicy,\n } = options;\n\n let config: ResolvedConfig | undefined;\n let resolved: TempestCspSource[] = [];\n let lastLogged = \"\";\n\n function add(list: TempestCspSource[], origin: string | undefined, from: string): void {\n if (origin === undefined || list.some((source) => source.origin === origin)) return;\n list.push({ origin, from });\n }\n\n function resolveSources(resolvedConfig: ResolvedConfig): TempestCspSource[] {\n const list: TempestCspSource[] = [];\n const values: Record<string, unknown> = resolvedConfig.env ?? {};\n const prefixes = [resolvedConfig.envPrefix ?? \"VITE_\"].flat();\n if (env === true) {\n for (const [name, value] of Object.entries(values)) {\n if (!prefixes.some((prefix) => name.startsWith(prefix))) continue;\n if (typeof value !== \"string\" || !ABSOLUTE_URL.test(value.trim())) continue;\n add(list, cspOrigin(value, `env ${name}`), `env ${name}`);\n }\n } else if (Array.isArray(env)) {\n for (const name of env) {\n const value = values[name];\n if (typeof value !== \"string\" || value.trim() === \"\") {\n resolvedConfig.logger.warn(\n `tempestCsp: env ${name} is empty — nothing added to connect-src`,\n );\n continue;\n }\n add(list, cspOrigin(value, `env ${name}`), `env ${name}`);\n }\n }\n connect.forEach((value, index) => {\n if (!value || value.trim() === \"\") {\n resolvedConfig.logger.warn(\n `tempestCsp: connect[${index}] is empty — skipped, nothing added to connect-src`,\n );\n return;\n }\n add(list, cspOrigin(value, `connect[${index}]`), `connect[${index}]`);\n });\n return list;\n }\n\n function withSockets(list: readonly TempestCspSource[]): TempestCspSource[] {\n const out = [...list];\n if (!sockets) return out;\n for (const { origin } of list) {\n const twin = origin.replace(/^http(s?):/i, \"ws$1:\");\n if (twin !== origin) add(out, twin, `sockets (${origin})`);\n }\n return out;\n }\n\n function defaults(baseOrigin: string | undefined, serve: boolean): Record<string, string[]> {\n const self = baseOrigin ? [\"'self'\", baseOrigin] : [\"'self'\"];\n const policy: Record<string, string[]> = {\n \"default-src\": [...self],\n \"script-src\": wasm ? [...self, \"'wasm-unsafe-eval'\"] : [...self],\n \"style-src\": inlineStyles || serve ? [...self, \"'unsafe-inline'\"] : [...self],\n \"img-src\": [...self, \"data:\", \"blob:\"],\n \"font-src\": [...self, \"data:\"],\n \"media-src\": [...self, \"blob:\"],\n \"connect-src\": [\"'self'\", \"blob:\", \"data:\"],\n \"worker-src\": [...self, \"blob:\"],\n \"manifest-src\": [...self],\n \"object-src\": [\"'none'\"],\n \"base-uri\": [\"'self'\"],\n \"form-action\": [\"'self'\"],\n };\n for (const [name, sources] of Object.entries(extra)) {\n const key = name.toLowerCase();\n if (sources === false) delete policy[key];\n else policy[key] = union(policy[key] ?? [], sources);\n }\n return policy;\n }\n\n const plugin: Plugin = {\n name: \"tempest-csp\",\n configResolved(resolvedConfig) {\n config = resolvedConfig;\n resolved = resolveSources(resolvedConfig);\n },\n transformIndexHtml: {\n order: \"post\",\n handler(source, ctx) {\n const serve = config?.command === \"serve\";\n if (serve && !dev) return source;\n\n const connectSources = withSockets(resolved);\n const base = config?.base ?? \"/\";\n const baseOrigin = ABSOLUTE_URL.test(base) ? cspOrigin(base, \"base\") : undefined;\n if (baseOrigin) add(connectSources, baseOrigin, \"base\");\n const moduleIds = serve ? [] : bundleModuleIds(ctx);\n for (const known of SDK_ORIGINS) {\n if (serve || moduleIds.some((id) => known.module.test(id))) {\n add(connectSources, known.origin, known.from);\n }\n }\n const allSources = connectSources;\n\n const { html, declared = {} } = extractMetaPolicy(source);\n const policy = defaults(baseOrigin, serve);\n if (!(\"script-src\" in declared)) {\n policy[\"script-src\"] = union(\n policy[\"script-src\"] ?? [],\n inlineHashes(html, \"script\"),\n );\n }\n if (\n !(\"style-src\" in declared) &&\n !policy[\"style-src\"]?.includes(\"'unsafe-inline'\")\n ) {\n policy[\"style-src\"] = union(\n policy[\"style-src\"] ?? [],\n inlineHashes(html, \"style\"),\n );\n }\n const merged: Record<string, string[]> = { ...policy };\n for (const [name, sources] of Object.entries(declared)) merged[name] = sources;\n merged[\"connect-src\"] = union(\n merged[\"connect-src\"] ?? [\"'self'\"],\n allSources.map((entry) => entry.origin),\n );\n\n const result: TempestCspPolicy = {\n directives: merged,\n meta: serializeCsp(merged, \"meta\"),\n header: serializeCsp(merged, \"header\"),\n connect: allSources,\n command: serve ? \"serve\" : \"build\",\n };\n\n if (log && config && result.header !== lastLogged) {\n lastLogged = result.header;\n const lines = allSources.map((entry) => ` ${entry.origin} ← ${entry.from}`);\n config.logger.info(\n [\n `tempest-csp: connect-src ${merged[\"connect-src\"].join(\" \")}`,\n ...(lines.length > 0\n ? lines\n : [\" (no cross-origin endpoint: only 'self')\"]),\n ].join(\"\\n\"),\n );\n }\n onPolicy?.(result);\n return insertMeta(html, result.meta);\n },\n },\n };\n\n return plugin as TempestVitePlugin;\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { createHash as e } from "node:crypto";
|
|
2
|
+
//#region src/vite/tempest-csp.ts
|
|
3
|
+
var t = /* @__PURE__ */ new Set([
|
|
4
|
+
"frame-ancestors",
|
|
5
|
+
"report-uri",
|
|
6
|
+
"sandbox"
|
|
7
|
+
]), n = [{
|
|
8
|
+
module: /[\\/]forms[\\/]use-viacep\.(?:[cm]?js|tsx?)$/,
|
|
9
|
+
origin: "https://viacep.com.br",
|
|
10
|
+
from: "tempest-react-sdk useViaCEP"
|
|
11
|
+
}], r = /^(?:https?|wss?):\/\//i, i = /^((?:https?|wss?):\/\/\*\.[a-z0-9.-]+(?::\d+)?)(?:[/?#].*)?$/i;
|
|
12
|
+
function a(e, t) {
|
|
13
|
+
let n = e.trim();
|
|
14
|
+
if (n.startsWith("/") && !n.startsWith("//")) return;
|
|
15
|
+
let a = i.exec(n);
|
|
16
|
+
if (a) return a[1].toLowerCase();
|
|
17
|
+
if (r.test(n)) try {
|
|
18
|
+
let e = new URL(n);
|
|
19
|
+
return `${e.protocol}//${e.host}`;
|
|
20
|
+
} catch {
|
|
21
|
+
throw Error(`tempestCsp: ${t} is not a valid URL: "${e}"`);
|
|
22
|
+
}
|
|
23
|
+
throw Error(`tempestCsp: ${t} must be an absolute http(s)/ws(s) URL or a root-relative path, got "${e}"`);
|
|
24
|
+
}
|
|
25
|
+
function o(e) {
|
|
26
|
+
let t = {};
|
|
27
|
+
for (let n of e.split(";")) {
|
|
28
|
+
let [e, ...r] = n.trim().split(/\s+/);
|
|
29
|
+
if (!e) continue;
|
|
30
|
+
let i = e.toLowerCase();
|
|
31
|
+
i in t || (t[i] = r);
|
|
32
|
+
}
|
|
33
|
+
return t;
|
|
34
|
+
}
|
|
35
|
+
function s(e, n) {
|
|
36
|
+
return Object.entries(e).filter(([e]) => n === "header" || !t.has(e)).map(([e, t]) => [e, ...t].join(" ")).join("; ");
|
|
37
|
+
}
|
|
38
|
+
function c(e, t) {
|
|
39
|
+
let n = e.filter((e) => e !== "'none'" || t.length === 0);
|
|
40
|
+
for (let e of t) n.includes(e) || n.push(e);
|
|
41
|
+
return n.length === 0 ? ["'none'"] : n;
|
|
42
|
+
}
|
|
43
|
+
function l(t) {
|
|
44
|
+
return `'sha256-${e("sha256").update(t, "utf8").digest("base64")}'`;
|
|
45
|
+
}
|
|
46
|
+
function u(e) {
|
|
47
|
+
return e.replace(/'|'|'/gi, "'").replace(/"/gi, "\"").replace(/&/gi, "&");
|
|
48
|
+
}
|
|
49
|
+
function d(e) {
|
|
50
|
+
let t = {};
|
|
51
|
+
for (let n of e.matchAll(/([^\s=<>/]+)\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]+))/g)) t[n[1].toLowerCase()] = u(n[2] ?? n[3] ?? n[4] ?? "");
|
|
52
|
+
return t;
|
|
53
|
+
}
|
|
54
|
+
function f(e) {
|
|
55
|
+
let t = [...e.matchAll(/<meta\b[^>]*>/gi)].filter((e) => d(e[0])["http-equiv"]?.toLowerCase() === "content-security-policy");
|
|
56
|
+
if (t.length === 0) return { html: e };
|
|
57
|
+
if (t.length > 1) throw Error("tempestCsp: index.html declares more than one Content-Security-Policy <meta>; merge them into one");
|
|
58
|
+
let n = t[0];
|
|
59
|
+
return {
|
|
60
|
+
html: e.slice(0, n.index) + e.slice(n.index + n[0].length).replace(/^[ \t]*\r?\n/, ""),
|
|
61
|
+
declared: o(d(n[0]).content ?? "")
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
function p(e, t) {
|
|
65
|
+
let n = RegExp(`<${t}\\b([^>]*)>([\\s\\S]*?)</${t}>`, "gi"), r = [];
|
|
66
|
+
for (let i of e.matchAll(n)) {
|
|
67
|
+
let e = d(`<x ${i[1]}>`);
|
|
68
|
+
if (t === "script" && "src" in e || i[2].length === 0) continue;
|
|
69
|
+
let n = l(i[2]);
|
|
70
|
+
r.includes(n) || r.push(n);
|
|
71
|
+
}
|
|
72
|
+
return r;
|
|
73
|
+
}
|
|
74
|
+
function m(e, t) {
|
|
75
|
+
let n = `<meta http-equiv="Content-Security-Policy" content="${t.replace(/&/g, "&").replace(/"/g, """)}" />`, r = /<meta\b[^>]*\bcharset\s*=[^>]*>/i.exec(e), i = /<head\b[^>]*>/i.exec(e), a = r !== null && i !== null && /<(?:script|link|style)\b/i.test(e.slice(i.index, r.index)), o = r && i && r.index > i.index && !a ? r : i;
|
|
76
|
+
if (!o) return `${n}\n${e}`;
|
|
77
|
+
let s = o.index + o[0].length;
|
|
78
|
+
return `${e.slice(0, s)}\n ${n}${e.slice(s)}`;
|
|
79
|
+
}
|
|
80
|
+
function h(e) {
|
|
81
|
+
let t = [];
|
|
82
|
+
for (let n of Object.values(e.bundle ?? {})) n.type === "chunk" && t.push(...n.moduleIds ?? Object.keys(n.modules ?? {}));
|
|
83
|
+
return t;
|
|
84
|
+
}
|
|
85
|
+
function g(e = {}) {
|
|
86
|
+
let { connect: t = [], env: i = !0, sockets: o = !0, wasm: l = !0, inlineStyles: u = !0, directives: d = {}, dev: g = !0, log: _ = !0, onPolicy: v } = e, y, b = [], x = "";
|
|
87
|
+
function S(e, t, n) {
|
|
88
|
+
t === void 0 || e.some((e) => e.origin === t) || e.push({
|
|
89
|
+
origin: t,
|
|
90
|
+
from: n
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
function C(e) {
|
|
94
|
+
let n = [], o = e.env ?? {}, s = [e.envPrefix ?? "VITE_"].flat();
|
|
95
|
+
if (i === !0) for (let [e, t] of Object.entries(o)) s.some((t) => e.startsWith(t)) && typeof t == "string" && r.test(t.trim()) && S(n, a(t, `env ${e}`), `env ${e}`);
|
|
96
|
+
else if (Array.isArray(i)) for (let t of i) {
|
|
97
|
+
let r = o[t];
|
|
98
|
+
if (typeof r != "string" || r.trim() === "") {
|
|
99
|
+
e.logger.warn(`tempestCsp: env ${t} is empty — nothing added to connect-src`);
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
S(n, a(r, `env ${t}`), `env ${t}`);
|
|
103
|
+
}
|
|
104
|
+
return t.forEach((t, r) => {
|
|
105
|
+
if (!t || t.trim() === "") {
|
|
106
|
+
e.logger.warn(`tempestCsp: connect[${r}] is empty — skipped, nothing added to connect-src`);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
S(n, a(t, `connect[${r}]`), `connect[${r}]`);
|
|
110
|
+
}), n;
|
|
111
|
+
}
|
|
112
|
+
function w(e) {
|
|
113
|
+
let t = [...e];
|
|
114
|
+
if (!o) return t;
|
|
115
|
+
for (let { origin: n } of e) {
|
|
116
|
+
let e = n.replace(/^http(s?):/i, "ws$1:");
|
|
117
|
+
e !== n && S(t, e, `sockets (${n})`);
|
|
118
|
+
}
|
|
119
|
+
return t;
|
|
120
|
+
}
|
|
121
|
+
function T(e, t) {
|
|
122
|
+
let n = e ? ["'self'", e] : ["'self'"], r = {
|
|
123
|
+
"default-src": [...n],
|
|
124
|
+
"script-src": l ? [...n, "'wasm-unsafe-eval'"] : [...n],
|
|
125
|
+
"style-src": u || t ? [...n, "'unsafe-inline'"] : [...n],
|
|
126
|
+
"img-src": [
|
|
127
|
+
...n,
|
|
128
|
+
"data:",
|
|
129
|
+
"blob:"
|
|
130
|
+
],
|
|
131
|
+
"font-src": [...n, "data:"],
|
|
132
|
+
"media-src": [...n, "blob:"],
|
|
133
|
+
"connect-src": [
|
|
134
|
+
"'self'",
|
|
135
|
+
"blob:",
|
|
136
|
+
"data:"
|
|
137
|
+
],
|
|
138
|
+
"worker-src": [...n, "blob:"],
|
|
139
|
+
"manifest-src": [...n],
|
|
140
|
+
"object-src": ["'none'"],
|
|
141
|
+
"base-uri": ["'self'"],
|
|
142
|
+
"form-action": ["'self'"]
|
|
143
|
+
};
|
|
144
|
+
for (let [e, t] of Object.entries(d)) {
|
|
145
|
+
let n = e.toLowerCase();
|
|
146
|
+
t === !1 ? delete r[n] : r[n] = c(r[n] ?? [], t);
|
|
147
|
+
}
|
|
148
|
+
return r;
|
|
149
|
+
}
|
|
150
|
+
return {
|
|
151
|
+
name: "tempest-csp",
|
|
152
|
+
configResolved(e) {
|
|
153
|
+
y = e, b = C(e);
|
|
154
|
+
},
|
|
155
|
+
transformIndexHtml: {
|
|
156
|
+
order: "post",
|
|
157
|
+
handler(e, t) {
|
|
158
|
+
let i = y?.command === "serve";
|
|
159
|
+
if (i && !g) return e;
|
|
160
|
+
let o = w(b), l = y?.base ?? "/", u = r.test(l) ? a(l, "base") : void 0;
|
|
161
|
+
u && S(o, u, "base");
|
|
162
|
+
let d = i ? [] : h(t);
|
|
163
|
+
for (let e of n) (i || d.some((t) => e.module.test(t))) && S(o, e.origin, e.from);
|
|
164
|
+
let C = o, { html: E, declared: D = {} } = f(e), O = T(u, i);
|
|
165
|
+
"script-src" in D || (O["script-src"] = c(O["script-src"] ?? [], p(E, "script"))), !("style-src" in D) && !O["style-src"]?.includes("'unsafe-inline'") && (O["style-src"] = c(O["style-src"] ?? [], p(E, "style")));
|
|
166
|
+
let k = { ...O };
|
|
167
|
+
for (let [e, t] of Object.entries(D)) k[e] = t;
|
|
168
|
+
k["connect-src"] = c(k["connect-src"] ?? ["'self'"], C.map((e) => e.origin));
|
|
169
|
+
let A = {
|
|
170
|
+
directives: k,
|
|
171
|
+
meta: s(k, "meta"),
|
|
172
|
+
header: s(k, "header"),
|
|
173
|
+
connect: C,
|
|
174
|
+
command: i ? "serve" : "build"
|
|
175
|
+
};
|
|
176
|
+
if (_ && y && A.header !== x) {
|
|
177
|
+
x = A.header;
|
|
178
|
+
let e = C.map((e) => ` ${e.origin} ← ${e.from}`);
|
|
179
|
+
y.logger.info([`tempest-csp: connect-src ${k["connect-src"].join(" ")}`, ...e.length > 0 ? e : [" (no cross-origin endpoint: only 'self')"]].join("\n"));
|
|
180
|
+
}
|
|
181
|
+
return v?.(A), m(E, A.meta);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
//#endregion
|
|
187
|
+
export { a as cspOrigin, o as parseCsp, s as serializeCsp, g as tempestCsp };
|
|
188
|
+
|
|
189
|
+
//# sourceMappingURL=tempest-csp.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tempest-csp.js","names":[],"sources":["../../src/vite/tempest-csp.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport type { IndexHtmlTransformContext, Plugin, ResolvedConfig } from \"vite\";\n\nimport type { TempestVitePlugin } from \"./tempest-pwa-manifest\";\n\n/** Options for {@link tempestCsp}. */\nexport interface TempestCspOptions {\n /**\n * Endpoints the app calls at runtime, as full URLs. Only the origin\n * (`scheme://host[:port]`) enters `connect-src`: a source expression with a path\n * matches that path alone, so `https://idp/realms/x` would still block\n * `https://idp/realms/x/protocol/openid-connect/token`.\n *\n * Empty entries (`undefined`, `null`, `false`, `\"\"`) are skipped with a build\n * warning instead of leaving a hole in the directive, so passing an env var\n * that is not set is safe. A root-relative path (`/api`) is same-origin and\n * already covered by `'self'`. Anything else that is not an absolute\n * `http(s)`/`ws(s)` URL throws at config time — failing the build, not\n * production. A wildcard host (`https://*.example.com`) is kept as written.\n */\n connect?: ReadonlyArray<string | null | undefined | false>;\n /**\n * Read origins from the same environment the runtime reads. `true` (the\n * default) adds every variable Vite exposes to `import.meta.env` (the\n * `envPrefix` ones, `VITE_` by default) whose value is an absolute\n * `http(s)`/`ws(s)` URL. This is the structural bridge: an origin added to\n * `.env` for a new runtime call lands in the policy with no second list to\n * keep in sync. Pass a list of names to include exactly those — a name that\n * resolves to nothing is reported as a build warning — or `false` to rely on\n * {@link TempestCspOptions.connect} alone.\n */\n env?: boolean | readonly string[];\n /**\n * Also allow the WebSocket twin of every `http(s)` origin (`https://api` →\n * `wss://api`). Measured in Chromium (23/09/2026): `connect-src https://api`\n * blocks `new WebSocket(\"wss://api\")`, while `'self'` does cover a same-origin\n * `ws:` — so an app that derives its socket URL from the API URL breaks\n * without this. Default `true`.\n */\n sockets?: boolean;\n /**\n * Add `'wasm-unsafe-eval'` to `script-src`, which `onnxruntime-web` (the\n * `/vision` and `/tabular` subpaths) needs to compile its WebAssembly. Without\n * it `WebAssembly.compile` throws a `CompileError` naming `script-src`.\n * It grants WebAssembly compilation only, not `eval`. Default `true`.\n */\n wasm?: boolean;\n /**\n * Add `'unsafe-inline'` to `style-src`. React's `style` prop does **not** need\n * it — it writes through the CSSOM, which CSP does not govern (measured in\n * Chromium, 23/09/2026). What needs it is a `<style>` element or a `style=\"\"`\n * attribute, and the SDK's own `applyTheme` injects a `<style>`. Pass `false`\n * when the app never calls `applyTheme`; inline `<style>` blocks in\n * `index.html` are then hashed instead. Always on under `vite dev`, whose CSS\n * injection is a `<style>` element. Default `true`.\n */\n inlineStyles?: boolean;\n /**\n * Extra sources per directive, merged into the defaults (`{ \"img-src\":\n * [\"https://tiles.example.com\"] }`), or `false` to drop a directive.\n * `frame-ancestors`, `report-uri` and `sandbox` are ignored by browsers when\n * delivered by `<meta>`, so they reach {@link TempestCspPolicy.header} only.\n */\n directives?: Readonly<Record<string, readonly string[] | false>>;\n /**\n * Apply the policy under `vite dev` too. Default `true`: the failure this\n * plugin exists for is \"works in dev, blocked in production\", and a policy\n * enforced in dev moves every missing origin to the first page load on the\n * developer's machine. Dev differs from build only in `style-src\n * 'unsafe-inline'` and in allowing every SDK-known origin (see\n * {@link TempestCspPolicy.connect}), because the dev server cannot know which\n * modules the page will load.\n */\n dev?: boolean;\n /** Print the resolved `connect-src` and where each origin came from. Default `true`. */\n log?: boolean;\n /**\n * Receive the resolved policy — for asserting it in a test, or for writing the\n * same policy as an HTTP header (the only channel for `frame-ancestors`).\n */\n onPolicy?: (policy: TempestCspPolicy) => void;\n}\n\n/** One origin in `connect-src` and the input that put it there. */\nexport interface TempestCspSource {\n /** The source expression, e.g. `https://api.example.com`. */\n origin: string;\n /** Where it came from: `env VITE_API_URL`, `connect[0]`, `sockets`, `base` or an SDK module. */\n from: string;\n}\n\n/** The policy {@link tempestCsp} resolved for one `index.html`. */\nexport interface TempestCspPolicy {\n /** Every directive and its sources, including the header-only ones. */\n directives: Record<string, string[]>;\n /** The policy as written into the `<meta http-equiv>` tag. */\n meta: string;\n /** The full policy for a `Content-Security-Policy` response header. */\n header: string;\n /** Every origin added to `connect-src`, with its provenance. */\n connect: TempestCspSource[];\n /** `\"serve\"` under `vite dev`, `\"build\"` under `vite build`. */\n command: \"serve\" | \"build\";\n}\n\n/**\n * Directives a browser ignores when the policy arrives by `<meta>` (CSP3 §3.3),\n * printing a console warning for each.\n */\nconst HEADER_ONLY_DIRECTIVES: ReadonlySet<string> = new Set([\n \"frame-ancestors\",\n \"report-uri\",\n \"sandbox\",\n]);\n\n/**\n * Origins the SDK itself calls, keyed by the module that calls them. In a build\n * the origin enters only when the module is in the bundle; in dev always.\n */\nconst SDK_ORIGINS: ReadonlyArray<{ module: RegExp; origin: string; from: string }> = [\n {\n module: /[\\\\/]forms[\\\\/]use-viacep\\.(?:[cm]?js|tsx?)$/,\n origin: \"https://viacep.com.br\",\n from: \"tempest-react-sdk useViaCEP\",\n },\n];\n\nconst ABSOLUTE_URL = /^(?:https?|wss?):\\/\\//i;\nconst WILDCARD_SOURCE = /^((?:https?|wss?):\\/\\/\\*\\.[a-z0-9.-]+(?::\\d+)?)(?:[/?#].*)?$/i;\n\n/**\n * Reduce a URL to the origin a CSP source expression should carry.\n *\n * @param value - The URL as the app configured it.\n * @param from - Its provenance, quoted in the error.\n * @returns The origin, or `undefined` for a root-relative path (covered by `'self'`).\n * @throws {Error} When the value is neither an absolute `http(s)`/`ws(s)` URL nor a path.\n */\nexport function cspOrigin(value: string, from: string): string | undefined {\n const trimmed = value.trim();\n if (trimmed.startsWith(\"/\") && !trimmed.startsWith(\"//\")) return undefined;\n const wildcard = WILDCARD_SOURCE.exec(trimmed);\n if (wildcard) return wildcard[1]!.toLowerCase();\n if (ABSOLUTE_URL.test(trimmed)) {\n try {\n const url = new URL(trimmed);\n return `${url.protocol}//${url.host}`;\n } catch {\n throw new Error(`tempestCsp: ${from} is not a valid URL: \"${value}\"`);\n }\n }\n throw new Error(\n `tempestCsp: ${from} must be an absolute http(s)/ws(s) URL or a root-relative path, got \"${value}\"`,\n );\n}\n\n/**\n * Parse a serialized policy into directives, lowercasing names. A repeated\n * directive keeps its first occurrence, as browsers do.\n *\n * @param policy - A `Content-Security-Policy` value.\n * @returns Directive name → sources.\n */\nexport function parseCsp(policy: string): Record<string, string[]> {\n const out: Record<string, string[]> = {};\n for (const part of policy.split(\";\")) {\n const [name, ...sources] = part.trim().split(/\\s+/);\n if (!name) continue;\n const key = name.toLowerCase();\n if (!(key in out)) out[key] = sources;\n }\n return out;\n}\n\n/**\n * Serialize directives into a policy string, dropping header-only directives\n * when the target is a `<meta>` tag.\n *\n * @param directives - Directive name → sources.\n * @param target - `\"meta\"` or `\"header\"`.\n * @returns The policy value.\n */\nexport function serializeCsp(\n directives: Readonly<Record<string, readonly string[]>>,\n target: \"meta\" | \"header\",\n): string {\n return Object.entries(directives)\n .filter(([name]) => target === \"header\" || !HEADER_ONLY_DIRECTIVES.has(name))\n .map(([name, sources]) => [name, ...sources].join(\" \"))\n .join(\"; \");\n}\n\nfunction union(base: readonly string[], extra: readonly string[]): string[] {\n const out = base.filter((source) => source !== \"'none'\" || extra.length === 0);\n for (const source of extra) if (!out.includes(source)) out.push(source);\n return out.length === 0 ? [\"'none'\"] : out;\n}\n\nfunction sha256(content: string): string {\n return `'sha256-${createHash(\"sha256\").update(content, \"utf8\").digest(\"base64\")}'`;\n}\n\nfunction decodeEntities(value: string): string {\n return value\n .replace(/'|'|'/gi, \"'\")\n .replace(/"/gi, '\"')\n .replace(/&/gi, \"&\");\n}\n\nfunction readAttributes(tag: string): Record<string, string> {\n const out: Record<string, string> = {};\n const pattern = /([^\\s=<>/]+)\\s*=\\s*(?:\"([^\"]*)\"|'([^']*)'|([^\\s>]+))/g;\n for (const match of tag.matchAll(pattern)) {\n out[match[1]!.toLowerCase()] = decodeEntities(match[2] ?? match[3] ?? match[4] ?? \"\");\n }\n return out;\n}\n\n/**\n * Find the policy `index.html` already declares and remove its tag, so the merged\n * policy replaces it instead of stacking a second one (two policies are\n * intersected by the browser, so the stricter would win silently).\n *\n * @throws {Error} When the page declares more than one CSP `<meta>`: merging an\n * intersection into a union would weaken it, so the plugin refuses.\n */\nfunction extractMetaPolicy(html: string): { html: string; declared?: Record<string, string[]> } {\n const tags = [...html.matchAll(/<meta\\b[^>]*>/gi)].filter(\n (match) =>\n readAttributes(match[0])[\"http-equiv\"]?.toLowerCase() === \"content-security-policy\",\n );\n if (tags.length === 0) return { html };\n if (tags.length > 1) {\n throw new Error(\n \"tempestCsp: index.html declares more than one Content-Security-Policy <meta>; merge them into one\",\n );\n }\n const tag = tags[0]!;\n return {\n html:\n html.slice(0, tag.index) +\n html.slice(tag.index! + tag[0].length).replace(/^[ \\t]*\\r?\\n/, \"\"),\n declared: parseCsp(readAttributes(tag[0]).content ?? \"\"),\n };\n}\n\nfunction inlineHashes(html: string, element: \"script\" | \"style\"): string[] {\n const pattern = new RegExp(`<${element}\\\\b([^>]*)>([\\\\s\\\\S]*?)</${element}>`, \"gi\");\n const hashes: string[] = [];\n for (const match of html.matchAll(pattern)) {\n const attributes = readAttributes(`<x ${match[1]}>`);\n if (element === \"script\" && \"src\" in attributes) continue;\n if (match[2]!.length === 0) continue;\n const hash = sha256(match[2]!);\n if (!hashes.includes(hash)) hashes.push(hash);\n }\n return hashes;\n}\n\n/**\n * Write the policy tag where it governs the whole page. A `<meta>` CSP applies\n * only to what follows it, so it goes right after `<meta charset>` — unless a\n * resource precedes the charset, as the React Refresh preamble does under\n * `vite dev` (`head-prepend`), in which case it goes right after `<head>`.\n */\nfunction insertMeta(html: string, policy: string): string {\n const escaped = policy.replace(/&/g, \"&\").replace(/\"/g, \""\");\n const tag = `<meta http-equiv=\"Content-Security-Policy\" content=\"${escaped}\" />`;\n const charset = /<meta\\b[^>]*\\bcharset\\s*=[^>]*>/i.exec(html);\n const head = /<head\\b[^>]*>/i.exec(html);\n const resourceFirst =\n charset !== null &&\n head !== null &&\n /<(?:script|link|style)\\b/i.test(html.slice(head.index, charset.index));\n const anchor = charset && head && charset.index > head.index && !resourceFirst ? charset : head;\n if (!anchor) return `${tag}\\n${html}`;\n const at = anchor.index + anchor[0].length;\n return `${html.slice(0, at)}\\n ${tag}${html.slice(at)}`;\n}\n\nfunction bundleModuleIds(ctx: IndexHtmlTransformContext): string[] {\n const ids: string[] = [];\n for (const output of Object.values(ctx.bundle ?? {})) {\n if (output.type !== \"chunk\") continue;\n ids.push(...(output.moduleIds ?? Object.keys(output.modules ?? {})));\n }\n return ids;\n}\n\n/**\n * Vite plugin that writes a Content-Security-Policy `<meta>` into `index.html`\n * and derives `connect-src` from the origins the app actually calls.\n *\n * It exists because of how a missing origin fails: the request is blocked\n * before a connection opens, so there is no entry in the Network tab, nothing in\n * the server log, `curl` succeeds, and the app sees `TypeError: Failed to\n * fetch` — indistinguishable from offline, DNS or CORS. And a hand-written CSP\n * plugin is usually `apply: \"build\"`, so it only fails in production. This\n * plugin closes both ends:\n *\n * - origins come from the same `import.meta.env` the runtime reads\n * ({@link TempestCspOptions.env}), reduced to `scheme://host[:port]`, with\n * empty values skipped — no second list to drift;\n * - the policy is enforced under `vite dev` as well ({@link TempestCspOptions.dev});\n * - the build prints every origin and its provenance.\n *\n * Defaults cover what the SDK's stack needs and nothing more: `'self'`\n * everywhere, `'wasm-unsafe-eval'` for ONNX Runtime, `blob:` for workers, object\n * URLs and media, `data:` for images and fonts, `object-src 'none'`,\n * `base-uri 'self'`, `form-action 'self'`. Inline `<script>` blocks left in\n * `index.html` (a `themeInitScript()` anti-flash script, say) are allowed by\n * SHA-256 hash, never by `'unsafe-inline'`.\n *\n * An existing CSP `<meta>` in `index.html` is merged, not overwritten: every\n * directive it declares is kept verbatim — the plugin never loosens a choice the\n * app wrote — except `connect-src`, which gains the resolved origins, since\n * that is the list the plugin exists to maintain. Directives it does not declare\n * come from the defaults.\n *\n * @param options - See {@link TempestCspOptions}.\n * @returns A Vite plugin, to be placed in `plugins`. It runs as a `post` HTML\n * transform, so a plugin that rewrites inline scripts after it would\n * invalidate the hashes.\n *\n * @example\n * // vite.config.ts\n * import { createViteConfig, tempestCsp } from \"tempest-react-sdk/vite\";\n *\n * export default createViteConfig({\n * plugins: [tempestCsp({ connect: [\"https://sso.example.com/realms/app\"] })],\n * });\n */\nexport function tempestCsp(options: TempestCspOptions = {}): TempestVitePlugin {\n const {\n connect = [],\n env = true,\n sockets = true,\n wasm = true,\n inlineStyles = true,\n directives: extra = {},\n dev = true,\n log = true,\n onPolicy,\n } = options;\n\n let config: ResolvedConfig | undefined;\n let resolved: TempestCspSource[] = [];\n let lastLogged = \"\";\n\n function add(list: TempestCspSource[], origin: string | undefined, from: string): void {\n if (origin === undefined || list.some((source) => source.origin === origin)) return;\n list.push({ origin, from });\n }\n\n function resolveSources(resolvedConfig: ResolvedConfig): TempestCspSource[] {\n const list: TempestCspSource[] = [];\n const values: Record<string, unknown> = resolvedConfig.env ?? {};\n const prefixes = [resolvedConfig.envPrefix ?? \"VITE_\"].flat();\n if (env === true) {\n for (const [name, value] of Object.entries(values)) {\n if (!prefixes.some((prefix) => name.startsWith(prefix))) continue;\n if (typeof value !== \"string\" || !ABSOLUTE_URL.test(value.trim())) continue;\n add(list, cspOrigin(value, `env ${name}`), `env ${name}`);\n }\n } else if (Array.isArray(env)) {\n for (const name of env) {\n const value = values[name];\n if (typeof value !== \"string\" || value.trim() === \"\") {\n resolvedConfig.logger.warn(\n `tempestCsp: env ${name} is empty — nothing added to connect-src`,\n );\n continue;\n }\n add(list, cspOrigin(value, `env ${name}`), `env ${name}`);\n }\n }\n connect.forEach((value, index) => {\n if (!value || value.trim() === \"\") {\n resolvedConfig.logger.warn(\n `tempestCsp: connect[${index}] is empty — skipped, nothing added to connect-src`,\n );\n return;\n }\n add(list, cspOrigin(value, `connect[${index}]`), `connect[${index}]`);\n });\n return list;\n }\n\n function withSockets(list: readonly TempestCspSource[]): TempestCspSource[] {\n const out = [...list];\n if (!sockets) return out;\n for (const { origin } of list) {\n const twin = origin.replace(/^http(s?):/i, \"ws$1:\");\n if (twin !== origin) add(out, twin, `sockets (${origin})`);\n }\n return out;\n }\n\n function defaults(baseOrigin: string | undefined, serve: boolean): Record<string, string[]> {\n const self = baseOrigin ? [\"'self'\", baseOrigin] : [\"'self'\"];\n const policy: Record<string, string[]> = {\n \"default-src\": [...self],\n \"script-src\": wasm ? [...self, \"'wasm-unsafe-eval'\"] : [...self],\n \"style-src\": inlineStyles || serve ? [...self, \"'unsafe-inline'\"] : [...self],\n \"img-src\": [...self, \"data:\", \"blob:\"],\n \"font-src\": [...self, \"data:\"],\n \"media-src\": [...self, \"blob:\"],\n \"connect-src\": [\"'self'\", \"blob:\", \"data:\"],\n \"worker-src\": [...self, \"blob:\"],\n \"manifest-src\": [...self],\n \"object-src\": [\"'none'\"],\n \"base-uri\": [\"'self'\"],\n \"form-action\": [\"'self'\"],\n };\n for (const [name, sources] of Object.entries(extra)) {\n const key = name.toLowerCase();\n if (sources === false) delete policy[key];\n else policy[key] = union(policy[key] ?? [], sources);\n }\n return policy;\n }\n\n const plugin: Plugin = {\n name: \"tempest-csp\",\n configResolved(resolvedConfig) {\n config = resolvedConfig;\n resolved = resolveSources(resolvedConfig);\n },\n transformIndexHtml: {\n order: \"post\",\n handler(source, ctx) {\n const serve = config?.command === \"serve\";\n if (serve && !dev) return source;\n\n const connectSources = withSockets(resolved);\n const base = config?.base ?? \"/\";\n const baseOrigin = ABSOLUTE_URL.test(base) ? cspOrigin(base, \"base\") : undefined;\n if (baseOrigin) add(connectSources, baseOrigin, \"base\");\n const moduleIds = serve ? [] : bundleModuleIds(ctx);\n for (const known of SDK_ORIGINS) {\n if (serve || moduleIds.some((id) => known.module.test(id))) {\n add(connectSources, known.origin, known.from);\n }\n }\n const allSources = connectSources;\n\n const { html, declared = {} } = extractMetaPolicy(source);\n const policy = defaults(baseOrigin, serve);\n if (!(\"script-src\" in declared)) {\n policy[\"script-src\"] = union(\n policy[\"script-src\"] ?? [],\n inlineHashes(html, \"script\"),\n );\n }\n if (\n !(\"style-src\" in declared) &&\n !policy[\"style-src\"]?.includes(\"'unsafe-inline'\")\n ) {\n policy[\"style-src\"] = union(\n policy[\"style-src\"] ?? [],\n inlineHashes(html, \"style\"),\n );\n }\n const merged: Record<string, string[]> = { ...policy };\n for (const [name, sources] of Object.entries(declared)) merged[name] = sources;\n merged[\"connect-src\"] = union(\n merged[\"connect-src\"] ?? [\"'self'\"],\n allSources.map((entry) => entry.origin),\n );\n\n const result: TempestCspPolicy = {\n directives: merged,\n meta: serializeCsp(merged, \"meta\"),\n header: serializeCsp(merged, \"header\"),\n connect: allSources,\n command: serve ? \"serve\" : \"build\",\n };\n\n if (log && config && result.header !== lastLogged) {\n lastLogged = result.header;\n const lines = allSources.map((entry) => ` ${entry.origin} ← ${entry.from}`);\n config.logger.info(\n [\n `tempest-csp: connect-src ${merged[\"connect-src\"].join(\" \")}`,\n ...(lines.length > 0\n ? lines\n : [\" (no cross-origin endpoint: only 'self')\"]),\n ].join(\"\\n\"),\n );\n }\n onPolicy?.(result);\n return insertMeta(html, result.meta);\n },\n },\n };\n\n return plugin as TempestVitePlugin;\n}\n"],"mappings":"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"}
|
package/dist/vite.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./vite/tempest-icons.cjs"),t=require("./vite/create-vite-config.cjs"),n=require("./vite/tempest-pwa-manifest.cjs"),r=require("./vite/tempest-pwa-icons.cjs"),i=require("./vite/tempest-pwa-dev-sw.cjs"),a=require("./vite/tempest-styles.cjs");exports.createViteConfig=t.createViteConfig,exports.tempestIcons=e.tempestIcons,exports.tempestPwaDevSw=i.tempestPwaDevSw,exports.tempestPwaIcons=r.tempestPwaIcons,exports.tempestPwaManifest=n.tempestPwaManifest,exports.tempestStyles=a.tempestStyles;
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./vite/tempest-icons.cjs"),t=require("./vite/create-vite-config.cjs"),n=require("./vite/tempest-pwa-manifest.cjs"),r=require("./vite/tempest-pwa-icons.cjs"),i=require("./vite/tempest-pwa-dev-sw.cjs"),a=require("./vite/tempest-styles.cjs"),o=require("./vite/tempest-csp.cjs");exports.createViteConfig=t.createViteConfig,exports.tempestCsp=o.tempestCsp,exports.tempestIcons=e.tempestIcons,exports.tempestPwaDevSw=i.tempestPwaDevSw,exports.tempestPwaIcons=r.tempestPwaIcons,exports.tempestPwaManifest=n.tempestPwaManifest,exports.tempestStyles=a.tempestStyles;
|
package/dist/vite.d.ts
CHANGED
|
@@ -88,6 +88,151 @@ export declare interface StyleManifest {
|
|
|
88
88
|
components: Record<string, readonly string[]>;
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
+
/**
|
|
92
|
+
* Vite plugin that writes a Content-Security-Policy `<meta>` into `index.html`
|
|
93
|
+
* and derives `connect-src` from the origins the app actually calls.
|
|
94
|
+
*
|
|
95
|
+
* It exists because of how a missing origin fails: the request is blocked
|
|
96
|
+
* before a connection opens, so there is no entry in the Network tab, nothing in
|
|
97
|
+
* the server log, `curl` succeeds, and the app sees `TypeError: Failed to
|
|
98
|
+
* fetch` — indistinguishable from offline, DNS or CORS. And a hand-written CSP
|
|
99
|
+
* plugin is usually `apply: "build"`, so it only fails in production. This
|
|
100
|
+
* plugin closes both ends:
|
|
101
|
+
*
|
|
102
|
+
* - origins come from the same `import.meta.env` the runtime reads
|
|
103
|
+
* ({@link TempestCspOptions.env}), reduced to `scheme://host[:port]`, with
|
|
104
|
+
* empty values skipped — no second list to drift;
|
|
105
|
+
* - the policy is enforced under `vite dev` as well ({@link TempestCspOptions.dev});
|
|
106
|
+
* - the build prints every origin and its provenance.
|
|
107
|
+
*
|
|
108
|
+
* Defaults cover what the SDK's stack needs and nothing more: `'self'`
|
|
109
|
+
* everywhere, `'wasm-unsafe-eval'` for ONNX Runtime, `blob:` for workers, object
|
|
110
|
+
* URLs and media, `data:` for images and fonts, `object-src 'none'`,
|
|
111
|
+
* `base-uri 'self'`, `form-action 'self'`. Inline `<script>` blocks left in
|
|
112
|
+
* `index.html` (a `themeInitScript()` anti-flash script, say) are allowed by
|
|
113
|
+
* SHA-256 hash, never by `'unsafe-inline'`.
|
|
114
|
+
*
|
|
115
|
+
* An existing CSP `<meta>` in `index.html` is merged, not overwritten: every
|
|
116
|
+
* directive it declares is kept verbatim — the plugin never loosens a choice the
|
|
117
|
+
* app wrote — except `connect-src`, which gains the resolved origins, since
|
|
118
|
+
* that is the list the plugin exists to maintain. Directives it does not declare
|
|
119
|
+
* come from the defaults.
|
|
120
|
+
*
|
|
121
|
+
* @param options - See {@link TempestCspOptions}.
|
|
122
|
+
* @returns A Vite plugin, to be placed in `plugins`. It runs as a `post` HTML
|
|
123
|
+
* transform, so a plugin that rewrites inline scripts after it would
|
|
124
|
+
* invalidate the hashes.
|
|
125
|
+
*
|
|
126
|
+
* @example
|
|
127
|
+
* // vite.config.ts
|
|
128
|
+
* import { createViteConfig, tempestCsp } from "tempest-react-sdk/vite";
|
|
129
|
+
*
|
|
130
|
+
* export default createViteConfig({
|
|
131
|
+
* plugins: [tempestCsp({ connect: ["https://sso.example.com/realms/app"] })],
|
|
132
|
+
* });
|
|
133
|
+
*/
|
|
134
|
+
export declare function tempestCsp(options?: TempestCspOptions): TempestVitePlugin;
|
|
135
|
+
|
|
136
|
+
/** Options for {@link tempestCsp}. */
|
|
137
|
+
export declare interface TempestCspOptions {
|
|
138
|
+
/**
|
|
139
|
+
* Endpoints the app calls at runtime, as full URLs. Only the origin
|
|
140
|
+
* (`scheme://host[:port]`) enters `connect-src`: a source expression with a path
|
|
141
|
+
* matches that path alone, so `https://idp/realms/x` would still block
|
|
142
|
+
* `https://idp/realms/x/protocol/openid-connect/token`.
|
|
143
|
+
*
|
|
144
|
+
* Empty entries (`undefined`, `null`, `false`, `""`) are skipped with a build
|
|
145
|
+
* warning instead of leaving a hole in the directive, so passing an env var
|
|
146
|
+
* that is not set is safe. A root-relative path (`/api`) is same-origin and
|
|
147
|
+
* already covered by `'self'`. Anything else that is not an absolute
|
|
148
|
+
* `http(s)`/`ws(s)` URL throws at config time — failing the build, not
|
|
149
|
+
* production. A wildcard host (`https://*.example.com`) is kept as written.
|
|
150
|
+
*/
|
|
151
|
+
connect?: ReadonlyArray<string | null | undefined | false>;
|
|
152
|
+
/**
|
|
153
|
+
* Read origins from the same environment the runtime reads. `true` (the
|
|
154
|
+
* default) adds every variable Vite exposes to `import.meta.env` (the
|
|
155
|
+
* `envPrefix` ones, `VITE_` by default) whose value is an absolute
|
|
156
|
+
* `http(s)`/`ws(s)` URL. This is the structural bridge: an origin added to
|
|
157
|
+
* `.env` for a new runtime call lands in the policy with no second list to
|
|
158
|
+
* keep in sync. Pass a list of names to include exactly those — a name that
|
|
159
|
+
* resolves to nothing is reported as a build warning — or `false` to rely on
|
|
160
|
+
* {@link TempestCspOptions.connect} alone.
|
|
161
|
+
*/
|
|
162
|
+
env?: boolean | readonly string[];
|
|
163
|
+
/**
|
|
164
|
+
* Also allow the WebSocket twin of every `http(s)` origin (`https://api` →
|
|
165
|
+
* `wss://api`). Measured in Chromium (23/09/2026): `connect-src https://api`
|
|
166
|
+
* blocks `new WebSocket("wss://api")`, while `'self'` does cover a same-origin
|
|
167
|
+
* `ws:` — so an app that derives its socket URL from the API URL breaks
|
|
168
|
+
* without this. Default `true`.
|
|
169
|
+
*/
|
|
170
|
+
sockets?: boolean;
|
|
171
|
+
/**
|
|
172
|
+
* Add `'wasm-unsafe-eval'` to `script-src`, which `onnxruntime-web` (the
|
|
173
|
+
* `/vision` and `/tabular` subpaths) needs to compile its WebAssembly. Without
|
|
174
|
+
* it `WebAssembly.compile` throws a `CompileError` naming `script-src`.
|
|
175
|
+
* It grants WebAssembly compilation only, not `eval`. Default `true`.
|
|
176
|
+
*/
|
|
177
|
+
wasm?: boolean;
|
|
178
|
+
/**
|
|
179
|
+
* Add `'unsafe-inline'` to `style-src`. React's `style` prop does **not** need
|
|
180
|
+
* it — it writes through the CSSOM, which CSP does not govern (measured in
|
|
181
|
+
* Chromium, 23/09/2026). What needs it is a `<style>` element or a `style=""`
|
|
182
|
+
* attribute, and the SDK's own `applyTheme` injects a `<style>`. Pass `false`
|
|
183
|
+
* when the app never calls `applyTheme`; inline `<style>` blocks in
|
|
184
|
+
* `index.html` are then hashed instead. Always on under `vite dev`, whose CSS
|
|
185
|
+
* injection is a `<style>` element. Default `true`.
|
|
186
|
+
*/
|
|
187
|
+
inlineStyles?: boolean;
|
|
188
|
+
/**
|
|
189
|
+
* Extra sources per directive, merged into the defaults (`{ "img-src":
|
|
190
|
+
* ["https://tiles.example.com"] }`), or `false` to drop a directive.
|
|
191
|
+
* `frame-ancestors`, `report-uri` and `sandbox` are ignored by browsers when
|
|
192
|
+
* delivered by `<meta>`, so they reach {@link TempestCspPolicy.header} only.
|
|
193
|
+
*/
|
|
194
|
+
directives?: Readonly<Record<string, readonly string[] | false>>;
|
|
195
|
+
/**
|
|
196
|
+
* Apply the policy under `vite dev` too. Default `true`: the failure this
|
|
197
|
+
* plugin exists for is "works in dev, blocked in production", and a policy
|
|
198
|
+
* enforced in dev moves every missing origin to the first page load on the
|
|
199
|
+
* developer's machine. Dev differs from build only in `style-src
|
|
200
|
+
* 'unsafe-inline'` and in allowing every SDK-known origin (see
|
|
201
|
+
* {@link TempestCspPolicy.connect}), because the dev server cannot know which
|
|
202
|
+
* modules the page will load.
|
|
203
|
+
*/
|
|
204
|
+
dev?: boolean;
|
|
205
|
+
/** Print the resolved `connect-src` and where each origin came from. Default `true`. */
|
|
206
|
+
log?: boolean;
|
|
207
|
+
/**
|
|
208
|
+
* Receive the resolved policy — for asserting it in a test, or for writing the
|
|
209
|
+
* same policy as an HTTP header (the only channel for `frame-ancestors`).
|
|
210
|
+
*/
|
|
211
|
+
onPolicy?: (policy: TempestCspPolicy) => void;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/** The policy {@link tempestCsp} resolved for one `index.html`. */
|
|
215
|
+
export declare interface TempestCspPolicy {
|
|
216
|
+
/** Every directive and its sources, including the header-only ones. */
|
|
217
|
+
directives: Record<string, string[]>;
|
|
218
|
+
/** The policy as written into the `<meta http-equiv>` tag. */
|
|
219
|
+
meta: string;
|
|
220
|
+
/** The full policy for a `Content-Security-Policy` response header. */
|
|
221
|
+
header: string;
|
|
222
|
+
/** Every origin added to `connect-src`, with its provenance. */
|
|
223
|
+
connect: TempestCspSource[];
|
|
224
|
+
/** `"serve"` under `vite dev`, `"build"` under `vite build`. */
|
|
225
|
+
command: "serve" | "build";
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/** One origin in `connect-src` and the input that put it there. */
|
|
229
|
+
export declare interface TempestCspSource {
|
|
230
|
+
/** The source expression, e.g. `https://api.example.com`. */
|
|
231
|
+
origin: string;
|
|
232
|
+
/** Where it came from: `env VITE_API_URL`, `connect[0]`, `sockets`, `base` or an SDK module. */
|
|
233
|
+
from: string;
|
|
234
|
+
}
|
|
235
|
+
|
|
91
236
|
/**
|
|
92
237
|
* Generate a static icon registry from the slugs your source mentions.
|
|
93
238
|
*
|
package/dist/vite.js
CHANGED
|
@@ -4,4 +4,5 @@ import { tempestPwaManifest as n } from "./vite/tempest-pwa-manifest.js";
|
|
|
4
4
|
import { tempestPwaIcons as r } from "./vite/tempest-pwa-icons.js";
|
|
5
5
|
import { tempestPwaDevSw as i } from "./vite/tempest-pwa-dev-sw.js";
|
|
6
6
|
import { tempestStyles as a } from "./vite/tempest-styles.js";
|
|
7
|
-
|
|
7
|
+
import { tempestCsp as o } from "./vite/tempest-csp.js";
|
|
8
|
+
export { t as createViteConfig, o as tempestCsp, e as tempestIcons, i as tempestPwaDevSw, r as tempestPwaIcons, n as tempestPwaManifest, a as tempestStyles };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tempest-react-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.68.0",
|
|
4
4
|
"description": "SDK público da Tempest com componentes, hooks e integrações para projetos React.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -110,7 +110,7 @@
|
|
|
110
110
|
},
|
|
111
111
|
"scripts": {
|
|
112
112
|
"dev": "vite build --watch",
|
|
113
|
-
"build": "node scripts/gen-scoped-reset.mjs && tsc -b tsconfig.build.json && node --max-old-space-size=
|
|
113
|
+
"build": "node scripts/gen-scoped-reset.mjs && tsc -b tsconfig.build.json && node --max-old-space-size=8192 ./node_modules/vite/bin/vite.js build && node scripts/split-css.mjs && node scripts/gen-style-manifest.mjs && node scripts/copy-css-assets.mjs && node scripts/link-component-css.mjs",
|
|
114
114
|
"postbuild": "node scripts/check-dist-guards.mjs",
|
|
115
115
|
"typecheck": "tsc -b tsconfig.json --noEmit",
|
|
116
116
|
"lint": "eslint .",
|
package/template/vite.config.ts
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
|
-
import { createViteConfig } from "tempest-react-sdk/vite";
|
|
1
|
+
import { createViteConfig, tempestCsp } from "tempest-react-sdk/vite";
|
|
2
2
|
|
|
3
3
|
// One call wires `@vitejs/plugin-react`, the `@` → `src` alias and dev-server
|
|
4
4
|
// defaults. Override anything (proxy, port, build target) inline.
|
|
5
|
+
//
|
|
6
|
+
// `tempestCsp()` writes a Content-Security-Policy into index.html, in dev and in
|
|
7
|
+
// the build alike. Its `connect-src` comes from every URL-valued VITE_* variable
|
|
8
|
+
// (VITE_API_URL, …), so an origin you call at runtime has to live in `.env` or
|
|
9
|
+
// in `tempestCsp({ connect: [...] })` — a missing one fails as
|
|
10
|
+
// `TypeError: Failed to fetch`, with a CSP error in the browser console.
|
|
5
11
|
export default createViteConfig({
|
|
6
12
|
// proxy: { "/api": "http://127.0.0.1:8000" },
|
|
13
|
+
plugins: [tempestCsp()],
|
|
7
14
|
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createViteConfig,
|
|
3
|
+
tempestCsp,
|
|
3
4
|
tempestPwaDevSw,
|
|
4
5
|
tempestPwaIcons,
|
|
5
6
|
tempestPwaManifest,
|
|
@@ -11,11 +12,16 @@ import {
|
|
|
11
12
|
// - tempestPwaManifest — emits dist/precache-manifest.json (it sees the icons
|
|
12
13
|
// above because they're emitted first), for offline app-shell precaching.
|
|
13
14
|
// - tempestPwaDevSw — serves /sw.js (and an empty manifest) under `npm run dev`.
|
|
15
|
+
// - tempestCsp — writes the Content-Security-Policy into index.html, in dev
|
|
16
|
+
// and build alike; `connect-src` comes from every URL-valued VITE_* variable, so
|
|
17
|
+
// an origin the app calls at runtime has to live in `.env` or in
|
|
18
|
+
// `tempestCsp({ connect: [...] })`.
|
|
14
19
|
export default createViteConfig({
|
|
15
20
|
// proxy: { "/api": "http://127.0.0.1:8000" },
|
|
16
21
|
plugins: [
|
|
17
22
|
tempestPwaIcons({ source: "public/icon.svg", appleSplash: true }),
|
|
18
23
|
tempestPwaManifest({ additionalUrls: ["/manifest.webmanifest", "/icon.svg"] }),
|
|
19
24
|
tempestPwaDevSw(),
|
|
25
|
+
tempestCsp(),
|
|
20
26
|
],
|
|
21
27
|
});
|