Use the Flex SDK in multiple tabs or sessions
Each active instance receives identical reservations when signed in under the same worker. This occurs if an agent opens the app across multiple tabs, windows, or devices, or uses the Flex UI and a Flex SDK app at the same time.
The Flex SDK doesn't coordinate between these sessions. If more than one session acts on the same reservation, for example by calling AcceptTask or RejectTask, only the first request succeeds. The other sessions fail with errors such as:
48910: Failed to accept reservation48917: Failed to create a conference48103: Failed to reject reservation
For voice tasks, a second accept attempt can also interfere with the call setup started by the first session.
To avoid these conflicts, make sure that only one session handles reservations for a worker at any time.
The Flex UI handles reservations on its own and doesn't know about your Flex SDK sessions. If an agent has the Flex UI open while also using your Flex SDK app, the Flex UI can accept a reservation before your app does, or the other way around.
Don't let agents use the Flex UI and a Flex SDK app as the same worker at the same time.
Auto-accepting tasks is the most common cause of these conflicts. For example, you might call AcceptTask from a listener that runs whenever a reservation is created. Every session signed in as the worker receives the reservation and runs the same listener, so all sessions try to accept it at the same time, and only the first request succeeds.
Instead, let the agent accept each task from the session they're working in, for example with an Accept button that calls AcceptTask. Only one session sends the request, so there's no conflict. The task is also accepted from the session the agent is actively using, which matters for voice tasks.
In a multi-session scenario, the result of an action isn't a reliable signal of a task's state. When AcceptTask fails in one session, the task might still have been accepted, either by another session or by an earlier request from this session. If your app updates its UI or state based on whether client.execute(new AcceptTask(...)) resolved or threw an error, sessions can show a task as accepted when it hasn't been, or can show a task as failed when it was successful.
Instead, update your application state from the events the Flex SDK emits. Every session signed in as the worker receives the same events, so all sessions converge on the same state, regardless of which session ran the action. Treat the action as a request to change state, and the event as the confirmation that the state changed.
Register listeners on the client with the SDK's listener actions:
AddTaskParticipantListenerreports when participants, including the agent, join, leave, or change on a task, or fail to.AddVoiceEventListenerreports voice events, such as an incoming call.
The following example tracks whether the current worker is an active participant on a task. Only the event listeners write to the application state:
1import { createClient } from "@twilio/flex-sdk";2import { AddTaskParticipantListener, ParticipantType } from "@twilio/flex-sdk/actions/Task";34// Application state, keyed by task SID. Only event listeners write to it.5const agentJoinedTask = new Map();67async function trackTask(client, taskSid: string, workerSid: string) {8const isThisWorker = (participant) =>9participant.type === ParticipantType.Agent &&10participant.routingProperties?.workerSid === workerSid;1112const { unsubscribe: unsubscribeAdded } = await client.execute(13new AddTaskParticipantListener(taskSid, "participantAdded", (task, participant) => {14if (isThisWorker(participant)) {15agentJoinedTask.set(task.sid, true);16renderTask(task.sid); // Update your UI17}18})19);2021const { unsubscribe: unsubscribeRemoved } = await client.execute(22new AddTaskParticipantListener(taskSid, "participantRemoved", (task, participant) => {23if (isThisWorker(participant)) {24agentJoinedTask.set(task.sid, false);25renderTask(task.sid);26}27})28);2930// Call this when you no longer need to track the task31return () => {32unsubscribeAdded();33unsubscribeRemoved();34};35}
With this pattern, the handler for the action doesn't change your state:
1import { AcceptTask } from "@twilio/flex-sdk/actions/Task";23async function onAcceptClicked(client, taskSid: string) {4try {5await client.execute(new AcceptTask(taskSid));6// Don't mark the task as accepted here. The listener does that.7} catch (error) {8// Don't mark the task as failed here either. Another session might have9// accepted it. Log the error, and let the listeners update the state.10console.error("AcceptTask failed", error);11}12}
Follow these best practices when handling events and managing task state across concurrent tabs or devices:
- Register listeners in every session so that all tabs show the same state.
- Render the state of a task from your stored state, not from a local variable that an action handler sets.
AddTaskParticipantListenerrequires the task's reservation to exist for the worker. If the reservation was already canceled or timed out, the listener fails withTaskReservationNotFound.- Listen for reservation-level events on the Worker (reservationCreated) and on each Reservation (timeout, canceled, rescinded, etc.).