//>>built define("dojox/socket", ["dojo", "dojo/Evented", "dojo/cookie", "dojo/_base/url"], function(dojo, Evented) { var WebSocket = window.WebSocket; function Socket(/*dojo.__XhrArgs*/ argsOrUrl){ // summary: // Provides a simple socket connection using WebSocket, or alternate // communication mechanisms in legacy browsers for comet-style communication. This is based // on the WebSocket API and returns an object that implements the WebSocket interface: // http://dev.w3.org/html5/websockets/#websocket // description: // Provides socket connections. This can be used with virtually any Comet protocol. // argsOrUrl: // This uses the same arguments as the other I/O functions in Dojo, or a // URL to connect to. The URL should be a relative URL in order to properly // work with WebSockets (it can still be host relative, like //other-site.org/endpoint) // returns: // An object that implements the WebSocket API // example: // | dojo.require("dojox.socket"); // | var socket = dojox.socket({"//comet-server/comet"); // | // we could also add auto-reconnect support // | // now we can connect to standard HTML5 WebSocket-style events // | dojo.connect(socket, "onmessage", function(event){ // | var message = event.data; // | // do something with the message // | }); // | // send something // | socket.send("hi there"); // | whenDone(function(){ // | socket.close(); // | }); // You can also use the Reconnect module: // | dojo.require("dojox.socket"); // | dojo.require("dojox.socket.Reconnect"); // | var socket = dojox.socket({url:"/comet"}); // | // add auto-reconnect support // | socket = dojox.socket.Reconnect(socket); if(typeof argsOrUrl == "string"){ argsOrUrl = {url: argsOrUrl}; } return WebSocket ? dojox.socket.WebSocket(argsOrUrl, true) : dojox.socket.LongPoll(argsOrUrl); }; dojox.socket = Socket; Socket.WebSocket = function(args, fallback){ // summary: // A wrapper for WebSocket, than handles standard args and relative URLs var ws = new WebSocket(new dojo._Url(document.baseURI.replace(/^http/i,'ws'), args.url)); ws.on = function(type, listener){ ws.addEventListener(type, listener, true); }; var opened; dojo.connect(ws, "onopen", function(event){ opened = true; }); dojo.connect(ws, "onclose", function(event){ if(opened){ return; } if(fallback){ Socket.replace(ws, dojox.socket.LongPoll(args), true); } }); return ws; }; Socket.replace = function(socket, newSocket, listenForOpen){ // make the original socket a proxy for the new socket socket.send = dojo.hitch(newSocket, "send"); socket.close = dojo.hitch(newSocket, "close"); if(listenForOpen){ proxyEvent("open"); } // redirect the events as well dojo.forEach(["message", "close", "error"], proxyEvent); function proxyEvent(type){ (newSocket.addEventListener || newSocket.on).call(newSocket, type, function(event){ var newEvent = document.createEvent("MessageEvent"); newEvent.initMessageEvent(event.type, false, false, event.data, event.origin, event.lastEventId, event.source); socket.dispatchEvent(newEvent); }, true); } }; Socket.LongPoll = function(/*dojo.__XhrArgs*/ args){ // summary: // Provides a simple long-poll based comet-style socket/connection to a server and returns an // object implementing the WebSocket interface: // http://dev.w3.org/html5/websockets/#websocket // args: // This uses the same arguments as the other I/O functions in Dojo, with this addition: // args.interval: // Indicates the amount of time (in milliseconds) after a response was received // before another request is made. By default, a request is made immediately // after getting a response. The interval can be increased to reduce load on the // server or to do simple time-based polling where the server always responds // immediately. // args.transport: // Provide an alternate transport like dojo.io.script.get // returns: // An object that implements the WebSocket API // example: // | dojo.require("dojox.socket.LongPoll"); // | var socket = dojox.socket.LongPoll({url:"/comet"}); // or: // | dojo.require("dojox.socket.LongPoll"); // | dojox.socket.LongPoll.add(); // | var socket = dojox.socket({url:"/comet"}); var cancelled = false, first = true, timeoutId, connections = []; // create the socket object var socket = { send: function(data){ // summary: // Send some data using XHR or provided transport var sendArgs = dojo.delegate(args); sendArgs.rawBody = data; clearTimeout(timeoutId); var deferred = first ? (first = false) || socket.firstRequest(sendArgs) : socket.transport(sendArgs); connections.push(deferred); deferred.then(function(response){ // got a response socket.readyState = 1; // remove the current connection connections.splice(dojo.indexOf(connections, deferred), 1); // reconnect to listen for the next message if there are no active connections, // we queue it up in case one of the onmessage handlers has a message to send if(!connections.length){ timeoutId = setTimeout(connect, args.interval); } if(response){ // now send the message along to listeners fire("message", {data: response}, deferred); } }, function(error){ connections.splice(dojo.indexOf(connections, deferred), 1); // an error occurred, fire the appropriate event listeners if(!cancelled){ fire("error", {error:error}, deferred); if(!connections.length){ socket.readyState = 3; fire("close", {wasClean:false}, deferred); } } }); return deferred; }, close: function(){ // summary: // Close the connection socket.readyState = 2; cancelled = true; for(var i = 0; i < connections.length; i++){ connections[i].cancel(); } socket.readyState = 3; fire("close", {wasClean:true}); }, transport: args.transport || dojo.xhrPost, args: args, url: args.url, readyState: 0, CONNECTING: 0, OPEN: 1, CLOSING: 2, CLOSED: 3, dispatchEvent: function(event){ fire(event.type, event); }, on: Evented.prototype.on, firstRequest: function(args){ // summary: // This allows for special handling for the first request. This is useful for // providing information to disambiguate between the first request and // subsequent long-poll requests so the server can properly setup a // connection on the first connection or reject a request for an expired // connection if the request is not expecting to be the first for a connection. // This method can be overriden. The default behavior is to include a Pragma // header with a value of "start-long-poll" var headers = (args.headers || (args.headers = {})); headers.Pragma = "start-long-poll"; try{ return this.transport(args); }finally{ // cleanup the header so it is not used on subsequent requests delete headers.Pragma; } } }; function connect(){ if(socket.readyState == 0){ // we fire the open event now because we really don't know when the "socket" // is truly open, and this gives us a to do a send() and get it included in the // HTTP request fire("open",{}); } // make the long-poll connection, to wait for response from the server if(!connections.length){ socket.send(); } } function fire(type, object, deferred){ if(socket["on" + type]){ var event = document.createEvent("HTMLEvents"); event.initEvent(type, false, false); dojo.mixin(event, object); event.ioArgs = deferred && deferred.ioArgs; socket["on" + type](event); } } // provide an alias for Dojo's connect method socket.connect = socket.on; // do the initial connection setTimeout(connect); return socket; }; return Socket; });