You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
229 lines
7.5 KiB
229 lines
7.5 KiB
define([ |
|
"dojo/_base/array", |
|
"dojo/_base/lang", |
|
"dojo/_base/xhr", |
|
"dojo/aspect", |
|
"dojo/on", |
|
"dojo/Evented", |
|
"dojo/_base/url" |
|
], function(array, lang, xhr, aspect, on, Evented, dBaseUrl) { |
|
|
|
var WebSocket = window.WebSocket; |
|
|
|
var Socket = function(/*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: |
|
// | require(["dojox/socket", "dojo/aspect"], function(socket, aspect) { |
|
// | var sock = socket({"url://comet-server/comet"); |
|
// | // we could also add auto-reconnect support |
|
// | // now we can connect to standard HTML5 WebSocket-style events |
|
// | aspect.after(socket, "onmessage", function(event){ |
|
// | var message = event.data; |
|
// | // do something with the message |
|
// | }); |
|
// | // send something |
|
// | sock.send("hi there"); |
|
// | ... |
|
// | }); |
|
// You can also use the Reconnect module: |
|
// | require["dojox/socket", "dojox/socket/Reconnect"], function(dxSocket, reconnect){ |
|
// | var socket = dxSocket({url:"/comet"}); |
|
// | // add auto-reconnect support |
|
// | socket = reconnect(socket); |
|
if(typeof argsOrUrl == "string"){ |
|
argsOrUrl = {url: argsOrUrl}; |
|
} |
|
return WebSocket ? Socket.WebSocket(argsOrUrl, true) : Socket.LongPoll(argsOrUrl); |
|
}; |
|
|
|
Socket.WebSocket = function(args, fallback){ |
|
// summary: |
|
// A wrapper for WebSocket, than handles standard args and relative URLs |
|
var baseURI = document.baseURI || window.location.href; |
|
var ws = new WebSocket(new dBaseUrl(baseURI.replace(/^http/i,'ws'), args.url)); |
|
ws.on = function(type, listener){ |
|
ws.addEventListener(type, listener, true); |
|
}; |
|
var opened; |
|
aspect.after(ws, "onopen", function(event){ |
|
opened = true; |
|
}, true); |
|
aspect.after(ws, "onclose", function(event){ |
|
if(opened){ |
|
return; |
|
} |
|
if(fallback){ |
|
Socket.replace(ws, Socket.LongPoll(args), true); |
|
} |
|
}, true); |
|
return ws; |
|
}; |
|
|
|
Socket.replace = function(socket, newSocket, listenForOpen){ |
|
// make the original socket a proxy for the new socket |
|
socket.send = lang.hitch(newSocket, "send"); |
|
socket.close = lang.hitch(newSocket, "close"); |
|
var proxyEvent = function(type){ |
|
(newSocket.addEventListener || newSocket.on).call(newSocket, type, function(event){ |
|
on.emit(socket, event.type, event); |
|
}, true); |
|
}; |
|
|
|
if(listenForOpen){ |
|
proxyEvent("open"); |
|
} |
|
// redirect the events as well |
|
array.forEach(["message", "close", "error"], proxyEvent); |
|
}; |
|
|
|
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 fire, connect; |
|
var socket = { |
|
send: function(data){ |
|
// summary: |
|
// Send some data using XHR or provided transport |
|
var sendArgs = lang.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(array.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(array.indexOf(connections, deferred), 1); |
|
// an error occurred, fire the appropriate event listeners |
|
if(!cancelled){ |
|
fire("error", {error:error}, deferred); |
|
if(!connections.length){ |
|
clearTimeout(timeoutId); |
|
socket.readyState = 3; |
|
fire("close", {wasClean:false}, deferred); |
|
} |
|
} |
|
}); |
|
return deferred; |
|
}, |
|
close: function(){ |
|
// summary: |
|
// Close the connection |
|
socket.readyState = 2; |
|
cancelled = true; |
|
var i; |
|
for(i = 0; i < connections.length; i++){ |
|
connections[i].cancel(); |
|
} |
|
socket.readyState = 3; |
|
fire("close", {wasClean:true}); |
|
}, |
|
transport: args.transport || xhr.post, |
|
args: args, |
|
url: args.url, |
|
readyState: 0, |
|
CONNECTING: 0, |
|
OPEN: 1, |
|
CLOSING: 2, |
|
CLOSED: 3, |
|
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; |
|
} |
|
} |
|
}; |
|
fire = function(type, object, deferred){ |
|
if(socket["on" + type]){ |
|
object.ioArgs = deferred && deferred.ioArgs; |
|
object.type = type; |
|
on.emit(socket, type, object); |
|
} |
|
}; |
|
connect = function(){ |
|
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(); |
|
} |
|
}; |
|
// provide an alias for Dojo's connect method |
|
socket.connect = socket.on; |
|
// do the initial connection |
|
setTimeout(connect); |
|
return socket; |
|
}; |
|
|
|
return Socket; |
|
|
|
});
|
|
|