Tutorial: Mobile Push Notifications

Serverless DatabaseNotifications Mobile Push Notifications

Webcom provides a "mobile push notification" function that allows a mobile application to subscribe to a data node of the Webcom database and to be notified when data under this node is updated even if the mobile application is stopped. In this case, the application is launched or woken up.

In usual settings, when data is updated somewhere in a Webcom database, it is shared in real time with every instance of Android app that runs in foreground on every connected device. However this operation relies on websockets, which die as soon as apps go to background.

The "mobile push notification" function overcomes this limitation by making it possible to notify a stopped or background Android app upon a data update in the Webcom database. It can also optionally forward data to it.

With this feature, you choose the path of the data node in the Webcom database that may wake up your Android app. This path covers updates on all its child data nodes. For example, if you subscribe to the root path, every change in the database will be notified.

Subscribing and unsubscribing are done using either methods of the Webcom Android SDK or Webcom specific REST requests. Then the waking up process uses Firebase Cloud Messaging (FCM, formerly Google Cloud Messaging) or Huawei HMS push kit for Android devices and Apple Push Notification service (APNs) for Apple devices.

Actually, your app asks Webcom to listen to some data node at a given path, and as soon as something happens under this path, Webcom asks FCM (or HMS for huawei notification only) to send an appropriate message to appropriate (subscribed) devices. Your device receives this message and has to handle it. Typically you can display it to the user through a notification or launch your app so that it resynchronizes itself using Webcom SDK API and detects what is new and what to do.

You also need to provide the FCM token (or HMS push token) to allow FCM (or HMS) to send the message to the right device. Retrieving this FCM token or HMS push token is done through FCM/HMS API on the device itself.

Mobile "push notification" developer configuration

Get the key from FCM

Mobile notification is a functionality provided by Google or Huawei for Android OS (FCM or HMS push kit) and by Apple for iOS (APNs). Webcom interconnects only with FCM (for google and Apple) and HMS for huawei. As FCM provides a relay service to APNs, APNs can also be used via Webcom and FCM. In order to use FCM (or APNs through the FCM relay), you need to configure a Google key in Webcom developer console.

To get such a key, first, go to Firebase console and create a project, then generate a new private key in project settings / Service account and download it.

In addition, if you want to use APNs, you need to follow this procedure.

Get configuration elements from Huawei console

Go to huawei console (in project settings / general information) and get "Project ID", "App ID" and "App secret".

Put it in your Webcom app

Go to notifications tab in Webcom developer console, enable mobile push functionality, and upload private key (from FCM), and/or for huawei: set "project_id" = "Project ID", "cliend_id" = "App ID" , "client_secret" = "App secret".

Add a mobile subscription

In order to set up wake-up subscriptions from your mobile app code, you can use either the Webcom Android SDK API, the Webcom iOS SDK API or the low-level REST API.

In both cases, you need the "FCM token" of the mobile device. You can get it using FCM API:

//for google/fcm:
String myDeviceId = FirebaseInstanceId.getInstance().getToken(); 

//For huawei/hms:
String appId = AGConnectServicesConfig.fromContext(context).getString("client/app_id");
String token = HmsInstanceId.getInstance(context).getToken(appId, DEFAULT_TOKEN_SCOPE);
import Firebase

// make the AppDelegate conforms to UNUserNotificationCenterDelegate and MessagingDelegate
final class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate, MessagingDelegate {
    // complete the `application(_:didFinishLaunchingWithOptions:)` method:
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // swiftlint:disable:this discouraged_optional_collection
        FirebaseApp.configure()
        Messaging.messaging().delegate = self
        UNUserNotificationCenter.current().delegate = self
        UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { _, _ in }
        application.registerForRemoteNotifications()
        return true
    }

    // add the following method in the AppDelegate:
    func messaging(_ messaging: Messaging, didReceiveRegistrationToken firebaseMessagingToken: String) {
        Webcom.deviceToken = firebaseMessagingToken
    }
}

Here is an example (replace “<your-app>” with your actual application identifier):


// coming soon : new android API

Webcom ref = new Webcom("<your-app>").child("some/webcom/path");
ref.wakeupSubscribe(myDeviceId, "value", new OnComplete() {
                @Override
                public void onComplete() {
                    Log.d(TAG, " wakeupSubscribe onComplete ");
                }

                @Override
                public void onError(final WebcomError error) {
                    Log.d(TAG, " wakeupSubscribe onError ", error.toException());
                }
            });

This sections should be updated with new API

In the Android version, the path of the subscription to register is given by the path associated to the Webcom object reference on which the wakeupSubscribe method is called.

The second parameter of this method is a subscription mode that controls the data to send back in future notifications. It can be either "value", "noData" (or null, this is the default value) or "childEvent" (see below).

The last parameter is a handler whose onComplete callback is called asynchronously once the wake-up subscription is successfully registered on the Webcom server. If the registration fails on the server, then the onError callback is called instead.

With this method, the subscription is registered using the current authentication state maintained by the Webcom SDK. The authentication state constraints the data nodes on which the subscriptions are allowed.

You need to use a DataNode instance:

let node = Webcom.defaultApplication.dataService.node(for: "some/webcom/path")
let descriptor = DataSubscriptionDescriptor(eventType: .valueChange)
node.subscribeThroughNotifications(descriptor: descriptor) { result in
    switch result {
    case .success:
        print("subcription succeeded")
    case let .failure(error):
        print("subcription error:", error)
    }
}

Here is an example (replace “<your-app>” with your actual application identifier):

KIND="mobile" // for google/fcm
KIND="huawei" // for HMS push kit

curl -X POST "https://io.datasync.orange.com/datasync/v2/<your-app>/subscriptions/<your-webcom-uid>?allowsUpdate=true" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <your-webcom-token>" \
  -d '{"kind":"$KIND", "mode" : "value", "path":"/", "context": "<FCM-token>"}' \
  -v

Please note that authentication is requiered to be able to add a "mobile-push" subscription. When authenticated on your app (please refer to authentication documentation) you get a "uid" and a "token", that you should use on this API.

In the body part, you have to give a "subscription" document (please refer to API documentation for details). The context field is mandatory and refers to the FCM token. Other fields control the Webcom the data (format, encryption, filters) to send back in future notifications.

Please see REST API documentations for fields details and returned information and error codes.

Notes:

  • data included in FCM, HMS push and APNs notifications are limited to 4KB
  • A subscription (mobile or webhook) is uniquely identified by a 6-tuple (Webcom app id, uid**,kind (=="mobile here"),destination (absent here),** path**,** context (FCM token)) (or the returned identifier, which is computed from this information).

Refresh a subscription

A subscription must be refreshed before expiration. Expiration time is configured through settings on Webcom developer console. To refresh a subscription, you can send the same request than the original subscription (do not forget allowsUpdate=true query parameter). Another solution is to send a PUT request on the subscription path (please see API doc for details).

If a subscription expires, an FCM message for revocation will be sent to subscribed devices with status "expired".

Device id (e.g. FCM token) refresh

When receiving FCM notification "onTokenRefresh", you have to

  • add a subscription with new FCM-token,
  • remove the subscription with former FCM-token to clean it on Webcom side.

Remove a mobile push subscription

Removing a subscription may be done like this (replace “<your-app>” with your actual application identifier):

// coming soon: new android API
Webcom ref = new Webcom("<your-app>").child("some/webcom/path");
ref.wakeupUnsubscribe(myDeviceId, new OnComplete() {
                @Override
                public void onComplete() {
                    Log.d(TAG, " wakeup UNSubscribe onComplete ");
                }

                @Override
                public void onError(final WebcomError error) {
                    Log.d(TAG, " wakeup UNSubscribe onError ", error.toException());
                }
            });

In the Android version, like for adding a subscription, the path of the subscription to remove is retrieved from the path associated to the Webcom object reference on which the wakeupUnsubscribe is called. This method expects as first parameter the "device id" (it must be the same than the one used with the wakeupSubscribe method). The second parameter is a handler whose onComplete callback is called asynchronously once the wake-up subscription is successfully unregistered on the Webcom server. If the unregistration fails on the server, then the onError callback is called instead.

You can use the DataSubscription instance returned by the subscribeThroughNotifications() method:

let node = ...
let descriptor = ...
let subscription = node.subscribeThroughNotifications(descriptor: descriptor) ...
subscription.cancel() { result in
    switch result {
    case .success:
        print("unsubcription succeeded")
    case let .failure(error):
        print("unsubcription error:", error)
    }
}

Or, you can directly use a DataNode instance:

let node = Webcom.defaultApplication.dataService.node(for: "some/webcom/path")
node.dataNode.unsubscribeFromNotifications { result in
    switch result {
    case .success:
        print("unsubcription succeeded")
    case let .failure(error):
        print("unsubcription error:", error)
    }
}

There are two variants to remove a subscription:


curl -X POST "https://io.datasync.orange.com/datasync/v2/<your-app>/subscriptions/<your-webcom-uid>?allowsUpdate=true" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <your-webcom-token>" \
  -d '{"kind":"mobile", "mode" : "value", "path":"/", "context": "<FCM-token>", expirationTimestampSeconds: 0}' \
  -v

OR with the subscription identifier get form "id" field or "Content-Location" header of subscription response:

curl -X DELETE "https://io.datasync.orange.com/datasync/v2/<your-app>/subscriptions/<your-webcom-uid>/<your-subscription-id>" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <your-webcom-token>" \
  -v 

Receive and parse a notification message (sometimes called notification) (v2 notification format)

When data is written on a path subscribed for "mobile-push" in the Webcom database, Webcom sends FCM data messages to notify your application (see documentation on FCM messages).

For more details please refer to the official FCM documentation.

Notification message format

The notification message encoding is described below. Please note that if you use Android or iOS SDK, you don't need to you don't need to decode it yourself, please see relevant section (comming soon).

Data contained in notification has the following structure:

  • a srv (service) string field with a fixed value "webcom" (may allow distinction with other notification sources),
  • a kid (key identifier) string field that will contain the key identifier when encryption is enabled,
  • a evt (event) string field, JOSE serialized object (may be encrypted JWE) containing the notified event itself (described below),
  • a ver (version) string field, giving the notification format version ("v2" is the default one documented in this section)
The event itself is JOSE or JWE encoded and has the following structure: - a `t` (type) string field with value `r` (revoke) for revocation message or `v` (value changed) for data events, - a `m` (mode) optional string field (present for type=v) containing the subscription mode ("value", "noData" or "childEvent") - a `a` (appid) string field containing the application identifier - a `v` (value) optional opaque object field (for m=value only) containing new data at subscribed path - a `c` (child details) optional object field (for m=childEvent only) containing child details about this event - a `r` (reason) optional string field (for t=r only) containing revocation reason (an error code), - a `s` (status) optional field whose string value can be "`OK`", "`OnlyKeys`" or "`TooBig`" if the payload is too big and userData has been removed (see [limitations](#limitations))

The child details object field has the following structure:

  • a a (added) mapping object field containing created child name associated with values,
  • a r (removed) array field contains deleted child names,
  • a c (changed) mapping object field containing changed chiled name associated with new values.

... coming soon ...

Receive and parse a notification message (sometimes called notification) (legacy v1 notification format)

In "noData" mode

The device is just awaken and can reconnect through Webcom API to read needed data and resynchronize.

The data payload sent by Webcom has the following structure:

  • a t (type) field with string value "noData"
  • an app string field containing the Webcom application name
  • an sp string field containing the subscription path within the Webcom database

In "value" mode

The data contains the complete new value of the data node at subscription path.

The data payload sent by Webcom has the following structure:

  • a t (type) field with string value "value"
  • an app string field containing the Webcom application name
  • an sp string field containing the subscription path within the Webcom database
  • a d (userData) field with a string value that contains a JSON encoded object (due to FCM limitations) representing the current data at the subscribed path. This field may be omitted if the payload size is too big (see limitations).
  • an s (status) field whose string value can be "OK" or "TooBig" if the payload is too big and userData has been removed (see limitations)

In "childEvent" mode

The data payload sent by Webcom has the following structure:

  • a t (type) field with string value "childEvent"
  • an app string field containing the Webcom application name
  • an sp string field containing the subscription path within the Webcom database
  • an s (status) field whose string value can be "OK" or "TooBig" if the payload is too big and userData has been removed (see limitations)
  • an added field with a string value that contains a JSON encoded object representing the data of the added child nodes. This field may be omitted if the payload size is too big (see limitations).
  • a removed field with a string value that contains a JSON encoded array containing the keys of the removed children. This field may be omitted if the payload size is too big (see limitations).
  • a changed field with a string value that contains a JSON encoded object representing the data of the changed child nodes. This field may be omitted if the payload size is too big (see limitations).

Subscription revocation

When a subscription is revoked on the Webcom server, a FCM data message is sent to each subscribed device with the following data payload structure:

  • a t (type) field with string value "revoke"
  • a r (reason) string field containing the status code of the revocation cause (see below)
  • an app string field containing the Webcom application name
  • an sp string field containing the subscription path within the Webcom database

Revocation causes are:

Status code Description
permission_denied Security rules prevent read access to the data located at the subscribed data node (according to current data and the specified authentication token)
namespace_does_not_exist The Webcom application database has just been deleted
disabled Mobile wake-up feature has just been disabled for the Webcom application
expired Subscription has expired
too_big The data located at the subscribed data node is too big and can no longer be subscribed (see weight limitations) because it would result in unreasonable performances

Size limitations

Data payload size limit

The data payload of wake-up messages is limited to 4KB on both FCM and APNs.

More precisely, the whole data payload of a wake-up message is defined by a JSON compact-serialized string. And the length of this string should not be more than 4093 bytes. For example, the following data payload is 114 bytes-long:

{"s":"OK","t":"value","app":"toto","sp":"/some/webcom/path","d":"{\"a\":\"WGj2WtQWVEjQGhQqb7r678xW3PwWmB4Z3zj\"}"}

If the length is 4064 bytes-long or more:

  • in "value" mode: the status field (s) is set to "TooBig" and the userData field (d) is removed
  • in "childEvent" mode:
    • if possible, the status field (s) is set to "OnlyKeys", both added and changed fields contain only keys with true as mapped values,
    • otherwise (the length of the data payload with only keys and true as value is 4064 bytes or more) the status field (s) is set to "TooBig" and added, removed and changed fields are empty.

When data is missing because of payload size limitation, the only way for application to retrieve it is to invoke explicitly read functions of the Webcom SDK API.

Subscription path length limit

Subscription path length cannot be more than 2048 bytes.

Errors when sending notification to FCM servers

In case of error returned by FCM server. If it is recoverable ("QUOTA_EXCEEDED", "UNAVAILABLE", "INTERNAL", "UNSPECIFIED_ERROR"), it is trying again with exponential backoff starting after 1 second, and during 2 hours. If after that it still fails, the current pending notifcations are discarded but the subscription remains active for future notifications.

In case of other error, the subscription is revoked, and if possible, a "revoke" notification is sent.