noticed 1.3.2 → 1.6.3
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 +76 -177
- data/lib/generators/noticed/model/base_generator.rb +47 -0
- data/lib/generators/noticed/model/mysql_generator.rb +18 -0
- data/lib/generators/noticed/model/postgresql_generator.rb +18 -0
- data/lib/generators/noticed/model/sqlite3_generator.rb +18 -0
- data/lib/generators/noticed/model_generator.rb +10 -2
- data/lib/generators/noticed/templates/README +1 -1
- data/lib/noticed/base.rb +23 -7
- data/lib/noticed/coder.rb +2 -0
- data/lib/noticed/delivery_methods/action_cable.rb +13 -1
- data/lib/noticed/delivery_methods/base.rb +22 -13
- data/lib/noticed/delivery_methods/email.rb +29 -4
- data/lib/noticed/delivery_methods/fcm.rb +96 -0
- data/lib/noticed/delivery_methods/ios.rb +171 -0
- data/lib/noticed/engine.rb +4 -0
- data/lib/noticed/has_notifications.rb +21 -4
- data/lib/noticed/model.rb +22 -2
- data/lib/noticed/translation.rb +5 -1
- data/lib/noticed/version.rb +1 -1
- data/lib/noticed.rb +4 -11
- data/lib/rails_6_polyfills/actioncable/test_adapter.rb +70 -0
- data/lib/rails_6_polyfills/actioncable/test_helper.rb +143 -0
- data/lib/rails_6_polyfills/activejob/serializers.rb +240 -0
- data/lib/rails_6_polyfills/base.rb +18 -0
- metadata +15 -75
|
@@ -6,11 +6,11 @@ module Noticed
|
|
|
6
6
|
|
|
7
7
|
class_attribute :option_names, instance_writer: false, default: []
|
|
8
8
|
|
|
9
|
-
attr_reader :notification, :options, :params, :recipient, :record
|
|
9
|
+
attr_reader :notification, :options, :params, :recipient, :record, :logger
|
|
10
10
|
|
|
11
11
|
class << self
|
|
12
12
|
# Copy option names from parent
|
|
13
|
-
def inherited(base)
|
|
13
|
+
def inherited(base) # :nodoc:
|
|
14
14
|
base.option_names = option_names.dup
|
|
15
15
|
super
|
|
16
16
|
end
|
|
@@ -29,16 +29,24 @@ module Noticed
|
|
|
29
29
|
end
|
|
30
30
|
end
|
|
31
31
|
|
|
32
|
-
def
|
|
33
|
-
@notification = args
|
|
34
|
-
@options = args[:options]
|
|
32
|
+
def assign_args(args)
|
|
33
|
+
@notification = args.fetch(:notification_class).constantize.new(args[:params])
|
|
34
|
+
@options = args[:options] || {}
|
|
35
35
|
@params = args[:params]
|
|
36
36
|
@recipient = args[:recipient]
|
|
37
37
|
@record = args[:record]
|
|
38
38
|
|
|
39
|
+
# Set the default logger
|
|
40
|
+
@logger = @options.fetch(:logger, Rails.logger)
|
|
41
|
+
|
|
39
42
|
# Make notification aware of database record and recipient during delivery
|
|
40
43
|
@notification.record = args[:record]
|
|
41
44
|
@notification.recipient = args[:recipient]
|
|
45
|
+
self
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def perform(args)
|
|
49
|
+
assign_args(args)
|
|
42
50
|
|
|
43
51
|
return if (condition = @options[:if]) && !@notification.send(condition)
|
|
44
52
|
return if (condition = @options[:unless]) && @notification.send(condition)
|
|
@@ -57,25 +65,26 @@ module Noticed
|
|
|
57
65
|
# Helper method for making POST requests from delivery methods
|
|
58
66
|
#
|
|
59
67
|
# Usage:
|
|
60
|
-
# post("http://example.com", basic_auth: {user:, pass:}, json: {}, form: {})
|
|
68
|
+
# post("http://example.com", basic_auth: {user:, pass:}, headers: {}, json: {}, form: {})
|
|
61
69
|
#
|
|
62
70
|
def post(url, args = {})
|
|
63
71
|
basic_auth = args.delete(:basic_auth)
|
|
72
|
+
headers = args.delete(:headers)
|
|
64
73
|
|
|
65
|
-
request =
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
HTTP
|
|
69
|
-
end
|
|
74
|
+
request = HTTP
|
|
75
|
+
request = request.basic_auth(user: basic_auth[:user], pass: basic_auth[:pass]) if basic_auth
|
|
76
|
+
request = request.headers(headers) if headers
|
|
70
77
|
|
|
71
78
|
response = request.post(url, args)
|
|
72
79
|
|
|
73
80
|
if options[:debug]
|
|
74
|
-
|
|
75
|
-
|
|
81
|
+
logger.debug("POST #{url}")
|
|
82
|
+
logger.debug("Response: #{response.code}: #{response}")
|
|
76
83
|
end
|
|
77
84
|
|
|
78
85
|
if !options[:ignore_failure] && !response.status.success?
|
|
86
|
+
puts response.status
|
|
87
|
+
puts response.body
|
|
79
88
|
raise ResponseUnsuccessful.new(response)
|
|
80
89
|
end
|
|
81
90
|
|
|
@@ -4,17 +4,42 @@ module Noticed
|
|
|
4
4
|
option :mailer
|
|
5
5
|
|
|
6
6
|
def deliver
|
|
7
|
-
|
|
7
|
+
if options[:enqueue]
|
|
8
|
+
mailer.with(format).send(mailer_method.to_sym).deliver_later
|
|
9
|
+
else
|
|
10
|
+
mailer.with(format).send(mailer_method.to_sym).deliver_now
|
|
11
|
+
end
|
|
8
12
|
end
|
|
9
13
|
|
|
10
14
|
private
|
|
11
15
|
|
|
16
|
+
# mailer: "UserMailer"
|
|
17
|
+
# mailer: UserMailer
|
|
18
|
+
# mailer: :my_method - `my_method` should return Class
|
|
12
19
|
def mailer
|
|
13
|
-
options.fetch(:mailer)
|
|
20
|
+
option = options.fetch(:mailer)
|
|
21
|
+
case option
|
|
22
|
+
when String
|
|
23
|
+
option.constantize
|
|
24
|
+
when Symbol
|
|
25
|
+
notification.send(option)
|
|
26
|
+
else
|
|
27
|
+
option
|
|
28
|
+
end
|
|
14
29
|
end
|
|
15
30
|
|
|
16
|
-
|
|
17
|
-
|
|
31
|
+
# Method should be a symbol
|
|
32
|
+
#
|
|
33
|
+
# If notification responds to symbol, call that method and use return value
|
|
34
|
+
# If notification does not respond to symbol, use the symbol for the mailer method
|
|
35
|
+
# Otherwise, use the underscored notification class name as the mailer method
|
|
36
|
+
def mailer_method
|
|
37
|
+
method_name = options[:method]&.to_sym
|
|
38
|
+
if method_name.present?
|
|
39
|
+
notification.respond_to?(method_name) ? notification.send(method_name) : method_name
|
|
40
|
+
else
|
|
41
|
+
notification.class.name.underscore
|
|
42
|
+
end
|
|
18
43
|
end
|
|
19
44
|
|
|
20
45
|
def format
|
|
@@ -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.response.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
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
require "apnotic"
|
|
2
|
+
|
|
3
|
+
module Noticed
|
|
4
|
+
module DeliveryMethods
|
|
5
|
+
class Ios < Base
|
|
6
|
+
cattr_accessor :connection_pool
|
|
7
|
+
|
|
8
|
+
def deliver
|
|
9
|
+
raise ArgumentError, "bundle_identifier is missing" if bundle_identifier.blank?
|
|
10
|
+
raise ArgumentError, "key_id is missing" if key_id.blank?
|
|
11
|
+
raise ArgumentError, "team_id is missing" if team_id.blank?
|
|
12
|
+
raise ArgumentError, "Could not find APN cert at '#{cert_path}'" unless valid_cert_path?
|
|
13
|
+
|
|
14
|
+
device_tokens.each do |device_token|
|
|
15
|
+
connection_pool.with do |connection|
|
|
16
|
+
apn = Apnotic::Notification.new(device_token)
|
|
17
|
+
format_notification(apn)
|
|
18
|
+
|
|
19
|
+
response = connection.push(apn)
|
|
20
|
+
raise "Timeout sending iOS push notification" unless response
|
|
21
|
+
|
|
22
|
+
if bad_token?(response)
|
|
23
|
+
# Allow notification to cleanup invalid iOS device tokens
|
|
24
|
+
cleanup_invalid_token(device_token)
|
|
25
|
+
elsif !response.ok?
|
|
26
|
+
raise "Request failed #{response.body}"
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def format_notification(apn)
|
|
35
|
+
apn.topic = bundle_identifier
|
|
36
|
+
|
|
37
|
+
if (method = options[:format])
|
|
38
|
+
notification.send(method, apn)
|
|
39
|
+
elsif params[:message].present?
|
|
40
|
+
apn.alert = params[:message]
|
|
41
|
+
else
|
|
42
|
+
raise ArgumentError, "No message for iOS delivery. Either include message in params or add the 'format' option in 'deliver_by :ios'."
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def device_tokens
|
|
47
|
+
if notification.respond_to?(:ios_device_tokens)
|
|
48
|
+
Array.wrap(notification.ios_device_tokens(recipient))
|
|
49
|
+
else
|
|
50
|
+
raise NoMethodError, <<~MESSAGE
|
|
51
|
+
You must implement `ios_device_tokens` to send iOS notifications
|
|
52
|
+
|
|
53
|
+
# This must return an Array of iOS device tokens
|
|
54
|
+
def ios_device_tokens(recipient)
|
|
55
|
+
recipient.ios_device_tokens.pluck(:token)
|
|
56
|
+
end
|
|
57
|
+
MESSAGE
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def bad_token?(response)
|
|
62
|
+
response.status == "410" || (response.status == "400" && response.body["reason"] == "BadDeviceToken")
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def cleanup_invalid_token(token)
|
|
66
|
+
return unless notification.respond_to?(:cleanup_device_token)
|
|
67
|
+
notification.send(:cleanup_device_token, token: token, platform: "iOS")
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def connection_pool
|
|
71
|
+
self.class.connection_pool ||= new_connection_pool
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def new_connection_pool
|
|
75
|
+
handler = proc do |connection|
|
|
76
|
+
connection.on(:error) do |exception|
|
|
77
|
+
Rails.logger.info "Apnotic exception raised: #{exception}"
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
if development?
|
|
82
|
+
Apnotic::ConnectionPool.development(connection_pool_options, pool_options, &handler)
|
|
83
|
+
else
|
|
84
|
+
Apnotic::ConnectionPool.new(connection_pool_options, pool_options, &handler)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def connection_pool_options
|
|
89
|
+
{
|
|
90
|
+
auth_method: :token,
|
|
91
|
+
cert_path: cert_path,
|
|
92
|
+
key_id: key_id,
|
|
93
|
+
team_id: team_id
|
|
94
|
+
}
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def bundle_identifier
|
|
98
|
+
option = options[:bundle_identifier]
|
|
99
|
+
case option
|
|
100
|
+
when String
|
|
101
|
+
option
|
|
102
|
+
when Symbol
|
|
103
|
+
notification.send(option)
|
|
104
|
+
else
|
|
105
|
+
Rails.application.credentials.dig(:ios, :bundle_identifier)
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def cert_path
|
|
110
|
+
option = options[:cert_path]
|
|
111
|
+
case option
|
|
112
|
+
when String
|
|
113
|
+
option
|
|
114
|
+
when Symbol
|
|
115
|
+
notification.send(option)
|
|
116
|
+
else
|
|
117
|
+
Rails.root.join("config/certs/ios/apns.p8")
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def key_id
|
|
122
|
+
option = options[:key_id]
|
|
123
|
+
case option
|
|
124
|
+
when String
|
|
125
|
+
option
|
|
126
|
+
when Symbol
|
|
127
|
+
notification.send(option)
|
|
128
|
+
else
|
|
129
|
+
Rails.application.credentials.dig(:ios, :key_id)
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def team_id
|
|
134
|
+
option = options[:team_id]
|
|
135
|
+
case option
|
|
136
|
+
when String
|
|
137
|
+
option
|
|
138
|
+
when Symbol
|
|
139
|
+
notification.send(option)
|
|
140
|
+
else
|
|
141
|
+
Rails.application.credentials.dig(:ios, :team_id)
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def development?
|
|
146
|
+
option = options[:development]
|
|
147
|
+
case option
|
|
148
|
+
when Symbol
|
|
149
|
+
!!notification.send(option)
|
|
150
|
+
else
|
|
151
|
+
!!option
|
|
152
|
+
end
|
|
153
|
+
end
|
|
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
|
+
|
|
164
|
+
def pool_options
|
|
165
|
+
{
|
|
166
|
+
size: options.fetch(:pool_size, 5)
|
|
167
|
+
}
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|
data/lib/noticed/engine.rb
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
module Noticed
|
|
2
2
|
module HasNotifications
|
|
3
|
-
# Defines a method for the association and a
|
|
3
|
+
# Defines a method for the association and a before_destroy callback to remove notifications
|
|
4
4
|
# where this record is a param
|
|
5
5
|
#
|
|
6
6
|
# class User < ApplicationRecord
|
|
@@ -15,10 +15,19 @@ module Noticed
|
|
|
15
15
|
|
|
16
16
|
class_methods do
|
|
17
17
|
def has_noticed_notifications(param_name: model_name.singular, **options)
|
|
18
|
-
model = options.fetch(:model_name, "Notification").constantize
|
|
19
|
-
|
|
20
18
|
define_method "notifications_as_#{param_name}" do
|
|
21
|
-
model.
|
|
19
|
+
model = options.fetch(:model_name, "Notification").constantize
|
|
20
|
+
case current_adapter
|
|
21
|
+
when "postgresql", "postgis"
|
|
22
|
+
model.where("params @> ?", Noticed::Coder.dump(param_name.to_sym => self).to_json)
|
|
23
|
+
when "mysql2"
|
|
24
|
+
model.where("JSON_CONTAINS(params, ?)", Noticed::Coder.dump(param_name.to_sym => self).to_json)
|
|
25
|
+
when "sqlite3"
|
|
26
|
+
model.where("json_extract(params, ?) = ?", "$.#{param_name}", Noticed::Coder.dump(self).to_json)
|
|
27
|
+
else
|
|
28
|
+
# This will perform an exact match which isn't ideal
|
|
29
|
+
model.where(params: {param_name.to_sym => self})
|
|
30
|
+
end
|
|
22
31
|
end
|
|
23
32
|
|
|
24
33
|
if options.fetch(:destroy, true)
|
|
@@ -28,5 +37,13 @@ module Noticed
|
|
|
28
37
|
end
|
|
29
38
|
end
|
|
30
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
|
|
31
48
|
end
|
|
32
49
|
end
|
data/lib/noticed/model.rb
CHANGED
|
@@ -1,11 +1,23 @@
|
|
|
1
1
|
module Noticed
|
|
2
2
|
module Model
|
|
3
|
+
DATABASE_ERROR_CLASS_NAMES = lambda {
|
|
4
|
+
classes = [ActiveRecord::NoDatabaseError]
|
|
5
|
+
classes << ActiveRecord::ConnectionNotEstablished
|
|
6
|
+
classes << Mysql2::Error if defined?(::Mysql2)
|
|
7
|
+
classes << PG::ConnectionBad if defined?(::PG)
|
|
8
|
+
classes
|
|
9
|
+
}.call.freeze
|
|
10
|
+
|
|
3
11
|
extend ActiveSupport::Concern
|
|
4
12
|
|
|
5
13
|
included do
|
|
6
14
|
self.inheritance_column = nil
|
|
7
15
|
|
|
8
|
-
|
|
16
|
+
if Rails.gem_version >= Gem::Version.new("7.1.0.alpha")
|
|
17
|
+
serialize :params, coder: noticed_coder
|
|
18
|
+
else
|
|
19
|
+
serialize :params, noticed_coder
|
|
20
|
+
end
|
|
9
21
|
|
|
10
22
|
belongs_to :recipient, polymorphic: true
|
|
11
23
|
|
|
@@ -32,7 +44,9 @@ module Noticed
|
|
|
32
44
|
else
|
|
33
45
|
Noticed::TextCoder
|
|
34
46
|
end
|
|
35
|
-
rescue
|
|
47
|
+
rescue *DATABASE_ERROR_CLASS_NAMES => _error
|
|
48
|
+
warn("Noticed was unable to bootstrap correctly as the database is unavailable.")
|
|
49
|
+
|
|
36
50
|
Noticed::TextCoder
|
|
37
51
|
end
|
|
38
52
|
end
|
|
@@ -42,6 +56,7 @@ module Noticed
|
|
|
42
56
|
@_notification ||= begin
|
|
43
57
|
instance = type.constantize.with(params)
|
|
44
58
|
instance.record = self
|
|
59
|
+
instance.recipient = recipient
|
|
45
60
|
instance
|
|
46
61
|
end
|
|
47
62
|
end
|
|
@@ -61,5 +76,10 @@ module Noticed
|
|
|
61
76
|
def read?
|
|
62
77
|
read_at?
|
|
63
78
|
end
|
|
79
|
+
|
|
80
|
+
# If a GlobalID record in params is no longer found, the params will default with a noticed_error key
|
|
81
|
+
def deserialize_error?
|
|
82
|
+
!!params[:noticed_error]
|
|
83
|
+
end
|
|
64
84
|
end
|
|
65
85
|
end
|
data/lib/noticed/translation.rb
CHANGED
|
@@ -7,6 +7,10 @@ module Noticed
|
|
|
7
7
|
:notifications
|
|
8
8
|
end
|
|
9
9
|
|
|
10
|
+
def class_scope
|
|
11
|
+
self.class.name.underscore.tr("/", ".")
|
|
12
|
+
end
|
|
13
|
+
|
|
10
14
|
def translate(key, **options)
|
|
11
15
|
I18n.translate(scope_translation_key(key), **options)
|
|
12
16
|
end
|
|
@@ -14,7 +18,7 @@ module Noticed
|
|
|
14
18
|
|
|
15
19
|
def scope_translation_key(key)
|
|
16
20
|
if key.to_s.start_with?(".")
|
|
17
|
-
"#{i18n_scope}.#{
|
|
21
|
+
"#{i18n_scope}.#{class_scope}#{key}"
|
|
18
22
|
else
|
|
19
23
|
key
|
|
20
24
|
end
|
data/lib/noticed/version.rb
CHANGED
data/lib/noticed.rb
CHANGED
|
@@ -12,26 +12,19 @@ module Noticed
|
|
|
12
12
|
autoload :NotificationChannel, "noticed/notification_channel"
|
|
13
13
|
|
|
14
14
|
module DeliveryMethods
|
|
15
|
-
autoload :Base, "noticed/delivery_methods/base"
|
|
16
15
|
autoload :ActionCable, "noticed/delivery_methods/action_cable"
|
|
16
|
+
autoload :Base, "noticed/delivery_methods/base"
|
|
17
17
|
autoload :Database, "noticed/delivery_methods/database"
|
|
18
18
|
autoload :Email, "noticed/delivery_methods/email"
|
|
19
|
-
autoload :
|
|
19
|
+
autoload :Fcm, "noticed/delivery_methods/fcm"
|
|
20
|
+
autoload :Ios, "noticed/delivery_methods/ios"
|
|
20
21
|
autoload :MicrosoftTeams, "noticed/delivery_methods/microsoft_teams"
|
|
22
|
+
autoload :Slack, "noticed/delivery_methods/slack"
|
|
21
23
|
autoload :Test, "noticed/delivery_methods/test"
|
|
22
24
|
autoload :Twilio, "noticed/delivery_methods/twilio"
|
|
23
25
|
autoload :Vonage, "noticed/delivery_methods/vonage"
|
|
24
26
|
end
|
|
25
27
|
|
|
26
|
-
def self.notify(recipients:, notification:)
|
|
27
|
-
recipients.each do |recipient|
|
|
28
|
-
notification.notify(recipient)
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
# Clear the recipient after sending to the group
|
|
32
|
-
notification.recipient = nil
|
|
33
|
-
end
|
|
34
|
-
|
|
35
28
|
mattr_accessor :parent_class
|
|
36
29
|
@@parent_class = "ApplicationJob"
|
|
37
30
|
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "action_cable/subscription_adapter/base"
|
|
4
|
+
require "action_cable/subscription_adapter/subscriber_map"
|
|
5
|
+
require "action_cable/subscription_adapter/async"
|
|
6
|
+
|
|
7
|
+
module ActionCable
|
|
8
|
+
module SubscriptionAdapter
|
|
9
|
+
# == Test adapter for Action Cable
|
|
10
|
+
#
|
|
11
|
+
# The test adapter should be used only in testing. Along with
|
|
12
|
+
# <tt>ActionCable::TestHelper</tt> it makes a great tool to test your Rails application.
|
|
13
|
+
#
|
|
14
|
+
# To use the test adapter set +adapter+ value to +test+ in your +config/cable.yml+ file.
|
|
15
|
+
#
|
|
16
|
+
# NOTE: Test adapter extends the <tt>ActionCable::SubscriptionsAdapter::Async</tt> adapter,
|
|
17
|
+
# so it could be used in system tests too.
|
|
18
|
+
class Test < Async
|
|
19
|
+
def broadcast(channel, payload)
|
|
20
|
+
broadcasts(channel) << payload
|
|
21
|
+
super
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def broadcasts(channel)
|
|
25
|
+
channels_data[channel] ||= []
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def clear_messages(channel)
|
|
29
|
+
channels_data[channel] = []
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def clear
|
|
33
|
+
@channels_data = nil
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def channels_data
|
|
39
|
+
@channels_data ||= {}
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Update how broadcast_for determines the channel name so it's consistent with the Rails 6 way
|
|
45
|
+
module Channel
|
|
46
|
+
module Broadcasting
|
|
47
|
+
delegate :broadcast_to, to: :class
|
|
48
|
+
module ClassMethods
|
|
49
|
+
def broadcast_to(model, message)
|
|
50
|
+
ActionCable.server.broadcast(broadcasting_for(model), message)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def broadcasting_for(model)
|
|
54
|
+
serialize_broadcasting([channel_name, model])
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def serialize_broadcasting(object) # :nodoc:
|
|
58
|
+
case # standard:disable Style/EmptyCaseCondition
|
|
59
|
+
when object.is_a?(Array)
|
|
60
|
+
object.map { |m| serialize_broadcasting(m) }.join(":")
|
|
61
|
+
when object.respond_to?(:to_gid_param)
|
|
62
|
+
object.to_gid_param
|
|
63
|
+
else
|
|
64
|
+
object.to_param
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|