2019-05-02 17:17:27 -05:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2018-05-04 17:31:48 -05:00
|
|
|
class PushNotificationPusher
|
2019-02-15 14:11:44 -06:00
|
|
|
TOKEN_VALID_FOR_SECONDS ||= 5 * 60
|
2021-06-07 13:46:07 -05:00
|
|
|
CONNECTION_TIMEOUT_SECONDS = 5
|
2019-02-15 14:11:44 -06:00
|
|
|
|
2018-05-04 17:31:48 -05:00
|
|
|
def self.push(user, payload)
|
2022-05-31 21:00:05 -05:00
|
|
|
message = nil
|
2021-02-25 12:10:37 -06:00
|
|
|
I18n.with_locale(user.effective_locale) do
|
2022-05-31 21:00:05 -05:00
|
|
|
notification_icon_name = Notification.types[payload[:notification_type]]
|
2023-01-09 06:20:10 -06:00
|
|
|
if !File.exist?(
|
|
|
|
File.expand_path(
|
|
|
|
"../../app/assets/images/push-notifications/#{notification_icon_name}.png",
|
|
|
|
__dir__,
|
|
|
|
),
|
|
|
|
)
|
2022-05-31 21:00:05 -05:00
|
|
|
notification_icon_name = "discourse"
|
|
|
|
end
|
2023-01-09 06:20:10 -06:00
|
|
|
notification_icon =
|
|
|
|
ActionController::Base.helpers.image_url("push-notifications/#{notification_icon_name}.png")
|
2022-05-31 21:00:05 -05:00
|
|
|
|
2021-02-25 12:10:37 -06:00
|
|
|
message = {
|
2023-01-09 06:20:10 -06:00
|
|
|
title:
|
|
|
|
payload[:translated_title] ||
|
|
|
|
I18n.t(
|
|
|
|
"discourse_push_notifications.popup.#{Notification.types[payload[:notification_type]]}",
|
|
|
|
site_title: SiteSetting.title,
|
|
|
|
topic: payload[:topic_title],
|
|
|
|
username: payload[:username],
|
|
|
|
),
|
2021-02-25 12:10:37 -06:00
|
|
|
body: payload[:excerpt],
|
|
|
|
badge: get_badge,
|
2022-05-31 21:00:05 -05:00
|
|
|
icon: notification_icon,
|
2021-11-03 14:18:48 -05:00
|
|
|
tag: payload[:tag] || "#{Discourse.current_hostname}-#{payload[:topic_id]}",
|
2021-02-25 12:10:37 -06:00
|
|
|
base_url: Discourse.base_url,
|
|
|
|
url: payload[:post_url],
|
2023-01-09 06:20:10 -06:00
|
|
|
hide_when_active: true,
|
2021-02-25 12:10:37 -06:00
|
|
|
}
|
2019-01-10 08:38:31 -06:00
|
|
|
|
2023-01-09 06:20:10 -06:00
|
|
|
subscriptions(user).each { |subscription| send_notification(user, subscription, message) }
|
2018-05-04 17:31:48 -05:00
|
|
|
end
|
2022-05-31 21:00:05 -05:00
|
|
|
|
|
|
|
message
|
2018-05-04 17:31:48 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
def self.subscriptions(user)
|
|
|
|
user.push_subscriptions
|
|
|
|
end
|
|
|
|
|
|
|
|
def self.clear_subscriptions(user)
|
|
|
|
user.push_subscriptions.clear
|
|
|
|
end
|
|
|
|
|
2021-06-04 14:05:46 -05:00
|
|
|
def self.subscribe(user, push_params, send_confirmation)
|
|
|
|
data = push_params.to_json
|
2018-05-08 19:14:14 -05:00
|
|
|
subscriptions = PushSubscription.where(user: user, data: data)
|
|
|
|
subscriptions_count = subscriptions.count
|
|
|
|
|
2023-01-09 06:20:10 -06:00
|
|
|
new_subscription =
|
|
|
|
if subscriptions_count > 1
|
|
|
|
subscriptions.destroy_all
|
|
|
|
PushSubscription.create!(user: user, data: data)
|
|
|
|
elsif subscriptions_count == 0
|
|
|
|
PushSubscription.create!(user: user, data: data)
|
|
|
|
end
|
2018-05-08 19:14:14 -05:00
|
|
|
|
2018-05-04 17:31:48 -05:00
|
|
|
if send_confirmation == "true"
|
|
|
|
message = {
|
2023-01-09 06:20:10 -06:00
|
|
|
title:
|
|
|
|
I18n.t("discourse_push_notifications.popup.confirm_title", site_title: SiteSetting.title),
|
2018-05-04 17:31:48 -05:00
|
|
|
body: I18n.t("discourse_push_notifications.popup.confirm_body"),
|
2018-12-04 03:48:16 -06:00
|
|
|
icon: ActionController::Base.helpers.image_url("push-notifications/check.png"),
|
2018-05-04 17:31:48 -05:00
|
|
|
badge: get_badge,
|
2023-01-09 06:20:10 -06:00
|
|
|
tag: "#{Discourse.current_hostname}-subscription",
|
2018-05-04 17:31:48 -05:00
|
|
|
}
|
|
|
|
|
2021-06-04 14:05:46 -05:00
|
|
|
send_notification(user, new_subscription, message)
|
2018-05-04 17:31:48 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def self.unsubscribe(user, subscription)
|
2018-05-08 19:14:14 -05:00
|
|
|
PushSubscription.find_by(user: user, data: subscription.to_json)&.destroy!
|
2018-05-04 17:31:48 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
def self.get_badge
|
2019-01-02 01:29:17 -06:00
|
|
|
if (url = SiteSetting.site_push_notifications_icon_url).present?
|
|
|
|
url
|
2018-11-14 01:03:02 -06:00
|
|
|
else
|
|
|
|
ActionController::Base.helpers.image_url("push-notifications/discourse.png")
|
|
|
|
end
|
2018-05-04 17:31:48 -05:00
|
|
|
end
|
|
|
|
|
2021-05-26 15:49:20 -05:00
|
|
|
MAX_ERRORS ||= 3
|
2023-01-09 06:20:10 -06:00
|
|
|
MIN_ERROR_DURATION ||= 86_400 # 1 day
|
2021-05-26 15:49:20 -05:00
|
|
|
|
2021-06-07 13:46:07 -05:00
|
|
|
def self.handle_generic_error(subscription, error, user, endpoint, message)
|
2021-05-26 15:49:20 -05:00
|
|
|
subscription.error_count += 1
|
|
|
|
subscription.first_error_at ||= Time.zone.now
|
|
|
|
|
|
|
|
delta = Time.zone.now - subscription.first_error_at
|
|
|
|
if subscription.error_count >= MAX_ERRORS && delta > MIN_ERROR_DURATION
|
|
|
|
subscription.destroy!
|
|
|
|
else
|
|
|
|
subscription.save!
|
|
|
|
end
|
2021-06-07 13:46:07 -05:00
|
|
|
|
|
|
|
Discourse.warn_exception(
|
|
|
|
error,
|
|
|
|
message: "Failed to send push notification",
|
|
|
|
env: {
|
|
|
|
user_id: user.id,
|
|
|
|
endpoint: endpoint,
|
2023-01-09 06:20:10 -06:00
|
|
|
message: message.to_json,
|
|
|
|
},
|
2021-06-07 13:46:07 -05:00
|
|
|
)
|
2021-05-26 15:49:20 -05:00
|
|
|
end
|
|
|
|
|
2018-05-04 17:31:48 -05:00
|
|
|
def self.send_notification(user, subscription, message)
|
2021-05-26 15:49:20 -05:00
|
|
|
parsed_data = subscription.parsed_data
|
|
|
|
|
|
|
|
endpoint = parsed_data["endpoint"]
|
|
|
|
p256dh = parsed_data.dig("keys", "p256dh")
|
|
|
|
auth = parsed_data.dig("keys", "auth")
|
2021-03-19 08:24:03 -05:00
|
|
|
|
|
|
|
if (endpoint.blank? || p256dh.blank? || auth.blank?)
|
2021-05-26 15:49:20 -05:00
|
|
|
subscription.destroy!
|
2021-03-19 08:24:03 -05:00
|
|
|
return
|
|
|
|
end
|
|
|
|
|
2018-05-04 17:31:48 -05:00
|
|
|
begin
|
2022-12-27 12:28:13 -06:00
|
|
|
WebPush.payload_send(
|
2021-03-19 08:24:03 -05:00
|
|
|
endpoint: endpoint,
|
2018-05-04 17:31:48 -05:00
|
|
|
message: message.to_json,
|
2021-03-19 08:24:03 -05:00
|
|
|
p256dh: p256dh,
|
|
|
|
auth: auth,
|
2018-05-04 17:31:48 -05:00
|
|
|
vapid: {
|
|
|
|
subject: Discourse.base_url,
|
|
|
|
public_key: SiteSetting.vapid_public_key,
|
2019-02-15 14:11:44 -06:00
|
|
|
private_key: SiteSetting.vapid_private_key,
|
2023-01-09 06:20:10 -06:00
|
|
|
expiration: TOKEN_VALID_FOR_SECONDS,
|
2021-06-07 13:46:07 -05:00
|
|
|
},
|
|
|
|
open_timeout: CONNECTION_TIMEOUT_SECONDS,
|
|
|
|
read_timeout: CONNECTION_TIMEOUT_SECONDS,
|
2023-01-09 06:20:10 -06:00
|
|
|
ssl_timeout: CONNECTION_TIMEOUT_SECONDS,
|
2018-05-04 17:31:48 -05:00
|
|
|
)
|
2021-05-26 15:49:20 -05:00
|
|
|
|
|
|
|
if subscription.first_error_at || subscription.error_count != 0
|
|
|
|
subscription.update_columns(error_count: 0, first_error_at: nil)
|
|
|
|
end
|
2022-12-27 12:28:13 -06:00
|
|
|
rescue WebPush::ExpiredSubscription
|
2021-05-26 15:49:20 -05:00
|
|
|
subscription.destroy!
|
2022-12-27 12:28:13 -06:00
|
|
|
rescue WebPush::ResponseError => e
|
2019-02-15 10:40:33 -06:00
|
|
|
if e.response.message == "MismatchSenderId"
|
2021-05-26 15:49:20 -05:00
|
|
|
subscription.destroy!
|
2019-02-15 10:40:33 -06:00
|
|
|
else
|
2021-06-07 13:46:07 -05:00
|
|
|
handle_generic_error(subscription, e, user, endpoint, message)
|
2019-02-15 10:40:33 -06:00
|
|
|
end
|
2021-06-07 13:46:07 -05:00
|
|
|
rescue Timeout::Error => e
|
|
|
|
handle_generic_error(subscription, e, user, endpoint, message)
|
2018-05-04 17:31:48 -05:00
|
|
|
end
|
|
|
|
end
|
2021-05-26 15:49:20 -05:00
|
|
|
|
|
|
|
private_class_method :send_notification
|
|
|
|
private_class_method :handle_generic_error
|
2018-05-04 17:31:48 -05:00
|
|
|
end
|