Skip to content

fix: [SDK-5461] cold-start tap capture on scene apps and launch URL replay - #71

Merged
fadi-george merged 1 commit into
mainfrom
fadi/SDK-5461-cold-start-tap-launch-url-scene-based
Oct 13, 2026
Merged

fadi-george merged 1 commit into
mainfrom
fadi/SDK-5461-cold-start-tap-launch-url-scene-based

Conversation

@fadi-george

Copy link
Copy Markdown
Contributor

Description

One Line Summary

Fix two iOS cold-start notification tap problems: taps are not captured on Capacitor 8 scene-based apps, and the launch URL never opens.

Details

Motivation

Fixes #69. Both problems were reproduced on a simulator (repro branch: fadi/sdk-5498-repro, see examples/demo/REPRO.md there).

  1. Nothing captured on scene apps. OSCapacitorLaunchOptions wrapped didReceiveNotificationResponse on whichever UN delegate existed at didFinishLaunching, which is the OneSignal SDK's own delegate. In the Capacitor 8 scene template, CAPBridgeViewController is created later in SceneDelegate, and Capacitor's NotificationRouter replaces that delegate, so the tap went to an unwrapped delegate and was never replayed. This happens whenever handleApplicationNotifications is left at its default.
  2. Launch URL never opened. initialize replayed captured responses on Capacitor's bridge GCD queue. The iOS SDK opens launch URLs with performSelector:afterDelay:, which never fires on a thread without a run loop. The click fired, but the URL did not open.

Scope

  • Capture now hooks +[OSNotificationsManager processNotificationResponse:], which every tap reaches regardless of the current delegate (the SDK re-swizzles each new delegate, and apps with swizzling disabled forward to it manually). The hook stores the response only until initialize drains the queue, and always calls through to the original.
  • Replay runs on the main queue.
  • pendingColdStartResponses and consumeColdStartResponses are replaced by a single @synchronized drainColdStartResponses, since capture runs on main and drain runs on the bridge queue.
  • iOS only. No JS or Android changes. Not changed: foreground display with NotificationRouter as the delegate (the setup guide should recommend ios.handleApplicationNotifications: false).
  • The root limitation in the native SDK is tracked in SDK-5498.

Testing

Unit testing

No native test target exists in this repo. JS tests pass (vp test, 267 tests).

Manual testing

iOS 26.2 simulator, Capacitor 8.5.3, OneSignal iOS SDK 5.8.0, SPM demo. Kill the app, simctl push a OneSignal payload with launch URL https://onesignal.com, tap the banner.

Config Before After
Scene template, default config no click, no URL click fires, Safari opens
Scene template, handleApplicationNotifications: false click, no URL click fires, Safari opens
No scenes, handleApplicationNotifications: false click, no URL click fires, Safari opens
No scenes, default config click, no URL click fires, Safari opens
Background tap after a cold start works works, handled once, not re-captured

Each tap produced exactly one handleNotificationOpened. Not tested: CocoaPods demo (demo_pods) and a physical device.

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

🤖 Generated with Claude Code

…eplay

Hook +[OSNotificationsManager processNotificationResponse:] instead of the
UN delegate present at launch, so taps are captured even when Capacitor's
NotificationRouter replaces the delegate later (scene-based template).
Replay captured responses on the main queue so the SDK's delayed launch URL
open actually fires, and make capture/drain thread-safe.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@fadi-george
fadi-george requested a review from a team October 12, 2026 21:51
@fadi-george
fadi-george added this pull request to stack #73 October 12, 2026 22:20
@nan-li

nan-li commented Oct 12, 2026

Copy link
Copy Markdown
Contributor

Not tested: CocoaPods demo (demo_pods)

The changes here make sense. Are you able to test the scenarios in the table you laid out using the pods demo? I think the linking with cocoapods may be a little different, although I don't expect it to be a problem. Still worth a manual test check.

@fadi-george

fadi-george commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor Author

Not tested: CocoaPods demo (demo_pods)

The changes here make sense. Are you able to test the scenarios in the table you laid out using the pods demo? I think the linking with cocoapods may be a little different, although I don't expect it to be a problem. Still worth a manual test check.

Tested same scenarios for cocoa-pods and they all pass

@fadi-george
fadi-george merged commit 86a071f into main Oct 13, 2026
6 checks passed
@fadi-george
fadi-george deleted the fadi/SDK-5461-cold-start-tap-launch-url-scene-based branch October 13, 2026 17:09
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.

Cold-start tap: launch URL never opened, and no capture on scene-based apps (iOS, Capacitor 8)

2 participants