tauri-plugin-better-auth 0.0.0-stage → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Muhammed Koç and tauri-plugin-better-auth contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,55 @@
1
- # Temporary Holding Version
1
+ # tauri-plugin-better-auth
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ [Better Auth](https://better-auth.com) for [Tauri 2](https://tauri.app). Users
4
+ sign in through the system browser with PKCE. The session lives in Rust, and
5
+ webview code never sees a token.
6
+
7
+ > **Tauri 2 line (`0.1.x`).** For Tauri 3 (alpha), install
8
+ > `tauri-plugin-better-auth@next` and use crate version `0.2.0-alpha.1`
9
+ > ([`v3` branch](https://github.com/laakal/tauri-plugin-better-auth/tree/v3)).
10
+ > Keep the npm package and the crate on the same line.
11
+
12
+ This npm package is one half of the plugin. The other half is the Rust crate
13
+ [`tauri-plugin-better-auth`](https://crates.io/crates/tauri-plugin-better-auth).
14
+ Full documentation:
15
+ [github.com/laakal/tauri-plugin-better-auth](https://github.com/laakal/tauri-plugin-better-auth#readme).
16
+
17
+ ```sh
18
+ npm install tauri-plugin-better-auth
19
+ ```
20
+
21
+ | Import | Where | What |
22
+ | --------------------------------- | ----------------------- | --------------------------------------------------------------------- |
23
+ | `tauri-plugin-better-auth` | Your Better Auth server | `tauri()` server plugin: `/tauri/authorize`, `/tauri/token` |
24
+ | `tauri-plugin-better-auth/proxy` | Your hand-off web page | `tauriProxyClient()`: request capture, consent hand-off, cancel |
25
+ | `tauri-plugin-better-auth/client` | The Tauri webview | `withTauri()`, `tauriFetch`, `requestAuth()`, events, tokenless types |
26
+
27
+ ```ts
28
+ // Server
29
+ import { bearer } from 'better-auth/plugins';
30
+ import { tauri } from 'tauri-plugin-better-auth';
31
+ betterAuth({
32
+ plugins: [
33
+ bearer(),
34
+ tauri({
35
+ clientID: 'com.example.app',
36
+ authorizeOrigins: ['https://example.com'],
37
+ }),
38
+ ],
39
+ });
40
+
41
+ // Tauri webview
42
+ import { createAuthClient } from 'better-auth/react';
43
+ import {
44
+ asTauriClient,
45
+ requestAuth,
46
+ withTauri,
47
+ } from 'tauri-plugin-better-auth/client';
48
+ const authClient = asTauriClient(
49
+ createAuthClient(withTauri({ baseURL: 'https://api.example.com/api/auth' })),
50
+ );
51
+ await requestAuth();
52
+ ```
53
+
54
+ Requires `better-auth` >= 1.7.4 and, for `/client`, `@tauri-apps/api` >= 2.11.
55
+ MIT licensed.
@@ -0,0 +1,222 @@
1
+ import { UnlistenFn } from "@tauri-apps/api/event";
2
+ import { BetterAuthClientOptions } from "@better-auth/core";
3
+ /** How the browser hands the sign-in back to the app. */
4
+ type Transport = 'auto' | 'deep-link' | 'loopback';
5
+ /** The signed-in user (Better Auth user object). */
6
+ type TauriUser = Record<string, unknown> & {
7
+ id: string;
8
+ email?: string;
9
+ name?: string;
10
+ image?: string | null;
11
+ };
12
+ /** The current session as the webview sees it: never contains a token. */
13
+ interface TauriSessionSnapshot<U = TauriUser, S = Record<string, unknown>> {
14
+ user: U;
15
+ /** Better Auth session object without `token`. */
16
+ session: S;
17
+ /** True when the server could not be reached and this is the cached copy. */
18
+ offline: boolean;
19
+ }
20
+ interface RequestAuthOptions {
21
+ /** Social provider the hand-off page should start (if it allows it). */
22
+ provider?: string;
23
+ /** Overrides the transport configured in Rust for this sign-in. */
24
+ transport?: Transport;
25
+ }
26
+ interface RequestAuthResult {
27
+ /** The sign-in URL: show it when the browser could not be opened. */
28
+ url: string;
29
+ /** Whether the system browser was opened. */
30
+ opened: boolean;
31
+ /** The transport chosen for this sign-in. */
32
+ transport: Transport;
33
+ }
34
+ /** Error raised by the plugin commands; `code` is stable (see the README). */
35
+ export declare class TauriAuthError extends Error {
36
+ readonly code: string;
37
+ name: string;
38
+ constructor(code: string, message: string);
39
+ }
40
+ /** True for errors raised by this plugin. */
41
+ export declare function isTauriAuthError(error: unknown): error is TauriAuthError;
42
+ /**
43
+ * Opens the system browser on your sign-in page. The result arrives later as
44
+ * an `authenticated` event (deep link or loopback), or through
45
+ * {@link authenticate} when the user pastes the code.
46
+ */
47
+ export declare function requestAuth(options?: RequestAuthOptions): Promise<RequestAuthResult>;
48
+ /** Completes a sign-in with a pasted code or callback link. */
49
+ export declare function authenticate<U = TauriUser>(codeOrLink: string): Promise<TauriSessionSnapshot<U>>;
50
+ /** Cancels the sign-in in progress. */
51
+ export declare function cancelAuth(): Promise<void>;
52
+ /**
53
+ * The current session, verified with the server. With `cachedOnly`, returns
54
+ * the last known session without a network call (fast, for startup guards).
55
+ * When the server is unreachable the cached copy comes back with `offline`.
56
+ */
57
+ export declare function getSession<U = TauriUser>(options?: {
58
+ cachedOnly?: boolean;
59
+ }): Promise<TauriSessionSnapshot<U> | null>;
60
+ /** Signs out locally (always) and on the server (best effort). */
61
+ export declare function signOut(): Promise<void>;
62
+ /** Why the user is no longer signed in. */
63
+ type SignOutReason = 'user' | 'revoked' | 'expired';
64
+ /** Payload of the error event. */
65
+ interface TauriAuthErrorEvent {
66
+ code: string;
67
+ message: string;
68
+ }
69
+ export declare const EVENT_AUTHENTICATED = "better-auth://authenticated";
70
+ export declare const EVENT_SIGNED_OUT = "better-auth://signed-out";
71
+ export declare const EVENT_ERROR = "better-auth://error";
72
+ /** Fires after every successful sign-in (browser or in-app). */
73
+ export declare function onAuthenticated<U = TauriUser>(callback: (user: U) => void): Promise<UnlistenFn>;
74
+ /** Fires when the session ends: signed out, revoked by the server, or expired. */
75
+ export declare function onSignedOut(callback: (event: {
76
+ reason: SignOutReason;
77
+ }) => void): Promise<UnlistenFn>;
78
+ /** Fires when a background sign-in (deep link or loopback) fails. */
79
+ export declare function onAuthError(callback: (error: TauriAuthErrorEvent) => void): Promise<UnlistenFn>;
80
+ /**
81
+ * A `fetch` that sends the request through the plugin's Rust bridge.
82
+ *
83
+ * Rust adds the session's bearer token and your app headers, refuses anything
84
+ * outside the configured auth operations and API routes, never follows
85
+ * redirects, and strips `set-cookie`/`set-auth-token` from responses. The
86
+ * webview never sees a token.
87
+ *
88
+ * Use it as Better Auth's `customFetchImpl` (see `withTauri`) and as the
89
+ * `fetch` of your API client. Bodies are buffered (no streaming).
90
+ */
91
+ export declare const tauriFetch: typeof fetch;
92
+ /**
93
+ * Better Auth client plugin for the Tauri webview. Refreshes `useSession()`
94
+ * when a browser sign-in completes or the session ends, and adds
95
+ * `authClient.tauri.*` actions. Added for you by {@link withTauri}.
96
+ */
97
+ export declare const tauriClient: () => {
98
+ id: "tauri";
99
+ getActions: (_$fetch: import("@better-fetch/fetch").BetterFetch, $store: import("better-auth").ClientStore) => {
100
+ tauri: {
101
+ /** Opens the system browser on the sign-in page. */
102
+ requestAuth: typeof requestAuth;
103
+ /** Completes a sign-in with a pasted code or link. */
104
+ authenticate: typeof authenticate;
105
+ /** Cancels the sign-in in progress. */
106
+ cancelAuth: typeof cancelAuth;
107
+ /** The current session from Rust (with offline fallback). */
108
+ getSession: typeof getSession;
109
+ /** Signs out locally and on the server. */
110
+ signOut: typeof signOut;
111
+ };
112
+ };
113
+ };
114
+ type PluginsOf<O> = O extends {
115
+ plugins: infer P extends readonly unknown[];
116
+ } ? P : [];
117
+ /** Options returned by {@link withTauri}. */
118
+ type WithTauri<O extends BetterAuthClientOptions> = Omit<O, 'plugins' | 'fetchOptions' | 'disableDefaultFetchPlugins'> & {
119
+ plugins: [...PluginsOf<O>, ReturnType<typeof tauriClient>];
120
+ fetchOptions: Omit<NonNullable<O['fetchOptions']>, 'customFetchImpl'> & {
121
+ customFetchImpl: typeof tauriFetch;
122
+ };
123
+ disableDefaultFetchPlugins: true;
124
+ };
125
+ /**
126
+ * Adapts Better Auth client options for the Tauri webview. Pass the result to
127
+ * the `createAuthClient` of your framework.
128
+ *
129
+ * - every request goes through the Rust bridge (`tauriFetch`): the token is
130
+ * added in Rust and never reaches the page;
131
+ * - Better Auth's redirect plugin is disabled, so no response can navigate
132
+ * the webview away from the app (start social sign-in with
133
+ * `authClient.tauri.requestAuth({ provider })` instead);
134
+ * - `useSession()` refreshes after browser sign-ins and sign-outs.
135
+ *
136
+ * @example
137
+ * ```ts
138
+ * import { createAuthClient } from 'better-auth/react';
139
+ * import { emailOTPClient } from 'better-auth/client/plugins';
140
+ * import { asTauriClient, withTauri } from 'tauri-plugin-better-auth/client';
141
+ *
142
+ * export const authClient = asTauriClient(
143
+ * createAuthClient(withTauri({ baseURL: 'https://api.example.com/api/auth', plugins: [emailOTPClient()] })),
144
+ * );
145
+ * ```
146
+ */
147
+ export declare function withTauri<O extends BetterAuthClientOptions>(options: O): WithTauri<O>;
148
+ /**
149
+ * Types that describe a Better Auth client as it behaves inside a Tauri
150
+ * webview: sessions carry no token, and only the operations the Rust bridge
151
+ * allows are present.
152
+ */
153
+ /** A `{ session, user }` pair without `session.token`. */
154
+ type Tokenless<T> = T extends {
155
+ session: infer S;
156
+ user: infer U;
157
+ } ? {
158
+ session: Omit<S, 'token'>;
159
+ user: U;
160
+ } : T;
161
+ type TokenlessData<D> = D extends {
162
+ session: unknown;
163
+ user: unknown;
164
+ } ? Tokenless<D> : D;
165
+ /** Maps `{ data: … }` (a hook result or a fetch response) to tokenless data. */
166
+ type MapData<T> = T extends {
167
+ data: infer D;
168
+ } ? Omit<T, 'data'> & {
169
+ data: TokenlessData<D>;
170
+ } : T;
171
+ /** React/Vue/Svelte hooks are functions; the vanilla client exposes an atom. */
172
+ type MapUseSession<U> = U extends ((...args: infer A) => infer R) ? (...args: A) => MapData<R> : U extends {
173
+ get(): infer V;
174
+ } ? Omit<U, 'get' | 'value' | 'subscribe' | 'listen'> & {
175
+ get(): MapData<V>;
176
+ readonly value: MapData<V> | undefined;
177
+ subscribe(listener: (value: MapData<V>) => void): () => void;
178
+ listen(listener: (value: MapData<V>) => void): () => void;
179
+ } : U;
180
+ type MapGetSession<G> = G extends ((...args: infer A) => Promise<infer R>) ? (...args: A) => Promise<MapData<R>> : G;
181
+ type Restrict<T, K extends string> = T extends object ? Pick<T, Extract<keyof T, K>> : never;
182
+ /** Sign-in methods the bridge accepts by default. */
183
+ type TauriSignInMethod = 'email' | 'emailOtp';
184
+ /**
185
+ * Client actions the bridge refuses: they read or mint credentials, or need
186
+ * browser redirects. Use `authClient.tauri.requestAuth()` for browser flows.
187
+ */
188
+ type TauriUnsupportedAction = 'listSessions' | 'revokeSession' | 'revokeSessions' | 'revokeOtherSessions' | 'oneTimeToken' | 'linkSocial' | 'unlinkAccount' | 'getAccessToken' | 'refreshToken' | 'accountInfo' | 'multiSession' | 'token';
189
+ /**
190
+ * A Better Auth client as seen in the Tauri webview: tokenless sessions,
191
+ * in-app sign-in limited to `email` and `emailOtp` (plus `ExtraSignIn`, for
192
+ * routes you allowed in Rust with `Builder::auth_route`).
193
+ */
194
+ type TauriAuthClient<C, ExtraSignIn extends string = never> = Omit<C, 'useSession' | 'getSession' | 'hydrateSession' | '$Infer' | 'signIn' | 'signUp' | TauriUnsupportedAction> & {
195
+ useSession: C extends {
196
+ useSession: infer U;
197
+ } ? MapUseSession<U> : never;
198
+ getSession: C extends {
199
+ getSession: infer G;
200
+ } ? MapGetSession<G> : never;
201
+ signIn: C extends {
202
+ signIn: infer S;
203
+ } ? Restrict<S, TauriSignInMethod | ExtraSignIn> : never;
204
+ signUp: C extends {
205
+ signUp: infer S;
206
+ } ? Restrict<S, 'email'> : never;
207
+ $Infer: (C extends {
208
+ $Infer: infer I;
209
+ } ? Omit<I, 'Session'> : {}) & {
210
+ Session: C extends {
211
+ $Infer: {
212
+ Session: infer S;
213
+ };
214
+ } ? Tokenless<S> : never;
215
+ };
216
+ };
217
+ /**
218
+ * Narrows a client created with `withTauri` to {@link TauriAuthClient}.
219
+ * Runtime no-op: the bridge already enforces all of this.
220
+ */
221
+ export declare function asTauriClient<C, ExtraSignIn extends string = never>(client: C): TauriAuthClient<C, ExtraSignIn>;
222
+ export type { RequestAuthOptions, RequestAuthResult, SignOutReason, TauriAuthClient, TauriAuthErrorEvent, TauriSessionSnapshot, TauriSignInMethod, TauriUnsupportedAction, TauriUser, Tokenless, Transport, WithTauri };
package/dist/client.js ADDED
@@ -0,0 +1,239 @@
1
+ import { invoke, isTauri } from "@tauri-apps/api/core";
2
+ import { listen } from "@tauri-apps/api/event";
3
+ // src/client/commands.ts
4
+ /** Error raised by the plugin commands; `code` is stable (see the README). */
5
+ var TauriAuthError = class extends Error {
6
+ code;
7
+ name = "TauriAuthError";
8
+ constructor(code, message) {
9
+ super(message);
10
+ this.code = code;
11
+ }
12
+ };
13
+ /** True for errors raised by this plugin. */
14
+ function isTauriAuthError(error) {
15
+ return error instanceof TauriAuthError;
16
+ }
17
+ function toError(error) {
18
+ if (typeof error === "object" && error !== null && "code" in error && typeof error.code === "string") {
19
+ const message = "message" in error && typeof error.message === "string" ? error.message : error.code;
20
+ return new TauriAuthError(error.code, message);
21
+ }
22
+ return error instanceof Error ? error : new Error(String(error));
23
+ }
24
+ /** `invoke` for this plugin, with errors mapped to {@link TauriAuthError}. */
25
+ async function call(command, args) {
26
+ try {
27
+ return await invoke(`plugin:better-auth|${command}`, args);
28
+ } catch (error) {
29
+ throw toError(error);
30
+ }
31
+ }
32
+ /**
33
+ * Opens the system browser on your sign-in page. The result arrives later as
34
+ * an `authenticated` event (deep link or loopback), or through
35
+ * {@link authenticate} when the user pastes the code.
36
+ */
37
+ function requestAuth(options = {}) {
38
+ return call("request_auth", { options });
39
+ }
40
+ /** Completes a sign-in with a pasted code or callback link. */
41
+ function authenticate(codeOrLink) {
42
+ return call("authenticate", { code: codeOrLink });
43
+ }
44
+ /** Cancels the sign-in in progress. */
45
+ function cancelAuth() {
46
+ return call("cancel_auth");
47
+ }
48
+ /**
49
+ * The current session, verified with the server. With `cachedOnly`, returns
50
+ * the last known session without a network call (fast, for startup guards).
51
+ * When the server is unreachable the cached copy comes back with `offline`.
52
+ */
53
+ function getSession(options = {}) {
54
+ return call("get_session", { cachedOnly: options.cachedOnly ?? false });
55
+ }
56
+ /** Signs out locally (always) and on the server (best effort). */
57
+ function signOut() {
58
+ return call("sign_out");
59
+ }
60
+ // src/client/events.ts
61
+ const EVENT_AUTHENTICATED = "better-auth://authenticated";
62
+ const EVENT_SIGNED_OUT = "better-auth://signed-out";
63
+ const EVENT_ERROR = "better-auth://error";
64
+ /** Fires after every successful sign-in (browser or in-app). */
65
+ function onAuthenticated(callback) {
66
+ return listen(EVENT_AUTHENTICATED, (event) => callback(event.payload.user));
67
+ }
68
+ /** Fires when the session ends: signed out, revoked by the server, or expired. */
69
+ function onSignedOut(callback) {
70
+ return listen(EVENT_SIGNED_OUT, (event) => callback(event.payload));
71
+ }
72
+ /** Fires when a background sign-in (deep link or loopback) fails. */
73
+ function onAuthError(callback) {
74
+ return listen(EVENT_ERROR, (event) => callback(event.payload));
75
+ }
76
+ // src/client/fetch.ts
77
+ const NULL_BODY_STATUSES = /* @__PURE__ */ new Set([
78
+ 101,
79
+ 204,
80
+ 205,
81
+ 304
82
+ ]);
83
+ const MAX_ID = 4294967295;
84
+ let nextId = 1;
85
+ function takeId() {
86
+ const id = nextId;
87
+ nextId = nextId >= MAX_ID ? 1 : nextId + 1;
88
+ return id;
89
+ }
90
+ function isTextual(contentType) {
91
+ if (!contentType) return false;
92
+ const value = contentType.toLowerCase();
93
+ return value.startsWith("text/") || value.includes("json") || value.includes("xml") || value.includes("javascript") || value.includes("x-www-form-urlencoded");
94
+ }
95
+ function toBase64(bytes) {
96
+ let binary = "";
97
+ for (let i = 0; i < bytes.length; i += 32768) binary += String.fromCharCode(...bytes.subarray(i, i + 32768));
98
+ return btoa(binary);
99
+ }
100
+ function fromBase64(data) {
101
+ const binary = atob(data);
102
+ const bytes = new Uint8Array(binary.length);
103
+ for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
104
+ return bytes;
105
+ }
106
+ async function encodeBody(request) {
107
+ if (request.method === "GET" || request.method === "HEAD") return null;
108
+ const bytes = new Uint8Array(await request.arrayBuffer());
109
+ if (bytes.byteLength === 0) return null;
110
+ if (isTextual(request.headers.get("content-type"))) try {
111
+ return {
112
+ encoding: "text",
113
+ data: new TextDecoder("utf-8", { fatal: true }).decode(bytes)
114
+ };
115
+ } catch {}
116
+ return {
117
+ encoding: "base64",
118
+ data: toBase64(bytes)
119
+ };
120
+ }
121
+ function abortError(signal) {
122
+ return signal?.reason ?? new DOMException("The operation was aborted.", "AbortError");
123
+ }
124
+ /**
125
+ * A `fetch` that sends the request through the plugin's Rust bridge.
126
+ *
127
+ * Rust adds the session's bearer token and your app headers, refuses anything
128
+ * outside the configured auth operations and API routes, never follows
129
+ * redirects, and strips `set-cookie`/`set-auth-token` from responses. The
130
+ * webview never sees a token.
131
+ *
132
+ * Use it as Better Auth's `customFetchImpl` (see `withTauri`) and as the
133
+ * `fetch` of your API client. Bodies are buffered (no streaming).
134
+ */
135
+ const tauriFetch = async (input, init) => {
136
+ const request = new Request(input, init);
137
+ const signal = init?.signal ?? request.signal;
138
+ if (signal?.aborted) throw abortError(signal);
139
+ const id = takeId();
140
+ const headers = [];
141
+ request.headers.forEach((value, name) => headers.push([name, value]));
142
+ const body = await encodeBody(request);
143
+ const onAbort = () => {
144
+ invoke("plugin:better-auth|fetch_cancel", { id }).catch(() => {});
145
+ };
146
+ signal?.addEventListener("abort", onAbort, { once: true });
147
+ try {
148
+ const response = await invoke("plugin:better-auth|fetch", { request: {
149
+ id,
150
+ url: request.url,
151
+ method: request.method,
152
+ headers,
153
+ body
154
+ } });
155
+ const payload = response.body === null || NULL_BODY_STATUSES.has(response.status) ? null : response.body.encoding === "text" ? response.body.data : fromBase64(response.body.data);
156
+ return new Response(payload, {
157
+ status: response.status,
158
+ statusText: response.statusText,
159
+ headers: response.headers
160
+ });
161
+ } catch (error) {
162
+ if (signal?.aborted) throw abortError(signal);
163
+ if ((typeof error === "object" && error !== null && "code" in error ? String(error.code) : void 0) === "ABORTED") throw abortError(signal);
164
+ const message = typeof error === "object" && error !== null && "message" in error ? String(error.message) : String(error);
165
+ throw new TypeError(`Failed to fetch: ${message}`, { cause: error });
166
+ } finally {
167
+ signal?.removeEventListener("abort", onAbort);
168
+ }
169
+ };
170
+ // src/client/plugin.ts
171
+ /**
172
+ * Better Auth client plugin for the Tauri webview. Refreshes `useSession()`
173
+ * when a browser sign-in completes or the session ends, and adds
174
+ * `authClient.tauri.*` actions. Added for you by {@link withTauri}.
175
+ */
176
+ const tauriClient = () => ({
177
+ id: "tauri",
178
+ getActions: (_$fetch, $store) => {
179
+ if (typeof window !== "undefined" && isTauri()) {
180
+ const refresh = () => $store.notify("$sessionSignal");
181
+ onAuthenticated(refresh);
182
+ onSignedOut(refresh);
183
+ }
184
+ return { tauri: {
185
+ /** Opens the system browser on the sign-in page. */
186
+ requestAuth,
187
+ /** Completes a sign-in with a pasted code or link. */
188
+ authenticate,
189
+ /** Cancels the sign-in in progress. */
190
+ cancelAuth,
191
+ /** The current session from Rust (with offline fallback). */
192
+ getSession,
193
+ /** Signs out locally and on the server. */
194
+ signOut
195
+ } };
196
+ }
197
+ });
198
+ /**
199
+ * Adapts Better Auth client options for the Tauri webview. Pass the result to
200
+ * the `createAuthClient` of your framework.
201
+ *
202
+ * - every request goes through the Rust bridge (`tauriFetch`): the token is
203
+ * added in Rust and never reaches the page;
204
+ * - Better Auth's redirect plugin is disabled, so no response can navigate
205
+ * the webview away from the app (start social sign-in with
206
+ * `authClient.tauri.requestAuth({ provider })` instead);
207
+ * - `useSession()` refreshes after browser sign-ins and sign-outs.
208
+ *
209
+ * @example
210
+ * ```ts
211
+ * import { createAuthClient } from 'better-auth/react';
212
+ * import { emailOTPClient } from 'better-auth/client/plugins';
213
+ * import { asTauriClient, withTauri } from 'tauri-plugin-better-auth/client';
214
+ *
215
+ * export const authClient = asTauriClient(
216
+ * createAuthClient(withTauri({ baseURL: 'https://api.example.com/api/auth', plugins: [emailOTPClient()] })),
217
+ * );
218
+ * ```
219
+ */
220
+ function withTauri(options) {
221
+ return {
222
+ ...options,
223
+ disableDefaultFetchPlugins: true,
224
+ fetchOptions: {
225
+ ...options.fetchOptions,
226
+ customFetchImpl: tauriFetch
227
+ },
228
+ plugins: [...options.plugins ?? [], tauriClient()]
229
+ };
230
+ }
231
+ // src/client/types.ts
232
+ /**
233
+ * Narrows a client created with `withTauri` to {@link TauriAuthClient}.
234
+ * Runtime no-op: the bridge already enforces all of this.
235
+ */
236
+ function asTauriClient(client) {
237
+ return client;
238
+ }
239
+ export { EVENT_AUTHENTICATED, EVENT_ERROR, EVENT_SIGNED_OUT, TauriAuthError, asTauriClient, authenticate, cancelAuth, getSession, isTauriAuthError, onAuthError, onAuthenticated, onSignedOut, requestAuth, signOut, tauriClient, tauriFetch, withTauri };