tempest-react-sdk 0.81.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 (194) hide show
  1. package/README.md +9 -10
  2. package/bin/create-tempest-app.e2e.test.mjs +31 -0
  3. package/bin/create-tempest-app.mjs +11 -1
  4. package/bin/lib/doctor/doctor.e2e.test.mjs +29 -0
  5. package/bin/lib/doctor/vitest.mjs +120 -0
  6. package/bin/lib/doctor/vitest.test.mjs +139 -0
  7. package/bin/lib/fs/contain.mjs +7 -0
  8. package/bin/lib/openapi/escape.mjs +68 -4
  9. package/bin/lib/openapi/generate.hostile.test.mjs +228 -0
  10. package/bin/lib/openapi/generate.mjs +119 -15
  11. package/bin/lib/openapi/pattern-risk.mjs +134 -0
  12. package/bin/lib/openapi/pattern-risk.test.mjs +45 -0
  13. package/bin/tempest-gen.test.mjs +100 -0
  14. package/bin/tempest.mjs +24 -8
  15. package/dist/access/use-can.cjs +1 -1
  16. package/dist/access/use-can.cjs.map +1 -1
  17. package/dist/access/use-can.js +25 -28
  18. package/dist/access/use-can.js.map +1 -1
  19. package/dist/auth/create-tempest-auth.cjs +1 -1
  20. package/dist/auth/create-tempest-auth.cjs.map +1 -1
  21. package/dist/auth/create-tempest-auth.js +189 -143
  22. package/dist/auth/create-tempest-auth.js.map +1 -1
  23. package/dist/auth/session-identity.cjs +2 -0
  24. package/dist/auth/session-identity.cjs.map +1 -0
  25. package/dist/auth/session-identity.js +12 -0
  26. package/dist/auth/session-identity.js.map +1 -0
  27. package/dist/br/boleto.cjs.map +1 -1
  28. package/dist/br/boleto.js.map +1 -1
  29. package/dist/components/AuthForm/AuthLinks.cjs +1 -1
  30. package/dist/components/AuthForm/AuthLinks.cjs.map +1 -1
  31. package/dist/components/AuthForm/AuthLinks.js +18 -15
  32. package/dist/components/AuthForm/AuthLinks.js.map +1 -1
  33. package/dist/components/AuthPage/AuthPage.cjs +1 -1
  34. package/dist/components/AuthPage/AuthPage.cjs.map +1 -1
  35. package/dist/components/AuthPage/AuthPage.js +58 -52
  36. package/dist/components/AuthPage/AuthPage.js.map +1 -1
  37. package/dist/components/Breadcrumbs/Breadcrumbs.cjs +1 -1
  38. package/dist/components/Breadcrumbs/Breadcrumbs.cjs.map +1 -1
  39. package/dist/components/Breadcrumbs/Breadcrumbs.js +22 -21
  40. package/dist/components/Breadcrumbs/Breadcrumbs.js.map +1 -1
  41. package/dist/components/Button/Button.cjs +1 -1
  42. package/dist/components/Button/Button.cjs.map +1 -1
  43. package/dist/components/Button/Button.js +41 -40
  44. package/dist/components/Button/Button.js.map +1 -1
  45. package/dist/components/Chat/ChatAttachments.cjs +1 -1
  46. package/dist/components/Chat/ChatAttachments.cjs.map +1 -1
  47. package/dist/components/Chat/ChatAttachments.js +38 -38
  48. package/dist/components/Form/Form.cjs +1 -1
  49. package/dist/components/Form/Form.cjs.map +1 -1
  50. package/dist/components/Form/Form.js +30 -32
  51. package/dist/components/Form/Form.js.map +1 -1
  52. package/dist/components/Layout/Layout.cjs +1 -1
  53. package/dist/components/Layout/Layout.cjs.map +1 -1
  54. package/dist/components/Layout/Layout.js +25 -24
  55. package/dist/components/Layout/Layout.js.map +1 -1
  56. package/dist/components/Layout/gap.cjs +2 -0
  57. package/dist/components/Layout/gap.cjs.map +1 -0
  58. package/dist/components/Layout/gap.js +15 -0
  59. package/dist/components/Layout/gap.js.map +1 -0
  60. package/dist/components/NavigationMenu/NavigationMenu.cjs +1 -1
  61. package/dist/components/NavigationMenu/NavigationMenu.cjs.map +1 -1
  62. package/dist/components/NavigationMenu/NavigationMenu.js +71 -70
  63. package/dist/components/NavigationMenu/NavigationMenu.js.map +1 -1
  64. package/dist/components/Sidebar/Sidebar.cjs.map +1 -1
  65. package/dist/components/Sidebar/Sidebar.js.map +1 -1
  66. package/dist/components/Sidebar/SidebarEntry.cjs +1 -1
  67. package/dist/components/Sidebar/SidebarEntry.cjs.map +1 -1
  68. package/dist/components/Sidebar/SidebarEntry.js +30 -29
  69. package/dist/components/Sidebar/SidebarEntry.js.map +1 -1
  70. package/dist/data/create-data-provider.cjs +1 -1
  71. package/dist/data/create-data-provider.cjs.map +1 -1
  72. package/dist/data/create-data-provider.js +1 -2
  73. package/dist/data/create-data-provider.js.map +1 -1
  74. package/dist/forms/validate-form.cjs +1 -1
  75. package/dist/forms/validate-form.cjs.map +1 -1
  76. package/dist/forms/validate-form.js +6 -1
  77. package/dist/forms/validate-form.js.map +1 -1
  78. package/dist/forms/zod-resolver.cjs +1 -1
  79. package/dist/forms/zod-resolver.cjs.map +1 -1
  80. package/dist/forms/zod-resolver.js +8 -3
  81. package/dist/forms/zod-resolver.js.map +1 -1
  82. package/dist/http/api-client.cjs +1 -1
  83. package/dist/http/api-client.cjs.map +1 -1
  84. package/dist/http/api-client.js +90 -72
  85. package/dist/http/api-client.js.map +1 -1
  86. package/dist/http/credential-scope.cjs +1 -1
  87. package/dist/http/credential-scope.cjs.map +1 -1
  88. package/dist/http/credential-scope.js +33 -9
  89. package/dist/http/credential-scope.js.map +1 -1
  90. package/dist/http/presigned-post.cjs.map +1 -1
  91. package/dist/http/presigned-post.js.map +1 -1
  92. package/dist/http/resumable-upload.cjs +1 -1
  93. package/dist/http/resumable-upload.cjs.map +1 -1
  94. package/dist/http/resumable-upload.js +167 -112
  95. package/dist/http/resumable-upload.js.map +1 -1
  96. package/dist/http/upload-with-progress.cjs +1 -1
  97. package/dist/http/upload-with-progress.cjs.map +1 -1
  98. package/dist/http/upload-with-progress.js +100 -44
  99. package/dist/http/upload-with-progress.js.map +1 -1
  100. package/dist/oauth/GoogleSignIn.cjs +1 -1
  101. package/dist/oauth/GoogleSignIn.cjs.map +1 -1
  102. package/dist/oauth/GoogleSignIn.js +5 -4
  103. package/dist/oauth/GoogleSignIn.js.map +1 -1
  104. package/dist/offline/create-offline-sync.cjs +1 -1
  105. package/dist/offline/create-offline-sync.cjs.map +1 -1
  106. package/dist/offline/create-offline-sync.js +87 -69
  107. package/dist/offline/create-offline-sync.js.map +1 -1
  108. package/dist/query/offline-persistence.cjs +1 -1
  109. package/dist/query/offline-persistence.cjs.map +1 -1
  110. package/dist/query/offline-persistence.js +67 -44
  111. package/dist/query/offline-persistence.js.map +1 -1
  112. package/dist/sse/create-event-stream.cjs +1 -1
  113. package/dist/sse/create-event-stream.cjs.map +1 -1
  114. package/dist/sse/create-event-stream.js +48 -41
  115. package/dist/sse/create-event-stream.js.map +1 -1
  116. package/dist/sse/ticket-event-stream.cjs +1 -1
  117. package/dist/sse/ticket-event-stream.cjs.map +1 -1
  118. package/dist/sse/ticket-event-stream.js +48 -19
  119. package/dist/sse/ticket-event-stream.js.map +1 -1
  120. package/dist/sse/use-event-stream.cjs +1 -1
  121. package/dist/sse/use-event-stream.cjs.map +1 -1
  122. package/dist/sse/use-event-stream.js +19 -14
  123. package/dist/sse/use-event-stream.js.map +1 -1
  124. package/dist/sw/background-sync.cjs +1 -1
  125. package/dist/sw/background-sync.cjs.map +1 -1
  126. package/dist/sw/background-sync.js +10 -8
  127. package/dist/sw/background-sync.js.map +1 -1
  128. package/dist/sw/cache.cjs +1 -1
  129. package/dist/sw/cache.cjs.map +1 -1
  130. package/dist/sw/cache.js +111 -55
  131. package/dist/sw/cache.js.map +1 -1
  132. package/dist/sw/create-push-handler.cjs +1 -1
  133. package/dist/sw/create-push-handler.cjs.map +1 -1
  134. package/dist/sw/create-push-handler.js +20 -10
  135. package/dist/sw/create-push-handler.js.map +1 -1
  136. package/dist/sw.d.ts +65 -14
  137. package/dist/tabular/compact.cjs +1 -1
  138. package/dist/tabular/compact.cjs.map +1 -1
  139. package/dist/tabular/compact.js +35 -16
  140. package/dist/tabular/compact.js.map +1 -1
  141. package/dist/tabular/manifest.cjs +1 -1
  142. package/dist/tabular/manifest.cjs.map +1 -1
  143. package/dist/tabular/manifest.js +33 -15
  144. package/dist/tabular/manifest.js.map +1 -1
  145. package/dist/tabular.d.ts +11 -1
  146. package/dist/tempest-react-sdk.cjs +1 -1
  147. package/dist/tempest-react-sdk.d.ts +543 -70
  148. package/dist/tempest-react-sdk.js +224 -224
  149. package/dist/theme/initial-theme.cjs +4 -4
  150. package/dist/theme/initial-theme.cjs.map +1 -1
  151. package/dist/theme/initial-theme.js +8 -5
  152. package/dist/theme/initial-theme.js.map +1 -1
  153. package/dist/utils/compressed-storage.cjs +1 -1
  154. package/dist/utils/compressed-storage.cjs.map +1 -1
  155. package/dist/utils/compressed-storage.js +13 -7
  156. package/dist/utils/compressed-storage.js.map +1 -1
  157. package/dist/utils/objects.cjs +1 -1
  158. package/dist/utils/objects.cjs.map +1 -1
  159. package/dist/utils/objects.js +13 -7
  160. package/dist/utils/objects.js.map +1 -1
  161. package/dist/utils/safe-url.cjs +1 -1
  162. package/dist/utils/safe-url.cjs.map +1 -1
  163. package/dist/utils/safe-url.js +4 -1
  164. package/dist/utils/safe-url.js.map +1 -1
  165. package/dist/vite/base-url.cjs +1 -1
  166. package/dist/vite/base-url.cjs.map +1 -1
  167. package/dist/vite/base-url.js +5 -1
  168. package/dist/vite/base-url.js.map +1 -1
  169. package/dist/vite/tempest-pwa-dev-sw.cjs +1 -1
  170. package/dist/vite/tempest-pwa-dev-sw.cjs.map +1 -1
  171. package/dist/vite/tempest-pwa-dev-sw.js +96 -31
  172. package/dist/vite/tempest-pwa-dev-sw.js.map +1 -1
  173. package/dist/vite/tempest-pwa-manifest.cjs +2 -2
  174. package/dist/vite/tempest-pwa-manifest.cjs.map +1 -1
  175. package/dist/vite/tempest-pwa-manifest.js +32 -16
  176. package/dist/vite/tempest-pwa-manifest.js.map +1 -1
  177. package/dist/vite.cjs +1 -1
  178. package/dist/vite.d.ts +63 -12
  179. package/dist/vite.js +4 -4
  180. package/dist/webrtc/peer-mesh.cjs +1 -1
  181. package/dist/webrtc/peer-mesh.cjs.map +1 -1
  182. package/dist/webrtc/peer-mesh.js +7 -1
  183. package/dist/webrtc/peer-mesh.js.map +1 -1
  184. package/dist/ws/create-web-socket.cjs +1 -1
  185. package/dist/ws/create-web-socket.cjs.map +1 -1
  186. package/dist/ws/create-web-socket.js +77 -61
  187. package/dist/ws/create-web-socket.js.map +1 -1
  188. package/dist/ws/use-web-socket.cjs +1 -1
  189. package/dist/ws/use-web-socket.cjs.map +1 -1
  190. package/dist/ws/use-web-socket.js +4 -3
  191. package/dist/ws/use-web-socket.js.map +1 -1
  192. package/package.json +1 -1
  193. package/template-pwa/src/pwa.tsx +42 -0
  194. package/template-pwa/src/sw.ts +5 -4
@@ -186,3 +186,103 @@ describe("tempest gen api / gen icons — write containment (#484)", () => {
186
186
  expect(filesUnder(outside)).toEqual([]);
187
187
  });
188
188
  });
189
+
190
+ describe("tempest gen api — spec text never drives the terminal (#533)", () => {
191
+ const TERMINAL_CONTROL = /[\u0000-\u0008\u000b-\u001f\u007f-\u009f]/;
192
+ const SGR = /\u001b\[\d+(?:;\d+)*m/g;
193
+ let cwd;
194
+
195
+ beforeEach(() => {
196
+ cwd = mkdtempSync(join(tmpdir(), "tempest-gen-tty-"));
197
+ mkdirSync(join(cwd, "src"));
198
+ });
199
+
200
+ afterEach(() => {
201
+ rmSync(cwd, { recursive: true, force: true });
202
+ });
203
+
204
+ /**
205
+ * Write `doc` as the project's spec and run `tempest gen api` on it.
206
+ *
207
+ * @param {object} doc - The OpenAPI document.
208
+ * @returns {{ status: number | null, out: string }} Exit code and the
209
+ * combined output with the CLI's own color codes removed.
210
+ */
211
+ function genApi(doc) {
212
+ writeFileSync(join(cwd, "spec.json"), JSON.stringify(doc));
213
+ const result = spawnSync(process.execPath, [CLI, "gen", "api", "spec.json"], {
214
+ cwd,
215
+ encoding: "utf8",
216
+ });
217
+ return { status: result.status, out: (result.stdout + result.stderr).replace(SGR, "") };
218
+ }
219
+
220
+ it("a tag carrying OSC, CSI and carriage-return sequences prints without them", () => {
221
+ const tag = "users\u001b]0;PWNED\u0007\u001b]8;;https://evil/\u001b\\x\u009b2J\r";
222
+ const { status, out } = genApi({
223
+ ...SPEC,
224
+ paths: {
225
+ "/users": { get: { tags: [tag], responses: { 200: { description: "ok" } } } },
226
+ },
227
+ });
228
+
229
+ expect(status).toBe(0);
230
+ expect(out).not.toMatch(TERMINAL_CONTROL);
231
+ expect(out).not.toContain("\u001b]");
232
+ expect(out).toContain("users]0;PWNED]8;;https://evil/\\x2J");
233
+ });
234
+
235
+ it("the usage hint names the folder and class that were generated", () => {
236
+ const { out } = genApi({
237
+ ...SPEC,
238
+ paths: {
239
+ "/users": {
240
+ get: { tags: ["User Profiles"], responses: { 200: { description: "ok" } } },
241
+ },
242
+ },
243
+ });
244
+
245
+ expect(out).toContain('import { UserProfilesService } from "@/api/user-profiles";');
246
+ });
247
+
248
+ it("a refusal that quotes a C1 control from the spec prints without it", () => {
249
+ const { status, out } = genApi({
250
+ ...SPEC,
251
+ paths: { "https://x\u009b2J/": { get: { responses: { 200: { description: "ok" } } } } },
252
+ });
253
+
254
+ expect(status).toBe(1);
255
+ expect(out).toMatch(/does not start with "\/"/);
256
+ expect(out).not.toMatch(TERMINAL_CONTROL);
257
+ });
258
+
259
+ it("a nested-quantifier pattern is printed as a warning and the files are still written", () => {
260
+ const { status, out } = genApi({
261
+ ...SPEC,
262
+ components: {
263
+ schemas: {
264
+ Q: { type: "object", properties: { s: { type: "string", pattern: "(a+)+" } } },
265
+ },
266
+ },
267
+ paths: {
268
+ "/users": {
269
+ post: {
270
+ tags: ["users"],
271
+ requestBody: {
272
+ content: {
273
+ "application/json": { schema: { $ref: "#/components/schemas/Q" } },
274
+ },
275
+ },
276
+ responses: { 200: { description: "ok" } },
277
+ },
278
+ },
279
+ },
280
+ });
281
+
282
+ expect(status).toBe(0);
283
+ expect(out).toMatch(
284
+ /! OpenAPI schema "Q" at properties\.s: pattern "\(a\+\)\+" repeats a group/,
285
+ );
286
+ expect(existsSync(join(cwd, "src", "api", "users", "schemas.ts"))).toBe(true);
287
+ });
288
+ });
package/bin/tempest.mjs CHANGED
@@ -27,7 +27,9 @@ import {
27
27
  } from "./lib/css/index.mjs";
28
28
  import { analyzeDesign } from "./lib/design/index.mjs";
29
29
  import { checkLucide } from "./lib/doctor/lucide.mjs";
30
+ import { checkVitest, VITEST_CONFIG_FILES } from "./lib/doctor/vitest.mjs";
30
31
  import { generateRegistry, loadIconTables } from "./lib/icons/generate.mjs";
32
+ import { terminalText } from "./lib/openapi/escape.mjs";
31
33
  import { generate } from "./lib/openapi/generate.mjs";
32
34
  import { loadSpec } from "./lib/openapi/load.mjs";
33
35
 
@@ -789,6 +791,18 @@ function doctor(args = []) {
789
791
  ],
790
792
  );
791
793
  }
794
+ if (usesSdk) {
795
+ checks.push(
796
+ ...checkVitest({
797
+ vitestInstalled: Boolean(installedVersion("vitest")),
798
+ configs: Object.fromEntries(
799
+ VITEST_CONFIG_FILES.filter((name) => existsSync(join(ROOT, name))).map(
800
+ (name) => [name, readFileSync(join(ROOT, name), "utf8")],
801
+ ),
802
+ ),
803
+ }),
804
+ );
805
+ }
792
806
  const entry = firstExisting(["src/main.tsx", "src/main.ts", "src/index.tsx", "src/index.ts"]);
793
807
  if (usesSdk) {
794
808
  // Without the stylesheet every component renders unstyled, so for an SDK
@@ -1330,7 +1344,7 @@ async function genApi(args) {
1330
1344
  }
1331
1345
  console.log(`${c.dim}→ loading ${source}${c.reset}`);
1332
1346
  const doc = await loadSpec(source);
1333
- const { files, tags } = generate(doc);
1347
+ const { files, tags, services, warnings } = generate(doc);
1334
1348
 
1335
1349
  const targets = Object.entries(files).map(([rel, contents]) => ({
1336
1350
  dest: join(outDir, rel),
@@ -1349,8 +1363,11 @@ async function genApi(args) {
1349
1363
  }
1350
1364
 
1351
1365
  console.log(
1352
- `\n${c.green}✓ Generated${c.reset} ${Object.keys(files).length} files for ${tags.length} route group(s): ${c.bold}${tags.join(", ")}${c.reset}`,
1366
+ `\n${c.green}✓ Generated${c.reset} ${Object.keys(files).length} files for ${tags.length} route group(s): ${c.bold}${terminalText(tags.join(", "))}${c.reset}`,
1353
1367
  );
1368
+ for (const warning of warnings) {
1369
+ console.log(`${c.yellow}! ${terminalText(warning)}${c.reset}`);
1370
+ }
1354
1371
  console.log(` ${c.dim}out: ${outDir}${c.reset}`);
1355
1372
  const prettier = localBin("prettier");
1356
1373
  if (prettier) {
@@ -1359,12 +1376,11 @@ async function genApi(args) {
1359
1376
  }
1360
1377
  console.log(`\n${c.dim}Inject an ApiClient into a service:${c.reset}`);
1361
1378
  console.log(` import { createApiClient } from "tempest-react-sdk";`);
1362
- if (tags[0]) {
1363
- const cls = tags[0].replace(/[^a-zA-Z0-9]+(.)?/g, (_, ch) => (ch ? ch.toUpperCase() : ""));
1364
- const Cls = cls.charAt(0).toUpperCase() + cls.slice(1) + "Service";
1365
- console.log(` import { ${Cls} } from "@/api/${tags[0].toLowerCase()}";`);
1379
+ if (services[0]) {
1380
+ const { slug, className } = services[0];
1381
+ console.log(` import { ${className} } from "@/api/${slug}";`);
1366
1382
  console.log(
1367
- ` const svc = new ${Cls}(createApiClient({ baseURL: import.meta.env.VITE_API_URL }));`,
1383
+ ` const svc = new ${className}(createApiClient({ baseURL: import.meta.env.VITE_API_URL }));`,
1368
1384
  );
1369
1385
  }
1370
1386
  return 0;
@@ -1721,7 +1737,7 @@ if (result instanceof Promise) {
1721
1737
  .then((code) => process.exit(code))
1722
1738
  .catch((err) => {
1723
1739
  console.error(
1724
- `${c.red}✗ ${err instanceof Error ? err.message : String(err)}${c.reset}`,
1740
+ `${c.red}✗ ${terminalText(err instanceof Error ? err.message : String(err))}${c.reset}`,
1725
1741
  );
1726
1742
  process.exit(1);
1727
1743
  });
@@ -1,2 +1,2 @@
1
- const e=require("./access-control-context.cjs");let t=require("react");function n(e){return typeof e==`boolean`?{can:e}:e}function r(r){let i=e.useAccessControl(),a=JSON.stringify(r),[o,s]=(0,t.useState)(()=>i?{allowed:!1,isLoading:!0}:{allowed:!0,isLoading:!1});return(0,t.useEffect)(()=>{if(!i){s({allowed:!0,isLoading:!1});return}let e=!1;return s(e=>({...e,isLoading:!0})),Promise.resolve(i.can(r)).then(t=>{if(e)return;let r=n(t);s({allowed:r.can,isLoading:!1,reason:r.reason})}).catch(t=>{e||s({allowed:!1,isLoading:!1,reason:t instanceof Error?t.message:`access check failed`})}),()=>{e=!0}},[i,a]),o}exports.useCan=r;
1
+ const e=require("./access-control-context.cjs");let t=require("react");var n={allowed:!1,isLoading:!0},r={allowed:!0,isLoading:!1};function i(e){return typeof e==`boolean`?{can:e}:e}function a(a){let o=e.useAccessControl(),s=JSON.stringify(a),[c,l]=(0,t.useState)(null);return(0,t.useEffect)(()=>{if(!o)return;let e=!1,t=t=>{e||l({control:o,key:s,result:t})};return Promise.resolve(o.can(a)).then(e=>{let n=i(e);t({allowed:n.can,isLoading:!1,reason:n.reason})}).catch(e=>{t({allowed:!1,isLoading:!1,reason:e instanceof Error?e.message:`access check failed`})}),()=>{e=!0}},[o,s]),o?c===null||c.control!==o||c.key!==s?n:c.result:r}exports.useCan=a;
2
2
  //# sourceMappingURL=use-can.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"use-can.cjs","names":["useAccessControl","useState","require_access_control_context","react","normalize","result","useCan","control","paramsKey","params","state","setState","cancelled","prev","raw","error"],"sources":["../../src/access/use-can.ts"],"sourcesContent":["import { useEffect, useState } from \"react\";\nimport { useAccessControl } from \"./access-control-context\";\nimport type { CanParams, CanResult } from \"./types\";\n\nexport interface UseCanResult {\n /** Whether the action is permitted. Defaults to `true` while loading is not the concern of the caller. */\n allowed: boolean;\n /** `true` while an async `can` check is pending. */\n isLoading: boolean;\n /** Optional explanation, typically present when `allowed` is `false`. */\n reason?: string;\n}\n\nfunction normalize(result: boolean | CanResult): CanResult {\n return typeof result === \"boolean\" ? { can: result } : result;\n}\n\n/**\n * Resolve an access check against the {@link AccessControl} in context.\n *\n * Handles sync booleans, sync {@link CanResult}, and promises of either. When\n * no provider is present, access is allowed (see `useAccessControl`). The check\n * re-runs whenever the params change.\n *\n * @param params - The action/resource being checked.\n */\nexport function useCan(params: CanParams): UseCanResult {\n const control = useAccessControl();\n const paramsKey = JSON.stringify(params);\n\n const [state, setState] = useState<UseCanResult>(() =>\n control ? { allowed: false, isLoading: true } : { allowed: true, isLoading: false },\n );\n\n useEffect(() => {\n if (!control) {\n setState({ allowed: true, isLoading: false });\n return;\n }\n\n let cancelled = false;\n setState((prev) => ({ ...prev, isLoading: true }));\n\n Promise.resolve(control.can(params))\n .then((raw) => {\n if (cancelled) return;\n const result = normalize(raw);\n setState({ allowed: result.can, isLoading: false, reason: result.reason });\n })\n .catch((error: unknown) => {\n if (cancelled) return;\n setState({\n allowed: false,\n isLoading: false,\n reason: error instanceof Error ? error.message : \"access check failed\",\n });\n });\n\n return () => {\n cancelled = true;\n };\n // `paramsKey` is the stable stringified form of `params`.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [control, paramsKey]);\n\n return state;\n}\n"],"mappings":"A,uEAaA,SAASI,EAAU,EAAwC,CACvD,OAAO,OAAOC,GAAW,UAAY,CAAE,IAAKA,CAAO,EAAIA,CAC3D,CAWA,SAAgBC,EAAO,EAAiC,CACpD,IAAMC,EAAUP,EAAAA,iBAAiB,EAC3BQ,EAAY,KAAK,UAAUC,CAAM,EAEjC,CAACC,EAAOC,IAAA,EAAYV,EAAAA,SAAAA,KACtBM,EAAU,CAAE,QAAS,GAAO,UAAW,EAAK,EAAI,CAAE,QAAS,GAAM,UAAW,EAAM,CACtF,EAiCA,OA/BA,EAAAJ,EAAA,UAAA,KAAgB,CACZ,GAAI,CAACI,EAAS,CACVI,EAAS,CAAE,QAAS,GAAM,UAAW,EAAM,CAAC,EAC5C,MACJ,CAEA,IAAIC,EAAY,GAkBhB,OAjBAD,EAAU,IAAU,CAAE,GAAGE,EAAM,UAAW,EAAK,EAAE,EAEjD,QAAQ,QAAQN,EAAQ,IAAIE,CAAM,CAAC,CAAC,CAC/B,KAAM,GAAQ,CACX,GAAIG,EAAW,OACf,IAAMP,EAASD,EAAUU,CAAG,EAC5BH,EAAS,CAAE,QAASN,EAAO,IAAK,UAAW,GAAO,OAAQA,EAAO,MAAO,CAAC,CAC7E,CAAC,CAAC,CACD,MAAO,GAAmB,CACnBO,GACJD,EAAS,CACL,QAAS,GACT,UAAW,GACX,OAAQI,aAAiB,MAAQA,EAAM,QAAU,qBACrD,CAAC,CACL,CAAC,MAEQ,CACT,EAAY,EAChB,CAGJ,EAAG,CAACR,EAASC,CAAS,CAAC,EAEhBE,CACX,C"}
1
+ {"version":3,"file":"use-can.cjs","names":["useAccessControl","useState","require_access_control_context","react","PENDING","ALLOW_ALL","normalize","result","useCan","control","paramsKey","params","resolved","setResolved","cancelled","settle","raw","error"],"sources":["../../src/access/use-can.ts"],"sourcesContent":["import { useEffect, useState } from \"react\";\nimport { useAccessControl } from \"./access-control-context\";\nimport type { AccessControl, CanParams, CanResult } from \"./types\";\n\nexport interface UseCanResult {\n /** Whether the action is permitted. Defaults to `true` while loading is not the concern of the caller. */\n allowed: boolean;\n /** `true` while an async `can` check is pending. */\n isLoading: boolean;\n /** Optional explanation, typically present when `allowed` is `false`. */\n reason?: string;\n}\n\n/** The answer for a check that has not resolved yet. */\nconst PENDING: UseCanResult = { allowed: false, isLoading: true };\n\n/** The answer when no provider is mounted. */\nconst ALLOW_ALL: UseCanResult = { allowed: true, isLoading: false };\n\n/** A resolved answer together with the check it answers. */\ninterface ResolvedCheck {\n control: AccessControl;\n key: string;\n result: UseCanResult;\n}\n\nfunction normalize(result: boolean | CanResult): CanResult {\n return typeof result === \"boolean\" ? { can: result } : result;\n}\n\n/**\n * Resolve an access check against the {@link AccessControl} in context.\n *\n * Handles sync booleans, sync {@link CanResult}, and promises of either. When\n * no provider is present, access is allowed (see `useAccessControl`). The check\n * re-runs whenever the params change; they are compared by their\n * `JSON.stringify` form, so an inline object literal does not re-run it on\n * every render.\n *\n * A resolved answer is stored with the control and the params it answers, and\n * is only returned while both still match. Until 0.81.0 the previous answer was\n * returned with `isLoading: false` for the render between a params change and\n * the effect that re-ran the check, so `<Can>` showed the children for the new\n * resource for one frame on the strength of the old resource's permission. The\n * first render for new params now reads `{ allowed: false, isLoading: true }`.\n *\n * @param params - The action/resource being checked.\n * @returns The answer for exactly these params, or the pending state.\n */\nexport function useCan(params: CanParams): UseCanResult {\n const control = useAccessControl();\n const paramsKey = JSON.stringify(params);\n const [resolved, setResolved] = useState<ResolvedCheck | null>(null);\n\n useEffect(() => {\n if (!control) return;\n let cancelled = false;\n const settle = (result: UseCanResult): void => {\n if (!cancelled) setResolved({ control, key: paramsKey, result });\n };\n\n Promise.resolve(control.can(params))\n .then((raw) => {\n const result = normalize(raw);\n settle({ allowed: result.can, isLoading: false, reason: result.reason });\n })\n .catch((error: unknown) => {\n settle({\n allowed: false,\n isLoading: false,\n reason: error instanceof Error ? error.message : \"access check failed\",\n });\n });\n\n return () => {\n cancelled = true;\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [control, paramsKey]);\n\n if (!control) return ALLOW_ALL;\n if (resolved === null || resolved.control !== control || resolved.key !== paramsKey) {\n return PENDING;\n }\n return resolved.result;\n}\n"],"mappings":"A,uEAcA,IAAMI,EAAwB,CAAE,QAAS,GAAO,UAAW,EAAK,EAG1DC,EAA0B,CAAE,QAAS,GAAM,UAAW,EAAM,EASlE,SAASC,EAAU,EAAwC,CACvD,OAAO,OAAOC,GAAW,UAAY,CAAE,IAAKA,CAAO,EAAIA,CAC3D,CAqBA,SAAgBC,EAAO,EAAiC,CACpD,IAAMC,EAAUT,EAAAA,iBAAiB,EAC3BU,EAAY,KAAK,UAAUC,CAAM,EACjC,CAACC,EAAUC,IAAA,EAAeZ,EAAAA,SAAAA,CAA+B,IAAI,EAgCnE,OA9BA,EAAAE,EAAA,UAAA,KAAgB,CACZ,GAAI,CAACM,EAAS,OACd,IAAIK,EAAY,GACVC,EAAU,GAA+B,CACtCD,GAAWD,EAAY,CAAE,UAAS,IAAKH,EAAW,QAAO,CAAC,CACnE,EAeA,OAbA,QAAQ,QAAQD,EAAQ,IAAIE,CAAM,CAAC,CAAC,CAC/B,KAAM,GAAQ,CACX,IAAMJ,EAASD,EAAUU,CAAG,EAC5BD,EAAO,CAAE,QAASR,EAAO,IAAK,UAAW,GAAO,OAAQA,EAAO,MAAO,CAAC,CAC3E,CAAC,CAAC,CACD,MAAO,GAAmB,CACvBQ,EAAO,CACH,QAAS,GACT,UAAW,GACX,OAAQE,aAAiB,MAAQA,EAAM,QAAU,qBACrD,CAAC,CACL,CAAC,MAEQ,CACT,EAAY,EAChB,CAEJ,EAAG,CAACR,EAASC,CAAS,CAAC,EAElBD,EACDG,IAAa,MAAQA,EAAS,UAAYH,GAAWG,EAAS,MAAQF,EAC/DN,EAEJQ,EAAS,OAJKP,CAKzB,C"}
@@ -1,49 +1,46 @@
1
1
  import { useAccessControl as e } from "./access-control-context.js";
2
2
  import { useEffect as t, useState as n } from "react";
3
3
  //#region src/access/use-can.ts
4
- function r(e) {
4
+ var r = {
5
+ allowed: !1,
6
+ isLoading: !0
7
+ }, i = {
8
+ allowed: !0,
9
+ isLoading: !1
10
+ };
11
+ function a(e) {
5
12
  return typeof e == "boolean" ? { can: e } : e;
6
13
  }
7
- function i(i) {
8
- let a = e(), o = JSON.stringify(i), [s, c] = n(() => a ? {
9
- allowed: !1,
10
- isLoading: !0
11
- } : {
12
- allowed: !0,
13
- isLoading: !1
14
- });
14
+ function o(o) {
15
+ let s = e(), c = JSON.stringify(o), [l, u] = n(null);
15
16
  return t(() => {
16
- if (!a) {
17
- c({
18
- allowed: !0,
19
- isLoading: !1
17
+ if (!s) return;
18
+ let e = !1, t = (t) => {
19
+ e || u({
20
+ control: s,
21
+ key: c,
22
+ result: t
20
23
  });
21
- return;
22
- }
23
- let e = !1;
24
- return c((e) => ({
25
- ...e,
26
- isLoading: !0
27
- })), Promise.resolve(a.can(i)).then((t) => {
28
- if (e) return;
29
- let n = r(t);
30
- c({
24
+ };
25
+ return Promise.resolve(s.can(o)).then((e) => {
26
+ let n = a(e);
27
+ t({
31
28
  allowed: n.can,
32
29
  isLoading: !1,
33
30
  reason: n.reason
34
31
  });
35
- }).catch((t) => {
36
- e || c({
32
+ }).catch((e) => {
33
+ t({
37
34
  allowed: !1,
38
35
  isLoading: !1,
39
- reason: t instanceof Error ? t.message : "access check failed"
36
+ reason: e instanceof Error ? e.message : "access check failed"
40
37
  });
41
38
  }), () => {
42
39
  e = !0;
43
40
  };
44
- }, [a, o]), s;
41
+ }, [s, c]), s ? l === null || l.control !== s || l.key !== c ? r : l.result : i;
45
42
  }
46
43
  //#endregion
47
- export { i as useCan };
44
+ export { o as useCan };
48
45
 
49
46
  //# sourceMappingURL=use-can.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"use-can.js","names":["normalize","result","useCan","control","useAccessControl","paramsKey","params","state","setState","useState","useEffect","cancelled","prev","raw","error"],"sources":["../../src/access/use-can.ts"],"sourcesContent":["import { useEffect, useState } from \"react\";\nimport { useAccessControl } from \"./access-control-context\";\nimport type { CanParams, CanResult } from \"./types\";\n\nexport interface UseCanResult {\n /** Whether the action is permitted. Defaults to `true` while loading is not the concern of the caller. */\n allowed: boolean;\n /** `true` while an async `can` check is pending. */\n isLoading: boolean;\n /** Optional explanation, typically present when `allowed` is `false`. */\n reason?: string;\n}\n\nfunction normalize(result: boolean | CanResult): CanResult {\n return typeof result === \"boolean\" ? { can: result } : result;\n}\n\n/**\n * Resolve an access check against the {@link AccessControl} in context.\n *\n * Handles sync booleans, sync {@link CanResult}, and promises of either. When\n * no provider is present, access is allowed (see `useAccessControl`). The check\n * re-runs whenever the params change.\n *\n * @param params - The action/resource being checked.\n */\nexport function useCan(params: CanParams): UseCanResult {\n const control = useAccessControl();\n const paramsKey = JSON.stringify(params);\n\n const [state, setState] = useState<UseCanResult>(() =>\n control ? { allowed: false, isLoading: true } : { allowed: true, isLoading: false },\n );\n\n useEffect(() => {\n if (!control) {\n setState({ allowed: true, isLoading: false });\n return;\n }\n\n let cancelled = false;\n setState((prev) => ({ ...prev, isLoading: true }));\n\n Promise.resolve(control.can(params))\n .then((raw) => {\n if (cancelled) return;\n const result = normalize(raw);\n setState({ allowed: result.can, isLoading: false, reason: result.reason });\n })\n .catch((error: unknown) => {\n if (cancelled) return;\n setState({\n allowed: false,\n isLoading: false,\n reason: error instanceof Error ? error.message : \"access check failed\",\n });\n });\n\n return () => {\n cancelled = true;\n };\n // `paramsKey` is the stable stringified form of `params`.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [control, paramsKey]);\n\n return state;\n}\n"],"mappings":";;;AAaA,SAASA,EAAU,GAAwC;CACvD,OAAO,OAAOC,KAAW,YAAY,EAAE,KAAKA,EAAO,IAAIA;AAC3D;AAWA,SAAgBC,EAAO,GAAiC;CACpD,IAAMC,IAAUC,EAAiB,GAC3BC,IAAY,KAAK,UAAUC,CAAM,GAEjC,CAACC,GAAOC,KAAYC,QACtBN,IAAU;EAAE,SAAS;EAAO,WAAW;CAAK,IAAI;EAAE,SAAS;EAAM,WAAW;CAAM,CACtF;CAiCA,OA/BAO,QAAgB;EACZ,IAAI,CAACP,GAAS;GACV,EAAS;IAAE,SAAS;IAAM,WAAW;GAAM,CAAC;GAC5C;EACJ;EAEA,IAAIQ,IAAY;EAkBhB,OAjBAH,GAAU,OAAU;GAAE,GAAGI;GAAM,WAAW;EAAK,EAAE,GAEjD,QAAQ,QAAQT,EAAQ,IAAIG,CAAM,CAAC,CAAC,CAC/B,MAAM,MAAQ;GACX,IAAIK,GAAW;GACf,IAAMV,IAASD,EAAUa,CAAG;GAC5B,EAAS;IAAE,SAASZ,EAAO;IAAK,WAAW;IAAO,QAAQA,EAAO;GAAO,CAAC;EAC7E,CAAC,CAAC,CACD,OAAO,MAAmB;GACvB,AAAIU,KACJH,EAAS;IACL,SAAS;IACT,WAAW;IACX,QAAQM,aAAiB,QAAQA,EAAM,UAAU;GACrD,CAAC;EACL,CAAC,SAEQ;GACT,IAAY;EAChB;CAGJ,GAAG,CAACX,GAASE,CAAS,CAAC,GAEhBE;AACX"}
1
+ {"version":3,"file":"use-can.js","names":["PENDING","ALLOW_ALL","normalize","result","useCan","control","useAccessControl","paramsKey","params","resolved","setResolved","useState","useEffect","cancelled","settle","raw","error"],"sources":["../../src/access/use-can.ts"],"sourcesContent":["import { useEffect, useState } from \"react\";\nimport { useAccessControl } from \"./access-control-context\";\nimport type { AccessControl, CanParams, CanResult } from \"./types\";\n\nexport interface UseCanResult {\n /** Whether the action is permitted. Defaults to `true` while loading is not the concern of the caller. */\n allowed: boolean;\n /** `true` while an async `can` check is pending. */\n isLoading: boolean;\n /** Optional explanation, typically present when `allowed` is `false`. */\n reason?: string;\n}\n\n/** The answer for a check that has not resolved yet. */\nconst PENDING: UseCanResult = { allowed: false, isLoading: true };\n\n/** The answer when no provider is mounted. */\nconst ALLOW_ALL: UseCanResult = { allowed: true, isLoading: false };\n\n/** A resolved answer together with the check it answers. */\ninterface ResolvedCheck {\n control: AccessControl;\n key: string;\n result: UseCanResult;\n}\n\nfunction normalize(result: boolean | CanResult): CanResult {\n return typeof result === \"boolean\" ? { can: result } : result;\n}\n\n/**\n * Resolve an access check against the {@link AccessControl} in context.\n *\n * Handles sync booleans, sync {@link CanResult}, and promises of either. When\n * no provider is present, access is allowed (see `useAccessControl`). The check\n * re-runs whenever the params change; they are compared by their\n * `JSON.stringify` form, so an inline object literal does not re-run it on\n * every render.\n *\n * A resolved answer is stored with the control and the params it answers, and\n * is only returned while both still match. Until 0.81.0 the previous answer was\n * returned with `isLoading: false` for the render between a params change and\n * the effect that re-ran the check, so `<Can>` showed the children for the new\n * resource for one frame on the strength of the old resource's permission. The\n * first render for new params now reads `{ allowed: false, isLoading: true }`.\n *\n * @param params - The action/resource being checked.\n * @returns The answer for exactly these params, or the pending state.\n */\nexport function useCan(params: CanParams): UseCanResult {\n const control = useAccessControl();\n const paramsKey = JSON.stringify(params);\n const [resolved, setResolved] = useState<ResolvedCheck | null>(null);\n\n useEffect(() => {\n if (!control) return;\n let cancelled = false;\n const settle = (result: UseCanResult): void => {\n if (!cancelled) setResolved({ control, key: paramsKey, result });\n };\n\n Promise.resolve(control.can(params))\n .then((raw) => {\n const result = normalize(raw);\n settle({ allowed: result.can, isLoading: false, reason: result.reason });\n })\n .catch((error: unknown) => {\n settle({\n allowed: false,\n isLoading: false,\n reason: error instanceof Error ? error.message : \"access check failed\",\n });\n });\n\n return () => {\n cancelled = true;\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [control, paramsKey]);\n\n if (!control) return ALLOW_ALL;\n if (resolved === null || resolved.control !== control || resolved.key !== paramsKey) {\n return PENDING;\n }\n return resolved.result;\n}\n"],"mappings":";;;AAcA,IAAMA,IAAwB;CAAE,SAAS;CAAO,WAAW;AAAK,GAG1DC,IAA0B;CAAE,SAAS;CAAM,WAAW;AAAM;AASlE,SAASC,EAAU,GAAwC;CACvD,OAAO,OAAOC,KAAW,YAAY,EAAE,KAAKA,EAAO,IAAIA;AAC3D;AAqBA,SAAgBC,EAAO,GAAiC;CACpD,IAAMC,IAAUC,EAAiB,GAC3BC,IAAY,KAAK,UAAUC,CAAM,GACjC,CAACC,GAAUC,KAAeC,EAA+B,IAAI;CAgCnE,OA9BAC,QAAgB;EACZ,IAAI,CAACP,GAAS;EACd,IAAIQ,IAAY,IACVC,KAAU,MAA+B;GAC3C,AAAKD,KAAWH,EAAY;IAAE;IAAS,KAAKH;IAAW;GAAO,CAAC;EACnE;EAeA,OAbA,QAAQ,QAAQF,EAAQ,IAAIG,CAAM,CAAC,CAAC,CAC/B,MAAM,MAAQ;GACX,IAAML,IAASD,EAAUa,CAAG;GAC5B,EAAO;IAAE,SAASZ,EAAO;IAAK,WAAW;IAAO,QAAQA,EAAO;GAAO,CAAC;EAC3E,CAAC,CAAC,CACD,OAAO,MAAmB;GACvB,EAAO;IACH,SAAS;IACT,WAAW;IACX,QAAQa,aAAiB,QAAQA,EAAM,UAAU;GACrD,CAAC;EACL,CAAC,SAEQ;GACT,IAAY;EAChB;CAEJ,GAAG,CAACX,GAASE,CAAS,CAAC,GAElBF,IACDI,MAAa,QAAQA,EAAS,YAAYJ,KAAWI,EAAS,QAAQF,IAC/DP,IAEJS,EAAS,SAJKR;AAKzB"}
@@ -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("./create-auth-store.cjs"),i=require("./refresh-queue.cjs");var a=1e4;function o(e,t){return e.length===0?Promise.resolve():new Promise(n=>{let r=setTimeout(n,t);Promise.allSettled(e).then(()=>{clearTimeout(r),n()})})}function s(e){let t=e??{};return{token:t.access_token,refreshToken:t.refresh_token}}function c(c){let{baseURL:l,prefix:u,loginPath:d=`/api/auth/login`,refreshPath:ee=`/api/auth/refresh`,logoutPath:f,onLogoutError:te,mePath:p,storeName:m=`tempest-auth`,storage:h=`local`,withCredentials:g=!1,csrf:_,fetcher:v,parseTokens:y=s,parseUser:ne,refreshBody:b=e=>e?{refresh_token:e}:void 0,retry:re,redirectTo:x,onSessionEnd:S}=c,C=r.createAuthStore({name:m,storage:h}),w=`${m}-refresh`;function T(){return typeof window>`u`?null:h===`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=()=>C.getState(),A=()=>k().token;async function ie(){if(!p)return k().user;let e=j,n;try{n=await Z.get(p,{skipAuthRetry:!0})}catch(n){throw j===e?n:new t.SessionSupersededError}if(j!==e)throw new t.SessionSupersededError;return k().setUser(n),n}let j=0,M=Promise.resolve(),N=0,P=new Set,F=new Set,I=new Set,L=new Set;function R(e){let t=e.then(()=>void 0,()=>void 0);return I.add(t),t.then(()=>I.delete(t)),e}function ae(t){e.isDevBuild()&&console.warn(`[tempest-react-sdk] createTempestAuth: logoutPath revocation failed`,t);try{te?.(t)}catch{return}}function z(e){let t=(async()=>{try{await e()}catch(e){ae(e)}})();return F.add(t),t.then(()=>F.delete(t)),t}function B(e,t){f&&!g&&t&&z(()=>V(e).post(f,{body:b(t)}))}function V(e){return n.createApiClient({baseURL:l,prefix:u,withCredentials:g,csrf:_,fetcher:v,getToken:()=>e})}function H(){j+=1,W=i.createRefreshQueue(U)}async function U(){let e=A();if(!X().continues)throw new t.SessionSupersededError;if(A()!==e)return;let n=j;L.add(n);try{let e;try{e=await R(Z.post(ee,{body:b(E()),skipAuth:!0}))}catch(e){throw j===n?e:new t.SessionSupersededError}let{token:r,refreshToken:i}=y(e);if(j!==n||!X().continues)throw B(r??null,i),new t.SessionSupersededError;k().setToken(r),i&&O(i)}finally{L.delete(n)}}let W=i.createRefreshQueue(U);function G(e){let t=A();return t===null?!1:e.token===null||e.token!==t}function K(){return A()===null&&E()===null}async function q(){if(!(P.size>0&&N===0&&(await Promise.allSettled([...P]),A()!==null)))throw Error(`anonymous 401 with no session`)}async function oe(e){if(e.epoch!==null&&e.epoch!==j)throw new t.SessionSupersededError;if(G(e)){if(e.token===null)throw Error(`anonymous 401 under a live session`)}else return e.token===null&&K()?L.has(j)?W():q():W()}async function se(e,n){if(n.epoch!==null&&n.epoch!==j||!X().continues)throw new t.SessionSupersededError;G(n)||n.token===null&&K()||await pe()}async function ce(e){F.size>0&&await o([...F],a);let t=le(e);P.add(t);try{return await t}finally{P.delete(t)}}async function le(e){let n=j,r=await R(Z.post(d,{body:e,skipAuth:!0})),{token:i,refreshToken:a}=y(r);if(j!==n)throw B(i??null,a),new t.SessionSupersededError;let{done:o}=J(`switch`),s=j;if(await o.catch(()=>void 0),j!==s)throw new t.SessionSupersededError;k().setToken(i),O(a??null);let c=ne?.(r);return c==null?ie():(k().setUser(c),c)}function J(e){let{user:t,token:n}=k(),r=n!==null||t!==null||E()!==null;return(r||e!==`expired`)&&H(),k().logout(),O(null),r?{live:r,done:Y({reason:e,user:t,token:n,elsewhere:!1})}:{live:r,done:e===`switch`||N===0?M:Promise.resolve()}}function Y(e){if(!S)return Promise.resolve();let t=V(e.token);N+=1;let n=(async()=>{try{await S({...e,api:t})}finally{--N}})();return M=n.then(()=>void 0,()=>void 0),n.then(()=>void 0)}function ue(){let e=T();if(!e)return null;let t=e.getItem(w),n=e.getItem(m);if(!n)return{user:null,token:null,refreshToken:t};try{let e=JSON.parse(n);return{user:e.state?.user??null,token:e.state?.token??null,refreshToken:t}}catch{return{user:null,token:null,refreshToken:t}}}function X(){let e=ue(),{user:t,token:n}=k();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&&r)return{continues:!0,done:Promise.resolve()};let i=e.token!==null&&(e.token===n||n!==null&&r),a=n!==null||t!==null||D!==null;if(i||H(),D=e.refreshToken,C.setState({user:e.user,token:e.token,isAuthenticated:e.token!==null}),i||!a)return{continues:i,done:Promise.resolve()};let o=Y({reason:e.token===null?`logout`:`switch`,user:t,token:n,elsewhere:!0});return o.catch(()=>void 0),{continues:i,done:o}}function de(){if(!f)return null;let e=f,{token:t,user:n}=k(),r=E();if(!g&&t===null&&n===null&&r===null)return null;let i=g?[...I]:[];return z(async()=>(await o(i,a),V(t).post(e,{body:b(r)})))}function fe(){let e=X();if(!e.continues)return e.done;let t=de(),{done:n}=J(`logout`);return t?Promise.all([t,n]).then(()=>void 0):n}async function pe(){let{live:e,done:t}=J(`expired`);await t.catch(()=>void 0),e&&x&&typeof window<`u`&&window.location.assign(x)}let Z=n.createApiClient({baseURL:l,prefix:u,withCredentials:g,csrf:_,fetcher:v,getToken:A,getSessionEpoch:()=>j,refresh:oe,retry:re,onUnauthorized:se}),me=()=>W();function Q(e){e.storageArea===T()&&(e.key===null||e.key===m||e.key===w)&&X()}let $=typeof window>`u`?null:window;return $?.addEventListener(`storage`,Q),{useAuthStore:C,api:Z,login:ce,logout:fe,refresh:me,getToken:A,dispose:()=>$?.removeEventListener(`storage`,Q)}}exports.createTempestAuth=c;
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