socket.js 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. /*
  2. Copyright (c) 2004-2012, The Dojo Foundation All Rights Reserved.
  3. Available via Academic Free License >= 2.1 OR the modified BSD license.
  4. see: http://dojotoolkit.org/license for details
  5. */
  6. if(!dojo._hasResource["dojox.socket"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
  7. dojo._hasResource["dojox.socket"] = true;
  8. dojo.provide("dojox.socket");
  9. dojo.require("dojo.cookie");
  10. var WebSocket = window.WebSocket;
  11. function Socket(/*dojo.__XhrArgs*/ argsOrUrl){
  12. // summary:
  13. // Provides a simple socket connection using WebSocket, or alternate
  14. // communication mechanisms in legacy browsers for comet-style communication. This is based
  15. // on the WebSocket API and returns an object that implements the WebSocket interface:
  16. // http://dev.w3.org/html5/websockets/#websocket
  17. // description:
  18. // Provides socket connections. This can be used with virtually any Comet protocol.
  19. // argsOrUrl:
  20. // This uses the same arguments as the other I/O functions in Dojo, or a
  21. // URL to connect to. The URL should be a relative URL in order to properly
  22. // work with WebSockets (it can still be host relative, like //other-site.org/endpoint)
  23. // returns:
  24. // An object that implements the WebSocket API
  25. // example:
  26. // | dojo.require("dojox.socket");
  27. // | var socket = dojox.socket({"//comet-server/comet");
  28. // | // we could also add auto-reconnect support
  29. // | // now we can connect to standard HTML5 WebSocket-style events
  30. // | dojo.connect(socket, "onmessage", function(event){
  31. // | var message = event.data;
  32. // | // do something with the message
  33. // | });
  34. // | // send something
  35. // | socket.send("hi there");
  36. // | whenDone(function(){
  37. // | socket.close();
  38. // | });
  39. // You can also use the Reconnect module:
  40. // | dojo.require("dojox.socket");
  41. // | dojo.require("dojox.socket.Reconnect");
  42. // | var socket = dojox.socket({url:"/comet"});
  43. // | // add auto-reconnect support
  44. // | socket = dojox.socket.Reconnect(socket);
  45. if(typeof argsOrUrl == "string"){
  46. argsOrUrl = {url: argsOrUrl};
  47. }
  48. return WebSocket ? dojox.socket.WebSocket(argsOrUrl, true) : dojox.socket.LongPoll(argsOrUrl);
  49. };
  50. dojox.socket = Socket;
  51. Socket.WebSocket = function(args, fallback){
  52. // summary:
  53. // A wrapper for WebSocket, than handles standard args and relative URLs
  54. var ws = new WebSocket(new dojo._Url(document.baseURI.replace(/^http/i,'ws'), args.url));
  55. ws.on = function(type, listener){
  56. ws.addEventListener(type, listener, true);
  57. };
  58. var opened;
  59. dojo.connect(ws, "onopen", function(event){
  60. opened = true;
  61. });
  62. dojo.connect(ws, "onclose", function(event){
  63. if(opened){
  64. return;
  65. }
  66. if(fallback){
  67. Socket.replace(ws, dojox.socket.LongPoll(args), true);
  68. }
  69. });
  70. return ws;
  71. };
  72. Socket.replace = function(socket, newSocket, listenForOpen){
  73. // make the original socket a proxy for the new socket
  74. socket.send = dojo.hitch(newSocket, "send");
  75. socket.close = dojo.hitch(newSocket, "close");
  76. if(listenForOpen){
  77. proxyEvent("open");
  78. }
  79. // redirect the events as well
  80. dojo.forEach(["message", "close", "error"], proxyEvent);
  81. function proxyEvent(type){
  82. (newSocket.addEventListener || newSocket.on).call(newSocket, type, function(event){
  83. var newEvent = document.createEvent("MessageEvent");
  84. newEvent.initMessageEvent(event.type, false, false, event.data, event.origin, event.lastEventId, event.source);
  85. socket.dispatchEvent(newEvent);
  86. }, true);
  87. }
  88. };
  89. Socket.LongPoll = function(/*dojo.__XhrArgs*/ args){
  90. // summary:
  91. // Provides a simple long-poll based comet-style socket/connection to a server and returns an
  92. // object implementing the WebSocket interface:
  93. // http://dev.w3.org/html5/websockets/#websocket
  94. // args:
  95. // This uses the same arguments as the other I/O functions in Dojo, with this addition:
  96. // args.interval:
  97. // Indicates the amount of time (in milliseconds) after a response was received
  98. // before another request is made. By default, a request is made immediately
  99. // after getting a response. The interval can be increased to reduce load on the
  100. // server or to do simple time-based polling where the server always responds
  101. // immediately.
  102. // args.transport:
  103. // Provide an alternate transport like dojo.io.script.get
  104. // returns:
  105. // An object that implements the WebSocket API
  106. // example:
  107. // | dojo.require("dojox.socket.LongPoll");
  108. // | var socket = dojox.socket.LongPoll({url:"/comet"});
  109. // or:
  110. // | dojo.require("dojox.socket.LongPoll");
  111. // | dojox.socket.LongPoll.add();
  112. // | var socket = dojox.socket({url:"/comet"});
  113. var cancelled = false,
  114. first = true,
  115. timeoutId,
  116. connections = [];
  117. // create the socket object
  118. var socket = {
  119. send: function(data){
  120. // summary:
  121. // Send some data using XHR or provided transport
  122. var sendArgs = dojo.delegate(args);
  123. sendArgs.rawBody = data;
  124. clearTimeout(timeoutId);
  125. var deferred = first ? (first = false) || socket.firstRequest(sendArgs) :
  126. socket.transport(sendArgs);
  127. connections.push(deferred);
  128. deferred.then(function(response){
  129. // got a response
  130. socket.readyState = 1;
  131. // remove the current connection
  132. connections.splice(dojo.indexOf(connections, deferred), 1);
  133. // reconnect to listen for the next message if there are no active connections,
  134. // we queue it up in case one of the onmessage handlers has a message to send
  135. if(!connections.length){
  136. timeoutId = setTimeout(connect, args.interval);
  137. }
  138. if(response){
  139. // now send the message along to listeners
  140. fire("message", {data: response}, deferred);
  141. }
  142. }, function(error){
  143. connections.splice(dojo.indexOf(connections, deferred), 1);
  144. // an error occurred, fire the appropriate event listeners
  145. if(!cancelled){
  146. fire("error", {error:error}, deferred);
  147. if(!connections.length){
  148. socket.readyState = 3;
  149. fire("close", {wasClean:false}, deferred);
  150. }
  151. }
  152. });
  153. return deferred;
  154. },
  155. close: function(){
  156. // summary:
  157. // Close the connection
  158. socket.readyState = 2;
  159. cancelled = true;
  160. for(var i = 0; i < connections.length; i++){
  161. connections[i].cancel();
  162. }
  163. socket.readyState = 3;
  164. fire("close", {wasClean:true});
  165. },
  166. transport: args.transport || dojo.xhrPost,
  167. args: args,
  168. url: args.url,
  169. readyState: 0,
  170. CONNECTING: 0,
  171. OPEN: 1,
  172. CLOSING: 2,
  173. CLOSED: 3,
  174. dispatchEvent: function(event){
  175. fire(event.type, event);
  176. },
  177. on: function(type, callback){
  178. return dojo.connect(this, "on" + type, callback);
  179. },
  180. firstRequest: function(args){
  181. // summary:
  182. // This allows for special handling for the first request. This is useful for
  183. // providing information to disambiguate between the first request and
  184. // subsequent long-poll requests so the server can properly setup a
  185. // connection on the first connection or reject a request for an expired
  186. // connection if the request is not expecting to be the first for a connection.
  187. // This method can be overriden. The default behavior is to include a Pragma
  188. // header with a value of "start-long-poll"
  189. var headers = (args.headers || (args.headers = {}));
  190. headers.Pragma = "start-long-poll";
  191. try{
  192. return this.transport(args);
  193. }finally{
  194. // cleanup the header so it is not used on subsequent requests
  195. delete headers.Pragma;
  196. }
  197. }
  198. };
  199. function connect(){
  200. if(socket.readyState == 0){
  201. // we fire the open event now because we really don't know when the "socket"
  202. // is truly open, and this gives us a to do a send() and get it included in the
  203. // HTTP request
  204. fire("open",{});
  205. }
  206. // make the long-poll connection, to wait for response from the server
  207. if(!connections.length){
  208. socket.send();
  209. }
  210. }
  211. function fire(type, object, deferred){
  212. if(socket["on" + type]){
  213. var event = document.createEvent("HTMLEvents");
  214. event.initEvent(type, false, false);
  215. dojo.mixin(event, object);
  216. event.ioArgs = deferred && deferred.ioArgs;
  217. socket["on" + type](event);
  218. }
  219. }
  220. // provide an alias for Dojo's connect method
  221. socket.connect = socket.on;
  222. // do the initial connection
  223. setTimeout(connect);
  224. return socket;
  225. };
  226. }