browser.js 16.9 KB
Newer Older
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
1 2
/**
 * Wechat for Bot. and for human who can talk with bot/robot
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
3
 *
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
4
 * Interface for puppet
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
5
 *
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
6 7 8 9
 * Licenst: ISC
 * https://github.com/zixia/wechaty
 *
 */
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
10

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
11
const fs            = require('fs')
12
const co            = require('co')
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
13
const path          = require('path')
14 15
const util          = require('util')
const EventEmitter  = require('events')
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
16 17
const WebDriver     = require('selenium-webdriver')
const retryPromise  = require('retry-promise').default // https://github.com/olalonde/retry-promise
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
18

19
const log = require('../npmlog-env')
20

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
21
const Config  = require('../config')
22

23
class Browser extends EventEmitter {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
24 25

  constructor({
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
26
    head = process.env.WECHATY_HEAD || Config.DEFAULT_HEAD
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
27 28
    , sessionFile
  } = {}) {
29
    log.verbose('Browser', 'constructor() with head(%s) sessionFile(%s)', head, sessionFile)
30
    super()
31
    log.verbose('PuppetWebBrowser', 'constructor()')
32
    this.head = head
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
33
    this.sessionFile = sessionFile // a file to save session cookies
34 35

    this.live = false
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
36 37
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
38

39
  toString() { return `Browser({head:${this.head})` }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
40 41

  init() {
42
    return this.initDriver()
43 44 45 46
    .then(() => {
      this.live = true
      return this
    })
47 48 49
    .catch(e => {
      // XXX: must has a `.catch` here, or promise will hang! 2016/6/7
      // XXX: if no `.catch` here, promise will hang!
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
50
      // with selenium-webdriver v2.53.2
51 52 53 54
      // XXX: https://github.com/SeleniumHQ/selenium/issues/2233
      log.error('PuppetWebBrowser', 'init() exception: %s', e.message)
      throw e
    })
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
55 56
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
57 58 59 60 61 62 63 64 65 66 67 68 69
  open(url) {
    url = url || 'https://wx.qq.com'
    log.verbose('PuppetWebBrowser', `open(${url})`)

    // TODO: set a timer to guard driver.get timeout, then retry 3 times 201607
    return this.driver.get(url)
    .catch(e => {
      log.error('PuppetWebBrowser', 'open() exception: %s', e.message)
      this.dead(e.message)
      throw e
    })
  }

70
  initDriver() {
71
    log.verbose('PuppetWebBrowser', 'initDriver(head: %s)', this.head)
72
    return new Promise((resolve, reject) => {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
73 74 75 76 77 78 79
      switch (true) {
        case !this.head: // no head default to phantomjs
        case /phantomjs/i.test(this.head):
        case /phantom/i.test(this.head):
          this.driver = this.getPhantomJsDriver()
          break
        case /firefox/i.test(this.head):
80 81 82 83
          this.driver = new WebDriver.Builder()
          .setAlertBehavior('ignore')
          .forBrowser('firefox')
          .build()
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
84 85
          break
        case /chrome/i.test(this.head):
86 87 88 89
          this.driver = new WebDriver.Builder()
          .setAlertBehavior('ignore')
          .forBrowser('chrome')
          .build()
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
90 91
          break
        default: // unsupported browser head
92 93 94
          throw new Error('unsupported head: ' + this.head)
      }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
95 96 97
      // XXX: if no `setTimeout()` here, promise will hang forever!
      // with a confirmed bug in selenium-webdriver v2.53.2:
      // https://github.com/SeleniumHQ/selenium/issues/2233
98 99 100
      // FIXED: selenium v3 released 20160807
      // setTimeout(() => { resolve(this.driver) }, 0)
      resolve(this.driver)
101
    })
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
102
  }
103

104 105 106 107 108
  refresh() {
    log.verbose('PuppetWebBrowser', 'refresh()')
    return this.driver.navigate().refresh()
  }

109
  getPhantomJsDriver() {
110
    // setup custom phantomJS capability https://github.com/SeleniumHQ/selenium/issues/2069
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
111
    const phantomjsExe = require('phantomjs-prebuilt').path
112
    // const phantomjsExe = require('phantomjs2').path
113 114

    const phantomjsArgs = [
115 116 117 118
      '--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
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
119 120 121 122 123

      // 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' // 

124
    ]
125
    if (process.env.WECHATY_DEBUG) {
126 127 128
      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')
129
    } else {
130 131 132 133 134
      if (log && log.level === 'silent') {
        phantomjsArgs.push('--webdriver-loglevel=NONE')
      } else {
        phantomjsArgs.push('--webdriver-loglevel=ERROR')
      }
135 136 137 138 139 140
    }

    const customPhantom = WebDriver.Capabilities.phantomjs()
    .setAlertBehavior('ignore')
    .set('phantomjs.binary.path', phantomjsExe)
    .set('phantomjs.cli.args', phantomjsArgs)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
141

142
    log.silly('PuppetWebBrowser', 'phantomjs binary: ' + phantomjsExe)
143
    log.silly('PuppetWebBrowser', 'phantomjs args: ' + phantomjsArgs.join(' '))
144

145
    const driver = new WebDriver.Builder()
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
146 147
    .withCapabilities(customPhantom)
    .build()
148
    
149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167
		/**
		 *  ISSUE #21 - https://github.com/zixia/wechaty/issues/21
	 	 *
 	 	 *	http://phantomjs.org/api/webpage/handler/on-resource-requested.html
		 *	http://stackoverflow.com/a/29544970/1123955
		 *  https://github.com/geeeeeeeeek/electronic-wechat/pull/319
		 *
		 */
  	driver.executePhantomJS(`
this.onResourceRequested = function(request, net) {
   console.log('REQUEST ' + request.url);
   blockRe = /wx\.qq\.com\/\?t=v2\/fake/i
   if (blockRe.test(request.url)) {
       console.log('Abort ' + request.url);
       net.abort();
   }
}
`)

168 169 170 171
    // https://github.com/detro/ghostdriver/blob/f976007a431e634a3ca981eea743a2686ebed38e/src/session.js#L233
    // driver.manage().timeouts().pageLoadTimeout(2000)
    
    return driver
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
172
  }
173

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
174
  quit() {
175
    log.verbose('PuppetWebBrowser', 'quit()')
176
    this.live = false
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
177
    if (!this.driver) {
178
      log.verbose('PuppetWebBrowser', 'driver.quit() skipped because no driver')
179
      return Promise.resolve('no driver')
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
180
    } else if (!this.driver.getSession()) {
181
      this.driver = null
182
      log.verbose('PuppetWebBrowser', 'driver.quit() skipped because no driver session')
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
183
      return Promise.resolve('no driver session')
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
184
    }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
185

186
    return co.call(this, function* () {
187
      log.silly('PuppetWebBrowser', 'quit() co()')
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
188
      yield this.driver.close() // http://stackoverflow.com/a/32341885/1123955
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
189
      log.silly('PuppetWebBrowser', 'quit() driver.close()-ed')
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
190
      yield this.driver.quit()
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
191
      log.silly('PuppetWebBrowser', 'quit() driver.quit()-ed')
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
192
      this.driver = null
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
193
      log.silly('PuppetWebBrowser', 'quit() this.driver = null')
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
194 195

      yield this.clean()
196
      log.silly('PuppetWebBrowser', 'quit() co() end')
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
197 198 199 200 201 202 203 204 205 206 207 208 209 210 211
    }).catch(e => {
      // console.log(e)
      // log.warn('PuppetWebBrowser', 'err: %s %s %s %s', e.code, e.errno, e.syscall, e.message)
      log.error('PuppetWebBrowser', 'quit() exception: %s', e.message)

      const crashMsgs = [
        'ECONNREFUSED'
        , 'WebDriverError: .* not reachable'
        , 'NoSuchWindowError: no such window: target window already closed'
      ]
      const crashRegex = new RegExp(crashMsgs.join('|'), 'i')

      if (crashRegex.test(e.message)) { log.warn('PuppetWebBrowser', 'driver.quit() browser crashed') }
      else                            { log.warn('PuppetWebBrowser', 'driver.quit() exception: %s', e.message) }
    })
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
212
  }
213

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
214
  clean() {
215
    const max = 15
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
216
    const backoff = 100
217

218 219 220
    /**
     * max = (2*totalTime/backoff) ^ (1/2)
     * timeout = 45000 for {max: 30, backoff: 100}
221
     * timeout = 11250 for {max: 15, backoff: 100}
222
     */
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
223
    const timeout = max * (backoff * max) / 2
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
224

225
    return retryPromise({ max: max, backoff: backoff }, attempt => {
226
      log.silly('PuppetWebBrowser', 'clean() retryPromise: attempt %s time for timeout %s'
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
227
        , attempt,  timeout)
228 229

      return new Promise((resolve, reject) => {
230 231 232
        this.getBrowserPids()
        .then(pids => {
          if (pids.length === 0) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
233
            log.verbose('PuppetWebBrowser', 'clean() retryPromise() resolved')
234
            resolve('clean() browser process not found, at attemp#' + attempt)
235
          } else {
236
            reject(new Error('clean() found browser process, not clean, dirty'))
237 238
          }
        })
239
        .catch(e => reject(e))
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
240 241
      })
    })
242
    .catch(e => {
243
      log.error('PuppetWebBrowser', 'retryPromise failed: %s', e.message)
244 245
      throw e
    })
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
246
  }
247

248
  getBrowserPids() {
249 250
    log.silly('Browser', 'getBrowserPids()')
    
251 252 253 254 255 256
    return new Promise((resolve, reject) => {
      require('ps-tree')(process.pid, (err, children) => {
        if (err) {
          reject(err)
          return
        }
257
        let browserRe
258 259 260 261 262

        switch (true) {
          case !this.head: // no head default to phantomjs
          case /phantomjs/i.test(this.head):
          case /phantom/i.test(this.head):
263 264 265
            browserRe = 'phantomjs'
            break

266 267 268 269 270
          case this.head: // head default to chrome
          case /chrome/i.test(this.head):
            browserRe = 'chrome(?!driver)'
            break

271 272 273 274
          default:
            log.warn('PuppetWebBrowser', 'getBrowserPids() for unsupported head: %s', this.head)
            browserRe = this.head
        }
275

276
        let matchRegex = new RegExp(browserRe, 'i')
277
        const pids = children.filter(child => {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
278
          log.silly('Browser', 'getBrowserPids() child: %s', JSON.stringify(child))
279 280
          // https://github.com/indexzero/ps-tree/issues/18
          return matchRegex.test('' + child.COMMAND + child.COMM)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
281 282
        }).map(child => child.PID)

283 284 285 286 287 288
        resolve(pids)
        return
      })
    })
  }

289 290 291 292 293 294 295
  /**
   * only wrap addCookies for convinience
   *
   * use this.driver.manage() to call other functions like:
   * deleteCookie / getCookie / getCookies
   */
  addCookies(cookie) {
296
    if (this.dead()) { return Promise.reject(new Error('addCookies() - browser dead'))}
297

298
    if (typeof cookie.map === 'function') {
299
      return cookie.map(c => {
300
        return this.addCookies(c)
301 302
      })
    }
303 304 305 306 307 308
    /**
     * convert expiry from seconds to milliseconds. https://github.com/SeleniumHQ/selenium/issues/2245
     * with selenium-webdriver v2.53.2
     * NOTICE: the lastest branch of selenium-webdriver for js has changed the interface of addCookie:
     * https://github.com/SeleniumHQ/selenium/commit/02f407976ca1d516826990f11aca7de3c16ba576
     */
309
    // if (cookie.expiry) { cookie.expiry = cookie.expiry * 1000 /* XXX: be aware of new version of webdriver */}
310

311
    log.silly('PuppetWebBrowser', 'addCookies("%s")', JSON.stringify(cookie))
312 313

    return this.driver.manage()
314 315 316 317 318
    // this is old webdriver format
    // .addCookie(cookie.name, cookie.value, cookie.path
    //   , cookie.domain, cookie.secure, cookie.expiry)
    // thisi is new webdriver format
    .addCookie(cookie)
319
    .catch(e => {
320
      log.warn('PuppetWebBrowser', 'addCookies() exception: %s', e.message)
321 322
      throw e
    })
323
  }
324 325

  execute(script, ...args) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
326
    log.silly('PuppetWebBrowser', `Browser.execute(${script.slice(0, 80)})`)
327
    // log.verbose('PuppetWebBrowser', `Browser.execute() driver.getSession: %s`, util.inspect(this.driver.getSession()))
328
    if (this.dead()) { return Promise.reject(new Error('browser dead')) }
329 330 331

    return this.driver.executeScript.apply(this.driver, arguments)
    .catch(e => {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
332 333
      // this.dead(e)
      log.warn('PuppetWebBrowser', 'execute() exception: %s', e.message)
334 335 336 337
      throw e
    })
  }

338 339 340 341 342
  /**
   *
   * check whether browser is full functional
   *
   */
343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369
  readyLive() {
    log.verbose('PuppetWebBrowser', 'readyLive()')
    if (this.dead()) {
      return Promise.reject(new Error('this.dead() true'))
    }
    return new Promise((resolve, reject) => {
      this.execute('return 1+1')
      .then(r => {
        if (r === 2) {
          resolve(true) // browser ok, living
          return
        }
        const errMsg = 'deadEx() found dead browser coz 1+1 = ' + r + ' (not 2)'
        log.verbose('PuppetWebBrowser', errMsg)
        this.dead(errMsg)
        reject(new Error(errMsg)) // browser not ok, dead
        return
      })
      .catch(e => {
        const errMsg = 'deadEx() found dead browser coz 1+1 = ' + e.message
        log.verbose('PuppetWebBrowser', errMsg)
        this.dead(errMsg)
        reject(new Error(errMsg)) // browser not live
        return
      })
    })
  }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
370

371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386
  dead(forceReason) {
    let errMsg
    let dead = false

    if (forceReason) {
      dead = true
      errMsg = forceReason
    } else if (!this.live) {
      dead = true
      errMsg = 'browser not live'
    } else if (!this.driver || !this.driver.getSession()) {
      dead = true
      errMsg = 'no driver or session'
    }

    if (dead) {
387
      log.warn('PuppetWebBrowser', 'dead() because %s', errMsg)
388
      this.live = false
389
      // must use nextTick here, or promise will hang... 2016/6/10
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
390 391
      process.nextTick(_ => {
        log.verbose('PuppetWebBrowser', 'dead() emit a `dead` event because %s', errMsg)
392 393 394 395 396 397
        this.emit('dead', errMsg)
      })
    }
    return dead
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
398 399 400 401
  checkSession() {
    // just check cookies, no file operation
    log.verbose('PuppetWebBrowser', 'checkSession()')

402
    if (this.dead()) { return Promise.reject(new Error('checkSession() - browser dead'))}
403 404 405 406

    return this.driver.manage().getCookies()
    .then(cookies => {
      // log.silly('PuppetWeb', 'checkSession %s', require('util').inspect(cookies.map(c => { return {name: c.name/*, value: c.value, expiresType: typeof c.expires, expires: c.expires*/} })))
407
      log.silly('PuppetWebBrowser', 'checkSession %s', cookies.map(c => c.name).join(','))
408 409
      return cookies
    })
410 411 412 413
    .catch(e => {
      log.error('PuppetWebBrowser', 'checkSession() getCookies() exception: %s', e.message)
      throw e
    })
414 415
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
416 417 418 419 420 421
  cleanSession() {
    log.verbose('PuppetWebBrowser', `cleanSession(${this.sessionFile})`)
    if (!this.sessionFile) {
      return Promise.reject(new Error('cleanSession() no session'))
    }

422
    if (this.dead())  { return Promise.reject(new Error('cleanSession() - browser dead'))}
423

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
424
    const filename = this.sessionFile
425 426 427
    return new Promise((resolve, reject) => {
      require('fs').unlink(filename, err => {
        if (err && err.code!=='ENOENT') {
428
          log.silly('PuppetWebBrowser', 'cleanSession() unlink session file %s fail: %s', filename, err.message)
429 430 431 432 433 434
        }
        resolve()
      })
    })
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
435 436 437 438 439 440 441 442 443
  saveSession() {
    log.silly('PuppetWebBrowser', `saveSession(${this.sessionFile})`)
    if (!this.sessionFile) {
      return Promise.reject(new Error('saveSession() no session'))
    } else if (this.dead()) {
      return Promise.reject(new Error('saveSession() - browser dead'))
    }

    const filename = this.sessionFile
444

445 446 447 448 449 450 451 452 453 454 455 456 457 458
    function cookieFilter(cookies) {
      const skipNames = [
        'ChromeDriver'
        , 'MM_WX_SOUND_STATE'
        , 'MM_WX_NOTIFY_STATE'
      ]
      const skipNamesRegex = new RegExp(skipNames.join('|'), 'i')
      return cookies.filter(c => {
        if (skipNamesRegex.test(c.name)) { return false }
        // else if (!/wx\.qq\.com/i.test(c.domain))  { return false }
        else                             { return true }
      })
    }
  
459 460
    return new Promise((resolve, reject) => {
      this.driver.manage().getCookies()
461 462
      .then(cookieFilter)
      .then(cookies => {
463 464
        // log.silly('PuppetWeb', 'saving %d cookies for session: %s', cookies.length
        //   , util.inspect(cookies.map(c => { return {name: c.name /*, value: c.value, expiresType: typeof c.expires, expires: c.expires*/} })))
465
        log.silly('PuppetWebBrowser', 'saving %d cookies for session: %s', cookies.length, cookies.map(c => c.name).join(','))
466 467 468 469

        const jsonStr = JSON.stringify(cookies)
        fs.writeFile(filename, jsonStr, function(err) {
          if(err) {
470
            log.error('PuppetWebBrowser', 'saveSession() fail to write file %s: %s', filename, err.Error)
471 472
            return reject(err)
          }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
473
          log.silly('PuppetWebBrowser', 'saved session(%d cookies) to %s', cookies.length, filename)
474
          return resolve(cookies)
475 476 477 478 479
        })
      })
      .catch(e => {
        log.error('PuppetWebBrowser', 'saveSession() getCookies() exception: %s', e.message)
        reject(e)
480 481 482 483
      })
    })
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
484 485 486 487 488 489 490
  loadSession() {
    log.verbose('PuppetWebBrowser', `loadSession(${this.sessionFile})`)
    if (!this.sessionFile) {
      return Promise.reject(new Error('loadSession() no sessionFile'))
    } else if (this.dead()) {
      return Promise.reject(new Error('loadSession() - browser dead'))
    }
491

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
492
    const filename = this.sessionFile
493 494 495 496

    return new Promise((resolve, reject) => {
      fs.readFile(filename, (err, jsonStr) => {
        if (err) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
497
          if (err) { log.silly('PuppetWebBrowser', 'loadSession(%s) skipped because error code: %s', filename, err.code) }
498
          return reject(new Error('error code:' + err.code))
499 500 501 502
        }
        const cookies = JSON.parse(jsonStr)

        const ps = this.addCookies(cookies)
503
        Promise.all(ps)
504
        .then(() => {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
505
          log.verbose('PuppetWebBrowser', 'loaded session(%d cookies) from %s', cookies.length, filename)
506 507 508
          resolve(cookies)
        })
        .catch(e => {
509
          log.error('PuppetWebBrowser', 'loadSession() addCookies() exception: %s', e.message)
510 511
          reject(e)
        })
512 513 514
      })
    })
  }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
515 516 517
}

module.exports = Browser