diff --git a/.travis.yml b/.travis.yml index 83eb97e55de35945504823ec2eb34744d32f0908..001e92b1ee0104669ad42fd0d2d77cbd36c3cbe0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,5 +16,5 @@ notifications: urls: - ${GITTER_IM_URL} on_success: change # options: [always|never|change] default: always - on_failure: always # options: [always|never|change] default: always + on_failure: change # options: [always|never|change] default: always on_start: never # options: [always|never|change] default: always diff --git a/app.json b/app.json index 40dbc834e34fa4fec1228efd0b326ad0105a6699..b0719fdeb438df332bf1c30632fbfec9fb3194cc 100644 --- a/app.json +++ b/app.json @@ -1,6 +1,6 @@ { - "name": "Wechaty Cloud Bot for IO", - "description": "Wechat for Bot. Get your own cloud bot by deploy me", + "name": "Wechaty Cloud Bot for IO @ Heroku", + "description": "Wechat for Bot. Get your Heroku cloud bot by deploy me", "repository": "https://github.com/zixia/wechaty", "logo": "https://raw.githubusercontent.com/zixia/wechaty/master/image/wechaty-icon.png", "keywords": ["wechaty", "wechat", "bot", "chatbot", "framework", "cloudbot"], diff --git a/bin/io-bot.js b/bin/io-bot old mode 100644 new mode 100755 similarity index 98% rename from bin/io-bot.js rename to bin/io-bot index ed57a5d6ed01f5c9e063f8805c4a019852ce596a..ac66f5098cdff90ddfd5dc4b5f762685035cc5da --- a/bin/io-bot.js +++ b/bin/io-bot @@ -1,3 +1,5 @@ +#!/usr/bin/env node + const Wechaty = require('..') const log = Wechaty.log diff --git a/example/ding-dong-bot.js b/example/ding-dong-bot.js index f12dd148c54372dbba7b2d798b62b2525e316238..57981aeb78fc5fa20c1aef1ed11d51b4ad7de425 100644 --- a/example/ding-dong-bot.js +++ b/example/ding-dong-bot.js @@ -31,6 +31,7 @@ const bot = new Wechaty({ profile: 'example-bot.wechaty.json' }) bot .on('login' , user => log.info('Bot', `${user.name()} logined`)) .on('logout' , user => log.info('Bot', `${user.name()} logouted`)) +.on('error' , e => log.info('Bot', 'error: %s', e)) .on('scan', ({url, code}) => { if (!/201|200/.test(code)) { let loginUrl = url.replace(/\/qrcode\//, '/l/') diff --git a/package.json b/package.json index 0d69936b65d66d112a8c63ce37099431fb4f48c0..795ab5f390b9406dd7cc439c6e2ccccb5bf6f5c6 100644 --- a/package.json +++ b/package.json @@ -1,14 +1,14 @@ { "name": "wechaty", - "version": "0.2.7", + "version": "0.2.8", "description": "Wechat for Bot. (Personal Account NOT Official Account)", "main": "index.js", "scripts": { "lint": "eslint src test", "pretest": "npm run lint", - "start": "node bin/io-bot.js", + "start": "node bin/io-bot", "demo": "node example/ding-dong-bot.js", - "test": "cross-env TAP_TIMEOUT=600 tap --reporter=tap test" + "test": "cross-env TAP_TIMEOUT=600 tap --reporter=tap test/{*,**/*}.spec.js" }, "repository": { "type": "git", @@ -52,7 +52,7 @@ "phantomjs-prebuilt": "latest", "ps-tree": "latest", "retry-promise": "latest", - "selenium-webdriver": "^2.53.2", + "selenium-webdriver": "latest", "socket.io": "latest", "ws": "latest" }, diff --git a/src/message.js b/src/message.js index fb8525ee900b9e3d30e065cae1d10af1d525089a..d1543f0a3fefa9870cdef83f8febb5b81ed68087 100644 --- a/src/message.js +++ b/src/message.js @@ -167,3 +167,7 @@ Object.keys(Message.Type).forEach(k => { Message.attach = function(puppet) { Message.puppet = puppet } module.exports = Message + +/* + * join room in mac client: https://support.weixin.qq.com/cgi-bin/mmsupport-bin/addchatroombyinvite?ticket=AUbv%2B4GQA1Oo65ozlIqRNw%3D%3D&exportkey=AS9GWEg4L82fl3Y8e2OeDbA%3D&lang=en&pass_ticket=T6dAZXE27Y6R29%2FFppQPqaBlNwZzw9DAN5RJzzzqeBA%3D&wechat_real_lang=en + */ diff --git a/src/npmlog-env.js b/src/npmlog-env.js index b8d6d8e0e6823758d2811e1215f3073af1c29da7..f3bf99451b0f09045de35e52d3d3bcfa2c4a955a 100644 --- a/src/npmlog-env.js +++ b/src/npmlog-env.js @@ -1,14 +1,14 @@ -const log = require('npmlog') - -const level = process.env.WECHATY_LOG -const levelRegexStr = 'silly|verbose|info|warn|error|silent' -const levelRegex = new RegExp(levelRegexStr, 'i') -if (levelRegex.test(level)) { - log.level = level.toLowerCase() - log.verbose('NpmLog', 'WECHATY_LOG set level to %s', level) -} -else if (level){ - log.warn('NpmLog', 'env WECHATY_LOG(%s) must be one of silly|verbose|info|warn|error|silent', level) -} - -module.exports = log +const log = require('npmlog') + +const level = process.env.WECHATY_LOG +const levelRegexStr = 'silly|verbose|info|warn|error|silent' +const levelRegex = new RegExp(levelRegexStr, 'i') +if (levelRegex.test(level)) { + log.level = level.toLowerCase() + log.verbose('NpmLog', 'WECHATY_LOG set level to %s', level) +} +else if (level){ + log.warn('NpmLog', 'env WECHATY_LOG(%s) must be one of silly|verbose|info|warn|error|silent', level) +} + +module.exports = log diff --git a/src/puppet-web/browser.js b/src/puppet-web/browser.js index 48b77cd5e54d9c770b4e225455fbdde94f4bb685..17a3ea5fc500faf7becb90beb45de6cfc3ef54a0 100644 --- a/src/puppet-web/browser.js +++ b/src/puppet-web/browser.js @@ -108,13 +108,20 @@ class Browser extends EventEmitter { // const phantomjsExe = require('phantomjs2').path const phantomjsArgs = [ - '--ignore-ssl-errors=true' // this help socket.io connect with localhost - , '--load-images=false' - // , '--webdriver-logfile=/tmp/wd.log' - // , '--webdriver-loglevel=DEBUG' + '--load-images=false' + , '--ignore-ssl-errors=true' // this help socket.io connect with localhost + , '--web-security=false' // https://github.com/ariya/phantomjs/issues/12440#issuecomment-52155299 + , '--ssl-protocol=TLSv1' // https://github.com/ariya/phantomjs/issues/11239#issuecomment-42362211 + + // issue: Secure WebSocket(wss) do not work with Self Signed Certificate in PhantomJS #12 + // , '--ssl-certificates-path=D:\\cygwin64\\home\\zixia\\git\\wechaty' // http://stackoverflow.com/a/32690349/1123955 + // , '--ssl-client-certificate-file=cert.pem' // + ] if (process.env.WECHATY_DEBUG) { - phantomjsArgs.push('--remote-debugger-port=8080') // XXX: be careful when in production usage. + phantomjsArgs.push('--remote-debugger-port=8080') // XXX: be careful when in production env. + phantomjsArgs.push('--webdriver-loglevel=DEBUG') + // phantomjsArgs.push('--webdriver-logfile=webdriver.debug.log') } const customPhantom = WebDriver.Capabilities.phantomjs() @@ -208,22 +215,24 @@ class Browser extends EventEmitter { return } let browserRe - switch (this.head) { - case true: - case 'chrome': - browserRe = 'chrome(?!driver)' - break - case false: - case undefined: - case null: - case 'phantomjs': + + switch (true) { + case !this.head: // no head default to phantomjs + case /phantomjs/i.test(this.head): + case /phantom/i.test(this.head): browserRe = 'phantomjs' break + case this.head: // head default to chrome + case /chrome/i.test(this.head): + browserRe = 'chrome(?!driver)' + break + default: log.warn('PuppetWebBrowser', 'getBrowserPids() for unsupported head: %s', this.head) browserRe = this.head } + let matchRegex = new RegExp(browserRe, 'i') const pids = children.filter(child => { // https://github.com/indexzero/ps-tree/issues/18 @@ -284,6 +293,11 @@ class Browser extends EventEmitter { }) } + /** + * + * check whether browser is full functional + * + */ readyLive() { log.verbose('PuppetWebBrowser', 'readyLive()') if (this.dead()) { diff --git a/src/puppet-web/event.js b/src/puppet-web/event.js index a44756797684e753a47dfbbb16beb840efa85b9c..97159d4e49efe23ce5ba18d1994bb189a5098ee2 100644 --- a/src/puppet-web/event.js +++ b/src/puppet-web/event.js @@ -48,7 +48,7 @@ function onBrowserDead(e) { // guard by variable: isBrowserBirthing to prevent the 2nd time entrance. if (this.isBrowserBirthing) { if (this.isBrowserBirthing === true) { - log.warn('PuppetWebEvent', 'onBrowserDead() Im busy, dont call me again before I return. this time will return and do nothing') + log.verbose('PuppetWebEvent', 'onBrowserDead() Im busy, dont call me again before I return. this time will return and do nothing') return } else { log.warn('PuppetWebEvent', 'onBrowserDead() Im FAKE busy? isBrowserBirthing is not boolean true!') @@ -56,7 +56,7 @@ function onBrowserDead(e) { } return co.call(this, function* () { - log.warn('PuppetWebEvent', 'onBrowserDead() co() set isBrowserBirthing true') + log.verbose('PuppetWebEvent', 'onBrowserDead() co() set isBrowserBirthing true') this.isBrowserBirthing = true const TIMEOUT = 180000 // 180s / 3m @@ -171,7 +171,10 @@ function onServerDisconnect(data) { // because the browser has just refreshed, need some time to re-init to ready. // if the browser is not ready, bridge init will fail, // caused browser dead and have to be restarted. 2016/6/12 - setTimeout(() => { + setTimeout(_ => { + if (!this.bridge) { + return // XXX: sometimes this.bridge gone in this timeout. why? what's happend between the last if(!this.bridge) check and the timeout call? + } this.bridge.init() .then(r => log.verbose('PuppetWebEvent', 'onServerDisconnect() bridge re-inited: %s', r)) .catch(e => log.error('PuppetWebEvent', 'onServerDisconnect() exception: [%s]', e)) diff --git a/src/puppet-web/injectio.js b/src/puppet-web/injectio.js index 7a9c901ed65e7263a9754af7ffc8e486778a2efc..e08b2cc49e98b8b0033f7af0d1205d37eebc68a0 100644 --- a/src/puppet-web/injectio.js +++ b/src/puppet-web/injectio.js @@ -449,7 +449,7 @@ return (function(port) { } /*global io*/ // Wechaty global variable: socket - var socket = Wechaty.vars.socket = io.connect('https://127.0.0.1:' + port) + var socket = Wechaty.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 diff --git a/src/puppet-web/server.js b/src/puppet-web/server.js index c6338c952fc8be220085114802692290a6c6be39..e945f0f8a65b946648b1b5ed46147abf5094f7f2 100644 --- a/src/puppet-web/server.js +++ b/src/puppet-web/server.js @@ -99,7 +99,8 @@ class Server extends EventEmitter { }) socketServer.sockets.on('connection', (s) => { log.verbose('PuppetWebServer', 'createSocketIo() got connection from browser') - if (this.socketClient) { this.socketClient = null } // close() ??? + // console.log(s.handshake) + if (this.socketClient) { this.socketClient = undefined } // close() ??? this.socketClient = s this.initEventsFromClient(s) }) @@ -112,9 +113,9 @@ class Server extends EventEmitter { this.emit('connection', client) client.on('disconnect', e => { - log.verbose('PuppetWebServer', 'socket.io disconnect: %s', e) + log.silly('PuppetWebServer', 'socket.io disconnect: %s', e) // 1. Browser reload / 2. Lost connection(Bad network) - this.socketClient = null + this.socketClient = undefined this.emit('disconnect', e) }) diff --git a/src/puppet-web/ssl-pem.js b/src/puppet-web/ssl-pem.js index 61138e2a836eafd5d291c6bae09b760a497fddef..06e0f0249bf5d585d2150347b0ae2963b434e50e 100644 --- a/src/puppet-web/ssl-pem.js +++ b/src/puppet-web/ssl-pem.js @@ -6,8 +6,11 @@ * so it will not be a security issue. * * 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 req -x509 -days 3650 -nodes -newkey rsa:2048 -keyout key.pem -out cert.pem * openssl rsa -in key.pem -out newkey.pem && mv newkey.pem key.pem + * + * Reference: + * What is a Pem file - http://serverfault.com/a/9717 */ const key = ` -----BEGIN RSA PRIVATE KEY----- diff --git a/src/puppet-web/watchdog.js b/src/puppet-web/watchdog.js index 32ead3437469b277d24e020e1552db60329ecb7f..41619879ee83792c50e513d409c3a870bb303735 100644 --- a/src/puppet-web/watchdog.js +++ b/src/puppet-web/watchdog.js @@ -32,6 +32,9 @@ function onFeed({ , timeout = 60000 // 60s default. can be override in options but be careful about the number zero(0) } = {}) { + type = type || 'HEARTBEAT' // BUG compatible with issue: node-tap strange behaviour cause CircleCI & Travis-CI keep failing #11 + timeout = timeout || 60000 // BUG compatible with issue: node-tap strange behaviour cause CircleCI & Travis-CI keep failing #11 + if (!this) { throw new Error('onFeed() must has `this` of instanceof PuppetWeb') } @@ -66,7 +69,9 @@ function clearWatchDogTimer() { if (this.watchDogTimer) { clearTimeout(this.watchDogTimer) this.watchDogTimer = null - log.silly('PuppetWebWatchdog', 'clearWatchDogTimer() cleared') + + const timeLeft = this.watchDogTimerTime - Date.now() + log.silly('PuppetWebWatchdog', 'clearWatchDogTimer() [%d] seconds left', Math.ceil(timeLeft / 1000)) } else { log.silly('PuppetWebWatchdog', 'clearWatchDogTimer() nothing to clear') } @@ -79,6 +84,7 @@ function setWatchDogTimer(timeout) { log.silly('PuppetWebWatchdog', 'setWatchDogTimer(%d)', timeout) this.watchDogTimer = setTimeout(watchDogReset.bind(this, timeout), timeout) + this.watchDogTimerTime = Date.now() + timeout // block quit, force to use quit() // this.watchDogTimer.unref() // dont block quit } diff --git a/test/puppet-web/index.spec.js b/test/puppet-web/index.spec.js index be67a99f539f054f940f658490fbe0d8de839fa1..e7a69c885fd83037ecd8568b467b0ec0116937e1 100644 --- a/test/puppet-web/index.spec.js +++ b/test/puppet-web/index.spec.js @@ -12,7 +12,20 @@ const PROFILE = 'unit-test-session.wechaty.json' const PuppetWeb = require('../../src/puppet-web') const Message = require('../../src/message') -test('PuppetWeb smoke testing', function(t) { +test('Puppet Web Self Message Identification', function(t) { + const p = new PuppetWeb({port: PORT, head: HEAD, profile: PROFILE}) + t.ok(p, 'should instantiated a PuppetWeb') + + const EXPECTED_USER_ID = 'zixia' + const m = new Message() + m.set('from', EXPECTED_USER_ID) + p.userId = EXPECTED_USER_ID + t.ok(p.self(m), 'should identified self for message which from is self') + + t.end() +}) + +false && test('PuppetWeb smoke testing', function(t) { let pw = new PuppetWeb({port: PORT, head: HEAD, profile: PROFILE}) t.ok(pw, 'should instantiated a PuppetWeb') @@ -63,7 +76,6 @@ test('Puppet Web server/browser communication', function(t) { yield pw.init() t.pass('should be inited') -log.level = 'silly' const ret = yield dingSocket(pw.server) t.equal(ret, EXPECTED_DING_DATA, 'should got EXPECTED_DING_DATA after resolved dingSocket()') }) @@ -74,8 +86,6 @@ log.level = 'silly' .then(r => { // Finally pw.quit() .then(t.end) - -log.level = 'info' }) .catch(e => { t.fail(e) }) // Exception @@ -83,7 +93,7 @@ log.level = 'info' ///////////////////////////////////////////////////////////////////////////// function dingSocket(server) { const maxTime = 60000 // 60s - const waitTime = 500 + const waitTime = 3000 let totalTime = 0 return new Promise((resolve, reject) => { log.verbose('TestPuppetWeb', 'dingSocket()') @@ -96,7 +106,7 @@ log.level = 'info' return testDing() function testDing() { - // log.silly('TestPuppetWeb', server.socketio) + log.silly('TestPuppetWeb', 'dingSocket() server.socketServer: %s', server.socketServer) if (!server.socketClient) { totalTime += waitTime if (totalTime > maxTime) { @@ -107,7 +117,7 @@ log.level = 'info' setTimeout(testDing, waitTime) return } - //log.silly('TestPuppetWebServer', server.socketClient) + log.silly('TestPuppetWeb', 'dingSocket() server.socketClient: %s', server.socketClient) server.socketClient.once('dong', data => { log.verbose('TestPuppetWeb', 'socket recv event dong: ' + data) return resolve(data) @@ -117,16 +127,3 @@ log.level = 'info' }) } }) - -test('Puppet Web Self Message Identification', function(t) { - const p = new PuppetWeb({port: PORT, head: HEAD, profile: PROFILE}) - t.ok(p, 'should instantiated a PuppetWeb') - - const EXPECTED_USER_ID = 'zixia' - const m = new Message() - m.set('from', EXPECTED_USER_ID) - p.userId = EXPECTED_USER_ID - t.ok(p.self(m), 'should identified self for message which from is self') - - t.end() -}) diff --git a/test/puppet-web/watchdog.spec.js b/test/puppet-web/watchdog.spec.js index 3664ce7dbc11657207ba2f3da0a0f0c7d2df5df0..909677c5a43657def0e3d9bdb2243284e61a9a68 100644 --- a/test/puppet-web/watchdog.spec.js +++ b/test/puppet-web/watchdog.spec.js @@ -21,6 +21,12 @@ test('Puppet Web watchdog timer', function(t) { co(function* () { + const origLogLevel = log.level + if (log.level === 'info') { + log.level = 'silent' + t.pass('set log.level = silent to mute log when watchDog reset wechaty temporary') + } + yield pw.init() pw.quit() // yield pw.bridge.quit() @@ -34,7 +40,7 @@ test('Puppet Web watchdog timer', function(t) { data: 'active_for_timeout_1ms' , timeout: 1 }) - yield new Promise((resolve) => setTimeout(_ => resolve(), 10)) // wait untill reset + yield new Promise(resolve => setTimeout(resolve, 1000)) // wait untill reset t.equal(errorCounter, 1, 'should get event[error] after watchdog timeout') pw.once('error', e => t.fail('waitDing() triggered watchDogReset()')) @@ -42,12 +48,11 @@ test('Puppet Web watchdog timer', function(t) { const EXPECTED_DING_DATA = 'dingdong' pw.emit('watchdog', { data: 'feed to extend the dog life' }) - const origLogLevel = log.level - log.level = 'silly' - t.pass('set log.level = silent to mute log when watchDog reset wechaty temporary') const dong = yield waitDing(EXPECTED_DING_DATA) - log.level = origLogLevel t.equal(dong, EXPECTED_DING_DATA, 'should get EXPECTED_DING_DATA from ding after watchdog reset, and restored log level') + + log.level = origLogLevel + }) .catch(e => { // Exception t.fail('co exception: ' + e.message) @@ -73,6 +78,7 @@ test('Puppet Web watchdog timer', function(t) { return retryPromise({max: max, backoff: backoff}, function(attempt) { log.silly('TestPuppetWeb', 'waitDing() retryPromise: attampt %s/%s time for timeout %s' , attempt, max, timeout) + return pw.ding(data) .then(r => { if (!r) { @@ -85,6 +91,7 @@ test('Puppet Web watchdog timer', function(t) { log.verbose('TestPuppetWeb', 'waitDing() exception: %s', e.message) throw e }) + }) .catch(e => { log.error('TestPuppetWeb', 'retryPromise() waitDing() finally FAIL: %s', e.message)