Skip to content

Delivery improvements and rich content notifications

Ivan Bilobrk edited this page Jan 23, 2025 · 3 revisions

Notice

The following documentation is intended for Mobile Messaging Cordova Plugin version 6.0.0 and higher. If you are using an older version of the plugin where Carthage was used instead of CocoaPods, please refer to the previous version of this documentation.

In order to achieve full control over the push notification content that is displayed to the users, and offer Rich Notifications actions, you need a Notification Service Extension. To learn more about Notification Service Extension please see official Apple documentation about Notification Service Extension. With it you'll be able to attach static or animated (GIF) images, or even a video, to push notifications, as well as modify the notification text content prior to showing it to the user. But what is more important: Notification Service Extensions can run arbitrary code in background even if your main application is killed by the user. This feature gives us an opportunity to improve message handling and delivery reporting.

In this tutorial you will learn how to integrate the Notification Service Extension with your existing application.

Warning

  • Please make sure you've committed all your project changes before going further!

Setting up App Group ID for an App ID

As a prerequisite, you need to register a new App Group with a unique App Group Id on this site: https://developer.apple.com/account/ios/identifier/applicationGroup. The App Group is needed for your application and Notification Service Extension to exchange important data via shared data container, remember this ID for further integration steps.

Enabling Notification Service Extension in iOS for rich content and reliable delivery

  1. Notification Service Extension must be be configured for iOS platform (to display rich content in the push notification and have reliable delivery reporting). You need to add following parameters to your config.xml under the plugin section to automatically integrate Notification Service Extension, in the spec parameter provide current plugin version:

    ...
        <plugin name="com-infobip-plugins-mobilemessaging" spec="<current plugin version>">
    
            <variable name="IOS_EXTENSION_APP_CODE" value="{YOUR APPLICATION CODE FROM INFOBIP PORTAL}" />
            <variable name="IOS_EXTENSION_APP_GROUP" value="{APP GROUP ID FROM APPLE PORTAL}" />
    
        </plugin>
    ...

    Right after calling cordova prepare ios or cordova build ios Notification Service Extension target will be added to the project, development Team will be taken from main target build setting and automatic signing will be enabled by default.

    Notice

    iOS Notification Service Extension is configured for your application automatically by the plugin hooks. Plugin needs to know main iOS target for the project and the path to the iOS project. Cordova application name and platforms\ios\{applicationName}.xcodeproj will be used by default. If you need to change these default settings, you can use IOS_EXTENSION_PROJECT_MAIN_TARGET and IOS_EXTENSION_PROJECT_PATH parameters respectively. You can set these parameters in config.xml under the plugin section or provide them via command line when building the project.

  2. After adding the Notification Service Extension, you will need to make changes to the Podfile. Specifically, you need to tell the Notification Service Extension to use the same search paths as your main application target. Since Cordova generates new Podfile after each cordova prepare ios, cordova build ios and similar commands, manual changes to the Podfile are not a good option. See linked GitHub issue for more info. There are two ways you can ensure that required Podfile modifications remain even after such commands:

    2.1. Make changes to the node_modules/cordova-ios/lib/Podfile.js file. Find the function Podfile.prototype.getTemplate and make modifications so it adds the needed lines to the Podfile inside your main application target part:

       // **Warning:** Replace '<Your Notification Service Extension Name>' with your actual Notification Service Extension Name.
                '\ttarget \'<Your Notification Service Extension Name>\' do\n' +
                '\t\tinherit! :search_paths\n' +
                '\tend\n' +
                `\n`

    The whole function should look similar to this:

    Podfile.prototype.getTemplate = function () {
     // Escaping possible ' in the project name
     const projectName = this.escapeSingleQuotes(this.projectName);
     return util.format(
         '# DO NOT MODIFY -- auto-generated by Apache Cordova\n' +
             '%s\n' +
             'platform :ios, \'%s\'\n' +
             '%s\n' +
             'target \'%s\' do\n' +
             '\tproject \'%s.xcodeproj\'\n' +
             '%s\n' +
    // **Warning:** Replace '<Your Notification Service Extension Name>' with your actual Notification Service Extension Name.
             '\ttarget \'<Your Notification Service Extension Name>\' do\n' +
             '\t\tinherit! :search_paths\n' +
             '\tend\n' +
             'end\n',
         this.sourceToken, this.minDeploymentTarget, this.declarationToken, projectName, projectName, this.podToken);
    };

    2.2. Second option is to write custom after_prepare hook that will modify the Podfile and specify it in the config.xml file of your application:

    <hook type="after_prepare" src="path/to/your/hook.js" />

    The recommended place to put your hook is in the scripts folder of your Cordova project. The hook should look similar to this:

    Notice

    Please check comments in the code below and replace placeholders with your actual values.

    const fs = require('fs');
    const path = require('path');
    
    module.exports = function (ctx) {
        if (ctx.opts.platforms.indexOf('ios') < 0) { // project doesn't support ios at all
            return;
        }
        if (ctx.opts.cordova.platforms.length > 0 && ctx.opts.cordova.platforms.indexOf('ios') < 0) { // cordova prepare was explicitly called for non-ios platforms
            return;
        }
    
        var ConfigParser = ctx.requireCordovaModule('cordova-common').ConfigParser;
        var appConfig = new ConfigParser(path.join(ctx.opts.projectRoot, 'config.xml'));
        var appName = appConfig.name();
    
        if (ctx.opts.options === undefined) {
            console.log("WARNING: iOS platform is not added, mobile messaging plugin can't proceed. Call 'cordova prepare ios' after which ios platform will be added.");
            return;
        }
        
        var projectMainTarget = ctx.opts.options.IOS_EXTENSION_PROJECT_MAIN_TARGET || appName;
    
        if (!projectMainTarget) {
            console.log("ERROR: 'IOS_EXTENSION_PROJECT_MAIN_TARGET' not defined");
            console.log('-----------------------------');
            return;
        }
    
        const podfilePath = path.join(ctx.opts.projectRoot, 'platforms', 'ios', 'Podfile');
        if (!fs.existsSync(podfilePath)) {
            console.log('ERROR: Podfile not found. Make sure you have added ios platform.');
            return;
        }
    
        let podfileContent = fs.readFileSync(podfilePath, 'utf8');
    
    // **Warning:** Replace '<Your Notification Service Extension Name>' with your actual Notification Service Extension Name.
        const extensionSnippet = `     
         target '<Your Notification Service Extension Name>' do
            inherit! :search_paths
        end
    `;
    
         const marker = `target '${projectMainTarget}' do`;
         const index = podfileContent.indexOf(marker);
         if (index !== -1) {
             const insertPos = podfileContent.lastIndexOf('\n', podfileContent.indexOf('end', index)) + 1;
             podfileContent =
                 podfileContent.slice(0, insertPos) +
                 extensionSnippet +
                 podfileContent.slice(insertPos);
         } else {
             podfileContent += extensionSnippet;
         }
    
        fs.writeFileSync(podfilePath, podfileContent, 'utf8');
         
        var command = `
            cd ${path.join(ctx.opts.projectRoot, 'platforms', 'ios')};
            pod install;
            pod update;
            cd ${ctx.opts.projectRoot};
        `;
        var exec = require('child_process').exec;
        exec(command,
            function (error, stdout, stderr) {
                if (stdout) {
                    console.log('stdout: ' + stdout);
                }
                if (stderr) {
                    console.log('stderr: ' + stderr);
                }
                if (error !== null) {
                    console.log('exec error: ' + error);
                }
            });
        console.log('Podfile updated for Notification Service Extension.');
    };

    The provided hook will look for your main application target in Podfile and add the Notification Service Extension target modification at the end of it.

  3. Now you can run commands such as cordova prepare ios or cordova build ios. Be sure to check that the generated Podfile looks similar to this:

    use_frameworks!
    target 'YourProjectsMainTarget' do
        pod 'MobileMessaging'
         
        target '<Your Notification Service Extension Name>' do
            inherit! :search_paths
        end
    end

Enabling Notification Service Extension in iOS for rich content and reliable delivery using CocoaPods without use_frameworks! option

Requirements

  • Ruby 2.3.8 or higher to run "mmine" command.
  1. From the root of your iOS project install and run our special Ruby Gem called "mmine", passing appropriate parameters:

    $ sudo gem install mmine
    $ mmine integrate \
    --application-code <your Push Application Code from Infobip portal> \
    --project <absolute path to your Xcode project (.xcodeproj file)> \
    --app-group <your App Group Id> \
    --target <name of your projects main target> \
    --static-linkage \
    --Cordova

    This tool will automatically integrate Mobile Messaging Notification Service Extension into your Xcode project. Your Application Code will be automatically injected into NotificationService.swift source file as a hardcoded string parameter for MobileMessagingNotificationServiceExtension.startWithApplicationCode(_) method call.

  2. Please continue to follow instructions from previous section.

Troubleshooting and Customising Push Notification Content

Notice

If you are facing the following error in your console: [User Defaults] Failed to read values in CFPrefsPlistSource<0xXXXXXXX> (Domain: ..., User: kCFPreferencesAnyUser, ByHost: Yes, Container: (null)): Using kCFPreferencesAnyUser with a container is only allowed for SystemContainers, detaching from cfprefsd.

Although this warning doesn't mean that our code doesn't work, you can turn it off by prefixing your App Group ID with a Team ID of a certificate that you are signing the build with. For example: "example.group.com.mobile-messaging.notification-service-extension". The App Group ID itself doesn't need to be changed though.

Sending content

In order to receive media content through Push messages, you need to create a new campaign on Customer portal or send a message through Push HTTP API with contentUrl parameter.

Rich notification - CUP - step 1 Rich notification - CUP - step 2 Rich notification - CUP - step 3

Receiving on Android

Provided image will be displayed in the notification drawer where default rich notification’s design correlates with OS version. As of API 16 - Jelly Bean, image downloaded from provided URL will be displayed not only in normal view, but also in expanded, big view.

Rich notification - Android 7.1 Rich notification - Android 4.4
Preview of rich notifications on Android 7.1 and Android 4.4

Receiving on iOS

Provided content will be displayed on devices with iOS 10.+ in the notification center.

Rich notification - iOS10
Clone this wiki locally