FIX: Make discobot certificate faster/non blocking (#11344)

This moves the way we add the user avatar and site logo
to the discobot certificates from embeded base64 png to
just using the files urls in the href to the image tag.

This will make generation faster and the certificate
smaller overall, but it can't be used in a  `img` tag
anymore, since SVGs in `img` tags don't load the external images

In order to work around that we will move the certificate
in posts to an iframe, which works fine without any user
visible changes. For this to be possible the plugin automatically
adds the site current domain to the list of allowed iframe origins.
This commit is contained in:
Rafael dos Santos Silva 2021-02-01 20:49:32 -03:00 committed by GitHub
parent ca4a962766
commit bf5611f7eb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 33 additions and 64 deletions

View File

@ -137,7 +137,7 @@ module DiscourseNarrativeBot
src = Discourse.base_url + DiscourseNarrativeBot::Engine.routes.url_helpers.certificate_path(options)
alt = CGI.escapeHTML(I18n.t("#{self.class::I18N_KEY}.certificate.alt"))
"<img class='discobot-certificate' src='#{src}' width='650' height='464' alt='#{alt}'>"
"<iframe class='discobot-certificate' src='#{src}' width='650' height='464' alt='#{alt}'>"
end
protected

View File

@ -2,9 +2,9 @@
module DiscourseNarrativeBot
class CertificateGenerator
def initialize(user, date, avatar_data)
def initialize(user, date, avatar_url)
@user = user
@avatar_data = avatar_data
@avatar_url = avatar_url
date =
begin
@ -23,14 +23,14 @@ module DiscourseNarrativeBot
def new_user_track
svg_default_width = 538.583
logo_container = logo_group(55, svg_default_width, 350)
logo_container = logo_group(55, svg_default_width, 280)
ApplicationController.render(inline: read_template('new_user'), assigns: assign_options(svg_default_width, logo_container))
end
def advanced_user_track
svg_default_width = 722.8
logo_container = logo_group(40, svg_default_width, 280)
logo_container = logo_group(40, svg_default_width, 350)
ApplicationController.render(inline: read_template('advanced_user'), assigns: assign_options(svg_default_width, logo_container))
end
@ -46,7 +46,7 @@ module DiscourseNarrativeBot
width: width,
discobot_user: @discobot_user,
date: @date,
avatar_url: base64_image_data(@avatar_data),
avatar_url: @avatar_url,
logo_group: logo_group,
name: name
}
@ -59,48 +59,16 @@ module DiscourseNarrativeBot
def logo_group(size, width, height)
return unless SiteSetting.site_logo_small_url.present?
begin
uri = URI(SiteSetting.site_logo_small_url)
uri = URI(SiteSetting.site_logo_small_url)
logo_uri =
if uri.host.blank? || uri.scheme.blank?
URI("#{Discourse.base_url}/#{uri.path}")
else
uri
end
logo_uri =
if uri.host.blank? || uri.scheme.blank?
URI("#{Discourse.base_url}/#{uri.path}")
else
uri
end
<<~URL
<g transform="translate(#{width / 2 - (size / 2)} #{height})">
<image height="#{size}px" width="#{size}px" #{base64_image_link(logo_uri)}/>
</g>
URL
rescue URI::InvalidURIError
''
end
end
def base64_image_data(data)
return "" if data.blank?
"xlink:href=\"data:image/png;base64,#{Base64.strict_encode64(data)}\""
end
def base64_image_link(url)
if image = fetch_image(url)
base64_image_data(image)
else
""
end
end
def fetch_image(url)
FileHelper.download(
url.to_s,
max_file_size: SiteSetting.max_image_size_kb.kilobytes,
tmp_file_name: 'narrative-bot-logo',
follow_redirect: true
)&.read
rescue OpenURI::HTTPError
# Ignore if fetching image returns a non 200 response
{ size: size, width: width, height: height, logo_uri: logo_uri }
end
end
end

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 729 KiB

After

Width:  |  Height:  |  Size: 729 KiB

View File

@ -452,12 +452,14 @@
<clipPath id="clipCircle">
<circle r="15" cx="15" cy="15"/>
</clipPath>
<image clip-path="url(#clipCircle)" height="30px" width="30px" <%= sanitize(@avatar_url) %>/>
<image clip-path="url(#clipCircle)" height="30px" width="30px" href="<%= @avatar_url %>"/>
</g>
<text x="<%= @width / 2 %>" y="240.94" text-anchor="middle" font-size="24" fill="#020403" font-family="Tangerine, Tangerine">
<%= @name %>
</text>
<%== @logo_group %>
<g transform="translate(<%= @logo_group[:width] / 2 - (@logo_group[:size] / 2) %> <%= @logo_group[:height] %>)">
<image height="<%= @logo_group[:size] %>px" width="<%= @logo_group[:size] %>px" href="<%= @logo_group[:logo_uri] %>"/>
</g>
</g>
<g id="Layer_1-2" data-name="Layer 1">
<path d="M339.328 47.187c-.034 1.193 3.25 2.2 6.51 3.383 5.5 1.994 13.1 3.73 17.68 3.112 5.05-.55 4.982-2.217 4.054-4.8-.1-4.647.258-9.493.156-14.142l-27.128-.458c.5 4.698-1.778 8.207-1.272 12.905z" transform="translate(-.108)" fill-rule="evenodd" fill="url(#GradientFill_24)"/>

Before

Width:  |  Height:  |  Size: 494 KiB

After

Width:  |  Height:  |  Size: 495 KiB

View File

@ -50,6 +50,11 @@ after_initialize do
# Disable welcome message because that is what the bot is supposed to replace.
SiteSetting.send_welcome_message = false if SiteSetting.send_welcome_message
certificate_path = "#{Discourse.base_url}/discobot/certificate.svg"
if SiteSetting.discourse_narrative_bot_enabled && !SiteSetting.allowed_iframes.include?(certificate_path)
SiteSetting.allowed_iframes = SiteSetting.allowed_iframes.split('|').append("#{Discourse.base_url}/discobot/certificate.svg").join('|')
end
require_dependency 'plugin_store'
module ::DiscourseNarrativeBot
@ -94,8 +99,7 @@ after_initialize do
raise Discourse::NotFound if user.blank?
hijack do
avatar_data = fetch_avatar(user)
generator = CertificateGenerator.new(user, params[:date], avatar_data)
generator = CertificateGenerator.new(user, params[:date], avatar_url(user))
svg = params[:type] == 'advanced' ? generator.advanced_user_track : generator.new_user_track
@ -107,16 +111,8 @@ after_initialize do
private
def fetch_avatar(user)
avatar_url = UrlHelper.absolute(Discourse.base_path + user.avatar_template.gsub('{size}', '250'))
FileHelper.download(
avatar_url.to_s,
max_file_size: SiteSetting.max_image_size_kb.kilobytes,
tmp_file_name: 'narrative-bot-avatar',
follow_redirect: true
)&.read
rescue OpenURI::HTTPError
# Ignore if fetching image returns a non 200 response
def avatar_url(user)
UrlHelper.absolute(Discourse.base_path + user.avatar_template.gsub('{size}', '250'))
end
end
end

View File

@ -36,7 +36,9 @@ describe "Discobot Certificate" do
get '/discobot/certificate.svg', params: params
expect(response.status).to eq(200)
expect(response.body).to include('<image height="55px" width="55px" />')
expect(response.body).to include('<svg')
expect(response.body).to include(user.avatar_template.gsub('{size}', '250'))
expect(response.body).to include(SiteSetting.site_logo_small_url)
end
describe 'when params are missing' do