Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.10.0
1.11.1
9 changes: 7 additions & 2 deletions lib/reforge/sse_config_client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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?
Expand All @@ -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}"
Expand All @@ -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}"
Expand Down
6 changes: 3 additions & 3 deletions sdk-reforge.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
Expand Down
40 changes: 37 additions & 3 deletions test/test_sse_config_client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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