Skip to content

Migrate push registrations back to WPCom when the Woo-driven system is disabled#16183

Merged
irfano merged 12 commits into
release/25.1from
fix-25.1-woo-push-wpcom-migration
Jul 6, 2026
Merged

Migrate push registrations back to WPCom when the Woo-driven system is disabled#16183
irfano merged 12 commits into
release/25.1from
fix-25.1-woo-push-wpcom-migration

Conversation

@irfano

@irfano irfano commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Description

When the woo_self_driven_push_notifications_m1 feature flag is disabled remotely, devices that already registered with the Woo Core push system keep their local Woo registration. That state suppresses the WP.com fallback (the app drops incoming WP.com notifications for Woo-registered sites, and WP.com settings for those sites stay disabled), so those users can end up without notifications, permanently after an FCM token rotation. This PR adds recovery logic for both paths, following the approach suggested by Hicham (supersedes #16166):

Flag on: before re-registering, local Woo registrations whose stored token no longer matches the current FCM token are cleared, so a rotated token can't leave stale state behind (mirrors the iOS behavior of clearing registered site IDs on token change).

Flag off: Woo push registrations are migrated back to WP.com:

  • Sites with a working WP.com fallback (visible + full Jetpack connection) are re-enabled on WP.com first, and unregistered from Woo Core only if that succeeds. On failure they stay on Woo push and the migration retries on the next run.
  • Sites without a fallback (site credentials, Jetpack CP, hidden sites) are unregistered unconditionally.
flowchart TD
    A["RegisterDevice runs with current FCM token"] --> B{"Woo self-driven push flag on?"}

    B -- yes --> C["Clear local Woo registrations whose stored token != current FCM token"]
    C --> D["Woo registration for sites, as today"]
    D --> E["Successful registrations save current token and disable WP.com notifications"]

    B -- no --> G{"Trigger is SITE_SWITCH?"}
    G -- yes --> H["Skip migration"]
    G -- no --> I["Find locally Woo-registered site IDs"]
    I --> J{"Any Woo-registered sites?"}
    J -- no --> K["Nothing to migrate"]
    J -- yes --> L["Determine WP.com fallback sites: visible + full Jetpack, with WP.com account"]
    L --> M["Ensure WP.com device is registered"]
    M --> N["Re-enable WP.com notifications for fallback sites"]
    N --> O{"WP.com took over successfully?"}
    O -- yes --> P["Unregister ALL Woo-registered sites from Woo Core"]
    O -- no --> Q["Unregister only sites without a WP.com fallback"]
    P --> R["Local state cleared per site only after server delete succeeds"]
    Q --> R
Loading

Also adds wpcom_device_enable_push_notifications_success/error events, mirroring the existing disable events.

Test Steps

Use a debug build with a Jetpack-connected store on Woo Core 10.9.2. Until 10.9.2 is released, either install a nightly/dev build of the plugin or temporarily lower PUSH_NOTIFICATIONS_MIN_WC_VERSION to 10.9.1; on older versions the app treats even successfully registered sites as unregistered, which skews the results. The flag can be toggled from Developer Options → Feature Flags. To force an FCM token rotation, apply this debug receiver patch and use the adb broadcast from #16166.

Migration (flag off):

  1. With the flag on, register for Woo push notifications and confirm an order notification arrives.
  2. Disable the flag and foreground the app.
  3. Verify logs: Migrating Woo push registrations back to WP.com...WPCom notifications enabled for sites...Woo Core push token deleted for site....
  4. Create an order and confirm a single notification arrives (via WP.com, no duplicates).
  5. Foreground again and verify the migration does not re-run.

Token rotation while the flag is off (the original blackout scenario):

  1. With the flag on, register for Woo push notifications and confirm an order notification arrives.
  2. Disable the flag, then force an FCM token rotation using the debug receiver broadcast.
  3. Verify the migration logs from the previous scenario run.
  4. Create an order and confirm the notification arrives. Since the old token is dead after rotation, Woo Core can no longer deliver, so an arriving notification proves WP.com took over.

Stale token cleanup (flag on):

  1. With the flag on and the store registered, force an FCM token rotation.
  2. Verify logs: Cleared stale Woo Core push registration for site... followed by re-registration with the new token.
  3. Create an order and confirm the notification still arrives.

Site-credentials store (flag off): repeat the migration steps with a site-credentials login; verify no WP.com calls are made and the Woo token is still deleted.

Images/gif

N/A

  • I have considered if this change warrants release notes and have added them to RELEASE-NOTES.txt if necessary. Use the "[Internal]" label for non-user-facing changes.

@irfano irfano added type: bug A confirmed bug. feature: notifications Related to notifications or notifs. category: tracks Related to analytics, including Tracks Events. labels Jul 2, 2026
@dangermattic

dangermattic commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator
1 Warning
⚠️ This PR is assigned to the milestone 25.1 ❄️. The due date for this milestone has already passed.
Please assign it to a milestone with a later deadline or check whether the release for this milestone has already been finished.
2 Messages
📖 This PR contains changes to RELEASE-NOTES.txt.
Note that these changes won't affect the final version of the release notes as this version is in code freeze.
Please, get in touch with a release manager if you want to update the final release notes.
📖

This PR contains changes to Tracks-related logic. Please ensure (author and reviewer) the following are completed:

  • The tracks events must be validated in the Tracks system.
  • Verify the internal Tracks spreadsheet has also been updated.
  • Please consider registering any new events.
  • The PR must be assigned the category: tracks label.

Generated by 🚫 Danger

@irfano irfano added this to the 25.1 ❄️ milestone Jul 2, 2026
@irfano irfano force-pushed the fix-25.1-woo-push-wpcom-migration branch from 437ccdf to 0119ac6 Compare July 2, 2026 20:21
@wpmobilebot

wpmobilebot commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator

App Icon📲 You can test the changes from this Pull Request in WooCommerce Android by scanning the QR code below to install the corresponding build.

App NameWooCommerce Android
Platform📱 Mobile
FlavorJalapeno
Build TypeDebug
Build Number768
Version25.1-rc-2
Application IDcom.woocommerce.android.prealpha
Commiteca6fdb
Installation URL6dbi1s4fir00o
Automatticians: You can use our internal self-serve MC tool to give yourself access to those builds if needed.

@codecov-commenter

codecov-commenter commented Jul 2, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 72.72727% with 24 lines in your changes missing coverage. Please review.
✅ Project coverage is 42.52%. Comparing base (12719d3) to head (eca6fdb).

Files with missing lines Patch % Lines
...d/notifications/push/PushNotificationRepository.kt 63.93% 12 Missing and 10 partials ⚠️
...merce/android/notifications/push/RegisterDevice.kt 92.00% 0 Missing and 2 partials ⚠️
Additional details and impacted files
@@                Coverage Diff                 @@
##             release/25.1   #16183      +/-   ##
==================================================
+ Coverage           42.49%   42.52%   +0.02%     
- Complexity          13893    13917      +24     
==================================================
  Files                2529     2529              
  Lines              146006   146082      +76     
  Branches            21322    21347      +25     
==================================================
+ Hits                62051    62125      +74     
+ Misses              77645    77637       -8     
- Partials             6310     6320      +10     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

private fun Preferences.registeredSiteIds(): Set<Long> = asMap().keys
.mapNotNull { key ->
key.name
.takeIf { it.startsWith(PUSH_TOKEN_KEY_PREFIX) && !it.startsWith(PUSH_TOKEN_VALUE_KEY_PREFIX) }

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

No behavior change here. The old parsing in getWooPushRegisteredSiteIds already excluded push_token_value_* keys, but only as a side effect: removePrefix("push_token_") left "value_123", which fails toLongOrNull() and gets dropped. This condition states that rule explicitly instead of relying on the leftover string not parsing as a number. Same site IDs come out either way.

@hichamboushaba hichamboushaba left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks @irfano, code looks good, there is just one question to clarify before approving.

val shouldForce = trigger == Trigger.TOKEN_REFRESH

if (featureFlagRepository.isEnabled(FeatureFlag.WOO_SELF_DRIVEN_PUSH_NOTIFICATIONS_M1)) {
pushNotificationRepository.clearWooPushRegistrationsForStaleToken(token)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

❓ isn't the existing logic in

enough here? The token staleness was part of the refactor I did when I introduced RegisterDevice, and I'm trying to understand why we need to remove the registration state instead of what shouldRegisterWooPushForSite does.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

shouldRegisterWooPushForSite decides whether to retry the registration, but it doesn't change the registration state between attempts. Until a retry succeeds, the site still counts as Woo-registered. If the failure is persistent, this can last forever, and two problems remain during that time:

  1. The status keeps saying "registered". For app-password and Jetpack CP sites this also keeps the "Enable push notifications" entry points hidden, so the user can't recover from within the app, the only fix is clearing app data. And Woo push is the only channel these sites have.
  2. NotificationMessageHandler keeps dropping incoming WP.com notifications for the site. The Woo registration holds the dead token, so if WP.com is actually sending, e.g. when the disable call failed at registration time, those notifications never show up.

Clearing on token mismatch makes the local state match reality: the site isn't registered until a registration with the current token succeeds. It doesn't replace the staleness check, re-registration is still driven by shouldRegisterWooPushForSite for non-forced triggers and locale changes.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks, that makes sense. My remaining concern is mostly with the flow here:

clearWooPushRegistrationsForStaleToken(token)
shouldRegisterWooPushForSite(token, site.siteId)

shouldRegisterWooPushForSite already checks registration.token != currentToken, but after the up-front clear, stale-token cases become registration == null instead. Could we keep the stale-token cleanup closer to the per-site registration decision?

Something like:

val shouldRegisterSite = shouldForce ||
    pushNotificationRepository.shouldRegisterWooPushForSite(token, site.siteId)

if (shouldRegisterSite) {
    pushNotificationRepository.clearWooPushRegistrationForStaleToken(site.siteId, token)
    pushNotificationRepository.registerPushTokenInWooCoreSystem(...)
}

The reason I find this easier to reason about is that the same per-site flow owns both decisions: first decide whether this site needs registration, then clear stale local state only as part of that registration path. That makes the token-mismatch case explicit without having a separate pre-pass that changes what shouldRegisterWooPushForSite sees. Not blocking.

@irfano irfano Jul 4, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Agreed, moved the cleanup into the per-site path in eca6fdb.

@hichamboushaba hichamboushaba left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks @irfano, pre-approving, I left one last comment on the previous thread, but it's not a blocker.

@irfano irfano merged commit ec5f7a5 into release/25.1 Jul 6, 2026
16 checks passed
@irfano irfano deleted the fix-25.1-woo-push-wpcom-migration branch July 6, 2026 11:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

category: tracks Related to analytics, including Tracks Events. feature: notifications Related to notifications or notifs. type: bug A confirmed bug.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants