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