rpush 5.2.0 → 5.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.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +16 -0
  3. data/README.md +49 -4
  4. data/lib/rpush/client/active_model.rb +3 -0
  5. data/lib/rpush/client/active_model/adm/data_validator.rb +1 -1
  6. data/lib/rpush/client/active_model/apns/device_token_format_validator.rb +2 -2
  7. data/lib/rpush/client/active_model/apns/notification_payload_size_validator.rb +1 -1
  8. data/lib/rpush/client/active_model/gcm/expiry_collapse_key_mutual_inclusion_validator.rb +1 -1
  9. data/lib/rpush/client/active_model/payload_data_size_validator.rb +1 -1
  10. data/lib/rpush/client/active_model/registration_ids_count_validator.rb +1 -1
  11. data/lib/rpush/client/active_model/webpush/app.rb +41 -0
  12. data/lib/rpush/client/active_model/webpush/notification.rb +66 -0
  13. data/lib/rpush/client/active_record.rb +3 -0
  14. data/lib/rpush/client/active_record/apnsp8/notification.rb +1 -0
  15. data/lib/rpush/client/active_record/webpush/app.rb +11 -0
  16. data/lib/rpush/client/active_record/webpush/notification.rb +12 -0
  17. data/lib/rpush/client/redis.rb +3 -0
  18. data/lib/rpush/client/redis/apnsp8/notification.rb +2 -0
  19. data/lib/rpush/client/redis/webpush/app.rb +15 -0
  20. data/lib/rpush/client/redis/webpush/notification.rb +15 -0
  21. data/lib/rpush/configuration.rb +1 -1
  22. data/lib/rpush/daemon.rb +3 -0
  23. data/lib/rpush/daemon/apns2/delivery.rb +7 -1
  24. data/lib/rpush/daemon/webpush.rb +10 -0
  25. data/lib/rpush/daemon/webpush/delivery.rb +114 -0
  26. data/lib/rpush/version.rb +1 -1
  27. data/spec/functional/apns2_spec.rb +12 -2
  28. data/spec/functional/webpush_spec.rb +30 -0
  29. data/spec/spec_helper.rb +2 -0
  30. data/spec/unit/client/active_record/apnsp8/notification_spec.rb +28 -0
  31. data/spec/unit/client/active_record/webpush/app_spec.rb +6 -0
  32. data/spec/unit/client/active_record/webpush/notification_spec.rb +6 -0
  33. data/spec/unit/client/redis/apnsp8/notification_spec.rb +29 -0
  34. data/spec/unit/client/redis/webpush/app_spec.rb +5 -0
  35. data/spec/unit/client/redis/webpush/notification_spec.rb +5 -0
  36. data/spec/unit/client/shared/webpush/app.rb +33 -0
  37. data/spec/unit/client/shared/webpush/notification.rb +83 -0
  38. data/spec/unit/daemon/webpush/delivery_spec.rb +142 -0
  39. metadata +44 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6576c1456435dd596c992846892b840f204b4cf8956b13f0c06b9b336b6e0fd8
4
- data.tar.gz: 57ba3957011570e259585b3528f0bd344411f22db72bf03cea01bfe049107ac2
3
+ metadata.gz: 2da7543fed0193b0dcd20169efb5abd8170f15bc5043ef4ce2cb4d0e56380b4f
4
+ data.tar.gz: d7ba0f5b1dc97d17a7d23bf623d900b6da26b428ddcf4a8a93fce7641ab20f2d
5
5
  SHA512:
6
- metadata.gz: 1fcce116cbddf64aacbf00d2b7294056f3c502b59f108783b53a1c90807cd0816a68be2c43e543625d556ad7de484786edb2baad1dbdc2652cfc1c4f582539f9
7
- data.tar.gz: 0e468a1f43d8c422db967946fdc8d867bfc8bce6eaea511bff04e34108f77b75e9b0f903a3c008594482fe159ee9b820cc65be81008d60e2261b26f61c6ca274
6
+ metadata.gz: 745e0799a2e66d4813b99c4a93eb5d172dcbbe4c1317bd945b1addf177720f489bbdb0d83d918b9ab0ba4b98c35d186c0522d39740541097e16e90d0687487d8
7
+ data.tar.gz: e6d249ad9c7366cc54267e4eaa6f3d1bb3670dc6211380aa8bced26b33053cc0fda328f33b72a1d843cdd794e44e0793cd579e49745c60e46aa83155a5d0acca
@@ -1,5 +1,21 @@
1
1
  # Changelog
2
2
 
3
+ ## [v5.3.0](https://github.com/rpush/rpush/tree/v5.3.0) (2021-01-07)
4
+
5
+ [Full Changelog](https://github.com/rpush/rpush/compare/v5.2.0...v5.3.0)
6
+
7
+ **Implemented enhancements:**
8
+
9
+ - support for Webpush with VAPID [\#574](https://github.com/rpush/rpush/pull/574) ([jkraemer](https://github.com/jkraemer))
10
+
11
+ **Merged pull requests:**
12
+
13
+ - Bug fix: APNS P8 Notifications Are Marked as Invalid When the Payload Exceeds 2kb \(2048 bytes\) [\#583](https://github.com/rpush/rpush/pull/583) ([gregblake](https://github.com/gregblake))
14
+ - Fix more Rails 6.1 deprecation warnings [\#582](https://github.com/rpush/rpush/pull/582) ([jas14](https://github.com/jas14))
15
+ - Feature/apns2 default headers [\#579](https://github.com/rpush/rpush/pull/579) ([jkraemer](https://github.com/jkraemer))
16
+ - Fix APNS2 documentation in README [\#578](https://github.com/rpush/rpush/pull/578) ([jamestjw](https://github.com/jamestjw))
17
+ - Fixed typo with misspell [\#575](https://github.com/rpush/rpush/pull/575) ([hsbt](https://github.com/hsbt))
18
+
3
19
  ## [v5.2.0](https://github.com/rpush/rpush/tree/v5.2.0) (2020-10-08)
4
20
 
5
21
  [Full Changelog](https://github.com/rpush/rpush/compare/v5.1.0...v5.2.0)
data/README.md CHANGED
@@ -18,6 +18,7 @@ Rpush aims to be the *de facto* gem for sending push notifications in Ruby. Its
18
18
  * [**Amazon Device Messaging**](#amazon-device-messaging)
19
19
  * [**Windows Phone Push Notification Service**](#windows-phone-notification-service)
20
20
  * [**Pushy**](#pushy)
21
+ * [**Webpush**](#webpush)
21
22
 
22
23
  #### Feature Highlights
23
24
 
@@ -96,6 +97,7 @@ app.name = "ios_app"
96
97
  app.certificate = File.read("/path/to/sandbox.pem")
97
98
  app.environment = "development"
98
99
  app.password = "certificate password"
100
+ app.bundle_id = "BUNDLE ID" # the unique bundle id of the app, like com.example.appname
99
101
  app.connections = 1
100
102
  app.save!
101
103
  ```
@@ -106,9 +108,9 @@ n.app = Rpush::Apns2::App.find_by_name("ios_app")
106
108
  n.device_token = "..." # hex string
107
109
  n.alert = "hi mom!"
108
110
  n.data = {
109
- headers: { 'apns-topic': "BUNDLE ID", # the bundle id of the app, like com.example.appname
110
- foo: :bar }
111
- }
111
+ headers: { 'apns-topic': "BUNDLE ID" }, # the bundle id of the app, like com.example.appname. Not necessary if set on the app (see above)
112
+ foo: :bar
113
+ }
112
114
  n.save!
113
115
  ```
114
116
 
@@ -249,7 +251,7 @@ n.save!
249
251
 
250
252
  #### Windows Raw Push Notifications
251
253
 
252
- Note: The data is passed as `.to_json` so only this format is supported, altough raw notifications are meant to support any kind of data.
254
+ Note: The data is passed as `.to_json` so only this format is supported, although raw notifications are meant to support any kind of data.
253
255
  Current data structure enforces hashes and `.to_json` representation is natural presentation of it.
254
256
 
255
257
  ```ruby
@@ -297,6 +299,49 @@ n.save!
297
299
 
298
300
  For more documentation on [Pushy](https://pushy.me/docs).
299
301
 
302
+ #### Webpush
303
+
304
+ [Webpush](https://tools.ietf.org/html/draft-ietf-webpush-protocol-10) is a
305
+ protocol for delivering push messages to desktop browsers. It's supported by
306
+ all major browsers (except Safari, you have to use one of the Apns transports
307
+ for that).
308
+
309
+ Using [VAPID](https://tools.ietf.org/html/draft-ietf-webpush-vapid-01), there
310
+ is no need for the sender of push notifications to register upfront with push
311
+ services (as was the case with the now legacy Mozilla or Google desktop push
312
+ providers).
313
+
314
+ Instead, you generate a pair of keys and use the public key when subscribing
315
+ users in your web app. The keys are stored along with an email address (which,
316
+ according to the spec, can be used by push service providers to contact you in
317
+ case of problems) in the `certificates` field of the Rpush Application record:
318
+
319
+ ```ruby
320
+ vapid_keypair = Webpush.generate_key.to_hash
321
+ app = Rpush::Webpush::App.new
322
+ app.name = 'webpush'
323
+ app.certificate = vapid_keypair.merge(subject: 'user@example.org').to_json
324
+ app.connections = 1
325
+ app.save!
326
+ ```
327
+
328
+ The `subscription` object you obtain from a subscribed browser holds an
329
+ endpoint URL and cryptographic keys. When sending a notification, simply pass
330
+ the whole subscription as sole member of the `registration_ids` collection:
331
+
332
+ ```ruby
333
+ n = Rpush::Webpush::Notification.new
334
+ n.app = Rpush::App.find_by_name("webpush")
335
+ n.registration_ids = [subscription]
336
+ n.data = { message: "hi mom!" }
337
+ n.save!
338
+ ```
339
+
340
+ In order to send the same message to multiple devices, create one
341
+ `Notification` per device, as passing multiple subscriptions at once as
342
+ `registration_ids` is not supported.
343
+
344
+
300
345
  ### Running Rpush
301
346
 
302
347
  It is recommended to run Rpush as a separate process in most cases, though embedding and manual modes are provided for low-workload environments.
@@ -32,3 +32,6 @@ require 'rpush/client/active_model/wns/notification'
32
32
  require 'rpush/client/active_model/pushy/app'
33
33
  require 'rpush/client/active_model/pushy/notification'
34
34
  require 'rpush/client/active_model/pushy/time_to_live_validator'
35
+
36
+ require 'rpush/client/active_model/webpush/app'
37
+ require 'rpush/client/active_model/webpush/notification'
@@ -5,7 +5,7 @@ module Rpush
5
5
  class DataValidator < ::ActiveModel::Validator
6
6
  def validate(record)
7
7
  return unless record.collapse_key.nil? && record.data.nil?
8
- record.errors[:data] << 'must be set unless collapse_key is specified'
8
+ record.errors.add :data, 'must be set unless collapse_key is specified'
9
9
  end
10
10
  end
11
11
  end
@@ -4,8 +4,8 @@ module Rpush
4
4
  module Apns
5
5
  class DeviceTokenFormatValidator < ::ActiveModel::Validator
6
6
  def validate(record)
7
- return if record.device_token =~ /^[a-z0-9]\w+$/i
8
- record.errors[:device_token] << "is invalid"
7
+ return if record.device_token =~ /\A[a-z0-9]\w+\z/i
8
+ record.errors.add :device_token, "is invalid"
9
9
  end
10
10
  end
11
11
  end
@@ -6,7 +6,7 @@ module Rpush
6
6
  def validate(record)
7
7
  limit = record.class.max_payload_bytesize
8
8
  return unless record.payload.bytesize > limit
9
- record.errors[:base] << "APN notification cannot be larger than #{limit} bytes. Try condensing your alert and device attributes."
9
+ record.errors.add :base, "APN notification cannot be larger than #{limit} bytes. Try condensing your alert and device attributes."
10
10
  end
11
11
  end
12
12
  end
@@ -5,7 +5,7 @@ module Rpush
5
5
  class ExpiryCollapseKeyMutualInclusionValidator < ::ActiveModel::Validator
6
6
  def validate(record)
7
7
  return unless record.collapse_key && !record.expiry
8
- record.errors[:expiry] << 'must be set when using a collapse_key'
8
+ record.errors.add :expiry, 'must be set when using a collapse_key'
9
9
  end
10
10
  end
11
11
  end
@@ -5,7 +5,7 @@ module Rpush
5
5
  def validate(record)
6
6
  limit = options[:limit] || 1024
7
7
  return unless record.data && record.payload_data_size > limit
8
- record.errors[:base] << "Notification payload data cannot be larger than #{limit} bytes."
8
+ record.errors.add :base, "Notification payload data cannot be larger than #{limit} bytes."
9
9
  end
10
10
  end
11
11
  end
@@ -5,7 +5,7 @@ module Rpush
5
5
  def validate(record)
6
6
  limit = options[:limit] || 100
7
7
  return unless record.registration_ids && record.registration_ids.size > limit
8
- record.errors[:base] << "Number of registration_ids cannot be larger than #{limit}."
8
+ record.errors.add :base, "Number of registration_ids cannot be larger than #{limit}."
9
9
  end
10
10
  end
11
11
  end
@@ -0,0 +1,41 @@
1
+ module Rpush
2
+ module Client
3
+ module ActiveModel
4
+ module Webpush
5
+ module App
6
+
7
+ class VapidKeypairValidator < ::ActiveModel::Validator
8
+ def validate(record)
9
+ return if record.vapid_keypair.blank?
10
+ keypair = record.vapid
11
+ %i[ subject public_key private_key ].each do |key|
12
+ unless keypair.key?(key)
13
+ record.errors.add(:vapid_keypair, "must have a #{key} entry")
14
+ end
15
+ end
16
+ rescue
17
+ record.errors.add(:vapid_keypair, 'must be valid JSON')
18
+ end
19
+ end
20
+
21
+ def self.included(base)
22
+ base.class_eval do
23
+ alias_attribute :vapid_keypair, :certificate
24
+ validates :vapid_keypair, presence: true
25
+ validates_with VapidKeypairValidator
26
+ end
27
+ end
28
+
29
+ def service_name
30
+ 'webpush'
31
+ end
32
+
33
+ def vapid
34
+ @vapid ||= JSON.parse(vapid_keypair).symbolize_keys
35
+ end
36
+
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,66 @@
1
+ module Rpush
2
+ module Client
3
+ module ActiveModel
4
+ module Webpush
5
+ module Notification
6
+
7
+ class RegistrationValidator < ::ActiveModel::Validator
8
+ KEYS = %i[ endpoint keys ].freeze
9
+ def validate(record)
10
+ return if record.registration_ids.blank?
11
+ return if record.registration_ids.size > 1
12
+ reg = record.registration_ids.first
13
+ unless reg.is_a?(Hash) &&
14
+ reg.keys.sort == KEYS &&
15
+ reg[:endpoint].is_a?(String) &&
16
+ reg[:keys].is_a?(Hash)
17
+ record.errors.add(:base, 'Registration must have :endpoint (String) and :keys (Hash) keys')
18
+ end
19
+ end
20
+ end
21
+
22
+ def self.included(base)
23
+ base.instance_eval do
24
+ alias_attribute :time_to_live, :expiry
25
+
26
+ validates :registration_ids, presence: true
27
+ validates :data, presence: true
28
+ validates :time_to_live, numericality: { only_integer: true, greater_than: 0 }, allow_nil: true
29
+
30
+ validates_with Rpush::Client::ActiveModel::PayloadDataSizeValidator, limit: 4096
31
+ validates_with Rpush::Client::ActiveModel::RegistrationIdsCountValidator, limit: 1
32
+ validates_with RegistrationValidator
33
+ end
34
+ end
35
+
36
+ def data=(value)
37
+ value = value.stringify_keys if value.respond_to?(:stringify_keys)
38
+ super value
39
+ end
40
+
41
+ def subscription
42
+ @subscription ||= registration_ids.first.deep_symbolize_keys
43
+ end
44
+
45
+ def message
46
+ data['message'].presence if data
47
+ end
48
+
49
+ # https://webpush-wg.github.io/webpush-protocol/#urgency
50
+ def urgency
51
+ data['urgency'].presence if data
52
+ end
53
+
54
+ def as_json(_options = nil)
55
+ {
56
+ 'data' => data,
57
+ 'time_to_live' => time_to_live,
58
+ 'registration_ids' => registration_ids
59
+ }
60
+ end
61
+
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -32,3 +32,6 @@ require 'rpush/client/active_record/adm/app'
32
32
 
33
33
  require 'rpush/client/active_record/pushy/notification'
34
34
  require 'rpush/client/active_record/pushy/app'
35
+
36
+ require 'rpush/client/active_record/webpush/notification'
37
+ require 'rpush/client/active_record/webpush/app'
@@ -3,6 +3,7 @@ module Rpush
3
3
  module ActiveRecord
4
4
  module Apnsp8
5
5
  class Notification < Rpush::Client::ActiveRecord::Apns::Notification
6
+ include Rpush::Client::ActiveModel::Apns2::Notification
6
7
  end
7
8
  end
8
9
  end
@@ -0,0 +1,11 @@
1
+ module Rpush
2
+ module Client
3
+ module ActiveRecord
4
+ module Webpush
5
+ class App < Rpush::Client::ActiveRecord::App
6
+ include Rpush::Client::ActiveModel::Webpush::App
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,12 @@
1
+ module Rpush
2
+ module Client
3
+ module ActiveRecord
4
+ module Webpush
5
+ class Notification < Rpush::Client::ActiveRecord::Notification
6
+ include Rpush::Client::ActiveModel::Webpush::Notification
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
12
+
@@ -44,6 +44,9 @@ require 'rpush/client/redis/wns/badge_notification'
44
44
  require 'rpush/client/redis/pushy/app'
45
45
  require 'rpush/client/redis/pushy/notification'
46
46
 
47
+ require 'rpush/client/redis/webpush/app'
48
+ require 'rpush/client/redis/webpush/notification'
49
+
47
50
  Modis.configure do |config|
48
51
  config.namespace = :rpush
49
52
  end
@@ -3,6 +3,8 @@ module Rpush
3
3
  module Redis
4
4
  module Apnsp8
5
5
  class Notification < Rpush::Client::Redis::Notification
6
+ include Rpush::Client::ActiveModel::Apns::Notification
7
+ include Rpush::Client::ActiveModel::Apns2::Notification
6
8
  include Rpush::Client::ActiveModel::Apnsp8::Notification
7
9
  end
8
10
  end
@@ -0,0 +1,15 @@
1
+ module Rpush
2
+ module Client
3
+ module Redis
4
+ module Webpush
5
+ class App < Rpush::Client::Redis::App
6
+ include Rpush::Client::ActiveModel::Webpush::App
7
+
8
+ def vapid_keypair=(value)
9
+ self.certificate = value
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ module Rpush
2
+ module Client
3
+ module Redis
4
+ module Webpush
5
+ class Notification < Rpush::Client::Redis::Notification
6
+ include Rpush::Client::ActiveModel::Webpush::Notification
7
+
8
+ def time_to_live=(value)
9
+ self.expiry = value
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -106,7 +106,7 @@ module Rpush
106
106
  client_module = Rpush::Client.const_get(client.to_s.camelize)
107
107
  Rpush.send(:include, client_module) unless Rpush.ancestors.include?(client_module)
108
108
 
109
- [:Apns, :Gcm, :Wpns, :Wns, :Adm, :Pushy].each do |service|
109
+ [:Apns, :Gcm, :Wpns, :Wns, :Adm, :Pushy, :Webpush].each do |service|
110
110
  Rpush.const_set(service, client_module.const_get(service)) unless Rpush.const_defined?(service)
111
111
  end
112
112
 
@@ -68,6 +68,9 @@ require 'rpush/daemon/adm'
68
68
  require 'rpush/daemon/pushy'
69
69
  require 'rpush/daemon/pushy/delivery'
70
70
 
71
+ require 'rpush/daemon/webpush/delivery'
72
+ require 'rpush/daemon/webpush'
73
+
71
74
  module Rpush
72
75
  module Daemon
73
76
  class << self
@@ -107,7 +107,13 @@ module Rpush
107
107
  end
108
108
 
109
109
  def prepare_headers(notification)
110
- notification_data(notification)[HTTP2_HEADERS_KEY] || {}
110
+ headers = {}
111
+
112
+ headers['apns-expiration'] = '0'
113
+ headers['apns-priority'] = '10'
114
+ headers['apns-topic'] = @app.bundle_id
115
+
116
+ headers.merge notification_data(notification)[HTTP2_HEADERS_KEY] || {}
111
117
  end
112
118
 
113
119
  def notification_data(notification)
@@ -0,0 +1,10 @@
1
+ module Rpush
2
+ module Daemon
3
+ module Webpush
4
+ extend ServiceConfigMethods
5
+
6
+ dispatcher :http
7
+ end
8
+ end
9
+ end
10
+
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "webpush"
4
+
5
+ module Rpush
6
+ module Daemon
7
+ module Webpush
8
+
9
+ # Webpush::Request handles all the encryption / signing.
10
+ # We just override #perform to inject the http instance that is managed
11
+ # by Rpush.
12
+ #
13
+ class Request < ::Webpush::Request
14
+ def perform(http)
15
+ req = Net::HTTP::Post.new(uri.request_uri, headers)
16
+ req.body = body
17
+ http.request(uri, req)
18
+ end
19
+ end
20
+
21
+ class Delivery < Rpush::Daemon::Delivery
22
+
23
+ OK = [ 200, 201, 202 ].freeze
24
+ TEMPORARY_FAILURES = [ 429, 500, 502, 503, 504 ].freeze
25
+
26
+ def initialize(app, http, notification, batch)
27
+ @app = app
28
+ @http = http
29
+ @notification = notification
30
+ @batch = batch
31
+ end
32
+
33
+ def perform
34
+ response = send_request
35
+ process_response response
36
+ rescue SocketError, SystemCallError => error
37
+ mark_retryable(@notification, Time.now + 10.seconds, error)
38
+ raise
39
+ rescue StandardError => error
40
+ mark_failed(error)
41
+ raise
42
+ ensure
43
+ @batch.notification_processed
44
+ end
45
+
46
+ private
47
+
48
+ def send_request
49
+ # The initializer is inherited from Webpush::Request and looks like
50
+ # this:
51
+ #
52
+ # initialize(message: '', subscription:, vapid:, **options)
53
+ #
54
+ # where subscription is a hash of :endpoint and :keys, and vapid
55
+ # holds the vapid public and private keys and the :subject (which is
56
+ # an email address).
57
+ Request.new(
58
+ message: @notification.message,
59
+ subscription: @notification.subscription,
60
+ vapid: @app.vapid,
61
+ ttl: @notification.time_to_live,
62
+ urgency: @notification.urgency
63
+ ).perform(@http)
64
+ end
65
+
66
+ def process_response(response)
67
+ case response.code.to_i
68
+ when *OK
69
+ mark_delivered
70
+ when *TEMPORARY_FAILURES
71
+ retry_delivery(response)
72
+ else
73
+ fail_delivery(response)
74
+ end
75
+ end
76
+
77
+ def retry_delivery(response)
78
+ time = deliver_after_header(response)
79
+ if time
80
+ mark_retryable(@notification, time)
81
+ else
82
+ mark_retryable_exponential(@notification)
83
+ end
84
+ log_info("Webpush endpoint responded with a #{response.code} error. #{retry_message}")
85
+ end
86
+
87
+ def fail_delivery(response)
88
+ fail_message = fail_message(response)
89
+ log_error("#{@notification.id} failed: #{fail_message}")
90
+ fail Rpush::DeliveryError.new(response.code.to_i, @notification.id, fail_message)
91
+ end
92
+
93
+ def deliver_after_header(response)
94
+ Rpush::Daemon::RetryHeaderParser.parse(response.header['retry-after'])
95
+ end
96
+
97
+ def retry_message
98
+ deliver_after = @notification.deliver_after.strftime('%Y-%m-%d %H:%M:%S')
99
+ "Notification #{@notification.id} will be retried after #{deliver_after} (retry #{@notification.retries})."
100
+ end
101
+
102
+ def fail_message(response)
103
+ msg = Rpush::Daemon::HTTP_STATUS_CODES[response.code.to_i]
104
+ if explanation = response.body.to_s[0..200].presence
105
+ msg += ": #{explanation}"
106
+ end
107
+ msg
108
+ end
109
+
110
+ end
111
+ end
112
+ end
113
+ end
114
+
@@ -1,7 +1,7 @@
1
1
  module Rpush
2
2
  module VERSION
3
3
  MAJOR = 5
4
- MINOR = 2
4
+ MINOR = 3
5
5
  TINY = 0
6
6
  PRE = nil
7
7
 
@@ -42,6 +42,7 @@ describe 'APNs http2 adapter' do
42
42
  app.certificate = TEST_CERT
43
43
  app.name = 'test'
44
44
  app.environment = 'development'
45
+ app.bundle_id = 'com.example.app'
45
46
  app.save!
46
47
  app
47
48
  end
@@ -75,7 +76,12 @@ describe 'APNs http2 adapter' do
75
76
  :post,
76
77
  "/3/device/#{fake_device_token}",
77
78
  { body: "{\"aps\":{\"alert\":\"test\",\"sound\":\"default\",\"content-available\":1}}",
78
- headers: {} }
79
+ headers: {
80
+ 'apns-expiration' => '0',
81
+ 'apns-priority' => '10',
82
+ 'apns-topic' => 'com.example.app'
83
+ }
84
+ }
79
85
  )
80
86
  .and_return(fake_http2_request)
81
87
 
@@ -104,7 +110,11 @@ describe 'APNs http2 adapter' do
104
110
  "/3/device/#{fake_device_token}",
105
111
  { body: "{\"aps\":{\"alert\":\"test\",\"sound\":\"default\","\
106
112
  "\"content-available\":1},\"some_field\":\"some value\"}",
107
- headers: { 'apns-topic' => bundle_id }
113
+ headers: {
114
+ 'apns-topic' => bundle_id,
115
+ 'apns-expiration' => '0',
116
+ 'apns-priority' => '10'
117
+ }
108
118
  }
109
119
  ).and_return(fake_http2_request)
110
120
 
@@ -0,0 +1,30 @@
1
+ require 'functional_spec_helper'
2
+
3
+ describe 'Webpush' do
4
+ let(:code) { 201 }
5
+ let(:response) { instance_double('Net::HTTPResponse', code: code, body: '') }
6
+ let(:http) { instance_double('Net::HTTP::Persistent', request: response, shutdown: nil) }
7
+ let(:app) { Rpush::Webpush::App.create!(name: 'MyApp', vapid_keypair: VAPID_KEYPAIR) }
8
+
9
+ let(:device_reg) {
10
+ { endpoint: 'https://webpush-provider.example.org/push/some-id',
11
+ keys: {'auth' => 'DgN9EBia1o057BdhCOGURA', 'p256dh' => 'BAtxJ--7vHq9IVm8utUB3peJ4lpxRqk1rukCIkVJOomS83QkCnrQ4EyYQsSaCRgy_c8XPytgXxuyAvRJdnTPK4A'} }
12
+ }
13
+ let(:notification) { Rpush::Webpush::Notification.create!(app: app, registration_ids: [device_reg], data: { message: 'test' }) }
14
+
15
+ before do
16
+ allow(Net::HTTP::Persistent).to receive_messages(new: http)
17
+ end
18
+
19
+ it 'deliveres a notification successfully' do
20
+ expect { Rpush.push }.to change { notification.reload.delivered }.to(true)
21
+ end
22
+
23
+ context 'when delivery failed' do
24
+ let(:code) { 404 }
25
+ it 'marks a notification as failed' do
26
+ expect { Rpush.push }.to change { notification.reload.failed }.to(true)
27
+ end
28
+ end
29
+ end
30
+
@@ -46,6 +46,8 @@ path = File.join(File.dirname(__FILE__), 'support')
46
46
  TEST_CERT = File.read(File.join(path, 'cert_without_password.pem'))
47
47
  TEST_CERT_WITH_PASSWORD = File.read(File.join(path, 'cert_with_password.pem'))
48
48
 
49
+ VAPID_KEYPAIR = Webpush.generate_key.to_hash.merge(subject: 'rpush-test@example.org').to_json
50
+
49
51
  def after_example_cleanup
50
52
  Rpush.logger = nil
51
53
  Rpush::Daemon.store = nil
@@ -0,0 +1,28 @@
1
+ require "unit_spec_helper"
2
+
3
+ describe Rpush::Client::ActiveRecord::Apnsp8::Notification do
4
+ subject(:notification) { described_class.new }
5
+
6
+ it_behaves_like 'Rpush::Client::Apns::Notification'
7
+ it_behaves_like 'Rpush::Client::ActiveRecord::Notification'
8
+
9
+ it "should validate the length of the binary conversion of the notification", :aggregate_failures do
10
+ notification = described_class.new
11
+ notification.app = Rpush::Apnsp8::App.create(apn_key: "1",
12
+ apn_key_id: "2",
13
+ name: 'test',
14
+ environment: 'development',
15
+ team_id: "3",
16
+ bundle_id: "4")
17
+ notification.device_token = "a" * 108
18
+ notification.alert = ""
19
+
20
+ notification.alert << "a" until notification.payload.bytesize == 4096
21
+ expect(notification.valid?).to be_truthy
22
+ expect(notification.errors[:base]).to be_empty
23
+
24
+ notification.alert << "a"
25
+ expect(notification.valid?).to be_falsey
26
+ expect(notification.errors[:base].include?("APN notification cannot be larger than 4096 bytes. Try condensing your alert and device attributes.")).to be_truthy
27
+ end
28
+ end if active_record?
@@ -0,0 +1,6 @@
1
+ require 'unit_spec_helper'
2
+
3
+ describe Rpush::Client::ActiveRecord::Webpush::App do
4
+ it_behaves_like 'Rpush::Client::Webpush::App'
5
+ it_behaves_like 'Rpush::Client::ActiveRecord::App'
6
+ end if active_record?
@@ -0,0 +1,6 @@
1
+ require 'unit_spec_helper'
2
+
3
+ describe Rpush::Client::ActiveRecord::Webpush::Notification do
4
+ it_behaves_like 'Rpush::Client::Webpush::Notification'
5
+ it_behaves_like 'Rpush::Client::ActiveRecord::Notification'
6
+ end if active_record?
@@ -0,0 +1,29 @@
1
+ require "unit_spec_helper"
2
+
3
+ describe Rpush::Client::Redis::Apnsp8::Notification do
4
+ after do
5
+ Rpush::Apnsp8::App.all.select { |a| a.environment == "development" && a.apn_key == "1" }.each(&:destroy)
6
+ end
7
+
8
+ it_behaves_like "Rpush::Client::Apns::Notification"
9
+
10
+ it "should validate the length of the binary conversion of the notification", :aggregate_failures do
11
+ notification = described_class.new
12
+ notification.app = Rpush::Apnsp8::App.create(apn_key: "1",
13
+ apn_key_id: "2",
14
+ name: 'test',
15
+ environment: 'development',
16
+ team_id: "3",
17
+ bundle_id: "4")
18
+ notification.device_token = "a" * 108
19
+ notification.alert = ""
20
+
21
+ notification.alert << "a" until notification.payload.bytesize == 4096
22
+ expect(notification.valid?).to be_truthy
23
+ expect(notification.errors[:base]).to be_empty
24
+
25
+ notification.alert << "a"
26
+ expect(notification.valid?).to be_falsey
27
+ expect(notification.errors[:base].include?("APN notification cannot be larger than 4096 bytes. Try condensing your alert and device attributes.")).to be_truthy
28
+ end
29
+ end if redis?
@@ -0,0 +1,5 @@
1
+ require 'unit_spec_helper'
2
+
3
+ describe Rpush::Client::Redis::Webpush::App do
4
+ it_behaves_like 'Rpush::Client::Webpush::App'
5
+ end if redis?
@@ -0,0 +1,5 @@
1
+ require 'unit_spec_helper'
2
+
3
+ describe Rpush::Client::Redis::Webpush::Notification do
4
+ it_behaves_like 'Rpush::Client::Webpush::Notification'
5
+ end if redis?
@@ -0,0 +1,33 @@
1
+ require 'unit_spec_helper'
2
+
3
+ shared_examples 'Rpush::Client::Webpush::App' do
4
+ describe 'validates' do
5
+ subject { described_class.new }
6
+
7
+ it 'validates presence of name' do
8
+ is_expected.not_to be_valid
9
+ expect(subject.errors[:name]).to eq ["can't be blank"]
10
+ end
11
+
12
+ it 'validates presence of vapid_keypair' do
13
+ is_expected.not_to be_valid
14
+ expect(subject.errors[:vapid_keypair]).to eq ["can't be blank"]
15
+ end
16
+
17
+ it 'should require the vapid keypair to have subject, public and private key' do
18
+ subject.vapid_keypair = {}.to_json
19
+ is_expected.not_to be_valid
20
+ expect(subject.errors[:vapid_keypair].sort).to eq [
21
+ 'must have a private_key entry',
22
+ 'must have a public_key entry',
23
+ 'must have a subject entry',
24
+ ]
25
+ end
26
+
27
+ it 'should require valid json for the keypair' do
28
+ subject.vapid_keypair = 'invalid'
29
+ is_expected.not_to be_valid
30
+ expect(subject.errors[:vapid_keypair].sort).to eq [ 'must be valid JSON' ]
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,83 @@
1
+ require 'unit_spec_helper'
2
+
3
+ shared_examples 'Rpush::Client::Webpush::Notification' do
4
+ subject(:notification) { described_class.new }
5
+
6
+ describe 'notification attributes' do
7
+ describe 'data' do
8
+ subject { described_class.new(data: { message: 'test', urgency: 'normal' } ) }
9
+ it 'has a message' do
10
+ expect(subject.message).to eq "test"
11
+ end
12
+ it 'has an urgency' do
13
+ expect(subject.urgency).to eq "normal"
14
+ end
15
+ end
16
+
17
+ describe 'subscription' do
18
+ let(:subscription){ { endpoint: 'https://push.example.org/foo', keys: {'foo' => 'bar'}} }
19
+ subject { described_class.new(registration_ids: [subscription]) }
20
+
21
+ it "has a subscription" do
22
+ expect(subject.subscription).to eq({ endpoint: 'https://push.example.org/foo', keys: {foo: 'bar'} })
23
+ end
24
+ end
25
+ end
26
+
27
+
28
+ describe 'validates' do
29
+ let(:app) { Rpush::Webpush::App.create!(name: 'MyApp', vapid_keypair: VAPID_KEYPAIR) }
30
+
31
+ describe 'data' do
32
+ subject { described_class.new(app: app, registration_ids: [{endpoint: 'https://push.example.org/foo', keys: {'foo' => 'bar'}}]) }
33
+ it 'validates presence' do
34
+ is_expected.not_to be_valid
35
+ expect(subject.errors[:data]).to eq ["can't be blank"]
36
+ end
37
+
38
+ it "has a 'data' payload limit of 4096 bytes" do
39
+ subject.data = { message: 'a' * 4096 }
40
+ is_expected.not_to be_valid
41
+ expected_errors = ["Notification payload data cannot be larger than 4096 bytes."]
42
+ expect(subject.errors[:base]).to eq expected_errors
43
+ end
44
+ end
45
+
46
+ describe 'registration_ids' do
47
+ subject { described_class.new(app: app, data: { message: 'test' }) }
48
+ it 'validates presence' do
49
+ is_expected.not_to be_valid
50
+ expect(subject.errors[:registration_ids]).to eq ["can't be blank"]
51
+ end
52
+
53
+ it 'limits the number of registration ids to exactly 1' do
54
+ subject.registration_ids = [{endpoint: 'string', keys: { 'a' => 'hash' }}] * 2
55
+ is_expected.not_to be_valid
56
+ expected_errors = ["Number of registration_ids cannot be larger than 1."]
57
+ expect(subject.errors[:base]).to eq expected_errors
58
+ end
59
+
60
+ it 'validates the structure of the registration' do
61
+ subject.registration_ids = ['a']
62
+ is_expected.not_to be_valid
63
+ expect(subject.errors[:base]).to eq [
64
+ "Registration must have :endpoint (String) and :keys (Hash) keys"
65
+ ]
66
+
67
+ subject.registration_ids = [{endpoint: 'string', keys: { 'a' => 'hash' }}]
68
+ is_expected.to be_valid
69
+ end
70
+ end
71
+
72
+ describe 'time_to_live' do
73
+ subject { described_class.new(app: app, data: { message: 'test' }, registration_ids: [{endpoint: 'https://push.example.org/foo', keys: {'foo' => 'bar'}}]) }
74
+
75
+ it 'should be > 0' do
76
+ subject.time_to_live = -1
77
+ is_expected.not_to be_valid
78
+ expect(subject.errors[:time_to_live]).to eq ['must be greater than 0']
79
+ end
80
+ end
81
+
82
+ end
83
+ end
@@ -0,0 +1,142 @@
1
+ require 'unit_spec_helper'
2
+
3
+ describe Rpush::Daemon::Webpush::Delivery do
4
+ let(:app) { Rpush::Webpush::App.create!(name: 'MyApp', vapid_keypair: VAPID_KEYPAIR) }
5
+
6
+ # Push subscription information as received from a client browser when the
7
+ # user subscribed to push notifications.
8
+ let(:device_reg) {
9
+ { endpoint: 'https://webpush-provider.example.org/push/some-id',
10
+ keys: {'auth' => 'DgN9EBia1o057BdhCOGURA', 'p256dh' => 'BAtxJ--7vHq9IVm8utUB3peJ4lpxRqk1rukCIkVJOomS83QkCnrQ4EyYQsSaCRgy_c8XPytgXxuyAvRJdnTPK4A'} }
11
+ }
12
+
13
+ let(:data) { { message: 'some message' } }
14
+ let(:notification) { Rpush::Webpush::Notification.create!(app: app, registration_ids: [device_reg], data: data) }
15
+ let(:batch) { instance_double('Rpush::Daemon::Batch', notification_processed: nil) }
16
+ let(:response) { instance_double('Net::HTTPResponse', code: response_code, header: response_header, body: response_body) }
17
+ let(:response_code) { 201 }
18
+ let(:response_header) { {} }
19
+ let(:response_body) { nil }
20
+ let(:http) { instance_double('Net::HTTP::Persistent', request: response) }
21
+ let(:logger) { instance_double('Rpush::Logger', error: nil, info: nil, warn: nil, internal_logger: nil) }
22
+ let(:now) { Time.parse('2020-10-13 00:00:00 UTC') }
23
+
24
+ before do
25
+ allow(Rpush).to receive_messages(logger: logger)
26
+ allow(Time).to receive_messages(now: now)
27
+ end
28
+
29
+ subject(:delivery) { described_class.new(app, http, notification, batch) }
30
+
31
+ describe '#perform' do
32
+ shared_examples 'process notification' do
33
+ it 'invoke batch.notification_processed' do
34
+ subject.perform rescue nil
35
+ expect(batch).to have_received(:notification_processed)
36
+ end
37
+ end
38
+
39
+ context 'when response code is 201' do
40
+ before do
41
+ allow(batch).to receive(:mark_delivered)
42
+ Rpush::Daemon.store = Rpush::Daemon::Store.const_get(Rpush.config.client.to_s.camelcase).new
43
+ end
44
+
45
+ it 'marks the notification as delivered' do
46
+ delivery.perform
47
+ expect(batch).to have_received(:mark_delivered).with(notification)
48
+ end
49
+
50
+ it_behaves_like 'process notification'
51
+ end
52
+
53
+ shared_examples 'retry delivery' do |response_code:|
54
+ let(:response_code) { response_code }
55
+
56
+ shared_examples 'logs' do |deliver_after:|
57
+ let(:expected_log_message) do
58
+ "[MyApp] Webpush endpoint responded with a #{response_code} error. Notification #{notification.id} will be retried after #{deliver_after} (retry 1)."
59
+ end
60
+
61
+ it 'logs that the notification will be retried' do
62
+ delivery.perform
63
+ expect(logger).to have_received(:info).with(expected_log_message)
64
+ end
65
+ end
66
+
67
+ context 'when Retry-After header is present' do
68
+ let(:response_header) { { 'retry-after' => 10 } }
69
+
70
+ before do
71
+ allow(delivery).to receive(:mark_retryable) do
72
+ notification.deliver_after = now + 10.seconds
73
+ notification.retries += 1
74
+ end
75
+ end
76
+
77
+ it 'retry the notification' do
78
+ delivery.perform
79
+ expect(delivery).to have_received(:mark_retryable).with(notification, now + 10.seconds)
80
+ end
81
+
82
+ it_behaves_like 'logs', deliver_after: '2020-10-13 00:00:10'
83
+ it_behaves_like 'process notification'
84
+ end
85
+
86
+ context 'when Retry-After header is not present' do
87
+ before do
88
+ allow(delivery).to receive(:mark_retryable_exponential) do
89
+ notification.deliver_after = now + 2.seconds
90
+ notification.retries = 1
91
+ end
92
+ end
93
+
94
+ it 'retry the notification' do
95
+ delivery.perform
96
+ expect(delivery).to have_received(:mark_retryable_exponential).with(notification)
97
+ end
98
+
99
+ it_behaves_like 'logs', deliver_after: '2020-10-13 00:00:02'
100
+ it_behaves_like 'process notification'
101
+ end
102
+ end
103
+
104
+ it_behaves_like 'retry delivery', response_code: 429
105
+ it_behaves_like 'retry delivery', response_code: 500
106
+ it_behaves_like 'retry delivery', response_code: 502
107
+ it_behaves_like 'retry delivery', response_code: 503
108
+ it_behaves_like 'retry delivery', response_code: 504
109
+
110
+ context 'when delivery failed' do
111
+ let(:response_code) { 400 }
112
+ let(:fail_message) { 'that was a bad request' }
113
+ before do
114
+ allow(response).to receive(:body) { fail_message }
115
+ allow(batch).to receive(:mark_failed)
116
+ end
117
+
118
+ it 'marks the notification as failed' do
119
+ expect { delivery.perform }.to raise_error(Rpush::DeliveryError)
120
+ expected_message = "Unable to deliver notification #{notification.id}, " \
121
+ "received error 400 (Bad Request: #{fail_message})"
122
+ expect(batch).to have_received(:mark_failed).with(notification, 400, expected_message)
123
+ end
124
+
125
+ it_behaves_like 'process notification'
126
+ end
127
+
128
+ context 'when SocketError raised' do
129
+ before do
130
+ allow(http).to receive(:request) { raise SocketError }
131
+ allow(delivery).to receive(:mark_retryable)
132
+ end
133
+
134
+ it 'retry delivery after 10 seconds' do
135
+ expect { delivery.perform }.to raise_error(SocketError)
136
+ expect(delivery).to have_received(:mark_retryable).with(notification, now + 10.seconds, SocketError)
137
+ end
138
+
139
+ it_behaves_like 'process notification'
140
+ end
141
+ end
142
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rpush
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.2.0
4
+ version: 5.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ian Leitch
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-10-08 00:00:00.000000000 Z
11
+ date: 2021-01-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: multi_json
@@ -134,6 +134,20 @@ dependencies:
134
134
  - - ">="
135
135
  - !ruby/object:Gem::Version
136
136
  version: '0'
137
+ - !ruby/object:Gem::Dependency
138
+ name: webpush
139
+ requirement: !ruby/object:Gem::Requirement
140
+ requirements:
141
+ - - "~>"
142
+ - !ruby/object:Gem::Version
143
+ version: '1.0'
144
+ type: :runtime
145
+ prerelease: false
146
+ version_requirements: !ruby/object:Gem::Requirement
147
+ requirements:
148
+ - - "~>"
149
+ - !ruby/object:Gem::Version
150
+ version: '1.0'
137
151
  - !ruby/object:Gem::Dependency
138
152
  name: rake
139
153
  requirement: !ruby/object:Gem::Requirement
@@ -423,6 +437,8 @@ files:
423
437
  - lib/rpush/client/active_model/pushy/notification.rb
424
438
  - lib/rpush/client/active_model/pushy/time_to_live_validator.rb
425
439
  - lib/rpush/client/active_model/registration_ids_count_validator.rb
440
+ - lib/rpush/client/active_model/webpush/app.rb
441
+ - lib/rpush/client/active_model/webpush/notification.rb
426
442
  - lib/rpush/client/active_model/wns/app.rb
427
443
  - lib/rpush/client/active_model/wns/notification.rb
428
444
  - lib/rpush/client/active_model/wpns/app.rb
@@ -444,6 +460,8 @@ files:
444
460
  - lib/rpush/client/active_record/notification.rb
445
461
  - lib/rpush/client/active_record/pushy/app.rb
446
462
  - lib/rpush/client/active_record/pushy/notification.rb
463
+ - lib/rpush/client/active_record/webpush/app.rb
464
+ - lib/rpush/client/active_record/webpush/notification.rb
447
465
  - lib/rpush/client/active_record/wns/app.rb
448
466
  - lib/rpush/client/active_record/wns/badge_notification.rb
449
467
  - lib/rpush/client/active_record/wns/notification.rb
@@ -466,6 +484,8 @@ files:
466
484
  - lib/rpush/client/redis/notification.rb
467
485
  - lib/rpush/client/redis/pushy/app.rb
468
486
  - lib/rpush/client/redis/pushy/notification.rb
487
+ - lib/rpush/client/redis/webpush/app.rb
488
+ - lib/rpush/client/redis/webpush/notification.rb
469
489
  - lib/rpush/client/redis/wns/app.rb
470
490
  - lib/rpush/client/redis/wns/badge_notification.rb
471
491
  - lib/rpush/client/redis/wns/notification.rb
@@ -520,6 +540,8 @@ files:
520
540
  - lib/rpush/daemon/string_helpers.rb
521
541
  - lib/rpush/daemon/synchronizer.rb
522
542
  - lib/rpush/daemon/tcp_connection.rb
543
+ - lib/rpush/daemon/webpush.rb
544
+ - lib/rpush/daemon/webpush/delivery.rb
523
545
  - lib/rpush/daemon/wns.rb
524
546
  - lib/rpush/daemon/wns/badge_request.rb
525
547
  - lib/rpush/daemon/wns/delivery.rb
@@ -552,6 +574,7 @@ files:
552
574
  - spec/functional/pushy_spec.rb
553
575
  - spec/functional/retry_spec.rb
554
576
  - spec/functional/synchronization_spec.rb
577
+ - spec/functional/webpush_spec.rb
555
578
  - spec/functional/wpns_spec.rb
556
579
  - spec/functional_spec_helper.rb
557
580
  - spec/spec_helper.rb
@@ -570,6 +593,7 @@ files:
570
593
  - spec/unit/client/active_record/apns/notification_spec.rb
571
594
  - spec/unit/client/active_record/apns2/app_spec.rb
572
595
  - spec/unit/client/active_record/apns2/notification_spec.rb
596
+ - spec/unit/client/active_record/apnsp8/notification_spec.rb
573
597
  - spec/unit/client/active_record/app_spec.rb
574
598
  - spec/unit/client/active_record/gcm/app_spec.rb
575
599
  - spec/unit/client/active_record/gcm/notification_spec.rb
@@ -578,6 +602,8 @@ files:
578
602
  - spec/unit/client/active_record/pushy/notification_spec.rb
579
603
  - spec/unit/client/active_record/shared/app.rb
580
604
  - spec/unit/client/active_record/shared/notification.rb
605
+ - spec/unit/client/active_record/webpush/app_spec.rb
606
+ - spec/unit/client/active_record/webpush/notification_spec.rb
581
607
  - spec/unit/client/active_record/wns/badge_notification_spec.rb
582
608
  - spec/unit/client/active_record/wns/raw_notification_spec.rb
583
609
  - spec/unit/client/active_record/wpns/app_spec.rb
@@ -589,12 +615,15 @@ files:
589
615
  - spec/unit/client/redis/apns/notification_spec.rb
590
616
  - spec/unit/client/redis/apns2/app_spec.rb
591
617
  - spec/unit/client/redis/apns2/notification_spec.rb
618
+ - spec/unit/client/redis/apnsp8/notification_spec.rb
592
619
  - spec/unit/client/redis/app_spec.rb
593
620
  - spec/unit/client/redis/gcm/app_spec.rb
594
621
  - spec/unit/client/redis/gcm/notification_spec.rb
595
622
  - spec/unit/client/redis/notification_spec.rb
596
623
  - spec/unit/client/redis/pushy/app_spec.rb
597
624
  - spec/unit/client/redis/pushy/notification_spec.rb
625
+ - spec/unit/client/redis/webpush/app_spec.rb
626
+ - spec/unit/client/redis/webpush/notification_spec.rb
598
627
  - spec/unit/client/redis/wns/badge_notification_spec.rb
599
628
  - spec/unit/client/redis/wns/raw_notification_spec.rb
600
629
  - spec/unit/client/redis/wpns/app_spec.rb
@@ -610,6 +639,8 @@ files:
610
639
  - spec/unit/client/shared/notification.rb
611
640
  - spec/unit/client/shared/pushy/app.rb
612
641
  - spec/unit/client/shared/pushy/notification.rb
642
+ - spec/unit/client/shared/webpush/app.rb
643
+ - spec/unit/client/shared/webpush/notification.rb
613
644
  - spec/unit/client/shared/wns/badge_notification.rb
614
645
  - spec/unit/client/shared/wns/raw_notification.rb
615
646
  - spec/unit/client/shared/wpns/app.rb
@@ -638,6 +669,7 @@ files:
638
669
  - spec/unit/daemon/store/active_record_spec.rb
639
670
  - spec/unit/daemon/store/redis_spec.rb
640
671
  - spec/unit/daemon/tcp_connection_spec.rb
672
+ - spec/unit/daemon/webpush/delivery_spec.rb
641
673
  - spec/unit/daemon/wns/delivery_spec.rb
642
674
  - spec/unit/daemon/wns/post_request_spec.rb
643
675
  - spec/unit/daemon/wpns/delivery_spec.rb
@@ -695,6 +727,7 @@ test_files:
695
727
  - spec/functional/pushy_spec.rb
696
728
  - spec/functional/retry_spec.rb
697
729
  - spec/functional/synchronization_spec.rb
730
+ - spec/functional/webpush_spec.rb
698
731
  - spec/functional/wpns_spec.rb
699
732
  - spec/functional_spec_helper.rb
700
733
  - spec/spec_helper.rb
@@ -713,6 +746,7 @@ test_files:
713
746
  - spec/unit/client/active_record/apns/notification_spec.rb
714
747
  - spec/unit/client/active_record/apns2/app_spec.rb
715
748
  - spec/unit/client/active_record/apns2/notification_spec.rb
749
+ - spec/unit/client/active_record/apnsp8/notification_spec.rb
716
750
  - spec/unit/client/active_record/app_spec.rb
717
751
  - spec/unit/client/active_record/gcm/app_spec.rb
718
752
  - spec/unit/client/active_record/gcm/notification_spec.rb
@@ -721,6 +755,8 @@ test_files:
721
755
  - spec/unit/client/active_record/pushy/notification_spec.rb
722
756
  - spec/unit/client/active_record/shared/app.rb
723
757
  - spec/unit/client/active_record/shared/notification.rb
758
+ - spec/unit/client/active_record/webpush/app_spec.rb
759
+ - spec/unit/client/active_record/webpush/notification_spec.rb
724
760
  - spec/unit/client/active_record/wns/badge_notification_spec.rb
725
761
  - spec/unit/client/active_record/wns/raw_notification_spec.rb
726
762
  - spec/unit/client/active_record/wpns/app_spec.rb
@@ -732,12 +768,15 @@ test_files:
732
768
  - spec/unit/client/redis/apns/notification_spec.rb
733
769
  - spec/unit/client/redis/apns2/app_spec.rb
734
770
  - spec/unit/client/redis/apns2/notification_spec.rb
771
+ - spec/unit/client/redis/apnsp8/notification_spec.rb
735
772
  - spec/unit/client/redis/app_spec.rb
736
773
  - spec/unit/client/redis/gcm/app_spec.rb
737
774
  - spec/unit/client/redis/gcm/notification_spec.rb
738
775
  - spec/unit/client/redis/notification_spec.rb
739
776
  - spec/unit/client/redis/pushy/app_spec.rb
740
777
  - spec/unit/client/redis/pushy/notification_spec.rb
778
+ - spec/unit/client/redis/webpush/app_spec.rb
779
+ - spec/unit/client/redis/webpush/notification_spec.rb
741
780
  - spec/unit/client/redis/wns/badge_notification_spec.rb
742
781
  - spec/unit/client/redis/wns/raw_notification_spec.rb
743
782
  - spec/unit/client/redis/wpns/app_spec.rb
@@ -753,6 +792,8 @@ test_files:
753
792
  - spec/unit/client/shared/notification.rb
754
793
  - spec/unit/client/shared/pushy/app.rb
755
794
  - spec/unit/client/shared/pushy/notification.rb
795
+ - spec/unit/client/shared/webpush/app.rb
796
+ - spec/unit/client/shared/webpush/notification.rb
756
797
  - spec/unit/client/shared/wns/badge_notification.rb
757
798
  - spec/unit/client/shared/wns/raw_notification.rb
758
799
  - spec/unit/client/shared/wpns/app.rb
@@ -781,6 +822,7 @@ test_files:
781
822
  - spec/unit/daemon/store/active_record_spec.rb
782
823
  - spec/unit/daemon/store/redis_spec.rb
783
824
  - spec/unit/daemon/tcp_connection_spec.rb
825
+ - spec/unit/daemon/webpush/delivery_spec.rb
784
826
  - spec/unit/daemon/wns/delivery_spec.rb
785
827
  - spec/unit/daemon/wns/post_request_spec.rb
786
828
  - spec/unit/daemon/wpns/delivery_spec.rb