tempest-react-sdk 0.82.0 → 0.83.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.
Files changed (41) hide show
  1. package/README.md +6 -6
  2. package/bin/lib/openapi/generate.hostile.test.mjs +91 -0
  3. package/bin/lib/openapi/generate.mjs +42 -3
  4. package/dist/auth/create-tempest-auth.cjs +1 -1
  5. package/dist/auth/create-tempest-auth.cjs.map +1 -1
  6. package/dist/auth/create-tempest-auth.js +135 -132
  7. package/dist/auth/create-tempest-auth.js.map +1 -1
  8. package/dist/http/resumable-upload.cjs +1 -1
  9. package/dist/http/resumable-upload.cjs.map +1 -1
  10. package/dist/http/resumable-upload.js +124 -75
  11. package/dist/http/resumable-upload.js.map +1 -1
  12. package/dist/http/upload-with-progress.cjs +1 -1
  13. package/dist/http/upload-with-progress.cjs.map +1 -1
  14. package/dist/http/upload-with-progress.js +100 -44
  15. package/dist/http/upload-with-progress.js.map +1 -1
  16. package/dist/query/offline-persistence.cjs +1 -1
  17. package/dist/query/offline-persistence.cjs.map +1 -1
  18. package/dist/query/offline-persistence.js +57 -44
  19. package/dist/query/offline-persistence.js.map +1 -1
  20. package/dist/sw/cache.cjs +1 -1
  21. package/dist/sw/cache.cjs.map +1 -1
  22. package/dist/sw/cache.js +82 -32
  23. package/dist/sw/cache.js.map +1 -1
  24. package/dist/sw.d.ts +21 -1
  25. package/dist/tempest-react-sdk.d.ts +90 -6
  26. package/dist/vite/base-url.cjs +1 -1
  27. package/dist/vite/base-url.cjs.map +1 -1
  28. package/dist/vite/base-url.js +5 -1
  29. package/dist/vite/base-url.js.map +1 -1
  30. package/dist/vite/tempest-pwa-dev-sw.cjs +1 -1
  31. package/dist/vite/tempest-pwa-dev-sw.cjs.map +1 -1
  32. package/dist/vite/tempest-pwa-dev-sw.js +96 -31
  33. package/dist/vite/tempest-pwa-dev-sw.js.map +1 -1
  34. package/dist/vite/tempest-pwa-manifest.cjs +2 -2
  35. package/dist/vite/tempest-pwa-manifest.cjs.map +1 -1
  36. package/dist/vite/tempest-pwa-manifest.js +32 -16
  37. package/dist/vite/tempest-pwa-manifest.js.map +1 -1
  38. package/dist/vite.cjs +1 -1
  39. package/dist/vite.d.ts +63 -12
  40. package/dist/vite.js +4 -4
  41. package/package.json +1 -1
package/README.md CHANGED
@@ -276,7 +276,7 @@ Every module is re-exported from the package root — `import { Button, useDebou
276
276
  | `router` _(dep: `react-router`)_ | `defineRoutes`, `AppRouter`, `RouteGuard`, + re-exports of React Router's whole client-side surface (`Link`, `NavLink`, `Outlet`, `useNavigate`, `useParams`, `useSearchParams`, `matchPath`, `generatePath`, `createBrowserRouter`, `useLoaderData`, …; its `Form` as `RouterForm`), types: `TempestRouteObject`, `RouterKind`, `AppRouterProps`, `RouteGuardProps` |
277
277
  | `store` _(dep: `zustand`)_ | `createStore`, `createSelectors`, types: `CreateStoreOptions`, `CreateStorePersistOptions`, `WithSelectors` |
278
278
  | `app` | `AppProviders` (composes `ErrorBoundary` → `QueryProvider` → `ThemeProvider` → `I18nProvider`), type: `AppProvidersProps` |
279
- | `vite` _(subpath `tempest-react-sdk/vite`)_ | `createViteConfig`, `tempestPwaManifest` (emits `precache-manifest.json` for offline precache), `tempestPwaIcons` (generates the PNG icon set from one SVG via `sharp`), `tempestPwaDevSw` (serves the SW under `npm run dev`), `tempestCsp` (writes a Content-Security-Policy whose `connect-src` is derived from the app's `VITE_*` URLs, enforced in dev too), `tempestPwaIcons({ appleSplash })` (Apple splash screens), types: `CreateViteConfigOptions`, `ProxyEntry`, `TempestViteConfig`, `TempestPwaManifestOptions`, `TempestPwaIconsOptions`, `TempestPwaDevSwOptions`, `TempestCspOptions`, `TempestCspPolicy`, `TempestCspSource`, `AppleSplashSpec`, `TempestVitePlugin` |
279
+ | `vite` _(subpath `tempest-react-sdk/vite`)_ | `createViteConfig`, `tempestPwaManifest` (emits `precache-manifest.json` for offline precache), `tempestPwaIcons` (generates the PNG icon set from one SVG via `sharp`), `tempestPwaDevSw` (serves the SW under `npm run dev` and bundles it into `vite build`), `tempestCsp` (writes a Content-Security-Policy whose `connect-src` is derived from the app's `VITE_*` URLs, enforced in dev too), `tempestPwaIcons({ appleSplash })` (Apple splash screens), types: `CreateViteConfigOptions`, `ProxyEntry`, `TempestViteConfig`, `TempestPwaManifestOptions`, `TempestPwaIconsOptions`, `TempestPwaDevSwOptions`, `TempestCspOptions`, `TempestCspPolicy`, `TempestCspSource`, `AppleSplashSpec`, `TempestVitePlugin` |
280
280
  | `forms` _(peer: `zod`, `react-hook-form`)_ | `validateForm`, `zodResolver`, `useZodForm`, `validateCPF`, `validateCNPJ`, `formatCEP`, `formatCNPJ`, `unmask`, `CPFInput`, `CNPJInput`, `PhoneInput`, `CEPInput`, `MoneyInput`, `useViaCEP` |
281
281
  | `sse` | `createEventStream`, `useEventStream` (`schema` valida cada frame — zod/valibot/arktype via Standard Schema — e `onParseError` para frame que não é JSON) |
282
282
  | `ws` | `createWebSocket` (handshake timeout, silence watchdog, jittered backoff, `navigator.onLine` suspend, close-code classification, `opened` promise), `useWebSocket`, `schema` + `onValidationError` (frame fora do contrato não é entregue; o `pong` do heartbeat continua saindo), `onParseError` (frame que não é JSON: descarta em vez de entregar `string` como `T`), `isRejectionCloseCode`, `HEARTBEAT_CLOSE_CODE` |
@@ -312,7 +312,7 @@ Every module is re-exported from the package root — `import { Button, useDebou
312
312
  | `router` _(dep: `react-router`)_ | `defineRoutes`, `AppRouter`, `RouteGuard`, + re-exports of React Router's whole client-side surface (`Link`, `NavLink`, `Outlet`, `useNavigate`, `useParams`, `useSearchParams`, `matchPath`, `generatePath`, `createBrowserRouter`, `useLoaderData`, …; its `Form` as `RouterForm`), types: `TempestRouteObject`, `RouterKind`, `AppRouterProps`, `RouteGuardProps` |
313
313
  | `store` _(dep: `zustand`)_ | `createStore`, `createSelectors`, types: `CreateStoreOptions`, `CreateStorePersistOptions`, `WithSelectors` |
314
314
  | `app` | `AppProviders` (composes `ErrorBoundary` → `QueryProvider` → `ThemeProvider` → `I18nProvider`), type: `AppProvidersProps` |
315
- | `vite` _(subpath `tempest-react-sdk/vite`)_ | `createViteConfig`, `tempestPwaManifest` (emits `precache-manifest.json` for offline precache), `tempestPwaIcons` (generates the PNG icon set from one SVG via `sharp`), `tempestPwaDevSw` (serves the SW under `npm run dev`), `tempestPwaIcons({ appleSplash })` (Apple splash screens), types: `CreateViteConfigOptions`, `ProxyEntry`, `TempestViteConfig`, `TempestPwaManifestOptions`, `TempestPwaIconsOptions`, `TempestPwaDevSwOptions`, `AppleSplashSpec`, `TempestVitePlugin` |
315
+ | `vite` _(subpath `tempest-react-sdk/vite`)_ | `createViteConfig`, `tempestPwaManifest` (emits `precache-manifest.json` for offline precache), `tempestPwaIcons` (generates the PNG icon set from one SVG via `sharp`), `tempestPwaDevSw` (serves the SW under `npm run dev` and bundles it into `vite build`), `tempestPwaIcons({ appleSplash })` (Apple splash screens), types: `CreateViteConfigOptions`, `ProxyEntry`, `TempestViteConfig`, `TempestPwaManifestOptions`, `TempestPwaIconsOptions`, `TempestPwaDevSwOptions`, `AppleSplashSpec`, `TempestVitePlugin` |
316
316
  | `forms` _(peer: `zod`, `react-hook-form`)_ | `validateForm`, `zodResolver`, `useZodForm`, `validateCPF`, `validateCNPJ`, `formatCEP`, `formatCNPJ`, `unmask`, `CPFInput`, `CNPJInput`, `PhoneInput`, `CEPInput`, `MoneyInput`, `useViaCEP` |
317
317
  | `sse` | `createEventStream`, `useEventStream` (`schema` valida cada frame — zod/valibot/arktype via Standard Schema — e `onParseError` para frame que não é JSON) |
318
318
  | `ws` | `createWebSocket` (handshake timeout, silence watchdog, jittered backoff, `navigator.onLine` suspend, close-code classification, `opened` promise), `useWebSocket`, `schema` + `onValidationError` (frame fora do contrato não é entregue; o `pong` do heartbeat continua saindo), `onParseError` (frame que não é JSON: descarta em vez de entregar `string` como `T`), `isRejectionCloseCode`, `HEARTBEAT_CLOSE_CODE` |
@@ -343,7 +343,7 @@ Every module is re-exported from the package root — `import { Button, useDebou
343
343
 
344
344
  Full per-module docs are published as a bilingual MkDocs site on GitHub Pages — **[Português (BR)](https://mauriciobenjamin700.github.io/tempest-react-sdk/)** / **[English (US)](https://mauriciobenjamin700.github.io/tempest-react-sdk/en/)** (one page per module + draw.io diagrams in [`docs/diagrams/`](./docs/diagrams)). The source markdown lives in [`docs/`](./docs) (PT-BR base files + `.en.md` translations).
345
345
 
346
- > **Local preview:** `pip install -r docs/requirements.txt && mkdocs serve` (the published site is built and deployed automatically by `.github/workflows/docs.yml`).
346
+ > **Local preview:** `pip install -r docs/requirements.txt && mkdocs serve` (the published site is built and pushed to `gh-pages` by `make docs-deploy`, from the maintainer's machine).
347
347
 
348
348
  A demo app exercising every module lives in [`examples/gallery`](./examples/gallery) — `cd examples/gallery && npm install && npm run dev`.
349
349
 
@@ -1321,7 +1321,7 @@ installNotificationClickHandler({
1321
1321
  });
1322
1322
  ```
1323
1323
 
1324
- The SDK does **not** ship the worker file or the bundler config — pair it with `vite-plugin-pwa` (`injectManifest`) or a separately bundled worker.
1324
+ The SDK does **not** ship the worker file — you write `src/sw.ts`. `tempestPwaDevSw()` from `tempest-react-sdk/vite` bundles it to `dist/sw.js` in `vite build` (and serves it in dev), and `tempestPwaManifest()` fails the build when the manifest would ship without a worker. See [Vite Config › PWA](https://mauriciobenjamin700.github.io/tempest-react-sdk/en/vite-config/#pwa-the-service-worker-in-the-production-build).
1325
1325
 
1326
1326
  ### Audio playback recipe
1327
1327
 
@@ -1999,10 +1999,10 @@ Two commands, with the release PR's review in between:
1999
1999
 
2000
2000
  ```bash
2001
2001
  make release TAG=X.Y.Z # opens the release PR (no tag, no publish)
2002
- make release-tag TAG=X.Y.Z # after the merge: tags the merge commit and pushes it
2002
+ make release-tag TAG=X.Y.Z # after the merge: build + smokes + npm publish + tag + GitHub Release
2003
2003
  ```
2004
2004
 
2005
- The tag push runs `.github/workflows/release-npm.yml`, which publishes through npm Trusted Publishing (OIDC, with provenance) — no npm token anywhere, and no local publish (`make publish` refuses). Pipeline and the repository owner's settings checklist: [Release pipeline](https://mauriciobenjamin700.github.io/tempest-react-sdk/en/release/).
2005
+ There is no GitHub Actions in this repository: `make release-tag` runs the ref guard, builds and smokes the merged commit, publishes the smoked tarball with the maintainer's npm credential (no provenance — that needs a CI runner's OIDC), pushes the tag and creates the GitHub Release. A bare `npm publish` is refused (`make publish` exits 1). Pipeline and the repository owner's settings checklist: [Release pipeline](https://mauriciobenjamin700.github.io/tempest-react-sdk/en/release/).
2006
2006
 
2007
2007
  Tags follow `vMAJOR.MINOR.PATCH`. CSS tokens (`--tempest-*`) are treated as public API — renames bump the minor (or major, on removal).
2008
2008
 
@@ -724,6 +724,97 @@ describe("generate — spec text that would leave its place in the output (#533)
724
724
  );
725
725
  });
726
726
 
727
+ const KEYWORD_NAMES = [
728
+ "pattern",
729
+ "default",
730
+ "enum",
731
+ "items",
732
+ "properties",
733
+ "additionalProperties",
734
+ "allOf",
735
+ "anyOf",
736
+ "oneOf",
737
+ "required",
738
+ "type",
739
+ "$ref",
740
+ ];
741
+
742
+ /**
743
+ * A `Body` schema with one property, named `name`, whose schema is `inner`.
744
+ *
745
+ * @param {string} name - The property name, often a JSON Schema keyword.
746
+ * @param {object} inner - The property's schema.
747
+ * @returns {object} The spec.
748
+ */
749
+ function keywordNamedProperty(name, inner) {
750
+ return spec(POST_BODY, {
751
+ Body: { type: "object", required: [name], properties: { [name]: inner } },
752
+ });
753
+ }
754
+
755
+ it.each(KEYWORD_NAMES)(
756
+ "a property named %s holding a required __proto__ is refused, naming where it sits (#562)",
757
+ (name) => {
758
+ const inner = JSON.parse(
759
+ '{"type":"object","required":["__proto__"],"properties":{"__proto__":{"type":"string"}}}',
760
+ );
761
+ expect(() => generate(keywordNamedProperty(name, inner))).toThrow(
762
+ `schema "Body" at properties.${name}: a property named "__proto__" cannot be validated`,
763
+ );
764
+ },
765
+ );
766
+
767
+ it.each(KEYWORD_NAMES)(
768
+ "a property named %s whose schema has a __proto__ default is refused at that property (#562)",
769
+ (name) => {
770
+ const inner = JSON.parse('{"type":"object","default":{"__proto__":{"admin":true}}}');
771
+ expect(() => generate(keywordNamedProperty(name, inner))).toThrow(
772
+ `schema "Body" at properties.${name}: the default value has a "__proto__" key`,
773
+ );
774
+ },
775
+ );
776
+
777
+ it.each(KEYWORD_NAMES)(
778
+ "an invalid regex under a property named %s fails generation (#562)",
779
+ (name) => {
780
+ const inner = {
781
+ type: "object",
782
+ properties: { code: { type: "string", pattern: "(" } },
783
+ };
784
+ expect(() => generate(keywordNamedProperty(name, inner))).toThrow(
785
+ `schema "Body" at properties.${name}.properties.code: pattern "(" is not a valid`,
786
+ );
787
+ },
788
+ );
789
+
790
+ it.each(KEYWORD_NAMES)(
791
+ "a nested-quantifier pattern under a property named %s comes back as a warning (#562)",
792
+ (name) => {
793
+ const inner = { type: "string", pattern: "^(a+)+$" };
794
+ const { warnings } = generate(keywordNamedProperty(name, inner));
795
+ expect(warnings).toHaveLength(1);
796
+ expect(warnings[0]).toContain(`schema "Body" at properties.${name}: pattern`);
797
+ },
798
+ );
799
+
800
+ it("properties named after keywords generate, typecheck and are each validated (#562)", () => {
801
+ const properties = Object.fromEntries(
802
+ KEYWORD_NAMES.map((name) => [name, { type: "string", minLength: 2 }]),
803
+ );
804
+ const generated = spec(POST_BODY, {
805
+ Body: { type: "object", required: KEYWORD_NAMES, properties },
806
+ });
807
+ const { files, warnings } = generate(generated);
808
+ expect(warnings).toEqual([]);
809
+ expect(typecheck(files, "t")).toEqual([]);
810
+ const schemas = evaluate(files["t/schemas.ts"], vm.createContext({}), {});
811
+ const valid = Object.fromEntries(KEYWORD_NAMES.map((name) => [name, "ok"]));
812
+ expect(schemas.BodySchema.safeParse(valid).success).toBe(true);
813
+ for (const name of KEYWORD_NAMES) {
814
+ expect(schemas.BodySchema.safeParse({ ...valid, [name]: {} }).success).toBe(false);
815
+ }
816
+ });
817
+
727
818
  it("properties called constructor or prototype still generate and are validated", () => {
728
819
  const { files } = generate(
729
820
  spec(POST_BODY, {
@@ -175,6 +175,23 @@ function hasProtoKey(value) {
175
175
  return Object.values(value).some(hasProtoKey);
176
176
  }
177
177
 
178
+ /**
179
+ * Keywords whose value maps a name the spec author picked to a sub-schema.
180
+ *
181
+ * Their keys are data, not keywords: a property may be called `pattern`,
182
+ * `default` or `$ref`. {@link checkSchema} walks into every entry as a schema
183
+ * and never reads the map itself as one — it used to, so a property named
184
+ * `pattern` or `default` was skipped as if it were the keyword, and its schema
185
+ * escaped every check below (#562).
186
+ */
187
+ const SCHEMA_MAPS = new Set([
188
+ "properties",
189
+ "patternProperties",
190
+ "dependentSchemas",
191
+ "$defs",
192
+ "definitions",
193
+ ]);
194
+
178
195
  /**
179
196
  * Check a component schema before it is turned into Zod source.
180
197
  *
@@ -194,6 +211,11 @@ function hasProtoKey(value) {
194
211
  *
195
212
  * Every failure names the schema and where in it the problem sits.
196
213
  *
214
+ * The walk reaches every sub-schema. Only a schema's own `pattern` string and
215
+ * `default` value are not walked as schemas — the checks above read them — and
216
+ * the entries of a {@link SCHEMA_MAPS} keyword are always walked, whatever the
217
+ * property is called (#562).
218
+ *
197
219
  * @param {unknown} node - The schema (sub)node.
198
220
  * @param {string} schema - The component schema name, for the messages.
199
221
  * @param {string[]} warnings - Collects the non-fatal findings.
@@ -233,15 +255,32 @@ function checkSchema(node, schema, warnings, at = []) {
233
255
  );
234
256
  }
235
257
  for (const [key, value] of Object.entries(node)) {
236
- if (key !== "pattern" && key !== "default")
258
+ if (SCHEMA_MAPS.has(key) && value && typeof value === "object" && !Array.isArray(value)) {
259
+ for (const [name, sub] of Object.entries(value)) {
260
+ checkSchema(sub, schema, warnings, [...at, key, name]);
261
+ }
262
+ } else if (key !== "pattern" && key !== "default") {
237
263
  checkSchema(value, schema, warnings, [...at, key]);
264
+ }
238
265
  }
239
266
  }
240
267
 
241
- /** Collect component-schema names referenced (transitively) by a schema node. */
268
+ /**
269
+ * Collect component-schema names referenced (transitively) by a schema node.
270
+ *
271
+ * Only a string `$ref` is a reference: inside `properties`, `$ref` can be a
272
+ * property name whose value is a schema, and reading it as a reference crashed
273
+ * the run with `ref.lastIndexOf is not a function` (#562).
274
+ *
275
+ * @param {unknown} node - The schema (sub)node.
276
+ * @param {Record<string, object>} schemas - `components.schemas`.
277
+ * @param {Set<string>} [acc] - Collects the referenced names.
278
+ * @param {Set<string>} [seen] - Names already expanded, against cycles.
279
+ * @returns {Set<string>} `acc`.
280
+ */
242
281
  function collectRefs(node, schemas, acc = new Set(), seen = new Set()) {
243
282
  if (!node || typeof node !== "object") return acc;
244
- if (node.$ref) {
283
+ if (typeof node.$ref === "string") {
245
284
  const name = refName(node.$ref);
246
285
  if (!acc.has(name)) {
247
286
  acc.add(name);
@@ -1,2 +1,2 @@
1
- const e=require("../utils/dev-mode.cjs"),t=require("../http/errors.cjs"),n=require("../http/api-client.cjs"),r=require("./session-identity.cjs"),i=require("./create-auth-store.cjs"),a=require("./refresh-queue.cjs");var o=1e4,s=1e4;function c(e,t){return e.length===0?Promise.resolve():new Promise(n=>{let r=setTimeout(n,t);Promise.allSettled(e).then(()=>{clearTimeout(r),n()})})}function ee(){let e=globalThis.crypto;return typeof e?.randomUUID==`function`?e.randomUUID():`${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`}var te=`unlineaged`,l=new Set;function ne(t){e.isDevBuild()&&!l.has(t)&&(l.add(t),console.warn(`[tempest-react-sdk] createTempestAuth: withCredentials is set without logoutPath. logout() cannot clear the HttpOnly refresh cookie, so the next refresh() — the boot restore — signs the same account back in. Pass logoutPath (the backend's cookie logout route) so the server revokes it.`))}function re(e){let t=e??{};return{token:t.access_token,refreshToken:t.refresh_token}}function u(l){let{baseURL:u,prefix:d,loginPath:ie=`/api/auth/login`,refreshPath:ae=`/api/auth/refresh`,logoutPath:f,onLogoutError:oe,mePath:p,storeName:m=`tempest-auth`,storage:h=`local`,withCredentials:g=!1,csrf:_,fetcher:se,parseTokens:v=re,parseUser:ce,refreshBody:y=e=>e?{refresh_token:e}:void 0,retry:le,redirectTo:b,onSessionEnd:x}=l;g&&!f&&ne(m);let S=i.createAuthStore({name:m,storage:h}),C=`${m}-refresh`;function w(){return typeof window>`u`?null:h===`session`?window.sessionStorage:window.localStorage}function T(){return w()?.getItem(C)??null}let E=T();function D(e){E=e;let t=w();t&&(e?t.setItem(C,e):t.removeItem(C))}let O=`${m}-lineage`,ue=`tempest-react-sdk:${m}:refresh`,k=w()?.getItem(O)??null;function A(e){k=e;let t=w();t&&(e?t.setItem(O,e):t.removeItem(O))}let j=()=>S.getState(),M=()=>j().token;async function de(){if(!p)return j().user;let e=N,n;try{n=await Q.get(p,{skipAuthRetry:!0})}catch(n){throw N===e?n:new t.SessionSupersededError}if(N!==e||!Z().continues)throw new t.SessionSupersededError;return j().setUser(n),n}let N=0,P=Promise.resolve(),F=0,I=new Set,L=new Set,R=new Set,z=new Set;function B(e){let t=e.then(()=>void 0,()=>void 0);return R.add(t),t.then(()=>R.delete(t)),e}function fe(t){e.isDevBuild()&&console.warn(`[tempest-react-sdk] createTempestAuth: logoutPath revocation failed`,t);try{oe?.(t)}catch{return}}function V(e){let t=(async()=>{try{await e()}catch(e){fe(e)}})();return L.add(t),t.then(()=>L.delete(t)),t}function H(e,t){f&&!g&&t&&V(()=>U(e).post(f,{body:y(t)}))}function U(e){return n.createApiClient({baseURL:u,prefix:d,withCredentials:g,csrf:_,fetcher:se,getToken:()=>e})}function W(){N+=1,K=a.createRefreshQueue(G)}async function G(){let e=M(),t=N;z.add(t);try{await pe(()=>me(e))}finally{z.delete(t)}}async function pe(e){let t=h===`local`&&typeof navigator<`u`?navigator.locks:void 0;if(!t)return e();let n=new AbortController,r=setTimeout(()=>n.abort(),s),i=!1;try{await t.request(ue,{signal:n.signal},async()=>{i=!0,clearTimeout(r),await e()})}catch(t){if(i)throw t;clearTimeout(r),await e()}}async function me(e){let n=e!==null||j().user!==null||E!==null,r=Z();if(!n&&M()!==null)return;if(!r.continues)throw new t.SessionSupersededError;if(M()!==e)return;let i=N,a;try{a=await B(Q.post(ae,{body:y(T()),skipAuth:!0}))}catch(e){throw N===i?e:new t.SessionSupersededError}let{token:o,refreshToken:s}=v(a);if(N!==i||!Z().continues)throw H(o??null,s),new t.SessionSupersededError;j().setToken(o),s&&D(s)}let K=a.createRefreshQueue(G);function q(e){let t=M();return t===null?!1:e.token===null||e.token!==t}function J(){return M()===null&&T()===null}async function he(){if(!(I.size>0&&F===0&&(await Promise.allSettled([...I]),M()!==null)))throw Error(`anonymous 401 with no session`)}async function ge(e){if(e.epoch!==null&&e.epoch!==N)throw new t.SessionSupersededError;if(q(e)){if(e.token===null)throw Error(`anonymous 401 under a live session`)}else return e.token===null&&J()?z.has(N)?K():he():K()}async function _e(e,n){if(n.epoch!==null&&n.epoch!==N||!Z().continues)throw new t.SessionSupersededError;q(n)||n.token===null&&J()||await Ce()}async function ve(e){L.size>0&&await c([...L],o);let t=ye(e);I.add(t);try{return await t}finally{I.delete(t)}}async function ye(e){let n=N,r=await B(Q.post(ie,{body:e,skipAuth:!0})),{token:i,refreshToken:a}=v(r);if(N!==n)throw H(i??null,a),new t.SessionSupersededError;H(M(),T()??void 0);let{done:o}=Y(`switch`),s=N;if(await o.catch(()=>void 0),N!==s)throw new t.SessionSupersededError;A(ee()),j().setToken(i),D(a??null);let c=ce?.(r);return c==null?de():(j().setUser(c),c)}function Y(e){let{user:t,token:n}=j(),r=n!==null||t!==null||T()!==null;return(r||e!==`expired`)&&W(),j().logout(),D(null),A(null),r?{live:r,done:X({reason:e,user:t,token:n,elsewhere:!1})}:{live:r,done:e===`switch`||F===0?P:Promise.resolve()}}function X(e){if(!x)return Promise.resolve();let t=U(e.token);F+=1;let n=(async()=>{try{await x({...e,api:t})}finally{--F}})();return P=n.then(()=>void 0,()=>void 0),n.then(()=>void 0)}function be(){let e=w();if(!e)return null;let t=e.getItem(C),n=e.getItem(O),r=e.getItem(m);if(!r)return{user:null,token:null,refreshToken:t,lineage:n};try{let e=JSON.parse(r);return{user:e.state?.user??null,token:e.state?.token??null,refreshToken:t,lineage:n}}catch{return{user:null,token:null,refreshToken:t,lineage:n}}}function Z(){let e=be(),{user:t,token:n}=j();if(e===null)return{continues:!0,done:Promise.resolve()};let r=JSON.stringify(e.user)===JSON.stringify(t);if(e.token===n&&e.refreshToken===E&&e.lineage===k&&r)return{continues:!0,done:Promise.resolve()};let i=e.lineage!==null||k!==null?e.lineage===k:e.token===n||n!==null&&r,a=e.token!==null&&i,o=n!==null||t!==null||E!==null;if(a||W(),E=e.refreshToken,k=e.lineage,S.setState({user:e.user,token:e.token,isAuthenticated:e.token!==null}),a||!o)return{continues:a,done:Promise.resolve()};let s=X({reason:e.token===null?`logout`:`switch`,user:t,token:n,elsewhere:!0});return s.catch(()=>void 0),{continues:a,done:s}}function xe(){if(!f)return null;let e=f,{token:t,user:n}=j(),r=T();if(!g&&t===null&&n===null&&r===null)return null;let i=g?[...R]:[];return V(async()=>(await c(i,o),U(t).post(e,{body:y(r)})))}function Se(){let e=Z();if(!e.continues)return e.done;let t=xe(),{done:n}=Y(`logout`);return t?Promise.all([t,n]).then(()=>void 0):n}async function Ce(){let{live:e,done:t}=Y(`expired`);await t.catch(()=>void 0),e&&b&&typeof window<`u`&&window.location.assign(b)}let Q=n.createApiClient({baseURL:u,prefix:d,withCredentials:g,csrf:_,fetcher:se,getToken:M,getSessionEpoch:()=>N,refresh:ge,retry:le,onUnauthorized:_e}),we=()=>K(),Te=()=>M()===null?null:k??te,Ee={current:Te,subscribe:e=>S.subscribe(e)};r.bindSessionIdentity(Q,Ee),r.bindSessionIdentity(M,Ee);function De(e){e.storageArea===w()&&(e.key===null||e.key===m||e.key===C||e.key===O)&&Z()}let $=typeof window>`u`?null:window;return $?.addEventListener(`storage`,De),{useAuthStore:S,api:Q,login:ve,logout:Se,refresh:we,getToken:M,sessionKey:Te,dispose:()=>$?.removeEventListener(`storage`,De)}}exports.createTempestAuth=u;
1
+ const e=require("../utils/dev-mode.cjs"),t=require("../http/errors.cjs"),n=require("../http/api-client.cjs"),r=require("./session-identity.cjs"),i=require("./create-auth-store.cjs"),a=require("./refresh-queue.cjs");var o=1e4,s=1e4;function c(e,t){return e.length===0?Promise.resolve():new Promise(n=>{let r=setTimeout(n,t);Promise.allSettled(e).then(()=>{clearTimeout(r),n()})})}function l(){let e=globalThis.crypto;return typeof e?.randomUUID==`function`?e.randomUUID():`${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`}var u=new Set;function ee(t){e.isDevBuild()&&!u.has(t)&&(u.add(t),console.warn(`[tempest-react-sdk] createTempestAuth: withCredentials is set without logoutPath. logout() cannot clear the HttpOnly refresh cookie, so the next refresh() — the boot restore — signs the same account back in. Pass logoutPath (the backend's cookie logout route) so the server revokes it.`))}function te(e){let t=e??{};return{token:t.access_token,refreshToken:t.refresh_token}}function d(u){let{baseURL:d,prefix:f,loginPath:ne=`/api/auth/login`,refreshPath:re=`/api/auth/refresh`,logoutPath:p,onLogoutError:ie,mePath:m,storeName:h=`tempest-auth`,storage:g=`local`,withCredentials:_=!1,csrf:ae,fetcher:v,parseTokens:y=te,parseUser:oe,refreshBody:b=e=>e?{refresh_token:e}:void 0,retry:se,redirectTo:x,onSessionEnd:S}=u;_&&!p&&ee(h);let C=i.createAuthStore({name:h,storage:g}),w=`${h}-refresh`;function T(){return typeof window>`u`?null:g===`session`?window.sessionStorage:window.localStorage}function E(){return T()?.getItem(w)??null}let D=E();function O(e){D=e;let t=T();t&&(e?t.setItem(w,e):t.removeItem(w))}let k=`${h}-lineage`,ce=`tempest-react-sdk:${h}:refresh`,A=T()?.getItem(k)??null;function j(e){A=e;let t=T();t&&(e?t.setItem(k,e):t.removeItem(k))}let M=()=>C.getState(),N=()=>M().token;async function le(){if(!m)return M().user;let e=P,n;try{n=await Q.get(m,{skipAuthRetry:!0})}catch(n){throw P===e?n:new t.SessionSupersededError}if(P!==e||!Z().continues)throw new t.SessionSupersededError;return M().setUser(n),n}let P=0,ue=Promise.resolve(),F=0,I=new Set,L=new Set,R=new Set,z=new Set;function B(e){let t=e.then(()=>void 0,()=>void 0);return R.add(t),t.then(()=>R.delete(t)),e}function de(t){e.isDevBuild()&&console.warn(`[tempest-react-sdk] createTempestAuth: logoutPath revocation failed`,t);try{ie?.(t)}catch{return}}function V(e){let t=(async()=>{try{await e()}catch(e){de(e)}})();return L.add(t),t.then(()=>L.delete(t)),t}function H(e,t){p&&!_&&t&&V(()=>U(e).post(p,{body:b(t)}))}function U(e){return n.createApiClient({baseURL:d,prefix:f,withCredentials:_,csrf:ae,fetcher:v,getToken:()=>e})}function W(){P+=1,K=a.createRefreshQueue(G)}async function G(){let e=N(),t=P;z.add(t);try{await fe(()=>pe(e))}finally{z.delete(t)}}async function fe(e){let t=g===`local`&&typeof navigator<`u`?navigator.locks:void 0;if(!t)return e();let n=new AbortController,r=setTimeout(()=>n.abort(),s),i=!1;try{await t.request(ce,{signal:n.signal},async()=>{i=!0,clearTimeout(r),await e()})}catch(t){if(i)throw t;clearTimeout(r),await e()}}async function pe(e){let n=e!==null||M().user!==null||D!==null,r=Z();if(!n&&N()!==null)return;if(!r.continues)throw new t.SessionSupersededError;if(N()!==e)return;let i=P,a;try{a=await B(Q.post(re,{body:b(E()),skipAuth:!0}))}catch(e){throw P===i?e:new t.SessionSupersededError}let{token:o,refreshToken:s}=y(a);if(P!==i||!Z().continues)throw H(o??null,s),new t.SessionSupersededError;n||j(l()),M().setToken(o),s&&O(s)}let K=a.createRefreshQueue(G);function q(e){let t=N();return t===null?!1:e.token===null||e.token!==t}function J(){return N()===null&&E()===null}async function me(){if(!(I.size>0&&F===0&&(await Promise.allSettled([...I]),N()!==null)))throw Error(`anonymous 401 with no session`)}async function he(e){if(e.epoch!==null&&e.epoch!==P)throw new t.SessionSupersededError;if(q(e)){if(e.token===null)throw Error(`anonymous 401 under a live session`)}else return e.token===null&&J()?z.has(P)?K():me():K()}async function ge(e,n){if(n.epoch!==null&&n.epoch!==P||!Z().continues)throw new t.SessionSupersededError;q(n)||n.token===null&&J()||await Se()}async function _e(e){L.size>0&&await c([...L],o);let t=ve(e);I.add(t);try{return await t}finally{I.delete(t)}}async function ve(e){let n=P,r=await B(Q.post(ne,{body:e,skipAuth:!0})),{token:i,refreshToken:a}=y(r);if(P!==n)throw H(i??null,a),new t.SessionSupersededError;H(N(),E()??void 0);let{done:o}=Y(`switch`),s=P;if(await o.catch(()=>void 0),P!==s)throw new t.SessionSupersededError;j(l()),M().setToken(i),O(a??null);let c=oe?.(r);return c==null?le():(M().setUser(c),c)}function Y(e){let{user:t,token:n}=M(),r=n!==null||t!==null||E()!==null;return(r||e!==`expired`)&&W(),M().logout(),O(null),j(null),r?{live:r,done:X({reason:e,user:t,token:n,elsewhere:!1})}:{live:r,done:e===`switch`||F===0?ue:Promise.resolve()}}function X(e){if(!S)return Promise.resolve();let t=U(e.token);F+=1;let n=(async()=>{try{await S({...e,api:t})}finally{--F}})();return ue=n.then(()=>void 0,()=>void 0),n.then(()=>void 0)}function ye(){let e=T();if(!e)return null;let t=e.getItem(w),n=e.getItem(k),r=e.getItem(h);if(!r)return{user:null,token:null,refreshToken:t,lineage:n};try{let e=JSON.parse(r);return{user:e.state?.user??null,token:e.state?.token??null,refreshToken:t,lineage:n}}catch{return{user:null,token:null,refreshToken:t,lineage:n}}}function Z(){let e=ye(),{user:t,token:n}=M();if(e===null)return{continues:!0,done:Promise.resolve()};let r=JSON.stringify(e.user)===JSON.stringify(t);if(e.token===n&&e.refreshToken===D&&e.lineage===A&&r)return{continues:!0,done:Promise.resolve()};let i=e.lineage!==null||A!==null?e.lineage===A:e.token===n||n!==null&&r,a=e.token!==null&&i,o=n!==null||t!==null||D!==null;if(a||W(),D=e.refreshToken,A=e.lineage,C.setState({user:e.user,token:e.token,isAuthenticated:e.token!==null}),a||!o)return{continues:a,done:Promise.resolve()};let s=X({reason:e.token===null?`logout`:`switch`,user:t,token:n,elsewhere:!0});return s.catch(()=>void 0),{continues:a,done:s}}function be(){if(!p)return null;let e=p,{token:t,user:n}=M(),r=E();if(!_&&t===null&&n===null&&r===null)return null;let i=_?[...R]:[];return V(async()=>(await c(i,o),U(t).post(e,{body:b(r)})))}function xe(){let e=Z();if(!e.continues)return e.done;let t=be(),{done:n}=Y(`logout`);return t?Promise.all([t,n]).then(()=>void 0):n}async function Se(){let{live:e,done:t}=Y(`expired`);await t.catch(()=>void 0),e&&x&&typeof window<`u`&&window.location.assign(x)}let Q=n.createApiClient({baseURL:d,prefix:f,withCredentials:_,csrf:ae,fetcher:v,getToken:N,getSessionEpoch:()=>P,refresh:he,retry:se,onUnauthorized:ge}),Ce=()=>K(),$=null,we=()=>N()===null?null:A===null?($?.epoch!==P&&($={epoch:P,key:l()}),$.key):A,Te={current:we,subscribe:e=>C.subscribe(e)};r.bindSessionIdentity(Q,Te),r.bindSessionIdentity(N,Te);function Ee(e){e.storageArea===T()&&(e.key===null||e.key===h||e.key===w||e.key===k)&&Z()}let De=typeof window>`u`?null:window;return De?.addEventListener(`storage`,Ee),{useAuthStore:C,api:Q,login:_e,logout:xe,refresh:Ce,getToken:N,sessionKey:we,dispose:()=>De?.removeEventListener(`storage`,Ee)}}exports.createTempestAuth=d;
2
2
  //# sourceMappingURL=create-tempest-auth.cjs.map