OneSignal / OneSignal-Cordova-SDK

OneSignal is a free push notification service for mobile apps. This plugin makes it easy to integrate your Ionic, PhoneGap CLI, PhoneGap Build, Cordova, or Sencha Touch app with OneSignal. Supports Android, iOS, and Amazon's Fire OS platforms. https://onesignal.com
Other
250 stars 197 forks source link

Feature Request: Set Onesignal config without Xcode editing #537

Open Aarbel opened 5 years ago

Aarbel commented 5 years ago

https://documentation.onesignal.com/docs/cordova-sdk-setup

In your documentation you use many detours to don't use Cordova Core, like pure iOS distribution settings with Xcode (especially for iOS Service Extensions).

This is not sustainable with Cordova, because we rebuild the project everytime we add JS or settings in our Cordova App. So very very huge waste of time to all re-configure Xcode on each build ie 10 times a day.

Cf many users have conflicts with the "raw" settings that this SDK requires, ex: https://github.com/apache/cordova/issues/129

Do you plan to ship a fully "autonomous" plugin for iOS ?

Thanks a lot for your help !

jkasten2 commented 5 years ago

@Aarbel Support to add iOS App Extensions is something we were hoping Cordova would add. There is an open ticket for a feature request for this but there hasn't been any recent updates. https://issues.apache.org/jira/browse/CB-10218 If this was added as a feature to Cordova it would then also work on cloud builds like Adobe PhoneGap Build (PGB) where it might not be possible to modify the Xcode project, even programmatically.

Feel free to open another ticket about the "CordovaError: Could not find *-Info.plist file, or config.xml file." error. Please provide exact steps to reproduce and any additional information.

Aarbel commented 5 years ago

Thank you @jkasten2,

this issues exists since 2015, to you have a way to reach Cordova teams directly ?

Have you checked this plugin : https://github.com/dpa99c/cordova-custom-config ?

codinronan commented 5 years ago

Basically this happens because adding the notification service adds another config entry to the plist in index 0, and moves the cordova app to index 1. Problem is that cordova's scripts are hardcoded to look for the cordova app in index 0.

Really I just blow away platform and re-add every time, it takes about 10 minutes at this point that I've done it so many times. But yeah it is annoying...

rwillett commented 4 years ago

We have managed to automate some (but not all) elements in creating a OneSignal plugin and adding it to Xcode.

We have done this through a combination of scripting using Perl to generate files:

  1. Some are created by invoking Ionic and executing through Perl.
  2. Some files are updated using XML transformations (e.g. the config.xml files and the .plist files.
  3. The update to the NotificationService.m file is by modifying the file directly.

The biggest problem for us is Xcode. We have automated a lot of the stuff using Xcode and Keyboard Maestro. We invoke Keyboard Maestro from Perl using osascript.

However using KM on Xcode is fraught with problems as Xcode 11 is a complete mess. The use of twisties means you have to know exactly what state everything is and that's really only possible the very first time you run it.

If there is any to automate adding the OneSignal.framework to Xcode on the command line, we'd love to know how to do it as thats the biggest problem at the moment.

rwillett commented 4 years ago

[UPDATE] This code is left here in case somebody else can make it work properly. We could not get all the frameworks to load AND Xcode did weird things.

Rob


We have now progressed a lot further with automating adding the OneSignal 'stuff' to Ionic. We have shamelessly used other peoples work, specifically the Swrve code. All credit to them to putting this together. We cannot claim any credit here.

So the code below is run as a hook, after_platform_add. It creates the correct Xcode Target, it creates the right NotificationService.m (based on the code in the OneSignal docs).

It adds in three of the five frameworks. It does not add in the OneSignal.framework as we can't work out how to do it. It also does not add in SystemConfiguration.framework for some reason that we cannot understand at this moment.

It does not create an Entitlements.plist as we don't understand that. To be fair we don't understand a lot of this here...

This code is really raw, we have hacked it around to try and get it work based on our own limited and probably understanding of the npm module xcode. It may well cause your hair to fall out or grow in weird places for all we know. We have commented out bits we don't understand. All credit to the Swrve team for this though, any issues or problems, blame me :)

If anybody can shed light on why SystemConfiguration.framework doesn't work, or how to add in the OneSignal.framework that would be great.

One other thing we have learnt is that we can set a lot of the IPHONEOS_DEPLOYMENT_TARGET from here. We're still experimenting but wanted to share this as somebody may be able to help.

Rob

const fs = require('fs');
const path = require('path');
const xcode = require('xcode');
var appConfig;
var debug = 1;

function copyRecursiveSync(srcPath, destPath)
{
    let exists = fs.existsSync(srcPath);
    let stats = exists && fs.statSync(srcPath);
    let isDirectory = exists && stats.isDirectory();

    if (debug)
    {
    console.log(`copyRecursiveSync srcPath=${srcPath} destPath=${destPath}`);
    }

    if (exists && isDirectory) {
    fs.mkdirSync(destPath);
    // in a sync way we just check for each file in the folder and if its a directory call the same method recursive to copy its content as well
    fs.readdirSync(srcPath).forEach(function(childItemName) {
        copyRecursiveSync(path.join(srcPath, childItemName), path.join(destPath, childItemName));
    });
    } else {
    fs.linkSync(srcPath, destPath);
    }
}

function cordovaAppConfigForContext(context)
{
    const cordovaCommon = context.requireCordovaModule('cordova-common');
    return new cordovaCommon.ConfigParser('config.xml');
}

module.exports = function(context) {
    if (1)
    {
    console.log('Adding push extensions started');
    appConfig = cordovaAppConfigForContext(context);

    if (0 && debug)
    {
        console.log("appConfig = " + JSON.stringify(appConfig , null , 2));
    }

    iosSetupServiceExtension();

    // iosSwrveFrameworkEdit();
    }
};

function isEmptyString(str)
{
    return !str || 0 === str.length;
}

function cordovaPackageNameForPlatform(appConfig, platform)
{
    // check for the existence of platform-specific id, if it's not there fallback to basic id
    var packageName;

    if (platform == 'ios') {
    packageName = appConfig.ios_CFBundleIdentifier();
    }

    if (platform == 'android') {
    packageName = appConfig.android_packageName();
    }

    if (isEmptyString(packageName)) {
    packageName = appConfig.packageName();
    }

    return packageName;
}

function iosSetupServiceExtension() {
    if (debug)
    console.log("iosSetupServiceExtension started");

    // const appGroupIdentifier = appConfig.getPlatformPreference('swrve.appGroupIdentifier', 'ios');
    const appName = appConfig.name();
    const packageName = cordovaPackageNameForPlatform(appConfig, 'ios');
    const iosPath = 'platforms/ios/';
    const projPath = `${iosPath}${appName}.xcodeproj/project.pbxproj`;
    const extName = 'OneSignalNotificationServiceExtension';
    var extFiles = [ 'NotificationService.h', 'NotificationService.m', `${extName}-Info.plist` ];

    if (debug)
    {
    console.log("iosSetupServiceExtension: appName = '" + appName + "'");
    console.log("iosSetupServiceExtension: packageName = '" + packageName + "'");
    console.log("iosSetupServiceExtension: iosPath = '" + iosPath + "'");
    console.log("iosSetupServiceExtension: projPath = '" + projPath + "'");
    console.log("iosSetupServiceExtension: extName= '" + extName + "'");
    console.log("iosSetupServiceExtension: extFiles = '" + JSON.stringify(extFiles , null , 2) + "'");
    }

    /* if (!swrveUtils.isEmptyString(appGroupIdentifier))
    {
       extFiles.push(`Entitlements-${extName}.plist`);
    } */

    // The directory where the source extension files and common are stored
    const sourceDir = `scripts/extensions/ios/${extName}/`;

    if (debug)
    {
    console.log("iosSetupServiceExtension: sourceDir = ${sourceDir}");
    }
    // const sourceDir = `plugins/cordova-plugin-swrve/platforms/ios/${extName}/`;
    // const swrveSDKCommonDirectory = path.join(`${appName}`, 'Plugins', 'cordova-plugin-swrve');

    const proj = xcode.project(projPath);
    proj.parseSync();

    let NoServiceExtensionYet = true;

    if (!fs.existsSync(`${iosPath}${extName}`)) {
    console.log(`Adding ${extName} Push Service Extension to ${appName}...`);
    // Copy in the extension files
    fs.mkdirSync(`${iosPath}${extName}`);
    } else {
    NoServiceExtensionYet = false;
    console.log(`Swrve: ${extName} already exists at ${iosPath}`);
    }

    try {
    if (debug)
        console.log("iosSetupServiceExtension: NoServiceExtensionYet = " + NoServiceExtensionYet);

    if (NoServiceExtensionYet) {
        extFiles.map((file) => copyRecursiveSync(`${sourceDir}${file}`, `${iosPath}${extName}/${file}`));

        // Add a target for the extension
        let extTarget = proj.addTarget(extName, 'app_extension');

        // Create new PBXGroup for the extension
        let extGroup = proj.addPbxGroup(extFiles, extName, extName);

        // Add to PBXGroup under to CustomTemplate so files appear in the file explorer in Xcode
        let groups = proj.hash.project.objects['PBXGroup'];
        Object.keys(groups).forEach(function(key) {
        if (groups[key].name === 'CustomTemplate') {
            proj.addToPbxGroup(extGroup.uuid, key);
        }
        });

        // Add build phases
        proj.addBuildPhase([ 'NotificationService.m' ], 'PBXSourcesBuildPhase', 'Sources', extTarget.uuid);
        proj.addBuildPhase([], 'PBXResourcesBuildPhase', 'Resources', extTarget.uuid);

        // Add a new PBXFrameworksBuildPhase for the Frameworks used by the widget (NotificationCenter.framework, libCordova.a)
        var frameworksBuildPhase = proj.addBuildPhase(
        [],
        'PBXFrameworksBuildPhase',
        'Frameworks',
        extTarget.uuid
        );
        if (frameworksBuildPhase) {
        console.log('Successfully added PBXFrameworksBuildPhase!', 'info');
        }

        // Add the frameworks needed by our widget, add them to the existing Frameworks PbxGroup and PBXFrameworksBuildPhase
        var frameworkFile1 = proj.addFramework(
        './platforms/ios/pods/OneSignal/iOS_SDK/OneSignalSDK/Framework/OneSignal.framework',
        { target: extTarget.uuid }
        );

        if (frameworkFile1)
        console.log("'OneSignal.framework' added\n");

        var frameworksToAdd = [ 'SystemConfiguration.framework' , 'WebKit.framework' , 'UIKit.framework' , 'CoreGraphics.framework'];
            for (var i = 0 ; i < frameworksToAdd.length ; i++)
            {
                var value = frameworksToAdd[i];
                // Add the frameworks needed by OneSignal, add them to the existing Frameworks PbxGroup and PBXFrameworksBuildPhase
                var frameworkFile2 = proj.addFramework(value , { target: extTarget.uuid });

                if (frameworkFile2)
                    console.log("Framework " + value + " added");
                else
                    console.log("Framework " + value + " NOT added");
            }
    }

    if (debug)
        console.log("Now iterate through XCBuildConfig");

    // Iterate through the entire XCBuildConfig for config of the new target PRODUCT_NAME and modify it
    var config = proj.hash.project.objects['XCBuildConfiguration'];
    for (var ref in config) {
        if (config[ref].buildSettings !== undefined &&
        config[ref].buildSettings.PRODUCT_NAME !== undefined &&
        config[ref].buildSettings.PRODUCT_NAME.includes(extName))
        {
        console.log(`entered the setting: ${config[ref].buildSettings.PRODUCT_NAME} of ${ref}`);

        var INHERITED = '"$(inherited)"';
        if (!config[ref].buildSettings['FRAMEWORK_SEARCH_PATHS'] ||
            config[ref].buildSettings['FRAMEWORK_SEARCH_PATHS'] === INHERITED)
        {
            proj.hash.project.objects['XCBuildConfiguration'][ref].buildSettings['FRAMEWORK_SEARCH_PATHS'] = [
            INHERITED
            ];
        }

        // Set entitlements
        /* if (!swrveUtils.isEmptyString(appGroupIdentifier)) {
            proj.hash.project.objects['XCBuildConfiguration'][ref].buildSettings[
            'CODE_SIGN_ENTITLEMENTS'
            ] = `"$(PROJECT_DIR)/${extName}/Entitlements-${extName}.plist"`;
        } */

        // Fix issues with the framework search paths, deployment target and bundle id
        /*RBWproj.hash.project.objects['XCBuildConfiguration'][ref].buildSettings['FRAMEWORK_SEARCH_PATHS'].push(
            `"${swrveSDKCommonDirectory}"`
        );*/

        if (debug)
            console.log("Executing proj.hash.project.objects['XCBuildConfiguration'][ref].buildSettings['IPHONEOS_DEPLOYMENT_TARGET'] = '10.0';");

        proj.hash.project.objects['XCBuildConfiguration'][ref].buildSettings['IPHONEOS_DEPLOYMENT_TARGET'] = '10.0';
        proj.hash.project.objects['XCBuildConfiguration'][ref].buildSettings['MARKETING_VERSION'] = appConfig.version();
        proj.hash.project.objects['XCBuildConfiguration'][ref].buildSettings['CURRENT_PROJECT_VERSION'] = appConfig.version();

        var currentBundleID =
            proj.hash.project.objects['XCBuildConfiguration'][ref].buildSettings['PRODUCT_BUNDLE_IDENTIFIER'];

        if (isEmptyString(currentBundleID) ||
            !currentBundleID.includes(`${packageName}.OneSignalNotificationServiceExtension`))
        {
            proj.hash.project.objects['XCBuildConfiguration'][ref].buildSettings[
            'PRODUCT_BUNDLE_IDENTIFIER'
            ] = `${packageName}.OneSignalNotificationServiceExtension`;
        }

        // ensure code signing identity is pointed correctly
        proj.hash.project.objects['XCBuildConfiguration'][ref].buildSettings[
            'CODE_SIGN_IDENTITY'
        ] = `"iPhone Distribution"`;

        proj.hash.project.objects['XCBuildConfiguration'][ref].buildSettings['PRODUCT_NAME'] = `${extName}`;
        // proj.addBuildPhase([], 'PBXFrameworksBuildPhase', 'Frameworks', target.uuid);
        // proj.addBuildPhase([], 'PBXFrameworksBuildPhase', 'Frameworks', 'WebKit.framework');

        }
    }

    fs.writeFileSync(projPath, proj.writeSync());
    console.log(`Successfully added ${extName} service extension to ${appName}`);

    // now that everything is setup, modify included files to config.json
    // iosSetupServiceExtensionAppGroup();
    } catch (err) {
    console.error(`There was an issue creating the OneSignal Service Extension: ${err}`);
    }
}
ludufre commented 4 years ago

We have now progressed a lot further with automating adding the OneSignal 'stuff' to Ionic. We have shamelessly used other peoples work, specifically the Swrve code. All credit to them to putting this together. We cannot claim any credit here.

So the code below is run as a hook, after_platform_add. It creates the correct Xcode Target, it creates the right NotificationService.m (based on the code in the OneSignal docs).

It adds in three of the five frameworks. It does not add in the OneSignal.framework as we can't work out how to do it. It also does not add in SystemConfiguration.framework for some reason that we cannot understand at this moment.

It does not create an Entitlements.plist as we don't understand that. To be fair we don't understand a lot of this here...

This code is really raw, we have hacked it around to try and get it work based on our own limited and probably understanding of the npm module xcode. It may well cause your hair to fall out or grow in weird places for all we know. We have commented out bits we don't understand. All credit to the Swrve team for this though, any issues or problems, blame me :)

If anybody can shed light on why SystemConfiguration.framework doesn't work, or how to add in the OneSignal.framework that would be great.

One other thing we have learnt is that we can set a lot of the IPHONEOS_DEPLOYMENT_TARGET from here. We're still experimenting but wanted to share this as somebody may be able to help.

Rob

const fs = require('fs');
const path = require('path');
const xcode = require('xcode');
var appConfig;
var debug = 1;

function copyRecursiveSync(srcPath, destPath)
{
    let exists = fs.existsSync(srcPath);
    let stats = exists && fs.statSync(srcPath);
    let isDirectory = exists && stats.isDirectory();

    if (debug)
    {
  console.log(`copyRecursiveSync srcPath=${srcPath} destPath=${destPath}`);
    }

    if (exists && isDirectory) {
  fs.mkdirSync(destPath);
  // in a sync way we just check for each file in the folder and if its a directory call the same method recursive to copy its content as well
  fs.readdirSync(srcPath).forEach(function(childItemName) {
      copyRecursiveSync(path.join(srcPath, childItemName), path.join(destPath, childItemName));
  });
    } else {
  fs.linkSync(srcPath, destPath);
    }
}

function cordovaAppConfigForContext(context)
{
    const cordovaCommon = context.requireCordovaModule('cordova-common');
    return new cordovaCommon.ConfigParser('config.xml');
}

module.exports = function(context) {
    if (1)
    {
  console.log('Adding push extensions started');
  appConfig = cordovaAppConfigForContext(context);

  if (0 && debug)
  {
      console.log("appConfig = " + JSON.stringify(appConfig , null , 2));
  }

  iosSetupServiceExtension();

  // iosSwrveFrameworkEdit();
    }
};

function isEmptyString(str)
{
    return !str || 0 === str.length;
}

function cordovaPackageNameForPlatform(appConfig, platform)
{
    // check for the existence of platform-specific id, if it's not there fallback to basic id
    var packageName;

    if (platform == 'ios') {
  packageName = appConfig.ios_CFBundleIdentifier();
    }

    if (platform == 'android') {
  packageName = appConfig.android_packageName();
    }

    if (isEmptyString(packageName)) {
  packageName = appConfig.packageName();
    }

    return packageName;
}

function iosSetupServiceExtension() {
    if (debug)
  console.log("iosSetupServiceExtension started");

    // const appGroupIdentifier = appConfig.getPlatformPreference('swrve.appGroupIdentifier', 'ios');
    const appName = appConfig.name();
    const packageName = cordovaPackageNameForPlatform(appConfig, 'ios');
    const iosPath = 'platforms/ios/';
    const projPath = `${iosPath}${appName}.xcodeproj/project.pbxproj`;
    const extName = 'OneSignalNotificationServiceExtension';
    var extFiles = [ 'NotificationService.h', 'NotificationService.m', `${extName}-Info.plist` ];

    if (debug)
    {
  console.log("iosSetupServiceExtension: appName = '" + appName + "'");
  console.log("iosSetupServiceExtension: packageName = '" + packageName + "'");
  console.log("iosSetupServiceExtension: iosPath = '" + iosPath + "'");
  console.log("iosSetupServiceExtension: projPath = '" + projPath + "'");
  console.log("iosSetupServiceExtension: extName= '" + extName + "'");
  console.log("iosSetupServiceExtension: extFiles = '" + JSON.stringify(extFiles , null , 2) + "'");
    }

    /* if (!swrveUtils.isEmptyString(appGroupIdentifier))
    {
       extFiles.push(`Entitlements-${extName}.plist`);
    } */

    // The directory where the source extension files and common are stored
    const sourceDir = `scripts/extensions/ios/${extName}/`;

    if (debug)
    {
  console.log("iosSetupServiceExtension: sourceDir = ${sourceDir}");
    }
    // const sourceDir = `plugins/cordova-plugin-swrve/platforms/ios/${extName}/`;
    // const swrveSDKCommonDirectory = path.join(`${appName}`, 'Plugins', 'cordova-plugin-swrve');

    const proj = xcode.project(projPath);
    proj.parseSync();

    let NoServiceExtensionYet = true;

    if (!fs.existsSync(`${iosPath}${extName}`)) {
  console.log(`Adding ${extName} Push Service Extension to ${appName}...`);
  // Copy in the extension files
  fs.mkdirSync(`${iosPath}${extName}`);
    } else {
  NoServiceExtensionYet = false;
  console.log(`Swrve: ${extName} already exists at ${iosPath}`);
    }

    try {
  if (debug)
      console.log("iosSetupServiceExtension: NoServiceExtensionYet = " + NoServiceExtensionYet);

  if (NoServiceExtensionYet) {
      extFiles.map((file) => copyRecursiveSync(`${sourceDir}${file}`, `${iosPath}${extName}/${file}`));

      // Add a target for the extension
      let extTarget = proj.addTarget(extName, 'app_extension');

      // Create new PBXGroup for the extension
      let extGroup = proj.addPbxGroup(extFiles, extName, extName);

      // Add to PBXGroup under to CustomTemplate so files appear in the file explorer in Xcode
      let groups = proj.hash.project.objects['PBXGroup'];
      Object.keys(groups).forEach(function(key) {
      if (groups[key].name === 'CustomTemplate') {
          proj.addToPbxGroup(extGroup.uuid, key);
      }
      });

      // Add build phases
      proj.addBuildPhase([ 'NotificationService.m' ], 'PBXSourcesBuildPhase', 'Sources', extTarget.uuid);
      proj.addBuildPhase([], 'PBXResourcesBuildPhase', 'Resources', extTarget.uuid);

      // Add a new PBXFrameworksBuildPhase for the Frameworks used by the widget (NotificationCenter.framework, libCordova.a)
      var frameworksBuildPhase = proj.addBuildPhase(
      [],
      'PBXFrameworksBuildPhase',
      'Frameworks',
      extTarget.uuid
      );
      if (frameworksBuildPhase) {
      console.log('Successfully added PBXFrameworksBuildPhase!', 'info');
      }

      // Add the frameworks needed by our widget, add them to the existing Frameworks PbxGroup and PBXFrameworksBuildPhase
      var frameworkFile1 = proj.addFramework(
      './platforms/ios/pods/OneSignal/iOS_SDK/OneSignalSDK/Framework/OneSignal.framework',
      { target: extTarget.uuid }
      );

      if (frameworkFile1)
      console.log("'OneSignal.framework' added\n");

      var frameworksToAdd = [ 'SystemConfiguration.framework' , 'WebKit.framework' , 'UIKit.framework' , 'CoreGraphics.framework'];
            for (var i = 0 ; i < frameworksToAdd.length ; i++)
            {
                var value = frameworksToAdd[i];
                // Add the frameworks needed by OneSignal, add them to the existing Frameworks PbxGroup and PBXFrameworksBuildPhase
                var frameworkFile2 = proj.addFramework(value , { target: extTarget.uuid });

                if (frameworkFile2)
                    console.log("Framework " + value + " added");
                else
                    console.log("Framework " + value + " NOT added");
            }
  }

  if (debug)
      console.log("Now iterate through XCBuildConfig");

  // Iterate through the entire XCBuildConfig for config of the new target PRODUCT_NAME and modify it
  var config = proj.hash.project.objects['XCBuildConfiguration'];
  for (var ref in config) {
      if (config[ref].buildSettings !== undefined &&
      config[ref].buildSettings.PRODUCT_NAME !== undefined &&
      config[ref].buildSettings.PRODUCT_NAME.includes(extName))
      {
      console.log(`entered the setting: ${config[ref].buildSettings.PRODUCT_NAME} of ${ref}`);

      var INHERITED = '"$(inherited)"';
      if (!config[ref].buildSettings['FRAMEWORK_SEARCH_PATHS'] ||
          config[ref].buildSettings['FRAMEWORK_SEARCH_PATHS'] === INHERITED)
      {
          proj.hash.project.objects['XCBuildConfiguration'][ref].buildSettings['FRAMEWORK_SEARCH_PATHS'] = [
          INHERITED
          ];
      }

      // Set entitlements
      /* if (!swrveUtils.isEmptyString(appGroupIdentifier)) {
          proj.hash.project.objects['XCBuildConfiguration'][ref].buildSettings[
          'CODE_SIGN_ENTITLEMENTS'
          ] = `"$(PROJECT_DIR)/${extName}/Entitlements-${extName}.plist"`;
      } */

      // Fix issues with the framework search paths, deployment target and bundle id
      /*RBWproj.hash.project.objects['XCBuildConfiguration'][ref].buildSettings['FRAMEWORK_SEARCH_PATHS'].push(
          `"${swrveSDKCommonDirectory}"`
      );*/

      if (debug)
          console.log("Executing proj.hash.project.objects['XCBuildConfiguration'][ref].buildSettings['IPHONEOS_DEPLOYMENT_TARGET'] = '10.0';");

      proj.hash.project.objects['XCBuildConfiguration'][ref].buildSettings['IPHONEOS_DEPLOYMENT_TARGET'] = '10.0';
      proj.hash.project.objects['XCBuildConfiguration'][ref].buildSettings['MARKETING_VERSION'] = appConfig.version();
      proj.hash.project.objects['XCBuildConfiguration'][ref].buildSettings['CURRENT_PROJECT_VERSION'] = appConfig.version();

      var currentBundleID =
          proj.hash.project.objects['XCBuildConfiguration'][ref].buildSettings['PRODUCT_BUNDLE_IDENTIFIER'];

      if (isEmptyString(currentBundleID) ||
          !currentBundleID.includes(`${packageName}.OneSignalNotificationServiceExtension`))
      {
          proj.hash.project.objects['XCBuildConfiguration'][ref].buildSettings[
          'PRODUCT_BUNDLE_IDENTIFIER'
          ] = `${packageName}.OneSignalNotificationServiceExtension`;
      }

      // ensure code signing identity is pointed correctly
      proj.hash.project.objects['XCBuildConfiguration'][ref].buildSettings[
          'CODE_SIGN_IDENTITY'
      ] = `"iPhone Distribution"`;

      proj.hash.project.objects['XCBuildConfiguration'][ref].buildSettings['PRODUCT_NAME'] = `${extName}`;
      // proj.addBuildPhase([], 'PBXFrameworksBuildPhase', 'Frameworks', target.uuid);
      // proj.addBuildPhase([], 'PBXFrameworksBuildPhase', 'Frameworks', 'WebKit.framework');

      }
  }

  fs.writeFileSync(projPath, proj.writeSync());
  console.log(`Successfully added ${extName} service extension to ${appName}`);

  // now that everything is setup, modify included files to config.json
  // iosSetupServiceExtensionAppGroup();
    } catch (err) {
  console.error(`There was an issue creating the OneSignal Service Extension: ${err}`);
    }
}

Can you provide a PR?

rwillett commented 4 years ago

Hi,

We spent a lot more time on this since this post and sadly we have had to give up as

  1. The node xcode module is completely undocumented.
  2. The Xcode pbxproj file format is a nightmare to work with, especially when you have undocumented function calls.
  3. We found that strange and inconsistent things were happening with Xcode.
  4. The node module would not load up SystemConfiguration.framework without the module being replaced. However every time you do a ionic cordova platform add ios you needed to overwrite the xcode node module files with our own version.
  5. We never managed to get the OneSignal framework to load properly.

Since we had to do manual work to get SystemConfiguration.framework in AND the OneSignal.framework AND weird things were happening in Xcode AND we were spending a disproportionate amount of time AND nobody was offering any help at all, we decided to cut or losses and move on. We add the OneSignal stuff by hand now as it's easier.

We did look at using Keyboard Maestro but Xcode is such a vile pile of junk, we spent a few days and gave that up as well.

I have no idea how other projects do automatic builds but we've spent nearly six weeks of wasted time.

Sorry to be a grinch, but this is a dead end for us.

Rob

ludufre commented 3 years ago

I was able to create a hook script to add Notification Extension and App Groups. It worked with 5 of my projects.

Can anyone else test if it works?

Create file auto-nse.js in hooks/

#!/usr/bin/env node

var fs = require('fs');
var path = require('path');

module.exports = function (context) {
  const log = '\t[OneSignal - Notification Service Extension] ';
  var rootdir = context.opts.projectRoot;
  var project = path.join(rootdir, 'platforms/ios');

  console.log(log + 'Installation start');

  var configXml = fs.readFileSync(path.join(rootdir, 'config.xml'), 'utf8');
  var appName = configXml.match(/<name>(.+?)<\/name>/);
  if (!!!appName) {
    console.error(log + 'Can\'t get App Name from ./config.xml');
    process.exit(1);
  }
  appName = appName[1];

  var info = fs.readFileSync(path.join(project, appName, appName + '-Info.plist'), 'utf8');
  var config = [];
  for (var key of ['CFBundleShortVersionString', 'CFBundleVersion', 'CFBundleURLName']) {
    var matchs = info.match(new RegExp('<key>' + key + '<\/key>(?:(?:.|\n)+?)<string>(.+?)<\/string>'));
    if (!!!matchs) {
      console.error(log + 'Can\'t get ' + key + ' from ./' + appName + '-Info.plist');
      process.exit(1);
    }
    config[key] = matchs[1];
  }

  var projectid = config['CFBundleURLName'];
  var appVersion = config['CFBundleShortVersionString'];
  var appBuild = config['CFBundleVersion'];
  var appTarget = '11.0';

  var cnt = fs.readFileSync(path.join(project, appName + '.xcodeproj/project.pbxproj'), 'utf8');

  if (cnt.indexOf('NotificationService.m') > -1) {
    console.log(log + 'Source files and Frameworks already installed. [Skip]');
  } else {

    // Get Project Object ID
    var projId = cnt.match(/rootObject = (.+?)(?:| \/\* Project object \*\/);/);
    if (!!!projId) {
      console.error(log + 'Can\'t get Project object from project.pbxproj');
      process.exit(1);
    }
    projId = projId[1];

    // Append PBXBuildFile section
    console.log(log + 'Append PBXBuildFile section');
    cnt = cnt.replace('/* End PBXBuildFile section */',
      '\t\tFFFFFFFFFFFFFFFFFFFF5C15 /* NotificationService.m in Sources */ = {isa = PBXBuildFile; fileRef = FFFFFFFFFFFFFFFFFFFF6A7C /* NotificationService.m */; };\n' +
      '\t\tFFFFFFFFFFFFFFFFFFFF9238 /* OneSignalNotificationServiceExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = FFFFFFFFFFFFFFFFFFFF0CC6 /* OneSignalNotificationServiceExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };\n' +
      '\t\tFFFFFFFFFFFFFFFFFFFF9AC8 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FFFFFFFFFFFFFFFFFFFF8FC2 /* CoreGraphics.framework */; };\n' +
      '\t\tFFFFFFFFFFFFFFFFFFFFA186 /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FFFFFFFFFFFFFFFFFFFF16D6 /* WebKit.framework */; };\n' +
      '\t\tFFFFFFFFFFFFFFFFFFFFAED6 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FFFFFFFFFFFFFFFFFFFFA60A /* UIKit.framework */; };\n' +
      '\t\tFFFFFFFFFFFFFFFFFFFFA397 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FFFFFFFFFFFFFFFFFFFF15DB /* SystemConfiguration.framework */; };\n' +
      '\t\tFFFFFFFFFFFFFFFFFFFF5C17 /* OneSignal.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FFFFFFFFFFFFFFFFFFFF2F84 /* OneSignal.framework */; };\n' +
      "/* End PBXBuildFile section */");

    // Append PBXContainerItemProxy section
    console.log(log + 'Append PBXContainerItemProxy section');
    cnt = cnt.replace('/* End PBXContainerItemProxy section */',
      '\t\tFFFFFFFFFFFFFFFFFFFF0E7E /* PBXContainerItemProxy */ = {\n' +
      '\t\t\tisa = PBXContainerItemProxy;\n' +
      '\t\t\tcontainerPortal = ' + projId + ' /* Project object */;\n' +
      '\t\t\tproxyType = 1;\n' +
      '\t\t\tremoteGlobalIDString = FFFFFFFFFFFFFFFFFFFF5D00;\n' +
      '\t\t\tremoteInfo = OneSignalNotificationServiceExtension;\n' +
      '\t\t};\n' +
      "/* End PBXContainerItemProxy section */");

    // Add or append PBXCopyFilesBuildPhase section
    console.log(log + 'Append PBXCopyFilesBuildPhase section');
    if (cnt.indexOf('/* End PBXCopyFilesBuildPhase section */') === -1) {
      cnt = cnt.replace('/* End PBXContainerItemProxy section */', '/* End PBXContainerItemProxy section */\n\n/* Begin PBXCopyFilesBuildPhase section */\n/* End PBXCopyFilesBuildPhase section */');
    }
    cnt = cnt.replace('/* End PBXCopyFilesBuildPhase section */', '\t\tFFFFFFFFFFFFFFFFFFFF2C80 /* Embed App Extensions */ = {\n' +
      '\t\t\tisa = PBXCopyFilesBuildPhase;\n' +
      '\t\t\tbuildActionMask = 2147483647;\n' +
      '\t\t\tdstPath = \"\";\n' +
      '\t\t\tdstSubfolderSpec = 13;\n' +
      '\t\t\tfiles = (\n' +
      '\t\t\t\tFFFFFFFFFFFFFFFFFFFF9238 /* OneSignalNotificationServiceExtension.appex in Embed App Extensions */,\n' +
      '\t\t\t);\n' +
      '\t\t\tname = \"Embed App Extensions\";\n' +
      '\t\t\trunOnlyForDeploymentPostprocessing = 0;\n' +
      '\t\t};\n' +
      "/* End PBXCopyFilesBuildPhase section */");

    // Append PBXFileReference section
    console.log(log + 'Append PBXFileReference section');
    cnt = cnt.replace('/* End PBXFileReference section */',
      '\t\tFFFFFFFFFFFFFFFFFFFF0CC6 /* OneSignalNotificationServiceExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = OneSignalNotificationServiceExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };\n' +
      '\t\tFFFFFFFFFFFFFFFFFFFFCDA5 /* NotificationService.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NotificationService.h; sourceTree = "<group>"; };\n' +
      '\t\tFFFFFFFFFFFFFFFFFFFF6A7C /* NotificationService.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NotificationService.m; sourceTree = "<group>"; };\n' +
      '\t\tFFFFFFFFFFFFFFFFFFFFCB44 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };\n' +
      '\t\tFFFFFFFFFFFFFFFFFFFF8FC2 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; };\n' +
      '\t\tFFFFFFFFFFFFFFFFFFFF2F84 /* OneSignal.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OneSignal.framework; path = Pods/OneSignal/iOS_SDK/OneSignalSDK/Framework/OneSignal.framework; sourceTree = "<group>"; };\n' +
      '\t\tFFFFFFFFFFFFFFFFFFFF16D6 /* WebKit.framework */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = System/Library/Frameworks/WebKit.framework; sourceTree = SDKROOT; };\n' +
      '\t\tFFFFFFFFFFFFFFFFFFFFA60A /* UIKit.framework */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = undefined; includeInIndex = 0; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; };\n' +
      '\t\tFFFFFFFFFFFFFFFFFFFF15DB /* SystemConfiguration.framework */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = undefined; includeInIndex = 0; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; };\n' +
      '/* End PBXFileReference section */');

    // Append PBXFrameworksBuildPhase section
    console.log(log + 'Append PBXFrameworksBuildPhase section');
    cnt = cnt.replace('/* End PBXFrameworksBuildPhase section */',
      '\t\tFFFFFFFFFFFFFFFFFFFFFAB3 /* Frameworks */ = {\n' +
      '\t\t\tisa = PBXFrameworksBuildPhase;\n' +
      '\t\t\tbuildActionMask = 2147483647;\n' +
      '\t\t\tfiles = (\n' +
      '\t\t\t\tFFFFFFFFFFFFFFFFFFFF5C17 /* OneSignal.framework in Frameworks */,\n' +
      '\t\t\t\tFFFFFFFFFFFFFFFFFFFFA397 /* SystemConfiguration.framework in Frameworks */,\n' +
      '\t\t\t\tFFFFFFFFFFFFFFFFFFFFAED6 /* UIKit.framework in Frameworks */,\n' +
      '\t\t\t\tFFFFFFFFFFFFFFFFFFFFA186 /* WebKit.framework in Frameworks */,\n' +
      '\t\t\t\tFFFFFFFFFFFFFFFFFFFF9AC8 /* CoreGraphics.framework in Frameworks */,\n' +
      '\t\t\t);\n' +
      '\t\t\trunOnlyForDeploymentPostprocessing = 0;\n' +
      '\t\t};\n' +
      '/* End PBXFrameworksBuildPhase section */');

    // Append PBXGroup section > Products > children
    console.log(log + 'Append PBXGroup section > Products > children');
    var id = cnt.match(/productRefGroup = (.+?) \/\* Products \*\/;/); // Search for ID
    if (!!!id) {
      console.error(log + 'Can\'t get Products ID from project.pbxproj');
      process.exit(1);
    }
    id = id[1]; // Get matched value
    var matchs = cnt.match(new RegExp('\\t\\t' + id + ' \/\\* Products \\*\/ = {(?:(?:.|\n)+?)children = \\(\n((.|\n)+?)\\t\\t\\t\\);(?:(?:.|\n)+?)};')); // Search section
    if (!!!matchs) {
      console.error(log + 'Can\'t get Products content from project.pbxproj');
      process.exit(1);
    }
    var section = matchs[0]; // Store old section
    var appended = section.replace(matchs[1], matchs[1] + '\t\t\t\tFFFFFFFFFFFFFFFFFFFF0CC6 /* OneSignalNotificationServiceExtension.appex */,\n'); // Store new section
    cnt = cnt.replace(section, appended); // Replaces it

    // Append PBXGroup section > CustomTemplate > children
    console.log(log + 'Append PBXGroup section > CustomTemplate > children');
    var id = cnt.match(/mainGroup = (.+?)(?:| \/\* CustomTemplate \*\/);/); // Search for ID
    if (!!!id) {
      console.error(log + 'Can\'t get CustomTemplate ID from project.pbxproj');
      process.exit(1);
    }
    id = id[1]; // Get matched value
    var matchs = cnt.match(new RegExp('\\t\\t' + id + '(?:| \/\\* CustomTemplate \\*\/) = {(?:(?:.|\n)+?)children = \\(\n((.|\n)+?)\\t\\t\\t\\);(?:(?:.|\n)+?)};')); // Search section
    if (!!!matchs) {
      console.error(log + 'Can\'t get CustomTemplate content from project.pbxproj');
      process.exit(1);
    }
    var section = matchs[0]; // Store old section
    var appended = section.replace(matchs[1], matchs[1] + '\t\t\t\tFFFFFFFFFFFFFFFFFFFF1F29 /* OneSignalNotificationServiceExtension */,\n'); // Store new section
    cnt = cnt.replace(section, appended); // Replaces it

    // Append PBXGroup section > Frameworks > children
    console.log(log + 'Append PBXGroup section > Frameworks > children');
    var id = appended.match(/\t\t\t\t(.+?) \/\* Frameworks \*\/,/);
    if (!!!id) {
      console.error(log + 'Can\'t get Frameworks ID from project.pbxproj');
      process.exit(1);
    }
    id = id[1];
    var matchs = cnt.match(new RegExp('\\t\\t' + id + ' \/\\* Frameworks \\*\/ = {(?:(?:.|\n)+?)children = \\(\n((.|\n)+?)\\t\\t\\t\\);(?:(?:.|\n)+?)};')); // Search section
    if (!!!matchs) {
      console.error(log + 'Can\'t get Frameworks content from project.pbxproj');
      process.exit(1);
    }
    var section = matchs[0]; // Store old section
    var appended = section.replace(matchs[1], matchs[1] + '\t\t\t\tFFFFFFFFFFFFFFFFFFFF2F84 /* OneSignal.framework */,\n\t\t\t\tFFFFFFFFFFFFFFFFFFFF8FC2 /* CoreGraphics.framework */,\n' +  // Store new section
      '\t\t\t\tFFFFFFFFFFFFFFFFFFFF16D6 /* WebKit.framework */,\n' +
      '\t\t\t\tFFFFFFFFFFFFFFFFFFFFA60A /* UIKit.framework */,\n' +
      '\t\t\t\tFFFFFFFFFFFFFFFFFFFF15DB /* SystemConfiguration.framework */,\n')
    cnt = cnt.replace(section, appended); // Replaces it

    // Append PBXGroup section
    console.log(log + 'Append PBXGroup section');
    cnt = cnt.replace('/* End PBXGroup section */',
      '\t\tFFFFFFFFFFFFFFFFFFFF1F29 /* OneSignalNotificationServiceExtension */ = {\n' +
      '\t\t\tisa = PBXGroup;\n' +
      '\t\t\tchildren = (\n' +
      '\t\t\t\tFFFFFFFFFFFFFFFFFFFFCDA5 /* NotificationService.h */,\n' +
      '\t\t\t\tFFFFFFFFFFFFFFFFFFFF6A7C /* NotificationService.m */,\n' +
      '\t\t\t\tFFFFFFFFFFFFFFFFFFFFCB44 /* Info.plist */,\n' +
      '\t\t\t);\n' +
      '\t\t\tpath = OneSignalNotificationServiceExtension;\n' +
      '\t\t\tsourceTree = "<group>";\n' +
      '\t\t};\n' +
      '/* End PBXGroup section */');

    // Append PBXNativeTarget section > AppName > buildPhases & dependencies
    console.log(log + 'Append PBXNativeTarget section > AppName > buildPhases & dependencies');
    var matchs = cnt.match(new RegExp('\/\\* Begin PBXNativeTarget section \\*\/(?:(?:.|\n)+?)buildPhases = \\(\n((.|\n)+?)\\t\\t\\t\\);(?:(?:.|\n)+?)dependencies = \\(\n((.|\n)+?)\\t\\t\\t\\);((.|\n)+?)\/\\* End PBXNativeTarget section \\*\/')); // Search section
    if (!!!matchs) {
      console.error(log + 'Can\'t get target content from project.pbxproj');
      process.exit(1);
    }
    var section = matchs[0]; // Store old section
    var appended = section.replace(matchs[1], matchs[1] + '\t\t\t\tFFFFFFFFFFFFFFFFFFFF2C80 /* Embed App Extensions */,\n'); // Store new section
    appended = appended.replace(matchs[3], matchs[3] + '\t\t\t\tFFFFFFFFFFFFFFFFFFFF9CCD /* PBXTargetDependency */,\n'); // Store new section
    cnt = cnt.replace(section, appended); // Replaces it

    // Append PBXNativeTarget section
    console.log(log + 'Append PBXNativeTarget section');
    cnt = cnt.replace('/* End PBXNativeTarget section */',
      '\t\tFFFFFFFFFFFFFFFFFFFF5D00 /* OneSignalNotificationServiceExtension */ = {\n' +
      '\t\t\tisa = PBXNativeTarget;\n' +
      '\t\t\tbuildConfigurationList = FFFFFFFFFFFFFFFFFFFFB33D /* Build configuration list for PBXNativeTarget "OneSignalNotificationServiceExtension" */;\n' +
      '\t\t\tbuildPhases = (\n' +
      '\t\t\t\tFFFFFFFFFFFFFFFFFFFF7943 /* Sources */,\n' +
      '\t\t\t\tFFFFFFFFFFFFFFFFFFFFFAB3 /* Frameworks */,\n' +
      '\t\t\t\tFFFFFFFFFFFFFFFFFFFFBAB7 /* Resources */,\n' +
      '\t\t\t);\n' +
      '\t\t\tbuildRules = (\n' +
      '\t\t\t);\n' +
      '\t\t\tdependencies = (\n' +
      '\t\t\t);\n' +
      '\t\t\tname = OneSignalNotificationServiceExtension;\n' +
      '\t\t\tproductName = OneSignalNotificationServiceExtension;\n' +
      '\t\t\tproductReference = FFFFFFFFFFFFFFFFFFFF0CC6 /* OneSignalNotificationServiceExtension.appex */;\n' +
      '\t\t\tproductType = "com.apple.product-type.app-extension";\n' +
      '\t\t};\n' +
      '/* End PBXNativeTarget section */');

    // Append PBXProject section > AppName > attributes & targets
    console.log(log + 'Append PBXProject section > AppName > attributes & targets');
    var matchs = cnt.match(new RegExp('\/\\* Begin PBXProject section \\*\/(?:(?:.|\n)+?)attributes = \\{\n((.|\n)+?)\\t\\t\\t\\};(?:(?:.?|\n)+?)targets = \\(\n((.|\n)+?)\\t\\t\\t\\);((.|\n)+?)\/\\* End PBXProject section \\*\/')); // Search section
    if (!!!matchs) {
      console.error(log + 'Can\'t get PBXProject content from project.pbxproj');
      process.exit(1);
    }
    var section = matchs[0]; // Store old section
    var appended = section.replace(matchs[1], matchs[1] + '\t\t\t\tTargetAttributes = {\n\t\t\t\t\tFFFFFFFFFFFFFFFFFFFF5D00 = {\n\t\t\t\t\t\tCreatedOnToolsVersion = 12.4;\n\t\t\t\t\t};\n\t\t\t\t};\n'); // Store new section
    appended = appended.replace(matchs[3], matchs[3] + '\t\t\t\tFFFFFFFFFFFFFFFFFFFF5D00 /* OneSignalNotificationServiceExtension */,\n'); // Store new section
    cnt = cnt.replace(section, appended); // Replaces it

    // Append PBXNativeTarget section
    console.log(log + 'Append PBXNativeTarget section');
    cnt = cnt.replace('/* End PBXResourcesBuildPhase section */',
      '\t\tFFFFFFFFFFFFFFFFFFFFBAB7 /* Resources */ = {\n' +
      '\t\t\tisa = PBXResourcesBuildPhase;\n' +
      '\t\t\tbuildActionMask = 2147483647;\n' +
      '\t\t\tfiles = (\n' +
      '\t\t\t);\n' +
      '\t\t\trunOnlyForDeploymentPostprocessing = 0;\n' +
      '\t\t};\n' +
      '/* End PBXResourcesBuildPhase section */');

    // Append PBXSourcesBuildPhase section
    console.log(log + 'Append PBXSourcesBuildPhase section');
    cnt = cnt.replace('/* End PBXSourcesBuildPhase section */',
      '\t\tFFFFFFFFFFFFFFFFFFFF7943 /* Sources */ = {\n' +
      '\t\t\tisa = PBXSourcesBuildPhase;\n' +
      '\t\t\tbuildActionMask = 2147483647;\n' +
      '\t\t\tfiles = (\n' +
      '\t\t\t\tFFFFFFFFFFFFFFFFFFFF5C15 /* NotificationService.m in Sources */,\n' +
      '\t\t\t);\n' +
      '\t\t\trunOnlyForDeploymentPostprocessing = 0;\n' +
      '\t\t};\n' +
      '/* End PBXSourcesBuildPhase section */');

    // Append PBXTargetDependency section
    console.log(log + 'Append PBXTargetDependency section');
    cnt = cnt.replace('/* End PBXTargetDependency section */',
      '\t\tFFFFFFFFFFFFFFFFFFFF9CCD /* PBXTargetDependency */ = {\n' +
      '\t\t\tisa = PBXTargetDependency;\n' +
      '\t\t\ttarget = FFFFFFFFFFFFFFFFFFFF5D00 /* OneSignalNotificationServiceExtension */;\n' +
      '\t\t\ttargetProxy = FFFFFFFFFFFFFFFFFFFF0E7E /* PBXContainerItemProxy */;\n' +
      '\t\t};\n' +
      '/* End PBXTargetDependency section */');

    // Append XCBuildConfiguration section
    console.log(log + 'Append XCBuildConfiguration section');
    cnt = cnt.replace('/* End XCBuildConfiguration section */',
      '\t\tFFFFFFFFFFFFFFFFFFFF0074 /* Debug */ = {\n' +
      '\t\t\tisa = XCBuildConfiguration;\n' +
      '\t\t\tbuildSettings = {\n' +
      '\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n' +
      '\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n' +
      '\t\t\t\tCLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;\n' +
      '\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = "gnu++14";\n' +
      '\t\t\t\tCLANG_CXX_LIBRARY = "libc++";\n' +
      '\t\t\t\tCLANG_ENABLE_OBJC_WEAK = YES;\n' +
      '\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n' +
      '\t\t\t\tCLANG_WARN_DOCUMENTATION_COMMENTS = YES;\n' +
      '\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n' +
      '\t\t\t\tCLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;\n' +
      '\t\t\t\tCLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;\n' +
      '\t\t\t\tCODE_SIGN_IDENTITY = "Apple Development";\n' +
      '\t\t\t\tCODE_SIGN_STYLE = Automatic;\n' +
      '\t\t\t\tCOPY_PHASE_STRIP = NO;\n' +
      '\t\t\t\tCURRENT_PROJECT_VERSION = ' + appBuild + ';\n' +
      '\t\t\t\tDEBUG_INFORMATION_FORMAT = dwarf;\n' +
      '\t\t\t\tDEVELOPMENT_TEAM = 9B6ELYAL5D;\n' +
      '\t\t\t\tFRAMEWORK_SEARCH_PATHS = (\n' +
      '\t\t\t\t\t"$(inherited)",\n' +
      '\t\t\t\t\t"$(PROJECT_DIR)/Pods/OneSignal/iOS_SDK/OneSignalSDK/Framework",\n' +
      '\t\t\t\t);\n' +
      '\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu11;\n' +
      '\t\t\t\tGCC_DYNAMIC_NO_PIC = NO;\n' +
      '\t\t\t\tGCC_OPTIMIZATION_LEVEL = 0;\n' +
      '\t\t\t\tGCC_PREPROCESSOR_DEFINITIONS = (\n' +
      '\t\t\t\t\t"DEBUG=1",\n' +
      '\t\t\t\t\t"$(inherited)",\n' +
      '\t\t\t\t);\n' +
      '\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n' +
      '\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n' +
      '\t\t\t\tINFOPLIST_FILE = OneSignalNotificationServiceExtension/Info.plist;\n' +
      '\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = ' + appTarget + ';\n' +
      '\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n' +
      '\t\t\t\t\t"$(inherited)",\n' +
      '\t\t\t\t\t"@executable_path/Frameworks",\n' +
      '\t\t\t\t\t"@executable_path/../../Frameworks",\n' +
      '\t\t\t\t);\n' +
      '\t\t\t\tMARKETING_VERSION = ' + appVersion + ';\n' +
      '\t\t\t\tMTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;\n' +
      '\t\t\t\tMTL_FAST_MATH = YES;\n' +
      '\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = ' + projectid + '.OneSignalNotificationServiceExtension;\n' +
      '\t\t\t\tPRODUCT_NAME = "$(TARGET_NAME)";\n' +
      '\t\t\t\tSKIP_INSTALL = YES;\n' +
      '\t\t\t\tTARGETED_DEVICE_FAMILY = 1;\n' +
      '\t\t\t};\n' +
      '\t\t\tname = Debug;\n' +
      '\t\t};\n' +
      '\t\tFFFFFFFFFFFFFFFFFFFFE808 /* Release */ = {\n' +
      '\t\t\tisa = XCBuildConfiguration;\n' +
      '\t\t\tbuildSettings = {\n' +
      '\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n' +
      '\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n' +
      '\t\t\t\tCLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;\n' +
      '\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = "gnu++14";\n' +
      '\t\t\t\tCLANG_CXX_LIBRARY = "libc++";\n' +
      '\t\t\t\tCLANG_ENABLE_OBJC_WEAK = YES;\n' +
      '\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n' +
      '\t\t\t\tCLANG_WARN_DOCUMENTATION_COMMENTS = YES;\n' +
      '\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n' +
      '\t\t\t\tCLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;\n' +
      '\t\t\t\tCLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;\n' +
      '\t\t\t\tCODE_SIGN_IDENTITY = "Apple Development";\n' +
      '\t\t\t\tCODE_SIGN_STYLE = Automatic;\n' +
      '\t\t\t\tCOPY_PHASE_STRIP = NO;\n' +
      '\t\t\t\tCURRENT_PROJECT_VERSION = ' + appBuild + ';\n' +
      '\t\t\t\tDEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";\n' +
      '\t\t\t\tDEVELOPMENT_TEAM = 9B6ELYAL5D;\n' +
      '\t\t\t\tENABLE_NS_ASSERTIONS = NO;\n' +
      '\t\t\t\tFRAMEWORK_SEARCH_PATHS = (\n' +
      '\t\t\t\t\t"$(inherited)",\n' +
      '\t\t\t\t\t"$(PROJECT_DIR)/Pods/OneSignal/iOS_SDK/OneSignalSDK/Framework",\n' +
      '\t\t\t\t);\n' +
      '\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu11;\n' +
      '\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n' +
      '\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n' +
      '\t\t\t\tINFOPLIST_FILE = OneSignalNotificationServiceExtension/Info.plist;\n' +
      '\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = ' + appTarget + ';\n' +
      '\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n' +
      '\t\t\t\t\t"$(inherited)",\n' +
      '\t\t\t\t\t"@executable_path/Frameworks",\n' +
      '\t\t\t\t\t"@executable_path/../../Frameworks",\n' +
      '\t\t\t\t);\n' +
      '\t\t\t\tMARKETING_VERSION = ' + appVersion + ';\n' +
      '\t\t\t\tMTL_ENABLE_DEBUG_INFO = NO;\n' +
      '\t\t\t\tMTL_FAST_MATH = YES;\n' +
      '\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = ' + projectid + '.OneSignalNotificationServiceExtension;\n' +
      '\t\t\t\tPRODUCT_NAME = "$(TARGET_NAME)";\n' +
      '\t\t\t\tSKIP_INSTALL = YES;\n' +
      '\t\t\t\tTARGETED_DEVICE_FAMILY = 1;\n' +
      '\t\t\t\tVALIDATE_PRODUCT = YES;\n' +
      '\t\t\t};\n' +
      '\t\t\tname = Release;\n' +
      '\t\t};\n' +
      '/* End XCBuildConfiguration section */');

    // Append XCConfigurationList section
    console.log(log + 'Append XCConfigurationList section');
    cnt = cnt.replace('/* End XCConfigurationList section */',
      '\t\tFFFFFFFFFFFFFFFFFFFFB33D /* Build configuration list for PBXNativeTarget "OneSignalNotificationServiceExtension" */ = {\n' +
      '\t\t\tisa = XCConfigurationList;\n' +
      '\t\t\tbuildConfigurations = (\n' +
      '\t\t\t\tFFFFFFFFFFFFFFFFFFFF0074 /* Debug */,\n' +
      '\t\t\t\tFFFFFFFFFFFFFFFFFFFFE808 /* Release */,\n' +
      '\t\t\t);\n' +
      '\t\t\tdefaultConfigurationIsVisible = 0;\n' +
      '\t\t\tdefaultConfigurationName = Release;\n' +
      '\t\t};\n' +
      '/* End XCConfigurationList section */');

    fs.writeFileSync(path.join(project, appName + '.xcodeproj/project.pbxproj'), cnt);

    console.log(log + 'Source files and Frameworks installed');
  }

  // Entitlements
  ['Entitlements-Release.plist', 'Entitlements-Debug.plist'].forEach((target) => {
    var cnt = fs.readFileSync(path.join(project, appName, target), 'utf8');

    if (cnt.indexOf('<string>group.' + projectid + '.onesignal</string>') > -1) {
      console.log(log + 'App Group already created for ' + target + ' [Skip]');
    } else {

      if (cnt.indexOf('<key>com.apple.security.application-groups</key>') > -1) {
        cnt = cnt.replace('<key>com.apple.security.application-groups</key>\n\t<array>', '<key>com.apple.security.application-groups</key>\n\t<array>\n\t\t<string>group.' + projectid + '.onesignal</string>\t\t\t');
      } else {
        cnt = cnt.replace('</dict>\n</plist>', '\t<key>com.apple.security.application-groups</key>\n\t<array>\n\t\t<string>group.' + projectid + '.onesignal</string>\n\t</array>\n</dict>\n</plist>');
      }
      fs.writeFileSync(path.join(project, appName, target), cnt);

      console.log(log + 'App Group created for ' + target);
    }
  });

  // Dir
  var onesignalDir = path.join(project, 'OneSignalNotificationServiceExtension');
  if (!fs.existsSync(onesignalDir)) {
    fs.mkdirSync(onesignalDir);
    console.log(log + 'Folder OneSignalNotificationServiceExtension created');
  } else {
    console.log(log + 'Folder OneSignalNotificationServiceExtension already exists [Skip]');
  }

  // Files
  fs.writeFileSync(path.join(onesignalDir, 'Info.plist'),
    `<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>CFBundleDevelopmentRegion</key>
    <string>$(DEVELOPMENT_LANGUAGE)</string>
    <key>CFBundleDisplayName</key>
    <string>OneSignalNotificationServiceExtension</string>
    <key>CFBundleExecutable</key>
    <string>$(EXECUTABLE_NAME)</string>
    <key>CFBundleIdentifier</key>
    <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
    <key>CFBundleInfoDictionaryVersion</key>
    <string>6.0</string>
    <key>CFBundleName</key>
    <string>$(PRODUCT_NAME)</string>
    <key>CFBundlePackageType</key>
    <string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
    <key>CFBundleShortVersionString</key>
    <string>$(MARKETING_VERSION)</string>
    <key>CFBundleVersion</key>
    <string>$(CURRENT_PROJECT_VERSION)</string>
    <key>NSExtension</key>
    <dict>
        <key>NSExtensionPointIdentifier</key>
        <string>com.apple.usernotifications.service</string>
        <key>NSExtensionPrincipalClass</key>
        <string>NotificationService</string>
    </dict>
</dict>
</plist>`);
  console.log(log + 'File Info.plist created');

  fs.writeFileSync(path.join(onesignalDir, 'NotificationService.h'),
    `#import <UserNotifications/UserNotifications.h>

@interface NotificationService : UNNotificationServiceExtension

@end`);
  console.log(log + 'File NotificationService.h created');

  fs.writeFileSync(path.join(onesignalDir, 'NotificationService.m'),
    `#import <OneSignal/OneSignal.h>

#import "NotificationService.h"

@interface NotificationService ()

@property (nonatomic, strong) void (^contentHandler)(UNNotificationContent *contentToDeliver);
@property (nonatomic, strong) UNNotificationRequest *receivedRequest;
@property (nonatomic, strong) UNMutableNotificationContent *bestAttemptContent;

@end

@implementation NotificationService

- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {
    self.receivedRequest = request;
    self.contentHandler = contentHandler;
    self.bestAttemptContent = [request.content mutableCopy];

    [OneSignal didReceiveNotificationExtensionRequest:self.receivedRequest withMutableNotificationContent:self.bestAttemptContent];

    // DEBUGGING: Uncomment the 2 lines below and comment out the one above to ensure this extension is excuting
    //            Note, this extension only runs when mutable-content is set
    //            Setting an attachment or action buttons automatically adds this
    NSLog(@"Running NotificationServiceExtension");
    // self.bestAttemptContent.body = [@"[Modified] " stringByAppendingString:self.bestAttemptContent.body];

    self.contentHandler(self.bestAttemptContent);
}

- (void)serviceExtensionTimeWillExpire {
    // Called just before the extension will be terminated by the system.
    // Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used.

    [OneSignal serviceExtensionTimeWillExpireRequest:self.receivedRequest withMutableNotificationContent:self.bestAttemptContent];

    self.contentHandler(self.bestAttemptContent);
}

@end`);
  console.log(log + 'File NotificationService.m created');

  console.log(log + 'Installation finished');
};

Add to config.xml inside <platform name="browser">:

<hook src="hooks/auto-nse.js" type="after_platform_add" />
reynaldots commented 3 years ago

@ludufre

Worked perfectly!

Only in xcode did I remove the items responsible for the App Group (I don't use it yet) And I also had to mark this flag: https://stackoverflow.com/a/52156472/2188851

Thank you very much! We have a white-label project with 19 clients and every publication of those projects was terrible.

With your solution we can improve our process! Thank you very much!

Just a fix, the place to add "hooks/auto-nse.js" would be at <platform name="ios"> And not <platform name="browser">

ludufre commented 3 years ago

@reynaldots I forgot to mention. I created this PR, although it will not be merged, I fixed some things.

imirs commented 3 years ago

@ludufre, I implemented your code. and now I get this error: "OneSignalNotificationServiceExtension" requires a provisioning profile with the App Groups and Push Notifications features. Select a provisioning profile in the Signing & Capabilities editor. (in target 'OneSignalNotificationServiceExtension' my provisioning profile includes the push notification option. can you please help me with this?

ludufre commented 3 years ago

@ludufre, I implemented your code. and now I get this error: "OneSignalNotificationServiceExtension" requires a provisioning profile with the App Groups and Push Notifications features. Select a provisioning profile in the Signing & Capabilities editor. (in target 'OneSignalNotificationServiceExtension' my provisioning profile includes the push notification option. can you please help me with this?

You profile includes App Group too? You have to choose your Apple Develeoper Account in the extension too, just to be clear…

imirs commented 3 years ago

@ludufre, I implemented your code. and now I get this error: "OneSignalNotificationServiceExtension" requires a provisioning profile with the App Groups and Push Notifications features. Select a provisioning profile in the Signing & Capabilities editor. (in target 'OneSignalNotificationServiceExtension' my provisioning profile includes the push notification option. can you please help me with this?

You profile includes App Group too? You have to choose your Apple Developer Account in the extension too, just to be clear…

Yes, my profile includes the app group, what do you mean by "choose your Apple Developer Account in the extension too", something in the code snippet I need to replace? and would it be easier on the 3.0.0 version once it released?

imirs commented 3 years ago

can anyone help me please with creating a push with an image in IOS using Cordova build (without xcode)