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.
@@ -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
@@ -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
@@ -3,7 +3,4 @@ ENV["RAILS_ENV"] ||= 'test'
3
3
 
4
4
  require "action_view/railtie"
5
5
  require "nokogiri"
6
- require 'khipu-rails'
7
-
8
- KhipuRails::Config.user_id = 1234
9
- KhipuRails::Config.api_key = '1234567890asdfghjkl'
6
+ require 'khipu-rails'
@@ -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 */