1 var Stream = require('stream').Stream,
2 util = require('util'),
3 driver = require('websocket-driver'),
4 EventTarget = require('./api/event_target'),
5 Event = require('./api/event');
7 var API = function(options) {
8 options = options || {};
9 driver.validateOptions(options, ['headers', 'extensions', 'maxLength', 'ping', 'proxy', 'tls', 'ca']);
11 this.readable = this.writable = true;
13 var headers = options.headers;
15 for (var name in headers) this._driver.setHeader(name, headers[name]);
18 var extensions = options.extensions;
20 [].concat(extensions).forEach(this._driver.addExtension, this._driver);
23 this._ping = options.ping;
25 this.readyState = API.CONNECTING;
26 this.bufferedAmount = 0;
28 this.url = this._driver.url;
29 this.version = this._driver.version;
33 this._driver.on('open', function(e) { self._open() });
34 this._driver.on('message', function(e) { self._receiveMessage(e.data) });
35 this._driver.on('close', function(e) { self._beginClose(e.reason, e.code) });
37 this._driver.on('error', function(error) {
38 self._emitError(error.message);
40 this.on('error', function() {});
42 this._driver.messages.on('drain', function() {
47 this._pingTimer = setInterval(function() {
49 self.ping(self._pingId.toString());
50 }, this._ping * 1000);
52 this._configureStream();
55 this._stream.pipe(this._driver.io);
56 this._driver.io.pipe(this._stream);
59 util.inherits(API, Stream);
67 write: function(data) {
68 return this.send(data);
72 if (data !== undefined) this.send(data);
77 return this._driver.messages.pause();
81 return this._driver.messages.resume();
84 send: function(data) {
85 if (this.readyState > API.OPEN) return false;
86 if (!(data instanceof Buffer)) data = String(data);
87 return this._driver.messages.write(data);
90 ping: function(message, callback) {
91 if (this.readyState > API.OPEN) return false;
92 return this._driver.ping(message, callback);
95 close: function(code, reason) {
96 if (code === undefined) code = 1000;
97 if (reason === undefined) reason = '';
99 if (code !== 1000 && (code < 3000 || code > 4999))
100 throw new Error("Failed to execute 'close' on WebSocket: " +
101 "The code must be either 1000, or between 3000 and 4999. " +
102 code + " is neither.");
104 if (this.readyState !== API.CLOSED) this.readyState = API.CLOSING;
105 this._driver.close(reason, code);
108 _configureStream: function() {
111 this._stream.setTimeout(0);
112 this._stream.setNoDelay(true);
114 ['close', 'end'].forEach(function(event) {
115 this._stream.on(event, function() { self._finalizeClose() });
118 this._stream.on('error', function(error) {
119 self._emitError('Network error: ' + self.url + ': ' + error.message);
120 self._finalizeClose();
125 if (this.readyState !== API.CONNECTING) return;
127 this.readyState = API.OPEN;
128 this.protocol = this._driver.protocol || '';
130 var event = new Event('open');
131 event.initEvent('open', false, false);
132 this.dispatchEvent(event);
135 _receiveMessage: function(data) {
136 if (this.readyState > API.OPEN) return false;
138 if (this.readable) this.emit('data', data);
140 var event = new Event('message', {data: data});
141 event.initEvent('message', false, false);
142 this.dispatchEvent(event);
145 _emitError: function(message) {
146 if (this.readyState >= API.CLOSING) return;
148 var event = new Event('error', {message: message});
149 event.initEvent('error', false, false);
150 this.dispatchEvent(event);
153 _beginClose: function(reason, code) {
154 if (this.readyState === API.CLOSED) return;
155 this.readyState = API.CLOSING;
156 this._closeParams = [reason, code];
160 if (!this._stream.readable) this._finalizeClose();
164 _finalizeClose: function() {
165 if (this.readyState === API.CLOSED) return;
166 this.readyState = API.CLOSED;
168 if (this._pingTimer) clearInterval(this._pingTimer);
169 if (this._stream) this._stream.end();
171 if (this.readable) this.emit('end');
172 this.readable = this.writable = false;
174 var reason = this._closeParams ? this._closeParams[0] : '',
175 code = this._closeParams ? this._closeParams[1] : 1006;
177 var event = new Event('close', {code: code, reason: reason});
178 event.initEvent('close', false, false);
179 this.dispatchEvent(event);
183 for (var method in instance) API.prototype[method] = instance[method];
184 for (var key in EventTarget) API.prototype[key] = EventTarget[key];
186 module.exports = API;