From 1e0e9ee7464a553d891598bf4376d444b05bed3c Mon Sep 17 00:00:00 2001 From: Evie Gauthier Date: Sat, 5 Sep 2026 12:48:27 -0400 Subject: [PATCH] fix: keep APNs token events opt-in --- README.md | 15 +++++++++++++++ ios/Sources/AppDelegateSwizzler.swift | 4 +++- ios/Sources/NotificationPlugin.swift | 10 +++++++--- ios/Tests/PluginTests/PluginTests.swift | 10 ++++++++++ src/lib.rs | 6 ++++++ 5 files changed, 41 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index e00317f..caed0fa 100644 --- a/README.md +++ b/README.md @@ -463,6 +463,21 @@ Registers the app for push notifications. On Android this retrieves the FCM devi - Android: FCM device token - Linux: UnifiedPush endpoint URL (the URL your backend POSTs payloads to) +On iOS the APNs token is returned to the caller but is not also emitted through +the generic Tauri event channel by default. Apps that intentionally handle the +raw token in JavaScript can opt into the legacy `push-token` event in +`tauri.conf.json`: + +```json +{ + "plugins": { + "notifications": { + "exposePushTokenEvents": true + } + } +} +``` + ### `listDistributors()` **(Linux / UnifiedPush only)** Lists every running UnifiedPush distributor by its D-Bus bus name (e.g. `org.unifiedpush.Distributor.ntfy`). Returns an empty array when none is installed — that's the signal to ask the user to install one from . diff --git a/ios/Sources/AppDelegateSwizzler.swift b/ios/Sources/AppDelegateSwizzler.swift index 9a2d59f..d0087c1 100644 --- a/ios/Sources/AppDelegateSwizzler.swift +++ b/ios/Sources/AppDelegateSwizzler.swift @@ -77,7 +77,9 @@ final class PushForwarder: NSObject, UIApplicationDelegate { didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { let hex = deviceToken.map { String(format: "%02x", $0) }.joined() AppDelegateSwizzler.plugin?.handlePushTokenReceived(hex) - try? AppDelegateSwizzler.plugin?.trigger("push-token", data: ["token": hex]) + if AppDelegateSwizzler.plugin?.exposePushTokenEvents == true { + try? AppDelegateSwizzler.plugin?.trigger("push-token", data: ["token": hex]) + } AppDelegateSwizzler.callOriginalDidRegister( self, #selector(UIApplicationDelegate.application(_:didRegisterForRemoteNotificationsWithDeviceToken:)), diff --git a/ios/Sources/NotificationPlugin.swift b/ios/Sources/NotificationPlugin.swift index 4f494a3..0792613 100644 --- a/ios/Sources/NotificationPlugin.swift +++ b/ios/Sources/NotificationPlugin.swift @@ -163,11 +163,13 @@ struct SetPushMessageListenerActiveArgs: Decodable { struct PluginConfig: Decodable { let actionTypes: [ActionType]? + let exposePushTokenEvents: Bool? } class NotificationPlugin: Plugin { let notificationHandler = NotificationHandler() let notificationManager = NotificationManager() + var exposePushTokenEvents = false #if ENABLE_PUSH_NOTIFICATIONS // Completion handler for push token registration @@ -185,9 +187,11 @@ class NotificationPlugin: Plugin { public override func load(webview: WKWebView) { super.load(webview: webview) - if let config = try? parseConfig(PluginConfig.self), - let actionTypes = config.actionTypes { - makeCategories(actionTypes) + if let config = try? parseConfig(PluginConfig.self) { + if let actionTypes = config.actionTypes { + makeCategories(actionTypes) + } + exposePushTokenEvents = config.exposePushTokenEvents ?? false } #if ENABLE_PUSH_NOTIFICATIONS diff --git a/ios/Tests/PluginTests/PluginTests.swift b/ios/Tests/PluginTests/PluginTests.swift index 7db31cc..9c3d108 100644 --- a/ios/Tests/PluginTests/PluginTests.swift +++ b/ios/Tests/PluginTests/PluginTests.swift @@ -13,6 +13,16 @@ final class NotificationTests: XCTestCase { XCTAssertEqual(config.actionTypes?.first?.id, "sable-message") XCTAssertEqual(config.actionTypes?.first?.actions.first?.id, "sable-reply") XCTAssertEqual(config.actionTypes?.first?.actions.first?.input, true) + XCTAssertNil(config.exposePushTokenEvents) + } + + func testPluginConfigCanExplicitlyExposePushTokenEvents() throws { + let config = try JSONDecoder().decode( + PluginConfig.self, + from: Data(#"{"exposePushTokenEvents":true}"#.utf8) + ) + + XCTAssertEqual(config.exposePushTokenEvents, true) } func testSetPushMessageListenerActiveArgsDecodes() throws { diff --git a/src/lib.rs b/src/lib.rs index 80948df..8f87bde 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,6 +21,12 @@ pub struct PluginConfig { /// `aps.category` is received before JavaScript initializes. #[serde(default)] pub action_types: Vec, + /// Emit the raw APNs registration token through the generic Tauri event + /// channel. Disabled by default because Rust callers already receive the + /// token from `register_for_push_notifications` and may keep it outside + /// the renderer trust boundary. + #[serde(default)] + pub expose_push_token_events: bool, #[cfg(target_os = "windows")] pub windows: WindowsConfig, }