Files
orca/mobile/app/connection-log.tsx
Neil dc9a7abfc1 refactor(mobile): group host catalog credentials and descriptors (#40739)
* refactor(mobile): group host catalog credentials descriptors and connection history

* refactor(mobile): import RPC sender and result types from defining owners

* refactor(mobile): colocate push delivery with registration operations

* refactor(mobile): point push recordings at delivery owner
2026-11-10 02:52:14 -08:00

139 lines
5.3 KiB
TypeScript

import { useCallback, useEffect, useMemo, useState, useSyncExternalStore } from 'react'
import { View, Text, Pressable } from 'react-native'
import { useLocalSearchParams, useRouter } from 'expo-router'
import * as Clipboard from 'expo-clipboard'
import { loadHosts } from '../src/host-catalog/host-store'
import { connectionLogStore } from '../src/diagnostics/connection/persisted-connection-log-store'
import { useRpcClientContext } from '../src/transport/connection/client-context'
import { useHostClient } from '../src/transport/connection/host-client-hooks'
import {
useConnectionPathStatus,
useReconnectAttempt
} from '../src/transport/connection/client-context-connection-metrics'
import { useHostStatusGates } from '../src/transport/connection/host-status-gates'
import { ConnectionDiagnosticsScreen } from '../src/diagnostics/connection-diagnostics-screen'
import { createNativeDiagnosticsOperations } from '../src/diagnostics/native-diagnostics-operations'
import {
readHydratedConnectionLog,
resolveDiagnosticsHostId,
type DiagnosticsHostSelection
} from '../src/diagnostics/connection-diagnostics-screen-data'
import { connectionDiagnosticsScreenStyles as styles } from '../src/diagnostics/connection-diagnostics-screen-styles'
import { useMobileColorScheme } from '../src/theme/use-mobile-color-scheme'
import { NATIVE_APP_LOG_ID } from '../src/diagnostics/native-render-log'
import type { ConnectionLogEntry } from '../src/diagnostics/connection/connection-log-types'
import type { HostProfile } from '../src/host-catalog/host-profile'
import { pressedStyle } from '../src/theme/mobile-theme'
// Keep empty snapshot identity stable or useSyncExternalStore rerenders forever.
const EMPTY_ENTRIES: readonly ConnectionLogEntry[] = []
const subscribeAppLog = (listener: () => void) =>
connectionLogStore.subscribe(NATIVE_APP_LOG_ID, listener)
const getAppLog = () => connectionLogStore.get(NATIVE_APP_LOG_ID)
// Acquire client here so opening logs during host failure starts dialing and live logging.
export default function NativeConnectionLogRoute() {
useMobileColorScheme()
const clientContext = useRpcClientContext()
const router = useRouter()
const params = useLocalSearchParams<{ hostId?: string }>()
const routeKey = useMemo(() => ({}), [params.hostId])
const [hosts, setHosts] = useState<HostProfile[]>([])
const [manualSelection, setManualSelection] = useState<DiagnosticsHostSelection | null>(null)
useEffect(() => {
let stale = false
void loadHosts().then((loaded) => {
if (!stale) {
setHosts(loaded)
}
})
return () => {
stale = true
}
}, [])
const selectedId = resolveDiagnosticsHostId(hosts, params.hostId, manualSelection, routeKey)
const selected = hosts.find((host) => host.id === selectedId) ?? null
const { client, state } = useHostClient(selected?.id)
const { desktopAppVersion, statusPending } = useHostStatusGates({
hostId: selected?.id,
client,
connState: state
})
const reconnectAttempts = useReconnectAttempt(selected?.id)
const { activePath, pendingPath } = useConnectionPathStatus(selected?.id)
useEffect(() => {
void readHydratedConnectionLog(connectionLogStore, NATIVE_APP_LOG_ID)
if (selectedId) {
void readHydratedConnectionLog(connectionLogStore, selectedId)
}
}, [selectedId])
const subscribe = useCallback(
(listener: () => void) =>
selectedId ? connectionLogStore.subscribe(selectedId, listener) : () => {},
[selectedId]
)
const getSnapshot = useCallback(
() => (selectedId ? connectionLogStore.get(selectedId) : EMPTY_ENTRIES),
[selectedId]
)
const hostEntries = useSyncExternalStore(subscribe, getSnapshot)
const appEntries = useSyncExternalStore(subscribeAppLog, getAppLog)
const entries = useMemo(
() => [...hostEntries, ...appEntries].sort((a, b) => a.ts - b.ts),
[hostEntries, appEntries]
)
const device = useMemo(
() =>
createNativeDiagnosticsOperations(
selected,
clientContext,
statusPending ? null : desktopAppVersion
),
[selected, clientContext, desktopAppVersion, statusPending]
)
return (
<ConnectionDiagnosticsScreen
device={device}
host={selected}
state={state}
reconnectAttempts={reconnectAttempts}
activePath={activePath}
pendingPath={pendingPath}
entries={entries}
writeClipboard={(report) => Clipboard.setStringAsync(report)}
onBack={() => router.back()}
hostPicker={
hosts.length > 1 ? (
<View style={styles.hostPicker}>
{hosts.map((host) => (
<Pressable
key={host.id}
style={({ pressed }) => [
styles.hostChip,
host.id === selectedId && styles.hostChipActive,
pressed && pressedStyle
]}
onPress={() =>
setManualSelection({ hostId: host.id, requestedHostId: params.hostId, routeKey })
}
>
<Text
style={[styles.hostChipText, host.id === selectedId && styles.hostChipTextActive]}
numberOfLines={1}
>
{host.name}
</Text>
</Pressable>
))}
</View>
) : null
}
/>
)
}