diff --git a/frontend/src/desktop/desktopBridge.ts b/frontend/src/desktop/desktopBridge.ts new file mode 100644 index 000000000..780ab13e9 --- /dev/null +++ b/frontend/src/desktop/desktopBridge.ts @@ -0,0 +1,10 @@ +import { connectionModeService } from '@app/services/connectionModeService'; +import { tauriBackendService } from '@app/services/tauriBackendService'; + +/** + * Desktop implementation for completing self-hosted deep link SSO. + */ +export async function completeSelfHostedDeepLink(serverUrl: string): Promise { + await connectionModeService.switchToSelfHosted({ url: serverUrl }); + await tauriBackendService.initializeExternalBackend(); +} diff --git a/frontend/src/proprietary/auth/UseSession.tsx b/frontend/src/proprietary/auth/UseSession.tsx index 3e1cddd66..d67453fc8 100644 --- a/frontend/src/proprietary/auth/UseSession.tsx +++ b/frontend/src/proprietary/auth/UseSession.tsx @@ -109,7 +109,7 @@ export function AuthProvider({ children }: { children: ReactNode }) { if (typeof window !== 'undefined' && (window as any).__TAURI__ && window.location.pathname.startsWith('/login')) { try { const { authService } = await import('@app/services/authService'); - await authService.logout(); + await authService.localClearAuth(); } catch (desktopErr) { console.warn('[Auth] Failed to clear desktop auth state on login page init', desktopErr); } diff --git a/frontend/src/proprietary/desktopBridge.ts b/frontend/src/proprietary/desktopBridge.ts new file mode 100644 index 000000000..b8edd9fa6 --- /dev/null +++ b/frontend/src/proprietary/desktopBridge.ts @@ -0,0 +1,25 @@ +import { invoke, isTauri } from '@tauri-apps/api/core'; + +/** + * Desktop bridge used by self-hosted SSO deep links. + * Uses direct Tauri commands so we don't rely on desktop-specific path aliases. + */ +export async function completeSelfHostedDeepLink(serverUrl: string): Promise { + if (!isTauri()) return; + + const normalizedUrl = serverUrl.replace(/\/$/, ''); + + // Persist server config for desktop backend + try { + await invoke('set_connection_mode', { mode: 'selfhosted', serverConfig: { url: normalizedUrl } }); + } catch (err) { + console.warn('[DesktopBridge] Failed to set connection mode', err); + } + + // Ensure backend is started/pointing at the provided server + try { + await invoke('start_backend', { backendUrl: normalizedUrl }); + } catch (err) { + console.warn('[DesktopBridge] Failed to start backend', err); + } +} diff --git a/frontend/src/proprietary/routes/AuthCallback.tsx b/frontend/src/proprietary/routes/AuthCallback.tsx index 0cd2efaa4..154322a9a 100644 --- a/frontend/src/proprietary/routes/AuthCallback.tsx +++ b/frontend/src/proprietary/routes/AuthCallback.tsx @@ -82,12 +82,8 @@ export default function AuthCallback() { const parsed = JSON.parse(pending) as { serverUrl?: string } | null; if (parsed?.serverUrl) { try { - // Lazy-load desktop services only in Tauri runtime; TypeScript ignores missing types - // and Vite ignores analysis for these optional imports. - const { connectionModeService } = await import(/* @vite-ignore */ '../../desktop/services/connectionModeService'); - const { tauriBackendService } = await import(/* @vite-ignore */ '../../desktop/services/tauriBackendService'); - await connectionModeService.switchToSelfHosted({ url: parsed.serverUrl }); - await tauriBackendService.initializeExternalBackend(); + const { completeSelfHostedDeepLink } = await import('../desktopBridge'); + await completeSelfHostedDeepLink(parsed.serverUrl); } catch (innerErr) { console.error('[AuthCallback] Desktop fallback services unavailable', innerErr); } diff --git a/frontend/src/proprietary/services/authService.ts b/frontend/src/proprietary/services/authService.ts new file mode 100644 index 000000000..9d282f44e --- /dev/null +++ b/frontend/src/proprietary/services/authService.ts @@ -0,0 +1,16 @@ +/** + * Proprietary (web/SaaS) stub for the desktop auth service. + * Desktop build overrides @app/services/authService via tsconfig path order. + */ +export const authService = { + async localClearAuth(): Promise { + try { + localStorage.removeItem('stirling_jwt'); + } catch { + // ignore + } + }, + async logout(): Promise { + await this.localClearAuth(); + }, +}; diff --git a/frontend/src/proprietary/stubs/authServiceStub.ts b/frontend/src/proprietary/stubs/authServiceStub.ts new file mode 100644 index 000000000..b58d1f1f5 --- /dev/null +++ b/frontend/src/proprietary/stubs/authServiceStub.ts @@ -0,0 +1,10 @@ +/** + * Runtime-safe stub for desktop-only auth service. + * Proprietary (web/SaaS) build must not ship Tauri deps, so we expose just the + * minimal surface that desktop entry points dynamically import. + */ +export const authService = { + async localClearAuth(): Promise { + // noop for web builds + }, +}; diff --git a/frontend/src/proprietary/types/desktop-stubs.d.ts b/frontend/src/proprietary/types/desktop-stubs.d.ts new file mode 100644 index 000000000..7756b9d8f --- /dev/null +++ b/frontend/src/proprietary/types/desktop-stubs.d.ts @@ -0,0 +1,11 @@ +// Desktop bridge stub for proprietary build +declare module '@desktop/bridge' { + export function completeSelfHostedDeepLink(serverUrl: string): Promise; +} + +// Desktop authService stub for proprietary build (no-op) +declare module '@app/services/authService' { + export const authService: { + localClearAuth: () => Promise; + }; +}