mirror of
https://github.com/block/buzz.git
synced 2026-11-07 09:52:03 +00:00
fba8a7a94febd4fbfb97df90f7c6be8fb258cf4c
8 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
bf13f7c83b |
fix(ios): mint NIP-FI assertions in the notification extension (#8982)
Organization relays that enforce NIP-FI (federated identity) require a `Nostr-Federated-Identity` assertion on `/query`. The iOS notification service extension signed its `/query` with NIP-98 only, so the relay answered `401` and every push from an enforcing organization relay showed the generic "You have a new message." body. The app already mints these assertions in Dart from its organization account session, but that session lives in the app's own keychain, and assertions last minutes, so a cached one is useless by the time a push arrives. This change lets the extension mint its own: - **App:** whenever the organization `BuilderlabAccountClient` adopts or clears its session, the app writes or deletes a device-only keychain item in the group it already shares with the extension. The item holds the organization origin, the session credential and the organization's exact relay hosts (from `BUZZ_ORGANIZATION_RELAYS`). Builds without an organization never write it. - **Extension:** for a community whose relay host is one of those exact hosts, `BuzzRelayAssertionIssuer` requests an assertion from the organization issuer (`/api/goose/v1/buzz/identity/assertions`, session credential plus NIP-98 with the community's account key, the same request the app makes), checks it was issued for that key, and caches it in-process until 30 seconds before expiry. Both `/query` paths (message lookup and sender/channel enrichment) send it. Other relays get no header and never see the session. - Signing out awaits removal of the shared copy and fails if removal fails; each restore that finds no session retries the removal. - If issuance fails or the user is signed out, the extension sends the query without the header and the relay refuses it, which keeps today's generic fallback. Not covered here: relay-hosted avatars on enforcing relays still load without the assertion (the notification text and title are unaffected), hosted Builderlab communities, and Android. ### Validation Added tests: - `testOrganizationRelayQueriesCarryMintedAssertion`: against a stub relay that answers `401` without the assertion, two organization pushes resolve with message body and sender name from a single issuance, a non-organization relay gets neither the header nor the session, and after sign-out the notification falls back. Removing the header from the query path makes this test fail. - `testOrganizationAssertionIssuanceFailuresSendNoHeader`: refused, wrong-key, expired and header-injection responses produce no header. Subdomains and other ports of the organization relay host are never sent the session. - `builderlab_account_test.dart`: the shared copy follows exchange, restore and clear; a failed removal fails sign-out, and the next restore retries it. Focused existing validation: the full BuzzPushKit suite, the hosted Dart tests, `flutter analyze`, and an iOS device build of the app and extension. Not yet validated: a live push from the Block relay on a device. ## Before merge Live device push test: waived by Tom (2026-11-03). 🤖 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> |
||
|
|
f6f6d9a673 |
chore(mobile): delete dead code with no remaining UI path (#8880)
## Summary Deletes mobile code that no longer has a user-reachable path and has no counterpart in buzz-app, so it is unlikely to come back for parity. Net: 104 files, about 5,500 lines removed. No intended behavior change. - **iOS native message menu and action surface:** `NativeMessageMenu.swift` and `NativeMessageActionSurface.swift`, the `message`/`supportsMessage` coordinator branches, and the Dart selection hook. Dart stopped creating this view in [#8280](https://github.com/block/buzz/pull/8280). The native reactions sheet in the same coordinator is kept. - **Old home quick-actions launcher and `ComposeMorphIcon`:** only instantiated in tests since the nav-bar creation actions replaced them. - **Retired local theme stack:** `ThemeNotifier`, `AccentNotifier`, `SchemeNotifier`, `adaptive_theme.dart`, `AccentPickerPage`, the appearance-mode sheet and their helpers. These predate per-community theme sync. The legacy prefs migration in `community_theme_preference.dart` is kept. - **Unused widgets:** `ProfileAvatar` and `MaskedAvatarBadge`. - **Push permission-status plumbing:** the iOS `notificationAuthorizationStatus`/`openNotificationSettings` handlers, plus the uncalled strict age-gate snapshot registration and its Swift `syncAgeGatePushSnapshot` path. - **Orphaned files** with no importer: `profile_text_edit_sheet`, `editor_option_button`, `retained_page_transition`, `tappable_flapping_bee`, `agent_model_info`, `community_bot_profiles`, `sheet_divider`, `success_haptic`. - **Uncalled helpers:** the `pickAndUpload*`/camera helpers in `media_upload.dart`, unused `NostrFilters` builders, `mediaGetHeadersFor*`, and about 20 other uncalled functions, providers and styles. - **Removed-UI leftovers:** the never-wired invite directory providers, `AuthNotifier.signOut` and `removeActiveCommunityForSignOut` (the Sign out row is gone), `renameCommunity`, `HomePage.profilePageBuilder`/`SettingsPage.embedded`, and the create-forum branches of the create-channel sheet. Forum viewing is unchanged. Tests that only covered deleted code are removed. Tests of surviving code that used deleted helpers as drivers were repointed to the surviving API. An audit of `mobile/android` found no dead Kotlin, resources, manifest entries or Gradle dependencies, so Android code is unchanged. ## Validation - `flutter analyze`: no issues. - After rebasing onto main ([ |
||
|
|
7b9551aba8 |
fix(ios): open push replies in their containing thread (#8294)
Tapping a push notification for a thread reply currently opens the channel at its parent. Preserve the verified event's direct-parent thread ID through the iOS notification payload and Flutter bridge so the tap opens the containing thread and highlights that exact reply, even when newer replies have arrived. This matches Inbox's destination selection for nested replies; broadcast replies still open in the channel. Closest related work: https://github.com/block/buzz/pull/6159 (original push-tap routing). ### Validation - Follow-up [ |
||
|
|
868e2f8f32 |
refactor(push): one app profile per gateway (#8206)
Buzz push currently selects an application profile, such as `dogfood`, during client registration. We want each gateway to serve one Apple app configured by the operator instead. This removes profile selection from the client, relay, and gateway, while allowing internal and public gateways to use their own Apple identities. This PR implements that model in the Buzz source and upstream deployment chart. Discovery, enrollment, grants, and leases no longer carry an application-profile selector. Each gateway has one configured Apple application and APNs transport. The protocol changes are incompatible with the old registration format, so relays advertise `buzz-push-v1` instead of `nip-pl`. Old clients do not discover the new push capability. Updated clients use separate Keychain records and register again. The [push notes](https://github.com/block/buzz/blob/push-capability-v1/docs/buzz-push.md) describe the experimental contract. Landing this PR is the first step. Publish the matching gateway image and upstream chart, then land the [shared chart update](https://github.com/squareup/cash-helm-charts/pull/5740) and [gateway deployment update](https://github.com/squareup/builderbot-platform-core-infrastructure/pull/1085). Before cutover, stop old gateway instances and check legacy registration state. Migrations refuse to proceed if old gateway installations or active relay leases remain; they do not delete that state. Physical-device verification remains necessary before production activation. ### Validation Gateway and focused Flutter push tests pass, as do the Swift package, simulator build, and native notification/enrichment tests. A local relay advertises `buzz-push-v1` without `nip-pl` or `app_profiles`. The PostgreSQL suite passed 868 tests; its two migration-test failures were corrected and pass a focused rerun together with the gateway cutover guard. Chart rendering and release-contract checks pass. Desktop JavaScript tests, desktop and web builds, native desktop compilation, mobile analysis/tests, and desktop native tests pass. The full Rust unit lane and prescribed `just test` command pass, including workspace integration tests against task-isolated local services. The production overlay now requires an explicitly supplied APNs topic, with omission and rendered-value regression coverage. The corrected Swift checksum assertion passes the complete package suite: 78 XCTest tests and 42 Swift Testing tests. No physical-device enrollment or production cutover is claimed. Local merge validation: the embedded migration test and formatting pass after reserving profile migration 0060 behind rollback migration 0059. The local pre-push Rust lane failed ACP timing/steering tests (`idle_resets_on_stdout_activity`, `keepalive_resets_idle_past_deadline`, and the failed/missing-outcome steering tests). ACP and its dependency manifests are unchanged by this branch; all four pass in an isolated single-thread rerun. The documented local-check exception skips only the Rust pre-push lane for publication; required CI remains mandatory. Mobile and desktop pre-push lanes passed. Combined migration validation: after rollback 0059 landed, the main merge preserves profile migration 0060 and both embedded migration assertions. The focused migration test and formatting pass. The next local push again failed only the unchanged ACP idle/keepalive timing tests (1053 passed, 2 failed, 3 ignored); other lanes passed. Publication uses the same documented local Rust-lane exception; required CI remains mandatory. Follow-up review fix: removed the legacy profile column from the copied-lease fixture. All four event-follow-up contract tests pass on isolated PostgreSQL databases, covering desired and migrated schemas in dual and app-only modes. Formatting passes. Publication retains the prior passing local-lane evidence and uses the authorized pre-push exception for the known unrelated ACP timing failures; required CI remains mandatory. --------- Signed-off-by: Tom Brow <tomb@block.xyz> |
||
|
|
779af8886c |
fix(mobile): fail open when age checks are unavailable (#7714)
Age-check errors previously blocked Buzz startup. Normal app startup now proceeds immediately; only a valid under-18 response from the current launch request can restrict access. Age check enforcement remains disabled at compile time by default. Builds can opt in with `--dart-define=BUZZ_AGE_GATING_ENABLED=true`. --------- Signed-off-by: Tom Brow <tomb@block.xyz> |
||
|
|
76f7e5ef0c |
feat(push): make gateway origin configurable (#7261)
Make the push gateway origin configurable across the relay, gateway, mobile client, and deployment chart. This is a fresh deployment. The gateway has only run on personal development infrastructure, and push has no existing enabled users. Legacy migration, configured-to-unconfigured mobile transitions, and rolling upgrades from pre-launch gateway binaries are not supported. Populated pre-launch databases are rejected with an informative error before migrations run; their data is untouched. Mobile builds may omit the gateway URL, leaving push unavailable. Once push is enabled, subsequent builds must retain that configuration. ### Validation - All applicable CI checks pass at `5f546cfaf403f0386f146d213adfb4392f4ec29c`, including Rust, mobile/Swift, desktop/integration, security, image builds, and Helm checks. An unrelated agent cancellation test passed on retry. - PostgreSQL CI passed 384 tests, including the production-entry-point regression for refusing populated pre-launch databases before migrations, fresh initialization, and subsequent initialized deployments. - Focused local validation passed all 10 gateway PostgreSQL tests under the CI discovery filter and all 3 unconfigured-mobile tests. Repository-wide PostgreSQL discovery validation and the complete pre-push hooks passed. - Physical-device APNs/App Attest behavior and deployment to a live cluster were not verified in this work. --------- Signed-off-by: Tom Brow <tomb@block.xyz> Co-authored-by: Codex <noreply@openai.com> |
||
|
|
f78a32cf03 |
feat(mobile): hard-block under-18 users on the store age signal (#4665)
### What changed? Buzz mobile checks the app-store age signal at launch and hard-blocks the app behind a full-screen 18+ notice when the store says the signed-in person is under 18. No age signal (e.g. declined permission, indeterminate value, unsupported OS versions) results in no block. The age value is never persisted. It is read, used to compute one boolean, and discarded. Nothing is written to disk, and neither the age value nor the range is logged or sent to analytics. Platform notes: - **New dependency `com.google.android.play:age-signals:0.0.4`.** This is the only way to read the Play signal. Its AAR declares `minSdkVersion 23`, below the app's 24, so it forces **no minSdk bump**, and it self-declares its own consent activity, so **no AndroidManifest change** is needed. - **iOS entitlement and linking.** `DeclaredAgeRange` autolinks from the import. `Runner.entitlements` now declares `com.apple.developer.declared-age-range`, and the call remains behind `#available(iOS 26.0, *)`. ### Why? App stores now expose a declared-age range, and Buzz's Terms require users to be 18 or older. This wires the store signal to that rule with the smallest change that is defensible. Absent signals, declined or undeterminable results, and unsupported OS versions allow the app through. A transient native request failure does not become evidence that access is allowed: the app remains gated until a retry produces a result. Only an affirmative under-18 signal produces the permanent restriction screen. ### How is it tested? Added tests: - [`age_signal_provider_test`](https://github.com/block/buzz/tree/main/mobile/test/features/age_gate) covers the 17/18 boundary, open-ended ranges, no-signal, transient recovery, bounded retry exhaustion, malformed payloads, and request-once behavior - [`age_gate_app_test`](https://github.com/block/buzz/tree/main/mobile/test/features/age_gate) proves authenticated content remains unmounted while the signal is pending or retryable, and verifies the retry path - [`age_restriction_page_test`](https://github.com/block/buzz/tree/main/mobile/test/features/age_gate) asserts the block screen offers no bypass affordance - [`community_provider_test`](https://github.com/block/buzz/tree/main/mobile/test/shared/community) verifies age restriction clears push state, persists inactive leases, and retries a failed tombstone - [`AgeSignalPayloadTest`](https://github.com/block/buzz/tree/main/mobile/android/app/src/test/kotlin/xyz/block/buzz/mobile) pins the Android success payload to exactly `{status, ageUpper}` and verifies failures return the distinct retryable platform error ### Deployment note Enable the Declared Age Range capability and regenerate signing profiles carrying the entitlement. This relops/App Store Connect task is not performed by this PR. *🤖 This PR was authored [with an agent](codex://threads/019e5128-536b-73b3-8348-88651123f6da).* | Age restriction screen | | --- | | <img src="https://github.com/user-attachments/assets/5b0f48b1-df9b-4a60-9800-80b33f128535" alt="Age restriction screen" width="320"> | --------- Signed-off-by: Tom Brow <tomb@block.xyz> Co-authored-by: npub12wpjffj7q5qjsky5jvk4ldwlxmse5xll3d8gytk4wqd0c5y7jvwspg37n6 <538324a65e0501285894932d5fb5df36e19a1bff8b4e822ed5701afc509e931d@buzz.block.builderlab.xyz> Co-authored-by: npub1tquskdu6yc4h8l7xxtceculxw600grekeq0xg2ukqfrwl7vrzg3quz3gmp <58390b379a262b73ffc632f19c73e6769ef40f36c81e642b960246eff9831222@buzz.block.builderlab.xyz> Co-authored-by: Codex <noreply@openai.com> |
||
|
|
c432a111ca |
feat(mobile): push notifications MVP (#6269)
This PR implements MVP, iOS-only,
[NIP-PL](
|