@bigio/better-auth-electron 1.0.3 → 1.0.5
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 +263 -80
- package/dist/main.js +19 -36
- package/dist/main.js.map +1 -1
- package/dist/metafile-esm.json +1 -1
- package/dist/options.d.ts +29 -10
- package/dist/options.js +23 -11
- package/dist/options.js.map +1 -1
- package/dist/renderer.d.ts +27 -2
- package/dist/renderer.js +292 -150
- package/dist/renderer.js.map +1 -1
- package/dist/server.d.ts +5 -0
- package/dist/server.js +42 -21
- package/dist/server.js.map +1 -1
- package/dist/web.d.ts +8 -11
- package/dist/web.js +524 -192
- package/dist/web.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,14 +1,92 @@
|
|
|
1
|
-
|
|
1
|
+
# @bigio/better-auth-electron
|
|
2
|
+
|
|
3
|
+
This plugin establishes a secure, **event-driven** authentication bridge between Electron and Better Auth. It treats the system browser strictly as a **stateless transport layer**, utilizing **AES-encrypted tickets** and **PKCE verification** to perform session handoff without persisting web cookies in the desktop environment.
|
|
4
|
+
|
|
5
|
+
The renderer implementation abandons traditional redirect handling in favor of an IPC-based subscription model (`onDeepLink*`). It features a built-in **cold-start buffer** to guarantee token capture even if the deep link triggers the application before the UI is fully mounted. The API surface is designed to mirror the official Better Auth client patterns, ensuring strict typing and zero-friction integration.
|
|
6
|
+
|
|
7
|
+
### Key Architecture
|
|
8
|
+
|
|
9
|
+
- **Stateless Web Handoff**: The browser authenticates via OAuth but does not share the session with Electron. It passes an encrypted, time-limited ticket back to the desktop app.
|
|
10
|
+
- **Security**: Full PKCE flow (Proof Key for Code Exchange) with verified challenges and AES-encrypted exchange tickets.
|
|
11
|
+
- **Event-Driven Renderer**: No page reloads or router redirects. Listen for `onDeepLinkSuccess`, `onDeepLinkNewUser`, or `onDeepLinkFailed` directly within your React/Vue components.
|
|
12
|
+
- **Cold Start Support**: Includes an internal IPC buffer to cache deep link events that occur during the application boot phase, ensuring no authentication intent is lost while the renderer initializes.
|
|
13
|
+
- **API Parity**: Extends the `authClient` with a `bigio` namespace that mimics standard Better Auth methods (e.g., `signInSocial`).
|
|
14
|
+
- **Native Secure Context & Origin Fix:** Leverages `protocol.registerSchemesAsPrivileged` to treat your custom scheme as a secure context. This solves the infamous `Origin` header mismatch and enables `SameSite` cookies to work natively without hacks.
|
|
15
|
+
|
|
16
|
+
#### _I'm currently studying the official implementation; the documentation is still rough but will be improved soon. I'm eager and looking forward to exchanging ideas with everyone._
|
|
17
|
+
|
|
18
|
+
# Authentication Flow
|
|
19
|
+
|
|
20
|
+
The following diagram illustrates the complete OAuth authorization and session handoff process, including the interaction with external **OAuth Providers** (e.g., GitHub, Google):
|
|
21
|
+
|
|
22
|
+
```mermaid
|
|
23
|
+
sequenceDiagram
|
|
24
|
+
participant User as User
|
|
25
|
+
participant E_R as Electron Renderer
|
|
26
|
+
participant E_M as Electron Main
|
|
27
|
+
participant Browser as External Browser (Web Frontend)
|
|
28
|
+
participant Server as Auth Server (Better Auth)
|
|
29
|
+
participant Provider as OAuth Provider (GitHub/Google)
|
|
30
|
+
|
|
31
|
+
Note over E_R, E_M: 1. Initialization & Startup
|
|
32
|
+
E_R->>E_M: Send App Mounted Signal
|
|
33
|
+
User->>E_R: Click Login
|
|
34
|
+
E_R->>E_R: Click Login (window.open)
|
|
35
|
+
E_M->>E_M: Intercept Request, Generate PKCE Verifier
|
|
36
|
+
E_M->>Browser: Open External Browser (with Challenge)
|
|
37
|
+
|
|
38
|
+
Note over Browser, Provider: 2. Web Handoff Decision & OAuth Flow
|
|
39
|
+
alt Session Exists
|
|
40
|
+
Browser->>Browser: Detect Active Session
|
|
41
|
+
Browser->>User: Show "Continue as [User]" or "Switch Account"
|
|
42
|
+
alt User selects "Fast Login"
|
|
43
|
+
User->>Browser: Click "Continue as [User]"
|
|
44
|
+
Browser->>Server: Request Fast Ticket (POST /fast-ticket)
|
|
45
|
+
Server->>Server: Generate Encrypted Ticket
|
|
46
|
+
Server-->>Browser: Return Redirect URL (bigio://...)
|
|
47
|
+
else User selects "Switch Account"
|
|
48
|
+
User->>Browser: Click "Switch Account"
|
|
49
|
+
Browser->>Server: Initiate OAuth (signIn.social)
|
|
50
|
+
Server->>Provider: Redirect to Provider Login
|
|
51
|
+
Provider->>User: Prompt for Credentials
|
|
52
|
+
User->>Provider: Authorize App
|
|
53
|
+
Provider-->>Server: Callback with Code/Token
|
|
54
|
+
Note right of Server: Stateless: Set-Cookie is intercepted/removed
|
|
55
|
+
Server->>Server: Auth Success, Generate Encrypted Ticket
|
|
56
|
+
Server-->>Browser: Redirect to bigio://callback?ticket=...
|
|
57
|
+
end
|
|
58
|
+
else No Session
|
|
59
|
+
User->>Browser: Select Provider
|
|
60
|
+
Browser->>Server: Initiate OAuth (signIn.social)
|
|
61
|
+
Server->>Provider: Redirect to Provider Login
|
|
62
|
+
Provider->>User: Prompt for Credentials
|
|
63
|
+
User->>Provider: Authorize App
|
|
64
|
+
Provider-->>Server: Callback with Code/Token
|
|
65
|
+
Note right of Server: Stateless: Set-Cookie is intercepted/removed
|
|
66
|
+
Server->>Server: Auth Success, Generate Encrypted Ticket
|
|
67
|
+
Server-->>Browser: Redirect to bigio://callback?ticket=...
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
Note over Browser, E_R: 3. Session Handoff
|
|
71
|
+
Browser->>E_M: Trigger Deep Link (Custom Protocol)
|
|
72
|
+
E_M->>E_R: Send Ticket & Verifier via IPC
|
|
73
|
+
E_R->>E_R: Verify PKCE Challenge
|
|
74
|
+
E_R->>Server: POST /exchange-ticket (Ticket + Verifier)
|
|
75
|
+
Server->>Server: Decrypt Ticket, Verify PKCE
|
|
76
|
+
Server->>Server: Create Electron-specific Session
|
|
77
|
+
Server-->>E_R: Return Session Cookie (SameSite=None)
|
|
78
|
+
E_R->>E_R: Login Success, Refresh UI
|
|
79
|
+
```
|
|
2
80
|
|
|
3
81
|
Based on my study of the official implementation code, I have decided on the following to-do list:
|
|
4
82
|
|
|
5
|
-
|
|
83
|
+
**~~1. Architecture: The "Silent Handoff" (Stateless & Secure)~~**
|
|
6
84
|
|
|
7
|
-
- [
|
|
8
|
-
- _Action_: Strip the `Set-Cookie` header (specifically the session token) from the response to prevent overwriting the user's browser session
|
|
9
|
-
- _Goal_: Achieve strict physical isolation between Web Session and Electron Session
|
|
85
|
+
- [done] ~~**Server-Side Cookie Interception**: Modify `electron-server-plugin` to intercept the OAuth callback response.~~
|
|
86
|
+
- ~~_Action_: Strip the `Set-Cookie` header (specifically the session token) from the response to prevent overwriting the user's browser session.~~
|
|
87
|
+
- ~~_Goal_: Achieve strict physical isolation between Web Session and Electron Session.~~
|
|
10
88
|
|
|
11
|
-
-
|
|
89
|
+
- ~~**Stateless OAuth Flow**: Ensure the OAuth flow relies solely on the encrypted `Ticket` mechanism, making the browser a purely stateless transport layer for Electron authentication.~~
|
|
12
90
|
|
|
13
91
|
**2. Security & Hardening**
|
|
14
92
|
|
|
@@ -24,36 +102,13 @@ Based on my study of the official implementation code, I have decided on the fol
|
|
|
24
102
|
|
|
25
103
|
**3. Developer Experience (DX) & API**
|
|
26
104
|
|
|
27
|
-
- [
|
|
28
|
-
- _Feature_: Implement `authClient.bigio.signIn({ provider: 'github' })` wrapper
|
|
29
|
-
- _Implementation_: Utilize `window.open` (intercepted by Main) or IPC to trigger the flow, keeping the API consistent with the official web client style
|
|
30
|
-
|
|
31
|
-
- [ ] **Smart Web Handoff UI (Optional/Next)**: Update the web-side confirmation page to detect and display the currently logged-in web user, offering a "Continue as [User]" button for a seamless transition.
|
|
32
|
-
|
|
33
|
-
# @bigio/better-auth-electron
|
|
34
|
-
|
|
35
|
-
> **Work In Progress:** This library is actively being developed. Detailed documentation and architecture diagrams are coming soon.
|
|
36
|
-
|
|
37
|
-
**A type-safe, IPC-Event based Better Auth integration for Electron.**
|
|
105
|
+
- [done] ~~**Enhanced Renderer API**: Refactor `getActions` to introduce a dedicated `authClient.bigio` namespace.~~
|
|
106
|
+
- ~~_Feature_: Implement `authClient.bigio.signIn({ provider: 'github' })` wrapper.~~
|
|
107
|
+
- ~~_Implementation_: Utilize `window.open` (intercepted by Main) or IPC to trigger the flow, keeping the API consistent with the official web client style.~~
|
|
38
108
|
|
|
39
|
-
|
|
109
|
+
- [done] ~~**Smart Web Handoff UI (Optional/Next)**: Update the web-side confirmation page to detect and display the currently logged-in web user, offering a "Continue as [User]" button for a seamless transition.~~
|
|
40
110
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
- ** Native Secure Context & Origin Fix:**
|
|
44
|
-
Leverages `protocol.registerSchemesAsPrivileged` to treat your custom scheme as a secure context. This solves the infamous `Origin` header mismatch and enables `SameSite` cookies to work natively without hacks.
|
|
45
|
-
|
|
46
|
-
- ** Secure PKCE Flow:**
|
|
47
|
-
Implements the standard **Proof Key for Code Exchange** protocol out-of-the-box. Ensures enterprise-grade security for your OAuth exchanges without exposing secrets.
|
|
48
|
-
|
|
49
|
-
- ** Preact SSR Coming soon:**
|
|
50
|
-
Includes a dedicated, lightweight Preact entry point optimized for Server-Side Rendering (SSR) in login windows.
|
|
51
|
-
_(React 19 supported. Vue/Svelte support coming soon!)_
|
|
52
|
-
|
|
53
|
-
- ** Zero-IPC Session Handoff:**
|
|
54
|
-
Uses secure custom protocol deep links to transfer authentication states. Full TypeScript inference via Better Auth plugins — **no fragile IPC bridges** or manual message handling required.
|
|
55
|
-
|
|
56
|
-
## Installation
|
|
111
|
+
# Installation
|
|
57
112
|
|
|
58
113
|
```bash
|
|
59
114
|
pnpm add @bigio/better-auth-electron
|
|
@@ -65,12 +120,20 @@ Ensure peer dependencies are installed:(more framework support coming soon...)
|
|
|
65
120
|
pnpm add better-auth electron react react-dom
|
|
66
121
|
```
|
|
67
122
|
|
|
68
|
-
|
|
123
|
+
# Quick Start
|
|
69
124
|
|
|
70
125
|
### 1. Server Setup (`src/lib/auth.ts`)
|
|
71
126
|
|
|
72
127
|
Initialize Better Auth with the `electronServerPlugin`. This handles the ticket exchange and verification logic on your backend.
|
|
73
128
|
|
|
129
|
+
#### The "Silent Handoff" Mechanism (Stateless & Secure)
|
|
130
|
+
|
|
131
|
+
This plugin implements a **Server-Side Cookie Interception** strategy to ensure strict isolation between the Web Session and the Electron Session.
|
|
132
|
+
|
|
133
|
+
- It intercepts OAuth callback responses specifically for Electron. It actively **removes the `Set-Cookie` header** (which contains the session token) before the response reaches the browser.
|
|
134
|
+
- This guarantees that the Electron login flow **does not overwrite or interfere** with the user's existing browser session.
|
|
135
|
+
- Authentication relies solely on a one-time encrypted Ticket. The browser acts as a purely **`stateless`** transport layer for Electron.
|
|
136
|
+
|
|
74
137
|
```typescript
|
|
75
138
|
import { betterAuth } from 'better-auth'
|
|
76
139
|
import { electronServerPlugin } from '@bigio/better-auth-electron/server'
|
|
@@ -130,7 +193,6 @@ const { windowInjection, whenReadyInjection } = mainInjection({
|
|
|
130
193
|
PROVIDERS: ['github', 'google'],
|
|
131
194
|
BETTER_AUTH_BASEURL: 'http://localhost:3002',
|
|
132
195
|
FRONTEND_URL: 'http://localhost:3001/oauth',
|
|
133
|
-
CONTENT_SECURITY_POLICY: '',
|
|
134
196
|
/**
|
|
135
197
|
* [Optional] Content Security Policy (CSP) Configuration
|
|
136
198
|
* * Strategy: "All-or-Nothing"
|
|
@@ -195,37 +257,7 @@ export const authClient = createAuthClient({
|
|
|
195
257
|
setLazyClient(authClient)
|
|
196
258
|
```
|
|
197
259
|
|
|
198
|
-
### 4. Electron Renderer
|
|
199
|
-
|
|
200
|
-
In your Electron renderer (the UI), use the helper options to construct the correct OAuth URL that opens in the system's default browser.
|
|
201
|
-
|
|
202
|
-
```typescript
|
|
203
|
-
import type { ElectronButtonOptions } from '@bigio/better-auth-electron/options'
|
|
204
|
-
import { defaultButtonOptions } from '@bigio/better-auth-electron/options'
|
|
205
|
-
|
|
206
|
-
// Merge default options with any custom overrides
|
|
207
|
-
const config: ElectronButtonOptions = { ...defaultButtonOptions }
|
|
208
|
-
const { FRONTEND_URL, PROVIDER_NAME_IN_URL } = config
|
|
209
|
-
|
|
210
|
-
const ElectronLoginButton = ({ provider }: { provider: string }) => {
|
|
211
|
-
const handleOpen = () => {
|
|
212
|
-
// Construct the auth URL
|
|
213
|
-
const targetUrl = `${FRONTEND_URL}?${PROVIDER_NAME_IN_URL}=${provider}`
|
|
214
|
-
|
|
215
|
-
// Open in external browser (e.g., Chrome/Safari) to start the flow
|
|
216
|
-
window.open(targetUrl, '_blank')
|
|
217
|
-
console.log('Opening External Browser for OAuth...')
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
return (
|
|
221
|
-
<button onClick={handleOpen}>
|
|
222
|
-
Sign in with {provider}
|
|
223
|
-
</button>
|
|
224
|
-
)
|
|
225
|
-
}
|
|
226
|
-
```
|
|
227
|
-
|
|
228
|
-
### 5. Electron Renderer/Web Client (`src/renderer/lib/auth-client.ts`)
|
|
260
|
+
### 4. Electron Renderer/Web Client (`src/renderer/lib/auth-client.ts`)
|
|
229
261
|
|
|
230
262
|
This is the auth client running **inside your Electron app**. It listens for the custom protocol deep link to hydrate the session.
|
|
231
263
|
|
|
@@ -249,32 +281,183 @@ export const authClient = createAuthClient({
|
|
|
249
281
|
})
|
|
250
282
|
```
|
|
251
283
|
|
|
284
|
+
### 5. Electron Renderer Integration (New API)
|
|
285
|
+
|
|
286
|
+
I have refactored the client-side integration to closely mirror the official Better Auth API structure, while adapting it for the specific constraints of the Electron environment (IPC & Deep Linking).
|
|
287
|
+
|
|
288
|
+
### Key Features
|
|
289
|
+
|
|
290
|
+
- **Official API Parity:** Uses a syntax similar to `authClient.signIn.social`.
|
|
291
|
+
- **Callback Functions over URLs:** Instead of handling redirects, we use event subscriptions (`onDeepLinkSuccess`, `onDeepLinkNewUser`) to handle the authentication result.
|
|
292
|
+
- **Cold Start Support:** The plugin includes an internal buffer. If the application is opened via a Deep Link before the UI is fully mounted, the plugin caches the session data and triggers the callback immediately upon registration.
|
|
293
|
+
|
|
294
|
+
### Implementation
|
|
295
|
+
|
|
296
|
+
In your Electron renderer (e.g., `src/renderer/pages/login.tsx`), use the `bigio` namespace exposed by the client plugin.
|
|
297
|
+
|
|
298
|
+
#### 1. Triggering the Sign-In
|
|
299
|
+
|
|
300
|
+
Use `signInSocial` to initiate the flow. This handles the construction of the OAuth URL, serialization of scopes/parameters, and automatically opens the system default browser.
|
|
301
|
+
|
|
302
|
+
**Note on Constraints:**
|
|
303
|
+
|
|
304
|
+
1. **`disableRedirect`** is forced to `false`. The flow _must_ redirect to the deep link scheme to trigger the Electron app.
|
|
305
|
+
2. **`additionalData`** is **JSON serialized and encoded into the URL**. Do not pass sensitive data or large objects, as they may hit browser URL length limits.
|
|
306
|
+
|
|
307
|
+
```tsx
|
|
308
|
+
import { authClient } from '@/lib/auth-client' // Your initialized client
|
|
309
|
+
|
|
310
|
+
// ... inside your component
|
|
311
|
+
;<button
|
|
312
|
+
onClick={async () => {
|
|
313
|
+
await authClient.bigio.signInSocial({
|
|
314
|
+
// [Required] The provider key (e.g., 'github', 'google')
|
|
315
|
+
provider: 'github',
|
|
316
|
+
|
|
317
|
+
// [Optional] Array of OAuth scopes
|
|
318
|
+
scopes: ['repo', 'user'],
|
|
319
|
+
|
|
320
|
+
// [Optional] Object passed to the backend (JSON serialized via URL)
|
|
321
|
+
// Warning: Keep this payload small to avoid URL length issues.
|
|
322
|
+
additionalData: {
|
|
323
|
+
theme: 'dark',
|
|
324
|
+
ref_source: 'desktop_app',
|
|
325
|
+
},
|
|
326
|
+
|
|
327
|
+
// [Optional] Hint for the provider (e.g., email address)
|
|
328
|
+
loginHint: 'user@example.com',
|
|
329
|
+
|
|
330
|
+
// [Optional] Force a sign-up screen instead of sign-in
|
|
331
|
+
requestSignUp: false,
|
|
332
|
+
})
|
|
333
|
+
}}>
|
|
334
|
+
Sign in with GitHub
|
|
335
|
+
</button>
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
#### 2. Handling the Callback (Deep Link)
|
|
339
|
+
|
|
340
|
+
Instead of a page redirect, we listen for IPC completion events. You **must** register listeners for success, failure, and (optionally) new user creation.
|
|
341
|
+
|
|
342
|
+
These functions return an **unsubscribe** handler. You are responsible for cleaning this up to prevent memory leaks or double-firing events.
|
|
343
|
+
|
|
344
|
+
```tsx
|
|
345
|
+
import { useEffect } from 'react'
|
|
346
|
+
import { useNavigate } from 'react-router-dom'
|
|
347
|
+
import { authClient } from '@/lib/auth-client'
|
|
348
|
+
|
|
349
|
+
export default function LoginPage() {
|
|
350
|
+
const navigate = useNavigate()
|
|
351
|
+
|
|
352
|
+
useEffect(() => {
|
|
353
|
+
// 1. Handle Successful Login (Returning User)
|
|
354
|
+
const unsubscribeSuccess = authClient.bigio.onDeepLinkSuccess(async (data) => {
|
|
355
|
+
console.log('Login Successful:', data)
|
|
356
|
+
// data contains { user, session }
|
|
357
|
+
navigate('/dashboard')
|
|
358
|
+
return true
|
|
359
|
+
})
|
|
360
|
+
|
|
361
|
+
// 2. Handle New User Registration (First Time Login)
|
|
362
|
+
// If not provided, 'onDeepLinkSuccess' might be triggered depending on backend config,
|
|
363
|
+
// but it is recommended to handle new users explicitly if you have an onboarding flow.
|
|
364
|
+
const unsubscribeNewUser = authClient.bigio.onDeepLinkNewUser(async (data) => {
|
|
365
|
+
console.log('New User Registered:', data)
|
|
366
|
+
// data contains { user, session }
|
|
367
|
+
navigate('/onboarding')
|
|
368
|
+
return true
|
|
369
|
+
})
|
|
370
|
+
|
|
371
|
+
// 3. Handle Errors (Network issues, User denied access, Invalid State)
|
|
372
|
+
const unsubscribeError = authClient.bigio.onDeepLinkFailed(async (error) => {
|
|
373
|
+
console.error('Authentication Failed:', error)
|
|
374
|
+
// Show toast or error message
|
|
375
|
+
})
|
|
376
|
+
|
|
377
|
+
// Cleanup: Essential for React's StrictMode and component unmounting
|
|
378
|
+
return () => {
|
|
379
|
+
unsubscribeSuccess()
|
|
380
|
+
unsubscribeNewUser()
|
|
381
|
+
unsubscribeError()
|
|
382
|
+
}
|
|
383
|
+
}, [])
|
|
384
|
+
|
|
385
|
+
return (
|
|
386
|
+
// ... your JSX
|
|
387
|
+
)
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
### API Reference: `signInSocial`
|
|
393
|
+
|
|
394
|
+
| Parameter | Type | Description |
|
|
395
|
+
| ---------------- | ------------------------- | ------------------------------------------------------------------ |
|
|
396
|
+
| `provider` | `string` | **Required.** The key of the provider (e.g., `github`). |
|
|
397
|
+
| `scopes` | `string[]` | **Optional.** Additional OAuth scopes to request. |
|
|
398
|
+
| `additionalData` | `Record<string, unknown>` | **Optional.** Metadata sent to the backend. **Serialized to URL.** |
|
|
399
|
+
| `loginHint` | `string` | **Optional.** Passes a hint (usually email) to the provider. |
|
|
400
|
+
| `requestSignUp` | `boolean` | **Optional.** Hints the provider to show the registration page. |
|
|
401
|
+
|
|
252
402
|
### 6. Web/App Component Usage (`src/web/components/user-session.tsx`)
|
|
253
403
|
|
|
254
|
-
The `useElectronOAuthSession` hook is the
|
|
404
|
+
The `useElectronOAuthSession` hook is the core of the "Handoff" experience. It manages the synchronization between the web authentication state and the Electron application.
|
|
255
405
|
|
|
256
|
-
|
|
406
|
+
#### Component Implementation
|
|
407
|
+
|
|
408
|
+
The hook provides reactive states to manage the UI. Most importantly, the 'pending' state serves as a "Session Detected" signal.
|
|
409
|
+
|
|
410
|
+
To resolve this state, you use the `setFastLogin` function. Calling this function immediately updates the oauthStatus and triggers the next step in the authentication flow.
|
|
411
|
+
|
|
412
|
+
```tsx
|
|
413
|
+
import { useEffect } from 'react'
|
|
257
414
|
import { authClient } from '@/web/client'
|
|
258
415
|
|
|
259
416
|
export function UserSessionStatus() {
|
|
260
417
|
const {
|
|
261
|
-
data:
|
|
418
|
+
data: sessionData,
|
|
262
419
|
error,
|
|
263
|
-
isPending,
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
//
|
|
267
|
-
//
|
|
268
|
-
|
|
420
|
+
isPending, // Initial loading state
|
|
421
|
+
|
|
422
|
+
// Status enum: 'idle' | 'pending' | 'connecting' | 'succeed' | 'failed'
|
|
423
|
+
// 'pending': CRITICAL state. It confirms a valid session ALREADY exists
|
|
424
|
+
// and the system is pausing to wait for the user's decision.
|
|
425
|
+
oauthStatus,
|
|
426
|
+
oauthError,
|
|
427
|
+
|
|
428
|
+
// Action to control the flow:
|
|
429
|
+
// setFastLogin(true) = Fast Login (Use current session)
|
|
430
|
+
// setFastLogin(false) = Switch Account (Ignore current session)
|
|
431
|
+
setFastLogin,
|
|
269
432
|
} = authClient.bigio.useElectronOAuthSession()
|
|
270
433
|
|
|
271
|
-
|
|
272
|
-
|
|
434
|
+
/**
|
|
435
|
+
* Optional: Force Logic (Auto-decision)
|
|
436
|
+
* If you want to skip the user choice UI:
|
|
437
|
+
*/
|
|
438
|
+
useEffect(() => {
|
|
439
|
+
setFastLogin(true) // Force Fast Login immediately
|
|
440
|
+
// OR
|
|
441
|
+
setFastLogin(false) // Force Switch Account immediately
|
|
442
|
+
}, [])
|
|
273
443
|
|
|
444
|
+
/**
|
|
445
|
+
* Optional: User-decision
|
|
446
|
+
* If you want to let the user choice:
|
|
447
|
+
*/
|
|
274
448
|
return (
|
|
275
449
|
<div>
|
|
276
|
-
|
|
277
|
-
|
|
450
|
+
{/* The 'pending' status indicates a session collision/detection.
|
|
451
|
+
We present the choice to the user here.
|
|
452
|
+
*/}
|
|
453
|
+
{oauthStatus === 'pending' ? (
|
|
454
|
+
<>
|
|
455
|
+
{/* Option: Ignore current session and re-login */}
|
|
456
|
+
<button onClick={() => setFastLogin(false)}>Switch Account</button>
|
|
457
|
+
{/* Option: Use current session for Electron */}
|
|
458
|
+
<button onClick={() => setFastLogin(true)}>Fast Login</button>
|
|
459
|
+
</>
|
|
460
|
+
) : null}
|
|
278
461
|
</div>
|
|
279
462
|
)
|
|
280
463
|
}
|
package/dist/main.js
CHANGED
|
@@ -3,7 +3,7 @@ import path from 'path';
|
|
|
3
3
|
import process2 from 'process';
|
|
4
4
|
import { pathToFileURL } from 'url';
|
|
5
5
|
import { app, protocol, ipcMain, shell, session, net } from 'electron';
|
|
6
|
-
import
|
|
6
|
+
import z, { boolean } from 'zod';
|
|
7
7
|
|
|
8
8
|
var __create = Object.create;
|
|
9
9
|
var __defProp = Object.defineProperty;
|
|
@@ -2236,7 +2236,6 @@ var defaultMainPluginOptions = {
|
|
|
2236
2236
|
isOAuth: true,
|
|
2237
2237
|
BETTER_AUTH_BASEURL: "http://localhost:3002",
|
|
2238
2238
|
ELECTRON_APP_NAME: "bigio-electron-demo",
|
|
2239
|
-
PROVIDERS: ["github", "google"],
|
|
2240
2239
|
ELECTRON_APP_HOST: "app-renderer",
|
|
2241
2240
|
ELECTRON_SCHEME: "bigio",
|
|
2242
2241
|
ELECTRON_RENDERER_PATH: "out/renderer",
|
|
@@ -2245,11 +2244,10 @@ var defaultMainPluginOptions = {
|
|
|
2245
2244
|
CLEAR_COOKIES_EVENT_NAME: "clear-Cookies",
|
|
2246
2245
|
GET_COOKIES_EVENT_NAME: "get-Cookies",
|
|
2247
2246
|
ELECTRON_VERIFIER_LENGTH: 32,
|
|
2248
|
-
FRONTEND_URL: "http://localhost:
|
|
2247
|
+
FRONTEND_URL: "http://localhost:3001/oauth",
|
|
2249
2248
|
WEB_OAUTH_SIGNIN_CALLBACK_PATHNAME: "electron-handoff",
|
|
2250
2249
|
SCHEME_NAME_IN_URL: "scheme",
|
|
2251
|
-
|
|
2252
|
-
CHALLENGE_NAME_IN_URL: "electron_challenge",
|
|
2250
|
+
CHALLENGE_NAME_IN_URL: "electron-challenge",
|
|
2253
2251
|
CALLBACK_PATHNAME_IN_URL: "callbackpath",
|
|
2254
2252
|
OLD_SCHOOL_ONBEFORE_WAY: false,
|
|
2255
2253
|
ELECTRON_CALLBACK_HOST_PATH: "better-auth-callback",
|
|
@@ -2669,6 +2667,7 @@ function safeTry(func, errorMessage) {
|
|
|
2669
2667
|
var crypto = globalThis.crypto;
|
|
2670
2668
|
var ALGO_SHA = "SHA-256";
|
|
2671
2669
|
var GLOBAL_ENCODER = new TextEncoder();
|
|
2670
|
+
new TextDecoder();
|
|
2672
2671
|
function encode64(buffer) {
|
|
2673
2672
|
const checkBuffer = okOr(buffer, {
|
|
2674
2673
|
msg: "Invalid buffer input for toBase64Url",
|
|
@@ -2715,11 +2714,17 @@ async function pkceGenerateChallenge(verifier) {
|
|
|
2715
2714
|
});
|
|
2716
2715
|
return encode64(hashBuffer);
|
|
2717
2716
|
}
|
|
2717
|
+
z.object({
|
|
2718
|
+
scopes: z.array(z.string()).optional(),
|
|
2719
|
+
loginHint: z.string().optional(),
|
|
2720
|
+
additionalData: z.record(z.string(), z.any()).optional(),
|
|
2721
|
+
requestSignUp: boolean().optional()
|
|
2722
|
+
});
|
|
2718
2723
|
|
|
2719
2724
|
// src/main/electron-main-plugin.ts
|
|
2720
2725
|
import_main.default.initialize();
|
|
2721
2726
|
var isInitializedAtom = atom(false);
|
|
2722
|
-
var
|
|
2727
|
+
var deepLinkUrlTempAtom = atom(null);
|
|
2723
2728
|
var browserWindowAtom = atom(null);
|
|
2724
2729
|
var getMainWindow = () => {
|
|
2725
2730
|
const mainWindow = browserWindowAtom.get();
|
|
@@ -2757,9 +2762,9 @@ var popUpWindow = (win) => {
|
|
|
2757
2762
|
}
|
|
2758
2763
|
win.focus();
|
|
2759
2764
|
};
|
|
2760
|
-
var verifierZod =
|
|
2761
|
-
verifier:
|
|
2762
|
-
expiresAt:
|
|
2765
|
+
var verifierZod = z.object({
|
|
2766
|
+
verifier: z.string(),
|
|
2767
|
+
expiresAt: z.number()
|
|
2763
2768
|
});
|
|
2764
2769
|
var mainInjection = (options) => {
|
|
2765
2770
|
if (isInitializedAtom.get()) {
|
|
@@ -2786,11 +2791,9 @@ var mainInjection = (options) => {
|
|
|
2786
2791
|
FRONTEND_URL,
|
|
2787
2792
|
CHALLENGE_NAME_IN_URL,
|
|
2788
2793
|
SCHEME_NAME_IN_URL,
|
|
2789
|
-
PROVIDER_NAME_IN_URL,
|
|
2790
2794
|
ELECTRON_APP_HOST,
|
|
2791
2795
|
ELECTRON_RENDERER_PATH,
|
|
2792
2796
|
ELECTRON_VERIFIER_FILE_NAME,
|
|
2793
|
-
PROVIDERS,
|
|
2794
2797
|
ELECTRON_APP_NAME,
|
|
2795
2798
|
OLD_SCHOOL_ONBEFORE_WAY,
|
|
2796
2799
|
CONTENT_SECURITY_POLICY
|
|
@@ -2894,9 +2897,9 @@ var mainInjection = (options) => {
|
|
|
2894
2897
|
deepLinkURL,
|
|
2895
2898
|
verifier: getElectronVerifier()
|
|
2896
2899
|
});
|
|
2897
|
-
|
|
2900
|
+
deepLinkUrlTempAtom.set(null);
|
|
2898
2901
|
} else {
|
|
2899
|
-
|
|
2902
|
+
deepLinkUrlTempAtom.set(deepLinkURL);
|
|
2900
2903
|
}
|
|
2901
2904
|
};
|
|
2902
2905
|
app.on("open-url", (event, deepLinkURL) => {
|
|
@@ -2910,7 +2913,7 @@ var mainInjection = (options) => {
|
|
|
2910
2913
|
if (process2.platform === "win32") {
|
|
2911
2914
|
const coldStartUrl = process2.argv.find((arg) => arg.startsWith(`${ELECTRON_SCHEME}://`));
|
|
2912
2915
|
if (coldStartUrl) {
|
|
2913
|
-
|
|
2916
|
+
deepLinkUrlTempAtom.set(coldStartUrl);
|
|
2914
2917
|
}
|
|
2915
2918
|
}
|
|
2916
2919
|
app.on("second-instance", (_event, commandLine, workingDirectory) => {
|
|
@@ -2921,13 +2924,13 @@ var mainInjection = (options) => {
|
|
|
2921
2924
|
});
|
|
2922
2925
|
ipcMain.removeAllListeners(APP_MOUNTED_EVENT_NAME);
|
|
2923
2926
|
ipcMain.on(APP_MOUNTED_EVENT_NAME, (event) => {
|
|
2924
|
-
const deepLinkURL =
|
|
2927
|
+
const deepLinkURL = deepLinkUrlTempAtom.get();
|
|
2925
2928
|
if (deepLinkURL) {
|
|
2926
2929
|
event.sender.send(DEEPLINK_EVENT_NAME, {
|
|
2927
2930
|
deepLinkURL,
|
|
2928
2931
|
verifier: getElectronVerifier()
|
|
2929
2932
|
});
|
|
2930
|
-
|
|
2933
|
+
deepLinkUrlTempAtom.set(null);
|
|
2931
2934
|
}
|
|
2932
2935
|
});
|
|
2933
2936
|
ipcMain.removeHandler(GET_COOKIES_EVENT_NAME);
|
|
@@ -2977,25 +2980,6 @@ var mainInjection = (options) => {
|
|
|
2977
2980
|
true
|
|
2978
2981
|
);
|
|
2979
2982
|
const url = new URL(targetUrl);
|
|
2980
|
-
const provider = okOr(
|
|
2981
|
-
url.searchParams.get(PROVIDER_NAME_IN_URL),
|
|
2982
|
-
new BigIOError("No provider", {
|
|
2983
|
-
bigioErrorStack: [
|
|
2984
|
-
{
|
|
2985
|
-
ctx: targetUrl
|
|
2986
|
-
}
|
|
2987
|
-
]
|
|
2988
|
-
})
|
|
2989
|
-
);
|
|
2990
|
-
if (!PROVIDERS.includes(provider)) {
|
|
2991
|
-
throw new BigIOError("Error Provider", {
|
|
2992
|
-
bigioErrorStack: [
|
|
2993
|
-
{
|
|
2994
|
-
ctx: targetUrl
|
|
2995
|
-
}
|
|
2996
|
-
]
|
|
2997
|
-
});
|
|
2998
|
-
}
|
|
2999
2983
|
url.searchParams.set(CHALLENGE_NAME_IN_URL, challenge);
|
|
3000
2984
|
url.searchParams.set(SCHEME_NAME_IN_URL, ELECTRON_SCHEME);
|
|
3001
2985
|
shell.openExternal(url.toString());
|
|
@@ -3047,7 +3031,6 @@ var mainInjection = (options) => {
|
|
|
3047
3031
|
const responseHeaders = {
|
|
3048
3032
|
...details.responseHeaders,
|
|
3049
3033
|
"Content-Security-Policy": [CONTENT_SECURITY_POLICY ?? fallbackCSP]
|
|
3050
|
-
// 注入!
|
|
3051
3034
|
};
|
|
3052
3035
|
callback({
|
|
3053
3036
|
responseHeaders,
|