はじめに
今回はFirebaseFunctionsのhttp関数を使用して特定のデバイスにPush通知を送る実装を行なっていきます。実装前に下記の準備項目が必要になります。
事前準備
- 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の実装
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の実装
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で実際にPushを送信してみます。
$ curl -X POST https://yout-functions-url/push?device_token=your-device-token -d "message=I love fishing🎣"
結果
さいごに
今回はテスト的に実行できるようにするため、httpリクエストに認証は設定していませんでしたが、また実装し直したら編集しようと思います。また、CIなどを使ってfirebase/functionsなどをデプロイするとき、どのようにFirebaseプロジェクトの秘密鍵を参照させるのがベストなのでしょうか。。