Files
orca/src/preload/api/runtime-bridge.ts
T
Neil 401664298f fix(preload): make a dropped bridge key a compile error
The split silently dropped jira.searchUsers and
runtimeEnvironments.retryControlConnection. Neither failed typecheck: the bridge
modules carried no satisfies annotation and the composed api object was
unannotated, so a missing key was only a runtime TypeError in the renderer.

Annotates each module against PreloadApi, the type window.api is already
declared as, so the contract supplies the shape rather than a parallel copy.
Deleting jira.searchUsers now fails with TS2741 naming the key.

Turning this on surfaced 106 places where a bridge locally annotated
Promise<unknown> or unknown[] over a contract that declares concrete types --
the bridge was erasing types the renderer relied on. Those annotations are gone.

Also exposes app.awaitBeforeUnloadCheckpoint, which was declared and called but
never actually on the bridge, so the lazy-chunk recovery reload optional-chained
to a no-op and navigated without joining the checkpoint. The missing key was
caught by the new annotation rather than by hand.
2026-09-01 18:24:49 -07:00

146 lines
5.8 KiB
TypeScript

import { ipcRenderer } from 'electron'
import type { ClientHostedBrowserRowsEvent } from '../../shared/client-hosted-browser-rows'
import type {
RuntimeBrowserDriverState,
RuntimeRendererSyncWindowGraph,
RuntimeStatus,
RuntimeSyncWindowGraphResult,
RuntimeTerminalDriverState
} from '../../shared/runtime-types'
import type { RuntimeRpcResponse } from '../../shared/runtime-rpc-envelope'
import type { RuntimeEnvironmentSubscriptionHandle } from '../runtime-environment-subscriptions'
import type { PreloadApi } from '../api-types'
export const runtimeApi = {
syncWindowGraph: (graph: RuntimeRendererSyncWindowGraph): Promise<RuntimeSyncWindowGraphResult> =>
ipcRenderer.invoke('runtime:syncWindowGraph', graph),
getStatus: (): Promise<RuntimeStatus> => ipcRenderer.invoke('runtime:getStatus'),
call: (args: { method: string; params?: unknown }): Promise<RuntimeRpcResponse<unknown>> =>
ipcRenderer.invoke('runtime:call', args),
subscribe: async (
args: { method: string; params?: unknown },
callback: (response: RuntimeRpcResponse<unknown>) => void
): Promise<RuntimeEnvironmentSubscriptionHandle> => {
const subscriptionId = `desktop-${crypto.randomUUID()}`
const channel = `runtime:subscription:${subscriptionId}`
const listener = (_event: Electron.IpcRendererEvent, response: RuntimeRpcResponse<unknown>) =>
callback(response)
ipcRenderer.on(channel, listener)
try {
await ipcRenderer.invoke('runtime:subscribe', { subscriptionId, ...args })
} catch (error) {
ipcRenderer.removeListener(channel, listener)
throw error
}
return {
unsubscribe: () => {
ipcRenderer.removeListener(channel, listener)
ipcRenderer.send('runtime:unsubscribe', { subscriptionId })
},
sendBinary: () => {
throw new Error('Local runtime subscriptions do not accept binary input')
}
}
},
getTerminalFitOverrides: (): Promise<
{ ptyId: string; mode: 'mobile-fit' | 'remote-desktop-fit'; cols: number; rows: number }[]
> => ipcRenderer.invoke('runtime:getTerminalFitOverrides'),
getTerminalDrivers: (): Promise<
{
ptyId: string
driver: RuntimeTerminalDriverState
}[]
> => ipcRenderer.invoke('runtime:getTerminalDrivers'),
getBrowserDrivers: (): Promise<
{
browserPageId: string
driver: RuntimeBrowserDriverState
}[]
> => ipcRenderer.invoke('runtime:getBrowserDrivers'),
getBrowserRemoteViewerPages: (): Promise<string[]> =>
ipcRenderer.invoke('runtime:getBrowserRemoteViewerPages'),
getClientHostedBrowserRows: (): Promise<ClientHostedBrowserRowsEvent[]> =>
ipcRenderer.invoke('runtime:getClientHostedBrowserRows'),
restoreTerminalFit: (ptyId: string): Promise<{ restored: boolean }> =>
ipcRenderer.invoke('runtime:restoreTerminalFit', { ptyId }),
reclaimBrowserForDesktop: (browserPageId: string): Promise<{ reclaimed: boolean }> =>
ipcRenderer.invoke('runtime:reclaimBrowserForDesktop', { browserPageId }),
onTerminalFitOverrideChanged: (
callback: (event: {
ptyId: string
mode: 'mobile-fit' | 'remote-desktop-fit' | 'desktop-fit'
cols: number
rows: number
}) => void
): (() => void) => {
const listener = (
_event: Electron.IpcRendererEvent,
data: {
ptyId: string
mode: 'mobile-fit' | 'remote-desktop-fit' | 'desktop-fit'
cols: number
rows: number
}
) => callback(data)
ipcRenderer.on('runtime:terminalFitOverrideChanged', listener)
return () => ipcRenderer.removeListener('runtime:terminalFitOverrideChanged', listener)
},
onTerminalDriverChanged: (
callback: (event: { ptyId: string; driver: RuntimeTerminalDriverState }) => void
): (() => void) => {
const listener = (
_event: Electron.IpcRendererEvent,
data: {
ptyId: string
driver: RuntimeTerminalDriverState
}
) => callback(data)
ipcRenderer.on('runtime:terminalDriverChanged', listener)
return () => ipcRenderer.removeListener('runtime:terminalDriverChanged', listener)
},
onNativeChatLaunchDraftResolved: (
callback: (event: { tabId: string; text: string; createdAt: number }) => void
): (() => void) => {
const listener = (
_event: Electron.IpcRendererEvent,
data: { tabId: string; text: string; createdAt: number }
) => callback(data)
ipcRenderer.on('runtime:nativeChatLaunchDraftResolved', listener)
return () => ipcRenderer.removeListener('runtime:nativeChatLaunchDraftResolved', listener)
},
onBrowserDriverChanged: (
callback: (event: { browserPageId: string; driver: RuntimeBrowserDriverState }) => void
): (() => void) => {
const listener = (
_event: Electron.IpcRendererEvent,
data: {
browserPageId: string
driver: RuntimeBrowserDriverState
}
) => callback(data)
ipcRenderer.on('runtime:browserDriverChanged', listener)
return () => ipcRenderer.removeListener('runtime:browserDriverChanged', listener)
},
onBrowserRemoteViewersChanged: (
callback: (event: { browserPageId: string; hasRemoteViewers: boolean }) => void
): (() => void) => {
const listener = (
_event: Electron.IpcRendererEvent,
data: {
browserPageId: string
hasRemoteViewers: boolean
}
) => callback(data)
ipcRenderer.on('runtime:browserRemoteViewersChanged', listener)
return () => ipcRenderer.removeListener('runtime:browserRemoteViewersChanged', listener)
},
onClientHostedBrowserRowsChanged: (
callback: (event: ClientHostedBrowserRowsEvent) => void
): (() => void) => {
const listener = (_event: Electron.IpcRendererEvent, data: ClientHostedBrowserRowsEvent) =>
callback(data)
ipcRenderer.on('runtime:clientHostedBrowserRowsChanged', listener)
return () => ipcRenderer.removeListener('runtime:clientHostedBrowserRowsChanged', listener)
}
} satisfies PreloadApi['runtime']