rpush 7.0.1 → 8.0.0

Sign up to get free protection for your applications and to get access to all the features.
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