kameleoon-client-ruby 3.8.0 → 3.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/kameleoon/configuration/data_file.rb +21 -3
- data/lib/kameleoon/configuration/feature_flag.rb +7 -5
- data/lib/kameleoon/configuration/me_group.rb +19 -0
- data/lib/kameleoon/data/cbscores.rb +58 -0
- data/lib/kameleoon/data/geolocation.rb +0 -1
- data/lib/kameleoon/data/kcs_heat.rb +3 -3
- data/lib/kameleoon/data/manager/visitor.rb +14 -1
- data/lib/kameleoon/kameleoon_client.rb +87 -23
- data/lib/kameleoon/kameleoon_client_config.rb +8 -4
- data/lib/kameleoon/managers/remote_data/remote_visitor_data.rb +28 -5
- data/lib/kameleoon/network/network_manager.rb +8 -4
- data/lib/kameleoon/network/url_provider.rb +45 -15
- data/lib/kameleoon/types/remote_visitor_data_filter.rb +5 -3
- data/lib/kameleoon/utils.rb +40 -11
- data/lib/kameleoon/version.rb +1 -1
- metadata +4 -2
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 1831531ee6a40f0fbd32f110f9a9c9c1a33f71569c8b8de842bc01c06250d6d5
         | 
| 4 | 
            +
              data.tar.gz: 6e6f8f9d0ee511c2a27fc63ff040e6155b90df66925989d1d08bd473f86c071b
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 6f575eb2b6e12302ffa5cdb067582d994ab890edcab12f174732d396adc4248fc96a9e27935c008db52f59a93b5e0c713530356acab47711727651d2a4d48e3a
         | 
| 7 | 
            +
              data.tar.gz: '058e6a5284550663cf20223eca099728a9ca0985aacfcfa829e585070228a83b38b4facda77d38a7f3ae17ae8fad37cc7898e47e2519826364a46b3205b33452'
         | 
| @@ -3,15 +3,16 @@ | |
| 3 3 | 
             
            require 'kameleoon/configuration/custom_data_info'
         | 
| 4 4 | 
             
            require 'kameleoon/configuration/experiment'
         | 
| 5 5 | 
             
            require 'kameleoon/configuration/feature_flag'
         | 
| 6 | 
            +
            require 'kameleoon/configuration/me_group'
         | 
| 6 7 | 
             
            require 'kameleoon/configuration/settings'
         | 
| 7 8 | 
             
            require 'kameleoon/logging/kameleoon_logger'
         | 
| 8 9 |  | 
| 9 10 | 
             
            module Kameleoon
         | 
| 10 11 | 
             
              module Configuration
         | 
| 11 12 | 
             
                class DataFile
         | 
| 12 | 
            -
                  attr_reader :settings, :feature_flags, : | 
| 13 | 
            -
                              :rule_info_by_exp_id, :variation_by_id, :custom_data_info, | 
| 14 | 
            -
                              :holdout
         | 
| 13 | 
            +
                  attr_reader :settings, :feature_flags, :me_groups, :has_any_targeted_delivery_rule, :feature_flag_by_id,
         | 
| 14 | 
            +
                              :rule_by_segment_id, :rule_info_by_exp_id, :variation_by_id, :custom_data_info,
         | 
| 15 | 
            +
                              :experiment_ids_with_js_css_variable, :holdout
         | 
| 15 16 |  | 
| 16 17 | 
             
                  def to_s
         | 
| 17 18 | 
             
                    'DataFile{' \
         | 
| @@ -51,6 +52,7 @@ module Kameleoon | |
| 51 52 | 
             
                    Logging::KameleoonLogger.debug('CALL: DataFile.init_default')
         | 
| 52 53 | 
             
                    @settings = Settings.new
         | 
| 53 54 | 
             
                    @feature_flags = {}
         | 
| 55 | 
            +
                    @me_groups = {}
         | 
| 54 56 | 
             
                    @has_any_targeted_delivery_rule = false
         | 
| 55 57 | 
             
                    @custom_data_info = Kameleoon::Configuration::CustomDataInfo.new(nil)
         | 
| 56 58 | 
             
                    Logging::KameleoonLogger.debug('RETURN: DataFile.init_default')
         | 
| @@ -64,6 +66,7 @@ module Kameleoon | |
| 64 66 | 
             
                      ff = FeatureFlag.new(raw)
         | 
| 65 67 | 
             
                      @feature_flags[ff.feature_key] = ff
         | 
| 66 68 | 
             
                    end
         | 
| 69 | 
            +
                    @me_groups = make_me_groups(@feature_flags)
         | 
| 67 70 | 
             
                    @has_any_targeted_delivery_rule = any_targeted_delivery_rule?
         | 
| 68 71 | 
             
                    @custom_data_info = CustomDataInfo.new(configuration['customData'])
         | 
| 69 72 | 
             
                    @holdout = Experiment.from_json(configuration['holdout']) if configuration.include?('holdout')
         | 
| @@ -101,6 +104,21 @@ module Kameleoon | |
| 101 104 | 
             
                    @experiment_ids_with_js_css_variable.freeze
         | 
| 102 105 | 
             
                  end
         | 
| 103 106 |  | 
| 107 | 
            +
                  def make_me_groups(feature_flags)
         | 
| 108 | 
            +
                    me_group_lists = {}
         | 
| 109 | 
            +
                    feature_flags.each_value do |feature_flag|
         | 
| 110 | 
            +
                      next if feature_flag.me_group_name.nil?
         | 
| 111 | 
            +
             | 
| 112 | 
            +
                      me_group_list = me_group_lists[feature_flag.me_group_name]
         | 
| 113 | 
            +
                      if me_group_list
         | 
| 114 | 
            +
                        me_group_list.push(feature_flag)
         | 
| 115 | 
            +
                      else
         | 
| 116 | 
            +
                        me_group_lists[feature_flag.me_group_name] = [feature_flag]
         | 
| 117 | 
            +
                      end
         | 
| 118 | 
            +
                    end
         | 
| 119 | 
            +
                    me_group_lists.transform_values { |me_group_list| MEGroup.new(me_group_list) }
         | 
| 120 | 
            +
                  end
         | 
| 121 | 
            +
             | 
| 104 122 | 
             
                  def feature_flag_variable_js_css?(feature_flag)
         | 
| 105 123 | 
             
                    feature_flag.variations.first&.variables&.any? do |variable|
         | 
| 106 124 | 
             
                      %w[JS CSS].include?(variable.type)
         | 
| @@ -8,7 +8,7 @@ module Kameleoon | |
| 8 8 | 
             
              module Configuration
         | 
| 9 9 | 
             
                # Class for manage all feature flags with rules
         | 
| 10 10 | 
             
                class FeatureFlag
         | 
| 11 | 
            -
                  attr_accessor :id, :feature_key, :variations, :default_variation_key, :environment_enabled, :rules
         | 
| 11 | 
            +
                  attr_accessor :id, :feature_key, :variations, :default_variation_key, :me_group_name, :environment_enabled, :rules
         | 
| 12 12 |  | 
| 13 13 | 
             
                  def self.create_from_array(array)
         | 
| 14 14 | 
             
                    array&.map { |it| FeatureFlag.new(it) }
         | 
| @@ -16,10 +16,11 @@ module Kameleoon | |
| 16 16 |  | 
| 17 17 | 
             
                  def to_s
         | 
| 18 18 | 
             
                    'FeatureFlag{' \
         | 
| 19 | 
            -
                      "id:#{@id}, | 
| 20 | 
            -
                      "feature_key | 
| 21 | 
            -
                      "environment_enabled:#{@environment_enabled}, | 
| 22 | 
            -
                      "default_variation_key | 
| 19 | 
            +
                      "id:#{@id}," \
         | 
| 20 | 
            +
                      "feature_key:'#{@feature_key}'," \
         | 
| 21 | 
            +
                      "environment_enabled:#{@environment_enabled}," \
         | 
| 22 | 
            +
                      "default_variation_key:'#{@default_variation_key}'," \
         | 
| 23 | 
            +
                      "me_group_name:'#{@me_group_name}'," \
         | 
| 23 24 | 
             
                      "rules:#{@rules.size}" \
         | 
| 24 25 | 
             
                      '}'
         | 
| 25 26 | 
             
                  end
         | 
| @@ -29,6 +30,7 @@ module Kameleoon | |
| 29 30 | 
             
                    @feature_key = hash['featureKey']
         | 
| 30 31 | 
             
                    @variations = Variation.create_from_array(hash['variations'])
         | 
| 31 32 | 
             
                    @default_variation_key = hash['defaultVariationKey']
         | 
| 33 | 
            +
                    @me_group_name = hash['mutuallyExclusiveGroup']
         | 
| 32 34 | 
             
                    @environment_enabled = hash['environmentEnabled']
         | 
| 33 35 | 
             
                    @rules = Rule.create_from_array(hash['rules'])
         | 
| 34 36 | 
             
                  end
         | 
| @@ -0,0 +1,19 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Kameleoon
         | 
| 4 | 
            +
              module Configuration
         | 
| 5 | 
            +
                class MEGroup
         | 
| 6 | 
            +
                  attr_reader :feature_flags
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                  def initialize(feature_flags)
         | 
| 9 | 
            +
                    @feature_flags = feature_flags.sort { |a, b| a.id <=> b.id }
         | 
| 10 | 
            +
                  end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                  def get_feature_flag_by_hash(hash)
         | 
| 13 | 
            +
                    idx = (hash * @feature_flags.size).to_i
         | 
| 14 | 
            +
                    idx = @feature_flags.size - 1 if idx >= @feature_flags.size
         | 
| 15 | 
            +
                    @feature_flags[idx]
         | 
| 16 | 
            +
                  end
         | 
| 17 | 
            +
                end
         | 
| 18 | 
            +
              end
         | 
| 19 | 
            +
            end
         | 
| @@ -0,0 +1,58 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Kameleoon
         | 
| 4 | 
            +
              class CBScores
         | 
| 5 | 
            +
                # keys = experiment IDs / values = list of variation IDs ordered descending
         | 
| 6 | 
            +
                # by score (there may be several variation ids with same score)
         | 
| 7 | 
            +
                attr_reader :values
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                def initialize(cbs_map)
         | 
| 10 | 
            +
                  values = cbs_map.transform_values { |cbs_value| extract_var_ids(cbs_value) }
         | 
| 11 | 
            +
                  values.freeze
         | 
| 12 | 
            +
                  @values = values
         | 
| 13 | 
            +
                end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                def to_s
         | 
| 16 | 
            +
                  str_values = @values.transform_values { |vgs| vgs.map(&:to_s) }
         | 
| 17 | 
            +
                  "CBScores{values:#{str_values}}"
         | 
| 18 | 
            +
                end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                class ScoredVarId
         | 
| 21 | 
            +
                  attr_reader :variation_id, :score
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                  def initialize(variation_id, score)
         | 
| 24 | 
            +
                    @variation_id = variation_id
         | 
| 25 | 
            +
                    @score = score
         | 
| 26 | 
            +
                  end
         | 
| 27 | 
            +
                end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                class VarGroup
         | 
| 30 | 
            +
                  attr_reader :ids
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                  def initialize(ids)
         | 
| 33 | 
            +
                    ids.sort!
         | 
| 34 | 
            +
                    ids.freeze
         | 
| 35 | 
            +
                    @ids = ids
         | 
| 36 | 
            +
                  end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                  def to_s
         | 
| 39 | 
            +
                    "VarGroup{ids:#{@ids}}"
         | 
| 40 | 
            +
                  end
         | 
| 41 | 
            +
                end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                private
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                def extract_var_ids(scores)
         | 
| 46 | 
            +
                  grouped = {}
         | 
| 47 | 
            +
                  scores.each do |score|
         | 
| 48 | 
            +
                    glist = grouped[score.score]
         | 
| 49 | 
            +
                    if glist
         | 
| 50 | 
            +
                      glist.push(score.variation_id)
         | 
| 51 | 
            +
                    else
         | 
| 52 | 
            +
                      grouped[score.score] = [score.variation_id]
         | 
| 53 | 
            +
                    end
         | 
| 54 | 
            +
                  end
         | 
| 55 | 
            +
                  grouped.sort_by { |score, _| -score }.collect { |_, glist| VarGroup.new(glist) }
         | 
| 56 | 
            +
                end
         | 
| 57 | 
            +
              end
         | 
| 58 | 
            +
            end
         | 
| @@ -2,6 +2,7 @@ | |
| 2 2 |  | 
| 3 3 | 
             
            require 'concurrent'
         | 
| 4 4 | 
             
            require 'kameleoon/data/browser'
         | 
| 5 | 
            +
            require 'kameleoon/data/cbscores'
         | 
| 5 6 | 
             
            require 'kameleoon/data/conversion'
         | 
| 6 7 | 
             
            require 'kameleoon/data/custom_data'
         | 
| 7 8 | 
             
            require 'kameleoon/data/device'
         | 
| @@ -106,6 +107,12 @@ module Kameleoon | |
| 106 107 | 
             
                    kcs_heat
         | 
| 107 108 | 
             
                  end
         | 
| 108 109 |  | 
| 110 | 
            +
                  def cbscores
         | 
| 111 | 
            +
                    cbs = @data.cbscores
         | 
| 112 | 
            +
                    Logging::KameleoonLogger.debug('CALL/RETURN: Visitor.cbscores -> (cbs: %s)', cbs)
         | 
| 113 | 
            +
                    cbs
         | 
| 114 | 
            +
                  end
         | 
| 115 | 
            +
             | 
| 109 116 | 
             
                  def visitor_visits
         | 
| 110 117 | 
             
                    visitor_visits = @data.visitor_visits
         | 
| 111 118 | 
             
                    Logging::KameleoonLogger.debug('CALL/RETURN: Visitor.visitor_visits -> (visitor_visits: %s)', visitor_visits)
         | 
| @@ -242,6 +249,8 @@ module Kameleoon | |
| 242 249 | 
             
                          @data.set_geolocation(data, overwrite)
         | 
| 243 250 | 
             
                        when KcsHeat
         | 
| 244 251 | 
             
                          @data.kcs_heat = data
         | 
| 252 | 
            +
                        when CBScores
         | 
| 253 | 
            +
                          @data.set_cbscores(data, overwrite)
         | 
| 245 254 | 
             
                        when VisitorVisits
         | 
| 246 255 | 
             
                          @data.visitor_visits = data
         | 
| 247 256 | 
             
                        when UniqueIdentifier
         | 
| @@ -267,7 +276,7 @@ module Kameleoon | |
| 267 276 |  | 
| 268 277 | 
             
                class VisitorData
         | 
| 269 278 | 
             
                  attr_reader :mutex, :device, :browser, :geolocation, :operating_system
         | 
| 270 | 
            -
                  attr_accessor :last_activity_time, :legal_consent, :user_agent, :cookie, :kcs_heat, :visitor_visits,
         | 
| 279 | 
            +
                  attr_accessor :last_activity_time, :legal_consent, :user_agent, :cookie, :kcs_heat, :cbscores, :visitor_visits,
         | 
| 271 280 | 
             
                                :mapping_identifier, :forced_variations, :simulated_variations
         | 
| 272 281 |  | 
| 273 282 | 
             
                  def initialize
         | 
| @@ -398,6 +407,10 @@ module Kameleoon | |
| 398 407 | 
             
                    @operating_system = operating_system if overwrite || @operating_system.nil?
         | 
| 399 408 | 
             
                  end
         | 
| 400 409 |  | 
| 410 | 
            +
                  def set_cbscores(cbs, overwrite)
         | 
| 411 | 
            +
                    @cbscores = cbs if overwrite || @cbscores.nil?
         | 
| 412 | 
            +
                  end
         | 
| 413 | 
            +
             | 
| 401 414 | 
             
                  def add_forced_feature_variation(forced_variation)
         | 
| 402 415 | 
             
                    @simulated_variations ||= {}
         | 
| 403 416 | 
             
                    @simulated_variations[forced_variation.feature_key] = forced_variation
         | 
| @@ -67,7 +67,7 @@ module Kameleoon | |
| 67 67 | 
             
                    config.environment,
         | 
| 68 68 | 
             
                    config.default_timeout_millisecond,
         | 
| 69 69 | 
             
                    Network::AccessTokenSourceFactory.new(config.client_id, config.client_secret),
         | 
| 70 | 
            -
                    Network::UrlProvider.new(site_code)
         | 
| 70 | 
            +
                    Network::UrlProvider.new(site_code, config.network_domain)
         | 
| 71 71 | 
             
                  )
         | 
| 72 72 | 
             
                  @tracking_manager = Managers::Tracking::TrackingManager.new(
         | 
| 73 73 | 
             
                    @data_manager, @network_manager, @visitor_manager, config.tracking_interval_second, @scheduler
         | 
| @@ -922,37 +922,62 @@ module Kameleoon | |
| 922 922 | 
             
                  forced_variation = visitor&.get_forced_feature_variation(feature_flag.feature_key)
         | 
| 923 923 | 
             
                  if forced_variation
         | 
| 924 924 | 
             
                    eval_exp = EvaluatedExperiment.from_forced_variation(forced_variation)
         | 
| 925 | 
            -
                  elsif visitor_not_in_holdout?(visitor, visitor_code, track, save)
         | 
| 925 | 
            +
                  elsif visitor_not_in_holdout?(visitor, visitor_code, track, save) && \
         | 
| 926 | 
            +
                        ff_unrestricted_by_me_group?(visitor, visitor_code, feature_flag)
         | 
| 926 927 | 
             
                    eval_exp = _calculate_variation_key_for_feature(visitor_code, feature_flag)
         | 
| 927 928 | 
             
                  end
         | 
| 928 929 | 
             
                  save_variation(visitor_code, eval_exp, track: track) if save && !forced_variation&.simulated
         | 
| 929 930 | 
             
                  Logging::KameleoonLogger.debug(
         | 
| 930 931 | 
             
                    "RETURN: KameleoonClient.evaluate(visitor, visitor_code: '%s', feature_flag: %s, track: %s, save: %s)" \
         | 
| 931 | 
            -
                    ' -> (eval_exp: %s)',
         | 
| 932 | 
            -
                    visitor_code, feature_flag, track, save, eval_exp
         | 
| 932 | 
            +
                    ' -> (eval_exp: %s)', visitor_code, feature_flag, track, save, eval_exp
         | 
| 933 933 | 
             
                  )
         | 
| 934 934 | 
             
                  eval_exp
         | 
| 935 935 | 
             
                end
         | 
| 936 936 |  | 
| 937 | 
            +
                def ff_unrestricted_by_me_group?(visitor, visitor_code, feature_flag)
         | 
| 938 | 
            +
                  return true if feature_flag.me_group_name.nil?
         | 
| 939 | 
            +
             | 
| 940 | 
            +
                  Logging::KameleoonLogger.debug(
         | 
| 941 | 
            +
                    "CALL: KameleoonClient.ff_unrestricted_by_me_group?(visitor, visitor_code: '%s', feature_flag: %s)",
         | 
| 942 | 
            +
                    visitor_code, feature_flag
         | 
| 943 | 
            +
                  )
         | 
| 944 | 
            +
                  unrestricted = true
         | 
| 945 | 
            +
                  me_group = @data_manager.data_file.me_groups[feature_flag.me_group_name]
         | 
| 946 | 
            +
                  if me_group
         | 
| 947 | 
            +
                    code_for_hash = get_code_for_hash(visitor, visitor_code)
         | 
| 948 | 
            +
                    me_group_hash = Utils::Hasher.obtain_hash_for_me_group(code_for_hash, feature_flag.me_group_name)
         | 
| 949 | 
            +
                    Logging::KameleoonLogger.debug(
         | 
| 950 | 
            +
                      "Calculated ME group hash %s for code: '%s', meGroup: '%s'",
         | 
| 951 | 
            +
                      me_group_hash, code_for_hash, feature_flag.me_group_name
         | 
| 952 | 
            +
                    )
         | 
| 953 | 
            +
                    unrestricted = me_group.get_feature_flag_by_hash(me_group_hash) == feature_flag
         | 
| 954 | 
            +
                  end
         | 
| 955 | 
            +
                  Logging::KameleoonLogger.debug(
         | 
| 956 | 
            +
                    "RETURN: KameleoonClient.ff_unrestricted_by_me_group?(visitor, visitor_code: '%s', feature_flag: %s)" \
         | 
| 957 | 
            +
                    ' -> (unrestricted: %s)', visitor_code, feature_flag, unrestricted
         | 
| 958 | 
            +
                  )
         | 
| 959 | 
            +
                  unrestricted
         | 
| 960 | 
            +
                end
         | 
| 961 | 
            +
             | 
| 937 962 | 
             
                def visitor_not_in_holdout?(visitor, visitor_code, track, save)
         | 
| 963 | 
            +
                  holdout = @data_manager.data_file.holdout
         | 
| 964 | 
            +
                  return true if holdout.nil?
         | 
| 965 | 
            +
             | 
| 938 966 | 
             
                  in_holdout_variation_key = 'in-holdout'
         | 
| 939 967 | 
             
                  Logging::KameleoonLogger.debug(
         | 
| 940 968 | 
             
                    "CALL: KameleoonClient.visitor_not_in_holdout?(visitor, visitor_code: '%s', track: %s, save: %s)",
         | 
| 941 969 | 
             
                    visitor_code, track, save
         | 
| 942 970 | 
             
                  )
         | 
| 943 | 
            -
                  holdout = @data_manager.data_file.holdout
         | 
| 944 971 | 
             
                  is_not_in_holdout = true
         | 
| 945 | 
            -
                   | 
| 946 | 
            -
             | 
| 947 | 
            -
             | 
| 948 | 
            -
             | 
| 949 | 
            -
             | 
| 950 | 
            -
                     | 
| 951 | 
            -
             | 
| 952 | 
            -
                       | 
| 953 | 
            -
             | 
| 954 | 
            -
                        save_variation(visitor_code, eval_exp, track: track)
         | 
| 955 | 
            -
                      end
         | 
| 972 | 
            +
                  code_for_hash = get_code_for_hash(visitor, visitor_code)
         | 
| 973 | 
            +
                  variation_hash = Utils::Hasher.obtain(code_for_hash, holdout.id)
         | 
| 974 | 
            +
                  Logging::KameleoonLogger.debug("Calculated holdout hash %s for code '%s'", variation_hash, code_for_hash)
         | 
| 975 | 
            +
                  var_by_exp = holdout.get_variation(variation_hash)
         | 
| 976 | 
            +
                  unless var_by_exp.nil?
         | 
| 977 | 
            +
                    is_not_in_holdout = var_by_exp.variation_key != in_holdout_variation_key
         | 
| 978 | 
            +
                    if save
         | 
| 979 | 
            +
                      eval_exp = EvaluatedExperiment.new(var_by_exp, holdout, Configuration::RuleType::EXPERIMENTATION)
         | 
| 980 | 
            +
                      save_variation(visitor_code, eval_exp, track: track)
         | 
| 956 981 | 
             
                    end
         | 
| 957 982 | 
             
                  end
         | 
| 958 983 | 
             
                  Logging::KameleoonLogger.debug(
         | 
| @@ -1007,6 +1032,42 @@ module Kameleoon | |
| 1007 1032 | 
             
                  visitor&.mapping_identifier || visitor_code
         | 
| 1008 1033 | 
             
                end
         | 
| 1009 1034 |  | 
| 1035 | 
            +
                def evaluate_cbscores(visitor, visitor_code, rule)
         | 
| 1036 | 
            +
                  return nil if visitor&.cbscores.nil?
         | 
| 1037 | 
            +
             | 
| 1038 | 
            +
                  Logging::KameleoonLogger.debug(
         | 
| 1039 | 
            +
                    "CALL: KameleoonClient.evaluate_cbscores(visitor, visitor_code: '%s', rule: %s)", visitor_code, rule
         | 
| 1040 | 
            +
                  )
         | 
| 1041 | 
            +
                  eval_exp = nil
         | 
| 1042 | 
            +
                  var_id_group_by_scores = visitor.cbscores.values[rule.experiment.id]
         | 
| 1043 | 
            +
                  if var_id_group_by_scores
         | 
| 1044 | 
            +
                    var_by_exp_in_cbs = nil
         | 
| 1045 | 
            +
                    var_id_group_by_scores.each do |var_group|
         | 
| 1046 | 
            +
                      var_by_exp_in_cbs = rule.experiment.variations_by_exposition.filter do |var_by_exp|
         | 
| 1047 | 
            +
                        var_group.ids.include?(var_by_exp.variation_id)
         | 
| 1048 | 
            +
                      end
         | 
| 1049 | 
            +
                      break unless var_by_exp_in_cbs.empty?
         | 
| 1050 | 
            +
                    end
         | 
| 1051 | 
            +
                    if (var_by_exp_in_cbs&.size || 0).positive?
         | 
| 1052 | 
            +
                      size = var_by_exp_in_cbs.size
         | 
| 1053 | 
            +
                      if size > 1
         | 
| 1054 | 
            +
                        code_for_hash = get_code_for_hash(visitor, visitor_code)
         | 
| 1055 | 
            +
                        variation_hash = Utils::Hasher.obtain(code_for_hash, rule.experiment.id, rule.respool_time)
         | 
| 1056 | 
            +
                        Logging::KameleoonLogger.debug("Calculated CBS hash %s for code '%s'", variation_hash, code_for_hash)
         | 
| 1057 | 
            +
                        idx = [(variation_hash * size).to_i, size - 1].min
         | 
| 1058 | 
            +
                      else
         | 
| 1059 | 
            +
                        idx = 0
         | 
| 1060 | 
            +
                      end
         | 
| 1061 | 
            +
                      eval_exp = EvaluatedExperiment.from_var_by_exp_rule(var_by_exp_in_cbs[idx], rule)
         | 
| 1062 | 
            +
                    end
         | 
| 1063 | 
            +
                  end
         | 
| 1064 | 
            +
                  Logging::KameleoonLogger.debug(
         | 
| 1065 | 
            +
                    "RETURN: KameleoonClient.evaluate_cbscores(visitor, visitor_code: '%s', rule: %s) -> (eval_exp: %s)",
         | 
| 1066 | 
            +
                    visitor_code, rule, eval_exp
         | 
| 1067 | 
            +
                  )
         | 
| 1068 | 
            +
                  eval_exp
         | 
| 1069 | 
            +
                end
         | 
| 1070 | 
            +
             | 
| 1010 1071 | 
             
                ##
         | 
| 1011 1072 | 
             
                # helper method for calculate variation key for feature flag
         | 
| 1012 1073 | 
             
                def _calculate_variation_key_for_feature(visitor_code, feature_flag)
         | 
| @@ -1017,12 +1078,12 @@ module Kameleoon | |
| 1017 1078 | 
             
                  visitor = @visitor_manager.get_visitor(visitor_code)
         | 
| 1018 1079 | 
             
                  code_for_hash = get_code_for_hash(visitor, visitor_code)
         | 
| 1019 1080 | 
             
                  # no rules -> return default_variation_key
         | 
| 1020 | 
            -
                   | 
| 1081 | 
            +
                  eval_exp = nil
         | 
| 1021 1082 | 
             
                  feature_flag.rules.each do |rule|
         | 
| 1022 1083 | 
             
                    forced_variation = visitor&.get_forced_experiment_variation(rule.experiment.id)
         | 
| 1023 1084 | 
             
                    if forced_variation&.force_targeting
         | 
| 1024 1085 | 
             
                      # Forcing experiment variation in force-targeting mode
         | 
| 1025 | 
            -
                       | 
| 1086 | 
            +
                      eval_exp = EvaluatedExperiment.from_var_by_exp_rule(forced_variation.var_by_exp, rule)
         | 
| 1026 1087 | 
             
                      break
         | 
| 1027 1088 | 
             
                    end
         | 
| 1028 1089 | 
             
                    # check if visitor is targeted for rule, else next rule
         | 
| @@ -1030,7 +1091,7 @@ module Kameleoon | |
| 1030 1091 |  | 
| 1031 1092 | 
             
                    unless forced_variation.nil?
         | 
| 1032 1093 | 
             
                      # Forcing experiment variation in targeting-only mode
         | 
| 1033 | 
            -
                       | 
| 1094 | 
            +
                      eval_exp = EvaluatedExperiment.from_var_by_exp_rule(forced_variation.var_by_exp, rule)
         | 
| 1034 1095 | 
             
                      break
         | 
| 1035 1096 | 
             
                    end
         | 
| 1036 1097 | 
             
                    # uses for rule exposition
         | 
| @@ -1038,8 +1099,11 @@ module Kameleoon | |
| 1038 1099 | 
             
                    Logging::KameleoonLogger.debug("Calculated rule hash %s for code '%s'", hash_rule, code_for_hash)
         | 
| 1039 1100 | 
             
                    # check main expostion for rule with hashRule
         | 
| 1040 1101 | 
             
                    if hash_rule <= rule.exposition
         | 
| 1102 | 
            +
                      eval_exp = evaluate_cbscores(visitor, visitor_code, rule)
         | 
| 1103 | 
            +
                      break unless eval_exp.nil?
         | 
| 1104 | 
            +
             | 
| 1041 1105 | 
             
                      if rule.targeted_delivery_type?
         | 
| 1042 | 
            -
                         | 
| 1106 | 
            +
                        eval_exp = EvaluatedExperiment.from_var_by_exp_rule(rule.experiment.variations_by_exposition.first, rule)
         | 
| 1043 1107 | 
             
                        break
         | 
| 1044 1108 | 
             
                      end
         | 
| 1045 1109 | 
             
                      # uses for variation's expositions
         | 
| @@ -1048,7 +1112,7 @@ module Kameleoon | |
| 1048 1112 | 
             
                      # get variation key with new hashVariation
         | 
| 1049 1113 | 
             
                      variation = rule.experiment.get_variation(hash_variation)
         | 
| 1050 1114 | 
             
                      unless variation.nil?
         | 
| 1051 | 
            -
                         | 
| 1115 | 
            +
                        eval_exp = EvaluatedExperiment.from_var_by_exp_rule(variation, rule)
         | 
| 1052 1116 | 
             
                        break
         | 
| 1053 1117 | 
             
                      end
         | 
| 1054 1118 | 
             
                    # if visitor is targeted for targeted rule then break cycle -> return default
         | 
| @@ -1058,9 +1122,9 @@ module Kameleoon | |
| 1058 1122 | 
             
                  end
         | 
| 1059 1123 | 
             
                  Logging::KameleoonLogger.debug(
         | 
| 1060 1124 | 
             
                    "RETURN: KameleoonClient._calculate_variation_key_for_feature(visitor_code: '%s', feature_flag: %s) " \
         | 
| 1061 | 
            -
                      '-> (eval_exp: %s)', visitor_code, feature_flag,  | 
| 1125 | 
            +
                      '-> (eval_exp: %s)', visitor_code, feature_flag, eval_exp
         | 
| 1062 1126 | 
             
                  )
         | 
| 1063 | 
            -
                   | 
| 1127 | 
            +
                  eval_exp
         | 
| 1064 1128 | 
             
                end
         | 
| 1065 1129 |  | 
| 1066 1130 | 
             
                def calculate_variation_key(eval_exp, default_value)
         | 
| @@ -14,7 +14,7 @@ module Kameleoon | |
| 14 14 |  | 
| 15 15 | 
             
                attr_reader :client_id, :client_secret, :refresh_interval_second, :session_duration_second,
         | 
| 16 16 | 
             
                            :default_timeout_millisecond, :tracking_interval_second, :environment, :top_level_domain,
         | 
| 17 | 
            -
                            :verbose_mode
         | 
| 17 | 
            +
                            :verbose_mode, :network_domain
         | 
| 18 18 |  | 
| 19 19 | 
             
                def to_s
         | 
| 20 20 | 
             
                  'KameleoonClientConfig{' \
         | 
| @@ -25,7 +25,8 @@ module Kameleoon | |
| 25 25 | 
             
                    "environment:'#{@environment}'," \
         | 
| 26 26 | 
             
                    "default_timeout_millisecond:#{@default_timeout_millisecond}," \
         | 
| 27 27 | 
             
                    "top_level_domain:'#{@top_level_domain}'," \
         | 
| 28 | 
            -
                    "verbose_mode:#{verbose_mode}" \
         | 
| 28 | 
            +
                    "verbose_mode:#{verbose_mode}," \
         | 
| 29 | 
            +
                    "network_domain:'#{@network_domain}'" \
         | 
| 29 30 | 
             
                    '}'
         | 
| 30 31 | 
             
                end
         | 
| 31 32 |  | 
| @@ -39,7 +40,8 @@ module Kameleoon | |
| 39 40 | 
             
                  tracking_interval_millisecond: DEFAULT_TRACKING_INTERVAL_MILLISECONDS,
         | 
| 40 41 | 
             
                  environment: nil,
         | 
| 41 42 | 
             
                  top_level_domain: nil,
         | 
| 42 | 
            -
                  verbose_mode: nil
         | 
| 43 | 
            +
                  verbose_mode: nil,
         | 
| 44 | 
            +
                  network_domain: nil
         | 
| 43 45 | 
             
                )
         | 
| 44 46 | 
             
                  raise Exception::ConfigCredentialsInvalid, 'Client ID is not specified' if client_id&.empty? != false
         | 
| 45 47 | 
             
                  raise Exception::ConfigCredentialsInvalid, 'Client secret is not specified' if client_secret&.empty? != false
         | 
| @@ -114,6 +116,7 @@ module Kameleoon | |
| 114 116 | 
             
                    )
         | 
| 115 117 | 
             
                  end
         | 
| 116 118 | 
             
                  @top_level_domain = Utils::Domain.validate_top_level_domain(top_level_domain)
         | 
| 119 | 
            +
                  @network_domain = Utils::Domain.validate_network_domain(network_domain)
         | 
| 117 120 | 
             
                end
         | 
| 118 121 |  | 
| 119 122 | 
             
                def self.read_from_yaml(path)
         | 
| @@ -131,7 +134,8 @@ module Kameleoon | |
| 131 134 | 
             
                    tracking_interval_millisecond: yaml['tracking_interval_millisecond'],
         | 
| 132 135 | 
             
                    environment: yaml['environment'],
         | 
| 133 136 | 
             
                    top_level_domain: yaml['top_level_domain'],
         | 
| 134 | 
            -
                    verbose_mode: yaml['verbose_mode']
         | 
| 137 | 
            +
                    verbose_mode: yaml['verbose_mode'],
         | 
| 138 | 
            +
                    network_domain: yaml['network_domain']
         | 
| 135 139 | 
             
                  )
         | 
| 136 140 | 
             
                end
         | 
| 137 141 | 
             
              end
         | 
| @@ -1,22 +1,23 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 | 
            -
            require 'kameleoon/data/manager/page_view_visit'
         | 
| 4 | 
            -
            require 'kameleoon/data/manager/assigned_variation'
         | 
| 5 | 
            -
            require 'kameleoon/data/visitor_visits'
         | 
| 6 3 | 
             
            require 'kameleoon/data/browser'
         | 
| 7 | 
            -
            require 'kameleoon/data/ | 
| 4 | 
            +
            require 'kameleoon/data/cbscores'
         | 
| 8 5 | 
             
            require 'kameleoon/data/conversion'
         | 
| 9 6 | 
             
            require 'kameleoon/data/custom_data'
         | 
| 7 | 
            +
            require 'kameleoon/data/device'
         | 
| 10 8 | 
             
            require 'kameleoon/data/geolocation'
         | 
| 11 9 | 
             
            require 'kameleoon/data/kcs_heat'
         | 
| 12 10 | 
             
            require 'kameleoon/data/page_view'
         | 
| 11 | 
            +
            require 'kameleoon/data/visitor_visits'
         | 
| 12 | 
            +
            require 'kameleoon/data/manager/page_view_visit'
         | 
| 13 | 
            +
            require 'kameleoon/data/manager/assigned_variation'
         | 
| 13 14 |  | 
| 14 15 | 
             
            module Kameleoon
         | 
| 15 16 | 
             
              module Managers
         | 
| 16 17 | 
             
                module RemoteData
         | 
| 17 18 | 
             
                  class RemoteVisitorData
         | 
| 18 19 | 
             
                    attr_reader :custom_data_dict, :page_view_visits, :conversions, :experiments, :device, :browser,
         | 
| 19 | 
            -
                                :operating_system, :geolocation, :previous_visitor_visits, :kcs_heat, :visitor_code
         | 
| 20 | 
            +
                                :operating_system, :geolocation, :previous_visitor_visits, :kcs_heat, :cbs, :visitor_code
         | 
| 20 21 |  | 
| 21 22 | 
             
                    def initialize(hash)
         | 
| 22 23 | 
             
                      current_visit = hash['currentVisit']
         | 
| @@ -33,6 +34,7 @@ module Kameleoon | |
| 33 34 | 
             
                        @previous_visitor_visits = VisitorVisits.new(times_started)
         | 
| 34 35 | 
             
                      end
         | 
| 35 36 | 
             
                      @kcs_heat = parse_kcs_heat(hash['kcs'])
         | 
| 37 | 
            +
                      @cbs = parse_cbscores(hash['cbs'])
         | 
| 36 38 | 
             
                    end
         | 
| 37 39 |  | 
| 38 40 | 
             
                    def collect_data_to_add
         | 
| @@ -40,6 +42,7 @@ module Kameleoon | |
| 40 42 | 
             
                      data_to_add.concat(@custom_data_dict.values) unless @custom_data_dict.nil?
         | 
| 41 43 | 
             
                      data_to_add.push(@previous_visitor_visits) unless @previous_visitor_visits.nil?
         | 
| 42 44 | 
             
                      data_to_add.push(@kcs_heat) unless @kcs_heat.nil?
         | 
| 45 | 
            +
                      data_to_add.push(@cbs) unless @cbs.nil?
         | 
| 43 46 | 
             
                      data_to_add.concat(@page_view_visits.values) unless @page_view_visits.nil?
         | 
| 44 47 | 
             
                      data_to_add.concat(@experiments.values) unless @experiments.nil?
         | 
| 45 48 | 
             
                      data_to_add.concat(conversions_single_objects)
         | 
| @@ -184,6 +187,26 @@ module Kameleoon | |
| 184 187 | 
             
                      end
         | 
| 185 188 | 
             
                      KcsHeat.new(value_map)
         | 
| 186 189 | 
             
                    end
         | 
| 190 | 
            +
             | 
| 191 | 
            +
                    def parse_cbscores(cbs)
         | 
| 192 | 
            +
                      return nil if cbs.nil?
         | 
| 193 | 
            +
             | 
| 194 | 
            +
                      cbs_map = {}
         | 
| 195 | 
            +
                      cbs.each do |str_exp_id, scored_var_entries|
         | 
| 196 | 
            +
                        next unless str_exp_id.is_a?(String) && scored_var_entries.is_a?(Hash)
         | 
| 197 | 
            +
             | 
| 198 | 
            +
                        entries = []
         | 
| 199 | 
            +
                        scored_var_entries.each do |str_var_id, score|
         | 
| 200 | 
            +
                          next unless str_var_id.is_a?(String) && (score.is_a?(Float) || score.is_a?(Integer))
         | 
| 201 | 
            +
             | 
| 202 | 
            +
                          var_id = str_var_id.to_i
         | 
| 203 | 
            +
                          entries.push(CBScores::ScoredVarId.new(var_id, score))
         | 
| 204 | 
            +
                        end
         | 
| 205 | 
            +
                        exp_id = str_exp_id.to_i
         | 
| 206 | 
            +
                        cbs_map[exp_id] = entries
         | 
| 207 | 
            +
                      end
         | 
| 208 | 
            +
                      CBScores.new(cbs_map)
         | 
| 209 | 
            +
                    end
         | 
| 187 210 | 
             
                  end
         | 
| 188 211 | 
             
                end
         | 
| 189 212 | 
             
              end
         | 
| @@ -88,13 +88,13 @@ module Kameleoon | |
| 88 88 | 
             
                      if response.success?
         | 
| 89 89 | 
             
                        success = true
         | 
| 90 90 | 
             
                      elsif !response.error.nil?
         | 
| 91 | 
            -
                        log_level = attempt  | 
| 91 | 
            +
                        log_level = get_log_level(attempt, retry_limit)
         | 
| 92 92 | 
             
                        Logging::KameleoonLogger.log(log_level, "%s call '%s' failed: Error occurred during request: %s",
         | 
| 93 93 | 
             
                                                     request.method, request.url, response.error)
         | 
| 94 94 | 
             
                      else
         | 
| 95 | 
            -
                        log_level = attempt  | 
| 96 | 
            -
                        Logging::KameleoonLogger.log(log_level, "%s call '%s' failed: Received unexpected status code  | 
| 97 | 
            -
                                                     request.method, request.url, response.code)
         | 
| 95 | 
            +
                        log_level = get_log_level(attempt, retry_limit)
         | 
| 96 | 
            +
                        Logging::KameleoonLogger.log(log_level, "%s call '%s' failed: Received unexpected status code: %s, body: %s",
         | 
| 97 | 
            +
                                                     request.method, request.url, response.code, response.body)
         | 
| 98 98 | 
             
                        if response.code == 401 && request.access_token
         | 
| 99 99 | 
             
                          Logging::KameleoonLogger.warning("Unexpected rejection of access token '#{request.access_token}'")
         | 
| 100 100 | 
             
                          @access_token_source.discard_token(request.access_token)
         | 
| @@ -113,6 +113,10 @@ module Kameleoon | |
| 113 113 | 
             
                    success ? response.body : false
         | 
| 114 114 | 
             
                  end
         | 
| 115 115 |  | 
| 116 | 
            +
                  def get_log_level(attempt, attempt_count)
         | 
| 117 | 
            +
                    return log_level = attempt < attempt_count ? Logging::LogLevel::WARNING : Logging::LogLevel::ERROR
         | 
| 118 | 
            +
                  end
         | 
| 119 | 
            +
             | 
| 116 120 | 
             
                  def delay(period)
         | 
| 117 121 | 
             
                    sleep(period)
         | 
| 118 122 | 
             
                  end
         | 
| @@ -14,27 +14,43 @@ module Kameleoon | |
| 14 14 | 
             
                  POST_DATA_PATH = '/map/maps'
         | 
| 15 15 | 
             
                  ACCESS_TOKEN_PATH = '/oauth/token'
         | 
| 16 16 |  | 
| 17 | 
            -
                   | 
| 18 | 
            -
                   | 
| 17 | 
            +
                  DEFAULT_EVENTS_DOMAIN = 'events.kameleoon.eu'
         | 
| 18 | 
            +
                  DEFAULT_CONFIGURATION_DOMAIN = 'sdk-config.kameleoon.eu'
         | 
| 19 | 
            +
                  DEFAULT_ACCESS_TOKEN_DOMAIN = 'api.kameleoon.com'
         | 
| 19 20 | 
             
                  DEFAULT_DATA_API_DOMAIN = 'data.kameleoon.io'
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                  CONFIGURATION_API_URL_FORMAT = 'https://%s/%s'
         | 
| 23 | 
            +
                  DATA_API_URL_FORMAT = 'https://%s%s?%s'
         | 
| 24 | 
            +
                  RT_CONFIGURATION_URL_FORMAT = 'https://%s:8110/sse?%s'
         | 
| 25 | 
            +
                  ACCESS_TOKEN_URL_FORMAT = 'https://%s/oauth/token'
         | 
| 26 | 
            +
             | 
| 20 27 | 
             
                  TEST_DATA_API_DOMAIN = 'data.kameleoon.net'
         | 
| 21 | 
            -
                  DEFAULT_AUTOMATION_API_DOMAIN = 'api.kameleoon.com'
         | 
| 22 28 | 
             
                  TEST_AUTOMATION_API_DOMAIN = 'api.kameleoon.net'
         | 
| 23 29 |  | 
| 24 | 
            -
                  attr_reader :site_code, :data_api_domain, : | 
| 30 | 
            +
                  attr_reader :site_code, :data_api_domain, :events_domain, :configuration_domain, :access_token_domain
         | 
| 25 31 |  | 
| 26 32 | 
             
                  def initialize(
         | 
| 27 33 | 
             
                    site_code,
         | 
| 28 | 
            -
                     | 
| 29 | 
            -
                    automation_api_domain = DEFAULT_AUTOMATION_API_DOMAIN
         | 
| 34 | 
            +
                    network_domain = nil
         | 
| 30 35 | 
             
                  )
         | 
| 31 36 | 
             
                    @site_code = site_code
         | 
| 32 | 
            -
                    @data_api_domain =  | 
| 33 | 
            -
                    @ | 
| 37 | 
            +
                    @data_api_domain = DEFAULT_DATA_API_DOMAIN
         | 
| 38 | 
            +
                    @events_domain = DEFAULT_EVENTS_DOMAIN
         | 
| 39 | 
            +
                    @configuration_domain = DEFAULT_CONFIGURATION_DOMAIN
         | 
| 40 | 
            +
                    @access_token_domain = DEFAULT_ACCESS_TOKEN_DOMAIN
         | 
| 41 | 
            +
                    @is_custom_domain = false
         | 
| 42 | 
            +
                    update_domains(network_domain)
         | 
| 34 43 | 
             
                  end
         | 
| 35 44 |  | 
| 36 45 | 
             
                  def apply_data_api_domain(domain)
         | 
| 37 | 
            -
                     | 
| 46 | 
            +
                    return if domain.nil? || domain.empty?
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                    if @is_custom_domain
         | 
| 49 | 
            +
                      sub_domain = domain.split('.').first
         | 
| 50 | 
            +
                      @data_api_domain = @data_api_domain.sub(/^[^.]+/, sub_domain)
         | 
| 51 | 
            +
                    else
         | 
| 52 | 
            +
                      @data_api_domain = domain
         | 
| 53 | 
            +
                    end
         | 
| 38 54 | 
             
                  end
         | 
| 39 55 |  | 
| 40 56 | 
             
                  def make_tracking_url
         | 
| @@ -44,7 +60,7 @@ module Kameleoon | |
| 44 60 | 
             
                      siteCode: @site_code,
         | 
| 45 61 | 
             
                      bodyUa: true
         | 
| 46 62 | 
             
                    }
         | 
| 47 | 
            -
                     | 
| 63 | 
            +
                    format(DATA_API_URL_FORMAT, @data_api_domain, TRACKING_PATH, UriHelper.encode_query(params))
         | 
| 48 64 | 
             
                  end
         | 
| 49 65 |  | 
| 50 66 | 
             
                  def make_visitor_data_get_url(visitor_code, filter, is_unique_identifier = false)
         | 
| @@ -60,7 +76,8 @@ module Kameleoon | |
| 60 76 | 
             
                    params[:experiment] = true if filter.experiments
         | 
| 61 77 | 
             
                    params[:page] = true if filter.page_views
         | 
| 62 78 | 
             
                    params[:staticData] = true if filter.device || filter.browser || filter.operating_system
         | 
| 63 | 
            -
                     | 
| 79 | 
            +
                    params[:cbs] = true if filter.cbs
         | 
| 80 | 
            +
                    format(DATA_API_URL_FORMAT, @data_api_domain, VISITOR_DATA_PATH, UriHelper.encode_query(params))
         | 
| 64 81 | 
             
                  end
         | 
| 65 82 |  | 
| 66 83 | 
             
                  def make_api_data_get_request_url(key)
         | 
| @@ -68,11 +85,11 @@ module Kameleoon | |
| 68 85 | 
             
                      siteCode: @site_code,
         | 
| 69 86 | 
             
                      key: key
         | 
| 70 87 | 
             
                    }
         | 
| 71 | 
            -
                     | 
| 88 | 
            +
                    format(DATA_API_URL_FORMAT, @data_api_domain, GET_DATA_PATH, UriHelper.encode_query(params))
         | 
| 72 89 | 
             
                  end
         | 
| 73 90 |  | 
| 74 91 | 
             
                  def make_configuration_url(environment = nil, timestamp = nil)
         | 
| 75 | 
            -
                    url = format(CONFIGURATION_API_URL_FORMAT, @site_code)
         | 
| 92 | 
            +
                    url = format(CONFIGURATION_API_URL_FORMAT, @configuration_domain, @site_code)
         | 
| 76 93 | 
             
                    params = {}
         | 
| 77 94 | 
             
                    params[:environment] = environment unless environment.nil?
         | 
| 78 95 | 
             
                    params[:ts] = timestamp unless timestamp.nil?
         | 
| @@ -82,11 +99,24 @@ module Kameleoon | |
| 82 99 |  | 
| 83 100 | 
             
                  def make_real_time_url
         | 
| 84 101 | 
             
                    params = { siteCode: @site_code }
         | 
| 85 | 
            -
                     | 
| 102 | 
            +
                    format(RT_CONFIGURATION_URL_FORMAT, @events_domain, UriHelper.encode_query(params))
         | 
| 86 103 | 
             
                  end
         | 
| 87 104 |  | 
| 88 105 | 
             
                  def make_access_token_url
         | 
| 89 | 
            -
                     | 
| 106 | 
            +
                    format(ACCESS_TOKEN_URL_FORMAT, @access_token_domain)
         | 
| 107 | 
            +
                  end
         | 
| 108 | 
            +
             | 
| 109 | 
            +
                  private
         | 
| 110 | 
            +
             | 
| 111 | 
            +
                  def update_domains(network_domain)
         | 
| 112 | 
            +
                    return if network_domain.nil? || network_domain.empty?
         | 
| 113 | 
            +
             | 
| 114 | 
            +
                    @is_custom_domain = true
         | 
| 115 | 
            +
             | 
| 116 | 
            +
                    @data_api_domain = "data.#{network_domain}"
         | 
| 117 | 
            +
                    @events_domain = "events.#{network_domain}"
         | 
| 118 | 
            +
                    @configuration_domain = "sdk-config.#{network_domain}"
         | 
| 119 | 
            +
                    @access_token_domain = "api.#{network_domain}"
         | 
| 90 120 | 
             
                  end
         | 
| 91 121 | 
             
                end
         | 
| 92 122 | 
             
              end
         | 
| @@ -5,7 +5,7 @@ module Kameleoon | |
| 5 5 | 
             
              module Types
         | 
| 6 6 | 
             
                class RemoteVisitorDataFilter
         | 
| 7 7 | 
             
                  attr_reader :previous_visit_amount, :current_visit, :custom_data, :page_views, :geolocation, :device, :browser,
         | 
| 8 | 
            -
                              :operating_system, :conversions, :experiments, :kcs, :visitor_code
         | 
| 8 | 
            +
                              :operating_system, :conversions, :experiments, :kcs, :visitor_code, :cbs
         | 
| 9 9 |  | 
| 10 10 | 
             
                  def to_s
         | 
| 11 11 | 
             
                    "RemoteVisitorDataFilter{previous_visit_amount:#{@previous_visit_amount}," \
         | 
| @@ -19,13 +19,14 @@ module Kameleoon | |
| 19 19 | 
             
                      "conversions:#{@conversions}," \
         | 
| 20 20 | 
             
                      "experiments:#{@experiments}," \
         | 
| 21 21 | 
             
                      "kcs:#{@kcs}," \
         | 
| 22 | 
            -
                      "visitor_code:#{@visitor_code}}"
         | 
| 22 | 
            +
                      "visitor_code:#{@visitor_code}}" \
         | 
| 23 | 
            +
                      "cbs:#{@cbs}}"
         | 
| 23 24 | 
             
                  end
         | 
| 24 25 |  | 
| 25 26 | 
             
                  def initialize(
         | 
| 26 27 | 
             
                    previous_visit_amount: 1, current_visit: true, custom_data: true, page_views: false,
         | 
| 27 28 | 
             
                    geolocation: false, device: false, browser: false, operating_system: false, conversions: false,
         | 
| 28 | 
            -
                    experiments: false, kcs: false, visitor_code: true
         | 
| 29 | 
            +
                    experiments: false, kcs: false, visitor_code: true, cbs: false
         | 
| 29 30 | 
             
                  )
         | 
| 30 31 | 
             
                    @previous_visit_amount = previous_visit_amount
         | 
| 31 32 | 
             
                    @current_visit = current_visit
         | 
| @@ -39,6 +40,7 @@ module Kameleoon | |
| 39 40 | 
             
                    @experiments = experiments
         | 
| 40 41 | 
             
                    @kcs = kcs
         | 
| 41 42 | 
             
                    @visitor_code = visitor_code
         | 
| 43 | 
            +
                    @cbs = cbs
         | 
| 42 44 | 
             
                  end
         | 
| 43 45 | 
             
                end
         | 
| 44 46 | 
             
              end
         | 
    
        data/lib/kameleoon/utils.rb
    CHANGED
    
    | @@ -38,6 +38,10 @@ module Kameleoon | |
| 38 38 | 
             
                    calculate("#{visitor_code}#{container_id}#{suffix}")
         | 
| 39 39 | 
             
                  end
         | 
| 40 40 |  | 
| 41 | 
            +
                  def self.obtain_hash_for_me_group(visitor_code, me_group_name)
         | 
| 42 | 
            +
                    calculate("#{visitor_code}#{me_group_name}")
         | 
| 43 | 
            +
                  end
         | 
| 44 | 
            +
             | 
| 41 45 | 
             
                  def self.calculate(string_to_hash)
         | 
| 42 46 | 
             
                    parsed_value = Digest::SHA256.hexdigest(string_to_hash.encode('UTF-8')).to_i(16)
         | 
| 43 47 | 
             
                    max_value = BigDecimal('2')**BigDecimal('256')
         | 
| @@ -73,17 +77,7 @@ module Kameleoon | |
| 73 77 | 
             
                  def self.validate_top_level_domain(top_level_domain)
         | 
| 74 78 | 
             
                    return nil if top_level_domain.nil? || top_level_domain.empty?
         | 
| 75 79 |  | 
| 76 | 
            -
                    top_level_domain = top_level_domain.downcase
         | 
| 77 | 
            -
             | 
| 78 | 
            -
                    [HTTP, HTTPS].each do |protocol|
         | 
| 79 | 
            -
                      next unless top_level_domain.start_with?(protocol)
         | 
| 80 | 
            -
             | 
| 81 | 
            -
                      top_level_domain = top_level_domain[protocol.length..]
         | 
| 82 | 
            -
                      Logging::KameleoonLogger.warning(
         | 
| 83 | 
            -
                        "The top-level domain contains '%s'. Domain after protocol trimming: '%s'", protocol, top_level_domain
         | 
| 84 | 
            -
                      )
         | 
| 85 | 
            -
                      break
         | 
| 86 | 
            -
                    end
         | 
| 80 | 
            +
                    top_level_domain = check_and_trim_protocol(top_level_domain.downcase)
         | 
| 87 81 |  | 
| 88 82 | 
             
                    if !REGEX_DOMAIN.match?(top_level_domain) && top_level_domain != LOCALHOST
         | 
| 89 83 | 
             
                      Logging::KameleoonLogger.error(
         | 
| @@ -96,6 +90,41 @@ module Kameleoon | |
| 96 90 |  | 
| 97 91 | 
             
                    top_level_domain
         | 
| 98 92 | 
             
                  end
         | 
| 93 | 
            +
             | 
| 94 | 
            +
                  def self.validate_network_domain(network_domain)
         | 
| 95 | 
            +
                    return nil if network_domain.nil? || network_domain.empty?
         | 
| 96 | 
            +
             | 
| 97 | 
            +
                    network_domain = check_and_trim_protocol(network_domain.downcase)
         | 
| 98 | 
            +
             | 
| 99 | 
            +
                    # remove first and last dot
         | 
| 100 | 
            +
                    network_domain = network_domain.gsub(/^\.+|\.+$/, '')
         | 
| 101 | 
            +
             | 
| 102 | 
            +
                    if !REGEX_DOMAIN.match?(network_domain) && network_domain != LOCALHOST
         | 
| 103 | 
            +
                      Logging::KameleoonLogger.error(
         | 
| 104 | 
            +
                        "The network domain '%s' is invalid.",
         | 
| 105 | 
            +
                        network_domain
         | 
| 106 | 
            +
                      )
         | 
| 107 | 
            +
                      return nil
         | 
| 108 | 
            +
                    end
         | 
| 109 | 
            +
             | 
| 110 | 
            +
                    network_domain
         | 
| 111 | 
            +
                  end
         | 
| 112 | 
            +
             | 
| 113 | 
            +
                  private
         | 
| 114 | 
            +
             | 
| 115 | 
            +
                  def self.check_and_trim_protocol(domain)
         | 
| 116 | 
            +
                    [HTTP, HTTPS].each do |protocol|
         | 
| 117 | 
            +
                      next unless domain.start_with?(protocol)
         | 
| 118 | 
            +
             | 
| 119 | 
            +
                      domain = domain[protocol.length..]
         | 
| 120 | 
            +
                      Logging::KameleoonLogger.warning(
         | 
| 121 | 
            +
                        "The domain contains '%s'. Domain after protocol trimming: '%s'", protocol, domain
         | 
| 122 | 
            +
                      )
         | 
| 123 | 
            +
                      break
         | 
| 124 | 
            +
                    end
         | 
| 125 | 
            +
             | 
| 126 | 
            +
                    domain
         | 
| 127 | 
            +
                  end
         | 
| 99 128 | 
             
                end
         | 
| 100 129 | 
             
              end
         | 
| 101 130 | 
             
            end
         | 
    
        data/lib/kameleoon/version.rb
    CHANGED
    
    
    
        metadata
    CHANGED
    
    | @@ -1,14 +1,14 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: kameleoon-client-ruby
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 3. | 
| 4 | 
            +
              version: 3.10.0
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Kameleoon
         | 
| 8 8 | 
             
            autorequire: 
         | 
| 9 9 | 
             
            bindir: bin
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date: 2025- | 
| 11 | 
            +
            date: 2025-03-18 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 14 | 
             
              name: em-http-request
         | 
| @@ -80,12 +80,14 @@ files: | |
| 80 80 | 
             
            - lib/kameleoon/configuration/data_file.rb
         | 
| 81 81 | 
             
            - lib/kameleoon/configuration/experiment.rb
         | 
| 82 82 | 
             
            - lib/kameleoon/configuration/feature_flag.rb
         | 
| 83 | 
            +
            - lib/kameleoon/configuration/me_group.rb
         | 
| 83 84 | 
             
            - lib/kameleoon/configuration/rule.rb
         | 
| 84 85 | 
             
            - lib/kameleoon/configuration/settings.rb
         | 
| 85 86 | 
             
            - lib/kameleoon/configuration/variable.rb
         | 
| 86 87 | 
             
            - lib/kameleoon/configuration/variation.rb
         | 
| 87 88 | 
             
            - lib/kameleoon/configuration/variation_exposition.rb
         | 
| 88 89 | 
             
            - lib/kameleoon/data/browser.rb
         | 
| 90 | 
            +
            - lib/kameleoon/data/cbscores.rb
         | 
| 89 91 | 
             
            - lib/kameleoon/data/conversion.rb
         | 
| 90 92 | 
             
            - lib/kameleoon/data/cookie.rb
         | 
| 91 93 | 
             
            - lib/kameleoon/data/custom_data.rb
         |