Source
This app’s code and revision history are public. Download a copy or explore the files below.
Public
The app’s code and revision history are public.
Run it yourself
Take the whole tree and run it on your own machine. The download needs no account. Cloning needs a sign-in.
Download .zip
bounded clone 3151231032497ed786b88a0a
A copy you download is not bounded. The guarantees this app runs under - declared egress, proven invariants, writes that fail closed - live in the runtime serving this page, not in the files. What you get is auditable source you can read and run, with no runtime guarantees attached.
src/Home.tsx
// The landing's working parts: the shortener in the hero, and "Your links" for returning owners.
import { useEffect, useRef, useState, type FormEvent } from "react";
import { me, explain, sdk } from "./lib/bounded";
import { navigate, usePath } from "./lib/router";
import { openAll, saveLink, savedLinks, forgetLink, moveUrl, toCsv, type Link } from "./lib/mine";
import { copy, share, canShare, toast, download, Menu, Icon } from "./ui";
import { QrPanel } from "./Qr";
import { SITE_URL } from "../functions/lib/config";
import { checkUrl, checkCode, randomCode, randomKey, shortUrl, editUrl } from "../functions/lib/links";
const HOST = SITE_URL.replace(/^https?:\/\//, "");
async function create(url: string, code: string): Promise<string> {
const uid = await me();
const { setMany, serverTimestamp } = await sdk();
const key = randomKey();
await setMany([
{ path: `links/${code}`, document: { url, createdBy: uid, createdAt: serverTimestamp(), clicks: 0, disabled: false } },
{ path: `links/${code}/owners/${uid}`, document: { at: serverTimestamp() } },
{ path: `linkkeys/${code}`, document: { key } },
]);
saveLink(code, key);
return key;
}
/** The rules refuse a new link at an ending someone already has. */
const refused = (err: unknown) => {
const e = err as { status?: number; decline?: unknown; message?: string };
return Boolean(e?.decline) || e?.status === 403 || e?.status === 409 || /denied|403|409|forbidden|not allowed|constraint|policy/i.test(String(e?.message ?? err));
};
type Problem = { field: "url" | "code" | "form"; text: string } | null;
/** An assistant can send /?url=...&code=... to open the shortener filled in. */
function prefill() {
const q = new URLSearchParams(location.search);
const filled = { url: q.get("url") ?? "", code: q.get("code") ?? "" };
if (q.has("url") || q.has("code")) history.replaceState(null, "", "/" + location.hash);
return filled;
}
export function CreateLink() {
const [start] = useState(prefill);
const [url, setUrl] = useState(start.url);
const [ending, setEnding] = useState(start.code);
const [busy, setBusy] = useState(false);
const [problem, setProblem] = useState<Problem>(null);
const [made, setMade] = useState<{ code: string; ownerKey: string; url: string } | null>(null);
const [again, setAgain] = useState(false);
const submit = async (e: FormEvent) => {
e.preventDefault();
if (busy) return;
const checked = checkUrl(url);
if ("error" in checked) return setProblem({ field: "url", text: checked.error });
let code = "";
if (ending.trim()) {
const c = checkCode(ending);
if ("error" in c) return setProblem({ field: "code", text: c.error });
code = c.code;
}
setBusy(true);
setProblem(null);
try {
let key: string;
if (code) {
try {
key = await create(checked.url, code);
} catch (err) {
if (refused(err)) return setProblem({ field: "code", text: `"${code}" is taken. Try another ending.` });
throw err;
}
} else {
code = randomCode();
key = await create(checked.url, code).catch((err) => {
if (!refused(err)) throw err;
return create(checked.url, (code = randomCode()));
});
}
setMade({ code, ownerKey: key, url: checked.url });
setUrl("");
setEnding("");
} catch (err) {
setProblem({ field: "form", text: explain(err) });
} finally {
setBusy(false);
}
};
if (made) return <Result {...made} onAgain={() => { setMade(null); setAgain(true); }} />;
const urlError = problem?.field === "url" ? problem.text : null;
const codeError = problem?.field === "code" ? problem.text : null;
return (
<form className={`create card${again ? " enter" : ""}`} onSubmit={submit} noValidate aria-label="Shorten a link">
<div className="field">
<label className="label" htmlFor="long-url">Long link</label>
<input id="long-url" className="input" type="url" inputMode="url" value={url} autoFocus={again}
onChange={(e) => { setUrl(e.target.value); if (urlError) setProblem(null); }} onFocus={() => me().catch(() => {})}
placeholder="https://example.com/a/very/long/page" autoComplete="off" autoCapitalize="off" spellCheck={false}
aria-invalid={urlError ? true : undefined} aria-describedby={urlError ? "long-url-error" : undefined} />
{urlError && <p className="field-error" id="long-url-error">{urlError}</p>}
</div>
<div className="field">
<label className="label" htmlFor="ending">Ending <span className="optional">optional</span></label>
<div className={`prefix-input${codeError ? " is-invalid" : ""}`}>
<span aria-hidden="true">{HOST}/</span>
<input id="ending" value={ending} onChange={(e) => { setEnding(e.target.value); if (codeError) setProblem(null); }} maxLength={32} placeholder="spring-menu"
autoComplete="off" autoCapitalize="off" spellCheck={false} aria-invalid={codeError ? true : undefined}
aria-describedby={codeError ? "ending-error" : "ending-hint"} />
</div>
{codeError ? <p className="field-error" id="ending-error">{codeError}</p> : <small id="ending-hint">Leave it empty for a random one.</small>}
</div>
{problem?.field === "form" && <p className="notice notice-error" role="alert">{problem.text}</p>}
<button className="btn btn-lg btn-block" type="submit" aria-busy={busy || undefined}><span>Shorten</span></button>
<p className="hint center">Free. No sign-up. Never expires.</p>
</form>
);
}
function Result({ code, ownerKey, url, onAgain }: { code: string; ownerKey: string; url: string; onAgain: () => void }) {
const short = shortUrl(code);
const edit = `/e/${code}#k=${ownerKey}`;
return (
<div className="result card enter">
<p className="eyebrow" role="status">Your short link is ready</p>
<a className="short-link" href={short} target="_blank" rel="noopener"><span className="short-host">{HOST}/</span><strong>{code}</strong></a>
<p className="dest">Opens <span title={url}>{url.replace(/^https?:\/\//, "")}</span></p>
<div className="result-actions">
<button className="btn" type="button" onClick={() => copy(short, "Short link copied")}>Copy link</button>
{canShare() && <button className="btn btn-line" type="button" onClick={() => share(short, "Short link copied")}><Icon name="share" />Share</button>}
</div>
<QrPanel code={code} />
<div className="edit-note">
<p><strong>Keep your private edit link.</strong> It lets you change where the link goes and see its clicks, on any device. Anyone with it can edit the link.</p>
<div className="edit-actions">
<button className="btn btn-sm btn-line" type="button" onClick={() => copy(editUrl(code, ownerKey), "Edit link copied. Keep it somewhere safe.")}>Copy edit link</button>
<a className="btn btn-sm btn-quiet" href={edit} onClick={(e) => { e.preventDefault(); navigate(edit); }}>Open edit page</a>
</div>
</div>
<button className="btn btn-line btn-block" type="button" onClick={onAgain}>Shorten another link</button>
</div>
);
}
const PAGE = 10;
export function YourLinks() {
const path = usePath();
const [saved, setSaved] = useState(savedLinks);
const [data, setData] = useState<Record<string, Link | null>>({});
const [query, setQuery] = useState("");
const [all, setAll] = useState(false);
const loaded = useRef(new Set<string>());
useEffect(() => {
const onSaved = () => setSaved(savedLinks());
window.addEventListener("links:saved", onSaved);
return () => window.removeEventListener("links:saved", onSaved);
}, []);
// Open links not yet shown; coming back from an edit page refreshes them all, keeping old values on screen meanwhile.
useEffect(() => {
if (path !== "/") return;
const todo = saved.filter((s) => !loaded.current.has(s.code));
todo.forEach((s) => loaded.current.add(s.code));
openAll(todo, (code, link) => setData((d) => ({ ...d, [code]: link })));
}, [saved, path]);
useEffect(() => {
if (path === "/") return;
loaded.current.clear();
}, [path]);
if (!saved.length) return null;
const q = query.trim().toLowerCase();
const matches = q ? saved.filter((s) => s.code.toLowerCase().includes(q) || data[s.code]?.url.toLowerCase().includes(q)) : saved;
const shown = q || all ? matches : matches.slice(0, PAGE);
const ready = saved.every((s) => s.code in data);
const forget = (code: string) => {
const undo = forgetLink(code);
toast(`Removed ${HOST}/${code} from this browser. The link still works.`, { label: "Undo", run: undo });
};
const exportCsv = () => {
if (!ready) return toast("Still opening your links. Try again in a moment.");
download(new Blob([toCsv(saved, data)], { type: "text/csv;charset=utf-8" }), "links.csv");
};
return (
<div className="section-tight">
<div className="wrap">
<div className="mine-head">
<h2 className="h2">Your links</h2>
<span className="chip">{saved.length}</span>
</div>
{saved.length > 5 && (
<div className="find">
<Icon name="search" />
<label className="sr-only" htmlFor="find-link">Find a link</label>
<input id="find-link" className="input" type="search" value={query} onChange={(e) => setQuery(e.target.value)}
placeholder="Find by ending or address" autoComplete="off" autoCapitalize="off" spellCheck={false} />
</div>
)}
{shown.length ? (
<ul className="link-list">
{shown.map((s, i) => <LinkRow key={s.code} i={i} code={s.code} ownerKey={s.key} link={data[s.code]} onForget={() => forget(s.code)} />)}
</ul>
) : (
<p className="muted find-none">No links match "{query.trim()}".</p>
)}
{!q && !all && saved.length > PAGE && (
<button className="btn btn-quiet more" type="button" onClick={() => setAll(true)}>Show all {saved.length} links</button>
)}
<div className="mine-foot">
<p className="muted small">Saved in this browser only. Move them to your other devices, or keep a copy as a spreadsheet.</p>
<div className="row">
<button className="btn btn-sm btn-line" type="button"
onClick={() => copy(moveUrl(saved), `Link copied. Open it on your other device to add ${saved.length === 1 ? "this link" : `these ${saved.length} links`}. Anyone with it can edit them.`)}>
Copy link for another device
</button>
<button className="btn btn-sm btn-line" type="button" onClick={exportCsv}>Download CSV</button>
</div>
</div>
</div>
</div>
);
}
function LinkRow({ code, ownerKey, link, i, onForget }: { code: string; ownerKey: string; link: Link | null | undefined; i: number; onForget: () => void }) {
const edit = `/e/${code}#k=${ownerKey}`;
const short = shortUrl(code);
return (
<li className="link-row enter" style={{ ["--i" as string]: i }}>
<div className="link-main">
<a className="link-short" href={edit} onClick={(e) => { e.preventDefault(); navigate(edit); }}><span className="muted">{HOST}</span>/{code}</a>
{link === undefined ? <span className="skeleton skeleton-text link-skel" aria-hidden="true" />
: <span className="link-dest">{link === null ? "Could not open this link." : link.url.replace(/^https?:\/\//, "")}</span>}
</div>
<span className="link-clicks">
{link ? <>{link.disabled && <span className="chip chip-off">Off</span>}<strong>{link.clicks.toLocaleString("en-US")}</strong> {link.clicks === 1 ? "click" : "clicks"}</> : null}
</span>
<span className="link-actions">
<button className="btn btn-sm btn-line" type="button" onClick={() => copy(short, "Short link copied")} aria-label={`Copy ${HOST}/${code}`}>Copy</button>
<a className="btn btn-sm btn-line link-edit" href={edit} onClick={(e) => { e.preventDefault(); navigate(edit); }} aria-label={`Edit ${HOST}/${code}`}>Edit</a>
<Menu label={`More for ${HOST}/${code}`}>
<a className="phone-only" href={edit} onClick={(e) => { e.preventDefault(); navigate(edit); }}>Edit, clicks and QR code</a>
<a href={short} target="_blank" rel="noopener">Open the short link</a>
{canShare() && <button type="button" onClick={() => share(short, "Short link copied")}>Share</button>}
<button type="button" onClick={() => copy(editUrl(code, ownerKey), "Edit link copied. Keep it somewhere safe.")}>Copy edit link</button>
<hr />
<button type="button" className="danger" onClick={onForget}>Remove from this browser</button>
</Menu>
</span>
</li>
);
}
Change history
Quality pass: clicks per day, CSV export, links on other devices
Bounded source sync
Bounded source sync
Bounded source sync
Bounded source sync
Bounded source sync
Bounded source sync
Bounded source sync