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;
97 perMessageDeflate: this.perMessageDeflate
100 // SSL options for Node.js client
103 opts.passphrase = this.passphrase;
104 opts.cert = this.cert;
106 opts.ciphers = this.ciphers;
107 opts.rejectUnauthorized = this.rejectUnauthorized;
108 if (this.extraHeaders) {
109 opts.headers = this.extraHeaders;
111 if (this.localAddress) {
112 opts.localAddress = this.localAddress;
117 this.usingBrowserWebSocket && !this.isReactNative
119 ? new WebSocketImpl(uri, protocols)
120 : new WebSocketImpl(uri)
121 : new WebSocketImpl(uri, protocols, opts);
123 return this.emit('error', err);
126 if (this.ws.binaryType === undefined) {
127 this.supportsBinary = false;
130 if (this.ws.supports && this.ws.supports.binary) {
131 this.supportsBinary = true;
132 this.ws.binaryType = 'nodebuffer';
134 this.ws.binaryType = 'arraybuffer';
137 this.addEventListeners();
141 * Adds event listeners to the socket
146 WS.prototype.addEventListeners = function () {
149 this.ws.onopen = function () {
152 this.ws.onclose = function () {
155 this.ws.onmessage = function (ev) {
156 self.onData(ev.data);
158 this.ws.onerror = function (e) {
159 self.onError('websocket error', e);
164 * Writes data to socket.
166 * @param {Array} array of packets.
170 WS.prototype.write = function (packets) {
172 this.writable = false;
174 // encodePacket efficient as it uses WS framing
175 // no need for encodePayload
176 var total = packets.length;
177 for (var i = 0, l = total; i < l; i++) {
179 parser.encodePacket(packet, self.supportsBinary, function (data) {
180 if (!self.usingBrowserWebSocket) {
181 // always create a new object (GH-437)
183 if (packet.options) {
184 opts.compress = packet.options.compress;
187 if (self.perMessageDeflate) {
188 var len = 'string' === typeof data ? Buffer.byteLength(data) : data.length;
189 if (len < self.perMessageDeflate.threshold) {
190 opts.compress = false;
195 // Sometimes the websocket has already been closed but the browser didn't
196 // have a chance of informing us about it yet, in that case send will
199 if (self.usingBrowserWebSocket) {
200 // TypeError is thrown when passing the second argument on Safari
203 self.ws.send(data, opts);
206 debug('websocket closed before onclose event');
218 // defer to next tick to allow Socket to clear writeBuffer
219 setTimeout(function () {
220 self.writable = true;
232 WS.prototype.onClose = function () {
233 Transport.prototype.onClose.call(this);
242 WS.prototype.doClose = function () {
243 if (typeof this.ws !== 'undefined') {
249 * Generates uri for connection.
254 WS.prototype.uri = function () {
255 var query = this.query || {};
256 var schema = this.secure ? 'wss' : 'ws';
259 // avoid port if default for schema
260 if (this.port && (('wss' === schema && Number(this.port) !== 443) ||
261 ('ws' === schema && Number(this.port) !== 80))) {
262 port = ':' + this.port;
265 // append timestamp to URI
266 if (this.timestampRequests) {
267 query[this.timestampParam] = yeast();
270 // communicate binary support capabilities
271 if (!this.supportsBinary) {
275 query = parseqs.encode(query);
277 // prepend ? to query
282 var ipv6 = this.hostname.indexOf(':') !== -1;
283 return schema + '://' + (ipv6 ? '[' + this.hostname + ']' : this.hostname) + port + this.path + query;
287 * Feature detection for WebSocket.
289 * @return {Boolean} whether this transport is available.
293 WS.prototype.check = function () {
294 return !!WebSocketImpl && !('__initialize' in WebSocketImpl && this.name === WS.prototype.name);