@plitzi/sdk-auth 0.32.25 → 0.33.1

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 CHANGED
@@ -1,5 +1,28 @@
1
1
  # @plitzi/sdk-auth
2
2
 
3
+ ## 0.33.1
4
+
5
+ ### Patch Changes
6
+
7
+ - v0.33.1
8
+ - Updated dependencies
9
+ - @plitzi/sdk-navigation@0.33.1
10
+ - @plitzi/sdk-schema@0.33.1
11
+ - @plitzi/sdk-shared@0.33.1
12
+
13
+ ## 0.33.0
14
+
15
+ ### Minor Changes
16
+
17
+ - v0.33.0
18
+
19
+ ### Patch Changes
20
+
21
+ - Updated dependencies
22
+ - @plitzi/sdk-navigation@0.33.0
23
+ - @plitzi/sdk-schema@0.33.0
24
+ - @plitzi/sdk-shared@0.33.0
25
+
3
26
  ## 0.32.25
4
27
 
5
28
  ### Patch Changes
@@ -1,12 +1,9 @@
1
- import { Environment, Server } from '@plitzi/sdk-shared';
1
+ import { Server } from '@plitzi/sdk-shared';
2
2
  import { ReactNode } from 'react';
3
3
  export type AuthContextProviderProps = {
4
4
  children?: ReactNode;
5
- isHydrating?: boolean;
6
- previewMode?: boolean;
7
5
  offlineMode?: boolean;
8
6
  server: Server;
9
- environment?: Environment;
10
7
  };
11
- declare const AuthContextProvider: ({ previewMode, isHydrating, children, server, environment }: AuthContextProviderProps) => import("react").JSX.Element;
8
+ declare const AuthContextProvider: ({ children, server }: AuthContextProviderProps) => import("react").JSX.Element;
12
9
  export default AuthContextProvider;
@@ -1,98 +1,102 @@
1
1
  import e from "./hooks/useAuth/index.mjs";
2
2
  import { AuthContext as t } from "./AuthContext.mjs";
3
- import { useMemo as n } from "react";
4
- import { QueryBuilderEvaluator as r } from "@plitzi/plitzi-ui/QueryBuilder";
5
- import i from "@plitzi/sdk-navigation/hooks/useNavigation";
6
- import { processTwig as a } from "@plitzi/sdk-shared/helpers/twigWrapper";
7
- import { useCommonStore as o } from "@plitzi/sdk-shared/store";
8
- import { jsx as s } from "react/jsx-runtime";
3
+ import { use as n, useMemo as r, useRef as i } from "react";
4
+ import { QueryBuilderEvaluator as a } from "@plitzi/plitzi-ui/QueryBuilder";
5
+ import o from "@plitzi/sdk-navigation/hooks/useNavigation";
6
+ import { processTwig as s } from "@plitzi/sdk-shared/helpers/twigWrapper";
7
+ import c from "@plitzi/sdk-shared/network/NetworkContext";
8
+ import { useCommonStore as l, useRenderSettings as u } from "@plitzi/sdk-shared/store";
9
+ import { jsx as d } from "react/jsx-runtime";
9
10
  //#region src/AuthContextProvider.tsx
10
- var c = ({ previewMode: c = !0, isHydrating: l = !1, children: u, server: d, environment: f = "production" }) => {
11
- let [[{ userProvider: p, loginUrl: m, userUrl: h, refreshUrl: g, logoutUrl: _, tokenStorage: v = "localStorage", detailsPath: y = "details", tokenPath: b = "access_token", expirationTimePath: x = "expire_at" }, S]] = o(["schema.settings", "schema.variables"]), { queryParams: C, hostname: w } = i({ server: d }), T = n(() => ({
12
- queryParams: C,
13
- hostname: w,
14
- environment: f
11
+ var f = [
12
+ "loginUrl",
13
+ "userUrl",
14
+ "refreshUrl",
15
+ "logoutUrl",
16
+ "detailsPath",
17
+ "tokenPath",
18
+ "refreshTokenPath",
19
+ "expirationTimePath",
20
+ "refreshExpirationTimePath",
21
+ "sessionHintCookie",
22
+ "sessionExchangeUrl"
23
+ ], p = (e, t) => {
24
+ if (e === t) return !0;
25
+ if (!e || !t) return !1;
26
+ let n = e, r = t;
27
+ return [.../* @__PURE__ */ new Set([...Object.keys(n), ...Object.keys(r)])].every((e) => JSON.stringify(n[e]) === JSON.stringify(r[e]));
28
+ }, m = ({ children: m, server: h }) => {
29
+ let { previewMode: g, isHydrating: _, environment: v } = u(), { webKey: y } = n(c), [[b, x]] = l(["schema.settings", "schema.variables"]), { queryParams: S, hostname: C } = o({ server: h }), w = r(() => ({
30
+ queryParams: S,
31
+ hostname: C,
32
+ environment: v
15
33
  }), [
34
+ S,
16
35
  C,
17
- w,
18
- f
19
- ]), E = n(() => Array.isArray(S) ? S.reduce((e, t) => {
20
- let { name: n, value: i, subValues: a } = t;
21
- if (!Array.isArray(a) || a.length === 0) return {
36
+ v
37
+ ]), T = r(() => Array.isArray(x) ? x.reduce((e, t) => {
38
+ let { name: n, value: r, subValues: i } = t;
39
+ if (!Array.isArray(i) || i.length === 0) return {
22
40
  ...e,
23
- [n]: i
41
+ [n]: r
24
42
  };
25
- let o = a.find((e) => r(e.when, T));
43
+ let o = i.find((e) => a(e.when, w));
26
44
  return o ? {
27
45
  ...e,
28
46
  [n]: o.value
29
47
  } : {
30
48
  ...e,
31
- [n]: i
49
+ [n]: r
32
50
  };
33
- }, {}) : {}, [S, T]), D = n(() => {
51
+ }, {}) : {}, [x, w]), E = r(() => {
52
+ let e = Object.fromEntries(f.map((e) => [e, b[e]]));
34
53
  try {
35
- return JSON.parse(a(JSON.stringify({
36
- loginUrl: m,
37
- userUrl: h,
38
- refreshUrl: g,
39
- logoutUrl: _,
40
- detailsPath: y,
41
- tokenPath: b,
42
- expirationTimePath: x
43
- }), E));
54
+ return JSON.parse(s(JSON.stringify(e), T));
44
55
  } catch {
45
- return {
46
- loginUrl: m,
47
- userUrl: h,
48
- refreshUrl: g,
49
- logoutUrl: _,
50
- detailsPath: y,
51
- tokenPath: b,
52
- expirationTimePath: x
53
- };
56
+ return e;
54
57
  }
55
- }, [
56
- m,
57
- h,
58
- g,
59
- _,
60
- y,
58
+ }, [b, T]), D = r(() => ({
59
+ ...E,
60
+ spaceKey: y,
61
+ tokenStorage: b.tokenStorage ?? "localStorage",
62
+ sessionGate: b.sessionGate,
63
+ sessionRevalidateSeconds: b.sessionRevalidateSeconds
64
+ }), [
65
+ E,
61
66
  b,
62
- x,
63
- E
64
- ]), { manager: O, loading: k, authenticated: A } = e({
65
- server: d,
66
- isHydrating: l,
67
- tokenStorage: v,
68
- provider: p,
69
- loginUrl: D.loginUrl,
70
- userUrl: D.userUrl,
71
- refreshUrl: D.refreshUrl,
72
- logoutUrl: D.logoutUrl,
73
- detailsPath: D.detailsPath,
74
- tokenPath: D.tokenPath,
75
- expirationTimePath: D.expirationTimePath
76
- }), j = n(() => ({
77
- manager: O,
67
+ y
68
+ ]), { manager: O, loading: k, authenticated: A, state: j, bootstrapUser: M, bootstrapToken: N, peekedUser: P, peekedToken: F } = e({
69
+ server: h,
70
+ isHydrating: _,
71
+ provider: b.userProvider ?? "",
72
+ settings: D
73
+ }), I = O.getProvider()?.user, L = I ?? M ?? P, R = I ? O.getProvider()?.token?.accessToken : M ? N : F, z = i(void 0), B = r(() => {
74
+ let e = L ? {
75
+ details: L,
76
+ accessToken: R
77
+ } : void 0, t = z.current;
78
+ return t?.accessToken === e?.accessToken && p(t?.details, e?.details) || (z.current = e), z.current;
79
+ }, [L, R]), V = r(() => ({
78
80
  login: O.login.bind(O),
79
81
  refresh: O.refresh.bind(O),
82
+ revalidate: O.revalidate.bind(O),
83
+ invalidate: O.invalidate.bind(O),
80
84
  can: O.can.bind(O),
81
85
  logout: O.logout.bind(O),
82
- authenticated: A || !c,
83
- user: O.getProvider() ? {
84
- details: O.getProvider()?.user,
85
- accessToken: O.getProvider()?.token?.accessToken
86
- } : void 0
86
+ state: j,
87
+ authenticated: A || !g,
88
+ user: B
87
89
  }), [
88
90
  O,
91
+ j,
89
92
  A,
90
- c
93
+ g,
94
+ B
91
95
  ]);
92
- return /* @__PURE__ */ s(t, {
93
- value: j,
94
- children: !k && u
96
+ return /* @__PURE__ */ d(t, {
97
+ value: V,
98
+ children: !k && m
95
99
  });
96
100
  };
97
101
  //#endregion
98
- export { c as default };
102
+ export { m as default };
@@ -1,24 +1,62 @@
1
- import { default as Auth0Provider } from './providers/Auth0Provider';
2
- import { default as BasicAuthProvider } from './providers/BasicAuthProvider';
3
- import { default as AuthProvider, AuthEventListener, AuthProviderWithCache } from './AuthProvider';
4
- import { TokenResult } from '@plitzi/sdk-shared';
5
- declare const providers: {
6
- auth0: typeof Auth0Provider;
7
- basic: typeof BasicAuthProvider;
8
- };
9
- export type Providers = keyof typeof providers;
10
- export declare class AuthManager<U = Record<string, unknown>, T extends Providers = 'basic'> {
11
- private providerType;
12
- private provider?;
13
- constructor(providerType: Providers | undefined, listeners: AuthEventListener | AuthEventListener[], ...args: ConstructorParameters<(typeof providers)[T]>);
14
- getProviderType(): Providers;
15
- getProvider(): AuthProviderWithCache<U> | undefined;
16
- init(user?: U, skipAuth?: boolean): Promise<void> | undefined;
17
- login(...args: Parameters<AuthProvider['login']>): Promise<TokenResult | undefined>;
18
- refresh(): Promise<TokenResult | undefined>;
19
- getUser(): Promise<U | undefined> | undefined;
1
+ import { default as AuthProvider, AuthBootstrap, AuthEventListener } from './AuthProvider';
2
+ import { AuthProviderSettings } from './types';
3
+ import { AuthFailureReason, AuthState, TokenResult } from '@plitzi/sdk-shared';
4
+ export type AuthProviderFactory<U = Record<string, unknown>> = (settings: AuthProviderSettings) => AuthProvider<U>;
5
+ /**
6
+ * Adds an auth provider a space can then select by name in its settings. Spaces run against whichever backend their
7
+ * owner has — `basic` covers any HTTP+JSON API by configuration alone, and this is the way out for the ones it does
8
+ * not: register before the SDK mounts, and `userProvider: '<name>'` picks it up.
9
+ */
10
+ export declare const registerAuthProvider: <U = Record<string, unknown>>(name: string, factory: AuthProviderFactory<U>) => void;
11
+ export declare const getAuthProviderNames: () => string[];
12
+ /**
13
+ * Holds the provider a space selected and forwards to it. Its reason for existing is that the provider is chosen at
14
+ * runtime from the schema: everything above this line talks to one object whether the space authenticates against
15
+ * Plitzi, against Auth0, or against something the customer registered.
16
+ */
17
+ export declare class AuthManager<U = Record<string, unknown>> {
18
+ private readonly providerType;
19
+ private readonly provider?;
20
+ private readonly settings;
21
+ private readonly listeners;
22
+ constructor(providerType: string, listeners: AuthEventListener | AuthEventListener[], settings: AuthProviderSettings);
23
+ getProviderType(): string;
24
+ getProvider(): AuthProvider<U> | undefined;
25
+ getState(): AuthState;
26
+ /**
27
+ * What this browser can be seen to hold before anything has run.
28
+ *
29
+ * Answered even when no provider has been named yet, which is the state every page is in while its schema loads:
30
+ * where the credential is kept is a matter of settings — the storage key, the hint cookie — and not of which
31
+ * provider will end up using it, so it can be read now. Doing that read only after the schema arrived is what
32
+ * made a signed-in page paint its signed-out version first on every reload.
33
+ */
34
+ peekState(): AuthState;
35
+ peek(): {
36
+ state: AuthState;
37
+ user?: U;
38
+ token?: TokenResult;
39
+ };
20
40
  can(permission: string): boolean;
41
+ /**
42
+ * A space that names no provider — a widget, an offline render, anything that does not sign people in — has no
43
+ * session to settle, and must say so. Without this it never reports a state at all, and a caller waiting for one
44
+ * (the SDK holds its whole tree back until auth has decided) waits forever and renders nothing.
45
+ *
46
+ * What it must not say is that nobody is signed in when the server just rendered this page for somebody. There are
47
+ * two ways to arrive here holding a bootstrap: a space that genuinely names no provider, and — the common one — a
48
+ * space whose schema has not loaded yet, because `userProvider` is read from it and is empty for the first render
49
+ * of every page that fetches its schema over the network. Answering `guest` there contradicted the HTML that had
50
+ * just been sent: the page flipped to its signed-out version, then back once the schema arrived and a real
51
+ * provider adopted the same identity. The server's answer stands until something that can actually check it
52
+ * disagrees.
53
+ */
54
+ init(bootstrap?: AuthBootstrap<U>): Promise<void>;
55
+ login(...args: Parameters<AuthProvider<U>['login']>): Promise<TokenResult | undefined>;
56
+ refresh(): Promise<TokenResult | undefined>;
57
+ revalidate(force?: boolean): Promise<boolean>;
58
+ invalidate(reason?: AuthFailureReason): void;
21
59
  logout(): Promise<void>;
60
+ dispose(): void;
22
61
  on(listener: AuthEventListener): (() => void) | undefined;
23
62
  }
24
- export {};
@@ -1,14 +1,16 @@
1
- import e from "./providers/Auth0Provider.mjs";
2
- import t from "./providers/BasicAuthProvider.mjs";
1
+ import e from "./providers/BasicAuthProvider.mjs";
3
2
  //#region src/AuthManager.ts
4
- var n = {
5
- auth0: e,
6
- basic: t
7
- }, r = class {
8
- providerType = "basic";
9
- provider = void 0;
10
- constructor(e = "basic", t, ...r) {
11
- this.providerType = e, n[e] && (this.provider = new n[e](...r), this.provider.on(t));
3
+ var t = /* @__PURE__ */ new Map([["basic", (t) => new e(t)]]), n = (e, n) => {
4
+ t.set(e, n);
5
+ }, r = () => [...t.keys()], i = class {
6
+ providerType;
7
+ provider;
8
+ settings;
9
+ listeners;
10
+ constructor(e, n, r) {
11
+ this.providerType = e, this.settings = r, this.listeners = Array.isArray(n) ? n : [n];
12
+ let i = t.get(e);
13
+ i && (this.provider = i(r), this.provider.on(this.listeners));
12
14
  }
13
15
  getProviderType() {
14
16
  return this.providerType;
@@ -16,8 +18,28 @@ var n = {
16
18
  getProvider() {
17
19
  return this.provider;
18
20
  }
19
- init(e, t) {
20
- return this.provider?.init(e, t);
21
+ getState() {
22
+ return this.provider?.getState() ?? "guest";
23
+ }
24
+ peekState() {
25
+ return this.peek().state;
26
+ }
27
+ peek() {
28
+ return (this.provider ?? new e(this.settings)).peek();
29
+ }
30
+ can(e) {
31
+ return this.provider?.can(e) ?? !1;
32
+ }
33
+ init(e) {
34
+ if (!this.provider) {
35
+ let t = this.peekState(), n = e?.user || t === "authenticated" ? "authenticated" : "guest";
36
+ return this.listeners.forEach((e) => e({
37
+ type: "state",
38
+ state: n,
39
+ reason: "no-provider"
40
+ })), Promise.resolve();
41
+ }
42
+ return this.provider.init(e);
21
43
  }
22
44
  login(...e) {
23
45
  return this.provider?.login(...e) ?? Promise.resolve(void 0);
@@ -25,18 +47,21 @@ var n = {
25
47
  refresh() {
26
48
  return this.provider?.refresh() ?? Promise.resolve(void 0);
27
49
  }
28
- getUser() {
29
- return this.provider?.getUser();
50
+ revalidate(e) {
51
+ return this.provider?.revalidate(e) ?? Promise.resolve(!1);
30
52
  }
31
- can(e) {
32
- return this.provider?.can(e) ?? !1;
53
+ invalidate(e) {
54
+ this.provider?.invalidate(e);
33
55
  }
34
56
  logout() {
35
57
  return this.provider?.logout() ?? Promise.resolve();
36
58
  }
59
+ dispose() {
60
+ this.provider?.dispose();
61
+ }
37
62
  on(e) {
38
63
  return this.provider?.on(e);
39
64
  }
40
65
  };
41
66
  //#endregion
42
- export { r as AuthManager };
67
+ export { i as AuthManager, r as getAuthProviderNames, n as registerAuthProvider };
@@ -1,54 +1,191 @@
1
- import { Schema, TokenResult } from '@plitzi/sdk-shared';
2
- export type AuthProviderCache<U = Record<string, unknown>> = {
3
- token?: TokenResult;
4
- user?: U;
5
- };
6
- export type AuthState = 'init' | 'initLoading' | 'authenticating' | 'authenticated' | 'guest';
1
+ import { AuthFailureReason, AuthResult, AuthState, Schema, TokenResult } from '@plitzi/sdk-shared';
2
+ /** Why a state changed. Not control flow — it is what makes an auth trace readable, and every transition below
3
+ * names one, because "it went to guest" is not a diagnosis and "the hint cookie was gone" is. */
4
+ export type AuthStateReason = 'bootstrap' | 'restored' | 'granted' | 'renewed' | 'identity' | 'redirect' | 'exchanged' | 'no-provider' | 'skip-auth' | 'hint-missing' | 'no-evidence' | 'signed-out' | 'another-tab' | 'settled' | 'authenticating';
7
5
  export type AuthEvent = {
8
6
  type: 'state';
9
7
  state: AuthState;
8
+ reason?: AuthStateReason;
10
9
  } | {
11
10
  type: 'login';
12
- token: TokenResult;
11
+ token?: TokenResult;
13
12
  } | {
14
13
  type: 'logout';
15
14
  } | {
16
15
  type: 'expired';
16
+ reason: AuthFailureReason;
17
17
  };
18
18
  export type AuthEventListener = (event: AuthEvent) => void;
19
- export type AuthProviderWithCache<U> = AuthProvider<U> & Readonly<AuthProviderCache<U>>;
19
+ /** What the rendering server already knows about this visitor, handed over so the browser does not re-ask. */
20
+ export type AuthBootstrap<U> = {
21
+ user?: U;
22
+ accessToken?: string;
23
+ expiresAt?: number;
24
+ skipAuth?: boolean;
25
+ };
20
26
  export type AuthProviderProps = {
21
- enableRefresh?: boolean;
27
+ /** The credential naming the space this SDK instance renders, forwarded on requests made on the space's behalf —
28
+ * the exchange is one, since only the space can say which identity provider its credentials may come from. */
29
+ spaceKey?: string;
22
30
  tokenStorage?: Schema['settings']['tokenStorage'];
31
+ sessionHintCookie?: Schema['settings']['sessionHintCookie'];
32
+ sessionGate?: Schema['settings']['sessionGate'];
33
+ sessionRevalidateSeconds?: Schema['settings']['sessionRevalidateSeconds'];
34
+ storageKey?: string;
23
35
  };
36
+ /**
37
+ * Everything about a session that does not depend on how a particular backend is spoken to: what is known, how long
38
+ * it stays true, when to renew it, and what to do when it stops being true. A subclass supplies only the four
39
+ * requests, so an auth backend is described rather than reimplemented.
40
+ *
41
+ * The design goal is that knowing whether someone is signed in should normally cost nothing. Three sources answer it
42
+ * without a request — a server-rendered page's own answer, a stored session whose token has not lapsed, and the
43
+ * session hint cookie — and the backend is asked only when none of them can, or when what they say has gone stale.
44
+ */
24
45
  declare abstract class AuthProvider<U = Record<string, unknown>> {
25
46
  abstract readonly name: string;
26
- protected cache?: AuthProviderCache<U>;
27
- protected enableRefresh: boolean;
28
- protected tokenStorage: Schema['settings']['tokenStorage'];
29
47
  protected state: AuthState;
30
- private listeners;
31
- constructor({ enableRefresh, tokenStorage }?: AuthProviderProps);
32
- abstract init(user?: U, skipAuth?: boolean): Promise<void>;
33
- abstract login(...args: unknown[]): Promise<TokenResult | undefined>;
34
- abstract getUser(): Promise<U | undefined>;
35
- abstract refresh(): Promise<TokenResult | undefined>;
36
- abstract can(permission: string): boolean;
37
- abstract logout(): Promise<void>;
38
- protected internalRefresh(token?: TokenResult): void;
39
- protected internalGetUser(user?: U): void;
40
- protected internalLogin(user?: U, token?: TokenResult): void;
41
- protected internalLogout(): void;
42
- protected isExpired(): boolean;
43
- protected setCache<T extends keyof AuthProviderCache>(cacheKey: T | undefined, cache: AuthProviderCache<U>[T]): void;
44
- protected clearCache(cacheKey?: keyof AuthProviderCache<U>): void;
48
+ private session;
49
+ private readonly store;
50
+ private readonly gate;
51
+ private readonly hintCookie?;
52
+ private readonly revalidateSeconds;
53
+ private readonly listeners;
54
+ private renewal?;
55
+ private renewalTimer?;
56
+ private detachListeners?;
57
+ /** Set when a request failed to reach the backend, so the next `online` event retries instead of waiting. */
58
+ private offline;
59
+ protected readonly spaceKey: string;
60
+ constructor({ spaceKey, tokenStorage, sessionHintCookie, sessionGate, sessionRevalidateSeconds, storageKey }?: AuthProviderProps);
61
+ protected abstract get capabilities(): {
62
+ renew: boolean;
63
+ identity: boolean;
64
+ };
65
+ /** The backend's endpoints. Refusals reported from elsewhere are matched against these, so a session only reacts
66
+ * to the API that issued it. Empty means "unknown", and then every refusal is taken as ours. */
67
+ protected get endpoints(): string[];
68
+ protected abstract requestLogin(params: Record<string, unknown>): Promise<AuthResult<U>>;
69
+ protected abstract requestRenewal(refreshToken?: string): Promise<AuthResult<U>>;
70
+ protected abstract requestIdentity(): Promise<AuthResult<U>>;
71
+ protected abstract requestLogout(): Promise<void>;
72
+ /**
73
+ * Hands the credential this browser just obtained to the rendering server, so it can establish a session of its
74
+ * own — see `handOffToServer`. Returning undefined, the default, means this provider's grants already come from
75
+ * the server and there is nothing to hand over.
76
+ */
77
+ protected requestExchange(): Promise<AuthResult<U> | undefined>;
78
+ /**
79
+ * A grant waiting in the current URL, for providers whose sign-in is a redirect rather than a request: OAuth and
80
+ * OIDC (Auth0 among them) send the browser away and bring it back with a code to exchange. That is the freshest
81
+ * evidence a page can have, so it is consulted before anything stored — and it is the provider's job to clean the
82
+ * code out of the URL once taken.
83
+ *
84
+ * Returning undefined, the default, means this provider does not sign in by redirect.
85
+ */
86
+ protected consumeRedirect(): Promise<AuthResult<U> | undefined>;
87
+ get user(): U | undefined;
88
+ get token(): TokenResult | undefined;
89
+ getState(): AuthState;
90
+ can(permission: string): boolean;
91
+ /**
92
+ * What this browser can be seen to hold, right now, without asking anyone or changing anything.
93
+ *
94
+ * `init()` reaches the same conclusion, but it runs in an effect — so the first paint of a client-rendered page
95
+ * happens before it, and a visitor whose session was sitting in storage the whole time saw the signed-out page
96
+ * flash past. There is nothing to wait for: the credential is in `localStorage` and the hint is a cookie, both
97
+ * synchronous. This is the same evidence `decide()` acts on, read a few milliseconds earlier.
98
+ *
99
+ * `init` means "cannot say yet" — there is evidence, but confirming it needs a request. Only `authenticated` and
100
+ * `guest` are claims, and both are ones the browser can make on its own.
101
+ */
102
+ peekState(): AuthState;
103
+ /** The same read, with the identity it found — so a page can render the visitor, not just know there is one. */
104
+ peek(): {
105
+ state: AuthState;
106
+ user?: U;
107
+ token?: TokenResult;
108
+ };
109
+ /**
110
+ * Decides what this page already knows and what, if anything, it has to ask. Resolves once the answer is good
111
+ * enough to render with — which under the default gate means as soon as a stored session says so, with the
112
+ * confirmation happening behind the render rather than in front of it.
113
+ */
114
+ init(bootstrap?: AuthBootstrap<U>): Promise<void>;
115
+ /**
116
+ * No path may leave a page waiting. Every branch below either settles the state itself or comes back through here,
117
+ * so a backend that answers something nobody anticipated costs a visitor a wrong guess about their session — never
118
+ * a page that renders nothing at all.
119
+ */
120
+ private settle;
121
+ private decide;
122
+ /** Drops timers and window listeners. Called when the provider is replaced, so a re-configured space leaves none behind. */
123
+ dispose(): void;
124
+ login(params: Record<string, unknown>): Promise<TokenResult | undefined>;
125
+ /**
126
+ * A credential obtained in the browser — by a client-side identity provider, or by a redirect coming back from
127
+ * one — is unknown to the server that renders the pages. It therefore renders every one of them as a guest while
128
+ * the browser knows perfectly well who this is, and the page changes under the visitor the moment it hydrates.
129
+ *
130
+ * Handing the credential over closes that gap: the server verifies it with the provider and establishes its own
131
+ * session, cookie and all, so the next server-rendered page already knows. Providers whose grants came from that
132
+ * same server return undefined here and nothing happens.
133
+ */
134
+ private handOffToServer;
135
+ refresh(): Promise<TokenResult | undefined>;
136
+ /**
137
+ * Confirms the session against the backend. Skipped when the last confirmation is recent enough and the token has
138
+ * not lapsed, unless forced — which is what a caller does before an action it cannot take back.
139
+ */
140
+ revalidate(force?: boolean): Promise<boolean>;
141
+ /**
142
+ * What an app calls when the backend refused a credential it just used. `expired` is renewable and silently is;
143
+ * anything terminal ends the session here and now, without waiting for a timer to notice.
144
+ */
145
+ invalidate(reason?: AuthFailureReason): void;
146
+ logout(): Promise<void>;
45
147
  on(listeners: AuthEventListener | AuthEventListener[]): () => void;
46
148
  protected emit(event: AuthEvent): void;
47
- protected setState(state: AuthState): void;
48
- getState(): AuthState;
49
- protected request<T>(input: RequestInfo, init?: RequestInit): Promise<{
50
- data?: T;
51
- status: number;
52
- }>;
149
+ protected setState(state: AuthState, reason?: AuthStateReason): void;
150
+ /** Single-flight: a timer firing while a 401 is being handled must not rotate the refresh token twice. */
151
+ private renew;
152
+ private performRenewal;
153
+ private loadIdentity;
154
+ private handleFailure;
155
+ /** Takes on what a grant or an identity call returned, and records that the backend confirmed it just now. */
156
+ private adopt;
157
+ /**
158
+ * Ends the session locally. `reason` distinguishes an expiry the client discovered from a sign-out the person
159
+ * asked for; both clear the same state, but only the first is something the app may want to react to.
160
+ *
161
+ * The cleared entry is written back rather than removed, because "nobody is signed in, confirmed just now" is
162
+ * worth remembering: it is what keeps a signed-out visitor from paying for the same refused request on every load.
163
+ */
164
+ private endSession;
165
+ /** Unix seconds this session's access token dies at, from what the backend said or, failing that, from the token. */
166
+ private expiresAt;
167
+ private hasLiveToken;
168
+ private isFresh;
169
+ /**
170
+ * Whether this browser shows any sign of holding a session: a stored credential, or the hint cookie the backend
171
+ * publishes beside its httpOnly one.
172
+ *
173
+ * **Nothing is ever asked without it.** A page that calls an API "just in case" gets a 401 for every signed-out
174
+ * visitor on every load and every tab focus — noise in the logs that says nothing, and a request that could never
175
+ * have succeeded. Evidence is cheap to produce and the backend decides whether it is any good.
176
+ *
177
+ * The hint is read live rather than at boot, so a sign-in that happened in another app on this domain is picked
178
+ * up by the next revalidation instead of waiting for a reload.
179
+ */
180
+ private hasEvidence;
181
+ private scheduleRenewal;
182
+ private onRenewalDue;
183
+ private attach;
184
+ /**
185
+ * Another tab changed the session. Adopting it rather than re-checking is the point: two tabs that each renewed on
186
+ * their own would rotate the refresh token out from under each other, and the one that lost would sign its user
187
+ * out over nothing.
188
+ */
189
+ private adoptFromStorage;
53
190
  }
54
191
  export default AuthProvider;