apache / cordova-ios

Apache Cordova iOS
https://cordova.apache.org/
Apache License 2.0
2.16k stars 986 forks source link

Issue with `<privacy-manifest>` and `cordora prepare ios` #1418

Closed shajz closed 7 months ago

shajz commented 7 months ago

Bug Report

Problem

What is expected to happen?

The <privacy-manifest> element must be included in the generated PrivacyInfo.xcprivacy file, and the order of its properties must be the same.

What does actually happen?

When running cordova prepare ios, the order of properties is messed up and the mess is exported to the generated PrivacyInfo.xcprivacy file.

Information

config.xml, before running cordova prepare ios

<privacy-manifest>
  <key>NSPrivacyTracking</key>
  <true />
  <key>NSPrivacyCollectedDataTypes</key>
  <array />
  <key>NSPrivacyAccessedAPITypes</key>
  <array>
    <dict>
      <key>NSPrivacyAccessedAPIType</key>
      <string>NSPrivacyAccessedAPICategoryDiskSpace</string>
      <key>NSPrivacyAccessedAPITypeReasons</key>
      <array>
        <string>E174.1</string>
      </array>
    </dict>
    <dict>
      <key>NSPrivacyAccessedAPIType</key>
      <string>NSPrivacyAccessedAPICategoryUserDefaults</string>
      <key>NSPrivacyAccessedAPITypeReasons</key>
      <array>
        <string>CA92.1</string>
      </array>
    </dict>
    <dict>
      <key>NSPrivacyAccessedAPIType</key>
      <string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
      <key>NSPrivacyAccessedAPITypeReasons</key>
      <array>
        <string>3B52.1</string>
      </array>
    </dict>
    <dict>
      <key>NSPrivacyAccessedAPIType</key>
      <string>NSPrivacyAccessedAPICategorySystemBootTime</string>
      <key>NSPrivacyAccessedAPITypeReasons</key>
      <array>
        <string>35F9.1</string>
      </array>
    </dict>
  </array>
  <key>NSPrivacyTrackingDomains</key>
  <array />
</privacy-manifest>

config.xml, after running cordova prepare ios

<privacy-manifest>
  <key>NSPrivacyTracking</key>
  <key>NSPrivacyCollectedDataTypes</key>
  <key>NSPrivacyAccessedAPITypes</key>
  <key>NSPrivacyTrackingDomains</key>
  <true/>
  <array/>
  <array>
    <dict>
      <key>NSPrivacyAccessedAPIType</key>
      <key>NSPrivacyAccessedAPITypeReasons</key>
      <string>NSPrivacyAccessedAPICategoryDiskSpace</string>
      <array>
        <string>E174.1</string>
      </array>
    </dict>
    <dict>
      <key>NSPrivacyAccessedAPIType</key>
      <key>NSPrivacyAccessedAPITypeReasons</key>
      <string>NSPrivacyAccessedAPICategoryUserDefaults</string>
      <array>
        <string>CA92.1</string>
      </array>
    </dict>
    <dict>
      <key>NSPrivacyAccessedAPIType</key>
      <key>NSPrivacyAccessedAPITypeReasons</key>
      <string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
      <array>
        <string>3B52.1</string>
      </array>
    </dict>
    <dict>
      <key>NSPrivacyAccessedAPIType</key>
      <key>NSPrivacyAccessedAPITypeReasons</key>
      <string>NSPrivacyAccessedAPICategorySystemBootTime</string>
      <array>
        <string>35F9.1</string>
      </array>
    </dict>
  </array>
  <array/>
</privacy-manifest>

This is then outputted to the PrivacyInfo.xcprivacy file :

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"><dict><key>NSPrivacyTracking</key><key>NSPrivacyCollectedDataTypes</key><key>NSPrivacyAccessedAPITypes</key><key>NSPrivacyTrackingDomains</key><true /><array /><array><dict><key>NSPrivacyAccessedAPIType</key><key>NSPrivacyAccessedAPITypeReasons</key><string>NSPrivacyAccessedAPICategoryDiskSpace</string><array><string>E174.1</string></array></dict><dict><key>NSPrivacyAccessedAPIType</key><key>NSPrivacyAccessedAPITypeReasons</key><string>NSPrivacyAccessedAPICategoryUserDefaults</string><array><string>CA92.1</string></array></dict><dict><key>NSPrivacyAccessedAPIType</key><key>NSPrivacyAccessedAPITypeReasons</key><string>NSPrivacyAccessedAPICategoryFileTimestamp</string><array><string>3B52.1</string></array></dict><dict><key>NSPrivacyAccessedAPIType</key><key>NSPrivacyAccessedAPITypeReasons</key><string>NSPrivacyAccessedAPICategorySystemBootTime</string><array><string>35F9.1</string></array></dict></array><array /></dict></plist>

The correct output (that works well in XCode) is this one :

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"><dict><key>NSPrivacyTracking</key><true /><key>NSPrivacyCollectedDataTypes</key><array /><key>NSPrivacyAccessedAPITypes</key><array><dict><key>NSPrivacyAccessedAPIType</key><string>NSPrivacyAccessedAPICategoryDiskSpace</string><key>NSPrivacyAccessedAPITypeReasons</key><array><string>E174.1</string></array></dict><dict><key>NSPrivacyAccessedAPIType</key><string>NSPrivacyAccessedAPICategoryUserDefaults</string><key>NSPrivacyAccessedAPITypeReasons</key><array><string>CA92.1</string></array></dict><dict><key>NSPrivacyAccessedAPIType</key><string>NSPrivacyAccessedAPICategoryFileTimestamp</string><key>NSPrivacyAccessedAPITypeReasons</key><array><string>3B52.1</string></array></dict><dict><key>NSPrivacyAccessedAPIType</key><string>NSPrivacyAccessedAPICategorySystemBootTime</string><key>NSPrivacyAccessedAPITypeReasons</key><array><string>35F9.1</string></array></dict></array><key>NSPrivacyTrackingDomains</key><array /></dict></plist>

Command or Code

cordova prepare ios

Environment, Platform, Device

Mac OS 14.4.1

Version information

"cordova": "^12.0.0"
"cordova-ios": "^7.1.0"

Checklist

shajz commented 7 months ago

Oops, I forgot that my app edits the config.xml with a cordova hook and that's the source of my problem. Sorry for the spam

Yura13 commented 6 months ago

@shajz I have the same problem. How do you resolve it?

shajz commented 6 months ago

@Yura13 I was using xml2js https://www.npmjs.com/package/xml2js which doesn't preserve XML elements order. I switched to https://www.npmjs.com/package/fast-xml-parser.

Here's the code (quick and dirty js) :

// Parse config.xml, edit app & package name and save the changes

import { XMLBuilder, XMLParser } from "fast-xml-parser";
import { readFile, writeFile } from 'fs';

const configFile = "config.xml";

const commonOptions = {
  preserveOrder: true,
  ignoreAttributes: false,
  allowBooleanAttributes: true,
  commentPropName: "#comment",
  unpairedTags: [
    'content',
    'access',
    'allow-navigation',
    'preference',
    'allow-intent',
    'param',
    'hook',
    'application',
    'custom-preference',
    'resource-file',
    'icon',
    'splash',
    'true',
  ],
}

const parser = new XMLParser({
  ...commonOptions,
  ignoreDeclaration: false,
});

const builder = new XMLBuilder({
  ...commonOptions,
  suppressUnpairedNode: false,
  format: true,
});

export function updateConfigFile(overrides) {
  readFile(configFile, "utf-8", (err, data) => {
    if (err) {
      throw err;
    }

    const result = parser.parse(data);

    /** Overrides */
    if (overrides?.['android-packageName']) {
      result[1][':@']['@_android-packageName'] = overrides['android-packageName'];
    }

    if (overrides?.['ios-CFBundleIdentifier']) {
      result[1][':@']['@_ios-CFBundleIdentifier'] = overrides['ios-CFBundleIdentifier'];
    }

    if (overrides?.name) {
      result[1].widget = result[1].widget.map((obj) => ((obj?.name != null) ? {...obj, name: [ { '#text': overrides.name } ] } : obj));
    }

    const xml = builder.build(result);

    // write updated XML string to a file
    writeFile(configFile, xml, { encoding: "utf-8" }, (err) => {
      if (err) {
        throw err;
      }

      console.log(`Updated XML is written to a new file.`);
    });
  });
}

You can adapt the overrides part to your usecase, the syntax is weirder than xml2js but the order is preserved with the provided options :)

Saved this as a .mjs file so I could use import syntax safely but you can use require with .js files.

call it like this in your cordova hooks

updateConfigFile({
  'android-packageName': 'com.your.package',
  'ios-CFBundleIdentifier': 'com.your.package',
  name: 'Your app name',
});

Sorry I let this up without providing a solution https://xkcd.com/979/ 😅