websocket graceful shutdown and safety queue
[brisk.git] / web / xynt-streaming.js
1 // old targetpage == page and moved into start method
2
3 //
4 // CLASS transport_ws
5 //
6 function transport_ws(doc, xynt_streaming, page)
7 {
8     // if four arguments manage if WS or WSS connection
9     if (arguments.length > 3)
10         this.is_secure = arguments[3];
11     else
12         this.is_secure = false;
13
14     if (this.is_secure)
15         this.name = "WebSocketSecure";
16     else
17         this.name = "WebSocket";
18     this.ctx_new = "";
19     this.out_queue = [];
20     var self = this;
21
22     this.doc = doc;
23     this.failed = false;
24     this.xynt_streaming = xynt_streaming;
25     try {
26         this.xynt_streaming.log("PAGE: "+page);
27         this.ws = new WebSocket(page);
28         this.ws.onopen = function () {
29             console.log('WS On open');
30
31             self.xynt_streaming.log("onopen");
32             if (this.readyState == 1) {
33                 // connected
34                 self.ws_cb("open");
35                 self.init_steps = 1;
36             }
37         };
38         this.ws.onmessage = function (msg) {
39             console.log('WS On message');
40             self.xynt_streaming.log("onmessage");
41             // new data in msg.data
42             self.ctx_new += msg.data;
43         };
44         this.ws.onclose = function (msg) {
45             console.log('WS On close');
46             self.onopen  = null;
47             self.onclose = null;
48             self.onerror = null;
49             self.xynt_streaming.log("onclose"+self.init_steps);
50             if (self.init_steps == 0)
51                 self.ws_cb("error");
52             else
53                 self.ws_cb("close");
54         };
55         this.ws.onerror = function () {
56             // on error
57             this.onopen  = null;
58             this.onclose = null;
59             this.onerror = null;
60             self.xynt_streaming.log("onerror");
61             self.ws_cb("error");
62         };
63     }
64     catch (ex) {
65         throw "websocket creation failed";
66     }
67
68     this.stopped = false;
69 }
70
71 transport_ws.prototype = {
72     doc: null,
73     name: null,
74     xynt_streaming: "ready",
75     ws: null,
76     out_queue: null,
77     stopped: true,
78     failed: false,
79
80     init_steps: 0,
81
82     ctx_old: "",
83     ctx_old_len: 0,
84     ctx_new: "",
85
86     // script_clean: 0,
87
88     destroy: function () { /* public */
89         if (this.ws != null) {
90             this.ws_abort();
91         }
92         delete this.ws;
93     },
94
95     ws_cb: function (from) {
96         var ret;
97
98         if (from == "error") {
99             if (this.xynt_streaming != "ready") {
100                 if (this.xynt_streaming.transp_fback > 0) {
101 this.xynt_streaming.log("DEC: "+this.xynt_streaming.transp_fback);
102                     this.xynt_streaming.transp_fback--;
103                     this.stopped = true;
104                     this.xynt_streaming.reload();
105                 }
106             }
107         }
108         else if (from == "open") {
109             this.flush_out_queue();
110         }
111
112         if (this.ws != null && this.ws.readyState > 1) {
113             this.stopped = true;
114         }
115     },
116
117     flush_out_queue: function() {
118         var l_out = this.out_queue.length;
119         if (l_out == 0)
120             return;
121
122         for (var i = 0 ; i < l_out ; i++) {
123             if (this.ws.readyState != 1) {
124                 break;
125             }
126             var item = this.out_queue.shift();
127             var sent = true;
128             try {
129                 this.ws.send(item);
130             }
131             catch (ex) {
132                 this.out_queue.unshift(item);
133                 break;
134             }
135         }
136     },
137
138     send: function(msg) {
139         console.log('new send');
140         if (this.ws && this.ws.readyState == 1) {
141             try {
142                 console.log('Try send ... ');
143                 this.flush_out_queue();
144                 this.ws.send(msg);
145                 console.log(' ... done');
146             }
147             catch (ex) {
148                 console.log(' ... catched exception');
149                 this.flush_out.push(msg);
150             }
151         }
152         else {
153             console.log('ws not ready: push into flush_out');
154             this.flush_out.push(msg);
155         }
156     },
157
158     ws_abort: function() {
159         if (this.ws != null) {
160 this.xynt_streaming.log("WSCLOSE");
161             this.ws.close();
162         }
163     },
164
165     xstr_is_init: function () { /* public */
166         return (true);
167     },
168
169     /* only after a successfull is_initialized call */
170     xstr_is_ready: function () { /* public */
171         return (this.ws.readyState == 1);
172     },
173
174     xstr_set: function () { /* public */
175         // already set
176     },
177
178     ctx_new_is_set: function () { /* public */
179         return (this.ctx_new != null);
180     },
181
182     ctx_new_curlen_get: function () { /* public */
183         return (this.ctx_new.length);
184     },
185
186     ctx_new_getchar: function(idx) { /* public */
187         return (this.ctx_new[idx]);
188     },
189
190     ctx_old_len_is_set: function () { /* public */
191         return (true);
192     },
193
194     ctx_old_len_get: function () { /* public */
195         return (this.ctx_old_len);
196     },
197
198     ctx_old_len_set: function (len) { /* public */
199         this.ctx_old_len = len;
200     },
201
202     ctx_old_len_add: function (len) { /* public */
203         this.ctx_old_len += len;
204     },
205
206     new_part: function () { /* public */
207         return (this.ctx_new.substr(this.ctx_old_len));
208     },
209
210     scrcls_set: function (step) { /* public */
211         // this.script_clean = step;
212     },
213
214     postproc: function () {
215         if (this.stopped && !this.xstr_is_ready()) {
216             this.xynt_streaming.reload();
217         }
218     }
219 }
220
221 //
222 // CLASS transport_xhr
223 //
224 function transport_xhr(doc, xynt_streaming, page)
225 {
226     this.name = "XHR";
227     this.doc = doc;
228     this.xynt_streaming = xynt_streaming;
229     this.xhr = createXMLHttpRequest();
230     this.xhr.open('GET', page);
231
232     var self = this;
233     this.xhr.onreadystatechange = function () { self.xhr_cb(); };
234     this.xhr.send(null);
235
236     this.stopped = false;
237 }
238
239 transport_xhr.prototype = {
240     doc: null,
241     name: null,
242     xynt_streaming: "ready",
243     xhr: null,
244     stopped: true,
245
246     ctx_old: "",
247     ctx_old_len: 0,
248     ctx_new: null,
249
250     // script_clean: 0,
251
252     destroy: function () { /* public */
253         if (this.xhr != null) {
254             this.xhr_abort();
255         }
256         delete this.xhr;
257     },
258
259     xhr_cb: function () {
260         var ret;
261
262         if (this.xhr.readyState == 4) {
263             // console.log("SS: "+safestatus(xhr));
264
265             // NOTE: delay management later
266             // try {
267             //     if ((ret = safestatus(this.xhr)) == 200) {
268             //         this.delay = 0;
269             //         // console.log("del a null "+this.delayed);
270             //     } else if (ret != -1) {
271             //         this.delay = 5000;
272             //         this.hbit('X');
273             //         // alert('There was a problem with the request.' + ret);
274             //     }
275             // } catch(b) {};
276
277             // this.delayed = null;
278             this.stopped = true;
279         }
280     },
281
282     xhr_abort: function() {
283         if (this.xhr != null) {
284             this.xhr.abort();
285         }
286     },
287
288     xstr_is_init: function () { /* public */
289         try {
290             if (this.xhr.responseText != null) {
291                 this.ctx_new = this.xhr.responseText;
292             }
293         }
294         catch (e) {
295         }
296
297         return (this.ctx_new != null);
298     },
299
300     /* only after a successfull is_initialized call */
301     xstr_is_ready: function () { /* public */
302         return (this.xynt_streaming == "ready");
303     },
304
305     xstr_set: function () { /* public */
306         // already set
307     },
308
309     ctx_new_is_set: function () { /* public */
310         return (this.ctx_new != null);
311     },
312
313     ctx_new_curlen_get: function () { /* public */
314         return (this.ctx_new.length);
315     },
316
317     ctx_new_getchar: function(idx) { /* public */
318         return (this.ctx_new[idx]);
319     },
320
321     ctx_old_len_is_set: function () { /* public */
322         return (true);
323     },
324
325     ctx_old_len_get: function () { /* public */
326         return (this.ctx_old_len);
327     },
328
329     ctx_old_len_set: function (len) { /* public */
330         this.ctx_old_len = len;
331     },
332
333     ctx_old_len_add: function (len) { /* public */
334         this.ctx_old_len += len;
335     },
336
337     new_part: function () { /* public */
338         return (this.ctx_new.substr(this.ctx_old_len));
339     },
340
341     scrcls_set: function (step) { /* public */
342         // this.script_clean = step;
343     },
344
345     postproc: function () {
346         if (this.stopped && !this.xstr_is_ready()) {
347             this.xynt_streaming.reload();
348         }
349     }
350 }
351
352 //
353 // CLASS transport_htmlfile
354 //
355 function transport_htmlfile(doc, xynt_streaming, page)
356 {
357     this.name = "HTMLFile";
358     this.doc = doc;
359     this.xynt_streaming = xynt_streaming;
360     this.transfdoc = new ActiveXObject("htmlfile");
361     this.transfdoc.open();
362     this.transfdoc.write("<html><body><iframe id='iframe'></iframe></body></html>");
363     this.transfdoc.close();
364
365     this.ifra = this.transfdoc.getElementById("iframe");
366     this.ifra.contentWindow.location.href = page;
367     this.stopped = false;
368 }
369
370 transport_htmlfile.prototype = {
371     doc: null,
372     name: null,
373     xynt_streaming: null,
374     stopped: true,
375     ifra: null,
376     tradoc: null,
377
378     destroy: function () { /* public */
379         if (this.ifra != null) {
380         //     this.doc.body.removeChild(this.ifra);
381         //     delete this.ifra;
382              this.ifra = null;
383         }
384
385         if (this.transfdoc) {
386             delete this.transfdoc;
387             this.transfdoc = null;
388         }
389     },
390
391     xstr_is_init: function () { /* public */
392         return (typeof(this.ifra.contentWindow.xynt_streaming) != 'undefined');
393     },
394
395     /* only after a successfull is_initialized call */
396     xstr_is_ready: function () { /* public */
397         return (this.ifra.contentWindow.xynt_streaming == "ready");
398     },
399
400     /* only after a successfull is_initialized call */
401     xstr_set: function () { /* public */
402         if (this.ifra.contentWindow.xynt_streaming == "ready") {
403             this.ifra.contentWindow.xynt_streaming = this.xynt_streaming;
404             return (true);
405         }
406         else if (this.ifra.contentWindow.xynt_streaming == this.xynt_streaming) {
407             return (true);
408         }
409         else {
410             return (false);
411         }
412     },
413
414     ctx_new_is_set: function () { /* public */
415         return (typeof(this.ifra.contentWindow.ctx_new) != 'undefined');
416     },
417
418     ctx_new_curlen_get: function () { /* public */
419         return (this.ifra.contentWindow.ctx_new.length);
420     },
421
422     ctx_new_getchar: function(idx) { /* public */
423         return (this.ifra.contentWindow.ctx_new.charAt(idx));
424     },
425
426     ctx_old_len_is_set: function () { /* public */
427         return (typeof(this.ifra.contentWindow.ctx_old_len) != 'undefined');
428     },
429
430     ctx_old_len_get: function () { /* public */
431         return (this.ifra.contentWindow.ctx_old_len);
432     },
433
434     ctx_old_len_set: function (len) { /* public */
435         this.ifra.contentWindow.ctx_old_len = len;
436     },
437
438     ctx_old_len_add: function (len) { /* public */
439         this.ifra.contentWindow.ctx_old_len += len;
440     },
441
442     new_part: function () { /* public */
443         return (this.ifra.contentWindow.ctx_new.substr(this.ifra.contentWindow.ctx_old_len));
444     },
445
446     scrcls_set: function (step) { /* public */
447         this.ifra.contentWindow.script_clean = step;
448     },
449
450     postproc: function () { /* public */
451         if (this.stopped && !this.xstr_is_ready()) {
452             this.xynt_streaming.reload();
453         }
454     }
455 }
456
457 //
458 // CLASS transport_iframe
459 //
460 function transport_iframe(doc, xynt_streaming, page)
461 {
462     this.name = "IFRAME";
463     this.doc = doc;
464     this.xynt_streaming = xynt_streaming;
465     this.ifra = doc.createElement("iframe");
466     this.ifra.style.visibility = "hidden";
467     doc.body.appendChild(this.ifra);
468     this.ifra.contentWindow.location.href = page;
469     this.stopped = false;
470 }
471
472 transport_iframe.prototype = {
473     doc: null,
474     name: null,
475     xynt_streaming: null,
476     stopped: true,
477     ifra: null,
478
479     destroy: function () { /* public */
480         try {
481             if (this.ifra != null) {
482                 // NOTE:  on Opera this remove child crash js if called from
483                 //        inside of the iframe, on IE on Windows without
484                 //        it stream abort fails.
485                 //        the problem is fixed setting into the iframe's onload
486                 //        function the stopped attribute to true and delegate
487                 //        postproc() fired by xynt_streaming watchdog()
488                 this.doc.body.removeChild(this.ifra);
489                 delete this.ifra;
490                 this.ifra = null;
491             }
492         } catch (b) {
493             alert("destroy exception catched");
494         }
495     },
496
497     xstr_is_init: function () { /* public */
498         return (typeof(this.ifra.contentWindow.xynt_streaming) != 'undefined');
499     },
500
501     /* only after a successfull is_initialized call */
502     xstr_is_ready: function () { /* public */
503         return (this.ifra.contentWindow.xynt_streaming == "ready");
504     },
505
506     /* only after a successfull is_initialized call */
507     xstr_set: function () { /* public */
508         if (this.ifra.contentWindow.xynt_streaming == "ready") {
509             this.ifra.contentWindow.xynt_streaming = this.xynt_streaming;
510             return (true);
511         }
512         else if (this.ifra.contentWindow.xynt_streaming == this.xynt_streaming) {
513             return (true);
514         }
515         else {
516             return (false);
517         }
518     },
519
520
521     /* only after a successfull is_ready call to be sure the accessibility of the var */
522     xstr_set_old: function (xynt_streaming) { /* public */
523         this.ifra.contentWindow.xynt_streaming = xynt_streaming;
524     },
525
526     ctx_new_is_set: function () { /* public */
527         return (typeof(this.ifra.contentWindow.ctx_new) != 'undefined');
528     },
529
530     ctx_new_curlen_get: function () { /* public */
531         return (this.ifra.contentWindow.ctx_new.length);
532     },
533
534     ctx_new_getchar: function(idx) { /* public */
535         return (this.ifra.contentWindow.ctx_new.charAt(idx));
536     },
537
538     ctx_old_len_is_set: function () { /* public */
539         return (typeof(this.ifra.contentWindow.ctx_old_len) != 'undefined');
540     },
541
542     ctx_old_len_get: function () { /* public */
543         return (this.ifra.contentWindow.ctx_old_len);
544     },
545
546     ctx_old_len_set: function (len) { /* public */
547         this.ifra.contentWindow.ctx_old_len = len;
548     },
549
550     ctx_old_len_add: function (len) { /* public */
551         this.ifra.contentWindow.ctx_old_len += len;
552     },
553
554     new_part: function () { /* public */
555         return (this.ifra.contentWindow.ctx_new.substr(this.ifra.contentWindow.ctx_old_len));
556     },
557
558     scrcls_set: function (step) { /* public */
559         this.ifra.contentWindow.script_clean = step;
560     },
561
562     postproc: function () { /* public */
563         if (this.stopped && !this.xstr_is_ready()) {
564             this.xynt_streaming.reload();
565         }
566     }
567 }
568
569 function xynt_streaming(win, transp_type, transp_port, transp_fback, console, gst, from, cookiename, sess, sandbox, page, cmdproc)
570 {
571     this.win = win;
572     this.transp_type = transp_type;
573     this.transp_port = transp_port;
574     this.transp_fback = transp_fback;
575     this.console = console;
576     this.gst = gst;
577     this.from = from;
578     this.cookiename = cookiename;
579     this.sess = sess;
580     this.sandbox = sandbox;
581     this.page = page;
582     this.cmdproc = cmdproc;
583     // this.cmdproc = function(com){/* console.log("COM: "+com); */ eval(com);}
584     this.doc = win.document;
585     this.keepalive_old = -1;
586     this.keepalive_new = -1;
587
588     this.mon_errtime = this.keepalives_eq_max * this.watchdog_checktm * this.watchdog_timeout;
589     this.mon_wrntime = this.mon_errtime / 2;
590
591     this.mon_update();
592 }
593
594 xynt_streaming.prototype = {
595     win:               null,
596     transp_type:       null,
597     transp_port:         80,
598     transp_type_cur:   null,
599     transp_port_cur:     80,
600     transp_fback:         0,
601     transp:            null,
602     console:           null,
603     gst:               null,
604     from:              null,
605     cookiename:        null,
606     sess:              null,
607     sandbox:           null,
608     page:              null,
609     cmdproc:           null,
610
611     start_time:        0,
612     restart_wait:      5000, // wait restart_wait millisec before begin to check if restart is needed
613
614     doc:               null,
615     cookiepath: "/brisk/",
616     watchdog_hdl:      null,
617     hbit:              function () {},
618     keepalive_old:    -1,
619     keepalive_new:    -1,
620     keepalives_equal:  0,
621     /* NOTE: right watch_timeout value to 100, for devel reasons use 1000 or more */
622     /* restart after  4 * 40 * 100 millisec if server ping is missing => 16secs */
623     keepalives_eq_max: 4,
624     watchdog_checktm:  40,
625     watchdog_timeout:  100,
626     watchdog_ct:       0,
627     watchable:         false,
628     restart_n:         0,
629     comm_match:        /_*@BEGIN@(.*?)@END@/g,
630     comm_clean:        /_*@BEGIN@(.*?)@END@/,
631     stream:            "",
632     the_end:           false,
633
634     mon_time:         -1,
635     mon_wrntime:       0,
636     mon_errtime:       0,
637
638     mon_stat_old:      "",
639
640     mon_update: function()
641     {
642         var date = new Date();
643
644         this.mon_time = date.getTime();
645     },
646
647     /*
648       ping arrives at least every RD_KEEPALIVE_TOUT secs (currently 4 secs)
649
650       return values: 0 unknown
651                      1 ok
652                      2 warning
653                      3 error
654      */
655     mon_status: function()
656     {
657         var delta, date;
658
659         var date = new Date();
660
661         delta = date.getTime() - this.mon_time;
662
663         if (delta < this.mon_wrntime)
664             return 1;
665         else if (delta < this.mon_errtime)
666             return 2;
667         else
668             return 3;
669     },
670
671     start: function() { /* public */
672         var transp_type;
673         var page;
674
675         // this.log("start "+this.transp_type+" "+this.transp_fback);
676         if (this.the_end)
677             return;
678
679         createCookie(this.cookiename, sess, 24*365, this.cookiepath);
680         // alert("start");
681         this.log("xynt_streaming:start restart: "+this.restart_n);
682         this.keepalives_equal = 0;
683
684         // page arrangement
685         this.page = url_complete(this.win.location.href, this.page);
686
687         // DEFAULT TRANSPORT PROTOCOL HERE websocketsec, websocket
688         if (this.transp_fback > 0) {
689             if (location.protocol == 'https:') {
690                 transp_type = "websocketsec";
691                 transp_port = 443;
692             }
693             else {
694                 transp_type = "websocket";
695                 transp_port = (this.transp_fback == 2 ? 80 : 8080);
696             }
697
698         }
699         else {
700             transp_type = this.transp_type;
701             transp_port = this.transp_port;
702         }
703
704         this.transp_type_cur = transp_type;
705         this.transp_port_cur = transp_port;
706
707         if (transp_type == "websocket" || transp_type == "websocketsec") {
708             var end_proto, first_slash, newpage;
709
710             // change protocol
711             this.log("precha ["+this.page+"]");
712             if (transp_type == "websocketsec") {
713                 newpage = this.page.replace(/\.php$/g, "_wss.php").replace(/\.php\?/g, "_wss.php?");
714                 }
715             else {
716                 newpage = this.page;
717                 }
718             end_proto = newpage.indexOf("://");
719             first_slash = newpage.substring(end_proto+3).indexOf("/");
720
721             page = (transp_type == "websocketsec" ? "wss://" : "ws://")
722                 + newpage.substring(end_proto+3, end_proto+3 + first_slash) + ":"
723                 + transp_port + newpage.substring(end_proto+3 + first_slash);
724             // this.log("MOP WS: " + page);
725         }
726         else {
727             page = this.page;
728         }
729         // stat, subst, this.gst.st
730
731         page = url_append_args(page, "sess", this.sess, "stat", stat, "subst", subst, "step", this.gst.st, "from", this.from);
732         // this.log("the page:");
733         // this.log(page);
734
735         try {
736             // transport instantiation
737             if (transp_type == "websocketsec") {
738                 page = url_append_args(page, "transp", "websocketsec");
739                 this.transp = new transport_ws(this.doc, this, page, true);
740             }
741             else if (transp_type == "websocket") {
742                 page = url_append_args(page, "transp", "websocket");
743                 this.transp = new transport_ws(this.doc, this, page);
744             }
745             else if (transp_type == "xhr") {
746                 page = url_append_args(page, "transp", "xhr");
747                 this.transp = new transport_xhr(this.doc, this, page);
748             }
749             else if (transp_type == "iframe") {
750                 page = url_append_args(page, "transp", "iframe");
751                 this.transp = new transport_iframe(this.doc, this, page);
752             }
753             else if (transp_type == "htmlfile") {
754                 page = url_append_args(page, "transp", "htmlfile");
755                 this.transp = new transport_htmlfile(this.doc, this, page);
756             }
757             else
758                 return;
759         }
760         catch (err) {
761             if (this.transp_fback > 0) {
762                 this.transp_fback--;
763                 this.start();
764                 return;
765             }
766         }
767
768         // watchdog setting
769         this.watchdog_ct  = 0;
770         if (!this.the_end) {
771             this.watchdog_hdl = setTimeout(function(obj) { obj.log("tout1"); obj.watchdog(); }, this.watchdog_timeout, this);
772         }
773
774         var date = new Date();
775         this.start_time = date.getTime();
776     },
777
778     stop: function() {
779         this.the_end = true;
780         this.abort();
781     },
782
783     hbit_set: function (hbit) {
784         this.hbit = hbit;
785     },
786
787
788     hbit_status: function () {
789         var ret;
790
791         ret = this.mon_status();
792         // console.log("mon_status: "+ret+" 0: "+this.mon_time);
793         switch (ret) {
794         case 0:
795             mon_stat = "b";
796             break;
797         case 1:
798             mon_stat = "g";
799             break;
800         case 2:
801             mon_stat = "y";
802             break;
803         case 3:
804             mon_stat = "r";
805             break;
806         }
807
808         if (this.mon_stat_old != mon_stat) {
809             this.hbit(mon_stat);
810             this.mon_stat_old = mon_stat;
811         }
812     },
813
814
815     watchdog: function () {
816         // alert("watchdog");
817         var i, again;
818         var comm_newpart, comm_len, comm_arr;
819         var ctx_new_len;
820
821         if (this.sandbox != null) {
822             var zug = "WATCHDOG  sess = ["+this.sess+"]  step = "+this.gst.st+" step_loc = "+this.gst.st_loc+" step_loc_new = "+this.gst.st_loc_new+"Transport: "+this.transp.name;
823             if (zug != this.sandbox.innerHTML)
824                 this.sandbox.innerHTML = zug;
825         }
826
827         // WATCHDOGING THE CONNECTION
828         this.log("hs::watchdog: start, cur equal times: "+this.keepalives_equal);
829         if (!this.watchable) {
830             do {
831                 try{
832                     // if (typeof(this.ifra.contentWindow.xynt_streaming) == 'undefined')
833                     if (!this.transp.xstr_is_init()) {
834                         this.log("hs::watchdog: xstr_is_init = false");
835                         break;
836                     }
837                 }
838                 catch(b) {
839                     this.log("hs::watchdog: exception");
840                     break;
841                 }
842
843                 /*
844                   on IE7 the the window frame scope is cleaned after the href is set, so we wait
845                   for a well know variable value before assign this object value to it (OO is a passion)
846                 */
847                 // if (this.ifra.contentWindow.xynt_streaming == "ready") {
848                 if (this.transp.xstr_set()) {
849                     // this.ifra.contentWindow.xynt_streaming = this;
850                     this.watchable = true;
851                     this.watchdog_ct = 0;
852                     this.log("hs::watchdog: watchable = yes");
853                 }
854             } while (false);
855         }
856         if ( (this.watchdog_ct % this.watchdog_checktm) == 0) {
857             this.log("hs::watchdog: this.keepalive_old: "+this.keepalive_old+" this.keepalive_new: "+this.keepalive_new);
858             if (this.keepalive_old == this.keepalive_new) {
859                 this.keepalives_equal++;
860             }
861             else {
862                 this.keepalive_old = this.keepalive_new;
863                 this.keepalives_equal = 0;
864             }
865
866             if (this.keepalives_equal >= this.keepalives_eq_max) {
867                 this.log("hs::watchdog: MAX ACHIEVED "+this.keepalives_equal);
868                 this.reload();
869                 // alert("watchdog return reload");
870                 this.hbit_status();
871                 return;
872             }
873         }
874
875         // PICK COMMANDS FROM STREAM
876         do {
877             // alert("do--while begin ["+again+"]");
878             // CHECK: maybe again here isn't needed
879             again = 0;
880             try {
881                 /* if (typeof(this.ifra.contentWindow.ctx_new)     == 'undefined' ||
882                    typeof(this.ifra.contentWindow.ctx_old_len) == 'undefined') */
883                 if (!this.transp.ctx_new_is_set() || !this.transp.ctx_old_len_is_set())
884                     break;
885             }
886             catch(b) {
887                 break;
888             }
889
890             // ctx_new_len = this.ifra.contentWindow.ctx_new.length;
891             ctx_new_len = this.transp.ctx_new_curlen_get();
892             // if (ctx_new_len <= this.ifra.contentWindow.ctx_old_len) {
893             if (ctx_new_len <= this.transp.ctx_old_len_get()) {
894                 break;
895             }
896             this.log("new: "+ ctx_new_len + "  old: "+this.transp.ctx_old_len_get());
897             this.keepalive_new++;
898             // alert("pre-loop 1");
899             for (i = this.transp.ctx_old_len_get() ; i < ctx_new_len ; i++) {
900                 // if (this.ifra.contentWindow.ctx_new.charAt(i) != '_') {
901                 if (this.transp.ctx_new_getchar(i) != '_') {
902                     // this.log("ctx_new.char(i) != '_' ["+this.ifra.contentWindow.ctx_new.charAt(i)+"]");
903                     break;
904                 }
905                 this.mon_update();
906                 this.hbit_status();
907
908                 // else {
909                 //     this.log("ctx_new.charAt(i) == '_'");
910                 // }
911             }
912             // this.ifra.contentWindow.ctx_old_len = i;
913             this.transp.ctx_old_len_set(i);
914             if (i == ctx_new_len) {
915                 this.log("old_len == i");
916                 break;
917             }
918             else {
919                 this.log("old_len != i: "+i);
920             }
921             // alert("do--while middle ["+this.ifra.contentWindow.ctx_old_len+"]");
922
923             comm_newpart = this.transp.new_part();
924             this.log("COM_NEWPART: ["+comm_newpart+"]");
925             comm_len = 0;
926             comm_arr = comm_newpart.match(this.comm_match);
927
928             // alert("do--while middle2 ["+again+"]");
929             if (comm_arr) {
930                 var comm_arr_len = comm_arr.length;
931                 for (i = 0 ; i < comm_arr_len ; i++) {
932                     var temp = comm_arr[i].replace(this.comm_clean,"$1").split("|");
933                     this.gst.comms = this.gst.comms.concat(temp);
934                     comm_len += comm_arr[i].length;
935                 }
936                 again = 1;
937                 this.mon_update();
938                 this.hbit_status();
939             }
940             // this.ifra.contentWindow.ctx_old_len += comm_len;
941             this.transp.ctx_old_len_add(comm_len);
942             // this.ifra.contentWindow.script_clean = this.gst.st;
943             this.transp.scrcls_set(this.gst.st);
944             // alert("do--while end ["+again+"]");
945         } while (again);
946
947         // alert("post while");
948         // EXECUTION OF STREAM COMMANDS
949         do {
950             again = 0;
951             //MOP ?? xhrrestart = 0;
952             if (this.gst.st_loc < this.gst.st_loc_new) {
953                 // there is some slow actions running
954                 break;
955             }
956             else if (this.gst.comms.length > 0) {
957                 var singlecomm;
958
959                 singlecomm = this.gst.comms.shift();
960                 // alert("EXE"+gugu);
961                 // $("xhrdeltalog").innerHTML = "EVALL: "+singlecomm.replace("<", "&lt;", "g"); +"<br>";
962                 //xx this.hbit("+");
963
964                 // alert("SINGLE: ["+singlecomm+"]");
965                 // window.console.log("["+singlecomm+"]");
966                 this.cmdproc(singlecomm);
967                 if (this.transp_type_cur) {
968                     this.transp_type = this.transp_type_cur;
969                     this.transp_port = this.transp_port_cur;
970                 }
971                 again = 1;
972             }
973         } while (again);
974         this.watchdog_ct++;
975         if (!this.the_end) {
976             var date = new Date();
977             if (date.getTime() > (this.start_time + this.restart_wait)) {
978                 this.transp.postproc();
979             }
980             this.watchdog_hdl = setTimeout(function(obj) { /* obj.log("tout2"); */ obj.watchdog(); }, this.watchdog_timeout, this);
981             this.hbit_status();
982         }
983         // alert("watchdog return normal");
984
985         return;
986     },
987
988     send: function(msg) {
989         if (typeof(this.transp.send) == 'undefined') {
990             this.log('send not implemented for ' + this.transp_type);
991             return;
992         }
993
994         return this.transp.send(msg);
995     },
996
997     //
998     // moved to xynt-streaming-ifra as push()
999     //
1000     // keepalive: function (s) {
1001     //     this.log("hs::keepalive");
1002     //     if (s != null) {
1003     //         this.log(s);
1004     //         this.ifra.contentWindow.ctx_new += "@BEGIN@"+s+"@END@";
1005     //     }
1006     //     else {
1007     //         this.ifra.contentWindow.ctx_new += "_";
1008     //     }
1009     //     // this.keepalive_new++;
1010     // },
1011
1012     abort: function () { /* public */
1013         // this.log("PATH: "+this.ifra.contentWindow.location.protocol + "://" + this.ifra.contentWindow.location.host + "/" + this.ifra.contentWindow.location.pathname);
1014
1015         this.gst.abort();
1016         if (this.watchdog_hdl != null) {
1017             clearTimeout(this.watchdog_hdl);
1018             this.watchdog_hdl = null;
1019         }
1020
1021         this.restart_n++;
1022         this.log("hs::reload");
1023         this.watchable = false;
1024         if (this.transp != null) {
1025             this.transp.destroy();
1026             delete this.transp;
1027             this.transp = null;
1028         }
1029     },
1030
1031     reload: function () {
1032         this.abort();
1033         this.start(null);
1034     },
1035
1036     log: function (s) {
1037         if (this.console != null) {
1038             return (this.console.log(s));
1039         }
1040     }
1041 }