react-native-firebaseのFCMでiOS本番環境で通知が届かない時にやったこと

タイトル通りだとReactNativeの話ぽいですが基本iOSの話です。
自分の場合は以下の開発環境で本番環境のみリリースしてから届かなくなりました。

###### 開発環境

- React Native `v0.57.0`
- react-native-firebase `v4.3.8`
- Push通知証明書はp12ファイルを利用
- プロビジョニングは開発用は個人アカウントで用意、リリース用は会社に用意してもらっている（会社のApple Developer権限がない状態）
    - 開発用の個人アカウントのSigingはAuto Signing
    - リリース用はもらったプロビジョニングプロファイルをあてる
- Firebaseも同様に開発用とリリース用でプロジェクトがそれぞれある
- 開発用はAdHoc（Sandbox）だからか通知は問題なく届く

###### 届かないとはどういう現象？

- Backgroundで通知センターに何も来ない
- Foregroundで通知が来たタイミングでGUI上にアクションを起こすようにしていたが何もおこらない


## 届かなくなったタイミング

リリース前のある段階まではリリース版をTestflightで配布しても届いていました。
そこから特に実装面で何かしたわけではないですが、プロビジョニングプロファイルの更新タイミングがリリース直後にやってきました。それまでの間アプリ上で特に明示的に通知を流していなかったので気づかず。

証明書更新のタイミングでTestFlightで配信してFCMのコンソールからテスト配信を試みるも届かないことが判明。
ここから戦いの始まり。

## 原因を探る

![](https://i.stack.imgur.com/fpT9Q.png)
引用元: [Sending FCM notifications without App Server
](https://stackoverflow.com/questions/52992733/sending-fcm-notifications-without-app-server)


上の引用元の画像の通り、FCMはアプリからFCM APIを叩きにいきFCM Tokenを取得しにいきます。原因の切り分けをしました。およそ原因になりそうなのは以下のどれか。

1. FCM側の問題
2. Xcodeの問題
3. APNsの問題
4. 実装の問題

とりあえず最初に証明書周りを怪しみ書き出し直したりしてFCMコンソールからテスト通知を打つもうまくいきませんでしたので、一つづつ問題を探っていくことにしました。


## FCMの問題について

### curlで通知を叩いて結果を調べてみる

```terminal
curl -X POST --header "Authorization: key=<FCMのコンソールにあるサーバキー>" \
--Header "Content-Type: application/json" \
https://fcm.googleapis.com/fcm/send \
-d @- << EOF
{
    "to": "<通知をおくりたいFCM Token>",
    "notification": {
        "body": "お知らせテスト"
    },
    "priority":10,
    "mutable_content":true
}
EOF
```

![fcm_console_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/47955/7ae08b02-3f16-efff-97ab-5711426d43bb.png)

ここでサーバキーが間違っていると以下のようなエラーがでます。証明書が間違っているとerrorは `InvalidRegistration` とかになるはず。成功すれば `"success":1, "failure":0` となります。

```json
{"multicast_id":********,"success":0,"failure":1,"canonical_ids":0,"results":[{"error":"MismatchSenderId"}]}
```


#### 登録しているp12やp8ファイルの問題

これは最初前から使っているPush通知証明書だとうまくいっていたけどプロビジョニング更新したのでこちらも新たに書き出してFCMのコンソールで登録しようとすると…

```
BundleIdが一致していません
```

とエラーが出てアップロードできない…。
ここはp12で色々やっても原因不明だったのでp8に切り替えたところアップできました。
おそらくこれでPush通知証明書は正常に通った模様。
再度curlを叩いところ　`"success":1, "failure":0` になったのでFCM側は成功しましたが通知は相変わらず来ません…。

## Xcodeの設定の問題

Xcodeか？と思いこの辺チェックしました。

- `PROJECT > TARGETS > 該当ターゲット` のCapabitiesの問題
    - Background ModesのRemote Notificationがオンになっているか
    - Push NotificationのStepsにチェックマークついているか
- XCodeのEntitlementの問題
    - APS Environmentがあるか
    - アーカイブした際にdebugとかに向いてないか
- XCodeのplistの問題
    - XCodeの　`Build Settings` plistがDebugに向いてないか
- GoogleService-info.plistの問題
    - 間違ってDebugのをあててないか？

自分の場合は開発版のTARGETをコピーして本番をつくったのでplistが開発版を向いてました。
ちなみにEntitlemnetsの値が「development」になっていてもアーカイブ時にproductionに勝手に書き換えてくれるのでここはそのままで良いぽいです。
GoogleService-info.plistは問題なく本番のものが当たってる模様。

とりあえずここまで確認して前項と同じくFCM APIをcurlで叩きますが通知は届きません。


## APNsの問題

次に確認したのはAPNs。以下をAppDelegate.mに仕込んでDevice Tokenを取得し、[NWPusher](https://github.com/noodlewerk/NWPusher)をMacにインストールしてFCMを介さずに通知が打てるか検証しました。

###### AppDelegate.m
```objective_c

- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
  NSLog(@"deviceToken: %@", deviceToken);
}
```

<img width="731" alt="スクリーンショット 2019-04-21 12.39.03.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/47955/06136b40-d853-6868-d0e1-8d0077794480.png">

※ AdHocで確認するには `Should use sandbox environment` にチェック、本番で確認するにはチェック外しましょう

これで通知が届けばAPNsではなくXcodeの設定か実装に問題があります。
ちなみに通知は無事に届きました！ということでプロビジョニングやPush通知証明証もFCMの疎通もAPNsも問題ないことがここまででわかりました。

で？結局何なの？


## 実装の問題

- react-native側の実装の問題
    - 主にForegroundで通知がキャッチできない場合
    - Backgroundで通知センター叩いたときにイベントが走らないのは今回は扱わないです
- `AppDelegate.m(or .swift)` の問題

確認する項目としては上の2項目ですが、問題は**「通知センターに届かないところまで」**なので `react-native側の実装の問題` はとりあえずスルー。 
で色々調べて行くうちについに判明しました。 `AppDelegate.m` の記述に原因が…。
react-native-firebaseのIssueに解決方法がありました。以下のIssueのコメントを参考にコードを追加すると通知が届きました。

```
It appears as if RNFirebase documentation is missing a few required steps. After adding the below code after [RNFirebaseNotifications configure]; in AppDelegate.m, remote notifications worked for me.

// Setup Notifications
  if ([UNUserNotificationCenter class] != nil) {
    // iOS 10 or later
    // For iOS 10 display notification (sent via APNS)
    [UNUserNotificationCenter currentNotificationCenter].delegate = self;
    UNAuthorizationOptions authOptions = UNAuthorizationOptionAlert |
    UNAuthorizationOptionSound | UNAuthorizationOptionBadge;
    [FIRMessaging messaging].delegate = self;
    [[UNUserNotificationCenter currentNotificationCenter]
     requestAuthorizationWithOptions:authOptions
     completionHandler:^(BOOL granted, NSError * _Nullable error) {
       if (error) { NSLog(@"%@", error); }
     }];
  } else {
    // iOS 10 notifications aren't available; fall back to iOS 8-9 notifications.
    UIUserNotificationType allNotificationTypes =
    (UIUserNotificationTypeSound | UIUserNotificationTypeAlert | UIUserNotificationTypeBadge);
    UIUserNotificationSettings *settings =
    [UIUserNotificationSettings settingsForTypes:allNotificationTypes categories:nil];
    [application registerUserNotificationSettings:settings];
  }
[application registerForRemoteNotifications];
Make sure to add the required import or else your code won't compile
#import <UserNotifications/UserNotifications.h>
```

引用元: [react-native-firebase: Issue#1203 IOS unable to receive notification](https://github.com/invertase/react-native-firebase/issues/1203#issuecomment-396730990)

このコメントにもありますが、このコードは自分の知る限りreact-native-firebaseの公式ドキュメントにもFCMの公式ドキュメントにもそれっぽいの探し出せませんでした。

これでcurlでFCM API叩いてもFCMコンソールからテスト通知配信しても通知は無事に届くようになりました。もちろんTestFlight環境でも同様に。