push_notification_worker.rb 2.6 KB

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