From 33adc849aa50c0a4cbe2c6075901922f572b8976 Mon Sep 17 00:00:00 2001 From: Sreeram Sreedhar Date: Tue, 23 Jun 2026 13:03:31 -0700 Subject: [PATCH] Start desktop auth from native login --- apps/desktop/app/login/page.tsx | 184 +++++++++++---- apps/desktop/lib/auth.ts | 12 + apps/desktop/src-tauri/src/auth.rs | 220 +++++++++++++++--- apps/desktop/src-tauri/src/lib.rs | 40 ++++ .../app/api/auth/desktop/callback/route.ts | 115 +++++++++ apps/web/app/auth/desktop/page.tsx | 196 ---------------- 6 files changed, 501 insertions(+), 266 deletions(-) create mode 100644 apps/web/app/api/auth/desktop/callback/route.ts delete mode 100644 apps/web/app/auth/desktop/page.tsx diff --git a/apps/desktop/app/login/page.tsx b/apps/desktop/app/login/page.tsx index 0a2c239f..18918034 100644 --- a/apps/desktop/app/login/page.tsx +++ b/apps/desktop/app/login/page.tsx @@ -23,18 +23,22 @@ import { useState, } from "react" import { - beginBrowserAuth, + beginSocialAuth, desktopDevAuthEnabled, getSession, onAuthChanged, onAuthError, + sendMagicLink, storeToken, + verifyMagicLinkToken, } from "@/lib/auth" export default function LoginPage() { const router = useRouter() const [token, setToken] = useState("") const [email, setEmail] = useState("") + const [submittedEmail, setSubmittedEmail] = useState(null) + const [loginCode, setLoginCode] = useState("") const [error, setError] = useState(null) const [isSubmitting, setIsSubmitting] = useState(false) const [isBrowserAuthPending, setIsBrowserAuthPending] = useState(false) @@ -47,6 +51,8 @@ export default function LoginPage() { if (!event.authenticated) return setError(null) setIsBrowserAuthPending(false) + setSubmittedEmail(null) + setIsSubmitting(false) try { await getSession() router.replace("/") @@ -63,6 +69,7 @@ export default function LoginPage() { onAuthError((message) => { setIsBrowserAuthPending(false) + setIsSubmitting(false) setError(message) }) .then((handler) => { @@ -93,20 +100,46 @@ export default function LoginPage() { } } - async function startBrowserAuth() { + async function startSocialAuth(provider: "google" | "github") { setError(null) + setSubmittedEmail(null) setIsBrowserAuthPending(true) try { - await beginBrowserAuth() + await beginSocialAuth(provider) } catch (err) { - setError(formatError(err, "Could not open browser sign-in")) + setError(formatError(err, `Could not start ${provider} sign-in`)) setIsBrowserAuthPending(false) } } - function onBrowserAuthSubmit(event: FormEvent) { + async function onEmailAuthSubmit(event: FormEvent) { event.preventDefault() - void startBrowserAuth() + setError(null) + setSubmittedEmail(null) + setIsSubmitting(true) + + try { + await sendMagicLink(email) + setSubmittedEmail(email) + } catch (err) { + setError(formatError(err, "Could not send login link")) + } finally { + setIsSubmitting(false) + } + } + + async function onVerifyTokenSubmit(event: FormEvent) { + event.preventDefault() + setError(null) + setIsSubmitting(true) + + try { + await verifyMagicLinkToken(loginCode) + setIsBrowserAuthPending(true) + } catch (err) { + setError(formatError(err, "Could not verify login code")) + setIsSubmitting(false) + } } const isAuthBusy = isBrowserAuthPending || isSubmitting @@ -168,48 +201,111 @@ export default function LoginPage() {
- } - authProvider="Google" - className="w-full" - disabled={isAuthBusy} - onClick={startBrowserAuth} - type="button" - /> + {submittedEmail ? ( +
+
+

+ Check your email +

+

+ Click the magic link sent to{" "} + + {submittedEmail} + {" "} + or enter the code below. +

+
- } - authProvider="Github" - className="w-full" - disabled={isAuthBusy} - onClick={startBrowserAuth} - type="button" - /> +
+ setLoginCode(event.target.value)} + placeholder="temporary login code" + type="text" + autoComplete="one-time-code" + className="desktop-login-email-input rounded-xl border-[#17202e] bg-[#040a14]/70 px-6 text-base text-foreground placeholder:text-muted-foreground/45" + /> - + + -
- setEmail(event.target.value)} - placeholder="your@email.com" - type="email" - autoComplete="email" - className="desktop-login-email-input rounded-xl border-[#17202e] bg-[#040a14]/70 px-6 text-base text-foreground placeholder:text-muted-foreground/45" - /> + +
+ ) : ( + <> + } + authProvider="Google" + className="w-full" + disabled={isAuthBusy} + onClick={() => startSocialAuth("google")} + type="button" + /> - - + } + authProvider="Github" + className="w-full" + disabled={isAuthBusy} + onClick={() => startSocialAuth("github")} + type="button" + /> + + + +
+ setEmail(event.target.value)} + placeholder="your@email.com" + type="email" + autoComplete="email" + className="desktop-login-email-input rounded-xl border-[#17202e] bg-[#040a14]/70 px-6 text-base text-foreground placeholder:text-muted-foreground/45" + /> + + +
+ + )}

By continuing, you agree to our{" "} diff --git a/apps/desktop/lib/auth.ts b/apps/desktop/lib/auth.ts index 2d440501..112715cc 100644 --- a/apps/desktop/lib/auth.ts +++ b/apps/desktop/lib/auth.ts @@ -36,6 +36,18 @@ export async function beginBrowserAuth() { return invoke("auth_begin_browser") } +export async function beginSocialAuth(provider: "google" | "github") { + return invoke("auth_begin_social", { provider }) +} + +export async function sendMagicLink(email: string) { + return invoke("auth_send_magic_link", { email }) +} + +export async function verifyMagicLinkToken(token: string) { + return invoke("auth_verify_magic_link_token", { token }) +} + export async function clearSession() { await invoke("auth_clear") } diff --git a/apps/desktop/src-tauri/src/auth.rs b/apps/desktop/src-tauri/src/auth.rs index 725220af..8f7b2789 100644 --- a/apps/desktop/src-tauri/src/auth.rs +++ b/apps/desktop/src-tauri/src/auth.rs @@ -1,5 +1,5 @@ use keyring::{Entry, Error as KeyringError}; -use serde::Serialize; +use serde::{Deserialize, Serialize}; use serde_json::Value; use std::{ io::{BufRead, BufReader, Write}, @@ -24,7 +24,22 @@ const DEFAULT_API_URL: &str = "https://api.supermemory.ai"; static TOKEN_CACHE: OnceLock>> = OnceLock::new(); static API_URL_CACHE: OnceLock>> = OnceLock::new(); -static PENDING_BROWSER_STATE: OnceLock>> = OnceLock::new(); +static PENDING_BROWSER_AUTH: OnceLock>> = OnceLock::new(); + +#[derive(Clone)] +struct PendingBrowserAuth { + state: String, + finish_url: String, +} + +struct DesktopAuthRequest { + finish_url: String, +} + +#[derive(Deserialize)] +struct SocialSignInResponse { + url: String, +} #[derive(Serialize)] #[serde(rename_all = "camelCase")] @@ -60,8 +75,8 @@ fn api_url_cache() -> &'static Mutex> { API_URL_CACHE.get_or_init(|| Mutex::new(None)) } -fn pending_browser_state() -> &'static Mutex> { - PENDING_BROWSER_STATE.get_or_init(|| Mutex::new(None)) +fn pending_browser_auth() -> &'static Mutex> { + PENDING_BROWSER_AUTH.get_or_init(|| Mutex::new(None)) } fn set_cached_token(token: Option) -> Result<(), String> { @@ -207,20 +222,115 @@ pub fn begin_browser_auth(on_complete: F) -> Result where F: FnOnce(Result) + Send + 'static, { - let state = uuid::Uuid::new_v4().to_string(); - { - let mut pending = pending_browser_state() - .lock() - .map_err(|_| "Could not lock browser auth state".to_string())?; - *pending = Some(state.clone()); - } - - let port = start_loopback_auth_server(on_complete)?; - let login_url = build_browser_login_url(&state, port)?; + let request = create_desktop_auth_request(on_complete)?; + let login_url = build_browser_login_url_from_finish_url(&request.finish_url)?; open_system_browser(&login_url)?; Ok(login_url) } +pub async fn begin_social_auth(provider: String, on_complete: F) -> Result +where + F: FnOnce(Result) + Send + 'static, +{ + let provider = match provider.as_str() { + "google" | "github" => provider, + _ => return Err("Unsupported social sign-in provider".to_string()), + }; + + let request = create_desktop_auth_request(on_complete)?; + let api_url = browser_auth_api_url(); + let sign_in_url = format!("{}/api/auth/sign-in/social", api_url.trim_end_matches('/')); + let response = reqwest::Client::new() + .post(&sign_in_url) + .header("Content-Type", "application/json") + .header("X-App-Source", "desktop") + .json(&serde_json::json!({ + "provider": provider, + "callbackURL": request.finish_url, + "errorCallbackURL": request.finish_url, + })) + .send() + .await + .map_err(|error| format!("Could not start {provider} sign-in: {error}"))?; + + if !response.status().is_success() { + let status = response.status(); + let body = response.text().await.unwrap_or_default(); + return Err(format!( + "Could not start {provider} sign-in ({status}): {body}" + )); + } + + let sign_in = response + .json::() + .await + .map_err(|error| format!("Could not parse {provider} sign-in response: {error}"))?; + + open_system_browser(&sign_in.url)?; + Ok(sign_in.url) +} + +pub async fn send_magic_link(email: String, on_complete: F) -> Result<(), String> +where + F: FnOnce(Result) + Send + 'static, +{ + let email = email.trim().to_string(); + if email.is_empty() { + return Err("Email cannot be empty".to_string()); + } + + let request = create_desktop_auth_request(on_complete)?; + let api_url = browser_auth_api_url(); + let magic_link_url = format!( + "{}/api/auth/sign-in/magic-link", + api_url.trim_end_matches('/') + ); + let response = reqwest::Client::new() + .post(&magic_link_url) + .header("Content-Type", "application/json") + .header("X-App-Source", "desktop") + .json(&serde_json::json!({ + "email": email, + "callbackURL": request.finish_url, + "errorCallbackURL": request.finish_url, + })) + .send() + .await + .map_err(|error| format!("Could not send login link: {error}"))?; + + if !response.status().is_success() { + let status = response.status(); + let body = response.text().await.unwrap_or_default(); + return Err(format!("Could not send login link ({status}): {body}")); + } + + Ok(()) +} + +pub fn verify_magic_link_token(token: String) -> Result { + let token = token.trim(); + if token.is_empty() { + return Err("Login code cannot be empty".to_string()); + } + + let finish_url = pending_finish_url()?; + let api_url = browser_auth_api_url(); + let mut verify_url = url::Url::parse(&format!( + "{}/api/auth/magic-link/verify", + api_url.trim_end_matches('/') + )) + .map_err(|error| format!("Invalid magic link verification URL: {error}"))?; + verify_url + .query_pairs_mut() + .append_pair("token", token) + .append_pair("callbackURL", &finish_url) + .append_pair("errorCallbackURL", &finish_url); + + let verify_url = verify_url.to_string(); + open_system_browser(&verify_url)?; + Ok(verify_url) +} + pub fn handle_deep_link(url: &str) -> Result { let parsed = url::Url::parse(url).map_err(|error| format!("Invalid auth callback URL: {error}"))?; @@ -315,19 +425,44 @@ fn handle_loopback_callback(url: &str) -> Result { }) } -fn build_browser_login_url(state: &str, callback_port: u16) -> Result { - let base = web_url(); - let mut url = url::Url::parse(&base) - .or_else(|_| url::Url::parse(&format!("{}/", base.trim_end_matches('/')))) - .map_err(|error| format!("Invalid Supermemory web URL: {error}"))?; - url.set_path("auth/desktop"); +fn create_desktop_auth_request(on_complete: F) -> Result +where + F: FnOnce(Result) + Send + 'static, +{ + let state = uuid::Uuid::new_v4().to_string(); + let port = start_loopback_auth_server(on_complete)?; + let callback_url = build_loopback_callback_url(&state, port)?; + let finish_url = build_desktop_finish_url(&callback_url)?; + { + let mut pending = pending_browser_auth() + .lock() + .map_err(|_| "Could not lock browser auth state".to_string())?; + *pending = Some(PendingBrowserAuth { + state, + finish_url: finish_url.clone(), + }); + } + + Ok(DesktopAuthRequest { finish_url }) +} + +fn build_loopback_callback_url(state: &str, callback_port: u16) -> Result { let mut callback = url::Url::parse(&format!("http://127.0.0.1:{callback_port}/callback")) .map_err(|error| format!("Invalid desktop callback URL: {error}"))?; callback .query_pairs_mut() .append_pair("state", state) .append_pair("api_url", &browser_auth_api_url()); + Ok(callback.to_string()) +} + +fn build_desktop_finish_url(callback_url: &str) -> Result { + let base = web_url(); + let mut url = url::Url::parse(&base) + .or_else(|_| url::Url::parse(&format!("{}/", base.trim_end_matches('/')))) + .map_err(|error| format!("Invalid Supermemory web URL: {error}"))?; + url.set_path("api/auth/desktop/callback"); let cwd = std::env::current_dir() .ok() @@ -335,7 +470,8 @@ fn build_browser_login_url(state: &str, callback_port: u16) -> Result Result Result { + let base = web_url(); + let mut url = url::Url::parse(&base) + .or_else(|_| url::Url::parse(&format!("{}/", base.trim_end_matches('/')))) + .map_err(|error| format!("Invalid Supermemory web URL: {error}"))?; + url.set_path("login"); + url.query_pairs_mut().append_pair("redirect", finish_url); + Ok(url.to_string()) +} + +#[cfg(test)] +fn build_browser_login_url(state: &str, callback_port: u16) -> Result { + let callback_url = build_loopback_callback_url(state, callback_port)?; + build_desktop_finish_url(&callback_url) +} + +fn pending_finish_url() -> Result { + pending_browser_auth() + .lock() + .map_err(|_| "Could not lock browser auth state".to_string())? + .as_ref() + .map(|pending| pending.finish_url.clone()) + .ok_or_else(|| "No browser auth request is pending".to_string()) +} + fn start_loopback_auth_server(on_complete: F) -> Result where F: FnOnce(Result) + Send + 'static, @@ -549,12 +710,12 @@ fn open_system_browser(url: &str) -> Result<(), String> { } fn verify_browser_state(state: &str) -> Result<(), String> { - let mut pending = pending_browser_state() + let mut pending = pending_browser_auth() .lock() .map_err(|_| "Could not lock browser auth state".to_string())?; - match pending.as_deref() { - Some(expected) if expected == state => { + match pending.as_ref() { + Some(expected) if expected.state == state => { *pending = None; Ok(()) } @@ -589,14 +750,14 @@ mod tests { use super::*; #[test] - fn browser_auth_url_uses_desktop_login_handoff_flow() { + fn browser_auth_url_uses_desktop_finish_callback_flow() { std::env::remove_var("SUPERMEMORY_DESKTOP_WEB_URL"); std::env::remove_var("SUPERMEMORY_DESKTOP_API_URL"); let url = url::Url::parse(&build_browser_login_url("state-123", 49876).unwrap()).unwrap(); assert_eq!( url.as_str().split('?').next().unwrap(), - "https://app.supermemory.ai/auth/desktop" + "https://app.supermemory.ai/api/auth/desktop/callback" ); assert_eq!( url.query_pairs() @@ -605,6 +766,13 @@ mod tests { .1, "Supermemory Desktop" ); + assert_eq!( + url.query_pairs() + .find(|(key, _)| key == "api_url") + .unwrap() + .1, + DEFAULT_BROWSER_API_URL + ); assert!(url.query_pairs().all(|(key, _)| key != "client")); let callback = url diff --git a/apps/desktop/src-tauri/src/lib.rs b/apps/desktop/src-tauri/src/lib.rs index 98cbc8b5..22b944a7 100644 --- a/apps/desktop/src-tauri/src/lib.rs +++ b/apps/desktop/src-tauri/src/lib.rs @@ -59,6 +59,43 @@ fn auth_begin_browser(app: tauri::AppHandle) -> Result { }) } +#[tauri::command] +async fn auth_begin_social(app: tauri::AppHandle, provider: String) -> Result { + let app_for_callback = app.clone(); + auth::begin_social_auth(provider, move |result| match result { + Ok(event) => { + let _ = app_for_callback.emit("auth:changed", event); + focus_main_window(&app_for_callback); + } + Err(error) => { + let _ = app_for_callback.emit("auth:error", error); + focus_main_window(&app_for_callback); + } + }) + .await +} + +#[tauri::command] +async fn auth_send_magic_link(app: tauri::AppHandle, email: String) -> Result<(), String> { + let app_for_callback = app.clone(); + auth::send_magic_link(email, move |result| match result { + Ok(event) => { + let _ = app_for_callback.emit("auth:changed", event); + focus_main_window(&app_for_callback); + } + Err(error) => { + let _ = app_for_callback.emit("auth:error", error); + focus_main_window(&app_for_callback); + } + }) + .await +} + +#[tauri::command] +fn auth_verify_magic_link_token(token: String) -> Result { + auth::verify_magic_link_token(token) +} + #[tauri::command] async fn auth_whoami() -> Result { auth::whoami().await @@ -191,6 +228,9 @@ pub fn run() { auth_get_token, auth_clear, auth_begin_browser, + auth_begin_social, + auth_send_magic_link, + auth_verify_magic_link_token, auth_whoami, spotlight_show, spotlight_hide, diff --git a/apps/web/app/api/auth/desktop/callback/route.ts b/apps/web/app/api/auth/desktop/callback/route.ts new file mode 100644 index 00000000..f1c83647 --- /dev/null +++ b/apps/web/app/api/auth/desktop/callback/route.ts @@ -0,0 +1,115 @@ +import { NextResponse, type NextRequest } from "next/server" + +const DEFAULT_API_URL = + process.env.NEXT_PUBLIC_BACKEND_URL ?? "https://api.supermemory.ai" + +function isValidDesktopCallback(callback: string): boolean { + try { + const url = new URL(callback) + const isLoopback = + (url.hostname === "localhost" || url.hostname === "127.0.0.1") && + url.protocol === "http:" && + url.pathname === "/callback" && + url.searchParams.has("state") + if (isLoopback) return true + + return ( + url.protocol === "supermemory:" && + url.hostname === "auth-callback" && + url.searchParams.has("state") + ) + } catch { + return false + } +} + +function isValidApiUrl(apiUrl: string): boolean { + try { + const url = new URL(apiUrl) + return url.protocol === "http:" || url.protocol === "https:" + } catch { + return false + } +} + +function redirectToCallback(callback: string, params: Record) { + const redirectUrl = new URL(callback) + for (const [key, value] of Object.entries(params)) { + redirectUrl.searchParams.set(key, value) + } + return NextResponse.redirect(redirectUrl) +} + +export async function GET(request: NextRequest) { + const url = new URL(request.url) + const callback = url.searchParams.get("callback") + if (!callback || !isValidDesktopCallback(callback)) { + return NextResponse.json( + { message: "Invalid desktop callback URL" }, + { status: 400 }, + ) + } + + if (url.searchParams.has("error")) { + return redirectToCallback(callback, { + error: url.searchParams.get("error") || "auth_failed", + }) + } + + const apiUrl = url.searchParams.get("api_url") || DEFAULT_API_URL + if (!isValidApiUrl(apiUrl)) { + return redirectToCallback(callback, { error: "invalid_api_url" }) + } + + const cookie = request.headers.get("cookie") + if (!cookie) { + return redirectToCallback(callback, { error: "missing_session" }) + } + + const deviceInfo = { + hostname: url.searchParams.get("hostname") || "Supermemory Desktop", + os: url.searchParams.get("os") || "desktop", + cwd: url.searchParams.get("cwd") || "", + cliVersion: url.searchParams.get("version") || "desktop", + } + + try { + const res = await fetch(`${apiUrl.replace(/\/+$/, "")}/v3/auth/agent-key`, { + method: "POST", + headers: { + "Content-Type": "application/json", + Cookie: cookie, + "X-App-Source": "desktop", + }, + body: JSON.stringify({ + name: "Supermemory Desktop", + permission: "write", + deviceInfo, + }), + }) + + if (!res.ok) { + const data = (await res.json().catch(() => ({}))) as { + message?: string + } + return redirectToCallback(callback, { + error: data.message ?? "desktop_key_failed", + }) + } + + const data = (await res.json()) as { key?: string } + if (!data.key) { + return redirectToCallback(callback, { error: "missing_desktop_key" }) + } + + return redirectToCallback(callback, { + apikey: data.key, + api_url: apiUrl, + }) + } catch (err) { + return redirectToCallback(callback, { + error: + err instanceof Error ? err.message : "desktop_auth_callback_failed", + }) + } +} diff --git a/apps/web/app/auth/desktop/page.tsx b/apps/web/app/auth/desktop/page.tsx deleted file mode 100644 index adc8e955..00000000 --- a/apps/web/app/auth/desktop/page.tsx +++ /dev/null @@ -1,196 +0,0 @@ -"use client" - -import { useAuth } from "@lib/auth-context" -import { useSession } from "@lib/auth" -import { Loader2, XCircle } from "lucide-react" -import { useRouter, useSearchParams } from "next/navigation" -import { Suspense, useEffect, useMemo, useRef, useState } from "react" -import { PENDING_CONNECT_URL_KEY } from "@/lib/constants" - -const API_URL = - process.env.NEXT_PUBLIC_BACKEND_URL ?? "https://api.supermemory.ai" - -type Status = "loading" | "creating" | "success" | "error" - -function isValidDesktopCallback(callback: string): boolean { - try { - const url = new URL(callback) - const isLoopback = - (url.hostname === "localhost" || url.hostname === "127.0.0.1") && - url.protocol === "http:" && - url.pathname === "/callback" && - url.searchParams.has("state") - if (isLoopback) return true - - return ( - url.protocol === "supermemory:" && - url.hostname === "auth-callback" && - url.searchParams.has("state") - ) - } catch { - return false - } -} - -function DesktopAuthContent() { - const router = useRouter() - const params = useSearchParams() - const { data: session, isPending } = useSession() - const { org, organizations, isRestoring } = useAuth() - const [status, setStatus] = useState("loading") - const [error, setError] = useState(null) - const hasStarted = useRef(false) - - const callback = params.get("callback") - const desktopHostname = params.get("hostname") || "Supermemory Desktop" - const desktopOs = params.get("os") || "desktop" - const desktopCwd = params.get("cwd") || "" - const desktopVersion = params.get("version") || "desktop" - const callbackIsValid = useMemo( - () => (callback ? isValidDesktopCallback(callback) : false), - [callback], - ) - - const shouldRedirectToOnboarding = - !isPending && - !isRestoring && - !!session && - Array.isArray(organizations) && - organizations.length === 0 - - useEffect(() => { - if (isPending || isRestoring) return - if (!session) return - if (organizations === null) return - if (organizations.length > 0) return - - try { - sessionStorage.setItem(PENDING_CONNECT_URL_KEY, window.location.href) - } catch (err) { - console.warn("Failed to store pending desktop auth URL", err) - } - router.replace("/onboarding") - }, [isPending, isRestoring, session, organizations, router]) - - useEffect(() => { - if (isPending || isRestoring || shouldRedirectToOnboarding) return - if (hasStarted.current) return - - if (!callback) { - setStatus("error") - setError("Missing desktop callback URL.") - return - } - if (!callbackIsValid) { - setStatus("error") - setError("Invalid desktop callback URL.") - return - } - if (!session || !org) { - setStatus("error") - setError("Your account is not fully set up yet.") - return - } - - hasStarted.current = true - const callbackUrl = callback - - async function finishDesktopAuth() { - try { - setStatus("creating") - const res = await fetch(`${API_URL}/v3/auth/agent-key`, { - method: "POST", - credentials: "include", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - name: "Supermemory Desktop", - permission: "write", - deviceInfo: { - hostname: desktopHostname, - os: desktopOs, - cwd: desktopCwd, - cliVersion: desktopVersion, - }, - }), - }) - - if (!res.ok) { - const data = (await res.json().catch(() => ({}))) as { - message?: string - } - throw new Error(data.message ?? "Failed to create desktop key") - } - - const data = (await res.json()) as { key: string } - setStatus("success") - - const redirectUrl = new URL(callbackUrl) - redirectUrl.searchParams.set("apikey", data.key) - redirectUrl.searchParams.set("api_url", API_URL) - window.location.href = redirectUrl.toString() - } catch (err) { - setStatus("error") - setError( - err instanceof Error ? err.message : "Failed to finish desktop login", - ) - } - } - - void finishDesktopAuth() - }, [ - callback, - callbackIsValid, - desktopCwd, - desktopHostname, - desktopOs, - desktopVersion, - isPending, - isRestoring, - org, - session, - shouldRedirectToOnboarding, - ]) - - if (status === "error") { - return ( -

-
- -
-

- Desktop login failed -

-

{error}

-
-
-
- ) - } - - return ( -
-
- -

- {status === "success" - ? "Returning to Supermemory Desktop..." - : "Finishing desktop login..."} -

-
-
- ) -} - -export default function DesktopAuthPage() { - return ( - - -
- } - > - - - ) -}