Project
SupportDesk (Realtime Support + Widget)
Socket.IO support inbox with assignment, tickets, analytics, and an embeddable widget (script + iframe)
- Role
- Solo
- Focus
- Realtime messaging, Embeddable widget, Multi-instance scaling
- Testing
- Jest (claim race, auth, widget, rate limit), Playwright E2E (customer→agent flow), Invite acceptance flow
- Key patterns
- Realtime, NestJS, Socket.IO, Redis
- NestJS
- TypeScript
- Prisma
- Postgres
- Redis
- Socket.IO
- React + Vite
- Playwright
What it does
- Agents handle customer chats in a real-time inbox (claim, assign, close)
- Turns conversations into tickets and tracks status
- Embeds a widget on any site with one script tag
- Shows needs-attention when the last message is from the customer, plus analytics and CSV export
- Scales Socket.IO across instances with the Redis adapter
Architecture
┌────────────────────────┐ ┌────────────────────────┐
│ Vite + React │─────►│ NestJS API + Gateway │
│ (Agent UI /inbox) │ │ REST + Socket.IO │
└───────────┬────────────┘ └───────────┬────────────┘
│ │
│ widget.js │ Prisma ORM
▼ ▼
┌───────────────────────┐ ┌──────────────────────────┐
│ Iframe Widget UI │◄───► │ Postgres │
│ /embed/widget │ │ convos/messages/tickets │
└───────────────────────┘ └────────────┬─────────────┘
│
▼
┌──────────────────┐
│ Redis adapter │
└──────────────────┘Reliability and guardrails
- Room joins and sends go through guarded handlers, with JWT on the API and the socket
- Needs-attention is computed from lastAgentMessageAt and lastCustomerMessageAt
- Rate limits on REST and Socket.IO return 429 with Retry-After
- Helmet headers, message sanitization, and an audit log sit on the write path
- Health checks cover Postgres and Redis; production Compose runs migrations before listen
Key engineering decisions
Needs-attention is a SQL comparison, not a flag written on every message
lastAgentMessageAt versus lastCustomerMessageAt keeps filtered inbox pages and totals correct.
Redis adapter for Socket.IO
More than one API instance can share rooms without sticky sessions.
Customer widget in an iframe, loaded by one script tag
The host page only loads widget.js. Sizing and open/close go through postMessage.
Failure modes handled
- Two agents claim the same conversation — the claim is guarded, and Jest covers the race
- Expired widget token — the widget creates a new customer session
- Redis or Postgres down — the health check fails and the container does not stay up
- Rate limit hit — the API returns 429 with Retry-After
- Customer message with no agent reply — the inbox shows needs-attention
How to demo in 2 minutes
- 1docker compose up -d, then npm install
- 2Apply Prisma migrations and seed the demo inbox
- 3npm run dev — UI on :5173, API on :3000/api
- 4Sign in as agent@local.test and open the inbox
- 5Load the widget script, send a customer message, and reply as the agent
- 6Claim a conversation and open analytics