1
0
Эх сурвалжийг харах

[quickfix] updated closure compilation flags
[add] JSdoc
[add] list channels on left bar
[add] select channel
[add] client <> server api
[bugfix] escaped rtm api

B Thibault 8 жил өмнө
parent
commit
ebadf468f1

+ 7 - 5
Makefile

@@ -1,17 +1,19 @@
 
-SRC=		cli/ui.js		\
-			cli/data.js		\
+SRC=		srv/src/slackData.js\
+			cli/resources.js	\
+			cli/ui.js			\
+			cli/data.js			\
 			cli/workflow.js
 
-OUTPUT=		src/public/slack.min.js
+OUTPUT=		srv/public/slack.min.js
 
 CLOSURE=	cli/closure-compiler-v20170218.jar
 
 all:
-	java -jar ${CLOSURE} --compilation_level SIMPLE --js_output_file ${OUTPUT} ${SRC}
+	java -jar ${CLOSURE} --compilation_level SIMPLE --language_in=ECMASCRIPT5_STRICT --warning_level=VERBOSE --js_output_file ${OUTPUT} ${SRC}
 
 debug:
-	java -jar ${CLOSURE} --compilation_level WHITESPACE_ONLY --js_output_file ${OUTPUT} ${SRC}
+	java -jar ${CLOSURE} --compilation_level WHITESPACE_ONLY --language_in=ECMASCRIPT5_STRICT --js_output_file ${OUTPUT} ${SRC}
 
 $OUTPUT: all
 

+ 14 - 6
cli/data.js

@@ -1,25 +1,33 @@
 
 var
     /**
-     * @type Slack
+     * @type SlackWrapper
     **/
     SLACK;
 
-function Slack() {
+/**
+ * @constructor
+**/
+function SlackWrapper() {
     /** @type {number} */
     this.lastServerVersion = 0;
+
+    /** @type {SlackData} */
+    this.context = new SlackData(null);
 }
 
-Slack.prototype.update = function(data) {
+SlackWrapper.prototype.update = function(data) {
     if (data.v)
         this.lastServerVersion = data.v;
     if (data.static) {
-        console.log("updated static");
+        this.context.updateStatic(data.static);
+        onContextUpdated();
     }
     if (data.live) {
         console.log("updated LIVE");
     }
-}
+    console.log(this);
+};
 
-SLACK = new Slack();
+SLACK = new SlackWrapper();
 

+ 17 - 0
cli/resources.js

@@ -0,0 +1,17 @@
+var R = {
+    id: {
+        chanList: "chanList"
+        ,chatList: "chatList"
+        ,currentRoom: {
+            title: "currentRoomTitle"
+        }
+    }
+    ,klass: {
+        selected: "selected"
+        ,noRoomSelected: "no-room-selected"
+        ,chatList: {
+            entry: "slack-context-room"
+        }
+    }
+};
+

+ 56 - 0
cli/ui.js

@@ -1,5 +1,61 @@
 
+/**
+ * @param {SlackChan} chan
+ * @return {Element}
+**/
+function createChanListItem(chan) {
+    var dom = document.createElement("li");
+    dom.id = chan.id;
+    dom.className = R.klass.chatList.entry;
+    dom.textContent = chan.name;
+    return dom;
+}
+
+function onContextUpdated() {
+    var chanListFram = document.createDocumentFragment();
+
+    for (var chanId in SLACK.context.self.channels) {
+        var chan = SLACK.context.channels[chanId]
+            ,chanListItem = createChanListItem(chan);
+        if (chanListItem) {
+            chanListFram.appendChild(chanListItem);
+        }
+    }
+    document.getElementById(R.id.chanList).textContent = "";
+    document.getElementById(R.id.chanList).appendChild(chanListFram);
+}
+
+function onRoomSelected() {
+    var name = SELECTED_ROOM.name;
+    if (!name) {
+        var members = [];
+        for (var i in SELECTED_ROOM.members) {
+            members.push(SELECTED_ROOM.members[i].name);
+        }
+        name = members.join(", ");
+    }
+    document.getElementById(R.id.currentRoom.title).textContent = name;
+}
+
+function onChanClick(e) {
+    while (e.target !== e.currentTarget && e.target) {
+        if (e.target.classList.contains(R.klass.chatList.entry)) {
+            var room = (SLACK.context.channels[e.target.id] ||
+                SLACK.context.ims[e.target.id] ||
+                SLACK.context.groups[e.target.id]);
+
+            if (room && room !== SELECTED_ROOM) {
+                selectRoom(room);
+            }
+            return;
+        }
+        e.target = e.target.parentElement;
+    }
+}
+
 document.addEventListener('DOMContentLoaded', function() {
+    document.getElementById(R.id.chatList).addEventListener("click", onChanClick);
+
     startPolling();
 });
 

+ 22 - 1
cli/workflow.js

@@ -4,6 +4,11 @@ var
      * @type {number} next period to wait before next retry in case of failure, in seconds
     **/
     NEXT_RETRY = 5
+
+    /**
+     * @type {SlackChan|SlackIms|SlackGroup}
+    **/
+    ,SELECTED_ROOM = null
 ;
 
 function poll(callback) {
@@ -23,7 +28,7 @@ function poll(callback) {
                 NEXT_RETRY = 5;
                 resp = xhr.response;
                 try {
-                    resp = JSON.parse(resp);
+                    resp = JSON.parse(/** @type {string} */ (resp));
                 } catch (e) {
                     resp = null;
                 }
@@ -57,3 +62,19 @@ function startPolling() {
     poll(onPollResponse);
 }
 
+/**
+ * @param {SlackChan|SlackIms|SlackGroup} room
+**/
+function selectRoom(room) {
+    if (SELECTED_ROOM)
+        unselectRoom();
+    document.getElementById(room.id).classList.add(R.klass.selected);
+    document.body.classList.remove(R.klass.noRoomSelected);
+    SELECTED_ROOM = room;
+    onRoomSelected();
+}
+
+function unselectRoom() {
+    document.getElementById(SELECTED_ROOM.id).classList.remove(R.klass.selected);
+}
+

+ 10 - 2
srv/public/index.html

@@ -5,10 +5,18 @@
     <body>
         <aside class="slack-context">
             <nav class="slack-context-menu"></nav>
-            <div class="slack-context-channellist"></div>
-            <div class="slack-context-imlist"></div>
+            <div class="slack-context-rooms" id="chatList">
+                <ul class="slack-context-channellist" id="chanList"></ul>
+                <div class="slack-context-imlist"></div>
+            </div>
         </aside>
         <div class="slack-chat-container">
+            <div class="slack-chat-title" id="currentRoomTitle"></div>
+            <div class="slack-chat-content"></div>
+            <div class="slack-chat-control">
+                <input type="text" />
+                <input type="submit" />
+            </div>
         </div>
         <script src="slack.min.js"></script>
     </body>

+ 17 - 2
srv/public/slack.min.js

@@ -1,2 +1,17 @@
-document.addEventListener("DOMContentLoaded",function(){startPolling()});var SLACK;function Slack(){this.lastServerVersion=0}Slack.prototype.update=function(b){b.v&&(this.lastServerVersion=b.v);b["static"]&&console.log("updated static");b.live&&console.log("updated LIVE")};SLACK=new Slack;var NEXT_RETRY=5;function poll(b){var a=new XMLHttpRequest;a.timeout=6E4;a.onreadystatechange=function(c){if(4===a.readyState)if(0===a.status)poll(b),NEXT_RETRY=5;else{c=null;var d=2===Math.floor(a.status/100);if(d){NEXT_RETRY=5;c=a.response;try{c=JSON.parse(c)}catch(e){c=null}}else NEXT_RETRY+=Math.floor(NEXT_RETRY/2),NEXT_RETRY=Math.min(60,NEXT_RETRY);b(d,c)}};a.open("GET","api?v="+SLACK.lastServerVersion,!0);a.send(null)}
-function onPollResponse(b,a){b?(a&&SLACK.update(a),startPolling()):setTimeout(startPolling,1E3*NEXT_RETRY)}function startPolling(){poll(onPollResponse)};
+function SlackTeam(a){this.id=a.id;this.name=a.name;this.domain=a.domain;this.callApp=a.prefs.calling_app_id;this.callAppName=a.prefs.calling_app_name;this.fileUploadPermission=a.prefs.disable_file_uploads;this.fileEditPermission=a.prefs.disable_file_editing;this.fileDeletePermission=a.prefs.disable_file_deleting;this.icons={image_34:a.icon.image_34,image_44:a.icon.image_44,image_68:a.icon.image_68,image_88:a.icon.image_88,image_102:a.icon.image_102,image_132:a.icon.image_132,image_230:a.icon.image_230,
+image_default:a.icon.image_default}}
+function SlackChan(a,c){this.id=a.id;this.name=a.name;this.created=a.created;this.creator=c.getMember(a.creator);this.archived=a.is_archived;this.isMember=a.is_member;this.lastRead=a.last_read;this.members={};if(a.members)for(var b=0,d=a.members.length;b<d;b++){var e=c.getMember(a.members[b]);this.members[e.id]=e;e.channels[this.id]=this}a.topic&&(this.topic=a.topic.value,this.topicCreator=c.getMember(a.topic.creator),this.topicTs=a.topic.last_set);a.purpose&&(this.purpose=a.purpose.value,this.purposeCreator=
+c.getMember(a.purpose.creator),this.purposeTs=a.purpose.last_set)}function SlackGroup(a){}function SlackIms(a){}
+function SlackUser(a){this.id=a.id;this.name=a.name;this.deleted=a.deleted;this.status=a.status;this.realName=a.real_name||a.profile.real_name;this.presence="away"!==a.presence;this.icons={image_24:a.profile.image_24,image_32:a.profile.image_32,image_48:a.profile.image_48,image_72:a.profile.image_72,image_192:a.profile.image_192,image_512:a.profile.image_512};this.email=a.profile.email;this.firstName=a.profile.first_name;this.lastName=a.profile.last_name;this.channels={}}
+function SlackBot(a){this.id=a.id;this.deleted=a.deleted;this.name=a.name;this.appId=a.app_id;this.icons={image_36:a.icons.image_36,image_48:a.icons.image_48,image_72:a.icons.image_72};this.channels={}}function SlackHistory(a){this.id=a.id;this.target=a;this.v=0;this.messages=[]}function SlackData(a){this.team=null;this.channels={};this.groups=[];this.ims=[];this.users={};this.self=null;this.bots={};this.history={};this.slack=a;this.liveV=this.staticV=0}
+SlackTeam.prototype.toStatic=function(){return{id:this.id,name:this.name,domain:this.domain,prefs:{calling_app_id:this.callApp,calling_app_name:this.callAppName,disable_file_uploads:this.fileUploadPermission,disable_file_editing:this.fileEditPermission,disable_file_deleting:this.fileDeletePermission},icon:this.icons}};SlackHistory.prototype.update=function(a){};SlackHistory.prototype.getUpdates=function(a){};
+SlackChan.prototype.toStatic=function(){var a={id:this.id,name:this.name,created:this.created,creator:this.creator.id,is_archived:this.archived,is_member:this.isMember,last_read:this.lastRead};this.isMember&&(a.members=Object.keys(this.members),a.topic={value:this.topic,creator:this.topicCreator?this.topicCreator.id:null,last_set:this.topicTs},a.purpose={value:this.purpose,creator:this.purposeCreator?this.purposeCreator.id:null,last_set:this.purposeTs});return a};
+SlackUser.prototype.toStatic=function(){return{id:this.id,name:this.name,deleted:this.deleted,status:this.status,real_name:this.realName,presence:this.presence?"present":"away",profile:{email:this.email,first_name:this.firstName,last_name:this.lastName,image_24:this.icons.image_24,image_32:this.icons.image_32,image_48:this.icons.image_48,image_72:this.icons.image_72,image_192:this.icons.image_192,image_512:this.icons.image_512}}};
+SlackData.prototype.updateStatic=function(a,c){for(var b=0,d=a.bots.length;b<d;b++)this.bots[a.bots[b].id]=new SlackBot(a.bots[b]);this.team=new SlackTeam(a.team);b=0;for(d=a.users.length;b<d;b++)this.users[a.users[b].id]=new SlackUser(a.users[b]);b=0;for(d=a.channels.length;b<d;b++)this.channels[a.channels[b].id]=new SlackChan(a.channels[b],this);this.staticV=parseFloat(a.latest_event_ts);this.self=this.getMember(a.self.id);if(this.slack){var e={},d=!1;for(b in this.channels)this.history[b]||(d=
+!0,e[b]=this.channels[b]);if(d)for(b in e){var f=this;this.slack.fetchHistory(b,function(a,b){if(null!=b){f.setHistory(e[a],b);e[a]=null;for(var d in e)if(e[d])return}c()})}else c()}};SlackData.prototype.setHistory=function(a,c){this.history[a.id]||(this.history[a.id]=new SlackHistory(a));this.history[a.id].update(c)};SlackData.prototype.getBotsByAppId=function(a){var c=[],b;for(b in this.bots)this.bots[b].appId===a&&c.push(this.bots[b]);return c};
+SlackData.prototype.getMember=function(a){return this.users[a]||this.bots[a]||null};SlackData.prototype.buildLive=function(a){var c={},b;for(b in this.history)this.history[b].v>a&&(c[b]=this.history[b].getUpdates(a));return c};
+SlackData.prototype.buildStatic=function(){var a={team:this.team.toStatic(),channels:[],groups:[],ims:[],users:[],bots:[],self:{id:this.self.id}},c;for(c in this.channels)a.channels.push(this.channels[c].toStatic());for(var b in this.users)a.users.push(this.users[b].toStatic());return a};SlackData.prototype.getUpdates=function(a){var c={};this.liveV>a&&(c.live=this.buildLive(a));this.staticV>a&&(c["static"]=this.buildStatic());return c};
+(function(){"undefined"!==typeof module&&(module.exports.SlackData=SlackData)})();var R={id:{chanList:"chanList",chatList:"chatList",currentRoom:{title:"currentRoomTitle"}},klass:{selected:"selected",noRoomSelected:"no-room-selected",chatList:{entry:"slack-context-room"}}};function createChanListItem(a){var c=document.createElement("li");c.id=a.id;c.className=R.klass.chatList.entry;c.textContent=a.name;return c}function onContextUpdated(){var a=document.createDocumentFragment(),c;for(c in SLACK.context.self.channels){var b=createChanListItem(SLACK.context.channels[c]);b&&a.appendChild(b)}document.getElementById(R.id.chanList).textContent="";document.getElementById(R.id.chanList).appendChild(a)}
+function onRoomSelected(){var a=SELECTED_ROOM.name;if(!a){var a=[],c;for(c in SELECTED_ROOM.members)a.push(SELECTED_ROOM.members[c].name);a=a.join(", ")}document.getElementById(R.id.currentRoom.title).textContent=a}
+function onChanClick(a){for(;a.target!==a.currentTarget&&a.target;){if(a.target.classList.contains(R.klass.chatList.entry)){(a=SLACK.context.channels[a.target.id]||SLACK.context.ims[a.target.id]||SLACK.context.groups[a.target.id])&&a!==SELECTED_ROOM&&selectRoom(a);break}a.target=a.target.parentElement}}document.addEventListener("DOMContentLoaded",function(){document.getElementById(R.id.chatList).addEventListener("click",onChanClick);startPolling()});var SLACK;function SlackWrapper(){this.lastServerVersion=0;this.context=new SlackData(null)}SlackWrapper.prototype.update=function(a){a.v&&(this.lastServerVersion=a.v);a["static"]&&(this.context.updateStatic(a["static"]),onContextUpdated());a.live&&console.log("updated LIVE");console.log(this)};SLACK=new SlackWrapper;var NEXT_RETRY=5,SELECTED_ROOM=null;function poll(a){var c=new XMLHttpRequest;c.timeout=6E4;c.onreadystatechange=function(b){if(4===c.readyState)if(0===c.status)poll(a),NEXT_RETRY=5;else{b=null;var d=2===Math.floor(c.status/100);if(d){NEXT_RETRY=5;b=c.response;try{b=JSON.parse(b)}catch(e){b=null}}else NEXT_RETRY+=Math.floor(NEXT_RETRY/2),NEXT_RETRY=Math.min(60,NEXT_RETRY);a(d,b)}};c.open("GET","api?v="+SLACK.lastServerVersion,!0);c.send(null)}
+function onPollResponse(a,c){a?(c&&SLACK.update(c),startPolling()):setTimeout(startPolling,1E3*NEXT_RETRY)}function startPolling(){poll(onPollResponse)}function selectRoom(a){SELECTED_ROOM&&unselectRoom();document.getElementById(a.id).classList.add(R.klass.selected);document.body.classList.remove(R.klass.noRoomSelected);SELECTED_ROOM=a;onRoomSelected()}function unselectRoom(){document.getElementById(SELECTED_ROOM.id).classList.remove(R.klass.selected)};

+ 4 - 0
srv/public/style.css

@@ -24,3 +24,7 @@ body {
     right: 0;
 }
 
+.slack-context-room.selected {
+    background-color: lightgrey;
+}
+

+ 5 - 4
srv/src/httpServ.js

@@ -38,7 +38,7 @@ Server.parseCookies = function(req) {
     }
     }
     return cookieObj;
-}
+};
 
 Server.stringifyCookies = function(cookies) {
     var cookieString = "";
@@ -50,11 +50,11 @@ Server.stringifyCookies = function(cookies) {
     cookieString += "; ";
     }
     return cookieString;
-}
+};
 
 Server.prototype.onListen = function() {
     console.log("onListen");
-}
+};
 
 function redirectToSlackAuth(res) {
     res.writeHeader("302", {
@@ -104,6 +104,7 @@ Server.prototype.onRequest = function(req, res) {
 
         res.slack = slackManager.lazyGet(req.session);
         res.slack.onRequest(req.urlObj.queryTokens.v || 0, (slack, newData) => {
+            console.log("write handler");
             if (!slack.connected) {
                 res.writeHeader("403", {
                     "Content-Type": "application/json"
@@ -119,7 +120,7 @@ Server.prototype.onRequest = function(req, res) {
             res.end();
         });
     }
-}
+};
 
 module.exports.HttpServ = Server;
 

+ 5 - 5
srv/src/session.js

@@ -16,14 +16,14 @@ function Session(reqT, sessId) {
 SessionManager.createSessionId = function(req) {
     var token = Date.now() + "" +Math.random();
     return crypto.createHash('md5').update(token).digest('hex');
-}
+};
 
 SessionManager.prototype.forRequest = function(request) {
     if (request.cookies && request.cookies.sessID) {
         return this.sessions.get(request.cookies.sessID);
     }
     return null;
-}
+};
 
 SessionManager.prototype.lazyForRequest = function(request) {
     var session = this.forRequest(request);
@@ -33,7 +33,7 @@ SessionManager.prototype.lazyForRequest = function(request) {
     var sessionId = SessionManager.createSessionId(request);
     session = new Session(request.reqT, sessionId);
     return session;
-}
+};
 
 SessionManager.prototype.saveSession = function(session) {
     if (session) {
@@ -41,7 +41,7 @@ SessionManager.prototype.saveSession = function(session) {
             life: SessionManager.defaultTtl
         });
     }
-}
+};
 
 Session.prototype.setSlackToken = function(reqT, slackToken) {
     this.slackUserId = slackToken["user_id"];
@@ -49,7 +49,7 @@ Session.prototype.setSlackToken = function(reqT, slackToken) {
     this.slackTeamId = slackToken["team_name"];
     this.slackToken = slackToken["access_token"];
     this.modifiedAt = reqT;
-}
+};
 
 var sessMan = new SessionManager();
 

+ 7 - 7
srv/src/slack.js

@@ -29,7 +29,7 @@ Slack.prototype.onRequest = function(knownVersion, cb) {
     } else {
         this.waitForEvent(knownVersion);
     }
-}
+};
 
 function httpsRequest(url, cb) {
     https.get(url, (res) => {
@@ -80,7 +80,7 @@ Slack.prototype.connect = function(knownVersion) {
             _this.waitForEvent(knownVersion);
         });
     });
-}
+};
 
 Slack.prototype.waitForEvent = function(knownVersion) {
     var tick = 0
@@ -108,7 +108,7 @@ Slack.prototype.waitForEvent = function(knownVersion) {
             return;
         }
     }, 1000);
-}
+};
 
 Slack.prototype.connectRtm = function(url, cb) {
     var _this = this;
@@ -132,10 +132,10 @@ Slack.prototype.connectRtm = function(url, cb) {
         console.error("RTM hang up");
         _this.close();
     });
-}
+};
 
 Slack.prototype.close = function() {
-}
+};
 
 Slack.getOauthToken = function(code, cb) {
     httpsRequest(SLACK_ENDPOINT+GETAPI.oauth
@@ -149,7 +149,7 @@ Slack.getOauthToken = function(code, cb) {
             cb(null);
         }
     });
-}
+};
 
 Slack.prototype.fetchHistory = function(targetId, cb) {
     httpsRequest(SLACK_ENDPOINT +GETAPI.history
@@ -159,7 +159,7 @@ Slack.prototype.fetchHistory = function(targetId, cb) {
     (status, resp) => {
         cb(targetId, status === 200 && resp && resp.ok ? resp.messages : null);
     });
-}
+};
 
 module.exports.Slack = Slack;
 

+ 225 - 69
srv/src/slackData.js

@@ -1,39 +1,67 @@
 
+/**
+ * @constructor
+**/
 function SlackTeam(teamData) {
-    this.id = teamData.id;
-    this.name = teamData.name;
-    this.domain = teamData.domain;
-    this.callApp = teamData.prefs.calling_app_id;
-    this.callAppName = teamData.prefs.calling_app_name;
-    this.fileUploadPermission = teamData.prefs.disable_file_uploads;
-    this.fileEditPermission = teamData.prefs.disable_file_editing;
-    this.fileDeletePermission = teamData.prefs.disable_file_deleting;
-    this.icons = teamData.icon; // image_34, image_44, image_68, image_88, image_102, image_132, image_230, image_default
+    this.id = teamData["id"];
+    this.name = teamData["name"];
+    this.domain = teamData["domain"];
+    this.callApp = teamData["prefs"]["calling_app_id"];
+    this.callAppName = teamData["prefs"]["calling_app_name"];
+    this.fileUploadPermission = teamData["prefs"]["disable_file_uploads"];
+    this.fileEditPermission = teamData["prefs"]["disable_file_editing"];
+    this.fileDeletePermission = teamData["prefs"]["disable_file_deleting"];
+    this.icons = {
+        image_34: teamData["icon"]["image_34"]
+        ,image_44: teamData["icon"]["image_44"]
+        ,image_68: teamData["icon"]["image_68"]
+        ,image_88: teamData["icon"]["image_88"]
+        ,image_102: teamData["icon"]["image_102"]
+        ,image_132: teamData["icon"]["image_132"]
+        ,image_230: teamData["icon"]["image_230"]
+        ,image_default: teamData["icon"]["image_default"]
+    };
 }
 
+/**
+ * @constructor
+**/
 function SlackChan(chanData, slackData) {
-    this.id = chanData.id;
-    this.name = chanData.name;
-    this.created = chanData.created;
-    this.creator = chanData.creator;
-    this.archived = chanData.is_archived;
-    this.isMember = chanData.is_member;
-    this.lastRead = chanData.last_read;
+    this.id = chanData["id"];
+    this.name = chanData["name"];
+    this.created = chanData["created"];
+    this.creator = slackData.getMember(chanData["creator"]);
+    this.archived = chanData["is_archived"];
+    this.isMember = chanData["is_member"];
+    this.lastRead = chanData["last_read"];
     this.members = {};
-    for (var i =0, nbMembers = chanData.members.length; i < nbMembers; i++)
-        this.members[chanData.members[i]] = slackData.getMember(chanData.members[i]);
-    this.topic = chanData.topic.value;
-    this.topicCreator = slackData.getMember(chanData.topic.creator);
-    this.topicTs = chanData.topic.last_set;
-    this.purpose = chanData.purpose.value;
-    this.purposeCreator = slackData.getMember(chanData.purpose.creator);
-    this.purposeTs = chanData.purpose.last_set;
+    if (chanData["members"]) for (var i =0, nbMembers = chanData["members"].length; i < nbMembers; i++) {
+        var member = slackData.getMember(chanData["members"][i]);
+        this.members[member.id] = member;
+        member.channels[this.id] = this;
+    }
+    if (chanData["topic"]) {
+        this.topic = chanData["topic"]["value"];
+        this.topicCreator = slackData.getMember(chanData["topic"]["creator"]);
+        this.topicTs = chanData["topic"]["last_set"];
+    }
+    if (chanData["purpose"]) {
+        this.purpose = chanData["purpose"]["value"];
+        this.purposeCreator = slackData.getMember(chanData["purpose"]["creator"]);
+        this.purposeTs = chanData["purpose"]["last_set"];
+    }
 }
 
+/**
+ * @constructor
+**/
 function SlackGroup(groupData) {
     // TODO
 }
 
+/**
+ * @constructor
+**/
 function SlackIms(imsData) {
     /*
     * TODO
@@ -55,35 +83,49 @@ function SlackIms(imsData) {
     */
 }
 
+/**
+ * @constructor
+**/
 function SlackUser(userData) {
-    this.id = userData.id;
-    this.name = userData.name;
-    this.deleted = userData.deleted;
-    this.status = userData.status;
-    this.realName = userData.real_name || userData.profile.real_name;
-    this.presence = userData.presence !== 'away';
+    this.id = userData["id"];
+    this.name = userData["name"];
+    this.deleted = userData["deleted"];
+    this.status = userData["status"];
+    this.realName = userData["real_name"] || userData["profile"]["real_name"];
+    this.presence = userData["presence"] !== 'away';
     this.icons = {
-        image_24: userData.profile.image_24
-        ,image_32: userData.profile.image_32
-        ,image_48: userData.profile.image_48
-        ,image_72: userData.profile.image_72
-        ,image_192: userData.profile.image_192
-        ,image_512: userData.profile.image_512
+        image_24: userData["profile"]["image_24"]
+        ,image_32: userData["profile"]["image_32"]
+        ,image_48: userData["profile"]["image_48"]
+        ,image_72: userData["profile"]["image_72"]
+        ,image_192: userData["profile"]["image_192"]
+        ,image_512: userData["profile"]["image_512"]
     };
-    this.email = userData.profile.email;
-    this.firstName = userData.profile.first_name;
-    this.lastName = userData.profile.last_name;
+    this.email = userData["profile"]["email"];
+    this.firstName = userData["profile"]["first_name"];
+    this.lastName = userData["profile"]["last_name"];
+    this.channels = {};
 }
 
+/**
+ * @constructor
+**/
 function SlackBot(botData) {
-    this.id = botData.id;
-    this.deleted = botData.deleted;
-    this.name = botData.name;
-    this.appId = botData.app_id;
-    // { image_36, image_48, image_72 }
-    this.icons = botData.icons;
+    this.id = botData["id"];
+    this.deleted = botData["deleted"];
+    this.name = botData["name"];
+    this.appId = botData["app_id"];
+    this.icons = {
+        image_36: botData["icons"]["image_36"]
+        ,image_48: botData["icons"]["image_48"]
+        ,image_72: botData["icons"]["image_72"]
+    };
+    this.channels = {};
 }
 
+/**
+ * @constructor
+**/
 function SlackHistory(target) {
     this.id = target.id;
     this.target = target;
@@ -91,6 +133,9 @@ function SlackHistory(target) {
     this.messages = [];
 }
 
+/**
+ * @constructor
+**/
 function SlackData(slack) {
     this.team = null;
     this.channels = {};
@@ -103,22 +148,98 @@ function SlackData(slack) {
     this.history = {};
     this.slack = slack;
 
+    /** @type {number} */
     this.staticV = 0;
+    /** @type {number} */
     this.liveV = 0;
 }
 
+/**
+ * @return {Object}
+**/
 SlackTeam.prototype.toStatic = function() {
-    return this;
-}
+    return {
+        "id": this.id
+        ,"name": this.name
+        ,"domain": this.domain
+        ,"prefs": {
+            "calling_app_id": this.callApp
+            ,"calling_app_name": this.callAppName
+            ,"disable_file_uploads": this.fileUploadPermission
+            ,"disable_file_editing": this.fileEditPermission
+            ,"disable_file_deleting": this.fileDeletePermission
+        }
+        ,"icon": this.icons
+    };
+};
 
 SlackHistory.prototype.update = function(messages) {
     // TODO
-}
+};
 
 SlackHistory.prototype.getUpdates = function(knownVersion) {
     // TODO
-}
+};
+
+/**
+ * @return {Object}
+**/
+SlackChan.prototype.toStatic = function() {
+    var res = {
+        "id": this.id
+        ,"name": this.name
+        ,"created": this.created
+        ,"creator": this.creator.id
+        ,"is_archived": this.archived
+        ,"is_member": this.isMember
+        ,"last_read": this.lastRead
+    };
+    if (this.isMember) {
+        res["members"] = Object.keys(this.members);
+        res["topic"] = {
+            "value": this.topic
+            ,"creator": this.topicCreator ? this.topicCreator.id : null
+            ,"last_set": this.topicTs
+        }
+        res["purpose"] = {
+            "value": this.purpose
+            ,"creator": this.purposeCreator ? this.purposeCreator.id : null
+            ,"last_set": this.purposeTs
+        }
+    }
+    return res;
+};
+
+/**
+ * @return {Object}
+**/
+SlackUser.prototype.toStatic = function() {
+    return {
+        "id": this.id
+        ,"name": this.name
+        ,"deleted": this.deleted
+        ,"status": this.status
+        ,"real_name": this.realName
+        ,"presence": this.presence ? "present" : "away"
+        ,"profile": {
+            "email": this.email
+            ,"first_name": this.firstName
+            ,"last_name": this.lastName
+
+            ,"image_24": this.icons.image_24
+            ,"image_32": this.icons.image_32
+            ,"image_48": this.icons.image_48
+            ,"image_72": this.icons.image_72
+            ,"image_192": this.icons.image_192
+            ,"image_512": this.icons.image_512
+        }
+    };
+};
 
+/**
+ * @param {*} data
+ * @param {function()=} callback
+**/
 SlackData.prototype.updateStatic = function(data, callback) {
     for (var i =0, nbBots = data.bots.length; i < nbBots; i++)
         this.bots[data.bots[i].id] = new SlackBot(data.bots[i]);
@@ -129,8 +250,12 @@ SlackData.prototype.updateStatic = function(data, callback) {
         this.channels[data.channels[i].id] = new SlackChan(data.channels[i], this);
     //this.groups.push(new SlackGroup(data.groups)); TODO
     //this.ims.push(new SlackIms(data.ims[0])); TODO
-    this.staticV = parseFloat(data.latest_event_ts);
+    this.staticV = parseFloat(data["latest_event_ts"]);
+    this.self = this.getMember(data["self"]["id"]);
 
+    if (!this.slack) {
+        return;
+    }
     var fetchHistory = {}
         ,doFetch = false;
     for (var i in this.channels)
@@ -144,7 +269,7 @@ SlackData.prototype.updateStatic = function(data, callback) {
     else for (var i in fetchHistory) {
         var _this = this;
 
-        this.slack.fetchHistory(i, (currentUpdated, data) => {
+        this.slack.fetchHistory(i, function(currentUpdated, data) {
             if (data != null) {
                 _this.setHistory(fetchHistory[currentUpdated], data);
                 fetchHistory[currentUpdated] = null;
@@ -156,14 +281,18 @@ SlackData.prototype.updateStatic = function(data, callback) {
             callback();
         });
     }
-}
+};
 
 SlackData.prototype.setHistory = function(target, data) {
     if (!this.history[target.id])
         this.history[target.id] = new SlackHistory(target);
     this.history[target.id].update(data);
-}
+};
 
+/**
+ * @param {string} appId
+ * @return {Array.<SlackBot>}
+**/
 SlackData.prototype.getBotsByAppId = function(appId) {
     var bots = [];
 
@@ -173,43 +302,70 @@ SlackData.prototype.getBotsByAppId = function(appId) {
         }
     }
     return bots;
-}
+};
 
+/**
+ * @param {string} mId
+ * @return {SlackUser|null}
+**/
 SlackData.prototype.getMember = function(mId) {
     return this.users[mId] || this.bots[mId] || null;
-}
+};
 
+/**
+ * @param {number} knownVersion
+ * @return {Object}
+**/
 SlackData.prototype.buildLive = function(knownVersion) {
     var res = {};
     for (var i in this.history)
         if (this.history[i].v > knownVersion)
             res[i] = this.history[i].getUpdates(knownVersion);
-}
+    return res;
+};
 
+/** @return {Object} */
 SlackData.prototype.buildStatic = function() {
     var res = {
-        team: this.team.toStatic()
-        ,channels: {}
-        ,groups: {}
-        ,ims: {}
-        ,users: {}
-        ,bots: {}
+        "team": this.team.toStatic()
+        ,"channels": []
+        ,"groups": [] // TODO
+        ,"ims": [] // TODO
+        ,"users": []
+        ,"bots": [] // TODO
+        ,"self": {
+            "id": this.self.id
+        }
     };
+    for (var chanId in this.channels) {
+        res.channels.push(this.channels[chanId].toStatic());
+    }
+    for (var userId in this.users) {
+        res.users.push(this.users[userId].toStatic());
+    }
     return res;
-}
+};
 
+/**
+ * @param {number} knownVersion
+ * @return {{live: (Object|undefined), static: (Object|undefined)}}
+**/
 SlackData.prototype.getUpdates = function(knownVersion) {
     var res = {};
+
     if (this.liveV > knownVersion) {
-        res.live = this.buildLive();
+        res["live"] = this.buildLive(knownVersion);
     }
     if (this.staticV > knownVersion) {
-        res.static = this.buildStatic();
+        res["static"] = this.buildStatic();
     }
     return res;
-}
+};
 
-if (module.exports) {
-    module.exports.SlackData = SlackData;
-}
+/** @suppress {undefinedVars,checkTypes} */
+(function() {
+    if (typeof module !== "undefined") {
+        module.exports.SlackData = SlackData;
+    }
+})();
 

+ 3 - 3
srv/src/slackManager.js

@@ -11,7 +11,7 @@ SlackManager.prototype.getScope = function() {
     return [
         "client"
     ];
-}
+};
 
 SlackManager.prototype.lazyGet = function(sess) {
     var key = "SLACK_" +sess.sessId
@@ -27,11 +27,11 @@ SlackManager.prototype.lazyGet = function(sess) {
         });
     }
     return val;
-}
+};
 
 SlackManager.prototype.removed = function(key, slack) {
     slack.close();
-}
+};
 
 module.exports.SlackManager = instance;
 

+ 2 - 2
srv/src/url.js

@@ -41,10 +41,10 @@ function Url(url) {
 
 Url.prototype.isPublic = function() {
     return this.serve !== null;
-}
+};
 
 Url.prototype.getReadStream = function() {
     return fs.createReadStream(this.serve.filename);
-}
+};
 
 module.exports = { Url: Url };