この記事でできるようになるもの
$ curl -X POST https://your-functions-url/push?device_token=your-device-token -d "message=I love fishing🎣"
のようなリクエストをターミナルで叩けば下記のようなプッシュ通知を送信できるようになります。
事前準備
- Firebaseプロジェクト
- Firebase/Messaging導入済のiosプロジェクト
- APNsのFirebaseアップロード
FirebaseCLIインストール
まずは、FirebaseCLIをインストールすることでFunctionsのDeployやプロジェクトの切り替えなどをCLIで操作できるようにします。今回はnpmでインストールを行います。
npmインストール
とりあえず最新のものをnodebrewで取得してきてPathを通すとこまで終わらせます。
$ brew install nodebrew $ nodebrew install-binary 13.7.0 $ nodebrew use v7.0.0 $ echo 'export PATH=$HOME/.nodebrew/current/bin:$PATH' >> ~/.bash_profile $ source ~/.bash_profile
firebase-toolsインストール
1.npmでfirebase-toolsをインストールします。
$ npm install -g firebase-tools
2.firebase-toolsコマンドを使用して、操作を行うユーザの認証をします。下記のコマンドを実行するとWebブラウザが立ち上がるので、Firebaseプロジェクトで編集権限のあるアカウントでログインを行います。
$ firebase login
3.firebaseのプロジェクトをuse
コマンドを使って指定します。この操作によりfirebase/functionsなどのデプロイ先を変更できたりします。
$ firebase use firebase_project_id
Functionsプロジェクト作成
今回はFunctionsのみ使用するので下記のコマンドでプロジェクトを立ち上げます。
$ firebase init functions
すると下記のような構造のプロジェクトが立ち上がるので、主にindex.js
を編集して関数を作成して行きます。
参照: https://firebase.google.com/docs/functions/get-started?hl=ja
FirebaseAdminSDKインストール
1.sdkの情報などを保存するpackage.json
を作成します。
$ npm init
2.firebase-admin npmパッケージをインストールします。
$ npm install firebase-admin --save
3.次にfirebase-admin
を初期化をするためにローカルの環境変数にFirebaseサービスアカウントの秘密鍵を生成したファイルへのパスを指定します。これを設定することでSDKの初期化時にキーが参照され、プロジェクトでの認証が完了します。CIなどでブランチごとにDeploy先を変更させたい時はどうやって秘密鍵を参照させるのがベストなんでしょうか?
$ export GOOGLE_APPLICATION_CREDENTIALS="/home/user/Downloads/service-account-file.json"
参照: https://firebase.google.com/docs/admin/setup?hl=ja
4.index.js
に移動してsdkの初期化コードを追加します。
const functions = require('firebase-functions'); const admin = require('firebase-admin'); admin.initializeApp();
node.jsの実装
index.js
const functions = require('firebase-functions'); const admin = require('firebase-admin'); admin.initializeApp(); //onRequestでhttpからの呼び出しを可能にします。 exports.push = functions.https.onRequest((request, response) => { if (request.query.device_token !== undefined && request.body.message !== undefined) { const device_token = request.query.device_token const message = request.body.message const payload = { notification: { body: message, badge: "1", sound:"default", } }; switch (request.method) { case 'POST': push(device_token, payload, response); break default: response.status(400).send({ error: 'Invalid request method' }) break } } else { response.status(400).send({ error: 'Invalid request parameters' }) } }) function push(token, payload, response) { const options = { priority: "high", }; //FCMにAdminSDKを介してPush通知を送信します。 admin.messaging().sendToDevice(token, payload, options) .then(pushResponse => { console.log("Successfully sent message:", pushResponse); response.status(200).send({message: 'Successfully sent message'}) }) .catch(error => { response.status(400).send({ error: 'Error sending message' }) }); }
swiftの実装
AppDelegate.swift
import UIKit import Firebase import UserNotifications import FirebaseMessaging @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? private var mainTabViewController: MainTabViewController? func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { //環境ごとにプロジェクトを変えてるためplistを変更しています。 let filePath = Bundle.main.path(forResource: Config.Server.instance.firebaseInfoPlistName, ofType:"plist") //Forced Unwrapping🚨 FirebaseApp.configure(options: FirebaseOptions(contentsOfFile:filePath!)!) initFirebaseMessaging() initRemoteNotification(application) window = UIWindow(frame: UIScreen.main.bounds) window!.makeKeyAndVisible() navigate() return true } func navigate(_ isTrial: Bool = false) { guard let window = window else { assert(false) return } let previousVC = window.rootViewController for v in window.subviews { v.removeFromSuperview() } let vc = MainTabViewController() mainTabViewController = vc window.rootViewController = vc if let previousVC = previousVC { previousVC.dismiss(animated: false) { previousVC.view.removeFromSuperview() } } } private func initRemoteNotification(_ application: UIApplication) { UNUserNotificationCenter.current().delegate = self let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound] //TODO: Relocate requestAuthorization method. UNUserNotificationCenter.current().requestAuthorization(options: authOptions, completionHandler: {_, _ in }) application.registerForRemoteNotifications() } private func initFirebaseMessaging() { //DelegateでdeviceTokenの変更を監視します。 Messaging.messaging().delegate = self //明示的にdeviceTokenを取得します。 InstanceID.instanceID().instanceID { (result, error) in if let error = error { //TODO: Error handling. print("Error fetching remote instance ID: \(error)") } else if let result = result { //TODO: Send token to parnovi api for update user fcm token. if authorized == true print("Remote instance ID token: \(result.token)") } } } } extension AppDelegate: UNUserNotificationCenterDelegate { func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { completionHandler([.badge, .sound, .alert]) } func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) { completionHandler() } } extension AppDelegate: MessagingDelegate { //Observe firebase messaging token. func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String) { } }
FunctionsのDeploy
実際に関数をデプロイしてPush通知を送信してみます。
$ firebase deploy --only functions
swiftのInstanceID.instanceID().instanceID
で取得したDeviceTokenを使ってcurlで下記のリクエストを叩くとプッシュ通知が送信されるようになります。
$ curl -X POST https://yout-functions-url/push?device_token=your-device-token -d "message=I love fishing🎣"