kameleoon-client-ruby 3.2.0 → 3.4.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.
- checksums.yaml +4 -4
- data/lib/kameleoon/configuration/custom_data_info.rb +16 -8
- data/lib/kameleoon/configuration/data_file.rb +40 -17
- data/lib/kameleoon/configuration/feature_flag.rb +10 -0
- data/lib/kameleoon/configuration/rule.rb +4 -0
- data/lib/kameleoon/configuration/settings.rb +13 -8
- data/lib/kameleoon/configuration/variation_exposition.rb +4 -0
- data/lib/kameleoon/data/browser.rb +4 -0
- data/lib/kameleoon/data/conversion.rb +4 -0
- data/lib/kameleoon/data/cookie.rb +4 -0
- data/lib/kameleoon/data/custom_data.rb +11 -3
- data/lib/kameleoon/data/data.rb +30 -4
- data/lib/kameleoon/data/device.rb +4 -0
- data/lib/kameleoon/data/geolocation.rb +5 -0
- data/lib/kameleoon/data/kcs_heat.rb +16 -0
- data/lib/kameleoon/data/manager/assigned_variation.rb +5 -0
- data/lib/kameleoon/data/manager/data_array_storage.rb +7 -0
- data/lib/kameleoon/data/manager/data_map_storage.rb +7 -0
- data/lib/kameleoon/data/manager/page_view_visit.rb +4 -0
- data/lib/kameleoon/data/manager/visitor.rb +198 -67
- data/lib/kameleoon/data/manager/visitor_manager.rb +54 -17
- data/lib/kameleoon/data/mapping_identifier.rb +33 -0
- data/lib/kameleoon/data/operating_system.rb +4 -0
- data/lib/kameleoon/data/page_view.rb +6 -1
- data/lib/kameleoon/data/unique_identifier.rb +11 -0
- data/lib/kameleoon/data/user_agent.rb +4 -0
- data/lib/kameleoon/data/visitor_visits.rb +12 -1
- data/lib/kameleoon/hybrid/manager.rb +13 -4
- data/lib/kameleoon/kameleoon_client.rb +348 -146
- data/lib/kameleoon/kameleoon_client_config.rb +64 -17
- data/lib/kameleoon/kameleoon_client_factory.rb +15 -2
- data/lib/kameleoon/logging/default_logger.rb +20 -0
- data/lib/kameleoon/logging/kameleoon_logger.rb +77 -0
- data/lib/kameleoon/logging/logger.rb +12 -0
- data/lib/kameleoon/managers/data/data_manager.rb +36 -0
- data/lib/kameleoon/managers/remote_data/remote_data_manager.rb +33 -18
- data/lib/kameleoon/managers/remote_data/remote_visitor_data.rb +42 -16
- data/lib/kameleoon/managers/tracking/tracking_builder.rb +149 -0
- data/lib/kameleoon/managers/tracking/tracking_manager.rb +97 -0
- data/lib/kameleoon/managers/tracking/visitor_tracking_registry.rb +94 -0
- data/lib/kameleoon/managers/warehouse/warehouse_manager.rb +22 -5
- data/lib/kameleoon/network/access_token_source.rb +46 -14
- data/lib/kameleoon/network/cookie/cookie_manager.rb +45 -7
- data/lib/kameleoon/network/net_provider.rb +2 -3
- data/lib/kameleoon/network/network_manager.rb +16 -21
- data/lib/kameleoon/network/request.rb +14 -3
- data/lib/kameleoon/network/response.rb +4 -0
- data/lib/kameleoon/network/url_provider.rb +11 -10
- data/lib/kameleoon/real_time/real_time_configuration_service.rb +10 -11
- data/lib/kameleoon/sdk_version.rb +31 -0
- data/lib/kameleoon/targeting/condition.rb +6 -2
- data/lib/kameleoon/targeting/condition_factory.rb +3 -0
- data/lib/kameleoon/targeting/conditions/browser_condition.rb +3 -3
- data/lib/kameleoon/targeting/conditions/cookie_condition.rb +10 -10
- data/lib/kameleoon/targeting/conditions/geolocation_condition.rb +0 -1
- data/lib/kameleoon/targeting/conditions/kcs_heat_range_condition.rb +37 -0
- data/lib/kameleoon/targeting/conditions/number_condition.rb +4 -4
- data/lib/kameleoon/targeting/conditions/operating_system_condition.rb +1 -2
- data/lib/kameleoon/targeting/conditions/sdk_language_condition.rb +2 -1
- data/lib/kameleoon/targeting/conditions/segment_condition.rb +3 -3
- data/lib/kameleoon/targeting/conditions/string_value_condition.rb +2 -1
- data/lib/kameleoon/targeting/conditions/target_feature_flag_condition.rb +7 -11
- data/lib/kameleoon/targeting/conditions/time_elapsed_since_visit_condition.rb +1 -2
- data/lib/kameleoon/targeting/conditions/visit_number_today_condition.rb +4 -4
- data/lib/kameleoon/targeting/conditions/visit_number_total_condition.rb +5 -3
- data/lib/kameleoon/targeting/conditions/visitor_new_return_condition.rb +7 -6
- data/lib/kameleoon/targeting/models.rb +0 -14
- data/lib/kameleoon/targeting/targeting_manager.rb +37 -7
- data/lib/kameleoon/targeting/tree_builder.rb +10 -5
- data/lib/kameleoon/types/remote_visitor_data_filter.rb +21 -3
- data/lib/kameleoon/types/variable.rb +21 -0
- data/lib/kameleoon/types/variation.rb +22 -0
- data/lib/kameleoon/utils.rb +18 -0
- data/lib/kameleoon/version.rb +1 -27
- metadata +16 -2
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'kameleoon/data/custom_data'
|
|
4
|
+
require 'kameleoon/logging/kameleoon_logger'
|
|
5
|
+
require 'kameleoon/network/activity_event'
|
|
6
|
+
require 'kameleoon/network/uri_helper'
|
|
7
|
+
|
|
8
|
+
module Kameleoon
|
|
9
|
+
module Managers
|
|
10
|
+
module Tracking
|
|
11
|
+
class TrackingBuilder
|
|
12
|
+
attr_reader :visitor_codes_to_send, :visitor_codes_to_keep, :tracking_lines, :unsent_visitor_data
|
|
13
|
+
|
|
14
|
+
def initialize(visitor_codes, data_file, visitor_manager, request_size_limit)
|
|
15
|
+
@visitor_codes = visitor_codes
|
|
16
|
+
@data_file = data_file
|
|
17
|
+
@visitor_manager = visitor_manager
|
|
18
|
+
@request_size_limit = request_size_limit
|
|
19
|
+
@built = false
|
|
20
|
+
@total_size = 0
|
|
21
|
+
# result
|
|
22
|
+
@visitor_codes_to_send = []
|
|
23
|
+
@visitor_codes_to_keep = []
|
|
24
|
+
@tracking_lines = []
|
|
25
|
+
@unsent_visitor_data = []
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Not thread-safe
|
|
29
|
+
def build
|
|
30
|
+
return if @built
|
|
31
|
+
|
|
32
|
+
@visitor_codes.each do |visitor_code|
|
|
33
|
+
if @total_size <= @request_size_limit
|
|
34
|
+
visitor = @visitor_manager.get_visitor(visitor_code)
|
|
35
|
+
is_consent_given = consent_given?(visitor)
|
|
36
|
+
data = collect_tracking_data(visitor_code, visitor, is_consent_given)
|
|
37
|
+
if !data.empty?
|
|
38
|
+
log_visitor_track_sending(visitor_code, is_consent_given, data)
|
|
39
|
+
@visitor_codes_to_send.push(visitor_code)
|
|
40
|
+
@unsent_visitor_data.concat(data)
|
|
41
|
+
else
|
|
42
|
+
log_visitor_track_no_data(visitor_code, is_consent_given)
|
|
43
|
+
end
|
|
44
|
+
else
|
|
45
|
+
@visitor_codes_to_keep.push(visitor_code)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
@built = true
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
private
|
|
52
|
+
|
|
53
|
+
def log_visitor_track_sending(visitor_code, is_consent_given, data)
|
|
54
|
+
Logging::KameleoonLogger.debug(
|
|
55
|
+
"Sending tracking request for unsent data %s of visitor '%s' with given (or not required) consent %s",
|
|
56
|
+
data, visitor_code, is_consent_given
|
|
57
|
+
)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def log_visitor_track_no_data(visitor_code, is_consent_given)
|
|
61
|
+
Logging::KameleoonLogger.debug(
|
|
62
|
+
"No data to send for visitor '%s' with given (or not required) consent %s",
|
|
63
|
+
visitor_code, is_consent_given
|
|
64
|
+
)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def consent_given?(visitor)
|
|
68
|
+
!@data_file.settings.is_consent_required || visitor&.legal_consent
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def collect_tracking_data(visitor_code, visitor, is_consent_given)
|
|
72
|
+
use_mapping_value, visitor = create_self_visitor_link_if_required(visitor_code, visitor)
|
|
73
|
+
Logging::KameleoonLogger.info(
|
|
74
|
+
"'%s' was used as a %s for visitor data tracking.",
|
|
75
|
+
visitor_code, (use_mapping_value ? 'mapping value' : 'visitor code')
|
|
76
|
+
)
|
|
77
|
+
unsent_data = get_unsent_visitor_data(visitor, is_consent_given)
|
|
78
|
+
collect_tracking_lines(visitor_code, visitor, unsent_data, use_mapping_value)
|
|
79
|
+
unsent_data
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def create_self_visitor_link_if_required(visitor_code, visitor)
|
|
83
|
+
is_mapped = !visitor&.mapping_identifier.nil?
|
|
84
|
+
is_unique_identifier = visitor&.is_unique_identifier
|
|
85
|
+
# need to find if anonymous visitor is behind unique (anonym doesn't exist if MappingIdentifier == null)
|
|
86
|
+
if is_unique_identifier && !is_mapped
|
|
87
|
+
# We haven't anonymous behind, in this case we should create "fake" anonymous with id == visitorCode
|
|
88
|
+
# and link it with with mapping value == visitorCode (like we do as we have real anonymous visitor)
|
|
89
|
+
visitor = @visitor_manager.add_data(
|
|
90
|
+
visitor_code, CustomData.new(@data_file.custom_data_info.mapping_identifier_index, visitor_code)
|
|
91
|
+
)
|
|
92
|
+
end
|
|
93
|
+
use_mapping_value = is_unique_identifier && (visitor_code != visitor&.mapping_identifier)
|
|
94
|
+
[use_mapping_value, visitor]
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def get_unsent_visitor_data(visitor, is_consent_given)
|
|
98
|
+
unsent_data = []
|
|
99
|
+
unless visitor.nil?
|
|
100
|
+
if is_consent_given
|
|
101
|
+
visitor.enumerate_sendable_data do |data|
|
|
102
|
+
unsent_data.push(data) if data.unsent
|
|
103
|
+
next true
|
|
104
|
+
end
|
|
105
|
+
else
|
|
106
|
+
visitor.conversions.enumerate do |c|
|
|
107
|
+
unsent_data.push(c) if c.unsent
|
|
108
|
+
next true
|
|
109
|
+
end
|
|
110
|
+
if @data_file.has_any_targeted_delivery_rule
|
|
111
|
+
visitor.variations.enumerate do |av|
|
|
112
|
+
unsent_data.push(av) if av.unsent && (av.rule_type == Configuration::RuleType::TARGETED_DELIVERY)
|
|
113
|
+
next true
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
unsent_data.push(Network::ActivityEvent.new) if unsent_data.empty? && is_consent_given
|
|
119
|
+
unsent_data
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def collect_tracking_lines(visitor_code, visitor, unsent_data, use_mapping_value)
|
|
123
|
+
visitor_code_param = Network::UriHelper.encode_query(
|
|
124
|
+
{ (use_mapping_value ? :mappingValue : :visitorCode) => visitor_code }
|
|
125
|
+
)
|
|
126
|
+
user_agent = visitor&.user_agent
|
|
127
|
+
unsent_data.each do |data|
|
|
128
|
+
line = data.obtain_full_post_text_line
|
|
129
|
+
next if line.empty?
|
|
130
|
+
|
|
131
|
+
line = add_line_params(line, visitor_code_param, user_agent)
|
|
132
|
+
@tracking_lines.push(line)
|
|
133
|
+
@total_size += line.size
|
|
134
|
+
user_agent = nil
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def add_line_params(tracking_line, visitor_code_param, user_agent)
|
|
139
|
+
tracking_line += "&#{visitor_code_param}"
|
|
140
|
+
unless user_agent.nil?
|
|
141
|
+
user_agent_param = Network::UriHelper.encode_query({ userAgent: user_agent })
|
|
142
|
+
tracking_line += "&#{user_agent_param}"
|
|
143
|
+
end
|
|
144
|
+
tracking_line
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'kameleoon/data/data'
|
|
4
|
+
require 'kameleoon/logging/kameleoon_logger'
|
|
5
|
+
require 'kameleoon/managers/tracking/tracking_builder'
|
|
6
|
+
require 'kameleoon/managers/tracking/visitor_tracking_registry'
|
|
7
|
+
|
|
8
|
+
module Kameleoon
|
|
9
|
+
module Managers
|
|
10
|
+
module Tracking
|
|
11
|
+
class TrackingManager
|
|
12
|
+
LINES_DELIMETER = "\n"
|
|
13
|
+
REQUEST_SIZE_LIMIT = 2560 * 1024 # 2.5 * 1024^2 characters
|
|
14
|
+
|
|
15
|
+
def initialize(
|
|
16
|
+
data_manager, network_manager, visitor_manager, track_interval_seconds, scheduler
|
|
17
|
+
)
|
|
18
|
+
Logging::KameleoonLogger.debug(
|
|
19
|
+
'CALL: TrackingManager.new(data_manager, network_manager. visitor_manager, ' \
|
|
20
|
+
'track_interval_seconds: %s, scheduler)', track_interval_seconds
|
|
21
|
+
)
|
|
22
|
+
@tracking_visitors = LockVisitorTrackingRegistry.new(visitor_manager)
|
|
23
|
+
@data_manager = data_manager
|
|
24
|
+
@network_manager = network_manager
|
|
25
|
+
@visitor_manager = visitor_manager
|
|
26
|
+
@track_all_job = scheduler.schedule_every(track_interval_seconds, method(:track_all)) unless scheduler.nil?
|
|
27
|
+
Logging::KameleoonLogger.debug(
|
|
28
|
+
'RETURN: TrackingManager.new(data_manager, network_manager. visitor_manager, ' \
|
|
29
|
+
'track_interval_seconds: %s, scheduler)', track_interval_seconds
|
|
30
|
+
)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def stop
|
|
34
|
+
Logging::KameleoonLogger.debug('CALL: TrackingManager.stop')
|
|
35
|
+
@track_all_job&.unschedule
|
|
36
|
+
@track_all_job = nil
|
|
37
|
+
Logging::KameleoonLogger.debug('RETURN: TrackingManager.stop')
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def add_visitor_code(visitor_code)
|
|
41
|
+
Logging::KameleoonLogger.debug("CALL: TrackingManager.add_visitor_code(visitor_code: '%s')", visitor_code)
|
|
42
|
+
@tracking_visitors.add(visitor_code)
|
|
43
|
+
Logging::KameleoonLogger.debug("RETURN: TrackingManager.add_visitor_code(visitor_code: '%s')", visitor_code)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def track_all
|
|
47
|
+
Logging::KameleoonLogger.debug('CALL: TrackingManager.track_all')
|
|
48
|
+
track(@tracking_visitors.extract)
|
|
49
|
+
Logging::KameleoonLogger.debug('RETURN: TrackingManager.track_all')
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def track_visitor(visitor_code)
|
|
53
|
+
Logging::KameleoonLogger.debug("CALL: TrackingManager.track_visitor(visitor_code: '%s')", visitor_code)
|
|
54
|
+
track([visitor_code])
|
|
55
|
+
Logging::KameleoonLogger.debug("RETURN: TrackingManager.track_visitor(visitor_code: '%s')", visitor_code)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
private
|
|
59
|
+
|
|
60
|
+
def track(visitor_codes)
|
|
61
|
+
builder = TrackingBuilder.new(visitor_codes, @data_manager.data_file, @visitor_manager, REQUEST_SIZE_LIMIT)
|
|
62
|
+
builder.build
|
|
63
|
+
unless builder.visitor_codes_to_keep.empty?
|
|
64
|
+
Logging::KameleoonLogger.warning(
|
|
65
|
+
'Visitor data to be tracked exceeded the request size limit. ' \
|
|
66
|
+
'Some visitor data is kept to be sent later. ' \
|
|
67
|
+
'If it is not caused by the peak load, decreasing the tracking interval is recommended.'
|
|
68
|
+
)
|
|
69
|
+
@tracking_visitors.add_all(builder.visitor_codes_to_keep)
|
|
70
|
+
end
|
|
71
|
+
perform_tracking_request(builder.visitor_codes_to_send, builder.unsent_visitor_data, builder.tracking_lines)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def perform_tracking_request(visitor_codes, visitor_data, tracking_lines)
|
|
75
|
+
return if visitor_data.empty?
|
|
76
|
+
|
|
77
|
+
lines = tracking_lines.join(LINES_DELIMETER)
|
|
78
|
+
# mark unsent data as transmitted
|
|
79
|
+
visitor_data.each { |d| d.mark_as_transmitting if d.is_a?(Kameleoon::Data) }
|
|
80
|
+
Thread.new do
|
|
81
|
+
result = @network_manager.send_tracking_data(lines)
|
|
82
|
+
if result != false
|
|
83
|
+
Logging::KameleoonLogger.debug('Successful request for tracking visitors: %s, data: %s',
|
|
84
|
+
visitor_codes, visitor_data)
|
|
85
|
+
visitor_data.each { |d| d.mark_as_sent if d.is_a?(Kameleoon::Data) }
|
|
86
|
+
else
|
|
87
|
+
Logging::KameleoonLogger.debug('Failed request for tracking visitors: %s, data: %s',
|
|
88
|
+
visitor_codes, visitor_data)
|
|
89
|
+
visitor_data.each { |d| d.mark_as_unsent if d.is_a?(Kameleoon::Data) }
|
|
90
|
+
@tracking_visitors.add_all(visitor_codes)
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'set'
|
|
4
|
+
|
|
5
|
+
module Kameleoon
|
|
6
|
+
module Managers
|
|
7
|
+
module Tracking
|
|
8
|
+
class LockVisitorTrackingRegistry
|
|
9
|
+
LIMITED_EXTRACTION_THRESHOLD_COEFFICIENT = 2
|
|
10
|
+
REMOVAL_FACTOR = 0.8
|
|
11
|
+
|
|
12
|
+
def initialize(visitor_manager, storage_limit = 1_000_000, extraction_limit = 20_000)
|
|
13
|
+
@visitor_manager = visitor_manager
|
|
14
|
+
@storage_limit = storage_limit
|
|
15
|
+
@extraction_limit = extraction_limit
|
|
16
|
+
@visitors = Set.new
|
|
17
|
+
@mutex = Mutex.new
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def add(visitor_code)
|
|
21
|
+
@mutex.synchronize { @visitors.add(visitor_code) }
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def add_all(visitor_codes)
|
|
25
|
+
@mutex.synchronize do
|
|
26
|
+
@visitors.merge(visitor_codes)
|
|
27
|
+
if @visitors.size > @storage_limit
|
|
28
|
+
erase_nonexistent_visitors
|
|
29
|
+
erase_to_storage_limit
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def extract
|
|
35
|
+
should_extract_all_be_used ? extract_all : extract_limited
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
def should_extract_all_be_used
|
|
41
|
+
@visitors.size < @extraction_limit * LIMITED_EXTRACTION_THRESHOLD_COEFFICIENT
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def extract_all
|
|
45
|
+
old_visitors = nil
|
|
46
|
+
new_visitors = Set.new
|
|
47
|
+
@mutex.synchronize do
|
|
48
|
+
old_visitors = @visitors
|
|
49
|
+
@visitors = new_visitors
|
|
50
|
+
end
|
|
51
|
+
old_visitors
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def extract_limited
|
|
55
|
+
extracted = nil
|
|
56
|
+
@mutex.synchronize do
|
|
57
|
+
if should_extract_all_be_used
|
|
58
|
+
extracted = extract_all
|
|
59
|
+
next
|
|
60
|
+
end
|
|
61
|
+
i = 0
|
|
62
|
+
extracted = []
|
|
63
|
+
@visitors.each do |vc|
|
|
64
|
+
break if i >= @extraction_limit
|
|
65
|
+
|
|
66
|
+
extracted.push(vc)
|
|
67
|
+
i += 1
|
|
68
|
+
@visitors.delete(vc)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
extracted
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Not thread-safe
|
|
75
|
+
def erase_nonexistent_visitors
|
|
76
|
+
@visitors.delete_if { |vc| @visitor_manager.get_visitor(vc).nil? }
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Not thread-safe
|
|
80
|
+
def erase_to_storage_limit
|
|
81
|
+
visitors_to_remove_count = @visitors.size - (@storage_limit * REMOVAL_FACTOR).to_i
|
|
82
|
+
return if visitors_to_remove_count <= 0
|
|
83
|
+
|
|
84
|
+
@visitors.each do |vc|
|
|
85
|
+
break if visitors_to_remove_count.zero?
|
|
86
|
+
|
|
87
|
+
visitors_to_remove_count -= 1
|
|
88
|
+
@visitors.delete(vc)
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'kameleoon/data/custom_data'
|
|
4
|
+
require 'kameleoon/logging/kameleoon_logger'
|
|
4
5
|
require 'kameleoon/utils'
|
|
5
6
|
|
|
6
7
|
module Kameleoon
|
|
@@ -9,24 +10,40 @@ module Kameleoon
|
|
|
9
10
|
class WarehouseManager
|
|
10
11
|
WAREHOUSE_AUDIENCES_FIELD_NAME = 'warehouseAudiences'
|
|
11
12
|
|
|
12
|
-
def initialize(network_manager, visitor_manager
|
|
13
|
+
def initialize(network_manager, visitor_manager)
|
|
14
|
+
Logging::KameleoonLogger.debug('CALL: WarehouseManager.new(networkManager, visitorManager)')
|
|
13
15
|
@network_manager = network_manager
|
|
14
16
|
@visitor_manager = visitor_manager
|
|
15
|
-
|
|
17
|
+
Logging::KameleoonLogger.debug('RETURN: WarehouseManager.new(networkManager, visitorManager)')
|
|
16
18
|
end
|
|
17
19
|
|
|
18
20
|
def get_visitor_warehouse_audience(visitor_code, custom_data_index, warehouse_key = nil, timeout = nil)
|
|
21
|
+
Logging::KameleoonLogger.debug(
|
|
22
|
+
"CALL: WarehouseManager.get_visitor_warehouse_audience(visitor_code: '%s', custom_data_index: %s," \
|
|
23
|
+
" warehouse_key: '%s', timeout: %s)", visitor_code, custom_data_index, warehouse_key, timeout
|
|
24
|
+
)
|
|
19
25
|
Utils::VisitorCode.validate(visitor_code)
|
|
20
26
|
remote_data_key = warehouse_key.nil? || warehouse_key.empty? ? visitor_code : warehouse_key
|
|
21
27
|
response = @network_manager.get_remote_data(remote_data_key, timeout)
|
|
22
|
-
|
|
28
|
+
unless response.is_a?(String)
|
|
29
|
+
Logging::KameleoonLogger.debug(
|
|
30
|
+
"RETURN: WarehouseManager.get_visitor_warehouse_audience(visitor_code: '%s', custom_data_index: %s," \
|
|
31
|
+
" warehouse_key: '%s', timeout: %s) -> (custom_data: nil)",
|
|
32
|
+
visitor_code, custom_data_index, warehouse_key, timeout
|
|
33
|
+
)
|
|
34
|
+
return nil
|
|
35
|
+
end
|
|
23
36
|
|
|
24
37
|
remote_data = JSON.parse(response)
|
|
25
38
|
warehouse_audiences = remote_data.is_a?(Hash) ? remote_data[WAREHOUSE_AUDIENCES_FIELD_NAME] : nil
|
|
26
39
|
data_values = warehouse_audiences.is_a?(Hash) ? warehouse_audiences.keys : []
|
|
27
40
|
warehouse_audiences_data = CustomData.new(custom_data_index, *data_values)
|
|
28
|
-
|
|
29
|
-
|
|
41
|
+
@visitor_manager.add_data(visitor_code, warehouse_audiences_data)
|
|
42
|
+
Logging::KameleoonLogger.debug(
|
|
43
|
+
"RETURN: WarehouseManager.get_visitor_warehouse_audience(visitor_code: '%s', custom_data_index: %s," \
|
|
44
|
+
" warehouse_key: '%s', timeout: %s) -> (custom_data: %s)",
|
|
45
|
+
visitor_code, custom_data_index, warehouse_key, timeout, warehouse_audiences_data
|
|
46
|
+
)
|
|
30
47
|
warehouse_audiences_data
|
|
31
48
|
end
|
|
32
49
|
end
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
require 'json'
|
|
4
4
|
require 'net/http'
|
|
5
5
|
require 'time'
|
|
6
|
+
require 'kameleoon/utils'
|
|
7
|
+
require 'kameleoon/logging/kameleoon_logger'
|
|
6
8
|
|
|
7
9
|
module Kameleoon
|
|
8
10
|
module Network
|
|
@@ -12,25 +14,38 @@ module Kameleoon
|
|
|
12
14
|
JWT_ACCESS_TOKEN_FIELD = 'access_token'
|
|
13
15
|
JWT_EXPIRES_IN_FIELD = 'expires_in'
|
|
14
16
|
|
|
15
|
-
def initialize(network_manager, client_id, client_secret
|
|
17
|
+
def initialize(network_manager, client_id, client_secret)
|
|
18
|
+
Logging::KameleoonLogger.debug(lambda {
|
|
19
|
+
format("CALL: AccessTokenSource.new(network_manager, client_id: '%s', client_secret: '%s')",
|
|
20
|
+
Utils::Strval.secret(client_id), Utils::Strval.secret(client_secret))
|
|
21
|
+
})
|
|
16
22
|
@network_manager = network_manager
|
|
17
23
|
@client_id = client_id
|
|
18
24
|
@client_secret = client_secret
|
|
19
25
|
@fetching = false
|
|
20
|
-
|
|
26
|
+
Logging::KameleoonLogger.debug(lambda {
|
|
27
|
+
format("RETURN: AccessTokenSource.new(network_manager, client_id: '%s', client_secret: '%s')",
|
|
28
|
+
Utils::Strval.secret(client_id), Utils::Strval.secret(client_secret))
|
|
29
|
+
})
|
|
21
30
|
end
|
|
22
31
|
|
|
23
32
|
def get_token(timeout = nil)
|
|
33
|
+
Logging::KameleoonLogger.debug('CALL: AccessTokenSource.getToken(timeout: %s)', timeout)
|
|
24
34
|
now = Time.new.to_i
|
|
25
35
|
token = @cached_token
|
|
26
36
|
return call_fetch_token(timeout) if token.nil? || token.expired?(now)
|
|
27
37
|
|
|
28
38
|
run_fetch_token if !@fetching && token.obsolete?(now)
|
|
39
|
+
Logging::KameleoonLogger.debug("RETURN: AccessTokenSource.getToken(timeout: %s) -> (token: '%s')",
|
|
40
|
+
timeout, token.value)
|
|
29
41
|
token.value
|
|
30
42
|
end
|
|
31
43
|
|
|
32
44
|
def discard_token(token)
|
|
45
|
+
Logging::KameleoonLogger.debug("CALL: AccessTokenSource.discard_token(token: '%s')", token)
|
|
33
46
|
@cached_token = nil if @cached_token&.value == token
|
|
47
|
+
Logging::KameleoonLogger.debug("RETURN: AccessTokenSource.discard_token(token: '%s')", token)
|
|
48
|
+
@cached_token
|
|
34
49
|
end
|
|
35
50
|
|
|
36
51
|
private
|
|
@@ -40,7 +55,7 @@ module Kameleoon
|
|
|
40
55
|
fetch_token(timeout)
|
|
41
56
|
rescue StandardError => e
|
|
42
57
|
@fetching = false
|
|
43
|
-
|
|
58
|
+
Logging::KameleoonLogger.error("Failed to call access token fetching: #{e}")
|
|
44
59
|
nil
|
|
45
60
|
end
|
|
46
61
|
|
|
@@ -50,13 +65,14 @@ module Kameleoon
|
|
|
50
65
|
Thread.new { fetch_token }
|
|
51
66
|
rescue StandardError => e
|
|
52
67
|
@fetching = false
|
|
53
|
-
|
|
68
|
+
Logging::KameleoonLogger.error("Failed to run access token fetching: #{e}")
|
|
54
69
|
end
|
|
55
70
|
|
|
56
71
|
def fetch_token(timeout = nil)
|
|
72
|
+
Logging::KameleoonLogger.debug('CALL: AccessTokenSource.fetch_token(timeout: %s)', timeout)
|
|
57
73
|
response_content = @network_manager.fetch_access_jwtoken(@client_id, @client_secret, timeout)
|
|
58
74
|
unless response_content
|
|
59
|
-
|
|
75
|
+
Logging::KameleoonLogger.error('Failed to fetch access JWT')
|
|
60
76
|
return nil
|
|
61
77
|
end
|
|
62
78
|
begin
|
|
@@ -64,14 +80,18 @@ module Kameleoon
|
|
|
64
80
|
token = jwt[JWT_ACCESS_TOKEN_FIELD]
|
|
65
81
|
expires_in = jwt[JWT_EXPIRES_IN_FIELD]
|
|
66
82
|
rescue JSON::ParserError => e
|
|
67
|
-
|
|
83
|
+
Logging::KameleoonLogger.error("Failed to parse access JWT: #{e}")
|
|
68
84
|
return nil
|
|
69
85
|
end
|
|
70
86
|
unless token.is_a?(String) && !token.empty? && expires_in.is_a?(Integer) && expires_in.positive?
|
|
71
|
-
|
|
87
|
+
Logging::KameleoonLogger.error('Failed to read access JWT')
|
|
72
88
|
return nil
|
|
73
89
|
end
|
|
74
|
-
handle_fetched_token(token, expires_in)
|
|
90
|
+
token = handle_fetched_token(token, expires_in)
|
|
91
|
+
Logging::KameleoonLogger.debug(
|
|
92
|
+
"RETURN: AccessTokenSource.fetch_token(timeout: %s) -> (token: '%s')", timeout, token
|
|
93
|
+
)
|
|
94
|
+
token
|
|
75
95
|
ensure
|
|
76
96
|
@fetching = false
|
|
77
97
|
end
|
|
@@ -83,9 +103,14 @@ module Kameleoon
|
|
|
83
103
|
obs_time = now + expires_in - TOKEN_OBSOLESCENCE_GAP
|
|
84
104
|
else
|
|
85
105
|
obs_time = exp_time
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
106
|
+
if expires_in <= TOKEN_EXPIRATION_GAP
|
|
107
|
+
Logging::KameleoonLogger.error(
|
|
108
|
+
'Access token life time (%ss) is not long enough to cache the token', expires_in
|
|
109
|
+
)
|
|
110
|
+
else
|
|
111
|
+
Logging::KameleoonLogger.warning(
|
|
112
|
+
'Access token life time (%ss) is not long enough to refresh cached token in background', expires_in
|
|
113
|
+
)
|
|
89
114
|
end
|
|
90
115
|
end
|
|
91
116
|
@cached_token = ExpiringToken.new(token, exp_time, obs_time)
|
|
@@ -112,14 +137,21 @@ module Kameleoon
|
|
|
112
137
|
end
|
|
113
138
|
|
|
114
139
|
class AccessTokenSourceFactory
|
|
115
|
-
def initialize(client_id, client_secret
|
|
140
|
+
def initialize(client_id, client_secret)
|
|
141
|
+
Logging::KameleoonLogger.debug(lambda {
|
|
142
|
+
format("CALL: AccessTokenSourceFactory.new(client_id: '%s', client_secret: '%s')",
|
|
143
|
+
Utils::Strval.secret(client_id), Utils::Strval.secret(client_secret))
|
|
144
|
+
})
|
|
116
145
|
@client_id = client_id
|
|
117
146
|
@client_secret = client_secret
|
|
118
|
-
|
|
147
|
+
Logging::KameleoonLogger.debug(lambda {
|
|
148
|
+
format("RETURN: AccessTokenSourceFactory.new(client_id: '%s', client_secret: '%s')",
|
|
149
|
+
Utils::Strval.secret(client_id), Utils::Strval.secret(client_secret))
|
|
150
|
+
})
|
|
119
151
|
end
|
|
120
152
|
|
|
121
153
|
def create(network_manager)
|
|
122
|
-
AccessTokenSource.new(network_manager, @client_id, @client_secret
|
|
154
|
+
AccessTokenSource.new(network_manager, @client_id, @client_secret)
|
|
123
155
|
end
|
|
124
156
|
end
|
|
125
157
|
end
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'kameleoon/utils'
|
|
4
|
+
require 'kameleoon/logging/kameleoon_logger'
|
|
4
5
|
|
|
5
6
|
module Kameleoon
|
|
6
7
|
module Network
|
|
@@ -10,49 +11,75 @@ module Kameleoon
|
|
|
10
11
|
COOKIE_TTL_SECONDS = 380 * 86_400 # 380 days in seconds
|
|
11
12
|
|
|
12
13
|
class CookieManager
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
@consent_required = false
|
|
14
|
+
def initialize(data_manager, top_level_domain)
|
|
15
|
+
Logging::KameleoonLogger.debug("CALL: CookieManager.new(top_level_domain: '%s')", top_level_domain)
|
|
16
|
+
@data_manager = data_manager
|
|
17
17
|
@top_level_domain = top_level_domain
|
|
18
|
+
Logging::KameleoonLogger.debug("RETURN: CookieManager.new(top_level_domain: '%s')", top_level_domain)
|
|
18
19
|
end
|
|
19
20
|
|
|
20
21
|
def get_or_add(cookies, default_visitor_code = nil)
|
|
21
22
|
return if cookies.nil?
|
|
22
23
|
|
|
24
|
+
Logging::KameleoonLogger.debug(
|
|
25
|
+
"CALL: CookieManager.get_or_add(cookies: %s, default_visitor_code: '%s')",
|
|
26
|
+
cookies, default_visitor_code
|
|
27
|
+
)
|
|
28
|
+
|
|
23
29
|
visitor_code = get_visitor_code_from_cookies(cookies)
|
|
24
30
|
unless visitor_code.nil?
|
|
25
31
|
Utils::VisitorCode.validate(visitor_code)
|
|
32
|
+
Logging::KameleoonLogger.debug("Read visitor code '%s' from cookies %s", visitor_code, cookies)
|
|
26
33
|
# Remove adding cookies when we will be sure that it doesn't break anything
|
|
27
|
-
add(visitor_code, cookies) unless @consent_required
|
|
34
|
+
add(visitor_code, cookies) unless @data_manager.consent_required?
|
|
35
|
+
Logging::KameleoonLogger.debug(
|
|
36
|
+
"RETURN: CookieManager.get_or_add(cookies: %s, default_visitor_code: '%s') -> (visitor_code: '%s')",
|
|
37
|
+
cookies, default_visitor_code, visitor_code
|
|
38
|
+
)
|
|
28
39
|
return visitor_code
|
|
29
40
|
end
|
|
30
41
|
|
|
31
42
|
if default_visitor_code.nil?
|
|
32
43
|
visitor_code = Utils::VisitorCode.generate
|
|
33
|
-
|
|
44
|
+
Logging::KameleoonLogger.debug("Generated new visitor code '%s'", visitor_code)
|
|
45
|
+
add(visitor_code, cookies) unless @data_manager.consent_required?
|
|
46
|
+
Logging::KameleoonLogger.debug(
|
|
47
|
+
"RETURN: CookieManager.get_or_add(cookies: %s, default_visitor_code: '%s') -> (visitor_code: '%s')",
|
|
48
|
+
cookies, default_visitor_code, visitor_code
|
|
49
|
+
)
|
|
34
50
|
return visitor_code
|
|
35
51
|
end
|
|
36
52
|
|
|
37
53
|
visitor_code = default_visitor_code
|
|
38
54
|
Utils::VisitorCode.validate(visitor_code)
|
|
55
|
+
Logging::KameleoonLogger.debug("Used default visitor code '{%s}'", default_visitor_code)
|
|
39
56
|
add(visitor_code, cookies)
|
|
57
|
+
Logging::KameleoonLogger.debug(
|
|
58
|
+
"RETURN: CookieManager.get_or_add(cookies: %s, default_visitor_code: '%s') -> (visitor_code: '%s')",
|
|
59
|
+
cookies, default_visitor_code, visitor_code)
|
|
40
60
|
visitor_code
|
|
41
61
|
end
|
|
42
62
|
|
|
43
63
|
def update(visitor_code, consent, cookies)
|
|
44
64
|
return if cookies.nil?
|
|
45
65
|
|
|
66
|
+
Logging::KameleoonLogger.debug("CALL: CookieManager.update(visitor_code: '%s', consent: %s, cookies: %s)",
|
|
67
|
+
visitor_code, consent, cookies)
|
|
68
|
+
|
|
46
69
|
if consent
|
|
47
70
|
add(visitor_code, cookies)
|
|
48
71
|
else
|
|
49
72
|
remove(cookies)
|
|
50
73
|
end
|
|
74
|
+
Logging::KameleoonLogger.debug("RETURN: CookieManager.update(visitor_code: '%s', consent: %s, cookies: %s)",
|
|
75
|
+
visitor_code, consent, cookies)
|
|
51
76
|
end
|
|
52
77
|
|
|
53
78
|
private
|
|
54
79
|
|
|
55
80
|
def add(visitor_code, cookies)
|
|
81
|
+
Logging::KameleoonLogger.debug("CALL: CookieManager.add(visitor_code: '%s', cookies: %s)",
|
|
82
|
+
visitor_code, cookies)
|
|
56
83
|
cookie = {
|
|
57
84
|
value: visitor_code,
|
|
58
85
|
expires: Time.now + COOKIE_TTL_SECONDS,
|
|
@@ -60,13 +87,20 @@ module Kameleoon
|
|
|
60
87
|
domain: @top_level_domain
|
|
61
88
|
}
|
|
62
89
|
cookies[VISITOR_CODE_COOKIE] = cookie
|
|
90
|
+
Logging::KameleoonLogger.debug("RETURN: CookieManager.add(visitor_code: '%s', cookies: %s)",
|
|
91
|
+
visitor_code, cookies)
|
|
92
|
+
cookies
|
|
63
93
|
end
|
|
64
94
|
|
|
65
95
|
def remove(cookies)
|
|
66
|
-
cookies
|
|
96
|
+
Logging::KameleoonLogger.debug('CALL: CookieManager.remove(cookies: %s)', cookies)
|
|
97
|
+
cookies[VISITOR_CODE_COOKIE] = nil if @data_manager.consent_required?
|
|
98
|
+
Logging::KameleoonLogger.debug('RETURN: CookieManager.remove(cookies: %s)', cookies)
|
|
99
|
+
cookies
|
|
67
100
|
end
|
|
68
101
|
|
|
69
102
|
def get_visitor_code_from_cookies(cookies)
|
|
103
|
+
Logging::KameleoonLogger.debug('CALL: CookieManager.get_visitor_code_from_cookies(cookies: %s)', cookies)
|
|
70
104
|
cookie = cookies[VISITOR_CODE_COOKIE]
|
|
71
105
|
case cookie
|
|
72
106
|
when String
|
|
@@ -76,6 +110,10 @@ module Kameleoon
|
|
|
76
110
|
end
|
|
77
111
|
visitor_code = visitor_code[COOKIE_KEY_JS.size..] if visitor_code&.start_with?(COOKIE_KEY_JS)
|
|
78
112
|
visitor_code = nil if visitor_code&.empty?
|
|
113
|
+
Logging::KameleoonLogger.debug(
|
|
114
|
+
"RETURN: CookieManager.get_visitor_code_from_cookies(cookies: %s) -> (visitor_code: '%s')",
|
|
115
|
+
cookies, visitor_code
|
|
116
|
+
)
|
|
79
117
|
visitor_code
|
|
80
118
|
end
|
|
81
119
|
end
|
|
@@ -8,7 +8,7 @@ require 'kameleoon/exceptions'
|
|
|
8
8
|
module Kameleoon
|
|
9
9
|
module Network
|
|
10
10
|
class NetProvider
|
|
11
|
-
def make_request(
|
|
11
|
+
def make_request(_request)
|
|
12
12
|
raise KameleoonError, 'Call of not implemented method!'
|
|
13
13
|
end
|
|
14
14
|
|
|
@@ -18,7 +18,6 @@ module Kameleoon
|
|
|
18
18
|
headers = { 'Content-Type' => request.content_type }
|
|
19
19
|
headers.merge!(request.extra_headers) unless request.extra_headers.nil?
|
|
20
20
|
headers['Authorization'] = "Bearer #{request.access_token}" unless request.access_token.nil?
|
|
21
|
-
headers['User-Agent'] = request.user_agent unless request.user_agent.nil?
|
|
22
21
|
headers
|
|
23
22
|
end
|
|
24
23
|
|
|
@@ -50,7 +49,7 @@ module Kameleoon
|
|
|
50
49
|
body = resp.body
|
|
51
50
|
body = nil if body&.empty?
|
|
52
51
|
Response.new(nil, resp.code.to_i, body, request)
|
|
53
|
-
rescue => e
|
|
52
|
+
rescue StandardError => e
|
|
54
53
|
Response.new(e, nil, nil, request)
|
|
55
54
|
end
|
|
56
55
|
end
|