UNPKG

@cliz/inlets

Version:
182 lines (181 loc) 8.97 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.createWebSocketMonitor = void 0; const websocket_1 = require("@zodash/websocket"); const doreamon_1 = require("@zodash/doreamon"); const cli_1 = require("@cliz/cli"); const hmac = require("@zodash/hmac"); const semver = require("semver"); const utils_1 = require("../../../utils"); const debug = require('debug')('inlets:monitor:ws'); const logger = doreamon_1.default.logger.getLogger('monitor:ws '); function createWebSocketMonitor(ctx, options) { const emitter = new doreamon_1.default.event.Event(); const version = options.version; const domain = options.domain; const secure = !!options.secure; const token = options.token; const port = +options.port; function getServerUrlBySubDomain(subDomain) { if (!subDomain) return null; if (secure) { return `https://${subDomain}.${domain}`; } return `http://${subDomain}.${domain}:${port}`; } const ws = new websocket_1.default.Server({ path: ctx.config.wsPath, }); ws.on('connection', async (wsSocket) => { logger.info('[tunnel:client] connect ws server ...'); let isAuthenticated = false; let subDomain = null; setTimeout(() => { if (!isAuthenticated && wsSocket.isAlive) { logger.info('[tunnel:client] removed without authorization'); wsSocket.disconnect(); } }, 10 * 1000); wsSocket.on('authenticate', async (authentication) => { var _a, _b; logger.info(`[tunnel:client] version: ${authentication.version}`); const clientVersion = authentication.version; const serverVersion = version; if (!semver.gte(clientVersion, serverVersion)) { logger.warn(`[tunnel:client] warning: client version(${clientVersion}) should be larger than or equal with server(${serverVersion}), which may cause tunnel broken.`); wsSocket.emit('warn', `[tunnel:client] warning: client version(${clientVersion}) should be larger than or equal with server(${serverVersion}), which may cause tunnel broken.`); } let signedSecret; let clientConfig; try { const res = await token(authentication.authType, authentication.clientId, { type: authentication.type, }); if (res.authType === 'public' && authentication.subDomain) { const message = 'subDomain is not allowed for public authType'; wsSocket.emit('authenticate', { ok: false, message }); return wsSocket.disconnect(); } signedSecret = res.token; clientConfig = res.config; if (!signedSecret) { isAuthenticated = false; const message = `invalid client(${authentication.authType})`; logger.info(`[tunnel:client] force disconnect - ${message} -`, authentication); wsSocket.emit('authenticate', { ok: false, message }); return wsSocket.disconnect(); } } catch (error) { isAuthenticated = false; const message = `invalid client(${error.message})`; logger.info(`[tunnel:client] force disconnect - ${message} -`, authentication); wsSocket.emit('authenticate', { ok: false, message }); return wsSocket.disconnect(); } const signature = hmac.hmacSHA512(authentication.timestamp.toString(), signedSecret); if (signature !== authentication.signature) { isAuthenticated = false; const message = `invalid signature`; logger.info(`[tunnel:client] force disconnect - ${message}`); wsSocket.emit('authenticate', { ok: false, message }); return wsSocket.disconnect(); } isAuthenticated = true; logger.info(`[tunnel:client] type: ${authentication.type}`); const containerId = doreamon_1.default.uuid(); if (authentication.type === 'tcp') { if (authentication.tunnelPort) { logger.info(`[tunnel:client] tunnel port: ${authentication.tunnelPort}`); } if (authentication.tunnelPort && !await cli_1.api.network.isPortAvailable(authentication.tunnelPort)) { isAuthenticated = false; const message = `tunnel tcp port(${authentication.tunnelPort}) has already been used.`; logger.info(`[tunnel:client] force disconnect - ${message}`); wsSocket.emit('authenticate', { ok: false, message }); return wsSocket.disconnect(); } ctx.container.create(containerId, token, wsSocket, authentication); } else if (authentication.type === 'http') { if (!authentication.subDomain) { subDomain = ctx.domainMappings.bindWs(wsSocket); } else { logger.info(`[tunnel:client][domain] request: ${authentication.subDomain}.${domain}`); const hasSubDomain = ctx.domainMappings.has(authentication.subDomain); if (hasSubDomain) { wsSocket.emit('authenticate', { ok: false, message: 'domain id has been used, please use another', }); logger.info(`[tunnel:client][domain] illegal: ${authentication.subDomain}.${domain}`); return wsSocket.disconnect(); } subDomain = ctx.domainMappings.bindWs(wsSocket, authentication.subDomain); } logger.info(`[tunnel:client][domain] ${subDomain}.${domain}`); } else { throw new Error(`unknown authentication type: ${authentication.type}`); } const config = { version, notification: (_a = options.notification) === null || _a === void 0 ? void 0 : _a.config, ...clientConfig, }; wsSocket.emit('authenticate', { ok: true, version, url: getServerUrlBySubDomain(subDomain), config, }); wsSocket.containerId = containerId; (_b = options === null || options === void 0 ? void 0 : options.notification) === null || _b === void 0 ? void 0 : _b.notify(`[上线] 客户端 - ${authentication.clientId}`, [ `客户端版本:${authentication.version}`, `客户端类型:${authentication.type}`, `客户端授权方式:${authentication.authType}`, `客户端端口:${authentication.tunnelPort}`, `当前时间:${doreamon_1.default.date().format('YYYY-MM-DD HH:mm:ss')}` ]); emitter.emit('tunnel', { type: authentication.type, containerId, }); }); wsSocket.on('response', async function onResponse(response) { if (!isAuthenticated) return; const [tcpId, requestId] = response.id.split(':'); const callback = ctx.callbackContainer.get(tcpId, requestId); if (callback) { const data = await utils_1.dataProcessor.server.onResponse(response.data); callback.apply(null, [data]); } }); wsSocket.on('disconnect', () => { var _a; logger.info('[tunnel:client] disconnected'); if (subDomain) { ctx.domainMappings.unbindWs(subDomain); } const authentication = ctx.container.get(wsSocket.containerId); if (!authentication) { return logger.error(`Cannot get container id: ${wsSocket.containerId}`); } (_a = options === null || options === void 0 ? void 0 : options.notification) === null || _a === void 0 ? void 0 : _a.notify(`[掉线] 客户端 - ${authentication.clientId}`, [ `客户端版本:${authentication.version}`, `客户端类型:${authentication.type}`, `客户端授权方式:${authentication.authType}`, `客户端端口:${authentication.tunnelPort}`, `当前时间:${doreamon_1.default.date().format('YYYY-MM-DD HH:mm:ss')}` ]); }); }); return { ws, emitter, }; } exports.createWebSocketMonitor = createWebSocketMonitor;