Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 14 additions & 8 deletions src/Packages/Audience/Editor/AudienceMobileBuildSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,23 @@ namespace Immutable.Audience.Editor
/// </remarks>
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]
Expand Down
20 changes: 20 additions & 0 deletions src/Packages/Audience/Editor/iOSPrivacyManifestPostProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
<key>NSPrivacyTracking</key>
<true/>
<key>NSPrivacyTrackingDomains</key>
<array/>
<array>
<string>api.immutable.com</string>
</array>
<key>NSPrivacyCollectedDataTypes</key>
<array>
<dict>
Expand Down
Loading