flow / src /lib /deepLink.ts
mfn
Share: permanent links (?w=) that open as your own copy; sign in with Hugging Face to share
aab2748
Raw History Blame Contribute Delete
3.55 kB
/**
* `?space=owner/name[&fn=/endpoint]` deep link ("Open in Flow" badges). The Space is read on load, kept in
* sessionStorage so it survives the sign-in redirect, and added once the editor is up.
* Kept free of app imports: main.tsx and hfAuth use it before the editor loads.
*/
const KEY = "flow-pending-space";
const FN_KEY = "flow-pending-fn";
const FN = /^\/?[\w.-]{1,80}$/;
const ID = /^[\w.-]+\/[\w.-]+$/;
const SUB = /^[a-z0-9-]+$/;
/** `owner/name`, or `sub:<subdomain>` for a *.hf.space URL (resolved later from the Space's config). */
export function normaliseSpace(raw: string): string | null {
const s = raw.trim();
if (ID.test(s)) return s;
let u: URL;
try {
u = new URL(s);
} catch {
return null;
}
if (u.protocol !== "https:" && u.protocol !== "http:") return null;
if (u.hostname === "huggingface.co" || u.hostname === "www.huggingface.co") {
const m = u.pathname.match(/^\/spaces\/([^/]+)\/([^/]+)/);
const id = m ? `${m[1]}/${m[2]}` : "";
return ID.test(id) ? id : null;
}
if (u.hostname.endsWith(".hf.space")) {
const sub = u.hostname.split(".")[0];
return SUB.test(sub) ? `sub:${sub}` : null;
}
return null;
}
/** Reads `?space=` into the stash and drops it from the address bar so a refresh doesn't add it twice. */
export function stashSpaceParam() {
const q = new URLSearchParams(location.search);
if (!q.has("space")) return;
const v = normaliseSpace(q.get("space") ?? "");
const fn = (q.get("fn") ?? "").trim();
q.delete("space");
q.delete("fn");
const s = q.toString();
history.replaceState(history.state, "", `${location.pathname}${s ? `?${s}` : ""}${location.hash}`);
if (!v) return;
try {
sessionStorage.setItem(KEY, v);
if (FN.test(fn)) sessionStorage.setItem(FN_KEY, fn.replace(/^\//, ""));
else sessionStorage.removeItem(FN_KEY);
} catch {
/* storage blocked */
}
}
/** The endpoint asked for with `&fn=`, without the leading slash. */
export function pendingFn(): string | null {
try {
return sessionStorage.getItem(FN_KEY);
} catch {
return null;
}
}
export function pendingSpace(): string | null {
try {
return sessionStorage.getItem(KEY);
} catch {
return null;
}
}
/** Returns the pending Space (and endpoint, if one was asked for) and forgets them. */
export function takePendingSpace(): { id: string; fn: string | null } | null {
const v = pendingSpace();
const fn = pendingFn();
try {
sessionStorage.removeItem(KEY);
sessionStorage.removeItem(FN_KEY);
} catch {
/* storage blocked */
}
return v ? { id: v, fn } : null;
}
/* `?w=<id>` share link: same idea, kept until the editor is up and the viewer has said yes. */
const SHARE_KEY = "flow-pending-share";
const SHARE = /^[A-Za-z0-9]{10}$/;
/** Reads `?w=` into the stash and drops it from the address bar. */
export function stashShareParam() {
const q = new URLSearchParams(location.search);
if (!q.has("w")) return;
const v = (q.get("w") ?? "").trim();
q.delete("w");
const s = q.toString();
history.replaceState(history.state, "", `${location.pathname}${s ? `?${s}` : ""}${location.hash}`);
if (!SHARE.test(v)) return;
try {
sessionStorage.setItem(SHARE_KEY, v);
} catch {
/* storage blocked */
}
}
export function pendingShare(): string | null {
try {
return sessionStorage.getItem(SHARE_KEY);
} catch {
return null;
}
}
export function clearPendingShare() {
try {
sessionStorage.removeItem(SHARE_KEY);
} catch {
/* storage blocked */
}
}