splitclient-rb 1.0.2.wip → 8.11.1.pre.rc1-java

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 (210) hide show
  1. checksums.yaml +5 -5
  2. data/.github/CODEOWNERS +1 -0
  3. data/.github/pull_request_template.md +9 -0
  4. data/.github/workflows/ci.yml +90 -0
  5. data/.github/workflows/update-license-year.yml +45 -0
  6. data/.gitignore +20 -0
  7. data/.rubocop.yml +76 -0
  8. data/.simplecov +1 -0
  9. data/CHANGES.txt +361 -2
  10. data/CONTRIBUTORS-GUIDE.md +49 -0
  11. data/Gemfile +2 -0
  12. data/LICENSE +3 -36
  13. data/NOTICE.txt +5 -0
  14. data/README.md +59 -127
  15. data/Rakefile +27 -3
  16. data/ext/murmurhash/MurmurHash3.java +302 -0
  17. data/gemfiles/faraday_after_0.13.gemfile +7 -0
  18. data/gemfiles/faraday_before_0.13.gemfile +8 -0
  19. data/lib/murmurhash/base.rb +58 -0
  20. data/lib/murmurhash/murmurhash.jar +0 -0
  21. data/lib/murmurhash/murmurhash_mri.rb +3 -0
  22. data/lib/splitclient-rb/cache/adapters/cache_adapter.rb +130 -0
  23. data/lib/splitclient-rb/cache/adapters/memory_adapter.rb +12 -0
  24. data/lib/splitclient-rb/cache/adapters/memory_adapters/map_adapter.rb +137 -0
  25. data/lib/splitclient-rb/cache/adapters/memory_adapters/queue_adapter.rb +53 -0
  26. data/lib/splitclient-rb/cache/adapters/redis_adapter.rb +178 -0
  27. data/lib/splitclient-rb/cache/fetchers/segment_fetcher.rb +83 -0
  28. data/lib/splitclient-rb/cache/fetchers/split_fetcher.rb +70 -0
  29. data/lib/splitclient-rb/cache/filter/bloom_filter.rb +67 -0
  30. data/lib/splitclient-rb/cache/filter/filter_adapter.rb +32 -0
  31. data/lib/splitclient-rb/cache/filter/flag_set_filter.rb +40 -0
  32. data/lib/splitclient-rb/cache/hashers/impression_hasher.rb +34 -0
  33. data/lib/splitclient-rb/cache/observers/impression_observer.rb +22 -0
  34. data/lib/splitclient-rb/cache/observers/noop_impression_observer.rb +10 -0
  35. data/lib/splitclient-rb/cache/repositories/events/memory_repository.rb +45 -0
  36. data/lib/splitclient-rb/cache/repositories/events/redis_repository.rb +35 -0
  37. data/lib/splitclient-rb/cache/repositories/events_repository.rb +61 -0
  38. data/lib/splitclient-rb/cache/repositories/flag_sets/memory_repository.rb +40 -0
  39. data/lib/splitclient-rb/cache/repositories/flag_sets/redis_repository.rb +49 -0
  40. data/lib/splitclient-rb/cache/repositories/impressions/memory_repository.rb +56 -0
  41. data/lib/splitclient-rb/cache/repositories/impressions/redis_repository.rb +57 -0
  42. data/lib/splitclient-rb/cache/repositories/impressions_repository.rb +23 -0
  43. data/lib/splitclient-rb/cache/repositories/repository.rb +24 -0
  44. data/lib/splitclient-rb/cache/repositories/rule_based_segments_repository.rb +136 -0
  45. data/lib/splitclient-rb/cache/repositories/segments_repository.rb +124 -0
  46. data/lib/splitclient-rb/cache/repositories/splits_repository.rb +296 -0
  47. data/lib/splitclient-rb/cache/routers/impression_router.rb +60 -0
  48. data/lib/splitclient-rb/cache/senders/events_sender.rb +42 -0
  49. data/lib/splitclient-rb/cache/senders/impressions_adapter/memory_sender.rb +71 -0
  50. data/lib/splitclient-rb/cache/senders/impressions_adapter/redis_sender.rb +69 -0
  51. data/lib/splitclient-rb/cache/senders/impressions_count_sender.rb +43 -0
  52. data/lib/splitclient-rb/cache/senders/impressions_formatter.rb +95 -0
  53. data/lib/splitclient-rb/cache/senders/impressions_sender.rb +52 -0
  54. data/lib/splitclient-rb/cache/senders/impressions_sender_adapter.rb +21 -0
  55. data/lib/splitclient-rb/cache/senders/localhost_repo_cleaner.rb +47 -0
  56. data/lib/splitclient-rb/cache/stores/localhost_split_builder.rb +95 -0
  57. data/lib/splitclient-rb/cache/stores/localhost_split_store.rb +110 -0
  58. data/lib/splitclient-rb/cache/stores/store_utils.rb +13 -0
  59. data/lib/splitclient-rb/clients/split_client.rb +530 -0
  60. data/lib/splitclient-rb/constants.rb +16 -0
  61. data/lib/splitclient-rb/engine/api/client.rb +87 -0
  62. data/lib/splitclient-rb/engine/api/events.rb +57 -0
  63. data/lib/splitclient-rb/engine/api/faraday_middleware/gzip.rb +58 -0
  64. data/lib/splitclient-rb/engine/api/impressions.rb +79 -0
  65. data/lib/splitclient-rb/engine/api/segments.rb +78 -0
  66. data/lib/splitclient-rb/engine/api/splits.rb +139 -0
  67. data/lib/splitclient-rb/engine/api/telemetry_api.rb +47 -0
  68. data/lib/splitclient-rb/engine/auth_api_client.rb +100 -0
  69. data/lib/splitclient-rb/engine/back_off.rb +26 -0
  70. data/lib/splitclient-rb/engine/common/impressions_counter.rb +45 -0
  71. data/lib/splitclient-rb/engine/common/impressions_manager.rb +165 -0
  72. data/lib/splitclient-rb/engine/common/noop_impressions_counter.rb +27 -0
  73. data/lib/{splitclient-engine → splitclient-rb/engine}/evaluator/splitter.rb +35 -22
  74. data/lib/splitclient-rb/engine/events/events_delivery.rb +20 -0
  75. data/lib/splitclient-rb/engine/events/events_manager.rb +194 -0
  76. data/lib/splitclient-rb/engine/events/events_manager_config.rb +96 -0
  77. data/lib/splitclient-rb/engine/events/events_task.rb +50 -0
  78. data/lib/splitclient-rb/engine/events/noop_events_queue.rb +13 -0
  79. data/lib/splitclient-rb/engine/fallback_treatment_calculator.rb +48 -0
  80. data/lib/splitclient-rb/engine/impressions/noop_unique_keys_tracker.rb +17 -0
  81. data/lib/splitclient-rb/engine/impressions/unique_keys_tracker.rb +144 -0
  82. data/lib/{splitclient-engine → splitclient-rb/engine}/matchers/all_keys_matcher.rb +10 -21
  83. data/lib/splitclient-rb/engine/matchers/between_matcher.rb +47 -0
  84. data/lib/splitclient-rb/engine/matchers/between_semver_matcher.rb +33 -0
  85. data/lib/{splitclient-engine → splitclient-rb/engine}/matchers/combiners.rb +4 -6
  86. data/lib/splitclient-rb/engine/matchers/combining_matcher.rb +80 -0
  87. data/lib/splitclient-rb/engine/matchers/contains_all_matcher.rb +20 -0
  88. data/lib/splitclient-rb/engine/matchers/contains_any_matcher.rb +16 -0
  89. data/lib/splitclient-rb/engine/matchers/contains_matcher.rb +43 -0
  90. data/lib/splitclient-rb/engine/matchers/dependency_matcher.rb +26 -0
  91. data/lib/splitclient-rb/engine/matchers/ends_with_matcher.rb +42 -0
  92. data/lib/splitclient-rb/engine/matchers/equal_to_boolean_matcher.rb +37 -0
  93. data/lib/splitclient-rb/engine/matchers/equal_to_matcher.rb +44 -0
  94. data/lib/splitclient-rb/engine/matchers/equal_to_semver_matcher.rb +28 -0
  95. data/lib/splitclient-rb/engine/matchers/equal_to_set_matcher.rb +16 -0
  96. data/lib/splitclient-rb/engine/matchers/greater_than_or_equal_to_matcher.rb +43 -0
  97. data/lib/splitclient-rb/engine/matchers/greater_than_or_equal_to_semver_matcher.rb +28 -0
  98. data/lib/splitclient-rb/engine/matchers/in_list_semver_matcher.rb +36 -0
  99. data/lib/splitclient-rb/engine/matchers/less_than_or_equal_to_matcher.rb +43 -0
  100. data/lib/splitclient-rb/engine/matchers/less_than_or_equal_to_semver_matcher.rb +28 -0
  101. data/lib/splitclient-rb/engine/matchers/matcher.rb +52 -0
  102. data/lib/splitclient-rb/engine/matchers/matches_string_matcher.rb +29 -0
  103. data/lib/splitclient-rb/engine/matchers/negation_matcher.rb +47 -0
  104. data/lib/splitclient-rb/engine/matchers/part_of_set_matcher.rb +22 -0
  105. data/lib/splitclient-rb/engine/matchers/prerequisites_matcher.rb +31 -0
  106. data/lib/splitclient-rb/engine/matchers/rule_based_segment_matcher.rb +78 -0
  107. data/lib/splitclient-rb/engine/matchers/semver.rb +201 -0
  108. data/lib/splitclient-rb/engine/matchers/set_matcher.rb +27 -0
  109. data/lib/splitclient-rb/engine/matchers/starts_with_matcher.rb +40 -0
  110. data/lib/splitclient-rb/engine/matchers/user_defined_segment_matcher.rb +28 -0
  111. data/lib/splitclient-rb/engine/matchers/whitelist_matcher.rb +66 -0
  112. data/lib/splitclient-rb/engine/metrics/binary_search_latency_tracker.rb +66 -0
  113. data/lib/splitclient-rb/engine/models/evaluation_options.rb +9 -0
  114. data/lib/splitclient-rb/engine/models/event_active_subscriptions.rb +14 -0
  115. data/lib/splitclient-rb/engine/models/events_metadata.rb +10 -0
  116. data/lib/splitclient-rb/engine/models/fallback_treatment.rb +11 -0
  117. data/lib/splitclient-rb/engine/models/fallback_treatments_configuration.rb +36 -0
  118. data/lib/splitclient-rb/engine/models/label.rb +10 -0
  119. data/lib/splitclient-rb/engine/models/sdk_event.rb +4 -0
  120. data/lib/splitclient-rb/engine/models/sdk_event_type.rb +4 -0
  121. data/lib/splitclient-rb/engine/models/sdk_internal_event.rb +8 -0
  122. data/lib/splitclient-rb/engine/models/sdk_internal_event_notification.rb +16 -0
  123. data/lib/splitclient-rb/engine/models/segment_type.rb +4 -0
  124. data/lib/splitclient-rb/engine/models/split.rb +17 -0
  125. data/lib/splitclient-rb/engine/models/split_http_response.rb +19 -0
  126. data/lib/splitclient-rb/engine/models/treatment.rb +3 -0
  127. data/lib/splitclient-rb/engine/models/valid_sdk_event.rb +14 -0
  128. data/lib/splitclient-rb/engine/parser/condition.rb +271 -0
  129. data/lib/splitclient-rb/engine/parser/evaluator.rb +112 -0
  130. data/lib/{splitclient-engine → splitclient-rb/engine}/parser/partition.rb +2 -4
  131. data/lib/splitclient-rb/engine/push_manager.rb +66 -0
  132. data/lib/splitclient-rb/engine/status_manager.rb +39 -0
  133. data/lib/splitclient-rb/engine/sync_manager.rb +183 -0
  134. data/lib/splitclient-rb/engine/synchronizer.rb +231 -0
  135. data/lib/splitclient-rb/exceptions.rb +26 -0
  136. data/lib/splitclient-rb/helpers/decryption_helper.rb +25 -0
  137. data/lib/splitclient-rb/helpers/evaluator_helper.rb +37 -0
  138. data/lib/splitclient-rb/helpers/repository_helper.rb +61 -0
  139. data/lib/splitclient-rb/helpers/thread_helper.rb +24 -0
  140. data/lib/splitclient-rb/helpers/util.rb +26 -0
  141. data/lib/splitclient-rb/managers/split_manager.rb +121 -0
  142. data/lib/splitclient-rb/spec.rb +9 -0
  143. data/lib/splitclient-rb/split_config.rb +591 -47
  144. data/lib/splitclient-rb/split_factory.rb +284 -0
  145. data/lib/splitclient-rb/split_factory_builder.rb +7 -0
  146. data/lib/splitclient-rb/split_factory_registry.rb +63 -0
  147. data/lib/splitclient-rb/split_logger.rb +23 -0
  148. data/lib/splitclient-rb/sse/event_source/client.rb +264 -0
  149. data/lib/splitclient-rb/sse/event_source/event_parser.rb +65 -0
  150. data/lib/splitclient-rb/sse/event_source/event_types.rb +15 -0
  151. data/lib/splitclient-rb/sse/event_source/stream_data.rb +22 -0
  152. data/lib/splitclient-rb/sse/notification_manager_keeper.rb +84 -0
  153. data/lib/splitclient-rb/sse/notification_processor.rb +48 -0
  154. data/lib/splitclient-rb/sse/sse_handler.rb +44 -0
  155. data/lib/splitclient-rb/sse/workers/segments_worker.rb +62 -0
  156. data/lib/splitclient-rb/sse/workers/splits_worker.rb +153 -0
  157. data/lib/splitclient-rb/telemetry/domain/constants.rb +48 -0
  158. data/lib/splitclient-rb/telemetry/domain/structs.rb +35 -0
  159. data/lib/splitclient-rb/telemetry/evaluation_consumer.rb +14 -0
  160. data/lib/splitclient-rb/telemetry/evaluation_producer.rb +21 -0
  161. data/lib/splitclient-rb/telemetry/init_consumer.rb +14 -0
  162. data/lib/splitclient-rb/telemetry/init_producer.rb +19 -0
  163. data/lib/splitclient-rb/telemetry/memory/memory_evaluation_consumer.rb +32 -0
  164. data/lib/splitclient-rb/telemetry/memory/memory_evaluation_producer.rb +24 -0
  165. data/lib/splitclient-rb/telemetry/memory/memory_init_consumer.rb +28 -0
  166. data/lib/splitclient-rb/telemetry/memory/memory_init_producer.rb +34 -0
  167. data/lib/splitclient-rb/telemetry/memory/memory_runtime_consumer.rb +119 -0
  168. data/lib/splitclient-rb/telemetry/memory/memory_runtime_producer.rb +87 -0
  169. data/lib/splitclient-rb/telemetry/memory/memory_synchronizer.rb +213 -0
  170. data/lib/splitclient-rb/telemetry/redis/redis_evaluation_producer.rb +38 -0
  171. data/lib/splitclient-rb/telemetry/redis/redis_init_producer.rb +37 -0
  172. data/lib/splitclient-rb/telemetry/redis/redis_synchronizer.rb +27 -0
  173. data/lib/splitclient-rb/telemetry/runtime_consumer.rb +25 -0
  174. data/lib/splitclient-rb/telemetry/runtime_producer.rb +25 -0
  175. data/lib/splitclient-rb/telemetry/storages/memory.rb +159 -0
  176. data/lib/splitclient-rb/telemetry/sync_task.rb +36 -0
  177. data/lib/splitclient-rb/telemetry/synchronizer.rb +33 -0
  178. data/lib/splitclient-rb/utilitites.rb +49 -0
  179. data/lib/splitclient-rb/validators.rb +361 -0
  180. data/lib/splitclient-rb/version.rb +1 -1
  181. data/lib/splitclient-rb.rb +177 -25
  182. data/sonar-project.properties +6 -0
  183. data/splitclient-rb.gemspec +53 -26
  184. data/tasks/benchmark_get_treatment.rake +31 -17
  185. data/tasks/irb.rake +6 -0
  186. metadata +411 -86
  187. data/NEWS +0 -9
  188. data/lib/splitclient-cache/local_store.rb +0 -45
  189. data/lib/splitclient-engine/impressions/impressions.rb +0 -79
  190. data/lib/splitclient-engine/matchers/between_matcher.rb +0 -48
  191. data/lib/splitclient-engine/matchers/combining_matcher.rb +0 -94
  192. data/lib/splitclient-engine/matchers/equal_to_matcher.rb +0 -48
  193. data/lib/splitclient-engine/matchers/greater_than_or_equal_to_matcher.rb +0 -48
  194. data/lib/splitclient-engine/matchers/less_than_or_equal_to_matcher.rb +0 -48
  195. data/lib/splitclient-engine/matchers/negation_matcher.rb +0 -54
  196. data/lib/splitclient-engine/matchers/user_defined_segment_matcher.rb +0 -61
  197. data/lib/splitclient-engine/matchers/whitelist_matcher.rb +0 -64
  198. data/lib/splitclient-engine/metrics/binary_search_latency_tracker.rb +0 -122
  199. data/lib/splitclient-engine/metrics/metrics.rb +0 -158
  200. data/lib/splitclient-engine/parser/condition.rb +0 -143
  201. data/lib/splitclient-engine/parser/segment.rb +0 -84
  202. data/lib/splitclient-engine/parser/segment_parser.rb +0 -46
  203. data/lib/splitclient-engine/parser/split.rb +0 -68
  204. data/lib/splitclient-engine/parser/split_adapter.rb +0 -433
  205. data/lib/splitclient-engine/parser/split_parser.rb +0 -139
  206. data/lib/splitclient-engine/partitions/treatments.rb +0 -40
  207. data/lib/splitclient-rb/split_client.rb +0 -156
  208. data/lib/splitclient-rb_utilitites.rb +0 -26
  209. data/tasks/console.rake +0 -4
  210. data/tasks/rspec.rake +0 -3
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'faraday'
4
+ require 'stringio'
5
+
6
+ module SplitIoClient
7
+ module FaradayMiddleware
8
+ class Gzip < Faraday::Middleware
9
+ ACCEPT_ENCODING = 'Accept-Encoding'
10
+ CONTENT_ENCODING = 'Content-Encoding'
11
+ CONTENT_LENGTH = 'Content-Length'
12
+ SUPPORTED_ENCODINGS = 'gzip,deflate'
13
+ RUBY_ENCODING = '1.9'.respond_to?(:force_encoding)
14
+
15
+ def call(env)
16
+ env[:request_headers][ACCEPT_ENCODING] ||= SUPPORTED_ENCODINGS
17
+ @app.call(env).on_complete do |response_env|
18
+ case response_env[:response_headers][CONTENT_ENCODING]
19
+ when 'gzip'
20
+ reset_body(response_env, &method(:uncompress_gzip))
21
+ when 'deflate'
22
+ reset_body(response_env, &method(:inflate))
23
+ end
24
+ end
25
+ end
26
+
27
+ def reset_body(env)
28
+ env[:body] = yield(env[:body])
29
+ env[:response_headers].delete(CONTENT_ENCODING)
30
+ env[:response_headers][CONTENT_LENGTH] = env[:body].length
31
+ end
32
+
33
+ def uncompress_gzip(body)
34
+ io = StringIO.new(body)
35
+ gzip_reader = if RUBY_ENCODING
36
+ Zlib::GzipReader.new(io, encoding: 'ASCII-8BIT')
37
+ else
38
+ Zlib::GzipReader.new(io)
39
+ end
40
+ gzip_reader.read
41
+ end
42
+
43
+ def inflate(body)
44
+ # Inflate as a DEFLATE (RFC 1950+RFC 1951) stream
45
+ Zlib::Inflate.inflate(body)
46
+ rescue Zlib::DataError
47
+ # Fall back to inflating as a "raw" deflate stream which
48
+ # Microsoft servers return
49
+ inflate = Zlib::Inflate.new(-Zlib::MAX_WBITS)
50
+ begin
51
+ inflate.inflate(body)
52
+ ensure
53
+ inflate.close
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SplitIoClient
4
+ module Api
5
+ class Impressions < Client
6
+ def initialize(api_key, config, telemetry_runtime_producer)
7
+ super(config)
8
+ @api_key = api_key
9
+ @telemetry_runtime_producer = telemetry_runtime_producer
10
+ end
11
+
12
+ def post(impressions)
13
+ if impressions.empty?
14
+ @config.split_logger.log_if_debug('No impressions to report')
15
+ return
16
+ end
17
+
18
+ start = Time.now
19
+
20
+ response = post_api("#{@config.events_uri}/testImpressions/bulk", @api_key, impressions, impressions_headers)
21
+
22
+ if response.success?
23
+ @config.split_logger.log_if_debug("Impressions reported: #{total_impressions(impressions)}")
24
+
25
+ bucket = BinarySearchLatencyTracker.get_bucket((Time.now - start) * 1000.0)
26
+ @telemetry_runtime_producer.record_sync_latency(Telemetry::Domain::Constants::IMPRESSIONS_SYNC, bucket)
27
+ @telemetry_runtime_producer.record_successful_sync(Telemetry::Domain::Constants::IMPRESSIONS_SYNC, (Time.now.to_f * 1000.0).to_i)
28
+ else
29
+ @telemetry_runtime_producer.record_sync_error(Telemetry::Domain::Constants::IMPRESSIONS_SYNC, response.status)
30
+
31
+ @config.logger.error("Unexpected status code while posting impressions: #{response.status}." \
32
+ ' - Check your API key and base URI')
33
+ raise 'Split SDK failed to connect to backend to post impressions'
34
+ end
35
+ end
36
+
37
+ def post_count(impressions_count)
38
+ if impressions_count.nil? || impressions_count[:pf].empty?
39
+ @config.split_logger.log_if_debug('No impressions count to send')
40
+ return
41
+ end
42
+
43
+ start = Time.now
44
+
45
+ response = post_api("#{@config.events_uri}/testImpressions/count", @api_key, impressions_count)
46
+
47
+ if response.success?
48
+ @config.split_logger.log_if_debug("Impressions count sent: #{impressions_count[:pf].length}")
49
+
50
+ bucket = BinarySearchLatencyTracker.get_bucket((Time.now - start) * 1000.0)
51
+ @telemetry_runtime_producer.record_sync_latency(Telemetry::Domain::Constants::IMPRESSION_COUNT_SYNC, bucket)
52
+ @telemetry_runtime_producer.record_successful_sync(Telemetry::Domain::Constants::IMPRESSION_COUNT_SYNC, (Time.now.to_f * 1000.0).to_i)
53
+ else
54
+ @telemetry_runtime_producer.record_sync_error(Telemetry::Domain::Constants::IMPRESSION_COUNT_SYNC, response.status)
55
+
56
+ @config.logger.error("Unexpected status code while posting impressions count: #{response.status}." \
57
+ ' - Check your API key and base URI')
58
+ raise 'Split SDK failed to connect to backend to post impressions'
59
+ end
60
+ end
61
+
62
+ def total_impressions(impressions)
63
+ return 0 if impressions.nil?
64
+
65
+ impressions.reduce(0) do |impressions_count, impression|
66
+ impressions_count += impression[:i].length
67
+ end
68
+ end
69
+
70
+ private
71
+
72
+ def impressions_headers
73
+ {
74
+ 'SplitSDKImpressionsMode' => @config.impressions_mode.to_s
75
+ }
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SplitIoClient
4
+ module Api
5
+ # Retrieves segment changes from the Split Backend
6
+ class Segments < Client
7
+ def initialize(api_key, segments_repository, config, telemetry_runtime_producer)
8
+ super(config)
9
+ @api_key = api_key
10
+ @segments_repository = segments_repository
11
+ @telemetry_runtime_producer = telemetry_runtime_producer
12
+ end
13
+
14
+ def fetch_segments_by_names(names, fetch_options = { cache_control_headers: false, till: nil })
15
+ return if names.nil? || names.empty?
16
+
17
+ names.each do |name|
18
+ since = @segments_repository.get_change_number(name)
19
+
20
+ loop do
21
+ segment = fetch_segment_changes(name, since, fetch_options)
22
+ @segments_repository.add_to_segment(segment)
23
+
24
+ @config.split_logger.log_if_debug("Segment #{name} fetched before: #{since}, \
25
+ till: #{@segments_repository.get_change_number(name)}")
26
+
27
+ break if since.to_i >= @segments_repository.get_change_number(name).to_i
28
+
29
+ since = @segments_repository.get_change_number(name)
30
+ end
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ def fetch_segment_changes(name, since, fetch_options = { cache_control_headers: false, till: nil })
37
+ start = Time.now
38
+
39
+ params = { since: since }
40
+ params[:till] = fetch_options[:till] unless fetch_options[:till].nil?
41
+ response = get_api("#{@config.base_uri}/segmentChanges/#{name}", @api_key, params, fetch_options[:cache_control_headers])
42
+
43
+ if response.success?
44
+ segment = JSON.parse(response.body, symbolize_names: true)
45
+ @segments_repository.set_change_number(name, segment[:till])
46
+
47
+ @config.split_logger.log_if_debug("\'#{segment[:name]}\' segment retrieved.")
48
+ unless segment[:added].empty?
49
+ @config.split_logger.log_if_debug("\'#{segment[:name]}\' #{segment[:added].size} added keys")
50
+ end
51
+ unless segment[:removed].empty?
52
+ @config.split_logger.log_if_debug("\'#{segment[:name]}\' #{segment[:removed].size} removed keys")
53
+ end
54
+ @config.split_logger.log_if_transport("Segment changes response: #{segment.to_s}")
55
+
56
+ bucket = BinarySearchLatencyTracker.get_bucket((Time.now - start) * 1000.0)
57
+ @telemetry_runtime_producer.record_sync_latency(Telemetry::Domain::Constants::SEGMENT_SYNC, bucket)
58
+ @telemetry_runtime_producer.record_successful_sync(Telemetry::Domain::Constants::SEGMENT_SYNC, (Time.now.to_f * 1000.0).to_i)
59
+
60
+ segment
61
+ elsif response.status == 403
62
+ @telemetry_runtime_producer.record_sync_error(Telemetry::Domain::Constants::SEGMENT_SYNC, response.status)
63
+
64
+ @config.logger.error('Factory Instantiation: You passed a browser type api_key, ' \
65
+ 'please grab an api key from the Split console that is of type sdk')
66
+ @config.valid_mode = false
67
+ else
68
+ @telemetry_runtime_producer.record_sync_error(Telemetry::Domain::Constants::SEGMENT_SYNC, response.status)
69
+
70
+ @config.logger.error("Unexpected status code while fetching segments: #{response.status}." \
71
+ "Since #{since} - Check your API key and base URI")
72
+
73
+ raise 'Split SDK failed to connect to backend to fetch segments'
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,139 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SplitIoClient
4
+ module Api
5
+ # Retrieves split definitions from the Split Backend
6
+ class Splits < Client
7
+
8
+ PROXY_CHECK_INTERVAL_SECONDS = 24 * 60 * 60
9
+ SPEC_1_1 = "1.1"
10
+
11
+ def initialize(api_key, config, telemetry_runtime_producer)
12
+ super(config)
13
+ @api_key = api_key
14
+ @telemetry_runtime_producer = telemetry_runtime_producer
15
+ @flag_sets_filter = @config.flag_sets_filter
16
+ @spec_version = SplitIoClient::Spec::FeatureFlags::SPEC_VERSION
17
+ @last_proxy_check_timestamp = 0
18
+ @clear_storage = false
19
+ @old_spec_since = nil
20
+ end
21
+
22
+ def since(since, since_rbs, fetch_options = { cache_control_headers: false, till: nil, sets: nil})
23
+ start = Time.now
24
+
25
+ if check_last_proxy_check_timestamp
26
+ @spec_version = SplitIoClient::Spec::FeatureFlags::SPEC_VERSION
27
+ @config.logger.debug("Switching to new Feature flag spec #{@spec_version} and fetching.") if @config.debug_enabled
28
+ @old_spec_since = since
29
+ since = -1
30
+ since_rbs = -1
31
+ fetch_options = { cache_control_headers: false, till: nil, sets: nil}
32
+ end
33
+
34
+ if @spec_version == Splits::SPEC_1_1
35
+ since = @old_spec_since unless @old_spec_since.nil?
36
+ params = { s: @spec_version, since: since }
37
+ @old_spec_since = nil
38
+ else
39
+ params = { s: @spec_version, since: since, rbSince: since_rbs }
40
+ end
41
+
42
+ params[:sets] = @flag_sets_filter.join(",") unless @flag_sets_filter.empty?
43
+ params[:till] = fetch_options[:till] unless fetch_options[:till].nil?
44
+ @config.logger.debug("Fetching from splitChanges with #{params}: ") if @config.debug_enabled
45
+ response = get_api("#{@config.base_uri}/splitChanges", @api_key, params, fetch_options[:cache_control_headers])
46
+ if response.status == 414
47
+ @config.logger.error("Error fetching feature flags; the amount of flag sets provided are too big, causing uri length error.")
48
+ raise ApiException.new response.body, 414
49
+ end
50
+
51
+ if response.status == 400 and sdk_url_overriden? and @spec_version == SplitIoClient::Spec::FeatureFlags::SPEC_VERSION
52
+ @config.logger.warn("Detected proxy response error, changing spec version from #{@spec_version} to #{Splits::SPEC_1_1} and re-fetching.")
53
+ @spec_version = Splits::SPEC_1_1
54
+ @last_proxy_check_timestamp = Time.now
55
+ return since(since, 0, fetch_options = {cache_control_headers: fetch_options[:cache_control_headers], till: fetch_options[:till],
56
+ sets: fetch_options[:sets]})
57
+ end
58
+ if response.success?
59
+ result = JSON.parse(response.body, symbolize_names: true)
60
+
61
+ return process_result(result, since, since_rbs, start)
62
+ end
63
+ @telemetry_runtime_producer.record_sync_error(Telemetry::Domain::Constants::SPLIT_SYNC, response.status)
64
+
65
+ @config.logger.error("Unexpected status code while fetching feature flags: #{response.status}. " \
66
+ 'Check your API key and base URI')
67
+
68
+ raise 'Split SDK failed to connect to backend to fetch feature flags definitions'
69
+ end
70
+
71
+ def clear_storage
72
+ @clear_storage
73
+ end
74
+
75
+ private
76
+
77
+ def process_result(result, since, since_rbs, start)
78
+ if @spec_version == Splits::SPEC_1_1
79
+ result = convert_to_new_spec(result)
80
+ end
81
+
82
+ result[:rbs][:d] = check_rbs_data(result[:rbs][:d])
83
+ result = objects_with_segment_names(result)
84
+
85
+ if @spec_version == SplitIoClient::Spec::FeatureFlags::SPEC_VERSION
86
+ @clear_storage = @last_proxy_check_timestamp != 0
87
+ @last_proxy_check_timestamp = 0
88
+ end
89
+
90
+ unless result[:ff][:d].empty?
91
+ @config.split_logger.log_if_debug("#{result[:ff][:d].length} feature flags retrieved. since=#{since}")
92
+ end
93
+ @config.split_logger.log_if_transport("Feature flag changes response: #{result[:ff].to_s}")
94
+
95
+ unless result[:rbs][:d].empty?
96
+ @config.split_logger.log_if_debug("#{result[:rbs][:d].length} rule based segments retrieved. since=#{since_rbs}")
97
+ end
98
+ @config.split_logger.log_if_transport("rule based segments changes response: #{result[:rbs].to_s}")
99
+
100
+ bucket = BinarySearchLatencyTracker.get_bucket((Time.now - start) * 1000.0)
101
+ @telemetry_runtime_producer.record_sync_latency(Telemetry::Domain::Constants::SPLIT_SYNC, bucket)
102
+ @telemetry_runtime_producer.record_successful_sync(Telemetry::Domain::Constants::SPLIT_SYNC, (Time.now.to_f * 1000.0).to_i)
103
+
104
+ result
105
+ end
106
+
107
+ def check_rbs_data(rbs_data)
108
+ rbs_data.each do |rb_segment|
109
+ rb_segment[:excluded] = {:keys => [], :segments => []} if rb_segment[:excluded].nil?
110
+ rb_segment[:excluded][:keys] = [] if rb_segment[:excluded][:keys].nil?
111
+ rb_segment[:excluded][:segments] = [] if rb_segment[:excluded][:segments].nil?
112
+ end
113
+ rbs_data
114
+ end
115
+
116
+ def objects_with_segment_names(parsed_objects)
117
+ parsed_objects[:segment_names] = Set.new
118
+ parsed_objects[:segment_names] =
119
+ parsed_objects[:ff][:d].each_with_object(Set.new) do |split, splits|
120
+ splits << Helpers::Util.segment_names_by_object(split, "IN_SEGMENT")
121
+ end.flatten
122
+
123
+ parsed_objects[:rbs][:d].each do |rule_based_segment|
124
+ parsed_objects[:segment_names].merge Helpers::Util.segment_names_in_rb_segment(rule_based_segment, "IN_SEGMENT")
125
+ end
126
+
127
+ parsed_objects
128
+ end
129
+
130
+ def check_last_proxy_check_timestamp
131
+ @spec_version == Splits::SPEC_1_1 and ((Time.now - @last_proxy_check_timestamp) >= Splits::PROXY_CHECK_INTERVAL_SECONDS)
132
+ end
133
+
134
+ def convert_to_new_spec(body)
135
+ {:ff => {:d => body[:splits], :s => body[:since], :t => body[:till]}, :rbs => {:d => [], :s => -1, :t => -1}}
136
+ end
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SplitIoClient
4
+ module Api
5
+ class TelemetryApi < Client
6
+ def initialize(config, api_key, telemetry_runtime_producer)
7
+ super(config)
8
+ @api_key = api_key
9
+ @telemetry_runtime_producer = telemetry_runtime_producer
10
+ end
11
+
12
+ def record_init(config_init)
13
+ post_telemetry("#{@config.telemetry_service_url}/metrics/config", config_init, 'init')
14
+ end
15
+
16
+ def record_stats(stats)
17
+ post_telemetry("#{@config.telemetry_service_url}/metrics/usage", stats, 'stats')
18
+ end
19
+
20
+ def record_unique_keys(uniques)
21
+ return if uniques[:keys].empty?
22
+
23
+ post_telemetry("#{@config.telemetry_service_url}/keys/ss", uniques, 'unique_keys')
24
+ rescue StandardError => e
25
+ @config.log_found_exception(__method__.to_s, e)
26
+ end
27
+
28
+ private
29
+
30
+ def post_telemetry(url, obj, method)
31
+ start = Time.now
32
+ response = post_api(url, @api_key, obj)
33
+
34
+ if response.success?
35
+ @config.split_logger.log_if_debug("Telemetry post succeeded: record #{method}.")
36
+
37
+ bucket = BinarySearchLatencyTracker.get_bucket((Time.now - start) * 1000.0)
38
+ @telemetry_runtime_producer.record_sync_latency(Telemetry::Domain::Constants::TELEMETRY_SYNC, bucket)
39
+ @telemetry_runtime_producer.record_successful_sync(Telemetry::Domain::Constants::TELEMETRY_SYNC, (Time.now.to_f * 1000.0).to_i)
40
+ else
41
+ @telemetry_runtime_producer.record_sync_error(Telemetry::Domain::Constants::TELEMETRY_SYNC, response.status)
42
+ @config.logger.error("Unexpected status code while posting telemetry #{method}: #{response.status}.")
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'jwt'
4
+ require 'cgi'
5
+
6
+ module SplitIoClient
7
+ module Engine
8
+ class AuthApiClient
9
+ def initialize(config, telemetry_runtime_producer)
10
+ @config = config
11
+ @api_client = SplitIoClient::Api::Client.new(@config)
12
+ @telemetry_runtime_producer = telemetry_runtime_producer
13
+ end
14
+
15
+ def authenticate(api_key)
16
+ start = Time.now
17
+ response = @api_client.get_api("#{@config.auth_service_url}?s=#{SplitIoClient::Spec::FeatureFlags::SPEC_VERSION}", api_key)
18
+
19
+ return process_success(response, start) if response.success?
20
+
21
+ return process_error(response) if response.status >= 400 && response.status < 500
22
+
23
+ @telemetry_runtime_producer.record_sync_error(Telemetry::Domain::Constants::TOKEN_SYNC, response.status.to_i)
24
+ if @config.debug_enabled
25
+ @config.logger.debug("Error connecting to: #{@config.auth_service_url}. Response status: #{response.status}")
26
+ end
27
+ { push_enabled: false, retry: true }
28
+ rescue StandardError => e
29
+ @config.logger.debug("AuthApiClient error: #{e.inspect}.") if @config.debug_enabled
30
+ { push_enabled: false, retry: false }
31
+ end
32
+
33
+ private
34
+
35
+ def expiration(token_decoded)
36
+ exp = token_decoded[0]['exp']
37
+ issued_at = token_decoded[0]['iat']
38
+
39
+ exp - issued_at - SplitIoClient::Constants::EXPIRATION_RATE
40
+ end
41
+
42
+ def channels(token_decoded)
43
+ capability = token_decoded[0]['x-ably-capability']
44
+ channels_hash = JSON.parse(capability)
45
+ channels_string = channels_hash.keys.join(',')
46
+ channels_string = control_channels(channels_string)
47
+ @config.logger.debug("Channels #{channels_string}") if @config.debug_enabled
48
+ CGI.escape(channels_string)
49
+ end
50
+
51
+ def decode_token(token)
52
+ JWT.decode token, nil, false
53
+ end
54
+
55
+ def process_error(response)
56
+ if @config.debug_enabled
57
+ @config.logger.debug("Error connecting to: #{@config.auth_service_url}. Response status: #{response.status}")
58
+ end
59
+ @telemetry_runtime_producer.record_auth_rejections if response.status == 401
60
+
61
+ { push_enabled: false, retry: false }
62
+ end
63
+
64
+ def process_success(response, start)
65
+ @config.logger.debug("Success connection to: #{@config.auth_service_url}") if @config.debug_enabled
66
+ record_telemetry(start)
67
+
68
+ body_json = JSON.parse(response.body, symbolize_names: true)
69
+ push_enabled = body_json[:pushEnabled]
70
+ token = body_json[:token]
71
+
72
+ if push_enabled
73
+ decoded_token = decode_token(token)
74
+ channels = channels(decoded_token)
75
+ exp = expiration(decoded_token)
76
+
77
+ @telemetry_runtime_producer.record_token_refreshes
78
+ end
79
+
80
+ { push_enabled: push_enabled, token: token, channels: channels, exp: exp, retry: true }
81
+ end
82
+
83
+ def control_channels(channels_string)
84
+ prefix = SplitIoClient::Constants::OCCUPANCY_CHANNEL_PREFIX
85
+ control_pri = SplitIoClient::Constants::CONTROL_PRI
86
+ control_sec = SplitIoClient::Constants::CONTROL_SEC
87
+ channels_string = channels_string.gsub(control_pri, "#{prefix}#{control_pri}")
88
+
89
+ channels_string.gsub(control_sec, "#{prefix}#{control_sec}")
90
+ end
91
+
92
+ def record_telemetry(start)
93
+ bucket = BinarySearchLatencyTracker.get_bucket((Time.now - start) * 1000.0)
94
+ @telemetry_runtime_producer.record_sync_latency(Telemetry::Domain::Constants::TOKEN_SYNC, bucket)
95
+ timestamp = (Time.now.to_f * 1000.0).to_i
96
+ @telemetry_runtime_producer.record_successful_sync(Telemetry::Domain::Constants::TOKEN_SYNC, timestamp)
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: false
2
+
3
+ module SplitIoClient
4
+ module Engine
5
+ BACKOFF_MAX_ALLOWED = 1800
6
+ class BackOff
7
+ def initialize(back_off_base, attempt = 0, max_allowed = BACKOFF_MAX_ALLOWED)
8
+ @attempt = attempt
9
+ @back_off_base = back_off_base
10
+ @max_allowed = max_allowed
11
+ end
12
+
13
+ def interval
14
+ interval = 0
15
+ interval = (@back_off_base * (2**@attempt)) if @attempt.positive?
16
+ @attempt += 1
17
+
18
+ interval >= @max_allowed ? @max_allowed : interval
19
+ end
20
+
21
+ def reset
22
+ @attempt = 0
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'concurrent'
4
+
5
+ module SplitIoClient
6
+ module Engine
7
+ module Common
8
+ TIME_INTERVAL_MS = 3600 * 1000
9
+
10
+ class ImpressionCounter
11
+ DEFAULT_AMOUNT = 1
12
+
13
+ def initialize
14
+ @cache = Concurrent::Hash.new
15
+ end
16
+
17
+ def inc(split_name, time_frame)
18
+ key = make_key(split_name, time_frame)
19
+
20
+ current_amount = @cache[key]
21
+ @cache[key] = current_amount.nil? ? DEFAULT_AMOUNT : (current_amount + DEFAULT_AMOUNT)
22
+ end
23
+
24
+ def pop_all
25
+ to_return = Concurrent::Hash.new
26
+
27
+ @cache.each do |key, value|
28
+ to_return[key] = value
29
+ end
30
+ @cache.clear
31
+
32
+ to_return
33
+ end
34
+
35
+ def make_key(split_name, time_frame)
36
+ "#{split_name}::#{ImpressionCounter.truncate_time_frame(time_frame)}"
37
+ end
38
+
39
+ def self.truncate_time_frame(timestamp_ms)
40
+ timestamp_ms - (timestamp_ms % TIME_INTERVAL_MS)
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end