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 +4 -4
- data/.github/workflows/ci.yml +23 -0
- data/.gitignore +1 -9
- data/README.md +50 -211
- data/lib/web_push/encryption.rb +5 -7
- data/lib/web_push/request.rb +5 -33
- data/lib/web_push/vapid_key.rb +41 -17
- data/lib/web_push/version.rb +1 -1
- data/lib/web_push.rb +2 -4
- data/spec/spec_helper.rb +1 -2
- data/spec/web_push/encryption_spec.rb +4 -6
- data/spec/web_push/request_spec.rb +1 -46
- data/spec/web_push/vapid_key_spec.rb +29 -0
- data/spec/web_push_spec.rb +4 -4
- data/web-push.gemspec +4 -6
- metadata +20 -71
- data/.rspec +0 -2
- data/.rubocop.yml +0 -30
- data/.travis.yml +0 -24
- data/CHANGELOG.md +0 -183
- data/Rakefile +0 -8
- data/bin/console +0 -14
- data/bin/rake +0 -16
- data/bin/rspec +0 -16
- data/bin/setup +0 -8
- data/lib/tasks/web_push.rake +0 -14
- data/lib/web_push/railtie.rb +0 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a688b354339ddf4f82d8febb54a2042bb432357dffd77a571c3c9212c107aa2f
|
4
|
+
data.tar.gz: 01060b2687a57217cafb2cf96b3925c6e2e39102daf6f796277d06d767ae6a0e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
data/README.md
CHANGED
@@ -1,45 +1,37 @@
|
|
1
1
|
# WebPush
|
2
2
|
|
3
|
-
[](https://travis-ci.org/zaru/webpush)
|
6
|
-
[](https://badge.fury.io/rb/webpush)
|
3
|
+
[](https://badge.fury.io/rb/web-push)
|
4
|
+

|
7
5
|
|
8
|
-
This gem makes it possible to send push messages to web browsers from Ruby backends using the [Web Push Protocol](https://
|
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
|
-
|
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
|
12
|
+
Add this line to the Gemfile:
|
17
13
|
|
18
14
|
```ruby
|
19
15
|
gem 'web-push'
|
20
16
|
```
|
21
17
|
|
22
|
-
|
23
|
-
|
24
|
-
$ bundle
|
25
|
-
|
26
|
-
Or install it yourself as:
|
18
|
+
Or install the gem:
|
27
19
|
|
28
|
-
|
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.
|
35
|
-
2.
|
36
|
-
3.
|
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`
|
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
|
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
|
-
|
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
|
-
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
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
|
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
|
-
|
195
|
-
//
|
196
|
-
|
197
|
-
|
198
|
-
|
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
|
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
|
-
|
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: "
|
247
|
-
body: "
|
248
|
-
icon: "
|
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
|
-
|
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
|
-
|
191
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/pushpad/web-push.
|
343
192
|
|
344
|
-
|
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
|
-
|
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.
|
data/lib/web_push/encryption.rb
CHANGED
@@ -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.
|
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 =
|
29
|
+
prk = OpenSSL::KDF.hkdf(shared_secret, salt: client_auth_token, info: info, hash: hash, length: 32)
|
31
30
|
|
32
|
-
content_encryption_key =
|
31
|
+
content_encryption_key = OpenSSL::KDF.hkdf(prk, salt: salt, info: content_encryption_key_info, hash: hash, length: 16)
|
33
32
|
|
34
|
-
nonce =
|
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
|
|
data/lib/web_push/request.rb
CHANGED
@@ -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
|
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
|
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,
|
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
|
data/lib/web_push/vapid_key.rb
CHANGED
@@ -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
|
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
|
-
|
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 =
|
35
|
-
@curve.
|
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
|
-
|
53
|
+
set_keys! key, nil
|
61
54
|
end
|
62
55
|
|
63
56
|
def private_key=(key)
|
64
|
-
|
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
|
-
|
82
|
-
|
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
|
-
|
107
|
+
def public_key_to_pem
|
108
|
+
curve.public_to_pem
|
85
109
|
end
|
86
110
|
|
87
111
|
def inspect
|
data/lib/web_push/version.rb
CHANGED