• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright: Hiroshi Ichikawa <http://gimite.net/en/>
2// License: New BSD License
3// Reference: http://dev.w3.org/html5/websockets/
4// Reference: http://tools.ietf.org/html/rfc6455
5
6(function() {
7
8  if (window.WEB_SOCKET_FORCE_FLASH) {
9    // Keeps going.
10  } else if (window.WebSocket) {
11    return;
12  } else if (window.MozWebSocket) {
13    // Firefox.
14    window.WebSocket = MozWebSocket;
15    return;
16  }
17
18  var logger;
19  if (window.WEB_SOCKET_LOGGER) {
20    logger = WEB_SOCKET_LOGGER;
21  } else if (window.console && window.console.log && window.console.error) {
22    // In some environment, console is defined but console.log or console.error is missing.
23    logger = window.console;
24  } else {
25    logger = {log: function(){ }, error: function(){ }};
26  }
27
28  // swfobject.hasFlashPlayerVersion("10.0.0") doesn't work with Gnash.
29  if (swfobject.getFlashPlayerVersion().major < 10) {
30    logger.error("Flash Player >= 10.0.0 is required.");
31    return;
32  }
33  if (location.protocol == "file:") {
34    logger.error(
35      "WARNING: web-socket-js doesn't work in file:///... URL " +
36      "unless you set Flash Security Settings properly. " +
37      "Open the page via Web server i.e. http://...");
38  }
39
40  /**
41   * Our own implementation of WebSocket class using Flash.
42   * @param {string} url
43   * @param {array or string} protocols
44   * @param {string} proxyHost
45   * @param {int} proxyPort
46   * @param {string} headers
47   */
48  window.WebSocket = function(url, protocols, proxyHost, proxyPort, headers) {
49    var self = this;
50    self.__id = WebSocket.__nextId++;
51    WebSocket.__instances[self.__id] = self;
52    self.readyState = WebSocket.CONNECTING;
53    self.bufferedAmount = 0;
54    self.__events = {};
55    if (!protocols) {
56      protocols = [];
57    } else if (typeof protocols == "string") {
58      protocols = [protocols];
59    }
60    // Uses setTimeout() to make sure __createFlash() runs after the caller sets ws.onopen etc.
61    // Otherwise, when onopen fires immediately, onopen is called before it is set.
62    self.__createTask = setTimeout(function() {
63      WebSocket.__addTask(function() {
64        self.__createTask = null;
65        WebSocket.__flash.create(
66            self.__id, url, protocols, proxyHost || null, proxyPort || 0, headers || null);
67      });
68    }, 0);
69  };
70
71  /**
72   * Send data to the web socket.
73   * @param {string} data  The data to send to the socket.
74   * @return {boolean}  True for success, false for failure.
75   */
76  WebSocket.prototype.send = function(data) {
77    if (this.readyState == WebSocket.CONNECTING) {
78      throw "INVALID_STATE_ERR: Web Socket connection has not been established";
79    }
80    // We use encodeURIComponent() here, because FABridge doesn't work if
81    // the argument includes some characters. We don't use escape() here
82    // because of this:
83    // https://developer.mozilla.org/en/Core_JavaScript_1.5_Guide/Functions#escape_and_unescape_Functions
84    // But it looks decodeURIComponent(encodeURIComponent(s)) doesn't
85    // preserve all Unicode characters either e.g. "\uffff" in Firefox.
86    // Note by wtritch: Hopefully this will not be necessary using ExternalInterface.  Will require
87    // additional testing.
88    var result = WebSocket.__flash.send(this.__id, encodeURIComponent(data));
89    if (result < 0) { // success
90      return true;
91    } else {
92      this.bufferedAmount += result;
93      return false;
94    }
95  };
96
97  /**
98   * Close this web socket gracefully.
99   */
100  WebSocket.prototype.close = function() {
101    if (this.__createTask) {
102      clearTimeout(this.__createTask);
103      this.__createTask = null;
104      this.readyState = WebSocket.CLOSED;
105      return;
106    }
107    if (this.readyState == WebSocket.CLOSED || this.readyState == WebSocket.CLOSING) {
108      return;
109    }
110    this.readyState = WebSocket.CLOSING;
111    WebSocket.__flash.close(this.__id);
112  };
113
114  /**
115   * Implementation of {@link <a href="http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-registration">DOM 2 EventTarget Interface</a>}
116   *
117   * @param {string} type
118   * @param {function} listener
119   * @param {boolean} useCapture
120   * @return void
121   */
122  WebSocket.prototype.addEventListener = function(type, listener, useCapture) {
123    if (!(type in this.__events)) {
124      this.__events[type] = [];
125    }
126    this.__events[type].push(listener);
127  };
128
129  /**
130   * Implementation of {@link <a href="http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-registration">DOM 2 EventTarget Interface</a>}
131   *
132   * @param {string} type
133   * @param {function} listener
134   * @param {boolean} useCapture
135   * @return void
136   */
137  WebSocket.prototype.removeEventListener = function(type, listener, useCapture) {
138    if (!(type in this.__events)) return;
139    var events = this.__events[type];
140    for (var i = events.length - 1; i >= 0; --i) {
141      if (events[i] === listener) {
142        events.splice(i, 1);
143        break;
144      }
145    }
146  };
147
148  /**
149   * Implementation of {@link <a href="http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-registration">DOM 2 EventTarget Interface</a>}
150   *
151   * @param {Event} event
152   * @return void
153   */
154  WebSocket.prototype.dispatchEvent = function(event) {
155    var events = this.__events[event.type] || [];
156    for (var i = 0; i < events.length; ++i) {
157      events[i](event);
158    }
159    var handler = this["on" + event.type];
160    if (handler) handler.apply(this, [event]);
161  };
162
163  /**
164   * Handles an event from Flash.
165   * @param {Object} flashEvent
166   */
167  WebSocket.prototype.__handleEvent = function(flashEvent) {
168
169    if ("readyState" in flashEvent) {
170      this.readyState = flashEvent.readyState;
171    }
172    if ("protocol" in flashEvent) {
173      this.protocol = flashEvent.protocol;
174    }
175
176    var jsEvent;
177    if (flashEvent.type == "open" || flashEvent.type == "error") {
178      jsEvent = this.__createSimpleEvent(flashEvent.type);
179    } else if (flashEvent.type == "close") {
180      jsEvent = this.__createSimpleEvent("close");
181      jsEvent.wasClean = flashEvent.wasClean ? true : false;
182      jsEvent.code = flashEvent.code;
183      jsEvent.reason = flashEvent.reason;
184    } else if (flashEvent.type == "message") {
185      var data = decodeURIComponent(flashEvent.message);
186      jsEvent = this.__createMessageEvent("message", data);
187    } else {
188      throw "unknown event type: " + flashEvent.type;
189    }
190
191    this.dispatchEvent(jsEvent);
192
193  };
194
195  WebSocket.prototype.__createSimpleEvent = function(type) {
196    if (document.createEvent && window.Event) {
197      var event = document.createEvent("Event");
198      event.initEvent(type, false, false);
199      return event;
200    } else {
201      return {type: type, bubbles: false, cancelable: false};
202    }
203  };
204
205  WebSocket.prototype.__createMessageEvent = function(type, data) {
206    if (document.createEvent && window.MessageEvent && !window.opera) {
207      var event = document.createEvent("MessageEvent");
208      event.initMessageEvent("message", false, false, data, null, null, window, null);
209      return event;
210    } else {
211      // IE and Opera, the latter one truncates the data parameter after any 0x00 bytes.
212      return {type: type, data: data, bubbles: false, cancelable: false};
213    }
214  };
215
216  /**
217   * Define the WebSocket readyState enumeration.
218   */
219  WebSocket.CONNECTING = 0;
220  WebSocket.OPEN = 1;
221  WebSocket.CLOSING = 2;
222  WebSocket.CLOSED = 3;
223
224  // Field to check implementation of WebSocket.
225  WebSocket.__isFlashImplementation = true;
226  WebSocket.__initialized = false;
227  WebSocket.__flash = null;
228  WebSocket.__instances = {};
229  WebSocket.__tasks = [];
230  WebSocket.__nextId = 0;
231
232  /**
233   * Load a new flash security policy file.
234   * @param {string} url
235   */
236  WebSocket.loadFlashPolicyFile = function(url){
237    WebSocket.__addTask(function() {
238      WebSocket.__flash.loadManualPolicyFile(url);
239    });
240  };
241
242  /**
243   * Loads WebSocketMain.swf and creates WebSocketMain object in Flash.
244   */
245  WebSocket.__initialize = function() {
246
247    if (WebSocket.__initialized) return;
248    WebSocket.__initialized = true;
249
250    if (WebSocket.__swfLocation) {
251      // For backword compatibility.
252      window.WEB_SOCKET_SWF_LOCATION = WebSocket.__swfLocation;
253    }
254    if (!window.WEB_SOCKET_SWF_LOCATION) {
255      logger.error("[WebSocket] set WEB_SOCKET_SWF_LOCATION to location of WebSocketMain.swf");
256      return;
257    }
258    if (!window.WEB_SOCKET_SUPPRESS_CROSS_DOMAIN_SWF_ERROR &&
259        !WEB_SOCKET_SWF_LOCATION.match(/(^|\/)WebSocketMainInsecure\.swf(\?.*)?$/) &&
260        WEB_SOCKET_SWF_LOCATION.match(/^\w+:\/\/([^\/]+)/)) {
261      var swfHost = RegExp.$1;
262      if (location.host != swfHost) {
263        logger.error(
264            "[WebSocket] You must host HTML and WebSocketMain.swf in the same host " +
265            "('" + location.host + "' != '" + swfHost + "'). " +
266            "See also 'How to host HTML file and SWF file in different domains' section " +
267            "in README.md. If you use WebSocketMainInsecure.swf, you can suppress this message " +
268            "by WEB_SOCKET_SUPPRESS_CROSS_DOMAIN_SWF_ERROR = true;");
269      }
270    }
271    var container = document.createElement("div");
272    container.id = "webSocketContainer";
273    // Hides Flash box. We cannot use display: none or visibility: hidden because it prevents
274    // Flash from loading at least in IE. So we move it out of the screen at (-100, -100).
275    // But this even doesn't work with Flash Lite (e.g. in Droid Incredible). So with Flash
276    // Lite, we put it at (0, 0). This shows 1x1 box visible at left-top corner but this is
277    // the best we can do as far as we know now.
278    container.style.position = "absolute";
279    if (WebSocket.__isFlashLite()) {
280      container.style.left = "0px";
281      container.style.top = "0px";
282    } else {
283      container.style.left = "-100px";
284      container.style.top = "-100px";
285    }
286    var holder = document.createElement("div");
287    holder.id = "webSocketFlash";
288    container.appendChild(holder);
289    document.body.appendChild(container);
290    // See this article for hasPriority:
291    // http://help.adobe.com/en_US/as3/mobile/WS4bebcd66a74275c36cfb8137124318eebc6-7ffd.html
292    swfobject.embedSWF(
293      WEB_SOCKET_SWF_LOCATION,
294      "webSocketFlash",
295      "1" /* width */,
296      "1" /* height */,
297      "10.0.0" /* SWF version */,
298      null,
299      null,
300      {hasPriority: true, swliveconnect : true, allowScriptAccess: "always"},
301      null,
302      function(e) {
303        if (!e.success) {
304          logger.error("[WebSocket] swfobject.embedSWF failed");
305        }
306      }
307    );
308
309  };
310
311  /**
312   * Called by Flash to notify JS that it's fully loaded and ready
313   * for communication.
314   */
315  WebSocket.__onFlashInitialized = function() {
316    // We need to set a timeout here to avoid round-trip calls
317    // to flash during the initialization process.
318    setTimeout(function() {
319      WebSocket.__flash = document.getElementById("webSocketFlash");
320      WebSocket.__flash.setCallerUrl(location.href);
321      WebSocket.__flash.setDebug(!!window.WEB_SOCKET_DEBUG);
322      for (var i = 0; i < WebSocket.__tasks.length; ++i) {
323        WebSocket.__tasks[i]();
324      }
325      WebSocket.__tasks = [];
326    }, 0);
327  };
328
329  /**
330   * Called by Flash to notify WebSockets events are fired.
331   */
332  WebSocket.__onFlashEvent = function() {
333    setTimeout(function() {
334      try {
335        // Gets events using receiveEvents() instead of getting it from event object
336        // of Flash event. This is to make sure to keep message order.
337        // It seems sometimes Flash events don't arrive in the same order as they are sent.
338        var events = WebSocket.__flash.receiveEvents();
339        for (var i = 0; i < events.length; ++i) {
340          WebSocket.__instances[events[i].webSocketId].__handleEvent(events[i]);
341        }
342      } catch (e) {
343        logger.error(e);
344      }
345    }, 0);
346    return true;
347  };
348
349  // Called by Flash.
350  WebSocket.__log = function(message) {
351    logger.log(decodeURIComponent(message));
352  };
353
354  // Called by Flash.
355  WebSocket.__error = function(message) {
356    logger.error(decodeURIComponent(message));
357  };
358
359  WebSocket.__addTask = function(task) {
360    if (WebSocket.__flash) {
361      task();
362    } else {
363      WebSocket.__tasks.push(task);
364    }
365  };
366
367  /**
368   * Test if the browser is running flash lite.
369   * @return {boolean} True if flash lite is running, false otherwise.
370   */
371  WebSocket.__isFlashLite = function() {
372    if (!window.navigator || !window.navigator.mimeTypes) {
373      return false;
374    }
375    var mimeType = window.navigator.mimeTypes["application/x-shockwave-flash"];
376    if (!mimeType || !mimeType.enabledPlugin || !mimeType.enabledPlugin.filename) {
377      return false;
378    }
379    return mimeType.enabledPlugin.filename.match(/flashlite/i) ? true : false;
380  };
381
382  if (!window.WEB_SOCKET_DISABLE_AUTO_INITIALIZATION) {
383    // NOTE:
384    //   This fires immediately if web_socket.js is dynamically loaded after
385    //   the document is loaded.
386    swfobject.addDomLoadEvent(function() {
387      WebSocket.__initialize();
388    });
389  }
390
391})();
392