The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

HTTP::Request::Webpush - HTTP Request for web push notifications

VERSION

version 0.15

SYNOPSIS

 use HTTP::Request::Webpush;

 #This should be the application-wide VAPID key pair
 #The APP_PUB part must be the same used by the user UA when requesting the subscription
 use constant APP_PUB => 'BCAI...RA8';
 use constant APP_KEY => 'M6x...UQTow';
 
 #This should be previously collected from an already subscribed user UA
 my $subscription='{"endpoint":"https://foo/fooer","expirationTime":null,"keys":{"p256dh":"BCNS...","auth":"dZ..."}}';

 my $message=HTTP::Request::Webpush->new();
 $message->auth(APP_PUB, APP_KEY);
 $message->subscription($subscription);
 $message->subject('mailto:bobsbeverage@some.com');
 $message->content('Hello world');
 
 #Additional headers can be applied with inherited HTTP::Response methods
 $message->header('TTL' => '90');

 #To send a single push message
 $message->encode();
 my $ua = LWP::UserAgent->new;
 my $response = $ua->request($message);

 #To send a batch of messages using the same application's end encryption key
 my $ecc = Crypt::PK::ECC->new();
 $ecc->generate_key('prime256v1');
 
 for (@cleverly_stored_subscriptions) {
    my $message=HTTP::Request::Webpush->new(reuseecc => $ecc);
    $message->subscription($_);
    $message->subject('mailto:bobsbeverage@some.com');
    $message->content('Come taste our new pale ale brand');
    $message->encode();
    my $response = $ua->request($message);
 }
 

DESCRIPTION

HTTP::Request::Webpush produces an HTTP::Request for Application-side Webpush notifications as described on RFC8291. Such requests can then be submitted to the push message channel so they will pop-up in the corresponding end user host. In this scheme, an Application is a server-side component that sends push notification to previously subscribed browser worker(s). This class only covers the Application role. A lot must be done on the browser side to setup a full working push notification system.

In practical terms, this class is a glue for all the encryption steps involved in setting up a RFC8291 message, along with the RFC8292 VAPID scheme.

$r=HTTP::Request::Webpush->new()
$r=HTTP::Request::Webpush->new(auth => $my_key, subscription => $my_subs, content='New lager batch arrived')

The following options can be supplied in the constructor: subscription, auth, reuseecc, subject, content.

$r->subscription($hash_reference)
$r->subscription('{"endpoint":"https://foo/fooer","expirationTime":null,"keys":{"p256dh":"BCNS...","auth":"dZ..."}}');

This sets the subscription object related to this notification service. This should be the same object returned inside the browser environment using the browser's Push API PushManager.subscribe() method. The argument can be either a JSON string or a previously setup hash reference. The HTTP::Request uri is taken verbatim from the endpoint of the subscription object.

$r->auth($pk) #pk being a Crypt::PK::ECC ref
$r->auth($pub_bin, $priv_bin)
$r->authbase64('BCAI...jARA8','M6...Tow')

This sets the authentication key for the VAPID authentication scheme related to this push service. This can either be a (public, private) pair or an already setup Crypt::PK::ECC object. The public part must be the same used earlier in the browser environment in the PushManager.subscribe() applicationServerKey option. The key pair can be passed as URL safe base64 strings using the authbase64() variant.

$r->reuseecc($ecc) #ecc being a Crypt::PK::ECC ref

By default, HTTP::Request::Webpush creates a new P-256 key pair for the encryption step each time. In large push batches this can be time consuming. You can reuse the same previously setup key pair in repeated messages using this method.

$r->subject('mailto:jdoe@some.com')

This establish the contact information related to the origin of the push service. This method isn't enforced since RFC8292 mentions this as a SHOULD practice. But, if a valid contact information is not included, the browser push service is likely to bounce the message. The URI passed is used as the 'sub' claim in the authentication JWT.

$r->content('Try our new draft beer')

This sets the unencripted message content, or the payload in terms of RFC8291. This is actually inherited from HTTP::Message, as well as other methods that can be used to set the message content.

$r->encode('aes128gcm')

This does the encryption process, as well as setting the headers expected by the push service. aes128gcm is the only acceptable argument. Before calling this, the subscription and auth must be supplied. You must call this method before submitting the message, otherwise the encryption process won't happen.

Please note that encode() and content() are inherited from HTTP::Message.

REMARKS

No backward decryption is provided by this class, so the decoded_content() and decode() methods will fail.

After you encode(), the content can still be accessed through HTTP::Message standard methods and it will be the binary body of the encrypted message.

This class sets the following headers: Authorization, Crypto-Key, Content-Length, Content-Type, Content-Encoding. Additional headers might be added using the HTTP::Message::header() method. Please note that the browser push service will likely bounce the message if TTL is missing. The standard also states that an Urgency header might apply.

REFERENCES

This class relies on Digest::SHA for the HKDF derivation, Crypt::AuthEnc::GCM for the encryption itself, Crypt::PK::ECC for key management and Crypt::PRNG for the salt.

RFC8291 establish the encription steps: https://tools.ietf.org/html/rfc8291

RFC8292 establish the VAPID scheme: https://tools.ietf.org/html/rfc8292

RFC8030 covers the whole HTTP push life cycle https://tools.ietf.org/html/rfc8030

The following code samples and tutorials were very useful:

https://developers.google.com/web/updates/2016/03/web-push-encryption

https://developers.google.com/web/fundamentals/push-notifications/web-push-protocol

https://adiary.adiary.jp/0391

AUTHOR

Erich Strelow <estrelow@cpan.org>

COPYRIGHT AND LICENSE

Copyright 2021 Erich Strelow

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.