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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7d51a71ea79cd23f422a5235fce6f2e3fb94cae538b01222dd4c23b770985e59
4
- data.tar.gz: 29f2501a20ffedf1637ee6d441b637358982761955d6235317f49b5284f17b70
3
+ metadata.gz: 4f1490fd854e391fea3b908cad394e328a3540b6368942b72c1a9979c9002b87
4
+ data.tar.gz: 3abf894648cd3662a1a3a3453254014fa20c5405af28f6e01c8e336c810c6031
5
5
  SHA512:
6
- metadata.gz: 7f4855238a2e8731458757336bc964a36d537e77e7e1b30eb73b5c7e270ee59ee021813b620c0997b9781a8e062b10f3a4797eaceffcd9ca731e5e86f11f1e12
7
- data.tar.gz: e7d3659d4de54bc0be3aadf6ed96d213bf148ce481306ad8668b589dd892a26a74e78206c6d78dd9577c915e96bab1351c19f08c95ce8b4ae85aaef237b6bd6f
6
+ metadata.gz: 76f8eb07daaf102c292c9ed04a6d788dfcef4a7cf757875603a68d2a1272b8e543dbfabd89226a6f775cd71d243998b8b897e6339c616d0a87b03624054720e3
7
+ data.tar.gz: d8f94272a6afa522a6ea5c48e3c0f9b91fec6bd1b2382e0ff2a9a94cfbdfb3c23d9f278a5bf143d4161a05cff385ad335d278a5634e8f3a23f4f63373c6da281
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'concurrent'
4
+
5
+ module Kameleoon
6
+ class ClientReadiness
7
+ attr_reader :is_initializing, :success
8
+
9
+ def initialize
10
+ @is_initializing = false
11
+ @success = false
12
+ @condition = Concurrent::ReadWriteLock.new
13
+ reset
14
+ end
15
+
16
+ def reset
17
+ @success = false
18
+ unless @is_initializing
19
+ @is_initializing = true
20
+ @condition.acquire_write_lock
21
+ end
22
+ end
23
+
24
+ def set(success)
25
+ @success = success
26
+ if @is_initializing
27
+ @condition.release_write_lock
28
+ @is_initializing = false
29
+ end
30
+ end
31
+
32
+ def wait
33
+ if @is_initializing
34
+ @condition.acquire_read_lock
35
+ @condition.release_read_lock
36
+ end
37
+ @success
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'kameleoon/configuration/settings'
4
+
5
+ module Kameleoon
6
+ module Configuration
7
+ class DataFile
8
+ attr_reader :settings, :feature_flags, :has_any_targeted_delivery_rule
9
+
10
+ def initialize(environment)
11
+ @settings = Settings.new
12
+ @feature_flags = {}
13
+ @environment = environment
14
+ end
15
+
16
+ def init(configuration)
17
+ @settings.update(configuration['configuration'])
18
+ configuration['featureFlags'].each do |raw|
19
+ ff = FeatureFlag.new(raw)
20
+ @feature_flags[ff.feature_key] = ff
21
+ end
22
+ @has_any_targeted_delivery_rule = any_targeted_delivery_rule?
23
+ self
24
+ end
25
+
26
+ def get_feature_flag(feature_key)
27
+ ff = @feature_flags[feature_key]
28
+ raise Exception::FeatureNotFound, feature_key if ff.nil?
29
+ raise Exception::FeatureEnvironmentDisabled.new(feature_key, @environment) unless ff.environment_enabled
30
+
31
+ ff
32
+ end
33
+
34
+ private
35
+
36
+ def any_targeted_delivery_rule?
37
+ @feature_flags.any? { |_, ff| ff.environment_enabled && ff.rules.any?(&:targeted_delivery_type?) }
38
+ end
39
+ end
40
+ end
41
+ end
@@ -8,7 +8,7 @@ module Kameleoon
8
8
  module Configuration
9
9
  # Class for manage all feature flags with rules
10
10
  class FeatureFlag
11
- attr_accessor :id, :feature_key, :variations, :default_variation_key, :rules
11
+ attr_accessor :id, :feature_key, :variations, :default_variation_key, :environment_enabled, :rules
12
12
 
13
13
  def self.create_from_array(array)
14
14
  array&.map { |it| FeatureFlag.new(it) }
@@ -17,8 +17,9 @@ module Kameleoon
17
17
  def initialize(hash)
18
18
  @id = hash['id']
19
19
  @feature_key = hash['featureKey']
20
- @default_variation_key = hash['defaultVariationKey']
21
20
  @variations = Variation.create_from_array(hash['variations'])
21
+ @default_variation_key = hash['defaultVariationKey']
22
+ @environment_enabled = hash['environmentEnabled']
22
23
  @rules = Rule.create_from_array(hash['rules'])
23
24
  end
24
25
 
@@ -8,8 +8,23 @@ module Kameleoon
8
8
  module Configuration
9
9
  # RuleType has a possible rule types
10
10
  module RuleType
11
- EXPERIMENTATION = 'EXPERIMENTATION'
12
- TARGETED_DELIVERY = 'TARGETED_DELIVERY'
11
+ UNKNOWN = 0
12
+ EXPERIMENTATION = 1
13
+ TARGETED_DELIVERY = 2
14
+
15
+ EXPERIMENTATION_LITERAL = 'EXPERIMENTATION'
16
+ TARGETED_DELIVERY_LITERAL = 'TARGETED_DELIVERY'
17
+
18
+ def self.from_literal(literal)
19
+ case literal
20
+ when EXPERIMENTATION_LITERAL
21
+ EXPERIMENTATION
22
+ when TARGETED_DELIVERY_LITERAL
23
+ TARGETED_DELIVERY
24
+ else
25
+ UNKNOWN
26
+ end
27
+ end
13
28
  end
14
29
 
15
30
  # Rule is a class for new rules of feature flags
@@ -24,7 +39,7 @@ module Kameleoon
24
39
  def initialize(hash)
25
40
  @id = hash['id']
26
41
  @order = hash['order']
27
- @type = hash['type']
42
+ @type = RuleType.from_literal(hash['type'])
28
43
  @exposition = hash['exposition']
29
44
  @experiment_id = hash['experimentId']
30
45
  @respool_time = hash['respoolTime']
@@ -7,13 +7,18 @@ module Kameleoon
7
7
  # state of real time update for site code and etc
8
8
  class Settings
9
9
  attr_accessor :real_time_update
10
+ attr_reader :is_consent_required, :data_api_domain
10
11
 
11
12
  def initialize
12
13
  @real_time_update = false
14
+ @is_consent_required = false
15
+ @data_api_domain = nil
13
16
  end
14
17
 
15
18
  def update(configuration)
16
19
  @real_time_update = configuration['realTimeUpdate'] || false
20
+ @is_consent_required = configuration['consentType'] == 'REQUIRED'
21
+ @data_api_domain = configuration['dataApiDomain']
17
22
  end
18
23
  end
19
24
  end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'experiment'
4
-
5
3
  module Kameleoon
6
4
  # Module which contains all internal data of SDK
7
5
  module Configuration
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'experiment'
4
-
5
3
  module Kameleoon
6
4
  # Module which contains all internal data of SDK
7
5
  module Configuration
@@ -15,7 +15,7 @@ module Kameleoon
15
15
  end
16
16
 
17
17
  # Represents browser data for tracking calls
18
- class Browser < Data
18
+ class Browser < DuplicationUnsafeData
19
19
  attr_reader :type, :version
20
20
 
21
21
  # @param [BrowserType] browser_type Browser type, can be: CHROME, INTERNET_EXPLORER, FIREFOX, SAFARI, OPERA, OTHER
@@ -6,7 +6,7 @@ require_relative 'data'
6
6
 
7
7
  module Kameleoon
8
8
  # Conversion class uses for tracking conversion
9
- class Conversion < Data
9
+ class Conversion < DuplicationSafeData
10
10
  attr_reader :goal_id, :revenue, :negative
11
11
 
12
12
  # @param [Integer] goal_id Id of the goal associated to the conversion
@@ -6,7 +6,7 @@ require_relative 'data'
6
6
 
7
7
  module Kameleoon
8
8
  # Represents any custom data for targeting conditions
9
- class CustomData < Data
9
+ class CustomData < DuplicationUnsafeData
10
10
  attr_reader :id, :values
11
11
 
12
12
  # @param [Integer] id Id of the custom data
@@ -23,23 +23,19 @@ module Kameleoon
23
23
  id = hash['id']
24
24
  raise Kameleoon::Exception::NotFound.new('id'), '"id" is mandatory' if id.nil?
25
25
 
26
- @id = id.to_s
27
- value = hash['value']
26
+ @id = id
28
27
  values = hash['values']
29
- if values.nil? && value.nil?
30
- raise Kameleoon::Exception::NotFound.new('value or values'), '"value" or "values" is mandatory'
31
- end
32
-
33
- if values.nil?
34
- @values = [value]
35
- else
36
- @values = values.is_a?(Array) ? values.dup : [values]
37
- @values.append(value) unless value.nil?
38
- end
28
+ raise Kameleoon::Exception::NotFound.new('values'), '"values" is mandatory' if values.nil?
29
+
30
+ @values = values.is_a?(Array) ? values.dup : [values]
39
31
  else
40
- @id = arg0.to_s
32
+ @id = arg0
41
33
  @values = args
42
34
  end
35
+ return if @id.is_a?(Integer)
36
+
37
+ warn "CustomData field 'id' must be of 'Integer' type"
38
+ @id = @id.is_a?(String) ? @id.to_i : -1
43
39
  end
44
40
  # rubocop:enable Metrics/MethodLength
45
41
 
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'json'
4
4
  require 'kameleoon/exceptions'
5
+ require 'kameleoon/utils'
5
6
 
6
7
  module Kameleoon
7
8
  NONCE_LENGTH = 16
@@ -12,12 +13,12 @@ module Kameleoon
12
13
  CONVERSION = 'CONVERSION'
13
14
  DEVICE = 'DEVICE'
14
15
  PAGE_VIEW = 'PAGE_VIEW'
16
+ ASSIGNED_VARIATION = 'ASSIGNED_VARIATION'
15
17
  end
16
18
 
17
19
  # Represents base class for any Kameleoon data
18
20
  class Data
19
- attr_reader :instance
20
- attr_accessor :sent
21
+ attr_reader :instance, :sent
21
22
 
22
23
  def initialize(data_type)
23
24
  @instance = data_type
@@ -28,10 +29,28 @@ module Kameleoon
28
29
  raise KameleoonError.new('ToDo: implement this method.'), 'ToDo: implement this method.'
29
30
  end
30
31
 
32
+ def mark_as_sent
33
+ @sent = true
34
+ @nonce = nil
35
+ end
36
+ end
37
+
38
+ class DuplicationSafeData < Data
39
+ def initialize(data_type)
40
+ super(data_type)
41
+ @nonce = Kameleoon::Utils.generate_random_string(NONCE_LENGTH)
42
+ end
43
+
44
+ private
45
+
46
+ attr_reader :nonce
47
+ end
48
+
49
+ class DuplicationUnsafeData < Data
31
50
  private
32
51
 
33
52
  def nonce
34
- @nonce = Kameleoon::Utils.generate_random_string(NONCE_LENGTH) if @nonce.nil?
53
+ @nonce = Kameleoon::Utils.generate_random_string(NONCE_LENGTH) if !@sent && @nonce.nil?
35
54
  @nonce
36
55
  end
37
56
  end
@@ -12,8 +12,9 @@ module Kameleoon
12
12
  end
13
13
 
14
14
  # Device uses for sending deviceType parameter for tracking calls
15
- class Device < Data
15
+ class Device < DuplicationUnsafeData
16
16
  attr_reader :device_type
17
+
17
18
  def initialize(device_type)
18
19
  super(DataType::DEVICE)
19
20
  @device_type = device_type
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'kameleoon/data/data'
4
+ require 'kameleoon/network/uri_helper'
5
+
6
+ module Kameleoon
7
+ module DataManager
8
+ ##
9
+ # AssignedVariation represents a variation assigned to a visitor.
10
+ class AssignedVariation < DuplicationUnsafeData
11
+ EVENT_TYPE = 'experiment'
12
+
13
+ attr_reader :experiment_id, :variation_id, :rule_type, :assignment_time
14
+
15
+ def initialize(experiment_id, variation_id, rule_type, assignment_time: nil)
16
+ super(DataType::ASSIGNED_VARIATION)
17
+ @experiment_id = experiment_id
18
+ @variation_id = variation_id
19
+ @rule_type = rule_type
20
+ @assignment_time = assignment_time || Time.new.to_i
21
+ end
22
+
23
+ def obtain_full_post_text_line
24
+ params = {
25
+ eventType: EVENT_TYPE,
26
+ id: @experiment_id,
27
+ variationId: @variation_id,
28
+ nonce: nonce
29
+ }
30
+ Kameleoon::Network::UriHelper.encode_query(params)
31
+ end
32
+
33
+ def valid?(respool_time)
34
+ respool_time.nil? || (@assignment_time >= respool_time)
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kameleoon
4
+ module DataManager
5
+ ##
6
+ # DataArrayStorage is a readonly accessor to a sequntial storage of specific visitor data.
7
+ # It is thread-safe
8
+ class DataArrayStorage
9
+ def initialize(mutex, array)
10
+ @mutex = mutex
11
+ @array = array
12
+ end
13
+
14
+ def enumerate(&blk)
15
+ return if @array.nil?
16
+
17
+ @mutex.with_read_lock do
18
+ @array.each { |v| blk.call(v) }
19
+ end
20
+ end
21
+
22
+ def last
23
+ return nil if @array.nil?
24
+
25
+ value = nil
26
+ @mutex.with_read_lock do
27
+ value = @array.last
28
+ end
29
+ value
30
+ end
31
+
32
+ def size
33
+ return 0 if @array.nil?
34
+
35
+ value = nil
36
+ @mutex.with_read_lock do
37
+ value = @array.size
38
+ end
39
+ value
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kameleoon
4
+ module DataManager
5
+ ##
6
+ # DataMapStorage is a readonly accessor to a key-value based storage of specific visitor data.
7
+ # It is thread-safe
8
+ class DataMapStorage
9
+ def initialize(mutex, map)
10
+ @mutex = mutex
11
+ @map = map
12
+ end
13
+
14
+ def enumerate(&blk)
15
+ return if @map.nil?
16
+
17
+ @mutex.with_read_lock do
18
+ @map.each { |_k, v| blk.call(v) }
19
+ end
20
+ end
21
+
22
+ def get(key)
23
+ return nil if @map.nil?
24
+
25
+ value = nil
26
+ @mutex.with_read_lock do
27
+ value = @map[key]
28
+ end
29
+ value
30
+ end
31
+
32
+ def size
33
+ return 0 if @map.nil?
34
+
35
+ value = nil
36
+ @mutex.with_read_lock do
37
+ value = @map.size
38
+ end
39
+ value
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kameleoon
4
+ module DataManager
5
+ class PageViewVisit
6
+ attr_reader :page_view, :count
7
+
8
+ def initialize(page_view, count = 1)
9
+ @page_view = page_view
10
+ @count = count
11
+ end
12
+
13
+ def overwrite(page_view)
14
+ @page_view = page_view
15
+ @count += 1
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,142 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'concurrent'
4
+ require 'kameleoon/data/browser'
5
+ require 'kameleoon/data/conversion'
6
+ require 'kameleoon/data/custom_data'
7
+ require 'kameleoon/data/device'
8
+ require 'kameleoon/data/page_view'
9
+ require 'kameleoon/data/user_agent'
10
+ require 'kameleoon/data/manager/assigned_variation'
11
+ require 'kameleoon/data/manager/page_view_visit'
12
+ require 'kameleoon/data/manager/data_map_storage'
13
+ require 'kameleoon/data/manager/data_array_storage'
14
+
15
+ module Kameleoon
16
+ module DataManager
17
+ ##
18
+ # Visitor is a container of all data assigned to a visitor.
19
+ # It is thread-safe
20
+ class Visitor
21
+ attr_reader :last_activity_time, :user_agent, :device, :browser
22
+ attr_accessor :legal_consent
23
+
24
+ def initialize
25
+ @mutex = Concurrent::ReadWriteLock.new
26
+ @legal_consent = false
27
+ update_last_activity_time
28
+ end
29
+
30
+ def update_last_activity_time
31
+ @last_activity_time = Time.new.to_i
32
+ end
33
+
34
+ def enumerate_sendable_data(&blk)
35
+ blk.call(@device) unless @device.nil?
36
+ blk.call(@browser) unless @browser.nil?
37
+ @mutex.with_read_lock do
38
+ @custom_data_map&.each { |_, cd| blk.call(cd) }
39
+ @page_view_visits&.each { |_, pvv| blk.call(pvv.page_view) }
40
+ @conversions&.each { |c| blk.call(c) }
41
+ @variations&.each { |_, av| blk.call(av) }
42
+ end
43
+ end
44
+
45
+ def count_sendable_data
46
+ count = 0
47
+ @mutex.with_read_lock do
48
+ count += 1 unless @device.nil?
49
+ count += 1 unless @browser.nil?
50
+ count += @custom_data_map.size unless @custom_data_map.nil?
51
+ count += @page_view_visits.size unless @page_view_visits.nil?
52
+ count += @conversions.size unless @conversions.nil?
53
+ count += @variations.size unless @variations.nil?
54
+ end
55
+ count
56
+ end
57
+
58
+ def custom_data
59
+ DataMapStorage.new(@mutex, @custom_data_map)
60
+ end
61
+
62
+ def page_view_visits
63
+ DataMapStorage.new(@mutex, @page_view_visits)
64
+ end
65
+
66
+ def conversions
67
+ DataArrayStorage.new(@mutex, @conversions)
68
+ end
69
+
70
+ def variations
71
+ DataMapStorage.new(@mutex, @variations)
72
+ end
73
+
74
+ def add_data(log_func, *args)
75
+ @mutex.with_write_lock do
76
+ args.each do |data|
77
+ case data
78
+ when Kameleoon::UserAgent
79
+ add_user_agent(data)
80
+ when Kameleoon::Device
81
+ add_device(data)
82
+ when Kameleoon::Browser
83
+ add_browser(data)
84
+ when Kameleoon::CustomData
85
+ add_custom_data(data)
86
+ when Kameleoon::PageView
87
+ add_page_view(data)
88
+ when Kameleoon::Conversion
89
+ add_conversion(data)
90
+ else
91
+ log_func&.call("Data has unsupported type '#{data.class}'")
92
+ end
93
+ end
94
+ end
95
+ end
96
+
97
+ def assign_variation(variation)
98
+ @mutex.with_write_lock do
99
+ @variations = {} if @variations.nil?
100
+ @variations[variation.experiment_id] = variation
101
+ end
102
+ end
103
+
104
+ private
105
+
106
+ def add_user_agent(user_agent)
107
+ @user_agent = user_agent.value
108
+ end
109
+
110
+ def add_device(device)
111
+ @device = device
112
+ end
113
+
114
+ def add_browser(browser)
115
+ @browser = browser
116
+ end
117
+
118
+ def add_custom_data(custom_data)
119
+ @custom_data_map = {} if @custom_data_map.nil?
120
+ @custom_data_map[custom_data.id] = custom_data
121
+ end
122
+
123
+ def add_page_view(page_view)
124
+ return if page_view.url.nil? || page_view.url.empty?
125
+
126
+ @page_view_visits = {} if @page_view_visits.nil?
127
+ visit = @page_view_visits[page_view.url]
128
+ if visit.nil?
129
+ visit = DataManager::PageViewVisit.new(page_view)
130
+ else
131
+ visit.overwrite(page_view)
132
+ end
133
+ @page_view_visits[page_view.url] = visit
134
+ end
135
+
136
+ def add_conversion(conversion)
137
+ @conversions = [] if @conversions.nil?
138
+ @conversions.push(conversion)
139
+ end
140
+ end
141
+ end
142
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'concurrent'
4
+ require 'rufus/scheduler'
5
+ require 'kameleoon/data/manager/visitor'
6
+
7
+ module Kameleoon
8
+ module DataManager
9
+ ##
10
+ # VisitorManager stores Visitor instances and manages visitor sessions.
11
+ # It is thread-safe except `stop` method
12
+ class VisitorManager
13
+ attr_reader :expiration_period
14
+
15
+ def initialize(expiration_period)
16
+ @expiration_period = expiration_period
17
+ @visitors = Concurrent::Map.new
18
+ start
19
+ end
20
+
21
+ def stop
22
+ return if @purge_job.nil?
23
+
24
+ @purge_job.unschedule
25
+ @purge_job = nil
26
+ end
27
+
28
+ def get_visitor(visitor_code)
29
+ @visitors.compute_if_present(visitor_code) do |visitor|
30
+ visitor.update_last_activity_time
31
+ visitor
32
+ end
33
+ end
34
+
35
+ def get_or_create_visitor(visitor_code)
36
+ @visitors.compute(visitor_code) do |former_v|
37
+ next DataManager::Visitor.new if former_v.nil?
38
+
39
+ former_v.update_last_activity_time
40
+ former_v
41
+ end
42
+ end
43
+
44
+ def enumerate(&blk)
45
+ @visitors.each_pair { |vc, v| blk.call(vc, v) }
46
+ end
47
+
48
+ def size
49
+ @visitors.size
50
+ end
51
+
52
+ private
53
+
54
+ def purge
55
+ expired_time = Time.new.to_i - @expiration_period
56
+ @visitors.each_pair do |vc, v|
57
+ next if v.last_activity_time >= expired_time
58
+
59
+ @visitors.compute_if_present(vc) { |visitor| v.last_activity_time < expired_time ? nil : visitor }
60
+ end
61
+ end
62
+
63
+ def start
64
+ stop unless @purge_job.nil?
65
+ @purge_job = Rufus::Scheduler.singleton.schedule_every @expiration_period do
66
+ purge
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -6,7 +6,7 @@ require_relative 'data'
6
6
 
7
7
  module Kameleoon
8
8
  # Represents page view data for tracking calls
9
- class PageView < Data
9
+ class PageView < DuplicationSafeData
10
10
  attr_reader :url, :title, :referrer
11
11
 
12
12
  # @param [String] url Url of the page
@@ -15,6 +15,7 @@ module Kameleoon
15
15
  def initialize(url, title, referrers = nil)
16
16
  super(DataType::PAGE_VIEW)
17
17
  @url = url || ''
18
+ print('Kameleoon SDK: Url for PageView is required parameter, the data will be ignored.') if url == ''
18
19
  @title = title || ''
19
20
  @referrers = referrers.instance_of?(Integer) ? [referrers] : referrers
20
21
  end