NAME
VAPID - Voluntary Application Server Identification
VERSION
Version 1.05
SYNOPSIS
use VAPID qw/all/;
my ($public, $private) = generate_vapid_keys();
# Validate keys
validate_public_key($public);
validate_private_key($private);
# Send a push notification
my $subscription = {
endpoint => $endpoint_from_browser,
keys => {
p256dh => $p256dh_from_browser,
auth => $auth_from_browser
}
};
my $result = send_push_notification(
subscription => $subscription,
payload => 'Hello World!',
vapid_public => $public,
vapid_private => $private,
subject => 'mailto:email@lnation.org',
ttl => 60
);
if ($result->{success}) {
print "Notification sent!\n";
}
# Or build the request manually for more control
my $req = build_push_request(
subscription => $subscription,
payload => 'Hello World!',
vapid_public => $public,
vapid_private => $private,
subject => 'mailto:email@lnation.org'
);
# Or just generate headers (legacy)
my $auth_headers = generate_vapid_header(
'https://updates.push.services.mozilla.com',
'mailto:email@lnation.org',
$public,
$private,
time + 60
);
DESCRIPTION
VAPID, which stands for Voluntary Application Server Identity, is a new way to send and receive website push notifications. Your VAPID keys allow you to send web push campaigns without having to send them through a service like Firebase Cloud Messaging (or FCM). Instead, the application server can voluntarily identify itself to your web push provider.
EXPORT
generate_vapid_keys
Generates vapid private and public keys.
generate_vapid_header
Generates the Authorization and Crypto-Key headers that should be passed when making a request to push a notification.
generate_future_expiration_timestamp
Generates a time that is in future based upon the number of seconds if passed, the default is 12 hours.
validate_subject
Validate the subject.
validate_public_key
Validate the public key.
validate_private_key
Validate the private key.
validate_expiration
Validate the expiration key.
validate_subscription
Validate a push subscription object. Expects a hash reference with:
{
endpoint => 'https://fcm.googleapis.com/...',
keys => {
p256dh => '...',
auth => '...'
}
}
encrypt_payload
Encrypt a message payload for web push using ECDH key agreement and AES-GCM.
my $encrypted = encrypt_payload($message, $subscription);
build_push_request
Build a complete HTTP::Request object for sending a push notification.
my $req = build_push_request(
subscription => $subscription,
payload => 'Hello World',
vapid_public => $public,
vapid_private => $private,
subject => 'mailto:email@example.com',
ttl => 60
);
send_push_notification
Send a push notification and return the result.
my $result = send_push_notification(
subscription => $subscription,
payload => 'Hello World',
vapid_public => $public,
vapid_private => $private,
subject => 'mailto:email@example.com',
ttl => 60
);
if ($result->{success}) {
print "Notification sent!\n";
}
Example
The following is pseudo code but it should get you started.
STEP 1 - generate private and public keys
my ($public, $private) = generate_vapid_keys()
$c->stash({
VAPID_USER_PUBLIC_KEY => $public
});
STEP 2 - main.js
var publicKey = [% VAPID_USER_PUBLIC_KEY %];
navigator.serviceWorker.getRegistrations().then(function (registrations) {
navigator.serviceWorker.register('/service-worker.js').then(function (worker) {
console.log('Service Worker Registered');
worker.pushManager.getSubscription().then(function(sub) {
if (sub === null) {
// Update UI to ask user to register for Push
subscribeUser();
console.log('Not subscribed to push service!');
} else {
// We have a subscription, update the database
console.log('Subscription object: ', sub);
}
});
});
});
function subscribeUser() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.ready.then(function(reg) {
reg.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: publicKey
}).then(function(sub) {
// We have a subscription, update the database
console.log('Endpoint URL: ', sub.endpoint);
}).catch(function(e) {
if (Notification.permission === 'denied') {
console.warn('Permission for notifications was denied');
} else {
console.error('Unable to subscribe to push', e);
}
});
})
}
}
STEP 3 - service-worker.js
self.addEventListener('push', function(e) {
var body;
if (e.data) {
body = e.data.text();
} else {
body = 'Push message no payload';
}
var options = {
body: body,
icon: 'images/notification-flat.png',
vibrate: [100, 50, 100],
data: {
dateOfArrival: Date.now(),
primaryKey: 1
},
};
e.waitUntil(
self.registration.showNotification('Push Notification', options)
);
});
STEP 4 - manifest.json
Required for Chrome; Firefox works even without this file:
{
"short_name" : "Push",
"name" : "Push Dashboard",
"icons" : [
{
"src" : "/icon-144x144.png",
"type" : "image/png",
"sizes" : "144x144"
}
],
"display" : "standalone",
"start_url" : "/",
"background_color" : "#fff",
"theme_color" : "#fff",
"scope" : "/"
}
STEP 5 - send the push notification
Using send_push_notification (recommended):
use VAPID qw/all/;
my $subscription = {
endpoint => $subscription_url,
keys => {
p256dh => $user_p256dh_key,
auth => $user_auth_key
}
};
my $result = send_push_notification(
subscription => $subscription,
payload => 'Hello from VAPID!',
vapid_public => $public,
vapid_private => $private,
subject => 'mailto:email@lnation.org',
ttl => 60
);
if ($result->{success}) {
print "Push message sent successfully.\n";
} else {
print "Push message failed: ", $result->{message}, "\n";
}
STEP 5 (alternative) - build request manually
Using build_push_request for more control:
use VAPID qw/all/;
use LWP::UserAgent;
my $subscription = {
endpoint => $subscription_url,
keys => {
p256dh => $user_p256dh_key,
auth => $user_auth_key
}
};
my $req = build_push_request(
subscription => $subscription,
payload => 'Hello from VAPID!',
vapid_public => $public,
vapid_private => $private,
subject => 'mailto:email@lnation.org',
ttl => 60
);
my $ua = LWP::UserAgent->new;
my $resp = $ua->request($req);
if ($resp->is_success) {
print "Push message sent successfully.\n";
} else {
print "Push message failed: ", $resp->as_string, "\n";
}
STEP 5 (legacy) - generate headers only
For backward compatibility or custom implementations:
my $notification_host = URI->new($subscription_url)->host;
my $auth_headers = generate_vapid_header(
"https://$notification_host",
'mailto:email@lnation.org',
$public,
$private,
time + 60
);
# Then manually construct HTTP request with headers
# Note: This does not encrypt the payload
Curl from the command line (no payload):
curl "{SUBSCRIPTION_URL}" --request POST --header "TTL: 60" --header "Content-Length: 0" --header "Authorization: {AUTHORIZATION_HEADER}" --header "Crypto-Key: {CRYPTO_KEY_HEADER}"
AUTHOR
LNATION, <email at lnation.org>
BUGS
Please report any bugs or feature requests to bug-vapid at rt.cpan.org, or through the web interface at https://rt.cpan.org/NoAuth/ReportBug.html?Queue=VAPID. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.
SUPPORT
You can find documentation for this module with the perldoc command.
perldoc VAPID
You can also look for information at:
RT: CPAN's request tracker (report bugs here)
CPAN Ratings
Search CPAN
ACKNOWLEDGEMENTS
LICENSE AND COPYRIGHT
This software is Copyright (c) 2020 by LNATION.
This is free software, licensed under:
The Artistic License 2.0 (GPL Compatible)