var Stream = require('stream').Stream,
|
|
util = require('util'),
|
|
driver = require('websocket-driver'),
|
|
EventTarget = require('./api/event_target'),
|
|
Event = require('./api/event');
|
|
|
|
var API = function(options) {
|
|
options = options || {};
|
|
driver.validateOptions(options, ['headers', 'extensions', 'maxLength', 'ping', 'proxy', 'tls', 'ca']);
|
|
|
|
this.readable = this.writable = true;
|
|
|
|
var headers = options.headers;
|
|
if (headers) {
|
|
for (var name in headers) this._driver.setHeader(name, headers[name]);
|
|
}
|
|
|
|
var extensions = options.extensions;
|
|
if (extensions) {
|
|
[].concat(extensions).forEach(this._driver.addExtension, this._driver);
|
|
}
|
|
|
|
this._ping = options.ping;
|
|
this._pingId = 0;
|
|
this.readyState = API.CONNECTING;
|
|
this.bufferedAmount = 0;
|
|
this.protocol = '';
|
|
this.url = this._driver.url;
|
|
this.version = this._driver.version;
|
|
|
|
var self = this;
|
|
|
|
this._driver.on('open', function(e) { self._open() });
|
|
this._driver.on('message', function(e) { self._receiveMessage(e.data) });
|
|
this._driver.on('close', function(e) { self._beginClose(e.reason, e.code) });
|
|
|
|
this._driver.on('error', function(error) {
|
|
self._emitError(error.message);
|
|
});
|
|
this.on('error', function() {});
|
|
|
|
this._driver.messages.on('drain', function() {
|
|
self.emit('drain');
|
|
});
|
|
|
|
if (this._ping)
|
|
this._pingTimer = setInterval(function() {
|
|
self._pingId += 1;
|
|
self.ping(self._pingId.toString());
|
|
}, this._ping * 1000);
|
|
|
|
this._configureStream();
|
|
|
|
if (!this._proxy) {
|
|
this._stream.pipe(this._driver.io);
|
|
this._driver.io.pipe(this._stream);
|
|
}
|
|
};
|
|
util.inherits(API, Stream);
|
|
|
|
API.CONNECTING = 0;
|
|
API.OPEN = 1;
|
|
API.CLOSING = 2;
|
|
API.CLOSED = 3;
|
|
|
|
var instance = {
|
|
write: function(data) {
|
|
return this.send(data);
|
|
},
|
|
|
|
end: function(data) {
|
|
if (data !== undefined) this.send(data);
|
|
this.close();
|
|
},
|
|
|
|
pause: function() {
|
|
return this._driver.messages.pause();
|
|
},
|
|
|
|
resume: function() {
|
|
return this._driver.messages.resume();
|
|
},
|
|
|
|
send: function(data) {
|
|
if (this.readyState > API.OPEN) return false;
|
|
if (!(data instanceof Buffer)) data = String(data);
|
|
return this._driver.messages.write(data);
|
|
},
|
|
|
|
ping: function(message, callback) {
|
|
if (this.readyState > API.OPEN) return false;
|
|
return this._driver.ping(message, callback);
|
|
},
|
|
|
|
close: function(code, reason) {
|
|
if (code === undefined) code = 1000;
|
|
if (reason === undefined) reason = '';
|
|
|
|
if (code !== 1000 && (code < 3000 || code > 4999))
|
|
throw new Error("Failed to execute 'close' on WebSocket: " +
|
|
"The code must be either 1000, or between 3000 and 4999. " +
|
|
code + " is neither.");
|
|
|
|
if (this.readyState !== API.CLOSED) this.readyState = API.CLOSING;
|
|
this._driver.close(reason, code);
|
|
},
|
|
|
|
_configureStream: function() {
|
|
var self = this;
|
|
|
|
this._stream.setTimeout(0);
|
|
this._stream.setNoDelay(true);
|
|
|
|
['close', 'end'].forEach(function(event) {
|
|
this._stream.on(event, function() { self._finalizeClose() });
|
|
}, this);
|
|
|
|
this._stream.on('error', function(error) {
|
|
self._emitError('Network error: ' + self.url + ': ' + error.message);
|
|
self._finalizeClose();
|
|
});
|
|
},
|
|
|
|
_open: function() {
|
|
if (this.readyState !== API.CONNECTING) return;
|
|
|
|
this.readyState = API.OPEN;
|
|
this.protocol = this._driver.protocol || '';
|
|
|
|
var event = new Event('open');
|
|
event.initEvent('open', false, false);
|
|
this.dispatchEvent(event);
|
|
},
|
|
|
|
_receiveMessage: function(data) {
|
|
if (this.readyState > API.OPEN) return false;
|
|
|
|
if (this.readable) this.emit('data', data);
|
|
|
|
var event = new Event('message', {data: data});
|
|
event.initEvent('message', false, false);
|
|
this.dispatchEvent(event);
|
|
},
|
|
|
|
_emitError: function(message) {
|
|
if (this.readyState >= API.CLOSING) return;
|
|
|
|
var event = new Event('error', {message: message});
|
|
event.initEvent('error', false, false);
|
|
this.dispatchEvent(event);
|
|
},
|
|
|
|
_beginClose: function(reason, code) {
|
|
if (this.readyState === API.CLOSED) return;
|
|
this.readyState = API.CLOSING;
|
|
this._closeParams = [reason, code];
|
|
|
|
if (this._stream) {
|
|
this._stream.end();
|
|
if (!this._stream.readable) this._finalizeClose();
|
|
}
|
|
},
|
|
|
|
_finalizeClose: function() {
|
|
if (this.readyState === API.CLOSED) return;
|
|
this.readyState = API.CLOSED;
|
|
|
|
if (this._pingTimer) clearInterval(this._pingTimer);
|
|
if (this._stream) this._stream.end();
|
|
|
|
if (this.readable) this.emit('end');
|
|
this.readable = this.writable = false;
|
|
|
|
var reason = this._closeParams ? this._closeParams[0] : '',
|
|
code = this._closeParams ? this._closeParams[1] : 1006;
|
|
|
|
var event = new Event('close', {code: code, reason: reason});
|
|
event.initEvent('close', false, false);
|
|
this.dispatchEvent(event);
|
|
}
|
|
};
|
|
|
|
for (var method in instance) API.prototype[method] = instance[method];
|
|
for (var key in EventTarget) API.prototype[key] = EventTarget[key];
|
|
|
|
module.exports = API;
|