Search code examples
iosapache-flexairapple-push-notificationsonesignal

Critical Alert support on OneSignal not working


We are having trouble using the new iOS Critical Alerts feature with OneSignal. The app we have is built using Apache Flex and the push notification payloads are being compiled/generated through Distriqt ANE and sent through the OneSignal iOS SDK. We have been approved by Apple for Critical Alerts but when attempting to send the payload through OneSignal, it does not work.

The Distriqt team has built in support for Critical Alerts, which is great, but once it is sent through OneSignal, the payload isn't delivered successfully and the critical alert is not generated. To be clear, general push notifications work fine.

OneSignal has documentation on Critical Alerts, though it is over simplified. The payload needed to trigger Critical Alerts on iOS is clear but there is no way to construct a payload like this through the OneSignal iOS SDK.

In the link, we have performed #1 (Update OneSignal App Payload Structure for iOS) but #2 is unclear. What does the payload need to look like? The code provided does not make that clear. Additionally, what is the referenced 'notification category extension' and if that code is identified, what are we needing to do at that point?

We have contacted OneSignal and have been unsuccessful in understanding how to successfully construct this payload. The documentation says 'iOS features OneSignal SDK supports' but from their support we received this:

Unfortunately our SDK is not setup to handle critical alerts.

One user reported he achieved this with our additional data parameter like this:

"data": {"CRITICAL_ALERT":"YES","CRITICAL_PAYLOAD":{"CRITICAL_VOLUME":"1.0","CRITICAL_SOUND":"Alert.wav"}}

Though that doesn't work. Although a relatively new feature, I am hoping someone in the community has been successful at this and can help. Thanks!


Solution

  • TL;DR Building an iOS Notification Service Extension is the solution. The OneSignal documentation mentions this but in far less detail.


    When Critical Alerts was introduced by Apple, they changed the sound parameter that is passed through with an APNS payload. Previously, the sound was only ever a string. For example:

    { 
        “aps” : { 
            “sound” : “critical-alert-sound.wav”
        }
    }
    

    With Critical Alerts, it is a dictionary. Example:

    { 
        “aps” : { 
            “sound” : { 
                “critical”: 1, 
                “name”: “critical-alert-sound.wav”, 
                “volume”: 1.0 
            } 
        }
    }
    

    The string version is still valid and is used for non-critical alerts. Based on inference and testing, when passing the sound parameter to OneSignal, it only supports the initial 'string' version. When OneSignal sends the payload to Apple, it passes it as a string thus even trying to pass a sound dictionary to OneSignal won't work because it gets parsed down before it gets to Apple. By the time Apple communicates back with your device, the dictionary is gone, thus preventing the device from ever recognizing it as a Critical Alert.

    This is where the Notification Service Extension comes in. From Apple's documentation:

    A UNNotificationServiceExtension object provides the entry point for a Notification Service app extension, which lets you customize the content of a remote notification before it is delivered to the user. A Notification Service app extension doesn't present any UI of its own. Instead, it is launched on demand when a notification of the appropriate type is delivered to the user’s device. You use this extension to modify the notification’s content or download content related to the extension.

    In short, you can intercept the notification JSON payload coming from Apple and modify it just before the user sees it.


    In order for the information to be passed to the device correctly from OneSignal->Apple->Device, you need to:

    1) Set the additional_data_is_root_payload value on your OneSignal account to true. This is done through an Update an App API call on OneSignal. I used Postman for this. To be clear, this is needed to be done one time, it is not something needed to be repeated every time you make an notification API call.

    2) In your OneSignal API payload, set the mutable_content parameter to true.

    As mentioned above, you can use values in the OneSignal data parameter of the payload. The data payload is an open field that can be used for any additional information you want to pass through the OneSignal->Apple->Device flow and does get delivered to the device/app which you can then parse however you would like. In my example, we use:

    "data": {"CRITICAL_ALERT":"YES"}
    

    The data payload is arbitrary, it just needs to match the checks you do in the Notification Service Extension.


    We then create our Notification Service Extension in XCode. There are some great step-by-step instructions here on creating an extension. This is done through XCode so if you are building a native app, it's simply done through your XCode project for your application. If you are using a framework like Adobe AIR, it is quite a bit more complicated and I will detail that in a different post. Keep in mind that the Notification Service Extension is basically a separate 'app' bundled with the parent app. It is compiled into a file with extension 'appex' and even though it is a separately bundled binary, it is specifically targeted to your parent app.

    Once the extension is created, your XCode project will have a file named NotificationService.swift with a class that has the didReceive method within it. We then added this code:

    self.contentHandler = contentHandler
    bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
    
    if let bestAttemptContent = bestAttemptContent {
    
    // Modify the notification content here...
        if ((request.content.userInfo["CRITICAL_ALERT"] as? String) == "YES"){
                bestAttemptContent.sound = UNNotificationSound.defaultCriticalSound(withAudioVolume: 1.0)
        }
    
        contentHandler(bestAttemptContent)
    }
    

    You can see that we check the data payload (request.content.userInfo) to see if the CRITICAL_ALERT key is set with a value of YES. If so, we add a 'critical sound' to the notification which effectively turns it into a critical alert. In this case, we kept it simple with the defaultCriticalSound function but you can also use criticalSoundNamed if you want to define your own sound. You could also pass the sound you want to use for the alert through the data payload and then parse it and add it in the Notification Service extension if you wanted to create specific sounds for specific notifications.


    At this point, we tested this through the OneSignal interface once we had deployed the app to our devices. This can be accessed by logging into your OneSignal account and then going to 'Messages' -> 'New Push' and choosing the devices you would like to send the push to:

    enter image description here

    Hopefully this is helpful to others using OneSignal for Critical Alerts (and hopefully they update their system soon to no longer need a Notification Service Extension).