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-" + dasherize_action(action) name = options.delete(:name) || "g-recaptcha-response[#{action}]" 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]}" html, tag_attributes = components(options) if 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 237 def self.recaptcha_v3_async_execute_function_name(action) "#{recaptcha_v3_execute_function_name(action)}Async" end
# File lib/recaptcha/helpers.rb, line 241 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 232 def self.recaptcha_v3_execute_function_name(action) "executeRecaptchaFor#{sanitize_action_for_js(action)}" end
# File lib/recaptcha/helpers.rb, line 103 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 109 def self.to_message(key, default) I18n.translate(key, default: default) end
Private Class Methods
# File lib/recaptcha/helpers.rb, line 118 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 289 def self.dasherize_action(action) action.to_s.gsub(/\W/, '-').dasherize end
v2
# File lib/recaptcha/helpers.rb, line 247 def self.default_callback(options = {}) nonce = options[:nonce] nonce_attr = " nonce='#{nonce}'" if nonce <<-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 eles = document.getElementsByClassName('g-recaptcha'); if (eles.length > 0) { var form = closestForm(eles[0]); if (form) { form.submit(); } } }; </script> HTML end
# File lib/recaptcha/helpers.rb, line 274 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 293 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 212 def self.recaptcha_v3_define_default_callback(callback) <<-HTML var #{callback} = function(id, token) { var element = document.getElementById(id); element.value = token; } </script> 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 225 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 173 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 function #{recaptcha_v3_execute_function_name(action)}() { grecaptcha.ready(function() { grecaptcha.execute('#{site_key}', {action: '#{action}'}).then(function(token) { //console.log('#{id}', 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!) 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 206 def self.recaptcha_v3_inline_script?(options) !Recaptcha.skip_env?(options[:env]) && options[:script] != false && options[:inline_script] != false 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 283 def self.sanitize_action_for_js(action) action.to_s.gsub(/\W/, '_').camelize end