369 lines
11 KiB
JavaScript
369 lines
11 KiB
JavaScript
const CONFIG = {
|
|
client_id: "3d463405-d098-4038-8b25-a44ca5b9ab9c",
|
|
redirect_uri: "http://127.0.0.1:3000/",
|
|
authorization_endpoint: "https://git.kjao.me/login/oauth/authorize",
|
|
token_endpoint: "https://git.kjao.me/login/oauth/access_token",
|
|
requested_scopes: "read:repository,write:repository"
|
|
};
|
|
|
|
const STATES = { // Either the non-d
|
|
auth: "auth",
|
|
error: "error",
|
|
loading: "loading",
|
|
repoSelected: "repoSelected",
|
|
repoCreated: "repoCreated",
|
|
buttonOn: "enabled",
|
|
featureOn: "fEnabled",
|
|
confirmOn: "enabled",
|
|
}
|
|
|
|
var G_REPO_VALUE = "",
|
|
G_CONFIRM_VALUE = "",
|
|
G_CONFIRM = -1;
|
|
|
|
// UTILITY //
|
|
|
|
document.on = document.addEventListener;
|
|
function sugar(obj) {
|
|
obj.on = obj.addEventListener;
|
|
obj.addClass = (x) => obj.classList.add(x);
|
|
obj.delClass = (x) => obj.classList.remove(x);
|
|
obj.hasClass = (x) => obj.classList.contains(x);
|
|
obj.setClass = (x, y) => obj.classList.toggle(x, y);
|
|
return obj;
|
|
}
|
|
|
|
function get(s) {
|
|
let x = [...document.querySelectorAll(s)].map(sugar);
|
|
return (x.length === 1) ? x[0] : x;
|
|
}
|
|
|
|
// Check if object is empty.
|
|
function isEmpty(obj) {
|
|
for (const prop in obj) {
|
|
if (Object.hasOwn(obj, prop)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Returns error and resolves promise if necessary.
|
|
function getErr(f) {
|
|
return (err) => {
|
|
if (err instanceof Promise) {
|
|
return err.then(f);
|
|
} else {
|
|
return err;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Parse a query string into an object
|
|
function parseQueryString(string) {
|
|
if(string == "") { return {}; }
|
|
var segments = string.split("&").map(s => s.split("=") );
|
|
var queryString = {};
|
|
segments.forEach(s => G_QUERYString[s[0]] = s[1]);
|
|
return queryString;
|
|
}
|
|
|
|
// Fetch function for applet use case.
|
|
async function jfetch(url, method, obj={}) {
|
|
let request = {
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
"Authorization": window.localStorage.getItem("token"),
|
|
},
|
|
method: method,
|
|
};
|
|
|
|
if (!isEmpty(obj)) request["body"] = JSON.stringify(obj);
|
|
|
|
return fetch(url, request)
|
|
.then((response) => {
|
|
let contentType = response.headers.get("Content-Type");
|
|
let json;
|
|
if (contentType && contentType.indexOf("application/json" !== -1)) {
|
|
json = response.json();
|
|
} else { // Reprocess non-json into json.
|
|
json = response.text().then((text) => {
|
|
return { status: response.status, "message": text };
|
|
});
|
|
}
|
|
if (!response.ok) { throw json; }
|
|
return json;
|
|
});
|
|
}
|
|
|
|
// OAUTH //
|
|
|
|
async function getToken() {
|
|
let response;
|
|
if (G_QUERY.code) {
|
|
response = await jfetch("/api/token", "POST", { code: G_QUERY.code });
|
|
} else {
|
|
response = await jfetch("/api/token", "PATCH", { code: window.localStorage.getItem("refresh_token") });
|
|
}
|
|
|
|
window.localStorage.setItem("token", response.access_token);
|
|
window.localStorage.setItem("token_expiry", (Date.now() + response.expires_in*1000).toString());
|
|
window.localStorage.setItem("refresh_token", response.refresh_token);
|
|
}
|
|
|
|
async function tryRefresh() {
|
|
if (Date.now() < parseInt(window.localStorage.getItem("token_expiry"))) return; // Not expired.
|
|
await getToken().catch((err) => {
|
|
let json = err.json();
|
|
if (json.status == 401 && json.code === 0) { // Could not refresh, so refresh token expired.
|
|
window.localStorage.clear();
|
|
alert(`Could not get new session, did you remove access from Gitea?`);
|
|
window.location = "/";
|
|
} else {
|
|
throw json;
|
|
}
|
|
});
|
|
}
|
|
|
|
// BUTTON ONCLICK MIDDLEWARE //
|
|
|
|
// Refresh token if necessary.
|
|
function tryAuth(f) {
|
|
return async (e) => {
|
|
e.preventDefault();
|
|
if (!G_AUTH) return;
|
|
await tryRefresh();
|
|
await f(e);
|
|
}
|
|
}
|
|
|
|
|
|
// Adds loading classes for UI while awaiting.
|
|
function doLoading(f) {
|
|
return async (e) => {
|
|
if (e.target.hasClass("loading")) return;
|
|
e.target.addClass(STATES["loading"]);
|
|
await f(e);
|
|
e.target.delClass(STATES["loading"]);
|
|
}
|
|
}
|
|
|
|
// BUTTON ONCLICK HELPERS //
|
|
|
|
function setRepoError(s, err) {
|
|
const input = get("#repoSelectInput"),
|
|
error = get("#repoSelectError"),
|
|
info = get("#repoInfo");
|
|
error.innerText = s;
|
|
info.setClass(STATES["repoSelected"], !err);
|
|
input.setClass(STATES["error"], err);
|
|
}
|
|
|
|
function setRepoActions(s) {
|
|
get("#repoCreate").setClass(STATES["buttonOn"], !s);
|
|
get("#repoDelete").setClass(STATES["buttonOn"], s);
|
|
get("#repoSecret").setClass(STATES["buttonOn"], s);
|
|
get("#repoFeatures").setClass(STATES["repoCreated"], s);
|
|
}
|
|
|
|
async function getConfirm() {
|
|
get("#confirmOverlay").addClass(STATES["confirmOn"]);
|
|
return new Promise((resolve, reject) => {
|
|
const check = function() {
|
|
if (G_CONFIRM == -1) {
|
|
setTimeout(check, 100);
|
|
} else {
|
|
get("#confirmOverlay").delClass(STATES["confirmOn"]);
|
|
resolve(G_CONFIRM);
|
|
G_CONFIRM = -1;
|
|
}
|
|
}
|
|
check();
|
|
});
|
|
}
|
|
|
|
document.on("keydown", (e) => {
|
|
if (e.keyCode == 27) get("#confirmOverlay").delClass(STATES["confirmOn"]);
|
|
});
|
|
|
|
// BUTTON ONCLICK FUNCTIONS //
|
|
|
|
get("#login").on("click", async (e) => {
|
|
e.preventDefault();
|
|
if (G_AUTH) {
|
|
window.localStorage.clear();
|
|
window.location = "/";
|
|
} else {
|
|
// Build the authorization URL
|
|
let url = CONFIG.authorization_endpoint
|
|
+ "?response_type=code"
|
|
+ "&client_id="+encodeURIComponent(CONFIG.client_id)
|
|
+ "&scope="+encodeURIComponent(CONFIG.requested_scopes)
|
|
+ "&redirect_uri="+encodeURIComponent(CONFIG.redirect_uri);
|
|
|
|
// Redirect to the authorization server
|
|
window.location = url;
|
|
}
|
|
});
|
|
|
|
get("#repoSelectInput").on("keydown", (e) => {
|
|
if (e.keyCode === 13) {
|
|
get("#repoSelectButton").click();
|
|
return;
|
|
}
|
|
|
|
let value = e.target.value;
|
|
if (value !== G_REPO_VALUE) {
|
|
get("#repoSelectError").innerText = ""; // Delete error text.
|
|
get("#repoSelectInput").delClass(STATES["error"]); // Remove error class.
|
|
get("#repoInfo").delClass(STATES["repoSelected"]); // Remove repoSelect class.
|
|
setRepoActions(false); // Set actions to default.
|
|
get(".feature").forEach((feature) => feature.delClass(STATES["featureOn"]));
|
|
G_REPO_VALUE = value;
|
|
}
|
|
});
|
|
|
|
get("#repoSelectButton").on("click", tryAuth(doLoading(selectRepo)));
|
|
async function selectRepo(e) {
|
|
const input = get("#repoSelectInput");
|
|
let repo = input.value.trim();
|
|
input.value = repo;
|
|
|
|
if (repo === "") {
|
|
setRepoError("Field cannot be empty.", true);
|
|
return;
|
|
}
|
|
|
|
await jfetch("/api/repo/" + repo, "GET")
|
|
.then((json) => {
|
|
setRepoError("OK", false);
|
|
setRepoActions(json["exists"]);
|
|
|
|
if (json["exists"]) {
|
|
json["features"].forEach((feature) => {
|
|
get(`.feature[data-tag=${feature}]`).addClass(STATES["featureOn"]);
|
|
});
|
|
}
|
|
|
|
get("#repoLinkInput").value = CONFIG["redirect_uri"] + repo;
|
|
get("#repoInfo").addClass(STATES["repoSelected"]);
|
|
|
|
})
|
|
.catch(getErr((err) => {
|
|
console.log(err);
|
|
setRepoError("Unable to find repository or you have insufficient permissions.", true);
|
|
}));
|
|
}
|
|
|
|
get("#repoCreate").on("click", tryAuth(doLoading(createRepo)));
|
|
async function createRepo(e) {
|
|
const input = get("#repoSelectInput");
|
|
await jfetch("/api/repo/" + input.value, "POST")
|
|
.then((json) => {
|
|
setRepoActions(true);
|
|
})
|
|
.catch(getErr((err) => {
|
|
console.log(err);
|
|
alert("An unexpected error occurred! Check console for more details.");
|
|
}));
|
|
}
|
|
|
|
get("#repoDelete").on("click", tryAuth(doLoading(deleteRepo)));
|
|
async function deleteRepo(e) {
|
|
const input = get("#repoSelectInput");
|
|
if (!(await getConfirm())) return;
|
|
await jfetch("/api/repo/" + input.value, "DELETE")
|
|
.then((json) => {
|
|
setRepoActions(false);
|
|
})
|
|
.catch(getErr((err) => {
|
|
console.log(err);
|
|
alert("An unexpected error occurred! Check console for more details.");
|
|
}));
|
|
}
|
|
|
|
get("#repoSecret").on("click", tryAuth(doLoading(regenSecret)));
|
|
async function regenSecret(e) {
|
|
const input = get("#repoSelectInput");
|
|
await jfetch("/api/repo/" + input.value, "PATCH", { secret: true, feature: "" })
|
|
.then((json) => {
|
|
setRepoActions(true);
|
|
})
|
|
.catch(getErr((err) => {
|
|
console.log(err);
|
|
alert("An unexpected error occurred! Check console for more details.");
|
|
}));
|
|
}
|
|
|
|
get("#repoLinkButton").on("click", async (e) => {
|
|
const input = get("#repoLinkInput");
|
|
input.select();
|
|
input.setSelectionRange(0, 99999);
|
|
navigator.clipboard.writeText(input.value);
|
|
});
|
|
|
|
get(".feature").forEach((el) => el.on("click", tryAuth(doLoading(toggleFeature))));
|
|
async function toggleFeature(e) {
|
|
const input = get("#repoSelectInput");
|
|
let attr = e.target.getAttribute("data-tag");
|
|
if (e.target.hasClass(STATES["featureOn"]) && !(await getConfirm())) return;
|
|
await jfetch("/api/repo/" + input.value, "PATCH", { secret: false, feature: attr })
|
|
.then((json) => {
|
|
e.target.setClass(STATES["featureOn"]);
|
|
})
|
|
.catch(getErr((err) => {
|
|
console.log(err);
|
|
alert("An unexpected error occurred! Check console for more details.");
|
|
}));
|
|
}
|
|
|
|
get("#confirmInput").on("keydown", function(e) {
|
|
if (e.keyCode === 13) {
|
|
get("#confirmButtonYes").click();
|
|
return;
|
|
}
|
|
|
|
if (e.keyCode === 27) {
|
|
get("#confirmButtonNo").click();
|
|
return;
|
|
}
|
|
|
|
let value = e.target.value;
|
|
if (value !== G_CONFIRM_VALUE) {
|
|
get("#confirmInput").delClass(STATES["error"]);
|
|
G_REPO_VALUE = value;
|
|
}
|
|
});
|
|
|
|
get("#confirmButtonNo").on("click", (e) => {
|
|
G_CONFIRM = false;
|
|
get("#confirmInput").value = "";
|
|
});
|
|
|
|
get("#confirmButtonYes").on("click", (e) => {
|
|
let confirmValue = get("#confirmInput").value.trim();
|
|
let repoValue = get("#repoSelectInput").value;
|
|
|
|
if (confirmValue === repoValue) {
|
|
G_CONFIRM = true;
|
|
get("#confirmInput").value = "";
|
|
} else {
|
|
get("#confirmInput").addClass(STATES["error"]);
|
|
}
|
|
});
|
|
|
|
async function init() {
|
|
// Get query and delete URL parameters.
|
|
if(G_QUERY.error) alert("Error returned from authorization server: "+q.error); // Check for auth errors.
|
|
if(G_QUERY.code) {
|
|
await getToken();
|
|
G_AUTH = true;
|
|
window.history.replaceState({}, document.title, window.location.pathname);
|
|
}
|
|
get("body").setClass(STATES["auth"], G_AUTH);
|
|
}
|
|
|
|
var G_QUERY = parseQueryString(window.location.search.substring(1));
|
|
var G_AUTH = window.localStorage.getItem("token") !== null;
|
|
init();
|