UNPKG

multi-automator

Version:
325 lines (324 loc) 12.7 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); /** * @desc: atx operation * @author: john_chen * @date: 2025.02.22 */ const axios_1 = __importDefault(require("axios")); const portfinder_1 = __importDefault(require("portfinder")); const config_1 = require("../config"); const time_1 = require("../utils/time"); const env_1 = require("./env"); const adb = __importStar(require("./adb")); const ATX_NAME = 'atx-agent'; const ATX_PATH = `${env_1.DEVICE_TMP_DIR}/${ATX_NAME}`; const ATX_DEVICE_PORT = 7900; class Atx { constructor({ deviceId, connectType, timeout = 20000 }) { this.id = deviceId; this.connectType = connectType; this.timeout = timeout; this.ip = '127.0.0.1'; this.port = null; this.atxIsRunning = false; this.uiautomatorIsRunning = false; } // 公共接口 async init() { config_1.logger.info('[atx.init]'); await this.setupConnection(); await this.restartAtxServer(); await this.startUiautomator(); } async close() { config_1.logger.info('[atx.close]'); if (this.uiautomatorIsRunning) { await this.stopUiautomatorServer(); this.uiautomatorIsRunning = false; } if (this.atxIsRunning) { await this.stopAtxServer(); this.atxIsRunning = false; } } async info() { return this.get('/info'); } // 应用包管理 async packages() { const res = await this.get('/packages'); return res.map(item => ({ appId: item.packageName, version: item.versionName, name: item.label })); } async packageInfo(packageName) { const { data } = await this.get(`/packages/${packageName}/info`); return { appId: data.packageName, version: data.versionName, name: data.label, activity: data.mainActivity }; } async source(timeout = this.timeout) { const res = await this.get('/dump/hierarchy', { timeout }); return res.result; } async deviceInfo(timeout = 20000) { let res = await this.jsonrpc('deviceInfo', [], { timeout }); config_1.logger.info(`[atx.deviceInfo] ${JSON.stringify(res)}`); return res; } // TODO: 有点问题、待解决 async screenshot(timeout = 20000) { let res = await this.get('/screenshot/0', { responseType: 'arraybuffer', timeout }); return Buffer.from(res); } // HTTP 请求封装 async get(path = '', options) { const fullPath = path.startsWith('/') ? path : `/${path}`; const { timeout = this.timeout, responseType, } = options || {}; const baseUrl = `http://${this.ip}:${this.port}`; // 构建请求配置 const config = { baseURL: baseUrl, timeout }; if (responseType) { config.responseType = responseType; } try { const { data } = await axios_1.default.get(fullPath, config); return data; } catch (err) { config_1.logger.error(`[atx.get] ${baseUrl}${fullPath} ${err.message}`); throw new Error(`[Atx.get] ${fullPath}: ${err.message}`); } } async post(path = '', data = {}, options) { const fullPath = path.startsWith('/') ? path : `/${path}`; const { timeout = this.timeout } = options || {}; try { const { data: responseData } = await axios_1.default.post(fullPath, data, { baseURL: `http://${this.ip}:${this.port}`, timeout }); return responseData; } catch (err) { if (err.message.includes('502') && fullPath === '/jsonrpc/0') { throw new Error('Jsonrpc 502'); } throw new Error(`[Atx.post] ${fullPath}: ${err.message}`); } } async jsonrpc(method, params = [], options = {}) { const { timeout = this.timeout } = options; await this.checkUiautomator(); const requestPayload = { jsonrpc: '2.0', method, id: String(Math.floor((0, time_1.currentTimestamp)() / 1000)), params }; const res = await this.post('/jsonrpc/0', requestPayload, { timeout }); // logger.info(`[atx.jsonrpc] res: ${JSON.stringify(res)}`); if (res === null || res === void 0 ? void 0 : res.result) { return res.result; } this.handleJsonRpcError(res.error); } handleJsonRpcError(error) { if (error) { const errorMessage = error.message || error.data || 'Unknown error occurred'; throw new Error(errorMessage); } throw new Error('jsonrpc request failed: no result or error provided'); } // 连接管理 async setupConnection() { if (this.connectType === 'usb') { this.port = await portfinder_1.default.getPortPromise(); await adb.forward(this.id, `tcp:${this.port}`, `tcp:${ATX_DEVICE_PORT}`); config_1.logger.info(`[atx.init] forward ${this.id} ${this.port} to ${ATX_DEVICE_PORT}`); } else if (this.connectType === 'wifi') { this.ip = await adb.ip(this.id); } else { throw new Error(`不支持连接类型: ${this.connectType}`); } config_1.logger.info(`[atx.init] http://${this.ip}:${this.port}`); } // ATX Server 管理 async restartAtxServer() { await this.stopAtxServer(); await this.startAtxServer(); } async startAtxServer() { config_1.logger.info('[atx.startAtxServer]'); let grepAtxCmd = `${ATX_NAME} server --addr=:${ATX_DEVICE_PORT} --nouia -d`; for (let ps of ['ps -ef', 'ps']) { let grepCmd = `${ps} | grep -v 'grep' | grep '${grepAtxCmd}'`; let atxProcesses = await adb.shell(this.id, grepCmd); if (atxProcesses) { config_1.logger.info(`[atx.startAtxServer] ${atxProcesses}`); return; } } let startCmd = `${ATX_PATH} server --addr=:${ATX_DEVICE_PORT} --nouia -d`; let res = await adb.shell(this.id, startCmd); if (res.includes('run atx-agent')) { this.atxIsRunning = true; config_1.logger.info('[atx.startAtxServer] success'); } else { throw new Error(`[atx.startAtxServer] ${res}`); } } async stopAtxServer() { config_1.logger.info('[atx.stopAtxServer]'); for (let ps of ['ps -ef', 'ps']) { let atxProcesses = await adb.shell(this.id, `${ps} | grep -v 'grep' | grep 'atx-agent'`); if (atxProcesses) { let atxProcessList = atxProcesses.split('\n'); for (let atxProcess of atxProcessList) { let atxProcessColumns = atxProcess.split(' '); let atxProcessValidColumns = []; for (let atxProcessColumn of atxProcessColumns) { if (0 !== atxProcessValidColumns.length || atxProcessColumn.startsWith('atx-agent')) { if ('-d' !== atxProcessColumn) { atxProcessValidColumns.push(atxProcessColumn); } } } let cmd = `${env_1.DEVICE_TMP_DIR}/${atxProcessValidColumns.join(' ')} --stop`; let res = await adb.shell(this.id, cmd); if (res.includes('stop server self')) { config_1.logger.info('[atx.stopAtxServer] success'); } else { throw new Error(`[atx.stopAtxServer] ${res}`); } } } } } // UIAutomator 管理 async startUiautomator(timeout = 20000) { config_1.logger.info('[atx.startUiautomator]'); const expiredTime = (0, time_1.currentTimestamp)() + timeout; let retryCount = 0; while ((0, time_1.currentTimestamp)() <= expiredTime) { try { retryCount++; await this.startUiautomatorServer(); await this.setTimeoutUiautomator(); if (await this.getUiautomatorState()) { this.uiautomatorIsRunning = true; config_1.logger.info('[atx.startUiautomator] success'); return; } } catch (err) { config_1.logger.error(`[atx.startUiautomator] 启动异常:${err.message}`); // await this.killUiautomatorProcess(); } if (retryCount > 3) { config_1.logger.info('[atx.startUiautomator] 重启 ATX Server'); await this.restartAtxServer(); } await (0, time_1.delay)(1000); } throw new Error(`[atx.startUiautomator] 耗时 ${timeout} ms 仍未启动`); } async checkUiautomator(timeout = 15000) { config_1.logger.info('[atx.checkUiautomator]'); const expiredTime = (0, time_1.currentTimestamp)() + timeout; do { try { let isRunning = await this.getUiautomatorState(); if (isRunning) return; await this.startUiautomatorServer(); await this.setTimeoutUiautomator(); } catch (err) { config_1.logger.warn('[atx.checkUiautomator] continue'); } await adb.checkConnect(this.id); await (0, time_1.delay)(1000); } while ((0, time_1.currentTimestamp)() <= expiredTime); throw new Error(`[atx.checkUiautomator] 超时 ${timeout}ms`); } async startUiautomatorServer() { const res = await this.post('/services/uiautomator'); config_1.logger.info(`[atx.startUiautomatorServer] ${res.description}`); if (!['success', 'already started', 'successfully started'].includes(res.description)) { throw new Error(`startUiautomator fail: ${res.description}`); } config_1.logger.info(`[atx.startUiautomatorServer] ${res.description}`); } async stopUiautomatorServer() { config_1.logger.info('[atx.stopUiautomatorServer]'); try { const res = await axios_1.default.delete('/uiautomator', { baseURL: `http://${this.ip}:${this.port}`, timeout: 6000 }); config_1.logger.info(`[atx.stopUiautomatorServer] ${res.data}`); } catch (err) { config_1.logger.error(`[atx.stopUiautomatorServer] ${err.stack}`); } } async setTimeoutUiautomator() { const res = await this.post('/newCommandTimeout', '10800'); if (!res.success) { throw new Error(`setTimeoutUiautomator fail: ${res.description}`); } config_1.logger.info(`[atx.setTimeoutUiautomator] ${res.description}`); } async getUiautomatorState() { try { const { running } = await this.get('/services/uiautomator'); config_1.logger.info(`[atx.getUiautomatorState] status: ${running}`); return running; } catch { return false; } } } exports.default = Atx;