mirror of
https://github.com/basecamp/once-campfire.git
synced 2026-10-08 15:50:08 +09:00
d485db6038
Each push notification carries the subscriber's unread room count as its badge. The pool built it per subscription, loading the user and counting their unread memberships: two queries for every subscriber, all in the job before the deliveries reach the threads. With 1,000 subscribed members the job spent ~180 ms and 2,000 queries there; with 5,000, a second. The pool now counts the unread rooms of a whole batch with one grouped query and hands each subscription its badge; nothing else in the notification needs the user. The queries still run before the work is posted to the threads, which run outside the Rails executor. Push::Subscription#notification still counts by itself when no badge is given, as for the test notification. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0142qgjggdJ2KDdGk7RF9Xm9
62 lines
2.0 KiB
Ruby
62 lines
2.0 KiB
Ruby
require "restricted_http/private_network_guard"
|
|
|
|
class Push::Subscription < ApplicationRecord
|
|
PERMITTED_ENDPOINT_HOSTS = %w[
|
|
jmt17.google.com
|
|
fcm.googleapis.com
|
|
updates.push.services.mozilla.com
|
|
web.push.apple.com
|
|
notify.windows.com
|
|
].freeze
|
|
|
|
belongs_to :user
|
|
|
|
validates :endpoint, presence: true
|
|
validate :validate_endpoint_url
|
|
|
|
def notification(badge: user.memberships.unread.count, **params)
|
|
# Defer DNS lookup to the delivery worker to prevent rebinding
|
|
WebPush::Notification.new(**params, badge: badge, endpoint: endpoint, endpoint_ip_resolver: method(:resolved_endpoint_ip), p256dh_key: p256dh_key, auth_key: auth_key)
|
|
end
|
|
|
|
# Validate at point of use, not just when saved.
|
|
def resolved_endpoint_ip
|
|
RestrictedHTTP::PrivateNetworkGuard.resolve(endpoint_uri.host) if permitted_endpoint_uri?
|
|
rescue RestrictedHTTP::Violation, Surfguard::Unresolvable
|
|
nil
|
|
end
|
|
|
|
private
|
|
# Validate endpoint shape. Belt & suspenders.
|
|
def permitted_endpoint_uri?
|
|
endpoint_uri&.scheme == "https" && endpoint_uri.port == 443 && permitted_endpoint_host?
|
|
end
|
|
|
|
def endpoint_uri
|
|
URI.parse(endpoint) if endpoint.present?
|
|
rescue URI::InvalidURIError
|
|
nil
|
|
end
|
|
|
|
def validate_endpoint_url
|
|
if endpoint_uri.nil?
|
|
errors.add(:endpoint, "is not a valid URL")
|
|
elsif endpoint_uri.scheme != "https"
|
|
errors.add(:endpoint, "must use HTTPS")
|
|
elsif endpoint_uri.port != 443
|
|
errors.add(:endpoint, "must use the default HTTPS port")
|
|
elsif !permitted_endpoint_host?
|
|
errors.add(:endpoint, "is not a permitted push service")
|
|
elsif resolved_endpoint_ip.nil?
|
|
errors.add(:endpoint, "resolves to a private or invalid IP address")
|
|
end
|
|
end
|
|
|
|
def permitted_endpoint_host?
|
|
host = endpoint_uri&.host&.downcase
|
|
host.present? && PERMITTED_ENDPOINT_HOSTS.any? do |permitted|
|
|
host == permitted || host.end_with?(".#{permitted}")
|
|
end
|
|
end
|
|
end
|