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.
|
|
/** * Module dependencies. */
var Transport = require('../transport'); var parser = require('engine.io-parser'); var parseqs = require('parseqs'); var inherit = require('component-inherit'); var yeast = require('yeast'); var debug = require('debug')('engine.io-client:websocket');
var BrowserWebSocket, NodeWebSocket;
if (typeof WebSocket !== 'undefined') { BrowserWebSocket = WebSocket; } else if (typeof self !== 'undefined') { BrowserWebSocket = self.WebSocket || self.MozWebSocket; }
if (typeof window === 'undefined') { try { NodeWebSocket = require('ws'); } catch (e) { } }
/** * Get either the `WebSocket` or `MozWebSocket` globals * in the browser or try to resolve WebSocket-compatible * interface exposed by `ws` for Node-like environment. */
var WebSocketImpl = BrowserWebSocket || NodeWebSocket;
/** * Module exports. */
module.exports = WS;
/** * WebSocket transport constructor. * * @api {Object} connection options * @api public */
function WS (opts) { var forceBase64 = (opts && opts.forceBase64); if (forceBase64) { this.supportsBinary = false; } this.perMessageDeflate = opts.perMessageDeflate; this.usingBrowserWebSocket = BrowserWebSocket && !opts.forceNode; this.protocols = opts.protocols; if (!this.usingBrowserWebSocket) { WebSocketImpl = NodeWebSocket; } Transport.call(this, opts); }
/** * Inherits from Transport. */
inherit(WS, Transport);
/** * Transport name. * * @api public */
WS.prototype.name = 'websocket';
/* * WebSockets support binary */
WS.prototype.supportsBinary = true;
/** * Opens socket. * * @api private */
WS.prototype.doOpen = function () { if (!this.check()) { // let probe timeout
return; }
var uri = this.uri(); var protocols = this.protocols;
var opts = {};
if (!this.isReactNative) { opts.agent = this.agent; opts.perMessageDeflate = this.perMessageDeflate;
// SSL options for Node.js client
opts.pfx = this.pfx; opts.key = this.key; opts.passphrase = this.passphrase; opts.cert = this.cert; opts.ca = this.ca; opts.ciphers = this.ciphers; opts.rejectUnauthorized = this.rejectUnauthorized; }
if (this.extraHeaders) { opts.headers = this.extraHeaders; } if (this.localAddress) { opts.localAddress = this.localAddress; }
try { this.ws = this.usingBrowserWebSocket && !this.isReactNative ? protocols ? new WebSocketImpl(uri, protocols) : new WebSocketImpl(uri) : new WebSocketImpl(uri, protocols, opts); } catch (err) { return this.emit('error', err); }
if (this.ws.binaryType === undefined) { this.supportsBinary = false; }
if (this.ws.supports && this.ws.supports.binary) { this.supportsBinary = true; this.ws.binaryType = 'nodebuffer'; } else { this.ws.binaryType = 'arraybuffer'; }
this.addEventListeners(); };
/** * Adds event listeners to the socket * * @api private */
WS.prototype.addEventListeners = function () { var self = this;
this.ws.onopen = function () { self.onOpen(); }; this.ws.onclose = function () { self.onClose(); }; this.ws.onmessage = function (ev) { self.onData(ev.data); }; this.ws.onerror = function (e) { self.onError('websocket error', e); }; };
/** * Writes data to socket. * * @param {Array} array of packets. * @api private */
WS.prototype.write = function (packets) { var self = this; this.writable = false;
// encodePacket efficient as it uses WS framing
// no need for encodePayload
var total = packets.length; for (var i = 0, l = total; i < l; i++) { (function (packet) { parser.encodePacket(packet, self.supportsBinary, function (data) { if (!self.usingBrowserWebSocket) { // always create a new object (GH-437)
var opts = {}; if (packet.options) { opts.compress = packet.options.compress; }
if (self.perMessageDeflate) { var len = 'string' === typeof data ? Buffer.byteLength(data) : data.length; if (len < self.perMessageDeflate.threshold) { opts.compress = false; } } }
// Sometimes the websocket has already been closed but the browser didn't
// have a chance of informing us about it yet, in that case send will
// throw an error
try { if (self.usingBrowserWebSocket) { // TypeError is thrown when passing the second argument on Safari
self.ws.send(data); } else { self.ws.send(data, opts); } } catch (e) { debug('websocket closed before onclose event'); }
--total || done(); }); })(packets[i]); }
function done () { self.emit('flush');
// fake drain
// defer to next tick to allow Socket to clear writeBuffer
setTimeout(function () { self.writable = true; self.emit('drain'); }, 0); } };
/** * Called upon close * * @api private */
WS.prototype.onClose = function () { Transport.prototype.onClose.call(this); };
/** * Closes socket. * * @api private */
WS.prototype.doClose = function () { if (typeof this.ws !== 'undefined') { this.ws.close(); } };
/** * Generates uri for connection. * * @api private */
WS.prototype.uri = function () { var query = this.query || {}; var schema = this.secure ? 'wss' : 'ws'; var port = '';
// avoid port if default for schema
if (this.port && (('wss' === schema && Number(this.port) !== 443) || ('ws' === schema && Number(this.port) !== 80))) { port = ':' + this.port; }
// append timestamp to URI
if (this.timestampRequests) { query[this.timestampParam] = yeast(); }
// communicate binary support capabilities
if (!this.supportsBinary) { query.b64 = 1; }
query = parseqs.encode(query);
// prepend ? to query
if (query.length) { query = '?' + query; }
var ipv6 = this.hostname.indexOf(':') !== -1; return schema + '://' + (ipv6 ? '[' + this.hostname + ']' : this.hostname) + port + this.path + query; };
/** * Feature detection for WebSocket. * * @return {Boolean} whether this transport is available. * @api public */
WS.prototype.check = function () { return !!WebSocketImpl && !('__initialize' in WebSocketImpl && this.name === WS.prototype.name); };
|