PageSourceSearch

https://fabulous.com/assets/js/push.js

js fabulous.com collected 2026-09-29 17:20:21 UTC 40,510 bytes, 1,154 lines download raw bytes

1/*global PushStream WebSocketWrapper EventSourceWrapper EventSource*/
2/*jshint evil: true, plusplus: false, regexp: false */
3/**
4The MIT License (MIT)
5
6Copyright (c) 2010-2014 Wandenberg Peixoto <[email protected]>, Rogério Carvalho Schneider <[email protected]>
7
8This file is part of Nginx Push Stream Module.
9
10Permission is hereby granted, free of charge, to any person obtaining a copy
11of this software and associated documentation files (the "Software"), to deal
12in the Software without restriction, including without limitation the rights
13to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14copies of the Software, and to permit persons to whom the Software is
15furnished to do so, subject to the following conditions:
16
17The above copyright notice and this permission notice shall be included in all
18copies or substantial portions of the Software.
19
20THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26SOFTWARE.
27
28pushstream.js
29
30Created: Nov 01, 2011
31Authors: Wandenberg Peixoto <[email protected]>, Rogério Carvalho Schneider <[email protected]>
32 */
33(function (window, document, undefined) {
34  "use strict";
35
36  /* prevent duplicate declaration */
37  if (window.PushStream) { return; }
38
39  var Utils = {};
40
41  var days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
42  var months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
43
44  var valueToTwoDigits = function (value) {
45    return ((value < 10) ? '0' : '') + value;
46  };
47
48  Utils.dateToUTCString = function (date) {
49    var time = valueToTwoDigits(date.getUTCHours()) + ':' + valueToTwoDigits(date.getUTCMinutes()) + ':' + valueToTwoDigits(date.getUTCSeconds());
50    return days[date.getUTCDay()] + ', ' + valueToTwoDigits(date.getUTCDate()) + ' ' + months[date.getUTCMonth()] + ' ' + date.getUTCFullYear() + ' ' + time + ' GMT';
51  };
52
53  var extend = function () {
54    var object = arguments[0] || {};
55    for (var i = 0; i < arguments.length; i++) {
56      var settings = arguments[i];
57      for (var attr in settings) {
58        if (!settings.hasOwnProperty || settings.hasOwnProperty(attr)) {
59          object[attr] = settings[attr];
60        }
61      }
62    }
63    return object;
64  };
65
66  var validChars  = /^[\],:{}\s]*$/,
67      validEscape = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,
68      validTokens = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,
69      validBraces = /(?:^|:|,)(?:\s*\[)+/g;
70
71  var trim = function(value) {
72    return value.replace(/^\s*/, "").replace(/\s*$/, "");
73  };
74
75  Utils.parseJSON = function(data) {
76    if (!data || !isString(data)) {
77      return null;
78    }
79
80    // Make sure leading/trailing whitespace is removed (IE can't handle it)
81    data = trim(data);
82
83    // Attempt to parse using the native JSON parser first
84    if (window.JSON && window.JSON.parse) {
85      try {
86        return window.JSON.parse( data );
87      } catch(e) {
88        throw "Invalid JSON: " + data;
89      }
90    }
91
92    // Make sure the incoming data is actual JSON
93    // Logic borrowed from http://json.org/json2.js
94    if (validChars.test(data.replace(validEscape, "@").replace( validTokens, "]").replace( validBraces, "")) ) {
95      return (new Function("return " + data))();
96    }
97
98    throw "Invalid JSON: " + data;
99  };
100
101  var getControlParams = function(pushstream) {
102    var data = {};
103    data[pushstream.tagArgument] = "";
104    data[pushstream.timeArgument] = "";
105    data[pushstream.eventIdArgument] = "";
106    if (pushstream.messagesControlByArgument) {
107      data[pushstream.tagArgument] = Number(pushstream._etag);
108      if (pushstream._lastModified) {
109        data[pushstream.timeArgument] = pushstream._lastModified;
110      } else if (pushstream._lastEventId) {
111        data[pushstream.eventIdArgument] = pushstream._lastEventId;
112      }
113    }
114    return data;
115  };
116
117  var getTime = function() {
118    return (new Date()).getTime();
119  };
120
121  var currentTimestampParam = function() {
122    return { "_" : getTime() };
123  };
124
125  var objectToUrlParams = function(settings) {
126    var params = settings;
127    if (typeof(settings) === 'object') {
128      params = '';
129      for (var attr in settings) {
130        if (!settings.hasOwnProperty || settings.hasOwnProperty(attr)) {
131          params += '&' + attr + '=' + window.escape(settings[attr]);
132        }
133      }
134      params = params.substring(1);
135    }
136
137    return params || '';
138  };
139
140  var addParamsToUrl = function(url, params) {
141    return url + ((url.indexOf('?') < 0) ? '?' : '&') + objectToUrlParams(params);
142  };
143
144  var isArray = Array.isArray || function(obj) {
145    return Object.prototype.toString.call(obj) === '[object Array]';
146  };
147
148  var isString = function(obj) {
149    return Object.prototype.toString.call(obj) === '[object String]';
150  };
151
152  var isDate = function(obj) {
153    return Object.prototype.toString.call(obj) === '[object Date]';
154  };
155
156  var Log4js = {
157    logger: null,
158    debug : function() { if  (PushStream.LOG_LEVEL === 'debug')                                         { Log4js._log.apply(Log4js._log, arguments); }},
159    info  : function() { if ((PushStream.LOG_LEVEL === 'info')  || (PushStream.LOG_LEVEL === 'debug'))  { Log4js._log.apply(Log4js._log, arguments); }},
160    error : function() {                                                                                  Log4js._log.apply(Log4js._log, arguments); },
161    _initLogger : function() {
162      var console = window.console;
163      if (console && console.log) {
164        if (console.log.apply) {
165          Log4js.logger = console.log;
166        } else if ((typeof console.log === "object") && Function.prototype.bind) {
167          Log4js.logger = Function.prototype.bind.call(console.log, console);
168        } else if ((typeof console.log === "object") && Function.prototype.call) {
169          Log4js.logger = function() {
170            Function.prototype.call.call(console.log, console, Array.prototype.slice.call(arguments));
171          };
172        }
173      }
174    },
175    _log  : function() {
176      if (!Log4js.logger) {
177        Log4js._initLogger();
178      }
179
180      if (Log4js.logger) {
181        try {
182          Log4js.logger.apply(window.console, arguments);
183        } catch(e) {
184          Log4js._initLogger();
185          if (Log4js.logger) {
186            Log4js.logger.apply(window.console, arguments);
187          }
188        }
189      }
190
191      var logElement = document.getElementById(PushStream.LOG_OUTPUT_ELEMENT_ID);
192      if (logElement) {
193        var str = '';
194        for (var i = 0; i < arguments.length; i++) {
195          str += arguments[i] + " ";
196        }
197        logElement.innerHTML += str + '\n';
198
199        var lines = logElement.innerHTML.split('\n');
200        if (lines.length > 100) {
201          logElement.innerHTML = lines.slice(-100).join('\n');
202        }
203      }
204    }
205  };
206
207  var Ajax = {
208    _getXHRObject : function(crossDomain) {
209      var xhr = false;
210      if (crossDomain) {
211        try { xhr = new window.XDomainRequest(); } catch (e) { }
212        if (xhr) {
213          return xhr;
214        }
215      }
216
217      try { xhr = new window.XMLHttpRequest(); }
218      catch (e1) {
219        try { xhr = new window.ActiveXObject("Msxml2.XMLHTTP"); }
220        catch (e2) {
221          try { xhr = new window.ActiveXObject("Microsoft.XMLHTTP"); }
222          catch (e3) {
223            xhr = false;
224          }
225        }
226      }
227      return xhr;
228    },
229
230    _send : function(settings, post) {
231      settings = settings || {};
232      settings.timeout = settings.timeout || 30000;
233      var xhr = Ajax._getXHRObject(settings.crossDomain);
234      if (!xhr||!settings.url) { return; }
235
236      Ajax.clear(settings);
237
238      settings.xhr = xhr;
239
240      if (window.XDomainRequest && (xhr instanceof window.XDomainRequest)) {
241        xhr.onload = function () {
242          if (settings.afterReceive) { settings.afterReceive(xhr); }
243          if (settings.success) { settings.success(xhr.responseText); }
244        };
245
246        xhr.onerror = xhr.ontimeout = function () {
247          if (settings.afterReceive) { settings.afterReceive(xhr); }
248          if (settings.error) { settings.error(xhr.status); }
249        };
250      } else {
251        xhr.onreadystatechange = function () {
252          if (xhr.readyState === 4) {
253            Ajax.clear(settings);
254            if (settings.afterReceive) { settings.afterReceive(xhr); }
255            if(xhr.status === 200) {
256              if (settings.success) { settings.success(xhr.responseText); }
257            } else {
258              if (settings.error) { settings.error(xhr.status); }
259            }
260          }
261        };
262      }
263
264      if (settings.beforeOpen) { settings.beforeOpen(xhr); }
265
266      var params = {};
267      var body = null;
268      var method = "GET";
269      if (post) {
270        body = objectToUrlParams(settings.data);
271        method = "POST";
272      } else {
273        params = settings.data || {};
274      }
275
276      xhr.open(method, addParamsToUrl(settings.url, extend({}, params, currentTimestampParam())), true);
277
278      if (settings.beforeSend) { settings.beforeSend(xhr); }
279
280      var onerror = function() {
281        Ajax.clear(settings);
282        try { xhr.abort(); } catch (e) { /* ignore error on closing */ }
283        settings.error(304);
284      };
285
286      if (post) {
287        if (xhr.setRequestHeader) {
288          xhr.setRequestHeader("Accept", "application/json");
289          xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
290        }
291      } else {
292        settings.timeoutId = window.setTimeout(onerror, settings.timeout + 2000);
293      }
294
295      xhr.send(body);
296      return xhr;
297    },
298
299    _clear_xhr : function(xhr) {
300      if (xhr) {
301        xhr.onreadystatechange = null;
302      }
303    },
304
305    _clear_script : function(script) {
306      // Handling memory leak in IE, removing and dereference the script
307      if (script) {
308        script.onerror = script.onload = script.onreadystatechange = null;
309        if (script.parentNode) { script.parentNode.removeChild(script); }
310      }
311    },
312
313    _clear_timeout : function(settings) {
314      settings.timeoutId = clearTimer(settings.timeoutId);
315    },
316
317    _clear_jsonp : function(settings) {
318      var callbackFunctionName = settings.data.callback;
319      if (callbackFunctionName) {
320        window[callbackFunctionName] = function() { window[callbackFunctionName] = null; };
321      }
322    },
323
324    clear : function(settings) {
325      Ajax._clear_timeout(settings);
326      Ajax._clear_jsonp(settings);
327      Ajax._clear_script(document.getElementById(settings.scriptId));
328      Ajax._clear_xhr(settings.xhr);
329    },
330
331    jsonp : function(settings) {
332      settings.timeout = settings.timeout || 30000;
333      Ajax.clear(settings);
334
335      var head = document.head || document.getElementsByTagName("head")[0];
336      var script = document.createElement("script");
337      var startTime = getTime();
338
339      var onerror = function() {
340        Ajax.clear(settings);
341        var endTime = getTime();
342        settings.error(((endTime - startTime) > settings.timeout/2) ? 304 : 403);
343      };
344
345      var onload = function() {
346        Ajax.clear(settings);
347        settings.load();
348      };
349
350      var onsuccess = function() {
351        settings.afterSuccess = true;
352        settings.success.apply(null, arguments);
353      };
354
355      script.onerror = onerror;
356      script.onload = script.onreadystatechange = function(eventLoad) {
357        if (!script.readyState || /loaded|complete/.test(script.readyState)) {
358          if (settings.afterSuccess) {
359            onload();
360          } else {
361            onerror();
362          }
363        }
364      };
365
366      if (settings.beforeOpen) { settings.beforeOpen({}); }
367      if (settings.beforeSend) { settings.beforeSend({}); }
368
369      settings.timeoutId = window.setTimeout(onerror, settings.timeout + 2000);
370      settings.scriptId = settings.scriptId || getTime();
371      settings.afterSuccess = null;
372
373      settings.data.callback = settings.scriptId + "_onmessage_" + getTime();
374      window[settings.data.callback] = onsuccess;
375
376      script.setAttribute("src", addParamsToUrl(settings.url, extend({}, settings.data, currentTimestampParam())));
377      script.setAttribute("async", "async");
378      script.setAttribute("id", settings.scriptId);
379
380      // Use insertBefore instead of appendChild to circumvent an IE6 bug.
381      head.insertBefore(script, head.firstChild);
382      return settings;
383    },
384
385    load : function(settings) {
386      return Ajax._send(settings, false);
387    },
388
389    post : function(settings) {
390      return Ajax._send(settings, true);
391    }
392  };
393
394  var escapeText = function(text) {
395    return (text) ? window.escape(text) : '';
396  };
397
398  var unescapeText = function(text) {
399    return (text) ? window.unescape(text) : '';
400  };
401
402  Utils.parseMessage = function(messageText, keys) {
403    var msg = messageText;
404    if (isString(messageText)) {
405      msg = Utils.parseJSON(messageText);
406    }
407
408    var message = {
409        id     : msg[keys.jsonIdKey],
410        channel: msg[keys.jsonChannelKey],
411        text   : isString(msg[keys.jsonTextKey]) ? unescapeText(msg[keys.jsonTextKey]) : msg[keys.jsonTextKey],
412        tag    : msg[keys.jsonTagKey],
413        time   : msg[keys.jsonTimeKey],
414        eventid: msg[keys.jsonEventIdKey] || ""
415    };
416
417    return message;
418  };
419
420  var getBacktrack = function(options) {
421    return (options.backtrack) ? ".b" + Number(options.backtrack) : "";
422  };
423
424  var getChannelsPath = function(channels, withBacktrack) {
425    var path = '';
426    for (var channelName in channels) {
427      if (!channels.hasOwnProperty || channels.hasOwnProperty(channelName)) {
428        path += "/" + channelName + (withBacktrack ? getBacktrack(channels[channelName]) : "");
429      }
430    }
431    return path;
432  };
433
434  var getSubscriberUrl = function(pushstream, prefix, extraParams, withBacktrack) {
435    var websocket = pushstream.wrapper.type === WebSocketWrapper.TYPE;
436    var useSSL = pushstream.useSSL;
437    var port = normalizePort(useSSL, pushstream.port);
438    var url = (websocket) ? ((useSSL) ? "wss://" : "ws://") : ((useSSL) ? "https://" : "http://");
439    url += pushstream.host;
440    url += (port ? (":" + port) : "");
441    url += prefix;
442
443    var channels = getChannelsPath(pushstream.channels, withBacktrack);
444    if (pushstream.channelsByArgument) {
445      var channelParam = {};
446      channelParam[pushstream.channelsArgument] = channels.substring(1);
447      extraParams = extend({}, extraParams, channelParam);
448    } else {
449      url += channels;
450    }
451    return addParamsToUrl(url, extraParams);
452  };
453
454  var getPublisherUrl = function(pushstream) {
455    var port = normalizePort(pushstream.useSSL, pushstream.port);
456    var url = (pushstream.useSSL) ? "https://" : "http://";
457    url += pushstream.host;
458    url += (port ? (":" + port) : "");
459    url += pushstream.urlPrefixPublisher;
460    url += "?id=" + getChannelsPath(pushstream.channels, false);
461
462    return url;
463  };
464
465  Utils.extract_xss_domain = function(domain) {
466    // if domain is an ip address return it, else return ate least the last two parts of it
467    if (domain.match(/^(\d{1,3}\.){3}\d{1,3}$/)) {
468      return domain;
469    }
470
471    var domainParts = domain.split('.');
472    // if the domain ends with 3 chars or 2 chars preceded by more than 4 chars,
473    // we can keep only 2 parts, else we have to keep at least 3 (
473or all domain name)
474    var keepNumber = Math.max(domainParts.length - 1, (domain.match(/(\w{4,}\.\w{2}|\.\w{3,})$/) ? 2 : 3));
475
476    return domainParts.slice(-1 * keepNumber).join('.');
477  };
478
479  var normalizePort = function (useSSL, port) {
480    port = Number(port || (useSSL ? 443 : 80));
481    return ((!useSSL && port === 80) || (useSSL && port === 443)) ? "" : port;
482  };
483
484  Utils.isCrossDomainUrl = function(url) {
485    if (!url) {
486      return false;
487    }
488
489    var parser = document.createElement('a');
490    parser.href = url;
491
492    var srcPort = normalizePort(window.location.protocol === "https:", window.location.port);
493    var dstPort = normalizePort(parser.protocol === "https:", parser.port);
494
495    return (window.location.protocol !== parser.protocol) ||
496           (window.location.hostname !== parser.hostname) ||
497           (srcPort !== dstPort);
498  };
499
500  var linker = function(method, instance) {
501    return function() {
502      return method.apply(instance, arguments);
503    };
504  };
505
506  var clearTimer = function(timer) {
507    if (timer) {
508      window.clearTimeout(timer);
509    }
510    return null;
511  };
512
513  /* common callbacks */
514  var onmessageCallback = function(event) {
515    Log4js.info("[" + this.type + "] message received", arguments);
516    var message = Utils.parseMessage(event.data, this.pushstream);
517    if (message.tag) { this.pushstream._etag = message.tag; }
518    if (message.time) { this.pushstream._lastModified = message.time; }
519    if (message.eventid) { this.pushstream._lastEventId = message.eventid; }
520    this.pushstream._onmessage(message.text, message.id, message.channel, message.eventid, true);
521  };
522
523  var onopenCallback = function() {
524    this.pushstream._onopen();
525    Log4js.info("[" + this.type + "] connection opened");
526  };
527
528  var onerrorCallback = function(event) {
529    Log4js.info("[" + this.type + "] error (disconnected by server):", event);
530    if ((this.pushstream.readyState === PushStream.OPEN) &&
531        (this.type === EventSourceWrapper.TYPE) &&
532        (event.type === 'error') &&
533        (this.connection.readyState === window.EventSource.CONNECTING)) {
534      // EventSource already has a reconnection function using the last-event-id header
535      return;
536    }
537    this._closeCurrentConnection();
538    this.pushstream._onerror({type: ((event && ((event.type === "load") || (event.type === "close"))) || (this.pushstream.readyState === PushStream.CONNECTING)) ? "load" : "timeout"});
539  };
540
541  /* wrappers */
542
543  var WebSocketWrapper = function(pushstream) {
544    if (!window.WebSocket && !window.MozWebSocket) { throw "WebSocket not supported"; }
545    this.type = WebSocketWrapper.TYPE;
546    this.pushstream = pushstream;
547    this.connection = null;
548  };
549
550  WebSocketWrapper.TYPE = "WebSocket";
551
552  WebSocketWrapper.prototype = {
553    connect: function() {
554      this._closeCurrentConnection();
555      var params = extend({}, this.pushstream.extraParams(), currentTimestampParam(), getControlParams(this.pushstream));
556      var url = getSubscriberUrl(this.pushstream, this.pushstream.urlPrefixWebsocket, params, !this.pushstream._useControlArguments());
557      this.connection = (window.WebSocket) ? new window.WebSocket(url) : new window.MozWebSocket(url);
558      this.connection.onerror   = linker(onerrorCallback, this);
559      this.connection.onclose   = linker(onerrorCallback, this);
560      this.connection.onopen    = linker(onopenCallback, this);
561      this.connection.onmessage = linker(onmessageCallback, this);
562      Log4js.info("[WebSocket] connecting to:", url);
563    },
564
565    disconnect: function() {
566      if (this.connection) {
567        Log4js.debug("[WebSocket] closing connection to:", this.connection.url);
568        this.connection.onclose = null;
569        this._closeCurrentConnection();
570        this.pushstream._onclose();
571      }
572    },
573
574    _closeCurrentConnection: function() {
575      if (this.connection) {
576        try { this.connection.close(); } catch (e) { /* ignore error on closing */ }
577        this.connection = null;
578      }
579    },
580
581    sendMessage: function(message) {
582      if (this.connection) { this.connection.send(message); }
583    }
584  };
585
586  var EventSourceWrapper = function(pushstream) {
587    if (!window.EventSource) { throw "EventSource not supported"; }
588    this.type = EventSourceWrapper.TYPE;
589    this.pushstream = pushstream;
590    this.connection = null;
591  };
592
593  EventSourceWrapper.TYPE = "EventSource";
594
595  EventSourceWrapper.prototype = {
596    connect: function() {
597      this._closeCurrentConnection();
598      var params = extend({}, this.pushstream.extraParams(), currentTimestampParam(), getControlParams(this.pushstream));
599      var url = getSubscriberUrl(this.pushstream, this.pushstream.urlPrefixEventsource, params, !this.pushstream._useControlArguments());
600      this.connection = new window.EventSource(url);
601      this.connection.onerror   = linker(onerrorCallback, this);
602      this.connection.onopen    = linker(onopenCallback, this);
603      this.connection.onmessage = linker(onmessageCallback, this);
604      Log4js.info("[EventSource] connecting to:", url);
605    },
606
607    disconnect: function() {
608      if (this.connection) {
609        Log4js.debug("[EventSource] closing connection to:", this.connection.url);
610        this.connection.onclose = null;
611        this._closeCurrentConnection();
612        this.pushstream._onclose();
613      }
614    },
615
616    _closeCurrentConnection: function() {
617      if (this.connection) {
618        try { this.connection.close(); } catch (e) { /* ignore error on closing */ }
619        this.connection = null;
620      }
621    }
622  };
623
624  var StreamWrapper = function(pushstream) {
625    this.type = StreamWrapper.TYPE;
626    this.pushstream = pushstream;
627    this.connection = null;
628    this.url = null;
629    this.frameloadtimer = null;
630    this.pingtimer = null;
631    this.iframeId = "PushStreamManager_" + pushstream.id;
632  };
633
634  StreamWrapper.TYPE = "Stream";
635
636  StreamWrapper.prototype = {
637    connect: function() {
638      this._closeCurrentConnection();
639      var domain = Utils.extract_xss_domain(this.pushstream.host);
640      try {
641        document.domain = domain;
642      } catch(e) {
643        Log4js.error("[Stream] (warning) problem setting document.domain = " + domain + " (OBS: IE8 does not support set IP numbers as domain)");
644      }
645      var params = extend({}, this.pushstream.extraParams(), currentTimestampParam(), {"streamid": this.pushstream.id}, getControlParams(this.pushstream));
646      this.url = getSubscriberUrl(this.pushstream, this.pushstream.urlPrefixStream, params, !this.pushstream._useControlArguments());
647      Log4js.debug("[Stream] connecting to:", this.url);
648      this.loadFrame(this.url);
649    },
650
651    disconnect: function() {
652      if (this.connection) {
653        Log4js.debug("[Stream] closing connection to:", this.url);
654        this._closeCurrentConnection();
655        this.pushstream._onclose();
656      }
657    },
658
659    _clear_iframe: function() {
660      var oldIframe = document.getElementById(this.iframeId);
661      if (oldIframe) {
662        oldIframe.onload = null;
663        oldIframe.src = "about:blank";
664        if (oldIframe.parentNode) { oldIframe.parentNode.removeChild(oldIframe); }
665      }
666    },
667
668    _closeCurrentConnection: function() {
669      this._clear_iframe();
670      if (this.connection) {
671        this.pingtimer = clearTimer(this.pingtimer);
672        this.frameloadtimer = clearTimer(this.frameloadtimer);
673        this.connection = null;
674        this.transferDoc = null;
675        if (typeof window.CollectGarbage === 'function') { window.CollectGarbage(); }
676      }
677    },
678
679    loadFrame: function(url) {
680      this._clear_iframe();
681
682      var ifr = null;
683      if ("ActiveXObject" in window) {
684        var transferDoc = new window.ActiveXObject("htmlfile");
685        transferDoc.open();
686        transferDoc.write("<html><script>document.domain='" + document.domain + "';</script><body><iframe id='" + this.iframeId + "' src='" + url + "'></iframe></body></html>");
687        transferDoc.parentWindow.PushStream = PushStream;
688        transferDoc.close();
689        ifr = transferDoc.getElementById(this.iframeId);
690        this.transferDoc = transferDoc;
691      } else {
692        ifr = document.createElement("IFRAME");
693        ifr.style.width = "1px";
694        ifr.style.height = "1px";
695        ifr.style.border = "none";
696        ifr.style.position = "absolute";
697        ifr.style.top = "-10px";
698        ifr.style.marginTop = "-10px";
699        ifr.style.zIndex = "-20";
700        ifr.PushStream = PushStream;
701        document.body.appendChild(ifr);
702        ifr.setAttribute("src", url);
703        ifr.setAttribute("id", this.iframeId);
704      }
705
706      ifr.onload = linker(onerrorCallback, this);
707      this.connection = ifr;
708      this.frameloadtimer = window.setTimeout(linker(onerrorCallback, this), this.pushstream.timeout);
709    },
710
711    register: function(iframeWindow) {
712      this.frameloadtimer = clearTimer(this.frameloadtimer);
713      iframeWindow.p = linker(this.process, this);
714      this.connection.onload = linker(this._onframeloaded, this);
715      this.pushstream._onopen();
716      this.setPingTimer();
717      Log4js.info("[Stream] frame registered");
718    },
719
720    process: function(id, channel, text, eventid, time, tag) {
721      this.pingtimer = clearTimer(this.pingtimer);
722      Log4js.info("[Stream] message received", arguments);
723      if (id !== -1) {
724        if (tag) { this.pushstream._etag = tag; }
725        if (time) { this.pushstream._lastModified = time; }
726        if (eventid) { this.pushstream._lastEventId = eventid; }
727      }
728      this.pushstream._onmessage(unescapeText(text), id, channel, eventid || "", true);
729      this.setPingTimer();
730    },
731
732    _onframeloaded: function() {
733      Log4js.info("[Stream] frame loaded (disconnected by server)");
734      this.connection.onload = null;
735      this.disconnect();
736    },
737
738    setPingTimer: function() {
739      if (this.pingtimer) { clearTimer(this.pingtimer); }
740      this.pingtimer = window.setTimeout(linker(onerrorCallback, this), this.pushstream.pingtimeout);
741    }
742  };
743
744  var LongPollingWrapper = function(pushstream) {
745    this.type = LongPollingWrapper.TYPE;
746    this.pushstream = pushstream;
747    this.connection = null;
748    this.opentimer = null;
749    this.messagesQueue = [];
750    this._linkedInternalListen = linker(this._internalListen, this);
751    this.xhrSettings = {
752        timeout: this.pushstream.timeout,
753        data: {},
754        url: null,
755        success: linker(this.onmessage, this),
756        error: linker(this.onerror, this),
757        load: linker(this.onload, this),
758        beforeSend: linker(this.beforeSend, this),
759        afterReceive: linker(this.afterReceive, this)
760    };
761  };
762
763  LongPollingWrapper.TYPE = "LongPolling";
764
765  LongPollingWrapper.prototype = {
766    connect: function() {
767      this.messagesQueue = [];
768      this._closeCurrentConnection();
769      this.urlWithBacktrack = getSubscriberUrl(this.pushstream, this.pushstream.urlPrefixLongpolling, {}, true);
770      this.urlWithoutBacktrack = getSubscriberUrl(this.pushstream, this.pushstream.urlPrefixLongpolling, {}, false);
771      this.xhrSettings.url = this.urlWithBacktrack;
772      this.useJSONP = this.pushstream._crossDomain || this.pushstream.useJSONP;
773      this.xhrSettings.scriptId = "PushStreamManager_" + this.pushstream.id;
774      if (this.useJSONP) {
775        this.pushstream.messagesControlByArgument = true;
776      }
777      this._listen();
778      this.opentimer = window.setTimeout(linker(onopenCallback, this), 150);
779      Log4js.info("[LongPolling] connecting to:", this.xhrSettings.url);
780    },
781
782    _listen: function() {
783      if (this._internalListenTimeout) { clearTimer(this._internalListenTimeout); }
784      this._internalListenTimeout = window.setTimeout(this._linkedInternalListen, 100);
785    },
786
787    _internalListen: function() {
788      if (this.pushstream._keepConnected) {
789        this.xhrSettings.url = this.pushstream._useControlArguments() ? this.urlWithoutBacktrack : this.urlWithBacktrack;
790        this.xhrSettings.data = extend({}, this.pushstream.extraParams(), this.xhrSettings.data, getControlParams(this.pushstream));
791        if (this.useJSONP) {
792          this.connection = Ajax.jsonp(this.xhrSettings);
793        } else if (!this.connection) {
794          this.connection = Ajax.load(this.xhrSettings);
795        }
796      }
797    },
798
799    disconnect: function() {
800      if (this.connection) {
801        Log4js.debug("[LongPolling] closing connection to:", this.xhrSettings.url);
802        this._closeCurrentConnection();
803        this.pushstream._onclose();
804      }
805    },
806
807    _closeCurrentConnection: function() {
808      this.opentimer = clearTimer(this.opentimer);
809      if (this.connection) {
810        try { this.connection.abort(); } catch (e) {
811          try { Ajax.clear(this.connection); } catch (e1) { /* ignore error on closing */ }
812        }
813        this.connection = null;
814        this.xhrSettings.url = null;
815      }
816    },
817
818    beforeSend: function(xhr) {
819      if (!this.pushstream.messagesControlByArgument) {
820        xhr.setRequestHeader("If-None-Match", this.pushstream._etag);
821        xhr.setRequestHeader("If-Modified-Since", this.pushstream._lastModified);
822      }
823    },
824
825    afterReceive: function(xhr) {
826      if (!this.pushstream.messagesControlByArgument) {
827        this.pushstream._etag = xhr.getResponseHeader('Etag');
828        this.pushstream._lastModified = xhr.getResponseHeader('Last-Modified');
829      }
830      this.connection = null;
831    },
832
833    onerror: function(status) {
834      this._closeCurrentConnection();
835      if (this.pushstream._keepConnected) { /* abort(), called by disconnect(), call this callback, but should be ignored */
836        if (status === 304) {
837          this._listen();
838        } else {
839          Log4js.info("[LongPolling] error (disconnected by server):", status);
840          this.pushstream._onerror({type: ((status === 403) || (this.pushstream.readyState === PushStream.CONNECTING)) ? "load" : "timeout"});
841        }
842      }
843    },
844
845    onload: function() {
846      this._listen();
847    },
848
849    onmessage: function(responseText) {
850      if (this._internalListenTimeout) { clearTimer(this._internalListenTimeout); }
851      Log4js.info("[LongPolling] message received", responseText);
852      var lastMessage = null;
853      var messages = isArray(responseText) ? responseText : responseText.replace(/\}\{/g, "}\r\n{").split("\r\n");
854      for (var i = 0; i < messages.length; i++) {
855        if (messages[i]) {
856          lastMessage = Utils.parseMessage(messages[i], this.pushstream);
857          this.messagesQueue.push(lastMessage);
858          if (this.pushstream.messagesControlByArgument && lastMessage.time) {
859            this.pushstream._etag = lastMessage.tag;
860            this.pushstream._lastModified = lastMessage.time;
861          }
862        }
863      }
864
865      this._listen();
866
867      while (this.messagesQueue.length > 0) {
868        var message = this.messagesQueue.shift();
869        this.pushstream._onmessage(message.text, message.id, message.channel, message.eventid, (this.messagesQueue.length === 0));
870      }
871    }
872  };
873
874  /* mains class */
875
876  var PushStreamManager = [];
877
878  var PushStream = function(settings) {
879    settings = settings || {};
880
881    this.id = PushStreamManager.push(this) - 1;
882
883    this.useSSL = settings.useSSL || false;
884    this.host = settings.host || window.location.hostname;
885    this.port = Number(settings.port || (this.useSSL ? 443 : 80));
886
887    this.timeout = settings.timeout || 30000;
888    this.pingtimeout = settings.pingtimeout || 30000;
889    this.reconnectOnTimeoutInterval = settings.reconnectOnTimeoutInterval || 3000;
890    this.reconnectOnChannelUnavailableInterval = settings.reconnectOnChannelUnavailableInterval || 60000;
891
892    this.lastEventId = settings.lastEventId || null;
893    this.messagesPublishedAfter = settings.messagesPublishedAfter;
894    this.messagesControlByArgument = settings.messagesControlByArgument || false;
895    this.tagArgument   = settings.tagArgument  || 'tag';
896    this.timeArgument  = settings.timeArgument || 'time';
897    this.eventIdArgument  = settings.eventIdArgument || 'eventid';
898    this.useJSONP      = settings.useJSONP     || false;
899
900    this._reconnecttimer = null;
901    this._etag = 0;
902    this._lastModified = null;
903    this._lastEventId = null;
904
905    this.urlPrefixPublisher   = settings.urlPrefixPublisher   || '/pub';
906    this.urlPrefixStream      = settings.urlPrefixStream      || '/sub';
907    this.urlPrefixEventsource = settings.urlPrefixEventsource || '/ev';
908    this.urlPrefixLongpolling = settings.urlPrefixLongpolling || '/lp';
909    this.urlPrefixWebsocket   = settings.urlPrefixWebsocket   || '/ws';
910
911    this.jsonIdKey      = settings.jsonIdKey      || 'id';
912    this.jsonChannelKey = settings.jsonChannelKey || 'channel';
913    this.jsonTextKey    = settings.jsonTextKey    || 'text';
914    this.jsonTagKey     = settings.jsonTagKey     || 'tag';
915    this.jsonTimeKey    = settings.jsonTimeKey    || 'time';
916    this.jsonEventIdKey = settings.jsonEventIdKey || 'eventid';
917
918    this.modes = (settings.modes || 'eventsource|websocket|stream|longpolling').split('|');
919    this.wrappers = [];
920    this.wrapper = null;
921
922    this.onchanneldeleted = settings.onchanneldeleted || null;
923    this.onmessage = settings.onmessage || null;
924    this.onerror = settings.onerror || null;
925    this.onstatuschange = settings.onstatuschange || null;
926    this.extraParams    = settings.extraParams    || function() { return {}; };
927
928    this.channels = {};
929    this.channelsCount = 0;
930    this.channelsByArgument   = settings.channelsByArgument   || false;
931    this.channelsArgument     = settings.channelsArgument     || 'channels';
932
933    this._crossDomain = Utils.isCrossDomainUrl(getPublisherUrl(this));
934
935    for (var i = 0; i < this.modes.length; i++) {
936      try {
937        var wrapper = null;
938        switch (this.modes[i]) {
939        case "websocket"  : wrapper = new WebSocketWrapper(this);   break;
940        case "eventsource": wrapper = new EventSourceWrapper(this); break;
941        case "longpolling": wrapper = new LongPollingWrapper(this); break;
942        case "stream"     : wrapper = new StreamWrapper(this);      break;
943        }
944        this.wrappers[this.wrappers.length] = wrapper;
945      } catch(e) { Log4js.info(e); }
946    }
947
948    this.readyState = 0;
949  };
950
951  /* constants */
952  PushStream.LOG_LEVEL = 'error'; /* debug, info, error */
953  PushStream.LOG_OUTPUT_ELEMENT_ID = 'Log4jsLogOutput';
954
955  /* status codes */
956  PushStream.CLOSED        = 0;
957  PushStream.CONNECTING    = 1;
958  PushStream.OPEN          = 2;
959
960  /* main code */
961  PushStream.prototype = {
962    addChannel: function(channel, options) {
963      if (escapeText(channel) !== channel) {
964        throw "Invalid channel name! Channel has to be a set of [a-zA-Z0-9]";
965      }
966      Log4js.debug("entering addChannel");
967      if (typeof(this.channels[channel]) !== "undefined") { throw "Cannot add channel " + channel + ": already subscribed"; }
968      options = options || {};
969      Log4js.info("adding channel", channel, options);
970      this.channels[channel] = options;
971      this.channelsCount++;
972      if (this.readyState !== PushStream.CLOSED) { this.connect(); }
973      Log4js.debug("leaving addChannel");
974    },
975
976    removeChannel: function(channel) {
977      if (this.channels[channel]) {
978        Log4js.info("removing channel", channel);
979        delete this.channels[channel];
980        this.channelsCount--;
981      }
982    },
983
984    removeAllChannels: function() {
985      Log4js.info("removing all channels");
986      this.channels = {};
987      this.channelsCount = 0;
988    },
989
990    _setState: function(state) {
991      if (this.readyState !== state) {
992        Log4js.info("status changed", state);
993        this.readyState = state;
994        if (this.onstatuschange) {
995          this.onstatuschange(this.readyState);
996        }
997      }
998    },
999
1000    connect: function() {
1001      Log4js.debug("entering connect");
1002      if (!this.host)                 { throw "PushStream host not specified"; }
1003      if (isNaN(this.port))           { throw "PushStream port not specified"; }
1004      if (!this.channelsCount)        { throw "No channels specified"; }
1005      if (this.wrappers.length === 0) { throw "No available support for this browser"; }
1006
1007      this._keepConnected = true;
1008      this._lastUsedMode = 0;
1009      this._connect();
1010
1011      Log4js.debug("leaving connect");
1012    },
1013
1014    disconnect: function() {
1015      Log4js.debug("entering disconnect");
1016      this._keepConnected = false;
1017      this._disconnect();
1018      this._setState(PushStream.CLOSED);
1019      Log4js.info("disconnected");
1020      Log4js.debug("leaving disconnect");
1021    },
1022
1023    _useControlArguments :function() {
1024      return this.messagesControlByArgument && ((this._lastModified !== null) || (this._lastEventId !== null));
1025    },
1026
1027    _connect: function() {
1028      if (this._lastEventId === null) {
1029        this._lastEventId = this.lastEventId;
1030      }
1031      if (this._lastModified === null) {
1032        var date = this.messagesPublishedAfter;
1033        if (!isDate(date)) {
1034          var messagesPublishedAfter = Number(this.messagesPublishedAfter);
1035          if (messagesPublishedAfter > 0) {
1036            date = new Date();
1037            date.setTime(date.getTime() - (messagesPublishedAfter * 1000));
1038          } else if (messagesPublishedAfter < 0) {
1039            date = new Date(0);
1040          }
1041        }
1042
1043        if (isDate(date)) {
1044          this._lastModified = Utils.dateToUTCString(date);
1045        }
1046      }
1047
1048      this._disconnect();
1049      this._setState(PushStream.CONNECTING);
1050      this.wrapper = this.wrappers[this._lastUsedMode++ % this.wrappers.length];
1051
1052      try {
1053        this.wrapper.connect();
1054      } catch (e) {
1055        //each wrapper has a cleanup routine at disconnect method
1056        if (this.wrapper) {
1057          this.wrapper.disconnect();
1058        }
1059      }
1060    },
1061
1062    _disconnect: function() {
1063      this._reconnecttimer = clearTimer(this._reconnecttimer);
1064      if (this.wrapper) {
1065        this.wrapper.disconnect();
1066      }
1067    },
1068
1069    _onopen: function() {
1070      this._reconnecttimer = clearTimer(this._reconnecttimer);
1071      this._setState(PushStream.OPEN);
1072      if (this._lastUsedMode > 0) {
1073        this._lastUsedMode--; //use same mode on next connection
1074      }
1075    },
1076
1077    _onclose: function() {
1078      this._reconnecttimer = clearTimer(this._reconnecttimer);
1079      this._setState(PushStream.CLOSED);
1080      this._reconnect(this.reconnectOnTimeoutInterval);
1081    },
1082
1083    _onmessage: function(text, id, channel, eventid, isLastMessageFromBatch) {
1084      Log4js.debug("message", text, id, channel, eventid, isLastMessageFromBatch);
1085      if (id === -2) {
1086        if (this.onchanneldeleted) { this.onchanneldeleted(channel); }
1087      } else if (id > 0) {
1088        if (this.onmessage) { this.onmessage(text, id, channel, eventid, isLastMessageFromBatch); }
1089      }
1090    },
1091
1092    _onerror: function(error) {
1093      this._setState(PushStream.CLOSED);
1094      this._reconnect((error.type === "timeout") ? this.reconnectOnTimeoutInterval : this.reconnectOnChannelUnavailableInterval);
1095      if (this.onerror) { this.onerror(error); }
1096    },
1097
1098    _reconnect: function(timeout) {
1099      if (this._keepConnected && !this._reconnecttimer && (this.readyState !== PushStream.CONNECTING)) {
1100        Log4js.info("trying to reconnect in", timeout);
1101        this._reconnecttimer = window.setTimeout(linker(this._connect, this), timeout);
1102      }
1103    },
1104
1105    sendMessage: function(message, successCallback, errorCallback) {
1106      message = escapeText(message);
1107      if (this.wrapper.type === WebSocketWrapper.TYPE) {
1108        this.wrapper.sendMessage(message);
1109        if (successCallback) { successCallback(); }
1110      } else {
1111        Ajax.post({url: getPublisherUrl(this), data: message, success: successCallback, error: errorCallback, crossDomain: this._crossDomain});
1112      }
1113    }
1114  };
1115
1116  PushStream.sendMessage = function(url, message, successCallback, errorCallback) {
1117    Ajax.post({url: url, data: escapeText(message), success: successCallback, error: errorCallback});
1118  };
1119
1120  // to make server header template more clear, it calls register and
1121  // by a url parameter we find the stream wrapper instance
1122  PushStream.register = function(iframe) {
1123    var matcher = iframe.window.location.href.match(/streamid=([0-9]*)&?/);
1124    if (matcher[1] && PushStreamManager[matcher[1]]) {
1125      PushStreamManager[matcher[1]].wrapper.register(iframe);
1126    }
1127  };
1128
1129  PushStream.unload = function() {
1130    for (var i = 0; i < PushStreamManager.length; i++) {
1131      try { PushStreamManager[i].disconnect(); } catch(e){}
1132    }
1133  };
1134
1135  /* make class public */
1136  window.PushStream = PushStream;
1137  window.PushStreamManager = PushStreamManager;
1138
1139  if (window.attachEvent) { window.attachEvent("onunload", PushStream.unload); }
1140  if (window.addEventListener) { window.addEventListener.call(window, "unload", PushStream.unload, false); }
1141
1142})(window, document);
1143
1144function http_push_alert(resp,id,channel){
1145
1146	alert(dump(resp));
1147
1148}
1149
1150function http_push_navigate(resp,id,channel){
1151
1152	window.location.href=resp.payload.url;
1153
1154}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.