diff --git a/lib/PlatformConfigParser.js b/lib/PlatformConfigParser.js
new file mode 100644
index 000000000..675287c62
--- /dev/null
+++ b/lib/PlatformConfigParser.js
@@ -0,0 +1,32 @@
+/**
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+*/
+
+const ConfigParser = require('cordova-common').ConfigParser;
+
+class PlatformConfigParser extends ConfigParser {
+ /**
+ * Returns the privacy manifest node, if available.
+ * Otherwise `null` is returned.
+ */
+ getPrivacyManifest () {
+ return this.doc.find('./platform[@name="ios"]/privacy-manifest');
+ }
+}
+
+module.exports = PlatformConfigParser;
diff --git a/lib/prepare.js b/lib/prepare.js
index 38239e4c8..cfaab02c2 100644
--- a/lib/prepare.js
+++ b/lib/prepare.js
@@ -17,12 +17,11 @@
under the License.
*/
-'use strict';
-
const fs = require('fs-extra');
const path = require('path');
const unorm = require('unorm');
const plist = require('plist');
+const et = require('elementtree');
const URL = require('url');
const events = require('cordova-common').events;
const xmlHelpers = require('cordova-common').xmlHelpers;
@@ -35,6 +34,7 @@ const FileUpdater = require('cordova-common').FileUpdater;
const projectFile = require('./projectFile');
const Podfile = require('./Podfile').Podfile;
const check_reqs = require('./check_reqs');
+const PlatformConfigParser = require('./PlatformConfigParser');
// launch storyboard and related constants
const IMAGESET_COMPACT_SIZE_CLASS = 'compact';
@@ -43,9 +43,16 @@ const CDV_ANY_SIZE_CLASS = 'any';
module.exports.prepare = function (cordovaProject, options) {
const platformJson = PlatformJson.load(this.locations.root, 'ios');
const munger = new PlatformMunger('ios', this.locations.root, platformJson, new PluginInfoProvider());
-
this._config = updateConfigFile(cordovaProject.projectConfig, munger, this.locations);
+ const parser = new PlatformConfigParser(cordovaProject.projectConfig.path);
+ try {
+ const manifest = parser.getPrivacyManifest();
+ overwritePrivacyManifest(manifest, this.locations);
+ } catch (err) {
+ return Promise.reject(new CordovaError(`Could not parse PrivacyManifest in config.xml: ${err}`));
+ }
+
// Update own www dir with project's www assets and plugins' assets and js-files
return updateWww(cordovaProject, this.locations)
// update project according to config.xml changes.
@@ -87,6 +94,33 @@ module.exports.clean = function (options) {
});
};
+/**
+ * Overwrites the privacy manifest file with the provided manifest or sets the default manifest.
+ * @param {ElementTree} manifest - The manifest to be written to the privacy manifest file.
+ * @param {Object} locations - The locations object containing the path to the Xcode Cordova project.
+ */
+function overwritePrivacyManifest (manifest, locations) {
+ const privacyManifestDest = path.join(locations.xcodeCordovaProj, 'PrivacyInfo.xcprivacy');
+ if (manifest != null) {
+ const XML_DECLARATION = '\n';
+ const DOCTYPE = '\n';
+ const plistElement = et.Element('plist');
+ plistElement.set('version', '1.0');
+ const dictElement = et.SubElement(plistElement, 'dict');
+ manifest.getchildren().forEach((child) => {
+ dictElement.append(child);
+ });
+ const etree = new et.ElementTree(plistElement);
+ const xmlString = XML_DECLARATION + DOCTYPE + etree.write({ xml_declaration: false });
+ fs.writeFileSync(privacyManifestDest, xmlString, 'utf-8');
+ return;
+ }
+ // Set default privacy manifest
+ const defaultPrivacyManifest = path.join(__dirname, '..', 'templates', 'project', '__PROJECT_NAME__', 'PrivacyInfo.xcprivacy');
+ const xmlString = fs.readFileSync(defaultPrivacyManifest, 'utf8');
+ fs.writeFileSync(privacyManifestDest, xmlString, 'utf-8');
+}
+
/**
* Updates config files in project based on app's config.xml and config munge,
* generated by plugins.
diff --git a/package-lock.json b/package-lock.json
index 744d6aa17..862e21156 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10,6 +10,7 @@
"license": "Apache-2.0",
"dependencies": {
"cordova-common": "^5.0.0",
+ "elementtree": "^0.1.7",
"execa": "^5.1.1",
"fs-extra": "^11.1.1",
"ios-sim": "^8.0.2",
diff --git a/package.json b/package.json
index 6082340a1..2a305d22b 100644
--- a/package.json
+++ b/package.json
@@ -51,7 +51,8 @@
"unorm": "^1.6.0",
"which": "^3.0.1",
"xcode": "^3.0.1",
- "xml-escape": "^1.1.0"
+ "xml-escape": "^1.1.0",
+ "elementtree": "^0.1.7"
},
"nyc": {
"include": [
diff --git a/tests/spec/unit/fixtures/prepare/no-privacy-manifest.xml b/tests/spec/unit/fixtures/prepare/no-privacy-manifest.xml
new file mode 100644
index 000000000..98afac213
--- /dev/null
+++ b/tests/spec/unit/fixtures/prepare/no-privacy-manifest.xml
@@ -0,0 +1,23 @@
+
+
+
+
+ SampleApp
+
diff --git a/tests/spec/unit/fixtures/prepare/privacy-manifest.xml b/tests/spec/unit/fixtures/prepare/privacy-manifest.xml
new file mode 100644
index 000000000..510464a50
--- /dev/null
+++ b/tests/spec/unit/fixtures/prepare/privacy-manifest.xml
@@ -0,0 +1,56 @@
+
+
+
+
+ SampleApp
+
+
+ NSPrivacyTracking
+
+ NSPrivacyAccessedAPITypes
+
+ NSPrivacyTrackingDomains
+
+ NSPrivacyCollectedDataTypes
+
+
+
+ NSPrivacyCollectedDataType
+ NSPrivacyCollectedDataTypeDeviceID
+
+
+ NSPrivacyCollectedDataTypeLinked
+
+
+
+ NSPrivacyCollectedDataTypeTracking
+
+
+
+ NSPrivacyCollectedDataTypePurposes
+
+ NSPrivacyCollectedDataTypePurposeAppFunctionality
+
+
+
+
+
+
diff --git a/tests/spec/unit/prepare.spec.js b/tests/spec/unit/prepare.spec.js
index b51a9f6da..2ec83531f 100644
--- a/tests/spec/unit/prepare.spec.js
+++ b/tests/spec/unit/prepare.spec.js
@@ -1504,6 +1504,44 @@ describe('prepare', () => {
expect(plist.build.calls.mostRecent().args[0].CFBundleDisplayName).toEqual('MyApp');
});
});
+ it('Test#021 : - should write out the privacy manifest ', () => {
+ plist.parse.and.callThrough();
+ writeFileSyncSpy.and.callThrough();
+ const projectRoot = iosProject;
+ const platformProjDir = path.join(projectRoot, 'platforms', 'ios', 'SampleApp');
+ const PlatformConfigParser = require('../../../lib/PlatformConfigParser');
+ const my_config = new PlatformConfigParser(path.join(FIXTURES, 'prepare', 'privacy-manifest.xml'));
+ const privacyManifest = my_config.getPrivacyManifest();
+ const overwritePrivacyManifest = prepare.__get__('overwritePrivacyManifest');
+ overwritePrivacyManifest(privacyManifest, p.locations);
+ const privacyManifestPathDest = path.join(platformProjDir, 'PrivacyInfo.xcprivacy');
+ expect(writeFileSyncSpy).toHaveBeenCalledWith(privacyManifestPathDest, jasmine.any(String), 'utf-8');
+ const xml = writeFileSyncSpy.calls.all()[0].args[1];
+ const json = plist.parse(xml);
+ expect(json.NSPrivacyTracking).toBeTrue();
+ expect(json.NSPrivacyAccessedAPITypes.length).toBe(0);
+ expect(json.NSPrivacyTrackingDomains.length).toBe(0);
+ expect(json.NSPrivacyCollectedDataTypes.length).toBe(1);
+ });
+ it('Test#022 : no - should write out the privacy manifest ', () => {
+ plist.parse.and.callThrough();
+ writeFileSyncSpy.and.callThrough();
+ const projectRoot = iosProject;
+ const platformProjDir = path.join(projectRoot, 'platforms', 'ios', 'SampleApp');
+ const PlatformConfigParser = require('../../../lib/PlatformConfigParser');
+ const my_config = new PlatformConfigParser(path.join(FIXTURES, 'prepare', 'no-privacy-manifest.xml'));
+ const privacyManifest = my_config.getPrivacyManifest();
+ const overwritePrivacyManifest = prepare.__get__('overwritePrivacyManifest');
+ overwritePrivacyManifest(privacyManifest, p.locations);
+ const privacyManifestPathDest = path.join(platformProjDir, 'PrivacyInfo.xcprivacy');
+ expect(writeFileSyncSpy).toHaveBeenCalledWith(privacyManifestPathDest, jasmine.any(String), 'utf-8');
+ const xml = writeFileSyncSpy.calls.all()[0].args[1];
+ const json = plist.parse(xml);
+ expect(json.NSPrivacyTracking).toBeFalse();
+ expect(json.NSPrivacyAccessedAPITypes.length).toBe(0);
+ expect(json.NSPrivacyTrackingDomains.length).toBe(0);
+ expect(json.NSPrivacyCollectedDataTypes.length).toBe(0);
+ });
});
describe(' tests', () => {