Restructure omni services and add Chatwoot research snapshot
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
class Notification::EmailNotificationService
|
||||
pattr_initialize [:notification!]
|
||||
|
||||
def perform
|
||||
# don't send emails if user read the push notification already
|
||||
return if notification.read_at.present?
|
||||
# don't send emails if user is not confirmed
|
||||
return if notification.user.confirmed_at.nil?
|
||||
return unless user_subscribed_to_notification?
|
||||
return unless notification.account.within_email_rate_limit?
|
||||
|
||||
send_notification_email
|
||||
notification.account.increment_email_sent_count
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# TODO : Clean up whatever happening over here
|
||||
# Segregate the mailers properly
|
||||
def send_notification_email
|
||||
AgentNotifications::ConversationNotificationsMailer.with(account: notification.account).public_send(
|
||||
notification.notification_type.to_s, notification.primary_actor, notification.user, notification.secondary_actor
|
||||
).deliver_later
|
||||
end
|
||||
|
||||
def user_subscribed_to_notification?
|
||||
notification_setting = notification.user.notification_settings.find_by(account_id: notification.account.id)
|
||||
return true if notification_setting.public_send("email_#{notification.notification_type}?")
|
||||
|
||||
false
|
||||
end
|
||||
end
|
||||
40
research/chatwoot/app/services/notification/fcm_service.rb
Normal file
40
research/chatwoot/app/services/notification/fcm_service.rb
Normal file
@@ -0,0 +1,40 @@
|
||||
class Notification::FcmService
|
||||
SCOPES = ['https://www.googleapis.com/auth/firebase.messaging'].freeze
|
||||
|
||||
def initialize(project_id, credentials)
|
||||
@project_id = project_id
|
||||
@credentials = credentials
|
||||
@token_info = nil
|
||||
end
|
||||
|
||||
def fcm_client
|
||||
FCM.new(current_token, credentials_path, @project_id)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def current_token
|
||||
@token_info = generate_token if @token_info.nil? || token_expired?
|
||||
@token_info[:token]
|
||||
end
|
||||
|
||||
def token_expired?
|
||||
Time.zone.now >= @token_info[:expires_at]
|
||||
end
|
||||
|
||||
def generate_token
|
||||
authorizer = Google::Auth::ServiceAccountCredentials.make_creds(
|
||||
json_key_io: credentials_path,
|
||||
scope: SCOPES
|
||||
)
|
||||
token = authorizer.fetch_access_token!
|
||||
{
|
||||
token: token['access_token'],
|
||||
expires_at: Time.zone.now + token['expires_in'].to_i
|
||||
}
|
||||
end
|
||||
|
||||
def credentials_path
|
||||
StringIO.new(@credentials)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,172 @@
|
||||
class Notification::PushNotificationService
|
||||
include Rails.application.routes.url_helpers
|
||||
|
||||
pattr_initialize [:notification!]
|
||||
|
||||
def perform
|
||||
return unless user_subscribed_to_notification?
|
||||
|
||||
notification_subscriptions.each do |subscription|
|
||||
send_browser_push(subscription)
|
||||
send_fcm_push(subscription)
|
||||
send_push_via_chatwoot_hub(subscription)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
delegate :user, to: :notification
|
||||
delegate :notification_subscriptions, to: :user
|
||||
delegate :notification_settings, to: :user
|
||||
|
||||
def user_subscribed_to_notification?
|
||||
notification_setting = notification_settings.find_by(account_id: notification.account.id)
|
||||
return true if notification_setting.public_send("push_#{notification.notification_type}?")
|
||||
|
||||
false
|
||||
end
|
||||
|
||||
def conversation
|
||||
@conversation ||= notification.conversation
|
||||
end
|
||||
|
||||
def push_message
|
||||
{
|
||||
title: notification.push_message_title,
|
||||
tag: "#{notification.notification_type}_#{conversation.display_id}_#{notification.id}",
|
||||
url: push_url
|
||||
}
|
||||
end
|
||||
|
||||
def push_url
|
||||
app_account_conversation_url(account_id: conversation.account_id, id: conversation.display_id)
|
||||
end
|
||||
|
||||
def can_send_browser_push?(subscription)
|
||||
VapidService.public_key && subscription.browser_push?
|
||||
end
|
||||
|
||||
def browser_push_payload(subscription)
|
||||
{
|
||||
message: JSON.generate(push_message),
|
||||
endpoint: subscription.subscription_attributes['endpoint'],
|
||||
p256dh: subscription.subscription_attributes['p256dh'],
|
||||
auth: subscription.subscription_attributes['auth'],
|
||||
vapid: {
|
||||
subject: push_url,
|
||||
public_key: VapidService.public_key,
|
||||
private_key: VapidService.private_key
|
||||
},
|
||||
ssl_timeout: 5,
|
||||
open_timeout: 5,
|
||||
read_timeout: 5
|
||||
}
|
||||
end
|
||||
|
||||
def send_browser_push(subscription)
|
||||
return unless can_send_browser_push?(subscription)
|
||||
|
||||
WebPush.payload_send(**browser_push_payload(subscription))
|
||||
Rails.logger.info("Browser push sent to #{user.email} with title #{push_message[:title]}")
|
||||
rescue StandardError => e
|
||||
handle_browser_push_error(e, subscription)
|
||||
end
|
||||
|
||||
def handle_browser_push_error(error, subscription)
|
||||
case error
|
||||
when WebPush::ExpiredSubscription, WebPush::InvalidSubscription, WebPush::Unauthorized
|
||||
Rails.logger.info "WebPush subscription expired: #{error.message}"
|
||||
subscription.destroy!
|
||||
when WebPush::TooManyRequests
|
||||
Rails.logger.warn "WebPush rate limited for #{user.email} on account #{notification.account.id}: #{error.message}"
|
||||
when Errno::ECONNRESET, Net::OpenTimeout, Net::ReadTimeout
|
||||
Rails.logger.error "WebPush operation error: #{error.message}"
|
||||
else
|
||||
ChatwootExceptionTracker.new(error, account: notification.account).capture_exception
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
def send_fcm_push(subscription)
|
||||
return unless firebase_credentials_present?
|
||||
return unless subscription.fcm?
|
||||
|
||||
fcm_service = Notification::FcmService.new(
|
||||
GlobalConfigService.load('FIREBASE_PROJECT_ID', nil), GlobalConfigService.load('FIREBASE_CREDENTIALS', nil)
|
||||
)
|
||||
fcm = fcm_service.fcm_client
|
||||
response = fcm.send_v1(fcm_options(subscription))
|
||||
remove_subscription_if_error(subscription, response)
|
||||
end
|
||||
|
||||
def send_push_via_chatwoot_hub(subscription)
|
||||
return if firebase_credentials_present?
|
||||
return unless chatwoot_hub_enabled?
|
||||
return unless subscription.fcm?
|
||||
|
||||
ChatwootHub.send_push(fcm_options(subscription))
|
||||
end
|
||||
|
||||
def firebase_credentials_present?
|
||||
GlobalConfigService.load('FIREBASE_PROJECT_ID', nil) && GlobalConfigService.load('FIREBASE_CREDENTIALS', nil)
|
||||
end
|
||||
|
||||
def chatwoot_hub_enabled?
|
||||
ActiveModel::Type::Boolean.new.cast(ENV.fetch('ENABLE_PUSH_RELAY_SERVER', true))
|
||||
end
|
||||
|
||||
def remove_subscription_if_error(subscription, response)
|
||||
if JSON.parse(response[:body])['results']&.first&.keys&.include?('error')
|
||||
subscription.destroy!
|
||||
else
|
||||
Rails.logger.info("FCM push sent to #{user.email} with title #{push_message[:title]}")
|
||||
end
|
||||
end
|
||||
|
||||
def fcm_options(subscription)
|
||||
{
|
||||
'token': subscription.subscription_attributes['push_token'],
|
||||
'data': fcm_data,
|
||||
'notification': fcm_notification,
|
||||
'android': fcm_android_options,
|
||||
'apns': fcm_apns_options,
|
||||
'fcm_options': {
|
||||
analytics_label: 'Label'
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def fcm_data
|
||||
{
|
||||
payload: {
|
||||
data: {
|
||||
notification: notification.fcm_push_data
|
||||
}
|
||||
}.to_json
|
||||
}
|
||||
end
|
||||
|
||||
def fcm_notification
|
||||
{
|
||||
title: notification.push_message_title,
|
||||
body: notification.push_message_body
|
||||
}
|
||||
end
|
||||
|
||||
def fcm_android_options
|
||||
{
|
||||
priority: 'high'
|
||||
}
|
||||
end
|
||||
|
||||
def fcm_apns_options
|
||||
{
|
||||
payload: {
|
||||
aps: {
|
||||
sound: 'default',
|
||||
category: Time.zone.now.to_i.to_s
|
||||
}
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user