fix: [SDK-4481] dedupe listeners and harden plugin lifecycle - #13
Merged
Conversation
fix(ios): capture launchOptions for cold-start notification taps fix(ios): use retainUntilConsumed for cold-start click replay
Contributor
Author
|
@claude review |
abdulraqeeb33
approved these changes
May 8, 2026
abdulraqeeb33
left a comment
There was a problem hiding this comment.
one small nit otherwise looks fine to me
nan-li
reviewed
May 8, 2026
Merged
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
One Line Summary
Stops the Capacitor SDK from registering duplicate native listeners on
OneSignal.initialize()re-entry, and rounds out the plugin lifecycle on both platforms.Details
Motivation
Calling
OneSignal.initialize()from JS more than once per plugin instance (effect re-runs, hot reload, StrictMode mount→unmount→remount) was layering additional native observers on top of the existing ones. BothEventProducer.subscribeon Android andNSMutableArray addObject:on iOS append without identity dedup, so each re-entry produced a parallel listener path. The same shape existed at the JS bridge layer in three of the four namespace classes, and a stale Vite prebundle in the demo was hiding the fix from local validation. This PR fixes the underlying dedup contract on both platforms, fixes a few related lifecycle correctness issues that surfaced during the investigation, and adds dev scripts so the demo can iterate on this kind of issue with live reload.Scope
Fixed:
foregroundWillDisplay,click,permissionChange,pushSubscriptionChange,userStateChange, and the five IAM lifecycle events firing N times after N init calls (Android, iOS, JS).notificationWillDisplayCacheandpreventDefaultCachegrowing unbounded; entries are now consumed on read.MainScope()coroutines leaking pastActivity.onDestroy()on Android.ApplicationServicenever sawMainActivity.onResume()because Capacitor initialized the SDK after the resume had already fired, leavingisInForeground = false.UNNotificationResponsetaps being dropped on iOS because the OneSignal SDK rejected them while noappIdwas set.displayNotificationandproceedWithWillDisplaywould race on the cache and reject one of them; both are now idempotent.Not changed: public API surface, native event payloads, JS event payloads, the OneSignal Android/iOS SDK versions in use.
Testing
Unit testing
vp testpasses 177/177 with 100% line/function/statement coverage. New tests added:InAppMessagesNamespace.test.ts— bridge-singleton test (3 add calls produce 1 bridge sub) and fan-out test (1 native event reaches all subscribers) for all five IAM events.PushSubscriptionNamespace.test.ts— bridge-singleton test forchange.UserNamespace.test.ts— bridge-singleton test forchange.Manual testing
Tested for IAMs and Notification listeners:
For Android:
Affected code checklist
Checklist
Overview
addEventListeneris additive andinitializeis idempotent)Testing
Final pass
Made with Cursor