1 | 'use strict';
|
---|
2 |
|
---|
3 | var Stream = require('stream').Stream,
|
---|
4 | util = require('util'),
|
---|
5 | driver = require('websocket-driver'),
|
---|
6 | EventTarget = require('./api/event_target'),
|
---|
7 | Event = require('./api/event');
|
---|
8 |
|
---|
9 | var API = function(options) {
|
---|
10 | options = options || {};
|
---|
11 | driver.validateOptions(options, ['headers', 'extensions', 'maxLength', 'ping', 'proxy', 'tls', 'ca']);
|
---|
12 |
|
---|
13 | this.readable = this.writable = true;
|
---|
14 |
|
---|
15 | var headers = options.headers;
|
---|
16 | if (headers) {
|
---|
17 | for (var name in headers) this._driver.setHeader(name, headers[name]);
|
---|
18 | }
|
---|
19 |
|
---|
20 | var extensions = options.extensions;
|
---|
21 | if (extensions) {
|
---|
22 | [].concat(extensions).forEach(this._driver.addExtension, this._driver);
|
---|
23 | }
|
---|
24 |
|
---|
25 | this._ping = options.ping;
|
---|
26 | this._pingId = 0;
|
---|
27 | this.readyState = API.CONNECTING;
|
---|
28 | this.bufferedAmount = 0;
|
---|
29 | this.protocol = '';
|
---|
30 | this.url = this._driver.url;
|
---|
31 | this.version = this._driver.version;
|
---|
32 |
|
---|
33 | var self = this;
|
---|
34 |
|
---|
35 | this._driver.on('open', function(e) { self._open() });
|
---|
36 | this._driver.on('message', function(e) { self._receiveMessage(e.data) });
|
---|
37 | this._driver.on('close', function(e) { self._beginClose(e.reason, e.code) });
|
---|
38 |
|
---|
39 | this._driver.on('error', function(error) {
|
---|
40 | self._emitError(error.message);
|
---|
41 | });
|
---|
42 | this.on('error', function() {});
|
---|
43 |
|
---|
44 | this._driver.messages.on('drain', function() {
|
---|
45 | self.emit('drain');
|
---|
46 | });
|
---|
47 |
|
---|
48 | if (this._ping)
|
---|
49 | this._pingTimer = setInterval(function() {
|
---|
50 | self._pingId += 1;
|
---|
51 | self.ping(self._pingId.toString());
|
---|
52 | }, this._ping * 1000);
|
---|
53 |
|
---|
54 | this._configureStream();
|
---|
55 |
|
---|
56 | if (!this._proxy) {
|
---|
57 | this._stream.pipe(this._driver.io);
|
---|
58 | this._driver.io.pipe(this._stream);
|
---|
59 | }
|
---|
60 | };
|
---|
61 | util.inherits(API, Stream);
|
---|
62 |
|
---|
63 | API.CONNECTING = 0;
|
---|
64 | API.OPEN = 1;
|
---|
65 | API.CLOSING = 2;
|
---|
66 | API.CLOSED = 3;
|
---|
67 |
|
---|
68 | API.CLOSE_TIMEOUT = 30000;
|
---|
69 |
|
---|
70 | var instance = {
|
---|
71 | write: function(data) {
|
---|
72 | return this.send(data);
|
---|
73 | },
|
---|
74 |
|
---|
75 | end: function(data) {
|
---|
76 | if (data !== undefined) this.send(data);
|
---|
77 | this.close();
|
---|
78 | },
|
---|
79 |
|
---|
80 | pause: function() {
|
---|
81 | return this._driver.messages.pause();
|
---|
82 | },
|
---|
83 |
|
---|
84 | resume: function() {
|
---|
85 | return this._driver.messages.resume();
|
---|
86 | },
|
---|
87 |
|
---|
88 | send: function(data) {
|
---|
89 | if (this.readyState > API.OPEN) return false;
|
---|
90 | if (!(data instanceof Buffer)) data = String(data);
|
---|
91 | return this._driver.messages.write(data);
|
---|
92 | },
|
---|
93 |
|
---|
94 | ping: function(message, callback) {
|
---|
95 | if (this.readyState > API.OPEN) return false;
|
---|
96 | return this._driver.ping(message, callback);
|
---|
97 | },
|
---|
98 |
|
---|
99 | close: function(code, reason) {
|
---|
100 | if (code === undefined) code = 1000;
|
---|
101 | if (reason === undefined) reason = '';
|
---|
102 |
|
---|
103 | if (code !== 1000 && (code < 3000 || code > 4999))
|
---|
104 | throw new Error("Failed to execute 'close' on WebSocket: " +
|
---|
105 | "The code must be either 1000, or between 3000 and 4999. " +
|
---|
106 | code + " is neither.");
|
---|
107 |
|
---|
108 | if (this.readyState < API.CLOSING) {
|
---|
109 | var self = this;
|
---|
110 | this._closeTimer = setTimeout(function() {
|
---|
111 | self._beginClose('', 1006);
|
---|
112 | }, API.CLOSE_TIMEOUT);
|
---|
113 | }
|
---|
114 |
|
---|
115 | if (this.readyState !== API.CLOSED) this.readyState = API.CLOSING;
|
---|
116 |
|
---|
117 | this._driver.close(reason, code);
|
---|
118 | },
|
---|
119 |
|
---|
120 | _configureStream: function() {
|
---|
121 | var self = this;
|
---|
122 |
|
---|
123 | this._stream.setTimeout(0);
|
---|
124 | this._stream.setNoDelay(true);
|
---|
125 |
|
---|
126 | ['close', 'end'].forEach(function(event) {
|
---|
127 | this._stream.on(event, function() { self._finalizeClose() });
|
---|
128 | }, this);
|
---|
129 |
|
---|
130 | this._stream.on('error', function(error) {
|
---|
131 | self._emitError('Network error: ' + self.url + ': ' + error.message);
|
---|
132 | self._finalizeClose();
|
---|
133 | });
|
---|
134 | },
|
---|
135 |
|
---|
136 | _open: function() {
|
---|
137 | if (this.readyState !== API.CONNECTING) return;
|
---|
138 |
|
---|
139 | this.readyState = API.OPEN;
|
---|
140 | this.protocol = this._driver.protocol || '';
|
---|
141 |
|
---|
142 | var event = new Event('open');
|
---|
143 | event.initEvent('open', false, false);
|
---|
144 | this.dispatchEvent(event);
|
---|
145 | },
|
---|
146 |
|
---|
147 | _receiveMessage: function(data) {
|
---|
148 | if (this.readyState > API.OPEN) return false;
|
---|
149 |
|
---|
150 | if (this.readable) this.emit('data', data);
|
---|
151 |
|
---|
152 | var event = new Event('message', { data: data });
|
---|
153 | event.initEvent('message', false, false);
|
---|
154 | this.dispatchEvent(event);
|
---|
155 | },
|
---|
156 |
|
---|
157 | _emitError: function(message) {
|
---|
158 | if (this.readyState >= API.CLOSING) return;
|
---|
159 |
|
---|
160 | var event = new Event('error', { message: message });
|
---|
161 | event.initEvent('error', false, false);
|
---|
162 | this.dispatchEvent(event);
|
---|
163 | },
|
---|
164 |
|
---|
165 | _beginClose: function(reason, code) {
|
---|
166 | if (this.readyState === API.CLOSED) return;
|
---|
167 | this.readyState = API.CLOSING;
|
---|
168 | this._closeParams = [reason, code];
|
---|
169 |
|
---|
170 | if (this._stream) {
|
---|
171 | this._stream.destroy();
|
---|
172 | if (!this._stream.readable) this._finalizeClose();
|
---|
173 | }
|
---|
174 | },
|
---|
175 |
|
---|
176 | _finalizeClose: function() {
|
---|
177 | if (this.readyState === API.CLOSED) return;
|
---|
178 | this.readyState = API.CLOSED;
|
---|
179 |
|
---|
180 | if (this._closeTimer) clearTimeout(this._closeTimer);
|
---|
181 | if (this._pingTimer) clearInterval(this._pingTimer);
|
---|
182 | if (this._stream) this._stream.end();
|
---|
183 |
|
---|
184 | if (this.readable) this.emit('end');
|
---|
185 | this.readable = this.writable = false;
|
---|
186 |
|
---|
187 | var reason = this._closeParams ? this._closeParams[0] : '',
|
---|
188 | code = this._closeParams ? this._closeParams[1] : 1006;
|
---|
189 |
|
---|
190 | var event = new Event('close', { code: code, reason: reason });
|
---|
191 | event.initEvent('close', false, false);
|
---|
192 | this.dispatchEvent(event);
|
---|
193 | }
|
---|
194 | };
|
---|
195 |
|
---|
196 | for (var method in instance) API.prototype[method] = instance[method];
|
---|
197 | for (var key in EventTarget) API.prototype[key] = EventTarget[key];
|
---|
198 |
|
---|
199 | module.exports = API;
|
---|