From b5638952bbc40260e9beae29733db68fe4a88d35 Mon Sep 17 00:00:00 2001 From: Jonathan Edey Date: Tue, 18 Mar 2025 10:15:53 -0400 Subject: [PATCH 1/3] feat(fcm): Support `proxy` field in FCM `AndroidNotification` --- firebase_admin/_messaging_encoder.py | 11 ++++++++++- firebase_admin/_messaging_utils.py | 6 +++++- integration/test_messaging.py | 3 ++- tests/test_messaging.py | 16 ++++++++++++++++ 4 files changed, 33 insertions(+), 3 deletions(-) diff --git a/firebase_admin/_messaging_encoder.py b/firebase_admin/_messaging_encoder.py index 85072b597..8ece9244f 100644 --- a/firebase_admin/_messaging_encoder.py +++ b/firebase_admin/_messaging_encoder.py @@ -319,7 +319,9 @@ def encode_android_notification(cls, notification): 'visibility': _Validators.check_string( 'AndroidNotification.visibility', notification.visibility, non_empty=True), 'notification_count': _Validators.check_number( - 'AndroidNotification.notification_count', notification.notification_count) + 'AndroidNotification.notification_count', notification.notification_count), + 'proxy': _Validators.check_string( + 'AndroidNotification.proxy', notification.proxy, non_empty=True) } result = cls.remove_null_values(result) color = result.get('color') @@ -363,6 +365,13 @@ def encode_android_notification(cls, notification): 'AndroidNotification.vibrate_timings_millis', msec) vibrate_timing_strings.append(formated_string) result['vibrate_timings'] = vibrate_timing_strings + + proxy = result.get('proxy') + if proxy: + if proxy not in ('allow', 'deny', 'if_priority_lowered'): + raise ValueError( + 'AndroidNotification.proxy must be "allow", "deny" or "if_priority_lowered".') + result['proxy'] = proxy.upper() return result @classmethod diff --git a/firebase_admin/_messaging_utils.py b/firebase_admin/_messaging_utils.py index 29b8276bc..657187774 100644 --- a/firebase_admin/_messaging_utils.py +++ b/firebase_admin/_messaging_utils.py @@ -145,6 +145,8 @@ class AndroidNotification: want the count here to represent the number of total new messages. If zero or unspecified, systems that support badging use the default, which is to increment a number displayed on the long-press menu each time a new notification arrives. + proxy: Sets if the notification may be proxied. Must be one of ``allow``, ``deny``, or + ``if_priority_lowered``. If unspecified, defaults to ``if_priority_lowered``. """ @@ -154,7 +156,8 @@ def __init__(self, title=None, body=None, icon=None, color=None, sound=None, tag title_loc_args=None, channel_id=None, image=None, ticker=None, sticky=None, event_timestamp=None, local_only=None, priority=None, vibrate_timings_millis=None, default_vibrate_timings=None, default_sound=None, light_settings=None, - default_light_settings=None, visibility=None, notification_count=None): + default_light_settings=None, visibility=None, notification_count=None, + proxy=None): self.title = title self.body = body self.icon = icon @@ -180,6 +183,7 @@ def __init__(self, title=None, body=None, icon=None, color=None, sound=None, tag self.default_light_settings = default_light_settings self.visibility = visibility self.notification_count = notification_count + self.proxy = proxy class LightSettings: diff --git a/integration/test_messaging.py b/integration/test_messaging.py index 50b4ae3a4..4c1d7d0dc 100644 --- a/integration/test_messaging.py +++ b/integration/test_messaging.py @@ -55,7 +55,8 @@ def test_send(): light_off_duration_millis=200, light_on_duration_millis=300 ), - notification_count=1 + notification_count=1, + proxy='if_priority_lowered', ) ), apns=messaging.APNSConfig(payload=messaging.APNSPayload( diff --git a/tests/test_messaging.py b/tests/test_messaging.py index edb36f53a..18134425b 100644 --- a/tests/test_messaging.py +++ b/tests/test_messaging.py @@ -534,6 +534,20 @@ def test_invalid_visibility(self, visibility): else: expected = 'AndroidNotification.visibility must be a non-empty string.' assert str(excinfo.value) == expected + + @pytest.mark.parametrize('proxy', NON_STRING_ARGS + ['foo']) + def test_invalid_proxy(self, proxy): + notification = messaging.AndroidNotification(proxy=proxy) + excinfo = self._check_notification(notification) + if isinstance(proxy, str): + if not proxy: + expected = 'AndroidNotification.proxy must be a non-empty string.' + else: + expected = ('AndroidNotification.proxy must be "allow", "deny" or' + ' "if_priority_lowered".') + else: + expected = 'AndroidNotification.proxy must be a non-empty string.' + assert str(excinfo.value) == expected @pytest.mark.parametrize('vibrate_timings', ['', 1, True, 'msec', ['500', 500], [0, 'abc']]) def test_invalid_vibrate_timings_millis(self, vibrate_timings): @@ -580,6 +594,7 @@ def test_android_notification(self): light_off_duration_millis=300, ), default_light_settings=False, visibility='public', notification_count=1, + proxy='if_priority_lowered', ) ) ) @@ -620,6 +635,7 @@ def test_android_notification(self): 'default_light_settings': False, 'visibility': 'PUBLIC', 'notification_count': 1, + 'proxy': 'IF_PRIORITY_LOWERED' }, }, } From 7d6ea6a70e5504d4f01d6e7c31bc569b564bf30d Mon Sep 17 00:00:00 2001 From: Jonathan Edey Date: Tue, 18 Mar 2025 10:23:49 -0400 Subject: [PATCH 2/3] fix lint --- firebase_admin/_messaging_encoder.py | 2 +- tests/test_messaging.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/firebase_admin/_messaging_encoder.py b/firebase_admin/_messaging_encoder.py index 8ece9244f..d7f233289 100644 --- a/firebase_admin/_messaging_encoder.py +++ b/firebase_admin/_messaging_encoder.py @@ -365,7 +365,7 @@ def encode_android_notification(cls, notification): 'AndroidNotification.vibrate_timings_millis', msec) vibrate_timing_strings.append(formated_string) result['vibrate_timings'] = vibrate_timing_strings - + proxy = result.get('proxy') if proxy: if proxy not in ('allow', 'deny', 'if_priority_lowered'): diff --git a/tests/test_messaging.py b/tests/test_messaging.py index 18134425b..b7b5c69ba 100644 --- a/tests/test_messaging.py +++ b/tests/test_messaging.py @@ -534,7 +534,7 @@ def test_invalid_visibility(self, visibility): else: expected = 'AndroidNotification.visibility must be a non-empty string.' assert str(excinfo.value) == expected - + @pytest.mark.parametrize('proxy', NON_STRING_ARGS + ['foo']) def test_invalid_proxy(self, proxy): notification = messaging.AndroidNotification(proxy=proxy) From 1023f48af83b90ae06c0300ea77924b51a81f887 Mon Sep 17 00:00:00 2001 From: Jonathan Edey Date: Wed, 19 Mar 2025 15:10:00 -0400 Subject: [PATCH 3/3] fix: Update `proxy` and `visibility` doc string with TW suggestion --- firebase_admin/_messaging_utils.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/firebase_admin/_messaging_utils.py b/firebase_admin/_messaging_utils.py index 657187774..ae1f5cc56 100644 --- a/firebase_admin/_messaging_utils.py +++ b/firebase_admin/_messaging_utils.py @@ -137,7 +137,8 @@ class AndroidNotification: If ``default_light_settings`` is set to ``True`` and ``light_settings`` is also set, the user-specified ``light_settings`` is used instead of the default value. visibility: Sets the visibility of the notification. Must be either ``private``, ``public``, - or ``secret``. If unspecified, default to ``private``. + or ``secret``. If unspecified, it remains undefined in the Admin SDK, and defers to + the FCM backend's default mapping. notification_count: Sets the number of items this notification represents. May be displayed as a badge count for Launchers that support badging. See ``NotificationBadge`` https://developer.android.com/training/notify-user/badges. For example, this might be @@ -146,7 +147,8 @@ class AndroidNotification: unspecified, systems that support badging use the default, which is to increment a number displayed on the long-press menu each time a new notification arrives. proxy: Sets if the notification may be proxied. Must be one of ``allow``, ``deny``, or - ``if_priority_lowered``. If unspecified, defaults to ``if_priority_lowered``. + ``if_priority_lowered``. If unspecified, it remains undefined in the Admin SDK, and + defers to the FCM backend's default mapping. """