Walkie-Talkie
A real-time communication and messaging platform built with React and Vite. Supports cloud-based messaging via Appwrite and LAN peer-to-peer audio via WebRTC (PeerJS). Users join shared rooms using hashed passwords (cloud) or generated room codes (LAN), communicate via corkboard messages or push-to-talk audio, with no user accounts required.
Tech Stack
| Technology | Version | Purpose |
|---|---|---|
| React | ^19.2.3 | UI framework — functional components + hooks |
| Vite | ^7.3.1 | Build tool / dev server (migrated from CRA) |
| JavaScript (ES2022) | ES Modules | Primary language — no TypeScript |
| Appwrite SDK | ^21.5.0 | Backend — auth, database, storage |
| PeerJS | ^1.5.5 | WebRTC abstraction for LAN peer-to-peer audio |
| Web Audio API | Browser built-in | Audio recording and playback |
| Vitest | ^4.0.17 | Test framework with jsdom environment |
| ESLint | Flat config | Linting with React + React Hooks plugins |
| Vercel | — | Deployment |
Key Decisions
| Decision | Rationale | Outcome |
|---|---|---|
| React + Vite | Modern build tooling, fast HMR, simpler than CRA | Good — fast dev iteration |
| Migrate Firebase to Appwrite | Simpler self-hosting, aligned with org infrastructure goals | Good — easier backend control |
| SHA-256 hashed password as corkboard ID | Password-derived ID without server-side auth | Good — simple shared access model |
| Appwrite anonymous auth | Session management without user accounts | Good — frictionless entry |
| PeerJS + PeerServer for WebRTC | Abstracts WebRTC connection complexity for LAN comms | Good — simpler peer management |
| Predictable host peer ID format | roomCode-host enables joiners to find host directly | Good — no discovery server needed |
| Exclude confusing chars from room codes | No 0/O, 1/I/L for human readability | Good — fewer user errors |
| crypto.getRandomValues for room codes | Secure random generation | Good — cryptographically safe |
| useRef for peer instance tracking | Avoids stale closures in PeerJS event handlers | Good — prevents callback bugs |
| 3 simplified connection states | connecting/connected/disconnected for clean UI | Good — easy to reason about |
| navigator.onLine for connection detection | Zero-dependency connection status using browser API | Good — lightweight, reliable |
| Answer calls without stream for receive-only | Enables listening before microphone is enabled | Good — flexible audio model |
| 50 message limit per corkboard | Prevents free-tier Appwrite storage abuse | Good — explicit resource limits |
Patterns Established
| Pattern | Description | Reusable |
|---|---|---|
| useConnectionStatus hook | Tracks browser online/offline via navigator.onLine | Yes |
| useNewMessageNotification hook | Detects new incoming messages, excludes own messages | Yes |
| usePeer hook | Manages PeerJS peer connection lifecycle and state | Yes |
| useRoom hook | Room creation, joining, peer tracking via data channels | Yes |
| useAudio hook | WebRTC audio stream management with mic permissions | Yes |
| appwriteService abstraction | Service layer wrapping Appwrite SDK operations | Yes |
| MIME type fallback pattern | Fallback chain for MediaRecorder (webm/mp4/ogg) | Yes |
| Centralized constants file | AUDIO, STORAGE, SECURITY, UI, FEATURES constants | Yes |
| ErrorBoundary component | React error boundary with fallback UI | Yes |
| ErrorToast component | Auto-dismissing error notification | Yes |
Lessons Learned
| Lesson | Impact |
|---|---|
| Safari/iOS needs mp4/x-m4a MIME fallbacks for MediaRecorder | WebM not supported on all browsers — always implement fallback chain |
| useRef avoids stale closures in PeerJS event handlers | State values were stale inside peer callbacks |
| Real-time Appwrite subscriptions need refetch pattern | Subscribe for notifications, then refetch full data |
| Retry connection with ref tracking prevents cleanup race conditions | React cleanup could cancel in-progress connections |
| Input sanitization must strip null bytes and control characters | Security hardening for user-facing password input |
Technical Debt
| Item | Priority | Notes |
|---|---|---|
| Floor control (PTT) not implemented | High | Phase 6 planned but not started |
| Mode switcher UI pending | High | Cloud vs LAN/PTT mode selection not built |
| PeerServer hardcoded to localhost:9000 | Medium | Needs configuration for LAN deployment |
| No end-to-end encryption | Medium | Planned for v1.2 |
| REACT_APP_ env vars in constants.js | Low | Legacy CRA prefix; should be VITE_ |
| No TypeScript | Low | Plain JavaScript; TS would improve maintainability |
Status
Active development — v1.1 LAN Push-to-Talk phase 5 of 7 complete. Two phases remain: Floor Control and Mode Switcher. CI/CD passing, test coverage >85%, deployed on Vercel.
Related
- SoundBoard — shares Web Audio API and MIME type fallback patterns
- Tech Stack — org-wide toolchain decisions