kameleoon-client-ruby 1.1.2 → 2.0.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.
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'kameleoon/targeting/models'
4
+
5
+ module Kameleoon
6
+ # Module which contains all internal data of SDK
7
+ module Configuration
8
+ # Class for manage all experiments and old feature flags
9
+ class Experiment
10
+ attr_accessor :id, :status, :site_enabled, :deviations, :respool_time, :variations, :targeting_segment
11
+
12
+ def self.create_from_array(array)
13
+ array&.map { |it| Experiment.new(it) }
14
+ end
15
+
16
+ def initialize(experiment_hash)
17
+ @id = experiment_hash['id']
18
+ @status = experiment_hash['status']
19
+ @site_enabled = experiment_hash['siteEnabled']
20
+ unless experiment_hash['deviations'].nil?
21
+ @deviations =
22
+ Hash[*experiment_hash['deviations'].map do |it|
23
+ [it['variationId'] == 'origin' ? '0' : it['variationId'], it['value']]
24
+ end.flatten]
25
+ end
26
+ unless experiment_hash['respoolTime'].nil?
27
+ @respool_time =
28
+ Hash[*experiment_hash['respoolTime'].map do |it|
29
+ [it['variationId'] == 'origin' ? '0' : it['variationId'], it['value']]
30
+ end.flatten]
31
+ end
32
+ unless experiment_hash['variations'].nil?
33
+ @variations =
34
+ experiment_hash['variations'].map do |it|
35
+ { 'id' => it['id'].to_i, 'customJson' => it['customJson'] }
36
+ end
37
+ end
38
+ @targeting_segment = Kameleoon::Targeting::Segment.new((experiment_hash['segment'])) if experiment_hash['segment']
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'experiment'
4
+
5
+ module Kameleoon
6
+ # Module which contains all internal data of SDK
7
+ module Configuration
8
+ # Class for manage all experiments and old feature flags
9
+ class FeatureFlag < Experiment
10
+ STATUS_ACTIVE = 'ACTIVE'
11
+ FEATURE_STATUS_DEACTIVATED = 'DEACTIVATED'
12
+ attr_accessor :identification_key, :feature_status, :schedules, :exposition_rate
13
+
14
+ def self.create_from_array(array)
15
+ array&.map { |it| FeatureFlag.new(it) }
16
+ end
17
+
18
+ def initialize(feature_flag_hash)
19
+ super(feature_flag_hash)
20
+ @feature_status = feature_flag_hash['featureStatus']
21
+ @identification_key = feature_flag_hash['identificationKey'] if feature_flag_hash['identificationKey']
22
+ @exposition_rate = feature_flag_hash['expositionRate']
23
+ @schedules = feature_flag_hash['schedules'] unless feature_flag_hash['schedules'].nil?
24
+ end
25
+
26
+ def is_scheduled_active(date)
27
+ current_status = @status == STATUS_ACTIVE
28
+ if @feature_status == FEATURE_STATUS_DEACTIVATED || @schedules.empty?
29
+ return current_status
30
+ end
31
+ @schedules.each do |schedule|
32
+ if (schedule['dateStart'].nil? || Time.parse(schedule['dateStart']).to_i < date) &&
33
+ (schedule['dateEnd'].nil? || Time.parse(schedule['dateEnd']).to_i > date)
34
+ return true
35
+ end
36
+ end
37
+ false
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'rule'
4
+ require_relative 'variation'
5
+
6
+ module Kameleoon
7
+ # Module which contains all internal data of SDK
8
+ module Configuration
9
+ # Class for manage all feature flags v2 with rules
10
+ class FeatureFlagV2
11
+ attr_accessor :id, :feature_key, :variations, :default_variation_key, :rules
12
+
13
+ def self.create_from_array(array)
14
+ array&.map { |it| FeatureFlagV2.new(it) }
15
+ end
16
+
17
+ def initialize(hash)
18
+ @id = hash['id']
19
+ @feature_key = hash['featureKey']
20
+ @default_variation_key = hash['defaultVariationKey']
21
+ @variations = Variation.create_from_array(hash['variations'])
22
+ @rules = Rule.create_from_array(hash['rules'])
23
+ end
24
+
25
+ def get_variation_key(key)
26
+ variations.select { |v| v.key == key }.first
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'variation_exposition'
4
+ require 'kameleoon/targeting/models'
5
+
6
+ module Kameleoon
7
+ # Module which contains all internal data of SDK
8
+ module Configuration
9
+ # RuleType has a possible rule types
10
+ module RuleType
11
+ EXPERIMENTATION = 'EXPERIMENTATION'
12
+ TARGETED_DELIVERY = 'TARGETED_DELIVERY'
13
+ end
14
+
15
+ # Rule is a class for new rules of feature flags
16
+ class Rule
17
+ attr_accessor :type, :exposition, :experiment_id, :variation_by_exposition, :targeting_segment
18
+
19
+ def self.create_from_array(array)
20
+ array&.map { |it| Rule.new(it) }
21
+ end
22
+
23
+ def initialize(hash)
24
+ @type = hash['type']
25
+ @exposition = hash['exposition']
26
+ @experiment_id = hash['experimentId']
27
+ @variation_by_exposition = VariationByExposition.create_from_array(hash['variationByExposition'])
28
+ @targeting_segment = Kameleoon::Targeting::Segment.new((hash['segment'])) if hash['segment']
29
+ end
30
+
31
+ def get_variation_key(hash_double)
32
+ total = 0.0
33
+ variation_by_exposition.each do |element|
34
+ total += element.exposition
35
+ return element.variation_key if total >= hash_double
36
+ end
37
+ nil
38
+ end
39
+
40
+ def get_variation_id_by_key(key)
41
+ variation_by_exposition.select { |v| v.variation_key == key }.first&.variation_id
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'experiment'
4
+
5
+ module Kameleoon
6
+ # Module which contains all internal data of SDK
7
+ module Configuration
8
+ # Variable class contains key / type / value of variable
9
+ class Variable
10
+ attr_accessor :key, :type, :value
11
+
12
+ def self.create_from_array(array)
13
+ array&.map { |it| Variable.new(it) }
14
+ end
15
+
16
+ def initialize(hash)
17
+ @key = hash['key']
18
+ @type = hash['type']
19
+ @value = hash['value']
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'variable'
4
+
5
+ module Kameleoon
6
+ # Module which contains all internal data of SDK
7
+ module Configuration
8
+ # Constant values of types of variations
9
+ module VariationType
10
+ VARIATION_OFF = 'off'
11
+ end
12
+
13
+ # Variation of feature flag
14
+ class Variation
15
+ attr_accessor :key, :variables
16
+
17
+ def self.create_from_array(array)
18
+ array&.map { |it| Variation.new(it) }
19
+ end
20
+
21
+ def initialize(hash)
22
+ @key = hash['key']
23
+ @variables = Variable.create_from_array(hash['variables'])
24
+ end
25
+
26
+ def get_variable_by_key(key)
27
+ variables.select { |var| var.key == key }.first
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'experiment'
4
+
5
+ module Kameleoon
6
+ # Module which contains all internal data of SDK
7
+ module Configuration
8
+ # VariationByExposition represents a variation with exposition rate for rule
9
+ class VariationByExposition
10
+ attr_accessor :variation_key, :variation_id, :exposition
11
+
12
+ def self.create_from_array(array)
13
+ array&.map { |it| VariationByExposition.new(it) }
14
+ end
15
+
16
+ def initialize(hash)
17
+ @variation_key = hash['variationKey']
18
+ @variation_id = hash['variationId']
19
+ @exposition = hash['exposition']
20
+ end
21
+ end
22
+ end
23
+ end
@@ -23,12 +23,19 @@ module Kameleoon
23
23
  end
24
24
 
25
25
  def obtain_hash_double(visitor_code, respool_times = {}, container_id = '')
26
+ obtain_hash_double_helper(visitor_code, respool_times, container_id, '')
27
+ end
28
+
29
+ def obtain_hash_double_v2(visitor_code, container_id = '', suffix = '')
30
+ obtain_hash_double_helper(visitor_code, {}, container_id, suffix)
31
+ end
32
+
33
+ def obtain_hash_double_helper(visitor_code, respool_times, container_id, suffix)
26
34
  identifier = visitor_code.to_s
27
35
  identifier += container_id.to_s
28
- if !respool_times.nil? && !respool_times.empty?
29
- identifier += respool_times.sort.to_h.values.join.to_s
30
- end
31
- (Digest::SHA256.hexdigest(identifier.encode('UTF-8')).to_i(16) / (BigDecimal("2") ** BigDecimal("256"))).round(16)
36
+ identifier += suffix.to_s
37
+ identifier += respool_times.sort.to_h.values.join.to_s if !respool_times.nil? && !respool_times.empty?
38
+ (Digest::SHA256.hexdigest(identifier.encode('UTF-8')).to_i(16) / (BigDecimal('2')**BigDecimal('256'))).round(16)
32
39
  end
33
40
 
34
41
  def check_visitor_code(visitor_code)
@@ -1,12 +1,14 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Kameleoon
2
4
  NONCE_LENGTH = 16
3
5
 
4
6
  module DataType
5
- CUSTOM = "CUSTOM"
6
- BROWSER = "BROWSER"
7
- CONVERSION = "CONVERSION"
8
- INTEREST = "INTEREST"
9
- PAGE_VIEW = "PAGE_VIEW"
7
+ CUSTOM = 'CUSTOM'
8
+ BROWSER = 'BROWSER'
9
+ CONVERSION = 'CONVERSION'
10
+ DEVICE = 'DEVICE'
11
+ PAGE_VIEW = 'PAGE_VIEW'
10
12
  end
11
13
 
12
14
  module BrowserType
@@ -18,6 +20,12 @@ module Kameleoon
18
20
  OTHER = 5
19
21
  end
20
22
 
23
+ module DeviceType
24
+ PHONE = 'PHONE'
25
+ TABLET = 'TABLET'
26
+ DESKTOP = 'DESKTOP'
27
+ end
28
+
21
29
  class Data
22
30
  attr_accessor :instance, :sent
23
31
 
@@ -89,7 +97,7 @@ module Kameleoon
89
97
 
90
98
  def obtain_full_post_text_line
91
99
  nonce = Kameleoon::Utils.generate_random_string(NONCE_LENGTH)
92
- "eventType=staticData&browser=" + @browser.to_s + "&nonce=" + nonce
100
+ "eventType=staticData&browserIndex=" + @browser.to_s + "&nonce=" + nonce
93
101
  end
94
102
  end
95
103
 
@@ -98,22 +106,22 @@ module Kameleoon
98
106
 
99
107
  # @param [String] url Url of the page
100
108
  # @param [String] title Title of the page
101
- # @param [Integer] referrer Optional field - Referrer id
102
- def initialize(url, title, referrer = nil)
109
+ # @param [Array] referrers Optional field - Referrer ids
110
+ def initialize(url, title, referrers = nil)
103
111
  @instance = DataType::PAGE_VIEW
104
112
  @sent = false
105
113
  @url = url
106
114
  @title = title
107
- @referrer = referrer
115
+ @referrers = referrers
108
116
  end
109
117
 
110
118
  def obtain_full_post_text_line
111
119
  nonce = Kameleoon::Utils.generate_random_string(NONCE_LENGTH)
112
- referrer_text = ""
113
- unless @referrer.nil?
114
- referrer_text = "&referrers=[" + @referrer.to_s + "]"
120
+ referrer_text = ''
121
+ unless @referrers.nil?
122
+ referrer_text = "&referrersIndices=" + @referrers.to_s
115
123
  end
116
- "eventType=page&href=" + encode(@url) + "&title=" + @title + "&keyPages=[]" + referrer_text + "&nonce=" + nonce
124
+ "eventType=page&href=" + encode(@url) + "&title=" + @title + referrer_text + "&nonce=" + nonce
117
125
  end
118
126
  end
119
127
 
@@ -137,19 +145,25 @@ module Kameleoon
137
145
  end
138
146
  end
139
147
 
140
- class Interest < Data
141
- attr_accessor :index
142
-
143
- # @param [Integer] index Index of the interest
144
- def initialize(index)
145
- @instance = DataType::INTEREST
148
+ # Device uses for sending deviceType parameter for tracking calls
149
+ class Device < Data
150
+ def initialize(device_type)
151
+ @instance = DataType::DEVICE
146
152
  @sent = false
147
- @index = index
153
+ @device_type = device_type
148
154
  end
149
155
 
150
156
  def obtain_full_post_text_line
151
157
  nonce = Kameleoon::Utils.generate_random_string(NONCE_LENGTH)
152
- "eventType=interests&indexes=[" + @index.to_s + "]&fresh=true&nonce=" + nonce
158
+ "eventType=staticData&deviceType=#{@device_type}&nonce=#{nonce}"
159
+ end
160
+ end
161
+
162
+ # UserAgent uses for changing User-Agent header for tracking calls
163
+ class UserAgent
164
+ attr_accessor :value
165
+ def initialize(value)
166
+ @value = value
153
167
  end
154
168
  end
155
- end
169
+ end
@@ -1,59 +1,82 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Kameleoon
2
4
  module Exception
5
+ # Base Error
3
6
  class KameleoonError < ::StandardError
4
7
  def initialize(message = nil)
5
- super("Kameleoon error: " + message)
8
+ super("Kameleoon error: #{message}")
6
9
  end
7
10
  end
11
+
12
+ # Base Not Found Error
8
13
  class NotFound < KameleoonError
9
- def initialize(value = "")
10
- super(value.to_s + " not found.")
14
+ def initialize(value = '')
15
+ super("#{value} not found.")
11
16
  end
12
17
  end
18
+
19
+ # Variation Not Found
13
20
  class VariationConfigurationNotFound < NotFound
14
- def initialize(id = "")
15
- super("Variation " + id.to_s)
21
+ def initialize(id = '')
22
+ super("Variation #{id}")
16
23
  end
17
24
  end
18
- class ExperimentConfigurationNotFound < NotFound
19
- def initialize(id = "")
20
- super("Experiment " + id.to_s)
25
+
26
+ # Experiment Configuration Not Found
27
+ class ExperimentConfigurationNotFound < NotFound
28
+ def initialize(id = '')
29
+ super("Experiment #{id}")
21
30
  end
22
31
  end
32
+
33
+ # Feature Flag Configuration Not Found
23
34
  class FeatureConfigurationNotFound < NotFound
24
- def initialize(id = "")
25
- super("Feature flag " + id.to_s)
35
+ def initialize(id = '')
36
+ super("Feature flag #{id}")
26
37
  end
27
38
  end
39
+
40
+ # Feature Variable Not Found
28
41
  class FeatureVariableNotFound < NotFound
29
- def initialize(key = "")
30
- super("Feature variable " + key.to_s)
42
+ def initialize(key = '')
43
+ super("Feature variable #{key}")
31
44
  end
32
45
  end
46
+
47
+ # Credentials Not Found
33
48
  class CredentialsNotFound < NotFound
34
49
  def initialize
35
- super("Credentials")
50
+ super('Credentials')
36
51
  end
37
52
  end
53
+
54
+ # Not Targeted (when visitor is not targeted for experiment or feature flag)
38
55
  class NotTargeted < KameleoonError
39
- def initialize(visitor_code = "")
40
- super("Visitor " + visitor_code + " is not targeted.")
56
+ def initialize(visitor_code = '')
57
+ super("Visitor #{visitor_code} is not targeted.")
41
58
  end
42
59
  end
43
- class NotActivated < KameleoonError
44
- def initialize(visitor_code = "")
45
- super("Visitor " + visitor_code + " is not activated.")
60
+
61
+ # Not Allocated (when visitor is not allocated for experiment)
62
+ class NotAllocated < KameleoonError
63
+ def initialize(visitor_code = '')
64
+ super("Visitor #{visitor_code} is not targeted.")
46
65
  end
47
66
  end
67
+
68
+ # Visitor Code Not Valod (empty or length > 255)
48
69
  class VisitorCodeNotValid < KameleoonError
49
- def initialize(message = "")
50
- super("Visitor code not valid: " + message)
70
+ def initialize(message = '')
71
+ super("Visitor code not valid: #{message}")
51
72
  end
52
73
  end
74
+
75
+ # SiteCode Disabled
53
76
  class SiteCodeDisabled < KameleoonError
54
- def initialize(message = "")
55
- super("Site with siteCode '" + message + "' is disabled")
77
+ def initialize(message = '')
78
+ super("Site with siteCode '#{message}' is disabled")
56
79
  end
57
80
  end
58
81
  end
59
- end
82
+ end
@@ -1,26 +1,29 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'kameleoon/client'
2
4
 
3
5
  module Kameleoon
6
+ # A Factory class for creating kameleoon clients
4
7
  module ClientFactory
5
8
  CONFIGURATION_UPDATE_INTERVAL = '60m'
6
- CONFIG_PATH = "/etc/kameleoon/client-ruby.yaml"
7
- DEFAULT_TIMEOUT = 2000 #milli-seconds
9
+ CONFIG_PATH = '/etc/kameleoon/client-ruby.yaml'
10
+ DEFAULT_TIMEOUT = 2000 # milli-seconds
11
+
12
+ @clients = {}
13
+
14
+ def self.create(site_code, config_path = CONFIG_PATH, client_id = nil, client_secret = nil)
15
+ if @clients[site_code].nil?
16
+ client = Client.new(site_code, config_path, CONFIGURATION_UPDATE_INTERVAL,
17
+ DEFAULT_TIMEOUT, client_id, client_secret)
18
+ client.send(:log, "Client created with site code: #{site_code}")
19
+ client.send(:fetch_configuration)
20
+ @clients.store(site_code, client)
21
+ end
22
+ @clients[site_code]
23
+ end
8
24
 
9
- ##
10
- # Create a kameleoon client object, each call create a new client.
11
- # The starting point for using the SDK is the initialization step. All interaction with the SDK is done through an object of the Kameleoon::Client class, therefore you need to create this object via Kameleoon::ClientFactory create static method.
12
- #
13
- # @param [String] site_code Site code
14
- # @param [Boolean] blocking - optional, default is false
15
- #
16
- # @return [Kameleoon::Client]
17
- #
18
- def self.create(site_code, blocking = false, config_path = CONFIG_PATH, client_id = nil, client_secret = nil)
19
- client = Client.new(site_code, config_path, blocking, CONFIGURATION_UPDATE_INTERVAL, DEFAULT_TIMEOUT, client_id, client_secret)
20
- client.send(:log, "Warning: you are using the blocking mode") if blocking
21
- client.send(:log, "Client created with site code: " + site_code.to_s)
22
- client.send(:fetch_configuration)
23
- client
25
+ def self.forget(site_code)
26
+ @clients.delete(site_code)
24
27
  end
25
28
  end
26
- end
29
+ end
@@ -1,16 +1,18 @@
1
- require "em-synchrony/em-http"
2
- require "kameleoon/version"
1
+ require 'em-synchrony/em-http'
2
+ require 'kameleoon/version'
3
3
  require 'net/http'
4
4
 
5
5
  module Kameleoon
6
6
  # @api private
7
7
  module Request
8
8
  protected
9
- API_URL = "https://api.kameleoon.com"
9
+
10
+ API_URL = 'https://api.kameleoon.com'.freeze
11
+ CLIENT_CONFIG_URL = 'https://client-config.kameleoon.com'.freeze
10
12
 
11
13
  module Method
12
- GET = "get"
13
- POST = "post"
14
+ GET = 'get'.freeze
15
+ POST = 'post'.freeze
14
16
  end
15
17
 
16
18
  def get(request_options, url = API_URL, connexion_options = {})
@@ -32,7 +34,7 @@ module Kameleoon
32
34
  private
33
35
 
34
36
  def request(method, request_options, url, connexion_options)
35
- connexion_options[:tls] = {verify_peer: false}
37
+ connexion_options[:tls] = { verify_peer: false }
36
38
  add_user_agent(request_options)
37
39
  case method
38
40
  when Method::POST then
@@ -40,7 +42,7 @@ module Kameleoon
40
42
  when Method::GET then
41
43
  return EventMachine::HttpRequest.new(url, connexion_options).get request_options
42
44
  else
43
- print "Unknown request type"
45
+ print 'Unknown request type'
44
46
  return false
45
47
  end
46
48
  end
@@ -68,22 +70,21 @@ module Kameleoon
68
70
  end
69
71
  end
70
72
 
71
- def is_successful(request)
73
+ def successful?(request)
72
74
  !request.nil? && request != false && /20\d/.match(request.response_header.status.to_s)
73
75
  end
74
76
 
75
- def is_successful_sync(response)
77
+ def successful_sync?(response)
76
78
  !response.nil? && response != false && response.is_a?(Net::HTTPSuccess)
77
79
  end
78
80
 
79
81
  def add_user_agent(request_options)
82
+ sdk_version = "sdk/ruby/#{Kameleoon::VERSION}"
80
83
  if request_options[:head].nil?
81
- request_options[:head] = {'Kameleoon-Client' => 'sdk/ruby/' + Kameleoon::VERSION}
84
+ request_options[:head] = { 'Kameleoon-Client' => sdk_version }
82
85
  else
83
- request_options[:head].store('Kameleoon-Client', 'sdk/ruby/' + Kameleoon::VERSION)
86
+ request_options[:head].store('Kameleoon-Client', sdk_version)
84
87
  end
85
88
  end
86
89
  end
87
90
  end
88
-
89
-
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'kameleoon/storage/visitor_variation'
4
+
5
+ module Kameleoon
6
+ module Storage
7
+ # VariationStorage is a container for saved variations associated with a visitor
8
+ class VariationStorage
9
+ def initialize
10
+ @storage = {}
11
+ end
12
+
13
+ def get_variation_id(visitor_code, experiment_id)
14
+ variation_valid?(visitor_code, experiment_id, nil)
15
+ end
16
+
17
+ def variation_valid?(visitor_code, experiment_id, respool_time)
18
+ return nil if @storage[visitor_code].nil? || @storage[visitor_code][experiment_id].nil?
19
+
20
+ variation = @storage[visitor_code][experiment_id]
21
+ return nil unless variation.valid?(respool_time)
22
+
23
+ variation.variation_id
24
+ end
25
+
26
+ def update_variation(visitor_code, experiment_id, variation_id)
27
+ @storage[visitor_code] = {} if @storage[visitor_code].nil?
28
+ @storage[visitor_code][experiment_id] = Kameleoon::Storage::VisitorVariation.new(variation_id)
29
+ end
30
+
31
+ def get_hash_saved_variation_id(visitor_code)
32
+ return nil if @storage[visitor_code].nil?
33
+
34
+ map_variations = {}
35
+ @storage[visitor_code].each do |key, value|
36
+ map_variations[key] = value.variation_id
37
+ end
38
+ map_variations
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kameleoon
4
+ module Storage
5
+ # VisitorVariation contains a saved variation id associated with a visitor
6
+ # and time when it was associated.
7
+ class VisitorVariation
8
+ attr_accessor :variation_id
9
+
10
+ def initialize(variation_id)
11
+ @variation_id = variation_id
12
+ @assignment_date = Time.now.to_i
13
+ end
14
+
15
+ def valid?(respool_time)
16
+ respool_time.nil? || @assignment_date > respool_time
17
+ end
18
+ end
19
+ end
20
+ end