de590bc51bf9a0a37ee10515d0168b1d0e5b4013
[platform/framework/web/wrtjs.git] /
1 /**
2  * Module dependencies.
3  */
4
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');
11
12 var BrowserWebSocket, NodeWebSocket;
13
14 if (typeof WebSocket !== 'undefined') {
15   BrowserWebSocket = WebSocket;
16 } else if (typeof self !== 'undefined') {
17   BrowserWebSocket = self.WebSocket || self.MozWebSocket;
18 }
19
20 if (typeof window === 'undefined') {
21   try {
22     NodeWebSocket = require('ws');
23   } catch (e) { }
24 }
25
26 /**
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.
30  */
31
32 var WebSocketImpl = BrowserWebSocket || NodeWebSocket;
33
34 /**
35  * Module exports.
36  */
37
38 module.exports = WS;
39
40 /**
41  * WebSocket transport constructor.
42  *
43  * @api {Object} connection options
44  * @api public
45  */
46
47 function WS (opts) {
48   var forceBase64 = (opts && opts.forceBase64);
49   if (forceBase64) {
50     this.supportsBinary = false;
51   }
52   this.perMessageDeflate = opts.perMessageDeflate;
53   this.usingBrowserWebSocket = BrowserWebSocket && !opts.forceNode;
54   this.protocols = opts.protocols;
55   if (!this.usingBrowserWebSocket) {
56     WebSocketImpl = NodeWebSocket;
57   }
58   Transport.call(this, opts);
59 }
60
61 /**
62  * Inherits from Transport.
63  */
64
65 inherit(WS, Transport);
66
67 /**
68  * Transport name.
69  *
70  * @api public
71  */
72
73 WS.prototype.name = 'websocket';
74
75 /*
76  * WebSockets support binary
77  */
78
79 WS.prototype.supportsBinary = true;
80
81 /**
82  * Opens socket.
83  *
84  * @api private
85  */
86
87 WS.prototype.doOpen = function () {
88   if (!this.check()) {
89     // let probe timeout
90     return;
91   }
92
93   var uri = this.uri();
94   var protocols = this.protocols;
95   var opts = {
96     agent: this.agent,
97     perMessageDeflate: this.perMessageDeflate
98   };
99
100   // SSL options for Node.js client
101   opts.pfx = this.pfx;
102   opts.key = this.key;
103   opts.passphrase = this.passphrase;
104   opts.cert = this.cert;
105   opts.ca = this.ca;
106   opts.ciphers = this.ciphers;
107   opts.rejectUnauthorized = this.rejectUnauthorized;
108   if (this.extraHeaders) {
109     opts.headers = this.extraHeaders;
110   }
111   if (this.localAddress) {
112     opts.localAddress = this.localAddress;
113   }
114
115   try {
116     this.ws =
117       this.usingBrowserWebSocket && !this.isReactNative
118         ? protocols
119           ? new WebSocketImpl(uri, protocols)
120           : new WebSocketImpl(uri)
121         : new WebSocketImpl(uri, protocols, opts);
122   } catch (err) {
123     return this.emit('error', err);
124   }
125
126   if (this.ws.binaryType === undefined) {
127     this.supportsBinary = false;
128   }
129
130   if (this.ws.supports && this.ws.supports.binary) {
131     this.supportsBinary = true;
132     this.ws.binaryType = 'nodebuffer';
133   } else {
134     this.ws.binaryType = 'arraybuffer';
135   }
136
137   this.addEventListeners();
138 };
139
140 /**
141  * Adds event listeners to the socket
142  *
143  * @api private
144  */
145
146 WS.prototype.addEventListeners = function () {
147   var self = this;
148
149   this.ws.onopen = function () {
150     self.onOpen();
151   };
152   this.ws.onclose = function () {
153     self.onClose();
154   };
155   this.ws.onmessage = function (ev) {
156     self.onData(ev.data);
157   };
158   this.ws.onerror = function (e) {
159     self.onError('websocket error', e);
160   };
161 };
162
163 /**
164  * Writes data to socket.
165  *
166  * @param {Array} array of packets.
167  * @api private
168  */
169
170 WS.prototype.write = function (packets) {
171   var self = this;
172   this.writable = false;
173
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++) {
178     (function (packet) {
179       parser.encodePacket(packet, self.supportsBinary, function (data) {
180         if (!self.usingBrowserWebSocket) {
181           // always create a new object (GH-437)
182           var opts = {};
183           if (packet.options) {
184             opts.compress = packet.options.compress;
185           }
186
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;
191             }
192           }
193         }
194
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
197         // throw an error
198         try {
199           if (self.usingBrowserWebSocket) {
200             // TypeError is thrown when passing the second argument on Safari
201             self.ws.send(data);
202           } else {
203             self.ws.send(data, opts);
204           }
205         } catch (e) {
206           debug('websocket closed before onclose event');
207         }
208
209         --total || done();
210       });
211     })(packets[i]);
212   }
213
214   function done () {
215     self.emit('flush');
216
217     // fake drain
218     // defer to next tick to allow Socket to clear writeBuffer
219     setTimeout(function () {
220       self.writable = true;
221       self.emit('drain');
222     }, 0);
223   }
224 };
225
226 /**
227  * Called upon close
228  *
229  * @api private
230  */
231
232 WS.prototype.onClose = function () {
233   Transport.prototype.onClose.call(this);
234 };
235
236 /**
237  * Closes socket.
238  *
239  * @api private
240  */
241
242 WS.prototype.doClose = function () {
243   if (typeof this.ws !== 'undefined') {
244     this.ws.close();
245   }
246 };
247
248 /**
249  * Generates uri for connection.
250  *
251  * @api private
252  */
253
254 WS.prototype.uri = function () {
255   var query = this.query || {};
256   var schema = this.secure ? 'wss' : 'ws';
257   var port = '';
258
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;
263   }
264
265   // append timestamp to URI
266   if (this.timestampRequests) {
267     query[this.timestampParam] = yeast();
268   }
269
270   // communicate binary support capabilities
271   if (!this.supportsBinary) {
272     query.b64 = 1;
273   }
274
275   query = parseqs.encode(query);
276
277   // prepend ? to query
278   if (query.length) {
279     query = '?' + query;
280   }
281
282   var ipv6 = this.hostname.indexOf(':') !== -1;
283   return schema + '://' + (ipv6 ? '[' + this.hostname + ']' : this.hostname) + port + this.path + query;
284 };
285
286 /**
287  * Feature detection for WebSocket.
288  *
289  * @return {Boolean} whether this transport is available.
290  * @api public
291  */
292
293 WS.prototype.check = function () {
294   return !!WebSocketImpl && !('__initialize' in WebSocketImpl && this.name === WS.prototype.name);
295 };