diff --git a/.gitignore b/.gitignore index 6258de3..3e5a1de 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,9 @@ node_modules -/lib/ demo coverage/ .idea/ .DS_Store .vscode/ -lib -dest +lib/ +dest/ frontend/weex diff --git a/.npmignore b/.npmignore index 5bc75c6..326c0f8 100644 --- a/.npmignore +++ b/.npmignore @@ -8,8 +8,6 @@ frontend/devtools .git .vscode -src - -dest +src/ diff --git a/README.md b/README.md index 364df12..d037d6c 100644 --- a/README.md +++ b/README.md @@ -24,8 +24,7 @@ We advise you to use weex-toolkit which will call weex-devtool. ## usage ``` bash -$ weex xbind debugx weex-debugger -$ weex debugx [options] [vue_file|bundles_dir] +$ weex debug [options] [vue_file|bundles_dir] ``` | Options | Description | | :--- | :--- | @@ -42,7 +41,7 @@ $ weex debugx [options] [vue_file|bundles_dir] #### start debugger ``` -$weex debugx +$ weex debug ``` his command will start debug server and launch a chrome opening `DeviceList` page. this page will display a QR code, you can use [Playground](https://weex.apache.org/cn/playground.html) scan it for starting debug or integrate [Weex devtools](#Integrate devtool) into your application. @@ -67,6 +66,7 @@ This command will compile each of the files in `your/vue/path` and deploy them o ## How to access devtools in native - Android: pls refer to [Weex Devtool Android](https://github.com/weexteam/weex_devtools_android/blob/master/README.md) + - Android: pls refer to [Weex Devtool Android](https://github.com/weexteam/weex_devtools_android/blob/master/README.md) - IOS: pls refer to [Weex Devtool IOS](https://github.com/weexteam/weex-devtool-iOS/blob/master/README-zh.md) + - IOS: pls refer to [Weex Devtool IOS](https://github.com/weexteam/weex-devtool-iOS/blob/master/README-zh.md) + \ No newline at end of file diff --git a/bin/weex-devtool.js b/bin/weex-devtool.js index 50c07e4..ab2c4d4 100755 --- a/bin/weex-devtool.js +++ b/bin/weex-devtool.js @@ -8,7 +8,6 @@ const exit = require('exit'); const path = require('path'); const detect = require('detect-port'); const del = require('del'); -const mlink = require('mlink'); const os = require('os'); const packageInfo = require('../package.json'); const debugRun = require('../lib/util/debug_run'); @@ -18,16 +17,20 @@ const hook = require('../lib/util/hook'); const env = require('../lib/util/env'); const hosts = require('../lib/util/hosts'); const headless = require('../lib/server/headless'); +const { + LOGLEVELS, + logger +} =require('../lib/util/logger') program .option('-v, --version', 'display version') .option('-h, --help', 'display help') .option('-H --host [host]', 'set the host ip of debugger server') -.option('-V, --verbose', 'display logs of debugger server') .option('-p, --port [port]', 'set debugger server port', '8088') .option('-m, --manual', 'manual mode,this mode will not auto open chrome') .option('--min', 'minimize the jsbundle') -.option('--debug', 'set log level to debug mode') +.option('--telemetry', 'upload usage data to help us improve the toolkit') +.option('--verbose', 'display all logs of debugger server') .option('--loglevel [loglevel]', 'set log level silent|error|warn|info|log|debug', 'error') .option('--remotedebugport [remotedebugport]', 'set the remote debug port', config.remoteDebugPort); @@ -44,19 +47,31 @@ if (program.help === undefined) { program.outputHelp(); exit(0); } + // Fix tj's commander bug overwrite --version if (program.version === undefined) { - console.log(packageInfo.version); + logger.log(packageInfo.version); exit(0); } if (program.host && !hosts.isValidLocalHost(program.host)) { - console.log('[' + program.host + '] is not your local address!'); + logger.error('[' + program.host + '] is not your local address!'); exit(0); } +if (program.telemetry) { + hook.allowTarck() +} + +if (program.loglevel) { + program.loglevel = program.loglevel.toLowercase && program.loglevel.toLowercase() + if(LOGLEVELS.indexOf(program.loglevel) > -1) { + logger.setLevel(program.loglevel) + } +} + if (program.verbose) { - config.logLevel = 'debug'; + logger.setLevel('verbose') } if (program.remotedebugport) { @@ -76,10 +91,8 @@ env.getVersionOf('node', (v) => { // Formate config config.ip = program.host || ip.address(); -config.verbose = program.verbose; config.manual = program.manual; config.min = program.min; -config.logLevel = program.loglevel; process.on('uncaughtException', (err) => { try { @@ -96,11 +109,11 @@ process.on('uncaughtException', (err) => { }, 30000); killTimer.unref(); } catch (e) { - console.log('Error Message: ', e.stack); + logger.error('Error Message: ', e.stack); } }); -process.on('unhandledRejection', (reason, p) => { +process.on('unhandledRejection', (reason, p) => {logger const params = Object.assign({ stack: reason, os: os.platform(), @@ -108,7 +121,7 @@ process.on('unhandledRejection', (reason, p) => { npm: config.npmVersion }, config.weexVersion); hook.record('/weex_tool.weex_debugger.app_crash', params); - console.log('Unhandled Rejection at:', p, 'reason:', reason); + logger.error(reason); // application specific logging, throwing an error, or other logic here }); @@ -132,7 +145,6 @@ detect(program.port).then( (open) => { debugRun(__filename, config); } else { - mlink.Logger.setLogLevel(mlink.Logger.LogLevel[config.logLevel.toUpperCase()]); // Clear files on bundleDir try { del.sync(path.join(__dirname, '../frontend/', config.bundleDir, '/*'), { diff --git a/frontend/assets/bug.png b/frontend/assets/bug.png new file mode 100644 index 0000000..ae774da Binary files /dev/null and b/frontend/assets/bug.png differ diff --git a/frontend/assets/debugger.css b/frontend/assets/debugger.css index db72fc0..8cb5a39 100644 --- a/frontend/assets/debugger.css +++ b/frontend/assets/debugger.css @@ -454,4 +454,22 @@ legend { #prophet-menu .menu-btn:hover { background: #ffffff; color: #000000; -} \ No newline at end of file +} +.bug_report{ + display: block; + cursor: pointer; + width: 50px; + height: 50px; + border-radius: 50%; + position: fixed; + right: 20px; + bottom: 20px; + z-index: 999; + opacity: .2; + transition: all .2s ease; + background: url('./bug.png') center center no-repeat #000; +} + +.bug_report:hover{ + opacity: 1; +} diff --git a/frontend/debug.html b/frontend/debug.html index 9972f89..35f91e7 100644 --- a/frontend/debug.html +++ b/frontend/debug.html @@ -161,6 +161,7 @@
+ diff --git a/frontend/devtool_fake.html b/frontend/devtool_fake.html new file mode 100644 index 0000000..e69de29 diff --git a/frontend/lib/runtime/Runtime.js b/frontend/lib/runtime/Runtime.js index 2576897..c216b37 100644 --- a/frontend/lib/runtime/Runtime.js +++ b/frontend/lib/runtime/Runtime.js @@ -1,277 +1,277 @@ -self.$$frameworkFlag={}; +importScripts('/lib/runtime/EventEmitter.js'); +self.$$frameworkFlag = {}; var channelId; -var injectedGlobals = [ - 'Promise', - // W3C - 'window', - 'weex', - 'service', - 'Rax', - 'services', - 'global', - 'screen', - 'document', - 'navigator', - 'location', - 'fetch', - 'Headers', - 'Response', - 'Request', - 'URL', - 'URLSearchParams', - 'setTimeout', - 'clearTimeout', - 'setInterval', - 'clearInterval', - 'requestAnimationFrame', - 'cancelAnimationFrame', - 'alert', - // ModuleJS - 'define', - 'require', - // Weex - 'bootstrap', - 'register', - 'render', - '__d', - '__r', - '__DEV__', - '__weex_define__', - '__weex_require__', - '__weex_viewmodel__', - '__weex_document__', - '__weex_bootstrap__', - '__weex_options__', - '__weex_data__', - '__weex_downgrade__', - '__weex_require_module__', - 'Vue' +var shouldReturnResult = false; +var requestId; +var payloads = [] +var injectedGlobals = ['Promise', + // W3C + 'window', 'weex', 'service', 'Rax', 'services', 'global', 'screen', 'document', 'navigator', 'location', 'fetch', 'Headers', 'Response', 'Request', 'URL', 'URLSearchParams', 'setTimeout', 'clearTimeout', 'setInterval', 'clearInterval', 'requestAnimationFrame', 'cancelAnimationFrame', 'alert', + // ModuleJS + 'define', 'require', + // Weex + 'bootstrap', 'register', 'render', '__d', '__r', '__DEV__', '__weex_define__', '__weex_require__', '__weex_viewmodel__', '__weex_document__', '__weex_bootstrap__', '__weex_options__', '__weex_data__', '__weex_downgrade__', '__weex_require_module__', 'Vue' ]; -let cachedSetTimeout=this.setTimeout; -Object.defineProperty(this,'setTimeout',{ - get:function(){ - return cachedSetTimeout; - }, - set:function(){ - - } +var cachedSetTimeout = this.setTimeout; +Object.defineProperty(this, 'setTimeout', { + get: function () { + return cachedSetTimeout; + }, + set: function () {} }); + function EventEmitter() { - this._handlers = {}; + this._handlers = {}; } -importScripts('/lib/runtime/EventEmitter.js'); -function createWeexBundleEntry(sourceUrl){ - var code=''; - if(self.$$frameworkFlag[sourceUrl]||self.$$frameworkFlag['@']){ - code+=(self.$$frameworkFlag[sourceUrl]||self.$$frameworkFlag['@'])+'\n'; - } - code+='__weex_bundle_entry__('; - injectedGlobals.forEach(function(g,i){ - if(false&&g==='navigator'){ - code+='typeof '+g+'==="undefined"||'+g+'===self.'+g+'?undefined:'+g; - } - else{ - code+='typeof '+g+'==="undefined"?undefined:'+g; - } - if(i", "license": "GPL-3.0", "main": "index.js", @@ -38,15 +38,16 @@ "detect-port": "^1.2.1", "exit": "^0.1.2", "fs-extra": "^5.0.0", + "invok": "^1.0.2", "ip": "^1.1.3", + "ipromise": "^1.0.2", "javascript-obfuscator": "^0.8.6", - "koa": "^2.4.1", - "koa-bodyparser": "^4.2.0", - "koa-router": "^5.4.0", - "koa-serve-static": "^1.0.0", - "koa-websocket": "^2.1.0", + "koa": "^1.2.0", + "koa-bodyparser": "^2.3.0", + "koa-router": "~5.2.3", + "koa-serve-static": "0.0.1", + "koa-websocket": "^2.0.0", "mkdirp": "^0.5.1", - "mlink": "^0.1.6", "node-watch": "^0.3.5", "opn": "^4.0.2", "parse-hosts": "^1.0.1", @@ -54,7 +55,7 @@ "request": "^2.83.0", "simrun": "^0.1.0", "webpack": "^1.13.1", - "weex-builder": "^0.3.8", + "weex-builder": "^0.3.12", "weex-components": "^0.2.0", "weex-loader": "^0.4.2-beta", "weex-transformer": "^0.3.1", diff --git a/src/lib/config.js b/src/lib/config.js index 753bbb1..7624a0a 100644 --- a/src/lib/config.js +++ b/src/lib/config.js @@ -4,7 +4,7 @@ */ module.exports = { bundleDir: 'weex', - remoteDebugPort: '9999', + remoteDebugPort: '9222', enableHeadless: true, getConnectUrl (channelId) { return this.connectUrl && this.connectUrl.replace('{channelId}', channelId); diff --git a/src/lib/devtool.js b/src/lib/devtool.js index b6f311f..7f40935 100644 --- a/src/lib/devtool.js +++ b/src/lib/devtool.js @@ -3,12 +3,18 @@ const path = require('path'); const chalk = require('chalk'); const config = require('./config'); const debugServer = require('../server'); -const launcher = require('../util/launcher'); -const headless = require('../server/headless'); const hook = require('../util/hook'); -const Router = require('mlink').Router; const boxen = require('boxen'); + const detect = require('detect-port'); +const launcher = require('../util/launcher'); +const headless = require('../server/headless'); +const mlink = require('../mlink/midware'); +const Router = mlink.Router; + +const { + logger +} = require('../util/logger'); // 1 - start with debugserver only // 3 - start with debugserver with headless server @@ -19,7 +25,7 @@ let startPath = 1; const builder = require('weex-builder'); function resolveBundleUrl (bundlePath, ip, port) { - return 'http://' + ip + ':' + port + path.join('/' + config.bundleDir, bundlePath.replace(/\.(we|vue)$/, '.js')).replace(/\\/g, '/'); + return 'http://' + ip + ':' + port + path.join('/' + config.bundleDir, bundlePath.replace(/\.(we|vue)$/, '.js')); } function isUrl (str) { @@ -60,7 +66,7 @@ exports.startServer = function (ip, port) { message += '- ' + chalk.bold('Websocket Address For Native: ') + ' ws://' + ip + ':' + port + '/debugProxy/native\n'; message += '- ' + chalk.bold('Debug Server: ') + ' http://' + ip + ':' + port + '\n'; debugServer.start(port, function () { - console.log(boxen(message, { + logger.log(boxen(message, { padding: 1, borderColor: 'green', margin: 1 @@ -72,17 +78,17 @@ exports.startServer = function (ip, port) { exports.launch = function (ip, port) { const debuggerURL = 'http://' + (ip || 'localhost') + ':' + port + '/'; - console.log('Launching Dev Tools...'); + logger.info('Launching Dev Tools...'); if (config.enableHeadless) { startPath += 2; // Check whether the port is occupied detect(config.remoteDebugPort).then(function (open) { if (+config.remoteDebugPort !== open) { headless.closeHeadless(); - console.log(`Starting inspector on port ${open}, because ${config.remoteDebugPort} is already in use`); + logger.info(`Starting inspector on port ${open}, because ${config.remoteDebugPort} is already in use`); } else { - console.log(`Starting inspector on port ${open}`); + logger.info(`Starting inspector on port ${open}`); } config.remoteDebugPort = open; headless.launchHeadless(`${config.ip}:${config.port}`, open); @@ -119,14 +125,14 @@ exports.start = function (target, config, cb) { devtool: 'inline-source-map' }, (err, output, json) => { if (err) { - console.error(err); + logger.error(err); } else { - console.log('Build completed!\nChild'); - console.log(output.toString()); + logger.info('Build completed!\nChild'); + logger.log(output.toString()); if (!shouldReloadDebugger) { shouldReloadDebugger = true; - const bundles = json.assets.map((o) => path.relative(path.join(__dirname, '../../frontend/weex'), o.name)); + const bundles = json.assets.map((o) => o.name); const bundleUrls = this.resolveBundlesAndEntry(config.entry, bundles, config.ip, config.port); config.bundleUrls = bundleUrls; this.startServerAndLaunch(config.ip, config.port, config.manual, cb); diff --git a/src/mlink/handlers/debugger.js b/src/mlink/handlers/debugger.js index 576e7f3..c122adb 100644 --- a/src/mlink/handlers/debugger.js +++ b/src/mlink/handlers/debugger.js @@ -1,6 +1,7 @@ -const Router = require('mlink').Router; +const mlink = require('../midware'); +const Router = mlink.Router; const debuggerRouter = Router.get('debugger'); -const DeviceManager = require('../lib/device_manager'); +const DeviceManager = require('../managers/device_manager'); const hook = require('../../util/hook'); debuggerRouter.registerHandler(function (message, next) { diff --git a/src/mlink/handlers/device_manager.js b/src/mlink/handlers/device_manager.js index 6af26d5..26c0b2b 100644 --- a/src/mlink/handlers/device_manager.js +++ b/src/mlink/handlers/device_manager.js @@ -1,5 +1,6 @@ -const Router = require('mlink').Router; -const DeviceManager = require('../lib/device_manager'); +const mlink = require('../midware'); +const Router = mlink.Router; +const DeviceManager = require('../managers/device_manager'); const config = require('../../lib/config'); const debuggerRouter = Router.get('debugger'); let registerDeviceChannelId; diff --git a/src/mlink/handlers/entry.js b/src/mlink/handlers/entry.js index 5182fe0..6b83658 100644 --- a/src/mlink/handlers/entry.js +++ b/src/mlink/handlers/entry.js @@ -1,5 +1,6 @@ -const Router = require('mlink').Router; -const simulatorManager = require('../lib/simulator_manager'); +const mlink = require('../midware'); +const Router = mlink.Router; +const simulatorManager = require('../managers/simulator_manager'); const config = require('../../lib/config'); const hook = require('../../util/hook'); const debuggerRouter = Router.get('debugger'); diff --git a/src/mlink/handlers/inspector.js b/src/mlink/handlers/inspector.js index 12a7c9f..57e3632 100644 --- a/src/mlink/handlers/inspector.js +++ b/src/mlink/handlers/inspector.js @@ -1,17 +1,16 @@ -const mlink = require('mlink'); +const mlink = require('../midware'); const debuggerRouter = mlink.Router.get('debugger'); -const Logger = mlink.Logger; -const DeviceManager = require('../lib/device_manager'); +const DeviceManager = require('../managers/device_manager'); const redirectMessage = /^(Page.(enable|disable|reload)|Debugger|Target|Worker|Runtime\.runIfWaitingForDebugger)/; const ignoredMessage = /^(ServiceWorker)/; +const { + logger +} = require('../../util/logger'); + debuggerRouter.registerHandler(function (message) { const device = DeviceManager.getDevice(message.channelId); if (device) { if (redirectMessage.test(message.payload.method)) { - if (message.payload.method === 'Debugger.setBreakpointByUrl') { - // message.discard(); - // return; - } if (message.payload && message.payload.method === 'Page.reload') { message.payload.ignoreCache = true; } @@ -28,7 +27,7 @@ debuggerRouter.registerHandler(function (message) { } } else { - Logger.error('loss message from "proxy.inspector" device not found in channelId [' + message.channelId + ']'); + logger.error('loss message from "proxy.inspector" device not found in channelId [' + message.channelId + ']'); message.to('proxy.native'); } }).at('proxy.inspector'); diff --git a/src/mlink/handlers/native.js b/src/mlink/handlers/native.js index ad837a7..5efef24 100644 --- a/src/mlink/handlers/native.js +++ b/src/mlink/handlers/native.js @@ -1,5 +1,6 @@ -const Router = require('mlink').Router; -const DeviceManager = require('../lib/device_manager'); +const mlink = require('../midware'); +const Router = mlink.Router; +const DeviceManager = require('../managers/device_manager'); const bundleWrapper = require('../../util/bundle_wrapper'); const MemoryFile = require('../../lib/memory_file'); const debuggerRouter = Router.get('debugger'); diff --git a/src/mlink/handlers/runtime_proxy.js b/src/mlink/handlers/runtime_proxy.js index e87bcb8..cb0e298 100644 --- a/src/mlink/handlers/runtime_proxy.js +++ b/src/mlink/handlers/runtime_proxy.js @@ -1,4 +1,5 @@ -const Router = require('mlink').Router; +const mlink = require('../midware'); +const Router = mlink.Router; const debuggerRouter = Router.get('debugger'); debuggerRouter.registerHandler(function (message) { if (message.payload.method === 'Debugger.scriptParsed' || message.payload.result && message.payload.result.frameTree) { diff --git a/src/mlink/handlers/runtime_worker.js b/src/mlink/handlers/runtime_worker.js index 6771fe7..6e7c106 100644 --- a/src/mlink/handlers/runtime_worker.js +++ b/src/mlink/handlers/runtime_worker.js @@ -1,9 +1,10 @@ -const Router = require('mlink').Router; -const Logger = require('mlink').Logger; +const mlink = require('../midware'); +const Router = mlink.Router; +const Logger = mlink.Logger; +const Hub = mlink.Hub; const debuggerRouter = Router.get('debugger'); -const DeviceManager = require('../lib/device_manager'); -const RuntimeManager = require('../lib/runtime_manager'); -const Hub = require('mlink').Hub; +const DeviceManager = require('../managers/device_manager'); +const RuntimeManager = require('../managers/runtime_manager'); const runtimeProxyHub = Hub.get('runtime.proxy'); debuggerRouter.registerHandler(function (message) { diff --git a/src/mlink/link.js b/src/mlink/link.js index 65dcaca..e5d2636 100644 --- a/src/mlink/link.js +++ b/src/mlink/link.js @@ -1,4 +1,4 @@ -const mlink = require('mlink'); +const mlink = require('./midware/index'); const Router = mlink.Router; const Hub = mlink.Hub; const debuggerRouter = new Router('debugger'); @@ -10,11 +10,16 @@ const entryHub = new Hub('page.entry'); const runtimeProxyHub = new Hub('runtime.proxy'); const syncHub = new Hub('sync'); -debuggerRouter.link(nativeProxyHub); -debuggerRouter.link(debuggerHub); -debuggerRouter.link(inspectorHub); -debuggerRouter.link(entryHub); -debuggerRouter.link(syncHub); -debuggerRouter.link(runtimeWorkerHub); -debuggerRouter.link(runtimeProxyHub); -mlink.load(__dirname); +const init = () => { + debuggerRouter.link(nativeProxyHub); + debuggerRouter.link(debuggerHub); + debuggerRouter.link(inspectorHub); + debuggerRouter.link(entryHub); + debuggerRouter.link(syncHub); + debuggerRouter.link(runtimeWorkerHub); + debuggerRouter.link(runtimeProxyHub); + mlink.load(__dirname); +}; +module.exports = { + init +}; diff --git a/src/mlink/lib/device_manager.js b/src/mlink/managers/device_manager.js similarity index 100% rename from src/mlink/lib/device_manager.js rename to src/mlink/managers/device_manager.js diff --git a/src/mlink/lib/runtime_manager.js b/src/mlink/managers/runtime_manager.js similarity index 84% rename from src/mlink/lib/runtime_manager.js rename to src/mlink/managers/runtime_manager.js index 51687da..f38adcb 100644 --- a/src/mlink/lib/runtime_manager.js +++ b/src/mlink/managers/runtime_manager.js @@ -1,9 +1,12 @@ -const mlink = require('mlink'); +const mlink = require('../midware'); const WebsocketTerminal = mlink.Terminal.WebsocketTerminal; const url = require('url'); const WebSocket = require('ws'); const request = require('../../util/request'); const config = require('../../lib/config'); +const { + logger +} = require('../../util/logger'); class RuntimeManager { constructor () { @@ -27,6 +30,7 @@ class RuntimeManager { if (found) { if (found.webSocketDebuggerUrl) { + logger.verbose(`Have found the webSocketDebuggerUrl: ${found.webSocketDebuggerUrl}`); const ws = new WebSocket(found.webSocketDebuggerUrl); const terminal = new WebsocketTerminal(ws, channelId); const _runtimeTerminalMaps = this.runtimeTerminalMap[channelId]; @@ -39,10 +43,12 @@ class RuntimeManager { resolve(terminal); } else { + logger.verbose(`Not found the webSocketDebuggerUrl from the ${found}`); reject('TOAST_DO_NOT_OPEN_CHROME_DEVTOOL'); } } else { + logger.verbose(`Not found the remote debug json`); reject('TOAST_CAN_NOT_FIND_RUNTIME'); } }).catch((e) => { @@ -57,7 +63,7 @@ class RuntimeManager { popTerminal.websocket.close(); } else { - console.error('try to remove a non-exist runtime'); + logger.error('Try to remove a non-exist runtime'); } } has (channelId) { diff --git a/src/mlink/lib/simulator_manager.js b/src/mlink/managers/simulator_manager.js similarity index 100% rename from src/mlink/lib/simulator_manager.js rename to src/mlink/managers/simulator_manager.js diff --git a/src/mlink/midware/index.js b/src/mlink/midware/index.js new file mode 100644 index 0000000..e5764dc --- /dev/null +++ b/src/mlink/midware/index.js @@ -0,0 +1,14 @@ +/** + * Created by exolution on 17/2/22. + */ + +module.exports = { + 'Router': require('./src/router'), + 'Enum': require('./src/enum'), + 'Filter': require('./src/filter'), + 'Hub': require('./src/hub'), + 'Terminal': require('./src/terminal'), + 'Handler': require('./src/handler'), + 'Channel': require('./src/channel'), + 'load': require('./util/loader') +}; diff --git a/src/mlink/midware/src/channel.js b/src/mlink/midware/src/channel.js new file mode 100644 index 0000000..58dfe22 --- /dev/null +++ b/src/mlink/midware/src/channel.js @@ -0,0 +1,93 @@ +const uuid = require('uuid'); +const CHANNEL_MODE = require('./enum').CHANNEL_MODE; +const util = require('../util'); +class Channel { + constructor (mode, enableMulticast) { + this.hubMap = {}; + this.mode = mode; + this.id = uuid(); + this.cache = []; + this.enableMulticast = enableMulticast; + } + + getTerminal (hubId) { + return this.hubMap[hubId]; + } + + findAll () { + return Object.keys(this.hubMap).map(hid => ({ hubId: hid, terminalId: this.hubMap[hid] })); + } + + findOthers (hubId, terminalId, toHubId) { + if (hubId === toHubId) { + throw new Error('hubId must be different from toHubId'); + } + else if (!this.has(hubId, terminalId) && !toHubId) { + throw new Error('terminal [' + hubId + '.' + (terminalId || '*') + '] is not in this chanel !'); + } + else { + const keys = Object.keys(this.hubMap); + if (keys.length > 2 && !this.enableMulticast && !toHubId) { + // channel中有超过三个hub的时候则默认不支持多播 + return []; + } + return keys.filter(hid => hid !== hubId && (!toHubId || util.matchHubId(toHubId, hid))) + .map(hid => ({ hubId: hid, terminalId: this.hubMap[hid] })); + } + } + + pushCache (message) { + this.cache.push(message); + } + + has (hubId, terminalId) { + return this.hubMap[hubId] && (!terminalId || this.hubMap[hubId] === terminalId); + } + + getCache (hubId) { + const hit = []; + const notHit = []; + this.cache.forEach(c => { + if (c._to.length > 0) { + if (c._from.hubId !== hubId && (!c._to[0].hubId || util.matchHubId(c._to[0].hubId, hubId))) { + hit.push(c); + } + else { + notHit.push(c); + } + } + }); + if (hit.length > 0) { + this.cache = notHit; + } + return hit; + } + + join (hubId, terminalId, forced) { + if (this.hubMap[hubId]) { + if (forced) { + this.hubMap[hubId] = terminalId; + } + else { + throw new Error('Join failed! There already exist one terminal in the same hub'); + } + } + else { + const hubIds = Object.keys(this.hubMap); + if (this.mode === CHANNEL_MODE.P2P && hubIds.length >= 2) { + throw new Error('A channel can just link two hub in p2p mode'); + } + this.hubMap[hubId] = terminalId; + } + } + + leave (hubId, terminalId) { + if (this.has(hubId, terminalId)) { + this.cache = this.cache.filter(c => c.hubId !== hubId || terminalId && c.terminalId !== terminalId); + delete this.hubMap[hubId]; + } + } + +} + +module.exports = Channel; diff --git a/src/mlink/midware/src/emitter.js b/src/mlink/midware/src/emitter.js new file mode 100644 index 0000000..4ef4bca --- /dev/null +++ b/src/mlink/midware/src/emitter.js @@ -0,0 +1,104 @@ +/** + * Created by exolution on 17/2/27. + */ +const util = require('../util'); +class Emitter { + constructor () { + this._eventHandler = {}; + } + + on (event, namespace, handler) { + if (arguments.length === 2) { + handler = namespace; + namespace = ''; + } + if (!this._eventHandler[event]) { + this._eventHandler[event] = {}; + } + + const target = util.objectLocate(this._eventHandler[event], namespace); + + if (target.__handlers__) { + target.__handlers__.push(handler); + } + else { + target.__handlers__ = [handler]; + } + } + + off (event, namespace) { + if (this._eventHandler[event]) { + if (!namespace) { + this._eventHandler[event] = {}; + } + else { + util.clearObjectAt(this._eventHandler[event], namespace); + } + } + } + + emit (event, namespace, data) { + if (arguments.length === 2) { + data = namespace; + namespace = ''; + } + if (this._eventHandler[event]) { + const context = { + namespace, + event, + path: namespace + }; + return this._emit(this._eventHandler[event], namespace, context, data); + } + else { + return false; + } + } + + _emit (prevTarget, namespace, context, data) { + context.path = namespace; + const target = util.objectGet(prevTarget, namespace); + if (target && target.__handlers__) { + target.__handlers__.forEach(h => h.call(context, data)); + } + else { + if (namespace) { + const ns = namespace.substr(0, namespace.lastIndexOf('.')); + return this._emit(prevTarget, ns, context, data); + } + else { + return false; + } + } + } + + broadcast (event, namespace, data) { + if (arguments.length === 2) { + data = namespace; + namespace = ''; + } + if (this._eventHandler[event]) { + const target = util.objectGet(this._eventHandler[event], namespace); + if (target) { + const context = { event, namespace, path: namespace }; + this._broadcast(target, context, data); + return true; + } + else return false; + } + else { + return false; + } + } + + _broadcast (target, context, data) { + target.__handlers__ && target.__handlers__.forEach(h => h.call(context, data)); + const keys = Object.keys(target).filter(k => k !== '__handlers__'); + + keys.forEach((k) => { + const ctx = { event: context.event, namespace: context.namespace, path: context.namespace + '.' + k }; + this._broadcast(target[k], ctx, data); + }); + } +} +module.exports = Emitter; diff --git a/src/mlink/midware/src/enum.js b/src/mlink/midware/src/enum.js new file mode 100644 index 0000000..06be669 --- /dev/null +++ b/src/mlink/midware/src/enum.js @@ -0,0 +1,8 @@ +/** + * Created by exolution on 17/2/24. + */ +exports.CHANNEL_MODE = { + P2P: 0, + P2P_STRICT: 1, + N2N: 2 +}; diff --git a/src/mlink/midware/src/filter.js b/src/mlink/midware/src/filter.js new file mode 100644 index 0000000..cec6f8d --- /dev/null +++ b/src/mlink/midware/src/filter.js @@ -0,0 +1,49 @@ +/** + * Created by exolution on 17/2/23. + */ +const invok = require('invok'); +const Promise = require('ipromise'); +class Filter { + constructor (handler, condition) { + this.condition = condition; + this.handler = handler; + this.isGeneratorFunction = Object.prototype.toString.call(handler) === '[object GeneratorFunction]'; + if (typeof this.condition === 'string') { + this.condition = new Function('message', 'with(message) {return ' + this.condition + ';}'); + } + } + + when (condition) { + if (condition === 'string') { + this.condition = new Function('message', 'with(message) {return ' + condition + ';}'); + } + } + + run (message, next) { + if (!this.condition || this.condition(message)) { + if (this.isGeneratorFunction) { + return invok(this.handler, this, [message, next]); + } + else { + const p = new Promise(); + this.handler(message, function () { + next().linkTo(p); + }); + return p; + } + } + else { + return next(); + } + } + +} +function resolveFilterChain (message, filterChain, currentIndex = 0) { + return filterChain[currentIndex].run(message, function () { + return currentIndex + 1 < filterChain.length ? + resolveFilterChain(message, filterChain, currentIndex + 1) : + Promise.resolve(); + }); +} +Filter.resolveFilterChain = resolveFilterChain; +module.exports = Filter; diff --git a/src/mlink/midware/src/handler.js b/src/mlink/midware/src/handler.js new file mode 100644 index 0000000..c309fc7 --- /dev/null +++ b/src/mlink/midware/src/handler.js @@ -0,0 +1,74 @@ +/** + * Created by exolution on 17/2/24. + */ +const Promise = require('ipromise'); +class Handler { + constructor (handler, router) { + this.handler = handler; + this.router = router; + } + + at (fromString) { + this.fromString = fromString; + return this; + } + + when (condition) { + if (typeof condition === 'string') { + this.condition = new Function('message', 'with(message) {return ' + condition + ';}'); + } + else if (typeof condition === 'function') { + this.condition = condition; + } + return this; + } + + test (message) { + return message.match(this.fromString) && (!this.condition || this.condition(message)); + } + + run (message) { + if (this.test(message)) { + return this.handler.call(this.router, message); + } + } +} +function _run (handlerList, message, i = 0) { + const promise = new Promise(); + const handler = handlerList[i]; + if (handler) { + const ret = handler.run(message); + if (ret && typeof ret.then === 'function') { + if (i + 1 < handlerList.length) { + ret.then(function (data) { + if (data === false) { + promise.resolve(false); + } + else { + promise.resolve(_run(handlerList, data || message, i + 1)); + } + }); + } + else { + return ret; + } + } + else if (ret === false) { + promise.resolve(ret); + } + else { + if (i + 1 < handlerList.length) { + promise.resolve(_run(handlerList, ret || message, i + 1)); + } + else { + promise.resolve(ret); + } + } + } + else { + promise.resolve(); + } + return promise; +} +Handler.run = _run; +module.exports = Handler; diff --git a/src/mlink/midware/src/hub.js b/src/mlink/midware/src/hub.js new file mode 100644 index 0000000..672abf9 --- /dev/null +++ b/src/mlink/midware/src/hub.js @@ -0,0 +1,116 @@ +const Filter = require('./filter'); +const Router = require('./router'); +const Message = require('./message'); +const { logger } = require('../../../util/logger'); +const _hubInstances = {}; +class Hub { + constructor (id) { + const self = this; + if (_hubInstances[id]) { + return _hubInstances[id]; + } + _hubInstances[id] = this; + this.id = id; + this.terminalMap = {}; + this.filterChain = []; + this._pushToRouter = new Filter(function * (message) { + const responseMessage = yield self.router._fetchMessage(message); + self.response = responseMessage; + return responseMessage; + }); + } + + static get (id) { + return _hubInstances[id] || new Hub(id); + } + + static check () { + Object.keys(_hubInstances).forEach((id) => { + if (!_hubInstances[id].router) { + console.warn('mlink warning: Hub[' + id + '] not join in any router.make sure your id is correct'); + } + }); + } + + join (terminal, forced) { + if (!this.router) { + throw new Error('A Hub must be linked with a Router before join terminals'); + } + if (!this.terminalMap[terminal.id]) { + terminal.hub = this.id; + this.terminalMap[terminal.id] = terminal; + this._setupTerminal(terminal, forced); + } + else { + throw new Error('can not add the same port'); + } + } + + setChannel (terminalId, channelId) { + if (this.terminalMap[terminalId]) { + this.terminalMap[terminalId].channelId = channelId; + } + else { + throw new Error('can not find terminal[' + terminalId + ']'); + } + } + + _setupTerminal (terminal, forced) { + terminal.on('destroy', () => { + if (this.terminalMap[terminal.id]) { + delete this.terminalMap[terminal.id]; + this.router._event({ + type: Router.Event.TERMINAL_LEAVED, + terminalId: terminal.id, + hubId: this.id, + channelId: terminal.channelId + }); + terminal = null; + } + else { + logger.warn('try to delete a non-exist terminal'); + } + }); + terminal.on('message', (message) => { + this.send(new Message(message, this.id, terminal.id, terminal.channelId)); + }); + this.router._event({ + type: Router.Event.TERMINAL_JOINED, + terminalId: terminal.id, + hubId: this.id, + channelId: terminal.channelId, + forced: forced + }); + } + + broadcast (message) { + for (const id in this.terminalMap) { + if (this.terminalMap.hasOwnProperty(id)) { + this.terminalMap[id].read(message.payload); + } + } + message.destroy(); + } + + pushToTerminal (terminalId, message) { + if (this.terminalMap[terminalId]) { + this.terminalMap[terminalId].read(message.payload); + } + else { + throw new Error('Terminal [' + terminalId + '] not found! @' + this.id); + } + } + + send (message) { + if (!this.router) { + throw new Error('this hub not linked with a router,message send failed!'); + } + Filter.resolveFilterChain(message, this.filterChain.concat(this._pushToRouter)).then(() => { + }).catch(e => logger.error(e)); + } + + filter (filter, condition) { + this.filterChain.push(new Filter(filter, condition)); + } +} +module.exports = Hub; diff --git a/src/mlink/midware/src/message.js b/src/mlink/midware/src/message.js new file mode 100644 index 0000000..8eff9b9 --- /dev/null +++ b/src/mlink/midware/src/message.js @@ -0,0 +1,110 @@ +/** + * Created by exolution on 17/3/22. + */ +let _uuid = 0; +let _messageBuffer = []; +const util = require('../util'); +const { logger } = require('../../../util/logger'); +class Message { + constructor (payload, hubId, terminalId, channelId) { + this._from = { + hubId, + terminalId + }; + this.channelId = channelId; + this._to = []; + this.destination = []; + this.id = _uuid++; + _messageBuffer.push(this); + this._payload = [payload]; + this._createTime = new Date(); + } + + get payload () { + return this._payload[0]; + } + + set payload (value) { + this._payload.unshift(value); + } + + reply () { + this.to(this._from.hubId, this._from.terminalId); + } + + match (fromString) { + return !fromString || util.matchHubId(fromString, this._from.hubId + '.' + this._from.terminalId); + } + + to (hubId, terminalId) { + this._to.push({ + hubId, + terminalId + }); + } + + discard () { + this._discard = true; + this.destroy(); + } + + destroy () { + if (this._discard) { + logger.verbose(`${this.id}#[${this._from.hubId}@${this._from.terminalId.split('-')[0]}-${this.channelId ? this.channelId.split('-')[0] : '*'}] discard`, this._payload.length > 1 ? this._payload : this.payload); + } + if (this.destination.length > 0) { + this.destination.forEach((dest) => { + const isReply = this._from.terminalId === dest.terminalId; + logger.verbose( + `${this.id}#[${this._from.hubId}@${this._from.terminalId.split('-')[0]}-${this.channelId ? this.channelId.split('-')[0] : '*'}]` + + `-->${isReply ? 'reply-->' : ''}[${dest.hubId}@${dest.terminalId && dest.terminalId.split('-')[0]}]:`, this._payload.length > 1 ? this._payload : this.payload + ); + }); + } + else { + logger.verbose(`${this.id}#[${this._from.hubId}@${this._from.terminalId.split('-')[0]}-${this.channelId ? this.channelId.split('-')[0] : '*'}] ->lost`, this._payload.length > 1 ? this._payload : this.payload); + } + _messageBuffer = _messageBuffer.filter(m => m.id !== this.id); + } + + isAlive () { + return !this._discard; + } + + route (resolver) { + this.routed = true; + if (this._to.length === 0 && this.channelId) { // todo 如果没有明确的to但是有channelId 该当如何 + this._to.push({}); + } + this.destination = []; + this._to.forEach((to) => { + if (!to.terminalId && resolver) { + this.destination.push.apply(this.destination, resolver(this._from, to, this.channelId)); + } + else { + this.destination.push(to); + } + }); + } + + selectOne (to) { + let found = false; + for (let i = 0; i < this._to.length; i++) { + if (this._to[i].hubId === to.hubId && this._to[i].terminalId === to.terminalId) { + this._to.splice(i, 1); + found = true; + break; + } + } + if (found) { + const selected = new Message(this.payload, this._from.hubId, this._from.terminalId, this.channelId); + selected._to = [to]; + return selected; + } + else { + throw new Error('message select not found'); + } + } + +} +module.exports = Message; diff --git a/src/mlink/midware/src/router.js b/src/mlink/midware/src/router.js new file mode 100644 index 0000000..58fcc62 --- /dev/null +++ b/src/mlink/midware/src/router.js @@ -0,0 +1,180 @@ +/** + * Created by exolution on 17/2/24. + */ +const Channel = require('./channel'); +const Handler = require('./handler'); +const Emitter = require('./emitter'); +const Message = require('./message'); +const { logger } = require('../../../util/logger'); +const _routerInstances = {}; +class Router extends Emitter { + constructor (id) { + super(); + if (_routerInstances[id]) { + return _routerInstances[id]; + } + + this.hubs = {}; + this.id = id; + this.channelMap = {}; + this.handlerList = []; + _routerInstances[id] = this; + } + + static check () { + Object.keys(_routerInstances).forEach((id) => { + if (Object.keys(_routerInstances[id].hubs).length === 0) { + console.warn('[Mlink Warning] Router[' + id + '] do not has any hub.make sure your id is correct'); + } + }); + } + + static get (id) { + return _routerInstances[id] || new Router(id); + } + + static dump () { + Object.keys(_routerInstances).forEach(id => { + const router = _routerInstances[id]; + for (const hid in router.hubs) { + console.log('Router[' + id + ']' + '<---->' + 'Hub[' + hid + ']'); + } + }); + } + + link (hub) { + this.hubs[hub.id] = hub; + hub.router = this; + } + + reply (message, payload) { + this.pushMessage(message._from.hubId, message._from.terminalId, payload); + } + + newChannel (mode) { + const channel = new Channel(mode); + this.channelMap[channel.id] = channel; + return channel.id; + } + + // fixme + pushMessageByChannelId (hubId, channelId, payload) { + const message = new Message(payload, 'unknown', 'unknown', channelId); + message.to(hubId); + message.route(); + this._fetchMessage(message); + } + + pushMessage (hubId, terminalId, payload) { + if (arguments.length === 2) { + payload = terminalId; + if (typeof hubId === 'string') { + [hubId, terminalId] = hubId.split('@'); + } + else if (typeof hubId === 'object') { + hubId = hubId.hubId; + terminalId = hubId.terminalId; + } + else { + throw new Error('the first argument of pushMessage must be a string or object'); + } + } + const message = new Message(payload, 'unknown', 'unknown'); + message.to(hubId, terminalId); + message.route(); + this._pushMessage(message); + } + + _pushMessage (message) { + message.destination.forEach(dest => { + if (this.hubs[dest.hubId]) { + if (dest.terminalId) { + this.hubs[dest.hubId].pushToTerminal(dest.terminalId, message); + } + else { + this.hubs[dest.hubId].broadcast(message); + } + } + else { + throw new Error('Hub [' + dest.hubId + '] not found!'); + } + }); + message.destroy(); + } + _dispatchMessage (message) { + if (message.isAlive()) { + message.route((from, to, channelId) => { + if (channelId) { + const channel = this.channelMap[channelId]; + if (channel) { + const others = channel.findOthers(from.hubId, from.terminalId, to.hubId); + if (others.length === 0) channel.pushCache(message.selectOne(to)); + return others; + } + else { + throw new Error('invalid channelId:' + channelId); + } + } + else { + // throw new Error('invalid message no channelId'); + } + }); + this._pushMessage(message); + } + } + + _fetchMessage (message) { + // Handler.run(this.handlerList) + this.emit(Router.Event.MESSAGE_RECEIVED, message._from.hubId + '.' + message._from.terminalId, message); + return Handler.run(this.handlerList, message).then((data) => { // todo 如何保证消息的顺序 + this._dispatchMessage(message); + }) + .catch(e => { + console.error(e); + }); + } + + registerHandler (handler) { + const currentHandler = new Handler(handler, this); + this.handlerList.push(currentHandler); + return currentHandler; + } + + _event (signal) { + switch (signal.type) { + case Router.Event.TERMINAL_JOINED: + logger.verbose(`[terminal]${signal.hubId}-${signal.terminalId} joined`); + if (signal.channelId) { + const channel = this.channelMap[signal.channelId]; + if (channel) { + logger.verbose(`[channel]${signal.hubId}-${signal.terminalId} join channel ${signal.channelId}`); + channel.join(signal.hubId, signal.terminalId, signal.forced); + } + else { + throw new Error('the channel [' + signal.channelId + '] of terminal [' + signal.terminalId + '] is not found!'); + } + const cacheMessages = channel.getCache(signal.hubId, signal.terminalId); + cacheMessages.forEach(m => this._dispatchMessage(m)); + } + this.emit(Router.Event.TERMINAL_JOINED, signal.hubId + '.' + signal.terminalId, signal); + break; + case Router.Event.TERMINAL_LEAVED: + logger.verbose(`[terminal]${signal.hubId}-${signal.terminalId} leaved`); + if (signal.channelId) { + const channel = this.channelMap[signal.channelId]; + if (channel) { + logger.verbose(`[channel]${signal.hubId}-${signal.terminalId} leave channel ${signal.channelId}`); + channel.leave(signal.hubId, signal.terminalId); + } + } + this.emit(Router.Event.TERMINAL_LEAVED, signal.hubId + '.' + signal.terminalId, signal); + break; + } + } +} +Router.Event = { + TERMINAL_JOINED: 0, + TERMINAL_LEAVED: 1, + MESSAGE_RECEIVED: 2 +}; +module.exports = Router; diff --git a/src/mlink/midware/src/terminal.js b/src/mlink/midware/src/terminal.js new file mode 100644 index 0000000..75ed60a --- /dev/null +++ b/src/mlink/midware/src/terminal.js @@ -0,0 +1,50 @@ +/** + * Created by exolution on 17/2/24. + */ +const EventEmitter = require('events').EventEmitter; +const uuid = require('uuid'); +const Promise = require('ipromise'); +class SyncTerminal extends EventEmitter { + constructor () { + super(); + this.id = uuid(); + } + + send (data) { + this.promise = new Promise(); + this.emit('message', data); + return this.promise; + } + + read (message) { + this.emit('destroy'); + this.promise.resolve(message); + } +} +class WebsocketTerminal extends EventEmitter { + constructor (websocket, channelId) { + super(); + this.channelId = channelId; + this.id = uuid(); + this.websocket = websocket; + websocket.on('connect', function () { + }); + websocket.on('message', (message) => { + this.emit('message', JSON.parse(message)); + }); + websocket.on('close', () => { + this.emit('destroy'); + }); + websocket.on('error', (err) => { + console.error(err); + }); + } + + read (message) { + if (this.websocket.readyState === 1) { + this.emit('read', message); + this.websocket.send(JSON.stringify(message)); + } + } +} +module.exports = { WebsocketTerminal, SyncTerminal }; diff --git a/src/mlink/midware/util/index.js b/src/mlink/midware/util/index.js new file mode 100644 index 0000000..1443d84 --- /dev/null +++ b/src/mlink/midware/util/index.js @@ -0,0 +1,45 @@ +exports.objectGet = function (object, prop, defaultValue) { + const props = prop.split('.'); + let p = props.shift(); + let cur = object; + while (p) { + cur = cur[p]; + if (cur === undefined || cur === null) break; + p = props.shift(); + } + return cur || defaultValue; +}; +exports.objectLocate = function objectLocate (object, prop) { + if (!prop) return object; + const props = prop.split('.'); + let p = props.shift(); + let cur = object; + while (p) { + if (cur[p] === undefined) { + cur[p] = {}; + } + cur = cur[p]; + p = props.shift(); + } + return cur; +}; +exports.clearObjectAt = function (object, prop) { + const props = prop.split('.'); + let p = props.shift(); + let cur = object; + while (p) { + if (props.length === 0) { + cur[p] = {}; + } + else { + cur = cur[p]; + } + + if (!cur) break; + p = props.shift(); + } + return cur; +}; +exports.matchHubId = function (base, target) { + return base === target || target.indexOf(base) === 0 && target.charAt(base.length) === '.'; +}; diff --git a/src/mlink/midware/util/invoker.js b/src/mlink/midware/util/invoker.js new file mode 100644 index 0000000..c405091 --- /dev/null +++ b/src/mlink/midware/util/invoker.js @@ -0,0 +1,62 @@ +/** + * Created by exolution on 17/2/23. + */ +const invok = require('invok'); +const Promise = require('ipromise'); +class Filter { + constructor (handler) { + this.handler = handler; + this.isGeneratorFunction = Object.prototype.toString.call(handler) === '[object GeneratorFunction]'; + } + run (message, next) { + if (this.isGeneratorFunction) { + return invok(this.handler, this, [message, next]); + } + else { + const p = new Promise(); + this.handler(message, function () { + next().linkTo(p); + }); + return p; + } + } + +} +function resolveFilterChain (message, filterChain, currentIndex = 0) { + return filterChain[currentIndex].run(message, function () { + return currentIndex + 1 < filterChain.length ? + resolveFilterChain(message, filterChain, currentIndex + 1) : + Promise.resolve(); + }); +} +Filter.resolveFilterChain = resolveFilterChain; +const filterChain = []; +filterChain.push(new Filter(function (message, next) { + message.from = 1; + message.to = 2; + next(); +})); +filterChain.push(new Filter(function * (message, next) { + message.data = 3; + yield next; +})); +function delay (time) { + const p = new Promise(); + setTimeout(() => p.resolve(), time); + return p; +} +filterChain.push(new Filter(function * (message, next) { + yield delay(3000); + message.data = 5; + yield next; +})); +filterChain.push(new Filter(function (message, next) { + setTimeout(function () { + next(); + }, 1000); +})); +Filter.resolveFilterChain({}, filterChain).then(function (data) { + console.log(2, data); +}, function (err) { + console.log(err); +}); diff --git a/src/mlink/midware/util/loader.js b/src/mlink/midware/util/loader.js new file mode 100644 index 0000000..dbba41f --- /dev/null +++ b/src/mlink/midware/util/loader.js @@ -0,0 +1,16 @@ +/** + * Created by exolution on 17/2/27. + */ +const fs = require('fs'); +const path = require('path'); +const Router = require('../src/router'); +const Hub = require('../src/hub'); +module.exports = function loader (dir, config = {}) { + require(path.join(dir, config.linkPath || 'link.js')); + const handlerPath = path.join(dir, config.handlerPath || 'handlers'); + fs.readdirSync(handlerPath).forEach((file) => { + require(path.join(handlerPath, file)); + }); + Router.check(); + Hub.check(); +}; diff --git a/src/server/headless.js b/src/server/headless.js index 720f119..c561701 100644 --- a/src/server/headless.js +++ b/src/server/headless.js @@ -1,15 +1,20 @@ const puppeteer = require('puppeteer'); let page; let browser = null; +const { + logger +} = require('../util/logger'); exports.launchHeadless = async (host, remotePort) => { browser = await puppeteer.launch({ args: [`--remote-debugging-port=${remotePort}`, `--disable-gpu`] }); + logger.verbose(`Headless has been launched`); if (!page) { page = await browser.newPage(); } await page.goto(`http://${host}/runtime.html`); + logger.verbose(`Headless page goto http://${host}/runtime.html`); }; exports.closeHeadless = async () => { if (page) { @@ -19,4 +24,5 @@ exports.closeHeadless = async () => { await browser.close(); } browser = null; + logger.verbose(`Cloased headless`); }; diff --git a/src/server/index.js b/src/server/index.js index 72b854e..4c867cc 100644 --- a/src/server/index.js +++ b/src/server/index.js @@ -1,4 +1,4 @@ -const Path = require('path'); +const path = require('path'); const Koa = require('koa'); const serve = require('koa-serve-static'); const Websockify = require('koa-websocket'); @@ -6,8 +6,13 @@ const bodyParser = require('koa-bodyparser'); const WsRouter = require('./router/Websocket'); const HttpRouter = require('./router/Http'); const app = Websockify(new Koa()); -const rootPath = Path.join(__dirname, '../../frontend/'); -require('../mlink/link'); +const rootPath = path.join(__dirname, '../../frontend/'); +const { init } = require('../mlink/link'); + +const { + logger +} = require('../util/logger'); + /* =================================== WebSocket Router @@ -15,14 +20,15 @@ require('../mlink/link'); */ exports.start = function (port, cb) { + init(); app.use(bodyParser()); app.ws.use(WsRouter.routes()); app.on('error', function (err, ctx) { if (err.status === 404) { - console.error(err); + logger.verbose(err); } else { - console.error(err); + logger.verbose(err); } }); /* diff --git a/src/server/router/Http.js b/src/server/router/Http.js index e7583f6..ceaf7ff 100644 --- a/src/server/router/Http.js +++ b/src/server/router/Http.js @@ -1,8 +1,7 @@ const Router = require('koa-router'); const MemoryFile = require('../../lib/memory_file'); -const mlink = require('mlink'); -const Logger = mlink.Logger; -const DeviceManager = require('../../mlink/lib/device_manager'); +const mlink = require('../../mlink/midware'); +const DeviceManager = require('../../mlink/managers/device_manager'); const URL = require('url'); const config = require('../../lib/config'); const bundleWrapper = require('../../util/bundle_wrapper'); @@ -10,6 +9,10 @@ const protocols = { 'http:': require('http'), 'https:': require('https') }; +const { + logger +} = require('../../util/logger'); + const httpRouter = new Router(); function getRemote (url) { @@ -41,6 +44,7 @@ const rSourceMapDetector = /\.map$/; httpRouter.get('/source/*', function * (next) { const path = this.params[0]; if (rSourceMapDetector.test(path)) { + logger.verbose(`Fetch sourcemap ${path}`); const content = yield getRemote('http://' + path); if (!content) { this.response.status = 404; @@ -60,8 +64,10 @@ httpRouter.get('/source/*', function * (next) { this.response.status = 200; this.type = 'text/javascript'; if (file.url && config.proxy) { + logger.verbose(`Fetch jsbundle ${file.url}`); const content = yield getRemote(file.url).catch(function (e) { - Logger.error(e); + // If file not found or got other http error. + logger.verbose(e); }); if (!content) { this.response.body = file.getContent(); @@ -100,7 +106,7 @@ httpRouter.post('/syncApi', function * () { } else { this.response.status = 500; - this.response.body = JSON.stringify({ error: 'device not found!' }); + // this.response.body = JSON.stringify({ error: 'device not found!' }); } }); module.exports = httpRouter; diff --git a/src/server/router/Websocket.js b/src/server/router/Websocket.js index 810fd49..cca7a92 100644 --- a/src/server/router/Websocket.js +++ b/src/server/router/Websocket.js @@ -1,5 +1,5 @@ const Router = require('koa-router'); -const mlink = require('mlink'); +const mlink = require('../../mlink/midware'); const WebsocketTerminal = mlink.Terminal.WebsocketTerminal; const inspectorHub = mlink.Hub.get('proxy.inspector'); @@ -8,12 +8,18 @@ const proxyDebuggerHub = mlink.Hub.get('page.debugger'); const runtimeWorkerHub = mlink.Hub.get('runtime.worker'); const entryHub = mlink.Hub.get('page.entry'); const wsRouter = Router(); +const { + logger +} = require('../../util/logger'); + wsRouter.all('/page/entry', function * (next) { + logger.verbose(`Joined entry hub joined`); entryHub.join(new WebsocketTerminal(this.websocket)); }); wsRouter.all('/debugProxy/inspector/:channelId', function * (next) { const terminal = new WebsocketTerminal(this.websocket); terminal.channelId = this.params.channelId; + logger.verbose(`Joined entry hub joined, channelId -> ${terminal.channelId}`); inspectorHub.join(terminal, true); yield next; }); @@ -21,14 +27,15 @@ wsRouter.all('/debugProxy/inspector/:channelId', function * (next) { wsRouter.all('/debugProxy/debugger/:channelId', function * (next) { const terminal = new WebsocketTerminal(this.websocket); terminal.channelId = this.params.channelId; + logger.verbose(`Joined entry hub joined, channelId -> ${terminal.channelId}`); proxyDebuggerHub.join(terminal, true); - yield next; }); wsRouter.all('/debugProxy/runtime/:channelId', function * (next) { const terminal = new WebsocketTerminal(this.websocket); terminal.channelId = this.params.channelId; + logger.verbose(`Joined entry hub joined, channelId -> ${terminal.channelId}`); runtimeWorkerHub.join(terminal); yield next; }); @@ -36,6 +43,7 @@ wsRouter.all('/debugProxy/runtime/:channelId', function * (next) { wsRouter.all('/debugProxy/native/:channelId', function * (next) { const terminal = new WebsocketTerminal(this.websocket); terminal.channelId = this.params.channelId; + logger.verbose(`Joined entry hub joined, channelId -> ${terminal.channelId}`); proxyNativeHub.join(terminal, true); yield next; }); diff --git a/src/util/hook.js b/src/util/hook.js index 2aa41c3..a07d4c1 100644 --- a/src/util/hook.js +++ b/src/util/hook.js @@ -1,8 +1,12 @@ const request = require('request'); const dns = require('dns'); +let shouldBeTelemetry = false; exports.record = (logkey, gokey) => { + if (!shouldBeTelemetry) { + return; + } let url = `http://gm.mmstat.com${logkey}?`; for (const i in gokey) { if (gokey.hasOwnProperty(i)) { @@ -16,3 +20,7 @@ exports.record = (logkey, gokey) => { } }); }; + +exports.allowTarck = () => { + shouldBeTelemetry = true; +}; diff --git a/src/util/logger.js b/src/util/logger.js new file mode 100644 index 0000000..1938d82 --- /dev/null +++ b/src/util/logger.js @@ -0,0 +1,118 @@ +const chalk = require('chalk'); +const EventEmitter = require('events').EventEmitter; + +const events = new EventEmitter(); +const LOGLEVELS = ['verbose', 'log', 'info', 'warn', 'error', 'success']; + +// global var +const LOGLEVEL = { + VERBOSE: 'verbose', + LOG: 'log', + WARN: 'warn', + INFO: 'info', + ERROR: 'error', + SUCCESS: 'success' +}; + +const SEVERITY = { + verbose: 1000, + log: 2000, + warn: 3000, + info: 3000, + error: 5000, + success: 10000 +}; + +const LOGCOLOR = { + verbose: 'grey', + log: 'white', + warn: 'yellow', + info: 'white', + error: 'red', + success: 'green' +}; + +let DEFAULT_LOGLEVEL = LOGLEVEL.LOG; + +const formatError = (error, isVerbose) => { + let message = ''; + if (error instanceof Error) { + if (isVerbose) { + message = error.stack; + } + else { + message = error; + } + } + else { + // Plain text error message + message = error; + } + if (typeof message === 'string' && message.toUpperCase().indexOf('ERROR:') !== 0) { + // Needed for backward compatibility with external tools + message = 'Error: ' + message; + } + return message; +}; + +const log = (loglevel) => { + return (message) => { + const isVerbose = DEFAULT_LOGLEVEL === LOGLEVEL.VERBOSE; + if (!SEVERITY[DEFAULT_LOGLEVEL] || SEVERITY[DEFAULT_LOGLEVEL] > SEVERITY[loglevel]) { + // return instance to allow to chain calls + return; + } + if (message instanceof Error || isVerbose && loglevel === 'error') { + message = formatError(message, isVerbose); + } + const color = LOGCOLOR[loglevel]; + let time; + let prefix; + let sep; + if (SEVERITY[loglevel] >= SEVERITY[LOGLEVEL.INFO]) { + time = new Date(); + prefix = chalk.gray(`${time.getHours()}:${time.getMinutes()}:${time.getSeconds()}`); + sep = ':'; + console.log(chalk.grey(prefix), sep, chalk[color](message)); + } + else { + console.log(chalk[color](message)); + } + }; +}; + +const subscribe = (event) => { + if (!(event instanceof EventEmitter)) { + throw new Error('Subscribe method only accepts an EventEmitter instance as argument'); + } + event + .on('verbose', log('verbose')) + .on('log', log('log')) + .on('info', log('info')) + .on('warn', log('warn')) + .on('error', log('error')) + .on('success', log('success')); + + return event; +}; + +const setLevel = (logLevel) => { + DEFAULT_LOGLEVEL = logLevel; +}; + +const logger = { + setLevel, + subscribe, + verbose: log('verbose'), + log: log('log'), + info: log('info'), + warn: log('warn'), + error: log('error'), + success: log('success') +}; + +module.exports = { + logger, + events, + LOGLEVELS +};