kameleoon-client-ruby 2.3.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 (52) 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} +164 -336
  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 -10
  46. data/lib/kameleoon/client_config.rb +0 -44
  47. data/lib/kameleoon/configuration/experiment.rb +0 -42
  48. data/lib/kameleoon/cookie.rb +0 -88
  49. data/lib/kameleoon/factory.rb +0 -43
  50. data/lib/kameleoon/network/experiment_event.rb +0 -35
  51. data/lib/kameleoon/storage/variation_storage.rb +0 -42
  52. 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,37 +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
39
  def initialize(site_code, config)
40
+ raise Exception::SiteCodeIsEmpty, 'Provided site_sode is empty' if site_code&.empty? != false
41
+
41
42
  @site_code = site_code
42
- read_config(config)
43
+ @config = config
43
44
  @real_time_configuration_service = nil
44
45
  @update_configuration_handler = nil
45
46
  @fetch_configuration_update_job = nil
46
- @settings = Kameleoon::Configuration::Settings.new
47
- @experiments = []
48
- @feature_flags = []
49
- @data = {}
50
- @user_agents = {}
51
- @variation_storage = Kameleoon::Storage::VariationStorage.new
52
- @hybrid_manager = Kameleoon::Hybrid::ManagerImpl.new(
53
- CACHE_EXPIRATION_TIMEOUT,
54
- CACHE_EXPIRATION_TIMEOUT * 3,
55
- Kameleoon::Storage::CacheFactoryImpl.new,
56
- method(:log)
57
- )
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))
58
50
  @network_manager = Network::NetworkManager.new(
59
- @environment,
60
- @default_timeout,
61
- 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),
62
54
  method(:log)
63
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
64
62
  end
65
63
 
66
64
  ##
@@ -72,8 +70,8 @@ module Kameleoon
72
70
  # @note The implementation logic is described here:
73
71
  # First we check if a kameleoonVisitorCode cookie or query parameter associated with the current HTTP request can be
74
72
  # found. If so, we will use this as the visitor identifier. If no cookie / parameter is found in the current
75
- # request, we either randomly generate a new identifier, or use the defaultVisitorCode argument as identifier if it
76
- # 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.
77
75
  # This can have the added benefit of matching Kameleoon visitors with their own users without any additional
78
76
  # look-ups in a matching table.
79
77
  # In any case, the server-side (via HTTP header) kameleoonVisitorCode cookie is set with the value. Then this
@@ -89,61 +87,15 @@ module Kameleoon
89
87
  # cookies = {'kameleoonVisitorCode' => '1234asdf4321fdsa'}
90
88
  # visitor_code = get_visitor_code(cookies, 'my-domaine.com')
91
89
  #
92
- def get_visitor_code(cookies, top_level_domain, default_visitor_code = nil)
93
- read_and_write(cookies, top_level_domain, cookies, default_visitor_code)
94
- end
95
-
96
- # DEPRECATED. Please use `get_visitor_code` instead.
97
- def obtain_visitor_code(cookies, top_level_domain, default_visitor_code = nil)
98
- warn '[DEPRECATION] `obtain_visitor_code` is deprecated. Please use `get_visitor_code` instead.'
99
- get_visitor_code(cookies, top_level_domain, default_visitor_code)
90
+ def get_visitor_code(cookies, default_visitor_code = nil)
91
+ @cookie_manager.get_or_add(cookies, default_visitor_code)
100
92
  end
101
93
 
102
- ##
103
- # Trigger an experiment.
104
- #
105
- # If such a visitor_code has never been associated with any variation, the SDK returns a randomly selected variation
106
- # If a user with a given visitor_code is already registered with a variation, it will detect the previously
107
- # registered variation and return the variation_id.
108
- # You have to make sure that proper error handling is set up in your code as shown in the example to the right to
109
- # catch potential exceptions.
110
- #
111
- # @param [String] visitor_code Visitor code
112
- # @param [Integer] experiment_id Id of the experiment you want to trigger.
113
- #
114
- # @return [Integer] Id of the variation
115
- #
116
- # @raise [Kameleoon::Exception::ExperimentConfigurationNotFound] Raise when experiment configuration is not found
117
- # @raise [Kameleoon::Exception::NotAllocated] The visitor triggered the experiment, but did not activate it.
118
- # Usually, this happens because the user has been associated with excluded traffic
119
- # @raise [Kameleoon::Exception::NotTargeted] The visitor is not targeted by the experiment, as the
120
- # associated targeting segment conditions were not fulfilled. He should see the reference variation
121
- # @raise [Kameleoon::Exception::VisitorCodeNotValid] If the visitor code is empty or longer than 255 chars
122
- #
123
- def trigger_experiment(visitor_code, experiment_id)
124
- check_visitor_code(visitor_code)
125
- experiment = @experiments.find { |exp| exp.id.to_s == experiment_id.to_s }
126
- if experiment.nil?
127
- raise Exception::ExperimentConfigurationNotFound.new(experiment_id),
128
- "Experiment #{experiment_id} is not found"
129
- end
130
- check_site_code_enable(experiment)
131
- targeted = check_targeting(visitor_code, experiment_id, experiment)
132
- if targeted
133
- # saved_variation = get_valid_saved_variation(visitor_code, experiment)
134
- variation_id = calculate_variation_for_experiment(visitor_code, experiment)
135
- save_variation(visitor_code, experiment_id, variation_id)
136
- end
137
- _send_tracking_request(visitor_code, experiment_id, variation_id)
138
- unless targeted
139
- raise Exception::NotTargeted.new(visitor_code),
140
- "Experiment #{experiment_id} is not targeted for visitor #{visitor_code}"
141
- end
142
- if variation_id.nil?
143
- raise Exception::NotAllocated.new(visitor_code),
144
- "Experiment #{experiment_id} is not active for visitor #{visitor_code}"
145
- end
146
- 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)
147
99
  end
148
100
 
149
101
  ##
@@ -157,19 +109,12 @@ module Kameleoon
157
109
  # @param [String] visitor_code Visitor code
158
110
  # @param [...Data] data Data to associate with the visitor code
159
111
  #
160
- # @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
161
113
  #
162
114
  def add_data(visitor_code, *args)
163
- check_visitor_code(visitor_code)
164
- @data.shift while ObjectSpace.memsize_of(@data) > @data_maximum_size * (2**20)
165
- args.each do |data_element|
166
- if data_element.is_a?(UserAgent)
167
- add_user_agent_data(visitor_code, data_element)
168
- next
169
- end
170
- @data[visitor_code] = [] unless @data.key?(visitor_code)
171
- @data[visitor_code].push(data_element)
172
- end
115
+ Utils::VisitorCode.validate(visitor_code)
116
+ visitor = @visitor_manager.get_or_create_visitor(visitor_code)
117
+ visitor.add_data(method(:log), *args)
173
118
  end
174
119
 
175
120
  ##
@@ -185,10 +130,10 @@ module Kameleoon
185
130
  # @param [Integer] goal_id Id of the goal
186
131
  # @param [Float] revenue Optional - Revenue of the conversion.
187
132
  #
188
- # @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
189
134
  #
190
135
  def track_conversion(visitor_code, goal_id, revenue = 0.0)
191
- check_visitor_code(visitor_code)
136
+ Utils::VisitorCode.validate(visitor_code)
192
137
  add_data(visitor_code, Conversion.new(goal_id, revenue))
193
138
  flush(visitor_code)
194
139
  end
@@ -202,74 +147,17 @@ module Kameleoon
202
147
  #
203
148
  # @param [String] visitor_code Optional field - Visitor code, without visitor code it flush all of the data
204
149
  #
205
- # @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
206
151
  #
207
152
  def flush(visitor_code = nil)
208
- check_visitor_code(visitor_code) unless visitor_code.nil?
209
- if !visitor_code.nil?
210
- _send_tracking_request(visitor_code)
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) }
211
156
  else
212
- @data.select { |_, values| values.any? { |data| !data.sent } }.each_key { |key| flush(key) }
157
+ _send_tracking_request(visitor_code, nil, true)
213
158
  end
214
159
  end
215
160
 
216
- ##
217
- # Obtain variation associated data.
218
- #
219
- # To retrieve JSON data associated with a variation, call the get_variation_associated_data method of our SDK.
220
- # The JSON data usually represents some metadata of the variation, and can be configured on our web application
221
- # interface or via our Automation API.
222
- # This method takes the variationID as a parameter and will return the data as a json string.
223
- # It will throw an exception () if the variation ID is wrong or corresponds to an experiment that is not yet online.
224
- #
225
- # @param [Integer] variation_id
226
- #
227
- # @return [Hash] Hash object of the json object.
228
- #
229
- # @raise [Kameleoon::Exception::VariationNotFound] Raise exception if the variation is not found.
230
- #
231
- def get_variation_associated_data(variation_id)
232
- variation = @experiments.map(&:variations).flatten.select { |var| var['id'].to_i == variation_id.to_i }.first
233
- if variation.nil?
234
- raise Exception::VariationConfigurationNotFound.new(variation_id),
235
- "Variation key #{variation_id} not found"
236
- else
237
- JSON.parse(variation['customJson'])
238
- end
239
- end
240
-
241
- # DEPRECATED. Please use `get_variation_associated_data` instead.
242
- def obtain_variation_associated_data(variation_id)
243
- warn '[DEPRECATION] `obtain_variation_associated_data` is deprecated.
244
- Please use `get_variation_associated_data` instead.'
245
- get_variation_associated_data(variation_id)
246
- end
247
-
248
- # #
249
- # Activate a feature toggle.
250
-
251
- # This method takes a visitor_code and feature_key (or feature_id) as mandatory arguments to check if the specified
252
- # feature will be active for a given user.
253
- # If such a user has never been associated with this feature flag, the SDK returns a boolean value randomly
254
- # (true if the user should have this feature or false if not). If a user with a given visitorCode is already
255
- # registered with this feature flag, it will detect the previous featureFlag value.
256
- # You have to make sure that proper error handling is set up in your code as shown in the example
257
- # to the right to catch potential exceptions.
258
-
259
- # @param [String] visitor_code
260
- # @param [String | Integer] feature_key
261
-
262
- # @raise [Kameleoon::Exception::FeatureConfigurationNotFound] Feature Flag isn't found in this configuration
263
- # @raise [Kameleoon::Exception::NotTargeted] The visitor is not targeted by the experiment, as the
264
- # associated targeting segment conditions were not fulfilled. He should see the reference variation
265
- # @raise [Kameleoon::Exception::VisitorCodeNotValid] If the visitor code is empty or longer than 255 chars
266
- #
267
- # DEPRECATED. Please use `feature_active?` instead.
268
- def activate_feature(visitor_code, feature_key)
269
- warn '[DEPRECATION] `activate_feature` is deprecated. Please use `feature_active?` instead.'
270
- feature_active?(visitor_code, feature_key)
271
- end
272
-
273
161
  ##
274
162
  # Check if feature is active for a given visitor code
275
163
  #
@@ -284,12 +172,14 @@ module Kameleoon
284
172
  # @param [String] visitor_code Unique identifier of the user. This field is mandatory.
285
173
  # @param [String] feature_key Key of the feature flag you want to expose to a user. This field is mandatory.
286
174
  #
287
- # @raise [Kameleoon::Exception::FeatureConfigurationNotFound] Feature Flag isn't found in this configuration
288
- # @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
289
177
  #
290
178
  def feature_active?(visitor_code, feature_key)
291
179
  _, variation_key = _get_feature_variation_key(visitor_code, feature_key)
292
180
  variation_key != Kameleoon::Configuration::VariationType::VARIATION_OFF
181
+ rescue Exception::FeatureEnvironmentDisabled
182
+ false
293
183
  end
294
184
 
295
185
  #
@@ -304,8 +194,10 @@ module Kameleoon
304
194
  # @param [String] visitor_code
305
195
  # @param [String] feature_key
306
196
  #
307
- # @raise [Kameleoon::Exception::FeatureConfigurationNotFound] Feature Flag isn't found in this configuration
308
- # @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
309
201
  #
310
202
  def get_feature_variation_key(visitor_code, feature_key)
311
203
  _, variation_key = _get_feature_variation_key(visitor_code, feature_key)
@@ -321,9 +213,11 @@ module Kameleoon
321
213
  # @param [String] feature_key
322
214
  # @param [String] variable_name
323
215
  #
324
- # @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
325
217
  # @raise [Kameleoon::Exception::FeatureVariableNotFound]
326
- # @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
327
221
  #
328
222
  def get_feature_variable(visitor_code, feature_key, variable_name)
329
223
  feature_flag, variation_key = _get_feature_variation_key(visitor_code, feature_key)
@@ -346,14 +240,15 @@ module Kameleoon
346
240
  # @param [String] feature_key
347
241
  # @param [String] variation_key
348
242
  #
349
- # @raise [Kameleoon::Exception::FeatureConfigurationNotFound] Feature Flag isn't found in this configuration
350
- # @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]
351
246
  #
352
- def get_feature_all_variables(feature_key, variation_key)
353
- 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)
354
249
  variation = feature_flag.get_variation_key(variation_key)
355
250
  if variation.nil?
356
- raise Exception::VariationConfigurationNotFound.new(variation_key),
251
+ raise Exception::FeatureVariationNotFound.new(variation_key),
357
252
  "Variation key #{variation_key} not found"
358
253
  end
359
254
  variables = {}
@@ -361,27 +256,6 @@ module Kameleoon
361
256
  variables
362
257
  end
363
258
 
364
- ##
365
- # Retrieve a feature variable.
366
- #
367
- # A feature variable can be changed easily via our web application.
368
- #
369
- # @param [String | Integer] feature_key
370
- # @param [String] variable_key
371
- #
372
- # @raise [Kameleoon::Exception::FeatureConfigurationNotFound] Feature Flag isn't found in this configuration
373
- # @raise [Kameleoon::Exception::FeatureVariableNotFound]
374
- #
375
- # DEPRECATED. Please use `get_feature_variable` instead.
376
- def obtain_feature_variable(feature_key, variable_key)
377
- warn '[DEPRECATION] `obtain_feature_variable` is deprecated. Please use `get_feature_variable` instead.'
378
- all_variables = get_feature_all_variables(
379
- feature_key,
380
- Configuration::VariationType::VARIATION_OFF
381
- )
382
- all_variables[variable_key]
383
- end
384
-
385
259
  ##
386
260
  # The get_remote_data method allows you to retrieve data (according to a key passed as argument)
387
261
  # stored on a remote Kameleoon server. Usually data will be stored on our remote
@@ -399,13 +273,6 @@ module Kameleoon
399
273
  JSON.parse(response) if response
400
274
  end
401
275
 
402
- ##
403
- # DEPRECATED. Please use `get_feature_variable` instead.
404
- def retrieve_data_from_remote_source(key, timeout = @default_timeout)
405
- warn '[DEPRECATION] `retrieve_data_from_remote_source` is deprecated. Please use `get_remote_date` instead.'
406
- get_remote_data(key, timeout)
407
- end
408
-
409
276
  ##
410
277
  # The get_remote_visitor_data is a method for retrieving custom data for
411
278
  # the latest visit of `visitor_code` from Kameleoon Data API and optionally adding it
@@ -426,55 +293,27 @@ module Kameleoon
426
293
  data_array
427
294
  end
428
295
 
429
- ##
430
- # Returns a list of all experiment ids
431
- #
432
- # @return [Array] array of all experiment ids
433
- def get_experiment_list # rubocop:disable Naming/AccessorMethodName
434
- @experiments.map { |it| it.id.to_i }
435
- end
436
-
437
- ##
438
- # Returns a list of all experiment ids targeted for a visitor
439
- # if only_allocated is `true` returns a list of allocated experiments for a visitor
440
- #
441
- # @raise [Kameleoon::Exception::VisitorCodeNotValid] If the visitor code is empty or longer than 255 chars
442
- #
443
- # @return [Array] array of all experiment ids accorging to a only_allocated parameter
444
- def get_experiment_list_for_visitor(visitor_code, only_allocated: true)
445
- list_ids = []
446
- @experiments.each do |experiment|
447
- next unless check_targeting(visitor_code, experiment.id.to_i, experiment)
448
- next if only_allocated && calculate_variation_for_experiment(visitor_code, experiment).nil?
449
-
450
- list_ids.push(experiment.id.to_i)
451
- end
452
- list_ids
453
- end
454
-
455
296
  ##
456
297
  # Returns a list of all feature flag keys
457
298
  #
458
299
  # @return [Array] array of all feature flag keys
459
300
  def get_feature_list # rubocop:disable Naming/AccessorMethodName
460
- @feature_flags.map(&:feature_key)
301
+ @data_file.feature_flags.keys
461
302
  end
462
303
 
463
304
  ##
464
305
  # Returns a list of active feature flag keys for a visitor
465
306
  #
466
- # @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
467
308
  #
468
309
  # @return [Array] array of active feature flag keys for a visitor
469
310
  def get_active_feature_list_for_visitor(visitor_code)
470
- check_visitor_code(visitor_code)
311
+ Utils::VisitorCode.validate(visitor_code)
471
312
  list_keys = []
472
- @feature_flags.each do |feature_flag|
313
+ @data_file.feature_flags.each do |feature_key, feature_flag|
473
314
  variation, rule, = _calculate_variation_key_for_feature(visitor_code, feature_flag)
474
315
  variation_key = _get_variation_key(variation, rule, feature_flag)
475
- if variation_key != Kameleoon::Configuration::VariationType::VARIATION_OFF
476
- list_keys.push(feature_flag.feature_key)
477
- end
316
+ list_keys.push(feature_key) if variation_key != Kameleoon::Configuration::VariationType::VARIATION_OFF
478
317
  end
479
318
  list_keys
480
319
  end
@@ -499,56 +338,49 @@ module Kameleoon
499
338
  # @return [String] JavasScript code to be inserted in your page to send automatically
500
339
  # the exposure events to the analytics solution you are using.
501
340
  def get_engine_tracking_code(visitor_code)
502
- @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)
503
343
  end
504
344
 
505
345
  private
506
346
 
507
- REFERENCE = 0
508
- CACHE_EXPIRATION_TIMEOUT = 5
509
- attr :site_code, :client_id, :client_secret, :access_token, :experiments, :feature_flags, :scheduler, :data,
510
- :tracking_url, :default_timeout, :interval, :memory_limit, :verbose_mode
511
-
512
- def read_config(config)
513
- @client_id = config.client_id
514
- @client_secret = config.client_secret
515
- @default_timeout = config.default_timeout # in ms
516
- @interval = "#{config.configuration_refresh_interval}m"
517
- @data_maximum_size = config.visitor_data_maximum_size
518
- @environment = config.environment
519
- @verbose_mode = config.verbose_mode
520
- if @client_id.nil? || @client_secret.nil?
521
- warn 'Kameleoon SDK: Credentials are invalid: client_id or client_secret (or both) are empty'
522
- end
523
- end
347
+ HYBRID_EXPIRATION_TIME = 5
524
348
 
525
- def fetch_configuration
526
- Rufus::Scheduler.singleton.in '0s' do
527
- log('Initial configuration fetch is started.')
528
- 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
529
361
  end
530
362
  end
531
363
 
532
364
  def fetch_configuration_job(time_stamp = nil)
533
- EM.synchrony do
365
+ Thread.new do
366
+ ok = false
534
367
  begin
535
368
  ok = obtain_configuration(time_stamp)
536
- if !ok && @settings.real_time_update
537
- @settings.real_time_update = false
538
- log('Switching to polling mode due to failed fetch')
539
- end
540
369
  rescue StandardError => e
541
370
  log("Error occurred during configuration fetching: #{e}")
542
371
  end
543
- manage_configuration_update(@settings.real_time_update)
544
- 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)
545
377
  end
546
378
  end
547
379
 
548
380
  def start_configuration_update_job_if_needed
549
381
  return unless @fetch_configuration_update_job.nil?
550
382
 
551
- @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
552
384
  log('Scheduled job to fetch configuration is started.')
553
385
  fetch_configuration_job
554
386
  end
@@ -608,12 +440,10 @@ module Kameleoon
608
440
  return false unless response
609
441
 
610
442
  configuration = JSON.parse(response)
611
- @experiments = Kameleoon::Configuration::Experiment.create_from_array(configuration['experiments']) ||
612
- @experiments
613
- @feature_flags = Kameleoon::Configuration::FeatureFlag.create_from_array(
614
- configuration['featureFlagConfigurations']
615
- )
616
- @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
+
617
447
  call_update_handler_if_needed(!time_stamp.nil?)
618
448
  log "Feature flags are fetched: #{response.inspect}"
619
449
  true
@@ -629,49 +459,16 @@ module Kameleoon
629
459
  @update_configuration_handler.call
630
460
  end
631
461
 
632
- def calculate_variation_for_experiment(visitor_code, experiment)
633
- threshold = obtain_hash_double(visitor_code, experiment.respool_time, experiment.id)
634
- experiment.deviations.each do |key, value|
635
- threshold -= value
636
- return key.to_s.to_i if threshold.negative?
637
- end
638
- nil
639
- end
640
-
641
- def find_feature_flag(feature_key)
642
- if feature_key.is_a?(String)
643
- feature_flag = @feature_flags.select { |ff| ff.feature_key == feature_key }.first
644
- else
645
- raise TypeError.new('Feature key should be a String or an Integer.'),
646
- 'Feature key should be a String or an Integer.'
647
- end
648
- error_message = "Feature #{feature_key} not found"
649
- raise Exception::FeatureConfigurationNotFound.new(feature_key), error_message if feature_flag.nil?
650
-
651
- feature_flag
652
- end
653
-
654
- def check_site_code_enable(campaign)
655
- raise Exception::SiteCodeDisabled.new(site_code), site_code unless campaign.site_enabled
656
- end
657
-
658
- def data_not_sent(visitor_code)
659
- @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
660
466
  end
661
467
 
662
468
  def log(text)
663
469
  print "Kameleoon SDK Log: #{text}\n" if @verbose_mode
664
470
  end
665
471
 
666
- def add_user_agent_data(visitor_code, user_agent)
667
- @user_agents[visitor_code] = user_agent
668
- end
669
-
670
- def set_user_agent_to_headers(visitor_code, headers)
671
- user_agent = @user_agents[visitor_code]
672
- headers['User-Agent'] = user_agent.value unless user_agent.nil?
673
- end
674
-
675
472
  # Uncomment when using storage
676
473
  # def get_valid_saved_variation(visitor_code, experiment)
677
474
  # variation_id = @variation_storage.get_variation_id(visitor_code, experiment.id.to_i)
@@ -685,29 +482,38 @@ module Kameleoon
685
482
 
686
483
  def check_targeting(visitor_code, campaign_id, exp_ff_rule)
687
484
  segment = exp_ff_rule.targeting_segment
688
- 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) })
689
489
  end
690
490
 
691
- def get_condition_data(type, visitor_code, campaign_id)
491
+ def get_condition_data(type, visitor, visitor_code, campaign_id)
692
492
  condition_data = nil
693
493
  case type
694
- when Kameleoon::Targeting::ConditionType::CUSTOM_DATUM,
695
- Kameleoon::Targeting::ConditionType::PAGE_TITLE,
696
- Kameleoon::Targeting::ConditionType::PAGE_URL,
697
- Kameleoon::Targeting::ConditionType::DEVICE_TYPE,
698
- Kameleoon::Targeting::ConditionType::BROWSER,
699
- Kameleoon::Targeting::ConditionType::CONVERSIONS
700
- 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?
701
505
  when Kameleoon::Targeting::ConditionType::SDK_LANGUAGE
702
506
  condition_data = Kameleoon::Targeting::SdkInfo.new(Kameleoon::SDK_NAME, Kameleoon::SDK_VERSION)
703
507
  when Kameleoon::Targeting::ConditionType::VISITOR_CODE
704
508
  condition_data = visitor_code
705
509
  when Kameleoon::Targeting::ConditionType::TARGET_EXPERIMENT
706
- condition_data = @variation_storage.get_hash_saved_variation_id(visitor_code)
510
+ condition_data = visitor.variations unless visitor.nil?
707
511
  when Kameleoon::Targeting::ConditionType::EXCLUSIVE_EXPERIMENT
708
- condition_data = OpenStruct.new
709
- condition_data.experiment_id = campaign_id
710
- 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
711
517
  end
712
518
  condition_data
713
519
  end
@@ -715,16 +521,21 @@ module Kameleoon
715
521
  ##
716
522
  # helper method for getting variation key for feature flag
717
523
  def _get_feature_variation_key(visitor_code, feature_key)
718
- check_visitor_code(visitor_code)
719
- feature_flag = find_feature_flag(feature_key)
524
+ Utils::VisitorCode.validate(visitor_code)
525
+ feature_flag = @data_file.get_feature_flag(feature_key)
720
526
  variation, rule = _calculate_variation_key_for_feature(visitor_code, feature_flag)
721
527
  variation_key = _get_variation_key(variation, rule, feature_flag)
528
+ visitor = nil
722
529
  unless rule.nil?
723
530
  experiment_id = rule.experiment_id
724
531
  variation_id = variation.variation_id unless variation.nil?
725
- 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
726
537
  end
727
- _send_tracking_request(visitor_code, experiment_id, variation_id)
538
+ _send_tracking_request(visitor_code, visitor)
728
539
  [feature_flag, variation_key]
729
540
  end
730
541
 
@@ -737,13 +548,13 @@ module Kameleoon
737
548
  next unless check_targeting(visitor_code, feature_flag.id, rule)
738
549
 
739
550
  # uses for rule exposition
740
- 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)
741
552
  # check main expostion for rule with hashRule
742
553
  if hash_rule <= rule.exposition
743
554
  return [rule.variation_by_exposition[0], rule] if rule.targeted_delivery_type?
744
555
 
745
556
  # uses for variation's expositions
746
- 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)
747
558
  # get variation key with new hashVariation
748
559
  variation = rule.get_variation(hash_variation)
749
560
  return [variation, rule] unless variation.nil?
@@ -755,16 +566,8 @@ module Kameleoon
755
566
  [nil, nil]
756
567
  end
757
568
 
758
- def save_variation(visitor_code, experiment_id, variation_id)
759
- return if experiment_id.nil? || variation_id.nil?
760
-
761
- @variation_storage.update_variation(visitor_code, experiment_id, variation_id)
762
- @hybrid_manager.add_variation(visitor_code, experiment_id, variation_id)
763
- end
764
-
765
569
  def _get_variation_key(var_by_exp, rule, feature_flag)
766
570
  return var_by_exp.variation_key unless var_by_exp.nil?
767
-
768
571
  return Kameleoon::Configuration::VariationType::VARIATION_OFF if !rule.nil? && rule.experimentation_type?
769
572
 
770
573
  feature_flag.default_variation_key
@@ -772,16 +575,37 @@ module Kameleoon
772
575
 
773
576
  ##
774
577
  # helper method for sending tracking requests for new FF
775
- def _send_tracking_request(visitor_code, experiment_id = nil, variation_id = nil)
776
- user_agent = @user_agents[visitor_code]&.value
777
- data_not_sent = data_not_sent(visitor_code)
778
- if experiment_id.nil? || variation_id.nil?
779
- 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 }
780
599
  else
781
- 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
782
607
  end
783
- log "Start post tracking: #{data_not_sent.inspect}"
784
- @network_manager.send_tracking_data(visitor_code, data_not_sent, user_agent)
608
+ unsent
785
609
  end
786
610
 
787
611
  ##
@@ -820,5 +644,9 @@ module Kameleoon
820
644
  log("Parsing of visitor data of '#{visitor_code}' failed: #{e}")
821
645
  []
822
646
  end
647
+
648
+ def consent_provided?(visitor)
649
+ !@data_file.settings.is_consent_required || visitor&.legal_consent
650
+ end
823
651
  end
824
652
  end