| '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; |