Files
bookmarks-api/extension/clients.js
T

83 lines
2.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use strict";
const CLIENT_NAME_RE = /^[A-Za-z0-9._-]{1,64}$/;
const TOKEN_PREFIX = "fab_";
function nowIso() {
return new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
}
async function sha256Hex(text) {
const buf = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(text));
return Array.from(new Uint8Array(buf), (b) => b.toString(16).padStart(2, "0")).join("");
}
function publicClient(row) {
return {
id: row.id,
name: row.name,
created: row.created,
last_used: row.last_used || null,
};
}
async function loadClients() {
const data = await browser.storage.local.get("clients");
return Array.isArray(data.clients) ? data.clients : [];
}
async function saveClients(clients) {
await browser.storage.local.set({ clients });
}
async function listClients() {
return (await loadClients()).map(publicClient);
}
async function createClient(name) {
name = String(name || "").trim();
if (!CLIENT_NAME_RE.test(name)) {
throw new Error("Name must be 164 characters: A-Za-z0-9._-");
}
const clients = await loadClients();
if (clients.some((row) => row.name === name)) {
throw new Error("A client with that name already exists");
}
const raw = new Uint8Array(32);
crypto.getRandomValues(raw);
const token = TOKEN_PREFIX + Array.from(raw, (b) => b.toString(16).padStart(2, "0")).join("");
const idBytes = new Uint8Array(16);
crypto.getRandomValues(idBytes);
const id = Array.from(idBytes, (b) => b.toString(16).padStart(2, "0")).join("");
clients.push({
id,
name,
token_hash: await sha256Hex(token),
created: nowIso(),
last_used: null,
});
await saveClients(clients);
return { id, name, token };
}
async function revokeClient(id) {
const clients = await loadClients();
const next = clients.filter((row) => row.id !== id && row.name !== id);
if (next.length === clients.length) {
throw new Error("No such client");
}
await saveClients(next);
return id;
}
async function findClientByTokenHash(tokenHash) {
const clients = await loadClients();
const row = clients.find((item) => item.token_hash === tokenHash);
if (!row) {
return null;
}
row.last_used = nowIso();
await saveClients(clients);
return publicClient(row);
}