Dígitro Neo Interact CTI Computer Telephony Integration |
Comando utilizado para criar ou renovar assinaturas. Para receber notificação de eventos que ocorrem no Neo Interact é necessário assiná-los. A assinatura é feita pelo comando subscribe da API REST do Neo Interact CTI.
Para criar uma nova assinatura de eventos, o campo subscribe deverá ser enviado ao Neo Interact sem o id. Se o id estiver presente, o comando será considerado como uma renovação de assinatura. A assinatura de eventos também deve especificar qual canal será utilizado para a recepção dos eventos, sendo mandatório que pelo menos um deles seja especificado. Há dois canais de assinatura possíveis:
Canais SSE
Webhook
Ambas técnicas foram descritas anteriormente. Quando canais SSE forem utilizados como canal de assinatura, eles devem ser abertos ANTES da solicitação da assinatura, pois é necessário referenciar o canal SSE por onde os eventos irão trafegar.
Toda assinatura possui um tempo de expiração, dado pelo campo expires informado na resposta do comando subscribe. Para que a assinatura continue válida, é necessário que sua renovação ocorra antes deste tempo. Na renovação da assinatura, os canais de notificação e filtro de eventos podem ser alterados.
Exemplo: Cliente CTI assina eventos em um canal SSE (channel_id) previamente conectado
Corpo da requisicao ao criar assinatura (sem filtro de eventos).
{
"subscription": {
"channel_id": 1,
}
}
Corpo da resposta ao criar assinatura. O campo id representa o identificador da assinatura. Ele será necessário para revalidar e/ou cancelar a assinatura. A assinatura deve ser renovada antes do tempo de expiração (em segundos) indicado.
{
"response": {
"command": "subscribe",
"id": "f2ba15e6-de7c-e291-b352-fa3d28d15de4",
"status": "ok",
"expires": 900,
}
}
As assinaturas ativas podem ser consultadas pelo comando get_subscriptions, informando-se o id da assinatura ou o canal associado.
Exemplo: Cliente cti verifica assinatura no servidor
Corpo da resposta ao verificar assinatura
"response": {
"resource": "agent",
"command": "get_subscriptions",
"status": "ok",
"subscriptions": [
{
"channel_id": 9,
"expires": 900,
"agents": [
"agent1.neocti@digitro.com",
"agent2.neocti@digitro.com",
],
"events": [
"all"
],
"services": [],
"media_types": [
"chat"
],
"items": [
"agents_list",
"classification_list"
],
"id": "91ca15e6-cd7a-4281-b322-fa3d18d15d35"
}
]
}
Uma assinatura poderá ser cancelada ao se enviar um comando de unsubscribe informando o código identificador da assinatura (id).
Exemplo: Cliente cti encerra assinatura de eventos
Corpo da requisicao de encerrar assinatura
{
"id": "f2ba15e6-de7c-e291-b352-fa3d28d15de4"
}
Corpo da resposta ao encerrar assinatura
{
"response": {
"command": "unsubscribe",
"id": "f2ba15e6-de7c-e291-b352-fa3d28d15de4",
"status": "ok"
}
}
CLIENT NEO-CTI
| |
| |
| Estabelecendo conexao SSE |
|----------------------------------------------->| GET /sse/open_channel
|<-----------------------------------------------| EVENT on_open_channel { channel_id: 1 }
| |
| |
| Com a conexao SSE estabelecida, pode-se |
| assinar eventos |
|----------------------------------------------->| POST /subscription/subscribe
|<-----------------------------------------------| 200 OK { subscription_id }
| |
| |
| Com a assinatura realizada, eventos são |
| transmitidos |
|<-----------------------------------------------| { event: on_login, ...}
| |
| |
| Renovando a assinatura antes do tempo de |
| expiração |
|----------------------------------------------->| POST /subscription/subscribe (subscription_id)
|<-----------------------------------------------| 200 OK
| |
| |
| Verificando o estado da assinatura no servidor |
|----------------------------------------------->| GET /subscription/get_subscriptions
|<-----------------------------------------------| 200 OK
| |
| |
| Encerrando a assinatura de eventos |
|----------------------------------------------->| POST /subscription/unsubscribe
|<-----------------------------------------------| 200 OK
O código exemplo abaixo:
const EventSource = require('eventsource')
const fetch = require('node-fetch')
function openSseChannel(ctiUrl, apiKey) {
return (new Promise((resolve, reject) => {
const url = `${ctiUrl}/sse/open_channel`
sseChannel = new EventSource(url, { headers: { 'X-API-KEY': apiKey } })
sseChannel.ctiUrl = ctiUrl
sseChannel.apiKey = apiKey
sseChannel.on('open', (msg) => {
console.log(`open`, msg)
})
sseChannel.on('error', (err) => {
console.log(`error`, err)
})
sseChannel.on('message', (msg) => {
const data = JSON.parse(msg.data)
console.log('heartbeating - data:', JSON.stringify(data, null, 2))
})
sseChannel.on('on_open_channel', (msg) => {
const data = JSON.parse(msg.data)
console.log('on_open_channel', JSON.stringify(data, null, 2))
sseChannel.id = Number(data.channel_id)
resolve(sseChannel)
})
const events = [
'on_login',
'on_logout',
'on_state_change',
'on_config_change',
'on_call_receive',
'on_call_accept',
'on_call_release',
'on_call_end',
'on_call_associated_data',
'on_call_generate',
'on_call_hold',
'on_call_retrieve',
'on_call_consultation',
'on_call_transfer',
'on_call_conference',
'on_chat_message',
'on_typing',
'on_call_record',
'on_agent_status',
]
events.forEach((event) => {
sseChannel.on(event, (msg) => {
const data = JSON.parse(msg.data)
console.log(event, JSON.stringify(data, null, 2))
})
})
}))
}
async function subscribeCtiEvents({ ctiUrl, apiKey, sseChannelId, webhook, subscriptionId, filters }) {
let subscription = {}
if (subscriptionId) {
subscription.id = subscriptionId
}
if (filters) {
subscription = { ...subscription, ...filters }
}
if (webhook) {
subscription.webhook = webhook
} else {
subscription.channel_id = sseChannelId
}
const headers = {
'X-API-KEY': apiKey,
'Content-Type': 'application/json'
}
const response = await fetch(`${ctiUrl}/subscription/subscribe`, { method: 'POST', headers, body: JSON.stringify({ subscription }) })
if (response.status === 200) {
const body = await response.json()
return body
}
return
}
async function main(ctiUrl, apiKey) {
const sseChannel = await openSseChannel(ctiUrl, apiKey)
console.log(`SSE channel id: ${sseChannel.id} opened`)
const filters = {} // A ausência de filtros, implica na assinatura de todos eventos daquelas categorias
const sseChannelId = sseChannel.id
const body = await subscribeCtiEvents({ ctiUrl, apiKey, sseChannelId, filters })
let subscriptionId = body.response.id
let expires = body.response.expires
console.log(`Subscribing events - Subscription id: ${subscriptionId}`)
setInterval(async () => {
const body = await subscribeCtiEvents({ ctiUrl, apiKey, sseChannelId, subscriptionId, filters })
subscriptionId = body.response.id
expires = body.response.expires
console.log(`Subscription id: ${subscriptionId} renewed`)
}, (expires - 30) * 1000) // Renova 30 segundos antes de expirar a validade da assinatura
}
const apiKey = '1f9cdebf-387c-4dc5-a0cd-ac873132ba90' // Alterar para sua API-KEY
const domain = 'client.saas' // Alterar para seu domínio
const ctiUrl = `https://${domain}.digitro.cloud/neo/cti/v1.0.0/`
main(ctiUrl, apiKey)
Variação do código exemplo acima com a passagem da apiKey via query string:
(...)
const querystring = require('querystring')
function openSseChannel(ctiUrl, apiKey) {
return (new Promise((resolve, reject) => {
const url = `${ctiUrl}/sse/open_channel?${querystring.encode({ apiKey })}`
sseChannel = new EventSource(url)
(...)
ATENÇÃO:
Muito embora o tratamento de eventos possa ser filtrado na própria aplicação do cliente, opcionalmente pode ser desejável que nem todos os eventos do Contact Center sejam reportados num mesmo canal, seja por restrições de tráfego, por restrições de processamento da aplicação ou por restrição de escopo, por exemplo.
O comando subscribe permite que filtros sejam aplicados por assinatura de eventos. Esses filtros são configurados nos dados passados no comando pelos campos:
Esses campos são arrays de strings nas quais são especificados os elementos de cada filtro que se deseja monitorar, seguindo as seguintes regras:
ATENÇÃO: A criação de múltiplos filtros pode levar a resultados inesperados, por exemplo a criação de um filtro de agentes com um agente específico e outro filtro de eventos com evento específico poderão ter suas ações somadas, ou seja, o critério de encaminhamento de eventos deverá atender ambos os filtros, no nosso exemplo, somente eventos específicos daqueles agentes específicos serão enviados.
Exemplo:
{
"subscription": {
"id": 1,
"channel_id": 1,
"expires": 3600,
"agents": [ "agente1.neocti@digitro.cloud" ],
"events": [ "on_login", "on_logout", "on_call_receive" ],
"services": [ "*" ],
"media_types": [],
// "items": [ "services_list", "agents_list", "pause_type_list", "classification_list" ],
}
}
No exemplo acima são definidos 5 filtros com as seguintes regras:
Atenção:
Para adicionar filtros ao código acima, basta acrescentar, por exemplo:
(...)
async function main(ctiUrl, apiKey, expires) {
const sseChannel = await openSseChannel(ctiUrl, apiKey)
console.log(`SSE channel id: ${sseChannel.id} opened`)
const filters = {
agents: ['agente1.neocti@digitro.com', 'agente2.neocti@digitro.com']
items: ['agents_list', 'classification_list'],
services:['*'],
media_types: ['chat'],
// events: ['all'], - A ausência do filtro implica na assinatura de todos eventos
}
const sseChannelId = sseChannel.id
const body = await subscribeCtiEvents({ ctiUrl, apiKey, sseChannelId, filters })
let subscriptionId = body.response.id
let expires = body.response.expires
console.log(`Subscribing events - Subscription id: ${subscriptionId}`)
setInterval(async () => {
const body = await subscribeCtiEvents({ ctiUrl, apiKey, sseChannelId, subscriptionId, filters })
subscriptionId = body.response.id
expires = body.response.expires
console.log(`Subscription id: ${subscriptionId} renewed`)
}, (expires - 30) * 1000) // Renova 30 segundos antes de expirar a validade da assinatura
}
(...)