Firebase FunctionでFirestoreの変更通知を送るには

# Firebase FunctionでFirestoreの変更通知を送るには

###### 前々々々々回：[Angularの新規プロジェクト作成手順 - material2とService Workerは使いたい](https://qiita.com/atomyah/items/3e125b072dd74181e582)

###### 前々々々回：[AngularプロジェクトにFirebaseを導入設定](https://qiita.com/atomyah/items/750d076e3dd5449423fd)

###### 前々々回：[Angular + FirebaseでCRUD機能を実現](https://qiita.com/atomyah/items/e27c6d2c08f4ac260cfe)

###### 前々回：[Firebase ：FireAuthを使って管理者ページを制限したいのだが](https://qiita.com/atomyah/items/88b61b1ff68cd1711856)

###### 前回：[Firebaseui＋Angularで、閲覧は自由、書き込みは認証を必要とする制限機能追加](<https://qiita.com/atomyah/items/9eef67bf5c35bc7920cb>)

<br>


Firestoreに書き込みあったらお知らせ通知ほしいな。の備忘録


firebase notificationで検索するといろいろ出てくる。やはりfirebase functionsが必要。


### firebase functionsとは？

AWS Lambda、Azure Functionと同じようなもの。


<br>


ではインプリしていこう。

### モジュールインストール

```cmd
C:\projectA>npm install firebase-functions@latest firebase-admin@latest --save
C:\projectA>npm install -g firebase-tools
```

firebase-toolsは以前入れたかもしれないが、「多くの場合、新機能とバグ修正は Firebase CLI の最新バージョンと `firebase-functions`SDK でのみ利用できます。Firebase プロジェクトの `functions` フォルダ内で上記コマンドを使用して Firebase CLI と SDK の両方を頻繁に更新することをおすすめします。」



ということなので実行。

<br>
### firebase initを実行

まずログイン

```cmd
C:\projectA>firebase login
```



ああやはりすでにfirebase-tools入れてログインしてた。

```cmd
Already logged in as xxxxxxx@gmail.com


   ╭───────────────────────────────────────────╮
   │                                           │
   │      Update available 6.5.0 → 6.5.3       │
   │   Run npm i -g firebase-tools to update   │
   │                                           │
   ╰───────────────────────────────────────────╯
```



<br>
ではfirebase initをば、

```cmd
C:\projectA>firebase init

     ######## #### ########  ######## ########     ###     ######  ########
     ##        ##  ##     ## ##       ##     ##  ##   ##  ##       ##
     ######    ##  ########  ######   ########  #########  ######  ######
     ##        ##  ##    ##  ##       ##     ## ##     ##       ## ##
     ##       #### ##     ## ######## ########  ##     ##  ######  ########

You're about to initialize a Firebase project in this directory:

  C:\projectA

? Are you ready to proceed? (Y/n)
```



Yといれてエンターで次のような

```cmd
? Are you ready to proceed? Yes
? Which Firebase CLI features do you want to set up for this folder? Press Space to select features, then Enter to confirm your choi
ces. (Press <space> to select)
>( ) Database: Deploy Firebase Realtime Database Rules
 ( ) Firestore: Deploy rules and create indexes for Firestore
 ( ) Functions: Configure and deploy Cloud Functions
 ( ) Hosting: Configure and deploy Firebase Hosting sites
 ( ) Storage: Deploy Cloud Storage security rules
```





Functionsを選ぶ。のちのちHostingにアップするのでHostingも選んでおく。

```cmd
 ( ) Database: Deploy Firebase Realtime Database Rules
 ( ) Firestore: Deploy rules and create indexes for Firestore
 (*) Functions: Configure and deploy Cloud Functions
>(*) Hosting: Configure and deploy Firebase Hosting sites
 ( ) Storage: Deploy Cloud Storage security rules

```



Firebase上に存在するどのプロジェクトと紐づけるのか聞いてくるので該当プロジェクトを選択。

```cmd
=== Project Setup

First, let's associate this project directory with a Firebase project.
You can create multiple project aliases by running firebase use --add,
but for now we'll just set up a default project.

? Select a default Firebase project for this directory:
  [don't setup a default project]
  codegrid-practice (CodeGrid-practice)
> projecta-xxxx (ProjectA)
  student-app-xxxx (student-app)
  [create a new project]
```



JavaScriptとTypeScriptのどっちか選べと。TypeScriptにしておく。

```cmd
=== Functions Setup

A functions directory will be created in your project with a Node.js
package pre-configured. Functions can be deployed with firebase deploy.

? What language would you like to use to write Cloud Functions?
  JavaScript
> TypeScript
```



最後の２つの質問はよく意味わからない。どっちもYesで。

```cmd
? Do you want to use TSLint to catch probable bugs and enforce style? Yes
+  Wrote functions/package.json
+  Wrote functions/tslint.json
+  Wrote functions/tsconfig.json
+  Wrote functions/src/index.ts
+  Wrote functions/.gitignore
? Do you want to install dependencies with npm now? Yes
```



おっと、Hostingの設定にも行ってしまった。

Angularを使ってるのでもちろん下記のように答える。

```cmd
=== Hosting Setup

Your public directory is the folder (relative to your project directory) that
will contain Hosting assets to be uploaded with firebase deploy. If you
have a build process for your assets, use your build's output directory.

? What do you want to use as your public directory? dist
? Configure as a single-page app (rewrite all urls to /index.html)? Yes
+  Wrote dist/index.html
```

<br>
functionsフォルダができている。
![1557567299943.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307441/94d327b4-fe1d-e620-b7d6-41784e0de24e.png)

<br>
### functionsのスクリプトをデプロイ

そのfunctionsにcdで移って、次のコマンドでモジュールをnpm install

```cmd
C:\projectA>cd functions
C:\projectA\functions>npm install --save request
C:\projectA\functions>npm install @types/request-promise
C:\projectA\functions>npm install request-promise --save

```
<br>


##### まずお試しスクリプト

functions配下のindex.tsを以下のように。

###### index.ts

```typescript
// 試し
import * as functions from 'firebase-functions';

exports.helloWorld = functions.https.onRequest((req, res) => {
    res.send("Hello from Firebase!");
  });
```

<br>

デプロイコマンド

```cmd
C:\projectA>firebase deploy --only functions:helloWorld
```

※`--only` オプションを付けることで、該当する関数のみデプロイする。

<br>

```cmd
+  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 (39.42 KB) for uploading
+  functions: functions folder uploaded successfully
i  functions: current functions in project: lineNotify(us-central1)
i  functions: uploading functions in project: helloWorld(us-central1)
i  functions: creating Node.js 8 function helloWorld(us-central1)...
+  functions[helloWorld(us-central1)]: Successful create operation.
Function URL (helloWorld): https://us-central1-projecta-xxxx.cloudfunctions.net/helloWorld

+  Deploy complete!
```


<br>


Function URLにアクセスしてみる。

```cmd
Function URL (helloWorld): https://us-central1-projecta-xxxx.cloudfunctions.net/helloWorld
```

<br>
ワンダホー
![1557633090161.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307441/f01b4837-8833-65e0-5ee6-2e0658332915.png)

<br>
##### 本番スクリプト

以下のとおりをindex.tsにコピペ.

※<生成したLINE Notifyのアクセストークン> と＜コレクション名＞の箇所だけ置き換えてください。※

LINE Notifyのアクセストークンの取得方法はこちら[LINE notify を使ってみる](<https://qiita.com/iitenkida7/items/576a8226ba6584864d95>)

###### index.ts

```typescript
import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';
import * as rp from 'request-promise';

// firebase の初期化
admin.initializeApp();

// MEMO: simple が true の場合、 3XX のステータスコードを返却された時でもエラーとなる模様
const request = rp.defaults({simple: false});

const FIRESTORE_COLLECTION = '<コレクション名>';
const LINE_ACCESS_TOKEN = '<生成したLINE Notifyのアクセストークン>';

// functions にデプロイする関数
// とあるコレクションにドキュメントを作成すると、LINEに通知します。
// MEMO: ドキュメントに対し、「作成」「更新」「削除」を取得することができます。詳細は下記参照
//       https://firebase.google.com/docs/functions/firestore-events?hl=ja
export const lineNotify = functions.firestore
  .document(FIRESTORE_COLLECTION + '/{doc}')
  .onCreate((snap, context) => {

  // line へ通知
  request({
    uri : 'https://notify-api.line.me/api/notify',
    method: 'POST',
    headers: {
      'Content-Type' : 'application/x-www-form-urlencoded',
      'Authorization': 'Bearer ' + LINE_ACCESS_TOKEN
    },
    form: {
      'message': 'Success!!!\r\n' + FIRESTORE_COLLECTION + 'にドキュメントが作成されました。'
    }
  }).then(function(response) { // 成功時 (3XX)
    // LINE Notify からのレスポンスを Functions のログに出力
    console.log('User has %d repos', response.length);
    console.log('response :' + response);
    return null;
  }).catch(function (err) { // 失敗時 (4XX or 5XX)
    Error(err);
   });
  return null;
});
```

[@r-hashioka](https://qiita.com/r-hashioka)様の[Cloud Functions for Firebase ～Firestore をトリガーとし、LINE に通知～](<https://qiita.com/r-hashioka/items/a1b64a6137839d418b27>)のまんまです。ありがとうございます。（`const db = admin.firestore();`の行だけ必要ないので削除しました）



<br>
デプロイコマンド（関数のlineNotifyをデプロイしている）

```cmd
C:\projectA>firebase deploy --only functions:lineNotify
```



```cmd
i  deploying functions
Running command: npm --prefix "$RESOURCE_DIR" run lint

> functions@ lint C:\projectA\functions
> tslint --project tsconfig.json

Running command: npm --prefix "$RESOURCE_DIR" run build

> functions@ build C:\projectA\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 (39.25 KB) for uploading
+  functions: functions folder uploaded successfully
i  functions: uploading functions in project: lineNotify(us-central1)
i  functions: creating Node.js 8 function lineNotify(us-central1)...
+  functions[lineNotify(us-central1)]: Successful create operation.

+  Deploy complete!
```

<br>

Firebaseコンソールに追加された。
![1557634542600.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307441/1391bf6e-a55f-56cb-b367-4fccb1ba2ebc.png)


<br>

### 動作確認

Firestoreを更新してみる。あたらしいドキュメントを追加。
![1557635008902.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307441/05e49341-fcd2-7901-8e04-e83eb71a3f9f.png)

<br>
ラインに通知が届かない。。。ログを見ると、
![1557635071897.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307441/b4d1480a-79e5-f166-32bf-860dda6fd7cd.png)

ちゃんと動いたようだが、

```
Billing account not configured. External network is not accessible and quotas are severely limited. Configure billing account to remove these restrictions
```

とな。どうやら**無料枠(Sparkプラン)だと外部API叩けない**ようだ。

<br>
ということで従量制のBladeにプランをアップグレード。Gmail APIを使ってメール送信にすればSparkでイケる。
Sparkプランで大丈夫なAPI群 → [Google Cloud Platform](<https://console.cloud.google.com/apis/library?hl=ja&pli=1>)

<br>
LINENotify来た
![1557636594495.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307441/470f54dc-5f04-3dfa-290b-829d39298301.png)


<br>

参考：
[Cloud Functions for Firebase ～Firestore をトリガーとし、LINE に通知～](<https://qiita.com/r-hashioka/items/a1b64a6137839d418b27>)
[Firebase で Cloud Functions を簡単にはじめよう](<https://qiita.com/tdkn/items/2ed2b01f2656fc50da8c>)
[Dialogflowを用いた外部APIの利用は無料ではできない！？](https://yutakami.work/?p=590)