PageSourceSearch

https://hn24.hnonline.sk/templates/site/js/pushstream.js

js hnonline.sk collected 2026-10-01 09:12:12 UTC 40,656 bytes, 1,144 lines download raw bytes

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

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.