Skip to content

[iOS] NSE: read app-written localized fallback JSON when push composition yields no content #92

Description

@pvg13

Summary

When wavesync_nse_handle_push produces no notification content (sync timeout, cold
group-key cache, no matching SyncNotify policy), WaveSyncNotificationService
delivers the operator's APNs placeholder text untouched. Apps can already write a
localized fallback file into the App Group container; the NSE template should read it
as a second-tier fallback — above the raw placeholder, below real composed content.

Follow-up to the NSE work on feat/alert-push; related: #78.

Current behavior (src/ios/Sources/WaveSyncPush/WaveSyncNotificationService.swift)

applyResult(_:to:) (line ~130) only overwrites best.title/best.body when the FFI
returned non-empty JSON fields:

private func applyResult(_ json: String?, to best: UNMutableNotificationContent) {
    guard let json = json, ... else {
        deliver(best)          // ← placeholder delivered untouched
        return
    }
    if let title = obj["title"] as? String, !title.isEmpty { best.title = title }
    if let body  = obj["body"]  as? String, !body.isEmpty  { best.body  = body }
    deliver(best)
}

The early-exit guards in didReceive (missing config dir, unencodable payload) have
the same effect: the user sees the relay's generic placeholder.

Proposed behavior

Downstream apps (Mediterranea does this today, every launch) write
<app group container>/.wavesync_notification_fallback.json:

{
  "es": { "title": "Mediterranea", "body": "Hay novedades en tu hogar" },
  "en": { "title": "Mediterranea", "body": "There's news in your household" }
}

In applyResult's no-content path (and the didReceive early-exit guards):

  1. Resolve the container the same way resolveConfigDir(groupId:) already does.
  2. If .wavesync_notification_fallback.json exists, parse it, pick the entry matching
    the device/app locale (fall back to the first entry).
  3. Apply its title/body before deliver(best).
  4. Any read/parse failure → deliver the placeholder as today (never fail the push).

Precedence: FFI-composed content > app fallback file > APNs placeholder.

Why

The placeholder is operator-branded and non-localized, so every degraded push (cold
cache is guaranteed for the first push after reinstall; short budgets on constrained
networks are common) surfaces text the app has no control over. The fallback file
gives apps a localized, branded degraded mode with zero protocol changes.

Notes

  • File format above is what Mediterranea already writes
    (write_notification_fallback, one JSON object per locale key).
  • Reading is NSE-side only; nothing writes from the extension (consistent with the
    load-only group-key cache design).

Metadata

Metadata

Assignees

No one assigned

    Labels

    feature-requestNew capability, not a bug fixpriority:highSignificant bug, common path affectedtopic:portabilityMulti-backend, IPv6, OS-specific

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions