import { Store } from "./store.ts"; function bufToBase64(buf: ArrayBuffer): string { return new Uint8Array(buf).toBase64(); } /** * This function can throw an error */ export async function createAccount(server_url: string, signup_key: string): Promise<{ user_id: string; is_admin: boolean }> { const keyPair = await crypto.subtle.generateKey("Ed25519", true, ["sign", "verify"]); const pubKeyRaw = await crypto.subtle.exportKey("raw", keyPair.publicKey); const privKeyRaw = await crypto.subtle.exportKey("pkcs8", keyPair.privateKey); const pub_key_b64 = bufToBase64(pubKeyRaw); const response = await fetch(server_url + "/create-account", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ signup_key, pub_key_b64 }), }); if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`); const json = await response.json(); // TODO validate data? await Store.set("server_url", server_url); await Store.set("user_id", json.user_id); await Store.set("is_admin", json.is_admin); await Store.set("priv_key", bufToBase64(privKeyRaw)); return json; } // this api is laughably vulnerable to a replay attack currently, but not later with key chaining export async function generateSignupKey(): Promise { return await post("generate-signup-key"); } export async function requestFriendRequest(friend_id: string): Promise { await post("request-friend-request", friend_id); } export async function isFriendRequestAccepted(friend_id: string): Promise { const res = await post("is-friend-request-accepted", friend_id); return res === "true"; } export async function sendPings(friend_id: string, ping: string): Promise { // later, accept a list of friend ids, but anyways this specific api won't stay in typescript for long since it needs to be run in the background await post("send-pings", JSON.stringify([{ receiver_id: friend_id, encrypted_ping: ping }])); } export async function getPings(friend_id: string): Promise { const res = await post("get-pings", friend_id); return JSON.parse(res); } /** * This function can throw an error */ async function post(endpoint: string, body: string | undefined = undefined) { const user_id = await Store.get("user_id"); const server_url = await Store.get("server_url"); const privKey_b64 = await Store.get("priv_key"); const bodyBytes = new TextEncoder().encode(body === undefined ? "" : body); const privKeyBytes = Uint8Array.fromBase64(privKey_b64); const privKey = await crypto.subtle.importKey("pkcs8", privKeyBytes.buffer, "Ed25519", false, ["sign"]); const signature = await crypto.subtle.sign("Ed25519", privKey, bodyBytes); const signature_b64 = bufToBase64(signature); const headers = { "x-auth": JSON.stringify({ user_id, signature: signature_b64 }), "Content-Type": "application/json", // TODO: not always json tho, but does it matter? }; const res = await fetch(`${server_url}/${endpoint}`, { method: "POST", headers, body: bodyBytes, // TODO: do we need to send bodyBytes instead to match server side auth? }); if (!res.ok) throw new Error(`HTTP ${res.status}: ${await res.text()}`); return await res.text(); }