
Partager:
Javier studied Industrial Engineering back in Madrid where he's from. He is now one of our Solution Engineers, so if you get into trouble using our APIs he may be the one that gives you a hand. Out of work he loves playing football and travelling as much as he can.
Ajouter des capacités vidéo à Zendesk avec l'API Video de Vonage
Temps de lecture : 14 minutes
Dans ce tutoriel, nous allons ajouter des fonctionnalités de vidéo, de partage d'écran et d'enregistrement à Zendesk en utilisant l'API Video de Vonage afin que vous puissiez offrir une expérience client plus riche.
Vous pensez peut-être que ce n'est pas pour vous car vous n'utilisez pas Zendesk, mais, en fait, il y a beaucoup d'autres systèmes de billetterie où vous pouvez appliquer ces leçons. Si cela ne vous a pas convaincu, laissez-nous vous montrer comment gérer les enregistrements de manière programmatique et les télécharger dans un ticket Zendesk pour que les deux parties puissent le télécharger.
Le scénario
La cliente souhaite discuter d'un ticket en suspens avec l'ingénieur support. Elle demande un appel vidéo avec l'ingénieur d'assistance en appuyant sur le bouton
Discuss Live with Javieret attend qu'il le rejoigne.

- Le ticket est mis à jour avec un commentaire interne, de sorte que l'ingénieur d'assistance est informé que le demandeur du ticket souhaite une session vidéo.

L'ingénieur support rejoint la session, ils parcourent le ticket (pas grand chose à discuter dans ce cas particulier 😂). Ils décident d'enregistrer l'appel, et une fois l'enregistrement arrêté, il est téléchargé sous la forme d'un commentaire de ticket afin que les deux participants puissent le télécharger.

Si cela a attiré votre attention, n'hésitez pas à nous suivre.
Architecture
Pour donner un aperçu de l'architecture de cette intégration, nous aimerions partager avec vous le diagramme suivant :

D'un côté, le client final demande un appel vidéo avec l'ingénieur d'assistance via la page de demande Zendesk. Le serveur traitera la demande et mettra à jour le ticket pour attirer l'attention de l'agent. De l'autre côté, l'agent qui utilise Zendesk rejoint la même session pour discuter en direct.
Conditions préalables
Avant de commencer, vous aurez besoin des éléments suivants :
Node.js installé et quelques connaissances de base en JavaScript
Un Account Zendesk avec des droits d'administrateur
Les Zendesk App Tools (ZAT) installés
Un compte Un compte Amazon S3
Vonage API Account
To complete this tutorial, you will need a Vonage API account. If you don’t have one already, you can sign up today and start building with free credit. Once you have an account, you can find your API Key and API Secret at the top of the Vonage API Dashboard.
Agent Zendesk
Pour commencer avec les Applications Zendesk, vous pouvez suivre leur Créer votre première application d'assistance de Zendesk. Accédez au répertoire de votre projet et exécutez la commande suivante.
zat newVous serez invité à fournir certaines informations telles que le nom de votre application ; nous l'appellerons Zendesk Video App. Il vous demandera également votre adresse électronique et quelques autres paramètres qui n'affecteront pas la fonctionnalité. Une fois la commande exécutée, vous verrez que l'application est créée. Nous allons également créer un dossier pour notre serveur. La structure finale du projet ressemble à ceci.
|--Application
|-- Server
|-- server.js
|-- Zendesk Video App
|-- manifest.json
|-- Assets
|-- iframe.html
|-- index.css
|-- index.jssNotre application sera constituée d'un cadre intégré à l'interface de Zendesk, et elle disposera d'une zone de chat vidéo avec plusieurs actions disponibles. Modifions le fichier iframe.html en ajoutant quelques boutons simples qui permettront à l'agent d'avoir un appel vidéo avec le client à l'intérieur du ticket. Vous pouvez copier-coller le code suivant dans votre fichier iframe.html:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/combine/npm/@zendeskgarden/css-bedrock@7.0.21,npm/@zendeskgarden/css-utilities@4.3.0">
<link href="main.css" rel="stylesheet">
</head>
<body>
<div id="content"></div>
<button id="initiatesession" class="button" onclick="initializeSession()">Initiate Session</button>
<button id="startPublishingVideoId" class="button" onclick="startPublishingVideo()">Turn on Video </button>
<button id="startPublishingScreenId" class="button" onclick="startPublishingScreen()">Share Screen</button>
<button id="handleRecording" class="button" onclick="handleRecording()">Start Recording</button>
<div id="videos" >
<div id="publisher" ></div>
<div id="subscriber" ></div>
</div>
<script id="requester-template" type="text/x-handlebars-template">
</script>
<script src="https://cdn.jsdelivr.net/npm/handlebars@4.3.3/dist/handlebars.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/jquery@3.4.1/dist/jquery.min.js"></script>
<script src="https://static.zdassets.com/zendesk_app_framework_sdk/2.0/zaf_sdk.min.js"></script>
<script src="https://static.opentok.com/v2/js/opentok.min.js"></script>
<script src="index.js"></script>
</body>
</html>
Nous ajouterons également quelques feuilles de style CSS de base pour les boutons.
.button {
background-color: #008CBA;;
border: none;
color: black;
padding: 15px 32px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 16px;
margin: 4px 2px;
cursor: pointer;
border-radius: 12px;
}Maintenant, éditez le fichier main.js qui instancie un client ZAF. Le client ZAF permet à votre application de communiquer avec le produit Zendesk hôte. Vous pouvez utiliser le client dans vos applications pour écouter les événements, obtenir ou définir des propriétés ou invoquer des actions. Dans ce cas, nous sommes intéressés par les détails du ticket sur lequel nous travaillons. En particulier, l'ID du ticket et l'ID du demandeur. Une fois la promesse tenue, nous pouvons envoyer une requête à notre serveur pour obtenir la clé API, l'identifiant de session et un jeton pour ce ticket. Toute la logique de génération de session proviendra de notre serveur. Nous y reviendrons plus tard.
$(function() {
let client = ZAFClient.init();
client.invoke('resize', { width: '100%', height: '79vh' });
videos.style.display = 'none';
client.get(['ticket.id', 'ticket.requester.id']).then(data => {
let user_id = data['ticket.requester.id']
let ticket_id = data['ticket.id'];
fetch(SERVER_BASE_URL + '/room/' + user_id + "-" + ticket_id).then(res => {
return res.json()
}).then(res => {
apiKey = res.apiKey;
sessionId = res.sessionId;
token = res.token;
}).catch(handleError);
});
});
Maintenant que nous disposons de ces valeurs, nous pouvons laisser l'agent choisir le moment de lancer la session Video. Nous allons définir une fonction initializeSession qui sera déclenchée lorsque l'agent cliquera sur le bouton Initiate session sur le bouton Nous allons définir l'affichage du conteneur de l'éditeur sur block pour le rendre visible (car il est initialement défini sur none). Nous démarrons la session en instanciant un objet session, puis nous initialisons l'éditeur.
let initializeSession = () => {
session = OT.initSession(apiKey, sessionId);
// Create a publisher
publisher = OT.initPublisher('publisher', {
insertMode: 'replace',
publishVideo: false,
}, handleError);
// Connect to the session
session.connect(token, error => {
// If the connection is successful, initialize a publisher and publish to the session
if (error) {
handleError(error);
} else {
session.publish(publisher)
document.getElementById("initiatesession").style.display = "none"
}
});
}
Nous allons également créer des récepteurs pour les événements qui sont envoyés par l'objet session. Nous nous appuierons sur les éléments archiveStarted et archiveSopped pour contrôler l'état de notre application, c'est-à-dire pour savoir si nous publions la vidéo ou si elle est désactivée en cas d'enregistrement.
Nous afficherons une valeur différente dans les boutons HTML, en fonction de l'état. Par exemple, lorsque nous recevons le message archiveStartednous voudrons que notre bouton affiche "Arrêter l'archivage" plutôt que "Démarrer l'archivage", car l'archivage/enregistrement est déjà lancé. Au début de notre code, nous avons défini quelques variables d'état (archiving, video, et screen) qui changeront en fonction de ces événements.
Nous souhaitons également nous abonner à un flux dès qu'il est créé, nous écouterons donc l'événement streamCreated événement.
session.on('archiveStarted', event => {
archiveID = event.id;
archiving = true
document.getElementById('handleRecording').innerHTML = 'Stop Archive';
console.log('ARCHIVE STARTED ' + archiveID);
});
session.on('archiveStopped', event => {
archiveID = event.id;
archiving = false
document.getElementById('handleRecording').innerHTML = 'Start Archive';
console.log('ARCHIVE STOPED ' + archiveID);
});
session.on("streamPropertyChanged", event => {
video = event.newValue
video ? document.getElementById("startPublishingVideoId").innerHTML = 'Turn Video off' : document.getElementById("startPublishingVideoId").innerHTML = 'Turn on Video';
});
session.on('streamCreated', event => {
console.log('stream created' + event.stream)
session.subscribe(event.stream, 'subscriber', {
insertMode: 'append',
}, handleError);
});
La fonction handleError que nous passons en tant que rappel est une fonction qui lance une alerte si une erreur se produit lors de l'écoute d'événements sur la session.
let handleError = (error) => {
if (error) {
alert(error.message);
}
}
Nous pouvons créer une fonction handleRecording qui déterminera si nous sommes déjà en train d'enregistrer ou non. Cela nous permettra de déclencher une fonction différente en fonction de l'état.
let handleRecording = () => {
archiving ? stopArchive() : startArchive();
}
La fonction StartArchive effectuera une requête POST vers la route de notre serveur archive/start de notre serveur. Nous devons passer notre sessionId afin que notre serveur sache quelle session déclenche l'enregistrement. Vous verrez plus loin dans ce tutoriel que nous faisons référence à l'enregistrement et au stockage de la session. Ne vous méprenez pas, il s'agit du même concept, mais nous utilisons le terme "archive" en interne :)
let startArchive = () => {
console.log('start');
fetch(SERVER_BASE_URL +'/archive/start', {
method: 'post',
headers: {
'Content-type': 'application/json'
},
body: JSON.stringify({
'sessionId': sessionId
})
})
.then((response) => {
return response.json();
})
.then((data) => {
console.log('data from server when starting archiving', data)
})
.catch(error => console.log('errror starting archive', error))
}
Quant à la fonction StopArchive c'est à peu près la même chose que StartArchive. Mais, dans ce cas, nous devons passer l'élément archiveID qui provient de l'événement archiveStarted événement.
let stopArchive = () => {
console.log('archiveID' + archiveID);
fetch(SERVER_BASE_URL + '/archive/' + archiveID + '/stop', {
method: 'post',
headers: {
'Content-type': 'application/json'
}
})
.then((response) => {
return response.json()
})
.then((data) => {
console.log('data from server when stopping archiving', data)
})
.catch(error => console.log('errror stopping archive', error))
}
Nous devons maintenant ajouter la prise en charge des flux de partage d'écran. Nous allons créer une fonction qui vérifiera si nous partageons déjà notre écran et si ce n'est pas le cas, elle créera un nouvel éditeur. Cette fonction agira comme un basculeur pour le flux de partage d'écran en conjonction avec certains événements, tout comme nous l'avons fait pour l'archivage.
Nous allons vérifier si le navigateur prend en charge le partage d'écran en appelant la méthode OT.checkScreenSharingCapability en appelant la méthode Nous expliquons plus en détail la prise en charge du partage d'écran dans la documentation sur la callback checkScreenSharingCapability. Pour certaines versions de navigateurs plus anciennes, vous devrez peut-être installer une extension, mais nous supposerons que les deux participants utilisent un navigateur récent pour des raisons de simplicité.
Notez que les événements que nous écoutons dans ce cas sont distribués par l'objet éditeur plutôt que par l'objet session. Reportez-vous à l'élément Événement de flux pour plus d'informations.
const startPublishingScreen = () => {
if (screenSharing === true) {
session.unpublish(screenPublisher)
} else {
OT.checkScreenSharingCapability(response => {
if (!response.supported || response.extensionRegistered === false) {
alert('Screen share is not supported in this browser')
} else {
screenPublisher = OT.initPublisher('screen', {
videoSource: 'screen'
}, error => {
if (error) {
console.log(error)
} else {
session.publish(screenPublisher, handleError)
.on("streamCreated", event => {
if (event.stream.videoType === 'screen') {
screenSharing = true;
document.getElementById("startPublishingScreenId").innerHTML = 'stop screenShare'
}
})
.on("streamDestroyed", event => {
if (event.stream.videoType === 'screen') {
screenSharing = false
document.getElementById("startPublishingScreenId").innerHTML = 'start screenShare'
}
})
}
})
}
})
}
}
Côté client
Maintenant que notre côté agent est opérationnel, nous devons penser à ajouter la capacité Video du côté client. L'objectif principal de ce billet est de mettre en relation le client final (demandeur de ticket) et l'agent d'assistance (assigné au ticket).
Pour ce faire, nous allons suivre les instructions suivantes guide de personnalisation du thème du centre d'aide afin d'accéder au code de la page du demandeur de ticket et de créer une expérience client plus riche dans le Centre d'aide.
Dans ce cas, nous sommes intéressés par la personnalisation des listes de demandes ou de tickets. Requests pagec'est-à-dire les listes de demandes ou de tickets assignés à un utilisateur spécifique. Comme expliqué dans l'article ci-dessus, le code HTML du Centre d'aide est contenu dans des modèles modifiables. Nous allons éditer le fichier requests_page.hbs . Nous allons éditer le fichier Le code sera très similaire au code JavaScript dans le fichier main.js dans le fichier
Tout d'abord, nous allons importer la bibliothèque Opentok. Cela permettra de télécharger la dernière version du SDK JS.
<script src="https://static.opentok.com/v2/js/opentok.min.js"></script>Nous ajoutons quelques balises de base qui contiendront la vidéo de l'éditeur et de l'abonné ainsi que quelques boutons qui géreront les fonctionnalités de notre application. Vous aurez remarqué que nous avons {{assignee.avatar_url}}. Il s'agit d'un langage de modèle appelé Curlybars qui nous permettra d'interagir avec les données du centre d'aide dans le contexte du ticket Zendsk.
Dans cet exemple, nous affichons une photo de l'attributaire du ticket sur le bouton qui lancera l'appel vidéo. L'objectif est d'offrir une expérience proche au client. Par ailleurs, pour simplifier les choses au début, nous masquerons tous les boutons, sauf celui qui déclenche l'appel. Pour ce faire, nous définirons la propriété d'affichage de nos éléments HTML à none.
<div>
<button class="button" onclick="initializeSession()" style="position:relative">
<img src={{assignee.avatar_url}} />
<span class="tooltiptext">Discuss live with {{assignee.name}}</span>
</button>
</div>
<button id="startPublishingVideoId" class="button" onclick="toggleVideo()" style="display:none">Turn Video off</button>
<button id="handleRecording" class="button" onclick="handleRecording()" style="display:none>Start video recording</button>
<button id="startPublishingScreenId" class="button" onclick="startPublishingScreen()" style="display:none">Share your screen</button>
<div id="videos">
<div id="publisher"></div>
<div id="subscriber"></div>
</div>
Nous allons définir quelques variables que nous utiliserons tout au long du code. Comme nous l'avons fait pour l'agent, nous allons travailler avec des variables d'état (video, archivinget screenSharing). Nous allons également définir le point de terminaison de notre serveur.
let sessionId;
let publisher;
let archiveId;
let screenSharing = false;
let archiving = false;
let video = true;
const SERVER_BASE_URL = 'SERVER_BASE_URL';Nous définissons une simple fonction de gestion des erreurs que nous utiliserons pour alerter l'utilisateur en cas d'erreur. Le seul but de cette définition en tant que fonction séparée est de nettoyer un peu notre code.
const handleError = (error) => {
if (error) {
alert(error.message);
}
}
Nous recherchons apiKey, sessionId, et token de notre serveur.
fetch(SERVER_BASE_URL + '/room/' + {{request.requester.id}} + '-' +{{request.id}}).then(res => {
return res.json()
}).then(res => {
apiKey = res.apiKey;
sessionId = res.sessionId;
token = res.token;
}).catch(handleError);
Ajoutez ensuite la fonction suivante initializeSession qui sera déclenchée dès que le client décidera de demander un appel vidéo avec l'agent d'assistance. Nous allons afficher les boutons qui étaient cachés au départ, puis nous allons d'abord instancier un objet de session et créer un éditeur. Enfin, nous essayons de nous connecter à la session. Si la connexion est réussie, nous essaierons de publier dans la session, comme expliqué précédemment.
const initializeSession = () => {
document.getElementById('startPublishingVideoId').style.display = "block";
document.getElementById('handleRecording').style.display = "block";
document.getElementById('startPublishingScreenId').style.display = "block";
videos.style.display = 'block';
session = OT.initSession(apiKey, sessionId);
publisher = OT.initPublisher('publisher', {
insertMode: 'append',
width: '100%',
height: '100%',
}, handleError);
session.connect(token, error => {
if (error) {
handleError(error);
} else {
session.publish(publisher, handleError);
}
});
session.on('streamCreated', (event) => {
session.subscribe(event.stream, 'subscriber', {
insertMode: 'append',
width: '100%',
height: '100%'
}, handleError);
});
session.on('archiveStarted', event => {
archiveID = event.id;
archiving = true
document.getElementById('handleRecording').innerHTML = 'Stop Archive';
console.log('ARCHIVE STARTED ' + archiveID);
});
session.on('archiveStopped', event => {
archiveID = event.id;
archiving = false
document.getElementById('handleRecording').innerHTML = 'Start Archive';
console.log('ARCHIVE STOPED ' + archiveID);
});
session.on("streamPropertyChanged", event => {
console.log(event.newValue)
video = event.newValue
video ? document.getElementById("startPublishingVideoId").innerHTML = 'Turn Video off' : document.getElementById("startPublishingVideoId").innerHTML = 'Turn Video on';
});
session.on('streamCreated', event => {
session.subscribe(event.stream, 'subscriber', {
insertMode: 'append',
}, handleError);
});
}
Nous allons utiliser des opérateurs ternaires pour décider si nous devons activer ou désactiver la vidéo. La même logique s'applique pour déterminer si nous allons appeler la fonction pour démarrer l'enregistrement ou pour l'arrêter.
const toggleVideo = () => {
video ? publisher.publishVideo(false) : publisher.publishVideo(true)
}
const handleRecording = () => {
archiving ? stopArchive() : startArchive();
}
Les startArchive() et startArchive() sont exactement les mêmes que celles de la fonction main.jsNous allons donc les omettre pour des raisons de simplicité. Vous pouvez également vouloir donner l'option d'initier des enregistrements à l'agent d'assistance et non au client final, mais c'est à vous de décider. Pour rendre les choses plus amusantes, nous allons permettre aux deux agents de lancer et d'arrêter les enregistrements, car ils pourront tous deux récupérer l'enregistrement après l'appel.
Serveur
Notre côté serveur sera composé de plusieurs routes pour traiter les demandes provenant soit de l'agent, soit de l'ingénieur d'assistance.
Importons les modules que nous allons utiliser pour notre application et définissons quelques variables d'environnement.
apiKey et apiSecret sont les identifiants de l'API Video qui se trouvent dans votre tableau de bordle remoteUri fait référence au point de terminaison Zendesk de votre organisation sous la forme de https://xxxxxx.zendesk.com/. Pour l'authentification Zendesk, consultez la page "Comment puis-je authentifier les demandes API"car ils prennent en charge différentes méthodes d'authentification ; nous avons utilisé le nom d'utilisateur et le jeton.
En ce qui concerne l'authentification avec AWS, il existe plusieurs méthodes supportéesNous avons choisi d'utiliser les variables d'environnement. Notez que dans ce cas, The SDK détecte automatiquement les identifiants AWS définis comme variables dans votre environnement et les utilise pour les requêtes SDK, ce qui élimine la nécessité de gérer les identifiants dans votre application. C'est pourquoi nous ne lisons pas les variables depuis notre fichier .env dans notre fichier
const fs = require('fs');
const bodyParser = require('body-parser')
const express = require('express');
const path = require('path');
const app = express();
const _ = require('lodash');
const request = require ('request')
const ZD = require('node-zendesk');
const cors = require('cors');
const dotenv = require('dotenv')
dotenv.config();
const apiKey = process.env.apiKey
const apiSecret = process.env.apiSecret
const AWS = require('aws-sdk');
const remoteUri = process.env.remoteUri
const client = ZD.createClient({
username: process.env.username,
token: process.env.token,
remoteUri: process.env.remoteUri
});
const OpenTok = require('opentok');
const opentok = new OpenTok(apiKey, apiSecret);
app.use(cors());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({
extended: true
}));
let ticketId
const app = express()
init()Ajoutez ceci à votre fichier index.js fichier.
const init = () => {
app.listen(8080, () => {
console.log('You\'re app is now ready at http://localhost:8080/');
}
La route qui gère la création des sessions et des jetons va vérifier s'il existe déjà une session créée pour discuter de ce ticket, et si ce n'est pas le cas, elle en créera une. Au cas où vous ne seriez pas familier avec le concept de jeton pour l'API Video, il s'agit d'une sorte de clé d'accès à la salle (session).
Il serait souhaitable d'avoir une solution plus sécurisée, mais nous avons décidé de faire une validation de base ici pour rester simple. Dans ce cas, nous recevons un paramètre name au format suivant XXXXXX-YYYYY. Vous souvenez-vous des appels de recherche que nous avons faits dans les deux parties (Agent et Client) ? Cela vient de là.
Nous ne générerons une session et un jeton que si l'identifiant du demandeur du ticket correspond à la deuxième partie de notre paramètre :name reçue. Nous allons utiliser un package Zendesk pour effectuer la validation. Par exemple, si nous recevons 1222-1234nous vérifierons via l'API Zendesk si le ticket 1234 a bien été demandé par l'utilisateur 1222. Si ce n'est pas le cas, nous renverrons un HTTP 404.
Vous verrez également qu'il y a une validation autour du référent et de l'origine de la demande. Il s'agit là d'une modification rapide qui permet de mettre à jour le ticket uniquement si la demande provient du client, et de faire savoir à l'ingénieur support que le demandeur du ticket souhaite avoir une session vidéo.
app.get('/room/:name', (req, res) => {
if (!req.params.name) {
res.status(402).end()
}
let roomName = req.params.name;
let sessionId;
let requesterId = roomName.split("-")[0]
ticketId = roomName.split("-")[1]
checkIfValid(ticketId, req).then(response => {
if (response && response.toString() === requesterId) {
if (req.headers.origin === endpoint && req.headers.referer.split("/")[3] === "hc") {
updateTicket(ticketId)
}
if (roomToSessionIdDictionary[roomName]) {
sessionId = roomToSessionIdDictionary[roomName];
token = opentok.generateToken(sessionId);
res.setHeader('Content-Type', 'application/json');
res.send({
apiKey: apiKey,
sessionId: sessionId,
token: token
});
} else {
giveMeSession().then(session => {
roomToSessionIdDictionary[roomName] = session.sessionId;
token = opentok.generateToken(session.sessionId);
res.setHeader('Content-Type', 'application/json');
res.send({
apiKey: apiKey,
sessionId: session.sessionId,
token: token
});
})
.catch(e => res.status(500).send({
error: 'createSession error:' + e
}))
}
} else {
res.status(404).end()
}
})
.catch((e) => {
res.status(404).end()
})
})
Dans une application réelle, vous devriez probablement stocker les identifiants de session dans votre base de données et vérifier si une session a déjà été créée pour ce ticket. Cependant, nous avons décidé d'utiliser simplement un dictionnaire qui stocke les identifiants de session associés à un nom de salle pour ce tutoriel. Gardez à l'esprit que ce dictionnaire sera réinitialisé lorsque vous redémarrerez votre serveur.
let roomToSessionIdDictionary = {};
// returns the room name, given a session ID that was associated with it
const findRoomFromSessionId = sessionId => {
return _.findKey(roomToSessionIdDictionary, value => { return value === sessionId; });
}
Comme nous l'avons mentionné, nous ne créerons une session que si aucune session n'est associée au nom de salle reçu. Nous enveloppons la méthode basée sur le callback dans une promesse qui renverra un objet session.
const giveMeSession = ()=>{
return new Promise((resolve, reject) => {
opentok.createSession({ mediaMode: 'routed' }, (err, session) => {
if (err) {
console.log('[Opentok - createRoutedSession] - Err', err);
reject(err);
}
resolve(session);
});
})
}
Nous avons également intégré dans une promesse le contrôle Zendesk qui nous permet d'interroger l'ID du ticket que nous avons reçu afin de déterminer si la demande est légitime ou non.
const checkIfValid = (ticketId, res) => {
return new Promise(
(resolve, reject) => {
client.tickets.show(ticketId, function(err, request, result){
if (err) reject(err);
resolve(result.requester_id);
})
}
);
};
Si la demande est valide et provient du côté client (et non de l'agent), mettez à jour le ticket afin que l'ingénieur support soit informé de la présence d'une personne en attente d'une session vidéo.
const updateTicket = (ticketId) => {
let notification = 'The requester of the ticket would like to talk to you.'
client.tickets.update(ticketId, {"ticket":{comment:{"body": notification, "public": false}}}, (err, req, res) => {
if(!err){console.log('Ticket updated')
}}
)}
Nous définissons les routes pour démarrer et arrêter l'archivage. Notez que la route pour arrêter l'archivage prend également l'identifiant de la session. Ceci afin que nos serveurs sachent pour quel identifiant de session vous essayez d'arrêter l'enregistrement.
app.post('/archive/start', (req, res) => {
var json = req.body;
var sessionId = json.sessionId;
opentok.startArchive(sessionId, { name: 'testSession' }, (err, archive) => {
if (err) {
console.error(err);
res.status(500).send({ error: 'startArchive error:' + err });
return;
}
res.setHeader('Content-Type', 'application/json');
res.send(archive);
});
});
app.post('/archive/:archiveId/stop', (req, res) => {
opentok.stopArchive(archiveId, function (err, archive) {
if (err) {
console.error('error in stopArchive');
console.error(err);
res.status(500).send({ error: 'stopArchive error:' + err });
return;
}
res.setHeader('Content-Type', 'application/json');
res.send(archive);
});
});
Si vous faites tourner votre serveur, exposez-le avec ngroket configurez l'URL de ngrok en tant que SERVER_BASE_URL dans les deux frontaux (côté client et côté agent). Vous avez maintenant une session Video, bravo !
D'accord, c'était cool, mais allons encore plus loin ! Ne serait-ce pas génial si nous pouvions aussi gérer dynamiquement l'enregistrement de l'appel et le télécharger vers Zendesk pour que l'ingénieur d'assistance et le client puissent le récupérer au moment qui leur convient le mieux ? C'est ce que nous allons faire !

Traitement des enregistrements
Tout d'abord, nous devons indiquer à l'API Video où nous voulons que notre enregistrement vidéo soit téléchargé. Comme nous allons utiliser un point de terminaison AWS S3, vous pouvez suivre nos conseils sur l'utilisation du stockage S3 avec l'archivage de l'API vidéo de Vonage. Utilisation du stockage S3 avec l'archivage de l'API Video de Vonage de Vonage. Une fois configuré, si vous avez une session vidéo et que vous lancez et arrêtez un enregistrement, il sera automatiquement téléchargé dans votre bucket S3.
Toutes les archives sont enregistrées dans un sous-répertoire de votre panier S3 dont le nom est votre clé API OpenTok, et chaque archive est enregistrée dans un sous-répertoire de celui-ci, dont le nom est l'identifiant de l'archive. Le fichier d'archive est archive.mp4.
Prenons l'exemple d'une archive dont la clé API et l'ID sont les suivants :
Clé API -- 123456
ID de l'archive -- ab0baa3d-2539-43a6-be42-b41ff1488af3
Le fichier de cette archive est téléchargé dans le répertoire suivant de votre panier S3 :
123456/ab0baa3d-2539-43a6-be42-b41ff1488af3/archive.mp4
Ensuite, nous avons besoin de savoir quand l'archive a été téléchargée sur notre panier S3 afin de pouvoir la récupérer. Nous allons configurer une route dans notre serveur pour écouter les événements liés aux archives. La plateforme Video API vous enverra un webhook à l'URL de rappel précédemment configurée lorsque l'état d'une archive changera.
Allez dans votre tableau de bord, cliquez sur le projet que vous utilisez, et configurez l'URL de votre serveur à https://YOUR_SERVER_URL/events. Comme expliqué dans le guide d'archivagela plateforme Video API vous enverra un état de disponibilité une fois que l'archive sera disponible pour le téléchargement à partir du seau S3. Nous écouterons cet événement sur notre serveur et le téléchargerons. Toute la logique sera gérée côté serveur (server.js ).
app.post('/events', (req, res) => {
res.send('OK')
if(req.body.status === 'uploaded'){
let key = apiKey + "/" + req.body.id + "/archive.mp4"
downloadVideo(req.body.id + ".mp4", key)
}
})
N'oubliez pas de configurer l'URL de votre serveur dans votre compte Video API. Sinon, vous ne recevrez pas ces webhooks sur votre serveur. Elle doit ressembler à ce qui suit :

Nous allons passer deux variables à la fonction downloadVideo l'une est le nom avec lequel nous voulons que notre archive soit téléchargée, et l'autre est la clé, de sorte que notre bac S3 sache quel enregistrement nous essayons de récupérer.
La requête transmettra les données retournées directement à un objet Stream de Node.js en appelant la méthode createReadStream sur la requête. L'appel de la méthode createReadStream renvoie le flux HTTP brut géré par la requête. Le flux de données brutes peut alors être acheminé vers un objet Stream de Node.js. Nous devrions maintenant être en mesure de télécharger les enregistrements de manière dynamique une fois qu'ils ont été téléchargés dans notre panier.
const downloadVideo = (name, key) => {
var fileStream = fs.createWriteStream(name);
s3 = new AWS.S3();
var s3Stream = s3.getObject({Bucket: process.env.BucketName, Key: key}).createReadStream();
s3Stream.on('error', (err) => {
console.error(err);
});
s3Stream.pipe(fileStream).on('error', (err) => {
// capture any errors that occur when writing data to the file
console.error('File Stream:', err);
}).on('close', () => {
console.log('Done.');
getToken(name)
});
}
Vous aurez remarqué que nous appelons une fonction getToken une fois le téléchargement du fichier terminé. Cela est dû au processus de téléchargement d'un fichier vers Zendesk. Vous pouvez faire ce que vous voulez avec le fichier à ce stade, puisqu'il est déjà téléchargé. Cependant, pour terminer notre post, téléchargeons l'enregistrement dans le ticket Zendesk pour que les deux participants puissent le regarder après l'appel.
Nous devons d'abord obtenir un jeton, puis mettre à jour le ticket en passant par ce jeton. Nous ferons la deuxième partie dans une fonction séparée appelée uploadVideo.
const getToken = (archiveName) => {
client.attachments.upload(__dirname + '/' + archiveName , {binary: false, filename: archiveName}, (err, req, result) => {
if (err) {
console.log("error:", err);
}
console.log("token:", result.upload.token);
uploadVideo(result.upload.token, ticketId)
})
}
const uploadVideo = (token, ticketId) =>{
let ticket = {
"ticket":{"comment": { "body": "This is the recording of the call", "public": true, "uploads":[token]},
}};
client.tickets.update(ticketId,ticket, (err, req, res) => {
if(!err){
console.log('ticket updated with the video recording')
}
})
}
Consultez la démo pour avoir une meilleure idée de la façon dont tout cela fonctionne. Adaptez ce tutoriel à vos besoins et laissez vos clients très satisfaits et de vrais défenseurs de l'expérience d'assistance.
Le code de ce projet se trouve dans le répertoire vonage-zendesk-integration sur GitHub.
Qu'allez-vous construire ensuite ? Faites-le nous savoir !
