Personiumってなに?
PersoniumはいまホットなPDS (Personal Data Store)
を実装に落とし込んだサーバソフトウェアです。
PDS
は、個人情報について「どの情報をどの人に開示するか」を個々人がコントロールできる自己情報コントロール権
を実現します。
Personium自体についてはTakkyさんのQiita連載がとても為になるので、そちらを参照してください。
情報銀行のプラットフォームとして利用できるかもしれないPersonium(PDS) を検証してみる 1 <はじめに>
対象とする読者・書きたい内容
Personium上でアプリケーションを開発している方。今回の記事では以下のことをご紹介します。
- セルマネージャでのイベント参照権限の付与
- クライアントアプリ側でのWebSocketによるリアルタイムイベント購読
- オレオレイベントの発火(
event.post
関数)
この記事に乗っていることはたいていドキュメントを読めばわかる内容です。
しかし仕様は網羅的に書いてあるものの、結局「やりたいこと」を実現するための逆引きができない現状ですので、
こういった「やってみた」記事を備忘的に書きます。
OSSなんだからドキュメントにコントリビュートしろと言われればそれまでですが
オレオレイベントの発火についてはドキュメントに記載がないため、今後変更される可能性があります。
また、現状(1.7.12)のPersoniumではイベント購読を許可するとすべてのイベントが購読可能になりますので、
本記事での使い方は開発目的以外で使用すると問題が起こる可能性があります。
実装
今回はPersoniumのデモ環境を使用します。デモ環境のセルはPersoniumコミュニティでお願いすると割り当ててもらえます。
セルマネージャでのイベント参照権限の付与
まずは、セルで発生したイベントを参照できる「ロール」を作成し、そのロールを「アカウント」に割り当てます。
セルマネージャへのログイン
ロールの作成
ロールへのイベント参照権限の付与
EventListener
ロールを付与したアカウントの作成
イベント購読用トークンの発行
イベント購読可能なアカウント「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 接続
イベントの購読には以下のステップに従います。
- WebSocket接続を確立する →
PersoniumWebSocketAdapter.open
メソッド - アクセストークン送付によるセッション確立 →
PersoniumWebSocketAdapter.auth
メソッド - イベント購読対象の指定 →
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
を使用することでクライアント上でリアルタイムに出力を確認することができます。
結果
上記コードでこのような形でリアルタイムなやり取りを実現できます。
画面向かって左側が今回作成したクライアントで、右側が実際にイベント発火用エンドポイントを開いた状態です。
右側のエンドポイントにアクセスすると内部的に上記のconsole.log
を実行する処理が走り、
Personiumのイベントハブを経由して左側のクライアントで取得できる、という構造になっています。
終わりに
自身が作成するアプリのエンドポイントのデバッグにあたり、リアルタイムなログが見られないのを不便に思い、実装しました。
初Qiitaなので勝手がわからず、色々とアラが目立つかも知れませんが、どなたかのお役に立てば幸いです。
編集リクエスト・サンプルコードへの指摘等も大歓迎です。