kameleoon-client-ruby 2.1.1 → 2.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|