Spaces:
Runtime error
Runtime error
Charlie
commited on
Commit
·
b200338
1
Parent(s):
b508f56
update files
Browse files- dist/main.js +121 -365
- index.html +8 -4
- src/main.ts +124 -106
- src/oauth.ts +14 -43
- styles/main.css +11 -0
dist/main.js
CHANGED
|
@@ -1,291 +1,31 @@
|
|
| 1 |
-
// node_modules/@huggingface/hub/dist/index.mjs
|
| 2 |
-
var HUB_URL = "https://huggingface.co";
|
| 3 |
-
async function createApiError(response, opts) {
|
| 4 |
-
const error = new HubApiError(response.url, response.status, response.headers.get("X-Request-Id") ?? opts?.requestId);
|
| 5 |
-
error.message = `Api error with status ${error.statusCode}${opts?.message ? `. ${opts.message}` : ""}`;
|
| 6 |
-
const trailer = [`URL: ${error.url}`, error.requestId ? `Request ID: ${error.requestId}` : void 0].filter(Boolean).join(". ");
|
| 7 |
-
if (response.headers.get("Content-Type")?.startsWith("application/json")) {
|
| 8 |
-
const json = await response.json();
|
| 9 |
-
error.message = json.error || json.message || error.message;
|
| 10 |
-
if (json.error_description) {
|
| 11 |
-
error.message = error.message ? error.message + `: ${json.error_description}` : json.error_description;
|
| 12 |
-
}
|
| 13 |
-
error.data = json;
|
| 14 |
-
} else {
|
| 15 |
-
error.data = { message: await response.text() };
|
| 16 |
-
}
|
| 17 |
-
error.message += `. ${trailer}`;
|
| 18 |
-
throw error;
|
| 19 |
-
}
|
| 20 |
-
var HubApiError = class extends Error {
|
| 21 |
-
statusCode;
|
| 22 |
-
url;
|
| 23 |
-
requestId;
|
| 24 |
-
data;
|
| 25 |
-
constructor(url, statusCode, requestId, message) {
|
| 26 |
-
super(message);
|
| 27 |
-
this.statusCode = statusCode;
|
| 28 |
-
this.requestId = requestId;
|
| 29 |
-
this.url = url;
|
| 30 |
-
}
|
| 31 |
-
};
|
| 32 |
-
var isBrowser = typeof window !== "undefined" && typeof window.document !== "undefined";
|
| 33 |
-
var isWebWorker = typeof self === "object" && self.constructor && self.constructor.name === "DedicatedWorkerGlobalScope";
|
| 34 |
-
var resolve;
|
| 35 |
-
var waitPromise = new Promise((r) => {
|
| 36 |
-
resolve = r;
|
| 37 |
-
});
|
| 38 |
-
function base64FromBytes(arr) {
|
| 39 |
-
if (globalThis.Buffer) {
|
| 40 |
-
return globalThis.Buffer.from(arr).toString("base64");
|
| 41 |
-
} else {
|
| 42 |
-
const bin = [];
|
| 43 |
-
arr.forEach((byte) => {
|
| 44 |
-
bin.push(String.fromCharCode(byte));
|
| 45 |
-
});
|
| 46 |
-
return globalThis.btoa(bin.join(""));
|
| 47 |
-
}
|
| 48 |
-
}
|
| 49 |
-
var REGEX_COMMIT_HASH = new RegExp("^[0-9a-f]{40}$");
|
| 50 |
-
async function oauthHandleRedirect(opts) {
|
| 51 |
-
if (typeof window === "undefined" && !opts?.redirectedUrl) {
|
| 52 |
-
throw new Error("oauthHandleRedirect is only available in the browser, unless you provide redirectedUrl");
|
| 53 |
-
}
|
| 54 |
-
if (typeof localStorage === "undefined" && (!opts?.nonce || !opts?.codeVerifier)) {
|
| 55 |
-
throw new Error(
|
| 56 |
-
"oauthHandleRedirect requires localStorage to be available, unless you provide nonce and codeVerifier"
|
| 57 |
-
);
|
| 58 |
-
}
|
| 59 |
-
const redirectedUrl = opts?.redirectedUrl ?? window.location.href;
|
| 60 |
-
const searchParams = (() => {
|
| 61 |
-
try {
|
| 62 |
-
return new URL(redirectedUrl).searchParams;
|
| 63 |
-
} catch (err) {
|
| 64 |
-
throw new Error("Failed to parse redirected URL: " + redirectedUrl);
|
| 65 |
-
}
|
| 66 |
-
})();
|
| 67 |
-
const [error, errorDescription] = [searchParams.get("error"), searchParams.get("error_description")];
|
| 68 |
-
if (error) {
|
| 69 |
-
throw new Error(`${error}: ${errorDescription}`);
|
| 70 |
-
}
|
| 71 |
-
const code = searchParams.get("code");
|
| 72 |
-
const nonce = opts?.nonce ?? localStorage.getItem("huggingface.co:oauth:nonce");
|
| 73 |
-
if (!code) {
|
| 74 |
-
throw new Error("Missing oauth code from query parameters in redirected URL: " + redirectedUrl);
|
| 75 |
-
}
|
| 76 |
-
if (!nonce) {
|
| 77 |
-
throw new Error("Missing oauth nonce from localStorage");
|
| 78 |
-
}
|
| 79 |
-
const codeVerifier = opts?.codeVerifier ?? localStorage.getItem("huggingface.co:oauth:code_verifier");
|
| 80 |
-
if (!codeVerifier) {
|
| 81 |
-
throw new Error("Missing oauth code_verifier from localStorage");
|
| 82 |
-
}
|
| 83 |
-
const state = searchParams.get("state");
|
| 84 |
-
if (!state) {
|
| 85 |
-
throw new Error("Missing oauth state from query parameters in redirected URL");
|
| 86 |
-
}
|
| 87 |
-
let parsedState;
|
| 88 |
-
try {
|
| 89 |
-
parsedState = JSON.parse(state);
|
| 90 |
-
} catch {
|
| 91 |
-
throw new Error("Invalid oauth state in redirected URL, unable to parse JSON: " + state);
|
| 92 |
-
}
|
| 93 |
-
if (parsedState.nonce !== nonce) {
|
| 94 |
-
throw new Error("Invalid oauth state in redirected URL");
|
| 95 |
-
}
|
| 96 |
-
const hubUrl = opts?.hubUrl || HUB_URL;
|
| 97 |
-
const openidConfigUrl = `${new URL(hubUrl).origin}/.well-known/openid-configuration`;
|
| 98 |
-
const openidConfigRes = await fetch(openidConfigUrl, {
|
| 99 |
-
headers: {
|
| 100 |
-
Accept: "application/json"
|
| 101 |
-
}
|
| 102 |
-
});
|
| 103 |
-
if (!openidConfigRes.ok) {
|
| 104 |
-
throw await createApiError(openidConfigRes);
|
| 105 |
-
}
|
| 106 |
-
const openidConfig = await openidConfigRes.json();
|
| 107 |
-
const tokenRes = await fetch(openidConfig.token_endpoint, {
|
| 108 |
-
method: "POST",
|
| 109 |
-
headers: {
|
| 110 |
-
"Content-Type": "application/x-www-form-urlencoded"
|
| 111 |
-
},
|
| 112 |
-
body: new URLSearchParams({
|
| 113 |
-
grant_type: "authorization_code",
|
| 114 |
-
code,
|
| 115 |
-
redirect_uri: parsedState.redirectUri,
|
| 116 |
-
code_verifier: codeVerifier
|
| 117 |
-
}).toString()
|
| 118 |
-
});
|
| 119 |
-
if (!opts?.codeVerifier) {
|
| 120 |
-
localStorage.removeItem("huggingface.co:oauth:code_verifier");
|
| 121 |
-
}
|
| 122 |
-
if (!opts?.nonce) {
|
| 123 |
-
localStorage.removeItem("huggingface.co:oauth:nonce");
|
| 124 |
-
}
|
| 125 |
-
if (!tokenRes.ok) {
|
| 126 |
-
throw await createApiError(tokenRes);
|
| 127 |
-
}
|
| 128 |
-
const token = await tokenRes.json();
|
| 129 |
-
const accessTokenExpiresAt = new Date(Date.now() + token.expires_in * 1e3);
|
| 130 |
-
const userInfoRes = await fetch(openidConfig.userinfo_endpoint, {
|
| 131 |
-
headers: {
|
| 132 |
-
Authorization: `Bearer ${token.access_token}`
|
| 133 |
-
}
|
| 134 |
-
});
|
| 135 |
-
if (!userInfoRes.ok) {
|
| 136 |
-
throw await createApiError(userInfoRes);
|
| 137 |
-
}
|
| 138 |
-
const userInfo = await userInfoRes.json();
|
| 139 |
-
return {
|
| 140 |
-
accessToken: token.access_token,
|
| 141 |
-
accessTokenExpiresAt,
|
| 142 |
-
userInfo,
|
| 143 |
-
state: parsedState.state,
|
| 144 |
-
scope: token.scope
|
| 145 |
-
};
|
| 146 |
-
}
|
| 147 |
-
async function oauthHandleRedirectIfPresent(opts) {
|
| 148 |
-
if (typeof window === "undefined" && !opts?.redirectedUrl) {
|
| 149 |
-
throw new Error("oauthHandleRedirect is only available in the browser, unless you provide redirectedUrl");
|
| 150 |
-
}
|
| 151 |
-
if (typeof localStorage === "undefined" && (!opts?.nonce || !opts?.codeVerifier)) {
|
| 152 |
-
throw new Error(
|
| 153 |
-
"oauthHandleRedirect requires localStorage to be available, unless you provide nonce and codeVerifier"
|
| 154 |
-
);
|
| 155 |
-
}
|
| 156 |
-
const searchParams = new URLSearchParams(opts?.redirectedUrl ?? window.location.search);
|
| 157 |
-
if (searchParams.has("error")) {
|
| 158 |
-
return oauthHandleRedirect(opts);
|
| 159 |
-
}
|
| 160 |
-
if (searchParams.has("code")) {
|
| 161 |
-
if (!localStorage.getItem("huggingface.co:oauth:nonce")) {
|
| 162 |
-
console.warn(
|
| 163 |
-
"Missing oauth nonce from localStorage. This can happen when the user refreshes the page after logging in, without changing the URL."
|
| 164 |
-
);
|
| 165 |
-
return false;
|
| 166 |
-
}
|
| 167 |
-
return oauthHandleRedirect(opts);
|
| 168 |
-
}
|
| 169 |
-
return false;
|
| 170 |
-
}
|
| 171 |
-
async function oauthLoginUrl(opts) {
|
| 172 |
-
if (typeof window === "undefined" && (!opts?.redirectUrl || !opts?.clientId)) {
|
| 173 |
-
throw new Error("oauthLogin is only available in the browser, unless you provide clientId and redirectUrl");
|
| 174 |
-
}
|
| 175 |
-
if (typeof localStorage === "undefined" && !opts?.localStorage) {
|
| 176 |
-
throw new Error(
|
| 177 |
-
"oauthLogin requires localStorage to be available in the context, unless you provide a localStorage empty object as argument"
|
| 178 |
-
);
|
| 179 |
-
}
|
| 180 |
-
const hubUrl = opts?.hubUrl || HUB_URL;
|
| 181 |
-
const openidConfigUrl = `${new URL(hubUrl).origin}/.well-known/openid-configuration`;
|
| 182 |
-
const openidConfigRes = await fetch(openidConfigUrl, {
|
| 183 |
-
headers: {
|
| 184 |
-
Accept: "application/json"
|
| 185 |
-
}
|
| 186 |
-
});
|
| 187 |
-
if (!openidConfigRes.ok) {
|
| 188 |
-
throw await createApiError(openidConfigRes);
|
| 189 |
-
}
|
| 190 |
-
const opendidConfig = await openidConfigRes.json();
|
| 191 |
-
const newNonce = globalThis.crypto.randomUUID();
|
| 192 |
-
const newCodeVerifier = globalThis.crypto.randomUUID() + globalThis.crypto.randomUUID();
|
| 193 |
-
if (opts?.localStorage) {
|
| 194 |
-
if (opts.localStorage.codeVerifier !== void 0 && opts.localStorage.codeVerifier !== null) {
|
| 195 |
-
throw new Error(
|
| 196 |
-
"localStorage.codeVerifier must be a initially set to null or undefined, and will be filled by oauthLoginUrl"
|
| 197 |
-
);
|
| 198 |
-
}
|
| 199 |
-
if (opts.localStorage.nonce !== void 0 && opts.localStorage.nonce !== null) {
|
| 200 |
-
throw new Error(
|
| 201 |
-
"localStorage.nonce must be a initially set to null or undefined, and will be filled by oauthLoginUrl"
|
| 202 |
-
);
|
| 203 |
-
}
|
| 204 |
-
opts.localStorage.codeVerifier = newCodeVerifier;
|
| 205 |
-
opts.localStorage.nonce = newNonce;
|
| 206 |
-
} else {
|
| 207 |
-
localStorage.setItem("huggingface.co:oauth:nonce", newNonce);
|
| 208 |
-
localStorage.setItem("huggingface.co:oauth:code_verifier", newCodeVerifier);
|
| 209 |
-
}
|
| 210 |
-
const redirectUri = opts?.redirectUrl || (typeof window !== "undefined" ? window.location.href : void 0);
|
| 211 |
-
if (!redirectUri) {
|
| 212 |
-
throw new Error("Missing redirectUrl");
|
| 213 |
-
}
|
| 214 |
-
const state = JSON.stringify({
|
| 215 |
-
nonce: newNonce,
|
| 216 |
-
redirectUri,
|
| 217 |
-
state: opts?.state
|
| 218 |
-
});
|
| 219 |
-
const variables = (
|
| 220 |
-
// @ts-expect-error window.huggingface is defined inside static Spaces.
|
| 221 |
-
typeof window !== "undefined" ? window.huggingface?.variables ?? null : null
|
| 222 |
-
);
|
| 223 |
-
const clientId = opts?.clientId || variables?.OAUTH_CLIENT_ID;
|
| 224 |
-
if (!clientId) {
|
| 225 |
-
if (variables) {
|
| 226 |
-
throw new Error("Missing clientId, please add hf_oauth: true to the README.md's metadata in your static Space");
|
| 227 |
-
}
|
| 228 |
-
throw new Error("Missing clientId");
|
| 229 |
-
}
|
| 230 |
-
const challenge = base64FromBytes(
|
| 231 |
-
new Uint8Array(await globalThis.crypto.subtle.digest("SHA-256", new TextEncoder().encode(newCodeVerifier)))
|
| 232 |
-
).replace(/[+]/g, "-").replace(/[/]/g, "_").replace(/=/g, "");
|
| 233 |
-
return `${opendidConfig.authorization_endpoint}?${new URLSearchParams({
|
| 234 |
-
client_id: clientId,
|
| 235 |
-
scope: opts?.scopes || variables?.OAUTH_SCOPES || "openid profile",
|
| 236 |
-
response_type: "code",
|
| 237 |
-
redirect_uri: redirectUri,
|
| 238 |
-
state,
|
| 239 |
-
code_challenge: challenge,
|
| 240 |
-
code_challenge_method: "S256"
|
| 241 |
-
}).toString()}`;
|
| 242 |
-
}
|
| 243 |
-
|
| 244 |
// src/oauth.ts
|
| 245 |
-
var
|
| 246 |
constructor() {
|
| 247 |
-
this.
|
| 248 |
-
const
|
| 249 |
-
if (
|
| 250 |
-
|
| 251 |
-
this.oauthResult = JSON.parse(storedOAuth);
|
| 252 |
-
} catch {
|
| 253 |
-
this.oauthResult = null;
|
| 254 |
-
}
|
| 255 |
}
|
| 256 |
}
|
| 257 |
-
|
| 258 |
-
this.
|
| 259 |
-
|
| 260 |
-
localStorage.setItem("oauth", JSON.stringify(this.oauthResult));
|
| 261 |
-
}
|
| 262 |
-
return this.oauthResult;
|
| 263 |
-
}
|
| 264 |
-
async initiateLogin() {
|
| 265 |
-
const loginUrl = await oauthLoginUrl({
|
| 266 |
-
scopes: window.huggingface?.variables?.OAUTH_SCOPES ?? ""
|
| 267 |
-
});
|
| 268 |
-
window.location.href = `${loginUrl}&prompt=consent`;
|
| 269 |
}
|
| 270 |
logout() {
|
| 271 |
-
|
| 272 |
-
|
| 273 |
window.location.reload();
|
| 274 |
}
|
| 275 |
getAccessToken() {
|
| 276 |
-
|
| 277 |
-
return this.oauthResult.accessToken ?? null;
|
| 278 |
-
}
|
| 279 |
-
return null;
|
| 280 |
}
|
| 281 |
isAuthenticated() {
|
| 282 |
-
return this.
|
| 283 |
}
|
| 284 |
};
|
| 285 |
-
var
|
| 286 |
|
| 287 |
// src/main.ts
|
| 288 |
-
console.log("huggingface env", window.huggingface);
|
| 289 |
async function getOrganizationInfo(accessToken) {
|
| 290 |
const response = await fetch("https://huggingface.co/api/whoami-v2", {
|
| 291 |
headers: {
|
|
@@ -366,102 +106,118 @@ async function createRepository(accessToken, name, organization, resourceGroupNa
|
|
| 366 |
}
|
| 367 |
}
|
| 368 |
var init = async () => {
|
| 369 |
-
|
| 370 |
-
|
| 371 |
-
|
| 372 |
-
|
| 373 |
-
|
| 374 |
-
|
| 375 |
-
|
| 376 |
-
|
| 377 |
-
|
| 378 |
-
|
| 379 |
-
|
| 380 |
-
|
| 381 |
-
|
| 382 |
-
|
| 383 |
-
|
| 384 |
-
|
| 385 |
-
|
| 386 |
-
|
| 387 |
-
|
| 388 |
-
|
| 389 |
-
|
| 390 |
-
|
| 391 |
-
|
| 392 |
-
|
| 393 |
-
|
| 394 |
-
|
| 395 |
-
|
| 396 |
-
|
| 397 |
-
|
| 398 |
-
|
| 399 |
-
|
| 400 |
-
|
| 401 |
-
|
| 402 |
-
|
| 403 |
-
|
| 404 |
-
|
| 405 |
-
|
| 406 |
-
|
| 407 |
-
|
| 408 |
-
|
| 409 |
-
|
| 410 |
-
|
| 411 |
-
|
| 412 |
-
|
| 413 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 414 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 415 |
}
|
| 416 |
-
};
|
| 417 |
-
}
|
| 418 |
-
const orgSelect = document.getElementById(
|
| 419 |
-
"org-select"
|
| 420 |
-
);
|
| 421 |
-
if (orgSelect && resourceGroupContainer) {
|
| 422 |
-
orgSelect.onchange = () => {
|
| 423 |
-
if (orgSelect.value) {
|
| 424 |
-
resourceGroupContainer.style.removeProperty("display");
|
| 425 |
-
} else {
|
| 426 |
-
resourceGroupContainer.style.display = "none";
|
| 427 |
-
}
|
| 428 |
-
};
|
| 429 |
-
}
|
| 430 |
-
try {
|
| 431 |
-
const orgInfo = await getOrganizationInfo(accessToken);
|
| 432 |
-
if (orgSelect && orgInfo.orgs) {
|
| 433 |
-
orgInfo.orgs.forEach((org) => {
|
| 434 |
-
const option = document.createElement("option");
|
| 435 |
-
option.value = org.name;
|
| 436 |
-
option.textContent = org.name;
|
| 437 |
-
orgSelect.appendChild(option);
|
| 438 |
-
});
|
| 439 |
}
|
| 440 |
-
|
| 441 |
-
|
| 442 |
-
|
| 443 |
-
|
| 444 |
-
|
| 445 |
-
|
| 446 |
-
|
| 447 |
-
|
| 448 |
-
|
| 449 |
-
);
|
| 450 |
}
|
| 451 |
-
}
|
| 452 |
-
|
| 453 |
-
|
| 454 |
-
const
|
| 455 |
-
if (
|
| 456 |
-
|
| 457 |
-
|
| 458 |
-
|
| 459 |
-
|
| 460 |
-
|
| 461 |
-
|
| 462 |
-
|
| 463 |
-
|
| 464 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 465 |
}
|
| 466 |
};
|
| 467 |
init().catch(console.error);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
// src/oauth.ts
|
| 2 |
+
var TokenManager = class {
|
| 3 |
constructor() {
|
| 4 |
+
this.token = null;
|
| 5 |
+
const storedToken = localStorage.getItem("token");
|
| 6 |
+
if (storedToken) {
|
| 7 |
+
this.token = storedToken;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
}
|
| 9 |
}
|
| 10 |
+
setToken(token) {
|
| 11 |
+
this.token = token;
|
| 12 |
+
localStorage.setItem("token", token);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
}
|
| 14 |
logout() {
|
| 15 |
+
this.token = null;
|
| 16 |
+
localStorage.removeItem("token");
|
| 17 |
window.location.reload();
|
| 18 |
}
|
| 19 |
getAccessToken() {
|
| 20 |
+
return this.token;
|
|
|
|
|
|
|
|
|
|
| 21 |
}
|
| 22 |
isAuthenticated() {
|
| 23 |
+
return this.token !== null;
|
| 24 |
}
|
| 25 |
};
|
| 26 |
+
var tokenManager = new TokenManager();
|
| 27 |
|
| 28 |
// src/main.ts
|
|
|
|
| 29 |
async function getOrganizationInfo(accessToken) {
|
| 30 |
const response = await fetch("https://huggingface.co/api/whoami-v2", {
|
| 31 |
headers: {
|
|
|
|
| 106 |
}
|
| 107 |
}
|
| 108 |
var init = async () => {
|
| 109 |
+
if (tokenManager.isAuthenticated()) {
|
| 110 |
+
showAuthenticatedUI();
|
| 111 |
+
} else {
|
| 112 |
+
showUnauthenticatedUI();
|
| 113 |
+
}
|
| 114 |
+
};
|
| 115 |
+
var showAuthenticatedUI = async () => {
|
| 116 |
+
const accessToken = tokenManager.getAccessToken();
|
| 117 |
+
if (!accessToken) {
|
| 118 |
+
throw new Error("Access token not found");
|
| 119 |
+
}
|
| 120 |
+
const tokenForm = document.getElementById("token-form");
|
| 121 |
+
if (tokenForm) {
|
| 122 |
+
tokenForm.style.display = "none";
|
| 123 |
+
}
|
| 124 |
+
const repoForm = document.getElementById("repo-form");
|
| 125 |
+
const signoutButton = document.getElementById("signout");
|
| 126 |
+
if (repoForm) {
|
| 127 |
+
repoForm.style.removeProperty("display");
|
| 128 |
+
}
|
| 129 |
+
if (signoutButton) {
|
| 130 |
+
signoutButton.style.removeProperty("display");
|
| 131 |
+
signoutButton.onclick = () => tokenManager.logout();
|
| 132 |
+
}
|
| 133 |
+
const createRepoButton = document.getElementById("create-repo");
|
| 134 |
+
const repoNameInput = document.getElementById(
|
| 135 |
+
"repo-name"
|
| 136 |
+
);
|
| 137 |
+
const resourceGroupInput = document.getElementById(
|
| 138 |
+
"resource-group-name"
|
| 139 |
+
);
|
| 140 |
+
const resourceGroupContainer = document.getElementById(
|
| 141 |
+
"resource-group-container"
|
| 142 |
+
);
|
| 143 |
+
if (createRepoButton && repoNameInput) {
|
| 144 |
+
createRepoButton.onclick = async () => {
|
| 145 |
+
const repoName = repoNameInput.value.trim();
|
| 146 |
+
const orgSelect2 = document.getElementById(
|
| 147 |
+
"org-select"
|
| 148 |
+
);
|
| 149 |
+
if (repoName) {
|
| 150 |
+
try {
|
| 151 |
+
const selectedOrg = orgSelect2?.value || void 0;
|
| 152 |
+
const resourceGroupName = selectedOrg ? resourceGroupInput?.value.trim() : void 0;
|
| 153 |
+
console.log({ selectedOrg, resourceGroupName });
|
| 154 |
+
await createRepository(
|
| 155 |
+
accessToken,
|
| 156 |
+
repoName,
|
| 157 |
+
selectedOrg,
|
| 158 |
+
resourceGroupName
|
| 159 |
+
);
|
| 160 |
+
repoNameInput.value = "";
|
| 161 |
+
if (resourceGroupInput) {
|
| 162 |
+
resourceGroupInput.value = "";
|
| 163 |
}
|
| 164 |
+
alert("Repository and resource group created successfully!");
|
| 165 |
+
} catch (error) {
|
| 166 |
+
console.error("Failed to create repository:", error);
|
| 167 |
+
alert("Failed to create repository. Please try again.");
|
| 168 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 169 |
}
|
| 170 |
+
};
|
| 171 |
+
}
|
| 172 |
+
const orgSelect = document.getElementById("org-select");
|
| 173 |
+
if (orgSelect && resourceGroupContainer) {
|
| 174 |
+
orgSelect.onchange = () => {
|
| 175 |
+
if (orgSelect.value) {
|
| 176 |
+
resourceGroupContainer.style.removeProperty("display");
|
| 177 |
+
} else {
|
| 178 |
+
resourceGroupContainer.style.display = "none";
|
|
|
|
| 179 |
}
|
| 180 |
+
};
|
| 181 |
+
}
|
| 182 |
+
try {
|
| 183 |
+
const orgInfo = await getOrganizationInfo(accessToken);
|
| 184 |
+
if (orgSelect && orgInfo.orgs) {
|
| 185 |
+
orgInfo.orgs.forEach((org) => {
|
| 186 |
+
const option = document.createElement("option");
|
| 187 |
+
option.value = org.name;
|
| 188 |
+
option.textContent = org.name;
|
| 189 |
+
orgSelect.appendChild(option);
|
| 190 |
+
});
|
| 191 |
+
}
|
| 192 |
+
const preElement = document.querySelector("pre");
|
| 193 |
+
if (preElement) {
|
| 194 |
+
preElement.textContent = JSON.stringify(orgInfo, null, 2);
|
| 195 |
+
}
|
| 196 |
+
} catch (error) {
|
| 197 |
+
console.error("Failed to fetch organization info:", error);
|
| 198 |
+
}
|
| 199 |
+
};
|
| 200 |
+
var showUnauthenticatedUI = () => {
|
| 201 |
+
const tokenForm = document.getElementById("token-form");
|
| 202 |
+
const tokenInput = document.getElementById("token-input");
|
| 203 |
+
const tokenSubmit = document.getElementById("token-submit");
|
| 204 |
+
if (tokenForm && tokenInput && tokenSubmit) {
|
| 205 |
+
tokenForm.style.removeProperty("display");
|
| 206 |
+
tokenSubmit.onclick = () => {
|
| 207 |
+
const token = tokenInput.value.trim();
|
| 208 |
+
if (token) {
|
| 209 |
+
tokenManager.setToken(token);
|
| 210 |
+
showAuthenticatedUI();
|
| 211 |
+
}
|
| 212 |
+
};
|
| 213 |
+
}
|
| 214 |
+
const repoForm = document.getElementById("repo-form");
|
| 215 |
+
const signoutButton = document.getElementById("signout");
|
| 216 |
+
if (repoForm) {
|
| 217 |
+
repoForm.style.display = "none";
|
| 218 |
+
}
|
| 219 |
+
if (signoutButton) {
|
| 220 |
+
signoutButton.style.display = "none";
|
| 221 |
}
|
| 222 |
};
|
| 223 |
init().catch(console.error);
|
index.html
CHANGED
|
@@ -4,15 +4,19 @@
|
|
| 4 |
<head>
|
| 5 |
<meta charset="UTF-8">
|
| 6 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 7 |
-
<title>HuggingFace
|
| 8 |
<link rel="stylesheet" href="styles/main.css">
|
| 9 |
</head>
|
| 10 |
|
| 11 |
<body>
|
| 12 |
<div id="app">
|
| 13 |
-
<h1>HuggingFace
|
| 14 |
<div class="auth-container">
|
| 15 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
<button id="signout" style="display: none;" class="auth-button">Sign out</button>
|
| 17 |
</div>
|
| 18 |
<div id="repo-form" style="display: none;" class="repo-form">
|
|
@@ -26,7 +30,7 @@
|
|
| 26 |
<button id="create-repo" class="repo-button">Create Repository</button>
|
| 27 |
</div>
|
| 28 |
<div id="content">
|
| 29 |
-
<h2>
|
| 30 |
<pre class="oauth-result"></pre>
|
| 31 |
</div>
|
| 32 |
</div>
|
|
|
|
| 4 |
<head>
|
| 5 |
<meta charset="UTF-8">
|
| 6 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 7 |
+
<title>HuggingFace API Demo</title>
|
| 8 |
<link rel="stylesheet" href="styles/main.css">
|
| 9 |
</head>
|
| 10 |
|
| 11 |
<body>
|
| 12 |
<div id="app">
|
| 13 |
+
<h1>HuggingFace API Demo</h1>
|
| 14 |
<div class="auth-container">
|
| 15 |
+
<div id="token-form" class="token-form">
|
| 16 |
+
<input type="password" id="token-input" placeholder="Enter your HuggingFace API token"
|
| 17 |
+
class="repo-input">
|
| 18 |
+
<button id="token-submit" class="auth-button">Submit Token</button>
|
| 19 |
+
</div>
|
| 20 |
<button id="signout" style="display: none;" class="auth-button">Sign out</button>
|
| 21 |
</div>
|
| 22 |
<div id="repo-form" style="display: none;" class="repo-form">
|
|
|
|
| 30 |
<button id="create-repo" class="repo-button">Create Repository</button>
|
| 31 |
</div>
|
| 32 |
<div id="content">
|
| 33 |
+
<h2>User Info</h2>
|
| 34 |
<pre class="oauth-result"></pre>
|
| 35 |
</div>
|
| 36 |
</div>
|
src/main.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
import {
|
| 2 |
|
| 3 |
interface OrganizationInfo {
|
| 4 |
type: string;
|
|
@@ -22,10 +22,6 @@ interface OrganizationMember {
|
|
| 22 |
role: string;
|
| 23 |
}
|
| 24 |
|
| 25 |
-
declare const window: HuggingFaceWindow;
|
| 26 |
-
|
| 27 |
-
console.log("huggingface env", window.huggingface);
|
| 28 |
-
|
| 29 |
async function getOrganizationInfo(
|
| 30 |
accessToken: string
|
| 31 |
): Promise<WhoAmIResponse> {
|
|
@@ -133,120 +129,142 @@ async function createRepository(
|
|
| 133 |
}
|
| 134 |
|
| 135 |
const init = async (): Promise<void> => {
|
| 136 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 137 |
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
| 143 |
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
)
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
)
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
);
|
| 161 |
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 190 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 191 |
}
|
| 192 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 193 |
}
|
| 194 |
|
| 195 |
-
//
|
| 196 |
-
const
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
if (orgSelect && resourceGroupContainer) {
|
| 200 |
-
orgSelect.onchange = () => {
|
| 201 |
-
if (orgSelect.value) {
|
| 202 |
-
resourceGroupContainer.style.removeProperty("display");
|
| 203 |
-
} else {
|
| 204 |
-
resourceGroupContainer.style.display = "none";
|
| 205 |
-
}
|
| 206 |
-
};
|
| 207 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 208 |
|
| 209 |
-
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
|
| 213 |
-
|
| 214 |
-
if (orgSelect && orgInfo.orgs) {
|
| 215 |
-
orgInfo.orgs.forEach((org) => {
|
| 216 |
-
const option = document.createElement("option");
|
| 217 |
-
option.value = org.name;
|
| 218 |
-
option.textContent = org.name;
|
| 219 |
-
orgSelect.appendChild(option);
|
| 220 |
-
});
|
| 221 |
-
}
|
| 222 |
|
| 223 |
-
|
| 224 |
-
|
| 225 |
-
|
| 226 |
-
|
| 227 |
-
|
| 228 |
-
|
| 229 |
-
|
| 230 |
-
},
|
| 231 |
-
null,
|
| 232 |
-
2
|
| 233 |
-
);
|
| 234 |
}
|
| 235 |
-
}
|
| 236 |
-
|
| 237 |
-
}
|
| 238 |
|
| 239 |
-
|
| 240 |
-
|
| 241 |
-
|
| 242 |
-
|
| 243 |
-
|
| 244 |
-
}
|
| 245 |
-
|
| 246 |
-
|
| 247 |
-
signinButton.style.removeProperty("display");
|
| 248 |
-
signinButton.onclick = () => oauthManager.initiateLogin();
|
| 249 |
-
}
|
| 250 |
}
|
| 251 |
};
|
| 252 |
|
|
|
|
| 1 |
+
import { tokenManager } from "./oauth";
|
| 2 |
|
| 3 |
interface OrganizationInfo {
|
| 4 |
type: string;
|
|
|
|
| 22 |
role: string;
|
| 23 |
}
|
| 24 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
async function getOrganizationInfo(
|
| 26 |
accessToken: string
|
| 27 |
): Promise<WhoAmIResponse> {
|
|
|
|
| 129 |
}
|
| 130 |
|
| 131 |
const init = async (): Promise<void> => {
|
| 132 |
+
if (tokenManager.isAuthenticated()) {
|
| 133 |
+
showAuthenticatedUI();
|
| 134 |
+
} else {
|
| 135 |
+
showUnauthenticatedUI();
|
| 136 |
+
}
|
| 137 |
+
};
|
| 138 |
|
| 139 |
+
const showAuthenticatedUI = async () => {
|
| 140 |
+
const accessToken = tokenManager.getAccessToken();
|
| 141 |
+
if (!accessToken) {
|
| 142 |
+
throw new Error("Access token not found");
|
| 143 |
+
}
|
| 144 |
|
| 145 |
+
// Hide token form
|
| 146 |
+
const tokenForm = document.getElementById("token-form");
|
| 147 |
+
if (tokenForm) {
|
| 148 |
+
tokenForm.style.display = "none";
|
| 149 |
+
}
|
| 150 |
|
| 151 |
+
// Show repo form and signout button
|
| 152 |
+
const repoForm = document.getElementById("repo-form");
|
| 153 |
+
const signoutButton = document.getElementById("signout");
|
| 154 |
+
if (repoForm) {
|
| 155 |
+
repoForm.style.removeProperty("display");
|
| 156 |
+
}
|
| 157 |
+
if (signoutButton) {
|
| 158 |
+
signoutButton.style.removeProperty("display");
|
| 159 |
+
signoutButton.onclick = () => tokenManager.logout();
|
| 160 |
+
}
|
|
|
|
| 161 |
|
| 162 |
+
// Add create repo functionality
|
| 163 |
+
const createRepoButton = document.getElementById("create-repo");
|
| 164 |
+
const repoNameInput = document.getElementById(
|
| 165 |
+
"repo-name"
|
| 166 |
+
) as HTMLInputElement;
|
| 167 |
+
const resourceGroupInput = document.getElementById(
|
| 168 |
+
"resource-group-name"
|
| 169 |
+
) as HTMLInputElement;
|
| 170 |
+
const resourceGroupContainer = document.getElementById(
|
| 171 |
+
"resource-group-container"
|
| 172 |
+
);
|
| 173 |
+
|
| 174 |
+
if (createRepoButton && repoNameInput) {
|
| 175 |
+
createRepoButton.onclick = async () => {
|
| 176 |
+
const repoName = repoNameInput.value.trim();
|
| 177 |
+
const orgSelect = document.getElementById(
|
| 178 |
+
"org-select"
|
| 179 |
+
) as HTMLSelectElement;
|
| 180 |
+
if (repoName) {
|
| 181 |
+
try {
|
| 182 |
+
const selectedOrg = orgSelect?.value || undefined;
|
| 183 |
+
const resourceGroupName = selectedOrg
|
| 184 |
+
? resourceGroupInput?.value.trim()
|
| 185 |
+
: undefined;
|
| 186 |
+
|
| 187 |
+
console.log({ selectedOrg, resourceGroupName });
|
| 188 |
+
await createRepository(
|
| 189 |
+
accessToken,
|
| 190 |
+
repoName,
|
| 191 |
+
selectedOrg,
|
| 192 |
+
resourceGroupName
|
| 193 |
+
);
|
| 194 |
+
repoNameInput.value = ""; // Clear input after success
|
| 195 |
+
if (resourceGroupInput) {
|
| 196 |
+
resourceGroupInput.value = ""; // Clear resource group input
|
| 197 |
}
|
| 198 |
+
alert("Repository and resource group created successfully!");
|
| 199 |
+
} catch (error) {
|
| 200 |
+
console.error("Failed to create repository:", error);
|
| 201 |
+
alert("Failed to create repository. Please try again.");
|
| 202 |
}
|
| 203 |
+
}
|
| 204 |
+
};
|
| 205 |
+
}
|
| 206 |
+
|
| 207 |
+
// Handle org select change to show/hide resource group input
|
| 208 |
+
const orgSelect = document.getElementById("org-select") as HTMLSelectElement;
|
| 209 |
+
if (orgSelect && resourceGroupContainer) {
|
| 210 |
+
orgSelect.onchange = () => {
|
| 211 |
+
if (orgSelect.value) {
|
| 212 |
+
resourceGroupContainer.style.removeProperty("display");
|
| 213 |
+
} else {
|
| 214 |
+
resourceGroupContainer.style.display = "none";
|
| 215 |
+
}
|
| 216 |
+
};
|
| 217 |
+
}
|
| 218 |
+
|
| 219 |
+
// Get organization info and populate org select
|
| 220 |
+
try {
|
| 221 |
+
const orgInfo = await getOrganizationInfo(accessToken);
|
| 222 |
+
|
| 223 |
+
// Populate org select
|
| 224 |
+
if (orgSelect && orgInfo.orgs) {
|
| 225 |
+
orgInfo.orgs.forEach((org) => {
|
| 226 |
+
const option = document.createElement("option");
|
| 227 |
+
option.value = org.name;
|
| 228 |
+
option.textContent = org.name;
|
| 229 |
+
orgSelect.appendChild(option);
|
| 230 |
+
});
|
| 231 |
}
|
| 232 |
|
| 233 |
+
// Display user info
|
| 234 |
+
const preElement = document.querySelector("pre");
|
| 235 |
+
if (preElement) {
|
| 236 |
+
preElement.textContent = JSON.stringify(orgInfo, null, 2);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 237 |
}
|
| 238 |
+
} catch (error) {
|
| 239 |
+
console.error("Failed to fetch organization info:", error);
|
| 240 |
+
}
|
| 241 |
+
};
|
| 242 |
|
| 243 |
+
const showUnauthenticatedUI = () => {
|
| 244 |
+
// Show token form
|
| 245 |
+
const tokenForm = document.getElementById("token-form");
|
| 246 |
+
const tokenInput = document.getElementById("token-input") as HTMLInputElement;
|
| 247 |
+
const tokenSubmit = document.getElementById("token-submit");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 248 |
|
| 249 |
+
if (tokenForm && tokenInput && tokenSubmit) {
|
| 250 |
+
tokenForm.style.removeProperty("display");
|
| 251 |
+
tokenSubmit.onclick = () => {
|
| 252 |
+
const token = tokenInput.value.trim();
|
| 253 |
+
if (token) {
|
| 254 |
+
tokenManager.setToken(token);
|
| 255 |
+
showAuthenticatedUI();
|
|
|
|
|
|
|
|
|
|
|
|
|
| 256 |
}
|
| 257 |
+
};
|
| 258 |
+
}
|
|
|
|
| 259 |
|
| 260 |
+
// Hide other UI elements
|
| 261 |
+
const repoForm = document.getElementById("repo-form");
|
| 262 |
+
const signoutButton = document.getElementById("signout");
|
| 263 |
+
if (repoForm) {
|
| 264 |
+
repoForm.style.display = "none";
|
| 265 |
+
}
|
| 266 |
+
if (signoutButton) {
|
| 267 |
+
signoutButton.style.display = "none";
|
|
|
|
|
|
|
|
|
|
| 268 |
}
|
| 269 |
};
|
| 270 |
|
src/oauth.ts
CHANGED
|
@@ -1,62 +1,33 @@
|
|
| 1 |
-
|
| 2 |
|
| 3 |
-
export
|
| 4 |
-
|
| 5 |
-
variables: {
|
| 6 |
-
OAUTH_SCOPES: string;
|
| 7 |
-
};
|
| 8 |
-
};
|
| 9 |
-
}
|
| 10 |
-
|
| 11 |
-
declare const window: HuggingFaceWindow;
|
| 12 |
-
|
| 13 |
-
export type OAuthResult = Record<string, any> | null | false;
|
| 14 |
-
|
| 15 |
-
export class OAuthManager {
|
| 16 |
-
private oauthResult: OAuthResult = null;
|
| 17 |
|
| 18 |
constructor() {
|
| 19 |
-
const
|
| 20 |
-
if (
|
| 21 |
-
|
| 22 |
-
this.oauthResult = JSON.parse(storedOAuth);
|
| 23 |
-
} catch {
|
| 24 |
-
this.oauthResult = null;
|
| 25 |
-
}
|
| 26 |
}
|
| 27 |
}
|
| 28 |
|
| 29 |
-
|
| 30 |
-
this.
|
| 31 |
-
|
| 32 |
-
localStorage.setItem("oauth", JSON.stringify(this.oauthResult));
|
| 33 |
-
}
|
| 34 |
-
return this.oauthResult;
|
| 35 |
-
}
|
| 36 |
-
|
| 37 |
-
async initiateLogin(): Promise<void> {
|
| 38 |
-
const loginUrl = await oauthLoginUrl({
|
| 39 |
-
scopes: window.huggingface?.variables?.OAUTH_SCOPES ?? "",
|
| 40 |
-
});
|
| 41 |
-
window.location.href = `${loginUrl}&prompt=consent`;
|
| 42 |
}
|
| 43 |
|
| 44 |
logout(): void {
|
| 45 |
-
|
| 46 |
-
|
| 47 |
window.location.reload();
|
| 48 |
}
|
| 49 |
|
| 50 |
getAccessToken(): string | null {
|
| 51 |
-
|
| 52 |
-
return this.oauthResult.accessToken ?? null;
|
| 53 |
-
}
|
| 54 |
-
return null;
|
| 55 |
}
|
| 56 |
|
| 57 |
isAuthenticated(): boolean {
|
| 58 |
-
return this.
|
| 59 |
}
|
| 60 |
}
|
| 61 |
|
| 62 |
-
export const
|
|
|
|
| 1 |
+
export type TokenResult = string | null;
|
| 2 |
|
| 3 |
+
export class TokenManager {
|
| 4 |
+
private token: TokenResult = null;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
|
| 6 |
constructor() {
|
| 7 |
+
const storedToken = localStorage.getItem("token");
|
| 8 |
+
if (storedToken) {
|
| 9 |
+
this.token = storedToken;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
}
|
| 11 |
}
|
| 12 |
|
| 13 |
+
setToken(token: string): void {
|
| 14 |
+
this.token = token;
|
| 15 |
+
localStorage.setItem("token", token);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
}
|
| 17 |
|
| 18 |
logout(): void {
|
| 19 |
+
this.token = null;
|
| 20 |
+
localStorage.removeItem("token");
|
| 21 |
window.location.reload();
|
| 22 |
}
|
| 23 |
|
| 24 |
getAccessToken(): string | null {
|
| 25 |
+
return this.token;
|
|
|
|
|
|
|
|
|
|
| 26 |
}
|
| 27 |
|
| 28 |
isAuthenticated(): boolean {
|
| 29 |
+
return this.token !== null;
|
| 30 |
}
|
| 31 |
}
|
| 32 |
|
| 33 |
+
export const tokenManager = new TokenManager();
|
styles/main.css
CHANGED
|
@@ -34,6 +34,17 @@ h2 {
|
|
| 34 |
margin: 2rem 0;
|
| 35 |
}
|
| 36 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
.auth-button {
|
| 38 |
background-color: #ff9d00;
|
| 39 |
color: white;
|
|
|
|
| 34 |
margin: 2rem 0;
|
| 35 |
}
|
| 36 |
|
| 37 |
+
.token-form {
|
| 38 |
+
max-width: 400px;
|
| 39 |
+
margin: 0 auto;
|
| 40 |
+
display: flex;
|
| 41 |
+
gap: 1rem;
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
.token-form input {
|
| 45 |
+
flex: 1;
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
.auth-button {
|
| 49 |
background-color: #ff9d00;
|
| 50 |
color: white;
|