提交 b7c581e8 编写于 作者: Huan (李卓桓)'s avatar Huan (李卓桓)

fighting for session support, half work

上级 626dac22
......@@ -28,7 +28,10 @@ Please wait... I'm trying to login in...
`
console.log(welcome)
const bot = new Wechaty({head: 'chrome'})
const bot = new Wechaty({
head: 'chrome'
, session: 'ding-dong-bot.wechaty'
})
bot
.on('login' , user => log.info('Bot', 'logined'))
......
文件模式从 100644 更改为 100755
const log = require('npmlog')
const level = process.env.WECHATY_DEBUG
const levelRegexStr = 'silly|verbose|info|warn|error'
const levelRegex = new RegExp(levelRegexStr, 'i')
if (levelRegex.test(level)) {
log.level = level.toLowerCase()
log.verbose('Npmlog', 'WECHATY_DEBUG set level to %s', level)
}
else if (level){
log.warn('Npmlog', 'env WECHATY_DEBUG(%s) must be one of silly|verbose|info|warn|error', level)
}
module.exports = log
......@@ -32,7 +32,7 @@ class Browser {
.then(() => this)
// XXX: if no `.catch` here, promise will hang!
// XXX: https://github.com/SeleniumHQ/selenium/issues/2233
.catch(e => throw e)
.catch(e => { throw e })
// console.log(p)
// return p.catch()
......@@ -68,11 +68,11 @@ class Browser {
})
}
open() {
const WX_URL = 'https://wx.qq.com'
log.verbose('Browser', `open()ing at ${WX_URL}`)
open(url) {
url = url || 'https://wx.qq.com'
log.verbose('Browser', `open()ing at ${url}`)
return this.driver.get(WX_URL)
return this.driver.get(url)
}
getPhantomJsDriver() {
......@@ -114,9 +114,9 @@ class Browser {
}
log.verbose('Browser', 'driver.quit')
return this.driver.close() // http://stackoverflow.com/a/32341885/1123955
.then(r => this.driver.quit())
.then(r => this.driver = null)
.then(r => this.clean())
.then(() => this.driver.quit())
.then(() => { this.driver = null })
.then(() => this.clean())
}
clean() {
......@@ -169,33 +169,29 @@ class Browser {
}
}
getCookies() {
log.silly('Browser', 'getCookies()')
// console.trace("#################")
if (!this.driver || !this.driver.getSession()) {
return Promise.reject('no driver or no session')
}
return this.driver.manage().getCookies()
.then(cookies => {
log.silly('Browser', 'getCookies() got %s cookies', cookies.length)
return cookies
})
.catch(e => {
log.error('Browser', 'getCookies %s', e)
throw e
})
}
setCookies(cookies) { return this.setCookie(cookies) }
setCookie(cookie) {
/**
* only wrap addCookies for convinience
*
* use this.driver.manage() to call other functions like:
* deleteCookie / getCookie / getCookies
*/
addCookies(cookie) {
if (cookie.map) {
return cookie.map(c => {
return this.setCookie(c)
return this.addCookies(c)
})
}
return this.driver.manage().addCookie(cookie.name, cookie.value, cookie.path, cookie.domain, cookie.secure, cookie.expiry)
// convert expiry from seconds to milliseconds. https://github.com/SeleniumHQ/selenium/issues/2245
if (cookie.expiry) { cookie.expiry = cookie.expiry * 1000 }
log.silly('Browser', 'addCookies("%s", "%s", "%s", "%s", "%s", "%s")'
, cookie.name, cookie.value, cookie.path, cookie.domain, cookie.secure, cookie.expiry
)
return this.driver.manage()
.addCookie(cookie.name, cookie.value, cookie.path
, cookie.domain, cookie.secure, cookie.expiry
)
}
}
......
......@@ -179,7 +179,7 @@ return (function(port) {
})
Wechaty.vars.scanCode = code
}
setTimeout(checkScan, 100)
setTimeout(checkScan, 300)
return
}
......
......@@ -18,9 +18,10 @@
***************************************/
const util = require('util')
const fs = require('fs')
const log = require('npmlog')
const co = require('co')
const log = require('./npmlog-env')
const Puppet = require('./puppet')
const Message = require('./message')
const Contact = require('./contact')
......@@ -36,7 +37,7 @@ class PuppetWeb extends Puppet {
options = options || {}
this.port = options.port || 8788 // W(87) X(88), ascii char code ;-]
this.head = options.head
this.name = options.name // if not set name, then dont store session.
this.session = options.session // if not set session, then dont store session.
this.user = null // <Contact> of user self
}
......@@ -44,7 +45,7 @@ class PuppetWeb extends Puppet {
toString() { return `Class PuppetWeb({browser:${this.browser},port:${this.port}})` }
init() {
log.verbose('PuppetWeb', 'init()')
log.verbose('PuppetWeb', `init() with port:${this.port}, head:${this.head}, session:${this.session}`)
return co.call(this, function* () {
......@@ -113,10 +114,15 @@ class PuppetWeb extends Puppet {
log.verbose('PuppetWeb', 'initBrowser')
this.browser = new Browser({ head: this.head })
return this.browser.init()
.then(() => this.loadSession()).catch(e => { /* fail safe */ })
.then(() => this.browser.open())
.catch(e => {
return co.call(this, function* () {
yield this.browser.init()
yield this.browser.open()
yield this.checkSession()
yield this.loadSession().catch(e => { log.verbose('PuppetWeb', 'loadSession rejected: %s', e) /* fail safe */ })
yield this.checkSession()
yield this.browser.open()
yield this.checkSession()
}).catch(e => {
log.error('PuppetWeb', 'initBrowser rejected: %s', e)
throw e
})
......@@ -157,7 +163,6 @@ class PuppetWeb extends Puppet {
}
onServerConnection(data) {
this.saveSession()
log.verbose('PuppetWeb', 'onServerConnection: %s', data.constructor.name)
}
onServerDisconnect(data) {
......@@ -170,23 +175,23 @@ class PuppetWeb extends Puppet {
}
onServerLogin(data) {
this.saveSession()
co.call(this, function* () {
// co.call to make `this` context work inside generator.
// See also: https://github.com/tj/co/issues/274
const userName = yield this.bridge.getUserName()
if (!userName) {
log.silly('PuppetWeb', 'onServerLogin: browser not full loaded, retry later.')
setTimeout(this.onServerLogin.bind(this), 100)
log.verbose('PuppetWeb', 'onServerLogin: browser not full loaded, retry later.')
setTimeout(this.onServerLogin.bind(this), 300)
return
}
log.silly('PuppetWeb', 'userName: %s', userName)
this.user = yield Contact.load(userName).ready()
log.verbose('PuppetWeb', `user ${this.user.name()} logined`)
this.emit('login', this.user)
})
.catch(e => log.error('PuppetWeb', 'onServerLogin co rejected: %s', e))
yield this.saveSession()
}).catch(e => log.error('PuppetWeb', 'onServerLogin co rejected: %s', e))
}
onServerLogout(data) {
if (this.user) {
......@@ -203,11 +208,14 @@ class PuppetWeb extends Puppet {
}
m.ready().then(() => this.emit('message', m))
}
/**
* `unload` event is sent from js@browser to webserver via socketio
* after received `unload`, we re-inject the Wechaty js code into browser.
*/
onServerUnload(data) {
/**
* `unload` event is sent from js@browser to webserver via socketio
* after received `unload`, we re-inject the Wechaty js code into browser.
*/
//XXX
return
log.verbose('PuppetWeb', 'server received unload event')
this.onServerLogout(data) // XXX: should emit event[logout] from browser
......@@ -220,6 +228,8 @@ class PuppetWeb extends Puppet {
.then(r => log.verbose('PuppetWeb', 'browser.quit()ed:' + r))
.then(r => this.browser.init())
.then(r => log.verbose('PuppetWeb', 'browser.re-init()ed:' + r))
.then(r => this.browser.open())
.then(r => log.verbose('PuppetWeb', 'browser.re-open()ed:' + r))
.then(r => this.bridge.init())
.then(r => log.verbose('PuppetWeb', 'bridge.re-init()ed:' + r))
.catch(e => log.error('PuppetWeb', 'onServerUnload() err: ' + e))
......@@ -256,7 +266,7 @@ class PuppetWeb extends Puppet {
// FIXME: find a alternate way to check a message create by `self`
.set('self' , this.user.id)
console.log(m)
log.verbose('PuppetWeb', 'reply() not sending message: %s', util.inspect(m))
return this.send(m)
}
......@@ -272,45 +282,78 @@ class PuppetWeb extends Puppet {
}
logined() { return !!(this.user) }
checkSession() {
log.verbose('PuppetWeb', `checkSession(${this.session})`)
return this.browser.driver.manage().getCookies()
.then(cookies => {
log.verbose('PuppetWeb', 'checkSession %s', require('util').inspect(cookies.map(c => { return {name: c.name, value: c.value} })))
return cookies
})
}
saveSession() {
if (!this.name) { return Promise.reject('saveSession() no name') }
const filename = this.name
log.verbose('PuppetWeb', `saveSession(${this.session})`)
if (!this.session) { return Promise.reject('saveSession() no session') }
const filename = this.session
return new Promise((resolve, reject) => {
this.browser.getCookies()
this.browser.driver.manage().getCookies()
.then(cookies => {
// console.log(cookies)
const jsonStr = JSON.stringify(cookies)
const filteredCookies = cookies.filter(c => {
if (/ChromeDriver/i.test(c.name)) { return false }
else { return true }
})
log.verbose('PuppetWeb', 'saving %d cookies for session: %s', cookies.length
, util.inspect(filteredCookies.map(c => c.name))
)
const jsonStr = JSON.stringify(filteredCookies)
fs.writeFile(filename, jsonStr, function(err) {
if(err) {
log.error('PuppetWeb', 'saveSession() fail to write file %s: %s', filename, err.Error)
return reject(err)
}
return resolve(cookies)
return resolve(filteredCookies)
})
})
})
}
loadSession() {
if (!this.name) { return Promise.reject('loadSession() no name') }
const filename = this.name
log.verbose('PuppetWeb', `loadSession(${this.session})`)
if (!this.session) { return Promise.resolve('loadSession() no session') }
const filename = this.session
return new Promise((resolve, reject) => {
fs.readFile(filename, (err, jsonStr) => {
if (err) {
if (err.code == 'ENOENT') {
log.verbose('PuppetWeb', 'loadSession() skipped coz no saved session')
} else {
log.verbose('PuppetWeb', 'loadSession() fail: %s', err.Error)
}
return reject(err.Error)
if (err) { log.verbose('PuppetWeb', 'loadSession(%s) skipped because: %s', this.session, err) }
return resolve(err.toString()) // resolve promise even there no session
}
const cookies = JSON.parse(jsonStr)
return Promise.all(this.browser.setCookies(cookies))
.then(() => resolve(cookies))
.catch(e => reject(e))
log.verbose('PuppetWeb', 'loading %d cookies for session', cookies.length )
const p = Promise.resolve()
log.verbose('PuppetWeb', 'cookies loaded from session:')
cookies.forEach(cookie => {
log.verbose('PuppetWeb', 'set cookie to browser: %s', cookie.name)
p.then(() => {
return this.browser.addCookies(cookie)
})
})
log.verbose('PuppetWeb', 'set cookies to browser end')
return p
.then(() => {
log.verbose('PuppetWeb', 'addCookies() all resolved')
resolve(cookies)
})
.catch(e => {
log.error('PuppetWeb', 'addCookies() rejected: %s', e)
reject(e)
)
// return Promise.all()
// .then(() => resolve(cookies))
// .catch(e => reject(e))
})
})
}
......
......@@ -8,10 +8,10 @@
* https://github.com/zixia/wechaty
*
*/
const log = require('npmlog')
const EventEmitter = require('events')
const log = require('./npmlog-env')
const Puppet = require('./puppet')
const PuppetWeb = require('./puppet-web')
......@@ -23,14 +23,20 @@ class Wechaty extends EventEmitter {
constructor(options) {
super()
this.options = options || {}
this.options.puppet = this.options.puppet || 'web'
this.options.name = this.options.name // no name, no session restore
this.options.puppet = this.options.puppet || process.env.WECHATY_PUPPET || 'web'
this.options.head = this.options.head || process.env.WECHATY_HEAD || false
this.options.session = this.options.session || process.env.WECHATY_SESSION // no session, no session restore
this.VERSION = require('../package.json').version
}
toString() { return 'Class Wechaty(' + this.puppet + ')'}
init() {
log.info('Wechaty', 'init() with version: %s', this.VERSION)
log.info('Wechaty', 'init() with version: %s, puppet: %s, head: %s, session: %s'
, this.VERSION
, this.options.puppet
, this.options.head
, this.options.session
)
this.initPuppet()
this.initEventHook()
......@@ -45,7 +51,7 @@ class Wechaty extends EventEmitter {
this.puppet = new Puppet.Web({
head: this.options.head
, port: this.options.port
, name: this.options.name
, session: this.options.session
})
break
default:
......@@ -89,12 +95,6 @@ class Wechaty extends EventEmitter {
// TODO: test through the server & browser
return 'dong'
}
/**
* @deprecated
* use on('scan', ({code, url}) => {}) instead.
*/
getLoginQrImgUrl() { return this.puppet.getLoginQrImgUrl() }
}
Puppet.Web = PuppetWeb
......
const co = require('co')
const test = require('tap').test
const log = require('npmlog')
// log.level = 'silly'
const log = require('../src/npmlog-env')
const Browser = require('../src/puppet-web-browser')
const PORT = 58788
const PORT = process.env.WECHATY_PORT || 58788
const HEAD = process.env.WECHATY_HEAD || false
test('Browser class smoking tests', function(t) {
const b = new Browser({port: PORT})
const b = new Browser({port: PORT, head: HEAD})
t.ok(b, 'Browser instance created')
co(function* () {
......@@ -20,9 +20,13 @@ test('Browser class smoking tests', function(t) {
const two = yield b.execute('return 1+1')
t.equal(two, 2, 'execute script ok')
let cookies = yield b.getCookies()
let cookies = yield b.driver.manage().getCookies()
t.ok(cookies.length, 'should got plenty of cookies')
yield b.driver.manage().deleteAllCookies()
cookies = yield b.driver.manage().getCookies()
t.equal(cookies.length, 0, 'should no cookie anymore after deleteAllCookies()')
const EXPECTED_COOKIES = [{
name: 'wechaty0'
, value: '8788-0'
......@@ -40,13 +44,18 @@ test('Browser class smoking tests', function(t) {
, expiry: 99999999999999
}]
yield b.setCookie(EXPECTED_COOKIES)
yield b.addCookies(EXPECTED_COOKIES)
cookies = yield b.getCookies()
cookies = yield b.driver.manage().getCookies()
const cookies0 = cookies.filter(c => { return RegExp(EXPECTED_COOKIES[0].name).test(c.name) })
t.equal(cookies0[0].name, EXPECTED_COOKIES[0].name, 'should filter out the cookie named wechaty0')
t.equal(cookies0[0].name, EXPECTED_COOKIES[0].name, 'getCookies() should filter out the cookie named wechaty0')
const cookies1 = cookies.filter(c => { return RegExp(EXPECTED_COOKIES[1].name).test(c.name) })
t.equal(cookies1[0].name, EXPECTED_COOKIES[1].name, 'should filter out the cookie named wechaty1')
t.equal(cookies1[0].name, EXPECTED_COOKIES[1].name, 'getCookies() should filter out the cookie named wechaty1')
yield b.open()
t.pass('re-opened url')
const cookieAfterOpen = yield b.driver.manage().getCookie(EXPECTED_COOKIES[0].name)
t.equal(cookieAfterOpen.name, EXPECTED_COOKIES[0].name, 'getCookie() should get expected cookie named after re-open url')
})
.catch((e) => { // Rejected
t.fail('co promise rejected:' + e)
......
const co = require('co')
const util = require('util')
const test = require('tap').test
const log = require('npmlog')
// log.level = 'verbose'
// log.level = 'silly'
const log = require('../src/npmlog-env')
const PORT = process.env.WECHATY_PORT || 58788
const HEAD = process.env.WECHATY_HEAD || false
const SESSION = process.env.WECHATY_SESSION || 'unit-test-session.wechaty.' + process.pid
const PuppetWeb = require('../src/puppet-web')
const PORT = 58788
const NAME = 'tmp-chatbot-name-for-unit-testing.wechaty'
function dingSocket(server) {
const maxTime = 9000
......@@ -41,7 +43,7 @@ function dingSocket(server) {
false && test('PuppetWeb smoke testing', function(t) {
let pw
co(function* () {
pw = new PuppetWeb({port: PORT})
pw = new PuppetWeb({port: PORT, head: HEAD, session: SESSION})
t.ok(pw, 'new PuppetWeb')
yield pw.init()
......@@ -83,7 +85,7 @@ false && test('PuppetWeb smoke testing', function(t) {
false && test('Puppet Web server/browser communication', function(t) {
let pw2
co(function* () {
pw2 = new PuppetWeb({port: PORT})
pw = new PuppetWeb({port: PORT, head: HEAD, session: SESSION})
t.ok(pw2, 'new PuppetWeb')
yield Promise.resolve()
......@@ -106,8 +108,8 @@ false && test('Puppet Web server/browser communication', function(t) {
})
test('Puppet Web WTF server/browser communication', function(t) {
const pw = new PuppetWeb({port: PORT})
false && test('Puppet Web WTF server/browser communication', function(t) {
pw = new PuppetWeb({port: PORT, head: HEAD, session: SESSION})
t.ok(pw, 'new PuppetWeb')
pw.init()
......@@ -139,44 +141,96 @@ test('Puppet Web WTF server/browser communication', function(t) {
}) // Exception
})
test('Puppet Web browser session save & load', function(t) {
let pw
pw = new PuppetWeb({port: PORT, name: NAME})
t.ok(pw, 'create PuppetWeb')
false && test('Puppet Web browser session save & load', function(t) {
let pw = new PuppetWeb({port: PORT, head: HEAD, session: SESSION})
t.ok(pw, 'new PuppetWeb')
co(function* () {
yield pw.init()
t.pass('pw inited')
const EXPECTED_COOKIE = {
name: 'wechaty'
, value: '8788'
name: 'wechaty_save_to_session'
, value: '### This cookie should be saved to session file, and load back at next PuppetWeb init ###'
, path: '/'
, domain: '.qq.com'
, secure: false
, expiry: 99999999999999
}
/*
{
name: 'wechaty0'
, value: '8788-0'
, path: '/'
, domain: '.qq.com'
, secure: false
, expiry: 99999999999999
}
*/
const EXPECTED_NAME_REGEX = new RegExp('^' + EXPECTED_COOKIE.name + '$')
yield pw.browser.driver.manage().deleteAllCookies()
let cookies = yield pw.browser.driver.manage().getCookies()
t.equal(cookies.length, 0, 'should no cookie after deleteAllCookies()')
yield pw.browser.addCookies(EXPECTED_COOKIE)
const cookieFromBrowser = yield pw.browser.driver.manage().getCookie(EXPECTED_COOKIE.name)
t.equal(cookieFromBrowser.name, EXPECTED_COOKIE.name, 'cookie from getCookie() should be same as we just set')
let cookiesFromCheck = yield pw.checkSession()
t.ok(cookiesFromCheck.length, 'should get cookies from checkSession() after addCookies()')
let cookieFromCheck = cookiesFromCheck.filter(c => EXPECTED_NAME_REGEX.test(c.name))
t.equal(cookieFromCheck[0].name, EXPECTED_COOKIE.name, 'cookie from checkSession() return should be same as we just set by addCookies()')
const cookiesFromSave = yield pw.saveSession()
t.ok(cookiesFromSave.length, 'should get cookies from saveSession()')
const cookieFromSave = cookiesFromSave.filter(c => EXPECTED_NAME_REGEX.test(c.name))
t.equal(cookieFromSave.length, 1, 'should has the cookie we just set')
t.equal(cookieFromSave[0].name, EXPECTED_COOKIE.name, 'cookie from saveSession() return should be same as we just set')
yield pw.browser.driver.manage().deleteAllCookies()
cookiesFromCheck = yield pw.checkSession()
t.equal(cookiesFromCheck.length, 0, 'should no cookie from checkSession() after deleteAllCookies()')
const cookiesFromLoad = yield pw.loadSession()
t.ok(cookiesFromLoad.length, 'should get cookies after loadSession()')
const cookieFromLoad = cookiesFromLoad.filter(c => EXPECTED_NAME_REGEX.test(c.name))
t.equal(cookieFromLoad[0].name, EXPECTED_COOKIE.name, 'cookie from loadSession() should has expected cookie')
// setTimeout(function() {
// co(function* () {
cookiesFromCheck = yield pw.checkSession()
t.ok(cookiesFromCheck.length, 'should get cookies from checkSession()')
cookieFromCheck = cookiesFromCheck.filter(c => EXPECTED_NAME_REGEX.test(c.name))
t.ok(cookieFromCheck.length, 'should has cookie after filtered')
t.equal(cookieFromCheck[0].name, EXPECTED_COOKIE.name, 'cookie from checkSession() return should has expected cookie')
pw.browser.setCookies(EXPECTED_COOKIE)
const cookies = yield pw.saveSession()
t.ok(cookies, 'saveSession should resolve cookies')
t.ok(cookies.length, 'cookies length should more than 0')
const cookiesLoad = yield pw.loadSession()
const cookiesFiltered = cookiesLoad.filter(c => /wechaty/i.test(c.name))
t.ok(cookiesLoad.length, 'pw session loaded')
t.end()
process.exit()
// })
// }, 100)
yield pw.quit()
t.pass('quited')
pw = new PuppetWeb({port: PORT, name: NAME})
pw = new PuppetWeb({port: PORT, head: HEAD, session: SESSION})
yield pw.init()
t.pass('re-new/init/open PuppetWeb')
const cookieAfterQuit = (yield pw.browser.driver.manage().getCookie(EXPECTED_COOKIE.name))
t.equal(cookieAfterQuit.name, EXPECTED_COOKIE.name, 'cookie from getCookie() after browser quite, should load the right cookie back')
const cookiesAfterQuit = yield pw.loadSession()
const cookiesFiltered2 = cookiesLoad.filter(c => /wechaty/i.test(c.name))
t.ok(cookiesFiltered2.length, 'should get old cookie after browser re-opened')
// clean
if (/^unit-test-session\.wechaty\.\d+/.test(SESSION)) {
require('fs').unlink(SESSION, err => {
if (err) { t.fail('unlink err: ' + err) }
else { t.pass('should unlinked wechaty session file before end') }
})
}
})
.catch(e => { // Reject
log.warn('TestingPuppetWeb', 'error: %s', e)
log.warn('TestPuppetWeb', 'error: %s', e)
t.fail(e)
})
.then(r => { // Finally
......
......@@ -56,7 +56,7 @@ test('WebDriver process create & quit test', function(t) {
// XXX WTF with co module???
test('WebDriver smoke testing', function(t) {
const wb = new PuppetWebBrowser()
const wb = new PuppetWebBrowser({port: PORT, head: HEAD})
t.ok(wb, 'Browser instnace')
const bridge = new PuppetWebBridge({browser: wb, port: PORT})
......@@ -105,7 +105,7 @@ test('WebDriver smoke testing', function(t) {
})
test('WebDriver WTF testing', function(t) {
const wb = new PuppetWebBrowser()
const wb = new PuppetWebBrowser({port: PORT, head: HEAD})
t.ok(wb, 'Browser instnace')
const bridge = new PuppetWebBridge({browser: wb, port: PORT})
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册