Something went wrong. Try again.
📰 A single-page application to manage standard.site publication on the AT Protocol cuducos.tngl.io/std-pub
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182// Minimal browser-crypto bridge for the Elm app.// Only responsibility: persistent ES256 keypair, DPoP JWT signing, PKCE S256,// and key/value storage. No HTTP, no OAuth logic (those stay in Elm).
const DB_NAME = "standard-publication";const STORE_NAME = "keys";const KEY_ID = "dpop";const SESSION_KEY = "sp:session";const PENDING_KEY = "sp:pendingOAuth";
// IndexedDB
const openDb = () => new Promise((resolve, reject) => { const req = indexedDB.open(DB_NAME, 1); req.onupgradeneeded = () => req.result.createObjectStore(STORE_NAME); req.onsuccess = () => resolve(req.result); req.onerror = () => reject(req.error); });
const idbGet = async (key) => { const db = await openDb(); return new Promise((resolve, reject) => { const req = db .transaction(STORE_NAME, "readonly") .objectStore(STORE_NAME) .get(key); req.onsuccess = () => resolve(req.result); req.onerror = () => reject(req.error); });};
const idbPut = async (key, value) => { const db = await openDb(); return new Promise((resolve, reject) => { const tx = db.transaction(STORE_NAME, "readwrite"); tx.objectStore(STORE_NAME).put(value, key); tx.oncomplete = () => resolve(); tx.onerror = () => reject(tx.error); });};
// Primitives
const utf8 = (str) => new TextEncoder().encode(str);
const b64url = (bytes) => { let bin = ""; for (let i = 0; i < bytes.length; i++) bin += String.fromCharCode(bytes[i]); return btoa(bin).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/, "");};
const sha256Bytes = async (bytes) => new Uint8Array(await crypto.subtle.digest("SHA-256", bytes));
const sha256B64Url = async (str) => b64url(await sha256Bytes(utf8(str)));
const randomJti = () => { const bytes = new Uint8Array(16); crypto.getRandomValues(bytes); return b64url(bytes);};
// DPoP keypair
const loadOrCreateKey = async () => { const existing = await idbGet(KEY_ID); if (existing) return existing; const pair = await crypto.subtle.generateKey( { name: "ECDSA", namedCurve: "P-256" }, false, ["sign", "verify"], ); await idbPut(KEY_ID, pair); return pair;};
const publicJwk = async (publicKey) => { const jwk = await crypto.subtle.exportKey("jwk", publicKey); return { crv: jwk.crv, kty: jwk.kty, x: jwk.x, y: jwk.y };};
// RFC 7638 thumbprint over canonical EC JWK.const jwkThumbprint = async (jwk) => sha256B64Url( JSON.stringify({ crv: jwk.crv, kty: jwk.kty, x: jwk.x, y: jwk.y }), );
const signDpopJwt = async ( privateKey, jwk, { htm, htu, nonce, accessToken, iss },) => { const header = { typ: "dpop+jwt", alg: "ES256", jwk }; const payload = { jti: randomJti(), htm, htu, iat: Math.floor(Date.now() / 1000), }; if (nonce) payload.nonce = nonce; if (iss) payload.iss = iss; if (accessToken) payload.ath = await sha256B64Url(accessToken);
const input = b64url(utf8(JSON.stringify(header))) + "." + b64url(utf8(JSON.stringify(payload))); const sig = new Uint8Array( await crypto.subtle.sign( { name: "ECDSA", hash: "SHA-256" }, privateKey, utf8(input), ), ); return input + "." + b64url(sig);};
// Storage
const readJSON = (store, key) => { try { const s = store.getItem(key); return s ? JSON.parse(s) : null; } catch { return null; }};
const writeJSON = (store, key, value) => store.setItem(key, JSON.stringify(value));
const remove = (store, key) => store.removeItem(key);
// Bootstrap
const pair = await loadOrCreateKey();const jwk = await publicJwk(pair.publicKey);const jkt = await jwkThumbprint(jwk);
const flags = { clientName: "Standard Publication", appUrl: "https://cuducos.tngl.io/std-pub", plcDirectoryUrl: "https://plc.directory", dohUrl: "https://cloudflare-dns.com/dns-query", bskyAppviewUrl: "https://api.bsky.app", jkt, now: Math.floor(Date.now() / 1000), session: readJSON(localStorage, SESSION_KEY), pendingOAuth: readJSON(sessionStorage, PENDING_KEY),};
const app = Elm.Main.init({ node: document.getElementById("app"), flags });
app.ports.signDpop.subscribe(async (req) => { try { const jwt = await signDpopJwt(pair.privateKey, jwk, req); app.ports.onDpopSigned.send({ id: req.id, jwt, error: null }); } catch (e) { app.ports.onDpopSigned.send({ id: req.id, jwt: null, error: e.message || String(e), }); }});
app.ports.sha256.subscribe(async ({ id, input }) => { app.ports.onSha256.send({ id, value: await sha256B64Url(input) });});
app.ports.saveSession.subscribe((session) => writeJSON(localStorage, SESSION_KEY, session),);app.ports.clearSession.subscribe(() => remove(localStorage, SESSION_KEY));app.ports.savePendingOAuth.subscribe((pending) => writeJSON(sessionStorage, PENDING_KEY, pending),);app.ports.clearPendingOAuth.subscribe(() => remove(sessionStorage, PENDING_KEY),);