refactor(mobile): drop the dead all-hosts client broadcast

Moving notification ownership to the root owner left subscribeAllHosts with
no consumers. It re-renders every subscriber on any host's open/close, so
leaving it wired is a standing invitation to reintroduce the O(N) fanout this
change removes. Per-host notifyHostState already covers every store mutation.

Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
Jinjing
2026-08-08 14:19:37 -07:00
co-authored by Orca
parent 9def8bc551
commit 97e606be38
2 changed files with 0 additions and 37 deletions
-17
View File
@@ -17,10 +17,8 @@ import { decrementPendingAcquisition } from './host-client-acquisition-count'
import { subscribeHostClientRevival } from './host-client-revival-scheduler'
import {
clientActivePath,
notifyAllHostListeners,
notifyHostStateListeners,
primeHostProfiles,
subscribeAllHostListener,
subscribeHostStateListener,
type CloseEntryOptions
} from './host-client-context-state'
@@ -52,7 +50,6 @@ export type RpcClientContextValue = {
getActivePath: (hostId: string) => MobileConnectionPath
getClient: (hostId: string) => RpcClient | null
subscribeHostState: (hostId: string, listener: (state: ConnectionState) => void) => () => void
subscribeAllHosts: (listener: () => void) => () => void
// Why: lets the home screen feed already-loaded HostProfiles so we don't pay loadHosts() latency twice.
primeHosts: (hosts: HostProfile[]) => void
}
@@ -63,7 +60,6 @@ export function RpcClientProvider({ children }: { children: ReactNode }) {
// Why: entries in a ref so state changes don't re-render the whole tree; propagation goes through per-host listener Sets.
const storeRef = useRef<Map<string, StoreEntry>>(new Map())
const stateListenersRef = useRef<Map<string, Set<(state: ConnectionState) => void>>>(new Map())
const allHostsListenersRef = useRef<Set<() => void>>(new Set())
// Pending opens keyed by hostId so two acquire() callers in the same render don't race the host lookup.
const pendingOpensRef = useRef(new HostClientOpenRegistry())
@@ -74,7 +70,6 @@ export function RpcClientProvider({ children }: { children: ReactNode }) {
const notifyHostState = (hostId: string, state: ConnectionState) =>
notifyHostStateListeners(stateListenersRef.current, hostId, state)
const notifyAllHosts = () => notifyAllHostListeners(allHostsListenersRef.current)
const closeEntry = useCallback((hostId: string, options: CloseEntryOptions) => {
const entry = storeRef.current.get(hostId)
@@ -92,7 +87,6 @@ export function RpcClientProvider({ children }: { children: ReactNode }) {
storeRef.current.delete(hostId)
entry?.client.close()
notifyHostState(hostId, 'disconnected')
notifyAllHosts()
}, [])
const closeHost = useCallback(
@@ -126,13 +120,11 @@ export function RpcClientProvider({ children }: { children: ReactNode }) {
} catch {
// Why: cold-start Keychain failure (iOS mid-unlock / Android Keystore race); surface 'disconnected' so the user can Reconnect.
notifyHostState(hostId, 'disconnected')
notifyAllHosts()
return null
}
if (!host) {
// Why: silent return leaves screens on a permanent spinner (STA-1511); surface 'disconnected' so they show a retry affordance.
notifyHostState(hostId, 'disconnected')
notifyAllHosts()
return null
}
}
@@ -153,7 +145,6 @@ export function RpcClientProvider({ children }: { children: ReactNode }) {
} catch {
// Why: openHostLogicalClient can throw synchronously (bad public key / invalid URL); notify so the UI leaves 'connecting'.
notifyHostState(hostId, 'disconnected')
notifyAllHosts()
return null
}
const unsubState = client.onStateChange((state) => {
@@ -173,7 +164,6 @@ export function RpcClientProvider({ children }: { children: ReactNode }) {
pendingAcquisitionsRef.current.delete(hostId)
storeRef.current.set(hostId, entry)
notifyHostState(hostId, entry.state)
notifyAllHosts()
return entry
} finally {
pendingOpensRef.current.deleteIfCurrent(hostId, pendingOpen)
@@ -312,11 +302,6 @@ export function RpcClientProvider({ children }: { children: ReactNode }) {
[]
)
const subscribeAllHosts = useCallback(
(listener: () => void) => subscribeAllHostListener(allHostsListenersRef.current, listener),
[]
)
// Close all clients on provider unmount. Empty deps: [closeEntry] would let Fast Refresh tear down all live sockets.
// eslint-disable-next-line react-hooks/exhaustive-deps
useEffect(() => {
@@ -352,7 +337,6 @@ export function RpcClientProvider({ children }: { children: ReactNode }) {
getActivePath,
getClient,
subscribeHostState,
subscribeAllHosts,
primeHosts
}),
[
@@ -369,7 +353,6 @@ export function RpcClientProvider({ children }: { children: ReactNode }) {
getActivePath,
getClient,
subscribeHostState,
subscribeAllHosts,
primeHosts
]
)
@@ -17,12 +17,6 @@ export function notifyHostStateListeners(
}
}
export function notifyAllHostListeners(listeners: Set<() => void>): void {
for (const listener of listeners) {
listener()
}
}
export function subscribeHostStateListener(
listeners: Map<string, Set<(state: ConnectionState) => void>>,
hostId: string,
@@ -42,20 +36,6 @@ export function subscribeHostStateListener(
}
}
export function subscribeAllHostListener(
listeners: Set<() => void>,
listener: () => void
): () => void {
listeners.add(listener)
return () => listeners.delete(listener)
}
export function listHostClients(
entries: ReadonlyMap<string, { client: RpcClient }>
): { hostId: string; client: RpcClient }[] {
return [...entries].map(([hostId, entry]) => ({ hostId, client: entry.client }))
}
export function primeHostProfiles(cache: Map<string, HostProfile>, hosts: HostProfile[]): void {
for (const host of hosts) {
cache.set(host.id, host)