Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
3c5c4b7
move CallbackRegistry to pro directory
AbanoubGhadban Sep 18, 2025
52b0915
move ComponentRegistry to pro directory
AbanoubGhadban Sep 18, 2025
0b3c322
move StoreRegistry to pro directory
AbanoubGhadban Sep 18, 2025
74d19ea
move client side renderer to pro directory
AbanoubGhadban Sep 18, 2025
001a433
Update licensing information and enhance Pro features integration
AbanoubGhadban Sep 18, 2025
c890168
Add proprietary licensing information to multiple Pro files
AbanoubGhadban Sep 18, 2025
444af62
Update package.json to reflect new Pro directory structure
AbanoubGhadban Sep 18, 2025
8a9b7cf
Refactor test imports to align with new Pro directory structure
AbanoubGhadban Sep 18, 2025
6e3aa5b
Update import paths in Pro directory to use .cts extensions
AbanoubGhadban Sep 18, 2025
0506496
Update test import path to reflect new Pro directory structure
AbanoubGhadban Sep 18, 2025
f91c629
fix rubocop errors
AbanoubGhadban Sep 18, 2025
09fb0fc
Refactor immediate hydration data attribute handling in Pro features
AbanoubGhadban Sep 18, 2025
7c0cd56
fix rubocop offenses
AbanoubGhadban Sep 18, 2025
7bf72be
Address PR review feedback: fix security issues and enhance Pro features
justin808 Sep 19, 2025
4c7f1de
Fix Pro file licensing headers: remove 'confidential' language
justin808 Sep 19, 2025
ed2ffaf
Refactor Pro helper methods and introduce utility for license validation
AbanoubGhadban Sep 19, 2025
c6c175f
rename force-load feature into immediate-hydrate feature
AbanoubGhadban Sep 19, 2025
c653805
removed unneeded package.oss.json file and unneeded tests
AbanoubGhadban Sep 19, 2025
d0f46c4
Add test setup for Pro features license validation
AbanoubGhadban Sep 19, 2025
8434b2d
Fix RuboCop offenses in Pro helper files
ihabadham Sep 19, 2025
6256ff1
more correction
ihabadham Sep 19, 2025
7fabaa8
Add Pro license files to indicate separate licensing
justin808 Sep 20, 2025
85b7530
Fix invalid directory reference in LICENSE.md
justin808 Sep 20, 2025
5539489
Rename Pro directory license files to NOTICE
justin808 Sep 20, 2025
819e538
Add CHANGELOG entry for Pro license structure implementation
justin808 Sep 20, 2025
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
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,21 @@ After a release, please make sure to run `bundle exec rake update_changelog`. Th

Changes since the last non-beta release.

#### Pro License Structure Implementation

**🔐 License Architecture**

- **Core/Pro separation**: Moved Pro features into dedicated `lib/react_on_rails/pro/` and `node_package/src/pro/` directories with clear licensing boundaries [PR 1791](https://github.com/shakacode/react_on_rails/pull/1791) by [abanoubghadban](https://github.com/AbanoubGhadban)
- **Runtime license validation**: Implemented Pro license gating with graceful fallback to core functionality when Pro license unavailable
- **License documentation**: Added NOTICE files in Pro directories referencing canonical `REACT-ON-RAILS-PRO-LICENSE.md`
- **Updated LICENSE.md**: Clearly distinguishes core MIT license from Pro-licensed directories

**⚡ Pro Feature Enhancements**

- **Immediate hydration**: Enhanced immediate hydration functionality with Pro license validation and warning badges
- **Security improvements**: Hardened DOM selectors using `CSS.escape()` and proper JavaScript escaping for XSS protection
- **Architecture refactoring**: Centralized Pro utilities and clean separation between core and Pro helper functionality

#### Enhanced TypeScript Generator Support

**🔧 Generator Improvements**
Expand Down
16 changes: 15 additions & 1 deletion LICENSE.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@

---

# MIT License
## MIT License for Core React on Rails

This license applies to all files within this repository, with the exception of the code located in the following directories, which are licensed separately under the React on Rails Pro License:

- `lib/react_on_rails/pro/`
- `node_package/src/pro/`

Copyright (c) 2017, 2018 Justin Gordon and ShakaCode
Copyright (c) 2015–2025 ShakaCode, LLC
Expand All @@ -31,3 +36,12 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

---

## React on Rails Pro License

The code in the directories listed above is part of the React on Rails Pro framework and is licensed under the React on Rails Pro License.

You can find the full text of the license agreement here:
[REACT-ON-RAILS-PRO-LICENSE.md](./REACT-ON-RAILS-PRO-LICENSE.md)
29 changes: 20 additions & 9 deletions knip.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,32 @@ const config: KnipConfig = {
'.': {
entry: [
'node_package/src/ReactOnRails.node.ts!',
'node_package/src/ReactOnRailsRSC.ts!',
'node_package/src/registerServerComponent/client.tsx!',
'node_package/src/registerServerComponent/server.tsx!',
'node_package/src/registerServerComponent/server.rsc.ts!',
'node_package/src/wrapServerComponentRenderer/server.tsx!',
'node_package/src/wrapServerComponentRenderer/server.rsc.tsx!',
'node_package/src/RSCRoute.tsx!',
'node_package/src/ServerComponentFetchError.ts!',
'node_package/src/pro/ReactOnRailsRSC.ts!',
'node_package/src/pro/registerServerComponent/client.tsx!',
'node_package/src/pro/registerServerComponent/server.tsx!',
'node_package/src/pro/registerServerComponent/server.rsc.ts!',
'node_package/src/pro/wrapServerComponentRenderer/server.tsx!',
'node_package/src/pro/wrapServerComponentRenderer/server.rsc.tsx!',
'node_package/src/pro/RSCRoute.tsx!',
'node_package/src/pro/ServerComponentFetchError.ts!',
'node_package/src/pro/getReactServerComponent.server.ts!',
'node_package/src/pro/transformRSCNodeStream.ts!',
'node_package/src/loadJsonFile.ts!',
'eslint.config.ts',
],
project: ['node_package/src/**/*.[jt]s{x,}!', 'node_package/tests/**/*.[jt]s{x,}'],
babel: {
config: ['node_package/babel.config.js'],
},
ignore: ['node_package/tests/emptyForTesting.js'],
ignore: [
'node_package/tests/emptyForTesting.js',
// Pro features exported for external consumption
'node_package/src/pro/streamServerRenderedReactComponent.ts:transformRenderStreamChunksToResultObject',
'node_package/src/pro/streamServerRenderedReactComponent.ts:streamServerRenderedComponent',
'node_package/src/pro/ServerComponentFetchError.ts:isServerComponentFetchError',
'node_package/src/pro/RSCRoute.tsx:RSCRouteProps',
'node_package/src/pro/streamServerRenderedReactComponent.ts:StreamingTrackers',
],
ignoreBinaries: [
// Knip fails to detect it's declared in devDependencies
'nps',
Expand Down
87 changes: 9 additions & 78 deletions lib/react_on_rails/helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,14 @@
require "react_on_rails/utils"
require "react_on_rails/json_output"
require "active_support/concern"
require "react_on_rails/pro/helper"

module ReactOnRails
module Helper
include ReactOnRails::Utils::Required
include ReactOnRails::Pro::Helper

COMPONENT_HTML_KEY = "componentHtml"
IMMEDIATE_HYDRATION_PRO_WARNING = "[REACT ON RAILS] The 'immediate_hydration' feature requires a " \
"React on Rails Pro license. " \
"Please visit https://shakacode.com/react-on-rails-pro to learn more."

# react_component_name: can be a React function or class component or a "Render-Function".
# "Render-Functions" differ from a React function in that they take two parameters, the
Expand Down Expand Up @@ -61,7 +60,6 @@ def react_component(component_name, options = {})
server_rendered_html = internal_result[:result]["html"]
console_script = internal_result[:result]["consoleReplayScript"]
render_options = internal_result[:render_options]
badge = pro_warning_badge_if_needed(internal_result[:immediate_hydration_requested])

case server_rendered_html
when String
Expand All @@ -71,7 +69,7 @@ def react_component(component_name, options = {})
console_script: console_script,
render_options: render_options
)
(badge + html).html_safe
html.html_safe
when Hash
msg = <<~MSG
Use react_component_hash (not react_component) to return a Hash to your ruby view code. See
Expand Down Expand Up @@ -218,21 +216,19 @@ def react_component_hash(component_name, options = {})
server_rendered_html = internal_result[:result]["html"]
console_script = internal_result[:result]["consoleReplayScript"]
render_options = internal_result[:render_options]
badge = pro_warning_badge_if_needed(internal_result[:immediate_hydration_requested])

if server_rendered_html.is_a?(String) && internal_result[:result]["hasErrors"]
server_rendered_html = { COMPONENT_HTML_KEY => internal_result[:result]["html"] }
end

if server_rendered_html.is_a?(Hash)
result = build_react_component_result_for_server_rendered_hash(
build_react_component_result_for_server_rendered_hash(
server_rendered_html: server_rendered_html,
component_specification_tag: internal_result[:tag],
console_script: console_script,
render_options: render_options
)
result[COMPONENT_HTML_KEY] = badge + result[COMPONENT_HTML_KEY]
result

else
msg = <<~MSG
Render-Function used by react_component_hash for #{component_name} is expected to return
Expand Down Expand Up @@ -260,8 +256,6 @@ def react_component_hash(component_name, options = {})
# hydrate this store immediately instead of waiting for the page to load.
def redux_store(store_name, props: {}, defer: false, immediate_hydration: nil)
immediate_hydration = ReactOnRails.configuration.immediate_hydration if immediate_hydration.nil?
badge = pro_warning_badge_if_needed(immediate_hydration)
immediate_hydration = false unless support_pro_features?

redux_store_data = { store_name: store_name,
props: props,
Expand All @@ -273,7 +267,7 @@ def redux_store(store_name, props: {}, defer: false, immediate_hydration: nil)
else
registered_stores << redux_store_data
result = render_redux_store_data(redux_store_data)
(badge + prepend_render_rails_context(result)).html_safe
prepend_render_rails_context(result).html_safe
end
end

Expand Down Expand Up @@ -452,33 +446,6 @@ def load_pack_for_generated_component(react_component_name, render_options)

# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity

# Checks if React on Rails Pro features are available
# @return [Boolean] true if Pro license is valid, false otherwise
def support_pro_features?
ReactOnRails::Utils.react_on_rails_pro_licence_valid?
end

def pro_warning_badge_if_needed(immediate_hydration)
return "".html_safe unless immediate_hydration
return "".html_safe if support_pro_features?

puts IMMEDIATE_HYDRATION_PRO_WARNING
Rails.logger.warn IMMEDIATE_HYDRATION_PRO_WARNING

tooltip_text = "The 'immediate_hydration' feature requires a React on Rails Pro license. Click to learn more."

badge_html = <<~HTML
<a href="https://shakacode.com/react-on-rails-pro" target="_blank" rel="noopener noreferrer" title="#{tooltip_text}">
<div style="position: fixed; top: 0; right: 0; width: 180px; height: 180px; overflow: hidden; z-index: 9999; pointer-events: none;">
<div style="position: absolute; top: 50px; right: -40px; transform: rotate(45deg); background-color: rgba(220, 53, 69, 0.85); color: white; padding: 7px 40px; text-align: center; font-weight: bold; font-family: sans-serif; font-size: 12px; box-shadow: 0 2px 8px rgba(0,0,0,0.3); pointer-events: auto;">
React On Rails Pro Required
</div>
</div>
</a>
HTML
badge_html.strip.html_safe
end

def run_stream_inside_fiber
unless ReactOnRails::Utils.react_on_rails_pro?
raise ReactOnRails::Error,
Expand Down Expand Up @@ -675,31 +642,10 @@ def internal_react_component(react_component_name, options = {})
# server has already rendered the HTML.

render_options = create_render_options(react_component_name, options)
# Capture the originally requested value so we can show a badge while still disabling the feature.
immediate_hydration_requested = render_options.immediate_hydration
render_options.set_option(:immediate_hydration, false) unless support_pro_features?

# Setup the page_loaded_js, which is the same regardless of prerendering or not!
# The reason is that React is smart about not doing extra work if the server rendering did its job.
component_specification_tag = content_tag(:script,
json_safe_and_pretty(render_options.client_props).html_safe,
type: "application/json",
class: "js-react-on-rails-component",
id: "js-react-on-rails-component-#{render_options.dom_id}",
"data-component-name" => render_options.react_component_name,
"data-trace" => (render_options.trace ? true : nil),
"data-dom-id" => render_options.dom_id,
"data-store-dependencies" => render_options.store_dependencies&.to_json,
"data-immediate-hydration" =>
(render_options.immediate_hydration ? true : nil))

if render_options.immediate_hydration
component_specification_tag.concat(
content_tag(:script, %(
typeof ReactOnRails === 'object' && ReactOnRails.reactOnRailsComponentLoaded('#{render_options.dom_id}');
).html_safe)
)
end
component_specification_tag = generate_component_script(render_options)

load_pack_for_generated_component(react_component_name, render_options)
# Create the HTML rendering part
Expand All @@ -708,27 +654,12 @@ def internal_react_component(react_component_name, options = {})
{
render_options: render_options,
tag: component_specification_tag,
result: result,
immediate_hydration_requested: immediate_hydration_requested
result: result
}
end

def render_redux_store_data(redux_store_data)
store_hydration_data = content_tag(:script,
json_safe_and_pretty(redux_store_data[:props]).html_safe,
type: "application/json",
"data-js-react-on-rails-store" => redux_store_data[:store_name].html_safe,
"data-immediate-hydration" =>
(redux_store_data[:immediate_hydration] ? true : nil))

if redux_store_data[:immediate_hydration]
store_hydration_data.concat(
content_tag(:script, <<~JS.strip_heredoc.html_safe
typeof ReactOnRails === 'object' && ReactOnRails.reactOnRailsStoreLoaded('#{redux_store_data[:store_name]}');
JS
)
)
end
store_hydration_data = generate_store_script(redux_store_data)

prepend_render_rails_context(store_hydration_data)
end
Expand Down
21 changes: 21 additions & 0 deletions lib/react_on_rails/pro/NOTICE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# React on Rails Pro License

The files in this directory and its subdirectories are licensed under the **React on Rails Pro** license, which is separate from the MIT license that covers the core React on Rails functionality.

## License Terms

These files are proprietary software and are **NOT** covered by the MIT license found in the root LICENSE.md file. Usage requires a valid React on Rails Pro license.

## Distribution

Files in this directory will be **omitted** from future distributions of the open source React on Rails Ruby gem. They are exclusively available to React on Rails Pro licensees.

## License Reference

For the complete React on Rails Pro license terms, see: `REACT-ON-RAILS-PRO-LICENSE.md` in the root directory of this repository.

## More Information

For React on Rails Pro licensing information and to obtain a license, please visit:
- [React on Rails Pro](https://www.shakacode.com/react-on-rails-pro/)
- Contact: [react_on_rails@shakacode.com](mailto:react_on_rails@shakacode.com)
Loading
Loading