Skip to content

calsbot/proximity-prototype-v1

Repository files navigation

meetmarket.io

Privacy-preserving gay social app. End-to-end encrypted messaging, client-side encrypted photos, location privacy via geohash k-anonymity.

Stack

  • Frontend: SvelteKit (Svelte 5 runes), static adapter (PWA)
  • Backend: Bun + Hono
  • Database: SQLite via Drizzle ORM
  • Crypto: Ed25519 signing, X25519 encryption, double ratchet DMs, nacl.secretbox media
  • Identity: Dual keypairs (Ed25519 for signing/DID + X25519 for encryption), stored in IndexedDB

Prerequisites

  • Bun (v1.0+)
  • Node.js (v18+, for frontend tooling)

Setup

git clone https://github.com/calsbot/proximity.git
cd proximity

# Install dependencies
cd server && bun install && cd ..
cd frontend && npm install && cd ..

Running locally

Two processes — server and frontend:

# Terminal 1: backend (port 3000)
cd server
bun run --watch src/index.ts

# Terminal 2: frontend (port 5173)
cd frontend
npm run dev -- --host

Open http://localhost:5173

The --host flag is required for preview tools and mobile testing on LAN.

Vite proxies all API routes (/auth, /profiles, /messages, /groups, /moderation, /invitations, /media, /push, /newsletter, /ws) to http://localhost:3000 automatically via vite.config.ts. No VITE_API_URL needed for local dev.

Environment variables

All env vars have sensible defaults for local development. No .env file required to get started.

Variable Default Description
PORT 3000 Server port
VAPID_PUBLIC_KEY built-in dev key Push notification public key
VAPID_PRIVATE_KEY built-in dev key Push notification private key
VAPID_SUBJECT mailto:admin@meetmarket.io Push notification contact
LISTMONK_API_URL http://localhost:9000 Newsletter API (optional)
LISTMONK_ADMIN_USER admin Listmonk API user (optional)
LISTMONK_ADMIN_PASSWORD admin Listmonk API password (optional)

Frontend uses VITE_API_URL (defaults to empty = same origin via Vite proxy) and VITE_WS_URL (defaults to auto-detect from window.location).

Database

SQLite file lives at server/data/proximity.db. Created automatically on first run.

cd server

# Generate migrations after schema changes
bun run db:generate

# Apply migrations
bun run db:migrate

# Visual DB browser
bun run db:studio

The server/data/ directory and server/drizzle/ (migration files) are gitignored.

Seed data (testing)

# Seed 500 test profiles across European cities
bun run seed-profiles.ts

# Seed 9 demo profiles in Berlin with avatars + groups + backup export
bun run scripts/seed-demo-profiles.ts

Project structure

/frontend                SvelteKit app
  /src/lib/crypto          Identity, messaging, media, sealed sender encryption
  /src/lib/services        Chat orchestration, WebSocket client, notifications
  /src/lib/stores          Svelte stores (identity, conversations, location)
  /src/routes              Pages (grid, chat, profile, setup, settings)
  /static                  PWA manifest, service worker, icons
  vite.config.ts           Dev proxy config (routes API calls to backend)
  svelte.config.js         Static adapter, SPA fallback

/server                  Bun + Hono API
  /src/index.ts            Server entry, WebSocket upgrade, browser session management
  /src/db/schema.ts        Drizzle ORM schema (profiles, messages, groups, blocks, reports, media)
  /src/routes              API route handlers
  /data                    SQLite database (gitignored, created on first run)
  /drizzle                 Generated migrations (gitignored)

/test-e2e.ts             End-to-end test suite (47 tests)
/seed-profiles.ts        Test profile seeder (500 European profiles)
/scripts/                Demo setup scripts

Key API routes

Path Description
/auth Register, challenge-response verify
/profiles/discover Find nearby profiles (POST, geohash cells)
/profiles/:did Get/update profile
/messages Send (POST), poll (GET with since timestamp)
/messages/sealed Sealed sender relay (server can't see sender)
/invitations/dm DM invitations — send, accept, decline, block
/groups CRUD, invite, join requests, admin transfer
/media Encrypted blob upload/download
/moderation Block, unblock, report
/location/ip IP-based approximate location (Cloudflare headers)
/newsletter/subscribe Listmonk newsletter signup

How it works

Identity: Each user generates Ed25519 (signing) + X25519 (encryption) keypairs on device. Identity is a did:key derived from the Ed25519 public key. Never leaves the device unless exported as a backup.

Messaging: DMs use X25519 Diffie-Hellman key agreement + a chain key ratchet (nacl.secretbox). Every message generates a fresh key. Group messages use sender keys (nacl.secretbox) distributed to members.

Sealed sender: After initial key exchange, messages are relayed through the server without the sender's identity attached. The server only sees the recipient's delivery token.

Location privacy: The client generates the user's geohash locally, mixes it with decoy geohash cells (k-anonymity), and sends the mixed set to the server. The server processes all cells and cannot distinguish which one is real.

Media: Photos are encrypted on-device with nacl.secretbox before upload. The server stores opaque blobs it cannot decrypt. Recipients receive the key out-of-band via the encrypted message channel.

Building for production

cd frontend
npm run build
# Output: frontend/build/ — deploy as static files behind a reverse proxy

Deployment

Production runs on a VM behind Cloudflare Tunnel + Caddy:

  • Caddy (port 8080): reverse proxies API routes to Bun, serves static frontend
  • Cloudflare Tunnel: terminates TLS, routes meetmarket.io to Caddy
  • Listmonk (port 9000, Docker): newsletter service at mail.meetmarket.io
# Deploy frontend (zero downtime — just static file swap)
scp -r frontend/build/* user@host:~/proximity/frontend/build/

# Deploy server (2-3s restart, WebSocket auto-reconnects)
scp -r server/src/* user@host:~/proximity/server/src/
ssh user@host "kill \$(pgrep -f 'bun run src/index.ts') && sleep 2 && \
  cd ~/proximity/server && nohup bun run src/index.ts > /tmp/server.log 2>&1 &"

Messages and data are persisted in SQLite on disk — nothing is lost during server restarts.

E2E tests

bun run test-e2e.ts

Covers auth, profiles, messaging (DM + group), sealed sender, encryption, invitations, moderation, media, and key ratcheting.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors