kameleoon-client-ruby 2.3.0 → 3.1.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 (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