ionic-team / capacitor

Build cross-platform Native Progressive Web Apps for iOS, Android, and the Web ⚡️
https://capacitorjs.com
MIT License
11.8k stars 994 forks source link

Platform-specific preferences in capacitor.config.json #2246

Closed jcleary98 closed 4 years ago

jcleary98 commented 4 years ago

Is it possible to have different values for android and ios in capacitor.config.json? I need to add APP_SECRET (used by AppCenter plugins), which has different values on each platform. When I add the following:

"cordova": { "preferences": { "APP_SECRET": "0000-0000-0000-0000-000000000000" } }

It will add this preference to the native config.xml files. If I manually add separate preference values to the config.xml files, they're cleared out when I run npx cap copy.

Alternatively, is there another way I can set these values in the native projects? Any help appreciated, thanks.

jcesarmobile commented 4 years ago

You are talking about cordova preferences and those don't support platform-specific values in cordova neither, so we won't implement this in Capacitor as it will make it different from Cordova behaviour.

For Capacitor configurations, some of them allow different values per platform.

asangadev commented 4 years ago

Is it possible to have different values for android and ios in capacitor.config.json? I need to add APP_SECRET (used by AppCenter plugins), which has different values on each platform. When I add the following:

"cordova": { "preferences": { "APP_SECRET": "0000-0000-0000-0000-000000000000" } }

It will add this preference to the native config.xml files. If I manually add separate preference values to the config.xml files, they're cleared out when I run npx cap copy.

Alternatively, is there another way I can set these values in the native projects? Any help appreciated, thanks.

@jcleary98 did you find any solution for this?

jcleary98 commented 4 years ago

@asangadev No, I haven't been able to integrate the App Center plugins as a result.

tntwist commented 4 years ago

Hi @jcesarmobile , working with platform specific preferences always worked in cordova for me. This is also suggested by Microsoft in order to use the app center plugin for cordova:

As a complete example, for a Apache Cordova project that supports both Android and iOS targets, you'll have separate app project definitions in App Center, and therefore different app secret values for each target platform. The relevant section of the project's config.xml file will look like the following:

<platform name="android">
<preference name="APP_SECRET" value="0000-0000-0000-0000-000000000001" />
</platform>
<platform name="ios">
<preference name="APP_SECRET" value="0000-0000-0000-0000-000000000002" />
</platform>

See here: https://docs.microsoft.com/en-us/appcenter/sdk/getting-started/cordova

I could not find any specifiy statement on the cordova docs, that this is supported but I it works to specify specific preferences in the platform tags. This also works with preferences of some common cordova plugins like cordova-plugin-statusbar or cordova-plugin-splashscreen. There is even a blog post on the offical corodva blog showing how to set the "WKWebViewOnly"-Preference specific for iOS.

<platform name="ios">
    <preference name="WKWebViewOnly" value="true" />

    <feature name="CDVWKWebViewEngine">
        <param name="ios-package" value="CDVWKWebViewEngine" />
    </feature>

    <preference name="CordovaWebViewEngine" value="CDVWKWebViewEngine" />
</platform>

It would be great if you could reconsider this issue.

@asangadev and @jcleary98 As a temporary workaround I dont use the copy command of capacitor via the Capacitor CLI.

I wrote a little script that adds this functionality on top of the copy command. Please keep in mind this is just a REALLY dirty solution:

// task to copy only the web assets.
// uses capacitor infrastructure: https://github.com/ionic-team/capacitor/blob/master/cli/src/tasks/copy.ts
// added functionality to specify platform specific cordova settings
const Config = require('@capacitor/cli/dist/config');
const Copy = require('@capacitor/cli/dist/tasks/copy');

const replace = require('replace-in-file');
const path = require('path');

async function run() {
    const config = new Config.Config(process.platform, process.cwd(), '../node_modules/@capacitor/cli/bin');
    await copy(config, 'android');
    await copy(config, 'ios');

    // fixes google api key for uk.co.workingedge.phonegap.plugin.launchnavigator
    await replace({
        files: path.join(process.cwd(), '/android/**/*.*'),
        from: "$GOOGLE_API_KEY_FOR_ANDROID",
        to: "MYKEY"
    });
};

async function copy(config, platform) {
    let basePrefs;
    if (config.app.extConfig && config.app.extConfig.cordova && config.app.extConfig.cordova[platform]) {
        const platformPreferences = config.app.extConfig.cordova[platform].preferences;
        if (platformPreferences) {
            basePrefs = config.app.extConfig.cordova.preferences;

            let combinedPrefs = {};
            if (basePrefs) Object.assign(combinedPrefs, basePrefs);
            Object.assign(combinedPrefs, platformPreferences);
            config.app.extConfig.cordova.preferences = combinedPrefs;
        }
    }

    await Copy.copyCommand(config, platform);

    if (config.app.extConfig && config.app.extConfig.cordova) {
        config.app.extConfig.cordova.preferences = basePrefs;
    }
}

run();

Hope it can help you.

jcleary98 commented 4 years ago

That's excellent @tntwist, I'll give it a try.

tntwist commented 4 years ago

I found a little bug in this script that sets the "global" cordova preferences to undefined when the given platform has no own preferences here is the updated script:

// task to copy only the web assets.
// uses capacitor infrastructure: https://github.com/ionic-team/capacitor/blob/master/cli/src/tasks/copy.ts
// added functionality to specify platform specific cordova settings
const Config = require('@capacitor/cli/dist/config');
const Copy = require('@capacitor/cli/dist/tasks/copy');

const replace = require('replace-in-file');
const path = require('path');

async function run() {
    const config = new Config.Config(process.platform, process.cwd(), '../node_modules/@capacitor/cli/bin');
    await copy(config, 'android');
    await copy(config, 'ios');

    // fixes google api key for uk.co.workingedge.phonegap.plugin.launchnavigator
    await replace({
        files: path.join(process.cwd(), '/android/**/*.*'),
        from: "$GOOGLE_API_KEY_FOR_ANDROID",
        to: "MYKEY"
    });
};

async function copy(config, platform) {
    let basePrefs;
    if (config.app.extConfig && config.app.extConfig.cordova) {
        basePrefs = config.app.extConfig.cordova.preferences;
    }

    if (config.app.extConfig && config.app.extConfig.cordova && config.app.extConfig.cordova[platform]) {
        const platformPreferences = config.app.extConfig.cordova[platform].preferences;
        if (platformPreferences) {
            let combinedPrefs = {};

            if (basePrefs) Object.assign(combinedPrefs, basePrefs);

            Object.assign(combinedPrefs, platformPreferences);

            config.app.extConfig.cordova.preferences = combinedPrefs;
        }
    }

    await Copy.copyCommand(config, platform);

    if (config.app.extConfig && config.app.extConfig.cordova) {
        config.app.extConfig.cordova.preferences = basePrefs;
    }
}

run();
ollyde commented 3 years ago

Hello, I'm just getting into Ionic (React). I cannot figure out how to apply preferences; the config.xml doesn't work and there's no guide for Ionic Capacity?

tntwist commented 3 years ago

Hi @ollydixon , the config.xml from cordova does not work with capacitor. You need to use the capactitor.config.json.

Here are the docs: https://capacitorjs.com/docs/reference/config

If you want to apply platform specific preferences like in cordova you need to use the script I provided in this issue. Here is an example config:

{
    "appId": "com.my.app",
    "appName": "My app",
    "bundledWebRuntime": false,
    "npmClient": "npm",
    "webDir": "www",
    "plugins": {
        "SplashScreen": {
            "launchAutoHide": false,
            "showSpinner": false,
            "spinnerColor": "#2698f3",
            "androidSplashResourceName": "splash_full"
        }
    },
    "cordova": {
        "android": {
            "preferences": {
                "APP_SECRET": "XXXDROID"
            }
        },
        "ios": {
            "preferences": {
                "APP_SECRET": "XXXXIOS"
            }
        }
    }
}

Have fun.

ollyde commented 3 years ago

@tntwist thanks, I don't see the mention of 'preferences' anywhere on those docs? It's very confusing hehe.

KirstenStake commented 3 years ago

@tntwist am looking to implement your work around solution but am having trouble figuring out where to place the running of the script to be executed. Are you able to provide some instructions around this please?

ollyde commented 3 years ago

@KirstenStake the documentation really sucks and its totally incompleted.

If it helps here is how my project is stuctured and the config.

Screenshot 2021-02-25 at 08 52 05
tntwist commented 3 years ago

@KirstenStake I run this script when ever I would like to copy the web assets to the android or ios project. This is normaly the case after a Build of the Ionic project. I start the script like this: node copyWebAssets.js.

Here is an extract of my ci pipeline:

...
steps:
- task: UseNode@1
  inputs:
    version: '12.x'
  displayName: 'Use node 12.x'

- task: Npm@1
  inputs:
    command: 'ci'
  displayName: 'Install node packages'

- script: npx ionic build
  displayName: 'Build ionic app'

- task: ArchiveFiles@2
  inputs:
    rootFolderOrFile: './www'
    includeRootFolder: false
    archiveType: 'zip'
    archiveFile: '$(Build.ArtifactStagingDirectory)/www.zip'
    replaceExistingArchive: true
  displayName: 'Archive www assets to artifacts'

- script: node ./scripts/copyWebAssets.js
  displayName: 'Copy web assets to native projects'

....

As you can see the script runs after the build of the ionic project.

I hope this helps you.

milkov85 commented 2 years ago

Hi @ollydixon , the config.xml from cordova does not work with capacitor. You need to use the capactitor.config.json.

Here are the docs: https://capacitorjs.com/docs/reference/config

If you want to apply platform specific preferences like in cordova you need to use the script I provided in this issue. Here is an example config:

{
    "appId": "com.my.app",
    "appName": "My app",
    "bundledWebRuntime": false,
    "npmClient": "npm",
    "webDir": "www",
    "plugins": {
        "SplashScreen": {
            "launchAutoHide": false,
            "showSpinner": false,
            "spinnerColor": "#2698f3",
            "androidSplashResourceName": "splash_full"
        }
    },
    "cordova": {
        "android": {
            "preferences": {
                "APP_SECRET": "XXXDROID"
            }
        },
        "ios": {
            "preferences": {
                "APP_SECRET": "XXXXIOS"
            }
        }
    }
}

Have fun.

Hi @tntwist ,

I tried this, but with no success, if I remove android or iosand have:

"cordova": {
         "preferences": {
              "APP_SECRET": "XXXDROID"
          }

it works, but with the platform it doesn't.

Any idea how this could be solved?

ollyde commented 2 years ago

@milkov85 thanks for the reply but we moved on from Cordova a long time ago to Flutter Web :-)

KirstenStake commented 2 years ago

@milkov85 we didn't get this to work either so introduced a script to replace the template strings inside, based on which build command we used for the platform. The set up was tedious but now just works without us having to touch anything anymore.

  1. Keep your capacitor.config.ts file as it is. Remove the platform specific object level from the cordova {} and only have preferences level {} with all your key/value pairs

  2. Create a file capacitor.tpl.config.ts in the same root level where the original capacitor.config.ts lives. Copy the entire contents from capacitor.config.ts into the new file and change the values of the preference keys to template strings. E.g.

import { CapacitorConfig } from '@capacitor/cli';

const config: CapacitorConfig = {
  appId: 'XYZ',
  appName: ''XYZ'',
  bundledWebRuntime: false,
  webDir: 'www',
  server: {
    iosScheme: 'ionic'
  },
  plugins: {
    SplashScreen: {
      launchAutoHide: false,
      launchShowDuration: 10000
    }
  },
  cordova: {
    preferences: {
      'com.appboy.api_key': '${appboy}',
         'APP_SECRET': '${appSecret}'
    }
  }
};

export default config;
  1. Create a file that houses your platform specific key/values. You can get as fancy as you want here as we have folders like this: preferences > android file and ios file > each platform folder containing preferences.dev.json, preferences.stag.json, preferences.prod.json. In these files create your json object consisting of the key/values of the template strings used in file above.

  2. Create your replacement script:

#!/usr/bin/env node

// Script to replace the template strings in capacitor.tpl.config.json based on environment and platform type

const fs = require('fs');
const path = require('path');
const compile = require('es6-template-strings/compile');
const resolveToString = require('es6-template-strings/resolve-to-string');

var ROOT_DIR = '';
var FILES = {
  SRC: 'capacitor.tpl.config.ts',
  DEST: 'capacitor.config.ts'
};

var env = 'dev';
if (process.env.npm_config_env) env = process.env.npm_config_env;
var platform = process.argv[2];
var envFile = 'preferences/' + platform + '/preferences.' + env + '.json';

var srcFileFull = path.join(ROOT_DIR, FILES.SRC);
var destFileFull = path.join(ROOT_DIR, FILES.DEST);
var configFileFull = path.join(ROOT_DIR, envFile);

var templateData = fs.readFileSync(srcFileFull, 'utf8');

var configData = fs.readFileSync(configFileFull, 'utf8');
var config = JSON.parse(configData);

var compiled = compile(templateData);
var content = resolveToString(compiled, config);

console.log('**~~Replacing template strings capacitor.json for env & platform:~~**', env, platform);

fs.writeFileSync(destFileFull, content);

5 Create your run scripts. We follow convention of env:platform. e.g. (this convention is followed in the script above, change accordingly). "prod:android": "npm run cap:template-strings --env=prod android {.....rest of the scripts here that you need to build}"

Hope that helps you at all. This was implemented a while back, not sure if there are better/easier ways now in Capacitor to do above. But once it's set up you pretty much don't need to touch it again

milkov85 commented 2 years ago

@KirstenStake thank you very much for the detailed explanation I will give it a try, hopefully it will work and I will not have to change the config each time I build different platform

ionitron-bot[bot] commented 1 year ago

Thanks for the issue! This issue is being locked to prevent comments that are not relevant to the original issue. If this is still an issue with the latest version of Capacitor, please create a new issue and ensure the template is fully filled out.