VOIP CLIENT

VoIP and messaging on the web. No native app required.
This is a personal project: a web-native VoIP and messaging platform that does what native apps do, entirely in the browser. SIP-based calling, real-time messaging, and contact management, built from scratch as a solo design and engineering exercise.
VoIP on the web is notoriously hostile.
WebRTC complexity
SIP + WebRTC isn't a stack you pick up in an afternoon. ICE negotiation, STUN/TURN servers, codec negotiation, NAT traversal: each layer has its own failure modes. Without proper abstraction, the calling logic bleeds into every part of the application.
Call state reliability
A dropped connection is forgivable. A UI that shows "Connected" when the call has dropped is not. Call state machines in WebRTC apps are inherently async and event-driven, so without explicit state modelling, race conditions produce UI states that don't match reality.
Two real-time systems, one interface
VoIP signaling and chat messaging are fundamentally different real-time transports: SIP over WebSocket vs. a standard WS message channel. The interface needed to present them as a unified experience without the underlying complexity leaking through to the user.
Abstract the hard parts. Model the state machine. Unify the transport.
SIP.js abstraction layer
A custom call manager sits on top of SIP.js, exposing a clean interface to the app: initiate, answer, hold, mute, hangup, with no ICE logic, no codec negotiation, just call state.
SIP.js UserAgent configured once at app init; STUN/TURN credentials injected from environment. All SIP events (invite, bye, failed, terminated) mapped to state machine transitions. The UI layer never touches SIP primitives directly. Media constraints negotiated at session start.
Explicit call state machine
Call lifecycle modelled as an explicit state machine: Idle → Ringing → Connecting → Connected → Held → Ended. Invalid transitions are rejected; the UI subscribes to state, not events: no ghost states, no flicker.
Zustand store per active call; transitions trigger via actions, not scattered event handlers. Local audio mute tracked independently from SIP hold; same UI update, different underlying mechanics. Full call history with duration and timestamps persisted to IndexedDB across sessions.
Unified socket layer
SIP signaling and chat messages share a single WebSocket with multiplexed channels; from the client's perspective, one connection handles both, and graceful reconnection covers both transports simultaneously.
Custom WS multiplexer with message-type headers for routing. Exponential backoff reconnection with queued message replay on reconnect. In-progress calls survive brief disconnects via SIP re-INVITE. Presence state (online/away/in-call) broadcast over the same channel, with no separate polling endpoint.
VoIP Calling
SIP-based voice calls directly in the browser. Mute, hold, and transfer: the full native-app feature set without an install. Audio waveform visualization, call timer, and instant state feedback. Sub-100ms audio path via optimized ICE/STUN configuration.


Real-time Messaging
Persistent chat alongside every call. Messages survive call disconnects and sync across sessions. Read receipts, delivery status, and presence indicators, all built on the same WebSocket channel as call signaling, so nothing is ever out of sync.
Contact Management
Contact list with live presence indicators: online, away, in-call. One tap to call or message. Presence broadcast over the unified WebSocket channel, updated in real time without polling. Call history per contact with duration and timestamps.

That's a wrap
You've seen everythingFive projects. One portfolio.