From 02d23b275937db23294f458e63f27132d98866d6 Mon Sep 17 00:00:00 2001 From: shankari Date: Fri, 4 Sep 2020 21:15:43 -0700 Subject: [PATCH] Support fcm tokens for iOS phones as well (#768) * Support fcm tokens for iOS phones as well By default, the FCM plugin generates FCM tokens for both android and iOS. I don't like this on principle because it makes it harder to migrate to a different push service later. So we typically configure the app to generate APNS tokens and map on the server side. But that now requires editing GoogleServices.plist (FCM is the default) and it is easy to forget to do that. However, others may also not have this concern, and it does simplify the server side logic significantly to not have to do the mapping. So I now support FCM on both platforms, and add a config option to specify whether we are using APNS or FCM for iOS. If we are using FCM, we can skip the entire mapping logic * Fix the default push configuration Otherwise the tests fail * Fix the sample push configuration in the tests as well I don't know why the test generates this file when we also copy it from `bin/deploy/push_conf.py` But not going to get into that complexity now. * Add a new test - Pass in the `apns` token type in one more location - Add a new test for the FCM token type to ensure that the mapping shortcut works properly --- bin/deploy/push_conf.py | 1 + conf/net/ext_service/push.json.sample | 3 +- .../push/notify_interface_impl/firebase.py | 5 ++++ emission/tests/netTests/TestPush.py | 29 +++++++++++++++++-- 4 files changed, 35 insertions(+), 3 deletions(-) diff --git a/bin/deploy/push_conf.py b/bin/deploy/push_conf.py index 313725691..88accddaa 100644 --- a/bin/deploy/push_conf.py +++ b/bin/deploy/push_conf.py @@ -16,6 +16,7 @@ data['provider'] = 'firebase' data['server_auth_token'] = 'firebase_api_key' data['app_package_name'] = 'edu.berkeley.eecs.embase' +data['ios_token_format'] = 'apns' f = open(real_path, "w") f.write(json.dumps(data)) f.close() diff --git a/conf/net/ext_service/push.json.sample b/conf/net/ext_service/push.json.sample index be12881bb..944f71e9a 100644 --- a/conf/net/ext_service/push.json.sample +++ b/conf/net/ext_service/push.json.sample @@ -1,5 +1,6 @@ { "provider": "firebase", "server_auth_token": "Get from firebase console", - "app_package_name": "full package name from config.xml. e.g. edu.berkeley.eecs.emission or edu.berkeley.eecs.embase. Defaults to edu.berkeley.eecs.embase" + "app_package_name": "full package name from config.xml. e.g. edu.berkeley.eecs.emission or edu.berkeley.eecs.embase. Defaults to edu.berkeley.eecs.embase", + "ios_token_format": "apns" } diff --git a/emission/net/ext_service/push/notify_interface_impl/firebase.py b/emission/net/ext_service/push/notify_interface_impl/firebase.py index 7ada42541..075f7805b 100644 --- a/emission/net/ext_service/push/notify_interface_impl/firebase.py +++ b/emission/net/ext_service/push/notify_interface_impl/firebase.py @@ -27,6 +27,7 @@ def __init__(self, push_config): else: logging.warning("No package name specified, defaulting to embase") self.app_package_name = "edu.berkeley.eecs.embase" + self.is_fcm_format = push_config["ios_token_format"] == "fcm" def get_and_invalidate_entries(self): # Need to figure out how to do this on firebase @@ -39,6 +40,10 @@ def print_dev_flag_warning(): logging.warning("https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages") def map_existing_fcm_tokens(self, token_map): + if self.is_fcm_format: + logging.info("iOS tokens are already in the FCM format, no mapping required") + return ({"ios": token_map["ios"], + "android": token_map["android"]}, []) # android tokens never need to be mapped, so let's just not even check them mapped_token_map = {"ios": [], "android": token_map["android"]} diff --git a/emission/tests/netTests/TestPush.py b/emission/tests/netTests/TestPush.py index 73da704ad..e0a756e22 100644 --- a/emission/tests/netTests/TestPush.py +++ b/emission/tests/netTests/TestPush.py @@ -47,7 +47,8 @@ def setUp(self): with open(self.push_conf_path, "w") as fd: fd.write(json.dumps({ "provider": "firebase", - "server_auth_token": "firebase_api_key" + "server_auth_token": "firebase_api_key", + "ios_token_format": "apns" })) logging.debug("Finished setting up %s" % self.push_conf_path) with open(self.push_conf_path) as fd: @@ -74,6 +75,7 @@ def testGetDefaultInterface(self): def testMappingQueries(self): import emission.net.ext_service.push.notify_queries as pnq import emission.core.wrapper.user as ecwu + import emission.core.get_database as edb self.test_email_1 = "test_push_1" self.test_email_2 = "test_push_2" @@ -108,7 +110,7 @@ def testFcmMapping(self): logging.debug("test token map = %s" % self.test_token_map) try: - fcm_instance = pnif.get_interface({"server_auth_token": "firebase_api_key"}) + fcm_instance = pnif.get_interface({"server_auth_token": "firebase_api_key", "ios_token_format": "apns"}) (mapped_token_map, unmapped_token_list) = fcm_instance.map_existing_fcm_tokens(self.test_token_map) # At this point, there is nothing in the database, so no iOS tokens will be mapped self.assertEqual(len(mapped_token_map["ios"]), 0) @@ -152,6 +154,29 @@ def testFcmMapping(self): finally: # Delete everything from the database edb.get_push_token_mapping_db().delete_many({}) + + def testFcmNoMapping(self): + import emission.net.ext_service.push.notify_interface_impl.firebase as pnif + import emission.core.get_database as edb + + self.test_token_list_ios = ["device_token_ios_%s" % i for i in range(10)] + self.test_token_list_android = ["device_token_android_%s" % i for i in range(10)] + self.test_token_map = {"ios": self.test_token_list_ios, + "android": self.test_token_list_android} + logging.debug("test token map = %s" % self.test_token_map) + + fcm_instance = pnif.get_interface({"server_auth_token": "firebase_api_key", "ios_token_format": "fcm"}) + (mapped_token_map, unmapped_token_list) = fcm_instance.map_existing_fcm_tokens(self.test_token_map) + # These are assumed to be FCM tokens directly, so no mapping required + self.assertEqual(len(mapped_token_map["ios"]), 10) + # android tokens should not be mapped, so they will be returned as-is + self.assertEqual(len(mapped_token_map["android"]), 10) + + # no tokens will be returned as needing a mapping + self.assertEqual(len(unmapped_token_list), 0) + + # and there will be no entries in the token mapping database + self.assertEqual(edb.get_push_token_mapping_db().count_documents({}), 0) if __name__ == '__main__': import emission.tests.common as etc