webpush 0.2.5 → 0.3.0
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/LICENSE +20 -0
- data/README.md +193 -9
- data/lib/webpush.rb +24 -18
- data/lib/webpush/encryption.rb +1 -0
- data/lib/webpush/errors.rb +9 -0
- data/lib/webpush/request.rb +86 -23
- data/lib/webpush/vapid_key.rb +75 -0
- data/lib/webpush/version.rb +1 -1
- data/webpush.gemspec +1 -0
- metadata +20 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ef85cc1d7d9a39a57426c5d1d20b89c0c5069407
|
4
|
+
data.tar.gz: 5b964e6fb1bec69c5bb3f331d139ab4b848d063e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ca808369991e12634181e0a748ea1c3f8a918ec370f14f7709820a9004305c041dab21b571d610d3db71fdd4ac382f2166641f1375ca345b267db134fbb351be
|
7
|
+
data.tar.gz: 0e95d566e542ce200ee670c12a932d8de8b76f69df13115785fc59d10a8b897df9734b48382656ac97263cbbea23003e92bb4df00fe55e2f214af88964743ae1
|
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2016 Hiroyuki Sakuraba
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
CHANGED
@@ -4,7 +4,7 @@
|
|
4
4
|
[](https://travis-ci.org/zaru/webpush)
|
5
5
|
[](https://badge.fury.io/rb/webpush)
|
6
6
|
|
7
|
-
This
|
7
|
+
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.
|
8
8
|
|
9
9
|
Payload is supported by Chrome50+, Firefox48+.
|
10
10
|
|
@@ -26,7 +26,188 @@ Or install it yourself as:
|
|
26
26
|
|
27
27
|
## Usage
|
28
28
|
|
29
|
-
|
29
|
+
Sending a web push message to a visitor of your website requires a number of steps:
|
30
|
+
|
31
|
+
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.
|
32
|
+
2. 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. For using Google's deprecated GCM protocol instead of VAPID, a separate GCM API key from your app settings would also be necessary.
|
33
|
+
3. A 'manifest.json' file, linked from your user's page, identifies your app settings, including the GCM sender ID.
|
34
|
+
5. 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.
|
35
|
+
6. Your server uses the `webpush` gem to send a notification with the `subscription` obtained from the client and an optional payload (the message).
|
36
|
+
7. 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.
|
37
|
+
|
38
|
+
### Generating VAPID keys
|
39
|
+
|
40
|
+
Use `webpush` to generate a VAPID key that has both a `public_key` and `private_key` attribute to be saved on the server side.
|
41
|
+
|
42
|
+
```ruby
|
43
|
+
# One-time, on the server
|
44
|
+
vapid_key = Webpush.generate_key
|
45
|
+
|
46
|
+
# Save these in your application server settings
|
47
|
+
vapid_key.public_key
|
48
|
+
vapid_key.private_key
|
49
|
+
```
|
50
|
+
|
51
|
+
### Declaring manifest.json
|
52
|
+
|
53
|
+
For Chrome web push, add the GCM sender id to a `manifest.json` file served at the scope of your app (or above), like at the root.
|
54
|
+
|
55
|
+
```javascript
|
56
|
+
{
|
57
|
+
"name": "My Website",
|
58
|
+
"gcm_sender_id": "1006629465533"
|
59
|
+
}
|
60
|
+
```
|
61
|
+
|
62
|
+
And link to it somewhere in the `<head>` tag:
|
63
|
+
|
64
|
+
```html
|
65
|
+
<!-- index.html -->
|
66
|
+
<link rel="manifest" href="/manifest.json" />
|
67
|
+
```
|
68
|
+
|
69
|
+
### Installing a service worker
|
70
|
+
|
71
|
+
Your application javascript must register a service worker script at an appropriate scope (we're sticking with the root).
|
72
|
+
|
73
|
+
```javascript
|
74
|
+
// application.js
|
75
|
+
// Register the serviceWorker script at /serviceworker.js from your server if supported
|
76
|
+
if (navigator.serviceWorker) {
|
77
|
+
navigator.serviceWorker.register('/serviceworker.js')
|
78
|
+
.then(function(reg) {
|
79
|
+
console.log('Service worker change, registered the service worker');
|
80
|
+
});
|
81
|
+
}
|
82
|
+
// Otherwise, no push notifications :(
|
83
|
+
else {
|
84
|
+
console.error('Service worker is not supported in this browser');
|
85
|
+
}
|
86
|
+
```
|
87
|
+
|
88
|
+
### Subscribing to push notifications
|
89
|
+
|
90
|
+
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).
|
91
|
+
|
92
|
+
```javascript
|
93
|
+
window.vapidPublicKey = new Uint8Array(<%= Base64.urlsafe_decode64(ENV['VAPID_PUBLIC_KEY']).bytes %>);
|
94
|
+
```
|
95
|
+
|
96
|
+
Your application javascript would then use the `navigator.serviceWorker.pushManager` to subscribe to push notifications, passing the VAPID public key to the subscription settings.
|
97
|
+
|
98
|
+
```javascript
|
99
|
+
// application.js
|
100
|
+
// When serviceWorker is supported, installed, and activated,
|
101
|
+
// subscribe the pushManager property with the vapidPublicKey
|
102
|
+
navigator.serviceWorker.ready.then((serviceWorkerRegistration) => {
|
103
|
+
serviceWorkerRegistration.pushManager
|
104
|
+
.subscribe({
|
105
|
+
userVisibleOnly: true,
|
106
|
+
applicationServerKey: window.vapidPublicKey
|
107
|
+
});
|
108
|
+
});
|
109
|
+
```
|
110
|
+
|
111
|
+
Note: If you will not be sending VAPID details, then there is no need generate VAPID
|
112
|
+
keys, and the `applicationServerKey` parameter may be omitted from the
|
113
|
+
`pushManager.subscribe` call.
|
114
|
+
|
115
|
+
### Triggering a web push notification
|
116
|
+
|
117
|
+
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.
|
118
|
+
|
119
|
+
```javascript
|
120
|
+
// application.js
|
121
|
+
// Send the subscription and message from the client for the backend
|
122
|
+
// to set up a push notification
|
123
|
+
$(".webpush-button").on("click", (e) => {
|
124
|
+
navigator.serviceWorker.ready
|
125
|
+
.then((serviceWorkerRegistration) => {
|
126
|
+
serviceWorkerRegistration.pushManager.getSubscription()
|
127
|
+
.then((subscription) => {
|
128
|
+
$.post("/push", { subscription: subscription.toJSON(), message: "You clicked a button!" });
|
129
|
+
});
|
130
|
+
});
|
131
|
+
```
|
132
|
+
|
133
|
+
Imagine a Ruby app endpoint that responds to the request by triggering notification through the `webpush` gem.
|
134
|
+
|
135
|
+
```ruby
|
136
|
+
# app.rb
|
137
|
+
# Use the webpush gem API to deliver a push notiifcation merging
|
138
|
+
# the message, subscription values, and vapid options
|
139
|
+
post "/push" do
|
140
|
+
Webpush.payload_send(
|
141
|
+
message: params[:message]
|
142
|
+
endpoint: params[:subscription][:endpoint],
|
143
|
+
p256dh: params[:subscription][:keys][:p256dh],
|
144
|
+
auth: params[:subscription][:keys][:p256dh],
|
145
|
+
vapid: {
|
146
|
+
subject: "mailto:sender@example.com",
|
147
|
+
public_key: ENV['VAPID_PUBLIC_KEY'],
|
148
|
+
private_key: ENV['VAPID_PRIVATE_KEY']
|
149
|
+
}
|
150
|
+
)
|
151
|
+
end
|
152
|
+
```
|
153
|
+
|
154
|
+
Note: the VAPID options should be omitted if the client-side subscription was
|
155
|
+
generated without the `applicationServerKey` parameter described earlier.
|
156
|
+
|
157
|
+
### Receiving the push event
|
158
|
+
|
159
|
+
Your `/serviceworker.js` script can respond to `'push'` events. One action it can take is to trigger desktop notifications by calling `showNotification` on the `registration` property.
|
160
|
+
|
161
|
+
```javascript
|
162
|
+
// serviceworker.js
|
163
|
+
// The serviceworker context can respond to 'push' events and trigger
|
164
|
+
// notifications on the registration property
|
165
|
+
self.addEventListener("push", (event) => {
|
166
|
+
let title = (event.data && event.data.text()) || "Yay a message";
|
167
|
+
let body = "We have received a push message";
|
168
|
+
let tag = "push-simple-demo-notification-tag";
|
169
|
+
let icon = '/assets/my-logo-120x120.png';
|
170
|
+
|
171
|
+
event.waitUntil(
|
172
|
+
self.registration.showNotification(title, { body, icon, tag })
|
173
|
+
)
|
174
|
+
});
|
175
|
+
```
|
176
|
+
|
177
|
+
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.
|
178
|
+
|
179
|
+
```javascript
|
180
|
+
// application.js
|
181
|
+
|
182
|
+
// Let's check if the browser supports notifications
|
183
|
+
if (!("Notification" in window)) {
|
184
|
+
console.error("This browser does not support desktop notification");
|
185
|
+
}
|
186
|
+
|
187
|
+
// Let's check whether notification permissions have already been granted
|
188
|
+
else if (Notification.permission === "granted") {
|
189
|
+
console.log("Permission to receive notifications has been granted");
|
190
|
+
}
|
191
|
+
|
192
|
+
// Otherwise, we need to ask the user for permission
|
193
|
+
else if (Notification.permission !== 'denied') {
|
194
|
+
Notification.requestPermission(function (permission) {
|
195
|
+
// If the user accepts, let's create a notification
|
196
|
+
if (permission === "granted") {
|
197
|
+
console.log("Permission to receive notifications has been granted");
|
198
|
+
}
|
199
|
+
});
|
200
|
+
}
|
201
|
+
```
|
202
|
+
|
203
|
+
If everything worked, you should see a desktop notification triggered via web
|
204
|
+
push. Yay!
|
205
|
+
|
206
|
+
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.
|
207
|
+
|
208
|
+
## API
|
209
|
+
|
210
|
+
### With a payload
|
30
211
|
|
31
212
|
```ruby
|
32
213
|
message = {
|
@@ -36,22 +217,25 @@ message = {
|
|
36
217
|
}
|
37
218
|
|
38
219
|
Webpush.payload_send(
|
39
|
-
endpoint: "https://
|
220
|
+
endpoint: "https://fcm.googleapis.com/gcm/send/eah7hak....",
|
40
221
|
message: JSON.generate(message),
|
41
222
|
p256dh: "BO/aG9nYXNkZmFkc2ZmZHNmYWRzZmFl...",
|
42
223
|
auth: "aW1hcmthcmFpa3V6ZQ==",
|
43
|
-
ttl: 600
|
44
|
-
|
224
|
+
ttl: 600 #optional, ttl in seconds, defaults to 2419200 (4 weeks),
|
225
|
+
vapid: {
|
226
|
+
subject: "mailto:sender@example.com",
|
227
|
+
public_key: ENV['VAPID_PUBLIC_KEY'],
|
228
|
+
private_key: ENV['VAPID_PRIVATE_KEY']
|
229
|
+
}
|
45
230
|
)
|
46
231
|
```
|
47
232
|
|
48
|
-
###
|
233
|
+
### Without a payload
|
49
234
|
|
50
235
|
```ruby
|
51
236
|
Webpush.payload_send(
|
52
|
-
endpoint: "https://
|
53
|
-
ttl: 600
|
54
|
-
api_key: "[GoogleDeveloper APIKEY]" # optional, not used in Firefox.
|
237
|
+
endpoint: "https://fcm.googleapis.com/gcm/send/eah7hak....",
|
238
|
+
ttl: 600 #optional, ttl in seconds, defaults to 2419200 (4 weeks)
|
55
239
|
)
|
56
240
|
```
|
57
241
|
|
data/lib/webpush.rb
CHANGED
@@ -5,15 +5,12 @@ require 'net/http'
|
|
5
5
|
require 'json'
|
6
6
|
|
7
7
|
require 'webpush/version'
|
8
|
+
require 'webpush/errors'
|
9
|
+
require 'webpush/vapid_key'
|
8
10
|
require 'webpush/encryption'
|
9
11
|
require 'webpush/request'
|
10
12
|
|
11
13
|
module Webpush
|
12
|
-
|
13
|
-
# It is temporary URL until supported by the GCM server.
|
14
|
-
GCM_URL = 'https://android.googleapis.com/gcm/send'
|
15
|
-
TEMP_GCM_URL = 'https://gcm-http.googleapis.com/gcm'
|
16
|
-
|
17
14
|
class << self
|
18
15
|
# Deliver the payload to the required endpoint given by the JavaScript
|
19
16
|
# PushSubscription. Including an optional message requires p256dh and
|
@@ -23,23 +20,32 @@ module Webpush
|
|
23
20
|
# @param message [String] the optional payload
|
24
21
|
# @param p256dh [String] the user's public ECDH key given by the PushSubscription
|
25
22
|
# @param auth [String] the user's private ECDH key given by the PushSubscription
|
23
|
+
# @param vapid [Hash<Symbol,String>] options for VAPID
|
24
|
+
# @option vapid [String] :subject contact URI for the app server as a "mailto:" or an "https:"
|
25
|
+
# @option vapid [String] :public_key the VAPID public key
|
26
|
+
# @option vapid [String] :private_key the VAPID private key
|
26
27
|
# @param options [Hash<Symbol,String>] additional options for the notification
|
27
|
-
# @option options [String] :api_key required for Google, omit for Firefox
|
28
28
|
# @option options [#to_s] :ttl Time-to-live in seconds
|
29
|
-
def payload_send(
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
29
|
+
def payload_send(message: "", endpoint:, p256dh: "", auth: "", vapid: {}, **options)
|
30
|
+
subscription = {
|
31
|
+
endpoint: endpoint,
|
32
|
+
keys: {
|
33
|
+
p256dh: p256dh,
|
34
|
+
auth: auth
|
35
|
+
}
|
36
|
+
}
|
37
|
+
Webpush::Request.new(
|
38
|
+
message: message,
|
39
|
+
subscription: subscription,
|
40
|
+
vapid: vapid,
|
41
|
+
**options
|
42
|
+
).perform
|
35
43
|
end
|
36
44
|
|
37
|
-
|
38
|
-
|
39
|
-
def
|
40
|
-
|
41
|
-
|
42
|
-
Webpush::Encryption.encrypt(message, p256dh, auth)
|
45
|
+
# public_key: vapid_key.public_key.to_bn.to_s(2)
|
46
|
+
# private_key: vapid_key.private_key.to_s(2)
|
47
|
+
def generate_key
|
48
|
+
VapidKey.new
|
43
49
|
end
|
44
50
|
end
|
45
51
|
end
|
data/lib/webpush/encryption.rb
CHANGED
data/lib/webpush/request.rb
CHANGED
@@ -1,20 +1,21 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
class ResponseError < RuntimeError
|
4
|
-
end
|
1
|
+
require 'jwt'
|
2
|
+
require 'base64'
|
5
3
|
|
6
|
-
|
7
|
-
|
4
|
+
module Webpush
|
5
|
+
# It is temporary URL until supported by the GCM server.
|
6
|
+
GCM_URL = 'https://android.googleapis.com/gcm/send'
|
7
|
+
TEMP_GCM_URL = 'https://gcm-http.googleapis.com/gcm'
|
8
8
|
|
9
9
|
class Request
|
10
|
-
def initialize(
|
11
|
-
|
10
|
+
def initialize(message: "", subscription:, vapid:, **options)
|
11
|
+
endpoint = subscription.fetch(:endpoint)
|
12
|
+
@endpoint = endpoint.gsub(GCM_URL, TEMP_GCM_URL)
|
13
|
+
@payload = build_payload(message, subscription)
|
14
|
+
@vapid_options = vapid
|
12
15
|
@options = default_options.merge(options)
|
13
|
-
@payload = @options.delete(:payload) || {}
|
14
16
|
end
|
15
17
|
|
16
18
|
def perform
|
17
|
-
uri = URI.parse(@endpoint)
|
18
19
|
http = Net::HTTP.new(uri.host, uri.port)
|
19
20
|
http.use_ssl = true
|
20
21
|
req = Net::HTTP::Post.new(uri.request_uri, headers)
|
@@ -36,52 +37,114 @@ module Webpush
|
|
36
37
|
headers["Content-Type"] = "application/octet-stream"
|
37
38
|
headers["Ttl"] = ttl
|
38
39
|
|
39
|
-
if
|
40
|
+
if @payload.has_key?(:server_public_key)
|
40
41
|
headers["Content-Encoding"] = "aesgcm"
|
41
42
|
headers["Encryption"] = "salt=#{salt_param}"
|
42
43
|
headers["Crypto-Key"] = "dh=#{dh_param}"
|
43
44
|
end
|
44
45
|
|
45
|
-
|
46
|
+
if api_key?
|
47
|
+
headers["Authorization"] = api_key
|
48
|
+
elsif vapid?
|
49
|
+
vapid_headers = build_vapid_headers
|
50
|
+
headers["Authorization"] = vapid_headers["Authorization"]
|
51
|
+
headers["Crypto-Key"] = [ headers["Crypto-Key"], vapid_headers["Crypto-Key"] ].compact.join(";")
|
52
|
+
end
|
46
53
|
|
47
54
|
headers
|
48
55
|
end
|
49
56
|
|
57
|
+
def build_vapid_headers
|
58
|
+
vapid_key = VapidKey.from_keys(vapid_public_key, vapid_private_key)
|
59
|
+
jwt = JWT.encode(jwt_payload, vapid_key.curve, 'ES256')
|
60
|
+
p256ecdsa = vapid_key.public_key_for_push_header
|
61
|
+
|
62
|
+
{
|
63
|
+
'Authorization' => 'WebPush ' + jwt,
|
64
|
+
'Crypto-Key' => 'p256ecdsa=' + p256ecdsa,
|
65
|
+
}
|
66
|
+
end
|
67
|
+
|
50
68
|
def body
|
51
69
|
@payload.fetch(:ciphertext, "")
|
52
70
|
end
|
53
71
|
|
54
72
|
private
|
55
73
|
|
74
|
+
def uri
|
75
|
+
@uri ||= URI.parse(@endpoint)
|
76
|
+
end
|
77
|
+
|
56
78
|
def ttl
|
57
79
|
@options.fetch(:ttl).to_s
|
58
80
|
end
|
59
81
|
|
60
|
-
def
|
61
|
-
@
|
82
|
+
def dh_param
|
83
|
+
trim_encode64(@payload.fetch(:server_public_key))
|
62
84
|
end
|
63
85
|
|
64
|
-
def
|
65
|
-
|
86
|
+
def salt_param
|
87
|
+
trim_encode64(@payload.fetch(:salt))
|
66
88
|
end
|
67
89
|
|
68
|
-
def
|
69
|
-
|
90
|
+
def jwt_payload
|
91
|
+
{
|
92
|
+
aud: audience,
|
93
|
+
exp: Time.now.to_i + expiration,
|
94
|
+
sub: subject,
|
95
|
+
}
|
70
96
|
end
|
71
97
|
|
72
|
-
def
|
73
|
-
|
98
|
+
def audience
|
99
|
+
uri.scheme + "://" + uri.host
|
74
100
|
end
|
75
101
|
|
76
|
-
def
|
77
|
-
|
102
|
+
def expiration
|
103
|
+
@vapid_options.fetch(:expiration, 24*60*60)
|
104
|
+
end
|
105
|
+
|
106
|
+
def subject
|
107
|
+
@vapid_options.fetch(:subject, 'sender@example.com')
|
108
|
+
end
|
109
|
+
|
110
|
+
def vapid_public_key
|
111
|
+
@vapid_options.fetch(:public_key, nil)
|
112
|
+
end
|
113
|
+
|
114
|
+
def vapid_private_key
|
115
|
+
@vapid_options.fetch(:private_key, nil)
|
78
116
|
end
|
79
117
|
|
80
118
|
def default_options
|
81
119
|
{
|
82
|
-
api_key: nil,
|
83
120
|
ttl: 60*60*24*7*4 # 4 weeks
|
84
121
|
}
|
85
122
|
end
|
123
|
+
|
124
|
+
def build_payload(message, subscription)
|
125
|
+
return {} if message.nil? || message.empty?
|
126
|
+
|
127
|
+
encrypt_payload(message, subscription.fetch(:keys))
|
128
|
+
end
|
129
|
+
|
130
|
+
def encrypt_payload(message, p256dh:, auth:)
|
131
|
+
Encryption.encrypt(message, p256dh, auth)
|
132
|
+
end
|
133
|
+
|
134
|
+
def api_key
|
135
|
+
@options.fetch(:api_key, nil)
|
136
|
+
end
|
137
|
+
|
138
|
+
def api_key?
|
139
|
+
!(api_key.nil? || api_key.empty?) && @endpoint =~ /\Ahttps:\/\/(android|gcm-http)\.googleapis\.com/
|
140
|
+
end
|
141
|
+
|
142
|
+
def vapid?
|
143
|
+
@vapid_options.any?
|
144
|
+
end
|
145
|
+
|
146
|
+
def trim_encode64(bin)
|
147
|
+
Base64.urlsafe_encode64(bin).delete('=')
|
148
|
+
end
|
86
149
|
end
|
87
150
|
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module Webpush
|
2
|
+
class VapidKey
|
3
|
+
def self.from_keys(public_key, private_key)
|
4
|
+
key = new
|
5
|
+
key.public_key = public_key
|
6
|
+
key.private_key = private_key
|
7
|
+
|
8
|
+
key
|
9
|
+
end
|
10
|
+
|
11
|
+
attr_reader :curve
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
@curve = OpenSSL::PKey::EC.new('prime256v1')
|
15
|
+
@curve.generate_key
|
16
|
+
end
|
17
|
+
|
18
|
+
# Retrieve the encoded EC public key for server-side storage
|
19
|
+
# @return encoded binary representaion of 65-byte VAPID public key
|
20
|
+
def public_key
|
21
|
+
encode64(curve.public_key.to_bn.to_s(2))
|
22
|
+
end
|
23
|
+
|
24
|
+
# Retrieve EC public key for Web Push
|
25
|
+
# @return the encoded VAPID public key suitable for Web Push transport
|
26
|
+
def public_key_for_push_header
|
27
|
+
trim_encode64(curve.public_key.to_bn.to_s(2))
|
28
|
+
end
|
29
|
+
|
30
|
+
# Convenience
|
31
|
+
# @return base64 urlsafe-encoded binary representaion of 32-byte VAPID private key
|
32
|
+
def private_key
|
33
|
+
Base64.urlsafe_encode64(curve.private_key.to_s(2))
|
34
|
+
end
|
35
|
+
|
36
|
+
def public_key=(key)
|
37
|
+
@curve.public_key = OpenSSL::PKey::EC::Point.new(group, to_big_num(key))
|
38
|
+
end
|
39
|
+
|
40
|
+
def private_key=(key)
|
41
|
+
@curve.private_key = to_big_num(key)
|
42
|
+
end
|
43
|
+
|
44
|
+
def curve_name
|
45
|
+
group.curve_name
|
46
|
+
end
|
47
|
+
|
48
|
+
def group
|
49
|
+
curve.group
|
50
|
+
end
|
51
|
+
|
52
|
+
def to_h
|
53
|
+
{ public_key: public_key, private_key: private_key }
|
54
|
+
end
|
55
|
+
alias to_hash to_h
|
56
|
+
|
57
|
+
def inspect
|
58
|
+
"#<#{self.class}:#{object_id.to_s(16)} #{to_h.map { |k, v| ":#{k}=#{v}" }.join(" ")}>"
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def to_big_num(key)
|
64
|
+
OpenSSL::BN.new(Base64.urlsafe_decode64(key), 2)
|
65
|
+
end
|
66
|
+
|
67
|
+
def encode64(bin)
|
68
|
+
Base64.urlsafe_encode64(bin)
|
69
|
+
end
|
70
|
+
|
71
|
+
def trim_encode64(bin)
|
72
|
+
encode64(bin).delete('=')
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
data/lib/webpush/version.rb
CHANGED
data/webpush.gemspec
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: webpush
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- zaru@sakuraba
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-10-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: hkdf
|
@@ -24,6 +24,20 @@ dependencies:
|
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0.2'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: jwt
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
27
41
|
- !ruby/object:Gem::Dependency
|
28
42
|
name: bundler
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -119,6 +133,7 @@ files:
|
|
119
133
|
- ".rspec"
|
120
134
|
- ".travis.yml"
|
121
135
|
- Gemfile
|
136
|
+
- LICENSE
|
122
137
|
- README.md
|
123
138
|
- Rakefile
|
124
139
|
- bin/console
|
@@ -127,7 +142,9 @@ files:
|
|
127
142
|
- bin/setup
|
128
143
|
- lib/webpush.rb
|
129
144
|
- lib/webpush/encryption.rb
|
145
|
+
- lib/webpush/errors.rb
|
130
146
|
- lib/webpush/request.rb
|
147
|
+
- lib/webpush/vapid_key.rb
|
131
148
|
- lib/webpush/version.rb
|
132
149
|
- webpush.gemspec
|
133
150
|
homepage: https://github.com/zaru/webpush
|
@@ -149,7 +166,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
149
166
|
version: '0'
|
150
167
|
requirements: []
|
151
168
|
rubyforge_project:
|
152
|
-
rubygems_version: 2.
|
169
|
+
rubygems_version: 2.5.1
|
153
170
|
signing_key:
|
154
171
|
specification_version: 4
|
155
172
|
summary: Encryption Utilities for Web Push payload.
|