├── .github
├── FUNDING.yml
└── workflows
│ └── maven.yml
├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── modules
└── javascript
│ ├── package.json
│ ├── pom.xml
│ └── src
│ └── main
│ └── webapp
│ ├── WEB-INF
│ └── web.xml
│ └── javascript
│ └── atmosphere.js
└── pom.xml
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: [atmosphere]
4 |
5 |
--------------------------------------------------------------------------------
/.github/workflows/maven.yml:
--------------------------------------------------------------------------------
1 | name: Java CI
2 |
3 | on: [push]
4 |
5 | jobs:
6 | build:
7 |
8 | runs-on: ubuntu-latest
9 |
10 | steps:
11 | - uses: actions/checkout@v1
12 | - name: Set up JDK 1.8
13 | uses: actions/setup-java@v1
14 | with:
15 | java-version: 1.8
16 | - name: Build with Maven
17 | run: mvn -B package --file pom.xml
18 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.class
2 | *~
3 | .*.swp
4 | .*.swo
5 | .loadpath
6 | .buildpath
7 | .project
8 | .settings
9 | .classpath
10 | .metadata
11 | .idea
12 | *.iml
13 | *.ipr
14 | *.iws
15 | nbproject
16 | .DS_Store
17 | target
18 | test-output
19 | nbactions.xml
20 | samples/gwt-demo/src/main/webapp/WEB-INF/classes/
21 | samples/gwt-chat/src/main/webapp/WEB-INF/classes/
22 | samples/gwt-conn-share/src/main/webapp/WEB-INF/classes/
23 | atlassian-ide-plugin.xml
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | dist: trusty
2 | sudo: false
3 | language: javascript
4 | jdk:
5 | - openjdk8
6 | before_install: 'mvn -version'
7 | install: 'mvn clean install'
8 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright {yyyy} {name of copyright owner}
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Welcome to Atmosphere: The Asynchronous WebSocket/Comet Framework
2 | Atmosphere transparently supports WebSockets, Server Sent Events (SSE), Long-Polling, HTTP Streaming (Forever frame) and JSONP.
3 |
4 | * [npm/Node.js client](https://github.com/Atmosphere/atmosphere.js-node)
5 |
6 | ## Install
7 |
8 | * NPM - [atmosphere.js](https://www.npmjs.com/package/atmosphere.js)
9 |
10 | ### maven
11 |
12 | ```xml
13 |
14 | org.atmosphere.client
15 | javascript
16 | 4.0.0
17 |
18 | ```
19 |
20 | ### npm
21 |
22 | ```shell
23 | npm install atmosphere.js
24 | ```
25 |
26 | Best way to start with Atmosphere is to use chatGPT and ask for an example!
27 |
--------------------------------------------------------------------------------
/modules/javascript/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "atmosphere.js",
3 | "version": "3.0.3",
4 | "description": "",
5 | "main": "src/main/webapp/javascript/atmosphere.js",
6 | "registry": "npm",
7 | "scripts": {
8 | "test": "echo \"Error: no test specified\" && exit 1"
9 | },
10 | "repository": {
11 | "type": "git",
12 | "url": "git+https://github.com/Atmosphere/atmosphere-javascript.git"
13 | },
14 | "author": "Jeanfrancois Arcand",
15 | "license": "Apache-2.0",
16 | "bugs": {
17 | "url": "https://github.com/Atmosphere/atmosphere-javascript/issues"
18 | },
19 | "homepage": "https://github.com/Atmosphere/atmosphere-javascript#readme"
20 | }
21 |
--------------------------------------------------------------------------------
/modules/javascript/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | org.atmosphere
5 | javascript-project
6 | 4.0.2-SNAPSHOT
7 | ../../pom.xml
8 |
9 | 4.0.0
10 | org.atmosphere.client
11 | javascript
12 | war
13 | 4.0.2-SNAPSHOT
14 | javascript
15 |
16 |
17 |
18 |
19 |
20 | net.alchim31.maven
21 | yuicompressor-maven-plugin
22 | 1.5.1
23 |
24 |
25 |
26 | compress
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/modules/javascript/src/main/webapp/WEB-INF/web.xml:
--------------------------------------------------------------------------------
1 | DUMMY web.xml
2 |
--------------------------------------------------------------------------------
/modules/javascript/src/main/webapp/javascript/atmosphere.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2011-2025 Async-IO.org
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 if (typeof exports !== 'undefined') {
31 | // CommonJS
32 | module.exports = factory();
33 | } else {
34 | // Browser globals, Window
35 | root.atmosphere = factory();
36 | }
37 | }(this, function () {
38 |
39 | "use strict";
40 |
41 | var offline = false,
42 | requests = [],
43 | callbacks = [],
44 | uuid = 0;
45 |
46 | /**
47 | * {boolean} If window beforeUnload event has been called.
48 | * Flag will be reset after 5000 ms
49 | *
50 | * @private
51 | */
52 | var _beforeUnloadState = false;
53 |
54 | var atmosphere = {
55 | version: "4.0.2",
56 | onError: function (response) {
57 | },
58 | onClose: function (response) {
59 | },
60 | onOpen: function (response) {
61 | },
62 | onReopen: function (response) {
63 | },
64 | onMessage: function (response) {
65 | },
66 | onReconnect: function (request, response) {
67 | },
68 | onMessagePublished: function (response) {
69 | },
70 | onTransportFailure: function (errorMessage, _request) {
71 | },
72 | onLocalMessage: function (response) {
73 | },
74 | onFailureToReconnect: function (request, response) {
75 | },
76 | onClientTimeout: function (request) {
77 | },
78 | onOpenAfterResume: function (request) {
79 | },
80 |
81 | /**
82 | * Creates an object based on an atmosphere subscription that exposes functions defined by the Websocket interface.
83 | *
84 | * @class WebsocketApiAdapter
85 | * @param {Object} request the request object to build the underlying subscription
86 | * @constructor
87 | */
88 | WebsocketApiAdapter: function (request) {
89 | var _socket, _adapter;
90 |
91 | /**
92 | * Overrides the onMessage callback in given request.
93 | *
94 | * @method onMessage
95 | * @param {Object} e the event object
96 | */
97 | request.onMessage = function (e) {
98 | _adapter.onmessage({ data: e.responseBody });
99 | };
100 |
101 | /**
102 | * Overrides the onMessagePublished callback in given request.
103 | *
104 | * @method onMessagePublished
105 | * @param {Object} e the event object
106 | */
107 | request.onMessagePublished = function (e) {
108 | _adapter.onmessage({ data: e.responseBody });
109 | };
110 |
111 | /**
112 | * Overrides the onOpen callback in given request to proxy the event to the adapter.
113 | *
114 | * @method onOpen
115 | * @param {Object} e the event object
116 | */
117 | request.onOpen = function (e) {
118 | _adapter.onopen(e);
119 | };
120 |
121 | _adapter = {
122 | close: function () {
123 | _socket.close();
124 | },
125 |
126 | send: function (data) {
127 | _socket.push(data);
128 | },
129 |
130 | onmessage: function (e) {
131 | },
132 |
133 | onopen: function (e) {
134 | },
135 |
136 | onclose: function (e) {
137 | },
138 |
139 | onerror: function (e) {
140 |
141 | }
142 | };
143 | _socket = new atmosphere.subscribe(request);
144 |
145 | return _adapter;
146 | },
147 |
148 | AtmosphereRequest: function (options) {
149 |
150 | /**
151 | * {Object} Request parameters.
152 | *
153 | * @private
154 | */
155 | var _request = {
156 | timeout: 300000,
157 | method: 'GET',
158 | headers: {},
159 | contentType: '',
160 | callback: null,
161 | url: '',
162 | data: '',
163 | suspend: true,
164 | maxRequest: -1,
165 | reconnect: true,
166 | mrequest: undefined,
167 | maxStreamingLength: 10000000,
168 | lastIndex: 0,
169 | logLevel: 'info',
170 | requestCount: 0,
171 | fallbackMethod: 'GET',
172 | fallbackTransport: 'streaming',
173 | transport: 'long-polling',
174 | webSocketUrl: undefined,
175 | webSocketImpl: null,
176 | webSocketBinaryType: null,
177 | dispatchUrl: null,
178 | webSocketPathDelimiter: "@@",
179 | enableXDR: false,
180 | rewriteURL: false,
181 | attachHeadersAsQueryString: true,
182 | executeCallbackBeforeReconnect: false,
183 | readyState: 0,
184 | withCredentials: false,
185 | trackMessageLength: false,
186 | messageDelimiter: '|',
187 | connectTimeout: -1,
188 | reconnectInterval: 0,
189 | dropHeaders: true,
190 | uuid: 0,
191 | shared: false,
192 | readResponsesHeaders: false,
193 | maxReconnectOnClose: 5,
194 | enableProtocol: true,
195 | disableDisconnect: false,
196 | pollingInterval: 0,
197 | heartbeat: {
198 | client: null,
199 | server: null
200 | },
201 | ackInterval: 0,
202 | reconnectOnServerError: true,
203 | handleOnlineOffline: true,
204 | maxWebsocketErrorRetries: 1,
205 | curWebsocketErrorRetries: 0,
206 | unloadBackwardCompat: !navigator.sendBeacon,
207 | useBeforeUnloadForCleanup: true,
208 | id: undefined,
209 | openId: undefined,
210 | reconnectId: undefined,
211 | firstMessage: undefined,
212 | isOpen: undefined,
213 | isReopen: undefined,
214 | closed: undefined,
215 | ctime: undefined,
216 | heartbeatTimer: undefined,
217 | force: undefined,
218 | onError: function (response) {
219 | },
220 | onClose: function (response) {
221 | },
222 | onOpen: function (response) {
223 | },
224 | onMessage: function (response) {
225 | },
226 | onReopen: function (request, response) {
227 | },
228 | onReconnect: function (request, response) {
229 | },
230 | onMessagePublished: function (response) {
231 | },
232 | onTransportFailure: function (reason, request) {
233 | },
234 | onLocalMessage: function (request) {
235 | },
236 | onFailureToReconnect: function (request, response) {
237 | },
238 | onClientTimeout: function (request) {
239 | },
240 | onOpenAfterResume: function (request) {
241 | }
242 | };
243 |
244 | /**
245 | * {Object} Request's last response.
246 | *
247 | * @private
248 | */
249 | var _response = {
250 | status: 200,
251 | reasonPhrase: "OK",
252 | responseBody: '',
253 | messages: [],
254 | headers: {},
255 | state: "messageReceived",
256 | transport: "polling",
257 | error: null,
258 | request: null,
259 | partialMessage: "",
260 | errorHandled: false,
261 | closedByClientTimeout: false,
262 | ffTryingReconnect: false
263 | };
264 |
265 | /**
266 | * {websocket} Opened web socket.
267 | *
268 | * @private
269 | */
270 | var _websocket = null;
271 |
272 | /**
273 | * {SSE} Opened SSE.
274 | *
275 | * @private
276 | */
277 | var _sse = null;
278 |
279 | /**
280 | * {XMLHttpRequest, ActiveXObject} Opened ajax request (in case of http-streaming or long-polling)
281 | *
282 | * @private
283 | */
284 | var _activeRequest = null;
285 |
286 | /**
287 | * {Object} Object use for streaming with IE.
288 | *
289 | * @private
290 | */
291 | var _ieStream = null;
292 |
293 | /**
294 | * {Object} Object use for jsonp transport.
295 | *
296 | * @private
297 | */
298 | var _jqxhr = null;
299 |
300 | /**
301 | * {boolean} If request has been subscribed or not.
302 | *
303 | * @private
304 | */
305 | var _subscribed = true;
306 |
307 | /**
308 | * {number} Number of test reconnection.
309 | *
310 | * @private
311 | */
312 | var _requestCount = 0;
313 |
314 | /**
315 | * The Heartbeat interval send by the server.
316 | * @type {int}
317 | * @private
318 | */
319 | var _heartbeatInterval = 0;
320 |
321 | /**
322 | * The Heartbeat bytes send by the server.
323 | * @type {string}
324 | * @private
325 | */
326 | var _heartbeatPadding = 'X';
327 |
328 | /**
329 | * {boolean} If request is currently aborted.
330 | *
331 | * @private
332 | */
333 | var _abortingConnection = false;
334 |
335 | /**
336 | * A local "channel' of communication.
337 | *
338 | * @private
339 | */
340 | var _localSocketF = null;
341 |
342 | /**
343 | * The storage used.
344 | *
345 | * @private
346 | */
347 | var _storageService;
348 |
349 | /**
350 | * Local communication
351 | *
352 | * @private
353 | */
354 | var _localStorageService = null;
355 |
356 | /**
357 | * A Unique ID
358 | *
359 | * @private
360 | */
361 | var guid = atmosphere.util.now();
362 |
363 | /** Trace time */
364 | var _traceTimer;
365 |
366 | /** Key for connection sharing */
367 | var _sharingKey;
368 |
369 | /**
370 | * {number} Holds the timeout ID for the beforeUnload flag reset.
371 | *
372 | * @private
373 | */
374 | var _beforeUnloadTimeoutId;
375 |
376 | // Automatic call to subscribe
377 | _subscribe(options);
378 |
379 | /**
380 | * Initialize atmosphere request object.
381 | *
382 | * @private
383 | */
384 | function _init() {
385 | _subscribed = true;
386 | _abortingConnection = false;
387 | _requestCount = 0;
388 |
389 | _websocket = null;
390 | _sse = null;
391 | _activeRequest = null;
392 | _ieStream = null;
393 | }
394 |
395 | /**
396 | * Re-initialize atmosphere object.
397 | *
398 | * @private
399 | */
400 | function _reinit() {
401 | _clearState();
402 | _init();
403 | }
404 |
405 | /**
406 | * Returns true if the given level is equal or above the configured log level.
407 | *
408 | * @private
409 | */
410 | function _canLog(level) {
411 | if (level == 'debug') {
412 | return _request.logLevel === 'debug';
413 | } else if (level == 'info') {
414 | return _request.logLevel === 'info' || _request.logLevel === 'debug';
415 | } else if (level == 'warn') {
416 | return _request.logLevel === 'warn' || _request.logLevel === 'info' || _request.logLevel === 'debug';
417 | } else if (level == 'error') {
418 | return _request.logLevel === 'error' || _request.logLevel === 'warn' || _request.logLevel === 'info' || _request.logLevel === 'debug';
419 | } else {
420 | return false;
421 | }
422 | }
423 |
424 | function _debug(msg) {
425 | if (_canLog('debug')) {
426 | atmosphere.util.debug(new Date() + " Atmosphere: " + msg);
427 | }
428 | }
429 |
430 | /**
431 | *
432 | * @private
433 | */
434 | function _verifyStreamingLength(ajaxRequest, rq) {
435 | // Wait to be sure we have the full message before closing.
436 | if (_response.partialMessage === "" && (rq.transport === 'streaming') && (ajaxRequest.responseText.length > rq.maxStreamingLength)) {
437 | return true;
438 | }
439 | return false;
440 | }
441 |
442 | /**
443 | * Disconnect
444 | *
445 | * @private
446 | */
447 | function _disconnect() {
448 | if (_request.enableProtocol && !_request.disableDisconnect && !_request.firstMessage) {
449 | var query = "X-Atmosphere-Transport=close&X-Atmosphere-tracking-id=" + _request.uuid;
450 |
451 | atmosphere.util.each(_request.headers, function (name, value) {
452 | var h = atmosphere.util.isFunction(value) ? value.call(this, _request, _request, _response) : value;
453 | if (h != null) {
454 | query += "&" + encodeURIComponent(name) + "=" + encodeURIComponent(h);
455 | }
456 | });
457 |
458 | var url = _request.url.replace(/([?&])_=[^&]*/, query);
459 | url = url + (url === _request.url ? (/\?/.test(_request.url) ? "&" : "?") + query : "");
460 |
461 | var rq = {
462 | connected: false
463 | };
464 | var closeR = new atmosphere.AtmosphereRequest(rq);
465 | closeR.connectTimeout = _request.connectTimeout;
466 | closeR.attachHeadersAsQueryString = false;
467 | closeR.dropHeaders = true;
468 | closeR.url = url;
469 | closeR.contentType = "text/plain";
470 | closeR.transport = 'polling';
471 | closeR.method = 'GET';
472 | closeR.data = '';
473 | closeR.heartbeat = null;
474 | if (_request.enableXDR) {
475 | closeR.enableXDR = _request.enableXDR
476 | }
477 | _pushOnClose("", closeR);
478 | }
479 | }
480 |
481 | /**
482 | * Close request.
483 | *
484 | * @private
485 | */
486 | function _close() {
487 | _debug("Closing (AtmosphereRequest._close() called)");
488 |
489 | _abortingConnection = true;
490 | if (_request.reconnectId) {
491 | clearTimeout(_request.reconnectId);
492 | delete _request.reconnectId;
493 | }
494 |
495 | if (_request.heartbeatTimer) {
496 | clearTimeout(_request.heartbeatTimer);
497 | }
498 |
499 | _request.reconnect = false;
500 | _response.request = _request;
501 | _response.state = 'unsubscribe';
502 | _response.responseBody = "";
503 | _response.status = 408;
504 | _response.partialMessage = "";
505 | _request.curWebsocketErrorRetries = 0;
506 | _invokeCallback();
507 | _disconnect();
508 | _clearState();
509 | }
510 |
511 | function _clearState() {
512 | _response.partialMessage = "";
513 | if (_request.id) {
514 | clearTimeout(_request.id);
515 | }
516 |
517 | if (_request.heartbeatTimer) {
518 | clearTimeout(_request.heartbeatTimer);
519 | }
520 |
521 | // https://github.com/Atmosphere/atmosphere/issues/1860#issuecomment-74707226
522 | if (_request.reconnectId) {
523 | clearTimeout(_request.reconnectId);
524 | delete _request.reconnectId;
525 | }
526 |
527 | if (_ieStream != null) {
528 | _ieStream.close();
529 | _ieStream = null;
530 | }
531 | if (_jqxhr != null) {
532 | _jqxhr.abort();
533 | _jqxhr = null;
534 | }
535 | if (_activeRequest != null) {
536 | _activeRequest.abort();
537 | _activeRequest = null;
538 | }
539 | if (_websocket != null) {
540 | if (_websocket.canSendMessage) {
541 | _debug("invoking .close() on WebSocket object");
542 | _websocket.close();
543 | }
544 | _websocket = null;
545 | }
546 | if (_sse != null) {
547 | _sse.close();
548 | _sse = null;
549 | }
550 | _clearStorage();
551 | }
552 |
553 | function _clearStorage() {
554 | // Stop sharing a connection
555 | if (_storageService != null) {
556 | // Clears trace timer
557 | clearInterval(_traceTimer);
558 | // Removes the trace
559 | document.cookie = _sharingKey + "=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/";
560 | // The heir is the parent unless unloading
561 | _storageService.signal("close", {
562 | reason: "",
563 | heir: !_abortingConnection ? guid : (_storageService.get("children") || [])[0]
564 | });
565 | _storageService.close();
566 | }
567 | if (_localStorageService != null) {
568 | _localStorageService.close();
569 | }
570 | }
571 |
572 | /**
573 | * Subscribe request using request transport.
574 | * If request is currently opened, this one will be closed.
575 | *
576 | * @param {Object} Request parameters.
577 | * @private
578 | */
579 | function _subscribe(options) {
580 | _reinit();
581 |
582 | _request = atmosphere.util.extend(_request, options);
583 | // Allow at least 1 request
584 | _request.mrequest = _request.reconnect;
585 | if (!_request.reconnect) {
586 | _request.reconnect = true;
587 | }
588 | }
589 |
590 | /**
591 | * Check if web socket is supported (check for custom implementation provided by request object or browser implementation).
592 | *
593 | * @returns {boolean} True if web socket is supported, false otherwise.
594 | * @private
595 | */
596 | function _supportWebsocket() {
597 | return _request.webSocketImpl != null || window.WebSocket || window.MozWebSocket;
598 | }
599 |
600 | /**
601 | * Check if server side events (SSE) is supported (check for custom implementation provided by request object or browser implementation).
602 | *
603 | * @returns {boolean} True if web socket is supported, false otherwise.
604 | * @private
605 | */
606 | function _supportSSE() {
607 | // Origin parts
608 | var url = atmosphere.util.getAbsoluteURL(_request.url.toLowerCase());
609 | var parts = /^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/.exec(url);
610 | var crossOrigin = !!(parts && (
611 | // protocol
612 | parts[1] != window.location.protocol ||
613 | // hostname
614 | parts[2] != window.location.hostname ||
615 | // port
616 | (parts[3] || (parts[1] === "http:" ? 80 : 443)) != (window.location.port || (window.location.protocol === "http:" ? 80 : 443))
617 | ));
618 | return window.EventSource && (!crossOrigin || !atmosphere.util.browser.safari || atmosphere.util.browser.vmajor >= 7);
619 | }
620 |
621 | /**
622 | * Open request using request transport.
623 | * If request transport is 'websocket' but websocket can't be opened, request will automatically reconnect using fallback transport.
624 | *
625 | * @private
626 | */
627 | function _execute() {
628 | // Shared across multiple tabs/windows.
629 | if (_request.shared) {
630 | _localStorageService = _local(_request);
631 | if (_localStorageService != null) {
632 | if (_canLog('debug')) {
633 | atmosphere.util.debug("Storage service available. All communication will be local");
634 | }
635 |
636 | if (_localStorageService.open(_request)) {
637 | // Local connection.
638 | return;
639 | }
640 | }
641 |
642 | if (_canLog('debug')) {
643 | atmosphere.util.debug("No Storage service available.");
644 | }
645 | // Wasn't local or an error occurred
646 | _localStorageService = null;
647 | }
648 |
649 | // Protocol
650 | _request.firstMessage = uuid == 0 ? true : false;
651 | _request.isOpen = false;
652 | _request.ctime = atmosphere.util.now();
653 |
654 | // We carry any UUID set by the user or from a previous connection.
655 | if (_request.uuid === 0) {
656 | _request.uuid = uuid;
657 | }
658 | _response.closedByClientTimeout = false;
659 |
660 | if (_request.transport === 'websocket') {
661 | if (!_supportWebsocket()) {
662 | _reconnectWithFallbackTransport("Websocket is not supported, using request.fallbackTransport (" + _request.fallbackTransport
663 | + ")");
664 | } else {
665 | _executeWebSocket(false);
666 | }
667 | } else if (_request.transport === 'sse') {
668 | if (!_supportSSE()) {
669 | _reconnectWithFallbackTransport("Server Side Events(SSE) is not supported, using request.fallbackTransport ("
670 | + _request.fallbackTransport + ")");
671 | } else {
672 | _executeSSE(false);
673 | }
674 | } else {
675 | _executeRequest(_request);
676 | }
677 | }
678 |
679 | function _local(request) {
680 | var trace, connector, orphan, name = "atmosphere-" + request.url, connectors = {
681 | storage: function () {
682 | function onstorage(event) {
683 | if (event.key === name && event.newValue) {
684 | listener(event.newValue);
685 | }
686 | }
687 |
688 | if (!atmosphere.util.storage) {
689 | return;
690 | }
691 |
692 | var storage = window.localStorage,
693 | get = function (key) {
694 | var item = storage.getItem(name + "-" + key);
695 | return item === null ? [] : JSON.parse(item);
696 | },
697 | set = function (key, value) {
698 | storage.setItem(name + "-" + key, JSON.stringify(value));
699 | };
700 |
701 | return {
702 | init: function () {
703 | set("children", get("children").concat([guid]));
704 | atmosphere.util.on(window, "storage", onstorage);
705 | return get("opened");
706 | },
707 | signal: function (type, data) {
708 | storage.setItem(name, JSON.stringify({
709 | target: "p",
710 | type: type,
711 | data: data
712 | }));
713 | },
714 | close: function () {
715 | var children = get("children");
716 |
717 | atmosphere.util.off(window, "storage", onstorage);
718 | if (children) {
719 | if (removeFromArray(children, request.id)) {
720 | set("children", children);
721 | }
722 | }
723 | }
724 | };
725 | },
726 | windowref: function () {
727 | var win = window.open("", name.replace(/\W/g, ""));
728 |
729 | if (!win || win.closed || !win.callbacks) {
730 | return;
731 | }
732 |
733 | return {
734 | init: function () {
735 | win.callbacks.push(listener);
736 | win.children.push(guid);
737 | return win.opened;
738 | },
739 | signal: function (type, data) {
740 | if (!win.closed && win.fire) {
741 | win.fire(JSON.stringify({
742 | target: "p",
743 | type: type,
744 | data: data
745 | }));
746 | }
747 | },
748 | close: function () {
749 | // Removes traces only if the parent is alive
750 | if (!orphan) {
751 | removeFromArray(win.callbacks, listener);
752 | removeFromArray(win.children, guid);
753 | }
754 | }
755 |
756 | };
757 | }
758 | };
759 |
760 | function removeFromArray(array, val) {
761 | var i, length = array.length;
762 |
763 | for (i = 0; i < length; i++) {
764 | if (array[i] === val) {
765 | array.splice(i, 1);
766 | }
767 | }
768 |
769 | return length !== array.length;
770 | }
771 |
772 | // Receives open, close and message command from the parent
773 | function listener(string) {
774 | var command = JSON.parse(string), data = command.data;
775 |
776 | if (command.target === "c") {
777 | switch (command.type) {
778 | case "open":
779 | _open("opening", 'local', _request);
780 | break;
781 | case "close":
782 | if (!orphan) {
783 | orphan = true;
784 | if (data.reason === "aborted") {
785 | _close();
786 | } else {
787 | // Gives the heir some time to reconnect
788 | if (data.heir === guid) {
789 | _execute();
790 | } else {
791 | setTimeout(function () {
792 | _execute();
793 | }, 100);
794 | }
795 | }
796 | }
797 | break;
798 | case "message":
799 | _prepareCallback(data, "messageReceived", 200, request.transport);
800 | break;
801 | case "localMessage":
802 | _localMessage(data);
803 | break;
804 | }
805 | }
806 | }
807 |
808 | function findTrace() {
809 | var matcher = new RegExp("(?:^|; )(" + encodeURIComponent(name) + ")=([^;]*)").exec(document.cookie);
810 | if (matcher) {
811 | return JSON.parse(decodeURIComponent(matcher[2]));
812 | }
813 | }
814 |
815 | // Finds and validates the parent socket's trace from the cookie
816 | trace = findTrace();
817 | if (!trace || atmosphere.util.now() - trace.ts > 1000) {
818 | return;
819 | }
820 |
821 | // Chooses a connector
822 | connector = connectors.storage() || connectors.windowref();
823 | if (!connector) {
824 | return;
825 | }
826 |
827 | return {
828 | open: function () {
829 | var parentOpened;
830 |
831 | // Checks the shared one is alive
832 | _traceTimer = setInterval(function () {
833 | var oldTrace = trace;
834 | trace = findTrace();
835 | if (!trace || oldTrace.ts === trace.ts) {
836 | // Simulates a close signal
837 | listener(JSON.stringify({
838 | target: "c",
839 | type: "close",
840 | data: {
841 | reason: "error",
842 | heir: oldTrace.heir
843 | }
844 | }));
845 | }
846 | }, 1000);
847 |
848 | parentOpened = connector.init();
849 | if (parentOpened) {
850 | // Firing the open event without delay robs the user of the opportunity to bind connecting event handlers
851 | setTimeout(function () {
852 | _open("opening", 'local', request);
853 | }, 50);
854 | }
855 | return parentOpened;
856 | },
857 | send: function (event) {
858 | connector.signal("send", event);
859 | },
860 | localSend: function (event) {
861 | connector.signal("localSend", JSON.stringify({
862 | id: guid,
863 | event: event
864 | }));
865 | },
866 | close: function () {
867 | // Do not signal the parent if this method is executed by the unload event handler
868 | if (!_abortingConnection) {
869 | clearInterval(_traceTimer);
870 | connector.signal("close");
871 | connector.close();
872 | }
873 | }
874 | };
875 | }
876 |
877 | function share() {
878 | var storageService, name = "atmosphere-" + _request.url, servers = {
879 | // Powered by the storage event and the localStorage
880 | // http://www.w3.org/TR/webstorage/#event-storage
881 | storage: function () {
882 | function onstorage(event) {
883 | // When a deletion, newValue initialized to null
884 | if (event.key === name && event.newValue) {
885 | listener(event.newValue);
886 | }
887 | }
888 |
889 | if (!atmosphere.util.storage) {
890 | return;
891 | }
892 |
893 | var storage = window.localStorage;
894 |
895 | return {
896 | init: function () {
897 | // Handles the storage event
898 | atmosphere.util.on(window, "storage", onstorage);
899 | },
900 | signal: function (type, data) {
901 | storage.setItem(name, JSON.stringify({
902 | target: "c",
903 | type: type,
904 | data: data
905 | }));
906 | },
907 | get: function (key) {
908 | return JSON.parse(storage.getItem(name + "-" + key));
909 | },
910 | set: function (key, value) {
911 | storage.setItem(name + "-" + key, JSON.stringify(value));
912 | },
913 | close: function () {
914 | atmosphere.util.off(window, "storage", onstorage);
915 | storage.removeItem(name);
916 | storage.removeItem(name + "-opened");
917 | storage.removeItem(name + "-children");
918 | }
919 |
920 | };
921 | },
922 | // Powered by the window.open method
923 | // https://developer.mozilla.org/en/DOM/window.open
924 | windowref: function () {
925 | // Internet Explorer raises an invalid argument error
926 | // when calling the window.open method with the name containing non-word characters
927 | var neim = name.replace(/\W/g, ""), container = document.getElementById(neim), win;
928 |
929 | if (!container) {
930 | container = document.createElement("div");
931 | container.id = neim;
932 | container.style.display = "none";
933 | container.innerHTML = '';
934 | document.body.appendChild(container);
935 | }
936 |
937 | win = container.firstChild.contentWindow;
938 |
939 | return {
940 | init: function () {
941 | // Callbacks from different windows
942 | win.callbacks = [listener];
943 | // In IE 8 and less, only string argument can be safely passed to the function in other window
944 | win.fire = function (string) {
945 | var i;
946 |
947 | for (i = 0; i < win.callbacks.length; i++) {
948 | win.callbacks[i](string);
949 | }
950 | };
951 | },
952 | signal: function (type, data) {
953 | if (!win.closed && win.fire) {
954 | win.fire(JSON.stringify({
955 | target: "c",
956 | type: type,
957 | data: data
958 | }));
959 | }
960 | },
961 | get: function (key) {
962 | return !win.closed ? win[key] : null;
963 | },
964 | set: function (key, value) {
965 | if (!win.closed) {
966 | win[key] = value;
967 | }
968 | },
969 | close: function () {
970 | }
971 | };
972 | }
973 | };
974 |
975 | // Receives send and close command from the children
976 | function listener(string) {
977 | var command = JSON.parse(string), data = command.data;
978 |
979 | if (command.target === "p") {
980 | switch (command.type) {
981 | case "send":
982 | _push(data, _request);
983 | break;
984 | case "localSend":
985 | _localMessage(data);
986 | break;
987 | case "close":
988 | _close();
989 | break;
990 | }
991 | }
992 | }
993 |
994 | _localSocketF = function propagateMessageEvent(context) {
995 | storageService.signal("message", context);
996 | };
997 |
998 | function leaveTrace() {
999 | document.cookie = _sharingKey + "=" +
1000 | // Opera's JSON implementation ignores a number whose a last digit of 0 strangely
1001 | // but has no problem with a number whose a last digit of 9 + 1
1002 | encodeURIComponent(JSON.stringify({
1003 | ts: atmosphere.util.now() + 1,
1004 | heir: (storageService.get("children") || [])[0]
1005 | })) + "; path=/";
1006 | }
1007 |
1008 | // Chooses a storageService
1009 | storageService = servers.storage() || servers.windowref();
1010 | storageService.init();
1011 |
1012 | if (_canLog('debug')) {
1013 | atmosphere.util.debug("Installed StorageService " + storageService);
1014 | }
1015 |
1016 | // List of children sockets
1017 | storageService.set("children", []);
1018 |
1019 | if (storageService.get("opened") != null && !storageService.get("opened")) {
1020 | // Flag indicating the parent socket is opened
1021 | storageService.set("opened", false);
1022 | }
1023 | // Leaves traces
1024 | _sharingKey = encodeURIComponent(name);
1025 | leaveTrace();
1026 | _traceTimer = setInterval(leaveTrace, 1000);
1027 |
1028 | _storageService = storageService;
1029 | }
1030 |
1031 | /**
1032 | * @private
1033 | */
1034 | function _open(state, transport, request) {
1035 | if (_request.shared && transport !== 'local') {
1036 | share();
1037 | }
1038 |
1039 | if (_storageService != null) {
1040 | _storageService.set("opened", true);
1041 | }
1042 |
1043 | request.close = function () {
1044 | _close();
1045 | };
1046 |
1047 | if (_requestCount > 0 && state === 're-connecting') {
1048 | request.isReopen = true;
1049 | _tryingToReconnect(_response);
1050 | } else if (!_response.error) {
1051 | _response.request = request;
1052 | var prevState = _response.state;
1053 | _response.state = state;
1054 | var prevTransport = _response.transport;
1055 | _response.transport = transport;
1056 |
1057 | var _body = _response.responseBody;
1058 | _invokeCallback();
1059 | _response.responseBody = _body;
1060 |
1061 | _response.state = prevState;
1062 | _response.transport = prevTransport;
1063 | }
1064 | }
1065 |
1066 | /**
1067 | * Execute request using jsonp transport.
1068 | *
1069 | * @param request {Object} request Request parameters, if undefined _request object will be used.
1070 | * @private
1071 | */
1072 | function _jsonp(request) {
1073 | // When CORS is enabled, make sure we force the proper transport.
1074 | request.transport = "jsonp";
1075 |
1076 | var rq = _request, script;
1077 | if ((request != null) && (typeof (request) !== 'undefined')) {
1078 | rq = request;
1079 | }
1080 |
1081 | _jqxhr = {
1082 | open: function () {
1083 | var callback = "atmosphere" + (++guid);
1084 |
1085 | function _reconnectOnFailure() {
1086 | rq.lastIndex = 0;
1087 |
1088 | if (rq.openId) {
1089 | clearTimeout(rq.openId);
1090 | }
1091 |
1092 | if (rq.heartbeatTimer) {
1093 | clearTimeout(rq.heartbeatTimer);
1094 | }
1095 |
1096 | if (rq.reconnect && _requestCount++ < rq.maxReconnectOnClose) {
1097 | _open('re-connecting', rq.transport, rq);
1098 | _reconnect(_jqxhr, rq, request.reconnectInterval);
1099 | rq.openId = setTimeout(function () {
1100 | _triggerOpen(rq);
1101 | }, rq.reconnectInterval + 1000);
1102 | } else if (_request.reconnect && _request.fallbackTransport !== 'none') {
1103 | _reconnectWithFallbackTransport("JSONP maxReconnectOnClose reached. Downgrading to " + _request.fallbackTransport);
1104 | } else {
1105 | _onError(0, "maxReconnectOnClose reached");
1106 | }
1107 | }
1108 |
1109 | function poll() {
1110 | var url = rq.url;
1111 | if (rq.dispatchUrl != null) {
1112 | url += rq.dispatchUrl;
1113 | }
1114 |
1115 | var data = rq.data;
1116 | if (rq.attachHeadersAsQueryString) {
1117 | url = _attachHeaders(rq);
1118 | if (data !== '') {
1119 | url += "&X-Atmosphere-Post-Body=" + encodeURIComponent(data);
1120 | }
1121 | data = '';
1122 | }
1123 |
1124 | var head = document.head || document.getElementsByTagName("head")[0] || document.documentElement;
1125 |
1126 | script = document.createElement("script");
1127 | script.src = url + "&jsonpTransport=" + callback;
1128 | script.clean = function () {
1129 | script.clean = script.onerror = script.onload = script.onreadystatechange = null;
1130 | if (script.parentNode) {
1131 | script.parentNode.removeChild(script);
1132 | }
1133 |
1134 | if (++request.scriptCount === 2) {
1135 | request.scriptCount = 1;
1136 | _reconnectOnFailure();
1137 | }
1138 |
1139 | };
1140 | script.onload = script.onreadystatechange = function () {
1141 | _debug("jsonp.onload");
1142 | if (!script.readyState || /loaded|complete/.test(script.readyState)) {
1143 | script.clean();
1144 | }
1145 | };
1146 |
1147 | script.onerror = function () {
1148 | _debug("jsonp.onerror");
1149 | request.scriptCount = 1;
1150 | script.clean();
1151 | };
1152 |
1153 | head.insertBefore(script, head.firstChild);
1154 | }
1155 |
1156 | // Attaches callback
1157 | window[callback] = function (msg) {
1158 | _debug("jsonp.window");
1159 | request.scriptCount = 0;
1160 | if (rq.reconnect && rq.maxRequest === -1 || rq.requestCount++ < rq.maxRequest) {
1161 |
1162 | // _readHeaders(_jqxhr, rq);
1163 | if (!rq.executeCallbackBeforeReconnect) {
1164 | _reconnect(_jqxhr, rq, rq.pollingInterval);
1165 | }
1166 |
1167 | if (msg != null && typeof msg !== 'string') {
1168 | try {
1169 | msg = msg.message;
1170 | } catch (err) {
1171 | // The message was partial
1172 | }
1173 | }
1174 | var skipCallbackInvocation = _trackMessageSize(msg, rq, _response);
1175 | if (!skipCallbackInvocation) {
1176 | _prepareCallback(_response.responseBody, "messageReceived", 200, rq.transport);
1177 | }
1178 |
1179 | if (rq.executeCallbackBeforeReconnect) {
1180 | _reconnect(_jqxhr, rq, rq.pollingInterval);
1181 | }
1182 | _timeout(rq);
1183 | } else {
1184 | atmosphere.util.log(_request.logLevel, ["JSONP reconnect maximum try reached " + _request.requestCount]);
1185 | _onError(0, "maxRequest reached");
1186 | }
1187 | };
1188 | setTimeout(function () {
1189 | poll();
1190 | }, 50);
1191 | },
1192 | abort: function () {
1193 | if (script && script.clean) {
1194 | script.clean();
1195 | }
1196 | }
1197 | };
1198 | _jqxhr.open();
1199 | }
1200 |
1201 | /**
1202 | * Build websocket object.
1203 | *
1204 | * @param location {string} Web socket url.
1205 | * @returns {websocket} Web socket object.
1206 | * @private
1207 | */
1208 | function _getWebSocket(location) {
1209 | if (_request.webSocketImpl != null) {
1210 | return _request.webSocketImpl;
1211 | } else {
1212 | if (window.WebSocket) {
1213 | return new WebSocket(location);
1214 | } else {
1215 | return new MozWebSocket(location);
1216 | }
1217 | }
1218 | }
1219 |
1220 | /**
1221 | * Build web socket url from request url.
1222 | *
1223 | * @return {string} Web socket url (start with "ws" or "wss" for secure web socket).
1224 | * @private
1225 | */
1226 | function _buildWebSocketUrl() {
1227 | return _attachHeaders(_request, atmosphere.util.getAbsoluteURL(_request.webSocketUrl || _request.url)).replace(/^http/, "ws");
1228 | }
1229 |
1230 | /**
1231 | * Build SSE url from request url.
1232 | *
1233 | * @return a url with Atmosphere's headers
1234 | * @private
1235 | */
1236 | function _buildSSEUrl() {
1237 | var url = _attachHeaders(_request);
1238 | return url;
1239 | }
1240 |
1241 | /**
1242 | * Open SSE.
1243 | * Automatically use fallback transport if SSE can't be opened.
1244 | *
1245 | * @private
1246 | */
1247 | function _executeSSE(sseOpened) {
1248 |
1249 | _response.transport = "sse";
1250 |
1251 | var location = _buildSSEUrl();
1252 |
1253 | if (_canLog('debug')) {
1254 | atmosphere.util.debug("Invoking executeSSE");
1255 | atmosphere.util.debug("Using URL: " + location);
1256 | }
1257 |
1258 | if (sseOpened && !_request.reconnect) {
1259 | if (_sse != null) {
1260 | _clearState();
1261 | }
1262 | return;
1263 | }
1264 |
1265 | try {
1266 | _sse = new EventSource(location, {
1267 | withCredentials: _request.withCredentials
1268 | });
1269 | } catch (e) {
1270 | _onError(0, e);
1271 | _reconnectWithFallbackTransport("SSE failed. Downgrading to fallback transport and resending");
1272 | return;
1273 | }
1274 |
1275 | if (_request.connectTimeout > 0) {
1276 | _request.id = setTimeout(function () {
1277 | if (!sseOpened) {
1278 | _clearState();
1279 | }
1280 | }, _request.connectTimeout);
1281 | }
1282 |
1283 | _sse.onopen = function () {
1284 | _debug("sse.onopen");
1285 | _timeout(_request);
1286 | if (_canLog('debug')) {
1287 | atmosphere.util.debug("SSE successfully opened");
1288 | }
1289 |
1290 | if (!_request.enableProtocol) {
1291 | if (!sseOpened) {
1292 | _open('opening', "sse", _request);
1293 | } else {
1294 | _open('re-opening', "sse", _request);
1295 | }
1296 | } else if (_request.isReopen) {
1297 | _request.isReopen = false;
1298 | _open('re-opening', _request.transport, _request);
1299 | }
1300 |
1301 | sseOpened = true;
1302 |
1303 | if (_request.method === 'POST') {
1304 | _response.state = "messageReceived";
1305 | _push(_request.data, _request);
1306 | }
1307 | };
1308 |
1309 | _sse.onmessage = function (message) {
1310 | _debug("sse.onmessage");
1311 | _timeout(_request);
1312 |
1313 | if (!_request.enableXDR && window.location.host && message.origin && message.origin !== window.location.protocol + "//" + window.location.host) {
1314 | atmosphere.util.log(_request.logLevel, ["Origin was not " + window.location.protocol + "//" + window.location.host]);
1315 | return;
1316 | }
1317 |
1318 | _response.state = 'messageReceived';
1319 | _response.status = 200;
1320 |
1321 | message = message.data;
1322 | var skipCallbackInvocation = _trackMessageSize(message, _request, _response);
1323 |
1324 | // https://github.com/remy/polyfills/blob/master/EventSource.js
1325 | // Since we polling.
1326 | /* if (_sse.URL) {
1327 | _sse.interval = 100;
1328 | _sse.URL = _buildSSEUrl();
1329 | } */
1330 |
1331 | if (!skipCallbackInvocation) {
1332 | _invokeCallback();
1333 | _response.responseBody = '';
1334 | _response.messages = [];
1335 | }
1336 | };
1337 |
1338 | _sse.onerror = function () {
1339 | _debug("sse.onerror");
1340 | clearTimeout(_request.id);
1341 |
1342 | if (_request.heartbeatTimer) {
1343 | clearTimeout(_request.heartbeatTimer);
1344 | }
1345 |
1346 | if (_response.closedByClientTimeout) {
1347 | return;
1348 | }
1349 |
1350 | _invokeClose(sseOpened);
1351 | _clearState();
1352 |
1353 | if (_abortingConnection) {
1354 | atmosphere.util.log(_request.logLevel, ["SSE closed normally"]);
1355 | } else if (!sseOpened) {
1356 | _reconnectWithFallbackTransport("SSE failed. Downgrading to fallback transport and resending");
1357 | } else if (_request.reconnect && _response.transport === 'sse') {
1358 | if (_requestCount++ < _request.maxReconnectOnClose) {
1359 | _open('re-connecting', _request.transport, _request);
1360 | if (_request.reconnectInterval > 0) {
1361 | // Prevent the online event to open a second connection while waiting for reconnect
1362 | var handleOnlineOffline = _request.handleOnlineOffline;
1363 | _request.handleOnlineOffline = false;
1364 | _request.reconnectId = setTimeout(function () {
1365 | _request.handleOnlineOffline = handleOnlineOffline;
1366 | _executeSSE(true);
1367 | }, _request.reconnectInterval);
1368 | } else {
1369 | _executeSSE(true);
1370 | }
1371 | _response.responseBody = "";
1372 | _response.messages = [];
1373 | } else {
1374 | atmosphere.util.log(_request.logLevel, ["SSE reconnect maximum try reached " + _requestCount]);
1375 | if (_request.reconnect && _request.fallbackTransport !== 'none') {
1376 | _reconnectWithFallbackTransport("SSE maxReconnectOnClose reached. Downgrading to " + _request.fallbackTransport);
1377 | } else {
1378 | _onError(0, "maxReconnectOnClose reached");
1379 | }
1380 | }
1381 | }
1382 | };
1383 | }
1384 |
1385 | /**
1386 | * Open web socket.
1387 | * Automatically use fallback transport if web socket can't be opened.
1388 | *
1389 | * @private
1390 | */
1391 | function _executeWebSocket(webSocketOpened) {
1392 |
1393 | _response.transport = "websocket";
1394 |
1395 | var location = _buildWebSocketUrl();
1396 | if (_canLog('debug')) {
1397 | atmosphere.util.debug("Invoking executeWebSocket, using URL: " + location);
1398 | }
1399 |
1400 | if (webSocketOpened && !_request.reconnect) {
1401 | if (_websocket != null) {
1402 | _clearState();
1403 | }
1404 | return;
1405 | }
1406 |
1407 | _websocket = _getWebSocket(location);
1408 | if (_request.webSocketBinaryType != null) {
1409 | _websocket.binaryType = _request.webSocketBinaryType;
1410 | }
1411 |
1412 | if (_request.connectTimeout > 0) {
1413 | _request.id = setTimeout(function () {
1414 | if (!webSocketOpened) {
1415 | var _message = {
1416 | code: 1002,
1417 | reason: "Connection timeout after " + _request.connectTimeout + "ms.",
1418 | wasClean: false
1419 | };
1420 | var socket = _websocket;
1421 | // Close it anyway
1422 | try {
1423 | _clearState();
1424 | } catch (e) {
1425 | }
1426 | socket.onclose(_message);
1427 | }
1428 |
1429 | }, _request.connectTimeout);
1430 | }
1431 |
1432 | _websocket.onopen = function () {
1433 | if (_websocket == null) {
1434 | this.close();
1435 | if (_request.transport == "websocket")
1436 | _close();
1437 | return;
1438 | }
1439 |
1440 | _debug("websocket.onopen");
1441 | if (!_request.enableProtocol || _request.connectTimeout <= 0)
1442 | _timeout(_request);
1443 | offline = false;
1444 |
1445 | if (_canLog('debug')) {
1446 | atmosphere.util.debug("Websocket successfully opened");
1447 | }
1448 |
1449 | var reopening = webSocketOpened;
1450 |
1451 | _websocket.canSendMessage = true;
1452 |
1453 | if (!_request.enableProtocol) {
1454 | webSocketOpened = true;
1455 | if (reopening) {
1456 | _open('re-opening', "websocket", _request);
1457 | } else {
1458 | _open('opening', "websocket", _request);
1459 | }
1460 | }
1461 |
1462 | if (_request.method === 'POST') {
1463 | _response.state = "messageReceived";
1464 | _websocket.send(_request.data);
1465 | }
1466 | };
1467 |
1468 | _websocket.onmessage = function (message) {
1469 | if (_websocket == null) {
1470 | this.close();
1471 | if (_request.transport == "websocket")
1472 | _close();
1473 | return;
1474 | }
1475 |
1476 | _debug("websocket.onmessage");
1477 | _timeout(_request);
1478 |
1479 | // We only consider it opened if we get the handshake data
1480 | // https://github.com/Atmosphere/atmosphere-javascript/issues/74
1481 | if (_request.enableProtocol) {
1482 | webSocketOpened = true;
1483 | }
1484 |
1485 | _response.state = 'messageReceived';
1486 | _response.status = 200;
1487 |
1488 | message = message.data;
1489 | var isString = typeof (message) === 'string';
1490 | if (isString) {
1491 | var skipCallbackInvocation = _trackMessageSize(message, _request, _response);
1492 | if (!skipCallbackInvocation) {
1493 | _invokeCallback();
1494 | _response.responseBody = '';
1495 | _response.messages = [];
1496 | }
1497 | } else {
1498 | message = _handleProtocol(_request, message);
1499 | if (message === "")
1500 | return;
1501 |
1502 | _response.responseBody = message;
1503 | _invokeCallback();
1504 | _response.responseBody = null;
1505 | }
1506 | };
1507 |
1508 | _websocket.onerror = function () {
1509 | _debug("websocket.onerror");
1510 | if (_response.transport !== 'websocket')
1511 | return;
1512 | clearTimeout(_request.id);
1513 |
1514 | if (_request.heartbeatTimer) {
1515 | clearTimeout(_request.heartbeatTimer);
1516 | }
1517 |
1518 | _response.error = true;
1519 | };
1520 |
1521 | _websocket.onclose = function (message) {
1522 | _debug("websocket.onclose");
1523 | if (_response.transport !== 'websocket')
1524 | return;
1525 | clearTimeout(_request.id);
1526 | if (_response.state === 'closed')
1527 | return;
1528 |
1529 | var reason = message.reason;
1530 | if (reason === "") {
1531 | switch (message.code) {
1532 | case 1000:
1533 | reason = "Normal closure; the connection successfully completed whatever purpose for which it was created.";
1534 | break;
1535 | case 1001:
1536 | reason = "The endpoint is going away, either because of a server failure or because the "
1537 | + "browser is navigating away from the page that opened the connection.";
1538 | break;
1539 | case 1002:
1540 | reason = "The endpoint is terminating the connection due to a protocol error.";
1541 | break;
1542 | case 1003:
1543 | reason = "The connection is being terminated because the endpoint received data of a type it "
1544 | + "cannot accept (for example, a text-only endpoint received binary data).";
1545 | break;
1546 | case 1004:
1547 | reason = "The endpoint is terminating the connection because a data frame was received that is too large.";
1548 | break;
1549 | case 1005:
1550 | reason = "Unknown: no status code was provided even though one was expected.";
1551 | break;
1552 | case 1006:
1553 | reason = "Connection was closed abnormally (that is, with no close frame being sent).";
1554 | break;
1555 | }
1556 | }
1557 |
1558 | if (_canLog('warn')) {
1559 | atmosphere.util.warn("Websocket closed, reason: " + reason + ' - wasClean: ' + message.wasClean);
1560 | }
1561 |
1562 | if (_response.closedByClientTimeout || (_request.handleOnlineOffline && offline)) {
1563 | // IFF online/offline events are handled and we happen to be offline, we stop all reconnect attempts and
1564 | // resume them in the "online" event (if we get here in that case, something else went wrong as the
1565 | // offline handler should stop any reconnect attempt).
1566 | //
1567 | // On the other hand, if we DO NOT handle online/offline events, we continue as before with reconnecting
1568 | // even if we are offline. Failing to do so would stop all reconnect attemps forever.
1569 | if (_request.reconnectId) {
1570 | clearTimeout(_request.reconnectId);
1571 | delete _request.reconnectId;
1572 | }
1573 | return;
1574 | }
1575 |
1576 | _invokeClose(webSocketOpened);
1577 |
1578 | _response.state = 'closed';
1579 |
1580 | if (_abortingConnection) {
1581 | atmosphere.util.log(_request.logLevel, ["Websocket closed normally"]);
1582 | } else if (_response.error && _request.curWebsocketErrorRetries < _request.maxWebsocketErrorRetries && _requestCount + 1 < _request.maxReconnectOnClose) {
1583 | _response.error = false;
1584 | _request.curWebsocketErrorRetries++;
1585 | _reconnectWebSocket();
1586 | } else if ((_response.error || !webSocketOpened || _request.maxWebsocketErrorRetries === 0) && _request.fallbackTransport !== 'websocket') {
1587 | _response.error = false;
1588 | _reconnectWithFallbackTransport("Websocket failed on first connection attempt. Downgrading to " + _request.fallbackTransport + " and resending");
1589 | } else if (_request.reconnect) {
1590 | _reconnectWebSocket();
1591 | }
1592 | };
1593 |
1594 | var ua = navigator.userAgent.toLowerCase();
1595 | var isAndroid = ua.indexOf("android") > -1;
1596 | if (isAndroid && _websocket.url === undefined) {
1597 | // Android 4.1 does not really support websockets and fails silently
1598 | _websocket.onclose({
1599 | reason: "Android 4.1 does not support websockets.",
1600 | wasClean: false
1601 | });
1602 | }
1603 | }
1604 |
1605 | function _handleProtocol(request, message) {
1606 |
1607 | var nMessage = message;
1608 | if (request.transport === 'polling') return nMessage;
1609 |
1610 | if (request.enableProtocol && request.firstMessage && atmosphere.util.trim(message).length !== 0) {
1611 | var pos = request.trackMessageLength ? 1 : 0;
1612 | var messages = message.split(request.messageDelimiter);
1613 |
1614 | if (messages.length <= pos + 1) {
1615 | // Something went wrong, normally with IE or when a message is written before the
1616 | // handshake has been received.
1617 | return nMessage;
1618 | }
1619 |
1620 | request.firstMessage = false;
1621 | request.uuid = atmosphere.util.trim(messages[pos]);
1622 |
1623 | if (messages.length <= pos + 2) {
1624 | atmosphere.util.log('error', ["Protocol data not sent by the server. " +
1625 | "If you enable protocol on client side, be sure to install JavascriptProtocol interceptor on server side." +
1626 | "Also note that atmosphere-runtime 2.2+ should be used."]);
1627 | }
1628 |
1629 | _heartbeatInterval = parseInt(atmosphere.util.trim(messages[pos + 1]), 10);
1630 | _heartbeatPadding = messages[pos + 2];
1631 |
1632 | if (request.transport !== 'long-polling') {
1633 | _triggerOpen(request);
1634 | }
1635 | uuid = request.uuid;
1636 | nMessage = "";
1637 |
1638 | // We have trailing messages
1639 | pos = request.trackMessageLength ? 4 : 3;
1640 | if (messages.length > pos + 1) {
1641 | for (var i = pos; i < messages.length; i++) {
1642 | nMessage += messages[i];
1643 | if (i + 1 !== messages.length) {
1644 | nMessage += request.messageDelimiter;
1645 | }
1646 | }
1647 | }
1648 |
1649 | if (request.ackInterval !== 0) {
1650 | setTimeout(function () {
1651 | _push("...ACK...", request);
1652 | }, request.ackInterval);
1653 | }
1654 | } else if (request.enableProtocol && request.firstMessage && atmosphere.util.browser.msie && +atmosphere.util.browser.version.split(".")[0] < 10) {
1655 | // In case we are getting some junk from IE
1656 | atmosphere.util.log(_request.logLevel, ["Receiving unexpected data from IE"]);
1657 | } else {
1658 | _triggerOpen(request);
1659 | }
1660 | return nMessage;
1661 | }
1662 |
1663 | function _timeout(_request) {
1664 | clearTimeout(_request.id);
1665 | if (_request.timeout > 0 && _request.transport !== 'polling') {
1666 | _request.id = setTimeout(function () {
1667 | _onClientTimeout(_request);
1668 | _disconnect();
1669 | _clearState();
1670 | }, _request.timeout);
1671 | }
1672 | }
1673 |
1674 | function _onClientTimeout(_request) {
1675 | _response.closedByClientTimeout = true;
1676 | _response.state = 'closedByClient';
1677 | _response.responseBody = "";
1678 | _response.status = 408;
1679 | _response.messages = [];
1680 | _invokeCallback();
1681 | }
1682 |
1683 | function _onError(code, reason) {
1684 | _clearState();
1685 | clearTimeout(_request.id);
1686 | _response.state = 'error';
1687 | _response.reasonPhrase = reason;
1688 | _response.responseBody = "";
1689 | _response.status = code;
1690 | _response.messages = [];
1691 | _invokeCallback();
1692 | }
1693 |
1694 | /**
1695 | * Track received message and make sure callbacks/functions are only invoked when the complete message has been received.
1696 | *
1697 | * @param message
1698 | * @param request
1699 | * @param response
1700 | */
1701 | function _trackMessageSize(message, request, response) {
1702 | message = _handleProtocol(request, message);
1703 | if (message.length === 0)
1704 | return true;
1705 |
1706 | response.responseBody = message;
1707 |
1708 | if (request.trackMessageLength) {
1709 | // prepend partialMessage if any
1710 | message = response.partialMessage + message;
1711 |
1712 | var messages = [];
1713 | var messageStart = message.indexOf(request.messageDelimiter);
1714 | if (messageStart != -1) {
1715 | while (messageStart !== -1) {
1716 | var str = message.substring(0, messageStart);
1717 | var messageLength = +str;
1718 | if (isNaN(messageLength)) {
1719 | // Discard partial message, otherwise it would never recover from this condition
1720 | response.partialMessage = '';
1721 | throw new Error('message length "' + str + '" is not a number');
1722 | }
1723 | messageStart += request.messageDelimiter.length;
1724 | if (messageStart + messageLength > message.length) {
1725 | // message not complete, so there is no trailing messageDelimiter
1726 | messageStart = -1;
1727 | } else {
1728 | // message complete, so add it
1729 | messages.push(message.substring(messageStart, messageStart + messageLength));
1730 | // remove consumed characters
1731 | message = message.substring(messageStart + messageLength, message.length);
1732 | messageStart = message.indexOf(request.messageDelimiter);
1733 | }
1734 | }
1735 |
1736 | /* keep any remaining data */
1737 | response.partialMessage = message;
1738 |
1739 | if (messages.length !== 0) {
1740 | response.responseBody = messages.join(request.messageDelimiter);
1741 | response.messages = messages;
1742 | return false;
1743 | } else {
1744 | response.responseBody = "";
1745 | response.messages = [];
1746 | return true;
1747 | }
1748 | }
1749 | }
1750 | response.responseBody = message;
1751 | response.messages = [message];
1752 | return false;
1753 | }
1754 |
1755 | function _reconnectWebSocket() {
1756 | _clearState();
1757 | if (_requestCount++ < _request.maxReconnectOnClose) {
1758 | _open('re-connecting', _request.transport, _request);
1759 | if (_request.reconnectInterval > 0) {
1760 | // Prevent the online event to open a second connection while waiting for reconnect
1761 | var handleOnlineOffline = _request.handleOnlineOffline;
1762 | _request.handleOnlineOffline = false;
1763 | _request.reconnectId = setTimeout(function () {
1764 | _request.handleOnlineOffline = handleOnlineOffline;
1765 | _response.responseBody = "";
1766 | _response.messages = [];
1767 | _executeWebSocket(true);
1768 | }, _request.reconnectInterval);
1769 | } else {
1770 | _response.responseBody = "";
1771 | _response.messages = [];
1772 | _executeWebSocket(true);
1773 | }
1774 | } else {
1775 | atmosphere.util.log(_request.logLevel, ["Websocket reconnect maximum try reached " + _requestCount]);
1776 | if (_request.reconnect && _request.fallbackTransport !== 'none') {
1777 | _reconnectWithFallbackTransport("Websocket maxReconnectOnClose reached. Downgrading to " + _request.fallbackTransport);
1778 | } else {
1779 | _onError(0, "maxReconnectOnClose reached");
1780 | }
1781 | }
1782 | }
1783 |
1784 | /**
1785 | * Reconnect request with fallback transport.
1786 | * Used in case websocket can't be opened.
1787 | *
1788 | * @private
1789 | */
1790 | function _reconnectWithFallbackTransport(errorMessage) {
1791 | atmosphere.util.log(_request.logLevel, [errorMessage]);
1792 |
1793 | _clearState();
1794 |
1795 | if (typeof (_request.onTransportFailure) !== 'undefined') {
1796 | _request.onTransportFailure(errorMessage, _request);
1797 | }
1798 |
1799 | if (_request.reconnect && _request.transport !== 'none' || _request.transport == null) {
1800 | _request.transport = _request.fallbackTransport;
1801 | _request.method = _request.fallbackMethod;
1802 | _response.transport = _request.fallbackTransport;
1803 | _response.state = '';
1804 | _request.fallbackTransport = 'none';
1805 | if (_request.reconnectInterval > 0) {
1806 | // Prevent the online event to open a second connection while waiting for reconnect
1807 | var handleOnlineOffline = _request.handleOnlineOffline;
1808 | _request.handleOnlineOffline = false;
1809 | _request.reconnectId = setTimeout(function () {
1810 | _request.handleOnlineOffline = handleOnlineOffline;
1811 | _execute();
1812 | }, _request.reconnectInterval);
1813 | } else {
1814 | _execute();
1815 | }
1816 | } else {
1817 | _onError(500, "Unable to reconnect with fallback transport");
1818 | }
1819 | }
1820 |
1821 | /**
1822 | * Get url from request and attach headers to it.
1823 | *
1824 | * @param request {Object} request Request parameters, if undefined _request object will be used.
1825 | *
1826 | * @returns {Object} Request object, if undefined, _request object will be used.
1827 | * @private
1828 | */
1829 | function _attachHeaders(request, url) {
1830 | var rq = _request;
1831 | if ((request != null) && (typeof (request) !== 'undefined')) {
1832 | rq = request;
1833 | }
1834 |
1835 | if (url == null) {
1836 | url = rq.url;
1837 | }
1838 |
1839 | // If not enabled
1840 | if (!rq.attachHeadersAsQueryString)
1841 | return url;
1842 |
1843 | // If already added
1844 | if (url.indexOf("X-Atmosphere-Framework") !== -1) {
1845 | return url;
1846 | }
1847 |
1848 | url += (url.indexOf('?') !== -1) ? '&' : '?';
1849 | url += "X-Atmosphere-tracking-id=" + rq.uuid;
1850 | url += "&X-Atmosphere-Framework=" + atmosphere.version;
1851 | url += "&X-Atmosphere-Transport=" + rq.transport;
1852 |
1853 | if (rq.trackMessageLength) {
1854 | url += "&X-Atmosphere-TrackMessageSize=" + "true";
1855 | }
1856 |
1857 | if (rq.heartbeat !== null && rq.heartbeat.server !== null) {
1858 | url += "&X-Heartbeat-Server=" + rq.heartbeat.server;
1859 | }
1860 |
1861 | if (rq.contentType !== '') {
1862 | //Eurk!
1863 | url += "&Content-Type=" + (rq.transport === 'websocket' ? rq.contentType : encodeURIComponent(rq.contentType));
1864 | }
1865 |
1866 | if (rq.enableProtocol) {
1867 | url += "&X-atmo-protocol=true";
1868 | }
1869 |
1870 | atmosphere.util.each(rq.headers, function (name, value) {
1871 | var h = atmosphere.util.isFunction(value) ? value.call(this, rq, request, _response) : value;
1872 | if (h != null) {
1873 | url += "&" + encodeURIComponent(name) + "=" + encodeURIComponent(h);
1874 | }
1875 | });
1876 |
1877 | return url;
1878 | }
1879 |
1880 | function _triggerOpen(rq) {
1881 | if (!rq.isOpen) {
1882 | rq.isOpen = true;
1883 | _open('opening', rq.transport, rq);
1884 | } else if (rq.isReopen) {
1885 | rq.isReopen = false;
1886 | _open('re-opening', rq.transport, rq);
1887 | } else if (_response.state === 'messageReceived' && (rq.transport === 'jsonp' || rq.transport === 'long-polling')) {
1888 | _openAfterResume(_response);
1889 | } else {
1890 | return;
1891 | }
1892 |
1893 | _startHeartbeat(rq);
1894 | }
1895 |
1896 | function _startHeartbeat(rq) {
1897 | if (rq.heartbeatTimer != null) {
1898 | clearTimeout(rq.heartbeatTimer);
1899 | }
1900 |
1901 | if (!isNaN(_heartbeatInterval) && _heartbeatInterval > 0) {
1902 | var _pushHeartbeat = function () {
1903 | if (_canLog('debug')) {
1904 | atmosphere.util.debug("Sending heartbeat");
1905 | }
1906 | _push(_heartbeatPadding, _request);
1907 | rq.heartbeatTimer = setTimeout(_pushHeartbeat, _heartbeatInterval);
1908 | };
1909 | rq.heartbeatTimer = setTimeout(_pushHeartbeat, _heartbeatInterval);
1910 | }
1911 | }
1912 |
1913 | /**
1914 | * Execute ajax request.
1915 | *
1916 | * @param request {Object} request Request parameters, if undefined _request object will be used.
1917 | * @private
1918 | */
1919 | function _executeRequest(request) {
1920 | var rq = _request;
1921 | if ((request != null) || (typeof (request) !== 'undefined')) {
1922 | rq = request;
1923 | }
1924 |
1925 | rq.lastIndex = 0;
1926 | rq.readyState = 0;
1927 |
1928 | // CORS fake using JSONP
1929 | if ((rq.transport === 'jsonp') || ((rq.enableXDR) && (atmosphere.util.checkCORSSupport()))) {
1930 | _jsonp(rq);
1931 | return;
1932 | }
1933 |
1934 | if (atmosphere.util.browser.msie && +atmosphere.util.browser.version.split(".")[0] < 10) {
1935 | if ((rq.transport === 'streaming')) {
1936 | if (rq.enableXDR && window.XDomainRequest) {
1937 | _ieXDR(rq);
1938 | } else {
1939 | _ieStreaming(rq);
1940 | }
1941 | return;
1942 | }
1943 |
1944 | if ((rq.enableXDR) && (window.XDomainRequest)) {
1945 | _ieXDR(rq);
1946 | return;
1947 | }
1948 | }
1949 |
1950 | var reconnectFExec = function (force) {
1951 | rq.lastIndex = 0;
1952 | _requestCount++; // Increase also when forcing reconnect as _open checks _requestCount
1953 | if (force || (rq.reconnect && _requestCount <= rq.maxReconnectOnClose)) {
1954 | var delay = force ? 0 : request.reconnectInterval; // Reconnect immediately if the server resumed the connection (timeout)
1955 | _response.ffTryingReconnect = true;
1956 | _open('re-connecting', request.transport, request);
1957 | _reconnect(ajaxRequest, rq, delay);
1958 | } else if (_request.reconnect && _request.fallbackTransport !== 'none') {
1959 | _reconnectWithFallbackTransport("maxReconnectOnClose reached. Downgrading to " + _request.fallbackTransport);
1960 | } else {
1961 | _onError(0, "maxReconnectOnClose reached");
1962 | }
1963 | };
1964 |
1965 | var reconnectF = function (force) {
1966 | if (_beforeUnloadState) {
1967 | // ATMOSPHERE-JAVASCRIPT-143: Delay reconnect to avoid reconnect attempts before an actual unload (we don't know if an unload will happen, yet)
1968 | atmosphere.util.debug(new Date() + " Atmosphere: reconnectF: execution delayed due to _beforeUnloadState flag");
1969 | setTimeout(function () {
1970 | reconnectFExec(force);
1971 | }, 5000);
1972 | } else {
1973 | reconnectFExec(force);
1974 | }
1975 | };
1976 |
1977 | var disconnected = function () {
1978 | // Prevent onerror callback to be called
1979 | _response.errorHandled = true;
1980 | _clearState();
1981 | reconnectF(false);
1982 | };
1983 |
1984 | if (rq.force || (rq.reconnect && (rq.maxRequest === -1 || rq.requestCount++ < rq.maxRequest))) {
1985 | rq.force = false;
1986 |
1987 | var ajaxRequest = atmosphere.util.xhr();
1988 | ajaxRequest.hasData = false;
1989 |
1990 | _doRequest(ajaxRequest, rq, true);
1991 |
1992 | if (rq.suspend) {
1993 | _activeRequest = ajaxRequest;
1994 | }
1995 |
1996 | if (rq.transport !== 'polling') {
1997 | _response.transport = rq.transport;
1998 |
1999 | ajaxRequest.onabort = function () {
2000 | _debug("ajaxrequest.onabort")
2001 | _invokeClose(true);
2002 | };
2003 |
2004 | ajaxRequest.onerror = function () {
2005 | _debug("ajaxrequest.onerror")
2006 | _response.error = true;
2007 | _response.ffTryingReconnect = true;
2008 | try {
2009 | _response.status = ajaxRequest.status;
2010 | } catch (e) {
2011 | _response.status = 500;
2012 | }
2013 |
2014 | if (!_response.status) {
2015 | _response.status = 500;
2016 | }
2017 | if (!_response.errorHandled) {
2018 | _clearState();
2019 | reconnectF(false);
2020 | }
2021 | };
2022 | }
2023 |
2024 | ajaxRequest.onreadystatechange = function () {
2025 | _debug("ajaxRequest.onreadystatechange, new state: " + ajaxRequest.readyState);
2026 | if (_abortingConnection) {
2027 | _debug("onreadystatechange has been ignored due to _abortingConnection flag");
2028 | return;
2029 | }
2030 |
2031 | _response.error = false;
2032 | var skipCallbackInvocation = false;
2033 | var update = false;
2034 |
2035 | if (rq.transport === 'streaming' && rq.readyState > 2 && ajaxRequest.readyState === 4) {
2036 | _clearState();
2037 | reconnectF(false);
2038 | return;
2039 | }
2040 |
2041 | rq.readyState = ajaxRequest.readyState;
2042 |
2043 | if (rq.transport === 'streaming' && ajaxRequest.readyState >= 3) {
2044 | update = true;
2045 | } else if (rq.transport === 'long-polling' && ajaxRequest.readyState === 4) {
2046 | update = true;
2047 | }
2048 | _timeout(_request);
2049 |
2050 | if (rq.transport !== 'polling') {
2051 | // MSIE 9 and lower status can be higher than 1000, Chrome can be 0
2052 | var status = 200;
2053 | if (ajaxRequest.readyState === 4) {
2054 | status = ajaxRequest.status > 1000 ? 0 : ajaxRequest.status;
2055 | }
2056 |
2057 | if (!rq.reconnectOnServerError && (status >= 300 && status < 600)) {
2058 | _onError(status, ajaxRequest.statusText);
2059 | return;
2060 | }
2061 |
2062 | if (status >= 300 || status === 0) {
2063 | if (!rq.isOpen && _canLog('warn')) {
2064 | atmosphere.util.warn(rq.transport + " connection failed with status: " + status + " " + (ajaxRequest.statusText || "Unable to connect"));
2065 | }
2066 | disconnected();
2067 | return;
2068 | }
2069 |
2070 | // Firefox incorrectly send statechange 0->2 when a reconnect attempt fails. The above checks ensure that onopen is not called for these
2071 | if ((!rq.enableProtocol || !request.firstMessage) && (ajaxRequest.readyState === 2 || ajaxRequest.readyState > 2 && !rq.isOpen)) {
2072 | // In that case, ajaxRequest.onerror will be called just after onreadystatechange is called, so we delay the trigger until we are
2073 | // guarantee the connection is well established.
2074 | if (atmosphere.util.browser.mozilla && _response.ffTryingReconnect) {
2075 | _response.ffTryingReconnect = false;
2076 | setTimeout(function () {
2077 | if (!_response.ffTryingReconnect) {
2078 | _triggerOpen(rq);
2079 | }
2080 | }, 500);
2081 | } else {
2082 | _triggerOpen(rq);
2083 | }
2084 | }
2085 |
2086 | } else if (ajaxRequest.readyState === 4) {
2087 | update = true;
2088 | }
2089 |
2090 | if (update) {
2091 | var responseText = ajaxRequest.responseText;
2092 | _response.errorHandled = false;
2093 |
2094 | // IE behave the same way when resuming long-polling or when the server goes down.
2095 | if (rq.transport === 'long-polling' && atmosphere.util.trim(responseText).length === 0) {
2096 | // For browser that aren't support onabort
2097 | if (!ajaxRequest.hasData) {
2098 | reconnectF(true);
2099 | } else {
2100 | ajaxRequest.hasData = false;
2101 | }
2102 | return;
2103 | }
2104 | ajaxRequest.hasData = true;
2105 |
2106 | _readHeaders(ajaxRequest, _request);
2107 |
2108 | if (rq.transport === 'streaming') {
2109 | if (!atmosphere.util.browser.opera) {
2110 | var message = responseText.substring(rq.lastIndex, responseText.length);
2111 | skipCallbackInvocation = _trackMessageSize(message, rq, _response);
2112 |
2113 | rq.lastIndex = responseText.length;
2114 | if (skipCallbackInvocation) {
2115 | return;
2116 | }
2117 | } else {
2118 | atmosphere.util.iterate(function () {
2119 | if (_response.status !== 500 && ajaxRequest.responseText.length > rq.lastIndex) {
2120 | try {
2121 | _response.status = ajaxRequest.status;
2122 | _response.headers = atmosphere.util.parseHeaders(ajaxRequest.getAllResponseHeaders());
2123 |
2124 | _readHeaders(ajaxRequest, _request);
2125 |
2126 | } catch (e) {
2127 | _response.status = 404;
2128 | }
2129 | _timeout(_request);
2130 |
2131 | _response.state = "messageReceived";
2132 | var message = ajaxRequest.responseText.substring(rq.lastIndex);
2133 | rq.lastIndex = ajaxRequest.responseText.length;
2134 |
2135 | skipCallbackInvocation = _trackMessageSize(message, rq, _response);
2136 | if (!skipCallbackInvocation) {
2137 | _invokeCallback();
2138 | }
2139 |
2140 | if (_verifyStreamingLength(ajaxRequest, rq)) {
2141 | _reconnectOnMaxStreamingLength(ajaxRequest, rq);
2142 | return;
2143 | }
2144 | } else if (_response.status > 400) {
2145 | // Prevent replaying the last message.
2146 | rq.lastIndex = ajaxRequest.responseText.length;
2147 | return false;
2148 | }
2149 | }, 0);
2150 | }
2151 | } else {
2152 | skipCallbackInvocation = _trackMessageSize(responseText, rq, _response);
2153 | }
2154 | var closeStream = _verifyStreamingLength(ajaxRequest, rq);
2155 |
2156 | try {
2157 | _response.status = ajaxRequest.status;
2158 | _response.headers = atmosphere.util.parseHeaders(ajaxRequest.getAllResponseHeaders());
2159 |
2160 | _readHeaders(ajaxRequest, rq);
2161 | } catch (e) {
2162 | _response.status = 404;
2163 | }
2164 |
2165 | if (rq.suspend) {
2166 | _response.state = _response.status === 0 ? "closed" : "messageReceived";
2167 | } else {
2168 | _response.state = "messagePublished";
2169 | }
2170 |
2171 | var isAllowedToReconnect = !closeStream && request.transport !== 'streaming' && request.transport !== 'polling';
2172 | if (isAllowedToReconnect && !rq.executeCallbackBeforeReconnect) {
2173 | _reconnect(ajaxRequest, rq, rq.pollingInterval);
2174 | }
2175 |
2176 | if (_response.responseBody.length !== 0 && !skipCallbackInvocation)
2177 | _invokeCallback();
2178 |
2179 | if (isAllowedToReconnect && rq.executeCallbackBeforeReconnect) {
2180 | _reconnect(ajaxRequest, rq, rq.pollingInterval);
2181 | }
2182 |
2183 | if (closeStream) {
2184 | _reconnectOnMaxStreamingLength(ajaxRequest, rq);
2185 | }
2186 | }
2187 | };
2188 |
2189 | try {
2190 | ajaxRequest.send(rq.data);
2191 | _subscribed = true;
2192 | } catch (e) {
2193 | atmosphere.util.log(rq.logLevel, ["Unable to connect to " + rq.url]);
2194 | _onError(0, e);
2195 | }
2196 |
2197 | } else {
2198 | if (rq.logLevel === 'debug') {
2199 | atmosphere.util.log(rq.logLevel, ["Max re-connection reached."]);
2200 | }
2201 | _onError(0, "maxRequest reached");
2202 | }
2203 | }
2204 |
2205 | function _reconnectOnMaxStreamingLength(ajaxRequest, rq) {
2206 | _response.messages = [];
2207 | rq.isReopen = true;
2208 | _close();
2209 | _abortingConnection = false;
2210 | _reconnect(ajaxRequest, rq, 500);
2211 | }
2212 |
2213 | /**
2214 | * Do ajax request.
2215 | *
2216 | * @param ajaxRequest Ajax request.
2217 | * @param request Request parameters.
2218 | * @param create If ajax request has to be open.
2219 | */
2220 | function _doRequest(ajaxRequest, request, create) {
2221 | // Prevent Android to cache request
2222 | var url = request.url;
2223 | if (request.dispatchUrl != null && request.method === 'POST') {
2224 | url += request.dispatchUrl;
2225 | }
2226 | url = _attachHeaders(request, url);
2227 | url = atmosphere.util.prepareURL(url);
2228 |
2229 | if (create) {
2230 | ajaxRequest.open(request.method, url, true);
2231 | if (request.connectTimeout > 0) {
2232 | request.id = setTimeout(function () {
2233 | if (request.requestCount === 0) {
2234 | _clearState();
2235 | _prepareCallback("Connect timeout", "closed", 200, request.transport);
2236 | }
2237 | }, request.connectTimeout);
2238 | }
2239 | }
2240 |
2241 | if (_request.withCredentials && _request.transport !== 'websocket') {
2242 | if ("withCredentials" in ajaxRequest) {
2243 | ajaxRequest.withCredentials = true;
2244 | }
2245 | }
2246 |
2247 | if (!_request.dropHeaders) {
2248 | ajaxRequest.setRequestHeader("X-Atmosphere-Framework", atmosphere.version);
2249 | ajaxRequest.setRequestHeader("X-Atmosphere-Transport", request.transport);
2250 |
2251 | if (request.heartbeat !== null && request.heartbeat.server !== null) {
2252 | ajaxRequest.setRequestHeader("X-Heartbeat-Server", ajaxRequest.heartbeat.server);
2253 | }
2254 |
2255 | if (request.trackMessageLength) {
2256 | ajaxRequest.setRequestHeader("X-Atmosphere-TrackMessageSize", "true");
2257 | }
2258 | ajaxRequest.setRequestHeader("X-Atmosphere-tracking-id", request.uuid);
2259 |
2260 | atmosphere.util.each(request.headers, function (name, value) {
2261 | var h = atmosphere.util.isFunction(value) ? value.call(this, ajaxRequest, request, create, _response) : value;
2262 | if (h != null) {
2263 | ajaxRequest.setRequestHeader(name, h);
2264 | }
2265 | });
2266 | }
2267 |
2268 | if (request.contentType !== '') {
2269 | ajaxRequest.setRequestHeader("Content-Type", request.contentType);
2270 | }
2271 | }
2272 |
2273 | function _reconnect(ajaxRequest, request, delay) {
2274 |
2275 | if (_response.closedByClientTimeout) {
2276 | return;
2277 | }
2278 |
2279 | if (request.reconnect || (request.suspend && _subscribed)) {
2280 | var status = 0;
2281 | if (ajaxRequest && ajaxRequest.readyState > 1) {
2282 | status = ajaxRequest.status > 1000 ? 0 : ajaxRequest.status;
2283 | }
2284 | _response.status = status === 0 ? 204 : status;
2285 | _response.reasonPhrase = status === 0 ? "Server resumed the connection or down." : "OK";
2286 |
2287 | clearTimeout(request.id);
2288 | if (request.reconnectId) {
2289 | clearTimeout(request.reconnectId);
2290 | delete request.reconnectId;
2291 | }
2292 |
2293 | if (delay > 0) {
2294 | // For whatever reason, never cancel a reconnect timeout as it is mandatory to reconnect.
2295 | // Prevent the online event to open a second connection while waiting for reconnect
2296 | var handleOnlineOffline = _request.handleOnlineOffline;
2297 | _request.handleOnlineOffline = false;
2298 | _request.reconnectId = setTimeout(function () {
2299 | _request.handleOnlineOffline = handleOnlineOffline;
2300 | _executeRequest(request);
2301 | }, delay);
2302 | } else {
2303 | _executeRequest(request);
2304 | }
2305 | }
2306 | }
2307 |
2308 | function _tryingToReconnect(response) {
2309 | response.state = 're-connecting';
2310 | _invokeFunction(response);
2311 | }
2312 |
2313 | function _openAfterResume(response) {
2314 | response.state = 'openAfterResume';
2315 | _invokeFunction(response);
2316 | response.state = 'messageReceived';
2317 | }
2318 |
2319 | function _ieXDR(request) {
2320 | if (request.transport !== "polling") {
2321 | _ieStream = _configureXDR(request);
2322 | _ieStream.open();
2323 | } else {
2324 | _configureXDR(request).open();
2325 | }
2326 | }
2327 |
2328 | function _configureXDR(request) {
2329 | var rq = _request;
2330 | if ((request != null) && (typeof (request) !== 'undefined')) {
2331 | rq = request;
2332 | }
2333 |
2334 | var transport = rq.transport;
2335 | var lastIndex = 0;
2336 | var xdr = new window.XDomainRequest();
2337 | var reconnect = function () {
2338 | if (rq.transport === "long-polling" && (rq.reconnect && (rq.maxRequest === -1 || rq.requestCount++ < rq.maxRequest))) {
2339 | xdr.status = 200;
2340 | _ieXDR(rq);
2341 | }
2342 | };
2343 |
2344 | var rewriteURL = rq.rewriteURL || function (url) {
2345 | // Maintaining session by rewriting URL
2346 | // http://stackoverflow.com/questions/6453779/maintaining-session-by-rewriting-url
2347 | var match = /(?:^|;\s*)(JSESSIONID|PHPSESSID)=([^;]*)/.exec(document.cookie);
2348 |
2349 | switch (match && match[1]) {
2350 | case "JSESSIONID":
2351 | return url.replace(/;jsessionid=[^\?]*|(\?)|$/, ";jsessionid=" + match[2] + "$1");
2352 | case "PHPSESSID":
2353 | return url.replace(/\?PHPSESSID=[^&]*&?|\?|$/, "?PHPSESSID=" + match[2] + "&").replace(/&$/, "");
2354 | }
2355 | return url;
2356 | };
2357 |
2358 | // Handles open and message event
2359 | xdr.onprogress = function () {
2360 | handle(xdr);
2361 | };
2362 | // Handles error event
2363 | xdr.onerror = function () {
2364 | // If the server doesn't send anything back to XDR will fail with polling
2365 | if (rq.transport !== 'polling') {
2366 | _clearState();
2367 | if (_requestCount++ < rq.maxReconnectOnClose) {
2368 | if (rq.reconnectInterval > 0) {
2369 | rq.reconnectId = setTimeout(function () {
2370 | _open('re-connecting', request.transport, request);
2371 | _ieXDR(rq);
2372 | }, rq.reconnectInterval);
2373 | } else {
2374 | _open('re-connecting', request.transport, request);
2375 | _ieXDR(rq);
2376 | }
2377 | } else if (_request.reconnect && _request.fallbackTransport !== 'none') {
2378 | _reconnectWithFallbackTransport("maxReconnectOnClose reached. Downgrading to " + _request.fallbackTransport);
2379 | } else {
2380 | _onError(0, "maxReconnectOnClose reached");
2381 | }
2382 | }
2383 | };
2384 |
2385 | // Handles close event
2386 | xdr.onload = function () {
2387 | };
2388 |
2389 | var handle = function (xdr) {
2390 | clearTimeout(rq.id);
2391 | var message = xdr.responseText;
2392 |
2393 | message = message.substring(lastIndex);
2394 | lastIndex += message.length;
2395 |
2396 | if (transport !== 'polling') {
2397 | _timeout(rq);
2398 |
2399 | var skipCallbackInvocation = _trackMessageSize(message, rq, _response);
2400 |
2401 | if (transport === 'long-polling' && atmosphere.util.trim(message).length === 0)
2402 | return;
2403 |
2404 | if (rq.executeCallbackBeforeReconnect) {
2405 | reconnect();
2406 | }
2407 |
2408 | if (!skipCallbackInvocation) {
2409 | _prepareCallback(_response.responseBody, "messageReceived", 200, transport);
2410 | }
2411 |
2412 | if (!rq.executeCallbackBeforeReconnect) {
2413 | reconnect();
2414 | }
2415 | }
2416 | };
2417 |
2418 | return {
2419 | open: function () {
2420 | var url = rq.url;
2421 | if (rq.dispatchUrl != null) {
2422 | url += rq.dispatchUrl;
2423 | }
2424 | url = _attachHeaders(rq, url);
2425 | xdr.open(rq.method, rewriteURL(url));
2426 | if (rq.method === 'GET') {
2427 | xdr.send();
2428 | } else {
2429 | xdr.send(rq.data);
2430 | }
2431 |
2432 | if (rq.connectTimeout > 0) {
2433 | rq.id = setTimeout(function () {
2434 | if (rq.requestCount === 0) {
2435 | _clearState();
2436 | _prepareCallback("Connect timeout", "closed", 200, rq.transport);
2437 | }
2438 | }, rq.connectTimeout);
2439 | }
2440 | },
2441 | close: function () {
2442 | xdr.abort();
2443 | }
2444 | };
2445 | }
2446 |
2447 | function _ieStreaming(request) {
2448 | _ieStream = _configureIE(request);
2449 | _ieStream.open();
2450 | }
2451 |
2452 | function _configureIE(request) {
2453 | var rq = _request;
2454 | if ((request != null) && (typeof (request) !== 'undefined')) {
2455 | rq = request;
2456 | }
2457 |
2458 | var stop;
2459 | var doc = new window.ActiveXObject("htmlfile");
2460 |
2461 | doc.open();
2462 | doc.close();
2463 |
2464 | var url = rq.url;
2465 | if (rq.dispatchUrl != null) {
2466 | url += rq.dispatchUrl;
2467 | }
2468 |
2469 | if (rq.transport !== 'polling') {
2470 | _response.transport = rq.transport;
2471 | }
2472 |
2473 | return {
2474 | open: function () {
2475 | var iframe = doc.createElement("iframe");
2476 |
2477 | url = _attachHeaders(rq);
2478 | if (rq.data !== '') {
2479 | url += "&X-Atmosphere-Post-Body=" + encodeURIComponent(rq.data);
2480 | }
2481 |
2482 | // Finally attach a timestamp to prevent Android and IE caching.
2483 | url = atmosphere.util.prepareURL(url);
2484 |
2485 | iframe.src = url;
2486 | doc.body.appendChild(iframe);
2487 |
2488 | // For the server to respond in a consistent format regardless of user agent, we polls response text
2489 | var cdoc = iframe.contentDocument || iframe.contentWindow.document;
2490 |
2491 | stop = atmosphere.util.iterate(function () {
2492 | try {
2493 | if (!cdoc.firstChild) {
2494 | return;
2495 | }
2496 |
2497 | var res = cdoc.body ? cdoc.body.lastChild : cdoc;
2498 | if (res.omgThisIsBroken) {
2499 | // Cause an exception when res is null, to trigger a reconnect...
2500 | }
2501 | var readResponse = function () {
2502 | // Clones the element not to disturb the original one
2503 | var clone = res.cloneNode(true);
2504 |
2505 | // If the last character is a carriage return or a line feed, IE ignores it in the innerText property
2506 | // therefore, we add another non-newline character to preserve it
2507 | clone.appendChild(cdoc.createTextNode("."));
2508 |
2509 | var text = clone.innerText;
2510 |
2511 | text = text.substring(0, text.length - 1);
2512 | return text;
2513 |
2514 | };
2515 |
2516 | // To support text/html content type
2517 | if (!cdoc.body || !cdoc.body.firstChild || cdoc.body.firstChild.nodeName.toLowerCase() !== "pre") {
2518 | // Injects a plaintext element which renders text without interpreting the HTML and cannot be stopped
2519 | // it is deprecated in HTML5, but still works
2520 | var head = cdoc.head || cdoc.getElementsByTagName("head")[0] || cdoc.documentElement || cdoc;
2521 | var script = cdoc.createElement("script");
2522 |
2523 | script.text = "document.write('')";
2524 |
2525 | head.insertBefore(script, head.firstChild);
2526 | head.removeChild(script);
2527 |
2528 | // The plaintext element will be the response container
2529 | res = cdoc.body.lastChild;
2530 | }
2531 |
2532 | if (rq.closed) {
2533 | rq.isReopen = true;
2534 | }
2535 |
2536 | // Handles message and close event
2537 | stop = atmosphere.util.iterate(function () {
2538 | var text = readResponse();
2539 | if (text.length > rq.lastIndex) {
2540 | _timeout(_request);
2541 |
2542 | _response.status = 200;
2543 | _response.error = false;
2544 |
2545 | // Empties response every time that it is handled
2546 | res.innerText = "";
2547 | var skipCallbackInvocation = _trackMessageSize(text, rq, _response);
2548 | if (skipCallbackInvocation) {
2549 | return false;
2550 | }
2551 |
2552 | _prepareCallback(_response.responseBody, "messageReceived", 200, rq.transport);
2553 | }
2554 |
2555 | rq.lastIndex = 0;
2556 |
2557 | if (cdoc.readyState === "complete") {
2558 | _invokeClose(true);
2559 | _open('re-connecting', rq.transport, rq);
2560 | if (rq.reconnectInterval > 0) {
2561 | rq.reconnectId = setTimeout(function () {
2562 | _ieStreaming(rq);
2563 | }, rq.reconnectInterval);
2564 | } else {
2565 | _ieStreaming(rq);
2566 | }
2567 | return false;
2568 | }
2569 | }, null);
2570 |
2571 | return false;
2572 | } catch (err) {
2573 | _response.error = true;
2574 | _open('re-connecting', rq.transport, rq);
2575 | if (_requestCount++ < rq.maxReconnectOnClose) {
2576 | if (rq.reconnectInterval > 0) {
2577 | rq.reconnectId = setTimeout(function () {
2578 | _ieStreaming(rq);
2579 | }, rq.reconnectInterval);
2580 | } else {
2581 | _ieStreaming(rq);
2582 | }
2583 | } else if (_request.reconnect && _request.fallbackTransport !== 'none') {
2584 | _reconnectWithFallbackTransport("maxReconnectOnClose reached. Downgrading to " + _request.fallbackTransport);
2585 | } else {
2586 | _onError(0, "maxReconnectOnClose reached");
2587 | }
2588 | doc.execCommand("Stop");
2589 | doc.close();
2590 | return false;
2591 | }
2592 | });
2593 | },
2594 |
2595 | close: function () {
2596 | if (stop) {
2597 | stop();
2598 | }
2599 |
2600 | doc.execCommand("Stop");
2601 | _invokeClose(true);
2602 | }
2603 | };
2604 | }
2605 |
2606 | /**
2607 | * Send message.
2608 | * Will be automatically dispatch to other connected.
2609 | *
2610 | * @param {Object, string} Message to send.
2611 | * @private
2612 | */
2613 | function _push(message, _request) {
2614 |
2615 | if (_localStorageService != null) {
2616 | _pushLocal(message);
2617 | } else if (_activeRequest != null || _sse != null
2618 | // Avoid errors when sending message during long-polling reconnection
2619 | || _request && _request.isOpen && _request.reconnect && _request.transport === "long-polling") {
2620 | _pushAjaxMessage(message);
2621 | } else if (_ieStream != null) {
2622 | _pushIE(message);
2623 | } else if (_jqxhr != null) {
2624 | _pushJsonp(message);
2625 | } else if (_websocket != null) {
2626 | _pushWebSocket(message);
2627 | // Avoid errors when sending message during websocket reconnection
2628 | } else if (_request && _request.isOpen && _request.reconnect && _request.isReopen && _request.transport === "websocket") {
2629 | _debug("Waiting for the websocket reconnection to send the message...");
2630 | var reopenHandler = _request.onReopen;
2631 | _request.onReopen = function() {
2632 | _request.onReopen = reopenHandler;
2633 | if (typeof (reopenHandler) !== 'undefined') {
2634 | reopenHandler.apply(this, arguments);
2635 | }
2636 | _push(message, _request);
2637 | }
2638 | } else {
2639 | _onError(0, "No suspended connection available");
2640 | atmosphere.util.error("No suspended connection available. Make sure atmosphere.subscribe has been called and request.onOpen invoked before trying to push data");
2641 | }
2642 | }
2643 |
2644 | function _pushOnClose(message, rq) {
2645 | if (!rq) {
2646 | rq = _getPushRequest(message);
2647 | }
2648 | rq.transport = "polling";
2649 | rq.method = "GET";
2650 | rq.withCredentials = false;
2651 | rq.reconnect = false;
2652 | rq.force = true;
2653 | rq.suspend = false;
2654 | rq.timeout = 1000;
2655 | if (_request.unloadBackwardCompat) {
2656 | _executeRequest(rq);
2657 | } else {
2658 | navigator.sendBeacon(rq.url, rq.data);
2659 | }
2660 | }
2661 |
2662 | function _pushLocal(message) {
2663 | _localStorageService.send(message);
2664 | }
2665 |
2666 | function _intraPush(message) {
2667 | // IE 9 will crash if not.
2668 | if (message.length === 0)
2669 | return;
2670 |
2671 | try {
2672 | if (_localStorageService) {
2673 | _localStorageService.localSend(message);
2674 | } else if (_storageService) {
2675 | _storageService.signal("localMessage", JSON.stringify({
2676 | id: guid,
2677 | event: message
2678 | }));
2679 | }
2680 | } catch (err) {
2681 | atmosphere.util.error(err);
2682 | }
2683 | }
2684 |
2685 | /**
2686 | * Send a message using currently opened ajax request (using http-streaming or long-polling).
2687 | *
2688 | * @param {string, Object} Message to send. This is an object, string message is saved in data member.
2689 | * @private
2690 | */
2691 | function _pushAjaxMessage(message) {
2692 | var rq = _getPushRequest(message);
2693 | _executeRequest(rq);
2694 | }
2695 |
2696 | /**
2697 | * Send a message using currently opened ie streaming (using http-streaming or long-polling).
2698 | *
2699 | * @param {string, Object} Message to send. This is an object, string message is saved in data member.
2700 | * @private
2701 | */
2702 | function _pushIE(message) {
2703 | if (_request.enableXDR && atmosphere.util.checkCORSSupport()) {
2704 | var rq = _getPushRequest(message);
2705 | // Do not reconnect since we are pushing.
2706 | rq.reconnect = false;
2707 | _jsonp(rq);
2708 | } else {
2709 | _pushAjaxMessage(message);
2710 | }
2711 | }
2712 |
2713 | /**
2714 | * Send a message using jsonp transport.
2715 | *
2716 | * @param {string, Object} Message to send. This is an object, string message is saved in data member.
2717 | * @private
2718 | */
2719 | function _pushJsonp(message) {
2720 | _pushAjaxMessage(message);
2721 | }
2722 |
2723 | function _getStringMessage(message) {
2724 | var msg = message;
2725 | if (typeof (msg) === 'object') {
2726 | msg = message.data;
2727 | }
2728 | return msg;
2729 | }
2730 |
2731 | /**
2732 | * Build request use to push message using method 'POST'
. Transport is defined as 'polling' and 'suspend' is set to false.
2733 | *
2734 | * @return {Object} Request object use to push message.
2735 | * @private
2736 | */
2737 | function _getPushRequest(message) {
2738 | var msg = _getStringMessage(message);
2739 |
2740 | var rq = {
2741 | connected: false,
2742 | timeout: 60000,
2743 | method: 'POST',
2744 | url: _request.url,
2745 | contentType: _request.contentType,
2746 | headers: _request.headers,
2747 | reconnect: true,
2748 | callback: null,
2749 | data: msg,
2750 | suspend: false,
2751 | maxRequest: -1,
2752 | logLevel: 'info',
2753 | requestCount: 0,
2754 | withCredentials: _request.withCredentials,
2755 | transport: 'polling',
2756 | isOpen: true,
2757 | attachHeadersAsQueryString: true,
2758 | enableXDR: _request.enableXDR,
2759 | uuid: _request.uuid,
2760 | dispatchUrl: _request.dispatchUrl,
2761 | enableProtocol: false,
2762 | messageDelimiter: '|',
2763 | trackMessageLength: _request.trackMessageLength,
2764 | maxReconnectOnClose: _request.maxReconnectOnClose,
2765 | heartbeatTimer: _request.heartbeatTimer,
2766 | heartbeat: _request.heartbeat
2767 | };
2768 |
2769 | if (typeof (message) === 'object') {
2770 | rq = atmosphere.util.extend(rq, message);
2771 | }
2772 |
2773 | return rq;
2774 | }
2775 |
2776 | /**
2777 | * Send a message using currently opened websocket.
2778 | *
2779 | */
2780 | function _pushWebSocket(message) {
2781 | var msg = atmosphere.util.isBinary(message) ? message : _getStringMessage(message);
2782 | var data;
2783 | try {
2784 | if (_request.dispatchUrl != null) {
2785 | data = _request.webSocketPathDelimiter + _request.dispatchUrl + _request.webSocketPathDelimiter + msg;
2786 | } else {
2787 | data = msg;
2788 | }
2789 |
2790 | if (!_websocket.canSendMessage) {
2791 | atmosphere.util.error("WebSocket not connected.");
2792 | return;
2793 | }
2794 |
2795 | _websocket.send(data);
2796 |
2797 | } catch (e) {
2798 | _websocket.onclose = function () {
2799 | };
2800 | _clearState();
2801 |
2802 | _reconnectWithFallbackTransport("Websocket failed. Downgrading to " + _request.fallbackTransport + " and resending " + message);
2803 | _pushAjaxMessage(message);
2804 | }
2805 | }
2806 |
2807 | function _localMessage(message) {
2808 | var m = JSON.parse(message);
2809 | if (m.id !== guid && typeof (_request.onLocalMessage) !== 'undefined') {
2810 | _request.onLocalMessage(m.event);
2811 | }
2812 | }
2813 |
2814 | function _prepareCallback(messageBody, state, errorCode, transport) {
2815 | _response.responseBody = messageBody;
2816 | _response.transport = transport;
2817 | _response.status = errorCode;
2818 | _response.state = state;
2819 |
2820 | _invokeCallback();
2821 | }
2822 |
2823 | function _readHeaders(xdr, request) {
2824 | if (!request.readResponsesHeaders) {
2825 | if (!request.enableProtocol) {
2826 | request.uuid = guid;
2827 | }
2828 | } else {
2829 | try {
2830 |
2831 | var tempUUID = xdr.getResponseHeader('X-Atmosphere-tracking-id');
2832 | if (tempUUID && tempUUID != null) {
2833 | request.uuid = tempUUID.split(" ").pop();
2834 | }
2835 | } catch (e) {
2836 | }
2837 | }
2838 | }
2839 |
2840 | function _invokeFunction(response) {
2841 | _f(response, _request);
2842 | // Global
2843 | _f(response, atmosphere.util);
2844 | }
2845 |
2846 | function _f(response, f) {
2847 | switch (response.state) {
2848 | case "messageReceived":
2849 | _debug("Firing onMessage");
2850 | _requestCount = 0;
2851 | if (typeof (f.onMessage) !== 'undefined')
2852 | f.onMessage(response);
2853 |
2854 | if (typeof (f.onmessage) !== 'undefined')
2855 | f.onmessage(response);
2856 | break;
2857 | case "error":
2858 | var dbgReasonPhrase = (typeof (response.reasonPhrase) != 'undefined') ? response.reasonPhrase : 'n/a';
2859 | _debug("Firing onError, reasonPhrase: " + dbgReasonPhrase);
2860 | if (typeof (f.onError) !== 'undefined')
2861 | f.onError(response);
2862 |
2863 | if (typeof (f.onerror) !== 'undefined')
2864 | f.onerror(response);
2865 | break;
2866 | case "opening":
2867 | delete _request.closed;
2868 | _debug("Firing onOpen");
2869 | if (typeof (f.onOpen) !== 'undefined')
2870 | f.onOpen(response);
2871 |
2872 | if (typeof (f.onopen) !== 'undefined')
2873 | f.onopen(response);
2874 | break;
2875 | case "messagePublished":
2876 | _debug("Firing messagePublished");
2877 | if (typeof (f.onMessagePublished) !== 'undefined')
2878 | f.onMessagePublished(response);
2879 | break;
2880 | case "re-connecting":
2881 | _debug("Firing onReconnect");
2882 | if (typeof (f.onReconnect) !== 'undefined')
2883 | f.onReconnect(_request, response);
2884 | break;
2885 | case "closedByClient":
2886 | _debug("Firing closedByClient");
2887 | if (typeof (f.onClientTimeout) !== 'undefined')
2888 | f.onClientTimeout(_request);
2889 | break;
2890 | case "re-opening":
2891 | delete _request.closed;
2892 | _debug("Firing onReopen");
2893 | if (typeof (f.onReopen) !== 'undefined')
2894 | f.onReopen(_request, response);
2895 | break;
2896 | case "fail-to-reconnect":
2897 | _debug("Firing onFailureToReconnect");
2898 | if (typeof (f.onFailureToReconnect) !== 'undefined')
2899 | f.onFailureToReconnect(_request, response);
2900 | break;
2901 | case "unsubscribe":
2902 | case "closed":
2903 | var closed = typeof (_request.closed) !== 'undefined' ? _request.closed : false;
2904 |
2905 | if (!closed) {
2906 | _debug("Firing onClose (" + response.state + " case)");
2907 | if (typeof (f.onClose) !== 'undefined') {
2908 | f.onClose(response);
2909 | }
2910 |
2911 | if (typeof (f.onclose) !== 'undefined') {
2912 | f.onclose(response);
2913 | }
2914 | } else {
2915 | _debug("Request already closed, not firing onClose (" + response.state + " case)");
2916 | }
2917 | _request.closed = true;
2918 | break;
2919 | case "openAfterResume":
2920 | if (typeof (f.onOpenAfterResume) !== 'undefined')
2921 | f.onOpenAfterResume(_request);
2922 | break;
2923 | }
2924 | }
2925 |
2926 | function _invokeClose(wasOpen) {
2927 | if (_response.state !== 'closed') {
2928 | _response.state = 'closed';
2929 | _response.responseBody = "";
2930 | _response.messages = [];
2931 | _response.status = !wasOpen ? 501 : 200;
2932 | _invokeCallback();
2933 | }
2934 | }
2935 |
2936 | /**
2937 | * Invoke request callbacks.
2938 | *
2939 | * @private
2940 | */
2941 | function _invokeCallback() {
2942 | var call = function (index, func) {
2943 | func(_response);
2944 | };
2945 |
2946 | if (_localStorageService == null && _localSocketF != null) {
2947 | _localSocketF(_response.responseBody);
2948 | }
2949 |
2950 | _request.reconnect = _request.mrequest;
2951 |
2952 | var isString = typeof (_response.responseBody) === 'string';
2953 | var messages = (isString && _request.trackMessageLength) ? (_response.messages.length > 0 ? _response.messages : ['']) : new Array(
2954 | _response.responseBody);
2955 | for (var i = 0; i < messages.length; i++) {
2956 |
2957 | if (messages.length > 1 && messages[i].length === 0) {
2958 | continue;
2959 | }
2960 | _response.responseBody = (isString) ? atmosphere.util.trim(messages[i]) : messages[i];
2961 |
2962 | if (_localStorageService == null && _localSocketF != null) {
2963 | _localSocketF(_response.responseBody);
2964 | }
2965 |
2966 | if (_response.state === "messageReceived") {
2967 | if (_response.responseBody.length === 0) {
2968 | continue;
2969 | } else if (isString && _heartbeatPadding === _response.responseBody) {
2970 | // reset the internal reconnect counter, when we received also heartbeat message from server
2971 | _requestCount = 0;
2972 | continue;
2973 | }
2974 | } else if (_response.state === "opening" || _response.state === "re-opening") {
2975 | // reset the internal reconnect counter when the connection is opened/reopened
2976 | _requestCount = 0;
2977 | }
2978 |
2979 | _invokeFunction(_response);
2980 |
2981 | // Invoke global callbacks
2982 | if (callbacks.length > 0) {
2983 | if (_canLog('debug')) {
2984 | atmosphere.util.debug("Invoking " + callbacks.length + " global callbacks: " + _response.state);
2985 | }
2986 | try {
2987 | atmosphere.util.each(callbacks, call);
2988 | } catch (e) {
2989 | atmosphere.util.log(_request.logLevel, ["Callback exception" + e]);
2990 | }
2991 | }
2992 |
2993 | // Invoke request callback
2994 | if (typeof (_request.callback) === 'function') {
2995 | if (_canLog('debug')) {
2996 | atmosphere.util.debug("Invoking request callbacks");
2997 | }
2998 | try {
2999 | _request.callback(_response);
3000 | } catch (e) {
3001 | atmosphere.util.log(_request.logLevel, ["Callback exception" + e]);
3002 | }
3003 | }
3004 | }
3005 | }
3006 |
3007 | this.subscribe = function (options) {
3008 | _subscribe(options);
3009 | _execute();
3010 | };
3011 |
3012 | this.execute = function () {
3013 | _execute();
3014 | };
3015 |
3016 | this.close = function () {
3017 | _close();
3018 | };
3019 |
3020 | this.disconnect = function () {
3021 | _disconnect();
3022 | };
3023 |
3024 | this.getUrl = function () {
3025 | return _request.url;
3026 | };
3027 |
3028 | this.push = function (message, dispatchUrl) {
3029 | if (dispatchUrl != null) {
3030 | var originalDispatchUrl = _request.dispatchUrl;
3031 | _request.dispatchUrl = dispatchUrl;
3032 | _push(message, _request);
3033 | _request.dispatchUrl = originalDispatchUrl;
3034 | } else {
3035 | _push(message, _request);
3036 | }
3037 | };
3038 |
3039 | this.getUUID = function () {
3040 | return _request.uuid;
3041 | };
3042 |
3043 | this.pushLocal = function (message) {
3044 | _intraPush(message);
3045 | };
3046 |
3047 | this.enableProtocol = function () {
3048 | return _request.enableProtocol;
3049 | };
3050 |
3051 | this.init = function () {
3052 | _init();
3053 | };
3054 |
3055 | this.request = _request;
3056 | this.response = _response;
3057 | }
3058 | };
3059 |
3060 | atmosphere.subscribe = function (url, callback, request) {
3061 | if (typeof (callback) === 'function') {
3062 | atmosphere.addCallback(callback);
3063 | }
3064 |
3065 | if (typeof (url) !== "string") {
3066 | request = url;
3067 | } else {
3068 | request.url = url;
3069 | }
3070 |
3071 | // https://github.com/Atmosphere/atmosphere-javascript/issues/58
3072 | uuid = ((typeof (request) !== 'undefined') && typeof (request.uuid) !== 'undefined') ? request.uuid : 0;
3073 |
3074 | var rq = new atmosphere.AtmosphereRequest(request);
3075 | rq.execute();
3076 |
3077 | requests[requests.length] = rq;
3078 | return rq;
3079 | };
3080 |
3081 | atmosphere.unsubscribe = function () {
3082 | if (requests.length > 0) {
3083 | var requestsClone = [].concat(requests);
3084 | for (var i = 0; i < requestsClone.length; i++) {
3085 | var rq = requestsClone[i];
3086 | rq.close();
3087 | clearTimeout(rq.response.request.id);
3088 |
3089 | if (rq.heartbeatTimer) {
3090 | clearTimeout(rq.heartbeatTimer);
3091 | }
3092 | }
3093 | }
3094 | requests = [];
3095 | callbacks = [];
3096 | };
3097 |
3098 | atmosphere.unsubscribeUrl = function (url) {
3099 | var idx = -1;
3100 | if (requests.length > 0) {
3101 | for (var i = 0; i < requests.length; i++) {
3102 | var rq = requests[i];
3103 |
3104 | // Suppose you can subscribe once to an url
3105 | if (rq.getUrl() === url) {
3106 | rq.close();
3107 | clearTimeout(rq.response.request.id);
3108 |
3109 | if (rq.heartbeatTimer) {
3110 | clearTimeout(rq.heartbeatTimer);
3111 | }
3112 |
3113 | idx = i;
3114 | break;
3115 | }
3116 | }
3117 | }
3118 | if (idx >= 0) {
3119 | requests.splice(idx, 1);
3120 | }
3121 | };
3122 |
3123 | atmosphere.addCallback = function (func) {
3124 | if (atmosphere.util.inArray(func, callbacks) === -1) {
3125 | callbacks.push(func);
3126 | }
3127 | };
3128 |
3129 | atmosphere.removeCallback = function (func) {
3130 | var index = atmosphere.util.inArray(func, callbacks);
3131 | if (index !== -1) {
3132 | callbacks.splice(index, 1);
3133 | }
3134 | };
3135 |
3136 | atmosphere.util = {
3137 | browser: {},
3138 |
3139 | parseHeaders: function (headerString) {
3140 | var match, rheaders = /^(.*?):[ \t]*([^\r\n]*)\r?$/mg, headers = {};
3141 | while (match = rheaders.exec(headerString)) {
3142 | headers[match[1]] = match[2];
3143 | }
3144 | return headers;
3145 | },
3146 |
3147 | now: function () {
3148 | return new Date().getTime();
3149 | },
3150 |
3151 | isArray: function (array) {
3152 | return Object.prototype.toString.call(array) === "[object Array]";
3153 | },
3154 |
3155 | inArray: function (elem, array) {
3156 | if (!Array.prototype.indexOf) {
3157 | var len = array.length;
3158 | for (var i = 0; i < len; ++i) {
3159 | if (array[i] === elem) {
3160 | return i;
3161 | }
3162 | }
3163 | return -1;
3164 | }
3165 | return array.indexOf(elem);
3166 | },
3167 |
3168 | isBinary: function (data) {
3169 | // True if data is an instance of Blob, ArrayBuffer or ArrayBufferView
3170 | return /^\[object\s(?:Blob|ArrayBuffer|.+Array)\]$/.test(Object.prototype.toString.call(data));
3171 | },
3172 |
3173 | isFunction: function (fn) {
3174 | return Object.prototype.toString.call(fn) === "[object Function]";
3175 | },
3176 |
3177 | getAbsoluteURL: function (url) {
3178 | if (typeof (document.createElement) === 'undefined') {
3179 | // assuming the url to be already absolute when DOM is not supported
3180 | return url;
3181 | }
3182 | var div = document.createElement("div");
3183 |
3184 | // Uses an innerHTML property to obtain an absolute URL
3185 | div.innerHTML = '';
3186 |
3187 | // encodeURI and decodeURI are needed to normalize URL between IE and non-IE,
3188 | // since IE doesn't encode the href property value and return it - http://jsfiddle.net/Yq9M8/1/
3189 |
3190 | var ua = window.navigator.userAgent;
3191 | if (ua.indexOf('MSIE ') > 0 || ua.indexOf('Trident/') > 0 || ua.indexOf('Edge/') > 0) {
3192 | return atmosphere.util.fixedEncodeURI(decodeURI(div.firstChild.href));
3193 | }
3194 | return div.firstChild.href;
3195 | },
3196 |
3197 | fixedEncodeURI: function (str) {
3198 | return encodeURI(str).replace(/%5B/g, '[').replace(/%5D/g, ']');
3199 | },
3200 |
3201 | prepareURL: function (url) {
3202 | // Attaches a time stamp to prevent caching
3203 | var ts = atmosphere.util.now();
3204 | var ret = url.replace(/([?&])_=[^&]*/, "$1_=" + ts);
3205 |
3206 | return ret + (ret === url ? (/\?/.test(url) ? "&" : "?") + "_=" + ts : "");
3207 | },
3208 |
3209 | trim: function (str) {
3210 | if (!String.prototype.trim) {
3211 | return str.toString().replace(/(?:(?:^|\n)\s+|\s+(?:$|\n))/g, "").replace(/\s+/g, " ");
3212 | } else {
3213 | return str.toString().trim();
3214 | }
3215 | },
3216 |
3217 | param: function (params) {
3218 | var prefix, s = [];
3219 |
3220 | function add(key, value) {
3221 | value = atmosphere.util.isFunction(value) ? value() : (value == null ? "" : value);
3222 | s.push(encodeURIComponent(key) + "=" + encodeURIComponent(value));
3223 | }
3224 |
3225 | function buildParams(prefix, obj) {
3226 | var name;
3227 |
3228 | if (atmosphere.util.isArray(obj)) {
3229 | atmosphere.util.each(obj, function (i, v) {
3230 | if (/\[\]$/.test(prefix)) {
3231 | add(prefix, v);
3232 | } else {
3233 | buildParams(prefix + "[" + (typeof v === "object" ? i : "") + "]", v);
3234 | }
3235 | });
3236 | } else if (Object.prototype.toString.call(obj) === "[object Object]") {
3237 | for (name in obj) {
3238 | buildParams(prefix + "[" + name + "]", obj[name]);
3239 | }
3240 | } else {
3241 | add(prefix, obj);
3242 | }
3243 | }
3244 |
3245 | for (prefix in params) {
3246 | buildParams(prefix, params[prefix]);
3247 | }
3248 |
3249 | return s.join("&").replace(/%20/g, "+");
3250 | },
3251 |
3252 | storage: (function () {
3253 | try {
3254 | return !!(window.localStorage && window.StorageEvent);
3255 | } catch (e) {
3256 | //Firefox throws an exception here, see
3257 | //https://bugzilla.mozilla.org/show_bug.cgi?id=748620
3258 | return false;
3259 | }
3260 | })(),
3261 |
3262 | iterate: function (fn, interval) {
3263 | var timeoutId;
3264 |
3265 | // Though the interval is 0 for real-time application, there is a delay between setTimeout calls
3266 | // For detail, see https://developer.mozilla.org/en/window.setTimeout#Minimum_delay_and_timeout_nesting
3267 | interval = interval || 0;
3268 |
3269 | (function loop() {
3270 | timeoutId = setTimeout(function () {
3271 | if (fn() === false) {
3272 | return;
3273 | }
3274 |
3275 | loop();
3276 | }, interval);
3277 | })();
3278 |
3279 | return function () {
3280 | clearTimeout(timeoutId);
3281 | };
3282 | },
3283 |
3284 | each: function (obj, callback) {
3285 | if (!obj) return;
3286 | var value, i = 0, length = obj.length, isArray = atmosphere.util.isArray(obj);
3287 |
3288 | if (isArray) {
3289 | for (; i < length; i++) {
3290 | value = callback.call(obj[i], i, obj[i]);
3291 |
3292 | if (value === false) {
3293 | break;
3294 | }
3295 | }
3296 | } else {
3297 | for (i in obj) {
3298 | value = callback.call(obj[i], i, obj[i]);
3299 |
3300 | if (value === false) {
3301 | break;
3302 | }
3303 | }
3304 | }
3305 |
3306 | return obj;
3307 | },
3308 |
3309 | extend: function (target) {
3310 | var i, options, name;
3311 |
3312 | for (i = 1; i < arguments.length; i++) {
3313 | if ((options = arguments[i]) != null) {
3314 | for (name in options) {
3315 | target[name] = options[name];
3316 | }
3317 | }
3318 | }
3319 |
3320 | return target;
3321 | },
3322 | on: function (elem, type, fn) {
3323 | if (elem.addEventListener) {
3324 | elem.addEventListener(type, fn, false);
3325 | } else if (elem.attachEvent) {
3326 | elem.attachEvent("on" + type, fn);
3327 | }
3328 | },
3329 | off: function (elem, type, fn) {
3330 | if (elem.removeEventListener) {
3331 | elem.removeEventListener(type, fn, false);
3332 | } else if (elem.detachEvent) {
3333 | elem.detachEvent("on" + type, fn);
3334 | }
3335 | },
3336 |
3337 | log: function (level, args) {
3338 | if (window.console) {
3339 | var logger = window.console[level];
3340 | if (typeof logger === 'function') {
3341 | logger.apply(window.console, args);
3342 | }
3343 | }
3344 | },
3345 |
3346 | warn: function () {
3347 | atmosphere.util.log('warn', arguments);
3348 | },
3349 |
3350 | info: function () {
3351 | atmosphere.util.log('info', arguments);
3352 | },
3353 |
3354 | debug: function () {
3355 | atmosphere.util.log('debug', arguments);
3356 | },
3357 |
3358 | error: function () {
3359 | atmosphere.util.log('error', arguments);
3360 | },
3361 |
3362 | xhr: function () {
3363 | try {
3364 | return new window.XMLHttpRequest();
3365 | } catch (e1) {
3366 | try {
3367 | return new window.ActiveXObject("Microsoft.XMLHTTP");
3368 | } catch (e2) {
3369 | }
3370 | }
3371 | },
3372 |
3373 | checkCORSSupport: function () {
3374 | if (atmosphere.util.browser.msie && !window.XDomainRequest && +atmosphere.util.browser.version.split(".")[0] < 11) {
3375 | return true;
3376 | } else if (atmosphere.util.browser.opera && +atmosphere.util.browser.version.split(".") < 12.0) {
3377 | return true;
3378 | }
3379 |
3380 | // KreaTV 4.1 -> 4.4
3381 | else if (atmosphere.util.trim(navigator.userAgent).slice(0, 16) === "KreaTVWebKit/531") {
3382 | return true;
3383 | }
3384 | // KreaTV 3.8
3385 | else if (atmosphere.util.trim(navigator.userAgent).slice(-7).toLowerCase() === "kreatel") {
3386 | return true;
3387 | }
3388 |
3389 | // Force older Android versions to use CORS as some version like 2.2.3 fail otherwise
3390 | var ua = navigator.userAgent.toLowerCase();
3391 | var androidVersionMatches = ua.match(/.+android ([0-9]{1,2})/i),
3392 | majorVersion = parseInt((androidVersionMatches && androidVersionMatches[0]) || -1, 10);
3393 | if (!isNaN(majorVersion) && majorVersion > -1 && majorVersion < 3) {
3394 | return true;
3395 | }
3396 | return false;
3397 | }
3398 | };
3399 |
3400 | // Browser sniffing
3401 | (function () {
3402 | var ua = navigator.userAgent.toLowerCase(),
3403 | match = /(chrome)[ \/]([\w.]+)/.exec(ua) ||
3404 | /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) ||
3405 | /(msie) ([\w.]+)/.exec(ua) ||
3406 | /(trident)(?:.*? rv:([\w.]+)|)/.exec(ua) ||
3407 | ua.indexOf("android") < 0 && /version\/(.+) (safari)/.exec(ua) ||
3408 | ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) ||
3409 | [];
3410 |
3411 | // Swaps variables
3412 | if (match[2] === "safari") {
3413 | match[2] = match[1];
3414 | match[1] = "safari";
3415 | }
3416 | atmosphere.util.browser[match[1] || ""] = true;
3417 | atmosphere.util.browser.version = match[2] || "0";
3418 | atmosphere.util.browser.vmajor = atmosphere.util.browser.version.split(".")[0];
3419 |
3420 | // Trident is the layout engine of the Internet Explorer
3421 | // IE 11 has no "MSIE: 11.0" token
3422 | if (atmosphere.util.browser.trident) {
3423 | atmosphere.util.browser.msie = true;
3424 | }
3425 |
3426 | // The storage event of Internet Explorer and Firefox 3 works strangely
3427 | if (atmosphere.util.browser.msie || (atmosphere.util.browser.mozilla && +atmosphere.util.browser.version.split(".")[0] === 1)) {
3428 | atmosphere.util.storage = false;
3429 | }
3430 | })();
3431 |
3432 | atmosphere.callbacks = {
3433 | unload: function () {
3434 | atmosphere.util.debug(new Date() + " Atmosphere: " + "unload event");
3435 | // Check if we should use the old unload behavior or if beforeunload hasn't handled cleanup
3436 | var shouldCleanupInUnload = requests.length > 0 &&
3437 | (requests[0].request.useBeforeUnloadForCleanup === false || !_beforeUnloadState);
3438 |
3439 | if (shouldCleanupInUnload) {
3440 | atmosphere.unsubscribe();
3441 | }
3442 | },
3443 | beforeUnload: function () {
3444 | atmosphere.util.debug(new Date() + " Atmosphere: " + "beforeunload event");
3445 |
3446 | // If another unload attempt was made within the 5000ms timeout
3447 | if (atmosphere._beforeUnloadTimeoutId != null) {
3448 | clearTimeout(atmosphere._beforeUnloadTimeoutId);
3449 | }
3450 |
3451 | // ATMOSPHERE-JAVASCRIPT-143: Delay reconnect to avoid reconnect attempts before an actual unload (we don't know if an unload will happen, yet)
3452 | _beforeUnloadState = true;
3453 |
3454 | // Check if we should cleanup in beforeunload (default behavior for better bfcache compatibility)
3455 | var shouldCleanupInBeforeUnload = requests.length > 0 &&
3456 | requests[0].request.useBeforeUnloadForCleanup !== false;
3457 |
3458 | if (shouldCleanupInBeforeUnload) {
3459 | // Primary cleanup now happens here instead of in unload event
3460 | // This ensures compatibility with Chrome's bfcache and follows modern best practices
3461 | atmosphere.unsubscribe();
3462 | }
3463 |
3464 | atmosphere._beforeUnloadTimeoutId = setTimeout(function () {
3465 | atmosphere.util.debug(new Date() + " Atmosphere: " + "beforeunload event timeout reached. Reset _beforeUnloadState flag");
3466 | _beforeUnloadState = false;
3467 | }, 5000);
3468 | },
3469 | offline: function () {
3470 | atmosphere.util.debug(new Date() + " Atmosphere: offline event");
3471 | offline = true;
3472 | if (requests.length > 0) {
3473 | var requestsClone = [].concat(requests);
3474 | for (var i = 0; i < requestsClone.length; i++) {
3475 | var rq = requestsClone[i];
3476 | if (rq.request.handleOnlineOffline) {
3477 | rq.close();
3478 | clearTimeout(rq.response.request.id);
3479 |
3480 | if (rq.heartbeatTimer) {
3481 | clearTimeout(rq.heartbeatTimer);
3482 | }
3483 | }
3484 | }
3485 | }
3486 | },
3487 | online: function () {
3488 | atmosphere.util.debug(new Date() + " Atmosphere: online event");
3489 | if (requests.length > 0) {
3490 | for (var i = 0; i < requests.length; i++) {
3491 | if (requests[i].request.handleOnlineOffline) {
3492 | requests[i].init();
3493 | requests[i].execute();
3494 | }
3495 | }
3496 | }
3497 | offline = false;
3498 | }
3499 | };
3500 |
3501 | atmosphere.bindEvents = function () {
3502 | atmosphere.util.on(window, "unload", atmosphere.callbacks.unload);
3503 | atmosphere.util.on(window, "beforeunload", atmosphere.callbacks.beforeUnload);
3504 | atmosphere.util.on(window, "offline", atmosphere.callbacks.offline);
3505 | atmosphere.util.on(window, "online", atmosphere.callbacks.online);
3506 | };
3507 |
3508 | atmosphere.unbindEvents = function () {
3509 | atmosphere.util.off(window, "unload", atmosphere.callbacks.unload);
3510 | atmosphere.util.off(window, "beforeunload", atmosphere.callbacks.beforeUnload);
3511 | atmosphere.util.off(window, "offline", atmosphere.callbacks.offline);
3512 | atmosphere.util.off(window, "online", atmosphere.callbacks.online);
3513 | };
3514 |
3515 | atmosphere.bindEvents();
3516 |
3517 | return atmosphere;
3518 | }));
3519 | /* jshint eqnull:true, noarg:true, noempty:true, eqeqeq:true, evil:true, laxbreak:true, undef:true, browser:true, indent:false, maxerr:50 */
3520 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | org.sonatype.oss
5 | oss-parent
6 | 9
7 |
8 | 4.0.0
9 | org.atmosphere
10 | javascript-project
11 | javascript-project
12 | 4.0.2-SNAPSHOT
13 | pom
14 |
15 | Javascript clients for Atmosphere
16 |
17 | http://github.com/Atmosphere/atmosphere-javascript
18 |
19 | scm:git:git@github.com:Atmosphere/atmosphere-javascript.git
20 | scm:git:git@github.com:Atmosphere/atmosphere-javascript.git
21 | http://github.com/Atmosphere/atmosphere-javascript
22 | HEAD
23 |
24 |
25 | 2.2.0
26 |
27 |
28 |
29 | jfarcand
30 | Jeanfrancois Arcand
31 | jfarcand@apache.org
32 |
33 |
34 |
35 |
36 | Apache License 2.0
37 | http://www.apache.org/licenses/LICENSE-2.0.html
38 | repo
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 | org.apache.maven.wagon
50 | wagon-ssh-external
51 | 3.4.0
52 |
53 |
54 | install
55 |
56 |
57 | org.apache.maven.plugins
58 | maven-resources-plugin
59 | 2.4.3
60 |
61 | UTF-8
62 |
63 |
64 |
65 | org.apache.maven.plugins
66 | maven-release-plugin
67 | 3.0.0-M1
68 |
69 |
70 | maven-clean-plugin
71 | 2.4
72 |
73 |
74 |
75 | ${project.basedir}/META-INF
76 |
77 |
78 | ${project.basedir}/works
79 |
80 |
81 | ${project.basedir}/overlays
82 |
83 |
84 | ${project.basedir}
85 |
86 | *.log
87 |
88 |
89 |
90 |
91 |
92 |
93 | org.apache.maven.plugins
94 | maven-source-plugin
95 | 2.1.2
96 |
97 |
98 | attach-sources
99 | verify
100 |
101 | jar-no-fork
102 |
103 |
104 |
105 |
106 |
107 | org.apache.maven.plugins
108 | maven-jxr-plugin
109 | 2.3
110 |
111 | true
112 |
113 |
114 |
115 |
116 |
117 | modules/javascript
118 |
119 |
120 | target/site
121 |
122 |
123 | org.apache.maven.plugins
124 | maven-javadoc-plugin
125 |
126 | true
127 | true
128 |
129 | http://docs.oracle.com/javaee/6/docs/api/
130 | http://docs.oracle.com/javase/6/docs/api/
131 |
132 |
133 |
134 |
135 | org.apache.maven.plugins
136 | maven-jxr-plugin
137 | 2.3
138 |
139 | true
140 |
141 |
142 |
143 | org.apache.maven.plugins
144 | maven-project-info-reports-plugin
145 |
146 |
147 |
148 | project-team
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 | https://oss.sonatype.org/content/repositories/snapshots
157 | false
158 |
159 |
160 |
161 |
162 |
163 |
--------------------------------------------------------------------------------