socket.js 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. define("dojox/socket", [
  2. "dojo/_base/array",
  3. "dojo/_base/lang",
  4. "dojo/_base/xhr",
  5. "dojo/aspect",
  6. "dojo/on",
  7. "dojo/Evented",
  8. "dojo/_base/url"
  9. ], function(array, lang, xhr, aspect, on, Evented, dBaseUrl) {
  10. var WebSocket = window.WebSocket;
  11. var Socket = function(/*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. // | require(["dojox/socket", "dojo/aspect"], function(socket, aspect) {
  27. // | var sock = socket({"url://comet-server/comet");
  28. // | // we could also add auto-reconnect support
  29. // | // now we can connect to standard HTML5 WebSocket-style events
  30. // | aspect.after(socket, "onmessage", function(event){
  31. // | var message = event.data;
  32. // | // do something with the message
  33. // | });
  34. // | // send something
  35. // | sock.send("hi there");
  36. // | ...
  37. // | });
  38. // You can also use the Reconnect module:
  39. // | require["dojox/socket", "dojox/socket/Reconnect"], function(dxSocket, reconnect){
  40. // | var socket = dxSocket({url:"/comet"});
  41. // | // add auto-reconnect support
  42. // | socket = reconnect(socket);
  43. if(typeof argsOrUrl == "string"){
  44. argsOrUrl = {url: argsOrUrl};
  45. }
  46. return WebSocket ? Socket.WebSocket(argsOrUrl, true) : Socket.LongPoll(argsOrUrl);
  47. };
  48. Socket.WebSocket = function(args, fallback){
  49. // summary:
  50. // A wrapper for WebSocket, than handles standard args and relative URLs
  51. var baseURI = document.baseURI || window.location.href;
  52. var ws = new WebSocket(new dBaseUrl(baseURI.replace(/^http/i,'ws'), args.url));
  53. ws.on = function(type, listener){
  54. ws.addEventListener(type, listener, true);
  55. };
  56. var opened;
  57. aspect.after(ws, "onopen", function(event){
  58. opened = true;
  59. }, true);
  60. aspect.after(ws, "onclose", function(event){
  61. if(opened){
  62. return;
  63. }
  64. if(fallback){
  65. Socket.replace(ws, Socket.LongPoll(args), true);
  66. }
  67. }, true);
  68. return ws;
  69. };
  70. Socket.replace = function(socket, newSocket, listenForOpen){
  71. // make the original socket a proxy for the new socket
  72. socket.send = lang.hitch(newSocket, "send");
  73. socket.close = lang.hitch(newSocket, "close");
  74. var proxyEvent = function(type){
  75. (newSocket.addEventListener || newSocket.on).call(newSocket, type, function(event){
  76. on.emit(socket, event.type, event);
  77. }, true);
  78. };
  79. if(listenForOpen){
  80. proxyEvent("open");
  81. }
  82. // redirect the events as well
  83. array.forEach(["message", "close", "error"], proxyEvent);
  84. };
  85. Socket.LongPoll = function(/*dojo.__XhrArgs*/ args){
  86. // summary:
  87. // Provides a simple long-poll based comet-style socket/connection to a server and returns an
  88. // object implementing the WebSocket interface:
  89. // http://dev.w3.org/html5/websockets/#websocket
  90. // args:
  91. // This uses the same arguments as the other I/O functions in Dojo, with this addition:
  92. // args.interval:
  93. // Indicates the amount of time (in milliseconds) after a response was received
  94. // before another request is made. By default, a request is made immediately
  95. // after getting a response. The interval can be increased to reduce load on the
  96. // server or to do simple time-based polling where the server always responds
  97. // immediately.
  98. // args.transport:
  99. // Provide an alternate transport like dojo.io.script.get
  100. // returns:
  101. // An object that implements the WebSocket API
  102. // example:
  103. // | dojo.require("dojox.socket.LongPoll");
  104. // | var socket = dojox.socket.LongPoll({url:"/comet"});
  105. // or:
  106. // | dojo.require("dojox.socket.LongPoll");
  107. // | dojox.socket.LongPoll.add();
  108. // | var socket = dojox.socket({url:"/comet"});
  109. var cancelled = false,
  110. first = true,
  111. timeoutId,
  112. connections = [];
  113. // create the socket object
  114. var fire, connect;
  115. var socket = {
  116. send: function(data){
  117. // summary:
  118. // Send some data using XHR or provided transport
  119. var sendArgs = lang.delegate(args);
  120. sendArgs.rawBody = data;
  121. clearTimeout(timeoutId);
  122. var deferred = first ? (first = false) || socket.firstRequest(sendArgs) :
  123. socket.transport(sendArgs);
  124. connections.push(deferred);
  125. deferred.then(function(response){
  126. // got a response
  127. socket.readyState = 1;
  128. // remove the current connection
  129. connections.splice(array.indexOf(connections, deferred), 1);
  130. // reconnect to listen for the next message if there are no active connections,
  131. // we queue it up in case one of the onmessage handlers has a message to send
  132. if(!connections.length){
  133. timeoutId = setTimeout(connect, args.interval);
  134. }
  135. if(response){
  136. // now send the message along to listeners
  137. fire("message", {data: response}, deferred);
  138. }
  139. }, function(error){
  140. connections.splice(array.indexOf(connections, deferred), 1);
  141. // an error occurred, fire the appropriate event listeners
  142. if(!cancelled){
  143. fire("error", {error:error}, deferred);
  144. if(!connections.length){
  145. socket.readyState = 3;
  146. fire("close", {wasClean:false}, deferred);
  147. }
  148. }
  149. });
  150. return deferred;
  151. },
  152. close: function(){
  153. // summary:
  154. // Close the connection
  155. socket.readyState = 2;
  156. cancelled = true;
  157. var i;
  158. for(i = 0; i < connections.length; i++){
  159. connections[i].cancel();
  160. }
  161. socket.readyState = 3;
  162. fire("close", {wasClean:true});
  163. },
  164. transport: args.transport || xhr.post,
  165. args: args,
  166. url: args.url,
  167. readyState: 0,
  168. CONNECTING: 0,
  169. OPEN: 1,
  170. CLOSING: 2,
  171. CLOSED: 3,
  172. on: Evented.prototype.on,
  173. firstRequest: function(args){
  174. // summary:
  175. // This allows for special handling for the first request. This is useful for
  176. // providing information to disambiguate between the first request and
  177. // subsequent long-poll requests so the server can properly setup a
  178. // connection on the first connection or reject a request for an expired
  179. // connection if the request is not expecting to be the first for a connection.
  180. // This method can be overriden. The default behavior is to include a Pragma
  181. // header with a value of "start-long-poll"
  182. var headers = (args.headers || (args.headers = {}));
  183. headers.Pragma = "start-long-poll";
  184. try{
  185. return this.transport(args);
  186. }finally{
  187. // cleanup the header so it is not used on subsequent requests
  188. delete headers.Pragma;
  189. }
  190. }
  191. };
  192. fire = function(type, object, deferred){
  193. if(socket["on" + type]){
  194. object.ioArgs = deferred && deferred.ioArgs;
  195. object.type = type;
  196. on.emit(socket, type, object);
  197. }
  198. };
  199. connect = function(){
  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. // provide an alias for Dojo's connect method
  212. socket.connect = socket.on;
  213. // do the initial connection
  214. setTimeout(connect);
  215. return socket;
  216. };
  217. return Socket;
  218. });