require 'active_support/json' require 'active_support/core_ext/hash/indifferent_access' require 'active_support/core_ext/string/output_safety' require 'action_view' module IntercomRails class ScriptTag include ::ActionView::Helpers::JavaScriptHelper attr_reader :user_details, :company_details, :show_everywhere, :session_duration attr_accessor :secret, :widget_options, :controller, :nonce def initialize(options = {}) self.secret = options[:secret] || Config.api_secret self.widget_options = widget_options_from_config.merge(options[:widget] || {}) self.controller = options[:controller] @show_everywhere = options[:show_everywhere] @session_duration = session_duration_from_config self.user_details = options[:find_current_user_details] ? find_current_user_details : options[:user_details] # Request specific custom data for non-signed up users base on lead_attributes self.user_details = self.user_details.merge(find_lead_attributes) self.company_details = if options[:find_current_company_details] find_current_company_details elsif options[:user_details] options[:user_details].delete(:company) if options[:user_details] end self.nonce = options[:nonce] end def valid? return false if user_details[:excluded_user] == true valid = user_details[:app_id].present? unless @show_everywhere valid = valid && (user_details[:user_id] || user_details[:email]).present? end if nonce valid = valid && valid_nonce? end valid end def valid_nonce? valid = false if nonce # Base64 regexp: # - blocks of 4 [A-Za-z0-9+/] # followed either by: # - blocks of 2 [A-Za-z0-9+/] + '==' # - blocks of 3 [A-Za-z0-9+/] + '=' base64_regexp = Regexp.new('^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$') m = base64_regexp.match(nonce) if nonce == m.to_s valid = true end end valid end def intercom_settings hsh = user_details hsh[:session_duration] = @session_duration if @session_duration.present? hsh[:widget] = widget_options if widget_options.present? hsh[:company] = company_details if company_details.present? hsh[:hide_default_launcher] = Config.hide_default_launcher if Config.hide_default_launcher hsh end def to_s js_options = 'id="IntercomSettingsScriptTag"' if nonce && valid_nonce? js_options = js_options + " nonce=\"#{nonce}\"" end str = "\n" str.respond_to?(:html_safe) ? str.html_safe : str end def csp_sha256 base64_sha256 = Base64.encode64(Digest::SHA256.digest(intercom_javascript)) csp_hash = "'sha256-#{base64_sha256}'".delete("\n") csp_hash end def find_lead_attributes lead_attributes = IntercomRails.config.user.lead_attributes return {} unless controller.present? && lead_attributes && lead_attributes.size > 0 # Get custom data. This also allows to retrieve custom data # set via helper function intercom_custom_data custom_data = controller.intercom_custom_data.user.with_indifferent_access custom_data.select {|k, v| lead_attributes.map(&:to_s).include?(k)} end private def intercom_javascript intercom_settings_json = ActiveSupport::JSON.encode(intercom_settings).gsub('<', '\u003C') str = "window.intercomSettings = #{intercom_settings_json};(function(){var w=window;var ic=w.Intercom;if(typeof ic===\"function\"){ic('reattach_activator');ic('update',intercomSettings);}else{var d=document;var i=function(){i.c(arguments)};i.q=[];i.c=function(args){i.q.push(args)};w.Intercom=i;function l(){var s=d.createElement('script');s.type='text/javascript';s.async=true;s.src='#{Config.library_url || "https://widget.intercom.io/widget/#{j app_id}"}';var x=d.getElementsByTagName('script')[0];x.parentNode.insertBefore(s,x);}if(w.attachEvent){w.attachEvent('onload',l);}else{w.addEventListener('load',l,false);}};})()" str end def user_details=(user_details) @user_details = DateHelper.convert_dates_to_unix_timestamps(user_details || {}) @user_details = @user_details.with_indifferent_access.tap do |u| [:email, :name, :user_id].each { |k| u.delete(k) if u[k].nil? } u[:user_hash] ||= user_hash if secret.present? && (u[:user_id] || u[:email]).present? u[:app_id] ||= app_id end end def find_current_user_details return {} unless controller.present? Proxy::User.current_in_context(controller).to_hash rescue NoUserFoundError {} rescue ExcludedUserFoundError { excluded_user: true } end def company_details=(company_details) @company_details = DateHelper.convert_dates_to_unix_timestamps(company_details || {}) @company_details = @company_details.with_indifferent_access.tap do |c| [:id, :name].each { |k| c.delete(k) if c[k].nil? } end end def find_current_company_details return {} unless controller.present? Proxy::Company.current_in_context(controller).to_hash rescue NoCompanyFoundError {} end def user_hash OpenSSL::HMAC.hexdigest("sha256", secret, (user_details[:user_id] || user_details[:email]).to_s) end def session_duration_from_config session_duration = IntercomRails.config.session_duration if session_duration && valid_session_duration?(session_duration) session_duration end end def valid_session_duration?(session_duration) session_duration.is_a?(Integer) && session_duration > 0 end def app_id return user_details[:app_id] if user_details[:app_id].present? return IntercomRails.config.app_id if IntercomRails.config.app_id.present? return 'abcd1234' if defined?(Rails) && (Rails.env.development? || Rails.env.test?) nil end def widget_options_from_config config = {} custom_activator = Config.inbox.custom_activator activator = case Config.inbox.style when :default '#IntercomDefaultWidget' when :custom custom_activator || '#Intercom' else nil end config[:activator] = activator if activator config end end end