← Projects
Tools·Oct 2025

PasteRelay: Real-Time WebRTC Clipboard

Minimal peer-to-peer online clipboard with zero server storage

WebRTCWebSocketNext.jsNodeJSTypeScriptDockerWebDev
View Repository →

PasteRelay

A minimal 1:1 online clipboard that creates a temporary tunnel between two devices using a PIN. Text-only MVP using WebRTC DataChannels with a lightweight WebSocket signaling server.

open app

Author: Francesco Vigni · License: MIT

Features (MVP)

  • Create a session to get a short PIN and wait for a peer.
  • Join a session by PIN; invalid PINs show clear errors.
  • Shared clipboard view to send/receive text in real time.
  • Peer-to-peer transport (WebRTC DataChannel) with STUN.
  • No server-side storage of clipboard contents; signaling metadata only.

Monorepo layout

apps/
  web/        — Next.js app (UI, client WebRTC)
  signaling/  — Node.js WebSocket server (signaling + PIN registry)
packages/
  shared/     — TypeScript types and schemas for signaling + payloads

Requirements

  • Node.js 18+
  • npm 8+ (workspaces)

Quick start

Install dependencies (root workspaces):

npm install

Run dev (web on http://localhost:3000, signaling on ws://localhost:8004):

npm run dev

Open two browsers/devices:

  1. Device A: Create a session, share the PIN.
  2. Device B: Join with that PIN.

Both will enter the shared clipboard and can exchange text.

Run with Docker (local)

  1. Build images and start:
docker compose up --build

Open:

To stop:

docker compose down

Deploy on a VPS

  1. Copy this repo (or only apps/* and packages/*) to your VPS.
  2. Ensure Docker and docker compose are installed.
  3. Set your .env (consider a public hostname and CORS/reverse proxy if needed).
  4. Run docker compose up -d --build.

Optionally place Nginx/Caddy in front:

  • Proxy http://localhost:3000 → your domain for the web app.
  • Expose ws://yourdomain:8004 or proxy a path like /ws to the signaling container.

Nginx example

server {
  listen 80;
  server_name pasterelay.example.com;

  location / {
    proxy_pass http://127.0.0.1:3000;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  }

  # WebSocket proxy at /ws (update PUBLIC_WS_URL=wss://pasterelay.example.com/ws)
  location /ws {
    proxy_pass http://127.0.0.1:8004;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "Upgrade";
    proxy_set_header Host $host;
  }
}

Don’t forget to enable TLS (e.g., with certbot) and set PUBLIC_WS_URL to wss://.

Environment variables

Create a .env file at repo root (used by docker-compose and web):

SIGNALING_PORT=8004
PIN_LENGTH=6
SESSION_TTL_SECONDS=3600
ICE_SERVERS=[{"urls":["stun:stun.l.google.com:19302"]}]
PUBLIC_WS_URL=ws://localhost:8004
# TURN (optional)
# TURN_URL=turn:your.turn.server:3478
# TURN_USERNAME=youruser
# TURN_CREDENTIAL=yourpass

Scripts

npm run dev           # runs signaling and web together
npm run dev:signaling # runs only signaling server
npm run dev:web       # runs only web app

MIT License © PasteRelay