Skip to content

fix: [SDK-4792] keep devices subscribed when a push token fetch temporarily fails#2670

Merged
nan-li merged 2 commits into
mainfrom
nan/sdk-4792
Jun 26, 2026
Merged

fix: [SDK-4792] keep devices subscribed when a push token fetch temporarily fails#2670
nan-li merged 2 commits into
mainfrom
nan/sdk-4792

Conversation

@nan-li

@nan-li nan-li commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Description

One Line Summary

A temporary push token retrieval error no longer marks an already-subscribed device as unsubscribed.

Details

Motivation

Some devices showed as Unsubscribed in OneSignal even though they could still receive push.

When the SDK briefly can't get a push token from FCM/HMS (for example a short network or Google/Huawei service hiccup), it produces an error code such as -9 or -11 with no token in the response. The SDK saved that error onto a subscription that was already subscribed, which told the OneSignal backend the device was unsubscribed.

There was already a check meant to prevent this, but it relied on a value kept only in memory. That value is lost when the app process restarts, so opening the app fresh during an outage could still trigger the bug.

Scope

  • The fix is in SubscriptionManager, the single place push subscription status is saved. It ignores a temporary, no-token error when the subscription is already subscribed and still has a token. Because it reads the saved subscription, it also holds after an app restart.
  • Real unsubscribes are unaffected: turning off notification permission, opting out, or disabling via the REST API are not temporary errors and still update the status.
  • Email and SMS subscriptions are untouched, and the normal case of receiving a fresh token still updates as before.

Other

The error codes treated as temporary (ignored only when a valid token already exists): -8, -9, -11, -12 (FCM), -25, -27 (HMS), and -29 (FCM auth). They're grouped behind a new internal SubscriptionStatus.isRetryableTokenError flag.

Testing

Unit testing

Added to SubscriptionManagerTests:

  • For each of the 7 temporary codes: an already-subscribed device with a token stays subscribed and keeps its token.
  • Real downgrades (no permission, opt-out, REST API disable) still apply.
  • The error is still saved when there is no token to protect, or when the device is not already subscribed.
  • The new flag matches exactly those 7 codes and nothing else.
  • Fixed an existing test that paired a real token with an error code, a combination that never happens in production (a success always returns a token; an error always returns no token).

Manual testing

On a device already subscribed with a valid token, forced push registration to return -9 (SERVICE_NOT_AVAILABLE) with no token, then cold-started the app:

  • Before this change: the status was overwritten to -9 and the device was marked unsubscribed.
  • After this change: the -9 is ignored, the device stays subscribed, and no subscription update is sent.

Affected code checklist

  • Notifications
    • Display
    • Open
    • Push Processing
    • Confirm Deliveries
  • Outcomes
  • Sessions
  • In-App Messaging
  • REST API requests
  • Public API changes

Checklist

Overview

  • I have filled out all REQUIRED sections above
  • PR does one thing
  • Any Public API changes are explained in the PR details and conform to existing APIs

Testing

  • I have included test coverage for these changes, or explained why they are not needed
  • All automated tests pass, or I explained why that is not possible
  • I have personally tested this on my device, or explained why that is not possible

Final pass

  • Code is as readable as possible.
  • I have reviewed this PR myself, ensuring it meets each checklist item

Made with Cursor

…h errors

A tokenless, transient FCM/HMS token-fetch error (e.g. -9, -11) could overwrite an
already-SUBSCRIBED push subscription and flip it to unsubscribed on the backend. The
prior guard lived in PushTokenManager using in-memory state that reset on process
restart, so a cold start during an FCM/HMS outage could still persist the error.

- Add SubscriptionStatus.isRetryableTokenError covering the transient FCM/HMS codes
  (-8, -9, -11, -12, -25, -27, -29).
- Add a durable guard in SubscriptionManager.addOrUpdatePushSubscriptionToken that
  ignores a tokenless retryable error when the persisted subscription is already
  SUBSCRIBED with a valid token. Real opt-outs / permission changes are not retryable
  and still downgrade as before.
- Add tests for the guard and the new flag; correct an existing test that paired a
  non-null token with an error status (an impossible production combination).

Co-authored-by: Cursor <cursoragent@cursor.com>
@github-actions

github-actions Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

📊 Diff Coverage Report

Diff Coverage Report (Changed Lines Only)

Gate: aggregate coverage on changed executable lines must be ≥ 80% (JaCoCo line data for lines touched in the diff).

Changed Files Coverage

  • SubscriptionModel.kt: 9/9 touched executable lines (100.0%) (27 touched lines in diff)
  • SubscriptionManager.kt: 8/8 touched executable lines (100.0%) (13 touched lines in diff)

Overall (aggregate gate)

17/17 touched executable lines covered (100.0% — requires ≥ 80%)

📥 View workflow run

…iption update

Check the guard's side effect, not just the final model values: a retryable token-fetch
error with no token emits no model change (so no subscription update is sent), while a
non-retryable status still emits one. Also correct the -29 (AUTHENTICATION_FAILED) doc to
note it is retryable, matching how PushRegistrator already classifies it.

Co-authored-by: Cursor <cursoragent@cursor.com>
@nan-li nan-li merged commit 0e90955 into main Jun 26, 2026
5 checks passed
@nan-li nan-li deleted the nan/sdk-4792 branch June 26, 2026 15:57
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