kameleoon-client-ruby 2.2.0 → 3.0.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.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/lib/kameleoon/client_readiness.rb +40 -0
  3. data/lib/kameleoon/configuration/data_file.rb +41 -0
  4. data/lib/kameleoon/configuration/feature_flag.rb +3 -2
  5. data/lib/kameleoon/configuration/rule.rb +18 -3
  6. data/lib/kameleoon/configuration/settings.rb +3 -1
  7. data/lib/kameleoon/configuration/variable.rb +0 -2
  8. data/lib/kameleoon/configuration/variation_exposition.rb +0 -2
  9. data/lib/kameleoon/data/browser.rb +1 -1
  10. data/lib/kameleoon/data/conversion.rb +1 -1
  11. data/lib/kameleoon/data/custom_data.rb +10 -14
  12. data/lib/kameleoon/data/data.rb +22 -3
  13. data/lib/kameleoon/data/device.rb +2 -1
  14. data/lib/kameleoon/data/manager/assigned_variation.rb +38 -0
  15. data/lib/kameleoon/data/manager/data_array_storage.rb +43 -0
  16. data/lib/kameleoon/data/manager/data_map_storage.rb +43 -0
  17. data/lib/kameleoon/data/manager/page_view_visit.rb +19 -0
  18. data/lib/kameleoon/data/manager/visitor.rb +142 -0
  19. data/lib/kameleoon/data/manager/visitor_manager.rb +71 -0
  20. data/lib/kameleoon/data/page_view.rb +2 -1
  21. data/lib/kameleoon/exceptions.rb +30 -35
  22. data/lib/kameleoon/hybrid/manager.rb +13 -31
  23. data/lib/kameleoon/{client.rb → kameleoon_client.rb} +165 -333
  24. data/lib/kameleoon/kameleoon_client_config.rb +91 -0
  25. data/lib/kameleoon/kameleoon_client_factory.rb +42 -0
  26. data/lib/kameleoon/network/activity_event.rb +6 -3
  27. data/lib/kameleoon/network/cookie/cookie_manager.rb +84 -0
  28. data/lib/kameleoon/network/net_provider.rb +5 -37
  29. data/lib/kameleoon/network/network_manager.rb +8 -7
  30. data/lib/kameleoon/network/request.rb +3 -2
  31. data/lib/kameleoon/network/response.rb +0 -8
  32. data/lib/kameleoon/network/url_provider.rb +5 -3
  33. data/lib/kameleoon/targeting/conditions/browser_condition.rb +2 -3
  34. data/lib/kameleoon/targeting/conditions/conversion_condition.rb +12 -4
  35. data/lib/kameleoon/targeting/conditions/custom_datum.rb +19 -13
  36. data/lib/kameleoon/targeting/conditions/device_condition.rb +3 -4
  37. data/lib/kameleoon/targeting/conditions/exclusive_experiment.rb +2 -1
  38. data/lib/kameleoon/targeting/conditions/page_title_condition.rb +11 -4
  39. data/lib/kameleoon/targeting/conditions/page_url_condition.rb +18 -4
  40. data/lib/kameleoon/targeting/conditions/string_value_condition.rb +2 -0
  41. data/lib/kameleoon/targeting/conditions/target_experiment.rb +11 -6
  42. data/lib/kameleoon/utils.rb +41 -4
  43. data/lib/kameleoon/version.rb +1 -1
  44. data/lib/kameleoon.rb +4 -2
  45. metadata +14 -9
  46. data/lib/kameleoon/configuration/experiment.rb +0 -42
  47. data/lib/kameleoon/cookie.rb +0 -88
  48. data/lib/kameleoon/factory.rb +0 -29
  49. data/lib/kameleoon/network/experiment_event.rb +0 -35
  50. data/lib/kameleoon/storage/variation_storage.rb +0 -42
  51. data/lib/kameleoon/storage/visitor_variation.rb +0 -20
@@ -1,27 +1,25 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'kameleoon/client_readiness'
3
4
  require 'kameleoon/targeting/models'
4
5
  require 'kameleoon/exceptions'
5
- require 'kameleoon/cookie'
6
6
  require 'kameleoon/data/custom_data'
7
7
  require 'kameleoon/data/user_agent'
8
+ require 'kameleoon/data/manager/assigned_variation'
9
+ require 'kameleoon/data/manager/visitor_manager'
8
10
  require 'kameleoon/configuration/feature_flag'
9
11
  require 'kameleoon/configuration/variation'
10
- require 'kameleoon/configuration/settings'
12
+ require 'kameleoon/configuration/data_file'
11
13
  require 'kameleoon/network/activity_event'
12
- require 'kameleoon/network/experiment_event'
13
14
  require 'kameleoon/network/url_provider'
14
15
  require 'kameleoon/network/network_manager'
16
+ require 'kameleoon/network/cookie/cookie_manager'
15
17
  require 'kameleoon/real_time/real_time_configuration_service'
16
- require 'kameleoon/storage/variation_storage'
17
18
  require 'kameleoon/hybrid/manager'
18
19
  require 'kameleoon/storage/cache_factory'
19
20
  require 'rufus/scheduler'
20
21
  require 'yaml'
21
22
  require 'json'
22
- require 'em-synchrony'
23
- require 'em-synchrony/em-http'
24
- require 'em-synchrony/fiber_iterator'
25
23
  require 'objspace'
26
24
  require 'time'
27
25
  require 'ostruct'
@@ -30,45 +28,37 @@ module Kameleoon
30
28
  ##
31
29
  # Client for Kameleoon
32
30
  #
33
- class Client
34
- include Cookie
31
+ class KameleoonClient
35
32
  include Exception
36
33
 
34
+ attr_reader :site_code
35
+
37
36
  ##
38
- # You should create Client with the Client Factory only.
37
+ # You should create KameleoonClient with the Client Factory only.
39
38
  #
40
- def initialize(site_code, path_config_file, interval, default_timeout, client_id = nil, client_secret = nil)
41
- config = YAML.load_file(path_config_file)
39
+ def initialize(site_code, config)
40
+ raise Exception::SiteCodeIsEmpty, 'Provided site_sode is empty' if site_code&.empty? != false
41
+
42
42
  @site_code = site_code
43
- @default_timeout = config['default_timeout'] || default_timeout # in ms
44
- refresh_interval = config['actions_configuration_refresh_interval']
45
- @interval = refresh_interval.nil? ? interval : "#{refresh_interval}m"
43
+ @config = config
46
44
  @real_time_configuration_service = nil
47
45
  @update_configuration_handler = nil
48
46
  @fetch_configuration_update_job = nil
49
- @client_id = client_id || config['client_id']
50
- @client_secret = client_secret || config['client_secret']
51
- @data_maximum_size = config['visitor_data_maximum_size'] || 500 # mb
52
- @environment = config['environment'] || DEFAULT_ENVIRONMENT
53
- @settings = Kameleoon::Configuration::Settings.new
54
- @verbose_mode = config['verbose_mode'] || false
55
- @experiments = []
56
- @feature_flags = []
57
- @data = {}
58
- @user_agents = {}
59
- @variation_storage = Kameleoon::Storage::VariationStorage.new
60
- @hybrid_manager = Kameleoon::Hybrid::ManagerImpl.new(
61
- CACHE_EXPIRATION_TIMEOUT,
62
- CACHE_EXPIRATION_TIMEOUT * 3,
63
- Kameleoon::Storage::CacheFactoryImpl.new,
64
- method(:log)
65
- )
47
+ @data_file = Configuration::DataFile.new(config.environment)
48
+ @visitor_manager = Kameleoon::DataManager::VisitorManager.new(config.session_duration_second)
49
+ @hybrid_manager = Kameleoon::Hybrid::ManagerImpl.new(HYBRID_EXPIRATION_TIME, method(:log))
66
50
  @network_manager = Network::NetworkManager.new(
67
- @environment,
68
- @default_timeout,
69
- Network::UrlProvider.new(@site_code, Network::UrlProvider::DEFAULT_DATA_API_URL),
51
+ config.environment,
52
+ config.default_timeout_millisecond,
53
+ Network::UrlProvider.new(site_code, Network::UrlProvider::DEFAULT_DATA_API_URL),
70
54
  method(:log)
71
55
  )
56
+ @cookie_manager = Network::Cookie::CookieManager.new(config.top_level_domain)
57
+ @readiness = ClientReadiness.new
58
+ end
59
+
60
+ def wait_init
61
+ @readiness.wait
72
62
  end
73
63
 
74
64
  ##
@@ -80,8 +70,8 @@ module Kameleoon
80
70
  # @note The implementation logic is described here:
81
71
  # First we check if a kameleoonVisitorCode cookie or query parameter associated with the current HTTP request can be
82
72
  # found. If so, we will use this as the visitor identifier. If no cookie / parameter is found in the current
83
- # request, we either randomly generate a new identifier, or use the defaultVisitorCode argument as identifier if it
84
- # is passed. This allows our customers to use their own identifiers as visitor codes, should they wish to.
73
+ # request, we either randomly generate a new identifier, or use the default_visitor_code argument as identifier if
74
+ # it is passed. This allows our customers to use their own identifiers as visitor codes, should they wish to.
85
75
  # This can have the added benefit of matching Kameleoon visitors with their own users without any additional
86
76
  # look-ups in a matching table.
87
77
  # In any case, the server-side (via HTTP header) kameleoonVisitorCode cookie is set with the value. Then this
@@ -97,61 +87,15 @@ module Kameleoon
97
87
  # cookies = {'kameleoonVisitorCode' => '1234asdf4321fdsa'}
98
88
  # visitor_code = get_visitor_code(cookies, 'my-domaine.com')
99
89
  #
100
- def get_visitor_code(cookies, top_level_domain, default_visitor_code = nil)
101
- read_and_write(cookies, top_level_domain, cookies, default_visitor_code)
90
+ def get_visitor_code(cookies, default_visitor_code = nil)
91
+ @cookie_manager.get_or_add(cookies, default_visitor_code)
102
92
  end
103
93
 
104
- # DEPRECATED. Please use `get_visitor_code` instead.
105
- def obtain_visitor_code(cookies, top_level_domain, default_visitor_code = nil)
106
- warn '[DEPRECATION] `obtain_visitor_code` is deprecated. Please use `get_visitor_code` instead.'
107
- get_visitor_code(cookies, top_level_domain, default_visitor_code)
108
- end
109
-
110
- ##
111
- # Trigger an experiment.
112
- #
113
- # If such a visitor_code has never been associated with any variation, the SDK returns a randomly selected variation
114
- # If a user with a given visitor_code is already registered with a variation, it will detect the previously
115
- # registered variation and return the variation_id.
116
- # You have to make sure that proper error handling is set up in your code as shown in the example to the right to
117
- # catch potential exceptions.
118
- #
119
- # @param [String] visitor_code Visitor code
120
- # @param [Integer] experiment_id Id of the experiment you want to trigger.
121
- #
122
- # @return [Integer] Id of the variation
123
- #
124
- # @raise [Kameleoon::Exception::ExperimentConfigurationNotFound] Raise when experiment configuration is not found
125
- # @raise [Kameleoon::Exception::NotAllocated] The visitor triggered the experiment, but did not activate it.
126
- # Usually, this happens because the user has been associated with excluded traffic
127
- # @raise [Kameleoon::Exception::NotTargeted] The visitor is not targeted by the experiment, as the
128
- # associated targeting segment conditions were not fulfilled. He should see the reference variation
129
- # @raise [Kameleoon::Exception::VisitorCodeNotValid] If the visitor code is empty or longer than 255 chars
130
- #
131
- def trigger_experiment(visitor_code, experiment_id)
132
- check_visitor_code(visitor_code)
133
- experiment = @experiments.find { |exp| exp.id.to_s == experiment_id.to_s }
134
- if experiment.nil?
135
- raise Exception::ExperimentConfigurationNotFound.new(experiment_id),
136
- "Experiment #{experiment_id} is not found"
137
- end
138
- check_site_code_enable(experiment)
139
- targeted = check_targeting(visitor_code, experiment_id, experiment)
140
- if targeted
141
- # saved_variation = get_valid_saved_variation(visitor_code, experiment)
142
- variation_id = calculate_variation_for_experiment(visitor_code, experiment)
143
- save_variation(visitor_code, experiment_id, variation_id)
144
- end
145
- _send_tracking_request(visitor_code, experiment_id, variation_id)
146
- unless targeted
147
- raise Exception::NotTargeted.new(visitor_code),
148
- "Experiment #{experiment_id} is not targeted for visitor #{visitor_code}"
149
- end
150
- if variation_id.nil?
151
- raise Exception::NotAllocated.new(visitor_code),
152
- "Experiment #{experiment_id} is not active for visitor #{visitor_code}"
153
- end
154
- variation_id
94
+ def set_legal_consent(visitor_code, consent, cookies = nil)
95
+ Utils::VisitorCode.validate(visitor_code)
96
+ visitor = @visitor_manager.get_or_create_visitor(visitor_code)
97
+ visitor.legal_consent = consent
98
+ @cookie_manager.update(visitor_code, consent, cookies)
155
99
  end
156
100
 
157
101
  ##
@@ -165,19 +109,12 @@ module Kameleoon
165
109
  # @param [String] visitor_code Visitor code
166
110
  # @param [...Data] data Data to associate with the visitor code
167
111
  #
168
- # @raise [Kameleoon::Exception::VisitorCodeNotValid] If the visitor code is empty or longer than 255 chars
112
+ # @raise [Kameleoon::Exception::VisitorCodeInvalid] If the visitor code is empty or longer than 255 chars
169
113
  #
170
114
  def add_data(visitor_code, *args)
171
- check_visitor_code(visitor_code)
172
- @data.shift while ObjectSpace.memsize_of(@data) > @data_maximum_size * (2**20)
173
- args.each do |data_element|
174
- if data_element.is_a?(UserAgent)
175
- add_user_agent_data(visitor_code, data_element)
176
- next
177
- end
178
- @data[visitor_code] = [] unless @data.key?(visitor_code)
179
- @data[visitor_code].push(data_element)
180
- end
115
+ Utils::VisitorCode.validate(visitor_code)
116
+ visitor = @visitor_manager.get_or_create_visitor(visitor_code)
117
+ visitor.add_data(method(:log), *args)
181
118
  end
182
119
 
183
120
  ##
@@ -193,10 +130,10 @@ module Kameleoon
193
130
  # @param [Integer] goal_id Id of the goal
194
131
  # @param [Float] revenue Optional - Revenue of the conversion.
195
132
  #
196
- # @raise [Kameleoon::Exception::VisitorCodeNotValid] If the visitor code is empty or longer than 255 chars
133
+ # @raise [Kameleoon::Exception::VisitorCodeInvalid] If the visitor code is empty or longer than 255 chars
197
134
  #
198
135
  def track_conversion(visitor_code, goal_id, revenue = 0.0)
199
- check_visitor_code(visitor_code)
136
+ Utils::VisitorCode.validate(visitor_code)
200
137
  add_data(visitor_code, Conversion.new(goal_id, revenue))
201
138
  flush(visitor_code)
202
139
  end
@@ -210,74 +147,17 @@ module Kameleoon
210
147
  #
211
148
  # @param [String] visitor_code Optional field - Visitor code, without visitor code it flush all of the data
212
149
  #
213
- # @raise [Kameleoon::Exception::VisitorCodeNotValid] If the visitor code is not null and empty or longer than 255 chars
150
+ # @raise [Kameleoon::Exception::VisitorCodeInvalid] If the visitor code is not nil and is empty or longer than 255 chars
214
151
  #
215
152
  def flush(visitor_code = nil)
216
- check_visitor_code(visitor_code) unless visitor_code.nil?
217
- if !visitor_code.nil?
218
- _send_tracking_request(visitor_code)
219
- else
220
- @data.select { |_, values| values.any? { |data| !data.sent } }.each_key { |key| flush(key) }
221
- end
222
- end
223
-
224
- ##
225
- # Obtain variation associated data.
226
- #
227
- # To retrieve JSON data associated with a variation, call the get_variation_associated_data method of our SDK.
228
- # The JSON data usually represents some metadata of the variation, and can be configured on our web application
229
- # interface or via our Automation API.
230
- # This method takes the variationID as a parameter and will return the data as a json string.
231
- # It will throw an exception () if the variation ID is wrong or corresponds to an experiment that is not yet online.
232
- #
233
- # @param [Integer] variation_id
234
- #
235
- # @return [Hash] Hash object of the json object.
236
- #
237
- # @raise [Kameleoon::Exception::VariationNotFound] Raise exception if the variation is not found.
238
- #
239
- def get_variation_associated_data(variation_id)
240
- variation = @experiments.map(&:variations).flatten.select { |var| var['id'].to_i == variation_id.to_i }.first
241
- if variation.nil?
242
- raise Exception::VariationConfigurationNotFound.new(variation_id),
243
- "Variation key #{variation_id} not found"
153
+ Utils::VisitorCode.validate(visitor_code) unless visitor_code.nil?
154
+ if visitor_code.nil?
155
+ @visitor_manager.enumerate { |visitor_code, visitor| _send_tracking_request(visitor_code, visitor, false) }
244
156
  else
245
- JSON.parse(variation['customJson'])
157
+ _send_tracking_request(visitor_code, nil, true)
246
158
  end
247
159
  end
248
160
 
249
- # DEPRECATED. Please use `get_variation_associated_data` instead.
250
- def obtain_variation_associated_data(variation_id)
251
- warn '[DEPRECATION] `obtain_variation_associated_data` is deprecated.
252
- Please use `get_variation_associated_data` instead.'
253
- get_variation_associated_data(variation_id)
254
- end
255
-
256
- # #
257
- # Activate a feature toggle.
258
-
259
- # This method takes a visitor_code and feature_key (or feature_id) as mandatory arguments to check if the specified
260
- # feature will be active for a given user.
261
- # If such a user has never been associated with this feature flag, the SDK returns a boolean value randomly
262
- # (true if the user should have this feature or false if not). If a user with a given visitorCode is already
263
- # registered with this feature flag, it will detect the previous featureFlag value.
264
- # You have to make sure that proper error handling is set up in your code as shown in the example
265
- # to the right to catch potential exceptions.
266
-
267
- # @param [String] visitor_code
268
- # @param [String | Integer] feature_key
269
-
270
- # @raise [Kameleoon::Exception::FeatureConfigurationNotFound] Feature Flag isn't found in this configuration
271
- # @raise [Kameleoon::Exception::NotTargeted] The visitor is not targeted by the experiment, as the
272
- # associated targeting segment conditions were not fulfilled. He should see the reference variation
273
- # @raise [Kameleoon::Exception::VisitorCodeNotValid] If the visitor code is empty or longer than 255 chars
274
- #
275
- # DEPRECATED. Please use `feature_active?` instead.
276
- def activate_feature(visitor_code, feature_key)
277
- warn '[DEPRECATION] `activate_feature` is deprecated. Please use `feature_active?` instead.'
278
- feature_active?(visitor_code, feature_key)
279
- end
280
-
281
161
  ##
282
162
  # Check if feature is active for a given visitor code
283
163
  #
@@ -292,12 +172,14 @@ module Kameleoon
292
172
  # @param [String] visitor_code Unique identifier of the user. This field is mandatory.
293
173
  # @param [String] feature_key Key of the feature flag you want to expose to a user. This field is mandatory.
294
174
  #
295
- # @raise [Kameleoon::Exception::FeatureConfigurationNotFound] Feature Flag isn't found in this configuration
296
- # @raise [Kameleoon::Exception::VisitorCodeNotValid] If the visitor code is empty or longer than 255 chars
175
+ # @raise [Kameleoon::Exception::FeatureNotFound] Feature Flag isn't found in this configuration
176
+ # @raise [Kameleoon::Exception::VisitorCodeInvalid] If the visitor code is empty or longer than 255 chars
297
177
  #
298
178
  def feature_active?(visitor_code, feature_key)
299
179
  _, variation_key = _get_feature_variation_key(visitor_code, feature_key)
300
180
  variation_key != Kameleoon::Configuration::VariationType::VARIATION_OFF
181
+ rescue Exception::FeatureEnvironmentDisabled
182
+ false
301
183
  end
302
184
 
303
185
  #
@@ -312,8 +194,10 @@ module Kameleoon
312
194
  # @param [String] visitor_code
313
195
  # @param [String] feature_key
314
196
  #
315
- # @raise [Kameleoon::Exception::FeatureConfigurationNotFound] Feature Flag isn't found in this configuration
316
- # @raise [Kameleoon::Exception::VisitorCodeNotValid] If the visitor code is empty or longer than 255 chars
197
+ # @raise [Kameleoon::Exception::FeatureNotFound] Feature Flag isn't found in this configuration
198
+ # @raise [Kameleoon::Exception::VisitorCodeInvalid] If the visitor code is empty or longer than 255 chars
199
+ # @raise [Kameleoon::Exception::FeatureEnvironmentDisabled] If the requested feature flag is disabled for
200
+ # the current environment
317
201
  #
318
202
  def get_feature_variation_key(visitor_code, feature_key)
319
203
  _, variation_key = _get_feature_variation_key(visitor_code, feature_key)
@@ -329,9 +213,11 @@ module Kameleoon
329
213
  # @param [String] feature_key
330
214
  # @param [String] variable_name
331
215
  #
332
- # @raise [Kameleoon::Exception::FeatureConfigurationNotFound] Feature Flag isn't found in this configuration
216
+ # @raise [Kameleoon::Exception::FeatureNotFound] Feature Flag isn't found in this configuration
333
217
  # @raise [Kameleoon::Exception::FeatureVariableNotFound]
334
- # @raise [Kameleoon::Exception::VisitorCodeNotValid] If the visitor code is empty or longer than 255 chars
218
+ # @raise [Kameleoon::Exception::VisitorCodeInvalid] If the visitor code is empty or longer than 255 chars
219
+ # @raise [Kameleoon::Exception::FeatureEnvironmentDisabled] If the requested feature flag is disabled for
220
+ # the current environment
335
221
  #
336
222
  def get_feature_variable(visitor_code, feature_key, variable_name)
337
223
  feature_flag, variation_key = _get_feature_variation_key(visitor_code, feature_key)
@@ -354,14 +240,15 @@ module Kameleoon
354
240
  # @param [String] feature_key
355
241
  # @param [String] variation_key
356
242
  #
357
- # @raise [Kameleoon::Exception::FeatureConfigurationNotFound] Feature Flag isn't found in this configuration
358
- # @raise [Kameleoon::Exception::VariationConfigurationNotFound]
243
+ # @raise [Kameleoon::Exception::FeatureNotFound] Feature Flag isn't found in this configuration
244
+ # @raise [Kameleoon::Exception::FeatureVariationNotFound]
245
+ # @raise [Kameleoon::Exception::FeatureEnvironmentDisabled]
359
246
  #
360
- def get_feature_all_variables(feature_key, variation_key)
361
- feature_flag = find_feature_flag(feature_key)
247
+ def get_feature_variation_variables(feature_key, variation_key)
248
+ feature_flag = @data_file.get_feature_flag(feature_key)
362
249
  variation = feature_flag.get_variation_key(variation_key)
363
250
  if variation.nil?
364
- raise Exception::VariationConfigurationNotFound.new(variation_key),
251
+ raise Exception::FeatureVariationNotFound.new(variation_key),
365
252
  "Variation key #{variation_key} not found"
366
253
  end
367
254
  variables = {}
@@ -369,27 +256,6 @@ module Kameleoon
369
256
  variables
370
257
  end
371
258
 
372
- ##
373
- # Retrieve a feature variable.
374
- #
375
- # A feature variable can be changed easily via our web application.
376
- #
377
- # @param [String | Integer] feature_key
378
- # @param [String] variable_key
379
- #
380
- # @raise [Kameleoon::Exception::FeatureConfigurationNotFound] Feature Flag isn't found in this configuration
381
- # @raise [Kameleoon::Exception::FeatureVariableNotFound]
382
- #
383
- # DEPRECATED. Please use `get_feature_variable` instead.
384
- def obtain_feature_variable(feature_key, variable_key)
385
- warn '[DEPRECATION] `obtain_feature_variable` is deprecated. Please use `get_feature_variable` instead.'
386
- all_variables = get_feature_all_variables(
387
- feature_key,
388
- Configuration::VariationType::VARIATION_OFF
389
- )
390
- all_variables[variable_key]
391
- end
392
-
393
259
  ##
394
260
  # The get_remote_data method allows you to retrieve data (according to a key passed as argument)
395
261
  # stored on a remote Kameleoon server. Usually data will be stored on our remote
@@ -407,13 +273,6 @@ module Kameleoon
407
273
  JSON.parse(response) if response
408
274
  end
409
275
 
410
- ##
411
- # DEPRECATED. Please use `get_feature_variable` instead.
412
- def retrieve_data_from_remote_source(key, timeout = @default_timeout)
413
- warn '[DEPRECATION] `retrieve_data_from_remote_source` is deprecated. Please use `get_remote_date` instead.'
414
- get_remote_data(key, timeout)
415
- end
416
-
417
276
  ##
418
277
  # The get_remote_visitor_data is a method for retrieving custom data for
419
278
  # the latest visit of `visitor_code` from Kameleoon Data API and optionally adding it
@@ -434,55 +293,27 @@ module Kameleoon
434
293
  data_array
435
294
  end
436
295
 
437
- ##
438
- # Returns a list of all experiment ids
439
- #
440
- # @return [Array] array of all experiment ids
441
- def get_experiment_list # rubocop:disable Naming/AccessorMethodName
442
- @experiments.map { |it| it.id.to_i }
443
- end
444
-
445
- ##
446
- # Returns a list of all experiment ids targeted for a visitor
447
- # if only_allocated is `true` returns a list of allocated experiments for a visitor
448
- #
449
- # @raise [Kameleoon::Exception::VisitorCodeNotValid] If the visitor code is empty or longer than 255 chars
450
- #
451
- # @return [Array] array of all experiment ids accorging to a only_allocated parameter
452
- def get_experiment_list_for_visitor(visitor_code, only_allocated: true)
453
- list_ids = []
454
- @experiments.each do |experiment|
455
- next unless check_targeting(visitor_code, experiment.id.to_i, experiment)
456
- next if only_allocated && calculate_variation_for_experiment(visitor_code, experiment).nil?
457
-
458
- list_ids.push(experiment.id.to_i)
459
- end
460
- list_ids
461
- end
462
-
463
296
  ##
464
297
  # Returns a list of all feature flag keys
465
298
  #
466
299
  # @return [Array] array of all feature flag keys
467
300
  def get_feature_list # rubocop:disable Naming/AccessorMethodName
468
- @feature_flags.map(&:feature_key)
301
+ @data_file.feature_flags.keys
469
302
  end
470
303
 
471
304
  ##
472
305
  # Returns a list of active feature flag keys for a visitor
473
306
  #
474
- # @raise [Kameleoon::Exception::VisitorCodeNotValid] If the visitor code is empty or longer than 255 chars
307
+ # @raise [Kameleoon::Exception::VisitorCodeInvalid] If the visitor code is empty or longer than 255 chars
475
308
  #
476
309
  # @return [Array] array of active feature flag keys for a visitor
477
310
  def get_active_feature_list_for_visitor(visitor_code)
478
- check_visitor_code(visitor_code)
311
+ Utils::VisitorCode.validate(visitor_code)
479
312
  list_keys = []
480
- @feature_flags.each do |feature_flag|
313
+ @data_file.feature_flags.each do |feature_key, feature_flag|
481
314
  variation, rule, = _calculate_variation_key_for_feature(visitor_code, feature_flag)
482
315
  variation_key = _get_variation_key(variation, rule, feature_flag)
483
- if variation_key != Kameleoon::Configuration::VariationType::VARIATION_OFF
484
- list_keys.push(feature_flag.feature_key)
485
- end
316
+ list_keys.push(feature_key) if variation_key != Kameleoon::Configuration::VariationType::VARIATION_OFF
486
317
  end
487
318
  list_keys
488
319
  end
@@ -507,44 +338,49 @@ module Kameleoon
507
338
  # @return [String] JavasScript code to be inserted in your page to send automatically
508
339
  # the exposure events to the analytics solution you are using.
509
340
  def get_engine_tracking_code(visitor_code)
510
- @hybrid_manager.get_engine_tracking_code(visitor_code)
341
+ visitor_variations = @visitor_manager.get_visitor(visitor_code)&.variations
342
+ @hybrid_manager.get_engine_tracking_code(visitor_variations)
511
343
  end
512
344
 
513
345
  private
514
346
 
515
- REFERENCE = 0
516
- DEFAULT_ENVIRONMENT = 'production'
517
- CACHE_EXPIRATION_TIMEOUT = 5
518
- attr :site_code, :client_id, :client_secret, :access_token, :experiments, :feature_flags, :scheduler, :data,
519
- :tracking_url, :default_timeout, :interval, :memory_limit, :verbose_mode
347
+ HYBRID_EXPIRATION_TIME = 5
520
348
 
521
- def fetch_configuration
522
- Rufus::Scheduler.singleton.in '0s' do
523
- log('Initial configuration fetch is started.')
524
- fetch_configuration_job
349
+ def fetch_configuration_initially
350
+ log('Initial configuration fetch is started.')
351
+ Thread.new do
352
+ ok = false
353
+ begin
354
+ ok = obtain_configuration
355
+ log('Initial configuration fetch failed') unless ok
356
+ rescue StandardError => e
357
+ log("Initial configuration fetch failed: #{e}")
358
+ end
359
+ @readiness.set(ok)
360
+ manage_configuration_update(@data_file.settings.real_time_update) if ok
525
361
  end
526
362
  end
527
363
 
528
364
  def fetch_configuration_job(time_stamp = nil)
529
- EM.synchrony do
365
+ Thread.new do
366
+ ok = false
530
367
  begin
531
368
  ok = obtain_configuration(time_stamp)
532
- if !ok && @settings.real_time_update
533
- @settings.real_time_update = false
534
- log('Switching to polling mode due to failed fetch')
535
- end
536
369
  rescue StandardError => e
537
370
  log("Error occurred during configuration fetching: #{e}")
538
371
  end
539
- manage_configuration_update(@settings.real_time_update)
540
- EM.stop
372
+ if !ok && @data_file.settings.real_time_update
373
+ @data_file.settings.real_time_update = false
374
+ log('Switching to polling mode due to failed fetch')
375
+ end
376
+ manage_configuration_update(@data_file.settings.real_time_update)
541
377
  end
542
378
  end
543
379
 
544
380
  def start_configuration_update_job_if_needed
545
381
  return unless @fetch_configuration_update_job.nil?
546
382
 
547
- @fetch_configuration_update_job = Rufus::Scheduler.singleton.schedule_every @interval do
383
+ @fetch_configuration_update_job = Rufus::Scheduler.singleton.schedule_every @config.refresh_interval_second do
548
384
  log('Scheduled job to fetch configuration is started.')
549
385
  fetch_configuration_job
550
386
  end
@@ -604,12 +440,10 @@ module Kameleoon
604
440
  return false unless response
605
441
 
606
442
  configuration = JSON.parse(response)
607
- @experiments = Kameleoon::Configuration::Experiment.create_from_array(configuration['experiments']) ||
608
- @experiments
609
- @feature_flags = Kameleoon::Configuration::FeatureFlag.create_from_array(
610
- configuration['featureFlagConfigurations']
611
- )
612
- @settings.update(configuration['configuration'])
443
+ @data_file = Configuration::DataFile.new(@config.environment).init(configuration)
444
+ @cookie_manager.consent_required =
445
+ @data_file.settings.is_consent_required && !@data_file.has_any_targeted_delivery_rule
446
+
613
447
  call_update_handler_if_needed(!time_stamp.nil?)
614
448
  log "Feature flags are fetched: #{response.inspect}"
615
449
  true
@@ -625,49 +459,16 @@ module Kameleoon
625
459
  @update_configuration_handler.call
626
460
  end
627
461
 
628
- def calculate_variation_for_experiment(visitor_code, experiment)
629
- threshold = obtain_hash_double(visitor_code, experiment.respool_time, experiment.id)
630
- experiment.deviations.each do |key, value|
631
- threshold -= value
632
- return key.to_s.to_i if threshold.negative?
633
- end
634
- nil
635
- end
636
-
637
- def find_feature_flag(feature_key)
638
- if feature_key.is_a?(String)
639
- feature_flag = @feature_flags.select { |ff| ff.feature_key == feature_key }.first
640
- else
641
- raise TypeError.new('Feature key should be a String or an Integer.'),
642
- 'Feature key should be a String or an Integer.'
643
- end
644
- error_message = "Feature #{feature_key} not found"
645
- raise Exception::FeatureConfigurationNotFound.new(feature_key), error_message if feature_flag.nil?
646
-
647
- feature_flag
648
- end
649
-
650
- def check_site_code_enable(campaign)
651
- raise Exception::SiteCodeDisabled.new(site_code), site_code unless campaign.site_enabled
652
- end
653
-
654
- def data_not_sent(visitor_code)
655
- @data.key?(visitor_code) ? @data[visitor_code].reject(&:sent) : []
462
+ def dispose
463
+ stop_configuration_update_job_if_needed
464
+ stop_real_time_configuration_service_if_needed
465
+ @visitor_manager.stop
656
466
  end
657
467
 
658
468
  def log(text)
659
469
  print "Kameleoon SDK Log: #{text}\n" if @verbose_mode
660
470
  end
661
471
 
662
- def add_user_agent_data(visitor_code, user_agent)
663
- @user_agents[visitor_code] = user_agent
664
- end
665
-
666
- def set_user_agent_to_headers(visitor_code, headers)
667
- user_agent = @user_agents[visitor_code]
668
- headers['User-Agent'] = user_agent.value unless user_agent.nil?
669
- end
670
-
671
472
  # Uncomment when using storage
672
473
  # def get_valid_saved_variation(visitor_code, experiment)
673
474
  # variation_id = @variation_storage.get_variation_id(visitor_code, experiment.id.to_i)
@@ -681,29 +482,38 @@ module Kameleoon
681
482
 
682
483
  def check_targeting(visitor_code, campaign_id, exp_ff_rule)
683
484
  segment = exp_ff_rule.targeting_segment
684
- segment.nil? || segment.check_tree(->(type) { get_condition_data(type, visitor_code, campaign_id) })
485
+ return true if segment.nil?
486
+
487
+ visitor = @visitor_manager.get_visitor(visitor_code)
488
+ segment.check_tree(->(type) { get_condition_data(type, visitor, visitor_code, campaign_id) })
685
489
  end
686
490
 
687
- def get_condition_data(type, visitor_code, campaign_id)
491
+ def get_condition_data(type, visitor, visitor_code, campaign_id)
688
492
  condition_data = nil
689
493
  case type
690
- when Kameleoon::Targeting::ConditionType::CUSTOM_DATUM,
691
- Kameleoon::Targeting::ConditionType::PAGE_TITLE,
692
- Kameleoon::Targeting::ConditionType::PAGE_URL,
693
- Kameleoon::Targeting::ConditionType::DEVICE_TYPE,
694
- Kameleoon::Targeting::ConditionType::BROWSER,
695
- Kameleoon::Targeting::ConditionType::CONVERSIONS
696
- condition_data = (@data[visitor_code] || []).flatten
494
+ when Kameleoon::Targeting::ConditionType::CUSTOM_DATUM
495
+ condition_data = visitor.custom_data unless visitor.nil?
496
+ when Kameleoon::Targeting::ConditionType::PAGE_TITLE,
497
+ Kameleoon::Targeting::ConditionType::PAGE_URL
498
+ condition_data = visitor.page_view_visits unless visitor.nil?
499
+ when Kameleoon::Targeting::ConditionType::DEVICE_TYPE
500
+ condition_data = visitor.device unless visitor.nil?
501
+ when Kameleoon::Targeting::ConditionType::BROWSER
502
+ condition_data = visitor.browser unless visitor.nil?
503
+ when Kameleoon::Targeting::ConditionType::CONVERSIONS
504
+ condition_data = visitor.conversions unless visitor.nil?
697
505
  when Kameleoon::Targeting::ConditionType::SDK_LANGUAGE
698
506
  condition_data = Kameleoon::Targeting::SdkInfo.new(Kameleoon::SDK_NAME, Kameleoon::SDK_VERSION)
699
507
  when Kameleoon::Targeting::ConditionType::VISITOR_CODE
700
508
  condition_data = visitor_code
701
509
  when Kameleoon::Targeting::ConditionType::TARGET_EXPERIMENT
702
- condition_data = @variation_storage.get_hash_saved_variation_id(visitor_code)
510
+ condition_data = visitor.variations unless visitor.nil?
703
511
  when Kameleoon::Targeting::ConditionType::EXCLUSIVE_EXPERIMENT
704
- condition_data = OpenStruct.new
705
- condition_data.experiment_id = campaign_id
706
- condition_data.storage = @variation_storage.get_hash_saved_variation_id(visitor_code)
512
+ unless visitor.nil?
513
+ condition_data = OpenStruct.new
514
+ condition_data.experiment_id = campaign_id
515
+ condition_data.storage = visitor.variations
516
+ end
707
517
  end
708
518
  condition_data
709
519
  end
@@ -711,16 +521,21 @@ module Kameleoon
711
521
  ##
712
522
  # helper method for getting variation key for feature flag
713
523
  def _get_feature_variation_key(visitor_code, feature_key)
714
- check_visitor_code(visitor_code)
715
- feature_flag = find_feature_flag(feature_key)
524
+ Utils::VisitorCode.validate(visitor_code)
525
+ feature_flag = @data_file.get_feature_flag(feature_key)
716
526
  variation, rule = _calculate_variation_key_for_feature(visitor_code, feature_flag)
717
527
  variation_key = _get_variation_key(variation, rule, feature_flag)
528
+ visitor = nil
718
529
  unless rule.nil?
719
530
  experiment_id = rule.experiment_id
720
531
  variation_id = variation.variation_id unless variation.nil?
721
- save_variation(visitor_code, experiment_id, variation_id)
532
+ visitor = @visitor_manager.get_or_create_visitor(visitor_code)
533
+ unless experiment_id.nil? || variation_id.nil?
534
+ as_variation = Kameleoon::DataManager::AssignedVariation.new(experiment_id, variation_id, rule.type)
535
+ visitor.assign_variation(as_variation)
536
+ end
722
537
  end
723
- _send_tracking_request(visitor_code, experiment_id, variation_id)
538
+ _send_tracking_request(visitor_code, visitor)
724
539
  [feature_flag, variation_key]
725
540
  end
726
541
 
@@ -733,13 +548,13 @@ module Kameleoon
733
548
  next unless check_targeting(visitor_code, feature_flag.id, rule)
734
549
 
735
550
  # uses for rule exposition
736
- hash_rule = obtain_hash_double_rule(visitor_code, rule.id, rule.respool_time)
551
+ hash_rule = Utils::HashDouble.obtain_rule(visitor_code, rule.id, rule.respool_time)
737
552
  # check main expostion for rule with hashRule
738
553
  if hash_rule <= rule.exposition
739
554
  return [rule.variation_by_exposition[0], rule] if rule.targeted_delivery_type?
740
555
 
741
556
  # uses for variation's expositions
742
- hash_variation = obtain_hash_double_rule(visitor_code, rule.experiment_id, rule.respool_time)
557
+ hash_variation = Utils::HashDouble.obtain_rule(visitor_code, rule.experiment_id, rule.respool_time)
743
558
  # get variation key with new hashVariation
744
559
  variation = rule.get_variation(hash_variation)
745
560
  return [variation, rule] unless variation.nil?
@@ -751,16 +566,8 @@ module Kameleoon
751
566
  [nil, nil]
752
567
  end
753
568
 
754
- def save_variation(visitor_code, experiment_id, variation_id)
755
- return if experiment_id.nil? || variation_id.nil?
756
-
757
- @variation_storage.update_variation(visitor_code, experiment_id, variation_id)
758
- @hybrid_manager.add_variation(visitor_code, experiment_id, variation_id)
759
- end
760
-
761
569
  def _get_variation_key(var_by_exp, rule, feature_flag)
762
570
  return var_by_exp.variation_key unless var_by_exp.nil?
763
-
764
571
  return Kameleoon::Configuration::VariationType::VARIATION_OFF if !rule.nil? && rule.experimentation_type?
765
572
 
766
573
  feature_flag.default_variation_key
@@ -768,16 +575,37 @@ module Kameleoon
768
575
 
769
576
  ##
770
577
  # helper method for sending tracking requests for new FF
771
- def _send_tracking_request(visitor_code, experiment_id = nil, variation_id = nil)
772
- user_agent = @user_agents[visitor_code]&.value
773
- data_not_sent = data_not_sent(visitor_code)
774
- if experiment_id.nil? || variation_id.nil?
775
- data_not_sent.append(Network::ActivityEvent.new) if data_not_sent.empty?
578
+ def _send_tracking_request(visitor_code, visitor = nil, force_request = true)
579
+ if visitor.nil?
580
+ visitor = @visitor_manager.get_visitor(visitor_code)
581
+ return if visitor.nil? && @data_file.settings.is_consent_required
582
+ end
583
+ consent = consent_provided?(visitor)
584
+ user_agent = visitor&.user_agent
585
+ unsent = visitor.nil? ? [] : select_unsent_data(visitor, consent)
586
+ if unsent.empty?
587
+ return unless force_request && consent
588
+
589
+ unsent.push(Network::ActivityEvent.new)
590
+ end
591
+ log "Start post tracking: #{unsent.inspect}"
592
+ @network_manager.send_tracking_data(visitor_code, unsent, user_agent)
593
+ end
594
+
595
+ def select_unsent_data(visitor, consent)
596
+ unsent = []
597
+ if consent
598
+ visitor.enumerate_sendable_data { |data| unsent.push(data) unless data.sent }
776
599
  else
777
- data_not_sent.append(Network::ExperimentEvent.new(experiment_id, variation_id))
600
+ visitor.conversions.enumerate { |conversion| unsent.push(conversion) unless conversion.sent }
601
+ if @data_file.has_any_targeted_delivery_rule
602
+ visitor.variations.enumerate do |variation|
603
+ unsent.push(variation) unless
604
+ variation.sent || (variation.rule_type != Configuration::RuleType::TARGETED_DELIVERY)
605
+ end
606
+ end
778
607
  end
779
- log "Start post tracking: #{data_not_sent.inspect}"
780
- @network_manager.send_tracking_data(visitor_code, data_not_sent, user_agent)
608
+ unsent
781
609
  end
782
610
 
783
611
  ##
@@ -816,5 +644,9 @@ module Kameleoon
816
644
  log("Parsing of visitor data of '#{visitor_code}' failed: #{e}")
817
645
  []
818
646
  end
647
+
648
+ def consent_provided?(visitor)
649
+ !@data_file.settings.is_consent_required || visitor&.legal_consent
650
+ end
819
651
  end
820
652
  end