webpush 0.2.5 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
[![Build Status](https://travis-ci.org/zaru/webpush.svg?branch=master)](https://travis-ci.org/zaru/webpush)
|
5
5
|
[![Gem Version](https://badge.fury.io/rb/webpush.svg)](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.
|