@soorya-u/better-auth-desktop 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/README.md +257 -0
- package/dist/adapters/electrobun.d.mts +49 -0
- package/dist/adapters/electrobun.mjs +166 -0
- package/dist/adapters/electron.d.mts +55 -0
- package/dist/adapters/electron.mjs +205 -0
- package/dist/client-De_zQzE1.d.mts +34 -0
- package/dist/client-DhRcoap2.mjs +157 -0
- package/dist/client.d.mts +3 -0
- package/dist/client.mjs +2 -0
- package/dist/core/index.d.mts +6 -0
- package/dist/core/index.mjs +5 -0
- package/dist/exchange-DCZ0j_T5.mjs +217 -0
- package/dist/index-CP_2Wlut.d.mts +61 -0
- package/dist/index.d.mts +10 -0
- package/dist/index.mjs +7 -0
- package/dist/loopback-ClkAoS2u.d.mts +13 -0
- package/dist/loopback-DTQgg1Za.mjs +35 -0
- package/dist/rpc/webview.d.mts +26 -0
- package/dist/rpc/webview.mjs +325 -0
- package/dist/server/index.d.mts +60 -0
- package/dist/server/index.mjs +213 -0
- package/dist/storage-BcwDmS-W.mjs +30 -0
- package/dist/storage-DtgX_vmE.d.mts +10 -0
- package/dist/types-h9AEfSnq.d.mts +75 -0
- package/dist/version-1hEssvcU.mjs +5 -0
- package/dist/web/index.d.mts +32 -0
- package/dist/web/index.mjs +41 -0
- package/package.json +153 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { c as RequestAuthOptions, i as DesktopClientOptions, n as AuthUser, r as DesktopAdapter } from "./types-h9AEfSnq.mjs";
|
|
2
|
+
import { BetterFetch, CreateFetchOption } from "@better-fetch/fetch";
|
|
3
|
+
import { BetterAuthClientOptions } from "@better-auth/core";
|
|
4
|
+
import { User } from "@better-auth/core/db";
|
|
5
|
+
|
|
6
|
+
//#region src/core/exchange.d.ts
|
|
7
|
+
type ExchangeTokenArgs = {
|
|
8
|
+
$fetch: BetterFetch;
|
|
9
|
+
options: DesktopClientOptions;
|
|
10
|
+
token: string;
|
|
11
|
+
onAuthenticated: (user: AuthUser) => void;
|
|
12
|
+
fetchOptions?: Omit<CreateFetchOption, "method"> | undefined;
|
|
13
|
+
};
|
|
14
|
+
declare function exchangeToken({
|
|
15
|
+
$fetch,
|
|
16
|
+
options,
|
|
17
|
+
token,
|
|
18
|
+
onAuthenticated,
|
|
19
|
+
fetchOptions
|
|
20
|
+
}: ExchangeTokenArgs): Promise<{
|
|
21
|
+
data: {
|
|
22
|
+
token: string;
|
|
23
|
+
user: AuthUser;
|
|
24
|
+
};
|
|
25
|
+
error: null;
|
|
26
|
+
} | {
|
|
27
|
+
data: null;
|
|
28
|
+
error: {
|
|
29
|
+
message?: string | undefined;
|
|
30
|
+
status: number;
|
|
31
|
+
statusText: string;
|
|
32
|
+
};
|
|
33
|
+
}>;
|
|
34
|
+
type StartAuthFlowArgs = {
|
|
35
|
+
adapter: DesktopAdapter;
|
|
36
|
+
$fetch: BetterFetch;
|
|
37
|
+
clientOptions: BetterAuthClientOptions | undefined;
|
|
38
|
+
options: DesktopClientOptions;
|
|
39
|
+
cfg: RequestAuthOptions;
|
|
40
|
+
onAuthenticated: (user: AuthUser) => void;
|
|
41
|
+
onError?: (error: unknown) => void;
|
|
42
|
+
};
|
|
43
|
+
declare function startAuthFlow({
|
|
44
|
+
adapter,
|
|
45
|
+
$fetch,
|
|
46
|
+
clientOptions,
|
|
47
|
+
options,
|
|
48
|
+
cfg,
|
|
49
|
+
onAuthenticated,
|
|
50
|
+
onError
|
|
51
|
+
}: StartAuthFlowArgs): Promise<void>;
|
|
52
|
+
//#endregion
|
|
53
|
+
//#region src/core/user.d.ts
|
|
54
|
+
type FetchUserImageResult = {
|
|
55
|
+
bytes: Uint8Array;
|
|
56
|
+
mimeType: string;
|
|
57
|
+
};
|
|
58
|
+
declare function fetchUserImage(baseURL: string | undefined, url: string): Promise<FetchUserImageResult | null>;
|
|
59
|
+
declare function normalizeUserOutput<U extends User & Record<string, any>>(user: U, _options?: DesktopClientOptions | undefined): U;
|
|
60
|
+
//#endregion
|
|
61
|
+
export { StartAuthFlowArgs as a, ExchangeTokenArgs as i, fetchUserImage as n, exchangeToken as o, normalizeUserOutput as r, startAuthFlow as s, FetchUserImageResult as t };
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +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-h9AEfSnq.mjs";
|
|
2
|
+
import { a as desktopClient, i as createDesktopCookieLayer, n as DesktopClientPluginOptions, r as DesktopCookieLayer, t as DesktopClientInternals } from "./client-De_zQzE1.mjs";
|
|
3
|
+
import { n as keychainStorage, t as KeychainStorageOptions } from "./storage-DtgX_vmE.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-CP_2Wlut.mjs";
|
|
5
|
+
import { a as isAllowedLoopbackPort, i as generateNonce, n as LOOPBACK_HOST, o as parseLoopbackUrl, r as buildLoopbackUrl, s as successPage, t as AllowedLoopbackPorts } from "./loopback-ClkAoS2u.mjs";
|
|
6
|
+
|
|
7
|
+
//#region src/version.d.ts
|
|
8
|
+
declare const PACKAGE_VERSION: string;
|
|
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, successPage };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { t as PACKAGE_VERSION } from "./version-1hEssvcU.mjs";
|
|
2
|
+
import { n as desktopClient, t as createDesktopCookieLayer } from "./client-DhRcoap2.mjs";
|
|
3
|
+
import { a as parseLoopbackUrl, i as isAllowedLoopbackPort, n as buildLoopbackUrl, o as successPage, r as generateNonce, t as LOOPBACK_HOST } from "./loopback-DTQgg1Za.mjs";
|
|
4
|
+
import { i as normalizeUserOutput, n as startAuthFlow, r as fetchUserImage, t as exchangeToken } from "./exchange-DCZ0j_T5.mjs";
|
|
5
|
+
import { t as keychainStorage } from "./storage-BcwDmS-W.mjs";
|
|
6
|
+
import "./core/index.mjs";
|
|
7
|
+
export { LOOPBACK_HOST, PACKAGE_VERSION, buildLoopbackUrl, createDesktopCookieLayer, desktopClient, exchangeToken, fetchUserImage, generateNonce, isAllowedLoopbackPort, keychainStorage, normalizeUserOutput, parseLoopbackUrl, startAuthFlow, successPage };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
//#region src/core/loopback.d.ts
|
|
2
|
+
declare const LOOPBACK_HOST = "127.0.0.1";
|
|
3
|
+
type AllowedLoopbackPorts = number[] | {
|
|
4
|
+
min: number;
|
|
5
|
+
max: number;
|
|
6
|
+
};
|
|
7
|
+
declare function generateNonce(): string;
|
|
8
|
+
declare function buildLoopbackUrl(port: number, path: string, nonce: string): string;
|
|
9
|
+
declare function parseLoopbackUrl(raw: string, allowedPorts?: AllowedLoopbackPorts): URL | null;
|
|
10
|
+
declare function isAllowedLoopbackPort(port: number, allowed: AllowedLoopbackPorts): boolean;
|
|
11
|
+
declare function successPage(title?: string, message?: string): string;
|
|
12
|
+
//#endregion
|
|
13
|
+
export { isAllowedLoopbackPort as a, generateNonce as i, LOOPBACK_HOST as n, parseLoopbackUrl as o, buildLoopbackUrl as r, successPage as s, AllowedLoopbackPorts as t };
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { generateRandomString } from "better-auth/crypto";
|
|
2
|
+
//#region src/core/loopback.ts
|
|
3
|
+
const LOOPBACK_HOST = "127.0.0.1";
|
|
4
|
+
function generateNonce() {
|
|
5
|
+
return generateRandomString(24, "a-z", "A-Z", "0-9");
|
|
6
|
+
}
|
|
7
|
+
function buildLoopbackUrl(port, path, nonce) {
|
|
8
|
+
const url = new URL(`http://${LOOPBACK_HOST}:${port}`);
|
|
9
|
+
url.pathname = path.startsWith("/") ? path : `/${path}`;
|
|
10
|
+
url.searchParams.set("nonce", nonce);
|
|
11
|
+
return url.toString();
|
|
12
|
+
}
|
|
13
|
+
function parseLoopbackUrl(raw, allowedPorts) {
|
|
14
|
+
let url;
|
|
15
|
+
try {
|
|
16
|
+
url = new URL(raw);
|
|
17
|
+
} catch {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
if (url.protocol !== "http:") return null;
|
|
21
|
+
if (url.hostname !== "127.0.0.1") return null;
|
|
22
|
+
const port = Number(url.port);
|
|
23
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) return null;
|
|
24
|
+
if (allowedPorts && !isAllowedLoopbackPort(port, allowedPorts)) return null;
|
|
25
|
+
return url;
|
|
26
|
+
}
|
|
27
|
+
function isAllowedLoopbackPort(port, allowed) {
|
|
28
|
+
if (Array.isArray(allowed)) return allowed.includes(port);
|
|
29
|
+
return port >= allowed.min && port <= allowed.max;
|
|
30
|
+
}
|
|
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
|
+
//#endregion
|
|
35
|
+
export { parseLoopbackUrl as a, isAllowedLoopbackPort as i, buildLoopbackUrl as n, successPage as o, generateNonce as r, LOOPBACK_HOST as t };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { c as RequestAuthOptions } from "../types-h9AEfSnq.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 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
|
+
//#region src/rpc/webview.d.ts
|
|
24
|
+
declare function defineAuthWebviewRPC(): AuthBridges;
|
|
25
|
+
//#endregion
|
|
26
|
+
export { defineAuthWebviewRPC };
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
//#region node_modules/electrobun/dist/api/shared/rpc.ts
|
|
2
|
+
const MAX_ID = 1e10;
|
|
3
|
+
const DEFAULT_MAX_REQUEST_TIME = 1e3;
|
|
4
|
+
function missingTransportMethodError(methods, action) {
|
|
5
|
+
const methodsString = methods.map((m) => `"${m}"`).join(", ");
|
|
6
|
+
return /* @__PURE__ */ new Error(`This RPC instance cannot ${action} because the transport did not provide one or more of these methods: ${methodsString}`);
|
|
7
|
+
}
|
|
8
|
+
function createRPC(options = {}) {
|
|
9
|
+
let debugHooks = {};
|
|
10
|
+
let transport = {};
|
|
11
|
+
let requestHandler = void 0;
|
|
12
|
+
function setTransport(newTransport) {
|
|
13
|
+
if (transport.unregisterHandler) transport.unregisterHandler();
|
|
14
|
+
transport = newTransport;
|
|
15
|
+
transport.registerHandler?.(handler);
|
|
16
|
+
}
|
|
17
|
+
function setRequestHandler(h) {
|
|
18
|
+
if (typeof h === "function") {
|
|
19
|
+
requestHandler = h;
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
requestHandler = (method, params) => {
|
|
23
|
+
const handlerFn = h[method];
|
|
24
|
+
if (handlerFn) return handlerFn(params);
|
|
25
|
+
const fallbackHandler = h._;
|
|
26
|
+
if (!fallbackHandler) throw new Error(`The requested method has no handler: ${String(method)}`);
|
|
27
|
+
return fallbackHandler(method, params);
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
const { maxRequestTime = DEFAULT_MAX_REQUEST_TIME } = options;
|
|
31
|
+
if (options.transport) setTransport(options.transport);
|
|
32
|
+
if (options.requestHandler) setRequestHandler(options.requestHandler);
|
|
33
|
+
if (options._debugHooks) debugHooks = options._debugHooks;
|
|
34
|
+
let lastRequestId = 0;
|
|
35
|
+
function getRequestId() {
|
|
36
|
+
if (lastRequestId <= MAX_ID) return ++lastRequestId;
|
|
37
|
+
return lastRequestId = 0;
|
|
38
|
+
}
|
|
39
|
+
const requestListeners = /* @__PURE__ */ new Map();
|
|
40
|
+
const requestTimeouts = /* @__PURE__ */ new Map();
|
|
41
|
+
function requestFn(method, ...args) {
|
|
42
|
+
const params = args[0];
|
|
43
|
+
return new Promise((resolve, reject) => {
|
|
44
|
+
if (!transport.send) throw missingTransportMethodError(["send"], "make requests");
|
|
45
|
+
const requestId = getRequestId();
|
|
46
|
+
const request = {
|
|
47
|
+
type: "request",
|
|
48
|
+
id: requestId,
|
|
49
|
+
method,
|
|
50
|
+
params
|
|
51
|
+
};
|
|
52
|
+
requestListeners.set(requestId, {
|
|
53
|
+
resolve,
|
|
54
|
+
reject
|
|
55
|
+
});
|
|
56
|
+
if (maxRequestTime !== Infinity) requestTimeouts.set(requestId, setTimeout(() => {
|
|
57
|
+
requestTimeouts.delete(requestId);
|
|
58
|
+
requestListeners.delete(requestId);
|
|
59
|
+
reject(/* @__PURE__ */ new Error("RPC request timed out."));
|
|
60
|
+
}, maxRequestTime));
|
|
61
|
+
debugHooks.onSend?.(request);
|
|
62
|
+
transport.send(request);
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
const request = new Proxy(requestFn, { get: (target, prop, receiver) => {
|
|
66
|
+
if (prop in target) return Reflect.get(target, prop, receiver);
|
|
67
|
+
return (params) => requestFn(prop, params);
|
|
68
|
+
} });
|
|
69
|
+
const requestProxy = request;
|
|
70
|
+
function sendFn(message, ...args) {
|
|
71
|
+
const payload = args[0];
|
|
72
|
+
if (!transport.send) throw missingTransportMethodError(["send"], "send messages");
|
|
73
|
+
const rpcMessage = {
|
|
74
|
+
type: "message",
|
|
75
|
+
id: message,
|
|
76
|
+
payload
|
|
77
|
+
};
|
|
78
|
+
debugHooks.onSend?.(rpcMessage);
|
|
79
|
+
transport.send(rpcMessage);
|
|
80
|
+
}
|
|
81
|
+
const send = new Proxy(sendFn, { get: (target, prop, receiver) => {
|
|
82
|
+
if (prop in target) return Reflect.get(target, prop, receiver);
|
|
83
|
+
return (payload) => sendFn(prop, payload);
|
|
84
|
+
} });
|
|
85
|
+
const sendProxy = send;
|
|
86
|
+
const messageListeners = /* @__PURE__ */ new Map();
|
|
87
|
+
const wildcardMessageListeners = /* @__PURE__ */ new Set();
|
|
88
|
+
function addMessageListener(message, listener) {
|
|
89
|
+
if (!transport.registerHandler) throw missingTransportMethodError(["registerHandler"], "register message listeners");
|
|
90
|
+
if (message === "*") {
|
|
91
|
+
wildcardMessageListeners.add(listener);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
if (!messageListeners.has(message)) messageListeners.set(message, /* @__PURE__ */ new Set());
|
|
95
|
+
messageListeners.get(message).add(listener);
|
|
96
|
+
}
|
|
97
|
+
function removeMessageListener(message, listener) {
|
|
98
|
+
if (message === "*") {
|
|
99
|
+
wildcardMessageListeners.delete(listener);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
messageListeners.get(message)?.delete(listener);
|
|
103
|
+
if (messageListeners.get(message)?.size === 0) messageListeners.delete(message);
|
|
104
|
+
}
|
|
105
|
+
async function handler(message) {
|
|
106
|
+
debugHooks.onReceive?.(message);
|
|
107
|
+
if (!("type" in message)) throw new Error("Message does not contain a type.");
|
|
108
|
+
if (message.type === "request") {
|
|
109
|
+
if (!transport.send || !requestHandler) throw missingTransportMethodError(["send", "requestHandler"], "handle requests");
|
|
110
|
+
const { id, method, params } = message;
|
|
111
|
+
let response;
|
|
112
|
+
try {
|
|
113
|
+
response = {
|
|
114
|
+
type: "response",
|
|
115
|
+
id,
|
|
116
|
+
success: true,
|
|
117
|
+
payload: await requestHandler(method, params)
|
|
118
|
+
};
|
|
119
|
+
} catch (error) {
|
|
120
|
+
if (!(error instanceof Error)) throw error;
|
|
121
|
+
response = {
|
|
122
|
+
type: "response",
|
|
123
|
+
id,
|
|
124
|
+
success: false,
|
|
125
|
+
error: error.message
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
debugHooks.onSend?.(response);
|
|
129
|
+
transport.send(response);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
if (message.type === "response") {
|
|
133
|
+
const timeout = requestTimeouts.get(message.id);
|
|
134
|
+
if (timeout != null) clearTimeout(timeout);
|
|
135
|
+
requestTimeouts.delete(message.id);
|
|
136
|
+
const { resolve, reject } = requestListeners.get(message.id) ?? {};
|
|
137
|
+
requestListeners.delete(message.id);
|
|
138
|
+
if (!message.success) reject?.(new Error(message.error));
|
|
139
|
+
else resolve?.(message.payload);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
if (message.type === "message") {
|
|
143
|
+
for (const listener of wildcardMessageListeners) listener(message.id, message.payload);
|
|
144
|
+
const listeners = messageListeners.get(message.id);
|
|
145
|
+
if (!listeners) return;
|
|
146
|
+
for (const listener of listeners) listener(message.payload);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
throw new Error(`Unexpected RPC message type: ${message.type}`);
|
|
150
|
+
}
|
|
151
|
+
return {
|
|
152
|
+
setTransport,
|
|
153
|
+
setRequestHandler,
|
|
154
|
+
request,
|
|
155
|
+
requestProxy,
|
|
156
|
+
send,
|
|
157
|
+
sendProxy,
|
|
158
|
+
addMessageListener,
|
|
159
|
+
removeMessageListener,
|
|
160
|
+
proxy: {
|
|
161
|
+
send: sendProxy,
|
|
162
|
+
request: requestProxy
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
function defineElectrobunRPC(_side, config) {
|
|
167
|
+
const rpc = createRPC({
|
|
168
|
+
maxRequestTime: config.maxRequestTime,
|
|
169
|
+
requestHandler: {
|
|
170
|
+
...config.handlers.requests,
|
|
171
|
+
...config.extraRequestHandlers
|
|
172
|
+
},
|
|
173
|
+
transport: { registerHandler: () => {} }
|
|
174
|
+
});
|
|
175
|
+
const messageHandlers = config.handlers.messages;
|
|
176
|
+
if (messageHandlers) rpc.addMessageListener("*", ((messageName, payload) => {
|
|
177
|
+
const globalHandler = messageHandlers["*"];
|
|
178
|
+
if (globalHandler) globalHandler(messageName, payload);
|
|
179
|
+
const messageHandler = messageHandlers[messageName];
|
|
180
|
+
if (messageHandler) messageHandler(payload);
|
|
181
|
+
}));
|
|
182
|
+
return rpc;
|
|
183
|
+
}
|
|
184
|
+
//#endregion
|
|
185
|
+
//#region node_modules/electrobun/dist/api/browser/index.ts
|
|
186
|
+
const WEBVIEW_ID = window.__electrobunWebviewId;
|
|
187
|
+
const RPC_SOCKET_PORT = window.__electrobunRpcSocketPort;
|
|
188
|
+
var Electroview = class {
|
|
189
|
+
bunSocket;
|
|
190
|
+
rpc;
|
|
191
|
+
rpcHandler;
|
|
192
|
+
constructor(config) {
|
|
193
|
+
this.rpc = config.rpc;
|
|
194
|
+
this.init();
|
|
195
|
+
}
|
|
196
|
+
init() {
|
|
197
|
+
this.initSocketToBun();
|
|
198
|
+
window.__electrobun.receiveMessageFromBun = this.receiveMessageFromBun.bind(this);
|
|
199
|
+
if (this.rpc) this.rpc.setTransport(this.createTransport());
|
|
200
|
+
}
|
|
201
|
+
initSocketToBun() {
|
|
202
|
+
if (!RPC_SOCKET_PORT || !WEBVIEW_ID) return;
|
|
203
|
+
const socket = new WebSocket(`ws://localhost:${RPC_SOCKET_PORT}/socket?webviewId=${WEBVIEW_ID}`);
|
|
204
|
+
this.bunSocket = socket;
|
|
205
|
+
socket.addEventListener("open", () => {});
|
|
206
|
+
socket.addEventListener("message", async (event) => {
|
|
207
|
+
const message = event.data;
|
|
208
|
+
if (typeof message === "string") try {
|
|
209
|
+
const encryptedPacket = JSON.parse(message);
|
|
210
|
+
const decrypted = await window.__electrobun_decrypt(encryptedPacket.encryptedData, encryptedPacket.iv, encryptedPacket.tag);
|
|
211
|
+
this.rpcHandler?.(JSON.parse(decrypted));
|
|
212
|
+
} catch (err) {
|
|
213
|
+
console.error("Error parsing bun message:", err);
|
|
214
|
+
}
|
|
215
|
+
else if (message instanceof Blob) {} else console.error("UNKNOWN DATA TYPE RECEIVED:", event.data);
|
|
216
|
+
});
|
|
217
|
+
socket.addEventListener("error", (event) => {
|
|
218
|
+
console.error("Socket error:", event);
|
|
219
|
+
});
|
|
220
|
+
socket.addEventListener("close", (_event) => {});
|
|
221
|
+
}
|
|
222
|
+
createTransport() {
|
|
223
|
+
const that = this;
|
|
224
|
+
return {
|
|
225
|
+
send(message) {
|
|
226
|
+
try {
|
|
227
|
+
const messageString = JSON.stringify(message);
|
|
228
|
+
that.bunBridge(messageString);
|
|
229
|
+
} catch (error) {
|
|
230
|
+
console.error("bun: failed to serialize message to webview", error);
|
|
231
|
+
}
|
|
232
|
+
},
|
|
233
|
+
registerHandler(handler) {
|
|
234
|
+
that.rpcHandler = handler;
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
async bunBridge(msg) {
|
|
239
|
+
if (this.bunSocket?.readyState === WebSocket.OPEN) try {
|
|
240
|
+
const { encryptedData, iv, tag } = await window.__electrobun_encrypt(msg);
|
|
241
|
+
const encryptedPacketString = JSON.stringify({
|
|
242
|
+
encryptedData,
|
|
243
|
+
iv,
|
|
244
|
+
tag
|
|
245
|
+
});
|
|
246
|
+
this.bunSocket.send(encryptedPacketString);
|
|
247
|
+
return;
|
|
248
|
+
} catch (error) {
|
|
249
|
+
console.error("Error sending message to bun via socket:", error);
|
|
250
|
+
}
|
|
251
|
+
window.__electrobunBunBridge?.postMessage(msg);
|
|
252
|
+
}
|
|
253
|
+
receiveMessageFromBun(msg) {
|
|
254
|
+
if (this.rpcHandler) this.rpcHandler(msg);
|
|
255
|
+
}
|
|
256
|
+
static defineRPC(config) {
|
|
257
|
+
return defineElectrobunRPC("webview", {
|
|
258
|
+
...config,
|
|
259
|
+
extraRequestHandlers: { evaluateJavascriptWithResponse: ({ script }) => {
|
|
260
|
+
return new Promise((resolve) => {
|
|
261
|
+
try {
|
|
262
|
+
const result = new Function(script)();
|
|
263
|
+
if (result instanceof Promise) result.then((resolvedResult) => {
|
|
264
|
+
resolve(resolvedResult);
|
|
265
|
+
}).catch((error) => {
|
|
266
|
+
console.error("bun: async script execution failed", error);
|
|
267
|
+
resolve(String(error));
|
|
268
|
+
});
|
|
269
|
+
else resolve(result);
|
|
270
|
+
} catch (error) {
|
|
271
|
+
console.error("bun: failed to eval script", error);
|
|
272
|
+
resolve(String(error));
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
} }
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
//#endregion
|
|
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
|
+
*/
|
|
293
|
+
function defineAuthWebviewRPC() {
|
|
294
|
+
const rpc = Electroview.defineRPC({
|
|
295
|
+
maxRequestTime: 3e4,
|
|
296
|
+
handlers: {
|
|
297
|
+
requests: {},
|
|
298
|
+
messages: {}
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
new Electroview({ rpc });
|
|
302
|
+
return {
|
|
303
|
+
getUser: () => rpc.request.getUser({}),
|
|
304
|
+
requestAuth: (options) => rpc.request.requestAuth({ options }),
|
|
305
|
+
signOut: () => rpc.request.signOut({}),
|
|
306
|
+
getUserImage: (url) => rpc.request.getUserImage({ url }),
|
|
307
|
+
onAuthenticated: (callback) => {
|
|
308
|
+
const handler = (user) => callback(user);
|
|
309
|
+
rpc.addMessageListener("onAuthenticated", handler);
|
|
310
|
+
return () => rpc.removeMessageListener("onAuthenticated", handler);
|
|
311
|
+
},
|
|
312
|
+
onUserUpdated: (callback) => {
|
|
313
|
+
const handler = (user) => callback(user);
|
|
314
|
+
rpc.addMessageListener("onUserUpdated", handler);
|
|
315
|
+
return () => rpc.removeMessageListener("onUserUpdated", handler);
|
|
316
|
+
},
|
|
317
|
+
onAuthError: (callback) => {
|
|
318
|
+
const handler = (ctx) => callback(ctx);
|
|
319
|
+
rpc.addMessageListener("onAuthError", handler);
|
|
320
|
+
return () => rpc.removeMessageListener("onAuthError", handler);
|
|
321
|
+
}
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
//#endregion
|
|
325
|
+
export { defineAuthWebviewRPC };
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { t as AllowedLoopbackPorts } from "../loopback-ClkAoS2u.mjs";
|
|
2
|
+
import * as _$better_auth0 from "better-auth";
|
|
3
|
+
import { BetterAuthPlugin } from "better-auth";
|
|
4
|
+
|
|
5
|
+
//#region src/server/error-codes.d.ts
|
|
6
|
+
declare const DESKTOP_ERROR_CODES: {
|
|
7
|
+
INVALID_CLIENT_ID: _$better_auth0.RawError<"INVALID_CLIENT_ID">;
|
|
8
|
+
INVALID_TOKEN: _$better_auth0.RawError<"INVALID_TOKEN">;
|
|
9
|
+
STATE_MISMATCH: _$better_auth0.RawError<"STATE_MISMATCH">;
|
|
10
|
+
MISSING_CODE_CHALLENGE: _$better_auth0.RawError<"MISSING_CODE_CHALLENGE">;
|
|
11
|
+
INVALID_CODE_VERIFIER: _$better_auth0.RawError<"INVALID_CODE_VERIFIER">;
|
|
12
|
+
MISSING_STATE: _$better_auth0.RawError<"MISSING_STATE">;
|
|
13
|
+
MISSING_PKCE: _$better_auth0.RawError<"MISSING_PKCE">;
|
|
14
|
+
INVALID_PKCE_METHOD: _$better_auth0.RawError<"INVALID_PKCE_METHOD">;
|
|
15
|
+
INVALID_LOOPBACK_URL: _$better_auth0.RawError<"INVALID_LOOPBACK_URL">;
|
|
16
|
+
};
|
|
17
|
+
//#endregion
|
|
18
|
+
//#region src/server/types.d.ts
|
|
19
|
+
type DesktopServerOptions = {
|
|
20
|
+
/**
|
|
21
|
+
* Client ID used to identify the desktop client during authorization. Must
|
|
22
|
+
* match the `clientID` configured in the desktop adapter.
|
|
23
|
+
* @default "desktop"
|
|
24
|
+
*/
|
|
25
|
+
clientID?: string;
|
|
26
|
+
/**
|
|
27
|
+
* Duration (in seconds) for which the one-time authorization code is valid.
|
|
28
|
+
* @default 300
|
|
29
|
+
*/
|
|
30
|
+
codeExpiresIn?: number;
|
|
31
|
+
/**
|
|
32
|
+
* Disable rewriting the request `origin` from the `desktop-origin` header.
|
|
33
|
+
* @default false
|
|
34
|
+
*/
|
|
35
|
+
disableOriginOverride?: boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Name of the one-time-code query/hash parameter appended to the loopback or
|
|
38
|
+
* web-callback URL.
|
|
39
|
+
* @default "token"
|
|
40
|
+
*/
|
|
41
|
+
hashKey?: string;
|
|
42
|
+
/**
|
|
43
|
+
* Optional branded web callback page. When set, `oauth-complete` redirects the
|
|
44
|
+
* browser here (`#token=…&loopback=…`) instead of straight to the loopback,
|
|
45
|
+
* and the page must call `forwardToDesktop()` to complete the hand-off.
|
|
46
|
+
* Leave unset for the default direct-to-loopback redirect.
|
|
47
|
+
*/
|
|
48
|
+
webCallbackUrl?: string;
|
|
49
|
+
/**
|
|
50
|
+
* Optional hardening: restrict the loopback port the server is willing to
|
|
51
|
+
* redirect to. Either an explicit allowlist or an inclusive `{ min, max }`
|
|
52
|
+
* range. Omit to allow any ephemeral port.
|
|
53
|
+
*/
|
|
54
|
+
allowedLoopbackPorts?: AllowedLoopbackPorts;
|
|
55
|
+
};
|
|
56
|
+
//#endregion
|
|
57
|
+
//#region src/server/plugin.d.ts
|
|
58
|
+
declare const betterAuthDesktop: (options?: DesktopServerOptions) => BetterAuthPlugin;
|
|
59
|
+
//#endregion
|
|
60
|
+
export { DESKTOP_ERROR_CODES, type DesktopServerOptions, betterAuthDesktop };
|