configcat 9.0.0 → 10.0.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: 4aad1052b20cd0204dc7b7b6a0ca9cb556e5fc43f7a5598c06441ac27e12fae0
4
- data.tar.gz: 6efb60cc11630106701b68dd0d9fd14c1f1e38bb10746f4d5a031a0170052713
3
+ metadata.gz: 4d2b7f4e5d47d25f547528e0168dd531f50c84e44a9a1e9f6634c5f68db29dd0
4
+ data.tar.gz: e92023320e954118f80842efd483efd9cdb6865d4ac33f8d5c02230b0b11a981
5
5
  SHA512:
6
- metadata.gz: 906f61166e8ecbf15ed35ba50cf0f5cfa0132a822ea2023e0639e45d8ea44f76965b8c5c67e7d64464a45afc7fa4d7da500a89f5a132ce807bf7bd734940b0af
7
- data.tar.gz: 6211e2b597e3f25b09a7424906fab85a3a3edf89ab927d8c7302c35ae6703c6d1c43fd7d321bf051348a5201118c4bb90eff784327857311a1dc2e16e1336acb
6
+ metadata.gz: e72cf2f0a18bb717468a45727f924af34fbdf2dd188e2605243250112494eca928903f984248bc3edf15cf9ea32bb2975bfbec055f5c40fb78675ecf70f71237
7
+ data.tar.gz: 8172327e157b399f1ad53b1583a474af2c03ad012796523c68522568872801c63949027b89ec823a6b9459811e3181e1760bb4484a0c07cbc5bbc2b5691bf0a5
@@ -28,9 +28,10 @@ module ConfigCat
28
28
  client = @@instances[sdk_key]
29
29
  if client
30
30
  if options
31
+ masked_sdk_key = ConfigCatLogger.mask_sdk_key(sdk_key)
31
32
  client.log.warn(3000, "There is an existing client instance for the specified SDK Key. " \
32
33
  "No new client instance will be created and the specified options are ignored. " \
33
- "Returning the existing client instance. SDK Key: '#{sdk_key}'.")
34
+ "Returning the existing client instance. SDK Key: '#{masked_sdk_key}'.")
34
35
  end
35
36
  return client
36
37
  end
@@ -98,7 +99,7 @@ module ConfigCat
98
99
  read_timeout: options.read_timeout_seconds,
99
100
  data_governance: options.data_governance)
100
101
 
101
- @_config_service = ConfigService.new(@sdk_key,
102
+ @_config_service = ConfigService.new(@_sdk_key,
102
103
  options.polling_mode,
103
104
  @hooks,
104
105
  @_config_fetcher,
@@ -1,5 +1,12 @@
1
1
  module ConfigCat
2
2
  class ConfigCatLogger
3
+ def self.mask_sdk_key(sdk_key)
4
+ num_chars_to_keep = 6
5
+ return sdk_key if sdk_key.length <= num_chars_to_keep
6
+
7
+ sdk_key[0, sdk_key.length - num_chars_to_keep].gsub(%r{[^/]}, '*') + sdk_key[-num_chars_to_keep, num_chars_to_keep]
8
+ end
9
+
3
10
  def initialize(hooks)
4
11
  @hooks = hooks
5
12
  end
@@ -1,4 +1,5 @@
1
1
  require 'configcat/utils'
2
+ require 'json'
2
3
 
3
4
  module ConfigCat
4
5
  class ConfigEntry
@@ -26,13 +26,14 @@ module ConfigCat
26
26
  end
27
27
 
28
28
  class FetchResponse
29
- attr_reader :entry, :error, :is_transient_error
29
+ attr_reader :entry, :error, :is_transient_error, :cf_ray_id
30
30
 
31
- def initialize(status, entry, error = nil, is_transient_error = false)
31
+ def initialize(status, entry, error = nil, is_transient_error = false, cf_ray_id = nil)
32
32
  @status = status
33
33
  @entry = entry
34
34
  @error = error
35
35
  @is_transient_error = is_transient_error
36
+ @cf_ray_id = cf_ray_id
36
37
  end
37
38
 
38
39
  # Gets whether a new configuration value was fetched or not.
@@ -53,19 +54,22 @@ module ConfigCat
53
54
  @status == Status::FAILURE
54
55
  end
55
56
 
56
- def self.success(entry)
57
- FetchResponse.new(Status::FETCHED, entry)
57
+ def self.success(entry, cf_ray_id = nil)
58
+ FetchResponse.new(Status::FETCHED, entry, nil, false, cf_ray_id)
58
59
  end
59
60
 
60
- def self.not_modified
61
- FetchResponse.new(Status::NOT_MODIFIED, ConfigEntry::EMPTY)
61
+ def self.not_modified(cf_ray_id = nil)
62
+ FetchResponse.new(Status::NOT_MODIFIED, ConfigEntry::EMPTY, nil, false, cf_ray_id)
62
63
  end
63
64
 
64
- def self.failure(error, is_transient_error)
65
- FetchResponse.new(Status::FAILURE, ConfigEntry::EMPTY, error, is_transient_error)
65
+ def self.failure(error, is_transient_error, cf_ray_id = nil)
66
+ FetchResponse.new(Status::FAILURE, ConfigEntry::EMPTY, error, is_transient_error, cf_ray_id)
66
67
  end
67
68
  end
68
69
 
70
+ RETRY_DELAY_SECONDS = 0.05
71
+ CONNECTION_RESET_THRESHOLD_SECONDS = 30
72
+
69
73
  class ConfigFetcher
70
74
  def initialize(sdk_key, log, mode, base_url: nil, proxy_address: nil, proxy_port: nil, proxy_user: nil, proxy_pass: nil,
71
75
  open_timeout: 10, read_timeout: 30,
@@ -79,6 +83,7 @@ module ConfigCat
79
83
  @_open_timeout = open_timeout
80
84
  @_read_timeout = read_timeout
81
85
  @_headers = { "User-Agent" => ((("ConfigCat-Ruby/") + mode) + ("-")) + VERSION, "X-ConfigCat-UserAgent" => ((("ConfigCat-Ruby/") + mode) + ("-")) + VERSION, "Content-Type" => "application/json" }
86
+ @_last_connection_reset = nil
82
87
  if !base_url.equal?(nil)
83
88
  @_base_url_overridden = true
84
89
  @_base_url = base_url.chomp("/")
@@ -146,7 +151,9 @@ module ConfigCat
146
151
 
147
152
  # To prevent loops we check if we retried at least 3 times with the new base_url
148
153
  if retries >= 2
149
- @log.error(1104, "Redirection loop encountered while trying to fetch config JSON. Please contact us at https://configcat.com/support/")
154
+ error = "Redirection loop encountered while trying to fetch config JSON. Please contact us at https://configcat.com/support/"
155
+ error = "%s (Ray ID: %s)" % [error, fetch_response.cf_ray_id] if fetch_response.cf_ray_id
156
+ @log.error(1104, error)
150
157
  return fetch_response
151
158
  end
152
159
 
@@ -163,6 +170,25 @@ module ConfigCat
163
170
  private
164
171
 
165
172
  def _fetch(etag)
173
+ response = _fetch_http(etag)
174
+ if response.is_failed && response.is_transient_error
175
+ _reset_http_if_needed
176
+ sleep(RETRY_DELAY_SECONDS)
177
+ response = _fetch_http(etag)
178
+ end
179
+ response
180
+ end
181
+
182
+ def _reset_http_if_needed
183
+ now = Utils.get_utc_now_seconds_since_epoch
184
+ if @_last_connection_reset.nil? || now - @_last_connection_reset >= CONNECTION_RESET_THRESHOLD_SECONDS
185
+ close
186
+ @_last_connection_reset = now
187
+ end
188
+ end
189
+
190
+ def _fetch_http(etag)
191
+ response_cf_ray_id = nil
166
192
  begin
167
193
  @log.debug("Fetching configuration from ConfigCat")
168
194
  uri = URI.parse((((@_base_url + ("/")) + BASE_PATH) + @_sdk_key) + BASE_EXTENSION)
@@ -170,40 +196,57 @@ module ConfigCat
170
196
  headers["If-None-Match"] = etag.empty? ? nil : etag
171
197
  _create_http()
172
198
  request = Net::HTTP::Get.new(uri.request_uri, headers)
173
- response = @_http.request(request)
199
+ response = @_http.request(request) do |res|
200
+ # Capture the response before reading the body, so headers remain accessible even if the body download fails
201
+ response_cf_ray_id = res["CF-RAY"]
202
+ res.read_body
203
+ end
204
+
174
205
  case response
175
- when Net::HTTPSuccess
206
+ when Net::HTTPOK
176
207
  @log.debug("ConfigCat configuration json fetch response code:#{response.code} Cached:#{response['ETag']}")
208
+ content = response.body
209
+ if content.nil? || content.empty?
210
+ raise ArgumentError.new("Config JSON content cannot be null or empty.")
211
+ end
212
+ config = JSON.parse(content)
213
+ if config.nil?
214
+ raise ArgumentError.new("Invalid config JSON content: #{content}")
215
+ end
216
+ Config.fixup_config_salt_and_segments(config)
177
217
  response_etag = response["ETag"]
178
218
  if response_etag.nil?
179
219
  response_etag = ""
180
220
  end
181
- config = JSON.parse(response.body)
182
- Config.fixup_config_salt_and_segments(config)
183
- return FetchResponse.success(ConfigEntry.new(config, response_etag, response.body, Utils.get_utc_now_seconds_since_epoch))
221
+ return FetchResponse.success(ConfigEntry.new(config, response_etag, response.body, Utils.get_utc_now_seconds_since_epoch), response_cf_ray_id)
184
222
  when Net::HTTPNotModified
185
- return FetchResponse.not_modified
223
+ return FetchResponse.not_modified(response_cf_ray_id)
186
224
  when Net::HTTPNotFound, Net::HTTPForbidden
187
- error = "Your SDK Key seems to be wrong. You can find the valid SDK Key at https://app.configcat.com/sdkkey. Received unexpected response: #{response}"
225
+ masked_sdk_key = ConfigCatLogger.mask_sdk_key(@_sdk_key)
226
+ error = "Your SDK Key seems to be wrong: '#{masked_sdk_key}'. You can find the valid SDK Key at https://app.configcat.com/sdkkey. Received unexpected response: #{response}"
227
+ error = "%s (Ray ID: %s)" % [error, response_cf_ray_id] if response_cf_ray_id
188
228
  @log.error(1100, error)
189
- return FetchResponse.failure(error, false)
229
+ return FetchResponse.failure(error, false, response_cf_ray_id)
190
230
  else
191
231
  raise Net::HTTPError.new("", response)
192
232
  end
193
233
  rescue Net::HTTPError => e
194
234
  error = "Unexpected HTTP response was received while trying to fetch config JSON: #{e}"
235
+ error = "%s (Ray ID: %s)" % [error, response_cf_ray_id] if response_cf_ray_id
195
236
  @log.error(1101, error)
196
- return FetchResponse.failure(error, true)
237
+ return FetchResponse.failure(error, true, response_cf_ray_id)
197
238
  rescue Timeout::Error => e
198
239
  error = "Request timed out while trying to fetch config JSON. Timeout values: [connect: #{get_open_timeout()}s, read: #{get_read_timeout()}s]"
240
+ error = "%s (Ray ID: %s)" % [error, response_cf_ray_id] if response_cf_ray_id
199
241
  @log.error(1102, error)
200
- return FetchResponse.failure(error, true)
242
+ return FetchResponse.failure(error, true, response_cf_ray_id)
201
243
  rescue Exception => e
202
244
  error = "Unexpected error occurred while trying to fetch config JSON. It is most likely due to a local network " \
203
245
  "issue. Please make sure your application can reach the ConfigCat CDN servers (or your proxy server) " \
204
246
  "over HTTP. #{e}"
247
+ error = "%s (Ray ID: %s)" % [error, response_cf_ray_id] if response_cf_ray_id
205
248
  @log.error(1103, error)
206
- return FetchResponse.failure(error, true)
249
+ return FetchResponse.failure(error, true, response_cf_ray_id)
207
250
  end
208
251
  end
209
252
 
@@ -2,6 +2,7 @@ require 'concurrent'
2
2
  require 'configcat/configentry'
3
3
  require 'configcat/pollingmode'
4
4
  require 'configcat/refreshresult'
5
+ require 'digest'
5
6
 
6
7
 
7
8
  module ConfigCat
@@ -1,5 +1,6 @@
1
1
  require 'configcat/overridedatasource'
2
2
  require 'configcat/config'
3
+ require 'json'
3
4
 
4
5
 
5
6
  module ConfigCat
@@ -2,7 +2,9 @@ require 'configcat/user'
2
2
  require 'configcat/config'
3
3
  require 'configcat/evaluationcontext'
4
4
  require 'configcat/evaluationlogbuilder'
5
+ require 'date'
5
6
  require 'digest'
7
+ require 'json'
6
8
  require 'semantic'
7
9
 
8
10
 
@@ -1,3 +1,6 @@
1
+ require 'date'
2
+ require 'json'
3
+
1
4
  module ConfigCat
2
5
  # User Object. Contains user attributes which are used for evaluating targeting rules and percentage options.
3
6
  class User
@@ -1,3 +1,3 @@
1
1
  module ConfigCat
2
- VERSION = "9.0.0"
2
+ VERSION = "10.0.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: configcat
3
3
  version: !ruby/object:Gem::Version
4
- version: 9.0.0
4
+ version: 10.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - ConfigCat
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-09-10 00:00:00.000000000 Z
11
+ date: 2026-10-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby