From b6aafe59c7eb12e37fc03243b32e9911dcbae2a3 Mon Sep 17 00:00:00 2001 From: Huan LI Date: Thu, 17 Aug 2017 23:18:16 +0800 Subject: [PATCH] remove socket.io library, use native WebSocket with ws (#502) --- src/puppet-web/server.ts | 83 +++++++++++++++++------------- src/puppet-web/wechaty-bro.js | 63 +++++++++++++---------- test/puppet-web/puppet-web.spec.ts | 25 ++++++--- test/puppet-web/server.spec.ts | 4 +- 4 files changed, 104 insertions(+), 71 deletions(-) diff --git a/src/puppet-web/server.ts b/src/puppet-web/server.ts index 31be652e..ac8b72cb 100644 --- a/src/puppet-web/server.ts +++ b/src/puppet-web/server.ts @@ -16,21 +16,25 @@ * limitations under the License. * */ -import * as io from 'socket.io' -import * as https from 'https' import * as bodyParser from 'body-parser' - -import * as express from 'express' import { EventEmitter } from 'events' +import * as express from 'express' +import * as https from 'https' +import * as WebSocket from 'ws' import { log } from '../config' +export interface WechatyBroEvent { + name: string, + data: string | object, +} + export class Server extends EventEmitter { private express: express.Application private httpsServer: https.Server | null - public socketServer: SocketIO.Server | null - public socketClient: SocketIO.Socket | null + public socketServer: WebSocket.Server | null + public socketClient: WebSocket | null constructor(private port: number) { super() @@ -44,7 +48,7 @@ export class Server extends EventEmitter { try { this.createExpress() await this.createHttpsServer(this.express) - this.createSocketIo(this.httpsServer) + this.createWebSocketServer(this.httpsServer) return @@ -105,30 +109,32 @@ export class Server extends EventEmitter { /** * Socket IO */ - public createSocketIo(httpsServer): SocketIO.Server { - this.socketServer = io.listen(httpsServer, { - // log: true + public createWebSocketServer(httpsServer): WebSocket.Server { + this.socketServer = new WebSocket.Server({ + server: httpsServer, }) - this.socketServer.sockets.on('connection', (s) => { - log.verbose('PuppetWebServer', 'createSocketIo() got connection from browser') - // console.log(s.handshake) + this.socketServer.on('connection', client => { + log.verbose('PuppetWebServer', 'createWebSocketServer() got connection from browser') if (this.socketClient) { - log.warn('PuppetWebServer', 'createSocketIo() on(connection) there already has a this.socketClient') + log.warn('PuppetWebServer', 'createWebSocketServer() on(connection) there already has a this.socketClient') this.socketClient = null // close() ??? } - this.socketClient = s - this.initEventsFromClient(s) + this.socketClient = client + this.initEventsFromClient(client) }) return this.socketServer } - private initEventsFromClient(client: SocketIO.Socket): void { + private initEventsFromClient(client: WebSocket): void { log.verbose('PuppetWebServer', 'initEventFromClient()') this.emit('connection', client) + client.on('open', () => { + log.silly('PuppetWebServer', 'initEventsFromClient() on(open) WebSocket opened') + }) - client.on('disconnect', e => { - log.silly('PuppetWebServer', 'initEventsFromClient() on(disconnect) socket.io disconnect: %s', e) + client.on('close', e => { + log.silly('PuppetWebServer', 'initEventsFromClient() on(disconnect) WebSocket disconnect: %s', e) // 1. Browser reload / 2. Lost connection(Bad network) this.socketClient = null this.emit('disconnect', e) @@ -139,23 +145,27 @@ export class Server extends EventEmitter { log.error('PuppetWebServer', 'initEventsFromClient() on(error): %s', e.stack) }) - // Events from Wechaty@Broswer --to--> Server - ; // MUST KEEP: seprator - [ - 'message', - 'scan', - 'login', - 'logout', - 'log', - 'unload', // @depreciated 20160825 zixia - // when `unload` there should always be a `disconnect` event? - 'ding', - ].map(e => { - client.on(e, data => { - // log.silly('PuppetWebServer', `initEventsFromClient() forward client event[${e}](${data}) from browser by emit it`) - this.emit(e, data) - }) + client.on('message', data => { + const obj = JSON.parse(data as string) as WechatyBroEvent + this.emit(obj.name, obj.data) }) + // Events from Wechaty@Broswer --to--> Server + // ; // MUST KEEP: seprator + // [ + // 'message', + // 'scan', + // 'login', + // 'logout', + // 'log', + // 'unload', // @depreciated 20160825 zixia + // // when `unload` there should always be a `disconnect` event? + // 'ding', + // ].map(e => { + // client.on(e, data => { + // // log.silly('PuppetWebServer', `initEventsFromClient() forward client event[${e}](${data}) from browser by emit it`) + // this.emit(e, data) + // }) + // }) return } @@ -181,3 +191,6 @@ export class Server extends EventEmitter { } export default Server +export { + Server as PuppetWebServer, +} diff --git a/src/puppet-web/wechaty-bro.js b/src/puppet-web/wechaty-bro.js index 8f07a72e..ffb17383 100644 --- a/src/puppet-web/wechaty-bro.js +++ b/src/puppet-web/wechaty-bro.js @@ -146,14 +146,15 @@ */ function emit(event, data) { var eventsBuf = WechatyBro.vars.eventsBuf - if (!eventsBuf.map) { + if (!Array.isArray(eventsBuf)) { throw new Error('WechatyBro.vars.eventsBuf must be a Array') } if (event) { eventsBuf.unshift([event, data]) } var socket = WechatyBro.vars.socket - if (!socket) { + // readyState: A value of 1 indicates that the connection is established and communication is possible. + if (!socket || socket.readyState !== 1) { clog('WechatyBro.vars.socket not ready') return setTimeout(emit, 1000) // resent eventsBuf after 1000ms } @@ -165,8 +166,15 @@ var eventData = eventsBuf.pop() if (eventData && eventData.map && eventData.length===2) { clog('emiting ' + eventData[0]) - socket.emit(eventData[0], eventData[1]) - } else { clog('WechatyBro.emit() got invalid eventData: ' + eventData[0] + ', ' + eventData[1] + ', length: ' + eventData.length) } + // socket.emit(eventData[0], eventData[1]) + var obj = { + name: eventData[0], + data: eventData[1], + } + socket.send(JSON.stringify(obj)) + } else { + clog('WechatyBro.emit() got invalid eventData: ' + eventData[0] + ', ' + eventData[1] + ', length: ' + eventData.length) + } } if (bufLen > 1) { clog('WechatyBro.vars.eventsBuf[' + bufLen + '] all sent') } @@ -380,31 +388,34 @@ } function connectSocket() { log('connectSocket()') - if (typeof io !== 'function') { - log('connectSocket: io not found. loading lib...') - // http://stackoverflow.com/a/3248500/1123955 - var script = document.createElement('script') - script.onload = function() { - log('socket io lib loaded.') - connectSocket() + /*global socket*/ // WechatyBro global variable: socket + var socket = WechatyBro.vars.socket = new WebSocket('wss://127.0.0.1:' + port) + + socket.onmessage = function(messageEvent) { + var data = messageEvent.data + log('socket.onmessage: ' + data) + + var recvObj = JSON.parse(data) + var name = recvObj.name + var data = recvObj.data + switch (name) { + // ding -> dong. for test & live check purpose + // ping/pong are reserved by socket.io https://github.com/socketio/socket.io/issues/2414 + case 'ding': + var obj = { + name: 'dong', + data: data, + } + socket.send(JSON.stringify(obj)) + break + default: + clog('unknown event name: ' + name) } - script.src = '//cdnjs.cloudflare.com/ajax/libs/socket.io/1.4.5/socket.io.min.js' - document.getElementsByTagName('head')[0].appendChild(script) - return // wait to be called via script.onload() } - /*global io*/ // WechatyBro global variable: socket - var socket = WechatyBro.vars.socket = io.connect('wss://127.0.0.1:' + port/*, {transports: ['websocket']}*/) - - // ding -> dong. for test & live check purpose - // ping/pong are reserved by socket.io https://github.com/socketio/socket.io/issues/2414 - socket.on('ding', function(data) { - log('received socket io event: ding(' + data + '). emit dong...') - socket.emit('dong', data) - }) - - socket.on('connect' , function(e) { clog('connected to server:' + e) }) - socket.on('disconnect', function(e) { clog('socket disconnect:' + e) }) + socket.onopen = function(e) { clog('connected to server:' + e) } + socket.onclose = function(e) { clog('socket disconnect:' + e) } + socket.onerror = function(e) { clog('WebSocket error:' + e) } } /** diff --git a/test/puppet-web/puppet-web.spec.ts b/test/puppet-web/puppet-web.spec.ts index 70c3174c..2db973ce 100644 --- a/test/puppet-web/puppet-web.spec.ts +++ b/test/puppet-web/puppet-web.spec.ts @@ -16,14 +16,17 @@ * limitations under the License. * */ -import { test } from 'ava' +import { test } from 'ava' import { config, log, -} from '../../src/config' -import PuppetWeb from '../../src/puppet-web' -import PuppetWebServer from '../../src/puppet-web/server' +} from '../../src/config' +import PuppetWeb from '../../src/puppet-web' +import { + PuppetWebServer, + WechatyBroEvent, +} from '../../src/puppet-web/server' /** * the reason why use `test.serial` here is: @@ -85,7 +88,7 @@ test.serial('server/browser socketio ding', async t => { ///////////////////////////////////////////////////////////////////////////// - function dingSocket(server: PuppetWebServer) { + function dingSocket(server: PuppetWebServer): Promise { const maxTime = 60000 // 60s const waitTime = 3000 let totalTime = 0 @@ -112,15 +115,21 @@ test.serial('server/browser socketio ding', async t => { setTimeout(testDing, waitTime) return } - log.silly('TestPuppetWeb', 'dingSocket() server.socketClient: %s', server.socketClient) - server.socketClient.once('dong', data => { + + // server.socketClient is set + log.silly('TestPuppetWeb', 'dingSocket() server.socketClient: %s', server.socketClient.readyState) + server.once('dong', data => { log.verbose('TestPuppetWeb', 'socket recv event dong: ' + data) clearTimeout(timeoutTimer) return resolve(data) }) - server.socketClient.emit('ding', EXPECTED_DING_DATA) + const obj: WechatyBroEvent = { + name: 'ding', + data: EXPECTED_DING_DATA, + } + server.socketClient.send(JSON.stringify(obj)) } }) } diff --git a/test/puppet-web/server.spec.ts b/test/puppet-web/server.spec.ts index a5f7f426..dc7e72e0 100644 --- a/test/puppet-web/server.spec.ts +++ b/test/puppet-web/server.spec.ts @@ -41,8 +41,8 @@ test('create & close', async t => { t.is(typeof httpsServer, 'object', 'create https server') httpsServer.on('close', _ => spy('onClose')) - const socketio = s.createSocketIo(httpsServer) - t.is(typeof socketio, 'object', 'should created socket io instance') + const socket = s.createWebSocketServer(httpsServer) + t.is(typeof socket, 'object', 'should created WebSocket instance') const retClose = await new Promise((resolve, reject) => { ; (httpsServer as any).close(_ => { -- GitLab