@deepkey/sdk 1.0.0 → 1.2.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/README.md CHANGED
@@ -180,6 +180,46 @@ await fetch('/api/things', {
180
180
  })
181
181
  ```
182
182
 
183
+ ## Provider connections (durable provider access)
184
+
185
+ Login discards provider tokens. When your product needs to act on a provider on the user's behalf (for example manage several X accounts), the signed-in user **connects** accounts. DeepKey keeps the provider refresh token; browsers only see metadata and your server receives short-lived provider access tokens.
186
+
187
+ ```ts
188
+ // Browser — the user is already signed in (any method: email, Google, X).
189
+ await deepkey.connections.connect({
190
+ provider: 'x',
191
+ options: { redirectTo: `${window.location.origin}/settings` }, // must be allow-listed
192
+ })
193
+ // Full-page redirect to the provider. DeepKey returns to redirectTo with
194
+ // ?connection_id=...&provider=x (or ?error=access_denied&error_code=...).
195
+
196
+ const { data, error } = await deepkey.connections.list()
197
+ // data.connections: [{ id, provider, provider_user_id, identity_data, scope, status, created_at, updated_at }]
198
+
199
+ await deepkey.connections.revoke(connectionId) // revokes at the provider and deletes the connection
200
+ ```
201
+
202
+ ```ts
203
+ // Server — secret key only.
204
+ const { data: token, error } = await deepkey.connections.issueAccessToken(connectionId)
205
+ // token: { access_token, token_type: 'bearer', expires_at, scope } — no refresh token, ever.
206
+ // error?.code === 'connection_reauthorization_required' → the user must connect again.
207
+
208
+ const { data } = await deepkey.connections.list({ userId })
209
+ await deepkey.connections.revoke(connectionId)
210
+ ```
211
+
212
+ **How a connection is tied to the user.** `connections.connect()` sends the current DeepKey session as the bearer, so DeepKey binds the new connection to the signed-in `user.id` (the id is kept on DeepKey with the single-use OAuth state and re-checked on the callback; nothing from the browser chooses the user). On your server, correlate with the same id you get from `getUser(jwt)`:
213
+
214
+ ```ts
215
+ const { data: { user } } = await deepkey.auth.getUser(jwt) // your authenticated user
216
+ const { data: { connections } } = await deepkey.connections.list({ userId: user!.id })
217
+ // Only trust a connection_id from your frontend if it is in this list
218
+ // (every item also carries user_id === user.id).
219
+ ```
220
+
221
+ Scopes are a project policy: the adapter's minimum plus the `connectScopes` configured for the provider on the DeepKey console. They are requested only by `connections.connect()`, never during login. Each connection's `scope` reports what the provider actually granted.
222
+
183
223
  ## Server client (validating tokens in your backend)
184
224
 
185
225
  ```ts
@@ -206,12 +246,52 @@ const { payload } = await jwtVerify(jwt, jwks) // payload.sub = user id
206
246
 
207
247
  Use `getUser()` when you need revocation-awareness (a signed-out session fails immediately); use JWKS verification when latency matters (tokens remain valid until expiry).
208
248
 
249
+ ## User API keys (long-lived credentials for your users)
250
+
251
+ Issue keys to your own users for CLIs, integrations, and automations that cannot hold a browser session. A key always belongs to **one user of your project**. These are not your project keys: a user API key looks like `bt_ak_…`, and every method below runs on your server with the **secret key**.
252
+
253
+ ```ts
254
+ import { createServerClient } from '@deepkey/sdk/server'
255
+
256
+ const deepkey = createServerClient(process.env.DEEPKEY_SECRET_KEY) // bt_secret_...
257
+
258
+ // Create — `key` is the plaintext. Show it to the user now: it is returned once and can never be read again.
259
+ const { data: { apiKey, key }, error } = await deepkey.apiKeys.create({ userId: user.id, name: 'CI deploy' })
260
+ // apiKey: { id, user_id, name, last4, last_used_at, revoked_at, created_at }
261
+
262
+ // List — metadata only (active and revoked), newest first. Never the plaintext.
263
+ const { data: { apiKeys } } = await deepkey.apiKeys.list({ userId: user.id })
264
+
265
+ // Revoke — immediate and idempotent. The key stays listed with `revoked_at` set.
266
+ await deepkey.apiKeys.revoke(apiKey.id)
267
+ ```
268
+
269
+ **Validating a key** is the API-key counterpart of `auth.getUser(jwt)` and returns the same `User`:
270
+
271
+ ```ts
272
+ // In your request handler:
273
+ const presented = request.headers.get('x-api-key')
274
+ const { data: { user, apiKey }, error } = await deepkey.apiKeys.verify(presented)
275
+ if (error || !user) return new Response('Unauthorized', { status: 401 })
276
+ // user.id is the same stable UUID you get from getUser(jwt); apiKey tells you which key was used.
277
+ ```
278
+
279
+ | `error.code` | Meaning |
280
+ |---|---|
281
+ | `invalid_user_api_key` (401) | Unknown, malformed, revoked, or issued by another project. Deliberately indistinguishable. |
282
+ | `user_banned` (401) | The key is intact but its owner is banned. |
283
+ | `secret_key_required` (403) | The client was created with a publishable key. |
284
+ | `user_not_found` (404) | `create` received a `userId` that is not a user of this project. |
285
+ | `api_key_not_found` (404) | `revoke` received an id that is not a key of this project. |
286
+
287
+ `verify` sends the key in the request body, never in a URL. A successful `verify` updates `last_used_at`; a rejected one does not. DeepKey stores only a SHA-256 hash and the last four characters, so a lost key cannot be recovered: revoke it and create a new one. Only trust an `apiKey.id` sent by your frontend if it appears in `apiKeys.list({ userId })` for your authenticated user.
288
+
209
289
  ## Types (full public surface)
210
290
 
211
291
  ```ts
212
292
  // '@deepkey/sdk'
213
293
  const DEEPKEY_API_URL: 'https://deepkey.dev/api'
214
- function createAuthClient(publishableKey: string, options?: AuthClientOptions): { auth: Auth }
294
+ function createAuthClient(publishableKey: string, options?: AuthClientOptions): { auth: Auth; connections: Connections }
215
295
 
216
296
  class Auth {
217
297
  signInWithOtp(credentials: SignInWithOtpCredentials): Promise<AuthResponse<{ user: null; session: null }>>
@@ -224,6 +304,26 @@ class Auth {
224
304
  signOut(): Promise<{ error: AuthError | null }>
225
305
  }
226
306
 
307
+ class Connections {
308
+ connect(credentials: ConnectProviderCredentials): Promise<AuthResponse<{ provider: string; url: string | null }>>
309
+ list(): Promise<AuthResponse<{ connections: ProviderConnection[] }>>
310
+ revoke(connectionId: string): Promise<{ error: AuthError | null }>
311
+ }
312
+
313
+ interface ConnectProviderCredentials { provider: string; options?: { redirectTo?: string; skipBrowserRedirect?: boolean } }
314
+ interface ProviderConnection {
315
+ id: string // stable reference for your own tables
316
+ user_id: string // the DeepKey user that granted it (same id as getUser(jwt).user.id)
317
+ provider: string
318
+ provider_user_id: string // immutable provider subject
319
+ identity_data: Record<string, unknown>
320
+ scope: string | null // what the provider granted
321
+ status: 'active' | 'reauthorization_required'
322
+ created_at: string
323
+ updated_at: string
324
+ }
325
+ interface ProviderAccessToken { access_token: string; token_type: 'bearer'; expires_at: number | null; scope: string | null }
326
+
227
327
  interface AuthClientOptions {
228
328
  persistSession?: boolean
229
329
  autoRefreshToken?: boolean
@@ -261,9 +361,30 @@ interface User {
261
361
  // '@deepkey/sdk/server'
262
362
  const DEEPKEY_API_URL: 'https://deepkey.dev/api'
263
363
  function createServerClient(secretKey: string, options?: ServerClientOptions): {
264
- auth: { getUser(jwt: string): Promise<{ data: { user: User | null }; error: AuthError | null }> }
364
+ auth: { getUser(jwt: string): Promise<AuthResponse<{ user: User | null }>> }
365
+ connections: {
366
+ list(params: { userId: string }): Promise<AuthResponse<{ connections: ProviderConnection[] }>>
367
+ issueAccessToken(connectionId: string): Promise<AuthResponse<ProviderAccessToken | null>>
368
+ revoke(connectionId: string): Promise<{ error: AuthError | null }>
369
+ }
370
+ apiKeys: {
371
+ create(params: { userId: string; name?: string }): Promise<AuthResponse<{ apiKey: UserApiKey | null; key: string | null }>>
372
+ list(params: { userId: string }): Promise<AuthResponse<{ apiKeys: UserApiKey[] }>>
373
+ revoke(apiKeyId: string): Promise<{ error: AuthError | null }>
374
+ verify(apiKey: string): Promise<AuthResponse<{ user: User | null; apiKey: UserApiKey | null }>>
375
+ }
265
376
  }
266
377
  interface ServerClientOptions { fetch?: typeof fetch }
378
+
379
+ interface UserApiKey {
380
+ id: string
381
+ user_id: string // same id as User.id
382
+ name: string | null
383
+ last4: string // last four characters of the plaintext, for display
384
+ last_used_at: string | null // last successful apiKeys.verify
385
+ revoked_at: string | null // set once revoked; a revoked key never verifies again
386
+ created_at: string
387
+ }
267
388
  ```
268
389
 
269
390
  ## HTTP endpoints used (for debugging)
@@ -279,12 +400,21 @@ All calls send the API key as the `apikey` header against `https://deepkey.dev/a
279
400
  | token refresh | `POST /token?grant_type=refresh_token` |
280
401
  | `getUser` | `GET /user` (with `Authorization: Bearer`) |
281
402
  | `signOut` | `POST /logout` |
403
+ | `connections.connect` | `POST /connections/authorize` (bearer) → navigate to `data.url`; provider returns to `redirectTo?connection_id=…` |
404
+ | `connections.list` | `GET /connections` (bearer) or `GET /connections?user_id=…` (secret key) |
405
+ | server `connections.issueAccessToken` | `POST /connections/:id/token` (secret key) |
406
+ | `connections.revoke` | `DELETE /connections/:id` |
407
+ | server `apiKeys.create` | `POST /api-keys` with `{ user_id, name? }` (secret key) |
408
+ | server `apiKeys.list` | `GET /api-keys?user_id=…` (secret key) |
409
+ | server `apiKeys.verify` | `POST /api-keys/verify` with `{ api_key }` (secret key) |
410
+ | server `apiKeys.revoke` | `DELETE /api-keys/:id` (secret key) |
282
411
 
283
412
  ## Gotchas & troubleshooting
284
413
 
285
414
  - **Login redirects to the wrong URL** → your `redirectTo`/`emailRedirectTo` is not on the project's redirect allow-list; the service silently fell back to the Site URL. Add the URL (with wildcards if needed) in the console.
286
415
  - **Session not restored after redirect** → the tokens arrive in the URL *fragment* (`#access_token=…`). Make sure the client is constructed on the page the user lands on, before anything rewrites `location`, and that `detectSessionInUrl` isn't disabled. The fragment is stripped from the address bar immediately after being read.
287
416
  - **Using the secret key in the browser (or the publishable key on the server)** → key types are not interchangeable: `bt_pub_…` for the browser client, `bt_secret_…` for `createServerClient`.
417
+ - **`apiKeys.*` returns `secret_key_required`** → user API keys are server-only. Call them from `createServerClient(bt_secret_…)`, never from the browser client.
288
418
  - **`getUser()` returns `Auth session missing!`** (`code: 'session_missing'`) → no active session and no `jwt` argument was passed.
289
419
  - **SSR** → the default storage falls back to in-memory when `localStorage` is unavailable, so importing the client on the server is safe, but sessions only persist in the browser. Do sign-in flows client-side; validate tokens server-side with `@deepkey/sdk/server`.
290
420
  - **Multiple tabs** → handled: sessions sync via storage events, and refresh-token rotation has a grace window on the service to absorb concurrent refreshes.
@@ -0,0 +1,61 @@
1
+ // src/types.ts
2
+ var AuthError = class extends Error {
3
+ constructor(message, status, code) {
4
+ super(message);
5
+ this.name = "AuthError";
6
+ this.status = status;
7
+ this.code = code;
8
+ }
9
+ };
10
+
11
+ // src/fetch.ts
12
+ var request = async (options) => {
13
+ const fetchImpl = options.fetchImpl ?? fetch;
14
+ try {
15
+ const headers = { apikey: options.apikey };
16
+ if (options.body !== void 0) headers["Content-Type"] = "application/json";
17
+ if (options.accessToken) headers.Authorization = `Bearer ${options.accessToken}`;
18
+ const response = await fetchImpl(`${options.baseUrl}${options.path}`, {
19
+ method: options.method,
20
+ headers,
21
+ body: options.body !== void 0 ? JSON.stringify(options.body) : void 0
22
+ });
23
+ if (response.status === 204) return { data: {}, error: null };
24
+ let payload;
25
+ try {
26
+ payload = await response.json();
27
+ } catch {
28
+ payload = null;
29
+ }
30
+ if (!response.ok) {
31
+ const errorBody = payload?.error;
32
+ return {
33
+ data: null,
34
+ error: new AuthError(
35
+ errorBody?.message ?? `Request failed with status ${response.status}`,
36
+ response.status,
37
+ errorBody?.code
38
+ )
39
+ };
40
+ }
41
+ return { data: payload, error: null };
42
+ } catch (err) {
43
+ const message = err instanceof Error ? err.message : "Network request failed";
44
+ return { data: null, error: new AuthError(message, 0, "network_error") };
45
+ }
46
+ };
47
+
48
+ // src/constants.ts
49
+ var DEEPKEY_API_URL = "https://deepkey.dev/api";
50
+ var resolveBaseUrl = (url) => {
51
+ const candidate = typeof url === "string" ? url.trim() : "";
52
+ return (candidate || DEEPKEY_API_URL).replace(/\/+$/, "");
53
+ };
54
+
55
+ export {
56
+ AuthError,
57
+ request,
58
+ DEEPKEY_API_URL,
59
+ resolveBaseUrl
60
+ };
61
+ //# sourceMappingURL=chunk-J4IK2N4V.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/types.ts","../src/fetch.ts","../src/constants.ts"],"sourcesContent":["export interface UserIdentity {\n identity_id: string\n id: string\n user_id: string\n provider: string\n identity_data: Record<string, unknown>\n last_sign_in_at: string | null\n created_at: string\n updated_at: string\n}\n\nexport interface User {\n id: string\n aud: string\n role: string\n email: string | null\n email_confirmed_at: string | null\n app_metadata: { provider?: string | null; providers?: string[]; [key: string]: unknown }\n user_metadata: {\n name?: string\n full_name?: string\n avatar_url?: string\n picture?: string\n email?: string\n [key: string]: unknown\n }\n identities?: UserIdentity[]\n created_at: string\n updated_at?: string\n last_sign_in_at?: string | null\n}\n\nexport interface Session {\n access_token: string\n refresh_token: string\n token_type: 'bearer'\n expires_in: number\n expires_at: number\n user: User\n}\n\nexport class AuthError extends Error {\n status?: number\n code?: string\n\n constructor(message: string, status?: number, code?: string) {\n super(message)\n this.name = 'AuthError'\n this.status = status\n this.code = code\n }\n}\n\nexport type AuthChangeEvent = 'INITIAL_SESSION' | 'SIGNED_IN' | 'SIGNED_OUT' | 'TOKEN_REFRESHED'\n\nexport interface Subscription {\n id: string\n unsubscribe: () => void\n}\n\nexport interface StorageAdapter {\n getItem(key: string): string | null\n setItem(key: string, value: string): void\n removeItem(key: string): void\n}\n\nexport interface AuthClientOptions {\n /**\n * @internal Replaces the hosted DeepKey API origin. Used only by DeepKey's own\n * console and test suites; not supported for consumers and stripped from the\n * published type declarations.\n */\n __internalBaseUrl?: string\n storageKey?: string\n autoRefreshToken?: boolean\n detectSessionInUrl?: boolean\n persistSession?: boolean\n storage?: StorageAdapter\n fetch?: typeof fetch\n}\n\nexport interface ServerClientOptions {\n /**\n * @internal Replaces the hosted DeepKey API origin. Used only by DeepKey's own\n * console and test suites; not supported for consumers and stripped from the\n * published type declarations.\n */\n __internalBaseUrl?: string\n fetch?: typeof fetch\n}\n\nexport interface SignInWithOtpCredentials {\n email: string\n options?: {\n emailRedirectTo?: string\n shouldCreateUser?: boolean\n data?: Record<string, unknown>\n }\n}\n\nexport interface VerifyOtpParams {\n email: string\n token: string\n type: 'email' | 'magiclink'\n}\n\nexport interface ConnectProviderCredentials {\n provider: string\n options?: {\n redirectTo?: string\n skipBrowserRedirect?: boolean\n }\n}\n\n// Metadata only: provider tokens never appear in browser responses.\nexport interface ProviderConnection {\n id: string\n // The DeepKey user that granted the connection: the same `user.id` your server gets from getUser(jwt).\n user_id: string\n provider: string\n provider_user_id: string\n identity_data: Record<string, unknown>\n scope: string | null\n status: 'active' | 'reauthorization_required'\n created_at: string\n updated_at: string\n}\n\n// Issued to consumer servers through the secret key; short-lived and refreshed by DeepKey.\nexport interface ProviderAccessToken {\n access_token: string\n token_type: 'bearer'\n expires_at: number | null\n scope: string | null\n}\n\n// A long-lived credential your application issues to one of its users. Not the project\n// key: the plaintext is returned once by `apiKeys.create` and is never readable again.\nexport interface UserApiKey {\n id: string\n // The DeepKey user the key acts for: the same `user.id` your server gets from getUser(jwt).\n user_id: string\n name: string | null\n // Last four characters of the plaintext, for display.\n last4: string\n // Last successful `apiKeys.verify`; null until the key is used.\n last_used_at: string | null\n // Set once the key is revoked; a revoked key never verifies again.\n revoked_at: string | null\n created_at: string\n}\n\nexport interface SignInWithOAuthCredentials {\n provider: string\n options?: {\n redirectTo?: string\n scopes?: string\n queryParams?: Record<string, string>\n skipBrowserRedirect?: boolean\n }\n}\n\nexport interface AuthResponse<T> {\n data: T\n error: AuthError | null\n}\n","import { AuthError } from './types'\n\nexport interface RequestOptions {\n method: 'GET' | 'POST' | 'PUT' | 'DELETE'\n path: string\n apikey: string\n baseUrl: string\n accessToken?: string\n body?: unknown\n fetchImpl?: typeof fetch\n}\n\nexport interface RequestResult<T> {\n data: T | null\n error: AuthError | null\n}\n\n// All service errors are shaped { error: { code, message } }.\nexport const request = async <T>(options: RequestOptions): Promise<RequestResult<T>> => {\n const fetchImpl = options.fetchImpl ?? fetch\n try {\n const headers: Record<string, string> = { apikey: options.apikey }\n if (options.body !== undefined) headers['Content-Type'] = 'application/json'\n if (options.accessToken) headers.Authorization = `Bearer ${options.accessToken}`\n\n const response = await fetchImpl(`${options.baseUrl}${options.path}`, {\n method: options.method,\n headers,\n body: options.body !== undefined ? JSON.stringify(options.body) : undefined,\n })\n\n if (response.status === 204) return { data: {} as T, error: null }\n\n let payload: unknown\n try {\n payload = await response.json()\n } catch {\n payload = null\n }\n\n if (!response.ok) {\n const errorBody = (payload as { error?: { code?: string; message?: string } } | null)?.error\n return {\n data: null,\n error: new AuthError(\n errorBody?.message ?? `Request failed with status ${response.status}`,\n response.status,\n errorBody?.code,\n ),\n }\n }\n\n return { data: payload as T, error: null }\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Network request failed'\n return { data: null, error: new AuthError(message, 0, 'network_error') }\n }\n}\n","// The hosted DeepKey API. Every client talks to this origin; only DeepKey's own\n// console and test suites replace it through the internal override.\nexport const DEEPKEY_API_URL = 'https://deepkey.dev/api'\n\n// Resolves the base URL used for every request. An absent or blank internal\n// override keeps the hosted API; a provided value has any trailing slash removed\n// so path joins stay predictable.\nexport const resolveBaseUrl = (url?: string | null): string => {\n const candidate = typeof url === 'string' ? url.trim() : ''\n return (candidate || DEEPKEY_API_URL).replace(/\\/+$/, '')\n}\n"],"mappings":";AAyCO,IAAM,YAAN,cAAwB,MAAM;AAAA,EAInC,YAAY,SAAiB,QAAiB,MAAe;AAC3D,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,SAAS;AACd,SAAK,OAAO;AAAA,EACd;AACF;;;ACjCO,IAAM,UAAU,OAAU,YAAuD;AACtF,QAAM,YAAY,QAAQ,aAAa;AACvC,MAAI;AACF,UAAM,UAAkC,EAAE,QAAQ,QAAQ,OAAO;AACjE,QAAI,QAAQ,SAAS,OAAW,SAAQ,cAAc,IAAI;AAC1D,QAAI,QAAQ,YAAa,SAAQ,gBAAgB,UAAU,QAAQ,WAAW;AAE9E,UAAM,WAAW,MAAM,UAAU,GAAG,QAAQ,OAAO,GAAG,QAAQ,IAAI,IAAI;AAAA,MACpE,QAAQ,QAAQ;AAAA,MAChB;AAAA,MACA,MAAM,QAAQ,SAAS,SAAY,KAAK,UAAU,QAAQ,IAAI,IAAI;AAAA,IACpE,CAAC;AAED,QAAI,SAAS,WAAW,IAAK,QAAO,EAAE,MAAM,CAAC,GAAQ,OAAO,KAAK;AAEjE,QAAI;AACJ,QAAI;AACF,gBAAU,MAAM,SAAS,KAAK;AAAA,IAChC,QAAQ;AACN,gBAAU;AAAA,IACZ;AAEA,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,YAAa,SAAoE;AACvF,aAAO;AAAA,QACL,MAAM;AAAA,QACN,OAAO,IAAI;AAAA,UACT,WAAW,WAAW,8BAA8B,SAAS,MAAM;AAAA,UACnE,SAAS;AAAA,UACT,WAAW;AAAA,QACb;AAAA,MACF;AAAA,IACF;AAEA,WAAO,EAAE,MAAM,SAAc,OAAO,KAAK;AAAA,EAC3C,SAAS,KAAK;AACZ,UAAM,UAAU,eAAe,QAAQ,IAAI,UAAU;AACrD,WAAO,EAAE,MAAM,MAAM,OAAO,IAAI,UAAU,SAAS,GAAG,eAAe,EAAE;AAAA,EACzE;AACF;;;ACvDO,IAAM,kBAAkB;AAKxB,IAAM,iBAAiB,CAAC,QAAgC;AAC7D,QAAM,YAAY,OAAO,QAAQ,WAAW,IAAI,KAAK,IAAI;AACzD,UAAQ,aAAa,iBAAiB,QAAQ,QAAQ,EAAE;AAC1D;","names":[]}
@@ -79,6 +79,39 @@ interface VerifyOtpParams {
79
79
  token: string;
80
80
  type: 'email' | 'magiclink';
81
81
  }
82
+ interface ConnectProviderCredentials {
83
+ provider: string;
84
+ options?: {
85
+ redirectTo?: string;
86
+ skipBrowserRedirect?: boolean;
87
+ };
88
+ }
89
+ interface ProviderConnection {
90
+ id: string;
91
+ user_id: string;
92
+ provider: string;
93
+ provider_user_id: string;
94
+ identity_data: Record<string, unknown>;
95
+ scope: string | null;
96
+ status: 'active' | 'reauthorization_required';
97
+ created_at: string;
98
+ updated_at: string;
99
+ }
100
+ interface ProviderAccessToken {
101
+ access_token: string;
102
+ token_type: 'bearer';
103
+ expires_at: number | null;
104
+ scope: string | null;
105
+ }
106
+ interface UserApiKey {
107
+ id: string;
108
+ user_id: string;
109
+ name: string | null;
110
+ last4: string;
111
+ last_used_at: string | null;
112
+ revoked_at: string | null;
113
+ created_at: string;
114
+ }
82
115
  interface SignInWithOAuthCredentials {
83
116
  provider: string;
84
117
  options?: {
@@ -95,4 +128,4 @@ interface AuthResponse<T> {
95
128
 
96
129
  declare const DEEPKEY_API_URL = "https://deepkey.dev/api";
97
130
 
98
- export { type AuthClientOptions as A, DEEPKEY_API_URL as D, type SignInWithOtpCredentials as S, type User as U, type VerifyOtpParams as V, type AuthResponse as a, type Session as b, type SignInWithOAuthCredentials as c, type AuthChangeEvent as d, type Subscription as e, AuthError as f, type ServerClientOptions as g, type StorageAdapter as h, type UserIdentity as i };
131
+ export { type AuthResponse as A, type ConnectProviderCredentials as C, DEEPKEY_API_URL as D, type ProviderConnection as P, type SignInWithOtpCredentials as S, type User as U, type VerifyOtpParams as V, AuthError as a, type AuthClientOptions as b, type Session as c, type SignInWithOAuthCredentials as d, type AuthChangeEvent as e, type Subscription as f, type ProviderAccessToken as g, type ServerClientOptions as h, type StorageAdapter as i, type UserApiKey as j, type UserIdentity as k };
@@ -79,6 +79,39 @@ interface VerifyOtpParams {
79
79
  token: string;
80
80
  type: 'email' | 'magiclink';
81
81
  }
82
+ interface ConnectProviderCredentials {
83
+ provider: string;
84
+ options?: {
85
+ redirectTo?: string;
86
+ skipBrowserRedirect?: boolean;
87
+ };
88
+ }
89
+ interface ProviderConnection {
90
+ id: string;
91
+ user_id: string;
92
+ provider: string;
93
+ provider_user_id: string;
94
+ identity_data: Record<string, unknown>;
95
+ scope: string | null;
96
+ status: 'active' | 'reauthorization_required';
97
+ created_at: string;
98
+ updated_at: string;
99
+ }
100
+ interface ProviderAccessToken {
101
+ access_token: string;
102
+ token_type: 'bearer';
103
+ expires_at: number | null;
104
+ scope: string | null;
105
+ }
106
+ interface UserApiKey {
107
+ id: string;
108
+ user_id: string;
109
+ name: string | null;
110
+ last4: string;
111
+ last_used_at: string | null;
112
+ revoked_at: string | null;
113
+ created_at: string;
114
+ }
82
115
  interface SignInWithOAuthCredentials {
83
116
  provider: string;
84
117
  options?: {
@@ -95,4 +128,4 @@ interface AuthResponse<T> {
95
128
 
96
129
  declare const DEEPKEY_API_URL = "https://deepkey.dev/api";
97
130
 
98
- export { type AuthClientOptions as A, DEEPKEY_API_URL as D, type SignInWithOtpCredentials as S, type User as U, type VerifyOtpParams as V, type AuthResponse as a, type Session as b, type SignInWithOAuthCredentials as c, type AuthChangeEvent as d, type Subscription as e, AuthError as f, type ServerClientOptions as g, type StorageAdapter as h, type UserIdentity as i };
131
+ export { type AuthResponse as A, type ConnectProviderCredentials as C, DEEPKEY_API_URL as D, type ProviderConnection as P, type SignInWithOtpCredentials as S, type User as U, type VerifyOtpParams as V, AuthError as a, type AuthClientOptions as b, type Session as c, type SignInWithOAuthCredentials as d, type AuthChangeEvent as e, type Subscription as f, type ProviderAccessToken as g, type ServerClientOptions as h, type StorageAdapter as i, type UserApiKey as j, type UserIdentity as k };
package/dist/index.cjs CHANGED
@@ -22,18 +22,12 @@ var index_exports = {};
22
22
  __export(index_exports, {
23
23
  Auth: () => Auth,
24
24
  AuthError: () => AuthError,
25
+ Connections: () => Connections,
25
26
  DEEPKEY_API_URL: () => DEEPKEY_API_URL,
26
27
  createAuthClient: () => createAuthClient
27
28
  });
28
29
  module.exports = __toCommonJS(index_exports);
29
30
 
30
- // src/constants.ts
31
- var DEEPKEY_API_URL = "https://deepkey.dev/api";
32
- var resolveBaseUrl = (url) => {
33
- const candidate = typeof url === "string" ? url.trim() : "";
34
- return (candidate || DEEPKEY_API_URL).replace(/\/+$/, "");
35
- };
36
-
37
31
  // src/types.ts
38
32
  var AuthError = class extends Error {
39
33
  constructor(message, status, code) {
@@ -81,6 +75,50 @@ var request = async (options) => {
81
75
  }
82
76
  };
83
77
 
78
+ // src/connections.ts
79
+ var Connections = class {
80
+ constructor(auth, options) {
81
+ this.auth = auth;
82
+ this.options = options;
83
+ }
84
+ async connect(credentials) {
85
+ const { data, error } = await this.request(
86
+ "POST",
87
+ "/auth/v1/connections/authorize",
88
+ { provider: credentials.provider, redirect_to: credentials.options?.redirectTo }
89
+ );
90
+ if (error || !data) return { data: { provider: credentials.provider, url: null }, error };
91
+ if (!credentials.options?.skipBrowserRedirect && typeof window !== "undefined") {
92
+ window.location.assign(data.url);
93
+ }
94
+ return { data: { provider: credentials.provider, url: data.url }, error: null };
95
+ }
96
+ async list() {
97
+ const { data, error } = await this.request("GET", "/auth/v1/connections");
98
+ return { data: { connections: data?.connections ?? [] }, error };
99
+ }
100
+ async revoke(connectionId) {
101
+ const { error } = await this.request("DELETE", `/auth/v1/connections/${encodeURIComponent(connectionId)}`);
102
+ return { error };
103
+ }
104
+ async request(method, path, body) {
105
+ const {
106
+ data: { session }
107
+ } = await this.auth.getSession();
108
+ if (!session) {
109
+ return { data: null, error: new AuthError("Auth session missing!", 400, "session_missing") };
110
+ }
111
+ return request({ method, path, body, accessToken: session.access_token, ...this.options });
112
+ }
113
+ };
114
+
115
+ // src/constants.ts
116
+ var DEEPKEY_API_URL = "https://deepkey.dev/api";
117
+ var resolveBaseUrl = (url) => {
118
+ const candidate = typeof url === "string" ? url.trim() : "";
119
+ return (candidate || DEEPKEY_API_URL).replace(/\/+$/, "");
120
+ };
121
+
84
122
  // src/storage.ts
85
123
  var MemoryStorage = class {
86
124
  constructor() {
@@ -425,6 +463,12 @@ var Auth = class {
425
463
  }
426
464
  };
427
465
  var createAuthClient = (publishableKey, options) => {
428
- return { auth: new Auth(publishableKey, options) };
466
+ const auth = new Auth(publishableKey, options);
467
+ const connections = new Connections(auth, {
468
+ apikey: publishableKey ?? "",
469
+ baseUrl: resolveBaseUrl(options?.__internalBaseUrl),
470
+ fetchImpl: options?.fetch
471
+ });
472
+ return { auth, connections };
429
473
  };
430
474
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/constants.ts","../src/types.ts","../src/fetch.ts","../src/storage.ts","../src/url.ts","../src/client.ts"],"sourcesContent":["export { createAuthClient, Auth, type AuthClient } from './client'\nexport { DEEPKEY_API_URL } from './constants'\nexport {\n AuthError,\n type AuthChangeEvent,\n type AuthClientOptions,\n type AuthResponse,\n type ServerClientOptions,\n type Session,\n type SignInWithOAuthCredentials,\n type SignInWithOtpCredentials,\n type StorageAdapter,\n type Subscription,\n type User,\n type UserIdentity,\n type VerifyOtpParams,\n} from './types'\n","// The hosted DeepKey API. Every client talks to this origin; only DeepKey's own\n// console and test suites replace it through the internal override.\nexport const DEEPKEY_API_URL = 'https://deepkey.dev/api'\n\n// Resolves the base URL used for every request. An absent or blank internal\n// override keeps the hosted API; a provided value has any trailing slash removed\n// so path joins stay predictable.\nexport const resolveBaseUrl = (url?: string | null): string => {\n const candidate = typeof url === 'string' ? url.trim() : ''\n return (candidate || DEEPKEY_API_URL).replace(/\\/+$/, '')\n}\n","export interface UserIdentity {\n identity_id: string\n id: string\n user_id: string\n provider: string\n identity_data: Record<string, unknown>\n last_sign_in_at: string | null\n created_at: string\n updated_at: string\n}\n\nexport interface User {\n id: string\n aud: string\n role: string\n email: string | null\n email_confirmed_at: string | null\n app_metadata: { provider?: string | null; providers?: string[]; [key: string]: unknown }\n user_metadata: {\n name?: string\n full_name?: string\n avatar_url?: string\n picture?: string\n email?: string\n [key: string]: unknown\n }\n identities?: UserIdentity[]\n created_at: string\n updated_at?: string\n last_sign_in_at?: string | null\n}\n\nexport interface Session {\n access_token: string\n refresh_token: string\n token_type: 'bearer'\n expires_in: number\n expires_at: number\n user: User\n}\n\nexport class AuthError extends Error {\n status?: number\n code?: string\n\n constructor(message: string, status?: number, code?: string) {\n super(message)\n this.name = 'AuthError'\n this.status = status\n this.code = code\n }\n}\n\nexport type AuthChangeEvent = 'INITIAL_SESSION' | 'SIGNED_IN' | 'SIGNED_OUT' | 'TOKEN_REFRESHED'\n\nexport interface Subscription {\n id: string\n unsubscribe: () => void\n}\n\nexport interface StorageAdapter {\n getItem(key: string): string | null\n setItem(key: string, value: string): void\n removeItem(key: string): void\n}\n\nexport interface AuthClientOptions {\n /**\n * @internal Replaces the hosted DeepKey API origin. Used only by DeepKey's own\n * console and test suites; not supported for consumers and stripped from the\n * published type declarations.\n */\n __internalBaseUrl?: string\n storageKey?: string\n autoRefreshToken?: boolean\n detectSessionInUrl?: boolean\n persistSession?: boolean\n storage?: StorageAdapter\n fetch?: typeof fetch\n}\n\nexport interface ServerClientOptions {\n /**\n * @internal Replaces the hosted DeepKey API origin. Used only by DeepKey's own\n * console and test suites; not supported for consumers and stripped from the\n * published type declarations.\n */\n __internalBaseUrl?: string\n fetch?: typeof fetch\n}\n\nexport interface SignInWithOtpCredentials {\n email: string\n options?: {\n emailRedirectTo?: string\n shouldCreateUser?: boolean\n data?: Record<string, unknown>\n }\n}\n\nexport interface VerifyOtpParams {\n email: string\n token: string\n type: 'email' | 'magiclink'\n}\n\nexport interface SignInWithOAuthCredentials {\n provider: string\n options?: {\n redirectTo?: string\n scopes?: string\n queryParams?: Record<string, string>\n skipBrowserRedirect?: boolean\n }\n}\n\nexport interface AuthResponse<T> {\n data: T\n error: AuthError | null\n}\n","import { AuthError } from './types'\n\nexport interface RequestOptions {\n method: 'GET' | 'POST' | 'PUT' | 'DELETE'\n path: string\n apikey: string\n baseUrl: string\n accessToken?: string\n body?: unknown\n fetchImpl?: typeof fetch\n}\n\nexport interface RequestResult<T> {\n data: T | null\n error: AuthError | null\n}\n\n// All service errors are shaped { error: { code, message } }.\nexport const request = async <T>(options: RequestOptions): Promise<RequestResult<T>> => {\n const fetchImpl = options.fetchImpl ?? fetch\n try {\n const headers: Record<string, string> = { apikey: options.apikey }\n if (options.body !== undefined) headers['Content-Type'] = 'application/json'\n if (options.accessToken) headers.Authorization = `Bearer ${options.accessToken}`\n\n const response = await fetchImpl(`${options.baseUrl}${options.path}`, {\n method: options.method,\n headers,\n body: options.body !== undefined ? JSON.stringify(options.body) : undefined,\n })\n\n if (response.status === 204) return { data: {} as T, error: null }\n\n let payload: unknown\n try {\n payload = await response.json()\n } catch {\n payload = null\n }\n\n if (!response.ok) {\n const errorBody = (payload as { error?: { code?: string; message?: string } } | null)?.error\n return {\n data: null,\n error: new AuthError(\n errorBody?.message ?? `Request failed with status ${response.status}`,\n response.status,\n errorBody?.code,\n ),\n }\n }\n\n return { data: payload as T, error: null }\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Network request failed'\n return { data: null, error: new AuthError(message, 0, 'network_error') }\n }\n}\n","import type { StorageAdapter } from './types'\n\nclass MemoryStorage implements StorageAdapter {\n private store = new Map<string, string>()\n getItem(key: string) {\n return this.store.get(key) ?? null\n }\n setItem(key: string, value: string) {\n this.store.set(key, value)\n }\n removeItem(key: string) {\n this.store.delete(key)\n }\n}\n\n// localStorage when available, in-memory fallback (SSR, tests, restricted contexts).\nexport const defaultStorage = (): StorageAdapter => {\n try {\n if (typeof window !== 'undefined' && window.localStorage) {\n const probe = '__bt_auth_probe__'\n window.localStorage.setItem(probe, '1')\n window.localStorage.removeItem(probe)\n return window.localStorage\n }\n } catch {\n // fall through to memory storage\n }\n return new MemoryStorage()\n}\n\n// Publishable keys look like `bt_pub_<project-slug>_<random>`; the slug namespaces the\n// persisted session per project (mirrors supabase's `sb-<ref>-auth-token`).\nexport const deriveStorageKey = (publishableKey: string | undefined): string => {\n const match = publishableKey?.match(/^bt_pub_([a-z0-9-]+)_[A-Za-z0-9_-]+$/)\n return match ? `bt-${match[1]}-auth-token` : 'bt-auth-token'\n}\n","export interface FragmentTokens {\n access_token: string\n refresh_token: string\n expires_in: number\n expires_at: number\n token_type: 'bearer'\n type?: string\n}\n\nexport interface FragmentError {\n error: string\n error_code?: string\n error_description?: string\n}\n\nexport type ParsedFragment = { tokens: FragmentTokens } | { authError: FragmentError } | null\n\n// Parses the implicit-flow return fragment (`#access_token=...` or `#error=...`).\nexport const parseFragment = (hash: string): ParsedFragment => {\n if (!hash || hash.length < 2) return null\n const params = new URLSearchParams(hash.startsWith('#') ? hash.slice(1) : hash)\n\n const error = params.get('error')\n if (error) {\n return {\n authError: {\n error,\n error_code: params.get('error_code') ?? undefined,\n error_description: params.get('error_description') ?? undefined,\n },\n }\n }\n\n const accessToken = params.get('access_token')\n const refreshToken = params.get('refresh_token')\n if (!accessToken || !refreshToken) return null\n\n const expiresIn = Number(params.get('expires_in') ?? 3600)\n const expiresAt = Number(params.get('expires_at') ?? Math.floor(Date.now() / 1000) + expiresIn)\n\n return {\n tokens: {\n access_token: accessToken,\n refresh_token: refreshToken,\n expires_in: expiresIn,\n expires_at: expiresAt,\n token_type: 'bearer',\n type: params.get('type') ?? undefined,\n },\n }\n}\n\n// Removes the auth fragment from the address bar (before any await, so tokens never\n// linger in history).\nexport const cleanFragmentFromUrl = () => {\n if (typeof window === 'undefined' || typeof history === 'undefined') return\n history.replaceState(null, '', window.location.pathname + window.location.search)\n}\n","import { resolveBaseUrl } from './constants'\nimport { request } from './fetch'\nimport { defaultStorage, deriveStorageKey } from './storage'\nimport {\n AuthError,\n type AuthChangeEvent,\n type AuthClientOptions,\n type AuthResponse,\n type Session,\n type SignInWithOAuthCredentials,\n type SignInWithOtpCredentials,\n type StorageAdapter,\n type Subscription,\n type User,\n type VerifyOtpParams,\n} from './types'\nimport { cleanFragmentFromUrl, parseFragment } from './url'\n\nconst REFRESH_TICK_MS = 30_000\n// Refresh when the access token is within this margin of expiry.\nconst EXPIRY_MARGIN_SECONDS = 90\n\ntype Listener = (event: AuthChangeEvent, session: Session | null) => void\n\nexport class Auth {\n private baseUrl: string\n private apikey: string\n private storage: StorageAdapter\n private storageKey: string\n private persistSession: boolean\n private autoRefreshToken: boolean\n private detectSessionInUrl: boolean\n private fetchImpl?: typeof fetch\n\n private session: Session | null = null\n private listeners = new Map<string, Listener>()\n private listenerSeq = 0\n private initializePromise: Promise<void> | null = null\n private refreshPromise: Promise<AuthResponse<{ session: Session | null; user: User | null }>> | null = null\n private refreshTimer: ReturnType<typeof setInterval> | null = null\n\n constructor(publishableKey: string, options: AuthClientOptions = {}) {\n // Lazy by design: an empty key must not throw at construction time (CI builds\n // with empty envs import this module transitively). Failures surface on first call.\n // The hosted DeepKey API is the only supported origin. The internal override\n // exists for DeepKey's console and tests and is not part of the public contract.\n this.baseUrl = resolveBaseUrl(options.__internalBaseUrl)\n this.apikey = publishableKey ?? ''\n this.persistSession = options.persistSession ?? true\n this.autoRefreshToken = options.autoRefreshToken ?? true\n this.detectSessionInUrl = options.detectSessionInUrl ?? true\n this.storage = options.storage ?? defaultStorage()\n this.storageKey = options.storageKey ?? deriveStorageKey(publishableKey)\n this.fetchImpl = options.fetch\n\n this.setupCrossTabSync()\n }\n\n // ---- public API (supabase-js compatible) ----\n\n async signInWithOtp(credentials: SignInWithOtpCredentials): Promise<AuthResponse<{ user: null; session: null }>> {\n const { error } = await this.request<Record<string, never>>('POST', '/auth/v1/otp', {\n email: credentials.email,\n create_user: credentials.options?.shouldCreateUser,\n redirect_to: credentials.options?.emailRedirectTo,\n })\n return { data: { user: null, session: null }, error }\n }\n\n async verifyOtp(params: VerifyOtpParams): Promise<AuthResponse<{ user: User | null; session: Session | null }>> {\n const { data, error } = await this.request<Session>('POST', '/auth/v1/verify', {\n type: 'email',\n email: params.email,\n token: params.token,\n })\n if (error || !data) return { data: { user: null, session: null }, error }\n\n await this.saveSession(data)\n this.emit('SIGNED_IN', data)\n return { data: { user: data.user, session: data }, error: null }\n }\n\n async signInWithOAuth(\n credentials: SignInWithOAuthCredentials,\n ): Promise<AuthResponse<{ provider: string; url: string }>> {\n const url = new URL(`${this.baseUrl}/auth/v1/authorize`)\n url.searchParams.set('provider', credentials.provider)\n url.searchParams.set('apikey', this.apikey)\n if (credentials.options?.redirectTo) url.searchParams.set('redirect_to', credentials.options.redirectTo)\n if (credentials.options?.scopes) url.searchParams.set('scopes', credentials.options.scopes)\n for (const [key, value] of Object.entries(credentials.options?.queryParams ?? {})) {\n url.searchParams.set(key, value)\n }\n\n if (!credentials.options?.skipBrowserRedirect && typeof window !== 'undefined') {\n window.location.assign(url.toString())\n }\n return { data: { provider: credentials.provider, url: url.toString() }, error: null }\n }\n\n onAuthStateChange(callback: Listener): { data: { subscription: Subscription } } {\n const id = `listener-${++this.listenerSeq}`\n this.listeners.set(id, callback)\n\n // Every subscriber receives INITIAL_SESSION asynchronously once initialization\n // settles — the supabase-js contract that AuthProviders rely on to resolve loading.\n this.initialize().then(() => {\n if (this.listeners.has(id)) callback('INITIAL_SESSION', this.session)\n })\n\n return {\n data: {\n subscription: {\n id,\n unsubscribe: () => {\n this.listeners.delete(id)\n },\n },\n },\n }\n }\n\n async signOut(): Promise<{ error: AuthError | null }> {\n await this.initialize()\n const session = this.session\n if (session) {\n // Best-effort server-side revocation; local sign-out always succeeds.\n await this.request('POST', '/auth/v1/logout', {}, session.access_token)\n }\n this.clearSession()\n this.emit('SIGNED_OUT', null)\n return { error: null }\n }\n\n async getSession(): Promise<AuthResponse<{ session: Session | null }>> {\n await this.initialize()\n\n if (this.session && this.isExpired(this.session) && this.autoRefreshToken) {\n const { error } = await this.refreshWithLock()\n if (error && error.code !== 'network_error') {\n return { data: { session: null }, error: null }\n }\n }\n\n return { data: { session: this.session }, error: null }\n }\n\n async getUser(jwt?: string): Promise<AuthResponse<{ user: User | null }>> {\n let token = jwt\n if (!token) {\n const {\n data: { session },\n } = await this.getSession()\n token = session?.access_token\n }\n if (!token) {\n return { data: { user: null }, error: new AuthError('Auth session missing!', 400, 'session_missing') }\n }\n\n const { data, error } = await this.request<User>('GET', '/auth/v1/user', undefined, token)\n return { data: { user: data }, error }\n }\n\n async refreshSession(currentSession?: {\n refresh_token: string\n }): Promise<AuthResponse<{ session: Session | null; user: User | null }>> {\n await this.initialize()\n const refreshToken = currentSession?.refresh_token ?? this.session?.refresh_token\n if (!refreshToken) {\n return {\n data: { session: null, user: null },\n error: new AuthError('Auth session missing!', 400, 'session_missing'),\n }\n }\n return this.refreshWithLock(refreshToken)\n }\n\n // ---- internals ----\n\n private async request<T>(method: 'GET' | 'POST', path: string, body?: unknown, accessToken?: string) {\n return request<T>({\n method,\n path,\n body,\n accessToken,\n apikey: this.apikey,\n baseUrl: this.baseUrl,\n fetchImpl: this.fetchImpl,\n })\n }\n\n private initialize(): Promise<void> {\n this.initializePromise ??= this.doInitialize()\n return this.initializePromise\n }\n\n private async doInitialize(): Promise<void> {\n if (this.detectSessionInUrl && typeof window !== 'undefined') {\n const parsed = parseFragment(window.location.hash)\n if (parsed) {\n // Strip tokens from the address bar synchronously, before any await.\n cleanFragmentFromUrl()\n\n if ('authError' in parsed) {\n console.warn(\n `[deepkey] Auth redirect returned an error: ${parsed.authError.error_code ?? parsed.authError.error}`,\n parsed.authError.error_description ?? '',\n )\n } else {\n const { data: user } = await this.request<User>('GET', '/auth/v1/user', undefined, parsed.tokens.access_token)\n if (user) {\n const session: Session = { ...parsed.tokens, token_type: 'bearer', user }\n await this.saveSession(session)\n this.emit('SIGNED_IN', session)\n return\n }\n }\n }\n }\n\n const stored = this.loadStoredSession()\n if (!stored) return\n\n if (this.isExpired(stored)) {\n if (!this.autoRefreshToken) return\n this.session = stored\n const { error } = await this.refreshWithLock()\n if (error && error.code !== 'network_error') this.clearSession()\n return\n }\n\n this.session = stored\n this.startRefreshTimer()\n }\n\n private isExpired(session: Session): boolean {\n return session.expires_at <= Math.floor(Date.now() / 1000) + EXPIRY_MARGIN_SECONDS\n }\n\n private async refreshWithLock(\n refreshToken?: string,\n ): Promise<AuthResponse<{ session: Session | null; user: User | null }>> {\n this.refreshPromise ??= this.doRefresh(refreshToken).finally(() => {\n this.refreshPromise = null\n })\n return this.refreshPromise\n }\n\n private async doRefresh(\n refreshToken?: string,\n ): Promise<AuthResponse<{ session: Session | null; user: User | null }>> {\n const token = refreshToken ?? this.session?.refresh_token\n if (!token) {\n return {\n data: { session: null, user: null },\n error: new AuthError('Auth session missing!', 400, 'session_missing'),\n }\n }\n\n const { data, error } = await this.request<Session>('POST', '/auth/v1/token?grant_type=refresh_token', {\n refresh_token: token,\n })\n\n if (error) {\n // Network failures keep the session (retry next tick); auth failures clear it.\n if (error.code !== 'network_error') {\n this.clearSession()\n this.emit('SIGNED_OUT', null)\n }\n return { data: { session: null, user: null }, error }\n }\n\n if (data) {\n await this.saveSession(data)\n this.emit('TOKEN_REFRESHED', data)\n return { data: { session: data, user: data.user }, error: null }\n }\n return { data: { session: null, user: null }, error: new AuthError('Empty refresh response') }\n }\n\n private async saveSession(session: Session) {\n this.session = session\n if (this.persistSession) {\n try {\n this.storage.setItem(this.storageKey, JSON.stringify(session))\n } catch {\n // Persistence is best-effort (private mode, quota).\n }\n }\n this.startRefreshTimer()\n }\n\n private clearSession() {\n this.session = null\n try {\n this.storage.removeItem(this.storageKey)\n } catch {\n // ignore\n }\n this.stopRefreshTimer()\n }\n\n private loadStoredSession(): Session | null {\n if (!this.persistSession) return null\n try {\n const raw = this.storage.getItem(this.storageKey)\n if (!raw) return null\n const parsed = JSON.parse(raw) as Session\n if (!parsed.access_token || !parsed.refresh_token) return null\n return parsed\n } catch {\n return null\n }\n }\n\n private startRefreshTimer() {\n if (!this.autoRefreshToken || this.refreshTimer || typeof setInterval === 'undefined') return\n this.refreshTimer = setInterval(() => {\n if (this.session && this.isExpired(this.session)) void this.refreshWithLock()\n }, REFRESH_TICK_MS)\n // Do not keep node processes alive because of the timer (no-op in browsers).\n const timer = this.refreshTimer as unknown as { unref?: () => void }\n if (typeof timer === 'object' && typeof timer.unref === 'function') timer.unref()\n }\n\n private stopRefreshTimer() {\n if (this.refreshTimer) {\n clearInterval(this.refreshTimer)\n this.refreshTimer = null\n }\n }\n\n private setupCrossTabSync() {\n if (typeof window === 'undefined' || !this.persistSession) return\n window.addEventListener('storage', event => {\n if (event.key !== this.storageKey) return\n if (!event.newValue) {\n if (this.session) {\n this.session = null\n this.stopRefreshTimer()\n this.emit('SIGNED_OUT', null)\n }\n return\n }\n try {\n const next = JSON.parse(event.newValue) as Session\n const changed = next.access_token !== this.session?.access_token\n this.session = next\n this.startRefreshTimer()\n if (changed) this.emit('TOKEN_REFRESHED', next)\n } catch {\n // ignore malformed writes\n }\n })\n }\n\n private emit(event: AuthChangeEvent, session: Session | null) {\n for (const listener of this.listeners.values()) {\n try {\n listener(event, session)\n } catch (err) {\n console.error('[deepkey] onAuthStateChange listener threw', err)\n }\n }\n }\n}\n\nexport interface AuthClient {\n auth: Auth\n}\n\nexport const createAuthClient = (publishableKey: string, options?: AuthClientOptions): AuthClient => {\n return { auth: new Auth(publishableKey, options) }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/types.ts","../src/fetch.ts","../src/connections.ts","../src/constants.ts","../src/storage.ts","../src/url.ts","../src/client.ts"],"sourcesContent":["export { createAuthClient, Auth, type AuthClient } from './client'\nexport { Connections } from './connections'\nexport { DEEPKEY_API_URL } from './constants'\nexport {\n AuthError,\n type AuthChangeEvent,\n type AuthClientOptions,\n type AuthResponse,\n type ConnectProviderCredentials,\n type ServerClientOptions,\n type ProviderAccessToken,\n type ProviderConnection,\n type Session,\n type SignInWithOAuthCredentials,\n type SignInWithOtpCredentials,\n type StorageAdapter,\n type Subscription,\n type User,\n type UserApiKey,\n type UserIdentity,\n type VerifyOtpParams,\n} from './types'\n","export interface UserIdentity {\n identity_id: string\n id: string\n user_id: string\n provider: string\n identity_data: Record<string, unknown>\n last_sign_in_at: string | null\n created_at: string\n updated_at: string\n}\n\nexport interface User {\n id: string\n aud: string\n role: string\n email: string | null\n email_confirmed_at: string | null\n app_metadata: { provider?: string | null; providers?: string[]; [key: string]: unknown }\n user_metadata: {\n name?: string\n full_name?: string\n avatar_url?: string\n picture?: string\n email?: string\n [key: string]: unknown\n }\n identities?: UserIdentity[]\n created_at: string\n updated_at?: string\n last_sign_in_at?: string | null\n}\n\nexport interface Session {\n access_token: string\n refresh_token: string\n token_type: 'bearer'\n expires_in: number\n expires_at: number\n user: User\n}\n\nexport class AuthError extends Error {\n status?: number\n code?: string\n\n constructor(message: string, status?: number, code?: string) {\n super(message)\n this.name = 'AuthError'\n this.status = status\n this.code = code\n }\n}\n\nexport type AuthChangeEvent = 'INITIAL_SESSION' | 'SIGNED_IN' | 'SIGNED_OUT' | 'TOKEN_REFRESHED'\n\nexport interface Subscription {\n id: string\n unsubscribe: () => void\n}\n\nexport interface StorageAdapter {\n getItem(key: string): string | null\n setItem(key: string, value: string): void\n removeItem(key: string): void\n}\n\nexport interface AuthClientOptions {\n /**\n * @internal Replaces the hosted DeepKey API origin. Used only by DeepKey's own\n * console and test suites; not supported for consumers and stripped from the\n * published type declarations.\n */\n __internalBaseUrl?: string\n storageKey?: string\n autoRefreshToken?: boolean\n detectSessionInUrl?: boolean\n persistSession?: boolean\n storage?: StorageAdapter\n fetch?: typeof fetch\n}\n\nexport interface ServerClientOptions {\n /**\n * @internal Replaces the hosted DeepKey API origin. Used only by DeepKey's own\n * console and test suites; not supported for consumers and stripped from the\n * published type declarations.\n */\n __internalBaseUrl?: string\n fetch?: typeof fetch\n}\n\nexport interface SignInWithOtpCredentials {\n email: string\n options?: {\n emailRedirectTo?: string\n shouldCreateUser?: boolean\n data?: Record<string, unknown>\n }\n}\n\nexport interface VerifyOtpParams {\n email: string\n token: string\n type: 'email' | 'magiclink'\n}\n\nexport interface ConnectProviderCredentials {\n provider: string\n options?: {\n redirectTo?: string\n skipBrowserRedirect?: boolean\n }\n}\n\n// Metadata only: provider tokens never appear in browser responses.\nexport interface ProviderConnection {\n id: string\n // The DeepKey user that granted the connection: the same `user.id` your server gets from getUser(jwt).\n user_id: string\n provider: string\n provider_user_id: string\n identity_data: Record<string, unknown>\n scope: string | null\n status: 'active' | 'reauthorization_required'\n created_at: string\n updated_at: string\n}\n\n// Issued to consumer servers through the secret key; short-lived and refreshed by DeepKey.\nexport interface ProviderAccessToken {\n access_token: string\n token_type: 'bearer'\n expires_at: number | null\n scope: string | null\n}\n\n// A long-lived credential your application issues to one of its users. Not the project\n// key: the plaintext is returned once by `apiKeys.create` and is never readable again.\nexport interface UserApiKey {\n id: string\n // The DeepKey user the key acts for: the same `user.id` your server gets from getUser(jwt).\n user_id: string\n name: string | null\n // Last four characters of the plaintext, for display.\n last4: string\n // Last successful `apiKeys.verify`; null until the key is used.\n last_used_at: string | null\n // Set once the key is revoked; a revoked key never verifies again.\n revoked_at: string | null\n created_at: string\n}\n\nexport interface SignInWithOAuthCredentials {\n provider: string\n options?: {\n redirectTo?: string\n scopes?: string\n queryParams?: Record<string, string>\n skipBrowserRedirect?: boolean\n }\n}\n\nexport interface AuthResponse<T> {\n data: T\n error: AuthError | null\n}\n","import { AuthError } from './types'\n\nexport interface RequestOptions {\n method: 'GET' | 'POST' | 'PUT' | 'DELETE'\n path: string\n apikey: string\n baseUrl: string\n accessToken?: string\n body?: unknown\n fetchImpl?: typeof fetch\n}\n\nexport interface RequestResult<T> {\n data: T | null\n error: AuthError | null\n}\n\n// All service errors are shaped { error: { code, message } }.\nexport const request = async <T>(options: RequestOptions): Promise<RequestResult<T>> => {\n const fetchImpl = options.fetchImpl ?? fetch\n try {\n const headers: Record<string, string> = { apikey: options.apikey }\n if (options.body !== undefined) headers['Content-Type'] = 'application/json'\n if (options.accessToken) headers.Authorization = `Bearer ${options.accessToken}`\n\n const response = await fetchImpl(`${options.baseUrl}${options.path}`, {\n method: options.method,\n headers,\n body: options.body !== undefined ? JSON.stringify(options.body) : undefined,\n })\n\n if (response.status === 204) return { data: {} as T, error: null }\n\n let payload: unknown\n try {\n payload = await response.json()\n } catch {\n payload = null\n }\n\n if (!response.ok) {\n const errorBody = (payload as { error?: { code?: string; message?: string } } | null)?.error\n return {\n data: null,\n error: new AuthError(\n errorBody?.message ?? `Request failed with status ${response.status}`,\n response.status,\n errorBody?.code,\n ),\n }\n }\n\n return { data: payload as T, error: null }\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Network request failed'\n return { data: null, error: new AuthError(message, 0, 'network_error') }\n }\n}\n","import type { Auth } from './client'\nimport { request } from './fetch'\nimport { AuthError, type AuthResponse, type ConnectProviderCredentials, type ProviderConnection } from './types'\n\nexport interface ConnectionsOptions {\n apikey: string\n baseUrl: string\n fetchImpl?: typeof fetch\n}\n\n// Durable provider access for the signed-in user, kept apart from login identities:\n// a user who signed in with Google can connect several X accounts. Provider refresh\n// tokens stay on DeepKey; browsers only ever see connection metadata.\nexport class Connections {\n constructor(\n private auth: Auth,\n private options: ConnectionsOptions,\n ) {}\n\n async connect(\n credentials: ConnectProviderCredentials,\n ): Promise<AuthResponse<{ provider: string; url: string | null }>> {\n const { data, error } = await this.request<{ provider: string; url: string }>(\n 'POST',\n '/auth/v1/connections/authorize',\n { provider: credentials.provider, redirect_to: credentials.options?.redirectTo },\n )\n if (error || !data) return { data: { provider: credentials.provider, url: null }, error }\n\n if (!credentials.options?.skipBrowserRedirect && typeof window !== 'undefined') {\n window.location.assign(data.url)\n }\n return { data: { provider: credentials.provider, url: data.url }, error: null }\n }\n\n async list(): Promise<AuthResponse<{ connections: ProviderConnection[] }>> {\n const { data, error } = await this.request<{ connections: ProviderConnection[] }>('GET', '/auth/v1/connections')\n return { data: { connections: data?.connections ?? [] }, error }\n }\n\n async revoke(connectionId: string): Promise<{ error: AuthError | null }> {\n const { error } = await this.request('DELETE', `/auth/v1/connections/${encodeURIComponent(connectionId)}`)\n return { error }\n }\n\n private async request<T>(method: 'GET' | 'POST' | 'DELETE', path: string, body?: unknown) {\n const {\n data: { session },\n } = await this.auth.getSession()\n if (!session) {\n return { data: null, error: new AuthError('Auth session missing!', 400, 'session_missing') }\n }\n return request<T>({ method, path, body, accessToken: session.access_token, ...this.options })\n }\n}\n","// The hosted DeepKey API. Every client talks to this origin; only DeepKey's own\n// console and test suites replace it through the internal override.\nexport const DEEPKEY_API_URL = 'https://deepkey.dev/api'\n\n// Resolves the base URL used for every request. An absent or blank internal\n// override keeps the hosted API; a provided value has any trailing slash removed\n// so path joins stay predictable.\nexport const resolveBaseUrl = (url?: string | null): string => {\n const candidate = typeof url === 'string' ? url.trim() : ''\n return (candidate || DEEPKEY_API_URL).replace(/\\/+$/, '')\n}\n","import type { StorageAdapter } from './types'\n\nclass MemoryStorage implements StorageAdapter {\n private store = new Map<string, string>()\n getItem(key: string) {\n return this.store.get(key) ?? null\n }\n setItem(key: string, value: string) {\n this.store.set(key, value)\n }\n removeItem(key: string) {\n this.store.delete(key)\n }\n}\n\n// localStorage when available, in-memory fallback (SSR, tests, restricted contexts).\nexport const defaultStorage = (): StorageAdapter => {\n try {\n if (typeof window !== 'undefined' && window.localStorage) {\n const probe = '__bt_auth_probe__'\n window.localStorage.setItem(probe, '1')\n window.localStorage.removeItem(probe)\n return window.localStorage\n }\n } catch {\n // fall through to memory storage\n }\n return new MemoryStorage()\n}\n\n// Publishable keys look like `bt_pub_<project-slug>_<random>`; the slug namespaces the\n// persisted session per project (mirrors supabase's `sb-<ref>-auth-token`).\nexport const deriveStorageKey = (publishableKey: string | undefined): string => {\n const match = publishableKey?.match(/^bt_pub_([a-z0-9-]+)_[A-Za-z0-9_-]+$/)\n return match ? `bt-${match[1]}-auth-token` : 'bt-auth-token'\n}\n","export interface FragmentTokens {\n access_token: string\n refresh_token: string\n expires_in: number\n expires_at: number\n token_type: 'bearer'\n type?: string\n}\n\nexport interface FragmentError {\n error: string\n error_code?: string\n error_description?: string\n}\n\nexport type ParsedFragment = { tokens: FragmentTokens } | { authError: FragmentError } | null\n\n// Parses the implicit-flow return fragment (`#access_token=...` or `#error=...`).\nexport const parseFragment = (hash: string): ParsedFragment => {\n if (!hash || hash.length < 2) return null\n const params = new URLSearchParams(hash.startsWith('#') ? hash.slice(1) : hash)\n\n const error = params.get('error')\n if (error) {\n return {\n authError: {\n error,\n error_code: params.get('error_code') ?? undefined,\n error_description: params.get('error_description') ?? undefined,\n },\n }\n }\n\n const accessToken = params.get('access_token')\n const refreshToken = params.get('refresh_token')\n if (!accessToken || !refreshToken) return null\n\n const expiresIn = Number(params.get('expires_in') ?? 3600)\n const expiresAt = Number(params.get('expires_at') ?? Math.floor(Date.now() / 1000) + expiresIn)\n\n return {\n tokens: {\n access_token: accessToken,\n refresh_token: refreshToken,\n expires_in: expiresIn,\n expires_at: expiresAt,\n token_type: 'bearer',\n type: params.get('type') ?? undefined,\n },\n }\n}\n\n// Removes the auth fragment from the address bar (before any await, so tokens never\n// linger in history).\nexport const cleanFragmentFromUrl = () => {\n if (typeof window === 'undefined' || typeof history === 'undefined') return\n history.replaceState(null, '', window.location.pathname + window.location.search)\n}\n","import { Connections } from './connections'\nimport { resolveBaseUrl } from './constants'\nimport { request } from './fetch'\nimport { defaultStorage, deriveStorageKey } from './storage'\nimport {\n AuthError,\n type AuthChangeEvent,\n type AuthClientOptions,\n type AuthResponse,\n type Session,\n type SignInWithOAuthCredentials,\n type SignInWithOtpCredentials,\n type StorageAdapter,\n type Subscription,\n type User,\n type VerifyOtpParams,\n} from './types'\nimport { cleanFragmentFromUrl, parseFragment } from './url'\n\nconst REFRESH_TICK_MS = 30_000\n// Refresh when the access token is within this margin of expiry.\nconst EXPIRY_MARGIN_SECONDS = 90\n\ntype Listener = (event: AuthChangeEvent, session: Session | null) => void\n\nexport class Auth {\n private baseUrl: string\n private apikey: string\n private storage: StorageAdapter\n private storageKey: string\n private persistSession: boolean\n private autoRefreshToken: boolean\n private detectSessionInUrl: boolean\n private fetchImpl?: typeof fetch\n\n private session: Session | null = null\n private listeners = new Map<string, Listener>()\n private listenerSeq = 0\n private initializePromise: Promise<void> | null = null\n private refreshPromise: Promise<AuthResponse<{ session: Session | null; user: User | null }>> | null = null\n private refreshTimer: ReturnType<typeof setInterval> | null = null\n\n constructor(publishableKey: string, options: AuthClientOptions = {}) {\n // Lazy by design: an empty key must not throw at construction time (CI builds\n // with empty envs import this module transitively). Failures surface on first call.\n // The hosted DeepKey API is the only supported origin. The internal override\n // exists for DeepKey's console and tests and is not part of the public contract.\n this.baseUrl = resolveBaseUrl(options.__internalBaseUrl)\n this.apikey = publishableKey ?? ''\n this.persistSession = options.persistSession ?? true\n this.autoRefreshToken = options.autoRefreshToken ?? true\n this.detectSessionInUrl = options.detectSessionInUrl ?? true\n this.storage = options.storage ?? defaultStorage()\n this.storageKey = options.storageKey ?? deriveStorageKey(publishableKey)\n this.fetchImpl = options.fetch\n\n this.setupCrossTabSync()\n }\n\n // ---- public API (supabase-js compatible) ----\n\n async signInWithOtp(credentials: SignInWithOtpCredentials): Promise<AuthResponse<{ user: null; session: null }>> {\n const { error } = await this.request<Record<string, never>>('POST', '/auth/v1/otp', {\n email: credentials.email,\n create_user: credentials.options?.shouldCreateUser,\n redirect_to: credentials.options?.emailRedirectTo,\n })\n return { data: { user: null, session: null }, error }\n }\n\n async verifyOtp(params: VerifyOtpParams): Promise<AuthResponse<{ user: User | null; session: Session | null }>> {\n const { data, error } = await this.request<Session>('POST', '/auth/v1/verify', {\n type: 'email',\n email: params.email,\n token: params.token,\n })\n if (error || !data) return { data: { user: null, session: null }, error }\n\n await this.saveSession(data)\n this.emit('SIGNED_IN', data)\n return { data: { user: data.user, session: data }, error: null }\n }\n\n async signInWithOAuth(\n credentials: SignInWithOAuthCredentials,\n ): Promise<AuthResponse<{ provider: string; url: string }>> {\n const url = new URL(`${this.baseUrl}/auth/v1/authorize`)\n url.searchParams.set('provider', credentials.provider)\n url.searchParams.set('apikey', this.apikey)\n if (credentials.options?.redirectTo) url.searchParams.set('redirect_to', credentials.options.redirectTo)\n if (credentials.options?.scopes) url.searchParams.set('scopes', credentials.options.scopes)\n for (const [key, value] of Object.entries(credentials.options?.queryParams ?? {})) {\n url.searchParams.set(key, value)\n }\n\n if (!credentials.options?.skipBrowserRedirect && typeof window !== 'undefined') {\n window.location.assign(url.toString())\n }\n return { data: { provider: credentials.provider, url: url.toString() }, error: null }\n }\n\n onAuthStateChange(callback: Listener): { data: { subscription: Subscription } } {\n const id = `listener-${++this.listenerSeq}`\n this.listeners.set(id, callback)\n\n // Every subscriber receives INITIAL_SESSION asynchronously once initialization\n // settles — the supabase-js contract that AuthProviders rely on to resolve loading.\n this.initialize().then(() => {\n if (this.listeners.has(id)) callback('INITIAL_SESSION', this.session)\n })\n\n return {\n data: {\n subscription: {\n id,\n unsubscribe: () => {\n this.listeners.delete(id)\n },\n },\n },\n }\n }\n\n async signOut(): Promise<{ error: AuthError | null }> {\n await this.initialize()\n const session = this.session\n if (session) {\n // Best-effort server-side revocation; local sign-out always succeeds.\n await this.request('POST', '/auth/v1/logout', {}, session.access_token)\n }\n this.clearSession()\n this.emit('SIGNED_OUT', null)\n return { error: null }\n }\n\n async getSession(): Promise<AuthResponse<{ session: Session | null }>> {\n await this.initialize()\n\n if (this.session && this.isExpired(this.session) && this.autoRefreshToken) {\n const { error } = await this.refreshWithLock()\n if (error && error.code !== 'network_error') {\n return { data: { session: null }, error: null }\n }\n }\n\n return { data: { session: this.session }, error: null }\n }\n\n async getUser(jwt?: string): Promise<AuthResponse<{ user: User | null }>> {\n let token = jwt\n if (!token) {\n const {\n data: { session },\n } = await this.getSession()\n token = session?.access_token\n }\n if (!token) {\n return { data: { user: null }, error: new AuthError('Auth session missing!', 400, 'session_missing') }\n }\n\n const { data, error } = await this.request<User>('GET', '/auth/v1/user', undefined, token)\n return { data: { user: data }, error }\n }\n\n async refreshSession(currentSession?: {\n refresh_token: string\n }): Promise<AuthResponse<{ session: Session | null; user: User | null }>> {\n await this.initialize()\n const refreshToken = currentSession?.refresh_token ?? this.session?.refresh_token\n if (!refreshToken) {\n return {\n data: { session: null, user: null },\n error: new AuthError('Auth session missing!', 400, 'session_missing'),\n }\n }\n return this.refreshWithLock(refreshToken)\n }\n\n // ---- internals ----\n\n private async request<T>(method: 'GET' | 'POST', path: string, body?: unknown, accessToken?: string) {\n return request<T>({\n method,\n path,\n body,\n accessToken,\n apikey: this.apikey,\n baseUrl: this.baseUrl,\n fetchImpl: this.fetchImpl,\n })\n }\n\n private initialize(): Promise<void> {\n this.initializePromise ??= this.doInitialize()\n return this.initializePromise\n }\n\n private async doInitialize(): Promise<void> {\n if (this.detectSessionInUrl && typeof window !== 'undefined') {\n const parsed = parseFragment(window.location.hash)\n if (parsed) {\n // Strip tokens from the address bar synchronously, before any await.\n cleanFragmentFromUrl()\n\n if ('authError' in parsed) {\n console.warn(\n `[deepkey] Auth redirect returned an error: ${parsed.authError.error_code ?? parsed.authError.error}`,\n parsed.authError.error_description ?? '',\n )\n } else {\n const { data: user } = await this.request<User>('GET', '/auth/v1/user', undefined, parsed.tokens.access_token)\n if (user) {\n const session: Session = { ...parsed.tokens, token_type: 'bearer', user }\n await this.saveSession(session)\n this.emit('SIGNED_IN', session)\n return\n }\n }\n }\n }\n\n const stored = this.loadStoredSession()\n if (!stored) return\n\n if (this.isExpired(stored)) {\n if (!this.autoRefreshToken) return\n this.session = stored\n const { error } = await this.refreshWithLock()\n if (error && error.code !== 'network_error') this.clearSession()\n return\n }\n\n this.session = stored\n this.startRefreshTimer()\n }\n\n private isExpired(session: Session): boolean {\n return session.expires_at <= Math.floor(Date.now() / 1000) + EXPIRY_MARGIN_SECONDS\n }\n\n private async refreshWithLock(\n refreshToken?: string,\n ): Promise<AuthResponse<{ session: Session | null; user: User | null }>> {\n this.refreshPromise ??= this.doRefresh(refreshToken).finally(() => {\n this.refreshPromise = null\n })\n return this.refreshPromise\n }\n\n private async doRefresh(\n refreshToken?: string,\n ): Promise<AuthResponse<{ session: Session | null; user: User | null }>> {\n const token = refreshToken ?? this.session?.refresh_token\n if (!token) {\n return {\n data: { session: null, user: null },\n error: new AuthError('Auth session missing!', 400, 'session_missing'),\n }\n }\n\n const { data, error } = await this.request<Session>('POST', '/auth/v1/token?grant_type=refresh_token', {\n refresh_token: token,\n })\n\n if (error) {\n // Network failures keep the session (retry next tick); auth failures clear it.\n if (error.code !== 'network_error') {\n this.clearSession()\n this.emit('SIGNED_OUT', null)\n }\n return { data: { session: null, user: null }, error }\n }\n\n if (data) {\n await this.saveSession(data)\n this.emit('TOKEN_REFRESHED', data)\n return { data: { session: data, user: data.user }, error: null }\n }\n return { data: { session: null, user: null }, error: new AuthError('Empty refresh response') }\n }\n\n private async saveSession(session: Session) {\n this.session = session\n if (this.persistSession) {\n try {\n this.storage.setItem(this.storageKey, JSON.stringify(session))\n } catch {\n // Persistence is best-effort (private mode, quota).\n }\n }\n this.startRefreshTimer()\n }\n\n private clearSession() {\n this.session = null\n try {\n this.storage.removeItem(this.storageKey)\n } catch {\n // ignore\n }\n this.stopRefreshTimer()\n }\n\n private loadStoredSession(): Session | null {\n if (!this.persistSession) return null\n try {\n const raw = this.storage.getItem(this.storageKey)\n if (!raw) return null\n const parsed = JSON.parse(raw) as Session\n if (!parsed.access_token || !parsed.refresh_token) return null\n return parsed\n } catch {\n return null\n }\n }\n\n private startRefreshTimer() {\n if (!this.autoRefreshToken || this.refreshTimer || typeof setInterval === 'undefined') return\n this.refreshTimer = setInterval(() => {\n if (this.session && this.isExpired(this.session)) void this.refreshWithLock()\n }, REFRESH_TICK_MS)\n // Do not keep node processes alive because of the timer (no-op in browsers).\n const timer = this.refreshTimer as unknown as { unref?: () => void }\n if (typeof timer === 'object' && typeof timer.unref === 'function') timer.unref()\n }\n\n private stopRefreshTimer() {\n if (this.refreshTimer) {\n clearInterval(this.refreshTimer)\n this.refreshTimer = null\n }\n }\n\n private setupCrossTabSync() {\n if (typeof window === 'undefined' || !this.persistSession) return\n window.addEventListener('storage', event => {\n if (event.key !== this.storageKey) return\n if (!event.newValue) {\n if (this.session) {\n this.session = null\n this.stopRefreshTimer()\n this.emit('SIGNED_OUT', null)\n }\n return\n }\n try {\n const next = JSON.parse(event.newValue) as Session\n const changed = next.access_token !== this.session?.access_token\n this.session = next\n this.startRefreshTimer()\n if (changed) this.emit('TOKEN_REFRESHED', next)\n } catch {\n // ignore malformed writes\n }\n })\n }\n\n private emit(event: AuthChangeEvent, session: Session | null) {\n for (const listener of this.listeners.values()) {\n try {\n listener(event, session)\n } catch (err) {\n console.error('[deepkey] onAuthStateChange listener threw', err)\n }\n }\n }\n}\n\nexport interface AuthClient {\n auth: Auth\n connections: Connections\n}\n\nexport const createAuthClient = (publishableKey: string, options?: AuthClientOptions): AuthClient => {\n const auth = new Auth(publishableKey, options)\n const connections = new Connections(auth, {\n apikey: publishableKey ?? '',\n baseUrl: resolveBaseUrl(options?.__internalBaseUrl),\n fetchImpl: options?.fetch,\n })\n return { auth, connections }\n}\n"],"mappings":"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","names":[]}