noticed 1.5.3 → 1.5.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3a91f0aa918496552c3b0f9153c1a4351a32f10910389780a71e8cb76d91df30
4
- data.tar.gz: 653ebc0c8ba59639e405fe78dbc6a4ce0480baf8abe0824f0f46e09cf47137d7
3
+ metadata.gz: a428bc0978afe8b14abcb97a3d1044aa6b44acca00822f4f561eb2315392420d
4
+ data.tar.gz: 5dc07c6c18523c083e981f0440f1c4d273f36e1b83e78c04e31f0822ada927c1
5
5
  SHA512:
6
- metadata.gz: f4bf8b33c4c68666e312451ee812ba5578d1eedb526108ef3e07f1c5f8a7eb57ed41cc2a077366d9c211ec72dd9fd9585c3297f0931dad6f80d23493fd98e96f
7
- data.tar.gz: b043568083a5515b00d32936d7883cf617299965bcc1365eeff748c36faf52575a8bcc3fae0c20101b5e669691eaa5d5cebc381004fb7b2e993099041e552ce4
6
+ metadata.gz: 958fde4c959e18dfa7c569d4c5e3449b13332bd9a2161b1e0083b327f148edfb1ac454030576dc7f366d82c62ae7c6466f4a6d27fd44b97927d1b57e391498ca
7
+ data.tar.gz: 1a18f5937d945ecf386a74002ca5dc7b61b441d583b41dd9383f1cb171ca442dd82827ecf93febf2053441cf566f3739b5eddbe8da2c7ab0ec7ac8b647e27f5e
data/README.md CHANGED
@@ -16,6 +16,7 @@ Currently, we support these notification delivery methods out of the box:
16
16
  * Twilio (SMS)
17
17
  * Vonage / Nexmo (SMS)
18
18
  * iOS Apple Push Notifications
19
+ * Firebase Cloud Messaging (Android and more)
19
20
 
20
21
  And you can easily add new notification types for any other delivery methods.
21
22
 
@@ -190,6 +191,31 @@ Example:
190
191
  deliver_by :slack, debug: true
191
192
  ```
192
193
 
194
+ ## ✅ Best Practices
195
+
196
+ A common use case is to trigger a notification when a record is created. For example,
197
+
198
+ ```ruby
199
+ class Message < ApplicationRecord
200
+ belongs_to :recipient, class_name: "User"
201
+
202
+ after_create_commit :notify_recipient
203
+
204
+ private
205
+
206
+ def notify_recipient
207
+ NewMessageNotification.with(message: self).deliver_later(recipient)
208
+ end
209
+ ```
210
+
211
+ If you are creating the notification on a background job (i.e. via `#deliver_later`), make sure you use a `commit` hook such as `after_create_commit` or `after_commit`.
212
+
213
+ Using `after_create` might cause the notification delivery methods to fail. This is because the job was enqueued while inside a database transaction, and the `Message` record might not yet be saved to the database.
214
+
215
+ A common symptom of this problem is undelivered notifications and the following error in your logs.
216
+
217
+ > `Discarded Noticed::DeliveryMethods::Email due to a ActiveJob::DeserializationError.`
218
+
193
219
  ## 🚛 Delivery Methods
194
220
 
195
221
  The delivery methods are designed to be modular so you can customize the way each type gets delivered.
@@ -205,6 +231,7 @@ For example, emails will require a subject, body, and email address while an SMS
205
231
  * [Test](docs/delivery_methods/test.md)
206
232
  * [Twilio](docs/delivery_methods/twilio.md)
207
233
  * [Vonage](docs/delivery_methods/vonage.md)
234
+ * [Firebase Cloud Messaging](docs/delivery_methods/fcm.md)
208
235
 
209
236
  ### Fallback Notifications
210
237
 
data/lib/noticed/base.rb CHANGED
@@ -81,6 +81,8 @@ module Noticed
81
81
  def run_delivery(recipient, enqueue: true)
82
82
  delivery_methods = self.class.delivery_methods.dup
83
83
 
84
+ self.recipient = recipient
85
+
84
86
  # Run database delivery inline first if it exists so other methods have access to the record
85
87
  if (index = delivery_methods.find_index { |m| m[:name] == :database })
86
88
  delivery_method = delivery_methods.delete_at(index)
@@ -29,8 +29,8 @@ module Noticed
29
29
  end
30
30
  end
31
31
 
32
- def perform(args)
33
- @notification = args[:notification_class].constantize.new(args[:params])
32
+ def assign_args(args)
33
+ @notification = args.fetch(:notification_class).constantize.new(args[:params])
34
34
  @options = args[:options] || {}
35
35
  @params = args[:params]
36
36
  @recipient = args[:recipient]
@@ -39,6 +39,11 @@ module Noticed
39
39
  # Make notification aware of database record and recipient during delivery
40
40
  @notification.record = args[:record]
41
41
  @notification.recipient = args[:recipient]
42
+ self
43
+ end
44
+
45
+ def perform(args)
46
+ assign_args(args)
42
47
 
43
48
  return if (condition = @options[:if]) && !@notification.send(condition)
44
49
  return if (condition = @options[:unless]) && @notification.send(condition)
@@ -57,16 +62,16 @@ module Noticed
57
62
  # Helper method for making POST requests from delivery methods
58
63
  #
59
64
  # Usage:
60
- # post("http://example.com", basic_auth: {user:, pass:}, json: {}, form: {})
65
+ # post("http://example.com", basic_auth: {user:, pass:}, headers: {}, json: {}, form: {})
61
66
  #
62
67
  def post(url, args = {})
68
+ options ||= {}
63
69
  basic_auth = args.delete(:basic_auth)
70
+ headers = args.delete(:headers)
64
71
 
65
- request = if basic_auth
66
- HTTP.basic_auth(user: basic_auth[:user], pass: basic_auth[:pass])
67
- else
68
- HTTP
69
- end
72
+ request = HTTP
73
+ request = request.basic_auth(user: basic_auth[:user], pass: basic_auth[:pass]) if basic_auth
74
+ request = request.headers(headers) if headers
70
75
 
71
76
  response = request.post(url, args)
72
77
 
@@ -76,6 +81,8 @@ module Noticed
76
81
  end
77
82
 
78
83
  if !options[:ignore_failure] && !response.status.success?
84
+ puts response.status
85
+ puts response.body
79
86
  raise ResponseUnsuccessful.new(response)
80
87
  end
81
88
 
@@ -0,0 +1,96 @@
1
+ require "googleauth"
2
+
3
+ # class CommentNotifier
4
+ # deliver_by :fcm, credentials: Rails.root.join("config/certs/fcm.json"), format: :format_notification
5
+ #
6
+ # deliver_by :fcm, credentials: :fcm_credentials
7
+ # def fcm_credentials
8
+ # { project_id: "api-12345" }
9
+ # end
10
+ # end
11
+
12
+ module Noticed
13
+ module DeliveryMethods
14
+ class Fcm < Base
15
+ BASE_URI = "https://fcm.googleapis.com/v1/projects/"
16
+
17
+ option :format
18
+
19
+ def deliver
20
+ device_tokens.each do |device_token|
21
+ post("#{BASE_URI}#{project_id}/messages:send", headers: {authorization: "Bearer #{access_token}"}, json: {message: format(device_token)})
22
+ rescue ResponseUnsuccessful => exception
23
+ if exception.code == 404
24
+ cleanup_invalid_token(device_token)
25
+ else
26
+ raise
27
+ end
28
+ end
29
+ end
30
+
31
+ def cleanup_invalid_token(device_token)
32
+ return unless notification.respond_to?(:cleanup_device_token)
33
+ notification.send(:cleanup_device_token, token: device_token, platform: "fcm")
34
+ end
35
+
36
+ def credentials
37
+ @credentials ||= begin
38
+ option = options[:credentials]
39
+ credentials_hash = case option
40
+ when Hash
41
+ option
42
+ when Pathname
43
+ load_json(option)
44
+ when String
45
+ load_json(Rails.root.join(option))
46
+ when Symbol
47
+ notification.send(option)
48
+ else
49
+ Rails.application.credentials.fcm
50
+ end
51
+
52
+ credentials_hash.symbolize_keys
53
+ end
54
+ end
55
+
56
+ def load_json(path)
57
+ JSON.parse(File.read(path))
58
+ end
59
+
60
+ def project_id
61
+ credentials[:project_id]
62
+ end
63
+
64
+ def access_token
65
+ token = authorizer.fetch_access_token!
66
+ token["access_token"]
67
+ end
68
+
69
+ def authorizer
70
+ @authorizer ||= options.fetch(:authorizer, Google::Auth::ServiceAccountCredentials).make_creds(
71
+ json_key_io: StringIO.new(credentials.to_json),
72
+ scope: "https://www.googleapis.com/auth/firebase.messaging"
73
+ )
74
+ end
75
+
76
+ def format(device_token)
77
+ notification.send(options[:format], device_token)
78
+ end
79
+
80
+ def device_tokens
81
+ if notification.respond_to?(:fcm_device_tokens)
82
+ Array.wrap(notification.fcm_device_tokens(recipient))
83
+ else
84
+ raise NoMethodError, <<~MESSAGE
85
+ You must implement `fcm_device_tokens` to send Firebase Cloud Messaging notifications
86
+
87
+ # This must return an Array of FCM device tokens
88
+ def fcm_device_tokens(recipient)
89
+ recipient.fcm_device_tokens.pluck(:token)
90
+ end
91
+ MESSAGE
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
@@ -9,7 +9,7 @@ module Noticed
9
9
  raise ArgumentError, "bundle_identifier is missing" if bundle_identifier.blank?
10
10
  raise ArgumentError, "key_id is missing" if key_id.blank?
11
11
  raise ArgumentError, "team_id is missing" if team_id.blank?
12
- raise ArgumentError, "Could not find APN cert at '#{cert_path}'" unless File.exist?(cert_path)
12
+ raise ArgumentError, "Could not find APN cert at '#{cert_path}'" unless valid_cert_path?
13
13
 
14
14
  device_tokens.each do |device_token|
15
15
  connection_pool.with do |connection|
@@ -51,8 +51,8 @@ module Noticed
51
51
  You must implement `ios_device_tokens` to send iOS notifications
52
52
 
53
53
  # This must return an Array of iOS device tokens
54
- def ios_device_tokens(user)
55
- user.ios_device_tokens.pluck(:token)
54
+ def ios_device_tokens(recipient)
55
+ recipient.ios_device_tokens.pluck(:token)
56
56
  end
57
57
  MESSAGE
58
58
  end
@@ -152,6 +152,15 @@ module Noticed
152
152
  end
153
153
  end
154
154
 
155
+ def valid_cert_path?
156
+ case cert_path
157
+ when File, StringIO
158
+ cert_path.size > 0
159
+ else
160
+ File.exist?(cert_path)
161
+ end
162
+ end
163
+
155
164
  def pool_options
156
165
  {
157
166
  size: options.fetch(:pool_size, 5)
@@ -1,3 +1,3 @@
1
1
  module Noticed
2
- VERSION = "1.5.3"
2
+ VERSION = "1.5.7"
3
3
  end
data/lib/noticed.rb CHANGED
@@ -22,6 +22,7 @@ module Noticed
22
22
  autoload :Test, "noticed/delivery_methods/test"
23
23
  autoload :Twilio, "noticed/delivery_methods/twilio"
24
24
  autoload :Vonage, "noticed/delivery_methods/vonage"
25
+ autoload :Fcm, "noticed/delivery_methods/fcm"
25
26
  end
26
27
 
27
28
  def self.notify(recipients:, notification:)
@@ -1,11 +1,11 @@
1
1
  # The following implements polyfills for Rails < 6.0
2
2
  module ActionCable
3
3
  # If the Rails 6.0 ActionCable::TestHelper is missing then allow it to autoload
4
- unless ActionCable.const_defined? "TestHelper"
4
+ unless ActionCable.const_defined? :TestHelper
5
5
  autoload :TestHelper, "rails_6_polyfills/actioncable/test_helper.rb"
6
6
  end
7
7
  # If the Rails 6.0 test SubscriptionAdapter is missing then allow it to autoload
8
- unless ActionCable.const_defined? "SubscriptionAdapter::Test"
8
+ unless ActionCable::SubscriptionAdapter.const_defined? :Test
9
9
  module SubscriptionAdapter
10
10
  autoload :Test, "rails_6_polyfills/actioncable/test_adapter.rb"
11
11
  end
@@ -13,6 +13,6 @@ module ActionCable
13
13
  end
14
14
 
15
15
  # If the Rails 6.0 ActionJob Serializers are missing then load support for them
16
- unless Object.const_defined?("ActiveJob::Serializers")
16
+ unless ActiveJob.const_defined?(:Serializers)
17
17
  require "rails_6_polyfills/activejob/serializers"
18
18
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: noticed
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.5.3
4
+ version: 1.5.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Oliver
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-11-17 00:00:00.000000000 Z
11
+ date: 2022-01-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -132,6 +132,7 @@ files:
132
132
  - lib/noticed/delivery_methods/base.rb
133
133
  - lib/noticed/delivery_methods/database.rb
134
134
  - lib/noticed/delivery_methods/email.rb
135
+ - lib/noticed/delivery_methods/fcm.rb
135
136
  - lib/noticed/delivery_methods/ios.rb
136
137
  - lib/noticed/delivery_methods/microsoft_teams.rb
137
138
  - lib/noticed/delivery_methods/slack.rb
@@ -169,7 +170,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
169
170
  - !ruby/object:Gem::Version
170
171
  version: '0'
171
172
  requirements: []
172
- rubygems_version: 3.2.22
173
+ rubygems_version: 3.3.3
173
174
  signing_key:
174
175
  specification_version: 4
175
176
  summary: Notifications for Ruby on Rails applications