@better-auth/expo 1.7.0-rc.3 → 1.7.0-rc.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/client.d.ts CHANGED
@@ -1,6 +1,7 @@
1
- import { parseSetCookieHeader } from "better-auth/cookies";
1
+ import { parseSetCookieHeader } from "better-auth/cookies/utils";
2
2
  import { FocusManager, OnlineManager } from "better-auth/client";
3
3
  import { ClientFetchOption, ClientStore } from "@better-auth/core";
4
+ import * as SecureStore from "expo-secure-store";
4
5
  //#region src/focus-manager.d.ts
5
6
  declare function setupExpoFocusManager(): FocusManager;
6
7
  //#endregion
@@ -8,12 +9,13 @@ declare function setupExpoFocusManager(): FocusManager;
8
9
  declare function setupExpoOnlineManager(): OnlineManager;
9
10
  //#endregion
10
11
  //#region src/client.d.ts
12
+ /**
13
+ * Storage used by the Expo client for cookies and cached session data.
14
+ */
15
+ type ExpoClientStorage = Pick<typeof SecureStore, "setItem" | "setItemAsync" | "getItem" | "getItemAsync">;
11
16
  interface ExpoClientOptions {
12
17
  scheme?: string | undefined;
13
- storage: {
14
- setItem: (key: string, value: string) => any;
15
- getItem: (key: string) => string | null;
16
- };
18
+ storage: ExpoClientStorage;
17
19
  /**
18
20
  * Prefix for local storage keys (e.g., "my-app_cookie", "my-app_session_data")
19
21
  * @default "better-auth"
@@ -52,7 +54,7 @@ interface ExpoClientOptions {
52
54
  webBrowserOptions?: import("expo-web-browser").AuthSessionOpenOptions;
53
55
  }
54
56
  declare function getSetCookie(header: string, prevCookie?: string | undefined): string;
55
- declare function getCookie(cookie: string): string;
57
+ declare function getCookie(cookie: string | null): string;
56
58
  /**
57
59
  * Check if the Set-Cookie header contains better-auth cookies.
58
60
  * This prevents infinite refetching when non-better-auth cookies (like third-party cookies) change.
@@ -79,16 +81,14 @@ declare function hasBetterAuthCookies(setCookieHeader: string, cookiePrefix: str
79
81
  * @returns normalized cookie name
80
82
  */
81
83
  declare function normalizeCookieName(name: string): string;
82
- declare function storageAdapter(storage: {
83
- getItem: (name: string) => string | null;
84
- setItem: (name: string, value: string) => unknown;
85
- }): {
84
+ declare function storageAdapter(storage: ExpoClientStorage): {
86
85
  /**
87
86
  * Reads a value, reassembling it if it was split across chunk keys. A value
88
87
  * that fit is returned as-is (values written before chunking still read
89
88
  * back); a missing chunk returns `null` so a torn write fails closed.
90
89
  */
91
90
  getItem: (name: string) => string | null;
91
+ getItemAsync: (name: string) => Promise<string | null>;
92
92
  /**
93
93
  * Stores `value`, splitting it across chunk keys when it exceeds the
94
94
  * per-write limit. The base key is cleared before the chunks are rewritten
@@ -97,7 +97,8 @@ declare function storageAdapter(storage: {
97
97
  * Failures are logged, not thrown: persistence is best-effort and must not
98
98
  * break the request.
99
99
  */
100
- setItem: (name: string, value: string) => Promise<void>;
100
+ setItem: (name: string, value: string) => void;
101
+ setItemAsync: (name: string, value: string) => Promise<void>;
101
102
  };
102
103
  declare const expoClient: (opts: ExpoClientOptions) => {
103
104
  id: "expo";
@@ -106,19 +107,17 @@ declare const expoClient: (opts: ExpoClientOptions) => {
106
107
  /**
107
108
  * Get the stored cookie.
108
109
  *
109
- * You can use this to get the cookie stored in the device and use it in your fetch
110
- * requests.
111
- *
112
110
  * @example
113
111
  * ```ts
114
- * const cookie = client.getCookie();
112
+ * const cookie = await client.getCookie();
115
113
  * fetch("https://api.example.com", {
116
114
  * headers: {
117
115
  * cookie,
118
116
  * },
119
117
  * });
118
+ * ```
120
119
  */
121
- getCookie: () => string;
120
+ getCookie: () => Promise<string>;
122
121
  };
123
122
  fetchPlugins: {
124
123
  id: string;
@@ -187,4 +186,4 @@ declare const expoClient: (opts: ExpoClientOptions) => {
187
186
  }[];
188
187
  };
189
188
  //#endregion
190
- export { expoClient, getCookie, getSetCookie, hasBetterAuthCookies, normalizeCookieName, parseSetCookieHeader, setupExpoFocusManager, setupExpoOnlineManager, storageAdapter };
189
+ export { ExpoClientStorage, expoClient, getCookie, getSetCookie, hasBetterAuthCookies, normalizeCookieName, parseSetCookieHeader, setupExpoFocusManager, setupExpoOnlineManager, storageAdapter };
package/dist/client.js CHANGED
@@ -1,6 +1,6 @@
1
- import { t as PACKAGE_VERSION } from "./version-xoFrTW0m.js";
1
+ import { t as PACKAGE_VERSION } from "./version-CwxXc1Y6.js";
2
2
  import { safeJSONParse } from "@better-auth/core/utils/json";
3
- import { SECURE_COOKIE_PREFIX, parseSetCookieHeader, parseSetCookieHeader as parseSetCookieHeader$1, stripSecureCookiePrefix } from "better-auth/cookies";
3
+ import { SECURE_COOKIE_PREFIX, parseSetCookieHeader, parseSetCookieHeader as parseSetCookieHeader$1, stripSecureCookiePrefix } from "better-auth/cookies/utils";
4
4
  import Constants from "expo-constants";
5
5
  import * as Linking from "expo-linking";
6
6
  import { AppState, Platform } from "react-native";
@@ -107,10 +107,7 @@ function getSetCookie(header, prevCookie) {
107
107
  return JSON.stringify(toSetCookie);
108
108
  }
109
109
  function getCookie(cookie) {
110
- let parsed = {};
111
- try {
112
- parsed = JSON.parse(cookie);
113
- } catch {}
110
+ const parsed = safeJSONParse(cookie) ?? {};
114
111
  return Object.entries(parsed).reduce((acc, [key, value]) => {
115
112
  if (value.expires && new Date(value.expires) < /* @__PURE__ */ new Date()) return acc;
116
113
  return acc ? `${acc}; ${key}=${value.value}` : `${key}=${value.value}`;
@@ -213,6 +210,17 @@ const STORAGE_VALUE_LIMIT = 1800;
213
210
  * survives the native storage bridge without C-string truncation.
214
211
  */
215
212
  const CHUNK_MARKER = "ba-chunks:";
213
+ function getStorageWrites(key, value) {
214
+ if (value.length <= STORAGE_VALUE_LIMIT) return [[key, value]];
215
+ const count = Math.ceil(value.length / STORAGE_VALUE_LIMIT);
216
+ const writes = [[key, ""]];
217
+ for (let i = 0; i < count; i++) {
218
+ const start = i * STORAGE_VALUE_LIMIT;
219
+ writes.push([`${key}.${i}`, value.slice(start, start + STORAGE_VALUE_LIMIT)]);
220
+ }
221
+ writes.push([key, `${CHUNK_MARKER}${count}`]);
222
+ return writes;
223
+ }
216
224
  function storageAdapter(storage) {
217
225
  return {
218
226
  /**
@@ -234,6 +242,20 @@ function storageAdapter(storage) {
234
242
  }
235
243
  return value;
236
244
  },
245
+ getItemAsync: async (name) => {
246
+ const key = normalizeCookieName(name);
247
+ const stored = await storage.getItemAsync(key);
248
+ if (stored == null || !stored.startsWith(CHUNK_MARKER)) return stored;
249
+ const count = Number(stored.slice(11));
250
+ if (!Number.isInteger(count) || count < 1) return null;
251
+ let value = "";
252
+ for (let i = 0; i < count; i++) {
253
+ const chunk = await storage.getItemAsync(`${key}.${i}`);
254
+ if (chunk == null) return null;
255
+ value += chunk;
256
+ }
257
+ return value;
258
+ },
237
259
  /**
238
260
  * Stores `value`, splitting it across chunk keys when it exceeds the
239
261
  * per-write limit. The base key is cleared before the chunks are rewritten
@@ -242,20 +264,18 @@ function storageAdapter(storage) {
242
264
  * Failures are logged, not thrown: persistence is best-effort and must not
243
265
  * break the request.
244
266
  */
245
- setItem: async (name, value) => {
267
+ setItem: (name, value) => {
246
268
  const key = normalizeCookieName(name);
247
269
  try {
248
- if (value.length <= STORAGE_VALUE_LIMIT) {
249
- await storage.setItem(key, value);
250
- return;
251
- }
252
- await storage.setItem(key, "");
253
- const count = Math.ceil(value.length / STORAGE_VALUE_LIMIT);
254
- for (let i = 0; i < count; i++) {
255
- const start = i * STORAGE_VALUE_LIMIT;
256
- await storage.setItem(`${key}.${i}`, value.slice(start, start + STORAGE_VALUE_LIMIT));
257
- }
258
- await storage.setItem(key, `${CHUNK_MARKER}${count}`);
270
+ for (const [writeKey, writeValue] of getStorageWrites(key, value)) storage.setItem(writeKey, writeValue);
271
+ } catch (error) {
272
+ console.error(`[better-auth/expo] failed to persist "${key}" to storage`, error);
273
+ }
274
+ },
275
+ setItemAsync: async (name, value) => {
276
+ const key = normalizeCookieName(name);
277
+ try {
278
+ for (const [writeKey, writeValue] of getStorageWrites(key, value)) await storage.setItemAsync(writeKey, writeValue);
259
279
  } catch (error) {
260
280
  console.error(`[better-auth/expo] failed to persist "${key}" to storage`, error);
261
281
  }
@@ -267,18 +287,42 @@ const expoClient = (opts) => {
267
287
  const storagePrefix = opts?.storagePrefix || "better-auth";
268
288
  const cookieName = `${storagePrefix}_cookie`;
269
289
  const localCacheName = `${storagePrefix}_session_data`;
270
- const storage = storageAdapter(opts?.storage);
290
+ const storage = storageAdapter(opts.storage);
271
291
  const isWeb = Platform.OS === "web";
272
292
  const cookiePrefix = opts?.cookiePrefix || "better-auth";
293
+ let sessionCacheHydration;
294
+ const restoreSessionCache = async () => {
295
+ if (isWeb || opts?.disableCache) return;
296
+ const sessionAtom = store?.atoms.session;
297
+ if (!sessionAtom) return;
298
+ const initialSessionState = sessionAtom.get();
299
+ if (initialSessionState.data !== null) return;
300
+ const raw = await storage.getItemAsync(localCacheName);
301
+ const cached = raw ? safeJSONParse(raw) : null;
302
+ const expiresAt = cached?.session?.expiresAt;
303
+ const expiresAtMs = expiresAt ? new Date(expiresAt).getTime() : NaN;
304
+ if (!!cached?.user?.id && !!cached.session?.id && expiresAtMs > Date.now() && sessionAtom.get() === initialSessionState) sessionAtom.set({
305
+ ...initialSessionState,
306
+ data: cached,
307
+ error: null
308
+ });
309
+ };
310
+ const hydrateSessionCache = () => {
311
+ if (!sessionCacheHydration) sessionCacheHydration = restoreSessionCache().catch((error) => {
312
+ sessionCacheHydration = void 0;
313
+ throw error;
314
+ });
315
+ return sessionCacheHydration;
316
+ };
273
317
  const clearSessionCache = async () => {
274
- await storage.setItem(cookieName, "{}");
318
+ await storage.setItemAsync(cookieName, "{}");
275
319
  store?.atoms.session?.set({
276
320
  ...store.atoms.session.get(),
277
321
  data: null,
278
322
  error: null,
279
323
  isPending: false
280
324
  });
281
- await storage.setItem(localCacheName, "{}");
325
+ await storage.setItemAsync(localCacheName, "{}");
282
326
  };
283
327
  const rawScheme = opts?.scheme || Constants.expoConfig?.scheme || Constants.platform?.scheme;
284
328
  const scheme = Array.isArray(rawScheme) ? rawScheme[0] : rawScheme;
@@ -288,36 +332,22 @@ const expoClient = (opts) => {
288
332
  version: PACKAGE_VERSION,
289
333
  getActions(_fetch, $store) {
290
334
  store = $store;
291
- const sessionAtom = $store.atoms.session;
292
- if (!isWeb && !opts?.disableCache && sessionAtom) {
293
- const raw = storage.getItem(localCacheName);
294
- const cached = raw ? safeJSONParse(raw) : null;
295
- const exp = cached?.session?.expiresAt;
296
- const expMs = exp ? new Date(exp).getTime() : NaN;
297
- if (!!cached?.user?.id && !!cached.session?.id && expMs > Date.now()) sessionAtom.set({
298
- ...sessionAtom.get(),
299
- data: cached,
300
- error: null
301
- });
302
- }
303
335
  return {
304
336
  /**
305
337
  * Get the stored cookie.
306
338
  *
307
- * You can use this to get the cookie stored in the device and use it in your fetch
308
- * requests.
309
- *
310
339
  * @example
311
340
  * ```ts
312
- * const cookie = client.getCookie();
341
+ * const cookie = await client.getCookie();
313
342
  * fetch("https://api.example.com", {
314
343
  * headers: {
315
344
  * cookie,
316
345
  * },
317
346
  * });
347
+ * ```
318
348
  */
319
- getCookie: () => {
320
- return getCookie(storage.getItem(cookieName) || "{}");
349
+ getCookie: async () => {
350
+ return getCookie(await storage.getItemAsync(cookieName));
321
351
  } };
322
352
  },
323
353
  fetchPlugins: [{
@@ -325,23 +355,26 @@ getCookie: () => {
325
355
  name: "Expo",
326
356
  hooks: { async onSuccess(context) {
327
357
  if (isWeb) return;
358
+ const { pathname } = new URL(context.request.url);
328
359
  const setCookie = context.response.headers.get("set-cookie");
329
360
  if (setCookie) {
330
361
  if (hasBetterAuthCookies(setCookie, cookiePrefix)) {
331
- const prevCookie = storage.getItem(cookieName);
362
+ const prevCookie = await storage.getItemAsync(cookieName);
332
363
  const toSetCookie = getSetCookie(setCookie || "", prevCookie ?? void 0);
333
364
  if (hasSessionCookieChanged(prevCookie, toSetCookie)) {
334
- await storage.setItem(cookieName, toSetCookie);
365
+ await storage.setItemAsync(cookieName, toSetCookie);
335
366
  store?.notify("$sessionSignal");
336
- } else await storage.setItem(cookieName, toSetCookie);
367
+ } else await storage.setItemAsync(cookieName, toSetCookie);
337
368
  }
338
369
  }
339
- if (context.request.url.toString().includes("/get-session") && !opts?.disableCache) {
370
+ if (pathname.endsWith("/get-session") && !opts?.disableCache) {
340
371
  const data = context.data;
341
- await storage.setItem(localCacheName, JSON.stringify(data));
372
+ await storage.setItemAsync(localCacheName, JSON.stringify(data));
342
373
  }
343
- if (context.request.url.toString().includes("/sign-out")) await clearSessionCache();
344
- if (context.data?.redirect && (context.request.url.toString().includes("/sign-in") || context.request.url.toString().includes("/link-social")) && !context.request?.body.includes("idToken")) {
374
+ if (pathname.endsWith("/sign-out")) await clearSessionCache();
375
+ const isSignInRequest = pathname.endsWith("/sign-in") || pathname.includes("/sign-in/");
376
+ const isLinkSocialRequest = pathname.endsWith("/link-social");
377
+ if (context.data?.redirect && (isSignInRequest || isLinkSocialRequest) && !context.request?.body.includes("idToken")) {
345
378
  const to = JSON.parse(context.request.body)?.callbackURL;
346
379
  const signInURL = context.data?.url;
347
380
  let Browser = void 0;
@@ -357,7 +390,7 @@ getCookie: () => {
357
390
  if (Platform.OS === "android") try {
358
391
  Browser.dismissAuthSession();
359
392
  } catch {}
360
- const oauthStateValue = getOAuthStateValue(storage.getItem(cookieName), cookiePrefix);
393
+ const oauthStateValue = getOAuthStateValue(await storage.getItemAsync(cookieName), cookiePrefix);
361
394
  const params = new URLSearchParams({ authorizationURL: signInURL });
362
395
  if (oauthStateValue) params.append("oauthState", oauthStateValue);
363
396
  const proxyURL = `${context.request.baseURL}/expo-authorization-proxy?${params.toString()}`;
@@ -365,8 +398,8 @@ getCookie: () => {
365
398
  if (result.type !== "success") return;
366
399
  const cookie = new URL(result.url).searchParams.get("cookie");
367
400
  if (!cookie) return;
368
- const toSetCookie = getSetCookie(cookie, storage.getItem(cookieName) ?? void 0);
369
- await storage.setItem(cookieName, toSetCookie);
401
+ const toSetCookie = getSetCookie(cookie, await storage.getItemAsync(cookieName) ?? void 0);
402
+ await storage.setItemAsync(cookieName, toSetCookie);
370
403
  store?.notify("$sessionSignal");
371
404
  }
372
405
  } },
@@ -375,17 +408,19 @@ getCookie: () => {
375
408
  url,
376
409
  options
377
410
  };
411
+ const { pathname } = new URL(url, options?.baseURL);
412
+ if (pathname.endsWith("/get-session")) await hydrateSessionCache();
378
413
  options = options || {};
379
414
  options.credentials = "omit";
380
415
  if (options.body?.idToken !== void 0) {
381
- const cookie = url.includes("/link-social") ? getCookie(storage.getItem(cookieName) || "{}") : "";
416
+ const cookie = getCookie(pathname.endsWith("/link-social") ? await storage.getItemAsync(cookieName) : null);
382
417
  options.headers = {
383
418
  ...options.headers,
384
419
  ...cookie ? { cookie } : {},
385
420
  "x-skip-oauth-proxy": "true"
386
421
  };
387
422
  } else {
388
- const cookie = getCookie(storage.getItem(cookieName) || "{}");
423
+ const cookie = getCookie(await storage.getItemAsync(cookieName));
389
424
  options.headers = {
390
425
  ...options.headers,
391
426
  ...cookie ? { cookie } : {},
@@ -410,7 +445,7 @@ getCookie: () => {
410
445
  options.body.errorCallbackURL = url;
411
446
  }
412
447
  }
413
- if (url.includes("/sign-out")) await clearSessionCache();
448
+ if (pathname.endsWith("/sign-out")) await clearSessionCache();
414
449
  }
415
450
  return {
416
451
  url,
package/dist/index.d.ts CHANGED
@@ -27,7 +27,7 @@ declare const expo: (options?: ExpoOptions | undefined) => {
27
27
  hooks: {
28
28
  after: {
29
29
  matcher(context: import("better-auth").HookEndpointContext): boolean;
30
- handler: (inputContext: import("better-call").MiddlewareInputContext<import("better-call").MiddlewareOptions>) => Promise<void>;
30
+ handler: import("better-call").Middleware<import("better-call").MiddlewareOptions, (inputContext: import("better-call").MiddlewareInputContext<import("better-call").MiddlewareOptions>) => Promise<void>>;
31
31
  }[];
32
32
  };
33
33
  endpoints: {
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { t as PACKAGE_VERSION } from "./version-xoFrTW0m.js";
1
+ import { t as PACKAGE_VERSION } from "./version-CwxXc1Y6.js";
2
2
  import { createAuthMiddleware } from "@better-auth/core/api";
3
3
  import { HIDE_METADATA } from "better-auth";
4
4
  import { APIError, createAuthEndpoint } from "better-auth/api";
@@ -1,4 +1,4 @@
1
- import { t as PACKAGE_VERSION } from "../version-xoFrTW0m.js";
1
+ import { t as PACKAGE_VERSION } from "../version-CwxXc1Y6.js";
2
2
  //#region src/plugins/last-login-method.ts
3
3
  const paths = [
4
4
  "/callback/",
@@ -1,5 +1,5 @@
1
1
  //#endregion
2
2
  //#region src/version.ts
3
- const PACKAGE_VERSION = "1.7.0-rc.3";
3
+ const PACKAGE_VERSION = "1.7.0-rc.5";
4
4
  //#endregion
5
5
  export { PACKAGE_VERSION as t };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@better-auth/expo",
3
- "version": "1.7.0-rc.3",
3
+ "version": "1.7.0-rc.5",
4
4
  "description": "Better Auth integration for Expo and React Native applications.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -59,7 +59,7 @@
59
59
  },
60
60
  "dependencies": {
61
61
  "@better-fetch/fetch": "1.3.1",
62
- "better-call": "1.3.7",
62
+ "better-call": "1.4.0",
63
63
  "zod": "^4.3.6"
64
64
  },
65
65
  "devDependencies": {
@@ -67,19 +67,21 @@
67
67
  "expo-constants": "~56.0.18",
68
68
  "expo-linking": "~56.0.14",
69
69
  "expo-network": "~56.0.5",
70
+ "expo-secure-store": "~56.0.4",
70
71
  "expo-web-browser": "~56.0.5",
71
72
  "react-native": "~0.86.0",
72
73
  "tsdown": "0.22.7",
73
- "@better-auth/core": "1.7.0-rc.3",
74
- "better-auth": "1.7.0-rc.3"
74
+ "@better-auth/core": "1.7.0-rc.5",
75
+ "better-auth": "1.7.0-rc.5"
75
76
  },
76
77
  "peerDependencies": {
77
78
  "expo-constants": ">=17.0.0",
78
79
  "expo-linking": ">=7.0.0",
79
80
  "expo-network": ">=8.0.7",
81
+ "expo-secure-store": ">=12.5.0",
80
82
  "expo-web-browser": ">=14.0.0",
81
- "@better-auth/core": "^1.7.0-rc.3",
82
- "better-auth": "^1.7.0-rc.3"
83
+ "@better-auth/core": "^1.7.0-rc.5",
84
+ "better-auth": "^1.7.0-rc.5"
83
85
  },
84
86
  "peerDependenciesMeta": {
85
87
  "expo-constants": {
@@ -91,6 +93,9 @@
91
93
  "expo-network": {
92
94
  "optional": true
93
95
  },
96
+ "expo-secure-store": {
97
+ "optional": true
98
+ },
94
99
  "expo-web-browser": {
95
100
  "optional": true
96
101
  }