A quiet place to talk, steady yourself, and reflect. Free, anonymous peer support: real 1-on-1 matching, small topic rooms, calm tools, and a private journal β no account, no tracking, and honest language about all of it.
SoulSpace is peer support. It is not therapy, not a crisis service, and not an AI therapist. Crisis resources are localized (Pakistan first, plus international directories) and always one tap away.
Live: https://soul-space-alpha.vercel.app
The v1 MVP simulated its core promise: "matching" was a setTimeout, chat channels were random UUIDs nobody else could join, room counts were hardcoded, and two unauthenticated API routes allowed anyone to eavesdrop on any private channel or send messages as anyone. v2 is a security-first rebuild around the same product soul:
| v1 | v2 | |
|---|---|---|
| Matching | Fake timer | Real server-side queue (Turso) with topic compatibility pairing |
| Channel access | Anyone could auth anything | HMAC capability tokens, verified per-channel, fail-closed |
| Messaging | Open relay (spoof/spam/eavesdrop) | Server-authoritative sends with membership + rate limits |
| Crisis handling | Blocked common words, redirected mid-chat | Tiered detection: gentle nudges, never silent censorship, localized resources |
| Privacy claims | "100% secure & anonymous" | Precise, architecture-matched disclosures |
| Social proof | Fabricated counts everywhere | Removed; presence is real (Pusher presence channels) |
| Tests / CI | None | 38 vitest tests incl. attack paths + GitHub Actions |
Next.js 16 (App Router, React 19, Turbopack)
βββ src/app β routes (server components by default; chat/tools are client islands)
βββ src/app/api
β βββ match/* β join / status / leave (queue + pairing + capability tokens)
β βββ chat/* β send / leave (server-authoritative, membership-checked)
β βββ room/* β join / send (fixed room allowlist, server-assigned handles)
β βββ report β durable abuse reports
β βββ pusher/auth β channel authorization (capability-token gated)
β βββ health β honest provider configuration status
βββ src/lib
β βββ capability.ts β HMAC-signed, expiring, resource-bound tokens
β βββ matching.ts β pairing logic (FIFO + topic compatibility)
β βββ store.ts β Turso adapter w/ in-memory fallback + TTL cleanup
β βββ crisis.ts β tiered distress detection + localized helplines
β βββ ratelimit.ts β token-bucket limiter (per-instance on serverless)
β βββ validate.ts β strict input validation for every endpoint
βββ tests/ β vitest: matching lifecycle, capability forgeries, API attack paths
- No accounts, no cookies, no analytics. The browser generates a random UUID per conversation (
sessionStorage, never persisted server-side). - To join a chat or room the client must present a capability token the server issued to a specific participant. Tokens are HMAC-SHA256 signed (
APP_SECRET, falling back toPUSHER_SECRET), bound to one exact channel, and expire (2β3 h). /api/pusher/authre-verifies the token and live membership before authorizing any subscription. Ending a chat deletes its row β access is revoked immediately for both parties.- Messages are relayed via Pusher and never written to the database. The sender identity (
role/handle) is stamped server-side; clients cannot impersonate each other or publish to channels they don't hold tokens for.
| Data | Where | Lifetime |
|---|---|---|
| Chat messages | Relayed through Pusher, never stored by SoulSpace | Gone on delivery |
| Queue / pairing rows | Turso (or in-memory fallback) | Deleted on match/leave, TTL 10 min |
| Active chat rows | Turso (or in-memory fallback) | Deleted on leave, TTL 2 h |
| Check-ins / journal / safe kit | Browser localStorage only | Until user exports or erases |
| Abuse reports | Turso (or in-memory fallback) | Reviewed manually by the maintainer |
Copy .env.example β .env.local for development. In production (Vercel) set the same keys. Without Turso configured, matching still works but only pairs users served by the same warm instance β /api/health reports which mode is live (matching: "turso" | "memory-fallback").
| Variable | Required | Purpose |
|---|---|---|
NEXT_PUBLIC_PUSHER_KEY |
yes | Realtime client (public by design) |
NEXT_PUBLIC_PUSHER_CLUSTER |
yes | Realtime client |
PUSHER_APP_ID |
yes | Server-side Pusher |
PUSHER_SECRET |
yes | Server-side Pusher; also signs capability tokens if APP_SECRET unset |
APP_SECRET |
recommended | Dedicated capability-token signing key (openssl rand -hex 32) |
TURSO_DATABASE_URL |
recommended | Durable matching queue + reports |
TURSO_AUTH_TOKEN |
recommended | Turso auth |
npm install
cp .env.example .env.local # fill in real values
npm run dev # http://localhost:3000
npm test # vitest suite (38 tests)
npm run lint
npm run build # production build checkThe database schema is created on demand (CREATE TABLE IF NOT EXISTS in src/lib/store.ts) β no migration step needed.
Vercel auto-deploys the main branch. Pre-deploy checklist:
npm run build && npm testlocally- Vercel env vars present (see table above)
- After deploy:
GET /api/healthmust showrealtime: "configured"and (ideally)matching: "turso"
- What we defend: channel eavesdropping, message spoofing, cross-channel injection, unauthenticated relaying, flooding (burst + sustained buckets per session/IP), oversized/malformed payloads, cross-origin browser abuse, arbitrary room creation.
- Honest limitations: in-memory rate limiting is per serverless instance (approximate across instances); moderation is one human, not real-time; TLS transport only (no E2E encryption β disclosed in the privacy page, not hidden).
- Not provided: emergency intervention, medical advice, law-enforcement escalation. Crisis resources link to trained services (Umang/Rozan/Taskeen in Pakistan; findahelpline.com / IASP internationally).
Highest-value next steps, in order:
- Turso on production (if not yet configured) so matching spans serverless instances.
- Nonce-based CSP (replacing
unsafe-inlinefor scripts) and a load-tested presence model for rooms. - Moderation tooling: a private review view for stored reports; rate limits on a shared store (e.g. Upstash) for cross-instance accuracy.
- Listener mode: structured "just listening" vs "want to talk" intent during matching.
- Voice notes, hardened (size/duration caps + server-side crisis scan) β existed in v1, deliberately deferred.
Built as an independent, open-source product. Illustrations are original; fonts are Google Fonts (Inter, Fraunces). Not affiliated with any crisis service it links to.