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_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 _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