module Recaptcha::Helpers
Constants
- DEFAULT_ERRORS
Public Class Methods
# File lib/recaptcha/helpers.rb, line 9 def self.recaptcha_v3(options = {}) site_key = options[:site_key] ||= Recaptcha.configuration.site_key! action = options.delete(:action) || raise(Recaptcha::RecaptchaError, 'action is required') id = options.delete(:id) || "g-recaptcha-response-data-" + dasherize_action(action) name = options.delete(:name) || "g-recaptcha-response-data[#{action}]" turbolinks = options.delete(:turbolinks) options[:render] = site_key options[:script_async] ||= false options[:script_defer] ||= false element = options.delete(:element) element = element == false ? false : :input if element == :input callback = options.delete(:callback) || recaptcha_v3_default_callback_name(action) end options[:class] = "g-recaptcha-response #{options[:class]}" if turbolinks options[:onload] = recaptcha_v3_execute_function_name(action) end html, tag_attributes = components(options) if turbolinks html << recaptcha_v3_onload_script(site_key, action, callback, id, options) elsif recaptcha_v3_inline_script?(options) html << recaptcha_v3_inline_script(site_key, action, callback, id, options) end case element when :input html << %(<input type="hidden" name="#{name}" id="#{id}" #{tag_attributes}/>\n) when false # No tag nil else raise(RecaptchaError, "ReCAPTCHA element `#{options[:element]}` is not valid.") end html.respond_to?(:html_safe) ? html.html_safe : html end
Returns the name of an async JavaScript function that executes the reCAPTCHA code.
# File lib/recaptcha/helpers.rb, line 260 def self.recaptcha_v3_async_execute_function_name(action) "#{recaptcha_v3_execute_function_name(action)}Async" end
# File lib/recaptcha/helpers.rb, line 264 def self.recaptcha_v3_default_callback_name(action) "setInputWithRecaptchaResponseTokenFor#{sanitize_action_for_js(action)}" end
Returns the name of the JavaScript function that actually executes the reCAPTCHA code (calls grecaptcha.execute). You can call it again later to reset it.
# File lib/recaptcha/helpers.rb, line 255 def self.recaptcha_v3_execute_function_name(action) "executeRecaptchaFor#{sanitize_action_for_js(action)}" end
# File lib/recaptcha/helpers.rb, line 108 def self.to_error_message(key) default = DEFAULT_ERRORS.fetch(key) { raise ArgumentError "Unknown reCAPTCHA error - #{key}" } to_message("recaptcha.errors.#{key}", default) end
# File lib/recaptcha/helpers.rb, line 114 def self.to_message(key, default) I18n.translate(key, default: default) end
Private Class Methods
# File lib/recaptcha/helpers.rb, line 123 def self.components(options) html = +'' attributes = {} fallback_uri = +'' options = options.dup env = options.delete(:env) class_attribute = options.delete(:class) site_key = options.delete(:site_key) hl = options.delete(:hl) onload = options.delete(:onload) render = options.delete(:render) script_async = options.delete(:script_async) script_defer = options.delete(:script_defer) nonce = options.delete(:nonce) skip_script = (options.delete(:script) == false) || (options.delete(:external_script) == false) ui = options.delete(:ui) data_attribute_keys = [:badge, :theme, :type, :callback, :expired_callback, :error_callback, :size] data_attribute_keys << :tabindex unless ui == :button data_attributes = {} data_attribute_keys.each do |data_attribute| value = options.delete(data_attribute) data_attributes["data-#{data_attribute.to_s.tr('_', '-')}"] = value if value end unless Recaptcha.skip_env?(env) site_key ||= Recaptcha.configuration.site_key! script_url = Recaptcha.configuration.api_server_url query_params = hash_to_query( hl: hl, onload: onload, render: render ) script_url += "?#{query_params}" unless query_params.empty? async_attr = "async" if script_async != false defer_attr = "defer" if script_defer != false nonce_attr = " nonce='#{nonce}'" if nonce html << %(<script src="#{script_url}" #{async_attr} #{defer_attr} #{nonce_attr}></script>\n) unless skip_script fallback_uri = %(#{script_url.chomp(".js")}/fallback?k=#{site_key}) attributes["data-sitekey"] = site_key attributes.merge! data_attributes end # The remaining options will be added as attributes on the tag. attributes["class"] = "g-recaptcha #{class_attribute}" tag_attributes = attributes.merge(options).map { |k, v| %(#{k}="#{v}") }.join(" ") [html, tag_attributes, fallback_uri] end
Returns a dasherized string that is safe for use as an HTML ID ::dasherize_action('my/action') => 'my-action'
# File lib/recaptcha/helpers.rb, line 313 def self.dasherize_action(action) action.to_s.gsub(/\W/, '-').tr('_', '-') end
v2
# File lib/recaptcha/helpers.rb, line 270 def self.default_callback(options = {}) nonce = options[:nonce] nonce_attr = " nonce='#{nonce}'" if nonce selector_attr = options[:id] ? "##{options[:id]}" : ".g-recaptcha" <<-HTML <script#{nonce_attr}> var invisibleRecaptchaSubmit = function () { var closestForm = function (ele) { var curEle = ele.parentNode; while (curEle.nodeName !== 'FORM' && curEle.nodeName !== 'BODY'){ curEle = curEle.parentNode; } return curEle.nodeName === 'FORM' ? curEle : null }; var el = document.querySelector("#{selector_attr}") if (!!el) { var form = closestForm(el); if (form) { form.submit(); } } }; </script> HTML end
# File lib/recaptcha/helpers.rb, line 298 def self.default_callback_required?(options) options[:callback] == 'invisibleRecaptchaSubmit' && !Recaptcha.skip_env?(options[:env]) && options[:script] != false && options[:inline_script] != false end
# File lib/recaptcha/helpers.rb, line 317 def self.hash_to_query(hash) hash.delete_if { |_, val| val.nil? || val.empty? }.to_a.map { |pair| pair.join('=') }.join('&') end
# File lib/recaptcha/helpers.rb, line 236 def self.recaptcha_v3_define_default_callback(callback) <<-HTML var #{callback} = function(id, token) { var element = document.getElementById(id); element.value = token; } HTML end
Returns true if we should be adding the default callback. That is, if the given callback name is the default callback name (for the given action) and we are not skipping inline scripts for any reason.
# File lib/recaptcha/helpers.rb, line 248 def self.recaptcha_v3_define_default_callback?(callback, action, options) callback == recaptcha_v3_default_callback_name(action) && recaptcha_v3_inline_script?(options) end
Renders a script that calls `grecaptcha.execute` for the given `site_key` and `action` and calls the `callback` with the resulting response token.
# File lib/recaptcha/helpers.rb, line 178 def self.recaptcha_v3_inline_script(site_key, action, callback, id, options = {}) nonce = options[:nonce] nonce_attr = " nonce='#{nonce}'" if nonce <<-HTML <script#{nonce_attr}> // Define function so that we can call it again later if we need to reset it // This executes reCAPTCHA and then calls our callback. function #{recaptcha_v3_execute_function_name(action)}() { grecaptcha.ready(function() { grecaptcha.execute('#{site_key}', {action: '#{action}'}).then(function(token) { #{callback}('#{id}', token) }); }); }; // Invoke immediately #{recaptcha_v3_execute_function_name(action)}() // Async variant so you can await this function from another async function (no need for // an explicit callback function then!) // Returns a Promise that resolves with the response token. async function #{recaptcha_v3_async_execute_function_name(action)}() { return new Promise((resolve, reject) => { grecaptcha.ready(async function() { resolve(await grecaptcha.execute('#{site_key}', {action: '#{action}'})) }); }) }; #{recaptcha_v3_define_default_callback(callback) if recaptcha_v3_define_default_callback?(callback, action, options)} </script> HTML end
# File lib/recaptcha/helpers.rb, line 230 def self.recaptcha_v3_inline_script?(options) !Recaptcha.skip_env?(options[:env]) && options[:script] != false && options[:inline_script] != false end
# File lib/recaptcha/helpers.rb, line 212 def self.recaptcha_v3_onload_script(site_key, action, callback, id, options = {}) nonce = options[:nonce] nonce_attr = " nonce='#{nonce}'" if nonce <<-HTML <script#{nonce_attr}> function #{recaptcha_v3_execute_function_name(action)}() { grecaptcha.ready(function() { grecaptcha.execute('#{site_key}', {action: '#{action}'}).then(function(token) { #{callback}('#{id}', token) }); }); }; #{recaptcha_v3_define_default_callback(callback) if recaptcha_v3_define_default_callback?(callback, action, options)} </script> HTML end
Returns a camelized string that is safe for use in a JavaScript variable/function name. ::sanitize_action_for_js('my/action') => 'MyAction'
# File lib/recaptcha/helpers.rb, line 307 def self.sanitize_action_for_js(action) action.to_s.gsub(/\W/, '_').split(/\/|_/).map(&:capitalize).join end