splitclient-rb 7.0.4.pre.rc3-java → 7.1.0-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (32) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +10 -3
  3. data/CHANGES.txt +3 -0
  4. data/lib/splitclient-rb/cache/fetchers/segment_fetcher.rb +17 -6
  5. data/lib/splitclient-rb/cache/fetchers/split_fetcher.rb +21 -11
  6. data/lib/splitclient-rb/cache/repositories/splits_repository.rb +1 -1
  7. data/lib/splitclient-rb/clients/split_client.rb +4 -7
  8. data/lib/splitclient-rb/constants.rb +10 -0
  9. data/lib/splitclient-rb/engine/api/splits.rb +0 -1
  10. data/lib/splitclient-rb/engine/auth_api_client.rb +78 -0
  11. data/lib/splitclient-rb/engine/push_manager.rb +53 -0
  12. data/lib/splitclient-rb/engine/sync_manager.rb +123 -0
  13. data/lib/splitclient-rb/engine/synchronizer.rb +90 -0
  14. data/lib/splitclient-rb/exceptions.rb +8 -0
  15. data/lib/splitclient-rb/helpers/thread_helper.rb +25 -0
  16. data/lib/splitclient-rb/split_config.rb +42 -4
  17. data/lib/splitclient-rb/split_factory.rb +25 -15
  18. data/lib/splitclient-rb/sse/event_source/back_off.rb +25 -0
  19. data/lib/splitclient-rb/sse/event_source/client.rb +85 -60
  20. data/lib/splitclient-rb/sse/event_source/stream_data.rb +22 -0
  21. data/lib/splitclient-rb/sse/notification_manager_keeper.rb +71 -0
  22. data/lib/splitclient-rb/sse/notification_processor.rb +50 -0
  23. data/lib/splitclient-rb/sse/sse_handler.rb +47 -52
  24. data/lib/splitclient-rb/sse/workers/segments_worker.rb +21 -6
  25. data/lib/splitclient-rb/sse/workers/splits_worker.rb +24 -6
  26. data/lib/splitclient-rb/version.rb +1 -1
  27. data/lib/splitclient-rb.rb +10 -3
  28. data/splitclient-rb.gemspec +1 -0
  29. metadata +28 -7
  30. data/lib/splitclient-rb/engine/parser/split_adapter.rb +0 -105
  31. data/lib/splitclient-rb/sse/event_source/status.rb +0 -13
  32. data/lib/splitclient-rb/sse/workers/control_worker.rb +0 -34
@@ -102,7 +102,11 @@ module SplitIoClient
102
102
  @split_validator = SplitIoClient::Validators.new(self)
103
103
  @localhost_mode = opts[:localhost_mode]
104
104
 
105
- @push_notification_enabled = opts[:push_notification_enabled].nil? ? true : false
105
+ @streaming_enabled = consumer? ? false : (opts[:streaming_enabled].nil? ? SplitConfig.default_streaming_enabled : opts[:streaming_enabled])
106
+ @streaming_service_url = opts[:streaming_service_url] || SplitConfig.default_streaming_service_url
107
+ @auth_service_url = opts[:auth_service_url] || SplitConfig.default_auth_service_url
108
+ @auth_retry_back_off_base = SplitConfig.init_auth_retry_back_off(opts[:auth_retry_back_off_base] || SplitConfig.default_auth_retry_back_off_base)
109
+ @streaming_reconnect_back_off_base = SplitConfig.init_streaming_reconnect_back_off(opts[:streaming_reconnect_back_off_base] || SplitConfig.default_streaming_reconnect_back_off_base)
106
110
 
107
111
  startup_log
108
112
  end
@@ -256,7 +260,43 @@ module SplitIoClient
256
260
 
257
261
  attr_accessor :ip_addresses_enabled
258
262
 
259
- attr_accessor :push_notification_enabled
263
+ attr_accessor :auth_service_url
264
+
265
+ attr_accessor :auth_retry_back_off_base
266
+
267
+ attr_accessor :streaming_service_url
268
+
269
+ attr_accessor :streaming_reconnect_back_off_base
270
+
271
+ attr_accessor :streaming_enabled
272
+
273
+ def self.default_streaming_enabled
274
+ false
275
+ end
276
+
277
+ def self.default_streaming_service_url
278
+ 'https://split-realtime.ably.io/event-stream'
279
+ end
280
+
281
+ def self.default_auth_service_url
282
+ 'https://auth.split.io/api/auth'
283
+ end
284
+
285
+ def self.default_auth_retry_back_off_base
286
+ 1
287
+ end
288
+
289
+ def self.default_streaming_reconnect_back_off_base
290
+ 1
291
+ end
292
+
293
+ def self.init_auth_retry_back_off(auth_retry_back_off)
294
+ auth_retry_back_off < 1 ? SplitConfig.default_auth_retry_back_off_base : auth_retry_back_off
295
+ end
296
+
297
+ def self.init_streaming_reconnect_back_off(streaming_reconnect_back_off)
298
+ streaming_reconnect_back_off < 1 ? SplitConfig.default_streaming_reconnect_back_off_base : streaming_reconnect_back_off
299
+ end
260
300
 
261
301
  #
262
302
  # The default split client configuration
@@ -385,8 +425,6 @@ module SplitIoClient
385
425
  end
386
426
  end
387
427
 
388
-
389
-
390
428
  #
391
429
  # The default debug value
392
430
  #
@@ -6,6 +6,7 @@ module SplitIoClient
6
6
 
7
7
  include SplitIoClient::Cache::Repositories
8
8
  include SplitIoClient::Cache::Stores
9
+ include SplitIoClient::Cache::Senders
9
10
 
10
11
  attr_reader :adapter, :client, :manager, :config
11
12
 
@@ -29,16 +30,14 @@ module SplitIoClient
29
30
  @splits_repository = SplitsRepository.new(@config)
30
31
  @segments_repository = SegmentsRepository.new(@config)
31
32
  @impressions_repository = ImpressionsRepository.new(@config)
32
- @metrics_repository = MetricsRepository.new(@config)
33
33
  @events_repository = EventsRepository.new(@config, @api_key)
34
-
34
+ @metrics_repository = MetricsRepository.new(@config)
35
35
  @sdk_blocker = SDKBlocker.new(@splits_repository, @segments_repository, @config)
36
+ @metrics = Metrics.new(100, @metrics_repository)
36
37
 
37
- @adapter = start!
38
+ start!
38
39
 
39
- start_sse!
40
-
41
- @client = SplitClient.new(@api_key, @adapter, @splits_repository, @segments_repository, @impressions_repository, @metrics_repository, @events_repository, @sdk_blocker, @config, @sse_handler)
40
+ @client = SplitClient.new(@api_key, @metrics, @splits_repository, @segments_repository, @impressions_repository, @metrics_repository, @events_repository, @sdk_blocker, @config)
42
41
  @manager = SplitManager.new(@splits_repository, @sdk_blocker, @config)
43
42
 
44
43
  validate_api_key
@@ -49,7 +48,11 @@ module SplitIoClient
49
48
  end
50
49
 
51
50
  def start!
52
- SplitAdapter.new(@api_key, @splits_repository, @segments_repository, @impressions_repository, @metrics_repository, @events_repository, @sdk_blocker, @config)
51
+ if @config.localhost_mode
52
+ start_localhost_components
53
+ else
54
+ SplitIoClient::Engine::SyncManager.new(repositories, @api_key, @config, @sdk_blocker, @metrics).start
55
+ end
53
56
  end
54
57
 
55
58
  def stop!
@@ -118,15 +121,22 @@ module SplitIoClient
118
121
  end
119
122
  end
120
123
 
121
- def start_sse!
122
- if @config.push_notification_enabled
123
- @splits_worker = SSE::Workers::SplitsWorker.new(@adapter, @config, @splits_repository)
124
- @segments_worker = SSE::Workers::SegmentsWorker.new(@adapter, @config, @segments_repository)
125
- @control_worker = SSE::Workers::ControlWorker.new(@adapter, @config)
124
+ def repositories
125
+ repos = {}
126
+ repos[:splits] = @splits_repository
127
+ repos[:segments] = @segments_repository
128
+ repos[:impressions] = @impressions_repository
129
+ repos[:events] = @events_repository
130
+ repos[:metrics] = @metrics_repository
126
131
 
127
- options = { channels: 'mauro-c', key: 'fake_key', url_host: 'fake-url' }
128
- @sse_handler = SSE::SSEHandler.new(@config, options, @splits_worker, @segments_worker, @control_worker)
129
- end
132
+ repos
133
+ end
134
+
135
+ def start_localhost_components
136
+ LocalhostSplitStore.new(@splits_repository, @config, @sdk_blocker).call
137
+
138
+ # Starts thread which loops constantly and cleans up repositories to avoid memory issues in localhost mode
139
+ LocalhostRepoCleaner.new(@impressions_repository, @metrics_repository, @events_repository, @config).call
130
140
  end
131
141
  end
132
142
  end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: false
2
+
3
+ module SplitIoClient
4
+ module SSE
5
+ module EventSource
6
+ class BackOff
7
+ def initialize(back_off_base, attempt = 0)
8
+ @attempt = attempt
9
+ @back_off_base = back_off_base
10
+ end
11
+
12
+ def interval
13
+ interval = (@back_off_base * (2**@attempt)) if @attempt.positive?
14
+ @attempt += 1
15
+
16
+ interval || 0
17
+ end
18
+
19
+ def reset
20
+ @attempt = 0
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -8,107 +8,117 @@ module SplitIoClient
8
8
  module SSE
9
9
  module EventSource
10
10
  class Client
11
- DEFAULT_READ_TIMEOUT = 200
11
+ DEFAULT_READ_TIMEOUT = 70
12
12
  KEEP_ALIVE_RESPONSE = "c\r\n:keepalive\n\n\r\n".freeze
13
13
 
14
- def initialize(url, config, read_timeout: DEFAULT_READ_TIMEOUT)
15
- @uri = URI(url)
14
+ def initialize(config, read_timeout: DEFAULT_READ_TIMEOUT)
16
15
  @config = config
17
16
  @read_timeout = read_timeout
18
17
  @connected = Concurrent::AtomicBoolean.new(false)
19
18
  @socket = nil
19
+ @back_off = BackOff.new(@config.streaming_reconnect_back_off_base)
20
20
 
21
- @on = { event: ->(_) {}, error: ->(_) {} }
21
+ @on = { event: ->(_) {}, connected: ->(_) {}, disconnect: ->(_) {} }
22
22
 
23
23
  yield self if block_given?
24
-
25
- connect_thread
26
-
27
- connect_passenger_forked if defined?(PhusionPassenger)
28
24
  end
29
25
 
30
26
  def on_event(&action)
31
27
  @on[:event] = action
32
28
  end
33
29
 
34
- def on_error(&action)
35
- @on[:error] = action
30
+ def on_connected(&action)
31
+ @on[:connected] = action
32
+ end
33
+
34
+ def on_disconnect(&action)
35
+ @on[:disconnect] = action
36
36
  end
37
37
 
38
38
  def close
39
+ dispatch_disconnect
39
40
  @connected.make_false
41
+ SplitIoClient::Helpers::ThreadHelper.stop(:connect_stream, @config)
40
42
  @socket&.close
41
- @socket = nil
43
+ rescue StandardError => e
44
+ @config.logger.error("SSEClient close Error: #{e.inspect}")
42
45
  end
43
46
 
44
- def status
45
- return Status::CONNECTED if @connected.value
47
+ def start(url)
48
+ @uri = URI(url)
46
49
 
47
- Status::DISCONNECTED
50
+ connect_thread
51
+ rescue StandardError => e
52
+ @config.logger.error("SSEClient start Error: #{e.inspect}")
53
+ end
54
+
55
+ def connected?
56
+ @connected.value
48
57
  end
49
58
 
50
59
  private
51
60
 
52
61
  def connect_thread
53
62
  @config.threads[:connect_stream] = Thread.new do
63
+ @config.logger.info('Starting connect_stream thread ...') if @config.debug_enabled
54
64
  connect_stream
55
65
  end
56
66
  end
57
67
 
58
- def connect_passenger_forked
59
- PhusionPassenger.on_event(:starting_worker_process) { |forked| connect_thread if forked }
60
- end
61
-
62
68
  def connect_stream
63
- @config.logger.info("Connecting to #{@uri.host}...")
69
+ interval = @back_off.interval
70
+ sleep(interval) if interval.positive?
64
71
 
65
- begin
66
- @socket = socket_connect
67
-
68
- @socket.write(build_request(@uri))
69
- @connected.make_true
70
- rescue StandardError => e
71
- dispatch_error(e.inspect)
72
- end
72
+ socket_write
73
73
 
74
74
  while @connected.value
75
75
  begin
76
- partial_data = @socket.readpartial(2048, timeout: @read_timeout)
77
- rescue Socketry::TimeoutError
78
- @config.logger.error("Socket read time out in #{@read_timeout}")
76
+ partial_data = @socket.readpartial(10_000, timeout: @read_timeout)
77
+
78
+ raise 'eof exception' if partial_data == :eof
79
+ rescue StandardError => e
80
+ @config.logger.error(e.inspect) if @config.debug_enabled
79
81
  @connected.make_false
82
+ @socket&.close
83
+ @socket = nil
80
84
  connect_stream
81
85
  end
82
86
 
83
- proccess_data(partial_data) unless partial_data == KEEP_ALIVE_RESPONSE
87
+ process_data(partial_data)
84
88
  end
85
89
  end
86
90
 
91
+ def socket_write
92
+ @socket = socket_connect
93
+ @socket.write(build_request(@uri))
94
+ dispatch_connected
95
+ rescue StandardError => e
96
+ @config.logger.error("Error during connecting to #{@uri.host}. Error: #{e.inspect}")
97
+ close
98
+ end
99
+
87
100
  def socket_connect
88
101
  return Socketry::SSL::Socket.connect(@uri.host, @uri.port) if @uri.scheme.casecmp('https').zero?
89
102
 
90
103
  Socketry::TCP::Socket.connect(@uri.host, @uri.port)
91
104
  end
92
105
 
93
- def proccess_data(partial_data)
94
- unless partial_data.nil?
95
- @config.logger.debug("Event partial data: #{partial_data}")
106
+ def process_data(partial_data)
107
+ unless partial_data.nil? || partial_data == KEEP_ALIVE_RESPONSE
108
+ @config.logger.debug("Event partial data: #{partial_data}") if @config.debug_enabled
96
109
  buffer = read_partial_data(partial_data)
97
- event = parse_event(buffer)
98
-
99
- dispatch_event(event)
110
+ parse_event(buffer)
100
111
  end
101
112
  rescue StandardError => e
102
- dispatch_error(e.inspect)
113
+ @config.logger.error("process_data error: #{e.inspect}")
103
114
  end
104
115
 
105
116
  def build_request(uri)
106
117
  req = "GET #{uri.request_uri} HTTP/1.1\r\n"
107
118
  req << "Host: #{uri.host}\r\n"
108
119
  req << "Accept: text/event-stream\r\n"
109
- req << "Cache-Control: no-cache\r\n"
110
- req << "\r\n"
111
- @config.logger.debug("Request info: #{req}")
120
+ req << "Cache-Control: no-cache\r\n\r\n"
121
+ @config.logger.debug("Request info: #{req}") if @config.debug_enabled
112
122
  req
113
123
  end
114
124
 
@@ -120,43 +130,58 @@ module SplitIoClient
120
130
  end
121
131
 
122
132
  def parse_event(buffer)
123
- event_type = nil
124
- parsed_data = nil
125
- client_id = nil
133
+ type = nil
126
134
 
127
135
  buffer.each do |d|
128
136
  splited_data = d.split(':')
129
137
 
130
138
  case splited_data[0]
131
139
  when 'event'
132
- event_type = splited_data[1].strip
140
+ type = splited_data[1].strip
133
141
  when 'data'
134
- event_data = JSON.parse(d.sub('data: ', ''))
135
- client_id = event_data['clientId']&.strip
136
- parsed_data = JSON.parse(event_data['data'])
142
+ data = parse_event_data(d, type)
143
+ unless type.nil? || data[:data].nil?
144
+ event = StreamData.new(type, data[:client_id], data[:data], data[:channel])
145
+ dispatch_event(event)
146
+ end
137
147
  end
138
148
  end
149
+ rescue StandardError => e
150
+ @config.logger.error("Error during parsing a event: #{e.inspect}")
151
+ end
139
152
 
140
- return StreamData.new(event_type, client_id, parsed_data) unless event_type.nil? || parsed_data.nil?
153
+ def parse_event_data(data, type)
154
+ event_data = JSON.parse(data.sub('data: ', ''))
155
+ client_id = event_data['clientId']&.strip
156
+ channel = event_data['channel']&.strip
157
+ parsed_data = JSON.parse(event_data['data']) unless type == 'error'
158
+ parsed_data = event_data if type == 'error'
141
159
 
142
- raise 'Invalid event format.'
143
- rescue StandardError => e
144
- dispatch_error(e.inspect)
145
- nil
160
+ { client_id: client_id, channel: channel, data: parsed_data }
146
161
  end
147
162
 
148
163
  def dispatch_event(event)
149
- @config.logger.debug("Dispatching event: #{event}") unless event.nil?
150
- @on[:event].call(event) unless event.nil?
164
+ raise SSEClientException.new(event), 'Error event' if event.event_type == 'error'
165
+
166
+ @config.logger.debug("Dispatching event: #{event.event_type}, #{event.channel}") if @config.debug_enabled
167
+ @on[:event].call(event)
168
+ rescue SSEClientException => e
169
+ @config.logger.error("Event error: #{e.event.event_type}, #{e.event.data}")
170
+ close
151
171
  end
152
172
 
153
- def dispatch_error(error)
154
- @config.logger.debug("Dispatching error: #{error}")
155
- @on[:error].call(error)
173
+ def dispatch_connected
174
+ @connected.make_true
175
+ @back_off.reset
176
+ @config.logger.debug('Dispatching connected') if @config.debug_enabled
177
+ @on[:connected].call
156
178
  end
157
- end
158
179
 
159
- StreamData = Struct.new(:event_type, :client_id, :data)
180
+ def dispatch_disconnect
181
+ @config.logger.debug('Dispatching disconnect') if @config.debug_enabled
182
+ @on[:disconnect].call
183
+ end
184
+ end
160
185
  end
161
186
  end
162
187
  end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: false
2
+
3
+ module SplitIoClient
4
+ module SSE
5
+ module EventSource
6
+ class StreamData
7
+ attr_reader :event_type, :channel, :data, :client_id
8
+
9
+ def initialize(event_type, client_id, data, channel)
10
+ @event_type = event_type
11
+ @client_id = client_id
12
+ @data = data
13
+ @channel = channel&.gsub(SplitIoClient::Constants::OCCUPANCY_CHANNEL_PREFIX, '')
14
+ end
15
+
16
+ def occupancy?
17
+ @channel.include? 'control'
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'concurrent/atomics'
4
+
5
+ module SplitIoClient
6
+ module SSE
7
+ class NotificationManagerKeeper
8
+ def initialize(config)
9
+ @config = config
10
+ @publisher_available = Concurrent::AtomicBoolean.new(true)
11
+ @on = { occupancy: ->(_) {}, push_shutdown: ->(_) {} }
12
+
13
+ yield self if block_given?
14
+ end
15
+
16
+ def handle_incoming_occupancy_event(event)
17
+ if event.data['type'] == 'CONTROL'
18
+ process_event_control(event.data['controlType'])
19
+ elsif event.channel == SplitIoClient::Constants::CONTROL_PRI
20
+ process_event_occupancy(event.data['metrics']['publishers'])
21
+ end
22
+ rescue StandardError => e
23
+ @config.logger.error(e)
24
+ end
25
+
26
+ def on_occupancy(&action)
27
+ @on[:occupancy] = action
28
+ end
29
+
30
+ def on_push_shutdown(&action)
31
+ @on[:push_shutdown] = action
32
+ end
33
+
34
+ private
35
+
36
+ def process_event_control(type)
37
+ case type
38
+ when 'STREAMING_PAUSED'
39
+ dispatch_occupancy_event(false)
40
+ when 'STREAMING_RESUMED'
41
+ dispatch_occupancy_event(true) if @publisher_available.value
42
+ when 'STREAMING_DISABLED'
43
+ dispatch_push_shutdown
44
+ else
45
+ @config.logger.error("Incorrect event type: #{incoming_notification}")
46
+ end
47
+ end
48
+
49
+ def process_event_occupancy(publishers)
50
+ @config.logger.debug("Occupancy process event with #{publishers} publishers") if @config.debug_enabled
51
+ if publishers <= 0 && @publisher_available.value
52
+ @publisher_available.make_false
53
+ dispatch_occupancy_event(false)
54
+ elsif publishers >= 1 && !@publisher_available.value
55
+ @publisher_available.make_true
56
+ dispatch_occupancy_event(true)
57
+ end
58
+ end
59
+
60
+ def dispatch_occupancy_event(push_enable)
61
+ @config.logger.debug("Dispatching occupancy event with publisher avaliable: #{push_enable}")
62
+ @on[:occupancy].call(push_enable)
63
+ end
64
+
65
+ def dispatch_push_shutdown
66
+ @config.logger.debug('Dispatching push shutdown')
67
+ @on[:push_shutdown].call
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SplitIoClient
4
+ module SSE
5
+ class NotificationProcessor
6
+ def initialize(config, splits_worker, segments_worker)
7
+ @config = config
8
+ @splits_worker = splits_worker
9
+ @segments_worker = segments_worker
10
+ end
11
+
12
+ def process(incoming_notification)
13
+ case incoming_notification.data['type']
14
+ when SSE::EventSource::EventTypes::SPLIT_UPDATE
15
+ process_split_update(incoming_notification)
16
+ when SSE::EventSource::EventTypes::SPLIT_KILL
17
+ process_split_kill(incoming_notification)
18
+ when SSE::EventSource::EventTypes::SEGMENT_UPDATE
19
+ process_segment_update(incoming_notification)
20
+ else
21
+ @config.logger.error("Incorrect event type: #{incoming_notification}")
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def process_split_update(notification)
28
+ @config.logger.debug("SPLIT UPDATE notification received: #{notification}") if @config.debug_enabled
29
+ @splits_worker.add_to_queue(notification.data['changeNumber'])
30
+ end
31
+
32
+ def process_split_kill(notification)
33
+ @config.logger.debug("SPLIT KILL notification received: #{notification}") if @config.debug_enabled
34
+ change_number = notification.data['changeNumber']
35
+ default_treatment = notification.data['defaultTreatment']
36
+ split_name = notification.data['splitName']
37
+
38
+ @splits_worker.kill_split(change_number, split_name, default_treatment)
39
+ end
40
+
41
+ def process_segment_update(notification)
42
+ @config.logger.debug("SEGMENT UPDATE notification received: #{notification}") if @config.debug_enabled
43
+ change_number = notification.data['changeNumber']
44
+ segment_name = notification.data['segmentName']
45
+
46
+ @segments_worker.add_to_queue(change_number, segment_name)
47
+ end
48
+ end
49
+ end
50
+ end
@@ -5,78 +5,73 @@ module SplitIoClient
5
5
  class SSEHandler
6
6
  attr_reader :sse_client
7
7
 
8
- def initialize(config, options, splits_worker, segments_worker, control_worker)
8
+ def initialize(config, synchronizer, splits_repository, segments_repository, notification_manager_keeper)
9
9
  @config = config
10
- @options = options
11
- @splits_worker = splits_worker
12
- @segments_worker = segments_worker
13
- @control_worker = control_worker
14
-
15
- @sse_client = start_sse_client
16
- end
17
-
18
- private
10
+ @notification_manager_keeper = notification_manager_keeper
11
+ @splits_worker = SplitIoClient::SSE::Workers::SplitsWorker.new(synchronizer, config, splits_repository)
12
+ @segments_worker = SplitIoClient::SSE::Workers::SegmentsWorker.new(synchronizer, config, segments_repository)
13
+ @notification_processor = SplitIoClient::SSE::NotificationProcessor.new(config, @splits_worker, @segments_worker)
14
+ @sse_client = SSE::EventSource::Client.new(@config) do |client|
15
+ client.on_event { |event| handle_incoming_message(event) }
16
+ client.on_connected { process_connected }
17
+ client.on_disconnect { process_disconnect }
18
+ end
19
19
 
20
- def start_sse_client
21
- url = "#{@options[:url_host]}/event-stream?channels=#{@options[:channels]}&v=1.1&key=#{@options[:key]}"
20
+ @on = { connected: ->(_) {}, disconnect: ->(_) {} }
22
21
 
23
- sse_client = SSE::EventSource::Client.new(url, @config) do |client|
24
- client.on_event do |event|
25
- process_event(event)
26
- end
22
+ yield self if block_given?
23
+ end
27
24
 
28
- client.on_error do |error|
29
- process_error(error)
30
- end
31
- end
25
+ def start(token_jwt, channels)
26
+ url = "#{@config.streaming_service_url}?channels=#{channels}&v=1.1&accessToken=#{token_jwt}"
27
+ @sse_client.start(url)
28
+ end
32
29
 
33
- sse_client
30
+ def stop
31
+ @sse_client.close
32
+ stop_workers
33
+ rescue StandardError => e
34
+ @config.logger.debug("SSEHandler stop error: #{e.inspect}") if @config.debug_enabled
34
35
  end
35
36
 
36
- def process_event(event)
37
- case event.data['type']
38
- when SSE::EventSource::EventTypes::SPLIT_UPDATE
39
- split_update_notification(event)
40
- when SSE::EventSource::EventTypes::SPLIT_KILL
41
- split_kill_notification(event)
42
- when SSE::EventSource::EventTypes::SEGMENT_UPDATE
43
- segment_update_notification(event)
44
- when SSE::EventSource::EventTypes::CONTROL
45
- control_notification(event)
46
- else
47
- @config.logger.error("Incorrect event type: #{event}")
48
- end
37
+ def connected?
38
+ @sse_client&.connected? || false
49
39
  end
50
40
 
51
- def process_error(error)
52
- @config.logger.error("SSE::EventSource::Client error: #{error}")
41
+ def start_workers
42
+ @splits_worker.start
43
+ @segments_worker.start
53
44
  end
54
45
 
55
- def split_update_notification(event)
56
- @config.logger.debug("SPLIT UPDATE notification received: #{event}")
57
- @splits_worker.add_to_queue(event.data['changeNumber'])
46
+ def stop_workers
47
+ @splits_worker.stop
48
+ @segments_worker.stop
58
49
  end
59
50
 
60
- def split_kill_notification(event)
61
- @config.logger.debug("SPLIT KILL notification received: #{event}")
51
+ def on_connected(&action)
52
+ @on[:connected] = action
53
+ end
62
54
 
63
- change_number = event.data['changeNumber']
64
- default_treatment = event.data['defaultTreatment']
65
- split_name = event.data['splitName']
55
+ def on_disconnect(&action)
56
+ @on[:disconnect] = action
57
+ end
66
58
 
67
- @splits_worker.kill_split(change_number, split_name, default_treatment)
59
+ def process_disconnect
60
+ @on[:disconnect].call
68
61
  end
69
62
 
70
- def segment_update_notification(event)
71
- @config.logger.debug("SEGMENT UPDATE notification received: #{event}")
72
- change_number = event.data['changeNumber']
73
- segment_name = event.data['segmentName']
63
+ private
74
64
 
75
- @segments_worker.add_to_queue(change_number, segment_name)
65
+ def process_connected
66
+ @on[:connected].call
76
67
  end
77
68
 
78
- def control_notification(event)
79
- @config.logger.debug("CONTROL notification received: #{event}")
69
+ def handle_incoming_message(notification)
70
+ if notification.occupancy?
71
+ @notification_manager_keeper.handle_incoming_occupancy_event(notification)
72
+ else
73
+ @notification_processor.process(notification)
74
+ end
80
75
  end
81
76
  end
82
77
  end