kameleoon-client-ruby 3.12.0 → 3.13.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 40923e6f42531b0686515b2ed716e62c1fb99aaeb8a739014bb60b75d661b06d
4
- data.tar.gz: 7bcc9f40e5ffce5701d0046250e852ec7330638d2d3ee48ea838b7842054f280
3
+ metadata.gz: 05500f77891f22fd67b10f0dec830e6673a77d8ff83b391ffbf8547893bfcf1e
4
+ data.tar.gz: 4c572f1464af6e5026642e4fac03dcc1e1a99715228c76982c67de5063c2235e
5
5
  SHA512:
6
- metadata.gz: 760b08574a3dc5d79f6e8f241ec14bae4e31447ec8b39fe047d089edf5d64de2c95de43ed14dc995f32a68a806fb08ff8417f212e570ba5ac905b67908451a5b
7
- data.tar.gz: 6d906512cc30caa25506b0b2a26faaaa9c7ad545663b05c155c2a040098448c9e7e3f67a8226feea905e1d70c6d0bbe15bc013341bcdbac0b99cba12bb316606
6
+ metadata.gz: b7ac1acb43bf56661f194dacc810b064e2c4cba67010b1af49d928c190ae53c139ee0447fb7e5f5670ae1fb0c406a0efb7e2c7fe0eedac323635dc0e215de869
7
+ data.tar.gz: 58278744be7aaef43a0e0dcbe71bf93ee9ab9c1aec3a0b6e6f296e940f24ce943ee7c7998a550ecf1950cbcb3f36cb78858feacea6289a2b8ce1c3439b4b037e
@@ -10,28 +10,36 @@ require 'kameleoon/logging/kameleoon_logger'
10
10
  module Kameleoon
11
11
  module Configuration
12
12
  class DataFile
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,
13
+ attr_reader :last_modified, :settings, :feature_flags, :me_groups, :has_any_targeted_delivery_rule,
14
+ :feature_flag_by_id, :rule_by_segment_id, :rule_info_by_exp_id, :variation_by_id, :custom_data_info,
15
15
  :experiment_ids_with_js_css_variable, :holdout
16
16
 
17
17
  def to_s
18
18
  'DataFile{' \
19
19
  "environment:#{@environment}," \
20
+ "last_modified:#{@last_modified}," \
20
21
  "feature_flags:#{@feature_flags.size}," \
21
22
  "settings:#{@settings}" \
22
23
  '}'
23
24
  end
24
25
 
25
- def initialize(environment, configuration = nil)
26
- Logging::KameleoonLogger.debug('CALL: DataFile.new(environment: %s)', environment)
26
+ def initialize(environment, configuration = nil, last_modified = nil)
27
+ Logging::KameleoonLogger.debug(
28
+ "CALL: DataFile.new(environment: '%s', configuration, last_modified: '%s')",
29
+ environment, last_modified
30
+ )
27
31
  @environment = environment
32
+ @last_modified = last_modified
28
33
  if configuration.nil?
29
34
  init_default
30
35
  else
31
36
  init(configuration)
32
37
  end
33
38
  collect_indices
34
- Logging::KameleoonLogger.debug('RETURN: DataFile.new(environment: %s)', environment)
39
+ Logging::KameleoonLogger.debug(
40
+ "RETURN: DataFile.new(environment: '%s', configuration, last_modified: '%s')",
41
+ environment, last_modified
42
+ )
35
43
  end
36
44
 
37
45
  def get_feature_flag(feature_key)
@@ -18,6 +18,7 @@ module Kameleoon
18
18
  FORCED_FEATURE_VARIATION = 'FORCED_FEATURE_VARIATION'
19
19
  OPERATING_SYSTEM = 'OPERATING_SYSTEM'
20
20
  GEOLOCATION = 'GEOLOCATION'
21
+ VISITOR_VISITS = 'VISITOR_VISITS'
21
22
  end
22
23
 
23
24
  module DataState
@@ -30,7 +30,7 @@ module Kameleoon
30
30
  deviceType: @device_type,
31
31
  nonce: nonce
32
32
  }
33
- Kameleoon::Network::UriHelper.encode_query(params)
33
+ Network::UriHelper.encode_query(params)
34
34
  end
35
35
  end
36
36
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'concurrent'
4
+ require 'time'
4
5
  require 'kameleoon/data/browser'
5
6
  require 'kameleoon/data/cbscores'
6
7
  require 'kameleoon/data/conversion'
@@ -42,6 +43,10 @@ module Kameleoon
42
43
  update_last_activity_time
43
44
  end
44
45
 
46
+ def time_started
47
+ @data.time_started
48
+ end
49
+
45
50
  def last_activity_time
46
51
  @data.last_activity_time
47
52
  end
@@ -263,7 +268,7 @@ module Kameleoon
263
268
  when CBScores
264
269
  @data.set_cbscores(data, overwrite)
265
270
  when VisitorVisits
266
- @data.visitor_visits = data
271
+ @data.set_visitor_visits(data, overwrite)
267
272
  when UniqueIdentifier
268
273
  @is_unique_identifier = data.value
269
274
  else
@@ -286,12 +291,13 @@ module Kameleoon
286
291
  end
287
292
 
288
293
  class VisitorData
289
- attr_reader :mutex, :device, :browser, :geolocation, :operating_system
290
- attr_accessor :last_activity_time, :legal_consent, :user_agent, :cookie, :kcs_heat, :cbscores, :visitor_visits,
294
+ attr_reader :time_started, :mutex, :device, :browser, :geolocation, :operating_system, :visitor_visits
295
+ attr_accessor :last_activity_time, :legal_consent, :user_agent, :cookie, :kcs_heat, :cbscores,
291
296
  :mapping_identifier, :forced_variations, :simulated_variations
292
297
 
293
298
  def initialize
294
299
  Logging::KameleoonLogger.debug('CALL: VisitorData.new')
300
+ @time_started = (Time.now.to_f * 1000).to_i
295
301
  @mutex = Concurrent::ReadWriteLock.new
296
302
  @legal_consent = false
297
303
  Logging::KameleoonLogger.debug('RETURN: VisitorData.new')
@@ -318,11 +324,12 @@ module Kameleoon
318
324
  blk.call(@browser) unless @browser.nil?
319
325
  blk.call(@operating_system) unless @operating_system.nil?
320
326
  blk.call(@geolocation) unless @geolocation.nil?
327
+ blk.call(@visitor_visits) unless @visitor_visits.nil?
321
328
  @mutex.with_read_lock do
322
329
  @custom_data_map&.each { |_, cd| blk.call(cd) }
323
330
  @page_view_visits&.each { |_, pvv| blk.call(pvv.page_view) }
324
- @conversions&.each { |c| blk.call(c) }
325
331
  @variations&.each { |_, av| blk.call(av) }
332
+ @conversions&.each { |c| blk.call(c) }
326
333
  end
327
334
  end
328
335
 
@@ -332,8 +339,9 @@ module Kameleoon
332
339
  @mutex.with_read_lock do
333
340
  count += 1 unless @device.nil?
334
341
  count += 1 unless @browser.nil?
335
- count += 1 unless @geolocation.nil?
336
342
  count += 1 unless @operating_system.nil?
343
+ count += 1 unless @geolocation.nil?
344
+ count += 1 unless @visitor_visits.nil?
337
345
  count += @custom_data_map.size unless @custom_data_map.nil?
338
346
  count += @page_view_visits.size unless @page_view_visits.nil?
339
347
  count += @conversions.size unless @conversions.nil?
@@ -433,6 +441,10 @@ module Kameleoon
433
441
  @cbscores = cbs if overwrite || @cbscores.nil?
434
442
  end
435
443
 
444
+ def set_visitor_visits(visitor_visits, overwrite)
445
+ @visitor_visits = visitor_visits.localize(@time_started) if overwrite || @visitor_visits.nil?
446
+ end
447
+
436
448
  def add_forced_feature_variation(forced_variation)
437
449
  @simulated_variations ||= {}
438
450
  @simulated_variations[forced_variation.feature_key] = forced_variation
@@ -1,24 +1,77 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'kameleoon/data/data'
4
+ require 'kameleoon/network/uri_helper'
5
+
3
6
  module Kameleoon
4
- class VisitorVisits
5
- attr_reader :previous_visit_timestamps
7
+ class VisitorVisits < DuplicationUnsafeData
8
+ attr_reader :visit_number, :prev_visits, :time_started, :time_since_previous_visit
6
9
 
7
- def to_s
8
- "VisitorVisits{previous_visit_timestamps:#{@previous_visit_timestamps}}"
10
+ def initialize(prev_visits, visit_number = 0, time_started: 0, time_since_previous_visit: 0)
11
+ super(DataType::VISITOR_VISITS)
12
+ @visit_number = visit_number >= prev_visits.size ? visit_number : prev_visits.size
13
+ @prev_visits = prev_visits
14
+ @prev_visits.freeze
15
+ @time_started = time_started
16
+ @time_since_previous_visit = time_since_previous_visit
17
+ end
18
+
19
+ def localize(time_started)
20
+ time_since_previous_visit = 0
21
+ @prev_visits.each do |visit|
22
+ time_delta = time_started - visit.time_last_activity
23
+ unless time_delta.negative?
24
+ time_since_previous_visit = time_delta
25
+ break
26
+ end
27
+ end
28
+ VisitorVisits.new(
29
+ @prev_visits,
30
+ @visit_number,
31
+ time_started: time_started,
32
+ time_since_previous_visit: time_since_previous_visit
33
+ )
34
+ end
35
+
36
+ def obtain_full_post_text_line
37
+ params = {
38
+ eventType: 'staticData',
39
+ visitNumber: @visit_number,
40
+ timeSincePreviousVisit: @time_since_previous_visit,
41
+ nonce: nonce
42
+ }
43
+ Network::UriHelper.encode_query(params)
9
44
  end
10
45
 
11
- def initialize(previous_visit_timestamps = [])
12
- @previous_visit_timestamps = previous_visit_timestamps
13
- @previous_visit_timestamps.freeze
46
+ def to_s
47
+ 'VisitorVisits{' \
48
+ "visit_number:#{@visit_number}," \
49
+ "prev_visits:#{@prev_visits.map(&:to_s)}," \
50
+ "time_started:#{@time_started}," \
51
+ "time_since_previous_visit:#{@time_since_previous_visit}" \
52
+ '}'
14
53
  end
15
54
 
16
- def self.get_previous_visit_timestamps(visitor_visits)
17
- visitor_visits.is_a?(VisitorVisits) ? visitor_visits.previous_visit_timestamps : []
55
+ def ==(other)
56
+ other.is_a?(VisitorVisits) && (@visit_number == other.visit_number) && (@prev_visits == other.prev_visits) &&
57
+ (@time_started == other.time_started) && (@time_since_previous_visit == other.time_since_previous_visit)
18
58
  end
19
59
 
20
- def self.visitor_visits?(obj)
21
- obj.nil? || obj.is_a?(VisitorVisits)
60
+ class Visit
61
+ attr_reader :time_started, :time_last_activity
62
+
63
+ def initialize(time_started, time_last_activity = nil)
64
+ @time_started = time_started
65
+ @time_last_activity = time_last_activity || time_started
66
+ end
67
+
68
+ def to_s
69
+ "Visit{time_started:#{@time_started},time_last_activity:#{time_last_activity}}"
70
+ end
71
+
72
+ def ==(other)
73
+ other.is_a?(Visit) && (@time_started == other.time_started) && (@time_last_activity == other.time_last_activity)
74
+ end
22
75
  end
23
76
  end
24
77
  end
@@ -852,14 +852,17 @@ module Kameleoon
852
852
 
853
853
  def obtain_configuration(time_stamp = nil)
854
854
  Logging::KameleoonLogger.info('Fetching configuration from Client-Config service')
855
- response = @network_manager.fetch_configuration(time_stamp)
855
+ last_modified = @data_manager.data_file.last_modified
856
+ response = @network_manager.fetch_configuration(time_stamp, if_modified_since: last_modified)
856
857
  return false unless response
857
858
 
858
- configuration = JSON.parse(response)
859
- data_file = Configuration::DataFile.new(@config.environment, configuration)
860
- apply_new_configuration(data_file)
861
- call_update_handler_if_needed(!time_stamp.nil?)
862
- Logging::KameleoonLogger.info('Feature flags are fetched: %s', response.inspect)
859
+ if response.configuration
860
+ configuration = JSON.parse(response.configuration)
861
+ data_file = Configuration::DataFile.new(@config.environment, configuration, response.last_modified)
862
+ apply_new_configuration(data_file)
863
+ call_update_handler_if_needed(!time_stamp.nil?)
864
+ Logging::KameleoonLogger.info('Feature flags are fetched: %s', response.inspect)
865
+ end
863
866
  true
864
867
  end
865
868
 
@@ -871,7 +874,7 @@ module Kameleoon
871
874
  end
872
875
 
873
876
  ##
874
- # Call the handler when configuraiton was updated with new time stamp.
877
+ # Call the handler when configuration was updated with new time stamp.
875
878
  #
876
879
  # @param need_call [Bool] Indicates if we need to call handler or not.
877
880
  def call_update_handler_if_needed(need_call)
@@ -42,7 +42,7 @@ module Kameleoon
42
42
  filter = Types::RemoteVisitorDataFilter.new unless filter.is_a?(Types::RemoteVisitorDataFilter)
43
43
  is_unique_identifier = @visitor_manger.get_visitor(visitor_code)&.is_unique_identifier || false
44
44
  response = @network_manager.get_remote_visitor_data(visitor_code, filter, is_unique_identifier, timeout)
45
- remote_visitor_data = parse_custom_data_array(visitor_code, response)
45
+ remote_visitor_data = parse_custom_data_array(visitor_code, response, filter)
46
46
  remote_visitor_data.mark_data_as_sent(@data_manager.data_file.custom_data_info)
47
47
  data_to_add = remote_visitor_data.collect_data_to_add
48
48
  if add_data && !data_to_add.empty?
@@ -66,8 +66,8 @@ module Kameleoon
66
66
 
67
67
  ##
68
68
  # helper method used by `get_remote_visitor_data`
69
- def parse_custom_data_array(visitor_code, response)
70
- RemoteVisitorData.new(JSON.parse(response))
69
+ def parse_custom_data_array(visitor_code, response, filter)
70
+ RemoteVisitorData.new(JSON.parse(response), filter)
71
71
  rescue StandardError => e
72
72
  Logging::KameleoonLogger.error("Parsing of remote visitor data of '#{visitor_code}' failed: #{e}")
73
73
  raise
@@ -18,21 +18,23 @@ module Kameleoon
18
18
  module RemoteData
19
19
  class RemoteVisitorData
20
20
  attr_reader :custom_data_dict, :page_view_visits, :conversions, :experiments, :personalizations, :device,
21
- :browser, :operating_system, :geolocation, :previous_visitor_visits, :kcs_heat, :cbs, :visitor_code
21
+ :browser, :operating_system, :geolocation, :visitor_visits, :kcs_heat, :cbs, :visitor_code
22
22
 
23
- def initialize(hash)
23
+ def initialize(hash, filter)
24
+ @filter = filter
25
+ @visit_number = 0
24
26
  current_visit = hash['currentVisit']
25
- parse_visit(current_visit) unless current_visit.nil?
27
+ parse_visit(current_visit, false) unless current_visit.nil?
26
28
  previous_visits = hash['previousVisits']
27
29
  previous_visits = [] if previous_visits.nil?
28
30
 
29
31
  if previous_visits.size.positive?
30
- times_started = []
32
+ prev_visits = []
31
33
  previous_visits.each do |visit|
32
- times_started.push(visit['timeStarted'])
33
- parse_visit(visit)
34
+ prev_visits.push(VisitorVisits::Visit.new(visit['timeStarted'] || 0, visit['timeLastEvent']))
35
+ parse_visit(visit, true)
34
36
  end
35
- @previous_visitor_visits = VisitorVisits.new(times_started)
37
+ @visitor_visits = VisitorVisits.new(prev_visits, @visit_number)
36
38
  end
37
39
  @kcs_heat = parse_kcs_heat(hash['kcs'])
38
40
  @cbs = parse_cbscores(hash['cbs'])
@@ -41,7 +43,7 @@ module Kameleoon
41
43
  def collect_data_to_add
42
44
  data_to_add = []
43
45
  data_to_add.concat(@custom_data_dict.values) unless @custom_data_dict.nil?
44
- data_to_add.push(@previous_visitor_visits) unless @previous_visitor_visits.nil?
46
+ data_to_add.push(@visitor_visits) unless @visitor_visits.nil?
45
47
  data_to_add.push(@kcs_heat) unless @kcs_heat.nil?
46
48
  data_to_add.push(@cbs) unless @cbs.nil?
47
49
  data_to_add.concat(@page_view_visits.values) unless @page_view_visits.nil?
@@ -72,7 +74,7 @@ module Kameleoon
72
74
 
73
75
  private
74
76
 
75
- def parse_visit(hash)
77
+ def parse_visit(hash, is_prev_visit)
76
78
  @visitor_code = hash['visitorCode'] if @visitor_code.nil?
77
79
  custom_data_events = hash['customDataEvents']
78
80
  parse_custom_data(custom_data_events) if !custom_data_events.nil? && custom_data_events.size.positive?
@@ -86,7 +88,7 @@ module Kameleoon
86
88
  @geolocation = parse_geolocation(geolocation_events) if @geolocation.nil? && !geolocation_events.nil? && \
87
89
  geolocation_events.size.positive?
88
90
  static_data_events = hash['staticDataEvent']
89
- parse_static_data(static_data_events) unless static_data_events.nil?
91
+ parse_static_data(static_data_events, is_prev_visit) unless static_data_events.nil?
90
92
  personalization_events = hash['personalizationEvents']
91
93
  parse_personalizations(personalization_events) unless personalization_events.nil?
92
94
  end
@@ -144,17 +146,21 @@ module Kameleoon
144
146
  Geolocation.new(data['country'], data['region'], data['city'])
145
147
  end
146
148
 
147
- def parse_static_data(static_data_event)
149
+ def parse_static_data(static_data_event, is_prev_visit)
148
150
  data = static_data_event['data']
149
- if @device.nil?
151
+ if @visit_number.zero?
152
+ @visit_number = data['visitNumber'] || 0
153
+ @visit_number += 1 if is_prev_visit && @visit_number.positive?
154
+ end
155
+ if @filter.device && @device.nil?
150
156
  device_type = data['deviceType']
151
157
  @device = Device.new(device_type) unless device_type.nil?
152
158
  end
153
- if @browser.nil?
159
+ if @filter.browser && @browser.nil?
154
160
  browser_type = BrowserType.from_name(data['browser'])
155
161
  @browser = Browser.new(browser_type, data['browserVersion']) unless browser_type.nil?
156
162
  end
157
- if @operating_system.nil?
163
+ if @filter.operating_system && @operating_system.nil?
158
164
  operating_system_type = OperatingSystemType.from_name(data['os'])
159
165
  @operating_system = OperatingSystem.new(operating_system_type) unless operating_system_type.nil?
160
166
  end
@@ -103,16 +103,16 @@ module Kameleoon
103
103
  next true
104
104
  end
105
105
  else
106
- visitor.conversions.enumerate do |c|
107
- unsent_data.push(c) if c.unsent
108
- next true
109
- end
110
106
  if @data_file.has_any_targeted_delivery_rule
111
107
  visitor.variations.enumerate do |av|
112
108
  unsent_data.push(av) if av.unsent && (av.rule_type == Configuration::RuleType::TARGETED_DELIVERY)
113
109
  next true
114
110
  end
115
111
  end
112
+ visitor.conversions.enumerate do |c|
113
+ unsent_data.push(c) if c.unsent
114
+ next true
115
+ end
116
116
  end
117
117
  end
118
118
  unsent_data.push(Network::ActivityEvent.new) if unsent_data.empty? && is_consent_given
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kameleoon
4
+ module Network
5
+ class FetchedConfiguration
6
+ attr_reader :configuration, :last_modified
7
+
8
+ def initialize(configuration, last_modified)
9
+ @configuration = configuration
10
+ @last_modified = last_modified
11
+ end
12
+ end
13
+ end
14
+ end
@@ -22,7 +22,7 @@ module Kameleoon
22
22
  end
23
23
 
24
24
  def unknown_method_response(method, request)
25
- Response.new("Unknown request method '#{method}'", nil, nil, request)
25
+ Response.new("Unknown request method '#{method}'", nil, nil, nil, request)
26
26
  end
27
27
  end
28
28
 
@@ -48,9 +48,10 @@ module Kameleoon
48
48
  end
49
49
  body = resp.body
50
50
  body = nil if body&.empty?
51
- Response.new(nil, resp.code.to_i, body, request)
51
+ headers = resp.to_hash
52
+ Response.new(nil, resp.code.to_i, body, headers, request)
52
53
  rescue StandardError => e
53
- Response.new(e, nil, nil, request)
54
+ Response.new(e, nil, nil, nil, request)
54
55
  end
55
56
  end
56
57
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'kameleoon/logging/kameleoon_logger'
4
4
  require 'kameleoon/network/content_type'
5
+ require 'kameleoon/network/fetched_configuration'
5
6
  require 'kameleoon/network/method'
6
7
  require 'kameleoon/network/request'
7
8
  require 'kameleoon/network/net_provider'
@@ -19,6 +20,8 @@ module Kameleoon
19
20
  SDK_TYPE_HEADER = 'X-Kameleoon-SDK-Type'
20
21
  SDK_VERSION_HEADER = 'X-Kameleoon-SDK-Version'
21
22
  ACCESS_TOKEN_GRANT_TYPE = 'client_credentials'
23
+ HEADER_IF_MODIFIED_SINCE = 'If-Modified-Since'
24
+ HEADER_LAST_MODIFIED = 'last-modified' # in lower case because the network lib casts response headers to lower
22
25
 
23
26
  attr_reader :environment, :default_timeout, :access_token_source, :url_provider
24
27
 
@@ -30,26 +33,32 @@ module Kameleoon
30
33
  @sync_net_provider = SyncNetProvider.new
31
34
  end
32
35
 
33
- def fetch_configuration(timestamp = nil, timeout = nil)
36
+ def fetch_configuration(timestamp = nil, timeout = nil, if_modified_since: nil)
34
37
  url = @url_provider.make_configuration_url(@environment, timestamp)
35
38
  timeout = ensure_timeout(timeout)
36
- sdk_headers = { SDK_TYPE_HEADER => SDK_NAME, SDK_VERSION_HEADER => SDK_VERSION }
37
- request = Request.new(Method::GET, url, ContentType::JSON, timeout, extra_headers: sdk_headers)
38
- make_call(request, false, FETCH_CONFIGURATION_ATTEMPT_NUMBER - 1)
39
+ headers = { SDK_TYPE_HEADER => SDK_NAME, SDK_VERSION_HEADER => SDK_VERSION }
40
+ headers[HEADER_IF_MODIFIED_SINCE] = if_modified_since if if_modified_since
41
+ request = Request.new(Method::GET, url, ContentType::JSON, timeout, extra_headers: headers)
42
+ response, success = make_call(request, false, FETCH_CONFIGURATION_ATTEMPT_NUMBER - 1)
43
+ return nil unless success
44
+ return FetchedConfiguration.new(nil, nil) if response.code == 304
45
+
46
+ last_modified = response.headers&.[](HEADER_LAST_MODIFIED)&.first
47
+ FetchedConfiguration.new(response.body, last_modified)
39
48
  end
40
49
 
41
50
  def get_remote_data(key, timeout = nil)
42
51
  url = @url_provider.make_api_data_get_request_url(key)
43
52
  timeout = ensure_timeout(timeout)
44
53
  request = Request.new(Method::GET, url, ContentType::JSON, timeout)
45
- make_call(request, true)
54
+ unwrap_response(*make_call(request, true))
46
55
  end
47
56
 
48
57
  def get_remote_visitor_data(visitor_code, filter, is_unique_identifier, timeout = nil)
49
58
  url = @url_provider.make_visitor_data_get_url(visitor_code, filter, is_unique_identifier)
50
59
  timeout = ensure_timeout(timeout)
51
60
  request = Request.new(Method::GET, url, ContentType::JSON, timeout)
52
- make_call(request, true)
61
+ unwrap_response(*make_call(request, true))
53
62
  end
54
63
 
55
64
  def send_tracking_data(lines, timeout = nil)
@@ -58,7 +67,7 @@ module Kameleoon
58
67
  url = @url_provider.make_tracking_url
59
68
  timeout = ensure_timeout(timeout)
60
69
  request = Request.new(Method::POST, url, ContentType::TEXT, timeout, data: lines)
61
- make_call(request, true, TRACKING_CALL_ATTEMPT_NUMBER - 1, TRACKING_CALL_RETRY_DELAY)
70
+ unwrap_response(*make_call(request, true, TRACKING_CALL_ATTEMPT_NUMBER - 1, TRACKING_CALL_RETRY_DELAY))
62
71
  end
63
72
 
64
73
  def fetch_access_jwtoken(client_id, client_secret, timeout = nil)
@@ -71,7 +80,7 @@ module Kameleoon
71
80
  }
72
81
  data = UriHelper.encode_query(data_map).encode('UTF-8')
73
82
  request = Request.new(Method::POST, url, ContentType::FORM, timeout, data: data)
74
- make_call(request, false)
83
+ unwrap_response(*make_call(request, false))
75
84
  end
76
85
 
77
86
  private
@@ -81,6 +90,7 @@ module Kameleoon
81
90
  request, try_access_token_auth, retry_limit, retry_delay)
82
91
  attempt = 0
83
92
  success = false
93
+ response = nil
84
94
  while !success && (attempt <= retry_limit)
85
95
  delay(retry_delay) if attempt.positive? && retry_delay.positive?
86
96
  try_authorize(request) if try_access_token_auth
@@ -110,11 +120,11 @@ module Kameleoon
110
120
  attempt += 1
111
121
  end
112
122
  Logging::KameleoonLogger.debug('Fetched response %s for request %s', response, request)
113
- success ? response.body : false
123
+ [response, success]
114
124
  end
115
125
 
116
126
  def get_log_level(attempt, attempt_count)
117
- return log_level = attempt < attempt_count ? Logging::LogLevel::WARNING : Logging::LogLevel::ERROR
127
+ attempt < attempt_count ? Logging::LogLevel::WARNING : Logging::LogLevel::ERROR
118
128
  end
119
129
 
120
130
  def delay(period)
@@ -129,6 +139,10 @@ module Kameleoon
129
139
  token = @access_token_source.get_token(request.timeout)
130
140
  request.authorize(token)
131
141
  end
142
+
143
+ def unwrap_response(response, success)
144
+ success ? response.body : false
145
+ end
132
146
  end
133
147
  end
134
148
  end
@@ -16,7 +16,7 @@ module Kameleoon
16
16
  body = @data
17
17
  end
18
18
  end
19
- "HttpRequest{Method:'#{@method}',Url:'#{@url}',Headers:#{@extra_headers},Body:'#{body}'}"
19
+ "Request{method:'#{@method}',url:'#{@url}',headers:#{@extra_headers},body:'#{body}'}"
20
20
  end
21
21
 
22
22
  def initialize(method, url, content_type, timeout, extra_headers: nil, data: nil)
@@ -5,21 +5,22 @@ module Kameleoon
5
5
  ##
6
6
  # Response represents HTTP response.
7
7
  class Response
8
- attr_reader :error, :code, :body, :request
8
+ attr_reader :error, :code, :body, :headers, :request
9
9
 
10
10
  def to_s
11
- "HttpResponse{Code:'#{@code}',Reason:'#{@error}',Body:#{@body}}"
11
+ "Response{code:'#{@code}',error:'#{@error}',body:#{@body}}"
12
12
  end
13
13
 
14
- def initialize(error, code, body, request)
14
+ def initialize(error, code, body, headers, request)
15
15
  @error = error
16
16
  @code = code
17
17
  @body = body
18
+ @headers = headers
18
19
  @request = request
19
20
  end
20
21
 
21
22
  def success?
22
- @error.nil? && ((@code / 100 == 2) || (@code == 403))
23
+ @error.nil? && ((@code / 100 == 2) || (@code == 403) || (@code == 304))
23
24
  end
24
25
  end
25
26
  end
@@ -68,6 +68,7 @@ module Kameleoon
68
68
  params[is_unique_identifier ? :mappingValue : :visitorCode] = visitor_code
69
69
  params[:maxNumberPreviousVisits] = filter.previous_visit_amount
70
70
  params[:version] = 0
71
+ params[:staticData] = true
71
72
  params[:kcs] = true if filter.kcs
72
73
  params[:currentVisit] = true if filter.current_visit
73
74
  params[:customData] = true if filter.custom_data
@@ -75,7 +76,6 @@ module Kameleoon
75
76
  params[:geolocation] = true if filter.geolocation
76
77
  params[:experiment] = true if filter.experiments
77
78
  params[:page] = true if filter.page_views
78
- params[:staticData] = true if filter.device || filter.browser || filter.operating_system
79
79
  params[:personalization] = true if filter.personalization
80
80
  params[:cbs] = true if filter.cbs
81
81
  format(DATA_API_URL_FORMAT, @data_api_domain, VISITOR_DATA_PATH, UriHelper.encode_query(params))
@@ -11,7 +11,6 @@ module Kameleoon
11
11
  TARGET_FEATURE_FLAG = 'TARGET_FEATURE_FLAG'
12
12
  TARGET_EXPERIMENT = 'TARGET_EXPERIMENT'
13
13
  TARGET_PERSONALIZATION = 'TARGET_PERSONALIZATION'
14
- EXCLUSIVE_FEATURE_FLAG = 'EXCLUSIVE_FEATURE_FLAG'
15
14
  EXCLUSIVE_EXPERIMENT = 'EXCLUSIVE_EXPERIMENT'
16
15
  PAGE_URL = 'PAGE_URL'
17
16
  PAGE_VIEWS = 'PAGE_VIEWS'
@@ -3,7 +3,6 @@ require_relative 'conditions/target_experiment_condition'
3
3
  require_relative 'conditions/target_feature_flag_condition'
4
4
  require_relative 'conditions/target_personalization_condition'
5
5
  require_relative 'conditions/exclusive_experiment_condition'
6
- require_relative 'conditions/exclusive_feature_flag_condition'
7
6
  require_relative 'conditions/page_title_condition'
8
7
  require_relative 'conditions/page_url_condition'
9
8
  require_relative 'conditions/page_view_number_condition'
@@ -39,8 +38,6 @@ module Kameleoon
39
38
  TargetExperimentCondition.new(condition_json)
40
39
  when ConditionType::TARGET_PERSONALIZATION
41
40
  TargetPersonalizationCondition.new(condition_json)
42
- when ConditionType::EXCLUSIVE_FEATURE_FLAG
43
- ExclusiveFeatureFlagCondition.new(condition_json)
44
41
  when ConditionType::EXCLUSIVE_EXPERIMENT
45
42
  ExclusiveExperimentCondition.new(condition_json)
46
43
  when ConditionType::PAGE_URL
@@ -10,16 +10,17 @@ module Kameleoon
10
10
  def initialize(json_condition)
11
11
  count_in_millis = json_condition['countInMillis']
12
12
  super(json_condition, count_in_millis)
13
- @is_first_visit = @type == Kameleoon::Targeting::ConditionType::FIRST_VISIT
13
+ @is_first_visit = @type == ConditionType::FIRST_VISIT
14
14
  end
15
15
 
16
16
  def check(data)
17
- return false unless data.is_a?(Kameleoon::VisitorVisits) && !@condition_value.nil?
17
+ data ||= VisitorVisits.new([])
18
+ return false unless data.is_a?(VisitorVisits) && !@condition_value.nil?
18
19
 
19
- previous_visits_count = data.previous_visit_timestamps.count
20
- if previous_visits_count >= 1
20
+ prev_visits = data.prev_visits
21
+ if prev_visits.size >= 1
21
22
  now = (Time.now.to_f * 1000).to_i # ... * 1000 for convert seconds to milliseconds
22
- visit_time = data.previous_visit_timestamps[@is_first_visit ? previous_visits_count - 1 : 0]
23
+ visit_time = prev_visits[@is_first_visit ? prev_visits.size - 1 : 0].time_started
23
24
  return check_targeting(now - visit_time)
24
25
  end
25
26
  false
@@ -13,15 +13,27 @@ module Kameleoon
13
13
  end
14
14
 
15
15
  def check(data)
16
- return false unless VisitorVisits.visitor_visits?(data) && !@condition_value.nil?
16
+ return false unless data.is_a?(TargetingData) && !@condition_value.nil?
17
17
 
18
18
  number_of_visits_today = 0
19
19
  start_of_day = (Time.new.to_date.to_time.to_f * 1000).to_i # ... * 1000 to convert seconds to milliseconds
20
- for timestamp in VisitorVisits.get_previous_visit_timestamps(data)
21
- break if timestamp < start_of_day
20
+ data.visitor_visits.prev_visits.each do |visit|
21
+ break if visit.time_started < start_of_day
22
+
22
23
  number_of_visits_today += 1
23
24
  end
24
- check_targeting(number_of_visits_today + 1) # +1 for current visit
25
+ number_of_visits_today += 1 if data.current_visit_time_started >= start_of_day
26
+ check_targeting(number_of_visits_today)
27
+ end
28
+
29
+ class TargetingData
30
+ attr_reader :current_visit_time_started, :visitor_visits
31
+
32
+ def initialize(current_visit_time_started, visitor_visits)
33
+ @current_visit_time_started =
34
+ current_visit_time_started.is_a?(Integer) ? current_visit_time_started : (Time.new.to_f * 1000).to_i
35
+ @visitor_visits = visitor_visits.is_a?(VisitorVisits) ? visitor_visits : VisitorVisits.new([])
36
+ end
25
37
  end
26
38
  end
27
39
  end
@@ -13,10 +13,11 @@ module Kameleoon
13
13
  end
14
14
 
15
15
  def check(data)
16
- return false unless VisitorVisits.visitor_visits?(data) && !@condition_value.nil?
16
+ data ||= VisitorVisits.new([])
17
+ return false unless data.is_a?(VisitorVisits) && !@condition_value.nil?
17
18
 
18
- previous_visit_timestamps = VisitorVisits.get_previous_visit_timestamps(data)
19
- check_targeting(previous_visit_timestamps.size + 1) # +1 for current visit
19
+ prev_visits = data.prev_visits
20
+ check_targeting(prev_visits.size + 1) # +1 for current visit
20
21
  end
21
22
  end
22
23
  end
@@ -17,14 +17,15 @@ module Kameleoon
17
17
  end
18
18
 
19
19
  def check(data)
20
- return false unless VisitorVisits.visitor_visits?(data)
20
+ data ||= VisitorVisits.new([])
21
+ return false unless data.is_a?(VisitorVisits)
21
22
 
22
- previous_visit_timestamps = VisitorVisits.get_previous_visit_timestamps(data)
23
+ prev_visits = data.prev_visits
23
24
  case @visitor_type
24
25
  when VisitorType::NEW
25
- previous_visit_timestamps.empty?
26
+ prev_visits.empty?
26
27
  when VisitorType::RETURNING
27
- !previous_visit_timestamps.empty?
28
+ !prev_visits.empty?
28
29
  else
29
30
  false
30
31
  end
@@ -3,12 +3,12 @@
3
3
  require 'kameleoon/logging/kameleoon_logger'
4
4
  require 'kameleoon/targeting/condition'
5
5
  require 'kameleoon/targeting/conditions/exclusive_experiment_condition'
6
- require 'kameleoon/targeting/conditions/exclusive_feature_flag_condition'
7
6
  require 'kameleoon/targeting/conditions/sdk_language_condition'
8
7
  require 'kameleoon/targeting/conditions/segment_condition'
9
8
  require 'kameleoon/targeting/conditions/target_experiment_condition'
10
9
  require 'kameleoon/targeting/conditions/target_feature_flag_condition'
11
10
  require 'kameleoon/targeting/conditions/target_personalization_condition'
11
+ require 'kameleoon/targeting/conditions/visit_number_today_condition'
12
12
 
13
13
  module Kameleoon
14
14
  # @api private
@@ -85,16 +85,15 @@ module Kameleoon
85
85
  condition_data = TargetExperimentInfo.new(visitor&.variations)
86
86
  when ConditionType::TARGET_PERSONALIZATION
87
87
  condition_data = TargetPersonalizationInfo.new(visitor&.personalizations)
88
- when ConditionType::EXCLUSIVE_FEATURE_FLAG
89
- condition_data = ExclusiveFeatureFlagInfo.new(campaign_id, visitor&.variations)
90
88
  when ConditionType::EXCLUSIVE_EXPERIMENT
91
89
  condition_data = ExclusiveExperimentInfo.new(campaign_id, visitor&.variations, visitor&.personalizations)
92
90
  when ConditionType::FIRST_VISIT,
93
91
  ConditionType::LAST_VISIT,
94
92
  ConditionType::VISITS,
95
- ConditionType::SAME_DAY_VISITS,
96
93
  ConditionType::NEW_VISITORS
97
94
  condition_data = visitor.visitor_visits unless visitor.nil?
95
+ when ConditionType::SAME_DAY_VISITS
96
+ condition_data = VisitNumberTodayCondition::TargetingData.new(visitor&.time_started, visitor&.visitor_visits)
98
97
  when ConditionType::HEAT_SLICE
99
98
  condition_data = visitor&.kcs_heat
100
99
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Kameleoon
4
- SDK_VERSION = '3.12.0'
4
+ SDK_VERSION = '3.13.0'
5
5
  SDK_NAME = 'RUBY'
6
6
  end
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.12.0
4
+ version: 3.13.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-04-02 00:00:00.000000000 Z
11
+ date: 2025-05-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: em-http-request
@@ -130,6 +130,7 @@ files:
130
130
  - lib/kameleoon/network/activity_event.rb
131
131
  - lib/kameleoon/network/content_type.rb
132
132
  - lib/kameleoon/network/cookie/cookie_manager.rb
133
+ - lib/kameleoon/network/fetched_configuration.rb
133
134
  - lib/kameleoon/network/method.rb
134
135
  - lib/kameleoon/network/net_provider.rb
135
136
  - lib/kameleoon/network/network_manager.rb
@@ -153,7 +154,6 @@ files:
153
154
  - lib/kameleoon/targeting/conditions/custom_datum.rb
154
155
  - lib/kameleoon/targeting/conditions/device_condition.rb
155
156
  - lib/kameleoon/targeting/conditions/exclusive_experiment_condition.rb
156
- - lib/kameleoon/targeting/conditions/exclusive_feature_flag_condition.rb
157
157
  - lib/kameleoon/targeting/conditions/geolocation_condition.rb
158
158
  - lib/kameleoon/targeting/conditions/kcs_heat_range_condition.rb
159
159
  - lib/kameleoon/targeting/conditions/number_condition.rb
@@ -1,27 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'kameleoon/targeting/condition'
4
-
5
- module Kameleoon
6
- # @api private
7
- module Targeting
8
- # ExclusiveFeatureFlag represents an instance of Exclusive FeatureFlag condition in user account
9
- class ExclusiveFeatureFlagCondition < Condition
10
- def check(data)
11
- return false unless data.is_a?(ExclusiveFeatureFlagInfo)
12
-
13
- size = data.variations_storage&.size || 0
14
- size.zero? || (size == 1 && !data.variations_storage.get(data.current_experiment_id).nil?)
15
- end
16
- end
17
-
18
- class ExclusiveFeatureFlagInfo
19
- attr_reader :current_experiment_id, :variations_storage
20
-
21
- def initialize(current_experiment_id, variations_storage)
22
- @current_experiment_id = current_experiment_id
23
- @variations_storage = variations_storage
24
- end
25
- end
26
- end
27
- end