refactor(mobile): drop the browser adapter methods nothing calls

Only back, forward and reload have a consumer; the browser pane still issues
its own RPCs. subscribe, navigate, scroll, click, insertText, keypress and
dialog go, with the event and frame-comparison types that existed only for
them. click's fallback had also lost main's move/down/up sequence and cited a
caller that does not exist here.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
This commit is contained in:
Jinwoo-H
2026-09-09 01:45:44 -04:00
parent aefdf2d034
commit e1b7aa8223
2 changed files with 2 additions and 212 deletions
@@ -1,77 +1,10 @@
import type { BrowserPointerModifier } from '../browser/MobileBrowserPointerModifiers'
import type { MobileBrowserScreencastRequest } from '../browser/browser-screencast-request'
import type {
BrowserScreencastFrame,
BrowserScreencastFrameMetadata
} from '../transport/browser-screencast-protocol'
export type HostSessionBrowserTarget = {
workspaceId: string
pageId: string
}
export type HostSessionBrowserEvent =
| {
type: 'ready' | 'navigation'
tab: {
url: string
title: string
canGoBack: boolean
canGoForward: boolean
}
}
| { type: 'end' }
| { type: 'dialog'; dialogType: string; message: string }
| { type: 'dialogClosed' }
| { type: 'error'; message: string }
export type HostSessionBrowserOperations = {
subscribe(
target: HostSessionBrowserTarget,
request: MobileBrowserScreencastRequest,
listener: {
onEvent: (event: HostSessionBrowserEvent) => void
onFrame: (frame: BrowserScreencastFrame) => void
onError: (error: Error) => void
}
): () => void
navigate(target: HostSessionBrowserTarget, url: string): Promise<{ url: string }>
scroll(
target: HostSessionBrowserTarget,
point: { x: number; y: number },
delta: { dx: number; dy: number }
): Promise<void>
click(
target: HostSessionBrowserTarget,
point: { x: number; y: number },
button: 'left' | 'right',
modifiers: BrowserPointerModifier[],
radius?: number
): Promise<void>
insertText(target: HostSessionBrowserTarget, text: string): Promise<void>
keypress(
target: HostSessionBrowserTarget,
key: 'Enter' | 'Backspace' | 'Tab' | 'Escape'
): Promise<void>
dialog(target: HostSessionBrowserTarget, action: 'accept' | 'dismiss'): Promise<void>
back(target: HostSessionBrowserTarget): Promise<void>
forward(target: HostSessionBrowserTarget): Promise<void>
reload(target: HostSessionBrowserTarget): Promise<void>
}
export function hostSessionBrowserFrameMetadataEqual(
left: BrowserScreencastFrameMetadata,
right: BrowserScreencastFrameMetadata
): boolean {
return (
left.offsetTop === right.offsetTop &&
left.pageScaleFactor === right.pageScaleFactor &&
left.deviceWidth === right.deviceWidth &&
left.deviceHeight === right.deviceHeight &&
left.imageWidth === right.imageWidth &&
left.imageHeight === right.imageHeight &&
left.scrollOffsetX === right.scrollOffsetX &&
left.scrollOffsetY === right.scrollOffsetY &&
left.timestamp === right.timestamp
)
}
@@ -1,7 +1,5 @@
import type { RpcClient } from '../transport/rpc-client'
import type { RpcSuccess } from '../transport/types'
import type {
HostSessionBrowserEvent,
HostSessionBrowserOperations,
HostSessionBrowserTarget
} from './host-session-browser-operations'
@@ -10,80 +8,6 @@ export function nativeHostSessionBrowserOperations(
client: RpcClient
): HostSessionBrowserOperations {
return {
subscribe(target, request, listener) {
try {
return client.subscribe(
'browser.screencast',
{ ...nativeTarget(target), ...request },
(event) => {
const presented = nativeBrowserEvent(event)
if (presented) {
listener.onEvent(presented)
}
},
{ onBinaryFrame: listener.onFrame }
)
} catch (error) {
listener.onError(browserOperationError(error))
return () => {}
}
},
async navigate(target, url) {
const result = await requestResult(
client,
'browser.goto',
{ ...nativeTarget(target), url },
30_000
)
if (!isRecord(result) || typeof result.url !== 'string') {
throw new Error('Browser navigation failed')
}
return { url: result.url }
},
async scroll(target, point, delta) {
await requestResult(client, 'browser.mouseMove', { ...nativeTarget(target), ...point }, 5_000)
await requestResult(
client,
'browser.mouseWheel',
{ ...nativeTarget(target), ...delta },
5_000
)
},
async click(target, point, button, modifiers, radius) {
// No move/down/up fallback here: the live caller wraps that sequence in a try/catch
// because pointer commands race page navigation, and a throwing copy of it would surface
// those races as errors the user never saw.
await client.sendRequest(
'browser.mouseClick',
{
...nativeTarget(target),
...point,
button,
modifiers,
...(radius === undefined ? {} : { radius })
},
{ timeoutMs: 5_000 }
)
},
async insertText(target, text) {
await requestResult(
client,
'browser.keyboardInsertText',
{ ...nativeTarget(target), text },
5_000
)
},
async keypress(target, key) {
await requestResult(client, 'browser.keypress', { ...nativeTarget(target), key }, 5_000)
},
async dialog(target, action) {
await requestResult(
client,
action === 'accept' ? 'browser.dialogAccept' : 'browser.dialogDismiss',
nativeTarget(target),
5_000
)
},
async back(target) {
await requestResult(client, 'browser.back', nativeTarget(target))
},
@@ -100,76 +24,9 @@ function nativeTarget(target: HostSessionBrowserTarget): { worktree: string; pag
return { worktree: `id:${target.workspaceId}`, page: target.pageId }
}
async function requestResult(
client: RpcClient,
method: string,
payload: unknown,
timeoutMs = 15_000
): Promise<unknown> {
const response = await client.sendRequest(method, payload, { timeoutMs })
async function requestResult(client: RpcClient, method: string, payload: unknown): Promise<void> {
const response = await client.sendRequest(method, payload, { timeoutMs: 15_000 })
if (!response.ok) {
throw new Error(response.error.message)
}
return (response as RpcSuccess).result
}
function nativeBrowserEvent(value: unknown): HostSessionBrowserEvent | null {
if (!isRecord(value)) {
return null
}
if (value.type === 'ready') {
const tab = isRecord(value.tab) ? value.tab : {}
return {
type: 'ready',
tab: {
url: typeof tab.url === 'string' ? tab.url : 'about:blank',
title: typeof tab.title === 'string' ? tab.title : '',
canGoBack: tab.canGoBack === true,
canGoForward: tab.canGoForward === true
}
}
}
if (value.type === 'navigation') {
const tab = isRecord(value.tab) ? value.tab : {}
return {
type: 'navigation',
tab: {
url: typeof tab.url === 'string' ? tab.url : 'about:blank',
title: typeof tab.title === 'string' ? tab.title : '',
canGoBack: tab.canGoBack === true,
canGoForward: tab.canGoForward === true
}
}
}
if (value.type === 'end' || value.type === 'dialogClosed') {
return { type: value.type }
}
if (value.type === 'dialog') {
return {
type: 'dialog',
dialogType: typeof value.dialogType === 'string' ? value.dialogType : 'alert',
message: typeof value.message === 'string' ? value.message : 'Browser dialog'
}
}
if (value.type === 'error') {
const nested = isRecord(value.error) ? value.error : {}
return {
type: 'error',
message:
typeof value.message === 'string'
? value.message
: typeof nested.message === 'string'
? nested.message
: 'Browser stream failed.'
}
}
return null
}
function browserOperationError(error: unknown): Error {
return error instanceof Error ? error : new Error('Browser command failed')
}
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === 'object' && value !== null && !Array.isArray(value)
}