This demo showcases a simulation of call center where callers queue up to talk to available agents using OpenTok.
These are the OpenTok features used in this demo:
- Server-side SDK (NodeJS): Used to created dynamic session and token creation per call
- Publish and subscribe streams: For connecting agent with caller
- Signaling - To signal for agent join, call hold and call resume.
- Session monitoring - To keep active caller list updated
Install NodeJS v8.0+
Install dependencies with npm
$ npm install
Get OpenTok API keys and set them as environment variables:
$ export OPENTOK_API_KEY="opentok-api-key-here"
$ export OPENTOK_API_SECRET="opentok-api-secret-here"
Register session monitoring callback in your TokBox account for the path /ot_callback
. For example, if this application is hosted at https://example.com
, register this URL: https://example.com/ot_callback
.
Build assets for the app, run:
$ npm run app-build
$ npm start
This will start the application on port 8080
. To change port, set the PORT
environment variable. For example, to start the application on port 3000
, do this:
$ PORT=3000 npm start
To start secure server, set the SECURE
environment variable to some value. For example, this will start the application on HTTPS port 3000:
$ SECURE=1 PORT=3000 npm start
For development use, you can compile assets in development mode and launch server by running:
$ npm run dev
This application builds a simple queue where callers can wait for agents to talk to them. It uses NodeJS as the backend and VueJS in the frontend.
Note: The server stores all data in-memory for the purpose of this demo. A real-world application should use some database instead.
- Callers provide their name and reason for call when joining. They can also select whether they want to join as audio-only or using both audio and video.
- Once callers have joined, they wait in queue till an agent joins that call.
- Callers remain connected when they have been put on hold or if agent has disconnected.
- Callers can decide to exit the call at any time.
- Callers exit if agent triggers call end.
- Agents join and wait for callers to be available.
- Each agent can be assigned multiple callers. Assignment is done using least connections strategy - when a new caller joins, the caller is assigned to the agent with least number of assigned callers.
- When a caller is assigned to agent, agent's caller list is updated.
- Agent can join one caller at a time.
- Agent can put an existing caller on hold to join another caller.
- Agent can resume a call.
- Agent can end the call they are connected to. This will make the caller exit as well.
- Agent can switch between callers that agent has been assigned. If agent switches to another caller, then the current caller is put on hold.
Backend logic for this demo is present in server.js. These are what the server does:
- Call OpenTok REST API using the OpenTok Node SDK.
- Create functions to generate OpenTok sessions and tokens.
- Define
Caller
constructor used to represent a caller and provide methods to perform actions on each caller. - Define
Agent
constructor used to represent an agent and provide methods to perform actions on each agent. - Manage pending callers queue - A list of callers who are yet to be assigned to any agent.
- Perform assignment of callers to available agents.
- Send signals through the OpenTok REST API to coordinate interactions between agent and caller.
- Handle OpenTok session monitoring callbacks for connection created and connection destroyed events. This is used to keep track of when callers are ready to join agents and when they have left.
- Provides HTTP interfaces for frontend to communicate, with endpoints for agents and callers.
The frotend is a simple single-page application (SPA) built on VueJS with vue-router to route between agent and caller screens. It uses UIKit for drawing the UI. The demo intentionally keeps things simple without breaking down code into further components or customizing much of the UI elements.
These are the relevant files:
app.js
: Bootstrapping script that loads vue and vue-router and mounts routes from the components.components/
: Directory where all vue components are storedcomponents/home.vue
: Template for the homepage of the democomponents/caller.vue
: Component used for the caller screen. This sets up the caller's initial form and then manages the whole lifecycle of the caller.components/agent.vue
: Component used for the agent screen. This manages entire lifecycle of the agent.components/ot-publisher.vue
andcomponents/ot-subscriber.vue
provide reusable components for OpenTok publisher and subscriber objects.
The agent screen has all the magic in this demo. Here is how agent screen handles callers in the frotnend:
- Each caller uses a different OpenTok session.
- When agent wants to join a caller, the frontend retrieves a token for the agent for the session of that caller. Then, it connects agent to that session, publishes agent stream and subscribes to existing caller stream.
- When agent wants to put a caller on hold, agent unpublishes their stream and disconnects from the session.
- When agent wants to resume talking to a caller they have put on hold, step 2 is repeated.
So, the agent keeps on switching between OpenTok sessions - connecting to them and disconnecting as required. This whole process takes a reasonably short time. At each stage, the application sends out signals for each event so that the client UI can adjust accordingly.
A core part of this demo is managing caller queue and assigning callers to agents. All of this happens on the server side in server.js
. It uses OpenTok's session monitoring to reliably determine when a caller has connected or disconnected.
Call queue management is composed of six main pieces:
callers[]
: List of current callers. This is aMap
that uses caller ID as key and its correspondingCaller
instance as the value.agents[]
: List of active agents. This is anotherMap
that uses agent ID as key and its correspondingAgent
instance as the value.pendingQueue[]
: An array that stores callers (instances ofCaller
) who are yet to be assigned to any agent.assignCaller(caller)
: A function that takes aCaller
instance as argument and assigns it to an agent. If no agent is connected, this function adds the given caller topendingQueue[]
.agent.assignPending(limit = 1)
: A method onAgent
that assigns a number of callers frompendingQueue[]
to given agent in FIFO mode - callers who were in the queue earlier are assigned first.removeCaller(callerID)
: A function that removes a caller from list of active callers and also frompendingQueue[]
.
Here is a step-by-step description of how the call queue logic is handled:
- When a caller connects to the application, the caller screen access the HTTP endpoint at
GET /dial
.- This creates a new instance of the
Caller
constructor by callingnew Caller()
based on the caller's supplied details - name, reason and whether the call is audio-only - The caller is initial marked as not ready.
- Then, it tries to assign the caller to an agent by calling
assignCaller(caller)
. - Then, it creates a new OpenTok session and token for this caller and sends out a response with the caller status.
- This creates a new instance of the
- The caller screen on the frontend then uses this information to connect to the given OpenTok session and starts publishing the caller's stream.
- The server listens to OpenTok session monitoring callbacks in the HTTP endpoint
POST /ot_callback
.- When a caller connects to the session, OpenTok posts data with caller's connection information to this endpoint. The endpoint calls the
handleConnectionCreated()
handler to match the connection data with the caller ID and marks the caller as ready. - Similarly, when a caller disconnects from OpenTok, OpenTok posts the caller's connection information. Then, the endpoint cleans up the caller info by calling
removeCaller(callerID)
.
- When a caller connects to the session, OpenTok posts data with caller's connection information to this endpoint. The endpoint calls the
- When an agent joins, the agent interface calls the HTTP endpoint at
POST /agent
.- This creates a new instance of the
Agent
constructor. - Then it attempts to assign first 3 pending callers by calling
agent.assignPending(3)
. - Then it returns the agent ID.
- This creates a new instance of the
- The agent screen then keeps on polling the list of callers assigned to the current agent by hitting the HTTP endpoint at
/agent/:id/callers
.- This attempts to assign the first caller in the
pendingQueue[]
by callingagent.assignPending(1)
if the agent has less than 3 callers assigned at that point. - Then it returns list of currently assigned callers who are marked as ready (see step 3 above). This ensures that the agent screen only sees list of callers who are currently connected to OpenTok.
- This attempts to assign the first caller in the
- When a caller needs to be removed, then frontend calls the HTTP endpoint at
GET /call/:id/delete
. This callsremoveCaller(callerID)
internally. The frontend can call this HTTP endpoint when any of these events happen:- Caller ends call by clicking "End call" button in the caller's screen
- Agent ends current call by clicking "End call" button in the agent's screen
- Caller closes their browser window when call is ongoing
- When agent exits, either by closing their browser window or by clicking the "Exit" button, then existing callers assigned to the agent are moved to
pendingQueue[]
. That way, other agents can pick up those callers.
Interested in contributing? We ❤️ pull requests! See the Contribution guidelines.
We love to hear from you so if you have questions, comments or find a bug in the project, let us know! You can either:
- Open an issue on this repository
- See https://support.tokbox.com/ for support options
- Tweet at us! We're @VonageDev on Twitter
- Or join the Vonage Developer Community Slack
- Check out the Developer Documentation at https://tokbox.com/developer/