Replace Ed25519 with hashed API tokens and an in-Firefox client manager.
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
"use strict";
|
||||
|
||||
const rows = document.getElementById("rows");
|
||||
const nameInput = document.getElementById("name");
|
||||
const createBtn = document.getElementById("create");
|
||||
const errorEl = document.getElementById("error");
|
||||
const statusEl = document.getElementById("status");
|
||||
const modal = document.getElementById("secret-modal");
|
||||
const secretEl = document.getElementById("secret");
|
||||
const copyBtn = document.getElementById("copy");
|
||||
const closeBtn = document.getElementById("close");
|
||||
|
||||
function showError(text) {
|
||||
errorEl.hidden = !text;
|
||||
errorEl.textContent = text || "";
|
||||
}
|
||||
|
||||
function admin(method, args) {
|
||||
return browser.runtime.sendMessage({ type: "admin", method, args }).then((resp) => {
|
||||
if (!resp || !resp.ok) {
|
||||
throw new Error((resp && resp.error) || "host error");
|
||||
}
|
||||
return resp.result;
|
||||
});
|
||||
}
|
||||
|
||||
function render(clients) {
|
||||
if (!clients.length) {
|
||||
rows.innerHTML = '<tr><td colspan="4" class="muted">No clients yet. Generate a secret to get started.</td></tr>';
|
||||
return;
|
||||
}
|
||||
rows.innerHTML = "";
|
||||
for (const client of clients) {
|
||||
const tr = document.createElement("tr");
|
||||
tr.innerHTML = `
|
||||
<td>${escapeHtml(client.name)}</td>
|
||||
<td>${escapeHtml(client.created || "")}</td>
|
||||
<td>${escapeHtml(client.last_used || "never")}</td>
|
||||
<td><button type="button" data-revoke="${escapeHtml(client.id)}">Revoke</button></td>`;
|
||||
rows.appendChild(tr);
|
||||
}
|
||||
}
|
||||
|
||||
function escapeHtml(value) {
|
||||
return String(value)
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, """);
|
||||
}
|
||||
|
||||
async function refresh() {
|
||||
const info = await browser.runtime.sendMessage({ type: "status" });
|
||||
statusEl.textContent = info && info.hostUp
|
||||
? `Native host connected · http://127.0.0.1:${info.port}`
|
||||
: "Native host is not connected. Install the host, then reload this add-on.";
|
||||
if (!info || !info.hostUp) {
|
||||
rows.innerHTML = '<tr><td colspan="4" class="muted">Host offline.</td></tr>';
|
||||
return;
|
||||
}
|
||||
render(await admin("clients.list"));
|
||||
}
|
||||
|
||||
createBtn.addEventListener("click", async () => {
|
||||
showError("");
|
||||
try {
|
||||
const created = await admin("clients.create", [nameInput.value.trim()]);
|
||||
nameInput.value = "";
|
||||
secretEl.value = created.token;
|
||||
modal.showModal();
|
||||
await refresh();
|
||||
} catch (err) {
|
||||
showError(err.message);
|
||||
}
|
||||
});
|
||||
|
||||
rows.addEventListener("click", async (event) => {
|
||||
const id = event.target && event.target.getAttribute("data-revoke");
|
||||
if (!id) {
|
||||
return;
|
||||
}
|
||||
showError("");
|
||||
try {
|
||||
await admin("clients.revoke", [id]);
|
||||
await refresh();
|
||||
} catch (err) {
|
||||
showError(err.message);
|
||||
}
|
||||
});
|
||||
|
||||
copyBtn.addEventListener("click", async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(secretEl.value);
|
||||
copyBtn.textContent = "Copied";
|
||||
} catch (err) {
|
||||
showError(err.message);
|
||||
}
|
||||
});
|
||||
|
||||
closeBtn.addEventListener("click", () => {
|
||||
secretEl.value = "";
|
||||
copyBtn.textContent = "Copy";
|
||||
modal.close();
|
||||
});
|
||||
|
||||
refresh().catch((err) => showError(err.message));
|
||||
Reference in New Issue
Block a user