Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/pro-lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ jobs:
script/ci-changes-detector "$BASE_REF"
shell: bash

lint-js-and-ruby:
pro-lint-js-and-ruby:
needs: detect-changes
if: github.ref == 'refs/heads/master' || needs.detect-changes.outputs.run_pro_lint == 'true'
runs-on: ubuntu-22.04
Expand Down Expand Up @@ -148,7 +148,7 @@ jobs:
run: cd spec/dummy && bundle exec rake react_on_rails:generate_packs

- name: Lint Ruby
run: bundle exec rubocop
run: bundle exec rubocop --ignore-parent-exclusion

- name: Validate RBS type signatures
run: bundle exec rake rbs:validate
Expand Down
17 changes: 2 additions & 15 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,7 @@ tmp/

node_modules

/packages/*/lib

# TypeScript build artifacts in src (shouldn't be there, but just in case)
/packages/*/src/**/*.js
/packages/*/src/**/*.d.ts
/packages/*/src/**/*.d.cts
/packages/*/src/**/*.cjs
/packages/*/src/**/*.map
!/packages/*/src/**/*.test.js
!/packages/*/src/**/*.spec.js
/node_package/lib

yarn-debug.*
yarn-error.*
Expand Down Expand Up @@ -75,8 +66,4 @@ ssr-generated

# Claude Code local settings
.claude/settings.local.json
.claude/.fuse_hidden*

# Playwright test artifacts (from cypress-on-rails gem)
/spec/dummy/e2e/playwright-report/
/spec/dummy/test-results/
packages/
18 changes: 9 additions & 9 deletions react_on_rails_pro/lib/react_on_rails_pro/license_public_key.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,15 @@ module LicensePublicKey
# TODO: Add a prepublish check to ensure this key matches the latest public key from the API.
# This should be implemented after publishing the API endpoint on the ShakaCode website.
KEY = OpenSSL::PKey::RSA.new(<<~PEM.strip.strip_heredoc)
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzcS/fpHz5CbnTQxb4Zot
khjzXu7xNS+Y9VKfapMaHOMzNoCMfy1++hxHJatRedr+YQfZRCjfiN168Cpe+dhe
yfNtOoLU9/+/5jTsxH+WQJWNRswyKms5HNajlIMN1GEYdZmZbvOPaZvh6ENsT+EV
HnhjJtsHl7qltBoL0ul7rONxaNHCzJcKk4lf3B2/1j1wpA91MKz4bbQVh4/6Th0E
/39f0PWvvBXzQS+yt1qaa1DIX5YL6Aug5uEpb1+6QWcN3hCzqSPBv1HahrG50rsD
gf8KORV3X2N9t6j6iqPmRqfRcTBKtmPhM9bORtKiSwBK8LsIUzp2/UUmkdHnkyzu
NQIDAQAB
-----END PUBLIC KEY-----
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzcS/fpHz5CbnTQxb4Zot
khjzXu7xNS+Y9VKfapMaHOMzNoCMfy1++hxHJatRedr+YQfZRCjfiN168Cpe+dhe
yfNtOoLU9/+/5jTsxH+WQJWNRswyKms5HNajlIMN1GEYdZmZbvOPaZvh6ENsT+EV
HnhjJtsHl7qltBoL0ul7rONxaNHCzJcKk4lf3B2/1j1wpA91MKz4bbQVh4/6Th0E
/39f0PWvvBXzQS+yt1qaa1DIX5YL6Aug5uEpb1+6QWcN3hCzqSPBv1HahrG50rsD
gf8KORV3X2N9t6j6iqPmRqfRcTBKtmPhM9bORtKiSwBK8LsIUzp2/UUmkdHnkyzu
NQIDAQAB
-----END PUBLIC KEY-----
PEM
end
end
55 changes: 32 additions & 23 deletions react_on_rails_pro/lib/react_on_rails_pro/request.rb
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ def common_form_data
ReactOnRailsPro::Utils.common_form_data
end

def create_connection
def create_connection # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
url = ReactOnRailsPro.configuration.renderer_url
Rails.logger.info do
"[ReactOnRailsPro] Setting up Node Renderer connection to #{url}"
Expand All @@ -229,28 +229,37 @@ def create_connection
# https://honeyryderchuck.gitlab.io/httpx/wiki/Persistent
.plugin(
:retries, max_retries: 1,
retry_change_requests: true,
# Official HTTPx docs says that we should use the retry_on option to decide if teh request should be retried or not
# However, HTTPx assumes that connection errors such as timeout error should be retried by default and it doesn't consider retry_on block at all at that case
# So, we have to do the following trick to avoid retries when a Timeout error happens while streaming a component
# If the streamed component returned any chunks, it shouldn't retry on errors, as it would cause page duplication
# The SSR-generated html will be written to the page two times in this case
retry_after: ->(request, response) do
if (request.stream.instance_variable_get(:@react_on_rails_received_first_chunk))
e = response.error
raise ReactOnRailsPro::Error, "An error happened during server side render streaming of a component.\n" \
"Original error:\n#{e}\n#{e.backtrace}"
end

Rails.logger.info do
"[ReactOnRailsPro] An error happneding while making a request to the Node Renderer.\n" \
"Error: #{response.error}.\n" \
"Retrying by HTTPX \"retries\" plugin..."
end
# The retry_after block expects to return a delay to wait before retrying the request
# nil means no waiting delay
nil
end
retry_change_requests: true,
# Official HTTPx docs says that we should use the retry_on option to decide if the
# request should be retried or not
# However, HTTPx assumes that connection errors such as timeout error should be retried
# by default and it doesn't consider retry_on block at all at that case
# So, we have to do the following trick to avoid retries when a Timeout error happens
# while streaming a component
# If the streamed component returned any chunks, it shouldn't retry on errors, as it
# would cause page duplication
# The SSR-generated html will be written to the page two times in this case
retry_after: lambda do |request, response|
if request.stream.instance_variable_get(:@react_on_rails_received_first_chunk)
e = response.error
raise(
ReactOnRailsPro::Error,
"An error happened during server side render streaming " \
"of a component.\nOriginal error:\n#{e}\n#{e.backtrace}"
)
end

Rails.logger.info do
"[ReactOnRailsPro] An error occurred while making " \
"a request to the Node Renderer.\n" \
"Error: #{response.error}.\n" \
"Retrying by HTTPX \"retries\" plugin..."
end
# The retry_after block expects to return a delay to wait before
# retrying the request
# nil means no waiting delay
nil
end
)
.plugin(:stream)
# See https://www.rubydoc.info/gems/httpx/1.3.3/HTTPX%2FOptions:initialize for the available options
Expand Down
10 changes: 5 additions & 5 deletions react_on_rails_pro/lib/react_on_rails_pro/stream_request.rb
Original file line number Diff line number Diff line change
Expand Up @@ -51,23 +51,23 @@ def handle_chunk(chunk, position)
end
end

def each_chunk(&block)
def each_chunk(&block) # rubocop:disable Metrics/CyclomaticComplexity
return enum_for(:each_chunk) unless block

first_chunk = true
@component.each_chunk do |chunk|
position = first_chunk ? :first : :middle
modified_chunk = handle_chunk(chunk, position)
block.call(modified_chunk)
yield(modified_chunk)
first_chunk = false
end

# The last chunk contains the append content after the transformation
# All transformations are applied to the append content
last_chunk = handle_chunk("", :last)
block.call(last_chunk) unless last_chunk.empty?
rescue StandardError => err
current_error = err
yield(last_chunk) unless last_chunk.empty?
rescue StandardError => e
current_error = e
rescue_block_index = 0
while current_error.present? && (rescue_block_index < @rescue_blocks.size)
begin
Expand Down
11 changes: 6 additions & 5 deletions react_on_rails_pro/rakelib/public_key_management.rake
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ require "uri"
# rake react_on_rails_pro:verify_public_key # Verify current configuration
# rake react_on_rails_pro:public_key_help # Show help

namespace :react_on_rails_pro do
namespace :react_on_rails_pro do # rubocop:disable Metrics/BlockLength
desc "Update the public key for React on Rails Pro license validation"
task :update_public_key, [:source] do |_task, args|
task :update_public_key, [:source] do |_task, args| # rubocop:disable Metrics/BlockLength
source = args[:source] || "production"

# Determine the API URL based on the source
Expand Down Expand Up @@ -68,7 +68,7 @@ namespace :react_on_rails_pro do
# ShakaCode's public key for React on Rails Pro license verification
# The private key corresponding to this public key is held by ShakaCode
# and is never committed to the repository
# Last updated: #{Time.now.utc.strftime("%Y-%m-%d %H:%M:%S UTC")}
# Last updated: #{Time.now.utc.strftime('%Y-%m-%d %H:%M:%S UTC')}
# Source: #{api_url}
#
# You can update this public key by running the rake task:
Expand All @@ -86,12 +86,13 @@ namespace :react_on_rails_pro do
puts "✅ Updated Ruby public key: #{ruby_file_path}"

# Update Node/TypeScript public key file
node_file_path = File.join(File.dirname(__FILE__), "..", "packages", "node-renderer", "src", "shared", "licensePublicKey.ts")
node_file_path = File.join(File.dirname(__FILE__), "..", "packages", "node-renderer", "src", "shared",
"licensePublicKey.ts")
node_content = <<~TYPESCRIPT
// ShakaCode's public key for React on Rails Pro license verification
// The private key corresponding to this public key is held by ShakaCode
// and is never committed to the repository
// Last updated: #{Time.now.utc.strftime("%Y-%m-%d %H:%M:%S UTC")}
// Last updated: #{Time.now.utc.strftime('%Y-%m-%d %H:%M:%S UTC')}
// Source: #{api_url}
//
// You can update this public key by running the rake task:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# frozen_string_literal: true

class PagesController < ApplicationController
class PagesController < ApplicationController # rubocop:disable Metrics/ClassLength
include ReactOnRailsPro::RSCPayloadRenderer
include RscPostsPageOverRedisHelper

Expand Down Expand Up @@ -85,8 +85,8 @@ def redis_receiver
ensure
begin
redis&.close
rescue StandardError => close_err
Rails.logger.warn "Failed to close Redis: #{close_err.message}"
rescue StandardError => e
Rails.logger.warn "Failed to close Redis: #{e.message}"
end
end

Expand Down
2 changes: 1 addition & 1 deletion react_on_rails_pro/spec/dummy/config.ru
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@

# This file is used by Rack-based servers to start the application.

require ::File.expand_path("config/environment", __dir__)
require File.expand_path("config/environment", __dir__)

run Rails.application
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@
config.active_support.deprecation = :notify

# Use default logging formatter so that PID and timestamp are not suppressed.
config.log_formatter = ::Logger::Formatter.new
config.log_formatter = Logger::Formatter.new

# Use a different logger for distributed setups.
# require 'syslog/logger'
Expand Down
2 changes: 1 addition & 1 deletion react_on_rails_pro/spec/dummy/spec/rails_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@

# Remove this line if you're not using ActiveRecord or ActiveRecord fixtures
# For React on Rails Pro, using loadable-stats.json
config.fixture_paths = ["#{::Rails.root}/spec/fixtures"]
config.fixture_paths = ["#{Rails.root}/spec/fixtures"]

# If you're not using ActiveRecord, or you'd prefer not to run each of your
# examples within a transaction, remove the following line or assign false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@
html_nodes = Nokogiri::HTML(response.body)
expected = <<~JS
console.log.apply(console, ["[SERVER] RENDERED ReduxSharedStoreApp to dom node with id: ReduxSharedStoreApp-react-component-0"]);
console.log.apply(console, ["[SERVER] This is a script:\\\"</div>\\\"(/script> <script>alert('WTF1')(/script>"]);
console.log.apply(console, ["[SERVER] Script2:\\\"</div>\\\"(/script xx> <script>alert('WTF2')(/script xx>"]);
console.log.apply(console, ["[SERVER] Script3:\\\"</div>\\\"(/script xx> <script>alert('WTF3')(/script xx>"]);
console.log.apply(console, ["[SERVER] Script4\\\"</div>\\\"(/script <script>alert('WTF4')(/script>"]);
console.log.apply(console, ["[SERVER] Script5:\\\"</div>\\\"(/script> <script>alert('WTF5')(/script>"]);
console.log.apply(console, ["[SERVER] This is a script:\\"</div>\\"(/script> <script>alert('WTF1')(/script>"]);
console.log.apply(console, ["[SERVER] Script2:\\"</div>\\"(/script xx> <script>alert('WTF2')(/script xx>"]);
console.log.apply(console, ["[SERVER] Script3:\\"</div>\\"(/script xx> <script>alert('WTF3')(/script xx>"]);
console.log.apply(console, ["[SERVER] Script4\\"</div>\\"(/script <script>alert('WTF4')(/script>"]);
console.log.apply(console, ["[SERVER] Script5:\\"</div>\\"(/script> <script>alert('WTF5')(/script>"]);
console.log.apply(console, ["[SERVER] railsContext.serverSide is ","true"]);
JS

Expand Down
16 changes: 8 additions & 8 deletions react_on_rails_pro/spec/dummy/spec/system/integration_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ def change_text_expect_dom_selector(dom_selector, expect_no_change: false)
it "changes name in message according to input" do
visit "/client_side_hello_world"
change_text_expect_dom_selector("#HelloWorld-react-component-0")
click_link "Hello World Component Server Rendered, with extra options"
click_on "Hello World Component Server Rendered, with extra options"
change_text_expect_dom_selector("#my-hello-world-id")
end
end
Expand Down Expand Up @@ -174,19 +174,19 @@ def change_text_expect_dom_selector(dom_selector, expect_no_change: false)

before do
visit "/"
click_link "React Router"
click_on "React Router"
end

context "when rendering /react_router" do
it { is_expected.to have_text("Woohoo, we can use react-router here!") }

it "clicking links correctly renders other pages" do
click_link "Router First Page"
click_on "Router First Page"
expect(page).to have_current_path("/react_router/first_page")
first_page_header_text = page.find(:css, "h2#first-page").text
expect(first_page_header_text).to eq("React Router First Page")

click_link "Router Second Page"
click_on "Router Second Page"
expect(page).to have_current_path("/react_router/second_page")
second_page_header_text = page.find(:css, "h2#second-page").text
expect(second_page_header_text).to eq("React Router Second Page")
Expand Down Expand Up @@ -244,7 +244,7 @@ def change_text_expect_dom_selector(dom_selector, expect_no_change: false)

it "HelloWorldRehydratable onChange should trigger" do
within("form") do
click_button "refresh"
click_on "refresh"
end
within("#HelloWorldRehydratable-react-component-1") do
find("input").set "Should update"
Expand Down Expand Up @@ -396,14 +396,14 @@ def change_text_expect_dom_selector(dom_selector, expect_no_change: false)

it "hydrates the component" do
visit path
expect(page.html).to match(/client-bundle[^\"]*.js/)
expect(page.html).to match(/client-bundle[^"]*.js/)
change_text_expect_dom_selector(selector)
end

it "renders the page completely on server and displays content on client even without JavaScript" do
# Don't add client-bundle.js to the page to ensure that the app is not hydrated
visit "#{path}?skip_js_packs=true"
expect(page.html).not_to match(/client-bundle[^\"]*.js/)
expect(page.html).not_to match(/client-bundle[^"]*.js/)
# Ensure that the component state is not updated
change_text_expect_dom_selector(selector, expect_no_change: true)

Expand Down Expand Up @@ -432,7 +432,7 @@ def change_text_expect_dom_selector(dom_selector, expect_no_change: false)
"#ServerComponentRouter-react-component-0"

# Skip the test that fails without JavaScript - being addressed in another PR
it "renders the page completely on server and displays content on client even without JavaScript",
it "renders the page completely on server and displays content on client even without JavaScript", # rubocop:disable RSpec/NoExpectationExample
skip: "Being addressed in another PR" do
# This test is overridden to skip it
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@

context "with enabled JS", :js do
it "Has correct heading and text inside the text input" do
expect(page).to have_selector("h3", text: /\ARedux Hello, Mr. Server Side Rendering!\z/)
expect(page).to have_selector("input[type='text'][value='Mr. Server Side Rendering']")
expect(page).to have_css("h3", text: /\ARedux Hello, Mr. Server Side Rendering!\z/)
expect(page).to have_css("input[type='text'][value='Mr. Server Side Rendering']")
end

it "updates header in reaction to text input changes" do
new_value = "new value"
all("input[type='text']")[0].set(new_value)
expect(page).to have_selector("h3", text: /\ARedux Hello, #{new_value}!\z/)
expect(page).to have_css("h3", text: /\ARedux Hello, #{new_value}!\z/)
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@

# Log to STDOUT by default
config.logger = ActiveSupport::Logger.new($stdout)
.tap { |logger| logger.formatter = ::Logger::Formatter.new }
.tap { |logger| logger.formatter = Logger::Formatter.new }
.then { |logger| ActiveSupport::TaggedLogging.new(logger) }

# Prepend all log lines with the following tags.
Expand Down
Loading
Loading