mirror of
https://github.com/stablyai/orca.git
synced 2026-10-01 16:02:03 +00:00
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:
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user