mirror of
https://github.com/block/buzz.git
synced 2026-11-06 09:21:45 +00:00
On iOS, custom emoji in the native emoji picker often render as the sparkle fallback icon instead of the image. The native picker downloads custom emoji with `URLSession` and asks Flutter for request headers over the `mediaHeaders` channel call. That call returned only the media read `Authorization` header. Hosted relays also require the `Nostr-Federated-Identity` assertion, which the Flutter image path gets from `HostedRelayHttpClient`, so relay-hosted emoji get a 401 and the picker falls back to sparkles. Emoji hosted elsewhere still load, which is why only some are missing. The `mediaHeaders` bridge now also includes the hosted relay assertion via a new `HostedRelayAuth.headersForUrl`, scoped to the relay's own origin like `HostedRelayHttpClient`. The native loader refuses redirects for requests that carry the assertion, matching the Dart client, so the token can't be forwarded to another host. The iOS reactions sheet had the same gap: it passes avatar and custom emoji URLs to native views with only the `Authorization` header. A new `nativeMediaHeadersProvider` combines both headers for any native media consumer, and the reactions sheet uses it. ### Validation - Added tests: the native picker's `mediaHeaders` call returns the relay assertion for relay media and no headers for an external emoji URL; `headersForUrl` returns the assertion only for the relay origin (not another host, subdomain or port). The reactions sheet passes the combined headers for avatars and custom emoji, and `nativeMediaHeadersProvider` merges both headers only for relay URLs. - Focused existing validation: the emoji picker and organization relay auth suites pass, and `flutter analyze` is clean. In the full mobile suite, the only failures are 10 in `notification_toggle_test`, `push_bootstrap_test` and `push_bridge_test`, and they fail the same way on `main`. - Automated full-app simulator run (iPhone 17 Pro, iOS 27, debug build of this branch with hosted auth enabled, signed in to two hosted communities through a cloned simulator keychain, driven with AXe). Covered: launch signed in, channel and DM lists, opening a channel and a DM, the native emoji picker (its Custom section shows the emoji as an image, not the sparkle fallback), sending a custom emoji message and seeing it render, reacting with the custom emoji (shown in the quick-reaction row and as a reaction pill), opening a thread, switching communities, avatars and community icons, background and resume, and a cold relaunch. Result: no crashes or regressions seen. Screenshots are omitted because they show real account and community names. - Limitations: this run does not reproduce the original bug. The hosted community's custom emoji also loaded in a temporary build with the relay assertion removed from the `mediaHeaders` bridge, so its emoji URL evidently doesn't need the assertion, and the run doesn't prove the fix. The reactions detail sheet could not be reached because simulated long presses on the pill opened the message menu, and inline image attachments were not exercised. A device build against a community whose emoji are relay-hosted is still needed to confirm the fix. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Signed-off-by: Tom Brow <tomb@squareup.com> Co-authored-by: Tom Brow <tomb@squareup.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: studio <dc2fc481343672df31a689840c09299972e704dac592e0da6f18486646a7ee3e@buzz.block.builderlab.xyz>
27 lines
1.0 KiB
Dart
27 lines
1.0 KiB
Dart
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|
|
|
import '../relay/relay.dart';
|
|
import 'hosted_relay_auth.dart';
|
|
|
|
/// Headers for a relay media read that does not go through
|
|
/// [HostedRelayHttpClient], such as a native player or image loader that takes
|
|
/// headers up front.
|
|
///
|
|
/// [MediaGetAuthService.headersFor] alone is not enough on a hosted relay:
|
|
/// media reads also need the relay assertion, which only the hosted client adds.
|
|
/// The assertion is scoped to the relay origin and comes from the live
|
|
/// session's cache, so building headers never blocks.
|
|
final nativeMediaHeadersProvider =
|
|
Provider<Map<String, String> Function(String)>((ref) {
|
|
final media = ref.watch(mediaGetAuthServiceProvider);
|
|
final hosted = ref.watch(hostedRelayAuthProvider);
|
|
final config = ref.watch(relayConfigProvider);
|
|
return (url) {
|
|
final uri = Uri.tryParse(url);
|
|
return {
|
|
...media.headersFor(url),
|
|
if (uri != null) ...hosted.cachedHeadersForUrl(config, uri),
|
|
};
|
|
};
|
|
});
|