UNPKG

nodulator

Version:

Complete NodeJS Framework for Restfull APIs

289 lines (240 loc) 6.05 kB
/** * 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 = global.WebSocket || global.MozWebSocket; /** * 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 WebSocket = BrowserWebSocket; if (!WebSocket && typeof window === 'undefined') { try { WebSocket = require('ws'); } catch (e) { } } /** * 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; 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 self = this; var uri = this.uri(); var protocols = void(0); var opts = { agent: this.agent, 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; } this.ws = BrowserWebSocket ? new WebSocket(uri) : new WebSocket(uri, protocols, opts); if (this.ws.binaryType === undefined) { this.supportsBinary = false; } if (this.ws.supports && this.ws.supports.binary) { this.supportsBinary = true; this.ws.binaryType = 'buffer'; } 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); }; }; /** * Override `onData` to use a timer on iOS. * See: https://gist.github.com/mloughran/2052006 * * @api private */ if ('undefined' != typeof navigator && /iPad|iPhone|iPod/i.test(navigator.userAgent)) { WS.prototype.onData = function(data){ var self = this; setTimeout(function(){ Transport.prototype.onData.call(self, data); }, 0); }; } /** * 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 (!BrowserWebSocket) { // 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 ? global.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 (BrowserWebSocket) { // 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 && this.port != 443) || ('ws' == schema && 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 !!WebSocket && !('__initialize' in WebSocket && this.name === WS.prototype.name); };