"use strict";
/*
 * Tencent is pleased to support the open source community by making
 * Hippy available.
 *
 * Copyright (C) 2017-2019 THL A29 Limited, a Tencent company.
 * All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.SocketServer = void 0;
const ws_1 = require("ws");
const enum_1 = require("@debug-server-next/@types/enum");
const debug_targets_1 = require("@debug-server-next/controller/debug-targets");
const pub_sub_manager_1 = require("@debug-server-next/controller/pub-sub-manager");
const chrome_devtools_1 = require("@debug-server-next/controller/chrome-devtools");
const log_1 = require("@debug-server-next/utils/log");
const url_1 = require("@debug-server-next/utils/url");
const config_1 = require("@debug-server-next/config");
const hmr_1 = require("@debug-server-next/controller/hmr");
const vue_devtools_1 = require("@debug-server-next/controller/vue-devtools");
const react_devtools_1 = require("@debug-server-next/controller/react-devtools");
const heartbeatInterval = 30000;
const log = new log_1.Logger('socket-server', enum_1.WinstonColor.Cyan);
/**
 * Debug WebSocket server, support deploy in multiple node server, use Redis Pub/Sub broadcast message
 */
class SocketServer {
    constructor(server) {
        this.server = server;
    }
    start() {
        const wss = new ws_1.Server({
            noServer: true,
            path: config_1.config.wsPath,
        });
        this.wss = wss;
        this.server.on('upgrade', this.onUpgrade.bind(this));
        wss.on('connection', this.onConnection.bind(this));
        wss.on('error', (e) => {
            var _a;
            log.error('wss error: %s', (_a = e) === null || _a === void 0 ? void 0 : _a.stack);
        });
        wss.on('upgrade', (response) => {
            log.info('wss upgrade: %j', response);
        });
        wss.on('close', () => {
            clearInterval(this.interval);
        });
        this.interval = setInterval(() => {
            this.wss.clients.forEach((client) => {
                if (client.isAlive === false) {
                    client.terminate();
                    return;
                }
                client.isAlive = false;
                client.ping(() => { });
            });
        }, heartbeatInterval);
    }
    async close() {
        await pub_sub_manager_1.cleanAllDebugTargets();
        this.wss.close(() => {
            log.warn('wss closed.');
        });
    }
    onUpgrade(req, socket, head) {
        log.verbose('onUpgrade, ws url: %s', req.url);
        const wsUrlParams = url_1.parseWsUrl(req.url);
        const reason = url_1.getWsInvalidReason(wsUrlParams);
        if (reason) {
            log.warn('onUpgrade error: %s', reason);
            return socket.destroy();
        }
        this.wss.handleUpgrade(req, socket, head, (ws) => {
            this.wss.emit('connection', ws, req);
        });
    }
    /**
     * ⚠️: don't do async operation before subscribe, otherwise will miss message
     */
    async onConnection(ws, req) {
        const wsUrlParams = url_1.parseWsUrl(req.url);
        const host = (req.headers.host || req.headers.Host) || '';
        ws.isAlive = true;
        ws.on('pong', () => {
            ws.isAlive = true;
        });
        const { clientRole } = wsUrlParams;
        if ([enum_1.ClientRole.JSRuntime, enum_1.ClientRole.VueDevtools].includes(clientRole)) {
            vue_devtools_1.onVueClientConnection(ws, wsUrlParams);
            return;
        }
        if ([enum_1.ClientRole.ReactJSRuntime, enum_1.ClientRole.ReactDevtools].includes(clientRole)) {
            react_devtools_1.onReactClientConnection(ws, wsUrlParams);
            return;
        }
        if (clientRole === enum_1.ClientRole.HMRClient) {
            hmr_1.onHMRClientConnection(ws, wsUrlParams);
        }
        else if (clientRole === enum_1.ClientRole.HMRServer) {
            hmr_1.onHMRServerConnection(ws, wsUrlParams);
        }
        else if (clientRole === enum_1.ClientRole.Devtools) {
            chrome_devtools_1.onDevtoolsConnection(ws, wsUrlParams);
        }
        else {
            chrome_devtools_1.onAppConnection(ws, wsUrlParams, host);
        }
        if (clientRole === enum_1.ClientRole.Devtools) {
            const params = wsUrlParams;
            const exist = await this.checkDebugTargetExist(params);
            if (!exist) {
                const reason = `debugTarget not exist! ${params.clientId}`;
                log.warn(reason);
                ws.close(enum_1.WSCode.NoDebugTarget, reason);
            }
        }
    }
    async checkDebugTargetExist(wsUrlParams) {
        const { clientId, hash } = wsUrlParams;
        const debugTarget = await debug_targets_1.DebugTargetManager.findDebugTarget(clientId, hash);
        log.verbose('checkDebugTargetExist debug target: %j', debugTarget);
        return Boolean(debugTarget);
    }
}
exports.SocketServer = SocketServer;
