Skip to main content

baukit_push/
lib.rs

1//! Provider-neutral push delivery with an Expo adapter.
2//!
3//! The crate separates a small [`PushSender`] port from its [`ExpoPushSender`]
4//! adapter. Domain code builds [`PushMessage`] values and reads [`PushOutcome`]
5//! values; nothing above the port names a provider. Scheduling, quiet hours,
6//! and deciding who gets notified stay in the product.
7//!
8//! # Two-phase delivery
9//!
10//! Expo answers a send with a *ticket* per notification, then confirms delivery
11//! later through a *receipt*. [`ExpoPushSender`] runs both phases per batch, so
12//! one call returns [`PushDeliveryStatus::Delivered`] or
13//! [`PushDeliveryStatus::Rejected`] wherever Expo has settled, and
14//! [`PushDeliveryStatus::Accepted`] where it has not. Never resend on
15//! `Accepted`; the notification is in flight.
16//!
17//! # Pruning dead tokens
18//!
19//! A device token stops working once the app is uninstalled. Expo reports that
20//! as `DeviceNotRegistered`, which becomes
21//! [`PushRejection::DeviceNotRegistered`]. Delete those tokens after every
22//! send, or the same failures repeat forever:
23//!
24//! ```rust
25//! use baukit_push::{PushOutcome, PushSender};
26//!
27//! async fn deliver(
28//!     sender: &impl PushSender,
29//!     messages: Vec<baukit_push::PushMessage>,
30//! ) -> Result<Vec<String>, baukit_push::PushError> {
31//!     let outcomes = sender.send(messages).await?;
32//!     Ok(outcomes
33//!         .iter()
34//!         .filter(|outcome| PushOutcome::is_token_dead(outcome))
35//!         .map(|outcome| outcome.token.clone())
36//!         .collect())
37//! }
38//! ```
39//!
40//! # Retries
41//!
42//! Whole-request failures arrive as [`PushError::Transport`] carrying a
43//! [`baukit_http::RetryClass`], so an Expo rate limit with a `Retry-After`
44//! header reaches the caller as a concrete delay through
45//! [`PushError::retry_after`]. Per-notification refusals are not errors:
46//! inspect [`PushRejection::is_retryable`] on each outcome instead.
47
48#![deny(missing_docs)]
49
50mod config;
51mod expo;
52#[cfg(feature = "test-support")]
53mod fake;
54mod port;
55
56pub use config::{
57    DEFAULT_EXPO_ENDPOINT, MAX_BATCH_SIZE, PushConfig, PushOptions, PushOptionsError,
58};
59pub use expo::ExpoPushSender;
60#[cfg(feature = "test-support")]
61pub use fake::FakePushSender;
62pub use port::{
63    PushDeliveryStatus, PushError, PushFuture, PushMessage, PushOutcome, PushRejection, PushSender,
64};
65
66// Compiles the README's examples so they cannot drift from the API.
67#[doc = include_str!("../README.md")]
68#[cfg(doctest)]
69struct ReadmeDoctests;