ld-eventsource 2.2.6 → 2.4.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 +4 -4
- data/lib/ld-eventsource/client.rb +102 -9
- data/lib/ld-eventsource/impl/event_parser.rb +7 -2
- data/lib/ld-eventsource/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5199039ee9ec2f9b9843d5bbb79dbc12ca2a625cc0d79cb99b97bb41de729a6c
|
|
4
|
+
data.tar.gz: 37b524d87e083d857ab4202749f6f2aac649cb0b31c04401ecfe4dfa68d082ca
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6d1243db76fb427d8c5bcaae4976ce2942fe10c3ec45d4846f0b2b0d57bcb8ceb7a629da61f5f5a1f219afe29f8c1e5b8039dfad44af9b405225773499c9b4fd
|
|
7
|
+
data.tar.gz: 3a01a8c90f08f4740e27faf2d0789750fb15076a1e1242e45eebf09219ae500848003d09eeaa53fa16a14009383bbb424690b78419a01956d4cd7bbebfca0673
|
|
@@ -49,6 +49,9 @@ module SSE
|
|
|
49
49
|
# The default value for `reconnect_reset_interval` in {#initialize}.
|
|
50
50
|
DEFAULT_RECONNECT_RESET_INTERVAL = 60
|
|
51
51
|
|
|
52
|
+
# The default HTTP method for requests.
|
|
53
|
+
DEFAULT_HTTP_METHOD = "GET"
|
|
54
|
+
|
|
52
55
|
#
|
|
53
56
|
# Creates a new SSE client.
|
|
54
57
|
#
|
|
@@ -84,6 +87,16 @@ module SSE
|
|
|
84
87
|
# @param socket_factory [#open] (nil) an optional factory object for creating sockets,
|
|
85
88
|
# if you want to use something other than the default `TCPSocket`; it must implement
|
|
86
89
|
# `open(uri, timeout)` to return a connected `Socket`
|
|
90
|
+
# @param method [String] ("GET") the HTTP method to use for requests
|
|
91
|
+
# @param payload [String, Hash, Array, #call] (nil) optional request payload. If payload is a Hash or
|
|
92
|
+
# an Array, it will be converted to JSON and sent as the request body. A string will be sent as a non-JSON
|
|
93
|
+
# request body. If payload responds to #call, it will be invoked on each
|
|
94
|
+
# request to generate the payload dynamically.
|
|
95
|
+
# @param retry_enabled [Boolean] (true) whether to retry connections after failures. If false, the client
|
|
96
|
+
# will exit after the first connection failure instead of attempting to reconnect.
|
|
97
|
+
# @param http_client_options [Hash] (nil) additional options to pass to
|
|
98
|
+
# the HTTP client, such as `socket_factory` or `proxy`. These settings will override
|
|
99
|
+
# the socket factory and proxy settings.
|
|
87
100
|
# @yieldparam [Client] client the new client instance, before opening the connection
|
|
88
101
|
#
|
|
89
102
|
def initialize(uri,
|
|
@@ -95,17 +108,25 @@ module SSE
|
|
|
95
108
|
last_event_id: nil,
|
|
96
109
|
proxy: nil,
|
|
97
110
|
logger: nil,
|
|
98
|
-
socket_factory: nil
|
|
111
|
+
socket_factory: nil,
|
|
112
|
+
method: DEFAULT_HTTP_METHOD,
|
|
113
|
+
payload: nil,
|
|
114
|
+
retry_enabled: true,
|
|
115
|
+
http_client_options: nil)
|
|
99
116
|
@uri = URI(uri)
|
|
100
117
|
@stopped = Concurrent::AtomicBoolean.new(false)
|
|
118
|
+
@retry_enabled = retry_enabled
|
|
101
119
|
|
|
102
120
|
@headers = headers.clone
|
|
103
121
|
@connect_timeout = connect_timeout
|
|
104
122
|
@read_timeout = read_timeout
|
|
123
|
+
@method = method.to_s.upcase
|
|
124
|
+
@payload = payload
|
|
105
125
|
@logger = logger || default_logger
|
|
106
|
-
|
|
126
|
+
|
|
127
|
+
base_http_client_options = {}
|
|
107
128
|
if socket_factory
|
|
108
|
-
|
|
129
|
+
base_http_client_options["socket_class"] = socket_factory
|
|
109
130
|
end
|
|
110
131
|
|
|
111
132
|
if proxy
|
|
@@ -118,13 +139,18 @@ module SSE
|
|
|
118
139
|
end
|
|
119
140
|
|
|
120
141
|
if @proxy
|
|
121
|
-
|
|
142
|
+
base_http_client_options["proxy"] = {
|
|
122
143
|
:proxy_address => @proxy.host,
|
|
123
144
|
:proxy_port => @proxy.port,
|
|
124
145
|
}
|
|
146
|
+
base_http_client_options["proxy"][:proxy_username] = @proxy.user unless @proxy.user.nil?
|
|
147
|
+
base_http_client_options["proxy"][:proxy_password] = @proxy.password unless @proxy.password.nil?
|
|
125
148
|
end
|
|
126
149
|
|
|
127
|
-
|
|
150
|
+
options = http_client_options.is_a?(Hash) ? base_http_client_options.merge(http_client_options) : base_http_client_options
|
|
151
|
+
|
|
152
|
+
@http_client = HTTP::Client.new(options)
|
|
153
|
+
.follow
|
|
128
154
|
.timeout({
|
|
129
155
|
read: read_timeout,
|
|
130
156
|
connect: connect_timeout,
|
|
@@ -138,6 +164,7 @@ module SSE
|
|
|
138
164
|
|
|
139
165
|
@on = { event: ->(_) {}, error: ->(_) {} }
|
|
140
166
|
@last_id = last_event_id
|
|
167
|
+
@query_params_callback = nil
|
|
141
168
|
|
|
142
169
|
yield self if block_given?
|
|
143
170
|
|
|
@@ -180,6 +207,36 @@ module SSE
|
|
|
180
207
|
@on[:error] = action
|
|
181
208
|
end
|
|
182
209
|
|
|
210
|
+
#
|
|
211
|
+
# Specifies a block or Proc to generate query parameters dynamically. This will be called before
|
|
212
|
+
# each connection attempt (both initial connection and reconnections), allowing you to update
|
|
213
|
+
# query parameters based on the current client state.
|
|
214
|
+
#
|
|
215
|
+
# The block should return a Hash with string keys and string values, which will be merged with
|
|
216
|
+
# any existing query parameters in the base URI. If the callback raises an exception, it will be
|
|
217
|
+
# logged and the connection will proceed with the base URI's query parameters (or no query
|
|
218
|
+
# parameters if none were present).
|
|
219
|
+
#
|
|
220
|
+
# This is useful for scenarios where query parameters need to reflect the current state of the
|
|
221
|
+
# client, such as sending a "basis" parameter that represents what data the client already has.
|
|
222
|
+
#
|
|
223
|
+
# @example Using dynamic query parameters
|
|
224
|
+
# client = SSE::Client.new(base_uri) do |c|
|
|
225
|
+
# c.query_params do
|
|
226
|
+
# {
|
|
227
|
+
# "basis" => (selector.state if selector.defined?),
|
|
228
|
+
# "filter" => filter_key
|
|
229
|
+
# }.compact
|
|
230
|
+
# end
|
|
231
|
+
# c.on_event { |event| handle_event(event) }
|
|
232
|
+
# end
|
|
233
|
+
#
|
|
234
|
+
# @yieldreturn [Hash<String, String>] a hash of query parameter names to values
|
|
235
|
+
#
|
|
236
|
+
def query_params(&action)
|
|
237
|
+
@query_params_callback = action
|
|
238
|
+
end
|
|
239
|
+
|
|
183
240
|
#
|
|
184
241
|
# Permanently shuts down the client and its connection. No further events will be dispatched. This
|
|
185
242
|
# has no effect if called a second time.
|
|
@@ -246,6 +303,8 @@ module SSE
|
|
|
246
303
|
rescue StandardError => e
|
|
247
304
|
log_and_dispatch_error(e, "Unexpected error while closing stream")
|
|
248
305
|
end
|
|
306
|
+
|
|
307
|
+
return close unless @retry_enabled
|
|
249
308
|
end
|
|
250
309
|
end
|
|
251
310
|
|
|
@@ -261,10 +320,9 @@ module SSE
|
|
|
261
320
|
end
|
|
262
321
|
cxn = nil
|
|
263
322
|
begin
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
})
|
|
323
|
+
uri = build_uri_with_query_params
|
|
324
|
+
@logger.info { "Connecting to event stream at #{uri}" }
|
|
325
|
+
cxn = @http_client.request(@method, uri, build_opts)
|
|
268
326
|
if cxn.status.code == 200
|
|
269
327
|
content_type = cxn.content_type.mime_type
|
|
270
328
|
if content_type && content_type.start_with?("text/event-stream")
|
|
@@ -358,5 +416,40 @@ module SSE
|
|
|
358
416
|
h['Last-Event-Id'] = @last_id if !@last_id.nil? && @last_id != ""
|
|
359
417
|
h.merge(@headers)
|
|
360
418
|
end
|
|
419
|
+
|
|
420
|
+
def build_opts
|
|
421
|
+
return {headers: build_headers} if @payload.nil?
|
|
422
|
+
|
|
423
|
+
# Resolve payload if it's callable
|
|
424
|
+
resolved_payload = @payload.respond_to?(:call) ? @payload.call : @payload
|
|
425
|
+
|
|
426
|
+
if resolved_payload.is_a?(Hash) || resolved_payload.is_a?(Array)
|
|
427
|
+
{headers: build_headers, json: resolved_payload}
|
|
428
|
+
else
|
|
429
|
+
{headers: build_headers, body: resolved_payload.to_s}
|
|
430
|
+
end
|
|
431
|
+
end
|
|
432
|
+
|
|
433
|
+
def build_uri_with_query_params
|
|
434
|
+
uri = @uri.dup
|
|
435
|
+
|
|
436
|
+
if @query_params_callback
|
|
437
|
+
begin
|
|
438
|
+
dynamic_params = @query_params_callback.call
|
|
439
|
+
if dynamic_params.is_a?(Hash) && !dynamic_params.empty?
|
|
440
|
+
existing_params = uri.query ? URI.decode_www_form(uri.query).to_h : {}
|
|
441
|
+
merged_params = existing_params.merge(dynamic_params)
|
|
442
|
+
uri.query = URI.encode_www_form(merged_params)
|
|
443
|
+
elsif !dynamic_params.is_a?(Hash)
|
|
444
|
+
@logger.warn { "query_params callback returned non-Hash value: #{dynamic_params.class}, ignoring" }
|
|
445
|
+
end
|
|
446
|
+
rescue StandardError => e
|
|
447
|
+
@logger.warn { "query_params callback raised an exception: #{e.inspect}, proceeding with base URI" }
|
|
448
|
+
@logger.debug { "Exception trace: #{e.backtrace}" }
|
|
449
|
+
end
|
|
450
|
+
end
|
|
451
|
+
|
|
452
|
+
uri
|
|
453
|
+
end
|
|
361
454
|
end
|
|
362
455
|
end
|
|
@@ -42,9 +42,14 @@ module SSE
|
|
|
42
42
|
|
|
43
43
|
pos += 1 # skip colon
|
|
44
44
|
pos += 1 if pos < line.length && line[pos] == ' ' # skip optional single space, per SSE spec
|
|
45
|
-
|
|
45
|
+
value = line.slice(pos..-1)
|
|
46
46
|
|
|
47
|
-
item = process_field(name,
|
|
47
|
+
item = process_field(name, value)
|
|
48
|
+
gen.yield item unless item.nil?
|
|
49
|
+
else
|
|
50
|
+
# Handle field with no colon - treat as having empty value
|
|
51
|
+
# According to SSE spec, a line like "data" should be treated as "data:"
|
|
52
|
+
item = process_field(line, "")
|
|
48
53
|
gen.yield item unless item.nil?
|
|
49
54
|
end
|
|
50
55
|
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.
|
|
4
|
+
version: 2.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- LaunchDarkly
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2026-01-15 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: logger
|