webpush 0.1.6 → 0.2.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/.travis.yml +1 -0
- data/README.md +15 -1
- data/Rakefile +1 -1
- data/bin/rspec +16 -0
- data/lib/webpush/encryption.rb +18 -6
- data/lib/webpush/request.rb +75 -0
- data/lib/webpush/version.rb +1 -1
- data/lib/webpush.rb +20 -24
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1e5f86e721e160b17bf21789f6c9d11a5d7ac297
|
4
|
+
data.tar.gz: 2f484b4300166b10772c1ebd9266228e9d4cd970
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 109c269ebbc2443613635450d497c1c1c7b5f24dffa96761c987bd77fb1fec1c252e7851a1dea19ee1e3d88fdd293ee58870408dc6391d0e82a6fbb6a3e1f6d4
|
7
|
+
data.tar.gz: ba9f2b0d91d175fca0e462870aa36366a2cbf4cde21b91acb729c36a6b55bf1c5fa6e50f7e2c13dec2bdc13128e0d4aa6e8a0853696493104e5da5c516bf2f64
|
data/.travis.yml
CHANGED
data/README.md
CHANGED
@@ -1,5 +1,8 @@
|
|
1
1
|
# WebPush
|
2
2
|
|
3
|
+
[](https://codeclimate.com/github/zaru/webpush)
|
4
|
+
[](https://travis-ci.org/zaru/webpush)
|
5
|
+
|
3
6
|
This Gem will send the Web Push API. It supports the encryption necessary to payload.
|
4
7
|
|
5
8
|
Payload is supported by Chrome50+, Firefox48+.
|
@@ -22,6 +25,8 @@ Or install it yourself as:
|
|
22
25
|
|
23
26
|
## Usage
|
24
27
|
|
28
|
+
### using the payload
|
29
|
+
|
25
30
|
```ruby
|
26
31
|
message = {
|
27
32
|
title: "title",
|
@@ -30,14 +35,23 @@ message = {
|
|
30
35
|
}
|
31
36
|
|
32
37
|
Webpush.payload_send(
|
33
|
-
message: JSON.generate(message),
|
34
38
|
endpoint: "https://android.googleapis.com/gcm/send/eah7hak....",
|
39
|
+
message: JSON.generate(message),
|
35
40
|
p256dh: "BO/aG9nYXNkZmFkc2ZmZHNmYWRzZmFl...",
|
36
41
|
auth: "aW1hcmthcmFpa3V6ZQ==",
|
37
42
|
api_key: "[GoogleDeveloper APIKEY]" # optional, not used in Firefox.
|
38
43
|
)
|
39
44
|
```
|
40
45
|
|
46
|
+
### not use the payload
|
47
|
+
|
48
|
+
```ruby
|
49
|
+
Webpush.payload_send(
|
50
|
+
endpoint: "https://android.googleapis.com/gcm/send/eah7hak....",
|
51
|
+
api_key: "[GoogleDeveloper APIKEY]" # optional, not used in Firefox.
|
52
|
+
)
|
53
|
+
```
|
54
|
+
|
41
55
|
### ServiceWorker sample
|
42
56
|
|
43
57
|
see. https://github.com/zaru/web-push-sample
|
data/Rakefile
CHANGED
data/bin/rspec
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# This file was generated by Bundler.
|
4
|
+
#
|
5
|
+
# The application 'rspec' is installed as part of a gem, and
|
6
|
+
# this file is here to facilitate running it.
|
7
|
+
#
|
8
|
+
|
9
|
+
require "pathname"
|
10
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
|
11
|
+
Pathname.new(__FILE__).realpath)
|
12
|
+
|
13
|
+
require "rubygems"
|
14
|
+
require "bundler/setup"
|
15
|
+
|
16
|
+
load Gem.bin_path("rspec-core", "rspec")
|
data/lib/webpush/encryption.rb
CHANGED
@@ -3,6 +3,8 @@ module Webpush
|
|
3
3
|
extend self
|
4
4
|
|
5
5
|
def encrypt(message, p256dh, auth)
|
6
|
+
assert_arguments(message, p256dh, auth)
|
7
|
+
|
6
8
|
group_name = "prime256v1"
|
7
9
|
salt = Random.new.bytes(16)
|
8
10
|
|
@@ -18,15 +20,15 @@ module Webpush
|
|
18
20
|
|
19
21
|
client_auth_token = Base64.urlsafe_decode64(auth)
|
20
22
|
|
21
|
-
prk = HKDF.new(shared_secret, :
|
23
|
+
prk = HKDF.new(shared_secret, salt: client_auth_token, algorithm: 'SHA256', info: "Content-Encoding: auth\0").next_bytes(32)
|
22
24
|
|
23
25
|
context = create_context(client_public_key_bn, server_public_key_bn)
|
24
26
|
|
25
27
|
content_encryption_key_info = create_info('aesgcm', context)
|
26
|
-
content_encryption_key = HKDF.new(prk, :
|
28
|
+
content_encryption_key = HKDF.new(prk, salt: salt, info: content_encryption_key_info).next_bytes(16)
|
27
29
|
|
28
30
|
nonce_info = create_info('nonce', context)
|
29
|
-
nonce = HKDF.new(prk, :
|
31
|
+
nonce = HKDF.new(prk, salt: salt, info: nonce_info).next_bytes(12)
|
30
32
|
|
31
33
|
ciphertext = encrypt_payload(message, content_encryption_key, nonce)
|
32
34
|
|
@@ -40,9 +42,9 @@ module Webpush
|
|
40
42
|
|
41
43
|
private
|
42
44
|
|
43
|
-
def create_context(
|
44
|
-
c = convert16bit(
|
45
|
-
s = convert16bit(
|
45
|
+
def create_context(client_public_key, server_public_key)
|
46
|
+
c = convert16bit(client_public_key)
|
47
|
+
s = convert16bit(server_public_key)
|
46
48
|
context = "\0"
|
47
49
|
context += [c.bytesize].pack("n*")
|
48
50
|
context += c
|
@@ -77,5 +79,15 @@ module Webpush
|
|
77
79
|
def convert16bit(key)
|
78
80
|
[key.to_s(16)].pack("H*")
|
79
81
|
end
|
82
|
+
|
83
|
+
def assert_arguments(message, p256dh, auth)
|
84
|
+
raise ArgumentError, "message cannot be blank" if blank?(message)
|
85
|
+
raise ArgumentError, "p256dh cannot be blank" if blank?(p256dh)
|
86
|
+
raise ArgumentError, "auth cannot be blank" if blank?(auth)
|
87
|
+
end
|
88
|
+
|
89
|
+
def blank?(value)
|
90
|
+
value.nil? || value.empty?
|
91
|
+
end
|
80
92
|
end
|
81
93
|
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module Webpush
|
2
|
+
class Request
|
3
|
+
def initialize(endpoint, options = {})
|
4
|
+
@endpoint = endpoint
|
5
|
+
@options = default_options.merge(options)
|
6
|
+
@payload = @options.delete(:payload) || {}
|
7
|
+
end
|
8
|
+
|
9
|
+
def perform
|
10
|
+
uri = URI.parse(@endpoint)
|
11
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
12
|
+
http.use_ssl = true
|
13
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
14
|
+
req = Net::HTTP::Post.new(uri.request_uri, headers)
|
15
|
+
req.body = body
|
16
|
+
res = http.request(req)
|
17
|
+
res.code == "201"
|
18
|
+
rescue
|
19
|
+
false
|
20
|
+
end
|
21
|
+
|
22
|
+
def headers
|
23
|
+
headers = {}
|
24
|
+
headers["Content-Type"] = "application/octet-stream"
|
25
|
+
headers["Ttl"] = ttl
|
26
|
+
|
27
|
+
if encrypted_payload?
|
28
|
+
headers["Content-Encoding"] = "aesgcm"
|
29
|
+
headers["Encryption"] = "salt=#{salt_param}"
|
30
|
+
headers["Crypto-Key"] = "dh=#{dh_param}"
|
31
|
+
end
|
32
|
+
|
33
|
+
headers["Authorization"] = "key=#{api_key}" if api_key?
|
34
|
+
|
35
|
+
headers
|
36
|
+
end
|
37
|
+
|
38
|
+
def body
|
39
|
+
@payload.fetch(:ciphertext, "")
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def ttl
|
45
|
+
@options.fetch(:ttl).to_s
|
46
|
+
end
|
47
|
+
|
48
|
+
def api_key
|
49
|
+
@options.fetch(:api_key, nil)
|
50
|
+
end
|
51
|
+
|
52
|
+
def api_key?
|
53
|
+
!(api_key.nil? || api_key.empty?)
|
54
|
+
end
|
55
|
+
|
56
|
+
def encrypted_payload?
|
57
|
+
[:ciphertext, :server_public_key_bn, :salt].all? { |key| @payload.has_key?(key) }
|
58
|
+
end
|
59
|
+
|
60
|
+
def dh_param
|
61
|
+
Base64.urlsafe_encode64(@payload.fetch(:server_public_key_bn)).delete('=')
|
62
|
+
end
|
63
|
+
|
64
|
+
def salt_param
|
65
|
+
Base64.urlsafe_encode64(@payload.fetch(:salt)).delete('=')
|
66
|
+
end
|
67
|
+
|
68
|
+
def default_options
|
69
|
+
{
|
70
|
+
api_key: nil,
|
71
|
+
ttl: 60*60*24*7*4 # 4 weeks
|
72
|
+
}
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
data/lib/webpush/version.rb
CHANGED
data/lib/webpush.rb
CHANGED
@@ -6,6 +6,7 @@ require 'json'
|
|
6
6
|
|
7
7
|
require 'webpush/version'
|
8
8
|
require 'webpush/encryption'
|
9
|
+
require 'webpush/request'
|
9
10
|
|
10
11
|
module Webpush
|
11
12
|
|
@@ -14,36 +15,31 @@ module Webpush
|
|
14
15
|
TEMP_GCM_URL = 'https://gcm-http.googleapis.com/gcm'
|
15
16
|
|
16
17
|
class << self
|
17
|
-
|
18
|
+
# Deliver the payload to the required endpoint given by the JavaScript
|
19
|
+
# PushSubscription. Including an optional message requires p256dh and
|
20
|
+
# auth keys from the PushSubscription.
|
21
|
+
#
|
22
|
+
# @param endpoint [String] the required PushSubscription url
|
23
|
+
# @param message [String] the optional payload
|
24
|
+
# @param p256dh [String] the user's public ECDH key given by the PushSubscription
|
25
|
+
# @param auth [String] the user's private ECDH key given by the PushSubscription
|
26
|
+
# @param options [Hash<Symbol,String>] additional options for the notification
|
27
|
+
# @option options [String] :api_key required for Google, omit for Firefox
|
28
|
+
# @option options [#to_s] :ttl Time-to-live in seconds
|
29
|
+
def payload_send(endpoint:, message: "", p256dh: "", auth: "", **options)
|
18
30
|
endpoint = endpoint.gsub(GCM_URL, TEMP_GCM_URL)
|
19
31
|
|
20
|
-
payload =
|
21
|
-
|
32
|
+
payload = build_payload(message, p256dh, auth)
|
33
|
+
|
34
|
+
Webpush::Request.new(endpoint, options.merge(payload: payload)).perform
|
22
35
|
end
|
23
36
|
|
24
37
|
private
|
25
38
|
|
26
|
-
def
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
http.use_ssl = true
|
31
|
-
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
32
|
-
header = {
|
33
|
-
"Content-Type" => "application/octet-stream",
|
34
|
-
"Content-Encoding" => "aesgcm",
|
35
|
-
"Encryption" => "salt=#{Base64.urlsafe_encode64(payload[:salt]).delete('=')}",
|
36
|
-
"Crypto-Key" => "dh=#{Base64.urlsafe_encode64(payload[:server_public_key_bn]).delete('=')}",
|
37
|
-
"Ttl" => "2419200"
|
38
|
-
}
|
39
|
-
header["Authorization"] = "key=#{api_key}" unless api_key.empty?
|
40
|
-
req = Net::HTTP::Post.new(uri.request_uri, header)
|
41
|
-
req.body = payload[:ciphertext]
|
42
|
-
res = http.request(req)
|
43
|
-
return ("201" == res.code) ? true : false
|
44
|
-
rescue
|
45
|
-
return false
|
46
|
-
end
|
39
|
+
def build_payload(message, p256dh, auth)
|
40
|
+
return {} if message.nil? || message.empty?
|
41
|
+
|
42
|
+
Webpush::Encryption.encrypt(message, p256dh, auth)
|
47
43
|
end
|
48
44
|
end
|
49
45
|
end
|
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.2.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-05-
|
11
|
+
date: 2016-05-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: hkdf
|
@@ -123,9 +123,11 @@ files:
|
|
123
123
|
- Rakefile
|
124
124
|
- bin/console
|
125
125
|
- bin/rake
|
126
|
+
- bin/rspec
|
126
127
|
- bin/setup
|
127
128
|
- lib/webpush.rb
|
128
129
|
- lib/webpush/encryption.rb
|
130
|
+
- lib/webpush/request.rb
|
129
131
|
- lib/webpush/version.rb
|
130
132
|
- webpush.gemspec
|
131
133
|
homepage: https://github.com/zaru/webpush
|