web-push 1.0.0 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a165b29c06bb5d9a9aab0b65de25bd913ef7926909773485e97f0778bda4299d
4
- data.tar.gz: c3f9727392a4049e27ac6bf83718749d5110cd290fa37718ee5b988d2c9c16e3
3
+ metadata.gz: a688b354339ddf4f82d8febb54a2042bb432357dffd77a571c3c9212c107aa2f
4
+ data.tar.gz: 01060b2687a57217cafb2cf96b3925c6e2e39102daf6f796277d06d767ae6a0e
5
5
  SHA512:
6
- metadata.gz: ca5db1201aa140ed1abe235d217183270b8b171d8b368ea7b5392c9cb04a2bf923c1c0e468788e1c2846dbd5c38bc32af53d427dbf307193ccaa00efbf6dd7d6
7
- data.tar.gz: 8f179fd84cc0bd0f9e4899353d95af62e9e716552ae0bec86cdc7be2782056fcfb08b06a7efd3297e7468f1bb72ab6f5b16a29e31fc5ed0eb615117e790b8885
6
+ metadata.gz: 2386c18e4fef78c027b2a5dee6a1778dc82d43cf26dcf0468e83feb8ff972eca1b743c87d68612fffee8ea9feb05329bc4a06127599643de72d922b465aca776
7
+ data.tar.gz: b023139054b433c36d32d5a91465e8d0082231a6c7c2e8f0ff79825599457c28b57e51ffcabdf21e7fb03ab5e63a28750c00fddfea74204c3f8da640fd9ffe38
@@ -0,0 +1,23 @@
1
+ name: CI
2
+ on:
3
+ push:
4
+ branches: [ master ]
5
+ pull_request:
6
+ branches: [ master ]
7
+ jobs:
8
+ test:
9
+ strategy:
10
+ fail-fast: false
11
+ matrix:
12
+ os: [ubuntu-20.04, ubuntu-22.04]
13
+ ruby-version: ['3.0', '3.1', '3.2']
14
+ runs-on: ${{ matrix.os }}
15
+ steps:
16
+ - uses: actions/checkout@v3
17
+ - name: Set up Ruby
18
+ uses: ruby/setup-ruby@v1
19
+ with:
20
+ ruby-version: ${{ matrix.ruby-version }}
21
+ bundler-cache: true
22
+ - name: Run tests
23
+ run: bundle exec rspec
data/.gitignore CHANGED
@@ -1,10 +1,2 @@
1
- /.bundle/
2
- /.yardoc
3
1
  /Gemfile.lock
4
- /_yardoc/
5
- /coverage/
6
- /doc/
7
- /pkg/
8
- /spec/reports/
9
- /tmp/
10
- .idea/
2
+ /coverage
data/README.md CHANGED
@@ -1,45 +1,37 @@
1
1
  # WebPush
2
2
 
3
- [![Code Climate](https://codeclimate.com/github/zaru/webpush/badges/gpa.svg)](https://codeclimate.com/github/zaru/webpush)
4
- [![Test Coverage](https://codeclimate.com/github/zaru/webpush/badges/coverage.svg)](https://codeclimate.com/github/zaru/webpush/coverage)
5
- [![Build Status](https://travis-ci.org/zaru/webpush.svg?branch=master)](https://travis-ci.org/zaru/webpush)
6
- [![Gem Version](https://badge.fury.io/rb/webpush.svg)](https://badge.fury.io/rb/webpush)
3
+ [![Gem Version](https://badge.fury.io/rb/web-push.svg)](https://badge.fury.io/rb/web-push)
4
+ ![Build Status](https://github.com/pushpad/web-push/workflows/CI/badge.svg)
7
5
 
8
- 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.
6
+ 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).
9
7
 
10
- Payload is supported by Chrome 50+, Firefox 48+, Edge 79+.
11
-
12
- [webpush Demo app here (building by Sinatra app).](https://github.com/zaru/webpush_demo_ruby)
8
+ **Note**: This is an open source gem for Web Push. If you want to send web push notifications from Ruby using Pushpad, you need to use another gem ([pushpad gem](https://github.com/pushpad/pushpad-ruby)).
13
9
 
14
10
  ## Installation
15
11
 
16
- Add this line to your application's Gemfile:
12
+ Add this line to the Gemfile:
17
13
 
18
14
  ```ruby
19
15
  gem 'web-push'
20
16
  ```
21
17
 
22
- And then execute:
23
-
24
- $ bundle
25
-
26
- Or install it yourself as:
18
+ Or install the gem:
27
19
 
28
- $ gem install web-push
20
+ ```console
21
+ $ gem install web-push
22
+ ```
29
23
 
30
24
  ## Usage
31
25
 
32
26
  Sending a web push message to a visitor of your website requires a number of steps:
33
27
 
34
- 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.
35
- 2. A `manifest.json` file, linked from your user's page, identifies your app settings.
36
- 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.
37
- 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).
38
- 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.
28
+ 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.
29
+ 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).
30
+ 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.
39
31
 
40
32
  ### Generating VAPID keys
41
33
 
42
- 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.
34
+ 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.
43
35
 
44
36
  ```ruby
45
37
  # One-time, on the server
@@ -53,64 +45,25 @@ vapid_key.private_key
53
45
  vapid_key.to_pem
54
46
  ```
55
47
 
56
- ### Declaring manifest.json
57
-
58
- 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.
59
-
60
- ```javascript
61
- {
62
- "name": "My Website"
63
- }
64
- ```
65
- For Chrome web push, add the GCM sender id to a `manifest.json`.
66
-
67
- ```javascript
68
- {
69
- "name": "My Website",
70
- "gcm_sender_id": "1006629465533"
71
- }
72
- ```
73
-
74
- 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:
75
-
76
- ```html
77
- <!-- index.html -->
78
- <link rel="manifest" href="/manifest.json" />
79
- ```
80
-
81
48
  ### Installing a service worker
82
49
 
83
- Your application javascript must register a service worker script at an appropriate scope (we're sticking with the root).
50
+ Your application must use JavaScript to register a service worker script at an appropriate scope (root is recommended).
84
51
 
85
52
  ```javascript
86
- // application.js
87
- // Register the serviceWorker script at /serviceworker.js from your server if supported
88
- if (navigator.serviceWorker) {
89
- navigator.serviceWorker.register('/serviceworker.js')
90
- .then(function(reg) {
91
- console.log('Service worker change, registered the service worker');
92
- });
93
- }
94
- // Otherwise, no push notifications :(
95
- else {
96
- console.error('Service worker is not supported in this browser');
97
- }
53
+ navigator.serviceWorker.register('/service-worker.js')
98
54
  ```
99
55
 
100
56
  ### Subscribing to push notifications
101
57
 
102
- #### With VAPID
103
-
104
- 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).
58
+ 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.
105
59
 
106
60
  ```javascript
107
61
  window.vapidPublicKey = new Uint8Array(<%= Base64.urlsafe_decode64(ENV['VAPID_PUBLIC_KEY']).bytes %>);
108
62
  ```
109
63
 
110
- Your application javascript uses the `navigator.serviceWorker.pushManager` to subscribe to push notifications, passing the VAPID public key to the subscription settings.
64
+ Your JavaScript code uses the `pushManager` interface to subscribe to push notifications, passing the VAPID public key to the subscription settings.
111
65
 
112
66
  ```javascript
113
- // application.js
114
67
  // When serviceWorker is supported, installed, and activated,
115
68
  // subscribe the pushManager property with the vapidPublicKey
116
69
  navigator.serviceWorker.ready.then((serviceWorkerRegistration) => {
@@ -122,120 +75,49 @@ navigator.serviceWorker.ready.then((serviceWorkerRegistration) => {
122
75
  });
123
76
  ```
124
77
 
125
- #### Without VAPID
126
-
127
- 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.
128
-
129
- ```javascript
130
- // application.js
131
- // When serviceWorker is supported, installed, and activated,
132
- // subscribe the pushManager property with the vapidPublicKey
133
- navigator.serviceWorker.ready.then((serviceWorkerRegistration) => {
134
- serviceWorkerRegistration.pushManager
135
- .subscribe({
136
- userVisibleOnly: true
137
- });
138
- });
139
- ```
140
-
141
78
  ### Triggering a web push notification
142
79
 
143
- 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.
80
+ 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.
144
81
 
145
- ```javascript
146
- // application.js
147
- // Send the subscription and message from the client for the backend
148
- // to set up a push notification
149
- $(".web-push-button").on("click", (e) => {
150
- navigator.serviceWorker.ready
151
- .then((serviceWorkerRegistration) => {
152
- serviceWorkerRegistration.pushManager.getSubscription()
153
- .then((subscription) => {
154
- $.post("/push", { subscription: subscription.toJSON(), message: "You clicked a button!" });
155
- });
156
- });
157
- });
158
- ```
159
-
160
- Imagine a Ruby app endpoint that responds to the request by triggering notification through the `web-push` gem.
82
+ Then you can use this gem to send web push messages:
161
83
 
162
84
  ```ruby
163
- # app.rb
164
- # Use the web-push gem API to deliver a push notiifcation merging
165
- # the message, subscription values, and vapid options
166
- post "/push" do
167
- WebPush.payload_send(
168
- message: params[:message],
169
- endpoint: params[:subscription][:endpoint],
170
- p256dh: params[:subscription][:keys][:p256dh],
171
- auth: params[:subscription][:keys][:auth],
172
- vapid: {
173
- subject: "mailto:sender@example.com",
174
- public_key: ENV['VAPID_PUBLIC_KEY'],
175
- private_key: ENV['VAPID_PRIVATE_KEY']
176
- },
177
- ssl_timeout: 5, # value for Net::HTTP#ssl_timeout=, optional
178
- open_timeout: 5, # value for Net::HTTP#open_timeout=, optional
179
- read_timeout: 5 # value for Net::HTTP#read_timeout=, optional
180
- )
181
- end
85
+ WebPush.payload_send(
86
+ message: message,
87
+ endpoint: subscription['endpoint'],
88
+ p256dh: subscription['keys']['p256dh'],
89
+ auth: subscription['keys']['auth'],
90
+ vapid: {
91
+ subject: "mailto:sender@example.com",
92
+ public_key: ENV['VAPID_PUBLIC_KEY'],
93
+ private_key: ENV['VAPID_PRIVATE_KEY']
94
+ },
95
+ ssl_timeout: 5, # optional value for Net::HTTP#ssl_timeout=
96
+ open_timeout: 5, # optional value for Net::HTTP#open_timeout=
97
+ read_timeout: 5 # optional value for Net::HTTP#read_timeout=
98
+ )
182
99
  ```
183
100
 
184
- Note: the VAPID options should be omitted if the client-side subscription was
185
- generated without the `applicationServerKey` parameter described earlier. You
186
- would instead pass the GCM api key along with the api request as shown in the
187
- Usage section below.
188
-
189
101
  ### Receiving the push event
190
102
 
191
- 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.
103
+ 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.
192
104
 
193
105
  ```javascript
194
- // serviceworker.js
195
- // The serviceworker context can respond to 'push' events and trigger
196
- // notifications on the registration property
197
- self.addEventListener("push", (event) => {
198
- let title = (event.data && event.data.text()) || "Yay a message";
199
- let body = "We have received a push message";
200
- let tag = "push-simple-demo-notification-tag";
201
- let icon = '/assets/my-logo-120x120.png';
202
-
203
- event.waitUntil(
204
- self.registration.showNotification(title, { body, icon, tag })
205
- )
106
+ self.addEventListener('push', (event) => {
107
+ // Get the push message
108
+ var message = event.data;
109
+ // Display a notification
110
+ event.waitUntil(self.registration.showNotification('Example'));
206
111
  });
207
112
  ```
208
113
 
209
- 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.
114
+ 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:
210
115
 
211
116
  ```javascript
212
- // application.js
213
-
214
- // Let's check if the browser supports notifications
215
- if (!("Notification" in window)) {
216
- console.error("This browser does not support desktop notification");
217
- }
218
-
219
- // Let's check whether notification permissions have already been granted
220
- else if (Notification.permission === "granted") {
221
- console.log("Permission to receive notifications has been granted");
222
- }
223
-
224
- // Otherwise, we need to ask the user for permission
225
- else if (Notification.permission !== 'denied') {
226
- Notification.requestPermission(function (permission) {
227
- // If the user accepts, let's create a notification
228
- if (permission === "granted") {
229
- console.log("Permission to receive notifications has been granted");
230
- }
231
- });
232
- }
117
+ Notification.requestPermission();
233
118
  ```
234
119
 
235
- If everything worked, you should see a desktop notification triggered via web
236
- push. Yay!
237
-
238
- 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.
120
+ If everything worked, you should see a desktop notification triggered via web push. Yay!
239
121
 
240
122
  ## API
241
123
 
@@ -243,9 +125,9 @@ Note: if you're using Rails, check out [serviceworker-rails](https://github.com/
243
125
 
244
126
  ```ruby
245
127
  message = {
246
- title: "title",
247
- body: "body",
248
- icon: "http://example.com/icon.pn"
128
+ title: "Example",
129
+ body: "Hello, world!",
130
+ icon: "https://example.com/icon.png"
249
131
  }
250
132
 
251
133
  WebPush.payload_send(
@@ -298,59 +180,16 @@ WebPush.payload_send(
298
180
  p256dh: "BO/aG9nYXNkZmFkc2ZmZHNmYWRzZmFl...",
299
181
  auth: "aW1hcmthcmFpa3V6ZQ==",
300
182
  vapid: {
301
- subject: "mailto:sender@example.com"
183
+ subject: "mailto:sender@example.com",
302
184
  pem: ENV['VAPID_KEYS']
303
185
  }
304
186
  )
305
187
  ```
306
188
 
307
- ### With GCM api key
308
-
309
- ```ruby
310
- WebPush.payload_send(
311
- endpoint: "https://fcm.googleapis.com/gcm/send/eah7hak....",
312
- message: "A message",
313
- p256dh: "BO/aG9nYXNkZmFkc2ZmZHNmYWRzZmFl...",
314
- auth: "aW1hcmthcmFpa3V6ZQ==",
315
- api_key: "<GCM API KEY>"
316
- )
317
- ```
318
-
319
- ### ServiceWorker sample
320
-
321
- see. https://github.com/zaru/web-push-sample
322
-
323
- p256dh and auth generate sample code.
324
-
325
- ```javascript
326
- navigator.serviceWorker.ready.then(function(sw) {
327
- Notification.requestPermission(function(permission) {
328
- if(permission !== 'denied') {
329
- sw.pushManager.subscribe({userVisibleOnly: true}).then(function(s) {
330
- var data = {
331
- endpoint: s.endpoint,
332
- p256dh: btoa(String.fromCharCode.apply(null, new Uint8Array(s.getKey('p256dh')))).replace(/\+/g, '-').replace(/\//g, '_'),
333
- auth: btoa(String.fromCharCode.apply(null, new Uint8Array(s.getKey('auth')))).replace(/\+/g, '-').replace(/\//g, '_')
334
- }
335
- console.log(data);
336
- });
337
- }
338
- });
339
- });
340
- ```
189
+ ## Contributing
341
190
 
342
- payloads received sample code.
191
+ Bug reports and pull requests are welcome on GitHub at https://github.com/pushpad/web-push.
343
192
 
344
- ```javascript
345
- self.addEventListener("push", function(event) {
346
- var json = event.data.json();
347
- self.registration.showNotification(json.title, {
348
- body: json.body,
349
- icon: json.icon
350
- });
351
- });
352
- ```
353
-
354
- ## Contributing
193
+ ## Credits
355
194
 
356
- Bug reports and pull requests are welcome on GitHub at https://github.com/zaru/webpush.
195
+ 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.
@@ -4,15 +4,14 @@ module WebPush
4
4
  module Encryption
5
5
  extend self
6
6
 
7
- # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
8
7
  def encrypt(message, p256dh, auth)
9
8
  assert_arguments(message, p256dh, auth)
10
9
 
11
10
  group_name = 'prime256v1'
11
+ hash = 'SHA256'
12
12
  salt = Random.new.bytes(16)
13
13
 
14
- server = OpenSSL::PKey::EC.new(group_name)
15
- server.generate_key
14
+ server = OpenSSL::PKey::EC.generate(group_name)
16
15
  server_public_key_bn = server.public_key.to_bn
17
16
 
18
17
  group = OpenSSL::PKey::EC::Group.new(group_name)
@@ -27,11 +26,11 @@ module WebPush
27
26
  content_encryption_key_info = "Content-Encoding: aes128gcm\0"
28
27
  nonce_info = "Content-Encoding: nonce\0"
29
28
 
30
- prk = HKDF.new(shared_secret, salt: client_auth_token, algorithm: 'SHA256', info: info).next_bytes(32)
29
+ prk = OpenSSL::KDF.hkdf(shared_secret, salt: client_auth_token, info: info, hash: hash, length: 32)
31
30
 
32
- content_encryption_key = HKDF.new(prk, salt: salt, info: content_encryption_key_info).next_bytes(16)
31
+ content_encryption_key = OpenSSL::KDF.hkdf(prk, salt: salt, info: content_encryption_key_info, hash: hash, length: 16)
33
32
 
34
- nonce = HKDF.new(prk, salt: salt, info: nonce_info).next_bytes(12)
33
+ nonce = OpenSSL::KDF.hkdf(prk, salt: salt, info: nonce_info, hash: hash, length: 12)
35
34
 
36
35
  ciphertext = encrypt_payload(message, content_encryption_key, nonce)
37
36
 
@@ -43,7 +42,6 @@ module WebPush
43
42
 
44
43
  aes128gcmheader + ciphertext
45
44
  end
46
- # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
47
45
 
48
46
  private
49
47
 
@@ -1,25 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'uri'
4
- require 'jwt'
5
- require 'base64'
6
-
7
3
  module WebPush
8
- # It is temporary URL until supported by the GCM server.
9
- GCM_URL = 'https://android.googleapis.com/gcm/send'.freeze
10
- TEMP_GCM_URL = 'https://fcm.googleapis.com/fcm'.freeze
11
4
 
12
- # rubocop:disable Metrics/ClassLength
13
5
  class Request
14
6
  def initialize(message: '', subscription:, vapid:, **options)
15
7
  endpoint = subscription.fetch(:endpoint)
16
- @endpoint = endpoint.gsub(GCM_URL, TEMP_GCM_URL)
8
+ @endpoint = endpoint
17
9
  @payload = build_payload(message, subscription)
18
10
  @vapid_options = vapid
19
11
  @options = default_options.merge(options)
20
12
  end
21
13
 
22
- # rubocop:disable Metrics/AbcSize
23
14
  def perform
24
15
  http = Net::HTTP.new(uri.host, uri.port, *proxy_options)
25
16
  http.use_ssl = true
@@ -35,7 +26,6 @@ module WebPush
35
26
 
36
27
  resp
37
28
  end
38
- # rubocop:enable Metrics/AbcSize
39
29
 
40
30
  def proxy_options
41
31
  return [] unless @options[:proxy]
@@ -45,7 +35,6 @@ module WebPush
45
35
  [proxy_uri.host, proxy_uri.port, proxy_uri.user, proxy_uri.password]
46
36
  end
47
37
 
48
- # rubocop:disable Metrics/MethodLength
49
38
  def headers
50
39
  headers = {}
51
40
  headers['Content-Type'] = 'application/octet-stream'
@@ -57,24 +46,18 @@ module WebPush
57
46
  headers["Content-Length"] = @payload.length.to_s
58
47
  end
59
48
 
60
- if api_key?
61
- headers['Authorization'] = "key=#{api_key}"
62
- elsif vapid?
49
+ if vapid?
63
50
  headers["Authorization"] = build_vapid_header
64
51
  end
65
52
 
66
53
  headers
67
54
  end
68
- # rubocop:enable Metrics/MethodLength
69
55
 
70
56
  def build_vapid_header
71
- # https://tools.ietf.org/id/draft-ietf-webpush-vapid-03.html
72
-
73
57
  vapid_key = vapid_pem ? VapidKey.from_pem(vapid_pem) : VapidKey.from_keys(vapid_public_key, vapid_private_key)
74
58
  jwt = JWT.encode(jwt_payload, vapid_key.curve, 'ES256', jwt_header_fields)
75
59
  p256ecdsa = vapid_key.public_key_for_push_header
76
-
77
- "vapid t=#{jwt},k=#{p256ecdsa}"
60
+ "vapid t=#{jwt},k=#{p256ecdsa}"
78
61
  end
79
62
 
80
63
  def body
@@ -112,11 +95,11 @@ module WebPush
112
95
  end
113
96
 
114
97
  def expiration
115
- @vapid_options.fetch(:expiration, 24 * 60 * 60)
98
+ @vapid_options.fetch(:expiration, 12 * 60 * 60)
116
99
  end
117
100
 
118
101
  def subject
119
- @vapid_options.fetch(:subject, 'sender@example.com')
102
+ @vapid_options.fetch(:subject, 'mailto:sender@example.com')
120
103
  end
121
104
 
122
105
  def vapid_public_key
@@ -148,14 +131,6 @@ module WebPush
148
131
  Encryption.encrypt(message, p256dh, auth)
149
132
  end
150
133
 
151
- def api_key
152
- @options.fetch(:api_key, nil)
153
- end
154
-
155
- def api_key?
156
- !(api_key.nil? || api_key.empty?) && @endpoint =~ %r{\Ahttps://(android|gcm-http|fcm)\.googleapis\.com}
157
- end
158
-
159
134
  def vapid?
160
135
  @vapid_options.any?
161
136
  end
@@ -164,7 +139,6 @@ module WebPush
164
139
  WebPush.encode64(bin).delete('=')
165
140
  end
166
141
 
167
- # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity, Style/GuardClause
168
142
  def verify_response(resp)
169
143
  if resp.is_a?(Net::HTTPGone) # 410
170
144
  raise ExpiredSubscription.new(resp, uri.host)
@@ -183,7 +157,5 @@ module WebPush
183
157
  raise ResponseError.new(resp, uri.host)
184
158
  end
185
159
  end
186
- # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity, Style/GuardClause
187
160
  end
188
- # rubocop:enable Metrics/ClassLength
189
161
  end
@@ -10,9 +10,7 @@ module WebPush
10
10
  # @return [WebPush::VapidKey] a VapidKey instance for the given public and private keys
11
11
  def self.from_keys(public_key, private_key)
12
12
  key = new
13
- key.public_key = public_key
14
- key.private_key = private_key
15
-
13
+ key.set_keys! public_key, private_key
16
14
  key
17
15
  end
18
16
 
@@ -20,19 +18,14 @@ module WebPush
20
18
  #
21
19
  # @return [WebPush::VapidKey] a VapidKey instance for the given public and private keys
22
20
  def self.from_pem(pem)
23
- key = new
24
- src = OpenSSL::PKey.read pem
25
- key.curve.public_key = src.public_key
26
- key.curve.private_key = src.private_key
27
-
28
- key
21
+ new(OpenSSL::PKey.read(pem))
29
22
  end
30
23
 
31
24
  attr_reader :curve
32
25
 
33
- def initialize
34
- @curve = OpenSSL::PKey::EC.new('prime256v1')
35
- @curve.generate_key
26
+ def initialize(pkey = nil)
27
+ @curve = pkey
28
+ @curve = OpenSSL::PKey::EC.generate('prime256v1') if @curve.nil?
36
29
  end
37
30
 
38
31
  # Retrieve the encoded elliptic curve public key for VAPID protocol
@@ -57,11 +50,37 @@ module WebPush
57
50
  end
58
51
 
59
52
  def public_key=(key)
60
- curve.public_key = OpenSSL::PKey::EC::Point.new(group, to_big_num(key))
53
+ set_keys! key, nil
61
54
  end
62
55
 
63
56
  def private_key=(key)
64
- curve.private_key = to_big_num(key)
57
+ set_keys! nil, key
58
+ end
59
+
60
+ def set_keys!(public_key = nil, private_key = nil)
61
+ if public_key.nil?
62
+ public_key = curve.public_key
63
+ else
64
+ public_key = OpenSSL::PKey::EC::Point.new(group, to_big_num(public_key))
65
+ end
66
+
67
+ if private_key.nil?
68
+ private_key = curve.private_key
69
+ else
70
+ private_key = to_big_num(private_key)
71
+ end
72
+
73
+ asn1 = OpenSSL::ASN1::Sequence([
74
+ OpenSSL::ASN1::Integer.new(1),
75
+ # Not properly padded but OpenSSL doesn't mind
76
+ OpenSSL::ASN1::OctetString(private_key.to_s(2)),
77
+ OpenSSL::ASN1::ObjectId('prime256v1', 0, :EXPLICIT),
78
+ OpenSSL::ASN1::BitString(public_key.to_octet_string(:uncompressed), 1, :EXPLICIT),
79
+ ])
80
+
81
+ der = asn1.to_der
82
+
83
+ @curve = OpenSSL::PKey::EC.new(der)
65
84
  end
66
85
 
67
86
  def curve_name
@@ -78,10 +97,15 @@ module WebPush
78
97
  alias to_hash to_h
79
98
 
80
99
  def to_pem
81
- public_key = OpenSSL::PKey::EC.new curve
82
- public_key.private_key = nil
100
+ private_key_to_pem + public_key_to_pem
101
+ end
102
+
103
+ def private_key_to_pem
104
+ curve.to_pem
105
+ end
83
106
 
84
- curve.to_pem + public_key.to_pem
107
+ def public_key_to_pem
108
+ curve.public_to_pem
85
109
  end
86
110
 
87
111
  def inspect
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module WebPush
4
- VERSION = '1.0.0'.freeze
4
+ VERSION = '3.0.1'.freeze
5
5
  end