Fossil SCM
Apply the connection-recovery reporting to the other AJAX commands, so that they can trigger the polling to continue if they determine that the connection is back up. Internal cleanups.
Commit
fd36f8490beb74a33c298651c787d7179204c355ebd6f60a2e6529db96ab6fd5
Parent
2debc54e67ec71f…
1 file changed
+64
-39
+64
-39
| --- src/fossil.page.chat.js | ||
| +++ src/fossil.page.chat.js | ||
| @@ -192,23 +192,28 @@ | ||
| 192 | 192 | The timeing of resetting the delay when service returns is, |
| 193 | 193 | because of the long-poll connection and our lack of low-level |
| 194 | 194 | insight into the connection at this level, a bit wonky. |
| 195 | 195 | */ |
| 196 | 196 | timer:{ |
| 197 | - id: undefined, | |
| 197 | + tidPoller: undefined /* poller timer */, | |
| 198 | 198 | $initialDelay: 1000 /* initial polling interval (ms) */, |
| 199 | 199 | currentDelay: 1000 /* current polling interval */, |
| 200 | 200 | maxDelay: 60000 /* max interval when backing off for |
| 201 | 201 | connection errors */, |
| 202 | - minDelay: 7000 /* minimum delay time */, | |
| 203 | - idReconnect: undefined /*timer id for reconnection determination*/, | |
| 202 | + minDelay: 5000 /* minimum delay time */, | |
| 203 | + tidReconnect: undefined /*timer id for reconnection determination*/, | |
| 204 | + randomInterval: function(){ | |
| 205 | + return Math.floor(Math.random() * this.minDelay); | |
| 206 | + }, | |
| 204 | 207 | incrDelay: function(){ |
| 205 | 208 | if( this.maxDelay > this.currentDelay ){ |
| 206 | - this.currentDelay = | |
| 207 | - (this.currentDelay < this.minDelay) | |
| 208 | - ? this.minDelay | |
| 209 | - : (this.currentDelay * 3); | |
| 209 | + if(this.currentDelay < this.minDelay){ | |
| 210 | + this.currentDelay = this.minDelay; | |
| 211 | + }else{ | |
| 212 | + this.currentDelay *= 2; | |
| 213 | + } | |
| 214 | + this.currentDelay += this.randomInterval(); | |
| 210 | 215 | } |
| 211 | 216 | return this.currentDelay; |
| 212 | 217 | }, |
| 213 | 218 | resetDelay: function(){ |
| 214 | 219 | return this.currentDelay = this.$initialDelay; |
| @@ -268,10 +273,11 @@ | ||
| 268 | 273 | */ |
| 269 | 274 | ajaxStart: function(){ |
| 270 | 275 | if(1===++this.ajaxInflight){ |
| 271 | 276 | this.enableAjaxComponents(false); |
| 272 | 277 | } |
| 278 | + setupConnectionReestablished(); | |
| 273 | 279 | }, |
| 274 | 280 | /* Must be called after any ajax-related call for which |
| 275 | 281 | ajaxStart() was called, regardless of success or failure. If |
| 276 | 282 | it was the last such call (as measured by calls to |
| 277 | 283 | ajaxStart() and ajaxEnd()), elements disabled by a prior call |
| @@ -836,11 +842,15 @@ | ||
| 836 | 842 | // We need to fetch the plain-text version... |
| 837 | 843 | const self = this; |
| 838 | 844 | F.fetch('chat-fetch-one',{ |
| 839 | 845 | urlParams:{ name: id, raw: true}, |
| 840 | 846 | responseType: 'json', |
| 847 | + function(){ | |
| 848 | + Chat.ajaxStart(); | |
| 849 | + }, | |
| 841 | 850 | onload: function(msg){ |
| 851 | + reportConnectionReestablished(); | |
| 842 | 852 | content.$elems[1] = D.append(D.pre(),msg.xmsg); |
| 843 | 853 | content.$elems[1]._xmsgRaw = msg.xmsg/*used for copy-to-clipboard feature*/; |
| 844 | 854 | self.toggleTextMode(e); |
| 845 | 855 | }, |
| 846 | 856 | aftersend:function(){ |
| @@ -898,11 +908,14 @@ | ||
| 898 | 908 | if(!(e instanceof HTMLElement)) return; |
| 899 | 909 | if(this.userMayDelete(e)){ |
| 900 | 910 | this.ajaxStart(); |
| 901 | 911 | F.fetch("chat-delete/" + id, { |
| 902 | 912 | responseType: 'json', |
| 903 | - onload:(r)=>this.deleteMessageElem(r), | |
| 913 | + onload:(r)=>{ | |
| 914 | + reportConnectionReestablished(); | |
| 915 | + this.deleteMessageElem(r); | |
| 916 | + }, | |
| 904 | 917 | onerror:(err)=>this.reportErrorAsMessage(err) |
| 905 | 918 | }); |
| 906 | 919 | }else{ |
| 907 | 920 | this.deleteMessageElem(id); |
| 908 | 921 | } |
| @@ -1518,10 +1531,11 @@ | ||
| 1518 | 1531 | n: nFetch, |
| 1519 | 1532 | i: iFirst |
| 1520 | 1533 | }, |
| 1521 | 1534 | responseType: "json", |
| 1522 | 1535 | onload:function(jx){ |
| 1536 | + reportConnectionReestablished(); | |
| 1523 | 1537 | if( bDown ) jx.msgs.reverse(); |
| 1524 | 1538 | jx.msgs.forEach((m) => { |
| 1525 | 1539 | m.isSearchResult = true; |
| 1526 | 1540 | var mw = new Chat.MessageWidget(m); |
| 1527 | 1541 | if( bDown ){ |
| @@ -1685,43 +1699,49 @@ | ||
| 1685 | 1699 | const theMsg = findMessageWidgetParent(w); |
| 1686 | 1700 | if(theMsg) Chat.deleteMessageElem(theMsg); |
| 1687 | 1701 | })); |
| 1688 | 1702 | Chat.reportErrorAsMessage(w); |
| 1689 | 1703 | }; |
| 1704 | + | |
| 1705 | + const removeConnectionErrors = function() { | |
| 1706 | + D.remove(Chat.e.viewMessages.querySelectorAll( | |
| 1707 | + '.message-widget.error-connection')); | |
| 1708 | + }; | |
| 1690 | 1709 | |
| 1691 | 1710 | /* Assume the connection has been established, reset |
| 1692 | - the Chat.timer.idReconnect, and alert the user | |
| 1711 | + the Chat.timer.tidReconnect, and alert the user | |
| 1693 | 1712 | that the outage appears to be over. */ |
| 1694 | 1713 | const reportConnectionReestablished = function(){ |
| 1695 | - if( Chat.timer.idReconnect ){ | |
| 1696 | - clearTimeout(Chat.timer.idReconnect); | |
| 1697 | - Chat.timer.idReconnect = 0; | |
| 1714 | + if( Chat.timer.tidReconnect ){ | |
| 1715 | + clearTimeout(Chat.timer.tidReconnect); | |
| 1716 | + Chat.timer.tidReconnect = 0; | |
| 1698 | 1717 | } |
| 1699 | 1718 | if( Chat.timer.isDelayed() ){ |
| 1719 | + removeConnectionErrors(); | |
| 1700 | 1720 | Chat.timer.resetDelay(); |
| 1701 | 1721 | Chat.reportReconnection( |
| 1702 | 1722 | "Connection restored after outage." |
| 1703 | 1723 | ); |
| 1704 | 1724 | setTimeout( Chat.poll, 0 ); |
| 1705 | 1725 | } |
| 1706 | 1726 | }; |
| 1707 | 1727 | |
| 1708 | - /* If we're currently in delayed-retry mode, try to reset the delay | |
| 1709 | - if we're waiting for a while for the connection to complete, | |
| 1710 | - as that's an indication (not a guaranty) that we're connected | |
| 1711 | - and long-polling. */ | |
| 1728 | + /* To be called from F.fetch() beforesend() handlers. If we're | |
| 1729 | + currently in delayed-retry mode and a connection is start, try to | |
| 1730 | + reset the delay after N time waiting on that connection. The fact | |
| 1731 | + that the connection is waiting to respond, rather than outright | |
| 1732 | + failing, is a good hint that the outage is over and we can reset | |
| 1733 | + the back-off timer. */ | |
| 1712 | 1734 | const setupConnectionReestablished = function(){ |
| 1713 | - if( !Chat.timer.idReconnect && Chat.timer.isDelayed() ){ | |
| 1714 | - Chat.timer.idReconnect = setTimeout(()=>{ | |
| 1715 | - Chat.timer.idReconnect = 0; | |
| 1735 | + if( !Chat.timer.tidReconnect && Chat.timer.isDelayed() ){ | |
| 1736 | + Chat.timer.tidReconnect = setTimeout(()=>{ | |
| 1737 | + Chat.timer.tidReconnect = 0; | |
| 1716 | 1738 | if( poll.running ){ |
| 1717 | 1739 | reportConnectionReestablished(); |
| 1718 | 1740 | } |
| 1719 | 1741 | }, Chat.timer.$initialDelay * 5 ); |
| 1720 | - Chat.e.viewMessages.querySelectorAll( | |
| 1721 | - '.message-widget.error-connection' | |
| 1722 | - ).forEach(e=>D.remove(e)); | |
| 1742 | + removeConnectionErrors(); | |
| 1723 | 1743 | } |
| 1724 | 1744 | }; |
| 1725 | 1745 | |
| 1726 | 1746 | /** |
| 1727 | 1747 | Submits the contents of the message input field (if not empty) |
| @@ -1777,14 +1797,10 @@ | ||
| 1777 | 1797 | const self = this; |
| 1778 | 1798 | fd.set("lmtime", localTime8601(new Date())); |
| 1779 | 1799 | F.fetch("chat-send",{ |
| 1780 | 1800 | payload: fd, |
| 1781 | 1801 | responseType: 'text', |
| 1782 | - beforesend: function(){ | |
| 1783 | - Chat.ajaxStart(); | |
| 1784 | - setupConnectionReestablished(); | |
| 1785 | - }, | |
| 1786 | 1802 | onerror:function(err){ |
| 1787 | 1803 | self.reportErrorAsMessage(err); |
| 1788 | 1804 | recoverFailedMessage(fallback); |
| 1789 | 1805 | }, |
| 1790 | 1806 | onload:function(txt){ |
| @@ -2286,10 +2302,11 @@ | ||
| 2286 | 2302 | /*filename needed for mimetype determination*/); |
| 2287 | 2303 | fd.append('render_mode',F.page.previewModes.wiki); |
| 2288 | 2304 | F.fetch('ajax/preview-text',{ |
| 2289 | 2305 | payload: fd, |
| 2290 | 2306 | onload: function(html){ |
| 2307 | + reportConnectionReestablished(); | |
| 2291 | 2308 | Chat.setPreviewText(html); |
| 2292 | 2309 | F.pikchr.addSrcView(Chat.e.viewPreview.querySelectorAll('svg.pikchr')); |
| 2293 | 2310 | }, |
| 2294 | 2311 | onerror: function(e){ |
| 2295 | 2312 | F.fetch.onerror(e); |
| @@ -2423,10 +2440,11 @@ | ||
| 2423 | 2440 | onerror:function(err){ |
| 2424 | 2441 | Chat.reportErrorAsMessage(err); |
| 2425 | 2442 | Chat._isBatchLoading = false; |
| 2426 | 2443 | }, |
| 2427 | 2444 | onload:function(x){ |
| 2445 | + reportConnectionReestablished(); | |
| 2428 | 2446 | let gotMessages = x.msgs.length; |
| 2429 | 2447 | newcontent(x,true); |
| 2430 | 2448 | Chat._isBatchLoading = false; |
| 2431 | 2449 | Chat.updateActiveUserList(); |
| 2432 | 2450 | if(Chat._gotServerError){ |
| @@ -2512,10 +2530,11 @@ | ||
| 2512 | 2530 | onerror:function(err){ |
| 2513 | 2531 | Chat.setCurrentView(Chat.e.viewMessages); |
| 2514 | 2532 | Chat.reportErrorAsMessage(err); |
| 2515 | 2533 | }, |
| 2516 | 2534 | onload:function(jx){ |
| 2535 | + reportConnectionReestablished(); | |
| 2517 | 2536 | let previd = 0; |
| 2518 | 2537 | D.clearElement(eMsgTgt); |
| 2519 | 2538 | jx.msgs.forEach((m)=>{ |
| 2520 | 2539 | m.isSearchResult = true; |
| 2521 | 2540 | const mw = new Chat.MessageWidget(m); |
| @@ -2548,44 +2567,50 @@ | ||
| 2548 | 2567 | }/*Chat.submitSearch()*/; |
| 2549 | 2568 | |
| 2550 | 2569 | /** |
| 2551 | 2570 | Deal with the last poll() response and maybe re-start poll(). |
| 2552 | 2571 | */ |
| 2553 | - const afterPollFetch = function f(isOkay = true){ | |
| 2572 | + const afterPollFetch = function f(err){ | |
| 2554 | 2573 | if(true===f.isFirstCall){ |
| 2555 | 2574 | f.isFirstCall = false; |
| 2556 | 2575 | Chat.ajaxEnd(); |
| 2557 | 2576 | Chat.e.viewMessages.classList.remove('loading'); |
| 2558 | 2577 | setTimeout(function(){ |
| 2559 | 2578 | Chat.scrollMessagesTo(1); |
| 2560 | 2579 | }, 250); |
| 2561 | 2580 | } |
| 2562 | - if(Chat.timer.id) { | |
| 2563 | - clearTimeout(Chat.timer.id); | |
| 2564 | - Chat.timer.id = 0; | |
| 2581 | + if(Chat.timer.tidPoller) { | |
| 2582 | + clearTimeout(Chat.timer.tidPoller); | |
| 2583 | + Chat.timer.tidPoller = 0; | |
| 2565 | 2584 | } |
| 2566 | 2585 | if(Chat._gotServerError){ |
| 2567 | 2586 | Chat.reportErrorAsMessage( |
| 2568 | 2587 | "Shutting down chat poller due to server-side error. ", |
| 2569 | - "Reload this page to reactivate it."); | |
| 2570 | - Chat.timer.id = undefined; | |
| 2588 | + "Reload this page to reactivate it." | |
| 2589 | + ); | |
| 2590 | + Chat.timer.tidPoller = undefined; | |
| 2571 | 2591 | poll.running = false; |
| 2572 | 2592 | } else { |
| 2573 | 2593 | poll.running = false; |
| 2574 | - if( isOkay ){ | |
| 2575 | - Chat.timer.id = setTimeout( | |
| 2594 | + if( !err ){ | |
| 2595 | + /* Restart the poller. */ | |
| 2596 | + Chat.timer.tidPoller = setTimeout( | |
| 2576 | 2597 | poll, Chat.timer.resetDelay() |
| 2577 | 2598 | ); |
| 2578 | 2599 | }else{ |
| 2600 | + /* Delay a while before trying again, noting that other Chat | |
| 2601 | + APIs may try and succeed at connections before this timer | |
| 2602 | + resolves. */ | |
| 2579 | 2603 | const delay = Chat.timer.incrDelay(); |
| 2580 | 2604 | const msg = D.addClass( |
| 2581 | 2605 | Chat.reportErrorAsMessage( |
| 2582 | 2606 | "Connection error. Retrying in ", |
| 2583 | - delay, " ms.").e.body, | |
| 2607 | + delay, " ms." | |
| 2608 | + ).e.body, | |
| 2584 | 2609 | 'error-connection' |
| 2585 | 2610 | ); |
| 2586 | - Chat.timer.id = setTimeout(()=>{ | |
| 2611 | + Chat.timer.tidPoller = setTimeout(()=>{ | |
| 2587 | 2612 | D.remove(msg); |
| 2588 | 2613 | poll(); |
| 2589 | 2614 | }, delay); |
| 2590 | 2615 | } |
| 2591 | 2616 | //console.log("isOkay =",isOkay,"currentDelay =",Chat.timer.currentDelay); |
| @@ -2634,31 +2659,31 @@ | ||
| 2634 | 2659 | Chat._isBatchLoading = false; |
| 2635 | 2660 | if(Chat.verboseErrors) console.error("poll onerror:",err); |
| 2636 | 2661 | /* ^^^ we don't use Chat.reportError() here b/c the polling |
| 2637 | 2662 | fails exepectedly when it times out, but is then immediately |
| 2638 | 2663 | resumed, and reportError() produces a loud error message. */ |
| 2639 | - afterPollFetch(false); | |
| 2664 | + afterPollFetch(err); | |
| 2640 | 2665 | }, |
| 2641 | 2666 | onload:function(y){ |
| 2642 | 2667 | reportConnectionReestablished(); |
| 2643 | 2668 | newcontent(y); |
| 2644 | 2669 | if(Chat._isBatchLoading){ |
| 2645 | 2670 | Chat._isBatchLoading = false; |
| 2646 | 2671 | Chat.updateActiveUserList(); |
| 2647 | 2672 | } |
| 2648 | - afterPollFetch(true); | |
| 2673 | + afterPollFetch(); | |
| 2649 | 2674 | } |
| 2650 | 2675 | }); |
| 2651 | 2676 | }; |
| 2652 | 2677 | poll.isFirstCall = true; |
| 2653 | 2678 | Chat.poll = poll; |
| 2654 | 2679 | Chat._gotServerError = poll.running = false; |
| 2655 | 2680 | if( window.fossil.config.chat.fromcli ){ |
| 2656 | 2681 | Chat.chatOnlyMode(true); |
| 2657 | 2682 | } |
| 2658 | - Chat.timer.id = setTimeout(poll, Chat.timer.resetDelay()); | |
| 2683 | + Chat.timer.tidPoller = setTimeout(poll, Chat.timer.resetDelay()); | |
| 2659 | 2684 | delete ForceResizeKludge.$disabled; |
| 2660 | 2685 | ForceResizeKludge(); |
| 2661 | 2686 | Chat.animate.$disabled = false; |
| 2662 | 2687 | setTimeout( ()=>Chat.inputFocus(), 0 ); |
| 2663 | 2688 | F.page.chat = Chat/* enables testing the APIs via the dev tools */; |
| 2664 | 2689 | }); |
| 2665 | 2690 |
| --- src/fossil.page.chat.js | |
| +++ src/fossil.page.chat.js | |
| @@ -192,23 +192,28 @@ | |
| 192 | The timeing of resetting the delay when service returns is, |
| 193 | because of the long-poll connection and our lack of low-level |
| 194 | insight into the connection at this level, a bit wonky. |
| 195 | */ |
| 196 | timer:{ |
| 197 | id: undefined, |
| 198 | $initialDelay: 1000 /* initial polling interval (ms) */, |
| 199 | currentDelay: 1000 /* current polling interval */, |
| 200 | maxDelay: 60000 /* max interval when backing off for |
| 201 | connection errors */, |
| 202 | minDelay: 7000 /* minimum delay time */, |
| 203 | idReconnect: undefined /*timer id for reconnection determination*/, |
| 204 | incrDelay: function(){ |
| 205 | if( this.maxDelay > this.currentDelay ){ |
| 206 | this.currentDelay = |
| 207 | (this.currentDelay < this.minDelay) |
| 208 | ? this.minDelay |
| 209 | : (this.currentDelay * 3); |
| 210 | } |
| 211 | return this.currentDelay; |
| 212 | }, |
| 213 | resetDelay: function(){ |
| 214 | return this.currentDelay = this.$initialDelay; |
| @@ -268,10 +273,11 @@ | |
| 268 | */ |
| 269 | ajaxStart: function(){ |
| 270 | if(1===++this.ajaxInflight){ |
| 271 | this.enableAjaxComponents(false); |
| 272 | } |
| 273 | }, |
| 274 | /* Must be called after any ajax-related call for which |
| 275 | ajaxStart() was called, regardless of success or failure. If |
| 276 | it was the last such call (as measured by calls to |
| 277 | ajaxStart() and ajaxEnd()), elements disabled by a prior call |
| @@ -836,11 +842,15 @@ | |
| 836 | // We need to fetch the plain-text version... |
| 837 | const self = this; |
| 838 | F.fetch('chat-fetch-one',{ |
| 839 | urlParams:{ name: id, raw: true}, |
| 840 | responseType: 'json', |
| 841 | onload: function(msg){ |
| 842 | content.$elems[1] = D.append(D.pre(),msg.xmsg); |
| 843 | content.$elems[1]._xmsgRaw = msg.xmsg/*used for copy-to-clipboard feature*/; |
| 844 | self.toggleTextMode(e); |
| 845 | }, |
| 846 | aftersend:function(){ |
| @@ -898,11 +908,14 @@ | |
| 898 | if(!(e instanceof HTMLElement)) return; |
| 899 | if(this.userMayDelete(e)){ |
| 900 | this.ajaxStart(); |
| 901 | F.fetch("chat-delete/" + id, { |
| 902 | responseType: 'json', |
| 903 | onload:(r)=>this.deleteMessageElem(r), |
| 904 | onerror:(err)=>this.reportErrorAsMessage(err) |
| 905 | }); |
| 906 | }else{ |
| 907 | this.deleteMessageElem(id); |
| 908 | } |
| @@ -1518,10 +1531,11 @@ | |
| 1518 | n: nFetch, |
| 1519 | i: iFirst |
| 1520 | }, |
| 1521 | responseType: "json", |
| 1522 | onload:function(jx){ |
| 1523 | if( bDown ) jx.msgs.reverse(); |
| 1524 | jx.msgs.forEach((m) => { |
| 1525 | m.isSearchResult = true; |
| 1526 | var mw = new Chat.MessageWidget(m); |
| 1527 | if( bDown ){ |
| @@ -1685,43 +1699,49 @@ | |
| 1685 | const theMsg = findMessageWidgetParent(w); |
| 1686 | if(theMsg) Chat.deleteMessageElem(theMsg); |
| 1687 | })); |
| 1688 | Chat.reportErrorAsMessage(w); |
| 1689 | }; |
| 1690 | |
| 1691 | /* Assume the connection has been established, reset |
| 1692 | the Chat.timer.idReconnect, and alert the user |
| 1693 | that the outage appears to be over. */ |
| 1694 | const reportConnectionReestablished = function(){ |
| 1695 | if( Chat.timer.idReconnect ){ |
| 1696 | clearTimeout(Chat.timer.idReconnect); |
| 1697 | Chat.timer.idReconnect = 0; |
| 1698 | } |
| 1699 | if( Chat.timer.isDelayed() ){ |
| 1700 | Chat.timer.resetDelay(); |
| 1701 | Chat.reportReconnection( |
| 1702 | "Connection restored after outage." |
| 1703 | ); |
| 1704 | setTimeout( Chat.poll, 0 ); |
| 1705 | } |
| 1706 | }; |
| 1707 | |
| 1708 | /* If we're currently in delayed-retry mode, try to reset the delay |
| 1709 | if we're waiting for a while for the connection to complete, |
| 1710 | as that's an indication (not a guaranty) that we're connected |
| 1711 | and long-polling. */ |
| 1712 | const setupConnectionReestablished = function(){ |
| 1713 | if( !Chat.timer.idReconnect && Chat.timer.isDelayed() ){ |
| 1714 | Chat.timer.idReconnect = setTimeout(()=>{ |
| 1715 | Chat.timer.idReconnect = 0; |
| 1716 | if( poll.running ){ |
| 1717 | reportConnectionReestablished(); |
| 1718 | } |
| 1719 | }, Chat.timer.$initialDelay * 5 ); |
| 1720 | Chat.e.viewMessages.querySelectorAll( |
| 1721 | '.message-widget.error-connection' |
| 1722 | ).forEach(e=>D.remove(e)); |
| 1723 | } |
| 1724 | }; |
| 1725 | |
| 1726 | /** |
| 1727 | Submits the contents of the message input field (if not empty) |
| @@ -1777,14 +1797,10 @@ | |
| 1777 | const self = this; |
| 1778 | fd.set("lmtime", localTime8601(new Date())); |
| 1779 | F.fetch("chat-send",{ |
| 1780 | payload: fd, |
| 1781 | responseType: 'text', |
| 1782 | beforesend: function(){ |
| 1783 | Chat.ajaxStart(); |
| 1784 | setupConnectionReestablished(); |
| 1785 | }, |
| 1786 | onerror:function(err){ |
| 1787 | self.reportErrorAsMessage(err); |
| 1788 | recoverFailedMessage(fallback); |
| 1789 | }, |
| 1790 | onload:function(txt){ |
| @@ -2286,10 +2302,11 @@ | |
| 2286 | /*filename needed for mimetype determination*/); |
| 2287 | fd.append('render_mode',F.page.previewModes.wiki); |
| 2288 | F.fetch('ajax/preview-text',{ |
| 2289 | payload: fd, |
| 2290 | onload: function(html){ |
| 2291 | Chat.setPreviewText(html); |
| 2292 | F.pikchr.addSrcView(Chat.e.viewPreview.querySelectorAll('svg.pikchr')); |
| 2293 | }, |
| 2294 | onerror: function(e){ |
| 2295 | F.fetch.onerror(e); |
| @@ -2423,10 +2440,11 @@ | |
| 2423 | onerror:function(err){ |
| 2424 | Chat.reportErrorAsMessage(err); |
| 2425 | Chat._isBatchLoading = false; |
| 2426 | }, |
| 2427 | onload:function(x){ |
| 2428 | let gotMessages = x.msgs.length; |
| 2429 | newcontent(x,true); |
| 2430 | Chat._isBatchLoading = false; |
| 2431 | Chat.updateActiveUserList(); |
| 2432 | if(Chat._gotServerError){ |
| @@ -2512,10 +2530,11 @@ | |
| 2512 | onerror:function(err){ |
| 2513 | Chat.setCurrentView(Chat.e.viewMessages); |
| 2514 | Chat.reportErrorAsMessage(err); |
| 2515 | }, |
| 2516 | onload:function(jx){ |
| 2517 | let previd = 0; |
| 2518 | D.clearElement(eMsgTgt); |
| 2519 | jx.msgs.forEach((m)=>{ |
| 2520 | m.isSearchResult = true; |
| 2521 | const mw = new Chat.MessageWidget(m); |
| @@ -2548,44 +2567,50 @@ | |
| 2548 | }/*Chat.submitSearch()*/; |
| 2549 | |
| 2550 | /** |
| 2551 | Deal with the last poll() response and maybe re-start poll(). |
| 2552 | */ |
| 2553 | const afterPollFetch = function f(isOkay = true){ |
| 2554 | if(true===f.isFirstCall){ |
| 2555 | f.isFirstCall = false; |
| 2556 | Chat.ajaxEnd(); |
| 2557 | Chat.e.viewMessages.classList.remove('loading'); |
| 2558 | setTimeout(function(){ |
| 2559 | Chat.scrollMessagesTo(1); |
| 2560 | }, 250); |
| 2561 | } |
| 2562 | if(Chat.timer.id) { |
| 2563 | clearTimeout(Chat.timer.id); |
| 2564 | Chat.timer.id = 0; |
| 2565 | } |
| 2566 | if(Chat._gotServerError){ |
| 2567 | Chat.reportErrorAsMessage( |
| 2568 | "Shutting down chat poller due to server-side error. ", |
| 2569 | "Reload this page to reactivate it."); |
| 2570 | Chat.timer.id = undefined; |
| 2571 | poll.running = false; |
| 2572 | } else { |
| 2573 | poll.running = false; |
| 2574 | if( isOkay ){ |
| 2575 | Chat.timer.id = setTimeout( |
| 2576 | poll, Chat.timer.resetDelay() |
| 2577 | ); |
| 2578 | }else{ |
| 2579 | const delay = Chat.timer.incrDelay(); |
| 2580 | const msg = D.addClass( |
| 2581 | Chat.reportErrorAsMessage( |
| 2582 | "Connection error. Retrying in ", |
| 2583 | delay, " ms.").e.body, |
| 2584 | 'error-connection' |
| 2585 | ); |
| 2586 | Chat.timer.id = setTimeout(()=>{ |
| 2587 | D.remove(msg); |
| 2588 | poll(); |
| 2589 | }, delay); |
| 2590 | } |
| 2591 | //console.log("isOkay =",isOkay,"currentDelay =",Chat.timer.currentDelay); |
| @@ -2634,31 +2659,31 @@ | |
| 2634 | Chat._isBatchLoading = false; |
| 2635 | if(Chat.verboseErrors) console.error("poll onerror:",err); |
| 2636 | /* ^^^ we don't use Chat.reportError() here b/c the polling |
| 2637 | fails exepectedly when it times out, but is then immediately |
| 2638 | resumed, and reportError() produces a loud error message. */ |
| 2639 | afterPollFetch(false); |
| 2640 | }, |
| 2641 | onload:function(y){ |
| 2642 | reportConnectionReestablished(); |
| 2643 | newcontent(y); |
| 2644 | if(Chat._isBatchLoading){ |
| 2645 | Chat._isBatchLoading = false; |
| 2646 | Chat.updateActiveUserList(); |
| 2647 | } |
| 2648 | afterPollFetch(true); |
| 2649 | } |
| 2650 | }); |
| 2651 | }; |
| 2652 | poll.isFirstCall = true; |
| 2653 | Chat.poll = poll; |
| 2654 | Chat._gotServerError = poll.running = false; |
| 2655 | if( window.fossil.config.chat.fromcli ){ |
| 2656 | Chat.chatOnlyMode(true); |
| 2657 | } |
| 2658 | Chat.timer.id = setTimeout(poll, Chat.timer.resetDelay()); |
| 2659 | delete ForceResizeKludge.$disabled; |
| 2660 | ForceResizeKludge(); |
| 2661 | Chat.animate.$disabled = false; |
| 2662 | setTimeout( ()=>Chat.inputFocus(), 0 ); |
| 2663 | F.page.chat = Chat/* enables testing the APIs via the dev tools */; |
| 2664 | }); |
| 2665 |
| --- src/fossil.page.chat.js | |
| +++ src/fossil.page.chat.js | |
| @@ -192,23 +192,28 @@ | |
| 192 | The timeing of resetting the delay when service returns is, |
| 193 | because of the long-poll connection and our lack of low-level |
| 194 | insight into the connection at this level, a bit wonky. |
| 195 | */ |
| 196 | timer:{ |
| 197 | tidPoller: undefined /* poller timer */, |
| 198 | $initialDelay: 1000 /* initial polling interval (ms) */, |
| 199 | currentDelay: 1000 /* current polling interval */, |
| 200 | maxDelay: 60000 /* max interval when backing off for |
| 201 | connection errors */, |
| 202 | minDelay: 5000 /* minimum delay time */, |
| 203 | tidReconnect: undefined /*timer id for reconnection determination*/, |
| 204 | randomInterval: function(){ |
| 205 | return Math.floor(Math.random() * this.minDelay); |
| 206 | }, |
| 207 | incrDelay: function(){ |
| 208 | if( this.maxDelay > this.currentDelay ){ |
| 209 | if(this.currentDelay < this.minDelay){ |
| 210 | this.currentDelay = this.minDelay; |
| 211 | }else{ |
| 212 | this.currentDelay *= 2; |
| 213 | } |
| 214 | this.currentDelay += this.randomInterval(); |
| 215 | } |
| 216 | return this.currentDelay; |
| 217 | }, |
| 218 | resetDelay: function(){ |
| 219 | return this.currentDelay = this.$initialDelay; |
| @@ -268,10 +273,11 @@ | |
| 273 | */ |
| 274 | ajaxStart: function(){ |
| 275 | if(1===++this.ajaxInflight){ |
| 276 | this.enableAjaxComponents(false); |
| 277 | } |
| 278 | setupConnectionReestablished(); |
| 279 | }, |
| 280 | /* Must be called after any ajax-related call for which |
| 281 | ajaxStart() was called, regardless of success or failure. If |
| 282 | it was the last such call (as measured by calls to |
| 283 | ajaxStart() and ajaxEnd()), elements disabled by a prior call |
| @@ -836,11 +842,15 @@ | |
| 842 | // We need to fetch the plain-text version... |
| 843 | const self = this; |
| 844 | F.fetch('chat-fetch-one',{ |
| 845 | urlParams:{ name: id, raw: true}, |
| 846 | responseType: 'json', |
| 847 | function(){ |
| 848 | Chat.ajaxStart(); |
| 849 | }, |
| 850 | onload: function(msg){ |
| 851 | reportConnectionReestablished(); |
| 852 | content.$elems[1] = D.append(D.pre(),msg.xmsg); |
| 853 | content.$elems[1]._xmsgRaw = msg.xmsg/*used for copy-to-clipboard feature*/; |
| 854 | self.toggleTextMode(e); |
| 855 | }, |
| 856 | aftersend:function(){ |
| @@ -898,11 +908,14 @@ | |
| 908 | if(!(e instanceof HTMLElement)) return; |
| 909 | if(this.userMayDelete(e)){ |
| 910 | this.ajaxStart(); |
| 911 | F.fetch("chat-delete/" + id, { |
| 912 | responseType: 'json', |
| 913 | onload:(r)=>{ |
| 914 | reportConnectionReestablished(); |
| 915 | this.deleteMessageElem(r); |
| 916 | }, |
| 917 | onerror:(err)=>this.reportErrorAsMessage(err) |
| 918 | }); |
| 919 | }else{ |
| 920 | this.deleteMessageElem(id); |
| 921 | } |
| @@ -1518,10 +1531,11 @@ | |
| 1531 | n: nFetch, |
| 1532 | i: iFirst |
| 1533 | }, |
| 1534 | responseType: "json", |
| 1535 | onload:function(jx){ |
| 1536 | reportConnectionReestablished(); |
| 1537 | if( bDown ) jx.msgs.reverse(); |
| 1538 | jx.msgs.forEach((m) => { |
| 1539 | m.isSearchResult = true; |
| 1540 | var mw = new Chat.MessageWidget(m); |
| 1541 | if( bDown ){ |
| @@ -1685,43 +1699,49 @@ | |
| 1699 | const theMsg = findMessageWidgetParent(w); |
| 1700 | if(theMsg) Chat.deleteMessageElem(theMsg); |
| 1701 | })); |
| 1702 | Chat.reportErrorAsMessage(w); |
| 1703 | }; |
| 1704 | |
| 1705 | const removeConnectionErrors = function() { |
| 1706 | D.remove(Chat.e.viewMessages.querySelectorAll( |
| 1707 | '.message-widget.error-connection')); |
| 1708 | }; |
| 1709 | |
| 1710 | /* Assume the connection has been established, reset |
| 1711 | the Chat.timer.tidReconnect, and alert the user |
| 1712 | that the outage appears to be over. */ |
| 1713 | const reportConnectionReestablished = function(){ |
| 1714 | if( Chat.timer.tidReconnect ){ |
| 1715 | clearTimeout(Chat.timer.tidReconnect); |
| 1716 | Chat.timer.tidReconnect = 0; |
| 1717 | } |
| 1718 | if( Chat.timer.isDelayed() ){ |
| 1719 | removeConnectionErrors(); |
| 1720 | Chat.timer.resetDelay(); |
| 1721 | Chat.reportReconnection( |
| 1722 | "Connection restored after outage." |
| 1723 | ); |
| 1724 | setTimeout( Chat.poll, 0 ); |
| 1725 | } |
| 1726 | }; |
| 1727 | |
| 1728 | /* To be called from F.fetch() beforesend() handlers. If we're |
| 1729 | currently in delayed-retry mode and a connection is start, try to |
| 1730 | reset the delay after N time waiting on that connection. The fact |
| 1731 | that the connection is waiting to respond, rather than outright |
| 1732 | failing, is a good hint that the outage is over and we can reset |
| 1733 | the back-off timer. */ |
| 1734 | const setupConnectionReestablished = function(){ |
| 1735 | if( !Chat.timer.tidReconnect && Chat.timer.isDelayed() ){ |
| 1736 | Chat.timer.tidReconnect = setTimeout(()=>{ |
| 1737 | Chat.timer.tidReconnect = 0; |
| 1738 | if( poll.running ){ |
| 1739 | reportConnectionReestablished(); |
| 1740 | } |
| 1741 | }, Chat.timer.$initialDelay * 5 ); |
| 1742 | removeConnectionErrors(); |
| 1743 | } |
| 1744 | }; |
| 1745 | |
| 1746 | /** |
| 1747 | Submits the contents of the message input field (if not empty) |
| @@ -1777,14 +1797,10 @@ | |
| 1797 | const self = this; |
| 1798 | fd.set("lmtime", localTime8601(new Date())); |
| 1799 | F.fetch("chat-send",{ |
| 1800 | payload: fd, |
| 1801 | responseType: 'text', |
| 1802 | onerror:function(err){ |
| 1803 | self.reportErrorAsMessage(err); |
| 1804 | recoverFailedMessage(fallback); |
| 1805 | }, |
| 1806 | onload:function(txt){ |
| @@ -2286,10 +2302,11 @@ | |
| 2302 | /*filename needed for mimetype determination*/); |
| 2303 | fd.append('render_mode',F.page.previewModes.wiki); |
| 2304 | F.fetch('ajax/preview-text',{ |
| 2305 | payload: fd, |
| 2306 | onload: function(html){ |
| 2307 | reportConnectionReestablished(); |
| 2308 | Chat.setPreviewText(html); |
| 2309 | F.pikchr.addSrcView(Chat.e.viewPreview.querySelectorAll('svg.pikchr')); |
| 2310 | }, |
| 2311 | onerror: function(e){ |
| 2312 | F.fetch.onerror(e); |
| @@ -2423,10 +2440,11 @@ | |
| 2440 | onerror:function(err){ |
| 2441 | Chat.reportErrorAsMessage(err); |
| 2442 | Chat._isBatchLoading = false; |
| 2443 | }, |
| 2444 | onload:function(x){ |
| 2445 | reportConnectionReestablished(); |
| 2446 | let gotMessages = x.msgs.length; |
| 2447 | newcontent(x,true); |
| 2448 | Chat._isBatchLoading = false; |
| 2449 | Chat.updateActiveUserList(); |
| 2450 | if(Chat._gotServerError){ |
| @@ -2512,10 +2530,11 @@ | |
| 2530 | onerror:function(err){ |
| 2531 | Chat.setCurrentView(Chat.e.viewMessages); |
| 2532 | Chat.reportErrorAsMessage(err); |
| 2533 | }, |
| 2534 | onload:function(jx){ |
| 2535 | reportConnectionReestablished(); |
| 2536 | let previd = 0; |
| 2537 | D.clearElement(eMsgTgt); |
| 2538 | jx.msgs.forEach((m)=>{ |
| 2539 | m.isSearchResult = true; |
| 2540 | const mw = new Chat.MessageWidget(m); |
| @@ -2548,44 +2567,50 @@ | |
| 2567 | }/*Chat.submitSearch()*/; |
| 2568 | |
| 2569 | /** |
| 2570 | Deal with the last poll() response and maybe re-start poll(). |
| 2571 | */ |
| 2572 | const afterPollFetch = function f(err){ |
| 2573 | if(true===f.isFirstCall){ |
| 2574 | f.isFirstCall = false; |
| 2575 | Chat.ajaxEnd(); |
| 2576 | Chat.e.viewMessages.classList.remove('loading'); |
| 2577 | setTimeout(function(){ |
| 2578 | Chat.scrollMessagesTo(1); |
| 2579 | }, 250); |
| 2580 | } |
| 2581 | if(Chat.timer.tidPoller) { |
| 2582 | clearTimeout(Chat.timer.tidPoller); |
| 2583 | Chat.timer.tidPoller = 0; |
| 2584 | } |
| 2585 | if(Chat._gotServerError){ |
| 2586 | Chat.reportErrorAsMessage( |
| 2587 | "Shutting down chat poller due to server-side error. ", |
| 2588 | "Reload this page to reactivate it." |
| 2589 | ); |
| 2590 | Chat.timer.tidPoller = undefined; |
| 2591 | poll.running = false; |
| 2592 | } else { |
| 2593 | poll.running = false; |
| 2594 | if( !err ){ |
| 2595 | /* Restart the poller. */ |
| 2596 | Chat.timer.tidPoller = setTimeout( |
| 2597 | poll, Chat.timer.resetDelay() |
| 2598 | ); |
| 2599 | }else{ |
| 2600 | /* Delay a while before trying again, noting that other Chat |
| 2601 | APIs may try and succeed at connections before this timer |
| 2602 | resolves. */ |
| 2603 | const delay = Chat.timer.incrDelay(); |
| 2604 | const msg = D.addClass( |
| 2605 | Chat.reportErrorAsMessage( |
| 2606 | "Connection error. Retrying in ", |
| 2607 | delay, " ms." |
| 2608 | ).e.body, |
| 2609 | 'error-connection' |
| 2610 | ); |
| 2611 | Chat.timer.tidPoller = setTimeout(()=>{ |
| 2612 | D.remove(msg); |
| 2613 | poll(); |
| 2614 | }, delay); |
| 2615 | } |
| 2616 | //console.log("isOkay =",isOkay,"currentDelay =",Chat.timer.currentDelay); |
| @@ -2634,31 +2659,31 @@ | |
| 2659 | Chat._isBatchLoading = false; |
| 2660 | if(Chat.verboseErrors) console.error("poll onerror:",err); |
| 2661 | /* ^^^ we don't use Chat.reportError() here b/c the polling |
| 2662 | fails exepectedly when it times out, but is then immediately |
| 2663 | resumed, and reportError() produces a loud error message. */ |
| 2664 | afterPollFetch(err); |
| 2665 | }, |
| 2666 | onload:function(y){ |
| 2667 | reportConnectionReestablished(); |
| 2668 | newcontent(y); |
| 2669 | if(Chat._isBatchLoading){ |
| 2670 | Chat._isBatchLoading = false; |
| 2671 | Chat.updateActiveUserList(); |
| 2672 | } |
| 2673 | afterPollFetch(); |
| 2674 | } |
| 2675 | }); |
| 2676 | }; |
| 2677 | poll.isFirstCall = true; |
| 2678 | Chat.poll = poll; |
| 2679 | Chat._gotServerError = poll.running = false; |
| 2680 | if( window.fossil.config.chat.fromcli ){ |
| 2681 | Chat.chatOnlyMode(true); |
| 2682 | } |
| 2683 | Chat.timer.tidPoller = setTimeout(poll, Chat.timer.resetDelay()); |
| 2684 | delete ForceResizeKludge.$disabled; |
| 2685 | ForceResizeKludge(); |
| 2686 | Chat.animate.$disabled = false; |
| 2687 | setTimeout( ()=>Chat.inputFocus(), 0 ); |
| 2688 | F.page.chat = Chat/* enables testing the APIs via the dev tools */; |
| 2689 | }); |
| 2690 |