kameleoon-client-ruby 3.3.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 +37 -15
- 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 +4 -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 +197 -73
- 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 +4 -0
- data/lib/kameleoon/hybrid/manager.rb +13 -4
- data/lib/kameleoon/kameleoon_client.rb +303 -148
- 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 +32 -15
- 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 +4 -4
- 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 +4 -2
- 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/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/models.rb +0 -14
- data/lib/kameleoon/targeting/targeting_manager.rb +35 -7
- data/lib/kameleoon/targeting/tree_builder.rb +10 -5
- data/lib/kameleoon/types/remote_visitor_data_filter.rb +13 -0
- data/lib/kameleoon/types/variable.rb +4 -0
- data/lib/kameleoon/types/variation.rb +4 -0
- data/lib/kameleoon/utils.rb +18 -0
- data/lib/kameleoon/version.rb +1 -27
- metadata +12 -2
@@ -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
|
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'kameleoon/logging/kameleoon_logger'
|
3
4
|
require 'kameleoon/network/content_type'
|
4
5
|
require 'kameleoon/network/method'
|
5
6
|
require 'kameleoon/network/request'
|
@@ -21,13 +22,12 @@ module Kameleoon
|
|
21
22
|
|
22
23
|
attr_reader :environment, :default_timeout, :access_token_source, :url_provider
|
23
24
|
|
24
|
-
def initialize(environment, default_timeout, access_token_source_factory, url_provider
|
25
|
+
def initialize(environment, default_timeout, access_token_source_factory, url_provider)
|
25
26
|
@environment = environment
|
26
27
|
@default_timeout = default_timeout
|
27
28
|
@access_token_source = access_token_source_factory.create(self)
|
28
29
|
@url_provider = url_provider
|
29
30
|
@sync_net_provider = SyncNetProvider.new
|
30
|
-
@log_func = log_func
|
31
31
|
end
|
32
32
|
|
33
33
|
def fetch_configuration(timestamp = nil, timeout = nil)
|
@@ -52,17 +52,13 @@ module Kameleoon
|
|
52
52
|
make_call(request, true)
|
53
53
|
end
|
54
54
|
|
55
|
-
def send_tracking_data(
|
55
|
+
def send_tracking_data(lines, timeout = nil)
|
56
56
|
return if lines.nil? || lines.empty?
|
57
57
|
|
58
|
-
url = @url_provider.make_tracking_url
|
58
|
+
url = @url_provider.make_tracking_url
|
59
59
|
timeout = ensure_timeout(timeout)
|
60
|
-
|
61
|
-
request
|
62
|
-
Thread.new do
|
63
|
-
result = make_call(request, true, TRACKING_CALL_ATTEMPT_NUMBER - 1, TRACKING_CALL_RETRY_DELAY)
|
64
|
-
lines.each(&:mark_as_sent) if result != false
|
65
|
-
end
|
60
|
+
request = Request.new(Method::POST, url, ContentType::TEXT, timeout, data: lines)
|
61
|
+
make_call(request, true, TRACKING_CALL_ATTEMPT_NUMBER - 1, TRACKING_CALL_RETRY_DELAY)
|
66
62
|
end
|
67
63
|
|
68
64
|
def fetch_access_jwtoken(client_id, client_secret, timeout = nil)
|
@@ -81,6 +77,8 @@ module Kameleoon
|
|
81
77
|
private
|
82
78
|
|
83
79
|
def make_call(request, try_access_token_auth, retry_limit = 0, retry_delay = 0)
|
80
|
+
Logging::KameleoonLogger.debug('Running request %s with access token %s, retry limit %s, retry delay %s ms',
|
81
|
+
request, try_access_token_auth, retry_limit, retry_delay)
|
84
82
|
attempt = 0
|
85
83
|
success = false
|
86
84
|
while !success && (attempt <= retry_limit)
|
@@ -90,33 +88,30 @@ module Kameleoon
|
|
90
88
|
if response.success?
|
91
89
|
success = true
|
92
90
|
elsif !response.error.nil?
|
93
|
-
|
91
|
+
Logging::KameleoonLogger.warning("%s call '%s' failed: Error occurred during request: %s",
|
92
|
+
request.method, request.url, response.error)
|
94
93
|
else
|
95
|
-
|
94
|
+
Logging::KameleoonLogger.warning("%s call '%s' failed: Received unexpected status code '%s'",
|
95
|
+
request.method, request.url, response.code)
|
96
96
|
if response.code == 401 && request.access_token
|
97
|
-
|
97
|
+
Logging::KameleoonLogger.warning("Unexpected rejection of access token '#{request.access_token}'")
|
98
98
|
@access_token_source.discard_token(request.access_token)
|
99
99
|
if attempt == retry_limit
|
100
100
|
try_access_token_auth = false
|
101
101
|
retry_delay = 0
|
102
102
|
request.authorize(nil)
|
103
103
|
attempt -= 1
|
104
|
+
Logging::KameleoonLogger.error("Wrong Kameleoon API access token slows down the SDK's requests")
|
104
105
|
end
|
105
106
|
end
|
106
107
|
end
|
107
108
|
attempt += 1
|
108
109
|
end
|
110
|
+
Logging::KameleoonLogger.debug('Fetched response %s for request %s', response, request)
|
111
|
+
Logging::KameleoonLogger.error("Failed %s request '%s'", request.method, request.url) unless success
|
109
112
|
success ? response.body : false
|
110
113
|
end
|
111
114
|
|
112
|
-
def log_failure(request, message)
|
113
|
-
return if @log_func.nil?
|
114
|
-
|
115
|
-
premsg = "#{request.method} call '#{request.url}' "
|
116
|
-
premsg += request.data.nil? ? 'failed' : "(data '#{request.data}') failed"
|
117
|
-
@log_func.call("#{premsg}: #{message}")
|
118
|
-
end
|
119
|
-
|
120
115
|
def delay(period)
|
121
116
|
sleep(period)
|
122
117
|
end
|
@@ -5,14 +5,25 @@ module Kameleoon
|
|
5
5
|
##
|
6
6
|
# Request represents HTTP request.
|
7
7
|
class Request
|
8
|
-
attr_reader :method, :url, :content_type, :timeout, :
|
8
|
+
attr_reader :method, :url, :content_type, :timeout, :extra_headers, :data, :access_token
|
9
9
|
|
10
|
-
def
|
10
|
+
def to_s
|
11
|
+
body = 'null'
|
12
|
+
unless @data.nil?
|
13
|
+
if @data.is_a?(String)
|
14
|
+
body = @data.start_with?('grant_type=client_credentials') ? '****' : @data
|
15
|
+
else
|
16
|
+
body = @data
|
17
|
+
end
|
18
|
+
end
|
19
|
+
"HttpRequest{Method:'#{@method}',Url:'#{@url}',Headers:#{@extra_headers},Body:'#{body}'}"
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize(method, url, content_type, timeout, extra_headers: nil, data: nil)
|
11
23
|
@method = method
|
12
24
|
@url = url
|
13
25
|
@content_type = content_type
|
14
26
|
@timeout = timeout
|
15
|
-
@user_agent = user_agent
|
16
27
|
@extra_headers = extra_headers
|
17
28
|
@data = !data.nil? && data.is_a?(String) ? data.encode('UTF-8') : data
|
18
29
|
end
|