EddyVerbruggen / cordova-plugin-googleplus

:heavy_plus_sign: Cordova plugin to login with Google Sign-In on iOS and Android
567 stars 629 forks source link

Not issue, question: iOS & Android separate bundle ids #367

Open santekotturi opened 7 years ago

santekotturi commented 7 years ago

My android and ios apps use different bundle ids.

Says here https://github.com/EddyVerbruggen/cordova-plugin-googleplus#3-google-api-setup that "It is (strongly) recommended that you use the same project for both iOS and Android." Does "same project" === same bundle id??

I was originally planning on uninstalling the reinstalling the plugin as a before_prepare hook, however, it seems as though I can simply change the REVERSED_CLIENT_ID value in config.xml and the plugin will use this value. My concern was that when the plugin is installed, it configures platform specific variables deep down inside something in /plugins/cordova-plugin-googleplus//.../entitlements?/config??/etc

But I've used this little node script and tested on ios & android. it's working for me, just curious if anyone else can weigh in on whether this should work (or if I'm just getting lucky somehow). I'm not a plugin master, despite having read through the src code, cant quite tell if something is configured during install that I would also need to change.

edit-config.js

#!/usr/bin/env node

/* Essentially just using Cheerio to parse the XML file, 
 * traverse to the bundle id node and the plugin client id 
 * edit the values for each platform
 * save the configured config.xml file
 * Usage: expects arg ios or android
 * for ios: node edit-config.js ios
 * for android: node edit-config.js android 
*/

var path = require('path')
    , fs = require('fs')
    , cheerio = require('cheerio')
    , _ = require('lodash')
    , filename = 'config.xml'
    , page = fs.readFileSync(filename, 'utf-8')
    , $ = cheerio.load(page, { decodeEntities: false, xmlMode: true })
    ;

if (process.argv[2] === 'ios') {
    console.log('Setting bundle id for ios');
// set the ios specific widget ID. 
    $('widget')[0].attribs.id = 'com.ionicframework.nativeauthios';
// set the ios specific googleplus plugin client id
    _editGooglePlusClientId('ios');
} else if (process.argv[2] === 'android') {
    console.log('Setting bundle id for android');
// set the android specific 
    $('widget')[0].attribs.id = 'com.ionicframework.nativeauthAndroid';
    _editGooglePlusClientId('android');
} else {
    console.error("ERROR: Incorrect bundle ID provided. Needs to be 'ios' or 'android'");
    process.exit();
}

fs.writeFileSync(filename, $.xml());

console.log('Done editing config.xml');
process.exit();

function _editGooglePlusClientId(platform) {
    var pluginTag = _.find($('plugin'), function (tag) {
        return tag.attribs.name === 'cordova-plugin-googleplus'
    })

    var variableTag = _.find(pluginTag.children, function (child) {
        if (child.attribs && child.attribs.name) {
            return child.attribs.name === 'REVERSED_CLIENT_ID'
        }
    })

    if (platform === 'ios') {
        console.log('setting googleplus client id for ios');
        variableTag.attribs.value = 'com.googleusercontent.apps.XXXXX-XXXXX'
    } else if (platform === 'android') {
        console.log('setting googleplus client id for android');
        variableTag.attribs.value = 'XXXXX-XXXXX.apps.googleusercontent.com'
    } else console.error('Unsupported platform.')

}

Again, I've run this using a single cordova (ionic) app codebase after registering my ios bundle and android bundle id on console.developers.com with all the fingerprints etc. It works, just want to see if this kind of this is technically ok to do, and if so, is this something I could wrap up and potentially include to help others who are faced with having to handle two separate bundle ids.

wearetelescopic commented 7 years ago

This is exactly what I just started doing for a project - create a script that switches the reversed client id depending on ios or android. It's really not well described in the tutorial, as I originally assumed it would work as the "hybrid app" method or have some sort setting for each platform as the whole point of a cordova build is to only have to build the app once for both platforms. This should be made more obvious.

I'll try your script - it might have save me quite a bit of time, thanks!

chr4ss1 commented 7 years ago

that's something similar I did. However for me it was important to have separate REVERSE_CLIENT_ID for debug / release builds.

What I ended up doing is that I store all the settings for current build in ".env" file, and I also have ".env.dev" and ".env.prod" files which get transformed to .env file before the build.

I also wrote cordova hook which reads the settings from .env file, and reads in config.xml and adds "directive" functionality. It basically uses xml2js library to turn config.xml in to temp json object, do the necessary changes / transformations, and save it back to XML file.

The benefit of this is that it's generic, and you can safely check in config.xml to repository.

So, my config xml looks something like this:

<variable name="REVERSE_CLIENT_ID" dynamic-replace-value="@@MYVALUEINENV@@" value="" />

"dynamic-replace-*" is basically a "directive" for that element which lets you read variable from ".env" file, and modify any attribute value. @@..@@ is basically a syntax to look up value from the config. You could also have static values.

That concept could be extended in the future to add ios and android as well in this way:

<variable name="REVERSE_CLIENT_ID" dynamic-replace-ios-value="@@MYVALUEINENV@@" dynamic-replace-android-value="@@MYVALUEINENV@@" value="" />

Say you want to have different bundle ID for ios, you would do:

<widget id="" dynamic-replace-ios-id="@@MYIOSID@@" dynamic-replace-android-id="@@MYANDROIDID@@">....</widget>

this is my cordova hook in config.xml: <hook src="scripts/cordova/config-transformation-hook.js" type="before_prepare"/>

#!/usr/bin/env node

module.exports = function(ctx) {

    var fs = require('fs');
    var path = require('path');
    var parseString = require('xml2js').parseString;
    var xml2js = require('xml2js');

    var Q = ctx.requireCordovaModule('q');
    var deferral = new Q.defer();

    const projectRoot = ctx.opts.projectRoot;
    const configXmlFilePath = path.join(projectRoot, 'config.xml');
    const envFilePath = path.join(projectRoot, '.env');

    let configXmlFileContent = fs.readFileSync(configXmlFilePath, 'utf8');
    let envFileContent = fs.readFileSync(envFilePath, 'utf8');

    let replacements = [];

    // read all the possible replacements
    // note the syntax is @@var@@=VALUE
    for(const replacement of envFileContent.split('\n')) {
        try{
            if(replacement.length === 0)
                continue;

            const replacementParts = replacement.split('=', 2);
            if(replacementParts.length !== 2 || replacementParts[0] === undefined || replacementParts[1] === undefined)
                continue;

            const replacementKey = replacementParts[0];
            const replacementValue = replacementParts[1];

            const match = replacementKey.match(/^@@(.*?)@@$/);
            if(!match)
                continue;

            replacements[replacementKey] = replacementValue;
        } catch(e) {
            console.log("Error in config transformation hook: ", e);
        }
    }

    function recursivelyUpdate(result) {
        if(!result) 
            return;

        if(typeof result === 'string' || typeof propertyValue === 'function')
            return;

        let attributes = result['$'];
        if(attributes) {
            for(let attributeName in attributes) {
                let match = attributeName.match(/^dynamic-replacement-(.*?)$/);
                if(!match)
                    continue;

                let replacementKey = attributes[attributeName];

                if(replacements[replacementKey] !== undefined) {
                    attributes[match[1]] = replacements[replacementKey];
                }
            }
        }

        for(let propertyName in result) {
            if(propertyName !== '$') {

                let propertyValue = result[propertyName];

                if(Array.isArray(propertyValue)) {
                    for(let item of propertyValue) {
                        recursivelyUpdate(item);
                    }
                } else {
                    recursivelyUpdate(propertyValue);
                }           
            }
        }
    }

    // after the reading is done,
    // let's transform config.xml
    parseString(configXmlFileContent, {preserveChildrenOrder: true, includeWhiteChars: true}, function (err, result) {

        recursivelyUpdate(result);

        var builder = new xml2js.Builder();
        var transformedConfigXmlFileContent = builder.buildObject(result);

        fs.writeFileSync(configXmlFilePath, transformedConfigXmlFileContent, 'utf8');

        deferral.resolve();
    });

    return deferral.promise;
}

my .env file looks like this:

@@TEST_VAR@@=hello there

Note that the REVERSED_CLIENT_ID is not used on Android anyways so there is no point to transform it between platforms.