@cliz/inlets
Version:
Cloud Native Tunnel
182 lines (181 loc) • 8.97 kB
JavaScript
;
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;