Spaces:
Sleeping
Sleeping
File size: 3,840 Bytes
89b6a21 0dfe898 89b6a21 7d5735a 89b6a21 0dfe898 c29f3d3 0dfe898 89b6a21 7d5735a 89b6a21 7d5735a 127bfda 89b6a21 7d5735a 89b6a21 7d5735a 89b6a21 97d4d0b 7d5735a 97d4d0b 7d5735a 97d4d0b 7d5735a 97d4d0b 7d5735a 97d4d0b 89b6a21 7d5735a 89b6a21 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 | // dashboard/features/shared/api.js
import { getStorageItem } from "./dom.js";
export const TIMEOUT_DEFAULT_MS = 12000;
/**
* Standard fetch wrapper that throws HTTP errors
* and handles abort/timeout.
*/
export async function fetchWithTimeout(url, options = {}, timeoutMs = TIMEOUT_DEFAULT_MS) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
let headers = {
"Content-Type": "application/json",
...options.headers,
};
const tokenInput = document.getElementById("adminTokenInput");
let domToken = tokenInput ? tokenInput.value.trim() : "";
let localToken = getStorageItem("byte_dashboard_admin_token");
let serverToken = window.BYTE_CONFIG?.adminToken || "";
let activeToken = domToken || localToken || serverToken;
if (activeToken) {
headers["X-Byte-Admin-Token"] = activeToken;
}
const fetchOptions = {
...options,
signal: controller.signal,
cache: "no-store",
headers,
};
// HF Spaces Subdomain Resolver for Private Iframes
let finalUrl = url;
if (url.startsWith("./api/") || url.startsWith("/api/")) {
const routePath = url.replace(/^\.\//, "/").replace(/^\/api\//, "api/");
const currentOrigin = window.location.origin;
if (currentOrigin.includes(".hf.space") || currentOrigin.includes("localhost") || currentOrigin.includes("127.0.0.1")) {
finalUrl = `${currentOrigin}/${routePath}`;
} else {
// Fallback for remote context outside Space (unlikely in this setup)
finalUrl = `https://juancs-dev-twitch-byte-bot.hf.space/${routePath}`;
}
}
// Attach token in query string as fallback (HF proxy may block headers).
if (activeToken) {
const separator = finalUrl.includes("?") ? "&" : "?";
finalUrl = `${finalUrl}${separator}auth=${encodeURIComponent(activeToken)}`;
}
try {
const response = await fetch(finalUrl, fetchOptions);
let payload = {};
// Resilient path for empty/non-JSON body with successful HTTP status.
try {
payload = await response.json();
} catch (_err) {
payload = { ok: response.ok, rawFallback: true };
}
// API contract always includes "ok: boolean".
const isPayloadOk = typeof payload === "object" && payload !== null && payload.ok !== false;
if (!response.ok || !isPayloadOk) {
const serverMessage = payload?.message || payload?.error || `HTTP ${response.status}`;
const err = new Error(serverMessage);
err.status = response.status;
err.payload = payload;
// Visual warning for missing/invalid permission on 403.
if (response.status === 403) {
console.warn("Access denied (403). Check your admin token.");
const feedback = document.getElementById("channelFeedbackMsg");
if (feedback) {
feedback.textContent = "Error 403: Admin token is invalid or missing in 'Optional Auth'.";
feedback.className = "panel-hint event-level-error";
}
const tokenInput = document.getElementById("adminTokenInput");
if (tokenInput) {
tokenInput.style.border = "2px solid var(--danger)";
tokenInput.placeholder = "ENTER TOKEN HERE";
}
}
throw err;
}
return payload;
} catch (error) {
if (error.name === "AbortError") {
throw new Error(`Timeout after ${timeoutMs / 1000}s. Network failed or bot is unresponsive.`);
}
throw error;
} finally {
clearTimeout(timeoutId);
}
}
|