feat(settings): show Orca Relay sign-in status

Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
Jinwoo-H
2026-07-13 04:50:16 -04:00
co-authored by Orca
parent 144f947301
commit e212216568
13 changed files with 294 additions and 2 deletions
+1
View File
@@ -2202,6 +2202,7 @@ app.whenReady().then(async () => {
runtimeRpc,
onStatus: (status) => {
desktopRelayStatus = status
mainWindow?.webContents.send('mobile:relayStatusChanged', status)
}
})
desktopRelayService = relayService
+6
View File
@@ -293,4 +293,10 @@ describe('registerMobileHandlers', () => {
).resolves.toEqual({ revoked: true })
expect(revokeMobileDevice).toHaveBeenCalledWith('mobile-1')
})
it('reports the current relay broker status without exposing a toggle', () => {
registerMobileHandlers({} as never, { getRelayStatus: () => 'registered' })
expect(handlers.get('mobile:getRelayStatus')?.()).toEqual({ status: 'registered' })
})
})
+3
View File
@@ -17,6 +17,7 @@ import type {
} from '../shared/local-log-tail-types'
import type { ReadClipboardTextOptions } from '../shared/clipboard-text'
import type { AppIdentity } from '../shared/app-identity'
import type { MobileRelayStatus } from '../shared/mobile-relay-status'
import type {
CreateLocalOrcaProfileArgs,
CreateLocalOrcaProfileResult,
@@ -3167,6 +3168,8 @@ export type PreloadApi = {
listRuntimeAccessGrants: () => Promise<{ grants: RuntimeAccessGrant[] }>
revokeRuntimeAccess: (args: { deviceId: string }) => Promise<{ revoked: boolean }>
isWebSocketReady: () => Promise<{ ready: boolean; endpoint: string | null }>
getRelayStatus: () => Promise<{ status: MobileRelayStatus }>
onRelayStatusChanged: (callback: (status: MobileRelayStatus) => void) => () => void
}
speech: {
getCatalog: () => Promise<SpeechModelManifest[]>
+12 -1
View File
@@ -12,6 +12,7 @@ import type { TerminalPaneSplitSource } from '../shared/feature-education-teleme
import type { ProjectExecutionRuntimeResolution } from '../shared/project-execution-runtime'
import type { StartupCommandDelivery } from '../shared/codex-startup-delivery'
import type { SleepingAgentLaunchConfig } from '../shared/agent-session-resume'
import type { MobileRelayStatus } from '../shared/mobile-relay-status'
import type {
BaseRefSearchResult,
BaseRefDefaultResult,
@@ -4295,7 +4296,17 @@ const api = {
ipcRenderer.invoke('mobile:revokeRuntimeAccess', args),
isWebSocketReady: (): Promise<{ ready: boolean; endpoint: string | null }> =>
ipcRenderer.invoke('mobile:isWebSocketReady')
ipcRenderer.invoke('mobile:isWebSocketReady'),
getRelayStatus: (): Promise<{ status: MobileRelayStatus }> =>
ipcRenderer.invoke('mobile:getRelayStatus'),
onRelayStatusChanged: (callback: (status: MobileRelayStatus) => void): (() => void) => {
const listener = (_event: Electron.IpcRendererEvent, status: MobileRelayStatus) =>
callback(status)
ipcRenderer.on('mobile:relayStatusChanged', listener)
return () => ipcRenderer.removeListener('mobile:relayStatusChanged', listener)
}
},
agentStatus: {
@@ -12,6 +12,7 @@ import { MobilePairingQrSection } from './MobilePairingQrSection'
import { MobilePairedDevicesSection, type PairedDevice } from './MobilePairedDevicesSection'
import { MobileAutoRestoreFitSection } from './MobileAutoRestoreFitSection'
import { WindowsFirewallNotice } from '../mobile/WindowsFirewallNotice'
import { MobileRelayStatusSection } from './MobileRelayStatusSection'
import { translate } from '@/i18n/i18n'
export { getMobilePaneSearchEntries } from './mobile-pane-search'
@@ -161,6 +162,8 @@ export function MobilePane(): React.JSX.Element {
return (
<div className="space-y-6">
<MobileRelayStatusSection />
<MobileNetworkInterfaceSection
networkInterfaces={networkInterfaces}
selectedAddress={selectedAddress}
@@ -0,0 +1,88 @@
// @vitest-environment happy-dom
import '@testing-library/jest-dom/vitest'
import { cleanup, render, screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import type { MobileRelayStatus } from '../../../../shared/mobile-relay-status'
import type { OrcaProfileAuthStatus } from '../../../../shared/orca-profiles'
import { MobileRelayStatusSection } from './MobileRelayStatusSection'
type MobileRelayStoreState = {
orcaProfileAuthStatus: OrcaProfileAuthStatus | null
orcaProfileConnecting: boolean
connectCurrentOrcaProfile: () => Promise<null>
}
const mocks = vi.hoisted(() => ({
state: {} as MobileRelayStoreState
}))
vi.mock('@/store', () => ({
useAppStore: (selector: (state: MobileRelayStoreState) => unknown) => selector(mocks.state)
}))
describe('MobileRelayStatusSection', () => {
let statusListener: ((status: MobileRelayStatus) => void) | null
const connect = vi.fn().mockResolvedValue(null)
beforeEach(() => {
statusListener = null
connect.mockClear()
Object.defineProperty(window, 'api', {
configurable: true,
value: {
mobile: {
getRelayStatus: vi.fn().mockResolvedValue({ status: 'registered' }),
onRelayStatusChanged: vi.fn((listener: (status: MobileRelayStatus) => void) => {
statusListener = listener
return vi.fn()
})
}
}
})
mocks.state = {
orcaProfileAuthStatus: {
activeProfileId: 'profile-1',
configured: true,
state: 'local',
persistence: 'none'
},
orcaProfileConnecting: false,
connectCurrentOrcaProfile: connect
}
})
afterEach(() => cleanup())
it('keeps direct pairing available while offering desktop sign-in', async () => {
const user = userEvent.setup()
render(<MobileRelayStatusSection />)
expect(
screen.getByText('LAN and Tailscale pairing still work without an account.')
).toBeVisible()
await user.click(screen.getByRole('button', { name: 'Sign in' }))
expect(connect).toHaveBeenCalledOnce()
})
it('shows live automatic relay status for a signed-in desktop', async () => {
mocks.state = {
orcaProfileAuthStatus: {
activeProfileId: 'profile-1',
configured: true,
state: 'connected',
persistence: 'encrypted'
},
orcaProfileConnecting: false,
connectCurrentOrcaProfile: connect
}
render(<MobileRelayStatusSection />)
await waitFor(() => expect(screen.getByText('Registered')).toBeVisible())
statusListener?.('offline')
await waitFor(() => expect(screen.getByText('Offline')).toBeVisible())
expect(screen.queryByRole('button', { name: 'Sign in' })).not.toBeInTheDocument()
})
})
@@ -0,0 +1,112 @@
import { useEffect, useState } from 'react'
import { Loader2 } from 'lucide-react'
import { Badge } from '../ui/badge'
import { Button } from '../ui/button'
import { translate } from '@/i18n/i18n'
import { useAppStore } from '@/store'
import type { MobileRelayStatus } from '../../../../shared/mobile-relay-status'
function relayStatusLabel(status: MobileRelayStatus): string {
if (status === 'registered') {
return translate('auto.components.settings.MobileRelayStatusSection.registered', 'Registered')
}
if (status === 'connecting') {
return translate('auto.components.settings.MobileRelayStatusSection.connecting', 'Connecting')
}
if (status === 'draining') {
return translate(
'auto.components.settings.MobileRelayStatusSection.reconnecting',
'Reconnecting'
)
}
return translate('auto.components.settings.MobileRelayStatusSection.offline', 'Offline')
}
export function MobileRelayStatusSection(): React.JSX.Element {
const authStatus = useAppStore((state) => state.orcaProfileAuthStatus)
const connecting = useAppStore((state) => state.orcaProfileConnecting)
const connect = useAppStore((state) => state.connectCurrentOrcaProfile)
const [relayStatus, setRelayStatus] = useState<MobileRelayStatus>('offline')
const signedIn = authStatus?.state === 'connected'
const configured = authStatus?.configured !== false
useEffect(() => {
let receivedEvent = false
let active = true
const unsubscribe = window.api.mobile.onRelayStatusChanged((status) => {
receivedEvent = true
if (active) {
setRelayStatus(status)
}
})
void window.api.mobile
.getRelayStatus()
.then(({ status }) => {
if (active && !receivedEvent) {
setRelayStatus(status)
}
})
.catch(() => {})
return () => {
active = false
unsubscribe()
}
}, [])
return (
<section className="space-y-2">
<h3 className="text-sm font-medium">
{translate('auto.components.settings.MobileRelayStatusSection.title', 'Orca Relay')}
</h3>
<div className="flex items-center justify-between gap-4 rounded-lg border border-border/60 px-3 py-2.5">
<div className="min-w-0 space-y-0.5">
<p className="text-sm font-medium">
{signedIn
? translate(
'auto.components.settings.MobileRelayStatusSection.automatic',
'Connect from anywhere automatically'
)
: translate(
'auto.components.settings.MobileRelayStatusSection.signInPrompt',
'Sign in on this desktop to connect from anywhere'
)}
</p>
<p className="text-xs text-muted-foreground">
{signedIn
? translate(
'auto.components.settings.MobileRelayStatusSection.directStillAvailable',
'LAN and Tailscale connections remain available.'
)
: translate(
'auto.components.settings.MobileRelayStatusSection.directNeedsNoAccount',
'LAN and Tailscale pairing still work without an account.'
)}
</p>
</div>
{signedIn ? (
<Badge variant="outline" className="shrink-0">
{relayStatusLabel(relayStatus)}
</Badge>
) : configured ? (
<Button
type="button"
size="sm"
className="w-24 shrink-0"
disabled={connecting}
onClick={() => void connect()}
>
{connecting ? <Loader2 className="animate-spin" /> : null}
{translate('auto.components.settings.MobileRelayStatusSection.signIn', 'Sign in')}
</Button>
) : (
<Badge variant="outline" className="shrink-0">
{translate(
'auto.components.settings.MobileRelayStatusSection.unavailable',
'Unavailable'
)}
</Badge>
)}
</div>
</section>
)
}
+13
View File
@@ -9032,6 +9032,19 @@
"TerminalInteractionSection": {
"567633ff50": "Right-click pastes the clipboard into the terminal. Control-click to open the context menu.",
"c64497148a": "Right-click pastes the clipboard. Control-click opens the context menu."
},
"MobileRelayStatusSection": {
"registered": "Registered",
"connecting": "Connecting",
"reconnecting": "Reconnecting",
"offline": "Offline",
"title": "Orca Relay",
"automatic": "Connect from anywhere automatically",
"signInPrompt": "Sign in on this desktop to connect from anywhere",
"directStillAvailable": "LAN and Tailscale connections remain available.",
"directNeedsNoAccount": "LAN and Tailscale pairing still work without an account.",
"signIn": "Sign in",
"unavailable": "Unavailable"
}
},
"right": {
+13
View File
@@ -9032,6 +9032,19 @@
"TerminalInteractionSection": {
"567633ff50": "Right-click pastes the clipboard into the terminal. Control-click to open the context menu.",
"c64497148a": "Right-click pastes the clipboard. Control-click opens the context menu."
},
"MobileRelayStatusSection": {
"registered": "Registered",
"connecting": "Connecting",
"reconnecting": "Reconnecting",
"offline": "Offline",
"title": "Orca Relay",
"automatic": "Connect from anywhere automatically",
"signInPrompt": "Sign in on this desktop to connect from anywhere",
"directStillAvailable": "LAN and Tailscale connections remain available.",
"directNeedsNoAccount": "LAN and Tailscale pairing still work without an account.",
"signIn": "Sign in",
"unavailable": "Unavailable"
}
},
"right": {
+13
View File
@@ -9032,6 +9032,19 @@
"TerminalInteractionSection": {
"567633ff50": "Right-click pastes the clipboard into the terminal. Control-click to open the context menu.",
"c64497148a": "Right-click pastes the clipboard. Control-click opens the context menu."
},
"MobileRelayStatusSection": {
"registered": "Registered",
"connecting": "Connecting",
"reconnecting": "Reconnecting",
"offline": "Offline",
"title": "Orca Relay",
"automatic": "Connect from anywhere automatically",
"signInPrompt": "Sign in on this desktop to connect from anywhere",
"directStillAvailable": "LAN and Tailscale connections remain available.",
"directNeedsNoAccount": "LAN and Tailscale pairing still work without an account.",
"signIn": "Sign in",
"unavailable": "Unavailable"
}
},
"right": {
+13
View File
@@ -9032,6 +9032,19 @@
"TerminalInteractionSection": {
"567633ff50": "Right-click pastes the clipboard into the terminal. Control-click to open the context menu.",
"c64497148a": "Right-click pastes the clipboard. Control-click opens the context menu."
},
"MobileRelayStatusSection": {
"registered": "Registered",
"connecting": "Connecting",
"reconnecting": "Reconnecting",
"offline": "Offline",
"title": "Orca Relay",
"automatic": "Connect from anywhere automatically",
"signInPrompt": "Sign in on this desktop to connect from anywhere",
"directStillAvailable": "LAN and Tailscale connections remain available.",
"directNeedsNoAccount": "LAN and Tailscale pairing still work without an account.",
"signIn": "Sign in",
"unavailable": "Unavailable"
}
},
"right": {
+13
View File
@@ -9032,6 +9032,19 @@
"TerminalInteractionSection": {
"567633ff50": "Right-click pastes the clipboard into the terminal. Control-click to open the context menu.",
"c64497148a": "Right-click pastes the clipboard. Control-click opens the context menu."
},
"MobileRelayStatusSection": {
"registered": "Registered",
"connecting": "Connecting",
"reconnecting": "Reconnecting",
"offline": "Offline",
"title": "Orca Relay",
"automatic": "Connect from anywhere automatically",
"signInPrompt": "Sign in on this desktop to connect from anywhere",
"directStillAvailable": "LAN and Tailscale connections remain available.",
"directNeedsNoAccount": "LAN and Tailscale pairing still work without an account.",
"signIn": "Sign in",
"unavailable": "Unavailable"
}
},
"right": {
+4 -1
View File
@@ -758,7 +758,10 @@ function createWebPreloadApi(): Partial<PreloadApi> {
revokeDevice: () => Promise.resolve({ revoked: false }),
listRuntimeAccessGrants: () => Promise.resolve({ grants: [] }),
revokeRuntimeAccess: () => Promise.resolve({ revoked: false }),
isWebSocketReady: () => Promise.resolve({ ready: Boolean(activeEnvironment), endpoint: null })
isWebSocketReady: () =>
Promise.resolve({ ready: Boolean(activeEnvironment), endpoint: null }),
getRelayStatus: () => Promise.resolve({ status: 'offline' as const }),
onRelayStatusChanged: () => noopUnsubscribe
},
telemetryTrack: () => Promise.resolve(),
telemetrySetOptIn: () => Promise.resolve(),