@better-auth/expo 1.7.0-beta.0 → 1.7.0-beta.10

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
@@ -84,10 +84,23 @@ declare function hasBetterAuthCookies(setCookieHeader: string, cookiePrefix: str
84
84
  declare function normalizeCookieName(name: string): string;
85
85
  declare function storageAdapter(storage: {
86
86
  getItem: (name: string) => string | null;
87
- setItem: (name: string, value: string) => void;
87
+ setItem: (name: string, value: string) => unknown;
88
88
  }): {
89
+ /**
90
+ * Reads a value, reassembling it if it was split across chunk keys. A value
91
+ * that fit is returned as-is (values written before chunking still read
92
+ * back); a missing chunk returns `null` so a torn write fails closed.
93
+ */
89
94
  getItem: (name: string) => string | null;
90
- setItem: (name: string, value: string) => void;
95
+ /**
96
+ * Stores `value`, splitting it across chunk keys when it exceeds the
97
+ * per-write limit. The base key is cleared before the chunks are rewritten
98
+ * and set to the marker last, as the commit point, so a write interrupted
99
+ * partway through reads as absent rather than a mix of old and new chunks.
100
+ * Failures are logged, not thrown: persistence is best-effort and must not
101
+ * break the request.
102
+ */
103
+ setItem: (name: string, value: string) => Promise<void>;
91
104
  };
92
105
  declare const expoClient: (opts: ExpoClientOptions) => {
93
106
  id: "expo";
@@ -119,11 +132,6 @@ declare const expoClient: (opts: ExpoClientOptions) => {
119
132
  init(url: string, options: ({
120
133
  priority?: RequestPriority | undefined;
121
134
  method?: string | undefined;
122
- headers?: (HeadersInit & (HeadersInit | {
123
- accept: "application/json" | "text/plain" | "application/octet-stream";
124
- "content-type": "application/json" | "text/plain" | "application/x-www-form-urlencoded" | "multipart/form-data" | "application/octet-stream";
125
- authorization: "Bearer" | "Basic";
126
- })) | undefined;
127
135
  redirect?: RequestRedirect | undefined;
128
136
  window?: null | undefined;
129
137
  cache?: RequestCache | undefined;
@@ -159,6 +167,12 @@ declare const expoClient: (opts: ExpoClientOptions) => {
159
167
  prefix: string | (() => string | undefined) | undefined;
160
168
  value: string | (() => string | undefined) | undefined;
161
169
  }) | undefined;
170
+ headers?: {} | {
171
+ [x: string]: string | undefined;
172
+ accept?: ((string & {}) | "application/json" | "text/plain" | "application/octet-stream") | undefined;
173
+ "content-type"?: ((string & {}) | "application/json" | "text/plain" | "application/octet-stream" | "application/x-www-form-urlencoded" | "multipart/form-data") | undefined;
174
+ authorization?: ((string & {}) | `Bearer ${string}` | `Basic ${string}`) | undefined;
175
+ } | undefined;
162
176
  body?: any;
163
177
  query?: any;
164
178
  params?: any;
package/dist/client.js CHANGED
@@ -1,4 +1,4 @@
1
- import { t as PACKAGE_VERSION } from "./version-3o9_RtC5.js";
1
+ import { t as PACKAGE_VERSION } from "./version-CXjBN8D3.js";
2
2
  import { safeJSONParse } from "@better-auth/core/utils/json";
3
3
  import { SECURE_COOKIE_PREFIX, parseSetCookieHeader, parseSetCookieHeader as parseSetCookieHeader$1, stripSecureCookiePrefix } from "better-auth/cookies";
4
4
  import Constants from "expo-constants";
@@ -195,13 +195,54 @@ function hasBetterAuthCookies(setCookieHeader, cookiePrefix) {
195
195
  function normalizeCookieName(name) {
196
196
  return name.replace(/:/g, "_");
197
197
  }
198
+ /**
199
+ * Max characters written per `setItem`. Native secure stores silently reject
200
+ * oversized writes (iOS Keychain refuses values above ~2KB), losing the cookie,
201
+ * so a larger value is split across keys here. Mirrors the server's
202
+ * `chunkCookie`/`joinChunks` in `session-store.ts`; keep the two in sync.
203
+ *
204
+ * @see https://github.com/better-auth/better-auth/issues/9151
205
+ */
206
+ const STORAGE_VALUE_LIMIT = 1800;
207
+ /**
208
+ * Marks a base key whose value is split across `<key>.0..N` chunks. The leading
209
+ * control char can't start a JSON value (so it never collides) and, unlike NUL,
210
+ * survives the native storage bridge without C-string truncation.
211
+ */
212
+ const CHUNK_MARKER = "ba-chunks:";
198
213
  function storageAdapter(storage) {
199
214
  return {
200
215
  getItem: (name) => {
201
- return storage.getItem(normalizeCookieName(name));
216
+ const key = normalizeCookieName(name);
217
+ const stored = storage.getItem(key);
218
+ if (stored == null || !stored.startsWith(CHUNK_MARKER)) return stored;
219
+ const count = Number(stored.slice(11));
220
+ if (!Number.isInteger(count) || count < 1) return null;
221
+ let value = "";
222
+ for (let i = 0; i < count; i++) {
223
+ const chunk = storage.getItem(`${key}.${i}`);
224
+ if (chunk == null) return null;
225
+ value += chunk;
226
+ }
227
+ return value;
202
228
  },
203
- setItem: (name, value) => {
204
- return storage.setItem(normalizeCookieName(name), value);
229
+ setItem: async (name, value) => {
230
+ const key = normalizeCookieName(name);
231
+ try {
232
+ if (value.length <= STORAGE_VALUE_LIMIT) {
233
+ await storage.setItem(key, value);
234
+ return;
235
+ }
236
+ await storage.setItem(key, "");
237
+ const count = Math.ceil(value.length / STORAGE_VALUE_LIMIT);
238
+ for (let i = 0; i < count; i++) {
239
+ const start = i * STORAGE_VALUE_LIMIT;
240
+ await storage.setItem(`${key}.${i}`, value.slice(start, start + STORAGE_VALUE_LIMIT));
241
+ }
242
+ await storage.setItem(key, `${CHUNK_MARKER}${count}`);
243
+ } catch (error) {
244
+ console.error(`[better-auth/expo] failed to persist "${key}" to storage`, error);
245
+ }
205
246
  }
206
247
  };
207
248
  }
@@ -213,6 +254,16 @@ const expoClient = (opts) => {
213
254
  const storage = storageAdapter(opts?.storage);
214
255
  const isWeb = Platform.OS === "web";
215
256
  const cookiePrefix = opts?.cookiePrefix || "better-auth";
257
+ const clearSessionCache = async () => {
258
+ await storage.setItem(cookieName, "{}");
259
+ store?.atoms.session?.set({
260
+ ...store.atoms.session.get(),
261
+ data: null,
262
+ error: null,
263
+ isPending: false
264
+ });
265
+ await storage.setItem(localCacheName, "{}");
266
+ };
216
267
  const rawScheme = opts?.scheme || Constants.expoConfig?.scheme || Constants.platform?.scheme;
217
268
  const scheme = Array.isArray(rawScheme) ? rawScheme[0] : rawScheme;
218
269
  if (!scheme && !isWeb) throw new Error("Scheme not found in app.json. Please provide a scheme in the options.");
@@ -221,6 +272,18 @@ const expoClient = (opts) => {
221
272
  version: PACKAGE_VERSION,
222
273
  getActions(_, $store) {
223
274
  store = $store;
275
+ const sessionAtom = $store.atoms.session;
276
+ if (!isWeb && !opts?.disableCache && sessionAtom) {
277
+ const raw = storage.getItem(localCacheName);
278
+ const cached = raw ? safeJSONParse(raw) : null;
279
+ const exp = cached?.session?.expiresAt;
280
+ const expMs = exp ? new Date(exp).getTime() : NaN;
281
+ if (!!cached?.user?.id && !!cached.session?.id && expMs > Date.now()) sessionAtom.set({
282
+ ...sessionAtom.get(),
283
+ data: cached,
284
+ error: null
285
+ });
286
+ }
224
287
  return { getCookie: () => {
225
288
  return getCookie(storage.getItem(cookieName) || "{}");
226
289
  } };
@@ -236,15 +299,16 @@ const expoClient = (opts) => {
236
299
  const prevCookie = storage.getItem(cookieName);
237
300
  const toSetCookie = getSetCookie(setCookie || "", prevCookie ?? void 0);
238
301
  if (hasSessionCookieChanged(prevCookie, toSetCookie)) {
239
- storage.setItem(cookieName, toSetCookie);
302
+ await storage.setItem(cookieName, toSetCookie);
240
303
  store?.notify("$sessionSignal");
241
- } else storage.setItem(cookieName, toSetCookie);
304
+ } else await storage.setItem(cookieName, toSetCookie);
242
305
  }
243
306
  }
244
307
  if (context.request.url.toString().includes("/get-session") && !opts?.disableCache) {
245
308
  const data = context.data;
246
- storage.setItem(localCacheName, JSON.stringify(data));
309
+ await storage.setItem(localCacheName, JSON.stringify(data));
247
310
  }
311
+ if (context.request.url.toString().includes("/sign-out")) await clearSessionCache();
248
312
  if (context.data?.redirect && (context.request.url.toString().includes("/sign-in") || context.request.url.toString().includes("/link-social")) && !context.request?.body.includes("idToken")) {
249
313
  const to = JSON.parse(context.request.body)?.callbackURL;
250
314
  const signInURL = context.data?.url;
@@ -270,7 +334,7 @@ const expoClient = (opts) => {
270
334
  const cookie = new URL(result.url).searchParams.get("cookie");
271
335
  if (!cookie) return;
272
336
  const toSetCookie = getSetCookie(cookie, storage.getItem(cookieName) ?? void 0);
273
- storage.setItem(cookieName, toSetCookie);
337
+ await storage.setItem(cookieName, toSetCookie);
274
338
  store?.notify("$sessionSignal");
275
339
  }
276
340
  } },
@@ -281,11 +345,14 @@ const expoClient = (opts) => {
281
345
  };
282
346
  options = options || {};
283
347
  options.credentials = "omit";
284
- if (options.body?.idToken !== void 0) options.headers = {
285
- ...options.headers,
286
- "x-skip-oauth-proxy": "true"
287
- };
288
- else {
348
+ if (options.body?.idToken !== void 0) {
349
+ const cookie = url.includes("/link-social") ? getCookie(storage.getItem(cookieName) || "{}") : "";
350
+ options.headers = {
351
+ ...options.headers,
352
+ ...cookie ? { cookie } : {},
353
+ "x-skip-oauth-proxy": "true"
354
+ };
355
+ } else {
289
356
  const cookie = getCookie(storage.getItem(cookieName) || "{}");
290
357
  options.headers = {
291
358
  ...options.headers,
@@ -311,16 +378,7 @@ const expoClient = (opts) => {
311
378
  options.body.errorCallbackURL = url;
312
379
  }
313
380
  }
314
- if (url.includes("/sign-out")) {
315
- storage.setItem(cookieName, "{}");
316
- store?.atoms.session?.set({
317
- ...store.atoms.session.get(),
318
- data: null,
319
- error: null,
320
- isPending: false
321
- });
322
- storage.setItem(localCacheName, "{}");
323
- }
381
+ if (url.includes("/sign-out")) await clearSessionCache();
324
382
  }
325
383
  return {
326
384
  url,
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { t as PACKAGE_VERSION } from "./version-3o9_RtC5.js";
1
+ import { t as PACKAGE_VERSION } from "./version-CXjBN8D3.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";
@@ -12,14 +12,22 @@ const expoAuthorizationProxy = createAuthEndpoint("/expo-authorization-proxy", {
12
12
  }),
13
13
  metadata: HIDE_METADATA
14
14
  }, async (ctx) => {
15
+ const { authorizationURL } = ctx.query;
16
+ if (authorizationURL.includes("#")) throw new APIError("BAD_REQUEST", { message: "Invalid authorizationURL" });
17
+ let url;
18
+ try {
19
+ url = new URL(authorizationURL);
20
+ } catch {
21
+ throw new APIError("BAD_REQUEST", { message: "Invalid authorizationURL" });
22
+ }
23
+ if (url.protocol !== "https:" || url.origin === new URL(ctx.context.baseURL).origin) throw new APIError("BAD_REQUEST", { message: "Invalid authorizationURL" });
15
24
  const { oauthState } = ctx.query;
16
25
  if (oauthState) {
17
26
  const oauthStateCookie = ctx.context.createAuthCookie("oauth_state", { maxAge: 600 });
18
27
  ctx.setCookie(oauthStateCookie.name, oauthState, oauthStateCookie.attributes);
19
- return ctx.redirect(ctx.query.authorizationURL);
28
+ return ctx.redirect(authorizationURL);
20
29
  }
21
- const { authorizationURL } = ctx.query;
22
- const state = new URL(authorizationURL).searchParams.get("state");
30
+ const state = url.searchParams.get("state");
23
31
  if (!state) throw new APIError("BAD_REQUEST", { message: "Unexpected error" });
24
32
  const stateCookie = ctx.context.createAuthCookie("state", { maxAge: 300 });
25
33
  await ctx.setSignedCookie(stateCookie.name, state, ctx.context.secret, stateCookie.attributes);
@@ -53,7 +61,7 @@ const expo = (options) => {
53
61
  },
54
62
  hooks: { after: [{
55
63
  matcher(context) {
56
- return !!(context.path?.startsWith("/callback") || context.path?.startsWith("/oauth2/callback") || context.path?.startsWith("/magic-link/verify") || context.path?.startsWith("/verify-email"));
64
+ return !!(context.path?.startsWith("/callback") || context.path?.startsWith("/magic-link/verify") || context.path?.startsWith("/verify-email"));
57
65
  },
58
66
  handler: createAuthMiddleware(async (ctx) => {
59
67
  const headers = ctx.context.responseHeaders;
@@ -1,8 +1,7 @@
1
- import { t as PACKAGE_VERSION } from "../version-3o9_RtC5.js";
1
+ import { t as PACKAGE_VERSION } from "../version-CXjBN8D3.js";
2
2
  //#region src/plugins/last-login-method.ts
3
3
  const paths = [
4
4
  "/callback/",
5
- "/oauth2/callback/",
6
5
  "/sign-in/email",
7
6
  "/sign-up/email"
8
7
  ];
@@ -1,5 +1,5 @@
1
1
  //#endregion
2
2
  //#region src/version.ts
3
- const PACKAGE_VERSION = "1.7.0-beta.0";
3
+ const PACKAGE_VERSION = "1.7.0-beta.10";
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-beta.0",
3
+ "version": "1.7.0-beta.10",
4
4
  "description": "Better Auth integration for Expo and React Native applications.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -58,28 +58,28 @@
58
58
  }
59
59
  },
60
60
  "dependencies": {
61
- "@better-fetch/fetch": "1.1.21",
62
- "better-call": "1.3.5",
61
+ "@better-fetch/fetch": "1.3.1",
62
+ "better-call": "1.3.7",
63
63
  "zod": "^4.3.6"
64
64
  },
65
65
  "devDependencies": {
66
- "@better-fetch/fetch": "1.1.21",
67
- "expo-constants": "~55.0.7",
68
- "expo-linking": "~55.0.7",
69
- "expo-network": "~55.0.8",
70
- "expo-web-browser": "~55.0.9",
71
- "react-native": "~0.84.1",
66
+ "@better-fetch/fetch": "1.3.1",
67
+ "expo-constants": "~56.0.18",
68
+ "expo-linking": "~56.0.14",
69
+ "expo-network": "~56.0.5",
70
+ "expo-web-browser": "~56.0.5",
71
+ "react-native": "~0.86.0",
72
72
  "tsdown": "0.21.1",
73
- "@better-auth/core": "1.7.0-beta.0",
74
- "better-auth": "1.7.0-beta.0"
73
+ "@better-auth/core": "1.7.0-beta.10",
74
+ "better-auth": "1.7.0-beta.10"
75
75
  },
76
76
  "peerDependencies": {
77
77
  "expo-constants": ">=17.0.0",
78
78
  "expo-linking": ">=7.0.0",
79
79
  "expo-network": ">=8.0.7",
80
80
  "expo-web-browser": ">=14.0.0",
81
- "@better-auth/core": "^1.7.0-beta.0",
82
- "better-auth": "^1.7.0-beta.0"
81
+ "@better-auth/core": "^1.7.0-beta.10",
82
+ "better-auth": "^1.7.0-beta.10"
83
83
  },
84
84
  "peerDependenciesMeta": {
85
85
  "expo-constants": {