kameleoon-client-ruby 2.2.0 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
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