cobalt/web/src/lib/api/api.ts
wukko add0ab4adf
web/lib/api: wait for turnstile solution, refactor
now cobalt waits for turnstile for 15 seconds before showing an assistive dialog, instead of showing the dialog right away. much better ux!
2025-05-23 20:37:47 +06:00

144 lines
3.6 KiB
TypeScript

import { get } from "svelte/store";
import settings from "$lib/state/settings";
import { getSession, resetSession } from "$lib/api/session";
import { currentApiURL } from "$lib/api/api-url";
import { turnstileEnabled, turnstileSolved } from "$lib/state/turnstile";
import cachedInfo from "$lib/state/server-info";
import { getServerInfo } from "$lib/api/server-info";
import type { Optional } from "$lib/types/generic";
import type { CobaltAPIResponse, CobaltErrorResponse, CobaltSaveRequestBody } from "$lib/types/api";
const waitForTurnstile = async () => {
return await new Promise((resolve, reject) => {
const unsub = turnstileSolved.subscribe((solved) => {
if (solved) {
unsub();
resolve(true);
}
});
// wait for turnstile to finish for 15 seconds
setTimeout(() => {
unsub();
reject(false);
}, 15 * 1000)
});
}
const getAuthorization = async () => {
const processing = get(settings).processing;
if (processing.enableCustomApiKey && processing.customApiKey.length > 0) {
return `Api-Key ${processing.customApiKey}`;
}
if (!get(turnstileEnabled)) {
return;
}
if (!get(turnstileSolved)) {
try {
await waitForTurnstile();
} catch {
return {
status: "error",
error: {
code: "error.captcha_too_long"
}
} as CobaltErrorResponse;
}
}
const session = await getSession();
if (session) {
if ("error" in session) {
if (session.error.code !== "error.api.auth.not_configured") {
return session;
}
} else {
return `Bearer ${session.token}`;
}
}
}
const request = async (requestBody: CobaltSaveRequestBody, justRetried = false) => {
await getServerInfo();
const getCachedInfo = get(cachedInfo);
if (!getCachedInfo) {
return {
status: "error",
error: {
code: "error.api.unreachable"
}
} as CobaltErrorResponse;
}
const api = currentApiURL();
const authorization = await getAuthorization();
if (authorization && typeof authorization !== "string") {
return authorization;
}
let extraHeaders = {};
if (authorization) {
extraHeaders = {
"Authorization": authorization
}
}
const response: Optional<CobaltAPIResponse> = await fetch(api, {
method: "POST",
redirect: "manual",
signal: AbortSignal.timeout(20000),
body: JSON.stringify(requestBody),
headers: {
"Accept": "application/json",
"Content-Type": "application/json",
...extraHeaders,
},
})
.then(r => r.json())
.catch((e) => {
if (e?.message?.includes("timed out")) {
return {
status: "error",
error: {
code: "error.api.timed_out"
}
} as CobaltErrorResponse;
}
});
if (
response?.status === 'error'
&& response?.error.code === 'error.api.auth.jwt.invalid'
&& !justRetried
) {
resetSession();
await getAuthorization();
return request(requestBody, true);
}
return response;
}
const probeCobaltTunnel = async (url: string) => {
const request = await fetch(`${url}&p=1`).catch(() => {});
if (request?.status === 200) {
return request?.status;
}
return 0;
}
export default {
request,
probeCobaltTunnel,
}