Skip to content

Commit 278b8dc

Browse files
Add license attribution comment and refactor license validation methods for clarity
1 parent 05b8615 commit 278b8dc

File tree

5 files changed

+139
-100
lines changed

5 files changed

+139
-100
lines changed

lib/react_on_rails/helper.rb

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -620,10 +620,23 @@ def rails_context_if_not_already_rendered
620620

621621
@rendered_rails_context = true
622622

623-
content_tag(:script,
624-
json_safe_and_pretty(data).html_safe,
625-
type: "application/json",
626-
id: "js-react-on-rails-context")
623+
attribution_comment = react_on_rails_attribution_comment
624+
script_tag = content_tag(:script,
625+
json_safe_and_pretty(data).html_safe,
626+
type: "application/json",
627+
id: "js-react-on-rails-context")
628+
629+
"#{attribution_comment}\n#{script_tag}".html_safe
630+
end
631+
632+
# Generates the HTML attribution comment
633+
# Pro version calls ReactOnRailsPro::Utils for license-specific details
634+
def react_on_rails_attribution_comment
635+
if ReactOnRails::Utils.react_on_rails_pro?
636+
ReactOnRailsPro::Utils.pro_attribution_comment
637+
else
638+
"<!-- Powered by React on Rails (c) ShakaCode | Open Source -->"
639+
end
627640
end
628641

629642
# prepend the rails_context if not yet applied

react_on_rails_pro/lib/react_on_rails_pro/engine.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ class Engine < Rails::Engine
1515
config.after_initialize do
1616
Rails.logger.info "[React on Rails Pro] Validating license..."
1717

18-
ReactOnRailsPro::LicenseValidator.validate!
18+
ReactOnRailsPro::LicenseValidator.validated_license_data!
1919

2020
Rails.logger.info "[React on Rails Pro] License validation successful"
2121
end

react_on_rails_pro/lib/react_on_rails_pro/license_validator.rb

Lines changed: 72 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -5,82 +5,110 @@
55
module ReactOnRailsPro
66
class LicenseValidator
77
class << self
8-
# Validates the license and raises an exception if invalid.
9-
# Caches the result after first validation.
10-
#
11-
# @return [Boolean] true if license is valid
8+
# Validates the license and returns the license data
9+
# Caches the result after first validation
10+
# @return [Hash] The license data
1211
# @raise [ReactOnRailsPro::Error] if license is invalid
13-
def validate!
14-
return @validate if defined?(@validate)
15-
16-
@validate = validate_license
12+
def validated_license_data!
13+
return @license_data if defined?(@license_data)
14+
15+
begin
16+
# Load and decode license (but don't cache yet)
17+
license_data = load_and_decode_license
18+
19+
# Validate the license (raises if invalid, returns grace_days)
20+
grace_days = validate_license_data(license_data)
21+
22+
# Validation passed - now cache both data and grace days
23+
@license_data = license_data
24+
@grace_days_remaining = grace_days
25+
26+
@license_data
27+
rescue JWT::DecodeError => e
28+
error = "Invalid license signature: #{e.message}. " \
29+
"Your license file may be corrupted. " \
30+
"Get a FREE evaluation license at https://shakacode.com/react-on-rails-pro"
31+
handle_invalid_license(error)
32+
rescue StandardError => e
33+
error = "License validation error: #{e.message}. " \
34+
"Get a FREE evaluation license at https://shakacode.com/react-on-rails-pro"
35+
handle_invalid_license(error)
36+
end
1737
end
1838

1939
def reset!
20-
remove_instance_variable(:@validate) if defined?(@validate)
2140
remove_instance_variable(:@license_data) if defined?(@license_data)
22-
remove_instance_variable(:@validation_error) if defined?(@validation_error)
41+
remove_instance_variable(:@grace_days_remaining) if defined?(@grace_days_remaining)
2342
end
2443

25-
def license_data
26-
@license_data ||= load_and_decode_license
44+
# Checks if the current license is an evaluation/free license
45+
# @return [Boolean] true if plan is not "paid"
46+
def evaluation?
47+
data = validated_license_data!
48+
plan = data["plan"]
49+
plan != "paid"
2750
end
2851

29-
attr_reader :validation_error
52+
# Returns remaining grace period days if license is expired but in grace period
53+
# @return [Integer, nil] Number of days remaining, or nil if not in grace period
54+
def grace_days_remaining
55+
# Ensure license is validated and cached
56+
validated_license_data!
57+
58+
# Return cached grace days (nil if not in grace period)
59+
@grace_days_remaining
60+
end
3061

3162
private
3263

3364
# Grace period: 1 month (in seconds)
3465
GRACE_PERIOD_SECONDS = 30 * 24 * 60 * 60
3566

36-
def validate_license
37-
license = load_and_decode_license
38-
67+
# Validates the license data and raises if invalid
68+
# Logs info/errors and handles grace period logic
69+
# @param license [Hash] The decoded license data
70+
# @return [Integer, nil] Grace days remaining if in grace period, nil otherwise
71+
# @raise [ReactOnRailsPro::Error] if license is invalid
72+
def validate_license_data(license)
3973
# Check that exp field exists
4074
unless license["exp"]
41-
@validation_error = "License is missing required expiration field. " \
42-
"Your license may be from an older version. " \
43-
"Get a FREE evaluation license at https://shakacode.com/react-on-rails-pro"
44-
handle_invalid_license(@validation_error)
75+
error = "License is missing required expiration field. " \
76+
"Your license may be from an older version. " \
77+
"Get a FREE evaluation license at https://shakacode.com/react-on-rails-pro"
78+
handle_invalid_license(error)
4579
end
4680

4781
# Check expiry with grace period for production
4882
current_time = Time.now.to_i
4983
exp_time = license["exp"]
84+
grace_days = nil
5085

5186
if current_time > exp_time
5287
days_expired = ((current_time - exp_time) / (24 * 60 * 60)).to_i
5388

54-
@validation_error = "License has expired #{days_expired} day(s) ago. " \
55-
"Get a FREE evaluation license (3 months) at https://shakacode.com/react-on-rails-pro " \
56-
"or upgrade to a paid license for production use."
89+
error = "License has expired #{days_expired} day(s) ago. " \
90+
"Get a FREE evaluation license (3 months) at https://shakacode.com/react-on-rails-pro " \
91+
"or upgrade to a paid license for production use."
5792

5893
# In production, allow a grace period of 1 month with error logging
5994
if production? && within_grace_period?(exp_time)
60-
grace_days_remaining = grace_days_remaining(exp_time)
95+
# Calculate grace days once here
96+
grace_days = calculate_grace_days_remaining(exp_time)
6197
Rails.logger.error(
62-
"[React on Rails Pro] WARNING: #{@validation_error} " \
63-
"Grace period: #{grace_days_remaining} day(s) remaining. " \
98+
"[React on Rails Pro] WARNING: #{error} " \
99+
"Grace period: #{grace_days} day(s) remaining. " \
64100
"Application will fail to start after grace period expires."
65101
)
66102
else
67-
handle_invalid_license(@validation_error)
103+
handle_invalid_license(error)
68104
end
69105
end
70106

71107
# Log license type if present (for analytics)
72108
log_license_info(license)
73109

74-
true
75-
rescue JWT::DecodeError => e
76-
@validation_error = "Invalid license signature: #{e.message}. " \
77-
"Your license file may be corrupted. " \
78-
"Get a FREE evaluation license at https://shakacode.com/react-on-rails-pro"
79-
handle_invalid_license(@validation_error)
80-
rescue StandardError => e
81-
@validation_error = "License validation error: #{e.message}. " \
82-
"Get a FREE evaluation license at https://shakacode.com/react-on-rails-pro"
83-
handle_invalid_license(@validation_error)
110+
# Return grace days (nil if not in grace period)
111+
grace_days
84112
end
85113

86114
def production?
@@ -91,9 +119,14 @@ def within_grace_period?(exp_time)
91119
Time.now.to_i <= exp_time + GRACE_PERIOD_SECONDS
92120
end
93121

94-
def grace_days_remaining(exp_time)
122+
# Calculates remaining grace period days
123+
# @param exp_time [Integer] Expiration timestamp
124+
# @return [Integer] Days remaining (0 or more)
125+
def calculate_grace_days_remaining(exp_time)
95126
grace_end = exp_time + GRACE_PERIOD_SECONDS
96127
seconds_remaining = grace_end - Time.now.to_i
128+
return 0 if seconds_remaining <= 0
129+
97130
(seconds_remaining / (24 * 60 * 60)).to_i
98131
end
99132

@@ -114,7 +147,7 @@ def load_and_decode_license
114147
algorithm: "RS256",
115148
# Disable automatic expiration verification so we can handle it manually with custom logic
116149
verify_expiration: false
117-
# JWT.decode returns an array [data, header]; we use `.first` to get the data (payload).
150+
# JWT.decode returns an array [data, header]; we use `.first` to get the data (payload).
118151
).first
119152
end
120153

react_on_rails_pro/lib/react_on_rails_pro/utils.rb

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,5 +164,23 @@ def self.printable_cache_key(cache_key)
164164
end
165165
end.join("_").underscore
166166
end
167+
168+
# Generates the Pro-specific HTML attribution comment based on license status
169+
# Called by React on Rails helper to generate license-specific attribution
170+
def self.pro_attribution_comment
171+
base = "Powered by React on Rails Pro (c) ShakaCode"
172+
173+
# Check if in grace period
174+
grace_days = ReactOnRailsPro::LicenseValidator.grace_days_remaining
175+
comment = if grace_days
176+
"#{base} | Licensed (Expired - Grace Period: #{grace_days} days remaining)"
177+
elsif ReactOnRailsPro::LicenseValidator.evaluation?
178+
"#{base} | Evaluation License"
179+
else
180+
"#{base} | Licensed"
181+
end
182+
183+
"<!-- #{comment} -->"
184+
end
167185
end
168186
end

0 commit comments

Comments
 (0)