Skip to content

Additional fields in the messaging.WebpushNotification type #186

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Aug 13, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
- [added] The `db.reference()` method now optionally takes a `url`
parameter. This can be used to access multiple Firebase Databases
in the same project more easily.
- [added] The `messaging.WebpushNotification` type now supports
additional parameters.

# v2.12.0

Expand Down
110 changes: 109 additions & 1 deletion firebase_admin/messaging.py
Original file line number Diff line number Diff line change
Expand Up @@ -283,21 +283,76 @@ def __init__(self, headers=None, data=None, notification=None):
self.notification = notification


class WebpushNotificationAction(object):
"""An action available to the users when the notification is presented.

Args:
action: Action string.
title: Title string.
icon: Icon URL for the action (optional).
"""

def __init__(self, action, title, icon=None):
self.action = action
self.title = title
self.icon = icon


class WebpushNotification(object):
"""Webpush-specific notification parameters.

Refer to the `Notification Reference`_ for more information.

Args:
title: Title of the notification (optional). If specified, overrides the title set via
``messaging.Notification``.
body: Body of the notification (optional). If specified, overrides the body set via
``messaging.Notification``.
icon: Icon URL of the notification (optional).
actions: A list of ``messaging.WebpushNotificationAction`` instances (optional).
badge: URL of the image used to represent the notification when there is
not enough space to display the notification itself (optional).
data: Any arbitrary JSON data that should be associated with the notification (optional).
direction: The direction in which to display the notification (optional). Must be either
'auto', 'ltr' or 'rtl'.
image: The URL of an image to be displayed in the notification (optional).
language: Notification language (optional).
renotify: A boolean indicating whether the user should be notified after a new
notification replaces an old one (optional).
require_interaction: A boolean indicating whether a notification should remain active
until the user clicks or dismisses it, rather than closing automatically (optional).
silent: True to indicate that the notification should be silent (optional).
tag: An identifying tag on the notification (optional).
timestamp_millis: A timestamp value in milliseconds on the notification (optional).
vibrate: A vibration pattern for the device's vibration hardware to emit when the
notification fires (optional). THe pattern is specified as an integer array.
custom_data: A dict of custom key-value pairs to be included in the notification
(optional)

.. _Notification Reference: https://developer.mozilla.org/en-US/docs/Web/API\
/notification/Notification
"""

def __init__(self, title=None, body=None, icon=None):
def __init__(self, title=None, body=None, icon=None, actions=None, badge=None, data=None,
direction=None, image=None, language=None, renotify=None,
require_interaction=None, silent=None, tag=None, timestamp_millis=None,
vibrate=None, custom_data=None):
self.title = title
self.body = body
self.icon = icon
self.actions = actions
self.badge = badge
self.data = data
self.direction = direction
self.image = image
self.language = language
self.renotify = renotify
self.require_interaction = require_interaction
self.silent = silent
self.tag = tag
self.timestamp_millis = timestamp_millis
self.vibrate = vibrate
self.custom_data = custom_data


class APNSConfig(object):
Expand Down Expand Up @@ -579,15 +634,68 @@ def encode_webpush_notification(cls, notification):
raise ValueError('WebpushConfig.notification must be an instance of '
'WebpushNotification class.')
result = {
'actions': cls.encode_webpush_notification_actions(notification.actions),
'badge': _Validators.check_string(
'WebpushNotification.badge', notification.badge),
'body': _Validators.check_string(
'WebpushNotification.body', notification.body),
'data': notification.data,
'dir': _Validators.check_string(
'WebpushNotification.direction', notification.direction),
'icon': _Validators.check_string(
'WebpushNotification.icon', notification.icon),
'image': _Validators.check_string(
'WebpushNotification.image', notification.image),
'lang': _Validators.check_string(
'WebpushNotification.language', notification.language),
'renotify': notification.renotify,
'requireInteraction': notification.require_interaction,
'silent': notification.silent,
'tag': _Validators.check_string(
'WebpushNotification.tag', notification.tag),
'timestamp': _Validators.check_number(
'WebpushNotification.timestamp_millis', notification.timestamp_millis),
'title': _Validators.check_string(
'WebpushNotification.title', notification.title),
'vibrate': notification.vibrate,
}
direction = result.get('dir')
if direction and direction not in ('auto', 'ltr', 'rtl'):
raise ValueError('WebpushNotification.direction must be "auto", "ltr" or "rtl".')
if notification.custom_data is not None:
if not isinstance(notification.custom_data, dict):
raise ValueError('WebpushNotification.custom_data must be a dict.')
for key, value in notification.custom_data.items():
if key in result:
raise ValueError(
'Multiple specifications for {0} in WebpushNotification.'.format(key))
result[key] = value
return cls.remove_null_values(result)

@classmethod
def encode_webpush_notification_actions(cls, actions):
"""Encodes a list of WebpushNotificationActions into JSON."""
if actions is None:
return None
if not isinstance(actions, list):
raise ValueError('WebpushConfig.notification.actions must be a list of '
'WebpushNotificationAction instances.')
results = []
for action in actions:
if not isinstance(action, WebpushNotificationAction):
raise ValueError('WebpushConfig.notification.actions must be a list of '
'WebpushNotificationAction instances.')
result = {
'action': _Validators.check_string(
'WebpushNotificationAction.action', action.action),
'title': _Validators.check_string(
'WebpushNotificationAction.title', action.title),
'icon': _Validators.check_string(
'WebpushNotificationAction.icon', action.icon),
}
results.append(cls.remove_null_values(result))
return results

@classmethod
def encode_apns(cls, apns):
"""Encodes an APNSConfig instance into JSON."""
Expand Down
157 changes: 153 additions & 4 deletions tests/test_messaging.py
Original file line number Diff line number Diff line change
Expand Up @@ -447,25 +447,174 @@ def test_invalid_icon(self, data):
excinfo = self._check_notification(notification)
assert str(excinfo.value) == 'WebpushNotification.icon must be a string.'

@pytest.mark.parametrize('data', NON_STRING_ARGS)
def test_invalid_badge(self, data):
notification = messaging.WebpushNotification(badge=data)
excinfo = self._check_notification(notification)
assert str(excinfo.value) == 'WebpushNotification.badge must be a string.'

@pytest.mark.parametrize('data', NON_STRING_ARGS + ['foo'])
def test_invalid_direction(self, data):
notification = messaging.WebpushNotification(direction=data)
excinfo = self._check_notification(notification)
if isinstance(data, six.string_types):
assert str(excinfo.value) == ('WebpushNotification.direction must be "auto", '
'"ltr" or "rtl".')
else:
assert str(excinfo.value) == 'WebpushNotification.direction must be a string.'

@pytest.mark.parametrize('data', NON_STRING_ARGS)
def test_invalid_image(self, data):
notification = messaging.WebpushNotification(image=data)
excinfo = self._check_notification(notification)
assert str(excinfo.value) == 'WebpushNotification.image must be a string.'

@pytest.mark.parametrize('data', NON_STRING_ARGS)
def test_invalid_language(self, data):
notification = messaging.WebpushNotification(language=data)
excinfo = self._check_notification(notification)
assert str(excinfo.value) == 'WebpushNotification.language must be a string.'

@pytest.mark.parametrize('data', NON_STRING_ARGS)
def test_invalid_tag(self, data):
notification = messaging.WebpushNotification(tag=data)
excinfo = self._check_notification(notification)
assert str(excinfo.value) == 'WebpushNotification.tag must be a string.'

@pytest.mark.parametrize('data', ['', 'foo', list(), tuple(), dict()])
def test_invalid_timestamp(self, data):
notification = messaging.WebpushNotification(timestamp_millis=data)
excinfo = self._check_notification(notification)
assert str(excinfo.value) == 'WebpushNotification.timestamp_millis must be a number.'

@pytest.mark.parametrize('data', ['', list(), tuple(), True, False, 1, 0])
def test_invalid_custom_data(self, data):
notification = messaging.WebpushNotification(custom_data=data)
excinfo = self._check_notification(notification)
assert str(excinfo.value) == 'WebpushNotification.custom_data must be a dict.'

@pytest.mark.parametrize('data', ['', dict(), tuple(), True, False, 1, 0, [1, 2]])
def test_invalid_actions(self, data):
notification = messaging.WebpushNotification(actions=data)
excinfo = self._check_notification(notification)
assert str(excinfo.value) == ('WebpushConfig.notification.actions must be a list of '
'WebpushNotificationAction instances.')

def test_webpush_notification(self):
msg = messaging.Message(
topic='topic',
webpush=messaging.WebpushConfig(
notification=messaging.WebpushNotification(title='t', body='b', icon='i')
notification=messaging.WebpushNotification(
badge='badge',
body='body',
data={'foo': 'bar'},
icon='icon',
image='image',
language='language',
renotify=True,
require_interaction=True,
silent=True,
tag='tag',
timestamp_millis=100,
title='title',
vibrate=[100, 200, 100],
custom_data={'k1': 'v1', 'k2': 'v2'},
),
)
)
expected = {
'topic': 'topic',
'webpush': {
'notification': {
'title': 't',
'body': 'b',
'icon': 'i',
'badge': 'badge',
'body': 'body',
'data': {'foo': 'bar'},
'icon': 'icon',
'image': 'image',
'lang': 'language',
'renotify': True,
'requireInteraction': True,
'silent': True,
'tag': 'tag',
'timestamp': 100,
'vibrate': [100, 200, 100],
'title': 'title',
'k1': 'v1',
'k2': 'v2',
},
},
}
check_encoding(msg, expected)

def test_multiple_field_specifications(self):
notification = messaging.WebpushNotification(
badge='badge',
custom_data={'badge': 'other badge'},
)
excinfo = self._check_notification(notification)
expected = 'Multiple specifications for badge in WebpushNotification.'
assert str(excinfo.value) == expected

def test_webpush_notification_action(self):
msg = messaging.Message(
topic='topic',
webpush=messaging.WebpushConfig(
notification=messaging.WebpushNotification(
actions=[
messaging.WebpushNotificationAction(
action='a1',
title='t1',
),
messaging.WebpushNotificationAction(
action='a2',
title='t2',
icon='i2',
),
],
),
)
)
expected = {
'topic': 'topic',
'webpush': {
'notification': {
'actions': [
{
'action': 'a1',
'title': 't1',
},
{
'action': 'a2',
'title': 't2',
'icon': 'i2',
},
],
},
},
}
check_encoding(msg, expected)

@pytest.mark.parametrize('data', NON_STRING_ARGS)
def test_invalid_action_name(self, data):
action = messaging.WebpushNotificationAction(action=data, title='title')
notification = messaging.WebpushNotification(actions=[action])
excinfo = self._check_notification(notification)
assert str(excinfo.value) == 'WebpushNotificationAction.action must be a string.'

@pytest.mark.parametrize('data', NON_STRING_ARGS)
def test_invalid_action_title(self, data):
action = messaging.WebpushNotificationAction(action='action', title=data)
notification = messaging.WebpushNotification(actions=[action])
excinfo = self._check_notification(notification)
assert str(excinfo.value) == 'WebpushNotificationAction.title must be a string.'

@pytest.mark.parametrize('data', NON_STRING_ARGS)
def test_invalid_action_icon(self, data):
action = messaging.WebpushNotificationAction(action='action', title='title', icon=data)
notification = messaging.WebpushNotification(actions=[action])
excinfo = self._check_notification(notification)
assert str(excinfo.value) == 'WebpushNotificationAction.icon must be a string.'


class TestAPNSConfigEncoder(object):

Expand Down