From 4c98e1712a93b84d095188ecff1a4c9faaa11bd8 Mon Sep 17 00:00:00 2001 From: Ben Langfeld Date: Wed, 4 Sep 2024 17:48:10 -0300 Subject: [PATCH 01/32] Version should only be bumped on release --- lib/rpush/version.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/rpush/version.rb b/lib/rpush/version.rb index 992422280..485fdb1a6 100644 --- a/lib/rpush/version.rb +++ b/lib/rpush/version.rb @@ -1,8 +1,8 @@ module Rpush module VERSION MAJOR = 7 - MINOR = 1 - TINY = 0 + MINOR = 0 + TINY = 1 PRE = nil STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".").freeze From 1279a428a3277b9c46121c255d7b5fc3f469f5a0 Mon Sep 17 00:00:00 2001 From: Ben Osheroff Date: Thu, 5 Sep 2024 17:36:44 +0100 Subject: [PATCH 02/32] Document how to set title/subtitle/body for apns (#640) I sort of just guessed at this method of getting more advanced data into the APNS body and it worked, figured it'd be better if it was explicit. Co-authored-by: Ben Langfeld --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 2b1fde050..eba3f373c 100644 --- a/README.md +++ b/README.md @@ -83,6 +83,7 @@ n = Rpush::Apnsp8::Notification.new n.app = Rpush::Apnsp8::App.find_by_name("ios_app") n.device_token = "..." # hex string n.alert = "hi mom!" +# n.alert = { title: "push title", subtitle: "more to say", body: "hi mom!" } n.data = { foo: :bar } n.save! ``` @@ -107,6 +108,7 @@ n = Rpush::Apns2::Notification.new n.app = Rpush::Apns2::App.find_by_name("ios_app") n.device_token = "..." # hex string n.alert = "hi mom!" +# n.alert = { title: "push title", subtitle: "more to say", body: "hi mom!" } n.data = { headers: { 'apns-topic': "BUNDLE ID" }, # the bundle id of the app, like com.example.appname. Not necessary if set on the app (see above) foo: :bar @@ -133,6 +135,7 @@ n = Rpush::Apns::Notification.new n.app = Rpush::Apns::App.find_by_name("ios_app") n.device_token = "..." # hex string n.alert = "hi mom!" +# n.alert = { title: "push title", subtitle: "more to say", body: "hi mom!" } n.data = { foo: :bar } n.save! ``` From ef13b40096577e93334a8153205ac1cb934219be Mon Sep 17 00:00:00 2001 From: Ben Langfeld Date: Thu, 5 Sep 2024 17:43:45 -0300 Subject: [PATCH 03/32] Revert "Fix silent APNS notifications for Apns2 and Apnsp8" (#684) Reverts rpush/rpush#596 Fixes #647 --- CHANGELOG.md | 2 ++ .../client/active_model/apns/notification.rb | 4 ---- lib/rpush/daemon/apns2/delivery.rb | 1 - lib/rpush/daemon/apnsp8/delivery.rb | 1 - spec/functional/apns2_spec.rb | 6 ++---- spec/unit/client/shared/apns/notification.rb | 15 --------------- 6 files changed, 4 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b2bee5b6..597e6aac6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ **Merged pull requests:** * Support for FCMv1 [\#620](https://github.com/rpush/rpush/pull/620) ([mirkode](https://github.com/mirkode)), [\#660](https://github.com/rpush/rpush/pull/660) ([AnilRh](https://github.com/AnilRh)) and [\#673](https://github.com/rpush/rpush/pull/673) ([SixiS](https://github.com/SixiS), [Henridv](https://github.com/Henridv) & [benlangfeld](https://github.com/benlangfeld)) +* No longer silence content-available notifications for APNs. Reverts the following change from the v6.0.0 release. See https://github.com/rpush/rpush/issues/647. + * Fix silent APNS notifications for Apns2 and Apnsp8 [\#596](https://github.com/rpush/rpush/pull/596) ([shved270189](https://github.com/shved270189)) **Breaking:** diff --git a/lib/rpush/client/active_model/apns/notification.rb b/lib/rpush/client/active_model/apns/notification.rb index 8b0847034..1b7aceffa 100644 --- a/lib/rpush/client/active_model/apns/notification.rb +++ b/lib/rpush/client/active_model/apns/notification.rb @@ -52,10 +52,6 @@ def content_available=(bool) self.data = (data || {}).merge(CONTENT_AVAILABLE_KEY => true) end - def content_available? - (self.data || {})[CONTENT_AVAILABLE_KEY] - end - def as_json(options = nil) # rubocop:disable Metrics/AbcSize, Metrics/PerceivedComplexity json = ActiveSupport::OrderedHash.new diff --git a/lib/rpush/daemon/apns2/delivery.rb b/lib/rpush/daemon/apns2/delivery.rb index 4949f41fc..eff20db0a 100644 --- a/lib/rpush/daemon/apns2/delivery.rb +++ b/lib/rpush/daemon/apns2/delivery.rb @@ -112,7 +112,6 @@ def prepare_headers(notification) headers['apns-expiration'] = '0' headers['apns-priority'] = '10' headers['apns-topic'] = @app.bundle_id - headers['apns-push-type'] = 'background' if notification.content_available? headers.merge notification_data(notification)[HTTP2_HEADERS_KEY] || {} end diff --git a/lib/rpush/daemon/apnsp8/delivery.rb b/lib/rpush/daemon/apnsp8/delivery.rb index 276525b7d..4f5fd0a8d 100644 --- a/lib/rpush/daemon/apnsp8/delivery.rb +++ b/lib/rpush/daemon/apnsp8/delivery.rb @@ -149,7 +149,6 @@ def prepare_headers(notification) headers['apns-priority'] = '10' headers['apns-topic'] = @app.bundle_id headers['authorization'] = "bearer #{jwt_token}" - headers['apns-push-type'] = 'background' if notification.content_available? headers.merge notification_data(notification)[HTTP2_HEADERS_KEY] || {} end diff --git a/spec/functional/apns2_spec.rb b/spec/functional/apns2_spec.rb index 302d7f046..73d6c30ed 100644 --- a/spec/functional/apns2_spec.rb +++ b/spec/functional/apns2_spec.rb @@ -79,8 +79,7 @@ def create_notification headers: { 'apns-expiration' => '0', 'apns-priority' => '10', - 'apns-topic' => 'com.example.app', - 'apns-push-type' => 'background' + 'apns-topic' => 'com.example.app' } } ) @@ -114,8 +113,7 @@ def create_notification headers: { 'apns-topic' => bundle_id, 'apns-expiration' => '0', - 'apns-priority' => '10', - 'apns-push-type' => 'background' + 'apns-priority' => '10' } } ).and_return(fake_http2_request) diff --git a/spec/unit/client/shared/apns/notification.rb b/spec/unit/client/shared/apns/notification.rb index cf8b58d26..4af9be30a 100644 --- a/spec/unit/client/shared/apns/notification.rb +++ b/spec/unit/client/shared/apns/notification.rb @@ -165,21 +165,6 @@ end end - describe 'content_available?' do - context 'if not set' do - it 'should be false' do - expect(notification.content_available?).to be_falsey - end - end - - context 'if set' do - it 'should be true' do - notification.content_available = true - expect(notification.content_available?).to be_truthy - end - end - end - describe 'url-args' do it 'includes url-args in the payload' do notification.url_args = ['url-arg-1'] From 7fb4788381e602ab5d54ad9285a6f986353da19a Mon Sep 17 00:00:00 2001 From: Ben Langfeld Date: Fri, 6 Sep 2024 13:43:21 -0300 Subject: [PATCH 04/32] Upgrade locked deps Missed in https://github.com/rpush/rpush/commit/4c98e1712a93b84d095188ecff1a4c9faaa11bd8 --- Gemfile.lock | 41 ++++++++++++++++++++++------------------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index fd44332d4..ae91abf81 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - rpush (7.1.0) + rpush (7.0.1) activesupport (>= 5.2, < 7.1.0) googleauth jwt (>= 1.5.6) @@ -39,15 +39,15 @@ GEM i18n (>= 1.6, < 2) minitest (>= 5.1) tzinfo (~> 2.0) - addressable (2.8.6) - public_suffix (>= 2.0.2, < 6.0) + addressable (2.8.7) + public_suffix (>= 2.0.2, < 7.0) appraisal (2.4.1) bundler rake thor (>= 0.14.0) ast (2.4.2) base64 (0.2.0) - builder (3.2.4) + builder (3.3.0) byebug (11.1.3) codeclimate-test-reporter (1.0.7) simplecov @@ -65,21 +65,23 @@ GEM reline (>= 0.3.8) diff-lcs (1.5.1) docile (1.4.0) - erubi (1.12.0) - faraday (2.9.0) - faraday-net_http (>= 2.0, < 3.2) - faraday-net_http (3.1.0) + erubi (1.13.0) + faraday (2.11.0) + faraday-net_http (>= 2.0, < 3.4) + logger + faraday-net_http (3.3.0) net-http - google-cloud-env (2.1.0) + google-cloud-env (2.2.0) faraday (>= 1.0, < 3.a) - googleauth (1.9.2) + googleauth (1.11.0) faraday (>= 1.0, < 3.a) google-cloud-env (~> 2.1) jwt (>= 1.4, < 3.0) multi_json (~> 1.11) os (>= 0.9, < 2.0) signet (>= 0.16, < 2.a) - http-2 (0.11.0) + http-2 (0.12.0) + base64 i18n (1.14.5) concurrent-ruby (~> 1.0) io-console (0.7.2) @@ -88,10 +90,11 @@ GEM reline (>= 0.4.2) jwt (2.8.2) base64 + logger (1.6.1) loofah (2.22.0) crass (~> 1.0.2) nokogiri (>= 1.12.0) - method_source (1.0.0) + method_source (1.1.0) mini_portile2 (2.8.5) minitest (5.24.1) modis (4.3.0) @@ -109,7 +112,7 @@ GEM connection_pool (~> 2.2) net-http2 (0.18.5) http-2 (~> 0.11) - nokogiri (1.16.0) + nokogiri (1.16.7) mini_portile2 (~> 2.8.2) racc (~> 1.4) openssl (3.2.0) @@ -120,9 +123,9 @@ GEM pg (1.2.3) psych (5.1.2) stringio - public_suffix (5.0.4) - racc (1.7.3) - rack (2.2.8) + public_suffix (6.0.1) + racc (1.8.1) + rack (2.2.9) rack-test (2.1.0) rack (>= 1.3) rails-dom-testing (2.2.0) @@ -181,7 +184,7 @@ GEM rubocop (>= 1.7.0, < 2.0) rubocop-ast (>= 0.4.0) ruby-progressbar (1.11.0) - signet (0.18.0) + signet (0.19.0) addressable (~> 2.8) faraday (>= 0.17.5, < 3.a) jwt (>= 1.5, < 3.0) @@ -201,11 +204,11 @@ GEM tzinfo (2.0.6) concurrent-ruby (~> 1.0) unicode-display_width (2.1.0) - uri (0.13.0) + uri (0.13.1) web-push (3.0.1) jwt (~> 2.0) openssl (~> 3.0) - zeitwerk (2.6.12) + zeitwerk (2.6.18) PLATFORMS ruby From d8010481f24f562acd5e6b1aedd3712d979600f0 Mon Sep 17 00:00:00 2001 From: Ben Langfeld Date: Fri, 6 Sep 2024 15:30:49 -0300 Subject: [PATCH 05/32] Release v8.0.0 (#685) Fixes #588 Fixes #598 Fixes #683 Major release because of reduced compatibility with Ruby/Rails versions. --- CHANGELOG.md | 16 +++++++++++++++- Gemfile.lock | 2 +- lib/rpush/version.rb | 4 ++-- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 597e6aac6..5e9be9679 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,21 @@ * Dropped support for Ruby 2.4, 2.5, 2.6 and Rails 5.2. -[Full Changelog](https://github.com/rpush/rpush/compare/v7.0.1...HEAD) +[Full Changelog](https://github.com/rpush/rpush/compare/v8.0.0...HEAD) + +## [v7.0.1](https://github.com/rpush/rpush/tree/v8.0.0) (2024-09-06) + +**Merged pull requests:** + +* Support for FCMv1 [\#620](https://github.com/rpush/rpush/pull/620) ([mirkode](https://github.com/mirkode)), [\#660](https://github.com/rpush/rpush/pull/660) ([AnilRh](https://github.com/AnilRh)) and [\#673](https://github.com/rpush/rpush/pull/673) ([SixiS](https://github.com/SixiS), [Henridv](https://github.com/Henridv) & [benlangfeld](https://github.com/benlangfeld)) +* No longer silence content-available notifications for APNs. Reverts the following change from the v6.0.0 release. See https://github.com/rpush/rpush/issues/647. + * Fix silent APNS notifications for Apns2 and Apnsp8 [\#596](https://github.com/rpush/rpush/pull/596) ([shved270189](https://github.com/shved270189)) + +**Breaking:** + +* Dropped support for Ruby 2.4, 2.5, 2.6 and Rails 5.2. + +[Full Changelog](https://github.com/rpush/rpush/compare/v7.0.1...v8.0.0) ## [v7.0.1](https://github.com/rpush/rpush/tree/v7.0.1) (2022-03-02) diff --git a/Gemfile.lock b/Gemfile.lock index ae91abf81..7a2ab1d01 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - rpush (7.0.1) + rpush (8.0.0) activesupport (>= 5.2, < 7.1.0) googleauth jwt (>= 1.5.6) diff --git a/lib/rpush/version.rb b/lib/rpush/version.rb index 485fdb1a6..02ce7b8c5 100644 --- a/lib/rpush/version.rb +++ b/lib/rpush/version.rb @@ -1,8 +1,8 @@ module Rpush module VERSION - MAJOR = 7 + MAJOR = 8 MINOR = 0 - TINY = 1 + TINY = 0 PRE = nil STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".").freeze From 3f3b4f2016efa7cbc1b76ce6307f2ff592f3a6f7 Mon Sep 17 00:00:00 2001 From: Ben Langfeld Date: Fri, 6 Sep 2024 15:31:58 -0300 Subject: [PATCH 06/32] Changelog typo --- CHANGELOG.md | 8 -------- 1 file changed, 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e9be9679..e9ca80542 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,14 +4,6 @@ **Merged pull requests:** -* Support for FCMv1 [\#620](https://github.com/rpush/rpush/pull/620) ([mirkode](https://github.com/mirkode)), [\#660](https://github.com/rpush/rpush/pull/660) ([AnilRh](https://github.com/AnilRh)) and [\#673](https://github.com/rpush/rpush/pull/673) ([SixiS](https://github.com/SixiS), [Henridv](https://github.com/Henridv) & [benlangfeld](https://github.com/benlangfeld)) -* No longer silence content-available notifications for APNs. Reverts the following change from the v6.0.0 release. See https://github.com/rpush/rpush/issues/647. - * Fix silent APNS notifications for Apns2 and Apnsp8 [\#596](https://github.com/rpush/rpush/pull/596) ([shved270189](https://github.com/shved270189)) - -**Breaking:** - -* Dropped support for Ruby 2.4, 2.5, 2.6 and Rails 5.2. - [Full Changelog](https://github.com/rpush/rpush/compare/v8.0.0...HEAD) ## [v7.0.1](https://github.com/rpush/rpush/tree/v8.0.0) (2024-09-06) From 5e6c16b5cd06b1e76a46091890e47192f510d929 Mon Sep 17 00:00:00 2001 From: Ben Langfeld Date: Fri, 6 Sep 2024 16:31:26 -0300 Subject: [PATCH 07/32] Drop support for Ruby 2.x (#672) Still supporting some upstream unsupported versions for now to make it easier for users of rpush to upgrade. https://endoflife.date/rails https://endoflife.date/ruby --- .github/workflows/test.yml | 6 +----- .rubocop.yml | 2 +- CHANGELOG.md | 4 ++++ Gemfile.lock | 2 +- rpush.gemspec | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a0399e026..78db0ab35 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -54,14 +54,10 @@ jobs: matrix: gemfile: ['rails_6.0', 'rails_6.1', 'rails_7.0'] - ruby: ['2.7', '3.0', '3.1'] + ruby: ['3.0', '3.1'] client: ['active_record', 'redis'] - exclude: - - ruby: '2.7' - gemfile: 'rails_7.0' - env: # $BUNDLE_GEMFILE must be set at the job level, so it is set for all steps BUNDLE_GEMFILE: gemfiles/${{ matrix.gemfile }}.gemfile diff --git a/.rubocop.yml b/.rubocop.yml index 4d2ba87c9..3396b606a 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -7,7 +7,7 @@ AllCops: - lib/generators/**/* - vendor/bundle/**/* NewCops: enable - TargetRubyVersion: 2.4 + TargetRubyVersion: 3.0 Layout/LineLength: Enabled: false diff --git a/CHANGELOG.md b/CHANGELOG.md index e9ca80542..8b6217b07 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ **Merged pull requests:** +**Breaking:** + +* Drop support for Ruby 2.x [\#672](https://github.com/rpush/rpush/pull/672) ([benlangfeld](https://github.com/benlangfeld)) + [Full Changelog](https://github.com/rpush/rpush/compare/v8.0.0...HEAD) ## [v7.0.1](https://github.com/rpush/rpush/tree/v8.0.0) (2024-09-06) diff --git a/Gemfile.lock b/Gemfile.lock index 7a2ab1d01..c6b14921f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -2,7 +2,7 @@ PATH remote: . specs: rpush (8.0.0) - activesupport (>= 5.2, < 7.1.0) + activesupport (>= 6.0, < 7.1.0) googleauth jwt (>= 1.5.6) multi_json (~> 1.0) diff --git a/rpush.gemspec b/rpush.gemspec index 9ad58e7c1..393ff1808 100644 --- a/rpush.gemspec +++ b/rpush.gemspec @@ -23,7 +23,7 @@ Gem::Specification.new do |s| s.executables = `git ls-files -- bin`.split("\n").map { |f| File.basename(f) } s.require_paths = ["lib"] - s.required_ruby_version = '>= 2.7.0' + s.required_ruby_version = '>= 3.0.0' s.post_install_message = <<~POST_INSTALL_MESSAGE When upgrading Rpush, don't forget to run `bundle exec rpush init` to get all the latest migrations. @@ -36,7 +36,7 @@ Gem::Specification.new do |s| s.add_runtime_dependency 'net-http-persistent' s.add_runtime_dependency 'net-http2', '~> 0.18', '>= 0.18.3' s.add_runtime_dependency 'jwt', '>= 1.5.6' - s.add_runtime_dependency 'activesupport', '>= 5.2', '< 7.1.0' + s.add_runtime_dependency 'activesupport', '>= 6.0', '< 7.1.0' s.add_runtime_dependency 'thor', ['>= 0.18.1', '< 2.0'] s.add_runtime_dependency 'railties' s.add_runtime_dependency 'rainbow' From 82738ab9be1786ca403b6fe3decd3f7a75d43bc8 Mon Sep 17 00:00:00 2001 From: Ben Langfeld Date: Fri, 6 Sep 2024 16:47:38 -0300 Subject: [PATCH 08/32] Remove APNSv1 implementation (#680) The binary interface was shut down by Apple. Fixes https://github.com/rpush/rpush/issues/568. Also makes the test suite more reliable and faster. Re-run of https://github.com/rpush/rpush/pull/614. --- CHANGELOG.md | 1 + README.md | 36 +-- lib/generators/templates/rpush.rb | 7 - lib/rpush.rb | 1 - lib/rpush/apns_feedback.rb | 18 -- lib/rpush/configuration.rb | 19 +- lib/rpush/daemon.rb | 7 - lib/rpush/daemon/apns.rb | 17 - lib/rpush/daemon/apns/delivery.rb | 43 --- lib/rpush/daemon/apns/feedback_receiver.rb | 91 ------ lib/rpush/daemon/dispatcher/apns_tcp.rb | 152 --------- lib/rpush/daemon/dispatcher/tcp.rb | 22 -- lib/rpush/daemon/service_config_methods.rb | 2 - lib/rpush/daemon/store/active_record.rb | 7 - lib/rpush/daemon/store/interface.rb | 2 +- lib/rpush/daemon/store/redis.rb | 4 - lib/rpush/daemon/tcp_connection.rb | 190 ------------ lib/rpush/reflection_collection.rb | 2 +- spec/functional/apns_spec.rb | 162 ---------- spec/functional/cli_spec.rb | 56 +++- spec/functional/embed_spec.rb | 83 +++-- spec/functional/new_app_spec.rb | 44 --- spec/functional_spec_helper.rb | 6 - spec/spec_helper.rb | 2 + spec/unit/apns_feedback_spec.rb | 39 --- spec/unit/daemon/apns/delivery_spec.rb | 108 ------- .../daemon/apns/feedback_receiver_spec.rb | 137 -------- spec/unit/daemon/dispatcher/tcp_spec.rb | 32 -- spec/unit/daemon/shared/store.rb | 9 - spec/unit/daemon/tcp_connection_spec.rb | 293 ------------------ 30 files changed, 110 insertions(+), 1482 deletions(-) delete mode 100644 lib/rpush/apns_feedback.rb delete mode 100644 lib/rpush/daemon/apns.rb delete mode 100644 lib/rpush/daemon/apns/delivery.rb delete mode 100644 lib/rpush/daemon/apns/feedback_receiver.rb delete mode 100644 lib/rpush/daemon/dispatcher/apns_tcp.rb delete mode 100644 lib/rpush/daemon/dispatcher/tcp.rb delete mode 100644 lib/rpush/daemon/tcp_connection.rb delete mode 100644 spec/functional/apns_spec.rb delete mode 100644 spec/functional/new_app_spec.rb delete mode 100644 spec/unit/apns_feedback_spec.rb delete mode 100644 spec/unit/daemon/apns/delivery_spec.rb delete mode 100644 spec/unit/daemon/apns/feedback_receiver_spec.rb delete mode 100644 spec/unit/daemon/dispatcher/tcp_spec.rb delete mode 100644 spec/unit/daemon/tcp_connection_spec.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b6217b07..d43640c63 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ **Breaking:** +* Removed legacy APNSv1 implementation (Apple binary protocol) since this was shut down in 2021. [\#680](https://github.com/rpush/rpush/pull/680) ([benlangfeld](https://github.com/benlangfeld)) * Drop support for Ruby 2.x [\#672](https://github.com/rpush/rpush/pull/672) ([benlangfeld](https://github.com/benlangfeld)) [Full Changelog](https://github.com/rpush/rpush/compare/v8.0.0...HEAD) diff --git a/README.md b/README.md index eba3f373c..72a397260 100644 --- a/README.md +++ b/README.md @@ -57,10 +57,9 @@ There is a choice of two modes (and one legacy mode) using certificates or using * `Rpush::Apns2` This requires an annually renewable certificate. see https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/establishing_a_certificate-based_connection_to_apns * `Rpush::Apnsp8` This uses encrypted tokens and requires an encryption key id and encryption key (provide as a p8 file). (see https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/establishing_a_token-based_connection_to_apns) -* `Rpush::Apns` There is also the original APNS (the original version using certificates with a binary underlying protocol over TCP directly rather than over Http/2). Apple have [announced](https://developer.apple.com/news/?id=c88acm2b) that this is not supported after March 31, 2021. -If this is your first time using the APNs, you will need to generate either SSL certificates (for Apns2 or Apns) or an Encryption Key (p8) and an Encryption Key ID (for Apnsp8). See [Generating Certificates](https://github.com/rpush/rpush/wiki/Generating-Certificates) for instructions. +If this is your first time using the APNs, you will need to generate either SSL certificates (for standard Apns) or an Encryption Key (p8) and an Encryption Key ID (for Apnsp8). See [Generating Certificates](https://github.com/rpush/rpush/wiki/Generating-Certificates) for instructions. ##### Apnsp8 @@ -118,35 +117,13 @@ n.save! You should also implement the [ssl_certificate_will_expire](https://github.com/rpush/rpush/wiki/Reflection-API) reflection to monitor when your certificate is due to expire. -##### Apns (legacy protocol) - -```ruby -app = Rpush::Apns::App.new -app.name = "ios_app" -app.certificate = File.read("/path/to/sandbox.pem") -app.environment = "development" # APNs environment. -app.password = "certificate password" -app.connections = 1 -app.save! -``` - -```ruby -n = Rpush::Apns::Notification.new -n.app = Rpush::Apns::App.find_by_name("ios_app") -n.device_token = "..." # hex string -n.alert = "hi mom!" -# n.alert = { title: "push title", subtitle: "more to say", body: "hi mom!" } -n.data = { foo: :bar } -n.save! -``` - ##### Safari Push Notifications Using one of the notifications methods above, the `url_args` attribute is available for Safari Push Notifications. ##### Environment -The app `environment` for any Apns* option is "development" for XCode installs, and "production" for app store and TestFlight. Note that for Apns2 you can now use one (production + sandbox) certificate (you don't need a separate "sandbox" or development certificate), but if you do generate a development/sandbox certificate it can only be used for "development". With Apnsp8 tokens, you can target either "development" or "production" environments. +The app `environment` for any Apns* option is "development" for XCode installs, and "production" for app store and TestFlight. Note that you can now use one (production + sandbox) certificate (you don't need a separate "sandbox" or development certificate), but if you do generate a development/sandbox certificate it can only be used for "development". With Apnsp8 tokens, you can target either "development" or "production" environments. #### Firebase Cloud Messaging @@ -156,10 +133,10 @@ You will need two params to make use of FCM via Rpush. - `firebase_project_id` - The `Project ID` in your Firebase Project Settings - `json_key` - The JSON key file for a service account with the `Firebase Admin SDK Administrator Service Agent` role. -Create service account in the google cloud account attached to your firebase account: -https://console.cloud.google.com/iam-admin/serviceaccounts -Make sure it has Role `Firebase Admin SDK Administrator Service Agent` -Add + Download the json key for the service account. +Create service account in the google cloud account attached to your firebase account: +https://console.cloud.google.com/iam-admin/serviceaccounts +Make sure it has Role `Firebase Admin SDK Administrator Service Agent` +Add + Download the json key for the service account. Once you have those two params, you can create an FCM app and send notifications. @@ -411,7 +388,6 @@ Rpush will deliver all pending notifications and then exit. ```ruby Rpush.push -Rpush.apns_feedback ``` See [Push API](https://github.com/rpush/rpush/wiki/Push-API) for more details. diff --git a/lib/generators/templates/rpush.rb b/lib/generators/templates/rpush.rb index f102adc65..e8679355e 100644 --- a/lib/generators/templates/rpush.rb +++ b/lib/generators/templates/rpush.rb @@ -30,9 +30,6 @@ # If the logger goes to stdout, you can disable foreground logging to avoid duplication. # config.foreground_logging = false - # config.apns.feedback_receiver.enabled = true - # config.apns.feedback_receiver.frequency = 60 - end Rpush.reflect do |on| @@ -76,10 +73,6 @@ # on.notification_id_will_retry do |app, notification_id, retry_after| # end - # Called when a TCP connection is lost and will be reconnected. - # on.tcp_connection_lost do |app, error| - # end - # Called for each recipient which successfully receives a notification. This # can occur more than once for the same notification when there are multiple # recipients. diff --git a/lib/rpush.rb b/lib/rpush.rb index e913d30a8..d9d532984 100644 --- a/lib/rpush.rb +++ b/lib/rpush.rb @@ -20,7 +20,6 @@ require 'rpush/plugin' require 'rpush/embed' require 'rpush/push' -require 'rpush/apns_feedback' module Rpush def self.jruby? diff --git a/lib/rpush/apns_feedback.rb b/lib/rpush/apns_feedback.rb deleted file mode 100644 index defd81631..000000000 --- a/lib/rpush/apns_feedback.rb +++ /dev/null @@ -1,18 +0,0 @@ -module Rpush - def self.apns_feedback - require 'rpush/daemon' - Rpush::Daemon.common_init - - Rpush::Apns::App.all.each do |app| - # Redis stores every App type on the same namespace, hence the - # additional filtering - next unless app.service_name == 'apns' - next unless app.feedback_enabled - - receiver = Rpush::Daemon::Apns::FeedbackReceiver.new(app) - receiver.check_for_feedback - end - - nil - end -end diff --git a/lib/rpush/configuration.rb b/lib/rpush/configuration.rb index 77ddcad1e..6eb6e9775 100644 --- a/lib/rpush/configuration.rb +++ b/lib/rpush/configuration.rb @@ -16,27 +16,12 @@ def configure end end - CURRENT_ATTRS = [:push_poll, :embedded, :pid_file, :batch_size, :push, :client, :logger, :log_file, :foreground, :foreground_logging, :log_level, :plugin, :apns] + CURRENT_ATTRS = [:push_poll, :embedded, :pid_file, :batch_size, :push, :client, :logger, :log_file, :foreground, :foreground_logging, :log_level, :plugin] DEPRECATED_ATTRS = [] CONFIG_ATTRS = CURRENT_ATTRS + DEPRECATED_ATTRS class ConfigurationError < StandardError; end - class ApnsFeedbackReceiverConfiguration < Struct.new(:frequency, :enabled) # rubocop:disable Style/StructInheritance - def initialize - super - self.enabled = true - self.frequency = 60 - end - end - - class ApnsConfiguration < Struct.new(:feedback_receiver) # rubocop:disable Style/StructInheritance - def initialize - super - self.feedback_receiver = ApnsFeedbackReceiverConfiguration.new - end - end - class Configuration < Struct.new(*CONFIG_ATTRS) # rubocop:disable Style/StructInheritance include Deprecatable @@ -55,8 +40,6 @@ def initialize self.foreground = false self.foreground_logging = true - self.apns = ApnsConfiguration.new - # Internal options. self.embedded = false self.push = false diff --git a/lib/rpush/daemon.rb b/lib/rpush/daemon.rb index 274375dd4..c00fa3068 100644 --- a/lib/rpush/daemon.rb +++ b/lib/rpush/daemon.rb @@ -19,11 +19,8 @@ require 'rpush/daemon/queue_payload' require 'rpush/daemon/synchronizer' require 'rpush/daemon/app_runner' -require 'rpush/daemon/tcp_connection' require 'rpush/daemon/dispatcher_loop' require 'rpush/daemon/dispatcher/http' -require 'rpush/daemon/dispatcher/tcp' -require 'rpush/daemon/dispatcher/apns_tcp' require 'rpush/daemon/dispatcher/apns_http2' require 'rpush/daemon/dispatcher/apnsp8_http2' require 'rpush/daemon/service_config_methods' @@ -38,10 +35,6 @@ require 'rpush/daemon/store/interface' -require 'rpush/daemon/apns/delivery' -require 'rpush/daemon/apns/feedback_receiver' -require 'rpush/daemon/apns' - require 'rpush/daemon/apns2/delivery' require 'rpush/daemon/apns2' diff --git a/lib/rpush/daemon/apns.rb b/lib/rpush/daemon/apns.rb deleted file mode 100644 index 1b9e0acf7..000000000 --- a/lib/rpush/daemon/apns.rb +++ /dev/null @@ -1,17 +0,0 @@ -module Rpush - module Daemon - module Apns - extend ServiceConfigMethods - - HOSTS = { - production: ['gateway.push.apple.com', 2195], - development: ['gateway.sandbox.push.apple.com', 2195], # deprecated - sandbox: ['gateway.sandbox.push.apple.com', 2195] - } - - batch_deliveries true - dispatcher :apns_tcp, host: proc { |app| HOSTS[app.environment.to_sym] } - loops Rpush::Daemon::Apns::FeedbackReceiver, if: -> { Rpush.config.apns.feedback_receiver.enabled && !Rpush.config.push } - end - end -end diff --git a/lib/rpush/daemon/apns/delivery.rb b/lib/rpush/daemon/apns/delivery.rb deleted file mode 100644 index 08945207c..000000000 --- a/lib/rpush/daemon/apns/delivery.rb +++ /dev/null @@ -1,43 +0,0 @@ -module Rpush - module Daemon - module Apns - class Delivery < Rpush::Daemon::Delivery - def initialize(app, connection, batch) - @app = app - @connection = connection - @batch = batch - end - - def perform - @connection.write(batch_to_binary) - mark_batch_delivered - describe_deliveries - rescue Rpush::Daemon::TcpConnectionError => error - mark_batch_retryable(Time.now + 10.seconds, error) - raise - rescue StandardError => error - mark_batch_failed(error) - raise - ensure - @batch.all_processed - end - - protected - - def batch_to_binary - payload = "" - @batch.each_notification do |notification| - payload << notification.to_binary - end - payload - end - - def describe_deliveries - @batch.each_notification do |notification| - log_info("#{notification.id} sent to #{notification.device_token}") - end - end - end - end - end -end diff --git a/lib/rpush/daemon/apns/feedback_receiver.rb b/lib/rpush/daemon/apns/feedback_receiver.rb deleted file mode 100644 index e69ab4b7f..000000000 --- a/lib/rpush/daemon/apns/feedback_receiver.rb +++ /dev/null @@ -1,91 +0,0 @@ -# encoding: UTF-8 - -module Rpush - module Daemon - module Apns - class FeedbackReceiver - include Reflectable - include Loggable - - TUPLE_BYTES = 38 - HOSTS = { - production: ['feedback.push.apple.com', 2196], - development: ['feedback.sandbox.push.apple.com', 2196], # deprecated - sandbox: ['feedback.sandbox.push.apple.com', 2196] - } - - def initialize(app) - @app = app - @host, @port = HOSTS[@app.environment.to_sym] - @certificate = app.certificate - @password = app.password - @interruptible_sleep = InterruptibleSleep.new - end - - def start - return if Rpush.config.push - return unless @app.feedback_enabled - Rpush.logger.info("[#{@app.name}] Starting feedback receiver... ", true) - - @thread = Thread.new do - loop do - break if @stop - check_for_feedback - @interruptible_sleep.sleep(Rpush.config.apns.feedback_receiver.frequency) - end - - Rpush::Daemon.store.release_connection - end - - puts Rainbow('✔').green if Rpush.config.foreground && Rpush.config.foreground_logging - end - - def stop - @stop = true - @interruptible_sleep.stop - @thread.join if @thread - rescue StandardError => e - log_error(e) - reflect(:error, e) - ensure - @thread = nil - end - - def check_for_feedback - connection = nil - begin - connection = Rpush::Daemon::TcpConnection.new(@app, @host, @port) - connection.connect - tuple = connection.read(TUPLE_BYTES) - - while tuple - timestamp, device_token = parse_tuple(tuple) - create_feedback(timestamp, device_token) - tuple = connection.read(TUPLE_BYTES) - end - rescue StandardError => e - log_error(e) - reflect(:error, e) - ensure - connection.close if connection - end - end - - protected - - def parse_tuple(tuple) - failed_at, _, device_token = tuple.unpack("N1n1H*") - [Time.at(failed_at).utc, device_token] - end - - def create_feedback(failed_at, device_token) - formatted_failed_at = failed_at.strftime('%Y-%m-%d %H:%M:%S UTC') - log_info("[FeedbackReceiver] Delivery failed at #{formatted_failed_at} for #{device_token}.") - - feedback = Rpush::Daemon.store.create_apns_feedback(failed_at, device_token, @app) - reflect(:apns_feedback, feedback) - end - end - end - end -end diff --git a/lib/rpush/daemon/dispatcher/apns_tcp.rb b/lib/rpush/daemon/dispatcher/apns_tcp.rb deleted file mode 100644 index 24d09817a..000000000 --- a/lib/rpush/daemon/dispatcher/apns_tcp.rb +++ /dev/null @@ -1,152 +0,0 @@ -module Rpush - module Daemon - module Dispatcher - class ApnsTcp < Rpush::Daemon::Dispatcher::Tcp - include Loggable - include Reflectable - - SELECT_TIMEOUT = 10 - ERROR_TUPLE_BYTES = 6 - APNS_ERRORS = { - 1 => 'Processing error', - 2 => 'Missing device token', - 3 => 'Missing topic', - 4 => 'Missing payload', - 5 => 'Missing token size', - 6 => 'Missing topic size', - 7 => 'Missing payload size', - 8 => 'Invalid device token', - 10 => 'APNs closed connection (possible maintenance)', - 255 => 'None (unknown error)' - } - - def initialize(*args) - super - @dispatch_mutex = Mutex.new - @stop_error_receiver = false - @connection.on_connect { start_error_receiver } - end - - def dispatch(payload) - @dispatch_mutex.synchronize do - @delivery_class.new(@app, @connection, payload.batch).perform - record_batch(payload.batch) - end - end - - def cleanup - if Rpush.config.push - # In push mode only a single batch is sent, followed by immediate shutdown. - # Allow the error receiver time to handle any errors. - @reconnect_disabled = true - sleep 1 - end - - @stop_error_receiver = true - super - @error_receiver_thread.join if @error_receiver_thread - rescue StandardError => e - log_error(e) - reflect(:error, e) - ensure - @error_receiver_thread = nil - end - - private - - def start_error_receiver - @error_receiver_thread = Thread.new do - check_for_error until @stop_error_receiver - Rpush::Daemon.store.release_connection - end - end - - def delivered_buffer - @delivered_buffer ||= RingBuffer.new(Rpush.config.batch_size * 10) - end - - def record_batch(batch) - batch.each_delivered do |notification| - delivered_buffer << notification.id - end - end - - def check_for_error - begin - # On Linux, select returns nil from a dropped connection. - # On OS X, Errno::EBADF is raised following a Errno::EADDRNOTAVAIL from the write call. - return unless @connection.select(SELECT_TIMEOUT) - tuple = @connection.read(ERROR_TUPLE_BYTES) - rescue *TcpConnection::TCP_ERRORS - reconnect unless @stop_error_receiver - return - end - - @dispatch_mutex.synchronize { handle_error_response(tuple) } - rescue StandardError => e - log_error(e) - end - - def handle_error_response(tuple) - if tuple - _, code, notification_id = tuple.unpack('ccN') - handle_error(code, notification_id) - else - handle_disconnect - end - - if Rpush.config.push - # Only attempt to handle a single error in Push mode. - @stop_error_receiver = true - return - end - - reconnect - ensure - delivered_buffer.clear - end - - def reconnect - return if @reconnect_disabled - log_error("Lost connection to #{@connection.host}:#{@connection.port}, reconnecting...") - @connection.reconnect_with_rescue - end - - def handle_disconnect - log_error("The APNs disconnected before any notifications could be delivered. This usually indicates you are using an invalid certificate.") if delivered_buffer.size == 0 - end - - def handle_error(code, notification_id) - notification_id = Rpush::Daemon.store.translate_integer_notification_id(notification_id) - failed_pos = delivered_buffer.index(notification_id) - description = description_for_code(code) - log_error("Notification #{notification_id} failed with error: " + description) - Rpush::Daemon.store.mark_ids_failed([notification_id], code, description, Time.now) - reflect(:notification_id_failed, @app, notification_id, code, description) - - if failed_pos - retry_ids = delivered_buffer[(failed_pos + 1)..-1] - retry_notification_ids(retry_ids, notification_id) - elsif delivered_buffer.size > 0 - log_error("Delivery sequence unknown for notifications following #{notification_id}.") - end - end - - def description_for_code(code) - APNS_ERRORS[code.to_i] ? "#{APNS_ERRORS[code.to_i]} (#{code})" : "Unknown error code #{code.inspect}. Possible Rpush bug?" - end - - def retry_notification_ids(ids, notification_id) - return if ids.size == 0 - - now = Time.now - Rpush::Daemon.store.mark_ids_retryable(ids, now) - notifications_str = 'Notification' - notifications_str += 's' if ids.size > 1 - log_warn("#{notifications_str} #{ids.join(', ')} will be retried due to the failure of notification #{notification_id}.") - ids.each { |id| reflect(:notification_id_will_retry, @app, id, now) } - end - end - end - end -end diff --git a/lib/rpush/daemon/dispatcher/tcp.rb b/lib/rpush/daemon/dispatcher/tcp.rb deleted file mode 100644 index a043ea08f..000000000 --- a/lib/rpush/daemon/dispatcher/tcp.rb +++ /dev/null @@ -1,22 +0,0 @@ -module Rpush - module Daemon - module Dispatcher - class Tcp - def initialize(app, delivery_class, options = {}) - @app = app - @delivery_class = delivery_class - @host, @port = options[:host].call(@app) - @connection = Rpush::Daemon::TcpConnection.new(@app, @host, @port) - end - - def dispatch(payload) - @delivery_class.new(@app, @connection, payload.notification, payload.batch).perform - end - - def cleanup - @connection.close if @connection - end - end - end - end -end diff --git a/lib/rpush/daemon/service_config_methods.rb b/lib/rpush/daemon/service_config_methods.rb index 5dc7393f1..fed762ac9 100644 --- a/lib/rpush/daemon/service_config_methods.rb +++ b/lib/rpush/daemon/service_config_methods.rb @@ -3,8 +3,6 @@ module Daemon module ServiceConfigMethods DISPATCHERS = { http: Rpush::Daemon::Dispatcher::Http, - tcp: Rpush::Daemon::Dispatcher::Tcp, - apns_tcp: Rpush::Daemon::Dispatcher::ApnsTcp, apns_http2: Rpush::Daemon::Dispatcher::ApnsHttp2, apnsp8_http2: Rpush::Daemon::Dispatcher::Apnsp8Http2 } diff --git a/lib/rpush/daemon/store/active_record.rb b/lib/rpush/daemon/store/active_record.rb index 6e1d4fbdf..3189e78ec 100644 --- a/lib/rpush/daemon/store/active_record.rb +++ b/lib/rpush/daemon/store/active_record.rb @@ -138,13 +138,6 @@ def mark_ids_failed(ids, code, description, time) end end - def create_apns_feedback(failed_at, device_token, app) - with_database_reconnect_and_retry do - Rpush::Client::ActiveRecord::Apns::Feedback.create!(failed_at: failed_at, - device_token: device_token, app_id: app.id) - end - end - def create_fcm_notification(attrs, data, app) notification = Rpush::Client::ActiveRecord::Fcm::Notification.new create_fcm_like_notification(notification, attrs, data, app) diff --git a/lib/rpush/daemon/store/interface.rb b/lib/rpush/daemon/store/interface.rb index 515732fd2..f4d420944 100644 --- a/lib/rpush/daemon/store/interface.rb +++ b/lib/rpush/daemon/store/interface.rb @@ -4,7 +4,7 @@ module Store class Interface PUBLIC_METHODS = [:deliverable_notifications, :mark_retryable, :mark_batch_retryable, :mark_delivered, :mark_batch_delivered, - :mark_failed, :mark_batch_failed, :create_apns_feedback, + :mark_failed, :mark_batch_failed, :create_fcm_notification, :create_gcm_notification, :create_adm_notification, :update_app, :update_notification, :release_connection, :all_apps, :app, :mark_ids_failed, :mark_ids_retryable, diff --git a/lib/rpush/daemon/store/redis.rb b/lib/rpush/daemon/store/redis.rb index c1557844d..f1adc4c13 100644 --- a/lib/rpush/daemon/store/redis.rb +++ b/lib/rpush/daemon/store/redis.rb @@ -88,10 +88,6 @@ def mark_ids_retryable(ids, deliver_after) end end - def create_apns_feedback(failed_at, device_token, app) - Rpush::Client::Redis::Apns::Feedback.create!(failed_at: failed_at, device_token: device_token, app_id: app.id) - end - def create_fcm_notification(attrs, data, app) notification = Rpush::Client::Redis::Fcm::Notification.new create_fcm_like_notification(notification, attrs, data, app) diff --git a/lib/rpush/daemon/tcp_connection.rb b/lib/rpush/daemon/tcp_connection.rb deleted file mode 100644 index 381791d38..000000000 --- a/lib/rpush/daemon/tcp_connection.rb +++ /dev/null @@ -1,190 +0,0 @@ -module Rpush - module Daemon - class TcpConnectionError < StandardError; end - - class TcpConnection - include Reflectable - include Loggable - - OSX_TCP_KEEPALIVE = 0x10 # Defined in - KEEPALIVE_INTERVAL = 5 - KEEPALIVE_IDLE = 5 - KEEPALIVE_MAX_FAIL_PROBES = 1 - TCP_ERRORS = [SystemCallError, OpenSSL::OpenSSLError, IOError] - - attr_accessor :last_touch - attr_reader :host, :port - - def self.idle_period - 30.minutes - end - - def initialize(app, host, port) - @app = app - @host = host - @port = port - @certificate = app.certificate - @password = app.password - @connected = false - @connection_callbacks = [] - touch - end - - def on_connect(&blk) - raise 'already connected' if @connected - @connection_callbacks << blk - end - - def connect - @ssl_context = setup_ssl_context - @tcp_socket, @ssl_socket = connect_socket - @connected = true - - @connection_callbacks.each do |blk| - begin - blk.call - rescue StandardError => e - log_error(e) - end - end - - @connection_callbacks.clear - end - - def close - @ssl_socket.close if @ssl_socket - @tcp_socket.close if @tcp_socket - rescue IOError # rubocop:disable HandleExceptions - end - - def read(num_bytes) - @ssl_socket.read(num_bytes) if @ssl_socket - end - - def select(timeout) - IO.select([@ssl_socket], nil, nil, timeout) if @ssl_socket - end - - def write(data) - connect unless @connected - reconnect_idle if idle_period_exceeded? - - retry_count = 0 - - begin - write_data(data) - rescue *TCP_ERRORS => e - retry_count += 1 - - if retry_count == 1 - log_error("Lost connection to #{@host}:#{@port} (#{e.class.name}, #{e.message}), reconnecting...") - reflect(:tcp_connection_lost, @app, e) - end - - if retry_count <= 3 - reconnect_with_rescue - sleep 1 - retry - else - raise TcpConnectionError, "#{@app.name} tried #{retry_count - 1} times to reconnect but failed (#{e.class.name}, #{e.message})." - end - end - end - - def reconnect_with_rescue - reconnect - rescue StandardError => e - log_error(e) - end - - def reconnect - close - @tcp_socket, @ssl_socket = connect_socket - end - - protected - - def reconnect_idle - log_info("Idle period exceeded, reconnecting...") - reconnect - end - - def idle_period_exceeded? - Time.now - last_touch > self.class.idle_period - end - - def write_data(data) - @ssl_socket.write(data) - @ssl_socket.flush - touch - end - - def touch - self.last_touch = Time.now - end - - def setup_ssl_context - ssl_context = OpenSSL::SSL::SSLContext.new - ssl_context.key = OpenSSL::PKey::RSA.new(@certificate, @password) - ssl_context.cert = OpenSSL::X509::Certificate.new(@certificate) - ssl_context - end - - def connect_socket - touch - check_certificate_expiration - - tcp_socket = TCPSocket.new(@host, @port) - tcp_socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, true) - tcp_socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, true) - - # Linux - if [:SOL_TCP, :TCP_KEEPIDLE, :TCP_KEEPINTVL, :TCP_KEEPCNT].all? { |c| Socket.const_defined?(c) } - tcp_socket.setsockopt(Socket::SOL_TCP, Socket::TCP_KEEPIDLE, KEEPALIVE_IDLE) - tcp_socket.setsockopt(Socket::SOL_TCP, Socket::TCP_KEEPINTVL, KEEPALIVE_INTERVAL) - tcp_socket.setsockopt(Socket::SOL_TCP, Socket::TCP_KEEPCNT, KEEPALIVE_MAX_FAIL_PROBES) - end - - # OSX - if RUBY_PLATFORM =~ /darwin/ - tcp_socket.setsockopt(Socket::IPPROTO_TCP, OSX_TCP_KEEPALIVE, KEEPALIVE_IDLE) - end - - ssl_socket = OpenSSL::SSL::SSLSocket.new(tcp_socket, @ssl_context) - ssl_socket.sync = true - ssl_socket.connect - [tcp_socket, ssl_socket] - rescue *TCP_ERRORS => error - if error.message =~ /certificate revoked/i - log_error('Certificate has been revoked.') - reflect(:ssl_certificate_revoked, @app, error) - end - raise TcpConnectionError, "#{error.class.name}, #{error.message}" - end - - def check_certificate_expiration - cert = @ssl_context.cert - if certificate_expired? - log_error(certificate_msg('expired')) - raise Rpush::CertificateExpiredError.new(@app, cert.not_after) - elsif certificate_expires_soon? - log_warn(certificate_msg('will expire')) - reflect(:ssl_certificate_will_expire, @app, cert.not_after) - end - end - - def certificate_msg(msg) - time = @ssl_context.cert.not_after.utc.strftime('%Y-%m-%d %H:%M:%S UTC') - "Certificate #{msg} at #{time}." - end - - def certificate_expired? - @ssl_context.cert.not_after && @ssl_context.cert.not_after.utc < Time.now.utc - end - - def certificate_expires_soon? - @ssl_context.cert.not_after && @ssl_context.cert.not_after.utc < (Time.now + 1.month).utc - end - end - end -end diff --git a/lib/rpush/reflection_collection.rb b/lib/rpush/reflection_collection.rb index f2574870c..e522f5be9 100644 --- a/lib/rpush/reflection_collection.rb +++ b/lib/rpush/reflection_collection.rb @@ -8,7 +8,7 @@ class NoSuchReflectionError < StandardError; end :gcm_delivered_to_recipient, :gcm_failed_to_recipient, :gcm_canonical_id, :gcm_invalid_registration_id, :fcm_delivered_to_recipient, :fcm_failed_to_recipient, :fcm_canonical_id, :fcm_invalid_device_token, :error, :adm_canonical_id, :adm_failed_to_recipient, :wns_invalid_channel, - :tcp_connection_lost, :ssl_certificate_will_expire, :ssl_certificate_revoked, + :ssl_certificate_will_expire, :ssl_certificate_revoked, :notification_id_will_retry, :notification_id_failed ] diff --git a/spec/functional/apns_spec.rb b/spec/functional/apns_spec.rb deleted file mode 100644 index 9cc7f1354..000000000 --- a/spec/functional/apns_spec.rb +++ /dev/null @@ -1,162 +0,0 @@ -require 'functional_spec_helper' - -describe 'APNs' do - let(:app) { create_app } - let(:tcp_socket) { double(TCPSocket, setsockopt: nil, close: nil) } - let(:ssl_socket) { double(OpenSSL::SSL::SSLSocket, :sync= => nil, connect: nil, write: nil, flush: nil, read: nil, close: nil) } - let(:io_double) { double(select: nil) } - let(:delivered_ids) { [] } - let(:failed_ids) { [] } - let(:retry_ids) { [] } - - before do - Rpush.config.push_poll = 0.5 - stub_tcp_connection(tcp_socket, ssl_socket, io_double) - end - - def create_app - app = Rpush::Apns::App.new - app.certificate = TEST_CERT - app.name = 'test' - app.environment = 'sandbox' - app.save! - app - end - - def create_notification - notification = Rpush::Apns::Notification.new - notification.app = app - notification.alert = 'test' - notification.device_token = 'a' * 108 - notification.save! - notification - end - - def wait - sleep 0.1 - end - - def wait_for_notification_to_deliver(notification) - timeout { wait until delivered_ids.include?(notification.id) } - end - - def wait_for_notification_to_fail(notification) - timeout { wait until failed_ids.include?(notification.id) } - end - - def wait_for_notification_to_retry(notification) - timeout { wait until retry_ids.include?(notification.id) } - end - - def fail_notification(notification) - allow(ssl_socket).to receive_messages(read: [8, 4, notification.id].pack('ccN')) - enable_io_select - end - - def enable_io_select - called = false - allow(io_double).to receive(:select) do - if called - nil - else - called = true - end - end - end - - it 'delivers a notification successfully' do - notification = create_notification - expect do - Rpush.push - notification.reload - end.to change(notification, :delivered).to(true) - end - - it 'receives feedback' do - app - tuple = "N\xE3\x84\r\x00 \x83OxfU\xEB\x9F\x84aJ\x05\xAD}\x00\xAF1\xE5\xCF\xE9:\xC3\xEA\a\x8F\x1D\xA4M*N\xB0\xCE\x17" - allow(ssl_socket).to receive(:read).and_return(tuple, nil) - Rpush.apns_feedback - feedback = Rpush::Apns::Feedback.all.first - expect(feedback).not_to be_nil - expect(feedback.app_id).to eq(app.id) - expect(feedback.device_token).to eq('834f786655eb9f84614a05ad7d00af31e5cfe93ac3ea078f1da44d2a4eb0ce17') - end - - describe 'delivery failures' do - before do - Rpush.reflect do |on| - on.notification_delivered do |n| - delivered_ids << n.id - end - - on.notification_id_failed do |_, n_id| - failed_ids << n_id - end - - on.notification_id_will_retry do |_, n_id| - retry_ids << n_id - end - - on.notification_will_retry do |n| - retry_ids << n.id - end - end - - Rpush.embed - end - - after do - Rpush.reflection_stack.clear - Rpush.reflection_stack.push(Rpush::ReflectionCollection.new) - - timeout { Rpush.shutdown } - end - - it 'fails to deliver a notification' do - notification = create_notification - wait_for_notification_to_deliver(notification) - fail_notification(notification) - wait_for_notification_to_fail(notification) - end - - describe 'with a failed connection' do - it 'retries all notifications' do - allow_any_instance_of(Rpush::Daemon::TcpConnection).to receive_messages(sleep: nil) - expect(ssl_socket).to receive(:write).at_least(1).times.and_raise(Errno::EPIPE) - notifications = 2.times.map { create_notification } - notifications.each { |n| wait_for_notification_to_retry(n) } - end - end - - describe 'with multiple notifications' do - let(:notification1) { create_notification } - let(:notification2) { create_notification } - let(:notification3) { create_notification } - let(:notification4) { create_notification } - let(:notifications) { [notification1, notification2, notification3, notification4] } - - it 'marks the correct notification as failed' do - notifications.each { |n| wait_for_notification_to_deliver(n) } - fail_notification(notification2) - wait_for_notification_to_fail(notification2) - end - - it 'does not mark prior notifications as failed' do - notifications.each { |n| wait_for_notification_to_deliver(n) } - fail_notification(notification2) - wait_for_notification_to_fail(notification2) - - expect(failed_ids).to_not include(notification1.id) - notification1.reload - expect(notification1.delivered).to eq(true) - end - - it 'marks notifications following the failed one as retryable' do - notifications.each { |n| wait_for_notification_to_deliver(n) } - fail_notification(notification2) - [notification3, notification4].each { |n| wait_for_notification_to_retry(n) } - end - end - end -end diff --git a/spec/functional/cli_spec.rb b/spec/functional/cli_spec.rb index e02fef4f2..30b921086 100644 --- a/spec/functional/cli_spec.rb +++ b/spec/functional/cli_spec.rb @@ -2,31 +2,57 @@ describe Rpush::CLI do def create_app - app = Rpush::Apns::App.new + app = Rpush::Apns2::App.new app.certificate = TEST_CERT app.name = 'test' - app.environment = 'sandbox' + app.environment = 'development' + app.bundle_id = 'com.example.app' app.save! app end - describe 'status' do - let(:tcp_socket) { double(TCPSocket, setsockopt: nil, close: nil) } - let(:ssl_socket) { double(OpenSSL::SSL::SSLSocket, :sync= => nil, connect: nil, write: nil, flush: nil, read: nil, close: nil) } - let(:io_double) { double(select: nil) } + let(:fake_client) { + double( + prepare_request: fake_http2_request, + close: 'ok', + call_async: 'ok', + join: 'ok', + on: 'ok' + ) + } + let(:fake_http2_request) { double } + let(:fake_http_resp_headers) { + { + ":status" => "200", + "apns-id"=>"C6D65840-5E3F-785A-4D91-B97D305C12F6" + } + } + let(:fake_http_resp_body) { '' } - before do - create_app - stub_tcp_connection(tcp_socket, ssl_socket, io_double) - Rpush.embed + before do + create_app + Rpush.config.push_poll = 0.5 - timeout do - Thread.pass until File.exist?(Rpush::Daemon::Rpc.socket_path) - end - end + allow(NetHttp2::Client). + to receive(:new).and_return(fake_client) + allow(fake_http2_request). + to receive(:on).with(:headers). + and_yield(fake_http_resp_headers) + allow(fake_http2_request). + to receive(:on).with(:body_chunk). + and_yield(fake_http_resp_body) + allow(fake_http2_request). + to receive(:on).with(:close). + and_yield + + Rpush.embed + end - after { timeout { Rpush.shutdown } } + after do + timeout { Rpush.shutdown } + end + describe 'status' do it 'prints the status' do expect(subject).to receive(:configure_rpush) { true } expect(subject).to receive(:puts).with(/app_runners:/) diff --git a/spec/functional/embed_spec.rb b/spec/functional/embed_spec.rb index 0adf57096..4005ca0a8 100644 --- a/spec/functional/embed_spec.rb +++ b/spec/functional/embed_spec.rb @@ -1,49 +1,80 @@ require 'functional_spec_helper' describe 'embedding' do - let(:timeout) { 10 } - let(:app) { Rpush::Apns::App.new } - let(:notification) { Rpush::Apns::Notification.new } - let(:tcp_socket) { double(TCPSocket, setsockopt: nil, close: nil) } - let(:ssl_socket) { double(OpenSSL::SSL::SSLSocket, :sync= => nil, connect: nil, write: nil, flush: nil, read: nil, close: nil) } - let(:io_double) { double(select: nil) } - - before do + def create_app + app = Rpush::Apns2::App.new app.certificate = TEST_CERT app.name = 'test' - app.environment = 'sandbox' + app.environment = 'development' + app.bundle_id = 'com.example.app' app.save! + app + end + let(:fake_device_token) { 'a' * 108 } + let(:notification_data) { nil } + + def create_notification(app) + notification = Rpush::Apns2::Notification.new notification.app = app + notification.sound = 'default' notification.alert = 'test' - notification.device_token = 'a' * 108 + notification.device_token = fake_device_token + notification.data = notification_data + notification.content_available = 1 notification.save! - - stub_tcp_connection + notification end - def stub_tcp_connection - allow_any_instance_of(Rpush::Daemon::TcpConnection).to receive_messages(connect_socket: [tcp_socket, ssl_socket]) - allow_any_instance_of(Rpush::Daemon::TcpConnection).to receive_messages(setup_ssl_context: double.as_null_object) - stub_const('Rpush::Daemon::TcpConnection::IO', io_double) - allow(Rpush::Daemon::Apns::FeedbackReceiver).to receive_messages(new: double.as_null_object) - end + let(:app) { create_app } + let(:notification) { create_notification(app) } + + let(:fake_client) { + double( + prepare_request: fake_http2_request, + close: 'ok', + call_async: 'ok', + join: 'ok', + on: 'ok' + ) + } + let(:fake_http2_request) { double } + let(:fake_http_resp_headers) { + { + ":status" => "200", + "apns-id"=>"C6D65840-5E3F-785A-4D91-B97D305C12F6" + } + } + let(:fake_http_resp_body) { '' } before do - Rpush.config.push_poll = 5 + Rpush.config.push_poll = 0.5 + + allow(NetHttp2::Client). + to receive(:new).and_return(fake_client) + allow(fake_http2_request). + to receive(:on).with(:headers). + and_yield(fake_http_resp_headers) + allow(fake_http2_request). + to receive(:on).with(:body_chunk). + and_yield(fake_http_resp_body) + allow(fake_http2_request). + to receive(:on).with(:close). + and_yield + Rpush.embed end + after do + timeout { Rpush.shutdown } + end + it 'delivers a notification successfully' do expect do - Timeout.timeout(timeout) do - until notification.delivered - notification.reload - sleep 0.1 - end + until notification.delivered + notification.reload + sleep 0.1 end end.to change(notification, :delivered).to(true) end - - after { Timeout.timeout(timeout) { Rpush.shutdown } } end diff --git a/spec/functional/new_app_spec.rb b/spec/functional/new_app_spec.rb deleted file mode 100644 index a1580413c..000000000 --- a/spec/functional/new_app_spec.rb +++ /dev/null @@ -1,44 +0,0 @@ -require 'functional_spec_helper' - -describe 'New app loading' do - let(:timeout) { 10 } - let(:app) { create_app } - let(:tcp_socket) { double(TCPSocket, setsockopt: nil, close: nil) } - let(:ssl_socket) { double(OpenSSL::SSL::SSLSocket, :sync= => nil, connect: nil, write: nil, flush: nil, read: nil, close: nil) } - let(:io_double) { double(select: nil) } - - before do - stub_tcp_connection - end - - def create_app - app = Rpush::Apns::App.new - app.certificate = TEST_CERT - app.name = 'test' - app.environment = 'sandbox' - app.save! - app - end - - def create_notification - notification = Rpush::Apns::Notification.new - notification.app = app - notification.alert = 'test' - notification.device_token = 'a' * 108 - notification.save! - notification - end - - def stub_tcp_connection - allow_any_instance_of(Rpush::Daemon::TcpConnection).to receive_messages(connect_socket: [tcp_socket, ssl_socket]) - allow_any_instance_of(Rpush::Daemon::TcpConnection).to receive_messages(setup_ssl_context: double.as_null_object) - stub_const('Rpush::Daemon::TcpConnection::IO', io_double) - end - - it 'delivers a notification successfully' do - notification = create_notification - Rpush.push - notification.reload - expect(notification.delivered).to eq(true) - end -end diff --git a/spec/functional_spec_helper.rb b/spec/functional_spec_helper.rb index 46da6f137..f524e7dd7 100644 --- a/spec/functional_spec_helper.rb +++ b/spec/functional_spec_helper.rb @@ -11,12 +11,6 @@ def timeout(&blk) Timeout.timeout(10, &blk) end -def stub_tcp_connection(tcp_socket, ssl_socket, io_double) - allow_any_instance_of(Rpush::Daemon::TcpConnection).to receive_messages(connect_socket: [tcp_socket, ssl_socket]) - allow_any_instance_of(Rpush::Daemon::TcpConnection).to receive_messages(setup_ssl_context: double.as_null_object) - stub_const('Rpush::Daemon::TcpConnection::IO', io_double) -end - RSpec.configure do |config| config.before(:each) do Modis.with_connection do |redis| diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 8911d55bf..d015ff036 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -58,6 +58,8 @@ def after_example_cleanup end Rpush.plugins.values.each(&:unload) Rpush.instance_variable_set('@plugins', {}) + Rpush.reflection_stack.clear + Rpush.reflection_stack.push(Rpush::ReflectionCollection.new) end RSpec.configure do |config| diff --git a/spec/unit/apns_feedback_spec.rb b/spec/unit/apns_feedback_spec.rb deleted file mode 100644 index 81267aef2..000000000 --- a/spec/unit/apns_feedback_spec.rb +++ /dev/null @@ -1,39 +0,0 @@ -require 'unit_spec_helper' - -describe Rpush, 'apns_feedback' do - let!(:apns_app) { Rpush::Apns::App.create!(apns_app_params) } - let(:apns_app_params) do - { - name: 'test', - environment: 'production', - certificate: TEST_CERT - } - end - let!(:gcm_app) { Rpush::Gcm::App.create!(name: 'MyApp', auth_key: 'abc123') } - - let(:receiver) { double(check_for_feedback: nil) } - - before do - allow(Rpush::Daemon::Apns::FeedbackReceiver).to receive(:new) { receiver } - end - - it 'initializes the daemon' do - expect(Rpush::Daemon).to receive(:common_init) - Rpush.apns_feedback - end - - it 'checks feedback for each app' do - expect(Rpush::Daemon::Apns::FeedbackReceiver).to receive(:new).with(apns_app).and_return(receiver) - expect(receiver).to receive(:check_for_feedback) - Rpush.apns_feedback - end - - context 'feedback disabled' do - let(:apns_app_params) { super().merge(feedback_enabled: false) } - - it 'does not initialize feedback receiver' do - expect(Rpush::Daemon::Apns::FeedbackReceiver).not_to receive(:new) - Rpush.apns_feedback - end - end -end diff --git a/spec/unit/daemon/apns/delivery_spec.rb b/spec/unit/daemon/apns/delivery_spec.rb deleted file mode 100644 index f6f1186fd..000000000 --- a/spec/unit/daemon/apns/delivery_spec.rb +++ /dev/null @@ -1,108 +0,0 @@ -require 'unit_spec_helper' - -describe Rpush::Daemon::Apns::Delivery do - let(:app) { double(name: 'MyApp') } - let(:notification1) { double.as_null_object } - let(:notification2) { double.as_null_object } - let(:batch) { double(mark_all_failed: nil, mark_all_delivered: nil, all_processed: nil) } - let(:logger) { double(error: nil, info: nil) } - let(:connection) { double(select: false, write: nil, reconnect: nil, close: nil, connect: nil) } - let(:delivery) { Rpush::Daemon::Apns::Delivery.new(app, connection, batch) } - - before do - allow(batch).to receive(:each_notification) do |&blk| - [notification1, notification2].each(&blk) - end - allow(Rpush).to receive_messages(logger: logger) - end - - it 'writes the binary batch' do - allow(notification1).to receive_messages(to_binary: 'binary1') - allow(notification2).to receive_messages(to_binary: 'binary2') - expect(connection).to receive(:write).with('binary1binary2') - delivery.perform - end - - it 'logs the notification deliveries' do - allow(notification1).to receive_messages(id: 666, device_token: 'abc123') - allow(notification2).to receive_messages(id: 42, device_token: 'abc456') - expect(logger).to receive(:info).with('[MyApp] 666 sent to abc123') - expect(logger).to receive(:info).with('[MyApp] 42 sent to abc456') - delivery.perform - end - - it 'marks all notifications as delivered' do - expect(delivery).to receive(:mark_batch_delivered) - delivery.perform - end - - it 'notifies the batch all notifications have been processed' do - expect(batch).to receive(:all_processed) - delivery.perform - end - - describe 'when an error is raised' do - it 'marks all notifications as failed' do - error = StandardError.new - allow(connection).to receive(:write).and_raise(error) - expect(delivery).to receive(:mark_batch_failed).with(error) - expect { delivery.perform }.to raise_error(error) - end - end - - # describe "when delivery fails" do - # before { connection.stub(select: true, read: [8, 4, 69].pack("ccN")) } - # - # it "marks the notification as failed" do - # delivery.should_receive(:mark_failed).with(4, "Unable to deliver notification 69, received error 4 (Missing payload)") - # perform - # end - # - # it "logs the delivery error" do - # # checking for the doublebed error doesn't work in jruby, but checking - # # for the exception by class does. - # - # # error = Rpush::DeliveryError.new(4, 12, "Missing payload") - # # Rpush::DeliveryError.stub(new: error) - # # expect { delivery.perform }.to raise_error(error) - # - # expect { delivery.perform }.to raise_error(Rpush::DeliveryError) - # end - # - # it "reads 6 bytes from the socket" do - # connection.should_receive(:read).with(6).and_return(nil) - # perform - # end - # - # it "does not attempt to read from the socket if the socket was not selected for reading after the timeout" do - # connection.stub(select: nil) - # connection.should_not_receive(:read) - # perform - # end - # - # it "reconnects the socket" do - # connection.should_receive(:reconnect) - # perform - # end - # - # it "logs that the connection is being reconnected" do - # Rpush.logger.should_receive(:error).with("[MyApp] Error received, reconnecting...") - # perform - # end - # - # context "when the APNs disconnects without returning an error" do - # before do - # connection.stub(read: nil) - # end - # - # it 'raises a DisconnectError error if the connection is closed without an error being returned' do - # expect { delivery.perform }.to raise_error(Rpush::DisconnectionError) - # end - # - # it 'marks the notification as failed' do - # delivery.should_receive(:mark_failed).with(nil, "The APNs disconnected without returning an error. This may indicate you are using an invalid certificate for the host.") - # perform - # end - # end - # end -end diff --git a/spec/unit/daemon/apns/feedback_receiver_spec.rb b/spec/unit/daemon/apns/feedback_receiver_spec.rb deleted file mode 100644 index eebbba40b..000000000 --- a/spec/unit/daemon/apns/feedback_receiver_spec.rb +++ /dev/null @@ -1,137 +0,0 @@ -require 'unit_spec_helper' -require 'rpush/daemon/store/active_record' - -describe Rpush::Daemon::Apns::FeedbackReceiver, 'check_for_feedback' do - let(:host) { 'feedback.push.apple.com' } - let(:port) { 2196 } - let(:frequency) { 60 } - let(:certificate) { double } - let(:password) { double } - let(:feedback_enabled) { true } - let(:app) do - double( - name: 'my_app', - password: password, - certificate: certificate, - feedback_enabled: feedback_enabled, - environment: 'production' - ) - end - let(:connection) { double(connect: nil, read: nil, close: nil) } - let(:logger) { double(error: nil, info: nil) } - let(:receiver) { Rpush::Daemon::Apns::FeedbackReceiver.new(app) } - let(:feedback) { double } - let(:sleeper) { double(Rpush::Daemon::InterruptibleSleep, sleep: nil, stop: nil) } - let(:store) { double(Rpush::Daemon::Store::ActiveRecord, create_apns_feedback: feedback, release_connection: nil) } - - before do - Rpush.config.apns.feedback_receiver.frequency = frequency - allow(Rpush::Daemon::InterruptibleSleep).to receive_messages(new: sleeper) - allow(Rpush).to receive_messages(logger: logger) - allow(Rpush::Daemon::TcpConnection).to receive_messages(new: connection) - receiver.instance_variable_set("@stop", false) - allow(Rpush::Daemon).to receive_messages(store: store) - end - - def double_connection_read_with_tuple - def connection.read(*) - unless @called - @called = true - "N\xE3\x84\r\x00 \x83OxfU\xEB\x9F\x84aJ\x05\xAD}\x00\xAF1\xE5\xCF\xE9:\xC3\xEA\a\x8F\x1D\xA4M*N\xB0\xCE\x17" - end - end - end - - it 'initializes the sleeper with the feedback polling frequency' do - expect(Rpush::Daemon::InterruptibleSleep).to receive_messages(new: sleeper) - Rpush::Daemon::Apns::FeedbackReceiver.new(app) - end - - it 'instantiates a new connection' do - expect(Rpush::Daemon::TcpConnection).to receive(:new).with(app, host, port) - receiver.check_for_feedback - end - - it 'connects to the feeback service' do - expect(connection).to receive(:connect) - receiver.check_for_feedback - end - - it 'closes the connection' do - expect(connection).to receive(:close) - receiver.check_for_feedback - end - - it 'reads from the connection' do - expect(connection).to receive(:read).with(38) - receiver.check_for_feedback - end - - it 'logs the feedback' do - double_connection_read_with_tuple - expect(Rpush.logger).to receive(:info).with("[my_app] [FeedbackReceiver] Delivery failed at 2011-12-10 16:08:45 UTC for 834f786655eb9f84614a05ad7d00af31e5cfe93ac3ea078f1da44d2a4eb0ce17.") - receiver.check_for_feedback - end - - it 'creates the feedback' do - expect(Rpush::Daemon.store).to receive(:create_apns_feedback).with(Time.at(1_323_533_325), '834f786655eb9f84614a05ad7d00af31e5cfe93ac3ea078f1da44d2a4eb0ce17', app) - double_connection_read_with_tuple - receiver.check_for_feedback - end - - it 'logs errors' do - error = StandardError.new('bork!') - allow(connection).to receive(:read).and_raise(error) - expect(Rpush.logger).to receive(:error).with(error) - receiver.check_for_feedback - end - - describe 'start' do - before do - allow(Thread).to receive(:new).and_yield - allow(receiver).to receive(:loop).and_yield - end - - it 'sleeps' do - allow(receiver).to receive(:check_for_feedback) - expect(sleeper).to receive(:sleep).at_least(:once) - receiver.start - end - - it 'checks for feedback when started' do - expect(receiver).to receive(:check_for_feedback).at_least(:once) - receiver.start - end - - context 'with feedback_enabled false' do - let(:feedback_enabled) { false } - - it 'does not check for feedback when started' do - expect(receiver).not_to receive(:check_for_feedback) - receiver.start - end - end - end - - describe 'stop' do - it 'interrupts sleep when stopped' do - allow(receiver).to receive(:check_for_feedback) - expect(sleeper).to receive(:stop) - receiver.stop - end - - it 'releases the store connection' do - allow(Thread).to receive(:new).and_yield - allow(receiver).to receive(:loop).and_yield - expect(Rpush::Daemon.store).to receive(:release_connection) - receiver.start - receiver.stop - end - end - - it 'reflects feedback was received' do - double_connection_read_with_tuple - expect(receiver).to receive(:reflect).with(:apns_feedback, feedback) - receiver.check_for_feedback - end -end diff --git a/spec/unit/daemon/dispatcher/tcp_spec.rb b/spec/unit/daemon/dispatcher/tcp_spec.rb deleted file mode 100644 index bbcac0952..000000000 --- a/spec/unit/daemon/dispatcher/tcp_spec.rb +++ /dev/null @@ -1,32 +0,0 @@ -require 'unit_spec_helper' - -describe Rpush::Daemon::Dispatcher::Tcp do - let(:app) { double } - let(:delivery) { double(perform: nil) } - let(:delivery_class) { double(new: delivery) } - let(:notification) { double } - let(:batch) { double } - let(:connection) { double(Rpush::Daemon::TcpConnection, connect: nil) } - let(:host) { 'localhost' } - let(:port) { 1234 } - let(:host_proc) { proc { [host, port] } } - let(:queue_payload) { Rpush::Daemon::QueuePayload.new(batch, notification) } - let(:dispatcher) { Rpush::Daemon::Dispatcher::Tcp.new(app, delivery_class, host: host_proc) } - - before { allow(Rpush::Daemon::TcpConnection).to receive_messages(new: connection) } - - describe 'dispatch' do - it 'delivers the notification' do - expect(delivery_class).to receive(:new).with(app, connection, notification, batch).and_return(delivery) - expect(delivery).to receive(:perform) - dispatcher.dispatch(queue_payload) - end - end - - describe 'cleanup' do - it 'closes the connection' do - expect(connection).to receive(:close) - dispatcher.cleanup - end - end -end diff --git a/spec/unit/daemon/shared/store.rb b/spec/unit/daemon/shared/store.rb index c3d67f97d..89f7c8518 100644 --- a/spec/unit/daemon/shared/store.rb +++ b/spec/unit/daemon/shared/store.rb @@ -233,15 +233,6 @@ end end - describe 'create_apns_feedback' do - it 'creates the Feedback record' do - expect(Rpush::Apns::Feedback).to receive(:create!).with( - failed_at: time, device_token: 'ab' * 32, app_id: app.id - ) - store.create_apns_feedback(time, 'ab' * 32, app) - end - end - describe 'create_gcm_notification' do let(:data) { { 'data' => true } } let(:attributes) { { device_token: 'ab' * 32 } } diff --git a/spec/unit/daemon/tcp_connection_spec.rb b/spec/unit/daemon/tcp_connection_spec.rb deleted file mode 100644 index f83c26cfb..000000000 --- a/spec/unit/daemon/tcp_connection_spec.rb +++ /dev/null @@ -1,293 +0,0 @@ -require "unit_spec_helper" - -describe Rpush::Daemon::TcpConnection do - let(:rsa_key) { double } - let(:certificate) { double } - let(:password) { double } - let(:x509_certificate) { OpenSSL::X509::Certificate.new(TEST_CERT) } - let(:ssl_context) { double(:key= => nil, :cert= => nil, cert: x509_certificate) } - let(:host) { 'gateway.push.apple.com' } - let(:port) { '2195' } - let(:tcp_socket) { double(setsockopt: nil, close: nil) } - let(:ssl_socket) { double(:sync= => nil, connect: nil, close: nil, write: nil, flush: nil) } - let(:logger) { double(info: nil, error: nil, warn: nil) } - let(:app) { double(name: 'Connection 0', certificate: certificate, password: password) } - let(:connection) { Rpush::Daemon::TcpConnection.new(app, host, port) } - - before do - allow(x509_certificate).to receive(:not_after).and_return(Time.now + 1.year) - allow(OpenSSL::SSL::SSLContext).to receive_messages(new: ssl_context) - allow(OpenSSL::PKey::RSA).to receive_messages(new: rsa_key) - allow(OpenSSL::X509::Certificate).to receive_messages(new: x509_certificate) - allow(TCPSocket).to receive_messages(new: tcp_socket) - allow(OpenSSL::SSL::SSLSocket).to receive_messages(new: ssl_socket) - allow(Rpush).to receive_messages(logger: logger) - allow(connection).to receive(:reflect) - end - - it "reads the number of bytes from the SSL socket" do - expect(ssl_socket).to receive(:read).with(123) - connection.connect - connection.read(123) - end - - it "selects on the SSL socket until the given timeout" do - expect(IO).to receive(:select).with([ssl_socket], nil, nil, 10) - connection.connect - connection.select(10) - end - - describe "when setting up the SSL context" do - it "sets the key on the context" do - expect(OpenSSL::PKey::RSA).to receive(:new).with(certificate, password).and_return(rsa_key) - expect(ssl_context).to receive(:key=).with(rsa_key) - connection.connect - end - - it "sets the cert on the context" do - expect(OpenSSL::X509::Certificate).to receive(:new).with(certificate).and_return(x509_certificate) - expect(ssl_context).to receive(:cert=).with(x509_certificate) - connection.connect - end - end - - describe "when connecting the socket" do - it "creates a TCP socket using the configured host and port" do - expect(TCPSocket).to receive(:new).with(host, port).and_return(tcp_socket) - connection.connect - end - - it "creates a new SSL socket using the TCP socket and SSL context" do - expect(OpenSSL::SSL::SSLSocket).to receive(:new).with(tcp_socket, ssl_context).and_return(ssl_socket) - connection.connect - end - - it "sets the sync option on the SSL socket" do - expect(ssl_socket).to receive(:sync=).with(true) - connection.connect - end - - it "connects the SSL socket" do - expect(ssl_socket).to receive(:connect) - connection.connect - end - - it "sets the socket option TCP_NODELAY" do - expect(tcp_socket).to receive(:setsockopt).with(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, true) - connection.connect - end - - it "sets the socket option SO_KEEPALIVE" do - expect(tcp_socket).to receive(:setsockopt).with(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, true) - connection.connect - end - - describe 'certificate expiry' do - it 'reflects if the certificate will expire soon' do - cert = x509_certificate - expect(connection).to receive(:reflect).with(:ssl_certificate_will_expire, app, cert.not_after) - Timecop.freeze(cert.not_after - 3.days) { connection.connect } - end - - it 'logs that the certificate will expire soon' do - cert = x509_certificate - expect(logger).to receive(:warn).with("[#{app.name}] Certificate will expire at #{cert.not_after.utc}.") - Timecop.freeze(cert.not_after - 3.days) { connection.connect } - end - - it 'does not reflect if the certificate will not expire soon' do - cert = x509_certificate - expect(connection).not_to receive(:reflect).with(:ssl_certificate_will_expire, app, kind_of(Time)) - Timecop.freeze(cert.not_after - 2.months) { connection.connect } - end - - it 'logs that the certificate has expired' do - cert = x509_certificate - expect(logger).to receive(:error).with("[#{app.name}] Certificate expired at #{cert.not_after.utc}.") - Timecop.freeze(cert.not_after + 1.day) { connection.connect rescue Rpush::CertificateExpiredError } - end - - it 'raises an error if the certificate has expired' do - cert = x509_certificate - Timecop.freeze(cert.not_after + 1.day) do - expect { connection.connect }.to raise_error(Rpush::CertificateExpiredError) - end - end - end - - describe 'certificate revocation' do - let(:cert_revoked_error) { OpenSSL::SSL::SSLError.new('certificate revoked') } - before do - allow(ssl_socket).to receive(:connect).and_raise(cert_revoked_error) - end - - it 'reflects that the certificate has been revoked' do - expect(connection).to receive(:reflect).with(:ssl_certificate_revoked, app, cert_revoked_error) - expect { connection.connect }.to raise_error(Rpush::Daemon::TcpConnectionError, 'OpenSSL::SSL::SSLError, certificate revoked') - end - - it 'logs that the certificate has been revoked' do - expect(logger).to receive(:error).with('[Connection 0] Certificate has been revoked.') - expect { connection.connect }.to raise_error(Rpush::Daemon::TcpConnectionError, 'OpenSSL::SSL::SSLError, certificate revoked') - end - end - end - - describe "when shuting down the connection" do - it "closes the TCP socket" do - connection.connect - expect(tcp_socket).to receive(:close) - connection.close - end - - it "does not attempt to close the TCP socket if it is not connected" do - connection.connect - expect(tcp_socket).not_to receive(:close) - connection.instance_variable_set("@tcp_socket", nil) - connection.close - end - - it "closes the SSL socket" do - connection.connect - expect(ssl_socket).to receive(:close) - connection.close - end - - it "does not attempt to close the SSL socket if it is not connected" do - connection.connect - expect(ssl_socket).not_to receive(:close) - connection.instance_variable_set("@ssl_socket", nil) - connection.close - end - - it "ignores IOError when the socket is already closed" do - allow(tcp_socket).to receive(:close).and_raise(IOError) - connection.connect - connection.close - end - end - - shared_examples_for "when the write fails" do - before do - allow(connection).to receive(:sleep) - connection.connect - allow(ssl_socket).to receive(:write).and_raise(error) - end - - it 'reflects the connection has been lost' do - expect(connection).to receive(:reflect).with(:tcp_connection_lost, app, kind_of(error.class)) - expect { connection.write(nil) }.to raise_error(Rpush::Daemon::TcpConnectionError) - end - - it "logs that the connection has been lost once only" do - expect(logger).to receive(:error).with("[Connection 0] Lost connection to gateway.push.apple.com:2195 (#{error.class.name}, #{error.message}), reconnecting...").once - expect { connection.write(nil) }.to raise_error(Rpush::Daemon::TcpConnectionError) - end - - it "retries to make a connection 3 times" do - expect(connection).to receive(:reconnect).exactly(3).times - expect { connection.write(nil) }.to raise_error(Rpush::Daemon::TcpConnectionError) - end - - it "raises a TcpConnectionError after 3 attempts at reconnecting" do - expect do - connection.write(nil) - end.to raise_error(Rpush::Daemon::TcpConnectionError, "Connection 0 tried 3 times to reconnect but failed (#{error.class.name}, #{error.message}).") - end - - it "sleeps 1 second before retrying the connection" do - expect(connection).to receive(:sleep).with(1) - expect { connection.write(nil) }.to raise_error(Rpush::Daemon::TcpConnectionError) - end - end - - describe "when write raises an Errno::EPIPE" do - it_should_behave_like "when the write fails" - - def error - Errno::EPIPE.new('an message') - end - end - - describe "when write raises an Errno::ETIMEDOUT" do - it_should_behave_like "when the write fails" - - def error - Errno::ETIMEDOUT.new('an message') - end - end - - describe "when write raises an OpenSSL::SSL::SSLError" do - it_should_behave_like "when the write fails" - - def error - OpenSSL::SSL::SSLError.new('an message') - end - end - - describe "when write raises an IOError" do - it_should_behave_like "when the write fails" - - def error - IOError.new('an message') - end - end - - describe "when reconnecting" do - before { connection.connect } - - it 'closes the socket' do - expect(connection).to receive(:close) - connection.send(:reconnect) - end - - it 'connects the socket' do - expect(connection).to receive(:connect_socket) - connection.send(:reconnect) - end - end - - describe "when sending a notification" do - before { connection.connect } - - it "writes the data to the SSL socket" do - expect(ssl_socket).to receive(:write).with("blah") - connection.write("blah") - end - - it "flushes the SSL socket" do - expect(ssl_socket).to receive(:flush) - connection.write("blah") - end - end - - describe 'idle period' do - before { connection.connect } - - it 'reconnects if the connection has been idle for more than the defined period' do - allow(Rpush::Daemon::TcpConnection).to receive_messages(idle_period: 60) - allow(Time).to receive_messages(now: Time.now + 61) - expect(connection).to receive(:reconnect) - connection.write('blah') - end - - it 'resets the last touch time' do - now = Time.now - allow(Time).to receive_messages(now: now) - connection.write('blah') - expect(connection.last_touch).to eq now - end - - it 'does not reconnect if the connection has not been idle for more than the defined period' do - expect(connection).not_to receive(:reconnect) - connection.write('blah') - end - - it 'logs the the connection is idle' do - allow(Rpush::Daemon::TcpConnection).to receive_messages(idle_period: 60) - allow(Time).to receive_messages(now: Time.now + 61) - expect(Rpush.logger).to receive(:info).with('[Connection 0] Idle period exceeded, reconnecting...') - connection.write('blah') - end - end -end From f009099aa4c5936466c9fc85c28dace7b7c1c15d Mon Sep 17 00:00:00 2001 From: Ben Langfeld Date: Fri, 6 Sep 2024 17:03:37 -0300 Subject: [PATCH 09/32] Another changelog typo (#687) --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d43640c63..6d2311671 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ [Full Changelog](https://github.com/rpush/rpush/compare/v8.0.0...HEAD) -## [v7.0.1](https://github.com/rpush/rpush/tree/v8.0.0) (2024-09-06) +## [v8.0.0](https://github.com/rpush/rpush/tree/v8.0.0) (2024-09-06) **Merged pull requests:** From 4eda5c7a57fe116f0a10561fca6b90f61b2ccf84 Mon Sep 17 00:00:00 2001 From: Ben Langfeld Date: Fri, 6 Sep 2024 17:36:14 -0300 Subject: [PATCH 10/32] Remove GCM implementation (#688) This was shut down by Google in August 2024 and replaced by FCM (supported in RPush 8.0.0). --- CHANGELOG.md | 1 + README.md | 37 +- lib/generators/templates/rpush.rb | 18 +- lib/rpush/client/active_model.rb | 4 - lib/rpush/client/active_model/gcm/app.rb | 19 - ...collapse_key_mutual_inclusion_validator.rb | 14 - .../client/active_model/gcm/notification.rb | 62 --- lib/rpush/client/active_record.rb | 3 - lib/rpush/client/active_record/gcm/app.rb | 11 - .../client/active_record/gcm/notification.rb | 11 - lib/rpush/client/redis.rb | 3 - lib/rpush/client/redis/gcm/app.rb | 11 - lib/rpush/client/redis/gcm/notification.rb | 11 - lib/rpush/configuration.rb | 2 +- lib/rpush/daemon.rb | 3 - lib/rpush/daemon/gcm.rb | 9 - lib/rpush/daemon/gcm/delivery.rb | 241 ----------- lib/rpush/daemon/store/active_record.rb | 9 +- lib/rpush/daemon/store/interface.rb | 2 +- lib/rpush/daemon/store/redis.rb | 9 +- lib/rpush/reflection_collection.rb | 1 - spec/functional/gcm_priority_spec.rb | 40 -- spec/functional/gcm_spec.rb | 46 --- spec/functional/retry_spec.rb | 25 +- spec/functional/synchronization_spec.rb | 2 +- .../unit/client/active_record/gcm/app_spec.rb | 6 - .../active_record/gcm/notification_spec.rb | 14 - spec/unit/client/active_record/shared/app.rb | 2 +- spec/unit/client/redis/gcm/app_spec.rb | 5 - .../client/redis/gcm/notification_spec.rb | 5 - spec/unit/client/shared/gcm/app.rb | 4 - spec/unit/client/shared/gcm/notification.rb | 77 ---- spec/unit/daemon/gcm/delivery_spec.rb | 387 ------------------ spec/unit/daemon/shared/store.rb | 33 -- 34 files changed, 41 insertions(+), 1086 deletions(-) delete mode 100644 lib/rpush/client/active_model/gcm/app.rb delete mode 100644 lib/rpush/client/active_model/gcm/expiry_collapse_key_mutual_inclusion_validator.rb delete mode 100644 lib/rpush/client/active_model/gcm/notification.rb delete mode 100644 lib/rpush/client/active_record/gcm/app.rb delete mode 100644 lib/rpush/client/active_record/gcm/notification.rb delete mode 100644 lib/rpush/client/redis/gcm/app.rb delete mode 100644 lib/rpush/client/redis/gcm/notification.rb delete mode 100644 lib/rpush/daemon/gcm.rb delete mode 100644 lib/rpush/daemon/gcm/delivery.rb delete mode 100644 spec/functional/gcm_priority_spec.rb delete mode 100644 spec/functional/gcm_spec.rb delete mode 100644 spec/unit/client/active_record/gcm/app_spec.rb delete mode 100644 spec/unit/client/active_record/gcm/notification_spec.rb delete mode 100644 spec/unit/client/redis/gcm/app_spec.rb delete mode 100644 spec/unit/client/redis/gcm/notification_spec.rb delete mode 100644 spec/unit/client/shared/gcm/app.rb delete mode 100644 spec/unit/client/shared/gcm/notification.rb delete mode 100644 spec/unit/daemon/gcm/delivery_spec.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d2311671..782b616d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ **Breaking:** * Removed legacy APNSv1 implementation (Apple binary protocol) since this was shut down in 2021. [\#680](https://github.com/rpush/rpush/pull/680) ([benlangfeld](https://github.com/benlangfeld)) +* Removed legacy GCM implementation since this was shut down by Google in August 2024 and replaced by FCM (supported in RPush 8.0.0) [\#688](https://github.com/rpush/rpush/pull/688) ([benlangfeld](https://github.com/benlangfeld)) * Drop support for Ruby 2.x [\#672](https://github.com/rpush/rpush/pull/672) ([benlangfeld](https://github.com/benlangfeld)) [Full Changelog](https://github.com/rpush/rpush/compare/v8.0.0...HEAD) diff --git a/README.md b/README.md index 72a397260..197aa8bd5 100644 --- a/README.md +++ b/README.md @@ -82,7 +82,7 @@ n = Rpush::Apnsp8::Notification.new n.app = Rpush::Apnsp8::App.find_by_name("ios_app") n.device_token = "..." # hex string n.alert = "hi mom!" -# n.alert = { title: "push title", subtitle: "more to say", body: "hi mom!" } +# n.alert = { title: "push title", subtitle: "more to say", body: "hi mom!" } n.data = { foo: :bar } n.save! ``` @@ -107,7 +107,7 @@ n = Rpush::Apns2::Notification.new n.app = Rpush::Apns2::App.find_by_name("ios_app") n.device_token = "..." # hex string n.alert = "hi mom!" -# n.alert = { title: "push title", subtitle: "more to say", body: "hi mom!" } +# n.alert = { title: "push title", subtitle: "more to say", body: "hi mom!" } n.data = { headers: { 'apns-topic': "BUNDLE ID" }, # the bundle id of the app, like com.example.appname. Not necessary if set on the app (see above) foo: :bar @@ -127,8 +127,6 @@ The app `environment` for any Apns* option is "development" for XCode installs, #### Firebase Cloud Messaging -##### Firebase Cloud Messaging API (V1) - You will need two params to make use of FCM via Rpush. - `firebase_project_id` - The `Project ID` in your Firebase Project Settings - `json_key` - The JSON key file for a service account with the `Firebase Admin SDK Administrator Service Agent` role. @@ -157,37 +155,6 @@ n.data = {}.transform_values(&:to_s) # All values going in here have to be strin n.save! ``` -##### Cloud Messaging API (Legacy) - -**Note:** Deprecated on 2023/6/20 and scheduled to be disabled on 2024/6/20. - -FCM and GCM are – as of writing – compatible with each other. See also [this comment](https://github.com/rpush/rpush/issues/284#issuecomment-228330206) for further references. - -Please refer to the Firebase Console on where to find your `auth_key` (probably called _Server Key_ there). To verify you have the right key, use tools like [Postman](https://www.getpostman.com/), [HTTPie](https://httpie.org/), `curl` or similar before reporting a new issue. See also [this comment](https://github.com/rpush/rpush/issues/346#issuecomment-289218776). - -```ruby -app = Rpush::Gcm::App.new -app.name = "android_app" -app.auth_key = "..." -app.connections = 1 -app.save! -``` - -```ruby -n = Rpush::Gcm::Notification.new -n.app = Rpush::Gcm::App.find_by_name("android_app") -n.registration_ids = ["..."] -n.data = { message: "hi mom!" } -n.priority = 'high' # Optional, can be either 'normal' or 'high' -n.content_available = true # Optional -# Optional notification payload. See the reference below for more keys you can use! -n.notification = { body: 'great match!', - title: 'Portugal vs. Denmark', - icon: 'myicon' - } -n.save! -``` - FCM also requires you to respond to [Canonical IDs](https://github.com/rpush/rpush/wiki/Canonical-IDs). Check the [FCM reference](https://firebase.google.com/docs/cloud-messaging/http-server-ref#notification-payload-support) for what keys you can use and are available to you. **Note:** Not all are yet implemented in Rpush. diff --git a/lib/generators/templates/rpush.rb b/lib/generators/templates/rpush.rb index e8679355e..0fd2464a3 100644 --- a/lib/generators/templates/rpush.rb +++ b/lib/generators/templates/rpush.rb @@ -81,7 +81,7 @@ # Called for each recipient which fails to receive a notification. This # can occur more than once for the same notification when there are multiple - # recipients. (do not handle invalid registration IDs here) + # recipients. (do not handle invalid device tokens here) # on.fcm_failed_to_recipient do |notification, error| # end @@ -93,23 +93,23 @@ # Called for each recipient which successfully receives a notification. This # can occur more than once for the same notification when there are multiple # recipients. - # on.gcm_delivered_to_recipient do |notification, registration_id| + # on.fcm_delivered_to_recipient do |notification, device_token| # end # Called for each recipient which fails to receive a notification. This # can occur more than once for the same notification when there are multiple - # recipients. (do not handle invalid registration IDs here) - # on.gcm_failed_to_recipient do |notification, error, registration_id| + # recipients. (do not handle invalid device tokens here) + # on.fcm_failed_to_recipient do |notification, error, device_token| # end - # Called when the GCM returns a canonical registration ID. + # Called when the FCM returns a canonical device token. # You will need to replace old_id with canonical_id in your records. - # on.gcm_canonical_id do |old_id, canonical_id| + # on.fcm_canonical_id do |old_id, canonical_id| # end - # Called when the GCM returns a failure that indicates an invalid registration id. - # You will need to delete the registration_id from your records. - # on.gcm_invalid_registration_id do |app, error, registration_id| + # Called when the FCM returns a failure that indicates an invalid device token. + # You will need to delete the device_token from your records. + # on.fcm_invalid_device_token do |app, error, device_token| # end # Called when an SSL certificate will expire within 1 month. diff --git a/lib/rpush/client/active_model.rb b/lib/rpush/client/active_model.rb index 7b602ce01..6d41430a7 100644 --- a/lib/rpush/client/active_model.rb +++ b/lib/rpush/client/active_model.rb @@ -25,10 +25,6 @@ require 'rpush/client/active_model/fcm/app' require 'rpush/client/active_model/fcm/notification' -require 'rpush/client/active_model/gcm/expiry_collapse_key_mutual_inclusion_validator' -require 'rpush/client/active_model/gcm/app' -require 'rpush/client/active_model/gcm/notification' - require 'rpush/client/active_model/wpns/app' require 'rpush/client/active_model/wpns/notification' diff --git a/lib/rpush/client/active_model/gcm/app.rb b/lib/rpush/client/active_model/gcm/app.rb deleted file mode 100644 index 3a3929eaa..000000000 --- a/lib/rpush/client/active_model/gcm/app.rb +++ /dev/null @@ -1,19 +0,0 @@ -module Rpush - module Client - module ActiveModel - module Gcm - module App - def self.included(base) - base.instance_eval do - validates :auth_key, presence: true - end - end - - def service_name - 'gcm' - end - end - end - end - end -end diff --git a/lib/rpush/client/active_model/gcm/expiry_collapse_key_mutual_inclusion_validator.rb b/lib/rpush/client/active_model/gcm/expiry_collapse_key_mutual_inclusion_validator.rb deleted file mode 100644 index c6f1b3b9d..000000000 --- a/lib/rpush/client/active_model/gcm/expiry_collapse_key_mutual_inclusion_validator.rb +++ /dev/null @@ -1,14 +0,0 @@ -module Rpush - module Client - module ActiveModel - module Gcm - class ExpiryCollapseKeyMutualInclusionValidator < ::ActiveModel::Validator - def validate(record) - return unless record.collapse_key && !record.expiry - record.errors.add :expiry, 'must be set when using a collapse_key' - end - end - end - end - end -end diff --git a/lib/rpush/client/active_model/gcm/notification.rb b/lib/rpush/client/active_model/gcm/notification.rb deleted file mode 100644 index 94f47c9d6..000000000 --- a/lib/rpush/client/active_model/gcm/notification.rb +++ /dev/null @@ -1,62 +0,0 @@ -module Rpush - module Client - module ActiveModel - module Gcm - module Notification - GCM_PRIORITY_HIGH = Rpush::Client::ActiveModel::Apns::Notification::APNS_PRIORITY_IMMEDIATE - GCM_PRIORITY_NORMAL = Rpush::Client::ActiveModel::Apns::Notification::APNS_PRIORITY_CONSERVE_POWER - GCM_PRIORITIES = [GCM_PRIORITY_HIGH, GCM_PRIORITY_NORMAL] - - def self.included(base) - base.instance_eval do - validates :registration_ids, presence: true - validates :priority, inclusion: { in: GCM_PRIORITIES }, allow_nil: true - validates :dry_run, inclusion: { in: [true, false] } - - validates_with Rpush::Client::ActiveModel::PayloadDataSizeValidator, limit: 4096 - validates_with Rpush::Client::ActiveModel::RegistrationIdsCountValidator, limit: 1000 - - validates_with Rpush::Client::ActiveModel::Gcm::ExpiryCollapseKeyMutualInclusionValidator - end - end - - # This is a hack. The schema defines `priority` to be an integer, but GCM expects a string. - # But for users of rpush to have an API they might expect (setting priority to `high`, not 10) - # we do a little conversion here. - # I'm not happy about it, but this will have to do until I can take a further look. - def priority=(priority) - case priority - when 'high', GCM_PRIORITY_HIGH - super(GCM_PRIORITY_HIGH) - when 'normal', GCM_PRIORITY_NORMAL - super(GCM_PRIORITY_NORMAL) - else - errors.add(:priority, 'must be one of either "normal" or "high"') - end - end - - def as_json(options = nil) # rubocop:disable Metrics/PerceivedComplexity - json = { - 'registration_ids' => registration_ids, - 'delay_while_idle' => delay_while_idle, - 'data' => data - } - json['collapse_key'] = collapse_key if collapse_key - json['content_available'] = content_available if content_available - json['mutable_content'] = mutable_content if mutable_content - json['dry_run'] = dry_run if dry_run - json['notification'] = notification if notification - json['priority'] = priority_for_notification if priority - json['time_to_live'] = expiry if expiry - json - end - - def priority_for_notification - return 'high' if priority == GCM_PRIORITY_HIGH - 'normal' if priority == GCM_PRIORITY_NORMAL - end - end - end - end - end -end diff --git a/lib/rpush/client/active_record.rb b/lib/rpush/client/active_record.rb index 6e6678ad4..af8652826 100644 --- a/lib/rpush/client/active_record.rb +++ b/lib/rpush/client/active_record.rb @@ -19,9 +19,6 @@ require 'rpush/client/active_record/fcm/notification' require 'rpush/client/active_record/fcm/app' -require 'rpush/client/active_record/gcm/notification' -require 'rpush/client/active_record/gcm/app' - require 'rpush/client/active_record/wpns/notification' require 'rpush/client/active_record/wpns/app' diff --git a/lib/rpush/client/active_record/gcm/app.rb b/lib/rpush/client/active_record/gcm/app.rb deleted file mode 100644 index a3ac05a40..000000000 --- a/lib/rpush/client/active_record/gcm/app.rb +++ /dev/null @@ -1,11 +0,0 @@ -module Rpush - module Client - module ActiveRecord - module Gcm - class App < Rpush::Client::ActiveRecord::App - include Rpush::Client::ActiveModel::Gcm::App - end - end - end - end -end diff --git a/lib/rpush/client/active_record/gcm/notification.rb b/lib/rpush/client/active_record/gcm/notification.rb deleted file mode 100644 index 294dcf34e..000000000 --- a/lib/rpush/client/active_record/gcm/notification.rb +++ /dev/null @@ -1,11 +0,0 @@ -module Rpush - module Client - module ActiveRecord - module Gcm - class Notification < Rpush::Client::ActiveRecord::Notification - include Rpush::Client::ActiveModel::Gcm::Notification - end - end - end - end -end diff --git a/lib/rpush/client/redis.rb b/lib/rpush/client/redis.rb index f47b237be..f9070133c 100644 --- a/lib/rpush/client/redis.rb +++ b/lib/rpush/client/redis.rb @@ -30,9 +30,6 @@ require 'rpush/client/redis/fcm/app' require 'rpush/client/redis/fcm/notification' -require 'rpush/client/redis/gcm/app' -require 'rpush/client/redis/gcm/notification' - require 'rpush/client/redis/adm/app' require 'rpush/client/redis/adm/notification' diff --git a/lib/rpush/client/redis/gcm/app.rb b/lib/rpush/client/redis/gcm/app.rb deleted file mode 100644 index 2bd8b9fa3..000000000 --- a/lib/rpush/client/redis/gcm/app.rb +++ /dev/null @@ -1,11 +0,0 @@ -module Rpush - module Client - module Redis - module Gcm - class App < Rpush::Client::Redis::App - include Rpush::Client::ActiveModel::Gcm::App - end - end - end - end -end diff --git a/lib/rpush/client/redis/gcm/notification.rb b/lib/rpush/client/redis/gcm/notification.rb deleted file mode 100644 index cb7ce5a0c..000000000 --- a/lib/rpush/client/redis/gcm/notification.rb +++ /dev/null @@ -1,11 +0,0 @@ -module Rpush - module Client - module Redis - module Gcm - class Notification < Rpush::Client::Redis::Notification - include Rpush::Client::ActiveModel::Gcm::Notification - end - end - end - end -end diff --git a/lib/rpush/configuration.rb b/lib/rpush/configuration.rb index 6eb6e9775..4e72133dc 100644 --- a/lib/rpush/configuration.rb +++ b/lib/rpush/configuration.rb @@ -89,7 +89,7 @@ def initialize_client client_module = Rpush::Client.const_get(client.to_s.camelize) Rpush.send(:include, client_module) unless Rpush.ancestors.include?(client_module) - [:Apns, :Fcm, :Gcm, :Wpns, :Wns, :Adm, :Pushy, :Webpush].each do |service| + [:Apns, :Fcm, :Wpns, :Wns, :Adm, :Pushy, :Webpush].each do |service| Rpush.const_set(service, client_module.const_get(service)) unless Rpush.const_defined?(service) end diff --git a/lib/rpush/daemon.rb b/lib/rpush/daemon.rb index c00fa3068..0a5279001 100644 --- a/lib/rpush/daemon.rb +++ b/lib/rpush/daemon.rb @@ -46,9 +46,6 @@ require 'rpush/daemon/fcm' require 'rpush/daemon/google_credential_cache' -require 'rpush/daemon/gcm/delivery' -require 'rpush/daemon/gcm' - require 'rpush/daemon/wpns/delivery' require 'rpush/daemon/wpns' diff --git a/lib/rpush/daemon/gcm.rb b/lib/rpush/daemon/gcm.rb deleted file mode 100644 index 8b1620c38..000000000 --- a/lib/rpush/daemon/gcm.rb +++ /dev/null @@ -1,9 +0,0 @@ -module Rpush - module Daemon - module Gcm - extend ServiceConfigMethods - - dispatcher :http - end - end -end diff --git a/lib/rpush/daemon/gcm/delivery.rb b/lib/rpush/daemon/gcm/delivery.rb deleted file mode 100644 index 56c660b9b..000000000 --- a/lib/rpush/daemon/gcm/delivery.rb +++ /dev/null @@ -1,241 +0,0 @@ -module Rpush - module Daemon - module Gcm - # https://firebase.google.com/docs/cloud-messaging/server - class Delivery < Rpush::Daemon::Delivery - include MultiJsonHelper - - host = 'https://fcm.googleapis.com' - FCM_URI = URI.parse("#{host}/fcm/send") - UNAVAILABLE_STATES = %w(Unavailable BadGateway InternalServerError) - INVALID_REGISTRATION_ID_STATES = %w(InvalidRegistration MismatchSenderId NotRegistered InvalidPackageName) - - def initialize(app, http, notification, batch) - @app = app - @http = http - @notification = notification - @batch = batch - end - - def perform - handle_response(do_post) - rescue SocketError => error - mark_retryable(@notification, Time.now + 10.seconds, error) - raise - rescue StandardError => error - mark_failed(error) - raise - ensure - @batch.notification_processed - end - - protected - - def handle_response(response) - case response.code.to_i - when 200 - ok(response) - when 400 - bad_request - when 401 - unauthorized - when 500 - internal_server_error(response) - when 502 - bad_gateway(response) - when 503 - service_unavailable(response) - when 500..599 - other_5xx_error(response) - else - fail Rpush::DeliveryError.new(response.code.to_i, @notification.id, Rpush::Daemon::HTTP_STATUS_CODES[response.code.to_i]) - end - end - - def ok(response) - results = process_response(response) - handle_successes(results.successes) - - if results.failures.any? - handle_failures(results.failures, response) - else - mark_delivered - log_info("#{@notification.id} sent to #{@notification.registration_ids.join(', ')}") - end - end - - def process_response(response) - body = multi_json_load(response.body) - results = Results.new(body['results'], @notification.registration_ids) - results.process(invalid: INVALID_REGISTRATION_ID_STATES, unavailable: UNAVAILABLE_STATES) - results - end - - def handle_successes(successes) - successes.each do |result| - reflect(:gcm_delivered_to_recipient, @notification, result[:registration_id]) - next unless result.key?(:canonical_id) - reflect(:gcm_canonical_id, result[:registration_id], result[:canonical_id]) - end - end - - def handle_failures(failures, response) - if failures[:unavailable].count == @notification.registration_ids.count - retry_delivery(@notification, response) - log_warn("All recipients unavailable. #{retry_message}") - else - if failures[:unavailable].any? - unavailable_idxs = failures[:unavailable].map { |result| result[:index] } - new_notification = create_new_notification(response, unavailable_idxs) - failures.description += " #{unavailable_idxs.join(', ')} will be retried as notification #{new_notification.id}." - end - handle_errors(failures) - fail Rpush::DeliveryError.new(nil, @notification.id, failures.description) - end - end - - def handle_errors(failures) - failures.each do |result| - reflect(:gcm_failed_to_recipient, @notification, result[:error], result[:registration_id]) - end - failures[:invalid].each do |result| - reflect(:gcm_invalid_registration_id, @app, result[:error], result[:registration_id]) - end - end - - def create_new_notification(response, unavailable_idxs) - attrs = { 'app_id' => @notification.app_id, 'collapse_key' => @notification.collapse_key, 'delay_while_idle' => @notification.delay_while_idle } - registration_ids = @notification.registration_ids.values_at(*unavailable_idxs) - Rpush::Daemon.store.create_gcm_notification(attrs, @notification.data, - registration_ids, deliver_after_header(response), @app) - end - - def bad_request - fail Rpush::DeliveryError.new(400, @notification.id, 'GCM failed to parse the JSON request. Possibly an Rpush bug, please open an issue.') - end - - def unauthorized - fail Rpush::DeliveryError.new(401, @notification.id, 'Unauthorized, check your App auth_key.') - end - - def internal_server_error(response) - retry_delivery(@notification, response) - log_warn("GCM responded with an Internal Error. " + retry_message) - end - - def bad_gateway(response) - retry_delivery(@notification, response) - log_warn("GCM responded with a Bad Gateway Error. " + retry_message) - end - - def service_unavailable(response) - retry_delivery(@notification, response) - log_warn("GCM responded with an Service Unavailable Error. " + retry_message) - end - - def other_5xx_error(response) - retry_delivery(@notification, response) - log_warn("GCM responded with a 5xx Error. " + retry_message) - end - - def deliver_after_header(response) - Rpush::Daemon::RetryHeaderParser.parse(response.header['retry-after']) - end - - def retry_delivery(notification, response) - time = deliver_after_header(response) - if time - mark_retryable(notification, time) - else - mark_retryable_exponential(notification) - end - end - - def retry_message - "Notification #{@notification.id} will be retried after #{@notification.deliver_after.strftime('%Y-%m-%d %H:%M:%S')} (retry #{@notification.retries})." - end - - def do_post - post = Net::HTTP::Post.new(FCM_URI.path, 'Content-Type' => 'application/json', - 'Authorization' => "key=#{@app.auth_key}") - post.body = @notification.as_json.to_json - @http.request(FCM_URI, post) - end - end - - class Results - attr_reader :successes, :failures - - def initialize(results_data, registration_ids) - @results_data = results_data - @registration_ids = registration_ids - end - - def process(failure_partitions = {}) # rubocop:disable Metrics/AbcSize - @successes = [] - @failures = Failures.new - failure_partitions.each_key do |category| - failures[category] = [] - end - - @results_data.each_with_index do |result, index| - entry = { - registration_id: @registration_ids[index], - index: index - } - if result['message_id'] - entry[:canonical_id] = result['registration_id'] if result['registration_id'].present? - successes << entry - elsif result['error'] - entry[:error] = result['error'] - failures << entry - failure_partitions.each do |category, error_states| - failures[category] << entry if error_states.include?(result['error']) - end - end - end - failures.all_failed = failures.count == @registration_ids.count - end - end - - class Failures < Hash - include Enumerable - attr_writer :all_failed, :description - - def initialize - super[:all] = [] - end - - def each - self[:all].each { |x| yield x } - end - - def <<(item) - self[:all] << item - end - - def description - @description ||= describe - end - - def any? - self[:all].any? - end - - private - - def describe - if @all_failed - error_description = "Failed to deliver to all recipients." - else - index_list = map { |item| item[:index] } - error_description = "Failed to deliver to recipients #{index_list.join(', ')}." - end - - error_list = map { |item| item[:error] } - error_description + " Errors: #{error_list.join(', ')}." - end - end - end - end -end \ No newline at end of file diff --git a/lib/rpush/daemon/store/active_record.rb b/lib/rpush/daemon/store/active_record.rb index 3189e78ec..f90c7b935 100644 --- a/lib/rpush/daemon/store/active_record.rb +++ b/lib/rpush/daemon/store/active_record.rb @@ -143,14 +143,9 @@ def create_fcm_notification(attrs, data, app) create_fcm_like_notification(notification, attrs, data, app) end - def create_gcm_notification(attrs, data, registration_ids, deliver_after, app) - notification = Rpush::Client::ActiveRecord::Gcm::Notification.new - create_gcm_like_notification(notification, attrs, data, registration_ids, deliver_after, app) - end - def create_adm_notification(attrs, data, registration_ids, deliver_after, app) notification = Rpush::Client::ActiveRecord::Adm::Notification.new - create_gcm_like_notification(notification, attrs, data, registration_ids, deliver_after, app) + create_adm_like_notification(notification, attrs, data, registration_ids, deliver_after, app) end def update_app(app) @@ -202,7 +197,7 @@ def create_fcm_like_notification(notification, attrs, data, app) # rubocop:disab end end - def create_gcm_like_notification(notification, attrs, data, registration_ids, deliver_after, app) # rubocop:disable Metrics/ParameterLists + def create_adm_like_notification(notification, attrs, data, registration_ids, deliver_after, app) # rubocop:disable Metrics/ParameterLists with_database_reconnect_and_retry do notification.assign_attributes(attrs) notification.data = data diff --git a/lib/rpush/daemon/store/interface.rb b/lib/rpush/daemon/store/interface.rb index f4d420944..94c9629dd 100644 --- a/lib/rpush/daemon/store/interface.rb +++ b/lib/rpush/daemon/store/interface.rb @@ -5,7 +5,7 @@ class Interface PUBLIC_METHODS = [:deliverable_notifications, :mark_retryable, :mark_batch_retryable, :mark_delivered, :mark_batch_delivered, :mark_failed, :mark_batch_failed, - :create_fcm_notification, :create_gcm_notification, :create_adm_notification, + :create_fcm_notification, :create_adm_notification, :update_app, :update_notification, :release_connection, :all_apps, :app, :mark_ids_failed, :mark_ids_retryable, :reopen_log, :pending_delivery_count, :translate_integer_notification_id] diff --git a/lib/rpush/daemon/store/redis.rb b/lib/rpush/daemon/store/redis.rb index f1adc4c13..f40a9f1e4 100644 --- a/lib/rpush/daemon/store/redis.rb +++ b/lib/rpush/daemon/store/redis.rb @@ -93,14 +93,9 @@ def create_fcm_notification(attrs, data, app) create_fcm_like_notification(notification, attrs, data, app) end - def create_gcm_notification(attrs, data, registration_ids, deliver_after, app) - notification = Rpush::Client::Redis::Gcm::Notification.new - create_gcm_like_notification(notification, attrs, data, registration_ids, deliver_after, app) - end - def create_adm_notification(attrs, data, registration_ids, deliver_after, app) notification = Rpush::Client::Redis::Adm::Notification.new - create_gcm_like_notification(notification, attrs, data, registration_ids, deliver_after, app) + create_adm_like_notification(notification, attrs, data, registration_ids, deliver_after, app) end def update_app(app) @@ -147,7 +142,7 @@ def create_fcm_like_notification(notification, attrs, data, app) # rubocop:disab notification end - def create_gcm_like_notification(notification, attrs, data, registration_ids, deliver_after, app) # rubocop:disable Metrics/ParameterLists + def create_adm_like_notification(notification, attrs, data, registration_ids, deliver_after, app) # rubocop:disable Metrics/ParameterLists notification.assign_attributes(attrs) notification.data = data notification.registration_ids = registration_ids diff --git a/lib/rpush/reflection_collection.rb b/lib/rpush/reflection_collection.rb index e522f5be9..c87c8c851 100644 --- a/lib/rpush/reflection_collection.rb +++ b/lib/rpush/reflection_collection.rb @@ -5,7 +5,6 @@ class NoSuchReflectionError < StandardError; end REFLECTIONS = [ :apns_feedback, :notification_enqueued, :notification_delivered, :notification_failed, :notification_will_retry, - :gcm_delivered_to_recipient, :gcm_failed_to_recipient, :gcm_canonical_id, :gcm_invalid_registration_id, :fcm_delivered_to_recipient, :fcm_failed_to_recipient, :fcm_canonical_id, :fcm_invalid_device_token, :error, :adm_canonical_id, :adm_failed_to_recipient, :wns_invalid_channel, :ssl_certificate_will_expire, :ssl_certificate_revoked, diff --git a/spec/functional/gcm_priority_spec.rb b/spec/functional/gcm_priority_spec.rb deleted file mode 100644 index 33992d533..000000000 --- a/spec/functional/gcm_priority_spec.rb +++ /dev/null @@ -1,40 +0,0 @@ -require 'functional_spec_helper' - -describe 'GCM priority' do - let(:app) { Rpush::Gcm::App.new } - let(:notification) { Rpush::Gcm::Notification.new } - let(:hydrated_notification) { Rpush::Gcm::Notification.find(notification.id) } - let(:response) { double(Net::HTTPResponse, code: 200) } - let(:http) { double(Net::HTTP::Persistent, request: response, shutdown: nil) } - let(:priority) { 'normal' } - - before do - app.name = 'test' - app.auth_key = 'abc123' - app.save! - - notification.app_id = app.id - notification.registration_ids = ['foo'] - notification.data = { message: 'test' } - notification.priority = priority - notification.save! - - allow(Net::HTTP::Persistent).to receive_messages(new: http) - end - - it 'supports normal priority' do - expect(hydrated_notification.as_json['priority']).to eq('normal') - end - - context 'high priority' do - let(:priority) { 'high' } - - it 'supports high priority' do - expect(hydrated_notification.as_json['priority']).to eq('high') - end - end - - it 'does not add an error when receiving expected priority' do - expect(hydrated_notification.errors.messages[:priority]).to be_empty - end -end diff --git a/spec/functional/gcm_spec.rb b/spec/functional/gcm_spec.rb deleted file mode 100644 index 7a50caaab..000000000 --- a/spec/functional/gcm_spec.rb +++ /dev/null @@ -1,46 +0,0 @@ -require 'functional_spec_helper' - -describe 'GCM' do - let(:app) { Rpush::Gcm::App.new } - let(:notification) { Rpush::Gcm::Notification.new } - let(:response) { double(Net::HTTPResponse, code: 200) } - let(:http) { double(Net::HTTP::Persistent, request: response, shutdown: nil) } - - before do - app.name = 'test' - app.auth_key = 'abc123' - app.save! - - notification.app_id = app.id - notification.registration_ids = ['foo'] - notification.data = { message: 'test' } - notification.save! - - allow(Net::HTTP::Persistent).to receive_messages(new: http) - end - - it 'delivers a notification successfully' do - allow(response).to receive_messages(body: JSON.dump(results: [{ message_id: notification.registration_ids.first.to_s }])) - - expect do - Rpush.push - notification.reload - end.to change(notification, :delivered).to(true) - end - - it 'fails to deliver a notification successfully' do - allow(response).to receive_messages(body: JSON.dump(results: [{ error: 'Err' }])) - Rpush.push - notification.reload - expect(notification.delivered).to eq(false) - end - - it 'retries notification that fail due to a SocketError' do - expect(http).to receive(:request).and_raise(SocketError.new) - expect(notification.deliver_after).to be_nil - expect do - Rpush.push - notification.reload - end.to change(notification, :deliver_after).to(kind_of(Time)) - end -end diff --git a/spec/functional/retry_spec.rb b/spec/functional/retry_spec.rb index 6a1ad038e..d1eeac846 100644 --- a/spec/functional/retry_spec.rb +++ b/spec/functional/retry_spec.rb @@ -1,10 +1,12 @@ require 'functional_spec_helper' describe 'Retries' do - let(:app) { Rpush::Gcm::App.new } - let(:notification) { Rpush::Gcm::Notification.new } + let(:app) { Rpush::Fcm::App.new } + let(:notification) { Rpush::Fcm::Notification.new } let(:response) { double(Net::HTTPResponse, code: 200) } let(:http) { double(Net::HTTP::Persistent, request: response, shutdown: nil) } + let(:fake_device_token) { 'a' * 108 } + let(:creds) {double(Google::Auth::UserRefreshCredentials)} before do Rpush::Daemon.common_init @@ -14,7 +16,7 @@ app.save! notification.app_id = app.id - notification.registration_ids = ['foo'] + notification.device_token = 'foo' notification.data = { message: 'test' } notification.save! @@ -23,7 +25,22 @@ end allow(Net::HTTP::Persistent).to receive_messages(new: http) - allow(response).to receive_messages(body: JSON.dump(results: [{ message_id: notification.registration_ids.first.to_s }])) + allow(creds).to receive(:fetch_access_token).and_return({'access_token': 'face_access_token'}) + + allow(::Google::Auth::ServiceAccountCredentials).to receive(:fetch_access_token).and_return({access_token: 'bbbbbb'}) + allow(::Google::Auth::ServiceAccountCredentials).to receive(:make_creds).and_return(creds) + allow_any_instance_of(::Rpush::Daemon::Fcm::Delivery).to receive(:necessary_data_exists?).and_return(true) + + example_success_body = { + "multicast_id": 108, + "success": 1, + "failure": 0, + "canonical_ids": 0, + "results": [ + { "message_id": "1:08" } + ] + }.to_json + allow(response).to receive_messages(body: example_success_body) end it 'delivers a notification due to be retried' do diff --git a/spec/functional/synchronization_spec.rb b/spec/functional/synchronization_spec.rb index 193817e7f..c66904607 100644 --- a/spec/functional/synchronization_spec.rb +++ b/spec/functional/synchronization_spec.rb @@ -2,7 +2,7 @@ describe 'Synchronization' do let(:timeout) { 10 } - let(:app) { Rpush::Gcm::App.new } + let(:app) { Rpush::Fcm::App.new } def wait_for_num_dispatchers(num) Timeout.timeout(timeout) do diff --git a/spec/unit/client/active_record/gcm/app_spec.rb b/spec/unit/client/active_record/gcm/app_spec.rb deleted file mode 100644 index f7d2925b6..000000000 --- a/spec/unit/client/active_record/gcm/app_spec.rb +++ /dev/null @@ -1,6 +0,0 @@ -require 'unit_spec_helper' - -describe Rpush::Client::ActiveRecord::Gcm::App do - it_behaves_like 'Rpush::Client::Gcm::App' - it_behaves_like 'Rpush::Client::ActiveRecord::App' -end if active_record? diff --git a/spec/unit/client/active_record/gcm/notification_spec.rb b/spec/unit/client/active_record/gcm/notification_spec.rb deleted file mode 100644 index f83a45607..000000000 --- a/spec/unit/client/active_record/gcm/notification_spec.rb +++ /dev/null @@ -1,14 +0,0 @@ -require 'unit_spec_helper' - -describe Rpush::Client::ActiveRecord::Gcm::Notification do - it_behaves_like 'Rpush::Client::Gcm::Notification' - it_behaves_like 'Rpush::Client::ActiveRecord::Notification' - - subject(:notification) { described_class.new } - let(:app) { Rpush::Gcm::App.create!(name: 'test', auth_key: 'abc') } - - it 'accepts non-booleans as a truthy value' do - notification.dry_run = 'Not a boolean' - expect(notification.as_json['dry_run']).to eq true - end -end if active_record? diff --git a/spec/unit/client/active_record/shared/app.rb b/spec/unit/client/active_record/shared/app.rb index acc1b8502..21b7a3c7f 100644 --- a/spec/unit/client/active_record/shared/app.rb +++ b/spec/unit/client/active_record/shared/app.rb @@ -8,7 +8,7 @@ app = Rpush::Apns::App.new(name: 'test', environment: 'development', certificate: TEST_CERT) expect(app.valid?).to eq(true) - app = Rpush::Gcm::App.new(name: 'test', environment: 'production', auth_key: TEST_CERT) + app = Rpush::Fcm::App.new(name: 'test', environment: 'production', json_key: TEST_CERT) expect(app.valid?).to eq(true) end end diff --git a/spec/unit/client/redis/gcm/app_spec.rb b/spec/unit/client/redis/gcm/app_spec.rb deleted file mode 100644 index 935363e9c..000000000 --- a/spec/unit/client/redis/gcm/app_spec.rb +++ /dev/null @@ -1,5 +0,0 @@ -require 'unit_spec_helper' - -describe Rpush::Client::Redis::Gcm::App do - it_behaves_like 'Rpush::Client::Gcm::App' -end if redis? diff --git a/spec/unit/client/redis/gcm/notification_spec.rb b/spec/unit/client/redis/gcm/notification_spec.rb deleted file mode 100644 index f1c163bd7..000000000 --- a/spec/unit/client/redis/gcm/notification_spec.rb +++ /dev/null @@ -1,5 +0,0 @@ -require 'unit_spec_helper' - -describe Rpush::Client::Redis::Gcm::Notification do - it_behaves_like 'Rpush::Client::Gcm::Notification' -end if redis? diff --git a/spec/unit/client/shared/gcm/app.rb b/spec/unit/client/shared/gcm/app.rb deleted file mode 100644 index f391bb689..000000000 --- a/spec/unit/client/shared/gcm/app.rb +++ /dev/null @@ -1,4 +0,0 @@ -require 'unit_spec_helper' - -shared_examples 'Rpush::Client::Gcm::App' do -end diff --git a/spec/unit/client/shared/gcm/notification.rb b/spec/unit/client/shared/gcm/notification.rb deleted file mode 100644 index 207b2a5b8..000000000 --- a/spec/unit/client/shared/gcm/notification.rb +++ /dev/null @@ -1,77 +0,0 @@ -require 'unit_spec_helper' - -shared_examples 'Rpush::Client::Gcm::Notification' do - let(:app) { Rpush::Gcm::App.create!(name: 'test', auth_key: 'abc') } - let(:notification) { described_class.new } - - it "has a 'data' payload limit of 4096 bytes" do - notification.data = { key: "a" * 4096 } - expect(notification.valid?).to be_falsey - expect(notification.errors[:base]).to eq ["Notification payload data cannot be larger than 4096 bytes."] - end - - it 'limits the number of registration ids to 1000' do - notification.registration_ids = ['a'] * (1000 + 1) - expect(notification.valid?).to be_falsey - expect(notification.errors[:base]).to eq ["Number of registration_ids cannot be larger than 1000."] - end - - it 'validates expiry is present if collapse_key is set' do - notification.collapse_key = 'test' - notification.expiry = nil - expect(notification.valid?).to be_falsey - expect(notification.errors[:expiry]).to eq ['must be set when using a collapse_key'] - end - - it 'includes time_to_live in the payload' do - notification.expiry = 100 - expect(notification.as_json['time_to_live']).to eq 100 - end - - it 'includes content_available in the payload' do - notification.content_available = true - expect(notification.as_json['content_available']).to eq true - end - - it 'includes mutable_content in the payload' do - notification.mutable_content = true - expect(notification.as_json['mutable_content']).to eq true - end - - it 'sets the priority to high when set to high' do - notification.priority = 'high' - expect(notification.as_json['priority']).to eq 'high' - end - - it 'sets the priority to normal when set to normal' do - notification.priority = 'normal' - expect(notification.as_json['priority']).to eq 'normal' - end - - it 'validates the priority is either "normal" or "high"' do - notification.priority = 'invalid' - expect(notification.errors[:priority]).to eq ['must be one of either "normal" or "high"'] - end - - it 'excludes the priority if it is not defined' do - expect(notification.as_json).not_to have_key 'priority' - end - - it 'includes the notification payload if defined' do - notification.notification = { key: 'any key is allowed' } - expect(notification.as_json).to have_key 'notification' - end - - it 'excludes the notification payload if undefined' do - expect(notification.as_json).not_to have_key 'notification' - end - - it 'includes the dry_run payload if defined' do - notification.dry_run = true - expect(notification.as_json['dry_run']).to eq true - end - - it 'excludes the dry_run payload if undefined' do - expect(notification.as_json).not_to have_key 'dry_run' - end -end diff --git a/spec/unit/daemon/gcm/delivery_spec.rb b/spec/unit/daemon/gcm/delivery_spec.rb deleted file mode 100644 index bcefa8999..000000000 --- a/spec/unit/daemon/gcm/delivery_spec.rb +++ /dev/null @@ -1,387 +0,0 @@ -require 'unit_spec_helper' - -describe Rpush::Daemon::Gcm::Delivery do - let(:app) { Rpush::Gcm::App.create!(name: 'MyApp', auth_key: 'abc123') } - let(:notification) { Rpush::Gcm::Notification.create!(app: app, registration_ids: ['xyz'], deliver_after: Time.now) } - let(:logger) { double(error: nil, info: nil, warn: nil) } - let(:response) { double(code: 200, header: {}) } - let(:http) { double(shutdown: nil, request: response) } - let(:now) { Time.parse('2012-10-14 00:00:00') } - let(:batch) { double(mark_failed: nil, mark_delivered: nil, mark_retryable: nil, notification_processed: nil) } - let(:delivery) { Rpush::Daemon::Gcm::Delivery.new(app, http, notification, batch) } - let(:store) { double(create_gcm_notification: double(id: 2)) } - - def perform - delivery.perform - end - - def perform_with_rescue - expect { perform }.to raise_error(StandardError) - end - - before do - allow(delivery).to receive_messages(reflect: nil) - allow(Rpush::Daemon).to receive_messages(store: store) - allow(Time).to receive_messages(now: now) - allow(Rpush).to receive_messages(logger: logger) - end - - shared_examples_for 'a notification with some delivery failures' do - let(:new_notification) { Rpush::Gcm::Notification.where('id != ?', notification.id).first } - - before { allow(response).to receive_messages(body: JSON.dump(body)) } - - it 'marks the original notification as failed' do - # error = Rpush::DeliveryError.new(nil, notification.id, error_description) - expect(delivery).to receive(:mark_failed) do |error| - expect(error.to_s).to match(error_description) - end - perform_with_rescue - end - - it 'creates a new notification for the unavailable devices' do - notification.update(registration_ids: %w(id_0 id_1 id_2), data: { 'one' => 1 }, collapse_key: 'thing', delay_while_idle: true) - allow(response).to receive_messages(header: { 'retry-after' => 10 }) - attrs = { 'collapse_key' => 'thing', 'delay_while_idle' => true, 'app_id' => app.id } - expect(store).to receive(:create_gcm_notification).with(attrs, notification.data, - %w(id_0 id_2), now + 10.seconds, notification.app) - perform_with_rescue - end - - it 'raises a DeliveryError' do - expect { perform }.to raise_error(Rpush::DeliveryError) - end - end - - describe 'a 200 response' do - before do - allow(response).to receive_messages(code: 200) - end - - it 'reflects on any IDs which successfully received the notification' do - body = { - 'failure' => 1, - 'success' => 1, - 'results' => [ - { 'message_id' => '1:000' }, - { 'error' => 'Err' } - ] - } - - allow(response).to receive_messages(body: JSON.dump(body)) - allow(notification).to receive_messages(registration_ids: %w(1 2)) - expect(delivery).to receive(:reflect).with(:gcm_delivered_to_recipient, notification, '1') - expect(delivery).not_to receive(:reflect).with(:gcm_delivered_to_recipient, notification, '2') - perform_with_rescue - end - - it 'reflects on any IDs which failed to receive the notification' do - body = { - 'failure' => 1, - 'success' => 1, - 'results' => [ - { 'error' => 'Err' }, - { 'message_id' => '1:000' } - ] - } - - allow(response).to receive_messages(body: JSON.dump(body)) - allow(notification).to receive_messages(registration_ids: %w(1 2)) - expect(delivery).to receive(:reflect).with(:gcm_failed_to_recipient, notification, 'Err', '1') - expect(delivery).not_to receive(:reflect).with(:gcm_failed_to_recipient, notification, anything, '2') - perform_with_rescue - end - - it 'reflects on canonical IDs' do - body = { - 'failure' => 0, - 'success' => 3, - 'canonical_ids' => 1, - 'results' => [ - { 'message_id' => '1:000' }, - { 'message_id' => '1:000', 'registration_id' => 'canonical123' }, - { 'message_id' => '1:000' } - ] } - - allow(response).to receive_messages(body: JSON.dump(body)) - allow(notification).to receive_messages(registration_ids: %w(1 2 3)) - expect(delivery).to receive(:reflect).with(:gcm_canonical_id, '2', 'canonical123') - perform - end - - it 'reflects on invalid IDs' do - body = { - 'failure' => 1, - 'success' => 2, - 'canonical_ids' => 0, - 'results' => [ - { 'message_id' => '1:000' }, - { 'error' => 'NotRegistered' }, - { 'message_id' => '1:000' } - ] - } - - allow(response).to receive_messages(body: JSON.dump(body)) - allow(notification).to receive_messages(registration_ids: %w(1 2 3)) - expect(delivery).to receive(:reflect).with(:gcm_invalid_registration_id, app, 'NotRegistered', '2') - perform_with_rescue - end - - describe 'when delivered successfully to all devices' do - let(:body) do - { - 'failure' => 0, - 'success' => 1, - 'results' => [{ 'message_id' => '1:000' }] - } - end - - before { allow(response).to receive_messages(body: JSON.dump(body)) } - - it 'marks the notification as delivered' do - expect(delivery).to receive(:mark_delivered) - perform - end - - it 'logs that the notification was delivered' do - expect(logger).to receive(:info).with("[MyApp] #{notification.id} sent to xyz") - perform - end - end - - it 'marks a notification as failed if any ids are invalid' do - body = { - 'failure' => 1, - 'success' => 2, - 'canonical_ids' => 0, - 'results' => [ - { 'message_id' => '1:000' }, - { 'error' => 'NotRegistered' }, - { 'message_id' => '1:000' } - ] - } - - allow(response).to receive_messages(body: JSON.dump(body)) - expect(delivery).to receive(:mark_failed) - expect(delivery).not_to receive(:mark_retryable) - expect(store).not_to receive(:create_gcm_notification) - perform_with_rescue - end - - it 'marks a notification as failed if any deliveries failed that cannot be retried' do - body = { - 'failure' => 1, - 'success' => 1, - 'results' => [ - { 'message_id' => '1:000' }, - { 'error' => 'InvalidDataKey' } - ] } - allow(response).to receive_messages(body: JSON.dump(body)) - error = Rpush::DeliveryError.new(nil, notification.id, 'Failed to deliver to all recipients. Errors: InvalidDataKey.') - expect(delivery).to receive(:mark_failed).with(error) - perform_with_rescue - end - - describe 'all deliveries failed with Unavailable or InternalServerError' do - let(:body) do - { - 'failure' => 2, - 'success' => 0, - 'results' => [ - { 'error' => 'Unavailable' }, - { 'error' => 'Unavailable' } - ] - } - end - - before do - allow(response).to receive_messages(body: JSON.dump(body)) - allow(notification).to receive_messages(registration_ids: %w(1 2)) - end - - it 'retries the notification respecting the Retry-After header' do - allow(response).to receive_messages(header: { 'retry-after' => 10 }) - expect(delivery).to receive(:mark_retryable).with(notification, now + 10.seconds) - perform - end - - it 'retries the notification using exponential back-off if the Retry-After header is not present' do - expect(delivery).to receive(:mark_retryable).with(notification, now + 2) - perform - end - - it 'does not mark the notification as failed' do - expect(delivery).not_to receive(:mark_failed) - perform - end - - it 'logs that the notification will be retried' do - notification.retries = 1 - notification.deliver_after = now + 2 - expect(Rpush.logger).to receive(:warn).with("[MyApp] All recipients unavailable. Notification #{notification.id} will be retried after 2012-10-14 00:00:02 (retry 1).") - perform - end - end - - describe 'all deliveries failed with some as Unavailable or InternalServerError' do - let(:body) do - { 'failure' => 3, - 'success' => 0, - 'results' => [ - { 'error' => 'Unavailable' }, - { 'error' => 'InvalidDataKey' }, - { 'error' => 'Unavailable' } - ] - } - end - let(:error_description) { /#{Regexp.escape("Failed to deliver to recipients 0, 1, 2. Errors: Unavailable, InvalidDataKey, Unavailable. 0, 2 will be retried as notification")} [\d]+\./ } - it_should_behave_like 'a notification with some delivery failures' - end - - describe 'some deliveries failed with Unavailable or InternalServerError' do - let(:body) do - { 'failure' => 2, - 'success' => 1, - 'results' => [ - { 'error' => 'Unavailable' }, - { 'message_id' => '1:000' }, - { 'error' => 'InternalServerError' } - ] - } - end - let(:error_description) { /#{Regexp.escape("Failed to deliver to recipients 0, 2. Errors: Unavailable, InternalServerError. 0, 2 will be retried as notification")} [\d]+\./ } - it_should_behave_like 'a notification with some delivery failures' - end - end - - describe 'a 503 response' do - before { allow(response).to receive_messages(code: 503) } - - it 'logs a warning that the notification will be retried.' do - notification.retries = 1 - notification.deliver_after = now + 2 - expect(logger).to receive(:warn).with("[MyApp] GCM responded with an Service Unavailable Error. Notification #{notification.id} will be retried after 2012-10-14 00:00:02 (retry 1).") - perform - end - - it 'respects an integer Retry-After header' do - allow(response).to receive_messages(header: { 'retry-after' => 10 }) - expect(delivery).to receive(:mark_retryable).with(notification, now + 10.seconds) - perform - end - - it 'respects a HTTP-date Retry-After header' do - allow(response).to receive_messages(header: { 'retry-after' => 'Wed, 03 Oct 2012 20:55:11 GMT' }) - expect(delivery).to receive(:mark_retryable).with(notification, Time.parse('Wed, 03 Oct 2012 20:55:11 GMT')) - perform - end - - it 'defaults to exponential back-off if the Retry-After header is not present' do - expect(delivery).to receive(:mark_retryable).with(notification, now + 2**1) - perform - end - end - - describe 'a 502 response' do - before { allow(response).to receive_messages(code: 502) } - - it 'logs a warning that the notification will be retried.' do - notification.retries = 1 - notification.deliver_after = now + 2 - expect(logger).to receive(:warn).with("[MyApp] GCM responded with a Bad Gateway Error. Notification #{notification.id} will be retried after 2012-10-14 00:00:02 (retry 1).") - perform - end - - it 'respects an integer Retry-After header' do - allow(response).to receive_messages(header: { 'retry-after' => 10 }) - expect(delivery).to receive(:mark_retryable).with(notification, now + 10.seconds) - perform - end - - it 'respects a HTTP-date Retry-After header' do - allow(response).to receive_messages(header: { 'retry-after' => 'Wed, 03 Oct 2012 20:55:11 GMT' }) - expect(delivery).to receive(:mark_retryable).with(notification, Time.parse('Wed, 03 Oct 2012 20:55:11 GMT')) - perform - end - - it 'defaults to exponential back-off if the Retry-After header is not present' do - expect(delivery).to receive(:mark_retryable).with(notification, now + 2**1) - perform - end - end - - describe 'a 500 response' do - before do - notification.update_attribute(:retries, 2) - allow(response).to receive_messages(code: 500) - end - - it 'logs a warning that the notification has been re-queued.' do - notification.retries = 3 - notification.deliver_after = now + 2**3 - expect(Rpush.logger).to receive(:warn).with("[MyApp] GCM responded with an Internal Error. Notification #{notification.id} will be retried after #{(now + 2**3).strftime('%Y-%m-%d %H:%M:%S')} (retry 3).") - perform - end - - it 'retries the notification in accordance with the exponential back-off strategy.' do - expect(delivery).to receive(:mark_retryable).with(notification, now + 2**3) - perform - end - end - - describe 'a 5xx response' do - before { allow(response).to receive_messages(code: 555) } - - it 'logs a warning that the notification will be retried.' do - notification.retries = 1 - notification.deliver_after = now + 2 - expect(logger).to receive(:warn).with("[MyApp] GCM responded with a 5xx Error. Notification #{notification.id} will be retried after 2012-10-14 00:00:02 (retry 1).") - perform - end - - it 'respects an integer Retry-After header' do - allow(response).to receive_messages(header: { 'retry-after' => 10 }) - expect(delivery).to receive(:mark_retryable).with(notification, now + 10.seconds) - perform - end - - it 'respects a HTTP-date Retry-After header' do - allow(response).to receive_messages(header: { 'retry-after' => 'Wed, 03 Oct 2012 20:55:11 GMT' }) - expect(delivery).to receive(:mark_retryable).with(notification, Time.parse('Wed, 03 Oct 2012 20:55:11 GMT')) - perform - end - - it 'defaults to exponential back-off if the Retry-After header is not present' do - expect(delivery).to receive(:mark_retryable).with(notification, now + 2**1) - perform - end - end - - describe 'a 401 response' do - before { allow(response).to receive_messages(code: 401) } - - it 'raises an error' do - expect { perform }.to raise_error(Rpush::DeliveryError) - end - end - - describe 'a 400 response' do - before { allow(response).to receive_messages(code: 400) } - - it 'marks the notification as failed' do - error = Rpush::DeliveryError.new(400, notification.id, 'GCM failed to parse the JSON request. Possibly an Rpush bug, please open an issue.') - expect(delivery).to receive(:mark_failed).with(error) - perform_with_rescue - end - end - - describe 'an un-handled response' do - before { allow(response).to receive_messages(code: 418) } - - it 'marks the notification as failed' do - error = Rpush::DeliveryError.new(418, notification.id, "I'm a Teapot") - expect(delivery).to receive(:mark_failed).with(error) - perform_with_rescue - end - end -end diff --git a/spec/unit/daemon/shared/store.rb b/spec/unit/daemon/shared/store.rb index 89f7c8518..7a97e6eee 100644 --- a/spec/unit/daemon/shared/store.rb +++ b/spec/unit/daemon/shared/store.rb @@ -233,39 +233,6 @@ end end - describe 'create_gcm_notification' do - let(:data) { { 'data' => true } } - let(:attributes) { { device_token: 'ab' * 32 } } - let(:registration_ids) { %w[123 456] } - let(:deliver_after) { time + 10.seconds } - let(:args) { [attributes, data, registration_ids, deliver_after, app] } - - it 'sets the given attributes' do - new_notification = store.create_gcm_notification(*args) - expect(new_notification.device_token).to eq 'ab' * 32 - end - - it 'sets the given data' do - new_notification = store.create_gcm_notification(*args) - expect(new_notification.data['data']).to be_truthy - end - - it 'sets the given registration IDs' do - new_notification = store.create_gcm_notification(*args) - expect(new_notification.registration_ids).to eq registration_ids - end - - it 'sets the deliver_after timestamp' do - new_notification = store.create_gcm_notification(*args) - expect(new_notification.deliver_after).to eq deliver_after - end - - it 'saves the new notification' do - new_notification = store.create_gcm_notification(*args) - expect(new_notification.new_record?).to be_falsey - end - end - describe 'create_adm_notification' do let(:data) { { 'data' => true } } let(:attributes) { { app_id: app.id, collapse_key: 'ckey', delay_while_idle: true } } From 925d5b37e2cbee1bb70109037ebfb5c50624ce7e Mon Sep 17 00:00:00 2001 From: Ben Langfeld Date: Fri, 6 Sep 2024 17:42:10 -0300 Subject: [PATCH 11/32] Test on newer rubies (#679) --- .github/workflows/test.yml | 2 +- CHANGELOG.md | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 78db0ab35..182fb4fc4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -54,7 +54,7 @@ jobs: matrix: gemfile: ['rails_6.0', 'rails_6.1', 'rails_7.0'] - ruby: ['3.0', '3.1'] + ruby: ['3.0', '3.1', '3.2', '3.3'] client: ['active_record', 'redis'] diff --git a/CHANGELOG.md b/CHANGELOG.md index 782b616d8..b3f1a5487 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ **Merged pull requests:** +* Support for Ruby 3.2 & 3.3 [\#679](https://github.com/rpush/rpush/pull/679) ([benlangfeld](https://github.com/benlangfeld)) + **Breaking:** * Removed legacy APNSv1 implementation (Apple binary protocol) since this was shut down in 2021. [\#680](https://github.com/rpush/rpush/pull/680) ([benlangfeld](https://github.com/benlangfeld)) From 8b2ab0dad2fba1bed42d9b36a18c5067fa8ea00d Mon Sep 17 00:00:00 2001 From: Ben Langfeld Date: Fri, 6 Sep 2024 18:25:17 -0300 Subject: [PATCH 12/32] Run rubocop in CI (#686) --- .github/workflows/test.yml | 3 + .rubocop_todo.yml | 611 ++++++++++++++++++++++++------------- Gemfile.lock | 39 +-- rpush.gemspec | 2 +- 4 files changed, 425 insertions(+), 230 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 182fb4fc4..9ea8cc23f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -87,6 +87,9 @@ jobs: POSTGRES_PORT: 5432 CLIENT: ${{ matrix.client }} + - name: Run rubocop + run: bundle exec rubocop + tests: runs-on: ubuntu-latest needs: test diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index e55d8733a..2aec09736 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,13 +1,37 @@ # This configuration was generated by # `rubocop --auto-gen-config` -# on 2022-01-21 11:42:55 UTC using RuboCop version 1.12.1. +# on 2024-09-06 21:22:46 UTC using RuboCop version 1.66.1. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. # Offense count: 10 -# Cop supports --auto-correct. +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: Include. +# Include: **/*.gemspec +Gemspec/AddRuntimeDependency: + Exclude: + - 'rpush.gemspec' + +# Offense count: 1 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: Severity, Include. +# Include: **/*.gemspec +Gemspec/DeprecatedAttributeAssignment: + Exclude: + - 'rpush.gemspec' + +# Offense count: 17 +# Configuration parameters: EnforcedStyle, AllowedGems, Include. +# SupportedStyles: Gemfile, gems.rb, gemspec +# Include: **/*.gemspec, **/Gemfile, **/gems.rb +Gemspec/DevelopmentDependencies: + Exclude: + - 'rpush.gemspec' + +# Offense count: 11 +# This cop supports safe autocorrection (--autocorrect). # Configuration parameters: TreatCommentsAsGroupSeparators, ConsiderPunctuation, Include. # Include: **/*.gemspec Gemspec/OrderedDependencies: @@ -15,7 +39,7 @@ Gemspec/OrderedDependencies: - 'rpush.gemspec' # Offense count: 8 -# Cop supports --auto-correct. +# This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle, IndentationWidth. # SupportedStyles: with_first_argument, with_fixed_indentation Layout/ArgumentAlignment: @@ -24,40 +48,42 @@ Layout/ArgumentAlignment: - 'lib/rpush/daemon/apnsp8/delivery.rb' # Offense count: 2 -# Cop supports --auto-correct. +# This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle, IndentOneStep, IndentationWidth. # SupportedStyles: case, end Layout/CaseIndentation: Exclude: - - 'lib/rpush/client/active_model/gcm/notification.rb' + - 'lib/rpush/client/active_model/fcm/notification.rb' -# Offense count: 9 -# Cop supports --auto-correct. +# Offense count: 23 +# This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle. # SupportedStyles: leading, trailing Layout/DotPosition: Exclude: - 'spec/functional/apns2_spec.rb' + - 'spec/functional/cli_spec.rb' + - 'spec/functional/embed_spec.rb' -# Offense count: 40 -# Cop supports --auto-correct. +# Offense count: 33 +# This cop supports safe autocorrection (--autocorrect). Layout/EmptyLineAfterGuardClause: Enabled: false # Offense count: 1 -# Cop supports --auto-correct. +# This cop supports safe autocorrection (--autocorrect). Layout/EmptyLineAfterMagicComment: Exclude: - 'rpush.gemspec' # Offense count: 1 -# Cop supports --auto-correct. +# This cop supports safe autocorrection (--autocorrect). Layout/EmptyLines: Exclude: - 'spec/unit/client/shared/webpush/notification.rb' # Offense count: 2 -# Cop supports --auto-correct. +# This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle. # SupportedStyles: around, only_before Layout/EmptyLinesAroundAccessModifier: @@ -66,23 +92,24 @@ Layout/EmptyLinesAroundAccessModifier: - 'lib/rpush/daemon/apnsp8/delivery.rb' # Offense count: 1 -# Cop supports --auto-correct. +# This cop supports safe autocorrection (--autocorrect). # Configuration parameters: AllowAliasSyntax, AllowedMethods. # AllowedMethods: alias_method, public, protected, private Layout/EmptyLinesAroundAttributeAccessor: Exclude: - 'lib/rpush/daemon/app_runner.rb' -# Offense count: 1 -# Cop supports --auto-correct. +# Offense count: 2 +# This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle. # SupportedStyles: empty_lines, no_empty_lines Layout/EmptyLinesAroundBlockBody: Exclude: + - 'spec/unit/client/active_record/fcm/notification_spec.rb' - 'spec/unit/client/shared/webpush/notification.rb' # Offense count: 2 -# Cop supports --auto-correct. +# This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle. # SupportedStyles: empty_lines, empty_lines_except_namespace, empty_lines_special, no_empty_lines, beginning_only, ending_only Layout/EmptyLinesAroundClassBody: @@ -90,7 +117,7 @@ Layout/EmptyLinesAroundClassBody: - 'lib/rpush/daemon/webpush/delivery.rb' # Offense count: 5 -# Cop supports --auto-correct. +# This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle. # SupportedStyles: empty_lines, empty_lines_except_namespace, empty_lines_special, no_empty_lines Layout/EmptyLinesAroundModuleBody: @@ -100,14 +127,14 @@ Layout/EmptyLinesAroundModuleBody: - 'lib/rpush/daemon/webpush/delivery.rb' # Offense count: 1 -# Cop supports --auto-correct. +# This cop supports safe autocorrection (--autocorrect). # Configuration parameters: AllowForAlignment, AllowBeforeTrailingComments, ForceEqualSignAlignment. Layout/ExtraSpacing: Exclude: - 'spec/unit/daemon/store/active_record/reconnectable_spec.rb' # Offense count: 2 -# Cop supports --auto-correct. +# This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle, IndentationWidth. # SupportedStyles: special_inside_parentheses, consistent, align_brackets Layout/FirstArrayElementIndentation: @@ -115,16 +142,8 @@ Layout/FirstArrayElementIndentation: - 'lib/rpush/daemon/store/active_record/reconnectable.rb' - 'spec/unit/daemon/store/active_record/reconnectable_spec.rb' -# Offense count: 1 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, IndentationWidth. -# SupportedStyles: special_inside_parentheses, consistent, align_braces -Layout/FirstHashElementIndentation: - Exclude: - - 'lib/rpush/client/active_model/gcm/notification.rb' - -# Offense count: 84 -# Cop supports --auto-correct. +# Offense count: 92 +# This cop supports safe autocorrection (--autocorrect). # Configuration parameters: AllowMultipleStyles, EnforcedHashRocketStyle, EnforcedColonStyle, EnforcedLastArgumentHashStyle. # SupportedHashRocketStyles: key, separator, table # SupportedColonStyles: key, separator, table @@ -136,57 +155,70 @@ Layout/HashAlignment: - 'lib/rpush/daemon/apns2/delivery.rb' - 'lib/rpush/daemon/apnsp8/delivery.rb' - 'lib/rpush/daemon/constants.rb' - - 'lib/rpush/daemon/gcm/delivery.rb' + - 'lib/rpush/daemon/fcm/delivery.rb' - 'lib/rpush/daemon/service_config_methods.rb' - 'lib/rpush/daemon/wns/delivery.rb' - 'lib/rpush/daemon/wpns/delivery.rb' - 'spec/functional/apns2_spec.rb' + - 'spec/functional/cli_spec.rb' + - 'spec/functional/embed_spec.rb' # Offense count: 2 -# Cop supports --auto-correct. +# This cop supports safe autocorrection (--autocorrect). Layout/HeredocIndentation: Exclude: - 'lib/rpush/daemon.rb' - 'lib/tasks/test.rake' -# Offense count: 1 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle. -# SupportedStyles: normal, indented_internal_methods -Layout/IndentationConsistency: - Exclude: - - 'spec/functional/apns2_spec.rb' - # Offense count: 2 -# Cop supports --auto-correct. -# Configuration parameters: Width, IgnoredPatterns. +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: Width, AllowedPatterns. Layout/IndentationWidth: Exclude: - 'examples/rpush.god' # Offense count: 1 -# Cop supports --auto-correct. +# This cop supports safe autocorrection (--autocorrect). Layout/InitialIndentation: Exclude: - 'spec/unit/reflection_collection_spec.rb' # Offense count: 1 -# Cop supports --auto-correct. +# This cop supports safe autocorrection (--autocorrect). Layout/LeadingEmptyLines: Exclude: - 'lib/rpush/client/redis.rb' -# Offense count: 6 -# Cop supports --auto-correct. +# Offense count: 7 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyle. +# SupportedStyles: space, no_space +Layout/LineContinuationSpacing: + Exclude: + - 'lib/rpush/daemon/apns2/delivery.rb' + - 'lib/rpush/daemon/apnsp8/delivery.rb' + - 'spec/functional/apns2_spec.rb' + +# Offense count: 5 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyle, IndentationWidth. +# SupportedStyles: aligned, indented +Layout/LineEndStringConcatenationIndentation: + Exclude: + - 'lib/rpush/daemon/apns2/delivery.rb' + - 'lib/rpush/daemon/apnsp8/delivery.rb' + - 'spec/unit/daemon/pushy/delivery_spec.rb' + +# Offense count: 2 +# This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle. # SupportedStyles: symmetrical, new_line, same_line Layout/MultilineHashBraceLayout: Exclude: - 'spec/functional/apns2_spec.rb' - - 'spec/unit/daemon/gcm/delivery_spec.rb' # Offense count: 2 -# Cop supports --auto-correct. +# This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle. # SupportedStyles: symmetrical, new_line, same_line Layout/MultilineMethodCallBraceLayout: @@ -194,35 +226,44 @@ Layout/MultilineMethodCallBraceLayout: - 'lib/rpush/daemon/apns2/delivery.rb' - 'lib/rpush/daemon/apnsp8/delivery.rb' -# Offense count: 4 -# Cop supports --auto-correct. +# Offense count: 2 +# This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle, IndentationWidth. # SupportedStyles: aligned, indented, indented_relative_to_receiver Layout/MultilineMethodCallIndentation: Exclude: - 'lib/rpush/daemon/wns/toast_request.rb' - 'lib/rpush/daemon/wpns/delivery.rb' - - 'spec/functional/apns2_spec.rb' # Offense count: 3 -# Cop supports --auto-correct. +# This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle, IndentationWidth. # SupportedStyles: aligned, indented Layout/MultilineOperationIndentation: Exclude: - 'lib/rpush/client/active_model/webpush/notification.rb' -# Offense count: 6 -# Cop supports --auto-correct. -# Configuration parameters: AllowForAlignment, EnforcedStyleForExponentOperator. +# Offense count: 1 +# This cop supports safe autocorrection (--autocorrect). +Layout/SpaceAfterColon: + Exclude: + - 'spec/unit/daemon/fcm/delivery_spec.rb' + +# Offense count: 12 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: AllowForAlignment, EnforcedStyleForExponentOperator, EnforcedStyleForRationalLiterals. # SupportedStylesForExponentOperator: space, no_space +# SupportedStylesForRationalLiterals: space, no_space Layout/SpaceAroundOperators: Exclude: - 'lib/rpush/client/active_model/webpush/notification.rb' - 'spec/functional/apns2_spec.rb' + - 'spec/functional/cli_spec.rb' + - 'spec/functional/embed_spec.rb' + - 'spec/unit/client/shared/fcm/notification.rb' # Offense count: 1 -# Cop supports --auto-correct. +# This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBraces. # SupportedStyles: space, no_space # SupportedStylesForEmptyBraces: space, no_space @@ -231,7 +272,7 @@ Layout/SpaceBeforeBlockBraces: - 'spec/unit/client/shared/webpush/notification.rb' # Offense count: 10 -# Cop supports --auto-correct. +# This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBrackets. # SupportedStyles: space, no_space, compact # SupportedStylesForEmptyBrackets: space, no_space @@ -242,34 +283,49 @@ Layout/SpaceInsideArrayLiteralBrackets: - 'lib/rpush/daemon/webpush/delivery.rb' - 'spec/unit/client/shared/webpush/app.rb' -# Offense count: 21 -# Cop supports --auto-correct. +# Offense count: 4 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBraces, SpaceBeforeBlockParameters. +# SupportedStyles: space, no_space +# SupportedStylesForEmptyBraces: space, no_space +Layout/SpaceInsideBlockBraces: + Exclude: + - 'spec/functional/fcm_spec.rb' + - 'spec/functional/retry_spec.rb' + +# Offense count: 35 +# This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBraces. # SupportedStyles: space, no_space, compact # SupportedStylesForEmptyBraces: space, no_space Layout/SpaceInsideHashLiteralBraces: Exclude: + - 'spec/functional/fcm_spec.rb' + - 'spec/functional/retry_spec.rb' - 'spec/functional/webpush_spec.rb' + - 'spec/unit/client/shared/fcm/notification.rb' - 'spec/unit/client/shared/webpush/notification.rb' + - 'spec/unit/daemon/service_config_methods_spec.rb' - 'spec/unit/daemon/webpush/delivery_spec.rb' + - 'spec/unit/daemon/wns/delivery_spec.rb' # Offense count: 1 -# Cop supports --auto-correct. +# This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle. -# SupportedStyles: space, no_space +# SupportedStyles: space, compact, no_space Layout/SpaceInsideParens: Exclude: - 'spec/unit/client/shared/webpush/notification.rb' # Offense count: 4 -# Cop supports --auto-correct. +# This cop supports safe autocorrection (--autocorrect). Layout/SpaceInsidePercentLiteralDelimiters: Exclude: - 'lib/rpush/client/active_model/webpush/app.rb' - 'lib/rpush/client/active_model/webpush/notification.rb' # Offense count: 4 -# Cop supports --auto-correct. +# This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle. # SupportedStyles: final_newline, final_blank_line Layout/TrailingEmptyLines: @@ -279,7 +335,16 @@ Layout/TrailingEmptyLines: - 'lib/rpush/daemon/webpush/delivery.rb' - 'spec/functional/webpush_spec.rb' +# Offense count: 5 +# This cop supports safe autocorrection (--autocorrect). +Lint/AmbiguousOperatorPrecedence: + Exclude: + - 'lib/rpush/daemon/delivery.rb' + - 'spec/unit/daemon/adm/delivery_spec.rb' + - 'spec/unit/daemon/fcm/delivery_spec.rb' + # Offense count: 1 +# This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: AllowSafeAssignment. Lint/AssignmentInCondition: Exclude: @@ -297,10 +362,11 @@ Lint/ConstantDefinitionInBlock: - 'spec/unit/deprecatable_spec.rb' - 'spec/unit/reflectable_spec.rb' -# Offense count: 3 +# Offense count: 4 # Configuration parameters: IgnoreLiteralBranches, IgnoreConstantBranches. Lint/DuplicateBranch: Exclude: + - 'lib/rpush/client/active_model/fcm/notification.rb' - 'lib/rpush/daemon/wns/delivery.rb' - 'spec/unit/daemon/store/active_record/reconnectable_spec.rb' @@ -310,7 +376,7 @@ Lint/EmptyBlock: Exclude: - 'bm/reflection_benchmark.rb' - 'spec/unit/client/redis/apns2/app_spec.rb' - - 'spec/unit/client/shared/gcm/app.rb' + - 'spec/unit/client/shared/fcm/app.rb' - 'spec/unit/client/shared/wpns/app.rb' # Offense count: 2 @@ -320,109 +386,121 @@ Lint/EmptyClass: - 'spec/unit/daemon/app_runner_spec.rb' - 'spec/unit/daemon/service_config_methods_spec.rb' +# Offense count: 1 +# This cop supports unsafe autocorrection (--autocorrect-all). +Lint/IncompatibleIoSelectWithFiberScheduler: + Exclude: + - 'lib/rpush/daemon/signal_handler.rb' + # Offense count: 6 Lint/IneffectiveAccessModifier: Exclude: - 'lib/rpush/daemon.rb' -# Offense count: 14 +# Offense count: 12 +# Configuration parameters: AllowedParentClasses. Lint/MissingSuper: Exclude: - 'lib/rpush/daemon/adm/delivery.rb' - - 'lib/rpush/daemon/apns/delivery.rb' - 'lib/rpush/daemon/apns2/delivery.rb' - 'lib/rpush/daemon/apnsp8/delivery.rb' - 'lib/rpush/daemon/delivery_error.rb' - 'lib/rpush/daemon/errors.rb' - - 'lib/rpush/daemon/gcm/delivery.rb' + - 'lib/rpush/daemon/fcm/delivery.rb' - 'lib/rpush/daemon/pushy/delivery.rb' - 'lib/rpush/daemon/retryable_error.rb' - 'lib/rpush/daemon/ring_buffer.rb' - 'lib/rpush/daemon/webpush/delivery.rb' - 'lib/rpush/daemon/wns/delivery.rb' - 'lib/rpush/daemon/wpns/delivery.rb' - - 'spec/unit/daemon/delivery_spec.rb' -# Offense count: 1 -Lint/NestedMethodDefinition: +# Offense count: 2 +# This cop supports unsafe autocorrection (--autocorrect-all). +Lint/NonAtomicFileOperation: Exclude: - - 'spec/unit/daemon/apns/feedback_receiver_spec.rb' + - 'lib/rpush/daemon/rpc/server.rb' -# Offense count: 5 -# Cop supports --auto-correct. +# Offense count: 6 +# This cop supports safe autocorrection (--autocorrect). Lint/RedundantCopDisableDirective: Exclude: - - 'lib/rpush/client/active_model/gcm/notification.rb' + - 'lib/rpush/client/active_model/fcm/notification.rb' - 'lib/rpush/daemon/interruptible_sleep.rb' - 'lib/rpush/daemon/rpc/client.rb' - - 'lib/rpush/daemon/tcp_connection.rb' + - 'lib/rpush/daemon/store/active_record.rb' + - 'lib/rpush/daemon/store/redis.rb' # Offense count: 1 -# Cop supports --auto-correct. +# This cop supports unsafe autocorrection (--autocorrect-all). +Lint/RedundantDirGlobSort: + Exclude: + - 'spec/unit_spec_helper.rb' + +# Offense count: 1 +# This cop supports unsafe autocorrection (--autocorrect-all). Lint/RedundantRequireStatement: Exclude: - 'lib/rpush/daemon.rb' -# Offense count: 4 +# Offense count: 3 # Configuration parameters: AllowComments, AllowNil. Lint/SuppressedException: Exclude: - 'lib/rpush/daemon/interruptible_sleep.rb' - 'lib/rpush/daemon/rpc/client.rb' - - 'lib/rpush/daemon/tcp_connection.rb' + +# Offense count: 22 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyle. +# SupportedStyles: strict, consistent +Lint/SymbolConversion: + Exclude: + - 'spec/functional/fcm_spec.rb' + - 'spec/functional/retry_spec.rb' # Offense count: 2 -# Cop supports --auto-correct. -# Configuration parameters: IgnoreEmptyBlocks, AllowUnusedKeywordArguments. +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: AutoCorrect, IgnoreEmptyBlocks, AllowUnusedKeywordArguments. Lint/UnusedBlockArgument: Exclude: - 'spec/functional/apns2_spec.rb' # Offense count: 3 -# Cop supports --auto-correct. -# Configuration parameters: AllowUnusedKeywordArguments, IgnoreEmptyMethods, IgnoreNotImplementedMethods. +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: AutoCorrect, AllowUnusedKeywordArguments, IgnoreEmptyMethods, IgnoreNotImplementedMethods. Lint/UnusedMethodArgument: Exclude: - 'lib/rpush/client/active_model/adm/notification.rb' - 'lib/rpush/client/active_model/apns/notification.rb' - - 'lib/rpush/client/active_model/gcm/notification.rb' + - 'lib/rpush/client/active_model/fcm/notification.rb' # Offense count: 2 -# Cop supports --auto-correct. -# Configuration parameters: ContextCreatingMethods, MethodCreatingMethods. +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: AutoCorrect, ContextCreatingMethods, MethodCreatingMethods. Lint/UselessAccessModifier: Exclude: - 'lib/rpush/daemon.rb' - 'lib/rpush/daemon/wns/post_request.rb' # Offense count: 5 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: AutoCorrect. Lint/UselessAssignment: Exclude: - 'spec/functional/apns2_spec.rb' # Offense count: 1 -# Cop supports --auto-correct. -# Configuration parameters: AllowComments. +# This cop supports unsafe autocorrection (--autocorrect-all). +# Configuration parameters: AutoCorrect. Lint/UselessMethodDefinition: Exclude: - 'lib/rpush/configuration.rb' -# Offense count: 83 -# Configuration parameters: CountComments, CountAsOne, ExcludedMethods, IgnoredMethods. -# IgnoredMethods: refine +# Offense count: 71 +# Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns. +# AllowedMethods: refine Metrics/BlockLength: - Max: 326 - -# Offense count: 1 -# Configuration parameters: IgnoredMethods. -Metrics/PerceivedComplexity: - Max: 9 - -# Offense count: 1 -# Cop supports --auto-correct. -Migration/DepartmentName: - Exclude: - - 'lib/rpush/daemon/tcp_connection.rb' + Max: 279 # Offense count: 1 # Configuration parameters: MinNameLength, AllowNamesEndingInNumbers, AllowedNames, ForbiddenNames. @@ -432,12 +510,13 @@ Naming/BlockParameterName: # Offense count: 1 # Configuration parameters: ForbiddenDelimiters. -# ForbiddenDelimiters: (?-mix:(^|\s)(EO[A-Z]{1}|END)(\s|$)) +# ForbiddenDelimiters: (?i-mx:(^|\s)(EO[A-Z]{1}|END)(\s|$)) Naming/HeredocDelimiterNaming: Exclude: - 'lib/rpush/daemon.rb' # Offense count: 1 +# This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: EnforcedStyleForLeadingUnderscores. # SupportedStylesForLeadingUnderscores: disallowed, required, optional Naming/MemoizedInstanceVariableName: @@ -446,119 +525,115 @@ Naming/MemoizedInstanceVariableName: # Offense count: 1 # Configuration parameters: MinNameLength, AllowNamesEndingInNumbers, AllowedNames, ForbiddenNames. -# AllowedNames: at, by, db, id, in, io, ip, of, on, os, pp, to +# AllowedNames: as, at, by, cc, db, id, if, in, io, ip, of, on, os, pp, to Naming/MethodParameterName: Exclude: - 'lib/rpush/daemon/loggable.rb' # Offense count: 2 -# Configuration parameters: EnforcedStyle, AllowedIdentifiers. +# Configuration parameters: EnforcedStyle, AllowedIdentifiers, AllowedPatterns. # SupportedStyles: snake_case, camelCase Naming/VariableName: Exclude: - 'lib/rpush/daemon/adm/delivery.rb' # Offense count: 1 -# Cop supports --auto-correct. +# This cop supports unsafe autocorrection (--autocorrect-all). Performance/AncestorsInclude: Exclude: - 'lib/rpush/configuration.rb' +# Offense count: 2 +# This cop supports unsafe autocorrection (--autocorrect-all). +Performance/MapCompact: + Exclude: + - 'lib/rpush/daemon/service_config_methods.rb' + - 'lib/rpush/daemon/store/redis.rb' + # Offense count: 1 -# Cop supports --auto-correct. +# This cop supports safe autocorrection (--autocorrect). Performance/RedundantBlockCall: Exclude: - 'bm/bench.rb' -# Offense count: 5 -# Cop supports --auto-correct. +# Offense count: 3 +# This cop supports safe autocorrection (--autocorrect). Performance/RegexpMatch: Exclude: - 'lib/rpush/client/active_model/apns/device_token_format_validator.rb' - 'lib/rpush/daemon/retry_header_parser.rb' - - 'lib/rpush/daemon/tcp_connection.rb' - 'spec/support/active_record_setup.rb' -# Offense count: 16 -# Cop supports --auto-correct. +# Offense count: 17 +# This cop supports safe autocorrection (--autocorrect). Performance/StringIdentifierArgument: Exclude: - 'lib/rpush/client/active_model/apns/notification.rb' + - 'lib/rpush/configuration.rb' - 'lib/rpush/daemon/loggable.rb' - 'lib/rpush/daemon/service_config_methods.rb' + - 'lib/rpush/deprecatable.rb' - 'lib/rpush/logger.rb' + - 'lib/rpush/plugin.rb' - 'spec/spec_helper.rb' - - 'spec/unit/daemon/apns/feedback_receiver_spec.rb' - 'spec/unit/daemon/store/active_record/reconnectable_spec.rb' - - 'spec/unit/daemon/tcp_connection_spec.rb' - 'spec/unit/logger_spec.rb' -# Offense count: 6 -# Cop supports --auto-correct. +# Offense count: 5 +# This cop supports unsafe autocorrection (--autocorrect-all). Performance/StringInclude: Exclude: - - 'lib/rpush/daemon/tcp_connection.rb' - 'lib/rpush/daemon/wns/post_request.rb' - 'spec/functional_spec_helper.rb' - 'spec/support/active_record_setup.rb' - 'spec/unit_spec_helper.rb' # Offense count: 1 -# Cop supports --auto-correct. -Performance/TimesMap: - Exclude: - - 'spec/functional/apns_spec.rb' - -# Offense count: 1 -# Cop supports --auto-correct. -# Configuration parameters: AutoCorrect. +# This cop supports unsafe autocorrection (--autocorrect-all). Security/JSONLoad: Exclude: - 'lib/rpush/daemon/rpc/server.rb' # Offense count: 1 -# Cop supports --auto-correct. +# This cop supports unsafe autocorrection (--autocorrect-all). Security/YAMLLoad: Exclude: - 'spec/support/active_record_setup.rb' # Offense count: 1 -# Cop supports --auto-correct. +# This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle. # SupportedStyles: prefer_alias, prefer_alias_method Style/Alias: Exclude: - 'lib/rpush/daemon/ring_buffer.rb' -# Offense count: 9 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, ProceduralMethods, FunctionalMethods, IgnoredMethods, AllowBracesOnProceduralOneLiners, BracesRequiredMethods. +# Offense count: 13 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyle, ProceduralMethods, FunctionalMethods, AllowedMethods, AllowedPatterns, AllowBracesOnProceduralOneLiners, BracesRequiredMethods. # SupportedStyles: line_count_based, semantic, braces_for_chaining, always_braces # ProceduralMethods: benchmark, bm, bmbm, create, each_with_object, measure, new, realtime, tap, with_object # FunctionalMethods: let, let!, subject, watch -# IgnoredMethods: lambda, proc, it +# AllowedMethods: lambda, proc, it Style/BlockDelimiters: Exclude: - 'spec/functional/apns2_spec.rb' + - 'spec/functional/cli_spec.rb' + - 'spec/functional/embed_spec.rb' - 'spec/functional/webpush_spec.rb' - 'spec/unit/daemon/webpush/delivery_spec.rb' -# Offense count: 1 -# Cop supports --auto-correct. -Style/CaseLikeIf: - Exclude: - - 'lib/rpush/cli.rb' - -# Offense count: 1 -# Cop supports --auto-correct. -# Configuration parameters: Keywords. +# Offense count: 3 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: Keywords, RequireColon. # Keywords: TODO, FIXME, OPTIMIZE, HACK, REVIEW, NOTE Style/CommentAnnotation: Exclude: - 'lib/rpush/daemon/apnsp8/delivery.rb' + - 'spec/unit/client/shared/fcm/notification.rb' # Offense count: 2 -# Cop supports --auto-correct. +# This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle, SingleLineConditionsOnly, IncludeTernaryExpressions. # SupportedStyles: assign_to_condition, assign_inside_condition Style/ConditionalAssignment: @@ -572,60 +647,79 @@ Style/DocumentDynamicEvalDefinition: - 'lib/rpush/deprecatable.rb' - 'lib/rpush/reflection_collection.rb' +# Offense count: 1 +# This cop supports safe autocorrection (--autocorrect). +Style/EmptyCaseCondition: + Exclude: + - 'lib/rpush/client/active_model/fcm/notification.rb' + # Offense count: 5 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle. +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: AutoCorrect, EnforcedStyle. # SupportedStyles: compact, expanded Style/EmptyMethod: Exclude: - 'lib/rpush/daemon/store/redis.rb' - 'spec/unit/daemon/app_runner_spec.rb' -# Offense count: 5 -# Cop supports --auto-correct. +# Offense count: 4 +# This cop supports safe autocorrection (--autocorrect). Style/Encoding: Exclude: - 'lib/rpush/cli.rb' - 'lib/rpush/daemon.rb' - - 'lib/rpush/daemon/apns/feedback_receiver.rb' - 'lib/rpush/daemon/app_runner.rb' - 'rpush.gemspec' # Offense count: 2 -# Cop supports --auto-correct. +# This cop supports safe autocorrection (--autocorrect). Style/EvalWithLocation: Exclude: - 'lib/rpush/deprecatable.rb' - 'lib/rpush/reflection_collection.rb' # Offense count: 1 -# Cop supports --auto-correct. +# This cop supports safe autocorrection (--autocorrect). Style/ExpandPathArguments: Exclude: - 'rpush.gemspec' -# Offense count: 2 -# Cop supports --auto-correct. +# Offense count: 1 +# This cop supports safe autocorrection (--autocorrect). Style/ExplicitBlockArgument: Exclude: - - 'lib/rpush/daemon/gcm/delivery.rb' - 'lib/rpush/daemon/store/active_record/reconnectable.rb' +# Offense count: 2 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: AllowedVars. +Style/FetchEnvVar: + Exclude: + - 'lib/rpush/daemon/fcm/delivery.rb' + - 'spec/spec_helper.rb' + +# Offense count: 1 +# This cop supports safe autocorrection (--autocorrect). +Style/FileWrite: + Exclude: + - 'lib/tasks/test.rake' + # Offense count: 3 -# Configuration parameters: MaxUnannotatedPlaceholdersAllowed, IgnoredMethods. +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: MaxUnannotatedPlaceholdersAllowed, AllowedMethods, AllowedPatterns. # SupportedStyles: annotated, template, unannotated Style/FormatStringToken: EnforcedStyle: unannotated -# Offense count: 279 -# Cop supports --auto-correct. +# Offense count: 266 +# This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: EnforcedStyle. # SupportedStyles: always, always_true, never Style/FrozenStringLiteralComment: Enabled: false # Offense count: 24 -# Cop supports --auto-correct. +# This cop supports unsafe autocorrection (--autocorrect-all). Style/GlobalStdStream: Exclude: - 'lib/rpush/cli.rb' @@ -637,75 +731,91 @@ Style/GlobalStdStream: - 'spec/unit/deprecation_spec.rb' - 'spec/unit/logger_spec.rb' -# Offense count: 7 -# Configuration parameters: MinBodyLength. +# Offense count: 5 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: MinBodyLength, AllowConsecutiveConditionals. Style/GuardClause: Exclude: - 'lib/rpush/client/active_model/certificate_private_key_validator.rb' - 'lib/rpush/daemon.rb' - 'lib/rpush/daemon/adm/delivery.rb' - 'lib/rpush/daemon/app_runner.rb' - - 'lib/rpush/daemon/tcp_connection.rb' - - 'spec/unit/daemon/apns/feedback_receiver_spec.rb' # Offense count: 1 -# Cop supports --auto-correct. +# This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: AllowSplatArgument. Style/HashConversion: Exclude: - 'lib/rpush/daemon/store/active_record.rb' -# Offense count: 1 -# Cop supports --auto-correct. +# Offense count: 2 +# This cop supports unsafe autocorrection (--autocorrect-all). +# Configuration parameters: AllowedReceivers. +# AllowedReceivers: Thread.current Style/HashEachMethods: Exclude: - 'lib/rpush/daemon/wns/post_request.rb' + - 'spec/spec_helper.rb' -# Offense count: 7 -# Cop supports --auto-correct. +# Offense count: 1 +# This cop supports unsafe autocorrection (--autocorrect-all). +Style/HashTransformKeys: + Exclude: + - 'lib/rpush/daemon/store/active_record.rb' + +# Offense count: 6 +# This cop supports safe autocorrection (--autocorrect). Style/IfUnlessModifier: Exclude: - 'lib/rpush/client/active_model/apns/notification.rb' - 'lib/rpush/client/active_model/webpush/app.rb' - 'lib/rpush/daemon/delivery.rb' - - 'lib/rpush/daemon/tcp_connection.rb' - 'lib/rpush/embed.rb' - 'spec/support/simplecov_helper.rb' +# Offense count: 1 +# This cop supports unsafe autocorrection (--autocorrect-all). +# Configuration parameters: AllowedMethods. +# AllowedMethods: nonzero? +Style/IfWithBooleanLiteralBranches: + Exclude: + - 'lib/rpush/client/active_model/fcm/notification.rb' + # Offense count: 2 -# Cop supports --auto-correct. +# This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: InverseMethods, InverseBlocks. Style/InverseMethods: Exclude: - 'lib/rpush/daemon/adm/delivery.rb' # Offense count: 46 -# Cop supports --auto-correct. +# This cop supports safe autocorrection (--autocorrect). Style/MultilineIfModifier: Enabled: false # Offense count: 1 -# Cop supports --auto-correct. +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: AllowMethodComparison, ComparisonsThreshold. Style/MultipleComparison: Exclude: - 'lib/rpush/client/active_model/apns/notification.rb' -# Offense count: 34 -# Cop supports --auto-correct. +# Offense count: 28 +# This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: EnforcedStyle. # SupportedStyles: literals, strict Style/MutableConstant: Enabled: false # Offense count: 1 -# Cop supports --auto-correct. +# This cop supports safe autocorrection (--autocorrect). Style/NegatedIfElseCondition: Exclude: - 'lib/rpush/cli.rb' -# Offense count: 11 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, IgnoredMethods. +# Offense count: 8 +# This cop supports unsafe autocorrection (--autocorrect-all). +# Configuration parameters: EnforcedStyle, AllowedMethods, AllowedPatterns. # SupportedStyles: predicate, comparison Style/NumericPredicate: Exclude: @@ -713,11 +823,16 @@ Style/NumericPredicate: - 'lib/rpush/daemon.rb' - 'lib/rpush/daemon/apnsp8/delivery.rb' - 'lib/rpush/daemon/app_runner.rb' - - 'lib/rpush/daemon/dispatcher/apns_tcp.rb' - 'lib/rpush/daemon/feeder.rb' - 'lib/rpush/daemon/store/redis.rb' - 'lib/rpush/daemon/synchronizer.rb' +# Offense count: 2 +Style/OpenStructUse: + Exclude: + - 'lib/rpush/configuration.rb' + - 'lib/rpush/plugin.rb' + # Offense count: 7 # Configuration parameters: AllowedMethods. # AllowedMethods: respond_to_missing? @@ -728,96 +843,171 @@ Style/OptionalBooleanParameter: - 'lib/tasks/test.rake' # Offense count: 3 -# Cop supports --auto-correct. +# This cop supports safe autocorrection (--autocorrect). Style/OrAssignment: Exclude: - 'lib/rpush/daemon/wns/delivery.rb' - 'lib/rpush/daemon/wpns/delivery.rb' -# Offense count: 15 -# Cop supports --auto-correct. +# Offense count: 6 +# This cop supports safe autocorrection (--autocorrect). # Configuration parameters: PreferredDelimiters. Style/PercentLiteralDelimiters: Exclude: - 'lib/rpush/client/active_model/apns/app.rb' - 'lib/rpush/client/active_model/apns2/app.rb' - 'lib/rpush/client/active_model/apnsp8/app.rb' - - 'lib/rpush/daemon/gcm/delivery.rb' - 'lib/rpush/daemon/signal_handler.rb' - 'spec/unit/daemon/adm/delivery_spec.rb' - - 'spec/unit/daemon/gcm/delivery_spec.rb' -# Offense count: 4 -# Cop supports --auto-correct. -Style/RedundantRegexpCharacterClass: +# Offense count: 20 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyle. +# SupportedStyles: same_as_string_literals, single_quotes, double_quotes +Style/QuotedSymbols: Exclude: - - 'lib/rpush/client/active_model/wns/notification.rb' - - 'lib/rpush/client/active_model/wpns/notification.rb' - - 'spec/unit/daemon/gcm/delivery_spec.rb' + - 'spec/functional/fcm_spec.rb' + - 'spec/functional/retry_spec.rb' # Offense count: 1 -# Cop supports --auto-correct. -Style/RedundantSelf: +# This cop supports safe autocorrection (--autocorrect). +Style/RedundantArrayConstructor: Exclude: - - 'lib/rpush/client/active_model/apns/notification.rb' + - 'lib/rpush/daemon/service_config_methods.rb' + +# Offense count: 6 +# This cop supports safe autocorrection (--autocorrect). +Style/RedundantBegin: + Exclude: + - 'lib/rpush/cli.rb' + - 'lib/rpush/daemon/batch.rb' + - 'lib/rpush/daemon/rpc/server.rb' + - 'lib/rpush/daemon/store/active_record/reconnectable.rb' + - 'lib/rpush/reflectable.rb' + - 'spec/support/active_record_setup.rb' # Offense count: 1 -# Cop supports --auto-correct. +# This cop supports safe autocorrection (--autocorrect). +Style/RedundantConditional: + Exclude: + - 'lib/rpush/client/active_model/fcm/notification.rb' + +# Offense count: 15 +# This cop supports safe autocorrection (--autocorrect). +Style/RedundantConstantBase: + Exclude: + - 'lib/rpush/daemon/store/active_record/reconnectable.rb' + - 'spec/functional/fcm_spec.rb' + - 'spec/functional/retry_spec.rb' + - 'spec/functional_spec_helper.rb' + - 'spec/spec_helper.rb' + - 'spec/unit/daemon/store/active_record/reconnectable_spec.rb' + - 'spec/unit/logger_spec.rb' + +# Offense count: 5 +# This cop supports safe autocorrection (--autocorrect). +Style/RedundantLineContinuation: + Exclude: + - 'lib/rpush/daemon/delivery_error.rb' + - 'lib/rpush/daemon/wns/toast_request.rb' + - 'lib/rpush/daemon/wpns/delivery.rb' + +# Offense count: 10 +# This cop supports safe autocorrection (--autocorrect). +Style/RedundantRegexpArgument: + Exclude: + - 'lib/rpush/daemon/wns/toast_request.rb' + - 'lib/rpush/daemon/wpns/delivery.rb' + +# Offense count: 1 +# This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle. # SupportedStyles: implicit, explicit Style/RescueStandardError: Exclude: - 'lib/rpush/client/active_model/webpush/app.rb' -# Offense count: 17 -# Cop supports --auto-correct. -# Configuration parameters: ConvertCodeThatCanStartToReturnNil, AllowedMethods. +# Offense count: 1 +# This cop supports unsafe autocorrection (--autocorrect-all). +# Configuration parameters: AllowedMethods, AllowedPatterns. +Style/ReturnNilInPredicateMethodDefinition: + Exclude: + - 'spec/unit/daemon/store/active_record/reconnectable_spec.rb' + +# Offense count: 10 +# This cop supports unsafe autocorrection (--autocorrect-all). +# Configuration parameters: ConvertCodeThatCanStartToReturnNil, AllowedMethods, MaxChainLength. # AllowedMethods: present?, blank?, presence, try, try! Style/SafeNavigation: Exclude: - 'lib/rpush/client/active_model/apns/notification.rb' - - 'lib/rpush/daemon/apns/feedback_receiver.rb' - 'lib/rpush/daemon/app_runner.rb' - - 'lib/rpush/daemon/dispatcher/apns_tcp.rb' - - 'lib/rpush/daemon/dispatcher/tcp.rb' - 'lib/rpush/daemon/dispatcher_loop.rb' - 'lib/rpush/daemon/feeder.rb' - 'lib/rpush/daemon/interruptible_sleep.rb' - 'lib/rpush/daemon/rpc/server.rb' - 'lib/rpush/daemon/signal_handler.rb' - - 'lib/rpush/daemon/tcp_connection.rb' - 'lib/rpush/embed.rb' - 'lib/rpush/logger.rb' # Offense count: 3 -# Cop supports --auto-correct. +# This cop supports safe autocorrection (--autocorrect). # Configuration parameters: AllowAsExpressionSeparator. Style/Semicolon: Exclude: - 'spec/functional/apns2_spec.rb' +# Offense count: 2 +# This cop supports unsafe autocorrection (--autocorrect-all). +Style/SlicingWithRange: + Exclude: + - 'spec/unit/client/shared/apns/notification.rb' + # Offense count: 11 -# Cop supports --auto-correct. +# This cop supports unsafe autocorrection (--autocorrect-all). +# Configuration parameters: Mode. Style/StringConcatenation: Exclude: - 'lib/rpush/cli.rb' - 'lib/rpush/daemon/adm/delivery.rb' - - 'lib/rpush/daemon/gcm/delivery.rb' + - 'lib/rpush/daemon/fcm/delivery.rb' - 'lib/rpush/daemon/wns/delivery.rb' - 'lib/rpush/daemon/wpns/delivery.rb' - 'lib/rpush/deprecation.rb' - 'spec/support/active_record_setup.rb' -# Offense count: 11 -# Cop supports --auto-correct. +# Offense count: 2 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyle. +# SupportedStyles: single_quotes, double_quotes +Style/StringLiteralsInInterpolation: + Exclude: + - 'spec/unit/logger_spec.rb' + +# Offense count: 3 +# This cop supports safe autocorrection (--autocorrect). +Style/SuperArguments: + Exclude: + - 'lib/rpush/client/active_model/fcm/notification.rb' + - 'lib/rpush/client/active_model/webpush/notification.rb' + - 'lib/rpush/configuration.rb' + +# Offense count: 1 +# This cop supports safe autocorrection (--autocorrect). +Style/SuperWithArgsParentheses: + Exclude: + - 'lib/rpush/client/active_model/webpush/notification.rb' + +# Offense count: 10 +# This cop supports safe autocorrection (--autocorrect). # Configuration parameters: . # SupportedStyles: percent, brackets Style/SymbolArray: EnforcedStyle: percent - MinSize: 21 + MinSize: 20 # Offense count: 2 -# Cop supports --auto-correct. +# This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle, AllowSafeAssignment. # SupportedStyles: require_parentheses, require_no_parentheses, require_parentheses_when_complex Style/TernaryParentheses: @@ -826,7 +1016,7 @@ Style/TernaryParentheses: - 'lib/rpush/daemon/store/active_record.rb' # Offense count: 1 -# Cop supports --auto-correct. +# This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyleForMultiline. # SupportedStylesForMultiline: comma, consistent_comma, no_comma Style/TrailingCommaInArrayLiteral: @@ -834,27 +1024,26 @@ Style/TrailingCommaInArrayLiteral: - 'spec/unit/client/shared/webpush/app.rb' # Offense count: 1 -# Cop supports --auto-correct. +# This cop supports safe autocorrection (--autocorrect). Style/UnlessElse: Exclude: - 'lib/rpush/daemon/store/active_record.rb' # Offense count: 1 -# Cop supports --auto-correct. +# This cop supports safe autocorrection (--autocorrect). Style/WhileUntilDo: Exclude: - 'lib/rpush/daemon/apnsp8/delivery.rb' # Offense count: 2 -# Cop supports --auto-correct. +# This cop supports safe autocorrection (--autocorrect). Style/WhileUntilModifier: Exclude: - 'lib/rpush/daemon/apnsp8/delivery.rb' - 'spec/functional/synchronization_spec.rb' -# Offense count: 4 -# Cop supports --auto-correct. +# Offense count: 1 +# This cop supports unsafe autocorrection (--autocorrect-all). Style/ZeroLengthPredicate: Exclude: - 'lib/rpush/daemon/app_runner.rb' - - 'lib/rpush/daemon/dispatcher/apns_tcp.rb' diff --git a/Gemfile.lock b/Gemfile.lock index c6b14921f..f2453b60c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -88,8 +88,10 @@ GEM irb (1.14.0) rdoc (>= 4.0.0) reline (>= 0.4.2) + json (2.7.2) jwt (2.8.2) base64 + language_server-protocol (3.17.0.3) logger (1.6.1) loofah (2.22.0) crass (~> 1.0.2) @@ -117,9 +119,10 @@ GEM racc (~> 1.4) openssl (3.2.0) os (1.1.4) - parallel (1.21.0) - parser (3.1.0.0) + parallel (1.26.3) + parser (3.3.5.0) ast (~> 2.4.1) + racc pg (1.2.3) psych (5.1.2) stringio @@ -150,10 +153,9 @@ GEM redis-client (>= 0.22.0) redis-client (0.22.2) connection_pool - regexp_parser (2.2.0) + regexp_parser (2.9.2) reline (0.5.9) io-console (~> 0.5) - rexml (3.2.5) rpush-redis (1.2.0) modis (>= 3.0, < 5.0) rspec (3.13.0) @@ -169,21 +171,22 @@ GEM diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) rspec-support (3.13.1) - rubocop (1.12.1) + rubocop (1.66.1) + json (~> 2.3) + language_server-protocol (>= 3.17.0) parallel (~> 1.10) - parser (>= 3.0.0.0) + parser (>= 3.3.0.2) rainbow (>= 2.2.2, < 4.0) - regexp_parser (>= 1.8, < 3.0) - rexml - rubocop-ast (>= 1.2.0, < 2.0) + regexp_parser (>= 2.4, < 3.0) + rubocop-ast (>= 1.32.2, < 2.0) ruby-progressbar (~> 1.7) - unicode-display_width (>= 1.4.0, < 3.0) - rubocop-ast (1.15.1) - parser (>= 3.0.1.1) - rubocop-performance (1.13.2) - rubocop (>= 1.7.0, < 2.0) - rubocop-ast (>= 0.4.0) - ruby-progressbar (1.11.0) + unicode-display_width (>= 2.4.0, < 3.0) + rubocop-ast (1.32.3) + parser (>= 3.3.1.0) + rubocop-performance (1.21.1) + rubocop (>= 1.48.1, < 2.0) + rubocop-ast (>= 1.31.1, < 2.0) + ruby-progressbar (1.13.0) signet (0.19.0) addressable (~> 2.8) faraday (>= 0.17.5, < 3.a) @@ -203,7 +206,7 @@ GEM timecop (0.9.4) tzinfo (2.0.6) concurrent-ruby (~> 1.0) - unicode-display_width (2.1.0) + unicode-display_width (2.5.0) uri (0.13.1) web-push (3.0.1) jwt (~> 2.0) @@ -226,7 +229,7 @@ DEPENDENCIES rpush! rpush-redis (~> 1.0) rspec - rubocop (~> 1.12.0) + rubocop (~> 1.66) rubocop-performance simplecov sqlite3 diff --git a/rpush.gemspec b/rpush.gemspec index 393ff1808..7643032ec 100644 --- a/rpush.gemspec +++ b/rpush.gemspec @@ -55,7 +55,7 @@ Gem::Specification.new do |s| s.add_development_dependency 'appraisal' s.add_development_dependency 'codeclimate-test-reporter', '1.0.7' s.add_development_dependency 'simplecov' - s.add_development_dependency 'rubocop', '~> 1.12.0' + s.add_development_dependency 'rubocop', '~> 1.66' s.add_development_dependency 'rubocop-performance' s.add_development_dependency 'byebug' From b51f86102d5b930100891f4d85dec5565c72808b Mon Sep 17 00:00:00 2001 From: Ben Langfeld Date: Fri, 6 Sep 2024 18:32:18 -0300 Subject: [PATCH 13/32] Apply rubocop rules for rake, rspec and rails (#690) --- .rubocop.yml | 6 +- .rubocop_todo.yml | 464 +++++++++++++++++++++++++++++++++++++++++++++- Gemfile.lock | 12 ++ rpush.gemspec | 3 + 4 files changed, 477 insertions(+), 8 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 3396b606a..ad6aa7e09 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,6 +1,10 @@ inherit_from: .rubocop_todo.yml -require: rubocop-performance +require: + - rubocop-performance + - rubocop-rake + - rubocop-rspec + - rubocop-rails AllCops: Exclude: diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 2aec09736..e2c7c05ec 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,6 +1,6 @@ # This configuration was generated by # `rubocop --auto-gen-config` -# on 2024-09-06 21:22:46 UTC using RuboCop version 1.66.1. +# on 2024-09-06 21:28:45 UTC using RuboCop version 1.66.1. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new @@ -22,7 +22,7 @@ Gemspec/DeprecatedAttributeAssignment: Exclude: - 'rpush.gemspec' -# Offense count: 17 +# Offense count: 20 # Configuration parameters: EnforcedStyle, AllowedGems, Include. # SupportedStyles: Gemfile, gems.rb, gemspec # Include: **/*.gemspec, **/Gemfile, **/gems.rb @@ -30,7 +30,7 @@ Gemspec/DevelopmentDependencies: Exclude: - 'rpush.gemspec' -# Offense count: 11 +# Offense count: 12 # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: TreatCommentsAsGroupSeparators, ConsiderPunctuation, Include. # Include: **/*.gemspec @@ -496,11 +496,11 @@ Lint/UselessMethodDefinition: Exclude: - 'lib/rpush/configuration.rb' -# Offense count: 71 -# Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns. +# Offense count: 1 +# Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns, inherit_mode. # AllowedMethods: refine Metrics/BlockLength: - Max: 279 + Max: 26 # Offense count: 1 # Configuration parameters: MinNameLength, AllowNamesEndingInNumbers, AllowedNames, ForbiddenNames. @@ -588,6 +588,456 @@ Performance/StringInclude: - 'spec/support/active_record_setup.rb' - 'spec/unit_spec_helper.rb' +# Offense count: 3 +RSpec/AnyInstance: + Exclude: + - 'spec/functional/fcm_spec.rb' + - 'spec/functional/retry_spec.rb' + - 'spec/unit/daemon/fcm/delivery_spec.rb' + +# Offense count: 28 +# This cop supports unsafe autocorrection (--autocorrect-all). +RSpec/BeEq: + Enabled: false + +# Offense count: 2 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: NegatedMatcher. +RSpec/ChangeByZero: + Exclude: + - 'spec/functional/apns2_spec.rb' + +# Offense count: 7 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyle. +# SupportedStyles: be_a, be_kind_of +RSpec/ClassCheck: + Exclude: + - 'spec/functional/apns2_spec.rb' + - 'spec/unit/daemon_spec.rb' + +# Offense count: 7 +# Configuration parameters: Prefixes, AllowedPatterns. +# Prefixes: when, with, without +RSpec/ContextWording: + Exclude: + - 'spec/functional/fcm_priority_spec.rb' + - 'spec/unit/client/shared/app.rb' + - 'spec/unit/daemon/store/active_record/reconnectable_spec.rb' + - 'spec/unit/daemon/wns/post_request_spec.rb' + +# Offense count: 3 +# Configuration parameters: IgnoredMetadata. +RSpec/DescribeClass: + Exclude: + - 'spec/functional/apns2_spec.rb' + - 'spec/functional/embed_spec.rb' + - 'spec/functional/fcm_priority_spec.rb' + +# Offense count: 9 +RSpec/DescribeMethod: + Exclude: + - 'spec/unit/daemon/app_runner_spec.rb' + - 'spec/unit/daemon_spec.rb' + - 'spec/unit/embed_spec.rb' + - 'spec/unit/push_spec.rb' + +# Offense count: 118 +# This cop supports unsafe autocorrection (--autocorrect-all). +# Configuration parameters: SkipBlocks, EnforcedStyle, OnlyStaticConstants. +# SupportedStyles: described_class, explicit +RSpec/DescribedClass: + Enabled: false + +# Offense count: 1 +# This cop supports unsafe autocorrection (--autocorrect-all). +# Configuration parameters: AutoCorrect. +RSpec/EmptyExampleGroup: + Exclude: + - 'spec/unit/client/redis/apns2/app_spec.rb' + +# Offense count: 1 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: AllowConsecutiveOneLiners. +RSpec/EmptyLineAfterExample: + Exclude: + - 'spec/unit/client/shared/webpush/notification.rb' + +# Offense count: 4 +# This cop supports safe autocorrection (--autocorrect). +RSpec/EmptyLineAfterFinalLet: + Exclude: + - 'spec/functional/webpush_spec.rb' + - 'spec/unit/client/shared/webpush/notification.rb' + - 'spec/unit/daemon/pushy/delivery_spec.rb' + - 'spec/unit/daemon/webpush/delivery_spec.rb' + +# Offense count: 7 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: AllowConsecutiveOneLiners. +RSpec/EmptyLineAfterHook: + Exclude: + - 'spec/unit/daemon/wns/delivery_spec.rb' + - 'spec/unit/daemon/wpns/delivery_spec.rb' + +# Offense count: 5 +# This cop supports safe autocorrection (--autocorrect). +RSpec/EmptyLineAfterSubject: + Exclude: + - 'spec/unit/client/shared/pushy/notification.rb' + - 'spec/unit/client/shared/webpush/notification.rb' + +# Offense count: 53 +# Configuration parameters: CountAsOne. +RSpec/ExampleLength: + Max: 25 + +# Offense count: 74 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: CustomTransform, IgnoredWords, DisallowedExamples. +# DisallowedExamples: works +RSpec/ExampleWording: + Enabled: false + +# Offense count: 1 +# This cop supports unsafe autocorrection (--autocorrect-all). +# Configuration parameters: EnforcedStyle. +# SupportedStyles: method_call, block +RSpec/ExpectChange: + Exclude: + - 'spec/unit/daemon/pushy/delivery_spec.rb' + +# Offense count: 5 +RSpec/ExpectInHook: + Exclude: + - 'spec/functional/apns2_spec.rb' + - 'spec/unit/daemon/adm/delivery_spec.rb' + +# Offense count: 11 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyle. +# SupportedStyles: implicit, each, example +RSpec/HookArgument: + Exclude: + - 'spec/functional/apns2_spec.rb' + - 'spec/functional_spec_helper.rb' + - 'spec/spec_helper.rb' + - 'spec/unit/client/shared/adm/app.rb' + - 'spec/unit/daemon/shared/store.rb' + - 'spec/unit_spec_helper.rb' + +# Offense count: 3 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: AutoCorrect. +RSpec/HooksBeforeExamples: + Exclude: + - 'spec/unit/daemon/feeder_spec.rb' + - 'spec/unit/daemon/store/active_record_spec.rb' + - 'spec/unit/daemon/store/redis_spec.rb' + +# Offense count: 19 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyle. +# SupportedStyles: single_line_only, single_statement_only, disallow, require_implicit +RSpec/ImplicitSubject: + Exclude: + - 'spec/unit/client/shared/pushy/app.rb' + - 'spec/unit/client/shared/pushy/notification.rb' + - 'spec/unit/client/shared/webpush/app.rb' + - 'spec/unit/client/shared/webpush/notification.rb' + +# Offense count: 4 +# Configuration parameters: Max, AllowedIdentifiers, AllowedPatterns. +RSpec/IndexedLet: + Exclude: + - 'spec/unit/daemon/apnsp8/delivery_spec.rb' + - 'spec/unit/daemon/batch_spec.rb' + +# Offense count: 11 +# Configuration parameters: AssignmentOnly. +RSpec/InstanceVariable: + Exclude: + - 'spec/functional/apns2_spec.rb' + - 'spec/unit/deprecatable_spec.rb' + - 'spec/unit/logger_spec.rb' + +# Offense count: 3 +# This cop supports safe autocorrection (--autocorrect). +RSpec/LeadingSubject: + Exclude: + - 'spec/unit/client/shared/webpush/notification.rb' + - 'spec/unit/daemon/pushy/delivery_spec.rb' + - 'spec/unit/daemon/webpush/delivery_spec.rb' + +# Offense count: 7 +RSpec/LeakyConstantDeclaration: + Exclude: + - 'spec/unit/daemon/delivery_spec.rb' + - 'spec/unit/daemon/service_config_methods_spec.rb' + - 'spec/unit/daemon/store/active_record/reconnectable_spec.rb' + - 'spec/unit/daemon_spec.rb' + - 'spec/unit/deprecatable_spec.rb' + - 'spec/unit/reflectable_spec.rb' + +# Offense count: 12 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: AutoCorrect. +RSpec/LetBeforeExamples: + Exclude: + - 'spec/unit/client/active_record/fcm/notification_spec.rb' + - 'spec/unit/client/active_record/wns/raw_notification_spec.rb' + - 'spec/unit/daemon/store/active_record_spec.rb' + - 'spec/unit/daemon/store/redis_spec.rb' + +# Offense count: 244 +# Configuration parameters: EnforcedStyle. +# SupportedStyles: have_received, receive +RSpec/MessageSpies: + Enabled: false + +# Offense count: 4 +RSpec/MultipleDescribes: + Exclude: + - 'spec/unit/configuration_spec.rb' + - 'spec/unit/daemon/app_runner_spec.rb' + - 'spec/unit/embed_spec.rb' + - 'spec/unit/reflection_collection_spec.rb' + +# Offense count: 121 +RSpec/MultipleExpectations: + Max: 13 + +# Offense count: 94 +# Configuration parameters: AllowSubject. +RSpec/MultipleMemoizedHelpers: + Max: 14 + +# Offense count: 4 +# Configuration parameters: EnforcedStyle, IgnoreSharedExamples. +# SupportedStyles: always, named_only +RSpec/NamedSubject: + Exclude: + - 'spec/functional/cli_spec.rb' + - 'spec/unit/daemon/pushy/delivery_spec.rb' + +# Offense count: 3 +# Configuration parameters: AllowedGroups. +RSpec/NestedGroups: + Max: 4 + +# Offense count: 5 +# Configuration parameters: AllowedPatterns. +# AllowedPatterns: ^expect_, ^assert_ +RSpec/NoExpectationExample: + Exclude: + - 'spec/functional/synchronization_spec.rb' + - 'spec/unit/daemon/apns/certificate_expired_error_spec.rb' + - 'spec/unit/daemon/apnsp8/delivery_spec.rb' + - 'spec/unit/daemon/app_runner_spec.rb' + +# Offense count: 7 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyle. +# SupportedStyles: not_to, to_not +RSpec/NotToNot: + Exclude: + - 'spec/unit/client/shared/apns/notification.rb' + - 'spec/unit/daemon/adm/delivery_spec.rb' + - 'spec/unit/daemon_spec.rb' + +# Offense count: 7 +RSpec/PendingWithoutReason: + Exclude: + - 'spec/unit/client/redis/apns/notification_spec.rb' + - 'spec/unit/client/redis/apns2/notification_spec.rb' + - 'spec/unit/client/redis/wns/raw_notification_spec.rb' + +# Offense count: 37 +# This cop supports unsafe autocorrection (--autocorrect-all). +# Configuration parameters: Strict, EnforcedStyle, AllowedExplicitMatchers. +# SupportedStyles: inflected, explicit +RSpec/PredicateMatcher: + Exclude: + - 'spec/unit/client/active_record/apns/notification_spec.rb' + - 'spec/unit/client/active_record/apns2/notification_spec.rb' + - 'spec/unit/client/active_record/apnsp8/notification_spec.rb' + - 'spec/unit/client/redis/apns/notification_spec.rb' + - 'spec/unit/client/redis/apns2/notification_spec.rb' + - 'spec/unit/client/redis/apnsp8/notification_spec.rb' + - 'spec/unit/client/shared/apns/feedback.rb' + - 'spec/unit/client/shared/apns/notification.rb' + - 'spec/unit/client/shared/fcm/notification.rb' + - 'spec/unit/daemon/shared/store.rb' + +# Offense count: 4 +# This cop supports unsafe autocorrection (--autocorrect-all). +RSpec/ReceiveMessages: + Exclude: + - 'spec/functional/fcm_spec.rb' + - 'spec/functional/retry_spec.rb' + +# Offense count: 2 +RSpec/RepeatedExampleGroupDescription: + Exclude: + - 'spec/unit/daemon/batch_spec.rb' + +# Offense count: 8 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyle. +# SupportedStyles: and_return, block +RSpec/ReturnFromStub: + Exclude: + - 'spec/functional/cli_spec.rb' + - 'spec/spec_helper.rb' + - 'spec/unit/client/active_record/wns/raw_notification_spec.rb' + - 'spec/unit/client/redis/wns/raw_notification_spec.rb' + - 'spec/unit/client/shared/wns/raw_notification.rb' + - 'spec/unit/daemon_spec.rb' + +# Offense count: 6 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: AutoCorrect. +RSpec/ScatteredLet: + Exclude: + - 'spec/functional/embed_spec.rb' + +# Offense count: 26 +# Configuration parameters: Include, CustomTransform, IgnoreMethods, IgnoreMetadata. +# Include: **/*_spec.rb +RSpec/SpecFilePathFormat: + Enabled: false + +# Offense count: 26 +RSpec/StubbedMock: + Exclude: + - 'spec/functional/adm_spec.rb' + - 'spec/functional/apns2_spec.rb' + - 'spec/functional/cli_spec.rb' + - 'spec/functional/fcm_spec.rb' + - 'spec/unit/daemon/adm/delivery_spec.rb' + - 'spec/unit/daemon/app_runner_spec.rb' + - 'spec/unit/daemon/dispatcher/http_spec.rb' + - 'spec/unit/daemon/service_config_methods_spec.rb' + - 'spec/unit/embed_spec.rb' + - 'spec/unit/logger_spec.rb' + +# Offense count: 14 +RSpec/SubjectStub: + Exclude: + - 'spec/functional/cli_spec.rb' + - 'spec/unit/daemon/pushy/delivery_spec.rb' + - 'spec/unit/daemon/webpush/delivery_spec.rb' + +# Offense count: 12 +# This cop supports unsafe autocorrection (--autocorrect-all). +# Configuration parameters: . +# SupportedStyles: constant, string +RSpec/VerifiedDoubleReference: + EnforcedStyle: string + +# Offense count: 99 +# Configuration parameters: IgnoreNameless, IgnoreSymbolicNames. +RSpec/VerifiedDoubles: + Enabled: false + +# Offense count: 3 +# This cop supports unsafe autocorrection (--autocorrect-all). +Rails/ApplicationRecord: + Exclude: + - 'lib/rpush/client/active_record/apns/feedback.rb' + - 'lib/rpush/client/active_record/app.rb' + - 'lib/rpush/client/active_record/notification.rb' + +# Offense count: 2 +# This cop supports unsafe autocorrection (--autocorrect-all). +# Configuration parameters: NilOrEmpty, NotPresent, UnlessPresent. +Rails/Blank: + Exclude: + - 'lib/rpush/daemon/apns2/delivery.rb' + - 'lib/rpush/daemon/apnsp8/delivery.rb' + +# Offense count: 4 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforceForPrefixed. +Rails/Delegate: + Exclude: + - 'spec/unit/daemon/adm/delivery_spec.rb' + - 'spec/unit/daemon/fcm/delivery_spec.rb' + - 'spec/unit/daemon/wns/delivery_spec.rb' + - 'spec/unit/daemon/wpns/delivery_spec.rb' + +# Offense count: 4 +# Configuration parameters: Include. +# Include: app/**/*.rb, config/**/*.rb, lib/**/*.rb +Rails/Exit: + Exclude: + - 'lib/rpush/cli.rb' + - 'lib/rpush/daemon.rb' + +# Offense count: 3 +# This cop supports unsafe autocorrection (--autocorrect-all). +Rails/NegateInclude: + Exclude: + - 'lib/rpush/daemon/adm/delivery.rb' + - 'lib/rpush/reflection_collection.rb' + +# Offense count: 21 +# This cop supports unsafe autocorrection (--autocorrect-all). +# Configuration parameters: Include. +# Include: app/**/*.rb, config/**/*.rb, db/**/*.rb, lib/**/*.rb +Rails/Output: + Exclude: + - 'lib/rpush/cli.rb' + - 'lib/rpush/client/redis.rb' + - 'lib/rpush/daemon.rb' + - 'lib/rpush/daemon/app_runner.rb' + +# Offense count: 2 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: NotNilAndNotEmpty, NotBlank, UnlessBlank. +Rails/Present: + Exclude: + - 'lib/rpush/daemon.rb' + +# Offense count: 2 +# This cop supports unsafe autocorrection (--autocorrect-all). +# Configuration parameters: Include. +# Include: **/Rakefile, **/*.rake +Rails/RakeEnvironment: + Exclude: + - 'lib/tasks/test.rake' + +# Offense count: 5 +# Configuration parameters: ForbiddenMethods, AllowedMethods. +# ForbiddenMethods: decrement!, decrement_counter, increment!, increment_counter, insert, insert!, insert_all, insert_all!, toggle!, touch, touch_all, update_all, update_attribute, update_column, update_columns, update_counters, upsert, upsert_all +Rails/SkipsModelValidations: + Exclude: + - 'lib/rpush/daemon/store/active_record.rb' + - 'spec/unit/daemon/fcm/delivery_spec.rb' + +# Offense count: 82 +# This cop supports unsafe autocorrection (--autocorrect-all). +# Configuration parameters: EnforcedStyle. +# SupportedStyles: strict, flexible +Rails/TimeZone: + Enabled: false + +# Offense count: 2 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyle. +# SupportedStyles: to_fs, to_formatted_s +Rails/ToFormattedS: + Exclude: + - 'lib/rpush/logger.rb' + - 'spec/unit/logger_spec.rb' + +# Offense count: 2 +# This cop supports safe autocorrection (--autocorrect). +Rails/WhereNot: + Exclude: + - 'spec/unit/daemon/wns/delivery_spec.rb' + - 'spec/unit/daemon/wpns/delivery_spec.rb' + # Offense count: 1 # This cop supports unsafe autocorrection (--autocorrect-all). Security/JSONLoad: @@ -708,6 +1158,7 @@ Style/FileWrite: # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: MaxUnannotatedPlaceholdersAllowed, AllowedMethods, AllowedPatterns. # SupportedStyles: annotated, template, unannotated +# AllowedMethods: redirect Style/FormatStringToken: EnforcedStyle: unannotated @@ -819,7 +1270,6 @@ Style/NegatedIfElseCondition: # SupportedStyles: predicate, comparison Style/NumericPredicate: Exclude: - - 'spec/**/*' - 'lib/rpush/daemon.rb' - 'lib/rpush/daemon/apnsp8/delivery.rb' - 'lib/rpush/daemon/app_runner.rb' diff --git a/Gemfile.lock b/Gemfile.lock index f2453b60c..57b3a9809 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -186,6 +186,15 @@ GEM rubocop-performance (1.21.1) rubocop (>= 1.48.1, < 2.0) rubocop-ast (>= 1.31.1, < 2.0) + rubocop-rails (2.26.0) + activesupport (>= 4.2.0) + rack (>= 1.1) + rubocop (>= 1.52.0, < 2.0) + rubocop-ast (>= 1.31.1, < 2.0) + rubocop-rake (0.6.0) + rubocop (~> 1.0) + rubocop-rspec (3.0.4) + rubocop (~> 1.61) ruby-progressbar (1.13.0) signet (0.19.0) addressable (~> 2.8) @@ -231,6 +240,9 @@ DEPENDENCIES rspec rubocop (~> 1.66) rubocop-performance + rubocop-rails + rubocop-rake + rubocop-rspec simplecov sqlite3 stackprof diff --git a/rpush.gemspec b/rpush.gemspec index 7643032ec..d124994a1 100644 --- a/rpush.gemspec +++ b/rpush.gemspec @@ -57,6 +57,9 @@ Gem::Specification.new do |s| s.add_development_dependency 'simplecov' s.add_development_dependency 'rubocop', '~> 1.66' s.add_development_dependency 'rubocop-performance' + s.add_development_dependency 'rubocop-rake' + s.add_development_dependency 'rubocop-rspec' + s.add_development_dependency 'rubocop-rails' s.add_development_dependency 'byebug' s.add_development_dependency 'pg' From 373376192245dc38f0120ef232fec3001c4ce8fc Mon Sep 17 00:00:00 2001 From: Ben Langfeld Date: Mon, 9 Sep 2024 11:30:51 -0300 Subject: [PATCH 14/32] Release version 9.0.0 (#694) --- CHANGELOG.md | 8 +++++++- Gemfile.lock | 2 +- lib/rpush/version.rb | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b3f1a5487..7896ab23a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ **Merged pull requests:** +[Full Changelog](https://github.com/rpush/rpush/compare/v9.0.0...HEAD) + +## [v9.0.0](https://github.com/rpush/rpush/tree/v9.0.0) (2024-09-09) + +**Merged pull requests:** + * Support for Ruby 3.2 & 3.3 [\#679](https://github.com/rpush/rpush/pull/679) ([benlangfeld](https://github.com/benlangfeld)) **Breaking:** @@ -12,7 +18,7 @@ * Removed legacy GCM implementation since this was shut down by Google in August 2024 and replaced by FCM (supported in RPush 8.0.0) [\#688](https://github.com/rpush/rpush/pull/688) ([benlangfeld](https://github.com/benlangfeld)) * Drop support for Ruby 2.x [\#672](https://github.com/rpush/rpush/pull/672) ([benlangfeld](https://github.com/benlangfeld)) -[Full Changelog](https://github.com/rpush/rpush/compare/v8.0.0...HEAD) +[Full Changelog](https://github.com/rpush/rpush/compare/v8.0.0...v9.0.0) ## [v8.0.0](https://github.com/rpush/rpush/tree/v8.0.0) (2024-09-06) diff --git a/Gemfile.lock b/Gemfile.lock index 57b3a9809..9355ca293 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - rpush (8.0.0) + rpush (9.0.0) activesupport (>= 6.0, < 7.1.0) googleauth jwt (>= 1.5.6) diff --git a/lib/rpush/version.rb b/lib/rpush/version.rb index 02ce7b8c5..74d65398c 100644 --- a/lib/rpush/version.rb +++ b/lib/rpush/version.rb @@ -1,6 +1,6 @@ module Rpush module VERSION - MAJOR = 8 + MAJOR = 9 MINOR = 0 TINY = 0 PRE = nil From ab9301e09be86d116f39cfe7e63e04f313a4367c Mon Sep 17 00:00:00 2001 From: Wout Ceulemans Date: Wed, 18 Sep 2024 23:36:26 +0200 Subject: [PATCH 15/32] feat: fcm add iOS badge support (#704) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #703 ### Background on the change FSM messages should be consistent with: https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages#Message These messages support a "apns" key: https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages#ApnsConfig Which in its turn has a "payload" key, described as: > APNs payload as a JSON object, including both aps dictionary and custom payload. See [Payload Key Reference](https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/generating_a_remote_notification). If present, it overrides [google.firebase.fcm.v1.Notification.title](https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages#Notification.FIELDS.title) and [google.firebase.fcm.v1.Notification.body](https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages#Notification.FIELDS.body). The Payload Key Reference contains information on supported keys, including a "badge": > The number to display in a badge on your app’s icon. Specify 0 to remove the current badge, if any. --- CHANGELOG.md | 2 ++ lib/rpush/client/active_model/fcm/notification.rb | 1 + spec/unit/client/shared/fcm/notification.rb | 7 ++++++- 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7896ab23a..a82724e17 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ **Merged pull requests:** +* Add support for FSM iOS badges [\#704](https://github.com/rpush/rpush/pull/704) ([WoutDev](https://github.com/WoutDev)) + [Full Changelog](https://github.com/rpush/rpush/compare/v9.0.0...HEAD) ## [v9.0.0](https://github.com/rpush/rpush/tree/v9.0.0) (2024-09-09) diff --git a/lib/rpush/client/active_model/fcm/notification.rb b/lib/rpush/client/active_model/fcm/notification.rb index 876969238..c4c821df5 100644 --- a/lib/rpush/client/active_model/fcm/notification.rb +++ b/lib/rpush/client/active_model/fcm/notification.rb @@ -77,6 +77,7 @@ def apns_config aps['mutable-content'] = 1 if mutable_content aps['content-available'] = 1 if content_available aps['sound'] = 'default' if sound == 'default' + aps['badge'] = badge if badge json['payload']['aps'] = aps diff --git a/spec/unit/client/shared/fcm/notification.rb b/spec/unit/client/shared/fcm/notification.rb index 55109f862..6d4162fee 100644 --- a/spec/unit/client/shared/fcm/notification.rb +++ b/spec/unit/client/shared/fcm/notification.rb @@ -26,7 +26,7 @@ expect(notification.as_json['message']['notification']).to eq({"title"=>"title", "body"=>"body"}) end - it "moves notification keys to the correcdt location" do + it "moves notification keys to the correct location" do notification.app = app notification.device_token = "valid" notification.notification = { "title" => "valid", "body" => "valid", "color" => "valid for android" } @@ -82,6 +82,11 @@ expect(notification.as_json['message']).to have_key 'notification' end + it 'includes the badge if defined' do + notification.badge = 3 + expect(notification.as_json['message']['apns']['payload']['aps']['badge']).to eq(3) + end + it 'excludes the notification payload if undefined' do expect(notification.as_json['message']).not_to have_key 'notification' end From 12d92519cb090f3b37d654ea206a3f107eab319d Mon Sep 17 00:00:00 2001 From: Ben Langfeld Date: Thu, 19 Sep 2024 16:16:43 -0300 Subject: [PATCH 16/32] Support Rails 7.1 (#675) --- .github/workflows/test.yml | 2 +- Appraisals | 8 +++++++ CHANGELOG.md | 1 + Gemfile.lock | 2 +- gemfiles/rails_7.1.gemfile | 11 ++++++++++ .../client/active_record/notification.rb | 2 ++ rpush.gemspec | 2 +- spec/spec_helper.rb | 22 +++++++++++++------ 8 files changed, 40 insertions(+), 10 deletions(-) create mode 100644 gemfiles/rails_7.1.gemfile diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9ea8cc23f..0ee37f0e5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -52,7 +52,7 @@ jobs: strategy: fail-fast: false matrix: - gemfile: ['rails_6.0', 'rails_6.1', 'rails_7.0'] + gemfile: ['rails_6.0', 'rails_6.1', 'rails_7.0', 'rails_7.1'] ruby: ['3.0', '3.1', '3.2', '3.3'] diff --git a/Appraisals b/Appraisals index 357705d6f..20eb9f6c1 100644 --- a/Appraisals +++ b/Appraisals @@ -33,3 +33,11 @@ appraise "rails-7.0" do gem "rails", "~> 7.0.0" end end + +appraise "rails-7.1" do + gem "activesupport", "~> 7.1.0" + + group :development do + gem "rails", "~> 7.1.0" + end +end diff --git a/CHANGELOG.md b/CHANGELOG.md index a82724e17..417bfb1f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ **Merged pull requests:** * Add support for FSM iOS badges [\#704](https://github.com/rpush/rpush/pull/704) ([WoutDev](https://github.com/WoutDev)) +* Support for Rails 7.1 [\#675](https://github.com/rpush/rpush/pull/675) ([benlangfeld](https://github.com/benlangfeld)) [Full Changelog](https://github.com/rpush/rpush/compare/v9.0.0...HEAD) diff --git a/Gemfile.lock b/Gemfile.lock index 9355ca293..e1b592acb 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -2,7 +2,7 @@ PATH remote: . specs: rpush (9.0.0) - activesupport (>= 6.0, < 7.1.0) + activesupport (>= 6.0, < 7.2.0, != 7.1.4) googleauth jwt (>= 1.5.6) multi_json (~> 1.0) diff --git a/gemfiles/rails_7.1.gemfile b/gemfiles/rails_7.1.gemfile new file mode 100644 index 000000000..462cdd699 --- /dev/null +++ b/gemfiles/rails_7.1.gemfile @@ -0,0 +1,11 @@ +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "activesupport", "~> 7.1.0" + +group :development do + gem "rails", "~> 7.1.0" +end + +gemspec path: "../" diff --git a/lib/rpush/client/active_record/notification.rb b/lib/rpush/client/active_record/notification.rb index 666c2fc7b..eff86b70f 100644 --- a/lib/rpush/client/active_record/notification.rb +++ b/lib/rpush/client/active_record/notification.rb @@ -7,6 +7,8 @@ class Notification < ::ActiveRecord::Base self.table_name = 'rpush_notifications' + self.default_column_serializer = YAML if respond_to?(:default_column_serializer) + serialize :registration_ids serialize :url_args diff --git a/rpush.gemspec b/rpush.gemspec index d124994a1..68a6dd32a 100644 --- a/rpush.gemspec +++ b/rpush.gemspec @@ -36,7 +36,7 @@ Gem::Specification.new do |s| s.add_runtime_dependency 'net-http-persistent' s.add_runtime_dependency 'net-http2', '~> 0.18', '>= 0.18.3' s.add_runtime_dependency 'jwt', '>= 1.5.6' - s.add_runtime_dependency 'activesupport', '>= 6.0', '< 7.1.0' + s.add_runtime_dependency 'activesupport', '>= 6.0', '!= 7.1.4', '< 7.2.0' # https://github.com/rails/rails/issues/52820 s.add_runtime_dependency 'thor', ['>= 0.18.1', '< 2.0'] s.add_runtime_dependency 'railties' s.add_runtime_dependency 'rainbow' diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index d015ff036..aeac01eb5 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -17,13 +17,6 @@ def client require 'timecop' require 'activerecord-jdbc-adapter' if defined? JRUBY_VERSION -require 'rpush' -require 'rpush/daemon' -require 'rpush/client/redis' -require 'rpush/client/active_record' -require 'rpush/daemon/store/active_record' -require 'rpush/daemon/store/redis' - def active_record? client == :active_record end @@ -32,6 +25,21 @@ def redis? client == :redis end +if active_record? + require 'active_record' + if ActiveRecord::Base.respond_to?(:default_column_serializer) + # New default in Rails 7.1: https://github.com/rails/rails/pull/47422 + ActiveRecord::Base.default_column_serializer = nil + end +end + +require 'rpush' +require 'rpush/daemon' +require 'rpush/client/redis' +require 'rpush/client/active_record' +require 'rpush/daemon/store/active_record' +require 'rpush/daemon/store/redis' + require 'support/active_record_setup' if active_record? RPUSH_ROOT = '/tmp/rails_root' From 54c9e88160094523a02e9c0dab1e07ac0416aeda Mon Sep 17 00:00:00 2001 From: Ben Langfeld Date: Tue, 24 Sep 2024 11:23:50 -0300 Subject: [PATCH 17/32] Enable running tests on sqlite3 (#705) --- CHANGELOG.md | 1 + Gemfile.lock | 4 ++-- rpush.gemspec | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 417bfb1f4..8b71208ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ * Add support for FSM iOS badges [\#704](https://github.com/rpush/rpush/pull/704) ([WoutDev](https://github.com/WoutDev)) * Support for Rails 7.1 [\#675](https://github.com/rpush/rpush/pull/675) ([benlangfeld](https://github.com/benlangfeld)) +* Enable running tests on sqlite3 [\#705](https://github.com/rpush/rpush/pull/705) ([benlangfeld](https://github.com/benlangfeld)) [Full Changelog](https://github.com/rpush/rpush/compare/v9.0.0...HEAD) diff --git a/Gemfile.lock b/Gemfile.lock index e1b592acb..4c74a9c4c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -207,7 +207,7 @@ GEM simplecov_json_formatter (~> 0.1) simplecov-html (0.12.3) simplecov_json_formatter (0.1.4) - sqlite3 (2.0.2) + sqlite3 (1.7.3) mini_portile2 (~> 2.8.0) stackprof (0.2.17) stringio (3.1.1) @@ -244,7 +244,7 @@ DEPENDENCIES rubocop-rake rubocop-rspec simplecov - sqlite3 + sqlite3 (~> 1.4) stackprof timecop diff --git a/rpush.gemspec b/rpush.gemspec index 68a6dd32a..9d57cc4da 100644 --- a/rpush.gemspec +++ b/rpush.gemspec @@ -64,5 +64,5 @@ Gem::Specification.new do |s| s.add_development_dependency 'pg' s.add_development_dependency 'mysql2' - s.add_development_dependency 'sqlite3' + s.add_development_dependency 'sqlite3', '~> 1.4' end From fe0decac1dcb2ef983773e3d8ceefb91bc455093 Mon Sep 17 00:00:00 2001 From: Ben Langfeld Date: Tue, 24 Sep 2024 11:48:40 -0300 Subject: [PATCH 18/32] Release 9.1.0 (#707) --- CHANGELOG.md | 6 +++++- lib/rpush/version.rb | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b71208ba..9399d3187 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,13 +2,17 @@ ## [Unreleased](https://github.com/rpush/rpush/tree/HEAD) +[Full Changelog](https://github.com/rpush/rpush/compare/v9.1.0...HEAD) + +## [v9.1.0](https://github.com/rpush/rpush/tree/v9.1.0) (2024-09-24) + **Merged pull requests:** * Add support for FSM iOS badges [\#704](https://github.com/rpush/rpush/pull/704) ([WoutDev](https://github.com/WoutDev)) * Support for Rails 7.1 [\#675](https://github.com/rpush/rpush/pull/675) ([benlangfeld](https://github.com/benlangfeld)) * Enable running tests on sqlite3 [\#705](https://github.com/rpush/rpush/pull/705) ([benlangfeld](https://github.com/benlangfeld)) -[Full Changelog](https://github.com/rpush/rpush/compare/v9.0.0...HEAD) +[Full Changelog](https://github.com/rpush/rpush/compare/v9.0.0...v9.1.0) ## [v9.0.0](https://github.com/rpush/rpush/tree/v9.0.0) (2024-09-09) diff --git a/lib/rpush/version.rb b/lib/rpush/version.rb index 74d65398c..8884a7bf6 100644 --- a/lib/rpush/version.rb +++ b/lib/rpush/version.rb @@ -1,7 +1,7 @@ module Rpush module VERSION MAJOR = 9 - MINOR = 0 + MINOR = 1 TINY = 0 PRE = nil From de06535cc55a24a485e7030f6e634983d297925c Mon Sep 17 00:00:00 2001 From: ben Date: Sat, 23 Nov 2024 14:13:56 +0100 Subject: [PATCH 19/32] Update FCM steps in README.md (#711) Had some struggles setting up FCM in my repository with the given documentation, so I'm suggesting some extra steps people might look over on their initial tests. From the documentation it's not immediately clear that `data` is not where the title and body of a notification go. Leaving `notification` blank causes rpush to return a success, but nothing will actually arrive on the device. The project ID also might have changed labels overtime, as setting the "project ID" as my `firebase_project_id` instead of the "project number" also prevents notifications from showing up. --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 197aa8bd5..7f9b4bef0 100644 --- a/README.md +++ b/README.md @@ -128,7 +128,7 @@ The app `environment` for any Apns* option is "development" for XCode installs, #### Firebase Cloud Messaging You will need two params to make use of FCM via Rpush. -- `firebase_project_id` - The `Project ID` in your Firebase Project Settings +- `firebase_project_id` - The `Project number` in your Firebase Project Settings - `json_key` - The JSON key file for a service account with the `Firebase Admin SDK Administrator Service Agent` role. Create service account in the google cloud account attached to your firebase account: @@ -151,6 +151,7 @@ fcm_app.save! n = Rpush::Fcm::Notification.new n.app = Rpush::Fcm::App.where(name: "fcm_app").first n.device_token = device_token # Note that device_token is used here instead of registration_ids +n.notification = { title: "push title", body: "hi mom!" } # either title or body needs to be set, or nothing goes through n.data = {}.transform_values(&:to_s) # All values going in here have to be strings, if you have anything else - nothing goes through n.save! ``` From 152a36a42810b11146e76c5cfbf3be739e83f5af Mon Sep 17 00:00:00 2001 From: Ben Langfeld Date: Wed, 27 Nov 2024 16:30:50 +0000 Subject: [PATCH 20/32] Stop running Rubocop in CI for now (#719) Pending https://github.com/rpush/rpush/pull/713. Necessary to avoid blocking critical work like https://github.com/rpush/rpush/pull/706. --- .github/workflows/test.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0ee37f0e5..c464039ca 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -87,9 +87,6 @@ jobs: POSTGRES_PORT: 5432 CLIENT: ${{ matrix.client }} - - name: Run rubocop - run: bundle exec rubocop - tests: runs-on: ubuntu-latest needs: test From 83812d4ac31a762a1d3e18d9300050a82dacfe4c Mon Sep 17 00:00:00 2001 From: Ben Langfeld Date: Thu, 28 Nov 2024 12:48:20 -0300 Subject: [PATCH 21/32] Support Rails 7.2 (#706) --- .github/workflows/test.yml | 6 +++++- Appraisals | 8 ++++++++ CHANGELOG.md | 2 ++ Gemfile.lock | 2 +- gemfiles/rails_7.2.gemfile | 11 +++++++++++ rpush.gemspec | 2 +- 6 files changed, 28 insertions(+), 3 deletions(-) create mode 100644 gemfiles/rails_7.2.gemfile diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c464039ca..cc522afd2 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -52,12 +52,16 @@ jobs: strategy: fail-fast: false matrix: - gemfile: ['rails_6.0', 'rails_6.1', 'rails_7.0', 'rails_7.1'] + gemfile: ['rails_6.0', 'rails_6.1', 'rails_7.0', 'rails_7.1', 'rails_7.2'] ruby: ['3.0', '3.1', '3.2', '3.3'] client: ['active_record', 'redis'] + exclude: + - ruby: '3.0' + gemfile: 'rails_7.2' + env: # $BUNDLE_GEMFILE must be set at the job level, so it is set for all steps BUNDLE_GEMFILE: gemfiles/${{ matrix.gemfile }}.gemfile diff --git a/Appraisals b/Appraisals index 20eb9f6c1..9931f8187 100644 --- a/Appraisals +++ b/Appraisals @@ -41,3 +41,11 @@ appraise "rails-7.1" do gem "rails", "~> 7.1.0" end end + +appraise "rails-7.2" do + gem "activesupport", "~> 7.2.0" + + group :development do + gem "rails", "~> 7.2.0" + end +end diff --git a/CHANGELOG.md b/CHANGELOG.md index 9399d3187..a3ec64fb8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## [Unreleased](https://github.com/rpush/rpush/tree/HEAD) +* Support for Rails 7.2 [\#706](https://github.com/rpush/rpush/pull/706) ([benlangfeld](https://github.com/benlangfeld)) + [Full Changelog](https://github.com/rpush/rpush/compare/v9.1.0...HEAD) ## [v9.1.0](https://github.com/rpush/rpush/tree/v9.1.0) (2024-09-24) diff --git a/Gemfile.lock b/Gemfile.lock index 4c74a9c4c..411e72a38 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -2,7 +2,7 @@ PATH remote: . specs: rpush (9.0.0) - activesupport (>= 6.0, < 7.2.0, != 7.1.4) + activesupport (>= 6.0, != 7.2.1, != 7.2.0, != 7.1.4) googleauth jwt (>= 1.5.6) multi_json (~> 1.0) diff --git a/gemfiles/rails_7.2.gemfile b/gemfiles/rails_7.2.gemfile new file mode 100644 index 000000000..b68f3f8ac --- /dev/null +++ b/gemfiles/rails_7.2.gemfile @@ -0,0 +1,11 @@ +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "activesupport", "~> 7.2.0" + +group :development do + gem "rails", "~> 7.2.0" +end + +gemspec path: "../" diff --git a/rpush.gemspec b/rpush.gemspec index 9d57cc4da..fdc736498 100644 --- a/rpush.gemspec +++ b/rpush.gemspec @@ -36,7 +36,7 @@ Gem::Specification.new do |s| s.add_runtime_dependency 'net-http-persistent' s.add_runtime_dependency 'net-http2', '~> 0.18', '>= 0.18.3' s.add_runtime_dependency 'jwt', '>= 1.5.6' - s.add_runtime_dependency 'activesupport', '>= 6.0', '!= 7.1.4', '< 7.2.0' # https://github.com/rails/rails/issues/52820 + s.add_runtime_dependency 'activesupport', '>= 6.0', '!= 7.1.4', '!= 7.2.0', '!= 7.2.1' # https://github.com/rails/rails/issues/52820 s.add_runtime_dependency 'thor', ['>= 0.18.1', '< 2.0'] s.add_runtime_dependency 'railties' s.add_runtime_dependency 'rainbow' From a436eb4888dd47e14163d480fb93b3078015308b Mon Sep 17 00:00:00 2001 From: Jimmy Reichley Date: Thu, 28 Nov 2024 10:52:51 -0500 Subject: [PATCH 22/32] Remove duplicate block (#717) Only difference was in the comment (possibly typo?) Co-authored-by: Ben Langfeld --- lib/generators/templates/rpush.rb | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lib/generators/templates/rpush.rb b/lib/generators/templates/rpush.rb index 0fd2464a3..72d534ae1 100644 --- a/lib/generators/templates/rpush.rb +++ b/lib/generators/templates/rpush.rb @@ -85,11 +85,6 @@ # on.fcm_failed_to_recipient do |notification, error| # end - # Called when the FCM returns a failure that indicates an invalid device token. - # You will need to delete the device token from your records. - # on.fcm_invalid_device_token do |app, error, device_token| - # end - # Called for each recipient which successfully receives a notification. This # can occur more than once for the same notification when there are multiple # recipients. From 4e9ca7326c27562510ba364216b585f2f295cd69 Mon Sep 17 00:00:00 2001 From: Erick Guan <297343+erickguan@users.noreply.github.com> Date: Thu, 28 Nov 2024 17:01:48 +0100 Subject: [PATCH 23/32] Release gem on GitHub (#712) Fixes #692 This should help release the gem on GitHub. When release, you can dispatch the release workflow on GitHub Actions. Choose "Release" workflow, and run it. The workflow will: 1. Build and push the gem with the version defined in `rpush/version.rb`. 2. Tag the repository with `v`. e.g., `v9.2.0`. 3. GitHub will create a release with release notes. Because the release workflow uses an action called `rubygems/release-gem`, to publish the gem via GitHub, the gem owners must configure the ["Trusted publishing"](https://guides.rubygems.org/trusted-publishing/). You must [add GitHub as a trusted platform](https://guides.rubygems.org/trusted-publishing/adding-a-publisher/). ------ Other changes: 1. I also bumped the Ruby in the development environment to 3.2 with gems including `pg`, `mysql2` to resolve a few build problems on my development environment. 2. I also use Gemfile for development dependencies as Rubocop suggests. 3. I updated `tests.yml` to run the latest Ubuntu. This can be further improved to use GitHub repo's ruleset to set certain or all tests must pass for PR to be able to merge. Happy to help on this area too. --------- Co-authored-by: Ben Langfeld --- .github/workflows/release.yml | 42 +++++++++++++++++++++++++++++++++++ .github/workflows/test.yml | 4 ++-- .ruby-version | 2 +- Gemfile.lock | 19 ++++++++-------- 4 files changed, 55 insertions(+), 12 deletions(-) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000..82322d14d --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,42 @@ +name: Release + +on: + workflow_dispatch: # allow repo collaborators to publish gem + +permissions: + contents: write # required for `rake release` to push the release tag + id-token: write # required for workflow to publish gem + +jobs: + release: + if: github.repository == 'rpush/rpush' && github.ref == 'refs/heads/master' + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + # required to run `rake release`. + - name: Setup Ruby and install dependencies + uses: ruby/setup-ruby@v1 + with: + # setup-ruby implicitly uses .ruby-version + bundler-cache: true + + # Run `rake release` to create git tag and push to repository based on `lib/rpush/version.rb`. + # Then publish the new gem via trusted publishing + # Read more on https://guides.rubygems.org/trusted-publishing/releasing-gems/ + - name: Publish gem + uses: rubygems/release-gem@v1 + + - name: Create GitHub Release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + VERSION="v$(bundle exec ruby -e 'puts Rpush::VERSION.to_s.chomp')" + + gh release create \ + $VERSION \ + --verify-tag \ + --generate-notes \ + --latest diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index cc522afd2..938484b3e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -22,7 +22,7 @@ env: jobs: test: - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest services: postgres: @@ -66,7 +66,7 @@ jobs: BUNDLE_GEMFILE: gemfiles/${{ matrix.gemfile }}.gemfile steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Set up Ruby uses: ruby/setup-ruby@v1 diff --git a/.ruby-version b/.ruby-version index fd2a01863..351227fca 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -3.1.0 +3.2.4 diff --git a/Gemfile.lock b/Gemfile.lock index 411e72a38..43c5473f3 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - rpush (9.0.0) + rpush (9.1.0) activesupport (>= 6.0, != 7.2.1, != 7.2.0, != 7.1.4) googleauth jwt (>= 1.5.6) @@ -66,14 +66,15 @@ GEM diff-lcs (1.5.1) docile (1.4.0) erubi (1.13.0) - faraday (2.11.0) + faraday (2.12.0) faraday-net_http (>= 2.0, < 3.4) + json logger faraday-net_http (3.3.0) net-http - google-cloud-env (2.2.0) + google-cloud-env (2.2.1) faraday (>= 1.0, < 3.a) - googleauth (1.11.0) + googleauth (1.11.2) faraday (>= 1.0, < 3.a) google-cloud-env (~> 2.1) jwt (>= 1.4, < 3.0) @@ -89,11 +90,11 @@ GEM rdoc (>= 4.0.0) reline (>= 0.4.2) json (2.7.2) - jwt (2.8.2) + jwt (2.9.3) base64 language_server-protocol (3.17.0.3) logger (1.6.1) - loofah (2.22.0) + loofah (2.23.1) crass (~> 1.0.2) nokogiri (>= 1.12.0) method_source (1.1.0) @@ -110,7 +111,7 @@ GEM mysql2 (0.5.6) net-http (0.4.1) uri - net-http-persistent (4.0.2) + net-http-persistent (4.0.4) connection_pool (~> 2.2) net-http2 (0.18.5) http-2 (~> 0.11) @@ -123,7 +124,7 @@ GEM parser (3.3.5.0) ast (~> 2.4.1) racc - pg (1.2.3) + pg (1.5.9) psych (5.1.2) stringio public_suffix (6.0.1) @@ -220,7 +221,7 @@ GEM web-push (3.0.1) jwt (~> 2.0) openssl (~> 3.0) - zeitwerk (2.6.18) + zeitwerk (2.7.1) PLATFORMS ruby From a299e247f4b8efa353a022b0432260b9768be887 Mon Sep 17 00:00:00 2001 From: Ben Langfeld Date: Thu, 28 Nov 2024 13:09:46 -0300 Subject: [PATCH 24/32] Release 9.2.0 (#721) --- CHANGELOG.md | 6 +++++- Gemfile.lock | 2 +- lib/rpush/version.rb | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a3ec64fb8..637941a85 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,9 +2,13 @@ ## [Unreleased](https://github.com/rpush/rpush/tree/HEAD) +[Full Changelog](https://github.com/rpush/rpush/compare/v9.2.0...HEAD) + +## [v9.2.0](https://github.com/rpush/rpush/tree/v9.2.0) (2024-11-28) + * Support for Rails 7.2 [\#706](https://github.com/rpush/rpush/pull/706) ([benlangfeld](https://github.com/benlangfeld)) -[Full Changelog](https://github.com/rpush/rpush/compare/v9.1.0...HEAD) +[Full Changelog](https://github.com/rpush/rpush/compare/v9.1.0...v9.2.0) ## [v9.1.0](https://github.com/rpush/rpush/tree/v9.1.0) (2024-09-24) diff --git a/Gemfile.lock b/Gemfile.lock index 43c5473f3..bd819ab90 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - rpush (9.1.0) + rpush (9.2.0) activesupport (>= 6.0, != 7.2.1, != 7.2.0, != 7.1.4) googleauth jwt (>= 1.5.6) diff --git a/lib/rpush/version.rb b/lib/rpush/version.rb index 8884a7bf6..8ead7e7f4 100644 --- a/lib/rpush/version.rb +++ b/lib/rpush/version.rb @@ -1,7 +1,7 @@ module Rpush module VERSION MAJOR = 9 - MINOR = 1 + MINOR = 2 TINY = 0 PRE = nil From 3c812dc24a86e63de3a0b7e8cb04c25aee2e312c Mon Sep 17 00:00:00 2001 From: Ben Langfeld Date: Thu, 28 Nov 2024 13:44:38 -0300 Subject: [PATCH 25/32] Upgraded Bundler --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index bd819ab90..8b54b3d56 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -250,4 +250,4 @@ DEPENDENCIES timecop BUNDLED WITH - 2.3.5 + 2.5.23 From ea5083944326a40688205b9dc32f80f9345f10bd Mon Sep 17 00:00:00 2001 From: Alex Katkova Date: Thu, 18 Apr 2019 16:10:18 -0700 Subject: [PATCH 26/32] Use ActiveRecord to fetch Apps instead of Redis. --- lib/rpush/daemon/store/redis.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/rpush/daemon/store/redis.rb b/lib/rpush/daemon/store/redis.rb index f40a9f1e4..278ceb44a 100644 --- a/lib/rpush/daemon/store/redis.rb +++ b/lib/rpush/daemon/store/redis.rb @@ -5,11 +5,11 @@ class Redis DEFAULT_MARK_OPTIONS = { persist: true } def app(app_id) - Rpush::Client::Redis::App.find(app_id) + Rpush::Client::ActiveRecord::App.find(app_id) end def all_apps - Rpush::Client::Redis::App.all + Rpush::Client::ActiveRecord::App.all end def deliverable_notifications(limit) From 276edacc2af952612dd6460891153411ce86d3c6 Mon Sep 17 00:00:00 2001 From: Alex Katkova Date: Thu, 6 Feb 2020 14:26:09 -0800 Subject: [PATCH 27/32] TA-28669. Stop saving all rpush IDs to set. --- lib/rpush/client/redis/notification.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/rpush/client/redis/notification.rb b/lib/rpush/client/redis/notification.rb index 931f91775..856cc8bf7 100644 --- a/lib/rpush/client/redis/notification.rb +++ b/lib/rpush/client/redis/notification.rb @@ -6,6 +6,8 @@ class Notification include Modis::Model include Rpush::Client::ActiveModel::Notification + enable_all_index false # prevent creation of massive rpush:notifications:all set + after_create :register_notification self.namespace = 'notifications' From ebf4371740ca8a78d73d754186baa1d63715038b Mon Sep 17 00:00:00 2001 From: Alex Katkova Date: Wed, 19 Jan 2022 09:20:50 -0800 Subject: [PATCH 28/32] TA-28669. Stop saving all rpush feedback IDs to set. --- lib/rpush/client/redis/apns/feedback.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/rpush/client/redis/apns/feedback.rb b/lib/rpush/client/redis/apns/feedback.rb index 9413c0d88..53ca40be9 100644 --- a/lib/rpush/client/redis/apns/feedback.rb +++ b/lib/rpush/client/redis/apns/feedback.rb @@ -5,6 +5,8 @@ module Apns class Feedback include Modis::Model + enable_all_index false # prevent creation of rpush:rpush:client:redis:apns:feedback:all set + attribute :app_id, :integer attribute :device_token, :string attribute :failed_at, :timestamp From b5f9b8ea4dbfcf4eb8557edd5405904634828810 Mon Sep 17 00:00:00 2001 From: Alex Katkova Date: Wed, 19 Jan 2022 09:20:58 -0800 Subject: [PATCH 29/32] Add temp logging to debug SSL error. --- lib/rpush/daemon/apns2/delivery.rb | 6 +++++- spec/functional/apns2_spec.rb | 12 ++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/lib/rpush/daemon/apns2/delivery.rb b/lib/rpush/daemon/apns2/delivery.rb index eff20db0a..f769ea056 100644 --- a/lib/rpush/daemon/apns2/delivery.rb +++ b/lib/rpush/daemon/apns2/delivery.rb @@ -17,7 +17,11 @@ def initialize(app, http2_client, batch) def perform @batch.each_notification do |notification| - prepare_async_post(notification) + begin + prepare_async_post(notification) + rescue OpenSSL::SSL::SSLError => error + log_error("Notification #{notification.id} failed with SSL error") + end end # Send all preprocessed requests at once diff --git a/spec/functional/apns2_spec.rb b/spec/functional/apns2_spec.rb index 73d6c30ed..26d962951 100644 --- a/spec/functional/apns2_spec.rb +++ b/spec/functional/apns2_spec.rb @@ -263,6 +263,18 @@ def create_notification end end + context 'when SSL error occurs' do + before(:each) do + expect(fake_client).to receive(:call_async) { raise(OpenSSL::SSL::SSLError) } + end + + it 'logs the error' do + expect(Rpush.logger).to receive(:error) + create_notification + Rpush.push + end + end + context 'when waiting for requests to complete times out' do let(:on_close) do proc { |&block| @thread = Thread.new { sleep(0.01) } } From b4e45d997973aebfda43b2588f9a882c7e3ccd3a Mon Sep 17 00:00:00 2001 From: Yanina Libenson Date: Wed, 31 Jul 2024 17:06:35 -0300 Subject: [PATCH 30/32] BUGS-1850. Retry after Errno::ECONNRESET --- lib/rpush/daemon/apns2/delivery.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rpush/daemon/apns2/delivery.rb b/lib/rpush/daemon/apns2/delivery.rb index f769ea056..4e545cb0d 100644 --- a/lib/rpush/daemon/apns2/delivery.rb +++ b/lib/rpush/daemon/apns2/delivery.rb @@ -30,7 +30,7 @@ def perform mark_batch_retryable(Time.now + 10.seconds, error) @client.close raise - rescue Errno::ECONNREFUSED, SocketError => error + rescue Errno::ECONNREFUSED, SocketError, Errno::ECONNRESET => error mark_batch_retryable(Time.now + 10.seconds, error) raise rescue StandardError => error From 060ca4b7b37bdfa7b1fced3cbf9cde416ff62f27 Mon Sep 17 00:00:00 2001 From: Robert Stojanovski Date: Fri, 21 Aug 2026 08:44:41 -0400 Subject: [PATCH 31/32] Retry apnsp8 frames dropped by a mid-flight connection reset, and add structured push logging (#7) * Retry APNs (apnsp8) frames dropped by a mid-flight connection reset When the APNs HTTP/2 connection is reset while a batch is in flight, net-http2 tears down the stream set and delivers the error to the client's on(:error) callback; #join then returns normally. The in-flight notifications never receive an on(:close), so they are marked neither delivered, failed, nor retryable, and the batch discards them silently on completion -- no retry, no failure record. An idle apnsp8 socket (e.g. a low-volume Live Activity app) that APNs resets loses the next frame this way. Fix, localized to the apnsp8 transport plus a read-only Batch helper: - Batch#unresolved returns notifications with no terminal outcome. - Apnsp8::Delivery#perform reconciles after #join: any unresolved notification is re-queued (retryable) instead of dropped. No-op on the normal path where every stream reported a result. - handle_response treats an absent status code (stream closed before APNs answered) as a transport failure -> retry, not a permanent failure. - Errno::ECONNRESET added to the retryable rescue (matching apns2), in case a reset ever surfaces synchronously rather than via on(:error). Every existing APNs status outcome (200/410/400/429/500/503) is unchanged; only the "no verdict from APNs" cases move from {silent drop, permanent fail} to {retry}. Specs: Batch#unresolved, plus apnsp8 reconciliation and no-status handling. * Structured push-pipeline logging (apnsp8 transport + Loggable helper) Add Loggable#log_push_event, which emits logfmt-style key=value lines so Datadog indexes each field and the lines join to the nexus push logs on rpush_notification_id. Give every apnsp8 notification a discoverable outcome: - delivery: delivered / failed / retrying (with the device token truncated), and a retryable APNs response is logged as a retry, not a failure - app_runner: enqueued per notification (both the batch and non-batch paths), and startup_failed when an app cannot start (bad cert) - dispatcher: connection_error on a socket-level client error, so a mid-flight reset is findable Logging only; no delivery behavior changes. * Quote the app name in structured push logs The app name bypassed push_event_value, so a name containing whitespace, =, or " produced an unparseable logfmt line and broke Datadog field indexing. Route it through the same quoter as every other field. * Log the error message on connection_error, and keep push logs single-line Review follow-up: the connection_error line logged only the exception class, so two same-class socket errors with different messages were indistinguishable in a Datadog query. Include the message. Also fold newlines in structured field values so a multi-line message cannot split the one-line record. --- lib/rpush/daemon/apnsp8/delivery.rb | 74 +++++++++++--- lib/rpush/daemon/app_runner.rb | 4 +- lib/rpush/daemon/batch.rb | 12 +++ lib/rpush/daemon/dispatcher/apnsp8_http2.rb | 2 +- lib/rpush/daemon/loggable.rb | 26 +++++ spec/unit/daemon/apnsp8/delivery_spec.rb | 98 ++++++++++++++++++- spec/unit/daemon/app_runner_spec.rb | 21 +++- spec/unit/daemon/batch_spec.rb | 27 +++++ .../daemon/dispatcher/apnsp8_http2_spec.rb | 34 +++++++ spec/unit/daemon/loggable_spec.rb | 75 ++++++++++++++ 10 files changed, 352 insertions(+), 21 deletions(-) create mode 100644 spec/unit/daemon/dispatcher/apnsp8_http2_spec.rb create mode 100644 spec/unit/daemon/loggable_spec.rb diff --git a/lib/rpush/daemon/apnsp8/delivery.rb b/lib/rpush/daemon/apnsp8/delivery.rb index 4f5fd0a8d..10f8a304b 100644 --- a/lib/rpush/daemon/apnsp8/delivery.rb +++ b/lib/rpush/daemon/apnsp8/delivery.rb @@ -9,6 +9,10 @@ class Delivery < Rpush::Daemon::Delivery RETRYABLE_CODES = [ 429, 500, 503 ] CLIENT_JOIN_TIMEOUT = 60 DEFAULT_MAX_CONCURRENT_STREAMS = 100 + # How long to defer a notification whose delivery could not be confirmed at the + # transport level: a dropped connection, or a stream that closed with no APNs + # status. Matches the existing service-unavailable / connection-error backoff. + RECONNECT_RETRY_DELAY = 10.seconds def initialize(app, http2_client, token_provider, batch) @app = app @@ -25,13 +29,24 @@ def perform # Send all preprocessed requests at once @client.join(timeout: CLIENT_JOIN_TIMEOUT) + + # A dropped connection tears down its in-flight streams WITHOUT raising here: + # net-http2 hands the socket error to the client's on(:error) callback and #join + # returns once the stream set is emptied. Those notifications never received an + # on(:close), so they hold no outcome and would be silently discarded when the + # batch completes. Re-queue them so the frame lands on a fresh connection instead + # of vanishing. No-op on the normal path where every stream reported a result. + retry_unresolved rescue NetHttp2::AsyncRequestTimeout => error - mark_batch_retryable(Time.now + 10.seconds, error) + mark_batch_retryable(Time.now + RECONNECT_RETRY_DELAY, error) @client.close raise - rescue Errno::ECONNREFUSED, SocketError, HTTP2::Error::StreamLimitExceeded => error + rescue Errno::ECONNREFUSED, SocketError, Errno::ECONNRESET, HTTP2::Error::StreamLimitExceeded => error # TODO restart connection when StreamLimitExceeded - mark_batch_retryable(Time.now + 10.seconds, error) + # ECONNRESET (an established connection reset by the peer) is retryable like a + # refused/failed connection: should it ever surface synchronously here rather than + # via the async on(:error) path above, it must not fall through to mark_batch_failed. + mark_batch_retryable(Time.now + RECONNECT_RETRY_DELAY, error) raise rescue StandardError => error mark_batch_failed(error) @@ -103,6 +118,11 @@ def handle_response(notification, response) ok(notification) when *RETRYABLE_CODES service_unavailable(notification, response) + when nil + # The stream closed before any :status header arrived — APNs returned no verdict + # (the connection dropped mid-flight). This is a transport failure, not an APNs + # rejection, so retry rather than mark it permanently failed. + connection_lost(notification) else reflect(:notification_id_failed, @app, @@ -114,16 +134,32 @@ def handle_response(notification, response) end def ok(notification) - log_info("#{notification.id} sent to #{notification.device_token}") + log_push_event(:delivered, notification: notification, + device_token: truncate_device_token(notification.device_token)) @batch.mark_delivered(notification) end def service_unavailable(notification, response) - @batch.mark_retryable(notification, Time.now + 10.seconds) - # Logs should go last as soon as we need to initialize - # retry time to display it in log - failed_message_to_log(notification, response) - retry_message_to_log(notification) + @batch.mark_retryable(notification, Time.now + RECONNECT_RETRY_DELAY) + # A retryable APNs response (429/500/503) is not a failure: log it only as a + # retry so the failure signal stays clean. Logs go last, after mark_retryable + # has set the deliver_after we want to display. + retry_message_to_log(notification, reason: response[:code]) + end + + # Re-queue every notification the batch never resolved — one whose HTTP/2 stream was + # abandoned when the connection dropped, so its on(:close) never fired and it holds no + # delivered/failed/retryable outcome. A no-op when every stream reported a result. + def retry_unresolved + @batch.unresolved.each { |notification| connection_lost(notification) } + end + + # A notification with no delivery outcome from APNs: retry it on a fresh connection + # rather than discard it. Shared by the mid-flight-drop sweep (#retry_unresolved) and + # the no-status branch of #handle_response. + def connection_lost(notification) + @batch.mark_retryable(notification, Time.now + RECONNECT_RETRY_DELAY) + retry_message_to_log(notification, reason: 'connection_lost') end def build_request(notification) @@ -157,15 +193,23 @@ def notification_data(notification) notification.data || {} end - def retry_message_to_log(notification) - log_warn("Notification #{notification.id} will be retried after "\ - "#{notification.deliver_after.strftime('%Y-%m-%d %H:%M:%S')} "\ - "(retry #{notification.retries}).") + def retry_message_to_log(notification, reason:) + log_push_event(:retrying, notification: notification, level: :warn, + reason: reason, + retry: notification.retries, + deliver_after: notification.deliver_after&.strftime('%Y-%m-%d %H:%M:%S')) end def failed_message_to_log(notification, response) - log_error("Notification #{notification.id} failed, "\ - "#{response[:code]}/#{response[:failure_reason]}") + log_push_event(:failed, notification: notification, level: :error, + code: response[:code], reason: response[:failure_reason]) + end + + # Keep the raw device token out of logs; a short prefix is enough to correlate. + def truncate_device_token(token) + return token if token.nil? || token.length <= 8 + + "#{token[0, 8]}…" end end end diff --git a/lib/rpush/daemon/app_runner.rb b/lib/rpush/daemon/app_runner.rb index 6e7311d24..6e1e83499 100644 --- a/lib/rpush/daemon/app_runner.rb +++ b/lib/rpush/daemon/app_runner.rb @@ -33,7 +33,7 @@ def self.start_app(app) runner.start_loops rescue StandardError => e @runners.delete(app.id) - Rpush.logger.error("[#{app.name}] Exception raised during startup. Notifications will not be delivered for this app.") + log_push_event(:startup_failed, app: app, level: :error, reason: e.message) Rpush.logger.error(e) reflect(:error, e) end @@ -123,11 +123,13 @@ def enqueue(notifications) batch = Batch.new(batch_notifications) queue.push(QueuePayload.new(batch)) end + notifications.each { |notification| log_push_event(:enqueued, notification: notification) } else batch = Batch.new(notifications) notifications.each do |notification| queue.push(QueuePayload.new(batch, notification)) reflect(:notification_enqueued, notification) + log_push_event(:enqueued, notification: notification) end end end diff --git a/lib/rpush/daemon/batch.rb b/lib/rpush/daemon/batch.rb index 92d585515..c7cfa22b5 100644 --- a/lib/rpush/daemon/batch.rb +++ b/lib/rpush/daemon/batch.rb @@ -27,6 +27,18 @@ def each_delivered(&blk) @delivered.each(&blk) end + # Notifications that reached no terminal outcome — neither delivered, failed, nor + # retryable. A dropped connection abandons in-flight HTTP/2 streams: their per-request + # on(:close) never fires, so nothing is recorded for them and they would be silently + # discarded when the batch completes. A transport calls this after a send attempt to + # re-queue such notifications rather than lose them. Read-only; safe on any transport. + def unresolved + @mutex.synchronize do + resolved = @delivered + @failed.values.flatten(1) + @retryable.values.flatten(1) + @notifications - resolved + end + end + def mark_retryable(notification, deliver_after) @mutex.synchronize do @retryable[deliver_after] ||= [] diff --git a/lib/rpush/daemon/dispatcher/apnsp8_http2.rb b/lib/rpush/daemon/dispatcher/apnsp8_http2.rb index 522d97bbb..5b8ade3ef 100644 --- a/lib/rpush/daemon/dispatcher/apnsp8_http2.rb +++ b/lib/rpush/daemon/dispatcher/apnsp8_http2.rb @@ -35,7 +35,7 @@ def create_http2_client(app) url = URLS[app.environment.to_sym] client = NetHttp2::Client.new(url, connect_timeout: DEFAULT_TIMEOUT) client.on(:error) do |error| - log_error(error) + log_push_event(:connection_error, level: :error, error: "#{error.class}: #{error.message}") reflect(:error, error) end client diff --git a/lib/rpush/daemon/loggable.rb b/lib/rpush/daemon/loggable.rb index 015a3e22f..4c23f0a18 100644 --- a/lib/rpush/daemon/loggable.rb +++ b/lib/rpush/daemon/loggable.rb @@ -21,8 +21,34 @@ def log_error(e) end end + # Emit a single structured push-pipeline log line in logfmt-style + # `key=value` pairs, so Datadog indexes each field and the lines join to + # the nexus push logs on `rpush_notification_id`. Field order is stable: + # event, notification id, app, then the caller's fields in the order given. + # nil-valued fields are dropped; values with whitespace/`=`/`"` are quoted. + def log_push_event(event, app: nil, notification: nil, level: :info, **fields) + parts = ["event=#{event}"] + parts << "rpush_notification_id=#{notification.id}" if notification + name = (app || instance_variable_get('@app'))&.name + parts << "app=#{push_event_value(name)}" unless name.nil? + fields.each do |key, value| + next if value.nil? + parts << "#{key}=#{push_event_value(value)}" + end + Rpush.logger.public_send(level, parts.join(' ')) + end + private + def push_event_value(value) + # A structured record is one line: fold any newline in the value to a space so a + # multi-line message (e.g. an exception) cannot split the record. + str = value.to_s.gsub(/[\r\n]+/, ' ') + return str unless str.match?(/[\s"=]/) + + %("#{str.gsub('"', '\"')}") + end + def app_prefix(msg) app = instance_variable_get('@app') msg = "[#{app.name}] #{msg}" if app diff --git a/spec/unit/daemon/apnsp8/delivery_spec.rb b/spec/unit/daemon/apnsp8/delivery_spec.rb index b1810cf5a..9298dd52b 100644 --- a/spec/unit/daemon/apnsp8/delivery_spec.rb +++ b/spec/unit/daemon/apnsp8/delivery_spec.rb @@ -3,7 +3,7 @@ describe Rpush::Daemon::Apnsp8::Delivery do subject(:delivery) { described_class.new(app, http2_client, token_provider, batch) } - let(:app) { double(bundle_id: 'MY BUNDLE ID') } + let(:app) { double(bundle_id: 'MY BUNDLE ID', name: 'my_app') } let(:notification1) { double('Notification 1', data: {}, as_json: {}).as_null_object } let(:notification2) { double('Notification 2', data: {}, as_json: {}).as_null_object } @@ -21,14 +21,51 @@ ) end - let(:batch) { double(mark_delivered: nil, all_processed: nil) } - let(:logger) { double(info: nil) } + let(:batch) do + double(mark_delivered: nil, mark_retryable: nil, mark_failed: nil, + all_processed: nil, unresolved: []) + end + let(:logger) { double('logger').as_null_object } before do allow(batch).to receive(:each_notification) do |&blk| [notification1, notification2].each(&blk) end allow(Rpush).to receive_messages(logger: logger) + allow(delivery).to receive(:reflect) + end + + describe 'structured outcome logging' do + let(:notification) do + double('Notification', + id: 7, + device_token: 'abcdef0123456789', + retries: 1, + deliver_after: Time.parse('2026-08-20 16:21:36 UTC')) + end + + it 'logs a structured delivered event on success' do + allow(batch).to receive(:mark_delivered) + expect(logger).to receive(:info) + .with('event=delivered rpush_notification_id=7 app=my_app device_token=abcdef01…') + delivery.send(:ok, notification) + end + + it 'logs a structured failed event on an APNs rejection' do + allow(batch).to receive(:mark_failed) + allow(delivery).to receive(:reflect) + expect(logger).to receive(:error) + .with('event=failed rpush_notification_id=7 app=my_app code=410 reason=Unregistered') + delivery.send(:handle_response, notification, code: 410, failure_reason: 'Unregistered') + end + + it 'logs a structured retrying event for a retryable code and does not log a failure' do + allow(batch).to receive(:mark_retryable) + expect(logger).not_to receive(:error) + expect(logger).to receive(:warn) + .with('event=retrying rpush_notification_id=7 app=my_app reason=503 retry=1 deliver_after="2026-08-20 16:21:36"') + delivery.send(:service_unavailable, notification, code: 503, failure_reason: 'ServiceUnavailable') + end end describe '#perform' do @@ -49,5 +86,60 @@ end end end + + context 'when the connection drops and a stream is left unresolved' do + before { allow(batch).to receive(:unresolved).and_return([notification1]) } + + it 'marks the abandoned notification retryable rather than discarding it' do + expect(batch).to receive(:mark_retryable).with(notification1, anything) + delivery.perform + end + end + + context 'when every stream resolved' do + it 'marks nothing retryable' do + expect(batch).not_to receive(:mark_retryable) + delivery.perform + end + end + end + + describe '#handle_response' do + def handle(response) + delivery.send(:handle_response, notification1, response) + end + + context 'with a 200 status' do + it 'marks the notification delivered' do + expect(batch).to receive(:mark_delivered).with(notification1) + handle(code: 200) + end + end + + context 'with a retryable status' do + it 'marks the notification retryable' do + expect(batch).to receive(:mark_retryable).with(notification1, anything) + handle(code: 503) + end + end + + context 'with no status (stream closed before APNs responded)' do + it 'marks the notification retryable, not failed' do + expect(batch).to receive(:mark_retryable).with(notification1, anything) + handle({}) + end + + it 'does not mark the notification failed' do + expect(batch).not_to receive(:mark_failed) + handle({}) + end + end + + context 'with a permanent error status' do + it 'marks the notification failed' do + expect(batch).to receive(:mark_failed).with(notification1, 400, 'BadDeviceToken') + handle(code: 400, failure_reason: 'BadDeviceToken') + end + end end end diff --git a/spec/unit/daemon/app_runner_spec.rb b/spec/unit/daemon/app_runner_spec.rb index e979b2cfb..e2ba8f271 100644 --- a/spec/unit/daemon/app_runner_spec.rb +++ b/spec/unit/daemon/app_runner_spec.rb @@ -75,6 +75,14 @@ class Delivery expect(Rpush.logger).to receive(:error) Rpush::Daemon::AppRunner.start_app(app) end + + it 'logs a structured startup_failed event when startup raises' do + allow(Rpush::Daemon::AppRunner).to receive(:new).with(app).and_return(runner) + allow(runner).to receive(:start_dispatchers).and_raise(StandardError, 'bad cert') + Rpush::Daemon::AppRunner.start_app(app) + expect(Rpush.logger).to have_received(:error) + .with('event=startup_failed app=test reason="bad cert"') + end end describe Rpush::Daemon::AppRunner, 'debug' do @@ -139,7 +147,7 @@ class Delivery end describe 'enqueue' do - let(:notification) { double } + let(:notification) { double(id: 1) } it 'enqueues the batch' do expect(queue).to receive(:push) do |queue_payload| @@ -154,11 +162,22 @@ class Delivery runner.enqueue([notification]) end + it 'logs a structured enqueued event for each notification' do + expect(logger).to receive(:info).with('event=enqueued rpush_notification_id=1 app=test') + runner.enqueue([notification]) + end + describe 'a service that batches deliveries' do before do allow(runner.send(:service)).to receive_messages(batch_deliveries?: true) end + it 'logs a structured enqueued event for each notification' do + allow(runner).to receive(:num_dispatcher_loops).and_return(1) + expect(logger).to receive(:info).with('event=enqueued rpush_notification_id=1 app=test') + runner.enqueue([notification]) + end + describe '1 notification with more than one dispatcher loop' do it 'does not raise ArgumentError: invalid slice size' do # https://github.com/rpush/rpush/issues/57 diff --git a/spec/unit/daemon/batch_spec.rb b/spec/unit/daemon/batch_spec.rb index aeb2f16f8..3df197521 100644 --- a/spec/unit/daemon/batch_spec.rb +++ b/spec/unit/daemon/batch_spec.rb @@ -135,6 +135,33 @@ end end + describe 'unresolved' do + it 'returns every notification with no outcome' do + expect(batch.unresolved).to eq [notification1, notification2] + end + + it 'excludes delivered notifications' do + batch.mark_delivered(notification1) + expect(batch.unresolved).to eq [notification2] + end + + it 'excludes failed notifications' do + batch.mark_failed(notification1, 400, 'BadDeviceToken') + expect(batch.unresolved).to eq [notification2] + end + + it 'excludes retryable notifications' do + batch.mark_retryable(notification1, time) + expect(batch.unresolved).to eq [notification2] + end + + it 'returns nothing once every notification resolved' do + batch.mark_delivered(notification1) + batch.mark_failed(notification2, 400, 'BadDeviceToken') + expect(batch.unresolved).to be_empty + end + end + describe 'complete' do before do allow(Rpush).to receive_messages(logger: double.as_null_object) diff --git a/spec/unit/daemon/dispatcher/apnsp8_http2_spec.rb b/spec/unit/daemon/dispatcher/apnsp8_http2_spec.rb new file mode 100644 index 000000000..267c8c984 --- /dev/null +++ b/spec/unit/daemon/dispatcher/apnsp8_http2_spec.rb @@ -0,0 +1,34 @@ +require 'unit_spec_helper' + +describe Rpush::Daemon::Dispatcher::Apnsp8Http2 do + let(:app) { double(name: 'my_app', environment: 'production') } + let(:delivery_class) { double('DeliveryClass') } + let(:logger) { double('logger').as_null_object } + let(:client) { double('NetHttp2::Client') } + + subject(:dispatcher) { described_class.new(app, delivery_class) } + + before do + allow(Rpush).to receive_messages(logger: logger) + allow(Rpush::Daemon::Apnsp8::Token).to receive(:new).and_return(double('Token')) + @callbacks = {} + allow(NetHttp2::Client).to receive(:new).and_return(client) + allow(client).to receive(:on) { |event, &blk| @callbacks[event] = blk } + end + + describe 'the client error callback' do + it 'logs a structured connection_error event with the error class and message' do + dispatcher + allow(dispatcher).to receive(:reflect) + expect(logger).to receive(:error) + .with('event=connection_error app=my_app error="SocketError: Socket was remotely closed"') + @callbacks[:error].call(SocketError.new('Socket was remotely closed')) + end + + it 'still reflects the error so upstream handlers fire' do + dispatcher + expect(dispatcher).to receive(:reflect).with(:error, kind_of(Errno::ECONNRESET)) + @callbacks[:error].call(Errno::ECONNRESET.new('Connection reset by peer')) + end + end +end diff --git a/spec/unit/daemon/loggable_spec.rb b/spec/unit/daemon/loggable_spec.rb new file mode 100644 index 000000000..f289ce1f9 --- /dev/null +++ b/spec/unit/daemon/loggable_spec.rb @@ -0,0 +1,75 @@ +require 'unit_spec_helper' + +describe Rpush::Daemon::Loggable do + let(:logger) { double(info: nil, warn: nil, error: nil) } + before { allow(Rpush).to receive_messages(logger: logger) } + + let(:klass) do + Class.new do + include Rpush::Daemon::Loggable + def initialize(app = nil) + @app = app + end + end + end + + let(:app) { double(name: 'my_app') } + let(:notification) { double(id: 42) } + + describe '#log_push_event' do + it 'writes a structured line with event, notification id, app, and fields at info level' do + obj = klass.new(app) + expect(logger).to receive(:info).with('event=delivered rpush_notification_id=42 app=my_app code=200') + obj.log_push_event(:delivered, notification: notification, code: 200) + end + + it 'routes the warn level to the warn logger' do + obj = klass.new(app) + expect(logger).to receive(:warn).with('event=retrying rpush_notification_id=42 app=my_app reason=503') + obj.log_push_event(:retrying, notification: notification, level: :warn, reason: 503) + end + + it 'routes the error level to the error logger' do + obj = klass.new(app) + expect(logger).to receive(:error).with('event=failed rpush_notification_id=42 app=my_app code=410 reason=Unregistered') + obj.log_push_event(:failed, notification: notification, level: :error, code: 410, reason: 'Unregistered') + end + + it 'omits the notification id when no notification is given' do + obj = klass.new(app) + expect(logger).to receive(:error).with('event=connection_error app=my_app error=Errno::ECONNRESET') + obj.log_push_event(:connection_error, level: :error, error: 'Errno::ECONNRESET') + end + + it 'prefers an explicitly passed app over the instance app' do + obj = klass.new(nil) + other = double(name: 'other_app') + expect(logger).to receive(:info).with('event=startup_failed app=other_app reason="bad cert"') + obj.log_push_event(:startup_failed, app: other, reason: 'bad cert') + end + + it 'quotes field values that contain whitespace' do + obj = klass.new(app) + expect(logger).to receive(:warn).with('event=retrying rpush_notification_id=42 app=my_app deliver_after="2026-08-20 16:21:36"') + obj.log_push_event(:retrying, notification: notification, level: :warn, deliver_after: '2026-08-20 16:21:36') + end + + it 'quotes the app name when it contains whitespace' do + obj = klass.new(double(name: 'My App')) + expect(logger).to receive(:info).with('event=delivered rpush_notification_id=42 app="My App"') + obj.log_push_event(:delivered, notification: notification) + end + + it 'collapses newlines in field values so the record stays one line' do + obj = klass.new(app) + expect(logger).to receive(:error).with('event=connection_error app=my_app error="a b"') + obj.log_push_event(:connection_error, level: :error, error: "a\nb") + end + + it 'omits fields whose value is nil' do + obj = klass.new(app) + expect(logger).to receive(:info).with('event=delivered rpush_notification_id=42 app=my_app') + obj.log_push_event(:delivered, notification: notification, device_token: nil) + end + end +end From 693ed0fd9b24c2077b54f63fa52c765a1e936bda Mon Sep 17 00:00:00 2001 From: Darren Cheng Date: Fri, 21 Aug 2026 06:29:21 -0700 Subject: [PATCH 32/32] Retry APNs2 frames dropped by a mid-flight connection reset (#9) Apns2::Delivery has the same latent silent-drop pattern PR #7 fixed for Apnsp8, called out there as a deliberate follow-up: a dropped HTTP/2 connection tears down its in-flight streams via net-http2's on(:error) callback rather than raising into #perform, so those notifications never receive an on(:close) and are marked neither delivered, failed, nor retryable -- silently discarded when the batch completes. This is the transport a cert-based app (e.g. currypizzahouse_ios) uses, observed in production as a connection that goes completely silent for hours -- no sends, no errors logged -- then resumes on its own with no restart. Reused a single push message's rpush_notifications rows confirm the same request succeeding on one attempt and silently vanishing (no delivered/failed/retryable outcome) on another, minutes apart, against the same two device tokens. Fix, mirroring Apnsp8::Delivery#perform and reusing Batch#unresolved (already added by #7, transport-agnostic): - Apns2::Delivery#perform reconciles after #join: any unresolved notification is re-queued (retryable) instead of dropped. - handle_response treats an absent status code (stream closed before APNs answered) as a transport failure -> retry, not a permanent failure (previously this fell through to the `else` branch and was marked permanently *failed* -- worse than Apnsp8's pre-#7 silent drop). - Also fixes the untested per-notification SSLError rescue named in #7's "Follow-ups" section: preparing a request could raise before the notification ever got a stream, and the old code just logged and moved on, leaving it with no outcome at all. Now retried via the same path. Every existing APNs status outcome (200/410/400/429/500/503) is unchanged; only the "no verdict from APNs" cases move from {silent drop, permanent fail} to {retry} -- strictly safer, matching #7's regression-safety argument for Apnsp8. Also brings structured push-event logging (#7) to this transport for parity: delivered/failed/retrying events on Delivery, and the dispatcher's connection_error now includes the error message (not just its class) -- addressing the one open review comment on #7 before it repeats here. Tests mirror #7's apnsp8 coverage: the reconnection sweep, no-status handling, the SSLError-at-prepare-time path, and the logging format cases. Stacked on rstojano/apnsp8-retry-dropped-frames (#7) to reuse Batch#unresolved and Loggable#log_push_event without redefining them; rebase onto master once #7 merges. Co-authored-by: Robert Stojanovski --- lib/rpush/daemon/apns2/delivery.rb | 92 +++++++++-- lib/rpush/daemon/dispatcher/apns_http2.rb | 6 +- spec/functional/apns2_spec.rb | 10 +- spec/unit/daemon/apns2/delivery_spec.rb | 146 ++++++++++++++++++ .../unit/daemon/dispatcher/apns_http2_spec.rb | 35 +++++ 5 files changed, 269 insertions(+), 20 deletions(-) create mode 100644 spec/unit/daemon/apns2/delivery_spec.rb create mode 100644 spec/unit/daemon/dispatcher/apns_http2_spec.rb diff --git a/lib/rpush/daemon/apns2/delivery.rb b/lib/rpush/daemon/apns2/delivery.rb index 4e545cb0d..9e737103e 100644 --- a/lib/rpush/daemon/apns2/delivery.rb +++ b/lib/rpush/daemon/apns2/delivery.rb @@ -8,6 +8,11 @@ module Apns2 class Delivery < Rpush::Daemon::Delivery RETRYABLE_CODES = [ 429, 500, 503 ] CLIENT_JOIN_TIMEOUT = 60 + # How long to defer a notification whose delivery could not be confirmed at the + # transport level: a dropped connection, or a stream that closed with no APNs + # status. Matches the existing service-unavailable / connection-error backoff. + # Mirrors Apnsp8::Delivery. + RECONNECT_RETRY_DELAY = 10.seconds def initialize(app, http2_client, batch) @app = app @@ -20,18 +25,32 @@ def perform begin prepare_async_post(notification) rescue OpenSSL::SSL::SSLError => error - log_error("Notification #{notification.id} failed with SSL error") + # Building the request for THIS notification raised before it ever got a + # stream, so it will never receive an on(:close) — left alone it would hold no + # outcome at all (neither delivered, failed, nor retryable) and silently + # vanish from the batch when the next notification is processed. Retry it + # explicitly instead of just logging and moving on. + prepare_failed(notification, error) end end # Send all preprocessed requests at once @client.join(timeout: CLIENT_JOIN_TIMEOUT) + + # A dropped connection tears down its in-flight streams WITHOUT raising here: + # net-http2 hands the socket error to the client's on(:error) callback and #join + # returns once the stream set is emptied. Those notifications never received an + # on(:close), so they hold no outcome and would be silently discarded when the + # batch completes. Re-queue them so the frame lands on a fresh connection instead + # of vanishing. No-op on the normal path where every stream reported a result. + # (Mirrors Apnsp8::Delivery#perform — see PR #7.) + retry_unresolved rescue NetHttp2::AsyncRequestTimeout => error - mark_batch_retryable(Time.now + 10.seconds, error) + mark_batch_retryable(Time.now + RECONNECT_RETRY_DELAY, error) @client.close raise rescue Errno::ECONNREFUSED, SocketError, Errno::ECONNRESET => error - mark_batch_retryable(Time.now + 10.seconds, error) + mark_batch_retryable(Time.now + RECONNECT_RETRY_DELAY, error) raise rescue StandardError => error mark_batch_failed(error) @@ -74,6 +93,11 @@ def handle_response(notification, response) ok(notification) when *RETRYABLE_CODES service_unavailable(notification, response) + when nil + # The stream closed before any :status header arrived — APNs returned no + # verdict (the connection dropped mid-flight). This is a transport failure, not + # an APNs rejection, so retry rather than mark it permanently failed. + connection_lost(notification) else reflect(:notification_id_failed, @app, @@ -85,16 +109,45 @@ def handle_response(notification, response) end def ok(notification) - log_info("#{notification.id} sent to #{notification.device_token}") + log_push_event(:delivered, notification: notification, + device_token: truncate_device_token(notification.device_token)) @batch.mark_delivered(notification) end def service_unavailable(notification, response) - @batch.mark_retryable(notification, Time.now + 10.seconds) - # Logs should go last as soon as we need to initialize - # retry time to display it in log - failed_message_to_log(notification, response) - retry_message_to_log(notification) + @batch.mark_retryable(notification, Time.now + RECONNECT_RETRY_DELAY) + # A retryable APNs response (429/500/503) is not a failure: log it only as a + # retry so the failure signal stays clean. Logs go last, after mark_retryable + # has set the deliver_after we want to display. + retry_message_to_log(notification, reason: response[:code]) + end + + # Re-queue every notification the batch never resolved — one whose HTTP/2 stream was + # abandoned when the connection dropped, so its on(:close) never fired and it holds + # no delivered/failed/retryable outcome. A no-op when every stream reported a result. + def retry_unresolved + @batch.unresolved.each { |notification| connection_lost(notification) } + end + + # A notification with no delivery outcome from APNs: retry it on a fresh connection + # rather than discard it. Shared by the mid-flight-drop sweep (#retry_unresolved) and + # the no-status branch of #handle_response. + def connection_lost(notification) + @batch.mark_retryable(notification, Time.now + RECONNECT_RETRY_DELAY) + retry_message_to_log(notification, reason: 'connection_lost') + end + + # The per-notification request-preparation step raised before its stream existed + # (e.g. an SSL renegotiation error on this cert-based connection), so it will never + # receive an on(:close). Retry it like any other transport-level drop rather than + # silently skipping to the next notification in the batch. + def prepare_failed(notification, error) + @batch.mark_retryable(notification, Time.now + RECONNECT_RETRY_DELAY) + # Keep `reason` a stable, low-cardinality value for classification and put the + # exception in its own `error` field, consistent with the dispatcher's + # connection_error event. + retry_message_to_log(notification, reason: 'prepare_failed', + error: "#{error.class}: #{error.message}") end def build_request(notification) @@ -124,15 +177,24 @@ def notification_data(notification) notification.data || {} end - def retry_message_to_log(notification) - log_warn("Notification #{notification.id} will be retried after "\ - "#{notification.deliver_after.strftime('%Y-%m-%d %H:%M:%S')} "\ - "(retry #{notification.retries}).") + def retry_message_to_log(notification, reason:, error: nil) + log_push_event(:retrying, notification: notification, level: :warn, + reason: reason, + error: error, + retry: notification.retries, + deliver_after: notification.deliver_after&.strftime('%Y-%m-%d %H:%M:%S')) end def failed_message_to_log(notification, response) - log_error("Notification #{notification.id} failed, "\ - "#{response[:code]}/#{response[:failure_reason]}") + log_push_event(:failed, notification: notification, level: :error, + code: response[:code], reason: response[:failure_reason]) + end + + # Keep the raw device token out of logs; a short prefix is enough to correlate. + def truncate_device_token(token) + return token if token.nil? || token.length <= 8 + + "#{token[0, 8]}…" end end end diff --git a/lib/rpush/daemon/dispatcher/apns_http2.rb b/lib/rpush/daemon/dispatcher/apns_http2.rb index 88a159bb4..7ab666a91 100644 --- a/lib/rpush/daemon/dispatcher/apns_http2.rb +++ b/lib/rpush/daemon/dispatcher/apns_http2.rb @@ -34,7 +34,11 @@ def create_http2_client(app) url = URLS[app.environment.to_sym] client = NetHttp2::Client.new(url, ssl_context: prepare_ssl_context, connect_timeout: DEFAULT_TIMEOUT) client.on(:error) do |error| - log_error(error) + # Include the message, not just the class: this is the one line meant to make a + # mid-flight reset findable by app and time in Datadog, and two different socket + # errors of the same class (e.g. two distinct SSLError causes) are otherwise + # indistinguishable here. + log_push_event(:connection_error, level: :error, error: "#{error.class}: #{error.message}") reflect(:error, error) end client diff --git a/spec/functional/apns2_spec.rb b/spec/functional/apns2_spec.rb index 26d962951..92890b9b4 100644 --- a/spec/functional/apns2_spec.rb +++ b/spec/functional/apns2_spec.rb @@ -268,10 +268,12 @@ def create_notification expect(fake_client).to receive(:call_async) { raise(OpenSSL::SSL::SSLError) } end - it 'logs the error' do - expect(Rpush.logger).to receive(:error) - create_notification - Rpush.push + it 'fails but retries delivery several times' do + notification = create_notification + expect do + Rpush.push + notification.reload + end.to change(notification, :retries) end end diff --git a/spec/unit/daemon/apns2/delivery_spec.rb b/spec/unit/daemon/apns2/delivery_spec.rb new file mode 100644 index 000000000..c8f446314 --- /dev/null +++ b/spec/unit/daemon/apns2/delivery_spec.rb @@ -0,0 +1,146 @@ +require 'unit_spec_helper' + +describe Rpush::Daemon::Apns2::Delivery do + subject(:delivery) { described_class.new(app, http2_client, batch) } + + let(:app) { double(bundle_id: 'MY BUNDLE ID', name: 'my_app') } + let(:notification1) { double('Notification 1', data: {}, as_json: {}).as_null_object } + let(:notification2) { double('Notification 2', data: {}, as_json: {}).as_null_object } + + let(:http_request) { double(on: nil) } + let(:http2_client) do + double( + call_async: nil, + join: nil, + prepare_request: http_request + ) + end + + let(:batch) do + double(mark_delivered: nil, mark_retryable: nil, mark_failed: nil, + all_processed: nil, unresolved: []) + end + let(:logger) { double('logger').as_null_object } + + before do + allow(batch).to receive(:each_notification) do |&blk| + [notification1, notification2].each(&blk) + end + allow(Rpush).to receive_messages(logger: logger) + allow(delivery).to receive(:reflect) + end + + describe 'structured outcome logging' do + let(:notification) do + double('Notification', + id: 7, + device_token: 'abcdef0123456789', + retries: 1, + deliver_after: Time.parse('2026-08-20 16:21:36 UTC')) + end + + it 'logs a structured delivered event on success' do + allow(batch).to receive(:mark_delivered) + expect(logger).to receive(:info) + .with('event=delivered rpush_notification_id=7 app=my_app device_token=abcdef01…') + delivery.send(:ok, notification) + end + + it 'logs a structured failed event on an APNs rejection' do + allow(batch).to receive(:mark_failed) + expect(logger).to receive(:error) + .with('event=failed rpush_notification_id=7 app=my_app code=410 reason=Unregistered') + delivery.send(:handle_response, notification, code: 410, failure_reason: 'Unregistered') + end + + it 'logs a structured retrying event for a retryable code and does not log a failure' do + allow(batch).to receive(:mark_retryable) + expect(logger).not_to receive(:error) + expect(logger).to receive(:warn) + .with('event=retrying rpush_notification_id=7 app=my_app reason=503 retry=1 deliver_after="2026-08-20 16:21:36"') + delivery.send(:service_unavailable, notification, code: 503, failure_reason: 'ServiceUnavailable') + end + end + + describe '#perform' do + context 'when the connection drops and a stream is left unresolved' do + before { allow(batch).to receive(:unresolved).and_return([notification1]) } + + it 'marks the abandoned notification retryable rather than discarding it' do + expect(batch).to receive(:mark_retryable).with(notification1, anything) + delivery.perform + end + end + + context 'when every stream resolved' do + it 'marks nothing retryable' do + expect(batch).not_to receive(:mark_retryable) + delivery.perform + end + end + + context 'when preparing a request raises an SSL error before it gets a stream' do + before do + allow(delivery).to receive(:prepare_async_post) do |notification| + raise OpenSSL::SSL::SSLError, 'session ticket not found' if notification == notification1 + end + end + + it 'marks the notification retryable instead of silently skipping it' do + expect(batch).to receive(:mark_retryable).with(notification1, anything) + delivery.perform + end + + it 'does not abort the batch — the next notification is still attempted' do + attempted = [] + allow(delivery).to receive(:prepare_async_post) do |notification| + attempted << notification + raise OpenSSL::SSL::SSLError, 'session ticket not found' if notification == notification1 + end + + delivery.perform + + expect(attempted).to eq([notification1, notification2]) + end + end + end + + describe '#handle_response' do + def handle(response) + delivery.send(:handle_response, notification1, response) + end + + context 'with a 200 status' do + it 'marks the notification delivered' do + expect(batch).to receive(:mark_delivered).with(notification1) + handle(code: 200) + end + end + + context 'with a retryable status' do + it 'marks the notification retryable' do + expect(batch).to receive(:mark_retryable).with(notification1, anything) + handle(code: 503) + end + end + + context 'with no status (stream closed before APNs responded)' do + it 'marks the notification retryable, not failed' do + expect(batch).to receive(:mark_retryable).with(notification1, anything) + handle({}) + end + + it 'does not mark the notification failed' do + expect(batch).not_to receive(:mark_failed) + handle({}) + end + end + + context 'with a permanent error status' do + it 'marks the notification failed' do + expect(batch).to receive(:mark_failed).with(notification1, 400, 'BadDeviceToken') + handle(code: 400, failure_reason: 'BadDeviceToken') + end + end + end +end diff --git a/spec/unit/daemon/dispatcher/apns_http2_spec.rb b/spec/unit/daemon/dispatcher/apns_http2_spec.rb new file mode 100644 index 000000000..076a690cd --- /dev/null +++ b/spec/unit/daemon/dispatcher/apns_http2_spec.rb @@ -0,0 +1,35 @@ +require 'unit_spec_helper' + +describe Rpush::Daemon::Dispatcher::ApnsHttp2 do + let(:app) { double(name: 'my_app', environment: 'production', certificate: 'cert', password: 'pass') } + let(:delivery_class) { double('DeliveryClass') } + let(:logger) { double('logger').as_null_object } + let(:client) { double('NetHttp2::Client') } + let(:ssl_context) { double('OpenSSL::SSL::SSLContext') } + + subject(:dispatcher) { described_class.new(app, delivery_class) } + + before do + allow(Rpush).to receive_messages(logger: logger) + allow_any_instance_of(described_class).to receive(:prepare_ssl_context).and_return(ssl_context) + @callbacks = {} + allow(NetHttp2::Client).to receive(:new).and_return(client) + allow(client).to receive(:on) { |event, &blk| @callbacks[event] = blk } + end + + describe 'the client error callback' do + it 'logs a structured connection_error event including the error message, not just its class' do + dispatcher + allow(dispatcher).to receive(:reflect) + expect(logger).to receive(:error) + .with('event=connection_error app=my_app error="Errno::ECONNRESET: Connection reset by peer"') + @callbacks[:error].call(Errno::ECONNRESET.new) + end + + it 'still reflects the error so upstream handlers fire' do + dispatcher + expect(dispatcher).to receive(:reflect).with(:error, kind_of(Errno::ECONNRESET)) + @callbacks[:error].call(Errno::ECONNRESET.new('Connection reset by peer')) + end + end +end