@webos-tools/cli
Version:
Command Line Interface for development webOS application and service
494 lines (443 loc) • 22.4 kB
JavaScript
/*
* Copyright (c) 2020-2024 LG Electronics Inc.
*
* SPDX-License-Identifier: Apache-2.0
*/
const async = require('async'),
npmlog = require('npmlog'),
path = require('path'),
request = require('request'),
util = require('util'),
installer = require('./install'),
launcher = require('./launch'),
errHndl = require('./base/error-handler'),
luna = require('./base/luna'),
novacom = require('./base/novacom'),
sdkenv = require('./base/sdkenv'),
server = require('./base/server'),
spinner = require('./util/spinner');
// The node service debugging ways has changed based on node version 8.
const nodeBaseVersion = "8",
defaultAppInsptPort = "9998",
defaultNodeInsptPort = "8080",
defaultServiceDebugPort = "5885";
let platformNodeVersion = "0";
(function() {
const log = npmlog;
log.heading = 'inspector';
log.level = 'warn';
const inspector = {
/**
* @property {Object} log an npm log instance
*/
log: log,
inspect: function(options, next) {
if (typeof next !== 'function') {
throw errHndl.getErrMsg("MISSING_CALLBACK", "next", util.inspect(next));
}
options.svcDbgInfo = {}; /* { id : { port : String , path : String } } */
if (options && Object.prototype.hasOwnProperty.call(options, 'serviceId')) {
if (options.serviceId instanceof Array) {
options.serviceId.forEach(function(id) {
options.svcDbgInfo[id] = {};
});
} else {
options.svcDbgInfo[options.serviceId] = {};
}
}
async.series([
_findSdkEnv,
_makeSession,
_getPkgList,
_runApp,
_runAppPortForwardServer,
_runAppInspector,
_runServicePortForwardServer
], function(err, results) {
log.silly("inspect#inspect()", "err:", err, ", results:", results);
const returnObj = {session: options.session};
returnObj.msg = options.serviceId ? results[6] : "Application Debugging - " + results[5];
next(err, returnObj);
});
function _findSdkEnv(next) {
const env = new sdkenv.Env();
env.getEnvValue("BROWSER", function(err, browserPath) {
options.bundledBrowserPath = browserPath;
next();
});
}
function _getPkgList(next) {
if (!options.serviceId) {
return next();
}
installer.list(options, function(err, pkgs) {
if (pkgs instanceof Array) {
options.instPkgs = pkgs;
}
next(err);
});
}
function _makeSession(next) {
options.nReplies = 1; // -n 1
const printTarget = true;
options.session = new novacom.Session(options.device, printTarget, next);
}
function _runApp(next) {
log.verbose("inspect#inspect()#_runApp()");
if (!options.appId || options.running) {
return next();
}
launcher.listRunningApp(options, function(err, runningApps) {
if (err) {
return next(err);
}
runningApps = [].concat(runningApps);
const runAppIds = runningApps.map(function(app) {
return app.id;
});
log.info("inspect#inspect()#_runApp()", "runAppIds:", runAppIds.join(','));
if (runAppIds.indexOf(options.appId) === -1) {
log.verbose("inspect#inspect()#_runApp#launch", options.appId);
launcher.launch(options, options.appId, {}, next);
} else {
next();
}
});
}
function _runAppPortForwardServer(next) {
if (options.appId) {
const insptPort = options.sessionInsptPort || defaultAppInsptPort;
log.verbose("inspect#inspect()#_runAppPortForwardServer()", "insptPort : " + insptPort);
options.session.forward(insptPort , options.hostPort || 0 /* random port */, options.appId, next);
} else {
next();
}
}
function __findNewDebugPort(dbgPort, next) {
const format = "netstat -ltn 2>/dev/null | grep :%s | wc -l",
cmdPortInUsed = util.format(format, dbgPort);
async.series([
options.session.run.bind(options.session, cmdPortInUsed, process.stdin, _onData, process.stderr),
], function(err) {
if (err) {
next(err);
}
});
function _onData(data) {
let str;
if (Buffer.isBuffer(data)) {
str = data.toString().trim();
} else {
str = data.trim();
}
if (str === "0") {
log.silly("inspect#inspect()#__findNewDebugPort()", "final dbgPort : " + dbgPort);
next(null, dbgPort);
} else if (str === "1") {
dbgPort = Number(dbgPort) +1;
__findNewDebugPort(dbgPort, next);
} else {
return next(errHndl.getErrMsg("FAILED_GET_PORT"));
}
}
}
function __getNodeVersion(next) {
const format = "node -v";
let count = 0;
async.series([
options.session.run.bind(options.session, format, process.stdin, _onData, process.stderr),
], function(err) {
if (err) {
next(err);
}
});
function _onData(data) {
if (++count > 1)
return;
if (Buffer.isBuffer(data)) {
platformNodeVersion = data.toString().trim();
} else {
platformNodeVersion = data.trim();
}
const parsedVersion = platformNodeVersion.split(".");
platformNodeVersion = Number(parsedVersion[0].substring(1, parsedVersion[0].length));
next();
}
}
function _runServicePortForwardServer(next) {
let guideText = "";
const svcIds = Object.keys(options.svcDbgInfo).filter(function(id) {
return id !== 'undefined';
});
async.forEachSeries(svcIds, __eachServicePortForward, function(err) {
next(err, guideText);
}
);
function __eachServicePortForward(serviceId, next) {
if (!serviceId) {
return next();
}
// Only for Auto, add display+1 in prefix port
const sessionPort = Number(options.display) +1;
let dbgPort = defaultServiceDebugPort;
if (options.sessionId && options.display !== undefined) {
dbgPort = sessionPort + dbgPort;
}
log.info("inspect#inspect()#_eachServicePortForward()", "sessionId:" + options.sessionId + ", default dbgPort : " + dbgPort);
const __printInspectGuide = function(svcId, next) {
if (options.open) {
guideText = "Cannot support \"--open option\" on platform node version 8 and later\n";
}
// From node v16.18.1 on platform, using 127.0.0.1 instead of localhost
guideText += "To debug your service, set " + "\"127.0.0.1:" + options.session.getLocalPortByName(svcId) +
"\" on Node's Inspector Client(Chrome DevTools, Visual Studio Code, etc.).";
next();
};
const __launchServiceInspector = function(svcId, next) {
if (!options.svcDbgInfo[svcId].port) {
return next();
}
// open browser with the following url.
// http://localhost:(host random port)/debug?port=(node debug port)
const ip = 'localhost',
nodeInsptPort = options.session.getLocalPortByName(svcId),
nodeDebugPort = options.svcDbgInfo[svcId].port,
urlFormat = "http://%s:%s/debug?port=%s",
nodeInsptUrl = util.format(urlFormat, ip, nodeInsptPort, nodeDebugPort);
let killTimer;
request.get(nodeInsptUrl, function(error, response) {
if (!error && response.statusCode === 200) {
server.runServer(__dirname, 0, _reqHandler, _postAction);
}
function _reqHandler(code, res) {
if (code === "@@ARES_CLOSE@@") {
res.status(200).send();
killTimer = setTimeout(function() {
process.exit(0);
}, 2 * 1000);
} else if (code === "@@GET_URL@@") {
clearTimeout(killTimer);
res.status(200).send(nodeInsptUrl);
}
}
function _postAction(err, serverInfo) {
if (err) {
process.exit(1);
} else if (serverInfo && serverInfo.msg && options.open) {
server.openBrowser(serverInfo.openBrowserUrl, options.bundledBrowserPath);
}
guideText = "nodeInsptUrl: " + nodeInsptUrl;
next();
}
});
};
async.waterfall([
function findSvcFilePath(next) {
log.info("inspect#inspect()#findSvcFilePath()");
spinner.start();
if (options.instPkgs) {
options.instPkgs.every(function(pkg) {
if (serviceId.indexOf(pkg.id) !== -1) {
options.svcDbgInfo[serviceId].path = path.join(path.dirname(pkg.folderPath), '..', 'services', serviceId).replace(/\\/g, '/');
return false;
}
return true;
});
}
if (!options.svcDbgInfo[serviceId].path) {
return next(errHndl.getErrMsg("FAILED_GET_SVCPATH", serviceId));
}
next();
},
function parserMeta(next) {
const metaFilePath = path.join(options.svcDbgInfo[serviceId].path, "services.json").replace(/\\/g, '/'),
cmdCatServiceInfo = "cat " + metaFilePath;
let metaData;
async.series([
options.session.run.bind(options.session, cmdCatServiceInfo, process.stdin, _onData, process.stderr)
], function(err) {
if (err) {
return next(errHndl.getErrMsg("FAILED_FIND_SERVICE", serviceId));
}
});
function _onData(data) {
if (Buffer.isBuffer(data)) {
metaData = data.toString().trim();
} else {
metaData = data.trim();
}
next(null, metaData);
}
},
function checkServiceType(metaData, next) {
try {
const metaInfo = JSON.parse(metaData);
if (metaInfo.engine === "native") {
return next(errHndl.getErrMsg("USE_GDB", serviceId));
}
next();
} catch (err) {
next(err);
}
},
function quitPrevService(next) {
options.nReplies = 1;
const param = {},
addr = {
"service": serviceId,
"method": "quit"
};
luna.send(options, addr, param, function() {
next();
}, next);
},
function mkDirForDbgFile(next) {
const cmdMkDir = "mkdir -p " + options.svcDbgInfo[serviceId].path + "/_ares";
options.session.runNoHangup(cmdMkDir, next);
},
__findNewDebugPort.bind(this, dbgPort),
function makeDbgFile(port, next) {
dbgPort = port;
const cmdWriteDbgPort = "echo " + dbgPort + " > " + options.svcDbgInfo[serviceId].path + "/_ares/debugger-port";
options.session.runNoHangup(cmdWriteDbgPort, next);
},
function(next) {
setTimeout(function() {
next();
},1000);
},
function runService(next) {
log.info("inspect#inspect()#runService()", "serviceId :", serviceId);
const param = {},
addr = {
"service": serviceId,
"method": "info"
};
options.svcDbgInfo[serviceId].port = dbgPort;
options.nReplies = 1;
luna.send(options, addr, param, function() {
next();
}, next);
},
function(next) {
setTimeout(function() {
next();
},1000);
},
__getNodeVersion.bind(this),
function doPortForward(next) {
log.info("inspect#inspect()#_doPortForward()", "platformNodeVersion: "+ platformNodeVersion + ", nodeBaseVersion: " + nodeBaseVersion);
if (platformNodeVersion < nodeBaseVersion) {
options.session.forward(defaultNodeInsptPort, options.hostPort || 0 /* random port */, serviceId, next);
}
else if (platformNodeVersion >= nodeBaseVersion) {
options.session.forward(dbgPort, options.hostPort || 0 /* random port */, serviceId, next);
}
},
function clearDbgFile(next) {
const cmdRmDbgFile = "rm -rf " + options.svcDbgInfo[serviceId].path + "/_ares";
options.session.runNoHangup(cmdRmDbgFile, next);
},
// FIXME: this code is need to improve.
function printGuide(next) {
spinner.stop();
if (platformNodeVersion < nodeBaseVersion) {
__launchServiceInspector(serviceId, next);
}
else if (platformNodeVersion >= nodeBaseVersion) {
__printInspectGuide(serviceId, next);
}
}
], function(err, results) {
log.silly("inspect#inspect()", "err:", err, ", results:", results);
next(err, results);
});
}
}
function _runAppInspector(next) {
let url, killTimer,
listFiles = [
{ reqPath: "/pagelist.json", propName: "inspectorUrl" }, /* AFRO, BHV */
{ reqPath: "/json/list", propName: "devtoolsFrontendUrl" } /* DRD */
];
function _getDisplayUrl(next) {
const listFile = listFiles.pop();
if (!listFile) {
return next();
}
request.get(url + listFile.reqPath, function(error, response, body) {
if (error || response.statusCode !== 200) {
return next();
}
const pagelist = JSON.parse(body);
for (const index in pagelist) {
if (pagelist[index].url.indexOf(options.appId) !== -1 ||
pagelist[index].url.indexOf(options.localIP) !== -1) {
if (!pagelist[index][listFile.propName]) {
return next(errHndl.getErrMsg("USING_WEBINSPECTOR"));
}
url += pagelist[index][listFile.propName];
listFiles = [];
break;
}
}
next(null, listFiles);
});
}
function _reqHandler(code, res) {
if (code === "@@ARES_CLOSE@@") {
res.status(200).send();
killTimer = setTimeout(function() {
process.exit(0);
}, 2 * 1000);
} else if (code === "@@GET_URL@@") {
clearTimeout(killTimer);
res.status(200).send(url);
}
}
function _postAction(err, serverInfo) {
spinner.stop();
if (err) {
process.exit(1);
} else if (serverInfo && serverInfo.msg && options.open) {
server.openBrowser(serverInfo.openBrowserUrl, options.bundledBrowserPath);
}
next(null, url);
}
if (options.appId) {
url = "http://localhost:" + options.session.getLocalPortByName(options.appId);
if (options.session.target.noPortForwarding) {
log.verbose("inspect#inspect()","noPortForwarding");
const insptPort = options.sessionInsptPort || defaultAppInsptPort;
url = "http://" + options.session.target.host + ":" + insptPort;
}
async.whilst(
function(callBack) {
callBack(null, listFiles && listFiles.length > 0);
},
_getDisplayUrl.bind(this),
function(err) {
if (err) {
return next(err);
}
server.runServer(__dirname, 0, _reqHandler, _postAction);
}
);
} else {
next();
}
}
},
stop: function(session, next) {
log.verbose("inspect#stop()", "session:", session);
if (!session.end) {
return next(errHndl.getErrMsg("NOT_EXIST_INSPECTOR"));
}
session.end();
next(null, {msg: "This inspection has stopped"});
}
};
if (typeof module !== 'undefined' && module.exports) {
module.exports = inspector;
}
}());