Spaces:
Running
Running
File size: 3,881 Bytes
cfbec3f | 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 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 | import { CONFIG } from "./config.js";
const AUTH_URL = "https://huggingface.co/oauth/authorize";
const TOKEN_URL = "https://huggingface.co/oauth/token";
const redirectUri = () => location.origin + location.pathname;
function b64url(bytes) {
let s = "";
bytes.forEach((b) => (s += String.fromCharCode(b)));
return btoa(s).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
}
function randomString(len) {
const a = new Uint8Array(len);
crypto.getRandomValues(a);
return b64url(a).slice(0, len);
}
export async function login() {
if (!CONFIG.HF_OAUTH_CLIENT_ID) {
alert("HF_OAUTH_CLIENT_ID is empty in config.js");
return;
}
const verifier = randomString(64);
const state = randomString(24);
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier));
const challenge = b64url(new Uint8Array(digest));
sessionStorage.setItem("hf_pkce", JSON.stringify({ verifier, state }));
const params = new URLSearchParams({
client_id: CONFIG.HF_OAUTH_CLIENT_ID,
redirect_uri: redirectUri(),
response_type: "code",
scope: "openid profile write-repos",
state,
code_challenge: challenge,
code_challenge_method: "S256",
});
location.href = `${AUTH_URL}?${params}`;
}
export function logout() {
sessionStorage.removeItem("hf_token");
sessionStorage.removeItem("hf_pkce");
}
// Call once on load. Returns the access token if this page was opened from the OAuth redirect.
export async function handleRedirect() {
const url = new URL(location.href);
const code = url.searchParams.get("code");
if (!code) return null;
const saved = JSON.parse(sessionStorage.getItem("hf_pkce") || "null");
history.replaceState({}, "", location.pathname);
if (!saved || saved.state !== url.searchParams.get("state")) {
throw new Error("OAuth state mismatch, please sign in again");
}
const body = new URLSearchParams({
grant_type: "authorization_code",
code,
redirect_uri: redirectUri(),
client_id: CONFIG.HF_OAUTH_CLIENT_ID,
code_verifier: saved.verifier,
});
const res = await fetch(TOKEN_URL, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body,
});
sessionStorage.removeItem("hf_pkce");
if (!res.ok) throw new Error(`token exchange failed (${res.status})`);
const json = await res.json();
sessionStorage.setItem("hf_token", json.access_token);
return json.access_token;
}
export async function whoami(token) {
const res = await fetch("https://huggingface.co/api/whoami-v2", {
headers: { Authorization: `Bearer ${token}` },
});
if (!res.ok) return null;
return res.json();
}
// Public read. The timestamp query defeats stale caches.
export async function readData() {
const url = `https://huggingface.co/datasets/${CONFIG.DATA_REPO}/resolve/main/${CONFIG.DATA_FILE}?t=${Date.now()}`;
const res = await fetch(url, { cache: "no-store" });
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
}
// Admin write: commit the full JSON file to the dataset repo.
export async function writeData(token, data) {
const bytes = new TextEncoder().encode(JSON.stringify(data, null, 2));
let bin = "";
bytes.forEach((b) => (bin += String.fromCharCode(b)));
const content = btoa(bin);
const ndjson = [
JSON.stringify({ key: "header", value: { summary: `Update ${CONFIG.DATA_FILE} from dashboard`, description: "" } }),
JSON.stringify({ key: "file", value: { path: CONFIG.DATA_FILE, encoding: "base64", content } }),
].join("\n");
const res = await fetch(`https://huggingface.co/api/datasets/${CONFIG.DATA_REPO}/commit/main`, {
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/x-ndjson",
},
body: ndjson,
});
if (!res.ok) throw new Error(`commit failed (${res.status})`);
}
|