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
functions/api.ts
// Links' public HTTP API: no account. Anyone can shorten a link; the owner key that comes back
// is what lets you read clicks and change the link later. Every route answers JSON except the
// QR code image; GET / describes the routes. Writes go through the same policy rules as the web app.
import { SITE_URL, API_BASE } from "./lib/config";
import { checkUrl, checkCode, isSafeDestination, randomCode, randomKey, shortUrl, editUrl, dayNumber, dayIso } from "./lib/links";
import { qrMatrix, qrSvg, isHexColor, scansWell } from "./lib/qr";

const NOW = { operation: "time", value: "now" };
const PLUS_ONE = { operation: "increment", value: 1 };
const CODE_RE = /^[A-Za-z0-9_-]{3,32}$/;

const cors = {
  "access-control-allow-origin": "*",
  "access-control-allow-methods": "GET, POST, PATCH, OPTIONS",
  "access-control-allow-headers": "content-type, x-owner-key",
};
const json = (body: unknown, status = 200) =>
  new Response(JSON.stringify(body, null, 2), { status, headers: { "content-type": "application/json; charset=utf-8", ...cors } });
const fail = (status: number, error: string) => json({ error }, status);

class HttpError extends Error { constructor(public status: number, message: string) { super(message); } }

async function body(req: Request): Promise<Record<string, any>> {
  try {
    const parsed = await req.json();
    if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) return parsed;
  } catch { /* fall through */ }
  throw new HttpError(400, "Send a JSON object body.");
}

interface Link { url: string; createdAt: number; clicks: number; disabled: boolean }

async function load(ctx: any, code: string): Promise<Link> {
  const link = CODE_RE.test(code) ? await ctx.bounded.get(`links/${code}`) : null;
  if (!link) throw new HttpError(404, "This link does not exist.");
  return link;
}

/** Every API caller shares one identity, so owner actions always check the key first. */
async function ownerKey(ctx: any, req: Request, code: string): Promise<string> {
  const key = req.headers.get("x-owner-key")?.trim() || "";
  if (!key) throw new HttpError(401, "Send the link's owner key in the x-owner-key header.");
  const stored = await ctx.bounded.get(`linkkeys/${code}`);
  if (!stored || stored.key !== key) throw new HttpError(403, "That owner key does not match this link.");
  return key;
}

const qrUrl = (code: string) => `${API_BASE}/qr/${code}.svg`;
const DAYS = 90;

/** Opens per UTC day over the last 90 days, oldest first; days with no opens are left out. */
async function clicksByDay(ctx: any, code: string): Promise<{ date: string; clicks: number }[]> {
  const since = dayNumber(new Date(Date.now() - (DAYS - 1) * 86400000));
  const res = await ctx.bounded.get(`links/${code}/days`, { filter: { d: { $gte: since } }, limit: DAYS + 1 });
  return ((res?.data || []) as { d: number; clicks: number }[])
    .sort((a, b) => a.d - b.d)
    .map((r) => ({ date: dayIso(r.d), clicks: r.clicks }));
}

function describe(code: string, link: Link, extra: Record<string, unknown> = {}) {
  return {
    code,
    shortUrl: shortUrl(code),
    url: link.url,
    clicks: link.clicks,
    disabled: link.disabled,
    createdAt: new Date(link.createdAt * 1000).toISOString(),
    qrSvg: qrUrl(code),
    ...extra,
  };
}

const routes = {
  name: "Links by OpenApps",
  about: "Free short links and QR codes that never expire. No account and no key to create a link; the owner key you get back lets you read clicks and change the link.",
  docs: `${SITE_URL}/llms.txt`,
  openapi: `${SITE_URL}/openapi.json`,
  routes: [
    "POST /links {url, code?} -> the short link, its QR code, a private edit link and the owner key",
    "GET /go/:code -> {url}: where a short link goes (counts one click)",
    "GET /links/:code with header x-owner-key -> url, clicks, clicksByDay (last 90 days), disabled, createdAt",
    "PATCH /links/:code with header x-owner-key {url?, disabled?} -> the updated link",
    "GET /qr/:code.svg?color=2e5ec4 -> the QR code as an SVG image",
  ],
};

export default async function api(req: Request, ctx: any) {
  if (req.method === "OPTIONS") return new Response(null, { status: 204, headers: cors });
  const url = new URL(req.url);
  const parts = url.pathname.split("/").filter(Boolean);
  // Accept both /links/... and /api/links/... so a copied path works either way.
  if (parts[0] === "api") parts.shift();
  try {
    if (!parts.length && req.method === "GET") return json(routes);
    const me = ctx.user.id;

    if (parts[0] === "go" && parts.length === 2 && req.method === "GET") {
      const link = await load(ctx, parts[1]);
      if (link.disabled) return fail(410, "This link was turned off by its owner.");
      if (!isSafeDestination(link.url)) return fail(410, "This link cannot be opened.");
      const day = dayNumber(new Date());
      try {
        await ctx.bounded.setMany([
          { path: `links/${parts[1]}`, document: { clicks: PLUS_ONE } },
          { path: `links/${parts[1]}/days/${day}`, document: { d: day, clicks: PLUS_ONE } },
        ]);
      } catch { /* a missed count never blocks the visit */ }
      return json({ url: link.url });
    }

    if (parts[0] === "qr" && parts.length === 2 && req.method === "GET") {
      const code = parts[1].replace(/\.svg$/, "");
      await load(ctx, code);
      const color = `#${(url.searchParams.get("color") || "111111").replace(/^#/, "")}`;
      if (!isHexColor(color)) return fail(400, "color must be a hex color like 2e5ec4.");
      if (!scansWell(color)) return fail(400, "That color is too light for phones to scan. Pick a darker one.");
      return new Response(qrSvg(qrMatrix(shortUrl(code)), color), {
        headers: { "content-type": "image/svg+xml; charset=utf-8", "cache-control": "public, max-age=86400", ...cors },
      });
    }

    if (parts[0] !== "links") return fail(404, "Unknown route. GET / lists the routes.");

    if (parts.length === 1 && req.method === "POST") {
      const input = await body(req);
      const checked = checkUrl(input.url);
      if ("error" in checked) return fail(400, checked.error);
      let code: string;
      if (input.code != null && String(input.code).trim() !== "") {
        const c = checkCode(input.code);
        if ("error" in c) return fail(400, c.error);
        if (await ctx.bounded.get(`links/${c.code}`)) return fail(409, `"${c.code}" is taken. Try another ending.`);
        code = c.code;
      } else {
        do code = randomCode(); while (await ctx.bounded.get(`links/${code}`));
      }
      const key = randomKey();
      try {
        await ctx.bounded.setMany([
          { path: `links/${code}`, document: { url: checked.url, createdBy: me, createdAt: NOW, clicks: 0, disabled: false } },
          { path: `links/${code}/owners/${me}`, document: { at: NOW } },
          { path: `linkkeys/${code}`, document: { key } },
        ]);
      } catch {
        if (await ctx.bounded.get(`links/${code}`)) return fail(409, `"${code}" is taken. Try another ending.`);
        throw new Error("The link could not be saved. Try again.");
      }
      return json(describe(code, { url: checked.url, createdAt: Math.floor(Date.now() / 1000), clicks: 0, disabled: false }, { editUrl: editUrl(code, key), ownerKey: key }), 201);
    }

    const code = parts[1];
    if (parts.length !== 2 || !code) return fail(404, "Unknown route. GET / lists the routes.");

    if (req.method === "GET") {
      const link = await load(ctx, code);
      await ownerKey(ctx, req, code);
      return json(describe(code, link, { clicksByDay: await clicksByDay(ctx, code) }));
    }

    if (req.method === "PATCH") {
      const link = await load(ctx, code);
      const key = await ownerKey(ctx, req, code);
      const input = await body(req);
      const patch: Partial<Link> = {};
      if (input.url != null) {
        const checked = checkUrl(input.url);
        if ("error" in checked) return fail(400, checked.error);
        patch.url = checked.url;
      }
      if (input.disabled != null) {
        if (typeof input.disabled !== "boolean") return fail(400, "disabled must be true or false.");
        patch.disabled = input.disabled;
      }
      if (!Object.keys(patch).length) return fail(400, "Send url, disabled, or both.");
      // Become an owner with the key (once), then change the link as one.
      if (!(await ctx.bounded.get(`links/${code}/owners/${me}`))) {
        await ctx.bounded.setMany([
          { path: `linkclaims/${code}/c/${me}`, document: { key } },
          { path: `links/${code}/owners/${me}`, document: { at: NOW } },
        ]);
      }
      await ctx.bounded.set(`links/${code}`, patch);
      return json(describe(code, { ...link, ...patch }));
    }

    return fail(404, "Unknown route. GET / lists the routes.");
  } catch (err) {
    if (err instanceof HttpError) return fail(err.status, err.message);
    const message = err instanceof Error ? err.message : String(err);
    return fail(500, `Something went wrong: ${message}`);
  }
}

Change history

Quality pass: clicks per day, CSV export, links on other devices
9e2af813ed · 4 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