diff --git a/docs/howto/dev-server.md b/docs/howto/dev-server.md new file mode 100644 index 0000000000..59862adf13 --- /dev/null +++ b/docs/howto/dev-server.md @@ -0,0 +1,214 @@ +# Using a dev version of the server + +This isn't required for most development -- you can use chat.zulip.org, +or another live Zulip community you belong to, for testing the mobile app. +But sometimes when debugging interactions with the server, or developing +server-side changes related to the mobile app, it's helpful to run the +mobile app against a development server which you control. + +Setting this up involves a few steps, but it should be straightforward if +our instructions are right and you follow them carefully. If these don't +work for you or you have any trouble, please report it in chat, with details +on exactly what you did and what happened; we'll help you debug, and then +adjust the instructions so they work for the next person with a setup like +yours. + +(Also, if these instructions don't discuss your setup, definitely ask in +chat! And PRs very welcome.) + + +## Summary checklist + +This checklist describes a typical setup (with your Zulip dev server +inside Vagrant). It may be helpful for reminders after you've done +this a time or two before. + +For details on each step, and for alternative configurations, see the +sections below. + +- [ ] Find your computer's IP address on your LAN; perhaps try + `ip route get 8 | perl -lne '/src (\S+)/ && print $1'`. +- [ ] Check your `~/.zulip-vagrant-config` (outside the dev VM). +- [ ] Run the server like `EXTERNAL_HOST=${ip_address}:9991 tools/run-dev`. +- [ ] Log in at `http://${ip_address}:9991`. Type `http://` explicitly. + + +## 1. Set up a dev server + +First, if you haven't already, you'll want to install and provision a +[Zulip Server dev VM](https://zulip.readthedocs.io/en/latest/development/overview.html). + +For most people the recommended setup uses Vagrant to manage a VM containing +the Zulip server. If you choose instead to run the Zulip server directly on +your host machine, these instructions will work with some variations. + +You'll run the Zulip server in the dev VM with `tools/run-dev`, following +the usual instructions for Zulip server development (linked above). [Step +4](#4-set-external_host) below adds some options to the `run-dev` command +to make it accessible from the mobile app. + + +## 2. Find the right IP address + +For development on the web app, you'd typically access your Zulip dev server +in a browser on your computer, with the URL `http://localhost:9991/`. This +refers to port 9991 on `localhost`, which is a name that works for talking +to a server on your own computer. + +That URL won't necessarily work for the mobile app, because on your phone +(either physical or emulated), `localhost` would be a name for the phone +itself, rather than your computer. Instead, we'll find an IP address that we +can use instead of `localhost`, which will reach your computer when used on +the phone. + +There are several ways to do this, depending on your platform. See below. + +### iOS simulator (macOS only) + +The iOS simulator shares its network interface with the computer it's running +on. Happily, this means `http://localhost:9991` will work without further +configuration; you can skip to [the last step](#last-step). + +### Android emulator + +This works if you're running the app in the Android emulator, on the same +computer where you're running the dev server (either as a Vagrant host, or +directly.) + +In this situation, the emulator provides 10.0.2.2 as a special alias for the +`localhost` of your computer. (See [upstream docs][android-emulator-net].) +So you can just use `10.0.2.2` below. + +There is one drawback: when using this address (in particular when using it +in Step 4 below), you won't be able to load the dev server in a browser on +your computer. If that makes your testing inconvenient, then move on to the +alternative approach below, which additionally works on all platforms. + +[android-emulator-net]: https://developer.android.com/studio/run/emulator-networking + +### Any physical or emulated device + +This method should work on any physical device, the Android emulator, +or the iOS simulator. + +First: +* If you're using a physical device, you'll need it and your computer to be + on the same local network. You can do this by connecting both the mobile device + and the computer to the same wifi network; or by creating a mobile hotspot + on the device and connecting the computer to that. +* For an emulator/simulator, you just need to run it on the same computer + you're running the Zulip server on. + +We'll use **the IP address your computer uses on the local network**. + * For a physical device, this should be on the same network the phone is on. + * For the Android emulator, any IP address that belongs to your + computer (and isn't a special "loopback" address like 127.0.0.1) + will do. + * For the iOS simulator, if you are not using the simpler method above, + even a loopback address like 127.0.0.1 will be fine. + +To find this, you can use a command-line tool like (on Linux or macOS) +`ip addr` or `ifconfig`; or look in the network pane of macOS's System +Preferences or of Windows's Control Panel. The IP address you want +will often start with `192.168` or `10.10`; the network interface it +belongs to might look like `wlp4s0` or `en1`. + +The command `ip route get 8.8.8.8` (on Linux) will show how your +computer would send a packet to the Internet, which is typically the +right direction. You'd use the address shown after `src`, which is +the one that belongs to your computer. + +For a detailed example, see our howto on [finding your IP +address](find-ip-address.md). + + +## 3. Listen on all interfaces + +(If you're using the Android emulator and the IP address 10.0.2.2, or if you're +using the iOS simulator and `localhost`, you can skip this step and move on +to step 4.) + +By default, the Zulip dev server only listens on the "loopback" network +interface, 127.0.0.1, aka `localhost`. This is a nice secure default, +because it means the only way to connect to the server is from on the +computer itself. But it's less helpful when what we want is to connect from +another device; so we'll configure it to listen on all your computer's +network interfaces. + +### If using Vagrant + +If you set up your dev server to run inside Vagrant (the recommended and +usual approach), then the process actually listening on `localhost:9991` is +a forwarder, set up by Vagrant, which passes requests on to the Zulip +server inside the VM. + +To make the forwarder listen on all network interfaces, just add the +following line to a file `~/.zulip-vagrant-config` on the host computer +(and create the file if it doesn't already exist): +``` +HOST_IP_ADDR 0.0.0.0 +``` + +Then restart the Vagrant guest using `vagrant reload`. + +### If running server directly on host + +If you're running the Zulip server directly on your computer, then you +control this by passing the option `--interface=` to `tools/run-dev`. +For example: +
+    $ tools/run-dev --interface=
+
+ +(But you'll probably add more to the command too; see step 4.) + + +## 4. Set EXTERNAL_HOST + +(If you're using `localhost` with the iOS simulator, you can skip this +step.) + +Like most complex web apps, the Zulip server has an idea internally of what +base URL it's supposed to be accessed at; we call this setting +`EXTERNAL_HOST`. In development, the setting is normally `localhost:9991`, +and corresponds to a base URL of `http://localhost:9991/`. + +Set this to `ADDRESS:9991`, where `ADDRESS` is the address you identified in +step 2. In development, we can do this with an environment variable. For +example, if in step 2 you chose 10.0.2.2, then run the server with this +command: + +
+  $ EXTERNAL_HOST=10.0.2.2:9991 tools/run-dev
+
+ +or if step 3 called for `--interface=`, then + +
+  $ EXTERNAL_HOST=10.0.2.2:9991 tools/run-dev --interface=
+
+ +(Note for Zulip server experts: This also sets `REALM_HOSTS`, via some logic +in `zproject/dev_settings.py`, which is actually the critical part here.) + + + +## 5. Log in! + +Now fire up the app on your emulator or device, go to the +"switch account" UI, and enter the URL of the dev server. + +This will be `http://ADDRESS:9991`, where `ADDRESS` is the address you +identified in step 2. (Be sure to type the `http://`.) + +This should get you the login screen! + +To log in, you'll want the password for some user on the dev server. +You can find that with a command like this: +``` +./manage.py print_initial_password iago@zulip.com +``` + +(Ideally the app would support the handy dev-only auth method which +bypasses the need for a password; that's #405. +Consider implementing that feature.) diff --git a/docs/howto/find-ip-address.md b/docs/howto/find-ip-address.md new file mode 100644 index 0000000000..0801e80285 --- /dev/null +++ b/docs/howto/find-ip-address.md @@ -0,0 +1,144 @@ +# Finding your computer's IP address + +This doc describes how to find the IP address your computer uses on the +local network. This is often needed as a step in setting up to [use a dev +version of the Zulip server](dev-server.md) with the app. + +In brief: +* Look at your network configuration, either with a command line tool like + `ip addr` or `ifconfig` (both available for Linux and macOS) or in the + network pane of macOS's System Preferences or of Windows's Control Panel. +* Look there for the IP address on your wifi interface, or whatever network + interface connects your computer to the Internet. The IP address you want + will often start with `192.168`. The network interface it belongs to + might look like `wlp4s0` or `en1`. + +More detail below. (PRs with examples of different configurations would be +very welcome!) + +## Example: `ip addr`, wifi, Linux + +If you run the command `ip addr` on a Linux machine connected to wifi, the +output might look similar to this: + +
+$ ip addr
+1: lo:  mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
+    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
+    inet 127.0.0.1/8 scope host lo
+    valid_lft forever preferred_lft forever
+    inet6 ::1/128 scope host
+    valid_lft forever preferred_lft forever
+2: enp0s25:  mtu 1500 qdisc fq_codel state DOWN group default qlen 1000
+    link/ether 54:ee:75:26:2e:d9 brd ff:ff:ff:ff:ff:ff
+3: wlp4s0:  mtu 1500 qdisc mq state UP group default qlen 1000
+    link/ether 90:48:9a:a5:28:ef brd ff:ff:ff:ff:ff:ff
+    inet 192.168.0.23/24 brd 10.0.0.255 scope global dynamic noprefixroute wlp4s0
+    valid_lft 225643sec preferred_lft 225643sec
+    inet6 2a00:1028:8386:75de:20a4:2fdf:5373:31c3/64 scope global temporary dynamic
+    valid_lft 172791sec preferred_lft 52729sec
+    inet6 2a00:1028:8386:75de:ca5:d3cf:dd83:9b69/64 scope global dynamic mngtmpaddr noprefixroute
+    valid_lft 172791sec preferred_lft 86391sec
+    inet6 fe80::83a6:19d:16c6:5e0d/64 scope link noprefixroute
+    valid_lft forever preferred_lft forever
+
+ +Here `lo` is the "loopback" interface accessible only from itself. `wlp4s0` +is this computer's wifi interface. IP (v4) addresses are introduced with +the label `inet`. + +The relevant IP address in this example is the `inet` value on the wifi +interface: `192.168.0.23`. + +## Example: `ifconfig`, wifi, macOS + +If you run the command `ifconfig` on a macOS machine connected to wifi, the +output might look similar to this: + +
+$ ifconfig
+lo0: flags=8049<UP,LOOPBACK,RUNNING,MULTICAST> mtu 16384
+	options=1203<RXCSUM,TXCSUM,TXSTATUS,SW_TIMESTAMP>
+	inet 127.0.0.1 netmask 0xff000000
+	inet6 ::1 prefixlen 128
+	inet6 fe80::1%lo0 prefixlen 64 scopeid 0x1
+	nd6 options=201<PERFORMNUD,DAD>
+gif0: flags=8010<POINTOPOINT,MULTICAST> mtu 1280
+stf0: flags=0<> mtu 1280
+XHC20: flags=0<> mtu 0
+en1: flags=8963<UP,BROADCAST,SMART,RUNNING,PROMISC,SIMPLEX,MULTICAST> mtu 1500
+	options=60<TSO4,TSO6>
+	ether 6a:00:02:98:c3:d0
+	media: autoselect <full-duplex>
+	status: inactive
+en2: flags=8963<UP,BROADCAST,SMART,RUNNING,PROMISC,SIMPLEX,MULTICAST> mtu 1500
+	options=60<TSO4,TSO6>
+	ether 6a:00:02:98:c3:d1
+	media: autoselect <full-duplex>
+	status: inactive
+en0: flags=8863<UP,BROADCAST,SMART,RUNNING,SIMPLEX,MULTICAST> mtu 1500
+	ether c4:b3:01:c1:ce:9f
+	inet6 fe80::47e:23e4:2dbd:e1fa%en0 prefixlen 64 secured scopeid 0x7
+	inet 192.168.86.89 netmask 0xffffff00 broadcast 192.168.86.255
+	nd6 options=201<PERFORMNUD,DAD>
+	media: autoselect
+	status: active
+p2p0: flags=8843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> mtu 2304
+	ether 06:b3:01:c1:ce:9f
+	media: autoselect
+	status: inactive
+awdl0: flags=8943<UP,BROADCAST,RUNNING,PROMISC,SIMPLEX,MULTICAST> mtu 1484
+	ether 32:d0:80:06:6c:3b
+	inet6 fe80::30d0:80ff:fe06:6c3b%awdl0 prefixlen 64 scopeid 0x9
+	nd6 options=201<PERFORMNUD,DAD>
+	media: autoselect
+	status: active
+bridge0: flags=8863<UP,BROADCAST,SMART,RUNNING,SIMPLEX,MULTICAST> mtu 1500
+	options=63<RXCSUM,TXCSUM,TSO4,TSO6>
+	ether 6a:00:02:98:c3:d0
+	Configuration:
+		id 0:0:0:0:0:0 priority 0 hellotime 0 fwddelay 0
+		maxage 0 holdcnt 0 proto stp maxaddr 100 timeout 1200
+		root id 0:0:0:0:0:0 priority 0 ifcost 0 port 0
+		ipfilter disabled flags 0x2
+	member: en1 flags=3<LEARNING,DISCOVER>
+	        ifmaxaddr 0 port 5 priority 0 path cost 0
+	member: en2 flags=3<LEARNING,DISCOVER>
+	        ifmaxaddr 0 port 6 priority 0 path cost 0
+	nd6 options=201<PERFORMNUD,DAD>
+	media: <unknown type>
+	status: inactive
+utun0: flags=8051<UP,POINTOPOINT,RUNNING,MULTICAST> mtu 2000
+	inet6 fe80::126b:c8e2:bc66:596%utun0 prefixlen 64 scopeid 0xb
+	nd6 options=201<PERFORMNUD,DAD>
+utun1: flags=8051<UP,POINTOPOINT,RUNNING,MULTICAST> mtu 1380
+	inet6 fe80::25e7:e24c:aadc:4f82%utun1 prefixlen 64 scopeid 0xc
+	nd6 options=201<PERFORMNUD,DAD>
+vboxnet0: flags=8842<BROADCAST,RUNNING,SIMPLEX,MULTICAST> mtu 1500
+	ether 0a:00:27:00:00:00
+vboxnet1: flags=8943<UP,BROADCAST,RUNNING,PROMISC,SIMPLEX,MULTICAST> mtu 1500
+	ether 0a:00:27:00:00:01
+	inet 172.28.128.1 netmask 0xffffff00 broadcast 172.28.128.255
+
+ +There are kind of a lot of different interfaces here. The ones without an +`inet` value aren't likely to matter -- that means an IPv4 address. Of +those, in this example: +* `lo0` is a "loopback" interface accessible only from itself. +* `vboxnet1` (and `vboxnet0`) are interfaces created by VirtualBox (or + perhaps created by Vagrant and used by VirtualBox) for communicating with + VMs managed by VirtualBox. +* `en0` is the wifi interface. + +(If your local network is very futuristic, it's possible your wifi interface +will have only an IPv6 address, labeled `inet6`. As of 2018, this is rare.) + +The relevant IP address in this example is the `inet` value on the wifi +interface: `192.168.86.89`. + +One command to help sort through this output would be +`ifconfig | grep 'inet.*broadcast'`: +
+inet 192.168.86.89 netmask 0xffffff00 broadcast 192.168.86.255
+inet 172.28.128.1 netmask 0xffffff00 broadcast 172.28.128.255
+
diff --git a/docs/howto/push-notifications-ios-simulator.md b/docs/howto/push-notifications-ios-simulator.md index 4ec1d6090a..3fb57e7604 100644 --- a/docs/howto/push-notifications-ios-simulator.md +++ b/docs/howto/push-notifications-ios-simulator.md @@ -1,7 +1,7 @@ # Testing Push Notifications on iOS Simulator For documentation on testing push notifications on Android or a real -iOS device, see https://github.com/zulip/zulip-mobile/blob/main/docs/howto/push-notifications.md +iOS device, see [push-notifications.md](push-notifications.md). This doc describes how to test client-side changes on iOS Simulator. It will demonstrate how to use APNs payloads the server sends to @@ -92,7 +92,7 @@ The `user_id` is that of `iago@zulip.com` in the Zulip dev environment. These canned payloads assume that EXTERNAL_HOST has its default value for the dev 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) +[set EXTERNAL_HOST to use an IP address](dev-server.md#4-set-external_host) in order to enable your device to connect to the dev server, you'll need to adjust the `realm_url` fields. You can do this by a find-and-replace for `localhost`; for example, @@ -219,7 +219,7 @@ for setting up a dev server. If you want to run the dev server on a different machine than the Mac host, you'll need to follow extra steps -[documented here](https://github.com/zulip/zulip-mobile/blob/main/docs/howto/dev-server.md) +[documented here](dev-server.md) to make it possible for the app running on the iOS Simulator to connect to the dev server. diff --git a/docs/howto/push-notifications.md b/docs/howto/push-notifications.md new file mode 100644 index 0000000000..83e8ae44d5 --- /dev/null +++ b/docs/howto/push-notifications.md @@ -0,0 +1,365 @@ +# Push notifications + +This doc describes how to test and develop changes to Zulip's mobile +push notifications. + +#### Contents (with permalinks) + +* [General tips](#general-tips) +* [Testing server-side changes (iOS or Android)](#server) +* [Testing client-side changes on Android](#android) +* [Testing client-side changes on iOS](#ios) + + +
+ +## General tips + +When testing Zulip's push notifications: + +* Try simulating a DM conversation between two users: one on the + mobile device, and one from your desktop. For example, if you've + logged in as "Iago" on your mobile device, log in as "Polonius" via + a web browser, and send a DM from Polonius to Iago. + +* Test different types of messages that will cause different types of + notifications: a 1:1 DM conversation, a group DM conversation, an + @-mention in a channel, a channel with notifications turned on, even + an @-mention in a 1:1 or group DM conversation. + +* Make sure "mobile push notifications" are on in the mobile user's + Zulip settings! + + * Try also turning on [the "even while online" + setting](https://zulip.com/help/test-mobile-notifications) -- + this is extremely helpful for testing notifications effectively, + with basically the one exception of if you're specifically working + on the "if online" aspect of the system. + +* Make sure notifications are enabled for Zulip in the device's system + settings! + + * To get to these, find "Notifications" or "Apps & Notifications" in + the system settings app, depending on OS and version; then find + Zulip in the list. Or on Android, in the launcher give the app's + icon a long-press -> "App Info" -> "Notifications". + + * Also check the settings there for whether and how the app's + notifications will pop on the screen, make a sound, etc. + + * Particularly for the debug build on one's personal device, it's + natural to disable notifications between development sessions to + suppress duplicates... then forget to re-enable them. + +* Leave the app in the background: that is, switch to the launcher / + home screen or to a different app to get it off the screen. Or + keep it on screen, or force-kill it, to test different scenarios! + + +
+ +## Testing server-side changes (iOS or Android) + +When making changes to the Zulip server, use these steps to test how +they affect notifications. + +First, three one-time setup steps: + +1. [Set up the dev server for mobile development](dev-server.md). + +2. Create a `zproject/custom_dev_settings.py` with the following line: + + ```python + ZULIP_SERVICES_URL = 'https://push.zulipchat.com' + ``` + + This matches the normal setting for a production install of the + Zulip server (set by `zproject/default_settings.py`). + + The Zulip server will helpfully print a line on startup to remind + you that this settings override file exists. + +3. Register your development server with our production bouncer by + running a command like this one, but with `[yourname]` replaced by + your name: + + ``` + EXTERNAL_HOST=[yourname].zulipdev.com:9991 python manage.py register_server --agree-to-terms-of-service + ``` + + (The exact value of `EXTERNAL_HOST` doesn't matter; in particular + it doesn't need to match the `EXTERNAL_HOST` that you use when + actually running the server, with `tools/run-dev`. It does need to + be distinct from any names that others have already registered.) + + This is a variation of our [instructions for production + deployments](https://zulip.readthedocs.io/en/latest/production/mobile-push-notifications.html), + adapted for the Zulip dev environment. + + You should only have to do this step once, unless you build a new + Zulip server dev environment from scratch. The credentials which + this command registers with the bouncer are kept in the + `zproject/dev-secrets.conf` file. + +4. If you were already running `tools/run-dev`, quit and restart it + after these setup steps. + + +Then, each time you test: + +1. Run `tools/run-dev` according to the instructions in + [dev-server.md](dev-server.md). Then follow that doc's + instructions to log into the dev server. Use the release build of + the app -- that is, the Zulip app installed from the App Store or + Play Store. + +2. Follow the general tips above to cause a push notification. For + example, log in from a browser as a different user, and send the + mobile user a DM. + + You should see a push notification appear on the mobile device! + + If you don't, check the general tips above. Then ask in chat and + let's debug. + + +
+ +## Testing client-side changes on Android + +Happily, this is straightforward: just edit, build, and run the app +the same as for any other change. + +If you're editing Kotlin code (not only Dart), then +Flutter's hot reload and hot restart won't pick that up; +you'll need to stop the app and use `flutter run` afresh. + + +
+ +## Testing client-side changes on iOS + +Apple makes this much more of a pain than it is on Android: APNs (*) +does not allow our production bouncer to send notifications to a +development build of the app. + +(*) i.e. "Apple Push Notification service" -- Apple is very Apple +about this name, styling it without an article and with the +idiosyncratic capitalization shown. + + +### Current workaround + +This workaround means using a development server. You'll first need +to [set up the dev server for mobile development](dev-server.md). + +You can tell your development server to talk to Apple's APNs "sandbox" +server, instead of its server meant for production, but you'll need a +certificate signed by Apple authorizing you to do so. Some background +on that is +[here](https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/sending_notification_requests_to_apns/#2947606). + +1. First, generate a certificate signing request (CSR) and + corresponding private key. Use the `tools/setup/apns/prep-cert` + script from the Zulip server tree: + ``` + $ tools/setup/apns/prep-cert request /tmp/apns.key /tmp/apns.csr + ``` + +2. Greg is authorized in Apple Developer to upload the CSR and obtain + the actual certificate, so you should send `apns.csr` to him and + ask him to do that. He'll follow the steps at + https://developer.apple.com/account/resources/certificates/add + with: + * Cert type: “Apple Push Notification service SSL (Sandbox)" + (not "Sandbox & Production") + * App ID: 66KHCWMEYB.org.zulip.Zulip + + to obtain a certificate file `aps_development.cer`, + and send it back to you. + +3. Combine the certificate with the key using the same tool: + ``` + $ tools/setup/apns/prep-cert combine \ + /tmp/apns.key /tmp/aps_development.cer zproject/apns-dev.pem + ``` + + The file `zproject/apns-dev.pem` is the output of all the steps + up to this point. + You can now delete the other files `/tmp/apns.key`, `/tmp/apns.csr`, + and `/tmp/aps_development.cer`. + +4. Restart `tools/run-dev` to let the server pick up the change. + It should automatically see the file `zproject/apns-dev.pem` + and use it to communicate with the APNs sandbox server. + +You should now be getting notifications on your iOS development build! + + +#### Troubleshooting + +If it's not working, first check that mobile notification settings are +on, using the web app's settings interface. See also the +troubleshooting items below. + +If none of those resolve the issue, please ask for help +in [#mobile-dev-help](https://chat.zulip.org/#narrow/channel/516-mobile-dev-help) on +chat.zulip.org, so we can debug. + + +##### Error: Your plan doesn't allow sending push notifications + +After the above setup is done, when the dev server tries to send a +notification (e.g. because you had some other user send a DM to the +user you've logged in as from the mobile app), you might get an error +like this one (reformatted for readability): + +``` +INFO [zerver.lib.push_notifications] Sending push notifications to mobile clients for user 11 +INFO [zr] 127.0.0.1 POST 400 8ms (db: 3ms/6q) /api/v1/remotes/push/notify [can_push=False/Missing data] (zulip-server:… via ZulipServer/11.0-dev+git) +INFO [zr] status=400, data=b'{"result":"error","msg":"Your plan doesn\'t allow sending push notifications.","code":"INVALID_ZULIP_SERVER"}\n', uid=zulip-server:… +WARN [django.server] "POST /api/v1/remotes/push/notify HTTP/1.1" 400 109 +ERR [] Problem handling data on queue missedmessage_mobile_notifications +Traceback (most recent call last): +… + File "/srv/zulip/zerver/lib/remote_server.py", line 195, in send_to_push_bouncer + raise PushNotificationBouncerError( +zerver.lib.remote_server.PushNotificationBouncerError: + Push notifications bouncer error: Your plan doesn't allow sending push notifications. +``` + +It's not clear why this happens. Try stopping the server +(i.e. `tools/run-dev`) and starting it again. On the +[one occasion][error-plan-missing] this error has been seen +as of this writing, the error went away after doing so. + +[error-plan-missing]: https://chat.zulip.org/#narrow/channel/243-mobile-team/topic/notifications.20from.20dev.20server/near/2163051 + + +##### Error: BadDeviceToken + +When the dev server tries to send a notification, +you might get in the server log an error like this one +(reformatted for readability): + +``` +INFO [zerver.lib.push_notifications] Sending push notifications to mobile clients for user 11 +… +INFO [zerver.lib.push_notifications] APNs: Removing invalid/expired token FD524D9B9018CB7340051C26E4F99304F71D4B37E2164265AB0BBA3543225331 (BadDeviceToken) +… +INFO [zerver.lib.push_notifications] Deleting push tokens based on response from bouncer: Android: [], Apple: ['FD524D9B9018CB7340051C26E4F99304F71D4B37E2164265AB0BBA3543225331'] +``` + +This can happen when you're using a release build of the app +(such as the one from TestFlight or the App Store), +which the server's sandbox-only certificate is unable to send to. +To fix it, use a debug build of the app. + + +##### Error: TopicDisallowed + +When the dev server tries to send a notification, +you might get in the server log an error like this one +(reformatted for readability): + +``` +INFO [zerver.lib.push_notifications] Sending push notifications to mobile clients for user 11 +INFO [zilencer.views] Sending mobile push notifications for remote user 1db86aa5-1327-4c03-8db8-c8189aa23b0c:: 0 via FCM devices, 1 via APNs devices +INFO [zerver.lib.push_notifications] APNs: Sending notification for remote user 1db86aa5-1327-4c03-8db8-c8189aa23b0c: to 1 devices (skipped 0 duplicates) +WARN [zerver.lib.push_notifications] APNs: Failed to send for user to device 34FBEF8A439C8F9B6CC6AEFC42CDFD2A9618FFA3E0C8C50B9A56CB96D3F2D727: TopicDisallowed +``` + +This can happen when the app's bundle ID +(which gets used as an APNs "topic") +is different from the one your server's APNs certificate is for. +To fix it, either get a certificate for the desired topic, +or edit the app's bundle ID to match the certificate. + +To edit the bundle ID, try a command like: +``` +$ perl -i -0pe 's/org\.zulip\.Zulip/OTHER_NAME/g' \ + -- $(git grep -l org.zulip.Zulip) +``` + + +### Another workaround (if the first doesn't work) + +Make a release build of the app, and upload it [as an alpha][]. +Update your device to the alpha from TestFlight, and test there. + +[as an alpha]: ../release.md#build-and-upload-alpha-ios + +This works OK, but it's slow: about 5m to build and upload, and +another few on Apple's servers before it's available in alpha. In +total perhaps 10m from "save and hit go" to actually getting to test. + +The long iteration cycle can be tolerable for changes that are very +likely to need only zero to one revision -- because they're very +small, or already well tested on Android -- but makes serious +development basically infeasible. + + +#### Tip: setting the iOS build number + +(This subsection hasn't actually been tested; it's lightly adapted +from instructions that worked in the legacy app. +If you try it out, be sure to watch for any wrong details, +and then please update this doc with what you learn.) + +For making a throwaway alpha build like this approach calls for, you +may find something like the following command helpful. It sets the +[iOS build number][], aka `CFBundleVersion` (which we normally leave +set to 1.0) to a new value so the new build can coexist on TestFlight +with previous builds: + + $ set-buildno () { + version="$1" perl -i -0pe ' + s|CFBundleVersion \s* \K [0-9.]+ + |$ENV{version}|xs + ' ios/Flutter/AppFrameworkInfo.plist && + git commit -am "version: Bump iOS build number to $1." + } + $ set-buildno 1.1 + +More specifically, the rule appears to be: + + * The user-facing version number must be strictly greater than the + last one published to the App Store. + + * The user-facing version number can stay the same across a series of + alpha and/or beta releases, but the build number must strictly + increase. + +So if the last version in main is already in the App Store (and not +still in alpha or beta), then we'll want to: + + * Increment the user-facing version number. + + * To avoid confusion when we next release, use a build number + starting with "0." -- so e.g. `set-buildno 0.0.1` or `set-buildno + 0.20200204.1`. + +[iOS build number]: https://developer.apple.com/documentation/bundleresources/information_property_list/cfbundleversion +[version number]: https://developer.apple.com/documentation/bundleresources/information_property_list/cfbundleshortversionstring + + +### Possible future solution + +Better yet, for many development purposes, would be to be able to +dispense with a local dev server, and still get notifications from +zulipchat.com and chat.zulip.org in a development build of the app. + +The ingredient that'd be needed for that is to get a production +Zulip server, or the production bouncer it talks to, +to optionally send notifications through +Apple's sandbox instance of APNs. + +* One way to arrange that might be to have the production bouncer talk + to either the production or staging instance of APNs; the client + tell the Zulip server which one to use for that client's + notifications, and the Zulip server pass that information on to the + bouncer. + +* Another might be to leave the bouncer out of it, and have the Zulip + server talk directly to the sandbox APNs when the client asks it to, + rather than talk to the bouncer.