@better-auth/expo 1.5.0 → 1.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -176,4 +176,4 @@ declare const expoClient: (opts: ExpoClientOptions) => {
176
176
  };
177
177
  //#endregion
178
178
  export { expoClient, getCookie, getSetCookie, hasBetterAuthCookies, normalizeCookieName, parseSetCookieHeader, setupExpoFocusManager, setupExpoOnlineManager, storageAdapter };
179
- //# sourceMappingURL=client.d.mts.map
179
+ //# sourceMappingURL=client.d.ts.map
@@ -1,4 +1,3 @@
1
- import { createRequire } from "node:module";
2
1
  import { safeJSONParse } from "@better-auth/core/utils/json";
3
2
  import { SECURE_COOKIE_PREFIX, parseSetCookieHeader, parseSetCookieHeader as parseSetCookieHeader$1, stripSecureCookiePrefix } from "better-auth/cookies";
4
3
  import Constants from "expo-constants";
@@ -7,7 +6,10 @@ import { AppState, Platform } from "react-native";
7
6
  import { kFocusManager, kOnlineManager } from "better-auth/client";
8
7
 
9
8
  //#region \0rolldown/runtime.js
10
- var __require = /* @__PURE__ */ createRequire(import.meta.url);
9
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, { get: (a, b) => (typeof require !== "undefined" ? require : a)[b] }) : x)(function(x) {
10
+ if (typeof require !== "undefined") return require.apply(this, arguments);
11
+ throw Error("Calling `require` for \"" + x + "\" in an environment that doesn't expose the `require` function. See https://rolldown.rs/in-depth/bundling-cjs#require-external-modules for more details.");
12
+ });
11
13
 
12
14
  //#endregion
13
15
  //#region src/focus-manager.ts
@@ -333,4 +335,4 @@ const expoClient = (opts) => {
333
335
 
334
336
  //#endregion
335
337
  export { expoClient, getCookie, getSetCookie, hasBetterAuthCookies, normalizeCookieName, parseSetCookieHeader, setupExpoFocusManager, setupExpoOnlineManager, storageAdapter };
336
- //# sourceMappingURL=client.mjs.map
338
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","names":["parseSetCookieHeader"],"sources":["../src/focus-manager.ts","../src/online-manager.ts","../src/client.ts"],"sourcesContent":["import type { FocusListener, FocusManager } from \"better-auth/client\";\nimport { kFocusManager } from \"better-auth/client\";\nimport type { AppStateStatus } from \"react-native\";\nimport { AppState } from \"react-native\";\n\nclass ExpoFocusManager implements FocusManager {\n\tlisteners = new Set<FocusListener>();\n\tsubscription?: ReturnType<typeof AppState.addEventListener>;\n\tisFocused: boolean | undefined;\n\n\tsubscribe(listener: FocusListener) {\n\t\tthis.listeners.add(listener);\n\t\treturn () => {\n\t\t\tthis.listeners.delete(listener);\n\t\t};\n\t}\n\n\tsetFocused(focused: boolean) {\n\t\tif (this.isFocused === focused) return;\n\t\tthis.isFocused = focused;\n\t\tthis.listeners.forEach((listener) => listener(focused));\n\t}\n\n\tsetup() {\n\t\tthis.subscription = AppState.addEventListener(\n\t\t\t\"change\",\n\t\t\t(state: AppStateStatus) => {\n\t\t\t\tthis.setFocused(state === \"active\");\n\t\t\t},\n\t\t);\n\n\t\treturn () => {\n\t\t\tthis.subscription?.remove();\n\t\t};\n\t}\n}\n\nexport function setupExpoFocusManager() {\n\tif (!(globalThis as any)[kFocusManager]) {\n\t\t(globalThis as any)[kFocusManager] = new ExpoFocusManager();\n\t}\n\treturn (globalThis as any)[kFocusManager] as FocusManager;\n}\n","import type { OnlineListener, OnlineManager } from \"better-auth/client\";\nimport { kOnlineManager } from \"better-auth/client\";\n\nclass ExpoOnlineManager implements OnlineManager {\n\tlisteners = new Set<OnlineListener>();\n\tisOnline = true;\n\tunsubscribe?: () => void;\n\n\tsubscribe(listener: OnlineListener) {\n\t\tthis.listeners.add(listener);\n\t\treturn () => {\n\t\t\tthis.listeners.delete(listener);\n\t\t};\n\t}\n\n\tsetOnline(online: boolean) {\n\t\tif (this.isOnline === online) return;\n\t\tthis.isOnline = online;\n\t\tthis.listeners.forEach((listener) => listener(online));\n\t}\n\n\tsetup() {\n\t\timport(\"expo-network\")\n\t\t\t.then(({ addNetworkStateListener }) => {\n\t\t\t\tconst subscription = addNetworkStateListener((state) => {\n\t\t\t\t\tthis.setOnline(!!state.isInternetReachable);\n\t\t\t\t});\n\t\t\t\tthis.unsubscribe = () => subscription.remove();\n\t\t\t})\n\t\t\t.catch(() => {\n\t\t\t\t// fallback to always online\n\t\t\t\tthis.setOnline(true);\n\t\t\t});\n\n\t\treturn () => {\n\t\t\tthis.unsubscribe?.();\n\t\t};\n\t}\n}\n\nexport function setupExpoOnlineManager() {\n\tif (!(globalThis as any)[kOnlineManager]) {\n\t\t(globalThis as any)[kOnlineManager] = new ExpoOnlineManager();\n\t}\n\treturn (globalThis as any)[kOnlineManager] as OnlineManager;\n}\n","import type {\n\tBetterAuthClientPlugin,\n\tClientFetchOption,\n\tClientStore,\n} from \"@better-auth/core\";\nimport { safeJSONParse } from \"@better-auth/core/utils/json\";\nimport {\n\tparseSetCookieHeader,\n\tSECURE_COOKIE_PREFIX,\n\tstripSecureCookiePrefix,\n} from \"better-auth/cookies\";\nimport Constants from \"expo-constants\";\nimport * as Linking from \"expo-linking\";\nimport { Platform } from \"react-native\";\nimport { setupExpoFocusManager } from \"./focus-manager\";\nimport { setupExpoOnlineManager } from \"./online-manager\";\n\nif (Platform.OS !== \"web\") {\n\tsetupExpoFocusManager();\n\tsetupExpoOnlineManager();\n}\n\ninterface ExpoClientOptions {\n\tscheme?: string | undefined;\n\tstorage: {\n\t\tsetItem: (key: string, value: string) => any;\n\t\tgetItem: (key: string) => string | null;\n\t};\n\t/**\n\t * Prefix for local storage keys (e.g., \"my-app_cookie\", \"my-app_session_data\")\n\t * @default \"better-auth\"\n\t */\n\tstoragePrefix?: string | undefined;\n\t/**\n\t * Prefix(es) for server cookie names to filter (e.g., \"better-auth.session_token\")\n\t * This is used to identify which cookies belong to better-auth to prevent\n\t * infinite refetching when third-party cookies are set.\n\t * Can be a single string or an array of strings to match multiple prefixes.\n\t * @default \"better-auth\"\n\t * @example \"better-auth\"\n\t * @example [\"better-auth\", \"my-app\"]\n\t */\n\tcookiePrefix?: string | string[] | undefined;\n\tdisableCache?: boolean | undefined;\n\t/**\n\t * Options to customize the Expo web browser behavior when opening authentication\n\t * sessions. These are passed directly to `expo-web-browser`'s\n\t * `Browser.openBrowserAsync`.\n\t *\n\t * For example, on iOS you can use `{ preferEphemeralSession: true }` to prevent\n\t * the authentication session from sharing cookies with the user's default\n\t * browser session:\n\t *\n\t * ```ts\n\t * const client = createClient({\n\t * expo: {\n\t * webBrowserOptions: {\n\t * preferEphemeralSession: true,\n\t * },\n\t * },\n\t * });\n\t * ```\n\t */\n\twebBrowserOptions?: import(\"expo-web-browser\").AuthSessionOpenOptions;\n}\n\ninterface StoredCookie {\n\tvalue: string;\n\texpires: string | null;\n}\n\nexport function getSetCookie(header: string, prevCookie?: string | undefined) {\n\tconst parsed = parseSetCookieHeader(header);\n\tconst toSetCookie =\n\t\tsafeJSONParse<Record<string, StoredCookie>>(prevCookie) ?? {};\n\tparsed.forEach((cookie, key) => {\n\t\tconst expiresAt = cookie[\"expires\"];\n\t\tconst maxAge = cookie[\"max-age\"];\n\t\tif (maxAge !== undefined && Number(maxAge) <= 0) {\n\t\t\tdelete toSetCookie[key];\n\t\t\treturn;\n\t\t}\n\t\tconst expires = maxAge\n\t\t\t? new Date(Date.now() + Number(maxAge) * 1000)\n\t\t\t: expiresAt\n\t\t\t\t? new Date(String(expiresAt))\n\t\t\t\t: null;\n\t\tif (expires && expires.getTime() <= Date.now()) {\n\t\t\tdelete toSetCookie[key];\n\t\t\treturn;\n\t\t}\n\t\ttoSetCookie[key] = {\n\t\t\tvalue: cookie[\"value\"],\n\t\t\texpires: expires ? expires.toISOString() : null,\n\t\t};\n\t});\n\treturn JSON.stringify(toSetCookie);\n}\n\nexport function getCookie(cookie: string) {\n\tlet parsed = {} as Record<string, StoredCookie>;\n\ttry {\n\t\tparsed = JSON.parse(cookie) as Record<string, StoredCookie>;\n\t} catch {}\n\tconst toSend = Object.entries(parsed).reduce((acc, [key, value]) => {\n\t\tif (value.expires && new Date(value.expires) < new Date()) {\n\t\t\treturn acc;\n\t\t}\n\t\treturn acc ? `${acc}; ${key}=${value.value}` : `${key}=${value.value}`;\n\t}, \"\");\n\treturn toSend;\n}\n\nfunction getOAuthStateValue(\n\tcookieJson: string | null,\n\tcookiePrefix: string | string[],\n): string | null {\n\tif (!cookieJson) return null;\n\n\tconst parsed = safeJSONParse<Record<string, StoredCookie>>(cookieJson);\n\tif (!parsed) return null;\n\n\tconst prefixes = Array.isArray(cookiePrefix) ? cookiePrefix : [cookiePrefix];\n\n\tfor (const prefix of prefixes) {\n\t\t// cookie strategy uses: <prefix>.oauth_state\n\t\tconst candidates = [\n\t\t\t`${SECURE_COOKIE_PREFIX}${prefix}.oauth_state`,\n\t\t\t`${prefix}.oauth_state`,\n\t\t];\n\n\t\tfor (const name of candidates) {\n\t\t\tconst value = parsed?.[name]?.value;\n\t\t\tif (value) return value;\n\t\t}\n\t}\n\n\treturn null;\n}\n\nfunction getOrigin(scheme: string) {\n\tconst schemeURI = Linking.createURL(\"\", { scheme });\n\treturn schemeURI;\n}\n\n/**\n * Compare if session cookies have actually changed by comparing their values.\n * Ignores expiry timestamps that naturally change on each request.\n *\n * @param prevCookie - Previous cookie JSON string\n * @param newCookie - New cookie JSON string\n * @returns true if session cookies have changed, false otherwise\n */\nfunction hasSessionCookieChanged(\n\tprevCookie: string | null,\n\tnewCookie: string,\n): boolean {\n\tif (!prevCookie) return true;\n\n\ttry {\n\t\tconst prev = JSON.parse(prevCookie) as Record<string, StoredCookie>;\n\t\tconst next = JSON.parse(newCookie) as Record<string, StoredCookie>;\n\n\t\t// Get all session-related cookie keys (session_token, session_data)\n\t\tconst sessionKeys = new Set<string>();\n\t\tObject.keys(prev).forEach((key) => {\n\t\t\tif (key.includes(\"session_token\") || key.includes(\"session_data\")) {\n\t\t\t\tsessionKeys.add(key);\n\t\t\t}\n\t\t});\n\t\tObject.keys(next).forEach((key) => {\n\t\t\tif (key.includes(\"session_token\") || key.includes(\"session_data\")) {\n\t\t\t\tsessionKeys.add(key);\n\t\t\t}\n\t\t});\n\n\t\t// Compare the values of session cookies (ignore expires timestamps)\n\t\tfor (const key of sessionKeys) {\n\t\t\tconst prevValue = prev[key]?.value;\n\t\t\tconst nextValue = next[key]?.value;\n\t\t\tif (prevValue !== nextValue) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\n\t\treturn false;\n\t} catch {\n\t\t// If parsing fails, assume cookie changed\n\t\treturn true;\n\t}\n}\n\n/**\n * Check if the Set-Cookie header contains better-auth cookies.\n * This prevents infinite refetching when non-better-auth cookies (like third-party cookies) change.\n *\n * Supports multiple cookie naming patterns:\n * - Default: \"better-auth.session_token\", \"better-auth-passkey\", \"__Secure-better-auth.session_token\"\n * - Custom prefix: \"myapp.session_token\", \"myapp-passkey\", \"__Secure-myapp.session_token\"\n * - Custom full names: \"my_custom_session_token\", \"custom_session_data\"\n * - No prefix (cookiePrefix=\"\"): matches any cookie with known suffixes\n * - Multiple prefixes: [\"better-auth\", \"my-app\"] matches cookies starting with any of the prefixes\n *\n * @param setCookieHeader - The Set-Cookie header value\n * @param cookiePrefix - The cookie prefix(es) to check for. Can be a string, array of strings, or empty string.\n * @returns true if the header contains better-auth cookies, false otherwise\n */\nexport function hasBetterAuthCookies(\n\tsetCookieHeader: string,\n\tcookiePrefix: string | string[],\n): boolean {\n\tconst cookies = parseSetCookieHeader(setCookieHeader);\n\tconst cookieSuffixes = [\"session_token\", \"session_data\"];\n\tconst prefixes = Array.isArray(cookiePrefix) ? cookiePrefix : [cookiePrefix];\n\n\t// Check if any cookie is a better-auth cookie\n\tfor (const name of cookies.keys()) {\n\t\t// Remove __Secure- prefix if present for comparison\n\t\tconst nameWithoutSecure = stripSecureCookiePrefix(name);\n\n\t\t// Check against all provided prefixes\n\t\tfor (const prefix of prefixes) {\n\t\t\tif (prefix) {\n\t\t\t\t// When prefix is provided, check if cookie starts with the prefix\n\t\t\t\t// This matches all better-auth cookies including session cookies, passkey cookies, etc.\n\t\t\t\tif (nameWithoutSecure.startsWith(prefix)) {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// When prefix is empty, check for common better-auth cookie patterns\n\t\t\t\tfor (const suffix of cookieSuffixes) {\n\t\t\t\t\tif (nameWithoutSecure.endsWith(suffix)) {\n\t\t\t\t\t\treturn true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn false;\n}\n\n/**\n * Expo secure store does not support colons in the keys.\n * This function replaces colons with underscores.\n *\n * @see https://github.com/better-auth/better-auth/issues/5426\n *\n * @param name cookie name to be saved in the storage\n * @returns normalized cookie name\n */\nexport function normalizeCookieName(name: string) {\n\treturn name.replace(/:/g, \"_\");\n}\n\nexport function storageAdapter(storage: {\n\tgetItem: (name: string) => string | null;\n\tsetItem: (name: string, value: string) => void;\n}) {\n\treturn {\n\t\tgetItem: (name: string) => {\n\t\t\treturn storage.getItem(normalizeCookieName(name));\n\t\t},\n\t\tsetItem: (name: string, value: string) => {\n\t\t\treturn storage.setItem(normalizeCookieName(name), value);\n\t\t},\n\t};\n}\n\nexport const expoClient = (opts: ExpoClientOptions) => {\n\tlet store: ClientStore | null = null;\n\tconst storagePrefix = opts?.storagePrefix || \"better-auth\";\n\tconst cookieName = `${storagePrefix}_cookie`;\n\tconst localCacheName = `${storagePrefix}_session_data`;\n\tconst storage = storageAdapter(opts?.storage);\n\tconst isWeb = Platform.OS === \"web\";\n\tconst cookiePrefix = opts?.cookiePrefix || \"better-auth\";\n\n\tconst rawScheme =\n\t\topts?.scheme || Constants.expoConfig?.scheme || Constants.platform?.scheme;\n\tconst scheme = Array.isArray(rawScheme) ? rawScheme[0] : rawScheme;\n\n\tif (!scheme && !isWeb) {\n\t\tthrow new Error(\n\t\t\t\"Scheme not found in app.json. Please provide a scheme in the options.\",\n\t\t);\n\t}\n\treturn {\n\t\tid: \"expo\",\n\t\tgetActions(_, $store) {\n\t\t\tstore = $store;\n\t\t\treturn {\n\t\t\t\t/**\n\t\t\t\t * Get the stored cookie.\n\t\t\t\t *\n\t\t\t\t * You can use this to get the cookie stored in the device and use it in your fetch\n\t\t\t\t * requests.\n\t\t\t\t *\n\t\t\t\t * @example\n\t\t\t\t * ```ts\n\t\t\t\t * const cookie = client.getCookie();\n\t\t\t\t * fetch(\"https://api.example.com\", {\n\t\t\t\t * \theaders: {\n\t\t\t\t * \t\tcookie,\n\t\t\t\t * \t},\n\t\t\t\t * });\n\t\t\t\t */\n\t\t\t\tgetCookie: () => {\n\t\t\t\t\tconst cookie = storage.getItem(cookieName);\n\t\t\t\t\treturn getCookie(cookie || \"{}\");\n\t\t\t\t},\n\t\t\t};\n\t\t},\n\t\tfetchPlugins: [\n\t\t\t{\n\t\t\t\tid: \"expo\",\n\t\t\t\tname: \"Expo\",\n\t\t\t\thooks: {\n\t\t\t\t\tasync onSuccess(context) {\n\t\t\t\t\t\tif (isWeb) return;\n\t\t\t\t\t\tconst setCookie = context.response.headers.get(\"set-cookie\");\n\t\t\t\t\t\tif (setCookie) {\n\t\t\t\t\t\t\t// Only process and notify if the Set-Cookie header contains better-auth cookies\n\t\t\t\t\t\t\t// This prevents infinite refetching when other cookies (like Cloudflare's __cf_bm) are present\n\t\t\t\t\t\t\tif (hasBetterAuthCookies(setCookie, cookiePrefix)) {\n\t\t\t\t\t\t\t\tconst prevCookie = storage.getItem(cookieName);\n\t\t\t\t\t\t\t\tconst toSetCookie = getSetCookie(\n\t\t\t\t\t\t\t\t\tsetCookie || \"\",\n\t\t\t\t\t\t\t\t\tprevCookie ?? undefined,\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t// Only notify $sessionSignal if the session cookie values actually changed\n\t\t\t\t\t\t\t\t// This prevents infinite refetching when the server sends the same cookie with updated expiry\n\t\t\t\t\t\t\t\tif (hasSessionCookieChanged(prevCookie, toSetCookie)) {\n\t\t\t\t\t\t\t\t\tstorage.setItem(cookieName, toSetCookie);\n\t\t\t\t\t\t\t\t\tstore?.notify(\"$sessionSignal\");\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t// Still update the storage to refresh expiry times, but don't trigger refetch\n\t\t\t\t\t\t\t\t\tstorage.setItem(cookieName, toSetCookie);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\tcontext.request.url.toString().includes(\"/get-session\") &&\n\t\t\t\t\t\t\t!opts?.disableCache\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\tconst data = context.data;\n\t\t\t\t\t\t\tstorage.setItem(localCacheName, JSON.stringify(data));\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\tcontext.data?.redirect &&\n\t\t\t\t\t\t\t(context.request.url.toString().includes(\"/sign-in\") ||\n\t\t\t\t\t\t\t\tcontext.request.url.toString().includes(\"/link-social\")) &&\n\t\t\t\t\t\t\t!context.request?.body.includes(\"idToken\") // id token is used for silent sign-in\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\tconst callbackURL = JSON.parse(context.request.body)?.callbackURL;\n\t\t\t\t\t\t\tconst to = callbackURL;\n\t\t\t\t\t\t\tconst signInURL = context.data?.url;\n\t\t\t\t\t\t\tlet Browser: typeof import(\"expo-web-browser\") | undefined =\n\t\t\t\t\t\t\t\tundefined;\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tBrowser = await import(\"expo-web-browser\");\n\t\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\tBrowser = require(\"expo-web-browser\");\n\t\t\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t\t\t\t\t'\"expo-web-browser\" is not installed as a dependency!',\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\tcause: error,\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tif (Platform.OS === \"android\") {\n\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\tBrowser!.dismissAuthSession();\n\t\t\t\t\t\t\t\t} catch {}\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tconst storedCookieJson = storage.getItem(cookieName);\n\t\t\t\t\t\t\tconst oauthStateValue = getOAuthStateValue(\n\t\t\t\t\t\t\t\tstoredCookieJson,\n\t\t\t\t\t\t\t\tcookiePrefix,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\tconst params = new URLSearchParams({\n\t\t\t\t\t\t\t\tauthorizationURL: signInURL,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\tif (oauthStateValue) {\n\t\t\t\t\t\t\t\tparams.append(\"oauthState\", oauthStateValue);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tconst proxyURL = `${context.request.baseURL}/expo-authorization-proxy?${params.toString()}`;\n\t\t\t\t\t\t\tconst result = await Browser!.openAuthSessionAsync(\n\t\t\t\t\t\t\t\tproxyURL,\n\t\t\t\t\t\t\t\tto,\n\t\t\t\t\t\t\t\topts?.webBrowserOptions,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\tif (result.type !== \"success\") return;\n\t\t\t\t\t\t\tconst url = new URL(result.url);\n\t\t\t\t\t\t\tconst cookie = url.searchParams.get(\"cookie\");\n\t\t\t\t\t\t\tif (!cookie) return;\n\t\t\t\t\t\t\tconst prevCookie = storage.getItem(cookieName);\n\t\t\t\t\t\t\tconst toSetCookie = getSetCookie(cookie, prevCookie ?? undefined);\n\t\t\t\t\t\t\tstorage.setItem(cookieName, toSetCookie);\n\t\t\t\t\t\t\tstore?.notify(\"$sessionSignal\");\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tasync init(url, options) {\n\t\t\t\t\tif (isWeb) {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\turl,\n\t\t\t\t\t\t\toptions: options as ClientFetchOption,\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\t\t\t\t\toptions = options || {};\n\t\t\t\t\toptions.credentials = \"omit\";\n\t\t\t\t\t/**\n\t\t\t\t\t * ID token flow (native sign-in) doesn't need cookie-based auth.\n\t\t\t\t\t * The ID token itself is cryptographically signed by the provider\n\t\t\t\t\t * and validated server-side, so no session cookies or origin\n\t\t\t\t\t * validation is required.\n\t\t\t\t\t *\n\t\t\t\t\t * Sending cookie/expo-origin headers for ID token requests triggers\n\t\t\t\t\t * unnecessary origin checks that fail for custom URL schemes.\n\t\t\t\t\t */\n\t\t\t\t\tconst isIdTokenRequest = options.body?.idToken !== undefined;\n\n\t\t\t\t\tif (isIdTokenRequest) {\n\t\t\t\t\t\toptions.headers = {\n\t\t\t\t\t\t\t...options.headers,\n\t\t\t\t\t\t\t\"x-skip-oauth-proxy\": \"true\",\n\t\t\t\t\t\t};\n\t\t\t\t\t} else {\n\t\t\t\t\t\tconst storedCookie = storage.getItem(cookieName);\n\t\t\t\t\t\tconst cookie = getCookie(storedCookie || \"{}\");\n\t\t\t\t\t\toptions.headers = {\n\t\t\t\t\t\t\t...options.headers,\n\t\t\t\t\t\t\t...(cookie ? { cookie } : {}),\n\t\t\t\t\t\t\t\"expo-origin\": getOrigin(scheme!),\n\t\t\t\t\t\t\t\"x-skip-oauth-proxy\": \"true\",\n\t\t\t\t\t\t};\n\t\t\t\t\t\tif (options.body?.callbackURL) {\n\t\t\t\t\t\t\tif (options.body.callbackURL.startsWith(\"/\")) {\n\t\t\t\t\t\t\t\tconst url = Linking.createURL(options.body.callbackURL);\n\t\t\t\t\t\t\t\toptions.body.callbackURL = url;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (options.body?.newUserCallbackURL) {\n\t\t\t\t\t\t\tif (options.body.newUserCallbackURL.startsWith(\"/\")) {\n\t\t\t\t\t\t\t\tconst url = Linking.createURL(options.body.newUserCallbackURL);\n\t\t\t\t\t\t\t\toptions.body.newUserCallbackURL = url;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (options.body?.errorCallbackURL) {\n\t\t\t\t\t\t\tif (options.body.errorCallbackURL.startsWith(\"/\")) {\n\t\t\t\t\t\t\t\tconst url = Linking.createURL(options.body.errorCallbackURL);\n\t\t\t\t\t\t\t\toptions.body.errorCallbackURL = url;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (url.includes(\"/sign-out\")) {\n\t\t\t\t\t\t\tstorage.setItem(cookieName, \"{}\");\n\t\t\t\t\t\t\tstore?.atoms.session?.set({\n\t\t\t\t\t\t\t\t...store.atoms.session.get(),\n\t\t\t\t\t\t\t\tdata: null,\n\t\t\t\t\t\t\t\terror: null,\n\t\t\t\t\t\t\t\tisPending: false,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\tstorage.setItem(localCacheName, \"{}\");\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\treturn {\n\t\t\t\t\t\turl,\n\t\t\t\t\t\toptions: options as ClientFetchOption,\n\t\t\t\t\t};\n\t\t\t\t},\n\t\t\t},\n\t\t],\n\t} satisfies BetterAuthClientPlugin;\n};\n\nexport { parseSetCookieHeader } from \"better-auth/cookies\";\nexport * from \"./focus-manager\";\nexport * from \"./online-manager\";\n"],"mappings":"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"}
@@ -62,4 +62,4 @@ declare const expo: (options?: ExpoOptions | undefined) => {
62
62
  };
63
63
  //#endregion
64
64
  export { ExpoOptions, expo };
65
- //# sourceMappingURL=index.d.mts.map
65
+ //# sourceMappingURL=index.d.ts.map
@@ -76,4 +76,4 @@ const expo = (options) => {
76
76
 
77
77
  //#endregion
78
78
  export { expo };
79
- //# sourceMappingURL=index.mjs.map
79
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/routes.ts","../src/index.ts"],"sourcesContent":["import { HIDE_METADATA } from \"better-auth\";\nimport { APIError, createAuthEndpoint } from \"better-auth/api\";\nimport * as z from \"zod\";\n\nexport const expoAuthorizationProxy = createAuthEndpoint(\n\t\"/expo-authorization-proxy\",\n\t{\n\t\tmethod: \"GET\",\n\t\tquery: z.object({\n\t\t\tauthorizationURL: z.string(),\n\t\t\toauthState: z.string().optional(),\n\t\t}),\n\t\tmetadata: HIDE_METADATA,\n\t},\n\tasync (ctx) => {\n\t\tconst { oauthState } = ctx.query;\n\t\tif (oauthState) {\n\t\t\tconst oauthStateCookie = ctx.context.createAuthCookie(\"oauth_state\", {\n\t\t\t\tmaxAge: 10 * 60, // 10 minutes\n\t\t\t});\n\t\t\tctx.setCookie(\n\t\t\t\toauthStateCookie.name,\n\t\t\t\toauthState,\n\t\t\t\toauthStateCookie.attributes,\n\t\t\t);\n\t\t\treturn ctx.redirect(ctx.query.authorizationURL);\n\t\t}\n\n\t\tconst { authorizationURL } = ctx.query;\n\t\tconst url = new URL(authorizationURL);\n\t\tconst state = url.searchParams.get(\"state\");\n\t\tif (!state) {\n\t\t\tthrow new APIError(\"BAD_REQUEST\", {\n\t\t\t\tmessage: \"Unexpected error\",\n\t\t\t});\n\t\t}\n\t\tconst stateCookie = ctx.context.createAuthCookie(\"state\", {\n\t\t\tmaxAge: 5 * 60, // 5 minutes\n\t\t});\n\t\tawait ctx.setSignedCookie(\n\t\t\tstateCookie.name,\n\t\t\tstate,\n\t\t\tctx.context.secret,\n\t\t\tstateCookie.attributes,\n\t\t);\n\t\treturn ctx.redirect(ctx.query.authorizationURL);\n\t},\n);\n","import type { BetterAuthPlugin } from \"@better-auth/core\";\nimport { createAuthMiddleware } from \"@better-auth/core/api\";\nimport { expoAuthorizationProxy } from \"./routes\";\n\nexport interface ExpoOptions {\n\t/**\n\t * Disable origin override for expo API routes\n\t * When set to true, the origin header will not be overridden for expo API routes\n\t */\n\tdisableOriginOverride?: boolean | undefined;\n}\n\ndeclare module \"@better-auth/core\" {\n\tinterface BetterAuthPluginRegistry<AuthOptions, Options> {\n\t\texpo: {\n\t\t\tcreator: typeof expo;\n\t\t};\n\t}\n}\n\nexport const expo = (options?: ExpoOptions | undefined) => {\n\treturn {\n\t\tid: \"expo\",\n\t\tinit: (ctx) => {\n\t\t\tconst trustedOrigins =\n\t\t\t\tprocess.env.NODE_ENV === \"development\" ? [\"exp://\"] : [];\n\n\t\t\treturn {\n\t\t\t\toptions: {\n\t\t\t\t\ttrustedOrigins,\n\t\t\t\t},\n\t\t\t};\n\t\t},\n\t\tasync onRequest(request, ctx) {\n\t\t\tif (options?.disableOriginOverride || request.headers.get(\"origin\")) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t/**\n\t\t\t * To bypass origin check from expo, we need to set the origin\n\t\t\t * header to the expo-origin header\n\t\t\t */\n\t\t\tconst expoOrigin = request.headers.get(\"expo-origin\");\n\t\t\tif (!expoOrigin) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Construct new Headers with new Request to avoid mutating the original request\n\t\t\tconst newHeaders = new Headers(request.headers);\n\t\t\tnewHeaders.set(\"origin\", expoOrigin);\n\t\t\tconst req = new Request(request, { headers: newHeaders });\n\n\t\t\treturn {\n\t\t\t\trequest: req,\n\t\t\t};\n\t\t},\n\t\thooks: {\n\t\t\tafter: [\n\t\t\t\t{\n\t\t\t\t\tmatcher(context) {\n\t\t\t\t\t\treturn !!(\n\t\t\t\t\t\t\tcontext.path?.startsWith(\"/callback\") ||\n\t\t\t\t\t\t\tcontext.path?.startsWith(\"/oauth2/callback\") ||\n\t\t\t\t\t\t\tcontext.path?.startsWith(\"/magic-link/verify\") ||\n\t\t\t\t\t\t\tcontext.path?.startsWith(\"/verify-email\")\n\t\t\t\t\t\t);\n\t\t\t\t\t},\n\t\t\t\t\thandler: createAuthMiddleware(async (ctx) => {\n\t\t\t\t\t\tconst headers = ctx.context.responseHeaders;\n\t\t\t\t\t\tconst location = headers?.get(\"location\");\n\t\t\t\t\t\tif (!location) {\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst isProxyURL = location.includes(\"/oauth-proxy-callback\");\n\t\t\t\t\t\tif (isProxyURL) {\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tlet redirectURL: URL;\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tredirectURL = new URL(location);\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst isHttpRedirect =\n\t\t\t\t\t\t\tredirectURL.protocol === \"http:\" ||\n\t\t\t\t\t\t\tredirectURL.protocol === \"https:\";\n\t\t\t\t\t\tif (isHttpRedirect) {\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst isTrustedOrigin = ctx.context.isTrustedOrigin(location);\n\t\t\t\t\t\tif (!isTrustedOrigin) {\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst cookie = headers?.get(\"set-cookie\");\n\t\t\t\t\t\tif (!cookie) {\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tredirectURL.searchParams.set(\"cookie\", cookie);\n\t\t\t\t\t\tctx.setHeader(\"location\", redirectURL.toString());\n\t\t\t\t\t}),\n\t\t\t\t},\n\t\t\t],\n\t\t},\n\t\tendpoints: {\n\t\t\texpoAuthorizationProxy,\n\t\t},\n\t\toptions,\n\t} satisfies BetterAuthPlugin;\n};\n"],"mappings":";;;;;;AAIA,MAAa,yBAAyB,mBACrC,6BACA;CACC,QAAQ;CACR,OAAO,EAAE,OAAO;EACf,kBAAkB,EAAE,QAAQ;EAC5B,YAAY,EAAE,QAAQ,CAAC,UAAU;EACjC,CAAC;CACF,UAAU;CACV,EACD,OAAO,QAAQ;CACd,MAAM,EAAE,eAAe,IAAI;AAC3B,KAAI,YAAY;EACf,MAAM,mBAAmB,IAAI,QAAQ,iBAAiB,eAAe,EACpE,QAAQ,KACR,CAAC;AACF,MAAI,UACH,iBAAiB,MACjB,YACA,iBAAiB,WACjB;AACD,SAAO,IAAI,SAAS,IAAI,MAAM,iBAAiB;;CAGhD,MAAM,EAAE,qBAAqB,IAAI;CAEjC,MAAM,QADM,IAAI,IAAI,iBAAiB,CACnB,aAAa,IAAI,QAAQ;AAC3C,KAAI,CAAC,MACJ,OAAM,IAAI,SAAS,eAAe,EACjC,SAAS,oBACT,CAAC;CAEH,MAAM,cAAc,IAAI,QAAQ,iBAAiB,SAAS,EACzD,QAAQ,KACR,CAAC;AACF,OAAM,IAAI,gBACT,YAAY,MACZ,OACA,IAAI,QAAQ,QACZ,YAAY,WACZ;AACD,QAAO,IAAI,SAAS,IAAI,MAAM,iBAAiB;EAEhD;;;;AC3BD,MAAa,QAAQ,YAAsC;AAC1D,QAAO;EACN,IAAI;EACJ,OAAO,QAAQ;AAId,UAAO,EACN,SAAS,EACR,gBAJD,QAAQ,IAAI,aAAa,gBAAgB,CAAC,SAAS,GAAG,EAAE,EAKvD,EACD;;EAEF,MAAM,UAAU,SAAS,KAAK;AAC7B,OAAI,SAAS,yBAAyB,QAAQ,QAAQ,IAAI,SAAS,CAClE;;;;;GAMD,MAAM,aAAa,QAAQ,QAAQ,IAAI,cAAc;AACrD,OAAI,CAAC,WACJ;GAID,MAAM,aAAa,IAAI,QAAQ,QAAQ,QAAQ;AAC/C,cAAW,IAAI,UAAU,WAAW;AAGpC,UAAO,EACN,SAHW,IAAI,QAAQ,SAAS,EAAE,SAAS,YAAY,CAAC,EAIxD;;EAEF,OAAO,EACN,OAAO,CACN;GACC,QAAQ,SAAS;AAChB,WAAO,CAAC,EACP,QAAQ,MAAM,WAAW,YAAY,IACrC,QAAQ,MAAM,WAAW,mBAAmB,IAC5C,QAAQ,MAAM,WAAW,qBAAqB,IAC9C,QAAQ,MAAM,WAAW,gBAAgB;;GAG3C,SAAS,qBAAqB,OAAO,QAAQ;IAC5C,MAAM,UAAU,IAAI,QAAQ;IAC5B,MAAM,WAAW,SAAS,IAAI,WAAW;AACzC,QAAI,CAAC,SACJ;AAGD,QADmB,SAAS,SAAS,wBAAwB,CAE5D;IAED,IAAI;AACJ,QAAI;AACH,mBAAc,IAAI,IAAI,SAAS;YACxB;AACP;;AAKD,QAFC,YAAY,aAAa,WACzB,YAAY,aAAa,SAEzB;AAGD,QAAI,CADoB,IAAI,QAAQ,gBAAgB,SAAS,CAE5D;IAED,MAAM,SAAS,SAAS,IAAI,aAAa;AACzC,QAAI,CAAC,OACJ;AAED,gBAAY,aAAa,IAAI,UAAU,OAAO;AAC9C,QAAI,UAAU,YAAY,YAAY,UAAU,CAAC;KAChD;GACF,CACD,EACD;EACD,WAAW,EACV,wBACA;EACD;EACA"}
@@ -48,4 +48,4 @@ declare const lastLoginMethodClient: (config: LastLoginMethodClientConfig) => {
48
48
  };
49
49
  //#endregion
50
50
  export { LastLoginMethodClientConfig, lastLoginMethodClient };
51
- //# sourceMappingURL=index.d.mts.map
51
+ //# sourceMappingURL=index.d.ts.map
@@ -44,4 +44,4 @@ const lastLoginMethodClient = (config) => {
44
44
 
45
45
  //#endregion
46
46
  export { lastLoginMethodClient };
47
- //# sourceMappingURL=index.mjs.map
47
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/plugins/last-login-method.ts"],"sourcesContent":["import type { Awaitable, BetterAuthClientPlugin } from \"@better-auth/core\";\n\nexport interface LastLoginMethodClientConfig {\n\tstorage: {\n\t\tsetItem: (key: string, value: string) => any;\n\t\tgetItem: (key: string) => string | null;\n\t\tdeleteItemAsync: (key: string) => Awaitable<void>;\n\t};\n\t/**\n\t * Prefix for local storage keys (e.g., \"my-app_last_login_method\")\n\t * @default \"better-auth\"\n\t */\n\tstoragePrefix?: string | undefined;\n\t/**\n\t * Custom resolve method for retrieving the last login method\n\t */\n\tcustomResolveMethod?:\n\t\t| ((url: string | URL) => Awaitable<string | undefined | null>)\n\t\t| undefined;\n}\n\nconst paths = [\n\t\"/callback/\",\n\t\"/oauth2/callback/\",\n\t\"/sign-in/email\",\n\t\"/sign-up/email\",\n];\nconst defaultResolveMethod = (url: string | URL) => {\n\tconst { pathname } = new URL(url.toString(), \"http://localhost\");\n\n\tif (paths.some((p) => pathname.includes(p))) {\n\t\treturn pathname.split(\"/\").pop();\n\t}\n\tif (pathname.includes(\"siwe\")) return \"siwe\";\n\tif (pathname.includes(\"/passkey/verify-authentication\")) {\n\t\treturn \"passkey\";\n\t}\n\n\treturn;\n};\n\nexport const lastLoginMethodClient = (config: LastLoginMethodClientConfig) => {\n\tconst resolveMethod = config.customResolveMethod || defaultResolveMethod;\n\tconst storagePrefix = config.storagePrefix || \"better-auth\";\n\tconst lastLoginMethodName = `${storagePrefix}_last_login_method`;\n\tconst storage = config.storage;\n\n\treturn {\n\t\tid: \"last-login-method-expo\",\n\t\tfetchPlugins: [\n\t\t\t{\n\t\t\t\tid: \"last-login-method-expo\",\n\t\t\t\tname: \"Last Login Method\",\n\t\t\t\thooks: {\n\t\t\t\t\tonResponse: async (ctx) => {\n\t\t\t\t\t\tconst lastMethod = await resolveMethod(ctx.request.url);\n\t\t\t\t\t\tif (!lastMethod) {\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tawait storage.setItem(lastLoginMethodName, lastMethod);\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t],\n\t\tgetActions() {\n\t\t\treturn {\n\t\t\t\t/**\n\t\t\t\t * Get the last used login method from storage\n\t\t\t\t *\n\t\t\t\t * @returns The last used login method or null if not found\n\t\t\t\t */\n\t\t\t\tgetLastUsedLoginMethod: (): string | null => {\n\t\t\t\t\treturn storage.getItem(lastLoginMethodName);\n\t\t\t\t},\n\t\t\t\t/**\n\t\t\t\t * Clear the last used login method from storage\n\t\t\t\t */\n\t\t\t\tclearLastUsedLoginMethod: async () => {\n\t\t\t\t\tawait storage.deleteItemAsync(lastLoginMethodName);\n\t\t\t\t},\n\t\t\t\t/**\n\t\t\t\t * Check if a specific login method was the last used\n\t\t\t\t * @param method The method to check\n\t\t\t\t * @returns True if the method was the last used, false otherwise\n\t\t\t\t */\n\t\t\t\tisLastUsedLoginMethod: (method: string): boolean => {\n\t\t\t\t\tconst lastMethod = storage.getItem(lastLoginMethodName);\n\t\t\t\t\treturn lastMethod === method;\n\t\t\t\t},\n\t\t\t};\n\t\t},\n\t} satisfies BetterAuthClientPlugin;\n};\n"],"mappings":";AAqBA,MAAM,QAAQ;CACb;CACA;CACA;CACA;CACA;AACD,MAAM,wBAAwB,QAAsB;CACnD,MAAM,EAAE,aAAa,IAAI,IAAI,IAAI,UAAU,EAAE,mBAAmB;AAEhE,KAAI,MAAM,MAAM,MAAM,SAAS,SAAS,EAAE,CAAC,CAC1C,QAAO,SAAS,MAAM,IAAI,CAAC,KAAK;AAEjC,KAAI,SAAS,SAAS,OAAO,CAAE,QAAO;AACtC,KAAI,SAAS,SAAS,iCAAiC,CACtD,QAAO;;AAMT,MAAa,yBAAyB,WAAwC;CAC7E,MAAM,gBAAgB,OAAO,uBAAuB;CAEpD,MAAM,sBAAsB,GADN,OAAO,iBAAiB,cACD;CAC7C,MAAM,UAAU,OAAO;AAEvB,QAAO;EACN,IAAI;EACJ,cAAc,CACb;GACC,IAAI;GACJ,MAAM;GACN,OAAO,EACN,YAAY,OAAO,QAAQ;IAC1B,MAAM,aAAa,MAAM,cAAc,IAAI,QAAQ,IAAI;AACvD,QAAI,CAAC,WACJ;AAGD,UAAM,QAAQ,QAAQ,qBAAqB,WAAW;MAEvD;GACD,CACD;EACD,aAAa;AACZ,UAAO;IAMN,8BAA6C;AAC5C,YAAO,QAAQ,QAAQ,oBAAoB;;IAK5C,0BAA0B,YAAY;AACrC,WAAM,QAAQ,gBAAgB,oBAAoB;;IAOnD,wBAAwB,WAA4B;AAEnD,YADmB,QAAQ,QAAQ,oBAAoB,KACjC;;IAEvB;;EAEF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@better-auth/expo",
3
- "version": "1.5.0",
3
+ "version": "1.5.1",
4
4
  "description": "Better Auth integration for Expo and React Native applications.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -23,36 +23,36 @@
23
23
  "files": [
24
24
  "dist"
25
25
  ],
26
- "main": "./dist/index.mjs",
27
- "module": "./dist/index.mjs",
28
- "types": "./dist/index.d.mts",
26
+ "main": "./dist/index.js",
27
+ "module": "./dist/index.js",
28
+ "types": "./dist/index.d.ts",
29
29
  "exports": {
30
30
  ".": {
31
31
  "dev-source": "./src/index.ts",
32
- "types": "./dist/index.d.mts",
33
- "default": "./dist/index.mjs"
32
+ "types": "./dist/index.d.ts",
33
+ "default": "./dist/index.js"
34
34
  },
35
35
  "./client": {
36
36
  "dev-source": "./src/client.ts",
37
- "types": "./dist/client.d.mts",
38
- "default": "./dist/client.mjs"
37
+ "types": "./dist/client.d.ts",
38
+ "default": "./dist/client.js"
39
39
  },
40
40
  "./plugins": {
41
41
  "dev-source": "./src/plugins/index.ts",
42
- "types": "./dist/plugins/index.d.mts",
43
- "default": "./dist/plugins/index.mjs"
42
+ "types": "./dist/plugins/index.d.ts",
43
+ "default": "./dist/plugins/index.js"
44
44
  }
45
45
  },
46
46
  "typesVersions": {
47
47
  "*": {
48
48
  "*": [
49
- "./dist/index.d.mts"
49
+ "./dist/index.d.ts"
50
50
  ],
51
51
  "client": [
52
- "./dist/client.d.mts"
52
+ "./dist/client.d.ts"
53
53
  ],
54
54
  "plugins": [
55
- "./dist/plugins/index.d.mts"
55
+ "./dist/plugins/index.d.ts"
56
56
  ]
57
57
  }
58
58
  },
@@ -68,17 +68,17 @@
68
68
  "expo-network": "~55.0.8",
69
69
  "expo-web-browser": "~55.0.9",
70
70
  "react-native": "~0.84.1",
71
- "tsdown": "^0.20.3",
72
- "@better-auth/core": "1.5.0",
73
- "better-auth": "1.5.0"
71
+ "tsdown": "0.21.0-beta.2",
72
+ "@better-auth/core": "1.5.1",
73
+ "better-auth": "1.5.1"
74
74
  },
75
75
  "peerDependencies": {
76
76
  "expo-constants": ">=17.0.0",
77
77
  "expo-linking": ">=7.0.0",
78
78
  "expo-network": ">=8.0.7",
79
79
  "expo-web-browser": ">=14.0.0",
80
- "@better-auth/core": "1.5.0",
81
- "better-auth": "1.5.0"
80
+ "@better-auth/core": "1.5.1",
81
+ "better-auth": "1.5.1"
82
82
  },
83
83
  "peerDependenciesMeta": {
84
84
  "expo-constants": {
@@ -1 +0,0 @@
1
- {"version":3,"file":"client.mjs","names":["parseSetCookieHeader"],"sources":["../src/focus-manager.ts","../src/online-manager.ts","../src/client.ts"],"sourcesContent":["import type { FocusListener, FocusManager } from \"better-auth/client\";\nimport { kFocusManager } from \"better-auth/client\";\nimport type { AppStateStatus } from \"react-native\";\nimport { AppState } from \"react-native\";\n\nclass ExpoFocusManager implements FocusManager {\n\tlisteners = new Set<FocusListener>();\n\tsubscription?: ReturnType<typeof AppState.addEventListener>;\n\tisFocused: boolean | undefined;\n\n\tsubscribe(listener: FocusListener) {\n\t\tthis.listeners.add(listener);\n\t\treturn () => {\n\t\t\tthis.listeners.delete(listener);\n\t\t};\n\t}\n\n\tsetFocused(focused: boolean) {\n\t\tif (this.isFocused === focused) return;\n\t\tthis.isFocused = focused;\n\t\tthis.listeners.forEach((listener) => listener(focused));\n\t}\n\n\tsetup() {\n\t\tthis.subscription = AppState.addEventListener(\n\t\t\t\"change\",\n\t\t\t(state: AppStateStatus) => {\n\t\t\t\tthis.setFocused(state === \"active\");\n\t\t\t},\n\t\t);\n\n\t\treturn () => {\n\t\t\tthis.subscription?.remove();\n\t\t};\n\t}\n}\n\nexport function setupExpoFocusManager() {\n\tif (!(globalThis as any)[kFocusManager]) {\n\t\t(globalThis as any)[kFocusManager] = new ExpoFocusManager();\n\t}\n\treturn (globalThis as any)[kFocusManager] as FocusManager;\n}\n","import type { OnlineListener, OnlineManager } from \"better-auth/client\";\nimport { kOnlineManager } from \"better-auth/client\";\n\nclass ExpoOnlineManager implements OnlineManager {\n\tlisteners = new Set<OnlineListener>();\n\tisOnline = true;\n\tunsubscribe?: () => void;\n\n\tsubscribe(listener: OnlineListener) {\n\t\tthis.listeners.add(listener);\n\t\treturn () => {\n\t\t\tthis.listeners.delete(listener);\n\t\t};\n\t}\n\n\tsetOnline(online: boolean) {\n\t\tif (this.isOnline === online) return;\n\t\tthis.isOnline = online;\n\t\tthis.listeners.forEach((listener) => listener(online));\n\t}\n\n\tsetup() {\n\t\timport(\"expo-network\")\n\t\t\t.then(({ addNetworkStateListener }) => {\n\t\t\t\tconst subscription = addNetworkStateListener((state) => {\n\t\t\t\t\tthis.setOnline(!!state.isInternetReachable);\n\t\t\t\t});\n\t\t\t\tthis.unsubscribe = () => subscription.remove();\n\t\t\t})\n\t\t\t.catch(() => {\n\t\t\t\t// fallback to always online\n\t\t\t\tthis.setOnline(true);\n\t\t\t});\n\n\t\treturn () => {\n\t\t\tthis.unsubscribe?.();\n\t\t};\n\t}\n}\n\nexport function setupExpoOnlineManager() {\n\tif (!(globalThis as any)[kOnlineManager]) {\n\t\t(globalThis as any)[kOnlineManager] = new ExpoOnlineManager();\n\t}\n\treturn (globalThis as any)[kOnlineManager] as OnlineManager;\n}\n","import type {\n\tBetterAuthClientPlugin,\n\tClientFetchOption,\n\tClientStore,\n} from \"@better-auth/core\";\nimport { safeJSONParse } from \"@better-auth/core/utils/json\";\nimport {\n\tparseSetCookieHeader,\n\tSECURE_COOKIE_PREFIX,\n\tstripSecureCookiePrefix,\n} from \"better-auth/cookies\";\nimport Constants from \"expo-constants\";\nimport * as Linking from \"expo-linking\";\nimport { Platform } from \"react-native\";\nimport { setupExpoFocusManager } from \"./focus-manager\";\nimport { setupExpoOnlineManager } from \"./online-manager\";\n\nif (Platform.OS !== \"web\") {\n\tsetupExpoFocusManager();\n\tsetupExpoOnlineManager();\n}\n\ninterface ExpoClientOptions {\n\tscheme?: string | undefined;\n\tstorage: {\n\t\tsetItem: (key: string, value: string) => any;\n\t\tgetItem: (key: string) => string | null;\n\t};\n\t/**\n\t * Prefix for local storage keys (e.g., \"my-app_cookie\", \"my-app_session_data\")\n\t * @default \"better-auth\"\n\t */\n\tstoragePrefix?: string | undefined;\n\t/**\n\t * Prefix(es) for server cookie names to filter (e.g., \"better-auth.session_token\")\n\t * This is used to identify which cookies belong to better-auth to prevent\n\t * infinite refetching when third-party cookies are set.\n\t * Can be a single string or an array of strings to match multiple prefixes.\n\t * @default \"better-auth\"\n\t * @example \"better-auth\"\n\t * @example [\"better-auth\", \"my-app\"]\n\t */\n\tcookiePrefix?: string | string[] | undefined;\n\tdisableCache?: boolean | undefined;\n\t/**\n\t * Options to customize the Expo web browser behavior when opening authentication\n\t * sessions. These are passed directly to `expo-web-browser`'s\n\t * `Browser.openBrowserAsync`.\n\t *\n\t * For example, on iOS you can use `{ preferEphemeralSession: true }` to prevent\n\t * the authentication session from sharing cookies with the user's default\n\t * browser session:\n\t *\n\t * ```ts\n\t * const client = createClient({\n\t * expo: {\n\t * webBrowserOptions: {\n\t * preferEphemeralSession: true,\n\t * },\n\t * },\n\t * });\n\t * ```\n\t */\n\twebBrowserOptions?: import(\"expo-web-browser\").AuthSessionOpenOptions;\n}\n\ninterface StoredCookie {\n\tvalue: string;\n\texpires: string | null;\n}\n\nexport function getSetCookie(header: string, prevCookie?: string | undefined) {\n\tconst parsed = parseSetCookieHeader(header);\n\tconst toSetCookie =\n\t\tsafeJSONParse<Record<string, StoredCookie>>(prevCookie) ?? {};\n\tparsed.forEach((cookie, key) => {\n\t\tconst expiresAt = cookie[\"expires\"];\n\t\tconst maxAge = cookie[\"max-age\"];\n\t\tif (maxAge !== undefined && Number(maxAge) <= 0) {\n\t\t\tdelete toSetCookie[key];\n\t\t\treturn;\n\t\t}\n\t\tconst expires = maxAge\n\t\t\t? new Date(Date.now() + Number(maxAge) * 1000)\n\t\t\t: expiresAt\n\t\t\t\t? new Date(String(expiresAt))\n\t\t\t\t: null;\n\t\tif (expires && expires.getTime() <= Date.now()) {\n\t\t\tdelete toSetCookie[key];\n\t\t\treturn;\n\t\t}\n\t\ttoSetCookie[key] = {\n\t\t\tvalue: cookie[\"value\"],\n\t\t\texpires: expires ? expires.toISOString() : null,\n\t\t};\n\t});\n\treturn JSON.stringify(toSetCookie);\n}\n\nexport function getCookie(cookie: string) {\n\tlet parsed = {} as Record<string, StoredCookie>;\n\ttry {\n\t\tparsed = JSON.parse(cookie) as Record<string, StoredCookie>;\n\t} catch {}\n\tconst toSend = Object.entries(parsed).reduce((acc, [key, value]) => {\n\t\tif (value.expires && new Date(value.expires) < new Date()) {\n\t\t\treturn acc;\n\t\t}\n\t\treturn acc ? `${acc}; ${key}=${value.value}` : `${key}=${value.value}`;\n\t}, \"\");\n\treturn toSend;\n}\n\nfunction getOAuthStateValue(\n\tcookieJson: string | null,\n\tcookiePrefix: string | string[],\n): string | null {\n\tif (!cookieJson) return null;\n\n\tconst parsed = safeJSONParse<Record<string, StoredCookie>>(cookieJson);\n\tif (!parsed) return null;\n\n\tconst prefixes = Array.isArray(cookiePrefix) ? cookiePrefix : [cookiePrefix];\n\n\tfor (const prefix of prefixes) {\n\t\t// cookie strategy uses: <prefix>.oauth_state\n\t\tconst candidates = [\n\t\t\t`${SECURE_COOKIE_PREFIX}${prefix}.oauth_state`,\n\t\t\t`${prefix}.oauth_state`,\n\t\t];\n\n\t\tfor (const name of candidates) {\n\t\t\tconst value = parsed?.[name]?.value;\n\t\t\tif (value) return value;\n\t\t}\n\t}\n\n\treturn null;\n}\n\nfunction getOrigin(scheme: string) {\n\tconst schemeURI = Linking.createURL(\"\", { scheme });\n\treturn schemeURI;\n}\n\n/**\n * Compare if session cookies have actually changed by comparing their values.\n * Ignores expiry timestamps that naturally change on each request.\n *\n * @param prevCookie - Previous cookie JSON string\n * @param newCookie - New cookie JSON string\n * @returns true if session cookies have changed, false otherwise\n */\nfunction hasSessionCookieChanged(\n\tprevCookie: string | null,\n\tnewCookie: string,\n): boolean {\n\tif (!prevCookie) return true;\n\n\ttry {\n\t\tconst prev = JSON.parse(prevCookie) as Record<string, StoredCookie>;\n\t\tconst next = JSON.parse(newCookie) as Record<string, StoredCookie>;\n\n\t\t// Get all session-related cookie keys (session_token, session_data)\n\t\tconst sessionKeys = new Set<string>();\n\t\tObject.keys(prev).forEach((key) => {\n\t\t\tif (key.includes(\"session_token\") || key.includes(\"session_data\")) {\n\t\t\t\tsessionKeys.add(key);\n\t\t\t}\n\t\t});\n\t\tObject.keys(next).forEach((key) => {\n\t\t\tif (key.includes(\"session_token\") || key.includes(\"session_data\")) {\n\t\t\t\tsessionKeys.add(key);\n\t\t\t}\n\t\t});\n\n\t\t// Compare the values of session cookies (ignore expires timestamps)\n\t\tfor (const key of sessionKeys) {\n\t\t\tconst prevValue = prev[key]?.value;\n\t\t\tconst nextValue = next[key]?.value;\n\t\t\tif (prevValue !== nextValue) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\n\t\treturn false;\n\t} catch {\n\t\t// If parsing fails, assume cookie changed\n\t\treturn true;\n\t}\n}\n\n/**\n * Check if the Set-Cookie header contains better-auth cookies.\n * This prevents infinite refetching when non-better-auth cookies (like third-party cookies) change.\n *\n * Supports multiple cookie naming patterns:\n * - Default: \"better-auth.session_token\", \"better-auth-passkey\", \"__Secure-better-auth.session_token\"\n * - Custom prefix: \"myapp.session_token\", \"myapp-passkey\", \"__Secure-myapp.session_token\"\n * - Custom full names: \"my_custom_session_token\", \"custom_session_data\"\n * - No prefix (cookiePrefix=\"\"): matches any cookie with known suffixes\n * - Multiple prefixes: [\"better-auth\", \"my-app\"] matches cookies starting with any of the prefixes\n *\n * @param setCookieHeader - The Set-Cookie header value\n * @param cookiePrefix - The cookie prefix(es) to check for. Can be a string, array of strings, or empty string.\n * @returns true if the header contains better-auth cookies, false otherwise\n */\nexport function hasBetterAuthCookies(\n\tsetCookieHeader: string,\n\tcookiePrefix: string | string[],\n): boolean {\n\tconst cookies = parseSetCookieHeader(setCookieHeader);\n\tconst cookieSuffixes = [\"session_token\", \"session_data\"];\n\tconst prefixes = Array.isArray(cookiePrefix) ? cookiePrefix : [cookiePrefix];\n\n\t// Check if any cookie is a better-auth cookie\n\tfor (const name of cookies.keys()) {\n\t\t// Remove __Secure- prefix if present for comparison\n\t\tconst nameWithoutSecure = stripSecureCookiePrefix(name);\n\n\t\t// Check against all provided prefixes\n\t\tfor (const prefix of prefixes) {\n\t\t\tif (prefix) {\n\t\t\t\t// When prefix is provided, check if cookie starts with the prefix\n\t\t\t\t// This matches all better-auth cookies including session cookies, passkey cookies, etc.\n\t\t\t\tif (nameWithoutSecure.startsWith(prefix)) {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// When prefix is empty, check for common better-auth cookie patterns\n\t\t\t\tfor (const suffix of cookieSuffixes) {\n\t\t\t\t\tif (nameWithoutSecure.endsWith(suffix)) {\n\t\t\t\t\t\treturn true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn false;\n}\n\n/**\n * Expo secure store does not support colons in the keys.\n * This function replaces colons with underscores.\n *\n * @see https://github.com/better-auth/better-auth/issues/5426\n *\n * @param name cookie name to be saved in the storage\n * @returns normalized cookie name\n */\nexport function normalizeCookieName(name: string) {\n\treturn name.replace(/:/g, \"_\");\n}\n\nexport function storageAdapter(storage: {\n\tgetItem: (name: string) => string | null;\n\tsetItem: (name: string, value: string) => void;\n}) {\n\treturn {\n\t\tgetItem: (name: string) => {\n\t\t\treturn storage.getItem(normalizeCookieName(name));\n\t\t},\n\t\tsetItem: (name: string, value: string) => {\n\t\t\treturn storage.setItem(normalizeCookieName(name), value);\n\t\t},\n\t};\n}\n\nexport const expoClient = (opts: ExpoClientOptions) => {\n\tlet store: ClientStore | null = null;\n\tconst storagePrefix = opts?.storagePrefix || \"better-auth\";\n\tconst cookieName = `${storagePrefix}_cookie`;\n\tconst localCacheName = `${storagePrefix}_session_data`;\n\tconst storage = storageAdapter(opts?.storage);\n\tconst isWeb = Platform.OS === \"web\";\n\tconst cookiePrefix = opts?.cookiePrefix || \"better-auth\";\n\n\tconst rawScheme =\n\t\topts?.scheme || Constants.expoConfig?.scheme || Constants.platform?.scheme;\n\tconst scheme = Array.isArray(rawScheme) ? rawScheme[0] : rawScheme;\n\n\tif (!scheme && !isWeb) {\n\t\tthrow new Error(\n\t\t\t\"Scheme not found in app.json. Please provide a scheme in the options.\",\n\t\t);\n\t}\n\treturn {\n\t\tid: \"expo\",\n\t\tgetActions(_, $store) {\n\t\t\tstore = $store;\n\t\t\treturn {\n\t\t\t\t/**\n\t\t\t\t * Get the stored cookie.\n\t\t\t\t *\n\t\t\t\t * You can use this to get the cookie stored in the device and use it in your fetch\n\t\t\t\t * requests.\n\t\t\t\t *\n\t\t\t\t * @example\n\t\t\t\t * ```ts\n\t\t\t\t * const cookie = client.getCookie();\n\t\t\t\t * fetch(\"https://api.example.com\", {\n\t\t\t\t * \theaders: {\n\t\t\t\t * \t\tcookie,\n\t\t\t\t * \t},\n\t\t\t\t * });\n\t\t\t\t */\n\t\t\t\tgetCookie: () => {\n\t\t\t\t\tconst cookie = storage.getItem(cookieName);\n\t\t\t\t\treturn getCookie(cookie || \"{}\");\n\t\t\t\t},\n\t\t\t};\n\t\t},\n\t\tfetchPlugins: [\n\t\t\t{\n\t\t\t\tid: \"expo\",\n\t\t\t\tname: \"Expo\",\n\t\t\t\thooks: {\n\t\t\t\t\tasync onSuccess(context) {\n\t\t\t\t\t\tif (isWeb) return;\n\t\t\t\t\t\tconst setCookie = context.response.headers.get(\"set-cookie\");\n\t\t\t\t\t\tif (setCookie) {\n\t\t\t\t\t\t\t// Only process and notify if the Set-Cookie header contains better-auth cookies\n\t\t\t\t\t\t\t// This prevents infinite refetching when other cookies (like Cloudflare's __cf_bm) are present\n\t\t\t\t\t\t\tif (hasBetterAuthCookies(setCookie, cookiePrefix)) {\n\t\t\t\t\t\t\t\tconst prevCookie = storage.getItem(cookieName);\n\t\t\t\t\t\t\t\tconst toSetCookie = getSetCookie(\n\t\t\t\t\t\t\t\t\tsetCookie || \"\",\n\t\t\t\t\t\t\t\t\tprevCookie ?? undefined,\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t// Only notify $sessionSignal if the session cookie values actually changed\n\t\t\t\t\t\t\t\t// This prevents infinite refetching when the server sends the same cookie with updated expiry\n\t\t\t\t\t\t\t\tif (hasSessionCookieChanged(prevCookie, toSetCookie)) {\n\t\t\t\t\t\t\t\t\tstorage.setItem(cookieName, toSetCookie);\n\t\t\t\t\t\t\t\t\tstore?.notify(\"$sessionSignal\");\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t// Still update the storage to refresh expiry times, but don't trigger refetch\n\t\t\t\t\t\t\t\t\tstorage.setItem(cookieName, toSetCookie);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\tcontext.request.url.toString().includes(\"/get-session\") &&\n\t\t\t\t\t\t\t!opts?.disableCache\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\tconst data = context.data;\n\t\t\t\t\t\t\tstorage.setItem(localCacheName, JSON.stringify(data));\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\tcontext.data?.redirect &&\n\t\t\t\t\t\t\t(context.request.url.toString().includes(\"/sign-in\") ||\n\t\t\t\t\t\t\t\tcontext.request.url.toString().includes(\"/link-social\")) &&\n\t\t\t\t\t\t\t!context.request?.body.includes(\"idToken\") // id token is used for silent sign-in\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\tconst callbackURL = JSON.parse(context.request.body)?.callbackURL;\n\t\t\t\t\t\t\tconst to = callbackURL;\n\t\t\t\t\t\t\tconst signInURL = context.data?.url;\n\t\t\t\t\t\t\tlet Browser: typeof import(\"expo-web-browser\") | undefined =\n\t\t\t\t\t\t\t\tundefined;\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tBrowser = await import(\"expo-web-browser\");\n\t\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\tBrowser = require(\"expo-web-browser\");\n\t\t\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t\t\t\t\t'\"expo-web-browser\" is not installed as a dependency!',\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\tcause: error,\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tif (Platform.OS === \"android\") {\n\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\tBrowser!.dismissAuthSession();\n\t\t\t\t\t\t\t\t} catch {}\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tconst storedCookieJson = storage.getItem(cookieName);\n\t\t\t\t\t\t\tconst oauthStateValue = getOAuthStateValue(\n\t\t\t\t\t\t\t\tstoredCookieJson,\n\t\t\t\t\t\t\t\tcookiePrefix,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\tconst params = new URLSearchParams({\n\t\t\t\t\t\t\t\tauthorizationURL: signInURL,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\tif (oauthStateValue) {\n\t\t\t\t\t\t\t\tparams.append(\"oauthState\", oauthStateValue);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tconst proxyURL = `${context.request.baseURL}/expo-authorization-proxy?${params.toString()}`;\n\t\t\t\t\t\t\tconst result = await Browser!.openAuthSessionAsync(\n\t\t\t\t\t\t\t\tproxyURL,\n\t\t\t\t\t\t\t\tto,\n\t\t\t\t\t\t\t\topts?.webBrowserOptions,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\tif (result.type !== \"success\") return;\n\t\t\t\t\t\t\tconst url = new URL(result.url);\n\t\t\t\t\t\t\tconst cookie = url.searchParams.get(\"cookie\");\n\t\t\t\t\t\t\tif (!cookie) return;\n\t\t\t\t\t\t\tconst prevCookie = storage.getItem(cookieName);\n\t\t\t\t\t\t\tconst toSetCookie = getSetCookie(cookie, prevCookie ?? undefined);\n\t\t\t\t\t\t\tstorage.setItem(cookieName, toSetCookie);\n\t\t\t\t\t\t\tstore?.notify(\"$sessionSignal\");\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tasync init(url, options) {\n\t\t\t\t\tif (isWeb) {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\turl,\n\t\t\t\t\t\t\toptions: options as ClientFetchOption,\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\t\t\t\t\toptions = options || {};\n\t\t\t\t\toptions.credentials = \"omit\";\n\t\t\t\t\t/**\n\t\t\t\t\t * ID token flow (native sign-in) doesn't need cookie-based auth.\n\t\t\t\t\t * The ID token itself is cryptographically signed by the provider\n\t\t\t\t\t * and validated server-side, so no session cookies or origin\n\t\t\t\t\t * validation is required.\n\t\t\t\t\t *\n\t\t\t\t\t * Sending cookie/expo-origin headers for ID token requests triggers\n\t\t\t\t\t * unnecessary origin checks that fail for custom URL schemes.\n\t\t\t\t\t */\n\t\t\t\t\tconst isIdTokenRequest = options.body?.idToken !== undefined;\n\n\t\t\t\t\tif (isIdTokenRequest) {\n\t\t\t\t\t\toptions.headers = {\n\t\t\t\t\t\t\t...options.headers,\n\t\t\t\t\t\t\t\"x-skip-oauth-proxy\": \"true\",\n\t\t\t\t\t\t};\n\t\t\t\t\t} else {\n\t\t\t\t\t\tconst storedCookie = storage.getItem(cookieName);\n\t\t\t\t\t\tconst cookie = getCookie(storedCookie || \"{}\");\n\t\t\t\t\t\toptions.headers = {\n\t\t\t\t\t\t\t...options.headers,\n\t\t\t\t\t\t\t...(cookie ? { cookie } : {}),\n\t\t\t\t\t\t\t\"expo-origin\": getOrigin(scheme!),\n\t\t\t\t\t\t\t\"x-skip-oauth-proxy\": \"true\",\n\t\t\t\t\t\t};\n\t\t\t\t\t\tif (options.body?.callbackURL) {\n\t\t\t\t\t\t\tif (options.body.callbackURL.startsWith(\"/\")) {\n\t\t\t\t\t\t\t\tconst url = Linking.createURL(options.body.callbackURL);\n\t\t\t\t\t\t\t\toptions.body.callbackURL = url;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (options.body?.newUserCallbackURL) {\n\t\t\t\t\t\t\tif (options.body.newUserCallbackURL.startsWith(\"/\")) {\n\t\t\t\t\t\t\t\tconst url = Linking.createURL(options.body.newUserCallbackURL);\n\t\t\t\t\t\t\t\toptions.body.newUserCallbackURL = url;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (options.body?.errorCallbackURL) {\n\t\t\t\t\t\t\tif (options.body.errorCallbackURL.startsWith(\"/\")) {\n\t\t\t\t\t\t\t\tconst url = Linking.createURL(options.body.errorCallbackURL);\n\t\t\t\t\t\t\t\toptions.body.errorCallbackURL = url;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (url.includes(\"/sign-out\")) {\n\t\t\t\t\t\t\tstorage.setItem(cookieName, \"{}\");\n\t\t\t\t\t\t\tstore?.atoms.session?.set({\n\t\t\t\t\t\t\t\t...store.atoms.session.get(),\n\t\t\t\t\t\t\t\tdata: null,\n\t\t\t\t\t\t\t\terror: null,\n\t\t\t\t\t\t\t\tisPending: false,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\tstorage.setItem(localCacheName, \"{}\");\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\treturn {\n\t\t\t\t\t\turl,\n\t\t\t\t\t\toptions: options as ClientFetchOption,\n\t\t\t\t\t};\n\t\t\t\t},\n\t\t\t},\n\t\t],\n\t} satisfies BetterAuthClientPlugin;\n};\n\nexport { parseSetCookieHeader } from \"better-auth/cookies\";\nexport * from \"./focus-manager\";\nexport * from \"./online-manager\";\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../src/routes.ts","../src/index.ts"],"sourcesContent":["import { HIDE_METADATA } from \"better-auth\";\nimport { APIError, createAuthEndpoint } from \"better-auth/api\";\nimport * as z from \"zod\";\n\nexport const expoAuthorizationProxy = createAuthEndpoint(\n\t\"/expo-authorization-proxy\",\n\t{\n\t\tmethod: \"GET\",\n\t\tquery: z.object({\n\t\t\tauthorizationURL: z.string(),\n\t\t\toauthState: z.string().optional(),\n\t\t}),\n\t\tmetadata: HIDE_METADATA,\n\t},\n\tasync (ctx) => {\n\t\tconst { oauthState } = ctx.query;\n\t\tif (oauthState) {\n\t\t\tconst oauthStateCookie = ctx.context.createAuthCookie(\"oauth_state\", {\n\t\t\t\tmaxAge: 10 * 60, // 10 minutes\n\t\t\t});\n\t\t\tctx.setCookie(\n\t\t\t\toauthStateCookie.name,\n\t\t\t\toauthState,\n\t\t\t\toauthStateCookie.attributes,\n\t\t\t);\n\t\t\treturn ctx.redirect(ctx.query.authorizationURL);\n\t\t}\n\n\t\tconst { authorizationURL } = ctx.query;\n\t\tconst url = new URL(authorizationURL);\n\t\tconst state = url.searchParams.get(\"state\");\n\t\tif (!state) {\n\t\t\tthrow new APIError(\"BAD_REQUEST\", {\n\t\t\t\tmessage: \"Unexpected error\",\n\t\t\t});\n\t\t}\n\t\tconst stateCookie = ctx.context.createAuthCookie(\"state\", {\n\t\t\tmaxAge: 5 * 60, // 5 minutes\n\t\t});\n\t\tawait ctx.setSignedCookie(\n\t\t\tstateCookie.name,\n\t\t\tstate,\n\t\t\tctx.context.secret,\n\t\t\tstateCookie.attributes,\n\t\t);\n\t\treturn ctx.redirect(ctx.query.authorizationURL);\n\t},\n);\n","import type { BetterAuthPlugin } from \"@better-auth/core\";\nimport { createAuthMiddleware } from \"@better-auth/core/api\";\nimport { expoAuthorizationProxy } from \"./routes\";\n\nexport interface ExpoOptions {\n\t/**\n\t * Disable origin override for expo API routes\n\t * When set to true, the origin header will not be overridden for expo API routes\n\t */\n\tdisableOriginOverride?: boolean | undefined;\n}\n\ndeclare module \"@better-auth/core\" {\n\tinterface BetterAuthPluginRegistry<AuthOptions, Options> {\n\t\texpo: {\n\t\t\tcreator: typeof expo;\n\t\t};\n\t}\n}\n\nexport const expo = (options?: ExpoOptions | undefined) => {\n\treturn {\n\t\tid: \"expo\",\n\t\tinit: (ctx) => {\n\t\t\tconst trustedOrigins =\n\t\t\t\tprocess.env.NODE_ENV === \"development\" ? [\"exp://\"] : [];\n\n\t\t\treturn {\n\t\t\t\toptions: {\n\t\t\t\t\ttrustedOrigins,\n\t\t\t\t},\n\t\t\t};\n\t\t},\n\t\tasync onRequest(request, ctx) {\n\t\t\tif (options?.disableOriginOverride || request.headers.get(\"origin\")) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t/**\n\t\t\t * To bypass origin check from expo, we need to set the origin\n\t\t\t * header to the expo-origin header\n\t\t\t */\n\t\t\tconst expoOrigin = request.headers.get(\"expo-origin\");\n\t\t\tif (!expoOrigin) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Construct new Headers with new Request to avoid mutating the original request\n\t\t\tconst newHeaders = new Headers(request.headers);\n\t\t\tnewHeaders.set(\"origin\", expoOrigin);\n\t\t\tconst req = new Request(request, { headers: newHeaders });\n\n\t\t\treturn {\n\t\t\t\trequest: req,\n\t\t\t};\n\t\t},\n\t\thooks: {\n\t\t\tafter: [\n\t\t\t\t{\n\t\t\t\t\tmatcher(context) {\n\t\t\t\t\t\treturn !!(\n\t\t\t\t\t\t\tcontext.path?.startsWith(\"/callback\") ||\n\t\t\t\t\t\t\tcontext.path?.startsWith(\"/oauth2/callback\") ||\n\t\t\t\t\t\t\tcontext.path?.startsWith(\"/magic-link/verify\") ||\n\t\t\t\t\t\t\tcontext.path?.startsWith(\"/verify-email\")\n\t\t\t\t\t\t);\n\t\t\t\t\t},\n\t\t\t\t\thandler: createAuthMiddleware(async (ctx) => {\n\t\t\t\t\t\tconst headers = ctx.context.responseHeaders;\n\t\t\t\t\t\tconst location = headers?.get(\"location\");\n\t\t\t\t\t\tif (!location) {\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst isProxyURL = location.includes(\"/oauth-proxy-callback\");\n\t\t\t\t\t\tif (isProxyURL) {\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tlet redirectURL: URL;\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tredirectURL = new URL(location);\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst isHttpRedirect =\n\t\t\t\t\t\t\tredirectURL.protocol === \"http:\" ||\n\t\t\t\t\t\t\tredirectURL.protocol === \"https:\";\n\t\t\t\t\t\tif (isHttpRedirect) {\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst isTrustedOrigin = ctx.context.isTrustedOrigin(location);\n\t\t\t\t\t\tif (!isTrustedOrigin) {\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst cookie = headers?.get(\"set-cookie\");\n\t\t\t\t\t\tif (!cookie) {\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tredirectURL.searchParams.set(\"cookie\", cookie);\n\t\t\t\t\t\tctx.setHeader(\"location\", redirectURL.toString());\n\t\t\t\t\t}),\n\t\t\t\t},\n\t\t\t],\n\t\t},\n\t\tendpoints: {\n\t\t\texpoAuthorizationProxy,\n\t\t},\n\t\toptions,\n\t} satisfies BetterAuthPlugin;\n};\n"],"mappings":";;;;;;AAIA,MAAa,yBAAyB,mBACrC,6BACA;CACC,QAAQ;CACR,OAAO,EAAE,OAAO;EACf,kBAAkB,EAAE,QAAQ;EAC5B,YAAY,EAAE,QAAQ,CAAC,UAAU;EACjC,CAAC;CACF,UAAU;CACV,EACD,OAAO,QAAQ;CACd,MAAM,EAAE,eAAe,IAAI;AAC3B,KAAI,YAAY;EACf,MAAM,mBAAmB,IAAI,QAAQ,iBAAiB,eAAe,EACpE,QAAQ,KACR,CAAC;AACF,MAAI,UACH,iBAAiB,MACjB,YACA,iBAAiB,WACjB;AACD,SAAO,IAAI,SAAS,IAAI,MAAM,iBAAiB;;CAGhD,MAAM,EAAE,qBAAqB,IAAI;CAEjC,MAAM,QADM,IAAI,IAAI,iBAAiB,CACnB,aAAa,IAAI,QAAQ;AAC3C,KAAI,CAAC,MACJ,OAAM,IAAI,SAAS,eAAe,EACjC,SAAS,oBACT,CAAC;CAEH,MAAM,cAAc,IAAI,QAAQ,iBAAiB,SAAS,EACzD,QAAQ,KACR,CAAC;AACF,OAAM,IAAI,gBACT,YAAY,MACZ,OACA,IAAI,QAAQ,QACZ,YAAY,WACZ;AACD,QAAO,IAAI,SAAS,IAAI,MAAM,iBAAiB;EAEhD;;;;AC3BD,MAAa,QAAQ,YAAsC;AAC1D,QAAO;EACN,IAAI;EACJ,OAAO,QAAQ;AAId,UAAO,EACN,SAAS,EACR,gBAJD,QAAQ,IAAI,aAAa,gBAAgB,CAAC,SAAS,GAAG,EAAE,EAKvD,EACD;;EAEF,MAAM,UAAU,SAAS,KAAK;AAC7B,OAAI,SAAS,yBAAyB,QAAQ,QAAQ,IAAI,SAAS,CAClE;;;;;GAMD,MAAM,aAAa,QAAQ,QAAQ,IAAI,cAAc;AACrD,OAAI,CAAC,WACJ;GAID,MAAM,aAAa,IAAI,QAAQ,QAAQ,QAAQ;AAC/C,cAAW,IAAI,UAAU,WAAW;AAGpC,UAAO,EACN,SAHW,IAAI,QAAQ,SAAS,EAAE,SAAS,YAAY,CAAC,EAIxD;;EAEF,OAAO,EACN,OAAO,CACN;GACC,QAAQ,SAAS;AAChB,WAAO,CAAC,EACP,QAAQ,MAAM,WAAW,YAAY,IACrC,QAAQ,MAAM,WAAW,mBAAmB,IAC5C,QAAQ,MAAM,WAAW,qBAAqB,IAC9C,QAAQ,MAAM,WAAW,gBAAgB;;GAG3C,SAAS,qBAAqB,OAAO,QAAQ;IAC5C,MAAM,UAAU,IAAI,QAAQ;IAC5B,MAAM,WAAW,SAAS,IAAI,WAAW;AACzC,QAAI,CAAC,SACJ;AAGD,QADmB,SAAS,SAAS,wBAAwB,CAE5D;IAED,IAAI;AACJ,QAAI;AACH,mBAAc,IAAI,IAAI,SAAS;YACxB;AACP;;AAKD,QAFC,YAAY,aAAa,WACzB,YAAY,aAAa,SAEzB;AAGD,QAAI,CADoB,IAAI,QAAQ,gBAAgB,SAAS,CAE5D;IAED,MAAM,SAAS,SAAS,IAAI,aAAa;AACzC,QAAI,CAAC,OACJ;AAED,gBAAY,aAAa,IAAI,UAAU,OAAO;AAC9C,QAAI,UAAU,YAAY,YAAY,UAAU,CAAC;KAChD;GACF,CACD,EACD;EACD,WAAW,EACV,wBACA;EACD;EACA"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../src/plugins/last-login-method.ts"],"sourcesContent":["import type { Awaitable, BetterAuthClientPlugin } from \"@better-auth/core\";\n\nexport interface LastLoginMethodClientConfig {\n\tstorage: {\n\t\tsetItem: (key: string, value: string) => any;\n\t\tgetItem: (key: string) => string | null;\n\t\tdeleteItemAsync: (key: string) => Awaitable<void>;\n\t};\n\t/**\n\t * Prefix for local storage keys (e.g., \"my-app_last_login_method\")\n\t * @default \"better-auth\"\n\t */\n\tstoragePrefix?: string | undefined;\n\t/**\n\t * Custom resolve method for retrieving the last login method\n\t */\n\tcustomResolveMethod?:\n\t\t| ((url: string | URL) => Awaitable<string | undefined | null>)\n\t\t| undefined;\n}\n\nconst paths = [\n\t\"/callback/\",\n\t\"/oauth2/callback/\",\n\t\"/sign-in/email\",\n\t\"/sign-up/email\",\n];\nconst defaultResolveMethod = (url: string | URL) => {\n\tconst { pathname } = new URL(url.toString(), \"http://localhost\");\n\n\tif (paths.some((p) => pathname.includes(p))) {\n\t\treturn pathname.split(\"/\").pop();\n\t}\n\tif (pathname.includes(\"siwe\")) return \"siwe\";\n\tif (pathname.includes(\"/passkey/verify-authentication\")) {\n\t\treturn \"passkey\";\n\t}\n\n\treturn;\n};\n\nexport const lastLoginMethodClient = (config: LastLoginMethodClientConfig) => {\n\tconst resolveMethod = config.customResolveMethod || defaultResolveMethod;\n\tconst storagePrefix = config.storagePrefix || \"better-auth\";\n\tconst lastLoginMethodName = `${storagePrefix}_last_login_method`;\n\tconst storage = config.storage;\n\n\treturn {\n\t\tid: \"last-login-method-expo\",\n\t\tfetchPlugins: [\n\t\t\t{\n\t\t\t\tid: \"last-login-method-expo\",\n\t\t\t\tname: \"Last Login Method\",\n\t\t\t\thooks: {\n\t\t\t\t\tonResponse: async (ctx) => {\n\t\t\t\t\t\tconst lastMethod = await resolveMethod(ctx.request.url);\n\t\t\t\t\t\tif (!lastMethod) {\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tawait storage.setItem(lastLoginMethodName, lastMethod);\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t],\n\t\tgetActions() {\n\t\t\treturn {\n\t\t\t\t/**\n\t\t\t\t * Get the last used login method from storage\n\t\t\t\t *\n\t\t\t\t * @returns The last used login method or null if not found\n\t\t\t\t */\n\t\t\t\tgetLastUsedLoginMethod: (): string | null => {\n\t\t\t\t\treturn storage.getItem(lastLoginMethodName);\n\t\t\t\t},\n\t\t\t\t/**\n\t\t\t\t * Clear the last used login method from storage\n\t\t\t\t */\n\t\t\t\tclearLastUsedLoginMethod: async () => {\n\t\t\t\t\tawait storage.deleteItemAsync(lastLoginMethodName);\n\t\t\t\t},\n\t\t\t\t/**\n\t\t\t\t * Check if a specific login method was the last used\n\t\t\t\t * @param method The method to check\n\t\t\t\t * @returns True if the method was the last used, false otherwise\n\t\t\t\t */\n\t\t\t\tisLastUsedLoginMethod: (method: string): boolean => {\n\t\t\t\t\tconst lastMethod = storage.getItem(lastLoginMethodName);\n\t\t\t\t\treturn lastMethod === method;\n\t\t\t\t},\n\t\t\t};\n\t\t},\n\t} satisfies BetterAuthClientPlugin;\n};\n"],"mappings":";AAqBA,MAAM,QAAQ;CACb;CACA;CACA;CACA;CACA;AACD,MAAM,wBAAwB,QAAsB;CACnD,MAAM,EAAE,aAAa,IAAI,IAAI,IAAI,UAAU,EAAE,mBAAmB;AAEhE,KAAI,MAAM,MAAM,MAAM,SAAS,SAAS,EAAE,CAAC,CAC1C,QAAO,SAAS,MAAM,IAAI,CAAC,KAAK;AAEjC,KAAI,SAAS,SAAS,OAAO,CAAE,QAAO;AACtC,KAAI,SAAS,SAAS,iCAAiC,CACtD,QAAO;;AAMT,MAAa,yBAAyB,WAAwC;CAC7E,MAAM,gBAAgB,OAAO,uBAAuB;CAEpD,MAAM,sBAAsB,GADN,OAAO,iBAAiB,cACD;CAC7C,MAAM,UAAU,OAAO;AAEvB,QAAO;EACN,IAAI;EACJ,cAAc,CACb;GACC,IAAI;GACJ,MAAM;GACN,OAAO,EACN,YAAY,OAAO,QAAQ;IAC1B,MAAM,aAAa,MAAM,cAAc,IAAI,QAAQ,IAAI;AACvD,QAAI,CAAC,WACJ;AAGD,UAAM,QAAQ,QAAQ,qBAAqB,WAAW;MAEvD;GACD,CACD;EACD,aAAa;AACZ,UAAO;IAMN,8BAA6C;AAC5C,YAAO,QAAQ,QAAQ,oBAAoB;;IAK5C,0BAA0B,YAAY;AACrC,WAAM,QAAQ,gBAAgB,oBAAoB;;IAOnD,wBAAwB,WAA4B;AAEnD,YADmB,QAAQ,QAAQ,oBAAoB,KACjC;;IAEvB;;EAEF"}