import { type SignOut, type SupportedStorage } from "@supabase/supabase-js"; import { type SessionAdapter } from "./api-client.ts"; interface SupabaseSessionResult { data: { session: { access_token: string; } | null; }; } /** The three auth calls the API client needs, kept structural so any Supabase schema fits. */ export interface SupabaseSessionAuth { getSession(): Promise; refreshSession(): Promise; signOut(options?: SignOut): Promise<{ error: unknown; }>; } /** * Where auth-js keeps the session: the two `createClient` options it reads it from. Build the * object once and pass it both as `createClient`'s `auth` and to {@link signOutEvenOffline}, so * the key the helper clears is the key auth-js writes because they are one value, not because * two copies happen to agree. A wrong key would read as "auth-js already removed it", and the * session would stay. */ export interface SupabaseSessionStorage { storage: SupportedStorage; storageKey: string; } /** * Auth FAILED rather than answered, so nothing is known about the credential it was handed: * either the request never landed, or GoTrue failed on its own. * * This is the question every site that ends a session has to ask: a token refresh, an API door, and * a callback choosing between "try again" and "that link is spent". Without it one bad minute at * GoTrue signs out everybody whose token happened to need refreshing, and tells everybody clicking * a recovery link that it has expired, which sends them to ask the service that is down for a * replacement. * * Both clauses carry weight, and the second is why this is a function rather than one call. * `isAuthRetryableFetchError` is a NAME check, and the statuses that earn that name are a list * auth-js owns. The list has drifted: 502, 503 and 504 at 2.91; those plus the 52x family and still * no 500 at 2.106; 500 through 530 at 2.108. Code leaning on it alone is right at whichever version * happens to be installed, which is not the same as being right, and a fleet installs several * versions at once. It is also a LIST rather than a range, so it has holes at every version, drift * or no drift: at 2.112 nothing covers 505 through 519 or 531 up, and a 507 or a 599 with a JSON * body arrives as a plain `AuthApiError`. Pinning a recent SDK does not make the status clause * redundant. * * The same statuses with a body that is NOT JSON, such as a proxy's HTML page, arrive as an * `AuthUnknownError`, which carries no status at any status, so nothing here can tell that 507 from * a malformed 400. It answers false for both, on purpose: reading an unreadable 4xx as an outage * would leave a dead session retrying forever, which is the other half of the same bug. * * Read the version a tree really serves with `bun why @supabase/auth-js`, which prints one * heading per RESOLVED version; `node_modules` keeps copies the resolver does not serve. * * Anything that is not an auth error at all answers true: a failure from outside auth-js is not a * verdict on the credential either. Nothing at all answers false. `null` is what every supabase-js * auth call returns on success, and success is not an outage. */ export declare function isAuthOutage(error: unknown): boolean; /** * supabase-js's default storage key: `sb-`, the first label of the project's host name, and * `-auth-token`. Set it as `storageKey` yourself rather than leaving it to the default. The value * is the same, so nobody who is signed in gets signed out by the change, and the key no longer * depends on supabase-js working it out the same way in the next version. */ export declare function supabaseStorageKey(supabaseUrl: string): string; /** * The `{ storage, storageKey }` supabase-js builds for itself when you pass neither: localStorage * if it takes a write, a store in memory if not, under {@link supabaseStorageKey}. Pass it to * `createClient`'s `auth` and to {@link signOutEvenOffline}. Nobody signed in today is signed out * by the change. * * A function, not `{ storage: localStorage, … }`, because that object is built at module scope, * and reading `localStorage` there throws in two places. A prerender under bun has none, so the * build fails. A browser that blocks storage raises a SecurityError, so the page is blank. * supabase-js keeps the session in memory in both, and so does this. On React Native, build the * object yourself with your `AsyncStorage`. */ export declare function supabaseAuthStorage(supabaseUrl: string): SupabaseSessionStorage; /** * Signs out, and makes sure the session leaves this device even when auth cannot be reached. * * `auth.signOut()` alone does not. In two cases it resolves `{ error }` and keeps the stored * session. Both were measured on nine auth-js versions from 2.106.2 to 2.117.2; run * `bun why @supabase/auth-js` to see which one you have: * * - The access token has expired, or is close enough to expiring that auth-js refreshes it * first, and the network is down. The refresh fails and `signOut()` returns that error before * it touches storage. This happens at every version, and it is the common case: a laptop that * wakes up offline with a tab still open. * - Before 2.110.2, the token is still good but `/logout` gets no answer. `signOut()` returns * before it removes anything. * * Either way a reload reads the session and signs the person back in. On a shared computer, that * person is whoever sits down next. A screen that says "signed out" at that point is wrong. * * It ends this device's session and no other: `signOut({ scope: "local" })`, which fails in both * cases above too, because it calls `/logout` as well. auth-js's default is `"global"`, which ends * every session the person has. Signing out of a laptop then signed them out of their phone, and a * tab left open anywhere failed its next refresh with "Invalid Refresh Token: Refresh Token Not * Found". To end the other sessions on purpose, use {@link signOutOtherDevices}. * * Removing the key yourself is not enough on its own: nothing tells your `onAuthStateChange` * listeners or your other tabs, and auth-js's PKCE verifiers stay behind. So after removing it, * this calls `signOut({ scope: "local" })`. That call finds no session, skips `/logout`, and runs * auth-js's own cleanup: every key it owns, and one `SIGNED_OUT` to every listener. * * It waits on auth for {@link SIGN_OUT_TIMEOUT_MS} at most. Offline with an expired token, * `signOut()` first sits in auth-js's refresh backoff, which measured 15 s at 2.108.2 through * 2.116.0 and 41–51 s at 2.106.2. Nobody at a shared computer waits that long, and a page that * navigates away in the meantime takes the removal with it. * * It resolves once the stored session is gone. `error` is `null` when auth ended the session on the * server too. Otherwise it is the reason auth did not: its error, a thrown value or the deadline. * The session may still be live on the server. `SIGNED_OUT` arrives once * auth-js has finished with the first call: about 100 ms later from 2.108.2 on, and up to 22 s * later at 2.106.2. If your screen does not reload, clear your own store when this resolves instead * of waiting for the event. */ export declare function signOutEvenOffline(auth: Pick, { storage, storageKey }: SupabaseSessionStorage): Promise<{ error: unknown; }>; /** * Ends the person's sessions on every other device and keeps this one signed in. For a "sign out * of other devices" button in account settings: a phone that was lost, a computer left signed in. * * It resolves `{ error: null }` once auth has ended the other sessions. Otherwise `error` says why * it did not: auth's error, a thrown value, or the deadline. This device is untouched either way, * so the person is still signed in and can press the button again. * * Like {@link signOutEvenOffline}, it waits on auth for {@link SIGN_OUT_TIMEOUT_MS} at most, because * offline with an expired token auth-js first sits in its refresh backoff. A deadline does not * prove the other sessions are still live: auth may end them after this gave up. */ export declare function signOutOtherDevices(auth: Pick): Promise<{ error: unknown; }>; /** * Connects a Supabase auth client to {@link SessionAdapter}. * * The distinction this preserves is the one that decides whether anybody is signed out: only * auth ANSWERING proves a session is gone. A fetch that never landed says nothing about it, and * neither does a 5xx, which is auth failing rather than auth answering. A thrown network error * stays on that side of the line too. * * Pass `stored` and `signOut` goes through {@link signOutEvenOffline}. That changes what its * rejection means. It still rejects when auth did not end the session, but by then the session * has already left this device. And a sign-out auth has not answered within * {@link SIGN_OUT_TIMEOUT_MS} now rejects instead of hanging. Without `stored`, it is * `auth.signOut({ scope: "local" })` and rejects on that call's error. Either way it ends this * device's session only: a token one device's API refused says nothing about the others. * * `stored` is optional here but not at a sign-out button. The API client signs out only after * auth has answered: a refresh auth refused, or a new token the API refused. So the network was up * a moment earlier, and what `stored` covers is the connection dropping in between. */ export declare function createSupabaseSessionAdapter(auth: SupabaseSessionAuth, stored?: SupabaseSessionStorage): SessionAdapter; /** * Every link type a GoTrue email can carry, including ones this app's templates do not send today. * One donor rejected `invite` because "invites are never sent", which was true of the product and * false of the deployment: an operator inviting somebody from Studio sent a branded email whose * link then said it was invalid. A type the templates never emit costs nothing to accept: the hash * is the proof, and GoTrue opens a session only for the person whose one-time token it is. * * Closed on purpose: auth-js's own `EmailOtpType` ends in `(string & {})`, so it cannot make a * destination map name every case. `Record` can, and that is where an app * decides that `recovery` and `invite` both continue to the new-password screen. GoTrue creates an * invited account with NO password, so landing one "inside" signs them in once and locks them out * after. */ export type EmailLinkType = "signup" | "invite" | "magiclink" | "recovery" | "email_change" | "email"; /** Everything an auth callback address can carry, decided from the address alone. */ export type AuthCallback = /** An email link with a one-use `token_hash`: trade it with `verifyOtp`, exactly once. */ { kind: "email-link"; tokenHash: string; type: EmailLinkType; } /** * A session arriving in the address: a PKCE `code`, or the implicit flow's tokens. In a browser * with `detectSessionInUrl` on, the client has already taken it. Wait for the session, and do not * trade it again, which would be a second exchange racing the first. Where nothing reads the * address for you (React Native), `credential` is what `exchangeCodeForSession` or `setSession` * takes. */ | { kind: "session"; type: EmailLinkType | null; credential: { code: string; } | { access_token: string; refresh_token: string; }; } /** * GoTrue, or the provider behind it, reported a failure in the address. `code` is GoTrue's stable * `error_code` (`otp_expired`, `bad_oauth_state`, …) and is what to choose words by; * `error_description` is left out because it is English-only and often names the wrong cause. */ | { kind: "error"; error: string | null; code: string | null; cancelled: boolean; } /** Nothing usable: cut short between the mail client and here, or edited by hand. */ | { kind: "invalid"; }; /** * Reads an auth callback address. Pure, so every edge case is testable without a router or a * client. Pass `location.search` and `location.hash`, or the two halves of a deep link. * * GoTrue writes a failure into the FRAGMENT on every path and into the query only on some. After a * provider round trip, it writes both; after an implicit-flow email link, it writes the fragment * alone. A callback reading one of the two misses a failure the other carries, and the person lands * on a screen that says nothing. Both are read here. * * `cancelled` is the provider refusing on its own, which for Google is the person pressing Cancel. * That is their choice, not a failure to report. It is NOT `error === "access_denied"`: GoTrue * writes that same value for an expired email link, a banned user and a disabled signup, each with * an `error_code` beside it, while a refusal relayed from the provider arrives with none. The * obvious test reads an expired link as a change of mind. And `error` itself is absent for a status * outside GoTrue's OAuth mapping. A rate-limited link arrives with `error_code` alone. */ export declare function parseAuthCallback(search: string | URLSearchParams, hash?: string): AuthCallback; export {}; //# sourceMappingURL=supabase-session.d.ts.map