PageRenderTime 33ms CodeModel.GetById 15ms app.highlight 13ms RepoModel.GetById 2ms app.codeStats 0ms

/web_socket.js

http://github.com/gimite/web-socket-js
JavaScript | 398 lines | 265 code | 32 blank | 101 comment | 81 complexity | 01c67ea19bc32dad426de3dbce1eb90e MD5 | raw file
  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 (window.MessageEvent && typeof(MessageEvent) == "function" && !window.opera) {
207      return new MessageEvent("message", {
208        "view": window,
209        "bubbles": false,
210        "cancelable": false,
211        "data": data
212      });
213    } else if (document.createEvent && window.MessageEvent && !window.opera) {
214      var event = document.createEvent("MessageEvent");
215    	event.initMessageEvent("message", false, false, data, null, null, window, null);
216      return event;
217    } else {
218      // Old IE and Opera, the latter one truncates the data parameter after any 0x00 bytes.
219      return {type: type, data: data, bubbles: false, cancelable: false};
220    }
221  };
222  
223  /**
224   * Define the WebSocket readyState enumeration.
225   */
226  WebSocket.CONNECTING = 0;
227  WebSocket.OPEN = 1;
228  WebSocket.CLOSING = 2;
229  WebSocket.CLOSED = 3;
230
231  // Field to check implementation of WebSocket.
232  WebSocket.__isFlashImplementation = true;
233  WebSocket.__initialized = false;
234  WebSocket.__flash = null;
235  WebSocket.__instances = {};
236  WebSocket.__tasks = [];
237  WebSocket.__nextId = 0;
238  
239  /**
240   * Load a new flash security policy file.
241   * @param {string} url
242   */
243  WebSocket.loadFlashPolicyFile = function(url){
244    WebSocket.__addTask(function() {
245      WebSocket.__flash.loadManualPolicyFile(url);
246    });
247  };
248
249  /**
250   * Loads WebSocketMain.swf and creates WebSocketMain object in Flash.
251   */
252  WebSocket.__initialize = function() {
253    
254    if (WebSocket.__initialized) return;
255    WebSocket.__initialized = true;
256    
257    if (WebSocket.__swfLocation) {
258      // For backword compatibility.
259      window.WEB_SOCKET_SWF_LOCATION = WebSocket.__swfLocation;
260    }
261    if (!window.WEB_SOCKET_SWF_LOCATION) {
262      logger.error("[WebSocket] set WEB_SOCKET_SWF_LOCATION to location of WebSocketMain.swf");
263      return;
264    }
265    if (!window.WEB_SOCKET_SUPPRESS_CROSS_DOMAIN_SWF_ERROR &&
266        !WEB_SOCKET_SWF_LOCATION.match(/(^|\/)WebSocketMainInsecure\.swf(\?.*)?$/) &&
267        WEB_SOCKET_SWF_LOCATION.match(/^\w+:\/\/([^\/]+)/)) {
268      var swfHost = RegExp.$1;
269      if (location.host != swfHost) {
270        logger.error(
271            "[WebSocket] You must host HTML and WebSocketMain.swf in the same host " +
272            "('" + location.host + "' != '" + swfHost + "'). " +
273            "See also 'How to host HTML file and SWF file in different domains' section " +
274            "in README.md. If you use WebSocketMainInsecure.swf, you can suppress this message " +
275            "by WEB_SOCKET_SUPPRESS_CROSS_DOMAIN_SWF_ERROR = true;");
276      }
277    }
278    var container = document.createElement("div");
279    container.id = "webSocketContainer";
280    // Hides Flash box. We cannot use display: none or visibility: hidden because it prevents
281    // Flash from loading at least in IE. So we move it out of the screen at (-100, -100).
282    // But this even doesn't work with Flash Lite (e.g. in Droid Incredible). So with Flash
283    // Lite, we put it at (0, 0). This shows 1x1 box visible at left-top corner but this is
284    // the best we can do as far as we know now.
285    container.style.position = "absolute";
286    if (WebSocket.__isFlashLite()) {
287      container.style.left = "0px";
288      container.style.top = "0px";
289    } else {
290      container.style.left = "-100px";
291      container.style.top = "-100px";
292    }
293    var holder = document.createElement("div");
294    holder.id = "webSocketFlash";
295    container.appendChild(holder);
296    document.body.appendChild(container);
297    // See this article for hasPriority:
298    // http://help.adobe.com/en_US/as3/mobile/WS4bebcd66a74275c36cfb8137124318eebc6-7ffd.html
299    swfobject.embedSWF(
300      WEB_SOCKET_SWF_LOCATION,
301      "webSocketFlash",
302      "1" /* width */,
303      "1" /* height */,
304      "10.0.0" /* SWF version */,
305      null,
306      null,
307      {hasPriority: true, swliveconnect : true, allowScriptAccess: "always"},
308      null,
309      function(e) {
310        if (!e.success) {
311          logger.error("[WebSocket] swfobject.embedSWF failed");
312        }
313      }
314    );
315    
316  };
317  
318  /**
319   * Called by Flash to notify JS that it's fully loaded and ready
320   * for communication.
321   */
322  WebSocket.__onFlashInitialized = function() {
323    // We need to set a timeout here to avoid round-trip calls
324    // to flash during the initialization process.
325    setTimeout(function() {
326      WebSocket.__flash = document.getElementById("webSocketFlash");
327      WebSocket.__flash.setCallerUrl(location.href);
328      WebSocket.__flash.setDebug(!!window.WEB_SOCKET_DEBUG);
329      for (var i = 0; i < WebSocket.__tasks.length; ++i) {
330        WebSocket.__tasks[i]();
331      }
332      WebSocket.__tasks = [];
333    }, 0);
334  };
335  
336  /**
337   * Called by Flash to notify WebSockets events are fired.
338   */
339  WebSocket.__onFlashEvent = function() {
340    setTimeout(function() {
341      try {
342        // Gets events using receiveEvents() instead of getting it from event object
343        // of Flash event. This is to make sure to keep message order.
344        // It seems sometimes Flash events don't arrive in the same order as they are sent.
345        var events = WebSocket.__flash.receiveEvents();
346        for (var i = 0; i < events.length; ++i) {
347          WebSocket.__instances[events[i].webSocketId].__handleEvent(events[i]);
348        }
349      } catch (e) {
350        logger.error(e);
351      }
352    }, 0);
353    return true;
354  };
355  
356  // Called by Flash.
357  WebSocket.__log = function(message) {
358    logger.log(decodeURIComponent(message));
359  };
360  
361  // Called by Flash.
362  WebSocket.__error = function(message) {
363    logger.error(decodeURIComponent(message));
364  };
365  
366  WebSocket.__addTask = function(task) {
367    if (WebSocket.__flash) {
368      task();
369    } else {
370      WebSocket.__tasks.push(task);
371    }
372  };
373  
374  /**
375   * Test if the browser is running flash lite.
376   * @return {boolean} True if flash lite is running, false otherwise.
377   */
378  WebSocket.__isFlashLite = function() {
379    if (!window.navigator || !window.navigator.mimeTypes) {
380      return false;
381    }
382    var mimeType = window.navigator.mimeTypes["application/x-shockwave-flash"];
383    if (!mimeType || !mimeType.enabledPlugin || !mimeType.enabledPlugin.filename) {
384      return false;
385    }
386    return mimeType.enabledPlugin.filename.match(/flashlite/i) ? true : false;
387  };
388  
389  if (!window.WEB_SOCKET_DISABLE_AUTO_INITIALIZATION) {
390    // NOTE:
391    //   This fires immediately if web_socket.js is dynamically loaded after
392    //   the document is loaded.
393    swfobject.addDomLoadEvent(function() {
394      WebSocket.__initialize();
395    });
396  }
397  
398})();