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>Username</th><td><input type="text" id="username"/></td></tr>
|
||||||
<tr><th>Password</th><td><input type="password" id="password"/></td></tr>
|
<tr><th>Password</th><td><input type="password" id="password"/></td></tr>
|
||||||
</table>
|
</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>
|
</form>
|
||||||
|
|
||||||
|
<pre id="user_json"></pre>
|
||||||
|
|
||||||
<script type="text/javascript" src="xhr.js"></script>
|
<script type="text/javascript" src="xhr.js"></script>
|
||||||
<script type="text/javascript" src="main.js"></script>
|
<script type="text/javascript" src="main.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
198
main.js
198
main.js
|
@ -1,7 +1,5 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const LOCALSTORAGE_KEY_SERVERS = "mattermostServers";
|
|
||||||
|
|
||||||
function byId(id, nullOk=false) {
|
function byId(id, nullOk=false) {
|
||||||
const el = document.getElementById(id);
|
const el = document.getElementById(id);
|
||||||
if (!el && !nullOk) {
|
if (!el && !nullOk) {
|
||||||
|
@ -10,57 +8,102 @@ function byId(id, nullOk=false) {
|
||||||
return el;
|
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 {
|
class MattermostApi {
|
||||||
constructor(endpoint) {
|
constructor(endpoint) {
|
||||||
this.endpoint = endpoint;
|
this._endpoint = endpoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
async validateToken(token) {
|
get id() {
|
||||||
const response = await ajax.getJson(`${this.endpoint}/users/me`, {
|
return this._endpoint;
|
||||||
headers: {
|
}
|
||||||
"Authorization": `Bearer ${token}`
|
|
||||||
}
|
async get(path, token) {
|
||||||
});
|
const headers = token ? {"Authorization": `Bearer ${token}`} : {};
|
||||||
|
const response = await ajax.getJson(`${this._endpoint}${path}`, {headers});
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw response;
|
throw response;
|
||||||
}
|
}
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
logIn(login_id, password) {
|
async post(path, data, token) {
|
||||||
return ajax.postJson(`${this.endpoint}/users/login`, {login_id, password})
|
const headers = token ? {"Authorization": `Bearer ${token}`} : {};
|
||||||
.then(response => {
|
const response = await ajax.postJson(`${this._endpoint}${path}`, data, {headers});
|
||||||
let token = response.getHeader("Token");
|
if (!response.ok) {
|
||||||
storeCredentials(this.endpoint, login_id, token);
|
throw response;
|
||||||
return 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
|
* Return an endpoint URL that has a protocol, domain and path
|
||||||
*/
|
*/
|
||||||
|
@ -75,30 +118,81 @@ function normalizedEndpoint(endpoint) {
|
||||||
return `${protocol}${domain}${path}`;
|
return `${protocol}${domain}${path}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function logIn() {
|
function createClient(endpoint) {
|
||||||
let endpoint = normalizedEndpoint(byId("server").value);
|
const api = new MattermostApi(normalizedEndpoint(endpoint));
|
||||||
|
const storage = new Storage();
|
||||||
|
return new MattermostClient(api, storage);
|
||||||
|
}
|
||||||
|
|
||||||
let api = new MattermostApi(endpoint);
|
function buttonDisable(element, text) {
|
||||||
api.logIn(byId("username").value, byId("password").value);
|
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() {
|
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) {
|
if (!cred || !cred.token) {
|
||||||
byId("validate").value = "No token, log in first";
|
buttonEnable(byId("validate"), "No token, log in first");
|
||||||
byId("validate").disabled = false;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let api = new MattermostApi(endpoint);
|
client.usersMe()
|
||||||
api.validateToken(cred.token)
|
.then(json => {
|
||||||
.then(() => {
|
buttonEnable(byId("validate"), "Validation succeeded");
|
||||||
byId("validate").value = "Validation succeeded";
|
byId("validate_message").innerText = "";
|
||||||
byId("validate").disabled = false;
|
byId("user_json").innerText = JSON.stringify(json, null, 2);
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(error => {
|
||||||
byId("validate").value = "Validation failed";
|
console.error(error);
|
||||||
byId("validate").disabled = false;
|
buttonEnable(byId("validate"), "Validation failed");
|
||||||
|
byId("validate_message").innerText = `${error}`;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
59
xhr.js
59
xhr.js
|
@ -1,8 +1,15 @@
|
||||||
const ajax = (function() { "use strict";
|
const ajax = (function() { "use strict";
|
||||||
|
|
||||||
class NetworkError extends Error {}
|
class AjaxError extends Error {
|
||||||
class UnexpectedMimeError extends Error {}
|
constructor (message, response, ...rest) {
|
||||||
class InvalidJsonError extends Error {}
|
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";
|
const MIME_JSON = "application/json";
|
||||||
|
|
||||||
|
@ -33,34 +40,32 @@ function xhrInitForPromise(resolve, reject, url, method, headers) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function xhrParseJsonResponse(xhr) {
|
function xhrParseJsonResponse(xhr) {
|
||||||
if (xhr.status === 0) {
|
xhr.responseJson = null;
|
||||||
console.error(xhr);
|
xhr.ok = false;
|
||||||
throw new NetworkError("Failed to connect to server");
|
|
||||||
}
|
|
||||||
|
|
||||||
let json;
|
if (xhr.responseText) {
|
||||||
if (!xhr.responseText) {
|
|
||||||
json = null;
|
|
||||||
} else {
|
|
||||||
const contentType = xhr.getResponseHeader("Content-Type");
|
const contentType = xhr.getResponseHeader("Content-Type");
|
||||||
if (contentType != MIME_JSON) {
|
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 {
|
try {
|
||||||
json = JSON.parse(xhr.responseText);
|
xhr.responseJson = JSON.parse(xhr.responseText);
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
throw new InvalidJsonError();
|
throw new InvalidJsonError("Server replied with JSON that we couldn't parse", xhr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
xhr.ok = 200 <= xhr.status && xhr.status < 300;
|
||||||
ok: 200 <= xhr.status && xhr.status < 300,
|
if (!xhr.ok) {
|
||||||
status: xhr.status,
|
console.error(xhr);
|
||||||
statusText: xhr.statusText,
|
if (xhr.status === 0) {
|
||||||
getHeader: header => xhr.getResponseHeader(header),
|
throw new NetworkError("Failed to connect to server. Developer console may have more information", xhr);
|
||||||
json,
|
} else {
|
||||||
xhr,
|
throw new NotOkError(xhr.statusText, xhr);
|
||||||
};
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return xhr;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getJson(url, options={}) {
|
function getJson(url, options={}) {
|
||||||
|
@ -73,7 +78,7 @@ function getJson(url, options={}) {
|
||||||
}).then(xhrParseJsonResponse);
|
}).then(xhrParseJsonResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
function postJson(url, data={}, options={}) {
|
function postJson(url, data=undefined, options={}) {
|
||||||
if (!options.headers) options.headers = {};
|
if (!options.headers) options.headers = {};
|
||||||
// This triggers CORS, which is not acceptable
|
// This triggers CORS, which is not acceptable
|
||||||
//options.headers["Content-Type"] = MIME_JSON;
|
//options.headers["Content-Type"] = MIME_JSON;
|
||||||
|
@ -81,12 +86,16 @@ function postJson(url, data={}, options={}) {
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
let xhr = xhrInitForPromise(resolve, reject, url, "POST", options.headers);
|
let xhr = xhrInitForPromise(resolve, reject, url, "POST", options.headers);
|
||||||
xhr.send(JSON.stringify(data));
|
if (data === undefined) {
|
||||||
|
xhr.send();
|
||||||
|
} else {
|
||||||
|
xhr.send(JSON.stringify(data));
|
||||||
|
}
|
||||||
}).then(xhrParseJsonResponse);
|
}).then(xhrParseJsonResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
NetworkError, UnexpectedMimeError, InvalidJsonError,
|
NetworkError, NotOkError, UnexpectedMimeError, InvalidJsonError,
|
||||||
getJson, postJson
|
getJson, postJson
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue