launchdarkly-server-sdk 8.14.0-java → 8.15.0-java

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: c6f092405b663bdca252bd2f617e1c81b8000860c5a711c6cfb3d0214f7d366d
4
- data.tar.gz: 5915a81dd336f244663cda3c52a68329a1d35c3b71716b5a050929df685ea5aa
3
+ metadata.gz: dc18b4c7af16130b9f7e9f7c8d0a4acd8e562d16ebb7714fc7846c9ff96d1d3f
4
+ data.tar.gz: 2c9bfdff939fb1f11a9bc83e707aa0e550136004c4a2870b7220fb293ad268f2
5
5
  SHA512:
6
- metadata.gz: 03b8f0e3c245e462e7de04adb9c9ea9a63d4f6c61eeb6564a7a668235c7a565aac296233a28079fefeff199b8b5dcb78baff34098180a7fd7c7d2fdb7ddf3c1c
7
- data.tar.gz: 19e70edd5df9b330aa1a68e0faa6ab4453bb7772629fad7b3b79271fb41b7003028a84b4719c2250987cf2db41d31f3c39fdc94ef3459d10c3f622aa32ba2f94
6
+ metadata.gz: 0b23cf9eda9b570844be07e71831f19734881b72d08c464b109929b17d225ac2083d78ead046418bdfec68e67db82f940c344efd3ce408ad1a1ea49e0d3dcaf2
7
+ data.tar.gz: ceb93287c34afbbb2301d3991267a10b1349aa126bea72c028552c431dc3493ec5f136d06b79f21641363dd9bfa87cce1a5ceb225534ff98ab8ad3088c2d7fc0
@@ -399,6 +399,30 @@ module LaunchDarkly
399
399
  #
400
400
  attr_reader :wrapper_version
401
401
 
402
+ #
403
+ # Returns a copy of this configuration with different wrapper information.
404
+ #
405
+ # This is intended for use by wrapper libraries, such as the LaunchDarkly OpenFeature provider, which need to
406
+ # identify themselves rather than the application which created the configuration.
407
+ #
408
+ # The copy is shallow: it shares the objects the original configuration references, such as the feature store,
409
+ # the logger, and the socket factory. Mutating one of those objects affects both configurations.
410
+ #
411
+ # @param wrapper_name [String, nil] see {#wrapper_name}
412
+ # @param wrapper_version [String, nil] see {#wrapper_version}
413
+ #
414
+ # @return [LaunchDarkly::Config]
415
+ #
416
+ def with_wrapper_information(wrapper_name, wrapper_version = nil)
417
+ updated = dup
418
+ updated.wrapper_name = wrapper_name
419
+ updated.wrapper_version = wrapper_version
420
+
421
+ updated
422
+ end
423
+
424
+ protected attr_writer :wrapper_name, :wrapper_version
425
+
402
426
  #
403
427
  # The factory used to construct sockets for HTTP operations. The factory must
404
428
  # provide the method `open(uri, timeout)`. The `open` method must return a
@@ -70,15 +70,15 @@ module LaunchDarkly
70
70
  filtered[:anonymous] = true if anonymous
71
71
 
72
72
  redacted = []
73
- private_attributes = @private_attributes.concat(context.private_attributes)
73
+ private_attributes = @private_attributes + context.private_attributes
74
74
 
75
75
  name = context.get_value(:name)
76
- if !name.nil? && !check_whole_attribute_private(:name, private_attributes, redacted, anonymous && redact_anonymous)
76
+ if !name.nil? && !check_whole_attribute_private(Reference.create_literal(:name), private_attributes, redacted, anonymous && redact_anonymous)
77
77
  filtered[:name] = name
78
78
  end
79
79
 
80
80
  context.get_custom_attribute_names.each do |attribute|
81
- unless check_whole_attribute_private(attribute, private_attributes, redacted, anonymous && redact_anonymous)
81
+ unless check_whole_attribute_private(Reference.create_literal(attribute), private_attributes, redacted, anonymous && redact_anonymous)
82
82
  value = context.get_value(attribute)
83
83
  filtered[attribute] = redact_json_value(nil, attribute, value, private_attributes, redacted)
84
84
  end
@@ -92,7 +92,7 @@ module LaunchDarkly
92
92
  #
93
93
  # Check if an entire attribute should be redacted.
94
94
  #
95
- # @param attribute [Symbol]
95
+ # @param attribute [Reference]
96
96
  # @param private_attributes [Array<Reference>]
97
97
  # @param redacted [Array<Symbol>]
98
98
  # @param redact_all [Boolean]
@@ -100,13 +100,13 @@ module LaunchDarkly
100
100
  #
101
101
  private def check_whole_attribute_private(attribute, private_attributes, redacted, redact_all)
102
102
  if @all_attributes_private || redact_all
103
- redacted << attribute
103
+ redacted << attribute.raw_path.to_sym
104
104
  return true
105
105
  end
106
106
 
107
107
  private_attributes.each do |private_attribute|
108
- if private_attribute.component(0) == attribute && private_attribute.depth == 1
109
- redacted << attribute
108
+ if private_attribute.component(0) == attribute.component(0) && private_attribute.depth == 1
109
+ redacted << attribute.raw_path.to_sym
110
110
  return true
111
111
  end
112
112
  end
@@ -1,3 +1,4 @@
1
+ require "ldclient-rb/impl/data_source"
1
2
  require "ldclient-rb/impl/repeating_task"
2
3
  require "ldclient-rb/impl/util"
3
4
 
@@ -35,7 +36,8 @@ module LaunchDarkly
35
36
 
36
37
  def poll
37
38
  begin
38
- all_data = @requestor.request_all_data
39
+ all_data, headers = request_all_data
40
+ DataSource.record_environment_id(@config.data_source_update_sink, headers)
39
41
  if all_data
40
42
  update_sink_or_data_store.init(all_data)
41
43
  if @initialized.make_true
@@ -92,6 +94,17 @@ module LaunchDarkly
92
94
  @config.data_source_update_sink || @config.feature_store
93
95
  end
94
96
 
97
+ #
98
+ # Requestors provided by application code may not be able to report the response headers.
99
+ #
100
+ # @return [Array(Hash, HTTP::Headers, nil)]
101
+ #
102
+ private def request_all_data
103
+ return @requestor.request_all_data_with_headers if @requestor.respond_to?(:request_all_data_with_headers)
104
+
105
+ [@requestor.request_all_data, nil]
106
+ end
107
+
95
108
  #
96
109
  # @param [LaunchDarkly::Interfaces::DataSource::ErrorInfo, nil] error_info
97
110
  #
@@ -40,8 +40,18 @@ module LaunchDarkly
40
40
  end
41
41
 
42
42
  def request_all_data()
43
- all_data = JSON.parse(make_request("/sdk/latest-all"), symbolize_names: true)
44
- Impl::Model.make_all_store_data(all_data, @config.logger)
43
+ request_all_data_with_headers.first
44
+ end
45
+
46
+ #
47
+ # Requests the full data set, also returning the headers of the response it was retrieved from.
48
+ #
49
+ # @return [Array(Hash, HTTP::Headers)]
50
+ #
51
+ def request_all_data_with_headers
52
+ body, headers = make_request("/sdk/latest-all")
53
+ all_data = JSON.parse(body, symbolize_names: true)
54
+ [Impl::Model.make_all_store_data(all_data, @config.logger), headers]
45
55
  end
46
56
 
47
57
  def stop
@@ -80,7 +90,7 @@ module LaunchDarkly
80
90
  etag = response.headers["etag"]
81
91
  @cache.write(uri, CacheEntry.new(etag, body)) unless etag.nil?
82
92
  end
83
- body
93
+ [body, response.headers]
84
94
  end
85
95
 
86
96
  def fix_encoding(body, content_type)
@@ -1,3 +1,4 @@
1
+ require "ldclient-rb/impl/data_source"
1
2
  require "ldclient-rb/impl/model/serialization"
2
3
  require "ldclient-rb/impl/util"
3
4
  require "ldclient-rb/in_memory_store"
@@ -54,6 +55,7 @@ module LaunchDarkly
54
55
 
55
56
  uri = Impl::Util.add_payload_filter_key(@config.stream_uri + "/all", @config)
56
57
  @es = SSE::Client.new(uri, **opts) do |conn|
58
+ conn.on_connect { |response_headers| DataSource.record_environment_id(@data_source_update_sink, response_headers) }
57
59
  conn.on_event { |event| process_message(event) }
58
60
  conn.on_error { |err|
59
61
  log_connection_result(false)
@@ -7,6 +7,27 @@ require "set"
7
7
  module LaunchDarkly
8
8
  module Impl
9
9
  module DataSource
10
+ LD_ENVID_HEADER = "X-LD-EnvID"
11
+
12
+ #
13
+ # Records the environment ID reported by LaunchDarkly, when the response headers provide one and the sink is
14
+ # able to hold it. Externally implemented sinks are not required to support this.
15
+ #
16
+ # @param sink [LaunchDarkly::Interfaces::DataSource::UpdateSink, nil]
17
+ # @param headers [#[], nil]
18
+ # @return [void]
19
+ #
20
+ def self.record_environment_id(sink, headers)
21
+ return if headers.nil? || !sink.respond_to?(:set_environment_id)
22
+
23
+ environment_id = headers[LD_ENVID_HEADER]
24
+ # The http gem returns arrays for repeated headers; normalize to a string.
25
+ environment_id = environment_id.first if environment_id.is_a?(Array)
26
+ return unless environment_id.is_a?(String) && !environment_id.empty?
27
+
28
+ sink.set_environment_id(environment_id)
29
+ end
30
+
10
31
  class StatusProvider
11
32
  include LaunchDarkly::Interfaces::DataSource::StatusProvider
12
33
 
@@ -41,12 +62,28 @@ module LaunchDarkly
41
62
  @dependency_tracker = LaunchDarkly::Impl::DependencyTracker.new
42
63
 
43
64
  @mutex = Mutex.new
65
+ @environment_id = nil
44
66
  @current_status = LaunchDarkly::Interfaces::DataSource::Status.new(
45
67
  LaunchDarkly::Interfaces::DataSource::Status::INITIALIZING,
46
68
  Time.now,
47
69
  nil)
48
70
  end
49
71
 
72
+ #
73
+ # @return [String, nil] The environment ID reported by LaunchDarkly, if known
74
+ #
75
+ def environment_id
76
+ @mutex.synchronize { @environment_id }
77
+ end
78
+
79
+ #
80
+ # @param environment_id [String]
81
+ # @return [void]
82
+ #
83
+ def set_environment_id(environment_id)
84
+ @mutex.synchronize { @environment_id = environment_id }
85
+ end
86
+
50
87
  def init(all_data)
51
88
  old_data = nil
52
89
  monitor_store_update do
@@ -121,6 +121,11 @@ module LaunchDarkly
121
121
  @flag_change_broadcaster
122
122
  end
123
123
 
124
+ # (see DataSystem#environment_id)
125
+ def environment_id
126
+ @data_source_update_sink.environment_id
127
+ end
128
+
124
129
  #
125
130
  # (see DataSystem#data_availability)
126
131
  #
@@ -100,6 +100,9 @@ module LaunchDarkly
100
100
  @store.with_persistence(wrapper, writable, @data_store_status_provider)
101
101
  end
102
102
 
103
+ @environment_id = nil
104
+ @environment_id_lock = Mutex.new
105
+
103
106
  # Threading
104
107
  @stop_event = Concurrent::Event.new
105
108
  @ready_event = Concurrent::Event.new
@@ -185,6 +188,11 @@ module LaunchDarkly
185
188
  @flag_change_broadcaster
186
189
  end
187
190
 
191
+ # (see DataSystem#environment_id)
192
+ def environment_id
193
+ @environment_id_lock.synchronize { @environment_id }
194
+ end
195
+
188
196
  # (see DataSystem#data_availability)
189
197
  def data_availability
190
198
  return DataAvailability::REFRESHED if @store.selector.defined?
@@ -270,6 +278,7 @@ module LaunchDarkly
270
278
  if basis_result.success?
271
279
  basis = basis_result.value
272
280
  @logger.info { "[LDClient] Initialized via #{initializer.name}" }
281
+ record_environment_id(basis.environment_id)
273
282
 
274
283
  # Apply the basis to the store regardless of whether fallback was signalled.
275
284
  # If the server returned a valid payload alongside the directive we still want
@@ -460,7 +469,10 @@ module LaunchDarkly
460
469
  @store.apply(update.change_set, true) if update.change_set
461
470
 
462
471
  # Set ready event on valid update
463
- @ready_event.set if update.state == LaunchDarkly::Interfaces::DataSource::Status::VALID
472
+ if update.state == LaunchDarkly::Interfaces::DataSource::Status::VALID
473
+ @ready_event.set
474
+ record_environment_id(update.environment_id)
475
+ end
464
476
 
465
477
  # Update status
466
478
  @data_source_status_provider.update_status(update.state, update.error)
@@ -481,6 +493,18 @@ module LaunchDarkly
481
493
  SyncResult::REMOVE
482
494
  end
483
495
 
496
+ #
497
+ # Retains the environment ID reported by a successful connection to LaunchDarkly.
498
+ #
499
+ # @param environment_id [String, nil]
500
+ # @return [void]
501
+ #
502
+ def record_environment_id(environment_id)
503
+ return unless environment_id.is_a?(String) && !environment_id.empty?
504
+
505
+ @environment_id_lock.synchronize { @environment_id = environment_id }
506
+ end
507
+
484
508
  #
485
509
  # Determine if we should fallback to the next synchronizer.
486
510
  #
@@ -313,7 +313,7 @@ module LaunchDarkly
313
313
  query_params << ["filter", @config.payload_filter_key] unless @config.payload_filter_key.nil?
314
314
 
315
315
  if selector && selector.defined?
316
- query_params << ["selector", selector.state]
316
+ query_params << ["basis", selector.state]
317
317
  end
318
318
 
319
319
  uri = @poll_uri
@@ -119,6 +119,15 @@ module LaunchDarkly
119
119
  raise NotImplementedError, "#{self.class} must implement #set_diagnostic_accumulator"
120
120
  end
121
121
 
122
+ #
123
+ # Returns the ID of the environment the SDK is connected to, if LaunchDarkly has reported one.
124
+ #
125
+ # @return [String, nil]
126
+ #
127
+ def environment_id
128
+ raise NotImplementedError, "#{self.class} must implement #environment_id"
129
+ end
130
+
122
131
  #
123
132
  # Represents the availability of data in the SDK.
124
133
  #
@@ -50,7 +50,7 @@ module LaunchDarkly
50
50
  # "segments": {
51
51
  # "segment-key-1": {
52
52
  # "key": "segment-key-1",
53
- # "includes": [ "user-key-1" ]
53
+ # "included": [ "user-key-1" ]
54
54
  # }
55
55
  # }
56
56
  # }
@@ -70,17 +70,27 @@ module LaunchDarkly
70
70
  attr_reader :default_value
71
71
  attr_reader :method
72
72
 
73
+ #
74
+ # The ID of the LaunchDarkly environment the SDK is connected to, if known. This is only available once the
75
+ # SDK has received data from LaunchDarkly.
76
+ #
77
+ # @return [String, nil]
78
+ #
79
+ attr_reader :environment_id
80
+
73
81
  #
74
82
  # @param key [String]
75
83
  # @param context [LaunchDarkly::LDContext]
76
84
  # @param default_value [any]
77
85
  # @param method [Symbol]
86
+ # @param environment_id [String, nil]
78
87
  #
79
- def initialize(key, context, default_value, method)
88
+ def initialize(key, context, default_value, method, environment_id = nil)
80
89
  @key = key
81
90
  @context = context
82
91
  @default_value = default_value
83
92
  @method = method
93
+ @environment_id = environment_id
84
94
  end
85
95
  end
86
96
  end
@@ -421,7 +421,7 @@ module LaunchDarkly
421
421
  # Hooks can be added and we want to ensure all correct stages for a given hook execute. For example, we do not
422
422
  # want to trigger the after_evaluation method without also triggering the before_evaluation method.
423
423
  hooks = @hooks.dup
424
- evaluation_series_context = Interfaces::Hooks::EvaluationSeriesContext.new(key, context, default, method)
424
+ evaluation_series_context = Interfaces::Hooks::EvaluationSeriesContext.new(key, context, default, method, @data_system.environment_id)
425
425
 
426
426
  [hooks, evaluation_series_context]
427
427
  end
@@ -1,3 +1,3 @@
1
1
  module LaunchDarkly
2
- VERSION = "8.14.0" # x-release-please-version
2
+ VERSION = "8.15.0" # x-release-please-version
3
3
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: launchdarkly-server-sdk
3
3
  version: !ruby/object:Gem::Version
4
- version: 8.14.0
4
+ version: 8.15.0
5
5
  platform: java
6
6
  authors:
7
7
  - LaunchDarkly
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2026-06-01 00:00:00.000000000 Z
10
+ date: 2026-08-20 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: aws-sdk-dynamodb