rpush 7.0.1 → 8.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +28 -4
  3. data/README.md +38 -1
  4. data/lib/generators/rpush_migration_generator.rb +1 -0
  5. data/lib/generators/templates/rpush.rb +17 -0
  6. data/lib/generators/templates/rpush_7_1_0_updates.rb +12 -0
  7. data/lib/rpush/client/active_model/apns/notification.rb +0 -4
  8. data/lib/rpush/client/active_model/fcm/app.rb +20 -0
  9. data/lib/rpush/client/active_model/fcm/expiry_collapse_key_mutual_inclusion_validator.rb +14 -0
  10. data/lib/rpush/client/active_model/fcm/notification.rb +129 -0
  11. data/lib/rpush/client/active_model/fcm/notification_keys_in_allowed_list_validator.rb +20 -0
  12. data/lib/rpush/client/active_model.rb +5 -0
  13. data/lib/rpush/client/active_record/fcm/app.rb +11 -0
  14. data/lib/rpush/client/active_record/fcm/notification.rb +11 -0
  15. data/lib/rpush/client/active_record.rb +3 -0
  16. data/lib/rpush/client/redis/app.rb +2 -0
  17. data/lib/rpush/client/redis/fcm/app.rb +11 -0
  18. data/lib/rpush/client/redis/fcm/notification.rb +11 -0
  19. data/lib/rpush/client/redis.rb +3 -0
  20. data/lib/rpush/configuration.rb +1 -1
  21. data/lib/rpush/daemon/apns2/delivery.rb +0 -1
  22. data/lib/rpush/daemon/apnsp8/delivery.rb +0 -1
  23. data/lib/rpush/daemon/fcm/delivery.rb +162 -0
  24. data/lib/rpush/daemon/fcm.rb +9 -0
  25. data/lib/rpush/daemon/gcm/delivery.rb +1 -1
  26. data/lib/rpush/daemon/google_credential_cache.rb +41 -0
  27. data/lib/rpush/daemon/store/active_record.rb +15 -0
  28. data/lib/rpush/daemon/store/interface.rb +2 -2
  29. data/lib/rpush/daemon/store/redis.rb +13 -0
  30. data/lib/rpush/daemon/webpush/delivery.rb +2 -2
  31. data/lib/rpush/daemon.rb +4 -0
  32. data/lib/rpush/reflection_collection.rb +3 -2
  33. data/lib/rpush/version.rb +2 -2
  34. data/lib/rpush.rb +1 -0
  35. data/spec/functional/apns2_spec.rb +2 -6
  36. data/spec/functional/fcm_priority_spec.rb +46 -0
  37. data/spec/functional/fcm_spec.rb +77 -0
  38. data/spec/functional_spec_helper.rb +1 -1
  39. data/spec/spec_helper.rb +2 -1
  40. data/spec/support/active_record_setup.rb +3 -1
  41. data/spec/unit/client/active_record/fcm/app_spec.rb +6 -0
  42. data/spec/unit/client/active_record/fcm/notification_spec.rb +10 -0
  43. data/spec/unit/client/redis/fcm/app_spec.rb +5 -0
  44. data/spec/unit/client/redis/fcm/notification_spec.rb +5 -0
  45. data/spec/unit/client/shared/apns/notification.rb +0 -15
  46. data/spec/unit/client/shared/fcm/app.rb +4 -0
  47. data/spec/unit/client/shared/fcm/notification.rb +92 -0
  48. data/spec/unit/configuration_spec.rb +1 -1
  49. data/spec/unit/daemon/apnsp8/delivery_spec.rb +1 -1
  50. data/spec/unit/daemon/fcm/delivery_spec.rb +127 -0
  51. data/spec/unit/daemon/service_config_methods_spec.rb +1 -1
  52. data/spec/unit/daemon/tcp_connection_spec.rb +8 -7
  53. data/spec/unit/daemon/wns/delivery_spec.rb +1 -1
  54. data/spec/unit/logger_spec.rb +1 -1
  55. data/spec/unit_spec_helper.rb +1 -1
  56. metadata +81 -17
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 409c28bf9f63343232e1c7db56f8d7131acc70b4828032059f37eab3a4606e6c
4
- data.tar.gz: 3f07d7e79828fa43ab5ed73a9b19183a9f4ac9e5a3d8891f1ecfec42c7375c03
3
+ metadata.gz: 563933a59960a39b083c6fa55d8300d1b92c29e332cb7ecded294ecc772965ef
4
+ data.tar.gz: dd9f6cba0c315d7398266a00a1c59cd121ad0167f1217e7781acb402d5ee12e9
5
5
  SHA512:
6
- metadata.gz: 400a7241c337ff9874767be4ee051d1494676665bd17e9b3acb2e3ce8efae236971a138888da6c0172e0c649276e97f4d639430733bad345e7980d408e8c3436
7
- data.tar.gz: e81b66da807a2bc8a386a12825aed37c452672be899b69ff633d65b80548903669e7d63b3b1eec899633557a489157e8083ed562b2665c017566df9ad220c151
6
+ metadata.gz: ab9641db8c6e0b0f77b1655d00082d29abb8ef0e83eadc640460abbad6624c96b1641501b597de20cc8c5c9dc8d39ecc34673ad174e6e42f0e036168822c36f9
7
+ data.tar.gz: 6218b33d044790b48cc8f14b4d3d43cbef90a1189daf52b248db2052c45d36120b946412e449212726d63ccb8392640f2cebe4bc0d02c904aa688ae27cec3354
data/CHANGELOG.md CHANGED
@@ -1,5 +1,33 @@
1
1
  # Changelog
2
2
 
3
+ ## [Unreleased](https://github.com/rpush/rpush/tree/HEAD)
4
+
5
+ **Merged pull requests:**
6
+
7
+ * 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))
8
+ * 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.
9
+ * Fix silent APNS notifications for Apns2 and Apnsp8 [\#596](https://github.com/rpush/rpush/pull/596) ([shved270189](https://github.com/shved270189))
10
+
11
+ **Breaking:**
12
+
13
+ * Dropped support for Ruby 2.4, 2.5, 2.6 and Rails 5.2.
14
+
15
+ [Full Changelog](https://github.com/rpush/rpush/compare/v8.0.0...HEAD)
16
+
17
+ ## [v7.0.1](https://github.com/rpush/rpush/tree/v8.0.0) (2024-09-06)
18
+
19
+ **Merged pull requests:**
20
+
21
+ * 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))
22
+ * 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.
23
+ * Fix silent APNS notifications for Apns2 and Apnsp8 [\#596](https://github.com/rpush/rpush/pull/596) ([shved270189](https://github.com/shved270189))
24
+
25
+ **Breaking:**
26
+
27
+ * Dropped support for Ruby 2.4, 2.5, 2.6 and Rails 5.2.
28
+
29
+ [Full Changelog](https://github.com/rpush/rpush/compare/v7.0.1...v8.0.0)
30
+
3
31
  ## [v7.0.1](https://github.com/rpush/rpush/tree/v7.0.1) (2022-03-02)
4
32
 
5
33
  [Full Changelog](https://github.com/rpush/rpush/compare/v7.0.0...v7.0.1)
@@ -8,10 +36,6 @@
8
36
 
9
37
  - Fix deprecation warnings from the redis gem [\#636](https://github.com/rpush/rpush/pull/636) ([sharang-d](https://github.com/sharang-d))
10
38
 
11
- ## [Unreleased](https://github.com/rpush/rpush/tree/HEAD)
12
-
13
- [Full Changelog](https://github.com/rpush/rpush/compare/v7.0.0...HEAD)
14
-
15
39
  ## [v7.0.0](https://github.com/rpush/rpush/tree/HEAD)
16
40
 
17
41
  [Full Changelog](https://github.com/rpush/rpush/compare/v6.0.1...v7.0.0)
data/README.md CHANGED
@@ -83,6 +83,7 @@ n = Rpush::Apnsp8::Notification.new
83
83
  n.app = Rpush::Apnsp8::App.find_by_name("ios_app")
84
84
  n.device_token = "..." # hex string
85
85
  n.alert = "hi mom!"
86
+ # n.alert = { title: "push title", subtitle: "more to say", body: "hi mom!" }
86
87
  n.data = { foo: :bar }
87
88
  n.save!
88
89
  ```
@@ -107,6 +108,7 @@ n = Rpush::Apns2::Notification.new
107
108
  n.app = Rpush::Apns2::App.find_by_name("ios_app")
108
109
  n.device_token = "..." # hex string
109
110
  n.alert = "hi mom!"
111
+ # n.alert = { title: "push title", subtitle: "more to say", body: "hi mom!" }
110
112
  n.data = {
111
113
  headers: { 'apns-topic': "BUNDLE ID" }, # the bundle id of the app, like com.example.appname. Not necessary if set on the app (see above)
112
114
  foo: :bar
@@ -133,6 +135,7 @@ n = Rpush::Apns::Notification.new
133
135
  n.app = Rpush::Apns::App.find_by_name("ios_app")
134
136
  n.device_token = "..." # hex string
135
137
  n.alert = "hi mom!"
138
+ # n.alert = { title: "push title", subtitle: "more to say", body: "hi mom!" }
136
139
  n.data = { foo: :bar }
137
140
  n.save!
138
141
  ```
@@ -147,6 +150,40 @@ The app `environment` for any Apns* option is "development" for XCode installs,
147
150
 
148
151
  #### Firebase Cloud Messaging
149
152
 
153
+ ##### Firebase Cloud Messaging API (V1)
154
+
155
+ You will need two params to make use of FCM via Rpush.
156
+ - `firebase_project_id` - The `Project ID` in your Firebase Project Settings
157
+ - `json_key` - The JSON key file for a service account with the `Firebase Admin SDK Administrator Service Agent` role.
158
+
159
+ Create service account in the google cloud account attached to your firebase account:
160
+ https://console.cloud.google.com/iam-admin/serviceaccounts
161
+ Make sure it has Role `Firebase Admin SDK Administrator Service Agent`
162
+ Add + Download the json key for the service account.
163
+
164
+ Once you have those two params, you can create an FCM app and send notifications.
165
+
166
+ ```ruby
167
+ fcm_app = Rpush::Fcm::App.new
168
+ fcm_app.name = "fcm_app"
169
+ fcm_app.firebase_project_id = "someapp-123456"
170
+ fcm_app.json_key = Rails.root.join("your/key/somewhere.json").read # or from a ENV variable - just needs to be the whole json file
171
+ fcm_app.connections = 30
172
+ fcm_app.save!
173
+ ```
174
+
175
+ ```ruby
176
+ n = Rpush::Fcm::Notification.new
177
+ n.app = Rpush::Fcm::App.where(name: "fcm_app").first
178
+ n.device_token = device_token # Note that device_token is used here instead of registration_ids
179
+ n.data = {}.transform_values(&:to_s) # All values going in here have to be strings, if you have anything else - nothing goes through
180
+ n.save!
181
+ ```
182
+
183
+ ##### Cloud Messaging API (Legacy)
184
+
185
+ **Note:** Deprecated on 2023/6/20 and scheduled to be disabled on 2024/6/20.
186
+
150
187
  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.
151
188
 
152
189
  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).
@@ -470,7 +507,7 @@ This will run RSpec against all versions of Rails.
470
507
  You need to specify a `BUNDLE_GEMFILE` pointing to the gemfile before running the normal test command:
471
508
 
472
509
  ```
473
- BUNDLE_GEMFILE=gemfiles/rails_5.2.gemfile rspec spec/unit/apns_feedback_spec.rb
510
+ BUNDLE_GEMFILE=gemfiles/rails_6.0.gemfile rspec spec/unit/apns_feedback_spec.rb
474
511
  ```
475
512
 
476
513
  ##### Multiple database adapter support
@@ -53,6 +53,7 @@ class RpushMigrationGenerator < Rails::Generators::Base
53
53
  add_rpush_migration('rpush_4_1_0_updates')
54
54
  add_rpush_migration('rpush_4_1_1_updates')
55
55
  add_rpush_migration('rpush_4_2_0_updates')
56
+ add_rpush_migration('rpush_7_1_0_updates')
56
57
  end
57
58
 
58
59
  protected
@@ -80,6 +80,23 @@ Rpush.reflect do |on|
80
80
  # on.tcp_connection_lost do |app, error|
81
81
  # end
82
82
 
83
+ # Called for each recipient which successfully receives a notification. This
84
+ # can occur more than once for the same notification when there are multiple
85
+ # recipients.
86
+ # on.fcm_delivered_to_recipient do |notification|
87
+ # end
88
+
89
+ # Called for each recipient which fails to receive a notification. This
90
+ # can occur more than once for the same notification when there are multiple
91
+ # recipients. (do not handle invalid registration IDs here)
92
+ # on.fcm_failed_to_recipient do |notification, error|
93
+ # end
94
+
95
+ # Called when the FCM returns a failure that indicates an invalid device token.
96
+ # You will need to delete the device token from your records.
97
+ # on.fcm_invalid_device_token do |app, error, device_token|
98
+ # end
99
+
83
100
  # Called for each recipient which successfully receives a notification. This
84
101
  # can occur more than once for the same notification when there are multiple
85
102
  # recipients.
@@ -0,0 +1,12 @@
1
+ class Rpush710Updates < ActiveRecord::Migration["#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}"]
2
+ def self.up
3
+ add_column :rpush_apps, :firebase_project_id, :string
4
+ add_column :rpush_apps, :json_key, :text
5
+ end
6
+
7
+ def self.down
8
+ remove_column :rpush_apps, :firebase_project_id
9
+ remove_column :rpush_apps, :json_key
10
+ end
11
+ end
12
+
@@ -52,10 +52,6 @@ module Rpush
52
52
  self.data = (data || {}).merge(CONTENT_AVAILABLE_KEY => true)
53
53
  end
54
54
 
55
- def content_available?
56
- (self.data || {})[CONTENT_AVAILABLE_KEY]
57
- end
58
-
59
55
  def as_json(options = nil) # rubocop:disable Metrics/AbcSize, Metrics/PerceivedComplexity
60
56
  json = ActiveSupport::OrderedHash.new
61
57
 
@@ -0,0 +1,20 @@
1
+ module Rpush
2
+ module Client
3
+ module ActiveModel
4
+ module Fcm
5
+ module App
6
+ def self.included(base)
7
+ base.instance_eval do
8
+ # TODO: Add whatever validation is needed here
9
+ # validates :auth_key, presence: true
10
+ end
11
+ end
12
+
13
+ def service_name
14
+ 'fcm'
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,14 @@
1
+ module Rpush
2
+ module Client
3
+ module ActiveModel
4
+ module Fcm
5
+ class ExpiryCollapseKeyMutualInclusionValidator < ::ActiveModel::Validator
6
+ def validate(record)
7
+ return unless record.collapse_key && !record.expiry
8
+ record.errors.add :expiry, 'must be set when using a collapse_key'
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,129 @@
1
+ module Rpush
2
+ module Client
3
+ module ActiveModel
4
+ module Fcm
5
+ module Notification
6
+ FCM_PRIORITY_HIGH = Rpush::Client::ActiveModel::Apns::Notification::APNS_PRIORITY_IMMEDIATE
7
+ FCM_PRIORITY_NORMAL = Rpush::Client::ActiveModel::Apns::Notification::APNS_PRIORITY_CONSERVE_POWER
8
+ FCM_PRIORITIES = [FCM_PRIORITY_HIGH, FCM_PRIORITY_NORMAL]
9
+
10
+ ROOT_NOTIFICATION_KEYS = %w[title body image].freeze
11
+ ANDROID_NOTIFICATION_KEYS = %w[icon tag color click_action body_loc_key body_loc_args title_loc_key
12
+ title_loc_args channel_id ticker sticky event_time local_only
13
+ default_vibrate_timings default_light_settings vibrate_timings
14
+ visibility notification_count light_settings].freeze
15
+
16
+ def self.included(base)
17
+ base.instance_eval do
18
+ validates :device_token, presence: true
19
+ validates :priority, inclusion: { in: FCM_PRIORITIES }, allow_nil: true
20
+
21
+ validates_with Rpush::Client::ActiveModel::PayloadDataSizeValidator, limit: 4096
22
+ validates_with Rpush::Client::ActiveModel::RegistrationIdsCountValidator, limit: 1000
23
+
24
+ validates_with Rpush::Client::ActiveModel::Fcm::ExpiryCollapseKeyMutualInclusionValidator
25
+ validates_with Rpush::Client::ActiveModel::Fcm::NotificationKeysInAllowedListValidator
26
+ end
27
+ end
28
+
29
+ def payload_data_size
30
+ multi_json_dump(as_json['message']['data']).bytesize
31
+ end
32
+
33
+ # This is a hack. The schema defines `priority` to be an integer, but FCM expects a string.
34
+ # But for users of rpush to have an API they might expect (setting priority to `high`, not 10)
35
+ # we do a little conversion here.
36
+ def priority=(priority)
37
+ case priority
38
+ when 'high', FCM_PRIORITY_HIGH
39
+ super(FCM_PRIORITY_HIGH)
40
+ when 'normal', FCM_PRIORITY_NORMAL
41
+ super(FCM_PRIORITY_NORMAL)
42
+ else
43
+ errors.add(:priority, 'must be one of either "normal" or "high"')
44
+ end
45
+ end
46
+
47
+ def dry_run=(value)
48
+ fail ArgumentError, 'FCM does not support dry run' if value
49
+ end
50
+
51
+ def as_json(options = nil) # rubocop:disable Metrics/PerceivedComplexity
52
+ json = {
53
+ 'data' => data,
54
+ 'android' => android_config,
55
+ 'apns' => apns_config,
56
+ 'token' => device_token
57
+ }
58
+
59
+ json['notification'] = root_notification if notification
60
+ { 'message' => json }
61
+ end
62
+
63
+ def android_config
64
+ json = ActiveSupport::OrderedHash.new
65
+ json['notification'] = android_notification if notification
66
+ json['collapse_key'] = collapse_key if collapse_key
67
+ json['priority'] = priority_str if priority
68
+ json['ttl'] = "#{expiry}s" if expiry
69
+ json
70
+ end
71
+
72
+ def apns_config
73
+ json = ActiveSupport::OrderedHash.new
74
+ json['payload'] = ActiveSupport::OrderedHash.new
75
+
76
+ aps = ActiveSupport::OrderedHash.new
77
+ aps['mutable-content'] = 1 if mutable_content
78
+ aps['content-available'] = 1 if content_available
79
+ aps['sound'] = 'default' if sound == 'default'
80
+
81
+ json['payload']['aps'] = aps
82
+
83
+ json
84
+ end
85
+
86
+ def notification=(value)
87
+ value = value.with_indifferent_access if value.is_a?(Hash)
88
+ super(value)
89
+ end
90
+
91
+ def root_notification
92
+ return {} unless notification
93
+
94
+ notification.slice(*ROOT_NOTIFICATION_KEYS)
95
+ end
96
+
97
+ def android_notification
98
+ json = notification&.slice(*ANDROID_NOTIFICATION_KEYS) || {}
99
+ json['notification_priority'] = priority_for_notification if priority
100
+ json['sound'] = sound if sound
101
+ json['default_sound'] = sound == 'default' ? true : false
102
+ json
103
+ end
104
+
105
+ def priority_str
106
+ case
107
+ when priority <= 5 then 'normal'
108
+ else
109
+ 'high'
110
+ end
111
+ end
112
+
113
+ def priority_for_notification
114
+ case priority
115
+ when 0 then 'PRIORITY_UNSPECIFIED'
116
+ when 1 then 'PRIORITY_MIN'
117
+ when 2 then 'PRIORITY_LOW'
118
+ when 5 then 'PRIORITY_DEFAULT'
119
+ when 6 then 'PRIORITY_HIGH'
120
+ when 10 then 'PRIORITY_MAX'
121
+ else
122
+ 'PRIORITY_DEFAULT'
123
+ end
124
+ end
125
+ end
126
+ end
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,20 @@
1
+ module Rpush
2
+ module Client
3
+ module ActiveModel
4
+ module Fcm
5
+ class NotificationKeysInAllowedListValidator < ::ActiveModel::Validator
6
+ def validate(record)
7
+ return unless record.notification
8
+
9
+ allowed_keys = Notification::ROOT_NOTIFICATION_KEYS + Notification::ANDROID_NOTIFICATION_KEYS
10
+ invalid_keys = record.notification.keys - allowed_keys
11
+
12
+ return if invalid_keys.empty?
13
+
14
+ record.errors.add(:notification, "contains invalid keys: #{invalid_keys.join(', ')}")
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -20,6 +20,11 @@ require 'rpush/client/active_model/adm/data_validator'
20
20
  require 'rpush/client/active_model/adm/app'
21
21
  require 'rpush/client/active_model/adm/notification'
22
22
 
23
+ require 'rpush/client/active_model/fcm/expiry_collapse_key_mutual_inclusion_validator'
24
+ require 'rpush/client/active_model/fcm/notification_keys_in_allowed_list_validator'
25
+ require 'rpush/client/active_model/fcm/app'
26
+ require 'rpush/client/active_model/fcm/notification'
27
+
23
28
  require 'rpush/client/active_model/gcm/expiry_collapse_key_mutual_inclusion_validator'
24
29
  require 'rpush/client/active_model/gcm/app'
25
30
  require 'rpush/client/active_model/gcm/notification'
@@ -0,0 +1,11 @@
1
+ module Rpush
2
+ module Client
3
+ module ActiveRecord
4
+ module Fcm
5
+ class App < Rpush::Client::ActiveRecord::App
6
+ include Rpush::Client::ActiveModel::Fcm::App
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ module Rpush
2
+ module Client
3
+ module ActiveRecord
4
+ module Fcm
5
+ class Notification < Rpush::Client::ActiveRecord::Notification
6
+ include Rpush::Client::ActiveModel::Fcm::Notification
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -16,6 +16,9 @@ require 'rpush/client/active_record/apns2/app'
16
16
  require 'rpush/client/active_record/apnsp8/notification'
17
17
  require 'rpush/client/active_record/apnsp8/app'
18
18
 
19
+ require 'rpush/client/active_record/fcm/notification'
20
+ require 'rpush/client/active_record/fcm/app'
21
+
19
22
  require 'rpush/client/active_record/gcm/notification'
20
23
  require 'rpush/client/active_record/gcm/app'
21
24
 
@@ -19,6 +19,8 @@ module Rpush
19
19
  attribute :team_id, :string
20
20
  attribute :bundle_id, :string
21
21
  attribute :feedback_enabled, :boolean, default: true
22
+ attribute :firebase_project_id, :string
23
+ attribute :json_key, :string
22
24
 
23
25
  index :name
24
26
 
@@ -0,0 +1,11 @@
1
+ module Rpush
2
+ module Client
3
+ module Redis
4
+ module Fcm
5
+ class App < Rpush::Client::Redis::App
6
+ include Rpush::Client::ActiveModel::Fcm::App
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ module Rpush
2
+ module Client
3
+ module Redis
4
+ module Fcm
5
+ class Notification < Rpush::Client::Redis::Notification
6
+ include Rpush::Client::ActiveModel::Fcm::Notification
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -27,6 +27,9 @@ require 'rpush/client/redis/apns2/notification'
27
27
  require 'rpush/client/redis/apnsp8/app'
28
28
  require 'rpush/client/redis/apnsp8/notification'
29
29
 
30
+ require 'rpush/client/redis/fcm/app'
31
+ require 'rpush/client/redis/fcm/notification'
32
+
30
33
  require 'rpush/client/redis/gcm/app'
31
34
  require 'rpush/client/redis/gcm/notification'
32
35
 
@@ -106,7 +106,7 @@ module Rpush
106
106
  client_module = Rpush::Client.const_get(client.to_s.camelize)
107
107
  Rpush.send(:include, client_module) unless Rpush.ancestors.include?(client_module)
108
108
 
109
- [:Apns, :Gcm, :Wpns, :Wns, :Adm, :Pushy, :Webpush].each do |service|
109
+ [:Apns, :Fcm, :Gcm, :Wpns, :Wns, :Adm, :Pushy, :Webpush].each do |service|
110
110
  Rpush.const_set(service, client_module.const_get(service)) unless Rpush.const_defined?(service)
111
111
  end
112
112
 
@@ -112,7 +112,6 @@ module Rpush
112
112
  headers['apns-expiration'] = '0'
113
113
  headers['apns-priority'] = '10'
114
114
  headers['apns-topic'] = @app.bundle_id
115
- headers['apns-push-type'] = 'background' if notification.content_available?
116
115
 
117
116
  headers.merge notification_data(notification)[HTTP2_HEADERS_KEY] || {}
118
117
  end
@@ -149,7 +149,6 @@ module Rpush
149
149
  headers['apns-priority'] = '10'
150
150
  headers['apns-topic'] = @app.bundle_id
151
151
  headers['authorization'] = "bearer #{jwt_token}"
152
- headers['apns-push-type'] = 'background' if notification.content_available?
153
152
 
154
153
  headers.merge notification_data(notification)[HTTP2_HEADERS_KEY] || {}
155
154
  end
@@ -0,0 +1,162 @@
1
+ module Rpush
2
+ module Daemon
3
+ module Fcm
4
+ # https://firebase.google.com/docs/cloud-messaging/server
5
+ class Delivery < Rpush::Daemon::Delivery
6
+ include MultiJsonHelper
7
+
8
+ HOST = 'https://fcm.googleapis.com'.freeze
9
+ SCOPE = 'https://www.googleapis.com/auth/firebase.messaging'.freeze
10
+
11
+ def initialize(app, http, notification, batch)
12
+ if necessary_data_exists?(app)
13
+ @app = app
14
+ @http = http
15
+ @notification = notification
16
+ @batch = batch
17
+
18
+ @uri = URI.parse("#{HOST}/v1/projects/#{@app.firebase_project_id || ENV['FIREBASE_PROJECT_ID']}/messages:send")
19
+ else
20
+ Rpush.logger.error("Cannot find necessary configuration! Please make sure you have set all necessary ENV variables or firebase_project_id and json_key attributes.")
21
+ end
22
+ end
23
+
24
+ def perform
25
+ handle_response(do_post)
26
+ rescue SocketError => error
27
+ mark_retryable(@notification, Time.now + 10.seconds, error)
28
+ raise
29
+ rescue StandardError => error
30
+ mark_failed(error)
31
+ raise
32
+ ensure
33
+ @batch.notification_processed
34
+ end
35
+
36
+ protected
37
+
38
+ def handle_response(response)
39
+ case response.code.to_i
40
+ when 200
41
+ ok
42
+ when 400
43
+ bad_request(response)
44
+ when 401
45
+ unauthorized
46
+ when 403
47
+ sender_id_mismatch
48
+ when 404
49
+ unregistered(response)
50
+ when 429
51
+ too_many_requests
52
+ when 500
53
+ internal_server_error(response)
54
+ when 502
55
+ bad_gateway(response)
56
+ when 503
57
+ service_unavailable(response)
58
+ when 500..599
59
+ other_5xx_error(response)
60
+ else
61
+ fail Rpush::DeliveryError.new(response.code.to_i, @notification.id, Rpush::Daemon::HTTP_STATUS_CODES[response.code.to_i])
62
+ end
63
+ end
64
+
65
+ def ok
66
+ reflect(:fcm_delivered_to_recipient, @notification)
67
+ mark_delivered
68
+ log_info("#{@notification.id} sent to #{@notification.device_token}")
69
+ end
70
+
71
+ def bad_request(response)
72
+ fail Rpush::DeliveryError.new(400, @notification.id, "FCM failed to handle the JSON request. (#{parse_error(response)})")
73
+ end
74
+
75
+ def unauthorized
76
+ fail Rpush::DeliveryError.new(401, @notification.id, 'Unauthorized, Bearer token could not be validated.')
77
+ end
78
+
79
+ def sender_id_mismatch
80
+ fail Rpush::DeliveryError.new(403, @notification.id, 'The sender ID was mismatched. It seems the device token is wrong.')
81
+ end
82
+
83
+ def unregistered(response)
84
+ error = parse_error(response)
85
+ reflect(:fcm_invalid_device_token, @app, error, @notification.device_token)
86
+ fail Rpush::DeliveryError.new(404, @notification.id, "Client was not registered for your app. (#{error})")
87
+ end
88
+
89
+ def too_many_requests
90
+ fail Rpush::DeliveryError.new(429, @notification.id, 'Slow down. Too many requests were sent!')
91
+ end
92
+
93
+ def internal_server_error(response)
94
+ retry_delivery(@notification, response)
95
+ log_warn("FCM responded with an Internal Error. " + retry_message)
96
+ end
97
+
98
+ def bad_gateway(response)
99
+ retry_delivery(@notification, response)
100
+ log_warn("FCM responded with a Bad Gateway Error. " + retry_message)
101
+ end
102
+
103
+ def service_unavailable(response)
104
+ retry_delivery(@notification, response)
105
+ log_warn("FCM responded with an Service Unavailable Error. " + retry_message)
106
+ end
107
+
108
+ def other_5xx_error(response)
109
+ retry_delivery(@notification, response)
110
+ log_warn("FCM responded with a 5xx Error. " + retry_message)
111
+ end
112
+
113
+ def parse_error(response)
114
+ error = multi_json_load(response.body)['error']
115
+ "#{error['status']}: #{error['message']}"
116
+ end
117
+
118
+ def deliver_after_header(response)
119
+ Rpush::Daemon::RetryHeaderParser.parse(response.header['retry-after'])
120
+ end
121
+
122
+ def retry_delivery(notification, response)
123
+ time = deliver_after_header(response)
124
+ if time
125
+ mark_retryable(notification, time)
126
+ else
127
+ mark_retryable_exponential(notification)
128
+ end
129
+ end
130
+
131
+ def retry_message
132
+ "Notification #{@notification.id} will be retried after #{@notification.deliver_after.strftime('%Y-%m-%d %H:%M:%S')} (retry #{@notification.retries})."
133
+ end
134
+
135
+ def obtain_access_token
136
+ GoogleCredentialCache.instance.access_token(SCOPE, @app.json_key)
137
+ end
138
+
139
+ def do_post
140
+ token = obtain_access_token['access_token']
141
+ post = Net::HTTP::Post.new(@uri.path, 'Content-Type' => 'application/json',
142
+ 'Authorization' => "Bearer #{token}")
143
+ post.body = @notification.as_json.to_json
144
+ @http.request(@uri, post)
145
+ end
146
+
147
+ def necessary_data_exists?(app)
148
+ # Needed for Google Auth
149
+ # See https://github.com/googleapis/google-auth-library-ruby#example-environment-variables
150
+ # for further information
151
+ (app.firebase_project_id || ENV.key?('FIREBASE_PROJECT_ID')) &&
152
+ (app.json_key || (
153
+ ENV.key?('GOOGLE_ACCOUNT_TYPE') &&
154
+ ENV.key?('GOOGLE_CLIENT_ID') &&
155
+ ENV.key?('GOOGLE_CLIENT_EMAIL') &&
156
+ ENV.key?('GOOGLE_PRIVATE_KEY')
157
+ ))
158
+ end
159
+ end
160
+ end
161
+ end
162
+ end