1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499 |
- /*
- * Copyright 2015 Async-IO.org
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- /**
- * Atmosphere.js
- * https://github.com/Atmosphere/atmosphere-javascript
- *
- * API reference
- * https://github.com/Atmosphere/atmosphere/wiki/jQuery.atmosphere.js-API
- *
- * Highly inspired by
- * - Portal by Donghwan Kim http://flowersinthesand.github.io/portal/
- */
- (function (root, factory) {
- if (typeof define === "function" && define.amd) {
- // AMD
- define(factory);
- } else if(typeof exports !== 'undefined') {
- // CommonJS
- module.exports = factory();
- } else {
- // Browser globals, Window
- root.atmosphere = factory();
- }
- }(this, function () {
- "use strict";
- var atmosphere = {},
- guid,
- offline = false,
- requests = [],
- callbacks = [],
- uuid = 0,
- hasOwn = Object.prototype.hasOwnProperty;
- atmosphere = {
- version: "2.3.3-javascript",
- onError: function (response) {
- },
- onClose: function (response) {
- },
- onOpen: function (response) {
- },
- onReopen: function (response) {
- },
- onMessage: function (response) {
- },
- onReconnect: function (request, response) {
- },
- onMessagePublished: function (response) {
- },
- onTransportFailure: function (errorMessage, _request) {
- },
- onLocalMessage: function (response) {
- },
- onFailureToReconnect: function (request, response) {
- },
- onClientTimeout: function (request) {
- },
- onOpenAfterResume: function (request) {
- },
- /**
- * Creates an object based on an atmosphere subscription that exposes functions defined by the Websocket interface.
- *
- * @class WebsocketApiAdapter
- * @param {Object} request the request object to build the underlying subscription
- * @constructor
- */
- WebsocketApiAdapter: function (request) {
- var _socket, _adapter;
- /**
- * Overrides the onMessage callback in given request.
- *
- * @method onMessage
- * @param {Object} e the event object
- */
- request.onMessage = function (e) {
- _adapter.onmessage({data: e.responseBody});
- };
- /**
- * Overrides the onMessagePublished callback in given request.
- *
- * @method onMessagePublished
- * @param {Object} e the event object
- */
- request.onMessagePublished = function (e) {
- _adapter.onmessage({data: e.responseBody});
- };
- /**
- * Overrides the onOpen callback in given request to proxy the event to the adapter.
- *
- * @method onOpen
- * @param {Object} e the event object
- */
- request.onOpen = function (e) {
- _adapter.onopen(e);
- };
- _adapter = {
- close: function () {
- _socket.close();
- },
- send: function (data) {
- _socket.push(data);
- },
- onmessage: function (e) {
- },
- onopen: function (e) {
- },
- onclose: function (e) {
- },
- onerror: function (e) {
- }
- };
- _socket = new atmosphere.subscribe(request);
- return _adapter;
- },
- AtmosphereRequest: function (options) {
- /**
- * {Object} Request parameters.
- *
- * @private
- */
- var _request = {
- timeout: 300000,
- method: 'GET',
- headers: {},
- contentType: '',
- callback: null,
- url: '',
- data: '',
- suspend: true,
- maxRequest: -1,
- reconnect: true,
- maxStreamingLength: 10000000,
- lastIndex: 0,
- logLevel: 'info',
- requestCount: 0,
- fallbackMethod: 'GET',
- fallbackTransport: 'streaming',
- transport: 'long-polling',
- webSocketImpl: null,
- webSocketBinaryType: null,
- dispatchUrl: null,
- webSocketPathDelimiter: "@@",
- enableXDR: false,
- rewriteURL: false,
- attachHeadersAsQueryString: true,
- executeCallbackBeforeReconnect: false,
- readyState: 0,
- withCredentials: false,
- trackMessageLength: false,
- messageDelimiter: '|',
- connectTimeout: -1,
- reconnectInterval: 0,
- dropHeaders: true,
- uuid: 0,
- async: true,
- shared: false,
- readResponsesHeaders: false,
- maxReconnectOnClose: 5,
- enableProtocol: true,
- disableDisconnect: false,
- pollingInterval: 0,
- heartbeat: {
- client: null,
- server: null
- },
- ackInterval: 0,
- closeAsync: false,
- reconnectOnServerError: true,
- handleOnlineOffline: true,
- onError: function (response) {
- },
- onClose: function (response) {
- },
- onOpen: function (response) {
- },
- onMessage: function (response) {
- },
- onReopen: function (request, response) {
- },
- onReconnect: function (request, response) {
- },
- onMessagePublished: function (response) {
- },
- onTransportFailure: function (reason, request) {
- },
- onLocalMessage: function (request) {
- },
- onFailureToReconnect: function (request, response) {
- },
- onClientTimeout: function (request) {
- },
- onOpenAfterResume: function (request) {
- }
- };
- /**
- * {Object} Request's last response.
- *
- * @private
- */
- var _response = {
- status: 200,
- reasonPhrase: "OK",
- responseBody: '',
- messages: [],
- headers: [],
- state: "messageReceived",
- transport: "polling",
- error: null,
- request: null,
- partialMessage: "",
- errorHandled: false,
- closedByClientTimeout: false,
- ffTryingReconnect: false
- };
- /**
- * {websocket} Opened web socket.
- *
- * @private
- */
- var _websocket = null;
- /**
- * {SSE} Opened SSE.
- *
- * @private
- */
- var _sse = null;
- /**
- * {XMLHttpRequest, ActiveXObject} Opened ajax request (in case of http-streaming or long-polling)
- *
- * @private
- */
- var _activeRequest = null;
- /**
- * {Object} Object use for streaming with IE.
- *
- * @private
- */
- var _ieStream = null;
- /**
- * {Object} Object use for jsonp transport.
- *
- * @private
- */
- var _jqxhr = null;
- /**
- * {boolean} If request has been subscribed or not.
- *
- * @private
- */
- var _subscribed = true;
- /**
- * {number} Number of test reconnection.
- *
- * @private
- */
- var _requestCount = 0;
- /**
- * The Heartbeat interval send by the server.
- * @type {int}
- * @private
- */
- var _heartbeatInterval = 0;
- /**
- * The Heartbeat bytes send by the server.
- * @type {string}
- * @private
- */
- var _heartbeatPadding = 'X';
- /**
- * {boolean} If request is currently aborted.
- *
- * @private
- */
- var _abortingConnection = false;
- /**
- * A local "channel' of communication.
- *
- * @private
- */
- var _localSocketF = null;
- /**
- * The storage used.
- *
- * @private
- */
- var _storageService;
- /**
- * Local communication
- *
- * @private
- */
- var _localStorageService = null;
- /**
- * A Unique ID
- *
- * @private
- */
- var guid = atmosphere.util.now();
- /** Trace time */
- var _traceTimer;
- /** Key for connection sharing */
- var _sharingKey;
- /**
- * {boolean} If window beforeUnload event has been called.
- * Flag will be reset after 5000 ms
- *
- * @private
- */
- var _beforeUnloadState = false;
- // Automatic call to subscribe
- _subscribe(options);
- /**
- * Initialize atmosphere request object.
- *
- * @private
- */
- function _init() {
- _subscribed = true;
- _abortingConnection = false;
- _requestCount = 0;
- _websocket = null;
- _sse = null;
- _activeRequest = null;
- _ieStream = null;
- }
- /**
- * Re-initialize atmosphere object.
- *
- * @private
- */
- function _reinit() {
- _clearState();
- _init();
- }
- /**
- * Returns true if the given level is equal or above the configured log level.
- *
- * @private
- */
- function _canLog(level) {
- if (level == 'debug') {
- return _request.logLevel === 'debug';
- } else if (level == 'info') {
- return _request.logLevel === 'info' || _request.logLevel === 'debug';
- } else if (level == 'warn') {
- return _request.logLevel === 'warn' || _request.logLevel === 'info' || _request.logLevel === 'debug';
- } else if (level == 'error') {
- return _request.logLevel === 'error' || _request.logLevel === 'warn' || _request.logLevel === 'info' || _request.logLevel === 'debug';
- } else {
- return false;
- }
- }
- function _debug(msg) {
- if (_canLog('debug')) {
- atmosphere.util.debug(new Date() + " Atmosphere: " + msg);
- }
- }
- /**
- *
- * @private
- */
- function _verifyStreamingLength(ajaxRequest, rq) {
- // Wait to be sure we have the full message before closing.
- if (_response.partialMessage === "" && (rq.transport === 'streaming') && (ajaxRequest.responseText.length > rq.maxStreamingLength)) {
- return true;
- }
- return false;
- }
- /**
- * Disconnect
- *
- * @private
- */
- function _disconnect() {
- if (_request.enableProtocol && !_request.disableDisconnect && !_request.firstMessage) {
- var query = "X-Atmosphere-Transport=close&X-Atmosphere-tracking-id=" + _request.uuid;
- atmosphere.util.each(_request.headers, function (name, value) {
- var h = atmosphere.util.isFunction(value) ? value.call(this, _request, _request, _response) : value;
- if (h != null) {
- query += "&" + encodeURIComponent(name) + "=" + encodeURIComponent(h);
- }
- });
- var url = _request.url.replace(/([?&])_=[^&]*/, query);
- url = url + (url === _request.url ? (/\?/.test(_request.url) ? "&" : "?") + query : "");
- var rq = {
- connected: false
- };
- var closeR = new atmosphere.AtmosphereRequest(rq);
- closeR.connectTimeout = _request.connectTimeout;
- closeR.attachHeadersAsQueryString = false;
- closeR.dropHeaders = true;
- closeR.url = url;
- closeR.contentType = "text/plain";
- closeR.transport = 'polling';
- closeR.method = 'GET';
- closeR.data = '';
- closeR.heartbeat = null;
- if (_request.enableXDR) {
- closeR.enableXDR = _request.enableXDR
- }
- closeR.async = _request.closeAsync;
- _pushOnClose("", closeR);
- }
- }
- /**
- * Close request.
- *
- * @private
- */
- function _close() {
- _debug("Closing (AtmosphereRequest._close() called)");
- _abortingConnection = true;
- if (_request.reconnectId) {
- clearTimeout(_request.reconnectId);
- delete _request.reconnectId;
- }
- if (_request.heartbeatTimer) {
- clearTimeout(_request.heartbeatTimer);
- }
- _request.reconnect = false;
- _response.request = _request;
- _response.state = 'unsubscribe';
- _response.responseBody = "";
- _response.status = 408;
- _response.partialMessage = "";
- _invokeCallback();
- _disconnect();
- _clearState();
- }
- function _clearState() {
- _response.partialMessage = "";
- if (_request.id) {
- clearTimeout(_request.id);
- }
- if (_request.heartbeatTimer) {
- clearTimeout(_request.heartbeatTimer);
- }
- // https://github.com/Atmosphere/atmosphere/issues/1860#issuecomment-74707226
- if(_request.reconnectId) {
- clearTimeout(_request.reconnectId);
- delete _request.reconnectId;
- }
- if (_ieStream != null) {
- _ieStream.close();
- _ieStream = null;
- }
- if (_jqxhr != null) {
- _jqxhr.abort();
- _jqxhr = null;
- }
- if (_activeRequest != null) {
- _activeRequest.abort();
- _activeRequest = null;
- }
- if (_websocket != null) {
- if (_websocket.canSendMessage) {
- _debug("invoking .close() on WebSocket object");
- _websocket.close();
- }
- _websocket = null;
- }
- if (_sse != null) {
- _sse.close();
- _sse = null;
- }
- _clearStorage();
- }
- function _clearStorage() {
- // Stop sharing a connection
- if (_storageService != null) {
- // Clears trace timer
- clearInterval(_traceTimer);
- // Removes the trace
- document.cookie = _sharingKey + "=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/";
- // The heir is the parent unless unloading
- _storageService.signal("close", {
- reason: "",
- heir: !_abortingConnection ? guid : (_storageService.get("children") || [])[0]
- });
- _storageService.close();
- }
- if (_localStorageService != null) {
- _localStorageService.close();
- }
- }
- /**
- * Subscribe request using request transport. <br>
- * If request is currently opened, this one will be closed.
- *
- * @param {Object} Request parameters.
- * @private
- */
- function _subscribe(options) {
- _reinit();
- _request = atmosphere.util.extend(_request, options);
- // Allow at least 1 request
- _request.mrequest = _request.reconnect;
- if (!_request.reconnect) {
- _request.reconnect = true;
- }
- }
- /**
- * Check if web socket is supported (check for custom implementation provided by request object or browser implementation).
- *
- * @returns {boolean} True if web socket is supported, false otherwise.
- * @private
- */
- function _supportWebsocket() {
- return _request.webSocketImpl != null || window.WebSocket || window.MozWebSocket;
- }
- /**
- * Check if server side events (SSE) is supported (check for custom implementation provided by request object or browser implementation).
- *
- * @returns {boolean} True if web socket is supported, false otherwise.
- * @private
- */
- function _supportSSE() {
- // Origin parts
- var url = atmosphere.util.getAbsoluteURL(_request.url.toLowerCase());
- var parts = /^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/.exec(url);
- var crossOrigin = !!(parts && (
- // protocol
- parts[1] != window.location.protocol ||
- // hostname
- parts[2] != window.location.hostname ||
- // port
- (parts[3] || (parts[1] === "http:" ? 80 : 443)) != (window.location.port || (window.location.protocol === "http:" ? 80 : 443))
- ));
- return window.EventSource && (!crossOrigin || !atmosphere.util.browser.safari || atmosphere.util.browser.vmajor >= 7);
- }
- /**
- * Open request using request transport. <br>
- * If request transport is 'websocket' but websocket can't be opened, request will automatically reconnect using fallback transport.
- *
- * @private
- */
- function _execute() {
- // Shared across multiple tabs/windows.
- if (_request.shared) {
- _localStorageService = _local(_request);
- if (_localStorageService != null) {
- if (_canLog('debug')) {
- atmosphere.util.debug("Storage service available. All communication will be local");
- }
- if (_localStorageService.open(_request)) {
- // Local connection.
- return;
- }
- }
- if (_canLog('debug')) {
- atmosphere.util.debug("No Storage service available.");
- }
- // Wasn't local or an error occurred
- _localStorageService = null;
- }
- // Protocol
- _request.firstMessage = uuid == 0 ? true : false;
- _request.isOpen = false;
- _request.ctime = atmosphere.util.now();
- // We carry any UUID set by the user or from a previous connection.
- if (_request.uuid === 0) {
- _request.uuid = uuid;
- }
- _response.closedByClientTimeout = false;
- if (_request.transport !== 'websocket' && _request.transport !== 'sse') {
- _executeRequest(_request);
- } else if (_request.transport === 'websocket') {
- if (!_supportWebsocket()) {
- _reconnectWithFallbackTransport("Websocket is not supported, using request.fallbackTransport (" + _request.fallbackTransport
- + ")");
- } else {
- _executeWebSocket(false);
- }
- } else if (_request.transport === 'sse') {
- if (!_supportSSE()) {
- _reconnectWithFallbackTransport("Server Side Events(SSE) is not supported, using request.fallbackTransport ("
- + _request.fallbackTransport + ")");
- } else {
- _executeSSE(false);
- }
- }
- }
- function _local(request) {
- var trace, connector, orphan, name = "atmosphere-" + request.url, connectors = {
- storage: function () {
- function onstorage(event) {
- if (event.key === name && event.newValue) {
- listener(event.newValue);
- }
- }
- if (!atmosphere.util.storage) {
- return;
- }
- var storage = window.localStorage,
- get = function (key) {
- var item = storage.getItem(name + "-" + key);
- return item === null ? [] : atmosphere.util.parseJSON(item);
- },
- set = function (key, value) {
- storage.setItem(name + "-" + key, atmosphere.util.stringifyJSON(value));
- };
- return {
- init: function () {
- set("children", get("children").concat([guid]));
- atmosphere.util.on(window, "storage", onstorage);
- return get("opened");
- },
- signal: function (type, data) {
- storage.setItem(name, atmosphere.util.stringifyJSON({
- target: "p",
- type: type,
- data: data
- }));
- },
- close: function () {
- var children = get("children");
- atmosphere.util.off(window, "storage", onstorage);
- if (children) {
- if (removeFromArray(children, request.id)) {
- set("children", children);
- }
- }
- }
- };
- },
- windowref: function () {
- var win = window.open("", name.replace(/\W/g, ""));
- if (!win || win.closed || !win.callbacks) {
- return;
- }
- return {
- init: function () {
- win.callbacks.push(listener);
- win.children.push(guid);
- return win.opened;
- },
- signal: function (type, data) {
- if (!win.closed && win.fire) {
- win.fire(atmosphere.util.stringifyJSON({
- target: "p",
- type: type,
- data: data
- }));
- }
- },
- close: function () {
- // Removes traces only if the parent is alive
- if (!orphan) {
- removeFromArray(win.callbacks, listener);
- removeFromArray(win.children, guid);
- }
- }
- };
- }
- };
- function removeFromArray(array, val) {
- var i, length = array.length;
- for (i = 0; i < length; i++) {
- if (array[i] === val) {
- array.splice(i, 1);
- }
- }
- return length !== array.length;
- }
- // Receives open, close and message command from the parent
- function listener(string) {
- var command = atmosphere.util.parseJSON(string), data = command.data;
- if (command.target === "c") {
- switch (command.type) {
- case "open":
- _open("opening", 'local', _request);
- break;
- case "close":
- if (!orphan) {
- orphan = true;
- if (data.reason === "aborted") {
- _close();
- } else {
- // Gives the heir some time to reconnect
- if (data.heir === guid) {
- _execute();
- } else {
- setTimeout(function () {
- _execute();
- }, 100);
- }
- }
- }
- break;
- case "message":
- _prepareCallback(data, "messageReceived", 200, request.transport);
- break;
- case "localMessage":
- _localMessage(data);
- break;
- }
- }
- }
- function findTrace() {
- var matcher = new RegExp("(?:^|; )(" + encodeURIComponent(name) + ")=([^;]*)").exec(document.cookie);
- if (matcher) {
- return atmosphere.util.parseJSON(decodeURIComponent(matcher[2]));
- }
- }
- // Finds and validates the parent socket's trace from the cookie
- trace = findTrace();
- if (!trace || atmosphere.util.now() - trace.ts > 1000) {
- return;
- }
- // Chooses a connector
- connector = connectors.storage() || connectors.windowref();
- if (!connector) {
- return;
- }
- return {
- open: function () {
- var parentOpened;
- // Checks the shared one is alive
- _traceTimer = setInterval(function () {
- var oldTrace = trace;
- trace = findTrace();
- if (!trace || oldTrace.ts === trace.ts) {
- // Simulates a close signal
- listener(atmosphere.util.stringifyJSON({
- target: "c",
- type: "close",
- data: {
- reason: "error",
- heir: oldTrace.heir
- }
- }));
- }
- }, 1000);
- parentOpened = connector.init();
- if (parentOpened) {
- // Firing the open event without delay robs the user of the opportunity to bind connecting event handlers
- setTimeout(function () {
- _open("opening", 'local', request);
- }, 50);
- }
- return parentOpened;
- },
- send: function (event) {
- connector.signal("send", event);
- },
- localSend: function (event) {
- connector.signal("localSend", atmosphere.util.stringifyJSON({
- id: guid,
- event: event
- }));
- },
- close: function () {
- // Do not signal the parent if this method is executed by the unload event handler
- if (!_abortingConnection) {
- clearInterval(_traceTimer);
- connector.signal("close");
- connector.close();
- }
- }
- };
- }
- function share() {
- var storageService, name = "atmosphere-" + _request.url, servers = {
- // Powered by the storage event and the localStorage
- // http://www.w3.org/TR/webstorage/#event-storage
- storage: function () {
- function onstorage(event) {
- // When a deletion, newValue initialized to null
- if (event.key === name && event.newValue) {
- listener(event.newValue);
- }
- }
- if (!atmosphere.util.storage) {
- return;
- }
- var storage = window.localStorage;
- return {
- init: function () {
- // Handles the storage event
- atmosphere.util.on(window, "storage", onstorage);
- },
- signal: function (type, data) {
- storage.setItem(name, atmosphere.util.stringifyJSON({
- target: "c",
- type: type,
- data: data
- }));
- },
- get: function (key) {
- return atmosphere.util.parseJSON(storage.getItem(name + "-" + key));
- },
- set: function (key, value) {
- storage.setItem(name + "-" + key, atmosphere.util.stringifyJSON(value));
- },
- close: function () {
- atmosphere.util.off(window, "storage", onstorage);
- storage.removeItem(name);
- storage.removeItem(name + "-opened");
- storage.removeItem(name + "-children");
- }
- };
- },
- // Powered by the window.open method
- // https://developer.mozilla.org/en/DOM/window.open
- windowref: function () {
- // Internet Explorer raises an invalid argument error
- // when calling the window.open method with the name containing non-word characters
- var neim = name.replace(/\W/g, ""), container = document.getElementById(neim), win;
- if (!container) {
- container = document.createElement("div");
- container.id = neim;
- container.style.display = "none";
- container.innerHTML = '<iframe name="' + neim + '" />';
- document.body.appendChild(container);
- }
- win = container.firstChild.contentWindow;
- return {
- init: function () {
- // Callbacks from different windows
- win.callbacks = [listener];
- // In IE 8 and less, only string argument can be safely passed to the function in other window
- win.fire = function (string) {
- var i;
- for (i = 0; i < win.callbacks.length; i++) {
- win.callbacks[i](string);
- }
- };
- },
- signal: function (type, data) {
- if (!win.closed && win.fire) {
- win.fire(atmosphere.util.stringifyJSON({
- target: "c",
- type: type,
- data: data
- }));
- }
- },
- get: function (key) {
- return !win.closed ? win[key] : null;
- },
- set: function (key, value) {
- if (!win.closed) {
- win[key] = value;
- }
- },
- close: function () {
- }
- };
- }
- };
- // Receives send and close command from the children
- function listener(string) {
- var command = atmosphere.util.parseJSON(string), data = command.data;
- if (command.target === "p") {
- switch (command.type) {
- case "send":
- _push(data);
- break;
- case "localSend":
- _localMessage(data);
- break;
- case "close":
- _close();
- break;
- }
- }
- }
- _localSocketF = function propagateMessageEvent(context) {
- storageService.signal("message", context);
- };
- function leaveTrace() {
- document.cookie = _sharingKey + "=" +
- // Opera's JSON implementation ignores a number whose a last digit of 0 strangely
- // but has no problem with a number whose a last digit of 9 + 1
- encodeURIComponent(atmosphere.util.stringifyJSON({
- ts: atmosphere.util.now() + 1,
- heir: (storageService.get("children") || [])[0]
- })) + "; path=/";
- }
- // Chooses a storageService
- storageService = servers.storage() || servers.windowref();
- storageService.init();
- if (_canLog('debug')) {
- atmosphere.util.debug("Installed StorageService " + storageService);
- }
- // List of children sockets
- storageService.set("children", []);
- if (storageService.get("opened") != null && !storageService.get("opened")) {
- // Flag indicating the parent socket is opened
- storageService.set("opened", false);
- }
- // Leaves traces
- _sharingKey = encodeURIComponent(name);
- leaveTrace();
- _traceTimer = setInterval(leaveTrace, 1000);
- _storageService = storageService;
- }
- /**
- * @private
- */
- function _open(state, transport, request) {
- if (_request.shared && transport !== 'local') {
- share();
- }
- if (_storageService != null) {
- _storageService.set("opened", true);
- }
- request.close = function () {
- _close();
- };
- if (_requestCount > 0 && state === 're-connecting') {
- request.isReopen = true;
- _tryingToReconnect(_response);
- } else if (_response.error == null) {
- _response.request = request;
- var prevState = _response.state;
- _response.state = state;
- var prevTransport = _response.transport;
- _response.transport = transport;
- var _body = _response.responseBody;
- _invokeCallback();
- _response.responseBody = _body;
- _response.state = prevState;
- _response.transport = prevTransport;
- }
- }
- /**
- * Execute request using jsonp transport.
- *
- * @param request {Object} request Request parameters, if undefined _request object will be used.
- * @private
- */
- function _jsonp(request) {
- // When CORS is enabled, make sure we force the proper transport.
- request.transport = "jsonp";
- var rq = _request, script;
- if ((request != null) && (typeof (request) !== 'undefined')) {
- rq = request;
- }
- _jqxhr = {
- open: function () {
- var callback = "atmosphere" + (++guid);
- function _reconnectOnFailure() {
- rq.lastIndex = 0;
- if (rq.openId) {
- clearTimeout(rq.openId);
- }
- if (rq.heartbeatTimer) {
- clearTimeout(rq.heartbeatTimer);
- }
- if (rq.reconnect && _requestCount++ < rq.maxReconnectOnClose) {
- _open('re-connecting', rq.transport, rq);
- _reconnect(_jqxhr, rq, request.reconnectInterval);
- rq.openId = setTimeout(function () {
- _triggerOpen(rq);
- }, rq.reconnectInterval + 1000);
- } else {
- _onError(0, "maxReconnectOnClose reached");
- }
- }
- function poll() {
- var url = rq.url;
- if (rq.dispatchUrl != null) {
- url += rq.dispatchUrl;
- }
- var data = rq.data;
- if (rq.attachHeadersAsQueryString) {
- url = _attachHeaders(rq);
- if (data !== '') {
- url += "&X-Atmosphere-Post-Body=" + encodeURIComponent(data);
- }
- data = '';
- }
- var head = document.head || document.getElementsByTagName("head")[0] || document.documentElement;
- script = document.createElement("script");
- script.src = url + "&jsonpTransport=" + callback;
- //script.async = rq.async;
- script.clean = function () {
- script.clean = script.onerror = script.onload = script.onreadystatechange = null;
- if (script.parentNode) {
- script.parentNode.removeChild(script);
- }
- if (++request.scriptCount === 2) {
- request.scriptCount = 1;
- _reconnectOnFailure();
- }
- };
- script.onload = script.onreadystatechange = function () {
- _debug("jsonp.onload");
- if (!script.readyState || /loaded|complete/.test(script.readyState)) {
- script.clean();
- }
- };
- script.onerror = function () {
- _debug("jsonp.onerror");
- request.scriptCount = 1;
- script.clean();
- };
- head.insertBefore(script, head.firstChild);
- }
- // Attaches callback
- window[callback] = function (msg) {
- _debug("jsonp.window");
- request.scriptCount = 0;
- if (rq.reconnect && rq.maxRequest === -1 || rq.requestCount++ < rq.maxRequest) {
- // _readHeaders(_jqxhr, rq);
- if (!rq.executeCallbackBeforeReconnect) {
- _reconnect(_jqxhr, rq, rq.pollingInterval);
- }
- if (msg != null && typeof msg !== 'string') {
- try {
- msg = msg.message;
- } catch (err) {
- // The message was partial
- }
- }
- var skipCallbackInvocation = _trackMessageSize(msg, rq, _response);
- if (!skipCallbackInvocation) {
- _prepareCallback(_response.responseBody, "messageReceived", 200, rq.transport);
- }
- if (rq.executeCallbackBeforeReconnect) {
- _reconnect(_jqxhr, rq, rq.pollingInterval);
- }
- _timeout(rq);
- } else {
- atmosphere.util.log(_request.logLevel, ["JSONP reconnect maximum try reached " + _request.requestCount]);
- _onError(0, "maxRequest reached");
- }
- };
- setTimeout(function () {
- poll();
- }, 50);
- },
- abort: function () {
- if (script && script.clean) {
- script.clean();
- }
- }
- };
- _jqxhr.open();
- }
- /**
- * Build websocket object.
- *
- * @param location {string} Web socket url.
- * @returns {websocket} Web socket object.
- * @private
- */
- function _getWebSocket(location) {
- if (_request.webSocketImpl != null) {
- return _request.webSocketImpl;
- } else {
- if (window.WebSocket) {
- return new WebSocket(location);
- } else {
- return new MozWebSocket(location);
- }
- }
- }
- /**
- * Build web socket url from request url.
- *
- * @return {string} Web socket url (start with "ws" or "wss" for secure web socket).
- * @private
- */
- function _buildWebSocketUrl() {
- return _attachHeaders(_request, atmosphere.util.getAbsoluteURL(_request.webSocketUrl || _request.url)).replace(/^http/, "ws");
- }
- /**
- * Build SSE url from request url.
- *
- * @return a url with Atmosphere's headers
- * @private
- */
- function _buildSSEUrl() {
- var url = _attachHeaders(_request);
- return url;
- }
- /**
- * Open SSE. <br>
- * Automatically use fallback transport if SSE can't be opened.
- *
- * @private
- */
- function _executeSSE(sseOpened) {
- _response.transport = "sse";
- var location = _buildSSEUrl();
- if (_canLog('debug')) {
- atmosphere.util.debug("Invoking executeSSE");
- atmosphere.util.debug("Using URL: " + location);
- }
- if (sseOpened && !_request.reconnect) {
- if (_sse != null) {
- _clearState();
- }
- return;
- }
- try {
- _sse = new EventSource(location, {
- withCredentials: _request.withCredentials
- });
- } catch (e) {
- _onError(0, e);
- _reconnectWithFallbackTransport("SSE failed. Downgrading to fallback transport and resending");
- return;
- }
- if (_request.connectTimeout > 0) {
- _request.id = setTimeout(function () {
- if (!sseOpened) {
- _clearState();
- }
- }, _request.connectTimeout);
- }
- _sse.onopen = function (event) {
- _debug("sse.onopen");
- _timeout(_request);
- if (_canLog('debug')) {
- atmosphere.util.debug("SSE successfully opened");
- }
- if (!_request.enableProtocol) {
- if (!sseOpened) {
- _open('opening', "sse", _request);
- } else {
- _open('re-opening', "sse", _request);
- }
- } else if (_request.isReopen) {
- _request.isReopen = false;
- _open('re-opening', _request.transport, _request);
- }
- sseOpened = true;
- if (_request.method === 'POST') {
- _response.state = "messageReceived";
- _sse.send(_request.data);
- }
- };
- _sse.onmessage = function (message) {
- _debug("sse.onmessage");
- _timeout(_request);
- if (!_request.enableXDR && window.location.host && message.origin && message.origin !== window.location.protocol + "//" + window.location.host) {
- atmosphere.util.log(_request.logLevel, ["Origin was not " + window.location.protocol + "//" + window.location.host]);
- return;
- }
- _response.state = 'messageReceived';
- _response.status = 200;
- message = message.data;
- var skipCallbackInvocation = _trackMessageSize(message, _request, _response);
- // https://github.com/remy/polyfills/blob/master/EventSource.js
- // Since we polling.
- /* if (_sse.URL) {
- _sse.interval = 100;
- _sse.URL = _buildSSEUrl();
- } */
- if (!skipCallbackInvocation) {
- _invokeCallback();
- _response.responseBody = '';
- _response.messages = [];
- }
- };
- _sse.onerror = function (message) {
- _debug("sse.onerror");
- clearTimeout(_request.id);
- if (_request.heartbeatTimer) {
- clearTimeout(_request.heartbeatTimer);
- }
- if (_response.closedByClientTimeout) {
- return;
- }
- _invokeClose(sseOpened);
- _clearState();
- if (_abortingConnection) {
- atmosphere.util.log(_request.logLevel, ["SSE closed normally"]);
- } else if (!sseOpened) {
- _reconnectWithFallbackTransport("SSE failed. Downgrading to fallback transport and resending");
- } else if (_request.reconnect && (_response.transport === 'sse')) {
- if (_requestCount++ < _request.maxReconnectOnClose) {
- _open('re-connecting', _request.transport, _request);
- if (_request.reconnectInterval > 0) {
- _request.reconnectId = setTimeout(function () {
- _executeSSE(true);
- }, _request.reconnectInterval);
- } else {
- _executeSSE(true);
- }
- _response.responseBody = "";
- _response.messages = [];
- } else {
- atmosphere.util.log(_request.logLevel, ["SSE reconnect maximum try reached " + _requestCount]);
- _onError(0, "maxReconnectOnClose reached");
- }
- }
- };
- }
- /**
- * Open web socket. <br>
- * Automatically use fallback transport if web socket can't be opened.
- *
- * @private
- */
- function _executeWebSocket(webSocketOpened) {
- _response.transport = "websocket";
- var location = _buildWebSocketUrl(_request.url);
- if (_canLog('debug')) {
- atmosphere.util.debug("Invoking executeWebSocket, using URL: " + location);
- }
- if (webSocketOpened && !_request.reconnect) {
- if (_websocket != null) {
- _clearState();
- }
- return;
- }
- _websocket = _getWebSocket(location);
- if (_request.webSocketBinaryType != null) {
- _websocket.binaryType = _request.webSocketBinaryType;
- }
- if (_request.connectTimeout > 0) {
- _request.id = setTimeout(function () {
- if (!webSocketOpened) {
- var _message = {
- code: 1002,
- reason: "",
- wasClean: false
- };
- _websocket.onclose(_message);
- // Close it anyway
- try {
- _clearState();
- } catch (e) {
- }
- return;
- }
- }, _request.connectTimeout);
- }
- _websocket.onopen = function (message) {
- _debug("websocket.onopen");
- _timeout(_request);
- offline = false;
- if (_canLog('debug')) {
- atmosphere.util.debug("Websocket successfully opened");
- }
- var reopening = webSocketOpened;
- if (_websocket != null) {
- _websocket.canSendMessage = true;
- }
- if (!_request.enableProtocol) {
- webSocketOpened = true;
- if (reopening) {
- _open('re-opening', "websocket", _request);
- } else {
- _open('opening', "websocket", _request);
- }
- }
- if (_websocket != null) {
- if (_request.method === 'POST') {
- _response.state = "messageReceived";
- _websocket.send(_request.data);
- }
- }
- };
- _websocket.onmessage = function (message) {
- _debug("websocket.onmessage");
- _timeout(_request);
- // We only consider it opened if we get the handshake data
- // https://github.com/Atmosphere/atmosphere-javascript/issues/74
- if (_request.enableProtocol) {
- webSocketOpened = true;
- }
- _response.state = 'messageReceived';
- _response.status = 200;
- message = message.data;
- var isString = typeof (message) === 'string';
- if (isString) {
- var skipCallbackInvocation = _trackMessageSize(message, _request, _response);
- if (!skipCallbackInvocation) {
- _invokeCallback();
- _response.responseBody = '';
- _response.messages = [];
- }
- } else {
- message = _handleProtocol(_request, message);
- if (message === "")
- return;
- _response.responseBody = message;
- _invokeCallback();
- _response.responseBody = null;
- }
- };
- _websocket.onerror = function (message) {
- _debug("websocket.onerror");
- clearTimeout(_request.id);
- if (_request.heartbeatTimer) {
- clearTimeout(_request.heartbeatTimer);
- }
- };
- _websocket.onclose = function (message) {
- _debug("websocket.onclose");
- clearTimeout(_request.id);
- if (_response.state === 'closed')
- return;
- var reason = message.reason;
- if (reason === "") {
- switch (message.code) {
- case 1000:
- reason = "Normal closure; the connection successfully completed whatever purpose for which it was created.";
- break;
- case 1001:
- reason = "The endpoint is going away, either because of a server failure or because the "
- + "browser is navigating away from the page that opened the connection.";
- break;
- case 1002:
- reason = "The endpoint is terminating the connection due to a protocol error.";
- break;
- case 1003:
- reason = "The connection is being terminated because the endpoint received data of a type it "
- + "cannot accept (for example, a text-only endpoint received binary data).";
- break;
- case 1004:
- reason = "The endpoint is terminating the connection because a data frame was received that is too large.";
- break;
- case 1005:
- reason = "Unknown: no status code was provided even though one was expected.";
- break;
- case 1006:
- reason = "Connection was closed abnormally (that is, with no close frame being sent).";
- break;
- }
- }
- if (_canLog('warn')) {
- atmosphere.util.warn("Websocket closed, reason: " + reason + ' - wasClean: ' + message.wasClean);
- }
- if (_response.closedByClientTimeout || (_request.handleOnlineOffline && offline)) {
- // IFF online/offline events are handled and we happen to be offline, we stop all reconnect attempts and
- // resume them in the "online" event (if we get here in that case, something else went wrong as the
- // offline handler should stop any reconnect attempt).
- //
- // On the other hand, if we DO NOT handle online/offline events, we continue as before with reconnecting
- // even if we are offline. Failing to do so would stop all reconnect attemps forever.
- if (_request.reconnectId) {
- clearTimeout(_request.reconnectId);
- delete _request.reconnectId;
- }
- return;
- }
- _invokeClose(webSocketOpened);
- _response.state = 'closed';
- if (_abortingConnection) {
- atmosphere.util.log(_request.logLevel, ["Websocket closed normally"]);
- } else if (!webSocketOpened) {
- _reconnectWithFallbackTransport("Websocket failed on first connection attempt. Downgrading to " + _request.fallbackTransport + " and resending");
- } else if (_request.reconnect && _response.transport === 'websocket' ) {
- _clearState();
- if (_requestCount++ < _request.maxReconnectOnClose) {
- _open('re-connecting', _request.transport, _request);
- if (_request.reconnectInterval > 0) {
- _request.reconnectId = setTimeout(function () {
- _response.responseBody = "";
- _response.messages = [];
- _executeWebSocket(true);
- }, _request.reconnectInterval);
- } else {
- _response.responseBody = "";
- _response.messages = [];
- _executeWebSocket(true);
- }
- } else {
- atmosphere.util.log(_request.logLevel, ["Websocket reconnect maximum try reached " + _requestCount]);
- if (_canLog('warn')) {
- atmosphere.util.warn("Websocket error, reason: " + message.reason);
- }
- _onError(0, "maxReconnectOnClose reached");
- }
- }
- };
- var ua = navigator.userAgent.toLowerCase();
- var isAndroid = ua.indexOf("android") > -1;
- if (isAndroid && _websocket.url === undefined) {
- // Android 4.1 does not really support websockets and fails silently
- _websocket.onclose({
- reason: "Android 4.1 does not support websockets.",
- wasClean: false
- });
- }
- }
- function _handleProtocol(request, message) {
- var nMessage = message;
- if (request.transport === 'polling') return nMessage;
- if (request.enableProtocol && request.firstMessage && atmosphere.util.trim(message).length !== 0) {
- var pos = request.trackMessageLength ? 1 : 0;
- var messages = message.split(request.messageDelimiter);
- if (messages.length <= pos + 1) {
- // Something went wrong, normally with IE or when a message is written before the
- // handshake has been received.
- return nMessage;
- }
- request.firstMessage = false;
- request.uuid = atmosphere.util.trim(messages[pos]);
- if (messages.length <= pos + 2) {
- atmosphere.util.log('error', ["Protocol data not sent by the server. " +
- "If you enable protocol on client side, be sure to install JavascriptProtocol interceptor on server side." +
- "Also note that atmosphere-runtime 2.2+ should be used."]);
- }
- _heartbeatInterval = parseInt(atmosphere.util.trim(messages[pos + 1]), 10);
- _heartbeatPadding = messages[pos + 2];
- if (request.transport !== 'long-polling') {
- _triggerOpen(request);
- }
- uuid = request.uuid;
- nMessage = "";
- // We have trailing messages
- pos = request.trackMessageLength ? 4 : 3;
- if (messages.length > pos + 1) {
- for (var i = pos; i < messages.length; i++) {
- nMessage += messages[i];
- if (i + 1 !== messages.length) {
- nMessage += request.messageDelimiter;
- }
- }
- }
- if (request.ackInterval !== 0) {
- setTimeout(function () {
- _push("...ACK...");
- }, request.ackInterval);
- }
- } else if (request.enableProtocol && request.firstMessage && atmosphere.util.browser.msie && +atmosphere.util.browser.version.split(".")[0] < 10) {
- // In case we are getting some junk from IE
- atmosphere.util.log(_request.logLevel, ["Receiving unexpected data from IE"]);
- } else {
- _triggerOpen(request);
- }
- return nMessage;
- }
- function _timeout(_request) {
- clearTimeout(_request.id);
- if (_request.timeout > 0 && _request.transport !== 'polling') {
- _request.id = setTimeout(function () {
- _onClientTimeout(_request);
- _disconnect();
- _clearState();
- }, _request.timeout);
- }
- }
- function _onClientTimeout(_request) {
- _response.closedByClientTimeout = true;
- _response.state = 'closedByClient';
- _response.responseBody = "";
- _response.status = 408;
- _response.messages = [];
- _invokeCallback();
- }
- function _onError(code, reason) {
- _clearState();
- clearTimeout(_request.id);
- _response.state = 'error';
- _response.reasonPhrase = reason;
- _response.responseBody = "";
- _response.status = code;
- _response.messages = [];
- _invokeCallback();
- }
- /**
- * Track received message and make sure callbacks/functions are only invoked when the complete message has been received.
- *
- * @param message
- * @param request
- * @param response
- */
- function _trackMessageSize(message, request, response) {
- message = _handleProtocol(request, message);
- if (message.length === 0)
- return true;
- response.responseBody = message;
- if (request.trackMessageLength) {
- // prepend partialMessage if any
- message = response.partialMessage + message;
- var messages = [];
- var messageStart = message.indexOf(request.messageDelimiter);
- if (messageStart != -1) {
- while (messageStart !== -1) {
- var str = message.substring(0, messageStart);
- var messageLength = +str;
- if (isNaN(messageLength)) {
- // Discard partial message, otherwise it would never recover from this condition
- response.partialMessage = '';
- throw new Error('message length "' + str + '" is not a number');
- }
- messageStart += request.messageDelimiter.length;
- if (messageStart + messageLength > message.length) {
- // message not complete, so there is no trailing messageDelimiter
- messageStart = -1;
- } else {
- // message complete, so add it
- messages.push(message.substring(messageStart, messageStart + messageLength));
- // remove consumed characters
- message = message.substring(messageStart + messageLength, message.length);
- messageStart = message.indexOf(request.messageDelimiter);
- }
- }
- /* keep any remaining data */
- response.partialMessage = message;
- if (messages.length !== 0) {
- response.responseBody = messages.join(request.messageDelimiter);
- response.messages = messages;
- return false;
- } else {
- response.responseBody = "";
- response.messages = [];
- return true;
- }
- }
- }
- response.responseBody = message;
- response.messages = [message];
- return false;
- }
- /**
- * Reconnect request with fallback transport. <br>
- * Used in case websocket can't be opened.
- *
- * @private
- */
- function _reconnectWithFallbackTransport(errorMessage) {
- atmosphere.util.log(_request.logLevel, [errorMessage]);
- if (typeof (_request.onTransportFailure) !== 'undefined') {
- _request.onTransportFailure(errorMessage, _request);
- } else if (typeof (atmosphere.util.onTransportFailure) !== 'undefined') {
- atmosphere.util.onTransportFailure(errorMessage, _request);
- }
- _request.transport = _request.fallbackTransport;
- var reconnectInterval = _request.connectTimeout === -1 ? 0 : _request.connectTimeout;
- if (_request.reconnect && _request.transport !== 'none' || _request.transport == null) {
- _request.method = _request.fallbackMethod;
- _response.transport = _request.fallbackTransport;
- _request.fallbackTransport = 'none';
- if (reconnectInterval > 0) {
- _request.reconnectId = setTimeout(function () {
- _execute();
- }, reconnectInterval);
- } else {
- _execute();
- }
- } else {
- _onError(500, "Unable to reconnect with fallback transport");
- }
- }
- /**
- * Get url from request and attach headers to it.
- *
- * @param request {Object} request Request parameters, if undefined _request object will be used.
- *
- * @returns {Object} Request object, if undefined, _request object will be used.
- * @private
- */
- function _attachHeaders(request, url) {
- var rq = _request;
- if ((request != null) && (typeof (request) !== 'undefined')) {
- rq = request;
- }
- if (url == null) {
- url = rq.url;
- }
- // If not enabled
- if (!rq.attachHeadersAsQueryString)
- return url;
- // If already added
- if (url.indexOf("X-Atmosphere-Framework") !== -1) {
- return url;
- }
- url += (url.indexOf('?') !== -1) ? '&' : '?';
- url += "X-Atmosphere-tracking-id=" + rq.uuid;
- url += "&X-Atmosphere-Framework=" + atmosphere.version;
- url += "&X-Atmosphere-Transport=" + rq.transport;
- if (rq.trackMessageLength) {
- url += "&X-Atmosphere-TrackMessageSize=" + "true";
- }
- if (rq.heartbeat !== null && rq.heartbeat.server !== null) {
- url += "&X-Heartbeat-Server=" + rq.heartbeat.server;
- }
- if (rq.contentType !== '') {
- //Eurk!
- url += "&Content-Type=" + (rq.transport === 'websocket' ? rq.contentType : encodeURIComponent(rq.contentType));
- }
- if (rq.enableProtocol) {
- url += "&X-atmo-protocol=true";
- }
- atmosphere.util.each(rq.headers, function (name, value) {
- var h = atmosphere.util.isFunction(value) ? value.call(this, rq, request, _response) : value;
- if (h != null) {
- url += "&" + encodeURIComponent(name) + "=" + encodeURIComponent(h);
- }
- });
- return url;
- }
- function _triggerOpen(rq) {
- if (!rq.isOpen) {
- rq.isOpen = true;
- _open('opening', rq.transport, rq);
- } else if (rq.isReopen) {
- rq.isReopen = false;
- _open('re-opening', rq.transport, rq);
- } else if (_response.state === 'messageReceived' && (rq.transport === 'jsonp' || rq.transport === 'long-polling')) {
- _openAfterResume(_response);
- } else {
- return;
- }
- _startHeartbeat(rq);
- }
- function _startHeartbeat(rq) {
- if (rq.heartbeatTimer != null) {
- clearTimeout(rq.heartbeatTimer);
- }
- if (!isNaN(_heartbeatInterval) && _heartbeatInterval > 0) {
- var _pushHeartbeat = function () {
- if (_canLog('debug')) {
- atmosphere.util.debug("Sending heartbeat");
- }
- _push(_heartbeatPadding);
- rq.heartbeatTimer = setTimeout(_pushHeartbeat, _heartbeatInterval);
- };
- rq.heartbeatTimer = setTimeout(_pushHeartbeat, _heartbeatInterval);
- }
- }
- /**
- * Execute ajax request. <br>
- *
- * @param request {Object} request Request parameters, if undefined _request object will be used.
- * @private
- */
- function _executeRequest(request) {
- var rq = _request;
- if ((request != null) || (typeof (request) !== 'undefined')) {
- rq = request;
- }
- rq.lastIndex = 0;
- rq.readyState = 0;
- // CORS fake using JSONP
- if ((rq.transport === 'jsonp') || ((rq.enableXDR) && (atmosphere.util.checkCORSSupport()))) {
- _jsonp(rq);
- return;
- }
- if (atmosphere.util.browser.msie && +atmosphere.util.browser.version.split(".")[0] < 10) {
- if ((rq.transport === 'streaming')) {
- if (rq.enableXDR && window.XDomainRequest) {
- _ieXDR(rq);
- } else {
- _ieStreaming(rq);
- }
- return;
- }
- if ((rq.enableXDR) && (window.XDomainRequest)) {
- _ieXDR(rq);
- return;
- }
- }
- var reconnectFExec = function (force) {
- rq.lastIndex = 0;
- _requestCount++; // Increase also when forcing reconnect as _open checks _requestCount
- if (force || (rq.reconnect && _requestCount <= rq.maxReconnectOnClose)) {
- var delay = force ? 0 : request.reconnectInterval; // Reconnect immediately if the server resumed the connection (timeout)
- _response.ffTryingReconnect = true;
- _open('re-connecting', request.transport, request);
- _reconnect(ajaxRequest, rq, delay);
- } else {
- _onError(0, "maxReconnectOnClose reached");
- }
- };
- var reconnectF = function (force){
- if(atmosphere._beforeUnloadState){
- // ATMOSPHERE-JAVASCRIPT-143: Delay reconnect to avoid reconnect attempts before an actual unload (we don't know if an unload will happen, yet)
- atmosphere.util.debug(new Date() + " Atmosphere: reconnectF: execution delayed due to _beforeUnloadState flag");
- setTimeout(function () {
- reconnectFExec(force);
- }, 5000);
- }else {
- reconnectFExec(force);
- }
- };
- var disconnected = function () {
- // Prevent onerror callback to be called
- _response.errorHandled = true;
- _clearState();
- reconnectF(false);
- };
- if (rq.force || (rq.reconnect && (rq.maxRequest === -1 || rq.requestCount++ < rq.maxRequest))) {
- rq.force = false;
- var ajaxRequest = atmosphere.util.xhr();
- ajaxRequest.hasData = false;
- _doRequest(ajaxRequest, rq, true);
- if (rq.suspend) {
- _activeRequest = ajaxRequest;
- }
- if (rq.transport !== 'polling') {
- _response.transport = rq.transport;
- ajaxRequest.onabort = function () {
- _debug("ajaxrequest.onabort")
- _invokeClose(true);
- };
- ajaxRequest.onerror = function () {
- _debug("ajaxrequest.onerror")
- _response.error = true;
- _response.ffTryingReconnect = true;
- try {
- _response.status = XMLHttpRequest.status;
- } catch (e) {
- _response.status = 500;
- }
- if (!_response.status) {
- _response.status = 500;
- }
- if (!_response.errorHandled) {
- _clearState();
- reconnectF(false);
- }
- };
- }
- ajaxRequest.onreadystatechange = function () {
- _debug("ajaxRequest.onreadystatechange, new state: " + ajaxRequest.readyState);
- if (_abortingConnection) {
- _debug("onreadystatechange has been ignored due to _abortingConnection flag");
- return;
- }
- _response.error = null;
- var skipCallbackInvocation = false;
- var update = false;
- if (rq.transport === 'streaming' && rq.readyState > 2 && ajaxRequest.readyState === 4) {
- _clearState();
- reconnectF(false);
- return;
- }
- rq.readyState = ajaxRequest.readyState;
- if (rq.transport === 'streaming' && ajaxRequest.readyState >= 3) {
- update = true;
- } else if (rq.transport === 'long-polling' && ajaxRequest.readyState === 4) {
- update = true;
- }
- _timeout(_request);
- if (rq.transport !== 'polling') {
- // MSIE 9 and lower status can be higher than 1000, Chrome can be 0
- var status = 200;
- if (ajaxRequest.readyState === 4) {
- status = ajaxRequest.status > 1000 ? 0 : ajaxRequest.status;
- }
- if (!rq.reconnectOnServerError && (status >= 300 && status < 600)) {
- _onError(status, ajaxRequest.statusText);
- return;
- }
- if (status >= 300 || status === 0) {
- disconnected();
- return;
- }
- // Firefox incorrectly send statechange 0->2 when a reconnect attempt fails. The above checks ensure that onopen is not called for these
- if ((!rq.enableProtocol || !request.firstMessage) && ajaxRequest.readyState === 2) {
- // Firefox incorrectly send statechange 0->2 when a reconnect attempt fails. The above checks ensure that onopen is not called for these
- // In that case, ajaxRequest.onerror will be called just after onreadystatechange is called, so we delay the trigger until we are
- // guarantee the connection is well established.
- if (atmosphere.util.browser.mozilla && _response.ffTryingReconnect) {
- _response.ffTryingReconnect = false;
- setTimeout(function () {
- if (!_response.ffTryingReconnect) {
- _triggerOpen(rq);
- }
- }, 500);
- } else {
- _triggerOpen(rq);
- }
- }
- } else if (ajaxRequest.readyState === 4) {
- update = true;
- }
- if (update) {
- var responseText = ajaxRequest.responseText;
- _response.errorHandled = false;
- // IE behave the same way when resuming long-polling or when the server goes down.
- if (rq.transport === 'long-polling' && atmosphere.util.trim(responseText).length === 0) {
- // For browser that aren't support onabort
- if (!ajaxRequest.hasData) {
- reconnectF(true);
- } else {
- ajaxRequest.hasData = false;
- }
- return;
- }
- ajaxRequest.hasData = true;
- _readHeaders(ajaxRequest, _request);
- if (rq.transport === 'streaming') {
- if (!atmosphere.util.browser.opera) {
- var message = responseText.substring(rq.lastIndex, responseText.length);
- skipCallbackInvocation = _trackMessageSize(message, rq, _response);
- rq.lastIndex = responseText.length;
- if (skipCallbackInvocation) {
- return;
- }
- } else {
- atmosphere.util.iterate(function () {
- if (_response.status !== 500 && ajaxRequest.responseText.length > rq.lastIndex) {
- try {
- _response.status = ajaxRequest.status;
- _response.headers = atmosphere.util.parseHeaders(ajaxRequest.getAllResponseHeaders());
- _readHeaders(ajaxRequest, _request);
- } catch (e) {
- _response.status = 404;
- }
- _timeout(_request);
- _response.state = "messageReceived";
- var message = ajaxRequest.responseText.substring(rq.lastIndex);
- rq.lastIndex = ajaxRequest.responseText.length;
- skipCallbackInvocation = _trackMessageSize(message, rq, _response);
- if (!skipCallbackInvocation) {
- _invokeCallback();
- }
- if (_verifyStreamingLength(ajaxRequest, rq)) {
- _reconnectOnMaxStreamingLength(ajaxRequest, rq);
- return;
- }
- } else if (_response.status > 400) {
- // Prevent replaying the last message.
- rq.lastIndex = ajaxRequest.responseText.length;
- return false;
- }
- }, 0);
- }
- } else {
- skipCallbackInvocation = _trackMessageSize(responseText, rq, _response);
- }
- var closeStream = _verifyStreamingLength(ajaxRequest, rq);
- try {
- _response.status = ajaxRequest.status;
- _response.headers = atmosphere.util.parseHeaders(ajaxRequest.getAllResponseHeaders());
- _readHeaders(ajaxRequest, rq);
- } catch (e) {
- _response.status = 404;
- }
- if (rq.suspend) {
- _response.state = _response.status === 0 ? "closed" : "messageReceived";
- } else {
- _response.state = "messagePublished";
- }
- var isAllowedToReconnect = !closeStream && request.transport !== 'streaming' && request.transport !== 'polling';
- if (isAllowedToReconnect && !rq.executeCallbackBeforeReconnect) {
- _reconnect(ajaxRequest, rq, rq.pollingInterval);
- }
- if (_response.responseBody.length !== 0 && !skipCallbackInvocation)
- _invokeCallback();
- if (isAllowedToReconnect && rq.executeCallbackBeforeReconnect) {
- _reconnect(ajaxRequest, rq, rq.pollingInterval);
- }
- if (closeStream) {
- _reconnectOnMaxStreamingLength(ajaxRequest, rq);
- }
- }
- };
- try {
- ajaxRequest.send(rq.data);
- _subscribed = true;
- } catch (e) {
- atmosphere.util.log(rq.logLevel, ["Unable to connect to " + rq.url]);
- _onError(0, e);
- }
- } else {
- if (rq.logLevel === 'debug') {
- atmosphere.util.log(rq.logLevel, ["Max re-connection reached."]);
- }
- _onError(0, "maxRequest reached");
- }
- }
- function _reconnectOnMaxStreamingLength(ajaxRequest, rq) {
- _response.messages = [];
- rq.isReopen = true;
- _close();
- _abortingConnection = false;
- _reconnect(ajaxRequest, rq, 500);
- }
- /**
- * Do ajax request.
- *
- * @param ajaxRequest Ajax request.
- * @param request Request parameters.
- * @param create If ajax request has to be open.
- */
- function _doRequest(ajaxRequest, request, create) {
- // Prevent Android to cache request
- var url = request.url;
- if (request.dispatchUrl != null && request.method === 'POST') {
- url += request.dispatchUrl;
- }
- url = _attachHeaders(request, url);
- url = atmosphere.util.prepareURL(url);
- if (create) {
- ajaxRequest.open(request.method, url, request.async);
- if (request.connectTimeout > 0) {
- request.id = setTimeout(function () {
- if (request.requestCount === 0) {
- _clearState();
- _prepareCallback("Connect timeout", "closed", 200, request.transport);
- }
- }, request.connectTimeout);
- }
- }
- if (_request.withCredentials && _request.transport !== 'websocket') {
- if ("withCredentials" in ajaxRequest) {
- ajaxRequest.withCredentials = true;
- }
- }
- if (!_request.dropHeaders) {
- ajaxRequest.setRequestHeader("X-Atmosphere-Framework", atmosphere.version);
- ajaxRequest.setRequestHeader("X-Atmosphere-Transport", request.transport);
- if (request.heartbeat !== null && request.heartbeat.server !== null) {
- ajaxRequest.setRequestHeader("X-Heartbeat-Server", ajaxRequest.heartbeat.server);
- }
- if (request.trackMessageLength) {
- ajaxRequest.setRequestHeader("X-Atmosphere-TrackMessageSize", "true");
- }
- ajaxRequest.setRequestHeader("X-Atmosphere-tracking-id", request.uuid);
- atmosphere.util.each(request.headers, function (name, value) {
- var h = atmosphere.util.isFunction(value) ? value.call(this, ajaxRequest, request, create, _response) : value;
- if (h != null) {
- ajaxRequest.setRequestHeader(name, h);
- }
- });
- }
- if (request.contentType !== '') {
- ajaxRequest.setRequestHeader("Content-Type", request.contentType);
- }
- }
- function _reconnect(ajaxRequest, request, delay) {
- if (_response.closedByClientTimeout) {
- return;
- }
- if (request.reconnect || (request.suspend && _subscribed)) {
- var status = 0;
- if (ajaxRequest && ajaxRequest.readyState > 1) {
- status = ajaxRequest.status > 1000 ? 0 : ajaxRequest.status;
- }
- _response.status = status === 0 ? 204 : status;
- _response.reason = status === 0 ? "Server resumed the connection or down." : "OK";
- clearTimeout(request.id);
- if (request.reconnectId) {
- clearTimeout(request.reconnectId);
- delete request.reconnectId;
- }
- if (delay > 0) {
- // For whatever reason, never cancel a reconnect timeout as it is mandatory to reconnect.
- _request.reconnectId = setTimeout(function () {
- _executeRequest(request);
- }, delay);
- } else {
- _executeRequest(request);
- }
- }
- }
- function _tryingToReconnect(response) {
- response.state = 're-connecting';
- _invokeFunction(response);
- }
- function _openAfterResume(response) {
- response.state = 'openAfterResume';
- _invokeFunction(response);
- response.state = 'messageReceived';
- }
- function _ieXDR(request) {
- if (request.transport !== "polling") {
- _ieStream = _configureXDR(request);
- _ieStream.open();
- } else {
- _configureXDR(request).open();
- }
- }
- function _configureXDR(request) {
- var rq = _request;
- if ((request != null) && (typeof (request) !== 'undefined')) {
- rq = request;
- }
- var transport = rq.transport;
- var lastIndex = 0;
- var xdr = new window.XDomainRequest();
- var reconnect = function () {
- if (rq.transport === "long-polling" && (rq.reconnect && (rq.maxRequest === -1 || rq.requestCount++ < rq.maxRequest))) {
- xdr.status = 200;
- _ieXDR(rq);
- }
- };
- var rewriteURL = rq.rewriteURL || function (url) {
- // Maintaining session by rewriting URL
- // http://stackoverflow.com/questions/6453779/maintaining-session-by-rewriting-url
- var match = /(?:^|;\s*)(JSESSIONID|PHPSESSID)=([^;]*)/.exec(document.cookie);
- switch (match && match[1]) {
- case "JSESSIONID":
- return url.replace(/;jsessionid=[^\?]*|(\?)|$/, ";jsessionid=" + match[2] + "$1");
- case "PHPSESSID":
- return url.replace(/\?PHPSESSID=[^&]*&?|\?|$/, "?PHPSESSID=" + match[2] + "&").replace(/&$/, "");
- }
- return url;
- };
- // Handles open and message event
- xdr.onprogress = function () {
- handle(xdr);
- };
- // Handles error event
- xdr.onerror = function () {
- // If the server doesn't send anything back to XDR will fail with polling
- if (rq.transport !== 'polling') {
- _clearState();
- if (_requestCount++ < rq.maxReconnectOnClose) {
- if (rq.reconnectInterval > 0) {
- rq.reconnectId = setTimeout(function () {
- _open('re-connecting', request.transport, request);
- _ieXDR(rq);
- }, rq.reconnectInterval);
- } else {
- _open('re-connecting', request.transport, request);
- _ieXDR(rq);
- }
- } else {
- _onError(0, "maxReconnectOnClose reached");
- }
- }
- };
- // Handles close event
- xdr.onload = function () {
- };
- var handle = function (xdr) {
- clearTimeout(rq.id);
- var message = xdr.responseText;
- message = message.substring(lastIndex);
- lastIndex += message.length;
- if (transport !== 'polling') {
- _timeout(rq);
- var skipCallbackInvocation = _trackMessageSize(message, rq, _response);
- if (transport === 'long-polling' && atmosphere.util.trim(message).length === 0)
- return;
- if (rq.executeCallbackBeforeReconnect) {
- reconnect();
- }
- if (!skipCallbackInvocation) {
- _prepareCallback(_response.responseBody, "messageReceived", 200, transport);
- }
- if (!rq.executeCallbackBeforeReconnect) {
- reconnect();
- }
- }
- };
- return {
- open: function () {
- var url = rq.url;
- if (rq.dispatchUrl != null) {
- url += rq.dispatchUrl;
- }
- url = _attachHeaders(rq, url);
- xdr.open(rq.method, rewriteURL(url));
- if (rq.method === 'GET') {
- xdr.send();
- } else {
- xdr.send(rq.data);
- }
- if (rq.connectTimeout > 0) {
- rq.id = setTimeout(function () {
- if (rq.requestCount === 0) {
- _clearState();
- _prepareCallback("Connect timeout", "closed", 200, rq.transport);
- }
- }, rq.connectTimeout);
- }
- },
- close: function () {
- xdr.abort();
- }
- };
- }
- function _ieStreaming(request) {
- _ieStream = _configureIE(request);
- _ieStream.open();
- }
- function _configureIE(request) {
- var rq = _request;
- if ((request != null) && (typeof (request) !== 'undefined')) {
- rq = request;
- }
- var stop;
- var doc = new window.ActiveXObject("htmlfile");
- doc.open();
- doc.close();
- var url = rq.url;
- if (rq.dispatchUrl != null) {
- url += rq.dispatchUrl;
- }
- if (rq.transport !== 'polling') {
- _response.transport = rq.transport;
- }
- return {
- open: function () {
- var iframe = doc.createElement("iframe");
- url = _attachHeaders(rq);
- if (rq.data !== '') {
- url += "&X-Atmosphere-Post-Body=" + encodeURIComponent(rq.data);
- }
- // Finally attach a timestamp to prevent Android and IE caching.
- url = atmosphere.util.prepareURL(url);
- iframe.src = url;
- doc.body.appendChild(iframe);
- // For the server to respond in a consistent format regardless of user agent, we polls response text
- var cdoc = iframe.contentDocument || iframe.contentWindow.document;
- stop = atmosphere.util.iterate(function () {
- try {
- if (!cdoc.firstChild) {
- return;
- }
- var res = cdoc.body ? cdoc.body.lastChild : cdoc;
- if (res.omgThisIsBroken) {
- // Cause an exception when res is null, to trigger a reconnect...
- }
- var readResponse = function () {
- // Clones the element not to disturb the original one
- var clone = res.cloneNode(true);
- // If the last character is a carriage return or a line feed, IE ignores it in the innerText property
- // therefore, we add another non-newline character to preserve it
- clone.appendChild(cdoc.createTextNode("."));
- var text = clone.innerText;
- text = text.substring(0, text.length - 1);
- return text;
- };
- // To support text/html content type
- if (!cdoc.body || !cdoc.body.firstChild || cdoc.body.firstChild.nodeName.toLowerCase() !== "pre") {
- // Injects a plaintext element which renders text without interpreting the HTML and cannot be stopped
- // it is deprecated in HTML5, but still works
- var head = cdoc.head || cdoc.getElementsByTagName("head")[0] || cdoc.documentElement || cdoc;
- var script = cdoc.createElement("script");
- script.text = "document.write('<plaintext>')";
- head.insertBefore(script, head.firstChild);
- head.removeChild(script);
- // The plaintext element will be the response container
- res = cdoc.body.lastChild;
- }
- if (rq.closed) {
- rq.isReopen = true;
- }
- // Handles message and close event
- stop = atmosphere.util.iterate(function () {
- var text = readResponse();
- if (text.length > rq.lastIndex) {
- _timeout(_request);
- _response.status = 200;
- _response.error = null;
- // Empties response every time that it is handled
- res.innerText = "";
- var skipCallbackInvocation = _trackMessageSize(text, rq, _response);
- if (skipCallbackInvocation) {
- return "";
- }
- _prepareCallback(_response.responseBody, "messageReceived", 200, rq.transport);
- }
- rq.lastIndex = 0;
- if (cdoc.readyState === "complete") {
- _invokeClose(true);
- _open('re-connecting', rq.transport, rq);
- if (rq.reconnectInterval > 0) {
- rq.reconnectId = setTimeout(function () {
- _ieStreaming(rq);
- }, rq.reconnectInterval);
- } else {
- _ieStreaming(rq);
- }
- return false;
- }
- }, null);
- return false;
- } catch (err) {
- _response.error = true;
- _open('re-connecting', rq.transport, rq);
- if (_requestCount++ < rq.maxReconnectOnClose) {
- if (rq.reconnectInterval > 0) {
- rq.reconnectId = setTimeout(function () {
- _ieStreaming(rq);
- }, rq.reconnectInterval);
- } else {
- _ieStreaming(rq);
- }
- } else {
- _onError(0, "maxReconnectOnClose reached");
- }
- doc.execCommand("Stop");
- doc.close();
- return false;
- }
- });
- },
- close: function () {
- if (stop) {
- stop();
- }
- doc.execCommand("Stop");
- _invokeClose(true);
- }
- };
- }
- /**
- * Send message. <br>
- * Will be automatically dispatch to other connected.
- *
- * @param {Object, string} Message to send.
- * @private
- */
- function _push(message) {
- if (_localStorageService != null) {
- _pushLocal(message);
- } else if (_activeRequest != null || _sse != null) {
- _pushAjaxMessage(message);
- } else if (_ieStream != null) {
- _pushIE(message);
- } else if (_jqxhr != null) {
- _pushJsonp(message);
- } else if (_websocket != null) {
- _pushWebSocket(message);
- } else {
- _onError(0, "No suspended connection available");
- atmosphere.util.error("No suspended connection available. Make sure atmosphere.subscribe has been called and request.onOpen invoked before trying to push data");
- }
- }
- function _pushOnClose(message, rq) {
- if (!rq) {
- rq = _getPushRequest(message);
- }
- rq.transport = "polling";
- rq.method = "GET";
- rq.withCredentials = false;
- rq.reconnect = false;
- rq.force = true;
- rq.suspend = false;
- rq.timeout = 1000;
- _executeRequest(rq);
- }
- function _pushLocal(message) {
- _localStorageService.send(message);
- }
- function _intraPush(message) {
- // IE 9 will crash if not.
- if (message.length === 0)
- return;
- try {
- if (_localStorageService) {
- _localStorageService.localSend(message);
- } else if (_storageService) {
- _storageService.signal("localMessage", atmosphere.util.stringifyJSON({
- id: guid,
- event: message
- }));
- }
- } catch (err) {
- atmosphere.util.error(err);
- }
- }
- /**
- * Send a message using currently opened ajax request (using http-streaming or long-polling). <br>
- *
- * @param {string, Object} Message to send. This is an object, string message is saved in data member.
- * @private
- */
- function _pushAjaxMessage(message) {
- var rq = _getPushRequest(message);
- _executeRequest(rq);
- }
- /**
- * Send a message using currently opened ie streaming (using http-streaming or long-polling). <br>
- *
- * @param {string, Object} Message to send. This is an object, string message is saved in data member.
- * @private
- */
- function _pushIE(message) {
- if (_request.enableXDR && atmosphere.util.checkCORSSupport()) {
- var rq = _getPushRequest(message);
- // Do not reconnect since we are pushing.
- rq.reconnect = false;
- _jsonp(rq);
- } else {
- _pushAjaxMessage(message);
- }
- }
- /**
- * Send a message using jsonp transport. <br>
- *
- * @param {string, Object} Message to send. This is an object, string message is saved in data member.
- * @private
- */
- function _pushJsonp(message) {
- _pushAjaxMessage(message);
- }
- function _getStringMessage(message) {
- var msg = message;
- if (typeof (msg) === 'object') {
- msg = message.data;
- }
- return msg;
- }
- /**
- * Build request use to push message using method 'POST' <br>. Transport is defined as 'polling' and 'suspend' is set to false.
- *
- * @return {Object} Request object use to push message.
- * @private
- */
- function _getPushRequest(message) {
- var msg = _getStringMessage(message);
- var rq = {
- connected: false,
- timeout: 60000,
- method: 'POST',
- url: _request.url,
- contentType: _request.contentType,
- headers: _request.headers,
- reconnect: true,
- callback: null,
- data: msg,
- suspend: false,
- maxRequest: -1,
- logLevel: 'info',
- requestCount: 0,
- withCredentials: _request.withCredentials,
- async: _request.async,
- transport: 'polling',
- isOpen: true,
- attachHeadersAsQueryString: true,
- enableXDR: _request.enableXDR,
- uuid: _request.uuid,
- dispatchUrl: _request.dispatchUrl,
- enableProtocol: false,
- messageDelimiter: '|',
- trackMessageLength: _request.trackMessageLength,
- maxReconnectOnClose: _request.maxReconnectOnClose,
- heartbeatTimer: _request.heartbeatTimer,
- heartbeat: _request.heartbeat
- };
- if (typeof (message) === 'object') {
- rq = atmosphere.util.extend(rq, message);
- }
- return rq;
- }
- /**
- * Send a message using currently opened websocket. <br>
- *
- */
- function _pushWebSocket(message) {
- var msg = atmosphere.util.isBinary(message) ? message : _getStringMessage(message);
- var data;
- try {
- if (_request.dispatchUrl != null) {
- data = _request.webSocketPathDelimiter + _request.dispatchUrl + _request.webSocketPathDelimiter + msg;
- } else {
- data = msg;
- }
- if (!_websocket.canSendMessage) {
- atmosphere.util.error("WebSocket not connected.");
- return;
- }
- _websocket.send(data);
- } catch (e) {
- _websocket.onclose = function (message) {
- };
- _clearState();
- _reconnectWithFallbackTransport("Websocket failed. Downgrading to " + _request.fallbackTransport + " and resending " + message);
- _pushAjaxMessage(message);
- }
- }
- function _localMessage(message) {
- var m = atmosphere.util.parseJSON(message);
- if (m.id !== guid) {
- if (typeof (_request.onLocalMessage) !== 'undefined') {
- _request.onLocalMessage(m.event);
- } else if (typeof (atmosphere.util.onLocalMessage) !== 'undefined') {
- atmosphere.util.onLocalMessage(m.event);
- }
- }
- }
- function _prepareCallback(messageBody, state, errorCode, transport) {
- _response.responseBody = messageBody;
- _response.transport = transport;
- _response.status = errorCode;
- _response.state = state;
- _invokeCallback();
- }
- function _readHeaders(xdr, request) {
- if (!request.readResponsesHeaders) {
- if (!request.enableProtocol) {
- request.uuid = guid;
- }
- }
- else {
- try {
- var tempUUID = xdr.getResponseHeader('X-Atmosphere-tracking-id');
- if (tempUUID && tempUUID != null) {
- request.uuid = tempUUID.split(" ").pop();
- }
- } catch (e) {
- }
- }
- }
- function _invokeFunction(response) {
- _f(response, _request);
- // Global
- _f(response, atmosphere.util);
- }
- function _f(response, f) {
- switch (response.state) {
- case "messageReceived":
- _debug("Firing onMessage");
- _requestCount = 0;
- if (typeof (f.onMessage) !== 'undefined')
- f.onMessage(response);
- if (typeof (f.onmessage) !== 'undefined')
- f.onmessage(response);
- break;
- case "error":
- var dbgReasonPhrase = (typeof(response.reasonPhrase) != 'undefined') ? response.reasonPhrase : 'n/a';
- _debug("Firing onError, reasonPhrase: " + dbgReasonPhrase);
- if (typeof (f.onError) !== 'undefined')
- f.onError(response);
- if (typeof (f.onerror) !== 'undefined')
- f.onerror(response);
- break;
- case "opening":
- delete _request.closed;
- _debug("Firing onOpen");
- if (typeof (f.onOpen) !== 'undefined')
- f.onOpen(response);
- if (typeof (f.onopen) !== 'undefined')
- f.onopen(response);
- break;
- case "messagePublished":
- _debug("Firing messagePublished");
- if (typeof (f.onMessagePublished) !== 'undefined')
- f.onMessagePublished(response);
- break;
- case "re-connecting":
- _debug("Firing onReconnect");
- if (typeof (f.onReconnect) !== 'undefined')
- f.onReconnect(_request, response);
- break;
- case "closedByClient":
- _debug("Firing closedByClient");
- if (typeof (f.onClientTimeout) !== 'undefined')
- f.onClientTimeout(_request);
- break;
- case "re-opening":
- delete _request.closed;
- _debug("Firing onReopen");
- if (typeof (f.onReopen) !== 'undefined')
- f.onReopen(_request, response);
- break;
- case "fail-to-reconnect":
- _debug("Firing onFailureToReconnect");
- if (typeof (f.onFailureToReconnect) !== 'undefined')
- f.onFailureToReconnect(_request, response);
- break;
- case "unsubscribe":
- case "closed":
- var closed = typeof (_request.closed) !== 'undefined' ? _request.closed : false;
- if (!closed) {
- _debug("Firing onClose (" + response.state + " case)");
- if (typeof (f.onClose) !== 'undefined') {
- f.onClose(response);
- }
- if (typeof (f.onclose) !== 'undefined') {
- f.onclose(response);
- }
- } else {
- _debug("Request already closed, not firing onClose (" + response.state + " case)");
- }
- _request.closed = true;
- break;
- case "openAfterResume":
- if (typeof (f.onOpenAfterResume) !== 'undefined')
- f.onOpenAfterResume(_request);
- break;
- }
- }
- function _invokeClose(wasOpen) {
- if (_response.state !== 'closed') {
- _response.state = 'closed';
- _response.responseBody = "";
- _response.messages = [];
- _response.status = !wasOpen ? 501 : 200;
- _invokeCallback();
- }
- }
- /**
- * Invoke request callbacks.
- *
- * @private
- */
- function _invokeCallback() {
- var call = function (index, func) {
- func(_response);
- };
- if (_localStorageService == null && _localSocketF != null) {
- _localSocketF(_response.responseBody);
- }
- _request.reconnect = _request.mrequest;
- var isString = typeof (_response.responseBody) === 'string';
- var messages = (isString && _request.trackMessageLength) ? (_response.messages.length > 0 ? _response.messages : ['']) : new Array(
- _response.responseBody);
- for (var i = 0; i < messages.length; i++) {
- if (messages.length > 1 && messages[i].length === 0) {
- continue;
- }
- _response.responseBody = (isString) ? atmosphere.util.trim(messages[i]) : messages[i];
- if (_localStorageService == null && _localSocketF != null) {
- _localSocketF(_response.responseBody);
- }
- if ((_response.responseBody.length === 0 ||
- (isString && _heartbeatPadding === _response.responseBody)) && _response.state === "messageReceived") {
- continue;
- }
- _invokeFunction(_response);
- // Invoke global callbacks
- if (callbacks.length > 0) {
- if (_canLog('debug')) {
- atmosphere.util.debug("Invoking " + callbacks.length + " global callbacks: " + _response.state);
- }
- try {
- atmosphere.util.each(callbacks, call);
- } catch (e) {
- atmosphere.util.log(_request.logLevel, ["Callback exception" + e]);
- }
- }
- // Invoke request callback
- if (typeof (_request.callback) === 'function') {
- if (_canLog('debug')) {
- atmosphere.util.debug("Invoking request callbacks");
- }
- try {
- _request.callback(_response);
- } catch (e) {
- atmosphere.util.log(_request.logLevel, ["Callback exception" + e]);
- }
- }
- }
- }
- this.subscribe = function (options) {
- _subscribe(options);
- _execute();
- };
- this.execute = function () {
- _execute();
- };
- this.close = function () {
- _close();
- };
- this.disconnect = function () {
- _disconnect();
- };
- this.getUrl = function () {
- return _request.url;
- };
- this.push = function (message, dispatchUrl) {
- if (dispatchUrl != null) {
- var originalDispatchUrl = _request.dispatchUrl;
- _request.dispatchUrl = dispatchUrl;
- _push(message);
- _request.dispatchUrl = originalDispatchUrl;
- } else {
- _push(message);
- }
- };
- this.getUUID = function () {
- return _request.uuid;
- };
- this.pushLocal = function (message) {
- _intraPush(message);
- };
- this.enableProtocol = function (message) {
- return _request.enableProtocol;
- };
- this.init = function () {
- _init();
- };
- this.request = _request;
- this.response = _response;
- }
- };
- atmosphere.subscribe = function (url, callback, request) {
- if (typeof (callback) === 'function') {
- atmosphere.addCallback(callback);
- }
- if (typeof (url) !== "string") {
- request = url;
- } else {
- request.url = url;
- }
- // https://github.com/Atmosphere/atmosphere-javascript/issues/58
- uuid = ((typeof (request) !== 'undefined') && typeof (request.uuid) !== 'undefined') ? request.uuid : 0;
- var rq = new atmosphere.AtmosphereRequest(request);
- rq.execute();
- requests[requests.length] = rq;
- return rq;
- };
- atmosphere.unsubscribe = function () {
- if (requests.length > 0) {
- var requestsClone = [].concat(requests);
- for (var i = 0; i < requestsClone.length; i++) {
- var rq = requestsClone[i];
- rq.close();
- clearTimeout(rq.response.request.id);
- if (rq.heartbeatTimer) {
- clearTimeout(rq.heartbeatTimer);
- }
- }
- }
- requests = [];
- callbacks = [];
- };
- atmosphere.unsubscribeUrl = function (url) {
- var idx = -1;
- if (requests.length > 0) {
- for (var i = 0; i < requests.length; i++) {
- var rq = requests[i];
- // Suppose you can subscribe once to an url
- if (rq.getUrl() === url) {
- rq.close();
- clearTimeout(rq.response.request.id);
- if (rq.heartbeatTimer) {
- clearTimeout(rq.heartbeatTimer);
- }
- idx = i;
- break;
- }
- }
- }
- if (idx >= 0) {
- requests.splice(idx, 1);
- }
- };
- atmosphere.addCallback = function (func) {
- if (atmosphere.util.inArray(func, callbacks) === -1) {
- callbacks.push(func);
- }
- };
- atmosphere.removeCallback = function (func) {
- var index = atmosphere.util.inArray(func, callbacks);
- if (index !== -1) {
- callbacks.splice(index, 1);
- }
- };
- atmosphere.util = {
- browser: {},
- parseHeaders: function (headerString) {
- var match, rheaders = /^(.*?):[ \t]*([^\r\n]*)\r?$/mg, headers = {};
- while (match = rheaders.exec(headerString)) {
- headers[match[1]] = match[2];
- }
- return headers;
- },
- now: function () {
- return new Date().getTime();
- },
- isArray: function (array) {
- return Object.prototype.toString.call(array) === "[object Array]";
- },
- inArray: function (elem, array) {
- if (!Array.prototype.indexOf) {
- var len = array.length;
- for (var i = 0; i < len; ++i) {
- if (array[i] === elem) {
- return i;
- }
- }
- return -1;
- }
- return array.indexOf(elem);
- },
- isBinary: function (data) {
- // True if data is an instance of Blob, ArrayBuffer or ArrayBufferView
- return /^\[object\s(?:Blob|ArrayBuffer|.+Array)\]$/.test(Object.prototype.toString.call(data));
- },
- isFunction: function (fn) {
- return Object.prototype.toString.call(fn) === "[object Function]";
- },
- getAbsoluteURL: function (url) {
- if (typeof (document.createElement) === 'undefined') {
- // assuming the url to be already absolute when DOM is not supported
- return url;
- }
- var div = document.createElement("div");
- // Uses an innerHTML property to obtain an absolute URL
- div.innerHTML = '<a href="' + url + '"/>';
- // encodeURI and decodeURI are needed to normalize URL between IE and non-IE,
- // since IE doesn't encode the href property value and return it - http://jsfiddle.net/Yq9M8/1/
- return encodeURI(decodeURI(div.firstChild.href));
- },
- prepareURL: function (url) {
- // Attaches a time stamp to prevent caching
- var ts = atmosphere.util.now();
- var ret = url.replace(/([?&])_=[^&]*/, "$1_=" + ts);
- return ret + (ret === url ? (/\?/.test(url) ? "&" : "?") + "_=" + ts : "");
- },
- trim: function (str) {
- if (!String.prototype.trim) {
- return str.toString().replace(/(?:(?:^|\n)\s+|\s+(?:$|\n))/g, "").replace(/\s+/g, " ");
- } else {
- return str.toString().trim();
- }
- },
- param: function (params) {
- var prefix, s = [];
- function add(key, value) {
- value = atmosphere.util.isFunction(value) ? value() : (value == null ? "" : value);
- s.push(encodeURIComponent(key) + "=" + encodeURIComponent(value));
- }
- function buildParams(prefix, obj) {
- var name;
- if (atmosphere.util.isArray(obj)) {
- atmosphere.util.each(obj, function (i, v) {
- if (/\[\]$/.test(prefix)) {
- add(prefix, v);
- } else {
- buildParams(prefix + "[" + (typeof v === "object" ? i : "") + "]", v);
- }
- });
- } else if (Object.prototype.toString.call(obj) === "[object Object]") {
- for (name in obj) {
- buildParams(prefix + "[" + name + "]", obj[name]);
- }
- } else {
- add(prefix, obj);
- }
- }
- for (prefix in params) {
- buildParams(prefix, params[prefix]);
- }
- return s.join("&").replace(/%20/g, "+");
- },
- storage: function () {
- try {
- return !!(window.localStorage && window.StorageEvent);
- } catch (e) {
- //Firefox throws an exception here, see
- //https://bugzilla.mozilla.org/show_bug.cgi?id=748620
- return false;
- }
- },
- iterate: function (fn, interval) {
- var timeoutId;
- // Though the interval is 0 for real-time application, there is a delay between setTimeout calls
- // For detail, see https://developer.mozilla.org/en/window.setTimeout#Minimum_delay_and_timeout_nesting
- interval = interval || 0;
- (function loop() {
- timeoutId = setTimeout(function () {
- if (fn() === false) {
- return;
- }
- loop();
- }, interval);
- })();
- return function () {
- clearTimeout(timeoutId);
- };
- },
- each: function (obj, callback, args) {
- if (!obj) return;
- var value, i = 0, length = obj.length, isArray = atmosphere.util.isArray(obj);
- if (args) {
- if (isArray) {
- for (; i < length; i++) {
- value = callback.apply(obj[i], args);
- if (value === false) {
- break;
- }
- }
- } else {
- for (i in obj) {
- value = callback.apply(obj[i], args);
- if (value === false) {
- break;
- }
- }
- }
- // A special, fast, case for the most common use of each
- } else {
- if (isArray) {
- for (; i < length; i++) {
- value = callback.call(obj[i], i, obj[i]);
- if (value === false) {
- break;
- }
- }
- } else {
- for (i in obj) {
- value = callback.call(obj[i], i, obj[i]);
- if (value === false) {
- break;
- }
- }
- }
- }
- return obj;
- },
- extend: function (target) {
- var i, options, name;
- for (i = 1; i < arguments.length; i++) {
- if ((options = arguments[i]) != null) {
- for (name in options) {
- target[name] = options[name];
- }
- }
- }
- return target;
- },
- on: function (elem, type, fn) {
- if (elem.addEventListener) {
- elem.addEventListener(type, fn, false);
- } else if (elem.attachEvent) {
- elem.attachEvent("on" + type, fn);
- }
- },
- off: function (elem, type, fn) {
- if (elem.removeEventListener) {
- elem.removeEventListener(type, fn, false);
- } else if (elem.detachEvent) {
- elem.detachEvent("on" + type, fn);
- }
- },
- log: function (level, args) {
- if (window.console) {
- var logger = window.console[level];
- if (typeof logger === 'function') {
- logger.apply(window.console, args);
- }
- }
- },
- warn: function () {
- atmosphere.util.log('warn', arguments);
- },
- info: function () {
- atmosphere.util.log('info', arguments);
- },
- debug: function () {
- atmosphere.util.log('debug', arguments);
- },
- error: function () {
- atmosphere.util.log('error', arguments);
- },
- xhr: function () {
- try {
- return new window.XMLHttpRequest();
- } catch (e1) {
- try {
- return new window.ActiveXObject("Microsoft.XMLHTTP");
- } catch (e2) {
- }
- }
- },
- parseJSON: function (data) {
- return !data ? null : window.JSON && window.JSON.parse ? window.JSON.parse(data) : new Function("return " + data)();
- },
- // http://github.com/flowersinthesand/stringifyJSON
- stringifyJSON: function (value) {
- var escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, meta = {
- '\b': '\\b',
- '\t': '\\t',
- '\n': '\\n',
- '\f': '\\f',
- '\r': '\\r',
- '"': '\\"',
- '\\': '\\\\'
- };
- function quote(string) {
- return '"' + string.replace(escapable, function (a) {
- var c = meta[a];
- return typeof c === "string" ? c : "\\u" + ("0000" + a.charCodeAt(0).toString(16)).slice(-4);
- }) + '"';
- }
- function f(n) {
- return n < 10 ? "0" + n : n;
- }
- return window.JSON && window.JSON.stringify ? window.JSON.stringify(value) : (function str(key, holder) {
- var i, v, len, partial, value = holder[key], type = typeof value;
- if (value && typeof value === "object" && typeof value.toJSON === "function") {
- value = value.toJSON(key);
- type = typeof value;
- }
- switch (type) {
- case "string":
- return quote(value);
- case "number":
- return isFinite(value) ? String(value) : "null";
- case "boolean":
- return String(value);
- case "object":
- if (!value) {
- return "null";
- }
- switch (Object.prototype.toString.call(value)) {
- case "[object Date]":
- return isFinite(value.valueOf()) ? '"' + value.getUTCFullYear() + "-" + f(value.getUTCMonth() + 1) + "-"
- + f(value.getUTCDate()) + "T" + f(value.getUTCHours()) + ":" + f(value.getUTCMinutes()) + ":" + f(value.getUTCSeconds())
- + "Z" + '"' : "null";
- case "[object Array]":
- len = value.length;
- partial = [];
- for (i = 0; i < len; i++) {
- partial.push(str(i, value) || "null");
- }
- return "[" + partial.join(",") + "]";
- default:
- partial = [];
- for (i in value) {
- if (hasOwn.call(value, i)) {
- v = str(i, value);
- if (v) {
- partial.push(quote(i) + ":" + v);
- }
- }
- }
- return "{" + partial.join(",") + "}";
- }
- }
- })("", {
- "": value
- });
- },
- checkCORSSupport: function () {
- if (atmosphere.util.browser.msie && !window.XDomainRequest && +atmosphere.util.browser.version.split(".")[0] < 11) {
- return true;
- } else if (atmosphere.util.browser.opera && +atmosphere.util.browser.version.split(".") < 12.0) {
- return true;
- }
- // KreaTV 4.1 -> 4.4
- else if (atmosphere.util.trim(navigator.userAgent).slice(0, 16) === "KreaTVWebKit/531") {
- return true;
- }
- // KreaTV 3.8
- else if (atmosphere.util.trim(navigator.userAgent).slice(-7).toLowerCase() === "kreatel") {
- return true;
- }
- // Force older Android versions to use CORS as some version like 2.2.3 fail otherwise
- var ua = navigator.userAgent.toLowerCase();
- var androidVersionMatches = ua.match(/.+android ([0-9]{1,2})/i),
- majorVersion = parseInt((androidVersionMatches && androidVersionMatches[0]) || -1, 10);
- if (!isNaN(majorVersion) && majorVersion > -1 && majorVersion < 3) {
- return true;
- }
- return false;
- }
- };
- guid = atmosphere.util.now();
- // Browser sniffing
- (function () {
- var ua = navigator.userAgent.toLowerCase(),
- match = /(chrome)[ \/]([\w.]+)/.exec(ua) ||
- /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) ||
- /(msie) ([\w.]+)/.exec(ua) ||
- /(trident)(?:.*? rv:([\w.]+)|)/.exec(ua) ||
- ua.indexOf("android") < 0 && /version\/(.+) (safari)/.exec(ua) ||
- ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) ||
- [];
- // Swaps variables
- if (match[2] === "safari") {
- match[2] = match[1];
- match[1] = "safari";
- }
- atmosphere.util.browser[match[1] || ""] = true;
- atmosphere.util.browser.version = match[2] || "0";
- atmosphere.util.browser.vmajor = atmosphere.util.browser.version.split(".")[0];
- // Trident is the layout engine of the Internet Explorer
- // IE 11 has no "MSIE: 11.0" token
- if (atmosphere.util.browser.trident) {
- atmosphere.util.browser.msie = true;
- }
- // The storage event of Internet Explorer and Firefox 3 works strangely
- if (atmosphere.util.browser.msie || (atmosphere.util.browser.mozilla && +atmosphere.util.browser.version.split(".")[0] === 1)) {
- atmosphere.util.storage = false;
- }
- })();
- atmosphere.callbacks = {
- unload: function() {
- atmosphere.util.debug(new Date() + " Atmosphere: " + "unload event");
- atmosphere.unsubscribe();
- },
- beforeUnload: function() {
- atmosphere.util.debug(new Date() + " Atmosphere: " + "beforeunload event");
- // ATMOSPHERE-JAVASCRIPT-143: Delay reconnect to avoid reconnect attempts before an actual unload (we don't know if an unload will happen, yet)
- atmosphere._beforeUnloadState = true;
- setTimeout(function () {
- atmosphere.util.debug(new Date() + " Atmosphere: " + "beforeunload event timeout reached. Reset _beforeUnloadState flag");
- atmosphere._beforeUnloadState = false;
- }, 5000);
- },
- offline: function() {
- atmosphere.util.debug(new Date() + " Atmosphere: offline event");
- offline = true;
- if (requests.length > 0) {
- var requestsClone = [].concat(requests);
- for (var i = 0; i < requestsClone.length; i++) {
- var rq = requestsClone[i];
- if(rq.request.handleOnlineOffline) {
- rq.close();
- clearTimeout(rq.response.request.id);
- if (rq.heartbeatTimer) {
- clearTimeout(rq.heartbeatTimer);
- }
- }
- }
- }
- },
- online: function() {
- atmosphere.util.debug(new Date() + " Atmosphere: online event");
- if (requests.length > 0) {
- for (var i = 0; i < requests.length; i++) {
- if(requests[i].request.handleOnlineOffline) {
- requests[i].init();
- requests[i].execute();
- }
- }
- }
- offline = false;
- }
- };
- atmosphere.bindEvents = function() {
- atmosphere.util.on(window, "unload", atmosphere.callbacks.unload);
- atmosphere.util.on(window, "beforeunload", atmosphere.callbacks.beforeUnload);
- atmosphere.util.on(window, "offline", atmosphere.callbacks.offline);
- atmosphere.util.on(window, "online", atmosphere.callbacks.online);
- };
- atmosphere.unbindEvents = function() {
- atmosphere.util.off(window, "unload", atmosphere.callbacks.unload);
- atmosphere.util.off(window, "beforeunload", atmosphere.callbacks.beforeUnload);
- atmosphere.util.off(window, "offline", atmosphere.callbacks.offline);
- atmosphere.util.off(window, "online", atmosphere.callbacks.online);
- };
- atmosphere.bindEvents();
- return atmosphere;
- }));
- /* jshint eqnull:true, noarg:true, noempty:true, eqeqeq:true, evil:true, laxbreak:true, undef:true, browser:true, indent:false, maxerr:50 */
|