web-push 1.0.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 +7 -0
- data/.gitignore +10 -0
- data/.rspec +2 -0
- data/.rubocop.yml +30 -0
- data/.travis.yml +24 -0
- data/CHANGELOG.md +183 -0
- data/Gemfile +4 -0
- data/LICENSE +21 -0
- data/README.md +356 -0
- data/Rakefile +8 -0
- data/bin/console +14 -0
- data/bin/rake +16 -0
- data/bin/rspec +16 -0
- data/bin/setup +8 -0
- data/lib/tasks/web_push.rake +14 -0
- data/lib/web-push.rb +3 -0
- data/lib/web_push/encryption.rb +77 -0
- data/lib/web_push/errors.rb +29 -0
- data/lib/web_push/railtie.rb +9 -0
- data/lib/web_push/request.rb +189 -0
- data/lib/web_push/vapid_key.rb +105 -0
- data/lib/web_push/version.rb +5 -0
- data/lib/web_push.rb +80 -0
- data/spec/spec_helper.rb +23 -0
- data/spec/web_push/encryption_spec.rb +95 -0
- data/spec/web_push/request_spec.rb +162 -0
- data/spec/web_push/vapid_key_spec.rb +66 -0
- data/spec/web_push_spec.rb +253 -0
- data/web-push.gemspec +25 -0
- metadata +183 -0
@@ -0,0 +1,162 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe WebPush::Request do
|
4
|
+
describe '#headers' do
|
5
|
+
let(:request) { build_request }
|
6
|
+
|
7
|
+
it { expect(request.headers['Content-Type']).to eq('application/octet-stream') }
|
8
|
+
it { expect(request.headers['Ttl']).to eq('2419200') }
|
9
|
+
it { expect(request.headers['Urgency']).to eq('normal') }
|
10
|
+
|
11
|
+
describe 'from :message' do
|
12
|
+
it 'inserts encryption headers for valid payload' do
|
13
|
+
allow(WebPush::Encryption).to receive(:encrypt).and_return('encrypted')
|
14
|
+
request = build_request(message: 'Hello')
|
15
|
+
|
16
|
+
expect(request.headers['Content-Encoding']).to eq('aes128gcm')
|
17
|
+
expect(request.headers['Content-Length']).to eq('9')
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
describe 'from :api_key' do
|
22
|
+
def build_request_with_api_key(endpoint, options = {})
|
23
|
+
subscription = {
|
24
|
+
endpoint: endpoint,
|
25
|
+
keys: {
|
26
|
+
p256dh: 'p256dh',
|
27
|
+
auth: 'auth'
|
28
|
+
}
|
29
|
+
}
|
30
|
+
WebPush::Request.new(message: '', subscription: subscription, vapid: {}, **options)
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'inserts Authorization header when api_key present, and endpoint is for Chrome\'s non-standards-compliant GCM endpoints' do
|
34
|
+
request = build_request_with_api_key('https://gcm-http.googleapis.com/gcm/xyz', api_key: 'api_key')
|
35
|
+
|
36
|
+
expect(request.headers['Authorization']).to eq('key=api_key')
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'insert Authorization header for Chrome\'s new standards-compliant endpoints, even if api_key is present' do
|
40
|
+
request = build_request_with_api_key('https://fcm.googleapis.com/fcm/send/ABCD1234', api_key: 'api_key')
|
41
|
+
|
42
|
+
expect(request.headers['Authorization']).to eq('key=api_key')
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'does not insert Authorization header when endpoint is not for Chrome, even if api_key is present' do
|
46
|
+
request = build_request_with_api_key('https://some.random.endpoint.com/xyz', api_key: 'api_key')
|
47
|
+
|
48
|
+
expect(request.headers['Authorization']).to be_nil
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'does not insert Authorization header when api_key blank' do
|
52
|
+
request = build_request_with_api_key('endpoint', api_key: nil)
|
53
|
+
|
54
|
+
expect(request.headers['Authorization']).to be_nil
|
55
|
+
|
56
|
+
request = build_request_with_api_key('endpoint', api_key: '')
|
57
|
+
|
58
|
+
expect(request.headers['Authorization']).to be_nil
|
59
|
+
|
60
|
+
request = build_request_with_api_key('endpoint')
|
61
|
+
|
62
|
+
expect(request.headers['Authorization']).to be_nil
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
describe 'from :ttl' do
|
67
|
+
it 'can override Ttl with :ttl option with string' do
|
68
|
+
request = build_request(ttl: '300')
|
69
|
+
|
70
|
+
expect(request.headers['Ttl']).to eq('300')
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'can override Ttl with :ttl option with fixnum' do
|
74
|
+
request = build_request(ttl: 60 * 5)
|
75
|
+
|
76
|
+
expect(request.headers['Ttl']).to eq('300')
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
describe 'from :urgency' do
|
81
|
+
it 'can override Urgency with :urgency option' do
|
82
|
+
request = build_request(urgency: 'high')
|
83
|
+
|
84
|
+
expect(request.headers['Urgency']).to eq('high')
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
describe '#build_vapid_header' do
|
90
|
+
it 'returns the VAPID header' do
|
91
|
+
time = Time.at(1_476_150_897)
|
92
|
+
jwt_payload = {
|
93
|
+
aud: 'https://fcm.googleapis.com',
|
94
|
+
exp: time.to_i + 24 * 60 * 60,
|
95
|
+
sub: 'mailto:sender@example.com'
|
96
|
+
}
|
97
|
+
jwt_header_fields = { "typ": "JWT", "alg": "ES256" }
|
98
|
+
|
99
|
+
vapid_key = WebPush::VapidKey.from_keys(vapid_public_key, vapid_private_key)
|
100
|
+
expect(Time).to receive(:now).and_return(time)
|
101
|
+
expect(WebPush::VapidKey).to receive(:from_keys).with(vapid_public_key, vapid_private_key).and_return(vapid_key)
|
102
|
+
expect(JWT).to receive(:encode).with(jwt_payload, vapid_key.curve, 'ES256', jwt_header_fields).and_return('jwt.encoded.payload')
|
103
|
+
|
104
|
+
request = build_request
|
105
|
+
header = request.build_vapid_header
|
106
|
+
|
107
|
+
expect(header).to eq("vapid t=jwt.encoded.payload,k=#{vapid_public_key.delete('=')}")
|
108
|
+
end
|
109
|
+
|
110
|
+
it 'supports PEM format' do
|
111
|
+
pem = WebPush::VapidKey.new.to_pem
|
112
|
+
expect(WebPush::VapidKey).to receive(:from_pem).with(pem).and_call_original
|
113
|
+
request = build_request(vapid: { subject: 'mailto:sender@example.com', pem: pem })
|
114
|
+
request.build_vapid_header
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
describe '#body' do
|
119
|
+
it 'is set to the payload if a message is provided' do
|
120
|
+
allow(WebPush::Encryption).to receive(:encrypt).and_return('encrypted')
|
121
|
+
|
122
|
+
request = build_request(message: 'Hello')
|
123
|
+
|
124
|
+
expect(request.body).to eq('encrypted')
|
125
|
+
end
|
126
|
+
|
127
|
+
it 'is empty string when no message is provided' do
|
128
|
+
request = build_request
|
129
|
+
|
130
|
+
expect(request.body).to eq('')
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
describe '#proxy_options' do
|
135
|
+
it 'returns an array of proxy options' do
|
136
|
+
request = build_request(proxy: 'http://user:password@proxy_addr:8080')
|
137
|
+
|
138
|
+
expect(request.proxy_options).to eq(['proxy_addr', 8080, 'user', 'password'])
|
139
|
+
end
|
140
|
+
|
141
|
+
it 'returns empty array' do
|
142
|
+
request = build_request
|
143
|
+
|
144
|
+
expect(request.proxy_options).to be_empty
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def build_request(options = {})
|
149
|
+
subscription = {
|
150
|
+
endpoint: endpoint,
|
151
|
+
keys: {
|
152
|
+
p256dh: 'p256dh',
|
153
|
+
auth: 'auth'
|
154
|
+
}
|
155
|
+
}
|
156
|
+
WebPush::Request.new(message: '', subscription: subscription, vapid: vapid_options, **options)
|
157
|
+
end
|
158
|
+
|
159
|
+
def endpoint
|
160
|
+
'https://fcm.googleapis.com/gcm/send/subscription-id'
|
161
|
+
end
|
162
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe WebPush::VapidKey do
|
4
|
+
it 'generates an elliptic curve' do
|
5
|
+
key = WebPush::VapidKey.new
|
6
|
+
expect(key.curve).to be_a(OpenSSL::PKey::EC)
|
7
|
+
expect(key.curve_name).to eq('prime256v1')
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'returns an encoded public key' do
|
11
|
+
key = WebPush::VapidKey.new
|
12
|
+
|
13
|
+
expect(Base64.urlsafe_decode64(key.public_key).bytesize).to eq(65)
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'returns an encoded private key' do
|
17
|
+
key = WebPush::VapidKey.new
|
18
|
+
|
19
|
+
expect(Base64.urlsafe_decode64(key.private_key).bytesize).to eq(32)
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'pretty prints encoded keys' do
|
23
|
+
key = WebPush::VapidKey.new
|
24
|
+
printed = key.inspect
|
25
|
+
|
26
|
+
expect(printed).to match(/public_key=#{key.public_key}/)
|
27
|
+
expect(printed).to match(/private_key=#{key.private_key}/)
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'returns hash of public and private keys' do
|
31
|
+
key = WebPush::VapidKey.new
|
32
|
+
hash = key.to_h
|
33
|
+
|
34
|
+
expect(hash[:public_key]).to eq(key.public_key)
|
35
|
+
expect(hash[:private_key]).to eq(key.private_key)
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'returns pem of public and private keys' do
|
39
|
+
key = WebPush::VapidKey.new
|
40
|
+
pem = key.to_pem
|
41
|
+
|
42
|
+
expect(pem).to include('-----BEGIN EC PRIVATE KEY-----')
|
43
|
+
expect(pem).to include('-----BEGIN PUBLIC KEY-----')
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'imports pem of public and private keys' do
|
47
|
+
pem = WebPush::VapidKey.new.to_pem
|
48
|
+
key = WebPush::VapidKey.from_pem pem
|
49
|
+
|
50
|
+
expect(key.to_pem).to eq(pem)
|
51
|
+
end
|
52
|
+
|
53
|
+
describe 'self.from_keys' do
|
54
|
+
it 'returns an encoded public key' do
|
55
|
+
key = WebPush::VapidKey.from_keys(vapid_public_key, vapid_private_key)
|
56
|
+
|
57
|
+
expect(key.public_key).to eq(vapid_public_key)
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'returns an encoded private key' do
|
61
|
+
key = WebPush::VapidKey.from_keys(vapid_public_key, vapid_private_key)
|
62
|
+
|
63
|
+
expect(key.private_key).to eq(vapid_private_key)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,253 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe WebPush do
|
4
|
+
it 'has a version number' do
|
5
|
+
expect(WebPush::VERSION).not_to be nil
|
6
|
+
end
|
7
|
+
|
8
|
+
shared_examples 'web push protocol standard error handling' do
|
9
|
+
it 'raises InvalidSubscription if the API returns a 404 Error' do
|
10
|
+
stub_request(:post, expected_endpoint)
|
11
|
+
.to_return(status: 404, body: '', headers: {})
|
12
|
+
expect { subject }.to raise_error(WebPush::InvalidSubscription)
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'raises ExpiredSubscription if the API returns a 410 Error' do
|
16
|
+
stub_request(:post, expected_endpoint)
|
17
|
+
.to_return(status: 410, body: '', headers: {})
|
18
|
+
expect { subject }.to raise_error(WebPush::ExpiredSubscription)
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'raises Unauthorized if the API returns a 401 Error, a 403 Error or 400 with specific message' do
|
22
|
+
stub_request(:post, expected_endpoint)
|
23
|
+
.to_return(status: 401, body: '', headers: {})
|
24
|
+
expect { subject }.to raise_error(WebPush::Unauthorized)
|
25
|
+
|
26
|
+
stub_request(:post, expected_endpoint)
|
27
|
+
.to_return(status: 403, body: '', headers: {})
|
28
|
+
expect { subject }.to raise_error(WebPush::Unauthorized)
|
29
|
+
|
30
|
+
stub_request(:post, expected_endpoint)
|
31
|
+
.to_return(status: [400, 'UnauthorizedRegistration'], body: '', headers: {})
|
32
|
+
expect { subject }.to raise_error(WebPush::Unauthorized)
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'raises PayloadTooLarge if the API returns a 413 Error' do
|
36
|
+
stub_request(:post, expected_endpoint)
|
37
|
+
.to_return(status: 413, body: '', headers: {})
|
38
|
+
expect { subject }.to raise_error(WebPush::PayloadTooLarge)
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'raises TooManyRequests if the API returns a 429 Error' do
|
42
|
+
stub_request(:post, expected_endpoint)
|
43
|
+
.to_return(status: 429, body: '', headers: {})
|
44
|
+
expect { subject }.to raise_error(WebPush::TooManyRequests)
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'raises PushServiceError if the API returns a 5xx Error' do
|
48
|
+
stub_request(:post, expected_endpoint)
|
49
|
+
.to_return(status: 500, body: '', headers: {})
|
50
|
+
expect { subject }.to raise_error(WebPush::PushServiceError)
|
51
|
+
|
52
|
+
stub_request(:post, expected_endpoint)
|
53
|
+
.to_return(status: 503, body: '', headers: {})
|
54
|
+
expect { subject }.to raise_error(WebPush::PushServiceError)
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'raises ResponseError for unsuccessful status code by default' do
|
58
|
+
stub_request(:post, expected_endpoint)
|
59
|
+
.to_return(status: 401, body: '', headers: {})
|
60
|
+
|
61
|
+
expect { subject }.to raise_error(WebPush::ResponseError)
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'supplies the original status code on the ResponseError' do
|
65
|
+
stub_request(:post, expected_endpoint)
|
66
|
+
.to_return(status: 401, body: 'Oh snap', headers: {})
|
67
|
+
|
68
|
+
expect { subject }.to raise_error { |error|
|
69
|
+
expect(error).to be_a(WebPush::ResponseError)
|
70
|
+
expect(error.response.code).to eq '401'
|
71
|
+
expect(error.response.body).to eq 'Oh snap'
|
72
|
+
}
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'sets the error message to be the host + stringified response' do
|
76
|
+
stub_request(:post, expected_endpoint)
|
77
|
+
.to_return(status: 401, body: 'Oh snap', headers: {})
|
78
|
+
|
79
|
+
host = URI.parse(expected_endpoint).host
|
80
|
+
|
81
|
+
expect { subject }.to raise_error { |error|
|
82
|
+
expect(error.message).to eq(
|
83
|
+
"host: #{host}, #<Net::HTTPUnauthorized 401 readbody=true>\nbody:\nOh snap"
|
84
|
+
)
|
85
|
+
}
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'raises exception on error by default' do
|
89
|
+
stub_request(:post, expected_endpoint).to_raise(StandardError)
|
90
|
+
|
91
|
+
expect { subject }.to raise_error(StandardError)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
shared_examples 'request headers with VAPID' do
|
96
|
+
let(:message) { JSON.generate(body: 'body') }
|
97
|
+
let(:p256dh) { 'BN4GvZtEZiZuqFxSKVZfSfluwKBD7UxHNBmWkfiZfCtgDE8Bwh-_MtLXbBxTBAWH9r7IPKL0lhdcaqtL1dfxU5E=' }
|
98
|
+
let(:auth) { 'Q2BoAjC09xH3ywDLNJr-dA==' }
|
99
|
+
let(:payload) { "encrypted" }
|
100
|
+
let(:expected_body) { payload }
|
101
|
+
let(:expected_headers) do
|
102
|
+
{
|
103
|
+
'Accept' => '*/*',
|
104
|
+
'Accept-Encoding' => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3',
|
105
|
+
'Content-Encoding' => 'aes128gcm',
|
106
|
+
'Content-Type' => 'application/octet-stream',
|
107
|
+
'Ttl' => '2419200',
|
108
|
+
'Urgency' => 'normal',
|
109
|
+
'User-Agent' => 'Ruby'
|
110
|
+
}
|
111
|
+
end
|
112
|
+
|
113
|
+
let(:vapid_header) { "vapid t=jwt.encoded.payload,k=#{vapid_public_key.delete('=')}" }
|
114
|
+
|
115
|
+
before do
|
116
|
+
allow(WebPush::Encryption).to receive(:encrypt).and_return(payload)
|
117
|
+
allow(JWT).to receive(:encode).and_return('jwt.encoded.payload')
|
118
|
+
end
|
119
|
+
|
120
|
+
subject do
|
121
|
+
WebPush.payload_send(
|
122
|
+
message: message,
|
123
|
+
endpoint: endpoint,
|
124
|
+
p256dh: p256dh,
|
125
|
+
auth: auth,
|
126
|
+
vapid: vapid_options
|
127
|
+
)
|
128
|
+
end
|
129
|
+
|
130
|
+
it 'calls the relevant service with the correct headers' do
|
131
|
+
expect(WebPush::Encryption).to receive(:encrypt).and_return(payload)
|
132
|
+
|
133
|
+
expected_headers['Authorization'] = vapid_header
|
134
|
+
|
135
|
+
stub_request(:post, expected_endpoint)
|
136
|
+
.with(body: expected_body, headers: expected_headers)
|
137
|
+
.to_return(status: 201, body: '', headers: {})
|
138
|
+
|
139
|
+
result = subject
|
140
|
+
|
141
|
+
expect(result).to be_a(Net::HTTPCreated)
|
142
|
+
expect(result.code).to eql('201')
|
143
|
+
end
|
144
|
+
|
145
|
+
include_examples 'web push protocol standard error handling'
|
146
|
+
|
147
|
+
it 'message is optional' do
|
148
|
+
expect(WebPush::Encryption).to_not receive(:encrypt)
|
149
|
+
|
150
|
+
expected_headers.delete('Crypto-Key')
|
151
|
+
expected_headers.delete('Content-Encoding')
|
152
|
+
expected_headers.delete('Encryption')
|
153
|
+
|
154
|
+
stub_request(:post, expected_endpoint)
|
155
|
+
.with(body: nil, headers: expected_headers)
|
156
|
+
.to_return(status: 201, body: '', headers: {})
|
157
|
+
|
158
|
+
WebPush.payload_send(endpoint: endpoint)
|
159
|
+
end
|
160
|
+
|
161
|
+
it 'vapid options are optional' do
|
162
|
+
expect(WebPush::Encryption).to receive(:encrypt).and_return(payload)
|
163
|
+
|
164
|
+
expected_headers.delete('Crypto-Key')
|
165
|
+
expected_headers.delete('Authorization')
|
166
|
+
|
167
|
+
stub_request(:post, expected_endpoint)
|
168
|
+
.with(body: expected_body, headers: expected_headers)
|
169
|
+
.to_return(status: 201, body: '', headers: {})
|
170
|
+
|
171
|
+
result = WebPush.payload_send(
|
172
|
+
message: message,
|
173
|
+
endpoint: endpoint,
|
174
|
+
p256dh: p256dh,
|
175
|
+
auth: auth
|
176
|
+
)
|
177
|
+
|
178
|
+
expect(result).to be_a(Net::HTTPCreated)
|
179
|
+
expect(result.code).to eql('201')
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
context 'chrome FCM endpoint' do
|
184
|
+
let(:endpoint) { 'https://fcm.googleapis.com/gcm/send/subscription-id' }
|
185
|
+
let(:expected_endpoint) { endpoint }
|
186
|
+
|
187
|
+
include_examples 'request headers with VAPID'
|
188
|
+
end
|
189
|
+
|
190
|
+
context 'firefox endpoint' do
|
191
|
+
let(:endpoint) { 'https://updates.push.services.mozilla.com/push/v1/subscription-id' }
|
192
|
+
let(:expected_endpoint) { endpoint }
|
193
|
+
|
194
|
+
include_examples 'request headers with VAPID'
|
195
|
+
end
|
196
|
+
|
197
|
+
context 'chrome GCM endpoint: request headers with GCM api key' do
|
198
|
+
let(:endpoint) { 'https://android.googleapis.com/gcm/send/subscription-id' }
|
199
|
+
let(:expected_endpoint) { 'https://fcm.googleapis.com/fcm/subscription-id' }
|
200
|
+
|
201
|
+
let(:message) { JSON.generate(body: 'body') }
|
202
|
+
let(:p256dh) { 'BN4GvZtEZiZuqFxSKVZfSfluwKBD7UxHNBmWkfiZfCtgDE8Bwh-_MtLXbBxTBAWH9r7IPKL0lhdcaqtL1dfxU5E=' }
|
203
|
+
let(:auth) { 'Q2BoAjC09xH3ywDLNJr-dA==' }
|
204
|
+
let(:payload) { "encrypted" }
|
205
|
+
let(:expected_body) { payload }
|
206
|
+
let(:expected_headers) do
|
207
|
+
{
|
208
|
+
'Accept' => '*/*',
|
209
|
+
'Accept-Encoding' => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3',
|
210
|
+
'Content-Encoding' => 'aes128gcm',
|
211
|
+
'Content-Type' => 'application/octet-stream',
|
212
|
+
'Ttl' => '2419200',
|
213
|
+
'Urgency' => 'normal',
|
214
|
+
'User-Agent' => 'Ruby'
|
215
|
+
}
|
216
|
+
end
|
217
|
+
|
218
|
+
let(:subscription) {}
|
219
|
+
|
220
|
+
before do
|
221
|
+
allow(WebPush::Encryption).to receive(:encrypt).and_return(payload)
|
222
|
+
end
|
223
|
+
|
224
|
+
subject { WebPush.payload_send(message: message, endpoint: endpoint, p256dh: p256dh, auth: auth, api_key: 'GCM_API_KEY') }
|
225
|
+
|
226
|
+
it 'calls the relevant service with the correct headers' do
|
227
|
+
expect(WebPush::Encryption).to receive(:encrypt).and_return(payload)
|
228
|
+
|
229
|
+
stub_request(:post, expected_endpoint)
|
230
|
+
.with(body: expected_body, headers: expected_headers)
|
231
|
+
.to_return(status: 201, body: '', headers: {})
|
232
|
+
|
233
|
+
result = subject
|
234
|
+
|
235
|
+
expect(result).to be_a(Net::HTTPCreated)
|
236
|
+
expect(result.code).to eql('201')
|
237
|
+
end
|
238
|
+
|
239
|
+
include_examples 'web push protocol standard error handling'
|
240
|
+
|
241
|
+
it 'message and encryption keys are optional' do
|
242
|
+
expect(WebPush::Encryption).to_not receive(:encrypt)
|
243
|
+
|
244
|
+
expected_headers.delete('Content-Encoding')
|
245
|
+
|
246
|
+
stub_request(:post, expected_endpoint)
|
247
|
+
.with(body: nil, headers: expected_headers)
|
248
|
+
.to_return(status: 201, body: '', headers: {})
|
249
|
+
|
250
|
+
WebPush.payload_send(endpoint: endpoint, api_key: 'GCM_API_KEY')
|
251
|
+
end
|
252
|
+
end
|
253
|
+
end
|
data/web-push.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
require_relative 'lib/web_push/version'
|
2
|
+
|
3
|
+
Gem::Specification.new do |spec|
|
4
|
+
spec.name = 'web-push'
|
5
|
+
spec.version = WebPush::VERSION
|
6
|
+
spec.authors = ['zaru', 'collimarco']
|
7
|
+
spec.email = ['support@pushpad.xyz']
|
8
|
+
|
9
|
+
spec.summary = 'Web Push library for Ruby (RFC8030)'
|
10
|
+
spec.homepage = 'https://github.com/pushpad/web-push'
|
11
|
+
|
12
|
+
spec.files = `git ls-files`.split("\n")
|
13
|
+
|
14
|
+
spec.required_ruby_version = '>= 2.2'
|
15
|
+
|
16
|
+
spec.add_dependency 'hkdf', '~> 0.2'
|
17
|
+
spec.add_dependency 'jwt', '~> 2.0'
|
18
|
+
|
19
|
+
spec.add_development_dependency 'bundler', '>= 1.17.3'
|
20
|
+
spec.add_development_dependency 'pry'
|
21
|
+
spec.add_development_dependency 'rake', '>= 10.0'
|
22
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
23
|
+
spec.add_development_dependency 'simplecov'
|
24
|
+
spec.add_development_dependency 'webmock', '~> 3.0'
|
25
|
+
end
|
metadata
ADDED
@@ -0,0 +1,183 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: web-push
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- zaru
|
8
|
+
- collimarco
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2022-11-26 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: hkdf
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - "~>"
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '0.2'
|
21
|
+
type: :runtime
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - "~>"
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: '0.2'
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: jwt
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - "~>"
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '2.0'
|
35
|
+
type: :runtime
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - "~>"
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '2.0'
|
42
|
+
- !ruby/object:Gem::Dependency
|
43
|
+
name: bundler
|
44
|
+
requirement: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - ">="
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: 1.17.3
|
49
|
+
type: :development
|
50
|
+
prerelease: false
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: 1.17.3
|
56
|
+
- !ruby/object:Gem::Dependency
|
57
|
+
name: pry
|
58
|
+
requirement: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '0'
|
63
|
+
type: :development
|
64
|
+
prerelease: false
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - ">="
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
- !ruby/object:Gem::Dependency
|
71
|
+
name: rake
|
72
|
+
requirement: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '10.0'
|
77
|
+
type: :development
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - ">="
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '10.0'
|
84
|
+
- !ruby/object:Gem::Dependency
|
85
|
+
name: rspec
|
86
|
+
requirement: !ruby/object:Gem::Requirement
|
87
|
+
requirements:
|
88
|
+
- - "~>"
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: '3.0'
|
91
|
+
type: :development
|
92
|
+
prerelease: false
|
93
|
+
version_requirements: !ruby/object:Gem::Requirement
|
94
|
+
requirements:
|
95
|
+
- - "~>"
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: '3.0'
|
98
|
+
- !ruby/object:Gem::Dependency
|
99
|
+
name: simplecov
|
100
|
+
requirement: !ruby/object:Gem::Requirement
|
101
|
+
requirements:
|
102
|
+
- - ">="
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: '0'
|
105
|
+
type: :development
|
106
|
+
prerelease: false
|
107
|
+
version_requirements: !ruby/object:Gem::Requirement
|
108
|
+
requirements:
|
109
|
+
- - ">="
|
110
|
+
- !ruby/object:Gem::Version
|
111
|
+
version: '0'
|
112
|
+
- !ruby/object:Gem::Dependency
|
113
|
+
name: webmock
|
114
|
+
requirement: !ruby/object:Gem::Requirement
|
115
|
+
requirements:
|
116
|
+
- - "~>"
|
117
|
+
- !ruby/object:Gem::Version
|
118
|
+
version: '3.0'
|
119
|
+
type: :development
|
120
|
+
prerelease: false
|
121
|
+
version_requirements: !ruby/object:Gem::Requirement
|
122
|
+
requirements:
|
123
|
+
- - "~>"
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '3.0'
|
126
|
+
description:
|
127
|
+
email:
|
128
|
+
- support@pushpad.xyz
|
129
|
+
executables: []
|
130
|
+
extensions: []
|
131
|
+
extra_rdoc_files: []
|
132
|
+
files:
|
133
|
+
- ".gitignore"
|
134
|
+
- ".rspec"
|
135
|
+
- ".rubocop.yml"
|
136
|
+
- ".travis.yml"
|
137
|
+
- CHANGELOG.md
|
138
|
+
- Gemfile
|
139
|
+
- LICENSE
|
140
|
+
- README.md
|
141
|
+
- Rakefile
|
142
|
+
- bin/console
|
143
|
+
- bin/rake
|
144
|
+
- bin/rspec
|
145
|
+
- bin/setup
|
146
|
+
- lib/tasks/web_push.rake
|
147
|
+
- lib/web-push.rb
|
148
|
+
- lib/web_push.rb
|
149
|
+
- lib/web_push/encryption.rb
|
150
|
+
- lib/web_push/errors.rb
|
151
|
+
- lib/web_push/railtie.rb
|
152
|
+
- lib/web_push/request.rb
|
153
|
+
- lib/web_push/vapid_key.rb
|
154
|
+
- lib/web_push/version.rb
|
155
|
+
- spec/spec_helper.rb
|
156
|
+
- spec/web_push/encryption_spec.rb
|
157
|
+
- spec/web_push/request_spec.rb
|
158
|
+
- spec/web_push/vapid_key_spec.rb
|
159
|
+
- spec/web_push_spec.rb
|
160
|
+
- web-push.gemspec
|
161
|
+
homepage: https://github.com/pushpad/web-push
|
162
|
+
licenses: []
|
163
|
+
metadata: {}
|
164
|
+
post_install_message:
|
165
|
+
rdoc_options: []
|
166
|
+
require_paths:
|
167
|
+
- lib
|
168
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
169
|
+
requirements:
|
170
|
+
- - ">="
|
171
|
+
- !ruby/object:Gem::Version
|
172
|
+
version: '2.2'
|
173
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
174
|
+
requirements:
|
175
|
+
- - ">="
|
176
|
+
- !ruby/object:Gem::Version
|
177
|
+
version: '0'
|
178
|
+
requirements: []
|
179
|
+
rubygems_version: 3.0.3.1
|
180
|
+
signing_key:
|
181
|
+
specification_version: 4
|
182
|
+
summary: Web Push library for Ruby (RFC8030)
|
183
|
+
test_files: []
|