mirror of
https://github.com/block/buzz.git
synced 2026-10-27 12:40:30 +00:00
A notification can arrive from someone whose profile the main app has never loaded. The iOS notification extension now fetches and caches sender profiles, channel details, and avatars itself. The main app supplies account configuration and Keychain credentials only. After resolving the message, the extension allows 1.5 seconds for sender and channel details. It uses available stale data when a refresh is slow or fails, delivers once, and lets late results update its private cache. Profiles, channel details, and avatar refresh checks share a one-hour freshness threshold. A verified avatar removal clears the previous image. Account removal prevents further use of that sign-in's cached data. Profile, channel metadata, and membership use separate bounded downloads, so an oversized roster cannot discard the smaller records. Sender enrichment can finish while channel requests are pending. A successful partial channel refresh atomically removes its old counterpart, preventing a mixed group identity or recipient count. Group-specific notification presentation still requires a verified recipient count. The [specification](https://github.com/block/buzz/blob/native-notification-enrichment/docs/native-notification-enrichment.md) describes the display, caching, and account-removal behavior. General persistent image caching in Flutter remains separate. ### Validation [Hosted CI](https://github.com/block/buzz/actions/runs/38780108487) passed on the final commit, including all Flutter shards, Android, the full iOS release and simulator builds, and native controls/push-navigation tests. The [current-commit security review](https://github.com/block/buzz/pull/8369#issuecomment-6197508385) found no issues. The native package passed 159 tests, including new coverage for cache freshness, avatar replacement and removal, sign-in isolation, concurrent storage, late results, single delivery, bounded downloads, and SVG validation. A regression test exposed identical HTTP authentication event IDs for requests in the same second; each request now includes a unique nonce, matching the Dart and CLI clients. Full local CI passed before that small authentication fix. The complete native suite and signed device build passed again on the latest commit, including response-size limits, overlapping avatar downloads, canonical relay-origin matching, independent profile/channel refreshes, account-key changes before and during requests, and a single initial snapshot read across 64 accounts, signed IPv6 avatar authorities, and partial channel refreshes. Focused Flutter regression tests passed 198 tests with three configuration-specific skips; seven avatar widget tests also passed. Flutter analysis is clean, and the notification extension builds for the simulator. On an iPhone SE, the notification displayed the sender name, avatar, channel, and message before the updated main app was opened. Repeat notifications used a byte-identical private cache. Three notifications delivered together displayed the avatar after the authentication fix. The next notification also displayed correctly after terminating the extension process. The existing generic-push design can select the same newest unread message for several pushes; this change does not alter message selection.  Authenticated HTTP avatar fetching and persistent HTTP cache reuse also passed on the SE. The first notification downloaded a relay thumbnail. After terminating the extension and expiring its separate avatar entry, the next notification displayed the avatar and refreshed that entry without another media request in the relay logs. The main app remained unopened throughout. The original app build and test profile were restored afterward. Automated tests cover slow and failed requests, stale images, removal, and account revocation; those cases have not all been exercised on the phone. ### Before merge - [x] Verify authenticated HTTP avatar fetching and cache reuse on iOS against a working relay media service. <!-- buzz-review-completed --> --------- Signed-off-by: Tom Brow <tomb@block.xyz>
76 lines
4.2 KiB
Markdown
76 lines
4.2 KiB
Markdown
# Sender information in iOS notifications
|
|
|
|
Buzz must show a sender's name and avatar even when the main app has never
|
|
loaded that sender's profile. The notification service extension fetches and
|
|
caches the information it needs. The main app supplies signed-in accounts,
|
|
relay configuration and credentials in the shared Keychain. It does not export
|
|
profiles, channel details or avatar images for notifications.
|
|
|
|
## Display behavior
|
|
|
|
The extension uses one freshness threshold of one hour for profiles, channel
|
|
details, and avatar refresh checks. The period starts at the last successful
|
|
check. Reading a record does not extend it. When a record is older, the
|
|
extension tries to refresh it and can use the previous record if the refresh
|
|
fails. This setting does not extend authorization. Image requests also follow
|
|
HTTP caching rules, which can avoid downloading unchanged image data again.
|
|
|
|
After it resolves the message, the extension allows up to 1.5 seconds for the
|
|
sender and channel details. It then delivers the best available notification.
|
|
If no sender name or avatar is available, it omits those values where iOS
|
|
supports this. It must not use a public key as the displayed name.
|
|
|
|
When a sender changes their avatar, the previous image remains usable until
|
|
the replacement arrives. When a verified profile removes the avatar, the old
|
|
image is no longer shown. A late response must not restore a removed avatar.
|
|
|
|
The extension supports the same avatar sources as the app: Buzz uploads,
|
|
external HTTP(S) images and supported inline images. It uses existing server
|
|
thumbnails for eligible Buzz uploads. A separate resized-image cache is not
|
|
required. Animated avatars use the sender-selected still image. Inline SVGs
|
|
are rendered natively. Image decoding has size limits; SVG decoding also limits
|
|
nesting and repeated references. Embedded fonts, external SVG resources and
|
|
recursive references are rejected. An image that cannot be decoded follows the
|
|
same stale-image or missing-image behavior as a failed download.
|
|
An empty profile query is not a verified removal and does not clear stale data.
|
|
|
|
## Fetching and storage
|
|
|
|
The extension uses native networking and its own persistent storage. Image
|
|
responses use URLSession and URLCache; verified profiles and channel details
|
|
have a separate bounded store. Each account and relay has separate cache data.
|
|
Credentials are not stored in these caches. Profile and channel requests run
|
|
independently, so a large or slow channel roster cannot block a sender profile
|
|
or avatar that is available before the delivery deadline.
|
|
When only channel metadata or membership refreshes successfully, the extension
|
|
keeps that record and removes its old counterpart in the same write. It does
|
|
not derive a group identity or recipient count from a mixed pair. If neither
|
|
request succeeds, the previous pair remains available.
|
|
|
|
Delivery happens only once. Ordinary requests can continue after delivery if
|
|
iOS keeps the process alive. A late profile response can start an avatar fetch,
|
|
and completed results can be saved for the next notification. These requests
|
|
use normal network timeouts. There are no background sessions, and successful
|
|
notification delivery does not depend on work continuing after delivery.
|
|
|
|
A cache miss is normal. Interrupted writes must leave complete old or new
|
|
records, not partial records. Invalid cache entries are reported and treated
|
|
as misses. The extension must work after iOS removes cache files or starts a
|
|
new process.
|
|
|
|
Removing an account immediately prevents access to its cached information.
|
|
The extension deletes the account's cache files when it next runs. Results
|
|
from requests started before removal cannot become usable in a new sign-in.
|
|
|
|
## Validation
|
|
|
|
Tests cover a sender never loaded by the app, cache hits and misses, stale
|
|
records, avatar replacement and removal, account removal, late responses and
|
|
exactly-once delivery. Storage tests cover concurrent access and interruption.
|
|
Device validation checks actual notification appearance, authenticated image
|
|
cache reuse, process restarts and notification bursts. In particular, accepting
|
|
nil name and image values in an API does not by itself prove how iOS renders
|
|
those values.
|
|
|
|
Persistent image caching for the main app is separate from this change.
|