"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 debug_target_1 = require("@debug-server-next/utils/debug-target");
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 vanilla_js_devtools_1 = require("@debug-server-next/controller/vanilla-js-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) => {
            log.error('wss error: %s', e === null || e === void 0 ? void 0 : e.stack);
        });
        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 (0, pub_sub_manager_1.cleanAllDebugTargets)();
        this.wss.close(() => {
            log.warn('wss closed.');
        });
    }
    async onUpgrade(req, socket, head) {
        log.verbose('onUpgrade, ws url: %s', req.url);
        const host = (req.headers.host || req.headers.Host) || '';
        const wsUrlParams = (0, url_1.parseWsUrl)(req.url);
        const reason = (0, url_1.getWsInvalidReason)(wsUrlParams);
        if (reason) {
            log.warn('onUpgrade error: %s', reason);
            return socket.destroy();
        }
        const { clientRole } = wsUrlParams;
        let debugTarget;
        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);
                return socket.destroy();
            }
        }
        else if ([enum_1.ClientRole.IOS, enum_1.ClientRole.Android].includes(clientRole)) {
            debugTarget = (0, debug_target_1.createTargetByWsUrlParams)(wsUrlParams, host);
            await (0, debug_target_1.patchRefAndSave)(debugTarget);
        }
        this.wss.handleUpgrade(req, socket, head, (ws) => {
            this.wss.emit('connection', ws, req, debugTarget);
        });
    }
    /**
     * ⚠️: don't do async operation before subscribe, otherwise will miss message
     */
    async onConnection(ws, req, debugTarget) {
        const wsUrlParams = (0, url_1.parseWsUrl)(req.url);
        ws.isAlive = true;
        ws.on('pong', () => {
            ws.isAlive = true;
        });
        const { clientRole } = wsUrlParams;
        if ([enum_1.ClientRole.JSRuntime, enum_1.ClientRole.VueDevtools].includes(clientRole)) {
            return (0, vue_devtools_1.onVueClientConnection)(ws, wsUrlParams);
        }
        if ([enum_1.ClientRole.VanillaJSRuntime].includes(clientRole)) {
            return (0, vanilla_js_devtools_1.onVanillaJSClientConnection)(ws, wsUrlParams);
        }
        if ([enum_1.ClientRole.ReactJSRuntime, enum_1.ClientRole.ReactDevtools].includes(clientRole)) {
            return (0, react_devtools_1.onReactClientConnection)(ws, wsUrlParams);
        }
        if (clientRole === enum_1.ClientRole.HMRClient) {
            return (0, hmr_1.onHMRClientConnection)(ws, wsUrlParams);
        }
        if (clientRole === enum_1.ClientRole.HMRServer) {
            return (0, hmr_1.onHMRServerConnection)(ws, wsUrlParams);
        }
        if (clientRole === enum_1.ClientRole.Devtools) {
            return (0, chrome_devtools_1.onDevtoolsConnection)(ws, wsUrlParams);
        }
        if ([enum_1.ClientRole.IOS, enum_1.ClientRole.Android].includes(clientRole)) {
            return (0, chrome_devtools_1.onAppConnection)(ws, wsUrlParams, debugTarget);
        }
    }
    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;
