Skip to main content

Crate pushicino

Crate pushicino 

Source
Expand description

This crate provides the tools necessary for your application to send Web Push messages in accordance with RFC 8030.

In addition to sending Push messages, this crate also handles:

  • the generation of valid Voluntary Application Server Identification (VAPID) tokens, as required, in accordance with RFC 8292 for any messages you send; and,
  • the proper encryption of message content you send, using AES128GCM, in accordance with RFC 8291.

§Setup a Push Service

Before your application can accept subscriptions from web browsers and send messages, you must first initialize a PushService with an ECDSA private key and metadata that will be used to generate VAPID tokens. The ECDSA private key and metadata you use represents your application’s “identity” to servers that deliver your push message: this means you should use the same private key across restarts of your application and across multiple running instances of your application.

The OpenSSL suite can be used to generate an ECDSA private key for use with this library as follows:

openssl ecparam -name secp256r1 -genkey -noout -out vapid.pem

This should have created a file called vapid.pem containing the private key. Keep it secret: losing it could allow an attacker to send push messages as if they were coming from your application.

Then, create a PushService as follows:

use pushicino::{PushService, Vapid, Subject, VapidKey};
let service = PushService::new(Vapid::new(
  Subject::parse("mailto:vapid@yourapplication.com")?,
  VapidKey::load_from_file("vapid.pem")?,
));

// If you're using Axum, you should put the PushService into your application's state object

If you’re using a web framework, like Axum, you should put the PushService that we created here into your application’s state and that can be accessed by your application’s route handlers.

§VAPID Endpoint

Frontend applications running on the browser will require your PushService’s public key. You should create an endpoint that serves this public key, in Base64 encoding, to the frontend. For example, in Axum, you could do:

use std::sync::Arc;
use axum::http::StatusCode;
use axum::extract::State;
use axum::Json;
use serde::Serialize;
use pushicino::PushService;

struct ApplicationState {
    push_service: PushService,
}

#[derive(Serialize)]
struct ApplicationConfiguration {
    vapid_public_key: String,
}

// Create a request handler that will serve the push service's public key in Base64 encoding
pub async fn application_configuration(State(state): State<Arc<ApplicationState>>)
    -> Result<Json<ApplicationConfiguration>, StatusCode> {
    Ok(Json(ApplicationConfiguration {
         vapid_public_key: state.push_service.vapid_public_key_base64(),
    }))
}

// Don't forget to register the `application_configuration` handler with your Axum router!

Feel free to use any other method, instead of the one suggested above, to transfer the public key to the frontend.

§Subscriptions Endpoints

When the user’s browser confirms they would like to receive push messages from your application, they will need to send the subscription details to your application. You can receive this information by setting up an endpoint to receive this information.

Subscription implements the serde::Deserialize trait (from the serde crate) and it matches the exact object returned by the PushSubscription.toJSON() method of the user agent. This allows you to accept a Subscription directly from your request handler.

For example, in Axum, you could do:

use std::sync::Arc;
use axum::http::StatusCode;
use axum::extract::State;
use axum::Json;
use serde::Serialize;
use pushicino::{PushService, Subscription};

struct ApplicationState {
    push_service: PushService,
}

async fn subscribe(
 State(application): State<Arc<ApplicationState>>,
 Json(request): Json<Subscription>,
) -> Result<(), StatusCode> {
  // At this point, you should persist the Subscription into a database or some other storage

  // Send a Web Push message immediately to the user, thanking them for subscribing!
  application.push_service.send(&request, "Hello, thanks for subscribing!".as_bytes())
   .await.unwrap();

  Ok(())
}

// Don't forget to register the `subscribe` handler with your Axum router!

It is your responsibility to persist the Subscription into storage (Subscription implements the serde::Serialize trait). It is highly recommended that you associate the user’s identity alongside the Subscription in case you wish to send Web Push messages to specific users.

Structs§

AuthenticationSecret
A sequence of 16 octets generated by the user agent to act as a symmetric key and will be mixed into the key generation process as described in [https://datatracker.ietf.org/doc/html/rfc8291#section-3.2](RFC 8291 Section 3.2).
PushService
The main entrypoint for sending Web Push messages.
Subject
An Url which uses the mailto: scheme and is suitable for use as the sub claim in VAPID-generated tokens.
Subscription
An object containing all the information needed to send a Web Push message.
Vapid
Establishes your server application’s identity when sending Web Push messages.
VapidKey
The secret key used to sign generated VAPID tokens.

Enums§

Error