Improve architecture
This commit is contained in:
parent
190663a253
commit
ae525c8904
3 changed files with 193 additions and 79 deletions
15
index.html
15
index.html
|
@ -12,9 +12,20 @@
|
|||
<tr><th>Username</th><td><input type="text" id="username"/></td></tr>
|
||||
<tr><th>Password</th><td><input type="password" id="password"/></td></tr>
|
||||
</table>
|
||||
<input type="button" onclick="this.disabled = true; this.value = 'Logging in...'; logIn(); return false" value="Log in"/><br/>
|
||||
<input type="button" onclick="this.disabled = true; this.value = 'Validating token'; validateToken(); return false" id="validate" value="Validate token"/> </input>(ignores username and password)
|
||||
|
||||
<input type="button" onclick="logIn(); return false" id="login" value="Log in"/>
|
||||
<span id="login_message"></span><br/>
|
||||
|
||||
<input type="button" onclick="validateToken(); return false" id="validate" value="Validate token"/>
|
||||
<span id="validate_message">(ignores username and password)</span><br/>
|
||||
|
||||
<input type="button" onclick="logOut(); return false" id="logout" value="Log out"/>
|
||||
<span id="logout_message"></span><br/>
|
||||
|
||||
</form>
|
||||
|
||||
<pre id="user_json"></pre>
|
||||
|
||||
<script type="text/javascript" src="xhr.js"></script>
|
||||
<script type="text/javascript" src="main.js"></script>
|
||||
</body>
|
||||
|
|
194
main.js
194
main.js
|
@ -1,7 +1,5 @@
|
|||
"use strict";
|
||||
|
||||
const LOCALSTORAGE_KEY_SERVERS = "mattermostServers";
|
||||
|
||||
function byId(id, nullOk=false) {
|
||||
const el = document.getElementById(id);
|
||||
if (!el && !nullOk) {
|
||||
|
@ -10,57 +8,102 @@ function byId(id, nullOk=false) {
|
|||
return el;
|
||||
}
|
||||
|
||||
function storeCredentials(endpoint, login_id, token) {
|
||||
let storedServers = JSON.parse(window.localStorage.getItem(LOCALSTORAGE_KEY_SERVERS) || "[]");
|
||||
if (!(endpoint in storedServers)) storedServers.push(endpoint);
|
||||
window.localStorage.setItem(LOCALSTORAGE_KEY_SERVERS, JSON.stringify(storedServers));
|
||||
|
||||
window.localStorage.setItem(`${LOCALSTORAGE_KEY_SERVERS}_${endpoint}`, JSON.stringify({login_id, token}));
|
||||
const LOCALSTORAGE_KEY_SERVER = "mattermostServer";
|
||||
const RE_SERVER_ITEM = new RegExp(`^${LOCALSTORAGE_KEY_SERVER}_(.*)\$`, "").compile();
|
||||
class Storage {
|
||||
getServers() {
|
||||
let servers = [];
|
||||
for (var i = 0; i < window.localStorage.length; i++) {
|
||||
const key = window.localStorage.key(i);
|
||||
const matches = key.match(RE_SERVER_ITEM);
|
||||
if (matches) {
|
||||
servers.push(matches[1]);
|
||||
}
|
||||
}
|
||||
return servers;
|
||||
}
|
||||
|
||||
_key_for(endpoint) {
|
||||
return `${LOCALSTORAGE_KEY_SERVER}_${endpoint}`;
|
||||
}
|
||||
|
||||
clear(endpoint) {
|
||||
window.localStorage.removeItem(this._key_for(endpoint));
|
||||
}
|
||||
|
||||
store(endpoint, login_id, token) {
|
||||
window.localStorage.setItem(this._key_for(endpoint), JSON.stringify({login_id, token}));
|
||||
}
|
||||
|
||||
get(endpoint) {
|
||||
return JSON.parse(window.localStorage.getItem(this._key_for(endpoint)) || "null");
|
||||
}
|
||||
}
|
||||
|
||||
function getCredentials(endpoint) {
|
||||
return JSON.parse(window.localStorage.getItem(`${LOCALSTORAGE_KEY_SERVERS}_${endpoint}`) || "null");
|
||||
}
|
||||
|
||||
class MattermostApi {
|
||||
constructor(endpoint) {
|
||||
this.endpoint = endpoint;
|
||||
this._endpoint = endpoint;
|
||||
}
|
||||
|
||||
async validateToken(token) {
|
||||
const response = await ajax.getJson(`${this.endpoint}/users/me`, {
|
||||
headers: {
|
||||
"Authorization": `Bearer ${token}`
|
||||
get id() {
|
||||
return this._endpoint;
|
||||
}
|
||||
});
|
||||
|
||||
async get(path, token) {
|
||||
const headers = token ? {"Authorization": `Bearer ${token}`} : {};
|
||||
const response = await ajax.getJson(`${this._endpoint}${path}`, {headers});
|
||||
if (!response.ok) {
|
||||
throw response;
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
logIn(login_id, password) {
|
||||
return ajax.postJson(`${this.endpoint}/users/login`, {login_id, password})
|
||||
.then(response => {
|
||||
let token = response.getHeader("Token");
|
||||
storeCredentials(this.endpoint, login_id, token);
|
||||
async post(path, data, token) {
|
||||
const headers = token ? {"Authorization": `Bearer ${token}`} : {};
|
||||
const response = await ajax.postJson(`${this._endpoint}${path}`, data, {headers});
|
||||
if (!response.ok) {
|
||||
throw response;
|
||||
}
|
||||
return response;
|
||||
})
|
||||
.then(response => {
|
||||
document.body.innerHTML = "";
|
||||
const pre = document.createElement("pre");
|
||||
pre.innerText = JSON.stringify(response.json, null, 2);
|
||||
document.body.appendChild(pre);
|
||||
|
||||
return response;
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(error);
|
||||
document.body.innerText = `An error occurred: ${error}`;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class MattermostClient {
|
||||
constructor (api, storage) {
|
||||
this.api = api;
|
||||
this.storage = storage;
|
||||
}
|
||||
|
||||
async logIn(login_id, password) {
|
||||
const response = await this.api.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.storage.store(this.api.id, login_id, token);
|
||||
return response.responseJson;
|
||||
}
|
||||
|
||||
async logOut() {
|
||||
const response = await this.api.post("/users/logout");
|
||||
//this.storage.clear(this.api.id);
|
||||
return response.responseJson;
|
||||
}
|
||||
|
||||
async usersMe() {
|
||||
const stored = this.storage.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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return an endpoint URL that has a protocol, domain and path
|
||||
*/
|
||||
|
@ -75,30 +118,81 @@ function normalizedEndpoint(endpoint) {
|
|||
return `${protocol}${domain}${path}`;
|
||||
}
|
||||
|
||||
function logIn() {
|
||||
let endpoint = normalizedEndpoint(byId("server").value);
|
||||
function createClient(endpoint) {
|
||||
const api = new MattermostApi(normalizedEndpoint(endpoint));
|
||||
const storage = new Storage();
|
||||
return new MattermostClient(api, storage);
|
||||
}
|
||||
|
||||
let api = new MattermostApi(endpoint);
|
||||
api.logIn(byId("username").value, byId("password").value);
|
||||
function buttonDisable(element, text) {
|
||||
element.value = text;
|
||||
element.disabled = true;
|
||||
}
|
||||
function buttonEnable(element, text) {
|
||||
element.value = text;
|
||||
element.disabled = false;
|
||||
}
|
||||
|
||||
function logIn() {
|
||||
byId("user_json").innerText = "";
|
||||
|
||||
const client = createClient(byId("server").value);
|
||||
|
||||
buttonDisable(byId("login"), "Logging in...");
|
||||
|
||||
client.logIn(byId("username").value, byId("password").value)
|
||||
.then(json => {
|
||||
buttonEnable(byId("login"), "Logged in");
|
||||
byId("login_message").innerText = "";
|
||||
byId("user_json").innerText = JSON.stringify(json, null, 2);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(error);
|
||||
buttonEnable(byId("login"), "Could not log in");
|
||||
byId("login_message").innerText = `${error}`;
|
||||
});
|
||||
}
|
||||
|
||||
function logOut() {
|
||||
const client = createClient(byId("server").value);
|
||||
|
||||
buttonDisable(byId("logout"), "Logging out...");
|
||||
|
||||
client.logOut()
|
||||
.then(response => {
|
||||
buttonEnable(byId("logout"), "Logged out");
|
||||
byId("logout_message").innerText = "";
|
||||
byId("user_json").innerText = "";
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(error);
|
||||
buttonEnable(byId("logout"), "Could not log out");
|
||||
byId("logout_message").innerText = `${error}`;
|
||||
});
|
||||
}
|
||||
|
||||
function validateToken() {
|
||||
let endpoint = normalizedEndpoint(byId("server").value);
|
||||
byId("user_json").innerText = "";
|
||||
|
||||
let cred = getCredentials(endpoint);
|
||||
const client = createClient(byId("server").value);
|
||||
|
||||
buttonDisable(byId("validate"), "Validating token...");
|
||||
|
||||
let cred = client.storage.get(client.api.id);
|
||||
if (!cred || !cred.token) {
|
||||
byId("validate").value = "No token, log in first";
|
||||
byId("validate").disabled = false;
|
||||
buttonEnable(byId("validate"), "No token, log in first");
|
||||
return;
|
||||
}
|
||||
|
||||
let api = new MattermostApi(endpoint);
|
||||
api.validateToken(cred.token)
|
||||
.then(() => {
|
||||
byId("validate").value = "Validation succeeded";
|
||||
byId("validate").disabled = false;
|
||||
client.usersMe()
|
||||
.then(json => {
|
||||
buttonEnable(byId("validate"), "Validation succeeded");
|
||||
byId("validate_message").innerText = "";
|
||||
byId("user_json").innerText = JSON.stringify(json, null, 2);
|
||||
})
|
||||
.catch(() => {
|
||||
byId("validate").value = "Validation failed";
|
||||
byId("validate").disabled = false;
|
||||
.catch(error => {
|
||||
console.error(error);
|
||||
buttonEnable(byId("validate"), "Validation failed");
|
||||
byId("validate_message").innerText = `${error}`;
|
||||
});
|
||||
}
|
||||
|
|
57
xhr.js
57
xhr.js
|
@ -1,8 +1,15 @@
|
|||
const ajax = (function() { "use strict";
|
||||
|
||||
class NetworkError extends Error {}
|
||||
class UnexpectedMimeError extends Error {}
|
||||
class InvalidJsonError extends Error {}
|
||||
class AjaxError extends Error {
|
||||
constructor (message, response, ...rest) {
|
||||
super(message, ...rest);
|
||||
this.response = response;
|
||||
}
|
||||
}
|
||||
class NetworkError extends AjaxError {}
|
||||
class NotOkError extends AjaxError {}
|
||||
class UnexpectedMimeError extends AjaxError {}
|
||||
class InvalidJsonError extends AjaxError {}
|
||||
|
||||
const MIME_JSON = "application/json";
|
||||
|
||||
|
@ -33,34 +40,32 @@ function xhrInitForPromise(resolve, reject, url, method, headers) {
|
|||
}
|
||||
|
||||
function xhrParseJsonResponse(xhr) {
|
||||
if (xhr.status === 0) {
|
||||
console.error(xhr);
|
||||
throw new NetworkError("Failed to connect to server");
|
||||
}
|
||||
xhr.responseJson = null;
|
||||
xhr.ok = false;
|
||||
|
||||
let json;
|
||||
if (!xhr.responseText) {
|
||||
json = null;
|
||||
} else {
|
||||
if (xhr.responseText) {
|
||||
const contentType = xhr.getResponseHeader("Content-Type");
|
||||
if (contentType != MIME_JSON) {
|
||||
throw new UnexpectedMimeError(`Server did not reply with JSON but with ${contentType}`);
|
||||
throw new UnexpectedMimeError(`Server did not reply with JSON but with ${contentType}`, xhr);
|
||||
}
|
||||
try {
|
||||
json = JSON.parse(xhr.responseText);
|
||||
xhr.responseJson = JSON.parse(xhr.responseText);
|
||||
} catch(e) {
|
||||
throw new InvalidJsonError();
|
||||
throw new InvalidJsonError("Server replied with JSON that we couldn't parse", xhr);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
ok: 200 <= xhr.status && xhr.status < 300,
|
||||
status: xhr.status,
|
||||
statusText: xhr.statusText,
|
||||
getHeader: header => xhr.getResponseHeader(header),
|
||||
json,
|
||||
xhr,
|
||||
};
|
||||
xhr.ok = 200 <= xhr.status && xhr.status < 300;
|
||||
if (!xhr.ok) {
|
||||
console.error(xhr);
|
||||
if (xhr.status === 0) {
|
||||
throw new NetworkError("Failed to connect to server. Developer console may have more information", xhr);
|
||||
} else {
|
||||
throw new NotOkError(xhr.statusText, xhr);
|
||||
}
|
||||
}
|
||||
|
||||
return xhr;
|
||||
}
|
||||
|
||||
function getJson(url, options={}) {
|
||||
|
@ -73,7 +78,7 @@ function getJson(url, options={}) {
|
|||
}).then(xhrParseJsonResponse);
|
||||
}
|
||||
|
||||
function postJson(url, data={}, options={}) {
|
||||
function postJson(url, data=undefined, options={}) {
|
||||
if (!options.headers) options.headers = {};
|
||||
// This triggers CORS, which is not acceptable
|
||||
//options.headers["Content-Type"] = MIME_JSON;
|
||||
|
@ -81,12 +86,16 @@ function postJson(url, data={}, options={}) {
|
|||
|
||||
return new Promise((resolve, reject) => {
|
||||
let xhr = xhrInitForPromise(resolve, reject, url, "POST", options.headers);
|
||||
if (data === undefined) {
|
||||
xhr.send();
|
||||
} else {
|
||||
xhr.send(JSON.stringify(data));
|
||||
}
|
||||
}).then(xhrParseJsonResponse);
|
||||
}
|
||||
|
||||
return {
|
||||
NetworkError, UnexpectedMimeError, InvalidJsonError,
|
||||
NetworkError, NotOkError, UnexpectedMimeError, InvalidJsonError,
|
||||
getJson, postJson
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in a new issue