No-auth multiplayer party games — synced across every phone in the room via WebSockets.
One person hosts on a big screen. Everyone else joins on their phone with a 6-letter code. No accounts, no app install, no waiting. Real-time state is pushed to every client over Supabase Realtime the moment anything changes.
Live at pregame.lol
Each player is secretly assigned another person in the room to impersonate. When you hit the hotseat, you answer questions as that person. Everyone else votes on who you were pretending to be.
- 3–20 players, 15–30 min
- Phases:
LOBBY → ASSIGNMENT → INTRO → WHEEL → HOTSEAT → VOTING → REVEAL → SCOREBOARD → COMPLETE
Civilians know the secret word. Imposters don't. Give clues, sniff out who's faking, vote to eliminate. Imposters win if they reach parity with civilians.
- 3–20 players, 10–20 min
- 1 imposter (3–7 players) or 2 imposters (8+)
- Phases:
LOBBY → SECRET_REVEAL → CLUE → VOTING → REVEAL → GAME_OVER
Every game action follows the same pipeline:
Client action (e.g. host clicks "Start Game")
→ HTTP POST to Next.js API route
→ Server validates state transition
→ Postgres write (Supabase)
→ Server broadcasts event on Supabase Realtime channel
→ All subscribed clients receive broadcast
→ React state updates → UI re-renders
Key design decisions:
- Server-authoritative broadcasts — clients never write to the Realtime channel directly. Only API routes broadcast, which prevents spoofing.
self: trueon channels — the broadcaster also receives its own event, so all clients (including the host) follow the same update code path.- State machine validation — every phase transition is validated server-side against an explicit
VALID_TRANSITIONSmap before any DB write. Bad requests fail fast with a 400. - Session IDs, not auth — players are identified by a UUID stored in
localStorage. Zero friction to join, and identity survives page refreshes.
// Supabase Realtime channel (one per room)
supabase.channel(`room:${roomCode}`, {
config: { broadcast: { self: true } }
})
.on('broadcast', { event: 'room_updated' }, ({ payload }) => {
setRoom(payload.room);
})
.subscribe();| Layer | Technology |
|---|---|
| Framework | Next.js 16 (App Router) |
| UI | React 19, TypeScript 5, Tailwind 4 |
| Real-time | Supabase Realtime (WebSocket pub/sub) |
| Database | Supabase (PostgreSQL) |
| Mobile | Capacitor 7 (iOS/Android) |
| Testing | Vitest, Testing Library, Playwright |
| Deploy | Vercel (web), Capacitor (iOS) |
web/
├── src/
│ ├── app/
│ │ ├── page.tsx # Home — game picker + room create/join
│ │ ├── room/[code]/ # Assumptions game client
│ │ ├── imposter/room/[code]/ # Imposter game client
│ │ └── api/
│ │ ├── rooms/ # Assumptions REST endpoints
│ │ └── imposter/rooms/ # Imposter REST endpoints
│ ├── components/ # Phase views (LobbyView, HotseatView, etc.)
│ ├── lib/
│ │ ├── gameEngine.ts # Pure state machine logic (no I/O)
│ │ ├── realtime.ts # Supabase Realtime subscriptions
│ │ ├── roomService.ts # DB read/write helpers
│ │ └── imposterService.ts # Imposter game DB helpers
│ └── types/index.ts # Shared TypeScript types
├── supabase/
│ └── schema.sql # Full database schema
└── capacitor.config.ts # iOS/Android build config
| Method | Route | Auth | Description |
|---|---|---|---|
POST |
/api/rooms |
— | Create room, returns room + host player |
POST |
/api/rooms/[code]/join |
session | Join room |
GET |
/api/rooms/[code] |
— | Get room state snapshot |
POST |
/api/rooms/[code]/start |
host | Start game, generate assignments |
POST |
/api/rooms/[code]/spin |
host | Spin wheel, select hotseat player |
POST |
/api/rooms/[code]/vote |
session | Submit vote for hotseat target |
POST |
/api/rooms/[code]/next |
host | Advance phase |
| Method | Route | Auth | Description |
|---|---|---|---|
POST |
/api/imposter/rooms |
— | Create room |
POST |
/api/imposter/rooms/[code]/join |
session | Join room |
GET |
/api/imposter/rooms/[code] |
— | Get room state snapshot |
POST |
/api/imposter/rooms/[code]/start |
host | Start round with topic + secret word |
POST |
/api/imposter/rooms/[code]/vote |
session | Vote to eliminate player |
POST |
/api/imposter/rooms/[code]/advance |
host | Advance phase |
All endpoints: return consistent { error, code } on failure, broadcast state update on success.
- Node.js 18+
- A Supabase project (free tier works)
# Clone and install
git clone https://github.com/upneja/assumption.git
cd assumption/web
npm install
# Configure environment
cp .env.example .env.local
# Fill in your Supabase URL and keys
# Run the schema
# Paste web/supabase/schema.sql into the Supabase SQL editor and run it
# Start dev server
npm run dev
# → http://localhost:3000NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
SUPABASE_URL=https://your-project.supabase.co
SUPABASE_SERVICE_ROLE_KEY=your-service-role-key # server-side only, never exposed to client
NEXT_PUBLIC_API_URL= # leave empty for local devnpm run dev # Dev server on localhost:3000
npm run build # Production build
npm run lint # ESLint
npm run test # Vitest unit tests
npm run test:coverage # Coverage report
npx playwright test # E2E tests
npm run type-check # TypeScript checkThe web app deploys to Vercel automatically on push to main. Add the four environment variables above in the Vercel project settings.
For iOS: see CAPACITOR_SETUP.md.
MIT