80 lines
		
	
	
		
			2.5 KiB
		
	
	
	
		
			Ruby
		
	
	
	
	
	
			
		
		
	
	
			80 lines
		
	
	
		
			2.5 KiB
		
	
	
	
		
			Ruby
		
	
	
	
	
	
# frozen_string_literal: true
 | 
						|
 | 
						|
class Web::PushNotificationWorker
 | 
						|
  include Sidekiq::Worker
 | 
						|
 | 
						|
  sidekiq_options queue: 'push', retry: 5
 | 
						|
 | 
						|
  TTL     = 48.hours.to_s
 | 
						|
  URGENCY = 'normal'
 | 
						|
 | 
						|
  def perform(subscription_id, notification_id)
 | 
						|
    @subscription = Web::PushSubscription.find(subscription_id)
 | 
						|
    @notification = Notification.find(notification_id)
 | 
						|
 | 
						|
    # Polymorphically associated activity could have been deleted
 | 
						|
    # in the meantime, so we have to double-check before proceeding
 | 
						|
    return unless @notification.activity.present? && @subscription.pushable?(@notification)
 | 
						|
 | 
						|
    payload = web_push_request.encrypt(push_notification_json)
 | 
						|
 | 
						|
    request_pool.with(web_push_request.audience) do |http_client|
 | 
						|
      request = Request.new(:post, web_push_request.endpoint, body: payload.fetch(:ciphertext), http_client: http_client)
 | 
						|
 | 
						|
      request.add_headers(
 | 
						|
        'Content-Type' => 'application/octet-stream',
 | 
						|
        'Ttl' => TTL,
 | 
						|
        'Urgency' => URGENCY,
 | 
						|
        'Content-Encoding' => 'aesgcm',
 | 
						|
        'Encryption' => "salt=#{Webpush.encode64(payload.fetch(:salt)).delete('=')}",
 | 
						|
        'Crypto-Key' => "dh=#{Webpush.encode64(payload.fetch(:server_public_key)).delete('=')};#{web_push_request.crypto_key_header}",
 | 
						|
        'Authorization' => web_push_request.authorization_header
 | 
						|
      )
 | 
						|
 | 
						|
      request.perform do |response|
 | 
						|
        # If the server responds with an error in the 4xx range
 | 
						|
        # that isn't about rate-limiting or timeouts, we can
 | 
						|
        # assume that the subscription is invalid or expired
 | 
						|
        # and must be removed
 | 
						|
 | 
						|
        if (400..499).cover?(response.code) && ![408, 429].include?(response.code)
 | 
						|
          @subscription.destroy!
 | 
						|
        elsif !(200...300).cover?(response.code)
 | 
						|
          raise Mastodon::UnexpectedResponseError, response
 | 
						|
        end
 | 
						|
      end
 | 
						|
    end
 | 
						|
  rescue ActiveRecord::RecordNotFound
 | 
						|
    true
 | 
						|
  end
 | 
						|
 | 
						|
  private
 | 
						|
 | 
						|
  def web_push_request
 | 
						|
    @web_push_request || WebPushRequest.new(@subscription)
 | 
						|
  end
 | 
						|
 | 
						|
  def push_notification_json
 | 
						|
    Oj.dump(serialized_notification_in_subscription_locale.as_json)
 | 
						|
  end
 | 
						|
 | 
						|
  def serialized_notification_in_subscription_locale
 | 
						|
    I18n.with_locale(@subscription.locale.presence || I18n.default_locale) do
 | 
						|
      serialized_notification
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  def serialized_notification
 | 
						|
    ActiveModelSerializers::SerializableResource.new(
 | 
						|
      @notification,
 | 
						|
      serializer: Web::NotificationSerializer,
 | 
						|
      scope: @subscription,
 | 
						|
      scope_name: :current_push_subscription
 | 
						|
    )
 | 
						|
  end
 | 
						|
 | 
						|
  def request_pool
 | 
						|
    RequestPool.current
 | 
						|
  end
 | 
						|
end
 |