@passflow/core 0.20.1 → 0.21.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/dist/index.d.ts 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,33 @@ 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;
3153
3435
  initialize(): void;
3154
- private refreshTokensCache;
3155
3436
  startTokenCheck(): void;
3156
3437
  private setupLifecycleListeners;
3157
- private stopTokenCheck;
3438
+ /**
3439
+ * Stops the expiry check and its foreground listener. Public so the refresh controller can stop
3440
+ * it when a session ends in this tab (it is called optionally: test fakes may lack it).
3441
+ */
3442
+ stopTokenCheck(): void;
3158
3443
  /**
3159
3444
  * Cleanup method to stop all intervals and remove event listeners.
3160
3445
  * Should be called when the service is no longer needed.
@@ -3162,10 +3447,23 @@ export declare class TokenCacheService {
3162
3447
  destroy(): void;
3163
3448
  setTokensCache(tokens: Tokens | undefined): void;
3164
3449
  getTokens(): Tokens | undefined;
3450
+ /**
3451
+ * The cached tokens, refreshed first when the access token has expired (trigger `token-check`).
3452
+ *
3453
+ * It asks the refresh controller, which starts a refresh or joins the one pending, and returns
3454
+ * the cache whatever the outcome: the new tokens, the kept ones after a failure that did not end
3455
+ * the session, `undefined` once it ended. It never rejects; the controller reports the failure.
3456
+ */
3165
3457
  getTokensWithRefresh(): Promise<Tokens | undefined>;
3166
3458
  getParsedTokens(): ParsedTokens | undefined;
3167
3459
  isExpired(): boolean;
3168
3460
  }
3461
+ /** How this instance's refresh controller coordinates (architecture §3.3, §3.4). */
3462
+ export type AuthServiceRefreshOptions = {
3463
+ coordination: ResolvedCoordination;
3464
+ /** `PassflowConfig.refreshStrikes`; the controller resolves the default. */
3465
+ strikes?: number;
3466
+ };
3169
3467
  /**
3170
3468
  * Service for handling authentication related functionality
3171
3469
  */
@@ -3185,13 +3483,19 @@ export declare class AuthService {
3185
3483
  private tokenDeliveryManager;
3186
3484
  private tokenExchangeConfig?;
3187
3485
  private pendingPkce?;
3486
+ /**
3487
+ * The one place this instance sends `/auth/refresh` (PFL-792). Every refresh path asks it: the
3488
+ * explicit `refreshToken()`, the upgrade commit, `getTokens(true)`/`session()` and, once
3489
+ * `Passflow` attaches it, `TokenCacheService.getTokensWithRefresh()`.
3490
+ */
3491
+ readonly refresh: RefreshController;
3188
3492
  constructor(authApi: AuthAPI, deviceService: DeviceService, storageManager: StorageManager, subscribeStore: PassflowStore, tokenCacheService: TokenCacheService, scopes: string[], createTenantForNewUser: boolean, url: string, sessionCallbacks: {
3189
3493
  createSession?: ({ tokens, parsedTokens }: {
3190
3494
  tokens?: Tokens;
3191
3495
  parsedTokens?: ParsedTokens;
3192
3496
  }) => Promise<void>;
3193
3497
  expiredSession?: () => Promise<void>;
3194
- }, appId?: string | undefined, tokenExchangeConfig?: TokenExchangeConfig, platform?: PlatformAdapter, readPkceEnabled?: (() => Promise<boolean>) | undefined);
3498
+ }, appId?: string | undefined, tokenExchangeConfig?: TokenExchangeConfig, platform?: PlatformAdapter, readPkceEnabled?: (() => Promise<boolean>) | undefined, refreshOptions?: AuthServiceRefreshOptions);
3195
3499
  /**
3196
3500
  * Initialize session state on page load for cookie/BFF mode
3197
3501
  */
@@ -3203,11 +3507,20 @@ export declare class AuthService {
3203
3507
  */
3204
3508
  restoreSession(): Promise<boolean>;
3205
3509
  /**
3206
- * Process successful authentication response
3207
- * Handles token storage, session state, CSRF tokens
3208
- * In BFF mode, forwards tokens to BFF server
3510
+ * Applies a sign-in's tokens and starts a new session in the refresh controller.
3511
+ *
3512
+ * Every sign-in reaches the tokens through here: password, passwordless, passkey, federated,
3513
+ * the 2FA code and recovery-code completion, PKCE completion, `joinInvitation` and tokens from a
3514
+ * redirect (`applyTokens`). So this is where the controller learns of a new session (amendment
3515
+ * A1): a tab that signed out, or whose session ended, refreshes again on the new one.
3209
3516
  */
3210
3517
  private processAuthResponse;
3518
+ /**
3519
+ * Stores an authentication response: token storage, session state, CSRF token. In BFF mode it
3520
+ * forwards the tokens to the BFF server first. Starts no session: a cookie-mode refresh applies
3521
+ * its answer through here too.
3522
+ */
3523
+ private applyAuthResponse;
3211
3524
  /**
3212
3525
  * Reports whether a sign-in response is still waiting on its second factor,
3213
3526
  * and when it is, hands the 2FA token on.
@@ -3269,7 +3582,28 @@ export declare class AuthService {
3269
3582
  signUp(payload: PassflowSignUpPayload): Promise<PassflowAuthorizationResponse>;
3270
3583
  passwordlessSignIn(payload: PassflowPasswordlessSignInPayload): Promise<PassflowPasswordlessResponse>;
3271
3584
  passwordlessSignInComplete(payload: PassflowPasswordlessSignInCompletePayload): Promise<PassflowValidationResponse>;
3272
- logOut(): Promise<void>;
3585
+ /**
3586
+ * Signs this tab out under the refresh lock (PFL-792 §2.8): stops this tab's refresh callers and
3587
+ * retry timer, waits at most 5 s for the lock (a request already in flight lands first), posts
3588
+ * `signed-out` to the other tabs, calls the logout API with the NEWEST stored refresh token (best
3589
+ * effort), then deletes the stored tokens, id token and CSRF token and clears the token cache.
3590
+ *
3591
+ * Emits no `SignOut`: `Passflow.logOut()` emits exactly one, after this resolves.
3592
+ *
3593
+ * @returns the `sub` of the session that ended, when known
3594
+ */
3595
+ logOut(): Promise<string | undefined>;
3596
+ /**
3597
+ * `Passflow.reset()` (PFL-792 amendment A2): the same local sign-out, synchronously, with no
3598
+ * API call and no lock. Emits no `SignOut`; the caller emits one.
3599
+ *
3600
+ * @returns the `sub` of the session that ended, when known
3601
+ */
3602
+ resetSession(): string | undefined;
3603
+ /** The logout API call. Server-side cleanup is best effort: every failure is ignored. */
3604
+ private callLogoutApi;
3605
+ /** Deletes every local trace of the session (both delivery modes) and clears the token cache. */
3606
+ private clearLocalSession;
3273
3607
  /**
3274
3608
  * The session and user identity this client currently holds, or `undefined`
3275
3609
  * when none is readable.
@@ -3302,21 +3636,38 @@ export declare class AuthService {
3302
3636
  * state — two different axes.
3303
3637
  */
3304
3638
  private assertSessionPin;
3639
+ /** The delivery mode the refresh transport sends in. BFF without a refresh URL has no endpoint: JSON, as before. */
3640
+ private refreshMode;
3641
+ /**
3642
+ * The controller's events, onto the store. Only `notify` is used (plus the error dedupe), so a
3643
+ * store double with `notify` alone works.
3644
+ */
3645
+ private createRefreshEmit;
3646
+ /**
3647
+ * One refresh request in the current delivery mode, with no events and no saving (architecture
3648
+ * §3.2): the controller decides what an answer or a failure means.
3649
+ *
3650
+ * - JSON: `AuthAPI.refreshToken(stored refresh token, stored scopes, stored access token[, upgrade])`.
3651
+ * - Cookie: `AuthAPI.refreshToken('', scopes[, undefined, upgrade])`; the server reads the HttpOnly cookie.
3652
+ * - BFF: `POST tokenExchange.refreshUrl`. A non-OK answer becomes a `PassflowError` (status, and
3653
+ * the body's `error.id` or `error.http.<status>`), so it is classified like any other answer.
3654
+ */
3655
+ private createRefreshTransport;
3656
+ /** The BFF refresh endpoint. A non-OK answer becomes a `PassflowError`; a network failure rejects as is. */
3657
+ private sendBffRefresh;
3305
3658
  /**
3306
3659
  * Refreshes the session, optionally committing an assurance upgrade.
3307
3660
  *
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.
3661
+ * Both go through the refresh controller, the one place this instance sends `/auth/refresh`: it
3662
+ * coordinates with other tabs, saves with a compare-and-set, classifies failures and emits the
3663
+ * events (PFL-792). A plain refresh is "fresh": it is satisfied only by tokens from a request
3664
+ * sent at or after the call. A failure keeps the tokens unless it really ends the session.
3314
3665
  *
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.
3666
+ * The upgrade commit is the one path that is never classified: any failure rejects with the
3667
+ * error unchanged and keeps the tokens, because a 4xx there is the server rejecting the PROOF,
3668
+ * not the session (logging the user out for mistyping six digits is the defect this flow exists
3669
+ * to fix). The upgrade transport classifies those failures itself. The session pin is checked
3670
+ * when the answer arrives, before anything is applied: see `assertSessionPin`.
3320
3671
  */
3321
3672
  refreshToken(upgrade?: UpgradeRefreshField, pin?: UpgradeSessionPin): Promise<PassflowAuthorizationResponse>;
3322
3673
  sendPasswordResetEmail(payload: PassflowSendPasswordResetEmailPayload): Promise<PassflowSuccessResponse>;
@@ -3347,6 +3698,13 @@ export declare class AuthService {
3347
3698
  * Handle session check and callbacks
3348
3699
  */
3349
3700
  submitSessionCheck(doRefresh?: boolean): Promise<Tokens | undefined>;
3701
+ /**
3702
+ * `getTokens(true)` / `session({ doRefresh: true })` (architecture §2.8): starts or joins a
3703
+ * refresh, then resolves with the cached tokens whatever the outcome: the kept tokens after a
3704
+ * transient, blocked or auth-with-strikes-left failure, `undefined` once the session ended. The
3705
+ * failure itself was reported by the controller, so it is not reported again here.
3706
+ */
3707
+ private refreshForSession;
3350
3708
  /**
3351
3709
  * Get tokens and refresh if needed
3352
3710
  * Cookie/BFF mode: Returns ID token only (access/refresh in HttpOnly cookies)
@@ -3396,15 +3754,6 @@ export declare class InvitationService {
3396
3754
  */
3397
3755
  getInvitationLink(invitationID: string): Promise<InviteLinkResponse>;
3398
3756
  }
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
3757
  /**
3409
3758
  * Flat user representation
3410
3759
  */
@@ -5116,8 +5465,12 @@ export declare class Passflow {
5116
5465
  * Creates proper ErrorPayload, distinguishes PassflowError from generic Error,
5117
5466
  * notifies error event, and re-throws the error.
5118
5467
  *
5468
+ * Reports at most once per error object: when a service already reported it (a 2FA wrapper,
5469
+ * a sign-in), the service's more specific report stands and nothing is added here.
5470
+ *
5119
5471
  * @param error - The error to handle
5120
- * @param context - Context description for the error
5472
+ * @param operation - The operation that failed (`ErrorPayload.operation`)
5473
+ * @param context - Context description for the fallback message
5121
5474
  * @throws The original error after handling
5122
5475
  */
5123
5476
  private handleError;
@@ -5200,11 +5553,31 @@ export declare class Passflow {
5200
5553
  * ```
5201
5554
  */
5202
5555
  reset(error?: string): void;
5556
+ /** The one `SignOut` of `logOut()`/`reset()` in this tab (PFL-792 §2.4). */
5557
+ private notifySignedOut;
5558
+ /**
5559
+ * Releases what this instance holds for token refresh: leaves the refresh lock queue, cancels
5560
+ * every refresh timer and the token check, removes its storage and foreground listeners, closes
5561
+ * its BroadcastChannel, and rejects pending refresh callers with `PassflowRefreshError`
5562
+ * (`error.refresh.disposed`). Afterwards the instance must not be used.
5563
+ *
5564
+ * The SDK never calls it by itself, not on `beforeunload` (it fires before a cancellable
5565
+ * "Leave site?" prompt and before a page enters the back/forward cache) nor on `pagehide`:
5566
+ * document teardown already releases the page's locks and channels. Call it when an SPA tears
5567
+ * the instance down, and in tests.
5568
+ *
5569
+ * @example
5570
+ * ```typescript
5571
+ * passflow.dispose();
5572
+ * ```
5573
+ */
5574
+ dispose(): void;
5203
5575
  /**
5204
5576
  * Refresh the access token using the refresh token.
5205
5577
  *
5206
5578
  * @returns Promise with new authorization response containing refreshed tokens
5207
- * @throws {Error} If no refresh token is found
5579
+ * @throws {Error} JSON mode with no stored refresh token (`No tokens found` / `No refresh token found`)
5580
+ * @throws {PassflowRefreshError} `error.refresh.session_ended` and the other ends, once the session ended
5208
5581
  * @throws {PassflowError} If refresh request fails
5209
5582
  *
5210
5583
  * @example
@@ -5904,6 +6277,51 @@ export declare class Passflow {
5904
6277
  */
5905
6278
  clearMagicLinkSession(): void;
5906
6279
  }
6280
+ /** §2.6 row 2: the session cannot be refreshed, whatever the status. */
6281
+ export declare const REFRESH_AUTH_FAILURE_IDS: readonly string[];
6282
+ /** Ids of {@link PassflowRefreshError}. */
6283
+ export declare const RefreshErrorId: {
6284
+ /** Joined another tab's cycle whose attempt failed transiently (status 503). */
6285
+ readonly unavailable: 'error.refresh.unavailable';
6286
+ /** Joined another tab's cycle whose attempt failed on auth with strikes left (status 401). */
6287
+ readonly authRetry: 'error.refresh.auth_retry';
6288
+ /** The session ended (strikes exhausted here or elsewhere). */
6289
+ readonly sessionEnded: 'error.refresh.session_ended';
6290
+ /** A sign-out (this tab or another tab of the user). */
6291
+ readonly signedOut: 'error.refresh.signed_out';
6292
+ /** `error.token.blocked` and no successor within 20 s. */
6293
+ readonly successorTimeout: 'error.refresh.successor_timeout';
6294
+ /** Storage now holds another user's tokens. */
6295
+ readonly identityChanged: 'error.refresh.identity_changed';
6296
+ /** `Passflow.dispose()` was called. */
6297
+ readonly disposed: 'error.refresh.disposed';
6298
+ };
6299
+ export type RefreshErrorId = (typeof RefreshErrorId)[keyof typeof RefreshErrorId];
6300
+ interface PassflowRefreshErrorOptions {
6301
+ message?: string;
6302
+ /** Epoch ms of the shared pending retry, for `unavailable` / `auth_retry`. */
6303
+ nextRetryAt?: number;
6304
+ /** The failure this error stands for (another tab's attempt, the request's own error). */
6305
+ cause?: unknown;
6306
+ }
6307
+ /** A refresh outcome the SDK decided, not the server: joined a failed cycle, or the session ended. */
6308
+ export declare class PassflowRefreshError extends PassflowError {
6309
+ readonly id: RefreshErrorId;
6310
+ readonly nextRetryAt?: number;
6311
+ readonly cause?: unknown;
6312
+ constructor(id: RefreshErrorId, options?: PassflowRefreshErrorOptions);
6313
+ }
6314
+ /** Why an upgrade commit was not sent, or its answer not applied. */
6315
+ type UpgradeCommitInterruptedReason = 'lock-timeout' | 'superseded';
6316
+ /**
6317
+ * Upgrade commit only: the commit could not be sent (lock wait over 10 s) or its answer arrived
6318
+ * after storage moved to another token. A plain Error with no `status` and no `response`: the
6319
+ * upgrade transport's classifier reads it as `network` (retryable, no invalidation).
6320
+ */
6321
+ export declare class UpgradeCommitInterruptedError extends Error {
6322
+ readonly reason: UpgradeCommitInterruptedReason;
6323
+ constructor(reason: UpgradeCommitInterruptedReason, message?: string);
6324
+ }
5907
6325
  /**
5908
6326
  * Validates JWT token format (not signature, just structure)
5909
6327
  * Checks that the token has 3 parts separated by dots and each part is valid base64url
@@ -5941,4 +6359,4 @@ export declare function isValidPhoneNumber(phone: string): boolean;
5941
6359
  * @returns true if valid username format, false otherwise
5942
6360
  */
5943
6361
  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 };
6362
+ 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 };