react_on_rails_pro 16.5.1 → 16.6.0.rc.1

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: 555e14ff0b3a9fe0dbdf0d03195d25512f535ce488f426f8258af5dde9e93fa3
4
- data.tar.gz: 2aa2508ce583c9b6c3907f990d8a07ce151c122a610524f89ea8a4b5fc75218c
3
+ metadata.gz: 3c916f9674e2623f411de371a45f7a4016780b2f0f2b267b2be6adc00b73e256
4
+ data.tar.gz: ea25bb1c946748b59252d44a5e42bdab08b9782d062ac1e03d35480e1d080f2f
5
5
  SHA512:
6
- metadata.gz: 58c950e2ace03c47a86777a398e8260d1ab67a0d28a9838431be74e181c6345af08e8ecf99d8500a179a4c62c9e871af9aa1ceef5991f199452cc2c3cdea0b6a
7
- data.tar.gz: 8df15aa0b502450842d744049f7f17aff8843b0afddccf0d97cdb926d0497fdf26cb6e834ca781ec4bd532e9ddf01bc237d14643a088eb8f854d1ea969bb840f
6
+ metadata.gz: 377bbdc3a94e443fa1fda13389603f1593a8fe3457af9ccc66ebfe5b710164cf1b7a225886d44e44edec6a73fd4bb4cd31813ee2c6f57f02c884efb79a1fb14f
7
+ data.tar.gz: c23fcc4de60626c285b8aeb47fb9b334fac21b57950976aa947be27e51831e3ef221a9e482fb3ba4dccaf693bd2aa755ab13f802780b27eca0411ac65dbca83a
data/CONTRIBUTING.md CHANGED
@@ -46,7 +46,7 @@ From [How to Write a Git Commit Message](http://chris.beams.io/posts/git-commit/
46
46
 
47
47
  ## Doc Changes
48
48
 
49
- When making doc changes, we want the change to work on both [the React on Rails docs site](https://reactonrails.com/docs/pro) and when browsing the GitHub repo.
49
+ When making doc changes, we want the change to work on both [the React on Rails docs site](https://reactonrails.com/docs/pro/) and when browsing the GitHub repo.
50
50
  For links from docs pages to non-doc files, use full GitHub URLs so links resolve correctly in both contexts.
51
51
 
52
52
  ### Links to other docs:
data/Gemfile.lock CHANGED
@@ -9,7 +9,7 @@ GIT
9
9
  PATH
10
10
  remote: ..
11
11
  specs:
12
- react_on_rails (16.5.1)
12
+ react_on_rails (16.6.0.rc.1)
13
13
  addressable
14
14
  connection_pool
15
15
  execjs (~> 2.5)
@@ -20,7 +20,7 @@ PATH
20
20
  PATH
21
21
  remote: .
22
22
  specs:
23
- react_on_rails_pro (16.5.1)
23
+ react_on_rails_pro (16.6.0.rc.1)
24
24
  addressable
25
25
  async (>= 2.29)
26
26
  connection_pool
@@ -29,7 +29,7 @@ PATH
29
29
  httpx (~> 1.5)
30
30
  jwt (~> 2.7)
31
31
  rainbow
32
- react_on_rails (= 16.5.1)
32
+ react_on_rails (= 16.6.0.rc.1)
33
33
 
34
34
  GEM
35
35
  remote: https://rubygems.org/
@@ -217,7 +217,7 @@ GEM
217
217
  rb-fsevent (~> 0.10, >= 0.10.3)
218
218
  rb-inotify (~> 0.9, >= 0.9.10)
219
219
  logger (1.7.0)
220
- loofah (2.25.0)
220
+ loofah (2.25.1)
221
221
  crass (~> 1.0.2)
222
222
  nokogiri (>= 1.12.0)
223
223
  mail (2.9.0)
@@ -249,11 +249,11 @@ GEM
249
249
  net-smtp (0.5.1)
250
250
  net-protocol
251
251
  nio4r (2.7.5)
252
- nokogiri (1.19.1-arm64-darwin)
252
+ nokogiri (1.19.2-arm64-darwin)
253
253
  racc (~> 1.4)
254
- nokogiri (1.19.1-x86_64-darwin)
254
+ nokogiri (1.19.2-x86_64-darwin)
255
255
  racc (~> 1.4)
256
- nokogiri (1.19.1-x86_64-linux-gnu)
256
+ nokogiri (1.19.2-x86_64-linux-gnu)
257
257
  racc (~> 1.4)
258
258
  package_json (0.2.0)
259
259
  parallel (1.27.0)
@@ -128,7 +128,10 @@ module ReactOnRailsProHelper
128
128
  # stream_react_component doesn't have the prerender option
129
129
  # Because setting prerender to false is equivalent to calling react_component with prerender: false
130
130
  options[:prerender] = true
131
- options = options.merge(immediate_hydration: true) unless options.key?(:immediate_hydration)
131
+ if options.key?(:immediate_hydration)
132
+ ReactOnRails::Helper.warn_removed_immediate_hydration_option("stream_react_component")
133
+ options.delete(:immediate_hydration)
134
+ end
132
135
 
133
136
  # Extract streaming-specific callback
134
137
  on_complete = options.delete(:on_complete)
@@ -15,6 +15,7 @@ module ReactOnRailsPro
15
15
  renderer_http_pool_size: Configuration::DEFAULT_RENDERER_HTTP_POOL_SIZE,
16
16
  renderer_http_pool_timeout: Configuration::DEFAULT_RENDERER_HTTP_POOL_TIMEOUT,
17
17
  renderer_http_pool_warn_timeout: Configuration::DEFAULT_RENDERER_HTTP_POOL_WARN_TIMEOUT,
18
+ renderer_http_keep_alive_timeout: Configuration::DEFAULT_RENDERER_HTTP_KEEP_ALIVE_TIMEOUT,
18
19
  renderer_password: nil,
19
20
  tracing: Configuration::DEFAULT_TRACING,
20
21
  dependency_globs: Configuration::DEFAULT_DEPENDENCY_GLOBS,
@@ -44,6 +45,7 @@ module ReactOnRailsPro
44
45
  DEFAULT_RENDERER_HTTP_POOL_SIZE = 10
45
46
  DEFAULT_RENDERER_HTTP_POOL_TIMEOUT = 5
46
47
  DEFAULT_RENDERER_HTTP_POOL_WARN_TIMEOUT = 0.25
48
+ DEFAULT_RENDERER_HTTP_KEEP_ALIVE_TIMEOUT = 30
47
49
  DEFAULT_SSR_TIMEOUT = 5
48
50
  DEFAULT_PRERENDER_CACHING = false
49
51
  DEFAULT_TRACING = false
@@ -72,7 +74,7 @@ module ReactOnRailsPro
72
74
  :rsc_payload_generation_url_path, :rsc_bundle_js_file, :react_client_manifest_file,
73
75
  :react_server_client_manifest_file
74
76
 
75
- attr_reader :concurrent_component_streaming_buffer_size
77
+ attr_reader :concurrent_component_streaming_buffer_size, :renderer_http_keep_alive_timeout
76
78
 
77
79
  # Sets the buffer size for concurrent component streaming.
78
80
  #
@@ -91,10 +93,23 @@ module ReactOnRailsPro
91
93
  @concurrent_component_streaming_buffer_size = value
92
94
  end
93
95
 
96
+ # Sets the keep-alive timeout (in seconds) for persistent HTTP connections to the node renderer.
97
+ #
98
+ # @param value [Numeric, nil] A positive number or nil (to use the HTTPX default)
99
+ # @raise [ReactOnRailsPro::Error] if value is not a positive number or nil
100
+ def renderer_http_keep_alive_timeout=(value)
101
+ unless value.nil? || (value.is_a?(Numeric) && value.positive? && value.finite?)
102
+ raise ReactOnRailsPro::Error,
103
+ "config.renderer_http_keep_alive_timeout must be a finite positive number or nil"
104
+ end
105
+ @renderer_http_keep_alive_timeout = value
106
+ end
107
+
94
108
  def initialize(renderer_url: nil, renderer_password: nil, server_renderer: nil, # rubocop:disable Metrics/AbcSize
95
109
  renderer_use_fallback_exec_js: nil, prerender_caching: nil,
96
110
  renderer_http_pool_size: nil, renderer_http_pool_timeout: nil,
97
- renderer_http_pool_warn_timeout: nil, tracing: nil,
111
+ renderer_http_pool_warn_timeout: nil, renderer_http_keep_alive_timeout: nil,
112
+ tracing: nil,
98
113
  dependency_globs: nil, excluded_dependency_globs: nil, rendering_returns_promises: nil,
99
114
  remote_bundle_cache_adapter: nil, ssr_pre_hook_js: nil, assets_to_copy: nil,
100
115
  renderer_request_retry_limit: nil, throw_js_errors: nil, ssr_timeout: nil,
@@ -111,6 +126,7 @@ module ReactOnRailsPro
111
126
  self.renderer_http_pool_size = renderer_http_pool_size
112
127
  self.renderer_http_pool_timeout = renderer_http_pool_timeout
113
128
  self.renderer_http_pool_warn_timeout = renderer_http_pool_warn_timeout
129
+ self.renderer_http_keep_alive_timeout = renderer_http_keep_alive_timeout
114
130
  self.tracing = tracing
115
131
  self.rendering_returns_promises = server_renderer == "NodeRenderer" ? rendering_returns_promises : false
116
132
  self.dependency_globs = dependency_globs
@@ -229,10 +245,68 @@ module ReactOnRailsPro
229
245
  end
230
246
 
231
247
  def setup_renderer_password
248
+ # Explicit passwords, including values loaded from ENV in the initializer, skip URL extraction.
249
+ # Blank values (nil or "") fall through so URL extraction and ENV fallback still apply.
232
250
  return if renderer_password.present?
233
251
 
234
252
  uri = URI(renderer_url)
235
253
  self.renderer_password = uri.password
254
+
255
+ # Mirror Node-side defaults: if Rails config and URL are both missing a password,
256
+ # use RENDERER_PASSWORD from env.
257
+ self.renderer_password = ENV.fetch("RENDERER_PASSWORD", nil) if renderer_password.blank?
258
+
259
+ validate_renderer_password_for_production
260
+ end
261
+
262
+ def validate_renderer_password_for_production
263
+ # Defense-in-depth: skip validation when a password is already configured (e.g. extracted
264
+ # from the renderer URL by setup_renderer_password, or set directly in the initializer).
265
+ return if renderer_password.present?
266
+ return unless node_renderer?
267
+
268
+ # Fail closed: only skip validation when RAILS_ENV is explicitly set to development or test.
269
+ # Rails.env defaults to "development" when RAILS_ENV is unset, which would silently skip
270
+ # validation in misconfigured environments. Checking ENV["RAILS_ENV"] directly matches the
271
+ # Node-side behavior where an unset environment is treated as production-like.
272
+ rails_env = ENV["RAILS_ENV"]&.downcase
273
+ return if rails_env.present? && %w[development test].include?(rails_env)
274
+
275
+ raise ReactOnRailsPro::Error, <<~MSG
276
+ RENDERER_PASSWORD must be set in production-like environments (staging, production, etc.)
277
+ when using the NodeRenderer.
278
+
279
+ In development and test environments, the renderer password is optional and no authentication
280
+ is required. In all other environments, you must explicitly configure a password to secure
281
+ communication between Rails and the Node Renderer.
282
+
283
+ To fix this, set the RENDERER_PASSWORD environment variable:
284
+
285
+ export RENDERER_PASSWORD="your-secure-password"
286
+
287
+ Rails reads it automatically. If you prefer to make it explicit in your initializer:
288
+
289
+ # config/initializers/react_on_rails_pro.rb
290
+ ReactOnRailsPro.configure do |config|
291
+ config.renderer_password = ENV.fetch("RENDERER_PASSWORD")
292
+ end
293
+
294
+ Set the same password for the Node Renderer via the RENDERER_PASSWORD environment variable.
295
+ Rails resolves the password in this order:
296
+ 1) config.renderer_password (blank values fall through to the next step)
297
+ 2) Password embedded in config.renderer_url (for example, https://:password@host:3800)
298
+ 3) ENV["RENDERER_PASSWORD"]
299
+
300
+ If Rails and the Node Renderer disagree about startup behavior, verify both RAILS_ENV and NODE_ENV.
301
+
302
+ Environment matrix:
303
+ development — password optional (no authentication)
304
+ test — password optional (no authentication)
305
+ (RAILS_ENV unset) — treated as production-like; RENDERER_PASSWORD required
306
+ staging — RENDERER_PASSWORD required
307
+ production — RENDERER_PASSWORD required
308
+ (any other) — RENDERER_PASSWORD required
309
+ MSG
236
310
  end
237
311
  end
238
312
  end
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ReactOnRailsPro
4
+ # Status code 400 indicates the renderer rejected the request payload or encountered an unhandled render error.
5
+ STATUS_BAD_REQUEST = 400
4
6
  # Status code 410 means to resend the request with the updated bundle.
5
7
  STATUS_SEND_BUNDLE = 410
6
8
  # Status code 412 means protocol versions are incompatible between the server and the renderer.
@@ -297,11 +297,11 @@ module ReactOnRailsPro
297
297
  # :write_timeout
298
298
  # :request_timeout
299
299
  # :operation_timeout
300
- # :keep_alive_timeout
301
300
  timeout: {
302
301
  connect_timeout: ReactOnRailsPro.configuration.renderer_http_pool_timeout,
303
- read_timeout: ReactOnRailsPro.configuration.ssr_timeout
304
- }
302
+ read_timeout: ReactOnRailsPro.configuration.ssr_timeout,
303
+ keep_alive_timeout: ReactOnRailsPro.configuration.renderer_http_keep_alive_timeout
304
+ }.compact
305
305
  )
306
306
  rescue StandardError => e
307
307
  message = <<~MSG
@@ -309,6 +309,7 @@ module ReactOnRailsPro
309
309
  renderer_http_pool_size = #{ReactOnRailsPro.configuration.renderer_http_pool_size}
310
310
  renderer_http_pool_timeout = #{ReactOnRailsPro.configuration.renderer_http_pool_timeout}
311
311
  renderer_http_pool_warn_timeout = #{ReactOnRailsPro.configuration.renderer_http_pool_warn_timeout}
312
+ renderer_http_keep_alive_timeout = #{ReactOnRailsPro.configuration.renderer_http_keep_alive_timeout}
312
313
  renderer_url = #{url}
313
314
  Be sure to use a url that contains the protocol of http or https.
314
315
  Original error is
@@ -74,9 +74,10 @@ module ReactOnRailsPro
74
74
  ReactOnRailsPro::Error.raise_duplicate_bundle_upload_error if send_bundle
75
75
 
76
76
  eval_js(js_code, render_options, send_bundle: true)
77
- when 400
77
+ when ReactOnRailsPro::STATUS_BAD_REQUEST
78
78
  raise ReactOnRailsPro::Error,
79
- "Renderer unhandled error at the VM level: #{response.status}:\n#{response.body}"
79
+ "Renderer rejected malformed request or hit an unhandled VM error: " \
80
+ "#{response.status}:\n#{response.body}"
80
81
  else
81
82
  raise ReactOnRailsPro::Error,
82
83
  "Unexpected response code from renderer: #{response.status}:\n#{response.body}"
@@ -140,6 +140,10 @@ module ReactOnRailsPro
140
140
  ReactOnRailsPro::Error.raise_duplicate_bundle_upload_error if send_bundle
141
141
 
142
142
  true
143
+ when ReactOnRailsPro::STATUS_BAD_REQUEST
144
+ raise ReactOnRailsPro::Error,
145
+ "Renderer rejected malformed request or hit an unhandled VM error: " \
146
+ "#{response.status}:\n#{error_body}"
143
147
  when ReactOnRailsPro::STATUS_INCOMPATIBLE
144
148
  raise ReactOnRailsPro::Error, error_body
145
149
  else
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ReactOnRailsPro
4
- VERSION = "16.5.1"
4
+ VERSION = "16.6.0.rc.1"
5
5
  PROTOCOL_VERSION = "2.0.0"
6
6
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: react_on_rails_pro
3
3
  version: !ruby/object:Gem::Version
4
- version: 16.5.1
4
+ version: 16.6.0.rc.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Justin Gordon
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-03-28 00:00:00.000000000 Z
11
+ date: 2026-04-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: addressable
@@ -128,14 +128,14 @@ dependencies:
128
128
  requirements:
129
129
  - - '='
130
130
  - !ruby/object:Gem::Version
131
- version: 16.5.1
131
+ version: 16.6.0.rc.1
132
132
  type: :runtime
133
133
  prerelease: false
134
134
  version_requirements: !ruby/object:Gem::Requirement
135
135
  requirements:
136
136
  - - '='
137
137
  - !ruby/object:Gem::Version
138
- version: 16.5.1
138
+ version: 16.6.0.rc.1
139
139
  - !ruby/object:Gem::Dependency
140
140
  name: bundler
141
141
  requirement: !ruby/object:Gem::Requirement