const STORAGE_KEY = "hfviewer:space-native-oauth:v2"; const START_URL = "https://hfviewer.com/api/hf_space_oauth/start"; const CONSUME_URL = "https://hfviewer.com/api/hf_space_oauth/consume"; const DESTINATION_URL = "https://hfviewer.com/api/hf_space_oauth/destination"; const POLL_INTERVAL_MS = 800; const FLOW_TIMEOUT_MS = 5 * 60 * 1000; const PREPARE_REFRESH_MS = 4 * 60 * 1000; const PREPARE_RETRY_DELAYS_MS = [0, 450, 1400]; const PREPARE_RECOVERY_DELAYS_MS = [2000, 8000, 30000, 60000]; const REQUEST_TIMEOUT_MS = 12 * 1000; const AUTH_CHANGED_EVENT = "hfviewer-space-auth-changed"; const OFFICIAL_SPACE_HOSTS = new Set([ "embedl-hfviewer.static.hf.space", "hfvwr-hfviewer-private-qa.static.hf.space", ]); // The Space shell intercepts canonical hfviewer.com links during capture and // treats every root-path URL as "return to the Space landing page", even when // its query opens an account view. Use the canonicalizing www host so these // remain ordinary external links; its 301 preserves the full query string. const ACCOUNT_DESTINATION_ORIGIN = "https://www.hfviewer.com"; const authHost = document.getElementById("hfnx-auth"); const compactAuthHost = document.getElementById("sx-auth"); let oauthResult = readStoredResult(); let rendering = false; let activeFlow = null; let preparedFlow = null; let preparePromise = null; let prepareFailureCount = 0; let prepareRecoveryTimer = null; let accountDestinationLinks = []; let lastAnnouncedAuthIdentity = null; let nativeLoginPopover = null; function currentSpaceDeployment() { const host = String(window.location.hostname || "").trim().toLowerCase(); if (host === "embedl-hfviewer.static.hf.space") return "production"; if (host === "hfvwr-hfviewer-private-qa.static.hf.space") return "qa"; if (host.endsWith(".hf.space")) return "copy"; return "local"; } function readStoredResult() { try { const value = localStorage.getItem(STORAGE_KEY); const result = value ? JSON.parse(value) : null; const expiresAt = Number(result?.spaceSession?.expiresAt || 0) * 1000; if (!result || !expiresAt || expiresAt <= Date.now()) { localStorage.removeItem(STORAGE_KEY); return null; } return result; } catch { return null; } } function writeStoredResult(value) { try { if (value) localStorage.setItem(STORAGE_KEY, JSON.stringify(value)); else localStorage.removeItem(STORAGE_KEY); } catch { // Keep the current-tab session even when durable storage is unavailable. } } function randomFlowValue() { const bytes = new Uint8Array(32); crypto.getRandomValues(bytes); return Array.from(bytes, (value) => value.toString(16).padStart(2, "0")).join(""); } function userFromResult(result) { const raw = result?.userInfo || result?.userinfo || result?.user_info || result?.user || {}; const username = String( raw.preferred_username || raw.preferredUsername || raw.username || raw.name || "" ).trim(); if (!username) return null; return { username, name: String(raw.name || username).trim(), picture: String(raw.picture || raw.avatar_url || raw.avatarUrl || "").trim(), profile: String(raw.profile || `https://huggingface.co/${encodeURIComponent(username)}`).trim(), subject: String(raw.sub || raw.id || "").trim(), email: String(raw.email || "").trim().toLowerCase(), }; } function trackAuthEvent(eventName, properties = {}) { try { const productContext = window.HFVIEWER_PRODUCT_CONTEXT && typeof window.HFVIEWER_PRODUCT_CONTEXT === "object" ? window.HFVIEWER_PRODUCT_CONTEXT : {}; window.hfviewerAnalyticsBridge?.capture?.(eventName, { product_surface: "hf_space", space_repo: String(productContext.space_repo || "unknown"), space_revision: String(productContext.space_revision || "unknown"), space_host: String(window.location.hostname || "unknown").toLowerCase(), space_deployment: currentSpaceDeployment(), auth_provider: "hugging_face", ...properties, }); } catch { // Authentication must remain independent of analytics availability. } } function identifyAuthenticatedUser(user, source) { if (!user?.username) return; try { window.hfviewerAnalyticsBridge?.identifyHfSession?.( { authenticated: true, email: user.email || "", user: { id: user.subject || "", username: user.username, name: user.name || user.username, email: user.email || "", }, }, source || "hf_space_native_oauth" ); } catch { // Consent-gated identity is best effort only. } } function keepReleasePills() { if (!authHost) return []; return Array.from(authHost.children).filter((element) => element.classList.contains("hfnx-auth__pill") ); } function makeAvatar(user) { if (user.picture) { const image = document.createElement("img"); image.className = "hfnx-auth__avatar"; image.src = user.picture; image.alt = ""; image.referrerPolicy = "no-referrer"; return image; } const letter = document.createElement("span"); letter.className = "hfnx-auth__avatar hfnx-auth__avatar--letter"; letter.textContent = user.username.charAt(0).toUpperCase(); return letter; } function makeHfviewerAccountLink(label, path, target) { const link = document.createElement("a"); link.className = "hfnx-auth__item"; link.href = new URL(path, ACCOUNT_DESTINATION_ORIGIN).href; link.target = "_blank"; link.rel = "noopener"; link.textContent = label; link.setAttribute("role", "menuitem"); link.dataset.accountDestination = target; link.dataset.accountDestinationReady = "0"; accountDestinationLinks.push(link); link.addEventListener("click", () => { trackAuthEvent("space_hfviewer_account_destination_opened", { target }); // The clicked handoff is single-use. Mint its replacement after the // browser has captured this anchor's current href for native navigation. link.dataset.accountDestinationReady = "0"; window.setTimeout(() => { void prepareAccountDestination(link); }, 0); }); return link; } function liveSpaceSession() { const session = oauthResult?.spaceSession; const expiresAt = Number(session?.expiresAt || 0) * 1000; if (!session?.id || !session?.secret || !expiresAt || expiresAt <= Date.now()) { return null; } return session; } async function prepareAccountDestination(link) { const session = liveSpaceSession(); const destination = String(link?.dataset?.accountDestination || ""); if (!session || !destination || link.dataset.accountDestinationPreparing === "1") return; link.dataset.accountDestinationPreparing = "1"; try { const result = await postJson(DESTINATION_URL, { link_session_id: session.id, link_session_secret: session.secret, destination, }); const loginUrl = new URL(String(result?.login_url || "")); if ( !["hfviewer.com", "www.hfviewer.com"].includes(loginUrl.hostname) || loginUrl.protocol !== "https:" || loginUrl.pathname !== "/api/hf_space_oauth/enter" ) throw new Error("The account handoff URL is invalid"); link.href = loginUrl.toString(); link.dataset.accountDestinationReady = "1"; } catch (error) { console.warn("Hugging Face Space account handoff preparation failed", error); link.dataset.accountDestinationReady = "0"; trackAuthEvent("space_hf_login_failed", { component: "space_account_destination", failure_stage: "destination_prepare", destination, error_name: String(error?.name || "Error").slice(0, 80), }); } finally { link.dataset.accountDestinationPreparing = "0"; } } function prepareAccountDestinations() { if (!liveSpaceSession()) { if (oauthResult) { oauthResult = null; writeStoredResult(null); renderAuth(); void prepareLogin(); } return; } for (const link of accountDestinationLinks) void prepareAccountDestination(link); } function makeLoggedOutControl() { const link = document.createElement("a"); link.dataset.spaceNativeAuth = "login"; link.className = "space-native-auth-login"; link.textContent = activeFlow ? "Signing in…" : "Log in"; link.title = activeFlow ? "Cancel this sign-in and try again" : "Sign in with Hugging Face"; link.href = preparedFlow?.loginUrl || "#"; link.target = "_blank"; link.rel = "noopener"; link.setAttribute("aria-disabled", preparedFlow || activeFlow ? "false" : "true"); link.addEventListener("click", (event) => { if (activeFlow) { event.preventDefault(); failLogin(new Error("Sign-in cancelled"), "handoff_cancelled"); return; } if (!preparedFlow) { event.preventDefault(); void prepareLogin(); return; } startLogin(preparedFlow, link); }); return link; } function closeNativeLoginPopover() { const popover = nativeLoginPopover; nativeLoginPopover = null; if (!popover) return; window.removeEventListener("resize", popover.reposition); window.removeEventListener("scroll", popover.reposition, true); document.removeEventListener("pointerdown", popover.onPointerDown, true); document.removeEventListener("keydown", popover.onKeyDown, true); popover.element.remove(); } function positionNativeLoginPopover() { const popover = nativeLoginPopover; if (!popover?.anchor?.isConnected) { closeNativeLoginPopover(); return; } const rect = popover.anchor.getBoundingClientRect(); const width = popover.element.offsetWidth || 320; const margin = 12; const left = Math.max( margin, Math.min(rect.left + rect.width / 2 - width / 2, window.innerWidth - width - margin) ); popover.element.style.left = `${Math.round(left + window.scrollX)}px`; popover.element.style.top = `${Math.round(rect.bottom + window.scrollY + 10)}px`; } function requestNativeLogin( anchor, { title = "Sign in with Hugging Face", description = "Continue to use this feature in the Space.", intent = "protected_action", } = {} ) { if (userFromResult(oauthResult) && liveSpaceSession()) return false; if (!anchor?.isConnected) return false; closeNativeLoginPopover(); const element = document.createElement("div"); element.className = "hf-watch-popover hf-login-popover space-native-login-popover"; element.setAttribute("role", "dialog"); element.setAttribute("aria-label", title); const close = document.createElement("button"); close.type = "button"; close.className = "hf-watch-popover__close"; close.setAttribute("aria-label", "Close"); close.textContent = "×"; const titleElement = document.createElement("p"); titleElement.className = "hf-watch-popover__title"; titleElement.textContent = title; const descriptionElement = document.createElement("p"); descriptionElement.className = "hf-watch-popover__sub"; descriptionElement.textContent = description; const continueLink = document.createElement("a"); continueLink.className = "hf-login-popover__btn"; continueLink.target = "_blank"; continueLink.rel = "noopener"; continueLink.setAttribute("aria-disabled", "true"); continueLink.innerHTML = '' + 'Preparing sign in…'; element.append(close, titleElement, descriptionElement, continueLink); document.body.appendChild(element); const popover = { element, anchor, intent, continueLink, reposition: () => positionNativeLoginPopover(), onPointerDown: (event) => { if (!element.contains(event.target) && !anchor.contains(event.target)) { closeNativeLoginPopover(); } }, onKeyDown: (event) => { if (event.key === "Escape") closeNativeLoginPopover(); }, }; nativeLoginPopover = popover; close.addEventListener("click", closeNativeLoginPopover); continueLink.addEventListener("click", (event) => { if (!preparedFlow || activeFlow) { event.preventDefault(); if (!activeFlow) void prepareLogin().then(() => refreshNativeLoginPopover()); return; } trackAuthEvent("space_hf_login_continue_clicked", { component: "space_native_login_popover", login_intent: intent, }); startLogin(preparedFlow, continueLink); }); window.addEventListener("resize", popover.reposition); window.addEventListener("scroll", popover.reposition, true); document.addEventListener("pointerdown", popover.onPointerDown, true); document.addEventListener("keydown", popover.onKeyDown, true); positionNativeLoginPopover(); trackAuthEvent("space_hf_login_preflight_shown", { component: "space_native_login_popover", login_intent: intent, }); void prepareLogin().then(() => refreshNativeLoginPopover()); return true; } function refreshNativeLoginPopover() { const popover = nativeLoginPopover; if (!popover) return; if (preparedFlow?.loginUrl && !activeFlow) { popover.continueLink.href = preparedFlow.loginUrl; popover.continueLink.setAttribute("aria-disabled", "false"); const label = popover.continueLink.querySelector("span"); if (label) label.textContent = "Continue with Hugging Face"; } positionNativeLoginPopover(); } function makeLoggedInControl(user, { compact = false } = {}) { const wrap = document.createElement("div"); wrap.dataset.spaceNativeAuth = "profile"; wrap.className = "space-native-auth-wrap"; if (compact) wrap.classList.add("space-native-auth-wrap--compact"); const chip = document.createElement("button"); chip.type = "button"; chip.className = "hfnx-auth__chip"; chip.setAttribute("aria-haspopup", "menu"); chip.setAttribute("aria-expanded", "false"); chip.appendChild(makeAvatar(user)); const label = document.createElement("span"); label.textContent = user.username; chip.appendChild(label); const menu = document.createElement("div"); menu.className = "hfnx-auth__menu space-native-auth-menu"; menu.setAttribute("role", "menu"); const myModels = makeHfviewerAccountLink( "My models", "/?my_models=1&blogs_with_graphs=1&next_landing=1", "my_models" ); const bookmarks = makeHfviewerAccountLink( "Bookmarks", "/?bookmarks=1&blogs_with_graphs=1&next_landing=1", "bookmarks" ); const releaseWatchlist = makeHfviewerAccountLink( "Release watchlist", "/?release_watchlist=1&blogs_with_graphs=1&next_landing=1", "release_watchlist" ); const writeArticle = makeHfviewerAccountLink( "Write article", "/?article_picker=1&login_intent=interactive_articles&blogs_with_graphs=1&next_landing=1", "write_article" ); const logout = document.createElement("button"); logout.type = "button"; logout.className = "hfnx-auth__item"; logout.textContent = "Log out"; logout.setAttribute("role", "menuitem"); logout.addEventListener("click", () => { trackAuthEvent("space_hf_logout"); oauthResult = null; writeStoredResult(null); renderAuth(); // Logged-in page loads intentionally do not prepare an OAuth flow. Once // the profile is cleared, prepare one immediately so the replacement Log // in control does not remain disabled until it is clicked or the periodic // refresh runs. void prepareLogin(); }); const setOpen = (open) => { menu.classList.toggle("is-open", open); chip.setAttribute("aria-expanded", open ? "true" : "false"); }; chip.addEventListener("click", (event) => { event.stopPropagation(); setOpen(!menu.classList.contains("is-open")); }); document.addEventListener("click", () => setOpen(false)); menu.append(myModels, bookmarks, releaseWatchlist, writeArticle, logout); wrap.append(chip, menu); return wrap; } function renderAuth() { if ((!authHost && !compactAuthHost) || rendering) return; rendering = true; accountDestinationLinks = []; const pills = keepReleasePills(); const user = userFromResult(oauthResult); if (authHost) { authHost.replaceChildren(...pills, user ? makeLoggedInControl(user) : makeLoggedOutControl()); } if (compactAuthHost) { compactAuthHost.replaceChildren(); compactAuthHost.hidden = !user; if (user) compactAuthHost.appendChild(makeLoggedInControl(user, { compact: true })); } queueMicrotask(() => { rendering = false; prepareAccountDestinations(); announceAuthState(); }); } function applyOauthResult(callbackResult, source) { oauthResult = callbackResult; writeStoredResult(callbackResult); activeFlow = null; renderAuth(); closeNativeLoginPopover(); const user = userFromResult(callbackResult); identifyAuthenticatedUser(user, source); trackAuthEvent("space_hf_login_completed", { component: "space_nav", username_present: !!user?.username, subject_present: !!user?.subject, email_present: !!user?.email, }); } function failLogin(error, failureStage) { console.warn("Hugging Face Space login failed", error); activeFlow = null; renderAuth(); trackAuthEvent("space_hf_login_failed", { component: "space_nav", failure_stage: failureStage, error_name: String(error?.name || "Error").slice(0, 80), error_message: String(error?.message || "Unknown error").slice(0, 240), request_status: Number(error?.status || 0) || undefined, }); void prepareLogin(); } function delay(milliseconds) { return new Promise((resolve) => window.setTimeout(resolve, milliseconds)); } function schedulePrepareRecovery() { if (prepareRecoveryTimer || oauthResult || activeFlow) return; const index = Math.min(prepareFailureCount, PREPARE_RECOVERY_DELAYS_MS.length - 1); const recoveryDelay = PREPARE_RECOVERY_DELAYS_MS[index]; prepareFailureCount += 1; prepareRecoveryTimer = window.setTimeout(() => { prepareRecoveryTimer = null; void prepareLogin(); }, recoveryDelay); } function retryableRequestError(error) { return ( error?.name === "TypeError" || error?.name === "AbortError" || [408, 425, 429, 500, 502, 503, 504].includes(Number(error?.status || 0)) ); } async function postJson(url, body, { retryDelays = [0] } = {}) { let lastError = null; for (let attempt = 0; attempt < retryDelays.length; attempt += 1) { if (retryDelays[attempt] > 0) await delay(retryDelays[attempt]); const controller = new AbortController(); const timer = window.setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS); try { const response = await fetch(url, { method: "POST", mode: "cors", credentials: "omit", headers: { "content-type": "application/json" }, body: JSON.stringify(body), signal: controller.signal, }); const payload = await response.json().catch(() => ({})); if (!response.ok && response.status !== 202) { const error = new Error(payload?.error || `Request failed (${response.status})`); error.status = response.status; throw error; } return payload; } catch (error) { lastError = error; if (!retryableRequestError(error) || attempt === retryDelays.length - 1) throw error; } finally { window.clearTimeout(timer); } } throw lastError || new Error("Request failed"); } function announceAuthState() { const user = userFromResult(oauthResult); const session = liveSpaceSession(); const identity = user && session ? `${user.subject || user.username}:${session.id}` : ""; if (identity === lastAnnouncedAuthIdentity) return; lastAnnouncedAuthIdentity = identity; window.dispatchEvent( new CustomEvent(AUTH_CHANGED_EVENT, { detail: { authenticated: !!identity, username: user?.username || "", }, }) ); } async function postAuthenticatedSpaceJson(url, payload) { const session = liveSpaceSession(); if (!session) throw new Error("The Space login expired. Please sign in again."); try { return await postJson(url, { ...(payload || {}), link_session_id: session.id, link_session_secret: session.secret, }); } catch (error) { if (/login expired|session expired|invalid|unauthorized/i.test(String(error?.message || ""))) { oauthResult = null; writeStoredResult(null); renderAuth(); void prepareLogin(); } throw error; } } window.hfviewerSpaceAuth = Object.freeze({ isAuthenticated: () => !!userFromResult(oauthResult) && !!liveSpaceSession(), postJson: postAuthenticatedSpaceJson, requestLogin: requestNativeLogin, }); async function pollForCompletion(flow) { while (activeFlow === flow && Date.now() - flow.startedAt < FLOW_TIMEOUT_MS) { try { const result = await postJson(CONSUME_URL, { flow_id: flow.id, flow_secret: flow.secret, }); if (result?.status === "complete" && userFromResult(result)) { applyOauthResult( { userInfo: result.userInfo, spaceSession: result.spaceSession }, "hf_space_native_oauth_handoff" ); return; } } catch (error) { failLogin(error, "handoff_consume"); return; } await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS)); } if (activeFlow === flow) failLogin(new Error("OAuth flow timed out"), "handoff_timeout"); } function startLogin(flow, link) { flow.startedAt = Date.now(); activeFlow = flow; preparedFlow = null; // Keep the clicked anchor alive until the browser has performed its native // target=_blank navigation. Replacing it synchronously can cancel the popup. link.textContent = "Signing in…"; link.title = "Cancel this sign-in and try again"; link.setAttribute("aria-disabled", "false"); trackAuthEvent("space_hf_login_clicked", { component: "space_nav" }); void pollForCompletion(flow); } async function prepareLogin() { if (oauthResult || activeFlow) return; // Authentication is intentionally available only in the two official // deployments. A fork can copy our declared space_repo value, but it cannot // claim an official browser origin and must never generate misleading auth // failures or receive a trusted handoff. if ( window.location.hostname.endsWith(".hf.space") && !OFFICIAL_SPACE_HOSTS.has(window.location.hostname.toLowerCase()) ) return; if ( preparedFlow && Date.now() - preparedFlow.preparedAt < PREPARE_REFRESH_MS ) return; if (preparePromise) return preparePromise; const flow = { id: randomFlowValue(), secret: randomFlowValue(), preparedAt: Date.now(), startedAt: 0, loginUrl: "", }; preparePromise = postJson( START_URL, { flow_id: flow.id, flow_secret: flow.secret }, { retryDelays: PREPARE_RETRY_DELAYS_MS } ).then((result) => { const loginUrl = new URL(String(result?.login_url || "")); if ( loginUrl.origin !== "https://huggingface.co" || loginUrl.pathname !== "/oauth/authorize" ) throw new Error("The OAuth login URL is invalid"); flow.loginUrl = loginUrl.toString(); preparedFlow = flow; prepareFailureCount = 0; if (prepareRecoveryTimer) window.clearTimeout(prepareRecoveryTimer); prepareRecoveryTimer = null; if (!activeFlow) renderAuth(); refreshNativeLoginPopover(); }).catch((error) => { console.warn("Hugging Face Space login preparation failed", error); trackAuthEvent("space_hf_login_failed", { component: "space_nav", failure_stage: "login_prepare", error_name: String(error?.name || "Error").slice(0, 80), error_message: String(error?.message || "Unknown error").slice(0, 240), request_status: Number(error?.status || 0) || undefined, retry_count: PREPARE_RETRY_DELAYS_MS.length - 1, }); schedulePrepareRecovery(); }).finally(() => { preparePromise = null; }); return preparePromise; } async function initializeApp() { renderAuth(); identifyAuthenticatedUser(userFromResult(oauthResult), "hf_space_native_oauth_restored"); await prepareLogin(); } if (authHost) { const observer = new MutationObserver(() => { if (!rendering && !authHost.querySelector("[data-space-native-auth]")) renderAuth(); }); observer.observe(authHost, { childList: true }); } window.addEventListener("storage", (event) => { if (event.key !== STORAGE_KEY) return; oauthResult = readStoredResult(); renderAuth(); }); window.setInterval(() => { void prepareLogin(); prepareAccountDestinations(); }, PREPARE_REFRESH_MS); void initializeApp();