Nuxeoでは、常にNuxeo Platformに優位性を追加する機会を探しています。この機能と私のAmazon製品への愛着を組み合わせて、数ヶ月前に、AlexaによってNuxeo Platformのドキュメントを検索することができるNuxeoのAlexaスキルを作成しました。新しいEcho Showがリリースされたとき、Echo Show画面にドキュメントを表示する機能を追加することで、Nuxeo Alexaエクスペリエンスを向上させる機会を得ました。つまり、デジタルアセットを検索し、指を使わなくても画面上に結果を表示するようにAlexaに依頼することができるようになります!

このスキルを深く掘り下げて実際に見てみましょう。

##従来のスキルの要約

以前に掲載したAlexaのブログで、AlexaアカウントとNuxeoインスタンスをリンクするための認証画面でスキルを開発する方法を示しました。

3つの主要コマンドがありました。

  • Searchドキュメントの検索
  • GetLast最後のドキュメントを見る
  • Tasks最後のタスクを見る

アーキテクチャーの構成:

アーキテクチャー

ここで、Echo Showに検索結果を表示させる手順を見てみましょう。

##表示の処理

Echo Showにデータを表示させるには、別の指示を回答に挿入させるだけでいいのです。

  {
    "type": "Display.RenderTemplate",
    "template": {...}
  }

現在のリストでは、6種類のレイアウトが利用できます。

BodyTemplate1BodyTemplate2BodyTemplate3
BodyTemplate1BodyTemplate2BodyTemplate3
BodyTemplate6ListTemplate1ListTemplate2
BodyTemplate6ListTemplate1ListTemplate2

###タスク

タスクについては、サムネイルは重要ではないので、[ListTemplate1]を使うことにします。コードをアップデートし、このレイアウトを追加します。アップデート前のコードとアップデート後のコード:

アップデート前

var tasks = '';
for (let id in doc.entries) {
    let res = "Task " + i++ + " " + ...;
    tasks += res;
    response.say(res);
}

アップデート後

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 });

SearchやGetLastリクエストからドキュメントを表示するには、ドキュメントサムネイルを表示したいので、ListTemplate2を選択しました。

しかし、サムネイルURLが認証を必要とするため、サムネイルの取得は失敗します。したがって、少し工夫してリクエストをプロキシする必要があります。サムネイルのREST API URLを使用する代わりに:

{nuxeo_url}/api/v1/{uuid}/@rendition/thumbnail

認証サーバーを通してそれをプロキシします。

https://nuxeo-auth.loopingz.com/thumbnail.php?token={token}&uid={uid}

##プロキシサムネイル

新しいアーキテクチャーの構成:

新しいアーキテクチャー

サムネイルプロキシはかなりシンプルです。認証トークンを使用してNuxeo PHP APIクライアントを起動し、次にサムネイルレンディションをGETします。失敗した場合は、ドキュメントのデフォルトイメージを送信します。

...
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();
}

セキュリティ

トークンがいくつかのURLに渡され、base64としてエンコードされただけなので、トークンをもう少し安全にし、AES-256を認証サーバーとラムダ関数の間の共有秘密鍵で使用することにしました。

そこで、暗号化をリンカに追加し、復号化をサムネイルプロキシに追加しました。

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));
}

難しかったのは、PHPからNodeJSへのエンコーディングを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');

この暗号化はかなり標準的です。256ビットの共有鍵を使い、ランダムなIV初期化ベクトルを生成します。両方を使用してデータを暗号化し、IVベクトルと暗号化データを連結します。

実機確認

このスキルが実際にどのように機能するかを示したデモをご覧いただけます。

GitHubリポジトリにソースがあります。ぜひ試してご意見をお聞かせください。