Skip to content

Commit 45c29fc

Browse files
docs: Document testing push notifications on iOS Simulator
1 parent 1aff059 commit 45c29fc

File tree

1 file changed

+253
-0
lines changed

1 file changed

+253
-0
lines changed
Lines changed: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
1+
# Testing Push Notifications on iOS Simulator
2+
3+
For documentation on testing push notifications on Android or a real
4+
iOS Device, see https://github.com/zulip/zulip-mobile/blob/main/docs/howto/push-notifications.md
5+
6+
This doc describes how to test client side changes on iOS Simulator.
7+
It will demonstrate how to use APNs payloads the server sends to
8+
Apple's Push Notification service to show notifications on iOS
9+
Simulator.
10+
11+
## 1. (Optional) Setup dev server
12+
13+
_Skip to step 6 in which you will use canned notification payloads,
14+
these intermediate steps records how to get those payloads, which may
15+
be useful in future._
16+
17+
Follow
18+
[this setup tutorial](https://zulip.readthedocs.io/en/latest/development/setup-recommended.html)
19+
to setup and run the dev server on same the Mac machine that hosts
20+
the iOS Simulator.
21+
22+
If you want to run the dev server on a different machine than the Mac
23+
host, you'll need to follow extra steps
24+
[documented here](https://github.com/zulip/zulip-mobile/blob/main/docs/howto/dev-server.md)
25+
to make it possible for the app running on the iOS Simulator to
26+
connect to the dev server.
27+
28+
## 2. (Optional) Setup the dev user to receive mobile notifications.
29+
30+
We'll use the devlogin user `[email protected]` to test notifications,
31+
log in to that user by going to `/devlogin` on that server on Web.
32+
33+
And then follow the steps [here](https://zulip.com/help/mobile-notifications)
34+
to enable Mobile Notifications for "Channels".
35+
36+
## 3. (Optional) Login to the dev user on zulip-flutter.
37+
38+
<!-- TODO(#405) Guide to use the new devlogin page instead -->
39+
40+
To login to this user in the Flutter app, you'll need the password
41+
that was generated by the development server. You can print the
42+
password by running this command inside your `vagrant ssh` shell:
43+
```
44+
$ ./manage.py print_initial_password [email protected]
45+
```
46+
47+
Then run the app on the iOS Simulator, accept the permission to
48+
receive push notifications, and then login to the dev user
49+
50+
51+
## 4. (Optional) Edit the server code to log the notification payload.
52+
53+
We need to retrieve the APNs payload the server generates and sends
54+
to the bouncer. To do that we can add a log statement after the
55+
server completes generating the APNs in `zerver/lib/push_notifications.py`:
56+
57+
```diff
58+
apns_payload = get_message_payload_apns(
59+
user_profile,
60+
message,
61+
trigger,
62+
mentioned_user_group_id,
63+
mentioned_user_group_name,
64+
can_access_sender,
65+
)
66+
gcm_payload, gcm_options = get_message_payload_gcm(
67+
user_profile, message, mentioned_user_group_id, mentioned_user_group_name, can_access_sender
68+
)
69+
logger.info("Sending push notifications to mobile clients for user %s", user_profile_id)
70+
+ logger.info("APNS payload %s", orjson.dumps(apns_payload))
71+
72+
android_devices = list(
73+
PushDeviceToken.objects.filter(user=user_profile, kind=PushDeviceToken.FCM).order_by("id")
74+
```
75+
76+
## 5. (Optional) Send messages to the dev user
77+
78+
To generate notifications to the dev user `[email protected]` we need to
79+
send messages from another user. For a variety of different types of
80+
payloads try sending a message in a topic, a message in a group DM,
81+
and one in one-one DM. Then look for the payloads in the server logs
82+
by searching for "APNS payload".
83+
84+
The logged payload JSON will have different structure than what an
85+
iOS device actually receives, to fix that, run the payload through
86+
the following command:
87+
88+
```shell-session
89+
$ echo '{"alert":{"title": ...' | jq '{aps: {alert: .alert, sound: .sound, badge: .badge}, zulip: .custom.zulip}'
90+
```
91+
92+
## 6. Push APNs payload to iOS Simulator
93+
94+
_If you skipped steps 2-5, you'll need pre-forged APNs payloads for
95+
existing messages in a default development server messages for the
96+
97+
98+
These canned payloads were generated from Zulip Server 11.0-dev+git
99+
8fd04b0f0, API Feature Level 377, in April 2025.
100+
101+
These canned payloads assume that EXTERNAL_HOST has its default value
102+
for the dev server. If you've
103+
[set EXTERNAL_HOST to use an IP address](https://github.com/zulip/zulip-mobile/blob/main/docs/howto/dev-server.md#4-set-external_host)
104+
in order to enable your device to connect to the dev server, you'll
105+
need to adjust the `realm_url` fields. You can do this by a
106+
find-and-replace for `localhost`; for example,
107+
`perl -i -0pe s/localhost/10.0.2.2/g tmp/*.json` after saving the
108+
canned payloads to files `tmp/*.json`.
109+
110+
<details>
111+
<summary>Payload: dm.json</summary>
112+
113+
```json
114+
{
115+
"aps": {
116+
"alert": {
117+
"title": "Zoe",
118+
"subtitle": "",
119+
"body": "But wouldn't that show you contextually who is in the audience before you have to open the compose box?"
120+
},
121+
"sound": "default",
122+
"badge": 0,
123+
},
124+
"zulip": {
125+
"server": "zulipdev.com:9991",
126+
"realm_id": 2,
127+
"realm_uri": "http://localhost:9991",
128+
"realm_url": "http://localhost:9991",
129+
"realm_name": "Zulip Dev",
130+
"user_id": 11,
131+
"sender_id": 7,
132+
"sender_email": "[email protected]",
133+
"time": 1740890583,
134+
"recipient_type": "private",
135+
"message_ids": [
136+
87
137+
]
138+
}
139+
}
140+
```
141+
142+
</details>
143+
144+
<details>
145+
<summary>Payload: group_dm.json</summary>
146+
147+
```json
148+
{
149+
"aps": {
150+
"alert": {
151+
"title": "Othello, the Moor of Venice, Polonius (guest), Iago",
152+
"subtitle": "Othello, the Moor of Venice:",
153+
"body": "Sit down awhile; And let us once again assail your ears, That are so fortified against our story What we have two nights seen."
154+
},
155+
"sound": "default",
156+
"badge": 0,
157+
},
158+
"zulip": {
159+
"server": "zulipdev.com:9991",
160+
"realm_id": 2,
161+
"realm_uri": "http://localhost:9991",
162+
"realm_url": "http://localhost:9991",
163+
"realm_name": "Zulip Dev",
164+
"user_id": 11,
165+
"sender_id": 12,
166+
"sender_email": "[email protected]",
167+
"time": 1740533641,
168+
"recipient_type": "private",
169+
"pm_users": "11,12,13",
170+
"message_ids": [
171+
17
172+
]
173+
}
174+
}
175+
```
176+
177+
</details>
178+
179+
<details>
180+
<summary>Payload: stream.json</summary>
181+
182+
```json
183+
{
184+
"aps": {
185+
"alert": {
186+
"title": "#devel > plotter",
187+
"subtitle": "Desdemona:",
188+
"body": "Despite the fact that such a claim at first glance seems counterintuitive, it is derived from known results. Electrical engineering follows a cycle of four phases: location, refinement, visualization, and evaluation."
189+
},
190+
"sound": "default",
191+
"badge": 0,
192+
},
193+
"zulip": {
194+
"server": "zulipdev.com:9991",
195+
"realm_id": 2,
196+
"realm_uri": "http://localhost:9991",
197+
"realm_url": "http://localhost:9991",
198+
"realm_name": "Zulip Dev",
199+
"user_id": 11,
200+
"sender_id": 9,
201+
"sender_email": "[email protected]",
202+
"time": 1740558997,
203+
"recipient_type": "stream",
204+
"stream": "devel",
205+
"stream_id": 11,
206+
"topic": "plotter",
207+
"message_ids": [
208+
40
209+
]
210+
}
211+
}
212+
```
213+
214+
</details>
215+
216+
To receive a notification on the iOS Simulator, we need to push
217+
the APNs payload to the specific running iOS Simulator by using it's
218+
device ID, you can get the device ID by running the following command:
219+
220+
```shell-session
221+
$ xcrun simctl list devices booted
222+
```
223+
224+
<details>
225+
<summary>Example output:</summary>
226+
227+
```shell-session
228+
$ xcrun simctl list devices booted
229+
== Devices ==
230+
-- iOS 18.3 --
231+
iPhone 16 Pro (90CC33B2-679B-4053-B380-7B986A29F28C) (Booted)
232+
```
233+
234+
</details>
235+
236+
And then push the payload using the following command:
237+
238+
```shell-session
239+
$ xcrun simctl push [device-id] com.zulip.flutter [payload json path]
240+
```
241+
242+
<details>
243+
<summary>Example output:</summary>
244+
245+
```shell-session
246+
$ xcrun simctl push 90CC33B2-679B-4053-B380-7B986A29F28C com.zulip.flutter ./dm.json
247+
Notification sent to 'com.zulip.flutter'
248+
```
249+
250+
</details>
251+
252+
Now, on the iOS Simulator you should have a notification and tapping
253+
on it should route to the specific conversation.

0 commit comments

Comments
 (0)