Skip to content

Commit 98934b2

Browse files
docs: Document testing push notifications on iOS Simulator
1 parent 57a83eb commit 98934b2

File tree

1 file changed

+235
-0
lines changed

1 file changed

+235
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
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+
Setup and run Zulip dev server following the [setup tutorial](https://zulip.readthedocs.io/en/latest/development/setup-recommended.html).
18+
19+
## 2. (Optional) Setup the dev user to receive mobile notifications.
20+
21+
We'll use the devlogin user `[email protected]` to test notifications,
22+
log in to that user by going to `/devlogin` on that server on Web.
23+
24+
And then follow the steps [here](https://zulip.com/help/mobile-notifications)
25+
to enable Mobile Notifications for "Channels".
26+
27+
## 3. (Optional) Login to the dev user on zulip-flutter.
28+
29+
<!-- TODO(405) Guide to use the new devlogin page instead -->
30+
31+
To login to this user in the Flutter app, you'll need the password
32+
that was generated by the development server. You can print the
33+
password by running this command inside your `vagrant ssh` shell:
34+
```
35+
$ ./manage.py print_initial_password [email protected]
36+
```
37+
38+
Then run the app on the iOS Simulator, accept the permission to
39+
receive push notifications, and then login to the dev user
40+
41+
42+
## 4. (Optional) Edit the server code to log the notification payload.
43+
44+
We need to retrieve the APNs payload the server generates and sends
45+
to the bouncer. To do that we can add a log statement after the
46+
server completes generating the APNs in `zerver/lib/push_notifications.py`:
47+
48+
```diff
49+
apns_payload = get_message_payload_apns(
50+
user_profile,
51+
message,
52+
trigger,
53+
mentioned_user_group_id,
54+
mentioned_user_group_name,
55+
can_access_sender,
56+
)
57+
gcm_payload, gcm_options = get_message_payload_gcm(
58+
user_profile, message, mentioned_user_group_id, mentioned_user_group_name, can_access_sender
59+
)
60+
logger.info("Sending push notifications to mobile clients for user %s", user_profile_id)
61+
+ logger.info("APNS payload %s", orjson.dumps(apns_payload))
62+
63+
android_devices = list(
64+
PushDeviceToken.objects.filter(user=user_profile, kind=PushDeviceToken.FCM).order_by("id")
65+
```
66+
67+
## 5. (Optional) Send messages to the dev user
68+
69+
To generate notifications to the dev user `[email protected]` we need to
70+
send messages from another user. For a variety of different types of
71+
payloads try sending a message in a topic, a message in a group DM,
72+
and one in one-one DM. Then look for the payloads in the server logs
73+
by searching for "APNS payload".
74+
75+
The logged payload JSON will have different structure than what an
76+
iOS device actually receives, to fix that, run the payload through
77+
the following command:
78+
79+
```shell-session
80+
$ echo '{"alert":{"title": ...' | jq '{aps: {alert: .alert, sound: .sound, badge: .badge}, zulip: .custom.zulip}'
81+
```
82+
83+
## 6. Push APNs payload to iOS Simulator
84+
85+
_If you skipped steps 2-5, you'll need pre-forged APNs payloads for
86+
existing messages in a default development server messages for the
87+
88+
89+
The canned payloads were generated from Zulip Server 11.0-dev+git
90+
8fd04b0f, API Feature Level 377, in April 2025.
91+
92+
<details>
93+
<summary>Payload: dm.json</summary>
94+
95+
```json
96+
{
97+
"aps": {
98+
"alert": {
99+
"title": "Zoe",
100+
"subtitle": "",
101+
"body": "But wouldn't that show you contextually who is in the audience before you have to open the compose box?"
102+
},
103+
"sound": "default",
104+
"badge": 0,
105+
},
106+
"zulip": {
107+
"server": "zulipdev.com:9991",
108+
"realm_id": 2,
109+
"realm_uri": "http://localhost:9991",
110+
"realm_url": "http://localhost:9991",
111+
"realm_name": "Zulip Dev",
112+
"user_id": 11,
113+
"sender_id": 7,
114+
"sender_email": "[email protected]",
115+
"time": 1740890583,
116+
"recipient_type": "private",
117+
"message_ids": [
118+
87
119+
]
120+
}
121+
}
122+
```
123+
124+
</details>
125+
126+
<details>
127+
<summary>Payload: group_dm.json</summary>
128+
129+
```json
130+
{
131+
"aps": {
132+
"alert": {
133+
"title": "Othello, the Moor of Venice, Polonius (guest), Iago",
134+
"subtitle": "Othello, the Moor of Venice:",
135+
"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."
136+
},
137+
"sound": "default",
138+
"badge": 0,
139+
},
140+
"zulip": {
141+
"server": "zulipdev.com:9991",
142+
"realm_id": 2,
143+
"realm_uri": "http://localhost:9991",
144+
"realm_url": "http://localhost:9991",
145+
"realm_name": "Zulip Dev",
146+
"user_id": 11,
147+
"sender_id": 12,
148+
"sender_email": "[email protected]",
149+
"time": 1740533641,
150+
"recipient_type": "private",
151+
"pm_users": "11,12,13",
152+
"message_ids": [
153+
17
154+
]
155+
}
156+
}
157+
```
158+
159+
</details>
160+
161+
<details>
162+
<summary>Payload: stream.json</summary>
163+
164+
```json
165+
{
166+
"aps": {
167+
"alert": {
168+
"title": "#devel > plotter",
169+
"subtitle": "Desdemona:",
170+
"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."
171+
},
172+
"sound": "default",
173+
"badge": 0,
174+
},
175+
"zulip": {
176+
"server": "zulipdev.com:9991",
177+
"realm_id": 2,
178+
"realm_uri": "http://localhost:9991",
179+
"realm_url": "http://localhost:9991",
180+
"realm_name": "Zulip Dev",
181+
"user_id": 11,
182+
"sender_id": 9,
183+
"sender_email": "[email protected]",
184+
"time": 1740558997,
185+
"recipient_type": "stream",
186+
"stream": "devel",
187+
"stream_id": 11,
188+
"topic": "plotter",
189+
"message_ids": [
190+
40
191+
]
192+
}
193+
}
194+
```
195+
196+
</details>
197+
198+
To receive a notification on the iOS Simulator, we need to push
199+
the APNs payload to the specific running iOS Simulator by using it's
200+
device ID, you can get the device ID by running the following command:
201+
202+
```shell-session
203+
$ xcrun simctl list devices booted
204+
```
205+
206+
<details>
207+
<summary>Example output:</summary>
208+
209+
```shell-session
210+
$ xcrun simctl list devices booted
211+
== Devices ==
212+
-- iOS 18.3 --
213+
iPhone 16 Pro (90CC33B2-679B-4053-B380-7B986A29F28C) (Booted)
214+
```
215+
216+
</details>
217+
218+
And then push the payload using the following command:
219+
220+
```shell-session
221+
$ xcrun simctl push [device-id] com.zulip.flutter [payload json path]
222+
```
223+
224+
<details>
225+
<summary>Example output:</summary>
226+
227+
```shell-session
228+
$ xcrun simctl push 90CC33B2-679B-4053-B380-7B986A29F28C com.zulip.flutter ./dm.json
229+
Notification sent to 'com.zulip.flutter'
230+
```
231+
232+
</details>
233+
234+
Now, on the iOS Simulator you should have a notification and tapping
235+
on it should route to the specific conversation.

0 commit comments

Comments
 (0)