-
-
Notifications
You must be signed in to change notification settings - Fork 633
feat: Add automatic license renewal for gem and Node Renderer #2254
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
WalkthroughThis PR introduces automatic license renewal for React on Rails Pro by adding persistent token caching, API-based license fetching with retry logic, intelligent refresh scheduling based on expiry dates, and async validation integration across both TypeScript and Ruby implementations with comprehensive test coverage. Changes
Sequence Diagram(s)sequenceDiagram
participant App as App/Renderer
participant Validator as LicenseValidator
participant Refresher as LicenseRefreshChecker
participant Fetcher as LicenseFetcher
participant Cache as LicenseCache
participant API as License API
participant Env as ENV/Config
App->>Validator: getValidatedLicenseData()
activate Validator
Validator->>Refresher: maybeRefreshLicense()
activate Refresher
Refresher->>Refresher: shouldCheckForRefresh()?
alt Refresh Needed
Refresher->>Fetcher: fetchLicense()
activate Fetcher
Fetcher->>Env: Get license_key
Fetcher->>API: GET /api/license<br/>(5s timeout, retries)
alt Success (200)
API-->>Fetcher: {token, expires_at}
Fetcher-->>Refresher: LicenseResponse
else Failure
API-->>Fetcher: Error/Timeout
Fetcher-->>Refresher: null
end
deactivate Fetcher
alt Token Received
Refresher->>Cache: writeCache({token, expires_at})
activate Cache
Cache->>Cache: Compute license_key_hash
Cache->>Env: Write to disk (0600 perms)
deactivate Cache
end
end
Refresher-->>Validator: (void)
deactivate Refresher
rect rgb(200, 220, 255)
Note over Validator: Load & Validate License
Validator->>Cache: getCachedToken()<br/>(if auto-refresh enabled)
alt Cache Hit
Cache-->>Validator: token
else Cache Miss
Validator->>Env: Read ENV/Config file
Env-->>Validator: token
end
end
Validator->>Validator: decode & validate JWT
Validator->>Cache: writeCache() for seeding
Validator-->>App: LicenseData
deactivate Validator
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
📜 Recent review detailsConfiguration used: Organization UI Review profile: CHILL Plan: Pro ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (2)
🧰 Additional context used🧠 Learnings (4)📓 Common learnings📚 Learning: 2025-10-23T17:22:01.074ZApplied to files:
📚 Learning: 2024-12-12T13:07:09.929ZApplied to files:
📚 Learning: 2025-12-19T18:57:59.314ZApplied to files:
🧬 Code graph analysis (1)packages/react-on-rails-pro-node-renderer/src/shared/licenseFetcher.ts (3)
🪛 Gitleaks (8.30.0)packages/react-on-rails-pro-node-renderer/tests/licenseFetcher.test.ts[high] 61-61: Detected a Generic API Key, potentially exposing access to various services and sensitive operations. (generic-api-key) [high] 180-180: Detected a Generic API Key, potentially exposing access to various services and sensitive operations. (generic-api-key) ⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (12)
🔇 Additional comments (4)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Code Review: Automatic License Renewal FeatureThanks for this comprehensive implementation! This is a well-thought-out feature with solid test coverage. Here's my detailed review: ✅ Strengths1. Excellent Architecture
2. Strong Test Coverage
3. Security Considerations
4. Good User Experience
🔍 Issues & RecommendationsCRITICAL: Security - License Key StorageRuby gem ( "Authorization" => "Bearer #{license_key}"Potential Issue: The license_key is used directly in HTTP headers. If logging middleware captures request headers, the permanent license key could be logged. Recommendation:
HIGH: Cache File Location - Ephemeral StorageRuby ( def cache_dir
Rails.root.join("tmp")
endIssue: On cloud platforms (Heroku, AWS Elastic Beanstalk, etc.), the Impact:
Recommendation: # Use a persistent location in production
def cache_dir
if Rails.env.production? && ENV['REACT_ON_RAILS_PRO_LICENSE_CACHE_DIR']
Pathname.new(ENV['REACT_ON_RAILS_PRO_LICENSE_CACHE_DIR'])
else
Rails.root.join("tmp")
end
endOr document clearly that users should configure persistent storage for the cache in production. MEDIUM: Race Condition in Cache SeedingRuby ( def seed_cache_if_needed(license_data)
return unless ReactOnRailsPro.configuration.auto_refresh_enabled?
return if LicenseCache.token.present? # Cache already exists
token = load_token_from_env_or_file
# ... write cache
endIssue: In multi-process deployments (Puma, Passenger), multiple workers could simultaneously check Impact:
Recommendation: # Add file locking
File.open(cache_path, File::RDWR|File::CREAT, 0600) do |file|
file.flock(File::LOCK_EX)
# Check again after acquiring lock
return if LicenseCache.token.present?
# ... write cache
endMEDIUM: Error Handling - Missing ValidationRuby ( Rails.logger.debug { "[ReactOnRailsPro] License fetched successfully" }
JSON.parse(response.body.to_s)Issue: No validation that the response contains required fields ( Impact: If API response format changes, invalid data could be cached. Recommendation: data = JSON.parse(response.body.to_s)
return nil unless data.is_a?(Hash) && data["token"].present? && data["expires_at"].present?
dataMEDIUM: TypeScript - Async Timing IssueTypeScript ( The refresh check in Question: How does the async refresh integrate with startup validation? If the app starts while a refresh is in-progress, does it use stale cache? Recommendation:
LOW: Code Quality - Magic NumbersRuby ( days_until_expiry = ((expires_at - Time.now) / 1.day).to_iNote: Uses DAILY_CHECK_THRESHOLD_DAYS = 7
WEEKLY_CHECK_THRESHOLD_DAYS = 30
if days_until_expiry <= DAILY_CHECK_THRESHOLD_DAYS
# ...Improves readability and makes thresholds configurable in the future. LOW: Documentation - Missing API ContractThe PR description mentions "Requires licensing app PR with /api/license endpoint" but: Missing:
Recommendation: Add API contract documentation so users understand the endpoint behavior. 🧪 Testing ObservationsGood Coverage:
Missing Tests:
📝 Documentation FeedbackLICENSE_SETUP.md is excellent! Clear distinction between license_key and license_token. Suggestions:
🎯 Summary
Overall: This is solid work with good architecture and test coverage. The critical and high issues are configuration/documentation problems rather than code bugs, so they're easy to address. Recommendation: ✅ Approve with changes - address the cache storage location and add documentation about logging risks before merging. 🚀 Pre-Merge ChecklistBefore merging, verify:
Great work on this feature! 🎉 |
size-limit report 📦
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
🧹 Nitpick comments (7)
packages/react-on-rails-pro-node-renderer/tests/licenseValidator.test.ts (1)
11-21: Consider consolidating mock setup.The auto-refresh modules are mocked twice: once at the top level (lines 11-21) and again in
beforeEach(lines 83-93). While this is necessary due tojest.resetModules(), consider extracting the mock definitions to a helper function to reduce duplication and ensure consistency.🔎 Proposed refactor to reduce duplication
+function setupAutoRefreshMocks() { + jest.doMock('../src/shared/licenseFetcher', () => ({ + isAutoRefreshEnabled: jest.fn().mockReturnValue(false), + })); + jest.doMock('../src/shared/licenseCache', () => ({ + getCachedToken: jest.fn().mockReturnValue(null), + })); + jest.doMock('../src/shared/licenseRefreshChecker', () => ({ + maybeRefreshLicense: jest.fn().mockResolvedValue(undefined), + seedCacheIfNeeded: jest.fn(), + })); +} + // Mock auto-refresh modules to prevent side effects -jest.mock('../src/shared/licenseFetcher', () => ({ - isAutoRefreshEnabled: jest.fn().mockReturnValue(false), -})); -jest.mock('../src/shared/licenseCache', () => ({ - getCachedToken: jest.fn().mockReturnValue(null), -})); -jest.mock('../src/shared/licenseRefreshChecker', () => ({ - maybeRefreshLicense: jest.fn().mockResolvedValue(undefined), - seedCacheIfNeeded: jest.fn(), -})); +setupAutoRefreshMocks(); // ... in beforeEach ... // Re-mock auto-refresh modules after resetModules - jest.doMock('../src/shared/licenseFetcher', () => ({ - isAutoRefreshEnabled: jest.fn().mockReturnValue(false), - })); - jest.doMock('../src/shared/licenseCache', () => ({ - getCachedToken: jest.fn().mockReturnValue(null), - })); - jest.doMock('../src/shared/licenseRefreshChecker', () => ({ - maybeRefreshLicense: jest.fn().mockResolvedValue(undefined), - seedCacheIfNeeded: jest.fn(), - })); + setupAutoRefreshMocks();Also applies to: 83-93
react_on_rails_pro/lib/react_on_rails_pro/license_fetcher.rb (1)
28-29: Non-200 responses silently return nil.The implementation returns
nilfor all non-200 status codes (401, 403, 404, 500, etc.) without distinguishing between client errors (4xx) and server errors (5xx). While this provides a graceful fallback, it may make debugging difficult.Consider logging the status code for non-200 responses to aid troubleshooting.
🔎 Proposed enhancement for debugging
response = HTTPX .plugin(:retries, max_retries: MAX_RETRIES, retry_after: RETRY_DELAY_SECONDS) .with(timeout: { request_timeout: REQUEST_TIMEOUT_SECONDS }) .with(headers: { "Authorization" => "Bearer #{license_key}", "User-Agent" => "ReactOnRailsPro-Gem" }) .get("#{api_url}/api/license") return nil if response.is_a?(HTTPX::ErrorResponse) - return nil unless response.status == 200 + unless response.status == 200 + Rails.logger.warn { "[ReactOnRailsPro] License fetch failed: HTTP #{response.status}" } + return nil + end Rails.logger.debug { "[ReactOnRailsPro] License fetched successfully" } JSON.parse(response.body.to_s)packages/react-on-rails-pro-node-renderer/src/shared/licenseValidator.ts (1)
89-120: Cache-first loading may mask ENV/file changes.When auto-refresh is enabled, the cache takes precedence over ENV and config file (lines 91-96). This means if a user updates
REACT_ON_RAILS_PRO_LICENSEor the config file, the cached token will still be used until it expires or the cache is cleared.Consider documenting this behavior, or provide a way to force cache invalidation (e.g., deleting the cache file) for users who need to switch licenses immediately.
react_on_rails_pro/lib/react_on_rails_pro/license_cache.rb (1)
35-36: Consider atomic file write with secure permissions.There's a small race window between
File.writeandFile.chmodwhere the file could be readable by others (depending on umask). For a security-sensitive license file, consider using atomic write with initial permissions.🔎 Proposed fix for atomic write with secure permissions
- File.write(cache_path, JSON.pretty_generate(cache_data)) - File.chmod(0o600, cache_path) + require "tempfile" + Tempfile.create(["license_cache", ".tmp"], cache_dir, mode: 0o600) do |temp| + temp.write(JSON.pretty_generate(cache_data)) + temp.close + FileUtils.mv(temp.path, cache_path) + endreact_on_rails_pro/lib/react_on_rails_pro/configuration.rb (1)
65-68: Track the TODO for production URL update.There's a TODO comment indicating the licensing app URL may need updating. Ensure this is addressed before the final release.
Do you want me to open an issue to track updating the license API URL once the licensing app is deployed to production?
packages/react-on-rails-pro-node-renderer/src/shared/licenseCache.ts (2)
150-161: Consider caching the result to avoid redundant file reads.
getFetchedAt()andgetExpiresAt()each callreadCache()independently. If called together (which seems likely), this results in multiple file reads. Consider either exposingreadCache()for callers to use directly, or caching the result.Additionally, the empty
catchblock on lines 158-159 silently swallows date parsing errors. While returningnullis reasonable, logging might help debugging.
128-131: Consider atomic write with secure permissions.Similar to the Ruby implementation, there's a small race window between
writeFileSyncandchmodSync. For improved security, consider writing to a temp file first and then renaming.🔎 Proposed fix for atomic write
+import * as os from 'os'; ... - fs.writeFileSync(cachePath, JSON.stringify(cacheData, null, 2)); - - // Set file permissions to 0600 (owner read/write only) - fs.chmodSync(cachePath, 0o600); + // Write atomically with secure permissions + const tempPath = path.join(cacheDir, `.license_cache_${process.pid}.tmp`); + fs.writeFileSync(tempPath, JSON.stringify(cacheData, null, 2), { mode: 0o600 }); + fs.renameSync(tempPath, cachePath);
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (27)
packages/react-on-rails-pro-node-renderer/package.jsonpackages/react-on-rails-pro-node-renderer/src/ReactOnRailsProNodeRenderer.tspackages/react-on-rails-pro-node-renderer/src/master.tspackages/react-on-rails-pro-node-renderer/src/shared/licenseCache.tspackages/react-on-rails-pro-node-renderer/src/shared/licenseFetcher.tspackages/react-on-rails-pro-node-renderer/src/shared/licenseRefreshChecker.tspackages/react-on-rails-pro-node-renderer/src/shared/licenseValidator.tspackages/react-on-rails-pro-node-renderer/tests/licenseCache.test.tspackages/react-on-rails-pro-node-renderer/tests/licenseFetcher.test.tspackages/react-on-rails-pro-node-renderer/tests/licenseRefreshChecker.test.tspackages/react-on-rails-pro-node-renderer/tests/licenseValidator.test.tsreact_on_rails_pro/LICENSE_SETUP.mdreact_on_rails_pro/README.mdreact_on_rails_pro/docs/configuration.mdreact_on_rails_pro/docs/installation.mdreact_on_rails_pro/lib/react_on_rails_pro.rbreact_on_rails_pro/lib/react_on_rails_pro/configuration.rbreact_on_rails_pro/lib/react_on_rails_pro/license_cache.rbreact_on_rails_pro/lib/react_on_rails_pro/license_fetcher.rbreact_on_rails_pro/lib/react_on_rails_pro/license_refresh_checker.rbreact_on_rails_pro/lib/react_on_rails_pro/license_validator.rbreact_on_rails_pro/spec/dummy/config/initializers/react_on_rails_pro.rbreact_on_rails_pro/spec/react_on_rails_pro/configuration_spec.rbreact_on_rails_pro/spec/react_on_rails_pro/license_cache_spec.rbreact_on_rails_pro/spec/react_on_rails_pro/license_fetcher_spec.rbreact_on_rails_pro/spec/react_on_rails_pro/license_validator_spec.rbreact_on_rails_pro/spec/react_on_rails_pro/spec_helper.rb
🧰 Additional context used
📓 Path-based instructions (2)
{package.json,webpack.config.js,packages/*/package.json,react_on_rails_pro/package.json}
📄 CodeRabbit inference engine (CLAUDE.md)
When resolving merge conflicts in build configuration files, verify file paths are correct by running
grep -r 'old/path' .and test affected scripts likepnpm run prepackbefore continuing the merge
Files:
packages/react-on-rails-pro-node-renderer/package.json
**/*.rb
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.rb: Runbundle exec rubocopand fix ALL violations before every commit/push
Runbundle exec rubocop(MANDATORY) before every commit to ensure zero offenses
Files:
react_on_rails_pro/spec/react_on_rails_pro/license_validator_spec.rbreact_on_rails_pro/lib/react_on_rails_pro/license_cache.rbreact_on_rails_pro/spec/react_on_rails_pro/license_fetcher_spec.rbreact_on_rails_pro/spec/react_on_rails_pro/license_cache_spec.rbreact_on_rails_pro/spec/react_on_rails_pro/spec_helper.rbreact_on_rails_pro/spec/dummy/config/initializers/react_on_rails_pro.rbreact_on_rails_pro/lib/react_on_rails_pro.rbreact_on_rails_pro/spec/react_on_rails_pro/configuration_spec.rbreact_on_rails_pro/lib/react_on_rails_pro/configuration.rbreact_on_rails_pro/lib/react_on_rails_pro/license_validator.rbreact_on_rails_pro/lib/react_on_rails_pro/license_refresh_checker.rbreact_on_rails_pro/lib/react_on_rails_pro/license_fetcher.rb
🧠 Learnings (25)
📓 Common learnings
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: Applies to /CHANGELOG_PRO.md : Update `/CHANGELOG_PRO.md` for user-visible changes in the Pro-only react_on_rails_pro gem and npm packages
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1875
File: lib/react_on_rails/utils.rb:112-124
Timestamp: 2025-10-23T17:22:01.074Z
Learning: In React on Rails, when Pro is installed but not licensed, the intended behavior is to raise an error on boot. The `react_on_rails_pro?` method validates licenses and should raise errors early (including during path resolution in methods like `server_bundle?`) to enforce licensing requirements rather than failing later with obscure errors.
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1781
File: node_package/src/ClientSideRenderer.ts:82-95
Timestamp: 2025-09-15T21:24:48.207Z
Learning: In React on Rails, the force_load feature includes both explicit `data-force-load="true"` usage and the ability to hydrate components during the page loading state (`document.readyState === 'loading'`). Both capabilities require a Pro license, so the condition `!railsContext.rorPro && (isComponentForceLoaded || document.readyState === 'loading')` correctly gates both scenarios.
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: Applies to /CHANGELOG.md : Update `/CHANGELOG.md` for user-visible changes in the open-source react_on_rails gem and npm package (features, bug fixes, breaking changes, deprecations, performance improvements)
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1644
File: lib/react_on_rails/helper.rb:190-197
Timestamp: 2025-02-18T13:08:01.477Z
Learning: RSC support validation in React on Rails Pro is handled through a chain of validations:
1. Pro version check in `run_stream_inside_fiber`
2. RSC support check during pack generation via `ReactOnRailsPro.configuration.enable_rsc_support`
3. RSC support validation during component registration
This makes additional validation in the helper methods unnecessary.
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: Applies to react_on_rails/spec/dummy/e2e/playwright/e2e/**/*.spec.js : Use Playwright E2E tests in `react_on_rails/spec/dummy/e2e/playwright/` for React component integration testing. Tests automatically start Rails server on port 5017 before running
📚 Learning: 2025-12-19T18:57:59.314Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: Applies to /CHANGELOG_PRO.md : Update `/CHANGELOG_PRO.md` for user-visible changes in the Pro-only react_on_rails_pro gem and npm packages
Applied to files:
react_on_rails_pro/docs/installation.mdpackages/react-on-rails-pro-node-renderer/package.jsonreact_on_rails_pro/spec/react_on_rails_pro/license_validator_spec.rbpackages/react-on-rails-pro-node-renderer/src/master.tspackages/react-on-rails-pro-node-renderer/src/ReactOnRailsProNodeRenderer.tsreact_on_rails_pro/lib/react_on_rails_pro/license_cache.rbpackages/react-on-rails-pro-node-renderer/src/shared/licenseValidator.tsreact_on_rails_pro/spec/react_on_rails_pro/license_fetcher_spec.rbreact_on_rails_pro/spec/react_on_rails_pro/license_cache_spec.rbpackages/react-on-rails-pro-node-renderer/src/shared/licenseCache.tsreact_on_rails_pro/spec/react_on_rails_pro/spec_helper.rbreact_on_rails_pro/LICENSE_SETUP.mdreact_on_rails_pro/spec/dummy/config/initializers/react_on_rails_pro.rbreact_on_rails_pro/docs/configuration.mdreact_on_rails_pro/lib/react_on_rails_pro.rbreact_on_rails_pro/spec/react_on_rails_pro/configuration_spec.rbreact_on_rails_pro/lib/react_on_rails_pro/configuration.rbreact_on_rails_pro/lib/react_on_rails_pro/license_validator.rbreact_on_rails_pro/README.mdreact_on_rails_pro/lib/react_on_rails_pro/license_refresh_checker.rbreact_on_rails_pro/lib/react_on_rails_pro/license_fetcher.rb
📚 Learning: 2025-12-19T18:57:59.314Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: Applies to /CHANGELOG.md : Update `/CHANGELOG.md` for user-visible changes in the open-source react_on_rails gem and npm package (features, bug fixes, breaking changes, deprecations, performance improvements)
Applied to files:
react_on_rails_pro/docs/installation.mdpackages/react-on-rails-pro-node-renderer/package.jsonreact_on_rails_pro/spec/react_on_rails_pro/license_validator_spec.rbreact_on_rails_pro/lib/react_on_rails_pro/license_cache.rbreact_on_rails_pro/spec/react_on_rails_pro/license_cache_spec.rbreact_on_rails_pro/LICENSE_SETUP.mdreact_on_rails_pro/spec/dummy/config/initializers/react_on_rails_pro.rbreact_on_rails_pro/docs/configuration.mdreact_on_rails_pro/lib/react_on_rails_pro.rbreact_on_rails_pro/lib/react_on_rails_pro/configuration.rbreact_on_rails_pro/lib/react_on_rails_pro/license_validator.rbreact_on_rails_pro/README.mdreact_on_rails_pro/lib/react_on_rails_pro/license_refresh_checker.rbreact_on_rails_pro/lib/react_on_rails_pro/license_fetcher.rb
📚 Learning: 2025-10-23T17:22:01.074Z
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1875
File: lib/react_on_rails/utils.rb:112-124
Timestamp: 2025-10-23T17:22:01.074Z
Learning: In React on Rails, when Pro is installed but not licensed, the intended behavior is to raise an error on boot. The `react_on_rails_pro?` method validates licenses and should raise errors early (including during path resolution in methods like `server_bundle?`) to enforce licensing requirements rather than failing later with obscure errors.
Applied to files:
react_on_rails_pro/docs/installation.mdreact_on_rails_pro/spec/react_on_rails_pro/license_validator_spec.rbreact_on_rails_pro/lib/react_on_rails_pro/license_cache.rbreact_on_rails_pro/spec/react_on_rails_pro/license_fetcher_spec.rbreact_on_rails_pro/spec/react_on_rails_pro/license_cache_spec.rbpackages/react-on-rails-pro-node-renderer/tests/licenseValidator.test.tsreact_on_rails_pro/LICENSE_SETUP.mdreact_on_rails_pro/spec/dummy/config/initializers/react_on_rails_pro.rbreact_on_rails_pro/docs/configuration.mdreact_on_rails_pro/lib/react_on_rails_pro.rbreact_on_rails_pro/spec/react_on_rails_pro/configuration_spec.rbreact_on_rails_pro/lib/react_on_rails_pro/configuration.rbreact_on_rails_pro/lib/react_on_rails_pro/license_validator.rbreact_on_rails_pro/README.mdreact_on_rails_pro/lib/react_on_rails_pro/license_refresh_checker.rbreact_on_rails_pro/lib/react_on_rails_pro/license_fetcher.rb
📚 Learning: 2025-09-15T21:24:48.207Z
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1781
File: node_package/src/ClientSideRenderer.ts:82-95
Timestamp: 2025-09-15T21:24:48.207Z
Learning: In React on Rails, the force_load feature includes both explicit `data-force-load="true"` usage and the ability to hydrate components during the page loading state (`document.readyState === 'loading'`). Both capabilities require a Pro license, so the condition `!railsContext.rorPro && (isComponentForceLoaded || document.readyState === 'loading')` correctly gates both scenarios.
Applied to files:
react_on_rails_pro/docs/installation.mdreact_on_rails_pro/spec/react_on_rails_pro/license_validator_spec.rbreact_on_rails_pro/lib/react_on_rails_pro/license_cache.rbreact_on_rails_pro/spec/react_on_rails_pro/license_fetcher_spec.rbreact_on_rails_pro/LICENSE_SETUP.mdreact_on_rails_pro/spec/dummy/config/initializers/react_on_rails_pro.rbreact_on_rails_pro/docs/configuration.mdreact_on_rails_pro/lib/react_on_rails_pro.rbreact_on_rails_pro/spec/react_on_rails_pro/configuration_spec.rbreact_on_rails_pro/lib/react_on_rails_pro/configuration.rbreact_on_rails_pro/lib/react_on_rails_pro/license_validator.rbreact_on_rails_pro/README.mdreact_on_rails_pro/lib/react_on_rails_pro/license_refresh_checker.rbreact_on_rails_pro/lib/react_on_rails_pro/license_fetcher.rb
📚 Learning: 2025-09-16T08:01:11.146Z
Learnt from: justin808
Repo: shakacode/react_on_rails PR: 1770
File: lib/generators/react_on_rails/templates/base/base/app/javascript/src/HelloWorld/ror_components/HelloWorld.client.jsx:2-2
Timestamp: 2025-09-16T08:01:11.146Z
Learning: React on Rails uses webpack CSS Modules configuration with namedExports: true, which requires the import syntax `import * as style from './file.module.css'` rather than the default export pattern. This configuration enables better tree shaking and bundle size optimization for CSS modules.
Applied to files:
react_on_rails_pro/docs/installation.mdreact_on_rails_pro/spec/react_on_rails_pro/spec_helper.rbreact_on_rails_pro/spec/dummy/config/initializers/react_on_rails_pro.rbreact_on_rails_pro/docs/configuration.mdreact_on_rails_pro/lib/react_on_rails_pro.rbreact_on_rails_pro/lib/react_on_rails_pro/configuration.rbreact_on_rails_pro/README.md
📚 Learning: 2025-04-26T21:55:55.874Z
Learnt from: alexeyr-ci2
Repo: shakacode/react_on_rails PR: 1732
File: spec/dummy/client/app-react16/startup/ReduxSharedStoreApp.client.jsx:40-44
Timestamp: 2025-04-26T21:55:55.874Z
Learning: In the react_on_rails project, files under `app-react16` directories are copied/moved to corresponding `/app` directories during the conversion process (removing the `-react16` suffix), which affects their relative import paths at runtime.
Applied to files:
react_on_rails_pro/docs/installation.mdreact_on_rails_pro/spec/dummy/config/initializers/react_on_rails_pro.rbreact_on_rails_pro/lib/react_on_rails_pro.rb
📚 Learning: 2025-12-19T18:57:59.314Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: Applies to sig/react_on_rails/**/*.rbs : RBS signatures in `sig/react_on_rails/` should be added for new Ruby files in `lib/react_on_rails/`, included in Steepfile, validated with `bundle exec rake rbs:validate`, and type-checked with `bundle exec rake rbs:steep`
Applied to files:
react_on_rails_pro/docs/installation.mdreact_on_rails_pro/lib/react_on_rails_pro/license_cache.rbreact_on_rails_pro/spec/react_on_rails_pro/license_cache_spec.rbreact_on_rails_pro/spec/react_on_rails_pro/spec_helper.rbreact_on_rails_pro/spec/dummy/config/initializers/react_on_rails_pro.rbreact_on_rails_pro/lib/react_on_rails_pro.rbreact_on_rails_pro/lib/react_on_rails_pro/license_validator.rbreact_on_rails_pro/README.mdreact_on_rails_pro/lib/react_on_rails_pro/license_refresh_checker.rb
📚 Learning: 2025-02-18T13:08:01.477Z
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1644
File: lib/react_on_rails/helper.rb:190-197
Timestamp: 2025-02-18T13:08:01.477Z
Learning: RSC support validation in React on Rails Pro is handled through a chain of validations:
1. Pro version check in `run_stream_inside_fiber`
2. RSC support check during pack generation via `ReactOnRailsPro.configuration.enable_rsc_support`
3. RSC support validation during component registration
This makes additional validation in the helper methods unnecessary.
Applied to files:
react_on_rails_pro/docs/installation.mdreact_on_rails_pro/spec/react_on_rails_pro/license_validator_spec.rbreact_on_rails_pro/lib/react_on_rails_pro/license_cache.rbreact_on_rails_pro/LICENSE_SETUP.mdreact_on_rails_pro/spec/dummy/config/initializers/react_on_rails_pro.rbreact_on_rails_pro/docs/configuration.mdreact_on_rails_pro/lib/react_on_rails_pro.rbreact_on_rails_pro/spec/react_on_rails_pro/configuration_spec.rbreact_on_rails_pro/lib/react_on_rails_pro/configuration.rbreact_on_rails_pro/lib/react_on_rails_pro/license_validator.rbreact_on_rails_pro/README.mdreact_on_rails_pro/lib/react_on_rails_pro/license_refresh_checker.rb
📚 Learning: 2025-12-19T18:57:59.314Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: Applies to lib/react_on_rails/engine.rb : In Rails Engine code, DO NOT use `rake_tasks` block to explicitly load rake files from `lib/tasks/` - Rails::Engine automatically loads them. Only use `rake_tasks` block if tasks are in non-standard location, need programmatic generation, or need to pass context
Applied to files:
react_on_rails_pro/docs/installation.mdreact_on_rails_pro/spec/dummy/config/initializers/react_on_rails_pro.rbreact_on_rails_pro/lib/react_on_rails_pro.rb
📚 Learning: 2024-12-12T13:07:09.929Z
Learnt from: alexeyr-ci
Repo: shakacode/react_on_rails PR: 1644
File: node_package/src/ReactOnRailsRSC.ts:87-87
Timestamp: 2024-12-12T13:07:09.929Z
Learning: When handling errors in 'node_package/src/ReactOnRailsRSC.ts', include the error stack in error messages in development and test environments to aid debugging.
Applied to files:
packages/react-on-rails-pro-node-renderer/package.json
📚 Learning: 2025-12-19T18:57:59.314Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: Applies to react_on_rails/spec/dummy/e2e/playwright/e2e/**/*.spec.js : Use Playwright E2E tests in `react_on_rails/spec/dummy/e2e/playwright/` for React component integration testing. Tests automatically start Rails server on port 5017 before running
Applied to files:
packages/react-on-rails-pro-node-renderer/tests/licenseFetcher.test.tsreact_on_rails_pro/spec/react_on_rails_pro/license_validator_spec.rbreact_on_rails_pro/spec/react_on_rails_pro/license_fetcher_spec.rbreact_on_rails_pro/spec/react_on_rails_pro/license_cache_spec.rbpackages/react-on-rails-pro-node-renderer/tests/licenseValidator.test.tsreact_on_rails_pro/spec/react_on_rails_pro/spec_helper.rbreact_on_rails_pro/LICENSE_SETUP.mdreact_on_rails_pro/spec/dummy/config/initializers/react_on_rails_pro.rbpackages/react-on-rails-pro-node-renderer/tests/licenseRefreshChecker.test.tsreact_on_rails_pro/docs/configuration.mdreact_on_rails_pro/spec/react_on_rails_pro/configuration_spec.rb
📚 Learning: 2025-02-13T14:29:49.267Z
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1644
File: spec/react_on_rails/utils_spec.rb:218-218
Timestamp: 2025-02-13T14:29:49.267Z
Learning: In RSpec tests, prefer using local variables over constants within test blocks to avoid constant redefinition warnings and maintain better test isolation.
Applied to files:
react_on_rails_pro/spec/react_on_rails_pro/license_validator_spec.rbreact_on_rails_pro/spec/react_on_rails_pro/license_cache_spec.rbreact_on_rails_pro/spec/react_on_rails_pro/spec_helper.rbreact_on_rails_pro/spec/react_on_rails_pro/configuration_spec.rb
📚 Learning: 2025-12-19T18:57:59.314Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: In IDE configuration, exclude these directories to prevent slowdowns: /coverage, /tmp, /gen-examples, /packages/react-on-rails/lib, /node_modules, /react_on_rails/spec/dummy/node_modules, /react_on_rails/spec/dummy/tmp, /react_on_rails/spec/dummy/app/assets/webpack, /react_on_rails/spec/dummy/log, /react_on_rails/spec/dummy/e2e/playwright-report, /react_on_rails/spec/dummy/test-results
Applied to files:
react_on_rails_pro/lib/react_on_rails_pro/license_cache.rbreact_on_rails_pro/spec/dummy/config/initializers/react_on_rails_pro.rbreact_on_rails_pro/docs/configuration.md
📚 Learning: 2025-02-12T16:38:06.537Z
Learnt from: Romex91
Repo: shakacode/react_on_rails PR: 1697
File: package-scripts.yml:28-28
Timestamp: 2025-02-12T16:38:06.537Z
Learning: The file `node_package/lib/ReactOnRails.full.js` is autogenerated during the build process and should not be present in the repository.
Applied to files:
react_on_rails_pro/lib/react_on_rails_pro/license_cache.rbreact_on_rails_pro/spec/dummy/config/initializers/react_on_rails_pro.rbreact_on_rails_pro/docs/configuration.mdreact_on_rails_pro/lib/react_on_rails_pro.rbreact_on_rails_pro/README.md
📚 Learning: 2025-12-19T18:57:59.314Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: Applies to react_on_rails/spec/dummy/e2e/playwright/app_commands/**/*.rb : Create custom Rails app commands in `e2e/playwright/app_commands/` directory with .rb files for reusable helpers in Playwright tests
Applied to files:
react_on_rails_pro/spec/react_on_rails_pro/license_cache_spec.rbreact_on_rails_pro/spec/react_on_rails_pro/spec_helper.rbreact_on_rails_pro/spec/dummy/config/initializers/react_on_rails_pro.rb
📚 Learning: 2025-12-19T18:57:59.314Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: Applies to react_on_rails/spec/dummy/e2e/playwright/e2e/**/*.spec.js : Use `app('clean')`, `appEval(code)`, `appFactories(options)`, and `appScenario(name)` helpers from cypress-on-rails in Playwright tests for Rails integration
Applied to files:
react_on_rails_pro/spec/react_on_rails_pro/license_cache_spec.rbpackages/react-on-rails-pro-node-renderer/tests/licenseValidator.test.tsreact_on_rails_pro/spec/react_on_rails_pro/spec_helper.rb
📚 Learning: 2025-12-19T18:57:59.314Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: Install Playwright browsers with `cd react_on_rails/spec/dummy && pnpm playwright install --with-deps` before running E2E tests
Applied to files:
packages/react-on-rails-pro-node-renderer/tests/licenseValidator.test.ts
📚 Learning: 2024-10-08T20:53:47.076Z
Learnt from: theforestvn88
Repo: shakacode/react_on_rails PR: 1620
File: spec/dummy/client/app/startup/HelloTurboStream.jsx:3-3
Timestamp: 2024-10-08T20:53:47.076Z
Learning: The `RailsContext` import in `spec/dummy/client/app/startup/HelloTurboStream.jsx` is used later in the project, as clarified by the user theforestvn88.
Applied to files:
react_on_rails_pro/spec/react_on_rails_pro/spec_helper.rb
📚 Learning: 2025-12-19T18:57:59.314Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: Shakapacker configuration should be tested by creating debug scripts in dummy app root (debug-webpack-rules.js, debug-webpack-with-config.js) to inspect webpack rules before committing configuration changes
Applied to files:
react_on_rails_pro/spec/react_on_rails_pro/spec_helper.rb
📚 Learning: 2025-02-13T16:50:26.861Z
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1644
File: node_package/src/turbolinksUtils.ts:34-36
Timestamp: 2025-02-13T16:50:26.861Z
Learning: In React on Rails, when checking for Turbolinks version 5 using `turbolinksVersion5()`, always ensure `Turbolinks` exists first by checking `turbolinksInstalled()` to prevent TypeError when accessing properties.
Applied to files:
react_on_rails_pro/LICENSE_SETUP.md
📚 Learning: 2025-02-18T13:08:01.477Z
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1644
File: lib/react_on_rails/helper.rb:190-197
Timestamp: 2025-02-18T13:08:01.477Z
Learning: RSC support validation is handled in deeper level calls of the React on Rails Pro codebase, so it doesn't need to be validated again in the `rsc_payload_react_component` helper method.
Applied to files:
react_on_rails_pro/LICENSE_SETUP.md
📚 Learning: 2025-07-08T05:57:29.630Z
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1745
File: node_package/src/RSCRequestTracker.ts:8-14
Timestamp: 2025-07-08T05:57:29.630Z
Learning: The global `generateRSCPayload` function in React on Rails Pro (RORP) is provided by the framework during rendering requests, not implemented in application code. The `declare global` statements are used to document the expected interface that RORP will inject at runtime.
Applied to files:
react_on_rails_pro/docs/configuration.mdreact_on_rails_pro/lib/react_on_rails_pro.rb
📚 Learning: 2025-02-13T16:50:47.848Z
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1644
File: node_package/src/clientStartup.ts:18-21
Timestamp: 2025-02-13T16:50:47.848Z
Learning: In the react_on_rails module, the `reactOnRailsPageUnloaded` function in clientStartup.ts is intentionally kept private as it's only used internally as a callback for `onPageUnloaded`.
Applied to files:
react_on_rails_pro/docs/configuration.mdreact_on_rails_pro/lib/react_on_rails_pro.rb
📚 Learning: 2024-06-27T05:22:46.156Z
Learnt from: justin808
Repo: shakacode/react_on_rails PR: 1622
File: spec/dummy/spec/rake/assets_precompile_rake_spec.rb:12-12
Timestamp: 2024-06-27T05:22:46.156Z
Learning: When stubbing environment variables in RSpec tests, use `before` and `after` hooks to ensure that the original values are restored after the tests, preventing any side effects on other tests. Example provided by justin808:
```ruby
describe "My test" do
before do
original_value = ENV["VARIABLE_NAME"]
allow(ENV).to receive(:[]).with("VARIABLE_NAME").and_return("stubbed_value")
end
after do
allow(ENV).to receive(:[]).with("VARIABLE_NAME").and_call_original
ENV["VARIABLE_NAME"] = original_value
end
it "tests something" do
# Your test code here
end
end
```
This practice ensures test isolation and reliability.
Applied to files:
react_on_rails_pro/spec/react_on_rails_pro/configuration_spec.rb
🧬 Code graph analysis (7)
packages/react-on-rails-pro-node-renderer/src/shared/licenseFetcher.ts (2)
react_on_rails_pro/spec/dummy/client/node-renderer.js (1)
process(5-5)react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/RouterApp.server.jsx (1)
controller(10-10)
packages/react-on-rails-pro-node-renderer/src/shared/licenseRefreshChecker.ts (2)
packages/react-on-rails-pro-node-renderer/src/shared/licenseCache.ts (4)
getFetchedAt(150-161)getExpiresAt(167-178)writeCache(105-136)getCachedToken(141-144)packages/react-on-rails-pro-node-renderer/src/shared/licenseFetcher.ts (2)
isAutoRefreshEnabled(51-60)fetchLicense(66-131)
packages/react-on-rails-pro-node-renderer/src/master.ts (2)
packages/react-on-rails-pro-node-renderer/src/shared/configBuilder.ts (1)
Config(25-89)packages/react-on-rails-pro-node-renderer/src/shared/licenseValidator.ts (1)
getValidatedLicenseData(204-247)
react_on_rails_pro/spec/react_on_rails_pro/license_cache_spec.rb (1)
react_on_rails_pro/lib/react_on_rails_pro/license_cache.rb (6)
cache_dir(75-77)write(27-39)cache_path(79-81)token(41-43)fetched_at(45-50)expires_at(52-57)
react_on_rails_pro/spec/react_on_rails_pro/configuration_spec.rb (1)
react_on_rails_pro/lib/react_on_rails_pro/configuration.rb (5)
configure(4-7)configuration(9-38)license_api_url(217-222)license_key(202-207)auto_refresh_enabled?(234-236)
react_on_rails_pro/lib/react_on_rails_pro/license_validator.rb (3)
react_on_rails_pro/lib/react_on_rails_pro/license_refresh_checker.rb (2)
seed_cache_if_needed(26-39)maybe_refresh_license(13-21)react_on_rails_pro/lib/react_on_rails_pro/configuration.rb (2)
configuration(9-38)auto_refresh_enabled?(234-236)react_on_rails_pro/lib/react_on_rails_pro/license_cache.rb (1)
token(41-43)
react_on_rails_pro/lib/react_on_rails_pro/license_fetcher.rb (1)
react_on_rails_pro/lib/react_on_rails_pro/configuration.rb (3)
configuration(9-38)auto_refresh_enabled?(234-236)license_api_url(217-222)
🪛 Gitleaks (8.30.0)
packages/react-on-rails-pro-node-renderer/tests/licenseFetcher.test.ts
[high] 62-62: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
[high] 187-187: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
react_on_rails_pro/LICENSE_SETUP.md
[high] 102-102: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
- GitHub Check: dummy-app-integration-tests (3.4, 22, latest)
- GitHub Check: build-dummy-app-webpack-test-bundles
- GitHub Check: rspec-package-tests (3.4, latest)
- GitHub Check: build
- GitHub Check: examples (3.4, latest)
- GitHub Check: precompile-check
- GitHub Check: pro-lint-js-and-ruby
- GitHub Check: build-dummy-app-webpack-test-bundles
- GitHub Check: check-bundle-size
- GitHub Check: markdown-link-check
- GitHub Check: claude-review
🔇 Additional comments (31)
react_on_rails_pro/lib/react_on_rails_pro.rb (1)
13-15: LGTM! Clean module loading sequence.The three new license management modules are loaded in the correct order before the validator, ensuring all dependencies are available when needed.
packages/react-on-rails-pro-node-renderer/tests/licenseRefreshChecker.test.ts (1)
1-222: LGTM! Comprehensive test coverage for license refresh logic.The test suite thoroughly exercises all three exported functions with appropriate mocking and edge case coverage. The beforeEach setup properly isolates tests, and console suppression keeps output clean.
packages/react-on-rails-pro-node-renderer/src/ReactOnRailsProNodeRenderer.ts (1)
45-45: Verify awaiting behavior is documented.The addition of
awaitmakes the master path consistent with the worker path and is necessary for async license validation. SincereactOnRailsProNodeRendererwas alreadyasync, callers should have been awaiting it.Please confirm that this change is documented in CHANGELOG_PRO.md, as it could affect callers who weren't properly awaiting the function (though they should have been).
Based on learnings, user-visible changes to the react_on_rails_pro npm package should be documented in CHANGELOG_PRO.md.
react_on_rails_pro/docs/installation.md (1)
50-51: LGTM! Clear guidance for automatic license renewal.The documentation appropriately directs paid subscribers to use the license key environment variable and links to detailed setup instructions.
react_on_rails_pro/spec/dummy/config/initializers/react_on_rails_pro.rb (1)
45-50: LGTM! Helpful configuration examples for development.The commented examples clearly document the auto-refresh options available for local testing, providing a useful reference without affecting default behavior.
react_on_rails_pro/spec/react_on_rails_pro/spec_helper.rb (1)
24-27: LGTM! Standard WebMock setup for HTTP testing.The WebMock integration with HTTPX adapter is properly configured to support testing of the license fetching functionality across all specs.
react_on_rails_pro/docs/configuration.md (1)
165-186: LGTM! Clear and comprehensive auto-refresh configuration documentation.The new LICENSE AUTO-REFRESH CONFIGURATION section thoroughly documents all three options with appropriate defaults, environment variable names, and usage guidance. The link to LICENSE_SETUP.md provides users with detailed setup instructions.
packages/react-on-rails-pro-node-renderer/package.json (1)
23-23: Ensure async-retry addition is documented in react_on_rails_pro/CHANGELOG.md.The
async-retrydependency version ^1.3.3 is confirmed as the latest stable version and is appropriately added for the license fetch retry logic. However, this change is not documented in the changelog. Update react_on_rails_pro/CHANGELOG.md to include this new dependency addition under the appropriate release section.Also applies to: 38-38
⛔ Skipped due to learnings
Learnt from: CR Repo: shakacode/react_on_rails PR: 0 File: CLAUDE.md:0-0 Timestamp: 2025-12-19T18:57:59.314Z Learning: Applies to /CHANGELOG.md : Update `/CHANGELOG.md` for user-visible changes in the open-source react_on_rails gem and npm package (features, bug fixes, breaking changes, deprecations, performance improvements)Learnt from: CR Repo: shakacode/react_on_rails PR: 0 File: CLAUDE.md:0-0 Timestamp: 2025-12-19T18:57:59.314Z Learning: Applies to /CHANGELOG_PRO.md : Update `/CHANGELOG_PRO.md` for user-visible changes in the Pro-only react_on_rails_pro gem and npm packagesLearnt from: CR Repo: shakacode/react_on_rails PR: 0 File: CLAUDE.md:0-0 Timestamp: 2025-12-19T18:57:59.314Z Learning: Applies to /{CHANGELOG.md,CHANGELOG_PRO.md} : Use format `[PR 1818](https://github.com/shakacode/react_on_rails/pull/1818) by [username](https://github.com/username)` in changelog entries (no hash in PR number)Learnt from: AbanoubGhadban Repo: shakacode/react_on_rails PR: 1875 File: lib/react_on_rails/utils.rb:112-124 Timestamp: 2025-10-23T17:22:01.074Z Learning: In React on Rails, when Pro is installed but not licensed, the intended behavior is to raise an error on boot. The `react_on_rails_pro?` method validates licenses and should raise errors early (including during path resolution in methods like `server_bundle?`) to enforce licensing requirements rather than failing later with obscure errors.Learnt from: alexeyr-ci Repo: shakacode/react_on_rails PR: 1687 File: spec/dummy/package.json:0-0 Timestamp: 2025-01-23T18:20:45.824Z Learning: When adding or updating dependencies in spec/dummy/package.json, maintain version consistency with other package.json files in the codebase to avoid potential version conflicts.Learnt from: Romex91 Repo: shakacode/react_on_rails PR: 1697 File: package-scripts.yml:28-28 Timestamp: 2025-02-12T16:38:06.537Z Learning: The file `node_package/lib/ReactOnRails.full.js` is autogenerated during the build process and should not be present in the repository.Learnt from: alexeyr-ci Repo: shakacode/react_on_rails PR: 1644 File: node_package/src/ReactOnRailsRSC.ts:87-87 Timestamp: 2024-12-12T13:07:09.929Z Learning: When handling errors in 'node_package/src/ReactOnRailsRSC.ts', include the error stack in error messages in development and test environments to aid debugging.Learnt from: AbanoubGhadban Repo: shakacode/react_on_rails PR: 1781 File: node_package/src/ClientSideRenderer.ts:82-95 Timestamp: 2025-09-15T21:24:48.207Z Learning: In React on Rails, the force_load feature includes both explicit `data-force-load="true"` usage and the ability to hydrate components during the page loading state (`document.readyState === 'loading'`). Both capabilities require a Pro license, so the condition `!railsContext.rorPro && (isComponentForceLoaded || document.readyState === 'loading')` correctly gates both scenarios.Learnt from: CR Repo: shakacode/react_on_rails PR: 0 File: CLAUDE.md:0-0 Timestamp: 2025-12-19T18:57:59.314Z Learning: Applies to sig/react_on_rails/**/*.rbs : RBS signatures in `sig/react_on_rails/` should be added for new Ruby files in `lib/react_on_rails/`, included in Steepfile, validated with `bundle exec rake rbs:validate`, and type-checked with `bundle exec rake rbs:steep`react_on_rails_pro/README.md (1)
297-298: LGTM! Clear guidance for paid subscribers.The documentation clearly directs paid subscribers to use the
LICENSE_KEYenvironment variable for automatic renewal, with an appropriate reference to detailed setup instructions.react_on_rails_pro/lib/react_on_rails_pro/license_validator.rb (2)
29-31: LGTM! Cache seeding correctly positioned.The cache seeding logic is appropriately placed after successful validation, ensuring only valid license data is cached. The comments clearly explain the purpose.
159-174: LGTM! License loading priority correctly updated.The license loading flow correctly implements the auto-refresh feature:
- Pre-load refresh attempt ensures fresh tokens
- Cache-first retrieval when auto-refresh is enabled
- Proper fallback chain: cache → ENV → config file
packages/react-on-rails-pro-node-renderer/tests/licenseFetcher.test.ts (1)
1-206: LGTM! Comprehensive test coverage.The test suite thoroughly covers the licenseFetcher functionality:
- Auto-refresh enabling/disabling scenarios
- API request handling with proper headers (Authorization, User-Agent)
- Error handling (401, network errors, JSON parsing)
- Retry logic with fake timers
- Custom API URL support
The test data (JWT tokens, license keys) are clearly test values, not real secrets.
Note: The Gitleaks warnings for lines 62 and 187 are false positives—these are test JWT tokens and mock API keys used exclusively in test scenarios.
react_on_rails_pro/LICENSE_SETUP.md (2)
79-142: LGTM! Excellent documentation for automatic license renewal.The automatic license renewal section is comprehensive and user-friendly:
- Clear distinction between License Token and License Key
- Well-organized setup instructions
- Helpful frequency table for refresh checks
- Appropriate guidance for air-gapped environments
- Privacy considerations transparently documented
350-360: LGTM! Clear FAQ entries for dual-token model.The FAQ entries effectively clarify:
- Differences between LICENSE and LICENSE_KEY
- When to use each method
- Behavior when both are set
- Data transmission transparency
Note: The Gitleaks warning for line 102 is a false positive—"lic_abc123xyz..." is clearly a placeholder example in documentation, not a real license key.
packages/react-on-rails-pro-node-renderer/tests/licenseCache.test.ts (1)
1-254: LGTM! Thorough test coverage for license cache.The test suite comprehensively validates the cache functionality:
- Read/write operations with license key validation
- Security features (0600 permissions, license_key_hash verification)
- Error handling (missing files, JSON parse errors, write failures)
- All public API methods (getCachedToken, getFetchedAt, getExpiresAt)
- Proper mocking and cleanup for filesystem operations
The tests ensure data integrity and security of the cached license data.
react_on_rails_pro/spec/react_on_rails_pro/license_validator_spec.rb (1)
258-601: LGTM! Comprehensive auto-refresh test coverage.The test suite thoroughly validates the license auto-refresh feature:
- Token source priority (cache → ENV → config) with proper gating
- Refresh timing logic with boundary tests (30, 8, 7, 3 days)
- Seeding behavior for first-boot scenarios
- Integration with LicenseCache, LicenseFetcher, and LicenseRefreshChecker
- Proper handling of fetch success/failure scenarios
The comment at lines 346-352 explaining the 60-day choice to avoid timing races demonstrates thoughtful test design.
react_on_rails_pro/spec/react_on_rails_pro/configuration_spec.rb (1)
301-427: LGTM! Complete configuration test coverage.The test suite thoroughly validates the license auto-refresh configuration:
- Default values (auto_refresh_license defaults to true)
- Configuration mutability
- ENV variable precedence (LICENSE_KEY, LICENSE_API_URL)
- Helper method logic (auto_refresh_enabled?)
- Edge cases (empty ENV, nil values)
- Proper ENV cleanup for test isolation
packages/react-on-rails-pro-node-renderer/src/master.ts (1)
14-18: The async conversion ofmasterRunis correctly implemented with properawaitofgetValidatedLicenseData(). The only call site inReactOnRailsProNodeRenderer.tsline 45 is already properly awaiting the function withawait master.default(config). No further changes needed.react_on_rails_pro/spec/react_on_rails_pro/license_fetcher_spec.rb (1)
1-185: LGTM! Comprehensive test coverage.The test suite thoroughly covers all scenarios for
LicenseFetcher.fetch:
- Auto-refresh disabled/enabled states
- Various HTTP response codes (200, 401, 404, 500)
- Network errors and timeouts
- Invalid JSON handling
- ENV variable precedence
Test isolation is properly maintained with appropriate setup/teardown.
react_on_rails_pro/lib/react_on_rails_pro/license_refresh_checker.rb (1)
1-75: LGTM! Well-structured refresh checker.The refresh timing logic is clean and correctly implements the documented schedule:
- ≤7 days: daily checks
- ≤30 days: weekly checks
30 days: no refresh
Cache seeding handles the first-boot scenario properly, and token loading follows the correct precedence (ENV → config file).
packages/react-on-rails-pro-node-renderer/src/shared/licenseRefreshChecker.ts (1)
1-133: LGTM! TypeScript implementation matches Ruby behavior.The TypeScript refresh checker correctly mirrors the Ruby implementation:
- Identical refresh schedule logic
- Proper async/await for fetch operations
- Consistent cache seeding behavior
- Correct time conversions (Unix timestamp to Date)
react_on_rails_pro/spec/react_on_rails_pro/license_cache_spec.rb (1)
1-283: LGTM! Comprehensive cache test coverage.The test suite thoroughly covers all aspects of the license cache:
- Read validation with hash verification
- Write operations with proper permissions
- Environment variable precedence
- Timestamp parsing and error handling
- Edge cases (missing files, invalid JSON, mismatched hashes)
FakeFS usage is correct with proper activation/deactivation.
packages/react-on-rails-pro-node-renderer/tests/licenseValidator.test.ts (1)
108-297: LGTM! Comprehensive async validator tests.The test suite has been properly migrated to the async API:
- All calls to
getValidatedLicenseData(),isEvaluation()useawait- Covers all critical scenarios (valid, expired, invalid, missing licenses)
- Tests caching behavior and reset functionality
- Proper test isolation with RSA key generation
packages/react-on-rails-pro-node-renderer/src/shared/licenseValidator.ts (1)
204-247: LGTM! Async validator implementation is solid.The migration to async API is well-executed:
- Cache-first loading with auto-refresh integration
- Proper cache seeding on first validation
- Consistent error handling
- All exported functions correctly updated to async
The integration of
maybeRefreshLicense()andseedCacheIfNeeded()completes the auto-renewal workflow.react_on_rails_pro/lib/react_on_rails_pro/license_cache.rb (2)
15-25: LGTM!The
readmethod properly handles edge cases with appropriate error handling. The defensive rescue forErrno::ENOENTalongside the existence check is good practice against TOCTOU race conditions.
68-73: LGTM!The hash truncation to 16 hex characters provides sufficient entropy for cache validation while keeping the stored data compact. The implementation correctly handles the nil case.
react_on_rails_pro/lib/react_on_rails_pro/configuration.rb (2)
199-222: LGTM!The environment variable precedence pattern is well-implemented. Using
.present?correctly handles both nil and empty string cases, and the fallback toDEFAULT_LICENSE_API_URLprovides sensible defaults.
229-236: LGTM!The
auto_refresh_enabled?method correctly gates auto-refresh on both the feature flag and the presence of a license key, preventing unnecessary API calls when credentials are missing.packages/react-on-rails-pro-node-renderer/src/shared/licenseCache.ts (3)
48-54: Verify cache directory alignment with Ruby implementation.This uses
process.cwd()to determine the cache directory, while Ruby usesRails.root. If the Node Renderer is started from a different working directory than the Rails root, they may use different cache files.Is it intentional that Ruby and Node maintain separate caches, or should they share the same cache file? If sharing is intended, consider making the cache path configurable or using a more explicit path resolution.
80-99: LGTM!The
readCachefunction has appropriate error handling that fails gracefully. The validation flow correctly checks for file existence, parses JSON, and validates the license key hash before returning data.
15-20: LGTM!The
CacheDatainterface clearly defines the cache structure, matching the Ruby implementation. This ensures consistency between the two implementations.
| const controller = new AbortController(); | ||
| const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS); | ||
|
|
||
| try { | ||
| const response = await fetch(url, { | ||
| method: 'GET', | ||
| headers: { | ||
| Authorization: `Bearer ${licenseKey}`, | ||
| 'User-Agent': 'ReactOnRailsPro-NodeRenderer', | ||
| }, | ||
| signal: controller.signal, | ||
| }); | ||
|
|
||
| if (response.status === 401 || response.status === 403) { | ||
| // Don't retry auth errors - bail immediately | ||
| bail(new UnauthorizedError()); | ||
| throw new UnauthorizedError(); // TypeScript needs this | ||
| } | ||
|
|
||
| if (response.status !== 200) { | ||
| throw new Error(`HTTP ${response.status}`); | ||
| } | ||
|
|
||
| return (await response.json()) as LicenseResponse; | ||
| } finally { | ||
| clearTimeout(timeoutId); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
# Find the test file for licenseFetcher
find . -name "*licenseFetcher*" -type f | head -20Repository: shakacode/react_on_rails
Length of output: 213
🏁 Script executed:
# Check if error handling properly covers AbortError
rg -n "AbortError|catch|error" packages/react-on-rails-pro-node-renderer/src/shared/licenseFetcher.ts -A 2 -B 2Repository: shakacode/react_on_rails
Length of output: 1384
🏁 Script executed:
# Look for retry logic and complete fetch implementation
rg -n "retry\|catch\|finally" packages/react-on-rails-pro-node-renderer/src/shared/licenseFetcher.ts -B 3 -A 5Repository: shakacode/react_on_rails
Length of output: 50
🏁 Script executed:
cat -n packages/react-on-rails-pro-node-renderer/src/shared/licenseFetcher.tsRepository: shakacode/react_on_rails
Length of output: 4685
🏁 Script executed:
cat -n packages/react-on-rails-pro-node-renderer/tests/licenseFetcher.test.tsRepository: shakacode/react_on_rails
Length of output: 7843
AbortError is properly handled by existing error handling logic.
When controller.abort() is called on timeout, the fetch rejects with an AbortError. This error flows through the retry mechanism (which catches all errors and retries), and if retries are exhausted, it's caught by the outer catch block at line 122. Since AbortError extends Error, it's handled by the generic error handler at lines 126-127 and logged correctly. The finally block at line 107 safely clears the timeout regardless of whether the error occurred.
However, there is no explicit test case covering timeout/abort scenarios. Consider adding a test case that mocks AbortError to ensure this timeout behavior is explicitly validated.
🤖 Prompt for AI Agents
In packages/react-on-rails-pro-node-renderer/src/shared/licenseFetcher.ts around
lines 82 to 108, the production code already handles AbortError via the retry
and outer catch, but there is no explicit test validating timeout/abort
behavior; add a unit test that mocks fetch to reject with an AbortError when the
AbortController signal is used (or simulate controller.abort()), use fake timers
or advance timers to trigger the REQUEST_TIMEOUT_MS path, assert that the retry
logic runs as expected and that the final error is handled/logged (and that
clearTimeout is still called or timers are cleaned up). Keep the test focused:
simulate a timeout-induced AbortError, verify retries/exhaustion behavior and
final error handling/logging rather than changing production code.
| // Try auto-refresh if enabled and near expiry | ||
| await maybeRefreshLicense(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potential race condition with concurrent validation calls.
If getValidatedLicenseData() is called multiple times concurrently before the first call completes, each call will invoke await maybeRefreshLicense() (line 211), potentially causing multiple concurrent API requests to the license server.
Consider adding a guard to ensure only one refresh attempt happens at a time, or document that callers should avoid concurrent calls during initialization.
Would you like me to propose a solution using a promise-based lock pattern to prevent concurrent refreshes?
🤖 Prompt for AI Agents
In packages/react-on-rails-pro-node-renderer/src/shared/licenseValidator.ts
around lines 210-211, concurrent callers invoking await maybeRefreshLicense()
can trigger multiple simultaneous refresh requests; add a single-flight guard by
introducing a module-scoped variable (e.g., refreshInFlight: Promise<void> |
null) so maybeRefreshLicense checks if refreshInFlight exists and if so returns
await refreshInFlight, otherwise assigns refreshInFlight to the promise
representing the ongoing refresh, awaits it, and clears refreshInFlight in a
finally block; ensure errors from the refresh propagate to callers and that the
guard covers both success and failure paths.
| def fetched_at | ||
| timestamp = read&.dig("fetched_at") | ||
| Time.parse(timestamp) if timestamp | ||
| rescue ArgumentError | ||
| nil | ||
| end | ||
|
|
||
| def expires_at | ||
| timestamp = read&.dig("expires_at") | ||
| Time.parse(timestamp) if timestamp | ||
| rescue ArgumentError | ||
| nil | ||
| end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add require "time" for Time.parse.
Time.parse is defined in Ruby's time library, which is not loaded by default. While it may work due to transitive requires, it's best to explicitly require it.
🔎 Proposed fix
Add to the requires at the top of the file:
require "json"
require "digest"
require "fileutils"
+require "time"Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
react_on_rails_pro/lib/react_on_rails_pro/license_cache.rb lines 45-57: the
methods use Time.parse but the file doesn't explicitly require Ruby's time
library; add require "time" near the top of the file alongside the other
requires (or at the top-level requires block) so Time.parse is available, then
run tests to confirm no further load issues.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
react_on_rails_pro/LICENSE_SETUP.md (1)
1-1: Fix broken external link in documentation.The
https://licenses.shakacode.comlink referenced on line 96 is currently inaccessible (HTTP 502 Bad Gateway). This link directs users to the "React on Rails Pro dashboard" to retrieve their license key. Update or replace this URL with a working alternative, or provide an alternative method for users to access their license management dashboard.The other external links (
https://shakacode.com/react-on-rails-proandhttps://shakacode.com) are accessible and working correctly.react_on_rails_pro/lib/react_on_rails_pro/configuration.rb (1)
100-144: RBS signature file is out of sync and must be updated to match the initialize method.The initialize method in
react_on_rails_pro/lib/react_on_rails_pro/configuration.rbnow includes four new parameters (concurrent_component_streaming_buffer_size,auto_refresh_license,license_api_url,license_key), but the corresponding RBS signature file atreact_on_rails_pro/sig/react_on_rails_pro/configuration.rbsis missing:
- The four new parameters in the
initializemethod signature (lines 52-78)- The corresponding
attr_accessordeclarations for these attributes (lines 26-50)- The four new
DEFAULT_*constants (lines 3-24)Update the RBS file to include all four parameters and their attribute declarations, then validate with
bundle exec rake rbs:validateandbundle exec rake rbs:steep.
🧹 Nitpick comments (7)
react_on_rails_pro/lib/react_on_rails_pro/license_fetcher.rb (1)
28-35: Consider handling JSON parse errors explicitly.If the API returns a 200 status but with malformed or empty JSON,
JSON.parse(response.body.to_s)will raiseJSON::ParserError. While this is caught by theStandardErrorrescue, it might be worth logging a more specific message for this case to aid debugging.🔎 Proposed enhancement for clearer error handling
return nil if response.is_a?(HTTPX::ErrorResponse) return nil unless response.status == 200 Rails.logger.debug { "[ReactOnRailsPro] License fetched successfully" } - JSON.parse(response.body.to_s) + begin + JSON.parse(response.body.to_s) + rescue JSON::ParserError => e + Rails.logger.warn { "[ReactOnRailsPro] License fetch received invalid JSON: #{e.message}" } + nil + end rescue StandardError => e Rails.logger.warn { "[ReactOnRailsPro] License fetch failed: #{e.message}" } nilpackages/react-on-rails-pro-node-renderer/src/shared/licenseRefreshChecker.ts (1)
85-103: Consider adding logging when config file is found but has issues.The function silently ignores errors when reading the config file. While this is graceful fallback behavior, a debug-level log might help users troubleshoot configuration issues.
🔎 Optional: Add debug logging
// Try config file try { const configPath = path.join(process.cwd(), 'config', 'react_on_rails_pro_license.key'); if (fs.existsSync(configPath)) { return fs.readFileSync(configPath, 'utf8').trim(); } - } catch { + } catch (error) { // Ignore errors reading config file + console.debug?.(`[React on Rails Pro] Could not read license file: ${error}`); }packages/react-on-rails-pro-node-renderer/src/shared/licenseCache.ts (1)
105-136: Solid cache write implementation with security considerations.The function:
- Creates directory recursively if needed
- Validates license key is configured before writing
- Sets restrictive file permissions (0600)
- Handles errors gracefully with warning logs
One minor note:
fs.writeFileSyncfollowed byfs.chmodSynchas a brief window where the file exists with default permissions. Consider usingfs.writeFileSyncwithmodeoption, though this is a very minor concern for a cache file.🔎 Optional: Atomic permission setting
- fs.writeFileSync(cachePath, JSON.stringify(cacheData, null, 2)); - - // Set file permissions to 0600 (owner read/write only) - fs.chmodSync(cachePath, 0o600); + fs.writeFileSync(cachePath, JSON.stringify(cacheData, null, 2), { mode: 0o600 });packages/react-on-rails-pro-node-renderer/src/shared/licenseFetcher.ts (1)
95-99: Clarify the bail+throw pattern for future maintainers.The
throw new UnauthorizedError()afterbail()is needed becausebail()doesn't returnneverin TypeScript. Consider adding a comment or using a type assertion.🔎 Optional: Clarify the pattern
if (response.status === 401 || response.status === 403) { // Don't retry auth errors - bail immediately bail(new UnauthorizedError()); - throw new UnauthorizedError(); // TypeScript needs this + // TypeScript requires a throw here because bail() doesn't return `never`. + // The throw is unreachable at runtime since bail() aborts the retry loop. + throw new UnauthorizedError(); }react_on_rails_pro/lib/react_on_rails_pro/license_cache.rb (2)
35-36: Non-atomic file write and permission setting could leave file temporarily exposed.There's a brief window between
File.writeandFile.chmodwhere the file has default permissions (typically 0644). For sensitive license data, consider using a more secure approach.🔎 Suggested atomic write approach
- File.write(cache_path, JSON.pretty_generate(cache_data)) - File.chmod(0o600, cache_path) + # Write to temp file with correct permissions, then atomic rename + temp_path = cache_path.sub_ext(".tmp") + File.open(temp_path, File::WRONLY | File::CREAT | File::TRUNC, 0o600) do |f| + f.write(JSON.pretty_generate(cache_data)) + end + File.rename(temp_path, cache_path)
45-56: Consider usingTime.iso8601for stricter parsing.Since timestamps are stored in ISO8601 format (line 32), using
Time.iso8601instead ofTime.parsewould provide stricter validation and clearer intent.Time.parseis more permissive and could accept malformed dates.🔎 Suggested change
def fetched_at timestamp = read&.dig("fetched_at") - Time.parse(timestamp) if timestamp - rescue ArgumentError + Time.iso8601(timestamp) if timestamp + rescue ArgumentError, TypeError nil end def expires_at timestamp = read&.dig("expires_at") - Time.parse(timestamp) if timestamp - rescue ArgumentError + Time.iso8601(timestamp) if timestamp + rescue ArgumentError, TypeError nil endreact_on_rails_pro/lib/react_on_rails_pro/license_refresh_checker.rb (1)
68-69: Consider rescuing file read errors.While
config_path.exist?reduces risk, there's still a race condition where the file could be deleted or become unreadable between the check and read. Also, encoding issues or permission problems could cause exceptions.🔎 Suggested defensive approach
config_path = Rails.root.join("config", "react_on_rails_pro_license.key") - return File.read(config_path).strip if config_path.exist? + return File.read(config_path).strip if config_path.exist? && config_path.readable? + rescue Errno::ENOENT, Errno::EACCES + nil
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (27)
packages/react-on-rails-pro-node-renderer/package.jsonpackages/react-on-rails-pro-node-renderer/src/ReactOnRailsProNodeRenderer.tspackages/react-on-rails-pro-node-renderer/src/master.tspackages/react-on-rails-pro-node-renderer/src/shared/licenseCache.tspackages/react-on-rails-pro-node-renderer/src/shared/licenseFetcher.tspackages/react-on-rails-pro-node-renderer/src/shared/licenseRefreshChecker.tspackages/react-on-rails-pro-node-renderer/src/shared/licenseValidator.tspackages/react-on-rails-pro-node-renderer/tests/licenseCache.test.tspackages/react-on-rails-pro-node-renderer/tests/licenseFetcher.test.tspackages/react-on-rails-pro-node-renderer/tests/licenseRefreshChecker.test.tspackages/react-on-rails-pro-node-renderer/tests/licenseValidator.test.tsreact_on_rails_pro/LICENSE_SETUP.mdreact_on_rails_pro/README.mdreact_on_rails_pro/docs/configuration.mdreact_on_rails_pro/docs/installation.mdreact_on_rails_pro/lib/react_on_rails_pro.rbreact_on_rails_pro/lib/react_on_rails_pro/configuration.rbreact_on_rails_pro/lib/react_on_rails_pro/license_cache.rbreact_on_rails_pro/lib/react_on_rails_pro/license_fetcher.rbreact_on_rails_pro/lib/react_on_rails_pro/license_refresh_checker.rbreact_on_rails_pro/lib/react_on_rails_pro/license_validator.rbreact_on_rails_pro/spec/dummy/config/initializers/react_on_rails_pro.rbreact_on_rails_pro/spec/react_on_rails_pro/configuration_spec.rbreact_on_rails_pro/spec/react_on_rails_pro/license_cache_spec.rbreact_on_rails_pro/spec/react_on_rails_pro/license_fetcher_spec.rbreact_on_rails_pro/spec/react_on_rails_pro/license_validator_spec.rbreact_on_rails_pro/spec/react_on_rails_pro/spec_helper.rb
🧰 Additional context used
📓 Path-based instructions (2)
**/*.rb
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.rb: Runbundle exec rubocopand fix ALL violations before every commit/push
Runbundle exec rubocop(MANDATORY) before every commit to ensure zero offenses
Files:
react_on_rails_pro/spec/react_on_rails_pro/configuration_spec.rbreact_on_rails_pro/spec/react_on_rails_pro/license_validator_spec.rbreact_on_rails_pro/lib/react_on_rails_pro/license_validator.rbreact_on_rails_pro/lib/react_on_rails_pro/license_cache.rbreact_on_rails_pro/spec/react_on_rails_pro/license_cache_spec.rbreact_on_rails_pro/lib/react_on_rails_pro/license_refresh_checker.rbreact_on_rails_pro/spec/dummy/config/initializers/react_on_rails_pro.rbreact_on_rails_pro/lib/react_on_rails_pro/license_fetcher.rbreact_on_rails_pro/spec/react_on_rails_pro/spec_helper.rbreact_on_rails_pro/spec/react_on_rails_pro/license_fetcher_spec.rbreact_on_rails_pro/lib/react_on_rails_pro.rbreact_on_rails_pro/lib/react_on_rails_pro/configuration.rb
{package.json,webpack.config.js,packages/*/package.json,react_on_rails_pro/package.json}
📄 CodeRabbit inference engine (CLAUDE.md)
When resolving merge conflicts in build configuration files, verify file paths are correct by running
grep -r 'old/path' .and test affected scripts likepnpm run prepackbefore continuing the merge
Files:
packages/react-on-rails-pro-node-renderer/package.json
🧠 Learnings (25)
📓 Common learnings
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: Applies to /CHANGELOG_PRO.md : Update `/CHANGELOG_PRO.md` for user-visible changes in the Pro-only react_on_rails_pro gem and npm packages
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1875
File: lib/react_on_rails/utils.rb:112-124
Timestamp: 2025-10-23T17:22:01.074Z
Learning: In React on Rails, when Pro is installed but not licensed, the intended behavior is to raise an error on boot. The `react_on_rails_pro?` method validates licenses and should raise errors early (including during path resolution in methods like `server_bundle?`) to enforce licensing requirements rather than failing later with obscure errors.
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1781
File: node_package/src/ClientSideRenderer.ts:82-95
Timestamp: 2025-09-15T21:24:48.207Z
Learning: In React on Rails, the force_load feature includes both explicit `data-force-load="true"` usage and the ability to hydrate components during the page loading state (`document.readyState === 'loading'`). Both capabilities require a Pro license, so the condition `!railsContext.rorPro && (isComponentForceLoaded || document.readyState === 'loading')` correctly gates both scenarios.
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1644
File: lib/react_on_rails/helper.rb:190-197
Timestamp: 2025-02-18T13:08:01.477Z
Learning: RSC support validation in React on Rails Pro is handled through a chain of validations:
1. Pro version check in `run_stream_inside_fiber`
2. RSC support check during pack generation via `ReactOnRailsPro.configuration.enable_rsc_support`
3. RSC support validation during component registration
This makes additional validation in the helper methods unnecessary.
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: Applies to /CHANGELOG.md : Update `/CHANGELOG.md` for user-visible changes in the open-source react_on_rails gem and npm package (features, bug fixes, breaking changes, deprecations, performance improvements)
📚 Learning: 2025-12-19T18:57:59.314Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: Applies to /CHANGELOG_PRO.md : Update `/CHANGELOG_PRO.md` for user-visible changes in the Pro-only react_on_rails_pro gem and npm packages
Applied to files:
packages/react-on-rails-pro-node-renderer/src/master.tsreact_on_rails_pro/README.mdreact_on_rails_pro/spec/react_on_rails_pro/configuration_spec.rbreact_on_rails_pro/LICENSE_SETUP.mdpackages/react-on-rails-pro-node-renderer/src/shared/licenseValidator.tsreact_on_rails_pro/spec/react_on_rails_pro/license_validator_spec.rbreact_on_rails_pro/docs/installation.mdreact_on_rails_pro/lib/react_on_rails_pro/license_validator.rbreact_on_rails_pro/lib/react_on_rails_pro/license_cache.rbreact_on_rails_pro/spec/react_on_rails_pro/license_cache_spec.rbreact_on_rails_pro/lib/react_on_rails_pro/license_refresh_checker.rbpackages/react-on-rails-pro-node-renderer/package.jsonreact_on_rails_pro/spec/dummy/config/initializers/react_on_rails_pro.rbreact_on_rails_pro/lib/react_on_rails_pro/license_fetcher.rbreact_on_rails_pro/spec/react_on_rails_pro/spec_helper.rbpackages/react-on-rails-pro-node-renderer/src/shared/licenseFetcher.tsreact_on_rails_pro/spec/react_on_rails_pro/license_fetcher_spec.rbpackages/react-on-rails-pro-node-renderer/src/shared/licenseCache.tsreact_on_rails_pro/lib/react_on_rails_pro.rbreact_on_rails_pro/lib/react_on_rails_pro/configuration.rbreact_on_rails_pro/docs/configuration.mdpackages/react-on-rails-pro-node-renderer/src/ReactOnRailsProNodeRenderer.ts
📚 Learning: 2025-12-19T18:57:59.314Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: Applies to /CHANGELOG.md : Update `/CHANGELOG.md` for user-visible changes in the open-source react_on_rails gem and npm package (features, bug fixes, breaking changes, deprecations, performance improvements)
Applied to files:
react_on_rails_pro/README.mdreact_on_rails_pro/LICENSE_SETUP.mdreact_on_rails_pro/docs/installation.mdreact_on_rails_pro/lib/react_on_rails_pro/license_validator.rbreact_on_rails_pro/lib/react_on_rails_pro/license_cache.rbreact_on_rails_pro/spec/react_on_rails_pro/license_cache_spec.rbreact_on_rails_pro/lib/react_on_rails_pro/license_refresh_checker.rbpackages/react-on-rails-pro-node-renderer/package.jsonreact_on_rails_pro/spec/dummy/config/initializers/react_on_rails_pro.rbreact_on_rails_pro/lib/react_on_rails_pro/license_fetcher.rbpackages/react-on-rails-pro-node-renderer/src/shared/licenseCache.tsreact_on_rails_pro/lib/react_on_rails_pro.rbreact_on_rails_pro/lib/react_on_rails_pro/configuration.rbreact_on_rails_pro/docs/configuration.md
📚 Learning: 2025-10-23T17:22:01.074Z
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1875
File: lib/react_on_rails/utils.rb:112-124
Timestamp: 2025-10-23T17:22:01.074Z
Learning: In React on Rails, when Pro is installed but not licensed, the intended behavior is to raise an error on boot. The `react_on_rails_pro?` method validates licenses and should raise errors early (including during path resolution in methods like `server_bundle?`) to enforce licensing requirements rather than failing later with obscure errors.
Applied to files:
react_on_rails_pro/README.mdreact_on_rails_pro/spec/react_on_rails_pro/configuration_spec.rbreact_on_rails_pro/LICENSE_SETUP.mdreact_on_rails_pro/spec/react_on_rails_pro/license_validator_spec.rbreact_on_rails_pro/docs/installation.mdreact_on_rails_pro/lib/react_on_rails_pro/license_validator.rbreact_on_rails_pro/lib/react_on_rails_pro/license_cache.rbreact_on_rails_pro/spec/react_on_rails_pro/license_cache_spec.rbreact_on_rails_pro/lib/react_on_rails_pro/license_refresh_checker.rbpackages/react-on-rails-pro-node-renderer/tests/licenseValidator.test.tsreact_on_rails_pro/spec/dummy/config/initializers/react_on_rails_pro.rbreact_on_rails_pro/lib/react_on_rails_pro/license_fetcher.rbreact_on_rails_pro/spec/react_on_rails_pro/license_fetcher_spec.rbpackages/react-on-rails-pro-node-renderer/src/shared/licenseCache.tsreact_on_rails_pro/lib/react_on_rails_pro.rbreact_on_rails_pro/lib/react_on_rails_pro/configuration.rbreact_on_rails_pro/docs/configuration.md
📚 Learning: 2025-12-19T18:57:59.314Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: Applies to /{CHANGELOG.md,CHANGELOG_PRO.md} : Use format `[PR 1818](https://github.com/shakacode/react_on_rails/pull/1818) by [username](https://github.com/username)` in changelog entries (no hash in PR number)
Applied to files:
react_on_rails_pro/README.md
📚 Learning: 2025-09-15T21:24:48.207Z
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1781
File: node_package/src/ClientSideRenderer.ts:82-95
Timestamp: 2025-09-15T21:24:48.207Z
Learning: In React on Rails, the force_load feature includes both explicit `data-force-load="true"` usage and the ability to hydrate components during the page loading state (`document.readyState === 'loading'`). Both capabilities require a Pro license, so the condition `!railsContext.rorPro && (isComponentForceLoaded || document.readyState === 'loading')` correctly gates both scenarios.
Applied to files:
react_on_rails_pro/README.mdreact_on_rails_pro/spec/react_on_rails_pro/configuration_spec.rbreact_on_rails_pro/LICENSE_SETUP.mdreact_on_rails_pro/spec/react_on_rails_pro/license_validator_spec.rbreact_on_rails_pro/docs/installation.mdreact_on_rails_pro/lib/react_on_rails_pro/license_validator.rbreact_on_rails_pro/lib/react_on_rails_pro/license_cache.rbreact_on_rails_pro/lib/react_on_rails_pro/license_refresh_checker.rbreact_on_rails_pro/spec/dummy/config/initializers/react_on_rails_pro.rbreact_on_rails_pro/lib/react_on_rails_pro/license_fetcher.rbreact_on_rails_pro/spec/react_on_rails_pro/license_fetcher_spec.rbreact_on_rails_pro/lib/react_on_rails_pro.rbreact_on_rails_pro/lib/react_on_rails_pro/configuration.rbreact_on_rails_pro/docs/configuration.md
📚 Learning: 2025-12-19T18:57:59.314Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: Applies to sig/react_on_rails/**/*.rbs : RBS signatures in `sig/react_on_rails/` should be added for new Ruby files in `lib/react_on_rails/`, included in Steepfile, validated with `bundle exec rake rbs:validate`, and type-checked with `bundle exec rake rbs:steep`
Applied to files:
react_on_rails_pro/README.mdreact_on_rails_pro/lib/react_on_rails_pro/license_validator.rbreact_on_rails_pro/lib/react_on_rails_pro/license_cache.rbreact_on_rails_pro/spec/react_on_rails_pro/license_cache_spec.rbreact_on_rails_pro/lib/react_on_rails_pro/license_refresh_checker.rbreact_on_rails_pro/spec/dummy/config/initializers/react_on_rails_pro.rbreact_on_rails_pro/spec/react_on_rails_pro/spec_helper.rbreact_on_rails_pro/lib/react_on_rails_pro.rb
📚 Learning: 2025-04-26T21:55:55.874Z
Learnt from: alexeyr-ci2
Repo: shakacode/react_on_rails PR: 1732
File: spec/dummy/client/app-react16/startup/ReduxSharedStoreApp.client.jsx:40-44
Timestamp: 2025-04-26T21:55:55.874Z
Learning: In the react_on_rails project, files under `app-react16` directories are copied/moved to corresponding `/app` directories during the conversion process (removing the `-react16` suffix), which affects their relative import paths at runtime.
Applied to files:
react_on_rails_pro/README.mdreact_on_rails_pro/spec/dummy/config/initializers/react_on_rails_pro.rbreact_on_rails_pro/lib/react_on_rails_pro.rb
📚 Learning: 2025-02-18T13:08:01.477Z
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1644
File: lib/react_on_rails/helper.rb:190-197
Timestamp: 2025-02-18T13:08:01.477Z
Learning: RSC support validation in React on Rails Pro is handled through a chain of validations:
1. Pro version check in `run_stream_inside_fiber`
2. RSC support check during pack generation via `ReactOnRailsPro.configuration.enable_rsc_support`
3. RSC support validation during component registration
This makes additional validation in the helper methods unnecessary.
Applied to files:
react_on_rails_pro/README.mdreact_on_rails_pro/spec/react_on_rails_pro/configuration_spec.rbreact_on_rails_pro/LICENSE_SETUP.mdreact_on_rails_pro/docs/installation.mdreact_on_rails_pro/lib/react_on_rails_pro/license_validator.rbreact_on_rails_pro/lib/react_on_rails_pro/license_cache.rbreact_on_rails_pro/lib/react_on_rails_pro/license_refresh_checker.rbreact_on_rails_pro/spec/dummy/config/initializers/react_on_rails_pro.rbreact_on_rails_pro/lib/react_on_rails_pro.rbreact_on_rails_pro/lib/react_on_rails_pro/configuration.rbreact_on_rails_pro/docs/configuration.md
📚 Learning: 2025-02-12T16:38:06.537Z
Learnt from: Romex91
Repo: shakacode/react_on_rails PR: 1697
File: package-scripts.yml:28-28
Timestamp: 2025-02-12T16:38:06.537Z
Learning: The file `node_package/lib/ReactOnRails.full.js` is autogenerated during the build process and should not be present in the repository.
Applied to files:
react_on_rails_pro/README.mdreact_on_rails_pro/lib/react_on_rails_pro/license_cache.rbpackages/react-on-rails-pro-node-renderer/package.jsonreact_on_rails_pro/spec/dummy/config/initializers/react_on_rails_pro.rbreact_on_rails_pro/lib/react_on_rails_pro.rbreact_on_rails_pro/docs/configuration.md
📚 Learning: 2025-09-16T08:01:11.146Z
Learnt from: justin808
Repo: shakacode/react_on_rails PR: 1770
File: lib/generators/react_on_rails/templates/base/base/app/javascript/src/HelloWorld/ror_components/HelloWorld.client.jsx:2-2
Timestamp: 2025-09-16T08:01:11.146Z
Learning: React on Rails uses webpack CSS Modules configuration with namedExports: true, which requires the import syntax `import * as style from './file.module.css'` rather than the default export pattern. This configuration enables better tree shaking and bundle size optimization for CSS modules.
Applied to files:
react_on_rails_pro/README.mdreact_on_rails_pro/docs/installation.mdreact_on_rails_pro/spec/dummy/config/initializers/react_on_rails_pro.rbreact_on_rails_pro/lib/react_on_rails_pro.rbreact_on_rails_pro/lib/react_on_rails_pro/configuration.rbreact_on_rails_pro/docs/configuration.md
📚 Learning: 2025-12-19T18:57:59.314Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: Applies to react_on_rails/spec/dummy/e2e/playwright/e2e/**/*.spec.js : Use Playwright E2E tests in `react_on_rails/spec/dummy/e2e/playwright/` for React component integration testing. Tests automatically start Rails server on port 5017 before running
Applied to files:
react_on_rails_pro/spec/react_on_rails_pro/configuration_spec.rbpackages/react-on-rails-pro-node-renderer/tests/licenseRefreshChecker.test.tsreact_on_rails_pro/LICENSE_SETUP.mdpackages/react-on-rails-pro-node-renderer/tests/licenseFetcher.test.tsreact_on_rails_pro/spec/react_on_rails_pro/license_validator_spec.rbreact_on_rails_pro/spec/react_on_rails_pro/license_cache_spec.rbpackages/react-on-rails-pro-node-renderer/tests/licenseValidator.test.tsreact_on_rails_pro/spec/dummy/config/initializers/react_on_rails_pro.rbreact_on_rails_pro/spec/react_on_rails_pro/spec_helper.rbreact_on_rails_pro/spec/react_on_rails_pro/license_fetcher_spec.rbreact_on_rails_pro/docs/configuration.md
📚 Learning: 2025-02-13T14:29:49.267Z
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1644
File: spec/react_on_rails/utils_spec.rb:218-218
Timestamp: 2025-02-13T14:29:49.267Z
Learning: In RSpec tests, prefer using local variables over constants within test blocks to avoid constant redefinition warnings and maintain better test isolation.
Applied to files:
react_on_rails_pro/spec/react_on_rails_pro/configuration_spec.rbreact_on_rails_pro/spec/react_on_rails_pro/license_validator_spec.rbreact_on_rails_pro/spec/react_on_rails_pro/license_cache_spec.rbreact_on_rails_pro/spec/react_on_rails_pro/spec_helper.rb
📚 Learning: 2024-06-27T05:22:46.156Z
Learnt from: justin808
Repo: shakacode/react_on_rails PR: 1622
File: spec/dummy/spec/rake/assets_precompile_rake_spec.rb:12-12
Timestamp: 2024-06-27T05:22:46.156Z
Learning: When stubbing environment variables in RSpec tests, use `before` and `after` hooks to ensure that the original values are restored after the tests, preventing any side effects on other tests. Example provided by justin808:
```ruby
describe "My test" do
before do
original_value = ENV["VARIABLE_NAME"]
allow(ENV).to receive(:[]).with("VARIABLE_NAME").and_return("stubbed_value")
end
after do
allow(ENV).to receive(:[]).with("VARIABLE_NAME").and_call_original
ENV["VARIABLE_NAME"] = original_value
end
it "tests something" do
# Your test code here
end
end
```
This practice ensures test isolation and reliability.
Applied to files:
react_on_rails_pro/spec/react_on_rails_pro/configuration_spec.rbreact_on_rails_pro/spec/react_on_rails_pro/spec_helper.rb
📚 Learning: 2025-02-13T16:50:26.861Z
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1644
File: node_package/src/turbolinksUtils.ts:34-36
Timestamp: 2025-02-13T16:50:26.861Z
Learning: In React on Rails, when checking for Turbolinks version 5 using `turbolinksVersion5()`, always ensure `Turbolinks` exists first by checking `turbolinksInstalled()` to prevent TypeError when accessing properties.
Applied to files:
react_on_rails_pro/LICENSE_SETUP.md
📚 Learning: 2025-02-18T13:08:01.477Z
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1644
File: lib/react_on_rails/helper.rb:190-197
Timestamp: 2025-02-18T13:08:01.477Z
Learning: RSC support validation is handled in deeper level calls of the React on Rails Pro codebase, so it doesn't need to be validated again in the `rsc_payload_react_component` helper method.
Applied to files:
react_on_rails_pro/LICENSE_SETUP.md
📚 Learning: 2025-12-19T18:57:59.314Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: Applies to lib/react_on_rails/engine.rb : In Rails Engine code, DO NOT use `rake_tasks` block to explicitly load rake files from `lib/tasks/` - Rails::Engine automatically loads them. Only use `rake_tasks` block if tasks are in non-standard location, need programmatic generation, or need to pass context
Applied to files:
react_on_rails_pro/docs/installation.mdreact_on_rails_pro/spec/dummy/config/initializers/react_on_rails_pro.rbreact_on_rails_pro/lib/react_on_rails_pro.rb
📚 Learning: 2025-12-19T18:57:59.314Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: In IDE configuration, exclude these directories to prevent slowdowns: /coverage, /tmp, /gen-examples, /packages/react-on-rails/lib, /node_modules, /react_on_rails/spec/dummy/node_modules, /react_on_rails/spec/dummy/tmp, /react_on_rails/spec/dummy/app/assets/webpack, /react_on_rails/spec/dummy/log, /react_on_rails/spec/dummy/e2e/playwright-report, /react_on_rails/spec/dummy/test-results
Applied to files:
react_on_rails_pro/lib/react_on_rails_pro/license_cache.rbreact_on_rails_pro/spec/dummy/config/initializers/react_on_rails_pro.rbreact_on_rails_pro/docs/configuration.md
📚 Learning: 2025-12-19T18:57:59.314Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: Install Playwright browsers with `cd react_on_rails/spec/dummy && pnpm playwright install --with-deps` before running E2E tests
Applied to files:
packages/react-on-rails-pro-node-renderer/tests/licenseValidator.test.ts
📚 Learning: 2025-12-19T18:57:59.314Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: Applies to react_on_rails/spec/dummy/e2e/playwright/e2e/**/*.spec.js : Use `app('clean')`, `appEval(code)`, `appFactories(options)`, and `appScenario(name)` helpers from cypress-on-rails in Playwright tests for Rails integration
Applied to files:
packages/react-on-rails-pro-node-renderer/tests/licenseValidator.test.tsreact_on_rails_pro/spec/react_on_rails_pro/spec_helper.rb
📚 Learning: 2025-12-19T18:57:59.314Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: Applies to react_on_rails/spec/dummy/e2e/playwright/app_commands/**/*.rb : Create custom Rails app commands in `e2e/playwright/app_commands/` directory with .rb files for reusable helpers in Playwright tests
Applied to files:
react_on_rails_pro/spec/dummy/config/initializers/react_on_rails_pro.rbreact_on_rails_pro/spec/react_on_rails_pro/spec_helper.rb
📚 Learning: 2024-10-08T20:53:47.076Z
Learnt from: theforestvn88
Repo: shakacode/react_on_rails PR: 1620
File: spec/dummy/client/app/startup/HelloTurboStream.jsx:3-3
Timestamp: 2024-10-08T20:53:47.076Z
Learning: The `RailsContext` import in `spec/dummy/client/app/startup/HelloTurboStream.jsx` is used later in the project, as clarified by the user theforestvn88.
Applied to files:
react_on_rails_pro/spec/react_on_rails_pro/spec_helper.rb
📚 Learning: 2025-12-19T18:57:59.314Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: Shakapacker configuration should be tested by creating debug scripts in dummy app root (debug-webpack-rules.js, debug-webpack-with-config.js) to inspect webpack rules before committing configuration changes
Applied to files:
react_on_rails_pro/spec/react_on_rails_pro/spec_helper.rb
📚 Learning: 2025-07-08T05:57:29.630Z
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1745
File: node_package/src/RSCRequestTracker.ts:8-14
Timestamp: 2025-07-08T05:57:29.630Z
Learning: The global `generateRSCPayload` function in React on Rails Pro (RORP) is provided by the framework during rendering requests, not implemented in application code. The `declare global` statements are used to document the expected interface that RORP will inject at runtime.
Applied to files:
react_on_rails_pro/lib/react_on_rails_pro.rbreact_on_rails_pro/docs/configuration.md
📚 Learning: 2025-02-13T16:50:47.848Z
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1644
File: node_package/src/clientStartup.ts:18-21
Timestamp: 2025-02-13T16:50:47.848Z
Learning: In the react_on_rails module, the `reactOnRailsPageUnloaded` function in clientStartup.ts is intentionally kept private as it's only used internally as a callback for `onPageUnloaded`.
Applied to files:
react_on_rails_pro/lib/react_on_rails_pro.rbreact_on_rails_pro/docs/configuration.md
🧬 Code graph analysis (10)
packages/react-on-rails-pro-node-renderer/src/master.ts (2)
packages/react-on-rails-pro-node-renderer/src/shared/configBuilder.ts (1)
Config(25-89)packages/react-on-rails-pro-node-renderer/src/shared/licenseValidator.ts (1)
getValidatedLicenseData(204-247)
react_on_rails_pro/spec/react_on_rails_pro/configuration_spec.rb (1)
react_on_rails_pro/lib/react_on_rails_pro/configuration.rb (5)
configure(4-7)configuration(9-38)license_api_url(217-222)license_key(202-207)auto_refresh_enabled?(234-236)
packages/react-on-rails-pro-node-renderer/src/shared/licenseRefreshChecker.ts (2)
packages/react-on-rails-pro-node-renderer/src/shared/licenseCache.ts (4)
getFetchedAt(150-161)getExpiresAt(167-178)writeCache(105-136)getCachedToken(141-144)packages/react-on-rails-pro-node-renderer/src/shared/licenseFetcher.ts (2)
isAutoRefreshEnabled(51-60)fetchLicense(66-131)
packages/react-on-rails-pro-node-renderer/tests/licenseFetcher.test.ts (1)
packages/react-on-rails-pro-node-renderer/src/shared/licenseFetcher.ts (2)
isAutoRefreshEnabled(51-60)fetchLicense(66-131)
react_on_rails_pro/spec/react_on_rails_pro/license_validator_spec.rb (2)
react_on_rails_pro/lib/react_on_rails_pro/license_validator.rb (2)
validated_license_data!(6-200)validated_license_data!(15-44)react_on_rails_pro/lib/react_on_rails_pro/license_refresh_checker.rb (3)
should_check_for_refresh?(41-54)maybe_refresh_license(13-21)seed_cache_if_needed(26-39)
react_on_rails_pro/spec/react_on_rails_pro/license_cache_spec.rb (2)
react_on_rails_pro/lib/react_on_rails_pro/license_cache.rb (6)
cache_dir(75-77)write(27-39)cache_path(79-81)token(41-43)fetched_at(45-50)expires_at(52-57)react_on_rails_pro/lib/react_on_rails_pro/configuration.rb (1)
configure(4-7)
react_on_rails_pro/lib/react_on_rails_pro/license_refresh_checker.rb (2)
react_on_rails_pro/lib/react_on_rails_pro/configuration.rb (2)
configuration(9-38)auto_refresh_enabled?(234-236)react_on_rails_pro/lib/react_on_rails_pro/license_cache.rb (4)
write(27-39)token(41-43)expires_at(52-57)fetched_at(45-50)
react_on_rails_pro/lib/react_on_rails_pro/license_fetcher.rb (1)
react_on_rails_pro/lib/react_on_rails_pro/configuration.rb (3)
configuration(9-38)auto_refresh_enabled?(234-236)license_api_url(217-222)
packages/react-on-rails-pro-node-renderer/src/shared/licenseFetcher.ts (2)
react_on_rails_pro/spec/dummy/client/node-renderer.js (1)
process(5-5)react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/RouterApp.server.jsx (1)
controller(10-10)
react_on_rails_pro/spec/react_on_rails_pro/license_fetcher_spec.rb (2)
react_on_rails_pro/lib/react_on_rails_pro/license_fetcher.rb (1)
api_url(44-46)react_on_rails_pro/lib/react_on_rails_pro/configuration.rb (2)
configure(4-7)license_api_url(217-222)
🪛 GitHub Actions: Check Markdown Links
react_on_rails_pro/LICENSE_SETUP.md
[error] 1-1: Network error: Connection failed. Check network connectivity and firewall settings
🪛 Gitleaks (8.30.0)
packages/react-on-rails-pro-node-renderer/tests/licenseCache.test.ts
[high] 50-50: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
[high] 70-70: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: rspec-dummy-app-node-renderer
- GitHub Check: dummy-app-node-renderer-e2e-tests
🔇 Additional comments (51)
packages/react-on-rails-pro-node-renderer/package.json (1)
23-23: Verify package version validity and security.The
async-retryversion (^1.3.3) is current and has no known security vulnerabilities. Version 1.3.3 is the latest available release (published 2021-08-17), is not deprecated, and is licensed under MIT. The corresponding type definitions (@types/async-retry@1.4.9) are compatible and properly included.react_on_rails_pro/spec/react_on_rails_pro/spec_helper.rb (1)
24-26: LGTM! WebMock configuration is appropriate.The addition of WebMock with its HTTPX adapter enables proper HTTP request stubbing for the new license-fetching tests, which is essential for testing the automatic license renewal feature without making real HTTP calls.
react_on_rails_pro/lib/react_on_rails_pro.rb (1)
13-15: LGTM! Module requires are appropriately ordered.The new license-related modules are correctly placed between
license_public_keyandlicense_validator, following a logical dependency order.react_on_rails_pro/docs/installation.md (1)
50-51: LGTM! Clear guidance for paid subscribers.The note appropriately directs paid subscribers to use the dedicated license key environment variable for automatic renewal, with a reference to detailed setup documentation.
packages/react-on-rails-pro-node-renderer/src/master.ts (1)
14-17: LGTM! Async transformation is appropriate.Converting
masterRunto async and awaitinggetValidatedLicenseData()is necessary to support the automatic license refresh flow, which includes HTTP requests and cache operations. The validation correctly blocks startup to ensure a valid license before forking workers.react_on_rails_pro/spec/dummy/config/initializers/react_on_rails_pro.rb (1)
45-50: LGTM! Helpful configuration example.The commented configuration block provides clear guidance for developers testing the license auto-refresh feature locally with a licensing app, without affecting the existing test suite.
packages/react-on-rails-pro-node-renderer/src/ReactOnRailsProNodeRenderer.ts (1)
45-45: LGTM! Consistent async handling.Awaiting
master.default(config)is consistent with the async transformation of the master initialization process and ensures license validation completes before the renderer is considered ready.react_on_rails_pro/README.md (1)
297-298: LGTM! Consistent documentation guidance.The note is consistent with the similar guidance in installation.md and appropriately directs paid subscribers to use the dedicated license key environment variable for automatic renewal.
react_on_rails_pro/spec/react_on_rails_pro/configuration_spec.rb (4)
301-320: LGTM! Proper test setup with ENV cleanup.The test suite correctly includes an
afterhook to clean up environment variables, following best practices for test isolation. Theauto_refresh_licensetests appropriately cover defaults and mutability.
322-338: LGTM! Complete coverage for license_api_url configuration.The tests appropriately verify both the default value and customization of the
license_api_urlconfiguration option.
340-379: LGTM! Comprehensive license_key precedence testing.The tests thoroughly cover the license key sourcing logic, including environment variable precedence and edge cases like empty ENV values, matching the implementation in
configuration.rb.
381-427: LGTM! Thorough auto_refresh_enabled? predicate testing.The tests comprehensively verify the
auto_refresh_enabled?logic, covering all combinations ofauto_refresh_licenseandlicense_keystates, including edge cases like empty strings and environment-based configuration.react_on_rails_pro/docs/configuration.md (1)
165-186: LGTM! Clear documentation for auto-refresh configuration.The new LICENSE AUTO-REFRESH CONFIGURATION section is well-structured and provides clear guidance on the three new configuration options. The cross-reference to LICENSE_SETUP.md for detailed documentation is appropriate.
react_on_rails_pro/spec/react_on_rails_pro/license_fetcher_spec.rb (1)
1-185: LGTM! Comprehensive test coverage for license fetcher.The test suite provides thorough coverage of the LicenseFetcher module:
- Configuration-driven behavior (auto_refresh on/off, license_key presence)
- Successful API fetch with proper Authorization header
- All major error scenarios (401, 404, 500, timeout, network errors, invalid JSON)
- ENV variable override support
- Proper test isolation with WebMock
packages/react-on-rails-pro-node-renderer/tests/licenseFetcher.test.ts (1)
1-206: LGTM! Thorough test coverage for Node license fetcher.The test suite provides comprehensive coverage:
- isAutoRefreshEnabled logic with all configuration combinations and case-insensitive values
- fetchLicense success and error paths
- Retry logic verification using fake timers
- User-Agent header inclusion
- Custom API URL support
- Proper mocking and cleanup
react_on_rails_pro/lib/react_on_rails_pro/license_validator.rb (2)
29-31: LGTM! Cache seeding on first boot.The call to
LicenseRefreshChecker.seed_cache_if_neededafter successful validation ensures the cache is populated on first boot, enabling subsequent auto-refresh checks.
158-180: LGTM! Cache-first license loading with auto-refresh.The updated
load_license_stringmethod correctly implements:
- Pre-refresh check via
maybe_refresh_license- Cache-first retrieval when auto-refresh is enabled
- Fallback to ENV and config file sources
- Clear priority documentation in comments
react_on_rails_pro/LICENSE_SETUP.md (2)
79-142: LGTM! Comprehensive auto-refresh documentation.The new "Automatic License Renewal" section provides clear guidance:
- Helpful comparison table (LICENSE_KEY vs LICENSE_TOKEN)
- Straightforward setup instructions
- Clear explanation of refresh schedule thresholds
- Privacy assurances about data transmission
- Instructions for disabling auto-refresh in air-gapped environments
350-360: LGTM! Helpful FAQ additions.The new FAQ entries clearly distinguish LICENSE from LICENSE_KEY and guide users on when to use each option.
react_on_rails_pro/spec/react_on_rails_pro/license_validator_spec.rb (4)
258-323: LGTM! Token source priority tests verify cache-first behavior.The tests correctly validate the priority order:
- Cached token (when auto-refresh enabled)
- ENV variable (fallback)
- Cache seeding when cache is empty
325-471: LGTM! Comprehensive boundary testing for refresh thresholds.The
should_check_for_refresh?tests thoroughly cover all critical boundaries:
30 days (no checks)
- 30 days boundary (weekly checks)
- 8 days boundary (still weekly)
- 7 days boundary (daily checks)
- 3 days (daily checks)
- nil fetched_at (triggers fetch)
The comment on lines 346-349 explaining the timing race with 31 days demonstrates thoughtful test design.
473-538: LGTM! maybe_refresh_license behavior properly tested.Tests verify:
- No fetch when auto_refresh disabled or should_check_for_refresh? returns false
- Cache write on successful fetch
- No cache write on fetch failure
540-601: LGTM! seed_cache_if_needed covers all scenarios.Tests validate seeding logic across:
- auto_refresh disabled (no write)
- Cache already has token (no write)
- Cache empty with ENV token (write with token and expiry)
- No token available (no write)
packages/react-on-rails-pro-node-renderer/tests/licenseCache.test.ts (1)
1-254: LGTM! Comprehensive cache test coverage.The test suite thoroughly validates:
- readCache: non-existent files, license_key_hash validation, valid cache, JSON errors
- writeCache: directory creation, correct data structure, file permissions (0600), error handling
- Accessor functions: getCachedToken, getFetchedAt, getExpiresAt
The static analysis warnings on lines 50 and 70 are false positives—these are test JWT token examples, not real secrets.
react_on_rails_pro/spec/react_on_rails_pro/license_cache_spec.rb (1)
1-283: LGTM! Thorough cache implementation tests.The test suite provides comprehensive coverage:
- read: all edge cases including hash validation, ENV override, invalid data
- write: data merging, hash/timestamp addition, permissions, directory creation, error handling
- Accessors (token, fetched_at, expires_at): valid and invalid scenarios
The use of FakeFS for filesystem isolation and time freezing for deterministic timestamps demonstrates solid testing practices.
react_on_rails_pro/lib/react_on_rails_pro/license_fetcher.rb (1)
16-36: Implementation looks solid overall.The fetch method correctly:
- Guards with
auto_refresh_enabled?check- Uses HTTPX retry plugin appropriately
- Includes timeout and proper headers
- Handles error responses gracefully
- Returns nil on failures for graceful fallback
packages/react-on-rails-pro-node-renderer/tests/licenseRefreshChecker.test.ts (3)
1-45: Comprehensive test setup with proper mock isolation.The test file correctly:
- Mocks dependencies before importing the module under test
- Resets environment variables and mocks in
beforeEach- Restores all mocks in
afterEach- Suppresses console output during tests
47-109: Thorough coverage of refresh timing logic.The
shouldCheckForRefreshtests appropriately cover:
- No expiry data case
- Far expiry (>30 days) - no refresh needed
- 30-day window with weekly check logic
- 7-day window with daily check logic
- Never-fetched edge case
131-150: Verify the expected cache write payload matches the implementation.The test expects
writeCacheto be called with onlytokenandexpires_at, but looking atlicenseCache.tsline 104-135,writeCachealso addsfetched_atandlicense_key_hashinternally. Ensure the test's expectation aligns with the actual function signature.packages/react-on-rails-pro-node-renderer/src/shared/licenseRefreshChecker.ts (3)
36-55: Consider edge case when exactly on 7-day or 30-day boundary.When
daysUntilExpiryequals exactly 7 or 30, the<=conditions work correctly. However,Math.floortruncates, so a license expiring in 7.9 days would show as 7 days. This seems intentional but worth noting for clarity.The logic is correct for the documented behavior.
61-79: Clean async orchestration with proper guards.The function:
- Checks
isAutoRefreshEnabled()first- Then checks
shouldCheckForRefresh()to avoid unnecessary API calls- Only writes to cache on successful fetch
- Returns void rather than success/failure, relying on cache state
112-133: Seed function correctly handles first-boot scenario.The function properly:
- Guards on auto-refresh being enabled
- Avoids overwriting existing cache
- Seeds from ENV or file
- Converts Unix timestamp to ISO string for cache storage
packages/react-on-rails-pro-node-renderer/src/shared/licenseValidator.ts (4)
5-7: Clean integration of auto-refresh modules.The new imports properly bring in the required functionality for cache-first loading and refresh orchestration.
88-96: Cache-first loading when auto-refresh is enabled.Good pattern: when auto-refresh is enabled, prefer the cached (potentially refreshed) token over the static ENV/file token. This ensures users benefit from renewed licenses.
204-227: Async flow integrates refresh before validation.The sequence is correct:
- Return cached data if already validated
- Try auto-refresh if enabled and near expiry
- Load and decode license
- Validate license data
- Cache results
- Seed cache for future boots
One observation:
seedCacheIfNeededis called even if the token came from cache (since validation passed). This is harmless but could be optimized to skip ifisAutoRefreshEnabled() && getCachedToken()was already true inloadLicenseString.
255-273: Async wrappers for public API.The
isEvaluationandgetGraceDaysRemainingfunctions correctly awaitgetValidatedLicenseData()before accessing cached data.packages/react-on-rails-pro-node-renderer/src/shared/licenseCache.ts (2)
33-35: Truncated hash provides reasonable security.Using the first 16 characters of SHA-256 (64 bits) provides sufficient collision resistance for cache invalidation purposes while keeping storage compact.
150-178: Accessors handle errors defensively.Both
getFetchedAtandgetExpiresAtproperly handle:
- Missing cache data
- Invalid date strings (via try-catch)
The pattern of returning
nullon any error is appropriate for optional cache data.packages/react-on-rails-pro-node-renderer/tests/licenseValidator.test.ts (3)
11-21: Proper mock isolation for auto-refresh modules.The mocks correctly disable auto-refresh behavior during tests:
isAutoRefreshEnabledreturnsfalsegetCachedTokenreturnsnullmaybeRefreshLicenseresolves immediatelyseedCacheIfNeededis a no-opThis ensures tests focus on license validation logic without refresh side effects.
83-94: Re-mocking afterresetModulesis necessary.Since
jest.resetModules()clears the module cache, the mocks need to be re-established usingjest.doMock. This is the correct pattern for dynamic module mocking.
109-124: Test correctly updated for async API.The test properly:
- Uses
asyncfunction declaration- Awaits
getValidatedLicenseData()- Validates the returned data structure
packages/react-on-rails-pro-node-renderer/src/shared/licenseFetcher.ts (2)
51-60: Auto-refresh enabled logic is correct.The function properly:
- Requires a license key to be set
- Defaults to enabled unless explicitly set to 'false'
- Uses case-insensitive comparison
80-118: Robust retry implementation with proper timeout handling.The implementation correctly:
- Uses AbortController with setTimeout for request timeout
- Clears timeout in finally block to prevent leaks
- Bails immediately on auth errors (no point retrying)
- Retries on transient failures (non-200 status)
- Logs retry attempts with error context
react_on_rails_pro/lib/react_on_rails_pro/configuration.rb (2)
65-68: Default values align with PR requirements.
DEFAULT_AUTO_REFRESH_LICENSE = trueenables auto-refresh by defaultDEFAULT_LICENSE_API_URLpoints to the licensing service- TODO comment appropriately notes pending deployment
199-236: Configuration accessors with proper ENV override pattern.The methods correctly:
- Check environment variables first (consistent with 12-factor app principles)
- Fall back to configured values
- Provide default for API URL
- Combine both conditions in
auto_refresh_enabled?Environment variable names (
REACT_ON_RAILS_PRO_LICENSE_KEY,REACT_ON_RAILS_PRO_LICENSE_API_URL) match the TypeScript implementation inlicenseFetcher.tsandlicenseCache.ts.react_on_rails_pro/lib/react_on_rails_pro/license_cache.rb (3)
15-25: LGTM on the read method structure.The method correctly handles cache file absence, JSON parsing errors, and validates the cached data against the current license key before returning. Good defensive programming with the rescue clause.
68-73: LGTM on hash truncation for cache validation.Using the first 16 hex characters (64 bits) of SHA256 is sufficient for detecting license key changes. This isn't used for cryptographic security, just cache invalidation.
75-81: LGTM on cache path configuration.Using
Rails.root.join("tmp")is appropriate for cache files. The Pathname-based approach is clean and handles path construction correctly.react_on_rails_pro/lib/react_on_rails_pro/license_refresh_checker.rb (3)
13-21: LGTM on refresh orchestration.The method has clean guard clauses and properly delegates to
LicenseFetcherandLicenseCache. The graceful fallback behavior (returning early on nil response) aligns with the PR objectives.
41-54: LGTM on refresh timing logic.The implementation correctly follows the documented refresh schedule. The approach of returning
falsewhenexpires_atis nil is a safe default.
56-61: LGTM on cooldown enforcement.Returning
truewhenfetched_atis nil correctly allows the first fetch attempt. The time comparison is straightforward.
| expires_at = Time.at(license_data["exp"]) | ||
|
|
||
| LicenseCache.write( | ||
| "token" => token, | ||
| "expires_at" => expires_at.iso8601 | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add guard for missing or invalid exp claim.
If license_data["exp"] is nil or not a valid numeric timestamp, Time.at(nil) will raise a TypeError. Consider adding a guard clause.
🔎 Suggested fix
def seed_cache_if_needed(license_data)
return unless ReactOnRailsPro.configuration.auto_refresh_enabled?
return if LicenseCache.token.present? # Cache already exists
token = load_token_from_env_or_file
return unless token
+ exp = license_data["exp"]
+ return unless exp.is_a?(Numeric)
+
- expires_at = Time.at(license_data["exp"])
+ expires_at = Time.at(exp)
LicenseCache.write(
"token" => token,
"expires_at" => expires_at.iso8601
)
end🤖 Prompt for AI Agents
In react_on_rails_pro/lib/react_on_rails_pro/license_refresh_checker.rb around
lines 33 to 38, add a guard that verifies license_data["exp"] is present and a
valid integer timestamp before calling Time.at; attempt to convert the claim
with Integer(license_data["exp"]) (rescuing ArgumentError/TypeError) and, on
failure, log or handle the invalid/missing claim and avoid calling Time.at (do
not write an invalid expires_at into LicenseCache), otherwise compute expires_at
= Time.at(valid_exp) and write the token and expires_at.iso8601 to the cache.
Code Review: Automatic License Renewal FeatureGreat work on this comprehensive feature! The implementation is well-designed and thoroughly tested (118 new tests!). However, I've identified 2 critical race conditions that need to be addressed before merge, plus several medium-priority improvements. Critical Issues (Must Fix)1. Race Condition: Non-Atomic Cache File WritesSeverity: HIGH Problem: Multiple processes can simultaneously write to cache file, causing corrupted JSON. Current Code: File.write(cache_path, JSON.pretty_generate(cache_data))
File.chmod(0o600, cache_path) # NOT atomicFix: Use atomic rename pattern:
Add test for concurrent writes to verify no corruption. 2. Race Condition: Duplicate API Fetch on StartupSeverity: MEDIUM Problem: During deployment, multiple processes trigger simultaneous API fetches (10+ workers × 2 retries = 20-30 requests). Fix: Add file-based lock using File.flock(LOCK_NB) to deduplicate API calls. Important Issues (Should Fix)3. Security DocumentationAdd best practices to LICENSE_SETUP.md:
4. Blocking Network Call on Boot
5. ObservabilityAdd structured logging for refresh events, expiry dates, and success/failure status. Nice-to-Have Improvements
Strengths
SummaryMust fix before merge:
Should fix: Security docs, performance, observability Once the critical race conditions are addressed, this will be a robust feature. Great work! 🎉 |
Comprehensive Code ReviewI've thoroughly reviewed all code changes in this PR. Overall, this is high-quality code with excellent test coverage and thoughtful architecture. Here are my findings: 🔴 Critical Issues - Must Fix Before Merge1. Negative Days Until Expiry BugLocation: Issue: If the license is already expired, days_until_expiry = ((expires_at - Time.now) / 1.day).to_i
# If license expired 1 day ago: days_until_expiry = -1
# This matches: days_until_expiry <= 7 ✅ (triggers daily refresh)This could trigger daily refresh attempts for expired licenses indefinitely. Fix: Add explicit check in both implementations: return false if days_until_expiry < 0 # Don't refresh if already expired2. File Permission Race ConditionLocation: Issue: Between File.write(cache_path, JSON.pretty_generate(cache_data))
File.chmod(0o600, cache_path) # File is publicly readable until this lineFix: Use atomic write with proper permissions: # Ruby
File.write(cache_path, JSON.pretty_generate(cache_data), perm: 0o600)
# TypeScript
fs.writeFileSync(cachePath, JSON.stringify(cacheData, null, 2), { mode: 0o600 });🟡 High Priority - Should Fix Soon3. Thread Safety: File Cache Race ConditionLocation: All cache operations Issue: In multi-threaded Rails (Puma) or multi-process Node Renderer, multiple processes might:
Fix: Add file locking: def write(data)
File.open(cache_path, File::RDWR | File::CREAT, 0o600) do |f|
f.flock(File::LOCK_EX)
f.write(JSON.pretty_generate(cache_data))
f.flush
end
endFor TypeScript, consider using the 4. Blocking Startup on Network DelayLocation: Issue: License validation blocks Node Renderer startup: await getValidatedLicenseData(); // Blocks for up to 5-15 seconds if network is slowRecommendation: Make refresh non-blocking: // Validate with cached/ENV license first (fast)
const licenseData = await getValidatedLicenseData();
// Trigger async refresh in background (don't block startup)
maybeRefreshLicense().catch(err => log.warn('Background refresh failed:', err));🟢 Minor Improvements5. TypeScript: Unreachable CodeLocation: bail(new UnauthorizedError());
throw new UnauthorizedError(); // TypeScript needs this - but bail() already throwsIf 6. Ruby: Explicit HTTP Status HandlingLocation: Consider matching TypeScript's explicit 401/403 handling: case response.status
when 401, 403
Rails.logger.error { "[ReactOnRailsPro] License fetch unauthorized" }
return nil
when 200
# success
else
Rails.logger.warn { "[ReactOnRailsPro] License fetch failed: HTTP #{response.status}" }
return nil
end7. Security: Add HTTPS ValidationLocation: Configuration Add validation to prevent accidental HTTP usage in production: if (!url.startsWith('https://') && process.env.NODE_ENV === 'production') {
throw new Error('License API URL must use HTTPS in production');
}✅ StrengthsExcellent work on:
Test coverage is particularly impressive:
📋 Missing Test Cases (Optional)Consider adding:
Overall AssessmentThis is production-ready code with the critical fixes applied. The architecture is solid, error handling is thorough, and test coverage is excellent. Main concerns are edge cases around thread safety and expired licenses. Recommended merge strategy:
Great work on this feature! 🎉 |
Code Review: Automatic License RenewalGreat work on this feature! The implementation is well-structured with comprehensive test coverage. Critical Issues1. HTTPX Thread Safety 2. Missing clearTimeout in TypeScript 3. TODO Comment Security Concerns1. Timing Attack - Cache validation uses direct string comparison. Use OpenSSL.fixed_length_secure_compare instead. 2. Weak Hash - Only 16 chars of SHA256 used (64 bits). Use at least 32 characters (128 bits). Bugs1. Race Condition - maybe_refresh_license called before seed_cache_if_needed on first boot. Cache is empty so refresh timing cannot be determined. 2. File I/O - Each cache accessor reads entire JSON file. Add memoization. RecommendationsMust fix: HTTPX thread-safety, clearTimeout, TODO comment Overall: Excellent design and test coverage. Address critical issues before merge! |
Code Review: Automatic License Renewal FeatureI've reviewed PR #2254 and overall this is a well-implemented feature with excellent test coverage. Here's my detailed feedback: Strengths1. Excellent Test Coverage
2. Strong Security Practices
3. Good Architecture
4. Documentation
Issues and RecommendationsHIGH PRIORITY1. Race Condition in Cache File OperationsLocation: licenseCache.ts:126 and license_cache.rb:82 Issue: The cache read/write operations are not atomic. Multiple Node processes could read cache simultaneously, both trigger refresh, race to write new token, and potentially corrupt the cache file. Recommendation: Use atomic write-rename pattern. Write to a temp file first, then atomically rename it. This is the standard pattern for safe file updates. 2. Missing Cache Directory PermissionsLocation: licenseCache.ts:130 Issue: If mkdirSync fails silently, chmodSync might fail on the cache file. Recommendation: Set directory mode during creation: fs.mkdirSync(cacheDir, { recursive: true, mode: 0o700 }) 3. Async/Await ConsistencyLocation: master.ts:14-17 Issue: While you correctly made masterRun async and await getValidatedLicenseData(), the function signature change could break existing code that doesn't await the result. Recommendation: Check if any code calls masterRun() without awaiting. Document as breaking change if needed. MEDIUM PRIORITY4. HTTP Retry Logic DifferencesRuby version uses HTTPX with fixed 1s delay between retries. TypeScript version uses async-retry with exponential backoff. Issue: Inconsistent retry behavior between implementations. Recommendation: Align retry strategies. Exponential backoff is better for production systems. 5. Cache Timestamp Parsing Edge CaseLocation: licenseCache.ts:153-158 and license_cache.rb:53-58 Issue: If cache contains invalid ISO8601 date, parsing fails silently and returns null. This could mask data corruption. Recommendation: Log warnings for invalid timestamps to help debug cache corruption issues. 6. Environment Variable Type SafetyLocation: licenseFetcher.ts:57 Issue: autoRefreshSetting?.toLowerCase() !== 'false' means any typo (fasle, False, FALSE) will enable auto-refresh. Recommendation: Also check for '0' as falsy value for better robustness. LOW PRIORITY7. HTTPX Dependency DocumentationLocation: license_fetcher.rb:3 Issue: The PR adds httpx as a dependency but doesn't mention it in the PR description. Recommendation: Verify httpx is in react_on_rails_pro.gemspec and document the new dependency. 8. Test Mock CleanupLocation: licenseCache.test.ts:32, licenseFetcher.test.ts:14 Issue: console.warn is mocked but output isn't verified in all tests where warnings are expected. Recommendation: Add assertions for warning messages where applicable. Security Review
Performance ConsiderationsPositive:
Potential concern:
Testing RecommendationsGreat coverage overall! A few additional test cases to consider:
Documentation ReviewLICENSE_SETUP.md is excellent! Clear, comprehensive, addresses common questions. Minor suggestions:
Pre-Merge ChecklistBefore merging, please verify:
Overall AssessmentQuality: 5/5 Recommendation: Approve with minor fixes The implementation is solid and well-tested. The race condition issue should be addressed before production deployment, but it's not a blocker for merge if you're comfortable with the risk (unlikely to manifest in typical deployments). Excellent work on the parallel implementation (Ruby + TypeScript), comprehensive testing, and thorough documentation! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (1)
packages/react-on-rails-pro-node-renderer/tests/licenseFetcher.test.ts (1)
56-202: Strong test coverage with minor gaps.The test suite is well-structured and covers most critical paths including retry logic, error handling, and header verification. The use of fake timers for retry tests is appropriate.
However, consider adding tests for:
- 403 Forbidden status: The implementation treats 403 the same as 401 (no retry), but only 401 is tested.
- Request timeout: The implementation uses
AbortControllerwith a timeout, but there's no explicit test for timeout scenarios.📝 Suggested additional test cases
Add a test for 403 status:
it('returns null on 403 forbidden', async () => { process.env.REACT_ON_RAILS_PRO_LICENSE_KEY = 'lic_invalid'; fetchSpy.mockResolvedValueOnce({ status: 403, }); const result = await fetchLicense(); expect(result).toBeNull(); expect(fetchSpy).toHaveBeenCalledTimes(1); // No retry });Add a test for timeout:
it('returns null on request timeout after retries', async () => { jest.useFakeTimers(); process.env.REACT_ON_RAILS_PRO_LICENSE_KEY = 'lic_test123'; fetchSpy.mockImplementation(() => new Promise((_, reject) => setTimeout(() => reject(new Error('Aborted')), 15000) ) ); const resultPromise = fetchLicense(); await jest.runAllTimersAsync(); const result = await resultPromise; expect(result).toBeNull(); expect(fetchSpy).toHaveBeenCalledTimes(3); });
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
.lychee.tomlpackages/react-on-rails-pro-node-renderer/tests/licenseFetcher.test.ts
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: Applies to /CHANGELOG_PRO.md : Update `/CHANGELOG_PRO.md` for user-visible changes in the Pro-only react_on_rails_pro gem and npm packages
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1875
File: lib/react_on_rails/utils.rb:112-124
Timestamp: 2025-10-23T17:22:01.074Z
Learning: In React on Rails, when Pro is installed but not licensed, the intended behavior is to raise an error on boot. The `react_on_rails_pro?` method validates licenses and should raise errors early (including during path resolution in methods like `server_bundle?`) to enforce licensing requirements rather than failing later with obscure errors.
📚 Learning: 2025-12-19T18:57:59.314Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: In IDE configuration, exclude these directories to prevent slowdowns: /coverage, /tmp, /gen-examples, /packages/react-on-rails/lib, /node_modules, /react_on_rails/spec/dummy/node_modules, /react_on_rails/spec/dummy/tmp, /react_on_rails/spec/dummy/app/assets/webpack, /react_on_rails/spec/dummy/log, /react_on_rails/spec/dummy/e2e/playwright-report, /react_on_rails/spec/dummy/test-results
Applied to files:
.lychee.toml
📚 Learning: 2025-12-19T18:57:59.314Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: Applies to react_on_rails/spec/dummy/e2e/playwright/e2e/**/*.spec.js : Use Playwright E2E tests in `react_on_rails/spec/dummy/e2e/playwright/` for React component integration testing. Tests automatically start Rails server on port 5017 before running
Applied to files:
packages/react-on-rails-pro-node-renderer/tests/licenseFetcher.test.ts
🧬 Code graph analysis (1)
packages/react-on-rails-pro-node-renderer/tests/licenseFetcher.test.ts (2)
react_on_rails_pro/spec/dummy/client/node-renderer.js (1)
process(5-5)packages/react-on-rails-pro-node-renderer/src/shared/licenseFetcher.ts (2)
isAutoRefreshEnabled(51-60)fetchLicense(66-131)
🪛 Gitleaks (8.30.0)
packages/react-on-rails-pro-node-renderer/tests/licenseFetcher.test.ts
[high] 67-67: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
[high] 186-186: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
🔇 Additional comments (7)
.lychee.toml (4)
1-13: LGTM! Well-configured retry and timeout settings.The retry logic (4 retries with 15s wait) and 20s timeout are appropriate for handling transient network failures in CI while avoiding excessive delays. The accepted status codes (200 and 308) are standard.
64-65: Track the TODO for post-deployment cleanup.The exclusion for
licenses.shakacode.comis appropriate while deployment is pending. Ensure this TODO is tracked (e.g., in a follow-up issue) so the exclusion is removed once the licensing app is live.Can you confirm:
- Is there a tracking issue for removing this exclusion after deployment?
- What is the expected deployment timeline for licenses.shakacode.com?
107-113: LGTM! Sensible path exclusions.The excluded paths are well-justified: placeholder links in planning docs, dependencies, test fixtures, and legacy TOC structure. The comment explaining why
SUMMARY.mdis retained is particularly helpful.
115-120: LGTM! Appropriate caching and fragment settings.Disabling fragment checking avoids flaky anchor validation, and 1-day cache expiry provides a good balance between freshness and CI performance.
packages/react-on-rails-pro-node-renderer/tests/licenseFetcher.test.ts (3)
1-25: LGTM: Solid test setup with proper timer cleanup.The test setup properly addresses async-retry timer leaks mentioned in the PR objectives. The combination of
jest.clearAllTimers()andjest.useRealTimers()inafterEachprevents "Cannot log after tests are done" errors, and the explanatory comment with the Jest issue reference is helpful for future maintainers.
27-54: LGTM: Comprehensive coverage of auto-refresh logic.The test cases thoroughly cover all branches of
isAutoRefreshEnabled(), including edge cases like case-insensitive handling and default behavior.
67-67: Static analysis false positive: mock test data, not real secrets.Gitleaks flagged these JWT tokens as potential secrets, but they're clearly mock test data (note the
...truncation). This is a false positive and safe to ignore.Also applies to: 186-186
Add configuration options for automatic license token refresh: - auto_refresh_license: enable/disable auto-refresh (default: true) - license_api_url: API endpoint URL (placeholder, TODO for production) - license_key: permanent key for API auth (ENV takes precedence) - auto_refresh_enabled?: helper to check if properly configured Follows existing pattern from load_license_string for ENV precedence. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
HTTP client to fetch license tokens from the licensing API: - 5 second request timeout - 2 retries with 1 second delay on transient failures - Returns parsed JSON on success, nil on failure - Falls back silently to cached token if fetch fails 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Caches license tokens to tmp/react_on_rails_pro_license.cache: - Persists across app restarts to reduce API calls - Validates cached token belongs to current license_key via hash - Sets 0600 file permissions for security - Stores token, expires_at, plan, fetched_at for refresh logic 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Modified load_license_string to support automatic license renewal: - Calls maybe_refresh_license before loading token - Cache takes priority when auto-refresh is enabled - Falls back to ENV → config file if no cache Refresh timing (based on cached expires_at): - >30 days until expiry: no checks - 8-30 days: weekly checks - ≤7 days: daily checks 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Prevents fetching on every boot during the final week. Now consistent: daily checks (1 day threshold) when ≤7 days, weekly checks (7 day threshold) when 8-30 days. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Adds 64 new tests for the auto-refresh license feature: - LicenseFetcher (13 tests): HTTP responses, timeouts, network errors, ENV priority - LicenseCache (22 tests): file operations, hash validation, time parsing - Configuration (14 tests): license_key, auto_refresh_enabled? conditions - LicenseValidator (15 tests): token priority, timing thresholds, refresh orchestration Uses WebMock with HTTPX adapter for HTTP stubbing and FakeFS for file system tests. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Remove separate webmock_helper.rb file and add the requires directly to spec_helper.rb, matching the pattern used in spec/dummy/spec/. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The original implementation had a gap: should_check_for_refresh? checked LicenseCache.expires_at, but the cache was only populated via API refresh. Since refresh required the cache to exist (to know the expiry), refresh never triggered - a chicken-and-egg problem. Fix: After successful JWT validation, if auto-refresh is enabled and cache is empty, seed the cache with the current token's data (token + expiry). This allows should_check_for_refresh? to work on subsequent boots. Flow: 1. First boot: JWT from ENV → validate → seed cache 2. Subsequent boots: cache exists → check expiry → refresh if needed 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Extract auto-refresh timing logic from LicenseValidator into a dedicated LicenseRefreshChecker module. This reduces LicenseValidator from 264 to 201 lines and removes the need for rubocop:disable directive. - Create LicenseRefreshChecker with: maybe_refresh_license, seed_cache_if_needed, should_check_for_refresh?, last_fetch_older_than?, load_token_from_env_or_file - LicenseValidator now calls the module directly at call sites - Update tests to call LicenseRefreshChecker directly instead of testing private methods via described_class.send() 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
- Add LICENSE_SETUP.md section explaining auto-refresh feature: - License Key vs License Token comparison table - Setup instructions - How refresh timing works (weekly at 30d, daily at 7d) - How to disable for air-gapped environments - Privacy section - New FAQ entries - Add configuration.md section with config options: - license_key - auto_refresh_license - license_api_url - Add notes in README.md and installation.md pointing paid subscribers to auto-refresh documentation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Implements Phase 4 of automatic license renewal for the Node Renderer. When configured with REACT_ON_RAILS_PRO_LICENSE_KEY, the Node Renderer will automatically fetch fresh license tokens when approaching expiry. New modules: - licenseFetcher.ts: HTTP client for license API (5s timeout, 2 retries) - licenseCache.ts: File-based token caching with license_key validation - licenseRefreshChecker.ts: Timing logic (weekly at 30 days, daily at 7 days) Changes: - licenseValidator.ts: Now async, integrates refresh before loading token - master.ts: Awaits async license validation - ReactOnRailsProNodeRenderer.ts: Awaits async master.default() ENV vars (shared with Rails via .env): - REACT_ON_RAILS_PRO_LICENSE_KEY: Permanent key for API auth - REACT_ON_RAILS_PRO_AUTO_REFRESH_LICENSE: Enable/disable (default: true) - REACT_ON_RAILS_PRO_LICENSE_API_URL: API endpoint (default: production) Tests: 51 tests covering all new functionality 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Both Ruby gem and Node Renderer now send identifying User-Agent headers: - Ruby gem: ReactOnRailsPro-Gem - Node Renderer: ReactOnRailsPro-NodeRenderer Helps with server-side logging and debugging license issues. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Use jest.useFakeTimers() to control async-retry's internal setTimeout calls, preventing timer leakage between tests. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
- Add User-Agent header verification test - Add invalid JSON response handling test - Add write failure handling test for cache 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- configuration.rb: Add ENV support for license_api_url (REACT_ON_RAILS_PRO_LICENSE_API_URL) to match license_key behavior. This enables consistent configuration via environment variables for both Ruby gem and Node Renderer. - licenseFetcher.ts: Fix TypeScript errors in async-retry callback: - Add generic type parameter to retry<LicenseResponse>() - Properly handle unknown error type in onRetry callback - dummy app: Add commented license config example for local testing 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copy .lychee.toml from master and add exclusion for licenses.shakacode.com which doesn't exist yet (licensing app deployment pending). TODO: Remove the exclusion once the licensing app is deployed. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Add jest.clearAllTimers() and jest.useRealTimers() to afterEach to prevent "Cannot log after tests are done" errors caused by async-retry's internal setTimeout callbacks escaping the fake timer boundary. The issue occurred because async-retry schedules retries via setTimeout, and when jest.useRealTimers() was called, pending microtasks could fire after the test completed. See: jestjs/jest#10487 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
4079e0e to
7593128
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (2)
packages/react-on-rails-pro-node-renderer/src/shared/licenseValidator.ts (1)
210-211: Consider adding single-flight guard for concurrent validation calls.A previous review comment identified a potential race condition: if
getValidatedLicenseData()is called multiple times concurrently before the first call completes, each invocation will triggermaybeRefreshLicense(), potentially causing multiple simultaneous API requests.While this scenario is unlikely during typical startup (single master process validates once), adding a promise-based lock pattern would prevent unnecessary concurrent refreshes. This is particularly relevant if the validator is called from multiple initialization paths.
🔎 Example single-flight guard pattern
+let refreshInFlight: Promise<void> | null = null; + export async function getValidatedLicenseData(): Promise<LicenseData> { if (cachedLicenseData !== undefined) { return cachedLicenseData; } try { // Try auto-refresh if enabled and near expiry - await maybeRefreshLicense(); + if (refreshInFlight) { + await refreshInFlight; + } else { + refreshInFlight = maybeRefreshLicense().finally(() => { + refreshInFlight = null; + }); + await refreshInFlight; + } // Load and decode license (but don't cache yet) const licenseData = loadAndDecodeLicense();react_on_rails_pro/lib/react_on_rails_pro/license_refresh_checker.rb (1)
33-38: Add guard for missing or invalidexpclaim.If
license_data["exp"]isnilor not a valid numeric timestamp,Time.at(nil)will raise aTypeError. This issue was flagged in a previous review but has not been addressed.🔎 Proposed fix
def seed_cache_if_needed(license_data) return unless ReactOnRailsPro.configuration.auto_refresh_enabled? return if LicenseCache.token.present? # Cache already exists token = load_token_from_env_or_file return unless token + exp = license_data["exp"] + return unless exp.is_a?(Numeric) + - expires_at = Time.at(license_data["exp"]) + expires_at = Time.at(exp) LicenseCache.write( "token" => token, "expires_at" => expires_at.iso8601 ) end
🧹 Nitpick comments (1)
packages/react-on-rails-pro-node-renderer/tests/licenseCache.test.ts (1)
66-67: Consider extracting repeated hash calculation to a test helper.The crypto hash calculation logic is duplicated across four tests. Extracting this to a helper function would improve maintainability and DRY principles.
Additionally, consider using ES6
importinstead ofrequire('crypto')for consistency with the rest of the file.🔎 Proposed refactor
Add a helper function at the top of the test suite:
+import * as crypto from 'crypto'; + jest.mock('fs'); +function computeExpectedHash(licenseKey: string): string { + return crypto.createHash('sha256').update(licenseKey).digest('hex').substring(0, 16); +} + describe('LicenseCache', () => {Then replace the duplicated code. For example, at lines 66-67:
- // Compute expected hash (first 16 chars of SHA256) - const crypto = require('crypto'); - const expectedHash = crypto.createHash('sha256').update('lic_test123').digest('hex').substring(0, 16); + const expectedHash = computeExpectedHash('lic_test123');Apply similar changes at lines 174-175, 201-202, and 230-231.
Also applies to: 174-175, 201-202, 230-231
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (28)
.lychee.tomlpackages/react-on-rails-pro-node-renderer/package.jsonpackages/react-on-rails-pro-node-renderer/src/ReactOnRailsProNodeRenderer.tspackages/react-on-rails-pro-node-renderer/src/master.tspackages/react-on-rails-pro-node-renderer/src/shared/licenseCache.tspackages/react-on-rails-pro-node-renderer/src/shared/licenseFetcher.tspackages/react-on-rails-pro-node-renderer/src/shared/licenseRefreshChecker.tspackages/react-on-rails-pro-node-renderer/src/shared/licenseValidator.tspackages/react-on-rails-pro-node-renderer/tests/licenseCache.test.tspackages/react-on-rails-pro-node-renderer/tests/licenseFetcher.test.tspackages/react-on-rails-pro-node-renderer/tests/licenseRefreshChecker.test.tspackages/react-on-rails-pro-node-renderer/tests/licenseValidator.test.tsreact_on_rails_pro/LICENSE_SETUP.mdreact_on_rails_pro/README.mdreact_on_rails_pro/docs/configuration.mdreact_on_rails_pro/docs/installation.mdreact_on_rails_pro/lib/react_on_rails_pro.rbreact_on_rails_pro/lib/react_on_rails_pro/configuration.rbreact_on_rails_pro/lib/react_on_rails_pro/license_cache.rbreact_on_rails_pro/lib/react_on_rails_pro/license_fetcher.rbreact_on_rails_pro/lib/react_on_rails_pro/license_refresh_checker.rbreact_on_rails_pro/lib/react_on_rails_pro/license_validator.rbreact_on_rails_pro/spec/dummy/config/initializers/react_on_rails_pro.rbreact_on_rails_pro/spec/react_on_rails_pro/configuration_spec.rbreact_on_rails_pro/spec/react_on_rails_pro/license_cache_spec.rbreact_on_rails_pro/spec/react_on_rails_pro/license_fetcher_spec.rbreact_on_rails_pro/spec/react_on_rails_pro/license_validator_spec.rbreact_on_rails_pro/spec/react_on_rails_pro/spec_helper.rb
✅ Files skipped from review due to trivial changes (1)
- react_on_rails_pro/spec/react_on_rails_pro/license_validator_spec.rb
🚧 Files skipped from review as they are similar to previous changes (11)
- react_on_rails_pro/spec/react_on_rails_pro/license_fetcher_spec.rb
- packages/react-on-rails-pro-node-renderer/package.json
- packages/react-on-rails-pro-node-renderer/tests/licenseRefreshChecker.test.ts
- react_on_rails_pro/spec/react_on_rails_pro/configuration_spec.rb
- .lychee.toml
- react_on_rails_pro/docs/installation.md
- react_on_rails_pro/spec/dummy/config/initializers/react_on_rails_pro.rb
- react_on_rails_pro/lib/react_on_rails_pro.rb
- packages/react-on-rails-pro-node-renderer/src/shared/licenseCache.ts
- react_on_rails_pro/lib/react_on_rails_pro/license_cache.rb
- packages/react-on-rails-pro-node-renderer/src/shared/licenseRefreshChecker.ts
🧰 Additional context used
📓 Path-based instructions (1)
**/*.rb
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.rb: Runbundle exec rubocopand fix ALL violations before every commit/push
Runbundle exec rubocop(MANDATORY) before every commit to ensure zero offenses
Files:
react_on_rails_pro/lib/react_on_rails_pro/license_fetcher.rbreact_on_rails_pro/spec/react_on_rails_pro/spec_helper.rbreact_on_rails_pro/lib/react_on_rails_pro/configuration.rbreact_on_rails_pro/lib/react_on_rails_pro/license_validator.rbreact_on_rails_pro/lib/react_on_rails_pro/license_refresh_checker.rbreact_on_rails_pro/spec/react_on_rails_pro/license_cache_spec.rb
🧠 Learnings (23)
📓 Common learnings
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: Applies to /CHANGELOG_PRO.md : Update `/CHANGELOG_PRO.md` for user-visible changes in the Pro-only react_on_rails_pro gem and npm packages
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: Applies to /CHANGELOG.md : Update `/CHANGELOG.md` for user-visible changes in the open-source react_on_rails gem and npm package (features, bug fixes, breaking changes, deprecations, performance improvements)
📚 Learning: 2025-12-19T18:57:59.314Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: Applies to /CHANGELOG_PRO.md : Update `/CHANGELOG_PRO.md` for user-visible changes in the Pro-only react_on_rails_pro gem and npm packages
Applied to files:
react_on_rails_pro/docs/configuration.mdpackages/react-on-rails-pro-node-renderer/src/shared/licenseValidator.tsreact_on_rails_pro/LICENSE_SETUP.mdreact_on_rails_pro/lib/react_on_rails_pro/license_fetcher.rbreact_on_rails_pro/spec/react_on_rails_pro/spec_helper.rbreact_on_rails_pro/lib/react_on_rails_pro/configuration.rbpackages/react-on-rails-pro-node-renderer/src/shared/licenseFetcher.tsreact_on_rails_pro/lib/react_on_rails_pro/license_validator.rbreact_on_rails_pro/README.mdreact_on_rails_pro/lib/react_on_rails_pro/license_refresh_checker.rbpackages/react-on-rails-pro-node-renderer/src/master.tsreact_on_rails_pro/spec/react_on_rails_pro/license_cache_spec.rb
📚 Learning: 2025-12-19T18:57:59.314Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: Applies to /CHANGELOG.md : Update `/CHANGELOG.md` for user-visible changes in the open-source react_on_rails gem and npm package (features, bug fixes, breaking changes, deprecations, performance improvements)
Applied to files:
react_on_rails_pro/docs/configuration.mdpackages/react-on-rails-pro-node-renderer/src/shared/licenseValidator.tsreact_on_rails_pro/LICENSE_SETUP.mdreact_on_rails_pro/lib/react_on_rails_pro/license_fetcher.rbreact_on_rails_pro/spec/react_on_rails_pro/spec_helper.rbreact_on_rails_pro/lib/react_on_rails_pro/configuration.rbreact_on_rails_pro/README.mdreact_on_rails_pro/lib/react_on_rails_pro/license_refresh_checker.rbpackages/react-on-rails-pro-node-renderer/src/master.tsreact_on_rails_pro/spec/react_on_rails_pro/license_cache_spec.rb
📚 Learning: 2025-10-23T17:22:01.074Z
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1875
File: lib/react_on_rails/utils.rb:112-124
Timestamp: 2025-10-23T17:22:01.074Z
Learning: In React on Rails, when Pro is installed but not licensed, the intended behavior is to raise an error on boot. The `react_on_rails_pro?` method validates licenses and should raise errors early (including during path resolution in methods like `server_bundle?`) to enforce licensing requirements rather than failing later with obscure errors.
Applied to files:
react_on_rails_pro/docs/configuration.mdpackages/react-on-rails-pro-node-renderer/src/shared/licenseValidator.tsreact_on_rails_pro/LICENSE_SETUP.mdreact_on_rails_pro/lib/react_on_rails_pro/license_fetcher.rbpackages/react-on-rails-pro-node-renderer/tests/licenseValidator.test.tsreact_on_rails_pro/spec/react_on_rails_pro/spec_helper.rbreact_on_rails_pro/lib/react_on_rails_pro/configuration.rbpackages/react-on-rails-pro-node-renderer/src/shared/licenseFetcher.tsreact_on_rails_pro/lib/react_on_rails_pro/license_validator.rbreact_on_rails_pro/README.mdreact_on_rails_pro/lib/react_on_rails_pro/license_refresh_checker.rbreact_on_rails_pro/spec/react_on_rails_pro/license_cache_spec.rb
📚 Learning: 2025-09-15T21:24:48.207Z
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1781
File: node_package/src/ClientSideRenderer.ts:82-95
Timestamp: 2025-09-15T21:24:48.207Z
Learning: In React on Rails, the force_load feature includes both explicit `data-force-load="true"` usage and the ability to hydrate components during the page loading state (`document.readyState === 'loading'`). Both capabilities require a Pro license, so the condition `!railsContext.rorPro && (isComponentForceLoaded || document.readyState === 'loading')` correctly gates both scenarios.
Applied to files:
react_on_rails_pro/docs/configuration.mdreact_on_rails_pro/LICENSE_SETUP.mdreact_on_rails_pro/lib/react_on_rails_pro/license_fetcher.rbreact_on_rails_pro/lib/react_on_rails_pro/configuration.rbreact_on_rails_pro/lib/react_on_rails_pro/license_validator.rbreact_on_rails_pro/README.mdreact_on_rails_pro/lib/react_on_rails_pro/license_refresh_checker.rb
📚 Learning: 2025-09-16T08:01:11.146Z
Learnt from: justin808
Repo: shakacode/react_on_rails PR: 1770
File: lib/generators/react_on_rails/templates/base/base/app/javascript/src/HelloWorld/ror_components/HelloWorld.client.jsx:2-2
Timestamp: 2025-09-16T08:01:11.146Z
Learning: React on Rails uses webpack CSS Modules configuration with namedExports: true, which requires the import syntax `import * as style from './file.module.css'` rather than the default export pattern. This configuration enables better tree shaking and bundle size optimization for CSS modules.
Applied to files:
react_on_rails_pro/docs/configuration.mdreact_on_rails_pro/spec/react_on_rails_pro/spec_helper.rbreact_on_rails_pro/lib/react_on_rails_pro/configuration.rbreact_on_rails_pro/README.md
📚 Learning: 2025-02-12T16:38:06.537Z
Learnt from: Romex91
Repo: shakacode/react_on_rails PR: 1697
File: package-scripts.yml:28-28
Timestamp: 2025-02-12T16:38:06.537Z
Learning: The file `node_package/lib/ReactOnRails.full.js` is autogenerated during the build process and should not be present in the repository.
Applied to files:
react_on_rails_pro/docs/configuration.mdreact_on_rails_pro/README.md
📚 Learning: 2025-02-18T13:08:01.477Z
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1644
File: lib/react_on_rails/helper.rb:190-197
Timestamp: 2025-02-18T13:08:01.477Z
Learning: RSC support validation in React on Rails Pro is handled through a chain of validations:
1. Pro version check in `run_stream_inside_fiber`
2. RSC support check during pack generation via `ReactOnRailsPro.configuration.enable_rsc_support`
3. RSC support validation during component registration
This makes additional validation in the helper methods unnecessary.
Applied to files:
react_on_rails_pro/docs/configuration.mdreact_on_rails_pro/LICENSE_SETUP.mdreact_on_rails_pro/lib/react_on_rails_pro/configuration.rbreact_on_rails_pro/README.mdreact_on_rails_pro/lib/react_on_rails_pro/license_refresh_checker.rb
📚 Learning: 2025-07-08T05:57:29.630Z
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1745
File: node_package/src/RSCRequestTracker.ts:8-14
Timestamp: 2025-07-08T05:57:29.630Z
Learning: The global `generateRSCPayload` function in React on Rails Pro (RORP) is provided by the framework during rendering requests, not implemented in application code. The `declare global` statements are used to document the expected interface that RORP will inject at runtime.
Applied to files:
react_on_rails_pro/docs/configuration.md
📚 Learning: 2025-12-19T18:57:59.314Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: In IDE configuration, exclude these directories to prevent slowdowns: /coverage, /tmp, /gen-examples, /packages/react-on-rails/lib, /node_modules, /react_on_rails/spec/dummy/node_modules, /react_on_rails/spec/dummy/tmp, /react_on_rails/spec/dummy/app/assets/webpack, /react_on_rails/spec/dummy/log, /react_on_rails/spec/dummy/e2e/playwright-report, /react_on_rails/spec/dummy/test-results
Applied to files:
react_on_rails_pro/docs/configuration.md
📚 Learning: 2025-12-19T18:57:59.314Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: Applies to react_on_rails/spec/dummy/e2e/playwright/e2e/**/*.spec.js : Use Playwright E2E tests in `react_on_rails/spec/dummy/e2e/playwright/` for React component integration testing. Tests automatically start Rails server on port 5017 before running
Applied to files:
react_on_rails_pro/docs/configuration.mdpackages/react-on-rails-pro-node-renderer/tests/licenseFetcher.test.tsreact_on_rails_pro/LICENSE_SETUP.mdpackages/react-on-rails-pro-node-renderer/tests/licenseValidator.test.tsreact_on_rails_pro/spec/react_on_rails_pro/spec_helper.rbreact_on_rails_pro/spec/react_on_rails_pro/license_cache_spec.rbpackages/react-on-rails-pro-node-renderer/tests/licenseCache.test.ts
📚 Learning: 2025-02-13T16:50:47.848Z
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1644
File: node_package/src/clientStartup.ts:18-21
Timestamp: 2025-02-13T16:50:47.848Z
Learning: In the react_on_rails module, the `reactOnRailsPageUnloaded` function in clientStartup.ts is intentionally kept private as it's only used internally as a callback for `onPageUnloaded`.
Applied to files:
react_on_rails_pro/docs/configuration.md
📚 Learning: 2025-02-13T16:50:26.861Z
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1644
File: node_package/src/turbolinksUtils.ts:34-36
Timestamp: 2025-02-13T16:50:26.861Z
Learning: In React on Rails, when checking for Turbolinks version 5 using `turbolinksVersion5()`, always ensure `Turbolinks` exists first by checking `turbolinksInstalled()` to prevent TypeError when accessing properties.
Applied to files:
react_on_rails_pro/LICENSE_SETUP.md
📚 Learning: 2025-02-18T13:08:01.477Z
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1644
File: lib/react_on_rails/helper.rb:190-197
Timestamp: 2025-02-18T13:08:01.477Z
Learning: RSC support validation is handled in deeper level calls of the React on Rails Pro codebase, so it doesn't need to be validated again in the `rsc_payload_react_component` helper method.
Applied to files:
react_on_rails_pro/LICENSE_SETUP.md
📚 Learning: 2025-12-19T18:57:59.314Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: Install Playwright browsers with `cd react_on_rails/spec/dummy && pnpm playwright install --with-deps` before running E2E tests
Applied to files:
packages/react-on-rails-pro-node-renderer/tests/licenseValidator.test.ts
📚 Learning: 2025-12-19T18:57:59.314Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: Applies to react_on_rails/spec/dummy/e2e/playwright/e2e/**/*.spec.js : Use `app('clean')`, `appEval(code)`, `appFactories(options)`, and `appScenario(name)` helpers from cypress-on-rails in Playwright tests for Rails integration
Applied to files:
packages/react-on-rails-pro-node-renderer/tests/licenseValidator.test.tsreact_on_rails_pro/spec/react_on_rails_pro/spec_helper.rb
📚 Learning: 2025-12-19T18:57:59.314Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: Applies to sig/react_on_rails/**/*.rbs : RBS signatures in `sig/react_on_rails/` should be added for new Ruby files in `lib/react_on_rails/`, included in Steepfile, validated with `bundle exec rake rbs:validate`, and type-checked with `bundle exec rake rbs:steep`
Applied to files:
react_on_rails_pro/spec/react_on_rails_pro/spec_helper.rbreact_on_rails_pro/README.mdreact_on_rails_pro/lib/react_on_rails_pro/license_refresh_checker.rbreact_on_rails_pro/spec/react_on_rails_pro/license_cache_spec.rb
📚 Learning: 2024-10-08T20:53:47.076Z
Learnt from: theforestvn88
Repo: shakacode/react_on_rails PR: 1620
File: spec/dummy/client/app/startup/HelloTurboStream.jsx:3-3
Timestamp: 2024-10-08T20:53:47.076Z
Learning: The `RailsContext` import in `spec/dummy/client/app/startup/HelloTurboStream.jsx` is used later in the project, as clarified by the user theforestvn88.
Applied to files:
react_on_rails_pro/spec/react_on_rails_pro/spec_helper.rb
📚 Learning: 2025-04-26T21:55:55.874Z
Learnt from: alexeyr-ci2
Repo: shakacode/react_on_rails PR: 1732
File: spec/dummy/client/app-react16/startup/ReduxSharedStoreApp.client.jsx:40-44
Timestamp: 2025-04-26T21:55:55.874Z
Learning: In the react_on_rails project, files under `app-react16` directories are copied/moved to corresponding `/app` directories during the conversion process (removing the `-react16` suffix), which affects their relative import paths at runtime.
Applied to files:
react_on_rails_pro/spec/react_on_rails_pro/spec_helper.rb
📚 Learning: 2025-12-19T18:57:59.314Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: Shakapacker configuration should be tested by creating debug scripts in dummy app root (debug-webpack-rules.js, debug-webpack-with-config.js) to inspect webpack rules before committing configuration changes
Applied to files:
react_on_rails_pro/spec/react_on_rails_pro/spec_helper.rb
📚 Learning: 2025-12-19T18:57:59.314Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: Applies to react_on_rails/spec/dummy/e2e/playwright/app_commands/**/*.rb : Create custom Rails app commands in `e2e/playwright/app_commands/` directory with .rb files for reusable helpers in Playwright tests
Applied to files:
react_on_rails_pro/spec/react_on_rails_pro/spec_helper.rbreact_on_rails_pro/spec/react_on_rails_pro/license_cache_spec.rb
📚 Learning: 2025-02-13T14:29:49.267Z
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1644
File: spec/react_on_rails/utils_spec.rb:218-218
Timestamp: 2025-02-13T14:29:49.267Z
Learning: In RSpec tests, prefer using local variables over constants within test blocks to avoid constant redefinition warnings and maintain better test isolation.
Applied to files:
react_on_rails_pro/spec/react_on_rails_pro/spec_helper.rbreact_on_rails_pro/spec/react_on_rails_pro/license_cache_spec.rb
📚 Learning: 2024-12-12T13:07:09.929Z
Learnt from: alexeyr-ci
Repo: shakacode/react_on_rails PR: 1644
File: node_package/src/ReactOnRailsRSC.ts:87-87
Timestamp: 2024-12-12T13:07:09.929Z
Learning: When handling errors in 'node_package/src/ReactOnRailsRSC.ts', include the error stack in error messages in development and test environments to aid debugging.
Applied to files:
packages/react-on-rails-pro-node-renderer/src/shared/licenseFetcher.ts
🧬 Code graph analysis (6)
react_on_rails_pro/lib/react_on_rails_pro/license_fetcher.rb (1)
react_on_rails_pro/lib/react_on_rails_pro/configuration.rb (3)
configuration(9-38)auto_refresh_enabled?(234-236)license_api_url(217-222)
packages/react-on-rails-pro-node-renderer/src/shared/licenseFetcher.ts (1)
react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/RouterApp.server.jsx (1)
controller(10-10)
react_on_rails_pro/lib/react_on_rails_pro/license_validator.rb (3)
react_on_rails_pro/lib/react_on_rails_pro/license_refresh_checker.rb (2)
seed_cache_if_needed(26-39)maybe_refresh_license(13-21)react_on_rails_pro/lib/react_on_rails_pro/configuration.rb (2)
configuration(9-38)auto_refresh_enabled?(234-236)react_on_rails_pro/lib/react_on_rails_pro/license_cache.rb (1)
token(41-43)
react_on_rails_pro/lib/react_on_rails_pro/license_refresh_checker.rb (2)
react_on_rails_pro/lib/react_on_rails_pro/configuration.rb (2)
configuration(9-38)auto_refresh_enabled?(234-236)react_on_rails_pro/lib/react_on_rails_pro/license_cache.rb (4)
write(27-39)token(41-43)expires_at(52-57)fetched_at(45-50)
packages/react-on-rails-pro-node-renderer/src/master.ts (2)
packages/react-on-rails-pro-node-renderer/src/shared/configBuilder.ts (1)
Config(25-89)packages/react-on-rails-pro-node-renderer/src/shared/licenseValidator.ts (1)
getValidatedLicenseData(204-247)
packages/react-on-rails-pro-node-renderer/tests/licenseCache.test.ts (2)
react_on_rails_pro/spec/dummy/client/node-renderer.js (1)
process(5-5)packages/react-on-rails-pro-node-renderer/src/shared/licenseCache.ts (5)
readCache(80-99)writeCache(105-136)getCachedToken(141-144)getFetchedAt(150-161)getExpiresAt(167-178)
🪛 Gitleaks (8.30.0)
packages/react-on-rails-pro-node-renderer/tests/licenseFetcher.test.ts
[high] 67-67: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
[high] 186-186: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
packages/react-on-rails-pro-node-renderer/tests/licenseCache.test.ts
[high] 50-50: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
[high] 70-70: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (12)
- GitHub Check: examples (3.4, latest)
- GitHub Check: examples (3.2, minimum)
- GitHub Check: build-dummy-app-webpack-test-bundles (3.2, 20, minimum)
- GitHub Check: rspec-package-tests (3.4, latest)
- GitHub Check: rspec-package-tests (3.2, minimum)
- GitHub Check: build-dummy-app-webpack-test-bundles
- GitHub Check: build-dummy-app-webpack-test-bundles
- GitHub Check: pro-lint-js-and-ruby
- GitHub Check: build
- GitHub Check: check-bundle-size
- GitHub Check: claude-review
- GitHub Check: markdown-link-check
🔇 Additional comments (33)
packages/react-on-rails-pro-node-renderer/tests/licenseCache.test.ts (3)
1-37: LGTM! Well-structured test setup.The test setup follows best practices with proper mocking, environment cleanup, and mock restoration. The beforeEach/afterEach pattern ensures test isolation.
50-50: Static analysis false positives: test mock data.The Gitleaks warnings about API keys on these lines are false positives. These are clearly test mock tokens (truncated JWT format with
...) used for testing the cache functionality, not actual secrets.Also applies to: 70-70
39-253: Excellent comprehensive test coverage!The test suite thoroughly covers all public API functions with appropriate edge cases:
- Cache validation with license key hash matching
- Filesystem error handling (missing files, parse errors, write failures)
- Permission settings (0600)
- Date parsing and extraction
- Fallback behavior when cache is invalid or missing
This provides strong confidence in the licenseCache module's correctness and robustness.
packages/react-on-rails-pro-node-renderer/src/shared/licenseFetcher.ts (1)
1-131: LGTM! Clean implementation of license fetching with retry logic.The implementation correctly handles:
- Auto-refresh enablement checks
- Retry logic with exponential backoff
- Timeout via AbortController
- Auth error bail pattern (401/403)
- Graceful error handling with appropriate logging
Note: A previous review comment suggested adding an explicit test case for timeout/abort scenarios to ensure the AbortError path is covered. Consider this as an enhancement to test coverage.
packages/react-on-rails-pro-node-renderer/src/ReactOnRailsProNodeRenderer.ts (1)
45-45: LGTM! Correctly awaits async master initialization.The addition of
awaitaligns with the asynchronous license validation flow introduced in master.ts.packages/react-on-rails-pro-node-renderer/src/master.ts (1)
14-18: LGTM! Proper async integration for license validation.The function correctly transitions to async and awaits license validation before proceeding with worker setup.
packages/react-on-rails-pro-node-renderer/tests/licenseFetcher.test.ts (2)
19-23: Good practice: Timer cleanup prevents test leaks.The
jest.clearAllTimers()andjest.useRealTimers()calls properly address the async-retry timer leak issue mentioned in the PR objectives.
67-67: Static analysis false positives: Mock tokens are safe.Gitleaks flags these as potential secrets, but they are test fixtures with mock JWT tokens. These are safe to ignore.
Also applies to: 186-186
packages/react-on-rails-pro-node-renderer/src/shared/licenseValidator.ts (2)
84-96: LGTM! Cache-first loading with proper fallback.The priority order (cache → ENV → config file) correctly implements the auto-refresh flow while maintaining backward compatibility.
204-226: LGTM! Async validation flow with auto-refresh integration.The validation sequence correctly:
- Checks cache
- Attempts auto-refresh if needed
- Loads and validates license
- Seeds cache for future refresh checks
packages/react-on-rails-pro-node-renderer/tests/licenseValidator.test.ts (2)
11-21: LGTM! Proper mocking prevents auto-refresh side effects.The auto-refresh module mocks correctly isolate the validator tests from side effects, ensuring tests remain fast and predictable. The re-mocking after
resetModulesmaintains isolation across test cases.Also applies to: 83-93
109-295: LGTM! Test suite properly updated for async API.All test cases correctly await the async validation methods, and the test coverage remains comprehensive.
react_on_rails_pro/spec/react_on_rails_pro/spec_helper.rb (1)
24-27: LGTM!The webmock and HTTPX adapter setup is correctly ordered and provides proper HTTP stubbing support for the new license fetcher tests.
react_on_rails_pro/README.md (1)
297-298: LGTM!Clear guidance for paid subscribers about using
LICENSE_KEYfor automatic renewal, with appropriate link to detailed documentation.react_on_rails_pro/LICENSE_SETUP.md (2)
79-142: LGTM!Comprehensive documentation for the automatic license renewal feature covering:
- Clear comparison table between License Token and License Key
- Well-documented refresh schedule thresholds
- Air-gapped environment configuration
- Privacy guarantees
The documentation aligns well with the implementation details in the codebase.
345-360: LGTM!FAQ additions address the key questions users will have about the new auto-refresh feature, including the LICENSE vs LICENSE_KEY distinction and data privacy.
react_on_rails_pro/docs/configuration.md (1)
165-186: LGTM!The license auto-refresh configuration section is well-documented with clear defaults and references to the detailed LICENSE_SETUP.md guide.
Minor note: Line 175 shows
config.license_key = ENV["REACT_ON_RAILS_PRO_LICENSE_KEY"], but since the getter already checks ENV first (as per configuration.rb lines 203-207), this explicit assignment is optional. The example is still valid for documentation purposes to show the ENV variable name.react_on_rails_pro/lib/react_on_rails_pro/license_validator.rb (2)
29-32: LGTM!Good practice to seed the cache on first successful validation. This ensures the cache has expiry information for refresh scheduling on subsequent boots.
158-180: LGTM!The license loading flow is well-structured:
- Attempt refresh if due (guarded by
should_check_for_refresh?inLicenseRefreshChecker)- Use cached token when auto-refresh is enabled
- Fall back to ENV variable
- Fall back to config file
The memoization at line 16 (
return @license_data if defined?(@license_data)) prevents repeated API calls within the same process lifecycle.react_on_rails_pro/spec/react_on_rails_pro/license_cache_spec.rb (4)
1-42: LGTM!Well-structured test setup with:
- FakeFS for filesystem isolation
- Proper configuration reset in before/after hooks
- ENV cleanup to prevent test pollution
43-123: LGTM!Comprehensive
.readtests covering all edge cases: missing file, invalid JSON, hash mismatch, nil key, valid cache, and ENV override.
125-187: LGTM!Good coverage of
.writebehavior including:
- Data merging with required fields
- Directory creation when missing
- Error handling that logs warnings without raising (graceful degradation)
189-283: LGTM!Accessor method tests (
token,fetched_at,expires_at) properly cover the happy path and edge cases including invalid time formats.react_on_rails_pro/lib/react_on_rails_pro/license_fetcher.rb (2)
16-36: Solid implementation with graceful fallback.The fetch method correctly:
- Guards on
auto_refresh_enabled?before making network calls- Uses HTTPX's retry plugin for transient failures
- Handles both network errors (
ErrorResponse) and non-200 responses- Returns
nilon any failure, allowing fallback to cached/existing tokenRegarding the fixed vs exponential retry strategy noted in past reviews: for this use case (license renewal during app boot, with graceful fallback), the simpler fixed-delay approach is acceptable. The critical path is protected by falling back to the cached token.
40-46: LGTM!Clean delegation to configuration for license_key and api_url.
react_on_rails_pro/lib/react_on_rails_pro/configuration.rb (3)
65-69: LGTM!Good defaults for the auto-refresh feature. The TODO comment for the license API URL is appropriate while the licensing app deployment is pending.
199-236: LGTM!Well-designed configuration methods:
license_keyandlicense_api_urlcorrectly prioritize ENV variables over configured valuesauto_refresh_enabled?properly gates on both the feature flag and having a key configured- Clear YARD documentation for each method
The
|| DEFAULT_LICENSE_API_URLfallback on line 221 ensures a URL is always returned even when both ENV and config are unset.
111-114: LGTM!New initialization parameters with appropriate defaults. The pattern follows existing configuration style in this file.
Also applies to: 141-143
react_on_rails_pro/lib/react_on_rails_pro/license_refresh_checker.rb (5)
13-21: LGTM!The refresh logic correctly guards against disabled auto-refresh and unnecessary checks, gracefully handles nil responses from the fetcher, and persists successful responses to the cache.
41-54: LGTM!The refresh schedule logic correctly implements the cadence from the PR objectives: daily checks when ≤7 days until expiry, weekly when ≤30 days, and no checks when >30 days. The method gracefully handles missing expiry timestamps and expired licenses.
56-61: LGTM!The timing logic correctly handles the case where no fetch has occurred yet (returning true to trigger a check) and accurately compares the time elapsed since the last fetch.
64-72: LGTM!The token loading logic correctly prioritizes the environment variable, falls back to the file, and returns nil when neither is available. The
.stripcall ensures whitespace doesn't cause issues with tokens read from files.
1-75: No action required. RuboCop violations are automatically detected and fixed by lefthook pre-commit hooks when you commit changes to this file.Likely an incorrect or invalid review comment.
Comprehensive Review of PR #2254: Automatic License RenewalExecutive SummaryThis PR adds automatic license renewal functionality for both the Ruby gem and Node Renderer. The implementation is well-designed and secure with excellent test coverage (110+ tests). However, there are 3 required fixes before merging. 🔐 Security Analysis - EXCELLENT ✅JWT Security - Perfect Implementation
File Permissions - Secure
API Authentication - Good
Secrets Management - Excellent
🐛 Required Fixes (MUST ADDRESS)1. ENV Inconsistency (CRITICAL)Issue: TypeScript supports Current Ruby ( def auto_refresh_enabled?
auto_refresh_license && license_key.present?
endCurrent TypeScript ( const autoRefreshSetting = process.env.REACT_ON_RAILS_PRO_AUTO_REFRESH_LICENSE;
return autoRefreshSetting?.toLowerCase() \!== 'false';Fix Required: Add ENV support to Ruby to match TypeScript: def auto_refresh_enabled?
env_setting = ENV.fetch("REACT_ON_RAILS_PRO_AUTO_REFRESH_LICENSE", nil)
enabled = if env_setting.present?
env_setting.downcase \!= "false"
else
auto_refresh_license
end
enabled && license_key.present?
end2. Missing Changelog Entry (REQUIRED)Per CLAUDE.md guidelines, need to update #### [Unreleased]
##### Added
- Automatic license renewal for Ruby gem and Node Renderer. When configured with a `license_key`, licenses are automatically renewed before expiration. [PR 2254](https://github.com/shakacode/react_on_rails/pull/2254) by [ihabadham](https://github.com/ihabadham)3. Inefficient Retry on Auth Failures (MEDIUM)Issue: Ruby retries on 401/403 errors, wasting retries on auth failures. TypeScript correctly bails immediately. Current Ruby ( return nil if response.is_a?(HTTPX::ErrorResponse)
return nil unless response.status == 200Fix Required: Add explicit check to NOT retry on auth errors: # Bail immediately on auth errors (don't waste retries)
return nil if [401, 403].include?(response.status)
return nil if response.is_a?(HTTPX::ErrorResponse)
return nil unless response.status == 200📊 Test Coverage - EXCELLENT ✅
Minor Gap: No integration test for full renewal flow (ENV → fetch → cache → validate). Optional but recommended for future PR. ⚡ Performance Analysis - EXCELLENT ✅Refresh Schedule - Optimal
Cache Persistence
Minor Note: First boot with near-expiry license blocks ~5 seconds on API call. Consider async refresh in future PR if this becomes an issue. 📚 Documentation - EXCELLENT ✅
TODO Comments: Remember to remove these once licensing app is deployed:
🎯 Code Quality - EXCELLENT ✅Ruby Implementation
TypeScript Implementation
Consistency Between Implementations
🔍 Minor Improvements (Optional - Future PRs)
✅ Final VerdictAPPROVE with 3 required fixes This is exceptionally well-implemented code with:
Action Items:
Once these 3 items are addressed, this PR is ready to merge. Great work! 🎉 |
async-retry's internal design mixes setTimeout with Promise chains, causing "Cannot log after tests are done" warnings in Jest CI. Replace with a simple custom retry implementation that: - Uses a simple for-loop with await for cleaner cleanup - Supports AbortRetryError for immediate abort (like auth errors) - Has onFailedAttempt callback for logging This fixes the package-js-tests CI failure on PR #2254. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Code Review: Automatic License RenewalGreat work on this feature! The implementation is well-structured and comprehensive. ✅ Strengths1. Excellent Architecture & Code Quality
2. Security Best Practices
3. Configuration & UX
🔍 Potential Issues & Recommendations1. Security: License Key Exposure Risk (Medium Priority)Issue: The license key is sent as a Bearer token in HTTP headers. If license_api_url is accidentally configured as HTTP (not HTTPS), the key would be transmitted in plaintext. Recommendation: Add validation in configuration.rb to warn if HTTP is used. Files: react_on_rails_pro/lib/react_on_rails_pro/configuration.rb:217-227 2. Race Condition: Cache Write in Multi-Process Environments (Low-Medium)Files: license_cache.rb:28-36, licenseCache.ts:105-136 Issue: In multi-process environments (Puma/Unicorn with multiple workers), simultaneous writes to the cache file could cause corruption. Scenario:
Recommendation: Add file locking using File.flock for atomic writes. 3. Performance: Cache Read on Every Request (Low)File: license_validator.rb:159-166 Issue: load_license_string calls LicenseRefreshChecker.maybe_refresh_license on every validation, which reads from disk cache on every server-rendered request. Recommendation: Consider memoizing the cache read or using a background job for periodic refresh checks. Note: validated_license_data! already memoizes the result, but maybe_refresh_license runs before caching kicks in on first call. 4. Documentation: Missing Troubleshooting Guide (Low)Missing info:
Recommendation: Add troubleshooting section to docs. 🧪 Test Coverage AnalysisExcellent coverage overall! The tests cover:
📋 Before Merging ChecklistPer CLAUDE.md requirements:
🎯 SummaryVerdict: ✅ Approve with minor recommendations This is a well-crafted feature with solid architecture, comprehensive tests, and good security practices. The issues identified are mostly nice-to-haves or defensive improvements rather than blockers. Priority fixes (if any):
Great work overall! 🎉 |
PR #2254 Code Review: Automatic License RenewalOverall Assessment: This is a well-designed, production-ready feature with excellent test coverage and consistent implementation across Ruby and TypeScript. Test Coverage: Excellent - 64 new Ruby tests, 54 new TypeScript tests. Strengths
High Priority Issues1. Ruby LicenseFetcher: Add specific HTTPX exception handling for timeout/network errors Medium Priority
Low Priority
Security
Performance
DocumentationConsider adding: migration guide, troubleshooting section, refresh window explanation ConclusionExcellent work! Production-ready with only minor improvements needed. Recommendation: Approve with optional fixes for High Priority items. |
Code Review: Automatic License Renewal (PR #2254)Thank you for this comprehensive implementation! The overall architecture is solid with good separation of concerns. Critical Issues1. Async/Await Mismatch (BLOCKING) - licenseValidator.ts loadAndDecodeLicense() is synchronous but calls await maybeRefreshLicense(). This will cause a runtime error. Fix: Make the entire chain async. 2. License Key Hash Too Short (SECURITY) - licenseCache.ts:33 and license_cache.rb:71 truncate SHA256 to only 16 characters (64 bits), reducing collision resistance from 2^128 to 2^32. Fix: Use at least 32 characters (128 bits). 3. Missing ENV Variable Support in Ruby - TypeScript reads REACT_ON_RAILS_PRO_AUTO_REFRESH_LICENSE from ENV, but Ruby configuration doesn't, creating inconsistent behavior between platforms. High Priority Issues4. Race Condition on Cache Write - TOCTOU race when multiple processes start simultaneously. Both might write to cache file concurrently. Fix: Use atomic writes or file locking. 5. Blocking I/O on Boot Path - License validation can trigger 5-second HTTP timeout during startup. Consider fire-and-forget pattern after initial validation. 6. Invalid Cache Cleanup - When license_key_hash doesn't match (user changed key), readCache() returns null but old cache file remains. Should delete cache file on hash mismatch. Medium Priority7. Missing Test Coverage - Need tests for: concurrent refresh attempts, clock skew scenarios, partial file writes, permission errors 8. Input Validation - Missing validation that expires_at is valid ISO8601 and license_api_url is valid URL Done WellFile permissions (0600), Bearer token auth, smart refresh schedule (7/30 day thresholds), AbortRetryError pattern, test isolation with FakeFS/WebMock, caching strategy SummaryMust Fix Before Merge: async/await bug (runtime error), hash length to 32+ chars (security), ENV variable support in Ruby (consistency) Strongly Recommended: File locking/atomic writes, cache cleanup on key change, concurrency tests Overall well-structured implementation with solid test coverage for happy paths. Once critical issues are addressed, this will be production-ready! Review performed following project guidelines in CLAUDE.md |
Summary
This PR adds automatic license renewal support for both the Ruby gem and Node Renderer. When a user's license is approaching expiry, the client automatically fetches the renewed license from the licensing API, eliminating the need for manual token updates after subscription renewals.
Key Features
LicenseFetcher,LicenseCache, andLicenseRefreshCheckermoduleslicenseFetcher.ts,licenseCache.ts,licenseRefreshChecker.ts)license_key,license_api_url, andauto_refresh_licensesettingsRefresh Schedule
Configuration
Ruby Gem (via initializer or ENV):
Node Renderer (via ENV):
REACT_ON_RAILS_PRO_LICENSE_KEY=lic_... REACT_ON_RAILS_PRO_LICENSE_API_URL=https://licenses.shakacode.com REACT_ON_RAILS_PRO_AUTO_REFRESH_LICENSE=true # defaultTest Plan
Related
/api/licenseendpoint🤖 Generated with Claude Code
Summary by CodeRabbit
Release Notes
New Features
Configuration
Documentation
✏️ Tip: You can customize this high-level summary in your review settings.