Skip to content

fix(expo): clean stale tools:replace entries when removing backup attributes#678

Open
matingathani wants to merge 2 commits intoAppsFlyerSDK:masterfrom
matingathani:fix/expo-plugin-tools-replace-deduplication
Open

fix(expo): clean stale tools:replace entries when removing backup attributes#678
matingathani wants to merge 2 commits intoAppsFlyerSDK:masterfrom
matingathani:fix/expo-plugin-tools-replace-deduplication

Conversation

@matingathani
Copy link
Copy Markdown

@matingathani matingathani commented Apr 10, 2026

Summary

Fixes a manifest merge failure that occurs when preferAppsFlyerBackupRules=true and another Expo plugin (e.g. expo-secure-store) has already added the removed backup attribute names to tools:replace.

When the plugin deletes android:dataExtractionRules or android:fullBackupContent from the app's manifest, any existing tools:replace entry referencing those same attribute names becomes stale. The Android manifest merger then fails with:

Multiple entries with same key: android:dataExtractionRules=REPLACE

Changes:

  • Track which backup attributes were deleted (removedKeys).
  • After deletion, filter stale attribute names out of any existing tools:replace value; delete tools:replace entirely if it becomes empty.
  • Only mutate the attribute and emit the log when entries were actually removed (no-ops cleanly when tools:replace contains no stale entries).

Test plan

  • preferAppsFlyerBackupRules=false (default): no manifest changes, no log output
  • preferAppsFlyerBackupRules=true, app has no backup attributes: no manifest changes
  • preferAppsFlyerBackupRules=true, app has backup attributes but no tools:replace: attributes removed, no tools:replace clean-up log
  • preferAppsFlyerBackupRules=true, app has backup attributes + tools:replace containing those keys: attributes removed, stale keys filtered from tools:replace, log emitted
  • preferAppsFlyerBackupRules=true, tools:replace contains only stale keys: entire tools:replace attribute deleted

…ributes

When preferAppsFlyerBackupRules=true, the plugin removes
android:dataExtractionRules and android:fullBackupContent from the app
manifest. If another Expo plugin (e.g. expo-secure-store) previously
added those attribute names to tools:replace, they become orphaned after
deletion and cause an Android manifest merge failure:

  "Multiple entries with same key: android:dataExtractionRules=REPLACE"

After removing each attribute, filter it out of any existing tools:replace
value using a Set-based dedup, removing the entry entirely if no other
keys remain.

Fixes AppsFlyerSDK#672
Copilot AI review requested due to automatic review settings April 10, 2026 23:30
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the Expo Android config plugin to prevent Android manifest merge failures caused by stale tools:replace entries after removing backup-related manifest attributes when preferAppsFlyerBackupRules=true.

Changes:

  • Track removed backup-related attributes (android:dataExtractionRules, android:fullBackupContent) when opting into AppsFlyer backup rules.
  • Remove any matching attribute names from tools:replace, deleting tools:replace entirely if it becomes empty.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +76 to +80
const filtered = appAttrs['tools:replace']
.split(',')
.map((s) => s.trim())
.filter((s) => s && !removedKeys.includes(s));
if (filtered.length > 0) {
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR description mentions a Set-based deduplication of tools:replace, but this implementation only filters out removedKeys and does not dedupe remaining entries. Either update the description to match the code, or switch this logic to a Set-based normalize+filter so duplicate keys in tools:replace can’t persist and keep breaking manifest merge.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch on the description. Updated the PR body to say 'filters stale entries' instead of 'Set-based deduplication' — the code only removes the deleted attribute keys from tools:replace and does not otherwise deduplicate remaining entries.

Comment on lines +76 to +85
const filtered = appAttrs['tools:replace']
.split(',')
.map((s) => s.trim())
.filter((s) => s && !removedKeys.includes(s));
if (filtered.length > 0) {
appAttrs['tools:replace'] = filtered.join(', ');
} else {
delete appAttrs['tools:replace'];
}
console.log('[AppsFlyerPlugin] Cleaned stale tools:replace entries for removed backup attributes');
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This log line will print whenever tools:replace exists, even if filtered is identical to the original value (i.e., nothing was actually cleaned). Consider only logging when a change was made, or include the removed keys in the log so it’s not misleading during debugging.

Suggested change
const filtered = appAttrs['tools:replace']
.split(',')
.map((s) => s.trim())
.filter((s) => s && !removedKeys.includes(s));
if (filtered.length > 0) {
appAttrs['tools:replace'] = filtered.join(', ');
} else {
delete appAttrs['tools:replace'];
}
console.log('[AppsFlyerPlugin] Cleaned stale tools:replace entries for removed backup attributes');
const existingReplaceEntries = appAttrs['tools:replace']
.split(',')
.map((s) => s.trim())
.filter(Boolean);
const filtered = existingReplaceEntries.filter((s) => !removedKeys.includes(s));
if (filtered.length !== existingReplaceEntries.length) {
if (filtered.length > 0) {
appAttrs['tools:replace'] = filtered.join(', ');
} else {
delete appAttrs['tools:replace'];
}
console.log('[AppsFlyerPlugin] Cleaned stale tools:replace entries for removed backup attributes');
}

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in latest commit — now captures existingReplaceEntries before filtering, and only mutates the attribute and emits the log when filtered.length !== existingReplaceEntries.length (i.e. something was actually removed).

…s were removed

Previously the log line '[AppsFlyerPlugin] Cleaned stale tools:replace entries'
printed unconditionally whenever tools:replace existed, even when no backup
attribute keys were present in it (i.e. nothing was actually cleaned).

Now: capture the original entry list, compute the filtered list, and only update
the attribute and emit the log when the two lists differ in length.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants