README.md in web-push-1.0.0 vs README.md in web-push-2.0.0

- old
+ new

@@ -1,47 +1,37 @@ # 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) +[![Gem Version](https://badge.fury.io/rb/web-push.svg)](https://badge.fury.io/rb/web-push) +![Build Status](https://github.com/pushpad/web-push/workflows/CI/badge.svg) -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. +This gem makes it possible to send push messages to web browsers from Ruby backends using the [Web Push Protocol](https://datatracker.ietf.org/doc/html/rfc8030). It supports [Message Encryption for Web Push](https://datatracker.ietf.org/doc/html/rfc8291) and [VAPID](https://datatracker.ietf.org/doc/html/rfc8292). -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: +Add this line to the Gemfile: ```ruby gem 'web-push' ``` -And then execute: +Or install the gem: - $ bundle +```console +$ gem install web-push +``` -Or install it yourself as: - - $ gem install web-push - ## 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 `web-push` 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. +1. 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, which creates a `subscription` JSON object on the client side. +2. Your server uses the `web-push` gem to send a notification with the `subscription` obtained from the client and an optional payload (the message). +3. 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 `web-push` to generate a VAPID key that has both a `public_key` and `private_key` attribute to be saved on the server side. +Use `web-push` to generate a VAPID key pair (that has both a `public_key` and `private_key`) and save it on the server side. ```ruby # One-time, on the server vapid_key = WebPush.generate_key @@ -51,68 +41,29 @@ # 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 `<head>` tag: - -```html -<!-- index.html --> -<link rel="manifest" href="/manifest.json" /> -``` - ### Installing a service worker -Your application javascript must register a service worker script at an appropriate scope (we're sticking with the root). +Your application must use JavaScript to register a service worker script at an appropriate scope (root is recommended). ```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'); -} +navigator.serviceWorker.register('/service-worker.js') ``` ### 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. -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. +Your JavaScript code uses the `pushManager` interface 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({ @@ -120,134 +71,63 @@ 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. +In order to send web push notifications, the push subscription must be stored in the backend. Get the subscription with `pushManager.getSubscription()` and store it in your database. -```javascript -// application.js -// Send the subscription and message from the client for the backend -// to set up a push notification -$(".web-push-button").on("click", (e) => { - navigator.serviceWorker.ready - .then((serviceWorkerRegistration) => { - serviceWorkerRegistration.pushManager.getSubscription() - .then((subscription) => { - $.post("/push", { subscription: subscription.toJSON(), message: "You clicked a button!" }); - }); - }); -}); -``` +Then you can use this gem to send web push messages: -Imagine a Ruby app endpoint that responds to the request by triggering notification through the `web-push` gem. - ```ruby -# app.rb -# Use the web-push 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:sender@example.com", - 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 +WebPush.payload_send( + message: message, + endpoint: subscription['endpoint'], + p256dh: subscription['keys']['p256dh'], + auth: subscription['keys']['auth'], + vapid: { + subject: "mailto:sender@example.com", + public_key: ENV['VAPID_PUBLIC_KEY'], + private_key: ENV['VAPID_PRIVATE_KEY'] + }, + ssl_timeout: 5, # optional value for Net::HTTP#ssl_timeout= + open_timeout: 5, # optional value for Net::HTTP#open_timeout= + read_timeout: 5 # optional value for Net::HTTP#read_timeout= +) ``` -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. +Your `service-worker.js` script should 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 }) - ) +self.addEventListener('push', (event) => { + // Get the push message + var message = event.data; + // Display a notification + event.waitUntil(self.registration.showNotification('Example')); }); ``` -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. +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. Use something like this in your JavaScript code: ```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"); - } - }); -} +Notification.requestPermission(); ``` -If everything worked, you should see a desktop notification triggered via web -push. Yay! +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" + title: "Example", + body: "Hello, world!", + icon: "https://example.com/icon.png" } WebPush.payload_send( endpoint: "https://fcm.googleapis.com/gcm/send/eah7hak....", message: JSON.generate(message), @@ -302,55 +182,12 @@ pem: ENV['VAPID_KEYS'] } ) ``` -### With GCM api key +## Contributing -```ruby -WebPush.payload_send( - endpoint: "https://fcm.googleapis.com/gcm/send/eah7hak....", - message: "A message", - p256dh: "BO/aG9nYXNkZmFkc2ZmZHNmYWRzZmFl...", - auth: "aW1hcmthcmFpa3V6ZQ==", - api_key: "<GCM API KEY>" -) -``` +Bug reports and pull requests are welcome on GitHub at https://github.com/pushpad/web-push. -### ServiceWorker sample +## Credits -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. +This library is a fork of [zaru/webpush](https://github.com/zaru/webpush) actively maintained by [Pushpad](https://pushpad.xyz) with many improvements, bug fixes and frequent updates.