From 531e3a18d7c2ef98c7bd703561936551e1ec62bb Mon Sep 17 00:00:00 2001 From: Ruben Nogueira Date: Fri, 21 Apr 2023 11:44:54 +0100 Subject: [PATCH] feat: add signin apple capability automatically fix: android fix: android --- .gitignore | 4 +- package.json | 6 +- plugin.xml | 2 +- scripts/after_prepare.js | 42 +++++--- scripts/before_plugin_uninstall.js | 10 -- scripts/helper.js | 144 ------------------------- scripts/lib/configXmlHelper.js | 117 ++++++++++++++++++++ scripts/lib/ios/projectEntitlements.js | 140 ++++++++++++++++++++++++ scripts/lib/xmlHelper.js | 57 ++++++++++ scripts/utilities.js | 90 ---------------- 10 files changed, 352 insertions(+), 260 deletions(-) delete mode 100644 scripts/before_plugin_uninstall.js delete mode 100644 scripts/helper.js create mode 100644 scripts/lib/configXmlHelper.js create mode 100644 scripts/lib/ios/projectEntitlements.js create mode 100644 scripts/lib/xmlHelper.js delete mode 100644 scripts/utilities.js diff --git a/.gitignore b/.gitignore index 723ef36..0f5657d 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ -.idea \ No newline at end of file +.idea +node_modules +package-lock.json \ No newline at end of file diff --git a/package.json b/package.json index 9398796..e6ed168 100644 --- a/package.json +++ b/package.json @@ -15,5 +15,9 @@ "bugs": { "url": "https://github.com/twogate/cordova-plugin-sign-in-with-apple/issues" }, - "homepage": "https://github.com/twogate/cordova-plugin-sign-in-with-apple#readme" + "homepage": "https://github.com/twogate/cordova-plugin-sign-in-with-apple#readme", + "dependencies": { + "plist": "^3.0.6", + "xml2js": "^0.5.0" + } } diff --git a/plugin.xml b/plugin.xml index 49af460..5dc9d74 100644 --- a/plugin.xml +++ b/plugin.xml @@ -42,6 +42,6 @@ Default - + diff --git a/scripts/after_prepare.js b/scripts/after_prepare.js index 4b2d51f..d250837 100644 --- a/scripts/after_prepare.js +++ b/scripts/after_prepare.js @@ -1,17 +1,33 @@ -#!/usr/bin/env node +/** +Hook is executed at the end of the 'prepare' stage. Usually, when you call 'cordova build'. -'use strict'; +It will inject required preferences in the platform-specific projects, based on +data you have specified in the projects config.xml file. +*/ -var fs = require('fs'); -var path = require('path'); -var utilities = require("./lib/utilities"); +var iosProjectEntitlements = require('./lib/ios/projectEntitlements.js'); +var IOS = 'ios'; -module.exports = function (context) { - //get platform from the context supplied by cordova - var platforms = context.opts.platforms; - // Copy key files to their platform specific folders - if (platforms.indexOf('ios') !== -1 && utilities.directoryExists(IOS_DIR)) { - console.log('Preparing Sign in with Apple on iOS'); - utilities.copyKey(PLATFORM.IOS); - } +module.exports = function(ctx) { + run(ctx); }; + +/** + * Execute hook. + * + * @param {Object} cordovaContext - cordova context object + */ +function run(cordovaContext) { + var platformsList = cordovaContext.opts.platforms; + + platformsList.forEach(function(platform) { + switch (platform) { + case IOS: + { + // generate entitlements file + iosProjectEntitlements.generateAssociatedDomainsEntitlements(cordovaContext); + break; + } + } + }); +} \ No newline at end of file diff --git a/scripts/before_plugin_uninstall.js b/scripts/before_plugin_uninstall.js deleted file mode 100644 index 094abb8..0000000 --- a/scripts/before_plugin_uninstall.js +++ /dev/null @@ -1,10 +0,0 @@ -// https://github.com/arnesson/cordova-plugin-firebase/blob/a32b126274b90d62e8c2a20205a137bc11941b76/scripts/ios/before_plugin_uninstall.js - -var helper = require("./helper"); - -module.exports = function(context) { - - // Remove the build script that was added when the plugin was installed. - var xcodeProjectPath = helper.getXcodeProjectPath(context); - helper.removeShellScriptBuildPhase(context, xcodeProjectPath); -}; diff --git a/scripts/helper.js b/scripts/helper.js deleted file mode 100644 index 364773f..0000000 --- a/scripts/helper.js +++ /dev/null @@ -1,144 +0,0 @@ -// https://github.com/arnesson/cordova-plugin-firebase/blob/a32b126274b90d62e8c2a20205a137bc11941b76/scripts/ios/helper.js -var fs = require("fs"); -var path = require("path"); -var utilities = require("./utilities"); - -/** - * This is used as the display text for the build phase block in XCode as well as the - * inline comments inside of the .pbxproj file for the build script phase block. - */ -var comment = "\"Crashlytics\""; - -module.exports = { - - /** - * Used to get the path to the XCode project's .pbxproj file. - * - * @param {object} context - The Cordova context. - * @returns The path to the XCode project's .pbxproj file. - */ - getXcodeProjectPath: function (context) { - - var appName = utilities.getAppName(context); - - return path.join("platforms", "ios", appName + ".xcodeproj", "project.pbxproj"); - }, - - /** - * This helper is used to add a build phase to the XCode project which runs a shell - * script during the build process. The script executes Crashlytics run command line - * tool with the API and Secret keys. This tool is used to upload the debug symbols - * (dSYMs) so that Crashlytics can display stack trace information in it's web console. - */ - addShellScriptBuildPhase: function (context, xcodeProjectPath) { - var xcode = context.requireCordovaModule("xcode"); - - // Read and parse the XCode project (.pxbproj) from disk. - // File format information: http://www.monobjc.net/xcode-project-file-format.html - var xcodeProject = xcode.project(xcodeProjectPath); - xcodeProject.parseSync(); - - // Build the body of the script to be executed during the build phase. - var script = '"' + '\\"${SRCROOT}\\"' + "/\\\"" + utilities.getAppName(context) + "\\\"/Plugins/" + utilities.getPluginId() + "/Fabric.framework/run" + '"'; - - // Generate a unique ID for our new build phase. - var id = xcodeProject.generateUuid(); - // Create the build phase. - xcodeProject.hash.project.objects.PBXShellScriptBuildPhase[id] = { - isa: "PBXShellScriptBuildPhase", - buildActionMask: 2147483647, - files: [], - inputPaths: [], - name: comment, - outputPaths: [], - runOnlyForDeploymentPostprocessing: 0, - shellPath: "/bin/sh", - shellScript: script, - showEnvVarsInLog: 0 - }; - - // Add a comment to the block (viewable in the source of the pbxproj file). - xcodeProject.hash.project.objects.PBXShellScriptBuildPhase[id + "_comment"] = comment; - - // Add this new shell script build phase block to the targets. - for (var nativeTargetId in xcodeProject.hash.project.objects.PBXNativeTarget) { - - // Skip over the comment blocks. - if (nativeTargetId.indexOf("_comment") !== -1) { - continue; - } - - var nativeTarget = xcodeProject.hash.project.objects.PBXNativeTarget[nativeTargetId]; - - nativeTarget.buildPhases.push({ - value: id, - comment: comment - }); - } - - // Finally, write the .pbxproj back out to disk. - fs.writeFileSync(xcodeProjectPath, xcodeProject.writeSync()); - }, - - /** - * This helper is used to remove the build phase from the XCode project that was added - * by the addShellScriptBuildPhase() helper method. - */ - removeShellScriptBuildPhase: function (context, xcodeProjectPath) { - - var xcode = context.requireCordovaModule("xcode"); - - // Read and parse the XCode project (.pxbproj) from disk. - // File format information: http://www.monobjc.net/xcode-project-file-format.html - var xcodeProject = xcode.project(xcodeProjectPath); - xcodeProject.parseSync(); - - // First, we want to delete the build phase block itself. - - var buildPhases = xcodeProject.hash.project.objects.PBXShellScriptBuildPhase; - - for (var buildPhaseId in buildPhases) { - - var buildPhase = xcodeProject.hash.project.objects.PBXShellScriptBuildPhase[buildPhaseId]; - var shouldDelete = false; - - if (buildPhaseId.indexOf("_comment") === -1) { - // Dealing with a build phase block. - - // If the name of this block matches ours, then we want to delete it. - shouldDelete = buildPhase.name && buildPhase.name.indexOf(comment) !== -1; - } else { - // Dealing with a comment block. - - // If this is a comment block that matches ours, then we want to delete it. - shouldDelete = buildPhaseId === comment; - } - - if (shouldDelete) { - delete buildPhases[buildPhaseId]; - } - } - - // Second, we want to delete the native target reference to the block. - - var nativeTargets = xcodeProject.hash.project.objects.PBXNativeTarget; - - for (var nativeTargetId in nativeTargets) { - - // Skip over the comment blocks. - if (nativeTargetId.indexOf("_comment") !== -1) { - continue; - } - - var nativeTarget = nativeTargets[nativeTargetId]; - - // We remove the reference to the block by filtering out the the ones that match. - nativeTarget.buildPhases = nativeTarget.buildPhases.filter(function (buildPhase) { - return buildPhase.comment !== comment; - }); - } - - // Finally, write the .pbxproj back out to disk. - fs.writeFileSync(xcodeProjectPath, xcodeProject.writeSync()); - } -}; diff --git a/scripts/lib/configXmlHelper.js b/scripts/lib/configXmlHelper.js new file mode 100644 index 0000000..10c0eef --- /dev/null +++ b/scripts/lib/configXmlHelper.js @@ -0,0 +1,117 @@ +/* +Helper class to read data from config.xml file. +*/ +var path = require('path'); +var xmlHelper = require('./xmlHelper.js'); +var ANDROID = 'android'; +var IOS = 'ios'; +var CONFIG_FILE_NAME = 'config.xml'; +var context; +var projectRoot; + +module.exports = ConfigXmlHelper; + +// region public API + +/** + * Constructor. + * + * @param {Object} cordovaContext - cordova context object + */ +function ConfigXmlHelper(cordovaContext) { + context = cordovaContext; + projectRoot = context.opts.projectRoot; +} + +/** + * Read config.xml data as JSON object. + * + * @return {Object} JSON object with data from config.xml + */ +ConfigXmlHelper.prototype.read = function() { + var filePath = getConfigXmlFilePath(); + + return xmlHelper.readXmlAsJson(filePath); +} + +/** + * Get package name for the application. Depends on the platform. + * + * @param {String} platform - 'ios' or 'android'; for what platform we need a package name + * @return {String} package/bundle name + */ +ConfigXmlHelper.prototype.getPackageName = function(platform) { + var configFilePath = getConfigXmlFilePath(); + var config = getCordovaConfigParser(configFilePath); + var packageName; + + switch (platform) { + case ANDROID: + { + packageName = config.android_packageName(); + break; + } + case IOS: + { + packageName = config.ios_CFBundleIdentifier(); + break; + } + } + if (packageName === undefined || packageName.length == 0) { + packageName = config.packageName(); + } + + return packageName; +} + +/** + * Get name of the current project. + * + * @return {String} name of the project + */ +ConfigXmlHelper.prototype.getProjectName = function() { + return getProjectName(); +} + +// endregion + +// region Private API + +/** + * Get config parser from cordova library. + * + * @param {String} configFilePath absolute path to the config.xml file + * @return {Object} + */ +function getCordovaConfigParser(configFilePath) { + var ConfigParser; + + // If we are running Cordova 5.4 or abova - use parser from cordova-common. + // Otherwise - from cordova-lib. + try { + ConfigParser = context.requireCordovaModule('cordova-common/src/ConfigParser/ConfigParser'); + } catch (e) { + ConfigParser = context.requireCordovaModule('cordova-lib/src/configparser/ConfigParser') + } + + return new ConfigParser(configFilePath); +} + +/** + * Get absolute path to the config.xml. + */ +function getConfigXmlFilePath() { + return path.join(projectRoot, CONFIG_FILE_NAME); +} + +/** + * Get project name from config.xml + */ +function getProjectName() { + var configFilePath = getConfigXmlFilePath(); + var config = getCordovaConfigParser(configFilePath); + + return config.name(); +} + +// endregion diff --git a/scripts/lib/ios/projectEntitlements.js b/scripts/lib/ios/projectEntitlements.js new file mode 100644 index 0000000..4e46056 --- /dev/null +++ b/scripts/lib/ios/projectEntitlements.js @@ -0,0 +1,140 @@ +/* +Script creates entitlements file with the list of hosts, specified in config.xml. +File name is: ProjectName.entitlements +Location: ProjectName/ + +Script only generates content. File it self is included in the xcode project in another hook: xcodePreferences.js. +*/ + +var path = require('path'); +var fs = require('fs'); +var plist = require('plist'); +var mkpath = require('mkpath'); +var ConfigXmlHelper = require('../configXmlHelper.js'); +var APPLE_SIGN_IN_KEY = 'com.apple.developer.applesignin'; +var context; +var projectName; +var entitlementsFilePath; + +module.exports = { + generateAssociatedDomainsEntitlements: generateEntitlements +}; + +// region Public API + +/** + * Generate entitlements file content. + * + * @param {Object} cordovaContext - cordova context object + * @param {Object} pluginPreferences - plugin preferences from config.xml; already parsed + */ +function generateEntitlements(cordovaContext) { + context = cordovaContext; + + var currentEntitlements = getEntitlementsFileContent(); + var newEntitlements = injectPreferences(currentEntitlements); + + saveContentToEntitlementsFile(newEntitlements); +} + +// endregion + +// region Work with entitlements file + +/** + * Save data to entitlements file. + * + * @param {Object} content - data to save; JSON object that will be transformed into xml + */ +function saveContentToEntitlementsFile(content) { + var plistContent = plist.build(content); + var filePath = pathToEntitlementsFile(); + + // ensure that file exists + mkpath.sync(path.dirname(filePath)); + + // save it's content + fs.writeFileSync(filePath, plistContent, 'utf8'); +} + +/** + * Read data from existing entitlements file. If none exist - default value is returned + * + * @return {String} entitlements file content + */ +function getEntitlementsFileContent() { + var pathToFile = pathToEntitlementsFile(); + var content; + + try { + content = fs.readFileSync(pathToFile, 'utf8'); + } catch (err) { + return defaultEntitlementsFile(); + } + + return plist.parse(content); +} + +/** + * Get content for an empty entitlements file. + * + * @return {String} default entitlements file content + */ +function defaultEntitlementsFile() { + return {}; +} + +/** + * Inject list of hosts into entitlements file. + * + * @param {Object} currentEntitlements - entitlements where to inject preferences + * @return {Object} new entitlements content + */ +function injectPreferences(currentEntitlements) { + var newEntitlements = currentEntitlements; + newEntitlements[APPLE_SIGN_IN_KEY] = ['Default']; + + return newEntitlements; +} + +// endregion + +// region Path helper methods + +/** + * Path to entitlements file. + * + * @return {String} absolute path to entitlements file + */ +function pathToEntitlementsFile() { + if (entitlementsFilePath === undefined) { + entitlementsFilePath = path.join(getProjectRoot(), 'platforms', 'ios', getProjectName(), 'Resources', getProjectName() + '.entitlements'); + } + + return entitlementsFilePath; +} + +/** + * Projects root folder path. + * + * @return {String} absolute path to the projects root + */ +function getProjectRoot() { + return context.opts.projectRoot; +} + +/** + * Name of the project from config.xml + * + * @return {String} project name + */ +function getProjectName() { + if (projectName === undefined) { + var configXmlHelper = new ConfigXmlHelper(context); + projectName = configXmlHelper.getProjectName(); + } + + return projectName; +} + +// endregion diff --git a/scripts/lib/xmlHelper.js b/scripts/lib/xmlHelper.js new file mode 100644 index 0000000..a2c9bf0 --- /dev/null +++ b/scripts/lib/xmlHelper.js @@ -0,0 +1,57 @@ +/* +Small helper class to read/write from/to xml file. +*/ + +var fs = require('fs'); +var xml2js = require('xml2js'); + +module.exports = { + readXmlAsJson: readXmlAsJson, + writeJsonAsXml: writeJsonAsXml +}; + +/** + * Read data from the xml file as JSON object. + * + * @param {String} filePath - absolute path to xml file + * @return {Object} JSON object with the contents of the xml file + */ +function readXmlAsJson(filePath) { + var xmlData; + var xmlParser; + var parsedData; + + try { + xmlData = fs.readFileSync(filePath, 'utf8'); + xmlParser = new xml2js.Parser(); + xmlParser.parseString(xmlData, function(err, data) { + if (data) { + parsedData = data; + } + }); + } catch (err) {} + + return parsedData; +} + +/** + * Write JSON object as xml into the specified file. + * + * @param {Object} jsData - JSON object to write + * @param {String} filePath - path to the xml file where data should be saved + * @return {boolean} true - if data saved to file; false - otherwise + */ +function writeJsonAsXml(jsData, filePath, options) { + var xmlBuilder = new xml2js.Builder(options); + var changedXmlData = xmlBuilder.buildObject(jsData); + var isSaved = true; + + try { + fs.writeFileSync(filePath, changedXmlData, 'utf8'); + } catch (err) { + console.log(err); + isSaved = false; + } + + return isSaved; +} diff --git a/scripts/utilities.js b/scripts/utilities.js deleted file mode 100644 index 21fe8d4..0000000 --- a/scripts/utilities.js +++ /dev/null @@ -1,90 +0,0 @@ -// https://github.com/arnesson/cordova-plugin-firebase/blob/a32b126274b90d62e8c2a20205a137bc11941b76/scripts/lib/utilities.js - -/** - * Utilities and shared functionality for the build hooks. - */ -var fs = require('fs'); -var path = require("path"); - -fs.ensureDirSync = function (dir) { - if (!fs.existsSync(dir)) { - dir.split(path.sep).reduce(function (currentPath, folder) { - currentPath += folder + path.sep; - if (!fs.existsSync(currentPath)) { - fs.mkdirSync(currentPath); - } - return currentPath; - }, ''); - } -}; - -module.exports = { - /** - * Used to get the name of the application as defined in the config.xml. - * - * @param {object} context - The Cordova context. - * @returns {string} The value of the name element in config.xml. - */ - getAppName: function (context) { - var ConfigParser = context.requireCordovaModule("cordova-lib").configparser; - var config = new ConfigParser("config.xml"); - return config.name(); - }, - - /** - * The ID of the plugin; this should match the ID in plugin.xml. - */ - getPluginId: function () { - return "cordova-plugin-sign-in-with-apple"; - }, - - copyKey: function (platform) { - for (var i = 0; i < platform.src.length; i++) { - var file = platform.src[i]; - if (this.fileExists(file)) { - try { - var contents = fs.readFileSync(file).toString(); - - try { - platform.dest.forEach(function (destinationPath) { - var folder = destinationPath.substring(0, destinationPath.lastIndexOf('/')); - fs.ensureDirSync(folder); - fs.writeFileSync(destinationPath, contents); - }); - } catch (e) { - // skip - } - } catch (err) { - console.log(err); - } - - break; - } - } - }, - - getValue: function (config, name) { - var value = config.match(new RegExp('<' + name + '(.*?)>(.*?)', 'i')); - if (value && value[2]) { - return value[2] - } else { - return null - } - }, - - fileExists: function (path) { - try { - return fs.statSync(path).isFile(); - } catch (e) { - return false; - } - }, - - directoryExists: function (path) { - try { - return fs.statSync(path).isDirectory(); - } catch (e) { - return false; - } - } -};