@atlasauth/js 0.7.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +47 -0
- package/dist/api-keys.d.ts +9 -1
- package/dist/api-keys.js +14 -2
- package/dist/api-keys.js.map +1 -1
- package/dist/attempt.d.ts +13 -0
- package/dist/attempt.js +10 -1
- package/dist/attempt.js.map +1 -1
- package/dist/environment.d.ts +79 -0
- package/dist/environment.js +74 -0
- package/dist/environment.js.map +1 -0
- package/dist/errors.d.ts +58 -0
- package/dist/errors.js +143 -0
- package/dist/errors.js.map +1 -0
- package/dist/fapi.d.ts +17 -0
- package/dist/fapi.js +20 -1
- package/dist/fapi.js.map +1 -1
- package/dist/frontchannel-logout.d.ts +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/instance-settings.d.ts +9 -0
- package/dist/instance-settings.js +2 -1
- package/dist/instance-settings.js.map +1 -1
- package/dist/reauth.d.ts +3 -1
- package/dist/reauth.js +14 -2
- package/dist/reauth.js.map +1 -1
- package/dist/sign-out-guard.d.ts +114 -0
- package/dist/sign-out-guard.js +197 -0
- package/dist/sign-out-guard.js.map +1 -0
- package/dist/sign-out.d.ts +28 -0
- package/dist/sign-out.js +47 -1
- package/dist/sign-out.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,52 @@
|
|
|
1
1
|
# @atlasauth/js changelog
|
|
2
2
|
|
|
3
|
+
## 0.9.0
|
|
4
|
+
- **Added `reason?: string` and `meta?: Record<string, unknown>` to `FieldError`**, parsed from the Frontend API
|
|
5
|
+
error envelope (`{ code, message, reason?, param?, meta? }`). `code` is unchanged. A network failure now carries
|
|
6
|
+
the client-side reason `network`.
|
|
7
|
+
- **Added `reason?: string` and `meta?: Record<string, unknown>` to `ApiKeyRequestError`** (new optional
|
|
8
|
+
constructor arguments `reason`, `meta` after `param`). `reauthenticate()` errors carry `.reason` too.
|
|
9
|
+
- **New: `localizedErrorMessage(error, strings?, fallback?)`** → the text to show for a server error: the catalog
|
|
10
|
+
string `error.<reason>` (placeholders filled from `meta`), else the server's `message`, else `error.generic`,
|
|
11
|
+
else an English generic sentence — never blank. `strings` is a flat catalog or a `(key) => string | undefined`
|
|
12
|
+
lookup (a lookup that echoes the key counts as missing). `ErrorStrings` accepts any object, so an
|
|
13
|
+
`interface`-typed bundle such as `esES` from `@atlasauth/localizations` passes without a cast.
|
|
14
|
+
- **New: `ERROR_REASONS`** (the 38 reasons the shipped catalogs translate), type **`ErrorReason`**,
|
|
15
|
+
**`errorReasonKey(reason)`** (`error.<reason>`), types **`ErrorLike`** and **`ErrorStrings`**, and
|
|
16
|
+
**`catalogLocale(catalog)`** (a bundle's own `locale` tag).
|
|
17
|
+
- **New: `FapiClientOptions.locale?: string`** — sent as `Accept-Language` on every request so the server's
|
|
18
|
+
`message` comes back in that language; `FapiClient.locale` getter and **`acceptLanguageFor(tag)`** (normalises
|
|
19
|
+
`pt_BR` → `pt-BR`, rejects anything that is not a plain BCP-47 tag). **`ReauthOptions.locale?: string`** does the
|
|
20
|
+
same for `reauthenticate()`.
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
## 0.8.0
|
|
24
|
+
- **New: `SignOutGuard`** — the safer sign-out every framework provider now drives, so they all behave like
|
|
25
|
+
`@atlasauth/react`: `new SignOutGuard({ request, frontendApi?, store?, tombstones?, retryDelaysMs?,
|
|
26
|
+
frontChannelLogout? })` (`SignOutGuardOptions`). Methods: `revoke(sessionId, { frontchannel?, request? })` →
|
|
27
|
+
`SignOutAttempt` (`{ sessionId, outcome }`; finds the cookie session with `GET /v1/client` when the id is unknown,
|
|
28
|
+
tombstones it before revoking, revokes with one retry, runs front-channel logout, and re-tries a failed revoke in
|
|
29
|
+
the background after 15 s, 1 min and 5 min); `settle(attempt, { strict? })` → `SignOutResult` (throws
|
|
30
|
+
`SignOutError` under `strict`); `promoted(attempt)` → the server-promoted session id to adopt, or null;
|
|
31
|
+
`refuse(sessionId)` → whether a booted session must not be adopted (re-tries its revoke in the background and
|
|
32
|
+
removes the tombstone once it lands); `isSignedOut(id)`; `signal(attempt)` / `learn(message)` for the cross-tab
|
|
33
|
+
`{ signedOut: string[] }` message; `watch(current, onSignedOut)` (another tab signed out of this session);
|
|
34
|
+
`ready`; `dispose()`; `tombstones`. **Added types** `FapiRequest`, `SignOutGuardOptions`, `SignOutAttempt`,
|
|
35
|
+
`SignedOutSignal`.
|
|
36
|
+
- **Tombstones on platforms without `localStorage`.** `SessionTombstonesOptions.store?: TombstoneStore` (new type:
|
|
37
|
+
`{ getItem(key), setItem(key, value) }`, sync or async) keeps the tombstones in the platform's own store
|
|
38
|
+
(AsyncStorage, `expo-secure-store`, `chrome.storage`, Capacitor Preferences…). **Added**
|
|
39
|
+
`SessionTombstones.ready: Promise<void>` (settles once the store has been read; immediate for `localStorage`).
|
|
40
|
+
- **Email delivery.** `InstanceSettings.emailDelivery: EmailDelivery` (new type: `{ available, passwordReset,
|
|
41
|
+
passwordSignUp }`, read from `email_delivery` on `GET /v1/appearance`; all `true` on a server that does not send
|
|
42
|
+
it). **New helpers:** `emailDeliveryFrom(body)` (an environment / appearance body or the object itself),
|
|
43
|
+
`offeredFirstFactors(strategies, delivery)` (drops `email_code` / `email_link` while email can't be delivered),
|
|
44
|
+
and the constants `EMAIL_DELIVERY_UNKNOWN`, `EMAIL_UNAVAILABLE_MESSAGE`, `EMAIL_FIRST_FACTORS`.
|
|
45
|
+
- **New: typed client environment.** `fetchClientEnvironment(client)` → `ClientEnvironment | null` and
|
|
46
|
+
`clientEnvironmentFrom(body)` for `GET /v1/client/environment`: `{ firstFactors, strategies, emailDelivery,
|
|
47
|
+
socialProviders }`, with the new `EnvironmentSocialProvider` type (`provider`, `native`, `mode`, `label`,
|
|
48
|
+
`display_name`, `icon`, `allow_sign_in`, `allow_sign_up`, `client_id?`, `bot_username?`).
|
|
49
|
+
|
|
3
50
|
## 0.7.0
|
|
4
51
|
- **Popup OAuth completes when the opener page sends `Cross-Origin-Opener-Policy: same-origin`.** That
|
|
5
52
|
header puts the provider popup in another browsing-context group: `popup.closed` reads `true` at once and
|
package/dist/api-keys.d.ts
CHANGED
|
@@ -62,7 +62,15 @@ export declare class ApiKeyRequestError extends Error {
|
|
|
62
62
|
readonly code: string;
|
|
63
63
|
readonly status: number;
|
|
64
64
|
readonly param?: string;
|
|
65
|
-
|
|
65
|
+
/**
|
|
66
|
+
* The server's stable reason (`too_many_attempts`, …), when it sent one: the
|
|
67
|
+
* catalog key `error.<reason>` a UI shows instead of `message`. See
|
|
68
|
+
* `localizedErrorMessage`.
|
|
69
|
+
*/
|
|
70
|
+
readonly reason?: string;
|
|
71
|
+
/** The server's `meta` detail for this error, when it sent one. */
|
|
72
|
+
readonly meta?: Record<string, unknown>;
|
|
73
|
+
constructor(status: number, code: string, message: string, param?: string, reason?: string, meta?: Record<string, unknown>);
|
|
66
74
|
/** True when the action needs a fresh re-authentication first. */
|
|
67
75
|
get needsReverification(): boolean;
|
|
68
76
|
}
|
package/dist/api-keys.js
CHANGED
|
@@ -10,12 +10,24 @@ class ApiKeyRequestError extends Error {
|
|
|
10
10
|
code;
|
|
11
11
|
status;
|
|
12
12
|
param;
|
|
13
|
-
|
|
13
|
+
/**
|
|
14
|
+
* The server's stable reason (`too_many_attempts`, …), when it sent one: the
|
|
15
|
+
* catalog key `error.<reason>` a UI shows instead of `message`. See
|
|
16
|
+
* `localizedErrorMessage`.
|
|
17
|
+
*/
|
|
18
|
+
reason;
|
|
19
|
+
/** The server's `meta` detail for this error, when it sent one. */
|
|
20
|
+
meta;
|
|
21
|
+
constructor(status, code, message, param, reason, meta) {
|
|
14
22
|
super(message);
|
|
15
23
|
this.name = 'ApiKeyRequestError';
|
|
16
24
|
this.status = status;
|
|
17
25
|
this.code = code;
|
|
18
26
|
this.param = param;
|
|
27
|
+
if (reason)
|
|
28
|
+
this.reason = reason;
|
|
29
|
+
if (meta)
|
|
30
|
+
this.meta = meta;
|
|
19
31
|
}
|
|
20
32
|
/** True when the action needs a fresh re-authentication first. */
|
|
21
33
|
get needsReverification() {
|
|
@@ -28,7 +40,7 @@ async function call(client, path, init = {}) {
|
|
|
28
40
|
const res = await client.request(path, init);
|
|
29
41
|
if (!res.ok || !res.data) {
|
|
30
42
|
const first = res.errors[0];
|
|
31
|
-
throw new ApiKeyRequestError(res.status, first?.code ?? 'UNKNOWN', first?.message ?? 'The request failed.', first?.param);
|
|
43
|
+
throw new ApiKeyRequestError(res.status, first?.code ?? 'UNKNOWN', first?.message ?? 'The request failed.', first?.param, first?.reason, first?.meta);
|
|
32
44
|
}
|
|
33
45
|
return res.data;
|
|
34
46
|
}
|
package/dist/api-keys.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api-keys.js","sourceRoot":"","sources":["../src/api-keys.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"api-keys.js","sourceRoot":"","sources":["../src/api-keys.ts"],"names":[],"mappings":"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"}
|
package/dist/attempt.d.ts
CHANGED
|
@@ -78,6 +78,19 @@ export interface FieldError {
|
|
|
78
78
|
code: string;
|
|
79
79
|
message: string;
|
|
80
80
|
param?: string;
|
|
81
|
+
/**
|
|
82
|
+
* The server's stable, machine-readable reason for this error
|
|
83
|
+
* (`form_password_incorrect`, `too_many_attempts`, …), finer-grained than
|
|
84
|
+
* `code`. Prebuilt UIs look it up as the catalog key `error.<reason>` to show
|
|
85
|
+
* the message in the app's language; see `localizedErrorMessage`. Absent on
|
|
86
|
+
* servers that predate it and on errors raised client-side.
|
|
87
|
+
*/
|
|
88
|
+
reason?: string;
|
|
89
|
+
/**
|
|
90
|
+
* Extra machine-readable detail the server attached (`min_length`,
|
|
91
|
+
* `providers`, …); also the interpolation values for `error.<reason>`.
|
|
92
|
+
*/
|
|
93
|
+
meta?: Record<string, unknown>;
|
|
81
94
|
}
|
|
82
95
|
export declare function fieldErrors(body: unknown): FieldError[];
|
|
83
96
|
/** Errors for one field, so a form can render them inline. */
|
package/dist/attempt.js
CHANGED
|
@@ -76,7 +76,16 @@ function fieldErrors(body) {
|
|
|
76
76
|
.filter((error) => {
|
|
77
77
|
return Boolean(error) && typeof error.message === 'string';
|
|
78
78
|
})
|
|
79
|
-
.map((error) =>
|
|
79
|
+
.map((error) => {
|
|
80
|
+
const out = { code: error.code, message: error.message, param: error.param };
|
|
81
|
+
const reason = error.reason;
|
|
82
|
+
if (typeof reason === 'string' && reason !== '')
|
|
83
|
+
out.reason = reason;
|
|
84
|
+
const meta = error.meta;
|
|
85
|
+
if (meta && typeof meta === 'object' && !Array.isArray(meta))
|
|
86
|
+
out.meta = meta;
|
|
87
|
+
return out;
|
|
88
|
+
});
|
|
80
89
|
}
|
|
81
90
|
/** Errors for one field, so a form can render them inline. */
|
|
82
91
|
function errorsFor(errors, param) {
|
package/dist/attempt.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"attempt.js","sourceRoot":"","sources":["../src/attempt.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;GAYG;;AAkDH,4BAqDC;AAGD,gCAEC;
|
|
1
|
+
{"version":3,"file":"attempt.js","sourceRoot":"","sources":["../src/attempt.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;GAYG;;AAkDH,4BAqDC;AAGD,gCAEC;AA6BD,kCAiBC;AAGD,8BAEC;AAGD,gCAEC;AAlHD,SAAgB,QAAQ,CAAC,OAAoB;IAC3C,QAAQ,OAAO,CAAC,MAAuB,EAAE,CAAC;QACxC,KAAK,kBAAkB;YACrB,OAAO,EAAE,IAAI,EAAE,oBAAoB,EAAE,CAAC;QAExC,KAAK,oBAAoB;YACvB,OAAO;gBACL,IAAI,EAAE,sBAAsB;gBAC5B,sEAAsE;gBACtE,qEAAqE;gBACrE,uEAAuE;gBACvE,wCAAwC;gBACxC,UAAU,EAAE,OAAO,CAAC,uBAAuB,IAAI,EAAE;aAClD,CAAC;QAEJ,KAAK,qBAAqB;YACxB,OAAO,EAAE,IAAI,EAAE,uBAAuB,EAAE,CAAC;QAE3C;;;;;WAKG;QACH,KAAK,sBAAsB;YACzB,OAAO,EAAE,IAAI,EAAE,sBAAsB,EAAE,CAAC;QAE1C,KAAK,0BAA0B;YAC7B,OAAO,EAAE,IAAI,EAAE,oBAAoB,EAAE,CAAC;QAExC,KAAK,eAAe;YAClB,OAAO,EAAE,IAAI,EAAE,iBAAiB,EAAE,CAAC;QAErC,KAAK,sBAAsB;YACzB,OAAO,EAAE,IAAI,EAAE,aAAa,EAAE,CAAC;QAEjC,KAAK,oBAAoB;YACvB,OAAO,EAAE,IAAI,EAAE,sBAAsB,EAAE,CAAC;QAE1C,KAAK,UAAU;YACb,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,CAAC,kBAAkB,IAAI,IAAI,EAAE,CAAC;QAEzE,KAAK,WAAW;YACd,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC;QAElD;YACE;;;;eAIG;YACH,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC;IACvD,CAAC;AACH,CAAC;AAED,iFAAiF;AACjF,SAAgB,UAAU,CAAC,OAAoB;IAC7C,OAAO,OAAO,CAAC,MAAM,KAAK,UAAU,IAAI,OAAO,CAAC,MAAM,KAAK,WAAW,CAAC;AACzE,CAAC;AA6BD,SAAgB,WAAW,CAAC,IAAa;IACvC,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ;QAAE,OAAO,EAAE,CAAC;IACjD,MAAM,MAAM,GAAI,IAA6B,CAAC,MAAM,CAAC;IACrD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;QAAE,OAAO,EAAE,CAAC;IAEtC,OAAO,MAAM;SACV,MAAM,CAAC,CAAC,KAAK,EAAuB,EAAE;QACrC,OAAO,OAAO,CAAC,KAAK,CAAC,IAAI,OAAQ,KAAoB,CAAC,OAAO,KAAK,QAAQ,CAAC;IAC7E,CAAC,CAAC;SACD,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;QACb,MAAM,GAAG,GAAe,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,CAAC;QACzF,MAAM,MAAM,GAAI,KAA8B,CAAC,MAAM,CAAC;QACtD,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,EAAE;YAAE,GAAG,CAAC,MAAM,GAAG,MAAM,CAAC;QACrE,MAAM,IAAI,GAAI,KAA4B,CAAC,IAAI,CAAC;QAChD,IAAI,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC;YAAE,GAAG,CAAC,IAAI,GAAG,IAA+B,CAAC;QACzG,OAAO,GAAG,CAAC;IACb,CAAC,CAAC,CAAC;AACP,CAAC;AAED,8DAA8D;AAC9D,SAAgB,SAAS,CAAC,MAA6B,EAAE,KAAa;IACpE,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,KAAK,KAAK,CAAC,CAAC;AACzD,CAAC;AAED,iEAAiE;AACjE,SAAgB,UAAU,CAAC,MAA6B;IACtD,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;AAChD,CAAC"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { FapiClient } from './fapi';
|
|
2
|
+
/**
|
|
3
|
+
* The client environment (`GET /v1/client/environment`) and the email-delivery
|
|
4
|
+
* facts both it and `GET /v1/appearance` publish.
|
|
5
|
+
*
|
|
6
|
+
* An app that cannot deliver email (a production instance with no email
|
|
7
|
+
* provider) cannot send a reset code, an email sign-in code or link, or a
|
|
8
|
+
* sign-up verification code. The server already stops offering email code / link
|
|
9
|
+
* and refuses those requests; `email_delivery` lets a sign-in UI say so up front
|
|
10
|
+
* instead: hide "Forgot password?" and email-code options, and show a "can't
|
|
11
|
+
* send email" state where a sign-up would need one.
|
|
12
|
+
*/
|
|
13
|
+
/** `email_delivery`: what works given whether the app can deliver email. */
|
|
14
|
+
export interface EmailDelivery {
|
|
15
|
+
/** The app can send email (its own provider, or the shared development sender). */
|
|
16
|
+
available: boolean;
|
|
17
|
+
/** "Forgot password?" can work: password sign-in is on and reset emails can be delivered. */
|
|
18
|
+
passwordReset: boolean;
|
|
19
|
+
/** Email + password sign-up can complete (email can be delivered, or verification is not required). */
|
|
20
|
+
passwordSignUp: boolean;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* What an older server (no `email_delivery`) is taken to mean: everything works.
|
|
24
|
+
* The server still refuses whatever it cannot deliver.
|
|
25
|
+
*/
|
|
26
|
+
export declare const EMAIL_DELIVERY_UNKNOWN: Readonly<EmailDelivery>;
|
|
27
|
+
/** The default English copy for the "can't send email" state. */
|
|
28
|
+
export declare const EMAIL_UNAVAILABLE_MESSAGE = "This app can't send email right now. Try another sign-in method or contact the app owner.";
|
|
29
|
+
/** The first factors that need the app to deliver email. */
|
|
30
|
+
export declare const EMAIL_FIRST_FACTORS: readonly string[];
|
|
31
|
+
/**
|
|
32
|
+
* Read `email_delivery` from a `/v1/client/environment` or `/v1/appearance`
|
|
33
|
+
* body — or from the `email_delivery` object itself. A missing object or field
|
|
34
|
+
* reads as available (see {@link EMAIL_DELIVERY_UNKNOWN}).
|
|
35
|
+
*/
|
|
36
|
+
export declare function emailDeliveryFrom(body: unknown): EmailDelivery;
|
|
37
|
+
/**
|
|
38
|
+
* The first factors a sign-in UI should offer: `strategies` without email code /
|
|
39
|
+
* link while the app cannot deliver email. A capability of the app, not of the
|
|
40
|
+
* user, so it never narrows the server's uniform list per identifier. With no
|
|
41
|
+
* `delivery` (not loaded yet) the list is returned unchanged.
|
|
42
|
+
*/
|
|
43
|
+
export declare function offeredFirstFactors(strategies: readonly string[], delivery: EmailDelivery | null | undefined): string[];
|
|
44
|
+
/** One entry of the environment's `social_providers`. */
|
|
45
|
+
export interface EnvironmentSocialProvider {
|
|
46
|
+
provider: string;
|
|
47
|
+
/** Whether the provider signs in natively (One Tap etc.) on this instance. */
|
|
48
|
+
native: boolean;
|
|
49
|
+
mode: 'native' | 'redirect';
|
|
50
|
+
/** The full button text, or null for the SDK's own label. */
|
|
51
|
+
label: string | null;
|
|
52
|
+
display_name: string | null;
|
|
53
|
+
icon: {
|
|
54
|
+
type: 'url' | 'svg';
|
|
55
|
+
value: string;
|
|
56
|
+
} | null;
|
|
57
|
+
allow_sign_in: boolean;
|
|
58
|
+
allow_sign_up: boolean;
|
|
59
|
+
/** Public client id, for native providers only. */
|
|
60
|
+
client_id?: string;
|
|
61
|
+
/** Telegram's bot username. */
|
|
62
|
+
bot_username?: string;
|
|
63
|
+
}
|
|
64
|
+
/** `GET /v1/client/environment`, typed. */
|
|
65
|
+
export interface ClientEnvironment {
|
|
66
|
+
/** The enabled first factors (`password`, `email_code`, `passkey`, …). */
|
|
67
|
+
firstFactors: string[];
|
|
68
|
+
/**
|
|
69
|
+
* The offered strategies, camelCase as the server sends them (`password`,
|
|
70
|
+
* `emailCode`, `emailLink`, `passkey`, `phoneCode`, …, `oauthProviders`).
|
|
71
|
+
*/
|
|
72
|
+
strategies: Record<string, unknown>;
|
|
73
|
+
emailDelivery: EmailDelivery;
|
|
74
|
+
socialProviders: EnvironmentSocialProvider[];
|
|
75
|
+
}
|
|
76
|
+
/** Read a `/v1/client/environment` body. Defensive: malformed parts read as empty. */
|
|
77
|
+
export declare function clientEnvironmentFrom(body: unknown): ClientEnvironment;
|
|
78
|
+
/** Fetch the client environment. Null when the server can't be reached or errors. */
|
|
79
|
+
export declare function fetchClientEnvironment(client: FapiClient): Promise<ClientEnvironment | null>;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.EMAIL_FIRST_FACTORS = exports.EMAIL_UNAVAILABLE_MESSAGE = exports.EMAIL_DELIVERY_UNKNOWN = void 0;
|
|
4
|
+
exports.emailDeliveryFrom = emailDeliveryFrom;
|
|
5
|
+
exports.offeredFirstFactors = offeredFirstFactors;
|
|
6
|
+
exports.clientEnvironmentFrom = clientEnvironmentFrom;
|
|
7
|
+
exports.fetchClientEnvironment = fetchClientEnvironment;
|
|
8
|
+
/**
|
|
9
|
+
* What an older server (no `email_delivery`) is taken to mean: everything works.
|
|
10
|
+
* The server still refuses whatever it cannot deliver.
|
|
11
|
+
*/
|
|
12
|
+
exports.EMAIL_DELIVERY_UNKNOWN = Object.freeze({
|
|
13
|
+
available: true,
|
|
14
|
+
passwordReset: true,
|
|
15
|
+
passwordSignUp: true,
|
|
16
|
+
});
|
|
17
|
+
/** The default English copy for the "can't send email" state. */
|
|
18
|
+
exports.EMAIL_UNAVAILABLE_MESSAGE = "This app can't send email right now. Try another sign-in method or contact the app owner.";
|
|
19
|
+
/** The first factors that need the app to deliver email. */
|
|
20
|
+
exports.EMAIL_FIRST_FACTORS = ['email_code', 'email_link'];
|
|
21
|
+
const isRecord = (v) => !!v && typeof v === 'object' && !Array.isArray(v);
|
|
22
|
+
/**
|
|
23
|
+
* Read `email_delivery` from a `/v1/client/environment` or `/v1/appearance`
|
|
24
|
+
* body — or from the `email_delivery` object itself. A missing object or field
|
|
25
|
+
* reads as available (see {@link EMAIL_DELIVERY_UNKNOWN}).
|
|
26
|
+
*/
|
|
27
|
+
function emailDeliveryFrom(body) {
|
|
28
|
+
const raw = isRecord(body) && isRecord(body.email_delivery) ? body.email_delivery : body;
|
|
29
|
+
if (!isRecord(raw) || !('available' in raw || 'password_reset' in raw || 'password_sign_up' in raw)) {
|
|
30
|
+
return { ...exports.EMAIL_DELIVERY_UNKNOWN };
|
|
31
|
+
}
|
|
32
|
+
const flag = (v) => (typeof v === 'boolean' ? v : true);
|
|
33
|
+
return {
|
|
34
|
+
available: flag(raw.available),
|
|
35
|
+
passwordReset: flag(raw.password_reset),
|
|
36
|
+
passwordSignUp: flag(raw.password_sign_up),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* The first factors a sign-in UI should offer: `strategies` without email code /
|
|
41
|
+
* link while the app cannot deliver email. A capability of the app, not of the
|
|
42
|
+
* user, so it never narrows the server's uniform list per identifier. With no
|
|
43
|
+
* `delivery` (not loaded yet) the list is returned unchanged.
|
|
44
|
+
*/
|
|
45
|
+
function offeredFirstFactors(strategies, delivery) {
|
|
46
|
+
if (!delivery || delivery.available)
|
|
47
|
+
return [...strategies];
|
|
48
|
+
return strategies.filter((s) => !exports.EMAIL_FIRST_FACTORS.includes(s));
|
|
49
|
+
}
|
|
50
|
+
/** Read a `/v1/client/environment` body. Defensive: malformed parts read as empty. */
|
|
51
|
+
function clientEnvironmentFrom(body) {
|
|
52
|
+
const root = isRecord(body) ? body : {};
|
|
53
|
+
return {
|
|
54
|
+
firstFactors: Array.isArray(root.first_factors)
|
|
55
|
+
? root.first_factors.filter((f) => typeof f === 'string')
|
|
56
|
+
: [],
|
|
57
|
+
strategies: isRecord(root.strategies) ? { ...root.strategies } : {},
|
|
58
|
+
emailDelivery: emailDeliveryFrom(root),
|
|
59
|
+
socialProviders: Array.isArray(root.social_providers)
|
|
60
|
+
? root.social_providers.filter((p) => isRecord(p) && typeof p.provider === 'string')
|
|
61
|
+
: [],
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
/** Fetch the client environment. Null when the server can't be reached or errors. */
|
|
65
|
+
async function fetchClientEnvironment(client) {
|
|
66
|
+
try {
|
|
67
|
+
const response = await client.get('/v1/client/environment');
|
|
68
|
+
return response.ok ? clientEnvironmentFrom(response.data) : null;
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=environment.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"environment.js","sourceRoot":"","sources":["../src/environment.ts"],"names":[],"mappings":";;;AAiDA,8CAWC;AAQD,kDAMC;AAkCD,sDAcC;AAGD,wDAOC;AA5GD;;;GAGG;AACU,QAAA,sBAAsB,GAA4B,MAAM,CAAC,MAAM,CAAC;IAC3E,SAAS,EAAE,IAAI;IACf,aAAa,EAAE,IAAI;IACnB,cAAc,EAAE,IAAI;CACrB,CAAC,CAAC;AAEH,iEAAiE;AACpD,QAAA,yBAAyB,GACpC,2FAA2F,CAAC;AAE9F,4DAA4D;AAC/C,QAAA,mBAAmB,GAAsB,CAAC,YAAY,EAAE,YAAY,CAAC,CAAC;AAEnF,MAAM,QAAQ,GAAG,CAAC,CAAU,EAAgC,EAAE,CAC5D,CAAC,CAAC,CAAC,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;AAEpD;;;;GAIG;AACH,SAAgB,iBAAiB,CAAC,IAAa;IAC7C,MAAM,GAAG,GAAG,QAAQ,CAAC,IAAI,CAAC,IAAI,QAAQ,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC,IAAI,CAAC;IACzF,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,WAAW,IAAI,GAAG,IAAI,gBAAgB,IAAI,GAAG,IAAI,kBAAkB,IAAI,GAAG,CAAC,EAAE,CAAC;QACpG,OAAO,EAAE,GAAG,8BAAsB,EAAE,CAAC;IACvC,CAAC;IACD,MAAM,IAAI,GAAG,CAAC,CAAU,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IACjE,OAAO;QACL,SAAS,EAAE,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC;QAC9B,aAAa,EAAE,IAAI,CAAC,GAAG,CAAC,cAAc,CAAC;QACvC,cAAc,EAAE,IAAI,CAAC,GAAG,CAAC,gBAAgB,CAAC;KAC3C,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,SAAgB,mBAAmB,CACjC,UAA6B,EAC7B,QAA0C;IAE1C,IAAI,CAAC,QAAQ,IAAI,QAAQ,CAAC,SAAS;QAAE,OAAO,CAAC,GAAG,UAAU,CAAC,CAAC;IAC5D,OAAO,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,2BAAmB,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;AACpE,CAAC;AAiCD,sFAAsF;AACtF,SAAgB,qBAAqB,CAAC,IAAa;IACjD,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;IACxC,OAAO;QACL,YAAY,EAAE,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,aAAa,CAAC;YAC7C,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC;YACtE,CAAC,CAAC,EAAE;QACN,UAAU,EAAE,QAAQ,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE;QACnE,aAAa,EAAE,iBAAiB,CAAC,IAAI,CAAC;QACtC,eAAe,EAAE,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,gBAAgB,CAAC;YACnD,CAAC,CAAE,IAAI,CAAC,gBAAgB,CAAC,MAAM,CAC3B,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,OAAO,CAAC,CAAC,QAAQ,KAAK,QAAQ,CACV;YAC9C,CAAC,CAAC,EAAE;KACP,CAAC;AACJ,CAAC;AAED,qFAAqF;AAC9E,KAAK,UAAU,sBAAsB,CAAC,MAAkB;IAC7D,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,GAAG,CAAU,wBAAwB,CAAC,CAAC;QACrE,OAAO,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC,qBAAqB,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACnE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC"}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server errors in the app's language.
|
|
3
|
+
*
|
|
4
|
+
* A Frontend API error carries a human `message` (in English unless the request
|
|
5
|
+
* sent an `Accept-Language` the server has strings for) and, on current
|
|
6
|
+
* servers, a stable `reason` such as `form_password_incorrect`. A prebuilt form
|
|
7
|
+
* looks the reason up as the catalog key `error.<reason>`, so a Spanish form
|
|
8
|
+
* shows a Spanish sentence even when the server answered in English, and an
|
|
9
|
+
* app can reword any error by overriding that one key.
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* The reasons the Frontend API sends that every shipped catalog translates, as
|
|
13
|
+
* `error.<reason>` keys. Listed for docs and coverage checks; a reason missing
|
|
14
|
+
* from this list still renders (as the server's `message`).
|
|
15
|
+
*/
|
|
16
|
+
export declare const ERROR_REASONS: readonly ["form_password_incorrect", "form_code_incorrect", "form_code_already_used", "form_code_expired", "form_code_max_attempts", "form_link_invalid", "form_identifier_not_found", "form_identifier_exists", "form_password_pwned", "form_password_too_short", "form_password_too_long", "form_password_requires_uppercase", "form_password_requires_lowercase", "form_password_requires_number", "form_password_requires_symbol", "form_password_not_allowed", "form_password_contains_identifier", "form_password_same_as_current", "form_password_reused", "password_expired", "email_not_verified", "sign_in_token_invalid", "too_many_attempts", "too_many_requests", "captcha_required", "captcha_failed", "captcha_unavailable", "email_delivery_unavailable", "strategy_disabled", "sign_up_restricted", "waitlisted", "identifier_not_allowed", "disposable_email_blocked", "sso_required", "sign_ins_disabled", "sign_ups_disabled", "high_risk_blocked", "sms_region_blocked"];
|
|
17
|
+
/** A reason with a string in every shipped catalog. */
|
|
18
|
+
export type ErrorReason = (typeof ERROR_REASONS)[number];
|
|
19
|
+
/** The catalog key for a reason: `error.<reason>`. */
|
|
20
|
+
export declare function errorReasonKey(reason: string): string;
|
|
21
|
+
/**
|
|
22
|
+
* What {@link localizedErrorMessage} reads strings from: a flat catalog object
|
|
23
|
+
* (`{ 'error.too_many_attempts': '…' }`, or a bundle such as `esES` from
|
|
24
|
+
* `@atlasauth/localizations`) or a lookup function. A function may answer a
|
|
25
|
+
* missing key with `undefined`, `null`, `''` or the key itself (the convention
|
|
26
|
+
* of the prebuilt UIs' `translate`); all four count as "no string". Typed as
|
|
27
|
+
* `object` so an `interface`-typed bundle is accepted without a cast.
|
|
28
|
+
*/
|
|
29
|
+
export type ErrorStrings = ((key: string) => string | null | undefined) | object;
|
|
30
|
+
/** Anything error-shaped: a `FieldError`, an `ApiKeyRequestError`, an `Error`, a server error object. */
|
|
31
|
+
export interface ErrorLike {
|
|
32
|
+
reason?: string | null;
|
|
33
|
+
message?: string | null;
|
|
34
|
+
code?: string | null;
|
|
35
|
+
/** Interpolation values for the catalog string (`{min_length}` ← `meta.min_length`). */
|
|
36
|
+
meta?: Readonly<Record<string, unknown>> | null;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* The message to show for a server error, in the app's language:
|
|
40
|
+
*
|
|
41
|
+
* 1. the catalog string `error.<reason>`, when the error has a reason and
|
|
42
|
+
* `strings` has that key, with `{placeholders}` filled from the error's
|
|
43
|
+
* `meta` (a placeholder `meta` cannot fill skips to step 2);
|
|
44
|
+
* 2. else the server's `message` (localised by the server when the client sent
|
|
45
|
+
* `Accept-Language` — see `FapiClientOptions.locale`);
|
|
46
|
+
* 3. else the catalog's `error.generic`, else an English generic sentence.
|
|
47
|
+
*
|
|
48
|
+
* Never returns an empty string. `fallback`, when given, is consulted for
|
|
49
|
+
* `error.generic` after `strings` — handy when `strings` is a language layer
|
|
50
|
+
* without the English defaults underneath it.
|
|
51
|
+
*/
|
|
52
|
+
export declare function localizedErrorMessage(error: ErrorLike | string | null | undefined, strings?: ErrorStrings | null, fallback?: ErrorStrings | null): string;
|
|
53
|
+
/**
|
|
54
|
+
* The BCP-47 tag a catalog declares for itself (`@atlasauth/localizations`
|
|
55
|
+
* bundles carry `locale: 'es-ES'`), or undefined. Prebuilt UIs that take a
|
|
56
|
+
* `localization` bundle use it as the `Accept-Language` they send.
|
|
57
|
+
*/
|
|
58
|
+
export declare function catalogLocale(catalog: object | null | undefined): string | undefined;
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Server errors in the app's language.
|
|
4
|
+
*
|
|
5
|
+
* A Frontend API error carries a human `message` (in English unless the request
|
|
6
|
+
* sent an `Accept-Language` the server has strings for) and, on current
|
|
7
|
+
* servers, a stable `reason` such as `form_password_incorrect`. A prebuilt form
|
|
8
|
+
* looks the reason up as the catalog key `error.<reason>`, so a Spanish form
|
|
9
|
+
* shows a Spanish sentence even when the server answered in English, and an
|
|
10
|
+
* app can reword any error by overriding that one key.
|
|
11
|
+
*/
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.ERROR_REASONS = void 0;
|
|
14
|
+
exports.errorReasonKey = errorReasonKey;
|
|
15
|
+
exports.localizedErrorMessage = localizedErrorMessage;
|
|
16
|
+
exports.catalogLocale = catalogLocale;
|
|
17
|
+
/**
|
|
18
|
+
* The reasons the Frontend API sends that every shipped catalog translates, as
|
|
19
|
+
* `error.<reason>` keys. Listed for docs and coverage checks; a reason missing
|
|
20
|
+
* from this list still renders (as the server's `message`).
|
|
21
|
+
*/
|
|
22
|
+
exports.ERROR_REASONS = Object.freeze([
|
|
23
|
+
'form_password_incorrect',
|
|
24
|
+
'form_code_incorrect',
|
|
25
|
+
'form_code_already_used',
|
|
26
|
+
'form_code_expired',
|
|
27
|
+
'form_code_max_attempts',
|
|
28
|
+
'form_link_invalid',
|
|
29
|
+
'form_identifier_not_found',
|
|
30
|
+
'form_identifier_exists',
|
|
31
|
+
'form_password_pwned',
|
|
32
|
+
'form_password_too_short',
|
|
33
|
+
'form_password_too_long',
|
|
34
|
+
'form_password_requires_uppercase',
|
|
35
|
+
'form_password_requires_lowercase',
|
|
36
|
+
'form_password_requires_number',
|
|
37
|
+
'form_password_requires_symbol',
|
|
38
|
+
'form_password_not_allowed',
|
|
39
|
+
'form_password_contains_identifier',
|
|
40
|
+
'form_password_same_as_current',
|
|
41
|
+
'form_password_reused',
|
|
42
|
+
'password_expired',
|
|
43
|
+
'email_not_verified',
|
|
44
|
+
'sign_in_token_invalid',
|
|
45
|
+
'too_many_attempts',
|
|
46
|
+
'too_many_requests',
|
|
47
|
+
'captcha_required',
|
|
48
|
+
'captcha_failed',
|
|
49
|
+
'captcha_unavailable',
|
|
50
|
+
'email_delivery_unavailable',
|
|
51
|
+
'strategy_disabled',
|
|
52
|
+
'sign_up_restricted',
|
|
53
|
+
'waitlisted',
|
|
54
|
+
'identifier_not_allowed',
|
|
55
|
+
'disposable_email_blocked',
|
|
56
|
+
'sso_required',
|
|
57
|
+
'sign_ins_disabled',
|
|
58
|
+
'sign_ups_disabled',
|
|
59
|
+
'high_risk_blocked',
|
|
60
|
+
'sms_region_blocked',
|
|
61
|
+
]);
|
|
62
|
+
/** The catalog key for a reason: `error.<reason>`. */
|
|
63
|
+
function errorReasonKey(reason) {
|
|
64
|
+
return `error.${reason}`;
|
|
65
|
+
}
|
|
66
|
+
/** The English fallback of last resort, so the result is never blank. */
|
|
67
|
+
const GENERIC_ERROR = 'Something went wrong. Please try again.';
|
|
68
|
+
/** A reason is a lower-case token; anything else is never used as a key. */
|
|
69
|
+
const REASON = /^[a-z][a-z0-9_]{0,63}$/;
|
|
70
|
+
/**
|
|
71
|
+
* Fill `{name}` from `meta` (string or finite-number values). Null when a
|
|
72
|
+
* placeholder has no usable value: a sentence with a hole in it is worse than
|
|
73
|
+
* the server's own message, which the server has already filled.
|
|
74
|
+
*/
|
|
75
|
+
function interpolate(template, meta) {
|
|
76
|
+
let complete = true;
|
|
77
|
+
const text = template.replace(/\{(\w+)\}/g, (match, name) => {
|
|
78
|
+
const value = meta && Object.prototype.hasOwnProperty.call(meta, name) ? meta[name] : undefined;
|
|
79
|
+
if (typeof value === 'string' && value !== '')
|
|
80
|
+
return value;
|
|
81
|
+
if (typeof value === 'number' && Number.isFinite(value))
|
|
82
|
+
return String(value);
|
|
83
|
+
complete = false;
|
|
84
|
+
return match;
|
|
85
|
+
});
|
|
86
|
+
return complete ? text : null;
|
|
87
|
+
}
|
|
88
|
+
function lookup(strings, key) {
|
|
89
|
+
if (!strings)
|
|
90
|
+
return null;
|
|
91
|
+
let value;
|
|
92
|
+
if (typeof strings === 'function') {
|
|
93
|
+
value = strings(key);
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
value = Object.prototype.hasOwnProperty.call(strings, key)
|
|
97
|
+
? strings[key]
|
|
98
|
+
: undefined;
|
|
99
|
+
}
|
|
100
|
+
if (typeof value !== 'string')
|
|
101
|
+
return null;
|
|
102
|
+
const text = value.trim();
|
|
103
|
+
return text === '' || text === key ? null : value;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* The message to show for a server error, in the app's language:
|
|
107
|
+
*
|
|
108
|
+
* 1. the catalog string `error.<reason>`, when the error has a reason and
|
|
109
|
+
* `strings` has that key, with `{placeholders}` filled from the error's
|
|
110
|
+
* `meta` (a placeholder `meta` cannot fill skips to step 2);
|
|
111
|
+
* 2. else the server's `message` (localised by the server when the client sent
|
|
112
|
+
* `Accept-Language` — see `FapiClientOptions.locale`);
|
|
113
|
+
* 3. else the catalog's `error.generic`, else an English generic sentence.
|
|
114
|
+
*
|
|
115
|
+
* Never returns an empty string. `fallback`, when given, is consulted for
|
|
116
|
+
* `error.generic` after `strings` — handy when `strings` is a language layer
|
|
117
|
+
* without the English defaults underneath it.
|
|
118
|
+
*/
|
|
119
|
+
function localizedErrorMessage(error, strings, fallback) {
|
|
120
|
+
const err = typeof error === 'string' ? { message: error } : (error ?? {});
|
|
121
|
+
const reason = typeof err.reason === 'string' ? err.reason : '';
|
|
122
|
+
if (reason && REASON.test(reason)) {
|
|
123
|
+
const template = lookup(strings, errorReasonKey(reason));
|
|
124
|
+
const text = template ? interpolate(template, err.meta) : null;
|
|
125
|
+
if (text)
|
|
126
|
+
return text;
|
|
127
|
+
}
|
|
128
|
+
if (typeof err.message === 'string' && err.message.trim() !== '')
|
|
129
|
+
return err.message;
|
|
130
|
+
return lookup(strings, 'error.generic') ?? lookup(fallback, 'error.generic') ?? GENERIC_ERROR;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* The BCP-47 tag a catalog declares for itself (`@atlasauth/localizations`
|
|
134
|
+
* bundles carry `locale: 'es-ES'`), or undefined. Prebuilt UIs that take a
|
|
135
|
+
* `localization` bundle use it as the `Accept-Language` they send.
|
|
136
|
+
*/
|
|
137
|
+
function catalogLocale(catalog) {
|
|
138
|
+
if (!catalog || typeof catalog !== 'object')
|
|
139
|
+
return undefined;
|
|
140
|
+
const tag = catalog.locale;
|
|
141
|
+
return typeof tag === 'string' && tag.trim() !== '' ? tag : undefined;
|
|
142
|
+
}
|
|
143
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"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"}
|
package/dist/fapi.d.ts
CHANGED
|
@@ -18,7 +18,22 @@ export interface FapiClientOptions {
|
|
|
18
18
|
/** The instance's FAPI origin. Empty for same-origin. */
|
|
19
19
|
baseUrl?: string;
|
|
20
20
|
fetchImpl?: typeof fetch;
|
|
21
|
+
/**
|
|
22
|
+
* The app's UI language as a BCP-47 tag (`"es"`, `"pt-BR"`). When set, every
|
|
23
|
+
* request carries it as `Accept-Language`, so the server writes its error
|
|
24
|
+
* `message`s in that language — the fallback a prebuilt form shows for an
|
|
25
|
+
* error whose `reason` its catalog has no string for. Unset sends no header
|
|
26
|
+
* (the browser's own `Accept-Language` applies). A malformed tag is ignored.
|
|
27
|
+
*/
|
|
28
|
+
locale?: string;
|
|
21
29
|
}
|
|
30
|
+
/**
|
|
31
|
+
* A locale tag normalised for the `Accept-Language` header (`pt_BR` → `pt-BR`),
|
|
32
|
+
* or null when it is not a plausible BCP-47 tag. Restricting it to letters,
|
|
33
|
+
* digits and hyphens keeps the header CORS-safelisted, so sending it never adds
|
|
34
|
+
* a preflight to a cross-origin Frontend API call.
|
|
35
|
+
*/
|
|
36
|
+
export declare function acceptLanguageFor(locale: string | null | undefined): string | null;
|
|
22
37
|
export interface FapiResponse<T> {
|
|
23
38
|
ok: boolean;
|
|
24
39
|
status: number;
|
|
@@ -28,6 +43,8 @@ export interface FapiResponse<T> {
|
|
|
28
43
|
export declare class FapiClient {
|
|
29
44
|
private readonly options;
|
|
30
45
|
constructor(options: FapiClientOptions);
|
|
46
|
+
/** The `Accept-Language` this client sends, or null when it sends none. */
|
|
47
|
+
get locale(): string | null;
|
|
31
48
|
/**
|
|
32
49
|
* The origin FAPI serves from — where an OAuth popup's callback page lives, so
|
|
33
50
|
* the popup flow can pin `postMessage`'s `event.origin` to it. Empty `baseUrl`
|
package/dist/fapi.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.initialFlow = exports.FapiClient = void 0;
|
|
4
|
+
exports.acceptLanguageFor = acceptLanguageFor;
|
|
4
5
|
exports.getProviderToken = getProviderToken;
|
|
5
6
|
exports.requestForStep = requestForStep;
|
|
6
7
|
exports.flowFromPending = flowFromPending;
|
|
@@ -10,11 +11,27 @@ exports.prepareFactor = prepareFactor;
|
|
|
10
11
|
exports.pollAttempt = pollAttempt;
|
|
11
12
|
exports.shouldKeepPolling = shouldKeepPolling;
|
|
12
13
|
const attempt_1 = require("./attempt");
|
|
14
|
+
/**
|
|
15
|
+
* A locale tag normalised for the `Accept-Language` header (`pt_BR` → `pt-BR`),
|
|
16
|
+
* or null when it is not a plausible BCP-47 tag. Restricting it to letters,
|
|
17
|
+
* digits and hyphens keeps the header CORS-safelisted, so sending it never adds
|
|
18
|
+
* a preflight to a cross-origin Frontend API call.
|
|
19
|
+
*/
|
|
20
|
+
function acceptLanguageFor(locale) {
|
|
21
|
+
if (typeof locale !== 'string')
|
|
22
|
+
return null;
|
|
23
|
+
const tag = locale.trim().replace(/_/g, '-');
|
|
24
|
+
return /^[A-Za-z]{2,8}(-[A-Za-z0-9]{1,8})*$/.test(tag) ? tag : null;
|
|
25
|
+
}
|
|
13
26
|
class FapiClient {
|
|
14
27
|
options;
|
|
15
28
|
constructor(options) {
|
|
16
29
|
this.options = options;
|
|
17
30
|
}
|
|
31
|
+
/** The `Accept-Language` this client sends, or null when it sends none. */
|
|
32
|
+
get locale() {
|
|
33
|
+
return acceptLanguageFor(this.options.locale);
|
|
34
|
+
}
|
|
18
35
|
/**
|
|
19
36
|
* The origin FAPI serves from — where an OAuth popup's callback page lives, so
|
|
20
37
|
* the popup flow can pin `postMessage`'s `event.origin` to it. Empty `baseUrl`
|
|
@@ -33,6 +50,7 @@ class FapiClient {
|
|
|
33
50
|
}
|
|
34
51
|
async request(path, init = {}) {
|
|
35
52
|
const doFetch = this.options.fetchImpl ?? fetch;
|
|
53
|
+
const language = this.locale;
|
|
36
54
|
let response;
|
|
37
55
|
try {
|
|
38
56
|
response = await doFetch(`${this.options.baseUrl ?? ''}${path}`, {
|
|
@@ -42,6 +60,7 @@ class FapiClient {
|
|
|
42
60
|
credentials: 'include',
|
|
43
61
|
headers: {
|
|
44
62
|
'x-publishable-key': this.options.publishableKey,
|
|
63
|
+
...(language ? { 'accept-language': language } : {}),
|
|
45
64
|
...(init.body ? { 'content-type': 'application/json' } : {}),
|
|
46
65
|
...init.headers,
|
|
47
66
|
},
|
|
@@ -57,7 +76,7 @@ class FapiClient {
|
|
|
57
76
|
ok: false,
|
|
58
77
|
status: 0,
|
|
59
78
|
data: null,
|
|
60
|
-
errors: [{ code: 'NETWORK', message: 'We could not reach the server.' }],
|
|
79
|
+
errors: [{ code: 'NETWORK', reason: 'network', message: 'We could not reach the server.' }],
|
|
61
80
|
};
|
|
62
81
|
}
|
|
63
82
|
let body = null;
|