TeamSpirit Developer Blog

チームスピリット開発者のブログ

Platform EventとElectronでChatter Desktop的なものを作る

この記事は弊社アドベントカレンダーの 11 日目の記事です。
投稿日と一致しないのはお察しください。

エンジニアの山﨑( id:dackdive )です。
少し前から役割がフロントエンドエンジニアからプロダクトマネージャーに変わり、現在はプロダクトの仕様検討をメインで行っています。日々精進です。

さて、最近は Einstein、Big Object、Salesforce DX などなど、開発者の興味を引く新機能が続々登場していますが
今回はその中でも個人的に興味のあった Platform Event を試してみようと思います。

Platform Event の特徴

Platform Event は Summer'17 で正式リリースされた 機能です。機能の概要は webinar や公式ドキュメントに譲るとして、個人的には以下のような点が特徴として挙げられるかと思います。

  • イベントを「イベントオブジェクト」という形で、カスタムオブジェクトと同じように定義できる
  • Salesforce イベントを送受信するための方法として、複数の選択肢がある(Apex やプロセスビルダーなど)
  • Salesforce 内だけでなく、Salesforce と外部のアプリケーションとの間でイベントを送受信できる
    • 一例として、webinar のデモ では Heroku アプリケーションと通信しています

機能の概要を聞いたとき、「あれ、これって Electron でアプリ作ったらローカルでも受信できるんだろうか?そしたらもうすぐ廃止される Chatter Desktop の代替アプリみたいなもの作れないかな〜(Chatter Desktop はポーリングだったし)」という気持ちになったので試してみた次第です。

なお、Platform Event についての基本的な知識は以下の Trailhead モジュールで学ぶことができます。

特に後者は、Salesforce 内ではありますがイベントを Lightning Component で受信する簡単なアプリケーションを作ってみることで Publisher 側、Subscriber 側両方のイメージがつかめるのでオススメです。
(この記事を書いた時点ではまだ英語ですが。。。)

コード

今回作成したサンプルはこちらに置いてあります。

実装:Publisher 編

ここからは実装の話です。まずは Publisher 側から。

イベントオブジェクトを定義する

まずはイベントオブジェクトを定義します。
クイック検索で「プラットフォームイベント」と検索すると該当の項目が見つかります。

イベントオブジェクトの登録手順の詳細は Trailhead で説明されているため省きますが、カスタムオブジェクトとほぼ同様の手順で定義できます。
カスタム項目については、今回は Chatter のフィードに相当する FeedItem オブジェクトのデータ定義 を眺めつつ、必要になりそうなものを適当にピックアップしました。
(結局メッセージ本文に相当する Body しか使わなかった)

f:id:dackdive:20171215034808p:plain

イベントの Publisher を定義する(プロセスビルダー)

イベントオブジェクトを定義したので、次はこれを Publish するしくみを作ります。
Salesforce から Publish するための手段はトリガ、プロセスビルダー、フローなどいくつかありますが、今回はさくっと試すためプロセスビルダーを使用します。
プロセスビルダーはイベントの Publisher 側にも Subscriber 側にもなれます。

f:id:dackdive:20171215035428p:plain

Publisher 側として定義する場合、はじめにイベント発火のトリガーとなるオブジェクトを選び(今回は「フィード項目」)、ルール適用時のアクション種別として「レコードを作成」を選びます。
対象のオブジェクトを選択するプルダウンで、カスタムオブジェクトに混じって先ほど定義したイベントオブジェクトも候補に表示されます。

最後に、イベントオブジェクトの各カスタム項目とフィード項目オブジェクトの各項目をマッピングしてあげれば完成です。

実装:Subscriber 編

続いて、イベントを Subscribe する Electron アプリの方に移ります。
Electron アプリ開発の経験はほとんどないので環境構築で消耗しないよう、また JS のライブラリは React&Redux しか書けないので、今回は「electron react redux boilerplate」などのキーワードで検索してヒットした jschr/electron-react-redux-boilerplate からスタートします。

※ 余談ですが、今思うと chentsulin/electron-react-boilerplate の方がいいかもしれません...

$ git clone https://github.com/jschr/electron-react-redux-boilerplate chatter-desktop
$ cd chatter-desktop

$ yarn
# または npm install

# yarn run develop で起動

jsforce と CometD をインストール

認証およびアクセストークンの取得に jsforce を使います。
また外部アプリケーションで Platform Event を受信するためには CometD が必要です。npm 経由でインストールできるパッケージ があるので、それを使います。

$ yarn add jsforce cometd

Subscriber の実装

最後に、イベントを Subscribe する部分の実装です。

// app/containers/Root.js
import { connect } from 'react-redux';
import Root from '../components/Root';
import { addFeedItem } from '../actions/feedItems';

import jsforce from 'jsforce';
import lib from 'cometd';
const cometd = new lib.CometD();

const mapStateToProps = (state) => {
  return state;
};

// NOTE: ここを自分の環境に置き換え
const USERNAME = 'username';
const PASSWORD = 'password';

const mapDispatchToProps = (dispatch) => {
  return {
    initialize: () => {
      // (1) 認証・アクセストークン取得
      const conn = new jsforce.Connection();
      conn.login(USERNAME, PASSWORD, (err, userInfo) => {
        // (2) CometD の設定
        cometd.configure({
          url: `${conn.instanceUrl}/cometd/41.0`,
          requestHeaders: { Authorization: `OAuth ${conn.accessToken}` },
          appendMessageTypeToURL: false,
        });
        cometd.websocketEnabled = false;

        // (3) 接続&イベントの Subscribe
        cometd.handshake((handshakeReply) => {
          if (handshakeReply.successful) {
            console.log('Connected to CometD.');
            const newSubscription = cometd.subscribe('/event/FeedItemPosted__e',
              (platformEvent) => {
                console.log('Platform event received: '+ (platformEvent.data.payload));
                dispatch(addFeedItem(platformEvent.data.payload));
              }
            );
          } else {
            console.error('Failed to connected to CometD.');
          }
        });
      });
    },
  };
};

export default connect(mapStateToProps, mapDispatchToProps)(Root);

アプリケーションを Redux で作っているので多少関係ないところも含まれていますが、 initialize 関数の中身がメインです。
initialize はアプリケーションのルートコンポーネントがマウントされたタイミングで呼び出されるイメージです)

(1) 〜 (4) まで軽く補足します。

(1) 認証・アクセストークン取得

jsforce を使って認証している部分です。本来ユーザ名・パスワードはログイン画面のような形でユーザに入力させたものを使うべきですが端折ってしまってます。
認証に成功すると Connection オブジェクトからアクセストークンを取得できるようになります。

(2) CometD の設定

ここについては正直まだ設定内容を完全には理解しておらず、Trailhead のコード をそのまま流用しました。

CometD のエンドポイントは https://<インスタンス URL>/cometd/<API バージョン> となり、インスタンス URL は Connection オブジェクトから取得しています。
appendMessageTypeToURLwebsocketEnabled についてはなぜ false に設定する必要があるのかわかってませんが、これを指定しないとエラーになることは確認できました。

(3) 接続&イベントの Subscribe

接続部分については同様に Trailhead のコードをそのまま使っています。Subscribe するチャンネル名は

/event/<イベントオブジェクト名>__e

となります。
最後に、新しいイベントを受信するたび Redux の action を dispatch し、画面に描画しています。action, reducer および描画コンポーネントについては リポジトリ をご参照ください。

できたもの

f:id:dackdive:20171215045733g:plain

(左)Electron アプリ、(右)ブラウザ

UI まで手が回らなかったので、Chatter Desktop ぽさは一切ないですね。。。
あと <p> タグとか表示されてるのはリッチテキストなのを考慮していないからですね。

それでも期待通り near realtime なフィード表示は実現でき、Platform Event が Electron アプリでも受信できることを確認できました。よかった。

TODO

よくよく考えると、全ての投稿が流れてきてしまうので自分のフィードの内容とは違ってしまいます。。。
Chatter まわりのデータ構造を勉強せねばという気持ちになりました。

余談:Chatter Desktop の現状と今後

Chatter Desktop は Spring'17 で廃止され、これ以降に作成した組織では利用できなくなっていました。
参考:Spring'16 リリースノート
Spring'17 以前に作成した組織については引き続き利用が可能で、現在も動いています。

また、
Chatter デスクトップの廃止
によると、

Summer '18 リリース* をもって、Chatter デスクトップは廃止され、Salesforce によるサポートが終了します。その時点でこの機能は使用不可になり、Salesforce によるサポートの提供も終了します。

とあるので、これまで使えていた組織も 2018 年夏をもって使えなくなるんでしょうか。知らなかった。

気になる代替手段についてですが、Windows については Microsoft から 新しいデスクトップアプリ がリリースされていますが、Mac は今のところありません。果たして。。。