SlackのRTM APIを使った開発のハマりどころやつらみ



主に [SlackをTwitterのタイムラインライクに閲覧できるクライアント](http://sisisin.hateblo.jp/entry/2018/12/03/100000) を作ったときにハマった話を書きます。

---

この記事は[オプトテクノロジーズアドベントカレンダー](https://qiita.com/advent-calendar/2018/opt-technologies)21日目のエントリーです。

20日目は @yorishigeito さんの「[開発合宿でIoTした](https://qiita.com/yorishigeito/items/0aaeb2783038583e89e4)」です
22日目は @hey_cube さんの「[Storybook + zisui + reg-suit な visual testing を構築する](https://qiita.com/hey_cube/items/fbeab4302cfb617cf941)」です

---


# 前提

アプリ要件
- Slackのメッセージをほぼリアルタイムに受け取って表示したい
- アプリ入れさせたくないのでwebサービスとして提供したい

以上の要件を満たすためにRTM APIを選択
Event APIという線もあったが、ngrok使ったりして開発するの地味に面倒そうだった（と思って試さなかったのがもしかしたら大失敗だったかも）


# ハマり・つらみ

ハマり

- `@slack/client`の RTM API実装がnodeじゃないと動かない
- OAuth Scopeの `client` と `identity.basic` を同時に許可する事ができない
- passport.jsのslackプラグインで使い物にならないものが乱立している

つらみ

- `rtm.start`エンドポイントの API rate limitが少なすぎる
- `@slack/client` の `RTMClient#start`でコネクション確立中に再度コネクション確立要求をすると例外を吐く
- slackのテキストパーサー・コンバータがない
- APIの仕様にドキュメントが追いついてない
- そもそもSlackを再実装するのが大変


解説していきます

## `@slack/client`の RTM API実装がnodeじゃないと動かない

まんまです。
これが理由で、なんらかのnode.jsが動くバックエンド実装が必要になってしまう
実際はelectronを使うかnodeでサーバーを立てるかの2択。
EventAPIを使っていたとしてもサーバーが必要になるので、どちらにせよフロントエンドで完結出来ないので手軽でない。。


## OAuth Scopeの `client` と `identity.basic` を同時に許可する事ができない

アプリを実際に使うにあたって、ユーザーはOAuthの認可フローを通す必要がある（ユースケースによっては[Slack Button](https://api.slack.com/docs/slack-button)　を利用することで省略も可能）。

このときに、RTM APIを使うのに必要な `client` という権限と、自分自身のユーザー情報を取得するのに必要な `identity.basic`という権限を同時に認可しようとするとSlackに以下のようなエラーメッセージと共に突っ返される

```
Invalid permissions requested
Cannot request object scopes with deprecated scopes
```

![ss_ 2018-12-25 22.22.53.png](https://qiita-image-store.s3.amazonaws.com/0/54872/91b3b5fc-0f65-a69f-9bf7-53d55e394bae.png)

ドキュメントからの推測しかできないが、おそらく　[Scopeのページ](https://api.slack.com/scopes)で `legacy` ラベルが張られている権限はその他のstableな権限と同時にリクエスト出来ないのではないかと予想される(実際試してみると `client` , `admin`のような組み合わせは通る)

この問題が解決できないと、ユーザーがアプリを動かすに至れないのでなんとかする必要があったが、どこにも情報がなかったのでかなり苦労した。
実際に作ったアプリでは、「 `identity.basic`の権限を許可する」というフローのあとに、「 `client`の権限を許可する」というフローを踏ませるワークアラウンドでなんとかした
~~これがほんとの二段階認証！~~

実際の動きはこんな感じ。最悪のUX。
https://gyazo.com/84cf702b999d7ac65c60f3f62e511210


## passport.jsのslackプラグインで使い物にならないものが乱立している

気をつけましょう・・・
passport.jsでOAuth認可フローを実装する場合、大抵 `passport-(サービス名)` という名前のライブラリを使うことになるが、 `passport-slack`は普通にバグっていて動かない上にメンテもされていないという状態になっている。
（普通に認可するだけだといいが、二段階認証（違う）を実装する都合でバグを踏んだみたいなのもあり・・・）

最終的には `passport-slack-oauth2`を使って動かせたのでよかったものの、結構辛かった・・・

## `rtm.start`エンドポイントの API rate limitが少なすぎる

rate limitがひどく低く、1分間に数回でlimitになる ( https://api.slack.com/methods/rtm.start )
またこれはあとから気づいたことだけど、SlackAPIはOAuthトークンごとのlimitではなく、ワークスペース単位のlimitなので、誰にでも使えるサービスとして提供するのに耐えられない（致命的）
https://api.slack.com/docs/rate-limits#overview


今回作ったアプリは、アプリのサーバーがユーザーごとにSlackとのコネクションを作り、リアルタイムにメッセージを受け取る形をとっている。
そのためnodeサーバーが再起動したときには、その時点で接続していたユーザーのコネクションを全て再接続したいところだが、それを安直にやるとrate limitに引っかかってしまう。つらい（つらい
ちなみにこの問題は時間がなかったので未解決・・・

## `@slack/client` の `RTMClient#start`でコネクション確立中に再度コネクション確立要求をすると例外を吐く

例えば「とあるユーザーがこのアプリを使い始めて、認可フローを終えてアプリに戻ってきたタイミング（＝初めてアプリのサーバーがSlackとコネクションを確立するタイミング）で、コネクションが成立する前にブラウザをリロードする」みたいな状況で死ぬ

しかもこの場合、ユーザー的にはあとからの接続要求が通ってほしいが、サーバーとしてはあとから来たほうで例外が出るのでフロントにコネクション成立した状態で返せないという状態になる（アプリの作りも悪いとは思う）
また、 `RTMClient`はコネクション確立中というステータスを取れないので、回避もままならない。例外をcatchしてもポーリングするしかなくて、先述したrate limitも相まって非常に難解な問題だった（なお未解決。。。


## slackのテキストパーサー・コンバータがない

そのまんま。
markdownライクなあの記法をちゃんと解釈してhtmlにしたいけど、それを実現するライブラリがなさそう（あったら教えてください）

たとえばSlackの取り消し線は `~文字列~`だが、これを ~~文字列~~ のように変更するという実装が簡単にできない。
一応ドキュメントは [ある](https://api.slack.com/docs/message-formatting)が、実装するのが大変・・・


## APIの仕様にドキュメントが追いついてない

つらい。ドキュメント書くのは大変だからしょうがないという気持ちもありつつ。。。

たとえば `message`イベントの[ドキュメント](https://api.slack.com/events/message)では解説されていないメッセージがバンバン飛んでくるので色んなメッセージを叩いてがんばりましょうという感じ。
botやアプリ連携で投稿されるCircleCIなどの投稿が代表（だったと思う）

## そもそもSlackを再実装するのが大変

当たり前。
クライアント作ってみて思ったが、Slack社はすごい。すごい！！



# おわりに

つらかった（小並感）