Categories
nativePHP

NativePHP Firebase Cloud Messaging and the deep link

Push notifications are a powerful tool to engage users — and opening a specific screen within your app from a notification can dramatically enhance the user experience.

Lately, I have been working with FCM and nativePHP. My use case was straightforward: to notify a callee of an incoming call via push notifications. Upon pressing the notification, a deep link to the callee interface was required.
For the record, I have zero experience with mobile app development, so bear with me.

What do I wanna achieve

NativePHP supports deep linking. Deeplinks. This works great when sending for example an email and use your deeplink inside of an email. But here are 2 scenario’s

  • Receiving a APN from FCM is not opening a deeplink, tapping it will open the app on the main page.
  • When the app is in the foreground APN are not shown send from FCM.

TLDR

What did I change to make this work

First of all we need to create a payload key that has the deeplink value

{
  "token": "fbC1hM1erEHmhm8PzeRKkp:APA91bGUlro5G91OY",
  "title" => 'Deep link test FCM',
  "body" => 'Opens a deeplink in app for the callee',
  "data": {
    "deeplink": "mysuperapp://asterisk/agent-endpoint"
  },
  "androidConfig": {
    "priority": "high",
    "notification": {
      "sound": "default"
    }
  },
  "apnsConfig": {
    "headers": {
      "apns-priority": "10"
    },
    "payload": {
      "aps": {
        "sound": "incoming-call.caf"
      }
    }
  }
}

The next part is adding some extra code to the nativephp/ios/AppDelegate.swift file

import SwiftUI
import AVFoundation
import UserNotifications
import FirebaseMessaging

class AppDelegate: NSObject, UIApplicationDelegate, MessagingDelegate, UNUserNotificationCenterDelegate {
    static let shared = AppDelegate()

    // Called when the app is launched
    func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
    ) -> Bool {
        // Set self as the delegate for the Messaging instance
        if FirebaseManager.shared.isConfigured {
            Messaging.messaging().delegate = self
        }

        // Check if the app was launched from a URL (custom scheme)
        if let url = launchOptions?[UIApplication.LaunchOptionsKey.url] as? URL {
            DebugLogger.shared.log("📱 AppDelegate: Cold start with custom scheme URL: \(url)")
            // Pass the URL to the DeepLinkRouter
            DeepLinkRouter.shared.handle(url: url)
        }

        // Check if the app was launched from a Universal Link
        if let userActivityDictionary = launchOptions?[UIApplication.LaunchOptionsKey.userActivityDictionary] as? [String: Any],
           let userActivity = userActivityDictionary["UIApplicationLaunchOptionsUserActivityKey"] as? NSUserActivity,
           userActivity.activityType == NSUserActivityTypeBrowsingWeb,
           let url = userActivity.webpageURL {
            DebugLogger.shared.log("📱 AppDelegate: Cold start with Universal Link: \(url)")
            // Pass the URL to the DeepLinkRouter
            DeepLinkRouter.shared.handle(url: url)
        }

        // <-- ADDED: Set notification center delegate to self
        UNUserNotificationCenter.current().delegate = self

        return true
    }

    // Called for Universal Links
    func application(
        _ application: UIApplication,
        continue userActivity: NSUserActivity,
        restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void
    ) -> Bool {
        // Check if this is a Universal Link
        if userActivity.activityType == NSUserActivityTypeBrowsingWeb,
           let url = userActivity.webpageURL {
            // Pass the URL to the DeepLinkRouter
            DeepLinkRouter.shared.handle(url: url)
            return true
        }

        return false
    }

    // Called when the user grants (or revokes) notification permissions
    func application(
        _ application: UIApplication,
        didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data
    ) {
        // Put the token in PHP's memory so the developer can fetch it manually
        let tokenString = deviceToken.map { String(format: "%02x", $0) }.joined()
        NativePHPSetPushTokenC(tokenString)

        if FirebaseManager.shared.isConfigured {
            // Pass to Firebase
            Messaging.messaging().deleteToken { error in
                Messaging.messaging().apnsToken = deviceToken
            }
        } else {
            // Fire TokenGenerated event
            LaravelBridge.shared.send?(
                "Native\\Mobile\\Events\\PushNotification\\TokenGenerated",
                ["token": tokenString]
            )
        }
    }

    func application(
        _ application: UIApplication,
        didFailToRegisterForRemoteNotificationsWithError error: Error
    ) {
        print("Failed to register for remote notifications:", error.localizedDescription)
    }

    // Handle deeplinks
    func application(
        _ app: UIApplication,
        open url: URL,
        options: [UIApplication.OpenURLOptionsKey: Any] = [:]
    ) -> Bool {
        // Pass the URL to the DeepLinkRouter
        DeepLinkRouter.shared.handle(url: url)
        return true
    }

    func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) {
        guard let fcmToken = fcmToken else { return }

        LaravelBridge.shared.send?(
            "Native\\Mobile\\Events\\PushNotification\\TokenGenerated",
            ["token": fcmToken]
        )
    }

    // <-- ADDED: Handle notification tap and extract deeplink URL from payload
    func userNotificationCenter(_ center: UNUserNotificationCenter,
                                didReceive response: UNNotificationResponse,
                                withCompletionHandler completionHandler: @escaping () -> Void) {
        let userInfo = response.notification.request.content.userInfo


        if let deeplinkString = userInfo["deeplink"] as? String,
           let deeplinkURL = URL(string: deeplinkString) {
            DeepLinkRouter.shared.handle(url: deeplinkURL)
        }
        completionHandler()
    }

    // <-- ADDED: Show notifications even when app is in foreground (optional)
    func userNotificationCenter(_ center: UNUserNotificationCenter,
                                willPresent notification: UNNotification,
                                withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
        completionHandler([.banner, .sound]) //
    }
}

Leave a Reply

Your email address will not be published. Required fields are marked *