paste-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 01fdb2b1f66e
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 6eca480d6447d873e2e5eed9
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/View.tsx
// A paste's page (/<id>, or /<id>#<key> when encrypted with a key in the link). It reads through the public API,
// so opening a paste needs no account and loads no SDK. A burn-after-reading paste asks before it is opened,
// and a paste locked with a password asks for it.
import { lazy, Suspense, useEffect, useState, type FormEvent } from "react";
import { SERVED_API } from "./lib/app";
import { navigate } from "./lib/router";
import { open as unseal, openWithPassword } from "./lib/crypto";
import { savedPaste } from "./lib/mine";
import { copy, share, canShare, toast, Menu, Icon } from "./ui";
import { CodeView } from "./Code";
import { PasteSkeleton } from "./Skeletons";
import { forkIntoEditor } from "./Editor";
import { languageOf, formatBytes, byteLength, PASSWORD_PREFIX } from "../functions/lib/paste";
import { adminPath, fromNow, shareUrl } from "./lib/format";

const Markdown = lazy(() => import("./Markdown"));

interface ApiPaste {
  id: string; url: string; rawUrl: string; title: string; language: string; size: number;
  encrypted: boolean; password?: boolean; burn: boolean; createdAt: string; expiresAt: string | null; content?: string; deleted?: boolean;
}
interface Shown extends ApiPaste { content: string }

type State =
  | { kind: "loading" }
  | { kind: "gone" }
  | { kind: "error"; message: string }
  | { kind: "confirm"; paste: ApiPaste }
  | { kind: "locked"; paste: Shown }
  | { kind: "nokey" }
  | { kind: "badkey" }
  | { kind: "ready"; paste: Shown };

async function fetchPaste(id: string, peek: boolean): Promise<ApiPaste | null> {
  const res = await fetch(`${SERVED_API}/pastes/${id}${peek ? "?peek=1" : ""}`);
  if (res.status === 404) return null;
  const body = await res.json().catch(() => ({}));
  if (!res.ok) throw new Error(body.error || "This paste could not be opened. Try again.");
  return body as ApiPaste;
}

const hasPassword = (p: ApiPaste) => p.password ?? Boolean(p.encrypted && p.content?.startsWith(PASSWORD_PREFIX));
const isLineHash = (hash: string) => /^#L\d+$/.test(hash);
/** The key of a paste encrypted with a key in the link: everything after the #, unless it is a line link. */
const linkSecret = () => (isLineHash(location.hash) ? "" : location.hash.slice(1));
/** fetch fails with a TypeError when the network or the server cannot be reached. */
const offline = () => "Paste could not be reached. Check your connection, then try again.";

export function ViewPage({ id }: { id: string }) {
  const [state, setState] = useState<State>({ kind: "loading" });

  const show = async (p: ApiPaste) => {
    const paste: Shown = { ...p, password: hasPassword(p), content: p.content ?? "" };
    if (paste.password) return setState({ kind: "locked", paste });
    if (p.encrypted) {
      const secret = linkSecret();
      if (!secret) return setState({ kind: "nokey" });
      try {
        return setState({ kind: "ready", paste: { ...paste, ...(await unseal(paste.content, secret)) } });
      } catch {
        return setState({ kind: "badkey" });
      }
    }
    setState({ kind: "ready", paste });
  };

  const load = (peek: boolean) => {
    if (peek) setState({ kind: "loading" });
    return fetchPaste(id, peek).then(
      (p) => {
        if (!p) return setState({ kind: "gone" });
        // An encrypted burn paste opened without its key would be deleted unread, so stop before opening it.
        if (p.burn && p.content == null) return setState(p.encrypted && !hasPassword(p) && !linkSecret() ? { kind: "nokey" } : { kind: "confirm", paste: p });
        return show(p);
      },
      (err) => setState({ kind: "error", message: !navigator.onLine || err instanceof TypeError ? offline() : (err as Error).message }),
    );
  };

  useEffect(() => {
    load(true);
    // Pasting the full link (with its #key) into a tab already on this paste changes only the hash. Line links do not reload.
    const onHash = () => { if (!isLineHash(location.hash)) load(true); };
    window.addEventListener("hashchange", onHash);
    return () => window.removeEventListener("hashchange", onHash);
  }, [id]);
  useEffect(() => {
    const title = state.kind === "ready" ? state.paste.title : "";
    document.title = `${title || "Paste"} | Paste`;
  }, [state]);

  if (state.kind === "loading") return <PasteSkeleton />;
  if (state.kind === "error") return (
    <Message title="This paste could not be opened.">
      <p className="lede">{state.message}</p>
      <button className="btn" type="button" onClick={() => load(true)}>Try again</button>
    </Message>
  );
  if (state.kind === "gone") return (
    <Message title="This paste is gone." art>
      <p className="lede">It expired, its owner deleted it, or it was set to delete after the first view and someone opened it. Or the link was cut short: check that you copied all of it.</p>
      <HomeLink />
    </Message>
  );
  if (state.kind === "nokey") return (
    <Message title="This link is missing its key.">
      <p className="lede">This paste is encrypted. Its key is the last part of the full link, after the # sign. Open the complete link you were sent.</p>
      <HomeLink />
    </Message>
  );
  if (state.kind === "badkey") return (
    <Message title="This paste could not be unlocked.">
      <p className="lede">The key at the end of the link does not open it. Check that you copied the whole link, including everything after the # sign.</p>
      <HomeLink />
    </Message>
  );
  if (state.kind === "confirm") return <Confirm id={id} paste={state.paste} onOpen={() => load(false)} />;
  if (state.kind === "locked") return <Unlock paste={state.paste} onOpen={(paste) => setState({ kind: "ready", paste })} />;
  return <PasteView id={id} paste={state.paste} />;
}

function Message({ title, art, children }: { title: string; art?: boolean; children: React.ReactNode }) {
  return (
    <div className="wrap page-pad narrow message enter">
      {art && <img className="art message-art" src="/art/card-paste.webp" alt="" width="720" height="511" />}
      <h1 className="h1">{title}</h1>
      {children}
    </div>
  );
}

const HomeLink = () => (
  <a className="btn" href="/" onClick={(e) => { e.preventDefault(); navigate("/"); }}>Make a paste</a>
);

function Confirm({ id, paste, onOpen }: { id: string; paste: ApiPaste; onOpen: () => Promise<void> }) {
  const mine = savedPaste(id);
  const [busy, setBusy] = useState(false);
  const open = () => { setBusy(true); onOpen().finally(() => setBusy(false)); };
  return (
    <Message title={paste.title || "This paste deletes itself after one view."}>
      {paste.title && <p className="eyebrow">Deletes itself after one view</p>}
      <p className="lede">
        {mine
          ? "You made this paste. If you open it here, it is deleted, and the link stops working for the person you meant it for."
          : `Once you open it, it is deleted from the server and this link stops working for everyone. Copy or download it while it is on your screen.${hasPassword(paste) ? " You need its password to read it." : ""}`}
      </p>
      <div className="row">
        {mine ? (
          <>
            <a className="btn" href={adminPath(mine)} onClick={(e) => { e.preventDefault(); navigate(adminPath(mine)); }}>Manage it instead</a>
            <button className="btn btn-line" type="button" onClick={open} aria-busy={busy || undefined}><span>Open and delete it</span></button>
          </>
        ) : (
          <button className="btn btn-lg" type="button" onClick={open} aria-busy={busy || undefined}><span>Open the paste</span></button>
        )}
      </div>
      <p className="hint">{formatBytes(paste.size)}{hasPassword(paste) ? ", locked with a password" : paste.encrypted ? ", encrypted" : ""}</p>
    </Message>
  );
}

/** Asks for the password. The locked text is already on the page, so a wrong guess can be retried even after a burn paste is deleted. */
function Unlock({ paste, onOpen }: { paste: Shown; onOpen: (paste: Shown) => void }) {
  const [password, setPassword] = useState("");
  const [busy, setBusy] = useState(false);
  const [error, setError] = useState<string | null>(null);
  const submit = async (e: FormEvent) => {
    e.preventDefault();
    if (!password) return setError("Type the password you were given.");
    setBusy(true);
    setError(null);
    try {
      onOpen({ ...paste, ...(await openWithPassword(paste.content, password)) });
    } catch {
      setError("That password does not open this paste. Check it and try again.");
      setBusy(false);
    }
  };
  return (
    <Message title="This paste is locked with a password.">
      {paste.deleted
        ? <p className="notice" role="status">It is already deleted from the server, because it was set to delete after one view. Keep this page open until it is unlocked.</p>
        : <p className="lede">The person who sent you the link has the password. It was locked in their browser, so our server cannot read it either.</p>}
      <form className="unlock" onSubmit={submit} noValidate>
        <label className="field">
          <span>Password</span>
          <input className="input" type="password" value={password} onChange={(e) => { setPassword(e.target.value); setError(null); }} autoFocus
            autoComplete="off" autoCapitalize="off" spellCheck={false} enterKeyHint="go"
            aria-invalid={error ? true : undefined} aria-describedby={error ? "unlock-error" : undefined} />
          {error && <span className="field-error" id="unlock-error" role="alert">{error}</span>}
        </label>
        <button className="btn" type="submit" aria-busy={busy || undefined}><Icon name="lock" /><span>Unlock</span></button>
      </form>
    </Message>
  );
}

function PasteView({ id, paste }: { id: string; paste: Shown }) {
  const isMarkdown = paste.language === "markdown";
  const [rendered, setRendered] = useState(isMarkdown);
  const [wrap, setWrap] = useState(() => matchMedia("(max-width: 640px)").matches);
  const mine = savedPaste(id);
  const lang = languageOf(paste.language);
  const lines = paste.content.split("\n").length - (paste.content.endsWith("\n") ? 1 : 0);
  const expires = paste.expiresAt ? Date.parse(paste.expiresAt) / 1000 : null;
  // A paste encrypted with a key in the link uses the # for its key, so its lines cannot have links.
  const secret = paste.encrypted && !paste.password ? linkSecret() : "";
  const link = shareUrl(id, secret || undefined);
  const lock = paste.password ? "Locked with a password" : paste.encrypted ? "Encrypted in the browser" : "";

  const download = () => {
    const name = (paste.title.replace(/[^\w.-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 60) || id) + (/\.\w{1,8}$/.test(paste.title) ? "" : `.${lang.ext}`);
    const url = URL.createObjectURL(new Blob([paste.content], { type: "text/plain;charset=utf-8" }));
    const a = Object.assign(document.createElement("a"), { href: url, download: name });
    a.click();
    setTimeout(() => URL.revokeObjectURL(url), 1000);
  };
  const fork = () => {
    forkIntoEditor({ content: paste.content, title: paste.title, language: paste.language, lock: paste.password ? "password" : paste.encrypted ? "link" : "none" });
    toast("A copy is in the editor. Change it, then create a new link.");
  };

  return (
    <div className="view enter">
      <header className="view-head">
        <div className="wrap">
          <h1 className="view-title">{paste.title || "Untitled paste"}</h1>
          <p className="view-meta">
            <span>{lang.name}</span>
            <span>{lines.toLocaleString()} {lines === 1 ? "line" : "lines"}</span>
            <span>{formatBytes(byteLength(paste.content))}</span>
            {lock && <span>{lock}</span>}
            {paste.deleted ? <span>Deleted after this view</span> : <span>{expires ? `Expires ${fromNow(expires)}` : "Never expires"}</span>}
          </p>
        </div>
      </header>

      <div className="wrap view-body">
        {paste.deleted && (
          <p className="notice notice-ok" role="status">This paste is now deleted from the server, and its link no longer works. This page is the only copy left, so copy or download it before you leave.</p>
        )}
        <div className="view-bar">
          <div className="row view-toggles">
            {isMarkdown && (
              <div className="seg" role="group" aria-label="Show as">
                <button type="button" aria-pressed={rendered} onClick={() => setRendered(true)}>Rendered</button>
                <button type="button" aria-pressed={!rendered} onClick={() => setRendered(false)}>Source</button>
              </div>
            )}
            {!rendered && (
              <label className="check small"><input type="checkbox" checked={wrap} onChange={(e) => setWrap(e.target.checked)} /> Wrap lines</label>
            )}
          </div>
          <div className="row view-actions">
            <button className="btn btn-sm" type="button" onClick={() => copy(paste.content, "Copied the paste")}><Icon name="copy" size={16} /><span>Copy</span></button>
            <button className="btn btn-sm btn-line" type="button" onClick={download}><Icon name="download" size={16} /><span>Download</span></button>
            {!paste.deleted && (
              <button className="btn btn-sm btn-line" type="button" onClick={() => share(link, paste.title || "Paste", "Link copied")}>
                <Icon name={canShare() ? "share" : "link"} size={16} /><span>{canShare() ? "Share" : "Copy link"}</span>
              </button>
            )}
            <Menu label="More actions">
              {!paste.encrypted && !paste.burn && <a href={paste.rawUrl} target="_blank" rel="noopener">Open raw text</a>}
              <button type="button" onClick={fork}>Edit a copy</button>
              <button type="button" onClick={() => window.print()}>Print</button>
              {mine && !paste.deleted && (
                <>
                  <hr />
                  <a href={adminPath(mine)} onClick={(e) => { e.preventDefault(); navigate(adminPath(mine)); }}>Edit or delete</a>
                </>
              )}
            </Menu>
          </div>
        </div>
        {rendered ? (
          <Suspense fallback={<div className="md md-loading" aria-busy="true"><span className="skeleton skeleton-text" /><span className="skeleton skeleton-text" /></div>}>
            <Markdown text={paste.content} />
          </Suspense>
        ) : (
          <CodeView code={paste.content} language={paste.language} wrap={wrap} linkable={!secret && !paste.deleted} />
        )}
        <p className="view-foot">
          <a href="/" onClick={(e) => { e.preventDefault(); navigate("/"); }}>Make your own paste</a>
          <span>Free, no sign-up, unlisted.</span>
        </p>
      </div>
    </div>
  );
}

Change history

Quality pass: password lock, year expiry, line links, text/plain API
01fdb2b1f6 · 4 hours ago
Bounded source sync
fe5788c2d2 · 1 day ago
Bounded source sync
ac6f7dbc95 · 1 day ago
Bounded source sync
0a45926293 · 1 day ago