Преглед на файлове

[refactor] server polling are now events-driven

B Thibault преди 8 години
родител
ревизия
59b8bae785
променени са 8 файла, в които са добавени 143 реда и са изтрити 69 реда
  1. 1 1
      srv/public/mimouchat.min.js
  2. 1 3
      srv/src/context.js
  3. 28 14
      srv/src/controller/apiController.js
  4. 74 41
      srv/src/multichatManager.js
  5. 12 7
      srv/src/slack.js
  6. 10 0
      srv/src/slackData.js
  7. 6 3
      srv/src/slackHistory.js
  8. 11 0
      srv/src/slackManager.js

+ 1 - 1
srv/public/mimouchat.min.js

@@ -103,7 +103,7 @@ c()});p.className="emojibar-overlay";m.className="emojibar";l.className="emojiba
 E.textContent=":"+a+":"):(H.textContent="",E.textContent="")})});m.addEventListener("click",function(b){a(b,function(a){a&&d()&&r&&r(a)})});return{isSupported:g,pa:function(a,b,c){return g()?(I=b,r=c,a.appendChild(p),a.appendChild(m),A.value="",e(),A.focus(),!0):!1},search:e,close:c,reset:function(){b();e()}}}();var C,T=[];function Yb(){da.call(this)}Yb.prototype=Object.create(da.prototype);Yb.prototype.constructor=Yb;function ta(a){return a.a?a.a.id:null}function Zb(){this.b=0;this.context=new ra;this.a={}}
 Zb.prototype.update=function(a){var b=Date.now();a.v&&(this.b=a.v);if(a["static"])for(g in a["static"]){var c=sa(this.context,g);c||(c=new Yb,this.context.push(c));var d={};a["static"][g].channels&&a["static"][g].channels.forEach(function(a){a.pins&&(d[a.id]=a.pins,a.pins=void 0)});fa(c,a["static"][g],b);for(var e in d){var f=[],h=this.a[e];h||(h=this.a[e]=new Y(e,250,null,b));d[e].forEach(function(a){f.push(h.b(a,b))});c.j[e].b=f}}ua(this.context,function(a){a.I===a.C&&(a=T.indexOf(a),-1!==a&&T.splice(a,
 1))});if(a.live){for(g in a.live)(c=this.a[g])?la(c,a.live[g],b):c=this.a[g]=new Y(g,250,a.live[g],b);for(var k in a.live){var g=D(this.context,k);(c=g.j[k])?(this.a[k].a.length&&ha(c,na(this.a[k]).m,b),c.fa||($b(g,c,a.live[k]),P&&a.live[P.id]&&U())):C.b=0}}a["static"]&&ib();var m=!1;a.typing&&this.context.a.forEach(function(c){var d=m,e=a.typing,f=!1;if(c.u)for(var g in c.u)e&&!e[g]&&(delete c.u[g],f=!0);if(e)for(g in e)if(c.j[g]){c.u[g]||(c.u[g]={});for(var h in e[g])c.u[g][h]||(f=!0),c.u[g][h]=
-b}m=d|f},this);(a["static"]||m)&&pb();a.config&&(X=new ac(a.config),Pb());if(O&&P&&a["static"]&&a["static"][O.a.id]&&a["static"][O.a.id].channels&&a["static"][O.a.id].channels)for(k=a["static"][O.a.id].channels,g=0,c=k.length;g<c;g++)if(k[g].id===P.id){U();break}};setInterval(function(){var a=!1,b=Date.now();va(function(c){var d=!1,e;for(e in c.u){var f=!0,h;for(h in c.u[e])c.u[e][h]+5E3<b?(delete c.u[e][h],d=!0):f=!1;f&&(delete c.u[e],d=!0)}d&&(a=!0)});a&&pb()},1E3);
+b}m=d|f},this);(a["static"]||m)&&pb();a.config&&(X=new ac(a.config),Pb());if(O&&P&&a["static"]&&a["static"][O.a.id]&&a["static"][O.a.id].channels&&a["static"][O.a.id].channels)for(k=a["static"][O.a.id].channels,g=0,c=k.length;g<c;g++)if(k[g].id===P.id){U();break}};setInterval(function(){var a=!1,b=Date.now();va(function(c){var d=!1,e;for(e in c.u){var f=!0,h;for(h in c.u[e])c.u[e][h]+6500<b?(delete c.u[e][h],d=!0):f=!1;f&&(delete c.u[e],d=!0)}d&&(a=!0)});a&&pb()},1E3);
 function $b(a,b,c){var d;if(b!==P||!window.hasFocus){var e=(d=a.self?a.self.id:null)?new RegExp("<@"+d):null,f=!1,h=!1,k=!1;c.forEach(function(c){if(!(parseFloat(c.ts)<=b.C)&&c.user!==a.self.id){h=!0;var d;if(!(d=b instanceof t)&&(d=c.text)&&!(d=e&&c.text.match(e)))a:{d=a.self.T.B;for(var g=0,m=d.length;g<m;g++)if(-1!==c.text.indexOf(d[g])){d=!0;break a}d=!1}d&&(-1===T.indexOf(b)&&(k=!0,T.push(b)),f=!0)}});if(h){nb();var g=document.getElementById("room_"+b.id);g&&(g.classList.add("unread"),f&&g.classList.add("unreadHi"));
 k&&!window.hasFocus&&yb()}}c.forEach(function(a){if(!d||d===a.K)for(var c=0,e=Ab.length;c<e;c++){var f=Ab[c];if(f.channel===b.id&&a.text&&a.text.trim()===f.text&&!!a.isMeMessage===f.Ba){Ab.splice(c,1);break}}})}function ub(){var a=P,b=T.indexOf(a);if(a.I>a.C){var c=C.a[a.id];c&&(c=na(c))&&(N(new K("POST","api/markread?room="+a.id+"&id="+c.id+"&ts="+c.m)),a.C=c.m)}0<=b&&(T.splice(b,1),nb());a=document.getElementById("room_"+a.id);a.classList.remove("unread");a.classList.remove("unreadHi")}C=new Zb;var ob=function(){function a(a,c){c.sort(function(){return Math.random()-.5});for(var d=0,e=20;e<m-40;e+=l)for(var f=0;f+l<=p;f+=l)h(a,c[d],e,f),d++,d===c.length&&(c.sort(b),d=0)}function b(a,b){return a.S?b.S?Math.random()-.5:-1:1}function c(a,b){for(var e=0,f=a.length;e<f;e++)if(void 0===a[e].S){d(a[e].src,function(d){a[e].S=d;c(a,b)});return}var g=[];a.forEach(function(a){a.S&&g.push(a.S)});b(g)}function d(a,b){N(Ga(Ea(Da(new K(a),function(a,c,d){if(d){var e=new Image;e.onload=function(){var a=
 document.createElement("canvas");a.height=a.width=A;a=a.getContext("2d");a.drawImage(e,0,0,A,A);var a=a.getImageData(0,0,A,A),c=0,d;for(d=0;d<a.width*a.height*4;d+=4)a.data[d]=a.data[d+1]=a.data[d+2]=(a.data[d]+a.data[d+1]+a.data[d+2])/3,a.data[d+3]=50,c+=a.data[d];if(50>c/(a.height*a.width))for(d=0;d<a.width*a.height*4;d+=4)a.data[d]=a.data[d+1]=a.data[d+2]=255-a.data[d];b(a)};e.onerror=function(){b(null)};e.src=window.URL.createObjectURL(d)}else b(null)}),function(){b(null)}),"blob"))}function e(){var a=

+ 1 - 3
srv/src/context.js

@@ -1,7 +1,5 @@
-
-
 /** @const */
-var TYPING_DELAY = 5000;
+var TYPING_DELAY = 6500;
 
 /**
  * @constructor

+ 28 - 14
srv/src/controller/apiController.js

@@ -395,20 +395,34 @@ module.exports.ApiController = {
                 res.end();
             }
         } else if (req.urlObj.match(["api"])) {
-            res.chatContext.poll(
-                (req.urlObj.queryTokens.v ? parseInt(req.urlObj.queryTokens.v[0], 10) : 0) || 0, req.reqT, (newData) => {
-                if (!res.ended) {
-                    try {
-                        res.writeHeader("200", {
-                            "Content-Type": "application/json"
-                        });
-                        res.end(JSON.stringify(newData));
-                    } catch (e) {}
-                }
-                sessionManager.saveSession(req.session);
-            }, (knownVersion, cb) => {
-                accountConfigManager.fromAccountIdAndVersion(req.account.id, knownVersion, cb);
-            });
+            let knownVersion = (req.urlObj.queryTokens.v ? parseInt(req.urlObj.queryTokens.v[0], 10) : 0) || 0;
+            res.chatContext.poll(knownVersion, req.reqT,
+                    (knownVersion, cb) => {
+                        accountConfigManager.fromAccountIdAndVersion(req.account.id, knownVersion, cb);
+                    })
+                .then((newData) => {
+                    if (!res.ended) {
+                        try {
+                            res.writeHeader("200", {
+                                "Content-Type": "application/json"
+                            });
+                            res.end(JSON.stringify(newData));
+                        } catch (e) {}
+                    }
+                    sessionManager.saveSession(req.session);
+                }).catch(() => {
+                    if (!res.ended) {
+                        try {
+                            res.writeHeader("200", {
+                                "Content-Type": "application/json"
+                            });
+                            res.end(JSON.stringify({
+                                v: knownVersion
+                            }));
+                        } catch (e) {}
+                    }
+                    sessionManager.saveSession(req.session);
+                });
         } else {
             srv.execTemplate(require('../template/_404.js'), req, res);
         }

+ 74 - 41
srv/src/multichatManager.js

@@ -294,59 +294,92 @@ MultiChatManager.prototype.pollPeriodical = function(knownVersion, withTyping) {
         });
 };
 
-MultiChatManager.prototype.startPolling = function(knownVersion, reqT, callback, withTyping) {
-    var _this = this;
-    var res = _this.pollPeriodical(knownVersion, withTyping),
-        now = Date.now();
-    if (res) {
-        callback(res);
-    } else if (now -reqT > POLL_TIMEOUT) {
-        callback({
-            "v": knownVersion
-        });
-    } else {
-        setTimeout(function() { _this.startPolling(knownVersion, reqT, callback, withTyping); }, 2000);
-    }
-};
+MultiChatManager.polling = [];
 
 /**
  * @param {number} knownVersion
- * @param {Function} callback
  * @param {(function(number, function((Array<{expose:Function, modified: number}>|null))))=} checkConfigUpdate
  * @param {boolean=} withTyping default true
 **/
-MultiChatManager.prototype.poll = function(knownVersion, reqT, callback, checkConfigUpdate, withTyping) {
-    this.contexts.forEach(function(ctx) {
+MultiChatManager.prototype.poll = function(knownVersion, reqT, checkConfigUpdate, withTyping) {
+    var _this = this;
+    _this.contexts.forEach(function(ctx) {
         ctx.onRequest();
     });
-    var _this = this;
-    if (checkConfigUpdate) {
-        checkConfigUpdate(knownVersion, function(config) {
-            if (config) {
-                var exposedConfig = [],
-                    v = 0;
-
-                config.forEach(function(conf) {
-                    exposedConfig.push(conf.expose());
-                    v = Math.max(v, conf.modified);
-                });
-
-                var res = _this.pollPeriodical(knownVersion, withTyping);
-                if (res) {
-                    res["config"] = exposedConfig;
-                    callback(res);
-                } else {
-                    callback({
-                        "config": exposedConfig,
-                        "v": v
+    var p = new Promise(function(succ, err) {
+        function launchPolling() {
+            var currentState = _this.pollPeriodical(knownVersion, withTyping);
+            if (currentState) {
+                succ(currentState);
+            } else {
+                var timeo = setTimeout(function() {
+                    for (var i =0, nbCtx = MultiChatManager.polling.length; i < nbCtx; i++) {
+                        if (MultiChatManager.polling[i].reqT === reqT &&
+                            MultiChatManager.polling[i].ctx === _this &&
+                            MultiChatManager.polling[i].knownVersion === knownVersion) {
+                            MultiChatManager.polling.splice(i, 1);
+                            break;
+                        }
+                    }
+                    err();
+                }, 55000);
+                MultiChatManager.polling.push({ ctx: _this, knownVersion: knownVersion, reqT: reqT, checkConfigUpdate: checkConfigUpdate, withTyping: withTyping, promise: succ, timeo: timeo });
+            }
+        };
+        if (checkConfigUpdate) {
+            checkConfigUpdate(knownVersion, function(config) {
+                if (config) {
+                    var exposedConfig = [],
+                        v = 0;
+
+                    config.forEach(function(conf) {
+                        exposedConfig.push(conf.expose());
+                        v = Math.max(v, conf.modified);
                     });
+
+                    var res = _this.pollPeriodical(knownVersion, withTyping);
+                    if (res) {
+                        res["config"] = exposedConfig;
+                        succ(res);
+                    } else {
+                        succ({
+                            "config": exposedConfig,
+                            "v": v
+                        });
+                    }
+                } else {
+                    launchPolling();
                 }
+            });
+        } else {
+            launchPolling();
+        }
+    });
+    return p;
+};
+
+MultiChatManager.prototype.containsCtx = function(ctx) {
+    for (var i =0, nbCtx = this.contexts.length; i < nbCtx; i++)
+        if (ctx === this.contexts[i])
+            return true;
+    return false;
+};
+
+/** @param {ChatContext} ctx */
+MultiChatManager.onCtxUpdated = function(ctx) {
+    var i =0;
+    while (MultiChatManager.polling[i]) {
+        var pollingObj = MultiChatManager.polling[i];
+        if (pollingObj.ctx.containsCtx(ctx)) {
+            var res = pollingObj.ctx.pollPeriodical(pollingObj.knownVersion, pollingObj.withTyping);
+            if (res) {
+                clearTimeout(pollingObj.timeo);
+                pollingObj.promise(res);
+                MultiChatManager.polling.splice(i, 1);
             } else {
-                _this.startPolling(knownVersion, reqT, callback, withTyping);
+                i++;
             }
-        });
-    } else {
-        _this.startPolling(knownVersion, reqT, callback, withTyping);
+        }
     }
 };
 

+ 12 - 7
srv/src/slack.js

@@ -8,6 +8,8 @@ const
     ,SlackHistory = require("./slackHistory.js").SlackHistory
     ,config = require("../config.js")
     ,httpsRequest = require('./httpsRequest.js').httpsRequest
+
+    ,MultiChatManager = require('./multichatManager.js').MultiChatManager
 ;
 
 const SLACK_ENDPOINT = "https://slack.com/api/"
@@ -304,27 +306,30 @@ Slack.prototype.onMessage = function(msg, t) {
                     _this.connected = true;
                     _this.unstackPendingMessages();
                     _this.ping();
+                    MultiChatManager.onCtxUpdated(_this);
                 });
             });
         });
     } else if (this.connected) {
-        this.data.onMessage(msg, t);
+        var updated = this.data.onMessage(msg, t);
         if ((msg["channel"] || msg["channel_id"] || (msg["item"] && msg["item"]["channel"])) && msg["type"] && UPDATE_LIVE.indexOf(msg["type"]) !== -1) {
             var channelId = this.data.team.id +'|' +(msg["channel"] || msg["channel_id"] || msg["item"]["channel"])
                 ,channel = this.data.channels[channelId]
-                ,histo = this.lazyHistory(channel);
-            var lastMsg = histo.push(msg, t);
+                ,histo = this.lazyHistory(channel)
+                ,msgRef = {};
+            var lastMsg = histo.push(msg, t, msgRef);
             if (lastMsg) {
                 this.data.liveV = t;
-                // FIXME not true (edit, etc..)
-                var messageObject = histo.lastMessage();
-                if (messageObject && messageObject.userId)
-                    this.data.stopTyping(channelId, messageObject.userId, t);
+                if (msgRef.msg && msgRef.msg.userId)
+                    this.data.stopTyping(channelId, msgRef.msg.userId, t);
             }
             histo.resort();
             if (channel)
                 channel.setLastMsg(lastMsg, t);
+            updated = true;
         }
+        if (updated)
+            MultiChatManager.onCtxUpdated(this);
     } else {
         this.pendingMessages.push(msg);
     }

+ 10 - 0
srv/src/slackData.js

@@ -337,15 +337,18 @@ SlackData.prototype.commandFactory = function(data) {
  * @param {number} t
 **/
 SlackData.prototype.onMessage = function(msg, t) {
+    var updated = false;
     if (msg["type"] === "presence_change") {
         var member = this.users[this.team.id +'|' +msg["user"]];
         if (member)
             member.setPresence(msg["presence"], t);
         this.staticV = Math.max(this.staticV, t);
+        updated = true;
     } else if (msg["type"] === "manual_presence_change") {
         if (this.self) {
             this.self.setPresence(msg["presence"], t);
             this.staticV = Math.max(this.staticV, t);
+            updated = true;
         }
     } else if (msg["type"] === "user_typing") {
         var chanId = this.team.id +'|' +msg["channel"];
@@ -353,12 +356,14 @@ SlackData.prototype.onMessage = function(msg, t) {
             this.typing[chanId] = {};
         this.typing[chanId][this.team.id +'|' +msg["user"]] = t;
         this.typingVersion = t;
+        updated = true;
     } else if (msg["type"] === "im_marked" || msg["type"] === "channel_marked" || msg["type"] === "group_marked") {
         var channel = this.channels[this.team.id +'|' +msg["channel"]];
         if (channel) {
             channel.lastRead = parseFloat(msg["ts"]) * 1000;
             this.staticV = channel.version = Math.max(channel.version, t);
         }
+        updated = true;
     } else if (msg["type"] === "star_added") {
         if (msg["user"] === this.self.remoteId && msg["item"]["type"] !== "message") {
             var targetChan = this.getChannelByRemoteId(msg["item"]["channel"]);
@@ -368,6 +373,7 @@ SlackData.prototype.onMessage = function(msg, t) {
                 this.staticV = Math.max(this.staticV, t);
             }
         }
+        updated = true;
     } else if (msg["type"] === "star_removed") {
         if (msg["user"] === this.self.remoteId && msg["item"]["type"] !== "message") {
             var targetChan = this.getChannelByRemoteId(msg["item"]["channel"]);
@@ -377,6 +383,7 @@ SlackData.prototype.onMessage = function(msg, t) {
                 this.staticV = Math.max(this.staticV, t);
             }
         }
+        updated = true;
     } else if (msg["type"] === "pin_added") {
         var targetChan = this.getChannelByRemoteId(msg["channel_id"] || msg["item"]["channel"]);
         if (!targetChan.pins) {
@@ -388,6 +395,7 @@ SlackData.prototype.onMessage = function(msg, t) {
             targetChan.version = Math.max(targetChan.version, t);
             this.staticV = Math.max(this.staticV, t);
         }
+        updated = true;
     } else if (msg["type"] === "pin_removed") {
         var targetChan = this.getChannelByRemoteId(msg["channel_id"] || msg["item"]["channel"]);
         if (!msg["pin_count"]) {
@@ -411,11 +419,13 @@ SlackData.prototype.onMessage = function(msg, t) {
             if (!found) // wtf out-of-sync
                 this.slack.fetchPinned(targetChan);
         }
+        updated = true;
     /*
     } else {
         console.log(msg);
     //*/
     }
+    return updated;
 };
 
 SlackData.prototype.getChannelByRemoteId = function(remoteId) {

+ 6 - 3
srv/src/slackHistory.js

@@ -70,9 +70,10 @@ SlackHistory.prototype.messageFactory = function(ev, ts) {
 /**
  * @param {Object} ev
  * @param {number} t
+ * @param {resultRef=} m
  * @return {number} ts
 **/
-SlackHistory.prototype.push = function(ev, t) {
+SlackHistory.prototype.push = function(ev, t, resultRef) {
     var msg;
     if (!ev["type"] || ev["type"] === "message") {
         var exists = false
@@ -95,7 +96,7 @@ SlackHistory.prototype.push = function(ev, t) {
             modifArg = ev["message"];
             targetId = ev["message"]["ts"];
         }
-        var msg = this.getMessageById(targetId);
+        msg = this.getMessageById(targetId);
         if (msg) {
             msg.update(this.prepareMessage(modifArg), t);
             exists = true;
@@ -141,11 +142,13 @@ SlackHistory.prototype.push = function(ev, t) {
         }
     } else if (ev["type"] === "star_added" || ev["type"] === "star_removed") {
         ev["item"]["subtype"] = "star_changed";
-        return this.push(ev["item"], t);
+        return this.push(ev["item"], t, resultRef);
     } else {
         return 0;
     }
     this.cleanOld(t);
+    if (resultRef)
+        resultRef.msg = msg;
     return msg ? msg.ts : 0;
 };
 

+ 11 - 0
srv/src/slackManager.js

@@ -1,5 +1,6 @@
 
 const Slack = require('./slack.js').Slack
+    ,MultiChatManager = require('./multichatManager.js').MultiChatManager
     ,INACTIVE_DELAY = 120000
     ,PING_DELAY = 600000
     ,RTM_PING_DELAY = 15000
@@ -18,6 +19,16 @@ setInterval(function() {
     });
 }, PING_DELAY);
 
+setInterval(function() {
+    var keys = instance.cache.keys(),
+        now = Date.now();
+    keys.forEach(function(instKey) {
+        var inst = instance.cache.get(instKey);
+        if (inst.data.cleanTyping(now))
+            MultiChatManager.onCtxUpdated(inst);
+    });
+}, 1000);
+
 SlackManager.prototype.getAuthScope = function() {
     return [
         "identity.basic",