@soorya-u/better-auth-desktop 0.1.0 → 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/README.md +35 -3
- package/dist/adapters/electrobun.d.mts +3 -3
- package/dist/adapters/electrobun.mjs +22 -4
- package/dist/adapters/electron.d.mts +3 -2
- package/dist/adapters/electron.mjs +33 -4
- package/dist/{client-DhRcoap2.mjs → client-CWgaf2aA.mjs} +4 -2
- package/dist/{client-De_zQzE1.d.mts → client-DsuEHH54.d.mts} +1 -1
- package/dist/client.d.mts +2 -2
- package/dist/client.mjs +1 -1
- package/dist/core/index.d.mts +6 -6
- package/dist/core/index.mjs +5 -5
- package/dist/{exchange-DCZ0j_T5.mjs → exchange-D93Uwk6_.mjs} +94 -55
- package/dist/{index-CP_2Wlut.d.mts → index-xd3h5rM3.d.mts} +7 -10
- package/dist/index.d.mts +6 -6
- package/dist/index.mjs +6 -6
- package/dist/{loopback-DTQgg1Za.mjs → loopback-CNOfW74-.mjs} +5 -4
- package/dist/{loopback-ClkAoS2u.d.mts → loopback-Dec4O7Rx.d.mts} +5 -2
- package/dist/react.d.mts +14 -0
- package/dist/react.mjs +31 -0
- package/dist/rpc/webview.d.mts +4 -22
- package/dist/rpc/webview.mjs +49 -15
- package/dist/schema-CE7bJ4bb.d.mts +33 -0
- package/dist/server/index.d.mts +1 -1
- package/dist/server/index.mjs +2 -2
- package/dist/{storage-BcwDmS-W.mjs → storage-DR69vdzj.mjs} +1 -1
- package/dist/storage-DbD61OuL.d.mts +11 -0
- package/dist/{types-h9AEfSnq.d.mts → types-oAdD2Qlz.d.mts} +2 -6
- package/dist/{version-1hEssvcU.mjs → version-D6UEvd2R.mjs} +1 -1
- package/dist/web/index.d.mts +39 -21
- package/dist/web/index.mjs +60 -25
- package/package.json +19 -3
- package/dist/storage-DtgX_vmE.d.mts +0 -10
package/README.md
CHANGED
|
@@ -113,6 +113,7 @@ export const authClient = createAuthClient({
|
|
|
113
113
|
clientID: "my-desktop-app",
|
|
114
114
|
storage: await keychainStorage(),
|
|
115
115
|
// loopbackPort: 51789, // optional; omit to bind 127.0.0.1:0 (OS-assigned)
|
|
116
|
+
// onStorageError: (err) => console.error("Keychain write failed", err),
|
|
116
117
|
}),
|
|
117
118
|
],
|
|
118
119
|
});
|
|
@@ -142,8 +143,12 @@ auth.onAuthenticated((user) => navigate("/app"));
|
|
|
142
143
|
await auth.requestAuth({ provider: "github" });
|
|
143
144
|
```
|
|
144
145
|
|
|
145
|
-
The bridge exposes `requestAuth
|
|
146
|
-
|
|
146
|
+
The bridge exposes: `requestAuth` (open browser flow), `getAuthUrl` (return the
|
|
147
|
+
URL without opening a browser — useful for copy-link flows), `getUser`,
|
|
148
|
+
`getSession`, `signOut`, `getUserImage`, `watchUser` (reactive subscription to
|
|
149
|
+
the current user, race-safe), `destroy` (remove all listeners on unmount /
|
|
150
|
+
hot-reload), and the `onAuthenticated` / `onUserUpdated` / `onAuthError` event
|
|
151
|
+
subscriptions.
|
|
147
152
|
|
|
148
153
|
No `urlSchemes` / `protocol` registration is needed in `electrobun.config.ts`.
|
|
149
154
|
|
|
@@ -199,6 +204,32 @@ const authClient = createAuthClient({ plugins: [webDesktop()] });
|
|
|
199
204
|
authClient.forwardToDesktop();
|
|
200
205
|
```
|
|
201
206
|
|
|
207
|
+
### Wrapping an auth client (renderer / web)
|
|
208
|
+
|
|
209
|
+
`wrapForDesktop` proxy-wraps a `better-auth` client so that `getSession`,
|
|
210
|
+
`signOut`, and `signIn.social` delegate to the desktop bridge instead of hitting
|
|
211
|
+
the network directly. Pass `null` as the bridge to get the original client
|
|
212
|
+
back unchanged (useful during SSR / before the bridge is initialized).
|
|
213
|
+
|
|
214
|
+
```ts
|
|
215
|
+
import { wrapForDesktop } from "@soorya-u/better-auth-desktop/web";
|
|
216
|
+
|
|
217
|
+
// wrap once, use everywhere like a normal better-auth client
|
|
218
|
+
const authClient = wrapForDesktop(baseClient, bridge);
|
|
219
|
+
await authClient.signIn.social({ provider: "github" }); // → opens loopback flow
|
|
220
|
+
await authClient.signOut(); // → bridge.signOut()
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
To also intercept `useSession`, pass a framework hook as the third argument:
|
|
224
|
+
|
|
225
|
+
```ts
|
|
226
|
+
import { useSession } from "@soorya-u/better-auth-desktop/react"; // React
|
|
227
|
+
const authClient = wrapForDesktop(baseClient, bridge, useSession);
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
Or supply your own hook for Vue / Svelte — type it with `UseSessionFn` from
|
|
231
|
+
`@soorya-u/better-auth-desktop/web`.
|
|
232
|
+
|
|
202
233
|
### Customizing the loopback success page
|
|
203
234
|
|
|
204
235
|
By default the loopback serves a minimal "you can close this tab" page. Override
|
|
@@ -240,7 +271,8 @@ type DesktopAdapter = {
|
|
|
240
271
|
| `@soorya-u/better-auth-desktop/electrobun` | `electrobunDesktop()` plugin + `keychainStorage()` (Bun main process) |
|
|
241
272
|
| `@soorya-u/better-auth-desktop/rpc/webview` | `defineAuthWebviewRPC()` (Electrobun renderer) |
|
|
242
273
|
| `@soorya-u/better-auth-desktop/electron` | `electronDesktop()` plugin + `electronStorage()` (Electron main process) |
|
|
243
|
-
| `@soorya-u/better-auth-desktop/web` | `forwardToDesktop()
|
|
274
|
+
| `@soorya-u/better-auth-desktop/web` | `wrapForDesktop()`, `forwardToDesktop()`, `webDesktop()` plugin |
|
|
275
|
+
| `@soorya-u/better-auth-desktop/react` | `useSession()` React hook for the desktop bridge |
|
|
244
276
|
| `@soorya-u/better-auth-desktop/client` | `desktopClient()` Better Auth client plugin |
|
|
245
277
|
| `@soorya-u/better-auth-desktop/core` | shared types & utilities for custom adapters |
|
|
246
278
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { n as AuthUser } from "../types-
|
|
2
|
-
import { n as DesktopClientPluginOptions } from "../client-
|
|
3
|
-
import { n as keychainStorage, t as KeychainStorageOptions } from "../storage-
|
|
1
|
+
import { n as AuthUser } from "../types-oAdD2Qlz.mjs";
|
|
2
|
+
import { n as DesktopClientPluginOptions } from "../client-DsuEHH54.mjs";
|
|
3
|
+
import { n as keychainStorage, t as KeychainStorageOptions } from "../storage-DbD61OuL.mjs";
|
|
4
4
|
import * as _$_better_fetch_fetch0 from "@better-fetch/fetch";
|
|
5
5
|
import { BetterFetch } from "@better-fetch/fetch";
|
|
6
6
|
import { BetterAuthClientOptions, ClientStore } from "@better-auth/core";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { t as PACKAGE_VERSION } from "../version-
|
|
2
|
-
import { t as createDesktopCookieLayer } from "../client-
|
|
3
|
-
import {
|
|
4
|
-
import { t as keychainStorage } from "../storage-
|
|
1
|
+
import { t as PACKAGE_VERSION } from "../version-D6UEvd2R.mjs";
|
|
2
|
+
import { t as createDesktopCookieLayer } from "../client-CWgaf2aA.mjs";
|
|
3
|
+
import { i as fetchUserImage, r as startAuthFlow, t as buildAuthUrl } from "../exchange-D93Uwk6_.mjs";
|
|
4
|
+
import { t as keychainStorage } from "../storage-DR69vdzj.mjs";
|
|
5
5
|
import { BrowserView, Utils } from "electrobun/bun";
|
|
6
6
|
//#region src/adapters/electrobun.ts
|
|
7
7
|
function createLoopbackAdapter(getSender, storage) {
|
|
@@ -109,6 +109,24 @@ const electrobunDesktop = (options) => {
|
|
|
109
109
|
})
|
|
110
110
|
});
|
|
111
111
|
},
|
|
112
|
+
getAuthUrl: async ({ options: cfg }) => {
|
|
113
|
+
return await buildAuthUrl({
|
|
114
|
+
adapter,
|
|
115
|
+
$fetch,
|
|
116
|
+
clientOptions,
|
|
117
|
+
options,
|
|
118
|
+
cfg,
|
|
119
|
+
onAuthenticated: (user) => adapter.notifyRenderer({
|
|
120
|
+
type: "authenticated",
|
|
121
|
+
user
|
|
122
|
+
}),
|
|
123
|
+
onError: (error) => adapter.notifyRenderer({
|
|
124
|
+
type: "error",
|
|
125
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
126
|
+
path: "/desktop/init-oauth-proxy"
|
|
127
|
+
})
|
|
128
|
+
});
|
|
129
|
+
},
|
|
112
130
|
getUser: async () => {
|
|
113
131
|
return await sanitizeUser((await $fetch("/get-session", {
|
|
114
132
|
method: "GET",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { l as Storage } from "../types-
|
|
2
|
-
import { n as DesktopClientPluginOptions } from "../client-
|
|
1
|
+
import { l as Storage } from "../types-oAdD2Qlz.mjs";
|
|
2
|
+
import { n as DesktopClientPluginOptions } from "../client-DsuEHH54.mjs";
|
|
3
3
|
import * as _$_better_fetch_fetch0 from "@better-fetch/fetch";
|
|
4
4
|
import { BetterFetch } from "@better-fetch/fetch";
|
|
5
5
|
import { BetterAuthClientOptions, ClientStore } from "@better-auth/core";
|
|
@@ -7,6 +7,7 @@ import { BetterAuthClientOptions, ClientStore } from "@better-auth/core";
|
|
|
7
7
|
//#region src/adapters/electron.d.ts
|
|
8
8
|
declare const ELECTRON_AUTH_CHANNELS: {
|
|
9
9
|
readonly requestAuth: "desktop:requestAuth";
|
|
10
|
+
readonly getAuthUrl: "desktop:getAuthUrl";
|
|
10
11
|
readonly getUser: "desktop:getUser";
|
|
11
12
|
readonly signOut: "desktop:signOut";
|
|
12
13
|
readonly getUserImage: "desktop:getUserImage";
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import { t as PACKAGE_VERSION } from "../version-
|
|
2
|
-
import { t as createDesktopCookieLayer } from "../client-
|
|
3
|
-
import {
|
|
1
|
+
import { t as PACKAGE_VERSION } from "../version-D6UEvd2R.mjs";
|
|
2
|
+
import { t as createDesktopCookieLayer } from "../client-CWgaf2aA.mjs";
|
|
3
|
+
import { i as fetchUserImage, r as startAuthFlow, t as buildAuthUrl } from "../exchange-D93Uwk6_.mjs";
|
|
4
4
|
import { Buffer } from "node:buffer";
|
|
5
5
|
import { createServer } from "node:http";
|
|
6
6
|
//#region src/adapters/electron.ts
|
|
7
7
|
const ELECTRON_AUTH_CHANNELS = {
|
|
8
8
|
requestAuth: "desktop:requestAuth",
|
|
9
|
+
getAuthUrl: "desktop:getAuthUrl",
|
|
9
10
|
getUser: "desktop:getUser",
|
|
10
11
|
signOut: "desktop:signOut",
|
|
11
12
|
getUserImage: "desktop:getUserImage",
|
|
@@ -56,7 +57,7 @@ function createLoopbackAdapter(shell, getWindow, storage) {
|
|
|
56
57
|
return {
|
|
57
58
|
openExternal: (url) => shell.openExternal(url),
|
|
58
59
|
serveLoopback(onRequest, opts) {
|
|
59
|
-
return new Promise((resolve) => {
|
|
60
|
+
return new Promise((resolve, reject) => {
|
|
60
61
|
const server = createServer(async (req, res) => {
|
|
61
62
|
const url = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
62
63
|
const query = {};
|
|
@@ -70,7 +71,16 @@ function createLoopbackAdapter(shell, getWindow, storage) {
|
|
|
70
71
|
res.writeHead(out.status, out.headers);
|
|
71
72
|
res.end(out.body);
|
|
72
73
|
});
|
|
74
|
+
const timer = setTimeout(() => {
|
|
75
|
+
server.close();
|
|
76
|
+
reject(/* @__PURE__ */ new Error("Loopback server timed out waiting for port"));
|
|
77
|
+
}, 1e4);
|
|
78
|
+
server.on("error", (err) => {
|
|
79
|
+
clearTimeout(timer);
|
|
80
|
+
reject(err);
|
|
81
|
+
});
|
|
73
82
|
server.listen(opts?.port ?? 0, "127.0.0.1", () => {
|
|
83
|
+
clearTimeout(timer);
|
|
74
84
|
const address = server.address();
|
|
75
85
|
resolve({
|
|
76
86
|
port: typeof address === "object" && address ? address.port : 0,
|
|
@@ -156,6 +166,24 @@ const electronDesktop = (options) => {
|
|
|
156
166
|
})
|
|
157
167
|
});
|
|
158
168
|
});
|
|
169
|
+
ipcMain.handle(ELECTRON_AUTH_CHANNELS.getAuthUrl, async (_event, cfg) => {
|
|
170
|
+
return await buildAuthUrl({
|
|
171
|
+
adapter,
|
|
172
|
+
$fetch,
|
|
173
|
+
clientOptions,
|
|
174
|
+
options,
|
|
175
|
+
cfg,
|
|
176
|
+
onAuthenticated: (user) => adapter.notifyRenderer({
|
|
177
|
+
type: "authenticated",
|
|
178
|
+
user
|
|
179
|
+
}),
|
|
180
|
+
onError: (error) => adapter.notifyRenderer({
|
|
181
|
+
type: "error",
|
|
182
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
183
|
+
path: "/desktop/init-oauth-proxy"
|
|
184
|
+
})
|
|
185
|
+
});
|
|
186
|
+
});
|
|
159
187
|
ipcMain.handle(ELECTRON_AUTH_CHANNELS.getUser, async () => {
|
|
160
188
|
return await sanitizeUser((await $fetch("/get-session", {
|
|
161
189
|
method: "GET",
|
|
@@ -192,6 +220,7 @@ const electronDesktop = (options) => {
|
|
|
192
220
|
return () => {
|
|
193
221
|
unsub?.();
|
|
194
222
|
ipcMain.removeHandler(ELECTRON_AUTH_CHANNELS.requestAuth);
|
|
223
|
+
ipcMain.removeHandler(ELECTRON_AUTH_CHANNELS.getAuthUrl);
|
|
195
224
|
ipcMain.removeHandler(ELECTRON_AUTH_CHANNELS.getUser);
|
|
196
225
|
ipcMain.removeHandler(ELECTRON_AUTH_CHANNELS.signOut);
|
|
197
226
|
ipcMain.removeHandler(ELECTRON_AUTH_CHANNELS.getUserImage);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as PACKAGE_VERSION } from "./version-
|
|
1
|
+
import { t as PACKAGE_VERSION } from "./version-D6UEvd2R.mjs";
|
|
2
2
|
import { cookieNameRegex, parseSetCookieHeader } from "better-auth/cookies";
|
|
3
3
|
//#region src/core/cookies.ts
|
|
4
4
|
function getSetCookie(header, prevCookie) {
|
|
@@ -64,7 +64,9 @@ function createDesktopCookieLayer(options) {
|
|
|
64
64
|
const setStored = (name, value) => {
|
|
65
65
|
try {
|
|
66
66
|
opts.storage.setItem(name, value);
|
|
67
|
-
} catch {
|
|
67
|
+
} catch (error) {
|
|
68
|
+
opts.onStorageError?.(error);
|
|
69
|
+
}
|
|
68
70
|
};
|
|
69
71
|
let store = null;
|
|
70
72
|
let serverOrigin;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { i as DesktopClientOptions, l as Storage } from "./types-
|
|
1
|
+
import { i as DesktopClientOptions, l as Storage } from "./types-oAdD2Qlz.mjs";
|
|
2
2
|
import { BetterFetch, BetterFetchPlugin } from "@better-fetch/fetch";
|
|
3
3
|
import { BetterAuthClientOptions, ClientStore } from "@better-auth/core";
|
|
4
4
|
|
package/dist/client.d.mts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { i as DesktopClientOptions, l as Storage } from "./types-
|
|
2
|
-
import { a as desktopClient, n as DesktopClientPluginOptions, t as DesktopClientInternals } from "./client-
|
|
1
|
+
import { i as DesktopClientOptions, l as Storage } from "./types-oAdD2Qlz.mjs";
|
|
2
|
+
import { a as desktopClient, n as DesktopClientPluginOptions, t as DesktopClientInternals } from "./client-DsuEHH54.mjs";
|
|
3
3
|
export { type DesktopClientInternals, type DesktopClientOptions, type DesktopClientPluginOptions, type Storage, desktopClient };
|
package/dist/client.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { n as desktopClient } from "./client-
|
|
1
|
+
import { n as desktopClient } from "./client-CWgaf2aA.mjs";
|
|
2
2
|
export { desktopClient };
|
package/dist/core/index.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { a as LoopbackRequest, c as RequestAuthOptions, i as DesktopClientOptions, l as Storage, n as AuthUser, o as LoopbackResponse, r as DesktopAdapter, s as LoopbackServer, t as AuthEvent } from "../types-
|
|
2
|
-
import { a as desktopClient, i as createDesktopCookieLayer, n as DesktopClientPluginOptions, r as DesktopCookieLayer, t as DesktopClientInternals } from "../client-
|
|
3
|
-
import { n as keychainStorage, t as KeychainStorageOptions } from "../storage-
|
|
4
|
-
import { a as StartAuthFlowArgs, i as ExchangeTokenArgs, n as fetchUserImage, o as exchangeToken, r as normalizeUserOutput, s as startAuthFlow, t as FetchUserImageResult } from "../index-
|
|
5
|
-
import { a as isAllowedLoopbackPort, i as generateNonce, n as LOOPBACK_HOST, o as parseLoopbackUrl, r as buildLoopbackUrl,
|
|
6
|
-
export { AllowedLoopbackPorts, AuthEvent, AuthUser, DesktopAdapter, DesktopClientInternals, DesktopClientOptions, DesktopClientPluginOptions, DesktopCookieLayer, ExchangeTokenArgs, FetchUserImageResult, KeychainStorageOptions, LOOPBACK_HOST, LoopbackRequest, LoopbackResponse, LoopbackServer, RequestAuthOptions, StartAuthFlowArgs, Storage, buildLoopbackUrl, createDesktopCookieLayer, desktopClient, exchangeToken, fetchUserImage, generateNonce, isAllowedLoopbackPort, keychainStorage, normalizeUserOutput, parseLoopbackUrl, startAuthFlow
|
|
1
|
+
import { a as LoopbackRequest, c as RequestAuthOptions, i as DesktopClientOptions, l as Storage, n as AuthUser, o as LoopbackResponse, r as DesktopAdapter, s as LoopbackServer, t as AuthEvent } from "../types-oAdD2Qlz.mjs";
|
|
2
|
+
import { a as desktopClient, i as createDesktopCookieLayer, n as DesktopClientPluginOptions, r as DesktopCookieLayer, t as DesktopClientInternals } from "../client-DsuEHH54.mjs";
|
|
3
|
+
import { n as keychainStorage, t as KeychainStorageOptions } from "../storage-DbD61OuL.mjs";
|
|
4
|
+
import { a as StartAuthFlowArgs, i as ExchangeTokenArgs, n as fetchUserImage, o as exchangeToken, r as normalizeUserOutput, s as startAuthFlow, t as FetchUserImageResult } from "../index-xd3h5rM3.mjs";
|
|
5
|
+
import { a as isAllowedLoopbackPort, i as generateNonce, n as LOOPBACK_HOST, o as parseLoopbackUrl, r as buildLoopbackUrl, t as AllowedLoopbackPorts } from "../loopback-Dec4O7Rx.mjs";
|
|
6
|
+
export { AllowedLoopbackPorts, AuthEvent, AuthUser, DesktopAdapter, DesktopClientInternals, DesktopClientOptions, DesktopClientPluginOptions, DesktopCookieLayer, ExchangeTokenArgs, FetchUserImageResult, KeychainStorageOptions, LOOPBACK_HOST, LoopbackRequest, LoopbackResponse, LoopbackServer, RequestAuthOptions, StartAuthFlowArgs, Storage, buildLoopbackUrl, createDesktopCookieLayer, desktopClient, exchangeToken, fetchUserImage, generateNonce, isAllowedLoopbackPort, keychainStorage, normalizeUserOutput, parseLoopbackUrl, startAuthFlow };
|
package/dist/core/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { n as desktopClient, t as createDesktopCookieLayer } from "../client-
|
|
2
|
-
import { a as parseLoopbackUrl, i as isAllowedLoopbackPort, n as buildLoopbackUrl,
|
|
3
|
-
import {
|
|
4
|
-
import { t as keychainStorage } from "../storage-
|
|
5
|
-
export { LOOPBACK_HOST, buildLoopbackUrl, createDesktopCookieLayer, desktopClient, exchangeToken, fetchUserImage, generateNonce, isAllowedLoopbackPort, keychainStorage, normalizeUserOutput, parseLoopbackUrl, startAuthFlow
|
|
1
|
+
import { n as desktopClient, t as createDesktopCookieLayer } from "../client-CWgaf2aA.mjs";
|
|
2
|
+
import { a as parseLoopbackUrl, i as isAllowedLoopbackPort, n as buildLoopbackUrl, r as generateNonce, t as LOOPBACK_HOST } from "../loopback-CNOfW74-.mjs";
|
|
3
|
+
import { a as normalizeUserOutput, i as fetchUserImage, n as exchangeToken, r as startAuthFlow } from "../exchange-D93Uwk6_.mjs";
|
|
4
|
+
import { t as keychainStorage } from "../storage-DR69vdzj.mjs";
|
|
5
|
+
export { LOOPBACK_HOST, buildLoopbackUrl, createDesktopCookieLayer, desktopClient, exchangeToken, fetchUserImage, generateNonce, isAllowedLoopbackPort, keychainStorage, normalizeUserOutput, parseLoopbackUrl, startAuthFlow };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as buildLoopbackUrl,
|
|
1
|
+
import { n as buildLoopbackUrl, r as generateNonce } from "./loopback-CNOfW74-.mjs";
|
|
2
2
|
import { BetterAuthError } from "@better-auth/core/error";
|
|
3
3
|
import { base64, base64Url } from "@better-auth/utils/base64";
|
|
4
4
|
import { createHash } from "@better-auth/utils/hash";
|
|
@@ -28,22 +28,31 @@ async function fetchUserImage(baseURL, url) {
|
|
|
28
28
|
} catch {
|
|
29
29
|
return null;
|
|
30
30
|
}
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
31
|
+
const controller = new AbortController();
|
|
32
|
+
const fetchTimer = setTimeout(() => controller.abort(), 1e4);
|
|
33
|
+
try {
|
|
34
|
+
const response = await fetch(resolvedUrl, {
|
|
35
|
+
method: "GET",
|
|
36
|
+
headers: { accept: "image/*" },
|
|
37
|
+
signal: controller.signal
|
|
38
|
+
});
|
|
39
|
+
if (!response.ok) return null;
|
|
40
|
+
const contentType = response.headers.get("content-type");
|
|
41
|
+
if (!contentType?.startsWith("image/") || contentType.startsWith("image/svg")) return null;
|
|
42
|
+
const contentLength = response.headers.get("content-length");
|
|
43
|
+
if (contentLength && Number(contentLength) > DEFAULT_MAX_BYTES) return null;
|
|
44
|
+
const buf = await response.arrayBuffer();
|
|
45
|
+
const bytes = new Uint8Array(buf);
|
|
46
|
+
if (bytes.byteLength > DEFAULT_MAX_BYTES) return null;
|
|
47
|
+
return {
|
|
48
|
+
bytes,
|
|
49
|
+
mimeType: contentType.split(";")[0]?.trim() || "image/png"
|
|
50
|
+
};
|
|
51
|
+
} catch {
|
|
52
|
+
return null;
|
|
53
|
+
} finally {
|
|
54
|
+
clearTimeout(fetchTimer);
|
|
55
|
+
}
|
|
47
56
|
}
|
|
48
57
|
function normalizeUserOutput(user, _options) {
|
|
49
58
|
return { ...user };
|
|
@@ -100,10 +109,15 @@ function loopbackSuccessResponse(success) {
|
|
|
100
109
|
headers: { location: success.redirectTo },
|
|
101
110
|
body: ""
|
|
102
111
|
};
|
|
103
|
-
return {
|
|
112
|
+
if (typeof success === "string") return {
|
|
104
113
|
status: 200,
|
|
105
114
|
headers: { "content-type": "text/html; charset=utf-8" },
|
|
106
|
-
body: success
|
|
115
|
+
body: success
|
|
116
|
+
};
|
|
117
|
+
return {
|
|
118
|
+
status: 200,
|
|
119
|
+
headers: { "content-type": "text/plain; charset=utf-8" },
|
|
120
|
+
body: "Authentication complete. You can close this tab."
|
|
107
121
|
};
|
|
108
122
|
}
|
|
109
123
|
async function sanitize(user, options) {
|
|
@@ -117,6 +131,10 @@ async function sanitize(user, options) {
|
|
|
117
131
|
if (u !== null) u = normalizeUserOutput(u, options);
|
|
118
132
|
return u;
|
|
119
133
|
}
|
|
134
|
+
/**
|
|
135
|
+
* Decodes the redirect token, looks up the matching PKCE verifier, and
|
|
136
|
+
* exchanges it at `/desktop/token`. Called automatically by the loopback handler.
|
|
137
|
+
*/
|
|
120
138
|
async function exchangeToken({ $fetch, options, token, onAuthenticated, fetchOptions }) {
|
|
121
139
|
const decoded = safeJSONParse(new TextDecoder().decode(base64Url.decode(decodeURIComponent(token))));
|
|
122
140
|
const codeVerifier = decoded ? verifierStore.get(decoded.state) : void 0;
|
|
@@ -139,7 +157,7 @@ async function exchangeToken({ $fetch, options, token, onAuthenticated, fetchOpt
|
|
|
139
157
|
}
|
|
140
158
|
});
|
|
141
159
|
}
|
|
142
|
-
async function
|
|
160
|
+
async function prepareAuthFlow({ adapter, $fetch, clientOptions, options, cfg, onAuthenticated, onError }) {
|
|
143
161
|
const baseURL = getBaseURL(clientOptions?.baseURL, clientOptions?.basePath, void 0, true);
|
|
144
162
|
if (!baseURL) throw new BetterAuthError("Base URL is required to start a desktop sign-in flow.");
|
|
145
163
|
const state = generateRandomString(16, "A-Z", "a-z", "0-9");
|
|
@@ -157,40 +175,45 @@ async function startAuthFlow({ adapter, $fetch, clientOptions, options, cfg, onA
|
|
|
157
175
|
server = null;
|
|
158
176
|
verifierStore.delete(state);
|
|
159
177
|
};
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
if (req.query.nonce !== nonce) return {
|
|
166
|
-
status: 403,
|
|
167
|
-
body: "Forbidden"
|
|
168
|
-
};
|
|
169
|
-
const token = req.query.token;
|
|
170
|
-
if (!token) return {
|
|
171
|
-
status: 400,
|
|
172
|
-
body: "Missing token"
|
|
173
|
-
};
|
|
174
|
-
try {
|
|
175
|
-
await exchangeToken({
|
|
176
|
-
$fetch,
|
|
177
|
-
options,
|
|
178
|
-
token,
|
|
179
|
-
onAuthenticated,
|
|
180
|
-
fetchOptions: { throw: true }
|
|
181
|
-
});
|
|
182
|
-
} catch (error) {
|
|
183
|
-
cleanup();
|
|
184
|
-
onError?.(error);
|
|
185
|
-
return {
|
|
186
|
-
status: 500,
|
|
187
|
-
headers: { "content-type": "text/plain; charset=utf-8" },
|
|
188
|
-
body: "Sign-in failed. You can close this tab."
|
|
178
|
+
try {
|
|
179
|
+
server = await adapter.serveLoopback(async (req) => {
|
|
180
|
+
if (req.path !== loopbackPath) return {
|
|
181
|
+
status: 404,
|
|
182
|
+
body: "Not found"
|
|
189
183
|
};
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
184
|
+
if (req.query.nonce !== nonce) return {
|
|
185
|
+
status: 403,
|
|
186
|
+
body: "Forbidden"
|
|
187
|
+
};
|
|
188
|
+
const token = req.query.token;
|
|
189
|
+
if (!token) return {
|
|
190
|
+
status: 400,
|
|
191
|
+
body: "Missing token"
|
|
192
|
+
};
|
|
193
|
+
try {
|
|
194
|
+
await exchangeToken({
|
|
195
|
+
$fetch,
|
|
196
|
+
options,
|
|
197
|
+
token,
|
|
198
|
+
onAuthenticated,
|
|
199
|
+
fetchOptions: { throw: true }
|
|
200
|
+
});
|
|
201
|
+
} catch (error) {
|
|
202
|
+
cleanup();
|
|
203
|
+
onError?.(error);
|
|
204
|
+
return {
|
|
205
|
+
status: 500,
|
|
206
|
+
headers: { "content-type": "text/plain; charset=utf-8" },
|
|
207
|
+
body: "Sign-in failed. You can close this tab."
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
cleanup();
|
|
211
|
+
return loopbackSuccessResponse(options.loopbackSuccess);
|
|
212
|
+
}, { port: options.loopbackPort });
|
|
213
|
+
} catch (error) {
|
|
214
|
+
verifierStore.delete(state);
|
|
215
|
+
throw error;
|
|
216
|
+
}
|
|
194
217
|
const loopbackUrl = buildLoopbackUrl(server.port, loopbackPath, nonce);
|
|
195
218
|
const url = new URL(`${baseURL}/desktop/init-oauth-proxy`);
|
|
196
219
|
for (const [key, value] of Object.entries(cfg)) {
|
|
@@ -206,12 +229,28 @@ async function startAuthFlow({ adapter, $fetch, clientOptions, options, cfg, onA
|
|
|
206
229
|
cleanup();
|
|
207
230
|
onError?.(new BetterAuthError("Desktop sign-in timed out."));
|
|
208
231
|
}, options.loopbackTimeout ?? DEFAULT_LOOPBACK_TIMEOUT);
|
|
232
|
+
return {
|
|
233
|
+
url: url.toString(),
|
|
234
|
+
cleanup
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Returns the init-oauth-proxy URL without opening a browser.
|
|
239
|
+
* The loopback server starts listening in the background — use for copy-link flows.
|
|
240
|
+
*/
|
|
241
|
+
async function buildAuthUrl(args) {
|
|
242
|
+
const { url } = await prepareAuthFlow(args);
|
|
243
|
+
return url;
|
|
244
|
+
}
|
|
245
|
+
/** Opens the init-oauth-proxy URL in the system browser and awaits the loopback callback. */
|
|
246
|
+
async function startAuthFlow(args) {
|
|
247
|
+
const { url, cleanup } = await prepareAuthFlow(args);
|
|
209
248
|
try {
|
|
210
|
-
await adapter.openExternal(url
|
|
249
|
+
await args.adapter.openExternal(url);
|
|
211
250
|
} catch (error) {
|
|
212
251
|
cleanup();
|
|
213
252
|
throw error;
|
|
214
253
|
}
|
|
215
254
|
}
|
|
216
255
|
//#endregion
|
|
217
|
-
export { normalizeUserOutput as i,
|
|
256
|
+
export { normalizeUserOutput as a, fetchUserImage as i, exchangeToken as n, startAuthFlow as r, buildAuthUrl as t };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { c as RequestAuthOptions, i as DesktopClientOptions, n as AuthUser, r as DesktopAdapter } from "./types-
|
|
1
|
+
import { c as RequestAuthOptions, i as DesktopClientOptions, n as AuthUser, r as DesktopAdapter } from "./types-oAdD2Qlz.mjs";
|
|
2
2
|
import { BetterFetch, CreateFetchOption } from "@better-fetch/fetch";
|
|
3
3
|
import { BetterAuthClientOptions } from "@better-auth/core";
|
|
4
4
|
import { User } from "@better-auth/core/db";
|
|
@@ -11,6 +11,10 @@ type ExchangeTokenArgs = {
|
|
|
11
11
|
onAuthenticated: (user: AuthUser) => void;
|
|
12
12
|
fetchOptions?: Omit<CreateFetchOption, "method"> | undefined;
|
|
13
13
|
};
|
|
14
|
+
/**
|
|
15
|
+
* Decodes the redirect token, looks up the matching PKCE verifier, and
|
|
16
|
+
* exchanges it at `/desktop/token`. Called automatically by the loopback handler.
|
|
17
|
+
*/
|
|
14
18
|
declare function exchangeToken({
|
|
15
19
|
$fetch,
|
|
16
20
|
options,
|
|
@@ -40,15 +44,8 @@ type StartAuthFlowArgs = {
|
|
|
40
44
|
onAuthenticated: (user: AuthUser) => void;
|
|
41
45
|
onError?: (error: unknown) => void;
|
|
42
46
|
};
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
$fetch,
|
|
46
|
-
clientOptions,
|
|
47
|
-
options,
|
|
48
|
-
cfg,
|
|
49
|
-
onAuthenticated,
|
|
50
|
-
onError
|
|
51
|
-
}: StartAuthFlowArgs): Promise<void>;
|
|
47
|
+
/** Opens the init-oauth-proxy URL in the system browser and awaits the loopback callback. */
|
|
48
|
+
declare function startAuthFlow(args: StartAuthFlowArgs): Promise<void>;
|
|
52
49
|
//#endregion
|
|
53
50
|
//#region src/core/user.d.ts
|
|
54
51
|
type FetchUserImageResult = {
|
package/dist/index.d.mts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { a as LoopbackRequest, c as RequestAuthOptions, i as DesktopClientOptions, l as Storage, n as AuthUser, o as LoopbackResponse, r as DesktopAdapter, s as LoopbackServer, t as AuthEvent } from "./types-
|
|
2
|
-
import { a as desktopClient, i as createDesktopCookieLayer, n as DesktopClientPluginOptions, r as DesktopCookieLayer, t as DesktopClientInternals } from "./client-
|
|
3
|
-
import { n as keychainStorage, t as KeychainStorageOptions } from "./storage-
|
|
4
|
-
import { a as StartAuthFlowArgs, i as ExchangeTokenArgs, n as fetchUserImage, o as exchangeToken, r as normalizeUserOutput, s as startAuthFlow, t as FetchUserImageResult } from "./index-
|
|
5
|
-
import { a as isAllowedLoopbackPort, i as generateNonce, n as LOOPBACK_HOST, o as parseLoopbackUrl, r as buildLoopbackUrl,
|
|
1
|
+
import { a as LoopbackRequest, c as RequestAuthOptions, i as DesktopClientOptions, l as Storage, n as AuthUser, o as LoopbackResponse, r as DesktopAdapter, s as LoopbackServer, t as AuthEvent } from "./types-oAdD2Qlz.mjs";
|
|
2
|
+
import { a as desktopClient, i as createDesktopCookieLayer, n as DesktopClientPluginOptions, r as DesktopCookieLayer, t as DesktopClientInternals } from "./client-DsuEHH54.mjs";
|
|
3
|
+
import { n as keychainStorage, t as KeychainStorageOptions } from "./storage-DbD61OuL.mjs";
|
|
4
|
+
import { a as StartAuthFlowArgs, i as ExchangeTokenArgs, n as fetchUserImage, o as exchangeToken, r as normalizeUserOutput, s as startAuthFlow, t as FetchUserImageResult } from "./index-xd3h5rM3.mjs";
|
|
5
|
+
import { a as isAllowedLoopbackPort, i as generateNonce, n as LOOPBACK_HOST, o as parseLoopbackUrl, r as buildLoopbackUrl, t as AllowedLoopbackPorts } from "./loopback-Dec4O7Rx.mjs";
|
|
6
6
|
|
|
7
7
|
//#region src/version.d.ts
|
|
8
8
|
declare const PACKAGE_VERSION: string;
|
|
9
9
|
//#endregion
|
|
10
|
-
export { AllowedLoopbackPorts, AuthEvent, AuthUser, DesktopAdapter, DesktopClientInternals, DesktopClientOptions, DesktopClientPluginOptions, DesktopCookieLayer, ExchangeTokenArgs, FetchUserImageResult, KeychainStorageOptions, LOOPBACK_HOST, LoopbackRequest, LoopbackResponse, LoopbackServer, PACKAGE_VERSION, RequestAuthOptions, StartAuthFlowArgs, Storage, buildLoopbackUrl, createDesktopCookieLayer, desktopClient, exchangeToken, fetchUserImage, generateNonce, isAllowedLoopbackPort, keychainStorage, normalizeUserOutput, parseLoopbackUrl, startAuthFlow
|
|
10
|
+
export { AllowedLoopbackPorts, AuthEvent, AuthUser, DesktopAdapter, DesktopClientInternals, DesktopClientOptions, DesktopClientPluginOptions, DesktopCookieLayer, ExchangeTokenArgs, FetchUserImageResult, KeychainStorageOptions, LOOPBACK_HOST, LoopbackRequest, LoopbackResponse, LoopbackServer, PACKAGE_VERSION, RequestAuthOptions, StartAuthFlowArgs, Storage, buildLoopbackUrl, createDesktopCookieLayer, desktopClient, exchangeToken, fetchUserImage, generateNonce, isAllowedLoopbackPort, keychainStorage, normalizeUserOutput, parseLoopbackUrl, startAuthFlow };
|
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { t as PACKAGE_VERSION } from "./version-
|
|
2
|
-
import { n as desktopClient, t as createDesktopCookieLayer } from "./client-
|
|
3
|
-
import { a as parseLoopbackUrl, i as isAllowedLoopbackPort, n as buildLoopbackUrl,
|
|
4
|
-
import {
|
|
5
|
-
import { t as keychainStorage } from "./storage-
|
|
1
|
+
import { t as PACKAGE_VERSION } from "./version-D6UEvd2R.mjs";
|
|
2
|
+
import { n as desktopClient, t as createDesktopCookieLayer } from "./client-CWgaf2aA.mjs";
|
|
3
|
+
import { a as parseLoopbackUrl, i as isAllowedLoopbackPort, n as buildLoopbackUrl, r as generateNonce, t as LOOPBACK_HOST } from "./loopback-CNOfW74-.mjs";
|
|
4
|
+
import { a as normalizeUserOutput, i as fetchUserImage, n as exchangeToken, r as startAuthFlow } from "./exchange-D93Uwk6_.mjs";
|
|
5
|
+
import { t as keychainStorage } from "./storage-DR69vdzj.mjs";
|
|
6
6
|
import "./core/index.mjs";
|
|
7
|
-
export { LOOPBACK_HOST, PACKAGE_VERSION, buildLoopbackUrl, createDesktopCookieLayer, desktopClient, exchangeToken, fetchUserImage, generateNonce, isAllowedLoopbackPort, keychainStorage, normalizeUserOutput, parseLoopbackUrl, startAuthFlow
|
|
7
|
+
export { LOOPBACK_HOST, PACKAGE_VERSION, buildLoopbackUrl, createDesktopCookieLayer, desktopClient, exchangeToken, fetchUserImage, generateNonce, isAllowedLoopbackPort, keychainStorage, normalizeUserOutput, parseLoopbackUrl, startAuthFlow };
|
|
@@ -10,6 +10,10 @@ function buildLoopbackUrl(port, path, nonce) {
|
|
|
10
10
|
url.searchParams.set("nonce", nonce);
|
|
11
11
|
return url.toString();
|
|
12
12
|
}
|
|
13
|
+
/**
|
|
14
|
+
* Returns `null` for anything other than a plain-http `127.0.0.1` URL so
|
|
15
|
+
* callers can safely reject untrusted redirect targets (never `localhost`, never https).
|
|
16
|
+
*/
|
|
13
17
|
function parseLoopbackUrl(raw, allowedPorts) {
|
|
14
18
|
let url;
|
|
15
19
|
try {
|
|
@@ -28,8 +32,5 @@ function isAllowedLoopbackPort(port, allowed) {
|
|
|
28
32
|
if (Array.isArray(allowed)) return allowed.includes(port);
|
|
29
33
|
return port >= allowed.min && port <= allowed.max;
|
|
30
34
|
}
|
|
31
|
-
function successPage(title = "Signed in", message = "You can close this tab and return to the app.") {
|
|
32
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>${title}</title></head><body style="font-family:system-ui,sans-serif;display:grid;place-items:center;min-height:100vh;margin:0;text-align:center"><main><h1 style="font-size:1.25rem;margin:0 0 .5rem">${title}</h1><p style="margin:0;opacity:.7">${message}</p></main></body></html>`;
|
|
33
|
-
}
|
|
34
35
|
//#endregion
|
|
35
|
-
export { parseLoopbackUrl as a, isAllowedLoopbackPort as i, buildLoopbackUrl as n,
|
|
36
|
+
export { parseLoopbackUrl as a, isAllowedLoopbackPort as i, buildLoopbackUrl as n, generateNonce as r, LOOPBACK_HOST as t };
|
|
@@ -6,8 +6,11 @@ type AllowedLoopbackPorts = number[] | {
|
|
|
6
6
|
};
|
|
7
7
|
declare function generateNonce(): string;
|
|
8
8
|
declare function buildLoopbackUrl(port: number, path: string, nonce: string): string;
|
|
9
|
+
/**
|
|
10
|
+
* Returns `null` for anything other than a plain-http `127.0.0.1` URL so
|
|
11
|
+
* callers can safely reject untrusted redirect targets (never `localhost`, never https).
|
|
12
|
+
*/
|
|
9
13
|
declare function parseLoopbackUrl(raw: string, allowedPorts?: AllowedLoopbackPorts): URL | null;
|
|
10
14
|
declare function isAllowedLoopbackPort(port: number, allowed: AllowedLoopbackPorts): boolean;
|
|
11
|
-
declare function successPage(title?: string, message?: string): string;
|
|
12
15
|
//#endregion
|
|
13
|
-
export { isAllowedLoopbackPort as a, generateNonce as i, LOOPBACK_HOST as n, parseLoopbackUrl as o, buildLoopbackUrl as r,
|
|
16
|
+
export { isAllowedLoopbackPort as a, generateNonce as i, LOOPBACK_HOST as n, parseLoopbackUrl as o, buildLoopbackUrl as r, AllowedLoopbackPorts as t };
|
package/dist/react.d.mts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { n as AuthUser } from "./types-oAdD2Qlz.mjs";
|
|
2
|
+
import { t as AuthBridges } from "./schema-CE7bJ4bb.mjs";
|
|
3
|
+
|
|
4
|
+
//#region src/react.d.ts
|
|
5
|
+
/** React hook that subscribes to the desktop bridge user session. Must be called from a component or another hook. */
|
|
6
|
+
declare function useSession<TUser extends AuthUser = AuthUser>(bridge: AuthBridges<TUser> | null): {
|
|
7
|
+
data: {
|
|
8
|
+
user: TUser;
|
|
9
|
+
} | null;
|
|
10
|
+
isPending: boolean;
|
|
11
|
+
error: unknown;
|
|
12
|
+
};
|
|
13
|
+
//#endregion
|
|
14
|
+
export { useSession };
|
package/dist/react.mjs
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
//#region src/react.ts
|
|
3
|
+
/** React hook that subscribes to the desktop bridge user session. Must be called from a component or another hook. */
|
|
4
|
+
function useSession(bridge) {
|
|
5
|
+
const [data, setData] = useState(null);
|
|
6
|
+
const [isPending, setIsPending] = useState(true);
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
setData(null);
|
|
9
|
+
setIsPending(true);
|
|
10
|
+
if (!bridge) {
|
|
11
|
+
setIsPending(false);
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
let settled = false;
|
|
15
|
+
const settle = (user) => {
|
|
16
|
+
if (!settled) {
|
|
17
|
+
settled = true;
|
|
18
|
+
setIsPending(false);
|
|
19
|
+
}
|
|
20
|
+
setData(user ? { user } : null);
|
|
21
|
+
};
|
|
22
|
+
return bridge.watchUser(settle);
|
|
23
|
+
}, [bridge]);
|
|
24
|
+
return {
|
|
25
|
+
data,
|
|
26
|
+
isPending,
|
|
27
|
+
error: null
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
//#endregion
|
|
31
|
+
export { useSession };
|
package/dist/rpc/webview.d.mts
CHANGED
|
@@ -1,26 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { User } from "@better-auth/core/db";
|
|
1
|
+
import { n as AuthUser } from "../types-oAdD2Qlz.mjs";
|
|
2
|
+
import { t as AuthBridges } from "../schema-CE7bJ4bb.mjs";
|
|
4
3
|
|
|
5
|
-
//#region src/rpc/schema.d.ts
|
|
6
|
-
type AuthBridges = {
|
|
7
|
-
getUser(): Promise<(User & Record<string, any>) | null>;
|
|
8
|
-
requestAuth(options: RequestAuthOptions): Promise<void>;
|
|
9
|
-
signOut(): Promise<void>;
|
|
10
|
-
getUserImage(url: string): Promise<{
|
|
11
|
-
dataUrl: string | null;
|
|
12
|
-
}>;
|
|
13
|
-
onAuthenticated(callback: (user: User & Record<string, any>) => void): () => void;
|
|
14
|
-
onUserUpdated(callback: (user: (User & Record<string, any>) | null) => void): () => void;
|
|
15
|
-
onAuthError(callback: (context: {
|
|
16
|
-
error: BetterFetchError | {
|
|
17
|
-
message: string;
|
|
18
|
-
};
|
|
19
|
-
path?: string;
|
|
20
|
-
}) => void): () => void;
|
|
21
|
-
};
|
|
22
|
-
//#endregion
|
|
23
4
|
//#region src/rpc/webview.d.ts
|
|
24
|
-
|
|
5
|
+
/** Initializes the Electrobun webview RPC and returns the desktop bridge. */
|
|
6
|
+
declare function defineAuthWebviewRPC<TUser extends AuthUser = AuthUser>(): AuthBridges<TUser>;
|
|
25
7
|
//#endregion
|
|
26
8
|
export { defineAuthWebviewRPC };
|
package/dist/rpc/webview.mjs
CHANGED
|
@@ -278,18 +278,7 @@ var Electroview = class {
|
|
|
278
278
|
};
|
|
279
279
|
//#endregion
|
|
280
280
|
//#region src/rpc/webview.ts
|
|
281
|
-
/**
|
|
282
|
-
* Renderer-side auth bridge for the Electrobun WebView. Call once at the top of
|
|
283
|
-
* your webview entry, then use the returned bridge:
|
|
284
|
-
*
|
|
285
|
-
* ```ts
|
|
286
|
-
* import { defineAuthWebviewRPC } from "@soorya-u/better-auth-desktop/rpc/webview";
|
|
287
|
-
* export const auth = defineAuthWebviewRPC();
|
|
288
|
-
*
|
|
289
|
-
* auth.onAuthenticated((user) => navigate("/threads"));
|
|
290
|
-
* await auth.requestAuth({ provider: "github" });
|
|
291
|
-
* ```
|
|
292
|
-
*/
|
|
281
|
+
/** Initializes the Electrobun webview RPC and returns the desktop bridge. */
|
|
293
282
|
function defineAuthWebviewRPC() {
|
|
294
283
|
const rpc = Electroview.defineRPC({
|
|
295
284
|
maxRequestTime: 3e4,
|
|
@@ -299,25 +288,70 @@ function defineAuthWebviewRPC() {
|
|
|
299
288
|
}
|
|
300
289
|
});
|
|
301
290
|
new Electroview({ rpc });
|
|
291
|
+
const teardowns = /* @__PURE__ */ new Set();
|
|
292
|
+
const track = (off) => {
|
|
293
|
+
teardowns.add(off);
|
|
294
|
+
return () => {
|
|
295
|
+
off();
|
|
296
|
+
teardowns.delete(off);
|
|
297
|
+
};
|
|
298
|
+
};
|
|
302
299
|
return {
|
|
303
300
|
getUser: () => rpc.request.getUser({}),
|
|
301
|
+
getSession: () => rpc.request.getUser({}).then((user) => ({
|
|
302
|
+
data: user ? { user } : null,
|
|
303
|
+
error: null
|
|
304
|
+
})).catch((error) => ({
|
|
305
|
+
data: null,
|
|
306
|
+
error
|
|
307
|
+
})),
|
|
308
|
+
watchUser: (callback) => {
|
|
309
|
+
let disposed = false;
|
|
310
|
+
let sawEvent = false;
|
|
311
|
+
const apply = (user) => {
|
|
312
|
+
sawEvent = true;
|
|
313
|
+
if (!disposed) callback(user);
|
|
314
|
+
};
|
|
315
|
+
const authHandler = (user) => apply(user);
|
|
316
|
+
const updateHandler = (user) => apply(user);
|
|
317
|
+
rpc.addMessageListener("onAuthenticated", authHandler);
|
|
318
|
+
rpc.addMessageListener("onUserUpdated", updateHandler);
|
|
319
|
+
rpc.request.getUser({}).then((user) => {
|
|
320
|
+
if (!disposed && !sawEvent) callback(user);
|
|
321
|
+
}).catch(() => {
|
|
322
|
+
if (!disposed && !sawEvent) callback(null);
|
|
323
|
+
});
|
|
324
|
+
const cleanup = () => {
|
|
325
|
+
disposed = true;
|
|
326
|
+
rpc.removeMessageListener("onAuthenticated", authHandler);
|
|
327
|
+
rpc.removeMessageListener("onUserUpdated", updateHandler);
|
|
328
|
+
teardowns.delete(cleanup);
|
|
329
|
+
};
|
|
330
|
+
teardowns.add(cleanup);
|
|
331
|
+
return cleanup;
|
|
332
|
+
},
|
|
304
333
|
requestAuth: (options) => rpc.request.requestAuth({ options }),
|
|
334
|
+
getAuthUrl: (options) => rpc.request.getAuthUrl({ options }),
|
|
305
335
|
signOut: () => rpc.request.signOut({}),
|
|
306
336
|
getUserImage: (url) => rpc.request.getUserImage({ url }),
|
|
307
337
|
onAuthenticated: (callback) => {
|
|
308
338
|
const handler = (user) => callback(user);
|
|
309
339
|
rpc.addMessageListener("onAuthenticated", handler);
|
|
310
|
-
return () => rpc.removeMessageListener("onAuthenticated", handler);
|
|
340
|
+
return track(() => rpc.removeMessageListener("onAuthenticated", handler));
|
|
311
341
|
},
|
|
312
342
|
onUserUpdated: (callback) => {
|
|
313
343
|
const handler = (user) => callback(user);
|
|
314
344
|
rpc.addMessageListener("onUserUpdated", handler);
|
|
315
|
-
return () => rpc.removeMessageListener("onUserUpdated", handler);
|
|
345
|
+
return track(() => rpc.removeMessageListener("onUserUpdated", handler));
|
|
316
346
|
},
|
|
317
347
|
onAuthError: (callback) => {
|
|
318
348
|
const handler = (ctx) => callback(ctx);
|
|
319
349
|
rpc.addMessageListener("onAuthError", handler);
|
|
320
|
-
return () => rpc.removeMessageListener("onAuthError", handler);
|
|
350
|
+
return track(() => rpc.removeMessageListener("onAuthError", handler));
|
|
351
|
+
},
|
|
352
|
+
destroy: () => {
|
|
353
|
+
for (const off of teardowns) off();
|
|
354
|
+
teardowns.clear();
|
|
321
355
|
}
|
|
322
356
|
};
|
|
323
357
|
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { c as RequestAuthOptions, n as AuthUser } from "./types-oAdD2Qlz.mjs";
|
|
2
|
+
import { BetterFetchError } from "@better-fetch/fetch";
|
|
3
|
+
import { User } from "@better-auth/core/db";
|
|
4
|
+
|
|
5
|
+
//#region src/rpc/schema.d.ts
|
|
6
|
+
type SessionResult<TUser> = {
|
|
7
|
+
data: {
|
|
8
|
+
user: TUser;
|
|
9
|
+
} | null;
|
|
10
|
+
error: unknown;
|
|
11
|
+
};
|
|
12
|
+
type AuthBridges<TUser extends AuthUser = AuthUser> = {
|
|
13
|
+
getUser(): Promise<TUser | null>;
|
|
14
|
+
getSession(): Promise<SessionResult<TUser>>;
|
|
15
|
+
watchUser(callback: (user: TUser | null) => void): () => void;
|
|
16
|
+
requestAuth(options: RequestAuthOptions): Promise<void>;
|
|
17
|
+
getAuthUrl(options: RequestAuthOptions): Promise<string>;
|
|
18
|
+
signOut(): Promise<void>;
|
|
19
|
+
getUserImage(url: string): Promise<{
|
|
20
|
+
dataUrl: string | null;
|
|
21
|
+
}>;
|
|
22
|
+
onAuthenticated(callback: (user: TUser) => void): () => void;
|
|
23
|
+
onUserUpdated(callback: (user: TUser | null) => void): () => void;
|
|
24
|
+
onAuthError(callback: (context: {
|
|
25
|
+
error: BetterFetchError | {
|
|
26
|
+
message: string;
|
|
27
|
+
};
|
|
28
|
+
path?: string;
|
|
29
|
+
}) => void): () => void;
|
|
30
|
+
destroy(): void;
|
|
31
|
+
};
|
|
32
|
+
//#endregion
|
|
33
|
+
export { AuthBridges as t };
|
package/dist/server/index.d.mts
CHANGED
package/dist/server/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { t as PACKAGE_VERSION } from "../version-
|
|
2
|
-
import { a as parseLoopbackUrl } from "../loopback-
|
|
1
|
+
import { t as PACKAGE_VERSION } from "../version-D6UEvd2R.mjs";
|
|
2
|
+
import { a as parseLoopbackUrl } from "../loopback-CNOfW74-.mjs";
|
|
3
3
|
import { setSessionCookie } from "better-auth/cookies";
|
|
4
4
|
import { APIError, BASE_ERROR_CODES } from "@better-auth/core/error";
|
|
5
5
|
import { base64Url } from "@better-auth/utils/base64";
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { l as Storage } from "./types-oAdD2Qlz.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/core/storage.d.ts
|
|
4
|
+
type KeychainStorageOptions = {
|
|
5
|
+
service?: string | undefined;
|
|
6
|
+
account?: string | undefined; /** Called when a keychain write fails. Use this to surface the error to the user or log it. */
|
|
7
|
+
onError?: (error: unknown) => void;
|
|
8
|
+
};
|
|
9
|
+
declare function keychainStorage(opts?: KeychainStorageOptions): Promise<Storage>;
|
|
10
|
+
//#endregion
|
|
11
|
+
export { keychainStorage as n, KeychainStorageOptions as t };
|
|
@@ -62,14 +62,10 @@ type DesktopClientOptions = {
|
|
|
62
62
|
cookiePrefix?: string | string[];
|
|
63
63
|
disableCache?: boolean;
|
|
64
64
|
sanitizeUser?: (user: AuthUser) => Awaitable<AuthUser>;
|
|
65
|
-
/**
|
|
66
|
-
* What the loopback shows after a successful exchange. A string is used as the
|
|
67
|
-
* HTML body; an object can redirect the browser to your own page instead.
|
|
68
|
-
* Defaults to a built-in "you can close this tab" page.
|
|
69
|
-
*/
|
|
70
65
|
loopbackSuccess?: string | {
|
|
71
66
|
redirectTo: string;
|
|
72
|
-
};
|
|
67
|
+
}; /** Called when a keychain/storage write fails. Use this to surface the error to the user or log it. */
|
|
68
|
+
onStorageError?: (error: unknown) => void;
|
|
73
69
|
};
|
|
74
70
|
//#endregion
|
|
75
71
|
export { LoopbackRequest as a, RequestAuthOptions as c, DesktopClientOptions as i, Storage as l, AuthUser as n, LoopbackResponse as o, DesktopAdapter as r, LoopbackServer as s, AuthEvent as t };
|
package/dist/web/index.d.mts
CHANGED
|
@@ -1,32 +1,50 @@
|
|
|
1
|
-
|
|
1
|
+
import { n as AuthUser } from "../types-oAdD2Qlz.mjs";
|
|
2
|
+
import { t as AuthBridges } from "../schema-CE7bJ4bb.mjs";
|
|
3
|
+
|
|
4
|
+
//#region src/web/forward.d.ts
|
|
2
5
|
type ForwardToDesktopOptions = {
|
|
3
|
-
|
|
6
|
+
hashKey?: string;
|
|
7
|
+
};
|
|
8
|
+
type ForwardToDesktopResult = {
|
|
9
|
+
success: true;
|
|
10
|
+
error: null;
|
|
11
|
+
} | {
|
|
12
|
+
success: false;
|
|
13
|
+
error: Error;
|
|
4
14
|
};
|
|
5
15
|
/**
|
|
6
|
-
*
|
|
7
|
-
* `
|
|
8
|
-
*
|
|
9
|
-
* (`http://127.0.0.1:<port>/…?token=…`) — no CORS / PNA / mixed-content.
|
|
10
|
-
*
|
|
11
|
-
* Returns `false` (a no-op) outside a browser or when the fragment is absent,
|
|
12
|
-
* so it is safe to call unconditionally on the callback page.
|
|
13
|
-
*/
|
|
14
|
-
declare function forwardToDesktop(options?: ForwardToDesktopOptions): boolean;
|
|
15
|
-
/**
|
|
16
|
-
* Better Auth client plugin wrapping {@link forwardToDesktop}, for consumers
|
|
17
|
-
* that prefer the `createAuthClient({ plugins: [...] })` pattern:
|
|
18
|
-
*
|
|
19
|
-
* ```ts
|
|
20
|
-
* const authClient = createAuthClient({ plugins: [webDesktop()] });
|
|
21
|
-
* authClient.forwardToDesktop();
|
|
22
|
-
* ```
|
|
16
|
+
* Reads `#token=…&loopback=…` from the URL fragment and navigates to the
|
|
17
|
+
* desktop loopback URL. Returns `success: false` outside a browser or when
|
|
18
|
+
* the fragment is absent.
|
|
23
19
|
*/
|
|
20
|
+
declare function forwardToDesktop(options?: ForwardToDesktopOptions): ForwardToDesktopResult;
|
|
21
|
+
/** Better-auth client plugin that exposes {@link forwardToDesktop} as a client action. */
|
|
24
22
|
declare const webDesktop: (options?: ForwardToDesktopOptions) => {
|
|
25
23
|
id: "desktop-web";
|
|
26
24
|
version: string;
|
|
27
25
|
getActions: () => {
|
|
28
|
-
forwardToDesktop: () =>
|
|
26
|
+
forwardToDesktop: () => ForwardToDesktopResult;
|
|
29
27
|
};
|
|
30
28
|
};
|
|
31
29
|
//#endregion
|
|
32
|
-
|
|
30
|
+
//#region src/web/wrap.d.ts
|
|
31
|
+
/**
|
|
32
|
+
* Session hook compatible with {@link wrapForDesktop}'s third parameter.
|
|
33
|
+
* Use this to type a custom framework hook (Vue, Svelte, etc.) before passing it in.
|
|
34
|
+
*/
|
|
35
|
+
type UseSessionFn<TUser extends AuthUser = AuthUser> = (bridge: AuthBridges<TUser> | null) => {
|
|
36
|
+
data: {
|
|
37
|
+
user: TUser;
|
|
38
|
+
} | null;
|
|
39
|
+
isPending: boolean;
|
|
40
|
+
error: unknown;
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Proxy-wraps a better-auth client so that `getSession`, `signOut`, and
|
|
44
|
+
* `signIn.social` are delegated to the desktop bridge. Pass an optional
|
|
45
|
+
* `useSessionFn` to also intercept `useSession`. Returns `base` unchanged
|
|
46
|
+
* when `bridge` is `null`.
|
|
47
|
+
*/
|
|
48
|
+
declare function wrapForDesktop<T extends object, TUser extends AuthUser = AuthUser>(base: T, bridge: AuthBridges<TUser> | null, useSessionFn?: UseSessionFn<TUser>): T;
|
|
49
|
+
//#endregion
|
|
50
|
+
export { type ForwardToDesktopOptions, type UseSessionFn, forwardToDesktop, webDesktop, wrapForDesktop };
|
package/dist/web/index.mjs
CHANGED
|
@@ -1,41 +1,76 @@
|
|
|
1
|
-
import { t as PACKAGE_VERSION } from "../version-
|
|
2
|
-
import { a as parseLoopbackUrl } from "../loopback-
|
|
3
|
-
//#region src/web/forward
|
|
1
|
+
import { t as PACKAGE_VERSION } from "../version-D6UEvd2R.mjs";
|
|
2
|
+
import { a as parseLoopbackUrl } from "../loopback-CNOfW74-.mjs";
|
|
3
|
+
//#region src/web/forward.ts
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
6
|
-
* `
|
|
7
|
-
*
|
|
8
|
-
* (`http://127.0.0.1:<port>/…?token=…`) — no CORS / PNA / mixed-content.
|
|
9
|
-
*
|
|
10
|
-
* Returns `false` (a no-op) outside a browser or when the fragment is absent,
|
|
11
|
-
* so it is safe to call unconditionally on the callback page.
|
|
5
|
+
* Reads `#token=…&loopback=…` from the URL fragment and navigates to the
|
|
6
|
+
* desktop loopback URL. Returns `success: false` outside a browser or when
|
|
7
|
+
* the fragment is absent.
|
|
12
8
|
*/
|
|
13
9
|
function forwardToDesktop(options) {
|
|
14
|
-
if (typeof window === "undefined" || !window) return
|
|
10
|
+
if (typeof window === "undefined" || !window) return {
|
|
11
|
+
success: false,
|
|
12
|
+
error: /* @__PURE__ */ new Error("Not running in a browser")
|
|
13
|
+
};
|
|
15
14
|
const hashKey = options?.hashKey ?? "token";
|
|
16
15
|
const params = new URLSearchParams(window.location.hash.replace(/^#/, ""));
|
|
17
16
|
const token = params.get(hashKey);
|
|
18
17
|
const loopback = params.get("loopback");
|
|
19
|
-
if (!token || !loopback) return
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
if (!token || !loopback) return {
|
|
19
|
+
success: false,
|
|
20
|
+
error: /* @__PURE__ */ new Error("Missing token or loopback in URL fragment")
|
|
21
|
+
};
|
|
22
|
+
const target = parseLoopbackUrl(loopback);
|
|
23
|
+
if (!target) return {
|
|
24
|
+
success: false,
|
|
25
|
+
error: /* @__PURE__ */ new Error("Invalid loopback URL")
|
|
26
|
+
};
|
|
22
27
|
target.searchParams.set(hashKey, token);
|
|
23
28
|
window.location.replace(target.toString());
|
|
24
|
-
return
|
|
29
|
+
return {
|
|
30
|
+
success: true,
|
|
31
|
+
error: null
|
|
32
|
+
};
|
|
25
33
|
}
|
|
26
|
-
/**
|
|
27
|
-
* Better Auth client plugin wrapping {@link forwardToDesktop}, for consumers
|
|
28
|
-
* that prefer the `createAuthClient({ plugins: [...] })` pattern:
|
|
29
|
-
*
|
|
30
|
-
* ```ts
|
|
31
|
-
* const authClient = createAuthClient({ plugins: [webDesktop()] });
|
|
32
|
-
* authClient.forwardToDesktop();
|
|
33
|
-
* ```
|
|
34
|
-
*/
|
|
34
|
+
/** Better-auth client plugin that exposes {@link forwardToDesktop} as a client action. */
|
|
35
35
|
const webDesktop = (options) => ({
|
|
36
36
|
id: "desktop-web",
|
|
37
37
|
version: PACKAGE_VERSION,
|
|
38
38
|
getActions: () => ({ forwardToDesktop: () => forwardToDesktop(options) })
|
|
39
39
|
});
|
|
40
40
|
//#endregion
|
|
41
|
-
|
|
41
|
+
//#region src/web/wrap.ts
|
|
42
|
+
/**
|
|
43
|
+
* Proxy-wraps a better-auth client so that `getSession`, `signOut`, and
|
|
44
|
+
* `signIn.social` are delegated to the desktop bridge. Pass an optional
|
|
45
|
+
* `useSessionFn` to also intercept `useSession`. Returns `base` unchanged
|
|
46
|
+
* when `bridge` is `null`.
|
|
47
|
+
*/
|
|
48
|
+
function wrapForDesktop(base, bridge, useSessionFn) {
|
|
49
|
+
if (!bridge) return base;
|
|
50
|
+
return new Proxy(base, { get(target, prop, receiver) {
|
|
51
|
+
if (prop === "useSession") {
|
|
52
|
+
if (useSessionFn) return () => useSessionFn(bridge);
|
|
53
|
+
return Reflect.get(target, prop, receiver);
|
|
54
|
+
}
|
|
55
|
+
if (prop === "getSession") return () => bridge.getSession();
|
|
56
|
+
if (prop === "signOut") return () => bridge.signOut();
|
|
57
|
+
if (prop !== "signIn") return Reflect.get(target, prop, receiver);
|
|
58
|
+
const signIn = Reflect.get(target, prop, receiver);
|
|
59
|
+
return new Proxy(signIn, { get(signInTarget, signInProp, signInReceiver) {
|
|
60
|
+
if (signInProp !== "social") return Reflect.get(signInTarget, signInProp, signInReceiver);
|
|
61
|
+
return (params) => {
|
|
62
|
+
const { disableRedirect, ...rest } = params;
|
|
63
|
+
if (disableRedirect) return bridge.getAuthUrl(rest).then((url) => ({
|
|
64
|
+
data: {
|
|
65
|
+
url,
|
|
66
|
+
redirect: false
|
|
67
|
+
},
|
|
68
|
+
error: null
|
|
69
|
+
}));
|
|
70
|
+
return bridge.requestAuth(rest);
|
|
71
|
+
};
|
|
72
|
+
} });
|
|
73
|
+
} });
|
|
74
|
+
}
|
|
75
|
+
//#endregion
|
|
76
|
+
export { forwardToDesktop, webDesktop, wrapForDesktop };
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@soorya-u/better-auth-desktop",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Loopback-based desktop auth for Better Auth. Framework-agnostic server + core with thin Electrobun and Electron adapters; OAuth hand-off via a 127.0.0.1 loopback navigation (no custom URL scheme).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
|
-
"homepage": "https://github.com/soorya-u/better-auth-
|
|
7
|
+
"homepage": "https://github.com/soorya-u/better-auth-desktop",
|
|
8
8
|
"repository": {
|
|
9
9
|
"type": "git",
|
|
10
|
-
"url": "git+https://github.com/soorya-u/better-auth-
|
|
10
|
+
"url": "git+https://github.com/soorya-u/better-auth-desktop.git"
|
|
11
11
|
},
|
|
12
12
|
"keywords": [
|
|
13
13
|
"electrobun",
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
},
|
|
24
24
|
"sideEffects": false,
|
|
25
25
|
"scripts": {
|
|
26
|
+
"prepare": "lefthook install",
|
|
26
27
|
"build": "tsdown",
|
|
27
28
|
"dev": "tsdown --watch",
|
|
28
29
|
"lint:package": "publint run --strict --pack false",
|
|
@@ -80,6 +81,11 @@
|
|
|
80
81
|
"dev-source": "./src/rpc/webview.ts",
|
|
81
82
|
"types": "./dist/rpc/webview.d.mts",
|
|
82
83
|
"default": "./dist/rpc/webview.mjs"
|
|
84
|
+
},
|
|
85
|
+
"./react": {
|
|
86
|
+
"dev-source": "./src/react.ts",
|
|
87
|
+
"types": "./dist/react.d.mts",
|
|
88
|
+
"default": "./dist/react.mjs"
|
|
83
89
|
}
|
|
84
90
|
},
|
|
85
91
|
"typesVersions": {
|
|
@@ -107,6 +113,9 @@
|
|
|
107
113
|
],
|
|
108
114
|
"rpc/webview": [
|
|
109
115
|
"./dist/rpc/webview.d.mts"
|
|
116
|
+
],
|
|
117
|
+
"react": [
|
|
118
|
+
"./dist/react.d.mts"
|
|
110
119
|
]
|
|
111
120
|
}
|
|
112
121
|
},
|
|
@@ -114,13 +123,16 @@
|
|
|
114
123
|
"zod": "^4.3.6"
|
|
115
124
|
},
|
|
116
125
|
"devDependencies": {
|
|
126
|
+
"@arethetypeswrong/cli": "^0.18.4",
|
|
117
127
|
"@biomejs/biome": "^2.5.1",
|
|
118
128
|
"@types/better-sqlite3": "^7.6.13",
|
|
119
129
|
"@types/bun": "latest",
|
|
130
|
+
"@types/react": "^19.2.17",
|
|
120
131
|
"better-auth": "^1.6.21",
|
|
121
132
|
"better-call": "1.3.7",
|
|
122
133
|
"better-sqlite3": "^12.11.1",
|
|
123
134
|
"electrobun": "^1.18.1",
|
|
135
|
+
"lefthook": "^2.1.9",
|
|
124
136
|
"publint": "^0.3.12",
|
|
125
137
|
"tsdown": "^0.21.1",
|
|
126
138
|
"typescript": "^5.9.3",
|
|
@@ -128,6 +140,7 @@
|
|
|
128
140
|
},
|
|
129
141
|
"peerDependencies": {
|
|
130
142
|
"@better-auth/core": "^1.6.21",
|
|
143
|
+
"react": ">=18.0.0",
|
|
131
144
|
"@better-auth/utils": "^0.4.2",
|
|
132
145
|
"@better-fetch/fetch": "^1.3.1",
|
|
133
146
|
"better-auth": "^1.6.21",
|
|
@@ -145,6 +158,9 @@
|
|
|
145
158
|
},
|
|
146
159
|
"electron-store": {
|
|
147
160
|
"optional": true
|
|
161
|
+
},
|
|
162
|
+
"react": {
|
|
163
|
+
"optional": true
|
|
148
164
|
}
|
|
149
165
|
},
|
|
150
166
|
"engines": {
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { l as Storage } from "./types-h9AEfSnq.mjs";
|
|
2
|
-
|
|
3
|
-
//#region src/core/storage.d.ts
|
|
4
|
-
type KeychainStorageOptions = {
|
|
5
|
-
service?: string | undefined;
|
|
6
|
-
account?: string | undefined;
|
|
7
|
-
};
|
|
8
|
-
declare function keychainStorage(opts?: KeychainStorageOptions): Promise<Storage>;
|
|
9
|
-
//#endregion
|
|
10
|
-
export { keychainStorage as n, KeychainStorageOptions as t };
|