UNPKG

@u4/adbkit

Version:

A Typescript client for the Android Debug Bridge.

557 lines 22.9 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 }); const fs_1 = __importDefault(require("fs")); const promise_duplex_1 = __importDefault(require("promise-duplex")); const stream_1 = require("stream"); const ThirdUtils_1 = __importDefault(require("../ThirdUtils")); const STF = __importStar(require("./STFServiceModel")); // import * as STFAg from "./STFAgentModel"; const protobufjs_1 = require("protobufjs"); const STFServiceBuf_1 = __importDefault(require("./STFServiceBuf")); const utils_1 = __importDefault(require("../../utils")); // const debug = Debug('STFService'); const PKG = 'jp.co.cyberagent.stf'; class STFService extends stream_1.EventEmitter { constructor(client, options = {}) { super(); this.client = client; this.on = (event, listener) => super.on(event, listener); this.off = (event, listener) => super.off(event, listener); this.once = (event, listener) => super.once(event, listener); this.emit = (event, ...args) => super.emit(event, ...args); this._cachedApkPath = ''; /** * esponce callback hooks */ this.responseHook = {}; /** * request id counter [1..0xFFFFFF] */ this.reqCnt = 1; this.config = { timeout: 15000, noInstall: false, ...options, }; this._maxContact = new Promise((resolve) => this.setMaxContact = resolve); this._width = new Promise((resolve) => this.setWidth = resolve); this._height = new Promise((resolve) => this.setHeight = resolve); this._maxPressure = new Promise((resolve) => this.setMaxPressure = resolve); } get maxContact() { return this._maxContact; } get width() { return this._width; } get height() { return this._height; } get maxPressure() { return this._maxPressure; } /** * find the APK and install it */ async installApk(version) { const apk = ThirdUtils_1.default.getResourcePath(`STFService_${version}.apk`); try { await fs_1.default.promises.stat(apk); } catch (e) { throw Error(`can not find APK bin/STFService_${version}.apk Err: ${JSON.stringify(e)}`); } this._cachedApkPath = ''; return this.client.install(apk); } /** * * @returns get agent setup path */ async getApkPath() { if (this._cachedApkPath) return this._cachedApkPath; /** * locate the installed apk file */ let setupPath = (await this.client.execOut(`pm path ${PKG}`, 'utf8')).trim(); if (!setupPath.startsWith('package:')) { return ''; // throw new Error(`Failed to find ${PKG} package path`); } setupPath = setupPath.substring(8); this._cachedApkPath = setupPath; return setupPath; } /** * get the current installed Agent version number * @returns 'MISSING' if not installed, 'OK' if expected, 'MISMATCH' if version differ */ async checkVersion(version) { const setupPath = await this.getApkPath(); const getVersion = `export CLASSPATH='${setupPath}'; exec app_process /system/bin '${PKG}.Agent' --version 2>/dev/null`; const currentVersion = await this.client.execOut(getVersion, 'utf8'); if (!currentVersion) return 'MISSING'; if (currentVersion.trim() !== version) { return 'MISMATCH'; } return 'OK'; } /** * start agent */ async startAgent() { const setupPath = await this.getApkPath(); const startAgent = `export CLASSPATH='${setupPath}'; exec app_process /system/bin '${PKG}.Agent' 2>&1`; const agentProcess = new promise_duplex_1.default(await this.client.exec(startAgent)); const result = await utils_1.default.waitforText(agentProcess, /@stfagent|Address already in use/, 10000); if (result.includes("@stfagent")) return; // started // console.log(`${this.client.serial} stfagent already running`); // debug only // ThirdUtils.dumpReadable(agentProcess, 'STFagent'); } /** * start long running service and keep the duplex opened */ async startService() { const props = await this.client.getProperties(); const action = `${PKG}.ACTION_START`; const component = `${PKG}/.Service`; const sdkLevel = parseInt(props['ro.build.version.sdk']); const startServiceCmd = (sdkLevel < 26) ? 'startservice' : 'start-foreground-service'; const duplex = new promise_duplex_1.default(await this.client.shell(`am ${startServiceCmd} --user 0 -a '${action}' -n '${component}'`)); await utils_1.default.waitforReadable(duplex); const msg = await duplex.setEncoding('utf8').readAll(); if (msg.includes('Error')) { throw Error(msg.trim()); } } /** * uninstall the service */ async uninstall() { await this.client.uninstall(PKG); await this.client.execOut('rm -f /data/local/tmp/minicap /data/local/tmp/minicap.so /data/local/tmp/minitouch /data/local/tmp/minirev'); return true; } async start() { this.protoSrv = await STFServiceBuf_1.default.get(); if (!this.config.noInstall) { const versionStatus = await this.checkVersion('2.4.9'); if (versionStatus === 'MISMATCH') { await this.uninstall(); } if (versionStatus !== 'OK') { await this.installApk('2.4.9'); } } await this.startService(); await this.startAgent(); this.servicesSocket = await this.client.openLocal2('localabstract:stfservice'); this.servicesSocket.once('close').then(() => this.stop()); void this.startServiceStream().catch((e) => { console.log('Service failed', e); this.stop(); }); return this; } /** * get minitouch duplex, if not connected open connexion */ async getMinitouchSocket() { if (this._minitouchagent) return this._minitouchagent; this._minitouchagent = this.client.openLocal2('localabstract:minitouchagent'); const socket = await this._minitouchagent; socket.once('close').then(() => { this._minitouchagent = undefined; // console.log('getMinitouchSocket just closed'); }); void this.startMinitouchStream(socket).catch(() => { socket.destroy(); }); return socket; } async getAgentSocket() { if (this._agentSocket) return this._agentSocket; this._agentSocket = this.client.openLocal2('localabstract:stfagent'); const socket = await this._agentSocket; socket.once('close').then(() => { this._agentSocket = undefined; // console.log('agentSocket just closed'); }); void this.startAgentStream(socket).catch(() => { socket.destroy(); }); return this._agentSocket; } async startServiceStream() { let buffer = null; for (;;) { await utils_1.default.waitforReadable(this.servicesSocket); if (!this.servicesSocket) return; const next = await this.servicesSocket.read(); if (!next) continue; if (buffer) { buffer = Buffer.concat([buffer, next]); } else { buffer = next; } while (buffer) { const reader = protobufjs_1.Reader.create(buffer); const envelopLen = reader.uint32(); const bufLen = envelopLen + reader.pos; // need mode data to complet envelop if (buffer.length < envelopLen) break; let chunk; if (bufLen === buffer.length) { // chunk len match Envelop len should speedup parsing, depending on nodeJS internal Buffer implementation, need to check Buffer.subarray implementation chunk = buffer.subarray(reader.pos); buffer = null; } else { chunk = buffer.subarray(reader.pos, bufLen); buffer = buffer.subarray(bufLen); } try { const eventObj = this.protoSrv.readEnvelope(chunk); const { id, message } = eventObj; if (id) { const resolv = this.responseHook[id]; if (resolv) { delete this.responseHook[id]; resolv(message); } else { console.error(`STFService RCV response to unknown QueryId:${id} Type:${eventObj.type}`); } continue; } switch (eventObj.type) { case STF.MessageType.EVENT_AIRPLANE_MODE: this.emit("airplaneMode", this.protoSrv.read.AirplaneModeEvent(message)); break; case STF.MessageType.EVENT_BATTERY: this.emit("battery", this.protoSrv.read.BatteryEvent(message)); break; case STF.MessageType.EVENT_CONNECTIVITY: this.emit("connectivity", this.protoSrv.read.ConnectivityEvent(message)); break; case STF.MessageType.EVENT_ROTATION: this.emit("rotation", this.protoSrv.read.RotationEvent(message)); break; case STF.MessageType.EVENT_PHONE_STATE: this.emit("phoneState", this.protoSrv.read.PhoneStateEvent(message)); break; case STF.MessageType.EVENT_BROWSER_PACKAGE: this.emit("browerPackage", this.protoSrv.read.BrowserPackageEvent(message)); break; default: console.error(`STFService Response Type (${eventObj.type}) is not implemented`); } } catch (e) { if (chunk) console.error(chunk.toString('hex')); console.error(e); } } await utils_1.default.delay(0); } } /** * RCV banne: * v 1 * ^ %d %d %d %d DEFAULT_MAX_CONTACTS, width, height, DEFAULT_MAX_PRESSURE; * @param socket */ async startMinitouchStream(socket) { socket.setEncoding('ascii'); let data = ''; for (;;) { await utils_1.default.waitforReadable(socket); const chunk = await socket.read(); if (!chunk) return; data = data + chunk; for (;;) { const p = data.indexOf('\n'); if (p >= 0) break; const line = data.substring(0, p); data = data.substring(p + 1); if (line.startsWith('v 1')) continue; if (line.startsWith('^')) { const [, mc, w, h, mp] = line.split(/ /); this.setMaxContact(Number(mc)); this.setWidth(Number(w)); this.setHeight(Number(h)); this.setMaxPressure(Number(mp)); continue; } console.error('minitouchSocket RCV chunk len:', line); } await utils_1.default.delay(0); } } async startAgentStream(socket) { for (;;) { await utils_1.default.waitforReadable(socket); const chunk = await socket.read(); if (chunk) { console.log('agentSocket RCV chunk len:', chunk.length, chunk.toString('hex').substring(0, 80)); } else { return; } await utils_1.default.delay(0); } } pushService(type, message, requestReader) { const id = (this.reqCnt + 1) | 0xFFFFFF; this.reqCnt = id; const envelope = { type, message, id }; let pReject; const promise = new Promise((resolve, reject) => { pReject = reject; this.responseHook[id] = (message) => { if (requestReader) { const conv = requestReader(message); resolve(conv); } else { resolve(); } }; }); const buf = this.protoSrv.write.Envelope(envelope); if (!this.servicesSocket) throw Error('servicesSocket is not open'); this.servicesSocket.write(buf); const timeout = utils_1.default.delay(this.config.timeout).then(() => { if (this.responseHook[id]) { delete this.responseHook[id]; pReject(Error('timeout')); } }); Promise.race([promise, timeout]); return promise; } /** * Generic method to push message to agent */ async pushAgent(type, message) { const envelope = { type, message }; // const buf = this.protoAgent.write.Envelope(envelope) const buf = this.protoSrv.write.Envelope(envelope); const socket = await this.getAgentSocket(); return socket.write(buf); } //////////////////////////// // public methods async getAccounts(type) { const message = this.protoSrv.write.GetAccountsRequest({ type }); return this.pushService(STF.MessageType.GET_ACCOUNTS, message, this.protoSrv.read.GetAccountsResponse); } async getBrowsers(req = {}) { const message = this.protoSrv.write.GetBrowsersRequest(req); return this.pushService(STF.MessageType.GET_BROWSERS, message, this.protoSrv.read.GetBrowsersResponse); } async getClipboard(type = STF.ClipboardType.TEXT) { const message = this.protoSrv.write.GetClipboardRequest({ type }); return this.pushService(STF.MessageType.GET_CLIPBOARD, message, this.protoSrv.read.GetClipboardResponse); } async getDisplay(id = 0) { const message = this.protoSrv.write.GetDisplayRequest({ id }); return this.pushService(STF.MessageType.GET_DISPLAY, message, this.protoSrv.read.GetDisplayResponse); } async getProperties(properties) { const message = this.protoSrv.write.GetPropertiesRequest({ properties }); return this.pushService(STF.MessageType.GET_PROPERTIES, message, this.protoSrv.read.GetPropertiesResponse); } async getRingerMode(req = {}) { const message = this.protoSrv.write.GetRingerModeRequest(req); return this.pushService(STF.MessageType.GET_RINGER_MODE, message, this.protoSrv.read.GetRingerModeResponse); } async getSdStatus(req = {}) { const message = this.protoSrv.write.GetSdStatusRequest(req); return this.pushService(STF.MessageType.GET_SD_STATUS, message, this.protoSrv.read.GetSdStatusResponse); } // invalid response send by the service // public async getVersion(): Promise<STF.GetVersionResponse> { // const message = this.proto.write.GetVersionRequest(); // return this.pushEnvelop<STF.GetVersionResponse>(STF.MessageType.GET_VERSION, message }) // } async getWifiStatus(req = {}) { const message = this.protoSrv.write.GetWifiStatusRequest(req); return this.pushService(STF.MessageType.GET_WIFI_STATUS, message, this.protoSrv.read.GetWifiStatusResponse); } async getBluetoothStatus(req = {}) { const message = this.protoSrv.write.GetBluetoothStatusRequest(req); return this.pushService(STF.MessageType.GET_BLUETOOTH_STATUS, message, this.protoSrv.read.GetBluetoothStatusResponse); } async getRootStatus(req = {}) { const message = this.protoSrv.write.GetRootStatusRequest(req); return this.pushService(STF.MessageType.GET_ROOT_STATUS, message, this.protoSrv.read.GetRootStatusResponse); } async setClipboard(req) { const message = this.protoSrv.write.SetClipboardRequest(req); return this.pushService(STF.MessageType.SET_CLIPBOARD, message, this.protoSrv.read.SetClipboardResponse); } async setKeyguardState(req) { const message = this.protoSrv.write.SetKeyguardStateRequest(req); return this.pushService(STF.MessageType.SET_KEYGUARD_STATE, message, this.protoSrv.read.SetKeyguardStateResponse); } async setRingerMode(req) { const message = this.protoSrv.write.SetRingerModeRequest(req); return this.pushService(STF.MessageType.SET_RINGER_MODE, message, this.protoSrv.read.SetRingerModeResponse); } async setRotationRequest(req) { const message = this.protoSrv.write.SetRotationRequest(req); return this.pushService(STF.MessageType.SET_ROTATION, message); } async setWakeLock(req) { const message = this.protoSrv.write.SetWakeLockRequest(req); return this.pushService(STF.MessageType.SET_WAKE_LOCK, message, this.protoSrv.read.GetWifiStatusResponse); } async setWifiEnabledRequest(req) { const message = this.protoSrv.write.SetWifiEnabledRequest(req); return this.pushService(STF.MessageType.SET_WIFI_ENABLED, message, this.protoSrv.read.SetWifiEnabledResponse); } async setBluetoothEnabledRequest(req) { const message = this.protoSrv.write.SetBluetoothEnabledRequest(req); return this.pushService(STF.MessageType.SET_BLUETOOTH_ENABLED, message, this.protoSrv.read.SetBluetoothEnabledResponse); } async setMasterMute(req) { const message = this.protoSrv.write.SetMasterMuteRequest(req); return this.pushService(STF.MessageType.SET_MASTER_MUTE, message, this.protoSrv.read.SetMasterMuteResponse); } // Agents async doKeyEvent(req) { const message = this.protoSrv.write.KeyEventRequest(req); return this.pushAgent(STF.MessageType.DO_KEYEVENT, message); } async doType(req) { const message = this.protoSrv.write.DoTypeRequest(req); return this.pushAgent(STF.MessageType.DO_TYPE, message); } async doWake(req) { const message = this.protoSrv.write.DoWakeRequest(req); return this.pushAgent(STF.MessageType.DO_WAKE, message); } async setRotation(req) { const message = this.protoSrv.write.SetRotationRequest(req); return this.pushAgent(STF.MessageType.SET_ROTATION, message); } /** * Send commit minitouch events */ async commit() { const cmd = `c\n`; const s = await this.getMinitouchSocket(); return s.write(cmd, 'ascii'); } /** * Send move minitouch events */ async move(x, y, contact = 0, pressure = 0) { const cmd = `m ${contact | 0} ${x | 0} ${y | 0} ${pressure | 0}\n`; const s = await this.getMinitouchSocket(); return s.write(cmd, 'ascii'); } /** * Send press down minitouch events */ async down(x, y, contact = 0, pressure = 0) { const cmd = `d ${contact} ${x | 0} ${y | 0} ${pressure | 0}\n`; const s = await this.getMinitouchSocket(); return s.write(cmd, 'ascii'); } /** * Send press up minitouch events */ async up(contact = 0) { const cmd = `u ${contact | 0}\n`; const s = await this.getMinitouchSocket(); return s.write(cmd, 'ascii'); } /** * Send move + commit minitouch events */ async moveCommit(x, y, contact = 0, pressure = 0) { const cmd = `m ${contact | 0} ${x | 0} ${y | 0} ${pressure | 0}\nc\n`; const s = await this.getMinitouchSocket(); return s.write(cmd, 'ascii'); } /** * Send press down + commit minitouch events */ async downCommit(x, y, contact = 0, pressure = 0) { const cmd = `d ${contact} ${x | 0} ${y | 0} ${pressure | 0}\nc\n`; const s = await this.getMinitouchSocket(); return s.write(cmd, 'ascii'); } /** * Send press up + commit minitouch events */ async upCommit(contact = 0) { const cmd = `u ${contact | 0}\nc\n`; const s = await this.getMinitouchSocket(); return s.write(cmd, 'ascii'); } /** * Send wait instruction minitouch events */ async wait(time) { const cmd = `w ${time}\n`; const s = await this.getMinitouchSocket(); return s.write(cmd, 'ascii'); } /** * stop the service */ stop() { let close = false; if (this.servicesSocket) { this.servicesSocket.destroy(); this.servicesSocket = undefined; close = true; } if (this._agentSocket) { this._agentSocket.then(a => a.destroy()); this._agentSocket = undefined; close = true; } if (this._minitouchagent) { this._minitouchagent.then(a => a.destroy()); this._minitouchagent = undefined; close = true; } if (close) this.emit('disconnect'); return close; } isRunning() { return this.servicesSocket !== null; } } exports.default = STFService; //# sourceMappingURL=STFService.js.map