server.ts 5.5 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
 * Web Server for puppet
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
5
 *
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
6
 * Class PuppetWebServer
7
 *
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
8 9 10 11
 * Licenst: ISC
 * https://github.com/zixia/wechaty
 *
 */
12 13 14
import * as io          from 'socket.io'
import * as https       from 'https'
import * as bodyParser  from 'body-parser'
15

16
import * as express     from 'express'
17
import { EventEmitter } from 'events'
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
18

19 20
import log     from '../brolog-env'

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
21
export class Server extends EventEmitter {
22
  private express:      express.Application
23
  private httpsServer:  https.Server | null
24

25 26
  private socketServer: SocketIO.Server | null
  private socketClient: SocketIO.Socket | null
27 28 29 30

  constructor(
    private port: number
  ) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
31
    super()
32 33 34 35

    if (!port) {
      throw new Error('port not found')
    }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
36

37
    // this.port = port
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
38 39
  }

40
  public toString() { return `Server({port:${this.port}})` }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
41

42
  public async init(): Promise<Server> {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
43
    log.verbose('PuppetWebServer', `init() on port ${this.port}`)
44

45 46 47
    // return new Promise((resolve, reject) => {
      // this.initEventsToClient()
    try {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
48
      this.express      = this.createExpress()
49 50
      this.httpsServer  = await this.createHttpsServer(this.express)
        // , r => resolve(r), e => reject(e)
51
      this.socketServer = this.createSocketIo(this.httpsServer)
52

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
53
      log.verbose('PuppetWebServer', 'init()-ed')
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
54
      return this
55 56
    } catch (e) {
    // .catch(e => {
57 58
      log.error('PuppetWebServer', 'init() exception: %s', e.message)
      throw e
59
    }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
60 61 62 63 64
  }

  /**
   * Https Server
   */
65
  public createHttpsServer(express: express.Application): Promise<https.Server> {
66
    return new Promise((resolve, reject) => {
67 68 69 70 71 72 73 74 75 76 77 78 79
      const srv = https.createServer({
                                        key:    require('./ssl-pem').key
                                        , cert: require('./ssl-pem').cert
                                      }
                                    , express
                                    ) // XXX: is express must exist here? try to get rid it later. 2016/6/11
                        .listen(this.port, err => {
                          if (err) {
                            log.error('PuppetWebServer', 'createHttpsServer() exception: %s', err)
                            return reject(err)
                          } else {
                            log.verbose('PuppetWebServer', `createHttpsServer() listen on port ${this.port}`)
                            resolve(srv)
80
                          }
81
                        })
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
82
    })
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
83 84 85
  }

  /**
86
   * express Middleware
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
87
   */
88
  public createExpress(): express.Application {
89
    const e = express()
90 91
    e.use(bodyParser.json())
    e.use(function(req, res, next) {
92 93 94 95 96
      // cannot use `*` if angular is set `.withCredentials = true`
      // see also: https://github.com/whatwg/fetch/issues/251#issuecomment-199946808
      // res.header('Access-Control-Allow-Origin', '*')
      res.header('Access-Control-Allow-Origin', req.headers['origin'])
      res.header('Access-Control-Allow-Credentials', 'true')
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
97
      res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept')
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
98 99
      next()
    })
100
    e.get('/ding', function(req, res) {
101
      log.silly('PuppetWebServer', 'createexpress() %s GET /ding', new Date())
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
102 103
      res.end('dong')
    })
104
    return e
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
105 106 107 108 109
  }

  /**
   * Socket IO
   */
110
  public createSocketIo(httpsServer): SocketIO.Server {
111 112
    const socketServer = io.listen(httpsServer, {
      // log: true
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
113 114
    })
    socketServer.sockets.on('connection', (s) => {
Huan (李卓桓)'s avatar
firebug  
Huan (李卓桓) 已提交
115 116
      log.verbose('PuppetWebServer', 'createSocketIo() got connection from browser')
      // console.log(s.handshake)
117 118
      if (this.socketClient) {
        log.warn('PuppetWebServer', 'createSocketIo() on(connection) there already has a this.socketClient')
119
        this.socketClient = null // close() ???
120
      }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
121
      this.socketClient = s
122
      this.initEventsFromClient(s)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
123 124 125 126
    })
    return socketServer
  }

127
  private initEventsFromClient(client: SocketIO.Socket): void {
128
    log.verbose('PuppetWebServer', 'initEventFromClient()')
129 130 131 132

    this.emit('connection', client)

    client.on('disconnect', e => {
133
      log.silly('PuppetWebServer', 'initEventsFromClient() on(discohnnect) socket.io disconnect: %s', e)
134
      // 1. Browser reload / 2. Lost connection(Bad network)
135
      this.socketClient = null
136
      this.emit('disconnect', e)
137 138
    })

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
139
    client.on('error' , e => {
Huan (李卓桓)'s avatar
bug fix  
Huan (李卓桓) 已提交
140
      // log.error('PuppetWebServer', 'initEventsFromClient() client on error: %s', e)
141
      log.error('PuppetWebServer', 'initEventsFromClient() on(error): %s', e.stack)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
142
    })
143 144

    // Events from Wechaty@Broswer --to--> Server
145
    ; [
146 147 148 149 150
      'message'
      , 'scan'
      , 'login'
      , 'logout'
      , 'log'
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
151 152
      , 'unload'  // @depreciated 20160825 zixia
                  // when `unload` there should always be a `disconnect` event?
153
      , 'ding'
154 155
    ].map(e => {
      client.on(e, data => {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
156
        // log.silly('PuppetWebServer', `initEventsFromClient() forward client event[${e}](${data}) from browser by emit it`)
157 158 159
        this.emit(e, data)
      })
    })
160 161

    return
162 163
  }

164
  public quit(): Promise<any> {
165
    log.verbose('PuppetWebServer', 'quit()')
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
166
    if (this.socketServer) {
167
      log.verbose('PuppetWebServer', 'closing socketServer')
168
      this.socketServer.close()
169
      this.socketServer = null
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
170 171
    }
    if (this.socketClient) {
172
      log.verbose('PuppetWebServer', 'closing socketClient')
173
      this.socketClient = null
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
174
    }
175
    if (this.httpsServer) {
176
      log.verbose('PuppetWebServer', 'closing httpsServer')
177 178
      this.httpsServer.close()
      this.httpsServer = null
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
179
    }
180
    return Promise.resolve()
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
181 182 183
  }
}

184
export default Server