import type { AppDispatch } from "../../store/types"; import type { SublayState } from "../../store/sublayReducers"; import type { AuthUser } from "../../interfaces/models/User"; export type GetSublayState = () => { sublay: SublayState; }; export interface MintAccountAccessTokenArgs { dispatch: AppDispatch; getState: GetSublayState; projectId: string; /** The stored, non-active account to mint for. */ userId: string; } /** Raised when a mint cannot be completed. Never carries the token itself. */ export declare class AccountTokenMintError extends Error { constructor(message: string); } /** * Everything one exchange produced for the target account. * * `refreshToken` is the token that is LIVE after the exchange — the successor * when the server rotated, the presented one when it did not. Callers that * install a session must use this value and never the token they passed in: * the one they passed in is, by then, revoked. */ export interface MintedAccountSession { accessToken: string; refreshToken: string; /** The profile the exchange returned, when it carried one. */ user: AuthUser | null; } /** * A session held open across the caller's install, so nothing can rotate behind * it. See `leaseAccountSession`. */ export interface MintedAccountLease { session: MintedAccountSession; /** * **Must be called, from a `finally`.** Until it is, every further mint for * this account is served the same session rather than starting a new * exchange — which is the point, and also means a leaked lease pins a stale * session for the lifetime of the process. * * Idempotent. */ release(): void; } /** Test seam — the map above is module state shared across a whole run. */ export declare function resetAccountTokenMints(): void; /** * The same exchange, with the flight held open until the caller says it is * done. * * ───────────────────────────────────────────────────────────────────────────── * WHY A LEASE AND NOT JUST A PROMISE * ───────────────────────────────────────────────────────────────────────────── * Without one, the flight is evicted the moment the exchange settles — before * the awaiting caller's continuation has even been scheduled. Anything that * asks for this account in that gap starts a SECOND exchange, which is a * perfectly legal rotation: it presents the successor S1 (already durably in * the map) and gets S2 back, writing S2 to the map. * * **"Anything" includes a SECOND TRANSITION INTO THE SAME ACCOUNT,** which is * why this survived the removal of push reconciliation's bulk pass. Nothing * upstream serializes transitions: `activateStoredAccount` has no re-entrancy * guard and is publicly exported, and `useSwitchAccount` never reads its own * `isSwitching` flag as a guard — that flag is per-hook-instance React state, * set inside the async callback, so a double tap or two mounted switchers both * get through, and the `userId === activeAccountId` early return only starts * refusing once the first transition has already installed. Remove the hold and * a double tap costs two exchanges, with the first tap installing the revoked * one. * * That is fine for the second caller and fatal for the first. The transition * core installs S1 into the auth slice, `useAccountSync` Phase B then rebuilds * the map entry from the live `refreshToken` — putting the revoked S1 back over * S2 and persisting it — and the next ordinary refresh presents a revoked * token. That trips reuse detection, which **destroys the account's whole token * family**. The user is signed out of that account with no route back. * * Re-reading the map at install time cannot fix this: the install runs * synchronously from teardown to `setTokens`, so a second exchange's response * can never land inside it. The only place to close the window is here — hold * the flight until the install has happened, so no second exchange can start at * all. * * **The work between acquiring and releasing must contain no `await`.** A lease * is a lock on this account's credential; holding it across I/O would stall * every other mint for that account behind it, and a leaked lease pins a stale * session forever. The one caller (`activateStoredAccount`) installs * synchronously and releases in a `finally` — and a second transition arriving * in the meantime is served that same session rather than rotating behind it. * * A failed exchange releases its own hold before rejecting — there is no * session to install, so there is nothing to protect. */ export declare function leaseAccountSession(args: MintAccountAccessTokenArgs): Promise; /** * Just the bearer token — for the one caller that needs to authorize a single * request as a stored account it is not signed into: the per-account push * toggle. Shares the same single flight, so a toggle and a transition racing * for the same account cost ONE rotation between them. * * **The non-holding entry point, and the only one.** There used to be a second * — a `mintAccountSession` returning the whole exchange result to a caller that * only read it — for push reconciliation's bulk pass. That pass no longer * exchanges anything, so the entry point went with it rather than staying as a * general "hand me a live session for any stored account" affordance with no * consumer to justify it. * * A caller that INSTALLS the returned credential as the live session must not * use this: it releases the flight the moment the exchange settles, which * reopens the window `leaseAccountSession` exists to close. */ export declare function mintAccountAccessToken(args: MintAccountAccessTokenArgs): Promise;