optimizely-sdk 4.0.1 → 5.0.0.pre.beta
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/optimizely/audience.rb +5 -5
- data/lib/optimizely/config/datafile_project_config.rb +542 -539
- data/lib/optimizely/config_manager/http_project_config_manager.rb +12 -4
- data/lib/optimizely/config_manager/project_config_manager.rb +2 -1
- data/lib/optimizely/config_manager/static_project_config_manager.rb +3 -2
- data/lib/optimizely/event_dispatcher.rb +2 -4
- data/lib/optimizely/exceptions.rb +15 -1
- data/lib/optimizely/helpers/constants.rb +45 -1
- data/lib/optimizely/helpers/http_utils.rb +3 -0
- data/lib/optimizely/helpers/sdk_settings.rb +61 -0
- data/lib/optimizely/helpers/validator.rb +55 -0
- data/lib/optimizely/notification_center_registry.rb +71 -0
- data/lib/optimizely/odp/lru_cache.rb +114 -0
- data/lib/optimizely/odp/odp_config.rb +102 -0
- data/lib/optimizely/odp/odp_event.rb +75 -0
- data/lib/optimizely/odp/odp_event_api_manager.rb +70 -0
- data/lib/optimizely/odp/odp_event_manager.rb +286 -0
- data/lib/optimizely/odp/odp_manager.rb +159 -0
- data/lib/optimizely/odp/odp_segment_api_manager.rb +122 -0
- data/lib/optimizely/odp/odp_segment_manager.rb +97 -0
- data/lib/optimizely/optimizely_config.rb +1 -1
- data/lib/optimizely/optimizely_factory.rb +7 -2
- data/lib/optimizely/optimizely_user_context.rb +40 -6
- data/lib/optimizely/user_condition_evaluator.rb +1 -1
- data/lib/optimizely/version.rb +2 -2
- data/lib/optimizely.rb +142 -10
- metadata +14 -4
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
#
|
4
|
-
# Copyright 2019-2020, 2022, Optimizely and contributors
|
4
|
+
# Copyright 2019-2020, 2022-2023, Optimizely and contributors
|
5
5
|
#
|
6
6
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
7
|
# you may not use this file except in compliance with the License.
|
@@ -33,12 +33,12 @@ module Optimizely
|
|
33
33
|
class HTTPProjectConfigManager < ProjectConfigManager
|
34
34
|
# Config manager that polls for the datafile and updated ProjectConfig based on an update interval.
|
35
35
|
|
36
|
-
attr_reader :stopped
|
36
|
+
attr_reader :stopped, :sdk_key
|
37
37
|
|
38
38
|
# Initialize config manager. One of sdk_key or url has to be set to be able to use.
|
39
39
|
#
|
40
|
-
# sdk_key - Optional string uniquely identifying the datafile. It's required unless a
|
41
|
-
# datafile
|
40
|
+
# sdk_key - Optional string uniquely identifying the datafile. It's required unless a datafile with sdk_key is passed in.
|
41
|
+
# datafile - Optional JSON string representing the project. If nil, sdk_key is required.
|
42
42
|
# polling_interval - Optional floating point number representing time interval in seconds
|
43
43
|
# at which to request datafile and set ProjectConfig.
|
44
44
|
# blocking_timeout - Optional Time in seconds to block the config call until config object has been initialized.
|
@@ -83,6 +83,10 @@ module Optimizely
|
|
83
83
|
@notification_center = notification_center.is_a?(Optimizely::NotificationCenter) ? notification_center : NotificationCenter.new(@logger, @error_handler)
|
84
84
|
@optimizely_config = nil
|
85
85
|
@config = datafile.nil? ? nil : DatafileProjectConfig.create(datafile, @logger, @error_handler, @skip_json_validation)
|
86
|
+
@sdk_key = sdk_key || @config&.sdk_key
|
87
|
+
|
88
|
+
raise MissingSdkKeyError if @sdk_key.nil?
|
89
|
+
|
86
90
|
@mutex = Mutex.new
|
87
91
|
@resource = ConditionVariable.new
|
88
92
|
@async_scheduler = AsyncScheduler.new(method(:fetch_datafile_config), @polling_interval, auto_update, @logger)
|
@@ -222,6 +226,10 @@ module Optimizely
|
|
222
226
|
|
223
227
|
@notification_center.send_notifications(NotificationCenter::NOTIFICATION_TYPES[:OPTIMIZELY_CONFIG_UPDATE])
|
224
228
|
|
229
|
+
NotificationCenterRegistry
|
230
|
+
.get_notification_center(@sdk_key, @logger)
|
231
|
+
&.send_notifications(NotificationCenter::NOTIFICATION_TYPES[:OPTIMIZELY_CONFIG_UPDATE])
|
232
|
+
|
225
233
|
@logger.log(Logger::DEBUG, 'Received new datafile and updated config. ' \
|
226
234
|
"Old revision number: #{previous_revision}. New revision number: #{@config.revision}.")
|
227
235
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
#
|
4
|
-
# Copyright 2019, Optimizely and contributors
|
4
|
+
# Copyright 2019, 2023, Optimizely and contributors
|
5
5
|
#
|
6
6
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
7
|
# you may not use this file except in compliance with the License.
|
@@ -20,5 +20,6 @@ module Optimizely
|
|
20
20
|
# Interface for fetching ProjectConfig instance.
|
21
21
|
|
22
22
|
def config; end
|
23
|
+
def sdk_key; end
|
23
24
|
end
|
24
25
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
#
|
4
|
-
# Copyright 2019-2020, 2022, Optimizely and contributors
|
4
|
+
# Copyright 2019-2020, 2022-2023, Optimizely and contributors
|
5
5
|
#
|
6
6
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
7
|
# you may not use this file except in compliance with the License.
|
@@ -23,7 +23,7 @@ require_relative 'project_config_manager'
|
|
23
23
|
module Optimizely
|
24
24
|
class StaticProjectConfigManager < ProjectConfigManager
|
25
25
|
# Implementation of ProjectConfigManager interface.
|
26
|
-
attr_reader :config
|
26
|
+
attr_reader :config, :sdk_key
|
27
27
|
|
28
28
|
def initialize(datafile, logger, error_handler, skip_json_validation)
|
29
29
|
# Looks up and sets datafile and config based on response body.
|
@@ -41,6 +41,7 @@ module Optimizely
|
|
41
41
|
error_handler,
|
42
42
|
skip_json_validation
|
43
43
|
)
|
44
|
+
@sdk_key = @config&.sdk_key
|
44
45
|
@optimizely_config = nil
|
45
46
|
end
|
46
47
|
|
@@ -17,6 +17,7 @@
|
|
17
17
|
#
|
18
18
|
require_relative 'exceptions'
|
19
19
|
require_relative 'helpers/http_utils'
|
20
|
+
require_relative 'helpers/constants'
|
20
21
|
|
21
22
|
module Optimizely
|
22
23
|
class NoOpEventDispatcher
|
@@ -26,9 +27,6 @@ module Optimizely
|
|
26
27
|
end
|
27
28
|
|
28
29
|
class EventDispatcher
|
29
|
-
# @api constants
|
30
|
-
REQUEST_TIMEOUT = 10
|
31
|
-
|
32
30
|
def initialize(logger: nil, error_handler: nil, proxy_config: nil)
|
33
31
|
@logger = logger || NoOpLogger.new
|
34
32
|
@error_handler = error_handler || NoOpErrorHandler.new
|
@@ -40,7 +38,7 @@ module Optimizely
|
|
40
38
|
# @param event - Event object
|
41
39
|
def dispatch_event(event)
|
42
40
|
response = Helpers::HttpUtils.make_request(
|
43
|
-
event.url, event.http_verb, event.params.to_json, event.headers, REQUEST_TIMEOUT, @proxy_config
|
41
|
+
event.url, event.http_verb, event.params.to_json, event.headers, Helpers::Constants::EVENT_DISPATCH_CONFIG[:REQUEST_TIMEOUT], @proxy_config
|
44
42
|
)
|
45
43
|
|
46
44
|
error_msg = "Event failed to dispatch with response code: #{response.code}"
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
#
|
4
|
-
# Copyright 2016-2020, 2022, Optimizely and contributors
|
4
|
+
# Copyright 2016-2020, 2022-2023, Optimizely and contributors
|
5
5
|
#
|
6
6
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
7
|
# you may not use this file except in compliance with the License.
|
@@ -25,6 +25,20 @@ module Optimizely
|
|
25
25
|
end
|
26
26
|
end
|
27
27
|
|
28
|
+
class HTTPUriError < Error
|
29
|
+
# Raised when a provided URI is invalid.
|
30
|
+
def initialize(msg = 'Provided URI was invalid.')
|
31
|
+
super
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class MissingSdkKeyError < Error
|
36
|
+
# Raised when a provided URI is invalid.
|
37
|
+
def initialize(msg = 'SDK key not provided/cannot be found in the datafile.')
|
38
|
+
super
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
28
42
|
class InvalidAudienceError < Error
|
29
43
|
# Raised when an invalid audience is provided
|
30
44
|
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
#
|
4
|
-
# Copyright 2016-2020, Optimizely and contributors
|
4
|
+
# Copyright 2016-2020, 2022, Optimizely and contributors
|
5
5
|
#
|
6
6
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
7
|
# you may not use this file except in compliance with the License.
|
@@ -382,6 +382,15 @@ module Optimizely
|
|
382
382
|
'EVALUATING_AUDIENCES_COMBINED' => "Evaluating audiences for rule '%s': %s."
|
383
383
|
}.merge(AUDIENCE_EVALUATION_LOGS).freeze
|
384
384
|
|
385
|
+
ODP_LOGS = {
|
386
|
+
FETCH_SEGMENTS_FAILED: 'Audience segments fetch failed (%s).',
|
387
|
+
ODP_EVENT_FAILED: 'ODP event send failed (%s).',
|
388
|
+
ODP_NOT_ENABLED: 'ODP is not enabled.',
|
389
|
+
ODP_NOT_INTEGRATED: 'ODP is not integrated.',
|
390
|
+
ODP_INVALID_DATA: 'ODP data is not valid.',
|
391
|
+
ODP_INVALID_ACTION: 'ODP action is not valid (cannot be empty).'
|
392
|
+
}.freeze
|
393
|
+
|
385
394
|
DECISION_NOTIFICATION_TYPES = {
|
386
395
|
'AB_TEST' => 'ab-test',
|
387
396
|
'FEATURE' => 'feature',
|
@@ -406,6 +415,41 @@ module Optimizely
|
|
406
415
|
'REQUEST_TIMEOUT' => 10
|
407
416
|
}.freeze
|
408
417
|
|
418
|
+
EVENT_DISPATCH_CONFIG = {
|
419
|
+
REQUEST_TIMEOUT: 10
|
420
|
+
}.freeze
|
421
|
+
|
422
|
+
ODP_GRAPHQL_API_CONFIG = {
|
423
|
+
REQUEST_TIMEOUT: 10
|
424
|
+
}.freeze
|
425
|
+
|
426
|
+
ODP_REST_API_CONFIG = {
|
427
|
+
REQUEST_TIMEOUT: 10
|
428
|
+
}.freeze
|
429
|
+
|
430
|
+
ODP_SEGMENTS_CACHE_CONFIG = {
|
431
|
+
DEFAULT_CAPACITY: 10_000,
|
432
|
+
DEFAULT_TIMEOUT_SECONDS: 600
|
433
|
+
}.freeze
|
434
|
+
|
435
|
+
ODP_MANAGER_CONFIG = {
|
436
|
+
KEY_FOR_USER_ID: 'fs_user_id',
|
437
|
+
EVENT_TYPE: 'fullstack'
|
438
|
+
}.freeze
|
439
|
+
|
440
|
+
ODP_CONFIG_STATE = {
|
441
|
+
UNDETERMINED: 'UNDETERMINED',
|
442
|
+
INTEGRATED: 'INTEGRATED',
|
443
|
+
NOT_INTEGRATED: 'NOT_INTEGRATED'
|
444
|
+
}.freeze
|
445
|
+
|
446
|
+
ODP_EVENT_MANAGER = {
|
447
|
+
DEFAULT_QUEUE_CAPACITY: 10_000,
|
448
|
+
DEFAULT_BATCH_SIZE: 10,
|
449
|
+
DEFAULT_FLUSH_INTERVAL_SECONDS: 1,
|
450
|
+
DEFAULT_RETRY_COUNT: 3
|
451
|
+
}.freeze
|
452
|
+
|
409
453
|
HTTP_HEADERS = {
|
410
454
|
'IF_MODIFIED_SINCE' => 'If-Modified-Since',
|
411
455
|
'LAST_MODIFIED' => 'Last-Modified'
|
@@ -17,6 +17,7 @@
|
|
17
17
|
#
|
18
18
|
|
19
19
|
require 'net/http'
|
20
|
+
require_relative '../exceptions'
|
20
21
|
|
21
22
|
module Optimizely
|
22
23
|
module Helpers
|
@@ -28,6 +29,8 @@ module Optimizely
|
|
28
29
|
#
|
29
30
|
uri = URI.parse(url)
|
30
31
|
|
32
|
+
raise HTTPUriError unless uri.respond_to?(:request_uri)
|
33
|
+
|
31
34
|
case http_method
|
32
35
|
when :get
|
33
36
|
request = Net::HTTP::Get.new(uri.request_uri)
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Copyright 2022, Optimizely and contributors
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
18
|
+
|
19
|
+
require_relative 'constants'
|
20
|
+
|
21
|
+
module Optimizely
|
22
|
+
module Helpers
|
23
|
+
class OptimizelySdkSettings
|
24
|
+
attr_accessor :odp_disabled, :segments_cache_size, :segments_cache_timeout_in_secs, :odp_segments_cache, :odp_segment_manager,
|
25
|
+
:odp_event_manager, :fetch_segments_timeout, :odp_event_timeout, :odp_flush_interval
|
26
|
+
|
27
|
+
# Contains configuration used for Optimizely Project initialization.
|
28
|
+
#
|
29
|
+
# @param disable_odp - Set this flag to true (default = false) to disable ODP features.
|
30
|
+
# @param segments_cache_size - The maximum size of audience segments cache (optional. default = 10,000). Set to zero to disable caching.
|
31
|
+
# @param segments_cache_timeout_in_secs - The timeout in seconds of audience segments cache (optional. default = 600). Set to zero to disable timeout.
|
32
|
+
# @param odp_segments_cache - A custom odp segments cache. Required methods include: `save(key, value)`, `lookup(key) -> value`, and `reset()`
|
33
|
+
# @param odp_segment_manager - A custom odp segment manager. Required method is: `fetch_qualified_segments(user_key, user_value, options)`.
|
34
|
+
# @param odp_event_manager - A custom odp event manager. Required method is: `send_event(type:, action:, identifiers:, data:)`
|
35
|
+
# @param odp_segment_request_timeout - Time to wait in seconds for fetch_qualified_segments (optional. default = 10).
|
36
|
+
# @param odp_event_request_timeout - Time to wait in seconds for send_odp_events (optional. default = 10).
|
37
|
+
# @param odp_event_flush_interval - Time to wait in seconds for odp events to accumulate before sending (optional. default = 1).
|
38
|
+
def initialize(
|
39
|
+
disable_odp: false,
|
40
|
+
segments_cache_size: Constants::ODP_SEGMENTS_CACHE_CONFIG[:DEFAULT_CAPACITY],
|
41
|
+
segments_cache_timeout_in_secs: Constants::ODP_SEGMENTS_CACHE_CONFIG[:DEFAULT_TIMEOUT_SECONDS],
|
42
|
+
odp_segments_cache: nil,
|
43
|
+
odp_segment_manager: nil,
|
44
|
+
odp_event_manager: nil,
|
45
|
+
odp_segment_request_timeout: nil,
|
46
|
+
odp_event_request_timeout: nil,
|
47
|
+
odp_event_flush_interval: nil
|
48
|
+
)
|
49
|
+
@odp_disabled = disable_odp
|
50
|
+
@segments_cache_size = segments_cache_size
|
51
|
+
@segments_cache_timeout_in_secs = segments_cache_timeout_in_secs
|
52
|
+
@odp_segments_cache = odp_segments_cache
|
53
|
+
@odp_segment_manager = odp_segment_manager
|
54
|
+
@odp_event_manager = odp_event_manager
|
55
|
+
@fetch_segments_timeout = odp_segment_request_timeout
|
56
|
+
@odp_event_timeout = odp_event_request_timeout
|
57
|
+
@odp_flush_interval = odp_event_flush_interval
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -178,6 +178,61 @@ module Optimizely
|
|
178
178
|
|
179
179
|
value.is_a?(Numeric) && value.to_f.finite? && value.abs <= Constants::FINITE_NUMBER_LIMIT
|
180
180
|
end
|
181
|
+
|
182
|
+
def odp_data_types_valid?(data)
|
183
|
+
valid_types = [String, Float, Integer, TrueClass, FalseClass, NilClass]
|
184
|
+
data&.values&.all? { |e| valid_types.member? e.class }
|
185
|
+
end
|
186
|
+
|
187
|
+
def segments_cache_valid?(segments_cache)
|
188
|
+
# Determines if a given segments_cache is valid.
|
189
|
+
#
|
190
|
+
# segments_cache - custom cache to be validated.
|
191
|
+
#
|
192
|
+
# Returns boolean depending on whether cache has required methods.
|
193
|
+
(
|
194
|
+
segments_cache.respond_to?(:reset) &&
|
195
|
+
segments_cache.method(:reset)&.parameters&.empty? &&
|
196
|
+
segments_cache.respond_to?(:lookup) &&
|
197
|
+
segments_cache.method(:lookup)&.parameters&.length&.positive? &&
|
198
|
+
segments_cache.respond_to?(:save) &&
|
199
|
+
segments_cache.method(:save)&.parameters&.length&.positive?
|
200
|
+
)
|
201
|
+
end
|
202
|
+
|
203
|
+
def segment_manager_valid?(segment_manager)
|
204
|
+
# Determines if a given segment_manager is valid.
|
205
|
+
#
|
206
|
+
# segment_manager - custom manager to be validated.
|
207
|
+
#
|
208
|
+
# Returns boolean depending on whether manager has required methods.
|
209
|
+
(
|
210
|
+
segment_manager.respond_to?(:odp_config) &&
|
211
|
+
segment_manager.respond_to?(:reset) &&
|
212
|
+
segment_manager.method(:reset)&.parameters&.empty? &&
|
213
|
+
segment_manager.respond_to?(:fetch_qualified_segments) &&
|
214
|
+
(segment_manager.method(:fetch_qualified_segments)&.parameters&.length || 0) >= 3
|
215
|
+
)
|
216
|
+
end
|
217
|
+
|
218
|
+
def event_manager_valid?(event_manager)
|
219
|
+
# Determines if a given event_manager is valid.
|
220
|
+
#
|
221
|
+
# event_manager - custom manager to be validated.
|
222
|
+
#
|
223
|
+
# Returns boolean depending on whether manager has required method and parameters.
|
224
|
+
return false unless
|
225
|
+
event_manager.respond_to?(:send_event) &&
|
226
|
+
event_manager.respond_to?(:start!) &&
|
227
|
+
(event_manager.method(:start!)&.parameters&.length || 0) >= 1 &&
|
228
|
+
event_manager.respond_to?(:update_config) &&
|
229
|
+
event_manager.respond_to?(:stop!)
|
230
|
+
|
231
|
+
required_parameters = Set[%i[keyreq type], %i[keyreq action], %i[keyreq identifiers], %i[keyreq data]]
|
232
|
+
existing_parameters = event_manager.method(:send_event).parameters.to_set
|
233
|
+
|
234
|
+
existing_parameters & required_parameters == required_parameters
|
235
|
+
end
|
181
236
|
end
|
182
237
|
end
|
183
238
|
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Copyright 2023, Optimizely and contributors
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
18
|
+
require_relative 'notification_center'
|
19
|
+
require_relative 'exceptions'
|
20
|
+
|
21
|
+
module Optimizely
|
22
|
+
class NotificationCenterRegistry
|
23
|
+
private_class_method :new
|
24
|
+
# Class managing internal notification centers.
|
25
|
+
# @api no-doc
|
26
|
+
@notification_centers = {}
|
27
|
+
@mutex = Mutex.new
|
28
|
+
|
29
|
+
# Returns an internal notification center for the given sdk_key, creating one
|
30
|
+
# if none exists yet.
|
31
|
+
#
|
32
|
+
# Args:
|
33
|
+
# sdk_key: A string sdk key to uniquely identify the notification center.
|
34
|
+
# logger: Optional logger.
|
35
|
+
|
36
|
+
# Returns:
|
37
|
+
# nil or NotificationCenter
|
38
|
+
def self.get_notification_center(sdk_key, logger)
|
39
|
+
unless sdk_key
|
40
|
+
logger&.log(Logger::ERROR, "#{MissingSdkKeyError.new.message} ODP may not work properly without it.")
|
41
|
+
return nil
|
42
|
+
end
|
43
|
+
|
44
|
+
notification_center = nil
|
45
|
+
|
46
|
+
@mutex.synchronize do
|
47
|
+
if @notification_centers.key?(sdk_key)
|
48
|
+
notification_center = @notification_centers[sdk_key]
|
49
|
+
else
|
50
|
+
notification_center = NotificationCenter.new(logger, nil)
|
51
|
+
@notification_centers[sdk_key] = notification_center
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
notification_center
|
56
|
+
end
|
57
|
+
|
58
|
+
# Remove a previously added notification center and clear all its listeners.
|
59
|
+
|
60
|
+
# Args:
|
61
|
+
# sdk_key: The sdk_key of the notification center to remove.
|
62
|
+
def self.remove_notification_center(sdk_key)
|
63
|
+
@mutex.synchronize do
|
64
|
+
@notification_centers
|
65
|
+
.delete(sdk_key)
|
66
|
+
&.clear_all_notification_listeners
|
67
|
+
end
|
68
|
+
nil
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Copyright 2022, Optimizely and contributors
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
18
|
+
|
19
|
+
module Optimizely
|
20
|
+
class LRUCache
|
21
|
+
# Least Recently Used cache that invalidates entries older than the timeout.
|
22
|
+
|
23
|
+
attr_reader :capacity, :timeout
|
24
|
+
|
25
|
+
def initialize(capacity, timeout_in_secs)
|
26
|
+
# @param capacity - The max size of the cache. If set <= 0, caching is disabled.
|
27
|
+
# @param timeout_in_secs - Seconds until a cache item is considered stale.
|
28
|
+
# If set <= 0, items never expire.
|
29
|
+
@cache_mutex = Mutex.new
|
30
|
+
@map = {}
|
31
|
+
@capacity = capacity
|
32
|
+
@timeout = timeout_in_secs
|
33
|
+
end
|
34
|
+
|
35
|
+
# Retrieve the non stale value from the cache corresponding to the provided key
|
36
|
+
# or nil if key is not found
|
37
|
+
# Moves the key/value pair to the end of the cache
|
38
|
+
#
|
39
|
+
# @param key - The key to retrieve
|
40
|
+
|
41
|
+
def lookup(key)
|
42
|
+
return nil if @capacity <= 0
|
43
|
+
|
44
|
+
@cache_mutex.synchronize do
|
45
|
+
return nil unless @map.include?(key)
|
46
|
+
|
47
|
+
element = @map.delete(key)
|
48
|
+
return nil if element.stale?(@timeout)
|
49
|
+
|
50
|
+
@map[key] = element
|
51
|
+
|
52
|
+
element.value
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Save a key/value pair into the cache
|
57
|
+
# Moves the key/value pair to the end of the cache
|
58
|
+
#
|
59
|
+
# @param key - A user key
|
60
|
+
# @param value - A user value
|
61
|
+
|
62
|
+
def save(key, value)
|
63
|
+
return if @capacity <= 0
|
64
|
+
|
65
|
+
@cache_mutex.synchronize do
|
66
|
+
@map.delete(key) if @map.key?(key)
|
67
|
+
|
68
|
+
@map[key] = CacheElement.new(value)
|
69
|
+
|
70
|
+
@map.delete(@map.first[0]) if @map.size > @capacity
|
71
|
+
nil
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# Clears the cache
|
76
|
+
|
77
|
+
def reset
|
78
|
+
return if @capacity <= 0
|
79
|
+
|
80
|
+
@cache_mutex.synchronize { @map.clear }
|
81
|
+
nil
|
82
|
+
end
|
83
|
+
|
84
|
+
# Retrieve a value from the cache for a given key or nil if key is not found
|
85
|
+
# Doesn't update the cache
|
86
|
+
#
|
87
|
+
# @param key - The key to retrieve
|
88
|
+
|
89
|
+
def peek(key)
|
90
|
+
return nil if @capacity <= 0
|
91
|
+
|
92
|
+
@cache_mutex.synchronize { @map[key]&.value }
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
class CacheElement
|
97
|
+
# Individual element for the LRUCache.
|
98
|
+
attr_reader :value, :timestamp
|
99
|
+
|
100
|
+
def initialize(value)
|
101
|
+
@value = value
|
102
|
+
@timestamp = Time.new
|
103
|
+
end
|
104
|
+
|
105
|
+
def stale?(timeout)
|
106
|
+
# Returns true if the provided timeout has passed since the element's timestamp.
|
107
|
+
#
|
108
|
+
# @param timeout - The duration to check against
|
109
|
+
return false if timeout <= 0
|
110
|
+
|
111
|
+
Time.new - @timestamp >= timeout
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Copyright 2022, Optimizely and contributors
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
18
|
+
|
19
|
+
require 'optimizely/logger'
|
20
|
+
require_relative '../helpers/constants'
|
21
|
+
|
22
|
+
module Optimizely
|
23
|
+
class OdpConfig
|
24
|
+
ODP_CONFIG_STATE = Helpers::Constants::ODP_CONFIG_STATE
|
25
|
+
# Contains configuration used for ODP integration.
|
26
|
+
#
|
27
|
+
# @param api_host - The host URL for the ODP audience segments API (optional).
|
28
|
+
# @param api_key - The public API key for the ODP account from which the audience segments will be fetched (optional).
|
29
|
+
# @param segments_to_check - An array of all ODP segments used in the current datafile (associated with api_host/api_key).
|
30
|
+
def initialize(api_key = nil, api_host = nil, segments_to_check = [])
|
31
|
+
@api_key = api_key
|
32
|
+
@api_host = api_host
|
33
|
+
@segments_to_check = segments_to_check
|
34
|
+
@mutex = Mutex.new
|
35
|
+
@odp_state = @api_host.nil? || @api_key.nil? ? ODP_CONFIG_STATE[:UNDETERMINED] : ODP_CONFIG_STATE[:INTEGRATED]
|
36
|
+
end
|
37
|
+
|
38
|
+
# Replaces the existing configuration
|
39
|
+
#
|
40
|
+
# @param api_host - The host URL for the ODP audience segments API (optional).
|
41
|
+
# @param api_key - The public API key for the ODP account from which the audience segments will be fetched (optional).
|
42
|
+
# @param segments_to_check - An array of all ODP segments used in the current datafile (associated with api_host/api_key).
|
43
|
+
#
|
44
|
+
# @return - True if the provided values were different than the existing values.
|
45
|
+
|
46
|
+
def update(api_key = nil, api_host = nil, segments_to_check = [])
|
47
|
+
updated = false
|
48
|
+
@mutex.synchronize do
|
49
|
+
@odp_state = api_host.nil? || api_key.nil? ? ODP_CONFIG_STATE[:NOT_INTEGRATED] : ODP_CONFIG_STATE[:INTEGRATED]
|
50
|
+
|
51
|
+
if @api_key != api_key || @api_host != api_host || @segments_to_check != segments_to_check
|
52
|
+
@api_key = api_key
|
53
|
+
@api_host = api_host
|
54
|
+
@segments_to_check = segments_to_check
|
55
|
+
updated = true
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
updated
|
60
|
+
end
|
61
|
+
|
62
|
+
# Returns the api host for odp connections
|
63
|
+
#
|
64
|
+
# @return - The api host.
|
65
|
+
|
66
|
+
def api_host
|
67
|
+
@mutex.synchronize { @api_host.clone }
|
68
|
+
end
|
69
|
+
|
70
|
+
# Returns the api key for odp connections
|
71
|
+
#
|
72
|
+
# @return - The api key.
|
73
|
+
|
74
|
+
def api_key
|
75
|
+
@mutex.synchronize { @api_key.clone }
|
76
|
+
end
|
77
|
+
|
78
|
+
# Returns An array of qualified segments for this user
|
79
|
+
#
|
80
|
+
# @return - An array of segments names.
|
81
|
+
|
82
|
+
def segments_to_check
|
83
|
+
@mutex.synchronize { @segments_to_check.clone }
|
84
|
+
end
|
85
|
+
|
86
|
+
# Replace qualified segments with provided segments
|
87
|
+
#
|
88
|
+
# @param segments - An array of segment names
|
89
|
+
|
90
|
+
def segments_to_check=(segments_to_check)
|
91
|
+
@mutex.synchronize { @segments_to_check = segments_to_check.clone }
|
92
|
+
end
|
93
|
+
|
94
|
+
# Returns the state of odp integration (UNDETERMINED, INTEGRATED, NOT_INTEGRATED)
|
95
|
+
#
|
96
|
+
# @return - string
|
97
|
+
|
98
|
+
def odp_state
|
99
|
+
@mutex.synchronize { @odp_state }
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|