Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
14 changes: 7 additions & 7 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
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