push_notification_worker.rb 2.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869
  1. # frozen_string_literal: true
  2. class Web::PushNotificationWorker
  3. include Sidekiq::Worker
  4. sidekiq_options queue: 'push', retry: 5
  5. TTL = 48.hours.to_s
  6. URGENCY = 'normal'
  7. def perform(subscription_id, notification_id)
  8. @subscription = Web::PushSubscription.find(subscription_id)
  9. @notification = Notification.find(notification_id)
  10. # Polymorphically associated activity could have been deleted
  11. # in the meantime, so we have to double-check before proceeding
  12. return unless @notification.activity.present? && @subscription.pushable?(@notification)
  13. payload = @subscription.encrypt(push_notification_json)
  14. request_pool.with(@subscription.audience) do |http_client|
  15. request = Request.new(:post, @subscription.endpoint, body: payload.fetch(:ciphertext), http_client: http_client)
  16. request.add_headers(
  17. 'Content-Type' => 'application/octet-stream',
  18. 'Ttl' => TTL,
  19. 'Urgency' => URGENCY,
  20. 'Content-Encoding' => 'aesgcm',
  21. 'Encryption' => "salt=#{Webpush.encode64(payload.fetch(:salt)).delete('=')}",
  22. 'Crypto-Key' => "dh=#{Webpush.encode64(payload.fetch(:server_public_key)).delete('=')};#{@subscription.crypto_key_header}",
  23. 'Authorization' => @subscription.authorization_header
  24. )
  25. request.perform do |response|
  26. # If the server responds with an error in the 4xx range
  27. # that isn't about rate-limiting or timeouts, we can
  28. # assume that the subscription is invalid or expired
  29. # and must be removed
  30. if (400..499).cover?(response.code) && ![408, 429].include?(response.code)
  31. @subscription.destroy!
  32. elsif !(200...300).cover?(response.code)
  33. raise Mastodon::UnexpectedResponseError, response
  34. end
  35. end
  36. end
  37. rescue ActiveRecord::RecordNotFound
  38. true
  39. end
  40. private
  41. def push_notification_json
  42. json = I18n.with_locale(@subscription.locale || I18n.default_locale) do
  43. ActiveModelSerializers::SerializableResource.new(
  44. @notification,
  45. serializer: Web::NotificationSerializer,
  46. scope: @subscription,
  47. scope_name: :current_push_subscription
  48. ).as_json
  49. end
  50. Oj.dump(json)
  51. end
  52. def request_pool
  53. RequestPool.current
  54. end
  55. end