Swift 5
iOS 13+
xCode 11
Node v14.2.0
Firebase/Firestore latest
Alice send push notification to Bob, while Bob's phone is .inactive
or .background
. Bob's phone should get notification and immediately trigger code.
This question has plenty of answers, but most of what I can find revolves around hacking the PushKit and CallKit native API to send .voIP
pushes. Per this question ( iOS 13 not getting VoIP Push Notifications in background ), Apple no longer allow you to send .voIP
pushes w/o triggering CallKit's native phone ring routine.
On iOS side, I have the following bits in AppDelegate.swift
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
registerForPushNotifications()
}
func application(_ application: UIApplication,
didReceiveRemoteNotification userInfo: [AnyHashable: Any],
fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void)
{
print(">>> I would like this to be triggered even if the app is asleep")
switch application.applicationState {
case .active:
print(">>>>>>> the app is in [FOREGROUND]: \(userInfo)")
break
case .inactive, .background:
print(">>>>>>>> the app is in [BACKGROUND]: \(userInfo)")
break
default:
break
}
}
func registerForPushNotifications() {
UNUserNotificationCenter.current().delegate = self
UNUserNotificationCenter
.current()
.requestAuthorization(options:[.alert, .sound, .badge]) {[weak self] granted, error in
guard granted else { return }
self?.getNotificationSettings()
}
}
func getNotificationSettings() {
UNUserNotificationCenter.current().getNotificationSettings { settings in
guard settings.authorizationStatus == .authorized else { return }
Messaging.messaging().delegate = self
DispatchQueue.main.async {
// Register with Apple Push Notification service
UIApplication.shared.registerForRemoteNotifications()
/// cache token client side and save in `didRegisterForRemoteNotificationsWithDeviceToken`
if let token = Messaging.messaging().fcmToken {
self.firebaseCloudMessagingToken = token
}
}
}
}
//@Use: listen for device token and save it in DB, so notifications can be sent to this phone
func application(_ application: UIApplication,
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
if (firebaseCloudMessagingToken != nil){
self.updateMyUserData(
name : nil
, pushNotificationToken: firebaseCloudMessagingToken!
)
}
}
func application(_ application: UIApplication,
didFailToRegisterForRemoteNotificationsWithError error: Error) {
///print(">>> Failed to register: \(error)")
}
@available(iOS 10.0, *)
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
// @NOTE: this fires when the app is open. So you can go the call screen right away
let payload = notification.request.content.userInfo as! [String:Any?]
let type = payload["notificationType"]
print(">> this fires if the app is currently open")
}
/// @NOTE: we are using backward compatible API to access user notification when the app is in the background
/// @source: https://firebase.google.com/docs/cloud-messaging/ios/receive#swift:-ios-10
@available(iOS 10.0, *)
func userNotificationCenter(_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: @escaping () -> Void) {
print(" this fires when the user taps on the notification message")
}
On the server/Node.js side, I send push notification this way:
// Declare app push notification provider for PushKit
const _ApnConfig_ = {
token: {
key : fileP8
, keyId : "ABCDEFG"
, teamId: "opqrst"
},
production: false
};
var apnProvider = new apn.Provider(_ApnConfig_);
exports.onSendNotification = functions.https.onRequest((request, response) => {
var date = new Date();
var timeStamp = date.getTime();
const deviceTok = "..."
var recepients = [apn.token( deviceTok )]
const notification = new apn.Notification();
notification.topic = "com.thisisnt.working"
notification.body = "Hello, world!";
notification.payload = {
from: "node-apn"
, source: "web"
, aps: {
"content-available": 1
, "data" : { "custom_key":"custom value", "custom_key_2":"custom value 2" }
}
};
notification.body = "Hello, world @ " + timeStamp;
return apnProvider.send(notification, recepients).then(function(res) {
console.log("res.sent: ", res.sent)
console.log("res.failed: ", res.failed)
res.failed.forEach( (item) => {
console.log(" \t\t\t failed with error:", item.error)
})
return response.send("finished!");
}).catch( function (error) {
console.log("Faled to send message: ", error)
return response.send("failed!");
})
})
Both are pretty standard. I have set the content-availabe
to 1
. Right now the messages are coming through and displayed by Apple Push Notification center, they're just not triggering the block with didReceiveRemoteNotification
as intended.
You need to enable the background mode - remote notifications capability.
To receive background notifications, you must add the remote notifications background mode to your app. In the Signing & Capability tab, add the Background Modes capability, then select the Remote notification checkbox.
Enabling the remote notifications background mode:
For watchOS, add this capability to your WatchKit Extension.
Pushing Background Updates to Your App | 将后台更新推送到您的应用程序 | Apple Developer Documentation
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.