6
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Personiumでのイベント発火をクライアントでリアルタイムに受け取る

Last updated at Posted at 2019-06-12

Personiumってなに?

PersoniumはいまホットなPDS (Personal Data Store)を実装に落とし込んだサーバソフトウェアです。
PDSは、個人情報について「どの情報をどの人に開示するか」を個々人がコントロールできる自己情報コントロール権を実現します。

Personium自体についてはTakkyさんのQiita連載がとても為になるので、そちらを参照してください。
情報銀行のプラットフォームとして利用できるかもしれないPersonium(PDS) を検証してみる 1 <はじめに>

対象とする読者・書きたい内容

Personium上でアプリケーションを開発している方。今回の記事では以下のことをご紹介します。

  • セルマネージャでのイベント参照権限の付与
  • クライアントアプリ側でのWebSocketによるリアルタイムイベント購読
  • オレオレイベントの発火(event.post関数)

この記事に乗っていることはたいていドキュメントを読めばわかる内容です。
しかし仕様は網羅的に書いてあるものの、結局「やりたいこと」を実現するための逆引きができない現状ですので、
こういった「やってみた」記事を備忘的に書きます。

OSSなんだからドキュメントにコントリビュートしろと言われればそれまでですが

オレオレイベントの発火についてはドキュメントに記載がないため、今後変更される可能性があります。

また、現状(1.7.12)のPersoniumではイベント購読を許可するとすべてのイベントが購読可能になりますので、
本記事での使い方は開発目的以外で使用すると問題が起こる可能性があります。

実装

今回はPersoniumのデモ環境を使用します。デモ環境のセルはPersoniumコミュニティでお願いすると割り当ててもらえます。

personium.io

セルマネージャでのイベント参照権限の付与

まずは、セルで発生したイベントを参照できる「ロール」を作成し、そのロールを「アカウント」に割り当てます。

セルマネージャへのログイン

  1. https://<CELL_FQDN>/io_personium_demo_cell-manager/src/login.htmlにアクセスし、ログインします。
    image.png

ロールの作成

  1. ロール作成ダイアログを開く
    image.png
  2. ロール名を決める
    image.png
  3. Createボタンを押し、ロールが追加されたことを確認する
    image.png

ロールへのイベント参照権限の付与

  1. Privileges設定画面を開く
    image.png
  2. EventListenerロールにEvent Event-Readを付与する
    image.png

EventListenerロールを付与したアカウントの作成

  1. アカウント作成画面を開く
    image.png
  2. EventListenerロールを付与したアカウントを作成する
    image.png

イベント購読用トークンの発行

イベント購読可能なアカウント「eventListener」の認証トークンを返すエンドポイントを作成します。

// Login
function init(request) {
  try {
    // 認証に使用するメソッドをPOSTに限定する
    personium.validateRequestMethod(['POST'], request);
    // アクセス元のWebページURLを照合する
    personium.verifyOrigin(request);

    // セルで認証し情報を取得する
    const appCell = _p.as("serviceSubject").cell();
    // トークンを取得する
    const ret = appCell.getToken();
    // ステータスコード200で結果(トークン)を返す
    return personium.createResponse(200, ret);
  } catch (e) {
    return personium.createErrorResponse(e);
  }
}

var _ = require("underscore")._;
var personium = require('personium').personium;

内部のpersoniumオブジェクトについては、template-app-cell
同梱されているファイルを借用しました。

ここではserviceSubjectという設定値を用いていますが、これをeventListenerに設定することで、
エンドポイントがEventListenerのRoleで処理を行うように設定できます。
serviceSubjectについては こちら
→ (serviceSubject登録方法はリクエストがあれば書きます。)

上記コードは模擬的なコードであり、来た全POSTリクエストに対してトークンを返してしまいます。
アクセス制限など、セキュリティ的な考慮を別途行ってください

WebSocketを用いた購読

イベント購読用エンドポイントを作成できたら、そことの通信を行い、実際にイベントを購読できるクライアントを実装します。

PersoniumWebSocketAdapter

まずはWebSocketを司る PersoniumWebSocketAdapter です。


class PersoniumWebSocketAdapter {
  constructor(appCellUrl, tokens) {
    this.appCellUrl = appCellUrl;
    this.tokens = tokens;
    this.ws = null;
    this.messageListeners = [];
  }
  handleClose(ex) {
    console.log('onclose#webSocket', ex);
  }
  handleMessage(ex) {
    console.log('onmessage#webSocket', ex);
    const jsonDat = ex.data ? JSON.parse(ex.data) : null;
    this.messageListeners = this.messageListeners.filter(
      listener => !listener(ex, jsonDat)
    )
    console.log("messageListener", this.messageListeners.length);
  }
  addListener(listener) {
    this.messageListeners.push(listener);
  }
  open() {
    return new Promise((resolve, reject) => {
      const wssAppCellUrl = appCellUrl.replace('https://', 'wss://');
      try{
        this.ws = new WebSocket(`${wssAppCellUrl}__event`);
        this.ws.onopen = (ex) => {
          console.log('onopen#webSocket', ex);
          resolve(true);
        }
        this.ws.onclose = (ex) => this.handleClose(ex);
        this.ws.onmessage = (ex) => this.handleMessage(ex);
      } catch(ex) {
        reject(ex);
      }
    });
  }
  subscribe(strEventType, strEventObject) {
    return new Promise((resolve, reject) => {
      this.addListener((ex, jsonDat) => {
        if (jsonDat.Response === "Subscribe") {
          console.log('Subscribe is accepted');
          resolve(true);
          return true;
        }
      });
      const requestObj = {
        Subscribe: {
          Type: strEventType,
          Object: strEventObject,
        },
      };
      this.send(JSON.stringify(requestObj));
    });
  }
  auth(AccessToken) {
    return new Promise((resolve, reject) => {
      this.addListener((ex) => {
        const jsonDat = JSON.parse(ex.data);
        if (jsonDat.Response === "AccessToken") {
          console.log('AccessToken is accepted');
          resolve(true);
          return true;
        }
      });
      this.send(JSON.stringify({AccessToken}));
    });
  }
  send(str) {
    this.ws.send(str);
  }
  close() {
    if (this.ws) this.ws.close();
  }
}

クラスの形で実装しています。このクラスを使用することで、下記ドキュメント記載の必要なステップを実行できるようになっています。
⇒ イベントバスへの Web Socket 接続

イベントの購読には以下のステップに従います。

  1. WebSocket接続を確立する → PersoniumWebSocketAdapter.open メソッド
  2. アクセストークン送付によるセッション確立 → PersoniumWebSocketAdapter.auth メソッド
  3. イベント購読対象の指定 → PersoniumWebSocketAdapter.subscribe メソッド

クラスを使用したクライアントの実装

上記クラスを使用したサンプルが以下の通りです。

// 対象セル
const appCellUrl = 'https://<CELL_FQDN>/';
// eventListenerのトークン取得エンドポイント
const appAuthURL = `${appCellUrl}__/html/Engine/getEventListenerToken`;
const accessData = {
  tokens: null,
  wsAdapter: null,
};

(async () => {
  let result = await fetch(appAuthURL, {method: 'POST'});
  if (!result.ok) throw new Exception("Cannot fetch EventListener token");

  let tokens = await result.json();
  accessData.tokens = Object.assign({}, tokens);
  accessData.wsAdapter = new PersoniumWebSocketAdapter(appCellUrl, tokens);

  // 1. WebSocket接続を確立する
  if (!await accessData.wsAdapter.open()) {
    throw new Exception("Cannot open websocket connection");
  }

  // 2. アクセストークン送付によるセッション確立
  if (!await accessData.wsAdapter.auth(accessData.tokens.access_token)) {
    throw new Exception("Cannot auth with EventListener token");
  }

  // 3. イベント購読対象の指定
  // EventType: 'console.log', EventObject: '*(すべて)'
  if (!await accessData.wsAdapter.subscribe('console.log', '*')) {
    throw new Exception("Cannot subscrive events");
  }

  // イベント受信時の処理記述
  accessData.wsAdapter.addListener((ex, jsonDat) => {
    // p タグ部分に内容を追加していく
    const child = document.createElement('p');
    child.innerHTML = jsonDat.Object;
    document.getElementById('root').appendChild(child);
  });
})();

イベントのトリガ

任意のエンジンスクリプトからオレオレイベントをトリガ(発火)できるようにしましょう。

const console = console || {};
console.log = function(str) {
  // セルを取得
  const appCell = _p.as("serviceSubject").cell();
  // 取得したセルに対しイベントを発火
  appCell.event.post({
    Type: 'console.log',
    Object: str,
    Info: 'infoData',
  });
};

Personiumのエンジンスクリプト内ではconsole系が使えないので、横着しています。
ここで実装したconsole.logを使用することでクライアント上でリアルタイムに出力を確認することができます。

結果

上記コードでこのような形でリアルタイムなやり取りを実現できます。

out.gif

画面向かって左側が今回作成したクライアントで、右側が実際にイベント発火用エンドポイントを開いた状態です。

右側のエンドポイントにアクセスすると内部的に上記のconsole.logを実行する処理が走り、
Personiumのイベントハブを経由して左側のクライアントで取得できる、という構造になっています。

終わりに

自身が作成するアプリのエンドポイントのデバッグにあたり、リアルタイムなログが見られないのを不便に思い、実装しました。

初Qiitaなので勝手がわからず、色々とアラが目立つかも知れませんが、どなたかのお役に立てば幸いです。
編集リクエスト・サンプルコードへの指摘等も大歓迎です。

参考URL

6
1
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?