kameleoon-client-ruby 2.3.0 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/lib/kameleoon/client_readiness.rb +40 -0
  3. data/lib/kameleoon/configuration/data_file.rb +41 -0
  4. data/lib/kameleoon/configuration/feature_flag.rb +3 -2
  5. data/lib/kameleoon/configuration/rule.rb +18 -3
  6. data/lib/kameleoon/configuration/settings.rb +3 -1
  7. data/lib/kameleoon/configuration/variable.rb +0 -2
  8. data/lib/kameleoon/configuration/variation_exposition.rb +0 -2
  9. data/lib/kameleoon/data/browser.rb +1 -1
  10. data/lib/kameleoon/data/conversion.rb +1 -1
  11. data/lib/kameleoon/data/custom_data.rb +10 -14
  12. data/lib/kameleoon/data/data.rb +22 -3
  13. data/lib/kameleoon/data/device.rb +2 -1
  14. data/lib/kameleoon/data/manager/assigned_variation.rb +38 -0
  15. data/lib/kameleoon/data/manager/data_array_storage.rb +43 -0
  16. data/lib/kameleoon/data/manager/data_map_storage.rb +43 -0
  17. data/lib/kameleoon/data/manager/page_view_visit.rb +19 -0
  18. data/lib/kameleoon/data/manager/visitor.rb +142 -0
  19. data/lib/kameleoon/data/manager/visitor_manager.rb +71 -0
  20. data/lib/kameleoon/data/page_view.rb +2 -1
  21. data/lib/kameleoon/exceptions.rb +30 -35
  22. data/lib/kameleoon/hybrid/manager.rb +13 -31
  23. data/lib/kameleoon/{client.rb → kameleoon_client.rb} +164 -336
  24. data/lib/kameleoon/kameleoon_client_config.rb +91 -0
  25. data/lib/kameleoon/kameleoon_client_factory.rb +42 -0
  26. data/lib/kameleoon/network/activity_event.rb +6 -3
  27. data/lib/kameleoon/network/cookie/cookie_manager.rb +84 -0
  28. data/lib/kameleoon/network/net_provider.rb +5 -37
  29. data/lib/kameleoon/network/network_manager.rb +8 -7
  30. data/lib/kameleoon/network/request.rb +3 -2
  31. data/lib/kameleoon/network/response.rb +0 -8
  32. data/lib/kameleoon/network/url_provider.rb +5 -3
  33. data/lib/kameleoon/targeting/conditions/browser_condition.rb +2 -3
  34. data/lib/kameleoon/targeting/conditions/conversion_condition.rb +12 -4
  35. data/lib/kameleoon/targeting/conditions/custom_datum.rb +19 -13
  36. data/lib/kameleoon/targeting/conditions/device_condition.rb +3 -4
  37. data/lib/kameleoon/targeting/conditions/exclusive_experiment.rb +2 -1
  38. data/lib/kameleoon/targeting/conditions/page_title_condition.rb +11 -4
  39. data/lib/kameleoon/targeting/conditions/page_url_condition.rb +18 -4
  40. data/lib/kameleoon/targeting/conditions/string_value_condition.rb +2 -0
  41. data/lib/kameleoon/targeting/conditions/target_experiment.rb +11 -6
  42. data/lib/kameleoon/utils.rb +41 -4
  43. data/lib/kameleoon/version.rb +1 -1
  44. data/lib/kameleoon.rb +4 -2
  45. metadata +14 -10
  46. data/lib/kameleoon/client_config.rb +0 -44
  47. data/lib/kameleoon/configuration/experiment.rb +0 -42
  48. data/lib/kameleoon/cookie.rb +0 -88
  49. data/lib/kameleoon/factory.rb +0 -43
  50. data/lib/kameleoon/network/experiment_event.rb +0 -35
  51. data/lib/kameleoon/storage/variation_storage.rb +0 -42
  52. data/lib/kameleoon/storage/visitor_variation.rb +0 -20
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7d51a71ea79cd23f422a5235fce6f2e3fb94cae538b01222dd4c23b770985e59
4
- data.tar.gz: 29f2501a20ffedf1637ee6d441b637358982761955d6235317f49b5284f17b70
3
+ metadata.gz: 3462fcc69d821034025ac9299b880f4e63ed5debca86aa85bcb5236b9658b0cb
4
+ data.tar.gz: 0eaf3a74b42adfb6b561048e9934ebf529047ea5307551f795a630a855b19404
5
5
  SHA512:
6
- metadata.gz: 7f4855238a2e8731458757336bc964a36d537e77e7e1b30eb73b5c7e270ee59ee021813b620c0997b9781a8e062b10f3a4797eaceffcd9ca731e5e86f11f1e12
7
- data.tar.gz: e7d3659d4de54bc0be3aadf6ed96d213bf148ce481306ad8668b589dd892a26a74e78206c6d78dd9577c915e96bab1351c19f08c95ce8b4ae85aaef237b6bd6f
6
+ metadata.gz: 157327482deee68063a881afca8d8b576f854d1fff756d589f4021e1793c7e1966ddfdd9120c36df189e51522518e6fb678f523373ee1525abf4aa932599b906
7
+ data.tar.gz: 8bfbf4b4917773954aba26163c3252a10f1890b49c047c46724c8e4eff4d1cdcefc64150ff801e4920d7ddb47479a4c5f3fc0c771299283a12af625f78b5de24
@@ -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']
@@ -6,14 +6,16 @@ module Kameleoon
6
6
  # KameleoonConfigurationSettings is used for saving setting's parameters, e.g
7
7
  # state of real time update for site code and etc
8
8
  class Settings
9
- attr_accessor :real_time_update
9
+ attr_accessor :real_time_update, :is_consent_required
10
10
 
11
11
  def initialize
12
12
  @real_time_update = false
13
+ @is_consent_required = false
13
14
  end
14
15
 
15
16
  def update(configuration)
16
17
  @real_time_update = configuration['realTimeUpdate'] || false
18
+ @is_consent_required = configuration['consentType'] == 'REQUIRED'
17
19
  end
18
20
  end
19
21
  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