var /** * @type {number} next period to wait before next retry in case of failure, in seconds **/ NEXT_RETRY = 0 /** * @type {Room} **/ ,SELECTED_ROOM = null /** @type {Message|null} */ ,REPLYING_TO = null /** @type {Message|null} */ ,EDITING = null ; /** * @param {Room} 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); } function outOfSync() { SLACK.lastServerVersion = 0; } /** * @param {Room} room **/ function sendTyping(room) { var xhr = new XMLHttpRequest() ,url = 'api/typing?room=' +room.id; xhr.open('POST', url, 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 {Room} 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.lastMsg && !SLACK.history[SELECTED_ROOM.id]) fetchHistory(SELECTED_ROOM, function(success) {}); } function unselectRoom() { document.getElementById(SELECTED_ROOM.id).classList.remove(R.klass.selected); } /** * @param {Room} 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 {Room} chan * @param {Command!} cmd * @param {string} args **/ function doCommand(chan, cmd, args) { var xhr = new XMLHttpRequest() ,url = 'api/cmd?room=' +chan.id +"&cmd=" +encodeURIComponent(cmd.name.substr(1)) +"&args=" +encodeURIComponent(args); xhr.open('POST', url, true); xhr.send(null); } /** * @param {Room} chan * @param {string} msg * @param {Message|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.users[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.small ,"text": replyTo.text ,"footer": footer ,"ts": replyTo.ts }; url += "&attachments=" +encodeURIComponent(JSON.stringify([attachment])); } 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(0, endCmd === -1 ? undefined : endCmd) ,args = endCmd === -1 ? "" : input.substr(endCmd) ,cmdObject = SLACK.context.commands.data[cmd]; if (cmdObject) { doCommand(SELECTED_ROOM, cmdObject, args.trim()); return true; } return false; } sendMsg(SELECTED_ROOM, input, REPLYING_TO); return true; } /** * @param {Room} chan * @param {string} text * @param {Message|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 {Room} chan * @param {Message|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 {Room} chan * @param {number} ts **/ function sendReadMArker(chan, ts) { var xhr = new XMLHttpRequest(); var url = 'api/markread?room=' +chan.id +"&ts=" +ts; xhr.open('POST', 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); }