module Recaptcha::Helpers
Constants
- DEFAULT_ERRORS
Public Class Methods
# File lib/recaptcha/helpers.rb, line 10 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 261 def self.recaptcha_v3_async_execute_function_name(action) "#{recaptcha_v3_execute_function_name(action)}Async" end
# File lib/recaptcha/helpers.rb, line 265 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 256 def self.recaptcha_v3_execute_function_name(action) "executeRecaptchaFor#{sanitize_action_for_js(action)}" end
# File lib/recaptcha/helpers.rb, line 109 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 115 def self.to_message(key, default) I18n.translate(key, default: default) end
Private Class Methods
# File lib/recaptcha/helpers.rb, line 124 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 314 def self.dasherize_action(action) action.to_s.gsub(/\W/, '-').tr('_', '-') end
v2
# File lib/recaptcha/helpers.rb, line 271 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 299 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 318 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 237 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 249 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 179 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 231 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 213 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 308 def self.sanitize_action_for_js(action) action.to_s.gsub(/\W/, '_').split(/\/|_/).map(&:capitalize).join end