@hippy/debug-server-next
Version:
Debug server for hippy.
191 lines (190 loc) • 9.37 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.onAppConnection = exports.onDevtoolsConnection = void 0;
const report_1 = require("@debug-server-next/utils/report");
const enum_1 = require("@debug-server-next/@types/enum");
const db_1 = require("@debug-server-next/db");
const client_1 = require("@debug-server-next/client");
const pub_sub_manager_1 = require("@debug-server-next/controller/pub-sub-manager");
const log_1 = require("@debug-server-next/utils/log");
const pub_sub_channel_1 = require("@debug-server-next/utils/pub-sub-channel");
const reload_adapter_1 = require("@debug-server-next/utils/reload-adapter");
const history_event_protocol_1 = require("@debug-server-next/utils/history-event-protocol");
const debug_targets_1 = require("@debug-server-next/controller/debug-targets");
const debug_target_1 = require("@debug-server-next/utils/debug-target");
const log = new log_1.Logger('chrome-devtools', enum_1.WinstonColor.Cyan);
/**
* pipe devtools ws message to redis pub/sub
*/
const onDevtoolsConnection = (ws, wsUrlParams) => {
const { Subscriber, Publisher } = (0, db_1.getDBOperator)();
const { extensionName, clientId, hash } = wsUrlParams;
const downwardChannelId = (0, pub_sub_channel_1.createDownwardChannel)(clientId, extensionName);
const upwardChannelId = (0, pub_sub_channel_1.createUpwardChannel)(clientId, extensionName);
const internalChannelId = (0, pub_sub_channel_1.createInternalChannel)(clientId, '');
report_1.report.event({
name: enum_1.ReportEvent.ConnectFrontend,
ext1: clientId,
});
const connectTime = Date.now();
log.info('devtools connected');
log.verbose('subscribe channel: %s, publish channel: %s', downwardChannelId, upwardChannelId);
const downwardSubscriber = new Subscriber(downwardChannelId);
// internal channel used to listen message between nodes, such as when app ws closed, notify devtools ws close
const internalSubscriber = new Subscriber(internalChannelId);
const internalPublisher = new Publisher(internalChannelId);
const publisher = new Publisher(upwardChannelId);
const downwardHandler = (msg) => {
ws.send(msg);
try {
const msgStr = msg;
const msgObj = JSON.parse(msgStr);
const { ts: start } = msgObj;
if (start) {
report_1.report.time(Date.now() - start, {
name: enum_1.ReportEvent.PubSub,
ext1: `${Math.ceil(msgStr.length / 1024)}KB`,
ext2: msgObj.method,
});
}
// evaluate response
if (msgObj.id === reload_adapter_1.runtimeEvaluateCommandId) {
try {
const { result } = msgObj.result;
const isVue = result.value.split('+')[0] !== 'undefined';
const isReact = result.value.split('+')[1] !== 'undefined';
log.info('receive evaluate response, welcome to use Hippy-%s', isVue ? 'Vue' : isReact ? 'React' : 'Other');
// get debug target
const { clientId } = wsUrlParams;
try {
(0, debug_target_1.updateDebugTarget)(clientId, {
driverType: isVue ? enum_1.AppDriverType.Vue : isReact ? enum_1.AppDriverType.React : enum_1.AppDriverType.Other,
}).then((debugTarget) => {
log.info('debug target updated for %s', debugTarget.title);
// 数据上报
report_1.report.event({
name: enum_1.ReportEvent.ConnectFrontend2,
ext1: debugTarget.title,
ext2: debugTarget.driverType,
ext3: debugTarget.platform,
bundleId: debugTarget.bundleId,
hostVersion: debugTarget.hostVersion,
sdkVersion: debugTarget.sdkVersion,
});
});
}
catch (e) {
log.error('update DebugTarget contextName fail', e.stack);
}
}
catch (e) {
log.error('evaluate response parse fail', e.stack);
}
}
}
catch (e) {
log.error('%s channel message are invalid JSON, %s', downwardChannelId, msg);
}
};
const internalHandler = (msg) => {
if (msg === enum_1.InternalChannelEvent.AppWSClose) {
log.verbose('close devtools ws connection');
ws.close(enum_1.WSCode.ClosePage, 'the target page is closed');
internalSubscriber.disconnect();
internalPublisher.disconnect();
}
};
downwardSubscriber.subscribe(downwardHandler);
internalSubscriber.subscribe(internalHandler);
internalPublisher.publish(enum_1.InternalChannelEvent.DevtoolsConnected);
// for iOS, must invoke Debugger.disable before devtools frontend connected, otherwise couldn't
// receive Debugger.scriptParsed event.
// for Android, both way is okay
reload_adapter_1.resumeCommands.map(publisher.publish.bind(publisher));
ws.on('message', (msg) => {
publisher.publish(msg.toString());
});
ws.on('close', async (code, reason) => {
log.info('devtools closed. code: %s, reason: %s', code, reason);
await Promise.all(reload_adapter_1.resumeCommands.map(publisher.publish.bind(publisher)));
// wait for publish finished
publisher.disconnect();
downwardSubscriber.disconnect();
internalSubscriber.disconnect();
internalPublisher.disconnect();
report_1.report.event({
name: enum_1.ReportEvent.DisConnectFrontend,
ext1: String(code),
ext2: String(Date.now() - connectTime),
});
});
ws.on('error', (e) => log.error('devtools ws client error: %j', e));
// send evaluate command to get driver type
debug_targets_1.DebugTargetManager.findDebugTarget(clientId, hash).then((debugTarget) => {
if (debugTarget.driverType === undefined) {
setTimeout(() => {
log.info('send evaluate command to get driver type...');
(0, reload_adapter_1.publishEvaluateCommand)(clientId, "typeof __VUE_ROOT_INSTANCES__ + '+' + typeof __REACT_DEVTOOLS_GLOBAL_HOOK__");
}, 2000);
}
else {
log.info('reopen frontend, debugTarget.driverType is %s', debugTarget.driverType);
}
});
};
exports.onDevtoolsConnection = onDevtoolsConnection;
const onAppConnection = async (ws, wsUrlParams, debugTarget) => {
const { clientId, clientRole, contextName } = wsUrlParams;
log.info('app connected. %s', contextName);
const platform = {
[enum_1.ClientRole.Android]: enum_1.DevicePlatform.Android,
[enum_1.ClientRole.IOS]: enum_1.DevicePlatform.IOS,
}[clientRole];
const useWS = client_1.appClientManager.shouldUseAppClientType(platform, "WSAppClient" /* AppClientType.WS */);
if (!useWS)
return log.verbose('current env is %s, ignore ws connection', global.debugAppArgv.env);
(0, pub_sub_manager_1.subscribeCommand)(debugTarget, ws);
report_1.report.event({
name: enum_1.ReportEvent.RemoteDebug,
ext1: contextName,
ext2: platform,
});
// when reload, iOS will create a new JSContext, so the debug protocol should resend
if (debugTarget.platform === enum_1.DevicePlatform.IOS)
(0, reload_adapter_1.publishReloadCommand)(debugTarget);
const connectTime = Date.now();
ws.on('close', (code, reason) => {
log.info('app closed. code: %j, reason: %s, contextName: %s', code, reason, contextName);
(0, history_event_protocol_1.clearHistoryProtocol)(clientId);
// when reload page, keep frontend open to debug lifecycle of created
const closeDevtools = code === enum_1.WSCode.ClosePage;
(0, pub_sub_manager_1.cleanDebugTarget)(clientId, closeDevtools);
report_1.report.event({
name: enum_1.ReportEvent.DisRemoteDebug,
ext1: String(code),
ext2: String(Date.now() - connectTime),
});
});
ws.on('error', (e) => log.error('WSAppClient error %j', e));
// log.info(`debug target is: ${JSON.stringify(debugTarget)}`);
};
exports.onAppConnection = onAppConnection;