Skip to content

Commit e5e9ec6

Browse files
authored
Merge pull request #792 from immutable/feat/sdk-451-ios-privacy-manifest-att
fix(audience): fix iOS App Store rejection due to incomplete privacy manifest and ATT copy
2 parents 3e4b09b + eba29d2 commit e5e9ec6

3 files changed

Lines changed: 37 additions & 9 deletions

File tree

src/Packages/Audience/Editor/AudienceMobileBuildSettings.cs

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,23 @@ namespace Immutable.Audience.Editor
1818
/// </remarks>
1919
public sealed class AudienceMobileBuildSettings : ScriptableObject
2020
{
21-
// Fallback so a build never ships with the key missing (which would
22-
// block App Store submission). Studios should override on the asset
23-
// with copy describing what is collected and why.
21+
// Fallback used when no asset exists or the field is left blank.
22+
// Apple reviewers reject generic strings — studios MUST replace this
23+
// with copy that names their app and explains the specific use case
24+
// (e.g. "Game Name uses your advertising identifier to attribute app
25+
// installs and measure ad campaign performance."). Submitting this
26+
// default will likely result in an App Store rejection.
2427
internal const string DefaultTrackingUsageDescription =
25-
"Your data may be used to deliver personalised ads to you. " +
26-
"You can change this preference at any time in Settings.";
28+
"This app uses your device's advertising identifier to attribute " +
29+
"app installs and measure ad campaign performance. You can change " +
30+
"this preference at any time in Settings > Privacy & Security > Tracking.";
2731

2832
[SerializeField]
29-
[Tooltip("Copy shown in the iOS App Tracking Transparency prompt. " +
30-
"Apple rejects empty or generic strings. Describe what is " +
31-
"collected and why.")]
33+
[Tooltip("REQUIRED: Customise this before submitting to the App Store. " +
34+
"Apple rejects generic strings — describe what YOUR app collects " +
35+
"and why (e.g. 'Game Name uses your advertising identifier to " +
36+
"attribute installs and measure ad performance.'). " +
37+
"Left blank, the SDK default is used but will likely be rejected.")]
3238
private string trackingUsageDescription = DefaultTrackingUsageDescription;
3339

3440
[SerializeField]

src/Packages/Audience/Editor/iOSPrivacyManifestPostProcessor.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,26 @@ internal static void ApplyAttributionPrivacyEntries(PlistElementDict root)
6969
// IDFA collection constitutes tracking under Apple's definition.
7070
root.SetBoolean("NSPrivacyTracking", true);
7171

72+
// Apple requires every domain contacted for tracking to be listed.
73+
const string trackingDomain = "api.immutable.com";
74+
PlistElementArray trackingDomains;
75+
if (root.values.TryGetValue("NSPrivacyTrackingDomains", out var existingDomains) &&
76+
existingDomains is PlistElementArray existingDomainsArray)
77+
{
78+
trackingDomains = existingDomainsArray;
79+
}
80+
else
81+
{
82+
trackingDomains = root.CreateArray("NSPrivacyTrackingDomains");
83+
}
84+
85+
var hasDomain = false;
86+
foreach (var item in trackingDomains.values)
87+
{
88+
if (item is PlistElementString ds && ds.value == trackingDomain) { hasDomain = true; break; }
89+
}
90+
if (!hasDomain) trackingDomains.AddString(trackingDomain);
91+
7292
PlistElementArray dataTypes;
7393
if (root.values.TryGetValue("NSPrivacyCollectedDataTypes", out var existing) &&
7494
existing is PlistElementArray existingArray)

src/Packages/Audience/Runtime/Plugins/iOS/PrivacyInfo.attribution.xcprivacy

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
<key>NSPrivacyTracking</key>
66
<true/>
77
<key>NSPrivacyTrackingDomains</key>
8-
<array/>
8+
<array>
9+
<string>api.immutable.com</string>
10+
</array>
911
<key>NSPrivacyCollectedDataTypes</key>
1012
<array>
1113
<dict>

0 commit comments

Comments
 (0)