UNPKG

hdckit

Version:

A pure Node.js client for the OpenHarmony Device Connector

330 lines (329 loc) 12.5 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const path_1 = __importDefault(require("path")); const trim_1 = __importDefault(require("licia/trim")); const contain_1 = __importDefault(require("licia/contain")); const cmpVersion_1 = __importDefault(require("licia/cmpVersion")); const toNum_1 = __importDefault(require("licia/toNum")); const getPort_1 = __importDefault(require("licia/getPort")); const strHash_1 = __importDefault(require("licia/strHash")); const now_1 = __importDefault(require("licia/now")); const sleep_1 = __importDefault(require("licia/sleep")); const node_net_1 = __importDefault(require("node:net")); const Emitter_1 = __importDefault(require("licia/Emitter")); const singleton_1 = __importDefault(require("licia/singleton")); const SDK_PATH = path_1.default.join(__dirname, '../../uitestkit_sdk/uitest_agent_v1.1.0.so'); const SDK_VERSION = '1.1.0'; const AGENT_PATH = '/data/local/tmp/agent.so'; class UiDriver extends Emitter_1.default { constructor(target, sdkPath, sdkVersion) { super(); this.connection = null; this.driverName = ''; this.port = 0; this.sdkVersion = SDK_VERSION; this.sdkPath = SDK_PATH; this.triedStarting = false; this.captureScreenCallback = null; this.send = async (method, api, args = {}) => { try { const connection = await this.getConnection(); const module = 'com.ohos.devicetest.hypiumApiHelper'; if (method === 'callHypiumApi') { return connection.sendMessage({ module, method, params: { api: `Driver.${api}`, this: this.driverName, args, message_type: 'hypium', }, }); } return connection.sendMessage({ module, method, params: { api, args, }, }); } catch (e) { if (e.message === 'timeout' && !this.triedStarting) { this.triedStarting = true; await this.stop(); return await this.send(method, api, args); } else { throw e; } } }; this.getConnection = (0, singleton_1.default)(async () => { let { connection } = this; if (!connection) { await this.start(); connection = new Connection(); connection.setOnMessage((sessionId, message) => { this.emit('message', sessionId, message); }); await connection.connect(this.port); connection.socket.on('end', () => { this.connection = null; }); const { result } = await connection.sendMessage({ module: 'com.ohos.devicetest.hypiumApiHelper', method: 'callHypiumApi', params: { api: 'Driver.create', this: null, args: [], message_type: 'hypium', }, }, 1000); this.driverName = result; this.connection = connection; } return connection; }); this.target = target; if (sdkPath) { this.sdkPath = sdkPath; } if (sdkVersion) { this.sdkVersion = sdkVersion; } } async start() { let uiTestPid = (0, trim_1.default)(await this.shell('pidof uitest')); const shouldUpdateSdk = await this.shouldUpdateSdk(); if (!uiTestPid || shouldUpdateSdk) { await this.shell('param set persist.ace.testmode.enabled 1'); if (shouldUpdateSdk) { if (uiTestPid) { await this.shell(`kill -9 ${uiTestPid}`); uiTestPid = ''; } await this.updateSdk(); } if (!uiTestPid) { await this.shell('uitest start-daemon singleness'); await (0, sleep_1.default)(2000); } } this.port = await this.forwardTcp(8012); } async stop() { if (this.connection) { this.connection.end(); this.connection = null; } const uiTestPid = (0, trim_1.default)(await this.shell('pidof uitest')); if (uiTestPid) { await this.shell(`kill -9 ${uiTestPid}`); } } async startCaptureScreen(callback, options = { scale: 1 }) { if (options.scale >= 1 || options.scale <= 0) { delete options.scale; } const { sessionId } = await this.send('Captures', 'startCaptureScreen', { options, }); if (this.captureScreenCallback) { throw new Error('Capture screen is already started'); } this.captureScreenCallback = (id, message) => { if (id === sessionId) { callback(message); } }; this.on('message', this.captureScreenCallback); } async stopCaptureScreen() { await this.send('Captures', 'stopCaptureScreen'); if (this.captureScreenCallback) { this.off('message', this.captureScreenCallback); this.captureScreenCallback = null; } } captureLayout() { return this.send('Captures', 'captureLayout').then(({ result }) => result); } getDisplaySize() { return this.send('CtrlCmd', 'getDisplaySize').then(({ result }) => result); } async touchDown(x, y) { await this.send('Gestures', 'touchDown', { x, y }); } async touchMove(x, y) { await this.send('Gestures', 'touchMove', { x, y }); } async touchUp(x, y) { await this.send('Gestures', 'touchUp', { x, y }); } async inputText(text, x = 0, y = 0) { await this.send('callHypiumApi', 'inputText', [{ x, y }, text]); } async forwardTcp(p) { const { target } = this; const remote = `tcp:${p}`; const forwards = await target.listForwards(); for (let i = 0, len = forwards.length; i < len; i++) { const forward = forwards[i]; if (forward.remote === remote) { return (0, toNum_1.default)(forward.local.replace('tcp:', '')); } } const port = await (0, getPort_1.default)(); const local = `tcp:${port}`; await target.forward(local, remote); return port; } async shouldUpdateSdk() { const result = await this.shell(`cat ${AGENT_PATH} | grep -a UITEST_AGENT_LIBRARY`); if (!(0, contain_1.default)(result, 'UITEST_AGENT_LIBRARY')) { return true; } const deviceSdkVersion = getSdkVersion(result); return (0, cmpVersion_1.default)(deviceSdkVersion, this.sdkVersion) < 0; } async updateSdk() { await this.shell(`rm ${AGENT_PATH}`); await this.target.sendFile(this.sdkPath, AGENT_PATH); } async shell(command) { const connection = await this.target.shell(command); return (await connection.readAll()).toString(); } } exports.default = UiDriver; const HEADER_BYTES = Buffer.from('_uitestkit_rpc_message_head_'); const TAILER_BYTES = Buffer.from('_uitestkit_rpc_message_tail_'); class Connection { constructor() { this.ended = false; this.resolves = new Map(); this.rejects = new Map(); this.buffer = Buffer.alloc(0); this.onData = (data) => { let buffer = this.buffer; buffer = Buffer.concat([buffer, data]); while (buffer.length >= HEADER_BYTES.length + 8) { const headerBytes = buffer.subarray(0, HEADER_BYTES.length); if (headerBytes.compare(HEADER_BYTES) !== 0) { buffer = Buffer.alloc(0); break; } if (buffer.length < HEADER_BYTES.length + 8) { break; } const sessionId = buffer.readUInt32BE(HEADER_BYTES.length); const len = buffer.readUInt32BE(HEADER_BYTES.length + 4); const totalLength = HEADER_BYTES.length + 8 + len + TAILER_BYTES.length; if (buffer.length < totalLength) { break; } const start = HEADER_BYTES.length + 8; const end = start + len; const message = buffer.subarray(start, end); const tailerBytes = buffer.subarray(end, end + TAILER_BYTES.length); if (tailerBytes.compare(TAILER_BYTES) !== 0) { buffer = Buffer.alloc(0); break; } const resolve = this.resolves.get(sessionId); const reject = this.rejects.get(sessionId); if (resolve) { try { const result = JSON.parse(message.toString()); if (result.exception) { reject(new Error(result.exception.message)); } else { resolve({ sessionId, result: result.result }); } // eslint-disable-next-line } catch (e) { resolve({ sessionId, result: message }); } this.resolves.delete(sessionId); this.rejects.delete(sessionId); } else if (this.onMessage) { this.onMessage(sessionId, message); } buffer = this.buffer.subarray(totalLength); } this.buffer = buffer; }; } connect(port) { const socket = node_net_1.default.connect({ host: '127.0.0.1', port, }); socket.setNoDelay(true); this.socket = socket; return new Promise((resolve, reject) => { socket.once('connect', async () => { socket.on('data', this.onData); resolve(null); }); socket.once('error', reject); socket.once('end', () => { this.ended = true; this.socket = null; }); }); } setOnMessage(onMessage) { this.onMessage = onMessage; } sendMessage(message, timeout = 0) { message = JSON.stringify(message); const sessionId = (0, strHash_1.default)((0, now_1.default)() + message); const sessionIdBuf = Buffer.alloc(4); sessionIdBuf.writeUInt32BE(sessionId, 0); return new Promise((resolve, reject) => { this.resolves.set(sessionId, resolve); this.rejects.set(sessionId, reject); this.sendRawMessage(sessionIdBuf, Buffer.from(message)); if (timeout) { setTimeout(() => { reject(new Error('timeout')); this.resolves.delete(sessionId); this.rejects.delete(sessionId); }, timeout); } }); } sendRawMessage(sessonId, message) { if (this.ended) { throw new Error('ended'); } const buffers = []; buffers.push(HEADER_BYTES); buffers.push(sessonId); const len = Buffer.alloc(4); len.writeUInt32BE(message.length, 0); buffers.push(len); buffers.push(message); buffers.push(TAILER_BYTES); this.socket.write(Buffer.concat(buffers)); } end() { if (this.socket) { this.socket.end(); } } } function getSdkVersion(raw) { return (0, trim_1.default)(raw.slice(raw.indexOf('@v') + 2)); }