LaravelでFirebase Cloud Messagingを使ってブラウザにプッシュ通知する

タイトルの通りです。
LaravelでFCMとやりとりして、今回はブラウザにプッシュ通知します。

# PHPでFirebase

Firebaseにはバックエンドで使うための公式のAdmin SDKがありますが、利用可能な言語にPHPはありません。

https://firebase.google.com/docs/admin/setup?hl=ja

なので、Firebaseとのやりとりを自前で実装するか、外部のライブラリに頼らないといけませんが、
調べたところ、「Firebase Admin SDK for PHP」が活発に開発も進められていてよさそうだったのでそれを使うことにしました。

https://github.com/kreait/firebase-php
https://firebase-php.readthedocs.io/en/latest/index.html

# 準備

https://firebase.google.com/docs/admin/setup?hl=ja#add_firebase_to_your_app

上のリンク先に従って、Firebaseのプロジェクトを作成、認証情報を含むJSONファイルをダウンロードします。このJSONファイルは後でサーバのSDKで使います。

Laravelのインストールは割愛します。Laravelのバージョンは5.8です。
Laravelをインストールしたら、「Firebase Admin SDK for PHP」をComposerでインストールします。

```
$ composer require kreait/firebase-php ^4.18
```

今回はブラウザで通知を受信するので、フロントエンドでも設定が必要です。

https://firebase.google.com/docs/cloud-messaging/js/client?hl=ja#configure_web_credentials_with_fcm

に従って、鍵ペアを生成します。

# フロントエンド

まずは、public以下にmanifest.jsonを作ります。gcm_sender_idは固定値です。

https://firebase.google.com/docs/cloud-messaging/js/client?hl=ja#configure_the_browser_to_receive_messages

```public/manifest.json
{
  "name": "Laravel FCM Sample",
  "gcm_sender_id": "103953800507"
}
```

今回はCDNから必要なファイルを読み込みます。
Firebaseコンソールの設定からスニペットをhtmlにコピペします。
通知の受信許可を求める処理や、登録トークンの取得処理も追加します。

```public/fcm.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <link rel="manifest" href="/manifest.json">
  <title>Laravel FCM sample</title>
</head>
<body>
<!-- The core Firebase JS SDK is always required and must be listed first -->
<script src="https://www.gstatic.com/firebasejs/6.0.4/firebase-app.js"></script>
<script src="https://www.gstatic.com/firebasejs/6.0.4/firebase-messaging.js"></script>

<script>
  // Your web app's Firebase configuration
  var firebaseConfig = {
    // 諸々の設定情報
  };
  // Initialize Firebase
  firebase.initializeApp(firebaseConfig);

  const messaging = firebase.messaging();

  // 通知の受信許可を求める
  // https://firebase.google.com/docs/cloud-messaging/js/client?hl=ja#request_permission_to_receive_notifications
  messaging.requestPermission().then(function() {
    console.log('Notification permission granted.');
  }).catch(function(err) {
    console.log('Unable to get permission to notify.', err);
  });

  // 現在の登録トークンを取得する
  // https://firebase.google.com/docs/cloud-messaging/js/client?hl=ja#%E7%8F%BE%E5%9C%A8%E3%81%AE%E7%99%BB%E9%8C%B2%E3%83%88%E3%83%BC%E3%82%AF%E3%83%B3%E3%81%AE%E5%8F%96%E5%BE%97
  messaging.getToken().then(function(currentToken) {
    if (currentToken) {
      console.log(currentToken);
    } else {
      console.log('No Instance ID token available. Request permission to generate one.');
    }
  }).catch(function(err) {
    console.log('An error occurred while retrieving token. ', err);
  });

  // フォアグラウンドで通知を受信する
  // https://firebase.google.com/docs/cloud-messaging/js/receive?hl=ja#handle_messages_when_your_web_app_is_in_the_foreground
  messaging.onMessage(function(payload) {
    console.log('Message received. ', payload);
  });
</script>
</body>
</html>
```

バックグラウンドでも通知を受信できるようにするために、Service Workerの設定も必要です。
`firebase-messaging-sw.js`というファイルを作ります。

```public/firebase-messaging-sw.js
importScripts("https://www.gstatic.com/firebasejs/6.0.4/firebase-app.js");
importScripts("https://www.gstatic.com/firebasejs/6.0.4/firebase-messaging.js");

// Your web app's Firebase configuration
var firebaseConfig = {
  // 諸々の設定情報
};
// Initialize Firebase
firebase.initializeApp(firebaseConfig);

const messaging = firebase.messaging();

messaging.setBackgroundMessageHandler(function(payload) {
  console.log('[firebase-messaging-sw.js] Received background message ', payload);
});
```

```
$ php artisan serve
```

を実行して、Chromeで`http://127.0.0.1:8000/fcm.html`にアクセスすると、通知の受信を許可するかどうかのダイアログが表示されるので許可します。

<img width="1135" alt="スクリーンショット 2019-05-28 17.08.22.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/123511/e4240917-2967-3637-a40a-a131c2ded6c3.png">

画面をリロードすると、デベロッパーツールのコンソールに登録トークンが出力されるので、そのトークン文字列を控えておきます。
ちなみに`php artisan serve`ではなくValetでやると「Uncaught FirebaseError: Messaging: This browser doesn't support the API's required to use the firebase SDK.」というエラーが出ます。何が違うのか。。。
ここまででフロントエンドの実装は終わりです。

# Laravel

次はプッシュ通知をリクエストする側のLaravelの実装になります。
何をトリガーに通知するかはいろいろあると思いますが、今回はArtisanコマンドを実行したら通知するように作ります。
サンプルコードも用意されています。

https://github.com/jeromegamez/firebase-php-examples/blob/master/src/Command/FcmSendMessageCommand.php

まずコマンドを作成します。

```
$ php artisan make:command SendTgifNotification
Console command created successfully.
```

SDKを使う準備ですが、認証情報のJSONを読み込むために、環境変数を使って自動で読み込む方法と、コードに直接パスを記述して読み込む方法があります。今回はパスを直接記述して読み込みます。

https://firebase-php.readthedocs.io/en/latest/setup.html

通知にはトピックに対して通知したり、いくつか種類があります。今回は登録トークンに対して通知します。Chromeが通知を受信、通知をクリックして送信したURLに遷移できたら成功です。

```app/Console/Commands/SendTgifNotification.php
<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Kreait\Firebase\Factory;
use Kreait\Firebase\ServiceAccount;
use Kreait\Firebase\Messaging\CloudMessage;
use Kreait\Firebase\Messaging\Notification;
use Kreait\Firebase\Messaging\WebPushConfig;

class SendTgifNotification extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'command:send-tgif-notification';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'TGIF!!';

    /**
     * Create a new command instance.
     *
     * @return void
     */
    public function __construct()
    {
        parent::__construct();
    }

    /**
     * Execute the console command.
     *
     * @return mixed
     */
    public function handle()
    {
        // ブラウザから取得した登録トークン
        $registration_token = '登録トークン';

        // 事前にダウンロードした認証情報のJSONを読み込む
        $serviceAccount = ServiceAccount::fromJsonFile(base_path() . '/google-service-account.json');

        $firebase = (new Factory)
            ->withServiceAccount($serviceAccount)
            ->create();

        $messaging = $firebase->getMessaging();

        // FCMに送信するデータの作成
        $notification = Notification::fromArray([
            'title' => 'TGIF!!!!!!!!!!',
            'body'  => 'Thank God It\'s Friday'
        ]);
        $config = WebPushConfig::fromArray([
            'fcm_options' => [
                'link' => 'https://firebase-php.readthedocs.io/en/latest/cloud-messaging.html#webpush',
            ],
        ]);

        $message = CloudMessage::withTarget('token', $registration_token)
            ->withNotification($notification)
            ->withWebPushConfig($config);

        $messaging->send($message);
    }
}
```

```
$ php artisan command:send-tgif-notification
```

<img width="940" alt="スクリーンショット 2019-05-28 18.02.43.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/123511/3fe419b5-2aa7-bc96-1f24-f4751260b15e.png">

コマンドを実行すると通知が出ました。クリックするとfcm_optionsで設定したURLに遷移することが確認できました。成功です:beer:

# さらにLaravelっぽく

今後の課題として、Laravelの通知を使ったかたちで実装したいですね。

https://laravel.com/docs/5.8/notifications

# 参考URL

https://firebase.google.com
https://github.com/kreait/firebase-php
https://firebase-php.readthedocs.io/en/latest/