Skip to content

feat: [SDK-4788] prototype iOS notification delegate proxy - #189

Closed
fadi-george wants to merge 2 commits into
mainfrom
fadi/sdk-4788-fix-1
Closed

feat: [SDK-4788] prototype iOS notification delegate proxy#189
fadi-george wants to merge 2 commits into
mainfrom
fadi/sdk-4788-fix-1

Conversation

@fadi-george

Copy link
Copy Markdown
Contributor

Description

One Line Summary

Prototypes an iOS managed delegate proxy that preserves OneSignal swizzling while forwarding third-party notification delegates safely.

Details

Motivation

OneSignal's prefixed swizzled selectors can crash the .NET registrar when Plugin.LocalNotification installs a managed UNUserNotificationCenterDelegate. The proxy supplies generated block-conversion metadata for both selector forms and prevents that registrar failure.

Scope

  • Wraps an existing iOS notification delegate before native OneSignal initialization.
  • Restores OneSignal's native IMPs after selector injection.
  • Keeps OneSignal foreground lifecycle/display handling while forwarding local notifications to the captured delegate.
  • Detects and logs delegates replaced after initialization.
  • Updates the demo's iOS FinishedLaunching ordering so the plugin delegate exists before OneSignal initializes.

This is an experimental compatibility path. It relies on private native OneSignal class/selector names and does not protect delegates assigned after OneSignal.Initialize. The generated binding helper/model types support the internal implementation and are not intended as consumer APIs.

Stacked on #188.

Testing

Manual testing

  • iOS Debug simulator: verified Plugin.LocalNotification foreground display (response 27).
  • iOS Debug simulator: verified OneSignal foreground lifecycle and banner display (response 7).
  • iOS Release/AOT simulator: repeated local and OneSignal foreground paths without ObjCRuntime.RuntimeException.
  • Confirmed removing the FinishedLaunching ordering reproduces the native crash; restoring it resolves the crash.
  • Android Debug build passed.
  • csharpier check . passed.

Notification-open behavior was manually smoke-tested, but Clicked callback logging should receive additional Release/AOT validation before treating this prototype as production-ready.

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

@fadi-george
fadi-george requested a review from a team as a code owner July 16, 2026 00:22
@fadi-george
fadi-george force-pushed the fadi/sdk-4788-fix-1 branch from 377b756 to 6d478df Compare July 16, 2026 00:24
@fadi-george

Copy link
Copy Markdown
Contributor Author

@claude review

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

No bugs found by the automated review, but this PR touches Objective-C runtime internals (manual IMP swizzling via method_getImplementation/method_setImplementation) and relies on private native OneSignal class/selector names, so I'd like a human to weigh in before merging.

What was reviewed:

  • The delegate proxy's selector forwarding for both Apple and OneSignal-prefixed UNUserNotificationCenterDelegate selectors.
  • The RepairOneSignalDispatch IMP-swizzling flow and its failure path (throws in Initialize() if repair fails).
  • The MAUI demo's FinishedLaunching reordering to capture the third-party delegate before OneSignal initializes.
  • Post-init delegate-replacement detection via DidBecomeActiveNotification.
Extended reasoning...

Overview

This PR adds an experimental iOS delegate proxy (iOSNotificationCenterDelegateProxy) that sits between OneSignal's native swizzled UNUserNotificationCenterDelegate and a third-party delegate (e.g. Plugin.LocalNotification), to avoid a .NET registrar crash when both installed delegates only implement the standard Apple selectors. It adds new binding definitions (OneSignalCoreHelper, OneSignalNotificationCenterDelegateProxy) exposing private OneSignal native selectors, wires the proxy into iOSOneSignal.Initialize, and updates the local-notification demo app and its README to reflect the new initialization ordering required on iOS.

Security risks

No injection/auth/credential exposure risk. The main risk is native-interop correctness: the code does raw Objective-C runtime IMP swapping via P/Invoke against class_getInstanceMethod/method_getImplementation/method_setImplementation. If OneSignal's native SDK renames/removes the targeted private class or selectors in a future release, this either silently no-ops (guarded, returns false) or triggers the explicit InvalidOperationException thrown in Initialize(), which would break app startup for any consumer relying on this path.

Level of scrutiny

This warrants above-average scrutiny. It is public-API-surfacing (new binding types), affects core notification dispatch on iOS, and depends on private/undocumented native selector and class names that are inherently fragile across OneSignal iOS SDK versions. The author explicitly frames this as an experimental prototype not yet production-ready, and calls out unresolved gaps (delegates replaced after Initialize are unprotected, Release/AOT validation for Clicked callbacks is incomplete). This is exactly the kind of judgment call — accepting a fragile-but-necessary interop workaround, and deciding whether throwing on repair failure is the right failure mode — that should have a human sign-off rather than an automated approval.

Other factors

Testing was manual (simulator only, per the PR description) with no new automated test coverage, which is reasonable for this kind of native-interop prototype but means there's no regression safety net. The PR is stacked on #188. No outstanding reviewer threads exist yet in the timeline besides the initial review request.

@abdulraqeeb33 abdulraqeeb33 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Thanks for the prototype — the overall approach makes sense for the Plugin.LocalNotification crash, and the README/demo ordering is clear about the preconditions.

I went through the proxy, init wiring, bindings, and the sample. Nothing that should block landing this as an experimental path, but a few things I'd want tightened (or at least commented) before we treat it as more than that:

Sample (MauiProgram)

  • The #if !IOS arm looks scary in isolation; iOS is actually covered via FinishedLaunching. Worth a one-line comment, and resolving appId from DotEnv before registering that handler.

Proxy

  • The no-op completionHandler for OneSignal payloads needs a why-comment (or a pass-through if it isn't guaranteed safe after the IMP repair).
  • Hard-coding OneSignalNotificationsUNUserNotificationCenter is the fragile bit we already call out — just confirming we're okay owning that across native SDK bumps.

Initialize

  • Throwing after native init has already succeeded leaves a half-up SDK. Loud failure is fine for now; softer failure mode later.
  • Delegate-replaced warning is Debug-only — fine for prototype, not for customers.

Bindings

  • OneSignalNotificationCenterDelegateProxy / OneSignalCoreHelper show up as public binding types. If we can keep them less discoverable, great; if not, explicit "not a consumer API" note somewhere helps.

Manual simulator testing looks solid for a prototype. Would still want another pass on Release/AOT Clicked before calling this production-ready, as you already noted.

Comment thread examples/plugin-local-notif/MauiProgram.cs

@abdulraqeeb33 abdulraqeeb33 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Inline follow-ups on the proxy completion path, init failure mode, Debug-only delegate check, and binding surface.

Comment thread OneSignalSDK.DotNet.iOS/iOSNotificationCenterDelegateProxy.cs
Comment thread OneSignalSDK.DotNet.iOS/iOSOneSignal.cs
Comment thread OneSignalSDK.DotNet.iOS/iOSOneSignal.cs
Comment thread OneSignalSDK.DotNet.iOS.Binding/ApiDefinitions.cs
Base automatically changed from fadi/sdk-4788 to main July 16, 2026 17:25
@fadi-george
fadi-george force-pushed the fadi/sdk-4788-fix-1 branch from 6d478df to b5ac0a2 Compare July 16, 2026 17:27

@abdulraqeeb33 abdulraqeeb33 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

thank you for addressing the comments

@nan-li nan-li left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Thanks for the prototype and the clear write-up: I'm not sure about the PR as the right fix since it is a dotnet workaround around native swizzling and it fixes a narrow slice. Im also unsure about its categorization as a prototype and experimental (?) and it's not clear what comes next. Maybe we should look at something more robust. The solution is probably going to come from the native iOS SDK?

@fadi-george
fadi-george deleted the fadi/sdk-4788-fix-1 branch July 22, 2026 00:07
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.

3 participants