Call State Machine Diagram - nself-org/nchat GitHub Wiki
Visual representation of the call state machine and valid transitions.
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Call State Machine β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
ββββββββββββ
β idle β
βββββββ¬βββββ
β initiateCall()
βΌ
βββββββββββββββ
β initiating βββββββΊ ending ββΊ ended ββΊ idle
ββββββββ¬βββββββ (cancel)
β
ββββββββββββββΌβββββββββββββ
β β
βΌ βΌ
βββββββββββββββ βββββββββββββββ
β ringing β β connecting β (group call)
ββββββββ¬βββββββ ββββββββ¬βββββββ
β β
β answered() β
βΌ β
βββββββββββββββ β
β connecting ββββββββββββββββββ
ββββββββ¬βββββββ
β connected()
βΌ
βββββββββββββββ
βββββΊβ connected ββββββ
β ββββββββ¬βββββββ β
β β β
β ββββββββΌβββββββ β
β β β β β
β βΌ βΌ βΌ β
β ββββββ ββββββ βββββββ
ββββheldβ βtranβ βrecoββ
ββββββ βsferβ βnnecββ
ββββββ βtingββ
βββββββ
β
βΌ
βββββββββββ
β ending β
ββββββ¬βββββ
β
βΌ
ββββββββββ
β ended β
ββββββ¬ββββ
β
βΌ
ββββββββββ
β idle β
ββββββββββ
Description: No active call Can Transition To: initiating Typical Duration: N/A (waiting state) Actions: None
Description: Creating call, getting media, initializing connection Can Transition To: ringing, connecting, ending, ended Typical Duration: 1-2 seconds Actions:
- Request microphone/camera permissions
- Get local media stream
- Create RTCPeerConnection
- Generate call ID
Description: Calling recipient, waiting for answer Can Transition To: connecting, ending, ended Typical Duration: Up to 30 seconds (timeout) Actions:
- Send call invitation
- Play ring back tone
- Wait for acceptance
Description: WebRTC negotiation in progress (offer/answer exchange) Can Transition To: connected, ending, ended Typical Duration: 2-5 seconds Actions:
- Exchange SDP offers/answers
- Gather ICE candidates
- Establish peer connection
Description: Call is active, media flowing Can Transition To: held, transferring, reconnecting, ending, ended Typical Duration: Varies (minutes to hours) Actions:
- Stream audio/video
- Monitor quality
- Handle media controls (mute, video toggle)
Description: Call on hold, media paused Can Transition To: connected, transferring, ending, ended Typical Duration: Varies Actions:
- Mute audio
- Pause video
- Show "on hold" status
Description: Call being transferred to another user Can Transition To: connected, ending, ended Typical Duration: 2-5 seconds Actions:
- Coordinate with transfer target
- Hand off media streams
- Notify participants
Description: Network issue, attempting to reconnect Can Transition To: connected, ending, ended Typical Duration: Up to 10 seconds Actions:
- Monitor ICE connection state
- Attempt to reestablish connection
- Show reconnecting UI
Description: Hanging up, cleaning up resources Can Transition To: ended Typical Duration: <1 second Actions:
- Close peer connection
- Stop media streams
- Send end call signal
Description: Call has ended, showing summary Can Transition To: idle Typical Duration: Brief (UI shows call ended) Actions:
- Display call duration
- Save call history
- Release resources
| From State | Valid Next States | Triggers |
|---|---|---|
| idle | initiating | User initiates call |
| initiating | ringing | Invitation sent (1-on-1) |
| initiating | connecting | Direct connection (group) |
| initiating | ending | User cancels |
| initiating | ended | Error during setup |
| ringing | connecting | Recipient answered |
| ringing | ending | User hangs up or timeout |
| ringing | ended | Recipient declined |
| connecting | connected | WebRTC established |
| connecting | ending | Connection failed |
| connecting | ended | Fatal error |
| connected | held | User puts on hold |
| connected | transferring | User transfers call |
| connected | reconnecting | Network issue |
| connected | ending | User hangs up |
| held | connected | User resumes |
| held | transferring | Transfer while held |
| held | ending | User ends held call |
| transferring | connected | Transfer complete |
| transferring | ending | Transfer cancelled |
| reconnecting | connected | Reconnected |
| reconnecting | ending | Reconnection failed |
| ending | ended | Cleanup complete |
| ended | idle | Reset to idle |
These transitions are not allowed:
- idle β connected (must go through initiating)
- ringing β held (can't hold unanswered call)
- ended β connected (can't revive ended call)
- Any state β ringing (except from initiating)
The state machine tracks three types of duration:
Time spent in the current state (since last transition).
const stateDuration = machine.getCurrentStateDuration() // millisecondsTotal time from idle β initiating until now.
const totalDuration = machine.getTotalDuration() // millisecondsTotal time spent in "connected" state (excludes held, reconnecting).
const connectedDuration = machine.getConnectedDuration() // millisecondsidle
β initiating (getting media)
β ringing (calling recipient)
β connecting (WebRTC negotiation)
β connected (talking)
β ending (hanging up)
β ended (call summary)
β idle (ready for next call)
Total Time: ~2 minutes Connected Time: ~1 minute 50 seconds
idle
β initiating
β ringing
β connecting
β connected (talking)
β held (on hold for 30s)
β connected (resumed)
β ending
β ended
β idle
Total Time: ~3 minutes Connected Time: ~2 minutes 30 seconds (excludes held time)
idle
β initiating
β ringing
β connecting
β connected (talking)
β reconnecting (network issue for 5s)
β connected (reconnected)
β ending
β ended
β idle
Total Time: ~2 minutes Connected Time: ~1 minute 55 seconds
idle
β initiating (getting media)
β ringing (calling recipient)
β ending (user cancelled)
β ended
β idle
Total Time: ~10 seconds Connected Time: 0 seconds
idle
β initiating
β ringing
β ended (recipient declined)
β idle
Total Time: ~5 seconds Connected Time: 0 seconds
idle
β initiating
β ringing
β connecting (WebRTC negotiation)
β ending (connection failed)
β ended
β idle
Total Time: ~15 seconds Connected Time: 0 seconds
idle
β initiating
β connecting (direct connection)
β connected (all participants join)
β ending
β ended
β idle
Total Time: ~5 minutes Connected Time: ~4 minutes 55 seconds
The state machine emits events for each transition:
{
from: 'ringing',
to: 'connected',
timestamp: Date,
reason?: 'answered',
metadata?: {
answeredBy: 'user-123',
delay: 5000
}
}// Listen to all transitions
machine.on('transition', (event) => {
console.log(`${event.from} -> ${event.to}`)
})
// Listen to specific state entry
machine.on('enter:connected', (event) => {
console.log('Call connected!')
startQualityMonitoring()
})
// Listen to specific state exit
machine.on('exit:connected', (event) => {
console.log('Call no longer connected')
stopQualityMonitoring()
})
// Listen to invalid transitions
machine.on('invalid-transition', ({ from, to }) => {
console.warn(`Cannot transition from ${from} to ${to}`)
})The state machine maintains a history of all transitions:
const history = machine.getHistory()
// Example history:
[
{ from: 'idle', to: 'initiating', timestamp: Date, reason: 'user-initiated' },
{ from: 'initiating', to: 'ringing', timestamp: Date },
{ from: 'ringing', to: 'connecting', timestamp: Date, reason: 'answered' },
{ from: 'connecting', to: 'connected', timestamp: Date },
{ from: 'connected', to: 'ending', timestamp: Date, reason: 'user-hangup' },
{ from: 'ending', to: 'ended', timestamp: Date },
]This history is useful for:
- Debugging state issues
- Analyzing call flows
- Calculating durations
- Audit trails
if (machine.canTransitionTo('connected')) {
machine.transition('connected')
} else {
console.error('Invalid transition to connected')
}machine.transition('ending', 'user-hangup')
machine.transition('ended', 'network-error', { error: err })machine.on('invalid-transition', ({ from, to }) => {
showError(`Cannot ${to} call while ${from}`)
})// Show live duration
setInterval(() => {
if (machine.isState('connected')) {
const duration = machine.getConnectedDuration()
updateCallTimer(duration)
}
}, 1000)machine.on('enter:ended', () => {
const connectedDuration = machine.getConnectedDuration()
const totalDuration = machine.getTotalDuration()
// Save to history
saveCallHistory({
connectedDuration,
totalDuration,
history: machine.getHistory(),
})
// Reset for next call
setTimeout(() => {
machine.transition('idle')
machine.reset()
}, 2000)
})const machine = createCallStateMachine({
onTransition: (event) => {
console.log('[State]', event.from, '->', event.to, event.reason)
},
onInvalidTransition: (from, to) => {
console.error('[State] Invalid:', from, '->', to)
},
})console.log('Current State:', machine.getState())
console.log('Display Name:', machine.getStateDisplayName())
console.log('Is Active?', machine.isActive())
console.log('Valid Next States:', getValidTransitions(machine.getState()))const history = machine.getHistory()
console.log('Call Flow:')
history.forEach((event, i) => {
console.log(`${i + 1}. ${event.from} -> ${event.to} (${event.reason || 'no reason'})`)
})Version: 0.4.0 Last Updated: January 30, 2026