Skip to content

Notifications: restore the sound on the extension's fallback notifications - #785

Open
mpretty-cyro wants to merge 1 commit into
session-foundation:devfrom
mpretty-cyro:fix/notification-extension-fallback-sound
Open

mpretty-cyro wants to merge 1 commit into
session-foundation:devfrom
mpretty-cyro:fix/notification-extension-fallback-sound

Conversation

@mpretty-cyro

Copy link
Copy Markdown
Collaborator

Reported twice this week (#28249, #28223): no sound on incoming message notifications, on iOS, with everything configured correctly — system notifications on with sounds enabled, Fast Mode on, a sound selected, "Sound when App is open" on.

The bug

NotificationServiceExtension.swift never sets a notification sound. The string sound does not appear in the file at all.

The extension has one path that builds content through the shared NotificationContent.toMutableContent(shouldPlaySound:) — that one attaches a sound correctly and is unaffected. It also has two paths that hand-roll a UNMutableNotificationContent, setting title, body, userInfo and badge but never sound:

Path What the user sees Sound
handleFailure "Session" / "You've got a new message" none set
handleFailureForVoIP "Session" / "<name> is calling…" none set

There is nothing to inherit from the payload either: it carries only spns and enc_payload (PushNotificationAPI.swift:146,150), and the sound preference lives in libSession locally and is never transmitted, so the server could not set the right one even in principle.

handleFailure is the default: arm of handleError, so any unrecognised error lands there. A user whose messages consistently fail to process in the extension gets a visible but silent notification for every single message, with nothing in the app's settings to explain it. The incoming call case is the worse of the two.

handleFailure already fetched notificationSettings for previewType and simply never read .sound.

This was fixed once already

  • ecf92ceeea (2021-10-19) — "fix PN sound settings not applying to remote PNs" — added notificationContent.sound = OWSSounds.notificationSound(for: thread).notificationSound(isQuiet: false).
  • a68ed28a7a (2022-03-09) — "Merge branch 'dev' into voice-calls-2" — dropped it. Not a decision: the other branch had rewritten the same delivery block and resolving in its favour removed the one line it had never had.

Gone ever since — zero occurrences in 2.0.0 through 2.15.4. The 2025 refactor restored a different mechanism (the shared presenter) covering the success path, and rebuilt the fallback paths from the post-merge shape.

That history is also why this is a named applyNotificationSound(to:notificationSettings:) rather than an assignment repeated at each call site. A bare content.sound = ... in the middle of a block someone else is rewriting is precisely what went missing last time; a missing call to a named function is conspicuous in a way a missing assignment is not.

Second, smaller fix

Preferences.Sound.notificationSound(isQuiet:) guards on filename() being nil to fall back to UNNotificationSound.default — but .default returns "", not nil. The guard never fired, the fallback was unreachable, and it built UNNotificationSound(named: ""), which resolves to no file and plays nothing.

Not reachable through the UI today (.default isn't offered in the sound picker, and libSession decodes a stored 0 to .note), so it's a latent trap rather than a live bug — but the fallback paths now depend on this function resolving correctly, so it's fixed here. Happy to split it out if you'd rather.

Testing

The extension has no test target, so the regression tests cover the sound resolution and NotificationContent in SessionMessagingKitTests instead. Added to NotificationsManagerSpec rather than a new file to avoid touching the project file.

  • Verified the .default test fails against the unfixed code and passes with it.
  • SessionMessagingKitTests passes in full.
  • ** BUILD SUCCEEDED ** for the Session scheme.

The handleFailure / handleFailureForVoIP changes themselves are not covered by an automated test — worth saying plainly. Manual verification needs a device on a production signing cert (third-party contributors can't exercise push at all, per BUILDING.md).

Still open on the tickets

This is a confirmed defect and worth fixing regardless, but it is not yet confirmed to be what the two reporters are hitting. The question that settles it is whether their notifications read "You've got a new message" or show the sender's name and message text — if the latter, the success path ran, a sound was attached, and something else is going on. I'll update once they reply.

Draft until then.

…tions

The notification service extension never set a sound on either of the paths that
build their own content, so every notification they produce is silent - the generic
"You've got a new message" shown whenever processing fails, and the incoming call
notification from handleFailureForVoIP. handleFailure already had the settings in
hand for previewType and simply never read sound. The success path is unaffected;
it builds content through NotificationContent.toMutableContent(shouldPlaySound:),
which does attach one.

handleFailure is the default: arm of handleError, so anything unrecognised lands
there. A user whose messages consistently fail to process in the extension gets a
banner with no sound for every message, with nothing in the app's settings to
explain it.

This was fixed once before, in ecf92ce ("fix PN sound settings not applying to
remote PNs"), and dropped while resolving the merge in a68ed28 where the other
branch had rewritten the same block. That is why it is a named function rather than
an assignment repeated at each call site - a bare `content.sound = ...` in the
middle of a rewritten block is exactly what went missing last time.

Also fixes Sound.default resolving to UNNotificationSound(named: ""), which plays
nothing: filename() returns an empty string rather than nil for that case, so the
existing nil guard never caught it and the UNNotificationSound.default fallback was
unreachable. Not currently reachable through the UI - default isn't offered in the
sound picker and libSession decodes a stored 0 to note - but the fallback paths now
depend on this function resolving correctly.

The extension has no test target, so the regression tests cover the sound resolution
and NotificationContent instead.
@mpretty-cyro
mpretty-cyro force-pushed the fix/notification-extension-fallback-sound branch from a10289a to 96bbc27 Compare September 8, 2026 01:44
@mpretty-cyro
mpretty-cyro marked this pull request as ready for review September 8, 2026 01:45
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.

1 participant