rox-rollout 4.1.0 → 5.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.
Files changed (121) hide show
  1. checksums.yaml +5 -5
  2. data/.circleci/config.yml +51 -23
  3. data/.editorconfig +12 -0
  4. data/.github/workflows/ruby.yml +20 -0
  5. data/.gitignore +2 -0
  6. data/.rubocop.yml +17 -0
  7. data/Gemfile +2 -2
  8. data/README.md +32 -0
  9. data/Rakefile +12 -9
  10. data/bin/console +3 -4
  11. data/e2e-server/run_server.sh +12 -0
  12. data/e2e-server/server.rb +158 -0
  13. data/e2e/container.rb +11 -14
  14. data/e2e/custom_props.rb +9 -9
  15. data/e2e/rox_e2e_test.rb +18 -20
  16. data/e2e/test_vars.rb +3 -6
  17. data/example/local.rb +40 -0
  18. data/lib/rox/core/analytics.rb +18 -0
  19. data/lib/rox/core/client/buid.rb +9 -44
  20. data/lib/rox/core/client/device_properties.rb +8 -2
  21. data/lib/rox/core/client/dynamic_api.rb +53 -15
  22. data/lib/rox/core/client/internal_flags.rb +14 -2
  23. data/lib/rox/core/client/sdk_settings.rb +1 -1
  24. data/lib/rox/core/configuration/configuration.rb +1 -1
  25. data/lib/rox/core/configuration/configuration_fetched_args.rb +2 -2
  26. data/lib/rox/core/configuration/configuration_fetched_invoker.rb +8 -3
  27. data/lib/rox/core/configuration/configuration_parser.rb +39 -38
  28. data/lib/rox/core/configuration/fetcher_error.rb +1 -1
  29. data/lib/rox/core/configuration/fetcher_status.rb +1 -1
  30. data/lib/rox/core/configuration/models/experiment_model.rb +1 -1
  31. data/lib/rox/core/configuration/models/target_group_model.rb +1 -1
  32. data/lib/rox/core/consts/build.rb +3 -3
  33. data/lib/rox/core/consts/environment.rb +34 -10
  34. data/lib/rox/core/consts/property_type.rb +18 -17
  35. data/lib/rox/core/context/merged_context.rb +2 -1
  36. data/lib/rox/core/core.rb +101 -33
  37. data/lib/rox/core/entities/default_flag_values.rb +10 -0
  38. data/lib/rox/core/entities/flag.rb +22 -7
  39. data/lib/rox/core/entities/flag_setter.rb +6 -6
  40. data/lib/rox/core/entities/rox_double.rb +11 -0
  41. data/lib/rox/core/entities/rox_int.rb +11 -0
  42. data/lib/rox/core/entities/{variant.rb → rox_string.rb} +20 -13
  43. data/lib/rox/core/error_handling/exception_trigger.rb +10 -0
  44. data/lib/rox/core/error_handling/userspace_handler_exception.rb +12 -0
  45. data/lib/rox/core/error_handling/userspace_unhandled_error_invoker.rb +41 -0
  46. data/lib/rox/core/impression/impression_args.rb +2 -2
  47. data/lib/rox/core/impression/impression_invoker.rb +41 -7
  48. data/lib/rox/core/impression/models/experiment.rb +1 -1
  49. data/lib/rox/core/impression/models/reporting_value.rb +10 -2
  50. data/lib/rox/core/logging/logging.rb +3 -3
  51. data/lib/rox/core/logging/no_op_logger.rb +1 -1
  52. data/lib/rox/core/network/configuration_fetch_result.rb +1 -1
  53. data/lib/rox/core/network/configuration_fetcher.rb +9 -8
  54. data/lib/rox/core/network/configuration_fetcher_base.rb +1 -1
  55. data/lib/rox/core/network/configuration_fetcher_roxy.rb +4 -4
  56. data/lib/rox/core/network/configuration_fetcher_self_managed.rb +30 -0
  57. data/lib/rox/core/network/configuration_source.rb +1 -1
  58. data/lib/rox/core/network/request.rb +3 -3
  59. data/lib/rox/core/network/request_configuration_builder.rb +12 -9
  60. data/lib/rox/core/network/request_data.rb +1 -1
  61. data/lib/rox/core/network/response.rb +20 -4
  62. data/lib/rox/core/network/state_sender.rb +146 -0
  63. data/lib/rox/core/notifications/notification_listener.rb +4 -4
  64. data/lib/rox/core/properties/custom_property.rb +1 -1
  65. data/lib/rox/core/properties/custom_property_type.rb +1 -1
  66. data/lib/rox/core/properties/device_property.rb +2 -2
  67. data/lib/rox/core/properties/property_factory.rb +132 -0
  68. data/lib/rox/core/register/registerer.rb +13 -12
  69. data/lib/rox/core/reporting/error_reporter.rb +14 -13
  70. data/lib/rox/core/repositories/custom_property_repository.rb +1 -1
  71. data/lib/rox/core/repositories/experiment_repository.rb +2 -4
  72. data/lib/rox/core/repositories/flag_repository.rb +8 -8
  73. data/lib/rox/core/repositories/roxx/experiments_extensions.rb +8 -8
  74. data/lib/rox/core/repositories/roxx/properties_extensions.rb +32 -7
  75. data/lib/rox/core/repositories/target_group_repository.rb +2 -4
  76. data/lib/rox/core/roxx/evaluation_result.rb +2 -0
  77. data/lib/rox/core/roxx/node.rb +1 -1
  78. data/lib/rox/core/roxx/parser.rb +46 -20
  79. data/lib/rox/core/roxx/regular_expression_extensions.rb +6 -3
  80. data/lib/rox/core/roxx/string_tokenizer.rb +3 -1
  81. data/lib/rox/core/roxx/symbols.rb +1 -1
  82. data/lib/rox/core/roxx/token_type.rb +1 -1
  83. data/lib/rox/core/roxx/tokenized_expression.rb +16 -12
  84. data/lib/rox/core/roxx/value_compare_extensions.rb +49 -29
  85. data/lib/rox/core/security/signature_verifier.rb +3 -3
  86. data/lib/rox/core/security/signature_verifier_mock.rb +12 -0
  87. data/lib/rox/core/utils/debouncer.rb +18 -0
  88. data/lib/rox/core/utils/type_utils.rb +1 -1
  89. data/lib/rox/server/client/sdk_settings.rb +1 -1
  90. data/lib/rox/server/client/server_properties.rb +1 -1
  91. data/lib/rox/server/flags/normalize_flag_type.rb +25 -0
  92. data/lib/rox/server/flags/rox_double.rb +8 -0
  93. data/lib/rox/server/flags/rox_flag.rb +1 -1
  94. data/lib/rox/server/flags/rox_int.rb +8 -0
  95. data/lib/rox/server/flags/rox_string.rb +8 -0
  96. data/lib/rox/server/flags/server_entities_provider.rb +14 -4
  97. data/lib/rox/server/logging/server_logger.rb +2 -2
  98. data/lib/rox/server/rox_options.rb +39 -8
  99. data/lib/rox/server/rox_server.rb +81 -60
  100. data/lib/rox/version.rb +1 -1
  101. data/rox.gemspec +13 -9
  102. metadata +95 -36
  103. data/README_DEVELOP.md +0 -19
  104. data/_archive/.document +0 -5
  105. data/_archive/.rspec +0 -1
  106. data/_archive/Gemfile +0 -15
  107. data/_archive/Gemfile.lock +0 -87
  108. data/_archive/README.md +0 -32
  109. data/_archive/README.rdoc +0 -19
  110. data/_archive/Rakefile +0 -50
  111. data/_archive/lib/expr_function_definition.rb +0 -52
  112. data/_archive/lib/function_definition.rb +0 -48
  113. data/_archive/lib/function_token.rb +0 -12
  114. data/_archive/lib/object_extends.rb +0 -12
  115. data/_archive/lib/ruby_interpreter.rb +0 -292
  116. data/_archive/lib/stack.rb +0 -48
  117. data/_archive/lib/string_extends.rb +0 -14
  118. data/_archive/spec/ruby_interpreter_spec.rb +0 -203
  119. data/_archive/spec/spec_helper.rb +0 -30
  120. data/_archive/spec/stack_spec.rb +0 -77
  121. data/lib/rox/server/flags/rox_variant.rb +0 -8
@@ -3,12 +3,24 @@ require 'rox/core/entities/flag'
3
3
  module Rox
4
4
  module Core
5
5
  class InternalFlags
6
- def initialize(experiment_repository, parser)
6
+ @@defaults_self_managed = {
7
+ 'rox.internal.pushUpdates' => false,
8
+ 'rox.internal.considerThrottleInPush' => false,
9
+ 'rox.internal.throttleFetchInSeconds' => 0
10
+ }
11
+
12
+ def initialize(experiment_repository, parser, rox_options)
7
13
  @experiment_repository = experiment_repository
8
14
  @parser = parser
15
+ @rox_options = rox_options
9
16
  end
10
17
 
11
18
  def enabled?(flag_name)
19
+ if @rox_options.self_managed?
20
+ value = @@defaults_self_managed[flag_name]
21
+ return value unless value.nil?
22
+ end
23
+
12
24
  internal_experiment = @experiment_repository.experiment_by_flag(flag_name)
13
25
  return false if internal_experiment.nil?
14
26
 
@@ -17,4 +29,4 @@ module Rox
17
29
  end
18
30
  end
19
31
  end
20
- end
32
+ end
@@ -2,4 +2,4 @@ module Rox
2
2
  module Core
3
3
  SdkSettings = Struct.new(:api_key, :dev_mode_secret)
4
4
  end
5
- end
5
+ end
@@ -2,4 +2,4 @@ module Rox
2
2
  module Core
3
3
  Configuration = Struct.new(:experiments, :target_groups, :signature_date)
4
4
  end
5
- end
5
+ end
@@ -14,10 +14,10 @@ module Rox
14
14
  end
15
15
 
16
16
  def self.error(error_details)
17
- args = ConfigurationFetchedArgs.new(FetcherStatus::ERROR_FETCHED_FAILED, nil, false )
17
+ args = ConfigurationFetchedArgs.new(FetcherStatus::ERROR_FETCHED_FAILED, nil, false)
18
18
  args.error_details = error_details
19
19
  args
20
20
  end
21
21
  end
22
22
  end
23
- end
23
+ end
@@ -3,9 +3,10 @@ require 'rox/core/configuration/configuration_fetched_args'
3
3
  module Rox
4
4
  module Core
5
5
  class ConfigurationFetchedInvoker
6
- def initialize
6
+ def initialize(user_unhandled_error_invoker)
7
7
  @fetched_handlers = []
8
8
  @mutex = Mutex.new
9
+ @user_unhandled_error_invoker = user_unhandled_error_invoker
9
10
  end
10
11
 
11
12
  def invoke(fetcher_status, creation_date, has_changes)
@@ -29,9 +30,13 @@ module Rox
29
30
  end
30
31
 
31
32
  handlers.each do |handler|
32
- handler.call(args)
33
+ begin
34
+ handler.call(args)
35
+ rescue StandardError => e
36
+ user_unhandled_error_invoker.invoke(handler, Rox::Core::CONFIGURATION_FETCHED_HANDLER, e)
37
+ end
33
38
  end
34
39
  end
35
40
  end
36
41
  end
37
- end
42
+ end
@@ -17,50 +17,51 @@ module Rox
17
17
  end
18
18
 
19
19
  def parse(fetch_result, sdk_settings)
20
- begin
21
- if fetch_result.nil? || fetch_result.data.nil? || fetch_result.data.empty?
22
- @configuration_fetched_invoker.invoke_error(FetcherError::EMPTY_JSON)
23
- @error_reporter.report('Failed to parse JSON configuration - Null Or Empty', ArgumentError.new('data'))
24
- return nil
25
- end
26
-
27
- begin
28
- json_obj = JSON.parse(fetch_result.data)
29
- rescue StandardError => ex
30
- @configuration_fetched_invoker.invoke_error(FetcherError::CORRUPTED_JSON)
31
- @error_reporter.report('Failed to parse JSON configuration', ex)
32
- return nil
33
- end
34
-
35
- if fetch_result.source != ConfigurationSource::ROXY && !@signature_verifier.verify(json_obj['data'], json_obj['signature_v0'])
36
- @configuration_fetched_invoker.invoke_error(FetcherError::SIGNATURE_VERIFICATION_ERROR)
37
- @error_reporter.report('Failed to validate signature', StandardError.new("Data : #{json_obj['data']} Signature : #{json_obj['signature_v0']}"))
38
- return nil
39
- end
20
+ if fetch_result.nil? || fetch_result.data.nil? || fetch_result.data.empty?
21
+ @configuration_fetched_invoker.invoke_error(FetcherError::EMPTY_JSON)
22
+ @error_reporter.report('Failed to parse JSON configuration - Null Or Empty', ArgumentError.new('data'))
23
+ return nil
24
+ end
40
25
 
41
- signature_date = DateTime.rfc3339(json_obj['signed_date']).to_time
42
- internal_data_string = json_obj['data']
43
- internal_data_object = JSON.parse(internal_data_string)
26
+ begin
27
+ json_obj = JSON.parse(fetch_result.data)
28
+ rescue StandardError => e
29
+ @configuration_fetched_invoker.invoke_error(FetcherError::CORRUPTED_JSON)
30
+ @error_reporter.report('Failed to parse JSON configuration', e)
31
+ return nil
32
+ end
44
33
 
45
- if fetch_result.source != ConfigurationSource::ROXY && internal_data_object['application'] != sdk_settings.api_key
46
- @configuration_fetched_invoker.invoke_error(FetcherError::MISMATCH_APP_KEY)
47
- @error_reporter.report('Failed to parse JSON configuration - ', StandardError.new("Internal Data: #{internal_data_object['application']} SdkSettings: #{sdk_settings.api_key}"))
48
- return nil
49
- end
34
+ if fetch_result.source != ConfigurationSource::ROXY && !@signature_verifier.verify(json_obj['data'],
35
+ json_obj['signature_v0'])
36
+ @configuration_fetched_invoker.invoke_error(FetcherError::SIGNATURE_VERIFICATION_ERROR)
37
+ @error_reporter.report('Failed to validate signature',
38
+ StandardError.new("Data : #{json_obj['data']} Signature : #{json_obj['signature_v0']}"))
39
+ return nil
40
+ end
50
41
 
51
- experiments = parse_experiments(internal_data_object)
52
- target_groups = parse_target_groups(internal_data_object)
42
+ signature_date = DateTime.rfc3339(json_obj['signed_date']).to_time
43
+ internal_data_string = json_obj['data']
44
+ internal_data_object = JSON.parse(internal_data_string)
53
45
 
54
- Configuration.new(experiments, target_groups, signature_date)
55
- rescue StandardError => ex
56
- Logging.logger.error('Failed to parse configurations', ex)
57
- @configuration_fetched_invoker.invoke_error(FetcherError::UNKNOWN)
46
+ if fetch_result.source != ConfigurationSource::ROXY && internal_data_object['application'] != sdk_settings.api_key
47
+ @configuration_fetched_invoker.invoke_error(FetcherError::MISMATCH_APP_KEY)
48
+ @error_reporter.report('Failed to parse JSON configuration - ',
49
+ StandardError.new("Internal Data: #{internal_data_object['application']} SdkSettings: #{sdk_settings.api_key}"))
58
50
  return nil
59
51
  end
52
+
53
+ experiments = parse_experiments(internal_data_object)
54
+ target_groups = parse_target_groups(internal_data_object)
55
+
56
+ Configuration.new(experiments, target_groups, signature_date)
57
+ rescue StandardError => e
58
+ Logging.logger.error('Failed to parse configurations', e)
59
+ @configuration_fetched_invoker.invoke_error(FetcherError::UNKNOWN)
60
+ nil
60
61
  end
61
62
 
62
63
  def parse_experiments(data)
63
- data['experiments'].map { |e| parse_experiment(e)}
64
+ data['experiments'].map { |e| parse_experiment(e) }
64
65
  end
65
66
 
66
67
  def parse_experiment(data)
@@ -69,12 +70,12 @@ module Rox
69
70
  name = data['name']
70
71
  id = data['_id']
71
72
  labels = data['labels'] || []
72
- flags = data['featureFlags'].map {|f| f['name']}
73
+ flags = data['featureFlags'].map { |f| f['name'] }
73
74
  ExperimentModel.new(id, name, condition, is_archived, flags, labels)
74
75
  end
75
76
 
76
77
  def parse_target_groups(data)
77
- data['targetGroups'].map { |g| parse_target_group(g)}
78
+ data['targetGroups'].map { |g| parse_target_group(g) }
78
79
  end
79
80
 
80
81
  def parse_target_group(data)
@@ -82,4 +83,4 @@ module Rox
82
83
  end
83
84
  end
84
85
  end
85
- end
86
+ end
@@ -10,4 +10,4 @@ module Rox
10
10
  NO_ERROR = 'NoError'.freeze
11
11
  end
12
12
  end
13
- end
13
+ end
@@ -7,4 +7,4 @@ module Rox
7
7
  ERROR_FETCHED_FAILED = 'ErrorFetchedFailed'.freeze
8
8
  end
9
9
  end
10
- end
10
+ end
@@ -2,4 +2,4 @@ module Rox
2
2
  module Core
3
3
  ExperimentModel = Struct.new(:id, :name, :condition, :is_archived, :flags, :labels)
4
4
  end
5
- end
5
+ end
@@ -2,4 +2,4 @@ module Rox
2
2
  module Core
3
3
  TargetGroupModel = Struct.new(:id, :condition)
4
4
  end
5
- end
5
+ end
@@ -1,8 +1,8 @@
1
1
  module Rox
2
2
  module Core
3
3
  module Build
4
- PLATFORM = 'Ruby'
5
- API_VERSION = '1.7.0'
4
+ PLATFORM = 'Ruby'.freeze
5
+ API_VERSION = '1.9.0'.freeze
6
6
  end
7
7
  end
8
- end
8
+ end
@@ -8,33 +8,57 @@ module Rox
8
8
  def self.cdn_path
9
9
  case ENV['ROLLOUT_MODE']
10
10
  when 'QA'
11
- 'https://s3.amazonaws.com/qa-rox-conf.rollout.io/v1/qa'
11
+ 'https://qa-conf.rollout.io'
12
12
  when 'LOCAL'
13
- 'https://s3.amazonaws.com/qa-rox-conf.rollout.io/v1/development'
13
+ 'https://development-conf.rollout.io'
14
14
  else
15
- 'https://s3.amazonaws.com/rox-conf.rollout.io/v1/production'
15
+ 'https://conf.rollout.io'
16
16
  end
17
17
  end
18
18
 
19
- def self.api_path
19
+ def self.api_path(api_url = nil)
20
20
  case ENV['ROLLOUT_MODE']
21
21
  when 'QA'
22
- 'https://qax.rollout.io/device/request_configuration'
22
+ 'https://qa-api.rollout.io/device/get_configuration'
23
23
  when 'LOCAL'
24
- 'http://127.0.0.1:8557/device/request_configuration'
24
+ 'http://127.0.0.1:8557/device/get_configuration'
25
25
  else
26
- 'https://x-api.rollout.io/device/request_configuration'
26
+ api_url ||= 'https://x-api.rollout.io'
27
+ "#{api_url}/device/get_configuration"
27
28
  end
28
29
  end
29
30
 
30
- def self.analytics_path
31
+ def self.state_cdn_path
32
+ case ENV['ROLLOUT_MODE']
33
+ when 'QA'
34
+ 'https://qa-statestore.rollout.io'
35
+ when 'LOCAL'
36
+ 'https://development-statestore.rollout.io'
37
+ else
38
+ 'https://statestore.rollout.io'
39
+ end
40
+ end
41
+
42
+ def self.state_api_path(api_url = nil)
43
+ case ENV['ROLLOUT_MODE']
44
+ when 'QA'
45
+ 'https://qa-api.rollout.io/device/update_state_store'
46
+ when 'LOCAL'
47
+ 'http://127.0.0.1:8557/device/update_state_store'
48
+ else
49
+ api_url ||= 'https://x-api.rollout.io'
50
+ "#{api_url}/device/update_state_store"
51
+ end
52
+ end
53
+
54
+ def self.analytics_path(analytics_url = 'https://analytic.rollout.io')
31
55
  case ENV['ROLLOUT_MODE']
32
56
  when 'QA'
33
57
  'https://qaanalytic.rollout.io'
34
58
  when 'LOCAL'
35
59
  'http://127.0.0.1:8787'
36
60
  else
37
- 'https://analytic.rollout.io'
61
+ analytics_url
38
62
  end
39
63
  end
40
64
 
@@ -50,4 +74,4 @@ module Rox
50
74
  end
51
75
  end
52
76
  end
53
- end
77
+ end
@@ -8,22 +8,23 @@ module Rox
8
8
  @name = name
9
9
  end
10
10
 
11
- CACHE_MISS_URL = PropertyType.new(1, 'cache_miss_url')
12
- PACKAGE_NAME = PropertyType.new(2, "package_name")
13
- VERSION_NAME = PropertyType.new(3, "version_name")
14
- LIB_VERSION = PropertyType.new(4, "lib_version")
15
- ROLLOUT_BUILD = PropertyType.new(5, "rollout_build")
16
- API_VERSION = PropertyType.new(6, "api_version")
17
- BUID = PropertyType.new(7, "buid")
18
- BUID_GENERATORS_LIST = PropertyType.new(8, "buid_generators_list")
19
- APP_VERSION = PropertyType.new(9, "app_version")
20
- APP_RELEASE = PropertyType.new(10, "app_release")
21
- DISTINCT_ID = PropertyType.new(11, "distinct_id")
22
- APP_KEY = PropertyType.new(12, "app_key")
23
- FEATURE_FLAGS = PropertyType.new(13, "feature_flags")
24
- REMOTE_VARIABLES = PropertyType.new(14, "remote_variables")
25
- CUSTOM_PROPERTIES = PropertyType.new(15, "custom_properties")
26
- PLATFORM = PropertyType.new(16, "platform")
11
+ CACHE_MISS_RELATIVE_URL = PropertyType.new(1, 'cache_miss_relative_url')
12
+ PACKAGE_NAME = PropertyType.new(2, 'package_name')
13
+ VERSION_NAME = PropertyType.new(3, 'version_name')
14
+ LIB_VERSION = PropertyType.new(4, 'lib_version')
15
+ ROLLOUT_BUILD = PropertyType.new(5, 'rollout_build')
16
+ API_VERSION = PropertyType.new(6, 'api_version')
17
+ BUID = PropertyType.new(7, 'buid')
18
+ BUID_GENERATORS_LIST = PropertyType.new(8, 'buid_generators_list')
19
+ APP_VERSION = PropertyType.new(9, 'app_version')
20
+ APP_RELEASE = PropertyType.new(10, 'app_release')
21
+ DISTINCT_ID = PropertyType.new(11, 'distinct_id')
22
+ APP_KEY = PropertyType.new(12, 'app_key')
23
+ FEATURE_FLAGS = PropertyType.new(13, 'feature_flags')
24
+ CUSTOM_PROPERTIES = PropertyType.new(14, 'custom_properties')
25
+ PLATFORM = PropertyType.new(15, 'platform')
26
+ DEV_MODE_SECRET = PropertyType.new(16, 'dev_mode_secret')
27
+ STATE_MD5 = PropertyType.new(17, 'state_md5')
27
28
  end
28
29
  end
29
- end
30
+ end
@@ -9,8 +9,9 @@ module Rox
9
9
  def [](key)
10
10
  return @local_context[key] unless @local_context.nil? || @local_context[key].nil?
11
11
  return @global_context[key] unless @global_context.nil?
12
+
12
13
  nil
13
14
  end
14
15
  end
15
16
  end
16
- end
17
+ end
data/lib/rox/core/core.rb CHANGED
@@ -11,6 +11,8 @@ require 'rox/core/network/request_configuration_builder'
11
11
  require 'rox/core/network/request'
12
12
  require 'rox/core/network/configuration_fetcher'
13
13
  require 'rox/core/network/configuration_fetcher_roxy'
14
+ require 'rox/core/network/configuration_fetcher_self_managed'
15
+ require 'rox/core/network/state_sender'
14
16
  require 'rox/core/notifications/notification_listener'
15
17
  require 'rox/core/register/registerer'
16
18
  require 'rox/core/client/internal_flags'
@@ -19,8 +21,11 @@ require 'rox/core/entities/flag_setter'
19
21
  require 'rox/core/impression/impression_invoker'
20
22
  require 'rox/core/reporting/error_reporter'
21
23
  require 'rox/core/security/signature_verifier'
24
+ require 'rox/core/security/signature_verifier_mock'
22
25
  require 'rox/core/utils/periodic_task'
23
26
  require 'rox/core/client/dynamic_api'
27
+ require 'rox/core/analytics'
28
+ require 'rox/core/error_handling/userspace_unhandled_error_invoker'
24
29
 
25
30
  module Rox
26
31
  module Core
@@ -30,14 +35,10 @@ module Rox
30
35
  @custom_property_repository = CustomPropertyRepository.new
31
36
  @target_group_repository = TargetGroupRepository.new
32
37
  @experiment_repository = ExperimentRepository.new
33
- @parser = Parser.new
38
+ @user_unhandled_error_invoker = Rox::Core::UserspaceUnhandledErrorInvoker.new
39
+ @parser = Parser.new(@user_unhandled_error_invoker)
34
40
 
35
- experiments_extensions = ExperimentsExtensions.new(@parser, @target_group_repository, @flag_repository, @experiment_repository)
36
- properties_extensions = PropertiesExtensions.new(@parser, @custom_property_repository)
37
- experiments_extensions.extend
38
- properties_extensions.extend
39
-
40
- @configuration_fetched_invoker = ConfigurationFetchedInvoker.new
41
+ @configuration_fetched_invoker = ConfigurationFetchedInvoker.new(@user_unhandled_error_invoker)
41
42
  @registerer = Registerer.new(@flag_repository)
42
43
 
43
44
  @sdk_settings = nil
@@ -50,57 +51,106 @@ module Rox
50
51
  @push_updates_listener = nil
51
52
  end
52
53
 
53
- def setup(sdk_settings, device_properties, rox_options)
54
+ def userspace_unhandled_error_handler=(handler)
55
+ @user_unhandled_error_invoker.handler = handler
56
+ end
57
+
58
+ def setup(sdk_settings, device_properties)
54
59
  @sdk_settings = sdk_settings
60
+ @rox_options = device_properties.rox_options
55
61
 
56
- roxy_path = rox_options.nil? || rox_options.roxy_url.nil? ? nil : rox_options.roxy_url
62
+ experiments_extensions = ExperimentsExtensions.new(@parser, @target_group_repository, @flag_repository,
63
+ @experiment_repository)
64
+ properties_extensions = PropertiesExtensions.new(@parser, @custom_property_repository, @rox_options.dynamic_property_rule_handler)
65
+ experiments_extensions.extend
66
+ properties_extensions.extend
67
+
68
+ roxy_path = @rox_options.nil? || @rox_options.roxy_url.nil? ? nil : @rox_options.roxy_url
69
+
70
+ validate_api_key(sdk_settings&.api_key) if roxy_path.nil?
57
71
 
58
72
  # TODO: Analytics.Analytics.Initialize(deviceProperties.RolloutKey, deviceProperties)
59
- @internal_flags = InternalFlags.new(@experiment_repository, @parser)
73
+ @internal_flags = InternalFlags.new(@experiment_repository, @parser, @rox_options)
60
74
 
75
+ @analytics_client = Analytics.new(sdk_settings.api_key).client
61
76
  # TODO: impressionInvoker = new ImpressionInvoker(internalFlags, customPropertyRepository, deviceProperties, Analytics.Analytics.Client, roxyPath != null);
62
- @impression_invoker = ImpressionInvoker.new(@internal_flags, @custom_property_repository, device_properties, nil, !roxy_path.nil?)
77
+ @impression_invoker = ImpressionInvoker.new(@internal_flags, @custom_property_repository, device_properties,
78
+ @analytics_client, !roxy_path.nil?, @user_unhandled_error_invoker)
63
79
  @flag_setter = FlagSetter.new(@flag_repository, @parser, @experiment_repository, @impression_invoker)
64
80
  buid = BUID.new(sdk_settings, device_properties, @flag_repository, @custom_property_repository)
65
81
 
66
- request_config_builder = RequestConfigurationBuilder.new(sdk_settings, buid, device_properties, roxy_path)
82
+ request_config_builder = RequestConfigurationBuilder.new(sdk_settings, buid, device_properties)
67
83
 
68
84
  client_request = Request.new
69
85
  err_reporter_request = Request.new
70
86
 
71
87
  @error_reporter = ErrorReporter.new(err_reporter_request, device_properties, buid)
72
88
 
73
- if roxy_path.nil?
74
- @configuration_fetcher = ConfigurationFetcher.new(request_config_builder, client_request, @configuration_fetched_invoker)
89
+ if @rox_options.self_managed?
90
+ @configuration_fetcher = ConfigurationFetcherSelfManaged.new(request_config_builder, client_request,
91
+ @configuration_fetched_invoker)
92
+ @state_sender = StateSender.new(@sdk_settings, device_properties, @flag_repository,
93
+ @custom_property_repository)
94
+ @flag_repository.register_flag_added_handler { @state_sender.delayed_send }
95
+ @custom_property_repository.register_property_added_handler { @state_sender.delayed_send }
96
+ elsif roxy_path.nil?
97
+ @configuration_fetcher = ConfigurationFetcher.new(request_config_builder, client_request,
98
+ @configuration_fetched_invoker)
99
+ @state_sender = StateSender.new(@sdk_settings, device_properties, @flag_repository,
100
+ @custom_property_repository)
101
+ @flag_repository.register_flag_added_handler { @state_sender.delayed_send }
102
+ @custom_property_repository.register_property_added_handler { @state_sender.delayed_send }
75
103
  else
76
- @configuration_fetcher = ConfigurationFetcherRoxy.new(request_config_builder, client_request, @configuration_fetched_invoker)
104
+ @configuration_fetcher = ConfigurationFetcherRoxy.new(request_config_builder, client_request,
105
+ @configuration_fetched_invoker)
77
106
  end
78
107
 
79
108
  configuration_fetched_handler = nil
80
- unless rox_options.nil?
81
- configuration_fetched_handler = rox_options.configuration_fetched_handler
82
- end
109
+ configuration_fetched_handler = @rox_options.configuration_fetched_handler unless @rox_options.nil?
83
110
 
84
111
  @configuration_fetched_invoker.register_fetched_handler(&wrap_configuration_fetched_handler(&configuration_fetched_handler))
85
112
 
86
- Thread.new do
113
+ @thread = Thread.new do
87
114
  Thread.current.report_on_exception = false if Thread.current.respond_to?(:report_on_exception)
88
115
  fetch
116
+ @state_sender&.send
89
117
 
90
- if !rox_options.nil? && !rox_options.impression_handler.nil?
91
- @impression_invoker.register_impression_handler(&rox_options.impression_handler)
118
+ if !@rox_options.nil? && !@rox_options.impression_handler.nil?
119
+ @impression_invoker.register_impression_handler(&@rox_options.impression_handler)
92
120
  end
93
121
 
94
- if !rox_options.nil? && !rox_options.fetch_interval.nil?
95
- PeriodicTask.run(rox_options.fetch_interval) { fetch }
122
+ if !@rox_options.nil? && !@rox_options.fetch_interval.nil?
123
+ PeriodicTask.run(@rox_options.fetch_interval) { fetch }
96
124
  end
97
125
  end
98
126
  end
99
127
 
128
+ def shutdown
129
+ return if @thread.nil?
130
+
131
+ Thread.kill(@thread)
132
+ @thread = nil
133
+
134
+ unless @push_updates_listener.nil?
135
+ @push_updates_listener.stop
136
+ @push_updates_listener = nil
137
+ end
138
+
139
+ return if @analytics_client.nil?
140
+
141
+ @analytics_client.flush
142
+ end
143
+
100
144
  def fetch
101
145
  return if @configuration_fetcher.nil?
102
146
 
103
- configuration_parser = ConfigurationParser.new(SignatureVerifier.new, @error_reporter, @configuration_fetched_invoker)
147
+ signature_verifier = if @rox_options.self_managed?
148
+ SignatureVerifierMock.new
149
+ else
150
+ SignatureVerifier.new
151
+ end
152
+ configuration_parser = ConfigurationParser.new(signature_verifier, @error_reporter,
153
+ @configuration_fetched_invoker)
104
154
  result = @configuration_fetcher.fetch
105
155
  return if result.nil?
106
156
 
@@ -113,17 +163,22 @@ module Rox
113
163
 
114
164
  has_changes = @last_configurations.nil? || @last_configurations.data != result.data
115
165
  @last_configurations = result
116
- @configuration_fetched_invoker.invoke(FetcherStatus::APPLIED_FROM_NETWORK, configuration.signature_date, has_changes)
166
+ @configuration_fetched_invoker.invoke(FetcherStatus::APPLIED_FROM_NETWORK, configuration.signature_date,
167
+ has_changes)
168
+ end
169
+
170
+ def register_with_namespace(namespace, rox_container)
171
+ @registerer.register_instance(rox_container, namespace)
117
172
  end
118
173
 
119
- def register(ns, rox_container)
120
- @registerer.register_instance(rox_container, ns)
174
+ def register(*args)
175
+ rox_container = args.pop
176
+ namespace = args.length == 1 ? args.pop : ''
177
+ @registerer.register_instance(rox_container, namespace)
121
178
  end
122
179
 
123
180
  def context=(context)
124
- @flag_repository.all_flags.each do |flag|
125
- flag.context = context
126
- end
181
+ @parser.global_context = context
127
182
  end
128
183
 
129
184
  def add_custom_property(property)
@@ -137,7 +192,7 @@ module Rox
137
192
  def wrap_configuration_fetched_handler(&handler)
138
193
  lambda do |args|
139
194
  start_or_stop_push_updated_listener unless args.fetcher_status == FetcherStatus::ERROR_FETCHED_FAILED
140
- handler.call(args) unless handler.nil?
195
+ handler&.call(args)
141
196
  end
142
197
  end
143
198
 
@@ -145,7 +200,7 @@ module Rox
145
200
  if @internal_flags.enabled?('rox.internal.pushUpdates')
146
201
  if @push_updates_listener.nil?
147
202
  @push_updates_listener = NotificationListener.new(Environment.notifications_path, @sdk_settings.api_key)
148
- @push_updates_listener.on 'changed' do |data|
203
+ @push_updates_listener.on 'changed' do |_data|
149
204
  fetch
150
205
  end
151
206
  @push_updates_listener.start
@@ -161,6 +216,19 @@ module Rox
161
216
  def dynamic_api(entities_provider)
162
217
  Rox::Core::DynamicApi.new(@flag_repository, entities_provider)
163
218
  end
219
+
220
+ def dump_state
221
+ @state_sender.dump_state
222
+ end
223
+
224
+ def validate_api_key(api_key)
225
+ valid_api_key_pattern = /^[a-f\d]{24}$/
226
+ if api_key&.strip&.empty?
227
+ raise ArgumentError, 'Blank Rollout api key - must be specified'
228
+ elsif !valid_api_key_pattern.match(api_key)
229
+ raise ArgumentError, 'Illegal Rollout api key'
230
+ end
231
+ end
164
232
  end
165
233
  end
166
- end
234
+ end