@hippy/debug-server-next
Version:
Debug server for hippy.
219 lines (218 loc) • 8.55 kB
JavaScript
;
/*
* 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 report_1 = require("@debug-server-next/utils/report");
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.info('HMR client connected');
const { Subscriber, DB } = (0, db_1.getDBOperator)();
const bundle = await new DB(config_1.config.redis.bundleTable).get(hash);
if (!bundle) {
const reason = 'hippy-dev not started, not support HMR!';
ws.close(enum_1.WSCode.InvalidRequestParams, reason);
return log.warn(reason);
}
report_1.report.event({
name: enum_1.ReportEvent.RemoteHMR,
ext2: enum_1.HMRReportExt2.Client,
});
const subscriber = new Subscriber((0, pub_sub_channel_1.createHMRChannel)(hash));
const hmrHandler = (msg) => {
if (msg === hmrCloseEvent) {
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.info('HMR client closed. code: %s, reason: %s', code, reason);
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.info('HMR server connected');
const { Publisher, DB } = (0, db_1.getDBOperator)();
const model = new DB(config_1.config.redis.bundleTable);
model.upsert(hash, { hash });
const publisher = new Publisher((0, pub_sub_channel_1.createHMRChannel)(hash));
report_1.report.event({
name: enum_1.ReportEvent.RemoteHMR,
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) => {
try {
const serverReceive = Date.now();
const { emitList, publicPath, ...emitJSON } = (0, 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,
ext1: hmrSize,
ext2: '',
};
if ('hadSyncBundleResource' in emitJSON) {
reportData.ext2 = hadSyncBundleResource
? enum_1.HMRSyncType.Patch
: enum_1.HMRSyncType.FirstTime;
report_1.report.event({
name: enum_1.ReportEvent.HMR,
ext1: hmrSize,
ext2: reportData.ext2,
});
}
report_1.report.time(serverReceive - pcToServer, reportData);
}
const folder = (0, 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: ', (e === null || e === void 0 ? void 0 : e.stack) || e);
}
});
ws.on('close', async (code, reason) => {
log.info('HMR server closed. code: %s, reason: %s', code, reason);
await publisher.publish(hmrCloseEvent);
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) {
if (sensitiveFiles.some((f) => name.indexOf(f) !== -1))
return;
const key = path_1.default.join(folder, name);
return (0, cos_1.cosUpload)(key, content);
}
async function cleanHMRFiles(files) {
try {
if (files.length === 0)
return;
log.verbose('clean cached static resources! \n %j', files);
if (config_1.config.staticFileStorage === enum_1.StaticFileStorage.COS) {
await (0, cos_1.deleteObjects)(files);
}
else {
await Promise.all(files.map((file) => {
const fullFname = path_1.default.join(config_1.config.hmrStaticPath, file);
return (0, 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);
}
}
const sensitiveFiles = [
'.DS_Store',
'.bashrc',
'.kshrc',
'.zshrc',
'.bash_history',
'.bash_profile',
'.bash_logout',
'known_hosts',
'.mysql_history',
'authorized_keys',
'.svn/entries',
'.svn/format',
'.git/config',
'.git/logs/HEAD',
'error.log',
'access.log',
'a.out',
'user.cfg',
'global.cfg',
'config.ini',
'.htaccess',
'README.md',
'private.key',
'.idea/workspace.xml',
'web.config',
'.env',
'package.json',
];