How to fix 'Multiple push notification' error in swift? - ios

I'm going through process of adding push notification on my iOS app by following a book and some docs on push notification.
Push notification was working fine for few days and then all of a sudden I started getting 3 push notification at a time and then it gradually increased to 7.
// this function is called from didFinishLaunchingWithOptions function
func requestForNotification(_ application: UIApplication){
UNUserNotificationCenter.current().requestAuthorization(options: [.badge,.sound,.alert]) { (granted, _) in
guard granted else {return}
DispatchQueue.main.async {
UIApplication.shared.registerForRemoteNotifications()
}
}
}
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
let token = deviceToken.reduce(""){$0 + String(format: "%02x",$1) }
sendTokenToService(token: token)
print("device token is:::::::::: \(token)")
}
//Send token to local server
func sendTokenToService(token:String){
var params = [String:AnyObject]()
params["token"] = token as AnyObject
APIManager.shared.request(apiRouter: APIRouter.init(endpoint: .addAdminToken(param: params))) { (response, success) in
if success, let response = response["response"] {
print(response)
}
}
}
registerForRemoteNotifications() is being called only once but I found this on apple's official docs:
registerForRemoteNotifications() method: UIKit might call it in other rare circumstances. For example, UIKit calls the method when the user launches an app after having restored a device from data that is not the device’s backup data. In this exceptional case, the app won’t know the new device’s token until the user launches it.
Any idea how to resolve this issue ?

Push notifications are delivered once per device token. If you have access to your push notification server (or push provider if you are not managing it yourself), you can verify that is the case. When you are building the app and installing on a device, the new build will most likely be generating a new token. This also happens when the user uninstalls/installs the app. This could be the reason you're getting multiple notifications. Apple is supposed to invalidate old device tokens and send feedback back to your server. For more information on how Apple sends you feedback, here's a link to their Apple APNS docs.

Related

How can I detect when the real iOS/APNS push token is registered with Firebase Cloud Messaging (FCM)?

Firebase's didReceiveRegistrationToken as seen below is getting called even if the user hasn't granted push notification permissions. I need to make sure that APNS push tokens are being registered for analytics, as well as for saving it on my server, but this function is getting called even when the user hasn't granted push permission. 🤷‍♂️
/**
* Requirement for Firebase push notifications.
* See documentation here: https://firebase.google.com/docs/cloud-messaging/ios/client
*/
extension AppDelegate: MessagingDelegate {
// Note: This callback is fired at each app startup and whenever a new token is generated.
func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String) {
print("Firebase registration token: \(fcmToken)")
let dataDict: [String: String] = ["token": fcmToken]
NotificationCenter.default.post(name: Notification.Name("FCMToken"), object: nil, userInfo: dataDict)
print("Getting called even if the user hasn't opted in for push notifications!")
}
}
TL;DR: FCM token is not a reliable way to detect if APNS token is registered. Disable method swizzling in firebase and listen to APNS registration manually.
FCM Tokens: An FCM token is also known as a device instance ID; it identifies that specific app on that specific device. In other words, FCM token doesn't equal an APNS token. Here is one explanation: APNs, FCM or GCM token.
FCM Token Auto Generation: Surprisingly, even without iOS user granting permission, Firebase generates an FCM token on app launch. I guess it wants to find a way to identify the app & device pair, so as soon as you launch, you're gonna have an FCM token. If you want, you can disable auto-generation and wait for the user to opt-in.
By default, the FCM SDK generates a registration token for the client app instance on app launch. If you want to get an explicit opt-in before using Instance ID, you can prevent generation at configure time by disabling FCM. To do this, add a metadata value to your Info.plist (not your GoogleService-Info.plist):
Here lies one of the problems! Even if the user has disabled auto-generated of FCM tokens, one will be generated when the user is prompted to enable push notifications not when the user has accepted push notification permission. This seems quiet odd and I ended up reporting this to Firebase.
APNS Token Swizzling: So by default Firebase takes over AppDelegate's didRegisterForRemoteNotificationsWithDeviceToken (called "swizzling"). You can disable this and override the delegate method yourself. This will then give you access to both APNS token as well as FCM token.
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
print("Successfully registered for notifications!")
// Required to make FCM work since we disabled swizzling!
Messaging.messaging().apnsToken = deviceToken
// String version of APNS so we can use save it/use it later/do analytics.
let apnsToken = deviceToken.map { String(format: "%02.2hhx", $0) }.joined()
}
Getting a callback for a device ID token and getting a callback for an actual pushed message are two different things. The device ID token callback just gives you a token that identifies the device for a later push notification. It isn't actually a pushed notification itself. It's just an ID.
That said, if you disagree with the behavior of the client SDK from a security perspective, you're free to contact Firebase support directly with feedback.

How to update APNS device token remotely

My application is using remote notification to remind users of planned actions. User may be informed even after a few months.
Application is updating device token during each time it starts (sends the received token to the server).
But there is a problem. Sometimes device token becomes invalid. (backend-service got error "Invalid token" from APNS). I know that it's normal that device token can change. But there is a case when user set reminder on after a few months and doesn't use app during this time.
How do I update device token when it was expired and when application is turned off?
At every time app running it will call below AppDelegate method, So you need to send the APN token every time app run:
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
let tokenParts = deviceToken.map { data -> String in
return String(format: "%02.2hhx", data)
}
let token = tokenParts.joined()
print("token :: ", token)
//TODO:- now you need to send above token to your API that saved this token to user profile
}

AWS SNS push notification not received on iPhone 6 and above

My coworker and I are building an app and implementing push notification feature utilizing AWS SNS API.
The issue here is that some devices work fine but some like iPhone 6,7,8 receive no notification regardless of the OS version (they're mostly iOS 11,12).
We've covered the basic by checking the below points:
notification turned on on all test devices
testers grant permission to receive notification at the app launch time
the p12 certificate uploaded to AWS is valid and the format is correct
The error messages we got from AWS CloudWatch Logs mostly were "bad device token" or "unregistered", but we are sure that we upload the device token as soon as we got it from APNS.
EDIT: actual code added
Register for notification (within didFinishLaunchingWithOptions)
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { granted, error in
if let error = error {
print("\(error.localizedDescription)")
} else {
application.registerForRemoteNotifications()
}
}
Get token
func application(application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
print("Successfully registered for notifications!")
// upload token to our database for later use
}
Is there anything we are missing?

iOS Firebase Push Notifications received to first token only, after token changes no longer received

edit: Updating question with new information.
So there's a number of questions out there about Firebase notifications not being received but I've not found one quite like this.
I've recently switched over from using the old p12 APNs certificates to the glorious new p8, and uploaded it to all of my projects on Firebase v4.0.0.
What I'm seeing is, when I do a fresh install of any of my projects, I can send & receive push notifications fine. But after some time, the token changes - and it just stops working - Firebase says "message sent successfully" but no message is received.
Weirdly - my app still receives push notifications to the previous Firebase token, while the new one reported by Firebase isn't working.
Following the advice at Debugging Firebase Cloud Messaging on iOS, I happily debugged the morning away:
Are there any error messages coming back from my Postman firebase attempts? Nope, success:1 every time
Am I getting pushes with app in the background, foreground or neither? Neither.
Are my AppDelegate remote notification registration attempts working successfully? Yes.
Can I directly send a message over APNs, using the new .p8 file? Yes (thanks to this). Messages are being received when I send them directly over APNs just fine!
In the Firebase console, if I send a message to all devices in a project, I receive the message to all devices. But if I try to limit it to my debug device via its FCM token, I get nothing.
So there's one last link that's just not working - appearing to work perfectly for new installs and then bombing after some amount of time - the FCM to APNs link. But how would I debug it?
Here in
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
...
NotificationCenter.default.addObserver(self,
selector: #selector(self.tokenRefreshNotification),
name: .firInstanceIDTokenRefresh,
object: nil)
return true
}
func tokenRefreshNotification(_ notification: Notification) {
if let refreshedToken = FIRInstanceID.instanceID().token() {
print("InstanceID token: \(refreshedToken)")
// Here you get the refreshed token
// here you can connect to fcm and do subscribe to notifications
}
}
Hopefully it will solve the problem.

Storing tokens for Push Notifications

our project currently has a Node jS backend and we want to implement push notifications for iOS. We did some research and figured out that that we will have to store the tokens that APN gives us in our DB in order to send push notifications to specific devices. Can someone confirm this or is there a better way of sending notifications?
Secondly, I also found that when devices go through software updates that this changes their token so does that mean we must have capability to update the token in our DB because it will change often. This is also pretty important. Is there also any other times that the token might change?
Lastly, are there any good libraries in Node for sending push notifications?
Thanks in advance!
You must send the notification accessToken to the server, Its like address for notification to be delivered. You dont have to worry about the variation in the accesstoken because you have to send it when you login everytime so the new updated accesstoken will append in server too.You have to register for remote notifiation in your Appdelegate like this and later send the saved token in nsuserdefault to the server in login API.
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
let settings = UIUserNotificationSettings(forTypes: [.Alert, .Badge, .Sound], categories: nil)
UIApplication.sharedApplication().registerUserNotificationSettings(settings)
UIApplication.sharedApplication().registerForRemoteNotifications()
return true
}
//Called if successfully registered for APNS.
func application(application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: NSData) {
// let deviceTokenString = NSString(format: "%#", deviceToken) as String
var tokenStr = deviceToken.description
tokenStr = tokenStr.stringByReplacingOccurrencesOfString("<", withString: "", options: [], range: nil)
tokenStr = tokenStr.stringByReplacingOccurrencesOfString(">", withString: "", options: [], range: nil)
tokenStr = tokenStr.stringByReplacingOccurrencesOfString(" ", withString: "", options: [], range: nil)
print(deviceToken.description)
print(tokenStr)
//save the token in NSUserDefaults
NSUserDefaults.standardUserDefaults().setObject(deviceTokenString, forKey: "deviceToken")
}
//Called if unable to register for APNS.
func application(application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: NSError) {
print(error)
}
Reference Apple's Documentation
The device token is your key to sending push notifications to your app
on a specific device. Device tokens can change, so your app needs to
reregister every time it is launched and pass the received token back
to your server. If you fail to update the device token, remote
notifications might not make their way to the user’s device. Device
tokens always change when the user restores backup data to a new
device or computer or reinstalls the operating system. When migrating
data to a new device or computer, the user must launch your app once
before remote notifications can be delivered to that device.
I have been looking for an optimal solution for this but seems there's no clear way of handling it since we are not aware when the token will change.
Does this this mean we should store the token in our database and fetch it every time a user opens the app and then try to compare them ? That is not efficient enough I guess.
In my opinion, keep the token in db for the server then keep a keep a copy in local storage so you can compare that with one that is generated every time the app is opened, unless the two are not the same, then you can update the one in the db.

Resources