-
Notifications
You must be signed in to change notification settings - Fork 229
/
socket.js
229 lines (219 loc) · 7.48 KB
/
socket.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
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;
});