5 var Transport = require('../transport');
6 var parser = require('engine.io-parser');
7 var parseqs = require('parseqs');
8 var inherit = require('component-inherit');
9 var yeast = require('yeast');
10 var debug = require('debug')('engine.io-client:websocket');
12 var BrowserWebSocket, NodeWebSocket;
14 if (typeof WebSocket !== 'undefined') {
15 BrowserWebSocket = WebSocket;
16 } else if (typeof self !== 'undefined') {
17 BrowserWebSocket = self.WebSocket || self.MozWebSocket;
20 if (typeof window === 'undefined') {
22 NodeWebSocket = require('ws');
27 * Get either the `WebSocket` or `MozWebSocket` globals
28 * in the browser or try to resolve WebSocket-compatible
29 * interface exposed by `ws` for Node-like environment.
32 var WebSocketImpl = BrowserWebSocket || NodeWebSocket;
41 * WebSocket transport constructor.
43 * @api {Object} connection options
48 var forceBase64 = (opts && opts.forceBase64);
50 this.supportsBinary = false;
52 this.perMessageDeflate = opts.perMessageDeflate;
53 this.usingBrowserWebSocket = BrowserWebSocket && !opts.forceNode;
54 this.protocols = opts.protocols;
55 if (!this.usingBrowserWebSocket) {
56 WebSocketImpl = NodeWebSocket;
58 Transport.call(this, opts);
62 * Inherits from Transport.
65 inherit(WS, Transport);
73 WS.prototype.name = 'websocket';
76 * WebSockets support binary
79 WS.prototype.supportsBinary = true;
87 WS.prototype.doOpen = function () {
94 var protocols = this.protocols;
98 if (!this.isReactNative) {
99 opts.agent = this.agent;
100 opts.perMessageDeflate = this.perMessageDeflate;
102 // SSL options for Node.js client
105 opts.passphrase = this.passphrase;
106 opts.cert = this.cert;
108 opts.ciphers = this.ciphers;
109 opts.rejectUnauthorized = this.rejectUnauthorized;
112 if (this.extraHeaders) {
113 opts.headers = this.extraHeaders;
115 if (this.localAddress) {
116 opts.localAddress = this.localAddress;
121 this.usingBrowserWebSocket && !this.isReactNative
123 ? new WebSocketImpl(uri, protocols)
124 : new WebSocketImpl(uri)
125 : new WebSocketImpl(uri, protocols, opts);
127 return this.emit('error', err);
130 if (this.ws.binaryType === undefined) {
131 this.supportsBinary = false;
134 if (this.ws.supports && this.ws.supports.binary) {
135 this.supportsBinary = true;
136 this.ws.binaryType = 'nodebuffer';
138 this.ws.binaryType = 'arraybuffer';
141 this.addEventListeners();
145 * Adds event listeners to the socket
150 WS.prototype.addEventListeners = function () {
153 this.ws.onopen = function () {
156 this.ws.onclose = function () {
159 this.ws.onmessage = function (ev) {
160 self.onData(ev.data);
162 this.ws.onerror = function (e) {
163 self.onError('websocket error', e);
168 * Writes data to socket.
170 * @param {Array} array of packets.
174 WS.prototype.write = function (packets) {
176 this.writable = false;
178 // encodePacket efficient as it uses WS framing
179 // no need for encodePayload
180 var total = packets.length;
181 for (var i = 0, l = total; i < l; i++) {
183 parser.encodePacket(packet, self.supportsBinary, function (data) {
184 if (!self.usingBrowserWebSocket) {
185 // always create a new object (GH-437)
187 if (packet.options) {
188 opts.compress = packet.options.compress;
191 if (self.perMessageDeflate) {
192 var len = 'string' === typeof data ? Buffer.byteLength(data) : data.length;
193 if (len < self.perMessageDeflate.threshold) {
194 opts.compress = false;
199 // Sometimes the websocket has already been closed but the browser didn't
200 // have a chance of informing us about it yet, in that case send will
203 if (self.usingBrowserWebSocket) {
204 // TypeError is thrown when passing the second argument on Safari
207 self.ws.send(data, opts);
210 debug('websocket closed before onclose event');
222 // defer to next tick to allow Socket to clear writeBuffer
223 setTimeout(function () {
224 self.writable = true;
236 WS.prototype.onClose = function () {
237 Transport.prototype.onClose.call(this);
246 WS.prototype.doClose = function () {
247 if (typeof this.ws !== 'undefined') {
253 * Generates uri for connection.
258 WS.prototype.uri = function () {
259 var query = this.query || {};
260 var schema = this.secure ? 'wss' : 'ws';
263 // avoid port if default for schema
264 if (this.port && (('wss' === schema && Number(this.port) !== 443) ||
265 ('ws' === schema && Number(this.port) !== 80))) {
266 port = ':' + this.port;
269 // append timestamp to URI
270 if (this.timestampRequests) {
271 query[this.timestampParam] = yeast();
274 // communicate binary support capabilities
275 if (!this.supportsBinary) {
279 query = parseqs.encode(query);
281 // prepend ? to query
286 var ipv6 = this.hostname.indexOf(':') !== -1;
287 return schema + '://' + (ipv6 ? '[' + this.hostname + ']' : this.hostname) + port + this.path + query;
291 * Feature detection for WebSocket.
293 * @return {Boolean} whether this transport is available.
297 WS.prototype.check = function () {
298 return !!WebSocketImpl && !('__initialize' in WebSocketImpl && this.name === WS.prototype.name);