import { JSONParse as json_parse, JSONStringify as json_stringify, } from "https://unpkg.com/json-with-bigint@3.4.4/json-with-bigint.js"; /// PKCE key generation. export const PKCE = { /// Create a verifier for [`PKCE::challenge`]. verifier: async (length) => { let text = ""; const possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; for (let i = 0; i < length; i++) { text += possible.charAt( Math.floor(Math.random() * possible.length), ); } return text; }, /// Create the challenge needed to request a user token. challenge: async (verifier) => { const data = new TextEncoder().encode(verifier); const digest = await window.crypto.subtle.digest("SHA-256", data); return btoa( String.fromCharCode.apply(null, [...new Uint8Array(digest)]), ) .replace(/\+/g, "-") .replace(/\//g, "_") .replace(/=+$/, ""); }, }; export default function tetratto({ host = "https://tetratto.com", api_key = null, app_id = 0n, user_token = null, user_verifier = null, user_id = 0n, }) { const GRANT_URL = `${host}/auth/connections_link/app/${app_id}`; function api_promise(res) { return new Promise((resolve, reject) => { if (res.ok) { resolve(res.payload); } else { reject(res.message); } }); } // app data async function app() { if (!api_key) { throw Error("No API key provided."); } return api_promise( json_parse( await ( await fetch(`${host}/api/v1/app_data/app`, { method: "GET", headers: { "Atto-Secret-Key": api_key, }, }) ).text(), ), ); } async function query(body) { if (!api_key) { throw Error("No API key provided."); } return api_promise( json_parse( await ( await fetch(`${host}/api/v1/app_data/query`, { method: "POST", headers: { "Content-Type": "application/json", "Atto-Secret-Key": api_key, }, body: json_stringify(body), }) ).text(), ), ); } async function insert(key, value) { if (!api_key) { throw Error("No API key provided."); } return api_promise( json_parse( await ( await fetch(`${host}/api/v1/app_data`, { method: "POST", headers: { "Content-Type": "application/json", "Atto-Secret-Key": api_key, }, body: json_stringify({ key, value, }), }) ).text(), ), ); } async function remove(id) { if (!api_key) { throw Error("No API key provided."); } return api_promise( json_parse( await ( await fetch(`${host}/api/v1/app_data/${id}`, { method: "DELETE", headers: { "Atto-Secret-Key": api_key, }, }) ).text(), ), ); } async function remove_query(body) { if (!api_key) { throw Error("No API key provided."); } return api_promise( json_parse( await ( await fetch(`${host}/api/v1/app_data/query`, { method: "DELETE", headers: { "Content-Type": "application/json", "Atto-Secret-Key": api_key, }, body: json_stringify(body), }) ).text(), ), ); } // user connection /// Extract the verifier, token, and user ID from the URL. function extract_verifier_token_uid() { const search = new URLSearchParams(window.location.search); return [ search.get("verifier"), search.get("token"), BigInt(search.get("uid")), ]; } /// Accept a connection grant and store it in localStorage. function localstorage_accept_connection() { const [verifier, token, uid] = extract_verifier_token_uid(); window.localStorage.setItem("atto:grant.verifier", verifier); window.localStorage.setItem("atto:grant.token", token); window.localStorage.setItem("atto:grant.user_id", uid); } async function refresh_token(verifier) { if (!user_token) { throw Error("No user token provided."); } return api_promise( json_parse( await ( await fetch( `${host}/api/v1/auth/user/${user_id}/grants/${app_id}/refresh`, { method, headers: { "Content-Type": "application/json", "X-Cookie": `__Secure-atto-token=${user_token}`, }, body: json_stringify({ verifier, }), }, ) ).text(), ), ); } async function request({ api_path, method = "POST", content_type = "application/json", body = "{}", }) { if (!user_token) { throw Error("No user token provided."); } return api_promise( json_parse( await ( await fetch(`${host}/api/v1/${api_path}`, { method, headers: { "Content-Type": content_type, "X-Cookie": `__Secure-atto-token=${user_token}`, }, body: content_type === "application/json" ? json_stringify(body) : body, }) ).text(), ), ); } // ... return { // app data app, query, insert, remove, remove_query, // user connection GRANT_URL, extract_verifier_token_uid, refresh_token, localstorage_accept_connection, request, }; } export function from_localstorage({ host = "https://tetratto.com", app_id = 0n, }) { const user_verifier = window.localStorage.getItem("atto:grant.verifier"); const user_token = window.localStorage.getItem("atto:grant.token"); const user_id = window.localStorage.getItem("atto:grant.user_id"); return tetratto({ host, app_id, user_verifier, user_id, user_token, }); }