Tutorial: Using Push Notifications

Serverless DatabaseNotifications Using Push Notifications

Getting the device identifier

Before invoking any Webcom API for subscribing through mobile push notifications, your app must provide the Webcom SDK with the device identifier. Getting this identifier must rely on the API of either the FCM library for Google-compliant Android devices and iOS devices or the HMS library for Huawei non Google-compliant Android devices.

In the FCM and HMS APIs, the device identifier we need is called FCM token or HMS token.

Not applicable for JavaScript API

Some configuration steps are required in your Android Studio project prior to using the FCM or HMS library.

At startup in your main Activity:

class MainActivity : AppCompatActivity() {
  override fun onCreate(savedInstanceState: Bundle?) {
    // ...

    // For FCM:
    FirebaseMessaging.getInstance().token.addOnSuccessListener { // it: String
      Webcom.deviceIdentifier = Webcom.DeviceIdentifier.Firebase(it)
    }
    // // For HMS:
    // val appId = AGConnectServicesConfig.FromContext(this@MainActivity).getString("client/app_id")
    // val token = HmsInstanceId.getInstance(this@MainActivity).getToken(appId, DEFAULT_TOKEN_SCOPE)
    // if (!token.isNullOrEmpty()) Webcom.deviceIdentifier = Webcom.DeviceIdentifier.Huawei(token)
    
    // ...

    Webcom.whenSdkIsInitialized {
        // ...
    }
  }
}

As the FCM or HMS token may change over time, it is also necessary to update the device identifier from the onNewToken(...) callback of the Firebase or HMS messaging service:

// For FCM:
class MyMessagingService : FirebaseMessagingService() {
    // ...
    override fun onNewToken(token: String) {
      Webcom.deviceIdentifier = Webcom.DeviceIdentifier.Firebase(token)
    }
    // ...
}
// For HMS:
class MyMessagingService : HmsMessageService() {
  // ...
  override fun onNewToken(token: String?, bundle: Bundle?) {
    if (!token.isNullOrEmpty()) Webcom.deviceIdentifier = Webcom.DeviceIdentifier.Firebase(token)
  }
  // ...
}

When the FCM or HMS token is renewed by the messaging service library (i.e. the onNewToken(...) callback is called), your application should remove all subscriptions previously added using the previous token and possibly add all relevant ones again using the new token.

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 {
        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
    }
}

When the FCM token is renewed by the messaging service library (i.e. the messaging(_:didReceiveRegistrationToken:) delegate method is called), your application should remove all subscriptions previously added using the previous token and possibly add all relevant ones again using the new token. To achieve that, it may use the updateDeviceTokenInAllSubscriptions(file:line:session:queue:then:) method.

Not applicable for REST API

Adding a subscription

With respect to subscription through realtime notifications, subscriptions through mobile push notifications have some limitations:

  • The size of the payload notifications sent through the targeted messaging service (FCM, APNs or HMS) is limited to 4 KBytes. If the size of the data to send is too large, the Webcom back end will "simplify" the notification content before sending it to the messaging service, and the client application, when receiving it, will have to query back the Webcom back end (typically using the standard read functions of the Webcom SDK) to retrieve the relevant data.
  • The size of the path of the subscribed data node is limited to 2048 bytes (including all / characters).
  • As this kind of subscription is aimed at remaining active even when the user closes her·his application on the mobile device (because such subscriptions are able to launch or wake up a closed or paused app), it must be limited in time. The default validity period of subscriptions is set up in the Webcom developer console ("notifications" tab) of a subscription. A subscription may also request a specific duration.
  • As notifications are relayed through third-party messaging services (FCM, APNs or HMS), their content should be ciphered to prevent from data leakages.
  • Subscribing requires a valid ongoing authentication. If no user is authenticated, then any subscription at any data node will fail.
  • Only one subscription can be done at a given data node with a given authentication. If another subscription is requested at the same data node with the same authentication, it automatically cancels and overwrites the previous one.
  • Although the same event types may be subscribed as with realtime notifications (value change, child addition, child change and child removal), no data subset may be queried.
Not applicable for JavaScript API

In Kotlin, subscribing through push mobile notifications is similar to subscribing through realtime notifications:

import com.orange.webcom.sdk.datasync.subscription.SubscribableEvent.*

val app = WebcomApplication.default
val manager = app.datasyncService.createManager()
val node = manager / "contacts"
var subscription: Subscription
node.subscribeThroughPushNotification(Value.Changed::class) { // it: WebcomResult<Subscription>
  when (it) {
    is WebcomResult.Success -> // the subscription has been accepted by the back end
      subscription = it.result
    is WebcomResult.Failure ->
      println("The subscription on CONTACTS has been rejected! ${it.error.message}")
  }
}

The event type to subscribe to is specified by the Kotlin class of one of the SubscribableEvent sub-interfaces.
By default, subscriptions will not cipher notifications. To do so, you must specify the cipher argument of the subscribeThroughPushNotification method.
The expirationDate or duration parameters make it possible to limit the subscription to a specific validity period. If not set, the default duration set up in the "notification" tab of the Webcom developer console is used.
Finally, the includesRevocation parameter allows notifying client applications when the subscription is revoked (because either it is expired or the client app explicitly unsubscribes).

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("subscription succeeded")
    case let .failure(error):
        print("subscription error:", error)
    }
}

Subscribing is implemented in REST using a POST request:

WEBCOM_APP="<your-app>"
AUTH_TOKEN="<Webcom-authentication-token>" # subscriptions require a valid authentication context
UID="<UID-of-the-user-authenticated-by-the-AUTH-TOKEN>"
KIND="mobile" // for google/fcm
KIND="huawei" // for HMS push kit
DEVICE_ID="<FCM-or-HMS-token>"

curl -X POST "https://io.datasync.orange.com/datasync/v2/$WEBCOM_APP/subscriptions/$UID?allowsUpdate=true" \
     -H "Content-Type: application/json" \
     -H "Authorization: Bearer $AUTH_TOKEN" \
     -d '{"notifFormatVersion": "v2", "kind":"'$KIND'", "mode":"value", "path":"/", "context":"'$DEVICE_ID'"}'

The JSON document passed in the request body may contain the following additional properties:

  • The mode and childEventFilter properties specify the subscribed event type (note that the latter property may combine several child events):
mode childEventFilter Subscribed event type
"value" Value Change
"noData" Value Change, except that the data of the subscribed node is never included within the sent notifications
"chidEvent" {"added":true} Child Addition
"chidEvent" {"changed":true} Child Change
"chidEvent" {"removed":true} Child Addition
  • The encryption property requests the back end to cipher the sent notifications. Possible values are: {"alg":"none"} (default, i.e. no encryption), {"alg":"A128CBC-HS256"}, {"alg":"A192CBC-HS384"}, {"alg":"A256CBC-HS512"}, {"alg":"none"}.
  • The receivesRevocations boolean property controls whether client applications are notified when the subscription is revoked (because either it is expired or the client app explicitly unsubscribes).
    If not set, revocations will be notified.
  • The expirationTimestampSeconds numeric property assigns a specific duration to the subscription.
    If not set, the default duration set up in the "notification" tab of the Webcom developer console is used.

When successful, the REST request returns a JSON document containing a referential description of the just created subscription. In particular, the id property gives the identifier of the subscription, to use further with other REST requests to update or delete the subscription.

The received JSON document may be further retrieved using a GET request:

SUBSCRIPTION_ID="<the-id-received-at-subscription>"

curl -X GET "https://io.datasync.orange.com/datasync/v2/$WEBCOM_APP/subscriptions/$UID/$SUBSCRIPTION_ID" \
     -H "Authorization: Bearer $AUTH_TOKEN"

Refreshing a subscription

If you need to extend a subscription beyond its expiration date, you have to refresh it before this date. As the Webcom back end manages at most one subscription per data node, authentication state and device, refreshing a subscription simply consists in adding a new one (therefore using the same API as shown in the previous section) to the same data node with the same authentication state.

For the REST API, refreshing a subscription is possible only if the allowsUpdate=true query parameter has been passed to the first adding request. Refreshing may also be done using a PUT request (see the REST API).

Removing a subscription

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

Not applicable for JavaScript API

In Kotlin, there are two ways of removing subscriptions through mobile push notifications:

  • Either you still hold the Subscription object previously returned by the subscribeThroughPushNotification(...) method. In this case, simply call its cancel() method:
subscription.cancel()
  • Or you don't hold the Subscription object. In this case, you can remove any existing subscription at a given data node:
val app = WebcomApplication.default
val manager = app.datasyncService.createManager()
val node = manager / "contacts"
node.cancelSubscriptionsThroughPushNotification { // it: WebcomResult<Subscription>
  when (it) {
    is WebcomResult.Success -> println("The subscriptions on CONTACTS have been canceled")
    is WebcomResult.Failure -> println("The cancelation of subscriptions on CONTACTS has failed! ${it.error.message}")
  }
}

In Swift, there are two ways of removing subscriptions through mobile push notifications:

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 don't hold the DataSubscription object. In this case, you can remove any existing subscription at a given DataNode:
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)
    }
}

In REST, there are two ways of removing subscriptions through mobile push notifications:

  • Either you still hold the subscription identifier returned by the request that previously added the subscription. In this case simply use a DELETE request. Note that the provided authentication token must refer to the same user UID as the one used to add the subscription:
WEBCOM_APP="<your-app>"
AUTH_TOKEN="<Webcom-authentication-token>" # an authentication context with the user identifier of the user that has added the subscription to remove 
SUBSCRIPTION_ID="<the-subscription-ID-returned-when-added-the-subscription>"

curl -X DELETE "https://io.datasync.orange.com/datasync/v2/$WEBCOM_APP/subscriptions/$UID/$SUBSCRIPTION_ID" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $AUTH_TOKEN"
  • Or you don't hold the subscription identifier. In this case, the request is the same as the one to add a subscription, with the expirationTimestampSeconds property of the passed JSON body set to 0. Note also that the provided authentication token must refer to the same user UID as the one used to add the subscription:
UID="<UID-of-the-user-authenticated-by-the-AUTH-TOKEN>"
KIND="mobile" // for google/fcm
KIND="huawei" // for HMS push kit
DEVICE_ID="<FCM-or-HMS-token>"

curl -X POST "https://io.datasync.orange.com/datasync/v2/$WEBCOM_APP/subscriptions/$UID?allowsUpdate=true" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $AUTH_TOKEN" \
  -d '{"kind":"'$KIND'", "mode":"value", "path":"/", "context":"'$DEVICE_ID'", expirationTimestampSeconds:0}'