From 48610587e3560d011620a0fcb5b4b888e80bceb6 Mon Sep 17 00:00:00 2001 From: Victor Uvarov Date: Sat, 16 Jan 2021 12:45:02 -0800 Subject: [PATCH 1/2] Add support for handling invalidated PushKit tokens --- README.md | 39 +++++++++++++++---- example/ios/Runner/AppDelegate.swift | 5 +++ example/lib/main.dart | 21 ++++++++-- .../FlutterVoipPushNotificationPlugin.h | 1 + .../FlutterVoipPushNotificationPlugin.m | 31 ++++++++++++++- lib/flutter_voip_push_notification.dart | 39 ++++++++++++++----- 6 files changed, 114 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 4c4abe1..d43b92d 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ # Flutter VoIP Push Notification + [![pub package](https://img.shields.io/pub/v/flutter_voip_push_notification.svg)](https://pub.dartlang.org/packages/flutter_voip_push_notification) Flutter VoIP Push Notification - Currently iOS >= 8.0 only @@ -22,10 +23,8 @@ Please refer to [VoIP Best Practices][2]. **Note**: Do NOT follow the `Configure VoIP Push Notification` part from the above link, use the instruction below instead. - #### AppDelegate.swift - ```swift ... @@ -50,6 +49,11 @@ import flutter_voip_push_notification /* <------ add this line */ FlutterVoipPushNotificationPlugin.didReceiveIncomingPush(with: payload, forType: type.rawValue) } + // Handle invalidated push tokens + func pushRegistry(_ registry: PKPushRegistry, didInvalidatePushTokenFor type: PKPushType) { + FlutterVoipPushNotificationPlugin.didInvalidatePushToken(forType: type.rawValue) + } + // Handle incoming pushes func pushRegistry(_ registry: PKPushRegistry, didUpdate pushCredentials: PKPushCredentials, for type: PKPushType) { // Process the received push @@ -62,7 +66,6 @@ import flutter_voip_push_notification /* <------ add this line */ #### AppDelegate.m Modification - ```objective-c ... @@ -87,6 +90,12 @@ import flutter_voip_push_notification /* <------ add this line */ [FlutterVoipPushNotificationPlugin didUpdatePushCredentials:credentials forType:(NSString *)type]; } +// The system calls this method when a previously provided push token is no longer valid for use. No action is necessary on your part to reregister the push type. Instead, use this method to notify your server not to send push notifications using the matching push token. +- (void)pushRegistry:(PKPushRegistry *)registry didInvalidatePushTokenForType:(PKPushType)type +{ + [FlutterVoipPushNotificationPlugin didInvalidatePushTokenForType:(NSString *)type]; +} + // Handle incoming pushes - (void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayload:(PKPushPayload *)payload forType:(NSString *)type { // Process the received push @@ -100,14 +109,13 @@ import flutter_voip_push_notification /* <------ add this line */ ``` ## Usage + Add `flutter_voip_push_notification` as a [dependency in your pubspec.yaml file](https://flutter.io/using-packages/). ### Example - ```dart -import 'package:flutter/material.dart'; import 'package:flutter/material.dart'; import 'dart:async'; import 'package:flutter_voip_push_notification/flutter_voip_push_notification.dart'; @@ -137,7 +145,11 @@ class _MyAppState extends State { _voipPush.onTokenRefresh.listen(onToken); // do configure voip push - _voipPush.configure(onMessage: onMessage, onResume: onResume); + _voipPush.configure( + onMessage: onMessage, + onResume: onResume, + onInvalidToken: onInvalidToken, + ); } /// Called when the device token changes @@ -169,9 +181,20 @@ class _MyAppState extends State { return null; } - showLocalNotification(Map notification) { + /// Call to receive an PushKit invalid token + /// + /// [invalidToken] is the token that is no longer valid + /// Check out why a token could no longer be valid + /// https://stackoverflow.com/questions/46977380/voip-push-under-what-circumstances-does-didinvalidatepushtokenfortype-get-calle#47015401 + Future onInvalidToken(String invalidToken) { + // Tell the server to remove the invalid token + print("received on background invalidToken: $invalidToken"); + return null; + } + + Future showLocalNotification(Map notification) { String alert = notification["aps"]["alert"]; - _voipPush.presentLocalNotification(LocalNotification( + return _voipPush.presentLocalNotification(LocalNotification( alertBody: "Hello $alert", )); } diff --git a/example/ios/Runner/AppDelegate.swift b/example/ios/Runner/AppDelegate.swift index 885a501..b6ab59c 100644 --- a/example/ios/Runner/AppDelegate.swift +++ b/example/ios/Runner/AppDelegate.swift @@ -27,6 +27,11 @@ import flutter_voip_push_notification FlutterVoipPushNotificationPlugin.didReceiveIncomingPush(with: payload, forType: type.rawValue) } + // Handle invalidated push tokens + func pushRegistry(_ registry: PKPushRegistry, didInvalidatePushTokenFor type: PKPushType) { + FlutterVoipPushNotificationPlugin.didInvalidatePushToken(forType: type.rawValue) + } + // Handle incoming pushes func pushRegistry(_ registry: PKPushRegistry, didUpdate pushCredentials: PKPushCredentials, for type: PKPushType) { // Process the received push diff --git a/example/lib/main.dart b/example/lib/main.dart index f3c31ba..b153778 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -27,7 +27,11 @@ class _MyAppState extends State { _voipPush.onTokenRefresh.listen(onToken); // do configure voip push - _voipPush.configure(onMessage: onMessage, onResume: onResume); + _voipPush.configure( + onMessage: onMessage, + onResume: onResume, + onInvalidToken: onInvalidToken, + ); } /// Called when the device token changes @@ -59,9 +63,20 @@ class _MyAppState extends State { return null; } - showLocalNotification(Map notification) { + /// Call to receive an PushKit invalid token + /// + /// [invalidToken] is the token that is no longer valid + /// Check out why a token could no longer be valid + /// https://stackoverflow.com/questions/46977380/voip-push-under-what-circumstances-does-didinvalidatepushtokenfortype-get-calle#47015401 + Future onInvalidToken(String invalidToken) { + // Tell the server to remove the invalid token + print("received on background invalidToken: $invalidToken"); + return null; + } + + Future showLocalNotification(Map notification) { String alert = notification["aps"]["alert"]; - _voipPush.presentLocalNotification(LocalNotification( + return _voipPush.presentLocalNotification(LocalNotification( alertBody: "Hello $alert", )); } diff --git a/ios/Classes/FlutterVoipPushNotificationPlugin.h b/ios/Classes/FlutterVoipPushNotificationPlugin.h index 0960f34..ef211a2 100644 --- a/ios/Classes/FlutterVoipPushNotificationPlugin.h +++ b/ios/Classes/FlutterVoipPushNotificationPlugin.h @@ -4,4 +4,5 @@ @interface FlutterVoipPushNotificationPlugin : NSObject + (void)didUpdatePushCredentials:(PKPushCredentials *)credentials forType:(NSString *)type; + (void)didReceiveIncomingPushWithPayload:(PKPushPayload *)payload forType:(NSString *)type; ++ (void)didInvalidatePushTokenForType:(NSString *)type; @end diff --git a/ios/Classes/FlutterVoipPushNotificationPlugin.m b/ios/Classes/FlutterVoipPushNotificationPlugin.m index 0f95bdd..a01c539 100644 --- a/ios/Classes/FlutterVoipPushNotificationPlugin.m +++ b/ios/Classes/FlutterVoipPushNotificationPlugin.m @@ -1,6 +1,7 @@ #import "FlutterVoipPushNotificationPlugin.h" NSString *const FlutterVoipRemoteNotificationsRegistered = @"voipRemoteNotificationsRegistered"; +NSString *const FlutterVoipPushTokenInvalidated = @"voipPushTokenInvalidated"; NSString *const FlutterVoipLocalNotificationReceived = @"voipLocalNotificationReceived"; NSString *const FlutterVoipRemoteNotificationReceived = @"voipRemoteNotificationReceived"; @@ -42,6 +43,10 @@ - (instancetype)initWithRegistrar:(NSObject*)registrar selector:@selector(handleRemoteNotificationReceived:) name:FlutterVoipRemoteNotificationReceived object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(handlePushTokenInvalidated:) + name:FlutterVoipPushTokenInvalidated + object:nil]; } return self; } @@ -105,7 +110,9 @@ - (NSDictionary *)checkPermissions - (void)voipRegistration { +#ifdef DEBUG NSLog(@"[FlutterVoipPushNotificationPlugin] voipRegistration"); +#endif dispatch_queue_t mainQueue = dispatch_get_main_queue(); // Create a push registry object _voipRegistry = [[PKPushRegistry alloc] initWithQueue: mainQueue]; @@ -153,8 +160,9 @@ - (NSString*)getToken + (void)didUpdatePushCredentials:(PKPushCredentials *)credentials forType:(NSString *)type { +#ifdef DEBUG NSLog(@"[FlutterVoipPushNotificationPlugin] didUpdatePushCredentials credentials.token = %@, type = %@", credentials.token, type); - +#endif NSMutableString *hexString = [NSMutableString string]; NSUInteger voipTokenLength = credentials.token.length; const unsigned char *bytes = credentials.token.bytes; @@ -167,9 +175,20 @@ + (void)didUpdatePushCredentials:(PKPushCredentials *)credentials forType:(NSStr } ++ (void)didInvalidatePushTokenForType:(NSString *)type +{ +#ifdef DEBUG + NSLog(@"[FlutterVoipPushNotificationPlugin] didInvalidatePushTokenFor type = %@", type); +#endif + [[NSNotificationCenter defaultCenter] postNotificationName:FlutterVoipPushTokenInvalidated + object:self]; +} + + (void)didReceiveIncomingPushWithPayload:(PKPushPayload *)payload forType:(NSString *)type { +#ifdef DEBUG NSLog(@"[FlutterVoipPushNotificationPlugin] didReceiveIncomingPushWithPayload payload.dictionaryPayload = %@, type = %@", payload.dictionaryPayload, type); +#endif [[NSNotificationCenter defaultCenter] postNotificationName:FlutterVoipRemoteNotificationReceived object:self userInfo:payload.dictionaryPayload]; @@ -177,7 +196,9 @@ + (void)didReceiveIncomingPushWithPayload:(PKPushPayload *)payload forType:(NSSt - (void)handleRemoteNotificationsRegistered:(NSNotification *)notification { +#ifdef DEBUG NSLog(@"[FlutterVoipPushNotificationPlugin] handleRemoteNotificationsRegistered notification.userInfo = %@", notification.userInfo); +#endif [_channel invokeMethod:@"onToken" arguments:notification.userInfo]; } @@ -205,4 +226,12 @@ - (void)handleRemoteNotificationReceived:(NSNotification *)notification } } +- (void)handlePushTokenInvalidated:(NSNotification *)notification +{ +#ifdef DEBUG + NSLog(@"[FlutterVoipPushNotificationPlugin] handlePushTokenInvalidated notification.userInfo = %@", notification.userInfo); +#endif + [_channel invokeMethod:@"onTokenInvalidated" arguments:@{@"deviceToken": [self getToken]}]; +} + @end diff --git a/lib/flutter_voip_push_notification.dart b/lib/flutter_voip_push_notification.dart index a12c6c4..b9846e0 100644 --- a/lib/flutter_voip_push_notification.dart +++ b/lib/flutter_voip_push_notification.dart @@ -12,6 +12,12 @@ import 'package:flutter/widgets.dart'; typedef Future MessageHandler( bool isLocal, Map notification); +/// Handler for invalid PushKit tokens +/// +/// [invalidToken] is the token that has been invalidated and should be removed from your server +/// https://stackoverflow.com/questions/46977380/voip-push-under-what-circumstances-does-didinvalidatepushtokenfortype-get-calle#47015401 +typedef Future InvalidTokenHandler(String invalidToken); + class NotificationSettings { const NotificationSettings({ this.sound = true, @@ -92,7 +98,7 @@ class FlutterVoipPushNotification { String _token; MessageHandler _onMessage; MessageHandler _onResume; - + InvalidTokenHandler _onInvalidToken; final StreamController _tokenStreamController = StreamController.broadcast(); @@ -106,9 +112,11 @@ class FlutterVoipPushNotification { void configure({ MessageHandler onMessage, MessageHandler onResume, + InvalidTokenHandler onInvalidToken, }) { _onMessage = onMessage; _onResume = onResume; + _onInvalidToken = onInvalidToken; _channel.setMethodCallHandler(_handleMethod); //_channel.invokeMethod('configure'); } @@ -120,12 +128,19 @@ class FlutterVoipPushNotification { _token = map["deviceToken"]; _tokenStreamController.add(_token); return null; + case "onTokenInvalidated": + final String invalidToken = map["deviceToken"]; + return _onInvalidToken?.call(invalidToken); case "onMessage": - return _onMessage( - map["local"], map["notification"].cast()); + final bool isLocal = map["local"]; + final Map notification = + map["notification"].cast(); + return _onMessage?.call(isLocal, notification); case "onResume": - return _onResume( - map["local"], map["notification"].cast()); + final bool isLocal = map["local"]; + final Map notification = + map["notification"].cast(); + return _onResume?.call(isLocal, notification); default: throw UnsupportedError("Unrecognized JSON message"); } @@ -138,16 +153,20 @@ class FlutterVoipPushNotification { /// Prompts the user for notification permissions the first time /// it is called. - Future requestNotificationPermissions( - [NotificationSettings iosSettings = - const NotificationSettings()]) async { + Future requestNotificationPermissions([ + NotificationSettings iosSettings = const NotificationSettings(), + ]) async { _channel.invokeMethod( - 'requestNotificationPermissions', iosSettings.toMap()); + 'requestNotificationPermissions', + iosSettings.toMap(), + ); } /// Schedules the local [notification] for immediate presentation. Future presentLocalNotification(LocalNotification notification) async { await _channel.invokeMethod( - 'presentLocalNotification', notification.toMap()); + 'presentLocalNotification', + notification.toMap(), + ); } } From dfbfade9b342216b886722be2bf8a59b3288331f Mon Sep 17 00:00:00 2001 From: Victor Uvarov Date: Sat, 16 Jan 2021 13:04:13 -0800 Subject: [PATCH 2/2] update change log --- CHANGELOG.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c578a2..bea62d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,17 @@ +# Flutter Voip Push Notification +## 0.0.4 + +- Add support for invalidating tokens ## 0.0.3 -* Rename IOSNotificationSettings to NotificationSettings and refactored example + +- Rename IOSNotificationSettings to NotificationSettings and refactored example ## 0.0.2 -* Add support for showing local notification, uses app delegate as push delegate + +- Add support for showing local notification, uses app delegate as push delegate ## 0.0.1 -* Initial release. \ No newline at end of file +- Initial release.