Dialer docsSocket.IO
Home Progressive Predictive Callback Disposition API Socket Dashboard

Socket.IO

What the client emits. What the server emits. Who listens.

Agent panel and dashboard share one Socket.IO server on /socket.io/. This is the wire protocol only. Calling is in the Progressive and Predictive guides. Lead ingest is the HTTP API.

WS_SECRET handshake Rooms: dashboard / ext:{n} src/ws/events.js

Connect

const socket = io('https://YOUR_HOST', {
  transports: ['websocket'],
  auth: { token: 'YOUR_WS_SECRET' }
});
socket.on('connect', () => {
  socket.emit('join', { role: 'agent', extension: '1007' });
});

WS_SECRET is required. Send it as handshake.auth.token, or query token / key. Wrong key → connect_error unauthorized. After connect, emit join or you are in no room.

Rooms

RoomJoin asReceives
dashboard{ role: 'dashboard' }Snapshot, patch, every dialer event, health, panel
ext:{extension}{ role: 'agent', extension }That agent’s status, dialer, call_connected

One socket is one role. Join again leaves the previous room. Several tabs on the same extension share the room. Disconnect waits 3 seconds before the agent is treated as gone (refresh-safe).

Client emits — server listens

The browser emits. Hub in src/ws/hub.js listens.

EmitPayloadServer does
join{ role: 'dashboard' } or { role: 'agent', extension: '1007' }Join room, then emit joined plus initial data
leave—Leave room. Agent: decrement panel count
ready—Presence ready. Writes dialer_employee.panel_status = active
pause—Presence paused. Writes dialer_employee.panel_status = pause
presence{ presence: 'ready' | 'paused' }Same as ready / pause
ping—Reply pong
redial{ oli_id }Re-originate that lead on this extension (skip wrap / queue)

Socket.IO disconnect is treated as leave. Missing extension on agent join → server_error extension is required to join as agent. Extension not in this module’s dialer_employee roster → extension is not assigned to this dialer. Ready/pause before join also emits server_error. redial before join, or without oli_id, returns { ok: false } (and server_error if you did not pass an ack callback). A successful agent join reloads that employee row and syncs the AMI roster.

Daily day reset at PAUSE_DAY_RESET_AT (default 09:30 IST / DB_TIMEZONE) closes open pauses that started before today (resume_reason = day_reset) and sets panel_status = yet_to_login. The dialer ignores those desks until the agent goes Active. Same-morning pauses stay open. Kill switch: PAUSE_DAY_RESET_ENABLED=0. The dashboard On pause list excludes yet_to_login; Work / Reason show yet to login instead of a pause clock.

Server emits — client listens

The hub emits. The browser listens.

ListenRoomWhen
joinedThat socketAfter a successful join
snapshotdashboardDashboard join, and full AMI snapshot
patchdashboardAMI incremental device-state changes
statusext:{n}AMI + panel/presence for that extension
paneldashboardAgent join, leave, ready, pause
presencedashboardLast tab for an extension left (reason: 'left')
dialerBothEngine phase: claim, buffer, connect, hangup
call_connectedBothBoth parties talking (once per call)
healthdashboardAMI/DB health
pongThat socketReply to ping
server_errorThat socketJoin / presence failures

Payloads

joined

{ "role": "agent", "extension": "1007", "rooms": ["ext:1007"] }

Dashboard join is { "role": "dashboard", "rooms": ["dashboard"] }.

dialer

Always has extension and phase: idle, paused, buffer, waiting, dialing, on_call. Also buffer_ms / buffer_ends_at (wrap after a connected call only), connect_ms, on_call_at, talk_ms, client_name, oli_id, form_type. Predictive adds outbound (other ringing/hold legs, phase ringing or waiting) and a floor-wide on_hold count of answered customers on MusicOnHold.

call_connected

{
  "extension": "1007",
  "oli_id": "DUMMY-001",
  "form_type": "GST",
  "priority_id": 6
}

Once per call when the conversation is actually up.

redial

After a drop, the CRM or the agent page emits redial from an agent socket. The engine skips wrap time and the queue, pins the lead to this agent, and originates again (progressive call.php, predictive customer-first to this extension). The agent panel Answered calls list uses student_id and sends ondemand: true while paused.

socket.emit('redial', { oli_id: 'OLI124' }, (result) => {
  // { ok: true, oli_id: 'OLI124' }
  // { ok: false, error: 'busy'|'not_ready'|'unknown_oli'|'originate_failed', message: '…' }
});

Works on Received, Not Connected, Abandoned, and Disposed rows. Rejected if the agent is already on another call, paused, or SIP is not idle. Then listen for dialer phase: dialing and call_connected.

status / panel

One extension: AMI category, panel active/inactive, presence ready/paused, pause_reason (open pause code while paused), pause_reason_note when the reason is Other, paused_at (epoch ms when the current pause started; dashboard Work column counts from this), name from fic_dialer_employee.display_name, assigned_roles from fic_dialer_employee.skills (JSON), panel_status (active / pause / yet_to_login), buffer_time, hold_timeout_seconds, microsip_alert when the panel is up but AMI is unavailable. CRM profile fields such as email/mobile are not on fic_dialer_employee and may be null. After day reset, panel_status is yet_to_login and pause reason/clock are null until the agent goes Active.

snapshot

updated_at, ami, summary (AMI on_call / idle / unavailable / total plus predictive on_hold), dialer (active config for this module’s module_id, including hold_timeout_seconds), extensions[] (same shape as status, only endpoints present in this module’s dialer_employee roster — not every PJSIP contact).

Built-in pages

PageEmitsListens
Dashboard /join dashboardsnapshot, patch, dialer, panel, presence, health
Agent /agent.html?ext=1007join agent, ready / pause, redialstatus, dialer, call_connected, joined, server_error

Socket does not push leads. Use POST /api/leads and PUT /api/escalated-tickets. Normal outbound is still engine-driven; redial is the client asking the engine to originate one specific lead now. Hub onAgentState is an in-process callback into the dialer, not a Socket.IO event.