class Rollbar::Middleware::Js
Middleware
to inject the rollbar.js snippet into a 200 html response
Constants
- JS_IS_INJECTED_KEY
- SNIPPET
Attributes
app[R]
config[R]
Public Class Methods
new(app, config)
click to toggle source
# File lib/rollbar/middleware/js.rb, line 19 def initialize(app, config) @app = app @config = config end
Public Instance Methods
call(env)
click to toggle source
# File lib/rollbar/middleware/js.rb, line 24 def call(env) app_result = app.call(env) begin return app_result unless add_js?(env, app_result[1]) response_string = add_js(env, app_result[2]) build_response(env, app_result, response_string) rescue StandardError => e Rollbar.log_error( "[Rollbar] Rollbar.js could not be added because #{e} exception" ) app_result end end
Private Instance Methods
add_js(env, response)
click to toggle source
# File lib/rollbar/middleware/js.rb, line 67 def add_js(env, response) body = join_body(response) close_old_response(response) return nil unless body insert_after_idx = find_insertion_point(body) return nil unless insert_after_idx build_body_with_js(env, body, insert_after_idx) rescue StandardError => e Rollbar.log_error( "[Rollbar] Rollbar.js could not be added because #{e} exception" ) nil end
add_js?(env, headers)
click to toggle source
# File lib/rollbar/middleware/js.rb, line 47 def add_js?(env, headers) enabled? && !env[JS_IS_INJECTED_KEY] && html?(headers) && !attachment?(headers) && !streaming?(env) end
add_person_data(js_config, env)
click to toggle source
# File lib/rollbar/middleware/js.rb, line 150 def add_person_data(js_config, env) person_data = extract_person_data_from_controller(env) return if person_data && person_data.empty? js_config[:payload] ||= {} js_config[:payload][:person] = person_data if person_data end
attachment?(headers)
click to toggle source
# File lib/rollbar/middleware/js.rb, line 56 def attachment?(headers) headers['Content-Disposition'].to_s.include?('attachment') end
build_body_with_js(env, body, head_open_end)
click to toggle source
# File lib/rollbar/middleware/js.rb, line 106 def build_body_with_js(env, body, head_open_end) return body unless head_open_end body[0..head_open_end] << config_js_tag(env) << snippet_js_tag(env) << body[head_open_end + 1..-1] end
build_response(env, app_result, response_string)
click to toggle source
# File lib/rollbar/middleware/js.rb, line 84 def build_response(env, app_result, response_string) return app_result unless response_string env[JS_IS_INJECTED_KEY] = true status, headers, = app_result if headers.key?('Content-Length') headers['Content-Length'] = response_string.bytesize.to_s end response = ::Rack::Response.new(response_string, status, headers) finished = response.finish # Rack < 2.x Response#finish returns self in array[2]. # Rack >= 2.x returns self.body. # Always return with the response object here regardless of rack version. finished[2] = response finished end
close_old_response(response)
click to toggle source
# File lib/rollbar/middleware/js.rb, line 131 def close_old_response(response) response.close if response.respond_to?(:close) end
config_js_tag(env)
click to toggle source
# File lib/rollbar/middleware/js.rb, line 135 def config_js_tag(env) require 'json' require 'rollbar/middleware/js/json_value' js_config = Rollbar::Util.deep_copy(config[:options]) add_person_data(js_config, env) # MUST use the Ruby JSON encoder (JSON#generate). # See lib/rollbar/middleware/js/json_value json = ::JSON.generate(js_config, Rollbar::JSON::JsOptionsState.new) script_tag("var _rollbarConfig = #{json};", env) end
enabled?()
click to toggle source
# File lib/rollbar/middleware/js.rb, line 43 def enabled? !!config[:enabled] end
find_end_after_regex(body, regex)
click to toggle source
# File lib/rollbar/middleware/js.rb, line 119 def find_end_after_regex(body, regex) open_idx = body.index(regex) body.index('>', open_idx) if open_idx end
find_insertion_point(body)
click to toggle source
# File lib/rollbar/middleware/js.rb, line 113 def find_insertion_point(body) find_end_after_regex(body, /<meta\s*charset=/i) || find_end_after_regex(body, /<meta\s*http-equiv="Content-Type"/i) || find_end_after_regex(body, /<head\W/i) end
html?(headers)
click to toggle source
# File lib/rollbar/middleware/js.rb, line 52 def html?(headers) headers['Content-Type'] && headers['Content-Type'].include?('text/html') end
html_safe_if_needed(string)
click to toggle source
# File lib/rollbar/middleware/js.rb, line 178 def html_safe_if_needed(string) string = string.html_safe if string.respond_to?(:html_safe) string end
join_body(response)
click to toggle source
# File lib/rollbar/middleware/js.rb, line 124 def join_body(response) response.to_enum.reduce('') do |acc, fragment| acc << fragment.to_s acc end end
js_snippet()
click to toggle source
# File lib/rollbar/middleware/js.rb, line 163 def js_snippet SNIPPET end
rails5_nonce(env)
click to toggle source
Rails
5.2+ Secure Content Policy
# File lib/rollbar/middleware/js.rb, line 184 def rails5_nonce(env) req = ::ActionDispatch::Request.new(env) # Rails will only return a nonce if the app has set a nonce generator. # So if we get a valid nonce here, we know we should use it. # # Having both 'unsafe-inline' and a nonce is a valid and preferred # browser compatibility configuration. # # If the script_src key is missing, Rails will not add the nonce to the headers, # so we detect this and will not add it in this case. req.respond_to?(:content_security_policy) && req.content_security_policy && req.content_security_policy.directives['script-src'] && req.content_security_policy_nonce end
script_tag(content, env)
click to toggle source
# File lib/rollbar/middleware/js.rb, line 167 def script_tag(content, env) nonce = rails5_nonce(env) || secure_headers_nonce(env) script_tag_content = if nonce "\n<script nonce=\"#{nonce}\">#{content}</script>" else "\n<script>#{content}</script>" end html_safe_if_needed(script_tag_content) end
secure_headers(req)
click to toggle source
# File lib/rollbar/middleware/js.rb, line 210 def secure_headers(req) return SecureHeadersFalse.new unless defined?(::SecureHeaders::Configuration) # If the nonce key has been set, the app is using nonces for this request. # If it hasn't, we shouldn't cause one to be added to script_src, so return now. return SecureHeadersFalse.new unless secure_headers_nonce_key(req) config = ::SecureHeaders::Configuration has_nonce = ::SecureHeaders.respond_to?( :content_security_policy_script_nonce ) secure_headers_cls = if !has_nonce SecureHeadersFalse elsif config.respond_to?(:get) SecureHeaders3To5 elsif config.dup.respond_to?(:csp) SecureHeaders6 else SecureHeadersFalse end secure_headers_cls.new end
secure_headers_nonce(env)
click to toggle source
Secure Headers gem
# File lib/rollbar/middleware/js.rb, line 202 def secure_headers_nonce(env) req = ::Rack::Request.new(env) return unless secure_headers(req).append_nonce? ::SecureHeaders.content_security_policy_script_nonce(req) end
secure_headers_nonce_key(req)
click to toggle source
# File lib/rollbar/middleware/js.rb, line 235 def secure_headers_nonce_key(req) defined?(::SecureHeaders::NONCE_KEY) && req.env[::SecureHeaders::NONCE_KEY] end
snippet_js_tag(env)
click to toggle source
# File lib/rollbar/middleware/js.rb, line 159 def snippet_js_tag(env) script_tag(js_snippet, env) end
streaming?(env)
click to toggle source
# File lib/rollbar/middleware/js.rb, line 60 def streaming?(env) return false unless defined?(ActionController::Live) env['action_controller.instance'] .class.included_modules.include?(ActionController::Live) end