Skip to content

Repository files navigation

Slip

Personal cross-device clipboard — paste on your phone, open a short link on your laptop. Friendly URLs, optional E2E encryption, expiry, syntax highlighting. React + Hono on Cloudflare Workers.

A personal cross-device clipboard. Paste a snippet on your phone, get a short friendly link (like slip.you.workers.dev/swift-otter), and open it on your laptop. Built for the "copy an error log on my Android phone → read it on my PC" workflow, without bouncing it through a chat app.

  • Friendly short links — two readable words, easy to type on the other device.
  • QR code — on the result card, for instant phone-to-phone handoff.
  • Per-paste expiry — 1 hour, 1 day, 1 week, or never. Handled natively by KV.
  • Unguessable mode — a toggle that mints a long random link for sensitive text.
  • End-to-end encryption (optional) — set a passphrase and the text is encrypted in your browser (AES-256-GCM) before upload; the server only ever stores ciphertext. The reader enters the passphrase to decrypt, also in-browser.
  • Syntax highlighting — pick a language (or let it autodetect) and the view page highlights the snippet by file type.
  • Copy all / Raw / Delete now on the view page.
  • Accounts (optional) — email + password login (JWT in an httpOnly cookie). Sign in and your slips are tied to you and listed on a My Slips page. Owned slips can only be deleted by their owner.
  • React + Vite frontend, Hono on Cloudflare Workers — one origin, one deploy.

Topics: cloudflare-workers · hono · react · vite · clipboard · pastebin · kv · encryption · self-hosted · typescript

Architecture

                         ┌────────────────────────── Cloudflare Worker ──────────────────────────┐
  phone  ──POST /api/slips──▶  Hono  ──put──▶  KV  (auto-expires via expirationTtl)
                         │      │                                                                  │
  laptop ──GET /swift-otter──▶  SPA (React) ──GET /api/slips/swift-otter──▶ Hono ──get──▶ KV       │
                         └───────────────────────────────────────────────────────────────────────┘
  • Frontend — a React SPA built by Vite. Client-side routes: / (paste page) and /:code (view page, which fetches the slip from the API).

  • Backend — a Hono app running on Cloudflare Workers. It serves the JSON API (/api/slips, /api/slips/:code), auth (/api/auth/{register,login,logout,me}), the signed-in user's slips (/api/my/slips), the raw-text endpoint (/raw/:code), and a health check (/api/health). Every other path falls through to the SPA's static assets.

  • Auth — email + password. Passwords are hashed with PBKDF2 (Web Crypto), and the session is a JWT (hono/jwt) stored in an httpOnly cookie. Set a JWT_SECRET (see below).

  • Storage — Workers KV, with prefixed keys so a slip lookup can never read a user record:

    • slip:<code> — the slip (carries userId when created while signed in).
    • user:<email> — the account record.
    • userslip:<userId>:<code> — a per-user index entry (in KV metadata) sharing the slip's TTL, so My Slips is a single kv.list() and expired entries drop off automatically.

    KV's built-in expirationTtl deletes expired slips for you, so there's no cleanup job.

The @cloudflare/vite-plugin runs the Worker inside Vite's dev server, so the frontend and API share one origin in development just like in production.

Layout

index.html              # Vite entry — mounts the React app
src/
  client/               # React SPA
    main.tsx            # router (/, /login, /my, /:code)
    pages/              # Home (paste), View (read), Login, MySlips
    components/         # shared Layout + nav
    lib/api.ts          # typed fetch wrappers
    lib/auth.tsx        # auth context (useAuth)
    lib/crypto.ts       # client-side AES-GCM encrypt/decrypt (passphrase slips)
    lib/highlight.ts    # highlight.js setup + language list
    styles.css          # the shared stylesheet
  worker/               # Hono backend
    index.ts            # routes + SPA fallback
    slips.ts            # code generation, KV record shape, limits
    auth.ts             # password hashing + JWT sessions
  shared/format.ts      # pure helpers used by both client and worker
test/slips.test.ts      # Vitest API tests (Hono app + mock KV)
wrangler.jsonc          # Worker + KV + assets config
.dev.vars               # local secrets (JWT_SECRET) — gitignored

Develop & deploy

You need a free Cloudflare account and Node.js installed. This repo uses pnpm.

# 1. Install deps
pnpm install

# 2. Provide a local auth secret (gitignored)
echo "JWT_SECRET=$(openssl rand -hex 32)" > .dev.vars

# 3. Run locally — Vite dev server + the Worker on one origin (local KV)
pnpm dev          # http://localhost:5173

# 4. Run the tests (no account needed)
pnpm test
pnpm typecheck

# 5. Log in and create the KV namespace (first time only)
npx wrangler login
npx wrangler kv namespace create SLIP_KV
#    -> copy the printed id into the kv_namespaces block in wrangler.jsonc

# 6. Set the production auth secret (stored encrypted by Cloudflare)
npx wrangler secret put JWT_SECRET

# 7. Build the SPA + Worker and ship it
pnpm deploy

Wrangler prints your URL, e.g. https://slip.<your-subdomain>.workers.dev. Bookmark it on both devices and you're done.

Use your own domain (optional)

In the Cloudflare dashboard, add a route or custom domain to the Worker — e.g. c.yourdomain.com — for even shorter links.

Notes & limits

  • Size cap: 200 KB per slip (plenty for logs; tune MAX_BYTES in src/worker/slips.ts).
  • Privacy: friendly word-links are short and therefore guessable by someone enumerating combinations. For anything sensitive, use the Unguessable link toggle, which generates a long random code. By default slips are stored as plaintext in KV (not encrypted at rest), so short expiry is your friend. For true privacy, set a passphrase: the content is encrypted in the browser and only ciphertext reaches Cloudflare — but the passphrase is unrecoverable, so don't lose it. Encrypted slips skip the Raw endpoint and show a 🔒 (no preview) on My Slips.
  • XSS: the view page renders slip content as React text nodes, so user input is escaped automatically.
  • Free tier: Workers + KV free limits (100k reads/day, 1k writes/day) are far beyond personal use.

Ideas to extend

The original three ideas — QR handoff, client-side encryption, and syntax highlighting — are now built in (see the feature list above). Next up:

  • Drag-and-drop / multi-file slips (still text, but tabbed).
  • A self-destruct-on-first-read option (burn after reading).
  • Web Share API integration so the result card can hand off to native share sheets.

About

Personal cross-device clipboard — paste on your phone, open a short link on your laptop. Friendly URLs, optional E2E encryption, expiry, syntax highlighting. React + Hono on Cloudflare Workers.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages