🦊

Bolt for JavaScriptと静的なコンテンツ、REST API, SocketIOを共存させる

2021/12/23に公開

本記事は Slack Advent Calendar 2021 の 23 日目の記事です。

はじめに

今年の Advent Calendar では、Slack について7 日目の Slack 通話の内容を Slack 上で見えるようにしようと、AWS について18 日目の Amazon Chime SDK for JavaScript で Among Us Automute とゲーム実況配信 その2を執筆しました。

そして、本記事が今年ラストの Advent Calendar への投稿となります。

上記の AWS Advent Calendar18 日目の 記事を執筆した後、そこで紹介した機能を Slack から起動できるように拡張しました。本記事では、その際に必要となった、 「Bolt for JavaScript の Web サーバ(Express)と、 Socket.IO、静的なコンテンツ、REST API を共存させる方法」をご紹介します。

image

AutoMute with Amazon Chime を Slack と連携させる際にネックとなったポイント

Among Us については、上記の私のブログ記事をはじめいろいろなところで説明されているため、ここでは省略します。

また、上記の記事で紹介した AutoMute、ゲーム実況配信機能(AutoMute with Amazon Chime と呼びます)については、上記の記事をご参照ください。要は、Among Us のゲームの状態に応じてボイスチャットの On/Off と、ゲーム画面の共有の On/Off を制御するツールです。

image

AutoMute with Amazon Chime では、ユーザアカウントの管理を独自に heroku 上の DB で行うようにしているのですが、Signup などがすこし面倒だなと思い始めました。そこで、この AutoMute with Amazon Chime を Slack から起動できるようにして、Slack からの起動の場合は Slack のユーザ情報を紐づけることによってアカウントの登録を省略できるようにしてみました。

Slack から起動する際には、Slack からのイベントを受信してハンドリングする機能を AutoMute with Amazon Chime 側に作成する必要があります。こういった機能を作る際には、Bolt (for Javascript)という Slack 公式のフレームワークを使用するのが一般的です。

この Bolt を使用 する時にすこし問題が発生したのですが、AutoMute with Amazon Chime の次の3つのポイントがその原因となっていました。

(1) Among Us のゲームの状態を amonguscapture というプログラムを用いて取得している点

(2) ボイスチャットや画面共有のバックエンド(WebRTC 部分)は Amazon Chime SDK を用いている点

(3) 作成した機能は heroku 上にデプロイされる点

それぞれ簡単に説明します。

amonguscapture

amonguscaptureは、Among Us のゲーム状態をキャプチャし、外部のプログラムにそのデータを送信するプログラムです。今回は、その送信先が AutoMute with Amazon Chime になります。amonguscapture は、このデータ送信で Socket.IO を使用しています。

Socket.IOは、サーバクライアント間で双方向通信をするためのラッパーライブラリです。amonguscapture が Socket.IO を使っているので、AutoMute with Amazon Chime も SocketIO を使って通信を行う必要があります。

Amazon Chime SDK

Amazon Chime をコントロールするためには、AWS の Credential が必要となります。この Credential を各クライアント(ブラウザ)に持たせるべきではありません。Amazon Chime をコントロールするための REST API サーバを作成しその中で Credential を保持し、ブラウザからはその API を叩くようにするという構成が望ましいです。AutoMute with Amazon Chime でも REST API を提供する構成になっています。

また、Amazon Chime の画面を静的なコンテンツとして提供する Web サーバの機能も提供しています。

heroku

AutoMute with Amazon Chime は、そのデプロイ先として heroku を想定しています[1]。heroku 上ではアプリケーション側から TCP ポートを開くことができません、起動時に割り当てられた唯一のポートだけを使用することができます。

Slack 連携をする際の問題と対応方針

上記の通り heroku 上では、起動時に割り当てられた唯一の TCP ポートしか使用できません。一方で、AutoMute with Amazon Chime には(1)Slack からイベントを受け取る機能、(2)amonguscapture から Among Us のゲーム状態を受け取る機能、(3))静的コンテンツのリクエストを受け付ける機能、(4)Amazon Chime をコントロールするリクエストを受け付ける機能(REST API)、といった4つのサーバ機能が必要となります(下図)。それぞれのサーバ機能に TCP ポートを割り当てることはできませんので、何とかして一つの TCP ポートでそれぞれのサーバ機能を実装する必要があります。
image

Slack からイベントを受け取るために使用する Bolt にはもともとウェブサーバが組み込まれています。このため、このウェブサーバにそれぞれの機能をアドオンしていくことにしました。

Bolt に各種サーバを追加する方法

Bolt が持つ Slack からのイベントを受け取るサーバ機能に加えて、次の三つのサーバ機能を追加する必要があります。
(1) 静的コンテンツを配信するウェブサーバ機能
(2) REST API サーバ機能
(3) Socket.IO のサーバ機能

以下、それぞれの追加方法を説明してきます。

静的コンテンツを配信するウェブサーバ機能を追加

Bolt の内部では Express をベースとした(内包した)ExpressReceiver というウェブサーバが動いています。
Bolt を起動した後だと、この ExpressReceiver の参照を取得してカスタマイズすることはできなさそうです。
しかし、Bolt 起動前にカスタマイズした ExpressReceiver を Bolt に渡すことができます。この Bolt に渡す ExpressReceiver の参照を保持しておくことで、静的コンテンツにルーティングする設定をすることができます。

const receiver = new ExpressReceiver({ signingSecret: AppConfig.signingSecret }); // <- customized ExpressReceiver
const config: AppOptions = {receiver};
const app = new App(config); // <- Initialize Bolt with customized ExcpressReceiver

receiver.app.use(cors());  // <- if you need cors
receiver.app.use("/static", express.static(STATIC_PATH)); // <- routing to static content

なお、ここではこのgithub の issueを参考にしました。なんと、この Slack アドベントカレンダーの管理している瀬良さんの回答でした。

REST API サーバ機能を追加

同様に、カスタマイズした ExpressReceiver に REST API を追加することができます。

receiver.app.use(express.json()); // <- to parse json
receiver.app.post(`/api/hogehoge`, async (req, res) => {
    const request = req.body as HTTPRequestBody;
    const info = decodeToken(request.token);
    const response: HTTPResponseBody = {
        success: true,
        code: HTTPResponseCode.SUCCESS,
        data: info,
    };
    res.send(JSON.stringify(response));
}

ちなみに、公式のドキュメントcustomRoutes:を使うというものが紹介されていますが、なぜかうまくいきませんでした。(あまり詳細は追ってないので、私の単純なミスだと思います。)

Socket.IO サーバ機能を追加

Socket.IO のサーバを作成するためには、HTTPServer を引数に渡す必要があります。公式のドキュメントには記載されていなさそうですが、ソースコード(型定義ファイルHTTPReceiver.d.ts)を見ると Bolt は開始する際に HTTPServer を返してくれることがわかります。

start(portOrListenOptions: number | ListenOptions, httpsServerOptions?: HTTPSServerOptions): Promise<HTTPSServer>;

この HTTPServer を Socket.IO に渡してあげることで、Socket.IO サーバ機能を作成することができます。

const server = await app.start(port);
io_server = new io.Server(server, {
            allowEIO3: true,
        });

ちなみに、amonguscapture が使っている socketIO のバージョンに合わせるため、allowEIO3は true にしておく必要があります。

以上のように Bolt のウェブサーバに機能を追加することで、Slack から AutoMute with Amazon Chime を起動できるようになりました。

リポジトリ

本記事でご紹介した内容は下記のリポジトリにある AutoMute with Amazon Chime のソースコードに反映されています。より詳細に興味があればそちらをご確認ください。また、導入方法も記載してありますので、是非使ってみてください。とてもエキサイティングな Among Us 生活が送れると思います。

https://github.com/w-okada/automute-us-with-chime
image

まとめ

Slack 連携で多く使われている Bolt(for JavaScript)のウェブサーバを用いて、Socket.IO サーバ、静的なコンテンツ配信サーバ、REST API サーバを実現する方式をご紹介しました。Heroku を用いた場合でもかなり柔軟な Slack 拡張機能が作れそうかな、と思います。是非皆さんも Slack App を作成して素敵なコミュニケーション空間を実現していきましょう。

脚注
  1. ちょっと手を加えれば各自のサーバや Docker コンテナでも動くと思います。 ↩︎

Discussion