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 + '(.*?)>(.*?)' + 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;
- }
- }
-};