kameleoon-client-ruby 2.1.1 → 2.3.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/client.rb +78 -66
- data/lib/kameleoon/client_config.rb +44 -0
- data/lib/kameleoon/data/browser.rb +10 -4
- data/lib/kameleoon/data/conversion.rb +9 -2
- data/lib/kameleoon/data/custom_data.rb +9 -2
- data/lib/kameleoon/data/data.rb +5 -2
- data/lib/kameleoon/data/device.rb +7 -2
- data/lib/kameleoon/data/page_view.rb +10 -7
- data/lib/kameleoon/factory.rb +23 -9
- data/lib/kameleoon/network/content_type.rb +11 -0
- data/lib/kameleoon/network/method.rb +10 -0
- data/lib/kameleoon/network/net_provider.rb +91 -0
- data/lib/kameleoon/network/network_manager.rb +114 -0
- data/lib/kameleoon/network/request.rb +20 -0
- data/lib/kameleoon/network/response.rb +30 -0
- data/lib/kameleoon/network/uri_helper.rb +1 -0
- data/lib/kameleoon/version.rb +1 -1
- metadata +9 -3
- data/lib/kameleoon/request.rb +0 -76
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7d51a71ea79cd23f422a5235fce6f2e3fb94cae538b01222dd4c23b770985e59
|
4
|
+
data.tar.gz: 29f2501a20ffedf1637ee6d441b637358982761955d6235317f49b5284f17b70
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7f4855238a2e8731458757336bc964a36d537e77e7e1b30eb73b5c7e270ee59ee021813b620c0997b9781a8e062b10f3a4797eaceffcd9ca731e5e86f11f1e12
|
7
|
+
data.tar.gz: e7d3659d4de54bc0be3aadf6ed96d213bf148ce481306ad8668b589dd892a26a74e78206c6d78dd9577c915e96bab1351c19f08c95ce8b4ae85aaef237b6bd6f
|
data/lib/kameleoon/client.rb
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'kameleoon/targeting/models'
|
4
|
-
require 'kameleoon/request'
|
5
4
|
require 'kameleoon/exceptions'
|
6
5
|
require 'kameleoon/cookie'
|
6
|
+
require 'kameleoon/data/custom_data'
|
7
7
|
require 'kameleoon/data/user_agent'
|
8
8
|
require 'kameleoon/configuration/feature_flag'
|
9
9
|
require 'kameleoon/configuration/variation'
|
@@ -11,6 +11,7 @@ require 'kameleoon/configuration/settings'
|
|
11
11
|
require 'kameleoon/network/activity_event'
|
12
12
|
require 'kameleoon/network/experiment_event'
|
13
13
|
require 'kameleoon/network/url_provider'
|
14
|
+
require 'kameleoon/network/network_manager'
|
14
15
|
require 'kameleoon/real_time/real_time_configuration_service'
|
15
16
|
require 'kameleoon/storage/variation_storage'
|
16
17
|
require 'kameleoon/hybrid/manager'
|
@@ -30,30 +31,19 @@ module Kameleoon
|
|
30
31
|
# Client for Kameleoon
|
31
32
|
#
|
32
33
|
class Client
|
33
|
-
include Request
|
34
34
|
include Cookie
|
35
35
|
include Exception
|
36
36
|
|
37
37
|
##
|
38
38
|
# You should create Client with the Client Factory only.
|
39
39
|
#
|
40
|
-
def initialize(site_code,
|
41
|
-
config = YAML.load_file(path_config_file)
|
40
|
+
def initialize(site_code, config)
|
42
41
|
@site_code = site_code
|
43
|
-
|
44
|
-
refresh_interval = config['actions_configuration_refresh_interval']
|
45
|
-
@interval = refresh_interval.nil? ? interval : "#{refresh_interval}m"
|
46
|
-
data_api_url = config['tracking_url'] || Network::UrlProvider::DEFAULT_DATA_API_URL
|
47
|
-
@url_provider = Network::UrlProvider.new(@site_code, data_api_url)
|
42
|
+
read_config(config)
|
48
43
|
@real_time_configuration_service = nil
|
49
44
|
@update_configuration_handler = nil
|
50
45
|
@fetch_configuration_update_job = nil
|
51
|
-
@client_id = client_id || config['client_id']
|
52
|
-
@client_secret = client_secret || config['client_secret']
|
53
|
-
@data_maximum_size = config['visitor_data_maximum_size'] || 500 # mb
|
54
|
-
@environment = config['environment'] || DEFAULT_ENVIRONMENT
|
55
46
|
@settings = Kameleoon::Configuration::Settings.new
|
56
|
-
@verbose_mode = config['verbose_mode'] || false
|
57
47
|
@experiments = []
|
58
48
|
@feature_flags = []
|
59
49
|
@data = {}
|
@@ -65,6 +55,12 @@ module Kameleoon
|
|
65
55
|
Kameleoon::Storage::CacheFactoryImpl.new,
|
66
56
|
method(:log)
|
67
57
|
)
|
58
|
+
@network_manager = Network::NetworkManager.new(
|
59
|
+
@environment,
|
60
|
+
@default_timeout,
|
61
|
+
Network::UrlProvider.new(@site_code, Network::UrlProvider::DEFAULT_DATA_API_URL),
|
62
|
+
method(:log)
|
63
|
+
)
|
68
64
|
end
|
69
65
|
|
70
66
|
##
|
@@ -268,7 +264,7 @@ module Kameleoon
|
|
268
264
|
# associated targeting segment conditions were not fulfilled. He should see the reference variation
|
269
265
|
# @raise [Kameleoon::Exception::VisitorCodeNotValid] If the visitor code is empty or longer than 255 chars
|
270
266
|
#
|
271
|
-
# DEPRECATED. Please use `
|
267
|
+
# DEPRECATED. Please use `feature_active?` instead.
|
272
268
|
def activate_feature(visitor_code, feature_key)
|
273
269
|
warn '[DEPRECATION] `activate_feature` is deprecated. Please use `feature_active?` instead.'
|
274
270
|
feature_active?(visitor_code, feature_key)
|
@@ -399,13 +395,8 @@ module Kameleoon
|
|
399
395
|
#
|
400
396
|
# @return [Hash] Hash object of the json object.
|
401
397
|
def get_remote_data(key, timeout = @default_timeout)
|
402
|
-
|
403
|
-
|
404
|
-
log "Retrieve API Data connexion: #{connexion_options.inspect}"
|
405
|
-
response = get_sync(url, connexion_options)
|
406
|
-
return nil unless successful_sync?(response)
|
407
|
-
|
408
|
-
JSON.parse(response.body) unless response.nil?
|
398
|
+
response = @network_manager.get_remote_data(key, timeout)
|
399
|
+
JSON.parse(response) if response
|
409
400
|
end
|
410
401
|
|
411
402
|
##
|
@@ -415,6 +406,26 @@ module Kameleoon
|
|
415
406
|
get_remote_data(key, timeout)
|
416
407
|
end
|
417
408
|
|
409
|
+
##
|
410
|
+
# The get_remote_visitor_data is a method for retrieving custom data for
|
411
|
+
# the latest visit of `visitor_code` from Kameleoon Data API and optionally adding it
|
412
|
+
# to the storage so that other methods could decide whether the current visitor is targeted or not.
|
413
|
+
#
|
414
|
+
# @param [String] visitor_code The visitor code for which you want to retrieve the assigned data.
|
415
|
+
# This field is mandatory.
|
416
|
+
# @param [Bool] add_data A boolean indicating whether the method should automatically add retrieved data
|
417
|
+
# for a visitor. If not specified, the default value is `True`. This field is optional.
|
418
|
+
# @param [Int] timeout Timeout for request (in milliseconds). Equals default_timeout in a config file.
|
419
|
+
# This field is optional.
|
420
|
+
#
|
421
|
+
# @return [Array] An array of data assigned to the given visitor.
|
422
|
+
def get_remote_visitor_data(visitor_code, timeout = nil, add_data: true)
|
423
|
+
response = @network_manager.get_remote_visitor_data(visitor_code, timeout)
|
424
|
+
data_array = parse_custom_data_array(visitor_code, response)
|
425
|
+
add_data(visitor_code, *data_array) if add_data && !data_array.empty?
|
426
|
+
data_array
|
427
|
+
end
|
428
|
+
|
418
429
|
##
|
419
430
|
# Returns a list of all experiment ids
|
420
431
|
#
|
@@ -494,11 +505,23 @@ module Kameleoon
|
|
494
505
|
private
|
495
506
|
|
496
507
|
REFERENCE = 0
|
497
|
-
DEFAULT_ENVIRONMENT = 'production'
|
498
508
|
CACHE_EXPIRATION_TIMEOUT = 5
|
499
509
|
attr :site_code, :client_id, :client_secret, :access_token, :experiments, :feature_flags, :scheduler, :data,
|
500
510
|
:tracking_url, :default_timeout, :interval, :memory_limit, :verbose_mode
|
501
511
|
|
512
|
+
def read_config(config)
|
513
|
+
@client_id = config.client_id
|
514
|
+
@client_secret = config.client_secret
|
515
|
+
@default_timeout = config.default_timeout # in ms
|
516
|
+
@interval = "#{config.configuration_refresh_interval}m"
|
517
|
+
@data_maximum_size = config.visitor_data_maximum_size
|
518
|
+
@environment = config.environment
|
519
|
+
@verbose_mode = config.verbose_mode
|
520
|
+
if @client_id.nil? || @client_secret.nil?
|
521
|
+
warn 'Kameleoon SDK: Credentials are invalid: client_id or client_secret (or both) are empty'
|
522
|
+
end
|
523
|
+
end
|
524
|
+
|
502
525
|
def fetch_configuration
|
503
526
|
Rufus::Scheduler.singleton.in '0s' do
|
504
527
|
log('Initial configuration fetch is started.')
|
@@ -509,7 +532,7 @@ module Kameleoon
|
|
509
532
|
def fetch_configuration_job(time_stamp = nil)
|
510
533
|
EM.synchrony do
|
511
534
|
begin
|
512
|
-
ok = obtain_configuration(
|
535
|
+
ok = obtain_configuration(time_stamp)
|
513
536
|
if !ok && @settings.real_time_update
|
514
537
|
@settings.real_time_update = false
|
515
538
|
log('Switching to polling mode due to failed fetch')
|
@@ -542,7 +565,7 @@ module Kameleoon
|
|
542
565
|
def start_real_time_configuration_service_if_needed
|
543
566
|
return unless @real_time_configuration_service.nil?
|
544
567
|
|
545
|
-
url = @url_provider.make_real_time_url
|
568
|
+
url = @network_manager.url_provider.make_real_time_url
|
546
569
|
fetch_func = proc { |real_time_event| fetch_configuration_job(real_time_event.time_stamp) }
|
547
570
|
@real_time_configuration_service =
|
548
571
|
Kameleoon::RealTime::RealTimeConfigurationService.new(url, fetch_func, method(:log))
|
@@ -579,13 +602,12 @@ module Kameleoon
|
|
579
602
|
# { 'field' => field, 'operator' => operator, 'parameters' => parameters }
|
580
603
|
# end
|
581
604
|
|
582
|
-
def obtain_configuration(
|
605
|
+
def obtain_configuration(time_stamp = nil)
|
583
606
|
log 'Fetching configuration from Client-Config service'
|
584
|
-
|
585
|
-
|
586
|
-
return false unless request
|
607
|
+
response = @network_manager.fetch_configuration(time_stamp)
|
608
|
+
return false unless response
|
587
609
|
|
588
|
-
configuration = JSON.parse(
|
610
|
+
configuration = JSON.parse(response)
|
589
611
|
@experiments = Kameleoon::Configuration::Experiment.create_from_array(configuration['experiments']) ||
|
590
612
|
@experiments
|
591
613
|
@feature_flags = Kameleoon::Configuration::FeatureFlag.create_from_array(
|
@@ -593,7 +615,7 @@ module Kameleoon
|
|
593
615
|
)
|
594
616
|
@settings.update(configuration['configuration'])
|
595
617
|
call_update_handler_if_needed(!time_stamp.nil?)
|
596
|
-
log "Feature flags are fetched: #{
|
618
|
+
log "Feature flags are fetched: #{response.inspect}"
|
597
619
|
true
|
598
620
|
end
|
599
621
|
|
@@ -616,15 +638,6 @@ module Kameleoon
|
|
616
638
|
nil
|
617
639
|
end
|
618
640
|
|
619
|
-
def request_configuration(url)
|
620
|
-
request = EM::Synchrony.sync get({}, url)
|
621
|
-
unless successful?(request)
|
622
|
-
log "Failed to fetch #{request.inspect}"
|
623
|
-
return false
|
624
|
-
end
|
625
|
-
request
|
626
|
-
end
|
627
|
-
|
628
641
|
def find_feature_flag(feature_key)
|
629
642
|
if feature_key.is_a?(String)
|
630
643
|
feature_flag = @feature_flags.select { |ff| ff.feature_key == feature_key }.first
|
@@ -760,39 +773,15 @@ module Kameleoon
|
|
760
773
|
##
|
761
774
|
# helper method for sending tracking requests for new FF
|
762
775
|
def _send_tracking_request(visitor_code, experiment_id = nil, variation_id = nil)
|
776
|
+
user_agent = @user_agents[visitor_code]&.value
|
763
777
|
data_not_sent = data_not_sent(visitor_code)
|
764
778
|
if experiment_id.nil? || variation_id.nil?
|
765
779
|
data_not_sent.append(Network::ActivityEvent.new) if data_not_sent.empty?
|
766
780
|
else
|
767
781
|
data_not_sent.append(Network::ExperimentEvent.new(experiment_id, variation_id))
|
768
782
|
end
|
769
|
-
options = {
|
770
|
-
body: (data_not_sent.map(&:obtain_full_post_text_line).join("\n") || '').encode('UTF-8'),
|
771
|
-
head: { 'Content-Type': 'text/plain' }
|
772
|
-
}
|
773
|
-
set_user_agent_to_headers(visitor_code, options[:head])
|
774
|
-
url = @url_provider.make_tracking_url(visitor_code)
|
775
|
-
trial = 0
|
776
|
-
success = false
|
777
783
|
log "Start post tracking: #{data_not_sent.inspect}"
|
778
|
-
|
779
|
-
while trial < 10
|
780
|
-
log "Send tracking #{options.inspect}"
|
781
|
-
response = post_sync(options, url)
|
782
|
-
log "Response #{response.inspect}"
|
783
|
-
if successful_sync?(response)
|
784
|
-
data_not_sent.each { |it| it.sent = true }
|
785
|
-
success = true
|
786
|
-
break
|
787
|
-
end
|
788
|
-
trial += 1
|
789
|
-
end
|
790
|
-
if success
|
791
|
-
log "Post to tracking is done after #{trial + 1} trials"
|
792
|
-
else
|
793
|
-
log "Post to tracking is failed after #{trial} trials"
|
794
|
-
end
|
795
|
-
end
|
784
|
+
@network_manager.send_tracking_data(visitor_code, data_not_sent, user_agent)
|
796
785
|
end
|
797
786
|
|
798
787
|
##
|
@@ -808,5 +797,28 @@ module Kameleoon
|
|
808
797
|
'Unknown type for feature variable'
|
809
798
|
end
|
810
799
|
end
|
800
|
+
|
801
|
+
##
|
802
|
+
# helper method used by `get_remote_visitor_data`
|
803
|
+
def parse_custom_data_array(visitor_code, response)
|
804
|
+
return [] unless response
|
805
|
+
|
806
|
+
raw = JSON.parse(response)
|
807
|
+
latest_record = raw['currentVisit']
|
808
|
+
if latest_record.nil?
|
809
|
+
previous_visits = raw['previousVisits']
|
810
|
+
return [] unless previous_visits
|
811
|
+
|
812
|
+
latest_record = previous_visits.first
|
813
|
+
end
|
814
|
+
events = latest_record['customDataEvents']
|
815
|
+
return [] if events.nil?
|
816
|
+
|
817
|
+
events.map { |ev| ev['data'] }.compact
|
818
|
+
.map { |jcd| CustomData.new(jcd['index'] || -1, *(jcd['valuesCountMap'] || {}).keys) }
|
819
|
+
rescue StandardError => e
|
820
|
+
log("Parsing of visitor data of '#{visitor_code}' failed: #{e}")
|
821
|
+
[]
|
822
|
+
end
|
811
823
|
end
|
812
824
|
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kameleoon
|
4
|
+
# Client configuration which can be used instead of an external configuration file
|
5
|
+
class ClientConfig
|
6
|
+
CONFIGURATION_UPDATE_INTERVAL = 60
|
7
|
+
VISITOR_DATA_MAXIMUM_SIZE = 500
|
8
|
+
DEFAULT_TIMEOUT = 2000 # milli-seconds
|
9
|
+
|
10
|
+
attr_reader :client_id, :client_secret, :data_refresh_interval, :default_timeout, :configuration_refresh_interval,
|
11
|
+
:visitor_data_maximum_size, :environment, :verbose_mode
|
12
|
+
|
13
|
+
def initialize(
|
14
|
+
client_id: nil,
|
15
|
+
client_secret: nil,
|
16
|
+
configuration_refresh_interval: CONFIGURATION_UPDATE_INTERVAL,
|
17
|
+
default_timeout: DEFAULT_TIMEOUT,
|
18
|
+
visitor_data_maximum_size: VISITOR_DATA_MAXIMUM_SIZE,
|
19
|
+
environment: nil,
|
20
|
+
verbose_mode: false
|
21
|
+
)
|
22
|
+
@client_id = client_id
|
23
|
+
@client_secret = client_secret
|
24
|
+
@configuration_refresh_interval = configuration_refresh_interval || CONFIGURATION_UPDATE_INTERVAL
|
25
|
+
@default_timeout = default_timeout || DEFAULT_TIMEOUT
|
26
|
+
@visitor_data_maximum_size = visitor_data_maximum_size || VISITOR_DATA_MAXIMUM_SIZE
|
27
|
+
@environment = environment
|
28
|
+
@verbose_mode = verbose_mode || false
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.make_from_yaml(yaml)
|
32
|
+
yaml ||= {}
|
33
|
+
ClientConfig.new(
|
34
|
+
client_id: yaml['client_id'],
|
35
|
+
client_secret: yaml['client_secret'],
|
36
|
+
configuration_refresh_interval: yaml['actions_configuration_refresh_interval'],
|
37
|
+
default_timeout: yaml['default_timeout'],
|
38
|
+
visitor_data_maximum_size: yaml['visitor_data_maximum_size'],
|
39
|
+
environment: yaml['environment'],
|
40
|
+
verbose_mode: yaml['verbose_mode']
|
41
|
+
)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -1,4 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'json'
|
4
|
+
require 'kameleoon/network/uri_helper'
|
2
5
|
require_relative 'data'
|
3
6
|
|
4
7
|
module Kameleoon
|
@@ -24,10 +27,13 @@ module Kameleoon
|
|
24
27
|
end
|
25
28
|
|
26
29
|
def obtain_full_post_text_line
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
30
|
+
params = {
|
31
|
+
eventType: 'staticData',
|
32
|
+
browserIndex: @type,
|
33
|
+
nonce: nonce
|
34
|
+
}
|
35
|
+
params[:browserVersion] = @version if @version.is_a?(Integer) || (@version.is_a?(Float) && !@version.nan?)
|
36
|
+
Kameleoon::Network::UriHelper.encode_query(params)
|
31
37
|
end
|
32
38
|
end
|
33
39
|
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'json'
|
4
|
+
require 'kameleoon/network/uri_helper'
|
4
5
|
require_relative 'data'
|
5
6
|
|
6
7
|
module Kameleoon
|
@@ -19,8 +20,14 @@ module Kameleoon
|
|
19
20
|
end
|
20
21
|
|
21
22
|
def obtain_full_post_text_line
|
22
|
-
|
23
|
-
|
23
|
+
params = {
|
24
|
+
eventType: 'conversion',
|
25
|
+
goalId: @goal_id,
|
26
|
+
revenue: @revenue,
|
27
|
+
negative: @negative,
|
28
|
+
nonce: nonce
|
29
|
+
}
|
30
|
+
Kameleoon::Network::UriHelper.encode_query(params)
|
24
31
|
end
|
25
32
|
end
|
26
33
|
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'json'
|
4
|
+
require 'kameleoon/network/uri_helper'
|
4
5
|
require_relative 'data'
|
5
6
|
|
6
7
|
module Kameleoon
|
@@ -46,8 +47,14 @@ module Kameleoon
|
|
46
47
|
return '' if @values.empty?
|
47
48
|
|
48
49
|
str_values = JSON.generate(Hash[@values.collect { |k| [k, 1] }])
|
49
|
-
|
50
|
-
|
50
|
+
params = {
|
51
|
+
eventType: 'customData',
|
52
|
+
index: @id,
|
53
|
+
valuesCountMap: str_values,
|
54
|
+
overwrite: 'true',
|
55
|
+
nonce: nonce
|
56
|
+
}
|
57
|
+
Kameleoon::Network::UriHelper.encode_query(params)
|
51
58
|
end
|
52
59
|
end
|
53
60
|
end
|
data/lib/kameleoon/data/data.rb
CHANGED
@@ -28,8 +28,11 @@ module Kameleoon
|
|
28
28
|
raise KameleoonError.new('ToDo: implement this method.'), 'ToDo: implement this method.'
|
29
29
|
end
|
30
30
|
|
31
|
-
|
32
|
-
|
31
|
+
private
|
32
|
+
|
33
|
+
def nonce
|
34
|
+
@nonce = Kameleoon::Utils.generate_random_string(NONCE_LENGTH) if @nonce.nil?
|
35
|
+
@nonce
|
33
36
|
end
|
34
37
|
end
|
35
38
|
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'json'
|
4
|
+
require 'kameleoon/network/uri_helper'
|
4
5
|
require_relative 'data'
|
5
6
|
|
6
7
|
module Kameleoon
|
@@ -19,8 +20,12 @@ module Kameleoon
|
|
19
20
|
end
|
20
21
|
|
21
22
|
def obtain_full_post_text_line
|
22
|
-
|
23
|
-
|
23
|
+
params = {
|
24
|
+
eventType: 'staticData',
|
25
|
+
deviceType: @device_type,
|
26
|
+
nonce: nonce
|
27
|
+
}
|
28
|
+
Kameleoon::Network::UriHelper.encode_query(params)
|
24
29
|
end
|
25
30
|
end
|
26
31
|
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'json'
|
4
|
+
require 'kameleoon/network/uri_helper'
|
4
5
|
require_relative 'data'
|
5
6
|
|
6
7
|
module Kameleoon
|
@@ -19,13 +20,15 @@ module Kameleoon
|
|
19
20
|
end
|
20
21
|
|
21
22
|
def obtain_full_post_text_line
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
"
|
23
|
+
params = {
|
24
|
+
eventType: 'page',
|
25
|
+
href: @url,
|
26
|
+
title: @title,
|
27
|
+
nonce: nonce
|
28
|
+
}
|
29
|
+
params[:referrersIndices] = "[#{@referrers.each(&:to_s).join(',')}]" if
|
30
|
+
!@referrers.nil? && !@referrers.empty?
|
31
|
+
Kameleoon::Network::UriHelper.encode_query(params)
|
29
32
|
end
|
30
33
|
end
|
31
34
|
end
|
data/lib/kameleoon/factory.rb
CHANGED
@@ -1,29 +1,43 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'kameleoon/client'
|
4
|
+
require 'kameleoon/client_config'
|
4
5
|
|
5
6
|
module Kameleoon
|
6
7
|
# A Factory class for creating kameleoon clients
|
7
8
|
module ClientFactory
|
8
|
-
CONFIGURATION_UPDATE_INTERVAL = '60m'
|
9
9
|
CONFIG_PATH = '/etc/kameleoon/client-ruby.yaml'
|
10
|
-
DEFAULT_TIMEOUT = 2000 # milli-seconds
|
11
10
|
|
12
11
|
@clients = {}
|
13
12
|
|
14
|
-
def self.create(site_code, config_path = CONFIG_PATH,
|
15
|
-
if
|
16
|
-
|
17
|
-
|
13
|
+
def self.create(site_code, config_path = CONFIG_PATH, config: nil)
|
14
|
+
if config.nil?
|
15
|
+
config_yaml = YAML.load_file(config_path) if File.exist?(config_path)
|
16
|
+
if config_yaml.nil?
|
17
|
+
warn "Kameleoon SDK: Configuration file with path #{config_path} does not exist" if config_yaml.nil?
|
18
|
+
config_yaml = {}
|
19
|
+
end
|
20
|
+
end
|
21
|
+
environment = config&.environment || (config_yaml || {})['environment']
|
22
|
+
client = @clients[get_client_key(site_code, environment)]
|
23
|
+
if client.nil?
|
24
|
+
config = ClientConfig.make_from_yaml(config_yaml) if config.nil?
|
25
|
+
client = Client.new(site_code, config)
|
18
26
|
client.send(:log, "Client created with site code: #{site_code}")
|
19
27
|
client.send(:fetch_configuration)
|
20
28
|
@clients.store(site_code, client)
|
21
29
|
end
|
22
|
-
|
30
|
+
client
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.forget(site_code, environment = '')
|
34
|
+
@clients.delete(get_client_key(site_code, environment))
|
23
35
|
end
|
24
36
|
|
25
|
-
|
26
|
-
|
37
|
+
private_class_method
|
38
|
+
|
39
|
+
def self.get_client_key(site_code, environment)
|
40
|
+
site_code + (environment || '')
|
27
41
|
end
|
28
42
|
end
|
29
43
|
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'em-synchrony/em-http'
|
4
|
+
require 'net/http'
|
5
|
+
require 'kameleoon/version'
|
6
|
+
require 'kameleoon/network/response'
|
7
|
+
require 'kameleoon/exceptions'
|
8
|
+
|
9
|
+
module Kameleoon
|
10
|
+
module Network
|
11
|
+
class NetProvider
|
12
|
+
def make_request(_request)
|
13
|
+
raise KameleoonError, 'Call of not implemented method!'
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def collect_headers(request)
|
19
|
+
headers = { 'Content-Type': request.content_type }
|
20
|
+
headers['User-Agent'] = request.user_agent unless request.user_agent.nil?
|
21
|
+
headers
|
22
|
+
end
|
23
|
+
|
24
|
+
def unknown_method_response(method, request)
|
25
|
+
Response.new("Unknown request method '#{method}'", nil, nil, request)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class EMNetProvider < NetProvider
|
30
|
+
def make_request(request)
|
31
|
+
connetion_options = {
|
32
|
+
tls: { verify_peer: false },
|
33
|
+
connect_timeout: request.timeout,
|
34
|
+
inactivity_timeout: request.timeout
|
35
|
+
}
|
36
|
+
headers = collect_headers(request)
|
37
|
+
request_options = { head: headers, body: request.data }
|
38
|
+
begin
|
39
|
+
case request.method
|
40
|
+
when Method::POST
|
41
|
+
EventMachine::HttpRequest.new(request.url, connetion_options).apost(request_options)
|
42
|
+
when Method::GET
|
43
|
+
EventMachine::HttpRequest.new(request.url, connetion_options).aget(request_options)
|
44
|
+
else
|
45
|
+
dfr = DeferrableResponse.new
|
46
|
+
dfr.response = unknown_method_response(request.method, request)
|
47
|
+
dfr
|
48
|
+
end
|
49
|
+
rescue => e
|
50
|
+
dfr = DeferrableResponse.new
|
51
|
+
dfr.response = Response.new(e, nil, nil, request)
|
52
|
+
dfr
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.em_resp_to_response(request, resp)
|
57
|
+
return resp if resp.is_a?(Response)
|
58
|
+
|
59
|
+
Response.new(nil, resp.response_header.status, resp.response, request)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
class SyncNetProvider < NetProvider
|
64
|
+
def make_request(request)
|
65
|
+
resp = nil
|
66
|
+
begin
|
67
|
+
case request.method
|
68
|
+
when Method::GET
|
69
|
+
req = Net::HTTP::Get.new(request.url)
|
70
|
+
when Method::POST
|
71
|
+
req = Net::HTTP::Post.new(request.url)
|
72
|
+
req.body = request.data
|
73
|
+
else
|
74
|
+
return unknown_method_response(request.method, request)
|
75
|
+
end
|
76
|
+
timeout = request.timeout.to_f / 1000.0
|
77
|
+
headers = collect_headers(request)
|
78
|
+
headers.each { |k, v| req[k] = v }
|
79
|
+
uri = URI(request.url)
|
80
|
+
Net::HTTP.start(uri.hostname, uri.port, use_ssl: true, open_timeout: timeout,
|
81
|
+
read_timeout: timeout, ssl_timeout: timeout) do |http|
|
82
|
+
resp = http.request(req)
|
83
|
+
end
|
84
|
+
rescue => e
|
85
|
+
return Response.new(e, nil, nil, request)
|
86
|
+
end
|
87
|
+
Response.new(nil, resp.code.to_i, resp.body, request)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'em-synchrony'
|
4
|
+
require 'kameleoon/network/content_type'
|
5
|
+
require 'kameleoon/network/method'
|
6
|
+
require 'kameleoon/network/request'
|
7
|
+
require 'kameleoon/network/net_provider'
|
8
|
+
|
9
|
+
module Kameleoon
|
10
|
+
module Network
|
11
|
+
##
|
12
|
+
# NetworkManager is used to make API calls.
|
13
|
+
class NetworkManager
|
14
|
+
FETCH_CONFIGURATION_ATTEMPT_NUMBER = 4
|
15
|
+
TRACKING_CALL_ATTEMPT_NUMBER = 4
|
16
|
+
TRACKING_CALL_RETRY_DELAY = 5.0 # in seconds
|
17
|
+
|
18
|
+
attr_reader :environment, :default_timeout, :url_provider
|
19
|
+
|
20
|
+
def initialize(environment, default_timeout, url_provider, log_func = nil)
|
21
|
+
@environment = environment
|
22
|
+
@default_timeout = default_timeout
|
23
|
+
@url_provider = url_provider
|
24
|
+
@em_net_provider = EMNetProvider.new
|
25
|
+
@sync_net_provider = SyncNetProvider.new
|
26
|
+
@log_func = log_func
|
27
|
+
end
|
28
|
+
|
29
|
+
def fetch_configuration(timestamp = nil, timeout = nil)
|
30
|
+
url = @url_provider.make_configuration_url(@environment, timestamp)
|
31
|
+
timeout = ensure_timeout(timeout)
|
32
|
+
request = Request.new(Method::GET, url, ContentType::JSON, timeout)
|
33
|
+
attempts_left = FETCH_CONFIGURATION_ATTEMPT_NUMBER
|
34
|
+
while attempts_left.positive?
|
35
|
+
em_resp = EM::Synchrony.sync(@em_net_provider.make_request(request))
|
36
|
+
response = EMNetProvider.em_resp_to_response(request, em_resp)
|
37
|
+
result = handle_response(response)
|
38
|
+
return result if result
|
39
|
+
|
40
|
+
attempts_left -= 1
|
41
|
+
end
|
42
|
+
false
|
43
|
+
end
|
44
|
+
|
45
|
+
def get_remote_data(key, timeout = nil)
|
46
|
+
url = @url_provider.make_api_data_get_request_url(key)
|
47
|
+
timeout = ensure_timeout(timeout)
|
48
|
+
request = Request.new(Method::GET, url, ContentType::JSON, timeout)
|
49
|
+
response = @sync_net_provider.make_request(request)
|
50
|
+
handle_response(response)
|
51
|
+
end
|
52
|
+
|
53
|
+
def get_remote_visitor_data(visitor_code, timeout = nil)
|
54
|
+
url = @url_provider.make_visitor_data_get_url(visitor_code)
|
55
|
+
timeout = ensure_timeout(timeout)
|
56
|
+
request = Request.new(Method::GET, url, ContentType::JSON, timeout)
|
57
|
+
response = @sync_net_provider.make_request(request)
|
58
|
+
handle_response(response)
|
59
|
+
end
|
60
|
+
|
61
|
+
def send_tracking_data(visitor_code, lines, user_agent, timeout = nil)
|
62
|
+
return if lines.nil? || lines.empty?
|
63
|
+
|
64
|
+
url = @url_provider.make_tracking_url(visitor_code)
|
65
|
+
timeout = ensure_timeout(timeout)
|
66
|
+
data = (lines.map(&:obtain_full_post_text_line).join("\n") || '').encode('UTF-8')
|
67
|
+
request = Request.new(Method::POST, url, ContentType::TEXT, timeout, user_agent, data)
|
68
|
+
Thread.new do
|
69
|
+
attempts_left = TRACKING_CALL_ATTEMPT_NUMBER
|
70
|
+
loop do
|
71
|
+
response = @sync_net_provider.make_request(request)
|
72
|
+
if handle_response(response) != false
|
73
|
+
lines.each { |line| line.sent = true }
|
74
|
+
break
|
75
|
+
end
|
76
|
+
attempts_left -= 1
|
77
|
+
break unless attempts_left.positive?
|
78
|
+
|
79
|
+
delay(TRACKING_CALL_RETRY_DELAY)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
private
|
85
|
+
|
86
|
+
def log_failure(request, message)
|
87
|
+
return if @log_func.nil?
|
88
|
+
|
89
|
+
premsg = "#{request.method} call '#{request.url}' "
|
90
|
+
premsg += request.data.nil? ? 'failed' : "(data '#{request.data}') failed"
|
91
|
+
@log_func.call("#{premsg}: #{message}")
|
92
|
+
end
|
93
|
+
|
94
|
+
def delay(period)
|
95
|
+
sleep(period)
|
96
|
+
end
|
97
|
+
|
98
|
+
def ensure_timeout(timeout)
|
99
|
+
timeout.nil? ? @default_timeout : timeout
|
100
|
+
end
|
101
|
+
|
102
|
+
def handle_response(response)
|
103
|
+
if !response.error.nil?
|
104
|
+
log_failure(response.request, "Error occurred during request: #{response.error}")
|
105
|
+
elsif response.code / 100 != 2
|
106
|
+
log_failure(response.request, "Received unexpected status code '#{response.code}'")
|
107
|
+
else
|
108
|
+
return response.body
|
109
|
+
end
|
110
|
+
false
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kameleoon
|
4
|
+
module Network
|
5
|
+
##
|
6
|
+
# Request represents HTTP request.
|
7
|
+
class Request
|
8
|
+
attr_reader :method, :url, :content_type, :timeout, :user_agent, :data
|
9
|
+
|
10
|
+
def initialize(method, url, content_type, timeout, user_agent = nil, data = nil)
|
11
|
+
@method = method
|
12
|
+
@url = url
|
13
|
+
@content_type = content_type
|
14
|
+
@timeout = timeout
|
15
|
+
@user_agent = user_agent
|
16
|
+
@data = !data.nil? && data.is_a?(String) ? data.encode('UTF-8') : data
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kameleoon
|
4
|
+
module Network
|
5
|
+
##
|
6
|
+
# Response represents HTTP response.
|
7
|
+
class Response
|
8
|
+
attr_reader :error, :code, :body, :request
|
9
|
+
|
10
|
+
def initialize(error, code, body, request)
|
11
|
+
@error = error
|
12
|
+
@code = code
|
13
|
+
@body = body
|
14
|
+
@request = request
|
15
|
+
end
|
16
|
+
|
17
|
+
def success?
|
18
|
+
@error.nil? && (@code / 100 == 2)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class DeferrableResponse < EventMachine::DefaultDeferrable
|
23
|
+
attr_writer :response
|
24
|
+
|
25
|
+
def callback(&blk)
|
26
|
+
blk.call(@response)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
data/lib/kameleoon/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: kameleoon-client-ruby
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kameleoon
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-10-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: em-http-request
|
@@ -76,6 +76,7 @@ files:
|
|
76
76
|
- README.md
|
77
77
|
- lib/kameleoon.rb
|
78
78
|
- lib/kameleoon/client.rb
|
79
|
+
- lib/kameleoon/client_config.rb
|
79
80
|
- lib/kameleoon/configuration/experiment.rb
|
80
81
|
- lib/kameleoon/configuration/feature_flag.rb
|
81
82
|
- lib/kameleoon/configuration/rule.rb
|
@@ -95,7 +96,13 @@ files:
|
|
95
96
|
- lib/kameleoon/factory.rb
|
96
97
|
- lib/kameleoon/hybrid/manager.rb
|
97
98
|
- lib/kameleoon/network/activity_event.rb
|
99
|
+
- lib/kameleoon/network/content_type.rb
|
98
100
|
- lib/kameleoon/network/experiment_event.rb
|
101
|
+
- lib/kameleoon/network/method.rb
|
102
|
+
- lib/kameleoon/network/net_provider.rb
|
103
|
+
- lib/kameleoon/network/network_manager.rb
|
104
|
+
- lib/kameleoon/network/request.rb
|
105
|
+
- lib/kameleoon/network/response.rb
|
99
106
|
- lib/kameleoon/network/uri_helper.rb
|
100
107
|
- lib/kameleoon/network/url_provider.rb
|
101
108
|
- lib/kameleoon/real_time/real_time_configuration_service.rb
|
@@ -103,7 +110,6 @@ files:
|
|
103
110
|
- lib/kameleoon/real_time/sse_client.rb
|
104
111
|
- lib/kameleoon/real_time/sse_message.rb
|
105
112
|
- lib/kameleoon/real_time/sse_request.rb
|
106
|
-
- lib/kameleoon/request.rb
|
107
113
|
- lib/kameleoon/storage/cache.rb
|
108
114
|
- lib/kameleoon/storage/cache_factory.rb
|
109
115
|
- lib/kameleoon/storage/variation_storage.rb
|
data/lib/kameleoon/request.rb
DELETED
@@ -1,76 +0,0 @@
|
|
1
|
-
require 'em-synchrony/em-http'
|
2
|
-
require 'kameleoon/version'
|
3
|
-
require 'net/http'
|
4
|
-
|
5
|
-
module Kameleoon
|
6
|
-
# @api private
|
7
|
-
module Request
|
8
|
-
protected
|
9
|
-
|
10
|
-
module Method
|
11
|
-
GET = 'get'.freeze
|
12
|
-
POST = 'post'.freeze
|
13
|
-
end
|
14
|
-
|
15
|
-
def get(request_options, url, connexion_options = {})
|
16
|
-
request(Method::GET, request_options, url, connexion_options)
|
17
|
-
end
|
18
|
-
|
19
|
-
def post(request_options, url, connexion_options = {})
|
20
|
-
request(Method::POST, request_options, url, connexion_options)
|
21
|
-
end
|
22
|
-
|
23
|
-
def get_sync(url, connexion_options = {})
|
24
|
-
request_sync(Method::GET, url, connexion_options, {})
|
25
|
-
end
|
26
|
-
|
27
|
-
def post_sync(request_options, url, connexion_options = {})
|
28
|
-
request_sync(Method::POST, url, connexion_options, request_options)
|
29
|
-
end
|
30
|
-
|
31
|
-
private
|
32
|
-
|
33
|
-
def request(method, request_options, url, connexion_options)
|
34
|
-
connexion_options[:tls] = { verify_peer: false }
|
35
|
-
case method
|
36
|
-
when Method::POST then
|
37
|
-
return EventMachine::HttpRequest.new(url, connexion_options).apost request_options
|
38
|
-
when Method::GET then
|
39
|
-
return EventMachine::HttpRequest.new(url, connexion_options).get request_options
|
40
|
-
else
|
41
|
-
print 'Unknown request type'
|
42
|
-
return false
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
def request_sync(method, url, connexion_options, request_options)
|
47
|
-
request_options = {} if request_options.nil?
|
48
|
-
case method
|
49
|
-
when Method::GET then
|
50
|
-
uri = URI(url)
|
51
|
-
request = Net::HTTP.new(url)
|
52
|
-
response = Net::HTTP.get_response(uri)
|
53
|
-
response
|
54
|
-
when Method::POST then
|
55
|
-
uri = URI.parse(url)
|
56
|
-
http = Net::HTTP.new(uri.host, uri.port)
|
57
|
-
http.use_ssl = true
|
58
|
-
request = Net::HTTP::Post.new(uri, initheader = request_options[:head])
|
59
|
-
request.body = request_options[:body]
|
60
|
-
response = http.request(request)
|
61
|
-
response
|
62
|
-
else
|
63
|
-
print "Unknown request type"
|
64
|
-
return false
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
|
-
def successful?(request)
|
69
|
-
!request.nil? && request != false && /20\d/.match(request.response_header.status.to_s)
|
70
|
-
end
|
71
|
-
|
72
|
-
def successful_sync?(response)
|
73
|
-
!response.nil? && response != false && response.is_a?(Net::HTTPSuccess)
|
74
|
-
end
|
75
|
-
end
|
76
|
-
end
|