Standard library · Web and browser

webpush

Sending notifications to browsers from a server, with VAPID keys and the standard encryption.

import webpush

A web push notification is a message a server sends to a user's browser even when the site is not open. The flow: make a key pair once with GenerateKeys, use the public key in the page JavaScript to subscribe, store the subscription the browser makes on your server, and send messages with Send.

The message is encrypted under RFC 8291 before it is sent, so the browser push service never sees its contents. When the result says gone, the user has cancelled the subscription.

Types

Keys

struct Keys
The VAPID keys of the server.
NameTypeDescription
public_keystr = ""The public key as url safe base64; this is what you give to applicationServerKey in the page JavaScript.
private_keystr = ""The private key; keep it secret.
okbool = falseThe keys are valid.
webpush-Keys.salam
import webpush
import str

func main:
    fresh := webpush.GenerateKeys()
    println fresh.ok, fresh.public_key.len(), fresh.private_key.len()

    k := webpush.KeysFrom("BLmOwafYGSjiuCX_1RSCO_n0YmacxYMDqitMydKE4A1Wp2qmUuMXg2IM_hzcrmZLun4jEu_lKfVdtnAp0LMK4NE", "Nhh4m4Va4yhL-53i302044iqeAeMtnOEYlIT2KWRJmg")
    println k.ok, webpush.KeysFrom("", "").ok
    at := webpush.VapidHeaderAt(k, "https://fcm.googleapis.com", "mailto:ali@example.com", 1700000000)
    println at
    now := webpush.VapidHeader(k, "https://fcm.googleapis.com", "mailto:ali@example.com")
    println str.StartsWith(now, "vapid t="), str.Contains(now, ", k=" + k.public_key)

    mut o := webpush.NewOptions("mailto:ali@example.com")
    println o.subject, o.ttl == webpush.DEFAULT_TTL, o.ttl / 86400
    o.urgency = "high"
    o.topic = "news"
    o.ttl = 3600
    println o.urgency, o.topic, o.ttl
end
Outputtrue 87 43 true false vapid t=eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJhdWQiOiJodHRwczovL2ZjbS5nb29nbGVhcGlzLmNvbSIsImV4cCI6MTcwMDA0MzIwMCwic3ViIjoibWFpbHRvOmFsaUBleGFtcGxlLmNvbSJ9.8Sx-V8MK5NDw923EAMtv8NSCJzQMdhQx8E_ItZUuaMU_VVCv0NzyPG0vZ6JfRsu-S6h99PJJFB6_vgd3NGT_Lw, k=BLmOwafYGSjiuCX_1RSCO_n0YmacxYMDqitMydKE4A1Wp2qmUuMXg2IM_hzcrmZLun4jEu_lKfVdtnAp0LMK4NE true true mailto:ali@example.com true 28 high news 3600

Subscription

struct Subscription
The subscription the browser makes with pushManager.subscribe and sends to your server.
NameTypeDescription
endpointstr = ""The address of the browser push service.
p256dhstr = ""The public key of the browser, from the keys part of the subscription.
authstr = ""The authentication secret, from the same part.
webpush-Encrypt.salam
import webpush

func main:
    sub := webpush.Subscription {
        endpoint = "https://push.example.net/x",
        p256dh = "BCVxsr7N_eNgVRqvHtD0zTZsEc6-VV-JvLexhqUzORcxaOzi6-AYWXvTBHm4bjyPjs7Vd8pZGH6SRpkNtoIAiw4",
        auth = "BTBZMqHH6r4Tts7J_aSIgg"
    }
    mut good := false
    body := webpush.EncryptWith(sub, "When I grow up, I want to be a watermelon", "yfWPiYE-n46HLnH0KqZOF1fJJU3MYrct3AELtAQ-oRw", "DGv6ra1nlYgDCS1FRnbzlw", good)
    println good
    println body

    one := webpush.Encrypt(sub, "hello", good)
    two := webpush.Encrypt(sub, "hello", good)
    println good, one != two, one.len() == two.len()
end
Outputtrue DGv6ra1nlYgDCS1FRnbzlwAAEABBBP4z9KsN6nGRTbVYI_c7VJSPQTBtkgcy27mlmlMoZIIgDll6e3vCYLocInmYWAmS6TlzAC8wEqKK6PBru3jl7A_yl95bQpu6cVPTpK4Mqgkf1CXztLVBSt2Ks3oZwbuwXPXLWyouBWLVWGNWQexSgSxsj_Qulcy4a-fN true true true

Options

struct Options
The send options.
NameTypeDescription
subjectstr = ""Your contact address, as a mailto: or a web address; the services require it.
ttlint = 2419200How many seconds the service should hold the message for a browser that is off.
urgencystr = ""The urgency: very-low, low, normal or high.
topicstr = ""The topic; a new message with the same topic replaces an earlier undelivered one.

The example for this item is shown with the ones above: webpush-Keys.salam

Result

struct Result
The result of the send.
NameTypeDescription
okbool = falseThe service accepted the message.
statusint = 0The HTTP status code of the service.
gonebool = falseThe subscription is no longer valid (404 or 410); delete it from your database.
errorstr = ""The error message.
webpush-Send.salam
import webpush

func main:
    k := webpush.KeysFrom("BLmOwafYGSjiuCX_1RSCO_n0YmacxYMDqitMydKE4A1Wp2qmUuMXg2IM_hzcrmZLun4jEu_lKfVdtnAp0LMK4NE", "Nhh4m4Va4yhL-53i302044iqeAeMtnOEYlIT2KWRJmg")
    sub := webpush.Subscription {
        endpoint = "https://fcm.googleapis.com/fcm/send/abc",
        p256dh = "BCVxsr7N_eNgVRqvHtD0zTZsEc6-VV-JvLexhqUzORcxaOzi6-AYWXvTBHm4bjyPjs7Vd8pZGH6SRpkNtoIAiw4",
        auth = "BTBZMqHH6r4Tts7J_aSIgg"
    }
    o := webpush.NewOptions("mailto:ali@example.com")
    r := webpush.Send(k, sub, `{"title":"hello","body":"you have a new message"}`, o)
    if r.ok:
        println "sent", r.status
    else r.gone:
        println "the subscription has expired; delete it"
    else:
        println "error:", r.status, r.error
    end
end

Constants and variables

DEFAULT_TTL

const DEFAULT_TTL: i32 = 2419200
How long the push service keeps a message by default: 28 days, in seconds.

The example for this item is shown with the ones above: webpush-Keys.salam

Functions

GenerateKeys

func GenerateKeys(): Keys
Makes a fresh VAPID key pair; make it once and keep it, because the subscriptions are tied to the public key.

The example for this item is shown with the ones above: webpush-Keys.salam

VapidHeaderAt

func VapidHeaderAt(keys: Keys, audience: str, subject: str, epoch_seconds: i64): str
The Authorization header for the service at the given address, at the given time in Unix seconds. The signature is deterministic, so the same input gives the same output.

The example for this item is shown with the ones above: webpush-Keys.salam

VapidHeader

func VapidHeader(keys: Keys, audience: str, subject: str): str
The same, at the current time.

The example for this item is shown with the ones above: webpush-Keys.salam

EncryptWith

func EncryptWith(sub: Subscription, payload: str, as_private: str, salt: str, ok &: bool): str
Encrypts the message with the given key and salt under RFC 8291; for testing against the standard vectors. In real work use Encrypt.

The example for this item is shown with the ones above: webpush-Encrypt.salam

Encrypt

func Encrypt(sub: Subscription, payload: str, ok &: bool): str
Encrypts the message for this subscription; with a fresh key and salt every time, so two outputs are never the same.

The example for this item is shown with the ones above: webpush-Encrypt.salam

Send

func Send(keys: Keys, sub: Subscription, payload: str, opts: Options): Result
Encrypts the message and sends it to the browser push service with the VAPID header. An empty message is a notification with no body.

The example for this item is shown with the ones above: webpush-Send.salam

NewOptions

func NewOptions(subject: str): Options
The default options with a contact address.

The example for this item is shown with the ones above: webpush-Keys.salam

KeysFrom

func KeysFrom(public_key: str, private_key: str): Keys
Returns stored keys; ok is false when either one is empty.

The example for this item is shown with the ones above: webpush-Keys.salam