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