FirebaseアプリからSlackへ通知を行う。Slack AppのWEBへのデプロイ編。

WEBアプリを利用してくれるユーザへの通知方法として、そのユーザのSlackに通知が飛ばせたらイイねという内容をQiita記事化しています。

## 概要

これまでの記事も含めて、やってる内容は以下の通り。

- FirebaseでつくったWEBアプリに「Add to Slack」ボタンをつけて、**FirebaseのユーザIDに対してSlackのアクセストークンを紐付けて保存する**
- 各FirebaseのユーザIDに紐付けたSlackのアクセストークンをつかうことで、各ユーザが指定したSlackへ通知を送る
- GCPのスケジューラ機能を使って、それらの通知を定期的に行う**(今回の記事)**。
- 「FirebaseのWEBアプリ」「Slackのアクセストークンを取得するOAuthの処理」「スケジューラから呼び出される通知処理」などを、Firebaseがホスティングしてくれる**Firebase Hosting/Cloud Functionsへデプロイ(本番リリース)する(今回の記事)**
- つくったSlackアプリを**任意のSlackのワークスペースで使用するための設定(今回の記事)**

[前回](https://qiita.com/masatomix/items/05136e5d7f9613ad6939)までで、基本的な処理シーケンスができあがりました。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/73777/110fd0ff-088a-9626-4c95-73fe0c288913.png)

上記の処理シーケンスがローカルの WEBサーバとFirebase Functionsで実装出来たので、あとは

- 「WEBアプリ」と「Firebase Functions」の**本番(Firebase Hosting)へのデプロイ**
- **スケジューラから chat_pub関数を呼び出す**事で、よりSlack上で動くアプリっぽくする
- そのための諸々の環境設定(**ローカルと本番だとURLがかわったりするので。。**)

などを行っていこうと思います。

## 前提環境

これまでの記事でインストールされてるプロダクトは以下のとおり。

```console
$ sw_vers
ProductName:	Mac OS X
ProductVersion:	10.14.3
BuildVersion:	18D42

$ node --version
v10.14.2

$ firebase --version
6.4.0
```

今回追加で、スケジューラを操作するための「Google Cloud Platform のCLI」を入れます。インストール はこちら。

- [Google Cloud SDK のインストール/アンインストールメモ(公式バージョン)](https://qiita.com/masatomix/items/57891a026856f5c39a7e)

完了すると、

```console
$ gcloud --version
Google Cloud SDK 263.0.0
bq 2.0.47
core 2019.09.13
gsutil 4.42
$
```

こんな感じになるかと思います。

##「WEBアプリ」と「Firebase Functions」の本番(Firebase Hosting)へのデプロイ

前回までのセットアップを行うことで、

```console
$ firebase serve --only hosting,functions
```

でローカルでWEBアプリとFunctionsが起動したのでした。

さて Firebase上の Hosting へのデプロイですが、いままでの一連の記事で「Firebase上にアプリは作成済み」で進めてきているので、デプロイ自体は

```console
$ firebase deploy --only hosting,functions
```

とシンプルです。しかしながら**[前回の記事](https://qiita.com/masatomix/items/05136e5d7f9613ad6939)までは、ローカルのサーバやFunctionsで疎通していたため、URLなどがローカルを指しているので、それらを書き換え**ます。おさらいすると、


| 項目       |                     値 |    
|:----:|:----------------|
|Firebaseプロジェクト名|``##PROJECT ID##``|
|WEBアプリのアクセスURL|``http://client.example.com:5000/`` **コード上は記載がないので変更箇所ナシ**|
|Functions上の関数(oauth)|``http://client.example.com:5001/##PROJECT ID##/us-central1/oauth`` **要変更**|
|Functions上の関数(chat)|``http://client.example.com:5001/##PROJECT ID##/us-central1/chat`` **コード上は記載がないので変更箇所ナシ**|
|投稿先のSlackワークスペース|Slack Appを開発しているワークスペース|
|Slack App のclient_id|``##SLACK CLIENT ID##``|
|Slack App のclient_secret|``##SLACK CLIENT SECRET##``|
|認可後の、Redirect URLs|``http://client.example.com:5001/##PROJECT ID##/us-central1/oauth``**要変更(Slack画面にて)**|

こんな感じだったので、該当箇所を書き換えていきます。


### 設定変更と再ビルド

具体的には以下の通り。

```console
$ cd todo-examples/

src/restConfig.js を自分の設定に書き換え

$ cat src/restConfig.js
export default {
  apiUri: 'http://client.example.com:5001/##PROJECT ID##/us-central1/oauth'
  → https://us-central1-[##PROJECT ID##].cloudfunctions.net/oauth
}

$ npm run build
```

つづいて、Firebase Functionsのビルド。

```console
$ cd functions/
functions/src/oauthConfig.ts  を自分の設定に書き換え

$ cat src/oauthConfig.ts 
export default {
  client_id: '##SLACK CLIENT ID##', ← さきほどひかえておいたSlackのClient IDの値を設定
  client_secret: '##SLACK CLIENT SECRET##',← さきほどひかえておいたSlackのClient Secretの値を設定
  authorization_endpoint: 'https://slack.com/oauth/authorize', ←ココはこのまま
  token_endpoint: 'https://slack.com/api/oauth.access', ←ココはこのまま
  redirect_uri: 'http://client.example.com:5001/##PROJECT ID##/us-central1/oauth', 
  → https://us-central1-[##PROJECT ID##].cloudfunctions.net/oauth
  scope: 'chat:write:user' ←ココはこのまま
}

$ npm run build
$ cd ../
```

環境によってprefixが異なるかもしれませんが、一度ビルドするとURLは固定になるので(後で出てきます)厳密にはそのURLなどを記載してください。


### Slack App のRedirect URLsの設定追加

前回は 「**Slack認可サーバからのリダイレクト先として、Slack Appの開発画面ではなく Firebase Functionsのoauth関数**」をリダイレクト先にしましたが、**今回はそれが本番サーバになるので、リダイレクト先の追加(もしくは書き換え)が必要**ですね。

Slack Appの開発画面>>「OAuth & Permissions」にRedirect URLs を設定出来る箇所があるんでしたね。そこで ``https://us-central1-[##PROJECT ID##].cloudfunctions.net/oauth`` を追加し Save URLs をクリックして保存しましょう。

![03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/73777/c0cdbd79-3f94-56fd-7fb4-bdd78fb72522.png)


### Distribution 設定を修正し、他のワークスペースからもSlackアプリを使用できるようにする

さてさて、これで本番デプロイでもよいんですが、もうひとつ。
このアプリはいま「Not distributed」というステータスです。
![n01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/73777/8253a087-2a21-91f5-e90a-d4b41bfc9df5.png)


このステータスは、このSlackアプリを作成したワークスペース以外からは使用できない状態ということです。ためしに他のワークスペースにログインした状態でSlackアプリのOAuth認可画面を呼び出してみると、、
![n02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/73777/9a4306f1-9cd3-1e8f-a996-4fd084273d14.png)


エラーとなりました。他のワークスペースでも使用可能にするためには、Slackアプリの開発画面より、Basic Information >> Manage Distribution >> Distribute App を選択
![n03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/73777/3e0dbea9-347a-6d79-7349-3d4da67917ef.png)


Remove Hard Coded Information をひらいて「**I’ve reviewed and removed any hard-coded information.**」 をチェックし「Activate Public Distribution」を押下します。
![n04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/73777/72aea07d-7673-4b2a-aa30-e39c2c8f6fb9.png)

この欄は上記の説明にもあるとおり「OAuthのアクセストークン値」や「WebhookのURL」など、**バレたら使われちゃう値をハードコード「してない」ことをちゃんと確認**しました、というチェックリストの役割になっています。

Slackアプリのステータスも「Distribution activated」となりましたね。
![n05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/73777/c8a7d693-ee9b-91d8-620c-454010cc7e7a.png)

再度、別のワークスペースにログインしてSlackアプリの認可画面を開いてみると、、
![n06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/73777/25c66839-cc3d-405c-4f7c-89f3b5168e49.png)

開けました。まだ**Slackさまが検証・承認はしてないけどね**というステータスだそうですが、まずはココまででよいでしょう。



### いよいよ本番デプロイ


```console
$ pwd
/xxx/xxx/todo-examples
$ firebase deploy --only hosting,functions

...割愛

=== Deploying to 'fb-samples-xxxxx'...

i  deploying functions, hosting
Running command: npm --prefix "$RESOURCE_DIR" run build

> functions@ build /xxx/xxx/todo-examples/functions
> tsc

✔  functions: Finished running predeploy script.
i  functions: ensuring necessary APIs are enabled...
✔  functions: all necessary APIs are enabled
i  functions: preparing functions directory for uploading...
i  functions: packaged functions (54.9 KB) for uploading
✔  functions: functions folder uploaded successfully
i  hosting[fb-samples-xxxxx]: beginning deploy...
...割愛


i  functions: creating Node.js 6 function chat(us-central1)...
i  functions: creating Node.js 6 function chat_pub(us-central1)...
i  functions: creating Node.js 6 function oauth(us-central1)...
Function URL (chat): https://us-central1-fb-samples-xxxxx.cloudfunctions.net/chat
Function URL (oauth): https://us-central1-fb-samples-xxxxx.cloudfunctions.net/oauth  '← URL Fixしました'

...割愛

✔  hosting[fb-samples-xxxxx]: release complete

✔  Deploy complete!

Please note that it can take up to 30 seconds for your updated functions to propagate.
Project Console: https://console.firebase.google.com/project/fb-samples-xxxxx/overview
Hosting URL: https://fb-samples-xxxxx.firebaseapp.com
$
```

以上で終了です。``https://fb-samples-xxxxx.firebaseapp.com`` へアクセスしてみると、、、
![01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/73777/9e4c5836-a040-72c9-f84d-0583c9b1e3c3.png)


表示されました！使ってみて、OAuthの処理シーケンスの確認や、FirestoreへSlackのアクセストークンが格納されていることなどを、あらためて確認しましょう！
あ、必要に応じて、前Firestoreに登録されたトークンなどは適宜削除してください:-)



### 参考:Firebaseのプロジェクト上へのアプリの作成

さてさて、一連の記事で「アプリは作成済み(=configを生成済)」としましたが、一応Firebaseのプロジェクト上にアプリを作成する記事のリンクをつけておきます。参考: [Firebase や Google Cloud Platformの初回のサインアップその他の備忘メモ](https://qiita.com/masatomix/items/791699b3de3486618dd7#firebase%E3%81%AE%E3%83%97%E3%83%AD%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E5%86%85%E3%81%AB%E3%82%A2%E3%83%97%E3%83%AA%E3%82%92%E4%BD%9C%E6%88%90%E3%81%99%E3%82%8B) 
「アプリを追加」の節で、**サーバ上にアプリ作成**が実施でき、あわせて**Hostingの領域も割り当て**られます。

また、[記事中のリンク](https://qiita.com/masatomix/items/bd1c3a6b31e8d2cb7aef)先に、**デプロイするためのWEBアプリをイチから作成する**手順も記載してありますので、``git clone``でコードを取得するのではなくイチからWEBアプリを作成する際には適宜ご参照ください。

サーバへのデプロイは以上です。


## スケジューラから chat_pub関数を呼び出す事で、よりSlack上で動くアプリっぽくする

Slackのアクセストークンを取得する処理を、本番WEBサーバへデプロイするコトが出来ました。

のこりは Google のスケジューラから呼び出す件ですが、実はそのためのコード``chat_pub``はすでに先ほどリリース済みです。

```typescript:functions/src/index.ts(スケジューラから呼び出すコード部のみ)

export const chat_pub = functions.pubsub.topic('slackChatTopic').onPublish(async message => {
    await sendSlack()
  })

async function sendSlack() {
  const querySnapshot = await admin.firestore().collection('slackToken').get()

  querySnapshot.forEach(doc => {
    const fbUserId = doc.id
    const jsonData = doc.data()

    const option = {
      url: 'https://slack.com/api/chat.postMessage',
      method: 'POST',
      headers: {
        'Content-Type': 'application/json; charset=UTF-8',
        Authorization: `Bearer ${jsonData.access_token}`
      },
      json: {
        channel: '#general',
        text: `${fbUserId} です、今日は！`
      }
    }
    request(option, (error, response, body) => {
      if (error) {
        console.log('error:', error)
        return
      }
      if (response && body) {
        console.log('status Code:', response && response.statusCode)
        console.log(body)
      }
    })
  })
}
```

``slackChatTopic``というTopicにメッセージが投げられたら ``functions.pubsub.topic('slackChatTopic').onPublish`` に渡されるコールバック関数が呼び出されるという仕組みです。

試しに疎通してみます。

```console
$ gcloud pubsub topics publish slackChatTopic --message '{"param1":"value1"}'
messageIds:
- '68937086780xxxx'
```

コールバックに渡された関数が呼び出された結果、Slackへ通知が行ったはずです。よさそうですね。


つづいてスケジュール設定ですが、かつて「[Cloud Functions for Firebase の関数をスケジューラから定期的に呼び出す](https://qiita.com/masatomix/items/b40a2c604319e52df4bf)」へ整理したので、それを参考に、10分に一回、実行するよう設定してみます。

まずはスケジュールの確認。まだ設定してないので空のハズ。

```console
$ gcloud scheduler jobs list
Listed 0 items.
```

ですね。さて、10分おきというcronを仕込んでみます。

```console
$ gcloud scheduler jobs create pubsub testJob \
   --topic slackChatTopic \
   --message-body='test' \
   --schedule '*/10 * * * *' \
   --time-zone='Asia/Tokyo'

name: projects/fb-samples-xxxxx/locations/asia-northeast1/jobs/testJob
pubsubTarget:
  data: dGVzdA==
  topicName: projects/fb-samples-xxxxx/topics/slackChatTopic
retryConfig:
  maxBackoffDuration: 3600s
  maxDoublings: 16
  maxRetryDuration: 0s
  minBackoffDuration: 5s
schedule: '*/10 * * * *'
state: ENABLED
timeZone: Asia/Tokyo
userUpdateTime: '2019-09-21T14:34:22Z'
```

ちゃんと設定されているか確認してみます。

```console
$ gcloud scheduler jobs list
ID       LOCATION         SCHEDULE (TZ)              TARGET_TYPE  STATE
testJob  asia-northeast1  */10 * * * * (Asia/Tokyo)  Pub/Sub      ENABLED
$
```

OKそうですね。

スケジューラが設定されたので、、、Slack側も、10分おきに通知が来てるか確認します。

![02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/73777/10d4336f-e28d-3f14-10b5-93006b294f34.png)

おお。OKですね！！


## まとめ

これまで数回の記事のまとめ。

- FirebaseでつくったWEBアプリに「Add to Slack」ボタンをつけて、**FirebaseのユーザIDに対してSlackのアクセストークンを紐付けて保存する**処理を実装しました。
- 各FirebaseのユーザIDに紐付けたSlackのアクセストークンをつかうことで、各ユーザが指定したSlackへ通知を送る処理を実装しました。
- GCPのスケジューラ機能を使って、それらの通知を定期的に行う実装をしました。
- 「FirebaseのWEBアプリ」「Slackのアクセストークンを取得するOAuthの処理」「スケジューラから呼び出される通知処理」などを、Firebaseがホスティングしてくれる**Firebase Hosting/Cloud Functionsへデプロイ(本番リリース)する**作業を実施しました。
- つくったSlackアプリを**任意のSlackのワークスペースで使用するための設定**を行いました。

今回は「WEBアプリ上でのSlack通知機能」でしたが、Slack上で使用するアプリ[^1]などを実装する場合は、**Slackサイトの「[Appディレクトリ](https://slack.com/apps)」に公開**して、そこからインストールする導線がスマートですよね。その「Appディレクトリへの公開」などについては、またいつか記事にしたいと思います。

[^1]: Slack上でアンケートとる「[Polly](https://slack.com/apps/A04E6JX41-polly)」とか


おつかれさまでしたー。


## 関連リンク

- [Cloud Functions for Firebase の関数をスケジューラから定期的に呼び出す](https://qiita.com/masatomix/items/b40a2c604319e52df4bf)
- [Cloud SDK gcloud scheduler](https://cloud.google.com/sdk/gcloud/reference/scheduler/)
- [FirebaseアプリからSlackへ通知を行う。アクセストークンは手動で設定する編。](https://qiita.com/masatomix/items/56fc2024e383875cca1a) 記事初回
- [FirebaseアプリからSlackへ通知を行う。アクセストークンも自動連携する編。](https://qiita.com/masatomix/items/05136e5d7f9613ad6939) 記事前回