tag:crieit.net,2005:https://crieit.net/tags/FCM/feed 「FCM」の記事 - Crieit Crieitでタグ「FCM」に投稿された最近の記事 2018-10-31T17:45:02+09:00 https://crieit.net/tags/FCM/feed tag:crieit.net,2005:PublicArticle/14473 2018-06-25T11:16:11+09:00 2018-10-31T17:45:02+09:00 https://crieit.net/posts/PHP-Laravel-FCM-Web PHP+Laravel+FCMでWebプッシュ通知を送る <p>LaravelでFCMを使ったWebプッシュ通知を実装しましたが、検索してもあまりサンプルなどなかったので書き残しておきます。</p> <h2 id="Firebaseに登録"><a href="#Firebase%E3%81%AB%E7%99%BB%E9%8C%B2">Firebaseに登録</a></h2> <p>FCMはFirebase Cloud Messagingなので、とりあえずFirebaseに登録する必要があります。基本的には下記のとおりに行うだけです。</p> <p><a target="_blank" rel="nofollow noopener" href="https://firebase.google.com/docs/cloud-messaging/js/client?hl=ja">JavaScript Firebase Cloud Messaging クライアント アプリを設定する</a></p> <p>このページの最初の方にJavaScriptに設定する方法が書かれたリンクもあります。</p> <p><a target="_blank" rel="nofollow noopener" href="https://firebase.google.com/docs/web/setup?hl=ja">Firebase を JavaScript プロジェクトに追加する</a></p> <p>このconfigの内容は、Firebaseの画面のProject Overviewというリンクをクリックした先の「ウェブアプリに Firebase を追加」というボタンをクリックすると出てきます。</p> <h3 id="Service Workerの設定"><a href="#Service+Worker%E3%81%AE%E8%A8%AD%E5%AE%9A">Service Workerの設定</a></h3> <p>前述のマニュアルを読んで地味に気づいにくいのがService Workerの設定です。これを行わないと動きません。具体的には下記のページに書かれています。</p> <p><a target="_blank" rel="nofollow noopener" href="https://firebase.google.com/docs/cloud-messaging/js/receive?hl=ja#setting_notification_options_in_the_service_worker">JavaScript クライアントでメッセージを受信する</a></p> <p>なんかプッシュ通知を送信したいのに「受信する」というタイトルなので初めて見た時は関係ないと思って飛ばしてしまって気づきませんでした。</p> <h2 id="Webプッシュ通知の許可"><a href="#Web%E3%83%97%E3%83%83%E3%82%B7%E3%83%A5%E9%80%9A%E7%9F%A5%E3%81%AE%E8%A8%B1%E5%8F%AF">Webプッシュ通知の許可</a></h2> <p>設定が終わったらあとはどんどん実装を進めていきます。最初は通知の許可です。これは結構みんな経験したことがあると思いますが、ブラウザで急に出てくる「通知を許可しますか?」みたいなやつです。</p> <p>許可されたらトークンが取れるので、それをLaravel側に送信してユーザー情報に保存します。</p> <pre><code class="javascript">import * as firebase from "firebase"; import axios from "axios"; const messaging = firebase.messaging(); messaging .requestPermission() .then(() => messaging.getToken()) .then(token => { if (token) { const params = { token, user_agent: window.navigator.userAgent }; return axios.post("/fcm-tokens", params); } }) .then(response => { if (response && response.data.result) { console.log('Done'); } }) .catch(err => { console.log(err); }); </code></pre> <p>上記のような感じで、ユーザーエージェントなどと紐付けてtokenを保存し、ユーザーのhasManyとして紐付けておきます。というのも、ユーザー毎ではなく操作する端末毎での許可になるので、1ユーザーにつき複数のtokenが紐づく形になるためです。</p> <h3 id="Laravel側"><a href="#Laravel%E5%81%B4">Laravel側</a></h3> <p>Laravel側ですが、今回僕は下記のライブラリを使用しました。</p> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/brozot/Laravel-FCM">brozot/Laravel-FCM: Laravel-FCM</a></p> <p>READMEを見てもらえば分かりますが、むちゃくちゃ簡単に必要な処理が可能です。</p> <h4 id="Webプッシュ通知する端末の登録"><a href="#Web%E3%83%97%E3%83%83%E3%82%B7%E3%83%A5%E9%80%9A%E7%9F%A5%E3%81%99%E3%82%8B%E7%AB%AF%E6%9C%AB%E3%81%AE%E7%99%BB%E9%8C%B2">Webプッシュ通知する端末の登録</a></h4> <p>上記を使って、Laravel側では下記のようにして保存しました。</p> <pre><code class="php"> public function store(Request $request) { $user = Auth::user(); $fcmToken = FcmToken::firstOrNew([ 'user_id' => $user->id, 'user_agent' => $request->input('user_agent'), ]); // 念の為 if (!$fcmToken->id) { $fcmToken->token = $request->input('token'); $fcmToken->save(); } // 適当にユーザーのグループ名を作る $groupName = $user->getFcmGroupName(); if ($user->notification_key) { FCMGroup::addToGroup($groupName, $user->notification_key, [$fcmToken->token]); } else { $user->notification_key = FCMGroup::createGroup($groupName, [$fcmToken->token]); $user->save(); } return response()->json(['result' => true]); } </code></pre> <p>非常に簡単ではありますが、FCMを初めて実装する人は何がなんやら分からないと思いますので説明していきます。</p> <p>本家のマニュアルにあるように、FCMでは複数の端末に一度にWebプッシュ通知を送る場合には、下記の方法があります。</p> <blockquote> <p>・トピック メッセージングでは、特定のトピックにオプトインした複数の端末にメッセージを送信できます。<br /> ・端末グループ メッセージングでは、定義したグループに属する複数の端末にメッセージを送信できます。</p> </blockquote> <p>つまり例えばある投稿をウォッチしている全ての端末にWebプッシュ通知を送りたい場合はトピックメッセージ、あるユーザーの端末全てに送りたい場合は端末グループメッセージを送る、ということです。</p> <p>今回のサンプルはユーザーへの通知を想定しているため、端末グループメッセージの説明を行います。</p> <pre><code class="php"> // 適当にユーザーのグループ名を作る $groupName = $user->getFcmGroupName(); </code></pre> <p>グループ名の文字列が必要なため、メソッド化し一意になるような文字列を返しています。</p> <pre><code class="php"> } else { $user->notification_key = FCMGroup::createGroup($groupName, [$fcmToken->token]); $user->save(); </code></pre> <p>グループがまだ存在しない場合はまずグループを作成します。作成時には追加するトークンも一緒に指定することができます。取得した通知キーはWebプッシュ通知の際に使用するのでLaravel側でDBに保存しておきます。</p> <pre><code class="php"> if ($user->notification_key) { FCMGroup::addToGroup($groupName, $user->notification_key, [$fcmToken->token]); </code></pre> <p>グループが既に存在する場合(PCで許可済みでスマホでも許可した場合等)には既に登録したグループに端末を追加するだけです。</p> <h2 id="Webプッシュ通知する端末の削除"><a href="#Web%E3%83%97%E3%83%83%E3%82%B7%E3%83%A5%E9%80%9A%E7%9F%A5%E3%81%99%E3%82%8B%E7%AB%AF%E6%9C%AB%E3%81%AE%E5%89%8A%E9%99%A4">Webプッシュ通知する端末の削除</a></h2> <p>FCMでWebプッシュ通知する端末を削除するのはLaravel側では下記のような処理になります。</p> <pre><code class="php"> $groupName = $user->getFcmGroupName(); FCMGroup::removeFromGroup($groupName, $user->notification_key, [$fcmToken->token]); $fcmToken->delete(); if (!$user->fcmTokens()->count()) { $user->notification_key = null; $user->save(); } </code></pre> <p>注意点として、マニュアルにも書かれていますがグループ内の端末が全てなくなった場合は、自動的にグループも削除されます。そのためユーザーに紐づくトークンを全て削除した場合はグループの通知keyも削除しておきましょう。</p> <h2 id="Webプッシュ通知を送信する"><a href="#Web%E3%83%97%E3%83%83%E3%82%B7%E3%83%A5%E9%80%9A%E7%9F%A5%E3%82%92%E9%80%81%E4%BF%A1%E3%81%99%E3%82%8B">Webプッシュ通知を送信する</a></h2> <p>送信は非常に簡単です。Laravel-FCMにかかれている通りそのままです。アイコン指定は追記していますが、httpsから始まるフルURLを指定します。</p> <pre><code class="php"> $notificationBuilder = new PayloadNotificationBuilder('新着コメント'); $notificationBuilder->setBody("「{$post->name}」にコメントが投稿されました。") ->setIcon(config('app.url') . '/img/icon.png') ->setSound('default'); $notification = $notificationBuilder->build(); $groupResponse = FCM::sendToGroup($user->notification_key, null, $notification, null); </code></pre> <p>一点注意点として、Laravel-FCMのREADMEを見ると宛先を配列で指定していますが、今回はうまく動きませんでした。</p> <p>これはたまたまなのかFCMの仕様なのか忘れてしまった&今回細かくは確認していないので、もし仕様であれば、一度にWebプッシュ通知を送信する宛先が多い場合は別途バックグラウンドで送信する、等が必要だと思われます。</p> <p>もしくはトピックグループ送信でも良いと思いますが、管理が非常に大変になりそうだったので今回は使いませんでした。</p> <h2 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h2> <p>とりあえずざっとにはなりますがLaravelでFCMのWebプッシュ通知を送信する方法をまとめました。とりあえずここに書いた内容を理解できれば、あとは本家のマニュアルの方を見てもスムーズに理解できるのではないかと思います。細かい部分は本家のマニュアルも実際に確認してみてください。</p> だら@Crieit開発者 tag:crieit.net,2005:PublicArticle/14472 2018-06-20T08:12:23+09:00 2019-02-28T16:25:32+09:00 https://crieit.net/posts/WEB-5b298dd7398c4 WEBサービスの通知機能実装が面倒すぎるので困った <p>先日Crieitにコメント機能を付けました。そうなると必要になってくるのが、通知機能です。</p> <p>記事を投稿した人はコメントがついたら通知が欲しいでしょうし、コメントした人も自分のコメントに返信が来たり、同じ記事にコメントがついた場合にはすぐに見たいと思いますので通知が欲しいはずだからです。</p> <h2 id="通知機能の選択肢"><a href="#%E9%80%9A%E7%9F%A5%E6%A9%9F%E8%83%BD%E3%81%AE%E9%81%B8%E6%8A%9E%E8%82%A2">通知機能の選択肢</a></h2> <p>WEBサービスに通知機能を実装する場合、現在だと大まかにふた通りのやり方があると思います。</p> <h3 id="メール通知"><a href="#%E3%83%A1%E3%83%BC%E3%83%AB%E9%80%9A%E7%9F%A5">メール通知</a></h3> <p>昔からある一般的な方法です。一番無難な方法でもあると思います。通知メールを残しておけば何の通知だったかあとでも確認できますし、メール通知機能さえあれば必要最低限はまかなえると思います。</p> <h3 id="PUSH通知"><a href="#PUSH%E9%80%9A%E7%9F%A5">PUSH通知</a></h3> <p>FCMを利用したPUSH通知です。メールとは違い、端末に通知が来て直接アプリケーションの画面が開けるため、スピード感がありWEBサービスには向いています。</p> <h2 id="どちらがいいのか?"><a href="#%E3%81%A9%E3%81%A1%E3%82%89%E3%81%8C%E3%81%84%E3%81%84%E3%81%AE%E3%81%8B%EF%BC%9F">どちらがいいのか?</a></h2> <p>最終的には両方の通知機能を実装することでしょう。ただ、個人でWEBサービスを作っている場合や、スタートアップなどがスモールスタートする場合などは何でもかんでも最初から実装しておけばいいというわけではありません。</p> <p>サービス的に両方絶対必要、ということであれば仕方ないですが、時間に限りがありますし通知機能の実装は非常に面倒ですので、絶対必要でなければどちらかの通知機能に絞った方が良い場合もあると思います。</p> <p>どちらにどういうメリットがあり、どちらがどれだけ面倒か考えていきます。</p> <h2 id="メール通知"><a href="#%E3%83%A1%E3%83%BC%E3%83%AB%E9%80%9A%E7%9F%A5">メール通知</a></h2> <p>メール通知のメリットは前述の通り、とりあえず作っておけば最低限大体のことは満たせるということです。ただ、実装方法は色々ありますが、結構面倒な点もあったりします。</p> <h3 id="サーバー上のメールサーバーを用意する場合"><a href="#%E3%82%B5%E3%83%BC%E3%83%90%E3%83%BC%E4%B8%8A%E3%81%AE%E3%83%A1%E3%83%BC%E3%83%AB%E3%82%B5%E3%83%BC%E3%83%90%E3%83%BC%E3%82%92%E7%94%A8%E6%84%8F%E3%81%99%E3%82%8B%E5%A0%B4%E5%90%88">サーバー上のメールサーバーを用意する場合</a></h3> <p>Postfix等を使えば特に外部サービスを利用しなくてもメールを送信することができます。ただ、自分で設定をしなければならないので面倒です。一回くらいなら自分で頑張って設定しようかと言う気にはなるのですが、もしもう1台増やすか、ということになった時などにまた同じ設定をしなければならないのかと思うとうんざりします。</p> <p>僕は仕事上インフラ周りも触ることはありますが、メールサーバーは正直必要最低限は最初から送信できたりすることもあり、WEBサーバーやDBの設定に比べて扱う頻度が低く、大体次触る頃にはほとんど設定方法を覚えていません。そのためネットで調べながら設定することになり、時々つまづくこともあるのでかなり面倒です。</p> <p>特に普段サーバー環境構築をしない人からするともっと大変だと思います。例えば下記の記事に設定が書いてありますが、長い設定画面が出てきたあたりで読む気すら起こらなくなると思います。<br /> <a target="_blank" rel="nofollow noopener" href="https://qiita.com/mizuki_takahashi/items/1b33e1f679359827c17d">Ubuntuでメールサーバー構築</a></p> <h3 id="メール送信サービスを利用する場合"><a href="#%E3%83%A1%E3%83%BC%E3%83%AB%E9%80%81%E4%BF%A1%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9%E3%82%92%E5%88%A9%E7%94%A8%E3%81%99%E3%82%8B%E5%A0%B4%E5%90%88">メール送信サービスを利用する場合</a></h3> <p>上記の理由から、SendGridやMailgunメールサービスをつかってメール通知を実装するともっと楽になります。色々と機能もついており非常に便利です。ただ、これはこれでいくつか問題があったりします。</p> <h4 id="送信数制限"><a href="#%E9%80%81%E4%BF%A1%E6%95%B0%E5%88%B6%E9%99%90">送信数制限</a></h4> <p>送信数制限があるため、多く送ると費用がかかるようになります。例えばSendGrid等は現在月12000通です。</p> <p>さすがにそれなら超えないんじゃないか? と思いますが、1サービスではなく1アカウント毎の制限になりますので、いくつもサービスを作っていて、ある程度メールを送信するしている場合はわりと超えてしまうのではないかと思います。</p> <p>今は良いけど今後サービス新しく作る場合にどうしようか? と悩むこともあると思います。</p> <h4 id="リレー方式の場合はダメな場合がある"><a href="#%E3%83%AA%E3%83%AC%E3%83%BC%E6%96%B9%E5%BC%8F%E3%81%AE%E5%A0%B4%E5%90%88%E3%81%AF%E3%83%80%E3%83%A1%E3%81%AA%E5%A0%B4%E5%90%88%E3%81%8C%E3%81%82%E3%82%8B">リレー方式の場合はダメな場合がある</a></h4> <p>僕が愛用しているGCEもそうですが、ポートに制限がかかっている場合があり、リレー方式の送信方法だとブロックされてしまい送信できません。マニュアルはありますがこれもちょっとうんざりする系です。<br /> <a target="_blank" rel="nofollow noopener" href="https://cloud.google.com/compute/docs/tutorials/sending-mail/using-sendgrid">SendGrid でのメールの送信</a></p> <h4 id="API方式がいい"><a href="#API%E6%96%B9%E5%BC%8F%E3%81%8C%E3%81%84%E3%81%84">API方式がいい</a></h4> <p>API方式は良いです。一番簡単だと思います。ただ、言語によってはなぜかリレー方式しか使えないライブラリがあったりするので、このあたり気をつけないとローカル環境では動いたのに本番では送信できないとなりますので気をつけましょう。</p> <p>あとはどの方式にも言えますが、DNSでSPFの設定をちゃんとやっておく必要があります。これをやっておかないとスパムメール扱いされてしまう事が多いです。メールサービスであればマニュアルに書いてあることも多いと思います。</p> <h3 id="WEB PUSH"><a href="#WEB+PUSH">WEB PUSH</a></h3> <p>FCMを利用したWEB PUSHによる通知です。スマホなどで通知を受け取り、すぐに目的のURLにアクセスできるため、サービスにユーザーを頻繁に呼びこむのに優れています。</p> <p>単にユーザーに通知を送るだけでなく、トピックを作っておいて、そこにユーザー端末を登録してトピックに通知を送ると登録されているユーザー全員に通知を送ることができる、などもあり、非常に便利です。(もちろん、そのあたりの登録、解除の仕組みなどもちゃんと作らないといけません。自前のDBに保存しておく必要もあります)</p> <h4 id="通知の履歴が残らない問題"><a href="#%E9%80%9A%E7%9F%A5%E3%81%AE%E5%B1%A5%E6%AD%B4%E3%81%8C%E6%AE%8B%E3%82%89%E3%81%AA%E3%81%84%E5%95%8F%E9%A1%8C">通知の履歴が残らない問題</a></h4> <p>通知の履歴が残らないため、通知を消してしまったり、通知からサービスにアクセスしても一旦どうでもよくて離脱し、再度気になった時にアクセスできない、といった問題が出てきます。(実際にはスマホには履歴が残っているのですが、見るのも面倒で知らない人も多いと思います)</p> <p>そのためサービス側に独自の通知履歴を作らなければなりません。</p> <h2 id="独自の通知の実装"><a href="#%E7%8B%AC%E8%87%AA%E3%81%AE%E9%80%9A%E7%9F%A5%E3%81%AE%E5%AE%9F%E8%A3%85">独自の通知の実装</a></h2> <p>独自の通知の実装ですが、これがまた面倒です。というのも、通知というのは色々なところで発生します。例えばフォローしているユーザーやカテゴリに何かが投稿されたり、自分の投稿にコメントがついたり。</p> <p>つまりDBの構造として単純なbelongsToで作ることができません。通知自体はあまり他のところで使われるものでもないのでpolymorphicであれば割とシンプルに困ることなく作れるかもしれません。何にしろ条件分岐にしろ、関連モデルに共通のメソッドを用意するにしろ、やることは増えるので面倒なのは面倒です。</p> <p>元はといえば単にユーザーに最低限の通知をしたかっただけのはずなのに…。</p> <h3 id="フロントとの連携の問題"><a href="#%E3%83%95%E3%83%AD%E3%83%B3%E3%83%88%E3%81%A8%E3%81%AE%E9%80%A3%E6%90%BA%E3%81%AE%E5%95%8F%E9%A1%8C">フロントとの連携の問題</a></h3> <p>単に通知一覧を実装するだけであれば問題ないのですが、WEBサービスのヘッダ部分に新着通知アラートをだしたりすることになるとまた面倒なことになります。どこまで閲覧済みかという情報を保存しておかなければならないからです。</p> <p>フロントエンドと連携する場合、ユーザーの確認とDBの更新のタイミングが必ずしも同じではなくなるため、適当に作ったりするとまだ確認していないのにある一瞬のタイミングで全ての通知を確認済みにしてしまって新着通知が消えてしまったり、逆にフロント側では新着確認済みになったのにリロードするとまた新着が復活してしまう、ということが発生したりします。</p> <p>Facabookもこの不具合が多く非常に困ったため、Fluxを提唱したという話をどこかで見ました。ほんとに通知機能というのはやっかいなシロモノです。</p> <p>個人のWEBサービスなら良いのですが、もし仕事でするとなると仕様策定側が便利にしたいと思ってあれこれ勝手に決めてしまうこともあるので大変そうです。</p> <h2 id="周辺機能の実装"><a href="#%E5%91%A8%E8%BE%BA%E6%A9%9F%E8%83%BD%E3%81%AE%E5%AE%9F%E8%A3%85">周辺機能の実装</a></h2> <p>通知を作るとなると、周辺にも色々と機能を実装する必要が出てきます。これも非常に面倒です。元はといえば単にユーザーに最低限の通知をしたかっただけのはずなのに…。</p> <ul> <li>トピックやユーザーなどにウォッチ機能、もしくは通知解除機能を実装する必要がある</li> <li>ユーザーの個人設定に一般的な通知許可設定を実装する必要がある、もしくはここにも分類毎の許可設定が必要</li> </ul> <h2 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h2> <p>こんな感じで、単に通知機能を作りたいだけのはずなのですが、周辺部分も含めると結構工数がかかってしまい、なかなか厄介です。考えれば考えるほど、こだわればこだわるほど色々できてしまう箇所ですので、いかに絞って仕様を策定するかは非常に重要になると思います。<br /> (メールサービス関連はざっと調べただけですので誤っている情報もあるかもしれません。検討される際は実際に確認してください)</p> <p>記事を書きつつ気持ちを落ち着けて考えてみましたが、CrieitとしてはとりあえずSendGrid等のAPI送信で試そうかなと思います。一応公式のライブラリの方はAPI方式っぽいので。(SendGrid公式のLaravelやRailsのサンプルはリレーっぽいのでなんか不親切な感じがします)</p> <p>…と決めたら決めたでまた悩む…。</p> <p>追記)<br /> FCMにする。</p> <p>追加)<br /> 結局メール(API)も入れました。</p> だら@Crieit開発者