"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.onHMRServerConnection = exports.onHMRClientConnection = void 0;
const tslib_1 = require("tslib");
const fs_1 = tslib_1.__importDefault(require("fs"));
const path_1 = tslib_1.__importDefault(require("path"));
const util_1 = require("util");
const enum_1 = require("@debug-server-next/@types/enum");
const url_1 = require("@debug-server-next/utils/url");
const log_1 = require("@debug-server-next/utils/log");
const pub_sub_channel_1 = require("@debug-server-next/utils/pub-sub-channel");
const db_1 = require("@debug-server-next/db");
const config_1 = require("@debug-server-next/config");
const buffer_1 = require("@debug-server-next/utils/buffer");
const cos_1 = require("@debug-server-next/utils/cos");
const aegis_1 = require("@debug-server-next/utils/aegis");
const log = new log_1.Logger('hmr-controller', enum_1.WinstonColor.Blue);
const hmrCloseEvent = 'HMR_SERVER_CLOSED';
/**
 * Pub/Sub HMR msg to client side
 */
const onHMRClientConnection = async (ws, wsUrlParams) => {
    const { hash } = wsUrlParams;
    log.verbose('HMR client connected, hash: %s', hash);
    const { Subscriber, DB } = db_1.getDBOperator();
    const bundle = await new DB(config_1.config.redis.bundleTable).get(hash);
    if (!bundle) {
        const reason = 'Hippy dev server not started, not support HMR!';
        ws.close(enum_1.WSCode.InvalidRequestParams, reason);
        return log.warn(reason);
    }
    aegis_1.aegis.reportEvent({
        name: enum_1.ReportEvent.RemoteHMR,
        ext1: hash,
        ext2: enum_1.HMRReportExt2.Client,
    });
    const subscriber = new Subscriber(pub_sub_channel_1.createHMRChannel(hash));
    const hmrHandler = (msg) => {
        if (msg === hmrCloseEvent) {
            subscriber.unsubscribe(hmrHandler);
            subscriber.disconnect();
            const reason = 'Hippy dev server closed, stop HMR!';
            ws.close(enum_1.WSCode.HMRServerClosed, reason);
            log.warn(reason);
        }
        else {
            log.verbose('receive HMR msg from redis: %s', msg);
            const msgObj = JSON.parse(msg.toString());
            if (msgObj.performance) {
                msgObj.performance.serverToApp = Date.now();
            }
            ws.send(JSON.stringify(msgObj));
        }
    };
    subscriber.subscribe(hmrHandler);
    ws.on('close', (code, reason) => {
        log.warn('HMR client ws closed, code: %s, reason: %s', code, reason);
        subscriber.unsubscribe(hmrHandler);
        subscriber.disconnect();
    });
    ws.on('error', (e) => log.error('HMR client ws error: %s', e.stack || e));
};
exports.onHMRClientConnection = onHMRClientConnection;
/**
 * Pub/Sub HMR msg to server side
 */
const onHMRServerConnection = (ws, wsUrlParams) => {
    const { hash } = wsUrlParams;
    log.verbose('HMR server connected, hash: %s', hash);
    const { Publisher, DB } = db_1.getDBOperator();
    const model = new DB(config_1.config.redis.bundleTable);
    model.upsert(hash, { hash });
    const publisher = new Publisher(pub_sub_channel_1.createHMRChannel(hash));
    aegis_1.aegis.reportEvent({
        name: enum_1.ReportEvent.RemoteHMR,
        ext1: hash,
        ext2: enum_1.HMRReportExt2.Server,
    });
    // store all synced dist file, and clean when HMR server disconnect
    const distFiles = new Set();
    ws.on('message', async (msg) => {
        var _a;
        try {
            const serverReceive = Date.now();
            const { emitList, publicPath, ...emitJSON } = buffer_1.decodeHMRData(msg);
            if (emitJSON.performance) {
                const { hadSyncBundleResource } = emitJSON;
                const { pcToServer } = emitJSON.performance;
                const hmrSize = `${Math.ceil(msg.length / 1024)}KB`;
                const reportData = {
                    name: enum_1.ReportEvent.HMRPCToServer,
                    duration: serverReceive - pcToServer,
                    ext1: hmrSize,
                    ext2: '',
                };
                if ('hadSyncBundleResource' in emitJSON)
                    reportData.ext2 = hadSyncBundleResource ? enum_1.HMRSyncType.Patch : enum_1.HMRSyncType.FirstTime;
                aegis_1.aegis.reportTime(reportData);
            }
            const folder = url_1.getBaseFolderOfPublicPath(publicPath);
            emitList.forEach((file) => {
                distFiles.add(path_1.default.join(folder, file.name));
            });
            if (emitList === null || emitList === void 0 ? void 0 : emitList.length) {
                await saveHMRFiles(folder, emitList);
            }
            const msgStr = JSON.stringify(emitJSON);
            log.verbose('receive HMR msg from PC: %s', msgStr);
            setTimeout(() => {
                var _a;
                if ((_a = emitJSON.messages) === null || _a === void 0 ? void 0 : _a.length)
                    publisher.publish(msgStr);
            }, 1000);
        }
        catch (e) {
            log.error('decodeHMRData failed: ', ((_a = e) === null || _a === void 0 ? void 0 : _a.stack) || e);
        }
    });
    ws.on('close', async (code, reason) => {
        log.warn('HMR server ws closed, code: %s, reason: %s', code, reason);
        publisher.publish(hmrCloseEvent);
        process.nextTick(() => {
            publisher.disconnect();
        });
        model.delete(hash);
        cleanHMRFiles(Array.from(distFiles));
    });
    ws.on('error', (e) => log.error('HMR server ws error: %s', e.stack || e));
};
exports.onHMRServerConnection = onHMRServerConnection;
async function saveHMRFiles(folder, emitList) {
    return Promise.all(emitList.map(async ({ name, content }) => {
        const saveFn = {
            [enum_1.StaticFileStorage.COS]: saveHMRFileToCOS,
            [enum_1.StaticFileStorage.Local]: saveHMRFileToLocal,
        }[config_1.config.staticFileStorage];
        return saveFn(folder, name, content);
    }));
}
async function saveHMRFileToLocal(folder, name, content) {
    const fullFname = path_1.default.resolve(config_1.config.hmrStaticPath, folder, name);
    const cacheFolder = path_1.default.dirname(fullFname);
    await fs_1.default.promises.mkdir(cacheFolder, { recursive: true });
    return fs_1.default.promises.writeFile(fullFname, content);
}
async function saveHMRFileToCOS(folder, name, content) {
    const key = path_1.default.join(folder, name);
    return cos_1.cosUpload(key, content);
}
async function cleanHMRFiles(files) {
    try {
        if (files.length === 0)
            return;
        log.warn('clean cached static resources! \n %j', files);
        if (config_1.config.staticFileStorage === enum_1.StaticFileStorage.COS) {
            await cos_1.deleteObjects(files);
        }
        else {
            await Promise.all(files.map((file) => {
                const fullFname = path_1.default.join(config_1.config.hmrStaticPath, file);
                return util_1.promisify(fs_1.default.rm)(fullFname, { force: true, recursive: true });
            }));
        }
    }
    catch (e) {
        log.error('clean cached static resources failed! %j', e.stack || e);
    }
}
