noticed 3.0.0 → 3.1.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.
- checksums.yaml +4 -4
- data/README.md +16 -0
- data/app/jobs/noticed/event_job.rb +11 -5
- data/app/models/concerns/noticed/deliverable.rb +13 -17
- data/app/models/concerns/noticed/notification_methods.rb +4 -2
- data/app/models/noticed/deliverable/deliver_by.rb +18 -10
- data/app/models/noticed/ephemeral.rb +13 -9
- data/app/models/noticed/event.rb +1 -10
- data/lib/generators/noticed/install_generator.rb +3 -3
- data/lib/generators/noticed/templates/README +2 -2
- data/lib/generators/noticed/templates/bulk_delivery_method.rb.tt +1 -1
- data/lib/generators/noticed/templates/delivery_method.rb.tt +1 -1
- data/lib/noticed/api_client.rb +2 -2
- data/lib/noticed/bulk_delivery_method.rb +7 -10
- data/lib/noticed/bulk_delivery_methods/bluesky.rb +0 -1
- data/lib/noticed/bulk_delivery_methods/slack.rb +1 -33
- data/lib/noticed/bulk_delivery_methods/webhook.rb +1 -13
- data/lib/noticed/coder.rb +7 -1
- data/lib/noticed/delivery_methods/action_push_native.rb +3 -2
- data/lib/noticed/delivery_methods/discord.rb +1 -1
- data/lib/noticed/delivery_methods/fcm.rb +14 -2
- data/lib/noticed/delivery_methods/ios.rb +20 -40
- data/lib/noticed/delivery_methods/slack.rb +1 -33
- data/lib/noticed/delivery_methods/webhook.rb +1 -12
- data/lib/noticed/has_notifications.rb +2 -10
- data/lib/noticed/slack_delivery.rb +42 -0
- data/lib/noticed/translation.rb +1 -5
- data/lib/noticed/version.rb +1 -1
- data/lib/noticed/webhook_delivery.rb +21 -0
- data/lib/noticed.rb +1 -1
- metadata +8 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5a62b2258520a1d287a6ab2ff9b73288e37ebd7d4b18994c0b13bcfb591e9440
|
|
4
|
+
data.tar.gz: ecf9a251b05942f6b32c3730b923d59e16e4eb9de169e361b1ce0cb2989750f1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 75edf989bdb4bc2fd7ee445f2852107fdfd629696affeae51d7e3d407ebdd97669b5d11bf8bbc79d92c4dc22dfde7bddcdd528c6f52c0230e1b169ef716d95cb
|
|
7
|
+
data.tar.gz: d34cdd0a3e905188d6efe9852b1b199148e9b4e02e0677d446fbf48ab94fb98d6bc87bf9b7917280649b6182f5e7ef9e9d64af0cd43ef1db4ea45b23d4517225
|
data/README.md
CHANGED
|
@@ -51,6 +51,8 @@ Bulk delivery methods we support:
|
|
|
51
51
|
[Watch Screencast](https://www.youtube.com/watch?v=SzX-aBEqnAc)
|
|
52
52
|
|
|
53
53
|
## 🚀 Installation
|
|
54
|
+
Noticed requires Rails 7.1 or newer.
|
|
55
|
+
|
|
54
56
|
Run the following command to add Noticed to your Gemfile:
|
|
55
57
|
|
|
56
58
|
```ruby
|
|
@@ -374,6 +376,20 @@ Each of these options are available for every delivery method (individual or bul
|
|
|
374
376
|
* `config.if` — Intended for a lambda or method; runs after the `wait` if configured; cancels the delivery method if returns falsey
|
|
375
377
|
* `config.unless` — Intended for a lambda or method; runs after the `wait` if configured; cancels the delivery method if returns truthy
|
|
376
378
|
|
|
379
|
+
Lambdas for `if` and `unless` are evaluated in the context of the Notification. A symbol calls a method on the Notifier and passes the notification:
|
|
380
|
+
|
|
381
|
+
```ruby
|
|
382
|
+
class MessageNotifier < Noticed::Event
|
|
383
|
+
deliver_by :email do |config|
|
|
384
|
+
config.if = :email_enabled?
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
def email_enabled?(notification)
|
|
388
|
+
notification.recipient.email_notifications?
|
|
389
|
+
end
|
|
390
|
+
end
|
|
391
|
+
```
|
|
392
|
+
|
|
377
393
|
The following are evaluated in the context of the Notification so it can be customize to the recipient:
|
|
378
394
|
|
|
379
395
|
* `config.wait` — (Should yield an `ActiveSupport::Duration`) Delays the job that runs this delivery method for the given duration of time
|
|
@@ -6,11 +6,17 @@ module Noticed
|
|
|
6
6
|
deliver_by.perform_later(event) if deliver_by.perform?(event)
|
|
7
7
|
end
|
|
8
8
|
|
|
9
|
-
# Enqueue individual deliveries
|
|
10
|
-
event.notifications.
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
# Enqueue individual deliveries in batches so large recipient lists don't load into memory all at once
|
|
10
|
+
event.notifications.find_in_batches do |notifications|
|
|
11
|
+
ActiveJob.perform_all_later notifications.flat_map { |notification| delivery_jobs_for(event, notification) }
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
def delivery_jobs_for(event, notification)
|
|
18
|
+
event.delivery_methods.values.filter_map do |deliver_by|
|
|
19
|
+
deliver_by.job(notification) if deliver_by.perform?(notification)
|
|
14
20
|
end
|
|
15
21
|
end
|
|
16
22
|
end
|
|
@@ -18,7 +18,7 @@ module Noticed
|
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
def bulk_deliver_by(name, options = {})
|
|
21
|
-
raise
|
|
21
|
+
raise ArgumentError, "#{name} has already been used for this Notifier." if bulk_delivery_methods.has_key?(name)
|
|
22
22
|
|
|
23
23
|
config = ActiveSupport::OrderedOptions.new.merge(options)
|
|
24
24
|
yield config if block_given?
|
|
@@ -26,7 +26,7 @@ module Noticed
|
|
|
26
26
|
end
|
|
27
27
|
|
|
28
28
|
def deliver_by(name, options = {})
|
|
29
|
-
raise
|
|
29
|
+
raise ArgumentError, "#{name} has already been used for this Notifier." if delivery_methods.has_key?(name)
|
|
30
30
|
|
|
31
31
|
if name == :database
|
|
32
32
|
Noticed.deprecator.warn <<-WARNING.squish
|
|
@@ -67,15 +67,17 @@ module Noticed
|
|
|
67
67
|
if self < Ephemeral
|
|
68
68
|
new(params: params)
|
|
69
69
|
else
|
|
70
|
-
|
|
71
|
-
new(params: params, record: record)
|
|
70
|
+
new(params: params.except(:record), record: params[:record])
|
|
72
71
|
end
|
|
73
72
|
end
|
|
74
73
|
|
|
75
74
|
def deliver(recipients = nil, **options)
|
|
76
75
|
new.deliver(recipients, **options)
|
|
77
76
|
end
|
|
78
|
-
|
|
77
|
+
|
|
78
|
+
def deliver_later(...)
|
|
79
|
+
deliver(...)
|
|
80
|
+
end
|
|
79
81
|
end
|
|
80
82
|
|
|
81
83
|
# CommentNotifier.deliver(User.all)
|
|
@@ -96,16 +98,7 @@ module Noticed
|
|
|
96
98
|
self.notifications_count = recipients_attributes.size
|
|
97
99
|
save!
|
|
98
100
|
|
|
99
|
-
|
|
100
|
-
notifications.insert_all!(recipients_attributes, record_timestamps: true) if recipients_attributes.any?
|
|
101
|
-
else
|
|
102
|
-
time = Time.current
|
|
103
|
-
recipients_attributes.each do |attributes|
|
|
104
|
-
attributes[:created_at] = time
|
|
105
|
-
attributes[:updated_at] = time
|
|
106
|
-
end
|
|
107
|
-
notifications.insert_all!(recipients_attributes) if recipients_attributes.any?
|
|
108
|
-
end
|
|
101
|
+
notifications.insert_all!(recipients_attributes, record_timestamps: true) if recipients_attributes.any?
|
|
109
102
|
end
|
|
110
103
|
|
|
111
104
|
# Enqueue delivery job
|
|
@@ -113,14 +106,17 @@ module Noticed
|
|
|
113
106
|
|
|
114
107
|
self
|
|
115
108
|
end
|
|
116
|
-
|
|
109
|
+
|
|
110
|
+
def deliver_later(...)
|
|
111
|
+
deliver(...)
|
|
112
|
+
end
|
|
117
113
|
|
|
118
114
|
def evaluate_recipients
|
|
119
115
|
return unless _recipients
|
|
120
116
|
|
|
121
117
|
if _recipients.respond_to?(:call, true)
|
|
122
118
|
instance_exec(&_recipients)
|
|
123
|
-
|
|
119
|
+
else
|
|
124
120
|
send(_recipients)
|
|
125
121
|
end
|
|
126
122
|
end
|
|
@@ -4,13 +4,15 @@ module Noticed
|
|
|
4
4
|
|
|
5
5
|
class_methods do
|
|
6
6
|
# Generate a Notification class each time a Notifier is defined
|
|
7
|
+
#
|
|
8
|
+
# Lookup must not inherit, otherwise a top-level ::Notification model in the host app would be used instead of Noticed::Notification
|
|
7
9
|
def inherited(notifier)
|
|
8
10
|
super
|
|
9
|
-
notifier.const_set :Notification, Class.new(const_defined?(:Notification) ? const_get(:Notification) : Noticed::Notification)
|
|
11
|
+
notifier.const_set :Notification, Class.new(const_defined?(:Notification, false) ? const_get(:Notification, false) : Noticed::Notification)
|
|
10
12
|
end
|
|
11
13
|
|
|
12
14
|
def notification_methods(&block)
|
|
13
|
-
const_get(:Notification).class_eval(&block)
|
|
15
|
+
const_get(:Notification, false).class_eval(&block)
|
|
14
16
|
end
|
|
15
17
|
end
|
|
16
18
|
end
|
|
@@ -4,7 +4,7 @@ module Noticed
|
|
|
4
4
|
attr_reader :name, :config, :bulk
|
|
5
5
|
|
|
6
6
|
def initialize(name, config, bulk: false)
|
|
7
|
-
@name, @config, @bulk
|
|
7
|
+
@name, @config, @bulk = name, config, bulk
|
|
8
8
|
end
|
|
9
9
|
|
|
10
10
|
def constant
|
|
@@ -14,16 +14,23 @@ module Noticed
|
|
|
14
14
|
|
|
15
15
|
def validate!
|
|
16
16
|
constant.required_option_names.each do |option|
|
|
17
|
-
raise ValidationError, "option `#{option}` must be set for `deliver_by :#{name}`"
|
|
17
|
+
raise ValidationError, "option `#{option}` must be set for `deliver_by :#{name}`" if config[option].nil?
|
|
18
18
|
end
|
|
19
19
|
end
|
|
20
20
|
|
|
21
|
+
# Builds the delivery job without enqueuing it, so jobs can be enqueued in bulk
|
|
22
|
+
def job(event_or_notification, options = {})
|
|
23
|
+
constant.new(name, event_or_notification).set(computed_options(options, event_or_notification))
|
|
24
|
+
end
|
|
25
|
+
|
|
21
26
|
def perform_later(event_or_notification, options = {})
|
|
22
|
-
|
|
27
|
+
job(event_or_notification, options).enqueue
|
|
23
28
|
end
|
|
24
29
|
|
|
25
|
-
|
|
26
|
-
|
|
30
|
+
# Ephemeral notifiers aren't persisted, so the notifier name, recipient(s), and params are passed to the job instead.
|
|
31
|
+
# Options like `wait` are evaluated against `context` (the ephemeral Event or Notification), matching the persisted path.
|
|
32
|
+
def ephemeral_perform_later(notifier, recipient, params, options, context:)
|
|
33
|
+
constant.set(computed_options(options, context))
|
|
27
34
|
.perform_later(name, "#{notifier}::Notification", recipient: recipient, params: params)
|
|
28
35
|
end
|
|
29
36
|
|
|
@@ -52,11 +59,12 @@ module Noticed
|
|
|
52
59
|
|
|
53
60
|
private
|
|
54
61
|
|
|
55
|
-
def computed_options(options,
|
|
56
|
-
options
|
|
57
|
-
options[:
|
|
58
|
-
options[:
|
|
59
|
-
options[:
|
|
62
|
+
def computed_options(options, context)
|
|
63
|
+
options = options.dup
|
|
64
|
+
options[:wait] ||= evaluate_option(:wait, context) if config.has_key?(:wait)
|
|
65
|
+
options[:wait_until] ||= evaluate_option(:wait_until, context) if config.has_key?(:wait_until)
|
|
66
|
+
options[:queue] ||= evaluate_option(:queue, context) if config.has_key?(:queue)
|
|
67
|
+
options[:priority] ||= evaluate_option(:priority, context) if config.has_key?(:priority)
|
|
60
68
|
options
|
|
61
69
|
end
|
|
62
70
|
end
|
|
@@ -6,7 +6,6 @@ module Noticed
|
|
|
6
6
|
include Noticed::Translation
|
|
7
7
|
include Rails.application.routes.url_helpers
|
|
8
8
|
|
|
9
|
-
attribute :record
|
|
10
9
|
attribute :params, default: {}
|
|
11
10
|
|
|
12
11
|
class Notification
|
|
@@ -27,27 +26,32 @@ module Noticed
|
|
|
27
26
|
end
|
|
28
27
|
end
|
|
29
28
|
|
|
30
|
-
# Dynamically define Notification on each Ephemeral Notifier
|
|
29
|
+
# Dynamically define Notification on each Ephemeral Notifier, inheriting from the parent Notifier's Notification
|
|
31
30
|
def self.inherited(notifier)
|
|
32
31
|
super
|
|
33
|
-
notifier.const_set :Notification, Class.new(Noticed::Ephemeral::Notification)
|
|
32
|
+
notifier.const_set :Notification, Class.new(const_defined?(:Notification, false) ? const_get(:Notification, false) : Noticed::Ephemeral::Notification)
|
|
34
33
|
end
|
|
35
34
|
|
|
36
35
|
def self.notification_methods(&block)
|
|
37
|
-
const_get(:Notification).class_eval(&block)
|
|
36
|
+
const_get(:Notification, false).class_eval(&block)
|
|
38
37
|
end
|
|
39
38
|
|
|
40
|
-
|
|
39
|
+
# EphemeralNotifier.with(message: "test").deliver(User.all, wait: 5.minutes)
|
|
40
|
+
def deliver(recipients = nil, **options)
|
|
41
41
|
recipients ||= evaluate_recipients
|
|
42
42
|
recipients = Array.wrap(recipients)
|
|
43
43
|
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
validate!
|
|
45
|
+
|
|
46
|
+
bulk_delivery_methods.each_value do |deliver_by|
|
|
47
|
+
deliver_by.ephemeral_perform_later(self.class.name, recipients, params, options, context: self) if deliver_by.perform?(self)
|
|
46
48
|
end
|
|
47
49
|
|
|
48
50
|
recipients.each do |recipient|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
+
notification = self.class::Notification.new(recipient: recipient, event: self)
|
|
52
|
+
|
|
53
|
+
delivery_methods.each_value do |deliver_by|
|
|
54
|
+
deliver_by.ephemeral_perform_later(self.class.name, recipient, params, options, context: notification) if deliver_by.perform?(notification)
|
|
51
55
|
end
|
|
52
56
|
end
|
|
53
57
|
|
data/app/models/noticed/event.rb
CHANGED
|
@@ -8,20 +8,11 @@ module Noticed
|
|
|
8
8
|
belongs_to :record, polymorphic: true, optional: true
|
|
9
9
|
has_many :notifications, dependent: :delete_all
|
|
10
10
|
|
|
11
|
-
accepts_nested_attributes_for :notifications
|
|
12
|
-
|
|
13
11
|
scope :newest_first, -> { order(created_at: :desc) }
|
|
14
12
|
|
|
15
13
|
attribute :params, :json, default: {}
|
|
16
14
|
|
|
17
|
-
|
|
18
|
-
if respond_to? :serialize
|
|
19
|
-
if Rails.gem_version >= Gem::Version.new("7.1.0.alpha")
|
|
20
|
-
serialize :params, coder: Coder
|
|
21
|
-
else
|
|
22
|
-
serialize :params, Coder
|
|
23
|
-
end
|
|
24
|
-
end
|
|
15
|
+
serialize :params, coder: Coder
|
|
25
16
|
end
|
|
26
17
|
end
|
|
27
18
|
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
module Noticed
|
|
4
4
|
module Generators
|
|
5
|
-
class
|
|
6
|
-
include Rails::Generators::ResourceHelpers
|
|
7
|
-
|
|
5
|
+
class InstallGenerator < Rails::Generators::Base
|
|
8
6
|
source_root File.expand_path("../templates", __FILE__)
|
|
9
7
|
|
|
8
|
+
desc "Copies the Noticed migrations into your application."
|
|
9
|
+
|
|
10
10
|
def create_migrations
|
|
11
11
|
rails_command "railties:install:migrations FROM=noticed", inline: true
|
|
12
12
|
end
|
|
@@ -4,5 +4,5 @@
|
|
|
4
4
|
Next steps:
|
|
5
5
|
1. Run `rails db:migrate`
|
|
6
6
|
2. Add `has_many :notifications, as: :recipient, dependent: :destroy, class_name: "Noticed::Notification"` to your User model(s).
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
3. Add `has_many :notifications, as: :record, dependent: :destroy, class_name: "Noticed::Event"` to your model(s) that notifications reference.
|
|
8
|
+
4. Generate notifiers with "rails g noticed:notifier"
|
|
@@ -2,7 +2,7 @@ class BulkDeliveryMethods::<%= class_name %> < ApplicationBulkDeliveryMethod
|
|
|
2
2
|
# To use this delivery method, specify the class option in your notifier.
|
|
3
3
|
#
|
|
4
4
|
# class MyNotifer < ApplicationNotifier
|
|
5
|
-
# bulk_deliver_by :<%=
|
|
5
|
+
# bulk_deliver_by :<%= file_name %>, class: "BulkDeliveryMethods::<%= class_name %>"
|
|
6
6
|
# end
|
|
7
7
|
|
|
8
8
|
# Specify required options for the deliver_by config block
|
|
@@ -2,7 +2,7 @@ class DeliveryMethods::<%= class_name %> < ApplicationDeliveryMethod
|
|
|
2
2
|
# To use this delivery method, specify the class option in your notifier.
|
|
3
3
|
#
|
|
4
4
|
# class MyNotifer < ApplicationNotifier
|
|
5
|
-
# deliver_by :<%=
|
|
5
|
+
# deliver_by :<%= file_name %>, class: "DeliveryMethods::<%= class_name %>"
|
|
6
6
|
# end
|
|
7
7
|
|
|
8
8
|
# Specify required options for the deliver_by config block
|
data/lib/noticed/api_client.rb
CHANGED
|
@@ -33,10 +33,10 @@ module Noticed
|
|
|
33
33
|
request.body = body
|
|
34
34
|
end
|
|
35
35
|
|
|
36
|
+
# Bodies aren't logged since they can contain credentials and access tokens
|
|
36
37
|
logger.debug("POST #{url}")
|
|
37
|
-
logger.debug(request.body)
|
|
38
38
|
response = http.request(request)
|
|
39
|
-
logger.debug("Response: #{response.code}
|
|
39
|
+
logger.debug("Response: #{response.code}")
|
|
40
40
|
|
|
41
41
|
raise ResponseUnsuccessful.new(response, url, args) unless response.code.start_with?("20")
|
|
42
42
|
|
|
@@ -13,13 +13,10 @@ module Noticed
|
|
|
13
13
|
|
|
14
14
|
def perform(delivery_method_name, event, recipient: nil, params: {}, overrides: {})
|
|
15
15
|
# Ephemeral notifications
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
@event = event
|
|
21
|
-
@config = event.bulk_delivery_methods.fetch(delivery_method_name).config.merge(overrides)
|
|
22
|
-
end
|
|
16
|
+
@event = event.is_a?(String) ? event.constantize.new_with_params(recipient, params).event : event
|
|
17
|
+
|
|
18
|
+
# Look up config from Notifier and merge overrides
|
|
19
|
+
@config = @event.bulk_delivery_methods.fetch(delivery_method_name).config.merge(overrides)
|
|
23
20
|
|
|
24
21
|
return false if config.has_key?(:if) && !evaluate_option(:if)
|
|
25
22
|
return false if config.has_key?(:unless) && evaluate_option(:unless)
|
|
@@ -34,15 +31,15 @@ module Noticed
|
|
|
34
31
|
end
|
|
35
32
|
|
|
36
33
|
def fetch_constant(name)
|
|
37
|
-
option =
|
|
38
|
-
option.is_a?(String) ? option.constantize :
|
|
34
|
+
option = evaluate_option(name)
|
|
35
|
+
option.is_a?(String) ? option.constantize : option
|
|
39
36
|
end
|
|
40
37
|
|
|
41
38
|
def evaluate_option(name)
|
|
42
39
|
option = config[name]
|
|
43
40
|
|
|
44
41
|
# Evaluate Proc within the context of the notifier
|
|
45
|
-
if option
|
|
42
|
+
if option.respond_to?(:call)
|
|
46
43
|
event.instance_exec(&option)
|
|
47
44
|
|
|
48
45
|
# Call method if symbol and matching method
|
|
@@ -1,39 +1,7 @@
|
|
|
1
1
|
module Noticed
|
|
2
2
|
module BulkDeliveryMethods
|
|
3
3
|
class Slack < BulkDeliveryMethod
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
required_options :json
|
|
7
|
-
|
|
8
|
-
def deliver
|
|
9
|
-
headers = evaluate_option(:headers)
|
|
10
|
-
json = evaluate_option(:json)
|
|
11
|
-
response = post_request url, headers: headers, json: json
|
|
12
|
-
|
|
13
|
-
if raise_if_not_ok? && !success?(response)
|
|
14
|
-
raise ResponseUnsuccessful.new(response, url, {headers: headers, json: json})
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
response
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
def url
|
|
21
|
-
evaluate_option(:url) || DEFAULT_URL
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
def raise_if_not_ok?
|
|
25
|
-
value = evaluate_option(:raise_if_not_ok)
|
|
26
|
-
value.nil? || value
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
def success?(response)
|
|
30
|
-
if response.content_type == "application/json"
|
|
31
|
-
JSON.parse(response.body).dig("ok")
|
|
32
|
-
else
|
|
33
|
-
# https://api.slack.com/changelog/2016-05-17-changes-to-errors-for-incoming-webhooks
|
|
34
|
-
response.is_a?(Net::HTTPSuccess)
|
|
35
|
-
end
|
|
36
|
-
end
|
|
4
|
+
include SlackDelivery
|
|
37
5
|
end
|
|
38
6
|
end
|
|
39
7
|
end
|
|
@@ -1,19 +1,7 @@
|
|
|
1
1
|
module Noticed
|
|
2
2
|
module BulkDeliveryMethods
|
|
3
3
|
class Webhook < BulkDeliveryMethod
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
def deliver
|
|
7
|
-
Rails.logger.debug(evaluate_option(:json))
|
|
8
|
-
post_request(
|
|
9
|
-
evaluate_option(:url),
|
|
10
|
-
basic_auth: evaluate_option(:basic_auth),
|
|
11
|
-
headers: evaluate_option(:headers),
|
|
12
|
-
json: evaluate_option(:json),
|
|
13
|
-
form: evaluate_option(:form),
|
|
14
|
-
body: evaluate_option(:body)
|
|
15
|
-
)
|
|
16
|
-
end
|
|
4
|
+
include WebhookDelivery
|
|
17
5
|
end
|
|
18
6
|
end
|
|
19
7
|
end
|
data/lib/noticed/coder.rb
CHANGED
|
@@ -2,10 +2,16 @@ require "active_job/arguments"
|
|
|
2
2
|
|
|
3
3
|
module Noticed
|
|
4
4
|
class Coder
|
|
5
|
+
# Rails 8.2+ (globalid 1.4+) raises GlobalID::Locator::RecordNotFound for missing records
|
|
6
|
+
# instead of ActiveRecord::RecordNotFound
|
|
7
|
+
RECORD_NOT_FOUND_ERRORS = [ActiveRecord::RecordNotFound].tap do |errors|
|
|
8
|
+
errors << GlobalID::Locator::RecordNotFound if defined?(GlobalID::Locator::RecordNotFound)
|
|
9
|
+
end.freeze
|
|
10
|
+
|
|
5
11
|
def self.load(data)
|
|
6
12
|
return if data.nil?
|
|
7
13
|
ActiveJob::Arguments.send(:deserialize_argument, data)
|
|
8
|
-
rescue
|
|
14
|
+
rescue *RECORD_NOT_FOUND_ERRORS => error
|
|
9
15
|
{noticed_error: error.message, original_params: data}
|
|
10
16
|
end
|
|
11
17
|
|
|
@@ -4,7 +4,7 @@ module Noticed
|
|
|
4
4
|
required_options :devices, :format
|
|
5
5
|
|
|
6
6
|
def deliver
|
|
7
|
-
notification =
|
|
7
|
+
notification = evaluate_option(:silent) ? notification_class.silent : notification_class
|
|
8
8
|
|
|
9
9
|
notification
|
|
10
10
|
.with_apple(evaluate_option(:with_apple))
|
|
@@ -14,8 +14,9 @@ module Noticed
|
|
|
14
14
|
.deliver_later_to(evaluate_option(:devices))
|
|
15
15
|
end
|
|
16
16
|
|
|
17
|
+
# `notification_class` rather than `class` since `config.class` is reserved for overriding the delivery method
|
|
17
18
|
def notification_class
|
|
18
|
-
fetch_constant(:
|
|
19
|
+
fetch_constant(:notification_class) || ApplicationPushNotification
|
|
19
20
|
end
|
|
20
21
|
end
|
|
21
22
|
end
|
|
@@ -34,8 +34,17 @@ module Noticed
|
|
|
34
34
|
end
|
|
35
35
|
end
|
|
36
36
|
|
|
37
|
+
# FCM returns 404 UNREGISTERED for tokens that are no longer valid. 400 INVALID_ARGUMENT is also
|
|
38
|
+
# returned for malformed payloads, so only treat it as a bad token when the error is about the token.
|
|
39
|
+
# https://firebase.google.com/docs/reference/fcm/rest/v1/ErrorCode
|
|
37
40
|
def bad_token?(response)
|
|
38
|
-
response.code == "404" || response.code == "400"
|
|
41
|
+
response.code == "404" || (response.code == "400" && token_error?(response))
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def token_error?(response)
|
|
45
|
+
JSON.parse(response.body).dig("error", "message").to_s.match?(/registration token/i)
|
|
46
|
+
rescue JSON::ParserError
|
|
47
|
+
false
|
|
39
48
|
end
|
|
40
49
|
|
|
41
50
|
def credentials
|
|
@@ -59,11 +68,14 @@ module Noticed
|
|
|
59
68
|
end
|
|
60
69
|
|
|
61
70
|
def access_token
|
|
71
|
+
@access_token ||= authorizer.fetch_access_token!["access_token"]
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def authorizer
|
|
62
75
|
@authorizer ||= (evaluate_option(:authorizer) || Google::Auth::ServiceAccountCredentials).make_creds(
|
|
63
76
|
json_key_io: StringIO.new(credentials.to_json),
|
|
64
77
|
scope: "https://www.googleapis.com/auth/firebase.messaging"
|
|
65
78
|
)
|
|
66
|
-
@authorizer.fetch_access_token!["access_token"]
|
|
67
79
|
end
|
|
68
80
|
end
|
|
69
81
|
end
|
|
@@ -3,29 +3,29 @@ require "apnotic"
|
|
|
3
3
|
module Noticed
|
|
4
4
|
module DeliveryMethods
|
|
5
5
|
class Ios < DeliveryMethod
|
|
6
|
-
cattr_accessor :development_connection_pool, :production_connection_pool
|
|
7
|
-
|
|
8
6
|
required_options :bundle_identifier, :key_id, :team_id, :apns_key, :device_tokens
|
|
9
7
|
|
|
8
|
+
# A connection is opened per delivery and closed afterwards. Long-lived connections
|
|
9
|
+
# get reset by Apple when idle, which stalls the next push for over a minute.
|
|
10
10
|
def deliver
|
|
11
|
+
connection = new_connection
|
|
12
|
+
|
|
11
13
|
evaluate_option(:device_tokens).each do |device_token|
|
|
12
14
|
apn = Apnotic::Notification.new(device_token)
|
|
13
15
|
format_notification(apn)
|
|
14
16
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
response = connection.push(apn)
|
|
18
|
-
raise "Timeout sending iOS push notification" unless response
|
|
19
|
-
connection.close
|
|
17
|
+
response = connection.push(apn)
|
|
18
|
+
raise "Timeout sending iOS push notification" unless response
|
|
20
19
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
end
|
|
20
|
+
if bad_token?(response) && config[:invalid_token]
|
|
21
|
+
# Allow notification to cleanup invalid iOS device tokens
|
|
22
|
+
notification.instance_exec(device_token, &config[:invalid_token])
|
|
23
|
+
elsif !response.ok?
|
|
24
|
+
raise "Request failed #{response.body}"
|
|
27
25
|
end
|
|
28
26
|
end
|
|
27
|
+
ensure
|
|
28
|
+
connection&.close
|
|
29
29
|
end
|
|
30
30
|
|
|
31
31
|
private
|
|
@@ -51,32 +51,16 @@ module Noticed
|
|
|
51
51
|
response.status == "410" || (response.status == "400" && response.body["reason"] == "BadDeviceToken")
|
|
52
52
|
end
|
|
53
53
|
|
|
54
|
-
def
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
self.class.production_connection_pool ||= new_connection_pool(development: false)
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
def new_connection_pool(development:)
|
|
63
|
-
handler = proc do |connection|
|
|
64
|
-
connection.on(:error) do |exception|
|
|
65
|
-
Rails.logger.info "Apnotic exception raised: #{exception}"
|
|
66
|
-
if config[:error_handler].respond_to?(:call)
|
|
67
|
-
notification.instance_exec(exception, &config[:error_handler])
|
|
68
|
-
end
|
|
69
|
-
end
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
if development
|
|
73
|
-
Apnotic::ConnectionPool.development(connection_pool_options, pool_options, &handler)
|
|
74
|
-
else
|
|
75
|
-
Apnotic::ConnectionPool.new(connection_pool_options, pool_options, &handler)
|
|
54
|
+
def new_connection
|
|
55
|
+
connection = evaluate_option(:development) ? Apnotic::Connection.development(connection_options) : Apnotic::Connection.new(connection_options)
|
|
56
|
+
connection.on(:error) do |exception|
|
|
57
|
+
Rails.logger.info "Apnotic exception raised: #{exception}"
|
|
58
|
+
notification.instance_exec(exception, &config[:error_handler]) if config[:error_handler]
|
|
76
59
|
end
|
|
60
|
+
connection
|
|
77
61
|
end
|
|
78
62
|
|
|
79
|
-
def
|
|
63
|
+
def connection_options
|
|
80
64
|
{
|
|
81
65
|
auth_method: :token,
|
|
82
66
|
cert_path: StringIO.new(evaluate_option(:apns_key)),
|
|
@@ -84,10 +68,6 @@ module Noticed
|
|
|
84
68
|
team_id: evaluate_option(:team_id)
|
|
85
69
|
}
|
|
86
70
|
end
|
|
87
|
-
|
|
88
|
-
def pool_options
|
|
89
|
-
{size: evaluate_option(:pool_size) || 5}
|
|
90
|
-
end
|
|
91
71
|
end
|
|
92
72
|
end
|
|
93
73
|
end
|
|
@@ -1,39 +1,7 @@
|
|
|
1
1
|
module Noticed
|
|
2
2
|
module DeliveryMethods
|
|
3
3
|
class Slack < DeliveryMethod
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
required_options :json
|
|
7
|
-
|
|
8
|
-
def deliver
|
|
9
|
-
headers = evaluate_option(:headers)
|
|
10
|
-
json = evaluate_option(:json)
|
|
11
|
-
response = post_request url, headers: headers, json: json
|
|
12
|
-
|
|
13
|
-
if raise_if_not_ok? && !success?(response)
|
|
14
|
-
raise ResponseUnsuccessful.new(response, url, {headers: headers, json: json})
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
response
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
def url
|
|
21
|
-
evaluate_option(:url) || DEFAULT_URL
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
def raise_if_not_ok?
|
|
25
|
-
value = evaluate_option(:raise_if_not_ok)
|
|
26
|
-
value.nil? || value
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
def success?(response)
|
|
30
|
-
if response.content_type == "application/json"
|
|
31
|
-
JSON.parse(response.body).dig("ok")
|
|
32
|
-
else
|
|
33
|
-
# https://api.slack.com/changelog/2016-05-17-changes-to-errors-for-incoming-webhooks
|
|
34
|
-
response.is_a?(Net::HTTPSuccess)
|
|
35
|
-
end
|
|
36
|
-
end
|
|
4
|
+
include SlackDelivery
|
|
37
5
|
end
|
|
38
6
|
end
|
|
39
7
|
end
|
|
@@ -1,18 +1,7 @@
|
|
|
1
1
|
module Noticed
|
|
2
2
|
module DeliveryMethods
|
|
3
3
|
class Webhook < DeliveryMethod
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
def deliver
|
|
7
|
-
post_request(
|
|
8
|
-
evaluate_option(:url),
|
|
9
|
-
basic_auth: evaluate_option(:basic_auth),
|
|
10
|
-
headers: evaluate_option(:headers),
|
|
11
|
-
json: evaluate_option(:json),
|
|
12
|
-
form: evaluate_option(:form),
|
|
13
|
-
body: evaluate_option(:body)
|
|
14
|
-
)
|
|
15
|
-
end
|
|
4
|
+
include WebhookDelivery
|
|
16
5
|
end
|
|
17
6
|
end
|
|
18
7
|
end
|
|
@@ -5,7 +5,7 @@ module Noticed
|
|
|
5
5
|
#
|
|
6
6
|
# class User < ApplicationRecord
|
|
7
7
|
# has_noticed_notifications
|
|
8
|
-
# has_noticed_notifications param_name: :owner, destroy: false,
|
|
8
|
+
# has_noticed_notifications param_name: :owner, destroy: false, model_name: "Notification"
|
|
9
9
|
# end
|
|
10
10
|
#
|
|
11
11
|
# @user.notifications_as_user
|
|
@@ -17,7 +17,7 @@ module Noticed
|
|
|
17
17
|
def has_noticed_notifications(param_name: model_name.singular, **options)
|
|
18
18
|
define_method :"notifications_as_#{param_name}" do
|
|
19
19
|
model = options.fetch(:model_name, "Noticed::Event").constantize
|
|
20
|
-
case
|
|
20
|
+
case model.connection_db_config.adapter
|
|
21
21
|
when "postgresql", "postgis"
|
|
22
22
|
model.where("params @> ?", Noticed::Coder.dump(param_name.to_sym => self).to_json)
|
|
23
23
|
when "mysql2", "trilogy"
|
|
@@ -37,13 +37,5 @@ module Noticed
|
|
|
37
37
|
end
|
|
38
38
|
end
|
|
39
39
|
end
|
|
40
|
-
|
|
41
|
-
def current_adapter
|
|
42
|
-
if ActiveRecord::Base.respond_to?(:connection_db_config)
|
|
43
|
-
ActiveRecord::Base.connection_db_config.adapter
|
|
44
|
-
else
|
|
45
|
-
ActiveRecord::Base.connection_config[:adapter]
|
|
46
|
-
end
|
|
47
|
-
end
|
|
48
40
|
end
|
|
49
41
|
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
module Noticed
|
|
2
|
+
# Shared between the individual and bulk Slack delivery methods
|
|
3
|
+
module SlackDelivery
|
|
4
|
+
extend ActiveSupport::Concern
|
|
5
|
+
|
|
6
|
+
DEFAULT_URL = "https://slack.com/api/chat.postMessage"
|
|
7
|
+
|
|
8
|
+
included do
|
|
9
|
+
required_options :json
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def deliver
|
|
13
|
+
headers = evaluate_option(:headers)
|
|
14
|
+
json = evaluate_option(:json)
|
|
15
|
+
response = post_request url, headers: headers, json: json
|
|
16
|
+
|
|
17
|
+
if raise_if_not_ok? && !success?(response)
|
|
18
|
+
raise ResponseUnsuccessful.new(response, url, {headers: headers, json: json})
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
response
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def url
|
|
25
|
+
evaluate_option(:url) || DEFAULT_URL
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def raise_if_not_ok?
|
|
29
|
+
value = evaluate_option(:raise_if_not_ok)
|
|
30
|
+
value.nil? || value
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def success?(response)
|
|
34
|
+
if response.content_type == "application/json"
|
|
35
|
+
JSON.parse(response.body).dig("ok")
|
|
36
|
+
else
|
|
37
|
+
# https://api.slack.com/changelog/2016-05-17-changes-to-errors-for-incoming-webhooks
|
|
38
|
+
response.is_a?(Net::HTTPSuccess)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
data/lib/noticed/translation.rb
CHANGED
|
@@ -12,11 +12,7 @@ module Noticed
|
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
def translate(key, **options)
|
|
15
|
-
|
|
16
|
-
ActiveSupport::HtmlSafeTranslation.translate scope_translation_key(key), **options
|
|
17
|
-
else
|
|
18
|
-
I18n.translate scope_translation_key(key), **options
|
|
19
|
-
end
|
|
15
|
+
ActiveSupport::HtmlSafeTranslation.translate scope_translation_key(key), **options
|
|
20
16
|
end
|
|
21
17
|
alias_method :t, :translate
|
|
22
18
|
|
data/lib/noticed/version.rb
CHANGED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module Noticed
|
|
2
|
+
# Shared between the individual and bulk Webhook delivery methods
|
|
3
|
+
module WebhookDelivery
|
|
4
|
+
extend ActiveSupport::Concern
|
|
5
|
+
|
|
6
|
+
included do
|
|
7
|
+
required_options :url
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def deliver
|
|
11
|
+
post_request(
|
|
12
|
+
evaluate_option(:url),
|
|
13
|
+
basic_auth: evaluate_option(:basic_auth),
|
|
14
|
+
headers: evaluate_option(:headers),
|
|
15
|
+
json: evaluate_option(:json),
|
|
16
|
+
form: evaluate_option(:form),
|
|
17
|
+
body: evaluate_option(:body)
|
|
18
|
+
)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
data/lib/noticed.rb
CHANGED
|
@@ -14,7 +14,7 @@ module Noticed
|
|
|
14
14
|
include ActiveSupport::Deprecation::DeprecatedConstantAccessor
|
|
15
15
|
|
|
16
16
|
def self.deprecator # :nodoc:
|
|
17
|
-
@deprecator ||= ActiveSupport::Deprecation.new("
|
|
17
|
+
@deprecator ||= ActiveSupport::Deprecation.new("4.0", "Noticed")
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
deprecate_constant :Base, "Noticed::Event", deprecator: deprecator
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: noticed
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.
|
|
4
|
+
version: 3.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Chris Oliver
|
|
@@ -15,14 +15,14 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - ">="
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version:
|
|
18
|
+
version: 7.1.0
|
|
19
19
|
type: :runtime
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
23
|
- - ">="
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version:
|
|
25
|
+
version: 7.1.0
|
|
26
26
|
description: Database, browser, realtime ActionCable, Email, SMS, Slack notifications,
|
|
27
27
|
and more for Rails apps
|
|
28
28
|
email:
|
|
@@ -82,12 +82,15 @@ files:
|
|
|
82
82
|
- lib/noticed/has_notifications.rb
|
|
83
83
|
- lib/noticed/notification_channel.rb
|
|
84
84
|
- lib/noticed/required_options.rb
|
|
85
|
+
- lib/noticed/slack_delivery.rb
|
|
85
86
|
- lib/noticed/translation.rb
|
|
86
87
|
- lib/noticed/version.rb
|
|
88
|
+
- lib/noticed/webhook_delivery.rb
|
|
87
89
|
homepage: https://github.com/excid3/noticed
|
|
88
90
|
licenses:
|
|
89
91
|
- MIT
|
|
90
|
-
metadata:
|
|
92
|
+
metadata:
|
|
93
|
+
rubygems_mfa_required: 'true'
|
|
91
94
|
rdoc_options: []
|
|
92
95
|
require_paths:
|
|
93
96
|
- lib
|
|
@@ -102,7 +105,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
102
105
|
- !ruby/object:Gem::Version
|
|
103
106
|
version: '0'
|
|
104
107
|
requirements: []
|
|
105
|
-
rubygems_version: 4.0.
|
|
108
|
+
rubygems_version: 4.0.20
|
|
106
109
|
specification_version: 4
|
|
107
110
|
summary: Notifications for Ruby on Rails applications
|
|
108
111
|
test_files: []
|