const mm_client = (function() { "use strict"; class MattermostClient { constructor (endpoint, credentials_provider) { this.endpoint = endpoint; this.credentials = credentials_provider; const creds = this.credentials.get(this.endpoint); this.token = creds ? creds.token : null; console.info(`Created MattermostClient for ${this.endpoint}, ${this.token ? "found token" : "did not find token"}`); this.socket = null; this.channelStore = new store.ChannelStore(this); } async get(path, queryParams) { const headers = this.token ? {"Authorization": `Bearer ${this.token}`} : {}; const response = await ajax.getJson(`${this.endpoint}${path}`, {headers, queryParams}); if (!response.ok) { throw response; } return response.responseJson; } async post(path, data) { const headers = this.token ? {"Authorization": `Bearer ${this.token}`} : {}; const response = await ajax.postJson(`${this.endpoint}${path}`, data, {headers}); if (!response.ok) { throw response; } return response; } async paginatedGet(url, queryParams, perPage=200) { let data = []; let params = {page: 0, per_page: perPage}; if (queryParams) { extend(params, queryParams); } let loadMore = true; while (loadMore) { if (params.page > 100) { throw new Error("Requesting more than 100 pages, something looks wrong or the response is massive (>20,000 items)"); } const response = await this.get(url, params); data = data.concat(response); loadMore = response.length > 0; params.page++; } return data; } start() { assert(this.token); if (this.socket) { try { this.socket.close(); } catch(e) {} } const users = this.regetUsers(); const me = this.userMe().then(data => {this.me = data;}); const channels = this.channelStore.fetch(); return Promise.all([ users, me, channels ]) .then(() => this.websocket()) .then(async () => { const curChan = currentChannel(); if (curChan.endpoint === this.endpoint) { console.debug("Reloading channel"); const {channels, teams, unread} = await this.channelStore.get(); const channel = channels[curChan.channel_id]; const team = channel["team_id"] ? teams[channel["team_id"]] : null; await switchToChannel(this, team, channel); } }) .catch(e => { console.error("Could not connect, trying again in 5 seconds", e); window.setTimeout(() => this.start(), 5000); }); } async loggedIn() { if (!this.token) { return false; } try { const meResponse = await this.userMe(); return true; } catch (e) { if (e instanceof ajax.NotOkError && e.xhr.status == 401) { return false; } else { throw e; } } } async logIn(login_id, password) { if (await this.loggedIn()) { throw Error("Already logged in on this server"); } const response = await this.post("/users/login", {login_id, password}); const token = response.getResponseHeader("Token"); if (!token) { throw Error("No Token header in response to log in request"); } this.credentials.store(this.endpoint, login_id, token); this.token = token; return response.responseJson; } async logOut() { console.log(this); console.log(this.token); assert(this.token, "logged in"); try { const response = await this.post("/users/logout", this.token); } catch (e) { console.warn(e); } // Verify that the token is now invalidated if (await this.loggedIn()) { throw new Error("Failed to log out: token still works after trying to log out"); } this.credentials.clear(this.endpoint); this.token = null; return response.responseJson; } user(user_id) { assertIsMattermostId(user_id); return this.get(`/users/${user_id}`); } userMe() { return this.get("/users/me"); } myTeams() { return this.get("/users/me/teams"); } myChannels(team_id) { assertIsMattermostId(team_id); return this.get(`/users/me/teams/${team_id}/channels`); } channelPosts(channel_id, beforePost=null, afterPost=null, since=null) { assertIsMattermostId(channel_id); assertIsNullOrMattermostId(beforePost); assertIsNullOrMattermostId(afterPost); return this.get(`/channels/${channel_id}/posts`, { before: beforePost, after: afterPost, since }); } getUnread(team_id) { return this.get(`/users/me/teams/${team_id}/channels/members`); } markChannelRead(channel_id) { assertIsMattermostId(channel_id); return this.post(`/channels/members/me/view`, { channel_id: channel_id }); } writePost(channel_id, message) { return this.post("/posts", { "channel_id": channel_id, "message": message }); } executeSlashCommand(channel_id, command) { return this.post("/commands/execute", {channel_id, command}); } getUsers() { if (!this.users) { this.users = this.paginatedGet("/users", {}).then(users => { const newUsers = Object.create(null); for (let user_data of users) { const user = user_data; newUsers[user.id] = user; } return newUsers; }); } return this.users; } regetUsers() { this.users = null; return this.getUsers(); } async filePublicLink(file_id) { const response = await this.get(`/files/${file_id}/link`, {}); return response.link; } websocket() { assert(this.token, "logged in"); const endpoint_destructuring = this.endpoint.match(/^([^:]+)(:\/\/.*)/); const protocol = endpoint_destructuring[1] === "https" ? "wss" : "ws"; const endpoint = protocol + endpoint_destructuring[2] + "/websocket"; const socket = new WebSocket(endpoint); socket.addEventListener("error", event => { console.error("Websocket errored", event); try { socket.close(); } catch(e) {} this.start(); }); socket.addEventListener("close", event => { this.socket = null; console.info("Websocket closed", event); }); socket.addEventListener("open", event => { console.log(`Opened websocket connection to ${endpoint}`); socket.send(`{"seq": 1, "action": "authentication_challenge", "data": {"token": "${this.token}"}}`); }); socket.addEventListener("message", event => { console.log("Message from server ", event.data); const data = JSON.parse(event.data); if (data.event === "posted") { const post = JSON.parse(data.data.post); pubsub.publish("MESSAGES_NEW", {...post, endpoint: this.endpoint}); } else if (data.event === "channel_viewed") { pubsub.publish("CHANNEL_READ", {endpoint: this.endpoint, channel_id: data.data.channel_id}); } else if ( data.event === "user_added" || data.event === "user_updated" || data.event === "user_removed" ) { this.regetUsers(); } else if ( data.event === "group_added" || data.event === "channel_created" || data.event === "channel_deleted" ) { this.channelStore.fetch(); } }); this.socket = socket; } } let clients = Object.create(null); function getOrCreate(endpoint) { if (!clients[endpoint]) { clients[endpoint] = new MattermostClient(endpoint, localstorage_credentials); } return clients[endpoint]; } function getOrCreateMultiple(endpoints) { return endpoints.map(getOrCreate); } function drop(endpoint) { delete clients[endpoint]; } return {getOrCreate, getOrCreateMultiple, drop}; })();