AshDispatch.PushBackend behaviour (AshDispatch v0.6.1)
View SourceBehaviour that a Web Push backend module implements. Configure a backend in your app config:
config :ash_dispatch, :push_backend, MyApp.PushThe transport (AshDispatch.Transports.Push) will call your backend's
deliver/4 whenever an event channel with transport: :push fires.
Why the encryption lives in your app, not here
Web Push (RFC 8291/8292) needs a VAPID keypair, an ECDH/HKDF
encryption step and a per-endpoint HTTP POST to whatever push service
the browser chose (FCM, Mozilla autopush, WNS…). Those are deployment
concerns — key material, egress rules, retry budget — so ash_dispatch
owns the routing and the audit trail and leaves the protocol to the
consumer, exactly like AshDispatch.SMSBackend.
Implementation contract
- One receipt fans out to many devices. Unlike email or SMS there
is no single address: a user has one subscription per browser and
machine. Resolve them from
receipt.user_id(or fromreceipt.recipientif your app stores a device token there) and send to each. The receipt represents the event delivery, not one device. - Read the payload from
receipt.content— typically:title,:bodyand the URL your service worker should open on click. Keep it small: push services cap the encrypted payload (4 KB is the safe ceiling). - Update the receipt via Ash:
- Success (at least one device accepted):
for_update(:mark_sent, %{provider_id: provider_id}) - No subscriptions at all:
for_update(:skip, %{error_message: "no_subscriptions"})— a user who never granted permission is not a failure. - Failure:
for_update(:mark_failed, %{error_message: reason})
- Success (at least one device accepted):
- Return
{:ok, updated_receipt}or{:error, reason}.
Pruning dead subscriptions
Push endpoints expire silently. A push service answers 404 or 410 Gone once a subscription is dead, and it will keep answering that
forever. Delete the subscription row on those two statuses — a backend
that does not prune accumulates dead endpoints and spends its retry
budget on browsers that no longer exist.
Treat 429 and 5xx as retryable instead; they are the push service
throttling you, not the subscription being gone.
Summary
Callbacks
@callback deliver( receipt :: struct(), context :: AshDispatch.Context.t(), channel :: AshDispatch.Channel.t(), event_config :: map() ) :: {:ok, struct()} | {:error, term()}