@passflow/core 0.20.1 → 0.21.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/dist/index.d.cts CHANGED
@@ -42,6 +42,8 @@ type Token = {
42
42
  iss: string;
43
43
  jti: string;
44
44
  sub: string;
45
+ /** Auth session the refresh lineage is anchored to; kept by every rotation of one sign-in. */
46
+ sid?: string;
45
47
  type: string;
46
48
  email?: string;
47
49
  phonenumber?: string;
@@ -345,6 +347,12 @@ declare class StorageManager {
345
347
  * Clear the CSRF token from storage
346
348
  */
347
349
  clearCsrfToken(): void;
350
+ /**
351
+ * Every storage key the token session lives in (JSON tokens, scopes, the cookie-mode ID token,
352
+ * CSRF token and delivery mode). The refresh coordinator filters `storage` events by it, so
353
+ * writes to unrelated keys never wake the refresh path.
354
+ */
355
+ tokenKeys(): string[];
348
356
  private getKeyForTokenType;
349
357
  }
350
358
  declare class DeviceService {
@@ -586,6 +594,13 @@ interface PlatformAdapter {
586
594
  * Must be synchronous. Use localStorage on web, MMKV on React Native.
587
595
  */
588
596
  readonly storage: PlatformStorage;
597
+ /**
598
+ * True when `storage` is shared by every tab of the origin (the browser's localStorage, or a
599
+ * wrapper over it). Token refresh coordinates tabs only over shared storage
600
+ * (`refreshCoordination: 'web-locks'`); a per-tab storage (sessionStorage, memory) shares no
601
+ * session. Optional: an adapter that leaves it unset is treated as per-tab. WebAdapter sets it.
602
+ */
603
+ readonly storageSharedAcrossTabs?: boolean;
589
604
  /**
590
605
  * Returns the current URL of the application.
591
606
  * On web: wraps window.location.
@@ -696,6 +711,11 @@ interface PlatformAdapter {
696
711
  }
697
712
  export declare class WebAdapter implements PlatformAdapter {
698
713
  get storage(): PlatformStorage;
714
+ /**
715
+ * `storage` is the browser's localStorage, which every tab of the origin shares. False in
716
+ * SSR/Node (the no-op storage) and where reading localStorage throws (storage blocked).
717
+ */
718
+ get storageSharedAcrossTabs(): boolean;
699
719
  /**
700
720
  * Replaces:
701
721
  * window.location.origin (passflow.ts:95, axios-client.ts:49)
@@ -796,6 +816,97 @@ export declare class WebAdapter implements PlatformAdapter {
796
816
  * Exported so consuming code can reference it without allocating a new instance.
797
817
  */
798
818
  export declare const webAdapter: WebAdapter;
819
+ /**
820
+ * Public types of multi-tab-safe token refresh (PFL-792, architecture §2.2-2.5).
821
+ *
822
+ * @module refresh/types
823
+ */
824
+ /** Options of one lock request: exclusive mode only, as the SDK uses `navigator.locks`. */
825
+ interface RefreshLockOptions {
826
+ ifAvailable?: boolean;
827
+ steal?: boolean;
828
+ signal?: AbortSignal;
829
+ }
830
+ /** The part of `navigator.locks` (LockManager) the SDK uses: exclusive mode only. */
831
+ interface RefreshLockManager {
832
+ request(name: string, options: RefreshLockOptions, callback: (lock: unknown) => unknown): Promise<unknown>;
833
+ }
834
+ /** The part of BroadcastChannel the SDK uses. Must not deliver an instance's own posts to itself. */
835
+ interface RefreshChannel {
836
+ postMessage(message: unknown): void;
837
+ addEventListener(type: 'message', listener: (event: {
838
+ data: unknown;
839
+ }) => void): void;
840
+ removeEventListener(type: 'message', listener: (event: {
841
+ data: unknown;
842
+ }) => void): void;
843
+ close(): void;
844
+ }
845
+ /** Time source for every refresh timer of one instance (heartbeat, silence, retry, waits). */
846
+ interface RefreshClock {
847
+ /** Epoch ms. */
848
+ now(): number;
849
+ /** Runs `fn` after `ms`; returns the cancel. */
850
+ setTimeout(fn: () => void, ms: number): () => void;
851
+ /** `[0, 1)` for backoff jitter. Default `Math.random`. */
852
+ random?(): number;
853
+ }
854
+ /** Transports for the cross-tab protocol, supplied by tests, Electron or custom hosts. */
855
+ interface RefreshCoordinator {
856
+ locks: RefreshLockManager;
857
+ /** One per Passflow instance; closed by `dispose()`. */
858
+ channel: RefreshChannel;
859
+ /** Default: `crypto.randomUUID()` or a random string. */
860
+ tabId?: string;
861
+ /** Another tab wrote the shared token storage. Called once at construction; returns unsubscribe. */
862
+ onStorageChange?: (listener: () => void) => () => void;
863
+ /** Default: `Date.now` / `setTimeout` / `Math.random`. */
864
+ clock?: RefreshClock;
865
+ }
866
+ /** How tabs of this origin coordinate token refresh (`PassflowConfig.refreshCoordination`). */
867
+ type RefreshCoordination = 'web-locks' | 'none' | RefreshCoordinator;
868
+ /** What asked for a refresh (`ErrorPayload.source`). */
869
+ type RefreshTrigger =
870
+ /** `Passflow.refreshToken()` / `AuthService.refreshToken()` */
871
+ 'refresh-token' |
872
+ /** `TokenCacheService.getTokensWithRefresh()` */
873
+ 'token-check' |
874
+ /** `getTokens(true)` / `session({ doRefresh: true })` */
875
+ 'session' |
876
+ /** An SDK API call whose access token expires within 30 s */
877
+ 'pre-request' |
878
+ /** Step-up commit */
879
+ 'upgrade' |
880
+ /** The scheduled retry, with no new caller */
881
+ 'retry' |
882
+ /** The held-tab send */
883
+ 'held';
884
+ /** How a failed `/auth/refresh` is read (architecture §2.6). */
885
+ type RefreshFailureKind = 'transient' | 'blocked' | 'auth';
886
+ /** Refresh details on `ErrorPayload` for operations `refresh` and `upgrade.commit`. */
887
+ interface RefreshErrorInfo {
888
+ classification: RefreshFailureKind;
889
+ /** A retry is scheduled (transient, or auth with strikes left). */
890
+ willRetry: boolean;
891
+ /** Epoch ms of the retry, when `willRetry`. */
892
+ nextRetryAt?: number;
893
+ /** Auth failures since the last success, after this one. */
894
+ strikes: number;
895
+ /** Resolved `refreshStrikes`. */
896
+ maxStrikes: number;
897
+ }
898
+ /** Whether an event was caused by this tab's own action or learnt from another tab. */
899
+ type EventOrigin = 'this-tab' | 'other-tab';
900
+ /** Why a session ended (`SessionExpired.reason`). */
901
+ type SessionExpiredReason =
902
+ /** `refreshStrikes` auth failures (this tab, or another tab of the lineage). */
903
+ 'refresh_failed' |
904
+ /** `error.token.blocked` and no successor within 20 s; this tab only. */
905
+ 'successor_timeout' |
906
+ /** Storage now holds another user's tokens. */
907
+ 'identity_changed' |
908
+ /** Legacy members, never emitted. */
909
+ 'token_invalid' | 'logged_out';
799
910
  /** Factor types the upgrade protocol understands, named as `upgrade.type` on the wire. */
800
911
  type UpgradeFactorType = 'totp' | 'recovery_codes' | 'passkey' | 'email_otp' | 'sms_otp';
801
912
  /** The prepare round's result — `POST /auth/upgrade/begin`, normalised to camelCase. */
@@ -1010,6 +1121,26 @@ export type PassflowConfig = {
1010
1121
  * resource that has to be torn down, which configuration cannot express.
1011
1122
  */
1012
1123
  upgrade?: PassflowUpgradeConfig;
1124
+ /**
1125
+ * How tabs of this origin coordinate token refresh.
1126
+ * - `'web-locks'` (default): a Web Lock plus a BroadcastChannel, on a browser page whose
1127
+ * `platform.storage` is shared by every tab (the default web adapter's localStorage) and that
1128
+ * has `navigator.locks` and `BroadcastChannel`. Elsewhere it falls back to `'none'`; on a
1129
+ * browser page with shared storage the fallback writes one `[passflow]` warning, at the first
1130
+ * refresh.
1131
+ * - `'none'`: no cross-tab coordination. Single flight within this instance, compare-and-set
1132
+ * save, per-instance retries.
1133
+ * - a `RefreshCoordinator` object: the same protocol as `'web-locks'` over the transports
1134
+ * you supply (tests, Electron, custom hosts). Never falls back.
1135
+ */
1136
+ refreshCoordination?: RefreshCoordination;
1137
+ /**
1138
+ * Authentication failures of `/auth/refresh` counted since the last successful refresh or
1139
+ * adoption, after which the session ends. Transient failures in between neither add a strike nor
1140
+ * reset the count. Default 3. A non-integer is floored; a value below 1 or not a finite number
1141
+ * uses the default.
1142
+ */
1143
+ refreshStrikes?: number;
1013
1144
  };
1014
1145
  export type PassflowUpgradeConfig = {
1015
1146
  /** Relying-party id forwarded verbatim to `POST /auth/upgrade/start` for passkey upgrades. */
@@ -1725,13 +1856,24 @@ export interface CLIAuthCompleteRequest {
1725
1856
  * config, so anything registered afterwards is structurally too late.
1726
1857
  */
1727
1858
  type AxiosUpgradeHandler = (error: AxiosError, challenge: UpgradeChallenge) => Promise<AxiosResponse>;
1859
+ /**
1860
+ * The pre-request refresh (PFL-792). Installed by `Passflow` on every client it owns; it asks the
1861
+ * instance's refresh controller, which coordinates with the other tabs and owns retries, strikes
1862
+ * and the session end. Resolves with the tokens to send (`undefined` once the session ended).
1863
+ *
1864
+ * `allowStale` is true when the access token is inside the 30 s window but not yet expired: during
1865
+ * a pending retry the controller then resolves at once and the call goes out with the current
1866
+ * bearer instead of waiting for the retry.
1867
+ */
1868
+ type AxiosRefreshHandler = (request: {
1869
+ trigger: 'pre-request';
1870
+ allowStale: boolean;
1871
+ }) => Promise<Tokens | undefined>;
1728
1872
  declare class AxiosClient {
1729
1873
  private instance;
1730
1874
  protected storageManager: StorageManager;
1731
1875
  protected deviceService: DeviceService;
1732
1876
  protected tokenDeliveryManager: TokenDeliveryManager;
1733
- private refreshPromise;
1734
- private isRefreshing;
1735
1877
  private _platform;
1736
1878
  tokenService: TokenService;
1737
1879
  get origin(): string;
@@ -1751,6 +1893,8 @@ declare class AxiosClient {
1751
1893
  private readonly protectedEndpoints;
1752
1894
  /** Set when a consumer wires the upgrade feature. Absent by default. */
1753
1895
  private upgradeHandler?;
1896
+ /** Set by `Passflow` (its refresh controller). Absent on a client built standalone. */
1897
+ private refreshHandler?;
1754
1898
  /**
1755
1899
  * Passflow's own API as an interception scope, resolved ONCE here from
1756
1900
  * `PassflowConfig.url` so no wrapper or adapter ever derives it. `null` when
@@ -1769,6 +1913,17 @@ declare class AxiosClient {
1769
1913
  * upgrade challenge. Passing `undefined` detaches it.
1770
1914
  */
1771
1915
  setUpgradeInterceptor(handler: AxiosUpgradeHandler | undefined): void;
1916
+ /**
1917
+ * Installs (or clears) the pre-request refresh. JSON mode only: before a request that needs an
1918
+ * access token (not `/auth/refresh`, not logout) whose access token expires within 30 s while a
1919
+ * refresh token is stored, the handler is awaited and its access token becomes the bearer. On
1920
+ * rejection the call still goes out with the current bearer when that token has not expired yet;
1921
+ * otherwise the call rejects with the handler's error. Nothing is ever deleted here.
1922
+ *
1923
+ * Without a handler (an API client built standalone, outside `Passflow`) no refresh runs: every
1924
+ * call sends the stored bearer as it is, and an expired one gets the server's 401.
1925
+ */
1926
+ setRefreshHandler(handler: AxiosRefreshHandler | undefined): void;
1772
1927
  /**
1773
1928
  * Re-issues a request after an elevation. Exposed for the upgrade handler,
1774
1929
  * which owns the replay-once decision; the marker preventing a second
@@ -1787,11 +1942,6 @@ declare class AxiosClient {
1787
1942
  * configure cookie mode for platforms where cookiesSupported() returns false.
1788
1943
  */
1789
1944
  private detectCookieSupport;
1790
- /**
1791
- * Refresh tokens using single-flight pattern to prevent race conditions
1792
- * Supports both cookie mode and JSON mode
1793
- */
1794
- private refreshTokens;
1795
1945
  private handleRateLimitError;
1796
1946
  private handleAxiosError;
1797
1947
  private send;
@@ -2929,6 +3079,117 @@ export declare class M2MTokenParseError extends M2MError {
2929
3079
  export declare class M2MConfigError extends M2MError {
2930
3080
  constructor(message: string);
2931
3081
  }
3082
+ /**
3083
+ * Logger interface for Passflow services
3084
+ */
3085
+ interface Logger {
3086
+ error(message: string, ...args: unknown[]): void;
3087
+ warn(message: string, ...args: unknown[]): void;
3088
+ info(message: string, ...args: unknown[]): void;
3089
+ debug(message: string, ...args: unknown[]): void;
3090
+ }
3091
+ /**
3092
+ * Session and user identity pinning for an upgrade flight (§13.4 revision 6).
3093
+ *
3094
+ * ## The rule
3095
+ *
3096
+ * An upgrade flight is bound to the **session and user identity it began
3097
+ * under**, and that identity is verified **at the moment tokens are applied** —
3098
+ * not only around `prepare` and `commit`. On mismatch the rotated tokens are
3099
+ * discarded, never applied, and the flight settles as `session changed
3100
+ * mid-flight`.
3101
+ *
3102
+ * ## Why the epoch counter was not enough
3103
+ *
3104
+ * The round-1 epoch check wraps the two rounds that talk to the server. It does
3105
+ * not wrap token APPLICATION, which happens inside `AuthService.refreshToken`
3106
+ * after the commit returns. A logout and re-login during an in-flight upgrade
3107
+ * therefore let user A's flight write its rotated tokens over user B's live
3108
+ * session: the request was authorized as A, the storage belongs to B, and
3109
+ * nothing in between compared the two principals.
3110
+ *
3111
+ * An epoch tells you something changed. An identity check tells you **whether
3112
+ * it is still the same principal**, which is the property actually required.
3113
+ * Both run, and they are complementary rather than redundant.
3114
+ *
3115
+ * ## Why this module is pure
3116
+ *
3117
+ * It imports nothing and reads no storage, so the token-applying path can be
3118
+ * given a pin by value and the comparison can be tested with literals. Reading
3119
+ * the identity is the host's job — it is the only code that knows where the
3120
+ * session lives in each delivery mode.
3121
+ */
3122
+ /**
3123
+ * The principal an upgrade flight is pinned to.
3124
+ *
3125
+ * `sub` is the user. `sid` is the session when the server issues one; it is
3126
+ * absent in tokens that carry no session claim, and the field is then simply
3127
+ * not part of the comparison — a missing claim cannot be made to prove
3128
+ * anything, and inventing a value would make two different sessions compare
3129
+ * equal or unequal arbitrarily.
3130
+ */
3131
+ type UpgradeSessionPin = {
3132
+ readonly sub: string;
3133
+ readonly sid?: string;
3134
+ };
3135
+ /** Lock and channel names for a storage prefix. Empty prefix: the portal's names. */
3136
+ interface RefreshNames {
3137
+ lock: string;
3138
+ takeoverLock: string;
3139
+ channel: string;
3140
+ }
3141
+ /** The transports of the cross-tab protocol for one instance. */
3142
+ interface CrossTabTransports {
3143
+ locks: RefreshLockManager;
3144
+ channel: RefreshChannel;
3145
+ tabId: string;
3146
+ }
3147
+ /** What `refreshCoordination` resolved to for one instance. */
3148
+ interface ResolvedCoordination {
3149
+ /** Cross-tab transports, or null: local (single-instance) coordination. */
3150
+ transports: CrossTabTransports | null;
3151
+ /** Lock and channel names for the storage prefix. */
3152
+ names: RefreshNames;
3153
+ /** The time source of every refresh timer of the instance. */
3154
+ clock: Required<RefreshClock>;
3155
+ /** Another tab wrote the shared token storage. Returns unsubscribe. */
3156
+ onStorageChange?: (listener: () => void) => () => void;
3157
+ /** Write the fallback warning once, when the instance first starts a refresh. */
3158
+ warnOnFirstCycle: boolean;
3159
+ }
3160
+ interface RefreshRequest {
3161
+ trigger: RefreshTrigger;
3162
+ /** `refreshToken()`: satisfied only by a pair from a request sent at or after the call (§2.8). */
3163
+ fresh?: boolean;
3164
+ }
3165
+ interface RefreshController {
3166
+ refresh(request: RefreshRequest): Promise<PassflowAuthorizationResponse>;
3167
+ /** The pre-request refresh (§2.8): the tokens to send the API call with. */
3168
+ ensureFresh(request: {
3169
+ trigger: 'pre-request';
3170
+ allowStale: boolean;
3171
+ }): Promise<Tokens | undefined>;
3172
+ commitUpgrade(upgrade: UpgradeRefreshField, pin?: UpgradeSessionPin): Promise<PassflowAuthorizationResponse>;
3173
+ /**
3174
+ * logOut (§2.8): rejects plain callers, stops the timers, waits for this tab's request in
3175
+ * flight, takes the lock (≤ 5 s), posts signed-out naming the newest stored token, runs
3176
+ * `cleanup(newestRefreshToken)`, ends the session here and releases. Emits nothing: the caller
3177
+ * emits its one SignOut.
3178
+ */
3179
+ signOut(cleanup: (newest?: string) => Promise<void>): Promise<void>;
3180
+ /** reset() (A2): the same without the lock or an API call, synchronously. */
3181
+ signOutNow(cleanup: (newest?: string) => void): void;
3182
+ /** A new session in this tab (sign-in, register, setTokens): new generation, counters reset. */
3183
+ sessionStarted(): void;
3184
+ /** The `sub` this tab's session belongs to. */
3185
+ identity(): string | undefined;
3186
+ readonly coordinated: boolean;
3187
+ /** This instance has a cycle: waiting for the lock, a request in flight, a retry or a successor pending. */
3188
+ readonly busy: boolean;
3189
+ /** The session ended here (or the instance was disposed): `refresh()` rejects with a PassflowRefreshError. */
3190
+ readonly ended: boolean;
3191
+ dispose(): void;
3192
+ }
2932
3193
  /**
2933
3194
  * Passflow event types
2934
3195
  */
@@ -2956,8 +3217,17 @@ export declare enum PassflowEvent {
2956
3217
  TwoFactorSetupMagicLinkFailed = "2fa:magic_link_failed",
2957
3218
  TwoFactorChallengeReceived = "two_factor_challenge_received",
2958
3219
  TwoFactorMethodSwitched = "two_factor_method_switched",
2959
- TwoFactorDeviceTrusted = "two_factor_device_trusted"
3220
+ TwoFactorDeviceTrusted = "two_factor_device_trusted",
3221
+ /** Token refresh entered a transient-failure episode (once per episode, per tab). */
3222
+ RefreshFailing = "refresh:failing",
3223
+ /** The transient-failure episode ended with a successful refresh or adoption. */
3224
+ RefreshRecovered = "refresh:recovered"
2960
3225
  }
3226
+ /**
3227
+ * The SDK operation that failed (`ErrorPayload.operation`). The SDK sets it on every `Error` it
3228
+ * emits, so a subscriber can tell a failed sign-in from a failed refresh without parsing messages.
3229
+ */
3230
+ type PassflowOperation = 'sign-in' | 'sign-up' | 'passwordless.start' | 'passwordless.complete' | 'password-reset.send' | 'password-reset.complete' | 'passkey.register' | 'passkey.authenticate' | 'federated.popup' | 'auth-redirect.url' | 'auth-redirect' | 'auth.is-authenticated' | 'session.check' | 'tokens.get' | 'tokens.load' | 'tokens.from-url' | 'refresh' | 'upgrade.commit' | 'log-out' | 'reset' | 'settings.app' | 'settings.all' | 'settings.password-policy' | 'settings.passkey' | 'passkey.list' | 'passkey.rename' | 'passkey.delete' | 'passkey.add' | 'tenant.create' | 'invitation.join' | 'invitation.request-link' | 'invitation.list' | 'invitation.delete' | 'invitation.resend' | 'invitation.get-link' | '2fa.status' | '2fa.setup.begin' | '2fa.setup.confirm' | '2fa.verify' | '2fa.recovery.use' | '2fa.disable' | '2fa.recovery.regenerate' | '2fa.methods.available' | '2fa.methods.registered' | '2fa.method.setup.start' | '2fa.method.setup.complete' | '2fa.method.remove' | '2fa.challenge.request' | '2fa.challenge.verify' | '2fa.method.switch' | '2fa.trusted-devices.list' | '2fa.trusted-devices.revoke';
2961
3231
  /**
2962
3232
  * Error payload interface for structured error information
2963
3233
  */
@@ -2966,7 +3236,17 @@ interface ErrorPayload {
2966
3236
  code?: string | number;
2967
3237
  details?: unknown;
2968
3238
  originalError?: unknown;
3239
+ /** The operation that failed. Always set by the SDK. */
3240
+ operation?: PassflowOperation;
3241
+ /** For operation `refresh` | `upgrade.commit`: what asked for the refresh. */
3242
+ source?: RefreshTrigger;
3243
+ /** For operation `refresh` | `upgrade.commit`: how the failure was classified and what follows. */
3244
+ refresh?: RefreshErrorInfo;
2969
3245
  }
3246
+ /** An `Error` payload as the SDK reports it: `operation` is required. */
3247
+ type OperationErrorPayload = ErrorPayload & {
3248
+ operation: PassflowOperation;
3249
+ };
2970
3250
  /**
2971
3251
  * Event-specific payload types
2972
3252
  */
@@ -2974,6 +3254,8 @@ type PassflowEventPayload = {
2974
3254
  [PassflowEvent.SignIn]: {
2975
3255
  tokens?: Tokens;
2976
3256
  parsedTokens?: ParsedTokens;
3257
+ /** `'other-tab'` only when an ended tab revives on a new session found in storage. Absent on this tab's own sign-in. */
3258
+ origin?: EventOrigin;
2977
3259
  };
2978
3260
  [PassflowEvent.SignInStart]: {
2979
3261
  email?: string;
@@ -2987,7 +3269,10 @@ type PassflowEventPayload = {
2987
3269
  email?: string;
2988
3270
  };
2989
3271
  [PassflowEvent.SignOut]: {
3272
+ /** `sub` of the session that ended, when known. */
2990
3273
  userId?: string;
3274
+ /** Always set by the SDK. `'other-tab'`: another tab signed out, or shared storage was emptied. */
3275
+ origin?: EventOrigin;
2991
3276
  };
2992
3277
  [PassflowEvent.SessionRestored]: {
2993
3278
  id: string;
@@ -2996,12 +3281,19 @@ type PassflowEventPayload = {
2996
3281
  [key: string]: unknown;
2997
3282
  };
2998
3283
  [PassflowEvent.SessionExpired]: {
2999
- reason: 'refresh_failed' | 'token_invalid' | 'logged_out';
3284
+ reason: SessionExpiredReason;
3285
+ /** Always set by the SDK. */
3286
+ origin?: EventOrigin;
3000
3287
  };
3001
3288
  [PassflowEvent.Error]: ErrorPayload;
3002
3289
  [PassflowEvent.Refresh]: {
3003
3290
  tokens?: Tokens;
3004
3291
  parsedTokens?: ParsedTokens;
3292
+ /**
3293
+ * Always set by the SDK. `'refresh'`: this tab's request; `'upgrade'`: this tab's step-up
3294
+ * commit; `'adopted'`: tokens this tab did not request (another tab's, or found in storage).
3295
+ */
3296
+ source?: 'refresh' | 'upgrade' | 'adopted';
3005
3297
  };
3006
3298
  [PassflowEvent.RefreshStart]: {
3007
3299
  tokenId?: string;
@@ -3061,6 +3353,20 @@ type PassflowEventPayload = {
3061
3353
  alternativeMethods: TwoFactorMethod[];
3062
3354
  };
3063
3355
  [PassflowEvent.TwoFactorDeviceTrusted]: Record<string, never>;
3356
+ [PassflowEvent.RefreshFailing]: {
3357
+ /** Epoch ms of the episode's first transient failure. */
3358
+ since: number;
3359
+ /** Epoch ms of the shared pending retry, when known. */
3360
+ nextRetryAt?: number;
3361
+ /** `'this-tab'` when this tab's own request failed. */
3362
+ origin: EventOrigin;
3363
+ };
3364
+ [PassflowEvent.RefreshRecovered]: {
3365
+ /** Same value as the `RefreshFailing` that opened the episode. */
3366
+ since: number;
3367
+ /** `'this-tab'`: own request succeeded; `'other-tab'`: adopted another tab's tokens. */
3368
+ origin: EventOrigin;
3369
+ };
3064
3370
  };
3065
3371
  /**
3066
3372
  * Passflow subscriber interface
@@ -3091,54 +3397,14 @@ declare class PassflowStore {
3091
3397
  * @param payload Event-specific payload data
3092
3398
  */
3093
3399
  notify<E extends PassflowEvent>(eventType: E, payload?: PassflowEventPayload[E]): void;
3400
+ /**
3401
+ * Notify subscribers of an error, at most once per error object (`notifyError` in `error-report.ts`).
3402
+ * @returns whether the event was emitted
3403
+ */
3404
+ notifyError(payload: OperationErrorPayload): boolean;
3094
3405
  }
3095
- /**
3096
- * Session and user identity pinning for an upgrade flight (§13.4 revision 6).
3097
- *
3098
- * ## The rule
3099
- *
3100
- * An upgrade flight is bound to the **session and user identity it began
3101
- * under**, and that identity is verified **at the moment tokens are applied** —
3102
- * not only around `prepare` and `commit`. On mismatch the rotated tokens are
3103
- * discarded, never applied, and the flight settles as `session changed
3104
- * mid-flight`.
3105
- *
3106
- * ## Why the epoch counter was not enough
3107
- *
3108
- * The round-1 epoch check wraps the two rounds that talk to the server. It does
3109
- * not wrap token APPLICATION, which happens inside `AuthService.refreshToken`
3110
- * after the commit returns. A logout and re-login during an in-flight upgrade
3111
- * therefore let user A's flight write its rotated tokens over user B's live
3112
- * session: the request was authorized as A, the storage belongs to B, and
3113
- * nothing in between compared the two principals.
3114
- *
3115
- * An epoch tells you something changed. An identity check tells you **whether
3116
- * it is still the same principal**, which is the property actually required.
3117
- * Both run, and they are complementary rather than redundant.
3118
- *
3119
- * ## Why this module is pure
3120
- *
3121
- * It imports nothing and reads no storage, so the token-applying path can be
3122
- * given a pin by value and the comparison can be tested with literals. Reading
3123
- * the identity is the host's job — it is the only code that knows where the
3124
- * session lives in each delivery mode.
3125
- */
3126
- /**
3127
- * The principal an upgrade flight is pinned to.
3128
- *
3129
- * `sub` is the user. `sid` is the session when the server issues one; it is
3130
- * absent in tokens that carry no session claim, and the field is then simply
3131
- * not part of the comparison — a missing claim cannot be made to prove
3132
- * anything, and inventing a value would make two different sessions compare
3133
- * equal or unequal arbitrarily.
3134
- */
3135
- type UpgradeSessionPin = {
3136
- readonly sub: string;
3137
- readonly sid?: string;
3138
- };
3139
3406
  export declare class TokenCacheService {
3140
3407
  private storageManager;
3141
- private authApi;
3142
3408
  private subscribeStore;
3143
3409
  private platform;
3144
3410
  tokensCache: Tokens | undefined;
@@ -3147,14 +3413,42 @@ export declare class TokenCacheService {
3147
3413
  private readonly CHECK_INTERVAL;
3148
3414
  private unsubscribeForeground;
3149
3415
  private unsubscribeUnload;
3150
- isRefreshing: boolean;
3416
+ private refreshController;
3417
+ /**
3418
+ * Set when the expiry check (`initialize()`, the 60 s check, foreground) finds the access token
3419
+ * expired, with `TokenCacheExpired{isExpired:true}`. Output only: it never gates a refresh. The
3420
+ * refresh controller clears it on every `Refresh`.
3421
+ */
3151
3422
  tokenExpiredFlag: boolean;
3152
- constructor(storageManager: StorageManager, authApi: AuthAPI, subscribeStore: PassflowStore, platform?: PlatformAdapter);
3423
+ /**
3424
+ * `authApi` is no longer used here (the refresh controller sends every refresh); the parameter
3425
+ * stays so the constructor's signature does not change.
3426
+ */
3427
+ constructor(storageManager: StorageManager, _authApi: AuthAPI, subscribeStore: PassflowStore, platform?: PlatformAdapter);
3428
+ /** Whether this instance has a refresh cycle (waiting, in flight, a retry or a successor pending). */
3429
+ get isRefreshing(): boolean;
3430
+ /**
3431
+ * Connects `getTokensWithRefresh()` to the instance's refresh controller. `Passflow` calls it at
3432
+ * construction. Without it, `getTokensWithRefresh()` returns the cache and sends nothing.
3433
+ */
3434
+ attachRefreshController(controller: Pick<RefreshController, 'refresh' | 'busy'> | undefined): void;
3435
+ /**
3436
+ * Starts the expiry check. `Passflow.subscribe()` calls it on every subscribe, so it must be
3437
+ * safe to repeat:
3438
+ * - A filled cache wins over storage. The cache only moves forward (the refresh controller
3439
+ * and its fence own every later change), while storage can still hold an older pair of the
3440
+ * same session (a held tab). Reading storage here would move the cache back.
3441
+ * - `TokenCacheExpired{isExpired:true}` fires only when the expired state changes, not on
3442
+ * every call.
3443
+ */
3153
3444
  initialize(): void;
3154
- private refreshTokensCache;
3155
3445
  startTokenCheck(): void;
3156
3446
  private setupLifecycleListeners;
3157
- private stopTokenCheck;
3447
+ /**
3448
+ * Stops the expiry check and its foreground listener. Public so the refresh controller can stop
3449
+ * it when a session ends in this tab (it is called optionally: test fakes may lack it).
3450
+ */
3451
+ stopTokenCheck(): void;
3158
3452
  /**
3159
3453
  * Cleanup method to stop all intervals and remove event listeners.
3160
3454
  * Should be called when the service is no longer needed.
@@ -3162,10 +3456,23 @@ export declare class TokenCacheService {
3162
3456
  destroy(): void;
3163
3457
  setTokensCache(tokens: Tokens | undefined): void;
3164
3458
  getTokens(): Tokens | undefined;
3459
+ /**
3460
+ * The cached tokens, refreshed first when the access token has expired (trigger `token-check`).
3461
+ *
3462
+ * It asks the refresh controller, which starts a refresh or joins the one pending, and returns
3463
+ * the cache whatever the outcome: the new tokens, the kept ones after a failure that did not end
3464
+ * the session, `undefined` once it ended. It never rejects; the controller reports the failure.
3465
+ */
3165
3466
  getTokensWithRefresh(): Promise<Tokens | undefined>;
3166
3467
  getParsedTokens(): ParsedTokens | undefined;
3167
3468
  isExpired(): boolean;
3168
3469
  }
3470
+ /** How this instance's refresh controller coordinates (architecture §3.3, §3.4). */
3471
+ export type AuthServiceRefreshOptions = {
3472
+ coordination: ResolvedCoordination;
3473
+ /** `PassflowConfig.refreshStrikes`; the controller resolves the default. */
3474
+ strikes?: number;
3475
+ };
3169
3476
  /**
3170
3477
  * Service for handling authentication related functionality
3171
3478
  */
@@ -3185,13 +3492,19 @@ export declare class AuthService {
3185
3492
  private tokenDeliveryManager;
3186
3493
  private tokenExchangeConfig?;
3187
3494
  private pendingPkce?;
3495
+ /**
3496
+ * The one place this instance sends `/auth/refresh` (PFL-792). Every refresh path asks it: the
3497
+ * explicit `refreshToken()`, the upgrade commit, `getTokens(true)`/`session()` and, once
3498
+ * `Passflow` attaches it, `TokenCacheService.getTokensWithRefresh()`.
3499
+ */
3500
+ readonly refresh: RefreshController;
3188
3501
  constructor(authApi: AuthAPI, deviceService: DeviceService, storageManager: StorageManager, subscribeStore: PassflowStore, tokenCacheService: TokenCacheService, scopes: string[], createTenantForNewUser: boolean, url: string, sessionCallbacks: {
3189
3502
  createSession?: ({ tokens, parsedTokens }: {
3190
3503
  tokens?: Tokens;
3191
3504
  parsedTokens?: ParsedTokens;
3192
3505
  }) => Promise<void>;
3193
3506
  expiredSession?: () => Promise<void>;
3194
- }, appId?: string | undefined, tokenExchangeConfig?: TokenExchangeConfig, platform?: PlatformAdapter, readPkceEnabled?: (() => Promise<boolean>) | undefined);
3507
+ }, appId?: string | undefined, tokenExchangeConfig?: TokenExchangeConfig, platform?: PlatformAdapter, readPkceEnabled?: (() => Promise<boolean>) | undefined, refreshOptions?: AuthServiceRefreshOptions);
3195
3508
  /**
3196
3509
  * Initialize session state on page load for cookie/BFF mode
3197
3510
  */
@@ -3203,11 +3516,20 @@ export declare class AuthService {
3203
3516
  */
3204
3517
  restoreSession(): Promise<boolean>;
3205
3518
  /**
3206
- * Process successful authentication response
3207
- * Handles token storage, session state, CSRF tokens
3208
- * In BFF mode, forwards tokens to BFF server
3519
+ * Applies a sign-in's tokens and starts a new session in the refresh controller.
3520
+ *
3521
+ * Every sign-in reaches the tokens through here: password, passwordless, passkey, federated,
3522
+ * the 2FA code and recovery-code completion, PKCE completion, `joinInvitation` and tokens from a
3523
+ * redirect (`applyTokens`). So this is where the controller learns of a new session (amendment
3524
+ * A1): a tab that signed out, or whose session ended, refreshes again on the new one.
3209
3525
  */
3210
3526
  private processAuthResponse;
3527
+ /**
3528
+ * Stores an authentication response: token storage, session state, CSRF token. In BFF mode it
3529
+ * forwards the tokens to the BFF server first. Starts no session: a cookie-mode refresh applies
3530
+ * its answer through here too.
3531
+ */
3532
+ private applyAuthResponse;
3211
3533
  /**
3212
3534
  * Reports whether a sign-in response is still waiting on its second factor,
3213
3535
  * and when it is, hands the 2FA token on.
@@ -3269,7 +3591,28 @@ export declare class AuthService {
3269
3591
  signUp(payload: PassflowSignUpPayload): Promise<PassflowAuthorizationResponse>;
3270
3592
  passwordlessSignIn(payload: PassflowPasswordlessSignInPayload): Promise<PassflowPasswordlessResponse>;
3271
3593
  passwordlessSignInComplete(payload: PassflowPasswordlessSignInCompletePayload): Promise<PassflowValidationResponse>;
3272
- logOut(): Promise<void>;
3594
+ /**
3595
+ * Signs this tab out under the refresh lock (PFL-792 §2.8): stops this tab's refresh callers and
3596
+ * retry timer, waits at most 5 s for the lock (a request already in flight lands first), posts
3597
+ * `signed-out` to the other tabs, calls the logout API with the NEWEST stored refresh token (best
3598
+ * effort), then deletes the stored tokens, id token and CSRF token and clears the token cache.
3599
+ *
3600
+ * Emits no `SignOut`: `Passflow.logOut()` emits exactly one, after this resolves.
3601
+ *
3602
+ * @returns the `sub` of the session that ended, when known
3603
+ */
3604
+ logOut(): Promise<string | undefined>;
3605
+ /**
3606
+ * `Passflow.reset()` (PFL-792 amendment A2): the same local sign-out, synchronously, with no
3607
+ * API call and no lock. Emits no `SignOut`; the caller emits one.
3608
+ *
3609
+ * @returns the `sub` of the session that ended, when known
3610
+ */
3611
+ resetSession(): string | undefined;
3612
+ /** The logout API call. Server-side cleanup is best effort: every failure is ignored. */
3613
+ private callLogoutApi;
3614
+ /** Deletes every local trace of the session (both delivery modes) and clears the token cache. */
3615
+ private clearLocalSession;
3273
3616
  /**
3274
3617
  * The session and user identity this client currently holds, or `undefined`
3275
3618
  * when none is readable.
@@ -3302,21 +3645,38 @@ export declare class AuthService {
3302
3645
  * state — two different axes.
3303
3646
  */
3304
3647
  private assertSessionPin;
3648
+ /** The delivery mode the refresh transport sends in. BFF without a refresh URL has no endpoint: JSON, as before. */
3649
+ private refreshMode;
3650
+ /**
3651
+ * The controller's events, onto the store. Only `notify` is used (plus the error dedupe), so a
3652
+ * store double with `notify` alone works.
3653
+ */
3654
+ private createRefreshEmit;
3655
+ /**
3656
+ * One refresh request in the current delivery mode, with no events and no saving (architecture
3657
+ * §3.2): the controller decides what an answer or a failure means.
3658
+ *
3659
+ * - JSON: `AuthAPI.refreshToken(stored refresh token, stored scopes, stored access token[, upgrade])`.
3660
+ * - Cookie: `AuthAPI.refreshToken('', scopes[, undefined, upgrade])`; the server reads the HttpOnly cookie.
3661
+ * - BFF: `POST tokenExchange.refreshUrl`. A non-OK answer becomes a `PassflowError` (status, and
3662
+ * the body's `error.id` or `error.http.<status>`), so it is classified like any other answer.
3663
+ */
3664
+ private createRefreshTransport;
3665
+ /** The BFF refresh endpoint. A non-OK answer becomes a `PassflowError`; a network failure rejects as is. */
3666
+ private sendBffRefresh;
3305
3667
  /**
3306
3668
  * Refreshes the session, optionally committing an assurance upgrade.
3307
3669
  *
3308
- * This is the ONLY token-storing path for all four delivery modes, which is
3309
- * why the upgrade commit goes through here rather than posting to
3310
- * `/auth/refresh` directly: it saves the rotated tokens, updates the cache and
3311
- * emits `Refresh`. Bypassing it would elevate the session on the server and
3312
- * leave the client holding stale tokens — everything looks fine until the next
3313
- * request 401s.
3670
+ * Both go through the refresh controller, the one place this instance sends `/auth/refresh`: it
3671
+ * coordinates with other tabs, saves with a compare-and-set, classifies failures and emits the
3672
+ * events (PFL-792). A plain refresh is "fresh": it is satisfied only by tokens from a request
3673
+ * sent at or after the call. A failure keeps the tokens unless it really ends the session.
3314
3674
  *
3315
- * An upgrade commit changes the error handling in one specific way: a 4xx is
3316
- * NOT treated as a dead session. The server answers a wrong code with a 401,
3317
- * and tearing the session down there is the logout-at-step-up defect. The
3318
- * upgrade transport classifies those failures and invalidates the session
3319
- * itself when the 401 turns out to be a genuine expiry.
3675
+ * The upgrade commit is the one path that is never classified: any failure rejects with the
3676
+ * error unchanged and keeps the tokens, because a 4xx there is the server rejecting the PROOF,
3677
+ * not the session (logging the user out for mistyping six digits is the defect this flow exists
3678
+ * to fix). The upgrade transport classifies those failures itself. The session pin is checked
3679
+ * when the answer arrives, before anything is applied: see `assertSessionPin`.
3320
3680
  */
3321
3681
  refreshToken(upgrade?: UpgradeRefreshField, pin?: UpgradeSessionPin): Promise<PassflowAuthorizationResponse>;
3322
3682
  sendPasswordResetEmail(payload: PassflowSendPasswordResetEmailPayload): Promise<PassflowSuccessResponse>;
@@ -3347,6 +3707,13 @@ export declare class AuthService {
3347
3707
  * Handle session check and callbacks
3348
3708
  */
3349
3709
  submitSessionCheck(doRefresh?: boolean): Promise<Tokens | undefined>;
3710
+ /**
3711
+ * `getTokens(true)` / `session({ doRefresh: true })` (architecture §2.8): starts or joins a
3712
+ * refresh, then resolves with the cached tokens whatever the outcome: the kept tokens after a
3713
+ * transient, blocked or auth-with-strikes-left failure, `undefined` once the session ended. The
3714
+ * failure itself was reported by the controller, so it is not reported again here.
3715
+ */
3716
+ private refreshForSession;
3350
3717
  /**
3351
3718
  * Get tokens and refresh if needed
3352
3719
  * Cookie/BFF mode: Returns ID token only (access/refresh in HttpOnly cookies)
@@ -3396,15 +3763,6 @@ export declare class InvitationService {
3396
3763
  */
3397
3764
  getInvitationLink(invitationID: string): Promise<InviteLinkResponse>;
3398
3765
  }
3399
- /**
3400
- * Logger interface for Passflow services
3401
- */
3402
- interface Logger {
3403
- error(message: string, ...args: unknown[]): void;
3404
- warn(message: string, ...args: unknown[]): void;
3405
- info(message: string, ...args: unknown[]): void;
3406
- debug(message: string, ...args: unknown[]): void;
3407
- }
3408
3766
  /**
3409
3767
  * Flat user representation
3410
3768
  */
@@ -5116,8 +5474,12 @@ export declare class Passflow {
5116
5474
  * Creates proper ErrorPayload, distinguishes PassflowError from generic Error,
5117
5475
  * notifies error event, and re-throws the error.
5118
5476
  *
5477
+ * Reports at most once per error object: when a service already reported it (a 2FA wrapper,
5478
+ * a sign-in), the service's more specific report stands and nothing is added here.
5479
+ *
5119
5480
  * @param error - The error to handle
5120
- * @param context - Context description for the error
5481
+ * @param operation - The operation that failed (`ErrorPayload.operation`)
5482
+ * @param context - Context description for the fallback message
5121
5483
  * @throws The original error after handling
5122
5484
  */
5123
5485
  private handleError;
@@ -5200,11 +5562,31 @@ export declare class Passflow {
5200
5562
  * ```
5201
5563
  */
5202
5564
  reset(error?: string): void;
5565
+ /** The one `SignOut` of `logOut()`/`reset()` in this tab (PFL-792 §2.4). */
5566
+ private notifySignedOut;
5567
+ /**
5568
+ * Releases what this instance holds for token refresh: leaves the refresh lock queue, cancels
5569
+ * every refresh timer and the token check, removes its storage and foreground listeners, closes
5570
+ * its BroadcastChannel, and rejects pending refresh callers with `PassflowRefreshError`
5571
+ * (`error.refresh.disposed`). Afterwards the instance must not be used.
5572
+ *
5573
+ * The SDK never calls it by itself, not on `beforeunload` (it fires before a cancellable
5574
+ * "Leave site?" prompt and before a page enters the back/forward cache) nor on `pagehide`:
5575
+ * document teardown already releases the page's locks and channels. Call it when an SPA tears
5576
+ * the instance down, and in tests.
5577
+ *
5578
+ * @example
5579
+ * ```typescript
5580
+ * passflow.dispose();
5581
+ * ```
5582
+ */
5583
+ dispose(): void;
5203
5584
  /**
5204
5585
  * Refresh the access token using the refresh token.
5205
5586
  *
5206
5587
  * @returns Promise with new authorization response containing refreshed tokens
5207
- * @throws {Error} If no refresh token is found
5588
+ * @throws {Error} JSON mode with no stored refresh token (`No tokens found` / `No refresh token found`)
5589
+ * @throws {PassflowRefreshError} `error.refresh.session_ended` and the other ends, once the session ended
5208
5590
  * @throws {PassflowError} If refresh request fails
5209
5591
  *
5210
5592
  * @example
@@ -5904,6 +6286,51 @@ export declare class Passflow {
5904
6286
  */
5905
6287
  clearMagicLinkSession(): void;
5906
6288
  }
6289
+ /** §2.6 row 2: the session cannot be refreshed, whatever the status. */
6290
+ export declare const REFRESH_AUTH_FAILURE_IDS: readonly string[];
6291
+ /** Ids of {@link PassflowRefreshError}. */
6292
+ export declare const RefreshErrorId: {
6293
+ /** Joined another tab's cycle whose attempt failed transiently (status 503). */
6294
+ readonly unavailable: 'error.refresh.unavailable';
6295
+ /** Joined another tab's cycle whose attempt failed on auth with strikes left (status 401). */
6296
+ readonly authRetry: 'error.refresh.auth_retry';
6297
+ /** The session ended (strikes exhausted here or elsewhere). */
6298
+ readonly sessionEnded: 'error.refresh.session_ended';
6299
+ /** A sign-out (this tab or another tab of the user). */
6300
+ readonly signedOut: 'error.refresh.signed_out';
6301
+ /** `error.token.blocked` and no successor within 20 s. */
6302
+ readonly successorTimeout: 'error.refresh.successor_timeout';
6303
+ /** Storage now holds another user's tokens. */
6304
+ readonly identityChanged: 'error.refresh.identity_changed';
6305
+ /** `Passflow.dispose()` was called. */
6306
+ readonly disposed: 'error.refresh.disposed';
6307
+ };
6308
+ export type RefreshErrorId = (typeof RefreshErrorId)[keyof typeof RefreshErrorId];
6309
+ interface PassflowRefreshErrorOptions {
6310
+ message?: string;
6311
+ /** Epoch ms of the shared pending retry, for `unavailable` / `auth_retry`. */
6312
+ nextRetryAt?: number;
6313
+ /** The failure this error stands for (another tab's attempt, the request's own error). */
6314
+ cause?: unknown;
6315
+ }
6316
+ /** A refresh outcome the SDK decided, not the server: joined a failed cycle, or the session ended. */
6317
+ export declare class PassflowRefreshError extends PassflowError {
6318
+ readonly id: RefreshErrorId;
6319
+ readonly nextRetryAt?: number;
6320
+ readonly cause?: unknown;
6321
+ constructor(id: RefreshErrorId, options?: PassflowRefreshErrorOptions);
6322
+ }
6323
+ /** Why an upgrade commit was not sent, or its answer not applied. */
6324
+ type UpgradeCommitInterruptedReason = 'lock-timeout' | 'superseded';
6325
+ /**
6326
+ * Upgrade commit only: the commit could not be sent (lock wait over 10 s) or its answer arrived
6327
+ * after storage moved to another token. A plain Error with no `status` and no `response`: the
6328
+ * upgrade transport's classifier reads it as `network` (retryable, no invalidation).
6329
+ */
6330
+ export declare class UpgradeCommitInterruptedError extends Error {
6331
+ readonly reason: UpgradeCommitInterruptedReason;
6332
+ constructor(reason: UpgradeCommitInterruptedReason, message?: string);
6333
+ }
5907
6334
  /**
5908
6335
  * Validates JWT token format (not signature, just structure)
5909
6336
  * Checks that the token has 3 parts separated by dots and each part is valid base64url
@@ -5941,4 +6368,4 @@ export declare function isValidPhoneNumber(phone: string): boolean;
5941
6368
  * @returns true if valid username format, false otherwise
5942
6369
  */
5943
6370
  export declare function isValidUsername(username: string): boolean;
5944
- export type { AssuranceBar, AuthCallbackResult, BodyKind, ChallengeChannel, CreateClientConfig, CurrentUrlInfo, ErrorPayload, FetchLike, FetchMiddleware, GroupMembership, InvitationToken, M2MClientConfig, M2MErrorCode, M2MErrorResponse, M2MRateLimitInfo, M2MTokenCache, M2MTokenClaims, M2MTokenRequestInfo, M2MTokenRequestOptions, M2MTokenResponse, OpenAuthUrlOptions, PassflowEventPayload, PassflowSubscriber, PasskeySupport, PlatformAdapter, PlatformStorage, RawUserMembership, RegisterOptions, ReplayPolicy, RequestSnapshot, ResponseInterceptorHost, RetryStrategy, SnapshotHeader, Tenant, TenantMembership, Token, UpgradeAssertContext, UpgradeAssertion, UpgradeChallenge, UpgradeControllerOptions, UpgradeDiagnostic, UpgradeDiagnosticReason, UpgradeDiagnosticSink, UpgradeElevation, UpgradeEnableOptions, UpgradeEndpoint, UpgradeErrorKind, UpgradeFacade, UpgradeFactor, UpgradeFactorInput, UpgradeFactorType, UpgradeFlightState, UpgradeFlightSummary, UpgradeOptions, UpgradePrepared, UpgradePromptHandler, UpgradePromptRequest, UpgradePromptResult, UpgradeProof, UpgradeReplayInfo, UpgradeResult, UpgradeSessionPin, UpgradeTransport, UpgradeTransportErrorInit, UpgradeWaiterSummary, UserMembership, Waiter, WaiterEntry, WaiterOutcome, WaiterReport };
6371
+ export type { AssuranceBar, AuthCallbackResult, BodyKind, ChallengeChannel, CreateClientConfig, CurrentUrlInfo, ErrorPayload, EventOrigin, FetchLike, FetchMiddleware, GroupMembership, InvitationToken, M2MClientConfig, M2MErrorCode, M2MErrorResponse, M2MRateLimitInfo, M2MTokenCache, M2MTokenClaims, M2MTokenRequestInfo, M2MTokenRequestOptions, M2MTokenResponse, OpenAuthUrlOptions, OperationErrorPayload, PassflowEventPayload, PassflowOperation, PassflowRefreshErrorOptions, PassflowSubscriber, PasskeySupport, PlatformAdapter, PlatformStorage, RawUserMembership, RefreshChannel, RefreshClock, RefreshCoordination, RefreshCoordinator, RefreshErrorInfo, RefreshFailureKind, RefreshLockManager, RefreshLockOptions, RefreshTrigger, RegisterOptions, ReplayPolicy, RequestSnapshot, ResponseInterceptorHost, RetryStrategy, SessionExpiredReason, SnapshotHeader, Tenant, TenantMembership, Token, UpgradeAssertContext, UpgradeAssertion, UpgradeChallenge, UpgradeCommitInterruptedReason, UpgradeControllerOptions, UpgradeDiagnostic, UpgradeDiagnosticReason, UpgradeDiagnosticSink, UpgradeElevation, UpgradeEnableOptions, UpgradeEndpoint, UpgradeErrorKind, UpgradeFacade, UpgradeFactor, UpgradeFactorInput, UpgradeFactorType, UpgradeFlightState, UpgradeFlightSummary, UpgradeOptions, UpgradePrepared, UpgradePromptHandler, UpgradePromptRequest, UpgradePromptResult, UpgradeProof, UpgradeReplayInfo, UpgradeResult, UpgradeSessionPin, UpgradeTransport, UpgradeTransportErrorInit, UpgradeWaiterSummary, UserMembership, Waiter, WaiterEntry, WaiterOutcome, WaiterReport };