@better-auth/expo 1.7.2 → 1.7.4

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
@@ -5,6 +5,35 @@ import * as _$_better_fetch_fetch0 from "@better-fetch/fetch";
5
5
  import { ClientFetchOption, ClientStore } from "@better-auth/core";
6
6
  import * as SecureStore from "expo-secure-store";
7
7
 
8
+ //#region src/client-storage.d.ts
9
+ /**
10
+ * Storage used by the Expo client for cookies and cached session data.
11
+ * Async access is coordinated through the provided object, so reuse it across
12
+ * clients that access the same stored data.
13
+ */
14
+ type ExpoClientStorage = Pick<typeof SecureStore, "setItem" | "setItemAsync" | "getItem" | "getItemAsync">;
15
+ /**
16
+ * Expo secure store does not support colons in the keys.
17
+ * This function replaces colons with underscores.
18
+ *
19
+ * @see https://github.com/better-auth/better-auth/issues/5426
20
+ *
21
+ * @param name cookie name to be saved in the storage
22
+ * @returns normalized cookie name
23
+ */
24
+ declare function normalizeCookieName(name: string): string;
25
+ interface ExpoStorageAdapter {
26
+ getItem(name: string): string | null;
27
+ getItemAsync(name: string): Promise<string | null>;
28
+ setItem(name: string, value: string): void;
29
+ setItemAsync(name: string, value: string): Promise<void>;
30
+ }
31
+ /**
32
+ * Wraps Expo storage with chunking, recoverable writes, and serialized async
33
+ * access.
34
+ */
35
+ declare function storageAdapter(storage: ExpoClientStorage): ExpoStorageAdapter;
36
+ //#endregion
8
37
  //#region src/focus-manager.d.ts
9
38
  declare function setupExpoFocusManager(): FocusManager;
10
39
  //#endregion
@@ -12,10 +41,6 @@ declare function setupExpoFocusManager(): FocusManager;
12
41
  declare function setupExpoOnlineManager(): OnlineManager;
13
42
  //#endregion
14
43
  //#region src/client.d.ts
15
- /**
16
- * Storage used by the Expo client for cookies and cached session data.
17
- */
18
- type ExpoClientStorage = Pick<typeof SecureStore, "setItem" | "setItemAsync" | "getItem" | "getItemAsync">;
19
44
  interface ExpoClientOptions {
20
45
  scheme?: string | undefined;
21
46
  storage: ExpoClientStorage;
@@ -74,35 +99,6 @@ declare function getCookie(cookie: string | null): string;
74
99
  * @returns true if the header contains better-auth cookies, false otherwise
75
100
  */
76
101
  declare function hasBetterAuthCookies(setCookieHeader: string, cookiePrefix: string | string[]): boolean;
77
- /**
78
- * Expo secure store does not support colons in the keys.
79
- * This function replaces colons with underscores.
80
- *
81
- * @see https://github.com/better-auth/better-auth/issues/5426
82
- *
83
- * @param name cookie name to be saved in the storage
84
- * @returns normalized cookie name
85
- */
86
- declare function normalizeCookieName(name: string): string;
87
- declare function storageAdapter(storage: ExpoClientStorage): {
88
- /**
89
- * Reads a value, reassembling it if it was split across chunk keys. A value
90
- * that fit is returned as-is (values written before chunking still read
91
- * back); a missing chunk returns `null` so a torn write fails closed.
92
- */
93
- getItem: (name: string) => string | null;
94
- getItemAsync: (name: string) => Promise<string | null>;
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) => void;
104
- setItemAsync: (name: string, value: string) => Promise<void>;
105
- };
106
102
  declare const expoClient: (opts: ExpoClientOptions) => {
107
103
  id: "expo";
108
104
  version: string;
@@ -189,4 +185,4 @@ declare const expoClient: (opts: ExpoClientOptions) => {
189
185
  }[];
190
186
  };
191
187
  //#endregion
192
- export { ExpoClientStorage, expoClient, getCookie, getSetCookie, hasBetterAuthCookies, normalizeCookieName, parseSetCookieHeader, setupExpoFocusManager, setupExpoOnlineManager, storageAdapter };
188
+ export { type ExpoClientStorage, expoClient, getCookie, getSetCookie, hasBetterAuthCookies, normalizeCookieName, parseSetCookieHeader, setupExpoFocusManager, setupExpoOnlineManager, storageAdapter };
package/dist/client.js CHANGED
@@ -1,9 +1,10 @@
1
- import { t as PACKAGE_VERSION } from "./version-D-tGaVc4.js";
1
+ import { t as PACKAGE_VERSION } from "./version-CuQDLAg7.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/utils";
4
4
  import Constants from "expo-constants";
5
5
  import * as Linking from "expo-linking";
6
6
  import { AppState, Platform } from "react-native";
7
+ import { logger } from "@better-auth/core/env";
7
8
  import { kFocusManager, kOnlineManager } from "better-auth/client";
8
9
  //#region \0rolldown/runtime.js
9
10
  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) {
@@ -11,6 +12,385 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
11
12
  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
13
  });
13
14
  //#endregion
15
+ //#region src/client-storage.ts
16
+ /**
17
+ * Expo secure store does not support colons in the keys.
18
+ * This function replaces colons with underscores.
19
+ *
20
+ * @see https://github.com/better-auth/better-auth/issues/5426
21
+ *
22
+ * @param name cookie name to be saved in the storage
23
+ * @returns normalized cookie name
24
+ */
25
+ function normalizeCookieName(name) {
26
+ return name.replace(/:/g, "_");
27
+ }
28
+ /**
29
+ * UTF-8 byte budget per stored chunk. Some native stores reject large values,
30
+ * so larger strings are split across keys.
31
+ *
32
+ * @see https://github.com/better-auth/better-auth/issues/9151
33
+ */
34
+ const STORAGE_VALUE_LIMIT = 1800;
35
+ const MAX_STORAGE_CHUNKS = 100;
36
+ function getUtf8ByteLength(character) {
37
+ const codePoint = character.codePointAt(0) ?? 0;
38
+ if (codePoint <= 127) return 1;
39
+ if (codePoint <= 2047) return 2;
40
+ if (codePoint <= 65535) return 3;
41
+ return 4;
42
+ }
43
+ function splitStorageValue(value) {
44
+ const chunks = [];
45
+ let start = 0;
46
+ let end = 0;
47
+ let bytes = 0;
48
+ for (const character of value) {
49
+ const characterBytes = getUtf8ByteLength(character);
50
+ if (bytes + characterBytes > STORAGE_VALUE_LIMIT) {
51
+ chunks.push(value.slice(start, end));
52
+ start = end;
53
+ bytes = 0;
54
+ }
55
+ bytes += characterBytes;
56
+ end += character.length;
57
+ }
58
+ chunks.push(value.slice(start));
59
+ return chunks;
60
+ }
61
+ /**
62
+ * Marks a base key whose value is split across multiple storage keys. Legacy
63
+ * markers contain only the chunk count. Current markers also identify the
64
+ * active slot and retain the previous slot's chunk count for recovery.
65
+ *
66
+ * @see https://github.com/better-auth/better-auth/issues/11082
67
+ */
68
+ const CHUNK_MARKER = "ba-chunks:";
69
+ const CHUNK_SLOTS = [
70
+ null,
71
+ 0,
72
+ 1
73
+ ];
74
+ function parseChunkCount(value) {
75
+ if (value === void 0) return null;
76
+ const count = Number(value);
77
+ if (!Number.isInteger(count) || count < 1 || count > MAX_STORAGE_CHUNKS) return null;
78
+ return count;
79
+ }
80
+ function parseChunkMarker(baseValue) {
81
+ const parts = baseValue.slice(11).split(":");
82
+ if (parts.length > 3) return null;
83
+ const [countValue, slotValue, fallbackCountValue] = parts;
84
+ const count = parseChunkCount(countValue);
85
+ if (count === null) return null;
86
+ if (slotValue === void 0) return fallbackCountValue === void 0 ? {
87
+ count,
88
+ slot: null,
89
+ fallbackCount: null
90
+ } : null;
91
+ if (slotValue !== "0" && slotValue !== "1") return null;
92
+ const fallbackCount = parseChunkCount(fallbackCountValue);
93
+ if (fallbackCountValue !== void 0 && fallbackCount === null) return null;
94
+ return {
95
+ count,
96
+ slot: slotValue === "0" ? 0 : 1,
97
+ fallbackCount
98
+ };
99
+ }
100
+ function getChunkKey(key, marker, index) {
101
+ return marker.slot === null ? `${key}.${index}` : `${key}.${marker.slot}.${index}`;
102
+ }
103
+ function getOtherSlot(slot) {
104
+ return slot === 0 ? 1 : 0;
105
+ }
106
+ function serializeChunkMarker(marker) {
107
+ if (marker.slot === null) return `${CHUNK_MARKER}${marker.count}`;
108
+ const fallback = marker.fallbackCount === null ? "" : `:${marker.fallbackCount}`;
109
+ return `${CHUNK_MARKER}${marker.count}:${marker.slot}${fallback}`;
110
+ }
111
+ function readChunks(storage, key, marker) {
112
+ let value = "";
113
+ for (let i = 0; i < marker.count; i++) {
114
+ const chunk = storage.getItem(getChunkKey(key, marker, i));
115
+ if (!chunk) return null;
116
+ value += chunk;
117
+ }
118
+ return value;
119
+ }
120
+ async function readChunksAsync(storage, key, marker) {
121
+ let value = "";
122
+ for (let i = 0; i < marker.count; i++) {
123
+ const chunk = await storage.getItemAsync(getChunkKey(key, marker, i));
124
+ if (!chunk) return null;
125
+ value += chunk;
126
+ }
127
+ return value;
128
+ }
129
+ function readStoredValue(storage, key, baseValue) {
130
+ if (baseValue == null || !baseValue.startsWith(CHUNK_MARKER)) return baseValue;
131
+ const marker = parseChunkMarker(baseValue);
132
+ if (!marker) return null;
133
+ const value = readChunks(storage, key, marker);
134
+ if (value !== null || marker.slot === null || marker.fallbackCount === null) return value;
135
+ return readChunks(storage, key, {
136
+ count: marker.fallbackCount,
137
+ slot: getOtherSlot(marker.slot),
138
+ fallbackCount: null
139
+ });
140
+ }
141
+ async function readStoredValueAsync(storage, key, baseValue) {
142
+ if (baseValue == null || !baseValue.startsWith(CHUNK_MARKER)) return baseValue;
143
+ const marker = parseChunkMarker(baseValue);
144
+ if (!marker) return null;
145
+ const value = await readChunksAsync(storage, key, marker);
146
+ if (value !== null || marker.slot === null || marker.fallbackCount === null) return value;
147
+ return readChunksAsync(storage, key, {
148
+ count: marker.fallbackCount,
149
+ slot: getOtherSlot(marker.slot),
150
+ fallbackCount: null
151
+ });
152
+ }
153
+ function getStorageWritePlan(key, value, currentBaseValue) {
154
+ const currentMarker = currentBaseValue?.startsWith(CHUNK_MARKER) ? parseChunkMarker(currentBaseValue) : null;
155
+ const chunks = splitStorageValue(value);
156
+ if (chunks.length === 1) return {
157
+ writes: [[key, value]],
158
+ cleanup: getUnusedChunkRanges(key, currentMarker, null)
159
+ };
160
+ const count = chunks.length;
161
+ if (count > MAX_STORAGE_CHUNKS) throw new Error(`Storage value requires ${count} chunks, exceeding the limit of ${MAX_STORAGE_CHUNKS}`);
162
+ const marker = {
163
+ count,
164
+ slot: currentMarker?.slot === 0 ? 1 : 0,
165
+ fallbackCount: currentMarker?.slot == null ? null : currentMarker.count
166
+ };
167
+ const writes = [];
168
+ if (currentMarker?.slot != null && currentMarker.fallbackCount !== null) writes.push([key, serializeChunkMarker({
169
+ ...currentMarker,
170
+ fallbackCount: null
171
+ })]);
172
+ for (const [index, chunk] of chunks.entries()) writes.push([getChunkKey(key, marker, index), chunk]);
173
+ writes.push([key, serializeChunkMarker(marker)]);
174
+ return {
175
+ writes,
176
+ cleanup: getUnusedChunkRanges(key, currentMarker, marker)
177
+ };
178
+ }
179
+ function getSlotChunkCount(marker, slot) {
180
+ if (!marker) return 0;
181
+ if (marker.slot === slot) return marker.count;
182
+ if (marker.slot === null || slot === null) return 0;
183
+ return marker.fallbackCount ?? 0;
184
+ }
185
+ function getUnusedChunkRanges(key, previousMarker, marker) {
186
+ return CHUNK_SLOTS.map((slot) => ({
187
+ prefix: slot === null ? key : `${key}.${slot}`,
188
+ start: getSlotChunkCount(marker, slot),
189
+ end: getSlotChunkCount(previousMarker, slot)
190
+ }));
191
+ }
192
+ const storageStates = /* @__PURE__ */ new WeakMap();
193
+ function getStorageState(storage, key) {
194
+ let states = storageStates.get(storage);
195
+ if (!states) {
196
+ states = /* @__PURE__ */ new Map();
197
+ storageStates.set(storage, states);
198
+ }
199
+ let state = states.get(key);
200
+ if (!state) {
201
+ state = {
202
+ pending: null,
203
+ pendingWrites: 0,
204
+ activeRead: null,
205
+ cleanupComplete: false
206
+ };
207
+ states.set(key, state);
208
+ }
209
+ return state;
210
+ }
211
+ function enqueueStorageOperation(state, operation) {
212
+ const queued = (state.pending ?? Promise.resolve()).then(operation, operation);
213
+ state.pending = queued;
214
+ const cleanup = () => {
215
+ if (state.pending === queued) state.pending = null;
216
+ };
217
+ queued.then(cleanup, cleanup);
218
+ return queued;
219
+ }
220
+ function enqueueStorageWrite(state, operation) {
221
+ state.pendingWrites++;
222
+ return enqueueStorageOperation(state, async () => {
223
+ try {
224
+ return await operation();
225
+ } finally {
226
+ state.pendingWrites--;
227
+ }
228
+ });
229
+ }
230
+ /** @internal */
231
+ function createManagedStorage(storage) {
232
+ const logWriteError = (key, error) => {
233
+ logger.error(`[better-auth/expo] failed to persist "${key}" to storage`, error);
234
+ };
235
+ const logCleanupError = (key, error) => {
236
+ logger.error(`[better-auth/expo] failed to clear unused chunks for "${key}"`, error);
237
+ };
238
+ const getItem = (name) => {
239
+ const key = normalizeCookieName(name);
240
+ return readStoredValue(storage, key, storage.getItem(key));
241
+ };
242
+ const getItemAsync = (name) => {
243
+ const key = normalizeCookieName(name);
244
+ const state = getStorageState(storage, key);
245
+ return enqueueStorageOperation(state, async () => {
246
+ const read = { snapshot: null };
247
+ state.activeRead = read;
248
+ try {
249
+ const value = await readStoredValueAsync(storage, key, await storage.getItemAsync(key));
250
+ return read.snapshot ? read.snapshot.value : value;
251
+ } finally {
252
+ state.activeRead = null;
253
+ }
254
+ });
255
+ };
256
+ const writeItem = (key, value, currentBaseValue) => {
257
+ const state = getStorageState(storage, key);
258
+ const { writes, cleanup } = getStorageWritePlan(key, value, currentBaseValue);
259
+ for (const [writeKey, writeValue] of writes) storage.setItem(writeKey, writeValue);
260
+ try {
261
+ const scanContiguousOrphans = !state.cleanupComplete;
262
+ for (const { prefix, start, end } of cleanup) {
263
+ if (scanContiguousOrphans) {
264
+ const orphanStart = Math.max(start, end);
265
+ let orphanEnd = orphanStart;
266
+ for (; orphanEnd < MAX_STORAGE_CHUNKS; orphanEnd++) if (!storage.getItem(`${prefix}.${orphanEnd}`)) break;
267
+ for (let i = orphanEnd - 1; i >= orphanStart; i--) storage.setItem(`${prefix}.${i}`, "");
268
+ }
269
+ for (let i = end - 1; i >= start; i--) storage.setItem(`${prefix}.${i}`, "");
270
+ }
271
+ state.cleanupComplete = true;
272
+ } catch (error) {
273
+ state.cleanupComplete = false;
274
+ logCleanupError(key, error);
275
+ }
276
+ };
277
+ const writeItemAsync = async (key, value, currentBaseValue) => {
278
+ const state = getStorageState(storage, key);
279
+ const { writes, cleanup } = getStorageWritePlan(key, value, currentBaseValue);
280
+ for (const [writeKey, writeValue] of writes) await storage.setItemAsync(writeKey, writeValue);
281
+ try {
282
+ const scanContiguousOrphans = !state.cleanupComplete;
283
+ for (const { prefix, start, end } of cleanup) {
284
+ if (scanContiguousOrphans) {
285
+ const orphanStart = Math.max(start, end);
286
+ let orphanEnd = orphanStart;
287
+ for (; orphanEnd < MAX_STORAGE_CHUNKS; orphanEnd++) if (!await storage.getItemAsync(`${prefix}.${orphanEnd}`)) break;
288
+ for (let i = orphanEnd - 1; i >= orphanStart; i--) await storage.setItemAsync(`${prefix}.${i}`, "");
289
+ }
290
+ for (let i = end - 1; i >= start; i--) await storage.setItemAsync(`${prefix}.${i}`, "");
291
+ }
292
+ state.cleanupComplete = true;
293
+ } catch (error) {
294
+ state.cleanupComplete = false;
295
+ logCleanupError(key, error);
296
+ }
297
+ };
298
+ const setItem = (name, value) => {
299
+ const key = normalizeCookieName(name);
300
+ const state = getStorageState(storage, key);
301
+ if (state.pendingWrites > 0) {
302
+ logWriteError(key, /* @__PURE__ */ new Error("Cannot write synchronously while an async write is pending"));
303
+ return;
304
+ }
305
+ let currentBaseValue = null;
306
+ try {
307
+ currentBaseValue = storage.getItem(key);
308
+ } catch (error) {
309
+ state.cleanupComplete = false;
310
+ if (splitStorageValue(value).length > 1) {
311
+ logWriteError(key, error);
312
+ return;
313
+ }
314
+ }
315
+ const read = state.activeRead;
316
+ if (read && read.snapshot === null) try {
317
+ read.snapshot = { value: readStoredValue(storage, key, currentBaseValue) };
318
+ } catch {
319
+ read.snapshot = { value: null };
320
+ }
321
+ try {
322
+ writeItem(key, value, currentBaseValue);
323
+ } catch (error) {
324
+ state.cleanupComplete = false;
325
+ logWriteError(key, error);
326
+ return;
327
+ }
328
+ if (read) read.snapshot = { value };
329
+ };
330
+ const setItemAsync = (name, value) => {
331
+ const key = normalizeCookieName(name);
332
+ const state = getStorageState(storage, key);
333
+ return enqueueStorageWrite(state, async () => {
334
+ let currentBaseValue = null;
335
+ try {
336
+ currentBaseValue = await storage.getItemAsync(key);
337
+ } catch (error) {
338
+ state.cleanupComplete = false;
339
+ if (splitStorageValue(value).length > 1) {
340
+ logWriteError(key, error);
341
+ return;
342
+ }
343
+ }
344
+ try {
345
+ await writeItemAsync(key, value, currentBaseValue);
346
+ } catch (error) {
347
+ state.cleanupComplete = false;
348
+ logWriteError(key, error);
349
+ }
350
+ });
351
+ };
352
+ const updateItemAsync = (name, update) => {
353
+ const key = normalizeCookieName(name);
354
+ const state = getStorageState(storage, key);
355
+ return enqueueStorageWrite(state, async () => {
356
+ try {
357
+ const currentBaseValue = await storage.getItemAsync(key);
358
+ const previousValue = await readStoredValueAsync(storage, key, currentBaseValue);
359
+ const value = update(previousValue);
360
+ await writeItemAsync(key, value, currentBaseValue);
361
+ return {
362
+ previousValue,
363
+ value
364
+ };
365
+ } catch (error) {
366
+ state.cleanupComplete = false;
367
+ logWriteError(key, error);
368
+ return null;
369
+ }
370
+ });
371
+ };
372
+ return {
373
+ getItem,
374
+ getItemAsync,
375
+ setItem,
376
+ setItemAsync,
377
+ updateItemAsync
378
+ };
379
+ }
380
+ /**
381
+ * Wraps Expo storage with chunking, recoverable writes, and serialized async
382
+ * access.
383
+ */
384
+ function storageAdapter(storage) {
385
+ const managedStorage = createManagedStorage(storage);
386
+ return {
387
+ getItem: managedStorage.getItem,
388
+ getItemAsync: managedStorage.getItemAsync,
389
+ setItem: managedStorage.setItem,
390
+ setItemAsync: managedStorage.setItemAsync
391
+ };
392
+ }
393
+ //#endregion
14
394
  //#region src/focus-manager.ts
15
395
  var ExpoFocusManager = class {
16
396
  listeners = /* @__PURE__ */ new Set();
@@ -183,111 +563,12 @@ function hasBetterAuthCookies(setCookieHeader, cookiePrefix) {
183
563
  }
184
564
  return false;
185
565
  }
186
- /**
187
- * Expo secure store does not support colons in the keys.
188
- * This function replaces colons with underscores.
189
- *
190
- * @see https://github.com/better-auth/better-auth/issues/5426
191
- *
192
- * @param name cookie name to be saved in the storage
193
- * @returns normalized cookie name
194
- */
195
- function normalizeCookieName(name) {
196
- return name.replace(/:/g, "_");
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:";
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
- }
224
- function storageAdapter(storage) {
225
- return {
226
- /**
227
- * Reads a value, reassembling it if it was split across chunk keys. A value
228
- * that fit is returned as-is (values written before chunking still read
229
- * back); a missing chunk returns `null` so a torn write fails closed.
230
- */
231
- getItem: (name) => {
232
- const key = normalizeCookieName(name);
233
- const stored = storage.getItem(key);
234
- if (stored == null || !stored.startsWith(CHUNK_MARKER)) return stored;
235
- const count = Number(stored.slice(11));
236
- if (!Number.isInteger(count) || count < 1) return null;
237
- let value = "";
238
- for (let i = 0; i < count; i++) {
239
- const chunk = storage.getItem(`${key}.${i}`);
240
- if (chunk == null) return null;
241
- value += chunk;
242
- }
243
- return value;
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
- },
259
- /**
260
- * Stores `value`, splitting it across chunk keys when it exceeds the
261
- * per-write limit. The base key is cleared before the chunks are rewritten
262
- * and set to the marker last, as the commit point, so a write interrupted
263
- * partway through reads as absent rather than a mix of old and new chunks.
264
- * Failures are logged, not thrown: persistence is best-effort and must not
265
- * break the request.
266
- */
267
- setItem: (name, value) => {
268
- const key = normalizeCookieName(name);
269
- try {
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);
279
- } catch (error) {
280
- console.error(`[better-auth/expo] failed to persist "${key}" to storage`, error);
281
- }
282
- }
283
- };
284
- }
285
566
  const expoClient = (opts) => {
286
567
  let store = null;
287
568
  const storagePrefix = opts?.storagePrefix || "better-auth";
288
569
  const cookieName = `${storagePrefix}_cookie`;
289
570
  const localCacheName = `${storagePrefix}_session_data`;
290
- const storage = storageAdapter(opts.storage);
571
+ const storage = createManagedStorage(opts.storage);
291
572
  const isWeb = Platform.OS === "web";
292
573
  const cookiePrefix = opts?.cookiePrefix || "better-auth";
293
574
  let sessionCacheHydration;
@@ -359,12 +640,8 @@ getCookie: async () => {
359
640
  const setCookie = context.response.headers.get("set-cookie");
360
641
  if (setCookie) {
361
642
  if (hasBetterAuthCookies(setCookie, cookiePrefix)) {
362
- const prevCookie = await storage.getItemAsync(cookieName);
363
- const toSetCookie = getSetCookie(setCookie || "", prevCookie ?? void 0);
364
- if (hasSessionCookieChanged(prevCookie, toSetCookie)) {
365
- await storage.setItemAsync(cookieName, toSetCookie);
366
- store?.notify("$sessionSignal");
367
- } else await storage.setItemAsync(cookieName, toSetCookie);
643
+ const update = await storage.updateItemAsync(cookieName, (currentValue) => getSetCookie(setCookie, currentValue ?? void 0));
644
+ if (update && hasSessionCookieChanged(update.previousValue, update.value)) store?.notify("$sessionSignal");
368
645
  }
369
646
  }
370
647
  if (pathname.endsWith("/get-session") && !opts?.disableCache) {
@@ -398,9 +675,7 @@ getCookie: async () => {
398
675
  if (result.type !== "success") return;
399
676
  const cookie = new URL(result.url).searchParams.get("cookie");
400
677
  if (!cookie) return;
401
- const toSetCookie = getSetCookie(cookie, await storage.getItemAsync(cookieName) ?? void 0);
402
- await storage.setItemAsync(cookieName, toSetCookie);
403
- store?.notify("$sessionSignal");
678
+ if (await storage.updateItemAsync(cookieName, (currentValue) => getSetCookie(cookie, currentValue ?? void 0))) store?.notify("$sessionSignal");
404
679
  }
405
680
  } },
406
681
  async init(url, options) {
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { t as PACKAGE_VERSION } from "./version-D-tGaVc4.js";
1
+ import { t as PACKAGE_VERSION } from "./version-CuQDLAg7.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-D-tGaVc4.js";
1
+ import { t as PACKAGE_VERSION } from "../version-CuQDLAg7.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.2";
3
+ const PACKAGE_VERSION = "1.7.4";
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.2",
3
+ "version": "1.7.4",
4
4
  "description": "Better Auth integration for Expo and React Native applications.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -60,7 +60,7 @@
60
60
  "dependencies": {
61
61
  "@better-fetch/fetch": "1.3.1",
62
62
  "better-call": "1.4.0",
63
- "zod": "^4.3.6"
63
+ "zod": "^4.5.4"
64
64
  },
65
65
  "devDependencies": {
66
66
  "@better-fetch/fetch": "1.3.1",
@@ -71,8 +71,8 @@
71
71
  "expo-web-browser": "~56.0.5",
72
72
  "react-native": "~0.86.0",
73
73
  "tsdown": "0.21.10",
74
- "@better-auth/core": "1.7.2",
75
- "better-auth": "1.7.2"
74
+ "@better-auth/core": "1.7.4",
75
+ "better-auth": "1.7.4"
76
76
  },
77
77
  "peerDependencies": {
78
78
  "expo-constants": ">=17.0.0",
@@ -80,8 +80,8 @@
80
80
  "expo-network": ">=8.0.7",
81
81
  "expo-secure-store": ">=12.5.0",
82
82
  "expo-web-browser": ">=14.0.0",
83
- "@better-auth/core": "^1.7.2",
84
- "better-auth": "^1.7.2"
83
+ "@better-auth/core": "^1.7.4",
84
+ "better-auth": "^1.7.4"
85
85
  },
86
86
  "peerDependenciesMeta": {
87
87
  "expo-constants": {