Spaces:
Running
Running
Download static/apiKey.js from vojmahdal/sentiment_analysis: direct link, hf CLI and curl.
- Browser
- Download file 5.19 kB
-
https://huggingface.co/spaces/vojmahdal/sentiment_analysis/resolve/main/static/apiKey.js
- Command line
-
hf download hf://spaces/vojmahdal/sentiment_analysis/static/apiKey.js
-
curl -L -o apiKey.js https://huggingface.co/spaces/vojmahdal/sentiment_analysis/resolve/main/static/apiKey.js
5.19 kB
| /** | |
| * Shared "API Key" handling for pages that call the protected endpoints | |
| * (/records, /records/export, /stats - see auth.py). The key is kept only | |
| * in this browser's localStorage, never sent anywhere except as the | |
| * X-API-Key header on requests to this same origin. | |
| * | |
| * Include this script before any page script that needs authHeaders(), | |
| * and call renderApiKeyField(containerEl) once to show the input. | |
| */ | |
| const API_KEY_STORAGE_KEY = "apiKey"; | |
| function getApiKey() { | |
| try { | |
| return localStorage.getItem(API_KEY_STORAGE_KEY) || ""; | |
| } catch { | |
| return ""; // private browsing / blocked storage - fail open to "[Redacted/]no key", not crash | |
| } | |
| } | |
| function setApiKey(key) { | |
| try { | |
| if (key) localStorage.setItem(API_KEY_STORAGE_KEY, key); | |
| else localStorage.removeItem(API_KEY_STORAGE_KEY); | |
| } catch { | |
| // ignore - see getApiKey() | |
| } | |
| } | |
| /** Headers object to spread into a fetch() call: {...authHeaders()} */ | |
| function authHeaders() { | |
| const key = getApiKey(); | |
| return key ? { "X-API-Key": key } : {}; | |
| } | |
| /** | |
| * Renders a small "API Key" field into `containerEl`. Used to unlock | |
| * /records, /records/export, /stats and the LLM engines (see | |
| * auth.require_api_key_for_llm) - the key is issued separately, not part of | |
| * the public interface. | |
| * | |
| * Once a key is saved, the text field is hidden and replaced by a short | |
| * "API Key set." status and an "Edit" button - so the key doesn't sit around | |
| * in a visible input - and "Edit" brings the field back (e.g. to fix a key | |
| * that turned out to be wrong, or to clear it). | |
| */ | |
| function renderApiKeyField(containerEl, { onSave } = {}) { | |
| const wrap = document.createElement("div"); | |
| wrap.className = "row apiKeyRow"; | |
| // Not the shared `.label` class here: it's `display: block; margin-bottom: | |
| // 8px`, meant for a label stacked above a field, and inside this inline | |
| // row it just crowded into the next element instead of sitting beside it. | |
| wrap.style.gap = "10px"; | |
| wrap.style.marginTop = "14px"; | |
| wrap.style.flexWrap = "wrap"; | |
| const label = document.createElement("span"); | |
| label.style.fontWeight = "600"; | |
| label.style.whiteSpace = "nowrap"; | |
| label.textContent = "API Key"; | |
| const input = document.createElement("input"); | |
| input.type = "password"; | |
| input.id = "apiKeyInput"; | |
| input.placeholder = "Only needed for Stored Records / export / LLM engines"; | |
| input.autocomplete = "off"; | |
| input.style.minWidth = "220px"; | |
| const saveBtn = document.createElement("button"); | |
| saveBtn.type = "button"; | |
| saveBtn.className = "btn secondary"; | |
| saveBtn.textContent = "Save"; | |
| const statusLabel = document.createElement("span"); | |
| statusLabel.className = "sub small"; | |
| statusLabel.style.margin = "0"; | |
| statusLabel.textContent = "API Key set."; | |
| const editBtn = document.createElement("button"); | |
| editBtn.type = "button"; | |
| editBtn.className = "btn secondary"; | |
| editBtn.textContent = "Edit"; | |
| const savedNote = document.createElement("span"); | |
| savedNote.className = "sub small"; | |
| savedNote.style.marginLeft = "4px"; | |
| savedNote.hidden = true; | |
| savedNote.textContent = "Saved."; | |
| function showEditing() { | |
| // Deliberately NOT pre-filled with the current key: once saved, the key | |
| // is write-only from this UI (same principle as a password manager not | |
| // re-displaying a stored password) - "Edit" types a replacement, it does | |
| // not reveal/copy the existing one (the browser's own password-reveal | |
| // icon would otherwise hand it over in one click). | |
| input.value = ""; | |
| input.hidden = false; | |
| saveBtn.hidden = false; | |
| statusLabel.hidden = true; | |
| editBtn.hidden = true; | |
| input.focus(); | |
| } | |
| function showSaved() { | |
| // Clear the DOM value, not just hide the field - otherwise the key sits | |
| // readable in the input's `value` attribute (Inspect Element, or | |
| // `document.getElementById('apiKeyInput').value` in the console) even | |
| // while visually hidden. | |
| input.value = ""; | |
| input.hidden = true; | |
| saveBtn.hidden = true; | |
| statusLabel.hidden = false; | |
| editBtn.hidden = false; | |
| } | |
| function refresh() { | |
| if (getApiKey()) showSaved(); | |
| else showEditing(); | |
| } | |
| saveBtn.addEventListener("click", () => { | |
| const typed = input.value.trim(); | |
| // An empty box on Save is treated as "cancel", not "clear the key" - the | |
| // field no longer shows the existing key (see showEditing()), so an | |
| // empty submit is far more likely to be an accidental click than a | |
| // deliberate sign-out. To actually remove a saved key, type over it with | |
| // something else, or clear it from this browser's storage directly. | |
| if (typed) { | |
| setApiKey(typed); | |
| savedNote.hidden = false; | |
| setTimeout(() => { savedNote.hidden = true; }, 1500); | |
| if (typeof onSave === "function") onSave(getApiKey()); | |
| } | |
| refresh(); | |
| }); | |
| editBtn.addEventListener("click", showEditing); | |
| input.addEventListener("keydown", (e) => { | |
| if (e.key === "Enter") saveBtn.click(); | |
| }); | |
| wrap.appendChild(label); | |
| wrap.appendChild(input); | |
| wrap.appendChild(saveBtn); | |
| wrap.appendChild(statusLabel); | |
| wrap.appendChild(editBtn); | |
| wrap.appendChild(savedNote); | |
| containerEl.appendChild(wrap); | |
| refresh(); | |
| } | |