watchdog.ts 4.6 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14
/**
 *
 * wechaty: Wechat for Bot. and for human who talk to bot/robot
 *
 * Class PuppetWeb Watchdog
 *
 * monitor puppet
 *
 * Licenst: ISC
 * https://github.com/zixia/wechaty
 *
 *
 * Class PuppetWeb
 *
15
 */
16
// const co    = require('co')
17

18 19
import log   from '../brolog-env'
import Event from './event'
20
import { WatchdogFood } from '../config'
21

22
/* tslint:disable:variable-name */
23 24 25 26 27
const Watchdog = {
  onFeed
}

// feed me in time(after 1st feed), or I'll restart system
28
function onFeed(food: WatchdogFood) {
29

30 31 32
  // change to tape instead of tap
  // 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
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
33

34 35 36 37 38 39 40
  if (!food.type) {
    food.type = 'HEARTBEAT'
  }
  if (!food.timeout) {
    food.timeout = 60000 // 60s default. can be override in options but be careful about the number zero(0)
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
41
  if (!this) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
42
    throw new Error('onFeed() must has `this` of instanceof PuppetWeb')
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
43 44
  }

45
  const feed = `${food.type}:[${food.data}]`
46
  log.silly('PuppetWebWatchdog', 'onFeed: %d, %s', food.timeout, feed)
47

48
  if (this.currentState() === 'killing'
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
49
  ) {
50
    log.warn('PuppetWebWatchdog', 'onFeed() is disabled because currentState is `killing`')
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
51 52
    return
  }
53 54 55 56 57 58
  // if (this.readyState() === 'disconnecting'
  //     // || this.readyState() === 'disconnected'
  // ) {
  //   log.warn('PuppetWebWatchdog', 'onFeed() is disabled because readyState is `disconnecting`')
  //   return
  // }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
59

60
  setWatchDogTimer.call(this, food.timeout, feed)
61

62
  this.emit('heartbeat', feed)
63

64
  monitorScan.call(this, food.type)
65
  autoSaveSession.call(this)
66

67
  switch (food.type) {
68 69 70
    case 'POISON':
      clearWatchDogTimer.call(this)
      break
71

72 73 74
    case 'SCAN':
    case 'HEARTBEAT':
      break
75

76
    default:
77
      throw new Error('Watchdog onFeed: unsupport type ' + food.type)
78
  }
79 80 81 82 83 84
}

function clearWatchDogTimer() {
  if (this.watchDogTimer) {
    clearTimeout(this.watchDogTimer)
    this.watchDogTimer = null
85 86

    const timeLeft = this.watchDogTimerTime - Date.now()
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
87
    log.silly('PuppetWebWatchdog', 'clearWatchDogTimer() [%d] seconds left', Math.ceil(timeLeft / 1000))
88
  } else {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
89
    log.silly('PuppetWebWatchdog', 'clearWatchDogTimer() nothing to clear')
90 91 92
  }
}

93
function setWatchDogTimer(timeout, feed) {
94 95 96

  clearWatchDogTimer.call(this)

97
  log.silly('PuppetWebWatchdog', 'setWatchDogTimer(%d, %s)', timeout, feed)
98

99
  this.watchDogTimer = setTimeout(watchDogReset.bind(this, timeout, feed), timeout)
100
  // this.watchDogTimer.unref()
101
  this.watchDogTimerTime = Date.now() + timeout
102 103 104
  // block quit, force to use quit() // this.watchDogTimer.unref() // dont block quit
}

105
function watchDogReset(timeout, lastFeed) {
106
  log.verbose('PuppetWebWatchdog', 'watchDogReset() timeout %d', timeout)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
107
  const e = new Error('watchdog reset after '
108
                        + Math.floor(timeout / 1000)
109 110 111
                        + ' seconds, last feed:'
                        + '[' + lastFeed + ']'
                    )
112 113 114 115 116 117 118 119 120 121 122 123 124
  this.emit('error', e)
  return Event.onBrowserDead.call(this, e)
}

/**
 *
 * Deal with Sessions(cookies)
 * save every 5 mins
 *
 */
function autoSaveSession() {
  const SAVE_SESSION_INTERVAL = 5 * 60 * 1000 // 5 mins
  if (Date.now() - this.watchDogLastSaveSession > SAVE_SESSION_INTERVAL) {
125 126 127 128
    log.verbose('PuppetWebWatchdog', 'watchDog() saveSession(%s) after %d minutes'
                                    , this.profile
                                    , Math.floor(SAVE_SESSION_INTERVAL / 1000 / 60)
              )
129 130 131 132 133 134 135 136 137 138 139 140 141 142 143
    this.browser.saveSession()
    this.watchDogLastSaveSession = Date.now()
  }
}

/**
 *
 * Deal with SCAN events
 *
 * if web browser stay at login qrcode page long time,
 * sometimes the qrcode will not refresh, leave there expired.
 * so we need to refresh the page after a while
 *
 */
function monitorScan(type) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
144
  log.silly('PuppetWebWatchdog', 'monitorScan(%s)', type)
145 146 147 148 149 150 151 152 153 154

  const scanTimeout = 10 * 60 * 1000 // 10 mins

  if (type === 'SCAN') { // watchDog was feed a 'scan' data
    this.lastScanEventTime = Date.now()
  }
  if (this.logined()) { // XXX: login status right?
    this.lastScanEventTime = null
  } else if (this.lastScanEventTime
              && Date.now() - this.lastScanEventTime > scanTimeout) {
155 156
    log.warn('PuppetWebWatchdog', 'monirotScan() refresh browser for no food of type scan after %s mins'
                                , Math.floor(scanTimeout / 1000 / 60))
157 158 159 160 161 162
    // try to fix the problem
    this.browser.refresh()
    this.lastScanEventTime = Date.now()
  }
}

163 164
// module.exports = Watchdog
export default Watchdog