push (Web Push)
On this page:
- spider.push: Web Push notifications to browsers (VAPID).
spider.push
spider.push: Web Push. Sends an encrypted notification to a browser's push subscription (RFC 8291, aes128gcm), signed with the app's VAPID keys (RFC 8292).
spider.push.VapidKeys
pub const VapidKeys = struct
A new VAPID key pair as raw bytes, from WebPush.generateKeys. To use it in a PushConfig (or in the environment), encode each key as base64url without padding.
Fields
private_key: [32]u8: The P-256 private scalar, big-endian.public_key: [65]u8: The P-256 public point, uncompressed (it starts with 0x04).
push.VapidKeys.privateKeyText
pub fn privateKeyText(self: VapidKeys) [43]u8
The private key as PushConfig.private_key and VAPID_PRIVATE_KEY take it: base64url without padding (43 characters).
push.VapidKeys.publicKeyText
pub fn publicKeyText(self: VapidKeys) [87]u8
The public key as PushConfig.public_key, VAPID_PUBLIC_KEY and the page's applicationServerKey take it: base64url without padding (87 characters).
spider.push.PushConfig
pub const PushConfig = struct
The identity the app sends pushes with.
Fields
subject: []const u8: The contact a push service may use, as amailto:orhttps:URL. It goes in thesubclaim.private_key: []const u8: The VAPID private key: the 32 bytes, base64url without padding.public_key: []const u8: The VAPID public key: the 65 bytes, base64url without padding. It is theapplicationServerKeythe page subscribes with, and is sent as it is.
spider.push.PushSubscription
pub const PushSubscription = struct
One browser subscription, with the three values of the browser's PushSubscription.toJSON(). The app stores them when the user subscribes.
Fields
endpoint: []const u8: The push service URL of this subscription.p256dh: []const u8: The browser's public key (keys.p256dh): 65 bytes, base64url without padding.auth: []const u8: The authentication secret (keys.auth): 16 bytes, base64url without padding.
spider.push.WebPush
pub const WebPush = struct
The Web Push sender.
const wp = spider.push.WebPush.initFromEnv();
try wp.sendRaw(arena, io, .{
.endpoint = s.endpoint,
.p256dh = s.p256dh,
.auth = s.auth,
}, payload, 86400);
Fields
config: PushConfig
push.WebPush.init
pub fn init(config: PushConfig) WebPush
A sender with the given keys. config is not copied: its strings must outlive the sender.
push.WebPush.initFromEnv
pub fn initFromEnv() WebPush
A sender configured from the variables VAPID_SUBJECT, VAPID_PRIVATE_KEY and VAPID_PUBLIC_KEY (the environment or .env). A variable that is not set becomes an empty string.
push.WebPush.generateKeys
pub fn generateKeys(io: std.Io) VapidKeys
A new random VAPID key pair. Generate it once and keep it: subscriptions are tied to the public key. keys.privateKeyText() and keys.publicKeyText() are the two values to put in the config.
push.WebPush.sendRaw
pub fn sendRaw(
self: *const WebPush,
arena: std.mem.Allocator,
io: std.Io,
subscription: PushSubscription,
payload: []const u8,
ttl: u32,
) !void
Encrypts payload for subscription and posts it to the subscription's push service, outside a request (a job, a boot hook). ttl is how many seconds the push service keeps the message for a device that is offline. The message is sent with Urgency: high. Everything is allocated in arena.
Fails with error.PushSubscriptionExpired when the service answers 410 (delete the subscription), error.PushForbidden for 403 (the VAPID keys are not the ones the subscription was made with), error.PushSendFailed for any other status that is not 200, 201 or 204, and with the HTTP client's error when the service cannot be reached. error.InvalidKeyLength when p256dh or auth of the subscription, or the private key of the config, does not decode to the size its kind has (65, 16 and 32 bytes; a forged or damaged subscription): nothing is sent. The public key of the config is not checked: it is sent as it is. A key that is not base64url, a p256dh that is not a point of the curve and an endpoint that is not a URL fail too, with the error of the decoder, of the curve or of the URL parser. error.PayloadTooLarge when payload is longer than max_payload_len (3993 bytes): what one push message holds.
push.WebPush.send
pub fn send(
self: *const WebPush,
c: *Ctx,
subscription: PushSubscription,
payload: []const u8,
ttl: u32,
) !void
sendRaw from a handler: it uses the request's arena and Io. Same errors; both log a refused push at warn with the status and the push service (never the whole endpoint).
spider.push.max_payload_len
pub const max_payload_len = 4096 - 86 - 17
The largest payload one push message carries, in bytes. Push services take a body of 4096 bytes at most; 86 of them are the header of the encrypted content and 17 the padding mark and the authentication tag.