@alxia/react-router 0.13.0 → 0.14.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 (76) hide show
  1. package/README.md +78 -0
  2. package/dist/chunks/index-0tyw54ad.js +160 -0
  3. package/dist/chunks/index-0tyw54ad.js.map +15 -0
  4. package/dist/chunks/index-c3k47yw7.js +124 -0
  5. package/dist/chunks/index-c3k47yw7.js.map +14 -0
  6. package/dist/errors/api-error.d.ts +18 -0
  7. package/dist/errors/api-error.d.ts.map +1 -1
  8. package/dist/errors/index.d.ts +1 -1
  9. package/dist/errors/index.d.ts.map +1 -1
  10. package/dist/errors/index.js +45 -110
  11. package/dist/errors/index.js.map +4 -8
  12. package/dist/errors/json-actions.d.ts.map +1 -1
  13. package/dist/errors/same-site.d.ts +25 -0
  14. package/dist/errors/same-site.d.ts.map +1 -0
  15. package/dist/session/codes.d.ts +7 -0
  16. package/dist/session/codes.d.ts.map +1 -0
  17. package/dist/session/cookies.d.ts +60 -0
  18. package/dist/session/cookies.d.ts.map +1 -0
  19. package/dist/session/flow-types.d.ts +178 -0
  20. package/dist/session/flow-types.d.ts.map +1 -0
  21. package/dist/session/guard.d.ts +10 -0
  22. package/dist/session/guard.d.ts.map +1 -0
  23. package/dist/session/index.d.ts +18 -0
  24. package/dist/session/index.d.ts.map +1 -0
  25. package/dist/session/index.js +637 -0
  26. package/dist/session/index.js.map +23 -0
  27. package/dist/session/land.d.ts +26 -0
  28. package/dist/session/land.d.ts.map +1 -0
  29. package/dist/session/link-land.d.ts +18 -0
  30. package/dist/session/link-land.d.ts.map +1 -0
  31. package/dist/session/link-token.d.ts +22 -0
  32. package/dist/session/link-token.d.ts.map +1 -0
  33. package/dist/session/link-types.d.ts +124 -0
  34. package/dist/session/link-types.d.ts.map +1 -0
  35. package/dist/session/links.d.ts +14 -0
  36. package/dist/session/links.d.ts.map +1 -0
  37. package/dist/session/model/index.d.ts +7 -0
  38. package/dist/session/model/index.d.ts.map +1 -0
  39. package/dist/session/model/index.js +25 -0
  40. package/dist/session/model/index.js.map +9 -0
  41. package/dist/session/model/paths.d.ts +24 -0
  42. package/dist/session/model/paths.d.ts.map +1 -0
  43. package/dist/session/model/safe-next.d.ts +15 -0
  44. package/dist/session/model/safe-next.d.ts.map +1 -0
  45. package/dist/session/model/second-factor.d.ts +16 -0
  46. package/dist/session/model/second-factor.d.ts.map +1 -0
  47. package/dist/session/model/site-path.d.ts +10 -0
  48. package/dist/session/model/site-path.d.ts.map +1 -0
  49. package/dist/session/model/step-up-answers.d.ts +40 -0
  50. package/dist/session/model/step-up-answers.d.ts.map +1 -0
  51. package/dist/session/model/step-up-props.d.ts +21 -0
  52. package/dist/session/model/step-up-props.d.ts.map +1 -0
  53. package/dist/session/model/step-up.d.ts +48 -0
  54. package/dist/session/model/step-up.d.ts.map +1 -0
  55. package/dist/session/second-factor.d.ts +22 -0
  56. package/dist/session/second-factor.d.ts.map +1 -0
  57. package/dist/session/serialize.d.ts +34 -0
  58. package/dist/session/serialize.d.ts.map +1 -0
  59. package/dist/session/sign-in.d.ts +17 -0
  60. package/dist/session/sign-in.d.ts.map +1 -0
  61. package/dist/session/step-up-codes.d.ts +9 -0
  62. package/dist/session/step-up-codes.d.ts.map +1 -0
  63. package/dist/session/step-up-types.d.ts +69 -0
  64. package/dist/session/step-up-types.d.ts.map +1 -0
  65. package/dist/session/step-up.d.ts +17 -0
  66. package/dist/session/step-up.d.ts.map +1 -0
  67. package/dist/session/step.d.ts +20 -0
  68. package/dist/session/step.d.ts.map +1 -0
  69. package/dist/session/viewer.d.ts +33 -0
  70. package/dist/session/viewer.d.ts.map +1 -0
  71. package/docs/README.md +1 -0
  72. package/docs/guide/errors.md +19 -0
  73. package/docs/guide/session.md +955 -0
  74. package/docs/roadmap.md +27 -0
  75. package/docs/troubleshooting.md +362 -1
  76. package/package.json +15 -2
package/README.md CHANGED
@@ -932,6 +932,56 @@ declare function fetchVersions(): Promise<{ en: string; fr: string }>;
932
932
  The [errors guide](https://github.com/softistx/alxia/blob/develop/packages/react-router/docs/guide/errors.md)
933
933
  has the rest.
934
934
 
935
+ ## Sessions for a BFF
936
+
937
+ An app that renders its pages and calls a GraphQL API that signs users in
938
+ with `@nxgt/janus` keeps the API's token in its own HttpOnly cookie.
939
+ `@alxia/react-router/session` holds that session's server side, built on
940
+ `/errors`; `/session/model` imports nothing and is safe in a component.
941
+ Neither adds a peer.
942
+
943
+ ```ts
944
+ // app/session.server.ts
945
+ import { sessionCookies, signInFlow } from '@alxia/react-router/session';
946
+ import { errors, json } from './errors.server'; // createApiErrors, jsonActions
947
+ import { SignOutMutation, execute } from './api.server';
948
+
949
+ export const cookies = sessionCookies({
950
+ session: 'my-app_session',
951
+ challenge: 'my-app_challenge',
952
+ secure: !import.meta.env.DEV,
953
+ });
954
+
955
+ export const signIn = signInFlow({
956
+ cookies,
957
+ errors,
958
+ json,
959
+ revoke: (token, request) =>
960
+ execute(SignOutMutation, {}, { token, signal: request.signal }),
961
+ attemptsLeft: (count, language) => `${count} tries left`,
962
+ });
963
+ ```
964
+
965
+ ```ts
966
+ // app/routes/sign-in.tsx
967
+ export async function action({ context, request }: Route.ActionArgs) {
968
+ const { language } = alxiaOf(context);
969
+ json.requireJsonPost(request); // a form posted before hydration goes back
970
+ const input = await json.readInput(request, SignInInput);
971
+ return signIn.signInStep(
972
+ async () => toAnswer((await execute(SignInMutation, input)).signIn),
973
+ { request, language, next: signIn.nextParam(request), extra: {} },
974
+ );
975
+ }
976
+ ```
977
+
978
+ A signed-in answer redirects to `next` (kept on the site by `safeNext`) with
979
+ the session cookie set; a second factor goes to the code page; a refusal stays
980
+ on the form. `stepUpSteps` asks for a code before a sensitive action, and
981
+ `authLinks` serves the pages a mail's link opens. The
982
+ [session guide](https://github.com/softistx/alxia/blob/develop/packages/react-router/docs/guide/session.md)
983
+ has a complete sign-in route and the rest.
984
+
935
985
  ## Traps
936
986
 
937
987
  - **A context key made in `app/` reaches the loaders only through the
@@ -1054,6 +1104,7 @@ From `@alxia/react-router/errors` (`react-router` is its only package import, an
1054
1104
  | `isApiErrorLike(error)` | whether `error` is an `@nxgt/graphql-client` `ApiError`, like the client's `isApiError`, plus a numeric `status`, which `ApiErrorLike` promises; it holds across two copies of the client |
1055
1105
  | `ApiErrorLike`, `ApiErrorEntryLike`, `ApiErrorExtensionsLike`, `ApiErrorParamsLike`, `ApiFieldErrorLike` | the shape of the refusal: `errors`, `code`, `status`, `requestId?`, and each error's `extensions` (`code`, `params`, `fields`, `field`, `minutes`, `retryAfter`) |
1056
1106
  | `waitMinutes(extensions)` | the minutes of a throttle's wait: `minutes` (at least one), else `retryAfter` rounded up |
1107
+ | `attemptsLeftOf(error)` | the tries a refused code leaves, as `@nxgt/janus` counts them: `extensions.attemptsLeft` of the first error, else `params.attemptsLeft`; `undefined` when it is not a whole number from 0 up |
1057
1108
  | `REQUEST_ID_HEADER`, `requestIdOf(response)` | `x-request-id`, and the id a response carries, if the API sent one |
1058
1109
  | `nextOf(url)` | the path and search of the page a request is for, without React Router's `.data`, `/_.data`, `_routes` and empty `index`; it never starts with two slashes, but an app still validates `next` before redirecting to it |
1059
1110
  | `FormErrors` | what a form action answers when the API refused the input: `fieldErrors` by field and a `formError` (`undefined` when every message sits under a field), already translated; `submitForm` answers it |
@@ -1072,6 +1123,32 @@ From `@alxia/react-router/errors` (`react-router` is its only package import, an
1072
1123
  | `useJsonFetcher(key?, action?)`, `JsonBody` | a fetcher that posts JSON: `{ fetcher, post(body, to?), busy }`; `to` is the action of one call |
1073
1124
  | `StandardSchemaV1` | the Standard Schema v1 type `readInput` takes, declared in `/errors` (type only, so no `@alxia/core`; `readInput`'s output is inferred from the schema's `~standard.types`); a Zod 4, Valibot or ArkType schema fits |
1074
1125
 
1126
+ From `@alxia/react-router/session` (`react-router` and `/errors`' files are its only imports):
1127
+
1128
+ | export | |
1129
+ | --- | --- |
1130
+ | `sessionCookies({ session, challenge, secure, challengePath?, challengeSeconds? })`, `SessionCookies`, `SessionCookiesOptions`, `CookieSession` | the session and second-factor challenge cookies, `HttpOnly; SameSite=Lax`, `__Host-` and `__Secure-` when `secure` (required, never read from the environment): `commit(token, expiresAt)`, `read(request)`, `token(request)`, `destroy()`, `refreshed(session, expiresAt)` (a `Set-Cookie` only when the expiry moved), `commitChallenge`, `challengeOf`, `destroyChallenge`, `signInPageHeaders(deadSession)` and `names` (the cookie names as sent, prefixes included); a cookie written by an app's own code in `<expiry ms>.<token>` is read as it is |
1131
+ | `serializeCookie(name, value, { maxAge, path?, secure })`, `readCookie(request, name)`, `secondsUntil(expiresAt, now?)`, `SerializeOptions` | the bytes `sessionCookies` writes, for the cookies of your own; a name that is not an RFC 6265 token throws a `TypeError` |
1132
+ | `signInFlow({ cookies, errors, json, revoke, attemptsLeft, paths?, clear?, unavailable?, codes?, endedParam? })`, `SignInFlow`, `SignInFlowOptions` | the server side of the sign-in pages, which also exposes what it was built with (`cookies`, `errors`, `json`, `attemptsLeft`, `unavailable(language)`, `clear()`) and `guard(request)`, the check an app's own POST step runs first: `nextParam`, `signInRedirect`, `landing`, `landReplacing` (revokes the session the browser held), `signInStep` (a same-site JSON post only; an API refusal as the form's message and status, an unreachable API as a 503), `readCodeInput`, `codeStep`, `codeRefusal` (with the tries left), `revokeSession`, `signOut` (JSON and same-origin only; it clears the challenge cookie too), `viewer` (`null` for a 401, `undefined` when unknown) and `requireViewer` (throws the sign-in redirect) |
1133
+ | `SignInAnswer`, `SignInFormError`, `SignInStayed`, `SignInCodes` | what a step answers (`signedIn`, `secondFactor` or `other`), a form step's answer when it stays on the page, and the `codes` option (`invalidCode`, `challengeOver`) |
1134
+ | `stepUpSteps({ signIn, spent, locked?, wording?, codes? })`, `StepUpSteps`, `StepUpStepsOptions`, `StepUpCall` | a step-up for a route's action, reading the flow's `errors`, `json` and `attemptsLeft`, each step taking `{ request, language }` and refusing a post that is not a same-site JSON one: `request(run, { request, language })` answers the challenge and how it is confirmed (a throttled mail is `error: 'throttled'` with `retryAfter`), `confirm(run, { request, language, via? })` answers `error: 'invalid'` with the tries left (and `locked: true` for an app or recovery code with none left), `expired` or `failed`; `request` may answer `throttled` with `retryAfter`; and `isStepUpRequired(error)` |
1135
+ | `StepUpCodes`, `StepUpStayed`, `INVALID_CODE`, `CHALLENGE_OVER`, `MAIL_THROTTLED`, `STEP_UP_REQUIRED`, `STEP_UP_OVER` | the `codes` option (`invalidCode`, `challengeOver`, `stepUpRequired`, `mailThrottled`), a step's answer that stays on the page, and janus's default codes, which an app can extend |
1136
+ | `authLinks({ signIn, codes?, wording?, start?, resetDone?, invalidState? })`, `AuthLinks`, `AuthLinksOptions`, `AuthLinksCodes` | the pages a mail's link opens, over the flow's cookies, errors and json: `linkArgs`, `spendToken(run, { request, language, path, states, fields? })`, `answerOf`, `landByLink`, `afterReset` (clears the session, the challenge and the flow's cookies), `requestLink(run, { request, language })` (a wait answers `throttled: true` and `retryAfter` when the API gives the seconds; `wording` words every refusal that is not a field's), `statePath` and `ended` |
1137
+ | `Spent`, `SpendOptions`, `LinkStayed`, `RequestLinkAnswer` | their types |
1138
+ | `TOKEN_PAGE_HEADERS` | `Referrer-Policy: no-referrer` and `X-Robots-Tag: noindex, nofollow`, for a page whose URL carries a token |
1139
+
1140
+ From `@alxia/react-router/session/model` (no import at all):
1141
+
1142
+ | export | |
1143
+ | --- | --- |
1144
+ | `safeNext(value)`, `HOME` | `value` when it is a path of this site (search and hash kept), else `/` (`HOME`): a `//`, a scheme, a backslash, a control character, more than 2000 characters or a dot segment that leaves the site |
1145
+ | `signInPath(next, { query?, path? }?)` (`path` must be a path of this site with no query or fragment, a `TypeError` otherwise), `secondFactorPath(next)`, `SIGN_IN_PATHS`, `SignInPaths` | the sign-in page's URL coming back to `next`, the second-factor page's, and the default paths `/sign-in` and `/sign-in/second-factor` |
1146
+ | `TOTP_CODE` | the regular expression of an authenticator app's code: six digits |
1147
+ | `SecondFactorKind`, `SignInStatus`, `StepUpVia` | `'code' \| 'recovery'`, `'signedIn' \| 'secondFactor'` and `'email' \| 'secondFactor'` |
1148
+ | `stepUpReducer`, `INITIAL_STEP_UP`, `StepUpState`, `StepUpAction`, `StepUpPhase` | the step-up's state on the page, with no React: `phase`, `via`, `challenge`, `attemptsLeft`, `locked`, `error`, `message` (the last refusal worded), `retryAfter`, `pending`, `resent` |
1149
+ | `stepUpProps(state)`, `StepUpProps` | what a code form shows (`via`, `attemptsLeft`, `locked`, `error`, `retryAfter`, `pending`, `resent`; never the challenge or the message), for a step-up dialog's props |
1150
+ | `StepUpRequestAnswer`, `StepUpConfirmAnswer`, `StepUpError` | what `stepUpSteps` answers, which the reducer takes as it is; `StepUpError` is `'invalid' \| 'expired' \| 'failed' \| 'throttled'` |
1151
+
1075
1152
  From `@alxia/react-router/document`:
1076
1153
 
1077
1154
  | export | |
@@ -1108,6 +1185,7 @@ The `alxia-react-router` bin, run with `bunx`:
1108
1185
  - [A CSP nonce](https://github.com/softistx/alxia/blob/develop/packages/react-router/docs/guide/csp-nonce.md), [Beside the pages](https://github.com/softistx/alxia/blob/develop/packages/react-router/docs/guide/beside-the-pages.md) (middlewares, routes, WebSockets, the client's files), [OpenAPI](https://github.com/softistx/alxia/blob/develop/packages/react-router/docs/guide/openapi.md), [Testing](https://github.com/softistx/alxia/blob/develop/packages/react-router/docs/guide/testing.md) and [Deploying](https://github.com/softistx/alxia/blob/develop/packages/react-router/docs/guide/deploying.md), with Docker.
1109
1186
  - [Installable and offline](https://github.com/softistx/alxia/blob/develop/packages/react-router/docs/guide/pwa.md): what the subpaths hold, the worker's contract, the worker, what it caches, the offline page, building the worker with `alxia()` or `buildWorker`, the hooks in React, registering the worker and offering an update, serving the worker's script, the web app manifest, and clearing at sign-out.
1110
1187
  - [An API's refusals](https://github.com/softistx/alxia/blob/develop/packages/react-router/docs/guide/errors.md): what `/errors` holds, recognising an API error, the request id, the page a request is for, the catalogue store, the form checks in the API's words, the provider, wording a refusal, loaders and the error page, actions, JSON actions and CSRF, and keeping the catalogue fresh.
1188
+ - [A BFF session](https://github.com/softistx/alxia/blob/develop/packages/react-router/docs/guide/session.md): redirects that stay on the site, the cookies, the sign-in route, the second factor, the viewer and sign-out, the step-up, and the pages a mail's link opens.
1111
1189
  - [Troubleshooting](https://github.com/softistx/alxia/blob/develop/packages/react-router/docs/troubleshooting.md): each message, and the traps that print none.
1112
1190
  - [Roadmap](https://github.com/softistx/alxia/blob/develop/packages/react-router/docs/roadmap.md): what is coming, and what is not planned.
1113
1191
  - [Example](https://github.com/softistx/alxia/tree/develop/examples/react-router): the official template, these four changes, then an `app/server.ts` with a session, an `/api`, secure headers with a nonce and a streamed page.
@@ -0,0 +1,160 @@
1
+ // src/session/model/safe-next.ts
2
+ var HOME2 = "/";
3
+ var BASE = "http://next.invalid";
4
+ function safeNext2(value) {
5
+ if (!value || value.length > 2000)
6
+ return HOME2;
7
+ if (!value.startsWith("/") || value.startsWith("//"))
8
+ return HOME2;
9
+ if (/[\\\p{Cc}\s]/u.test(value))
10
+ return HOME2;
11
+ try {
12
+ const url = new URL(value, BASE);
13
+ const path = `${url.pathname}${url.search}${url.hash}`;
14
+ if (url.origin !== BASE || path.startsWith("//"))
15
+ return HOME2;
16
+ return path;
17
+ } catch {
18
+ return HOME2;
19
+ }
20
+ }
21
+
22
+ // src/session/model/site-path.ts
23
+ function sitePath(path, {
24
+ label,
25
+ query = false
26
+ }) {
27
+ if (safeNext2(path) !== path || !query && /[?#]/.test(path)) {
28
+ const shown = query ? path : path.replace(/[?#].*$/s, "?…");
29
+ throw new TypeError(`${label} must be a path of this site${query ? "" : " with no query or fragment"}, starting with a single "/"; got ${JSON.stringify(shown)}.`);
30
+ }
31
+ return path;
32
+ }
33
+
34
+ // src/session/model/paths.ts
35
+ var SIGN_IN_PATHS2 = {
36
+ signIn: "/sign-in",
37
+ secondFactor: "/sign-in/second-factor"
38
+ };
39
+ function withNext(path, next, query) {
40
+ const params = new URLSearchParams(query);
41
+ const target = safeNext2(next);
42
+ if (target !== HOME2)
43
+ params.set("next", target);
44
+ const search = params.toString();
45
+ return search ? `${path}?${search}` : path;
46
+ }
47
+ function signInPath2(next, options = {}) {
48
+ const path = options.path === undefined ? SIGN_IN_PATHS2.signIn : sitePath(options.path, { label: "signInPath(): options.path" });
49
+ return withNext(path, next, options.query);
50
+ }
51
+ function secondFactorPath2(next, path = SIGN_IN_PATHS2.secondFactor) {
52
+ return withNext(path, next);
53
+ }
54
+ // src/session/model/second-factor.ts
55
+ var TOTP_CODE2 = /^\d{6}$/;
56
+ // src/session/model/step-up.ts
57
+ var INITIAL_STEP_UP2 = {
58
+ phase: "idle",
59
+ locked: false,
60
+ pending: false,
61
+ resent: false
62
+ };
63
+ function requested(state, answer) {
64
+ if (!answer.ok) {
65
+ return {
66
+ ...state,
67
+ phase: state.challenge ? "code" : "idle",
68
+ error: answer.error,
69
+ message: answer.message,
70
+ retryAfter: answer.retryAfter,
71
+ pending: false,
72
+ resent: false
73
+ };
74
+ }
75
+ return {
76
+ phase: "code",
77
+ via: answer.via,
78
+ challenge: answer.challenge,
79
+ locked: false,
80
+ pending: false,
81
+ resent: state.challenge !== undefined
82
+ };
83
+ }
84
+ function refused(state, answer) {
85
+ const none = answer.attemptsLeft === 0;
86
+ const over = answer.error === "expired" || none && state.via === "email";
87
+ if (over) {
88
+ return {
89
+ phase: "idle",
90
+ ...state.via ? { via: state.via } : {},
91
+ locked: false,
92
+ error: "expired",
93
+ message: answer.message,
94
+ pending: false,
95
+ resent: false
96
+ };
97
+ }
98
+ return {
99
+ ...state,
100
+ phase: "code",
101
+ error: answer.error,
102
+ message: answer.message,
103
+ retryAfter: undefined,
104
+ attemptsLeft: answer.attemptsLeft,
105
+ locked: answer.locked || none,
106
+ pending: false,
107
+ resent: false
108
+ };
109
+ }
110
+ function stepUpReducer2(state, action) {
111
+ switch (action.type) {
112
+ case "start":
113
+ return {
114
+ ...state,
115
+ phase: state.challenge ? "code" : "requesting",
116
+ error: undefined,
117
+ message: undefined,
118
+ retryAfter: undefined,
119
+ pending: true,
120
+ resent: false
121
+ };
122
+ case "requested":
123
+ return requested(state, action.answer);
124
+ case "submit":
125
+ return {
126
+ ...state,
127
+ phase: "confirming",
128
+ error: undefined,
129
+ message: undefined,
130
+ retryAfter: undefined,
131
+ pending: true
132
+ };
133
+ case "confirmed":
134
+ return action.answer.ok ? {
135
+ phase: "done",
136
+ ...state.via ? { via: state.via } : {},
137
+ locked: false,
138
+ pending: false,
139
+ resent: false
140
+ } : refused(state, action.answer);
141
+ case "release":
142
+ return INITIAL_STEP_UP2;
143
+ }
144
+ }
145
+ // src/session/model/step-up-props.ts
146
+ function stepUpProps2(state) {
147
+ return {
148
+ ...state.via ? { via: state.via } : {},
149
+ ...state.attemptsLeft === undefined ? {} : { attemptsLeft: state.attemptsLeft },
150
+ locked: state.locked,
151
+ ...state.error ? { error: state.error } : {},
152
+ ...state.retryAfter === undefined ? {} : { retryAfter: state.retryAfter },
153
+ pending: state.pending,
154
+ resent: state.resent
155
+ };
156
+ }
157
+ export { HOME2, safeNext2, sitePath, SIGN_IN_PATHS2, signInPath2, secondFactorPath2, TOTP_CODE2, INITIAL_STEP_UP2, stepUpReducer2, stepUpProps2 };
158
+
159
+ //# debugId=01582ABF6CEE43FB64756E2164756E21
160
+ //# sourceMappingURL=index-0tyw54ad.js.map
@@ -0,0 +1,15 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/session/model/safe-next.ts", "../src/session/model/site-path.ts", "../src/session/model/paths.ts", "../src/session/model/second-factor.ts", "../src/session/model/step-up.ts", "../src/session/model/step-up-props.ts"],
4
+ "sourcesContent": [
5
+ "/** Where a sign-in lands when `next` is missing or unsafe. */\nexport const HOME = '/';\n\n/** The base a `next` is resolved against; `.invalid` never resolves. */\nconst BASE = 'http://next.invalid';\n\n/**\n * The `next` a sign-in may redirect to: a path of this site only. Anything\n * that could leave it (`//host`, `/\\host`, `https://…`, `javascript:…`, a\n * control character a browser would strip, a value over 2000 characters)\n * falls back to `/`. The path is read after normalisation, so `/a/..//host`\n * is refused too.\n *\n * @example\n * safeNext('/orders?page=2'); // '/orders?page=2'\n * safeNext('//evil.test'); // '/'\n */\nexport function safeNext(value: string | null | undefined): string {\n\tif (!value || value.length > 2000) return HOME;\n\tif (!value.startsWith('/') || value.startsWith('//')) return HOME;\n\t// Browsers read `\\` as `/` and drop tabs and newlines in URLs.\n\tif (/[\\\\\\p{Cc}\\s]/u.test(value)) return HOME;\n\ttry {\n\t\tconst url = new URL(value, BASE);\n\t\tconst path = `${url.pathname}${url.search}${url.hash}`;\n\t\t// Dot segments can collapse into `//host` again: check what is answered.\n\t\tif (url.origin !== BASE || path.startsWith('//')) return HOME;\n\t\treturn path;\n\t} catch {\n\t\treturn HOME;\n\t}\n}\n",
6
+ "import { safeNext } from './safe-next';\n\n/**\n * A path of this site, or a `TypeError` naming `label`. With no `query`, the\n * path holds no `?` or `#` either, and the message prints it only up to\n * there, since what follows may be a token.\n */\nexport function sitePath(\n\tpath: string,\n\t{\n\t\tlabel,\n\t\tquery = false,\n\t}: { readonly label: string; readonly query?: boolean | undefined },\n): string {\n\tif (safeNext(path) !== path || (!query && /[?#]/.test(path))) {\n\t\tconst shown = query ? path : path.replace(/[?#].*$/s, '?…');\n\t\tthrow new TypeError(\n\t\t\t`${label} must be a path of this site${query ? '' : ' with no query or fragment'}, starting with a single \"/\"; got ${JSON.stringify(shown)}.`,\n\t\t);\n\t}\n\treturn path;\n}\n",
7
+ "import { HOME, safeNext } from './safe-next';\nimport { sitePath } from './site-path';\n\n/** The two pages of a sign-in. */\nexport interface SignInPaths {\n\treadonly signIn: string;\n\treadonly secondFactor: string;\n}\n\n/** The defaults: `/sign-in` and `/sign-in/second-factor`. */\nexport const SIGN_IN_PATHS: SignInPaths = {\n\tsignIn: '/sign-in',\n\tsecondFactor: '/sign-in/second-factor',\n};\n\nfunction withNext(\n\tpath: string,\n\tnext: string,\n\tquery?: Record<string, string>,\n): string {\n\tconst params = new URLSearchParams(query);\n\tconst target = safeNext(next);\n\tif (target !== HOME) params.set('next', target);\n\tconst search = params.toString();\n\treturn search ? `${path}?${search}` : path;\n}\n\n/**\n * The sign-in page's URL, coming back to `next` (none for the home page, and\n * `next` goes through `safeNext`). `query` adds parameters, such as\n * `{ expired: '1' }`; `path` replaces `/sign-in` and must be a path of this\n * site with no query or fragment (a `TypeError` otherwise).\n *\n * @example\n * signInPath('/orders'); // '/sign-in?next=%2Forders'\n * signInPath('/', { query: { expired: '1' } }); // '/sign-in?expired=1'\n */\nexport function signInPath(\n\tnext: string,\n\toptions: {\n\t\treadonly query?: Record<string, string> | undefined;\n\t\treadonly path?: string | undefined;\n\t} = {},\n): string {\n\tconst path =\n\t\toptions.path === undefined\n\t\t\t? SIGN_IN_PATHS.signIn\n\t\t\t: sitePath(options.path, { label: 'signInPath(): options.path' });\n\treturn withNext(path, next, options.query);\n}\n\n/** The second-factor page's URL, coming back to `next` as `signInPath` does. */\nexport function secondFactorPath(\n\tnext: string,\n\tpath: string = SIGN_IN_PATHS.secondFactor,\n): string {\n\treturn withNext(path, next);\n}\n",
8
+ "/** A code of an authenticator app: six digits. */\nexport const TOTP_CODE: RegExp = /^\\d{6}$/;\n\n/**\n * What a second-factor form sends: `code` is the issued code (an app's, or\n * the one mailed), `recovery` a recovery code. Janus has no name for it, only\n * `confirm` and `recover`.\n */\nexport type SecondFactorKind = 'code' | 'recovery';\n\n/** How a sign-in ended, in `@nxgt/janus`'s words (`SignInResult['status']`). */\nexport type SignInStatus = 'signedIn' | 'secondFactor';\n\n/**\n * How a step-up is confirmed, in `@nxgt/janus`'s words: a code mailed to the\n * current address, or the account's second factor.\n */\nexport type StepUpVia = 'email' | 'secondFactor';\n",
9
+ "import type { StepUpVia } from './second-factor';\nimport type {\n\tStepUpConfirmAnswer,\n\tStepUpError,\n\tStepUpRequestAnswer,\n} from './step-up-answers';\n\n/** Where a step-up is. */\nexport type StepUpPhase =\n\t| 'idle'\n\t| 'requesting'\n\t| 'code'\n\t| 'confirming'\n\t| 'done';\n\nexport interface StepUpState {\n\treadonly phase: StepUpPhase;\n\t/** How the challenge held (or the last one) is confirmed. */\n\treadonly via?: StepUpVia | undefined;\n\t/** The challenge held. A secret: never in a URL, a log or loader data. */\n\treadonly challenge?: string | undefined;\n\t/** The tries the last refused code left. */\n\treadonly attemptsLeft?: number | undefined;\n\t/** No try left on an app or recovery code: wait, asking again would fail too. */\n\treadonly locked: boolean;\n\treadonly error?: StepUpError | undefined;\n\t/** The last refusal worded for the page, until the next step or the release. */\n\treadonly message?: string | undefined;\n\t/** The seconds a throttled request asks to wait before asking again. */\n\treadonly retryAfter?: number | undefined;\n\t/** A request or a code is in flight. */\n\treadonly pending: boolean;\n\t/** The last request replaced a challenge that was held. */\n\treadonly resent: boolean;\n}\n\nexport type StepUpAction =\n\t| { readonly type: 'start' }\n\t| { readonly type: 'requested'; readonly answer: StepUpRequestAnswer }\n\t| { readonly type: 'submit' }\n\t| { readonly type: 'confirmed'; readonly answer: StepUpConfirmAnswer }\n\t| { readonly type: 'release' };\n\nexport const INITIAL_STEP_UP: StepUpState = {\n\tphase: 'idle',\n\tlocked: false,\n\tpending: false,\n\tresent: false,\n};\n\nfunction requested(\n\tstate: StepUpState,\n\tanswer: StepUpRequestAnswer,\n): StepUpState {\n\tif (!answer.ok) {\n\t\t// A resend refused leaves the code form up, and says why in it.\n\t\treturn {\n\t\t\t...state,\n\t\t\tphase: state.challenge ? 'code' : 'idle',\n\t\t\terror: answer.error,\n\t\t\tmessage: answer.message,\n\t\t\tretryAfter: answer.retryAfter,\n\t\t\tpending: false,\n\t\t\tresent: false,\n\t\t};\n\t}\n\treturn {\n\t\tphase: 'code',\n\t\tvia: answer.via,\n\t\tchallenge: answer.challenge,\n\t\tlocked: false,\n\t\tpending: false,\n\t\tresent: state.challenge !== undefined,\n\t};\n}\n\n/** A refused code. At 0 tries left a mailed code is over (ask again), an app or recovery code is locked (wait). */\nfunction refused(\n\tstate: StepUpState,\n\tanswer: Extract<StepUpConfirmAnswer, { ok: false }>,\n): StepUpState {\n\tconst none = answer.attemptsLeft === 0;\n\tconst over = answer.error === 'expired' || (none && state.via === 'email');\n\tif (over) {\n\t\treturn {\n\t\t\tphase: 'idle',\n\t\t\t...(state.via ? { via: state.via } : {}),\n\t\t\tlocked: false,\n\t\t\terror: 'expired',\n\t\t\tmessage: answer.message,\n\t\t\tpending: false,\n\t\t\tresent: false,\n\t\t};\n\t}\n\treturn {\n\t\t...state,\n\t\tphase: 'code',\n\t\terror: answer.error,\n\t\tmessage: answer.message,\n\t\tretryAfter: undefined,\n\t\tattemptsLeft: answer.attemptsLeft,\n\t\tlocked: answer.locked || none,\n\t\tpending: false,\n\t\tresent: false,\n\t};\n}\n\n/**\n * The step-up's transitions, pure. `start` (a request in flight) forgets the\n * last error but keeps a challenge held, so a refused \"Resend code\" leaves\n * the code form up; a refusal keeps its `message` (and a throttle its\n * `retryAfter`) until the next step; a granted request replaces the challenge; a code accepted\n * spends it; an expired or spent one leaves \"ask again\"; at 0 tries left a\n * mailed code is `expired` and an app or recovery code is `locked`.\n */\nexport function stepUpReducer(\n\tstate: StepUpState,\n\taction: StepUpAction,\n): StepUpState {\n\tswitch (action.type) {\n\t\tcase 'start':\n\t\t\treturn {\n\t\t\t\t...state,\n\t\t\t\tphase: state.challenge ? 'code' : 'requesting',\n\t\t\t\terror: undefined,\n\t\t\t\tmessage: undefined,\n\t\t\t\tretryAfter: undefined,\n\t\t\t\tpending: true,\n\t\t\t\tresent: false,\n\t\t\t};\n\t\tcase 'requested':\n\t\t\treturn requested(state, action.answer);\n\t\tcase 'submit':\n\t\t\treturn {\n\t\t\t\t...state,\n\t\t\t\tphase: 'confirming',\n\t\t\t\terror: undefined,\n\t\t\t\tmessage: undefined,\n\t\t\t\tretryAfter: undefined,\n\t\t\t\tpending: true,\n\t\t\t};\n\t\tcase 'confirmed':\n\t\t\treturn action.answer.ok\n\t\t\t\t? {\n\t\t\t\t\t\tphase: 'done',\n\t\t\t\t\t\t...(state.via ? { via: state.via } : {}),\n\t\t\t\t\t\tlocked: false,\n\t\t\t\t\t\tpending: false,\n\t\t\t\t\t\tresent: false,\n\t\t\t\t\t}\n\t\t\t\t: refused(state, action.answer);\n\t\tcase 'release':\n\t\t\treturn INITIAL_STEP_UP;\n\t}\n}\n",
10
+ "import type { StepUpVia } from './second-factor';\nimport type { StepUpState } from './step-up';\nimport type { StepUpError } from './step-up-answers';\n\n/**\n * The props of `@nxgt/material`'s `VerificationCode` and `StepUpDialog` that\n * the state decides. The state's `message` is not one: the dialog words its\n * own texts from `error`, `attemptsLeft`, `locked` and `retryAfter`. The callbacks (`onSubmit`, `onResend`) are the app's.\n */\nexport interface StepUpProps {\n\treadonly via?: StepUpVia;\n\treadonly attemptsLeft?: number;\n\treadonly locked: boolean;\n\treadonly error?: StepUpError;\n\t/** The seconds to wait when `error` is `throttled` and the API said how long. */\n\treadonly retryAfter?: number;\n\treadonly pending: boolean;\n\treadonly resent: boolean;\n}\n\n/** The state as the dialog's props: `{ ...stepUpProps(state), onSubmit, onResend }`. */\nexport function stepUpProps(state: StepUpState): StepUpProps {\n\treturn {\n\t\t...(state.via ? { via: state.via } : {}),\n\t\t...(state.attemptsLeft === undefined\n\t\t\t? {}\n\t\t\t: { attemptsLeft: state.attemptsLeft }),\n\t\tlocked: state.locked,\n\t\t...(state.error ? { error: state.error } : {}),\n\t\t...(state.retryAfter === undefined ? {} : { retryAfter: state.retryAfter }),\n\t\tpending: state.pending,\n\t\tresent: state.resent,\n\t};\n}\n"
11
+ ],
12
+ "mappings": "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",
13
+ "debugId": "01582ABF6CEE43FB64756E2164756E21",
14
+ "names": []
15
+ }
@@ -0,0 +1,124 @@
1
+ // src/errors/api-error.ts
2
+ var isNamed = (error, name) => error instanceof Error && error.name === name;
3
+ function isApiErrorLike2(error) {
4
+ return isNamed(error, "ApiError") && Array.isArray(error.errors) && typeof error.status === "number";
5
+ }
6
+ function waitMinutes2(extensions) {
7
+ const minutes = extensions?.params?.minutes ?? extensions?.minutes;
8
+ if (minutes !== undefined)
9
+ return Math.max(1, Number(minutes) || 1);
10
+ const seconds = extensions?.params?.retryAfter ?? extensions?.retryAfter;
11
+ return Math.max(1, Math.ceil(Number(seconds) / 60) || 1);
12
+ }
13
+ var MAX_RETRY_AFTER = 86400;
14
+ function secondsOf(value) {
15
+ const seconds = typeof value === "string" && /^\d+(\.\d+)?$/.test(value.trim()) ? Number(value.trim()) : value;
16
+ return typeof seconds === "number" && Number.isFinite(seconds) && seconds >= 0 ? Math.min(MAX_RETRY_AFTER, Math.ceil(seconds)) + 0 : undefined;
17
+ }
18
+ function retryAfterOf(error) {
19
+ const extensions = error.errors[0]?.extensions;
20
+ return secondsOf(extensions?.params?.retryAfter) ?? secondsOf(extensions?.retryAfter);
21
+ }
22
+ function attemptsLeftOf2(error) {
23
+ const extensions = error.errors[0]?.extensions;
24
+ for (const value of [
25
+ extensions?.attemptsLeft,
26
+ extensions?.params?.attemptsLeft
27
+ ]) {
28
+ if (typeof value === "number" && Number.isInteger(value) && value >= 0) {
29
+ return value;
30
+ }
31
+ }
32
+ return;
33
+ }
34
+ function valuesOf(extensions) {
35
+ if (!extensions)
36
+ return;
37
+ return extensions.params ? { ...extensions, ...extensions.params } : extensions;
38
+ }
39
+ var isStatusError = (error) => isNamed(error, "ApiStatusError") && typeof error.status === "number";
40
+ var isUnavailableError = (error) => isNamed(error, "ApiUnavailableError") && typeof error.reason === "string";
41
+
42
+ // src/errors/route-error.ts
43
+ import { data } from "react-router";
44
+
45
+ // src/errors/request-id.ts
46
+ var REQUEST_ID_HEADER2 = "x-request-id";
47
+ function requestIdOf2(response) {
48
+ return response.headers.get(REQUEST_ID_HEADER2) || undefined;
49
+ }
50
+
51
+ // src/errors/route-error.ts
52
+ var ROUTED = new Set([401, 403, 404, 503]);
53
+ function routeError2(error, { statuses = ROUTED } = {}) {
54
+ if (isApiErrorLike2(error)) {
55
+ const { requestId } = error;
56
+ if (statuses.has(error.status)) {
57
+ return data({ code: error.code, message: error.message, requestId }, { status: error.status });
58
+ }
59
+ return requestId ? data({ requestId }, { status: 500 }) : error;
60
+ }
61
+ if (error instanceof Response && statuses.has(error.status)) {
62
+ const requestId = requestIdOf2(error);
63
+ if (requestId) {
64
+ return data({ requestId }, { status: error.status });
65
+ }
66
+ }
67
+ return error;
68
+ }
69
+ function errorRequestId2(error) {
70
+ if (typeof error !== "object" || error === null)
71
+ return;
72
+ const { data: body } = error;
73
+ if (typeof body !== "object" || body === null)
74
+ return;
75
+ const { requestId } = body;
76
+ return typeof requestId === "string" && requestId ? requestId : undefined;
77
+ }
78
+
79
+ // src/errors/page-path.ts
80
+ function nextOf2(url) {
81
+ const { pathname, searchParams } = new URL(url);
82
+ const trimmed = pathname.endsWith("/_.data") ? pathname.slice(0, -"_.data".length) : pathname.replace(/\.data$/, "");
83
+ const path = trimmed.replace(/^\/{2,}/, "/");
84
+ const params = new URLSearchParams(searchParams);
85
+ params.delete("_routes");
86
+ for (const value of params.getAll("index")) {
87
+ if (!value)
88
+ params.delete("index", value);
89
+ }
90
+ const search = params.toString();
91
+ return search ? `${path}?${search}` : path;
92
+ }
93
+
94
+ // src/errors/same-site.ts
95
+ import { data as data2 } from "react-router";
96
+ function siteCheck(origin) {
97
+ return (request) => {
98
+ const set = typeof origin === "function" ? origin() : origin;
99
+ const own = set ?? new URL(request.url).origin;
100
+ const from = request.headers.get("origin");
101
+ const site = request.headers.get("sec-fetch-site");
102
+ if (from && from !== own || site && site !== "same-origin" && site !== "none") {
103
+ throw data2("Cross-site request refused", { status: 403 });
104
+ }
105
+ };
106
+ }
107
+ var SITE_CHECK = Symbol.for("@alxia/react-router:site-check@1");
108
+ function rememberSiteCheck(actions, check) {
109
+ Object.defineProperty(actions, SITE_CHECK, {
110
+ value: check,
111
+ enumerable: true,
112
+ writable: false
113
+ });
114
+ }
115
+ var hasSiteCheck = (actions) => typeof actions[SITE_CHECK] === "function";
116
+ function refuseCrossSite(actions, request) {
117
+ const check = actions[SITE_CHECK];
118
+ (typeof check === "function" ? check : siteCheck())(request);
119
+ }
120
+
121
+ export { isApiErrorLike2, waitMinutes2, retryAfterOf, attemptsLeftOf2, valuesOf, isStatusError, isUnavailableError, REQUEST_ID_HEADER2, requestIdOf2, routeError2, errorRequestId2, nextOf2, siteCheck, rememberSiteCheck, hasSiteCheck, refuseCrossSite };
122
+
123
+ //# debugId=6C1CCB39B164FD8364756E2164756E21
124
+ //# sourceMappingURL=index-c3k47yw7.js.map
@@ -0,0 +1,14 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/errors/api-error.ts", "../src/errors/route-error.ts", "../src/errors/request-id.ts", "../src/errors/page-path.ts", "../src/errors/same-site.ts"],
4
+ "sourcesContent": [
5
+ "/**\n * The shape of an API's refusal, as `@nxgt/graphql-client`'s `ApiError`\n * carries it, declared structurally: `/errors` never imports the client, so\n * an app needs it only if it calls the API through it. `api-error.spec.ts`\n * holds the two in step.\n */\n\n/** One refused input field: `extensions.fields` of a validation refusal. */\nexport interface ApiFieldErrorLike {\n\treadonly path: string;\n\treadonly code: string;\n\treadonly message: string;\n\t/** The values the field's message interpolates. */\n\treadonly params?: Readonly<Record<string, unknown>>;\n}\n\n/** The values a message interpolates; a throttle's wait is `minutes` or `retryAfter`. */\nexport interface ApiErrorParamsLike {\n\treadonly minutes?: unknown;\n\treadonly retryAfter?: unknown;\n\treadonly attemptsLeft?: unknown;\n\treadonly [key: string]: unknown;\n}\n\n/**\n * One GraphQL error's `extensions`. `code` is the API's own\n * `<module>.errors.<slug>`; the rest is what its messages name.\n */\nexport interface ApiErrorExtensionsLike {\n\treadonly code?: string;\n\t/** The values the message interpolates, when the API sends them. */\n\treadonly params?: ApiErrorParamsLike;\n\t/** A validation refusal: every field at fault, by input path. */\n\treadonly fields?: readonly ApiFieldErrorLike[];\n\t/** Any other refusal about one input field, by its path. */\n\treadonly field?: string;\n\t/** A throttle's wait, in minutes (`waitMinutes` coerces a string). */\n\treadonly minutes?: unknown;\n\t/** A throttle's wait, in seconds. */\n\treadonly retryAfter?: unknown;\n\t/** The tries a refused code leaves, as `@nxgt/janus` counts them. */\n\treadonly attemptsLeft?: unknown;\n\treadonly [key: string]: unknown;\n}\n\n/** One error of a response's `errors`. */\nexport interface ApiErrorEntryLike {\n\treadonly message: string;\n\treadonly extensions?: ApiErrorExtensionsLike;\n}\n\n/** A refusal read from a GraphQL API: what `ApiError` has, and the request id. */\nexport interface ApiErrorLike extends Error {\n\treadonly errors: readonly ApiErrorEntryLike[];\n\t/** The first error's `extensions.code`. */\n\treadonly code: string | undefined;\n\t/** The HTTP status to answer with. */\n\treadonly status: number;\n\t/** The id the API logged the request under, when the caller kept it. */\n\treadonly requestId?: string;\n}\n\n/** Whether `error` is an `Error` of this `name`: the client's errors are told by it, never `instanceof`. */\nexport const isNamed = (error: unknown, name: string): error is Error =>\n\terror instanceof Error && error.name === name;\n\n/**\n * Whether `error` is an `ApiError` of `@nxgt/graphql-client`, by the test\n * that client's `isApiError` makes (an `Error` named `ApiError` with an\n * `errors` array), plus a numeric `status`, which `ApiErrorLike` promises.\n * It holds across two copies of the client.\n */\nexport function isApiErrorLike(error: unknown): error is ApiErrorLike {\n\treturn (\n\t\tisNamed(error, 'ApiError') &&\n\t\tArray.isArray((error as { errors?: unknown }).errors) &&\n\t\ttypeof (error as { status?: unknown }).status === 'number'\n\t);\n}\n\n/**\n * The minutes a wait lasts, as the API words it: `minutes` (at least one, in\n * `extensions.params` or beside it), else the `retryAfter` seconds rounded\n * up, else one.\n */\nexport function waitMinutes(\n\textensions: ApiErrorExtensionsLike | undefined,\n): number {\n\tconst minutes = extensions?.params?.minutes ?? extensions?.minutes;\n\tif (minutes !== undefined) return Math.max(1, Number(minutes) || 1);\n\tconst seconds = extensions?.params?.retryAfter ?? extensions?.retryAfter;\n\treturn Math.max(1, Math.ceil(Number(seconds) / 60) || 1);\n}\n\nconst MAX_RETRY_AFTER = 86_400;\n\n/** A wait in seconds from one value: a finite number from 0, or a decimal string. */\nfunction secondsOf(value: unknown): number | undefined {\n\tconst seconds =\n\t\ttypeof value === 'string' && /^\\d+(\\.\\d+)?$/.test(value.trim())\n\t\t\t? Number(value.trim())\n\t\t\t: value;\n\treturn typeof seconds === 'number' && Number.isFinite(seconds) && seconds >= 0\n\t\t? Math.min(MAX_RETRY_AFTER, Math.ceil(seconds)) + 0\n\t\t: undefined;\n}\n\n/**\n * The seconds a throttle asks to wait: `retryAfter` of the first error's\n * `extensions.params`, else (when that is unusable) of its `extensions`.\n * Only a finite number from 0, or a decimal string, counts; it is rounded up\n * and held at a day (86 400), and `-0` is 0. `undefined` when the API sent\n * none usable. Internal: `/session` words it as a typed `retryAfter`.\n */\nexport function retryAfterOf(error: ApiErrorLike): number | undefined {\n\tconst extensions = error.errors[0]?.extensions;\n\treturn (\n\t\tsecondsOf(extensions?.params?.retryAfter) ??\n\t\tsecondsOf(extensions?.retryAfter)\n\t);\n}\n\n/**\n * The tries a refused code leaves, as `@nxgt/janus` counts them:\n * `extensions.attemptsLeft` of the first error, else the same key in its\n * `params`. A whole number from 0 up; `undefined` when the API sent none or\n * something that is not a count.\n */\nexport function attemptsLeftOf(error: ApiErrorLike): number | undefined {\n\tconst extensions = error.errors[0]?.extensions;\n\tfor (const value of [\n\t\textensions?.attemptsLeft,\n\t\textensions?.params?.attemptsLeft,\n\t]) {\n\t\tif (typeof value === 'number' && Number.isInteger(value) && value >= 0) {\n\t\t\treturn value;\n\t\t}\n\t}\n\treturn undefined;\n}\n\n/**\n * The values a message names: `extensions.params` over the extensions\n * themselves, where older errors and the extras of a few sit.\n */\nexport function valuesOf(\n\textensions: ApiErrorExtensionsLike | undefined,\n): Record<string, unknown> | undefined {\n\tif (!extensions) return undefined;\n\treturn extensions.params\n\t\t? { ...extensions, ...extensions.params }\n\t\t: extensions;\n}\n\n/** `@nxgt/graphql-client`'s `ApiStatusError`, as far as the failure reads it. */\nexport interface StatusErrorLike extends Error {\n\tstatus: number;\n\tstatusText?: string | undefined;\n\tbody?: unknown;\n}\n\n/** `@nxgt/graphql-client`'s `ApiUnavailableError`, as far as the failure reads it. */\nexport interface UnavailableErrorLike extends Error {\n\treason: string;\n}\n\nexport const isStatusError = (error: unknown): error is StatusErrorLike =>\n\tisNamed(error, 'ApiStatusError') &&\n\ttypeof (error as { status?: unknown }).status === 'number';\n\nexport const isUnavailableError = (\n\terror: unknown,\n): error is UnavailableErrorLike =>\n\tisNamed(error, 'ApiUnavailableError') &&\n\ttypeof (error as { reason?: unknown }).reason === 'string';\n",
6
+ "import { data } from 'react-router';\nimport { isApiErrorLike } from './api-error';\nimport { requestIdOf } from './request-id';\n\n/** The statuses an error page words: unauthenticated, forbidden, missing, unavailable. */\nconst ROUTED: ReadonlySet<number> = new Set([401, 403, 404, 503]);\n\n/**\n * What a route error of the API carries to the error page: the API's code\n * and message when it gave a code, and the `X-Request-Id` of the call as the\n * support reference (`errorRequestId`).\n */\nexport type RouteErrorData = {\n\tcode?: string | undefined;\n\tmessage?: string | undefined;\n\trequestId?: string | undefined;\n};\n\nexport interface RouteErrorOptions {\n\t/** The statuses that become a route error of their own; 401, 403, 404 and 503 by default. */\n\tstatuses?: ReadonlySet<number>;\n}\n\n/**\n * What a loader throws for an error from the API: an `ApiError` whose\n * status is routed (401, 403, 404 or 503) becomes a route error response of\n * that status, which the root `ErrorBoundary` renders as its page, with the\n * call's request id when the API gave one. A thrown routed `Response` that\n * carries a request id becomes one too (a bare `Response` hands the browser\n * its body only). Any other `ApiError` that has a request id becomes a 500\n * with just that id, so the generic error page can show it. Anything else\n * is returned as it is.\n */\nexport function routeError(\n\terror: unknown,\n\t{ statuses = ROUTED }: RouteErrorOptions = {},\n): unknown {\n\tif (isApiErrorLike(error)) {\n\t\tconst { requestId } = error;\n\t\tif (statuses.has(error.status)) {\n\t\t\treturn data<RouteErrorData>(\n\t\t\t\t{ code: error.code, message: error.message, requestId },\n\t\t\t\t{ status: error.status },\n\t\t\t);\n\t\t}\n\t\treturn requestId\n\t\t\t? data<RouteErrorData>({ requestId }, { status: 500 })\n\t\t\t: error;\n\t}\n\tif (error instanceof Response && statuses.has(error.status)) {\n\t\tconst requestId = requestIdOf(error);\n\t\tif (requestId) {\n\t\t\treturn data<RouteErrorData>({ requestId }, { status: error.status });\n\t\t}\n\t}\n\treturn error;\n}\n\n/**\n * The support reference an error page shows: the API's `X-Request-Id` a\n * route error carries (`routeError`), if it carries one.\n */\nexport function errorRequestId(error: unknown): string | undefined {\n\tif (typeof error !== 'object' || error === null) return undefined;\n\tconst { data: body } = error as { data?: unknown };\n\tif (typeof body !== 'object' || body === null) return undefined;\n\tconst { requestId } = body as { requestId?: unknown };\n\treturn typeof requestId === 'string' && requestId ? requestId : undefined;\n}\n",
7
+ "/** The header an API echoes on every response: the id its logs carry. */\nexport const REQUEST_ID_HEADER = 'x-request-id';\n\n/** The request id a response carries, if the API sent one. */\nexport function requestIdOf(response: Response): string | undefined {\n\treturn response.headers.get(REQUEST_ID_HEADER) || undefined;\n}\n",
8
+ "/**\n * The page a request is for, as `next` carries it back after sign-in: its\n * path and search, without React Router's single-fetch details. A client\n * navigation's `request.url` is `/todos/demo/list.data?q=a&_routes=…`\n * (`/_.data` for `/`, `?index` for an index action), as the server\n * runtime's own `normalizeUrl` strips them.\n */\nexport function nextOf(url: string): string {\n\tconst { pathname, searchParams } = new URL(url);\n\tconst trimmed = pathname.endsWith('/_.data')\n\t\t? pathname.slice(0, -'_.data'.length)\n\t\t: pathname.replace(/\\.data$/, '');\n\t// `new URL('http://h/\\\\evil.com')` has the path `//evil.com`: never let a\n\t// `next` start with two slashes, which a redirect reads as another site.\n\tconst path = trimmed.replace(/^\\/{2,}/, '/');\n\tconst params = new URLSearchParams(searchParams);\n\tparams.delete('_routes');\n\t// An empty `index` names the index route; a valued one is the page's own.\n\tfor (const value of params.getAll('index')) {\n\t\tif (!value) params.delete('index', value);\n\t}\n\tconst search = params.toString();\n\treturn search ? `${path}?${search}` : path;\n}\n",
9
+ "import { data } from 'react-router';\n\n/** Refuses a request from another site with a 403; reads headers only. */\nexport type SiteCheck = (request: Request) => void;\n\n/**\n * The same-site rule of `readJson`: an `Origin` that is not the app's own, or\n * a `Sec-Fetch-Site` that is neither `same-origin` nor `none`, is a 403.\n * `origin` is the app's configured origin (or a getter), else the request's.\n */\nexport function siteCheck(\n\torigin?: string | (() => string | undefined) | undefined,\n): SiteCheck {\n\treturn (request) => {\n\t\tconst set = typeof origin === 'function' ? origin() : origin;\n\t\tconst own = set ?? new URL(request.url).origin;\n\t\tconst from = request.headers.get('origin');\n\t\tconst site = request.headers.get('sec-fetch-site');\n\t\tif (\n\t\t\t(from && from !== own) ||\n\t\t\t(site && site !== 'same-origin' && site !== 'none')\n\t\t) {\n\t\t\tthrow data('Cross-site request refused', { status: 403 });\n\t\t}\n\t};\n}\n\n/**\n * Where a `jsonActions` instance keeps its site check: an enumerable property\n * under a registered symbol, so it survives `{ ...json }` and a second copy of\n * the package. Not public.\n */\nexport const SITE_CHECK = Symbol.for('@alxia/react-router:site-check@1');\n\n/** Puts the check on a `jsonActions` instance. */\nexport function rememberSiteCheck(actions: object, check: SiteCheck): void {\n\tObject.defineProperty(actions, SITE_CHECK, {\n\t\tvalue: check,\n\t\tenumerable: true,\n\t\twritable: false,\n\t});\n}\n\n/** Whether `actions` carries a site check, as every `jsonActions()` instance does. */\nexport const hasSiteCheck = (actions: object): boolean =>\n\ttypeof (actions as Record<symbol, unknown>)[SITE_CHECK] === 'function';\n\n/**\n * Refuses a cross-site request with the rule of the `jsonActions` instance\n * (its configured origin, read at each request); an object without a check\n * gets the request's own origin. It reads headers only, never the body.\n */\nexport function refuseCrossSite(actions: object, request: Request): void {\n\tconst check = (actions as Record<symbol, unknown>)[SITE_CHECK];\n\t(typeof check === 'function' ? (check as SiteCheck) : siteCheck())(request);\n}\n"
10
+ ],
11
+ "mappings": "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",
12
+ "debugId": "6C1CCB39B164FD8364756E2164756E21",
13
+ "names": []
14
+ }
@@ -16,6 +16,7 @@ export interface ApiFieldErrorLike {
16
16
  export interface ApiErrorParamsLike {
17
17
  readonly minutes?: unknown;
18
18
  readonly retryAfter?: unknown;
19
+ readonly attemptsLeft?: unknown;
19
20
  readonly [key: string]: unknown;
20
21
  }
21
22
  /**
@@ -34,6 +35,8 @@ export interface ApiErrorExtensionsLike {
34
35
  readonly minutes?: unknown;
35
36
  /** A throttle's wait, in seconds. */
36
37
  readonly retryAfter?: unknown;
38
+ /** The tries a refused code leaves, as `@nxgt/janus` counts them. */
39
+ readonly attemptsLeft?: unknown;
37
40
  readonly [key: string]: unknown;
38
41
  }
39
42
  /** One error of a response's `errors`. */
@@ -66,6 +69,21 @@ export declare function isApiErrorLike(error: unknown): error is ApiErrorLike;
66
69
  * up, else one.
67
70
  */
68
71
  export declare function waitMinutes(extensions: ApiErrorExtensionsLike | undefined): number;
72
+ /**
73
+ * The seconds a throttle asks to wait: `retryAfter` of the first error's
74
+ * `extensions.params`, else (when that is unusable) of its `extensions`.
75
+ * Only a finite number from 0, or a decimal string, counts; it is rounded up
76
+ * and held at a day (86 400), and `-0` is 0. `undefined` when the API sent
77
+ * none usable. Internal: `/session` words it as a typed `retryAfter`.
78
+ */
79
+ export declare function retryAfterOf(error: ApiErrorLike): number | undefined;
80
+ /**
81
+ * The tries a refused code leaves, as `@nxgt/janus` counts them:
82
+ * `extensions.attemptsLeft` of the first error, else the same key in its
83
+ * `params`. A whole number from 0 up; `undefined` when the API sent none or
84
+ * something that is not a count.
85
+ */
86
+ export declare function attemptsLeftOf(error: ApiErrorLike): number | undefined;
69
87
  /**
70
88
  * The values a message names: `extensions.params` over the extensions
71
89
  * themselves, where older errors and the extras of a few sit.
@@ -1 +1 @@
1
- {"version":3,"file":"api-error.d.ts","sourceRoot":"","sources":["../../src/errors/api-error.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,4EAA4E;AAC5E,MAAM,WAAW,iBAAiB;IACjC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,mDAAmD;IACnD,QAAQ,CAAC,MAAM,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CACpD;AAED,yFAAyF;AACzF,MAAM,WAAW,kBAAkB;IAClC,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAC;IAC9B,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CAChC;AAED;;;GAGG;AACH,MAAM,WAAW,sBAAsB;IACtC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,oEAAoE;IACpE,QAAQ,CAAC,MAAM,CAAC,EAAE,kBAAkB,CAAC;IACrC,iEAAiE;IACjE,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,iBAAiB,EAAE,CAAC;IAC/C,4DAA4D;IAC5D,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,sEAAsE;IACtE,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAC3B,qCAAqC;IACrC,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAC;IAC9B,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CAChC;AAED,0CAA0C;AAC1C,MAAM,WAAW,iBAAiB;IACjC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,UAAU,CAAC,EAAE,sBAAsB,CAAC;CAC7C;AAED,kFAAkF;AAClF,MAAM,WAAW,YAAa,SAAQ,KAAK;IAC1C,QAAQ,CAAC,MAAM,EAAE,SAAS,iBAAiB,EAAE,CAAC;IAC9C,2CAA2C;IAC3C,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,sCAAsC;IACtC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,wEAAwE;IACxE,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,4GAA4G;AAC5G,eAAO,MAAM,OAAO,GAAI,OAAO,OAAO,EAAE,MAAM,MAAM,KAAG,KAAK,IAAI,KAClB,CAAC;AAE/C;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,YAAY,CAMpE;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAC1B,UAAU,EAAE,sBAAsB,GAAG,SAAS,GAC5C,MAAM,CAKR;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CACvB,UAAU,EAAE,sBAAsB,GAAG,SAAS,GAC5C,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAKrC;AAED,iFAAiF;AACjF,MAAM,WAAW,eAAgB,SAAQ,KAAK;IAC7C,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,IAAI,CAAC,EAAE,OAAO,CAAC;CACf;AAED,sFAAsF;AACtF,MAAM,WAAW,oBAAqB,SAAQ,KAAK;IAClD,MAAM,EAAE,MAAM,CAAC;CACf;AAED,eAAO,MAAM,aAAa,GAAI,OAAO,OAAO,KAAG,KAAK,IAAI,eAEG,CAAC;AAE5D,eAAO,MAAM,kBAAkB,GAC9B,OAAO,OAAO,KACZ,KAAK,IAAI,oBAE+C,CAAC"}
1
+ {"version":3,"file":"api-error.d.ts","sourceRoot":"","sources":["../../src/errors/api-error.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- export { type ApiErrorEntryLike, type ApiErrorExtensionsLike, type ApiErrorLike, type ApiErrorParamsLike, type ApiFieldErrorLike, isApiErrorLike, waitMinutes, } from './api-error';
1
+ export { type ApiErrorEntryLike, type ApiErrorExtensionsLike, type ApiErrorLike, type ApiErrorParamsLike, type ApiFieldErrorLike, attemptsLeftOf, isApiErrorLike, waitMinutes, } from './api-error';
2
2
  export { type ApiErrors, type ApiErrorsOptions, type ApiRefusal, createApiErrors, } from './api-errors';
3
3
  export { apiCallFailure, type CallFailureOptions, type DocumentLike, operationName, } from './call-failure';
4
4
  export { type CatalogueReply, createErrorCatalogue, type ErrorCatalogue, type ErrorCatalogueOptions, } from './catalogue';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/errors/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,KAAK,iBAAiB,EACtB,KAAK,sBAAsB,EAC3B,KAAK,YAAY,EACjB,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,EACtB,cAAc,EACd,WAAW,GACX,MAAM,aAAa,CAAC;AACrB,OAAO,EACN,KAAK,SAAS,EACd,KAAK,gBAAgB,EACrB,KAAK,UAAU,EACf,eAAe,GACf,MAAM,cAAc,CAAC;AACtB,OAAO,EACN,cAAc,EACd,KAAK,kBAAkB,EACvB,KAAK,YAAY,EACjB,aAAa,GACb,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACN,KAAK,cAAc,EACnB,oBAAoB,EACpB,KAAK,cAAc,EACnB,KAAK,qBAAqB,GAC1B,MAAM,aAAa,CAAC;AACrB,OAAO,EACN,KAAK,eAAe,EACpB,KAAK,sBAAsB,EAC3B,eAAe,EACf,KAAK,aAAa,GAClB,MAAM,iBAAiB,CAAC;AACzB,YAAY,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EACN,KAAK,WAAW,EAChB,KAAK,kBAAkB,EACvB,WAAW,GACX,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACN,KAAK,WAAW,EAChB,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACtB,iBAAiB,EACjB,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,SAAS,GACd,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EACN,mBAAmB,EACnB,KAAK,wBAAwB,GAC7B,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC9D,OAAO,EACN,cAAc,EACd,KAAK,cAAc,EACnB,KAAK,iBAAiB,EACtB,UAAU,GACV,MAAM,eAAe,CAAC;AACvB,YAAY,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAC1D,OAAO,EAAE,KAAK,QAAQ,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/errors/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,KAAK,iBAAiB,EACtB,KAAK,sBAAsB,EAC3B,KAAK,YAAY,EACjB,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,EACtB,cAAc,EACd,cAAc,EACd,WAAW,GACX,MAAM,aAAa,CAAC;AACrB,OAAO,EACN,KAAK,SAAS,EACd,KAAK,gBAAgB,EACrB,KAAK,UAAU,EACf,eAAe,GACf,MAAM,cAAc,CAAC;AACtB,OAAO,EACN,cAAc,EACd,KAAK,kBAAkB,EACvB,KAAK,YAAY,EACjB,aAAa,GACb,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACN,KAAK,cAAc,EACnB,oBAAoB,EACpB,KAAK,cAAc,EACnB,KAAK,qBAAqB,GAC1B,MAAM,aAAa,CAAC;AACrB,OAAO,EACN,KAAK,eAAe,EACpB,KAAK,sBAAsB,EAC3B,eAAe,EACf,KAAK,aAAa,GAClB,MAAM,iBAAiB,CAAC;AACzB,YAAY,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EACN,KAAK,WAAW,EAChB,KAAK,kBAAkB,EACvB,WAAW,GACX,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACN,KAAK,WAAW,EAChB,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACtB,iBAAiB,EACjB,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,SAAS,GACd,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EACN,mBAAmB,EACnB,KAAK,wBAAwB,GAC7B,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC9D,OAAO,EACN,cAAc,EACd,KAAK,cAAc,EACnB,KAAK,iBAAiB,EACtB,UAAU,GACV,MAAM,eAAe,CAAC;AACvB,YAAY,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAC1D,OAAO,EAAE,KAAK,QAAQ,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC"}