Skip to main content

Push Notifications

This document describes push notifications from a technical/architectural point-of-view.

Types of Push Notifications

There are three different types of push notifications currently planned:

  1. Chat - users get notified of new messages, chat invitations, etc. within holi chat.
  2. Transactional - users get notified when something requires their attention or an action, e.g. “a user wants to join your space”, “you have been admitted into a space”
  3. Promotional - community and marketing “directly” reach out to users to e.g. promote new versions/features or events.

Chat Push Notifications

info

Push notifications for chat are currently only available for Mobile. Push for Web has not been implemented yet.

This is the first type of push notifications realised for holi. As holi chat relies on Matrix as a chat protocol, what can be done for push notifications is to some extend determined by the Matrix Push Gateway API specification. Architecturally, push notifications are triggered and delivered in the following way:

Assume users A and B chatting with each other:

  • Prerequisites:
    • [Frontend] A opens the chat client and a connection with the Matrix home server is established.
    • [Frontend] The app requests push permissions at an appropriate step in the startup or onboarding.
    • [Frontend] The client retrieves a “push token” (aka device token on iOS) unique to the app installation and device via a native call (expo-notifications).
    • [Frontend] The client registers an push notification event handler with the mobile operating system (via expo-notifications) for foreground and background notifications separately.
      • Foreground notifications can be intercepted, stopped and rescheduled with different content/options.
      • Background notifications (app is running, but not visible) can be watched, but not stopped or rewritten (with expo-notifications).
      • Closed app notifications (app is not running, killed by the user or the operating system) cannot be watched or altered (with expo-notifications).
    • [Frontend/Backend] The client registers a “pusher” with the Matrix home server. The pushers URL points to the Matrix Push Gateway (Sygnal). The registration can also contain specifics about how the Matrix Push Gateway should deliver each notification.
  • How a push is triggered:
    • [Backend] When B sends a message event (or any other type of event, e.g. a chat invitation) to A, the Matrix home server will forward this event to the Matrix Push Gateway, together with As previously registered push token.
    • [Backend] The push gateway delivers a push notification to APNs and FCM, respectively, depending on the list of registered pushers (clients can register multiple device pushers). Please note that there are two types of push notifications. The push gateway only delivers “data messages”, but we use it’s capability to have default fields, to deliver notification content, which allows us to have default text in every notification, in case it is not intercepted (closed app and background state)
      • “Notification messages” are immediately shown to the user. Every push that has enough content (e.g. a title or a body) for display, is detected to be a notification message.
      • “Data messages” are to be processed by the app first which then can decide if e.g. to display a push notification or to do something else (e.g. increment the unread messages count badge) instead.
    • [Backend] APNs and FCM deliver the push notifications to A’s device.
    • [Frontend]
      • [Foreground] The previously registered push notification handlers are notified by the mobile operating system. Our custom application logic then processes the push notification payload and decides if a user-visible push notification shall be triggered or something else should happen.
      • [Background, Closed app] The notification is shown as is (with default data from the configured pusher).
    • [Frontend] Once A clicks on the notification the app starts (if it’s not already running) and redirects to the respective room of the notification. This is done by picking up the notification when the app is started via expo-notifications, or listening to interactions via a expo-notifications handler, while the app is running.

Transactional Push Notifications

The holi app receives transactional push notifications via the Novu notification cloud through platform-specific providers (Firebase Cloud Messaging, Apple Push Notification service).

Okuna has been extended with the Novu sdk and models for each Notification type (cf. openbook_notifications/notifications.py). These models are used in the application logic, send Novu events and trigger pre-configured Novu workflows.

Novu is configured via its console by the holi staff. If you want to contribute a new notification, please contact us.

At start of the mobile client, the DeviceRegistration component obtains and sends a users device token to Okuna, to be subscribed to Novu. To display push notifications to the user the transactional push notifications also use the expo-notification handlers as described above (cf. apps/mobile/notifications/initNotifications.ts).

info

For chat, these notifications are handled by the chat-specific Sygnal push-gateway instead of Novu for now.

Technical Learnings and Constraints

During the implementation of the push notification spike we had a lot of important learnings and identified some pitfalls. These are the most important ones:

  • The expo-notifications library does not support iOS push notifications via FCM. Therefore we needed to set up both APNs and FCM for push notification delivery, even though Sygnal could deliver only to FCM and FCM would be able to also deliver to iOS.
  • Sygnal has a bug that resulted in push tokens being rejected by APNs. The tokens generated by expo-notifications are base64-encoded. Sygnal then converted the base64-encoded token to hex encoding before sending it to APNs which resulted in a 400 (BadDeviceToken) response from APNs. The bug was fixed and a pull request opened with the Sygnal project. Until that PR might be processed Daniel Bimschas therefore created a build locally from his fork and uploaded it to the GitLab container registry. In case you have to fix something more and update the image, you need to check out e.g. the abovementioned fork. Please note that the fix was implemented in a feature branch branched off the last working release (v0.12.0). Continue there. To build and push the image:
    docker buildx build --platform linux/amd64 -t gitlab.holi.team:5050/app/holi-chat-server/sygnal_pushkey_fix . --file docker/Dockerfile
    docker login gitlab.holi.team:5050
    docker push gitlab.holi.team:5050/app/holi-chat-server/sygnal_pushkey_fix
    In order to be able to login you need to create a "personal access token" on your GitLab account. Use that token as password on the login command. See also Authenticate with the Container Registry and Personal access tokens.
  • Matrix' Synapse server by default denylists private IP ranges for certain outgoing connections (including push gateways). So if you ever have trouble with connectivity between Synapse and other systems, please check ip_range_blacklist and ip_range_whitelist settings (see Configuration Manual - Synapse).

Developing and Testing with Push Notifications

Push notifications can NOT be tested with Expo Go and NOT with simulators / emulators either. They simply don’t receive push notifications from FCM / APNs. As a result

  • For development, we need to use Expo development builds. A development build (in short) is a binary containing all native dependencies, almost identical to the binary we ship to app stores later. However, it does not contain the actual JavaScript code yet. It allows you to develop like Expo Go, by showing you a screen at the start, where you can select a running metro Server (the thing you start with yarn mobile:dev_client).
  • In order to be able to install an "ad-hoc distribution" build, you need to turn on "developer mode" on the iPhone. Please note that the menu point might only be available when the iPhone is plugged in and XCode is running.
    cd apps/mobile
    # for iOS: make sure to register your UDID and select all UDIDs registered to be included in the build
    # in the process of registering your device, you'll be asked to install a profile on your iOS device
    # without this profile, you won't be able to install the binary on your iOS device because it could not
    # verify the signature of the build
    npx eas-cli device:create
    npx eas-cli build --profile development --platform ios # or android or all
  • When the build is done, you can download and install the binary on your iOS / Android device.
  • A development build can NOT connect to the "normal" Expo dev server started with yarn mobile:dev. Instead you need to run yarn mobile:dev --dev-client. This can be run in parallel to the "normal".
  • Development builds only have to be recreated when native dependencies change, or (for iOS) when new device UDIDs are added to the Holi Moli developer account. Please note there is a maximum of 100 devices that can be registered.