mirror of
https://github.com/block/buzz.git
synced 2026-10-28 13:01:10 +00:00
Push banners arrive silently because the gateway's fixed APNs body never requests a sound, even though the iOS app requests sound permission. Add `aps.sound: "default"` to that constant so ordinary alerts request the system sound even if the notification extension is not invoked. Preserve the existing alert text, mutable-content flag, and class-blind, content-free delivery contract; synchronize its normative specification and executable models. Companion client workaround: [#8429](https://github.com/block/buzz/pull/8429). This gateway change also covers the original-payload fallback and requires no mobile source change. Sound remains subject to iOS settings and authorization. It does not bypass Focus or Silent Mode, select critical alerts, or guarantee vibration. Every public-profile wake requests the same sound, including generic/restricted fallback banners. ### Validation - Extended the real HTTP transport test: reproduced missing sound (`null` versus `default`) before the fix, then passed with the fix. All 79 runnable gateway library tests pass; 17 infrastructure/manual tests remain ignored. - Added a guard against drift between the emitted bytes, NIP-PL, and both formal models. Fixed-payload and stateful-delivery models and mutation checks pass. - Inspected the 0.24.0 (7) iOS source: sound permission is requested; ordinary enrichment and restricted fallback copy incoming content. A macOS UserNotifications probe confirms copy retention, but does not test iOS Intents or audible device delivery. - Focused existing native resolver/handoff tests pass (35 tests on macOS). These cover resolution and fallback behavior, not iOS sound playback. - Deployed exact head [b8a0eb6](b8a0eb6b78) to the personal dev push gateway (Railway deployment `b3e52fc8-d1d3-48d7-ae21-1a83c5db2e99`). Verified the dev relay already routes to that gateway. On an iPhone 12 mini running the existing Buzz Dev 0.22 (10140), without the client workaround, automation captured the test banner and Tom confirmed audible sound on October 18. - Broad local validation reached an unrelated ACP startup-deadline failure in `signals_cancel_startup_turn_and_pending_stdin_without_hanging`. No ACP source or dependency changed, and ACP does not depend on the gateway. The full local gate is not reported as passing. - Local desktop pre-push tests passed 3,420 cases but failed two unchanged agent-discovery checks (a 300 ms subprocess deadline and a login-shell PATH comparison). Desktop does not depend on the gateway. Retried publication with only the Rust-suite and desktop-Tauri lanes excluded under the standing unrelated-failure exception; other hooks remain enabled. ### Before merge - [x] Verify a real push banner and audible sound through the dev relay and candidate dev gateway on an iOS device without the client workaround. Signed-off-by: Tom Brow <tomb@block.xyz>
161 lines
4.8 KiB
Rust
161 lines
4.8 KiB
Rust
//! Closed wire types for the stateful gateway.
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
pub const MAX_REQUEST_BYTES: usize = 8 * 1024;
|
|
/// Maximum decoded Apple App Attest object accepted by the verifier.
|
|
pub const MAX_APP_ATTESTATION_BYTES: usize = 16 * 1024;
|
|
/// Enrollment carries the maximum App Attest object as standard base64 plus a
|
|
/// bounded APNs endpoint and the closed JSON envelope. Other gateway requests
|
|
/// remain subject to `MAX_REQUEST_BYTES`.
|
|
pub const MAX_ENROLL_REQUEST_BYTES: usize =
|
|
MAX_APP_ATTESTATION_BYTES.div_ceil(3) * 4 + MAX_ENDPOINT_HEX_BYTES * 2 + 1024;
|
|
pub const MAX_GRANT_BYTES: usize = 4096;
|
|
pub const MAX_ENDPOINT_HEX_BYTES: usize = 512;
|
|
pub const APNS_RECONNECT_PAYLOAD: &[u8] =
|
|
br#"{"aps":{"alert":{"body":"Reconnect to your relay now"},"sound":"default","mutable-content":1}}"#;
|
|
pub const WIRE_VERSION: u8 = 1;
|
|
|
|
/// Relay request. It deliberately has no application-payload field:
|
|
/// the gateway emits one compiled-in APNs reconnect payload for every delivery.
|
|
/// `endpoint_grant` is opaque authenticated ciphertext minted by the gateway
|
|
/// sealing key and persisted with the relay-owned lease.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
#[serde(deny_unknown_fields)]
|
|
pub struct DeliveryRequest {
|
|
pub v: u8,
|
|
pub endpoint_grant: String,
|
|
pub request_id: uuid::Uuid,
|
|
pub expires_at: i64,
|
|
}
|
|
|
|
/// Opaque delivery capability plaintext. It contains no APNs token: the random
|
|
/// delegation id resolves through durable authority state, while the remaining
|
|
/// fields are authenticated fences that make stale or cross-relay use fail.
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
#[serde(deny_unknown_fields)]
|
|
pub struct EndpointGrant {
|
|
pub v: u8,
|
|
pub delegation_id: uuid::Uuid,
|
|
pub relay_pubkey: String,
|
|
pub endpoint_epoch: i64,
|
|
pub generation: i64,
|
|
pub expires_at: i64,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
#[serde(deny_unknown_fields)]
|
|
pub struct InstallationChallengeRequest {
|
|
pub v: u8,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize)]
|
|
pub struct InstallationChallengeResponse {
|
|
pub challenge_id: uuid::Uuid,
|
|
pub challenge: String,
|
|
pub expires_at: i64,
|
|
}
|
|
|
|
/// Direct app enrollment. `attestation` is Apple's CBOR object and `key_id` is
|
|
/// the App Attest key identifier, both base64 encoded. The attested key is the
|
|
/// installation authority; no second application signing key is introduced.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
#[serde(deny_unknown_fields)]
|
|
pub struct InstallationEnrollRequest {
|
|
pub v: u8,
|
|
pub challenge_id: uuid::Uuid,
|
|
pub challenge: String,
|
|
pub key_id: String,
|
|
pub attestation: String,
|
|
pub endpoint: String,
|
|
pub endpoint_epoch: i64,
|
|
pub expires_at: i64,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize)]
|
|
pub struct InstallationEnrollResponse {
|
|
pub installation_handle: uuid::Uuid,
|
|
pub endpoint_epoch: i64,
|
|
pub expires_at: i64,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
#[serde(deny_unknown_fields)]
|
|
pub struct DelegationRequest {
|
|
pub v: u8,
|
|
pub challenge_id: uuid::Uuid,
|
|
pub challenge: String,
|
|
pub installation_handle: uuid::Uuid,
|
|
pub endpoint_epoch: i64,
|
|
pub generation: i64,
|
|
pub relay_pubkey: String,
|
|
pub not_before: i64,
|
|
pub expires_at: i64,
|
|
pub assertion: String,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize)]
|
|
pub struct DelegationResponse {
|
|
pub endpoint_grant: String,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
#[serde(deny_unknown_fields)]
|
|
pub struct RotateEndpointRequest {
|
|
pub v: u8,
|
|
pub challenge_id: uuid::Uuid,
|
|
pub challenge: String,
|
|
pub installation_handle: uuid::Uuid,
|
|
pub endpoint_epoch: i64,
|
|
pub new_endpoint_epoch: i64,
|
|
pub endpoint: String,
|
|
pub assertion: String,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
#[serde(deny_unknown_fields)]
|
|
pub struct RevokeDelegationRequest {
|
|
pub v: u8,
|
|
pub challenge_id: uuid::Uuid,
|
|
pub challenge: String,
|
|
pub installation_handle: uuid::Uuid,
|
|
pub relay_pubkey: String,
|
|
pub generation: i64,
|
|
pub assertion: String,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
#[serde(deny_unknown_fields)]
|
|
pub struct RevokeInstallationRequest {
|
|
pub v: u8,
|
|
pub challenge_id: uuid::Uuid,
|
|
pub challenge: String,
|
|
pub installation_handle: uuid::Uuid,
|
|
pub endpoint_epoch: i64,
|
|
pub new_endpoint_epoch: i64,
|
|
pub assertion: String,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize)]
|
|
pub struct MutationResponse {
|
|
pub status: &'static str,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
#[serde(rename_all = "snake_case", tag = "status", deny_unknown_fields)]
|
|
pub enum DeliveryResponse {
|
|
Accepted,
|
|
InvalidEndpoint {
|
|
generation: i64,
|
|
invalid_at: Option<i64>,
|
|
},
|
|
Retry {
|
|
retry_after_seconds: Option<i64>,
|
|
},
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize)]
|
|
pub struct ErrorBody {
|
|
pub error: &'static str,
|
|
}
|