Skip to content

Commit 23b4561

Browse files
jamaalscarlettJamaal Scarlett
authored andcommitted
When sending a gcm notification, if the server responds with error
NotRegistered or InvalidRegistration set active=0 for the device instead of raising a NotificationError. The Notification error will be raised for all other errors returned.
1 parent cfd9ae0 commit 23b4561

File tree

3 files changed

+74
-3
lines changed

3 files changed

+74
-3
lines changed

push_notifications/gcm.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
"""
77

88
import json
9+
from .models import GCMDevice
10+
911

1012
try:
1113
from urllib.request import Request, urlopen
@@ -23,6 +25,8 @@
2325
class GCMError(NotificationError):
2426
pass
2527

28+
device_errors = ["NotRegistered", "InvalidRegistration"]
29+
2630

2731
def _chunks(l, n):
2832
"""
@@ -71,6 +75,11 @@ def _gcm_send_plain(registration_id, data, **kwargs):
7175

7276
result = _gcm_send(data, "application/x-www-form-urlencoded;charset=UTF-8")
7377
if result.startswith("Error="):
78+
for error in device_errors:
79+
if error in result:
80+
device = GCMDevice.objects.filter(registration_id=values["registration_id"])
81+
device.update(active=0)
82+
return result
7483
raise GCMError(result)
7584
return result
7685

@@ -95,7 +104,18 @@ def _gcm_send_json(registration_ids, data, **kwargs):
95104

96105
result = json.loads(_gcm_send(data, "application/json"))
97106
if result["failure"]:
98-
raise GCMError(result)
107+
ids_to_remove = []
108+
throw_error = 0
109+
for index, er in enumerate(result["results"]):
110+
if er.get("error", "none") in device_errors:
111+
ids_to_remove.append(values["registration_ids"][index])
112+
elif er.get("error", "none") is not "none":
113+
throw_error = 1
114+
if ids_to_remove:
115+
removed = GCMDevice.objects.filter(registration_id__in=ids_to_remove)
116+
removed.update(active=0)
117+
if throw_error:
118+
raise GCMError(result)
99119
return result
100120

101121

tests/mock_responses.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
GCM_PLAIN_RESPONSE = 'id=1:08'
22
GCM_JSON_RESPONSE = '{"multicast_id":108,"success":1,"failure":0,"canonical_ids":0,"results":[{"message_id":"1:08"}]}'
33
GCM_MULTIPLE_JSON_RESPONSE = '{"multicast_id":108,"success":2,"failure":0,"canonical_ids":0,"results":[{"message_id":"1:08"}, {"message_id": "1:09"}]}'
4+
GCM_PLAIN_RESPONSE_ERROR = ['Error=NotRegistered', 'Error=InvalidRegistration']
5+
GCM_PLAIN_RESPONSE_ERROR_B = 'Error=MismatchSenderId'
6+
GCM_JSON_RESPONSE_ERROR = '{"failure": 3, "canonical_ids": 0, "cast_id": 6358665107659088804, "results": [{"error": "NotRegistered"}, {"message_id": "0:1433830664381654%3449593ff9fd7ecd"}, {"error": "InvalidRegistration"}]}'

tests/test_models.py

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@
33
from django.test import TestCase
44
from django.utils import timezone
55
from push_notifications.models import GCMDevice, APNSDevice
6-
from tests.mock_responses import GCM_PLAIN_RESPONSE, GCM_MULTIPLE_JSON_RESPONSE
7-
6+
from tests.mock_responses import GCM_PLAIN_RESPONSE, \
7+
GCM_MULTIPLE_JSON_RESPONSE, GCM_PLAIN_RESPONSE_ERROR, \
8+
GCM_JSON_RESPONSE_ERROR, GCM_PLAIN_RESPONSE_ERROR_B
9+
from push_notifications.gcm import GCMError
810

911
class ModelTestCase(TestCase):
1012
def test_can_save_gcm_device(self):
@@ -105,6 +107,46 @@ def test_gcm_send_message_collapse_to_multiple_devices(self):
105107
"registration_ids": ["abc", "abc1"]
106108
}, separators=(",", ":"), sort_keys=True).encode("utf-8"), "application/json")
107109

110+
def test_gcm_send_message_to_single_device_with_error(self):
111+
# these errors are device specific, device.active will be set false
112+
device_list = ['abc', 'abc1']
113+
self.create_devices(device_list)
114+
for index, error in enumerate(GCM_PLAIN_RESPONSE_ERROR):
115+
with mock.patch("push_notifications.gcm._gcm_send",
116+
return_value=error) as p:
117+
device = GCMDevice.objects. \
118+
get(registration_id=device_list[index])
119+
device.send_message("Hello World!")
120+
assert GCMDevice.objects.get(registration_id=device_list[index]) \
121+
.active is False
122+
123+
def test_gcm_send_message_to_single_device_with_error_b(self):
124+
# these errors are not device specific, GCMError should be thrown
125+
device_list = ['abc']
126+
self.create_devices(device_list)
127+
with mock.patch("push_notifications.gcm._gcm_send",
128+
return_value=GCM_PLAIN_RESPONSE_ERROR_B) as p:
129+
device = GCMDevice.objects. \
130+
get(registration_id=device_list[0])
131+
with self.assertRaises(GCMError):
132+
device.send_message("Hello World!")
133+
assert GCMDevice.objects.get(registration_id=device_list[0]) \
134+
.active is True
135+
136+
def test_gcm_send_message_to_multiple_devices_with_error(self):
137+
device_list = ['abc', 'abc1', 'abc2']
138+
self.create_devices(device_list)
139+
with mock.patch("push_notifications.gcm._gcm_send",
140+
return_value=GCM_JSON_RESPONSE_ERROR) as p:
141+
devices = GCMDevice.objects.all()
142+
devices.send_message("Hello World")
143+
assert GCMDevice.objects.get(registration_id=device_list[0]) \
144+
.active is False
145+
assert GCMDevice.objects.get(registration_id=device_list[1]) \
146+
.active is True
147+
assert GCMDevice.objects.get(registration_id=device_list[2]) \
148+
.active is False
149+
108150
def test_apns_send_message(self):
109151
device = APNSDevice.objects.create(
110152
registration_id="abc",
@@ -122,3 +164,9 @@ def test_apns_send_message_extra(self):
122164
with mock.patch("push_notifications.apns._apns_pack_frame") as p:
123165
device.send_message("Hello world", extra={"foo": "bar"}, socket=socket, identifier=1, expiration=2, priority=5)
124166
p.assert_called_once_with("abc", b'{"aps":{"alert":"Hello world"},"foo":"bar"}', 1, 2, 5)
167+
168+
def create_devices(self, devices):
169+
for device in devices:
170+
GCMDevice.objects.create(
171+
registration_id=device,
172+
)

0 commit comments

Comments
 (0)