# frozen_string_literal: true # == Schema Information # # Table name: web_push_subscriptions # # id :integer not null, primary key # endpoint :string not null # key_p256dh :string not null # key_auth :string not null # data :json # created_at :datetime not null # updated_at :datetime not null # require 'webpush' require_relative '../../models/setting' class Web::PushSubscription < ApplicationRecord include RoutingHelper include StreamEntriesHelper include ActionView::Helpers::TranslationHelper include ActionView::Helpers::SanitizeHelper has_one :session_activation before_create :send_welcome_notification def push(notification) name = display_name notification.from_account title = title_str(name, notification) body = body_str notification dir = dir_str body url = url_str notification image = image_str notification actions = actions_arr notification access_token = actions.empty? ? nil : find_or_create_access_token(notification).token nsfw = notification.target_status.nil? || notification.target_status.spoiler_text.empty? ? nil : notification.target_status.spoiler_text # TODO: Make sure that the payload does not exceed 4KB - Webpush::PayloadTooLarge Webpush.payload_send( message: JSON.generate( title: title, dir: dir, image: image, badge: full_asset_url('badge.png', skip_pipeline: true), tag: notification.id, timestamp: notification.created_at, icon: notification.from_account.avatar_static_url, data: { content: decoder.decode(strip_tags(body)), nsfw: nsfw.nil? ? nil : decoder.decode(strip_tags(nsfw)), url: url, actions: actions, access_token: access_token, message: translate('push_notifications.group.title'), # Do not pass count, will be formatted in the ServiceWorker } ), endpoint: endpoint, p256dh: key_p256dh, auth: key_auth, vapid: { subject: "mailto:#{Setting.site_contact_email}", private_key: Rails.configuration.x.vapid_private_key, public_key: Rails.configuration.x.vapid_public_key, }, ttl: 40 * 60 * 60 # 48 hours ) end def pushable?(notification) data && data.key?('alerts') && data['alerts'][notification.type.to_s] end def as_payload payload = { id: id, endpoint: endpoint, } payload[:alerts] = data['alerts'] if data && data.key?('alerts') payload end private def title_str(name, notification) case notification.type when :mention then translate('push_notifications.mention.title', name: name) when :follow then translate('push_notifications.follow.title', name: name) when :favourite then translate('push_notifications.favourite.title', name: name) when :reblog then translate('push_notifications.reblog.title', name: name) end end def body_str(notification) case notification.type when :mention then notification.target_status.text when :follow then notification.from_account.note when :favourite then notification.target_status.text when :reblog then notification.target_status.text end end def url_str(notification) case notification.type when :mention then web_url("statuses/#{notification.target_status.id}") when :follow then web_url("accounts/#{notification.from_account.id}") when :favourite then web_url("statuses/#{notification.target_status.id}") when :reblog then web_url("statuses/#{notification.target_status.id}") end end def actions_arr(notification) actions = case notification.type when :mention then [ { title: translate('push_notifications.mention.action_favourite'), icon: full_asset_url('web-push-icon_favourite.png', skip_pipeline: true), todo: 'request', method: 'POST', action: "/api/v1/statuses/#{notification.target_status.id}/favourite", }, ] else [] end should_hide = notification.type.equal?(:mention) && !notification.target_status.nil? && (notification.target_status.sensitive || !notification.target_status.spoiler_text.empty?) can_boost = notification.type.equal?(:mention) && !notification.target_status.nil? && !notification.target_status.hidden? if should_hide actions.insert(0, title: translate('push_notifications.mention.action_expand'), icon: full_asset_url('web-push-icon_expand.png', skip_pipeline: true), todo: 'expand', action: 'expand') end if can_boost actions << { title: translate('push_notifications.mention.action_boost'), icon: full_asset_url('web-push-icon_boost.png', skip_pipeline: true), todo: 'request', method: 'POST', action: "/api/v1/statuses/#{notification.target_status.id}/reblog" } end actions end def image_str(notification) return nil if notification.target_status.nil? || notification.target_status.media_attachments.empty? full_asset_url(notification.target_status.media_attachments.first.file.url(:small)) end def dir_str(body) rtl?(body) ? 'rtl' : 'ltr' end def send_welcome_notification Webpush.payload_send( message: JSON.generate( title: translate('push_notifications.subscribed.title'), icon: full_asset_url('android-chrome-192x192.png', skip_pipeline: true), badge: full_asset_url('badge.png', skip_pipeline: true), data: { content: translate('push_notifications.subscribed.body'), actions: [], url: web_url('notifications'), } ), endpoint: endpoint, p256dh: key_p256dh, auth: key_auth, vapid: { subject: "mailto:#{Setting.site_contact_email}", private_key: Rails.configuration.x.vapid_private_key, public_key: Rails.configuration.x.vapid_public_key, }, ttl: 5 * 60 # 5 minutes ) end def find_or_create_access_token(notification) Doorkeeper::AccessToken.find_or_create_for( Doorkeeper::Application.find_by(superapp: true), notification.account.user.id, Doorkeeper::OAuth::Scopes.from_string('read write follow'), Doorkeeper.configuration.access_token_expires_in, Doorkeeper.configuration.refresh_token_enabled? ) end def decoder @decoder ||= HTMLEntities.new end end