{"id":13484249,"url":"https://github.com/zaru/webpush","last_synced_at":"2025-03-27T16:30:35.753Z","repository":{"id":6242072,"uuid":"55161891","full_name":"zaru/webpush","owner":"zaru","description":"webpush, Encryption Utilities for Web Push protocol","archived":false,"fork":false,"pushed_at":"2022-11-29T17:46:12.000Z","size":174,"stargazers_count":396,"open_issues_count":14,"forks_count":75,"subscribers_count":12,"default_branch":"master","last_synced_at":"2025-03-24T02:57:17.850Z","etag":null,"topics":["notifications","push-notifications","service-worker","webpush"],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/zaru.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-03-31T15:32:51.000Z","updated_at":"2025-03-18T17:11:51.000Z","dependencies_parsed_at":"2023-01-13T13:54:11.003Z","dependency_job_id":null,"html_url":"https://github.com/zaru/webpush","commit_stats":null,"previous_names":[],"tags_count":24,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zaru%2Fwebpush","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zaru%2Fwebpush/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zaru%2Fwebpush/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zaru%2Fwebpush/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/zaru","download_url":"https://codeload.github.com/zaru/webpush/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245882196,"owners_count":20687844,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["notifications","push-notifications","service-worker","webpush"],"created_at":"2024-07-31T17:01:21.364Z","updated_at":"2025-03-27T16:30:35.441Z","avatar_url":"https://github.com/zaru.png","language":"Ruby","readme":"# WebPush\n\n[![Code Climate](https://codeclimate.com/github/zaru/webpush/badges/gpa.svg)](https://codeclimate.com/github/zaru/webpush)\n[![Test Coverage](https://codeclimate.com/github/zaru/webpush/badges/coverage.svg)](https://codeclimate.com/github/zaru/webpush/coverage)\n[![Build Status](https://travis-ci.org/zaru/webpush.svg?branch=master)](https://travis-ci.org/zaru/webpush)\n[![Gem Version](https://badge.fury.io/rb/webpush.svg)](https://badge.fury.io/rb/webpush)\n\nThis 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.\n\nPayload is supported by Chrome 50+, Firefox 48+, Edge 79+.\n\n[webpush Demo app here (building by Sinatra app).](https://github.com/zaru/webpush_demo_ruby)\n\n## Installation\n\nAdd this line to your application's Gemfile:\n\n```ruby\ngem 'webpush'\n```\n\nAnd then execute:\n\n    $ bundle\n\nOr install it yourself as:\n\n    $ gem install webpush\n\n## Usage\n\nSending a web push message to a visitor of your website requires a number of steps:\n\n1. 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.\n2. A `manifest.json` file, linked from your user's page, identifies your app settings.\n3. 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.\n4. Your server uses the `webpush` gem to send a notification with the `subscription` obtained from the client and an optional payload (the message).\n5. 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.\n\n### Generating VAPID keys\n\nUse `webpush` to generate a VAPID key that has both a `public_key` and `private_key` attribute to be saved on the server side.\n\n```ruby\n# One-time, on the server\nvapid_key = Webpush.generate_key\n\n# Save these in your application server settings\nvapid_key.public_key\nvapid_key.private_key\n\n# Or you can save in PEM format if you prefer\nvapid_key.to_pem\n```\n\n### Declaring manifest.json\n\nCheck 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.\n\n```javascript\n{\n  \"name\": \"My Website\"\n}\n```\nFor Chrome web push, add the GCM sender id to a `manifest.json`.\n\n```javascript\n{\n  \"name\": \"My Website\",\n  \"gcm_sender_id\": \"1006629465533\"\n}\n```\n\nThe file is served within the scope of your service worker script, like at the root, and link to it somewhere in the `\u003chead\u003e` tag:\n\n```html\n\u003c!-- index.html --\u003e\n\u003clink rel=\"manifest\" href=\"/manifest.json\" /\u003e\n```\n\n### Installing a service worker\n\nYour application javascript must register a service worker script at an appropriate scope (we're sticking with the root).\n\n```javascript\n// application.js\n// Register the serviceWorker script at /serviceworker.js from your server if supported\nif (navigator.serviceWorker) {\n  navigator.serviceWorker.register('/serviceworker.js')\n  .then(function(reg) {\n     console.log('Service worker change, registered the service worker');\n  });\n}\n// Otherwise, no push notifications :(\nelse {\n  console.error('Service worker is not supported in this browser');\n}\n```\n\n### Subscribing to push notifications\n\n#### With VAPID\n\nThe 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).\n\n```javascript\nwindow.vapidPublicKey = new Uint8Array(\u003c%= Base64.urlsafe_decode64(ENV['VAPID_PUBLIC_KEY']).bytes %\u003e);\n```\n\nYour application javascript uses the `navigator.serviceWorker.pushManager` to subscribe to push notifications, passing the VAPID public key to the subscription settings.\n\n```javascript\n// application.js\n// When serviceWorker is supported, installed, and activated,\n// subscribe the pushManager property with the vapidPublicKey\nnavigator.serviceWorker.ready.then((serviceWorkerRegistration) =\u003e {\n  serviceWorkerRegistration.pushManager\n  .subscribe({\n    userVisibleOnly: true,\n    applicationServerKey: window.vapidPublicKey\n  });\n});\n```\n\n#### Without VAPID\n\nIf 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.\n\n```javascript\n// application.js\n// When serviceWorker is supported, installed, and activated,\n// subscribe the pushManager property with the vapidPublicKey\nnavigator.serviceWorker.ready.then((serviceWorkerRegistration) =\u003e {\n  serviceWorkerRegistration.pushManager\n  .subscribe({\n    userVisibleOnly: true\n  });\n});\n```\n\n### Triggering a web push notification\n\nHook 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.\n\n```javascript\n// application.js\n// Send the subscription and message from the client for the backend\n// to set up a push notification\n$(\".webpush-button\").on(\"click\", (e) =\u003e {\n  navigator.serviceWorker.ready\n  .then((serviceWorkerRegistration) =\u003e {\n    serviceWorkerRegistration.pushManager.getSubscription()\n    .then((subscription) =\u003e {\n      $.post(\"/push\", { subscription: subscription.toJSON(), message: \"You clicked a button!\" });\n    });\n  });\n});\n```\n\nImagine a Ruby app endpoint that responds to the request by triggering notification through the `webpush` gem.\n\n```ruby\n# app.rb\n# Use the webpush gem API to deliver a push notiifcation merging\n# the message, subscription values, and vapid options\npost \"/push\" do\n  Webpush.payload_send(\n    message: params[:message],\n    endpoint: params[:subscription][:endpoint],\n    p256dh: params[:subscription][:keys][:p256dh],\n    auth: params[:subscription][:keys][:auth],\n    vapid: {\n      subject: \"mailto:sender@example.com\",\n      public_key: ENV['VAPID_PUBLIC_KEY'],\n      private_key: ENV['VAPID_PRIVATE_KEY']\n    },\n    ssl_timeout: 5, # value for Net::HTTP#ssl_timeout=, optional\n    open_timeout: 5, # value for Net::HTTP#open_timeout=, optional\n    read_timeout: 5 # value for Net::HTTP#read_timeout=, optional\n  )\nend\n```\n\nNote: the VAPID options should be omitted if the client-side subscription was\ngenerated without the `applicationServerKey` parameter described earlier. You\nwould instead pass the GCM api key along with the api request as shown in the\nUsage section below.\n\n### Receiving the push event\n\nYour `/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.\n\n```javascript\n// serviceworker.js\n// The serviceworker context can respond to 'push' events and trigger\n// notifications on the registration property\nself.addEventListener(\"push\", (event) =\u003e {\n  let title = (event.data \u0026\u0026 event.data.text()) || \"Yay a message\";\n  let body = \"We have received a push message\";\n  let tag = \"push-simple-demo-notification-tag\";\n  let icon = '/assets/my-logo-120x120.png';\n\n  event.waitUntil(\n    self.registration.showNotification(title, { body, icon, tag })\n  )\n});\n```\n\nBefore 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.\n\n```javascript\n// application.js\n\n// Let's check if the browser supports notifications\nif (!(\"Notification\" in window)) {\n  console.error(\"This browser does not support desktop notification\");\n}\n\n// Let's check whether notification permissions have already been granted\nelse if (Notification.permission === \"granted\") {\n  console.log(\"Permission to receive notifications has been granted\");\n}\n\n// Otherwise, we need to ask the user for permission\nelse if (Notification.permission !== 'denied') {\n  Notification.requestPermission(function (permission) {\n    // If the user accepts, let's create a notification\n    if (permission === \"granted\") {\n      console.log(\"Permission to receive notifications has been granted\");\n    }\n  });\n}\n```\n\nIf everything worked, you should see a desktop notification triggered via web\npush. Yay!\n\nNote: 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.\n\n## API\n\n### With a payload\n\n```ruby\nmessage = {\n  title: \"title\",\n  body: \"body\",\n  icon: \"http://example.com/icon.pn\"\n}\n\nWebpush.payload_send(\n  endpoint: \"https://fcm.googleapis.com/gcm/send/eah7hak....\",\n  message: JSON.generate(message),\n  p256dh: \"BO/aG9nYXNkZmFkc2ZmZHNmYWRzZmFl...\",\n  auth: \"aW1hcmthcmFpa3V6ZQ==\",\n  ttl: 600, # optional, ttl in seconds, defaults to 2419200 (4 weeks)\n  urgency: 'normal' # optional, it can be very-low, low, normal, high, defaults to normal\n)\n```\n\n### Without a payload\n\n```ruby\nWebpush.payload_send(\n  endpoint: \"https://fcm.googleapis.com/gcm/send/eah7hak....\",\n  p256dh: \"BO/aG9nYXNkZmFkc2ZmZHNmYWRzZmFl...\",\n  auth: \"aW1hcmthcmFpa3V6ZQ==\"\n)\n```\n\n### With VAPID\n\nVAPID details are given as a hash with `:subject`, `:public_key`, and\n`: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`.\n\n```ruby\nWebpush.payload_send(\n  endpoint: \"https://fcm.googleapis.com/gcm/send/eah7hak....\",\n  message: \"A message\",\n  p256dh: \"BO/aG9nYXNkZmFkc2ZmZHNmYWRzZmFl...\",\n  auth: \"aW1hcmthcmFpa3V6ZQ==\",\n  vapid: {\n    subject: \"mailto:sender@example.com\",\n    public_key: ENV['VAPID_PUBLIC_KEY'],\n    private_key: ENV['VAPID_PRIVATE_KEY']\n  }\n)\n```\n\n### With VAPID in PEM format\n\nThis library also supports the PEM format for the VAPID keys:\n\n```ruby\nWebpush.payload_send(\n  endpoint: \"https://fcm.googleapis.com/gcm/send/eah7hak....\",\n  message: \"A message\",\n  p256dh: \"BO/aG9nYXNkZmFkc2ZmZHNmYWRzZmFl...\",\n  auth: \"aW1hcmthcmFpa3V6ZQ==\",\n  vapid: {\n    subject: \"mailto:sender@example.com\"\n    pem: ENV['VAPID_KEYS']\n  }\n)\n```\n\n### With GCM api key\n\n```ruby\nWebpush.payload_send(\n  endpoint: \"https://fcm.googleapis.com/gcm/send/eah7hak....\",\n  message: \"A message\",\n  p256dh: \"BO/aG9nYXNkZmFkc2ZmZHNmYWRzZmFl...\",\n  auth: \"aW1hcmthcmFpa3V6ZQ==\",\n  api_key: \"\u003cGCM API KEY\u003e\"\n)\n```\n\n### ServiceWorker sample\n\nsee. https://github.com/zaru/web-push-sample\n\np256dh and auth generate sample code.\n\n```javascript\nnavigator.serviceWorker.ready.then(function(sw) {\n  Notification.requestPermission(function(permission) {\n    if(permission !== 'denied') {\n      sw.pushManager.subscribe({userVisibleOnly: true}).then(function(s) {\n        var data = {\n          endpoint: s.endpoint,\n          p256dh: btoa(String.fromCharCode.apply(null, new Uint8Array(s.getKey('p256dh')))).replace(/\\+/g, '-').replace(/\\//g, '_'),\n          auth: btoa(String.fromCharCode.apply(null, new Uint8Array(s.getKey('auth')))).replace(/\\+/g, '-').replace(/\\//g, '_')\n        }\n        console.log(data);\n      });\n    }\n  });\n});\n```\n\npayloads received sample code.\n\n```javascript\nself.addEventListener(\"push\", function(event) {\n  var json = event.data.json();\n  self.registration.showNotification(json.title, {\n    body: json.body,\n    icon: json.icon\n  });\n});\n```\n\n## Contributing\n\nBug reports and pull requests are welcome on GitHub at https://github.com/zaru/webpush.\n","funding_links":[],"categories":["Push Notification","Notifications","Mobile Development"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzaru%2Fwebpush","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzaru%2Fwebpush","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzaru%2Fwebpush/lists"}