diff --git a/CHANGELOG.md b/CHANGELOG.md index f916227..d1a5c9c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## 1.11.1 - 2025-10-06 + +- quiet logging for SSE reconnections +- let the SSE::Client handle the Last-Event-ID header + ## 1.10.0 - 2025-10-02 - require `base64` for newest ruby versions diff --git a/VERSION b/VERSION index 81c871d..720c738 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.10.0 +1.11.1 diff --git a/lib/reforge/sse_config_client.rb b/lib/reforge/sse_config_client.rb index 79b55b0..473d177 100644 --- a/lib/reforge/sse_config_client.rb +++ b/lib/reforge/sse_config_client.rb @@ -72,6 +72,7 @@ def connect(&load_configs) headers: headers, read_timeout: @options.sse_read_timeout, reconnect_time: @options.sse_default_reconnect_time, + last_event_id: (@config_loader.highwater_mark&.positive? ? @config_loader.highwater_mark.to_s : nil), logger: Reforge::InternalLogger.new(SSE::Client)) do |client| client.on_event do |event| if event.data.nil? || event.data.empty? @@ -92,7 +93,12 @@ def connect(&load_configs) end client.on_error do |error| - @logger.error "SSE Streaming Error: #{error.inspect} for url #{url}" + # SSL "unexpected eof" is expected when SSE sessions timeout normally + if error.is_a?(OpenSSL::SSL::SSLError) && error.message.include?('unexpected eof') + @logger.debug "SSE Streaming: Connection closed (expected timeout) for url #{url}" + else + @logger.error "SSE Streaming Error: #{error.inspect} for url #{url}" + end if @options.errors_to_close_connection.any? { |klass| error.is_a?(klass) } @logger.debug "Closing SSE connection for url #{url}" @@ -106,7 +112,6 @@ def headers auth = "#{AUTH_USER}:#{@prefab_options.sdk_key}" auth_string = Base64.strict_encode64(auth) return { - 'Last-Event-ID' => @config_loader.highwater_mark, 'Authorization' => "Basic #{auth_string}", 'Accept' => 'text/event-stream', 'X-Reforge-SDK-Version' => "sdk-ruby-#{Reforge::VERSION}" diff --git a/sdk-reforge.gemspec b/sdk-reforge.gemspec index 2726313..1eee29a 100644 --- a/sdk-reforge.gemspec +++ b/sdk-reforge.gemspec @@ -2,16 +2,16 @@ # DO NOT EDIT THIS FILE DIRECTLY # Instead, edit Juwelier::Tasks in Rakefile, and run 'rake gemspec' # -*- encoding: utf-8 -*- -# stub: sdk-reforge 1.10.0 ruby lib +# stub: sdk-reforge 1.11.1 ruby lib Gem::Specification.new do |s| s.name = "sdk-reforge".freeze - s.version = "1.10.0" + s.version = "1.11.1" s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version= s.require_paths = ["lib".freeze] s.authors = ["Jeff Dwyer".freeze] - s.date = "2025-10-02" + s.date = "2025-10-06" s.description = "Feature Flags, Live Config as a service".freeze s.email = "jeff.dwyer@reforge.com.cloud".freeze s.extra_rdoc_files = [ diff --git a/test/test_sse_config_client.rb b/test/test_sse_config_client.rb index 966d27e..e2628de 100644 --- a/test/test_sse_config_client.rb +++ b/test/test_sse_config_client.rb @@ -16,7 +16,6 @@ def test_client client = Reforge::SSEConfigClient.new(options, config_loader) - assert_equal 4, client.headers['Last-Event-ID'] assert_equal "https://stream.goatsofreforge.com", client.source result = nil @@ -48,8 +47,6 @@ def test_failing_over client = Reforge::SSEConfigClient.new(prefab_options, config_loader, sse_options) - assert_equal 4, client.headers['Last-Event-ID'] - result = nil # fake our load_configs block @@ -247,4 +244,41 @@ def test_empty_data_validation 'Expected to have logged an error about empty data for nil' mock_client.verify end + + def test_last_event_id_initialization + # Test with positive highwater_mark + config_loader = OpenStruct.new(highwater_mark: 42) + prefab_options = OpenStruct.new(sse_sources: ['http://localhost:4567'], sdk_key: 'test') + client = Reforge::SSEConfigClient.new(prefab_options, config_loader) + + # Mock SSE::Client.new to capture the last_event_id argument + SSE::Client.stub :new, ->(*args, **kwargs, &block) { + assert_equal '42', kwargs[:last_event_id], 'Expected last_event_id to be "42"' + OpenStruct.new(closed?: false, close: nil) + } do + client.connect { |_configs, _event, _source| } + end + + # Test with nil highwater_mark + config_loader = OpenStruct.new(highwater_mark: nil) + client = Reforge::SSEConfigClient.new(prefab_options, config_loader) + + SSE::Client.stub :new, ->(*args, **kwargs, &block) { + assert_nil kwargs[:last_event_id], 'Expected last_event_id to be nil when highwater_mark is nil' + OpenStruct.new(closed?: false, close: nil) + } do + client.connect { |_configs, _event, _source| } + end + + # Test with zero highwater_mark + config_loader = OpenStruct.new(highwater_mark: 0) + client = Reforge::SSEConfigClient.new(prefab_options, config_loader) + + SSE::Client.stub :new, ->(*args, **kwargs, &block) { + assert_nil kwargs[:last_event_id], 'Expected last_event_id to be nil when highwater_mark is 0' + OpenStruct.new(closed?: false, close: nil) + } do + client.connect { |_configs, _event, _source| } + end + end end