diff options
| author | Owen Jacobson <owen@grimoire.ca> | 2025-11-07 21:39:39 -0500 |
|---|---|---|
| committer | Owen Jacobson <owen@grimoire.ca> | 2025-11-08 16:03:40 -0500 |
| commit | 6bab5b4405c9adafb2ce76540595a62eea80acc0 (patch) | |
| tree | 5b997adac55f47b52f30022013b8ec3b2c10bcc5 /src/app.rs | |
| parent | 9be808177a06b33892be6fdd7c1cb31cf3b924fa (diff) | |
De minimis "send me a notification" implementation.
When a user clicks "send a test notification," Pilcrow delivers a push message (with a fixed payload) to all active subscriptions. The included client then displays this as a notification, using browser APIs to do so. This lets us verify that push notification works, end to end - and it appears to.
The API endpoint for sending a test notification is not documented. I didn't feel it prudent to extensively document an endpoint that is intended to be temporary and whose side effects are very much subject to change. However, for posterity, the endpoint is
POST /api/push/ping
{}
and the push message payload is
ping
Subscriptions with permanent delivery failures are nuked when we encounter them. Subscriptions with temporary failures cause the `ping` endpoint to return an internal server error, and are not retried. We'll likely want retry logic - including retry logic to handle server restarts - for any more serious use, but for a smoke test, giving up immediately is fine.
To make the push implementation testable, `App` is now generic over it. Tests use a dummy implementation that stores sent messages in memory. This has some significant limitations, documented in the test suite, but it beats sending real notifications to nowhere in tests.
Diffstat (limited to 'src/app.rs')
| -rw-r--r-- | src/app.rs | 61 |
1 files changed, 37 insertions, 24 deletions
@@ -17,25 +17,27 @@ use crate::{ }; #[derive(Clone)] -pub struct App { +pub struct App<P> { db: SqlitePool, + webpush: P, events: event::Broadcaster, token_events: token::Broadcaster, } -impl App { - pub fn from(db: SqlitePool) -> Self { +impl<P> App<P> { + pub fn from(db: SqlitePool, webpush: P) -> Self { let events = event::Broadcaster::default(); let token_events = token::Broadcaster::default(); Self { db, + webpush, events, token_events, } } } -impl App { +impl<P> App<P> { pub fn boot(&self) -> Boot { Boot::new(self.db.clone()) } @@ -60,8 +62,11 @@ impl App { Messages::new(self.db.clone(), self.events.clone()) } - pub fn push(&self) -> Push { - Push::new(self.db.clone()) + pub fn push(&self) -> Push<P> + where + P: Clone, + { + Push::new(self.db.clone(), self.webpush.clone()) } pub fn setup(&self) -> Setup { @@ -80,58 +85,66 @@ impl App { pub fn vapid(&self) -> Vapid { Vapid::new(self.db.clone(), self.events.clone()) } + + #[cfg(test)] + pub fn webpush(&self) -> &P { + &self.webpush + } } -impl FromRef<App> for Boot { - fn from_ref(app: &App) -> Self { +impl<P> FromRef<App<P>> for Boot { + fn from_ref(app: &App<P>) -> Self { app.boot() } } -impl FromRef<App> for Conversations { - fn from_ref(app: &App) -> Self { +impl<P> FromRef<App<P>> for Conversations { + fn from_ref(app: &App<P>) -> Self { app.conversations() } } -impl FromRef<App> for Invites { - fn from_ref(app: &App) -> Self { +impl<P> FromRef<App<P>> for Invites { + fn from_ref(app: &App<P>) -> Self { app.invites() } } -impl FromRef<App> for Logins { - fn from_ref(app: &App) -> Self { +impl<P> FromRef<App<P>> for Logins { + fn from_ref(app: &App<P>) -> Self { app.logins() } } -impl FromRef<App> for Messages { - fn from_ref(app: &App) -> Self { +impl<P> FromRef<App<P>> for Messages { + fn from_ref(app: &App<P>) -> Self { app.messages() } } -impl FromRef<App> for Push { - fn from_ref(app: &App) -> Self { +impl<P> FromRef<App<P>> for Push<P> +where + P: Clone, +{ + fn from_ref(app: &App<P>) -> Self { app.push() } } -impl FromRef<App> for Setup { - fn from_ref(app: &App) -> Self { +impl<P> FromRef<App<P>> for Setup { + fn from_ref(app: &App<P>) -> Self { app.setup() } } -impl FromRef<App> for Tokens { - fn from_ref(app: &App) -> Self { +impl<P> FromRef<App<P>> for Tokens { + fn from_ref(app: &App<P>) -> Self { app.tokens() } } -impl FromRef<App> for Vapid { - fn from_ref(app: &App) -> Self { +impl<P> FromRef<App<P>> for Vapid { + fn from_ref(app: &App<P>) -> Self { app.vapid() } } |
