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