nativescript
Version:
Command-line interface for building NativeScript projects
252 lines • 13.4 kB
JavaScript
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AndroidProcessService = void 0;
const os_1 = require("os");
const device_android_debug_bridge_1 = require("../android/device-android-debug-bridge");
const constants_1 = require("../../constants");
const decorators_1 = require("../../decorators");
const yok_1 = require("../../yok");
const _ = require("lodash");
class AndroidProcessService {
constructor($errors, $cleanupService, $injector, $net, $staticConfig) {
this.$errors = $errors;
this.$cleanupService = $cleanupService;
this.$injector = $injector;
this.$net = $net;
this.$staticConfig = $staticConfig;
this._devicesAdbs = {};
this._forwardedLocalPorts = {};
}
async forwardFreeTcpToAbstractPort(portForwardInputData) {
const adb = await this.setupForPortForwarding(portForwardInputData);
return this.forwardPort(portForwardInputData, adb);
}
async mapAbstractToTcpPort(deviceIdentifier, appIdentifier, framework) {
const adb = await this.setupForPortForwarding({
deviceIdentifier,
appIdentifier,
});
const processId = (await this.getProcessIds(adb, [appIdentifier]))[appIdentifier];
const applicationNotStartedErrorMessage = `The application is not started on the device with identifier ${deviceIdentifier}.`;
if (!processId) {
this.$errors.fail(applicationNotStartedErrorMessage);
}
const abstractPortsInformation = await this.getAbstractPortsInformation(adb);
const abstractPort = await this.getAbstractPortForApplication(adb, processId, appIdentifier, abstractPortsInformation, framework);
if (!abstractPort) {
this.$errors.fail(applicationNotStartedErrorMessage);
}
const forwardedTcpPort = await this.forwardPort({
deviceIdentifier,
appIdentifier,
abstractPort: `localabstract:${abstractPort}`,
}, adb);
return forwardedTcpPort && forwardedTcpPort.toString();
}
async getMappedAbstractToTcpPorts(deviceIdentifier, appIdentifiers, framework) {
const adb = this.getAdb(deviceIdentifier), abstractPortsInformation = await this.getAbstractPortsInformation(adb), processIds = await this.getProcessIds(adb, appIdentifiers), adbForwardList = await adb.executeCommand(["forward", "--list"]), localPorts = {};
await Promise.all(_.map(appIdentifiers, async (appIdentifier) => {
localPorts[appIdentifier] = null;
const processId = processIds[appIdentifier];
if (!processId) {
return;
}
const abstractPort = await this.getAbstractPortForApplication(adb, processId, appIdentifier, abstractPortsInformation, framework);
if (!abstractPort) {
return;
}
const localPort = await this.getAlreadyMappedPort(adb, deviceIdentifier, abstractPort, adbForwardList);
if (localPort) {
localPorts[appIdentifier] = localPort;
}
}));
return localPorts;
}
async getDebuggableApps(deviceIdentifier) {
const adb = this.getAdb(deviceIdentifier);
const androidWebViewPortInformation = (await this.getAbstractPortsInformation(adb)).split(os_1.EOL);
// TODO: Add tests and make sure only unique names are returned. Input before groupBy is:
// [ { deviceIdentifier: 'SH26BW100473',
// appIdentifier: 'com.telerik.EmptyNS',
// framework: 'NativeScript' },
// { deviceIdentifier: 'SH26BW100473',
// appIdentifier: 'com.telerik.EmptyNS',
// framework: 'Cordova' },
// { deviceIdentifier: 'SH26BW100473',
// appIdentifier: 'chrome',
// framework: 'Cordova' },
// { deviceIdentifier: 'SH26BW100473',
// appIdentifier: 'chrome',
// framework: 'Cordova' } ]
const portInformation = await Promise.all(_.map(androidWebViewPortInformation, async (line) => (await this.getApplicationInfoFromWebViewPortInformation(adb, deviceIdentifier, line)) ||
(await this.getNativeScriptApplicationInformation(adb, deviceIdentifier, line))));
return _(portInformation)
.filter((deviceAppInfo) => !!deviceAppInfo)
.groupBy((element) => element.framework)
.map((group) => _.uniqBy(group, (g) => g.appIdentifier))
.flatten()
.value();
}
async getAppProcessId(deviceIdentifier, appIdentifier) {
const adb = this.getAdb(deviceIdentifier);
const processId = (await this.getProcessIds(adb, [appIdentifier]))[appIdentifier];
return processId ? processId.toString() : null;
}
async forwardPort(portForwardInputData, adb) {
let localPort = await this.getAlreadyMappedPort(adb, portForwardInputData.deviceIdentifier, portForwardInputData.abstractPort);
if (!localPort) {
localPort = await this.$net.getFreePort();
await adb.executeCommand(["forward", `tcp:${localPort}`, portForwardInputData.abstractPort], { deviceIdentifier: portForwardInputData.deviceIdentifier });
}
this._forwardedLocalPorts[portForwardInputData.deviceIdentifier] = localPort;
await this.$cleanupService.addCleanupCommand({
command: await this.$staticConfig.getAdbFilePath(),
args: [
"-s",
portForwardInputData.deviceIdentifier,
"forward",
"--remove",
`tcp:${localPort}`,
],
});
return localPort && +localPort;
}
async setupForPortForwarding(portForwardInputData) {
const adb = this.getAdb(portForwardInputData.deviceIdentifier);
return adb;
}
async getApplicationInfoFromWebViewPortInformation(adb, deviceIdentifier, information) {
// Need to search by processId to check for old Android webviews (@webview_devtools_remote_<processId>).
const processIdRegExp = / /g;
const processIdMatches = processIdRegExp.exec(information);
let cordovaAppIdentifier;
if (processIdMatches) {
const processId = processIdMatches[1];
cordovaAppIdentifier = await this.getApplicationIdentifierFromPid(adb, processId);
}
else {
// Search for appIdentifier (@<appIdentifier>_devtools_remote).
const chromeAppIdentifierRegExp = /@(.+)_devtools_remote\s?/g;
const chromeAppIdentifierMatches = chromeAppIdentifierRegExp.exec(information);
if (chromeAppIdentifierMatches && chromeAppIdentifierMatches.length > 0) {
cordovaAppIdentifier = chromeAppIdentifierMatches[1];
}
}
if (cordovaAppIdentifier) {
return {
deviceIdentifier: deviceIdentifier,
appIdentifier: cordovaAppIdentifier,
framework: constants_1.TARGET_FRAMEWORK_IDENTIFIERS.Cordova,
};
}
return null;
}
async getNativeScriptApplicationInformation(adb, deviceIdentifier, information) {
// Search for appIdentifier (@<appIdentifier-debug>).
const nativeScriptAppIdentifierRegExp = /@(.+)-(debug|inspectorServer)/g;
const nativeScriptAppIdentifierMatches = nativeScriptAppIdentifierRegExp.exec(information);
if (nativeScriptAppIdentifierMatches &&
nativeScriptAppIdentifierMatches.length > 0) {
const appIdentifier = nativeScriptAppIdentifierMatches[1];
return {
deviceIdentifier: deviceIdentifier,
appIdentifier: appIdentifier,
framework: constants_1.TARGET_FRAMEWORK_IDENTIFIERS.NativeScript,
};
}
return null;
}
async getAbstractPortForApplication(adb, processId, appIdentifier, abstractPortsInformation, framework) {
// The result will look like this (without the columns names):
// Num RefCount Protocol Flags Type St Inode Path
// 0000000000000000: 00000002 00000000 00010000 0001 01 189004 @webview_devtools_remote_25512
// The Path column is the abstract port.
framework = framework || "";
switch (framework.toLowerCase()) {
case constants_1.TARGET_FRAMEWORK_IDENTIFIERS.Cordova.toLowerCase():
return this.getCordovaPortInformation(abstractPortsInformation, appIdentifier, processId);
case constants_1.TARGET_FRAMEWORK_IDENTIFIERS.NativeScript.toLowerCase():
return this.getNativeScriptPortInformation(abstractPortsInformation, appIdentifier);
default:
return (this.getCordovaPortInformation(abstractPortsInformation, appIdentifier, processId) ||
this.getNativeScriptPortInformation(abstractPortsInformation, appIdentifier));
}
}
getCordovaPortInformation(abstractPortsInformation, appIdentifier, processId) {
return (this.getPortInformation(abstractPortsInformation, `${appIdentifier}_devtools_remote`) || this.getPortInformation(abstractPortsInformation, processId));
}
getNativeScriptPortInformation(abstractPortsInformation, appIdentifier) {
return this.getPortInformation(abstractPortsInformation, `${appIdentifier}-debug`);
}
async getAbstractPortsInformation(adb) {
return adb.executeShellCommand(["cat", "/proc/net/unix"]);
}
getPortInformation(abstractPortsInformation, searchedInfo) {
const processRegExp = new RegExp(`\\w+:\\s+(?:\\w+\\s+){1,6}@(.*?${searchedInfo})$`, "gm");
const match = processRegExp.exec(abstractPortsInformation);
return match && match[1];
}
async getProcessIds(adb, appIdentifiers) {
// Process information will look like this (without the columns names):
// USER PID PPID VSIZE RSS WCHAN PC NAME
// u0_a63 25512 1334 1519560 96040 ffffffff f76a8f75 S com.telerik.appbuildertabstest
const result = {};
const processIdInformation = await adb.executeShellCommand(["ps"]);
_.each(appIdentifiers, (appIdentifier) => {
const processIdRegExp = new RegExp(`^\\w*\\s*(\\d+).*?${appIdentifier}$`);
result[appIdentifier] = this.getFirstMatchingGroupFromMultilineResult(processIdInformation, processIdRegExp);
});
return result;
}
async getAlreadyMappedPort(adb, deviceIdentifier, abstractPort, adbForwardList) {
const allForwardedPorts = adbForwardList || (await adb.executeCommand(["forward", "--list"])) || "";
// Sample output:
// 5e2e580b tcp:62503 localabstract:webview_devtools_remote_7985
// 5e2e580b tcp:62524 localabstract:webview_devtools_remote_7986
// 5e2e580b tcp:63160 localabstract:webview_devtools_remote_7987
// 5e2e580b tcp:57577 localabstract:com.telerik.nrel-debug
const regex = new RegExp(`${deviceIdentifier}\\s+?tcp:(\\d+?)\\s+?.*?${abstractPort}$`);
return this.getFirstMatchingGroupFromMultilineResult(allForwardedPorts, regex);
}
getAdb(deviceIdentifier) {
if (!this._devicesAdbs[deviceIdentifier]) {
this._devicesAdbs[deviceIdentifier] = this.$injector.resolve(device_android_debug_bridge_1.DeviceAndroidDebugBridge, {
identifier: deviceIdentifier,
});
}
return this._devicesAdbs[deviceIdentifier];
}
async getApplicationIdentifierFromPid(adb, pid, psData) {
psData = psData || (await adb.executeShellCommand(["ps"]));
// Process information will look like this (without the columns names):
// USER PID PPID VSIZE RSS WCHAN PC NAME
// u0_a63 25512 1334 1519560 96040 ffffffff f76a8f75 S com.telerik.appbuildertabstest
return this.getFirstMatchingGroupFromMultilineResult(psData, new RegExp(`\\s+${pid}(?:\\s+\\d+){3}\\s+.*\\s+(.*?)$`));
}
getFirstMatchingGroupFromMultilineResult(input, regex) {
let result;
_((input || "").split("\n"))
.map((line) => line.trim())
.filter((line) => !!line)
.each((line) => {
const matches = line.match(regex);
if (matches && matches[1]) {
result = matches[1];
return false;
}
});
return result;
}
}
exports.AndroidProcessService = AndroidProcessService;
__decorate([
(0, decorators_1.exported)("androidProcessService")
], AndroidProcessService.prototype, "getAppProcessId", null);
yok_1.injector.register("androidProcessService", AndroidProcessService);
//# sourceMappingURL=android-process-service.js.map