The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.

NAME

VAPID - Voluntary Application Server Identification

VERSION

Version 1.01

SYNOPSIS

use VAPID qw/generate/;

my ($public, $private) = generate_vapid_keys()

validate_public_key($public);
validate_private_key($private);

...

my $auth_headers = generate_vapid_header(
	'https://updates.push.services.mozilla.com',
	'mailto:email@lnation.org',
	$public,
	$private,
	time + 60,
	$enc
);

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.

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 - generate headers

my $notificaiton_host = URI->new($subscription_url)->host;
my $auth_headers = generate_vapid_header(
	"https://$notification_host",
	'mailto:email@lnation.org',
	$public,
	$private,
	time + 60
);

STEP 6 - POST the push message

Curl from the command line:

curl "{SUBSCRIPTION_URL}" --request POST --header "TTL: 60" --header "Content-Length: 0" --header "Authorization: {AUTHORIZATION_HEADER}" --header "Crypto-Key: {CRYPTO_KEY_HEADER}"

or Perl with LWP:

use LWP::UserAgent;

my $req = HTTP::Request->new(POST => $subscription_url);
$req->header(TTL => 60);
$req->header('Authorization' => $auth_headers->{Authorization});
$req->header('Crypto-Key' => $auth_headers->{'Crypto-Key'});

my $ua = LWP::UserAgent->new;
my $resp = $ua->request($req);

if ($resp->is_success) {
	print "Push message sent out successfully.\n";
} else {
	print "Push message did not get through:\n", $resp->as_string, "\n";
}

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:

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)