diff --git a/.gitignore b/.gitignore index ea93a8e1f091..278d00a93752 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ __generated__ .clauderc .tmp/ *.private_ai_docs.md +.cursor/plans/ dist build-electron .next diff --git a/apps/desktop/app/app.ts b/apps/desktop/app/app.ts index 11248fbd1e16..9c05a20369da 100644 --- a/apps/desktop/app/app.ts +++ b/apps/desktop/app/app.ts @@ -55,7 +55,7 @@ import { } from './resoucePath'; import { initSentry } from './sentry'; import { startServices } from './service'; -import { setMainWindow } from './service/oauthLocalServer/oauthLocalServer'; +import { setMainWindowForOAuthServer } from './service/oauthLocalServer/oauthLocalServer'; logger.initialize(); logger.transports.file.maxSize = 1024 * 1024 * 10; @@ -564,7 +564,7 @@ async function createMainWindow() { void browserWindow.loadURL(src); // Set main window reference for OAuth server - setMainWindow(browserWindow); + setMainWindowForOAuthServer(browserWindow); // Protocol handler for win32 if (isWin || isMac) { diff --git a/apps/desktop/app/service/appleAuth/appleAuth.ts b/apps/desktop/app/service/appleAuth/appleAuth.ts new file mode 100644 index 000000000000..ec571575de35 --- /dev/null +++ b/apps/desktop/app/service/appleAuth/appleAuth.ts @@ -0,0 +1,80 @@ +/** + * Native Apple Sign-In service for macOS Desktop + * + * Uses the native apple-auth-macos module to perform Apple Sign-In + * with system UI (no browser required). + */ + +import { OneKeyLocalError } from '@onekeyhq/shared/src/errors'; + +// eslint-disable-next-line import/no-relative-packages +import type { IAppleSignInResult } from '../../../native-modules/apple-auth-macos'; + +// Only load on macOS +const isMacOS = process.platform === 'darwin'; + +let appleAuthModule: + | typeof import('../../../native-modules/apple-auth-macos') + | null = null; + +// Lazy load the native module +function getAppleAuthModule() { + if (!isMacOS) { + return null; + } + + if (!appleAuthModule) { + try { + // eslint-disable-next-line @typescript-eslint/no-require-imports + appleAuthModule = require('../../native-modules/apple-auth-macos'); + } catch (error) { + console.warn( + 'Failed to load apple-auth-macos:', + error instanceof Error ? error.message : error, + ); + return null; + } + } + + return appleAuthModule; +} + +/** + * Check if native Apple Sign-In is available. + * Requires macOS 10.15+ and the native module to be built. + */ +export function isAppleAuthAvailable(): boolean { + const module = getAppleAuthModule(); + if (!module) { + return false; + } + + try { + return module.isAvailable(); + } catch { + return false; + } +} + +/** + * Perform native Apple Sign-In. + * + * @returns Promise with identity token and nonce for Supabase + * @throws Error if sign-in fails or user cancels + */ +export async function signInWithApple(): Promise { + const module = getAppleAuthModule(); + + if (!module) { + throw new OneKeyLocalError( + 'Apple Sign-In native module is not available. ' + + 'Make sure you are on macOS and the module is built.', + ); + } + + if (!module.isAvailable()) { + throw new OneKeyLocalError('Apple Sign-In requires macOS 10.15 or later'); + } + + return module.signIn(); +} diff --git a/apps/desktop/app/service/oauthLocalServer/oauthCallbackHtml.ts b/apps/desktop/app/service/oauthLocalServer/oauthCallbackHtml.ts index df25ae795ce5..95bc19c187da 100644 --- a/apps/desktop/app/service/oauthLocalServer/oauthCallbackHtml.ts +++ b/apps/desktop/app/service/oauthLocalServer/oauthCallbackHtml.ts @@ -1,8 +1,8 @@ /** - * HTML templates returned by the localhost OAuth callback server (`/callback`). + * HTML templates returned by the localhost OAuth callback server (`/oauth_callback_desktop`). * * Why this exists: - * - Supabase redirects back to `http://127.0.0.1:/callback` with `code` (and `state`) + * - Supabase redirects back to `http://127.0.0.1:/oauth_callback_desktop` with `code` (and `state`) * in the URL query string (PKCE authorization code flow). * - We return an HTML page with JS to extract `code`/`state`, then POST them to `/complete` * so the desktop app can validate state (anti-CSRF) and exchange the code for a session. @@ -10,10 +10,204 @@ * Note: * - Browsers may block `window.close()` unless the tab/window was opened by script. * We still attempt a best-effort close and provide a manual Close button. + * + * Design tokens used (from Figma): + * - Colors: text rgba(0,0,0,0.88), textSubdued rgba(0,0,0,0.61), bgStrong rgba(0,0,0,0.05) + * - Font sizes: heading4xl 32px/40px, headingMd 16px/24px semibold, bodyLg 16px/24px + * - Spacing: space-6 24px, space-2 8px, space-3.5 14px + * - Border radius: radius-2 8px */ import { escape as escapeHtml } from 'lodash'; +// OneKey logomark SVG (circular version with brand green) +const ONEKEY_LOGOMARK_SVG = ` + + + + + + + + + + + + +`; + +// CSS styles following OneKey design tokens +const OAUTH_CALLBACK_STYLES = ` + +`; + const OAUTH_CALLBACK_CLOSE_SCRIPT = ` function clearUrlHash() { try { @@ -32,17 +226,29 @@ const OAUTH_CALLBACK_CLOSE_SCRIPT = ` export const OAUTH_CALLBACK_ERROR_HTML = ( errorMessage: string, ) => ` - + Login Failed + + ${OAUTH_CALLBACK_STYLES} - -
-

Login Failed

-

Error: ${escapeHtml(errorMessage)}

-

This tab may not close automatically due to browser restrictions. You can safely close it.

- + +
+ +
+

Login failed

+

Something went wrong during login.

+
+

${escapeHtml(errorMessage)}

+
+

Click the button below if this window does not close automatically.

+
+ +
+