UNPKG

vtex

Version:

The platform for e-commerce apps

118 lines (117 loc) 4.71 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const tslib_1 = require("tslib"); const get_stream_1 = tslib_1.__importDefault(require("get-stream")); const net_1 = tslib_1.__importDefault(require("net")); const ws_1 = tslib_1.__importDefault(require("ws")); const env_1 = require("../../api/env"); const Headers_1 = require("../../lib/constants/Headers"); const SessionManager_1 = require("../../api/session/SessionManager"); const locator_1 = require("../../api/locator"); const logger_1 = tslib_1.__importDefault(require("../../api/logger")); const user_agent_1 = tslib_1.__importDefault(require("../../user-agent")); const keepAliveDelayMs = 3 * 60 * 1000; const THIRTY_SECONDS_MS = 30 * 1000; const wsCloseCodeGoingAway = 1001; const wsCloseCodeError = 1011; const DEFAULT_DEBUGGER_PORT = 9229; const MAX_RETRY_COUNT = 40; function getErrorMessage(raw) { try { const errJson = JSON.parse(raw); return errJson.message || errJson.code || raw; } catch (err) { return raw; } } function webSocketTunnelHandler(host, path, server) { return (socket) => { socket.setKeepAlive(true, keepAliveDelayMs); const ws = new ws_1.default(`wss://${host}${path}`, { headers: { Authorization: SessionManager_1.SessionManager.getSingleton().checkAndGetToken(true), Host: host, 'user-agent': user_agent_1.default, [Headers_1.Headers.VTEX_RUNTIME_API]: 'true', ...(env_1.cluster() ? { [Headers_1.Headers.VTEX_UPSTREAM_TARGET]: env_1.cluster() } : null), }, }); const interval = setInterval(ws.ping, THIRTY_SECONDS_MS); const end = () => { clearInterval(interval); ws.removeAllListeners(); socket.removeAllListeners(); socket.destroy(); }; ws.on('close', end); ws.on('error', err => { end(); logger_1.default.error(`Debugger websocket error: ${err.name}: ${err.message}`); }); ws.on('unexpected-response', async (_, res) => { end(); const errMsg = getErrorMessage(await get_stream_1.default(res)); logger_1.default.warn(`Unexpected response from debugger hook (${res.statusCode}): ${errMsg}`); if (res.statusCode === 401 || res.statusCode === 403) { logger_1.default.warn(`Got unauthorized error from remote debugger, finalizing local debugger...`); server.close(); } }); ws.on('message', data => { try { socket.write(data); } catch (err) { end(); ws.close(wsCloseCodeError); } }); ws.on('open', () => { socket.on('data', data => { if (ws.readyState !== ws.OPEN) { logger_1.default.debug(`Tried to write to debugger websocket but it is not opened`); return; } ws.send(data, err => { if (err) { logger_1.default.error(`Error writing to debugger websocket: ${err.name}: ${err.message}`); } }); }); socket.on('close', hadError => { end(); ws.close(hadError ? wsCloseCodeError : wsCloseCodeGoingAway); }); }); }; } function startDebuggerTunnel(manifest, port = DEFAULT_DEBUGGER_PORT) { const { name, vendor, version, builders } = manifest; const { node, 'service-js': serviceJs } = builders; if (!node && !serviceJs) { return; } const { account, workspace } = SessionManager_1.SessionManager.getSingleton(); const appMajor = locator_1.versionMajor(version); const host = 'app.io.vtex.com'; const path = `/${vendor}.${name}/v${appMajor}/${account}/${workspace}/_debug/attach`; return new Promise((resolve, reject) => { const server = net_1.default.createServer(); server.on('connection', webSocketTunnelHandler(host, path, server)); server.on('error', err => { if (port < DEFAULT_DEBUGGER_PORT + MAX_RETRY_COUNT) { logger_1.default.warn(`Port ${port} in use, will try to open tunnel on port ${port + 1}`); resolve(startDebuggerTunnel(manifest, port + 1)); } else { reject(err); } }); server.listen(port, () => { const addr = server.address(); resolve(addr.port); }); }); } exports.default = startDebuggerTunnel;