@lunora/react-native 1.0.0-alpha.8 → 1.0.0-alpha.80

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -16,8 +16,9 @@ two seams a native app needs that a browser gives you for free: a durable offlin
16
16
  queue backed by `AsyncStorage`, and credentialed requests (there is no cookie jar
17
17
  in React Native, so the session has to be attached explicitly).
18
18
 
19
- This package **re-exports the entire `@lunora/react` surface**, so you import
20
- your hooks and provider from here, and adds:
19
+ This package **re-exports the whole `@lunora/react` surface** (see
20
+ [below](#re-exported-lunorareact-surface)), so you import your hooks and
21
+ provider from here, and adds:
21
22
 
22
23
  - `createLunoraClient(options)` — a `LunoraClient` factory tuned for React
23
24
  Native.
@@ -131,10 +132,24 @@ import { client } from "./lunora";
131
132
  function Root() {
132
133
  const { data: session } = authClient.useSession();
133
134
 
135
+ // `expoBearerToken` is async since better-auth 1.7.1, and an async function
136
+ // is not a valid effect cleanup return — so kick off a promise instead.
137
+ // `cancelled` is load-bearing: two session changes in quick succession leave
138
+ // two reads in flight, and without it the slower one reinstates the previous
139
+ // session's token.
134
140
  useEffect(() => {
135
- const token = expoBearerToken(authClient);
136
- client.setAuthToken(token); // HTTP `Authorization: Bearer …`
137
- client.setWsToken(token ?? undefined); // WS `?token=…`
141
+ let cancelled = false;
142
+
143
+ void (async () => {
144
+ const token = await expoBearerToken(authClient);
145
+ if (cancelled) return;
146
+ client.setAuthToken(token); // HTTP `Authorization: Bearer …`
147
+ client.setWsToken(token ?? undefined); // WS `?token=…`
148
+ })();
149
+
150
+ return () => {
151
+ cancelled = true;
152
+ };
138
153
  }, [session]);
139
154
 
140
155
  // …render the app
@@ -199,6 +214,23 @@ Everything on `LunoraClientOptions` (`url`, `wsUrl`, `authBasePath`,
199
214
  An explicit `persistence`, `fetch`, or `WebSocket` always takes precedence over
200
215
  the convenience derived from `storage` / `getAuthHeaders`.
201
216
 
217
+ ### Re-exported `@lunora/react` surface
218
+
219
+ Every hook, provider, and auth gate from [`@lunora/react`](../react) is
220
+ re-exported here, wholesale — a new hook there lands on native with no edit to
221
+ this package.
222
+
223
+ The payment kit is **not** part of that surface: `CheckoutButton`,
224
+ `CustomerPortalButton`, and `useCheckout` render a DOM `<button>` and navigate
225
+ via `globalThis.location`, so they ship from the `@lunora/react/payment` subpath
226
+ rather than the `@lunora/react` root, and nothing re-exports them here. Drive
227
+ purchases through the platform's own flow (or a WebView pointed at the web
228
+ checkout URL).
229
+
230
+ `useVoiceAgent` is re-exported but its defaults are Web APIs (`getUserMedia`,
231
+ Web Audio, `WebSocket`): on native, pass your own `createMicrophone` /
232
+ `createSpeaker` implementations.
233
+
202
234
  ### `@lunora/react-native/auth`
203
235
 
204
236
  - `expoBearerToken(authClient)` — reads the better-auth Expo session token (from
package/dist/auth.d.mts CHANGED
@@ -1,4 +1,20 @@
1
- export { expoClient, setupExpoFocusManager, setupExpoOnlineManager } from '@better-auth/expo/client';
1
+ export {
2
+ /**
3
+ * The slice of a key/value store the better-auth Expo plugin needs. Pass Expo
4
+ * `SecureStore` straight in.
5
+ *
6
+ * Re-exported from `@better-auth/expo/client` rather than restated here. This
7
+ * used to be a hand-written `SecureStorageLike` mirroring what upstream needed
8
+ * — two synchronous methods — and better-auth 1.7 moved the Expo storage
9
+ * integration to asynchronous `SecureStore` methods, so the real shape is now
10
+ * `getItem` + `getItemAsync` + `setItem` + `setItemAsync`. A hand-written
11
+ * mirror does not fail when upstream widens like that; it just silently
12
+ * describes a store the plugin will not accept. Re-exporting makes the
13
+ * compiler track it, which is the same lesson the `getCookie` shim taught in
14
+ * this file's history.
15
+ * @experimental
16
+ */
17
+ type ExpoClientStorage, expoClient, setupExpoFocusManager, setupExpoOnlineManager } from '@better-auth/expo/client';
2
18
  /**
3
19
  * Read the current better-auth session token from an Expo auth client, for use
4
20
  * as a **bearer** credential on the Lunora client — `client.setAuthToken(token)`
@@ -9,29 +25,27 @@ export { expoClient, setupExpoFocusManager, setupExpoOnlineManager } from '@bett
9
25
  * better-auth's `bearer` plugin accepts that value verbatim in the
10
26
  * `Authorization` header — so the native client authenticates WITHOUT sending a
11
27
  * `Cookie`, which the runtime's CSRF guard rejects on an `Origin`-less native
12
- * request (React Native sends no `Origin`). Returns `null` when signed out.
28
+ * request (React Native sends no `Origin`). Resolves `null` when signed out.
29
+ *
30
+ * **Async since better-auth 1.7.1**, which changed `@better-auth/expo`'s
31
+ * `getCookie` from `() => string` to `() => Promise<string>` (it reads
32
+ * `SecureStore` asynchronously now). The awaited value is what gets matched:
33
+ * regexing the Promise itself would test `"[object Promise]"`, find no
34
+ * `session_token`, and return `null` on every call — a signed-in native app
35
+ * that silently behaves as anonymous, which is exactly the failure this
36
+ * signature change prevents by making callers await.
13
37
  *
14
38
  * Re-run it whenever the session changes and feed the result to the client (see
15
39
  * the package README):
16
40
  *
17
41
  * ```ts
18
- * const token = expoBearerToken(authClient);
42
+ * const token = await expoBearerToken(authClient);
19
43
  * client.setAuthToken(token);
20
44
  * client.setWsToken(token ?? undefined);
21
45
  * ```
22
46
  * @experimental
23
47
  */
24
48
  declare const expoBearerToken: (authClient: {
25
- getCookie: () => string;
26
- }) => null | string;
27
- /**
28
- * The slice of a key/value store the better-auth Expo plugin needs — the shape
29
- * of `expo-secure-store` (a synchronous `getItem`, plus `setItem`). Pass Expo
30
- * `SecureStore` straight in.
31
- * @experimental
32
- */
33
- interface SecureStorageLike {
34
- getItem: (key: string) => null | string;
35
- setItem: (key: string, value: string) => unknown;
36
- }
37
- export { SecureStorageLike, expoBearerToken };
49
+ getCookie: () => Promise<string> | string;
50
+ }) => Promise<null | string>;
51
+ export { expoBearerToken };
package/dist/auth.d.ts CHANGED
@@ -1,4 +1,20 @@
1
- export { expoClient, setupExpoFocusManager, setupExpoOnlineManager } from '@better-auth/expo/client';
1
+ export {
2
+ /**
3
+ * The slice of a key/value store the better-auth Expo plugin needs. Pass Expo
4
+ * `SecureStore` straight in.
5
+ *
6
+ * Re-exported from `@better-auth/expo/client` rather than restated here. This
7
+ * used to be a hand-written `SecureStorageLike` mirroring what upstream needed
8
+ * — two synchronous methods — and better-auth 1.7 moved the Expo storage
9
+ * integration to asynchronous `SecureStore` methods, so the real shape is now
10
+ * `getItem` + `getItemAsync` + `setItem` + `setItemAsync`. A hand-written
11
+ * mirror does not fail when upstream widens like that; it just silently
12
+ * describes a store the plugin will not accept. Re-exporting makes the
13
+ * compiler track it, which is the same lesson the `getCookie` shim taught in
14
+ * this file's history.
15
+ * @experimental
16
+ */
17
+ type ExpoClientStorage, expoClient, setupExpoFocusManager, setupExpoOnlineManager } from '@better-auth/expo/client';
2
18
  /**
3
19
  * Read the current better-auth session token from an Expo auth client, for use
4
20
  * as a **bearer** credential on the Lunora client — `client.setAuthToken(token)`
@@ -9,29 +25,27 @@ export { expoClient, setupExpoFocusManager, setupExpoOnlineManager } from '@bett
9
25
  * better-auth's `bearer` plugin accepts that value verbatim in the
10
26
  * `Authorization` header — so the native client authenticates WITHOUT sending a
11
27
  * `Cookie`, which the runtime's CSRF guard rejects on an `Origin`-less native
12
- * request (React Native sends no `Origin`). Returns `null` when signed out.
28
+ * request (React Native sends no `Origin`). Resolves `null` when signed out.
29
+ *
30
+ * **Async since better-auth 1.7.1**, which changed `@better-auth/expo`'s
31
+ * `getCookie` from `() => string` to `() => Promise<string>` (it reads
32
+ * `SecureStore` asynchronously now). The awaited value is what gets matched:
33
+ * regexing the Promise itself would test `"[object Promise]"`, find no
34
+ * `session_token`, and return `null` on every call — a signed-in native app
35
+ * that silently behaves as anonymous, which is exactly the failure this
36
+ * signature change prevents by making callers await.
13
37
  *
14
38
  * Re-run it whenever the session changes and feed the result to the client (see
15
39
  * the package README):
16
40
  *
17
41
  * ```ts
18
- * const token = expoBearerToken(authClient);
42
+ * const token = await expoBearerToken(authClient);
19
43
  * client.setAuthToken(token);
20
44
  * client.setWsToken(token ?? undefined);
21
45
  * ```
22
46
  * @experimental
23
47
  */
24
48
  declare const expoBearerToken: (authClient: {
25
- getCookie: () => string;
26
- }) => null | string;
27
- /**
28
- * The slice of a key/value store the better-auth Expo plugin needs — the shape
29
- * of `expo-secure-store` (a synchronous `getItem`, plus `setItem`). Pass Expo
30
- * `SecureStore` straight in.
31
- * @experimental
32
- */
33
- interface SecureStorageLike {
34
- getItem: (key: string) => null | string;
35
- setItem: (key: string, value: string) => unknown;
36
- }
37
- export { SecureStorageLike, expoBearerToken };
49
+ getCookie: () => Promise<string> | string;
50
+ }) => Promise<null | string>;
51
+ export { expoBearerToken };
package/dist/auth.mjs CHANGED
@@ -1,2 +1 @@
1
- export { default as expoBearerToken } from './packem_shared/expoBearerToken-B0Zorz--.mjs';
2
- export { expoClient, setupExpoFocusManager, setupExpoOnlineManager } from '@better-auth/expo/client';
1
+ import{default as p}from"./packem_shared/expoBearerToken-CA88JtQp.mjs";import{expoClient as a,setupExpoFocusManager as n,setupExpoOnlineManager as t}from"@better-auth/expo/client";export{p as expoBearerToken,a as expoClient,n as setupExpoFocusManager,t as setupExpoOnlineManager};
package/dist/index.d.mts CHANGED
@@ -47,15 +47,21 @@ interface CreateLunoraClientOptions extends LunoraClientOptions {
47
47
  /**
48
48
  * React Native `AsyncStorage` (or any async key/value store with the same
49
49
  * `getItem`/`setItem`/`removeItem` surface — Expo `SecureStore`, an in-memory
50
- * map in tests). When supplied, the offline mutation queue is persisted here
51
- * via `createAsyncStoragePersistence`, so writes made offline survive an app
52
- * restart. Ignored when an explicit `persistence` is passed.
50
+ * map in tests). When supplied it backs two independent caches: the offline
51
+ * mutation queue (via `createAsyncStoragePersistence`, so writes made offline
52
+ * survive an app restart) and the durable query cache (via
53
+ * `createAsyncStorageQueryCache`, so reads repaint before the socket
54
+ * reconnects).
55
+ *
56
+ * Each is opted out of by its own option, not by the other: `persistence`
57
+ * overrides the queue and `queryCache` overrides the read cache, so
58
+ * `{ persistence: false, storage }` still writes query results to storage.
53
59
  */
54
60
  storage?: AsyncStorageLike;
55
61
  }
56
62
  /**
57
63
  * Construct a `LunoraClient` tuned for React Native / Expo — a thin wrapper over
58
- * `new LunoraClient(options)` that fills in the two things a browser gets for
64
+ * `new LunoraClient(options)` that fills in the three things a browser gets for
59
65
  * free but React Native does not.
60
66
  *
61
67
  * First, a durable offline queue: pass `storage` (React Native `AsyncStorage`,
@@ -64,13 +70,20 @@ interface CreateLunoraClientOptions extends LunoraClientOptions {
64
70
  * IndexedDB, which React Native lacks, so without this the queue lives only in
65
71
  * memory and is lost on reload.
66
72
  *
67
- * Second, credentialed requests: pass `getAuthHeaders` and the returned headers
73
+ * Second, a durable read cache: the same `storage` also backs the query cache
74
+ * through `createAsyncStorageQueryCache`, so cached reads render immediately
75
+ * after a restart while the socket reconnects — mirroring the browser's
76
+ * IndexedDB-backed default.
77
+ *
78
+ * Third, credentialed requests: pass `getAuthHeaders` and the returned headers
68
79
  * ride both the HTTP RPC path and the WebSocket upgrade, since React Native has
69
80
  * no cookie jar to attach a session implicitly.
70
81
  *
71
82
  * Everything on `LunoraClientOptions` is still accepted and passed through; an
72
- * explicit `persistence`, `fetch`, or `WebSocket` takes precedence over the
73
- * convenience derived from `storage` / `getAuthHeaders`. See the package README
83
+ * explicit `persistence`, `queryCache`, `fetch`, or `WebSocket` takes precedence
84
+ * over the convenience derived from `storage` / `getAuthHeaders`. `persistence`
85
+ * and `queryCache` override independently — `storage` backs both, so opting out
86
+ * of one leaves the other wired. See the package README
74
87
  * for a full setup example.
75
88
  * @experimental
76
89
  */
package/dist/index.d.ts CHANGED
@@ -47,15 +47,21 @@ interface CreateLunoraClientOptions extends LunoraClientOptions {
47
47
  /**
48
48
  * React Native `AsyncStorage` (or any async key/value store with the same
49
49
  * `getItem`/`setItem`/`removeItem` surface — Expo `SecureStore`, an in-memory
50
- * map in tests). When supplied, the offline mutation queue is persisted here
51
- * via `createAsyncStoragePersistence`, so writes made offline survive an app
52
- * restart. Ignored when an explicit `persistence` is passed.
50
+ * map in tests). When supplied it backs two independent caches: the offline
51
+ * mutation queue (via `createAsyncStoragePersistence`, so writes made offline
52
+ * survive an app restart) and the durable query cache (via
53
+ * `createAsyncStorageQueryCache`, so reads repaint before the socket
54
+ * reconnects).
55
+ *
56
+ * Each is opted out of by its own option, not by the other: `persistence`
57
+ * overrides the queue and `queryCache` overrides the read cache, so
58
+ * `{ persistence: false, storage }` still writes query results to storage.
53
59
  */
54
60
  storage?: AsyncStorageLike;
55
61
  }
56
62
  /**
57
63
  * Construct a `LunoraClient` tuned for React Native / Expo — a thin wrapper over
58
- * `new LunoraClient(options)` that fills in the two things a browser gets for
64
+ * `new LunoraClient(options)` that fills in the three things a browser gets for
59
65
  * free but React Native does not.
60
66
  *
61
67
  * First, a durable offline queue: pass `storage` (React Native `AsyncStorage`,
@@ -64,13 +70,20 @@ interface CreateLunoraClientOptions extends LunoraClientOptions {
64
70
  * IndexedDB, which React Native lacks, so without this the queue lives only in
65
71
  * memory and is lost on reload.
66
72
  *
67
- * Second, credentialed requests: pass `getAuthHeaders` and the returned headers
73
+ * Second, a durable read cache: the same `storage` also backs the query cache
74
+ * through `createAsyncStorageQueryCache`, so cached reads render immediately
75
+ * after a restart while the socket reconnects — mirroring the browser's
76
+ * IndexedDB-backed default.
77
+ *
78
+ * Third, credentialed requests: pass `getAuthHeaders` and the returned headers
68
79
  * ride both the HTTP RPC path and the WebSocket upgrade, since React Native has
69
80
  * no cookie jar to attach a session implicitly.
70
81
  *
71
82
  * Everything on `LunoraClientOptions` is still accepted and passed through; an
72
- * explicit `persistence`, `fetch`, or `WebSocket` takes precedence over the
73
- * convenience derived from `storage` / `getAuthHeaders`. See the package README
83
+ * explicit `persistence`, `queryCache`, `fetch`, or `WebSocket` takes precedence
84
+ * over the convenience derived from `storage` / `getAuthHeaders`. `persistence`
85
+ * and `queryCache` override independently — `storage` backs both, so opting out
86
+ * of one leaves the other wired. See the package README
74
87
  * for a full setup example.
75
88
  * @experimental
76
89
  */
package/dist/index.mjs CHANGED
@@ -1,2 +1 @@
1
- export { createLunoraClient } from './packem_shared/createLunoraClient-D9i-IKbc.mjs';
2
- export * from '@lunora/react';
1
+ import{createLunoraClient as o}from"./packem_shared/createLunoraClient-DriS3kEf.mjs";export*from"@lunora/react";export{o as createLunoraClient};
@@ -0,0 +1 @@
1
+ import{LunoraClient as h,createAsyncStorageQueryCache as d,createAsyncStoragePersistence as u}from"@lunora/client";const f=(r,t)=>(c,e)=>{const s=t();if(!s)return r(c,e);const o=new Headers(s);return e?.headers&&new Headers(e.headers).forEach((n,a)=>{o.set(a,n)}),r(c,{...e,headers:o})},i=(r,t)=>class extends r{constructor(e,s){const o=t();super(e,s,o?{headers:o}:void 0)}},k=r=>{const{getAuthHeaders:t,storage:c,...e}=r,s=t&&e.fetch===void 0&&typeof fetch=="function"?f(fetch.bind(globalThis),t):e.fetch,o=t&&e.WebSocket===void 0&&typeof WebSocket=="function"?i(WebSocket,t):e.WebSocket;return new h({...e,persistence:e.persistence??(c?u({storage:c}):void 0),queryCache:e.queryCache??(c?d({storage:c}):void 0),fetch:s,WebSocket:o})};export{k as createLunoraClient,f as withAuthHeaders,i as withAuthWebSocket};
@@ -0,0 +1 @@
1
+ const t=/(?:^|;)[^;=]*session_token=([^;]+)/,n=async e=>t.exec(await e.getCookie())?.[1]??null;export{n as default};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/react-native",
3
- "version": "1.0.0-alpha.8",
3
+ "version": "1.0.0-alpha.80",
4
4
  "description": "React Native / Expo integration for Lunora: an AsyncStorage-backed client factory, the useQuery/useMutation/useSubscription hooks, and a one-call better-auth Expo client",
5
5
  "keywords": [
6
6
  "cloudflare",
@@ -50,13 +50,13 @@
50
50
  "access": "public"
51
51
  },
52
52
  "dependencies": {
53
- "@lunora/client": "1.0.0-alpha.28",
54
- "@lunora/react": "1.0.0-alpha.32"
53
+ "@lunora/client": "1.0.0-alpha.101",
54
+ "@lunora/react": "1.0.0-alpha.106"
55
55
  },
56
56
  "peerDependencies": {
57
- "@better-auth/expo": "^1.6.23",
57
+ "@better-auth/expo": "^1.7.1",
58
58
  "@tanstack/react-query": "^5.101.0",
59
- "better-auth": "^1.6.23",
59
+ "better-auth": "^1.7.1",
60
60
  "react": "^19.2.7"
61
61
  },
62
62
  "peerDependenciesMeta": {
@@ -1,36 +0,0 @@
1
- import { LunoraClient, createAsyncStoragePersistence } from '@lunora/client';
2
-
3
- const withAuthHeaders = (fetchImpl, getAuthHeaders) => (input, init) => {
4
- const extra = getAuthHeaders();
5
- if (!extra) {
6
- return fetchImpl(input, init);
7
- }
8
- const merged = new Headers(extra);
9
- if (init?.headers) {
10
- new Headers(init.headers).forEach((value, key) => {
11
- merged.set(key, value);
12
- });
13
- }
14
- return fetchImpl(input, { ...init, headers: merged });
15
- };
16
- const withAuthWebSocket = (WebSocketImpl, getAuthHeaders) => class AuthWebSocket extends WebSocketImpl {
17
- constructor(url, protocols) {
18
- const headers = getAuthHeaders();
19
- super(url, protocols, headers ? { headers } : void 0);
20
- }
21
- };
22
- const createLunoraClient = (options) => {
23
- const { getAuthHeaders, storage, ...rest } = options;
24
- const authedFetch = getAuthHeaders && rest.fetch === void 0 && typeof fetch === "function" ? withAuthHeaders(fetch.bind(globalThis), getAuthHeaders) : rest.fetch;
25
- const authedWebSocket = getAuthHeaders && rest.WebSocket === void 0 && typeof WebSocket === "function" ? withAuthWebSocket(WebSocket, getAuthHeaders) : rest.WebSocket;
26
- return new LunoraClient({
27
- ...rest,
28
- // Auto-wire AsyncStorage persistence unless the caller passed an explicit
29
- // `persistence` (including `false` to opt out).
30
- persistence: rest.persistence ?? (storage ? createAsyncStoragePersistence({ storage }) : void 0),
31
- fetch: authedFetch,
32
- WebSocket: authedWebSocket
33
- });
34
- };
35
-
36
- export { createLunoraClient, withAuthHeaders, withAuthWebSocket };
@@ -1,7 +0,0 @@
1
- const SESSION_TOKEN_COOKIE = /(?:^|;)[^;=]*session_token=([^;]+)/;
2
- const expoBearerToken = (authClient) => {
3
- const match = SESSION_TOKEN_COOKIE.exec(authClient.getCookie());
4
- return match?.[1] ?? null;
5
- };
6
-
7
- export { expoBearerToken as default };