@faable/auth-js 1.6.1 → 1.6.2-next.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/README.md CHANGED
@@ -9,7 +9,7 @@
9
9
  <a href="https://faable.com">
10
10
  <h1 align="center">auth-js</h1>
11
11
  </a>
12
- <p align="center">An isomorphic Javascript library for Faable Auth</p>
12
+ <p align="center">An isomorphic JavaScript client for Faable Auth.</p>
13
13
  </p>
14
14
 
15
15
  <p align="center">
@@ -18,33 +18,226 @@
18
18
  </a>
19
19
  </p>
20
20
 
21
+ ## Features
22
+
23
+ - OAuth social connections (Google, GitHub, …) with PKCE and implicit flows
24
+ - Username + password login
25
+ - Passwordless: email magic link and OTP code
26
+ - Automatic token refresh with cross-tab synchronization via `BroadcastChannel`
27
+ - Pluggable storage adapters (`localStorage`, cookies, or custom)
28
+ - Server-side session helpers for Next.js
29
+
21
30
  ## Install
22
31
 
23
32
  ```bash
24
- npm install @faable/auth-js
33
+ npm install @faable/auth-js
34
+ ```
35
+
36
+ Requires Node.js `>=22.8` for development. The published bundle runs in any
37
+ modern browser and in Node/SSR environments.
38
+
39
+ ## Quick start
40
+
41
+ ```ts
42
+ import { createClient } from '@faable/auth-js'
43
+
44
+ export const auth = createClient({
45
+ domain: '<faableauth_domain>',
46
+ clientId: '<client_id>',
47
+ redirectUri: window.location.origin
48
+ })
49
+
50
+ // Trigger a social login
51
+ await auth.signInWithOauthConnection({ connection: 'google' })
52
+ ```
53
+
54
+ ## Configuration
55
+
56
+ `createClient(config)` accepts:
57
+
58
+ | Option | Type | Description |
59
+ | --------------- | ------------------ | --------------------------------------------------------------------- |
60
+ | `domain` | `string` | **Required.** Your Faable Auth tenant domain. |
61
+ | `clientId` | `string` | **Required.** Application client ID. |
62
+ | `redirectUri` | `string` | Default callback URL. Falls back to `window.location.origin`. |
63
+ | `scope` | `string` | Space-separated scopes. Defaults to `openid profile email`. |
64
+ | `storage` | `SupportedStorage` | Custom storage adapter. Defaults to `localStorage`. |
65
+ | `storageKey` | `string` | Prefix for the storage key. Final key is `${storageKey}-${clientId}`. |
66
+ | `cookieOptions` | `CookieOptions` | When set, switches storage to the cookie adapter. |
67
+ | `lock` | `LockFunc` | Custom locking primitive for concurrent refreshes. |
68
+ | `debug` | `boolean` | Enables verbose logging. |
69
+
70
+ ## Authentication flows
71
+
72
+ ### OAuth / social connection
73
+
74
+ ```ts
75
+ // Use the default connection configured on the tenant
76
+ await auth.signInWithOauthConnection({})
77
+
78
+ // Or pick a specific provider (by name or connection_id)
79
+ await auth.signInWithOauthConnection({
80
+ connection_id: 'conn_01HX…', // preferred when known; falls back to `connection` for legacy tenants
81
+ redirectTo: 'https://app.example.com/callback',
82
+ scopes: 'openid profile email',
83
+ queryParams: { prompt: 'select_account' }
84
+ })
85
+ ```
86
+
87
+ In browsers the SDK uses the PKCE flow by default and exchanges the `code` for a
88
+ session on the callback page. The first call to `createClient` automatically
89
+ processes the URL when the user lands back on the redirect target.
90
+
91
+ ### Username + password
92
+
93
+ ```ts
94
+ await auth.signInWithUsernamePassword({
95
+ username: 'user@example.com',
96
+ password: '••••••••',
97
+ redirectTo: 'https://app.example.com/callback'
98
+ })
99
+ ```
100
+
101
+ ### Passwordless (magic link or OTP)
102
+
103
+ ```ts
104
+ // Step 1 — request a code or link
105
+ await auth.signInWithPasswordless({
106
+ email: 'user@example.com',
107
+ type: 'code' // or "link"
108
+ })
109
+
110
+ // Step 2 — complete the login with the OTP the user received
111
+ const { data, error } = await auth.signInWithOtp({
112
+ username: 'user@example.com',
113
+ otp: '123456'
114
+ })
115
+ ```
116
+
117
+ ### Password reset
118
+
119
+ ```ts
120
+ await auth.changePassword({ email: 'user@example.com' })
121
+ ```
122
+
123
+ ### Sign out
124
+
125
+ ```ts
126
+ await auth.signOut() // global — all sessions for this user
127
+ await auth.signOut({ scope: 'local' }) // only this device
128
+ ```
129
+
130
+ ## Sessions and state changes
131
+
132
+ ```ts
133
+ // Get the current session (refreshes if needed)
134
+ const {
135
+ data: { session }
136
+ } = await auth.getSession()
137
+
138
+ // Subscribe to auth events
139
+ const {
140
+ data: { subscription }
141
+ } = auth.onAuthStateChange((event, session) => {
142
+ // event: INITIAL_SESSION | SIGNED_IN | SIGNED_OUT | TOKEN_REFRESHED | PASSWORD_RECOVERY | USER_UPDATED
143
+ })
144
+
145
+ // Stop listening
146
+ subscription.unsubscribe()
147
+
148
+ // Force a refresh
149
+ await auth.refreshSession()
25
150
  ```
26
151
 
27
- ## Configure
152
+ Auth events are broadcast across tabs using `BroadcastChannel`, so a sign-in or
153
+ sign-out in one tab is reflected in every other tab using the same `storageKey`.
154
+
155
+ ## Storage adapters
156
+
157
+ ### Trade-offs
158
+
159
+ Refresh tokens are sensitive: anyone who reads them can impersonate the user
160
+ until the token is revoked. The storage you pick decides where they live:
161
+
162
+ - **`localStorage` (default)** — simple and supports cross-tab sync via
163
+ `BroadcastChannel`, but any script running on the same origin can read it. **A
164
+ single XSS lets an attacker exfiltrate the refresh token.** Acceptable for
165
+ low-risk apps and prototypes; not recommended when the surface has third-party
166
+ scripts, user-generated HTML, or strict compliance requirements.
167
+ - **Cookies** — required for SSR (server reads them on every request) and the
168
+ only adapter that lets you scope storage with `Secure`, `SameSite`, and
169
+ `Domain`. Note that this library writes cookies from JavaScript, so they
170
+ cannot be marked `HttpOnly`; an XSS can still read them, but cookies make CSRF
171
+ and same-site policies enforceable in a way `localStorage` does not.
172
+ - **Custom adapter** — use for in-memory storage (tokens lost on reload, safest
173
+ against XSS), Web Workers, or platform-specific keychains.
28
174
 
29
- ```js
30
- import { createClient } from "@faable/auth-js";
175
+ If your app is exposed to untrusted content, prefer cookies with `Secure: true`
176
+ and `SameSite: "Lax"` (or `"Strict"`), and treat XSS prevention (CSP, escaping,
177
+ framework guarantees) as a hard requirement regardless of which adapter you
178
+ pick.
179
+
180
+ ### localStorage (default)
181
+
182
+ Used automatically in browsers. No configuration required.
183
+
184
+ ### Cookies
185
+
186
+ Useful for SSR setups where the server must read the session from the request.
187
+
188
+ ```ts
189
+ import { createClient } from '@faable/auth-js'
31
190
 
32
191
  export const auth = createClient({
33
- domain: "<faableauth_domain>",
34
- clientId: "<client_id>",
35
- });
192
+ domain: '<faableauth_domain>',
193
+ clientId: '<client_id>',
194
+ cookieOptions: {
195
+ domain: '.example.com',
196
+ path: '/',
197
+ sameSite: 'Lax',
198
+ secure: true,
199
+ maxAge: 60 * 60 * 24 * 7
200
+ }
201
+ })
36
202
  ```
37
203
 
38
- ## Login
204
+ ### Custom adapter
39
205
 
40
- To login with a specific connection (Google, Facebook, etc.), you can set it once on your client instance.
206
+ Provide any object that implements `getItem`, `setItem`, and `removeItem` (sync
207
+ or async). Set `isServer: true` if values may come from an untrusted source such
208
+ as request cookies.
41
209
 
42
- ```js
43
- // Sign in using default connection
44
- auth.signInWithOauthConnection();
210
+ ```ts
211
+ const memoryStorage = {
212
+ store: new Map<string, string>(),
213
+ getItem: (k: string) => memoryStorage.store.get(k) ?? null,
214
+ setItem: (k: string, v: string) => void memoryStorage.store.set(k, v),
215
+ removeItem: (k: string) => void memoryStorage.store.delete(k)
216
+ }
45
217
 
46
- // Or use specific connection
47
- auth.signInWithOauthConnection({
48
- connection: "<connection_id>",
49
- });
218
+ createClient({ domain, clientId, storage: memoryStorage })
50
219
  ```
220
+
221
+ ## Next.js / server-side
222
+
223
+ Use cookie storage on the client, then read the session from `next/headers` on
224
+ the server:
225
+
226
+ ```ts
227
+ // app/page.tsx
228
+ import { cookies } from "next/headers";
229
+ import { getSessionFromCookies } from "@faable/auth-js";
230
+
231
+ export default async function Page() {
232
+ const session = getSessionFromCookies(cookies(), "faable.auth.token-<client_id>");
233
+ if (!session) return <SignIn />;
234
+ return <Dashboard user={session.user} />;
235
+ }
236
+ ```
237
+
238
+ The storage key follows the pattern
239
+ `${storageKey ?? "faable.auth.token"}-${clientId}`.
240
+
241
+ ## License
242
+
243
+ See [LICENSE.md](LICENSE.md).
@@ -1,4 +1,4 @@
1
- import { BaseLog, BaseLogOptions } from "./BaseLog";
1
+ import { BaseLog, BaseLogOptions } from './BaseLog';
2
2
  export declare abstract class Base extends BaseLog {
3
3
  private static nextInstanceID;
4
4
  private instanceID;
@@ -1,5 +1,5 @@
1
- import { BaseLog, BaseLogOptions } from "./BaseLog";
2
- import { AuthError } from "./lib/errors";
1
+ import { BaseLog, BaseLogOptions } from './BaseLog';
2
+ import { AuthError } from './lib/errors';
3
3
  export default class FaableAuthApi extends BaseLog {
4
4
  base_url: string;
5
5
  constructor(base_url: string, config: BaseLogOptions);
@@ -1,18 +1,19 @@
1
- import { SupportedStorage, SignInWithOAuthConnection, OAuthResponse, Subscription, InitializeResult, CallRefreshTokenResult } from "./lib/types";
2
- import { FaableAuthClientConfig, AuthResponse, AuthChangeEvent } from "./lib/types";
3
- import { Session, SignOut } from "./lib/types";
4
- import { Deferred } from "./lib/helpers";
5
- import { cookieStorageAdapter } from "./lib/storage/cookie-storage";
6
- import { getSessionFromCookies } from "./lib/nextjs";
1
+ import { Base } from './Base';
2
+ import FaableAuthApi from './FaableAuthApi';
3
+ import { BroadcastSync } from './lib/broadcast_sync';
4
+ import { AuthError } from './lib/errors';
5
+ import { Deferred } from './lib/helpers';
6
+ import { getSessionFromCookies } from './lib/nextjs';
7
+ import { cookieStorageAdapter } from './lib/storage/cookie-storage';
8
+ import { AuthFlowType, CallRefreshTokenResult, InitializeResult, OAuthResponse, SignInWithOAuthConnection, Subscription, SupportedStorage } from './lib/types';
9
+ import { AuthChangeEvent, AuthResponse, FaableAuthClientConfig } from './lib/types';
10
+ import { Session, SignOut } from './lib/types';
11
+ import { Lock } from './lock/Lock';
7
12
  export { cookieStorageAdapter, getSessionFromCookies };
8
- import { AuthError } from "./lib/errors";
9
- import FaableAuthApi from "./FaableAuthApi";
10
- import { Base } from "./Base";
11
- import { Lock } from "./lock/Lock";
12
13
  export declare class FaableAuthClient extends Base {
13
14
  domainUrl: string;
14
15
  tokenIssuer: string;
15
- redirect_uri: string;
16
+ redirectUri: string;
16
17
  scope?: string;
17
18
  sessionCheckExpiryDays: number;
18
19
  protected initializePromise: Promise<InitializeResult> | null;
@@ -26,11 +27,16 @@ export declare class FaableAuthClient extends Base {
26
27
  protected visibilityChangedCallback: (() => Promise<any>) | null;
27
28
  protected refreshingDeferred: Deferred<CallRefreshTokenResult> | null;
28
29
  /**
29
- * Used to broadcast state change events to other tabs listening.
30
+ * Cross-tab broadcaster + local subscriber registry for state changes.
30
31
  */
31
- protected broadcastChannel: BroadcastChannel | null;
32
- protected stateChangeEmitters: Map<string, Subscription>;
32
+ protected broadcastSync: BroadcastSync;
33
33
  protected _session: Session | null;
34
+ /**
35
+ * Initiation flow to use when redirecting to /authorize. Defaults to
36
+ * PKCE in browsers (recommended for SPAs) and implicit otherwise.
37
+ * Distinct from the callback-side flow which is detected from URL params.
38
+ */
39
+ protected flowType: AuthFlowType;
34
40
  protected lock: Lock;
35
41
  constructor(config: FaableAuthClientConfig);
36
42
  /**
@@ -83,8 +89,8 @@ export declare class FaableAuthClient extends Base {
83
89
  * refresh the session. If refreshing fails it will be retried for as long as
84
90
  * necessary.
85
91
  *
86
- * If you set the {@link GoTrueClientOptions#autoRefreshToken} you don't need
87
- * to call this function, it will be called for you.
92
+ * If `autoRefreshToken` is enabled in the client config you don't need to
93
+ * call this function, it will be called for you.
88
94
  *
89
95
  * On browsers the refresh process works only when the tab/window is in the
90
96
  * foreground to conserve resources as well as prevent race conditions and
@@ -121,9 +127,12 @@ export declare class FaableAuthClient extends Base {
121
127
  signInWithUsernamePassword(data: {
122
128
  username: string;
123
129
  password: string;
124
- redirect_uri?: string;
130
+ redirectTo?: string;
125
131
  state?: string;
126
- }): Promise<void>;
132
+ }): Promise<{
133
+ data: null;
134
+ error: AuthError | null;
135
+ }>;
127
136
  /**
128
137
  * Completes a passwordless login using an OTP code.
129
138
  * @param data The username and OTP code.
@@ -138,14 +147,17 @@ export declare class FaableAuthClient extends Base {
138
147
  */
139
148
  signInWithPasswordless(data: {
140
149
  email: string;
141
- type: "code" | "link";
150
+ type: 'code' | 'link';
142
151
  }): Promise<{
143
152
  data: any;
144
153
  error: AuthError | null;
145
154
  }>;
146
155
  changePassword(params: {
147
156
  email: string;
148
- }): Promise<unknown>;
157
+ }): Promise<{
158
+ data: unknown;
159
+ error: AuthError | null;
160
+ }>;
149
161
  buildAuthorizeUrl(options?: {
150
162
  connection?: string;
151
163
  redirectTo?: string;
@@ -210,7 +222,6 @@ export declare class FaableAuthClient extends Base {
210
222
  */
211
223
  private __loadSession;
212
224
  private _removeSession;
213
- private _isValidSession;
214
225
  protected _setSession(currentSession: {
215
226
  access_token: string;
216
227
  refresh_token: string;
@@ -239,9 +250,7 @@ export declare class FaableAuthClient extends Base {
239
250
  signOut(options?: SignOut): Promise<{
240
251
  error: AuthError | null;
241
252
  }>;
242
- protected _signOut({ scope, returnTo }?: SignOut & {
243
- returnTo?: string;
244
- }): Promise<{
253
+ protected _signOut({ scope }?: SignOut): Promise<{
245
254
  error: AuthError | null;
246
255
  }>;
247
256
  /**
@@ -1,3 +1,3 @@
1
- import { FaableAuthClient } from "./FaableAuthClient";
2
- import { FaableAuthClientConfig } from "./lib/types";
1
+ import { FaableAuthClient } from './FaableAuthClient';
2
+ import { FaableAuthClientConfig } from './lib/types';
3
3
  export declare const createClient: (config: FaableAuthClientConfig) => FaableAuthClient;
@@ -0,0 +1,7 @@
1
+ import { FaableAuthClient, cookieStorageAdapter, getSessionFromCookies } from './FaableAuthClient';
2
+ import { createClient } from './createClient';
3
+ import { AuthError } from './lib/errors';
4
+ import { Session, User } from './lib/types';
5
+ import type { AuthChangeEvent, AuthFlowType, AuthResponse, CookieOptions, FaableAuthClientConfig, OAuthResponse, Provider, SignInWithOAuthConnection, SignOut, Subscription, SupportedStorage } from './lib/types';
6
+ export { Session, User, FaableAuthClient, AuthError, createClient, cookieStorageAdapter, getSessionFromCookies };
7
+ export type { FaableAuthClientConfig, SignInWithOAuthConnection, AuthResponse, AuthChangeEvent, Subscription, SignOut, SupportedStorage, CookieOptions, OAuthResponse, AuthFlowType, Provider };
@@ -1,5 +1 @@
1
- import { FaableAuthClient } from "../FaableAuthClient";
2
- import { Session, User } from "../lib/types";
3
- import { AuthError } from "../lib/errors";
4
- import { createClient } from "../createClient";
5
- export { Session, User, FaableAuthClient, AuthError, createClient };
1
+ export * from '../entry';
@@ -1,5 +1 @@
1
- import { FaableAuthClient } from "../FaableAuthClient";
2
- import { Session, User } from "../lib/types";
3
- import { AuthError } from "../lib/errors";
4
- import { createClient } from "../createClient";
5
- export { Session, User, FaableAuthClient, AuthError, createClient };
1
+ export * from '../entry';
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Picks the OAuth `response_type` for an authorize request.
3
+ *
4
+ * Browsers default to the authorization code flow (PKCE); non-browser
5
+ * environments default to the implicit token flow. A caller-provided
6
+ * value always wins.
7
+ */
8
+ export declare const resolveResponseType: (options: {
9
+ response_type?: string;
10
+ }, isBrowserEnv: boolean) => string;
11
+ /**
12
+ * Renders an HTML form returned by the authorization server and submits it,
13
+ * triggering the browser navigation that completes the username/password
14
+ * login flow.
15
+ */
16
+ export declare const buildAndSubmitForm: (formHtml: string, doc: Document) => void;
@@ -0,0 +1,22 @@
1
+ import type { AuthChangeEvent, Session, Subscription } from './types';
2
+ type Listener = (event: AuthChangeEvent, session: Session | null) => void | Promise<void>;
3
+ type DebugFn = (message: string, ...args: unknown[]) => void;
4
+ /**
5
+ * Wraps a BroadcastChannel plus an in-process subscriber registry so the
6
+ * auth client can fan out state changes to local subscribers and other tabs
7
+ * in one call. Cross-tab messages are delivered to local subscribers but
8
+ * NOT re-broadcast, to avoid echo loops.
9
+ */
10
+ export declare class BroadcastSync {
11
+ private debug;
12
+ private channel;
13
+ private subscribers;
14
+ constructor(channelName: string, debug: DebugFn);
15
+ subscribe(callback: Listener): {
16
+ subscription: Subscription;
17
+ };
18
+ notify(event: AuthChangeEvent, session: Session | null, broadcast?: boolean): Promise<void>;
19
+ private dispatch;
20
+ close(): void;
21
+ }
22
+ export {};
@@ -1,9 +1,8 @@
1
- export declare const GOTRUE_URL = "http://localhost:9999";
2
1
  export declare const STORAGE_KEY = "faableauth";
3
2
  export declare const EXPIRY_MARGIN = 10;
4
3
  export declare const API_VERSION_HEADER_NAME = "X-Faableauth-Api-Version";
5
4
  export declare const API_VERSIONS: {
6
- "2024-01-01": {
5
+ '2024-01-01': {
7
6
  timestamp: number;
8
7
  name: string;
9
8
  };
@@ -3,7 +3,7 @@
3
3
  * not included in this list (if the client library is older than the version
4
4
  * on the server).
5
5
  */
6
- export type ErrorCode = "unexpected_failure" | "validation_failed" | "bad_json" | "email_exists" | "phone_exists" | "bad_jwt" | "not_admin" | "no_authorization" | "user_not_found" | "session_not_found" | "flow_state_not_found" | "flow_state_expired" | "signup_disabled" | "user_banned" | "provider_email_needs_verification" | "invite_not_found" | "bad_oauth_state" | "bad_oauth_callback" | "oauth_provider_not_supported" | "unexpected_audience" | "single_identity_not_deletable" | "email_conflict_identity_not_deletable" | "identity_already_exists" | "email_provider_disabled" | "phone_provider_disabled" | "too_many_enrolled_mfa_factors" | "mfa_factor_name_conflict" | "mfa_factor_not_found" | "mfa_ip_address_mismatch" | "mfa_challenge_expired" | "mfa_verification_failed" | "mfa_verification_rejected" | "insufficient_aal" | "captcha_failed" | "saml_provider_disabled" | "manual_linking_disabled" | "sms_send_failed" | "email_not_confirmed" | "phone_not_confirmed" | "reauth_nonce_missing" | "saml_relay_state_not_found" | "saml_relay_state_expired" | "saml_idp_not_found" | "saml_assertion_no_user_id" | "saml_assertion_no_email" | "user_already_exists" | "sso_provider_not_found" | "saml_metadata_fetch_failed" | "saml_idp_already_exists" | "sso_domain_already_exists" | "saml_entity_id_mismatch" | "conflict" | "provider_disabled" | "user_sso_managed" | "reauthentication_needed" | "same_password" | "reauthentication_not_valid" | "otp_expired" | "otp_disabled" | "identity_not_found" | "weak_password" | "over_request_rate_limit" | "over_email_send_rate_limit" | "over_sms_send_rate_limit" | "bad_code_verifier";
6
+ export type ErrorCode = 'unexpected_failure' | 'validation_failed' | 'bad_json' | 'email_exists' | 'phone_exists' | 'bad_jwt' | 'not_admin' | 'no_authorization' | 'user_not_found' | 'session_not_found' | 'flow_state_not_found' | 'flow_state_expired' | 'signup_disabled' | 'user_banned' | 'provider_email_needs_verification' | 'invite_not_found' | 'bad_oauth_state' | 'bad_oauth_callback' | 'oauth_provider_not_supported' | 'unexpected_audience' | 'single_identity_not_deletable' | 'email_conflict_identity_not_deletable' | 'identity_already_exists' | 'email_provider_disabled' | 'phone_provider_disabled' | 'too_many_enrolled_mfa_factors' | 'mfa_factor_name_conflict' | 'mfa_factor_not_found' | 'mfa_ip_address_mismatch' | 'mfa_challenge_expired' | 'mfa_verification_failed' | 'mfa_verification_rejected' | 'insufficient_aal' | 'captcha_failed' | 'saml_provider_disabled' | 'manual_linking_disabled' | 'sms_send_failed' | 'email_not_confirmed' | 'phone_not_confirmed' | 'reauth_nonce_missing' | 'saml_relay_state_not_found' | 'saml_relay_state_expired' | 'saml_idp_not_found' | 'saml_assertion_no_user_id' | 'saml_assertion_no_email' | 'user_already_exists' | 'sso_provider_not_found' | 'saml_metadata_fetch_failed' | 'saml_idp_already_exists' | 'sso_domain_already_exists' | 'saml_entity_id_mismatch' | 'conflict' | 'provider_disabled' | 'user_sso_managed' | 'reauthentication_needed' | 'same_password' | 'reauthentication_not_valid' | 'otp_expired' | 'otp_disabled' | 'identity_not_found' | 'weak_password' | 'over_request_rate_limit' | 'over_email_send_rate_limit' | 'over_sms_send_rate_limit' | 'bad_code_verifier';
7
7
  export declare class AuthError extends Error {
8
8
  /**
9
9
  * Error code associated with the error. Most errors coming from
@@ -1,5 +1,5 @@
1
- import { AuthResponse, SupportedStorage, User } from "./types";
2
- import { JsonResponse } from "./fetch";
1
+ import { JsonResponse } from './fetch';
2
+ import { AuthResponse, SupportedStorage, User } from './types';
3
3
  export declare function decodeBase64URL(value: string): string;
4
4
  export declare function generatePKCEVerifier(): string;
5
5
  export declare function generatePKCEChallenge(verifier: string): Promise<string>;
@@ -19,7 +19,7 @@ export type RawAuthResponse = {
19
19
  token_type: string;
20
20
  };
21
21
  export declare function expiresAt(expiresIn: number): number;
22
- export declare function _sessionResponse({ data, }: JsonResponse<Partial<RawAuthResponse>>): AuthResponse;
22
+ export declare function _sessionResponse({ data }: JsonResponse<Partial<RawAuthResponse>>): AuthResponse;
23
23
  /**
24
24
  * A deferred represents some asynchronous work that is not yet finished, which
25
25
  * may or may not culminate in a value.
@@ -42,7 +42,7 @@ export declare function retryable<T>(fn: (attempt: number) => Promise<T>, isRetr
42
42
  * Creates a promise that resolves to null after some time.
43
43
  */
44
44
  export declare function sleep(time: number): Promise<null>;
45
- export declare const checkExpiresInTime: ({ expires_in, expires_at, refreshTick, }: {
45
+ export declare const checkExpiresInTime: ({ expires_in, expires_at, refreshTick }: {
46
46
  expires_in: string;
47
47
  expires_at?: string;
48
48
  refreshTick: number;
@@ -1,4 +1,4 @@
1
- import { Session } from "./types";
1
+ import { Session } from './types';
2
2
  /**
3
3
  * Helper for Next.js to get the session from the server-side cookies.
4
4
  * This can be used in API routes or Server Components.
@@ -0,0 +1,15 @@
1
+ import type { SupportedStorage } from './types';
2
+ export declare const CODE_VERIFIER_TTL_MS: number;
3
+ type LoadedCodeVerifier = {
4
+ verifier: string;
5
+ redirectType?: string;
6
+ };
7
+ export declare const saveCodeVerifier: (storage: SupportedStorage, key: string, { verifier, redirectType, now }: {
8
+ verifier: string;
9
+ redirectType?: string;
10
+ now?: number;
11
+ }) => Promise<void>;
12
+ export declare const loadCodeVerifier: (storage: SupportedStorage, key: string, { now }?: {
13
+ now?: number;
14
+ }) => Promise<LoadedCodeVerifier | null>;
15
+ export {};
@@ -0,0 +1,7 @@
1
+ import type { Session } from './types';
2
+ /**
3
+ * Type guard for a stored session blob — checks the presence of the fields
4
+ * the client needs to consider the value usable. Does not validate the
5
+ * cryptographic signature of the access/refresh tokens.
6
+ */
7
+ export declare const isValidSession: (value: unknown) => value is Session;
@@ -1,14 +1,20 @@
1
- import { SupportedStorage } from "../types";
2
- interface CookieOptions {
3
- domain?: string;
4
- path?: string;
5
- sameSite?: "Lax" | "Strict" | "None";
6
- secure?: boolean;
7
- maxAge?: number;
1
+ import { SupportedStorage } from '../types';
2
+ import { CookieAttributes } from './cookie_helpers';
3
+ export type CookieOptions = CookieAttributes;
4
+ /**
5
+ * Document-like surface the cookie adapter needs. Accepting a minimal shape
6
+ * (instead of the full DOM `Document`) keeps the adapter testable without
7
+ * jsdom and works in any environment that exposes a `cookie` property.
8
+ */
9
+ export interface CookieJar {
10
+ cookie: string;
8
11
  }
9
12
  /**
10
- * A storage adapter that uses document.cookie to persist data.
11
- * This is useful for SSR as cookies are sent to the server on every request.
13
+ * A storage adapter that uses `document.cookie` to persist sessions. Useful
14
+ * in SSR setups where the server reads the cookie on every request.
15
+ *
16
+ * The optional `jar` parameter lets tests inject a fake document; production
17
+ * code passes nothing and the adapter uses the real `document` in browsers
18
+ * (and no-ops on the server).
12
19
  */
13
- export declare const cookieStorageAdapter: (options?: CookieOptions) => SupportedStorage;
14
- export {};
20
+ export declare const cookieStorageAdapter: (options?: CookieOptions, jar?: CookieJar | null) => SupportedStorage;
@@ -0,0 +1,25 @@
1
+ export interface CookieAttributes {
2
+ domain?: string;
3
+ path?: string;
4
+ sameSite?: 'Lax' | 'Strict' | 'None';
5
+ secure?: boolean;
6
+ maxAge?: number;
7
+ }
8
+ /**
9
+ * Parses a raw cookie string (the shape of `document.cookie`) into a Map of
10
+ * decoded name → value. Splitting happens BEFORE decoding so encoded `;` and
11
+ * `=` characters inside a value don't break the parser.
12
+ */
13
+ export declare const parseCookies: (cookieString: string) => Map<string, string>;
14
+ /**
15
+ * Builds a `name=value; Attr=...` string suitable for assignment to
16
+ * `document.cookie`. Encodes name and value per RFC 6265 and forces `Secure`
17
+ * when `SameSite=None` (browsers reject the cookie otherwise).
18
+ */
19
+ export declare const serializeCookie: (name: string, value: string, attrs: CookieAttributes) => string;
20
+ /**
21
+ * Builds the cookie string that clears `name`. Browsers will only remove a
22
+ * cookie when the deletion attributes (Domain, Path, SameSite, Secure) match
23
+ * those used at write time, so we mirror them here.
24
+ */
25
+ export declare const serializeCookieRemoval: (name: string, attrs: CookieAttributes) => string;
@@ -1,4 +1,4 @@
1
- import { SupportedStorage } from "../types";
1
+ import { SupportedStorage } from '../types';
2
2
  /**
3
3
  * Provides safe access to the globalThis.localStorage property.
4
4
  */
@@ -1,3 +1,3 @@
1
- import { SupportedStorage } from "./types";
1
+ import { SupportedStorage } from './types';
2
2
  export declare const setItemAsync: (storage: SupportedStorage, key: string, data: any) => Promise<void>;
3
3
  export declare const getItemAsync: (storage: SupportedStorage, key: string) => Promise<unknown>;