khipu-rails 0.0.1 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/README.md +57 -11
- data/app/controllers/khipu_rails/application_controller.rb +5 -0
- data/app/controllers/khipu_rails/khipu_controller.rb +23 -0
- data/app/views/khipu_rails/khipu/notification.html +2 -0
- data/app/views/layouts/khipu_rails/application.html.erb +10 -0
- data/config/khipu.pem.cer +81 -0
- data/config/khipu_dev.pem.cer +22 -0
- data/config/routes.rb +3 -0
- data/khipu-rails.gemspec +6 -4
- data/lib/khipu-rails.rb +2 -0
- data/lib/khipu_rails.rb +60 -1
- data/lib/khipu_rails/button_helper.rb +45 -34
- data/lib/khipu_rails/config.rb +25 -17
- data/lib/khipu_rails/notification_validator.rb +47 -0
- data/lib/khipu_rails/receiver.rb +17 -0
- data/lib/khipu_rails/version.rb +1 -1
- data/spec/khipu_rails/button_helper_spec.rb +200 -0
- data/spec/khipu_rails/config_spec.rb +65 -0
- data/spec/khipu_rails/notification_validator_spec.rb +64 -0
- data/spec/khipu_rails/receiver_spec.rb +26 -0
- data/spec/khipu_rails_spec.rb +52 -0
- data/spec/spec_helper.rb +1 -4
- data/vendor/assets/javascripts/atmosphere.js +3081 -0
- data/vendor/assets/javascripts/khipu.js +373 -0
- metadata +71 -35
- data/spec/khipu_rails/khipu_helpers_spec.rb +0 -104
@@ -0,0 +1,26 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'spec_helper'
|
3
|
+
require "openssl"
|
4
|
+
|
5
|
+
describe KhipuRails::Receiver do
|
6
|
+
before :all do
|
7
|
+
@dev = KhipuRails::Receiver.new "1", "12345678", :dev
|
8
|
+
@pro = KhipuRails::Receiver.new "2", "87654321", :prod
|
9
|
+
end
|
10
|
+
|
11
|
+
it "has basic attributes" do
|
12
|
+
@dev.respond_to?(:id).should == true
|
13
|
+
@dev.respond_to?(:key).should == true
|
14
|
+
@dev.respond_to?(:mode).should == true
|
15
|
+
end
|
16
|
+
|
17
|
+
it "loads the correct public key" do
|
18
|
+
dev_cert_path = [KhipuRails.root, 'config', 'khipu_dev.pem.cer'].join('/')
|
19
|
+
dev_cert = OpenSSL::X509::Certificate.new File.read dev_cert_path
|
20
|
+
@dev.pkey.to_s.should == dev_cert.public_key.to_s
|
21
|
+
|
22
|
+
pro_cert_path = [KhipuRails.root, 'config', 'khipu.pem.cer'].join('/')
|
23
|
+
pro_cert = OpenSSL::X509::Certificate.new File.read pro_cert_path
|
24
|
+
@pro.pkey.to_s.should == pro_cert.public_key.to_s
|
25
|
+
end
|
26
|
+
end
|
data/spec/khipu_rails_spec.rb
CHANGED
@@ -5,4 +5,56 @@ describe KhipuRails do
|
|
5
5
|
it "adds the khipu_button helper to the view" do
|
6
6
|
ActionView::Base.method_defined?(:khipu_button).should == true
|
7
7
|
end
|
8
|
+
|
9
|
+
it "Returns a uses the correct parameters for the Hash with the default values" do
|
10
|
+
KhipuRails.config = nil
|
11
|
+
KhipuRails.configure do |config|
|
12
|
+
config.add_receiver "1392", 'b174c94de0ec3ce4f1c3156e309de45e8ce0f9ef', :dev
|
13
|
+
end
|
14
|
+
|
15
|
+
receiver = KhipuRails.config.receivers.first
|
16
|
+
values = KhipuRails.config.button_defaults
|
17
|
+
|
18
|
+
raw = "receiver_id=#{receiver.id}&"
|
19
|
+
raw += "subject=#{values[:subject]}&"
|
20
|
+
raw += "body=#{values[:body]}&"
|
21
|
+
raw += "amount=#{values[:amount]}&"
|
22
|
+
raw += "payer_email=#{values[:payer_email]}&"
|
23
|
+
raw += "bank_id=#{values[:bank_id]}&"
|
24
|
+
raw += "expires_date=#{values[:expires_date]}&"
|
25
|
+
raw += "transaction_id=#{values[:transaction_id]}&"
|
26
|
+
raw += "custom=#{values[:custom]}&"
|
27
|
+
raw += "notify_url=#{values[:notify_url]}&"
|
28
|
+
raw += "return_url=#{values[:return_url]}&"
|
29
|
+
raw += "cancel_url=#{values[:cancel_url]}&"
|
30
|
+
raw += "picture_url=#{values[:picture_url]}"
|
31
|
+
|
32
|
+
KhipuRails.raw_hash.should == raw
|
33
|
+
end
|
34
|
+
|
35
|
+
it "Returns a Hash with the default values" do
|
36
|
+
KhipuRails.config = nil
|
37
|
+
KhipuRails.configure do |config|
|
38
|
+
config.add_receiver "1392", 'b174c94de0ec3ce4f1c3156e309de45e8ce0f9ef', :dev
|
39
|
+
end
|
40
|
+
|
41
|
+
receiver = KhipuRails.config.receivers.first
|
42
|
+
values = KhipuRails.config.button_defaults
|
43
|
+
|
44
|
+
raw = "receiver_id=#{receiver.id}&"
|
45
|
+
raw += "subject=#{values[:subject]}&"
|
46
|
+
raw += "body=#{values[:body]}&"
|
47
|
+
raw += "amount=#{values[:amount]}&"
|
48
|
+
raw += "payer_email=#{values[:payer_email]}&"
|
49
|
+
raw += "bank_id=#{values[:bank_id]}&"
|
50
|
+
raw += "expires_date=#{values[:expires_date]}&"
|
51
|
+
raw += "transaction_id=#{values[:transaction_id]}&"
|
52
|
+
raw += "custom=#{values[:custom]}&"
|
53
|
+
raw += "notify_url=#{values[:notify_url]}&"
|
54
|
+
raw += "return_url=#{values[:return_url]}&"
|
55
|
+
raw += "cancel_url=#{values[:cancel_url]}&"
|
56
|
+
raw += "picture_url=#{values[:picture_url]}"
|
57
|
+
|
58
|
+
KhipuRails.khipu_hash.should == OpenSSL::HMAC.hexdigest('sha256', receiver.key, raw)
|
59
|
+
end
|
8
60
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -0,0 +1,3081 @@
|
|
1
|
+
/*
|
2
|
+
* Copyright 2014 Jeanfrancois Arcand
|
3
|
+
*
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
* you may not use this file except in compliance with the License.
|
6
|
+
* You may obtain a copy of the License at
|
7
|
+
*
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
*
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
* See the License for the specific language governing permissions and
|
14
|
+
* limitations under the License.
|
15
|
+
*/
|
16
|
+
/**
|
17
|
+
* Atmosphere.js
|
18
|
+
* https://github.com/Atmosphere/atmosphere-javascript
|
19
|
+
*
|
20
|
+
* API reference
|
21
|
+
* https://github.com/Atmosphere/atmosphere/wiki/jQuery.atmosphere.js-API
|
22
|
+
*
|
23
|
+
* Highly inspired by
|
24
|
+
* - Portal by Donghwan Kim http://flowersinthesand.github.io/portal/
|
25
|
+
*/
|
26
|
+
(function(root, factory) {
|
27
|
+
if (typeof define === "function" && define.amd) {
|
28
|
+
// AMD
|
29
|
+
define(factory);
|
30
|
+
} else {
|
31
|
+
// Browser globals, Window
|
32
|
+
root.atmosphere = factory();
|
33
|
+
}
|
34
|
+
}(this, function() {
|
35
|
+
|
36
|
+
"use strict";
|
37
|
+
|
38
|
+
var version = "2.1.5-javascript",
|
39
|
+
atmosphere = {},
|
40
|
+
guid,
|
41
|
+
requests = [],
|
42
|
+
callbacks = [],
|
43
|
+
uuid = 0,
|
44
|
+
hasOwn = Object.prototype.hasOwnProperty;
|
45
|
+
|
46
|
+
atmosphere = {
|
47
|
+
|
48
|
+
onError: function (response) {
|
49
|
+
},
|
50
|
+
onClose: function (response) {
|
51
|
+
},
|
52
|
+
onOpen: function (response) {
|
53
|
+
},
|
54
|
+
onReopen: function (response) {
|
55
|
+
},
|
56
|
+
onMessage: function (response) {
|
57
|
+
},
|
58
|
+
onReconnect: function (request, response) {
|
59
|
+
},
|
60
|
+
onMessagePublished: function (response) {
|
61
|
+
},
|
62
|
+
onTransportFailure: function (errorMessage, _request) {
|
63
|
+
},
|
64
|
+
onLocalMessage: function (response) {
|
65
|
+
},
|
66
|
+
onFailureToReconnect: function (request, response) {
|
67
|
+
},
|
68
|
+
onClientTimeout: function(request){
|
69
|
+
},
|
70
|
+
|
71
|
+
AtmosphereRequest: function (options) {
|
72
|
+
|
73
|
+
/**
|
74
|
+
* {Object} Request parameters.
|
75
|
+
*
|
76
|
+
* @private
|
77
|
+
*/
|
78
|
+
var _request = {
|
79
|
+
timeout: 300000,
|
80
|
+
method: 'GET',
|
81
|
+
headers: {},
|
82
|
+
contentType: '',
|
83
|
+
callback: null,
|
84
|
+
url: '',
|
85
|
+
data: '',
|
86
|
+
suspend: true,
|
87
|
+
maxRequest: -1,
|
88
|
+
reconnect: true,
|
89
|
+
maxStreamingLength: 10000000,
|
90
|
+
lastIndex: 0,
|
91
|
+
logLevel: 'info',
|
92
|
+
requestCount: 0,
|
93
|
+
fallbackMethod: 'GET',
|
94
|
+
fallbackTransport: 'streaming',
|
95
|
+
transport: 'long-polling',
|
96
|
+
webSocketImpl: null,
|
97
|
+
webSocketBinaryType: null,
|
98
|
+
dispatchUrl: null,
|
99
|
+
webSocketPathDelimiter: "@@",
|
100
|
+
enableXDR: false,
|
101
|
+
rewriteURL: false,
|
102
|
+
attachHeadersAsQueryString: true,
|
103
|
+
executeCallbackBeforeReconnect: false,
|
104
|
+
readyState: 0,
|
105
|
+
lastTimestamp: 0,
|
106
|
+
withCredentials: false,
|
107
|
+
trackMessageLength: false,
|
108
|
+
messageDelimiter: '|',
|
109
|
+
connectTimeout: -1,
|
110
|
+
reconnectInterval: 0,
|
111
|
+
dropHeaders: true,
|
112
|
+
uuid: 0,
|
113
|
+
async: true,
|
114
|
+
shared: false,
|
115
|
+
readResponsesHeaders: false,
|
116
|
+
maxReconnectOnClose: 5,
|
117
|
+
enableProtocol: true,
|
118
|
+
pollingInterval : 0,
|
119
|
+
onError: function (response) {
|
120
|
+
},
|
121
|
+
onClose: function (response) {
|
122
|
+
},
|
123
|
+
onOpen: function (response) {
|
124
|
+
},
|
125
|
+
onMessage: function (response) {
|
126
|
+
},
|
127
|
+
onReopen: function (request, response) {
|
128
|
+
},
|
129
|
+
onReconnect: function (request, response) {
|
130
|
+
},
|
131
|
+
onMessagePublished: function (response) {
|
132
|
+
},
|
133
|
+
onTransportFailure: function (reason, request) {
|
134
|
+
},
|
135
|
+
onLocalMessage: function (request) {
|
136
|
+
},
|
137
|
+
onFailureToReconnect: function (request, response) {
|
138
|
+
},
|
139
|
+
onClientTimeout: function(request){
|
140
|
+
}
|
141
|
+
};
|
142
|
+
|
143
|
+
/**
|
144
|
+
* {Object} Request's last response.
|
145
|
+
*
|
146
|
+
* @private
|
147
|
+
*/
|
148
|
+
var _response = {
|
149
|
+
status: 200,
|
150
|
+
reasonPhrase: "OK",
|
151
|
+
responseBody: '',
|
152
|
+
messages: [],
|
153
|
+
headers: [],
|
154
|
+
state: "messageReceived",
|
155
|
+
transport: "polling",
|
156
|
+
error: null,
|
157
|
+
request: null,
|
158
|
+
partialMessage: "",
|
159
|
+
errorHandled: false,
|
160
|
+
closedByClientTimeout: false
|
161
|
+
};
|
162
|
+
|
163
|
+
/**
|
164
|
+
* {websocket} Opened web socket.
|
165
|
+
*
|
166
|
+
* @private
|
167
|
+
*/
|
168
|
+
var _websocket = null;
|
169
|
+
|
170
|
+
/**
|
171
|
+
* {SSE} Opened SSE.
|
172
|
+
*
|
173
|
+
* @private
|
174
|
+
*/
|
175
|
+
var _sse = null;
|
176
|
+
|
177
|
+
/**
|
178
|
+
* {XMLHttpRequest, ActiveXObject} Opened ajax request (in case of http-streaming or long-polling)
|
179
|
+
*
|
180
|
+
* @private
|
181
|
+
*/
|
182
|
+
var _activeRequest = null;
|
183
|
+
|
184
|
+
/**
|
185
|
+
* {Object} Object use for streaming with IE.
|
186
|
+
*
|
187
|
+
* @private
|
188
|
+
*/
|
189
|
+
var _ieStream = null;
|
190
|
+
|
191
|
+
/**
|
192
|
+
* {Object} Object use for jsonp transport.
|
193
|
+
*
|
194
|
+
* @private
|
195
|
+
*/
|
196
|
+
var _jqxhr = null;
|
197
|
+
|
198
|
+
/**
|
199
|
+
* {boolean} If request has been subscribed or not.
|
200
|
+
*
|
201
|
+
* @private
|
202
|
+
*/
|
203
|
+
var _subscribed = true;
|
204
|
+
|
205
|
+
/**
|
206
|
+
* {number} Number of test reconnection.
|
207
|
+
*
|
208
|
+
* @private
|
209
|
+
*/
|
210
|
+
var _requestCount = 0;
|
211
|
+
|
212
|
+
/**
|
213
|
+
* {boolean} If request is currently aborded.
|
214
|
+
*
|
215
|
+
* @private
|
216
|
+
*/
|
217
|
+
var _abordingConnection = false;
|
218
|
+
|
219
|
+
/**
|
220
|
+
* A local "channel' of communication.
|
221
|
+
*
|
222
|
+
* @private
|
223
|
+
*/
|
224
|
+
var _localSocketF = null;
|
225
|
+
|
226
|
+
/**
|
227
|
+
* The storage used.
|
228
|
+
*
|
229
|
+
* @private
|
230
|
+
*/
|
231
|
+
var _storageService;
|
232
|
+
|
233
|
+
/**
|
234
|
+
* Local communication
|
235
|
+
*
|
236
|
+
* @private
|
237
|
+
*/
|
238
|
+
var _localStorageService = null;
|
239
|
+
|
240
|
+
/**
|
241
|
+
* A Unique ID
|
242
|
+
*
|
243
|
+
* @private
|
244
|
+
*/
|
245
|
+
var guid = atmosphere.util.now();
|
246
|
+
|
247
|
+
/** Trace time */
|
248
|
+
var _traceTimer;
|
249
|
+
|
250
|
+
/** Key for connection sharing */
|
251
|
+
var _sharingKey;
|
252
|
+
|
253
|
+
// Automatic call to subscribe
|
254
|
+
_subscribe(options);
|
255
|
+
|
256
|
+
/**
|
257
|
+
* Initialize atmosphere request object.
|
258
|
+
*
|
259
|
+
* @private
|
260
|
+
*/
|
261
|
+
function _init() {
|
262
|
+
_subscribed = true;
|
263
|
+
_abordingConnection = false;
|
264
|
+
_requestCount = 0;
|
265
|
+
|
266
|
+
_websocket = null;
|
267
|
+
_sse = null;
|
268
|
+
_activeRequest = null;
|
269
|
+
_ieStream = null;
|
270
|
+
}
|
271
|
+
|
272
|
+
/**
|
273
|
+
* Re-initialize atmosphere object.
|
274
|
+
*
|
275
|
+
* @private
|
276
|
+
*/
|
277
|
+
function _reinit() {
|
278
|
+
_clearState();
|
279
|
+
_init();
|
280
|
+
}
|
281
|
+
|
282
|
+
/**
|
283
|
+
*
|
284
|
+
* @private
|
285
|
+
*/
|
286
|
+
function _verifyStreamingLength(ajaxRequest, rq) {
|
287
|
+
// Wait to be sure we have the full message before closing.
|
288
|
+
if (_response.partialMessage === "" && (rq.transport === 'streaming') && (ajaxRequest.responseText.length > rq.maxStreamingLength)) {
|
289
|
+
_response.messages = [];
|
290
|
+
rq.reconnectingOnLength = true;
|
291
|
+
rq.isReopen = true;
|
292
|
+
_invokeClose(true);
|
293
|
+
_disconnect();
|
294
|
+
_clearState();
|
295
|
+
_reconnect(ajaxRequest, rq, rq.pollingInterval);
|
296
|
+
}
|
297
|
+
}
|
298
|
+
|
299
|
+
/**
|
300
|
+
* Disconnect
|
301
|
+
*
|
302
|
+
* @private
|
303
|
+
*/
|
304
|
+
function _disconnect() {
|
305
|
+
if (_request.enableProtocol && !_request.firstMessage) {
|
306
|
+
var query = "X-Atmosphere-Transport=close&X-Atmosphere-tracking-id=" + _request.uuid;
|
307
|
+
|
308
|
+
atmosphere.util.each(_request.headers, function (name, value) {
|
309
|
+
var h = atmosphere.util.isFunction(value) ? value.call(this, _request, _request, _response) : value;
|
310
|
+
if (h != null) {
|
311
|
+
query += "&" + encodeURIComponent(name) + "=" + encodeURIComponent(h);
|
312
|
+
}
|
313
|
+
});
|
314
|
+
|
315
|
+
var url = _request.url.replace(/([?&])_=[^&]*/, query);
|
316
|
+
url = url + (url === _request.url ? (/\?/.test(_request.url) ? "&" : "?") + query : "");
|
317
|
+
|
318
|
+
var rq = {
|
319
|
+
connected: false
|
320
|
+
};
|
321
|
+
var closeR = new atmosphere.AtmosphereRequest(rq);
|
322
|
+
closeR.attachHeadersAsQueryString = false;
|
323
|
+
closeR.dropHeaders = true;
|
324
|
+
closeR.url = url;
|
325
|
+
closeR.contentType = "text/plain";
|
326
|
+
closeR.transport = 'polling';
|
327
|
+
closeR.method = 'GET';
|
328
|
+
closeR.data = '';
|
329
|
+
closeR.async = false;
|
330
|
+
_pushOnClose("", closeR);
|
331
|
+
}
|
332
|
+
}
|
333
|
+
|
334
|
+
/**
|
335
|
+
* Close request.
|
336
|
+
*
|
337
|
+
* @private
|
338
|
+
*/
|
339
|
+
function _close() {
|
340
|
+
if (_request.reconnectId) {
|
341
|
+
clearTimeout(_request.reconnectId);
|
342
|
+
delete _request.reconnectId;
|
343
|
+
}
|
344
|
+
_request.reconnect = false;
|
345
|
+
_abordingConnection = true;
|
346
|
+
_response.request = _request;
|
347
|
+
_response.state = 'unsubscribe';
|
348
|
+
_response.responseBody = "";
|
349
|
+
_response.status = 408;
|
350
|
+
_response.partialMessage = "";
|
351
|
+
_invokeCallback();
|
352
|
+
_disconnect();
|
353
|
+
_clearState();
|
354
|
+
}
|
355
|
+
|
356
|
+
function _clearState() {
|
357
|
+
_response.partialMessage = "";
|
358
|
+
if (_request.id) {
|
359
|
+
clearTimeout(_request.id);
|
360
|
+
}
|
361
|
+
|
362
|
+
if (_ieStream != null) {
|
363
|
+
_ieStream.close();
|
364
|
+
_ieStream = null;
|
365
|
+
}
|
366
|
+
if (_jqxhr != null) {
|
367
|
+
_jqxhr.abort();
|
368
|
+
_jqxhr = null;
|
369
|
+
}
|
370
|
+
if (_activeRequest != null) {
|
371
|
+
_activeRequest.abort();
|
372
|
+
_activeRequest = null;
|
373
|
+
}
|
374
|
+
if (_websocket != null) {
|
375
|
+
if (_websocket.canSendMessage) {
|
376
|
+
_websocket.close();
|
377
|
+
}
|
378
|
+
_websocket = null;
|
379
|
+
}
|
380
|
+
if (_sse != null) {
|
381
|
+
_sse.close();
|
382
|
+
_sse = null;
|
383
|
+
}
|
384
|
+
_clearStorage();
|
385
|
+
}
|
386
|
+
|
387
|
+
function _clearStorage() {
|
388
|
+
// Stop sharing a connection
|
389
|
+
if (_storageService != null) {
|
390
|
+
// Clears trace timer
|
391
|
+
clearInterval(_traceTimer);
|
392
|
+
// Removes the trace
|
393
|
+
document.cookie = _sharingKey + "=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/";
|
394
|
+
// The heir is the parent unless unloading
|
395
|
+
_storageService.signal("close", {
|
396
|
+
reason: "",
|
397
|
+
heir: !_abordingConnection ? guid : (_storageService.get("children") || [])[0]
|
398
|
+
});
|
399
|
+
_storageService.close();
|
400
|
+
}
|
401
|
+
if (_localStorageService != null) {
|
402
|
+
_localStorageService.close();
|
403
|
+
}
|
404
|
+
}
|
405
|
+
|
406
|
+
/**
|
407
|
+
* Subscribe request using request transport. <br>
|
408
|
+
* If request is currently opened, this one will be closed.
|
409
|
+
*
|
410
|
+
* @param {Object} Request parameters.
|
411
|
+
* @private
|
412
|
+
*/
|
413
|
+
function _subscribe(options) {
|
414
|
+
_reinit();
|
415
|
+
|
416
|
+
_request = atmosphere.util.extend(_request, options);
|
417
|
+
// Allow at least 1 request
|
418
|
+
_request.mrequest = _request.reconnect;
|
419
|
+
if (!_request.reconnect) {
|
420
|
+
_request.reconnect = true;
|
421
|
+
}
|
422
|
+
}
|
423
|
+
|
424
|
+
/**
|
425
|
+
* Check if web socket is supported (check for custom implementation provided by request object or browser implementation).
|
426
|
+
*
|
427
|
+
* @returns {boolean} True if web socket is supported, false otherwise.
|
428
|
+
* @private
|
429
|
+
*/
|
430
|
+
function _supportWebsocket() {
|
431
|
+
return _request.webSocketImpl != null || window.WebSocket || window.MozWebSocket;
|
432
|
+
}
|
433
|
+
|
434
|
+
/**
|
435
|
+
* Check if server side events (SSE) is supported (check for custom implementation provided by request object or browser implementation).
|
436
|
+
*
|
437
|
+
* @returns {boolean} True if web socket is supported, false otherwise.
|
438
|
+
* @private
|
439
|
+
*/
|
440
|
+
function _supportSSE() {
|
441
|
+
return window.EventSource;
|
442
|
+
}
|
443
|
+
|
444
|
+
/**
|
445
|
+
* Open request using request transport. <br>
|
446
|
+
* If request transport is 'websocket' but websocket can't be opened, request will automatically reconnect using fallback transport.
|
447
|
+
*
|
448
|
+
* @private
|
449
|
+
*/
|
450
|
+
function _execute() {
|
451
|
+
// Shared across multiple tabs/windows.
|
452
|
+
if (_request.shared) {
|
453
|
+
_localStorageService = _local(_request);
|
454
|
+
if (_localStorageService != null) {
|
455
|
+
if (_request.logLevel === 'debug') {
|
456
|
+
atmosphere.util.debug("Storage service available. All communication will be local");
|
457
|
+
}
|
458
|
+
|
459
|
+
if (_localStorageService.open(_request)) {
|
460
|
+
// Local connection.
|
461
|
+
return;
|
462
|
+
}
|
463
|
+
}
|
464
|
+
|
465
|
+
if (_request.logLevel === 'debug') {
|
466
|
+
atmosphere.util.debug("No Storage service available.");
|
467
|
+
}
|
468
|
+
// Wasn't local or an error occurred
|
469
|
+
_localStorageService = null;
|
470
|
+
}
|
471
|
+
|
472
|
+
// Protocol
|
473
|
+
_request.firstMessage = uuid == 0 ? true : false;
|
474
|
+
_request.isOpen = false;
|
475
|
+
_request.ctime = atmosphere.util.now();
|
476
|
+
|
477
|
+
// We carry any UUID set by the user or from a previous connection.
|
478
|
+
if (_request.uuid === 0) {
|
479
|
+
_request.uuid = uuid;
|
480
|
+
}
|
481
|
+
_response.closedByClientTimeout = false;
|
482
|
+
|
483
|
+
if (_request.transport !== 'websocket' && _request.transport !== 'sse') {
|
484
|
+
_executeRequest(_request);
|
485
|
+
|
486
|
+
} else if (_request.transport === 'websocket') {
|
487
|
+
if (!_supportWebsocket()) {
|
488
|
+
_reconnectWithFallbackTransport("Websocket is not supported, using request.fallbackTransport (" + _request.fallbackTransport
|
489
|
+
+ ")");
|
490
|
+
} else {
|
491
|
+
_executeWebSocket(false);
|
492
|
+
}
|
493
|
+
} else if (_request.transport === 'sse') {
|
494
|
+
if (!_supportSSE()) {
|
495
|
+
_reconnectWithFallbackTransport("Server Side Events(SSE) is not supported, using request.fallbackTransport ("
|
496
|
+
+ _request.fallbackTransport + ")");
|
497
|
+
} else {
|
498
|
+
_executeSSE(false);
|
499
|
+
}
|
500
|
+
}
|
501
|
+
}
|
502
|
+
|
503
|
+
function _local(request) {
|
504
|
+
var trace, connector, orphan, name = "atmosphere-" + request.url, connectors = {
|
505
|
+
storage: function () {
|
506
|
+
function onstorage(event) {
|
507
|
+
if (event.key === name && event.newValue) {
|
508
|
+
listener(event.newValue);
|
509
|
+
}
|
510
|
+
}
|
511
|
+
|
512
|
+
if (!atmosphere.util.storage) {
|
513
|
+
return;
|
514
|
+
}
|
515
|
+
|
516
|
+
var storage = window.localStorage,
|
517
|
+
get = function (key) {
|
518
|
+
return atmosphere.util.parseJSON(storage.getItem(name + "-" + key));
|
519
|
+
},
|
520
|
+
set = function (key, value) {
|
521
|
+
storage.setItem(name + "-" + key, atmosphere.util.stringifyJSON(value));
|
522
|
+
};
|
523
|
+
|
524
|
+
return {
|
525
|
+
init: function () {
|
526
|
+
set("children", get("children").concat([guid]));
|
527
|
+
atmosphere.util.on(window, "storage", onstorage);
|
528
|
+
return get("opened");
|
529
|
+
},
|
530
|
+
signal: function (type, data) {
|
531
|
+
storage.setItem(name, atmosphere.util.stringifyJSON({
|
532
|
+
target: "p",
|
533
|
+
type: type,
|
534
|
+
data: data
|
535
|
+
}));
|
536
|
+
},
|
537
|
+
close: function () {
|
538
|
+
var children = get("children");
|
539
|
+
|
540
|
+
atmosphere.util.off(window, "storage", onstorage);
|
541
|
+
if (children) {
|
542
|
+
if (removeFromArray(children, request.id)) {
|
543
|
+
set("children", children);
|
544
|
+
}
|
545
|
+
}
|
546
|
+
}
|
547
|
+
};
|
548
|
+
},
|
549
|
+
windowref: function () {
|
550
|
+
var win = window.open("", name.replace(/\W/g, ""));
|
551
|
+
|
552
|
+
if (!win || win.closed || !win.callbacks) {
|
553
|
+
return;
|
554
|
+
}
|
555
|
+
|
556
|
+
return {
|
557
|
+
init: function () {
|
558
|
+
win.callbacks.push(listener);
|
559
|
+
win.children.push(guid);
|
560
|
+
return win.opened;
|
561
|
+
},
|
562
|
+
signal: function (type, data) {
|
563
|
+
if (!win.closed && win.fire) {
|
564
|
+
win.fire(atmosphere.util.stringifyJSON({
|
565
|
+
target: "p",
|
566
|
+
type: type,
|
567
|
+
data: data
|
568
|
+
}));
|
569
|
+
}
|
570
|
+
},
|
571
|
+
close: function () {
|
572
|
+
// Removes traces only if the parent is alive
|
573
|
+
if (!orphan) {
|
574
|
+
removeFromArray(win.callbacks, listener);
|
575
|
+
removeFromArray(win.children, guid);
|
576
|
+
}
|
577
|
+
}
|
578
|
+
|
579
|
+
};
|
580
|
+
}
|
581
|
+
};
|
582
|
+
|
583
|
+
function removeFromArray(array, val) {
|
584
|
+
var i, length = array.length;
|
585
|
+
|
586
|
+
for (i = 0; i < length; i++) {
|
587
|
+
if (array[i] === val) {
|
588
|
+
array.splice(i, 1);
|
589
|
+
}
|
590
|
+
}
|
591
|
+
|
592
|
+
return length !== array.length;
|
593
|
+
}
|
594
|
+
|
595
|
+
// Receives open, close and message command from the parent
|
596
|
+
function listener(string) {
|
597
|
+
var command = atmosphere.util.parseJSON(string), data = command.data;
|
598
|
+
|
599
|
+
if (command.target === "c") {
|
600
|
+
switch (command.type) {
|
601
|
+
case "open":
|
602
|
+
_open("opening", 'local', _request);
|
603
|
+
break;
|
604
|
+
case "close":
|
605
|
+
if (!orphan) {
|
606
|
+
orphan = true;
|
607
|
+
if (data.reason === "aborted") {
|
608
|
+
_close();
|
609
|
+
} else {
|
610
|
+
// Gives the heir some time to reconnect
|
611
|
+
if (data.heir === guid) {
|
612
|
+
_execute();
|
613
|
+
} else {
|
614
|
+
setTimeout(function () {
|
615
|
+
_execute();
|
616
|
+
}, 100);
|
617
|
+
}
|
618
|
+
}
|
619
|
+
}
|
620
|
+
break;
|
621
|
+
case "message":
|
622
|
+
_prepareCallback(data, "messageReceived", 200, request.transport);
|
623
|
+
break;
|
624
|
+
case "localMessage":
|
625
|
+
_localMessage(data);
|
626
|
+
break;
|
627
|
+
}
|
628
|
+
}
|
629
|
+
}
|
630
|
+
|
631
|
+
function findTrace() {
|
632
|
+
var matcher = new RegExp("(?:^|; )(" + encodeURIComponent(name) + ")=([^;]*)").exec(document.cookie);
|
633
|
+
if (matcher) {
|
634
|
+
return atmosphere.util.parseJSON(decodeURIComponent(matcher[2]));
|
635
|
+
}
|
636
|
+
}
|
637
|
+
|
638
|
+
// Finds and validates the parent socket's trace from the cookie
|
639
|
+
trace = findTrace();
|
640
|
+
if (!trace || atmosphere.util.now() - trace.ts > 1000) {
|
641
|
+
return;
|
642
|
+
}
|
643
|
+
|
644
|
+
// Chooses a connector
|
645
|
+
connector = connectors.storage() || connectors.windowref();
|
646
|
+
if (!connector) {
|
647
|
+
return;
|
648
|
+
}
|
649
|
+
|
650
|
+
return {
|
651
|
+
open: function () {
|
652
|
+
var parentOpened;
|
653
|
+
|
654
|
+
// Checks the shared one is alive
|
655
|
+
_traceTimer = setInterval(function () {
|
656
|
+
var oldTrace = trace;
|
657
|
+
trace = findTrace();
|
658
|
+
if (!trace || oldTrace.ts === trace.ts) {
|
659
|
+
// Simulates a close signal
|
660
|
+
listener(atmosphere.util.stringifyJSON({
|
661
|
+
target: "c",
|
662
|
+
type: "close",
|
663
|
+
data: {
|
664
|
+
reason: "error",
|
665
|
+
heir: oldTrace.heir
|
666
|
+
}
|
667
|
+
}));
|
668
|
+
}
|
669
|
+
}, 1000);
|
670
|
+
|
671
|
+
parentOpened = connector.init();
|
672
|
+
if (parentOpened) {
|
673
|
+
// Firing the open event without delay robs the user of the opportunity to bind connecting event handlers
|
674
|
+
setTimeout(function () {
|
675
|
+
_open("opening", 'local', request);
|
676
|
+
}, 50);
|
677
|
+
}
|
678
|
+
return parentOpened;
|
679
|
+
},
|
680
|
+
send: function (event) {
|
681
|
+
connector.signal("send", event);
|
682
|
+
},
|
683
|
+
localSend: function (event) {
|
684
|
+
connector.signal("localSend", atmosphere.util.stringifyJSON({
|
685
|
+
id: guid,
|
686
|
+
event: event
|
687
|
+
}));
|
688
|
+
},
|
689
|
+
close: function () {
|
690
|
+
// Do not signal the parent if this method is executed by the unload event handler
|
691
|
+
if (!_abordingConnection) {
|
692
|
+
clearInterval(_traceTimer);
|
693
|
+
connector.signal("close");
|
694
|
+
connector.close();
|
695
|
+
}
|
696
|
+
}
|
697
|
+
};
|
698
|
+
}
|
699
|
+
|
700
|
+
function share() {
|
701
|
+
var storageService, name = "atmosphere-" + _request.url, servers = {
|
702
|
+
// Powered by the storage event and the localStorage
|
703
|
+
// http://www.w3.org/TR/webstorage/#event-storage
|
704
|
+
storage: function () {
|
705
|
+
function onstorage(event) {
|
706
|
+
// When a deletion, newValue initialized to null
|
707
|
+
if (event.key === name && event.newValue) {
|
708
|
+
listener(event.newValue);
|
709
|
+
}
|
710
|
+
}
|
711
|
+
|
712
|
+
if (!atmosphere.util.storage) {
|
713
|
+
return;
|
714
|
+
}
|
715
|
+
|
716
|
+
var storage = window.localStorage;
|
717
|
+
|
718
|
+
return {
|
719
|
+
init: function () {
|
720
|
+
// Handles the storage event
|
721
|
+
atmosphere.util.on(window, "storage", onstorage);
|
722
|
+
},
|
723
|
+
signal: function (type, data) {
|
724
|
+
storage.setItem(name, atmosphere.util.stringifyJSON({
|
725
|
+
target: "c",
|
726
|
+
type: type,
|
727
|
+
data: data
|
728
|
+
}));
|
729
|
+
},
|
730
|
+
get: function (key) {
|
731
|
+
return atmosphere.util.parseJSON(storage.getItem(name + "-" + key));
|
732
|
+
},
|
733
|
+
set: function (key, value) {
|
734
|
+
storage.setItem(name + "-" + key, atmosphere.util.stringifyJSON(value));
|
735
|
+
},
|
736
|
+
close: function () {
|
737
|
+
atmosphere.util.off(window, "storage", onstorage);
|
738
|
+
storage.removeItem(name);
|
739
|
+
storage.removeItem(name + "-opened");
|
740
|
+
storage.removeItem(name + "-children");
|
741
|
+
}
|
742
|
+
|
743
|
+
};
|
744
|
+
},
|
745
|
+
// Powered by the window.open method
|
746
|
+
// https://developer.mozilla.org/en/DOM/window.open
|
747
|
+
windowref: function () {
|
748
|
+
// Internet Explorer raises an invalid argument error
|
749
|
+
// when calling the window.open method with the name containing non-word characters
|
750
|
+
var neim = name.replace(/\W/g, ""), container = document.getElementById(neim), win;
|
751
|
+
|
752
|
+
if (!container) {
|
753
|
+
container = document.createElement("div");
|
754
|
+
container.id = neim;
|
755
|
+
container.style.display = "none";
|
756
|
+
container.innerHTML = '<iframe name="' + neim + '" />';
|
757
|
+
document.body.appendChild(container);
|
758
|
+
}
|
759
|
+
|
760
|
+
win = container.firstChild.contentWindow;
|
761
|
+
|
762
|
+
return {
|
763
|
+
init: function () {
|
764
|
+
// Callbacks from different windows
|
765
|
+
win.callbacks = [listener];
|
766
|
+
// In IE 8 and less, only string argument can be safely passed to the function in other window
|
767
|
+
win.fire = function (string) {
|
768
|
+
var i;
|
769
|
+
|
770
|
+
for (i = 0; i < win.callbacks.length; i++) {
|
771
|
+
win.callbacks[i](string);
|
772
|
+
}
|
773
|
+
};
|
774
|
+
},
|
775
|
+
signal: function (type, data) {
|
776
|
+
if (!win.closed && win.fire) {
|
777
|
+
win.fire(atmosphere.util.stringifyJSON({
|
778
|
+
target: "c",
|
779
|
+
type: type,
|
780
|
+
data: data
|
781
|
+
}));
|
782
|
+
}
|
783
|
+
},
|
784
|
+
get: function (key) {
|
785
|
+
return !win.closed ? win[key] : null;
|
786
|
+
},
|
787
|
+
set: function (key, value) {
|
788
|
+
if (!win.closed) {
|
789
|
+
win[key] = value;
|
790
|
+
}
|
791
|
+
},
|
792
|
+
close: function () {
|
793
|
+
}
|
794
|
+
};
|
795
|
+
}
|
796
|
+
};
|
797
|
+
|
798
|
+
// Receives send and close command from the children
|
799
|
+
function listener(string) {
|
800
|
+
var command = atmosphere.util.parseJSON(string), data = command.data;
|
801
|
+
|
802
|
+
if (command.target === "p") {
|
803
|
+
switch (command.type) {
|
804
|
+
case "send":
|
805
|
+
_push(data);
|
806
|
+
break;
|
807
|
+
case "localSend":
|
808
|
+
_localMessage(data);
|
809
|
+
break;
|
810
|
+
case "close":
|
811
|
+
_close();
|
812
|
+
break;
|
813
|
+
}
|
814
|
+
}
|
815
|
+
}
|
816
|
+
|
817
|
+
_localSocketF = function propagateMessageEvent(context) {
|
818
|
+
storageService.signal("message", context);
|
819
|
+
};
|
820
|
+
|
821
|
+
function leaveTrace() {
|
822
|
+
document.cookie = _sharingKey + "=" +
|
823
|
+
// Opera's JSON implementation ignores a number whose a last digit of 0 strangely
|
824
|
+
// but has no problem with a number whose a last digit of 9 + 1
|
825
|
+
encodeURIComponent(atmosphere.util.stringifyJSON({
|
826
|
+
ts: atmosphere.util.now() + 1,
|
827
|
+
heir: (storageService.get("children") || [])[0]
|
828
|
+
})) + "; path=/";
|
829
|
+
}
|
830
|
+
|
831
|
+
// Chooses a storageService
|
832
|
+
storageService = servers.storage() || servers.windowref();
|
833
|
+
storageService.init();
|
834
|
+
|
835
|
+
if (_request.logLevel === 'debug') {
|
836
|
+
atmosphere.util.debug("Installed StorageService " + storageService);
|
837
|
+
}
|
838
|
+
|
839
|
+
// List of children sockets
|
840
|
+
storageService.set("children", []);
|
841
|
+
|
842
|
+
if (storageService.get("opened") != null && !storageService.get("opened")) {
|
843
|
+
// Flag indicating the parent socket is opened
|
844
|
+
storageService.set("opened", false);
|
845
|
+
}
|
846
|
+
// Leaves traces
|
847
|
+
_sharingKey = encodeURIComponent(name);
|
848
|
+
leaveTrace();
|
849
|
+
_traceTimer = setInterval(leaveTrace, 1000);
|
850
|
+
|
851
|
+
_storageService = storageService;
|
852
|
+
}
|
853
|
+
|
854
|
+
/**
|
855
|
+
* @private
|
856
|
+
*/
|
857
|
+
function _open(state, transport, request) {
|
858
|
+
if (_request.shared && transport !== 'local') {
|
859
|
+
share();
|
860
|
+
}
|
861
|
+
|
862
|
+
if (_storageService != null) {
|
863
|
+
_storageService.set("opened", true);
|
864
|
+
}
|
865
|
+
|
866
|
+
request.close = function () {
|
867
|
+
_close();
|
868
|
+
};
|
869
|
+
|
870
|
+
if (_requestCount > 0 && state === 're-connecting') {
|
871
|
+
request.isReopen = true;
|
872
|
+
_tryingToReconnect(_response);
|
873
|
+
} else if (_response.error == null) {
|
874
|
+
_response.request = request;
|
875
|
+
var prevState = _response.state;
|
876
|
+
_response.state = state;
|
877
|
+
var prevTransport = _response.transport;
|
878
|
+
_response.transport = transport;
|
879
|
+
|
880
|
+
var _body = _response.responseBody;
|
881
|
+
_invokeCallback();
|
882
|
+
_response.responseBody = _body;
|
883
|
+
|
884
|
+
_response.state = prevState;
|
885
|
+
_response.transport = prevTransport;
|
886
|
+
}
|
887
|
+
}
|
888
|
+
|
889
|
+
/**
|
890
|
+
* Execute request using jsonp transport.
|
891
|
+
*
|
892
|
+
* @param request {Object} request Request parameters, if undefined _request object will be used.
|
893
|
+
* @private
|
894
|
+
*/
|
895
|
+
function _jsonp(request) {
|
896
|
+
// When CORS is enabled, make sure we force the proper transport.
|
897
|
+
request.transport = "jsonp";
|
898
|
+
|
899
|
+
var rq = _request, script;
|
900
|
+
if ((request != null) && (typeof (request) !== 'undefined')) {
|
901
|
+
rq = request;
|
902
|
+
}
|
903
|
+
|
904
|
+
_jqxhr = {
|
905
|
+
open: function () {
|
906
|
+
var callback = "atmosphere" + (++guid);
|
907
|
+
|
908
|
+
function poll() {
|
909
|
+
var url = rq.url;
|
910
|
+
if (rq.dispatchUrl != null) {
|
911
|
+
url += rq.dispatchUrl;
|
912
|
+
}
|
913
|
+
|
914
|
+
var data = rq.data;
|
915
|
+
if (rq.attachHeadersAsQueryString) {
|
916
|
+
url = _attachHeaders(rq);
|
917
|
+
if (data !== '') {
|
918
|
+
url += "&X-Atmosphere-Post-Body=" + encodeURIComponent(data);
|
919
|
+
}
|
920
|
+
data = '';
|
921
|
+
}
|
922
|
+
|
923
|
+
var head = document.head || document.getElementsByTagName("head")[0] || document.documentElement;
|
924
|
+
|
925
|
+
script = document.createElement("script");
|
926
|
+
script.src = url + "&jsonpTransport=" + callback;
|
927
|
+
script.clean = function () {
|
928
|
+
script.clean = script.onerror = script.onload = script.onreadystatechange = null;
|
929
|
+
if (script.parentNode) {
|
930
|
+
script.parentNode.removeChild(script);
|
931
|
+
}
|
932
|
+
};
|
933
|
+
script.onload = script.onreadystatechange = function () {
|
934
|
+
if (!script.readyState || /loaded|complete/.test(script.readyState)) {
|
935
|
+
script.clean();
|
936
|
+
}
|
937
|
+
};
|
938
|
+
script.onerror = function () {
|
939
|
+
script.clean();
|
940
|
+
rq.lastIndex = 0;
|
941
|
+
|
942
|
+
if (rq.openId) {
|
943
|
+
clearTimeout(rq.openId);
|
944
|
+
}
|
945
|
+
|
946
|
+
if (rq.reconnect && _requestCount++ < rq.maxReconnectOnClose) {
|
947
|
+
_open('re-connecting', rq.transport, rq);
|
948
|
+
_reconnect(_jqxhr, rq, request.reconnectInterval);
|
949
|
+
rq.openId = setTimeout(function() {
|
950
|
+
_triggerOpen(rq);
|
951
|
+
}, rq.reconnectInterval + 1000);
|
952
|
+
} else {
|
953
|
+
_onError(0, "maxReconnectOnClose reached");
|
954
|
+
}
|
955
|
+
};
|
956
|
+
|
957
|
+
head.insertBefore(script, head.firstChild);
|
958
|
+
}
|
959
|
+
|
960
|
+
// Attaches callback
|
961
|
+
window[callback] = function (msg) {
|
962
|
+
if (rq.reconnect) {
|
963
|
+
if (rq.maxRequest === -1 || rq.requestCount++ < rq.maxRequest) {
|
964
|
+
// _readHeaders(_jqxhr, rq);
|
965
|
+
|
966
|
+
if (!rq.executeCallbackBeforeReconnect) {
|
967
|
+
_reconnect(_jqxhr, rq, rq.pollingInterval);
|
968
|
+
}
|
969
|
+
|
970
|
+
if (msg != null && typeof msg !== 'string') {
|
971
|
+
try {
|
972
|
+
msg = msg.message;
|
973
|
+
} catch (err) {
|
974
|
+
// The message was partial
|
975
|
+
}
|
976
|
+
}
|
977
|
+
|
978
|
+
var skipCallbackInvocation = _trackMessageSize(msg, rq, _response);
|
979
|
+
if (!skipCallbackInvocation) {
|
980
|
+
_prepareCallback(_response.responseBody, "messageReceived", 200, rq.transport);
|
981
|
+
}
|
982
|
+
|
983
|
+
if (rq.executeCallbackBeforeReconnect) {
|
984
|
+
_reconnect(_jqxhr, rq, rq.pollingInterval);
|
985
|
+
}
|
986
|
+
} else {
|
987
|
+
atmosphere.util.log(_request.logLevel, ["JSONP reconnect maximum try reached " + _request.requestCount]);
|
988
|
+
_onError(0, "maxRequest reached");
|
989
|
+
}
|
990
|
+
}
|
991
|
+
};
|
992
|
+
setTimeout(function () {
|
993
|
+
poll();
|
994
|
+
}, 50);
|
995
|
+
},
|
996
|
+
abort: function () {
|
997
|
+
if (script && script.clean) {
|
998
|
+
script.clean();
|
999
|
+
}
|
1000
|
+
}
|
1001
|
+
};
|
1002
|
+
|
1003
|
+
_jqxhr.open();
|
1004
|
+
}
|
1005
|
+
|
1006
|
+
/**
|
1007
|
+
* Build websocket object.
|
1008
|
+
*
|
1009
|
+
* @param location {string} Web socket url.
|
1010
|
+
* @returns {websocket} Web socket object.
|
1011
|
+
* @private
|
1012
|
+
*/
|
1013
|
+
function _getWebSocket(location) {
|
1014
|
+
if (_request.webSocketImpl != null) {
|
1015
|
+
return _request.webSocketImpl;
|
1016
|
+
} else {
|
1017
|
+
if (window.WebSocket) {
|
1018
|
+
return new WebSocket(location);
|
1019
|
+
} else {
|
1020
|
+
return new MozWebSocket(location);
|
1021
|
+
}
|
1022
|
+
}
|
1023
|
+
}
|
1024
|
+
|
1025
|
+
/**
|
1026
|
+
* Build web socket url from request url.
|
1027
|
+
*
|
1028
|
+
* @return {string} Web socket url (start with "ws" or "wss" for secure web socket).
|
1029
|
+
* @private
|
1030
|
+
*/
|
1031
|
+
function _buildWebSocketUrl() {
|
1032
|
+
return _attachHeaders(_request, atmosphere.util.getAbsoluteURL(_request.url)).replace(/^http/, "ws");
|
1033
|
+
}
|
1034
|
+
|
1035
|
+
/**
|
1036
|
+
* Build SSE url from request url.
|
1037
|
+
*
|
1038
|
+
* @return a url with Atmosphere's headers
|
1039
|
+
* @private
|
1040
|
+
*/
|
1041
|
+
function _buildSSEUrl() {
|
1042
|
+
var url = _attachHeaders(_request);
|
1043
|
+
return url;
|
1044
|
+
}
|
1045
|
+
|
1046
|
+
/**
|
1047
|
+
* Open SSE. <br>
|
1048
|
+
* Automatically use fallback transport if SSE can't be opened.
|
1049
|
+
*
|
1050
|
+
* @private
|
1051
|
+
*/
|
1052
|
+
function _executeSSE(sseOpened) {
|
1053
|
+
|
1054
|
+
_response.transport = "sse";
|
1055
|
+
|
1056
|
+
var location = _buildSSEUrl();
|
1057
|
+
|
1058
|
+
if (_request.logLevel === 'debug') {
|
1059
|
+
atmosphere.util.debug("Invoking executeSSE");
|
1060
|
+
atmosphere.util.debug("Using URL: " + location);
|
1061
|
+
}
|
1062
|
+
|
1063
|
+
if (_request.enableProtocol && sseOpened) {
|
1064
|
+
var time = atmosphere.util.now() - _request.ctime;
|
1065
|
+
_request.lastTimestamp = Number(_request.stime) + Number(time);
|
1066
|
+
}
|
1067
|
+
|
1068
|
+
if (sseOpened && !_request.reconnect) {
|
1069
|
+
if (_sse != null) {
|
1070
|
+
_clearState();
|
1071
|
+
}
|
1072
|
+
return;
|
1073
|
+
}
|
1074
|
+
|
1075
|
+
try {
|
1076
|
+
_sse = new EventSource(location, {
|
1077
|
+
withCredentials: _request.withCredentials
|
1078
|
+
});
|
1079
|
+
} catch (e) {
|
1080
|
+
_onError(0, e);
|
1081
|
+
_reconnectWithFallbackTransport("SSE failed. Downgrading to fallback transport and resending");
|
1082
|
+
return;
|
1083
|
+
}
|
1084
|
+
|
1085
|
+
if (_request.connectTimeout > 0) {
|
1086
|
+
_request.id = setTimeout(function () {
|
1087
|
+
if (!sseOpened) {
|
1088
|
+
_clearState();
|
1089
|
+
}
|
1090
|
+
}, _request.connectTimeout);
|
1091
|
+
}
|
1092
|
+
|
1093
|
+
_sse.onopen = function (event) {
|
1094
|
+
_timeout(_request);
|
1095
|
+
if (_request.logLevel === 'debug') {
|
1096
|
+
atmosphere.util.debug("SSE successfully opened");
|
1097
|
+
}
|
1098
|
+
|
1099
|
+
if (!_request.enableProtocol) {
|
1100
|
+
if (!sseOpened) {
|
1101
|
+
_open('opening', "sse", _request);
|
1102
|
+
} else {
|
1103
|
+
_open('re-opening', "sse", _request);
|
1104
|
+
}
|
1105
|
+
} else if (_request.isReopen) {
|
1106
|
+
_request.isReopen = false;
|
1107
|
+
_open('re-opening', _request.transport, _request);
|
1108
|
+
}
|
1109
|
+
|
1110
|
+
sseOpened = true;
|
1111
|
+
|
1112
|
+
if (_request.method === 'POST') {
|
1113
|
+
_response.state = "messageReceived";
|
1114
|
+
_sse.send(_request.data);
|
1115
|
+
}
|
1116
|
+
};
|
1117
|
+
|
1118
|
+
_sse.onmessage = function (message) {
|
1119
|
+
_timeout(_request);
|
1120
|
+
|
1121
|
+
if (!_request.enableXDR && message.origin && message.origin !== window.location.protocol + "//" + window.location.host) {
|
1122
|
+
atmosphere.util.log(_request.logLevel, ["Origin was not " + window.location.protocol + "//" + window.location.host]);
|
1123
|
+
return;
|
1124
|
+
}
|
1125
|
+
|
1126
|
+
_response.state = 'messageReceived';
|
1127
|
+
_response.status = 200;
|
1128
|
+
|
1129
|
+
message = message.data;
|
1130
|
+
var skipCallbackInvocation = _trackMessageSize(message, _request, _response);
|
1131
|
+
|
1132
|
+
// https://github.com/remy/polyfills/blob/master/EventSource.js
|
1133
|
+
// Since we polling.
|
1134
|
+
/* if (_sse.URL) {
|
1135
|
+
_sse.interval = 100;
|
1136
|
+
_sse.URL = _buildSSEUrl();
|
1137
|
+
} */
|
1138
|
+
|
1139
|
+
if (!skipCallbackInvocation) {
|
1140
|
+
_invokeCallback();
|
1141
|
+
_response.responseBody = '';
|
1142
|
+
_response.messages = [];
|
1143
|
+
}
|
1144
|
+
};
|
1145
|
+
|
1146
|
+
_sse.onerror = function (message) {
|
1147
|
+
clearTimeout(_request.id);
|
1148
|
+
|
1149
|
+
if (_response.closedByClientTimeout) return;
|
1150
|
+
|
1151
|
+
_invokeClose(sseOpened);
|
1152
|
+
_clearState();
|
1153
|
+
|
1154
|
+
if (_abordingConnection) {
|
1155
|
+
atmosphere.util.log(_request.logLevel, ["SSE closed normally"]);
|
1156
|
+
} else if (!sseOpened) {
|
1157
|
+
_reconnectWithFallbackTransport("SSE failed. Downgrading to fallback transport and resending");
|
1158
|
+
} else if (_request.reconnect && (_response.transport === 'sse')) {
|
1159
|
+
if (_requestCount++ < _request.maxReconnectOnClose) {
|
1160
|
+
_open('re-connecting', _request.transport, _request);
|
1161
|
+
if (_request.reconnectInterval > 0) {
|
1162
|
+
_request.reconnectId = setTimeout(function () {
|
1163
|
+
_executeSSE(true);
|
1164
|
+
}, _request.reconnectInterval);
|
1165
|
+
} else {
|
1166
|
+
_executeSSE(true);
|
1167
|
+
}
|
1168
|
+
_response.responseBody = "";
|
1169
|
+
_response.messages = [];
|
1170
|
+
} else {
|
1171
|
+
atmosphere.util.log(_request.logLevel, ["SSE reconnect maximum try reached " + _requestCount]);
|
1172
|
+
_onError(0, "maxReconnectOnClose reached");
|
1173
|
+
}
|
1174
|
+
}
|
1175
|
+
};
|
1176
|
+
}
|
1177
|
+
|
1178
|
+
/**
|
1179
|
+
* Open web socket. <br>
|
1180
|
+
* Automatically use fallback transport if web socket can't be opened.
|
1181
|
+
*
|
1182
|
+
* @private
|
1183
|
+
*/
|
1184
|
+
function _executeWebSocket(webSocketOpened) {
|
1185
|
+
|
1186
|
+
_response.transport = "websocket";
|
1187
|
+
|
1188
|
+
if (_request.enableProtocol && webSocketOpened) {
|
1189
|
+
var time = atmosphere.util.now() - _request.ctime;
|
1190
|
+
_request.lastTimestamp = Number(_request.stime) + Number(time);
|
1191
|
+
}
|
1192
|
+
|
1193
|
+
var location = _buildWebSocketUrl(_request.url);
|
1194
|
+
if (_request.logLevel === 'debug') {
|
1195
|
+
atmosphere.util.debug("Invoking executeWebSocket");
|
1196
|
+
atmosphere.util.debug("Using URL: " + location);
|
1197
|
+
}
|
1198
|
+
|
1199
|
+
if (webSocketOpened && !_request.reconnect) {
|
1200
|
+
if (_websocket != null) {
|
1201
|
+
_clearState();
|
1202
|
+
}
|
1203
|
+
return;
|
1204
|
+
}
|
1205
|
+
|
1206
|
+
_websocket = _getWebSocket(location);
|
1207
|
+
if (_request.webSocketBinaryType != null) {
|
1208
|
+
_websocket.binaryType = _request.webSocketBinaryType;
|
1209
|
+
}
|
1210
|
+
|
1211
|
+
if (_request.connectTimeout > 0) {
|
1212
|
+
_request.id = setTimeout(function () {
|
1213
|
+
if (!webSocketOpened) {
|
1214
|
+
var _message = {
|
1215
|
+
code: 1002,
|
1216
|
+
reason: "",
|
1217
|
+
wasClean: false
|
1218
|
+
};
|
1219
|
+
_websocket.onclose(_message);
|
1220
|
+
// Close it anyway
|
1221
|
+
try {
|
1222
|
+
_clearState();
|
1223
|
+
} catch (e) {
|
1224
|
+
}
|
1225
|
+
return;
|
1226
|
+
}
|
1227
|
+
|
1228
|
+
}, _request.connectTimeout);
|
1229
|
+
}
|
1230
|
+
|
1231
|
+
_websocket.onopen = function (message) {
|
1232
|
+
_timeout(_request);
|
1233
|
+
|
1234
|
+
if (_request.logLevel === 'debug') {
|
1235
|
+
atmosphere.util.debug("Websocket successfully opened");
|
1236
|
+
}
|
1237
|
+
|
1238
|
+
var reopening = webSocketOpened;
|
1239
|
+
|
1240
|
+
if(_websocket != null) {
|
1241
|
+
_websocket.canSendMessage = true;
|
1242
|
+
}
|
1243
|
+
|
1244
|
+
if (!_request.enableProtocol) {
|
1245
|
+
webSocketOpened = true;
|
1246
|
+
if (reopening) {
|
1247
|
+
_open('re-opening', "websocket", _request);
|
1248
|
+
} else {
|
1249
|
+
_open('opening', "websocket", _request);
|
1250
|
+
}
|
1251
|
+
}
|
1252
|
+
|
1253
|
+
if (_websocket != null) {
|
1254
|
+
if (_request.method === 'POST') {
|
1255
|
+
_response.state = "messageReceived";
|
1256
|
+
_websocket.send(_request.data);
|
1257
|
+
}
|
1258
|
+
}
|
1259
|
+
};
|
1260
|
+
|
1261
|
+
_websocket.onmessage = function (message) {
|
1262
|
+
_timeout(_request);
|
1263
|
+
|
1264
|
+
// We only consider it opened if we get the handshake data
|
1265
|
+
// https://github.com/Atmosphere/atmosphere-javascript/issues/74
|
1266
|
+
if (_request.enableProtocol) {
|
1267
|
+
webSocketOpened = true;
|
1268
|
+
}
|
1269
|
+
|
1270
|
+
_response.state = 'messageReceived';
|
1271
|
+
_response.status = 200;
|
1272
|
+
|
1273
|
+
message = message.data;
|
1274
|
+
var isString = typeof (message) === 'string';
|
1275
|
+
if (isString) {
|
1276
|
+
var skipCallbackInvocation = _trackMessageSize(message, _request, _response);
|
1277
|
+
if (!skipCallbackInvocation) {
|
1278
|
+
_invokeCallback();
|
1279
|
+
_response.responseBody = '';
|
1280
|
+
_response.messages = [];
|
1281
|
+
}
|
1282
|
+
} else {
|
1283
|
+
if (!_handleProtocol(_request, message))
|
1284
|
+
return;
|
1285
|
+
|
1286
|
+
_response.responseBody = message;
|
1287
|
+
_invokeCallback();
|
1288
|
+
_response.responseBody = null;
|
1289
|
+
}
|
1290
|
+
};
|
1291
|
+
|
1292
|
+
_websocket.onerror = function (message) {
|
1293
|
+
clearTimeout(_request.id);
|
1294
|
+
};
|
1295
|
+
|
1296
|
+
_websocket.onclose = function (message) {
|
1297
|
+
clearTimeout(_request.id);
|
1298
|
+
if (_response.state === 'closed')
|
1299
|
+
return;
|
1300
|
+
|
1301
|
+
var reason = message.reason;
|
1302
|
+
if (reason === "") {
|
1303
|
+
switch (message.code) {
|
1304
|
+
case 1000:
|
1305
|
+
reason = "Normal closure; the connection successfully completed whatever purpose for which " + "it was created.";
|
1306
|
+
break;
|
1307
|
+
case 1001:
|
1308
|
+
reason = "The endpoint is going away, either because of a server failure or because the "
|
1309
|
+
+ "browser is navigating away from the page that opened the connection.";
|
1310
|
+
break;
|
1311
|
+
case 1002:
|
1312
|
+
reason = "The endpoint is terminating the connection due to a protocol error.";
|
1313
|
+
break;
|
1314
|
+
case 1003:
|
1315
|
+
reason = "The connection is being terminated because the endpoint received data of a type it "
|
1316
|
+
+ "cannot accept (for example, a text-only endpoint received binary data).";
|
1317
|
+
break;
|
1318
|
+
case 1004:
|
1319
|
+
reason = "The endpoint is terminating the connection because a data frame was received that " + "is too large.";
|
1320
|
+
break;
|
1321
|
+
case 1005:
|
1322
|
+
reason = "Unknown: no status code was provided even though one was expected.";
|
1323
|
+
break;
|
1324
|
+
case 1006:
|
1325
|
+
reason = "Connection was closed abnormally (that is, with no close frame being sent).";
|
1326
|
+
break;
|
1327
|
+
}
|
1328
|
+
}
|
1329
|
+
|
1330
|
+
if (_request.logLevel === 'warn') {
|
1331
|
+
atmosphere.util.warn("Websocket closed, reason: " + reason);
|
1332
|
+
atmosphere.util.warn("Websocket closed, wasClean: " + message.wasClean);
|
1333
|
+
}
|
1334
|
+
|
1335
|
+
if (_response.closedByClientTimeout) {
|
1336
|
+
return;
|
1337
|
+
}
|
1338
|
+
|
1339
|
+
_invokeClose(webSocketOpened);
|
1340
|
+
|
1341
|
+
_response.state = 'closed';
|
1342
|
+
|
1343
|
+
if (_abordingConnection) {
|
1344
|
+
atmosphere.util.log(_request.logLevel, ["Websocket closed normally"]);
|
1345
|
+
} else if (!webSocketOpened) {
|
1346
|
+
_reconnectWithFallbackTransport("Websocket failed. Downgrading to Comet and resending");
|
1347
|
+
|
1348
|
+
} else if (_request.reconnect && _response.transport === 'websocket') {
|
1349
|
+
_clearState();
|
1350
|
+
if (_requestCount++ < _request.maxReconnectOnClose) {
|
1351
|
+
_open('re-connecting', _request.transport, _request);
|
1352
|
+
if (_request.reconnectInterval > 0) {
|
1353
|
+
_request.reconnectId = setTimeout(function () {
|
1354
|
+
_response.responseBody = "";
|
1355
|
+
_response.messages = [];
|
1356
|
+
_executeWebSocket(true);
|
1357
|
+
}, _request.reconnectInterval);
|
1358
|
+
} else {
|
1359
|
+
_response.responseBody = "";
|
1360
|
+
_response.messages = [];
|
1361
|
+
_executeWebSocket(true);
|
1362
|
+
}
|
1363
|
+
} else {
|
1364
|
+
atmosphere.util.log(_request.logLevel, ["Websocket reconnect maximum try reached " + _request.requestCount]);
|
1365
|
+
if (_request.logLevel === 'warn') {
|
1366
|
+
atmosphere.util.warn("Websocket error, reason: " + message.reason);
|
1367
|
+
}
|
1368
|
+
_onError(0, "maxReconnectOnClose reached");
|
1369
|
+
}
|
1370
|
+
}
|
1371
|
+
};
|
1372
|
+
|
1373
|
+
var ua = navigator.userAgent.toLowerCase();
|
1374
|
+
var isAndroid = ua.indexOf("android") > -1;
|
1375
|
+
if (isAndroid && _websocket.url === undefined) {
|
1376
|
+
// Android 4.1 does not really support websockets and fails silently
|
1377
|
+
_websocket.onclose({
|
1378
|
+
reason: "Android 4.1 does not support websockets.",
|
1379
|
+
wasClean: false
|
1380
|
+
});
|
1381
|
+
}
|
1382
|
+
}
|
1383
|
+
|
1384
|
+
function _handleProtocol(request, message) {
|
1385
|
+
|
1386
|
+
// The first messages is always the uuid.
|
1387
|
+
var b = true;
|
1388
|
+
|
1389
|
+
if (request.transport === 'polling') return b;
|
1390
|
+
|
1391
|
+
if (atmosphere.util.trim(message).length !== 0 && request.enableProtocol && request.firstMessage) {
|
1392
|
+
request.firstMessage = false;
|
1393
|
+
var messages = message.split(request.messageDelimiter);
|
1394
|
+
var pos = messages.length === 2 ? 0 : 1;
|
1395
|
+
request.uuid = atmosphere.util.trim(messages[pos]);
|
1396
|
+
request.stime = atmosphere.util.trim(messages[pos + 1]);
|
1397
|
+
b = false;
|
1398
|
+
if (request.transport !== 'long-polling') {
|
1399
|
+
_triggerOpen(request);
|
1400
|
+
}
|
1401
|
+
uuid = request.uuid;
|
1402
|
+
} else if (request.enableProtocol && request.firstMessage) {
|
1403
|
+
// In case we are getting some junk from IE
|
1404
|
+
b = false;
|
1405
|
+
} else {
|
1406
|
+
_triggerOpen(request);
|
1407
|
+
}
|
1408
|
+
return b;
|
1409
|
+
}
|
1410
|
+
|
1411
|
+
function _timeout(_request) {
|
1412
|
+
clearTimeout(_request.id);
|
1413
|
+
if (_request.timeout > 0 && _request.transport !== 'polling') {
|
1414
|
+
_request.id = setTimeout(function () {
|
1415
|
+
_onClientTimeout(_request);
|
1416
|
+
_disconnect();
|
1417
|
+
_clearState();
|
1418
|
+
}, _request.timeout);
|
1419
|
+
}
|
1420
|
+
}
|
1421
|
+
|
1422
|
+
function _onClientTimeout(_request) {
|
1423
|
+
_response.closedByClientTimeout = true;
|
1424
|
+
_response.state = 'closedByClient';
|
1425
|
+
_response.responseBody = "";
|
1426
|
+
_response.status = 408;
|
1427
|
+
_response.messages = [];
|
1428
|
+
_invokeCallback();
|
1429
|
+
}
|
1430
|
+
|
1431
|
+
function _onError(code, reason) {
|
1432
|
+
_clearState();
|
1433
|
+
clearTimeout(_request.id);
|
1434
|
+
_response.state = 'error';
|
1435
|
+
_response.reasonPhrase = reason;
|
1436
|
+
_response.responseBody = "";
|
1437
|
+
_response.status = code;
|
1438
|
+
_response.messages = [];
|
1439
|
+
_invokeCallback();
|
1440
|
+
}
|
1441
|
+
|
1442
|
+
/**
|
1443
|
+
* Track received message and make sure callbacks/functions are only invoked when the complete message has been received.
|
1444
|
+
*
|
1445
|
+
* @param message
|
1446
|
+
* @param request
|
1447
|
+
* @param response
|
1448
|
+
*/
|
1449
|
+
function _trackMessageSize(message, request, response) {
|
1450
|
+
if (!_handleProtocol(request, message))
|
1451
|
+
return true;
|
1452
|
+
if (message.length === 0)
|
1453
|
+
return true;
|
1454
|
+
|
1455
|
+
if (request.trackMessageLength) {
|
1456
|
+
// prepend partialMessage if any
|
1457
|
+
message = response.partialMessage + message;
|
1458
|
+
|
1459
|
+
var messages = [];
|
1460
|
+
var messageStart = message.indexOf(request.messageDelimiter);
|
1461
|
+
while (messageStart !== -1) {
|
1462
|
+
var str = atmosphere.util.trim(message.substring(0, messageStart));
|
1463
|
+
var messageLength = +str;
|
1464
|
+
if (isNaN(messageLength))
|
1465
|
+
throw new Error('message length "' + str + '" is not a number');
|
1466
|
+
messageStart += request.messageDelimiter.length;
|
1467
|
+
if (messageStart + messageLength > message.length) {
|
1468
|
+
// message not complete, so there is no trailing messageDelimiter
|
1469
|
+
messageStart = -1;
|
1470
|
+
} else {
|
1471
|
+
// message complete, so add it
|
1472
|
+
messages.push(message.substring(messageStart, messageStart + messageLength));
|
1473
|
+
// remove consumed characters
|
1474
|
+
message = message.substring(messageStart + messageLength, message.length);
|
1475
|
+
messageStart = message.indexOf(request.messageDelimiter);
|
1476
|
+
}
|
1477
|
+
}
|
1478
|
+
|
1479
|
+
/* keep any remaining data */
|
1480
|
+
response.partialMessage = message;
|
1481
|
+
|
1482
|
+
if (messages.length !== 0) {
|
1483
|
+
response.responseBody = messages.join(request.messageDelimiter);
|
1484
|
+
response.messages = messages;
|
1485
|
+
return false;
|
1486
|
+
} else {
|
1487
|
+
response.responseBody = "";
|
1488
|
+
response.messages = [];
|
1489
|
+
return true;
|
1490
|
+
}
|
1491
|
+
} else {
|
1492
|
+
response.responseBody = message;
|
1493
|
+
}
|
1494
|
+
return false;
|
1495
|
+
}
|
1496
|
+
|
1497
|
+
/**
|
1498
|
+
* Reconnect request with fallback transport. <br>
|
1499
|
+
* Used in case websocket can't be opened.
|
1500
|
+
*
|
1501
|
+
* @private
|
1502
|
+
*/
|
1503
|
+
function _reconnectWithFallbackTransport(errorMessage) {
|
1504
|
+
atmosphere.util.log(_request.logLevel, [errorMessage]);
|
1505
|
+
|
1506
|
+
if (typeof (_request.onTransportFailure) !== 'undefined') {
|
1507
|
+
_request.onTransportFailure(errorMessage, _request);
|
1508
|
+
} else if (typeof (atmosphere.util.onTransportFailure) !== 'undefined') {
|
1509
|
+
atmosphere.util.onTransportFailure(errorMessage, _request);
|
1510
|
+
}
|
1511
|
+
|
1512
|
+
_request.transport = _request.fallbackTransport;
|
1513
|
+
var reconnectInterval = _request.connectTimeout === -1 ? 0 : _request.connectTimeout;
|
1514
|
+
if (_request.reconnect && _request.transport !== 'none' || _request.transport == null) {
|
1515
|
+
_request.method = _request.fallbackMethod;
|
1516
|
+
_response.transport = _request.fallbackTransport;
|
1517
|
+
_request.fallbackTransport = 'none';
|
1518
|
+
if (reconnectInterval > 0) {
|
1519
|
+
_request.reconnectId = setTimeout(function () {
|
1520
|
+
_execute();
|
1521
|
+
}, reconnectInterval);
|
1522
|
+
} else {
|
1523
|
+
_execute();
|
1524
|
+
}
|
1525
|
+
} else {
|
1526
|
+
_onError(500, "Unable to reconnect with fallback transport");
|
1527
|
+
}
|
1528
|
+
}
|
1529
|
+
|
1530
|
+
/**
|
1531
|
+
* Get url from request and attach headers to it.
|
1532
|
+
*
|
1533
|
+
* @param request {Object} request Request parameters, if undefined _request object will be used.
|
1534
|
+
*
|
1535
|
+
* @returns {Object} Request object, if undefined, _request object will be used.
|
1536
|
+
* @private
|
1537
|
+
*/
|
1538
|
+
function _attachHeaders(request, url) {
|
1539
|
+
var rq = _request;
|
1540
|
+
if ((request != null) && (typeof (request) !== 'undefined')) {
|
1541
|
+
rq = request;
|
1542
|
+
}
|
1543
|
+
|
1544
|
+
if (url == null) {
|
1545
|
+
url = rq.url;
|
1546
|
+
}
|
1547
|
+
|
1548
|
+
// If not enabled
|
1549
|
+
if (!rq.attachHeadersAsQueryString)
|
1550
|
+
return url;
|
1551
|
+
|
1552
|
+
// If already added
|
1553
|
+
if (url.indexOf("X-Atmosphere-Framework") !== -1) {
|
1554
|
+
return url;
|
1555
|
+
}
|
1556
|
+
|
1557
|
+
url += (url.indexOf('?') !== -1) ? '&' : '?';
|
1558
|
+
url += "X-Atmosphere-tracking-id=" + rq.uuid;
|
1559
|
+
url += "&X-Atmosphere-Framework=" + version;
|
1560
|
+
url += "&X-Atmosphere-Transport=" + rq.transport;
|
1561
|
+
|
1562
|
+
if (rq.trackMessageLength) {
|
1563
|
+
url += "&X-Atmosphere-TrackMessageSize=" + "true";
|
1564
|
+
}
|
1565
|
+
|
1566
|
+
if (rq.lastTimestamp != null) {
|
1567
|
+
url += "&X-Cache-Date=" + rq.lastTimestamp;
|
1568
|
+
} else {
|
1569
|
+
url += "&X-Cache-Date=" + 0;
|
1570
|
+
}
|
1571
|
+
|
1572
|
+
if (rq.contentType !== '') {
|
1573
|
+
//Eurk!
|
1574
|
+
url += "&Content-Type=" + (rq.transport === 'websocket' ? rq.contentType : encodeURIComponent(rq.contentType));
|
1575
|
+
}
|
1576
|
+
|
1577
|
+
if (rq.enableProtocol) {
|
1578
|
+
url += "&X-atmo-protocol=true";
|
1579
|
+
}
|
1580
|
+
|
1581
|
+
atmosphere.util.each(rq.headers, function (name, value) {
|
1582
|
+
var h = atmosphere.util.isFunction(value) ? value.call(this, rq, request, _response) : value;
|
1583
|
+
if (h != null) {
|
1584
|
+
url += "&" + encodeURIComponent(name) + "=" + encodeURIComponent(h);
|
1585
|
+
}
|
1586
|
+
});
|
1587
|
+
|
1588
|
+
return url;
|
1589
|
+
}
|
1590
|
+
|
1591
|
+
function _triggerOpen(rq) {
|
1592
|
+
if (!rq.isOpen) {
|
1593
|
+
rq.isOpen = true;
|
1594
|
+
_open('opening', rq.transport, rq);
|
1595
|
+
} else if (rq.isReopen) {
|
1596
|
+
rq.isReopen = false;
|
1597
|
+
_open('re-opening', rq.transport, rq);
|
1598
|
+
}
|
1599
|
+
}
|
1600
|
+
|
1601
|
+
/**
|
1602
|
+
* Execute ajax request. <br>
|
1603
|
+
*
|
1604
|
+
* @param request {Object} request Request parameters, if undefined _request object will be used.
|
1605
|
+
* @private
|
1606
|
+
*/
|
1607
|
+
function _executeRequest(request) {
|
1608
|
+
var rq = _request;
|
1609
|
+
if ((request != null) || (typeof (request) !== 'undefined')) {
|
1610
|
+
rq = request;
|
1611
|
+
}
|
1612
|
+
|
1613
|
+
rq.lastIndex = 0;
|
1614
|
+
rq.readyState = 0;
|
1615
|
+
|
1616
|
+
// CORS fake using JSONP
|
1617
|
+
if ((rq.transport === 'jsonp') || ((rq.enableXDR) && (atmosphere.util.checkCORSSupport()))) {
|
1618
|
+
_jsonp(rq);
|
1619
|
+
return;
|
1620
|
+
}
|
1621
|
+
|
1622
|
+
if (atmosphere.util.browser.msie && +atmosphere.util.browser.version.split(".")[0] < 10) {
|
1623
|
+
if ((rq.transport === 'streaming')) {
|
1624
|
+
if (rq.enableXDR && window.XDomainRequest) {
|
1625
|
+
_ieXDR(rq);
|
1626
|
+
} else {
|
1627
|
+
_ieStreaming(rq);
|
1628
|
+
}
|
1629
|
+
return;
|
1630
|
+
}
|
1631
|
+
|
1632
|
+
if ((rq.enableXDR) && (window.XDomainRequest)) {
|
1633
|
+
_ieXDR(rq);
|
1634
|
+
return;
|
1635
|
+
}
|
1636
|
+
}
|
1637
|
+
|
1638
|
+
var reconnectF = function () {
|
1639
|
+
rq.lastIndex = 0;
|
1640
|
+
if (rq.reconnect && _requestCount++ < rq.maxReconnectOnClose) {
|
1641
|
+
_open('re-connecting', request.transport, request);
|
1642
|
+
_reconnect(ajaxRequest, rq, request.reconnectInterval);
|
1643
|
+
} else {
|
1644
|
+
_onError(0, "maxReconnectOnClose reached");
|
1645
|
+
}
|
1646
|
+
};
|
1647
|
+
|
1648
|
+
if (rq.force || (rq.reconnect && (rq.maxRequest === -1 || rq.requestCount++ < rq.maxRequest))) {
|
1649
|
+
rq.force = false;
|
1650
|
+
|
1651
|
+
var ajaxRequest = atmosphere.util.xhr();
|
1652
|
+
ajaxRequest.hasData = false;
|
1653
|
+
|
1654
|
+
_doRequest(ajaxRequest, rq, true);
|
1655
|
+
|
1656
|
+
if (rq.suspend) {
|
1657
|
+
_activeRequest = ajaxRequest;
|
1658
|
+
}
|
1659
|
+
|
1660
|
+
if (rq.transport !== 'polling') {
|
1661
|
+
_response.transport = rq.transport;
|
1662
|
+
|
1663
|
+
ajaxRequest.onabort = function () {
|
1664
|
+
_invokeClose(true);
|
1665
|
+
};
|
1666
|
+
|
1667
|
+
ajaxRequest.onerror = function () {
|
1668
|
+
_response.error = true;
|
1669
|
+
try {
|
1670
|
+
_response.status = XMLHttpRequest.status;
|
1671
|
+
} catch (e) {
|
1672
|
+
_response.status = 500;
|
1673
|
+
}
|
1674
|
+
|
1675
|
+
if (!_response.status) {
|
1676
|
+
_response.status = 500;
|
1677
|
+
}
|
1678
|
+
if (!_response.errorHandled) {
|
1679
|
+
_clearState();
|
1680
|
+
reconnectF();
|
1681
|
+
}
|
1682
|
+
};
|
1683
|
+
}
|
1684
|
+
|
1685
|
+
ajaxRequest.onreadystatechange = function () {
|
1686
|
+
if (_abordingConnection) {
|
1687
|
+
return;
|
1688
|
+
}
|
1689
|
+
|
1690
|
+
_response.error = null;
|
1691
|
+
var skipCallbackInvocation = false;
|
1692
|
+
var update = false;
|
1693
|
+
|
1694
|
+
if (rq.transport === 'streaming' && rq.readyState > 2 && ajaxRequest.readyState === 4) {
|
1695
|
+
if (rq.reconnectingOnLength) {
|
1696
|
+
return;
|
1697
|
+
}
|
1698
|
+
_clearState();
|
1699
|
+
reconnectF();
|
1700
|
+
return;
|
1701
|
+
}
|
1702
|
+
|
1703
|
+
rq.readyState = ajaxRequest.readyState;
|
1704
|
+
|
1705
|
+
if (rq.transport === 'streaming' && ajaxRequest.readyState >= 3) {
|
1706
|
+
update = true;
|
1707
|
+
} else if (rq.transport === 'long-polling' && ajaxRequest.readyState === 4) {
|
1708
|
+
update = true;
|
1709
|
+
}
|
1710
|
+
_timeout(_request);
|
1711
|
+
|
1712
|
+
if (rq.transport !== 'polling') {
|
1713
|
+
// MSIE 9 and lower status can be higher than 1000, Chrome can be 0
|
1714
|
+
var status = 200;
|
1715
|
+
if (ajaxRequest.readyState === 4) {
|
1716
|
+
status = ajaxRequest.status > 1000 ? 0 : ajaxRequest.status;
|
1717
|
+
}
|
1718
|
+
|
1719
|
+
if (status >= 300 || status === 0) {
|
1720
|
+
// Prevent onerror callback to be called
|
1721
|
+
_response.errorHandled = true;
|
1722
|
+
_clearState();
|
1723
|
+
reconnectF();
|
1724
|
+
return;
|
1725
|
+
}
|
1726
|
+
|
1727
|
+
// Firefox incorrectly send statechange 0->2 when a reconnect attempt fails. The above checks ensure that onopen is not called for these
|
1728
|
+
if ((!rq.enableProtocol || !request.firstMessage) && ajaxRequest.readyState === 2) {
|
1729
|
+
_triggerOpen(rq);
|
1730
|
+
}
|
1731
|
+
|
1732
|
+
} else if (ajaxRequest.readyState === 4) {
|
1733
|
+
update = true;
|
1734
|
+
}
|
1735
|
+
|
1736
|
+
if (update) {
|
1737
|
+
var responseText = ajaxRequest.responseText;
|
1738
|
+
|
1739
|
+
if (atmosphere.util.trim(responseText).length === 0 && rq.transport === 'long-polling') {
|
1740
|
+
// For browser that aren't support onabort
|
1741
|
+
if (!ajaxRequest.hasData) {
|
1742
|
+
_reconnect(ajaxRequest, rq, rq.pollingInterval);
|
1743
|
+
} else {
|
1744
|
+
ajaxRequest.hasData = false;
|
1745
|
+
}
|
1746
|
+
return;
|
1747
|
+
}
|
1748
|
+
ajaxRequest.hasData = true;
|
1749
|
+
|
1750
|
+
_readHeaders(ajaxRequest, _request);
|
1751
|
+
|
1752
|
+
if (rq.transport === 'streaming') {
|
1753
|
+
if (!atmosphere.util.browser.opera) {
|
1754
|
+
var message = responseText.substring(rq.lastIndex, responseText.length);
|
1755
|
+
skipCallbackInvocation = _trackMessageSize(message, rq, _response);
|
1756
|
+
|
1757
|
+
rq.lastIndex = responseText.length;
|
1758
|
+
if (skipCallbackInvocation) {
|
1759
|
+
return;
|
1760
|
+
}
|
1761
|
+
} else {
|
1762
|
+
atmosphere.util.iterate(function () {
|
1763
|
+
if (_response.status !== 500 && ajaxRequest.responseText.length > rq.lastIndex) {
|
1764
|
+
try {
|
1765
|
+
_response.status = ajaxRequest.status;
|
1766
|
+
_response.headers = atmosphere.util.parseHeaders(ajaxRequest.getAllResponseHeaders());
|
1767
|
+
|
1768
|
+
_readHeaders(ajaxRequest, _request);
|
1769
|
+
|
1770
|
+
} catch (e) {
|
1771
|
+
_response.status = 404;
|
1772
|
+
}
|
1773
|
+
_timeout(_request);
|
1774
|
+
|
1775
|
+
_response.state = "messageReceived";
|
1776
|
+
var message = ajaxRequest.responseText.substring(rq.lastIndex);
|
1777
|
+
rq.lastIndex = ajaxRequest.responseText.length;
|
1778
|
+
|
1779
|
+
skipCallbackInvocation = _trackMessageSize(message, rq, _response);
|
1780
|
+
if (!skipCallbackInvocation) {
|
1781
|
+
_invokeCallback();
|
1782
|
+
}
|
1783
|
+
|
1784
|
+
_verifyStreamingLength(ajaxRequest, rq);
|
1785
|
+
} else if (_response.status > 400) {
|
1786
|
+
// Prevent replaying the last message.
|
1787
|
+
rq.lastIndex = ajaxRequest.responseText.length;
|
1788
|
+
return false;
|
1789
|
+
}
|
1790
|
+
}, 0);
|
1791
|
+
}
|
1792
|
+
} else {
|
1793
|
+
skipCallbackInvocation = _trackMessageSize(responseText, rq, _response);
|
1794
|
+
}
|
1795
|
+
|
1796
|
+
try {
|
1797
|
+
_response.status = ajaxRequest.status;
|
1798
|
+
_response.headers = atmosphere.util.parseHeaders(ajaxRequest.getAllResponseHeaders());
|
1799
|
+
|
1800
|
+
_readHeaders(ajaxRequest, rq);
|
1801
|
+
} catch (e) {
|
1802
|
+
_response.status = 404;
|
1803
|
+
}
|
1804
|
+
|
1805
|
+
if (rq.suspend) {
|
1806
|
+
_response.state = _response.status === 0 ? "closed" : "messageReceived";
|
1807
|
+
} else {
|
1808
|
+
_response.state = "messagePublished";
|
1809
|
+
}
|
1810
|
+
|
1811
|
+
var isAllowedToReconnect = request.transport !== 'streaming' && request.transport !== 'polling';
|
1812
|
+
if (isAllowedToReconnect && !rq.executeCallbackBeforeReconnect) {
|
1813
|
+
_reconnect(ajaxRequest, rq, rq.pollingInterval);
|
1814
|
+
}
|
1815
|
+
|
1816
|
+
if (_response.responseBody.length !== 0 && !skipCallbackInvocation)
|
1817
|
+
_invokeCallback();
|
1818
|
+
|
1819
|
+
if (isAllowedToReconnect && rq.executeCallbackBeforeReconnect) {
|
1820
|
+
_reconnect(ajaxRequest, rq, rq.pollingInterval);
|
1821
|
+
}
|
1822
|
+
|
1823
|
+
_verifyStreamingLength(ajaxRequest, rq);
|
1824
|
+
}
|
1825
|
+
};
|
1826
|
+
|
1827
|
+
try {
|
1828
|
+
ajaxRequest.send(rq.data);
|
1829
|
+
_subscribed = true;
|
1830
|
+
} catch (e) {
|
1831
|
+
atmosphere.util.log(rq.logLevel, ["Unable to connect to " + rq.url]);
|
1832
|
+
_onError(0, e);
|
1833
|
+
}
|
1834
|
+
|
1835
|
+
} else {
|
1836
|
+
if (rq.logLevel === 'debug') {
|
1837
|
+
atmosphere.util.log(rq.logLevel, ["Max re-connection reached."]);
|
1838
|
+
}
|
1839
|
+
_onError(0, "maxRequest reached");
|
1840
|
+
}
|
1841
|
+
}
|
1842
|
+
|
1843
|
+
/**
|
1844
|
+
* Do ajax request.
|
1845
|
+
*
|
1846
|
+
* @param ajaxRequest Ajax request.
|
1847
|
+
* @param request Request parameters.
|
1848
|
+
* @param create If ajax request has to be open.
|
1849
|
+
*/
|
1850
|
+
function _doRequest(ajaxRequest, request, create) {
|
1851
|
+
// Prevent Android to cache request
|
1852
|
+
var url = request.url;
|
1853
|
+
if (request.dispatchUrl != null && request.method === 'POST') {
|
1854
|
+
url += request.dispatchUrl;
|
1855
|
+
}
|
1856
|
+
url = _attachHeaders(request, url);
|
1857
|
+
url = atmosphere.util.prepareURL(url);
|
1858
|
+
|
1859
|
+
if (create) {
|
1860
|
+
ajaxRequest.open(request.method, url, request.async);
|
1861
|
+
if (request.connectTimeout > 0) {
|
1862
|
+
request.id = setTimeout(function () {
|
1863
|
+
if (request.requestCount === 0) {
|
1864
|
+
_clearState();
|
1865
|
+
_prepareCallback("Connect timeout", "closed", 200, request.transport);
|
1866
|
+
}
|
1867
|
+
}, request.connectTimeout);
|
1868
|
+
}
|
1869
|
+
}
|
1870
|
+
|
1871
|
+
if (_request.withCredentials) {
|
1872
|
+
if ("withCredentials" in ajaxRequest) {
|
1873
|
+
ajaxRequest.withCredentials = true;
|
1874
|
+
}
|
1875
|
+
}
|
1876
|
+
|
1877
|
+
if (!_request.dropHeaders) {
|
1878
|
+
ajaxRequest.setRequestHeader("X-Atmosphere-Framework", atmosphere.util.version);
|
1879
|
+
ajaxRequest.setRequestHeader("X-Atmosphere-Transport", request.transport);
|
1880
|
+
if (request.lastTimestamp != null) {
|
1881
|
+
ajaxRequest.setRequestHeader("X-Cache-Date", request.lastTimestamp);
|
1882
|
+
} else {
|
1883
|
+
ajaxRequest.setRequestHeader("X-Cache-Date", 0);
|
1884
|
+
}
|
1885
|
+
|
1886
|
+
if (request.trackMessageLength) {
|
1887
|
+
ajaxRequest.setRequestHeader("X-Atmosphere-TrackMessageSize", "true");
|
1888
|
+
}
|
1889
|
+
ajaxRequest.setRequestHeader("X-Atmosphere-tracking-id", request.uuid);
|
1890
|
+
|
1891
|
+
atmosphere.util.each(request.headers, function (name, value) {
|
1892
|
+
var h = atmosphere.util.isFunction(value) ? value.call(this, ajaxRequest, request, create, _response) : value;
|
1893
|
+
if (h != null) {
|
1894
|
+
ajaxRequest.setRequestHeader(name, h);
|
1895
|
+
}
|
1896
|
+
});
|
1897
|
+
}
|
1898
|
+
|
1899
|
+
if (request.contentType !== '') {
|
1900
|
+
ajaxRequest.setRequestHeader("Content-Type", request.contentType);
|
1901
|
+
}
|
1902
|
+
}
|
1903
|
+
|
1904
|
+
function _reconnect(ajaxRequest, request, reconnectInterval) {
|
1905
|
+
if (request.reconnect || (request.suspend && _subscribed)) {
|
1906
|
+
var status = 0;
|
1907
|
+
if (ajaxRequest && ajaxRequest.readyState > 1) {
|
1908
|
+
status = ajaxRequest.status > 1000 ? 0 : ajaxRequest.status;
|
1909
|
+
}
|
1910
|
+
_response.status = status === 0 ? 204 : status;
|
1911
|
+
_response.reason = status === 0 ? "Server resumed the connection or down." : "OK";
|
1912
|
+
|
1913
|
+
clearTimeout(request.id);
|
1914
|
+
if (request.reconnectId) {
|
1915
|
+
clearTimeout(request.reconnectId);
|
1916
|
+
delete request.reconnectId;
|
1917
|
+
}
|
1918
|
+
|
1919
|
+
if (reconnectInterval > 0) {
|
1920
|
+
// For whatever reason, never cancel a reconnect timeout as it is mandatory to reconnect.
|
1921
|
+
_request.reconnectId = setTimeout(function () {
|
1922
|
+
_executeRequest(request);
|
1923
|
+
}, reconnectInterval);
|
1924
|
+
} else {
|
1925
|
+
_executeRequest(request);
|
1926
|
+
}
|
1927
|
+
}
|
1928
|
+
}
|
1929
|
+
|
1930
|
+
function _tryingToReconnect(response) {
|
1931
|
+
response.state = 're-connecting';
|
1932
|
+
_invokeFunction(response);
|
1933
|
+
}
|
1934
|
+
|
1935
|
+
function _ieXDR(request) {
|
1936
|
+
if (request.transport !== "polling") {
|
1937
|
+
_ieStream = _configureXDR(request);
|
1938
|
+
_ieStream.open();
|
1939
|
+
} else {
|
1940
|
+
_configureXDR(request).open();
|
1941
|
+
}
|
1942
|
+
}
|
1943
|
+
|
1944
|
+
function _configureXDR(request) {
|
1945
|
+
var rq = _request;
|
1946
|
+
if ((request != null) && (typeof (request) !== 'undefined')) {
|
1947
|
+
rq = request;
|
1948
|
+
}
|
1949
|
+
|
1950
|
+
var transport = rq.transport;
|
1951
|
+
var lastIndex = 0;
|
1952
|
+
var xdr = new window.XDomainRequest();
|
1953
|
+
|
1954
|
+
var reconnect = function () {
|
1955
|
+
if (rq.transport === "long-polling" && (rq.reconnect && (rq.maxRequest === -1 || rq.requestCount++ < rq.maxRequest))) {
|
1956
|
+
xdr.status = 200;
|
1957
|
+
_ieXDR(rq);
|
1958
|
+
}
|
1959
|
+
};
|
1960
|
+
|
1961
|
+
var rewriteURL = rq.rewriteURL || function (url) {
|
1962
|
+
// Maintaining session by rewriting URL
|
1963
|
+
// http://stackoverflow.com/questions/6453779/maintaining-session-by-rewriting-url
|
1964
|
+
var match = /(?:^|;\s*)(JSESSIONID|PHPSESSID)=([^;]*)/.exec(document.cookie);
|
1965
|
+
|
1966
|
+
switch (match && match[1]) {
|
1967
|
+
case "JSESSIONID":
|
1968
|
+
return url.replace(/;jsessionid=[^\?]*|(\?)|$/, ";jsessionid=" + match[2] + "$1");
|
1969
|
+
case "PHPSESSID":
|
1970
|
+
return url.replace(/\?PHPSESSID=[^&]*&?|\?|$/, "?PHPSESSID=" + match[2] + "&").replace(/&$/, "");
|
1971
|
+
}
|
1972
|
+
return url;
|
1973
|
+
};
|
1974
|
+
|
1975
|
+
// Handles open and message event
|
1976
|
+
xdr.onprogress = function () {
|
1977
|
+
handle(xdr);
|
1978
|
+
};
|
1979
|
+
// Handles error event
|
1980
|
+
xdr.onerror = function () {
|
1981
|
+
// If the server doesn't send anything back to XDR will fail with polling
|
1982
|
+
if (rq.transport !== 'polling') {
|
1983
|
+
_clearState();
|
1984
|
+
if (_requestCount++ < rq.maxReconnectOnClose) {
|
1985
|
+
if (rq.reconnectInterval > 0) {
|
1986
|
+
rq.reconnectId = setTimeout(function () {
|
1987
|
+
_open('re-connecting', request.transport, request);
|
1988
|
+
_ieXDR(rq);
|
1989
|
+
}, rq.reconnectInterval);
|
1990
|
+
} else {
|
1991
|
+
_open('re-connecting', request.transport, request);
|
1992
|
+
_ieXDR(rq);
|
1993
|
+
}
|
1994
|
+
} else {
|
1995
|
+
_onError(0, "maxReconnectOnClose reached");
|
1996
|
+
}
|
1997
|
+
}
|
1998
|
+
};
|
1999
|
+
|
2000
|
+
// Handles close event
|
2001
|
+
xdr.onload = function () {
|
2002
|
+
};
|
2003
|
+
|
2004
|
+
var handle = function (xdr) {
|
2005
|
+
clearTimeout(rq.id);
|
2006
|
+
var message = xdr.responseText;
|
2007
|
+
|
2008
|
+
message = message.substring(lastIndex);
|
2009
|
+
lastIndex += message.length;
|
2010
|
+
|
2011
|
+
if (transport !== 'polling') {
|
2012
|
+
_timeout(rq);
|
2013
|
+
|
2014
|
+
var skipCallbackInvocation = _trackMessageSize(message, rq, _response);
|
2015
|
+
|
2016
|
+
if (transport === 'long-polling' && atmosphere.util.trim(message).length === 0)
|
2017
|
+
return;
|
2018
|
+
|
2019
|
+
if (rq.executeCallbackBeforeReconnect) {
|
2020
|
+
reconnect();
|
2021
|
+
}
|
2022
|
+
|
2023
|
+
if (!skipCallbackInvocation) {
|
2024
|
+
_prepareCallback(_response.responseBody, "messageReceived", 200, transport);
|
2025
|
+
}
|
2026
|
+
|
2027
|
+
if (!rq.executeCallbackBeforeReconnect) {
|
2028
|
+
reconnect();
|
2029
|
+
}
|
2030
|
+
}
|
2031
|
+
};
|
2032
|
+
|
2033
|
+
return {
|
2034
|
+
open: function () {
|
2035
|
+
var url = rq.url;
|
2036
|
+
if (rq.dispatchUrl != null) {
|
2037
|
+
url += rq.dispatchUrl;
|
2038
|
+
}
|
2039
|
+
url = _attachHeaders(rq, url);
|
2040
|
+
xdr.open(rq.method, rewriteURL(url));
|
2041
|
+
if (rq.method === 'GET') {
|
2042
|
+
xdr.send();
|
2043
|
+
} else {
|
2044
|
+
xdr.send(rq.data);
|
2045
|
+
}
|
2046
|
+
|
2047
|
+
if (rq.connectTimeout > 0) {
|
2048
|
+
rq.id = setTimeout(function () {
|
2049
|
+
if (rq.requestCount === 0) {
|
2050
|
+
_clearState();
|
2051
|
+
_prepareCallback("Connect timeout", "closed", 200, rq.transport);
|
2052
|
+
}
|
2053
|
+
}, rq.connectTimeout);
|
2054
|
+
}
|
2055
|
+
},
|
2056
|
+
close: function () {
|
2057
|
+
xdr.abort();
|
2058
|
+
}
|
2059
|
+
};
|
2060
|
+
}
|
2061
|
+
|
2062
|
+
function _ieStreaming(request) {
|
2063
|
+
_ieStream = _configureIE(request);
|
2064
|
+
_ieStream.open();
|
2065
|
+
}
|
2066
|
+
|
2067
|
+
function _configureIE(request) {
|
2068
|
+
var rq = _request;
|
2069
|
+
if ((request != null) && (typeof (request) !== 'undefined')) {
|
2070
|
+
rq = request;
|
2071
|
+
}
|
2072
|
+
|
2073
|
+
var stop;
|
2074
|
+
var doc = new window.ActiveXObject("htmlfile");
|
2075
|
+
|
2076
|
+
doc.open();
|
2077
|
+
doc.close();
|
2078
|
+
|
2079
|
+
var url = rq.url;
|
2080
|
+
if (rq.dispatchUrl != null) {
|
2081
|
+
url += rq.dispatchUrl;
|
2082
|
+
}
|
2083
|
+
|
2084
|
+
if (rq.transport !== 'polling') {
|
2085
|
+
_response.transport = rq.transport;
|
2086
|
+
}
|
2087
|
+
|
2088
|
+
return {
|
2089
|
+
open: function () {
|
2090
|
+
var iframe = doc.createElement("iframe");
|
2091
|
+
|
2092
|
+
url = _attachHeaders(rq);
|
2093
|
+
if (rq.data !== '') {
|
2094
|
+
url += "&X-Atmosphere-Post-Body=" + encodeURIComponent(rq.data);
|
2095
|
+
}
|
2096
|
+
|
2097
|
+
// Finally attach a timestamp to prevent Android and IE caching.
|
2098
|
+
url = atmosphere.util.prepareURL(url);
|
2099
|
+
|
2100
|
+
iframe.src = url;
|
2101
|
+
doc.body.appendChild(iframe);
|
2102
|
+
|
2103
|
+
// For the server to respond in a consistent format regardless of user agent, we polls response text
|
2104
|
+
var cdoc = iframe.contentDocument || iframe.contentWindow.document;
|
2105
|
+
|
2106
|
+
stop = atmosphere.util.iterate(function () {
|
2107
|
+
try {
|
2108
|
+
if (!cdoc.firstChild) {
|
2109
|
+
return;
|
2110
|
+
}
|
2111
|
+
|
2112
|
+
var res = cdoc.body ? cdoc.body.lastChild : cdoc;
|
2113
|
+
var readResponse = function () {
|
2114
|
+
// Clones the element not to disturb the original one
|
2115
|
+
var clone = res.cloneNode(true);
|
2116
|
+
|
2117
|
+
// If the last character is a carriage return or a line feed, IE ignores it in the innerText property
|
2118
|
+
// therefore, we add another non-newline character to preserve it
|
2119
|
+
clone.appendChild(cdoc.createTextNode("."));
|
2120
|
+
|
2121
|
+
var text = clone.innerText;
|
2122
|
+
|
2123
|
+
text = text.substring(0, text.length - 1);
|
2124
|
+
return text;
|
2125
|
+
|
2126
|
+
};
|
2127
|
+
|
2128
|
+
// To support text/html content type
|
2129
|
+
if (!cdoc.body || !cdoc.body.firstChild || cdoc.body.firstChild.nodeName.toLowerCase() !== "pre") {
|
2130
|
+
// Injects a plaintext element which renders text without interpreting the HTML and cannot be stopped
|
2131
|
+
// it is deprecated in HTML5, but still works
|
2132
|
+
var head = cdoc.head || cdoc.getElementsByTagName("head")[0] || cdoc.documentElement || cdoc;
|
2133
|
+
var script = cdoc.createElement("script");
|
2134
|
+
|
2135
|
+
script.text = "document.write('<plaintext>')";
|
2136
|
+
|
2137
|
+
head.insertBefore(script, head.firstChild);
|
2138
|
+
head.removeChild(script);
|
2139
|
+
|
2140
|
+
// The plaintext element will be the response container
|
2141
|
+
res = cdoc.body.lastChild;
|
2142
|
+
}
|
2143
|
+
|
2144
|
+
if (rq.closed) {
|
2145
|
+
rq.isReopen = true;
|
2146
|
+
}
|
2147
|
+
|
2148
|
+
// Handles message and close event
|
2149
|
+
stop = atmosphere.util.iterate(function () {
|
2150
|
+
var text = readResponse();
|
2151
|
+
if (text.length > rq.lastIndex) {
|
2152
|
+
_timeout(_request);
|
2153
|
+
|
2154
|
+
_response.status = 200;
|
2155
|
+
_response.error = null;
|
2156
|
+
|
2157
|
+
// Empties response every time that it is handled
|
2158
|
+
res.innerText = "";
|
2159
|
+
var skipCallbackInvocation = _trackMessageSize(text, rq, _response);
|
2160
|
+
if (skipCallbackInvocation) {
|
2161
|
+
return "";
|
2162
|
+
}
|
2163
|
+
|
2164
|
+
_prepareCallback(_response.responseBody, "messageReceived", 200, rq.transport);
|
2165
|
+
}
|
2166
|
+
|
2167
|
+
rq.lastIndex = 0;
|
2168
|
+
|
2169
|
+
if (cdoc.readyState === "complete") {
|
2170
|
+
_invokeClose(true);
|
2171
|
+
_open('re-connecting', rq.transport, rq);
|
2172
|
+
if (rq.reconnectInterval > 0) {
|
2173
|
+
rq.reconnectId = setTimeout(function () {
|
2174
|
+
_ieStreaming(rq);
|
2175
|
+
}, rq.reconnectInterval);
|
2176
|
+
} else {
|
2177
|
+
_ieStreaming(rq);
|
2178
|
+
}
|
2179
|
+
return false;
|
2180
|
+
}
|
2181
|
+
}, null);
|
2182
|
+
|
2183
|
+
return false;
|
2184
|
+
} catch (err) {
|
2185
|
+
_response.error = true;
|
2186
|
+
_open('re-connecting', rq.transport, rq);
|
2187
|
+
if (_requestCount++ < rq.maxReconnectOnClose) {
|
2188
|
+
if (rq.reconnectInterval > 0) {
|
2189
|
+
rq.reconnectId = setTimeout(function () {
|
2190
|
+
_ieStreaming(rq);
|
2191
|
+
}, rq.reconnectInterval);
|
2192
|
+
} else {
|
2193
|
+
_ieStreaming(rq);
|
2194
|
+
}
|
2195
|
+
} else {
|
2196
|
+
_onError(0, "maxReconnectOnClose reached");
|
2197
|
+
}
|
2198
|
+
doc.execCommand("Stop");
|
2199
|
+
doc.close();
|
2200
|
+
return false;
|
2201
|
+
}
|
2202
|
+
});
|
2203
|
+
},
|
2204
|
+
|
2205
|
+
close: function () {
|
2206
|
+
if (stop) {
|
2207
|
+
stop();
|
2208
|
+
}
|
2209
|
+
|
2210
|
+
doc.execCommand("Stop");
|
2211
|
+
_invokeClose(true);
|
2212
|
+
}
|
2213
|
+
};
|
2214
|
+
}
|
2215
|
+
|
2216
|
+
/**
|
2217
|
+
* Send message. <br>
|
2218
|
+
* Will be automatically dispatch to other connected.
|
2219
|
+
*
|
2220
|
+
* @param {Object, string} Message to send.
|
2221
|
+
* @private
|
2222
|
+
*/
|
2223
|
+
function _push(message) {
|
2224
|
+
|
2225
|
+
if (_localStorageService != null) {
|
2226
|
+
_pushLocal(message);
|
2227
|
+
} else if (_activeRequest != null || _sse != null) {
|
2228
|
+
_pushAjaxMessage(message);
|
2229
|
+
} else if (_ieStream != null) {
|
2230
|
+
_pushIE(message);
|
2231
|
+
} else if (_jqxhr != null) {
|
2232
|
+
_pushJsonp(message);
|
2233
|
+
} else if (_websocket != null) {
|
2234
|
+
_pushWebSocket(message);
|
2235
|
+
} else {
|
2236
|
+
_onError(0, "No suspended connection available");
|
2237
|
+
atmosphere.util.error("No suspended connection available. Make sure atmosphere.subscribe has been called and request.onOpen invoked before invoking this method");
|
2238
|
+
}
|
2239
|
+
}
|
2240
|
+
|
2241
|
+
function _pushOnClose(message, rq) {
|
2242
|
+
if (!rq) {
|
2243
|
+
rq = _getPushRequest(message);
|
2244
|
+
}
|
2245
|
+
rq.transport = "polling";
|
2246
|
+
rq.method = "GET";
|
2247
|
+
rq.async = false;
|
2248
|
+
rq.withCredentials = false;
|
2249
|
+
rq.reconnect = false;
|
2250
|
+
rq.force = true;
|
2251
|
+
rq.suspend = false;
|
2252
|
+
rq.timeout = 1000;
|
2253
|
+
_executeRequest(rq);
|
2254
|
+
}
|
2255
|
+
|
2256
|
+
function _pushLocal(message) {
|
2257
|
+
_localStorageService.send(message);
|
2258
|
+
}
|
2259
|
+
|
2260
|
+
function _intraPush(message) {
|
2261
|
+
// IE 9 will crash if not.
|
2262
|
+
if (message.length === 0)
|
2263
|
+
return;
|
2264
|
+
|
2265
|
+
try {
|
2266
|
+
if (_localStorageService) {
|
2267
|
+
_localStorageService.localSend(message);
|
2268
|
+
} else if (_storageService) {
|
2269
|
+
_storageService.signal("localMessage", atmosphere.util.stringifyJSON({
|
2270
|
+
id: guid,
|
2271
|
+
event: message
|
2272
|
+
}));
|
2273
|
+
}
|
2274
|
+
} catch (err) {
|
2275
|
+
atmosphere.util.error(err);
|
2276
|
+
}
|
2277
|
+
}
|
2278
|
+
|
2279
|
+
/**
|
2280
|
+
* Send a message using currently opened ajax request (using http-streaming or long-polling). <br>
|
2281
|
+
*
|
2282
|
+
* @param {string, Object} Message to send. This is an object, string message is saved in data member.
|
2283
|
+
* @private
|
2284
|
+
*/
|
2285
|
+
function _pushAjaxMessage(message) {
|
2286
|
+
var rq = _getPushRequest(message);
|
2287
|
+
_executeRequest(rq);
|
2288
|
+
}
|
2289
|
+
|
2290
|
+
/**
|
2291
|
+
* Send a message using currently opened ie streaming (using http-streaming or long-polling). <br>
|
2292
|
+
*
|
2293
|
+
* @param {string, Object} Message to send. This is an object, string message is saved in data member.
|
2294
|
+
* @private
|
2295
|
+
*/
|
2296
|
+
function _pushIE(message) {
|
2297
|
+
if (_request.enableXDR && atmosphere.util.checkCORSSupport()) {
|
2298
|
+
var rq = _getPushRequest(message);
|
2299
|
+
// Do not reconnect since we are pushing.
|
2300
|
+
rq.reconnect = false;
|
2301
|
+
_jsonp(rq);
|
2302
|
+
} else {
|
2303
|
+
_pushAjaxMessage(message);
|
2304
|
+
}
|
2305
|
+
}
|
2306
|
+
|
2307
|
+
/**
|
2308
|
+
* Send a message using jsonp transport. <br>
|
2309
|
+
*
|
2310
|
+
* @param {string, Object} Message to send. This is an object, string message is saved in data member.
|
2311
|
+
* @private
|
2312
|
+
*/
|
2313
|
+
function _pushJsonp(message) {
|
2314
|
+
_pushAjaxMessage(message);
|
2315
|
+
}
|
2316
|
+
|
2317
|
+
function _getStringMessage(message) {
|
2318
|
+
var msg = message;
|
2319
|
+
if (typeof (msg) === 'object') {
|
2320
|
+
msg = message.data;
|
2321
|
+
}
|
2322
|
+
return msg;
|
2323
|
+
}
|
2324
|
+
|
2325
|
+
/**
|
2326
|
+
* Build request use to push message using method 'POST' <br>. Transport is defined as 'polling' and 'suspend' is set to false.
|
2327
|
+
*
|
2328
|
+
* @return {Object} Request object use to push message.
|
2329
|
+
* @private
|
2330
|
+
*/
|
2331
|
+
function _getPushRequest(message) {
|
2332
|
+
var msg = _getStringMessage(message);
|
2333
|
+
|
2334
|
+
var rq = {
|
2335
|
+
connected: false,
|
2336
|
+
timeout: 60000,
|
2337
|
+
method: 'POST',
|
2338
|
+
url: _request.url,
|
2339
|
+
contentType: _request.contentType,
|
2340
|
+
headers: _request.headers,
|
2341
|
+
reconnect: true,
|
2342
|
+
callback: null,
|
2343
|
+
data: msg,
|
2344
|
+
suspend: false,
|
2345
|
+
maxRequest: -1,
|
2346
|
+
logLevel: 'info',
|
2347
|
+
requestCount: 0,
|
2348
|
+
withCredentials: _request.withCredentials,
|
2349
|
+
async: _request.async,
|
2350
|
+
transport: 'polling',
|
2351
|
+
isOpen: true,
|
2352
|
+
attachHeadersAsQueryString: true,
|
2353
|
+
enableXDR: _request.enableXDR,
|
2354
|
+
uuid: _request.uuid,
|
2355
|
+
dispatchUrl: _request.dispatchUrl,
|
2356
|
+
enableProtocol: false,
|
2357
|
+
messageDelimiter: '|',
|
2358
|
+
maxReconnectOnClose: _request.maxReconnectOnClose
|
2359
|
+
};
|
2360
|
+
|
2361
|
+
if (typeof (message) === 'object') {
|
2362
|
+
rq = atmosphere.util.extend(rq, message);
|
2363
|
+
}
|
2364
|
+
|
2365
|
+
return rq;
|
2366
|
+
}
|
2367
|
+
|
2368
|
+
/**
|
2369
|
+
* Send a message using currently opened websocket. <br>
|
2370
|
+
*
|
2371
|
+
*/
|
2372
|
+
function _pushWebSocket(message) {
|
2373
|
+
var msg = atmosphere.util.isBinary(message) ? message : _getStringMessage(message);
|
2374
|
+
var data;
|
2375
|
+
try {
|
2376
|
+
if (_request.dispatchUrl != null) {
|
2377
|
+
data = _request.webSocketPathDelimiter + _request.dispatchUrl + _request.webSocketPathDelimiter + msg;
|
2378
|
+
} else {
|
2379
|
+
data = msg;
|
2380
|
+
}
|
2381
|
+
|
2382
|
+
if (!_websocket.canSendMessage) {
|
2383
|
+
atmosphere.util.error("WebSocket not connected.");
|
2384
|
+
return;
|
2385
|
+
}
|
2386
|
+
|
2387
|
+
_websocket.send(data);
|
2388
|
+
|
2389
|
+
} catch (e) {
|
2390
|
+
_websocket.onclose = function (message) {
|
2391
|
+
};
|
2392
|
+
_clearState();
|
2393
|
+
|
2394
|
+
_reconnectWithFallbackTransport("Websocket failed. Downgrading to Comet and resending " + message);
|
2395
|
+
_pushAjaxMessage(message);
|
2396
|
+
}
|
2397
|
+
}
|
2398
|
+
|
2399
|
+
function _localMessage(message) {
|
2400
|
+
var m = atmosphere.util.parseJSON(message);
|
2401
|
+
if (m.id !== guid) {
|
2402
|
+
if (typeof (_request.onLocalMessage) !== 'undefined') {
|
2403
|
+
_request.onLocalMessage(m.event);
|
2404
|
+
} else if (typeof (atmosphere.util.onLocalMessage) !== 'undefined') {
|
2405
|
+
atmosphere.util.onLocalMessage(m.event);
|
2406
|
+
}
|
2407
|
+
}
|
2408
|
+
}
|
2409
|
+
|
2410
|
+
function _prepareCallback(messageBody, state, errorCode, transport) {
|
2411
|
+
|
2412
|
+
_response.responseBody = messageBody;
|
2413
|
+
_response.transport = transport;
|
2414
|
+
_response.status = errorCode;
|
2415
|
+
_response.state = state;
|
2416
|
+
|
2417
|
+
_invokeCallback();
|
2418
|
+
}
|
2419
|
+
|
2420
|
+
function _readHeaders(xdr, request) {
|
2421
|
+
if (!request.readResponsesHeaders) {
|
2422
|
+
if (!request.enableProtocol) {
|
2423
|
+
request.lastTimestamp = atmosphere.util.now();
|
2424
|
+
request.uuid = guid;
|
2425
|
+
}
|
2426
|
+
}
|
2427
|
+
else {
|
2428
|
+
try {
|
2429
|
+
var tempDate = xdr.getResponseHeader('X-Cache-Date');
|
2430
|
+
if (tempDate && tempDate != null && tempDate.length > 0) {
|
2431
|
+
request.lastTimestamp = tempDate.split(" ").pop();
|
2432
|
+
}
|
2433
|
+
|
2434
|
+
var tempUUID = xdr.getResponseHeader('X-Atmosphere-tracking-id');
|
2435
|
+
if (tempUUID && tempUUID != null) {
|
2436
|
+
request.uuid = tempUUID.split(" ").pop();
|
2437
|
+
}
|
2438
|
+
} catch (e) {
|
2439
|
+
}
|
2440
|
+
}
|
2441
|
+
}
|
2442
|
+
|
2443
|
+
function _invokeFunction(response) {
|
2444
|
+
_f(response, _request);
|
2445
|
+
// Global
|
2446
|
+
_f(response, atmosphere.util);
|
2447
|
+
}
|
2448
|
+
|
2449
|
+
function _f(response, f) {
|
2450
|
+
switch (response.state) {
|
2451
|
+
case "messageReceived":
|
2452
|
+
_requestCount = 0;
|
2453
|
+
if (typeof (f.onMessage) !== 'undefined')
|
2454
|
+
f.onMessage(response);
|
2455
|
+
break;
|
2456
|
+
case "error":
|
2457
|
+
if (typeof (f.onError) !== 'undefined')
|
2458
|
+
f.onError(response);
|
2459
|
+
break;
|
2460
|
+
case "opening":
|
2461
|
+
delete _request.closed;
|
2462
|
+
if (typeof (f.onOpen) !== 'undefined')
|
2463
|
+
f.onOpen(response);
|
2464
|
+
break;
|
2465
|
+
case "messagePublished":
|
2466
|
+
if (typeof (f.onMessagePublished) !== 'undefined')
|
2467
|
+
f.onMessagePublished(response);
|
2468
|
+
break;
|
2469
|
+
case "re-connecting":
|
2470
|
+
if (typeof (f.onReconnect) !== 'undefined')
|
2471
|
+
f.onReconnect(_request, response);
|
2472
|
+
break;
|
2473
|
+
case "closedByClient":
|
2474
|
+
if (typeof (f.onClientTimeout) !== 'undefined')
|
2475
|
+
f.onClientTimeout(_request);
|
2476
|
+
break;
|
2477
|
+
case "re-opening":
|
2478
|
+
delete _request.closed;
|
2479
|
+
if (typeof (f.onReopen) !== 'undefined')
|
2480
|
+
f.onReopen(_request, response);
|
2481
|
+
break;
|
2482
|
+
case "fail-to-reconnect":
|
2483
|
+
if (typeof (f.onFailureToReconnect) !== 'undefined')
|
2484
|
+
f.onFailureToReconnect(_request, response);
|
2485
|
+
break;
|
2486
|
+
case "unsubscribe":
|
2487
|
+
case "closed":
|
2488
|
+
var closed = typeof (_request.closed) !== 'undefined' ? _request.closed : false;
|
2489
|
+
if (typeof (f.onClose) !== 'undefined' && !closed)
|
2490
|
+
f.onClose(response);
|
2491
|
+
_request.closed = true;
|
2492
|
+
break;
|
2493
|
+
}
|
2494
|
+
}
|
2495
|
+
|
2496
|
+
function _invokeClose(wasOpen) {
|
2497
|
+
if (_response.state !== 'closed') {
|
2498
|
+
_response.state = 'closed';
|
2499
|
+
_response.responseBody = "";
|
2500
|
+
_response.messages = [];
|
2501
|
+
_response.status = !wasOpen ? 501 : 200;
|
2502
|
+
_invokeCallback();
|
2503
|
+
}
|
2504
|
+
}
|
2505
|
+
|
2506
|
+
/**
|
2507
|
+
* Invoke request callbacks.
|
2508
|
+
*
|
2509
|
+
* @private
|
2510
|
+
*/
|
2511
|
+
function _invokeCallback() {
|
2512
|
+
var call = function (index, func) {
|
2513
|
+
func(_response);
|
2514
|
+
};
|
2515
|
+
|
2516
|
+
if (_localStorageService == null && _localSocketF != null) {
|
2517
|
+
_localSocketF(_response.responseBody);
|
2518
|
+
}
|
2519
|
+
|
2520
|
+
_request.reconnect = _request.mrequest;
|
2521
|
+
|
2522
|
+
var isString = typeof (_response.responseBody) === 'string';
|
2523
|
+
var messages = (isString && _request.trackMessageLength) ? (_response.messages.length > 0 ? _response.messages : ['']) : new Array(
|
2524
|
+
_response.responseBody);
|
2525
|
+
for (var i = 0; i < messages.length; i++) {
|
2526
|
+
|
2527
|
+
if (messages.length > 1 && messages[i].length === 0) {
|
2528
|
+
continue;
|
2529
|
+
}
|
2530
|
+
_response.responseBody = (isString) ? atmosphere.util.trim(messages[i]) : messages[i];
|
2531
|
+
|
2532
|
+
if (_localStorageService == null && _localSocketF != null) {
|
2533
|
+
_localSocketF(_response.responseBody);
|
2534
|
+
}
|
2535
|
+
|
2536
|
+
if (_response.responseBody.length === 0 && _response.state === "messageReceived") {
|
2537
|
+
continue;
|
2538
|
+
}
|
2539
|
+
|
2540
|
+
_invokeFunction(_response);
|
2541
|
+
|
2542
|
+
// Invoke global callbacks
|
2543
|
+
if (callbacks.length > 0) {
|
2544
|
+
if (_request.logLevel === 'debug') {
|
2545
|
+
atmosphere.util.debug("Invoking " + callbacks.length + " global callbacks: " + _response.state);
|
2546
|
+
}
|
2547
|
+
try {
|
2548
|
+
atmosphere.util.each(callbacks, call);
|
2549
|
+
} catch (e) {
|
2550
|
+
atmosphere.util.log(_request.logLevel, ["Callback exception" + e]);
|
2551
|
+
}
|
2552
|
+
}
|
2553
|
+
|
2554
|
+
// Invoke request callback
|
2555
|
+
if (typeof (_request.callback) === 'function') {
|
2556
|
+
if (_request.logLevel === 'debug') {
|
2557
|
+
atmosphere.util.debug("Invoking request callbacks");
|
2558
|
+
}
|
2559
|
+
try {
|
2560
|
+
_request.callback(_response);
|
2561
|
+
} catch (e) {
|
2562
|
+
atmosphere.util.log(_request.logLevel, ["Callback exception" + e]);
|
2563
|
+
}
|
2564
|
+
}
|
2565
|
+
}
|
2566
|
+
}
|
2567
|
+
|
2568
|
+
this.subscribe = function (options) {
|
2569
|
+
_subscribe(options);
|
2570
|
+
_execute();
|
2571
|
+
};
|
2572
|
+
|
2573
|
+
this.execute = function () {
|
2574
|
+
_execute();
|
2575
|
+
};
|
2576
|
+
|
2577
|
+
this.close = function () {
|
2578
|
+
_close();
|
2579
|
+
};
|
2580
|
+
|
2581
|
+
this.disconnect = function () {
|
2582
|
+
_disconnect();
|
2583
|
+
};
|
2584
|
+
|
2585
|
+
this.getUrl = function () {
|
2586
|
+
return _request.url;
|
2587
|
+
};
|
2588
|
+
|
2589
|
+
this.push = function (message, dispatchUrl) {
|
2590
|
+
if (dispatchUrl != null) {
|
2591
|
+
var originalDispatchUrl = _request.dispatchUrl;
|
2592
|
+
_request.dispatchUrl = dispatchUrl;
|
2593
|
+
_push(message);
|
2594
|
+
_request.dispatchUrl = originalDispatchUrl;
|
2595
|
+
} else {
|
2596
|
+
_push(message);
|
2597
|
+
}
|
2598
|
+
};
|
2599
|
+
|
2600
|
+
this.getUUID = function () {
|
2601
|
+
return _request.uuid;
|
2602
|
+
};
|
2603
|
+
|
2604
|
+
this.pushLocal = function (message) {
|
2605
|
+
_intraPush(message);
|
2606
|
+
};
|
2607
|
+
|
2608
|
+
this.enableProtocol = function (message) {
|
2609
|
+
return _request.enableProtocol;
|
2610
|
+
};
|
2611
|
+
|
2612
|
+
this.request = _request;
|
2613
|
+
this.response = _response;
|
2614
|
+
}
|
2615
|
+
};
|
2616
|
+
|
2617
|
+
atmosphere.subscribe = function (url, callback, request) {
|
2618
|
+
if (typeof (callback) === 'function') {
|
2619
|
+
atmosphere.addCallback(callback);
|
2620
|
+
}
|
2621
|
+
|
2622
|
+
// https://github.com/Atmosphere/atmosphere-javascript/issues/58
|
2623
|
+
uuid = 0;
|
2624
|
+
|
2625
|
+
if (typeof (url) !== "string") {
|
2626
|
+
request = url;
|
2627
|
+
} else {
|
2628
|
+
request.url = url;
|
2629
|
+
}
|
2630
|
+
|
2631
|
+
var rq = new atmosphere.AtmosphereRequest(request);
|
2632
|
+
rq.execute();
|
2633
|
+
|
2634
|
+
requests[requests.length] = rq;
|
2635
|
+
return rq;
|
2636
|
+
};
|
2637
|
+
|
2638
|
+
atmosphere.unsubscribe = function () {
|
2639
|
+
if (requests.length > 0) {
|
2640
|
+
var requestsClone = [].concat(requests);
|
2641
|
+
for (var i = 0; i < requestsClone.length; i++) {
|
2642
|
+
var rq = requestsClone[i];
|
2643
|
+
rq.close();
|
2644
|
+
clearTimeout(rq.response.request.id);
|
2645
|
+
}
|
2646
|
+
}
|
2647
|
+
requests = [];
|
2648
|
+
callbacks = [];
|
2649
|
+
};
|
2650
|
+
|
2651
|
+
atmosphere.unsubscribeUrl = function (url) {
|
2652
|
+
var idx = -1;
|
2653
|
+
if (requests.length > 0) {
|
2654
|
+
for (var i = 0; i < requests.length; i++) {
|
2655
|
+
var rq = requests[i];
|
2656
|
+
|
2657
|
+
// Suppose you can subscribe once to an url
|
2658
|
+
if (rq.getUrl() === url) {
|
2659
|
+
rq.close();
|
2660
|
+
clearTimeout(rq.response.request.id);
|
2661
|
+
idx = i;
|
2662
|
+
break;
|
2663
|
+
}
|
2664
|
+
}
|
2665
|
+
}
|
2666
|
+
if (idx >= 0) {
|
2667
|
+
requests.splice(idx, 1);
|
2668
|
+
}
|
2669
|
+
};
|
2670
|
+
|
2671
|
+
atmosphere.addCallback = function (func) {
|
2672
|
+
if (atmosphere.util.inArray(func, callbacks) === -1) {
|
2673
|
+
callbacks.push(func);
|
2674
|
+
}
|
2675
|
+
};
|
2676
|
+
|
2677
|
+
atmosphere.removeCallback = function (func) {
|
2678
|
+
var index = atmosphere.util.inArray(func, callbacks);
|
2679
|
+
if (index !== -1) {
|
2680
|
+
callbacks.splice(index, 1);
|
2681
|
+
}
|
2682
|
+
};
|
2683
|
+
|
2684
|
+
atmosphere.util = {
|
2685
|
+
browser: {},
|
2686
|
+
|
2687
|
+
parseHeaders: function (headerString) {
|
2688
|
+
var match, rheaders = /^(.*?):[ \t]*([^\r\n]*)\r?$/mg, headers = {};
|
2689
|
+
while (match = rheaders.exec(headerString)) {
|
2690
|
+
headers[match[1]] = match[2];
|
2691
|
+
}
|
2692
|
+
return headers;
|
2693
|
+
},
|
2694
|
+
|
2695
|
+
now: function () {
|
2696
|
+
return new Date().getTime();
|
2697
|
+
},
|
2698
|
+
|
2699
|
+
isArray: function (array) {
|
2700
|
+
return Object.prototype.toString.call(array) === "[object Array]";
|
2701
|
+
},
|
2702
|
+
|
2703
|
+
inArray: function (elem, array) {
|
2704
|
+
if (!Array.prototype.indexOf) {
|
2705
|
+
var len = array.length;
|
2706
|
+
for (var i = 0; i < len; ++i) {
|
2707
|
+
if (array[i] === elem) {
|
2708
|
+
return i;
|
2709
|
+
}
|
2710
|
+
}
|
2711
|
+
return -1;
|
2712
|
+
}
|
2713
|
+
return array.indexOf(elem);
|
2714
|
+
},
|
2715
|
+
|
2716
|
+
isBinary: function (data) {
|
2717
|
+
// True if data is an instance of Blob, ArrayBuffer or ArrayBufferView
|
2718
|
+
return /^\[object\s(?:Blob|ArrayBuffer|.+Array)\]$/.test(Object.prototype.toString.call(data));
|
2719
|
+
},
|
2720
|
+
|
2721
|
+
isFunction: function (fn) {
|
2722
|
+
return Object.prototype.toString.call(fn) === "[object Function]";
|
2723
|
+
},
|
2724
|
+
|
2725
|
+
getAbsoluteURL: function (url) {
|
2726
|
+
var div = document.createElement("div");
|
2727
|
+
|
2728
|
+
// Uses an innerHTML property to obtain an absolute URL
|
2729
|
+
div.innerHTML = '<a href="' + url + '"/>';
|
2730
|
+
|
2731
|
+
// encodeURI and decodeURI are needed to normalize URL between IE and non-IE,
|
2732
|
+
// since IE doesn't encode the href property value and return it - http://jsfiddle.net/Yq9M8/1/
|
2733
|
+
return encodeURI(decodeURI(div.firstChild.href));
|
2734
|
+
},
|
2735
|
+
|
2736
|
+
prepareURL: function (url) {
|
2737
|
+
// Attaches a time stamp to prevent caching
|
2738
|
+
var ts = atmosphere.util.now();
|
2739
|
+
var ret = url.replace(/([?&])_=[^&]*/, "$1_=" + ts);
|
2740
|
+
|
2741
|
+
return ret + (ret === url ? (/\?/.test(url) ? "&" : "?") + "_=" + ts : "");
|
2742
|
+
},
|
2743
|
+
|
2744
|
+
trim: function (str) {
|
2745
|
+
if (!String.prototype.trim) {
|
2746
|
+
return str.toString().replace(/(?:(?:^|\n)\s+|\s+(?:$|\n))/g, "").replace(/\s+/g, " ");
|
2747
|
+
} else {
|
2748
|
+
return str.toString().trim();
|
2749
|
+
}
|
2750
|
+
},
|
2751
|
+
|
2752
|
+
param: function (params) {
|
2753
|
+
var prefix, s = [];
|
2754
|
+
|
2755
|
+
function add(key, value) {
|
2756
|
+
value = atmosphere.util.isFunction(value) ? value() : (value == null ? "" : value);
|
2757
|
+
s.push(encodeURIComponent(key) + "=" + encodeURIComponent(value));
|
2758
|
+
}
|
2759
|
+
|
2760
|
+
function buildParams(prefix, obj) {
|
2761
|
+
var name;
|
2762
|
+
|
2763
|
+
if (atmosphere.util.isArray(obj)) {
|
2764
|
+
atmosphere.util.each(obj, function (i, v) {
|
2765
|
+
if (/\[\]$/.test(prefix)) {
|
2766
|
+
add(prefix, v);
|
2767
|
+
} else {
|
2768
|
+
buildParams(prefix + "[" + (typeof v === "object" ? i : "") + "]", v);
|
2769
|
+
}
|
2770
|
+
});
|
2771
|
+
} else if (Object.prototype.toString.call(obj) === "[object Object]") {
|
2772
|
+
for (name in obj) {
|
2773
|
+
buildParams(prefix + "[" + name + "]", obj[name]);
|
2774
|
+
}
|
2775
|
+
} else {
|
2776
|
+
add(prefix, obj);
|
2777
|
+
}
|
2778
|
+
}
|
2779
|
+
|
2780
|
+
for (prefix in params) {
|
2781
|
+
buildParams(prefix, params[prefix]);
|
2782
|
+
}
|
2783
|
+
|
2784
|
+
return s.join("&").replace(/%20/g, "+");
|
2785
|
+
},
|
2786
|
+
|
2787
|
+
storage: function () {
|
2788
|
+
try {
|
2789
|
+
return !!(window.localStorage && window.StorageEvent);
|
2790
|
+
} catch (e) {
|
2791
|
+
//Firefox throws an exception here, see
|
2792
|
+
//https://bugzilla.mozilla.org/show_bug.cgi?id=748620
|
2793
|
+
return false;
|
2794
|
+
}
|
2795
|
+
},
|
2796
|
+
|
2797
|
+
iterate: function (fn, interval) {
|
2798
|
+
var timeoutId;
|
2799
|
+
|
2800
|
+
// Though the interval is 0 for real-time application, there is a delay between setTimeout calls
|
2801
|
+
// For detail, see https://developer.mozilla.org/en/window.setTimeout#Minimum_delay_and_timeout_nesting
|
2802
|
+
interval = interval || 0;
|
2803
|
+
|
2804
|
+
(function loop() {
|
2805
|
+
timeoutId = setTimeout(function () {
|
2806
|
+
if (fn() === false) {
|
2807
|
+
return;
|
2808
|
+
}
|
2809
|
+
|
2810
|
+
loop();
|
2811
|
+
}, interval);
|
2812
|
+
})();
|
2813
|
+
|
2814
|
+
return function () {
|
2815
|
+
clearTimeout(timeoutId);
|
2816
|
+
};
|
2817
|
+
},
|
2818
|
+
|
2819
|
+
each: function (obj, callback, args) {
|
2820
|
+
if (!obj) return;
|
2821
|
+
var value, i = 0, length = obj.length, isArray = atmosphere.util.isArray(obj);
|
2822
|
+
|
2823
|
+
if (args) {
|
2824
|
+
if (isArray) {
|
2825
|
+
for (; i < length; i++) {
|
2826
|
+
value = callback.apply(obj[i], args);
|
2827
|
+
|
2828
|
+
if (value === false) {
|
2829
|
+
break;
|
2830
|
+
}
|
2831
|
+
}
|
2832
|
+
} else {
|
2833
|
+
for (i in obj) {
|
2834
|
+
value = callback.apply(obj[i], args);
|
2835
|
+
|
2836
|
+
if (value === false) {
|
2837
|
+
break;
|
2838
|
+
}
|
2839
|
+
}
|
2840
|
+
}
|
2841
|
+
|
2842
|
+
// A special, fast, case for the most common use of each
|
2843
|
+
} else {
|
2844
|
+
if (isArray) {
|
2845
|
+
for (; i < length; i++) {
|
2846
|
+
value = callback.call(obj[i], i, obj[i]);
|
2847
|
+
|
2848
|
+
if (value === false) {
|
2849
|
+
break;
|
2850
|
+
}
|
2851
|
+
}
|
2852
|
+
} else {
|
2853
|
+
for (i in obj) {
|
2854
|
+
value = callback.call(obj[i], i, obj[i]);
|
2855
|
+
|
2856
|
+
if (value === false) {
|
2857
|
+
break;
|
2858
|
+
}
|
2859
|
+
}
|
2860
|
+
}
|
2861
|
+
}
|
2862
|
+
|
2863
|
+
return obj;
|
2864
|
+
},
|
2865
|
+
|
2866
|
+
extend: function (target) {
|
2867
|
+
var i, options, name;
|
2868
|
+
|
2869
|
+
for (i = 1; i < arguments.length; i++) {
|
2870
|
+
if ((options = arguments[i]) != null) {
|
2871
|
+
for (name in options) {
|
2872
|
+
target[name] = options[name];
|
2873
|
+
}
|
2874
|
+
}
|
2875
|
+
}
|
2876
|
+
|
2877
|
+
return target;
|
2878
|
+
},
|
2879
|
+
on: function (elem, type, fn) {
|
2880
|
+
if (elem.addEventListener) {
|
2881
|
+
elem.addEventListener(type, fn, false);
|
2882
|
+
} else if (elem.attachEvent) {
|
2883
|
+
elem.attachEvent("on" + type, fn);
|
2884
|
+
}
|
2885
|
+
},
|
2886
|
+
off: function (elem, type, fn) {
|
2887
|
+
if (elem.removeEventListener) {
|
2888
|
+
elem.removeEventListener(type, fn, false);
|
2889
|
+
} else if (elem.detachEvent) {
|
2890
|
+
elem.detachEvent("on" + type, fn);
|
2891
|
+
}
|
2892
|
+
},
|
2893
|
+
|
2894
|
+
log: function (level, args) {
|
2895
|
+
if (window.console) {
|
2896
|
+
var logger = window.console[level];
|
2897
|
+
if (typeof logger === 'function') {
|
2898
|
+
logger.apply(window.console, args);
|
2899
|
+
}
|
2900
|
+
}
|
2901
|
+
},
|
2902
|
+
|
2903
|
+
warn: function () {
|
2904
|
+
atmosphere.util.log('warn', arguments);
|
2905
|
+
},
|
2906
|
+
|
2907
|
+
info: function () {
|
2908
|
+
atmosphere.util.log('info', arguments);
|
2909
|
+
},
|
2910
|
+
|
2911
|
+
debug: function () {
|
2912
|
+
atmosphere.util.log('debug', arguments);
|
2913
|
+
},
|
2914
|
+
|
2915
|
+
error: function () {
|
2916
|
+
atmosphere.util.log('error', arguments);
|
2917
|
+
},
|
2918
|
+
xhr: function () {
|
2919
|
+
try {
|
2920
|
+
return new window.XMLHttpRequest();
|
2921
|
+
} catch (e1) {
|
2922
|
+
try {
|
2923
|
+
return new window.ActiveXObject("Microsoft.XMLHTTP");
|
2924
|
+
} catch (e2) {
|
2925
|
+
}
|
2926
|
+
}
|
2927
|
+
},
|
2928
|
+
parseJSON: function (data) {
|
2929
|
+
return !data ? null : window.JSON && window.JSON.parse ? window.JSON.parse(data) : new Function("return " + data)();
|
2930
|
+
},
|
2931
|
+
// http://github.com/flowersinthesand/stringifyJSON
|
2932
|
+
stringifyJSON: function (value) {
|
2933
|
+
var escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, meta = {
|
2934
|
+
'\b': '\\b',
|
2935
|
+
'\t': '\\t',
|
2936
|
+
'\n': '\\n',
|
2937
|
+
'\f': '\\f',
|
2938
|
+
'\r': '\\r',
|
2939
|
+
'"': '\\"',
|
2940
|
+
'\\': '\\\\'
|
2941
|
+
};
|
2942
|
+
|
2943
|
+
function quote(string) {
|
2944
|
+
return '"' + string.replace(escapable, function (a) {
|
2945
|
+
var c = meta[a];
|
2946
|
+
return typeof c === "string" ? c : "\\u" + ("0000" + a.charCodeAt(0).toString(16)).slice(-4);
|
2947
|
+
}) + '"';
|
2948
|
+
}
|
2949
|
+
|
2950
|
+
function f(n) {
|
2951
|
+
return n < 10 ? "0" + n : n;
|
2952
|
+
}
|
2953
|
+
|
2954
|
+
return window.JSON && window.JSON.stringify ? window.JSON.stringify(value) : (function str(key, holder) {
|
2955
|
+
var i, v, len, partial, value = holder[key], type = typeof value;
|
2956
|
+
|
2957
|
+
if (value && typeof value === "object" && typeof value.toJSON === "function") {
|
2958
|
+
value = value.toJSON(key);
|
2959
|
+
type = typeof value;
|
2960
|
+
}
|
2961
|
+
|
2962
|
+
switch (type) {
|
2963
|
+
case "string":
|
2964
|
+
return quote(value);
|
2965
|
+
case "number":
|
2966
|
+
return isFinite(value) ? String(value) : "null";
|
2967
|
+
case "boolean":
|
2968
|
+
return String(value);
|
2969
|
+
case "object":
|
2970
|
+
if (!value) {
|
2971
|
+
return "null";
|
2972
|
+
}
|
2973
|
+
|
2974
|
+
switch (Object.prototype.toString.call(value)) {
|
2975
|
+
case "[object Date]":
|
2976
|
+
return isFinite(value.valueOf()) ? '"' + value.getUTCFullYear() + "-" + f(value.getUTCMonth() + 1) + "-"
|
2977
|
+
+ f(value.getUTCDate()) + "T" + f(value.getUTCHours()) + ":" + f(value.getUTCMinutes()) + ":" + f(value.getUTCSeconds())
|
2978
|
+
+ "Z" + '"' : "null";
|
2979
|
+
case "[object Array]":
|
2980
|
+
len = value.length;
|
2981
|
+
partial = [];
|
2982
|
+
for (i = 0; i < len; i++) {
|
2983
|
+
partial.push(str(i, value) || "null");
|
2984
|
+
}
|
2985
|
+
|
2986
|
+
return "[" + partial.join(",") + "]";
|
2987
|
+
default:
|
2988
|
+
partial = [];
|
2989
|
+
for (i in value) {
|
2990
|
+
if (hasOwn.call(value, i)) {
|
2991
|
+
v = str(i, value);
|
2992
|
+
if (v) {
|
2993
|
+
partial.push(quote(i) + ":" + v);
|
2994
|
+
}
|
2995
|
+
}
|
2996
|
+
}
|
2997
|
+
|
2998
|
+
return "{" + partial.join(",") + "}";
|
2999
|
+
}
|
3000
|
+
}
|
3001
|
+
})("", {
|
3002
|
+
"": value
|
3003
|
+
});
|
3004
|
+
},
|
3005
|
+
|
3006
|
+
checkCORSSupport: function () {
|
3007
|
+
if (atmosphere.util.browser.msie && !window.XDomainRequest && +atmosphere.util.browser.version.split(".")[0] < 11) {
|
3008
|
+
return true;
|
3009
|
+
} else if (atmosphere.util.browser.opera && +atmosphere.util.browser.version.split(".") < 12.0) {
|
3010
|
+
return true;
|
3011
|
+
}
|
3012
|
+
|
3013
|
+
// KreaTV 4.1 -> 4.4
|
3014
|
+
else if (atmosphere.util.trim(navigator.userAgent).slice(0, 16) === "KreaTVWebKit/531") {
|
3015
|
+
return true;
|
3016
|
+
}
|
3017
|
+
// KreaTV 3.8
|
3018
|
+
else if (atmosphere.util.trim(navigator.userAgent).slice(-7).toLowerCase() === "kreatel") {
|
3019
|
+
return true;
|
3020
|
+
}
|
3021
|
+
|
3022
|
+
// Force Android to use CORS as some version like 2.2.3 fail otherwise
|
3023
|
+
var ua = navigator.userAgent.toLowerCase();
|
3024
|
+
var isAndroid = ua.indexOf("android") > -1;
|
3025
|
+
if (isAndroid) {
|
3026
|
+
return true;
|
3027
|
+
}
|
3028
|
+
return false;
|
3029
|
+
}
|
3030
|
+
};
|
3031
|
+
|
3032
|
+
guid = atmosphere.util.now();
|
3033
|
+
|
3034
|
+
// Browser sniffing
|
3035
|
+
(function () {
|
3036
|
+
var ua = navigator.userAgent.toLowerCase(),
|
3037
|
+
match = /(chrome)[ \/]([\w.]+)/.exec(ua) ||
|
3038
|
+
/(webkit)[ \/]([\w.]+)/.exec(ua) ||
|
3039
|
+
/(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) ||
|
3040
|
+
/(msie) ([\w.]+)/.exec(ua) ||
|
3041
|
+
/(trident)(?:.*? rv:([\w.]+)|)/.exec(ua) ||
|
3042
|
+
ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) ||
|
3043
|
+
[];
|
3044
|
+
|
3045
|
+
atmosphere.util.browser[match[1] || ""] = true;
|
3046
|
+
atmosphere.util.browser.version = match[2] || "0";
|
3047
|
+
|
3048
|
+
// Trident is the layout engine of the Internet Explorer
|
3049
|
+
// IE 11 has no "MSIE: 11.0" token
|
3050
|
+
if (atmosphere.util.browser.trident) {
|
3051
|
+
atmosphere.util.browser.msie = true;
|
3052
|
+
}
|
3053
|
+
|
3054
|
+
// The storage event of Internet Explorer and Firefox 3 works strangely
|
3055
|
+
if (atmosphere.util.browser.msie || (atmosphere.util.browser.mozilla && +atmosphere.util.browser.version.split(".")[0] === 1)) {
|
3056
|
+
atmosphere.util.storage = false;
|
3057
|
+
}
|
3058
|
+
})();
|
3059
|
+
|
3060
|
+
atmosphere.util.on(window, "unload", function (event) {
|
3061
|
+
atmosphere.unsubscribe();
|
3062
|
+
});
|
3063
|
+
|
3064
|
+
// Pressing ESC key in Firefox kills the connection
|
3065
|
+
// for your information, this is fixed in Firefox 20
|
3066
|
+
// https://bugzilla.mozilla.org/show_bug.cgi?id=614304
|
3067
|
+
atmosphere.util.on(window, "keypress", function (event) {
|
3068
|
+
if (event.charCode === 27 || event.keyCode === 27) {
|
3069
|
+
if (event.preventDefault) {
|
3070
|
+
event.preventDefault();
|
3071
|
+
}
|
3072
|
+
}
|
3073
|
+
});
|
3074
|
+
|
3075
|
+
atmosphere.util.on(window, "offline", function () {
|
3076
|
+
atmosphere.unsubscribe();
|
3077
|
+
});
|
3078
|
+
|
3079
|
+
return atmosphere;
|
3080
|
+
}));
|
3081
|
+
/* jshint eqnull:true, noarg:true, noempty:true, eqeqeq:true, evil:true, laxbreak:true, undef:true, browser:true, indent:false, maxerr:50 */
|