Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

1 Commit
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

πŸ” Vault

A real-time screen sharing + file transfer + live chat platform. Zero accounts, zero plugins β€” just a 6-char code.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Host shares screen β†’ Viewers join with code β†’ Everyone can chat β”‚
β”‚  Anyone can send files β†’ 24h links + 6-char codes               β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Features

Feature Details
πŸ–₯️ Screen sharing WebRTC P2P, host β†’ multiple viewers, fullscreen on both ends
πŸ“€ File send Upload up to 50MB β†’ get a 6-char code + 24h download link
πŸ“₯ File receive Enter code or open direct link β†’ instant download
πŸ’¬ Live chat Real-time WebSocket chat with avatars, typing indicators
πŸ“Ž In-chat files Share files inside the chat; auto-uploaded and coded
πŸ‘₯ Presence Floating emoji avatars, arrow-key movable
πŸ”— Shareable links /watch/ROOMCODE and /download/FILECODE deep links

Quick Start

1. Clone & install

git clone <your-repo>
cd vault

# Backend
cd server && npm install

# Frontend
cd ../client && npm install

2. Configure environment

# client/.env  (copy from .env.example)
VITE_WS_URL=ws://localhost:3001
VITE_API_URL=http://localhost:3001

3. Run

Terminal 1 β€” backend:

cd server
node server.js
# πŸ” Vault Server on ws://localhost:3001

Terminal 2 β€” frontend:

cd client
npm run dev
# β†’ http://localhost:5173

That's it. Open two browser tabs, share a room code, and it works.


Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                      Browser (Host)                     β”‚
β”‚                                                         β”‚
β”‚  getDisplayMedia() β†’ RTCPeerConnection β†’ ICE/STUN      β”‚
β”‚  WebSocket ──────────────────────────────────────────┐  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”‚β”€β”€β”˜
                                                        β”‚
                   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”
                   β”‚        Vault Signaling Server          β”‚
                   β”‚   Node.js  Β·  ws  Β·  http             β”‚
                   β”‚                                        β”‚
                   β”‚  Sessions Map: roomCode β†’ { host,      β”‚
                   β”‚    viewers, chat }                     β”‚
                   β”‚                                        β”‚
                   β”‚  Files Map: code β†’ { data, expiresAt } β”‚
                   β”‚  (auto-purged after 24h)               β”‚
                   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”˜
                                                        β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”‚β”€β”€β”
β”‚                      Browser (Viewer)                   β”‚
β”‚                                                         β”‚
β”‚  WebSocket ◄────────────────────────────────────────────│
β”‚  Offer β†’ Answer β†’ ICE β†’ RTCPeerConnection              β”‚
β”‚  video.srcObject = remoteStream  β†’ plays stream         β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

WebRTC Signal Flow

Host                    Server                   Viewer
 β”‚                        β”‚                        β”‚
 │── host (name) ────────►│                        β”‚
 │◄─ room-created ────────│                        β”‚
 β”‚                        │◄─ viewer (code, name) ─│
 │◄─ viewer-joined ───────│── room-joined ─────────►│
 │── offer ──────────────►│── offer ───────────────►│
 β”‚                        │◄─ answer ───────────────│
 │◄─ answer ──────────────│                        β”‚
 │── ice ────────────────►│── ice ─────────────────►│
 │◄─ ice ─────────────────│◄─ ice ──────────────────│
 β”‚                        β”‚                        β”‚
 │◄══════════ P2P video stream (direct) ═══════════│

File Flow

Sender                   Server                  Receiver
  β”‚                        β”‚                        β”‚
  │── POST /api/files ─────►│                        β”‚
  β”‚   (base64 body)        β”‚ stores in fileStore     β”‚
  │◄─ { code, expiresAt } ─│                        β”‚
  β”‚                        β”‚                        β”‚
  β”‚   shares code/link     β”‚                        β”‚
  β”‚                        │◄─ GET /api/files/:code/meta
  β”‚                        │── { fileName, size } ──►│
  β”‚                        │◄─ GET /api/files/:code  β”‚
  β”‚                        │── binary stream ────────►│
  β”‚                        β”‚   (auto-deleted 24h)    β”‚

Folder Structure

vault/
β”œβ”€β”€ server/
β”‚   β”œβ”€β”€ server.js          # WebSocket signaling + REST API
β”‚   └── package.json
β”‚
└── client/
    β”œβ”€β”€ index.html
    β”œβ”€β”€ vite.config.js
    β”œβ”€β”€ .env.example
    └── src/
        β”œβ”€β”€ main.jsx
        β”œβ”€β”€ App.jsx                # Router + state machine
        β”œβ”€β”€ hooks/
        β”‚   β”œβ”€β”€ useWebRTC.js       # RTCPeerConnection logic
        β”‚   └── useWebSocket.js    # WS connection + reconnect
        β”œβ”€β”€ components/
        β”‚   β”œβ”€β”€ Lobby.jsx          # Landing/home page
        β”‚   β”œβ”€β”€ HostRoom.jsx       # Screen share host view
        β”‚   β”œβ”€β”€ ViewerRoom.jsx     # Stream viewer view
        β”‚   β”œβ”€β”€ ChatPanel.jsx      # Slide-in chat drawer
        β”‚   β”œβ”€β”€ VideoPlayer.jsx    # Video element + controls
        β”‚   β”œβ”€β”€ Avatar.jsx         # Static + floating avatars
        β”‚   β”œβ”€β”€ FileSend.jsx       # Upload β†’ get code/link
        β”‚   β”œβ”€β”€ FileDownload.jsx   # Enter code β†’ download
        β”‚   └── WatchPage.jsx      # Join stream by code/link
        β”œβ”€β”€ styles/
        β”‚   └── global.css
        └── utils/
            └── avatar.js          # Emoji avatars, helpers

URL Routes

URL Description
/ Home / lobby
/send Upload a file, get 24h code + link
/download Enter a file code to download
/download/ABC123 Direct download link (auto-fetches file)
/watch Enter a room code to join a stream
/watch/ABC123 Direct stream join link
/share/ABC123 Host view with shareable link (auto-set)

API Reference

POST /api/files

Upload a file as base64. Returns a 6-char code and expiry.

// Request
{
  "fileName": "report.pdf",
  "fileSize": 102400,
  "fileType": "application/pdf",
  "fileData": "data:application/pdf;base64,..."
}

// Response
{
  "code": "AB3K9X",
  "expiresAt": 1714000000000
}

GET /api/files/:code/meta

Check file info without downloading.

{
  "fileName": "report.pdf",
  "fileSize": 102400,
  "fileType": "application/pdf",
  "uploadedAt": 1713913600000,
  "expiresAt": 1714000000000,
  "downloads": 3
}

GET /api/files/:code

Download the file (binary, triggers browser download).

GET /api/session/:code/meta

Check if a room is live.

{
  "roomCode": "66F2WW",
  "hostName": "Bold Crane",
  "viewerCount": 2,
  "createdAt": 1713913600000
}

GET /health

Server health check.


WebSocket Message Types

Direction Type Payload
Client→Server host { name, avatar }
Client→Server viewer { roomCode, name, avatar }
Client→Server offer { sdp, targetId }
Client→Server answer { sdp }
Client→Server ice { candidate, targetId? }
Client→Server chat { text }
Client→Server typing { isTyping }
Client→Server file-share { fileName, fileCode, fileUrl, ... }
Server→Client room-created { roomCode, clientId }
Server→Client room-joined { roomCode, clientId, hostName, chatHistory }
Server→Client viewer-joined { viewerId, name, viewerCount }
Server→Client viewer-left { viewerId, viewerCount }
Server→Client offer { sdp }
Server→Client answer { sdp, viewerId }
Server→Client ice { candidate }
Server→Client chat { id, name, text, timestamp }
Server→Client typing { name, isTyping }
Server→Client host-left {}

Production Deployment

Environment variables

# server
PORT=3001

# client/.env.production
VITE_WS_URL=wss://your-domain.com/ws
VITE_API_URL=https://your-domain.com

Build frontend

cd client && npm run build
# Output: client/dist/

Nginx config (SPA + WebSocket + API proxy)

server {
    listen 443 ssl;
    server_name your-domain.com;

    # SPA
    root /var/www/vault/client/dist;
    index index.html;
    location / {
        try_files $uri $uri/ /index.html;   # SPA fallback
    }

    # REST API
    location /api {
        proxy_pass http://localhost:3001;
        proxy_http_version 1.1;
    }

    # WebSocket signaling
    location /ws {
        proxy_pass http://localhost:3001;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_read_timeout 3600s;
    }
}

Note: With this nginx config, update VITE_WS_URL=wss://your-domain.com/ws and the server must listen on /ws path. Or just use a subdomain for the WS server directly.

PM2 (process manager)

npm install -g pm2
cd server
pm2 start server.js --name vault-server
pm2 save
pm2 startup

TURN Server (for NAT traversal in production)

WebRTC works great on the same network or with STUN. For users behind strict firewalls/NATs, add a TURN server.

// In client/src/hooks/useWebRTC.js, add to ICE_SERVERS:
{
  urls: 'turn:your-turn-server.com:3478',
  username: 'vault',
  credential: 'your-secret',
}

Free TURN options: Metered.ca Β· Xirsys


Known Limits & Notes

  • File storage is in-memory. A server restart loses all files. For production, store files in S3/R2 and save the metadata in Redis.
  • Max file size is 50MB (base64 inflates ~33%, so actual payload is ~67MB). Configurable in server.js.
  • Chat history keeps last 200 messages per room in memory.
  • STUN only by default. Add TURN for cross-NAT production deployments.
  • No E2E encryption on the server API (files rest in memory as base64). WebRTC media is DTLS-encrypted by the spec.

License

MIT

About

share files securely or host a watchparty

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Packages

Contributors

Languages