Chez Nuxeo, nous cherchons toujours de nouvelles occasions d’ajouter de nouvelles fonctionnalités à Nuxeo Content Services Platform. En combinant cette volonté d’innover et ma passion pour les produits Amazon, j’ai créé il y a quelques mois un skill Nuxeo pour Alexa qui nous permet de parcourir et chercher les documents stockés dans Nuxeo Platform. Lors de la sortie du nouvel Echo Show, j’ai tout de suite vu une possibilité d’améliorer l’expérience Nuxeo - Alexa en affichant les documents sur l’écran de l’Echo Show. Cela signifie que vous pouvez demander à Alexa de parcourir vos ressources numériques et de vous montrer les résultats sur l’écran sans avoir à bouger le petit doigt !
Voyons ensemble comment configurer ce skill et le résultat.
Récap du skill précédent
Dans mon précédent article sur Alexa, je vous ai montré comment développer un skill avec un écran d’authentification permettant de lier votre compte Alexa à votre instance Nuxeo.
Il comptait 3 commandes principales
- Search, pour parcourir vos documents
- GetLast, pour consulter vos derniers documents
- Tasks, pour voir vos dernières tâches
L’architecture ressemblait à ça :
Voyons maintenant les étapes à suivre pour afficher les résultats de la recherche sur l’Echo Show.
Gestion de l’affichage
Pour afficher vos ressources sur l’Echo Show, il vous suffit d’inclure une directive supplémentaire dans votre réponse.
{
"type": "Display.RenderTemplate",
"template": {...}
}
6 types d’organisation sont disponibles dans la liste actuelle :
BodyTemplate1 | BodyTemplate2 | BodyTemplate3 |
---|---|---|
![]() | ![]() | ![]() |
BodyTemplate6 | ListTemplate1 | ListTemplate2 |
![]() | ![]() | ![]() |
Tasks
Pour les tâches, j’ai décidé d’utiliser ListTemplate1
car les miniatures ne sont pas indispensables. J’ai mis à jour le code et ajouté quelques lignes. Voici le code avant et après :
Avant
var tasks = '';
for (let id in doc.entries) {
let res = "Task " + i++ + " " + ...;
tasks += res;
response.say(res);
}
Après
var tasks = '';
var listItems = [];
for (let id in doc.entries) {
let res = "Task " + i++ + " " + ...;
tasks += res;
response.say(res);
listItems.push(
{
...
"textContent": {
"primartyText": {
...
}
}
});
}
let template = {
"type": "ListTemplate1",
"token": "workflow",
"title": "Your workflow tasks",
"listItems": listItems
};
response.directive({"type": "Display.RenderTemplate", "template": template });
GetLast et Search
Pour afficher nos documents à partir d’une requête Search ou GetLast, j’ai choisi ListTemplate2
car je voulais aussi afficher leurs miniatures.
Mais comme l’URL de la miniature nécessite une authentification, sa récupération entraîne une erreur. Il faut ruser et faire passer la requête par un proxy. Au lieu d’utiliser l’URL de l’API REST de la miniature :
{nuxeo_url}/api/v1/{uuid}/@rendition/thumbnail
Nous allons utiliser un proxy passant par notre serveur d’authentification :
https://nuxeo-auth.loopingz.com/thumbnail.php?token={token}&uid={uid}
Proxy pour les miniatures
La nouvelle architecture ressemble à ça :
Le proxy utilisé pour les miniatures est assez simple. Il utilise le token d’authentification pour initier le client d’API Nuxeo en PHP puis effectue un GET sur la miniature. En cas d’échec, il retournera une image par défaut pour le document.
...
try {
$client = new NuxeoClient($url);
$client = $client->setAuthenticationMethod(new TokenAuthentication($token));
$doc = $client->automation('Document.Fetch')->param('value', $uid)->execute(Document::className);
$thurl = $url."/api/v1/id/".$uid."/@rendition/thumbnail";
$res = $client->get($thurl);
header("Content-Type: ".$res->getContentType());
header("Content-Length: " . $res->getContentLength());
print($res->getBody());
} catch (Exception $e) {
defaultImage();
}
Sécurité
Comme le token passe maintenant par plusieurs URL et n’était encodé qu’en base64, j’ai décidé de le sécuriser un peu plus et d’utiliser un chiffrement AES-256 avec un secret partagé entre le serveur d’authentification et la fonction Lambda.
J’ai donc ajouté le chiffrement au linker et le déchiffrement au proxy utilisé pour les miniatures.
require 'secret.php';
function encrypt($data) {
global $SECRET;
$pass = openssl_digest($SECRET,"sha256", true);
$iv = openssl_random_pseudo_bytes(16);
$algo = "aes-256-ctr";
$options = OPENSSL_RAW_DATA;
$encData = openssl_encrypt($data, $algo, $pass, $options, $iv);
return base64_encode($iv.$encData);
}
function decrypt($data) {
global $SECRET;
$pass = openssl_digest($SECRET,"sha256", true);
$algo = "aes-256-ctr";
$options = OPENSSL_RAW_DATA;
$plain = base64_decode($data);
return openssl_decrypt(substr($plain,16), $algo, $pass, $options, substr($plain,0,16));
}
L’astuce était ensuite d’aligner le chiffrement de PHP et de NodeJS pour le skill Alexa.
const secret = require('./secret');
...
let tokenRaw = request.getSession().details.accessToken;
let raw = Buffer.from(tokenRaw, 'base64');
let iv = raw.slice(0,16);
let enc = raw.slice(16);
let cipher = crypto.createCipheriv(algo, secret, iv);
let dec = cipher.update(enc, 'ascii', 'ascii');
dec += cipher.final('ascii');
Le chiffrement est assez standard avec une clé partagée de 256 bits et la génération d’un vecteur d’initialisation IV aléatoire. Les deux sont utilisés pour chiffrer nos données puis pour concaténer les vecteurs IV avec les données chiffrées.
Le système en action
Voici une démo du résultat :
Vous trouverez les sources dans le repository GitHub. N’hésitez pas à essayer et à nous faire part de vos commentaires !