A WebRTC-based video ordering platform for Bestway Supermarket. Customers place live video calls to order takers who help them shop remotely. Managers monitor all branches in real time.
- Customer visits the site, enters their name, phone, and delivery address
- They join a queue for their nearest branch (auto-detected by GPS)
- An available order taker receives the call, sees customer details, and accepts
- Both sides connect via WebRTC for a live video/audio call
- The order is placed and fulfilled during the call
- Manager dashboard shows all active calls, queue sizes, and order taker status across all branches
| URL | Who uses it | Description |
|---|---|---|
/ |
Customers | Join queue, wait, video call |
/order-taker |
Staff | Login, go available, take calls |
/manager |
Managers | Live dashboard of all branches |
app/
page.tsx # Customer page
order-taker/
page.tsx # Order taker page
manager/
page.tsx # Manager dashboard
globals.css # All styling — fonts, colors, layout
layout.tsx # Root layout — imports CSS, sets page title
useCallDuration.ts # Shared call timer hook
server.js # Custom HTTPS + Socket.io server
create-user.js # CLI tool to create order taker / manager accounts
migrate-passwords.js # Migrates plain text passwords to bcrypt hashes
branches.json # Branch list with names, coordinates, WhatsApp numbers (never commit this)
users.json # User accounts (never commit this)
logs/ # Monthly log files (auto-created, never commit)
public/
favicon.jpg
ringtone.mp3
- Node.js 18+
- npm
npm installEdit branches.json:
[
{
"id": "cairo-branch",
"name": "Cairo Branch",
"lat": 30.0444,
"lng": 31.2357,
"whatsapp": "201012345678"
}
]The whatsapp number must be in international format without the +. It is optional — branches without it won't appear in the WhatsApp selector.
# Order taker assigned to a specific branch
node create-user.js ahmed password123 order_taker cairo-branch
# Roaming order taker (can log in to any branch)
node create-user.js sara password123 order_taker
# Manager (access to all branches)
node create-user.js manager1 password123 managerLocated in public\ringtone.mp3 and public\favicon.jpg respectively. Best size for a favicon is 48x48. Place and rename files as required.
npm run devThe server generates a self-signed SSL certificate automatically. Your browser will show a security warning — click Advanced → Proceed anyway.
To test on a phone over WiFi, go to chrome://flags/#unsafely-treat-insecure-origin-as-secure, add https://YOUR_LOCAL_IP:3000, enable, and relaunch Chrome.
Set these environment variables (in .env or your server environment):
NODE_ENV=production
SSL_KEY_PATH=/etc/letsencrypt/live/yourdomain.com/privkey.pem
SSL_CERT_PATH=/etc/letsencrypt/live/yourdomain.com/fullchain.pem
TURN_URL=turn:turn.yourdomain.com:3478
TURN_USER=supermarket
TURN_PASS=your_turn_password
ALLOWED_ORIGINS=https://order.yourdomain.comThen build and start:
npm run build
node server.js- SSH into your VPS
- Install Node.js, Nginx, Certbot, coturn
- Upload project files via FileZilla (SFTP to
/var/www/bestway) - Create
.envandusers.jsondirectly on the server - Run
npm install && npm run build - Set up systemd service (see below)
- Get SSL certificate with Certbot
- Configure Nginx reverse proxy
Create /etc/systemd/system/bestway.service:
[Unit]
Description=Bestway Live Shopping
After=network.target
[Service]
WorkingDirectory=/var/www/bestway
ExecStart=/usr/bin/node server.js
Restart=always
Environment=NODE_ENV=production
EnvironmentFile=/var/www/bestway/.env
[Install]
WantedBy=multi-user.targetsystemctl enable bestway
systemctl start bestwayUpload changed files via FileZilla, then:
# If you changed any app/ files or globals.css
npm run build
systemctl restart bestway
# If you only changed server.js or .env
systemctl restart bestwayRequired for calls between users on different networks (mobile data, different ISPs). Without it, most calls still work on the same WiFi but will fail on real networks.
Install:
apt install coturnConfigure /etc/turnserver.conf:
listening-port=3478
tls-listening-port=5349
external-ip=YOUR_VPS_IP
realm=turn.yourdomain.com
lt-cred-mech
user=supermarket:YOUR_TURN_PASSWORD
cert=/etc/letsencrypt/live/order.yourdomain.com/fullchain.pem
pkey=/etc/letsencrypt/live/order.yourdomain.com/privkey.pem
Open firewall ports:
ufw allow 3478
ufw allow 3478/udp
ufw allow 5349All events are written to logs/YYYY-MM.txt and logs/YYYY-MM.json automatically.
Logged events:
| Event | What's recorded |
|---|---|
login_success |
userId, role, branch |
login_failed |
userId, reason, IP |
customer_joined_queue |
name, phone, address, branch |
order_taker_available |
userId, branch |
order_taker_offline |
userId, branch |
order_taker_logout |
userId, branch |
call_started |
order taker, customer details, branch |
call_ended |
order taker, customer details, duration |
Plain text example:
[2026-03-08 14:32:01] CALL_STARTED orderTaker=ahmed branchId=cairo-branch customerName=Mohamed Ali customerPhone=201012345678
[2026-03-08 14:45:17] CALL_ENDED orderTaker=ahmed branchId=cairo-branch customerName=Mohamed Ali duration=13:16
- Passwords stored as bcrypt hashes (never plain text)
- TURN credentials served from server — never exposed in client source
- Rate limiting on login, queue join, and WebRTC signaling
- WebRTC signals only relayed between verified paired sockets
- CORS restricted to
ALLOWED_ORIGINSin production - Branch assignment enforced per user (roaming users can opt out)
| Layer | Technology |
|---|---|
| Frontend | Next.js 15, TypeScript |
| Styling | Plain CSS (globals.css) |
| Realtime | Socket.io |
| Video/Audio | WebRTC (MediaDevices API) |
| Server | Node.js, HTTPS |
| Auth | bcryptjs |
| TURN | coturn |
| Reverse proxy | Nginx |
| SSL | Let's Encrypt (Certbot) |
Each branch can have a WhatsApp number set in branches.json. A floating button on the customer page lets them open a WhatsApp chat directly with their branch to change orders or ask questions.
Number format: international without + — e.g. 201012345678 not +201012345678.
- All server state is in memory. If the server restarts, active calls and queues are cleared. This is intentional for simplicity — Redis would be needed at scale.
users.json,branches.jsonand.envmust be created manually on the server and are never committed to git.- Logs rotate automatically by month. Archive or delete old log files periodically to manage disk space.