server.ts 5.4 KB
Newer Older
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
1
/**
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
2
 *   Wechaty - https://github.com/chatie/wechaty
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
3
 *
4
 *   @copyright 2016-2017 Huan LI <zixia@zixia.net>
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
5
 *
6 7 8
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
9
 *
10 11 12 13 14 15 16
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
17 18
 *
 */
19 20
import * as bodyParser  from 'body-parser'
import { EventEmitter } from 'events'
21 22 23
import * as express     from 'express'
import * as https       from 'https'
import * as WebSocket   from 'ws'
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
24

25
import { log }          from '../config'
26

27 28 29 30 31
export interface WechatyBroEvent {
  name: string,
  data: string | object,
}

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
32
export class Server extends EventEmitter {
33
  private express:      express.Application
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
34
  private httpsServer:  https.Server | null
35

36 37
  public socketServer: WebSocket.Server | null
  public socketClient: WebSocket | null
38

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
39
  constructor(private port: number) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
40 41 42
    super()
  }

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

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
45
  public async init(): Promise<void> {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
46
    log.verbose('PuppetWebServer', `init() on port ${this.port}`)
47

48
    try {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
49 50
      this.createExpress()
      await this.createHttpsServer(this.express)
51
      this.createWebSocketServer(this.httpsServer)
52

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
53
      return
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
54

55
    } catch (e) {
56 57
      log.error('PuppetWebServer', 'init() exception: %s', e.message)
      throw e
58
    }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
59 60 61 62 63
  }

  /**
   * Https Server
   */
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
64
  public async createHttpsServer(express: express.Application): Promise<https.Server> {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
65
    this.httpsServer = <https.Server>await new Promise((resolve, reject) => {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
66

67
      const srv = https.createServer({
L
lijiarui 已提交
68 69
        key:  require('./ssl-pem').key,
        cert: require('./ssl-pem').cert,
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
70 71 72 73 74 75 76 77 78 79 80 81
      }, express) // XXX: is express must exist here? try to get rid it later. 2016/6/11

      srv.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)
        }
      })

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
82
    })
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
83

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
84
    return this.httpsServer
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
85 86 87
  }

  /**
88
   * express Middleware
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
89
   */
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
90
  public createExpress(): express.Application {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
91 92 93
    this.express = express()
    this.express.use(bodyParser.json())
    this.express.use(function(req, res, next) {
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', '*')
97
      res.header('Access-Control-Allow-Origin', req.headers['origin'] as string)
98
      res.header('Access-Control-Allow-Credentials', 'true')
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
99
      res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept')
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
100 101
      next()
    })
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
102
    this.express.get('/ding', function(req, res) {
103
      log.silly('PuppetWebServer', 'createexpress() %s GET /ding', new Date())
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
104 105
      res.end('dong')
    })
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
106
    return this.express
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
107 108 109 110 111
  }

  /**
   * Socket IO
   */
112 113 114
  public createWebSocketServer(httpsServer): WebSocket.Server {
    this.socketServer = new WebSocket.Server({
      server: httpsServer,
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
115
    })
116 117
    this.socketServer.on('connection', client => {
      log.verbose('PuppetWebServer', 'createWebSocketServer() got connection from browser')
118
      if (this.socketClient) {
119
        log.warn('PuppetWebServer', 'createWebSocketServer() on(connection) there already has a this.socketClient')
120
        this.socketClient = null // close() ???
121
      }
122 123
      this.socketClient = client
      this.initEventsFromClient(client)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
124
    })
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
125
    return this.socketServer
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
126 127
  }

128
  private initEventsFromClient(client: WebSocket): void {
129
    log.verbose('PuppetWebServer', 'initEventFromClient()')
130 131

    this.emit('connection', client)
132 133 134
    client.on('open', () => {
      log.silly('PuppetWebServer', 'initEventsFromClient() on(open) WebSocket opened')
    })
135

136 137
    client.on('close', e => {
      log.silly('PuppetWebServer', 'initEventsFromClient() on(disconnect) WebSocket disconnect: %s', e)
138
      // 1. Browser reload / 2. Lost connection(Bad network)
139
      this.socketClient = null
140
      this.emit('disconnect', e)
141 142
    })

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

148 149 150
    client.on('message', data => {
      const obj = JSON.parse(data as string) as WechatyBroEvent
      this.emit(obj.name, obj.data)
151
    })
152 153

    return
154 155
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
156
  public async quit(): Promise<void> {
157
    log.verbose('PuppetWebServer', 'quit()')
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
158
    if (this.socketServer) {
159
      log.verbose('PuppetWebServer', 'closing socketServer')
160
      this.socketServer.close()
161
      this.socketServer = null
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
162 163
    }
    if (this.socketClient) {
164
      log.verbose('PuppetWebServer', 'closing socketClient')
165
      this.socketClient = null
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
166
    }
167
    if (this.httpsServer) {
168
      log.verbose('PuppetWebServer', 'closing httpsServer')
169 170
      this.httpsServer.close()
      this.httpsServer = null
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
171
    }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
172
    return
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
173 174
  }
}
Huan (李卓桓)'s avatar
merge  
Huan (李卓桓) 已提交
175 176

export default Server
177 178 179
export {
  Server as PuppetWebServer,
}