diff --git a/README.md b/README.md index 98a84f6d2889ef156398d10e916c5a4fe4338f2c..77582b144aaf1001bb006ccc603f0cf4c63537f4 100644 --- a/README.md +++ b/README.md @@ -8,3 +8,5 @@ If you are interested in wechaty, please come back 4 weeks later. ;-p https://news.ycombinator.com/item?id=5388444 https://github.com/KostyaKow/shell_matrix http://hackertyper.com/ + +code check use eslint diff --git a/lib/puppet-web-injectio.js b/lib/puppet-web-injectio.js index 2b4de59c23af8d8636618c4a59179f92bdd175c9..a11f54f1a75597d01bb64b5757d522a3baf0b392 100644 --- a/lib/puppet-web-injectio.js +++ b/lib/puppet-web-injectio.js @@ -20,8 +20,13 @@ */ ;(function (port) { - var port = port || 8788 + port = port || 8788 + /** + * + * Get All I Need from Browser + * + */ var injector = angular.element(document).injector() var rootScope = injector.get("$rootScope") var http = injector.get("$http") @@ -32,42 +37,85 @@ var loginScope = angular.element(".login_box").scope() + var Wechaty = function () { - debug(true) - hook() + initZlog() + zlog('wechaty port ' + port) + /** + * socket might be destroyed(reconnected) + */ + var socket + + initSocket() + + hookUnload() + hookMessage() zlog('Wechaty injected!. ;-D') } angular.extend(Wechaty, { - debug: debug - , getLoginStatusCode: function () { return loginScope.code } + getLoginStatusCode: function () { return loginScope.code } , getLoginQrImgUrl: function () { return loginScope.qrcodeUrl } , isLogined: function () { return 200===loginScope.code } , isReady: function () { return !!(angular && angular.element && angular.element("body")) } + , log: function (msg) { SOCKET && SOCKET.emit('log', msg) } + , ping: function () { return 'pong' } }) - function debug(enable) { + function initZlog() { + var enable = true if (enable) { - delete window.console.log - delete window.console - window.console.zlog = window.console.log - window.zlog = function (s) { return window.console.zlog(s) } - window.console.log = function () {} + if (console) + delete console.log + delete console + console.zlog = window.console.log + window.zlog = function (s) { return console.zlog(s) } + console.log = function () {} } else { window.zlog = function () {} } } - function hook() { + function hookMessage() { rootScope.$on("message:add:success", function (event, data) { - http.post('https://localhost:' + port, data) + socket.emit('message', data) + .catch(function (e) { zlog('socket.emit(message, data) fail:'); zlog(e) }) + }) + } + + function hookUnload() { + window.addEventListener ('unload', function (e) { + socket.emit('unload') + }) + } + + function initSocket() { + // Wechaty global variable: socket + socket = io.connect('https://127.0.0.1:' + port) + + zlog('socket: ' + socket) + + socket.on('connect', function() { + zlog('on connect entried') + + rootScope.$on("message:add:success", function (event, data) { + socket.emit('message', data) + }) + + socket.on('disconnect', function(e) { + zlog('event: socket disconnect') + socket.emit('disconnect', e) + + // Reconnect... + setTimeout(initSocket, 1000) + }) }) + } window.Wechaty = Wechaty + return 'Wechaty' - return Wechaty }(arguments[0])) - diff --git a/lib/puppet-web.js b/lib/puppet-web.js index f9c49fcd4eccab654524bb137ca9a26a261eafbc..108ac0920a0b304c3b561f9b33cf4170e3834cfa 100644 --- a/lib/puppet-web.js +++ b/lib/puppet-web.js @@ -11,28 +11,36 @@ */ -/** +/************************************** * * Class PuppetWeb * - */ + ***************************************/ const EventEmitter = require('events') class PuppetWeb extends EventEmitter { - constructor() { + constructor(port) { super() const PORT = 8788 // W(87) X(88), ascii char code ;-] - const server = this.server = new WebServer(PORT) - const EVENTS = [ + this.port = port || PORT + const server = this.server = new WebServer(this.port) + + const EVENTS_IN = [ 'message' , 'login' , 'logout' ] + EVENTS_IN.map( event => + server.on(event, data => this.emit(event, data) ) + ) - EVENTS.map( event => { - server.on(event, data => { this.emit(event, data) }) - }) + const EVENTS_OUT = [ + 'sent' + ] + EVENTS_OUT.map( event => + this.on(event, data => server.emit(event, data) ) + ) } /** @@ -40,8 +48,12 @@ class PuppetWeb extends EventEmitter { * Interface Methods * */ - alive() { - return this.server && this.server.isLogined() + alive() { return this.server && this.server.isLogined() } + destroy() { + if (this.server) { + this.server.quit() + delete this.server + } } /** @@ -49,19 +61,22 @@ class PuppetWeb extends EventEmitter { * Public Methods * */ - getLoginQrImgUrl() { - return this.server.browserProxy('Wechaty.getLoginQrImgUrl()') - } - - + getLoginQrImgUrl() { return this.server.Wechaty_getLoginQrImgUrl() } + getLoginStatusCode() { return this.server.Wechaty_getLoginStatusCode() } } -/** +/**************************************** * * Class WebServer * - */ + * + * + * + * + * + * + ***************************************/ const fs = require('fs') const io = require('socket.io') @@ -71,30 +86,50 @@ const bodyParser = require('body-parser') const Express = require('express') class WebServer extends EventEmitter { - constructor(port) { + constructor() { super() - /** - * * io events proxy between server & browser - * */ - this.WEBSERVER_EVENTS = [ + this.EVENTS_IN = [ 'message' , 'login' , 'logout' + , 'unload' + ] + + this.EVENTS_OUT = [ + 'send' + , 'logout' ] - const express = this.initExpress() - const server = this.initHttpsServer(express, port) - const socketio = this.initSocketIo(server) + this.logined = false - const browser = this.browser = new WebBrowser(port) + this.on('login' , () => this.logined = true ) + this.on('logout', () => this.logined = false ) + + } - this.online = false + init(port) { + this.port = port || 8788 + + this.express = this.initExpress() + this.server = this.initHttpsServer(this.express, this.port) + this.socketio = this.initSocketIo(this.server) + + this.browser = new WebBrowser() + this.browser.init(this.port) + .then(() => { + console.error('browser init finished with port: ' + this.port + '.') + this.on('unload', () => { + console.error('webserver received unload event') + this.browser.inject() + .then(() => console.error('re-injected')) + }) + }) + - this.on('login' , () => { this.online = true }) - this.on('logout', () => { this.online = false }) + // this.debugLoop() } /** @@ -103,14 +138,15 @@ class WebServer extends EventEmitter { * */ initHttpsServer(express, port) { + port = port || this.port // http://blog.mgechev.com/2014/02/19/create-https-tls-ssl-application-with-express-nodejs/ // openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365 // openssl rsa -in key.pem -out newkey.pem && mv newkey.pem key.pem return https.createServer({ - key: fs.readFileSync('key.pem'), - cert: fs.readFileSync('cert.pem') - }, express).listen(port, function () { - console.log('Example app listening on port ' + port + '!') + key: fs.readFileSync (path.join(path.dirname(__filename), 'key.pem')), + cert: fs.readFileSync(path.join(path.dirname(__filename), 'cert.pem')) + }, express).listen(port, () => { + console.error(`initHttpsServer listening on port ${port}!`) }) } @@ -129,16 +165,16 @@ class WebServer extends EventEmitter { next() }) - app.get('/', function (req, res) { - console.log(new Date()) - res.send('Hello World!') + app.get('/ping', function (req, res) { + console.error(new Date() + ' GET /ping') + res.send('pong') }) - app.post('/', function (req, res) { - console.log('post ' + new Date()) - console.log(req.body) - res.send(req.body) - }) + // app.post('/', function (req, res) { + // console.log('post ' + new Date()) + // console.log(req.body) + // res.send(req.body) + // }) return app } @@ -153,24 +189,33 @@ class WebServer extends EventEmitter { log: true }) - ioServer.sockets.on('connection', function(sock) { + ioServer.sockets.on('connection', (s) => { console.log('socket.on connection entried') - socket = sock + let socket = s socket.on('disconnect', function() { + console.error('socket.io disconnected') + /** + * Possible conditions: + * 1. Browser reload + * 2. Lost connection(Bad network + * 3. + */ socket = null }) - this.WEBSERVER_EVENTS.map(event => { - // Events <--from-- Wechaty@Broswer + this.EVENTS_IN.map(event => { + // Events from Wechaty@Broswer --to--> Server socket.on(event, data => { - console.log(`recv even ${event} from browser`) + console.log(`recv event[${event}] from browser`) this.emit(event, data) }) + }) - // Event --to--> Wechaty@Browser + this.EVENTS_OUT.map(event => { + // Event from Server --to--> Wechaty@Browser this.on(event, data => { - console.log(`sent even ${event} to browser`) + console.log(`sent even[${event}] to browser`) socket.emit(event, data) }) }) @@ -188,13 +233,50 @@ class WebServer extends EventEmitter { } isLogined() { - return this.online + return this.logined } - browserProxy(wechatyFunction) { - if (!this.browser) throw new Error('theres no broswer found in server instance!') + quit() { + if (this.browser) { + this.browser.quit() + delete this.browser + } + + if (this.server) { + // TODO: close server + console.log('todo: close & quite server') + } + } + + /** + * + * Proxy Call to Wechaty in Browser + * + */ + browserExecute(script) { + if (!this.browser) + throw new Error('no browser!') + return this.browser.execute(script) + } - return this.browser.executeScript(wechatyFunction) + proxyWechaty(wechatyFunc) { + const args = Array.prototype.slice.call(arguments, 1) + const argsJson = JSON.stringify(args) + const wechatyScript = `return (Wechaty && Wechaty.${wechatyFunc}.apply(undefined, JSON.parse('${argsJson}')))` + + console.error('proxyWechaty: ' + wechatyScript) + return this.browserExecute(wechatyScript) + } + + Wechaty_getLoginStatusCode() { return this.proxyWechaty('getLoginStatusCode') } + Wechaty_getLoginQrImgUrl() { return this.proxyWechaty('getLoginQrImgUrl') } + // Wechaty_CARPEDIEM() { return this.proxyWechaty('call') } + + debugLoop() { + this.Wechaty_getLoginStatusCode().then((c) => { + console.error(`login status code: ${c}`) + setTimeout(this.debugLoop.bind(this), 3000) + }) } } @@ -203,87 +285,71 @@ class WebServer extends EventEmitter { */ -/** +/**************************************** * * Class WebBrowser * - */ + ***************************************/ const path = require('path') const WebDriver = require('selenium-webdriver') class WebBrowser { - constructor(port) { - const BROWSER = this.BROWSER = 'chrome' - const PORT = this.PORT = port || 8788 - const driver = this.driver = new WebDriver.Builder().forBrowser(BROWSER).build() + constructor(browser) { + const BROWSER = this.BROWSER = browser || 'chrome' - this.open() - .then(this.inject.bind(this)) + const driver = this.driver = new WebDriver.Builder().forBrowser(BROWSER).build() + } - this.loop() + init(port) { + console.log(`browser inititializing... port: ${port}`) + this.port = port || 8788 + return this.open().then(this.inject.bind(this)) } open() { const WX_URL = 'https://wx.qq.com' - this.driver.get(WX_URL) // open wechat web page - - console.log('waitting for dom ready') + return this.driver.get(WX_URL) // open wechat web page - return this.driver.wait(() => { - return this.driver.isElementPresent(WebDriver.By.css('div.login_box')) - }, 60*1000, '\nFailed to wait div.login_box') + // return this.driver.wait(() => { + // return this.driver.isElementPresent(WebDriver.By.css('div.login_box')) + // }, 60*1000, '\nFailed to wait div.login_box') } inject() { const injectio = fs.readFileSync( - path.join(__dirname, 'puppet-web-injectio.js') + path.join(path.dirname(__filename), 'puppet-web-injectio.js') , 'utf8' ) - console.log('start injecting') + const socketio = fs.readFileSync( + // 'https://cdnjs.cloudflare.com/ajax/libs/socket.io/1.4.5/socket.io.min.js' + path.join(path.dirname(__filename), '/socket.io.min.js') + , 'utf8' + ) - return this.executeScript(injectio, this.PORT) + console.error('injecting') + return this.execute(socketio) + .then(() => this.execute(injectio, this.port)) + .then(() => this.execute('Wechaty()')) + .then(() => console.error('injected')) } - loop() { - console.log('start wait Login') - - const that = this - function startCheck() { - that.executeScript('return Wechaty.getLoginStatusCode()').then( function (c) { - console.log('got code: ' + c) - if (200!=c) { - setTimeout(startCheck, 500) - } else { - doLogin() - } - }) - } - startCheck() - - function doLogin() { - console.log('logined?!') + quit() { + if (this.driver) { + this.driver.quit() + delete this.driver } - - } - - quit() { - return this.driver.quit() - } - - getLoginQrImgUrl() { - return this.executeScript('return Wechaty.getLoginQrImgUrl()') } - executeScript(script) { + execute(script, ...args) { if (!this.driver) throw new Error('driver not found') - // a promise - return this.driver - .executeScript(script) + return this.driver.executeScript(script, args) } } +PuppetWeb.WebServer = WebServer +PuppetWeb.WebBrowser = WebBrowser module.exports = PuppetWeb diff --git a/lib/puppet.js b/lib/puppet.js index 78ba8d88a2fdc654a17f95bae3505d74bd333ec1..75ac6cac4a699b8b9db812d653f9d58d7ed2afb9 100644 --- a/lib/puppet.js +++ b/lib/puppet.js @@ -16,6 +16,7 @@ class Puppet extends EventEmitter { this.soul = soul this.soul.on('message', data => { this.emit('message', data) }) } + detach() { if (!this.soul) throw new Error('there is no soul inside to detach!') this.soul.destroy() diff --git a/package.json b/package.json index 8046f1b0c8bfaab1e8e9d7786f659ee33c3974a8..3b2857cf2d48800364bfbe9966f775ddfba04d75 100755 --- a/package.json +++ b/package.json @@ -29,6 +29,14 @@ "selenium-webdriver": "", "socket.io": "^1.4.5" }, + "eslintConfig": { + "env": { + "browser": true, + "node": true, + "es6": true, + "shared-node-browser": true + } + }, "devDependencies": { "tape": "^4.5.1" } diff --git a/tests/puppet-web-browser-tests.js b/tests/puppet-web-browser-tests.js new file mode 100644 index 0000000000000000000000000000000000000000..c7f93988d1912eae710526678a04228f37957724 --- /dev/null +++ b/tests/puppet-web-browser-tests.js @@ -0,0 +1,34 @@ +const test = require('tape') +const PuppetWeb = require('../lib/puppet-web') + +const WebBrowser = PuppetWeb.WebBrowser + +test('WebBrowser class tests', function (t) { + //t.plan(5) + const b = new WebBrowser({browser: 'chrome'}) + t.ok(b, 'WebBrowser instance created') + + b.open() + .then(() => { + t.ok(true, 'url opened') + + b.inject() + .then(() => { + t.ok(true, 'wechaty injected') + + b.execute('return 1+1') + .then(n => t.equal(n, 2, 'exec 1+1 in browser, equal 2')) + + b.execute('return Wechaty && Wechaty.ping()') + .then(r => t.equal(r, 'pong', 'Wechaty.ping() returns pong')) + + b.execute('return Wechaty && Wechaty.isReady()') + .then(r => t.notEqual(typeof r, 'bool', 'Wechaty.isReady() returns bool')) + + t.end() + }) + }) + + b.quit() + +}) diff --git a/tests/puppet-web-server-tests.js b/tests/puppet-web-server-tests.js new file mode 100644 index 0000000000000000000000000000000000000000..14bc16a2056fb50ff3fb684cc4cae8d30b9d8d6d --- /dev/null +++ b/tests/puppet-web-server-tests.js @@ -0,0 +1,61 @@ +const https = require('https') + +const test = require('tape') +const PuppetWeb = require('../lib/puppet-web') + +const WebServer = PuppetWeb.WebServer + +test('WebServer basic tests', function (t) { + t.plan(9) + + const PORT = 58788 + const s = new WebServer() + t.equal(typeof s , 'object', 'WebServer instance created') + + const express = s.initExpress() + t.equal(typeof express, 'function', 'init express') + delete express + + const server = s.initHttpsServer(express, PORT) + t.equal(typeof server, 'object', 'init server') + server.on('close', () => t.ok(true, 'HttpsServer quited')) + server.close(() => t.ok(true, 'HttpsServer closed')) + delete server + + const socketio = s.initSocketIo() + t.equal(typeof socketio, 'object', 'init socket io') + delete socketio + + t.equal(s.isLogined() , false , 'instance not logined') + s.emit('login') + t.equal(s.isLogined() , true , 'logined after login event') + s.emit('logout') + t.equal(s.isLogined() , false , 'logouted after logout event') + + s.quit() + delete s + //t.end() +}) + +test('WebServer smoking tests', function (t) { + const PORT = 58788 + const s = new WebServer() + + t.plan(1) + console.log(`s.init(${PORT})`) + s.init(PORT) + + const options = require('url').parse(`https://localhost:${PORT}/ping`) + Object.assign(options, {rejectUnauthorized: false}) // permit self-signed CA + + https.get(options, (res) => { + res.on('data', chunk => { + t.equal(chunk.toString(), 'pong', 'https get /ping return pong') + }) + }).on('error', e => { + console.error(e) + t.ok(false, 'https get fail') + }) + +}) +