diff --git a/src/Packages/Audience/Editor/AudienceMobileBuildSettings.cs b/src/Packages/Audience/Editor/AudienceMobileBuildSettings.cs index 7049b346..13e42267 100644 --- a/src/Packages/Audience/Editor/AudienceMobileBuildSettings.cs +++ b/src/Packages/Audience/Editor/AudienceMobileBuildSettings.cs @@ -18,17 +18,23 @@ namespace Immutable.Audience.Editor /// public sealed class AudienceMobileBuildSettings : ScriptableObject { - // Fallback so a build never ships with the key missing (which would - // block App Store submission). Studios should override on the asset - // with copy describing what is collected and why. + // Fallback used when no asset exists or the field is left blank. + // Apple reviewers reject generic strings — studios MUST replace this + // with copy that names their app and explains the specific use case + // (e.g. "Game Name uses your advertising identifier to attribute app + // installs and measure ad campaign performance."). Submitting this + // default will likely result in an App Store rejection. internal const string DefaultTrackingUsageDescription = - "Your data may be used to deliver personalised ads to you. " + - "You can change this preference at any time in Settings."; + "This app uses your device's advertising identifier to attribute " + + "app installs and measure ad campaign performance. You can change " + + "this preference at any time in Settings > Privacy & Security > Tracking."; [SerializeField] - [Tooltip("Copy shown in the iOS App Tracking Transparency prompt. " + - "Apple rejects empty or generic strings. Describe what is " + - "collected and why.")] + [Tooltip("REQUIRED: Customise this before submitting to the App Store. " + + "Apple rejects generic strings — describe what YOUR app collects " + + "and why (e.g. 'Game Name uses your advertising identifier to " + + "attribute installs and measure ad performance.'). " + + "Left blank, the SDK default is used but will likely be rejected.")] private string trackingUsageDescription = DefaultTrackingUsageDescription; [SerializeField] diff --git a/src/Packages/Audience/Editor/iOSPrivacyManifestPostProcessor.cs b/src/Packages/Audience/Editor/iOSPrivacyManifestPostProcessor.cs index e8a12821..16542cf8 100644 --- a/src/Packages/Audience/Editor/iOSPrivacyManifestPostProcessor.cs +++ b/src/Packages/Audience/Editor/iOSPrivacyManifestPostProcessor.cs @@ -69,6 +69,26 @@ internal static void ApplyAttributionPrivacyEntries(PlistElementDict root) // IDFA collection constitutes tracking under Apple's definition. root.SetBoolean("NSPrivacyTracking", true); + // Apple requires every domain contacted for tracking to be listed. + const string trackingDomain = "api.immutable.com"; + PlistElementArray trackingDomains; + if (root.values.TryGetValue("NSPrivacyTrackingDomains", out var existingDomains) && + existingDomains is PlistElementArray existingDomainsArray) + { + trackingDomains = existingDomainsArray; + } + else + { + trackingDomains = root.CreateArray("NSPrivacyTrackingDomains"); + } + + var hasDomain = false; + foreach (var item in trackingDomains.values) + { + if (item is PlistElementString ds && ds.value == trackingDomain) { hasDomain = true; break; } + } + if (!hasDomain) trackingDomains.AddString(trackingDomain); + PlistElementArray dataTypes; if (root.values.TryGetValue("NSPrivacyCollectedDataTypes", out var existing) && existing is PlistElementArray existingArray) diff --git a/src/Packages/Audience/Runtime/Plugins/iOS/PrivacyInfo.attribution.xcprivacy b/src/Packages/Audience/Runtime/Plugins/iOS/PrivacyInfo.attribution.xcprivacy index 72d54c0c..2e5cb56c 100644 --- a/src/Packages/Audience/Runtime/Plugins/iOS/PrivacyInfo.attribution.xcprivacy +++ b/src/Packages/Audience/Runtime/Plugins/iOS/PrivacyInfo.attribution.xcprivacy @@ -5,7 +5,9 @@ NSPrivacyTracking NSPrivacyTrackingDomains - + + api.immutable.com + NSPrivacyCollectedDataTypes