You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
199 lines
5.3 KiB
199 lines
5.3 KiB
'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;
|
|
|