Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/zaru/webpush
webpush, Encryption Utilities for Web Push protocol
https://github.com/zaru/webpush
notifications push-notifications service-worker webpush
Last synced: 17 days ago
JSON representation
webpush, Encryption Utilities for Web Push protocol
- Host: GitHub
- URL: https://github.com/zaru/webpush
- Owner: zaru
- License: mit
- Created: 2016-03-31T15:32:51.000Z (over 8 years ago)
- Default Branch: master
- Last Pushed: 2022-11-29T17:46:12.000Z (almost 2 years ago)
- Last Synced: 2024-10-01T07:11:55.357Z (about 2 months ago)
- Topics: notifications, push-notifications, service-worker, webpush
- Language: Ruby
- Homepage:
- Size: 170 KB
- Stars: 393
- Watchers: 13
- Forks: 73
- Open Issues: 14
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
- awesome-ruby - webpush - Encryption Utilities for Web Push protocol (Mobile Development)
README
# WebPush
[![Code Climate](https://codeclimate.com/github/zaru/webpush/badges/gpa.svg)](https://codeclimate.com/github/zaru/webpush)
[![Test Coverage](https://codeclimate.com/github/zaru/webpush/badges/coverage.svg)](https://codeclimate.com/github/zaru/webpush/coverage)
[![Build Status](https://travis-ci.org/zaru/webpush.svg?branch=master)](https://travis-ci.org/zaru/webpush)
[![Gem Version](https://badge.fury.io/rb/webpush.svg)](https://badge.fury.io/rb/webpush)This gem makes it possible to send push messages to web browsers from Ruby backends using the [Web Push Protocol](https://tools.ietf.org/html/draft-ietf-webpush-protocol-10). It supports [Message Encryption for Web Push](https://tools.ietf.org/html/draft-ietf-webpush-encryption) to send messages securely from server to user agent.
Payload is supported by Chrome 50+, Firefox 48+, Edge 79+.
[webpush Demo app here (building by Sinatra app).](https://github.com/zaru/webpush_demo_ruby)
## Installation
Add this line to your application's Gemfile:
```ruby
gem 'webpush'
```And then execute:
$ bundle
Or install it yourself as:
$ gem install webpush
## Usage
Sending a web push message to a visitor of your website requires a number of steps:
1. Your server has (optionally) generated (one-time) a set of [Voluntary Application server Identification (VAPID)](https://tools.ietf.org/html/draft-ietf-webpush-vapid-01) keys. Otherwise, to send messages through Chrome, you have registered your site through the [Google Developer Console](https://console.developers.google.com/) and have obtained a GCM sender id and GCM API key from your app settings.
2. A `manifest.json` file, linked from your user's page, identifies your app settings.
3. Also in the user's web browser, a `serviceWorker` is installed and activated and its `pushManager` property is subscribed to push events with your VAPID public key, with creates a `subscription` JSON object on the client side.
4. Your server uses the `webpush` gem to send a notification with the `subscription` obtained from the client and an optional payload (the message).
5. Your service worker is set up to receive `'push'` events. To trigger a desktop notification, the user has accepted the prompt to receive notifications from your site.### Generating VAPID keys
Use `webpush` to generate a VAPID key that has both a `public_key` and `private_key` attribute to be saved on the server side.
```ruby
# One-time, on the server
vapid_key = Webpush.generate_key# Save these in your application server settings
vapid_key.public_key
vapid_key.private_key# Or you can save in PEM format if you prefer
vapid_key.to_pem
```### Declaring manifest.json
Check out the [Web Manifest docs](https://developer.mozilla.org/en-US/docs/Web/Manifest) for details on what to include in your `manifest.json` file. If using VAPID, no app credentials are needed.
```javascript
{
"name": "My Website"
}
```
For Chrome web push, add the GCM sender id to a `manifest.json`.```javascript
{
"name": "My Website",
"gcm_sender_id": "1006629465533"
}
```The file is served within the scope of your service worker script, like at the root, and link to it somewhere in the `` tag:
```html
```
### Installing a service worker
Your application javascript must register a service worker script at an appropriate scope (we're sticking with the root).
```javascript
// application.js
// Register the serviceWorker script at /serviceworker.js from your server if supported
if (navigator.serviceWorker) {
navigator.serviceWorker.register('/serviceworker.js')
.then(function(reg) {
console.log('Service worker change, registered the service worker');
});
}
// Otherwise, no push notifications :(
else {
console.error('Service worker is not supported in this browser');
}
```### Subscribing to push notifications
#### With VAPID
The VAPID public key you generated earlier is made available to the client as a `UInt8Array`. To do this, one way would be to expose the urlsafe-decoded bytes from Ruby to JavaScript when rendering the HTML template. (Global variables used here for simplicity).
```javascript
window.vapidPublicKey = new Uint8Array(<%= Base64.urlsafe_decode64(ENV['VAPID_PUBLIC_KEY']).bytes %>);
```Your application javascript uses the `navigator.serviceWorker.pushManager` to subscribe to push notifications, passing the VAPID public key to the subscription settings.
```javascript
// application.js
// When serviceWorker is supported, installed, and activated,
// subscribe the pushManager property with the vapidPublicKey
navigator.serviceWorker.ready.then((serviceWorkerRegistration) => {
serviceWorkerRegistration.pushManager
.subscribe({
userVisibleOnly: true,
applicationServerKey: window.vapidPublicKey
});
});
```#### Without VAPID
If you will not be sending VAPID details, then there is no need generate VAPID keys, and the `applicationServerKey` parameter may be omitted from the `pushManager.subscribe` call.
```javascript
// application.js
// When serviceWorker is supported, installed, and activated,
// subscribe the pushManager property with the vapidPublicKey
navigator.serviceWorker.ready.then((serviceWorkerRegistration) => {
serviceWorkerRegistration.pushManager
.subscribe({
userVisibleOnly: true
});
});
```### Triggering a web push notification
Hook into an client-side or backend event in your app to deliver a push message. The server must be made aware of the `subscription`. In the example below, we send the JSON generated subscription object to our backend at the "/push" endpoint with a message.
```javascript
// application.js
// Send the subscription and message from the client for the backend
// to set up a push notification
$(".webpush-button").on("click", (e) => {
navigator.serviceWorker.ready
.then((serviceWorkerRegistration) => {
serviceWorkerRegistration.pushManager.getSubscription()
.then((subscription) => {
$.post("/push", { subscription: subscription.toJSON(), message: "You clicked a button!" });
});
});
});
```Imagine a Ruby app endpoint that responds to the request by triggering notification through the `webpush` gem.
```ruby
# app.rb
# Use the webpush gem API to deliver a push notiifcation merging
# the message, subscription values, and vapid options
post "/push" do
Webpush.payload_send(
message: params[:message],
endpoint: params[:subscription][:endpoint],
p256dh: params[:subscription][:keys][:p256dh],
auth: params[:subscription][:keys][:auth],
vapid: {
subject: "mailto:[email protected]",
public_key: ENV['VAPID_PUBLIC_KEY'],
private_key: ENV['VAPID_PRIVATE_KEY']
},
ssl_timeout: 5, # value for Net::HTTP#ssl_timeout=, optional
open_timeout: 5, # value for Net::HTTP#open_timeout=, optional
read_timeout: 5 # value for Net::HTTP#read_timeout=, optional
)
end
```Note: the VAPID options should be omitted if the client-side subscription was
generated without the `applicationServerKey` parameter described earlier. You
would instead pass the GCM api key along with the api request as shown in the
Usage section below.### Receiving the push event
Your `/serviceworker.js` script may respond to `'push'` events. One action it can take is to trigger desktop notifications by calling `showNotification` on the `registration` property.
```javascript
// serviceworker.js
// The serviceworker context can respond to 'push' events and trigger
// notifications on the registration property
self.addEventListener("push", (event) => {
let title = (event.data && event.data.text()) || "Yay a message";
let body = "We have received a push message";
let tag = "push-simple-demo-notification-tag";
let icon = '/assets/my-logo-120x120.png';event.waitUntil(
self.registration.showNotification(title, { body, icon, tag })
)
});
```Before the notifications can be displayed, the user must grant permission for [notifications](https://developer.mozilla.org/en-US/docs/Web/API/notification) in a browser prompt, using something like the example below.
```javascript
// application.js// Let's check if the browser supports notifications
if (!("Notification" in window)) {
console.error("This browser does not support desktop notification");
}// Let's check whether notification permissions have already been granted
else if (Notification.permission === "granted") {
console.log("Permission to receive notifications has been granted");
}// Otherwise, we need to ask the user for permission
else if (Notification.permission !== 'denied') {
Notification.requestPermission(function (permission) {
// If the user accepts, let's create a notification
if (permission === "granted") {
console.log("Permission to receive notifications has been granted");
}
});
}
```If everything worked, you should see a desktop notification triggered via web
push. Yay!Note: if you're using Rails, check out [serviceworker-rails](https://github.com/rossta/serviceworker-rails), a gem that makes it easier to host serviceworker scripts and manifest.json files at canonical endpoints (i.e., non-digested URLs) while taking advantage of the asset pipeline.
## API
### With a payload
```ruby
message = {
title: "title",
body: "body",
icon: "http://example.com/icon.pn"
}Webpush.payload_send(
endpoint: "https://fcm.googleapis.com/gcm/send/eah7hak....",
message: JSON.generate(message),
p256dh: "BO/aG9nYXNkZmFkc2ZmZHNmYWRzZmFl...",
auth: "aW1hcmthcmFpa3V6ZQ==",
ttl: 600, # optional, ttl in seconds, defaults to 2419200 (4 weeks)
urgency: 'normal' # optional, it can be very-low, low, normal, high, defaults to normal
)
```### Without a payload
```ruby
Webpush.payload_send(
endpoint: "https://fcm.googleapis.com/gcm/send/eah7hak....",
p256dh: "BO/aG9nYXNkZmFkc2ZmZHNmYWRzZmFl...",
auth: "aW1hcmthcmFpa3V6ZQ=="
)
```### With VAPID
VAPID details are given as a hash with `:subject`, `:public_key`, and
`:private_key`. The `:subject` is a contact URI for the application server as either a "mailto:" or an "https:" address. The `:public_key` and `:private_key` should be passed as the base64-encoded values generated with `Webpush.generate_key`.```ruby
Webpush.payload_send(
endpoint: "https://fcm.googleapis.com/gcm/send/eah7hak....",
message: "A message",
p256dh: "BO/aG9nYXNkZmFkc2ZmZHNmYWRzZmFl...",
auth: "aW1hcmthcmFpa3V6ZQ==",
vapid: {
subject: "mailto:[email protected]",
public_key: ENV['VAPID_PUBLIC_KEY'],
private_key: ENV['VAPID_PRIVATE_KEY']
}
)
```### With VAPID in PEM format
This library also supports the PEM format for the VAPID keys:
```ruby
Webpush.payload_send(
endpoint: "https://fcm.googleapis.com/gcm/send/eah7hak....",
message: "A message",
p256dh: "BO/aG9nYXNkZmFkc2ZmZHNmYWRzZmFl...",
auth: "aW1hcmthcmFpa3V6ZQ==",
vapid: {
subject: "mailto:[email protected]"
pem: ENV['VAPID_KEYS']
}
)
```### With GCM api key
```ruby
Webpush.payload_send(
endpoint: "https://fcm.googleapis.com/gcm/send/eah7hak....",
message: "A message",
p256dh: "BO/aG9nYXNkZmFkc2ZmZHNmYWRzZmFl...",
auth: "aW1hcmthcmFpa3V6ZQ==",
api_key: ""
)
```### ServiceWorker sample
see. https://github.com/zaru/web-push-sample
p256dh and auth generate sample code.
```javascript
navigator.serviceWorker.ready.then(function(sw) {
Notification.requestPermission(function(permission) {
if(permission !== 'denied') {
sw.pushManager.subscribe({userVisibleOnly: true}).then(function(s) {
var data = {
endpoint: s.endpoint,
p256dh: btoa(String.fromCharCode.apply(null, new Uint8Array(s.getKey('p256dh')))).replace(/\+/g, '-').replace(/\//g, '_'),
auth: btoa(String.fromCharCode.apply(null, new Uint8Array(s.getKey('auth')))).replace(/\+/g, '-').replace(/\//g, '_')
}
console.log(data);
});
}
});
});
```payloads received sample code.
```javascript
self.addEventListener("push", function(event) {
var json = event.data.json();
self.registration.showNotification(json.title, {
body: json.body,
icon: json.icon
});
});
```## Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/zaru/webpush.