var /** * @type {number} next period to wait before next retry in case of failure, in seconds **/ NEXT_RETRY = 0 /** * @type {SlackChan|SlackIms|SlackGroup} **/ ,SELECTED_ROOM = null ; /** * @param {SlackChan|SlackIms|SlackGroup} room * @param {function(boolean)} cb **/ function fetchHistory(room, cb) { var xhr = new XMLHttpRequest(); xhr.open('GET', 'api/hist?room=' +room.id, true); xhr.send(null); } function poll(callback) { var xhr = new XMLHttpRequest(); xhr.timeout = 1000 * 60 * 1; // 3 min timeout xhr.onreadystatechange = function(e) { if (xhr.readyState === 4) { if (xhr.status === 0) { if (NEXT_RETRY) { NEXT_RETRY = 0; onNetworkStateUpdated(true); } poll(callback); // retry on timeout return; } var resp = null ,success = Math.floor(xhr.status / 100) === 2; if (success) { if (NEXT_RETRY) { NEXT_RETRY = 0; onNetworkStateUpdated(true); } resp = xhr.response; try { resp = JSON.parse(/** @type {string} */ (resp)); } catch (e) { resp = null; } } else { if (NEXT_RETRY) { NEXT_RETRY += Math.floor((NEXT_RETRY || 5)/2); NEXT_RETRY = Math.min(60, NEXT_RETRY); } else { NEXT_RETRY = 5; onNetworkStateUpdated(false); } } callback(success, resp); } }; xhr.open('GET', 'api?v=' +SLACK.lastServerVersion, true); xhr.send(null); } /** * @param {boolean} success * @param {*} response **/ function onPollResponse(success, response) { if (success) { if (response) { SLACK.update(response); } startPolling(); } else { setTimeout(startPolling, NEXT_RETRY * 1000); } } 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(); if (SELECTED_ROOM.lastRead && !SLACK.history[SELECTED_ROOM.id]) fetchHistory(SELECTED_ROOM, function(success) {}); } function unselectRoom() { document.getElementById(SELECTED_ROOM.id).classList.remove(R.klass.selected); } /** * @param {SlackGroup|SlackChan|SlackIms} chan * @param {string} filename * @param {File} file * @param {function(string?)} callback **/ function uploadFile(chan, filename, file, callback) { var fileReader = new FileReader() ,formData = new FormData() ,xhr = new XMLHttpRequest(); formData.append("file", file); formData.append("filename", filename); xhr.onreadystatechange = function() { if (xhr.readyState === 4) { if (xhr.status === 204) { callback(null); } else { callback(xhr.statusText); } } } xhr.open('POST', 'api/file?room=' +chan.id); xhr.send(formData); } /** * @param {SlackChan|SlackGroup|SlackIms} chan * @param {string} cmd * @param {string} args **/ function doCommand(chan, cmd, args) { if (cmd == "me") { if (REPLYING_TO) return false; sendMeMessage(chan, args); return true; } else if (cmd == "shrug") { if (args.length) args += ' '; return onTextEntered(args + "¯_(ツ)_/¯", true); } console.log("Unknown command " +cmd); return false; } /** * @param {SlackChan|SlackGroup|SlackIms} chan * @param {string} msg * @param {SlackMessage|null=} replyTo **/ function sendMsg(chan, msg, replyTo) { var xhr = new XMLHttpRequest(); var url = 'api/msg?room=' +chan.id +"&text=" +encodeURIComponent(msg); if (replyTo) { var sender = SLACK.context.getMember(replyTo.userId) ,footer = "Message"; if (chan.id[0] === 'C') { footer = "Channel message"; } else if (chan.id[0] === 'D') { footer = "Direct message"; } else if (chan.id[0] === 'G') { footer = "Group message"; } var attachment = { "fallback": replyTo.text ,"author_name": "<@" +sender.id +"|" +sender.name +">" ,"author_icon": sender.icons.image_48 ,"text": replyTo.text ,"footer": footer ,"ts": replyTo.ts }; url += "&attachments=" +encodeURIComponent(JSON.stringify([attachment])); } xhr.open('POST', url, true); xhr.send(null); } /** * @param {SlackChan|SlackGroup|SlackIms} chan * @param {string} msg **/ function sendMeMessage(chan, msg) { var xhr = new XMLHttpRequest() ,url = 'api/msg?room=' +chan.id +"&me&text=" +encodeURIComponent(msg); xhr.open('POST', url, true); xhr.send(null); } /** * @param {string} input * @param {boolean=} skipCommand * @return {boolean} true on recognized input **/ function onTextEntered(input, skipCommand) { var success = true; if (EDITING) { editMsg(SELECTED_ROOM, input, EDITING); return true; } if (input[0] === '/' && skipCommand !== true) { var endCmd = input.indexOf(' ') ,cmd = input.substr(1, endCmd === -1 ? undefined : endCmd -1) ,args = endCmd === -1 ? "" : input.substr(endCmd); return doCommand(SELECTED_ROOM, cmd, args.trim()); } sendMsg(SELECTED_ROOM, input, REPLYING_TO); return true; } /** * @param {SlackChan|SlackGroup|SlackIms} chan * @param {string} text * @param {SlackMessage|null=} msg **/ function editMsg(chan, text, msg) { var xhr = new XMLHttpRequest(); var url = 'api/msg?room=' +chan.id +"&ts=" +msg.id +"&text=" +encodeURIComponent(text); xhr.open('PUT', url, true); xhr.send(null); } /** * @param {SlackChan|SlackGroup|SlackIms} chan * @param {SlackMessage|null=} msg **/ function removeMsg(chan, msg) { var xhr = new XMLHttpRequest(); var url = 'api/msg?room=' +chan.id +"&ts=" +msg.id; xhr.open('DELETE', url, true); xhr.send(null); } /** * @param {string} channelId * @param {string} msgId * @param {string} reaction **/ function addReaction(channelId, msgId, reaction) { var xhr = new XMLHttpRequest(); var url = 'api/reaction?room=' +channelId +"&msg=" +msgId +"&reaction=" +encodeURIComponent(reaction); xhr.open('POST', url, true); xhr.send(null); } /** * @param {string} channelId * @param {string} msgId * @param {string} reaction **/ function removeReaction(channelId, msgId, reaction) { var xhr = new XMLHttpRequest(); var url = 'api/reaction?room=' +channelId +"&msg=" +msgId +"&reaction=" +encodeURIComponent(reaction); xhr.open('DELETE', url, true); xhr.send(null); }