tauri-apps / tauri-apps/plugins-workspace
[notification] iOS: toActiveNotification force-unwraps notificationsMap, crashing on any action tap after the app was terminated
- Dominant language
- Rust
- Stars
- 1.8k
- Forks
- 602
- Avg merge
- 4d 14h
- Merged PRs (30d)
- 9
Description
### Describe the bug
`toActiveNotification` force-unwraps an in-memory dictionary that is empty in the exact case a scheduled notification is designed for:
https://github.com/tauri-apps/plugins-workspace/blob/v2/plugins/notification/ios/Sources/NotificationHandler.swift
```swift
func toActiveNotification(_ request: UNNotificationRequest) -> ActiveNotification {
let notificationRequest = notificationsMap[request.identifier]! // 💥
```
`notificationsMap` is populated only by `saveNotification`, at schedule time, **in the current process**. A notification scheduled hours earlier and delivered after iOS has terminated the app finds it empty, and the force-unwrap traps.
That is the normal case for any scheduled reminder — the whole point of `Schedule.at` is that it survives the app not running — and the only case for an action tap forwarded from a paired Apple Watch.
Both callers reach it:
- `didReceive(response:)` — user taps the notification or one of its action buttons
- `willPresent(notification:)` — notification arrives while the app is foreground
### Reproduction
1. `sendNotification` with `schedule: Schedule.at(...)` a few minutes out
2. Force-quit the app (or let iOS terminate it)
3. When the notification fires, tap it
The app relaunches and immediately crashes instead of delivering the action.
### Suggested fix
Nothing the caller needs actually requires the map. `id`, `title`, `body` and `actionTypeId` all come from the `UNNotificationRequest`, which is always present; only `sound` and `attachments` came from the map, and both degrade cleanly:
```swift
let stored = notificationsMap[request.identifier]
return ActiveNotification(
id: Int(request.identifier) ?? -1,
title: request.content.title,
body: request.content.body,
sound: stored?.sound ?? "",
actionTypeId: request.content.categoryIdentifier,
attachments: stored?.attachments
)
```
Behaviour is unchanged whenever the entry exists. The same file already uses the safe `if let` form a few lines earlier in `willPresent`, so this reads as an oversight rather than a deliberate invariant.
### Full `tauri info` output
Plugin version 2.3.3; also present on `v2` HEAD as of today. Reproduced on an iOS 26 simulator with `tauri-cli` 2.9.5.
### Stack trace
Crash on relaunch in `toActiveNotification`, via `didReceive(response:)`.
### Additional context
I have been running this as a vendored patch for a few weeks and it has been stable. Happy to open a PR if useful.
Contributor guide
Research direction
Start in plugins/notification/ios/Sources/NotificationHandler.swift at toActiveNotification, then inspect its callers didReceive(response:) and willPresent(notification:). Verify the notification request supplies the core fields when notificationsMap is empty, and run the notification plugin’s iOS tests if available. Done means scheduled notifications opened after app termination no longer crash and stored sound or attachments remain available when present.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- ios, swift
- Domain
- mobile
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 88/100