require_dependency 'webpush' class PushNotificationPusher TOKEN_VALID_FOR_SECONDS ||= 5 * 60 def self.push(user, payload) message = { title: I18n.t( "discourse_push_notifications.popup.#{Notification.types[payload[:notification_type]]}", site_title: SiteSetting.title, topic: payload[:topic_title], username: payload[:username] ), body: payload[:excerpt], badge: get_badge, icon: ActionController::Base.helpers.image_url("push-notifications/#{Notification.types[payload[:notification_type]]}.png"), tag: "#{Discourse.current_hostname}-#{payload[:topic_id]}", base_url: Discourse.base_url, url: payload[:post_url], hide_when_active: true } subscriptions(user).each do |subscription| subscription = JSON.parse(subscription.data) send_notification(user, subscription, message) end end def self.subscriptions(user) user.push_subscriptions end def self.clear_subscriptions(user) user.push_subscriptions.clear end def self.subscribe(user, subscription, send_confirmation) data = subscription.to_json subscriptions = PushSubscription.where(user: user, data: data) subscriptions_count = subscriptions.count if subscriptions_count > 1 subscriptions.destroy_all PushSubscription.create!(user: user, data: data) elsif subscriptions_count == 0 PushSubscription.create!(user: user, data: data) end if send_confirmation == "true" message = { title: I18n.t("discourse_push_notifications.popup.confirm_title", site_title: SiteSetting.title), body: I18n.t("discourse_push_notifications.popup.confirm_body"), icon: ActionController::Base.helpers.image_url("push-notifications/check.png"), badge: get_badge, tag: "#{Discourse.current_hostname}-subscription" } send_notification(user, subscription, message) end end def self.unsubscribe(user, subscription) PushSubscription.find_by(user: user, data: subscription.to_json)&.destroy! end protected def self.get_badge if (url = SiteSetting.site_push_notifications_icon_url).present? url else ActionController::Base.helpers.image_url("push-notifications/discourse.png") end end def self.send_notification(user, subscription, message) begin Webpush.payload_send( endpoint: subscription["endpoint"], message: message.to_json, p256dh: subscription.dig("keys", "p256dh"), auth: subscription.dig("keys", "auth"), vapid: { subject: Discourse.base_url, public_key: SiteSetting.vapid_public_key, private_key: SiteSetting.vapid_private_key, expiration: TOKEN_VALID_FOR_SECONDS } ) rescue Webpush::ExpiredSubscription unsubscribe(user, subscription) rescue Webpush::ResponseError => e if e.response.message == "MismatchSenderId" unsubscribe(user, subscription) else Discourse.warn_exception( e, message: "Failed to send push notification", env: { user_id: user.id, endpoint: subscription["endpoint"], message: message.to_json } ) end end end end