123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199 |
- '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.CLOSING) {
- var self = this;
- this._closeTimer = setTimeout(function() {
- self._beginClose('', 1006);
- }, API.CLOSE_TIMEOUT);
- }
-
- 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.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;
|