ld-eventsource 2.3.0 → 2.5.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 56eca88c610f3aa1f572426034422f66f343ce30553c9d2e281462c4f3efe831
4
- data.tar.gz: a9719f1e29695c0468c8b745239feb71041d5735e32f071fd77873866518bad5
3
+ metadata.gz: 3ba844140cea3837caa7a02aa4676af7483faa11230a353da421e435932553b3
4
+ data.tar.gz: 30619609829908038a63d8302cefdbe8bda6935dc691ceaaabff225f2747da8c
5
5
  SHA512:
6
- metadata.gz: 2daad080ae032ab394dd4700e6bc22d20d167aca0e0a7ba9f74b41544ff0c5232cb0d34b6803fdc09342d95608732bea6c9ee7cef87bc912fef9a8ab10ccfb26
7
- data.tar.gz: 520224cf097791d2942ed0662dc29d94bff02c979fdebde479186efb21dbfa052aa80eee62779be723db2c918a9dd34a19ab4eea343453b7e114524a3dc23eb5
6
+ metadata.gz: 6b0ffc7be77854b053bc561d1389c3cb6327290066697ca40e6d7a96e29ad9192013b733e9b2973d0b1ebcb5aee66e0eb530066f6bbc3762f95fa80d28183b33
7
+ data.tar.gz: 7efe744ebf4c9cb9b4302fbbf5d2348a31a3f99f45de8cbd54346d2ac5eed8de8c8e79d5120888a2103d712afaf71035ebeec712664a797c229694a561b7dbe9
@@ -162,8 +162,9 @@ module SSE
162
162
  reconnect_reset_interval: reconnect_reset_interval)
163
163
  @first_attempt = true
164
164
 
165
- @on = { event: ->(_) {}, error: ->(_) {} }
165
+ @on = { event: ->(_) {}, error: ->(_) {}, connect: ->(_) {} }
166
166
  @last_id = last_event_id
167
+ @query_params_callback = nil
167
168
 
168
169
  yield self if block_given?
169
170
 
@@ -206,6 +207,56 @@ module SSE
206
207
  @on[:error] = action
207
208
  end
208
209
 
210
+ #
211
+ # Specifies a block or Proc to be called when a successful connection is established. This will
212
+ # be called with a single parameter containing the HTTP response headers. It is called
213
+ # from the same worker thread that reads the stream, so no more events will be dispatched until
214
+ # it returns.
215
+ #
216
+ # This is called every time a connection is successfully established, including on reconnections
217
+ # after a failure. It allows you to inspect server response headers such as rate limits, custom
218
+ # metadata, or fallback directives (e.g., `X-LD-FD-FALLBACK`).
219
+ #
220
+ # Any previously specified connect handler will be replaced.
221
+ #
222
+ # @yieldparam headers [Hash, nil] the HTTP response headers from the successful connection,
223
+ # or nil if not available. The headers object uses case-insensitive keys (via the http gem's
224
+ # HTTP::Headers).
225
+ #
226
+ def on_connect(&action)
227
+ @on[:connect] = action
228
+ end
229
+
230
+ #
231
+ # Specifies a block or Proc to generate query parameters dynamically. This will be called before
232
+ # each connection attempt (both initial connection and reconnections), allowing you to update
233
+ # query parameters based on the current client state.
234
+ #
235
+ # The block should return a Hash with string keys and string values, which will be merged with
236
+ # any existing query parameters in the base URI. If the callback raises an exception, it will be
237
+ # logged and the connection will proceed with the base URI's query parameters (or no query
238
+ # parameters if none were present).
239
+ #
240
+ # This is useful for scenarios where query parameters need to reflect the current state of the
241
+ # client, such as sending a "basis" parameter that represents what data the client already has.
242
+ #
243
+ # @example Using dynamic query parameters
244
+ # client = SSE::Client.new(base_uri) do |c|
245
+ # c.query_params do
246
+ # {
247
+ # "basis" => (selector.state if selector.defined?),
248
+ # "filter" => filter_key
249
+ # }.compact
250
+ # end
251
+ # c.on_event { |event| handle_event(event) }
252
+ # end
253
+ #
254
+ # @yieldreturn [Hash<String, String>] a hash of query parameter names to values
255
+ #
256
+ def query_params(&action)
257
+ @query_params_callback = action
258
+ end
259
+
209
260
  #
210
261
  # Permanently shuts down the client and its connection. No further events will be dispatched. This
211
262
  # has no effect if called a second time.
@@ -289,15 +340,18 @@ module SSE
289
340
  end
290
341
  cxn = nil
291
342
  begin
292
- @logger.info { "Connecting to event stream at #{@uri}" }
293
- cxn = @http_client.request(@method, @uri, build_opts)
343
+ uri = build_uri_with_query_params
344
+ @logger.info { "Connecting to event stream at #{uri}" }
345
+ cxn = @http_client.request(@method, uri, build_opts)
346
+ headers = cxn.headers
294
347
  if cxn.status.code == 200
295
348
  content_type = cxn.content_type.mime_type
296
349
  if content_type && content_type.start_with?("text/event-stream")
350
+ @on[:connect].call(headers) # Notify connect callback with headers
297
351
  return cxn # we're good to proceed
298
352
  else
299
353
  reset_http
300
- err = Errors::HTTPContentTypeError.new(content_type)
354
+ err = Errors::HTTPContentTypeError.new(content_type, headers)
301
355
  @on[:error].call(err)
302
356
  @logger.warn { "Event source returned unexpected content type '#{content_type}'" }
303
357
  end
@@ -305,7 +359,7 @@ module SSE
305
359
  body = cxn.to_s # grab the whole response body in case it has error details
306
360
  reset_http
307
361
  @logger.info { "Server returned error status #{cxn.status.code}" }
308
- err = Errors::HTTPStatusError.new(cxn.status.code, body)
362
+ err = Errors::HTTPStatusError.new(cxn.status.code, body, headers)
309
363
  @on[:error].call(err)
310
364
  end
311
365
  rescue
@@ -397,5 +451,27 @@ module SSE
397
451
  {headers: build_headers, body: resolved_payload.to_s}
398
452
  end
399
453
  end
454
+
455
+ def build_uri_with_query_params
456
+ uri = @uri.dup
457
+
458
+ if @query_params_callback
459
+ begin
460
+ dynamic_params = @query_params_callback.call
461
+ if dynamic_params.is_a?(Hash) && !dynamic_params.empty?
462
+ existing_params = uri.query ? URI.decode_www_form(uri.query).to_h : {}
463
+ merged_params = existing_params.merge(dynamic_params)
464
+ uri.query = URI.encode_www_form(merged_params)
465
+ elsif !dynamic_params.is_a?(Hash)
466
+ @logger.warn { "query_params callback returned non-Hash value: #{dynamic_params.class}, ignoring" }
467
+ end
468
+ rescue StandardError => e
469
+ @logger.warn { "query_params callback raised an exception: #{e.inspect}, proceeding with base URI" }
470
+ @logger.debug { "Exception trace: #{e.backtrace}" }
471
+ end
472
+ end
473
+
474
+ uri
475
+ end
400
476
  end
401
477
  end
@@ -9,9 +9,10 @@ module SSE
9
9
  # handler specified in {Client#on_error}.
10
10
  #
11
11
  class HTTPStatusError < StandardError
12
- def initialize(status, message)
12
+ def initialize(status, message, headers = nil)
13
13
  @status = status
14
14
  @message = message
15
+ @headers = headers
15
16
  super("HTTP error #{status}")
16
17
  end
17
18
 
@@ -22,6 +23,13 @@ module SSE
22
23
  # The response body, if any.
23
24
  # @return [String]
24
25
  attr_reader :message
26
+
27
+ # The HTTP response headers, if any.
28
+ #
29
+ # The headers object uses case-insensitive keys (via the http gem's HTTP::Headers).
30
+ #
31
+ # @return [Hash, nil] the response headers, or nil if not available
32
+ attr_reader :headers
25
33
  end
26
34
 
27
35
  #
@@ -29,14 +37,22 @@ module SSE
29
37
  # handler specified in {Client#on_error}.
30
38
  #
31
39
  class HTTPContentTypeError < StandardError
32
- def initialize(type)
40
+ def initialize(type, headers = nil)
33
41
  @content_type = type
42
+ @headers = headers
34
43
  super("invalid content type \"#{type}\"")
35
44
  end
36
45
 
37
46
  # The HTTP content type.
38
47
  # @return [String]
39
48
  attr_reader :type
49
+
50
+ # The HTTP response headers, if any.
51
+ #
52
+ # The headers object uses case-insensitive keys (via the http gem's HTTP::Headers).
53
+ #
54
+ # @return [Hash, nil] the response headers, or nil if not available
55
+ attr_reader :headers
40
56
  end
41
57
 
42
58
  #
@@ -1,3 +1,3 @@
1
1
  module SSE
2
- VERSION = "2.3.0" # x-release-please-version
2
+ VERSION = "2.5.0" # x-release-please-version
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ld-eventsource
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.3.0
4
+ version: 2.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - LaunchDarkly
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-09-04 00:00:00.000000000 Z
11
+ date: 2026-01-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: logger