/*
Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved.
Available via Academic Free License >= 2.1 OR the modified BSD license.
see: http://dojotoolkit.org/license for details
*/
if(!dojo._hasResource["dojox.socket"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dojox.socket"] = true;
dojo.provide("dojox.socket");
dojo.require("dojo.cookie");
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: function(type, callback){
return dojo.connect(this, "on" + type, callback);
},
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;
};
}