@limrun/api
Advanced tools
| import type { Tunnel } from "./tunnel.mjs"; | ||
| /** | ||
| * A client for interacting with a Limbar instance | ||
| */ | ||
| export type InstanceClient = { | ||
| /** | ||
| * Take a screenshot of the current screen | ||
| * @returns A promise that resolves to the screenshot data | ||
| */ | ||
| screenshot: () => Promise<ScreenshotData>; | ||
| /** | ||
| * Disconnect from the Limbar instance | ||
| */ | ||
| disconnect: () => void; | ||
| /** | ||
| * Establish an ADB tunnel to the instance. | ||
| * Returns the local TCP port and a cleanup function. | ||
| */ | ||
| startAdbTunnel: () => Promise<Tunnel>; | ||
| /** | ||
| * Send an asset URL to the instance. The instance will download the asset | ||
| * and process it (currently APK install is supported). Resolves on success, | ||
| * rejects with an Error on failure. | ||
| */ | ||
| sendAsset: (url: string) => Promise<void>; | ||
| }; | ||
| /** | ||
| * Controls the verbosity of logging in the client | ||
| */ | ||
| export type LogLevel = 'none' | 'error' | 'warn' | 'info' | 'debug'; | ||
| /** | ||
| * Configuration options for creating an Instance API client | ||
| */ | ||
| export type InstanceClientOptions = { | ||
| /** | ||
| * The URL of the ADB WebSocket endpoint. | ||
| */ | ||
| adbUrl: string; | ||
| /** | ||
| * The URL of the main endpoint WebSocket. | ||
| */ | ||
| endpointUrl: string; | ||
| /** | ||
| * The token to use for the WebSocket connections. | ||
| */ | ||
| token: string; | ||
| /** | ||
| * Path to the ADB executable. | ||
| * @default 'adb' | ||
| */ | ||
| adbPath?: string; | ||
| /** | ||
| * Controls logging verbosity | ||
| * @default 'info' | ||
| */ | ||
| logLevel?: LogLevel; | ||
| }; | ||
| type ScreenshotData = { | ||
| dataUri: string; | ||
| }; | ||
| /** | ||
| * Creates a client for interacting with a Limbar instance | ||
| * @param options Configuration options including webrtcUrl, token and log level | ||
| * @returns An InstanceClient for controlling the instance | ||
| */ | ||
| export declare function createInstanceClient(options: InstanceClientOptions): Promise<InstanceClient>; | ||
| export {}; | ||
| //# sourceMappingURL=instance-client.d.mts.map |
| {"version":3,"file":"instance-client.d.mts","sourceRoot":"","sources":["src/instance-client.ts"],"names":[],"mappings":"OAIO,KAAK,EAAE,MAAM,EAAE;AAEtB;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B;;;OAGG;IACH,UAAU,EAAE,MAAM,OAAO,CAAC,cAAc,CAAC,CAAC;IAC1C;;OAEG;IACH,UAAU,EAAE,MAAM,IAAI,CAAC;IAEvB;;;OAGG;IACH,cAAc,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;IACtC;;;;OAIG;IACH,SAAS,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3C,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;AAEpE;;GAEG;AACH,MAAM,MAAM,qBAAqB,GAAG;IAClC;;OAEG;IACH,MAAM,EAAE,MAAM,CAAC;IACf;;OAEG;IACH,WAAW,EAAE,MAAM,CAAC;IACpB;;OAEG;IACH,KAAK,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,QAAQ,CAAC,EAAE,QAAQ,CAAC;CACrB,CAAC;AAaF,KAAK,cAAc,GAAG;IACpB,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AA0BF;;;;GAIG;AACH,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,cAAc,CAAC,CA0OlG"} |
| import type { Tunnel } from "./tunnel.js"; | ||
| /** | ||
| * A client for interacting with a Limbar instance | ||
| */ | ||
| export type InstanceClient = { | ||
| /** | ||
| * Take a screenshot of the current screen | ||
| * @returns A promise that resolves to the screenshot data | ||
| */ | ||
| screenshot: () => Promise<ScreenshotData>; | ||
| /** | ||
| * Disconnect from the Limbar instance | ||
| */ | ||
| disconnect: () => void; | ||
| /** | ||
| * Establish an ADB tunnel to the instance. | ||
| * Returns the local TCP port and a cleanup function. | ||
| */ | ||
| startAdbTunnel: () => Promise<Tunnel>; | ||
| /** | ||
| * Send an asset URL to the instance. The instance will download the asset | ||
| * and process it (currently APK install is supported). Resolves on success, | ||
| * rejects with an Error on failure. | ||
| */ | ||
| sendAsset: (url: string) => Promise<void>; | ||
| }; | ||
| /** | ||
| * Controls the verbosity of logging in the client | ||
| */ | ||
| export type LogLevel = 'none' | 'error' | 'warn' | 'info' | 'debug'; | ||
| /** | ||
| * Configuration options for creating an Instance API client | ||
| */ | ||
| export type InstanceClientOptions = { | ||
| /** | ||
| * The URL of the ADB WebSocket endpoint. | ||
| */ | ||
| adbUrl: string; | ||
| /** | ||
| * The URL of the main endpoint WebSocket. | ||
| */ | ||
| endpointUrl: string; | ||
| /** | ||
| * The token to use for the WebSocket connections. | ||
| */ | ||
| token: string; | ||
| /** | ||
| * Path to the ADB executable. | ||
| * @default 'adb' | ||
| */ | ||
| adbPath?: string; | ||
| /** | ||
| * Controls logging verbosity | ||
| * @default 'info' | ||
| */ | ||
| logLevel?: LogLevel; | ||
| }; | ||
| type ScreenshotData = { | ||
| dataUri: string; | ||
| }; | ||
| /** | ||
| * Creates a client for interacting with a Limbar instance | ||
| * @param options Configuration options including webrtcUrl, token and log level | ||
| * @returns An InstanceClient for controlling the instance | ||
| */ | ||
| export declare function createInstanceClient(options: InstanceClientOptions): Promise<InstanceClient>; | ||
| export {}; | ||
| //# sourceMappingURL=instance-client.d.ts.map |
| {"version":3,"file":"instance-client.d.ts","sourceRoot":"","sources":["src/instance-client.ts"],"names":[],"mappings":"OAIO,KAAK,EAAE,MAAM,EAAE;AAEtB;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B;;;OAGG;IACH,UAAU,EAAE,MAAM,OAAO,CAAC,cAAc,CAAC,CAAC;IAC1C;;OAEG;IACH,UAAU,EAAE,MAAM,IAAI,CAAC;IAEvB;;;OAGG;IACH,cAAc,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;IACtC;;;;OAIG;IACH,SAAS,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3C,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;AAEpE;;GAEG;AACH,MAAM,MAAM,qBAAqB,GAAG;IAClC;;OAEG;IACH,MAAM,EAAE,MAAM,CAAC;IACf;;OAEG;IACH,WAAW,EAAE,MAAM,CAAC;IACpB;;OAEG;IACH,KAAK,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,QAAQ,CAAC,EAAE,QAAQ,CAAC;CACrB,CAAC;AAaF,KAAK,cAAc,GAAG;IACpB,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AA0BF;;;;GAIG;AACH,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,cAAc,CAAC,CA0OlG"} |
| "use strict"; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.createInstanceClient = createInstanceClient; | ||
| const ws_1 = require("ws"); | ||
| const node_child_process_1 = require("node:child_process"); | ||
| const tunnel_1 = require("./tunnel.js"); | ||
| /** | ||
| * Creates a client for interacting with a Limbar instance | ||
| * @param options Configuration options including webrtcUrl, token and log level | ||
| * @returns An InstanceClient for controlling the instance | ||
| */ | ||
| async function createInstanceClient(options) { | ||
| const serverAddress = `${options.endpointUrl}?token=${options.token}`; | ||
| const logLevel = options.logLevel ?? 'info'; | ||
| let ws = undefined; | ||
| const screenshotRequests = new Map(); | ||
| const assetRequests = new Map(); | ||
| // Logger functions | ||
| const logger = { | ||
| debug: (...args) => { | ||
| if (logLevel === 'debug') | ||
| console.log(...args); | ||
| }, | ||
| info: (...args) => { | ||
| if (logLevel === 'info' || logLevel === 'debug') | ||
| console.log(...args); | ||
| }, | ||
| warn: (...args) => { | ||
| if (logLevel === 'warn' || logLevel === 'info' || logLevel === 'debug') | ||
| console.warn(...args); | ||
| }, | ||
| error: (...args) => { | ||
| if (logLevel !== 'none') | ||
| console.error(...args); | ||
| }, | ||
| }; | ||
| return new Promise((resolveConnection, rejectConnection) => { | ||
| logger.debug(`Attempting to connect to WebSocket server at ${serverAddress}...`); | ||
| ws = new ws_1.WebSocket(serverAddress); | ||
| ws.on('message', (data) => { | ||
| let message; | ||
| try { | ||
| message = JSON.parse(data.toString()); | ||
| } | ||
| catch (e) { | ||
| logger.error({ data, error: e }, 'Failed to parse JSON message'); | ||
| return; | ||
| } | ||
| switch (message.type) { | ||
| case 'screenshot': { | ||
| if (!('dataUri' in message) || typeof message.dataUri !== 'string' || !('id' in message)) { | ||
| logger.warn('Received invalid screenshot message:', message); | ||
| break; | ||
| } | ||
| const screenshotMessage = message; | ||
| const request = screenshotRequests.get(screenshotMessage.id); | ||
| if (!request) { | ||
| logger.warn(`Received screenshot data for unknown or already handled session: ${screenshotMessage.id}`); | ||
| break; | ||
| } | ||
| logger.debug(`Received screenshot data URI for session ${screenshotMessage.id}.`); | ||
| request.resolver({ dataUri: screenshotMessage.dataUri }); | ||
| screenshotRequests.delete(screenshotMessage.id); | ||
| break; | ||
| } | ||
| case 'screenshotError': { | ||
| if (!('message' in message) || !('id' in message)) { | ||
| logger.warn('Received invalid screenshot error message:', message); | ||
| break; | ||
| } | ||
| const errorMessage = message; | ||
| const request = screenshotRequests.get(errorMessage.id); | ||
| if (!request) { | ||
| logger.warn(`Received screenshot error for unknown or already handled session: ${errorMessage.id}`); | ||
| break; | ||
| } | ||
| logger.error(`Server reported an error capturing screenshot for session ${errorMessage.id}:`, errorMessage.message); | ||
| request.rejecter(new Error(errorMessage.message)); | ||
| screenshotRequests.delete(errorMessage.id); | ||
| break; | ||
| } | ||
| case 'assetResult': { | ||
| logger.debug('Received assetResult:', message); | ||
| const request = assetRequests.get(message.url); | ||
| if (!request) { | ||
| logger.warn(`Received assetResult for unknown or already handled url: ${message.url}`); | ||
| break; | ||
| } | ||
| if (message.result === 'success') { | ||
| logger.debug('Asset result is success'); | ||
| request.resolver(); | ||
| assetRequests.delete(message.url); | ||
| break; | ||
| } | ||
| const errorMessage = typeof message.message === 'string' && message.message ? | ||
| message.message | ||
| : `Asset processing failed: ${JSON.stringify(message)}`; | ||
| logger.debug('Asset result is failure', errorMessage); | ||
| request.rejecter(new Error(errorMessage)); | ||
| assetRequests.delete(message.url); | ||
| break; | ||
| } | ||
| default: | ||
| logger.warn(`Received unexpected message type: ${message.type}`); | ||
| break; | ||
| } | ||
| }); | ||
| ws.on('error', (err) => { | ||
| logger.error('WebSocket error:', err.message); | ||
| if (ws && (ws.readyState === ws_1.WebSocket.CONNECTING || ws.readyState === ws_1.WebSocket.OPEN)) { | ||
| rejectConnection(err); | ||
| } | ||
| screenshotRequests.forEach((request) => request.rejecter(err)); | ||
| }); | ||
| ws.on('close', () => { | ||
| logger.debug('Disconnected from server.'); | ||
| screenshotRequests.forEach((request) => request.rejecter('Disconnected from server')); | ||
| }); | ||
| const screenshot = async () => { | ||
| if (!ws || ws.readyState !== ws_1.WebSocket.OPEN) { | ||
| return Promise.reject(new Error('WebSocket is not connected or connection is not open.')); | ||
| } | ||
| const id = 'ts-client-' + Date.now(); | ||
| const screenshotRequest = { | ||
| type: 'screenshot', | ||
| id, | ||
| }; | ||
| return new Promise((resolve, reject) => { | ||
| logger.debug('Sending screenshot request:', screenshotRequest); | ||
| ws.send(JSON.stringify(screenshotRequest), (err) => { | ||
| if (err) { | ||
| logger.error('Failed to send screenshot request:', err); | ||
| reject(err); | ||
| } | ||
| }); | ||
| const timeout = setTimeout(() => { | ||
| if (screenshotRequests.has(id)) { | ||
| logger.error(`Screenshot request timed out for session ${id}`); | ||
| screenshotRequests.get(id)?.rejecter(new Error('Screenshot request timed out')); | ||
| screenshotRequests.delete(id); | ||
| } | ||
| }, 30000); | ||
| screenshotRequests.set(id, { | ||
| resolver: (value) => { | ||
| clearTimeout(timeout); | ||
| resolve(value); | ||
| screenshotRequests.delete(id); | ||
| }, | ||
| rejecter: (reason) => { | ||
| clearTimeout(timeout); | ||
| reject(reason); | ||
| screenshotRequests.delete(id); | ||
| }, | ||
| }); | ||
| }); | ||
| }; | ||
| const disconnect = () => { | ||
| if (ws) { | ||
| logger.debug('Closing WebSocket connection.'); | ||
| ws.close(); | ||
| } | ||
| screenshotRequests.forEach((request) => request.rejecter('Websocket connection closed')); | ||
| }; | ||
| /** | ||
| * Opens a WebSocket TCP proxy for the ADB port and connects the local adb | ||
| * client to it. | ||
| */ | ||
| const startAdbTunnel = async () => { | ||
| const { address, close } = await (0, tunnel_1.startTcpTunnel)(options.adbUrl, options.token, '127.0.0.1', 0); | ||
| try { | ||
| await new Promise((resolve, reject) => { | ||
| (0, node_child_process_1.exec)(`${options.adbPath ?? 'adb'} connect ${address.address}:${address.port}`, (err) => { | ||
| if (err) | ||
| return reject(err); | ||
| resolve(); | ||
| }); | ||
| }); | ||
| logger.debug(`ADB connected on ${address.address}`); | ||
| } | ||
| catch (err) { | ||
| close(); | ||
| throw err; | ||
| } | ||
| return { address, close }; | ||
| }; | ||
| const sendAsset = async (url) => { | ||
| if (!ws || ws.readyState !== ws_1.WebSocket.OPEN) { | ||
| return Promise.reject(new Error('WebSocket is not connected or connection is not open.')); | ||
| } | ||
| const assetRequest = { | ||
| type: 'asset', | ||
| url, | ||
| }; | ||
| ws.send(JSON.stringify(assetRequest), (err) => { | ||
| if (err) { | ||
| logger.error('Failed to send asset request:', err); | ||
| } | ||
| }); | ||
| return new Promise((resolve, reject) => { | ||
| assetRequests.set(url, { resolver: resolve, rejecter: reject }); | ||
| }); | ||
| }; | ||
| ws.on('open', () => { | ||
| logger.debug(`Connected to ${serverAddress}`); | ||
| resolveConnection({ | ||
| screenshot, | ||
| disconnect, | ||
| startAdbTunnel, | ||
| sendAsset, | ||
| }); | ||
| }); | ||
| }); | ||
| } | ||
| //# sourceMappingURL=instance-client.js.map |
| {"version":3,"file":"instance-client.js","sourceRoot":"","sources":["src/instance-client.ts"],"names":[],"mappings":"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"} |
| import { WebSocket } from 'ws'; | ||
| import { exec } from 'node:child_process'; | ||
| import { startTcpTunnel } from "./tunnel.mjs"; | ||
| /** | ||
| * Creates a client for interacting with a Limbar instance | ||
| * @param options Configuration options including webrtcUrl, token and log level | ||
| * @returns An InstanceClient for controlling the instance | ||
| */ | ||
| export async function createInstanceClient(options) { | ||
| const serverAddress = `${options.endpointUrl}?token=${options.token}`; | ||
| const logLevel = options.logLevel ?? 'info'; | ||
| let ws = undefined; | ||
| const screenshotRequests = new Map(); | ||
| const assetRequests = new Map(); | ||
| // Logger functions | ||
| const logger = { | ||
| debug: (...args) => { | ||
| if (logLevel === 'debug') | ||
| console.log(...args); | ||
| }, | ||
| info: (...args) => { | ||
| if (logLevel === 'info' || logLevel === 'debug') | ||
| console.log(...args); | ||
| }, | ||
| warn: (...args) => { | ||
| if (logLevel === 'warn' || logLevel === 'info' || logLevel === 'debug') | ||
| console.warn(...args); | ||
| }, | ||
| error: (...args) => { | ||
| if (logLevel !== 'none') | ||
| console.error(...args); | ||
| }, | ||
| }; | ||
| return new Promise((resolveConnection, rejectConnection) => { | ||
| logger.debug(`Attempting to connect to WebSocket server at ${serverAddress}...`); | ||
| ws = new WebSocket(serverAddress); | ||
| ws.on('message', (data) => { | ||
| let message; | ||
| try { | ||
| message = JSON.parse(data.toString()); | ||
| } | ||
| catch (e) { | ||
| logger.error({ data, error: e }, 'Failed to parse JSON message'); | ||
| return; | ||
| } | ||
| switch (message.type) { | ||
| case 'screenshot': { | ||
| if (!('dataUri' in message) || typeof message.dataUri !== 'string' || !('id' in message)) { | ||
| logger.warn('Received invalid screenshot message:', message); | ||
| break; | ||
| } | ||
| const screenshotMessage = message; | ||
| const request = screenshotRequests.get(screenshotMessage.id); | ||
| if (!request) { | ||
| logger.warn(`Received screenshot data for unknown or already handled session: ${screenshotMessage.id}`); | ||
| break; | ||
| } | ||
| logger.debug(`Received screenshot data URI for session ${screenshotMessage.id}.`); | ||
| request.resolver({ dataUri: screenshotMessage.dataUri }); | ||
| screenshotRequests.delete(screenshotMessage.id); | ||
| break; | ||
| } | ||
| case 'screenshotError': { | ||
| if (!('message' in message) || !('id' in message)) { | ||
| logger.warn('Received invalid screenshot error message:', message); | ||
| break; | ||
| } | ||
| const errorMessage = message; | ||
| const request = screenshotRequests.get(errorMessage.id); | ||
| if (!request) { | ||
| logger.warn(`Received screenshot error for unknown or already handled session: ${errorMessage.id}`); | ||
| break; | ||
| } | ||
| logger.error(`Server reported an error capturing screenshot for session ${errorMessage.id}:`, errorMessage.message); | ||
| request.rejecter(new Error(errorMessage.message)); | ||
| screenshotRequests.delete(errorMessage.id); | ||
| break; | ||
| } | ||
| case 'assetResult': { | ||
| logger.debug('Received assetResult:', message); | ||
| const request = assetRequests.get(message.url); | ||
| if (!request) { | ||
| logger.warn(`Received assetResult for unknown or already handled url: ${message.url}`); | ||
| break; | ||
| } | ||
| if (message.result === 'success') { | ||
| logger.debug('Asset result is success'); | ||
| request.resolver(); | ||
| assetRequests.delete(message.url); | ||
| break; | ||
| } | ||
| const errorMessage = typeof message.message === 'string' && message.message ? | ||
| message.message | ||
| : `Asset processing failed: ${JSON.stringify(message)}`; | ||
| logger.debug('Asset result is failure', errorMessage); | ||
| request.rejecter(new Error(errorMessage)); | ||
| assetRequests.delete(message.url); | ||
| break; | ||
| } | ||
| default: | ||
| logger.warn(`Received unexpected message type: ${message.type}`); | ||
| break; | ||
| } | ||
| }); | ||
| ws.on('error', (err) => { | ||
| logger.error('WebSocket error:', err.message); | ||
| if (ws && (ws.readyState === WebSocket.CONNECTING || ws.readyState === WebSocket.OPEN)) { | ||
| rejectConnection(err); | ||
| } | ||
| screenshotRequests.forEach((request) => request.rejecter(err)); | ||
| }); | ||
| ws.on('close', () => { | ||
| logger.debug('Disconnected from server.'); | ||
| screenshotRequests.forEach((request) => request.rejecter('Disconnected from server')); | ||
| }); | ||
| const screenshot = async () => { | ||
| if (!ws || ws.readyState !== WebSocket.OPEN) { | ||
| return Promise.reject(new Error('WebSocket is not connected or connection is not open.')); | ||
| } | ||
| const id = 'ts-client-' + Date.now(); | ||
| const screenshotRequest = { | ||
| type: 'screenshot', | ||
| id, | ||
| }; | ||
| return new Promise((resolve, reject) => { | ||
| logger.debug('Sending screenshot request:', screenshotRequest); | ||
| ws.send(JSON.stringify(screenshotRequest), (err) => { | ||
| if (err) { | ||
| logger.error('Failed to send screenshot request:', err); | ||
| reject(err); | ||
| } | ||
| }); | ||
| const timeout = setTimeout(() => { | ||
| if (screenshotRequests.has(id)) { | ||
| logger.error(`Screenshot request timed out for session ${id}`); | ||
| screenshotRequests.get(id)?.rejecter(new Error('Screenshot request timed out')); | ||
| screenshotRequests.delete(id); | ||
| } | ||
| }, 30000); | ||
| screenshotRequests.set(id, { | ||
| resolver: (value) => { | ||
| clearTimeout(timeout); | ||
| resolve(value); | ||
| screenshotRequests.delete(id); | ||
| }, | ||
| rejecter: (reason) => { | ||
| clearTimeout(timeout); | ||
| reject(reason); | ||
| screenshotRequests.delete(id); | ||
| }, | ||
| }); | ||
| }); | ||
| }; | ||
| const disconnect = () => { | ||
| if (ws) { | ||
| logger.debug('Closing WebSocket connection.'); | ||
| ws.close(); | ||
| } | ||
| screenshotRequests.forEach((request) => request.rejecter('Websocket connection closed')); | ||
| }; | ||
| /** | ||
| * Opens a WebSocket TCP proxy for the ADB port and connects the local adb | ||
| * client to it. | ||
| */ | ||
| const startAdbTunnel = async () => { | ||
| const { address, close } = await startTcpTunnel(options.adbUrl, options.token, '127.0.0.1', 0); | ||
| try { | ||
| await new Promise((resolve, reject) => { | ||
| exec(`${options.adbPath ?? 'adb'} connect ${address.address}:${address.port}`, (err) => { | ||
| if (err) | ||
| return reject(err); | ||
| resolve(); | ||
| }); | ||
| }); | ||
| logger.debug(`ADB connected on ${address.address}`); | ||
| } | ||
| catch (err) { | ||
| close(); | ||
| throw err; | ||
| } | ||
| return { address, close }; | ||
| }; | ||
| const sendAsset = async (url) => { | ||
| if (!ws || ws.readyState !== WebSocket.OPEN) { | ||
| return Promise.reject(new Error('WebSocket is not connected or connection is not open.')); | ||
| } | ||
| const assetRequest = { | ||
| type: 'asset', | ||
| url, | ||
| }; | ||
| ws.send(JSON.stringify(assetRequest), (err) => { | ||
| if (err) { | ||
| logger.error('Failed to send asset request:', err); | ||
| } | ||
| }); | ||
| return new Promise((resolve, reject) => { | ||
| assetRequests.set(url, { resolver: resolve, rejecter: reject }); | ||
| }); | ||
| }; | ||
| ws.on('open', () => { | ||
| logger.debug(`Connected to ${serverAddress}`); | ||
| resolveConnection({ | ||
| screenshot, | ||
| disconnect, | ||
| startAdbTunnel, | ||
| sendAsset, | ||
| }); | ||
| }); | ||
| }); | ||
| } | ||
| //# sourceMappingURL=instance-client.mjs.map |
| {"version":3,"file":"instance-client.mjs","sourceRoot":"","sources":["src/instance-client.ts"],"names":[],"mappings":"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"} |
| import { WebSocket, Data } from 'ws'; | ||
| import { exec } from 'node:child_process'; | ||
| import { startTcpTunnel } from './tunnel'; | ||
| import type { Tunnel } from './tunnel'; | ||
| /** | ||
| * A client for interacting with a Limbar instance | ||
| */ | ||
| export type InstanceClient = { | ||
| /** | ||
| * Take a screenshot of the current screen | ||
| * @returns A promise that resolves to the screenshot data | ||
| */ | ||
| screenshot: () => Promise<ScreenshotData>; | ||
| /** | ||
| * Disconnect from the Limbar instance | ||
| */ | ||
| disconnect: () => void; | ||
| /** | ||
| * Establish an ADB tunnel to the instance. | ||
| * Returns the local TCP port and a cleanup function. | ||
| */ | ||
| startAdbTunnel: () => Promise<Tunnel>; | ||
| /** | ||
| * Send an asset URL to the instance. The instance will download the asset | ||
| * and process it (currently APK install is supported). Resolves on success, | ||
| * rejects with an Error on failure. | ||
| */ | ||
| sendAsset: (url: string) => Promise<void>; | ||
| }; | ||
| /** | ||
| * Controls the verbosity of logging in the client | ||
| */ | ||
| export type LogLevel = 'none' | 'error' | 'warn' | 'info' | 'debug'; | ||
| /** | ||
| * Configuration options for creating an Instance API client | ||
| */ | ||
| export type InstanceClientOptions = { | ||
| /** | ||
| * The URL of the ADB WebSocket endpoint. | ||
| */ | ||
| adbUrl: string; | ||
| /** | ||
| * The URL of the main endpoint WebSocket. | ||
| */ | ||
| endpointUrl: string; | ||
| /** | ||
| * The token to use for the WebSocket connections. | ||
| */ | ||
| token: string; | ||
| /** | ||
| * Path to the ADB executable. | ||
| * @default 'adb' | ||
| */ | ||
| adbPath?: string; | ||
| /** | ||
| * Controls logging verbosity | ||
| * @default 'info' | ||
| */ | ||
| logLevel?: LogLevel; | ||
| }; | ||
| type ScreenshotRequest = { | ||
| type: 'screenshot'; | ||
| id: string; | ||
| }; | ||
| type ScreenshotResponse = { | ||
| type: 'screenshot'; | ||
| dataUri: string; | ||
| id: string; | ||
| }; | ||
| type ScreenshotData = { | ||
| dataUri: string; | ||
| }; | ||
| type ScreenshotErrorResponse = { | ||
| type: 'screenshotError'; | ||
| message: string; | ||
| id: string; | ||
| }; | ||
| type AssetRequest = { | ||
| type: 'asset'; | ||
| url: string; | ||
| }; | ||
| type AssetResultResponse = { | ||
| type: 'assetResult'; | ||
| result: 'success' | 'failure' | string; | ||
| url: string; | ||
| message?: string; | ||
| }; | ||
| type ServerMessage = | ||
| | ScreenshotResponse | ||
| | ScreenshotErrorResponse | ||
| | AssetResultResponse | ||
| | { type: string; [key: string]: unknown }; | ||
| /** | ||
| * Creates a client for interacting with a Limbar instance | ||
| * @param options Configuration options including webrtcUrl, token and log level | ||
| * @returns An InstanceClient for controlling the instance | ||
| */ | ||
| export async function createInstanceClient(options: InstanceClientOptions): Promise<InstanceClient> { | ||
| const serverAddress = `${options.endpointUrl}?token=${options.token}`; | ||
| const logLevel = options.logLevel ?? 'info'; | ||
| let ws: WebSocket | undefined = undefined; | ||
| const screenshotRequests: Map< | ||
| string, | ||
| { | ||
| resolver: (value: ScreenshotData | PromiseLike<ScreenshotData>) => void; | ||
| rejecter: (reason?: any) => void; | ||
| } | ||
| > = new Map(); | ||
| const assetRequests: Map< | ||
| string, | ||
| { | ||
| resolver: (value: void | PromiseLike<void>) => void; | ||
| rejecter: (reason?: any) => void; | ||
| } | ||
| > = new Map(); | ||
| // Logger functions | ||
| const logger = { | ||
| debug: (...args: any[]) => { | ||
| if (logLevel === 'debug') console.log(...args); | ||
| }, | ||
| info: (...args: any[]) => { | ||
| if (logLevel === 'info' || logLevel === 'debug') console.log(...args); | ||
| }, | ||
| warn: (...args: any[]) => { | ||
| if (logLevel === 'warn' || logLevel === 'info' || logLevel === 'debug') console.warn(...args); | ||
| }, | ||
| error: (...args: any[]) => { | ||
| if (logLevel !== 'none') console.error(...args); | ||
| }, | ||
| }; | ||
| return new Promise<InstanceClient>((resolveConnection, rejectConnection) => { | ||
| logger.debug(`Attempting to connect to WebSocket server at ${serverAddress}...`); | ||
| ws = new WebSocket(serverAddress); | ||
| ws.on('message', (data: Data) => { | ||
| let message: ServerMessage; | ||
| try { | ||
| message = JSON.parse(data.toString()); | ||
| } catch (e) { | ||
| logger.error({ data, error: e }, 'Failed to parse JSON message'); | ||
| return; | ||
| } | ||
| switch (message.type) { | ||
| case 'screenshot': { | ||
| if (!('dataUri' in message) || typeof message.dataUri !== 'string' || !('id' in message)) { | ||
| logger.warn('Received invalid screenshot message:', message); | ||
| break; | ||
| } | ||
| const screenshotMessage = message as ScreenshotResponse; | ||
| const request = screenshotRequests.get(screenshotMessage.id); | ||
| if (!request) { | ||
| logger.warn( | ||
| `Received screenshot data for unknown or already handled session: ${screenshotMessage.id}`, | ||
| ); | ||
| break; | ||
| } | ||
| logger.debug(`Received screenshot data URI for session ${screenshotMessage.id}.`); | ||
| request.resolver({ dataUri: screenshotMessage.dataUri }); | ||
| screenshotRequests.delete(screenshotMessage.id); | ||
| break; | ||
| } | ||
| case 'screenshotError': { | ||
| if (!('message' in message) || !('id' in message)) { | ||
| logger.warn('Received invalid screenshot error message:', message); | ||
| break; | ||
| } | ||
| const errorMessage = message as ScreenshotErrorResponse; | ||
| const request = screenshotRequests.get(errorMessage.id); | ||
| if (!request) { | ||
| logger.warn( | ||
| `Received screenshot error for unknown or already handled session: ${errorMessage.id}`, | ||
| ); | ||
| break; | ||
| } | ||
| logger.error( | ||
| `Server reported an error capturing screenshot for session ${errorMessage.id}:`, | ||
| errorMessage.message, | ||
| ); | ||
| request.rejecter(new Error(errorMessage.message)); | ||
| screenshotRequests.delete(errorMessage.id); | ||
| break; | ||
| } | ||
| case 'assetResult': { | ||
| logger.debug('Received assetResult:', message); | ||
| const request = assetRequests.get(message.url as string); | ||
| if (!request) { | ||
| logger.warn(`Received assetResult for unknown or already handled url: ${message.url}`); | ||
| break; | ||
| } | ||
| if (message.result === 'success') { | ||
| logger.debug('Asset result is success'); | ||
| request.resolver(); | ||
| assetRequests.delete(message.url as string); | ||
| break; | ||
| } | ||
| const errorMessage = | ||
| typeof message.message === 'string' && message.message ? | ||
| message.message | ||
| : `Asset processing failed: ${JSON.stringify(message)}`; | ||
| logger.debug('Asset result is failure', errorMessage); | ||
| request.rejecter(new Error(errorMessage)); | ||
| assetRequests.delete(message.url as string); | ||
| break; | ||
| } | ||
| default: | ||
| logger.warn(`Received unexpected message type: ${message.type}`); | ||
| break; | ||
| } | ||
| }); | ||
| ws.on('error', (err: Error) => { | ||
| logger.error('WebSocket error:', err.message); | ||
| if (ws && (ws.readyState === WebSocket.CONNECTING || ws.readyState === WebSocket.OPEN)) { | ||
| rejectConnection(err); | ||
| } | ||
| screenshotRequests.forEach((request) => request.rejecter(err)); | ||
| }); | ||
| ws.on('close', () => { | ||
| logger.debug('Disconnected from server.'); | ||
| screenshotRequests.forEach((request) => request.rejecter('Disconnected from server')); | ||
| }); | ||
| const screenshot = async (): Promise<ScreenshotData> => { | ||
| if (!ws || ws.readyState !== WebSocket.OPEN) { | ||
| return Promise.reject(new Error('WebSocket is not connected or connection is not open.')); | ||
| } | ||
| const id = 'ts-client-' + Date.now(); | ||
| const screenshotRequest: ScreenshotRequest = { | ||
| type: 'screenshot', | ||
| id, | ||
| }; | ||
| return new Promise<ScreenshotData>((resolve, reject) => { | ||
| logger.debug('Sending screenshot request:', screenshotRequest); | ||
| ws!.send(JSON.stringify(screenshotRequest), (err?: Error) => { | ||
| if (err) { | ||
| logger.error('Failed to send screenshot request:', err); | ||
| reject(err); | ||
| } | ||
| }); | ||
| const timeout = setTimeout(() => { | ||
| if (screenshotRequests.has(id)) { | ||
| logger.error(`Screenshot request timed out for session ${id}`); | ||
| screenshotRequests.get(id)?.rejecter(new Error('Screenshot request timed out')); | ||
| screenshotRequests.delete(id); | ||
| } | ||
| }, 30000); | ||
| screenshotRequests.set(id, { | ||
| resolver: (value: ScreenshotData | PromiseLike<ScreenshotData>) => { | ||
| clearTimeout(timeout); | ||
| resolve(value); | ||
| screenshotRequests.delete(id); | ||
| }, | ||
| rejecter: (reason?: any) => { | ||
| clearTimeout(timeout); | ||
| reject(reason); | ||
| screenshotRequests.delete(id); | ||
| }, | ||
| }); | ||
| }); | ||
| }; | ||
| const disconnect = (): void => { | ||
| if (ws) { | ||
| logger.debug('Closing WebSocket connection.'); | ||
| ws.close(); | ||
| } | ||
| screenshotRequests.forEach((request) => request.rejecter('Websocket connection closed')); | ||
| }; | ||
| /** | ||
| * Opens a WebSocket TCP proxy for the ADB port and connects the local adb | ||
| * client to it. | ||
| */ | ||
| const startAdbTunnel = async (): Promise<Tunnel> => { | ||
| const { address, close } = await startTcpTunnel(options.adbUrl, options.token, '127.0.0.1', 0); | ||
| try { | ||
| await new Promise<void>((resolve, reject) => { | ||
| exec(`${options.adbPath ?? 'adb'} connect ${address.address}:${address.port}`, (err) => { | ||
| if (err) return reject(err); | ||
| resolve(); | ||
| }); | ||
| }); | ||
| logger.debug(`ADB connected on ${address.address}`); | ||
| } catch (err) { | ||
| close(); | ||
| throw err; | ||
| } | ||
| return { address, close }; | ||
| }; | ||
| const sendAsset = async (url: string): Promise<void> => { | ||
| if (!ws || ws.readyState !== WebSocket.OPEN) { | ||
| return Promise.reject(new Error('WebSocket is not connected or connection is not open.')); | ||
| } | ||
| const assetRequest: AssetRequest = { | ||
| type: 'asset', | ||
| url, | ||
| }; | ||
| ws.send(JSON.stringify(assetRequest), (err?: Error) => { | ||
| if (err) { | ||
| logger.error('Failed to send asset request:', err); | ||
| } | ||
| }); | ||
| return new Promise<void>((resolve, reject) => { | ||
| assetRequests.set(url, { resolver: resolve, rejecter: reject }); | ||
| }); | ||
| }; | ||
| ws.on('open', () => { | ||
| logger.debug(`Connected to ${serverAddress}`); | ||
| resolveConnection({ | ||
| screenshot, | ||
| disconnect, | ||
| startAdbTunnel, | ||
| sendAsset, | ||
| }); | ||
| }); | ||
| }); | ||
| } |
+126
| import * as net from 'net'; | ||
| import { WebSocket } from 'ws'; | ||
| export interface Tunnel { | ||
| address: { | ||
| address: string; | ||
| port: number; | ||
| }; | ||
| close: () => void; | ||
| } | ||
| /** | ||
| * Starts a one-shot TCP → WebSocket proxy. | ||
| * | ||
| * The function creates a local TCP server that listens on an ephemeral port on | ||
| * 127.0.0.1. As soon as the **first** TCP client connects the server stops | ||
| * accepting further connections and forwards all traffic between that client | ||
| * and `remoteURL` through an authenticated WebSocket. If you need to proxy | ||
| * more than one TCP connection, call `startTcpTunnel` again to create a new | ||
| * proxy instance. | ||
| * | ||
| * @param remoteURL Remote WebSocket endpoint (e.g. wss://example.com/instance) | ||
| * @param token Bearer token sent as `Authorization` header | ||
| * @param hostname Optional IP address to listen on. Default is 127.0.0.1 | ||
| * @param port Optional port number to listen on. Default is to ask Node.js | ||
| * to find an available non-privileged port. | ||
| */ | ||
| export async function startTcpTunnel( | ||
| remoteURL: string, | ||
| token: string, | ||
| hostname: string, | ||
| port: number, | ||
| ): Promise<Tunnel> { | ||
| return new Promise((resolve, reject) => { | ||
| const server = net.createServer(); | ||
| let ws: WebSocket | undefined; | ||
| let pingInterval: NodeJS.Timeout | undefined; | ||
| // close helper | ||
| const close = () => { | ||
| if (pingInterval) { | ||
| clearInterval(pingInterval); | ||
| pingInterval = undefined; | ||
| } | ||
| if (ws && ws.readyState === WebSocket.OPEN) { | ||
| ws.close(1000, 'close'); | ||
| } | ||
| if (server.listening) { | ||
| server.close(); | ||
| } | ||
| }; | ||
| // No AbortController support – proxy can be closed via the returned handle | ||
| // TCP server error | ||
| server.once('error', (err) => { | ||
| close(); | ||
| reject(new Error(`TCP server error: ${err.message}`)); | ||
| }); | ||
| // Listening | ||
| server.once('listening', () => { | ||
| const address = server.address(); | ||
| if (!address || typeof address === 'string') { | ||
| close(); | ||
| return reject(new Error('Failed to obtain listening address')); | ||
| } | ||
| resolve({ address, close }); | ||
| }); | ||
| // On first TCP connection | ||
| server.on('connection', (tcpSocket) => { | ||
| // Single-connection proxy | ||
| server.close(); | ||
| ws = new WebSocket(remoteURL, { | ||
| headers: { Authorization: `Bearer ${token}` }, | ||
| perMessageDeflate: false, | ||
| }); | ||
| // WebSocket error | ||
| ws.once('error', (err: any) => { | ||
| console.error('WebSocket error:', err); | ||
| tcpSocket.destroy(); | ||
| close(); | ||
| }); | ||
| ws.once('open', () => { | ||
| const socket = ws as WebSocket; // non-undefined after open | ||
| pingInterval = setInterval(() => { | ||
| if (socket.readyState === WebSocket.OPEN) { | ||
| (socket as any).ping(); | ||
| } | ||
| }, 30_000); | ||
| // TCP → WS | ||
| tcpSocket.on('data', (chunk) => { | ||
| if (socket.readyState === WebSocket.OPEN) { | ||
| socket.send(chunk); | ||
| } | ||
| }); | ||
| // WS → TCP | ||
| socket.on('message', (data: any) => { | ||
| if (!tcpSocket.destroyed) { | ||
| tcpSocket.write(data as Buffer); | ||
| } | ||
| }); | ||
| }); | ||
| // Mutual close | ||
| tcpSocket.on('close', close); | ||
| tcpSocket.on('error', (err: any) => { | ||
| console.error('TCP socket error:', err); | ||
| close(); | ||
| }); | ||
| ws.on('close', () => tcpSocket.destroy()); | ||
| }); | ||
| // Start listening | ||
| server.listen(port, hostname); | ||
| }); | ||
| } |
+25
| export interface Tunnel { | ||
| address: { | ||
| address: string; | ||
| port: number; | ||
| }; | ||
| close: () => void; | ||
| } | ||
| /** | ||
| * Starts a one-shot TCP → WebSocket proxy. | ||
| * | ||
| * The function creates a local TCP server that listens on an ephemeral port on | ||
| * 127.0.0.1. As soon as the **first** TCP client connects the server stops | ||
| * accepting further connections and forwards all traffic between that client | ||
| * and `remoteURL` through an authenticated WebSocket. If you need to proxy | ||
| * more than one TCP connection, call `startTcpTunnel` again to create a new | ||
| * proxy instance. | ||
| * | ||
| * @param remoteURL Remote WebSocket endpoint (e.g. wss://example.com/instance) | ||
| * @param token Bearer token sent as `Authorization` header | ||
| * @param hostname Optional IP address to listen on. Default is 127.0.0.1 | ||
| * @param port Optional port number to listen on. Default is to ask Node.js | ||
| * to find an available non-privileged port. | ||
| */ | ||
| export declare function startTcpTunnel(remoteURL: string, token: string, hostname: string, port: number): Promise<Tunnel>; | ||
| //# sourceMappingURL=tunnel.d.mts.map |
| {"version":3,"file":"tunnel.d.mts","sourceRoot":"","sources":["src/tunnel.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,MAAM;IACrB,OAAO,EAAE;QACP,OAAO,EAAE,MAAM,CAAC;QAChB,IAAI,EAAE,MAAM,CAAC;KACd,CAAC;IACF,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,cAAc,CAClC,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,MAAM,CAAC,CA6FjB"} |
+25
| export interface Tunnel { | ||
| address: { | ||
| address: string; | ||
| port: number; | ||
| }; | ||
| close: () => void; | ||
| } | ||
| /** | ||
| * Starts a one-shot TCP → WebSocket proxy. | ||
| * | ||
| * The function creates a local TCP server that listens on an ephemeral port on | ||
| * 127.0.0.1. As soon as the **first** TCP client connects the server stops | ||
| * accepting further connections and forwards all traffic between that client | ||
| * and `remoteURL` through an authenticated WebSocket. If you need to proxy | ||
| * more than one TCP connection, call `startTcpTunnel` again to create a new | ||
| * proxy instance. | ||
| * | ||
| * @param remoteURL Remote WebSocket endpoint (e.g. wss://example.com/instance) | ||
| * @param token Bearer token sent as `Authorization` header | ||
| * @param hostname Optional IP address to listen on. Default is 127.0.0.1 | ||
| * @param port Optional port number to listen on. Default is to ask Node.js | ||
| * to find an available non-privileged port. | ||
| */ | ||
| export declare function startTcpTunnel(remoteURL: string, token: string, hostname: string, port: number): Promise<Tunnel>; | ||
| //# sourceMappingURL=tunnel.d.ts.map |
| {"version":3,"file":"tunnel.d.ts","sourceRoot":"","sources":["src/tunnel.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,MAAM;IACrB,OAAO,EAAE;QACP,OAAO,EAAE,MAAM,CAAC;QAChB,IAAI,EAAE,MAAM,CAAC;KACd,CAAC;IACF,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,cAAc,CAClC,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,MAAM,CAAC,CA6FjB"} |
+102
| "use strict"; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.startTcpTunnel = startTcpTunnel; | ||
| const tslib_1 = require("./internal/tslib.js"); | ||
| const net = tslib_1.__importStar(require("net")); | ||
| const ws_1 = require("ws"); | ||
| /** | ||
| * Starts a one-shot TCP → WebSocket proxy. | ||
| * | ||
| * The function creates a local TCP server that listens on an ephemeral port on | ||
| * 127.0.0.1. As soon as the **first** TCP client connects the server stops | ||
| * accepting further connections and forwards all traffic between that client | ||
| * and `remoteURL` through an authenticated WebSocket. If you need to proxy | ||
| * more than one TCP connection, call `startTcpTunnel` again to create a new | ||
| * proxy instance. | ||
| * | ||
| * @param remoteURL Remote WebSocket endpoint (e.g. wss://example.com/instance) | ||
| * @param token Bearer token sent as `Authorization` header | ||
| * @param hostname Optional IP address to listen on. Default is 127.0.0.1 | ||
| * @param port Optional port number to listen on. Default is to ask Node.js | ||
| * to find an available non-privileged port. | ||
| */ | ||
| async function startTcpTunnel(remoteURL, token, hostname, port) { | ||
| return new Promise((resolve, reject) => { | ||
| const server = net.createServer(); | ||
| let ws; | ||
| let pingInterval; | ||
| // close helper | ||
| const close = () => { | ||
| if (pingInterval) { | ||
| clearInterval(pingInterval); | ||
| pingInterval = undefined; | ||
| } | ||
| if (ws && ws.readyState === ws_1.WebSocket.OPEN) { | ||
| ws.close(1000, 'close'); | ||
| } | ||
| if (server.listening) { | ||
| server.close(); | ||
| } | ||
| }; | ||
| // No AbortController support – proxy can be closed via the returned handle | ||
| // TCP server error | ||
| server.once('error', (err) => { | ||
| close(); | ||
| reject(new Error(`TCP server error: ${err.message}`)); | ||
| }); | ||
| // Listening | ||
| server.once('listening', () => { | ||
| const address = server.address(); | ||
| if (!address || typeof address === 'string') { | ||
| close(); | ||
| return reject(new Error('Failed to obtain listening address')); | ||
| } | ||
| resolve({ address, close }); | ||
| }); | ||
| // On first TCP connection | ||
| server.on('connection', (tcpSocket) => { | ||
| // Single-connection proxy | ||
| server.close(); | ||
| ws = new ws_1.WebSocket(remoteURL, { | ||
| headers: { Authorization: `Bearer ${token}` }, | ||
| perMessageDeflate: false, | ||
| }); | ||
| // WebSocket error | ||
| ws.once('error', (err) => { | ||
| console.error('WebSocket error:', err); | ||
| tcpSocket.destroy(); | ||
| close(); | ||
| }); | ||
| ws.once('open', () => { | ||
| const socket = ws; // non-undefined after open | ||
| pingInterval = setInterval(() => { | ||
| if (socket.readyState === ws_1.WebSocket.OPEN) { | ||
| socket.ping(); | ||
| } | ||
| }, 30000); | ||
| // TCP → WS | ||
| tcpSocket.on('data', (chunk) => { | ||
| if (socket.readyState === ws_1.WebSocket.OPEN) { | ||
| socket.send(chunk); | ||
| } | ||
| }); | ||
| // WS → TCP | ||
| socket.on('message', (data) => { | ||
| if (!tcpSocket.destroyed) { | ||
| tcpSocket.write(data); | ||
| } | ||
| }); | ||
| }); | ||
| // Mutual close | ||
| tcpSocket.on('close', close); | ||
| tcpSocket.on('error', (err) => { | ||
| console.error('TCP socket error:', err); | ||
| close(); | ||
| }); | ||
| ws.on('close', () => tcpSocket.destroy()); | ||
| }); | ||
| // Start listening | ||
| server.listen(port, hostname); | ||
| }); | ||
| } | ||
| //# sourceMappingURL=tunnel.js.map |
| {"version":3,"file":"tunnel.js","sourceRoot":"","sources":["src/tunnel.ts"],"names":[],"mappings":"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"} |
+98
| import * as net from 'net'; | ||
| import { WebSocket } from 'ws'; | ||
| /** | ||
| * Starts a one-shot TCP → WebSocket proxy. | ||
| * | ||
| * The function creates a local TCP server that listens on an ephemeral port on | ||
| * 127.0.0.1. As soon as the **first** TCP client connects the server stops | ||
| * accepting further connections and forwards all traffic between that client | ||
| * and `remoteURL` through an authenticated WebSocket. If you need to proxy | ||
| * more than one TCP connection, call `startTcpTunnel` again to create a new | ||
| * proxy instance. | ||
| * | ||
| * @param remoteURL Remote WebSocket endpoint (e.g. wss://example.com/instance) | ||
| * @param token Bearer token sent as `Authorization` header | ||
| * @param hostname Optional IP address to listen on. Default is 127.0.0.1 | ||
| * @param port Optional port number to listen on. Default is to ask Node.js | ||
| * to find an available non-privileged port. | ||
| */ | ||
| export async function startTcpTunnel(remoteURL, token, hostname, port) { | ||
| return new Promise((resolve, reject) => { | ||
| const server = net.createServer(); | ||
| let ws; | ||
| let pingInterval; | ||
| // close helper | ||
| const close = () => { | ||
| if (pingInterval) { | ||
| clearInterval(pingInterval); | ||
| pingInterval = undefined; | ||
| } | ||
| if (ws && ws.readyState === WebSocket.OPEN) { | ||
| ws.close(1000, 'close'); | ||
| } | ||
| if (server.listening) { | ||
| server.close(); | ||
| } | ||
| }; | ||
| // No AbortController support – proxy can be closed via the returned handle | ||
| // TCP server error | ||
| server.once('error', (err) => { | ||
| close(); | ||
| reject(new Error(`TCP server error: ${err.message}`)); | ||
| }); | ||
| // Listening | ||
| server.once('listening', () => { | ||
| const address = server.address(); | ||
| if (!address || typeof address === 'string') { | ||
| close(); | ||
| return reject(new Error('Failed to obtain listening address')); | ||
| } | ||
| resolve({ address, close }); | ||
| }); | ||
| // On first TCP connection | ||
| server.on('connection', (tcpSocket) => { | ||
| // Single-connection proxy | ||
| server.close(); | ||
| ws = new WebSocket(remoteURL, { | ||
| headers: { Authorization: `Bearer ${token}` }, | ||
| perMessageDeflate: false, | ||
| }); | ||
| // WebSocket error | ||
| ws.once('error', (err) => { | ||
| console.error('WebSocket error:', err); | ||
| tcpSocket.destroy(); | ||
| close(); | ||
| }); | ||
| ws.once('open', () => { | ||
| const socket = ws; // non-undefined after open | ||
| pingInterval = setInterval(() => { | ||
| if (socket.readyState === WebSocket.OPEN) { | ||
| socket.ping(); | ||
| } | ||
| }, 30000); | ||
| // TCP → WS | ||
| tcpSocket.on('data', (chunk) => { | ||
| if (socket.readyState === WebSocket.OPEN) { | ||
| socket.send(chunk); | ||
| } | ||
| }); | ||
| // WS → TCP | ||
| socket.on('message', (data) => { | ||
| if (!tcpSocket.destroyed) { | ||
| tcpSocket.write(data); | ||
| } | ||
| }); | ||
| }); | ||
| // Mutual close | ||
| tcpSocket.on('close', close); | ||
| tcpSocket.on('error', (err) => { | ||
| console.error('TCP socket error:', err); | ||
| close(); | ||
| }); | ||
| ws.on('close', () => tcpSocket.destroy()); | ||
| }); | ||
| // Start listening | ||
| server.listen(port, hostname); | ||
| }); | ||
| } | ||
| //# sourceMappingURL=tunnel.mjs.map |
| {"version":3,"file":"tunnel.mjs","sourceRoot":"","sources":["src/tunnel.ts"],"names":[],"mappings":"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"} |
+15
-0
| # Changelog | ||
| ## 0.8.0 (2025-09-09) | ||
| Full Changelog: [v0.7.0...v0.8.0](https://github.com/limrun-inc/typescript-sdk/compare/v0.7.0...v0.8.0) | ||
| ### Bug Fixes | ||
| * coerce nullable values to undefined ([0b8c51a](https://github.com/limrun-inc/typescript-sdk/commit/0b8c51a581fe97fcc9c2cd7016a3cceee0700d67)) | ||
| ### Chores | ||
| * **api:** fix linter issues ([9b65c4f](https://github.com/limrun-inc/typescript-sdk/commit/9b65c4f773a08bd85f23fdcbf2c6e2ae727d7a08)) | ||
| * **examples:** update to the latest @limrun/api ([a7e513e](https://github.com/limrun-inc/typescript-sdk/commit/a7e513e29c8948109dc0e2cb00d15a030e2cb7a3)) | ||
| * **tests:** fix the import to be compatible ([40f7fab](https://github.com/limrun-inc/typescript-sdk/commit/40f7fabcf473c28b1a711a8ea524624a3a982e21)) | ||
| ## 0.7.0 (2025-09-08) | ||
@@ -4,0 +19,0 @@ |
+1
-0
@@ -5,3 +5,4 @@ export { Limrun as default } from "./client.mjs"; | ||
| export { Limrun, type ClientOptions } from "./client.mjs"; | ||
| export { createInstanceClient } from "./instance-client.mjs"; | ||
| export { LimrunError, APIError, APIConnectionError, APIConnectionTimeoutError, APIUserAbortError, NotFoundError, ConflictError, RateLimitError, BadRequestError, AuthenticationError, InternalServerError, PermissionDeniedError, UnprocessableEntityError, } from "./core/error.mjs"; | ||
| //# sourceMappingURL=index.d.mts.map |
+1
-1
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"index.d.mts","sourceRoot":"","sources":["src/index.ts"],"names":[],"mappings":"OAEO,EAAE,MAAM,IAAI,OAAO,EAAE;OAErB,EAAE,KAAK,UAAU,EAAE,MAAM,EAAE;OAC3B,EAAE,UAAU,EAAE;OACd,EAAE,MAAM,EAAE,KAAK,aAAa,EAAE;OAC9B,EACL,WAAW,EACX,QAAQ,EACR,kBAAkB,EAClB,yBAAyB,EACzB,iBAAiB,EACjB,aAAa,EACb,aAAa,EACb,cAAc,EACd,eAAe,EACf,mBAAmB,EACnB,mBAAmB,EACnB,qBAAqB,EACrB,wBAAwB,GACzB"} | ||
| {"version":3,"file":"index.d.mts","sourceRoot":"","sources":["src/index.ts"],"names":[],"mappings":"OAEO,EAAE,MAAM,IAAI,OAAO,EAAE;OAErB,EAAE,KAAK,UAAU,EAAE,MAAM,EAAE;OAC3B,EAAE,UAAU,EAAE;OACd,EAAE,MAAM,EAAE,KAAK,aAAa,EAAE;OAC9B,EAAE,oBAAoB,EAAE;OACxB,EACL,WAAW,EACX,QAAQ,EACR,kBAAkB,EAClB,yBAAyB,EACzB,iBAAiB,EACjB,aAAa,EACb,aAAa,EACb,cAAc,EACd,eAAe,EACf,mBAAmB,EACnB,mBAAmB,EACnB,qBAAqB,EACrB,wBAAwB,GACzB"} |
+1
-0
@@ -5,3 +5,4 @@ export { Limrun as default } from "./client.js"; | ||
| export { Limrun, type ClientOptions } from "./client.js"; | ||
| export { createInstanceClient } from "./instance-client.js"; | ||
| export { LimrunError, APIError, APIConnectionError, APIConnectionTimeoutError, APIUserAbortError, NotFoundError, ConflictError, RateLimitError, BadRequestError, AuthenticationError, InternalServerError, PermissionDeniedError, UnprocessableEntityError, } from "./core/error.js"; | ||
| //# sourceMappingURL=index.d.ts.map |
+1
-1
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["src/index.ts"],"names":[],"mappings":"OAEO,EAAE,MAAM,IAAI,OAAO,EAAE;OAErB,EAAE,KAAK,UAAU,EAAE,MAAM,EAAE;OAC3B,EAAE,UAAU,EAAE;OACd,EAAE,MAAM,EAAE,KAAK,aAAa,EAAE;OAC9B,EACL,WAAW,EACX,QAAQ,EACR,kBAAkB,EAClB,yBAAyB,EACzB,iBAAiB,EACjB,aAAa,EACb,aAAa,EACb,cAAc,EACd,eAAe,EACf,mBAAmB,EACnB,mBAAmB,EACnB,qBAAqB,EACrB,wBAAwB,GACzB"} | ||
| {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["src/index.ts"],"names":[],"mappings":"OAEO,EAAE,MAAM,IAAI,OAAO,EAAE;OAErB,EAAE,KAAK,UAAU,EAAE,MAAM,EAAE;OAC3B,EAAE,UAAU,EAAE;OACd,EAAE,MAAM,EAAE,KAAK,aAAa,EAAE;OAC9B,EAAE,oBAAoB,EAAE;OACxB,EACL,WAAW,EACX,QAAQ,EACR,kBAAkB,EAClB,yBAAyB,EACzB,iBAAiB,EACjB,aAAa,EACb,aAAa,EACb,cAAc,EACd,eAAe,EACf,mBAAmB,EACnB,mBAAmB,EACnB,qBAAqB,EACrB,wBAAwB,GACzB"} |
+3
-1
@@ -7,3 +7,3 @@ "use strict"; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.UnprocessableEntityError = exports.PermissionDeniedError = exports.InternalServerError = exports.AuthenticationError = exports.BadRequestError = exports.RateLimitError = exports.ConflictError = exports.NotFoundError = exports.APIUserAbortError = exports.APIConnectionTimeoutError = exports.APIConnectionError = exports.APIError = exports.LimrunError = exports.Limrun = exports.APIPromise = exports.toFile = exports.default = void 0; | ||
| exports.UnprocessableEntityError = exports.PermissionDeniedError = exports.InternalServerError = exports.AuthenticationError = exports.BadRequestError = exports.RateLimitError = exports.ConflictError = exports.NotFoundError = exports.APIUserAbortError = exports.APIConnectionTimeoutError = exports.APIConnectionError = exports.APIError = exports.LimrunError = exports.createInstanceClient = exports.Limrun = exports.APIPromise = exports.toFile = exports.default = void 0; | ||
| var client_1 = require("./client.js"); | ||
@@ -17,2 +17,4 @@ Object.defineProperty(exports, "default", { enumerable: true, get: function () { return client_1.Limrun; } }); | ||
| Object.defineProperty(exports, "Limrun", { enumerable: true, get: function () { return client_2.Limrun; } }); | ||
| var instance_client_1 = require("./instance-client.js"); | ||
| Object.defineProperty(exports, "createInstanceClient", { enumerable: true, get: function () { return instance_client_1.createInstanceClient; } }); | ||
| var error_1 = require("./core/error.js"); | ||
@@ -19,0 +21,0 @@ Object.defineProperty(exports, "LimrunError", { enumerable: true, get: function () { return error_1.LimrunError; } }); |
+1
-1
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"index.js","sourceRoot":"","sources":["src/index.ts"],"names":[],"mappings":";AAAA,sFAAsF;;;AAEtF,sCAA6C;AAApC,iGAAA,MAAM,OAAW;AAE1B,6CAAyD;AAA/B,iGAAA,MAAM,OAAA;AAChC,qDAAgD;AAAvC,yGAAA,UAAU,OAAA;AACnB,sCAAsD;AAA7C,gGAAA,MAAM,OAAA;AACf,yCAcsB;AAbpB,oGAAA,WAAW,OAAA;AACX,iGAAA,QAAQ,OAAA;AACR,2GAAA,kBAAkB,OAAA;AAClB,kHAAA,yBAAyB,OAAA;AACzB,0GAAA,iBAAiB,OAAA;AACjB,sGAAA,aAAa,OAAA;AACb,sGAAA,aAAa,OAAA;AACb,uGAAA,cAAc,OAAA;AACd,wGAAA,eAAe,OAAA;AACf,4GAAA,mBAAmB,OAAA;AACnB,4GAAA,mBAAmB,OAAA;AACnB,8GAAA,qBAAqB,OAAA;AACrB,iHAAA,wBAAwB,OAAA"} | ||
| {"version":3,"file":"index.js","sourceRoot":"","sources":["src/index.ts"],"names":[],"mappings":";AAAA,sFAAsF;;;AAEtF,sCAA6C;AAApC,iGAAA,MAAM,OAAW;AAE1B,6CAAyD;AAA/B,iGAAA,MAAM,OAAA;AAChC,qDAAgD;AAAvC,yGAAA,UAAU,OAAA;AACnB,sCAAsD;AAA7C,gGAAA,MAAM,OAAA;AACf,wDAAyD;AAAhD,uHAAA,oBAAoB,OAAA;AAC7B,yCAcsB;AAbpB,oGAAA,WAAW,OAAA;AACX,iGAAA,QAAQ,OAAA;AACR,2GAAA,kBAAkB,OAAA;AAClB,kHAAA,yBAAyB,OAAA;AACzB,0GAAA,iBAAiB,OAAA;AACjB,sGAAA,aAAa,OAAA;AACb,sGAAA,aAAa,OAAA;AACb,uGAAA,cAAc,OAAA;AACd,wGAAA,eAAe,OAAA;AACf,4GAAA,mBAAmB,OAAA;AACnB,4GAAA,mBAAmB,OAAA;AACnB,8GAAA,qBAAqB,OAAA;AACrB,iHAAA,wBAAwB,OAAA"} |
+1
-0
@@ -6,3 +6,4 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. | ||
| export { Limrun } from "./client.mjs"; | ||
| export { createInstanceClient } from "./instance-client.mjs"; | ||
| export { LimrunError, APIError, APIConnectionError, APIConnectionTimeoutError, APIUserAbortError, NotFoundError, ConflictError, RateLimitError, BadRequestError, AuthenticationError, InternalServerError, PermissionDeniedError, UnprocessableEntityError, } from "./core/error.mjs"; | ||
| //# sourceMappingURL=index.mjs.map |
+1
-1
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"index.mjs","sourceRoot":"","sources":["src/index.ts"],"names":[],"mappings":"AAAA,sFAAsF;OAE/E,EAAE,MAAM,IAAI,OAAO,EAAE;OAErB,EAAmB,MAAM,EAAE;OAC3B,EAAE,UAAU,EAAE;OACd,EAAE,MAAM,EAAsB;OAC9B,EACL,WAAW,EACX,QAAQ,EACR,kBAAkB,EAClB,yBAAyB,EACzB,iBAAiB,EACjB,aAAa,EACb,aAAa,EACb,cAAc,EACd,eAAe,EACf,mBAAmB,EACnB,mBAAmB,EACnB,qBAAqB,EACrB,wBAAwB,GACzB"} | ||
| {"version":3,"file":"index.mjs","sourceRoot":"","sources":["src/index.ts"],"names":[],"mappings":"AAAA,sFAAsF;OAE/E,EAAE,MAAM,IAAI,OAAO,EAAE;OAErB,EAAmB,MAAM,EAAE;OAC3B,EAAE,UAAU,EAAE;OACd,EAAE,MAAM,EAAsB;OAC9B,EAAE,oBAAoB,EAAE;OACxB,EACL,WAAW,EACX,QAAQ,EACR,kBAAkB,EAClB,yBAAyB,EACzB,iBAAiB,EACjB,aAAa,EACb,aAAa,EACb,cAAc,EACd,eAAe,EACf,mBAAmB,EACnB,mBAAmB,EACnB,qBAAqB,EACrB,wBAAwB,GACzB"} |
@@ -83,3 +83,3 @@ "use strict"; | ||
| const maybeCoerceInteger = (value) => { | ||
| if (value === undefined) { | ||
| if (value == null) { | ||
| return undefined; | ||
@@ -91,3 +91,3 @@ } | ||
| const maybeCoerceFloat = (value) => { | ||
| if (value === undefined) { | ||
| if (value == null) { | ||
| return undefined; | ||
@@ -99,3 +99,3 @@ } | ||
| const maybeCoerceBoolean = (value) => { | ||
| if (value === undefined) { | ||
| if (value == null) { | ||
| return undefined; | ||
@@ -102,0 +102,0 @@ } |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"values.js","sourceRoot":"","sources":["../../src/internal/utils/values.ts"],"names":[],"mappings":";AAAA,sFAAsF;;;AAetF,4BAMC;AAGD,gCAIC;AAGD,wBAEC;AAED,sBAEC;AAnCD,+CAA+C;AAE/C,iDAAiD;AACjD,MAAM,sBAAsB,GAAG,sBAAsB,CAAC;AAE/C,MAAM,aAAa,GAAG,CAAC,GAAW,EAAW,EAAE;IACpD,OAAO,sBAAsB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC1C,CAAC,CAAC;AAFW,QAAA,aAAa,iBAExB;AAEK,IAAI,OAAO,GAAG,CAAC,GAAY,EAAoB,EAAE,CAAC,CAAC,CAAC,eAAO,GAAG,KAAK,CAAC,OAAO,CAAC,EAAE,IAAA,eAAO,EAAC,GAAG,CAAC,CAAC,CAAC;AAAxF,QAAA,OAAO,WAAiF;AACxF,QAAA,eAAe,GAAG,eAAsD,CAAC;AAEpF,oFAAoF;AACpF,SAAgB,QAAQ,CAAC,CAAU;IACjC,IAAI,OAAO,CAAC,KAAK,QAAQ,EAAE,CAAC;QAC1B,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,OAAO,CAAC,IAAI,EAAE,CAAC;AACjB,CAAC;AAED,uCAAuC;AACvC,SAAgB,UAAU,CAAC,GAA8B;IACvD,IAAI,CAAC,GAAG;QAAE,OAAO,IAAI,CAAC;IACtB,KAAK,MAAM,EAAE,IAAI,GAAG;QAAE,OAAO,KAAK,CAAC;IACnC,OAAO,IAAI,CAAC;AACd,CAAC;AAED,6DAA6D;AAC7D,SAAgB,MAAM,CAA4B,GAAM,EAAE,GAAgB;IACxE,OAAO,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;AACxD,CAAC;AAED,SAAgB,KAAK,CAAC,GAAY;IAChC,OAAO,GAAG,IAAI,IAAI,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;AACvE,CAAC;AAEM,MAAM,aAAa,GAAG,CAAI,KAA2B,EAAK,EAAE;IACjE,IAAI,KAAK,IAAI,IAAI,EAAE,CAAC;QAClB,MAAM,IAAI,mBAAW,CAAC,6CAA6C,KAAK,WAAW,CAAC,CAAC;IACvF,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC,CAAC;AANW,QAAA,aAAa,iBAMxB;AAEK,MAAM,uBAAuB,GAAG,CAAC,IAAY,EAAE,CAAU,EAAU,EAAE;IAC1E,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC;QAClD,MAAM,IAAI,mBAAW,CAAC,GAAG,IAAI,qBAAqB,CAAC,CAAC;IACtD,CAAC;IACD,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QACV,MAAM,IAAI,mBAAW,CAAC,GAAG,IAAI,6BAA6B,CAAC,CAAC;IAC9D,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC,CAAC;AARW,QAAA,uBAAuB,2BAQlC;AAEK,MAAM,aAAa,GAAG,CAAC,KAAc,EAAU,EAAE;IACtD,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IACxD,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,QAAQ,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IAE1D,MAAM,IAAI,mBAAW,CAAC,oBAAoB,KAAK,WAAW,OAAO,KAAK,iBAAiB,CAAC,CAAC;AAC3F,CAAC,CAAC;AALW,QAAA,aAAa,iBAKxB;AAEK,MAAM,WAAW,GAAG,CAAC,KAAc,EAAU,EAAE;IACpD,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAC5C,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,UAAU,CAAC,KAAK,CAAC,CAAC;IAExD,MAAM,IAAI,mBAAW,CAAC,oBAAoB,KAAK,WAAW,OAAO,KAAK,iBAAiB,CAAC,CAAC;AAC3F,CAAC,CAAC;AALW,QAAA,WAAW,eAKtB;AAEK,MAAM,aAAa,GAAG,CAAC,KAAc,EAAW,EAAE;IACvD,IAAI,OAAO,KAAK,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IAC7C,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,KAAK,MAAM,CAAC;IACvD,OAAO,OAAO,CAAC,KAAK,CAAC,CAAC;AACxB,CAAC,CAAC;AAJW,QAAA,aAAa,iBAIxB;AAEK,MAAM,kBAAkB,GAAG,CAAC,KAAc,EAAsB,EAAE;IACvE,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACxB,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,OAAO,IAAA,qBAAa,EAAC,KAAK,CAAC,CAAC;AAC9B,CAAC,CAAC;AALW,QAAA,kBAAkB,sBAK7B;AAEK,MAAM,gBAAgB,GAAG,CAAC,KAAc,EAAsB,EAAE;IACrE,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACxB,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,OAAO,IAAA,mBAAW,EAAC,KAAK,CAAC,CAAC;AAC5B,CAAC,CAAC;AALW,QAAA,gBAAgB,oBAK3B;AAEK,MAAM,kBAAkB,GAAG,CAAC,KAAc,EAAuB,EAAE;IACxE,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACxB,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,OAAO,IAAA,qBAAa,EAAC,KAAK,CAAC,CAAC;AAC9B,CAAC,CAAC;AALW,QAAA,kBAAkB,sBAK7B;AAEK,MAAM,QAAQ,GAAG,CAAC,IAAY,EAAE,EAAE;IACvC,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC1B,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC,CAAC;AANW,QAAA,QAAQ,YAMnB"} | ||
| {"version":3,"file":"values.js","sourceRoot":"","sources":["../../src/internal/utils/values.ts"],"names":[],"mappings":"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"} |
@@ -69,3 +69,3 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. | ||
| export const maybeCoerceInteger = (value) => { | ||
| if (value === undefined) { | ||
| if (value == null) { | ||
| return undefined; | ||
@@ -76,3 +76,3 @@ } | ||
| export const maybeCoerceFloat = (value) => { | ||
| if (value === undefined) { | ||
| if (value == null) { | ||
| return undefined; | ||
@@ -83,3 +83,3 @@ } | ||
| export const maybeCoerceBoolean = (value) => { | ||
| if (value === undefined) { | ||
| if (value == null) { | ||
| return undefined; | ||
@@ -86,0 +86,0 @@ } |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"values.mjs","sourceRoot":"","sources":["../../src/internal/utils/values.ts"],"names":[],"mappings":"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"} | ||
| {"version":3,"file":"values.mjs","sourceRoot":"","sources":["../../src/internal/utils/values.ts"],"names":[],"mappings":"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"} |
+21
-1
| { | ||
| "name": "@limrun/api", | ||
| "version": "0.7.0", | ||
| "version": "0.8.0", | ||
| "description": "The official TypeScript library for the Limrun API", | ||
@@ -89,2 +89,12 @@ "author": "Limrun <contact@limrun.com>", | ||
| }, | ||
| "./instance-client": { | ||
| "import": "./instance-client.mjs", | ||
| "require": "./instance-client.js" | ||
| }, | ||
| "./instance-client.js": { | ||
| "default": "./instance-client.js" | ||
| }, | ||
| "./instance-client.mjs": { | ||
| "default": "./instance-client.mjs" | ||
| }, | ||
| "./resource": { | ||
@@ -120,2 +130,12 @@ "import": "./resource.mjs", | ||
| }, | ||
| "./tunnel": { | ||
| "import": "./tunnel.mjs", | ||
| "require": "./tunnel.js" | ||
| }, | ||
| "./tunnel.js": { | ||
| "default": "./tunnel.js" | ||
| }, | ||
| "./tunnel.mjs": { | ||
| "default": "./tunnel.mjs" | ||
| }, | ||
| "./uploads": { | ||
@@ -122,0 +142,0 @@ "import": "./uploads.mjs", |
+1
-0
@@ -8,2 +8,3 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. | ||
| export { Limrun, type ClientOptions } from './client'; | ||
| export { createInstanceClient } from './instance-client'; | ||
| export { | ||
@@ -10,0 +11,0 @@ LimrunError, |
@@ -79,3 +79,3 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. | ||
| export const maybeCoerceInteger = (value: unknown): number | undefined => { | ||
| if (value === undefined) { | ||
| if (value == null) { | ||
| return undefined; | ||
@@ -87,3 +87,3 @@ } | ||
| export const maybeCoerceFloat = (value: unknown): number | undefined => { | ||
| if (value === undefined) { | ||
| if (value == null) { | ||
| return undefined; | ||
@@ -95,3 +95,3 @@ } | ||
| export const maybeCoerceBoolean = (value: unknown): boolean | undefined => { | ||
| if (value === undefined) { | ||
| if (value == null) { | ||
| return undefined; | ||
@@ -98,0 +98,0 @@ } |
+1
-1
@@ -1,1 +0,1 @@ | ||
| export const VERSION = '0.7.0'; // x-release-please-version | ||
| export const VERSION = '0.8.0'; // x-release-please-version |
+1
-1
@@ -1,2 +0,2 @@ | ||
| export declare const VERSION = "0.7.0"; | ||
| export declare const VERSION = "0.8.0"; | ||
| //# sourceMappingURL=version.d.mts.map |
+1
-1
@@ -1,2 +0,2 @@ | ||
| export declare const VERSION = "0.7.0"; | ||
| export declare const VERSION = "0.8.0"; | ||
| //# sourceMappingURL=version.d.ts.map |
+1
-1
| "use strict"; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.VERSION = void 0; | ||
| exports.VERSION = '0.7.0'; // x-release-please-version | ||
| exports.VERSION = '0.8.0'; // x-release-please-version | ||
| //# sourceMappingURL=version.js.map |
+1
-1
@@ -1,2 +0,2 @@ | ||
| export const VERSION = '0.7.0'; // x-release-please-version | ||
| export const VERSION = '0.8.0'; // x-release-please-version | ||
| //# sourceMappingURL=version.mjs.map |
| import type { Tunnel } from "./tunnel.mjs"; | ||
| export type { Tunnel } from "./tunnel.mjs"; | ||
| import { AndroidInstance } from "./android-instances.mjs"; | ||
| /** | ||
| * A client for interacting with a Limbar instance | ||
| */ | ||
| export type InstanceClient = { | ||
| /** | ||
| * Take a screenshot of the current screen | ||
| * @returns A promise that resolves to the screenshot data | ||
| */ | ||
| screenshot: () => Promise<ScreenshotData>; | ||
| /** | ||
| * Disconnect from the Limbar instance | ||
| */ | ||
| disconnect: () => void; | ||
| /** | ||
| * Establish an ADB tunnel to the instance. | ||
| * Returns the local TCP port and a cleanup function. | ||
| */ | ||
| startAdbTunnel: () => Promise<Tunnel>; | ||
| /** | ||
| * Send an asset URL to the instance. The instance will download the asset | ||
| * and process it (currently APK install is supported). Resolves on success, | ||
| * rejects with an Error on failure. | ||
| */ | ||
| sendAsset: (url: string) => Promise<void>; | ||
| }; | ||
| /** | ||
| * Controls the verbosity of logging in the client | ||
| */ | ||
| export type LogLevel = 'none' | 'error' | 'warn' | 'info' | 'debug'; | ||
| /** | ||
| * Configuration options for creating an Instance API client | ||
| */ | ||
| export type InstanceClientOptions = { | ||
| /** | ||
| * Path to the ADB executable. | ||
| * @default 'adb' | ||
| */ | ||
| adbPath?: string; | ||
| /** | ||
| * Controls logging verbosity | ||
| * @default 'info' | ||
| */ | ||
| logLevel?: LogLevel; | ||
| }; | ||
| type ScreenshotData = { | ||
| dataUri: string; | ||
| }; | ||
| /** | ||
| * Creates a client for interacting with a Limbar instance | ||
| * @param options Configuration options including webrtcUrl, token and log level | ||
| * @returns An InstanceClient for controlling the instance | ||
| */ | ||
| export declare function createInstanceClient(androidInstance: AndroidInstance, options?: InstanceClientOptions): Promise<InstanceClient>; | ||
| //# sourceMappingURL=instance-client.d.mts.map |
| {"version":3,"file":"instance-client.d.mts","sourceRoot":"","sources":["../src/resources/instance-client.ts"],"names":[],"mappings":"OAIO,KAAK,EAAE,MAAM,EAAE;YACV,EAAE,MAAM,EAAE;OACf,EAAE,eAAe,EAAE;AAE1B;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B;;;OAGG;IACH,UAAU,EAAE,MAAM,OAAO,CAAC,cAAc,CAAC,CAAC;IAC1C;;OAEG;IACH,UAAU,EAAE,MAAM,IAAI,CAAC;IAEvB;;;OAGG;IACH,cAAc,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;IACtC;;;;OAIG;IACH,SAAS,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3C,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;AAEpE;;GAEG;AACH,MAAM,MAAM,qBAAqB,GAAG;IAClC;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,QAAQ,CAAC,EAAE,QAAQ,CAAC;CACrB,CAAC;AAaF,KAAK,cAAc,GAAG;IACpB,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AA0BF;;;;GAIG;AACH,wBAAsB,oBAAoB,CACxC,eAAe,EAAE,eAAe,EAChC,OAAO,GAAE,qBAGR,GACA,OAAO,CAAC,cAAc,CAAC,CAmPzB"} |
| import type { Tunnel } from "./tunnel.js"; | ||
| export type { Tunnel } from "./tunnel.js"; | ||
| import { AndroidInstance } from "./android-instances.js"; | ||
| /** | ||
| * A client for interacting with a Limbar instance | ||
| */ | ||
| export type InstanceClient = { | ||
| /** | ||
| * Take a screenshot of the current screen | ||
| * @returns A promise that resolves to the screenshot data | ||
| */ | ||
| screenshot: () => Promise<ScreenshotData>; | ||
| /** | ||
| * Disconnect from the Limbar instance | ||
| */ | ||
| disconnect: () => void; | ||
| /** | ||
| * Establish an ADB tunnel to the instance. | ||
| * Returns the local TCP port and a cleanup function. | ||
| */ | ||
| startAdbTunnel: () => Promise<Tunnel>; | ||
| /** | ||
| * Send an asset URL to the instance. The instance will download the asset | ||
| * and process it (currently APK install is supported). Resolves on success, | ||
| * rejects with an Error on failure. | ||
| */ | ||
| sendAsset: (url: string) => Promise<void>; | ||
| }; | ||
| /** | ||
| * Controls the verbosity of logging in the client | ||
| */ | ||
| export type LogLevel = 'none' | 'error' | 'warn' | 'info' | 'debug'; | ||
| /** | ||
| * Configuration options for creating an Instance API client | ||
| */ | ||
| export type InstanceClientOptions = { | ||
| /** | ||
| * Path to the ADB executable. | ||
| * @default 'adb' | ||
| */ | ||
| adbPath?: string; | ||
| /** | ||
| * Controls logging verbosity | ||
| * @default 'info' | ||
| */ | ||
| logLevel?: LogLevel; | ||
| }; | ||
| type ScreenshotData = { | ||
| dataUri: string; | ||
| }; | ||
| /** | ||
| * Creates a client for interacting with a Limbar instance | ||
| * @param options Configuration options including webrtcUrl, token and log level | ||
| * @returns An InstanceClient for controlling the instance | ||
| */ | ||
| export declare function createInstanceClient(androidInstance: AndroidInstance, options?: InstanceClientOptions): Promise<InstanceClient>; | ||
| //# sourceMappingURL=instance-client.d.ts.map |
| {"version":3,"file":"instance-client.d.ts","sourceRoot":"","sources":["../src/resources/instance-client.ts"],"names":[],"mappings":"OAIO,KAAK,EAAE,MAAM,EAAE;YACV,EAAE,MAAM,EAAE;OACf,EAAE,eAAe,EAAE;AAE1B;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B;;;OAGG;IACH,UAAU,EAAE,MAAM,OAAO,CAAC,cAAc,CAAC,CAAC;IAC1C;;OAEG;IACH,UAAU,EAAE,MAAM,IAAI,CAAC;IAEvB;;;OAGG;IACH,cAAc,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;IACtC;;;;OAIG;IACH,SAAS,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3C,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;AAEpE;;GAEG;AACH,MAAM,MAAM,qBAAqB,GAAG;IAClC;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,QAAQ,CAAC,EAAE,QAAQ,CAAC;CACrB,CAAC;AAaF,KAAK,cAAc,GAAG;IACpB,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AA0BF;;;;GAIG;AACH,wBAAsB,oBAAoB,CACxC,eAAe,EAAE,eAAe,EAChC,OAAO,GAAE,qBAGR,GACA,OAAO,CAAC,cAAc,CAAC,CAmPzB"} |
| "use strict"; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.createInstanceClient = createInstanceClient; | ||
| const ws_1 = require("ws"); | ||
| const node_child_process_1 = require("node:child_process"); | ||
| const tunnel_js_1 = require("./tunnel.js"); | ||
| /** | ||
| * Creates a client for interacting with a Limbar instance | ||
| * @param options Configuration options including webrtcUrl, token and log level | ||
| * @returns An InstanceClient for controlling the instance | ||
| */ | ||
| async function createInstanceClient(androidInstance, options = { | ||
| adbPath: 'adb', | ||
| logLevel: 'info', | ||
| }) { | ||
| const token = androidInstance.status.token; | ||
| const serverAddress = `${androidInstance.status.endpointWebSocketUrl}?token=${token}`; | ||
| const logLevel = options.logLevel ?? 'info'; | ||
| let ws = undefined; | ||
| const screenshotRequests = new Map(); | ||
| const assetRequests = new Map(); | ||
| // Logger functions | ||
| const logger = { | ||
| debug: (...args) => { | ||
| if (logLevel === 'debug') | ||
| console.log(...args); | ||
| }, | ||
| info: (...args) => { | ||
| if (logLevel === 'info' || logLevel === 'debug') | ||
| console.log(...args); | ||
| }, | ||
| warn: (...args) => { | ||
| if (logLevel === 'warn' || logLevel === 'info' || logLevel === 'debug') | ||
| console.warn(...args); | ||
| }, | ||
| error: (...args) => { | ||
| if (logLevel !== 'none') | ||
| console.error(...args); | ||
| }, | ||
| }; | ||
| return new Promise((resolveConnection, rejectConnection) => { | ||
| logger.debug(`Attempting to connect to WebSocket server at ${serverAddress}...`); | ||
| ws = new ws_1.WebSocket(serverAddress); | ||
| ws.on('message', (data) => { | ||
| let message; | ||
| try { | ||
| message = JSON.parse(data.toString()); | ||
| } | ||
| catch (e) { | ||
| logger.error({ data, error: e }, 'Failed to parse JSON message'); | ||
| return; | ||
| } | ||
| switch (message.type) { | ||
| case 'screenshot': { | ||
| if (!('dataUri' in message) || typeof message.dataUri !== 'string' || !('id' in message)) { | ||
| logger.warn('Received invalid screenshot message:', message); | ||
| break; | ||
| } | ||
| const screenshotMessage = message; | ||
| const request = screenshotRequests.get(screenshotMessage.id); | ||
| if (!request) { | ||
| logger.warn(`Received screenshot data for unknown or already handled session: ${screenshotMessage.id}`); | ||
| break; | ||
| } | ||
| logger.debug(`Received screenshot data URI for session ${screenshotMessage.id}.`); | ||
| request.resolver({ dataUri: screenshotMessage.dataUri }); | ||
| screenshotRequests.delete(screenshotMessage.id); | ||
| break; | ||
| } | ||
| case 'screenshotError': { | ||
| if (!('message' in message) || !('id' in message)) { | ||
| logger.warn('Received invalid screenshot error message:', message); | ||
| break; | ||
| } | ||
| const errorMessage = message; | ||
| const request = screenshotRequests.get(errorMessage.id); | ||
| if (!request) { | ||
| logger.warn(`Received screenshot error for unknown or already handled session: ${errorMessage.id}`); | ||
| break; | ||
| } | ||
| logger.error(`Server reported an error capturing screenshot for session ${errorMessage.id}:`, errorMessage.message); | ||
| request.rejecter(new Error(errorMessage.message)); | ||
| screenshotRequests.delete(errorMessage.id); | ||
| break; | ||
| } | ||
| case 'assetResult': { | ||
| logger.debug('Received assetResult:', message); | ||
| const request = assetRequests.get(message.url); | ||
| if (!request) { | ||
| logger.warn(`Received assetResult for unknown or already handled url: ${message.url}`); | ||
| break; | ||
| } | ||
| if (message.result === 'success') { | ||
| logger.debug('Asset result is success'); | ||
| request.resolver(); | ||
| assetRequests.delete(message.url); | ||
| break; | ||
| } | ||
| const errorMessage = typeof message.message === 'string' && message.message ? | ||
| message.message | ||
| : `Asset processing failed: ${JSON.stringify(message)}`; | ||
| logger.debug('Asset result is failure', errorMessage); | ||
| request.rejecter(new Error(errorMessage)); | ||
| assetRequests.delete(message.url); | ||
| break; | ||
| } | ||
| default: | ||
| logger.warn(`Received unexpected message type: ${message.type}`); | ||
| break; | ||
| } | ||
| }); | ||
| ws.on('error', (err) => { | ||
| logger.error('WebSocket error:', err.message); | ||
| if (ws && (ws.readyState === ws_1.WebSocket.CONNECTING || ws.readyState === ws_1.WebSocket.OPEN)) { | ||
| rejectConnection(err); | ||
| } | ||
| screenshotRequests.forEach((request) => request.rejecter(err)); | ||
| }); | ||
| ws.on('close', () => { | ||
| logger.debug('Disconnected from server.'); | ||
| screenshotRequests.forEach((request) => request.rejecter('Disconnected from server')); | ||
| }); | ||
| const screenshot = async () => { | ||
| if (!ws || ws.readyState !== ws_1.WebSocket.OPEN) { | ||
| return Promise.reject(new Error('WebSocket is not connected or connection is not open.')); | ||
| } | ||
| const id = 'ts-client-' + Date.now(); | ||
| const screenshotRequest = { | ||
| type: 'screenshot', | ||
| id, | ||
| }; | ||
| return new Promise((resolve, reject) => { | ||
| logger.debug('Sending screenshot request:', screenshotRequest); | ||
| ws.send(JSON.stringify(screenshotRequest), (err) => { | ||
| if (err) { | ||
| logger.error('Failed to send screenshot request:', err); | ||
| reject(err); | ||
| } | ||
| }); | ||
| const timeout = setTimeout(() => { | ||
| if (screenshotRequests.has(id)) { | ||
| logger.error(`Screenshot request timed out for session ${id}`); | ||
| screenshotRequests.get(id)?.rejecter(new Error('Screenshot request timed out')); | ||
| screenshotRequests.delete(id); | ||
| } | ||
| }, 30000); | ||
| screenshotRequests.set(id, { | ||
| resolver: (value) => { | ||
| clearTimeout(timeout); | ||
| resolve(value); | ||
| screenshotRequests.delete(id); | ||
| }, | ||
| rejecter: (reason) => { | ||
| clearTimeout(timeout); | ||
| reject(reason); | ||
| screenshotRequests.delete(id); | ||
| }, | ||
| }); | ||
| }); | ||
| }; | ||
| const disconnect = () => { | ||
| if (ws) { | ||
| logger.debug('Closing WebSocket connection.'); | ||
| ws.close(); | ||
| } | ||
| screenshotRequests.forEach((request) => request.rejecter('Websocket connection closed')); | ||
| }; | ||
| /** | ||
| * Opens a WebSocket TCP proxy for the ADB port and connects the local adb | ||
| * client to it. | ||
| */ | ||
| const startAdbTunnel = async () => { | ||
| if (!androidInstance.status.adbWebSocketUrl) { | ||
| return Promise.reject(new Error('ADB WebSocket URL is not set')); | ||
| } | ||
| const { address, close } = await (0, tunnel_js_1.startTcpTunnel)(androidInstance.status.adbWebSocketUrl, token, '127.0.0.1', 0); | ||
| try { | ||
| await new Promise((resolve, reject) => { | ||
| (0, node_child_process_1.exec)(`${options.adbPath ?? 'adb'} connect ${address.address}:${address.port}`, (err) => { | ||
| if (err) | ||
| return reject(err); | ||
| resolve(); | ||
| }); | ||
| }); | ||
| logger.debug(`ADB connected on ${address.address}`); | ||
| } | ||
| catch (err) { | ||
| close(); | ||
| throw err; | ||
| } | ||
| return { address, close }; | ||
| }; | ||
| const sendAsset = async (url) => { | ||
| if (!ws || ws.readyState !== ws_1.WebSocket.OPEN) { | ||
| return Promise.reject(new Error('WebSocket is not connected or connection is not open.')); | ||
| } | ||
| const assetRequest = { | ||
| type: 'asset', | ||
| url, | ||
| }; | ||
| ws.send(JSON.stringify(assetRequest), (err) => { | ||
| if (err) { | ||
| logger.error('Failed to send asset request:', err); | ||
| } | ||
| }); | ||
| return new Promise((resolve, reject) => { | ||
| assetRequests.set(url, { resolver: resolve, rejecter: reject }); | ||
| }); | ||
| }; | ||
| ws.on('open', () => { | ||
| logger.debug(`Connected to ${serverAddress}`); | ||
| resolveConnection({ | ||
| screenshot, | ||
| disconnect, | ||
| startAdbTunnel, | ||
| sendAsset, | ||
| }); | ||
| }); | ||
| }); | ||
| } | ||
| //# sourceMappingURL=instance-client.js.map |
| {"version":3,"file":"instance-client.js","sourceRoot":"","sources":["../src/resources/instance-client.ts"],"names":[],"mappings":"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"} |
| import { WebSocket } from 'ws'; | ||
| import { exec } from 'node:child_process'; | ||
| import { startTcpTunnel } from "./tunnel.mjs"; | ||
| /** | ||
| * Creates a client for interacting with a Limbar instance | ||
| * @param options Configuration options including webrtcUrl, token and log level | ||
| * @returns An InstanceClient for controlling the instance | ||
| */ | ||
| export async function createInstanceClient(androidInstance, options = { | ||
| adbPath: 'adb', | ||
| logLevel: 'info', | ||
| }) { | ||
| const token = androidInstance.status.token; | ||
| const serverAddress = `${androidInstance.status.endpointWebSocketUrl}?token=${token}`; | ||
| const logLevel = options.logLevel ?? 'info'; | ||
| let ws = undefined; | ||
| const screenshotRequests = new Map(); | ||
| const assetRequests = new Map(); | ||
| // Logger functions | ||
| const logger = { | ||
| debug: (...args) => { | ||
| if (logLevel === 'debug') | ||
| console.log(...args); | ||
| }, | ||
| info: (...args) => { | ||
| if (logLevel === 'info' || logLevel === 'debug') | ||
| console.log(...args); | ||
| }, | ||
| warn: (...args) => { | ||
| if (logLevel === 'warn' || logLevel === 'info' || logLevel === 'debug') | ||
| console.warn(...args); | ||
| }, | ||
| error: (...args) => { | ||
| if (logLevel !== 'none') | ||
| console.error(...args); | ||
| }, | ||
| }; | ||
| return new Promise((resolveConnection, rejectConnection) => { | ||
| logger.debug(`Attempting to connect to WebSocket server at ${serverAddress}...`); | ||
| ws = new WebSocket(serverAddress); | ||
| ws.on('message', (data) => { | ||
| let message; | ||
| try { | ||
| message = JSON.parse(data.toString()); | ||
| } | ||
| catch (e) { | ||
| logger.error({ data, error: e }, 'Failed to parse JSON message'); | ||
| return; | ||
| } | ||
| switch (message.type) { | ||
| case 'screenshot': { | ||
| if (!('dataUri' in message) || typeof message.dataUri !== 'string' || !('id' in message)) { | ||
| logger.warn('Received invalid screenshot message:', message); | ||
| break; | ||
| } | ||
| const screenshotMessage = message; | ||
| const request = screenshotRequests.get(screenshotMessage.id); | ||
| if (!request) { | ||
| logger.warn(`Received screenshot data for unknown or already handled session: ${screenshotMessage.id}`); | ||
| break; | ||
| } | ||
| logger.debug(`Received screenshot data URI for session ${screenshotMessage.id}.`); | ||
| request.resolver({ dataUri: screenshotMessage.dataUri }); | ||
| screenshotRequests.delete(screenshotMessage.id); | ||
| break; | ||
| } | ||
| case 'screenshotError': { | ||
| if (!('message' in message) || !('id' in message)) { | ||
| logger.warn('Received invalid screenshot error message:', message); | ||
| break; | ||
| } | ||
| const errorMessage = message; | ||
| const request = screenshotRequests.get(errorMessage.id); | ||
| if (!request) { | ||
| logger.warn(`Received screenshot error for unknown or already handled session: ${errorMessage.id}`); | ||
| break; | ||
| } | ||
| logger.error(`Server reported an error capturing screenshot for session ${errorMessage.id}:`, errorMessage.message); | ||
| request.rejecter(new Error(errorMessage.message)); | ||
| screenshotRequests.delete(errorMessage.id); | ||
| break; | ||
| } | ||
| case 'assetResult': { | ||
| logger.debug('Received assetResult:', message); | ||
| const request = assetRequests.get(message.url); | ||
| if (!request) { | ||
| logger.warn(`Received assetResult for unknown or already handled url: ${message.url}`); | ||
| break; | ||
| } | ||
| if (message.result === 'success') { | ||
| logger.debug('Asset result is success'); | ||
| request.resolver(); | ||
| assetRequests.delete(message.url); | ||
| break; | ||
| } | ||
| const errorMessage = typeof message.message === 'string' && message.message ? | ||
| message.message | ||
| : `Asset processing failed: ${JSON.stringify(message)}`; | ||
| logger.debug('Asset result is failure', errorMessage); | ||
| request.rejecter(new Error(errorMessage)); | ||
| assetRequests.delete(message.url); | ||
| break; | ||
| } | ||
| default: | ||
| logger.warn(`Received unexpected message type: ${message.type}`); | ||
| break; | ||
| } | ||
| }); | ||
| ws.on('error', (err) => { | ||
| logger.error('WebSocket error:', err.message); | ||
| if (ws && (ws.readyState === WebSocket.CONNECTING || ws.readyState === WebSocket.OPEN)) { | ||
| rejectConnection(err); | ||
| } | ||
| screenshotRequests.forEach((request) => request.rejecter(err)); | ||
| }); | ||
| ws.on('close', () => { | ||
| logger.debug('Disconnected from server.'); | ||
| screenshotRequests.forEach((request) => request.rejecter('Disconnected from server')); | ||
| }); | ||
| const screenshot = async () => { | ||
| if (!ws || ws.readyState !== WebSocket.OPEN) { | ||
| return Promise.reject(new Error('WebSocket is not connected or connection is not open.')); | ||
| } | ||
| const id = 'ts-client-' + Date.now(); | ||
| const screenshotRequest = { | ||
| type: 'screenshot', | ||
| id, | ||
| }; | ||
| return new Promise((resolve, reject) => { | ||
| logger.debug('Sending screenshot request:', screenshotRequest); | ||
| ws.send(JSON.stringify(screenshotRequest), (err) => { | ||
| if (err) { | ||
| logger.error('Failed to send screenshot request:', err); | ||
| reject(err); | ||
| } | ||
| }); | ||
| const timeout = setTimeout(() => { | ||
| if (screenshotRequests.has(id)) { | ||
| logger.error(`Screenshot request timed out for session ${id}`); | ||
| screenshotRequests.get(id)?.rejecter(new Error('Screenshot request timed out')); | ||
| screenshotRequests.delete(id); | ||
| } | ||
| }, 30000); | ||
| screenshotRequests.set(id, { | ||
| resolver: (value) => { | ||
| clearTimeout(timeout); | ||
| resolve(value); | ||
| screenshotRequests.delete(id); | ||
| }, | ||
| rejecter: (reason) => { | ||
| clearTimeout(timeout); | ||
| reject(reason); | ||
| screenshotRequests.delete(id); | ||
| }, | ||
| }); | ||
| }); | ||
| }; | ||
| const disconnect = () => { | ||
| if (ws) { | ||
| logger.debug('Closing WebSocket connection.'); | ||
| ws.close(); | ||
| } | ||
| screenshotRequests.forEach((request) => request.rejecter('Websocket connection closed')); | ||
| }; | ||
| /** | ||
| * Opens a WebSocket TCP proxy for the ADB port and connects the local adb | ||
| * client to it. | ||
| */ | ||
| const startAdbTunnel = async () => { | ||
| if (!androidInstance.status.adbWebSocketUrl) { | ||
| return Promise.reject(new Error('ADB WebSocket URL is not set')); | ||
| } | ||
| const { address, close } = await startTcpTunnel(androidInstance.status.adbWebSocketUrl, token, '127.0.0.1', 0); | ||
| try { | ||
| await new Promise((resolve, reject) => { | ||
| exec(`${options.adbPath ?? 'adb'} connect ${address.address}:${address.port}`, (err) => { | ||
| if (err) | ||
| return reject(err); | ||
| resolve(); | ||
| }); | ||
| }); | ||
| logger.debug(`ADB connected on ${address.address}`); | ||
| } | ||
| catch (err) { | ||
| close(); | ||
| throw err; | ||
| } | ||
| return { address, close }; | ||
| }; | ||
| const sendAsset = async (url) => { | ||
| if (!ws || ws.readyState !== WebSocket.OPEN) { | ||
| return Promise.reject(new Error('WebSocket is not connected or connection is not open.')); | ||
| } | ||
| const assetRequest = { | ||
| type: 'asset', | ||
| url, | ||
| }; | ||
| ws.send(JSON.stringify(assetRequest), (err) => { | ||
| if (err) { | ||
| logger.error('Failed to send asset request:', err); | ||
| } | ||
| }); | ||
| return new Promise((resolve, reject) => { | ||
| assetRequests.set(url, { resolver: resolve, rejecter: reject }); | ||
| }); | ||
| }; | ||
| ws.on('open', () => { | ||
| logger.debug(`Connected to ${serverAddress}`); | ||
| resolveConnection({ | ||
| screenshot, | ||
| disconnect, | ||
| startAdbTunnel, | ||
| sendAsset, | ||
| }); | ||
| }); | ||
| }); | ||
| } | ||
| //# sourceMappingURL=instance-client.mjs.map |
| {"version":3,"file":"instance-client.mjs","sourceRoot":"","sources":["../src/resources/instance-client.ts"],"names":[],"mappings":"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"} |
| export interface Tunnel { | ||
| address: { | ||
| address: string; | ||
| port: number; | ||
| }; | ||
| close: () => void; | ||
| } | ||
| /** | ||
| * Starts a one-shot TCP → WebSocket proxy. | ||
| * | ||
| * The function creates a local TCP server that listens on an ephemeral port on | ||
| * 127.0.0.1. As soon as the **first** TCP client connects the server stops | ||
| * accepting further connections and forwards all traffic between that client | ||
| * and `remoteURL` through an authenticated WebSocket. If you need to proxy | ||
| * more than one TCP connection, call `startTcpTunnel` again to create a new | ||
| * proxy instance. | ||
| * | ||
| * @param remoteURL Remote WebSocket endpoint (e.g. wss://example.com/instance) | ||
| * @param token Bearer token sent as `Authorization` header | ||
| * @param hostname Optional IP address to listen on. Default is 127.0.0.1 | ||
| * @param port Optional port number to listen on. Default is to ask Node.js | ||
| * to find an available non-privileged port. | ||
| */ | ||
| export declare function startTcpTunnel(remoteURL: string, token: string, hostname: string, port: number): Promise<Tunnel>; | ||
| //# sourceMappingURL=tunnel.d.mts.map |
| {"version":3,"file":"tunnel.d.mts","sourceRoot":"","sources":["../src/resources/tunnel.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,MAAM;IACrB,OAAO,EAAE;QACP,OAAO,EAAE,MAAM,CAAC;QAChB,IAAI,EAAE,MAAM,CAAC;KACd,CAAC;IACF,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,cAAc,CAClC,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,MAAM,CAAC,CA6FjB"} |
| export interface Tunnel { | ||
| address: { | ||
| address: string; | ||
| port: number; | ||
| }; | ||
| close: () => void; | ||
| } | ||
| /** | ||
| * Starts a one-shot TCP → WebSocket proxy. | ||
| * | ||
| * The function creates a local TCP server that listens on an ephemeral port on | ||
| * 127.0.0.1. As soon as the **first** TCP client connects the server stops | ||
| * accepting further connections and forwards all traffic between that client | ||
| * and `remoteURL` through an authenticated WebSocket. If you need to proxy | ||
| * more than one TCP connection, call `startTcpTunnel` again to create a new | ||
| * proxy instance. | ||
| * | ||
| * @param remoteURL Remote WebSocket endpoint (e.g. wss://example.com/instance) | ||
| * @param token Bearer token sent as `Authorization` header | ||
| * @param hostname Optional IP address to listen on. Default is 127.0.0.1 | ||
| * @param port Optional port number to listen on. Default is to ask Node.js | ||
| * to find an available non-privileged port. | ||
| */ | ||
| export declare function startTcpTunnel(remoteURL: string, token: string, hostname: string, port: number): Promise<Tunnel>; | ||
| //# sourceMappingURL=tunnel.d.ts.map |
| {"version":3,"file":"tunnel.d.ts","sourceRoot":"","sources":["../src/resources/tunnel.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,MAAM;IACrB,OAAO,EAAE;QACP,OAAO,EAAE,MAAM,CAAC;QAChB,IAAI,EAAE,MAAM,CAAC;KACd,CAAC;IACF,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,cAAc,CAClC,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,MAAM,CAAC,CA6FjB"} |
| "use strict"; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.startTcpTunnel = startTcpTunnel; | ||
| const tslib_1 = require("../internal/tslib.js"); | ||
| const net = tslib_1.__importStar(require("net")); | ||
| const ws_1 = require("ws"); | ||
| /** | ||
| * Starts a one-shot TCP → WebSocket proxy. | ||
| * | ||
| * The function creates a local TCP server that listens on an ephemeral port on | ||
| * 127.0.0.1. As soon as the **first** TCP client connects the server stops | ||
| * accepting further connections and forwards all traffic between that client | ||
| * and `remoteURL` through an authenticated WebSocket. If you need to proxy | ||
| * more than one TCP connection, call `startTcpTunnel` again to create a new | ||
| * proxy instance. | ||
| * | ||
| * @param remoteURL Remote WebSocket endpoint (e.g. wss://example.com/instance) | ||
| * @param token Bearer token sent as `Authorization` header | ||
| * @param hostname Optional IP address to listen on. Default is 127.0.0.1 | ||
| * @param port Optional port number to listen on. Default is to ask Node.js | ||
| * to find an available non-privileged port. | ||
| */ | ||
| async function startTcpTunnel(remoteURL, token, hostname, port) { | ||
| return new Promise((resolve, reject) => { | ||
| const server = net.createServer(); | ||
| let ws; | ||
| let pingInterval; | ||
| // close helper | ||
| const close = () => { | ||
| if (pingInterval) { | ||
| clearInterval(pingInterval); | ||
| pingInterval = undefined; | ||
| } | ||
| if (ws && ws.readyState === ws_1.WebSocket.OPEN) { | ||
| ws.close(1000, 'close'); | ||
| } | ||
| if (server.listening) { | ||
| server.close(); | ||
| } | ||
| }; | ||
| // No AbortController support – proxy can be closed via the returned handle | ||
| // TCP server error | ||
| server.once('error', (err) => { | ||
| close(); | ||
| reject(new Error(`TCP server error: ${err.message}`)); | ||
| }); | ||
| // Listening | ||
| server.once('listening', () => { | ||
| const address = server.address(); | ||
| if (!address || typeof address === 'string') { | ||
| close(); | ||
| return reject(new Error('Failed to obtain listening address')); | ||
| } | ||
| resolve({ address, close }); | ||
| }); | ||
| // On first TCP connection | ||
| server.on('connection', (tcpSocket) => { | ||
| // Single-connection proxy | ||
| server.close(); | ||
| ws = new ws_1.WebSocket(remoteURL, { | ||
| headers: { Authorization: `Bearer ${token}` }, | ||
| perMessageDeflate: false, | ||
| }); | ||
| // WebSocket error | ||
| ws.once('error', (err) => { | ||
| console.error('WebSocket error:', err); | ||
| tcpSocket.destroy(); | ||
| close(); | ||
| }); | ||
| ws.once('open', () => { | ||
| const socket = ws; // non-undefined after open | ||
| pingInterval = setInterval(() => { | ||
| if (socket.readyState === ws_1.WebSocket.OPEN) { | ||
| socket.ping(); | ||
| } | ||
| }, 30000); | ||
| // TCP → WS | ||
| tcpSocket.on('data', (chunk) => { | ||
| if (socket.readyState === ws_1.WebSocket.OPEN) { | ||
| socket.send(chunk); | ||
| } | ||
| }); | ||
| // WS → TCP | ||
| socket.on('message', (data) => { | ||
| if (!tcpSocket.destroyed) { | ||
| tcpSocket.write(data); | ||
| } | ||
| }); | ||
| }); | ||
| // Mutual close | ||
| tcpSocket.on('close', close); | ||
| tcpSocket.on('error', (err) => { | ||
| console.error('TCP socket error:', err); | ||
| close(); | ||
| }); | ||
| ws.on('close', () => tcpSocket.destroy()); | ||
| }); | ||
| // Start listening | ||
| server.listen(port, hostname); | ||
| }); | ||
| } | ||
| //# sourceMappingURL=tunnel.js.map |
| {"version":3,"file":"tunnel.js","sourceRoot":"","sources":["../src/resources/tunnel.ts"],"names":[],"mappings":"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"} |
| import * as net from 'net'; | ||
| import { WebSocket } from 'ws'; | ||
| /** | ||
| * Starts a one-shot TCP → WebSocket proxy. | ||
| * | ||
| * The function creates a local TCP server that listens on an ephemeral port on | ||
| * 127.0.0.1. As soon as the **first** TCP client connects the server stops | ||
| * accepting further connections and forwards all traffic between that client | ||
| * and `remoteURL` through an authenticated WebSocket. If you need to proxy | ||
| * more than one TCP connection, call `startTcpTunnel` again to create a new | ||
| * proxy instance. | ||
| * | ||
| * @param remoteURL Remote WebSocket endpoint (e.g. wss://example.com/instance) | ||
| * @param token Bearer token sent as `Authorization` header | ||
| * @param hostname Optional IP address to listen on. Default is 127.0.0.1 | ||
| * @param port Optional port number to listen on. Default is to ask Node.js | ||
| * to find an available non-privileged port. | ||
| */ | ||
| export async function startTcpTunnel(remoteURL, token, hostname, port) { | ||
| return new Promise((resolve, reject) => { | ||
| const server = net.createServer(); | ||
| let ws; | ||
| let pingInterval; | ||
| // close helper | ||
| const close = () => { | ||
| if (pingInterval) { | ||
| clearInterval(pingInterval); | ||
| pingInterval = undefined; | ||
| } | ||
| if (ws && ws.readyState === WebSocket.OPEN) { | ||
| ws.close(1000, 'close'); | ||
| } | ||
| if (server.listening) { | ||
| server.close(); | ||
| } | ||
| }; | ||
| // No AbortController support – proxy can be closed via the returned handle | ||
| // TCP server error | ||
| server.once('error', (err) => { | ||
| close(); | ||
| reject(new Error(`TCP server error: ${err.message}`)); | ||
| }); | ||
| // Listening | ||
| server.once('listening', () => { | ||
| const address = server.address(); | ||
| if (!address || typeof address === 'string') { | ||
| close(); | ||
| return reject(new Error('Failed to obtain listening address')); | ||
| } | ||
| resolve({ address, close }); | ||
| }); | ||
| // On first TCP connection | ||
| server.on('connection', (tcpSocket) => { | ||
| // Single-connection proxy | ||
| server.close(); | ||
| ws = new WebSocket(remoteURL, { | ||
| headers: { Authorization: `Bearer ${token}` }, | ||
| perMessageDeflate: false, | ||
| }); | ||
| // WebSocket error | ||
| ws.once('error', (err) => { | ||
| console.error('WebSocket error:', err); | ||
| tcpSocket.destroy(); | ||
| close(); | ||
| }); | ||
| ws.once('open', () => { | ||
| const socket = ws; // non-undefined after open | ||
| pingInterval = setInterval(() => { | ||
| if (socket.readyState === WebSocket.OPEN) { | ||
| socket.ping(); | ||
| } | ||
| }, 30000); | ||
| // TCP → WS | ||
| tcpSocket.on('data', (chunk) => { | ||
| if (socket.readyState === WebSocket.OPEN) { | ||
| socket.send(chunk); | ||
| } | ||
| }); | ||
| // WS → TCP | ||
| socket.on('message', (data) => { | ||
| if (!tcpSocket.destroyed) { | ||
| tcpSocket.write(data); | ||
| } | ||
| }); | ||
| }); | ||
| // Mutual close | ||
| tcpSocket.on('close', close); | ||
| tcpSocket.on('error', (err) => { | ||
| console.error('TCP socket error:', err); | ||
| close(); | ||
| }); | ||
| ws.on('close', () => tcpSocket.destroy()); | ||
| }); | ||
| // Start listening | ||
| server.listen(port, hostname); | ||
| }); | ||
| } | ||
| //# sourceMappingURL=tunnel.mjs.map |
| {"version":3,"file":"tunnel.mjs","sourceRoot":"","sources":["../src/resources/tunnel.ts"],"names":[],"mappings":"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"} |
| import { WebSocket, Data } from 'ws'; | ||
| import { exec } from 'node:child_process'; | ||
| import { startTcpTunnel } from './tunnel.js'; | ||
| import type { Tunnel } from './tunnel.js'; | ||
| export type { Tunnel } from './tunnel.js'; | ||
| import { AndroidInstance } from './android-instances.js'; | ||
| /** | ||
| * A client for interacting with a Limbar instance | ||
| */ | ||
| export type InstanceClient = { | ||
| /** | ||
| * Take a screenshot of the current screen | ||
| * @returns A promise that resolves to the screenshot data | ||
| */ | ||
| screenshot: () => Promise<ScreenshotData>; | ||
| /** | ||
| * Disconnect from the Limbar instance | ||
| */ | ||
| disconnect: () => void; | ||
| /** | ||
| * Establish an ADB tunnel to the instance. | ||
| * Returns the local TCP port and a cleanup function. | ||
| */ | ||
| startAdbTunnel: () => Promise<Tunnel>; | ||
| /** | ||
| * Send an asset URL to the instance. The instance will download the asset | ||
| * and process it (currently APK install is supported). Resolves on success, | ||
| * rejects with an Error on failure. | ||
| */ | ||
| sendAsset: (url: string) => Promise<void>; | ||
| }; | ||
| /** | ||
| * Controls the verbosity of logging in the client | ||
| */ | ||
| export type LogLevel = 'none' | 'error' | 'warn' | 'info' | 'debug'; | ||
| /** | ||
| * Configuration options for creating an Instance API client | ||
| */ | ||
| export type InstanceClientOptions = { | ||
| /** | ||
| * Path to the ADB executable. | ||
| * @default 'adb' | ||
| */ | ||
| adbPath?: string; | ||
| /** | ||
| * Controls logging verbosity | ||
| * @default 'info' | ||
| */ | ||
| logLevel?: LogLevel; | ||
| }; | ||
| type ScreenshotRequest = { | ||
| type: 'screenshot'; | ||
| id: string; | ||
| }; | ||
| type ScreenshotResponse = { | ||
| type: 'screenshot'; | ||
| dataUri: string; | ||
| id: string; | ||
| }; | ||
| type ScreenshotData = { | ||
| dataUri: string; | ||
| }; | ||
| type ScreenshotErrorResponse = { | ||
| type: 'screenshotError'; | ||
| message: string; | ||
| id: string; | ||
| }; | ||
| type AssetRequest = { | ||
| type: 'asset'; | ||
| url: string; | ||
| }; | ||
| type AssetResultResponse = { | ||
| type: 'assetResult'; | ||
| result: 'success' | 'failure' | string; | ||
| url: string; | ||
| message?: string; | ||
| }; | ||
| type ServerMessage = | ||
| | ScreenshotResponse | ||
| | ScreenshotErrorResponse | ||
| | AssetResultResponse | ||
| | { type: string; [key: string]: unknown }; | ||
| /** | ||
| * Creates a client for interacting with a Limbar instance | ||
| * @param options Configuration options including webrtcUrl, token and log level | ||
| * @returns An InstanceClient for controlling the instance | ||
| */ | ||
| export async function createInstanceClient( | ||
| androidInstance: AndroidInstance, | ||
| options: InstanceClientOptions = { | ||
| adbPath: 'adb', | ||
| logLevel: 'info', | ||
| }, | ||
| ): Promise<InstanceClient> { | ||
| const token = androidInstance.status.token; | ||
| const serverAddress = `${androidInstance.status.endpointWebSocketUrl}?token=${token}`; | ||
| const logLevel = options.logLevel ?? 'info'; | ||
| let ws: WebSocket | undefined = undefined; | ||
| const screenshotRequests: Map< | ||
| string, | ||
| { | ||
| resolver: (value: ScreenshotData | PromiseLike<ScreenshotData>) => void; | ||
| rejecter: (reason?: any) => void; | ||
| } | ||
| > = new Map(); | ||
| const assetRequests: Map< | ||
| string, | ||
| { | ||
| resolver: (value: void | PromiseLike<void>) => void; | ||
| rejecter: (reason?: any) => void; | ||
| } | ||
| > = new Map(); | ||
| // Logger functions | ||
| const logger = { | ||
| debug: (...args: any[]) => { | ||
| if (logLevel === 'debug') console.log(...args); | ||
| }, | ||
| info: (...args: any[]) => { | ||
| if (logLevel === 'info' || logLevel === 'debug') console.log(...args); | ||
| }, | ||
| warn: (...args: any[]) => { | ||
| if (logLevel === 'warn' || logLevel === 'info' || logLevel === 'debug') console.warn(...args); | ||
| }, | ||
| error: (...args: any[]) => { | ||
| if (logLevel !== 'none') console.error(...args); | ||
| }, | ||
| }; | ||
| return new Promise<InstanceClient>((resolveConnection, rejectConnection) => { | ||
| logger.debug(`Attempting to connect to WebSocket server at ${serverAddress}...`); | ||
| ws = new WebSocket(serverAddress); | ||
| ws.on('message', (data: Data) => { | ||
| let message: ServerMessage; | ||
| try { | ||
| message = JSON.parse(data.toString()); | ||
| } catch (e) { | ||
| logger.error({ data, error: e }, 'Failed to parse JSON message'); | ||
| return; | ||
| } | ||
| switch (message.type) { | ||
| case 'screenshot': { | ||
| if (!('dataUri' in message) || typeof message.dataUri !== 'string' || !('id' in message)) { | ||
| logger.warn('Received invalid screenshot message:', message); | ||
| break; | ||
| } | ||
| const screenshotMessage = message as ScreenshotResponse; | ||
| const request = screenshotRequests.get(screenshotMessage.id); | ||
| if (!request) { | ||
| logger.warn( | ||
| `Received screenshot data for unknown or already handled session: ${screenshotMessage.id}`, | ||
| ); | ||
| break; | ||
| } | ||
| logger.debug(`Received screenshot data URI for session ${screenshotMessage.id}.`); | ||
| request.resolver({ dataUri: screenshotMessage.dataUri }); | ||
| screenshotRequests.delete(screenshotMessage.id); | ||
| break; | ||
| } | ||
| case 'screenshotError': { | ||
| if (!('message' in message) || !('id' in message)) { | ||
| logger.warn('Received invalid screenshot error message:', message); | ||
| break; | ||
| } | ||
| const errorMessage = message as ScreenshotErrorResponse; | ||
| const request = screenshotRequests.get(errorMessage.id); | ||
| if (!request) { | ||
| logger.warn( | ||
| `Received screenshot error for unknown or already handled session: ${errorMessage.id}`, | ||
| ); | ||
| break; | ||
| } | ||
| logger.error( | ||
| `Server reported an error capturing screenshot for session ${errorMessage.id}:`, | ||
| errorMessage.message, | ||
| ); | ||
| request.rejecter(new Error(errorMessage.message)); | ||
| screenshotRequests.delete(errorMessage.id); | ||
| break; | ||
| } | ||
| case 'assetResult': { | ||
| logger.debug('Received assetResult:', message); | ||
| const request = assetRequests.get(message.url as string); | ||
| if (!request) { | ||
| logger.warn(`Received assetResult for unknown or already handled url: ${message.url}`); | ||
| break; | ||
| } | ||
| if (message.result === 'success') { | ||
| logger.debug('Asset result is success'); | ||
| request.resolver(); | ||
| assetRequests.delete(message.url as string); | ||
| break; | ||
| } | ||
| const errorMessage = | ||
| typeof message.message === 'string' && message.message ? | ||
| message.message | ||
| : `Asset processing failed: ${JSON.stringify(message)}`; | ||
| logger.debug('Asset result is failure', errorMessage); | ||
| request.rejecter(new Error(errorMessage)); | ||
| assetRequests.delete(message.url as string); | ||
| break; | ||
| } | ||
| default: | ||
| logger.warn(`Received unexpected message type: ${message.type}`); | ||
| break; | ||
| } | ||
| }); | ||
| ws.on('error', (err: Error) => { | ||
| logger.error('WebSocket error:', err.message); | ||
| if (ws && (ws.readyState === WebSocket.CONNECTING || ws.readyState === WebSocket.OPEN)) { | ||
| rejectConnection(err); | ||
| } | ||
| screenshotRequests.forEach((request) => request.rejecter(err)); | ||
| }); | ||
| ws.on('close', () => { | ||
| logger.debug('Disconnected from server.'); | ||
| screenshotRequests.forEach((request) => request.rejecter('Disconnected from server')); | ||
| }); | ||
| const screenshot = async (): Promise<ScreenshotData> => { | ||
| if (!ws || ws.readyState !== WebSocket.OPEN) { | ||
| return Promise.reject(new Error('WebSocket is not connected or connection is not open.')); | ||
| } | ||
| const id = 'ts-client-' + Date.now(); | ||
| const screenshotRequest: ScreenshotRequest = { | ||
| type: 'screenshot', | ||
| id, | ||
| }; | ||
| return new Promise<ScreenshotData>((resolve, reject) => { | ||
| logger.debug('Sending screenshot request:', screenshotRequest); | ||
| ws!.send(JSON.stringify(screenshotRequest), (err?: Error) => { | ||
| if (err) { | ||
| logger.error('Failed to send screenshot request:', err); | ||
| reject(err); | ||
| } | ||
| }); | ||
| const timeout = setTimeout(() => { | ||
| if (screenshotRequests.has(id)) { | ||
| logger.error(`Screenshot request timed out for session ${id}`); | ||
| screenshotRequests.get(id)?.rejecter(new Error('Screenshot request timed out')); | ||
| screenshotRequests.delete(id); | ||
| } | ||
| }, 30000); | ||
| screenshotRequests.set(id, { | ||
| resolver: (value: ScreenshotData | PromiseLike<ScreenshotData>) => { | ||
| clearTimeout(timeout); | ||
| resolve(value); | ||
| screenshotRequests.delete(id); | ||
| }, | ||
| rejecter: (reason?: any) => { | ||
| clearTimeout(timeout); | ||
| reject(reason); | ||
| screenshotRequests.delete(id); | ||
| }, | ||
| }); | ||
| }); | ||
| }; | ||
| const disconnect = (): void => { | ||
| if (ws) { | ||
| logger.debug('Closing WebSocket connection.'); | ||
| ws.close(); | ||
| } | ||
| screenshotRequests.forEach((request) => request.rejecter('Websocket connection closed')); | ||
| }; | ||
| /** | ||
| * Opens a WebSocket TCP proxy for the ADB port and connects the local adb | ||
| * client to it. | ||
| */ | ||
| const startAdbTunnel = async (): Promise<Tunnel> => { | ||
| if (!androidInstance.status.adbWebSocketUrl) { | ||
| return Promise.reject(new Error('ADB WebSocket URL is not set')); | ||
| } | ||
| const { address, close } = await startTcpTunnel( | ||
| androidInstance.status.adbWebSocketUrl, | ||
| token, | ||
| '127.0.0.1', | ||
| 0, | ||
| ); | ||
| try { | ||
| await new Promise<void>((resolve, reject) => { | ||
| exec(`${options.adbPath ?? 'adb'} connect ${address.address}:${address.port}`, (err) => { | ||
| if (err) return reject(err); | ||
| resolve(); | ||
| }); | ||
| }); | ||
| logger.debug(`ADB connected on ${address.address}`); | ||
| } catch (err) { | ||
| close(); | ||
| throw err; | ||
| } | ||
| return { address, close }; | ||
| }; | ||
| const sendAsset = async (url: string): Promise<void> => { | ||
| if (!ws || ws.readyState !== WebSocket.OPEN) { | ||
| return Promise.reject(new Error('WebSocket is not connected or connection is not open.')); | ||
| } | ||
| const assetRequest: AssetRequest = { | ||
| type: 'asset', | ||
| url, | ||
| }; | ||
| ws.send(JSON.stringify(assetRequest), (err?: Error) => { | ||
| if (err) { | ||
| logger.error('Failed to send asset request:', err); | ||
| } | ||
| }); | ||
| return new Promise<void>((resolve, reject) => { | ||
| assetRequests.set(url, { resolver: resolve, rejecter: reject }); | ||
| }); | ||
| }; | ||
| ws.on('open', () => { | ||
| logger.debug(`Connected to ${serverAddress}`); | ||
| resolveConnection({ | ||
| screenshot, | ||
| disconnect, | ||
| startAdbTunnel, | ||
| sendAsset, | ||
| }); | ||
| }); | ||
| }); | ||
| } |
| import * as net from 'net'; | ||
| import { WebSocket } from 'ws'; | ||
| export interface Tunnel { | ||
| address: { | ||
| address: string; | ||
| port: number; | ||
| }; | ||
| close: () => void; | ||
| } | ||
| /** | ||
| * Starts a one-shot TCP → WebSocket proxy. | ||
| * | ||
| * The function creates a local TCP server that listens on an ephemeral port on | ||
| * 127.0.0.1. As soon as the **first** TCP client connects the server stops | ||
| * accepting further connections and forwards all traffic between that client | ||
| * and `remoteURL` through an authenticated WebSocket. If you need to proxy | ||
| * more than one TCP connection, call `startTcpTunnel` again to create a new | ||
| * proxy instance. | ||
| * | ||
| * @param remoteURL Remote WebSocket endpoint (e.g. wss://example.com/instance) | ||
| * @param token Bearer token sent as `Authorization` header | ||
| * @param hostname Optional IP address to listen on. Default is 127.0.0.1 | ||
| * @param port Optional port number to listen on. Default is to ask Node.js | ||
| * to find an available non-privileged port. | ||
| */ | ||
| export async function startTcpTunnel( | ||
| remoteURL: string, | ||
| token: string, | ||
| hostname: string, | ||
| port: number, | ||
| ): Promise<Tunnel> { | ||
| return new Promise((resolve, reject) => { | ||
| const server = net.createServer(); | ||
| let ws: WebSocket | undefined; | ||
| let pingInterval: NodeJS.Timeout | undefined; | ||
| // close helper | ||
| const close = () => { | ||
| if (pingInterval) { | ||
| clearInterval(pingInterval); | ||
| pingInterval = undefined; | ||
| } | ||
| if (ws && ws.readyState === WebSocket.OPEN) { | ||
| ws.close(1000, 'close'); | ||
| } | ||
| if (server.listening) { | ||
| server.close(); | ||
| } | ||
| }; | ||
| // No AbortController support – proxy can be closed via the returned handle | ||
| // TCP server error | ||
| server.once('error', (err) => { | ||
| close(); | ||
| reject(new Error(`TCP server error: ${err.message}`)); | ||
| }); | ||
| // Listening | ||
| server.once('listening', () => { | ||
| const address = server.address(); | ||
| if (!address || typeof address === 'string') { | ||
| close(); | ||
| return reject(new Error('Failed to obtain listening address')); | ||
| } | ||
| resolve({ address, close }); | ||
| }); | ||
| // On first TCP connection | ||
| server.on('connection', (tcpSocket) => { | ||
| // Single-connection proxy | ||
| server.close(); | ||
| ws = new WebSocket(remoteURL, { | ||
| headers: { Authorization: `Bearer ${token}` }, | ||
| perMessageDeflate: false, | ||
| }); | ||
| // WebSocket error | ||
| ws.once('error', (err: any) => { | ||
| console.error('WebSocket error:', err); | ||
| tcpSocket.destroy(); | ||
| close(); | ||
| }); | ||
| ws.once('open', () => { | ||
| const socket = ws as WebSocket; // non-undefined after open | ||
| pingInterval = setInterval(() => { | ||
| if (socket.readyState === WebSocket.OPEN) { | ||
| (socket as any).ping(); | ||
| } | ||
| }, 30_000); | ||
| // TCP → WS | ||
| tcpSocket.on('data', (chunk) => { | ||
| if (socket.readyState === WebSocket.OPEN) { | ||
| socket.send(chunk); | ||
| } | ||
| }); | ||
| // WS → TCP | ||
| socket.on('message', (data: any) => { | ||
| if (!tcpSocket.destroyed) { | ||
| tcpSocket.write(data as Buffer); | ||
| } | ||
| }); | ||
| }); | ||
| // Mutual close | ||
| tcpSocket.on('close', close); | ||
| tcpSocket.on('error', (err: any) => { | ||
| console.error('TCP socket error:', err); | ||
| close(); | ||
| }); | ||
| ws.on('close', () => tcpSocket.destroy()); | ||
| }); | ||
| // Start listening | ||
| server.listen(port, hostname); | ||
| }); | ||
| } |
642697
0.11%8542
-0.04%