kameleoon-client-ruby 2.3.0 → 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) 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 +5 -0
  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} +194 -334
  24. data/lib/kameleoon/kameleoon_client_config.rb +91 -0
  25. data/lib/kameleoon/kameleoon_client_factory.rb +42 -0
  26. data/lib/kameleoon/managers/warehouse/warehouse_manager.rb +33 -0
  27. data/lib/kameleoon/network/access_token_source.rb +109 -0
  28. data/lib/kameleoon/network/activity_event.rb +6 -3
  29. data/lib/kameleoon/network/cookie/cookie_manager.rb +84 -0
  30. data/lib/kameleoon/network/net_provider.rb +25 -58
  31. data/lib/kameleoon/network/network_manager.rb +65 -43
  32. data/lib/kameleoon/network/request.rb +7 -2
  33. data/lib/kameleoon/network/response.rb +0 -8
  34. data/lib/kameleoon/network/url_provider.rb +30 -12
  35. data/lib/kameleoon/real_time/sse_client.rb +2 -0
  36. data/lib/kameleoon/targeting/conditions/browser_condition.rb +2 -3
  37. data/lib/kameleoon/targeting/conditions/conversion_condition.rb +12 -4
  38. data/lib/kameleoon/targeting/conditions/custom_datum.rb +19 -13
  39. data/lib/kameleoon/targeting/conditions/device_condition.rb +3 -4
  40. data/lib/kameleoon/targeting/conditions/exclusive_experiment.rb +2 -1
  41. data/lib/kameleoon/targeting/conditions/page_title_condition.rb +11 -4
  42. data/lib/kameleoon/targeting/conditions/page_url_condition.rb +18 -4
  43. data/lib/kameleoon/targeting/conditions/string_value_condition.rb +2 -0
  44. data/lib/kameleoon/targeting/conditions/target_experiment.rb +11 -6
  45. data/lib/kameleoon/utils.rb +41 -4
  46. data/lib/kameleoon/version.rb +1 -1
  47. data/lib/kameleoon.rb +4 -2
  48. metadata +16 -10
  49. data/lib/kameleoon/client_config.rb +0 -44
  50. data/lib/kameleoon/configuration/experiment.rb +0 -42
  51. data/lib/kameleoon/cookie.rb +0 -88
  52. data/lib/kameleoon/factory.rb +0 -43
  53. data/lib/kameleoon/network/experiment_event.rb +0 -35
  54. data/lib/kameleoon/storage/variation_storage.rb +0 -42
  55. data/lib/kameleoon/storage/visitor_variation.rb +0 -20
@@ -1,27 +1,27 @@
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'
13
+ require 'kameleoon/network/access_token_source'
11
14
  require 'kameleoon/network/activity_event'
12
- require 'kameleoon/network/experiment_event'
13
- require 'kameleoon/network/url_provider'
14
15
  require 'kameleoon/network/network_manager'
16
+ require 'kameleoon/network/url_provider'
17
+ require 'kameleoon/network/cookie/cookie_manager'
18
+ require 'kameleoon/managers/warehouse/warehouse_manager'
15
19
  require 'kameleoon/real_time/real_time_configuration_service'
16
- require 'kameleoon/storage/variation_storage'
17
20
  require 'kameleoon/hybrid/manager'
18
21
  require 'kameleoon/storage/cache_factory'
19
22
  require 'rufus/scheduler'
20
23
  require 'yaml'
21
24
  require 'json'
22
- require 'em-synchrony'
23
- require 'em-synchrony/em-http'
24
- require 'em-synchrony/fiber_iterator'
25
25
  require 'objspace'
26
26
  require 'time'
27
27
  require 'ostruct'
@@ -30,37 +30,39 @@ module Kameleoon
30
30
  ##
31
31
  # Client for Kameleoon
32
32
  #
33
- class Client
34
- include Cookie
33
+ class KameleoonClient
35
34
  include Exception
36
35
 
36
+ attr_reader :site_code
37
+
37
38
  ##
38
- # You should create Client with the Client Factory only.
39
+ # You should create KameleoonClient with the Client Factory only.
39
40
  #
40
41
  def initialize(site_code, config)
42
+ raise Exception::SiteCodeIsEmpty, 'Provided site_sode is empty' if site_code&.empty? != false
43
+
41
44
  @site_code = site_code
42
- read_config(config)
45
+ @config = config
43
46
  @real_time_configuration_service = nil
44
47
  @update_configuration_handler = nil
45
48
  @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
- )
49
+ @data_file = Configuration::DataFile.new(config.environment)
50
+ @visitor_manager = Kameleoon::DataManager::VisitorManager.new(config.session_duration_second)
51
+ @hybrid_manager = Kameleoon::Hybrid::ManagerImpl.new(HYBRID_EXPIRATION_TIME, method(:log))
58
52
  @network_manager = Network::NetworkManager.new(
59
- @environment,
60
- @default_timeout,
61
- Network::UrlProvider.new(@site_code, Network::UrlProvider::DEFAULT_DATA_API_URL),
53
+ config.environment,
54
+ config.default_timeout_millisecond,
55
+ Network::AccessTokenSourceFactory.new(config.client_id, config.client_secret, method(:log)),
56
+ Network::UrlProvider.new(site_code),
62
57
  method(:log)
63
58
  )
59
+ @warehouse_manager = Managers::Warehouse::WarehouseManager.new(@network_manager, @visitor_manager, method(:log))
60
+ @cookie_manager = Network::Cookie::CookieManager.new(config.top_level_domain)
61
+ @readiness = ClientReadiness.new
62
+ end
63
+
64
+ def wait_init
65
+ @readiness.wait
64
66
  end
65
67
 
66
68
  ##
@@ -72,8 +74,8 @@ module Kameleoon
72
74
  # @note The implementation logic is described here:
73
75
  # First we check if a kameleoonVisitorCode cookie or query parameter associated with the current HTTP request can be
74
76
  # 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.
77
+ # request, we either randomly generate a new identifier, or use the default_visitor_code argument as identifier if
78
+ # it is passed. This allows our customers to use their own identifiers as visitor codes, should they wish to.
77
79
  # This can have the added benefit of matching Kameleoon visitors with their own users without any additional
78
80
  # look-ups in a matching table.
79
81
  # In any case, the server-side (via HTTP header) kameleoonVisitorCode cookie is set with the value. Then this
@@ -89,61 +91,15 @@ module Kameleoon
89
91
  # cookies = {'kameleoonVisitorCode' => '1234asdf4321fdsa'}
90
92
  # visitor_code = get_visitor_code(cookies, 'my-domaine.com')
91
93
  #
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)
94
+ def get_visitor_code(cookies, default_visitor_code = nil)
95
+ @cookie_manager.get_or_add(cookies, default_visitor_code)
100
96
  end
101
97
 
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
98
+ def set_legal_consent(visitor_code, consent, cookies = nil)
99
+ Utils::VisitorCode.validate(visitor_code)
100
+ visitor = @visitor_manager.get_or_create_visitor(visitor_code)
101
+ visitor.legal_consent = consent
102
+ @cookie_manager.update(visitor_code, consent, cookies)
147
103
  end
148
104
 
149
105
  ##
@@ -157,19 +113,12 @@ module Kameleoon
157
113
  # @param [String] visitor_code Visitor code
158
114
  # @param [...Data] data Data to associate with the visitor code
159
115
  #
160
- # @raise [Kameleoon::Exception::VisitorCodeNotValid] If the visitor code is empty or longer than 255 chars
116
+ # @raise [Kameleoon::Exception::VisitorCodeInvalid] If the visitor code is empty or longer than 255 chars
161
117
  #
162
118
  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
119
+ Utils::VisitorCode.validate(visitor_code)
120
+ visitor = @visitor_manager.get_or_create_visitor(visitor_code)
121
+ visitor.add_data(method(:log), *args)
173
122
  end
174
123
 
175
124
  ##
@@ -185,10 +134,10 @@ module Kameleoon
185
134
  # @param [Integer] goal_id Id of the goal
186
135
  # @param [Float] revenue Optional - Revenue of the conversion.
187
136
  #
188
- # @raise [Kameleoon::Exception::VisitorCodeNotValid] If the visitor code is empty or longer than 255 chars
137
+ # @raise [Kameleoon::Exception::VisitorCodeInvalid] If the visitor code is empty or longer than 255 chars
189
138
  #
190
139
  def track_conversion(visitor_code, goal_id, revenue = 0.0)
191
- check_visitor_code(visitor_code)
140
+ Utils::VisitorCode.validate(visitor_code)
192
141
  add_data(visitor_code, Conversion.new(goal_id, revenue))
193
142
  flush(visitor_code)
194
143
  end
@@ -202,74 +151,17 @@ module Kameleoon
202
151
  #
203
152
  # @param [String] visitor_code Optional field - Visitor code, without visitor code it flush all of the data
204
153
  #
205
- # @raise [Kameleoon::Exception::VisitorCodeNotValid] If the visitor code is not null and empty or longer than 255 chars
154
+ # @raise [Kameleoon::Exception::VisitorCodeInvalid] If the visitor code is not nil and is empty or longer than 255 chars
206
155
  #
207
156
  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)
157
+ Utils::VisitorCode.validate(visitor_code) unless visitor_code.nil?
158
+ if visitor_code.nil?
159
+ @visitor_manager.enumerate { |visitor_code, visitor| _send_tracking_request(visitor_code, visitor, false) }
211
160
  else
212
- @data.select { |_, values| values.any? { |data| !data.sent } }.each_key { |key| flush(key) }
161
+ _send_tracking_request(visitor_code, nil, true)
213
162
  end
214
163
  end
215
164
 
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
165
  ##
274
166
  # Check if feature is active for a given visitor code
275
167
  #
@@ -284,12 +176,14 @@ module Kameleoon
284
176
  # @param [String] visitor_code Unique identifier of the user. This field is mandatory.
285
177
  # @param [String] feature_key Key of the feature flag you want to expose to a user. This field is mandatory.
286
178
  #
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
179
+ # @raise [Kameleoon::Exception::FeatureNotFound] Feature Flag isn't found in this configuration
180
+ # @raise [Kameleoon::Exception::VisitorCodeInvalid] If the visitor code is empty or longer than 255 chars
289
181
  #
290
182
  def feature_active?(visitor_code, feature_key)
291
183
  _, variation_key = _get_feature_variation_key(visitor_code, feature_key)
292
184
  variation_key != Kameleoon::Configuration::VariationType::VARIATION_OFF
185
+ rescue Exception::FeatureEnvironmentDisabled
186
+ false
293
187
  end
294
188
 
295
189
  #
@@ -304,8 +198,10 @@ module Kameleoon
304
198
  # @param [String] visitor_code
305
199
  # @param [String] feature_key
306
200
  #
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
201
+ # @raise [Kameleoon::Exception::FeatureNotFound] Feature Flag isn't found in this configuration
202
+ # @raise [Kameleoon::Exception::VisitorCodeInvalid] If the visitor code is empty or longer than 255 chars
203
+ # @raise [Kameleoon::Exception::FeatureEnvironmentDisabled] If the requested feature flag is disabled for
204
+ # the current environment
309
205
  #
310
206
  def get_feature_variation_key(visitor_code, feature_key)
311
207
  _, variation_key = _get_feature_variation_key(visitor_code, feature_key)
@@ -321,9 +217,11 @@ module Kameleoon
321
217
  # @param [String] feature_key
322
218
  # @param [String] variable_name
323
219
  #
324
- # @raise [Kameleoon::Exception::FeatureConfigurationNotFound] Feature Flag isn't found in this configuration
220
+ # @raise [Kameleoon::Exception::FeatureNotFound] Feature Flag isn't found in this configuration
325
221
  # @raise [Kameleoon::Exception::FeatureVariableNotFound]
326
- # @raise [Kameleoon::Exception::VisitorCodeNotValid] If the visitor code is empty or longer than 255 chars
222
+ # @raise [Kameleoon::Exception::VisitorCodeInvalid] If the visitor code is empty or longer than 255 chars
223
+ # @raise [Kameleoon::Exception::FeatureEnvironmentDisabled] If the requested feature flag is disabled for
224
+ # the current environment
327
225
  #
328
226
  def get_feature_variable(visitor_code, feature_key, variable_name)
329
227
  feature_flag, variation_key = _get_feature_variation_key(visitor_code, feature_key)
@@ -346,14 +244,15 @@ module Kameleoon
346
244
  # @param [String] feature_key
347
245
  # @param [String] variation_key
348
246
  #
349
- # @raise [Kameleoon::Exception::FeatureConfigurationNotFound] Feature Flag isn't found in this configuration
350
- # @raise [Kameleoon::Exception::VariationConfigurationNotFound]
247
+ # @raise [Kameleoon::Exception::FeatureNotFound] Feature Flag isn't found in this configuration
248
+ # @raise [Kameleoon::Exception::FeatureVariationNotFound]
249
+ # @raise [Kameleoon::Exception::FeatureEnvironmentDisabled]
351
250
  #
352
- def get_feature_all_variables(feature_key, variation_key)
353
- feature_flag = find_feature_flag(feature_key)
251
+ def get_feature_variation_variables(feature_key, variation_key)
252
+ feature_flag = @data_file.get_feature_flag(feature_key)
354
253
  variation = feature_flag.get_variation_key(variation_key)
355
254
  if variation.nil?
356
- raise Exception::VariationConfigurationNotFound.new(variation_key),
255
+ raise Exception::FeatureVariationNotFound.new(variation_key),
357
256
  "Variation key #{variation_key} not found"
358
257
  end
359
258
  variables = {}
@@ -361,27 +260,6 @@ module Kameleoon
361
260
  variables
362
261
  end
363
262
 
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
263
  ##
386
264
  # The get_remote_data method allows you to retrieve data (according to a key passed as argument)
387
265
  # stored on a remote Kameleoon server. Usually data will be stored on our remote
@@ -390,7 +268,7 @@ module Kameleoon
390
268
  # way to quickly store massive amounts of data that can be later retrieved for each of your visitors / users.
391
269
  #
392
270
  # @param [String] key Key you want to retrieve data. This field is mandatory.
393
- # @param [Int] timeout Timeout for request (in milliseconds). Equals default_timeout in a config file.
271
+ # @param [Integer] timeout Timeout for request (in milliseconds). Equals default_timeout in a config file.
394
272
  # This field is optional.
395
273
  #
396
274
  # @return [Hash] Hash object of the json object.
@@ -399,13 +277,6 @@ module Kameleoon
399
277
  JSON.parse(response) if response
400
278
  end
401
279
 
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
280
  ##
410
281
  # The get_remote_visitor_data is a method for retrieving custom data for
411
282
  # the latest visit of `visitor_code` from Kameleoon Data API and optionally adding it
@@ -415,7 +286,7 @@ module Kameleoon
415
286
  # This field is mandatory.
416
287
  # @param [Bool] add_data A boolean indicating whether the method should automatically add retrieved data
417
288
  # for a visitor. If not specified, the default value is `True`. This field is optional.
418
- # @param [Int] timeout Timeout for request (in milliseconds). Equals default_timeout in a config file.
289
+ # @param [Integer] timeout Timeout for request (in milliseconds). Equals default_timeout in a config file.
419
290
  # This field is optional.
420
291
  #
421
292
  # @return [Array] An array of data assigned to the given visitor.
@@ -427,29 +298,30 @@ module Kameleoon
427
298
  end
428
299
 
429
300
  ##
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
301
+ # Retrieves data associated with a visitor's warehouse audiences and adds it to the visitor.
302
+ # Retrieves all audience data associated with the visitor in your data warehouse using the specified
303
+ # `visitor_code` and `warehouse_key`. The `warehouse_key` is typically your internal user ID.
304
+ # The `custom_data_index` parameter corresponds to the Kameleoon custom data that Kameleoon uses to target your
305
+ # visitors. You can refer to the
306
+ # <a href="https://help.kameleoon.com/warehouse-audience-targeting/">warehouse targeting documentation</a>
307
+ # for additional details. The method returns a `CustomData` object, confirming
308
+ # that the data has been added to the visitor and is available for targeting purposes.
309
+ #
310
+ # @param [String] visitor_code A unique visitor identification string, can't exceed 255 characters length.
311
+ # This field is mandatory.
312
+ # @param [Integer] custom_data_index An integer representing the index of the custom data you want to use to target
313
+ # your BigQuery Audiences. This field is mandatory.
314
+ # @param [String] warehouse_key A key to identify the warehouse data, typically your internal user ID.
315
+ # This field is optional.
316
+ # @param [Integer] timeout Timeout for request (in milliseconds). Equals default_timeout in a config file.
317
+ # This field is optional.
440
318
  #
441
- # @raise [Kameleoon::Exception::VisitorCodeNotValid] If the visitor code is empty or longer than 255 chars
319
+ # @raise [Kameleoon::Exception::VisitorCodeInvalid] If the visitor code is empty or longer than 255 chars.
320
+ # @raise [JSON::ParserError]
442
321
  #
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
322
+ # @return [Kameleoon::CustomData] A `CustomData` instance confirming that the data has been added to the visitor.
323
+ def get_visitor_warehouse_audience(visitor_code, custom_data_index, timeout = nil, warehouse_key: nil)
324
+ @warehouse_manager.get_visitor_warehouse_audience(visitor_code, custom_data_index, warehouse_key, timeout)
453
325
  end
454
326
 
455
327
  ##
@@ -457,24 +329,22 @@ module Kameleoon
457
329
  #
458
330
  # @return [Array] array of all feature flag keys
459
331
  def get_feature_list # rubocop:disable Naming/AccessorMethodName
460
- @feature_flags.map(&:feature_key)
332
+ @data_file.feature_flags.keys
461
333
  end
462
334
 
463
335
  ##
464
336
  # Returns a list of active feature flag keys for a visitor
465
337
  #
466
- # @raise [Kameleoon::Exception::VisitorCodeNotValid] If the visitor code is empty or longer than 255 chars
338
+ # @raise [Kameleoon::Exception::VisitorCodeInvalid] If the visitor code is empty or longer than 255 chars
467
339
  #
468
340
  # @return [Array] array of active feature flag keys for a visitor
469
341
  def get_active_feature_list_for_visitor(visitor_code)
470
- check_visitor_code(visitor_code)
342
+ Utils::VisitorCode.validate(visitor_code)
471
343
  list_keys = []
472
- @feature_flags.each do |feature_flag|
344
+ @data_file.feature_flags.each do |feature_key, feature_flag|
473
345
  variation, rule, = _calculate_variation_key_for_feature(visitor_code, feature_flag)
474
346
  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
347
+ list_keys.push(feature_key) if variation_key != Kameleoon::Configuration::VariationType::VARIATION_OFF
478
348
  end
479
349
  list_keys
480
350
  end
@@ -499,56 +369,49 @@ module Kameleoon
499
369
  # @return [String] JavasScript code to be inserted in your page to send automatically
500
370
  # the exposure events to the analytics solution you are using.
501
371
  def get_engine_tracking_code(visitor_code)
502
- @hybrid_manager.get_engine_tracking_code(visitor_code)
372
+ visitor_variations = @visitor_manager.get_visitor(visitor_code)&.variations
373
+ @hybrid_manager.get_engine_tracking_code(visitor_variations)
503
374
  end
504
375
 
505
376
  private
506
377
 
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
378
+ HYBRID_EXPIRATION_TIME = 5
524
379
 
525
- def fetch_configuration
526
- Rufus::Scheduler.singleton.in '0s' do
527
- log('Initial configuration fetch is started.')
528
- fetch_configuration_job
380
+ def fetch_configuration_initially
381
+ log('Initial configuration fetch is started.')
382
+ Thread.new do
383
+ ok = false
384
+ begin
385
+ ok = obtain_configuration
386
+ log('Initial configuration fetch failed') unless ok
387
+ rescue StandardError => e
388
+ log("Initial configuration fetch failed: #{e}")
389
+ end
390
+ @readiness.set(ok)
391
+ manage_configuration_update(@data_file.settings.real_time_update) if ok
529
392
  end
530
393
  end
531
394
 
532
395
  def fetch_configuration_job(time_stamp = nil)
533
- EM.synchrony do
396
+ Thread.new do
397
+ ok = false
534
398
  begin
535
399
  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
400
  rescue StandardError => e
541
401
  log("Error occurred during configuration fetching: #{e}")
542
402
  end
543
- manage_configuration_update(@settings.real_time_update)
544
- EM.stop
403
+ if !ok && @data_file.settings.real_time_update
404
+ @data_file.settings.real_time_update = false
405
+ log('Switching to polling mode due to failed fetch')
406
+ end
407
+ manage_configuration_update(@data_file.settings.real_time_update)
545
408
  end
546
409
  end
547
410
 
548
411
  def start_configuration_update_job_if_needed
549
412
  return unless @fetch_configuration_update_job.nil?
550
413
 
551
- @fetch_configuration_update_job = Rufus::Scheduler.singleton.schedule_every @interval do
414
+ @fetch_configuration_update_job = Rufus::Scheduler.singleton.schedule_every @config.refresh_interval_second do
552
415
  log('Scheduled job to fetch configuration is started.')
553
416
  fetch_configuration_job
554
417
  end
@@ -608,12 +471,11 @@ module Kameleoon
608
471
  return false unless response
609
472
 
610
473
  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'])
474
+ @data_file = Configuration::DataFile.new(@config.environment).init(configuration)
475
+ @cookie_manager.consent_required =
476
+ @data_file.settings.is_consent_required && !@data_file.has_any_targeted_delivery_rule
477
+ @network_manager.url_provider.apply_data_api_domain(@data_file.settings.data_api_domain)
478
+
617
479
  call_update_handler_if_needed(!time_stamp.nil?)
618
480
  log "Feature flags are fetched: #{response.inspect}"
619
481
  true
@@ -629,49 +491,16 @@ module Kameleoon
629
491
  @update_configuration_handler.call
630
492
  end
631
493
 
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) : []
494
+ def dispose
495
+ stop_configuration_update_job_if_needed
496
+ stop_real_time_configuration_service_if_needed
497
+ @visitor_manager.stop
660
498
  end
661
499
 
662
500
  def log(text)
663
501
  print "Kameleoon SDK Log: #{text}\n" if @verbose_mode
664
502
  end
665
503
 
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
504
  # Uncomment when using storage
676
505
  # def get_valid_saved_variation(visitor_code, experiment)
677
506
  # variation_id = @variation_storage.get_variation_id(visitor_code, experiment.id.to_i)
@@ -685,29 +514,38 @@ module Kameleoon
685
514
 
686
515
  def check_targeting(visitor_code, campaign_id, exp_ff_rule)
687
516
  segment = exp_ff_rule.targeting_segment
688
- segment.nil? || segment.check_tree(->(type) { get_condition_data(type, visitor_code, campaign_id) })
517
+ return true if segment.nil?
518
+
519
+ visitor = @visitor_manager.get_visitor(visitor_code)
520
+ segment.check_tree(->(type) { get_condition_data(type, visitor, visitor_code, campaign_id) })
689
521
  end
690
522
 
691
- def get_condition_data(type, visitor_code, campaign_id)
523
+ def get_condition_data(type, visitor, visitor_code, campaign_id)
692
524
  condition_data = nil
693
525
  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
526
+ when Kameleoon::Targeting::ConditionType::CUSTOM_DATUM
527
+ condition_data = visitor.custom_data unless visitor.nil?
528
+ when Kameleoon::Targeting::ConditionType::PAGE_TITLE,
529
+ Kameleoon::Targeting::ConditionType::PAGE_URL
530
+ condition_data = visitor.page_view_visits unless visitor.nil?
531
+ when Kameleoon::Targeting::ConditionType::DEVICE_TYPE
532
+ condition_data = visitor.device unless visitor.nil?
533
+ when Kameleoon::Targeting::ConditionType::BROWSER
534
+ condition_data = visitor.browser unless visitor.nil?
535
+ when Kameleoon::Targeting::ConditionType::CONVERSIONS
536
+ condition_data = visitor.conversions unless visitor.nil?
701
537
  when Kameleoon::Targeting::ConditionType::SDK_LANGUAGE
702
538
  condition_data = Kameleoon::Targeting::SdkInfo.new(Kameleoon::SDK_NAME, Kameleoon::SDK_VERSION)
703
539
  when Kameleoon::Targeting::ConditionType::VISITOR_CODE
704
540
  condition_data = visitor_code
705
541
  when Kameleoon::Targeting::ConditionType::TARGET_EXPERIMENT
706
- condition_data = @variation_storage.get_hash_saved_variation_id(visitor_code)
542
+ condition_data = visitor.variations unless visitor.nil?
707
543
  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)
544
+ unless visitor.nil?
545
+ condition_data = OpenStruct.new
546
+ condition_data.experiment_id = campaign_id
547
+ condition_data.storage = visitor.variations
548
+ end
711
549
  end
712
550
  condition_data
713
551
  end
@@ -715,16 +553,21 @@ module Kameleoon
715
553
  ##
716
554
  # helper method for getting variation key for feature flag
717
555
  def _get_feature_variation_key(visitor_code, feature_key)
718
- check_visitor_code(visitor_code)
719
- feature_flag = find_feature_flag(feature_key)
556
+ Utils::VisitorCode.validate(visitor_code)
557
+ feature_flag = @data_file.get_feature_flag(feature_key)
720
558
  variation, rule = _calculate_variation_key_for_feature(visitor_code, feature_flag)
721
559
  variation_key = _get_variation_key(variation, rule, feature_flag)
560
+ visitor = nil
722
561
  unless rule.nil?
723
562
  experiment_id = rule.experiment_id
724
563
  variation_id = variation.variation_id unless variation.nil?
725
- save_variation(visitor_code, experiment_id, variation_id)
564
+ visitor = @visitor_manager.get_or_create_visitor(visitor_code)
565
+ unless experiment_id.nil? || variation_id.nil?
566
+ as_variation = Kameleoon::DataManager::AssignedVariation.new(experiment_id, variation_id, rule.type)
567
+ visitor.assign_variation(as_variation)
568
+ end
726
569
  end
727
- _send_tracking_request(visitor_code, experiment_id, variation_id)
570
+ _send_tracking_request(visitor_code, visitor)
728
571
  [feature_flag, variation_key]
729
572
  end
730
573
 
@@ -737,13 +580,13 @@ module Kameleoon
737
580
  next unless check_targeting(visitor_code, feature_flag.id, rule)
738
581
 
739
582
  # uses for rule exposition
740
- hash_rule = obtain_hash_double_rule(visitor_code, rule.id, rule.respool_time)
583
+ hash_rule = Utils::HashDouble.obtain_rule(visitor_code, rule.id, rule.respool_time)
741
584
  # check main expostion for rule with hashRule
742
585
  if hash_rule <= rule.exposition
743
586
  return [rule.variation_by_exposition[0], rule] if rule.targeted_delivery_type?
744
587
 
745
588
  # uses for variation's expositions
746
- hash_variation = obtain_hash_double_rule(visitor_code, rule.experiment_id, rule.respool_time)
589
+ hash_variation = Utils::HashDouble.obtain_rule(visitor_code, rule.experiment_id, rule.respool_time)
747
590
  # get variation key with new hashVariation
748
591
  variation = rule.get_variation(hash_variation)
749
592
  return [variation, rule] unless variation.nil?
@@ -755,16 +598,8 @@ module Kameleoon
755
598
  [nil, nil]
756
599
  end
757
600
 
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
601
  def _get_variation_key(var_by_exp, rule, feature_flag)
766
602
  return var_by_exp.variation_key unless var_by_exp.nil?
767
-
768
603
  return Kameleoon::Configuration::VariationType::VARIATION_OFF if !rule.nil? && rule.experimentation_type?
769
604
 
770
605
  feature_flag.default_variation_key
@@ -772,16 +607,37 @@ module Kameleoon
772
607
 
773
608
  ##
774
609
  # 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?
610
+ def _send_tracking_request(visitor_code, visitor = nil, force_request = true)
611
+ if visitor.nil?
612
+ visitor = @visitor_manager.get_visitor(visitor_code)
613
+ return if visitor.nil? && @data_file.settings.is_consent_required
614
+ end
615
+ consent = consent_provided?(visitor)
616
+ user_agent = visitor&.user_agent
617
+ unsent = visitor.nil? ? [] : select_unsent_data(visitor, consent)
618
+ if unsent.empty?
619
+ return unless force_request && consent
620
+
621
+ unsent.push(Network::ActivityEvent.new)
622
+ end
623
+ log "Start post tracking: #{unsent.inspect}"
624
+ @network_manager.send_tracking_data(visitor_code, unsent, user_agent)
625
+ end
626
+
627
+ def select_unsent_data(visitor, consent)
628
+ unsent = []
629
+ if consent
630
+ visitor.enumerate_sendable_data { |data| unsent.push(data) unless data.sent }
780
631
  else
781
- data_not_sent.append(Network::ExperimentEvent.new(experiment_id, variation_id))
632
+ visitor.conversions.enumerate { |conversion| unsent.push(conversion) unless conversion.sent }
633
+ if @data_file.has_any_targeted_delivery_rule
634
+ visitor.variations.enumerate do |variation|
635
+ unsent.push(variation) unless
636
+ variation.sent || (variation.rule_type != Configuration::RuleType::TARGETED_DELIVERY)
637
+ end
638
+ end
782
639
  end
783
- log "Start post tracking: #{data_not_sent.inspect}"
784
- @network_manager.send_tracking_data(visitor_code, data_not_sent, user_agent)
640
+ unsent
785
641
  end
786
642
 
787
643
  ##
@@ -820,5 +676,9 @@ module Kameleoon
820
676
  log("Parsing of visitor data of '#{visitor_code}' failed: #{e}")
821
677
  []
822
678
  end
679
+
680
+ def consent_provided?(visitor)
681
+ !@data_file.settings.is_consent_required || visitor&.legal_consent
682
+ end
823
683
  end
824
684
  end