Skip to content

Commit 6323633

Browse files
docs: Document testing push notifications on iOS Simulator
1 parent da38a9a commit 6323633

File tree

1 file changed

+281
-0
lines changed

1 file changed

+281
-0
lines changed
Lines changed: 281 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,281 @@
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+
<div id="receive-notification" />
12+
13+
## Receive a notification on the iOS Simulator
14+
15+
Follow the following steps if you already have a valid APNs payload.
16+
17+
Otherwise, you can either use one of the pre-canned payloads
18+
provided [here](#pre-canned-payloads), or you can retrieve the APNs
19+
payload generated by the latest dev server by following the steps
20+
[here](#retrieve-apns-payload).
21+
22+
23+
### 1. Determine the device ID of the iOS Simulator
24+
25+
To receive a notification on the iOS Simulator, we need to first
26+
determine the device ID of the iOS Simulator, to specify which
27+
Simulator instance we want to push the payload to.
28+
29+
```shell-session
30+
$ xcrun simctl list devices booted
31+
```
32+
33+
<details>
34+
<summary>Example output:</summary>
35+
36+
```shell-session
37+
$ xcrun simctl list devices booted
38+
== Devices ==
39+
-- iOS 18.3 --
40+
iPhone 16 Pro (90CC33B2-679B-4053-B380-7B986A29F28C) (Booted)
41+
```
42+
43+
</details>
44+
45+
46+
### 2. Trigger a notification by pushing the payload to the iOS Simulator
47+
48+
By running the following command with a valid APNs payload, you should
49+
receive a notification on the iOS Simulator for the zulip-flutter app,
50+
and tapping on it should route to the respective conversation.
51+
52+
```shell-session
53+
$ xcrun simctl push [device-id] com.zulip.flutter [payload json path]
54+
```
55+
56+
<details>
57+
<summary>Example output:</summary>
58+
59+
```shell-session
60+
$ xcrun simctl push 90CC33B2-679B-4053-B380-7B986A29F28C com.zulip.flutter ./dm.json
61+
Notification sent to 'com.zulip.flutter'
62+
```
63+
64+
</details>
65+
66+
67+
<div id="pre-canned-payloads" />
68+
69+
## Pre-canned APNs payloads
70+
71+
The following pre-canned APNs payloads can be used in case you don't
72+
have one.
73+
74+
The following pre-canned payloads were generated from
75+
Zulip Server 11.0-dev+git 8fd04b0f0, API Feature Level 377,
76+
in April 2025.
77+
78+
Also, they assume that EXTERNAL_HOST has its default value for the dev
79+
server. If you've [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)
80+
in order to enable your device to connect to the dev server, you'll
81+
need to adjust the `realm_url` fields. You can do this by a
82+
find-and-replace for `localhost`; for example,
83+
`perl -i -0pe s/localhost/10.0.2.2/g tmp/*.json` after saving the
84+
canned payloads to files `tmp/*.json`.
85+
86+
<details>
87+
<summary>Payload: dm.json</summary>
88+
89+
```json
90+
{
91+
"aps": {
92+
"alert": {
93+
"title": "Zoe",
94+
"subtitle": "",
95+
"body": "But wouldn't that show you contextually who is in the audience before you have to open the compose box?"
96+
},
97+
"sound": "default",
98+
"badge": 0,
99+
},
100+
"zulip": {
101+
"server": "zulipdev.com:9991",
102+
"realm_id": 2,
103+
"realm_uri": "http://localhost:9991",
104+
"realm_url": "http://localhost:9991",
105+
"realm_name": "Zulip Dev",
106+
"user_id": 11,
107+
"sender_id": 7,
108+
"sender_email": "[email protected]",
109+
"time": 1740890583,
110+
"recipient_type": "private",
111+
"message_ids": [
112+
87
113+
]
114+
}
115+
}
116+
```
117+
118+
</details>
119+
120+
<details>
121+
<summary>Payload: group_dm.json</summary>
122+
123+
```json
124+
{
125+
"aps": {
126+
"alert": {
127+
"title": "Othello, the Moor of Venice, Polonius (guest), Iago",
128+
"subtitle": "Othello, the Moor of Venice:",
129+
"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."
130+
},
131+
"sound": "default",
132+
"badge": 0,
133+
},
134+
"zulip": {
135+
"server": "zulipdev.com:9991",
136+
"realm_id": 2,
137+
"realm_uri": "http://localhost:9991",
138+
"realm_url": "http://localhost:9991",
139+
"realm_name": "Zulip Dev",
140+
"user_id": 11,
141+
"sender_id": 12,
142+
"sender_email": "[email protected]",
143+
"time": 1740533641,
144+
"recipient_type": "private",
145+
"pm_users": "11,12,13",
146+
"message_ids": [
147+
17
148+
]
149+
}
150+
}
151+
```
152+
153+
</details>
154+
155+
<details>
156+
<summary>Payload: stream.json</summary>
157+
158+
```json
159+
{
160+
"aps": {
161+
"alert": {
162+
"title": "#devel > plotter",
163+
"subtitle": "Desdemona:",
164+
"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."
165+
},
166+
"sound": "default",
167+
"badge": 0,
168+
},
169+
"zulip": {
170+
"server": "zulipdev.com:9991",
171+
"realm_id": 2,
172+
"realm_uri": "http://localhost:9991",
173+
"realm_url": "http://localhost:9991",
174+
"realm_name": "Zulip Dev",
175+
"user_id": 11,
176+
"sender_id": 9,
177+
"sender_email": "[email protected]",
178+
"time": 1740558997,
179+
"recipient_type": "stream",
180+
"stream": "devel",
181+
"stream_id": 11,
182+
"topic": "plotter",
183+
"message_ids": [
184+
40
185+
]
186+
}
187+
}
188+
```
189+
190+
</details>
191+
192+
193+
<div id="retrieve-apns-payload" />
194+
195+
## Retrieve an APNs payload from dev server
196+
197+
### 1. Set up dev server
198+
199+
Follow
200+
[this setup tutorial](https://zulip.readthedocs.io/en/latest/development/setup-recommended.html)
201+
to setup and run the dev server on same the Mac machine that hosts
202+
the iOS Simulator.
203+
204+
If you want to run the dev server on a different machine than the Mac
205+
host, you'll need to follow extra steps
206+
[documented here](https://github.com/zulip/zulip-mobile/blob/main/docs/howto/dev-server.md)
207+
to make it possible for the app running on the iOS Simulator to
208+
connect to the dev server.
209+
210+
211+
### 2. Set up the dev user to receive mobile notifications.
212+
213+
We'll use the devlogin user `[email protected]` to test notifications,
214+
log in to that user by going to `/devlogin` on that server on Web.
215+
216+
And then follow the steps [here](https://zulip.com/help/mobile-notifications)
217+
to enable Mobile Notifications for "Channels".
218+
219+
220+
### 3. Log in as the dev user on zulip-flutter.
221+
222+
<!-- TODO(#405) Guide to use the new devlogin page instead -->
223+
224+
To login to this user in the Flutter app, you'll need the password
225+
that was generated by the development server. You can print the
226+
password by running this command inside your `vagrant ssh` shell:
227+
```
228+
$ ./manage.py print_initial_password [email protected]
229+
```
230+
231+
Then run the app on the iOS Simulator, accept the permission to
232+
receive push notifications, and then login to the dev user
233+
234+
235+
236+
### 4. Edit the server code to log the notification payload.
237+
238+
We need to retrieve the APNs payload the server generates and sends
239+
to the bouncer. To do that we can add a log statement after the
240+
server completes generating the APNs in `zerver/lib/push_notifications.py`:
241+
242+
```diff
243+
apns_payload = get_message_payload_apns(
244+
user_profile,
245+
message,
246+
trigger,
247+
mentioned_user_group_id,
248+
mentioned_user_group_name,
249+
can_access_sender,
250+
)
251+
gcm_payload, gcm_options = get_message_payload_gcm(
252+
user_profile, message, mentioned_user_group_id, mentioned_user_group_name, can_access_sender
253+
)
254+
logger.info("Sending push notifications to mobile clients for user %s", user_profile_id)
255+
+ logger.info("APNS payload %s", orjson.dumps(apns_payload).decode())
256+
257+
android_devices = list(
258+
PushDeviceToken.objects.filter(user=user_profile, kind=PushDeviceToken.FCM).order_by("id")
259+
```
260+
261+
262+
### 5. Send messages to the dev user
263+
264+
To generate notifications to the dev user `[email protected]` we need to
265+
send messages from another user. For a variety of different types of
266+
payloads try sending a message in a topic, a message in a group DM,
267+
and one in one-one DM. Then look for the payloads in the server logs
268+
by searching for "APNS payload".
269+
270+
271+
### 6. Transform and save the payload to a file
272+
273+
The logged payload JSON will have different structure than what an
274+
iOS device actually receives, to fix that and save the result to a
275+
file, run the payload through the following command:
276+
277+
```shell-session
278+
$ echo '{"alert":{"title": ...' \
279+
| jq '{aps: {alert: .alert, sound: .sound, badge: .badge}, zulip: .custom.zulip}' \
280+
> payload.json
281+
```

0 commit comments

Comments
 (0)