nativescript
Version:
Command-line interface for building NativeScript projects
209 lines • 9.82 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.AppDebugSocketProxyFactory = void 0;
const events_1 = require("events");
const constants_1 = require("../../constants");
const net = require("net");
const ws = require("ws");
const ios_device_lib_1 = require("ios-device-lib");
const yok_1 = require("../../common/yok");
class AppDebugSocketProxyFactory extends events_1.EventEmitter {
constructor($logger, $errors, $lockService, $options, $tempService, $net) {
super();
this.$logger = $logger;
this.$errors = $errors;
this.$lockService = $lockService;
this.$options = $options;
this.$tempService = $tempService;
this.$net = $net;
this.deviceWebServers = {};
this.deviceTcpServers = {};
}
getTCPSocketProxy(deviceIdentifier, appId) {
return this.deviceTcpServers[`${deviceIdentifier}-${appId}`];
}
async addTCPSocketProxy(device, appId, projectName, projectDir) {
const cacheKey = `${device.deviceInfo.identifier}-${appId}`;
const existingServer = this.deviceTcpServers[cacheKey];
if (existingServer) {
this.$errors.fail(`TCP socket proxy is already running for device '${device.deviceInfo.identifier}' and app '${appId}'`);
}
this.$logger.info("\nSetting up proxy...\nPress Ctrl + C to terminate, or disconnect.\n");
const server = net.createServer({
allowHalfOpen: true,
});
this.deviceTcpServers[cacheKey] = server;
server.on("connection", async (frontendSocket) => {
this.$logger.info("Frontend client connected.");
frontendSocket.on("end", () => {
this.$logger.info("Frontend socket closed!");
if (!this.$options.watch) {
process.exit(0);
}
});
const appDebugSocket = await device.getDebugSocket(appId, projectName, projectDir);
this.$logger.info("Backend socket created.");
appDebugSocket.on("end", () => {
this.$logger.info("Backend socket closed!");
if (!this.$options.watch) {
process.exit(0);
}
});
frontendSocket.on("close", async () => {
this.$logger.info("Frontend socket closed");
await device.destroyDebugSocket(appId);
});
appDebugSocket.on("close", () => {
this.$logger.info("Backend socket closed");
frontendSocket.destroy();
server.close();
delete this.deviceTcpServers[cacheKey];
});
appDebugSocket.pipe(frontendSocket);
frontendSocket.pipe(appDebugSocket);
frontendSocket.resume();
});
const socketFileLocation = await this.$tempService.path({
suffix: ".sock",
});
server.listen(socketFileLocation);
if (!this.$options.client) {
this.$logger.info("socket-file-location: " + socketFileLocation);
}
return server;
}
async ensureWebSocketProxy(device, appId, projectName, projectDir) {
const existingWebProxy = this.deviceWebServers[`${device.deviceInfo.identifier}-${appId}`];
const result = existingWebProxy ||
(await this.addWebSocketProxy(device, appId, projectName, projectDir));
// TODO: do not remove till VSCode waits for this message in order to reattach
this.$logger.info("Opened localhost " + result.options.port);
return result;
}
async addWebSocketProxy(device, appId, projectName, projectDir) {
let clientConnectionLockRelease;
const cacheKey = `${device.deviceInfo.identifier}-${appId}`;
const existingServer = this.deviceWebServers[cacheKey];
if (existingServer) {
this.$errors.fail(`Web socket proxy is already running for device '${device.deviceInfo.identifier}' and app '${appId}'`);
}
// NOTE: We will try to provide command line options to select ports, at least on the localhost.
const localPort = await this.$net.getAvailablePortInRange(41000);
this.$logger.info("\nSetting up debugger proxy...\nPress Ctrl + C to terminate, or disconnect.\n");
// NB: When the inspector frontend connects we might not have connected to the inspector backend yet.
// That's why we use the verifyClient callback of the websocket server to stall the upgrade request until we connect.
// We store the socket that connects us to the device in the upgrade request object itself and later on retrieve it
// in the connection callback.
let currentAppSocket = null;
let currentWebSocket = null;
const server = new ws.Server({
port: localPort,
verifyClient: async (info, callback) => {
let acceptHandshake = true;
clientConnectionLockRelease = null;
try {
clientConnectionLockRelease = await this.$lockService.lock(`debug-connection-${device.deviceInfo.identifier}-${appId}.lock`);
this.$logger.info("Frontend client connected.");
let appDebugSocket;
if (currentAppSocket) {
currentAppSocket.removeAllListeners();
currentAppSocket = null;
if (currentWebSocket) {
currentWebSocket.removeAllListeners();
currentWebSocket.close();
currentWebSocket = null;
}
await device.destroyDebugSocket(appId);
}
appDebugSocket = await device.getDebugSocket(appId, projectName, projectDir);
currentAppSocket = appDebugSocket;
this.$logger.info("Backend socket created.");
info.req["__deviceSocket"] = appDebugSocket;
}
catch (err) {
if (clientConnectionLockRelease) {
clientConnectionLockRelease();
}
err.deviceIdentifier = device.deviceInfo.identifier;
this.$logger.trace(err);
this.emit(constants_1.CONNECTION_ERROR_EVENT_NAME, err);
acceptHandshake = false;
this.$logger.warn(`Cannot connect to device socket. The error message is '${err.message}'.`);
}
callback(acceptHandshake);
},
});
this.deviceWebServers[cacheKey] = server;
server.on("connection", (webSocket, req) => {
currentWebSocket = webSocket;
const encoding = "utf16le";
const appDebugSocket = req["__deviceSocket"];
const packets = new ios_device_lib_1.MessageUnpackStream();
appDebugSocket.pipe(packets);
packets.on("data", (buffer) => {
const message = buffer.toString(encoding);
if (webSocket.readyState === webSocket.OPEN) {
if (process.env.DEBUG_DEVTOOLS_SOCKETS) {
console.log({
msgFromRuntime: JSON.parse(message),
});
}
webSocket.send(message);
}
else {
this.$logger.trace(`Received message ${message}, but unable to send it to webSocket as its state is: ${webSocket.readyState}`);
}
});
webSocket.on("error", (err) => {
this.$logger.trace("Error on debugger websocket", err);
});
appDebugSocket.on("error", (err) => {
this.$logger.trace("Error on debugger deviceSocket", err);
});
webSocket.on("message", (message) => {
const msg = message.toString();
if (process.env.DEBUG_DEVTOOLS_SOCKETS) {
console.log({
msgFromDevtools: JSON.parse(msg),
});
}
const length = Buffer.byteLength(msg, encoding);
const payload = Buffer.allocUnsafe(length + 4);
payload.writeInt32BE(length, 0);
payload.write(msg, 4, length, encoding);
appDebugSocket.write(payload);
});
appDebugSocket.on("close", () => {
currentAppSocket = null;
this.$logger.trace("Backend socket closed!");
webSocket.close();
});
webSocket.on("close", async () => {
currentWebSocket = null;
this.$logger.trace("Frontend socket closed!");
appDebugSocket.unpipe(packets);
packets.destroy();
await device.destroyDebugSocket(appId);
if (!this.$options.watch) {
process.exit(0);
}
});
clientConnectionLockRelease();
});
return server;
}
removeAllProxies() {
let deviceId;
for (deviceId in this.deviceWebServers) {
this.deviceWebServers[deviceId].close();
}
for (deviceId in this.deviceTcpServers) {
this.deviceTcpServers[deviceId].close();
}
this.deviceWebServers = {};
this.deviceTcpServers = {};
}
}
exports.AppDebugSocketProxyFactory = AppDebugSocketProxyFactory;
yok_1.injector.register("appDebugSocketProxyFactory", AppDebugSocketProxyFactory);
//# sourceMappingURL=app-debug-socket-proxy-factory.js.map