tetratto/crates/app/src/public/js/app_sdk.js

319 lines
9 KiB
JavaScript
Raw Normal View History

2025-07-18 13:22:25 -04:00
import {
JSONParse as json_parse,
JSONStringify as json_stringify,
} from "https://unpkg.com/json-with-bigint@3.4.4/json-with-bigint.js";
2025-07-19 00:44:12 -04:00
/// 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}`;
2025-07-18 13:22:25 -04:00
function api_promise(res) {
return new Promise((resolve, reject) => {
if (res.ok) {
resolve(res.payload);
} else {
reject(res.message);
}
});
}
2025-07-19 00:44:12 -04:00
// app data
2025-07-18 13:22:25 -04:00
async function app() {
2025-07-19 00:44:12 -04:00
if (!api_key) {
throw Error("No API key provided.");
}
2025-07-18 13:22:25 -04:00
return api_promise(
json_parse(
await (
2025-07-19 00:44:12 -04:00
await fetch(`${host}/api/v1/app_data/app`, {
2025-07-18 13:22:25 -04:00
method: "GET",
headers: {
"Atto-Secret-Key": api_key,
},
})
).text(),
),
);
}
async function query(body) {
2025-07-19 00:44:12 -04:00
if (!api_key) {
throw Error("No API key provided.");
}
2025-07-18 13:22:25 -04:00
return api_promise(
json_parse(
await (
2025-07-19 00:44:12 -04:00
await fetch(`${host}/api/v1/app_data/query`, {
2025-07-18 13:22:25 -04:00
method: "POST",
headers: {
"Content-Type": "application/json",
"Atto-Secret-Key": api_key,
},
body: json_stringify(body),
})
).text(),
),
);
}
async function insert(key, value) {
2025-07-19 00:44:12 -04:00
if (!api_key) {
throw Error("No API key provided.");
}
2025-07-18 13:22:25 -04:00
return api_promise(
json_parse(
await (
2025-07-19 00:44:12 -04:00
await fetch(`${host}/api/v1/app_data`, {
2025-07-18 13:22:25 -04:00
method: "POST",
headers: {
"Content-Type": "application/json",
"Atto-Secret-Key": api_key,
},
body: json_stringify({
key,
value,
}),
})
).text(),
),
);
}
2025-07-19 15:31:06 -04:00
async function update(id, value) {
if (!api_key) {
throw Error("No API key provided.");
}
return api_promise(
json_parse(
await (
await fetch(`${host}/api/v1/app_data/${id}/value`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"Atto-Secret-Key": api_key,
},
body: json_stringify({
value,
}),
})
).text(),
),
);
}
2025-07-19 23:21:01 -04:00
async function rename(id, key) {
if (!api_key) {
throw Error("No API key provided.");
}
return api_promise(
json_parse(
await (
await fetch(`${host}/api/v1/app_data/${id}/key`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"Atto-Secret-Key": api_key,
},
body: json_stringify({
key,
}),
})
).text(),
),
);
}
2025-07-18 13:22:25 -04:00
async function remove(id) {
2025-07-19 00:44:12 -04:00
if (!api_key) {
throw Error("No API key provided.");
}
2025-07-18 13:22:25 -04:00
return api_promise(
json_parse(
await (
2025-07-19 00:44:12 -04:00
await fetch(`${host}/api/v1/app_data/${id}`, {
2025-07-18 13:22:25 -04:00
method: "DELETE",
headers: {
"Atto-Secret-Key": api_key,
},
})
).text(),
),
);
}
async function remove_query(body) {
2025-07-19 00:44:12 -04:00
if (!api_key) {
throw Error("No API key provided.");
}
2025-07-18 13:22:25 -04:00
return api_promise(
json_parse(
await (
2025-07-19 00:44:12 -04:00
await fetch(`${host}/api/v1/app_data/query`, {
2025-07-18 13:22:25 -04:00
method: "DELETE",
headers: {
"Content-Type": "application/json",
"Atto-Secret-Key": api_key,
},
body: json_stringify(body),
})
).text(),
),
);
}
2025-07-19 00:44:12 -04:00
// 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);
}
2025-07-19 02:00:04 -04:00
async function refresh_token() {
2025-07-19 00:44:12 -04:00
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",
2025-07-19 02:00:04 -04:00
"X-Cookie": `Atto-Grant=${user_token}`,
2025-07-19 00:44:12 -04:00
},
body: json_stringify({
2025-07-19 02:00:04 -04:00
verifier: user_verifier,
2025-07-19 00:44:12 -04:00
}),
},
)
).text(),
),
);
}
async function request({
2025-07-19 02:00:04 -04:00
route,
2025-07-19 00:44:12 -04:00
method = "POST",
content_type = "application/json",
2025-07-19 02:00:04 -04:00
body = {},
2025-07-19 00:44:12 -04:00
}) {
if (!user_token) {
throw Error("No user token provided.");
}
return api_promise(
json_parse(
await (
2025-07-19 02:00:04 -04:00
await fetch(`${host}/api/v1/${route}`, {
2025-07-19 00:44:12 -04:00
method,
headers: {
2025-07-19 02:00:04 -04:00
"Content-Type":
method === "GET" ? null : content_type,
"X-Cookie": `Atto-Grant=${user_token}`,
2025-07-19 00:44:12 -04:00
},
body:
2025-07-19 02:00:04 -04:00
method === "GET"
? null
: content_type === "application/json"
? json_stringify(body)
: body,
2025-07-19 00:44:12 -04:00
})
).text(),
),
);
}
// ...
2025-07-18 13:22:25 -04:00
return {
2025-07-19 02:00:04 -04:00
user_id,
user_token,
user_verifier,
app_id,
api_key,
2025-07-19 00:44:12 -04:00
// app data
2025-07-18 13:22:25 -04:00
app,
query,
insert,
2025-07-19 15:31:06 -04:00
update,
2025-07-19 23:21:01 -04:00
rename,
2025-07-18 13:22:25 -04:00
remove,
remove_query,
2025-07-19 00:44:12 -04:00
// user connection
GRANT_URL,
extract_verifier_token_uid,
refresh_token,
localstorage_accept_connection,
request,
2025-07-18 13:22:25 -04:00
};
}
2025-07-19 00:44:12 -04:00
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,
});
}