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)); await Store.set("friends", []); await Store.set("is_sharing", true); // i'm adding this so it works in settings, i think it's more user friendly to have the location to be on by default, but... less privacy friendly? wait no.. nvm. it won't share location with the server since they don't have any added friends yet :) 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, "Ed25519", false, [ "sign", ]); const nonce = new Uint8Array(8); crypto.getRandomValues(nonce); const data_to_sign = new Uint8Array(bodyBytes.length + nonce.length); data_to_sign.set(bodyBytes); data_to_sign.set(nonce, bodyBytes.length); const signature = await crypto.subtle.sign("Ed25519", privKey, data_to_sign); const headers = { "x-auth": JSON.stringify({ user_id, signature: bufToBase64(signature), nonce: nonce.toBase64(), }), "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, }); if (!res.ok) throw new Error(`HTTP ${res.status}: ${await res.text()}`); return await res.text(); }