Spaces:
Running
Running
Commit Β·
6113d21
1
Parent(s): 5ce4ce7
Implement private space redirect functionality
Browse filesAdd support for private space redirection in HuggingClaw.
- health-server.js +72 -1
health-server.js
CHANGED
|
@@ -37,6 +37,21 @@ const DEVDATA_SEPARATE_DATASET = DEVDATA_DATASET_NAME !== BACKUP_DATASET_NAME;
|
|
| 37 |
const DEVDATA_ENABLED = JUPYTER_ENABLED && HF_BACKUP_ENABLED && DEVDATA_SEPARATE_DATASET && !/^(off|false|0|no)$/i.test((process.env.DEVDATA || "on").trim());
|
| 38 |
const APP_BASE = normalizeBase(process.env.APP_BASE, "/app");
|
| 39 |
const SYNC_STATUS_FILE = "/tmp/sync-status.json";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
const CLOUDFLARE_KEEPALIVE_STATUS_FILE =
|
| 41 |
"/tmp/huggingclaw-cloudflare-keepalive-status.json";
|
| 42 |
|
|
@@ -176,12 +191,24 @@ function renderDashboard(data) {
|
|
| 176 |
<a class="hero-action env" data-space-link="env-builder" href="/env-builder">βοΈ Env Builder β</a>
|
| 177 |
</div>
|
| 178 |
<section class="overview">${tilesHtml}</section>
|
| 179 |
-
<footer>Built by <a href="https://github.com/somratpro" target="_blank" rel="noopener noreferrer" style="color:inherit;text-decoration:none">@somratpro</a>${JUPYTER_ENABLED ? " Β· Terminal by JupyterLab" : ""}<br><span>Public Spaces
|
| 180 |
</main>
|
| 181 |
<script>
|
| 182 |
document.querySelectorAll('.local-time').forEach(el=>{const d=new Date(el.getAttribute('data-iso'));if(!isNaN(d))el.textContent='At '+d.toLocaleTimeString()});
|
| 183 |
const inEmbeddedApp = (() => { try { return window.top !== window.self; } catch { return true; } })();
|
| 184 |
const isDirectHfSpaceHost = /\.hf\.space$/i.test(window.location.hostname);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 185 |
// If inside the HF App iframe, force new-tab navigation so users can break out
|
| 186 |
// to the standalone Space host. Also keep direct .hf.space behavior opening new tabs.
|
| 187 |
const openInNewTab = inEmbeddedApp || isDirectHfSpaceHost;
|
|
@@ -198,6 +225,39 @@ function renderDashboard(data) {
|
|
| 198 |
</body></html>`;
|
| 199 |
}
|
| 200 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 201 |
function renderEnvBuilder() {
|
| 202 |
try {
|
| 203 |
return fs.readFileSync(require("path").join(__dirname, "env-builder.html"), "utf8");
|
|
@@ -308,6 +368,17 @@ const server = http.createServer(async (req, res) => {
|
|
| 308 |
return res.end(JSON.stringify({ model: LLM_MODEL, uptime: formatUptime(Date.now() - startTime), gatewayReady, jupyterReady, sync: getSyncStatus(), whatsapp: readGuardianStatus(), keepalive: getKeepaliveStatus() }));
|
| 309 |
}
|
| 310 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 311 |
if (pathname === "/env-builder" || pathname === "/env-builder/") {
|
| 312 |
res.writeHead(200, { "Content-Type": "text/html" });
|
| 313 |
return res.end(renderEnvBuilder());
|
|
|
|
| 37 |
const DEVDATA_ENABLED = JUPYTER_ENABLED && HF_BACKUP_ENABLED && DEVDATA_SEPARATE_DATASET && !/^(off|false|0|no)$/i.test((process.env.DEVDATA || "on").trim());
|
| 38 |
const APP_BASE = normalizeBase(process.env.APP_BASE, "/app");
|
| 39 |
const SYNC_STATUS_FILE = "/tmp/sync-status.json";
|
| 40 |
+
|
| 41 |
+
// ββ Private Space redirect support ββ
|
| 42 |
+
// HF automatically sets SPACE_ID as "username/spacename" in every Space container.
|
| 43 |
+
const SPACE_ID = (process.env.SPACE_ID || "").trim();
|
| 44 |
+
function deriveHfSpaceUrl() {
|
| 45 |
+
if (SPACE_ID) return `https://huggingface.co/spaces/${SPACE_ID}`;
|
| 46 |
+
const host = (process.env.SPACE_HOST || "").replace(/\.hf\.space$/i, "");
|
| 47 |
+
const author = (process.env.SPACE_AUTHOR_NAME || "").trim().toLowerCase();
|
| 48 |
+
if (author && host.toLowerCase().startsWith(author + "-")) {
|
| 49 |
+
const spaceName = host.slice(author.length + 1);
|
| 50 |
+
return `https://huggingface.co/spaces/${process.env.SPACE_AUTHOR_NAME}/${spaceName}`;
|
| 51 |
+
}
|
| 52 |
+
return "";
|
| 53 |
+
}
|
| 54 |
+
const HF_SPACE_URL = deriveHfSpaceUrl();
|
| 55 |
const CLOUDFLARE_KEEPALIVE_STATUS_FILE =
|
| 56 |
"/tmp/huggingclaw-cloudflare-keepalive-status.json";
|
| 57 |
|
|
|
|
| 191 |
<a class="hero-action env" data-space-link="env-builder" href="/env-builder">βοΈ Env Builder β</a>
|
| 192 |
</div>
|
| 193 |
<section class="overview">${tilesHtml}</section>
|
| 194 |
+
<footer>Built by <a href="https://github.com/somratpro" target="_blank" rel="noopener noreferrer" style="color:inherit;text-decoration:none">@somratpro</a>${JUPYTER_ENABLED ? " Β· Terminal by JupyterLab" : ""}<br><span>Public Spaces open via <code>.hf.space</code> directly. Private Spaces require the <a href="${HF_SPACE_URL || "#"}" target="_blank" rel="noopener noreferrer" style="color:inherit">Hugging Face App tab</a> for the authenticated session${HF_SPACE_URL ? ` β or share <code>huggingface.co/spaces/${SPACE_ID}</code>` : ""}.</span></footer>
|
| 195 |
</main>
|
| 196 |
<script>
|
| 197 |
document.querySelectorAll('.local-time').forEach(el=>{const d=new Date(el.getAttribute('data-iso'));if(!isNaN(d))el.textContent='At '+d.toLocaleTimeString()});
|
| 198 |
const inEmbeddedApp = (() => { try { return window.top !== window.self; } catch { return true; } })();
|
| 199 |
const isDirectHfSpaceHost = /\.hf\.space$/i.test(window.location.hostname);
|
| 200 |
+
const HF_SPACE_URL = ${JSON.stringify(HF_SPACE_URL)};
|
| 201 |
+
// ββ Private Space Guard ββ
|
| 202 |
+
// Direct .hf.space access outside the HF App iframe has no valid session cookie
|
| 203 |
+
// for private spaces β HF CDN returns 404 before the request reaches the container.
|
| 204 |
+
// Redirect users to huggingface.co/spaces/... which authenticates them properly.
|
| 205 |
+
if (isDirectHfSpaceHost && !inEmbeddedApp && HF_SPACE_URL) {
|
| 206 |
+
const notice = document.createElement('div');
|
| 207 |
+
notice.style.cssText = 'position:fixed;inset:0;display:flex;align-items:center;justify-content:center;background:#08080f;color:#f6f4ff;font-family:sans-serif;flex-direction:column;gap:16px;z-index:9999';
|
| 208 |
+
notice.innerHTML = '<span style="font-size:1.1rem">π Private Space — Redirecting…</span><a href="' + HF_SPACE_URL + '" style="color:#a5b4fc;font-size:.85rem">Click here if not redirected</a>';
|
| 209 |
+
document.body.appendChild(notice);
|
| 210 |
+
setTimeout(() => { window.location.replace(HF_SPACE_URL); }, 300);
|
| 211 |
+
}
|
| 212 |
// If inside the HF App iframe, force new-tab navigation so users can break out
|
| 213 |
// to the standalone Space host. Also keep direct .hf.space behavior opening new tabs.
|
| 214 |
const openInNewTab = inEmbeddedApp || isDirectHfSpaceHost;
|
|
|
|
| 225 |
</body></html>`;
|
| 226 |
}
|
| 227 |
|
| 228 |
+
function renderPrivateRedirect(targetUrl) {
|
| 229 |
+
const safeUrl = escapeHtml(targetUrl);
|
| 230 |
+
return `<!doctype html><html lang="en"><head>
|
| 231 |
+
<meta charset="utf-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/>
|
| 232 |
+
<meta http-equiv="refresh" content="3;url=${safeUrl}"/>
|
| 233 |
+
<title>HuggingClaw β Private Space</title>
|
| 234 |
+
<style>
|
| 235 |
+
:root{color-scheme:dark}
|
| 236 |
+
body{margin:0;min-height:100vh;display:flex;align-items:center;justify-content:center;
|
| 237 |
+
font-family:Inter,ui-sans-serif,system-ui,-apple-system,sans-serif;
|
| 238 |
+
background:#08080f;color:#f6f4ff;text-align:center;padding:24px}
|
| 239 |
+
.card{border:1px solid #26243a;background:#12111b;border-radius:14px;padding:36px 32px;max-width:440px}
|
| 240 |
+
h1{margin:0 0 12px;font-size:1.5rem}
|
| 241 |
+
p{color:#b8b3d7;line-height:1.6;margin:0 0 24px}
|
| 242 |
+
.btn{display:inline-flex;align-items:center;justify-content:center;
|
| 243 |
+
background:#fff;color:#000;font-weight:850;font-size:.95rem;
|
| 244 |
+
border-radius:8px;padding:12px 28px;text-decoration:none;transition:opacity .15s}
|
| 245 |
+
.btn:hover{opacity:.85}
|
| 246 |
+
.sub{color:#7f7a9e;font-size:.78rem;margin-top:16px}
|
| 247 |
+
</style></head><body>
|
| 248 |
+
<div class="card">
|
| 249 |
+
<h1>π Private Space</h1>
|
| 250 |
+
<p>This HuggingFace Space is private. You need to be logged in to <strong>huggingface.co</strong> to access it.<br><br>Redirecting you now…</p>
|
| 251 |
+
<a class="btn" href="${safeUrl}">Open on Hugging Face β</a>
|
| 252 |
+
<div class="sub">Redirecting in 3 seconds…</div>
|
| 253 |
+
</div>
|
| 254 |
+
<script>
|
| 255 |
+
// Immediate redirect if JS available β don't wait for meta refresh
|
| 256 |
+
setTimeout(() => { window.location.replace(${JSON.stringify(targetUrl)}); }, 100);
|
| 257 |
+
</script>
|
| 258 |
+
</body></html>`;
|
| 259 |
+
}
|
| 260 |
+
|
| 261 |
function renderEnvBuilder() {
|
| 262 |
try {
|
| 263 |
return fs.readFileSync(require("path").join(__dirname, "env-builder.html"), "utf8");
|
|
|
|
| 368 |
return res.end(JSON.stringify({ model: LLM_MODEL, uptime: formatUptime(Date.now() - startTime), gatewayReady, jupyterReady, sync: getSyncStatus(), whatsapp: readGuardianStatus(), keepalive: getKeepaliveStatus() }));
|
| 369 |
}
|
| 370 |
|
| 371 |
+
// Private space redirect β send users to the authenticated HF Spaces page.
|
| 372 |
+
// Works for both direct .hf.space links AND programmatic shares.
|
| 373 |
+
if (pathname === "/hf-redirect" || pathname === "/hf-redirect/") {
|
| 374 |
+
if (HF_SPACE_URL) {
|
| 375 |
+
res.writeHead(302, { Location: HF_SPACE_URL, "Cache-Control": "no-store" });
|
| 376 |
+
return res.end();
|
| 377 |
+
}
|
| 378 |
+
res.writeHead(404, { "Content-Type": "text/plain" });
|
| 379 |
+
return res.end("SPACE_ID not configured.");
|
| 380 |
+
}
|
| 381 |
+
|
| 382 |
if (pathname === "/env-builder" || pathname === "/env-builder/") {
|
| 383 |
res.writeHead(200, { "Content-Type": "text/html" });
|
| 384 |
return res.end(renderEnvBuilder());
|