diff --git a/mobile/src/session/host-session-browser-operations.ts b/mobile/src/session/host-session-browser-operations.ts index 22954d9cb4e..3931007539a 100644 --- a/mobile/src/session/host-session-browser-operations.ts +++ b/mobile/src/session/host-session-browser-operations.ts @@ -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 - click( - target: HostSessionBrowserTarget, - point: { x: number; y: number }, - button: 'left' | 'right', - modifiers: BrowserPointerModifier[], - radius?: number - ): Promise - insertText(target: HostSessionBrowserTarget, text: string): Promise - keypress( - target: HostSessionBrowserTarget, - key: 'Enter' | 'Backspace' | 'Tab' | 'Escape' - ): Promise - dialog(target: HostSessionBrowserTarget, action: 'accept' | 'dismiss'): Promise back(target: HostSessionBrowserTarget): Promise forward(target: HostSessionBrowserTarget): Promise reload(target: HostSessionBrowserTarget): Promise } - -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 - ) -} diff --git a/mobile/src/session/native-host-session-browser-operations.ts b/mobile/src/session/native-host-session-browser-operations.ts index e2fc3b8eacc..b3da99049e5 100644 --- a/mobile/src/session/native-host-session-browser-operations.ts +++ b/mobile/src/session/native-host-session-browser-operations.ts @@ -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 { - const response = await client.sendRequest(method, payload, { timeoutMs }) +async function requestResult(client: RpcClient, method: string, payload: unknown): Promise { + 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 { - return typeof value === 'object' && value !== null && !Array.isArray(value) }