def _ping_client(client): client_type = client.client_type if client_type in ClientType.FCM_CLIENTS: from models.notifications.ping import PingNotification notification = PingNotification() from models.notifications.requests.fcm_request import FCMRequest fcm_request = FCMRequest(firebase_app, notification, tokens=[client.messaging_id]) logging.info('Ping - {}'.format(str(fcm_request))) batch_response = fcm_request.send() if batch_response.failure_count > 0: response = batch_response.responses[0] logging.info('Error Sending Ping - {}'.format( response.exception)) return False else: logging.info('Ping Sent') elif client_type == ClientType.OS_ANDROID: # Send old notifications to Android from notifications.ping import PingNotification notification = PingNotification() notification.send({client_type: [client.messaging_id]}) else: raise Exception( 'Unsupported FCM client type: {}'.format(client_type)) return True
def test_fcm_message_empty(self): request = FCMRequest(self.app, notification=MockNotification(), tokens=['abc']) message = request._fcm_message() self.assertIsNotNone(message) self.assertIsNotNone(message.data) self.assertIsNone(message.notification) self.assertIsNone(message.android) self.assertTrue(isinstance(message.apns, messaging.APNSConfig)) self.assertIsNone(message.webpush) self.assertEqual(message.tokens, ['abc'])
def test_send_failed(self): batch_response = messaging.BatchResponse( [messaging.SendResponse(None, 'a')]) request = FCMRequest(app=self.app, notification=MockNotification(), tokens=['abc', 'def']) with patch.object( messaging, 'send_multicast', return_value=batch_response) as mock_send, patch.object( request, 'defer_track_notification') as mock_track: response = request.send() mock_send.assert_called_once() mock_track.assert_not_called() self.assertEqual(response, batch_response)
def test_fcm_message_data_payload_default(self): platform_config = PlatformConfig(priority=PlatformPriority.HIGH, collapse_key='collapse_key') request = FCMRequest(self.app, notification=MockNotification(), tokens=['abc']) message = request._fcm_message() self.assertIsNotNone(message) self.assertEqual(message.data, {'notification_type': 'verification'}) self.assertIsNone(message.notification) self.assertIsNone(message.android) self.assertTrue(isinstance(message.apns, messaging.APNSConfig)) self.assertIsNone(message.webpush) self.assertEqual(message.tokens, ['abc'])
def test_fcm_message_platform_config(self): platform_config = PlatformConfig(priority=PlatformPriority.HIGH, collapse_key='collapse_key') request = FCMRequest( self.app, notification=MockNotification(platform_config=platform_config), tokens=['abc']) message = request._fcm_message() self.assertIsNotNone(message) self.assertIsNotNone(message.data) self.assertIsNone(message.notification) self.assertTrue(isinstance(message.android, messaging.AndroidConfig)) self.assertTrue(isinstance(message.apns, messaging.APNSConfig)) self.assertTrue(isinstance(message.webpush, messaging.WebpushConfig)) self.assertEqual(message.tokens, ['abc'])
def test_str(self): request = FCMRequest(self.app, notification=MockNotification(), tokens=['abc']) self.assertEqual( "FCMRequest(tokens=['abc'], notification=MockNotification())", str(request))
def test_fcm_message_notification(self): platform_config = PlatformConfig(priority=PlatformPriority.HIGH, collapse_key='collapse_key') request = FCMRequest(self.app, notification=MockNotification( fcm_notification=messaging.Notification( title='Title', body='Some body message')), tokens=['abc']) message = request._fcm_message() self.assertIsNotNone(message) self.assertIsNotNone(message.data) self.assertTrue( isinstance(message.notification, messaging.Notification)) self.assertIsNone(message.android) self.assertTrue(isinstance(message.apns, messaging.APNSConfig)) self.assertIsNone(message.webpush) self.assertEqual(message.tokens, ['abc'])
def test_fcm_message_apns_content_available(self): request = FCMRequest(self.app, notification=MockNotification(), tokens=['abc']) message = request._fcm_message() self.assertIsNotNone(message) self.assertIsNotNone(message.data) self.assertIsNone(message.notification) self.assertIsNone(message.android) self.assertTrue(isinstance(message.apns, messaging.APNSConfig)) self.assertTrue(isinstance(message.apns.payload, messaging.APNSPayload)) self.assertTrue(isinstance(message.apns.payload.aps, messaging.Aps)) self.assertIsNone(message.apns.payload.aps.sound) self.assertTrue(message.apns.payload.aps.content_available) self.assertIsNone(message.webpush) self.assertEqual(message.tokens, ['abc'])
def test_init_delivery_too_many_tokens(self): with self.assertRaises(ValueError) as ex: FCMRequest(self.app, notification=MockNotification(), tokens=['a' for i in range(MAXIMUM_TOKENS + 1)]) self.assertEqual( str(ex.exception), 'FCMRequest tokens must contain less than {} tokens'.format( MAXIMUM_TOKENS))
def test_fcm_message_data_payload(self): platform_config = PlatformConfig(priority=PlatformPriority.HIGH, collapse_key='collapse_key') request = FCMRequest(self.app, notification=MockNotification( data_payload={'some_data': 'some test data'}), tokens=['abc']) message = request._fcm_message() self.assertIsNotNone(message) self.assertEqual(message.data, { 'notification_type': 'verification', 'some_data': 'some test data' }) self.assertIsNone(message.notification) self.assertIsNone(message.android) self.assertIsNone(message.apns) self.assertIsNone(message.webpush) self.assertEqual(message.tokens, ['abc'])
def test_fcm_message_platform_config_override(self): platform_config = PlatformConfig(priority=PlatformPriority.HIGH, collapse_key='collapse_key') apns_config = messaging.APNSConfig( headers={'apns-collapse-id': 'ios_collapse_key'}) request = FCMRequest(self.app, notification=MockNotification( platform_config=platform_config, apns_config=apns_config), tokens=['abc']) message = request._fcm_message() self.assertIsNotNone(message) self.assertIsNotNone(message.data) self.assertIsNone(message.notification) self.assertTrue(isinstance(message.android, messaging.AndroidConfig)) self.assertTrue(isinstance(message.apns, messaging.APNSConfig)) self.assertEqual(message.apns.headers, {'apns-collapse-id': 'ios_collapse_key'}) self.assertTrue(isinstance(message.webpush, messaging.WebpushConfig)) self.assertEqual(message.webpush.headers, { 'Topic': 'collapse_key', 'Urgency': 'high' }) self.assertEqual(message.tokens, ['abc'])
def _send_fcm(cls, clients, notification, backoff_iteration=0): # Only send to FCM clients if notifications are enabled if not cls._notifications_enabled(): return 1 # Only allow so many retries backoff_time = 2**backoff_iteration if backoff_time > MAXIMUM_BACKOFF: return 2 # Make sure we're only sending to FCM clients clients = [ client for client in clients if client.client_type in ClientType.FCM_CLIENTS ] from models.notifications.requests.fcm_request import FCMRequest, MAXIMUM_TOKENS # We can only send to so many FCM clients at a time - send to our clients across several requests for subclients in [ clients[i:i + MAXIMUM_TOKENS] for i in range(0, len(clients), MAXIMUM_TOKENS) ]: fcm_request = FCMRequest( firebase_app, notification, tokens=[client.messaging_id for client in subclients]) logging.info(str(fcm_request)) batch_response = fcm_request.send() retry_clients = [] # Handle our failed sends - this might include logging/alerting, removing old clients, or retrying sends from firebase_admin.exceptions import InvalidArgumentError, InternalError, UnavailableError from firebase_admin.messaging import QuotaExceededError, SenderIdMismatchError, ThirdPartyAuthError, UnregisteredError for index, response in enumerate([ response for response in batch_response.responses if not response.success ]): client = subclients[index] if isinstance(response.exception, UnregisteredError): logging.info( 'Deleting unregistered client with ID: {}'.format( client.messaging_id)) MobileClient.delete_for_messaging_id(client.messaging_id) elif isinstance(response.exception, SenderIdMismatchError): logging.info( 'Deleting mismatched client with ID: {}'.format( client.messaging_id)) MobileClient.delete_for_messaging_id(client.messaging_id) elif isinstance(response.exception, QuotaExceededError): logging.error('Qutoa exceeded - retrying client...') retry_clients.append(client) elif isinstance(response.exception, ThirdPartyAuthError): logging.critical( 'Third party error sending to FCM - {}'.format( response.exception)) elif isinstance(response.exception, InvalidArgumentError): logging.critical( 'Invalid argument when sending to FCM - {}'.format( response.exception)) elif isinstance(response.exception, InternalError): logging.error('Interal FCM error - retrying client...') retry_clients.append(client) elif isinstance(response.exception, UnavailableError): logging.error('FCM unavailable - retrying client...') retry_clients.append(client) else: debug_string = cls._debug_string(response.exception) logging.error('Unhandled FCM error for {} - {}'.format( client.messaging_id, debug_string)) if retry_clients: # Try again, with exponential backoff deferred.defer(cls._send_fcm, retry_clients, notification, backoff_iteration + 1, _countdown=backoff_time, _target='backend-tasks', _queue='push-notifications', _url='/_ah/queue/deferred_notification_send') return 0
def test_init_delivery_none(self): with self.assertRaises(TypeError): FCMRequest(self.app, notification=MockNotification())
def test_init_app(self): FCMRequest(self.app, notification=MockNotification(), tokens=['abcd'])
def test_subclass(self): request = FCMRequest(self.app, MockNotification(), tokens=['abcd']) self.assertTrue(isinstance(request, Request))