Skip to content

iOS push alerts are silent — add sound to the NIP-PL APNs constant #7186

Description

@crgallego

Motivation

The mobile app can wake on a NIP-PL push and show a banner, but the alert is silent. Operators using Buzz as a human–agent workspace need an audible cue when the phone is locked or the app is backgrounded — same expectation as Slack/iMessage, not a mute reconnect banner.

This is a product request from a multi-workspace operator (Litbox nest). It is not “please add push.” iOS push registration, lease publish, gateway delivery, and the Notification Service Extension already landed in #6269 (feat(mobile): push notifications MVP, merged 2026-08-28). The remaining hole is sound.

Why it is silent today

NIP-PL’s public APNs profile is class-blind: every accepted delivery sends one compiled-in body. That body has no sound key.

Verified at block/buzz bd734904:

// crates/buzz-push-gateway/src/model.rs
pub const APNS_RECONNECT_PAYLOAD: &[u8] =
    br#"{"aps":{"alert":{"body":"Reconnect to your relay now"},"mutable-content":1}}"#;

The same exact UTF-8 constant is pinned in:

  • docs/nips/NIP-PL.md:227
  • docs/formal/nip-pl/delivery.py (FIXED_BODY)
  • docs/formal/nip-pl/fixed_payload.py (C)

Under APNs, an alert without sound displays a banner without playing audio. Client permission is already requested (.alert, .badge, .sound in mobile/ios/Runner/AppDelegate.swift). The NSE (mobile/ios/NotificationService/NotificationService.swift) copies the incoming UNMutableNotificationContent and never sets .sound. So neither the gateway payload nor the extension adds a sound — permission alone does not play one.

The gateway cannot vary the body per event. NIP-PL § Wake Delivery requires application_body(a) = C_transport for every send, and DeliveryRequest carries only v, endpoint_grant, request_id, expires_at. silent is a matching preference only; the public APNs profile does not expose relay-selected notification classes. Sound therefore has two legal levers: put it in the constant, or set it on-device in the NSE. The constant is the one that still works when the extension times out or fails.

Proposed solution

v1: system default sound on every public-profile APNs wake. No per-slot custom sounds.

Amend the four pins together to:

{"aps":{"alert":{"body":"New activity in Buzz"},"sound":"default","mutable-content":1}}

Change the body text in the same edit. Without the NSE, this string is what every user sees on every push, and "Reconnect to your relay now" is internal plumbing language. "New activity in Buzz" (or maintainer-chosen copy) is a user-facing placeholder the NSE can still replace when it has relay data.

crates/buzz-push-gateway/src/apns.rs already asserts every sent body equals APNS_RECONNECT_PAYLOAD, so it follows the constant. No Desktop change. No Android work in this issue.

Acceptance:

  • A lock-screen / background APNs wake on a physical iOS device plays the system default sound when notification permission includes sound.
  • The four pins stay byte-identical to each other.
  • Formal noninterference still holds: application_body() takes no arguments.
  • NSE replacement path still runs; it may keep or set UNNotificationSound.default but must not depend on that for v1 audibility.

Alternatives considered

Approach Why not for v1
NSE sets content.sound and leave the constant mute Silent whenever the extension is not invoked, times out, or fails closed — the exact fallback path users hit.
Per-slot / per-channel custom sounds (Desktop local sounds) Desktop’s per-slot sounds are for an online app. Background APNs cannot reuse that path; the public profile is class-blind by spec.
Silent data-only wake + local notification Fails when the app is killed and iOS does not schedule the local notification.
Re-file “mobile never registers for push” Already shipped in #6269. #4657 is the registration bug, now stale for iOS.

Non-goals

  • Android / FCM — already #6092 (client) and #3229 (gateway profile).
  • Per-channel mute/level UI — #3234 / #3160.
  • Self-hosted relays reaching push.buzz.xyz#5206.
  • Custom sound files or notification categories.

Additional context

#6269 validated lock-screen presentation and NSE replacement on a physical iPhone 12 mini; that write-up does not claim an audible alert. Apple provisioning (.p8, App Attest app id, production vs sandbox) remains an operator/release step and is not this issue.

Duplicate check

Searched 2026-09-01 for push notification, alert sound, APNs, APNS_RECONNECT_PAYLOAD. Closest, none of which ask for sound on the fixed APNs body:

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions