iOSのサイレントプッシュ通知をAzure Functionsを使って実装してみた

## 概要
iOSの機能であるサイレントプッシュを使って、定期的にアプリ側でバックグランドフェッチして情報を収集したいケースがありましたので、**Azure Functions** を使ってみた！という話です。  
通知を送るのに使ったサービスは **Firebase Cloud Messaging** なのですが、そのFCMをキックするのに **Azure Functions** を使いました。サーバレスかつ、全部無料で実装できたのでウマウマです。


## 構成
ざっくりとしたシステム構成は以下のようにしました。
<img width="777" alt="スクリーンショット 2018-07-29 23.57.02.png" src="https://qiita-image-store.s3.amazonaws.com/0/13703/b37f314d-fd11-4a2a-8a5e-e545babe66f8.png">


## 環境
* Firebase Cloud Messaging
* Azure Functions
* iOSアプリ

## サイレントプッシュ通知とは
* iOS7から実装された機能です。サイレントプッシュ通知は名前の通り、アラートや音が出ないのでプッシュを受け取ったことはユーザには分かりません。
* 通知を受け取った端末は **30秒間** だけ処理を実行することが出来るので、その間にアプリはAPIをリクエストしたりして最新の状態にすることが出来ます。
* 注意したいのは、端末やアプリの状態によってはサイレントプッシュ通知を受け取っても処理がされない場合があります。例えば以下のケースでは処理が実行されません。  
  * マルチタスク機能でユーザ自らアプリをキルした状態の場合 
  * iOSの設定でアプリのバックグラウンド更新を有効にしていない場合
  * iOSの設定でバッテリーの低電力モードを有効にしている場合

## Azure Functionsを使うメリット
* **タイマー起動ができる**  
  定期的にサイレントプッシュを送りたいのですが、Azure Functionsならタイマー起動ができるのが魅力的でした。他のAWSやGCP等のFaaSを調べてもタイマー機能があるのはAzureだけでした。
* **Functionsの呼び出しは 月100万リクエストまで無料**  
  サイレントプッシュは30分単位で送りたかったので、余裕で無料枠で出来ることが分かり選択材料の１つとなりました。
* **設定が簡単**  
  実際やってみて短時間で設定が出来ました。サイレントプッシュするだけなら、登録する実行コードはFCMにHttpリクエストするだけです。



## iOSアプリ、FCMについて
iOSアプリとFirebase Cloud Messaging の設定ついてはこの記事では割愛します。
代わりに参考にしたリンクを貼っておきます。

* [iOS での Firebase Cloud Messaging クライアント アプリの設定](https://firebase.google.com/docs/cloud-messaging/ios/client?hl=ja)
* [【iOS】Firebase の Notifications でプッシュ通知を送る](https://qiita.com/koogawa/items/ca8cce019b4ff7ce2576)
* [【iOS10】Firebaseでサイレント通知を行う](https://qiita.com/shiba1014/items/099f8e7aa37d5e2540da)

## Azure Functionsの設定

1、Functionを作るためメニューの「リソースの作成」を選び、「Compute」->「Function App」を選びます。
<img width="500" alt="スクリーンショット 2018-07-18 23.56.58.png" src="https://qiita-image-store.s3.amazonaws.com/0/13703/27333930-2e26-d49f-fd90-2ee4722284f6.png">  
　 
2、作成画面に遷移するので、「アプリ名」に好きな名前を入れます（他はデフォルトのままにしました。また、従来課金プランにしないと無料にならなそうです。）
<img width="400" alt="スクリーンショット 2018-07-19 0.00.43.png" src="https://qiita-image-store.s3.amazonaws.com/0/13703/1ebda367-5778-ab6e-71d4-a4b8513c0744.png">  
　

3、「作成」ボタンをクリックすると、デプロイが始まるので終わるまで待ちます。  
　


4、デプロイが終わると通知が来ますので、「リソースへ移動」を選びます。
<img width="400" alt="スクリーンショット 2018-07-19 0.05.38.png" src="https://qiita-image-store.s3.amazonaws.com/0/13703/6155f9de-bc78-fff4-b35e-225146c2ec68.png">  
　

5、次にメニューの関数の横にある「+」ボタンを選びます。すると。シナリオと言語を選ぶ画面が表示されるのですが、下部に「カスタム関数」を選択します。
<img width="500" alt="スクリーンショット 2018-07-19 0.07.43.png" src="https://qiita-image-store.s3.amazonaws.com/0/13703/49de951f-0bdc-9efe-a4e0-409e6a8a29c8.png">  
　

6、テンプレートが表示されるので、「Time trigger」を選択します。
<img width="500" alt="スクリーンショット 2018-07-19 0.11.16.png" src="https://qiita-image-store.s3.amazonaws.com/0/13703/5e6f0206-c316-ddf0-4baf-ad2dca13da1e.png">  
　
7、言語、関数名、タイマースケジュールの設定が表示されるので好きな設定に変更します。（ここでは言語はjavascriptで、タイマーを30分周期にしたかったので以下のように設定をしました）
<img width="200" alt="スクリーンショット 2018-07-19 0.12.31.png" src="https://qiita-image-store.s3.amazonaws.com/0/13703/b11532d3-1610-5180-6424-5ea912950aaf.png">  
　

8、ここまで出来たら次はHTTPリクエストを簡単に実装するため、Node.jsのパッケージをインストールします。`https://{function_app_name}.scm.azurewebsites.net` にブラウザでアクセスします。自分の場合だと https://silentpushtest.scm.azurewebsites.net になります。（ここについて詳しく知りたい方は[Azure Functions の JavaScript 開発者向けガイド](https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-reference-node#node-version-and-package-management)が参考になるかと思います。）
　
9、すると、以下の画面になると思うので、メニューの「Debug Console」-> 「CMD」を選択します。
<img width="500" alt="スクリーンショット 2018-07-30 0.44.52.png" src="https://qiita-image-store.s3.amazonaws.com/0/13703/874bd738-1dbb-8fae-328c-a187b5e148ae.png">  
　

10、コンソール画面に遷移するので、`D:\home\site\wwwroot`までディレクトリを下っていきます。そしたら `npm init` を実行します。質問の回答はデフォルトのままにしました（ひたすらEnter）  

```console
D:\home\site\wwwroot> npm init
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.

See `npm help json` for definitive documentation on these fields
and exactly what they do.

Use `npm install <pkg> --save` afterwards to install a package and
save it as a dependency in the package.json file.

Press ^C at any time to quit.
name: (wwwroot) 
version: (1.0.0) 
description: 
entry point: (index.js) 
test command: 
git repository: 
keywords: 
author: 
license: (ISC) 
About to write to D:\home\site\wwwroot\package.json:

{
  "name": "wwwroot",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}


Is this ok? (yes) 
```

11、package.jsonが作られていればOKです。次に `request`パッケージをインストールします。  

  ```console
  D:\home\site\wwwroot> npm install request --save
  wwwroot@1.0.0 D:\home\site\wwwroot
  `-- request@2.87.0 

（省略）
  ```

12、さて、Functionの設定に戻ります。先ほど作成したFunctionを選ぶとソースコードを実装する画面になります。
<img width="500" alt="スクリーンショット 2018-07-30 1.00.15.png" src="https://qiita-image-store.s3.amazonaws.com/0/13703/4ef625d4-5ea0-5d97-4681-41efedc00759.png">  
　

13、ソースコードを以下にようにします。`ServerKey` はFirebaseの設定画面から取得することができます。`TopicName` はアプリ側で購読させたTopic名を指定してください。

```javascript
module.exports = function (context, myTimer) {
  var timeStamp = new Date().toISOString();

  if (myTimer.isPastDue) {
    context.log('JavaScript is running late!');
  }

  var request = require('request');

  var options = {
    url: 'https://fcm.googleapis.com/fcm/send',
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': 'key={ServerKey}'
    },
    json: {
      to: '/topics/{TopicName}',
      priority: 'high',
      content_available: true
    }
  };

  request.post(options, function (error, response, body) {
    context.log("Response Code : "+response.statusCode);
  });

  context.log('JavaScript timer trigger function ran!:', timeStamp);
  context.done();
};
```

14、「実行」ボタンを押すことで試しにサイレントプッシュが送れるか確認することができます。実行してみて問題がなければ「保存」ボタンを押して Functionの設定は以上で終わりです！

## 最後に
説明が長くなってしまいましたが、やってることはほぼGUI操作で終わるのでとても楽でした。
なりより、サーバを持たないかつ、無料で実装出来たのが非常に良かったです！