@lunora/react-native 1.0.0-alpha.1 → 1.0.0-alpha.100
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 +58 -14
- package/dist/auth.d.mts +49 -33
- package/dist/auth.d.ts +49 -33
- package/dist/auth.mjs +1 -2
- package/dist/index.d.mts +127 -62
- package/dist/index.d.ts +127 -62
- package/dist/index.mjs +1 -2
- package/dist/packem_shared/createLunoraClient-WOgHmG35.mjs +1 -0
- package/dist/packem_shared/expoBearerToken-CA88JtQp.mjs +1 -0
- package/package.json +5 -5
- package/dist/packem_shared/createLunoraClient-D9i-IKbc.mjs +0 -36
- package/dist/packem_shared/expoBearerToken-B0Zorz--.mjs +0 -7
package/README.md
CHANGED
|
@@ -6,16 +6,19 @@
|
|
|
6
6
|
|
|
7
7
|
</div>
|
|
8
8
|
|
|
9
|
+
> **Experimental** — this package is outside the Lunora 1.0 stability promise: its API may change in any release, without a major version bump.
|
|
10
|
+
|
|
9
11
|
---
|
|
10
12
|
|
|
11
13
|
The same live hooks you use on the web — `useQuery`, `useMutation`,
|
|
12
14
|
`useSubscription`, `useAuth`, `usePresence`, … — running on your phone, plus the
|
|
13
15
|
two seams a native app needs that a browser gives you for free: a durable offline
|
|
14
|
-
queue backed by `AsyncStorage`, and credentialed requests (
|
|
15
|
-
|
|
16
|
+
queue backed by `AsyncStorage`, and credentialed requests (the session is a
|
|
17
|
+
bearer, attached explicitly).
|
|
16
18
|
|
|
17
|
-
This package **re-exports the
|
|
18
|
-
|
|
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:
|
|
19
22
|
|
|
20
23
|
- `createLunoraClient(options)` — a `LunoraClient` factory tuned for React
|
|
21
24
|
Native.
|
|
@@ -87,10 +90,9 @@ other clients write, and `send` is optimistic and offline-safe.
|
|
|
87
90
|
|
|
88
91
|
## Authentication (better-auth + Expo)
|
|
89
92
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
CSRF guard rejects on an `Origin`-less native request (see [Why a bearer token](#why-a-bearer-token)).
|
|
93
|
+
The session is sent as a **bearer** token: the HTTP RPC carries it in the
|
|
94
|
+
`Authorization` header and the live socket carries it in the `?token=` query
|
|
95
|
+
param (see [Why a bearer token](#why-a-bearer-token)).
|
|
94
96
|
|
|
95
97
|
```tsx
|
|
96
98
|
// auth.ts
|
|
@@ -129,10 +131,24 @@ import { client } from "./lunora";
|
|
|
129
131
|
function Root() {
|
|
130
132
|
const { data: session } = authClient.useSession();
|
|
131
133
|
|
|
134
|
+
// `expoBearerToken` is async since better-auth 1.7.1, and an async function
|
|
135
|
+
// is not a valid effect cleanup return — so kick off a promise instead.
|
|
136
|
+
// `cancelled` is load-bearing: two session changes in quick succession leave
|
|
137
|
+
// two reads in flight, and without it the slower one reinstates the previous
|
|
138
|
+
// session's token.
|
|
132
139
|
useEffect(() => {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
140
|
+
let cancelled = false;
|
|
141
|
+
|
|
142
|
+
void (async () => {
|
|
143
|
+
const token = await expoBearerToken(authClient);
|
|
144
|
+
if (cancelled) return;
|
|
145
|
+
client.setAuthToken(token); // HTTP `Authorization: Bearer …`
|
|
146
|
+
client.setWsToken(token ?? undefined); // WS `?token=…`
|
|
147
|
+
})();
|
|
148
|
+
|
|
149
|
+
return () => {
|
|
150
|
+
cancelled = true;
|
|
151
|
+
};
|
|
136
152
|
}, [session]);
|
|
137
153
|
|
|
138
154
|
// …render the app
|
|
@@ -165,11 +181,22 @@ import { expo } from "@better-auth/expo";
|
|
|
165
181
|
|
|
166
182
|
Lunora's runtime enables a CSRF Origin-check by default — it rejects any
|
|
167
183
|
state-changing HTTP request or WebSocket upgrade that carries a `Cookie` but no
|
|
168
|
-
trusted `Origin`. React Native sends no `Origin`, so a cookie-based credential
|
|
169
|
-
|
|
170
|
-
|
|
184
|
+
trusted `Origin`. React Native sends no `Origin`, so a cookie-based credential is
|
|
185
|
+
**rejected** once signed in. A bearer token is the credential instead, and it
|
|
186
|
+
works identically on `react-native-web` (the browser lets you set
|
|
171
187
|
`Authorization`, and the token rides `?token=` on the socket).
|
|
172
188
|
|
|
189
|
+
Using a bearer does **not** by itself guarantee the absence of a `Cookie` header.
|
|
190
|
+
React Native has a real cookie jar — `fetch` is backed by the platform HTTP stack
|
|
191
|
+
(`NSURLSession` / `OkHttp`) and its shared cookie store — so the `Set-Cookie` a
|
|
192
|
+
better-auth sign-in returns is kept and re-attached to later requests, and the
|
|
193
|
+
guard then 403s every state-changing RPC with `FORBIDDEN_ORIGIN`. It is
|
|
194
|
+
intermittent (it depends on whether the jar holds the cookie that launch), and
|
|
195
|
+
`security.csrf.trustedOrigins` cannot fix it: the trust list is only consulted
|
|
196
|
+
for an `Origin` that was actually received, and a missing one is rejected
|
|
197
|
+
outright. `createLunoraClient` closes this by sending `credentials: "omit"` on
|
|
198
|
+
every request; if you pass your own `fetch`, wrap it in `withoutAmbientCookies`.
|
|
199
|
+
|
|
173
200
|
### TanStack Query focus / online managers
|
|
174
201
|
|
|
175
202
|
React Native doesn't fire the browser's `focus` / `online` events, so Query
|
|
@@ -197,6 +224,23 @@ Everything on `LunoraClientOptions` (`url`, `wsUrl`, `authBasePath`,
|
|
|
197
224
|
An explicit `persistence`, `fetch`, or `WebSocket` always takes precedence over
|
|
198
225
|
the convenience derived from `storage` / `getAuthHeaders`.
|
|
199
226
|
|
|
227
|
+
### Re-exported `@lunora/react` surface
|
|
228
|
+
|
|
229
|
+
Every hook, provider, and auth gate from [`@lunora/react`](../react) is
|
|
230
|
+
re-exported here, wholesale — a new hook there lands on native with no edit to
|
|
231
|
+
this package.
|
|
232
|
+
|
|
233
|
+
The payment kit is **not** part of that surface: `CheckoutButton`,
|
|
234
|
+
`CustomerPortalButton`, and `useCheckout` render a DOM `<button>` and navigate
|
|
235
|
+
via `globalThis.location`, so they ship from the `@lunora/react/payment` subpath
|
|
236
|
+
rather than the `@lunora/react` root, and nothing re-exports them here. Drive
|
|
237
|
+
purchases through the platform's own flow (or a WebView pointed at the web
|
|
238
|
+
checkout URL).
|
|
239
|
+
|
|
240
|
+
`useVoiceAgent` is re-exported but its defaults are Web APIs (`getUserMedia`,
|
|
241
|
+
Web Audio, `WebSocket`): on native, pass your own `createMicrophone` /
|
|
242
|
+
`createSpeaker` implementations.
|
|
243
|
+
|
|
200
244
|
### `@lunora/react-native/auth`
|
|
201
245
|
|
|
202
246
|
- `expoBearerToken(authClient)` — reads the better-auth Expo session token (from
|
package/dist/auth.d.mts
CHANGED
|
@@ -1,35 +1,51 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export {
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* `
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
* const token = expoBearerToken(authClient);
|
|
19
|
-
* client.setAuthToken(token);
|
|
20
|
-
* client.setWsToken(token ?? undefined);
|
|
21
|
-
* ```
|
|
22
|
-
*/
|
|
23
|
-
declare const expoBearerToken: (authClient: {
|
|
24
|
-
getCookie: () => string;
|
|
25
|
-
}) => null | string;
|
|
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';
|
|
26
18
|
/**
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* `
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
19
|
+
* Read the current better-auth session token from an Expo auth client, for use
|
|
20
|
+
* as a **bearer** credential on the Lunora client — `client.setAuthToken(token)`
|
|
21
|
+
* for HTTP RPC and `client.setWsToken(token ?? undefined)` for the live socket.
|
|
22
|
+
*
|
|
23
|
+
* The Expo plugin persists the session as a cookie in `SecureStore`;
|
|
24
|
+
* `getCookie()` exposes it, and this pulls the `session_token` value out.
|
|
25
|
+
* better-auth's `bearer` plugin accepts that value verbatim in the
|
|
26
|
+
* `Authorization` header — so the native client authenticates WITHOUT sending a
|
|
27
|
+
* `Cookie`, which the runtime's CSRF guard rejects on an `Origin`-less native
|
|
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.
|
|
37
|
+
*
|
|
38
|
+
* Re-run it whenever the session changes and feed the result to the client (see
|
|
39
|
+
* the package README):
|
|
40
|
+
*
|
|
41
|
+
* ```ts
|
|
42
|
+
* const token = await expoBearerToken(authClient);
|
|
43
|
+
* client.setAuthToken(token);
|
|
44
|
+
* client.setWsToken(token ?? undefined);
|
|
45
|
+
* ```
|
|
46
|
+
* @experimental
|
|
47
|
+
*/
|
|
48
|
+
declare const expoBearerToken: (authClient: {
|
|
49
|
+
getCookie: () => Promise<string> | string;
|
|
50
|
+
}) => Promise<null | string>;
|
|
51
|
+
export { expoBearerToken };
|
package/dist/auth.d.ts
CHANGED
|
@@ -1,35 +1,51 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export {
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* `
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
* const token = expoBearerToken(authClient);
|
|
19
|
-
* client.setAuthToken(token);
|
|
20
|
-
* client.setWsToken(token ?? undefined);
|
|
21
|
-
* ```
|
|
22
|
-
*/
|
|
23
|
-
declare const expoBearerToken: (authClient: {
|
|
24
|
-
getCookie: () => string;
|
|
25
|
-
}) => null | string;
|
|
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';
|
|
26
18
|
/**
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* `
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
19
|
+
* Read the current better-auth session token from an Expo auth client, for use
|
|
20
|
+
* as a **bearer** credential on the Lunora client — `client.setAuthToken(token)`
|
|
21
|
+
* for HTTP RPC and `client.setWsToken(token ?? undefined)` for the live socket.
|
|
22
|
+
*
|
|
23
|
+
* The Expo plugin persists the session as a cookie in `SecureStore`;
|
|
24
|
+
* `getCookie()` exposes it, and this pulls the `session_token` value out.
|
|
25
|
+
* better-auth's `bearer` plugin accepts that value verbatim in the
|
|
26
|
+
* `Authorization` header — so the native client authenticates WITHOUT sending a
|
|
27
|
+
* `Cookie`, which the runtime's CSRF guard rejects on an `Origin`-less native
|
|
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.
|
|
37
|
+
*
|
|
38
|
+
* Re-run it whenever the session changes and feed the result to the client (see
|
|
39
|
+
* the package README):
|
|
40
|
+
*
|
|
41
|
+
* ```ts
|
|
42
|
+
* const token = await expoBearerToken(authClient);
|
|
43
|
+
* client.setAuthToken(token);
|
|
44
|
+
* client.setWsToken(token ?? undefined);
|
|
45
|
+
* ```
|
|
46
|
+
* @experimental
|
|
47
|
+
*/
|
|
48
|
+
declare const expoBearerToken: (authClient: {
|
|
49
|
+
getCookie: () => Promise<string> | string;
|
|
50
|
+
}) => Promise<null | string>;
|
|
51
|
+
export { expoBearerToken };
|
package/dist/auth.mjs
CHANGED
|
@@ -1,2 +1 @@
|
|
|
1
|
-
|
|
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
|
@@ -1,75 +1,140 @@
|
|
|
1
1
|
import { AsyncStorageLike, LunoraClientOptions, LunoraClient } from '@lunora/client';
|
|
2
2
|
export * from '@lunora/react';
|
|
3
3
|
/**
|
|
4
|
-
* A `() => headers` factory the React Native client threads onto every HTTP RPC
|
|
5
|
-
* request *and* the WebSocket upgrade — a generic escape hatch for attaching a
|
|
6
|
-
* **custom** credential header (an API-gateway key, a proxy token, …)
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
|
|
4
|
+
* A `() => headers` factory the React Native client threads onto every HTTP RPC
|
|
5
|
+
* request *and* the WebSocket upgrade — a generic escape hatch for attaching a
|
|
6
|
+
* **custom** credential header (an API-gateway key, a proxy token, …). Return
|
|
7
|
+
* `undefined` (or an empty object) when there's nothing to attach.
|
|
8
|
+
*
|
|
9
|
+
* For better-auth Expo sessions, prefer a **bearer** token instead: read it with
|
|
10
|
+
* `@lunora/react-native/auth`'s `expoBearerToken` and feed it to
|
|
11
|
+
* `client.setAuthToken` / `setWsToken` (see the package README). Either way the
|
|
12
|
+
* client sends `credentials: "omit"`, so the platform cookie jar never attaches a
|
|
13
|
+
* session cookie the runtime's CSRF guard would reject on an `Origin`-less native
|
|
14
|
+
* request.
|
|
15
|
+
* @experimental
|
|
16
|
+
*/
|
|
16
17
|
type AuthHeadersFactory = () => Record<string, string> | undefined;
|
|
17
18
|
/**
|
|
18
|
-
* Options for `createLunoraClient` — the React Native / Expo counterpart to
|
|
19
|
-
* constructing a `LunoraClient` directly. It is `LunoraClientOptions` with two
|
|
20
|
-
* React-Native-shaped conveniences layered on. `storage` wires the
|
|
21
|
-
* AsyncStorage-backed offline-queue persistence for you (the browser default
|
|
22
|
-
* auto-probes IndexedDB, which React Native lacks, so without this the queue
|
|
23
|
-
* stays in memory and is lost on reload). `getAuthHeaders` is a generic escape
|
|
24
|
-
* hatch for a custom credential header on both the HTTP RPC path and the
|
|
25
|
-
* WebSocket upgrade; for better-auth sessions prefer a bearer token
|
|
26
|
-
* (`expoBearerToken` + the client's `setAuthToken` / `setWsToken`).
|
|
27
|
-
*
|
|
28
|
-
* The `persistence`, `fetch`, and `WebSocket` fields of `LunoraClientOptions`
|
|
29
|
-
* are still accepted and take precedence when you need full control; the two
|
|
30
|
-
* conveniences are sugar over exactly those seams.
|
|
31
|
-
|
|
19
|
+
* Options for `createLunoraClient` — the React Native / Expo counterpart to
|
|
20
|
+
* constructing a `LunoraClient` directly. It is `LunoraClientOptions` with two
|
|
21
|
+
* React-Native-shaped conveniences layered on. `storage` wires the
|
|
22
|
+
* AsyncStorage-backed offline-queue persistence for you (the browser default
|
|
23
|
+
* auto-probes IndexedDB, which React Native lacks, so without this the queue
|
|
24
|
+
* stays in memory and is lost on reload). `getAuthHeaders` is a generic escape
|
|
25
|
+
* hatch for a custom credential header on both the HTTP RPC path and the
|
|
26
|
+
* WebSocket upgrade; for better-auth sessions prefer a bearer token
|
|
27
|
+
* (`expoBearerToken` + the client's `setAuthToken` / `setWsToken`).
|
|
28
|
+
*
|
|
29
|
+
* The `persistence`, `fetch`, and `WebSocket` fields of `LunoraClientOptions`
|
|
30
|
+
* are still accepted and take precedence when you need full control; the two
|
|
31
|
+
* conveniences are sugar over exactly those seams.
|
|
32
|
+
* @experimental
|
|
33
|
+
*/
|
|
32
34
|
interface CreateLunoraClientOptions extends LunoraClientOptions {
|
|
33
35
|
/**
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
36
|
+
* Attaches a credential to every HTTP RPC request and to the WebSocket
|
|
37
|
+
* upgrade request. See `AuthHeadersFactory`. When omitted, requests go out
|
|
38
|
+
* with only whatever `LunoraClient` already sets (its bearer token from
|
|
39
|
+
* `setAuthToken`, if any).
|
|
40
|
+
*
|
|
41
|
+
* Merged UNDER the client's own headers on the HTTP path (an explicit
|
|
42
|
+
* `setAuthToken` bearer wins over a same-named header here); applied to the
|
|
43
|
+
* WebSocket upgrade via a wrapping `WebSocket`, since the socket has no other
|
|
44
|
+
* way to carry credentials in React Native.
|
|
45
|
+
*/
|
|
44
46
|
getAuthHeaders?: AuthHeadersFactory;
|
|
45
47
|
/**
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
48
|
+
* React Native `AsyncStorage` (or any async key/value store with the same
|
|
49
|
+
* `getItem`/`setItem`/`removeItem` surface — Expo `SecureStore`, an in-memory
|
|
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.
|
|
59
|
+
*/
|
|
52
60
|
storage?: AsyncStorageLike;
|
|
53
61
|
}
|
|
54
62
|
/**
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
|
|
63
|
+
* Wrap a `fetch` so no request carries the platform's ambient cookie credential.
|
|
64
|
+
*
|
|
65
|
+
* React Native **does** have a cookie jar — its `fetch` is backed by the
|
|
66
|
+
* platform HTTP stack (`NSURLSession` / `OkHttp`), which owns a shared,
|
|
67
|
+
* persistent cookie store — so a `Set-Cookie` from a better-auth sign-in is kept
|
|
68
|
+
* and re-attached to later requests automatically. The package's own docs used
|
|
69
|
+
* to assert the opposite, and the bearer design leaned on it: "there is no jar,
|
|
70
|
+
* therefore no `Cookie` header, therefore the runtime's CSRF guard never sees
|
|
71
|
+
* one".
|
|
72
|
+
*
|
|
73
|
+
* It does see one. The guard rejects an unsafe, cookie-bearing request whose
|
|
74
|
+
* `Origin` is missing or untrusted, and a native request sends no `Origin` — so
|
|
75
|
+
* every state-changing RPC 403s with `FORBIDDEN_ORIGIN` for as long as the jar
|
|
76
|
+
* holds that cookie. `security.csrf.trustedOrigins` cannot fix it: the trust list
|
|
77
|
+
* is only consulted for an `Origin` that was actually received, and a missing one
|
|
78
|
+
* is rejected outright. It is also intermittent — it depends on whether the jar
|
|
79
|
+
* happens to hold the session cookie that launch — which is what let it hide.
|
|
80
|
+
*
|
|
81
|
+
* `credentials: "omit"` is the fix, and it costs nothing on native: the session
|
|
82
|
+
* is a bearer there (`setAuthToken` / `setWsToken`), so the cookie was never the
|
|
83
|
+
* credential — only an accident of transport riding along. Wrapped INNERMOST by
|
|
84
|
+
* {@link createLunoraClient}, so it runs after every other layer and also
|
|
85
|
+
* overrides the `credentials: "include"` `@lunora/client` sends on its
|
|
86
|
+
* `get-session` probe.
|
|
87
|
+
*
|
|
88
|
+
* **Scope.** This covers `fetch`, which is the whole HTTP RPC/REST/storage
|
|
89
|
+
* surface. It does NOT cover the WebSocket upgrade — React Native attaches the
|
|
90
|
+
* same jar's cookie to the handshake and exposes no per-socket opt-out — nor
|
|
91
|
+
* `httpStream` imported standalone from `@lunora/client`, which resolves
|
|
92
|
+
* `globalThis.fetch` itself. Neither is a live 403 today: React Native sends an
|
|
93
|
+
* `Origin` equal to the server's on a WS handshake, and better-auth's `bearer`
|
|
94
|
+
* plugin overwrites the session cookie with the bearer rather than deferring to
|
|
95
|
+
* it. Both are noted so the guarantee is not read wider than it is.
|
|
96
|
+
*
|
|
97
|
+
* {@link createLunoraClient} applies it only on native — see
|
|
98
|
+
* {@link isNativeRuntime} — and only to the global `fetch`. Under
|
|
99
|
+
* `react-native-web` the jar is the browser's, `Origin` IS sent, the CSRF guard
|
|
100
|
+
* never fires, and a cookie session is a legitimate setup that this would
|
|
101
|
+
* silently sign out (`getCurrentUser` deliberately sends `credentials:
|
|
102
|
+
* "include"`). A caller-supplied `fetch` is never wrapped either, so a
|
|
103
|
+
* cookie-forwarding SSR transport keeps its credentials; apply this yourself if
|
|
104
|
+
* you want it.
|
|
105
|
+
*/
|
|
106
|
+
declare const withoutAmbientCookies: (fetchImpl: typeof fetch) => typeof fetch;
|
|
107
|
+
/**
|
|
108
|
+
* Construct a `LunoraClient` tuned for React Native / Expo — a thin wrapper over
|
|
109
|
+
* `new LunoraClient(options)` that fills in the three things a browser gets for
|
|
110
|
+
* free but React Native does not.
|
|
111
|
+
*
|
|
112
|
+
* First, a durable offline queue: pass `storage` (React Native `AsyncStorage`,
|
|
113
|
+
* or any async key/value store) and the offline mutation queue is persisted
|
|
114
|
+
* through `createAsyncStoragePersistence` — the browser default auto-probes
|
|
115
|
+
* IndexedDB, which React Native lacks, so without this the queue lives only in
|
|
116
|
+
* memory and is lost on reload.
|
|
117
|
+
*
|
|
118
|
+
* Second, a durable read cache: the same `storage` also backs the query cache
|
|
119
|
+
* through `createAsyncStorageQueryCache`, so cached reads render immediately
|
|
120
|
+
* after a restart while the socket reconnects — mirroring the browser's
|
|
121
|
+
* IndexedDB-backed default.
|
|
122
|
+
*
|
|
123
|
+
* Third, credentialed requests: the session is a bearer on this platform, so the
|
|
124
|
+
* returned `fetch` is wrapped in {@link withoutAmbientCookies} (React Native's
|
|
125
|
+
* cookie jar is real, and a stray session cookie 403s every state-changing RPC —
|
|
126
|
+
* see there), and passing `getAuthHeaders` additionally rides its headers on both
|
|
127
|
+
* the HTTP RPC path and the WebSocket upgrade.
|
|
128
|
+
*
|
|
129
|
+
* Everything on `LunoraClientOptions` is still accepted and passed through; an
|
|
130
|
+
* explicit `persistence`, `queryCache`, `fetch`, or `WebSocket` replaces the
|
|
131
|
+
* transport derived from `storage`. A `getAuthHeaders` factory is layered over a
|
|
132
|
+
* caller-supplied `fetch` rather than ignored — the previous behaviour dropped
|
|
133
|
+
* the credential on HTTP while still injecting it on the WS upgrade. `persistence`
|
|
134
|
+
* and `queryCache` override independently — `storage` backs both, so opting out
|
|
135
|
+
* of one leaves the other wired. See the package README
|
|
136
|
+
* for a full setup example.
|
|
137
|
+
* @experimental
|
|
138
|
+
*/
|
|
74
139
|
declare const createLunoraClient: (options: CreateLunoraClientOptions) => LunoraClient;
|
|
75
|
-
export { type AuthHeadersFactory, type CreateLunoraClientOptions, createLunoraClient };
|
|
140
|
+
export { type AuthHeadersFactory, type CreateLunoraClientOptions, createLunoraClient, withoutAmbientCookies };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,75 +1,140 @@
|
|
|
1
1
|
import { AsyncStorageLike, LunoraClientOptions, LunoraClient } from '@lunora/client';
|
|
2
2
|
export * from '@lunora/react';
|
|
3
3
|
/**
|
|
4
|
-
* A `() => headers` factory the React Native client threads onto every HTTP RPC
|
|
5
|
-
* request *and* the WebSocket upgrade — a generic escape hatch for attaching a
|
|
6
|
-
* **custom** credential header (an API-gateway key, a proxy token, …)
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
|
|
4
|
+
* A `() => headers` factory the React Native client threads onto every HTTP RPC
|
|
5
|
+
* request *and* the WebSocket upgrade — a generic escape hatch for attaching a
|
|
6
|
+
* **custom** credential header (an API-gateway key, a proxy token, …). Return
|
|
7
|
+
* `undefined` (or an empty object) when there's nothing to attach.
|
|
8
|
+
*
|
|
9
|
+
* For better-auth Expo sessions, prefer a **bearer** token instead: read it with
|
|
10
|
+
* `@lunora/react-native/auth`'s `expoBearerToken` and feed it to
|
|
11
|
+
* `client.setAuthToken` / `setWsToken` (see the package README). Either way the
|
|
12
|
+
* client sends `credentials: "omit"`, so the platform cookie jar never attaches a
|
|
13
|
+
* session cookie the runtime's CSRF guard would reject on an `Origin`-less native
|
|
14
|
+
* request.
|
|
15
|
+
* @experimental
|
|
16
|
+
*/
|
|
16
17
|
type AuthHeadersFactory = () => Record<string, string> | undefined;
|
|
17
18
|
/**
|
|
18
|
-
* Options for `createLunoraClient` — the React Native / Expo counterpart to
|
|
19
|
-
* constructing a `LunoraClient` directly. It is `LunoraClientOptions` with two
|
|
20
|
-
* React-Native-shaped conveniences layered on. `storage` wires the
|
|
21
|
-
* AsyncStorage-backed offline-queue persistence for you (the browser default
|
|
22
|
-
* auto-probes IndexedDB, which React Native lacks, so without this the queue
|
|
23
|
-
* stays in memory and is lost on reload). `getAuthHeaders` is a generic escape
|
|
24
|
-
* hatch for a custom credential header on both the HTTP RPC path and the
|
|
25
|
-
* WebSocket upgrade; for better-auth sessions prefer a bearer token
|
|
26
|
-
* (`expoBearerToken` + the client's `setAuthToken` / `setWsToken`).
|
|
27
|
-
*
|
|
28
|
-
* The `persistence`, `fetch`, and `WebSocket` fields of `LunoraClientOptions`
|
|
29
|
-
* are still accepted and take precedence when you need full control; the two
|
|
30
|
-
* conveniences are sugar over exactly those seams.
|
|
31
|
-
|
|
19
|
+
* Options for `createLunoraClient` — the React Native / Expo counterpart to
|
|
20
|
+
* constructing a `LunoraClient` directly. It is `LunoraClientOptions` with two
|
|
21
|
+
* React-Native-shaped conveniences layered on. `storage` wires the
|
|
22
|
+
* AsyncStorage-backed offline-queue persistence for you (the browser default
|
|
23
|
+
* auto-probes IndexedDB, which React Native lacks, so without this the queue
|
|
24
|
+
* stays in memory and is lost on reload). `getAuthHeaders` is a generic escape
|
|
25
|
+
* hatch for a custom credential header on both the HTTP RPC path and the
|
|
26
|
+
* WebSocket upgrade; for better-auth sessions prefer a bearer token
|
|
27
|
+
* (`expoBearerToken` + the client's `setAuthToken` / `setWsToken`).
|
|
28
|
+
*
|
|
29
|
+
* The `persistence`, `fetch`, and `WebSocket` fields of `LunoraClientOptions`
|
|
30
|
+
* are still accepted and take precedence when you need full control; the two
|
|
31
|
+
* conveniences are sugar over exactly those seams.
|
|
32
|
+
* @experimental
|
|
33
|
+
*/
|
|
32
34
|
interface CreateLunoraClientOptions extends LunoraClientOptions {
|
|
33
35
|
/**
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
36
|
+
* Attaches a credential to every HTTP RPC request and to the WebSocket
|
|
37
|
+
* upgrade request. See `AuthHeadersFactory`. When omitted, requests go out
|
|
38
|
+
* with only whatever `LunoraClient` already sets (its bearer token from
|
|
39
|
+
* `setAuthToken`, if any).
|
|
40
|
+
*
|
|
41
|
+
* Merged UNDER the client's own headers on the HTTP path (an explicit
|
|
42
|
+
* `setAuthToken` bearer wins over a same-named header here); applied to the
|
|
43
|
+
* WebSocket upgrade via a wrapping `WebSocket`, since the socket has no other
|
|
44
|
+
* way to carry credentials in React Native.
|
|
45
|
+
*/
|
|
44
46
|
getAuthHeaders?: AuthHeadersFactory;
|
|
45
47
|
/**
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
48
|
+
* React Native `AsyncStorage` (or any async key/value store with the same
|
|
49
|
+
* `getItem`/`setItem`/`removeItem` surface — Expo `SecureStore`, an in-memory
|
|
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.
|
|
59
|
+
*/
|
|
52
60
|
storage?: AsyncStorageLike;
|
|
53
61
|
}
|
|
54
62
|
/**
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
|
|
63
|
+
* Wrap a `fetch` so no request carries the platform's ambient cookie credential.
|
|
64
|
+
*
|
|
65
|
+
* React Native **does** have a cookie jar — its `fetch` is backed by the
|
|
66
|
+
* platform HTTP stack (`NSURLSession` / `OkHttp`), which owns a shared,
|
|
67
|
+
* persistent cookie store — so a `Set-Cookie` from a better-auth sign-in is kept
|
|
68
|
+
* and re-attached to later requests automatically. The package's own docs used
|
|
69
|
+
* to assert the opposite, and the bearer design leaned on it: "there is no jar,
|
|
70
|
+
* therefore no `Cookie` header, therefore the runtime's CSRF guard never sees
|
|
71
|
+
* one".
|
|
72
|
+
*
|
|
73
|
+
* It does see one. The guard rejects an unsafe, cookie-bearing request whose
|
|
74
|
+
* `Origin` is missing or untrusted, and a native request sends no `Origin` — so
|
|
75
|
+
* every state-changing RPC 403s with `FORBIDDEN_ORIGIN` for as long as the jar
|
|
76
|
+
* holds that cookie. `security.csrf.trustedOrigins` cannot fix it: the trust list
|
|
77
|
+
* is only consulted for an `Origin` that was actually received, and a missing one
|
|
78
|
+
* is rejected outright. It is also intermittent — it depends on whether the jar
|
|
79
|
+
* happens to hold the session cookie that launch — which is what let it hide.
|
|
80
|
+
*
|
|
81
|
+
* `credentials: "omit"` is the fix, and it costs nothing on native: the session
|
|
82
|
+
* is a bearer there (`setAuthToken` / `setWsToken`), so the cookie was never the
|
|
83
|
+
* credential — only an accident of transport riding along. Wrapped INNERMOST by
|
|
84
|
+
* {@link createLunoraClient}, so it runs after every other layer and also
|
|
85
|
+
* overrides the `credentials: "include"` `@lunora/client` sends on its
|
|
86
|
+
* `get-session` probe.
|
|
87
|
+
*
|
|
88
|
+
* **Scope.** This covers `fetch`, which is the whole HTTP RPC/REST/storage
|
|
89
|
+
* surface. It does NOT cover the WebSocket upgrade — React Native attaches the
|
|
90
|
+
* same jar's cookie to the handshake and exposes no per-socket opt-out — nor
|
|
91
|
+
* `httpStream` imported standalone from `@lunora/client`, which resolves
|
|
92
|
+
* `globalThis.fetch` itself. Neither is a live 403 today: React Native sends an
|
|
93
|
+
* `Origin` equal to the server's on a WS handshake, and better-auth's `bearer`
|
|
94
|
+
* plugin overwrites the session cookie with the bearer rather than deferring to
|
|
95
|
+
* it. Both are noted so the guarantee is not read wider than it is.
|
|
96
|
+
*
|
|
97
|
+
* {@link createLunoraClient} applies it only on native — see
|
|
98
|
+
* {@link isNativeRuntime} — and only to the global `fetch`. Under
|
|
99
|
+
* `react-native-web` the jar is the browser's, `Origin` IS sent, the CSRF guard
|
|
100
|
+
* never fires, and a cookie session is a legitimate setup that this would
|
|
101
|
+
* silently sign out (`getCurrentUser` deliberately sends `credentials:
|
|
102
|
+
* "include"`). A caller-supplied `fetch` is never wrapped either, so a
|
|
103
|
+
* cookie-forwarding SSR transport keeps its credentials; apply this yourself if
|
|
104
|
+
* you want it.
|
|
105
|
+
*/
|
|
106
|
+
declare const withoutAmbientCookies: (fetchImpl: typeof fetch) => typeof fetch;
|
|
107
|
+
/**
|
|
108
|
+
* Construct a `LunoraClient` tuned for React Native / Expo — a thin wrapper over
|
|
109
|
+
* `new LunoraClient(options)` that fills in the three things a browser gets for
|
|
110
|
+
* free but React Native does not.
|
|
111
|
+
*
|
|
112
|
+
* First, a durable offline queue: pass `storage` (React Native `AsyncStorage`,
|
|
113
|
+
* or any async key/value store) and the offline mutation queue is persisted
|
|
114
|
+
* through `createAsyncStoragePersistence` — the browser default auto-probes
|
|
115
|
+
* IndexedDB, which React Native lacks, so without this the queue lives only in
|
|
116
|
+
* memory and is lost on reload.
|
|
117
|
+
*
|
|
118
|
+
* Second, a durable read cache: the same `storage` also backs the query cache
|
|
119
|
+
* through `createAsyncStorageQueryCache`, so cached reads render immediately
|
|
120
|
+
* after a restart while the socket reconnects — mirroring the browser's
|
|
121
|
+
* IndexedDB-backed default.
|
|
122
|
+
*
|
|
123
|
+
* Third, credentialed requests: the session is a bearer on this platform, so the
|
|
124
|
+
* returned `fetch` is wrapped in {@link withoutAmbientCookies} (React Native's
|
|
125
|
+
* cookie jar is real, and a stray session cookie 403s every state-changing RPC —
|
|
126
|
+
* see there), and passing `getAuthHeaders` additionally rides its headers on both
|
|
127
|
+
* the HTTP RPC path and the WebSocket upgrade.
|
|
128
|
+
*
|
|
129
|
+
* Everything on `LunoraClientOptions` is still accepted and passed through; an
|
|
130
|
+
* explicit `persistence`, `queryCache`, `fetch`, or `WebSocket` replaces the
|
|
131
|
+
* transport derived from `storage`. A `getAuthHeaders` factory is layered over a
|
|
132
|
+
* caller-supplied `fetch` rather than ignored — the previous behaviour dropped
|
|
133
|
+
* the credential on HTTP while still injecting it on the WS upgrade. `persistence`
|
|
134
|
+
* and `queryCache` override independently — `storage` backs both, so opting out
|
|
135
|
+
* of one leaves the other wired. See the package README
|
|
136
|
+
* for a full setup example.
|
|
137
|
+
* @experimental
|
|
138
|
+
*/
|
|
74
139
|
declare const createLunoraClient: (options: CreateLunoraClientOptions) => LunoraClient;
|
|
75
|
-
export { type AuthHeadersFactory, type CreateLunoraClientOptions, createLunoraClient };
|
|
140
|
+
export { type AuthHeadersFactory, type CreateLunoraClientOptions, createLunoraClient, withoutAmbientCookies };
|
package/dist/index.mjs
CHANGED
|
@@ -1,2 +1 @@
|
|
|
1
|
-
|
|
2
|
-
export * from '@lunora/react';
|
|
1
|
+
import{createLunoraClient as t,withoutAmbientCookies as r}from"./packem_shared/createLunoraClient-WOgHmG35.mjs";export*from"@lunora/react";export{t as createLunoraClient,r as withoutAmbientCookies};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{LunoraClient as d,createAsyncStorageQueryCache as h,createAsyncStoragePersistence as i}from"@lunora/client";const u=()=>typeof document>"u",f=s=>(t,o)=>s(t,{...o,credentials:"omit"}),b=(s,t)=>(o,e)=>{const r=t();if(!r)return s(o,e);const c=new Headers(r);return e?.headers&&new Headers(e.headers).forEach((n,a)=>{c.set(a,n)}),s(o,{...e,headers:c})},S=(s,t)=>class extends s{constructor(e,r){const c=t();super(e,r,c?{headers:c}:void 0)}},y=s=>{const{getAuthHeaders:t,storage:o,...e}=s,r=e.fetch===void 0&&typeof fetch=="function"?fetch.bind(globalThis):void 0,c=e.fetch??(r&&u()?f(r):r),n=t&&c?b(c,t):c,a=t&&e.WebSocket===void 0&&typeof WebSocket=="function"?S(WebSocket,t):e.WebSocket;return new d({...e,persistence:e.persistence??(o?i({storage:o}):void 0),queryCache:e.queryCache??(o?h({storage:o}):void 0),fetch:n,WebSocket:a})};export{y as createLunoraClient,b as withAuthHeaders,S as withAuthWebSocket,f as withoutAmbientCookies};
|
|
@@ -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.
|
|
3
|
+
"version": "1.0.0-alpha.100",
|
|
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.
|
|
54
|
-
"@lunora/react": "1.0.0-alpha.
|
|
53
|
+
"@lunora/client": "1.0.0-alpha.123",
|
|
54
|
+
"@lunora/react": "1.0.0-alpha.129"
|
|
55
55
|
},
|
|
56
56
|
"peerDependencies": {
|
|
57
|
-
"@better-auth/expo": "^1.
|
|
57
|
+
"@better-auth/expo": "^1.7.1",
|
|
58
58
|
"@tanstack/react-query": "^5.101.0",
|
|
59
|
-
"better-auth": "^1.
|
|
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 };
|