diff --git a/index.html b/index.html index a088e2f..1326bad 100644 --- a/index.html +++ b/index.html @@ -64,7 +64,7 @@ - + diff --git a/js/controller/controller.js b/js/controller/controller.js index c41977e..8d2fd61 100644 --- a/js/controller/controller.js +++ b/js/controller/controller.js @@ -2,7 +2,7 @@ function createClient(endpoint) { const api = new mm_client.MattermostApi(normalizedEndpoint(endpoint)); - return new mm_client.MattermostClient(api); + return new mm_client.MattermostClient(api, localstorage_credentials); } function buttonDisable(element, text) { diff --git a/js/model/credentials.js b/js/model/localstorage_credentials.js similarity index 94% rename from js/model/credentials.js rename to js/model/localstorage_credentials.js index 4cb1339..911fd6e 100644 --- a/js/model/credentials.js +++ b/js/model/localstorage_credentials.js @@ -1,4 +1,4 @@ -const credentials = (function() { "use strict"; +const localstorage_credentials = (function() { "use strict"; const LOCALSTORAGE_KEY_SERVER = "mattermostServer"; const RE_SERVER_ITEM = new RegExp(`^${LOCALSTORAGE_KEY_SERVER}_(.*)\$`, ""); diff --git a/js/model/mm_client.js b/js/model/mm_client.js index 1006772..41f2741 100644 --- a/js/model/mm_client.js +++ b/js/model/mm_client.js @@ -31,74 +31,84 @@ class MattermostApi { class MattermostClient { - constructor (api) { + constructor (api, credentials_provider) { this.api = api; + this.credentials = credentials_provider; + const creds = this.credentials.get(this.api.id); + this.token = creds ? creds.token : null; + console.info(`Created MattermostClient for ${this.api.id}, ${this.token ? "found token" : "did not find token"}`); + } + + async authenticatedGet(url, queryParams) { + assert(this.token, "logged in"); + const response = await this.api.get(url, this.token, queryParams); + return response.responseJson; + } + + 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 (this.token && await this.tokenWorks()) { + throw Error("Already logged in on this server"); + } + const response = await this.api.post("/users/login", undefined, {login_id, password}); const token = response.getResponseHeader("Token"); if (!token) { throw Error("No Token header in response to log in request"); } - credentials.store(this.api.id, login_id, token); + this.credentials.store(this.api.id, login_id, token); + this.token = token; return response.responseJson; } async logOut() { - const stored = credentials.get(this.api.id); - if (!stored || !stored.token) { - throw Error("No token stored"); - } - const response = await this.api.post("/users/logout", stored.token); + assert(this.token, "logged in"); + const response = await this.api.post("/users/logout", this.token); // Verify that the token is now invalidated - let tokenWorks; - try { - const meResponse = await this.usersMe(); - tokenWorks = true; - } catch (e) { - if (e instanceof ajax.NotOkError && e.xhr.status == 401) { - tokenWorks = false; - } else { - throw e; - } - } - if (tokenWorks) { + if (await loggedIn()) { throw new Error("Failed to log out: token still works after trying to log out"); } - credentials.clear(this.api.id); + this.credentials.clear(this.api.id); + this.token = null; return response.responseJson; } - async usersMe() { - const stored = credentials.get(this.api.id); - if (!stored || !stored.token) { - throw Error("No token stored"); - } - const response = await this.api.get("/users/me", stored.token); - return response.responseJson; + user(user_id) { + assertIsMattermostId(user_id); + return this.authenticatedGet(`/users/${user_id}`); + } + userMe() { return this.authenticatedGet("/users/me"); } + myTeams() { return this.authenticatedGet("/users/me/teams"); } + + myChannels(team_id) { + assertIsMattermostId(team_id); + return this.authenticatedGet(`/users/me/teams/${team_id}/channels`); } - async myTeams() { - const stored = credentials.get(this.api.id); - const response = await this.api.get("/users/me/teams", stored.token); - return response.responseJson; - } - - async myChannels(team_id) { - const stored = credentials.get(this.api.id); - const response = await this.api.get(`/users/me/teams/${team_id}/channels`, stored.token); - return response.responseJson; - } - - async channelPosts(channel_id, beforePost=null, afterPost=null, since=null) { - const stored = credentials.get(this.api.id); - const response = await this.api.get(`/channels/${channel_id}/posts`, stored.token, { + channelPosts(channel_id, beforePost=null, afterPost=null, since=null) { + assertIsMattermostId(channel_id); + assertIsNullOrMattermostId(beforePost); + assertIsNullOrMattermostId(afterPost); + return this.authenticatedGet(`/channels/${channel_id}/posts`, { before: beforePost, after: afterPost, since }); - return response.responseJson; } } diff --git a/js/util.js b/js/util.js index 6e289eb..02284cd 100644 --- a/js/util.js +++ b/js/util.js @@ -44,3 +44,25 @@ function thisToArg(f) { } } + +class AssertionError extends Error {} +/** + * Throw an AssertionError if the first argument is not true + */ +function assert(condition, message) { + if (!condition) { + if (message) { + throw new AssertionError(`Assertion failed: ${message}`); + } else { + throw new AssertionError("Assertion failed"); + } + } +} + +const MATTERMOST_ID_REGEXP = /^[a-z0-9]{26}$/; +function assertIsMattermostId(string, name="") { + assert(MATTERMOST_ID_REGEXP.test(string), `${name} has the form of a Mattermost ID`); +} +function assertIsNullOrMattermostId(string, name="") { + assert(string === null || MATTERMOST_ID_REGEXP.test(string), `${name} is null or has the form of a Mattermost ID`); +} diff --git a/js/view/view.js b/js/view/view.js index d9314b6..f263ff6 100644 --- a/js/view/view.js +++ b/js/view/view.js @@ -1,5 +1,5 @@ function populateServerSelectionList() { - const servers = credentials.getServers(); + const servers = localstorage_credentials.getServers(); let nodes = []; for (let server of servers) { @@ -49,7 +49,7 @@ function populateChannelList() { } } - const servers = credentials.getServers(); + const servers = localstorage_credentials.getServers(); byId("channel_list").innerHTML = ""; for (let server of servers) {