web_socket.js 12 KB

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