links-free.openapps.xyz

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.
Owned app · rev 9e2af813edac
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.
← All files
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
9e2af813ed · 5 hours ago
Bounded source sync
29eee7dafa · 1 day ago
Bounded source sync
c5e634046a · 1 day ago
Bounded source sync
12abf93e7e · 1 day ago
Bounded source sync
d0d6a654b1 · 1 day ago
Bounded source sync
e03049f905 · 1 day ago
Bounded source sync
eb5c793bfa · 1 day ago
Bounded source sync
5dd8ec26e1 · 1 day ago