1 | var crypto = require('crypto');
|
2 | var EventEmitter = require('events').EventEmitter;
|
3 | var http = require('http');
|
4 | var https = require('https');
|
5 | var url = require('url');
|
6 | var util = require('util');
|
7 | var revolt = require('revolt');
|
8 |
|
9 | var WebSocket = module.exports = function(address, httpOptions) {
|
10 | EventEmitter.call(this);
|
11 |
|
12 | this.options = {
|
13 | method: 'GET',
|
14 | headers: {
|
15 | 'Connection': 'Upgrade',
|
16 | 'Upgrade': 'websocket',
|
17 | 'Sec-WebSocket-Version': '13',
|
18 | }
|
19 | };
|
20 |
|
21 | this.isClosed = false;
|
22 | this._requestExtensions = [];
|
23 | this._responseExtensions = [];
|
24 |
|
25 | var self = this;
|
26 | if (httpOptions) {
|
27 | for (k in httpOptions) {
|
28 | self.options[k] = httpOptions[k];
|
29 | }
|
30 | }
|
31 |
|
32 | this.setAddress(address);
|
33 |
|
34 | this.request = revolt();
|
35 | };
|
36 |
|
37 | util.inherits(WebSocket, EventEmitter);
|
38 |
|
39 | WebSocket.prototype.setAddress = function(address) {
|
40 | if (address.substr(0, 2) === 'ws') {
|
41 | address = 'http' + address.substr(2);
|
42 | }
|
43 |
|
44 | this.options.uri = address;
|
45 | var parsed = url.parse(address);
|
46 | this.options.headers['Host'] = parsed.host;
|
47 | };
|
48 |
|
49 | WebSocket.prototype.extendRequest = function(extensions) {
|
50 | this._requestExtensions = extensions;
|
51 | };
|
52 |
|
53 | WebSocket.prototype.extendResponse = function(extensions) {
|
54 | this._responseExtensions = extensions;
|
55 | };
|
56 |
|
57 | WebSocket.prototype.close = function() {
|
58 | if (this.isClosed) {
|
59 | return;
|
60 | }
|
61 |
|
62 | this.isClosed = true;
|
63 | if(this.socket) {
|
64 | this.socket.end();
|
65 | } else {
|
66 | this.emit('close', null, null, true);
|
67 | }
|
68 | };
|
69 |
|
70 | WebSocket.prototype.start = function() {
|
71 | var key = new Buffer('13' + '-' + Date.now()).toString('base64');
|
72 | var shasum = crypto.createHash('sha1');
|
73 | shasum.update(key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11');
|
74 | var expectedServerKey = shasum.digest('base64');
|
75 |
|
76 | this.options.headers['Sec-WebSocket-Key'] = key;
|
77 |
|
78 | var self = this;
|
79 | var req = this.request;
|
80 |
|
81 | this._requestExtensions.forEach(function(ext) {
|
82 | ext(req);
|
83 | });
|
84 |
|
85 | req = req.request(self.options);
|
86 |
|
87 | this._responseExtensions.forEach(function(ext) {
|
88 | req = ext(req);
|
89 | });
|
90 |
|
91 | var subscription = req.subscribe(function(env) {
|
92 |
|
93 | if (env.response.statusCode !== 101) {
|
94 | self.emit('error', 'server returned ' + env.response.statusCode);
|
95 | return;
|
96 | }
|
97 |
|
98 | var serverKey = env.response.headers['sec-websocket-accept'];
|
99 | if (typeof serverKey == 'undefined' || serverKey !== expectedServerKey) {
|
100 | self.emit('error', 'invalid server key');
|
101 | return;
|
102 | }
|
103 |
|
104 | if (env.response.head.length > 0) {
|
105 | env.request.connection.unshift(env.response.head);
|
106 | }
|
107 |
|
108 | self.isClosed = false;
|
109 | self.socket = env.request.connection;
|
110 | self.socket.on('close', function() {
|
111 | self.emit('close');
|
112 | env.request.abort();
|
113 | subscription.dispose();
|
114 | });
|
115 | self.emit('open', env.request.connection);
|
116 | }, function(err) {
|
117 | self.emit('error', err);
|
118 | });
|
119 | };
|