A WebRTC softphone demo built with Vue 3, demonstrating peer-to-peer call signaling through a B2BUA (Back-to-Back User Agent) over WebSocket.
The frontend handles the full call lifecycle — outbound dial, inbound ringing, answer, transfer, multi-party invite, hold/mute, and ICE negotiation against a TURN server.
- WebRTC P2P calling — direct media flow between peers, signaled through a B2BUA backend
- Outbound & inbound calls with ringing, busy, and reject states
- Call transfer — to another agent or to a queue
- Multi-party invite — bring additional agents into an active call
- Hold, mute mic, mute speaker
- Auto-reconnect WebSocket signaling
- Browser notifications for incoming calls
- Internal agent directory — search and dial online agents directly
- Vue 3 (Composition API) + Vite
- Naive UI component library
- Pinia state management
- Vue Router with auth guard
- Axios for REST API
- Native WebRTC + WebSocket APIs
src/
├── components/
│ ├── softphone/ # Softphone UI (dial, ringing, calling, transfer, invite)
│ │ └── handler/ # WebRTC + signaling logic
│ ├── AccountButton.vue
│ ├── AvatarContact.vue
│ └── MIcon.vue
├── pages/
│ ├── b2bua/ # Main B2BUA page
│ └── Login.vue
├── layouts/ # Desktop layout + top bar
├── services/ # API services (account, queue, workspace, websocket)
├── stores/ # Pinia stores (auth, app)
├── helper/ # Utilities (contact, phone format, date)
├── const/ # Endpoints, socket commands, storage keys, call constants
├── router/
└── main.js
- Node.js 18+
- B2BUA backend that speaks the WebSocket protocol used by
src/components/softphone/handler/Softphone.js(seesrc/const/SocketCMD.jsfor the command list) - TURN server for NAT traversal
- For local HTTPS development: a wildcard cert pair (e.g. via mkcert) — see Local HTTPS below
# 1. Clone and install
git clone <your-repo-url>
cd demo-call-b2bua-p2p
npm install
# 2. Configure environment
cp .env.example .env.local
# then edit .env.local with your values
# 3. Run dev server
npm run devDev server runs at https://<your-local-domain>:8080.
Copy .env.example to .env.local and fill in your values:
| Variable | Description |
|---|---|
VITE_API_URL |
Base URL for REST API requests (use / if proxied via Vite) |
VITE_ENV |
local enables withCredentials for cross-origin auth |
VITE_ROOT_DOMAIN |
Root domain used to build workplace URLs |
VITE_ID_BASE_URL |
Identity / account service URL |
VITE_FILE_SERVER_BASE_URL |
File server / CDN URL |
VITE_TURN_URL |
TURN server URL (e.g. turn:turn.example.com:3478?transport=udp) |
VITE_TURN_USERNAME |
TURN credential username |
VITE_TURN_CREDENTIAL |
TURN credential password |
VITE_PROXY_API_TARGET |
Backend target for /api/* requests in dev |
VITE_PROXY_WS_TARGET |
Backend target for /ws WebSocket in dev |
WebRTC requires a secure context. The dev server is configured for HTTPS at port 8080 and expects a cert pair in the project root:
_wildcard.omistack.local-key.pem
_wildcard.omistack.local.pem
Generate them with mkcert:
mkcert -install
mkcert "*.your-domain.local"Rename the output files (or update the paths in vite.config.js) and add an entry to your hosts file pointing the domain to 127.0.0.1.
| Command | Description |
|---|---|
npm run dev |
Start the Vite dev server (HTTPS) |
npm run build |
Production build to dist/ |
npm run preview |
Preview the production build locally |
The frontend connects to the backend via WebSocket at /ws. Each command is a JSON message with cmd and params fields. Key commands:
call_start_req/call_start_resp— initiate outbound callcall_answer_req— answer / reject / ringing notificationsdp_req/sdp_ntf— SDP offer/answer exchangecandidate_req/candidate_ntf— ICE candidate exchangecandidate_completed_req— ICE gathering completecall_transfer_req/call_invite_req— transfer or invite agentsend_call_req— terminate call
See src/const/SocketCMD.js for the full list.
- User dials a number →
call_start_req - Backend responds with
call_start_resp→ frontend createsRTCPeerConnection, generates SDP offer, sends viasdp_req - Backend forwards remote SDP via
sdp_ntf→ frontend sets remote description - ICE candidates flow both directions via
candidate_req/candidate_ntf - Once ICE completes, media flows peer-to-peer
WebRTC orchestration lives in src/components/softphone/handler/WebRTC.js; call state machine in src/components/softphone/handler/Softphone.js.
Modern browsers with WebRTC support: Chrome, Edge, Firefox, Safari (latest 2 versions).
MIT — see LICENSE (add one before publishing).
Issues and pull requests are welcome. Before opening a PR, please run npm run build to verify the project still compiles.