ola_api4.js 139 KB

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