@dreamshive/better-auth-tauri 0.1.0
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 +21 -0
- package/README.md +401 -0
- package/dist/client.d.ts +181 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +377 -0
- package/dist/client.js.map +1 -0
- package/dist/focus-manager.d.ts +19 -0
- package/dist/focus-manager.d.ts.map +1 -0
- package/dist/focus-manager.js +34 -0
- package/dist/focus-manager.js.map +1 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +119 -0
- package/dist/index.js.map +1 -0
- package/dist/online-manager.d.ts +15 -0
- package/dist/online-manager.d.ts.map +1 -0
- package/dist/online-manager.js +25 -0
- package/dist/online-manager.js.map +1 -0
- package/dist/routes.d.ts +46 -0
- package/dist/routes.d.ts.map +1 -0
- package/dist/routes.js +51 -0
- package/dist/routes.js.map +1 -0
- package/dist/utils.d.ts +21 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +41 -0
- package/dist/utils.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +2 -0
- package/dist/version.js.map +1 -0
- package/package.json +81 -0
- package/src/client.ts +491 -0
- package/src/focus-manager.ts +36 -0
- package/src/index.ts +140 -0
- package/src/online-manager.ts +29 -0
- package/src/routes.ts +67 -0
- package/src/utils.ts +42 -0
- package/src/version.ts +1 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Rully Ardiansyah
|
|
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
ADDED
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
# @dreamshive/better-auth-tauri
|
|
2
|
+
|
|
3
|
+
[Better Auth](https://better-auth.com) plugin for [Tauri](https://tauri.app) desktop apps. OAuth happens in the user's **system browser**, not the embedded webview — the session cookie rides back through a custom URI scheme deep link, and every outgoing request in the app is transparently authenticated.
|
|
4
|
+
|
|
5
|
+
Mirrors the architecture of the official [`@better-auth/expo`](https://github.com/better-auth/better-auth/tree/main/packages/expo) package, adapted for Tauri's browser-style webview.
|
|
6
|
+
|
|
7
|
+
## Why use this
|
|
8
|
+
|
|
9
|
+
- **OAuth providers that block embedded webviews still work** (Google's `disallowed_useragent`, enterprise SSO, upcoming Microsoft enforcement).
|
|
10
|
+
- **Users reuse their existing browser sessions** — no re-logging-in for github.com / google.com from inside your app.
|
|
11
|
+
- **Full address bar + padlock** — users can verify the provider's domain before signing in.
|
|
12
|
+
- **Same Better Auth API surface** — you keep using `authClient.signIn.social(...)` and `useSession()` as if nothing changed.
|
|
13
|
+
|
|
14
|
+
## How it works
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
┌──────────────────┐ ┌────────────────┐ ┌──────────────┐
|
|
18
|
+
│ Tauri app │ │ System browser │ │ Auth service │
|
|
19
|
+
│ (WKWebView/ │ │ (Safari/Chrome)│ │ │
|
|
20
|
+
│ WebView2) │ │ │ │ │
|
|
21
|
+
└────────┬─────────┘ └────────┬───────┘ └──────┬───────┘
|
|
22
|
+
│ signIn.social() │ │
|
|
23
|
+
│ ─────────────────────────────┼───────────────────────>│
|
|
24
|
+
│ │ │
|
|
25
|
+
│ <── { url, disableRedirect } ──────────────────────────│
|
|
26
|
+
│ │ │
|
|
27
|
+
│ shell.open(url) │ │
|
|
28
|
+
│ ────────────────────────────>│ │
|
|
29
|
+
│ │ /tauri-authz-proxy │
|
|
30
|
+
│ │ ──────────────────────>│
|
|
31
|
+
│ │ Set-Cookie: state=... │
|
|
32
|
+
│ │ <──────────────────────│
|
|
33
|
+
│ │ │
|
|
34
|
+
│ │ github.com OAuth... │
|
|
35
|
+
│ │ <─redirect to callback─│
|
|
36
|
+
│ │ /callback/github │
|
|
37
|
+
│ │ ──────────────────────>│
|
|
38
|
+
│ │ │
|
|
39
|
+
│ │ <── 302 sokudo://?cookie=<session>
|
|
40
|
+
│ │ (after-hook appends cookie
|
|
41
|
+
│ │ to custom-scheme redirect)
|
|
42
|
+
│ │ │
|
|
43
|
+
│ <── OS routes sokudo:// ────│ │
|
|
44
|
+
│ │
|
|
45
|
+
│ store cookie in OS keychain │
|
|
46
|
+
│ inject as `x-tauri-cookie` header on future calls │
|
|
47
|
+
│ ─────────────────────────────────────────────────────>│
|
|
48
|
+
│ │
|
|
49
|
+
│ <── server plugin rewrites x-tauri-cookie → Cookie ───│
|
|
50
|
+
│ Better Auth validates, returns session │
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Two things worth calling out:
|
|
54
|
+
|
|
55
|
+
1. **`x-tauri-cookie` header smuggling.** The Fetch spec marks `Cookie` as a [forbidden header name](https://fetch.spec.whatwg.org/#forbidden-header-name). Webviews silently drop attempts to set it. We smuggle the session through `x-tauri-cookie` and the **server** plugin rewrites it back to `Cookie` before Better Auth inspects the request.
|
|
56
|
+
2. **`disableRedirect: true` auto-injection.** Better Auth's Vue/React client normally navigates `window.location.href` to the OAuth URL — fine in a browser, catastrophic in a single-window Tauri app (the webview takes over with the provider's login page). The client plugin injects `disableRedirect: true` into `/sign-in/social` and `/sign-in/oauth2` requests so the client stays put.
|
|
57
|
+
|
|
58
|
+
## Installation
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
bun add @dreamshive/better-auth-tauri
|
|
62
|
+
# or: npm install / pnpm add / yarn add
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Peer Tauri plugins (install only in the desktop app that consumes the client):
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
bun add @tauri-apps/plugin-shell @tauri-apps/plugin-deep-link
|
|
69
|
+
cd src-tauri
|
|
70
|
+
cargo add tauri-plugin-shell tauri-plugin-deep-link
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
If you want the included focus/online managers to work, you also need:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
bun add @tauri-apps/api # onFocusChanged lives here
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Tauri configuration
|
|
80
|
+
|
|
81
|
+
### 1. Register your URI scheme
|
|
82
|
+
|
|
83
|
+
`src-tauri/tauri.conf.json`:
|
|
84
|
+
|
|
85
|
+
```json
|
|
86
|
+
{
|
|
87
|
+
"plugins": {
|
|
88
|
+
"deep-link": {
|
|
89
|
+
"desktop": {
|
|
90
|
+
"schemes": ["yourapp"]
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### 2. Initialize the Rust plugins
|
|
98
|
+
|
|
99
|
+
`src-tauri/src/lib.rs`:
|
|
100
|
+
|
|
101
|
+
```rust
|
|
102
|
+
pub fn run() {
|
|
103
|
+
tauri::Builder::default()
|
|
104
|
+
.plugin(tauri_plugin_shell::init())
|
|
105
|
+
.plugin(tauri_plugin_deep_link::init())
|
|
106
|
+
// ... your other plugins
|
|
107
|
+
.run(tauri::generate_context!())
|
|
108
|
+
.expect("error while running tauri application");
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### 3. Grant permissions
|
|
113
|
+
|
|
114
|
+
`src-tauri/capabilities/default.json`:
|
|
115
|
+
|
|
116
|
+
```json
|
|
117
|
+
{
|
|
118
|
+
"permissions": [
|
|
119
|
+
"core:default",
|
|
120
|
+
"shell:allow-open",
|
|
121
|
+
"deep-link:default"
|
|
122
|
+
]
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### 4. (Highly recommended) Add `tauri-plugin-single-instance`
|
|
127
|
+
|
|
128
|
+
Without this, opening a `yourapp://` deep link from a browser while the Tauri app is already running may spawn a second window instead of reusing the first. Install:
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
cd src-tauri
|
|
132
|
+
cargo add tauri-plugin-single-instance --features deep-link
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Register it **before** any other plugin in `lib.rs`:
|
|
136
|
+
|
|
137
|
+
```rust
|
|
138
|
+
use tauri::{Emitter, Manager};
|
|
139
|
+
|
|
140
|
+
pub fn run() {
|
|
141
|
+
tauri::Builder::default()
|
|
142
|
+
.plugin(tauri_plugin_single_instance::init(|app, argv, _cwd| {
|
|
143
|
+
if let Some(window) = app.get_webview_window("main") {
|
|
144
|
+
let _ = window.unminimize();
|
|
145
|
+
let _ = window.show();
|
|
146
|
+
let _ = window.set_focus();
|
|
147
|
+
}
|
|
148
|
+
for arg in argv.iter().skip(1) {
|
|
149
|
+
if arg.starts_with("yourapp://") {
|
|
150
|
+
let _ = app.emit("deep-link://new-url", vec![arg.clone()]);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}))
|
|
154
|
+
.plugin(tauri_plugin_shell::init())
|
|
155
|
+
.plugin(tauri_plugin_deep_link::init())
|
|
156
|
+
// ...
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Server setup (Better Auth backend)
|
|
161
|
+
|
|
162
|
+
```ts
|
|
163
|
+
import { betterAuth } from "better-auth";
|
|
164
|
+
import { tauri } from "@dreamshive/better-auth-tauri";
|
|
165
|
+
|
|
166
|
+
export const auth = betterAuth({
|
|
167
|
+
// ... your existing config
|
|
168
|
+
trustedOrigins: ["yourapp://"],
|
|
169
|
+
plugins: [
|
|
170
|
+
tauri(),
|
|
171
|
+
// ... your other plugins
|
|
172
|
+
],
|
|
173
|
+
});
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
The server plugin:
|
|
177
|
+
|
|
178
|
+
1. Remaps `tauri-origin` → `origin` so CSRF / trusted-origin checks accept the custom scheme.
|
|
179
|
+
2. Rewrites `x-tauri-cookie` → `Cookie` so the session cookie smuggled by the client is visible to Better Auth.
|
|
180
|
+
3. Appends `?cookie=<Set-Cookie>` to OAuth redirect URLs targeting custom schemes so the Tauri app can bridge the cookie jar.
|
|
181
|
+
4. Exposes a `/tauri-authorization-proxy` endpoint that plants the OAuth `state` cookie in the system browser's jar before redirecting to the provider (prevents callback state mismatch).
|
|
182
|
+
|
|
183
|
+
### CORS
|
|
184
|
+
|
|
185
|
+
Your auth server's CORS config must allow the custom headers this plugin sends:
|
|
186
|
+
|
|
187
|
+
```ts
|
|
188
|
+
cors({
|
|
189
|
+
origin: [/* your frontend origins */, "yourapp://"],
|
|
190
|
+
credentials: true,
|
|
191
|
+
allowedHeaders: [
|
|
192
|
+
"Content-Type",
|
|
193
|
+
"Authorization",
|
|
194
|
+
"Cookie",
|
|
195
|
+
"tauri-origin",
|
|
196
|
+
"x-tauri-cookie",
|
|
197
|
+
"x-skip-oauth-proxy",
|
|
198
|
+
],
|
|
199
|
+
})
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
## Client setup
|
|
203
|
+
|
|
204
|
+
```ts
|
|
205
|
+
import { createAuthClient } from "better-auth/vue"; // or /react, /solid, etc.
|
|
206
|
+
import { tauriClient } from "@dreamshive/better-auth-tauri/client";
|
|
207
|
+
import { fetch as tauriFetch } from "@tauri-apps/plugin-http";
|
|
208
|
+
import { isTauri } from "@tauri-apps/api/core";
|
|
209
|
+
|
|
210
|
+
export const authClient = createAuthClient({
|
|
211
|
+
baseURL: "https://auth.example.com",
|
|
212
|
+
fetchOptions: {
|
|
213
|
+
// Always use tauriFetch in Tauri — the webview's native fetch() drops
|
|
214
|
+
// our smuggled headers and hits CORS on custom schemes.
|
|
215
|
+
customFetchImpl: (...args) =>
|
|
216
|
+
isTauri() ? tauriFetch(...args) : fetch(...args),
|
|
217
|
+
},
|
|
218
|
+
plugins: [
|
|
219
|
+
tauriClient({
|
|
220
|
+
scheme: "yourapp", // must match tauri.conf.json
|
|
221
|
+
cookiePrefix: "yourapp", // must match your server's cookie prefix
|
|
222
|
+
storage: {
|
|
223
|
+
// See "Secure storage" below — localStorage is dev-only.
|
|
224
|
+
getItem: (k) => localStorage.getItem(k),
|
|
225
|
+
setItem: (k, v) => localStorage.setItem(k, v),
|
|
226
|
+
},
|
|
227
|
+
}),
|
|
228
|
+
],
|
|
229
|
+
});
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
Then use Better Auth normally:
|
|
233
|
+
|
|
234
|
+
```ts
|
|
235
|
+
await authClient.signIn.social({
|
|
236
|
+
provider: "github",
|
|
237
|
+
callbackURL: "/", // auto-rewritten to yourapp:///
|
|
238
|
+
});
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
The plugin takes it from there.
|
|
242
|
+
|
|
243
|
+
## Secure storage
|
|
244
|
+
|
|
245
|
+
`localStorage` is **not appropriate for production** — any XSS in your Tauri webview can read it directly.
|
|
246
|
+
|
|
247
|
+
For production, back the `storage` option with an encrypted store. Options, ranked by ergonomics:
|
|
248
|
+
|
|
249
|
+
### Option A — OS keychain (recommended)
|
|
250
|
+
|
|
251
|
+
Expose `keyring-rs` via custom Tauri commands. Per-platform native secret store (Apple Keychain, Windows Credential Manager, Secret Service on Linux).
|
|
252
|
+
|
|
253
|
+
```bash
|
|
254
|
+
cd src-tauri
|
|
255
|
+
cargo add keyring --features apple-native,windows-native,linux-native-sync-persistent
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
```rust
|
|
259
|
+
// src-tauri/src/keyring.rs
|
|
260
|
+
use keyring::Entry;
|
|
261
|
+
|
|
262
|
+
#[tauri::command]
|
|
263
|
+
pub fn keyring_get(service: String, key: String) -> Result<Option<String>, String> {
|
|
264
|
+
let entry = Entry::new(&service, &key).map_err(|e| e.to_string())?;
|
|
265
|
+
match entry.get_password() {
|
|
266
|
+
Ok(v) => Ok(Some(v)),
|
|
267
|
+
Err(keyring::Error::NoEntry) => Ok(None),
|
|
268
|
+
Err(e) => Err(e.to_string()),
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
#[tauri::command]
|
|
273
|
+
pub fn keyring_set(service: String, key: String, value: String) -> Result<(), String> {
|
|
274
|
+
let entry = Entry::new(&service, &key).map_err(|e| e.to_string())?;
|
|
275
|
+
entry.set_password(&value).map_err(|e| e.to_string())
|
|
276
|
+
}
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
Register in `lib.rs`:
|
|
280
|
+
|
|
281
|
+
```rust
|
|
282
|
+
mod keyring;
|
|
283
|
+
|
|
284
|
+
tauri::Builder::default()
|
|
285
|
+
.invoke_handler(tauri::generate_handler![
|
|
286
|
+
keyring::keyring_get,
|
|
287
|
+
keyring::keyring_set,
|
|
288
|
+
])
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
And in your client storage adapter:
|
|
292
|
+
|
|
293
|
+
```ts
|
|
294
|
+
import { invoke } from "@tauri-apps/api/core";
|
|
295
|
+
const SERVICE = "com.yourapp.session";
|
|
296
|
+
|
|
297
|
+
const keyringStorage = {
|
|
298
|
+
getItem: (key: string) => invoke<string | null>("keyring_get", { service: SERVICE, key }),
|
|
299
|
+
setItem: (key: string, value: string) => invoke("keyring_set", { service: SERVICE, key, value }),
|
|
300
|
+
};
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
### Option B — `tauri-plugin-stronghold`
|
|
304
|
+
|
|
305
|
+
Official Tauri encrypted vault (IOTA Stronghold). Requires a master passphrase that the app supplies.
|
|
306
|
+
|
|
307
|
+
### Option C — `@tauri-apps/plugin-store`
|
|
308
|
+
|
|
309
|
+
On-disk JSON, not encrypted, but lives behind Tauri IPC (not accessible from `document.localStorage`). Marginal improvement over `localStorage`.
|
|
310
|
+
|
|
311
|
+
## Options
|
|
312
|
+
|
|
313
|
+
### `tauri(options?)` — server plugin
|
|
314
|
+
|
|
315
|
+
| Option | Default | Description |
|
|
316
|
+
| --- | --- | --- |
|
|
317
|
+
| `disableOriginOverride` | `false` | Don't remap `tauri-origin` → `origin`. |
|
|
318
|
+
|
|
319
|
+
### `tauriClient(options)` — client plugin
|
|
320
|
+
|
|
321
|
+
| Option | Default | Description |
|
|
322
|
+
| --- | --- | --- |
|
|
323
|
+
| `scheme` | — (required) | Custom URI scheme registered in `tauri.conf.json`. |
|
|
324
|
+
| `storage` | — (required) | Key/value storage adapter. Async methods supported. |
|
|
325
|
+
| `storagePrefix` | `"better-auth"` | Prefix for storage keys. |
|
|
326
|
+
| `cookiePrefix` | `"better-auth"` | Server cookie-name prefix(es). |
|
|
327
|
+
| `disableCache` | `false` | Disable local `/get-session` response cache. |
|
|
328
|
+
| `refetchOnWindowFocus` | `true` | Refetch session when the Tauri window regains focus. |
|
|
329
|
+
| `refetchOnReconnect` | `true` | Refetch session when the network comes back online. |
|
|
330
|
+
|
|
331
|
+
## Exports
|
|
332
|
+
|
|
333
|
+
```ts
|
|
334
|
+
// Server
|
|
335
|
+
import { tauri, tauriAuthorizationProxy } from "@dreamshive/better-auth-tauri";
|
|
336
|
+
|
|
337
|
+
// Client
|
|
338
|
+
import {
|
|
339
|
+
tauriClient,
|
|
340
|
+
setupTauriFocusManager, // manual wiring if you disabled auto-setup
|
|
341
|
+
setupTauriOnlineManager,
|
|
342
|
+
} from "@dreamshive/better-auth-tauri/client";
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
## macOS dev workflow
|
|
346
|
+
|
|
347
|
+
Deep-link URI schemes on macOS are registered with Launch Services through the app's `.app` bundle's `Info.plist`. **`tauri dev` does not produce a bundle** — it runs a raw binary which macOS can't route deep links to.
|
|
348
|
+
|
|
349
|
+
Official Tauri guidance for developing against the deep-link plugin on macOS:
|
|
350
|
+
|
|
351
|
+
```bash
|
|
352
|
+
# Build once (also run after any Rust/plugin/capability change)
|
|
353
|
+
bun tauri build --debug
|
|
354
|
+
# Open the bundle so Launch Services indexes it
|
|
355
|
+
open src-tauri/target/debug/bundle/macos/your-app.app
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
Frontend (Vue/React/Svelte) HMR works fine through the built app if you point `frontendDist` at your dev server:
|
|
359
|
+
|
|
360
|
+
```json
|
|
361
|
+
// src-tauri/tauri.dev.conf.json
|
|
362
|
+
{
|
|
363
|
+
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
|
|
364
|
+
"build": {
|
|
365
|
+
"frontendDist": "http://localhost:3000",
|
|
366
|
+
"beforeBuildCommand": ""
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
```bash
|
|
372
|
+
bun tauri build --debug --config src-tauri/tauri.dev.conf.json
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
## Known limitations
|
|
376
|
+
|
|
377
|
+
- **macOS dev loop requires a bundle** (see above). Windows and Linux don't have this friction.
|
|
378
|
+
- **Scheme hijacking** — any app can register itself as a handler for your custom scheme. Production apps with meaningful attack surface should consider Universal Links (macOS) or App Links (Android equivalent).
|
|
379
|
+
- **Storage is the caller's responsibility.** This plugin doesn't bundle a secure-storage primitive; you provide the adapter.
|
|
380
|
+
- **No SSR support.** Tauri apps are client-only.
|
|
381
|
+
|
|
382
|
+
## Comparison to `@better-auth/expo`
|
|
383
|
+
|
|
384
|
+
Feature-for-feature parity on the auth flow, plus two Tauri-specific additions:
|
|
385
|
+
|
|
386
|
+
| Feature | Expo | Tauri (this) |
|
|
387
|
+
| --- | --- | --- |
|
|
388
|
+
| OAuth via system browser | ✅ | ✅ |
|
|
389
|
+
| Cookie bridge via URL query param | ✅ | ✅ |
|
|
390
|
+
| Authorization proxy endpoint for state | ✅ | ✅ |
|
|
391
|
+
| Redirect-to-scheme callback rewrite | ✅ | ✅ |
|
|
392
|
+
| Sign-out local cleanup | ✅ | ✅ |
|
|
393
|
+
| Third-party cookie filter | ✅ | ✅ |
|
|
394
|
+
| Focus refetch manager | ✅ (React Query) | ✅ (notifies Better Auth session signal) |
|
|
395
|
+
| Online refetch manager | ✅ (React Query) | ✅ (notifies Better Auth session signal) |
|
|
396
|
+
| **`x-tauri-cookie` smuggling** | — | ✅ required (browser forbids `Cookie`) |
|
|
397
|
+
| **`disableRedirect: true` injection** | — | ✅ required (browser has `window.location`) |
|
|
398
|
+
|
|
399
|
+
## License
|
|
400
|
+
|
|
401
|
+
MIT © [Rully Ardiansyah](https://github.com/DreamsHive)
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import type { BetterAuthClientPlugin, ClientStore } from "better-auth/client";
|
|
2
|
+
export interface TauriClientStorage {
|
|
3
|
+
getItem: (key: string) => string | null | Promise<string | null>;
|
|
4
|
+
setItem: (key: string, value: string) => void | Promise<void>;
|
|
5
|
+
}
|
|
6
|
+
export interface TauriClientOptions {
|
|
7
|
+
/**
|
|
8
|
+
* The custom URI scheme registered for the Tauri app in `tauri.conf.json`
|
|
9
|
+
* under `plugins.deep-link.desktop.schemes`.
|
|
10
|
+
*
|
|
11
|
+
* Example: `"sokudo"` → produces `sokudo://` deep links.
|
|
12
|
+
*/
|
|
13
|
+
scheme: string;
|
|
14
|
+
/**
|
|
15
|
+
* Persistent key/value storage for the local cookie jar and session cache.
|
|
16
|
+
* Provide an adapter around `@tauri-apps/plugin-store`, a keychain plugin,
|
|
17
|
+
* or `localStorage` for dev.
|
|
18
|
+
*/
|
|
19
|
+
storage: TauriClientStorage;
|
|
20
|
+
/**
|
|
21
|
+
* Prefix for keys written to `storage`.
|
|
22
|
+
* @default "better-auth"
|
|
23
|
+
*/
|
|
24
|
+
storagePrefix?: string;
|
|
25
|
+
/**
|
|
26
|
+
* The cookie-name prefix used by the Better Auth server. Used to filter
|
|
27
|
+
* the server's `Set-Cookie` header so third-party cookies (Cloudflare,
|
|
28
|
+
* analytics, etc.) don't trigger session refetches.
|
|
29
|
+
*
|
|
30
|
+
* Pass multiple prefixes if the server sets several (e.g. when you
|
|
31
|
+
* customize cookie names per instance).
|
|
32
|
+
*
|
|
33
|
+
* @default "better-auth"
|
|
34
|
+
*/
|
|
35
|
+
cookiePrefix?: string | string[];
|
|
36
|
+
/** Disable the local `/get-session` response cache. */
|
|
37
|
+
disableCache?: boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Refetch the session when the Tauri window regains focus.
|
|
40
|
+
* @default true
|
|
41
|
+
*/
|
|
42
|
+
refetchOnWindowFocus?: boolean;
|
|
43
|
+
/**
|
|
44
|
+
* Refetch the session when the network comes back online.
|
|
45
|
+
* @default true
|
|
46
|
+
*/
|
|
47
|
+
refetchOnReconnect?: boolean;
|
|
48
|
+
}
|
|
49
|
+
export declare const tauriClient: (opts: TauriClientOptions) => {
|
|
50
|
+
id: "tauri";
|
|
51
|
+
getActions(_: import("@better-fetch/fetch").BetterFetch, $store: ClientStore): {
|
|
52
|
+
/**
|
|
53
|
+
* Returns the currently-stored cookie string in the standard
|
|
54
|
+
* `key=value; key2=value2` format — useful if you need to attach it
|
|
55
|
+
* to a custom fetch outside of the Better Auth client.
|
|
56
|
+
*/
|
|
57
|
+
getCookie: () => Promise<string>;
|
|
58
|
+
};
|
|
59
|
+
fetchPlugins: {
|
|
60
|
+
id: string;
|
|
61
|
+
name: string;
|
|
62
|
+
hooks: {
|
|
63
|
+
onSuccess(context: import("@better-fetch/fetch").SuccessContext<any>): Promise<void>;
|
|
64
|
+
};
|
|
65
|
+
init(url: string, options: ({
|
|
66
|
+
cache?: RequestCache | undefined;
|
|
67
|
+
credentials?: RequestCredentials | undefined;
|
|
68
|
+
headers?: (HeadersInit & (HeadersInit | {
|
|
69
|
+
accept: "application/json" | "text/plain" | "application/octet-stream";
|
|
70
|
+
"content-type": "application/json" | "text/plain" | "application/x-www-form-urlencoded" | "multipart/form-data" | "application/octet-stream";
|
|
71
|
+
authorization: "Bearer" | "Basic";
|
|
72
|
+
})) | undefined;
|
|
73
|
+
integrity?: string | undefined;
|
|
74
|
+
keepalive?: boolean | undefined;
|
|
75
|
+
method?: string | undefined;
|
|
76
|
+
mode?: RequestMode | undefined;
|
|
77
|
+
priority?: RequestPriority | undefined;
|
|
78
|
+
redirect?: RequestRedirect | undefined;
|
|
79
|
+
referrer?: string | undefined;
|
|
80
|
+
referrerPolicy?: ReferrerPolicy | undefined;
|
|
81
|
+
signal?: (AbortSignal | null) | undefined;
|
|
82
|
+
window?: null | undefined;
|
|
83
|
+
onRequest?: (<T extends Record<string, any>>(context: import("@better-fetch/fetch").RequestContext<T>) => Promise<import("@better-fetch/fetch").RequestContext | void> | import("@better-fetch/fetch").RequestContext | void) | undefined;
|
|
84
|
+
onResponse?: ((context: import("@better-fetch/fetch").ResponseContext) => Promise<Response | void | import("@better-fetch/fetch").ResponseContext> | Response | import("@better-fetch/fetch").ResponseContext | void) | undefined;
|
|
85
|
+
onSuccess?: ((context: import("@better-fetch/fetch").SuccessContext<any>) => Promise<void> | void) | undefined;
|
|
86
|
+
onError?: ((context: import("@better-fetch/fetch").ErrorContext) => Promise<void> | void) | undefined;
|
|
87
|
+
onRetry?: ((response: import("@better-fetch/fetch").ResponseContext) => Promise<void> | void) | undefined;
|
|
88
|
+
hookOptions?: {
|
|
89
|
+
cloneResponse?: boolean;
|
|
90
|
+
} | undefined;
|
|
91
|
+
timeout?: number | undefined;
|
|
92
|
+
customFetchImpl?: import("@better-fetch/fetch").FetchEsque | undefined;
|
|
93
|
+
plugins?: import("@better-fetch/fetch").BetterFetchPlugin[] | undefined;
|
|
94
|
+
baseURL?: string | undefined;
|
|
95
|
+
throw?: boolean | undefined;
|
|
96
|
+
auth?: ({
|
|
97
|
+
type: "Bearer";
|
|
98
|
+
token: string | Promise<string | undefined> | (() => string | Promise<string | undefined> | undefined) | undefined;
|
|
99
|
+
} | {
|
|
100
|
+
type: "Basic";
|
|
101
|
+
username: string | (() => string | undefined) | undefined;
|
|
102
|
+
password: string | (() => string | undefined) | undefined;
|
|
103
|
+
} | {
|
|
104
|
+
type: "Custom";
|
|
105
|
+
prefix: string | (() => string | undefined) | undefined;
|
|
106
|
+
value: string | (() => string | undefined) | undefined;
|
|
107
|
+
}) | undefined;
|
|
108
|
+
body?: any;
|
|
109
|
+
query?: any;
|
|
110
|
+
params?: any;
|
|
111
|
+
duplex?: "full" | "half" | undefined;
|
|
112
|
+
jsonParser?: ((text: string) => Promise<any> | any) | undefined;
|
|
113
|
+
retry?: import("@better-fetch/fetch").RetryOptions | undefined;
|
|
114
|
+
retryAttempt?: number | undefined;
|
|
115
|
+
output?: (import("@better-fetch/fetch").StandardSchemaV1 | typeof Blob | typeof File) | undefined;
|
|
116
|
+
errorSchema?: import("@better-fetch/fetch").StandardSchemaV1 | undefined;
|
|
117
|
+
disableValidation?: boolean | undefined;
|
|
118
|
+
} & Record<string, any>) | undefined): Promise<{
|
|
119
|
+
url: string;
|
|
120
|
+
options: ({
|
|
121
|
+
cache?: RequestCache | undefined;
|
|
122
|
+
credentials?: RequestCredentials | undefined;
|
|
123
|
+
headers?: (HeadersInit & (HeadersInit | {
|
|
124
|
+
accept: "application/json" | "text/plain" | "application/octet-stream";
|
|
125
|
+
"content-type": "application/json" | "text/plain" | "application/x-www-form-urlencoded" | "multipart/form-data" | "application/octet-stream";
|
|
126
|
+
authorization: "Bearer" | "Basic";
|
|
127
|
+
})) | undefined;
|
|
128
|
+
integrity?: string | undefined;
|
|
129
|
+
keepalive?: boolean | undefined;
|
|
130
|
+
method?: string | undefined;
|
|
131
|
+
mode?: RequestMode | undefined;
|
|
132
|
+
priority?: RequestPriority | undefined;
|
|
133
|
+
redirect?: RequestRedirect | undefined;
|
|
134
|
+
referrer?: string | undefined;
|
|
135
|
+
referrerPolicy?: ReferrerPolicy | undefined;
|
|
136
|
+
signal?: (AbortSignal | null) | undefined;
|
|
137
|
+
window?: null | undefined;
|
|
138
|
+
onRequest?: (<T extends Record<string, any>>(context: import("@better-fetch/fetch").RequestContext<T>) => Promise<import("@better-fetch/fetch").RequestContext | void> | import("@better-fetch/fetch").RequestContext | void) | undefined;
|
|
139
|
+
onResponse?: ((context: import("@better-fetch/fetch").ResponseContext) => Promise<Response | void | import("@better-fetch/fetch").ResponseContext> | Response | import("@better-fetch/fetch").ResponseContext | void) | undefined;
|
|
140
|
+
onSuccess?: ((context: import("@better-fetch/fetch").SuccessContext<any>) => Promise<void> | void) | undefined;
|
|
141
|
+
onError?: ((context: import("@better-fetch/fetch").ErrorContext) => Promise<void> | void) | undefined;
|
|
142
|
+
onRetry?: ((response: import("@better-fetch/fetch").ResponseContext) => Promise<void> | void) | undefined;
|
|
143
|
+
hookOptions?: {
|
|
144
|
+
cloneResponse?: boolean;
|
|
145
|
+
} | undefined;
|
|
146
|
+
timeout?: number | undefined;
|
|
147
|
+
customFetchImpl?: import("@better-fetch/fetch").FetchEsque | undefined;
|
|
148
|
+
plugins?: import("@better-fetch/fetch").BetterFetchPlugin[] | undefined;
|
|
149
|
+
baseURL?: string | undefined;
|
|
150
|
+
throw?: boolean | undefined;
|
|
151
|
+
auth?: ({
|
|
152
|
+
type: "Bearer";
|
|
153
|
+
token: string | Promise<string | undefined> | (() => string | Promise<string | undefined> | undefined) | undefined;
|
|
154
|
+
} | {
|
|
155
|
+
type: "Basic";
|
|
156
|
+
username: string | (() => string | undefined) | undefined;
|
|
157
|
+
password: string | (() => string | undefined) | undefined;
|
|
158
|
+
} | {
|
|
159
|
+
type: "Custom";
|
|
160
|
+
prefix: string | (() => string | undefined) | undefined;
|
|
161
|
+
value: string | (() => string | undefined) | undefined;
|
|
162
|
+
}) | undefined;
|
|
163
|
+
body?: any;
|
|
164
|
+
query?: any;
|
|
165
|
+
params?: any;
|
|
166
|
+
duplex?: "full" | "half" | undefined;
|
|
167
|
+
jsonParser?: ((text: string) => Promise<any> | any) | undefined;
|
|
168
|
+
retry?: import("@better-fetch/fetch").RetryOptions | undefined;
|
|
169
|
+
retryAttempt?: number | undefined;
|
|
170
|
+
output?: (import("@better-fetch/fetch").StandardSchemaV1 | typeof Blob | typeof File) | undefined;
|
|
171
|
+
errorSchema?: import("@better-fetch/fetch").StandardSchemaV1 | undefined;
|
|
172
|
+
disableValidation?: boolean | undefined;
|
|
173
|
+
} & Record<string, any>) | undefined;
|
|
174
|
+
}>;
|
|
175
|
+
}[];
|
|
176
|
+
};
|
|
177
|
+
export { PACKAGE_VERSION } from "./version";
|
|
178
|
+
export type { BetterAuthClientPlugin };
|
|
179
|
+
export { setupTauriFocusManager } from "./focus-manager";
|
|
180
|
+
export { setupTauriOnlineManager } from "./online-manager";
|
|
181
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,sBAAsB,EACtB,WAAW,EACZ,MAAM,oBAAoB,CAAC;AAe5B,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACjE,OAAO,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/D;AAED,MAAM,WAAW,kBAAkB;IACjC;;;;;OAKG;IACH,MAAM,EAAE,MAAM,CAAC;IAEf;;;;OAIG;IACH,OAAO,EAAE,kBAAkB,CAAC;IAE5B;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB;;;;;;;;;OASG;IACH,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAEjC,uDAAuD;IACvD,YAAY,CAAC,EAAE,OAAO,CAAC;IAEvB;;;OAGG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAE/B;;;OAGG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B;AAsLD,eAAO,MAAM,WAAW,GAAI,MAAM,kBAAkB;;;QAoC5C;;;;WAIG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;6BAsMmgC,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iCAAD,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAN9gC,CAAC;AAEF,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAC5C,YAAY,EAAE,sBAAsB,EAAE,CAAC;AACvC,OAAO,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AACzD,OAAO,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAC"}
|