|
|
'use strict';
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;
API.CLOSE_TIMEOUT = 30000;
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; var self = this;
this._closeTimer = setTimeout(function() { self._beginClose('', 1006); }, API.CLOSE_TIMEOUT);
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.destroy(); if (!this._stream.readable) this._finalizeClose(); } },
_finalizeClose: function() { if (this.readyState === API.CLOSED) return; this.readyState = API.CLOSED;
if (this._closeTimer) clearTimeout(this._closeTimer); 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;
|