tag:crieit.net,2005:https://crieit.net/tags/Push%E9%80%9A%E7%9F%A5/feed
「Push通知」の記事 - Crieit
Crieitでタグ「Push通知」に投稿された最近の記事
2020-05-24T17:18:35+09:00
https://crieit.net/tags/Push%E9%80%9A%E7%9F%A5/feed
tag:crieit.net,2005:PublicArticle/15909
2020-05-24T17:18:35+09:00
2020-05-24T17:18:35+09:00
https://crieit.net/posts/Nuxt-Firebase-Cloud-Messaging-FCM-Web
Nuxt+Firebase Cloud Messaging(FCM)でWebプッシュ通知を送る
<p>WebでもFCMが使えるようになったので、試してみたときの備忘録。<br />
これでSafari以外には、通知が送れるようになる(<em>´ω`</em>)</p>
<h3 id="使い方"><a href="#%E4%BD%BF%E3%81%84%E6%96%B9">使い方</a></h3>
<p>構成は、@nuxtjs/pwaでPWA化している感じ。</p>
<h4 id="コンソール側"><a href="#%E3%82%B3%E3%83%B3%E3%82%BD%E3%83%BC%E3%83%AB%E5%81%B4">コンソール側</a></h4>
<p>Settingsでウェブプッシュ証明書を作成して、鍵ペアを取得する。</p>
<p><img width="973" alt="スクリーンショット_2020-05-24_17_06_33.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478782/d17f3a0e-e6df-73a7-d57b-a6ff36e7c79f.png"></p>
<p>これを、PUBLIC_VAPID_KEYという環境変数に設定しておく。</p>
<h4 id="クライアント側"><a href="#%E3%82%AF%E3%83%A9%E3%82%A4%E3%82%A2%E3%83%B3%E3%83%88%E5%81%B4">クライアント側</a></h4>
<h5 id="firebaseの初期化"><a href="#firebase%E3%81%AE%E5%88%9D%E6%9C%9F%E5%8C%96">firebaseの初期化</a></h5>
<p><code>~/plugins/firebase.ts</code>というファイルを用意し、firebaseを初期化</p>
<pre><code class="typescript">// ~/plugins/firebase.ts
import * as firebase from "firebase/app";
import "firebase/auth";
import "firebase/firestore";
import "firebase/messaging";
if (!firebase.apps.length) {
// まずは、firebaseの初期化
firebase.initializeApp({
apiKey: process.env.API_KEY,
authDomain: process.env.AUTH_DOMAIN,
databaseURL: process.env.DATABASE_URL,
projectId: process.env.PROJECT_ID,
storageBucket: process.env.STORAGE_BUCKET,
messagingSenderId: process.env.MESSAGING_SENDER_ID,
appId: process.env.APP_ID,
measurementId: process.env.MEASUREMENT_ID
});
// Push通知をサポートしているかをチェック
// サポートしていないと、firebase.messaging()を呼んだときに例外が発生
const isSupported = firebase.messaging.isSupported();
// コンソールで発行した、ウェブプッシュ証明書の鍵ペアを取得
const publicVapidKey = process.env.PUBLIC_VAPID_KEY;
if (!!publicVapidKey && process.client && !!isSupported) {
// FCMの初期化。鍵ペアを設定する
const messaging = firebase.messaging();
messaging.usePublicVapidKey(publicVapidKey);
// @nuxtjs/pwaが生成するsw.jsと、
// 後で作成するFCM受信処理用のsw-firebase-messaging.jsを統合するための設定
navigator.serviceWorker
.register("/sw.js")
.then(registration => messaging.useServiceWorker(registration))
.catch(err => console.error(err));
}
}
export default firebase;
</code></pre>
<h5 id="トークンの取得"><a href="#%E3%83%88%E3%83%BC%E3%82%AF%E3%83%B3%E3%81%AE%E5%8F%96%E5%BE%97">トークンの取得</a></h5>
<p>Push通知を送る際の宛先として、トークンを指定しないといけないので取得。<br />
トークンは端末ごとに取得する必要があるので、注意が必要。<br />
※PCとスマホだとそれぞれトークンが違う</p>
<pre><code class="typescript">import firebase from "~/plugins/firebase";
// トークンの取得
public async getToken(user: User) {
const isSupported = firebase.messaging.isSupported();
if (!isSupported) return;
const token = await firebase.messaging().getToken();
// firestoreにトークンを保存しておく処理(中身は略)
await saveToken(user, token);
}
</code></pre>
<p>Firestoreなどへユーザごとにトークンを保存しておく。</p>
<p><code>firebase.messaging().getToken();</code>を呼んだ際に、<br />
通知の設定が「確認」だと、許可を求めるダイアログが表示される。</p>
<p><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478782/e9976414-8418-09c1-8846-25211a949776.png" alt="スクリーンショット 2020-05-24 16.30.23.png" /></p>
<p>これが許可されていないと、トークンも取得できない。</p>
<p><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478782/c21078c8-38d0-04c7-133a-b5402611443d.png" alt="スクリーンショット 2020-05-24 16.30.35.png" /></p>
<h5 id="メッセージを受け取ったときの処理"><a href="#%E3%83%A1%E3%83%83%E3%82%BB%E3%83%BC%E3%82%B8%E3%82%92%E5%8F%97%E3%81%91%E5%8F%96%E3%81%A3%E3%81%9F%E3%81%A8%E3%81%8D%E3%81%AE%E5%87%A6%E7%90%86">メッセージを受け取ったときの処理</a></h5>
<p>メッセージを受信したときに受け取る関数は2つあり、</p>
<ul>
<li>フォアグラウンド(画面を見ている時) ... onMessage</li>
<li>バックグラウンド(画面を見ていない時) ... setBackgroundMessageHandler</li>
</ul>
<p>・【参考】<a target="_blank" rel="nofollow noopener" href="https://firebase.google.com/docs/cloud-messaging/js/receive#handle_messages_when_your_web_app_is_in_the_background">JavaScript クライアントでメッセージを受信する | Firebase</a></p>
<p>今回はバックグラウンドのときに通知を送りたいので、<br />
<code>setBackgroundMessageHandler</code>を設定していく。</p>
<p>こんな感じ。</p>
<p>ドキュメントを見ると、『<code>firebase-messaging-sw.js</code>というファイル名で作成』と書かれているけど、<br />
その名前にすると、自動で読み込まれてしまう。。</p>
<p>開発用と本番用で切り替えたいときなどもあるので、@nuxtjs/pwaが生成するsw.jsと統合できるように、<br />
<code>sw-firebase-messaging.js</code>という名前でファイルを作成しておく。</p>
<p>ファイル名を変更したので、上で書いている「firebaseの初期化」の部分で、<br />
<code>messaging.useServiceWorker(registration)</code>を呼んでいる形。</p>
<pre><code class="javascript">// ~/static/sw-firebase-messaging.js
importScripts("https://www.gstatic.com/firebasejs/7.14.2/firebase-app.js");
importScripts("https://www.gstatic.com/firebasejs/7.14.2/firebase-messaging.js");
// Firebaseの初期化
firebase.initializeApp({
apiKey: "...",
authDomain: "...",
databaseURL: "...",
projectId: "...",
storageBucket: "...",
messagingSenderId: "...",
appId: "...",
measurementId: "...",
});
// [START background_handler]
const isSupported = firebase.messaging.isSupported();
if (!!isSupported) {
const messaging = firebase.messaging();
// バックグラウンド時の処理
messaging.setBackgroundMessageHandler(function(payload) {
// 受け取ったFCMの内容を取得
const notificationTitle = payload.notification.title;
const notificationOptions = {
body: payload.notification.body,
icon: payload.notification.icon,
};
// 通知を作成する
return self.registration.showNotification(notificationTitle, notificationOptions);
});
}
// [END background_handler]
</code></pre>
<h5 id="nuxt.config.tsでPWA関連の設定をする"><a href="#nuxt.config.ts%E3%81%A7PWA%E9%96%A2%E9%80%A3%E3%81%AE%E8%A8%AD%E5%AE%9A%E3%82%92%E3%81%99%E3%82%8B">nuxt.config.tsでPWA関連の設定をする</a></h5>
<p>作成した<code>sw-firebase-messaging.js</code>を取り込む設定と、<br />
@nuxtjs/pwaが生成するmanifest.jsonに、gcm_sender_idを追加する設定を追加</p>
<pre><code class="typescript">import { Configuration } from "@nuxt/types";
const config: Configuration = {
// 略
modules: [
"@nuxtjs/pwa",
],
workbox: {
// sw-firebase-messaging.jsをimportするように追加
importScripts: [
"sw-firebase-messaging.js"
]
},
pwa: {
manifest: {
// manifest.jsonにgcm_sender_idを追加
gcm_sender_id: process.env.MESSAGING_SENDER_ID || ""
}
},
};
export default config;
</code></pre>
<p>これでクライアント側はOK!</p>
<h4 id="サーバ側: メッセージを送信する"><a href="#%E3%82%B5%E3%83%BC%E3%83%90%E5%81%B4%3A+%E3%83%A1%E3%83%83%E3%82%BB%E3%83%BC%E3%82%B8%E3%82%92%E9%80%81%E4%BF%A1%E3%81%99%E3%82%8B">サーバ側: メッセージを送信する</a></h4>
<p>メッセージの送信は、firebase-adminでできる。<br />
・<a target="_blank" rel="nofollow noopener" href="https://firebase.google.com/docs/cloud-messaging/send-message?hl=ja">アプリサーバーからの送信リクエストを作成する | Firebase</a></p>
<p>firestoreを利用しているので、Cloud Functionsのfirestoreトリガーを使い、<br />
ドキュメントが追加されたら通知するようにしている例。</p>
<pre><code class="typescript">import * as functions from "firebase-functions";
import admin from "../common/firebaseAdmin"; // 初期化済みのfirebase-admin
export default functions
.firestore.document("ドキュメントのパス")
.onCreate(async (snap, context) => {
// getTokenで保存しておいたトークンを取得(中身は略)
const token = getToken();
// 通知の送信
const title = "通知するタイトル";
const body = "通知する本文";
const icon = "通知で表示するアイコン画像のURL"
const link = "通知をタップしたときに開くURL"
await admin.messaging().send({
// 送信先の端末のトークン
token: token,
// 通知する内容
notification: {
title: title,
body: body
},
// Web Push向けの通知内容
webpush: {
notification: {
icon: icon
},
fcmOptions: {
link: link
}
}
});
});
</code></pre>
<p>送信はこれだけ!</p>
<h3 id="ほかの小ネタ"><a href="#%E3%81%BB%E3%81%8B%E3%81%AE%E5%B0%8F%E3%83%8D%E3%82%BF">ほかの小ネタ</a></h3>
<h4 id="トークンを削除する"><a href="#%E3%83%88%E3%83%BC%E3%82%AF%E3%83%B3%E3%82%92%E5%89%8A%E9%99%A4%E3%81%99%E3%82%8B">トークンを削除する</a></h4>
<p><code>firebase.messaging().deleteToken();</code>でトークンを無効化できる。</p>
<pre><code class="typescript">// トークンの削除
public async deleteToken(user: User) {
const isSupported = firebase.messaging.isSupported();
if (isSupported) {
const token = await firebase.messaging().getToken();
await firebase.messaging().deleteToken(token);
}
}
</code></pre>
<h4 id="フォアグラウンドで通知を受け取ったときになにかする"><a href="#%E3%83%95%E3%82%A9%E3%82%A2%E3%82%B0%E3%83%A9%E3%82%A6%E3%83%B3%E3%83%89%E3%81%A7%E9%80%9A%E7%9F%A5%E3%82%92%E5%8F%97%E3%81%91%E5%8F%96%E3%81%A3%E3%81%9F%E3%81%A8%E3%81%8D%E3%81%AB%E3%81%AA%E3%81%AB%E3%81%8B%E3%81%99%E3%82%8B">フォアグラウンドで通知を受け取ったときになにかする</a></h4>
<p><code>onMessage</code>を使うと、通知を受け取ったときに呼び出してくれる。</p>
<pre><code class="typescript">firebase.messaging().onMessage(async (payload) => {
// 受け取ったときの処理
});
</code></pre>
<h4 id="トークンが変更されたときになにかする"><a href="#%E3%83%88%E3%83%BC%E3%82%AF%E3%83%B3%E3%81%8C%E5%A4%89%E6%9B%B4%E3%81%95%E3%82%8C%E3%81%9F%E3%81%A8%E3%81%8D%E3%81%AB%E3%81%AA%E3%81%AB%E3%81%8B%E3%81%99%E3%82%8B">トークンが変更されたときになにかする</a></h4>
<p><code>onTokenRefresh</code>を使うと、トークンが更新されたときに呼び出してくれる。<br />
新しいトークンは再度<code>getToken()</code>を呼ばないといけない。</p>
<pre><code class="typescript">firebase.messaging().onTokenRefresh(async () => {
// トークンが更新されたときの処理
});
</code></pre>
<h4 id="通知の許可状態を確認する"><a href="#%E9%80%9A%E7%9F%A5%E3%81%AE%E8%A8%B1%E5%8F%AF%E7%8A%B6%E6%85%8B%E3%82%92%E7%A2%BA%E8%AA%8D%E3%81%99%E3%82%8B">通知の許可状態を確認する</a></h4>
<p>通知の状態は、<code>Notification.permission</code>で確認できるらしい。<br />
・<a target="_blank" rel="nofollow noopener" href="https://developer.mozilla.org/ja/docs/Web/API/notification/permission">Notification.permission - Web API | MDN</a></p>
<pre><code class="typescript">if (Notification.permission === "default") {
// 確認(デフォルト)
} else if (Notification.permission === "granted") {
// 許可
} else if (Notification.permission === "denied") {
// 拒否
}
</code></pre>
<p>以上!!</p>
<h3 id="【PR】これをつかって、こんなのつくりました!"><a href="#%E3%80%90PR%E3%80%91%E3%81%93%E3%82%8C%E3%82%92%E3%81%A4%E3%81%8B%E3%81%A3%E3%81%A6%E3%80%81%E3%81%93%E3%82%93%E3%81%AA%E3%81%AE%E3%81%A4%E3%81%8F%E3%82%8A%E3%81%BE%E3%81%97%E3%81%9F%EF%BC%81">【PR】これをつかって、こんなのつくりました!</a></h3>
<p><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478782/2f71c56a-0ab2-5910-f203-e1fca191ad69.png" alt="スクリーンショット 2020-05-24 12.44.28.png" /></p>
<p>こんな通知を受け取れます!<br />
<img width="359" alt="スクリーンショット_2020-05-24_13_34_17.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478782/1d4037bf-e662-a15a-4567-91f845ef5142.png"></p>
<p>1週間でWebサービスを作るイベント <a href="https://crieit.net/boards/web1week-202005">web1week</a>への投稿作品です!<br />
よかったら、遊んでみてください(<em>´ω`</em>)</p>
<p>■エアで投げ銭できるWebサービス「エア銭」<br />
URL: <a target="_blank" rel="nofollow noopener" href="https://air-money.netlify.app/">https://air-money.netlify.app/</a></p>
<h1 id="参考にしたサイトさま"><a href="#%E5%8F%82%E8%80%83%E3%81%AB%E3%81%97%E3%81%9F%E3%82%B5%E3%82%A4%E3%83%88%E3%81%95%E3%81%BE">参考にしたサイトさま</a></h1>
<ul>
<li><a target="_blank" rel="nofollow noopener" href="https://developers.cyberagent.co.jp/blog/archives/9662/">FRESH! における Web プッシュ通知機能 〜実装編〜 | CyberAgent Developers Blog</a></li>
<li><a target="_blank" rel="nofollow noopener" href="https://github.com/firebase/quickstart-js/blob/bcce38ebc1e5602560e2b76b20f19b7834b8279e/messaging/firebase-messaging-sw.js#L15-L37">quickstart-js/firebase-messaging-sw.js at bcce38ebc1e5602560e2b76b20f19b7834b8279e · firebase/quickstart-js</a></li>
<li><a target="_blank" rel="nofollow noopener" href="https://firebase.google.com/docs/reference/js/firebase.messaging.Messaging#deletetoken">Messaging | JavaScript SDK | Firebase</a></li>
<li><a target="_blank" rel="nofollow noopener" href="https://firebase.google.com/docs/cloud-messaging/js/send-multiple#%E3%83%88%E3%83%94%E3%83%83%E3%82%AF-http-post-%E3%83%AA%E3%82%AF%E3%82%A8%E3%82%B9%E3%83%88">複数のデバイスにメッセージを送信する | Firebase</a></li>
<li><a target="_blank" rel="nofollow noopener" href="https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages#WebpushConfig">REST Resource: projects.messages | Firebase</a></li>
<li><a target="_blank" rel="nofollow noopener" href="https://firebase.google.com/docs/cloud-messaging/send-message">アプリサーバーからの送信リクエストを作成する | Firebase</a></li>
<li><a target="_blank" rel="nofollow noopener" href="https://firebase.google.com/docs/cloud-messaging/js/first-message">バックグラウンド アプリにテスト メッセージを送信する | Firebase</a></li>
<li><a target="_blank" rel="nofollow noopener" href="https://firebase.google.com/docs/cloud-messaging/js/client">JavaScript Firebase Cloud Messaging クライアント アプリを設定する</a></li>
<li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/ryo_hisano/items/1171beca22d5a04ed802">Firebase Cloud Messagingで始めるWebプッシュ通知 - Qiita</a></li>
</ul>
きらぷか@積読ハウマッチ/SSSAPIなど
tag:crieit.net,2005:PublicArticle/14977
2019-05-10T23:08:59+09:00
2019-05-10T23:08:59+09:00
https://crieit.net/posts/Onesignal-NodeJS-push
Onesignalを使ってNodeJSでpush通知
<h1 id="背景"><a href="#%E8%83%8C%E6%99%AF">背景</a></h1>
<p><strong>手軽にプッシュ通知を実装したい!</strong></p>
<h2 id="何を使おうか?"><a href="#%E4%BD%95%E3%82%92%E4%BD%BF%E3%81%8A%E3%81%86%E3%81%8B%EF%BC%9F">何を使おうか?</a></h2>
<ul>
<li>pushbullet</li>
</ul>
<p>push通知アプリ。プラットフォームに依存しない。<br />
android 8に変えたらバックグラウンドタスクがkillされてるのか通知がこない。</p>
<ul>
<li>slack</li>
</ul>
<p>最近のトレンド。チームに加入してない人には通知がいかないのがデメリット。</p>
<ul>
<li>onesignal</li>
</ul>
<p>日本語対応が後手の印象。登録画面が怪しい。<br />
プッシュ通知が手軽に実装できる。</p>
<p><strong>過去、slackとpushbulletは実装したことがある(※ただしJava)ので、今回はNodeJSでonesignalの実装に挑んでみました。</strong></p>
<h1 id="使うライブラリ"><a href="#%E4%BD%BF%E3%81%86%E3%83%A9%E3%82%A4%E3%83%96%E3%83%A9%E3%83%AA">使うライブラリ</a></h1>
<ul>
<li><a target="_blank" rel="nofollow noopener" href="https://www.npmjs.com/package/onesignal-node">onesignal-node</a></li>
</ul>
<p>NodeJSでonesignalのAPIにアクセスできるライブラリ。</p>
<h1 id="実装"><a href="#%E5%AE%9F%E8%A3%85">実装</a></h1>
<h2 id="概要"><a href="#%E6%A6%82%E8%A6%81">概要</a></h2>
<p>4時間ごとに00分ちょうどに掲示板の書き込みがあるかヘッダを確認して更新があればプッシュ通知を出すというジョブを実装する。</p>
<h2 id="毎日4時間ごと(00分00秒)にジョブを実行する"><a href="#%E6%AF%8E%E6%97%A54%E6%99%82%E9%96%93%E3%81%94%E3%81%A8%2800%E5%88%8600%E7%A7%92%29%E3%81%AB%E3%82%B8%E3%83%A7%E3%83%96%E3%82%92%E5%AE%9F%E8%A1%8C%E3%81%99%E3%82%8B">毎日4時間ごと(00分00秒)にジョブを実行する</a></h2>
<pre><code class="javascript">let cronTime:string = "0 0 */4 * * *";
</code></pre>
<h2 id="即時関数"><a href="#%E5%8D%B3%E6%99%82%E9%96%A2%E6%95%B0">即時関数</a></h2>
<p>この中ではawaitを使用できる。<br />
戻り値がPromiseの処理を1行で記述できるのがasync/awaitの強み。<br />
(コールバック地獄とthen地獄はもう嫌だ)</p>
<pre><code class="javascript">(async()=>{
})().catch(...)
</code></pre>
<h2 id="レスポンスヘッダから最終更新日時を取得する"><a href="#%E3%83%AC%E3%82%B9%E3%83%9D%E3%83%B3%E3%82%B9%E3%83%98%E3%83%83%E3%83%80%E3%81%8B%E3%82%89%E6%9C%80%E7%B5%82%E6%9B%B4%E6%96%B0%E6%97%A5%E6%99%82%E3%82%92%E5%8F%96%E5%BE%97%E3%81%99%E3%82%8B">レスポンスヘッダから最終更新日時を取得する</a></h2>
<pre><code class="javascript">let data = await response.headers.get('last-modified')
</code></pre>
<h2 id="REST API keyとAUTH KEYを指定する"><a href="#REST+API+key%E3%81%A8AUTH+KEY%E3%82%92%E6%8C%87%E5%AE%9A%E3%81%99%E3%82%8B">REST API keyとAUTH KEYを指定する</a></h2>
<p>メニューから「ACCOUNT & API KEYS」のページを参照する。<br />
<a href="https://crieit.now.sh/upload_images/b08b548caec5795ad40e35af9c85f4c95cd58244524ac.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/b08b548caec5795ad40e35af9c85f4c95cd58244524ac.png?mw=700" alt="image" /></a></p>
<pre><code class="javascript">var myClient = new OneSignal.Client({
userAuthKey: '*******',
app: {
appAuthKey: '*******',
appId: '*******'
}
});
var firstNotification = new OneSignal.Notification({
contents: {
ja: last_modified+"掲示板が更新されました。",
en: "BBS is updated."
},
included_segments: ["All"]
});
let response = await myClient.sendNotification(firstNotification)
</code></pre>
<p>今回は最終更新日時はDBに格納せずテキストファイルで保持しています。</p>
<h2 id="全体の実装"><a href="#%E5%85%A8%E4%BD%93%E3%81%AE%E5%AE%9F%E8%A3%85">全体の実装</a></h2>
<pre><code class="javascript">var server = http.createServer(app);
let cronTime:string = "0 0 */4 * * *";
let job = new cronJob({
//実行したい日時 or crontab書式
cronTime: cronTime
//指定時に実行したい関数
, onTick: ()=> {
(async()=>{
let response = await fetch('*****')
let data = await response.headers.get('last-modified')
let last_modified = moment(data).format('YYYY/MM/DD HH:mm:SS')
let isExist:boolean
try {
fs.statSync('./logs/last-modified.txt')
isExist = true
} catch (error) {
isExist = false
}
let stored_time
if(isExist){
stored_time = fs.readFileSync('./logs/last-modified.txt').toString()
}
console.log(stored_time,last_modified)
if(stored_time === undefined || stored_time < last_modified){
//onesignalで通知を出す
var myClient = new OneSignal.Client({
userAuthKey: '*******',
app: { appAuthKey: '*******', appId: '*******' }
});
var firstNotification = new OneSignal.Notification({
contents: {
ja: last_modified+"掲示板が更新されました。",
en: "BBS is updated."
},
included_segments: ["All"]
});
let response = await myClient.sendNotification(firstNotification)
console.log(response.data, response.httpResponse.statusCode);
console.log('send')
//push通知の送信終了後にファイルを更新する
fs.writeFileSync('./logs/last-modified.txt',last_modified)
}
})()
.catch((err)=>{
systemLogger.error(err)
})
console.log('onTick!');
}
//ジョブの完了または停止時に実行する関数
, onComplete: function() {
console.log('onComplete!')
}
// コンストラクタを終する前にジョブを開始するかどうか
, start: false
//タイムゾーン
//, timeZone: "Japan/Tokyo"
})
//ジョブ開始
job.start();
server.listen(port);
server.on('error', onError);
</code></pre>
ckoshien
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開発者