UNPKG

nativescript

Version:

Command-line interface for building NativeScript projects

209 lines • 9.82 kB
"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