source_license_sdk 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +43 -0
- data/Gemfile +8 -0
- data/LICENSE +674 -0
- data/README.md +329 -0
- data/Rakefile +5 -0
- data/lib/source_license_sdk/client.rb +180 -0
- data/lib/source_license_sdk/exceptions.rb +62 -0
- data/lib/source_license_sdk/license_validator.rb +103 -0
- data/lib/source_license_sdk/machine_identifier.rb +179 -0
- data/lib/source_license_sdk/version.rb +5 -0
- data/lib/source_license_sdk.rb +93 -0
- data/source_license_sdk.gemspec +42 -0
- metadata +99 -0
data/README.md
ADDED
@@ -0,0 +1,329 @@
|
|
1
|
+
# Source-License Ruby SDK
|
2
|
+
|
3
|
+
A Ruby gem for easy integration with the Source-License platform for license validation and activation.
|
4
|
+
|
5
|
+
## Features
|
6
|
+
|
7
|
+
- **Simple License Validation**: Check if a license key is valid with one method call
|
8
|
+
- **License Activation**: Activate licenses on specific machines with automatic machine fingerprinting
|
9
|
+
- **License Enforcement**: Automatically exit your application if license validation fails
|
10
|
+
- **Rate Limiting Handling**: Built-in handling of API rate limits with retry information
|
11
|
+
- **Secure Communication**: Uses HTTPS and handles all Source-License API security requirements
|
12
|
+
- **Cross-Platform Machine Identification**: Works on Windows, macOS, and Linux
|
13
|
+
|
14
|
+
## Installation
|
15
|
+
|
16
|
+
Add this line to your application's Gemfile:
|
17
|
+
|
18
|
+
```ruby
|
19
|
+
gem 'source_license_sdk'
|
20
|
+
```
|
21
|
+
|
22
|
+
And then execute:
|
23
|
+
|
24
|
+
```bash
|
25
|
+
bundle install
|
26
|
+
```
|
27
|
+
|
28
|
+
Or install it yourself as:
|
29
|
+
|
30
|
+
```bash
|
31
|
+
gem install source_license_sdk
|
32
|
+
```
|
33
|
+
|
34
|
+
## Quick Start
|
35
|
+
|
36
|
+
### Basic Setup
|
37
|
+
|
38
|
+
```ruby
|
39
|
+
require 'source_license_sdk'
|
40
|
+
|
41
|
+
# Configure the SDK
|
42
|
+
SourceLicenseSDK.setup(
|
43
|
+
server_url: 'https://your-license-server.com',
|
44
|
+
license_key: 'YOUR-LICENSE-KEY',
|
45
|
+
machine_id: 'unique-machine-identifier' # Optional, will auto-generate if not provided
|
46
|
+
)
|
47
|
+
```
|
48
|
+
|
49
|
+
### Method 1: License Validation
|
50
|
+
|
51
|
+
Check if a license is valid without activating it:
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
result = SourceLicenseSDK.validate_license
|
55
|
+
|
56
|
+
if result.valid?
|
57
|
+
puts "License is valid!"
|
58
|
+
puts "Expires at: #{result.expires_at}" if result.expires_at
|
59
|
+
else
|
60
|
+
puts "License validation failed: #{result.error_message}"
|
61
|
+
end
|
62
|
+
```
|
63
|
+
|
64
|
+
### Method 2: License Activation
|
65
|
+
|
66
|
+
Activate a license on the current machine:
|
67
|
+
|
68
|
+
```ruby
|
69
|
+
result = SourceLicenseSDK.activate_license
|
70
|
+
|
71
|
+
if result.success?
|
72
|
+
puts "License activated successfully!"
|
73
|
+
puts "Activations remaining: #{result.activations_remaining}"
|
74
|
+
else
|
75
|
+
puts "Activation failed: #{result.error_message}"
|
76
|
+
end
|
77
|
+
```
|
78
|
+
|
79
|
+
### Method 3: License Enforcement
|
80
|
+
|
81
|
+
Automatically exit the application if license validation fails:
|
82
|
+
|
83
|
+
```ruby
|
84
|
+
# This will exit the program with code 1 if the license is invalid
|
85
|
+
SourceLicenseSDK.enforce_license!
|
86
|
+
|
87
|
+
# Your application code continues here only if license is valid
|
88
|
+
puts "Application starting with valid license..."
|
89
|
+
```
|
90
|
+
|
91
|
+
## Advanced Usage
|
92
|
+
|
93
|
+
### Custom Configuration
|
94
|
+
|
95
|
+
```ruby
|
96
|
+
SourceLicenseSDK.configure do |config|
|
97
|
+
config.server_url = 'https://your-license-server.com'
|
98
|
+
config.license_key = 'YOUR-LICENSE-KEY'
|
99
|
+
config.machine_id = 'custom-machine-id'
|
100
|
+
config.timeout = 30
|
101
|
+
config.verify_ssl = true
|
102
|
+
config.user_agent = 'MyApp/1.0.0'
|
103
|
+
end
|
104
|
+
```
|
105
|
+
|
106
|
+
### Manual Machine ID Generation
|
107
|
+
|
108
|
+
```ruby
|
109
|
+
# Generate a unique machine identifier
|
110
|
+
machine_id = SourceLicenseSDK::MachineIdentifier.generate
|
111
|
+
puts "Machine ID: #{machine_id}"
|
112
|
+
|
113
|
+
# Generate a machine fingerprint (more detailed)
|
114
|
+
fingerprint = SourceLicenseSDK::MachineIdentifier.generate_fingerprint
|
115
|
+
puts "Machine Fingerprint: #{fingerprint}"
|
116
|
+
```
|
117
|
+
|
118
|
+
### Error Handling
|
119
|
+
|
120
|
+
```ruby
|
121
|
+
begin
|
122
|
+
result = SourceLicenseSDK.validate_license
|
123
|
+
|
124
|
+
if result.valid?
|
125
|
+
puts "License is valid"
|
126
|
+
else
|
127
|
+
puts "License invalid: #{result.error_message}"
|
128
|
+
end
|
129
|
+
rescue SourceLicenseSDK::NetworkError => e
|
130
|
+
puts "Network error: #{e.message} (Code: #{e.response_code})"
|
131
|
+
rescue SourceLicenseSDK::RateLimitError => e
|
132
|
+
puts "Rate limited. Retry after #{e.retry_after} seconds"
|
133
|
+
rescue SourceLicenseSDK::ConfigurationError => e
|
134
|
+
puts "Configuration error: #{e.message}"
|
135
|
+
end
|
136
|
+
```
|
137
|
+
|
138
|
+
### Working with Results
|
139
|
+
|
140
|
+
```ruby
|
141
|
+
result = SourceLicenseSDK.validate_license
|
142
|
+
|
143
|
+
# Check various result properties
|
144
|
+
puts "Valid: #{result.valid?}"
|
145
|
+
puts "Expires at: #{result.expires_at}"
|
146
|
+
puts "Rate limited: #{result.rate_limited?}"
|
147
|
+
puts "Rate limit remaining: #{result.rate_limit_remaining}"
|
148
|
+
puts "Error code: #{result.error_code}" if result.error_code
|
149
|
+
|
150
|
+
# Convert to hash
|
151
|
+
puts result.to_h
|
152
|
+
```
|
153
|
+
|
154
|
+
### Custom License Enforcement
|
155
|
+
|
156
|
+
```ruby
|
157
|
+
# Custom exit code and message
|
158
|
+
SourceLicenseSDK.enforce_license!(
|
159
|
+
exit_code: 2,
|
160
|
+
custom_message: "This software requires a valid license to run."
|
161
|
+
)
|
162
|
+
|
163
|
+
# Use specific license key and machine ID
|
164
|
+
SourceLicenseSDK.enforce_license!(
|
165
|
+
'SPECIFIC-LICENSE-KEY',
|
166
|
+
machine_id: 'specific-machine-id'
|
167
|
+
)
|
168
|
+
```
|
169
|
+
|
170
|
+
## Integration Examples
|
171
|
+
|
172
|
+
### Ruby on Rails Application
|
173
|
+
|
174
|
+
```ruby
|
175
|
+
# config/initializers/source_license.rb
|
176
|
+
SourceLicenseSDK.setup(
|
177
|
+
server_url: Rails.application.credentials.license_server_url,
|
178
|
+
license_key: Rails.application.credentials.license_key
|
179
|
+
)
|
180
|
+
|
181
|
+
# In your application controller or concern
|
182
|
+
class ApplicationController < ActionController::Base
|
183
|
+
before_action :validate_license
|
184
|
+
|
185
|
+
private
|
186
|
+
|
187
|
+
def validate_license
|
188
|
+
result = SourceLicenseSDK.validate_license
|
189
|
+
|
190
|
+
unless result.valid?
|
191
|
+
render json: { error: 'Invalid license' }, status: :forbidden
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
```
|
196
|
+
|
197
|
+
### Command Line Tool
|
198
|
+
|
199
|
+
```ruby
|
200
|
+
#!/usr/bin/env ruby
|
201
|
+
require 'source_license_sdk'
|
202
|
+
|
203
|
+
# Setup license checking
|
204
|
+
SourceLicenseSDK.setup(
|
205
|
+
server_url: 'https://license.mycompany.com',
|
206
|
+
license_key: ARGV[0] || ENV['LICENSE_KEY']
|
207
|
+
)
|
208
|
+
|
209
|
+
# Enforce license before running
|
210
|
+
SourceLicenseSDK.enforce_license!(
|
211
|
+
custom_message: "Please provide a valid license key to use this tool."
|
212
|
+
)
|
213
|
+
|
214
|
+
# Your application logic here
|
215
|
+
puts "Tool is running with valid license!"
|
216
|
+
```
|
217
|
+
|
218
|
+
### Desktop Application
|
219
|
+
|
220
|
+
```ruby
|
221
|
+
require 'source_license_sdk'
|
222
|
+
|
223
|
+
class MyApplication
|
224
|
+
def initialize
|
225
|
+
setup_licensing
|
226
|
+
end
|
227
|
+
|
228
|
+
private
|
229
|
+
|
230
|
+
def setup_licensing
|
231
|
+
SourceLicenseSDK.setup(
|
232
|
+
server_url: 'https://licensing.myapp.com',
|
233
|
+
license_key: load_license_key,
|
234
|
+
auto_generate_machine_id: true
|
235
|
+
)
|
236
|
+
|
237
|
+
# Try to activate license if not already done
|
238
|
+
activate_license_if_needed
|
239
|
+
|
240
|
+
# Validate license on startup
|
241
|
+
validate_license!
|
242
|
+
end
|
243
|
+
|
244
|
+
def load_license_key
|
245
|
+
# Load from config file, registry, etc.
|
246
|
+
File.read('license.key').strip
|
247
|
+
rescue
|
248
|
+
nil
|
249
|
+
end
|
250
|
+
|
251
|
+
def activate_license_if_needed
|
252
|
+
result = SourceLicenseSDK.validate_license
|
253
|
+
|
254
|
+
unless result.valid?
|
255
|
+
puts "Activating license..."
|
256
|
+
activation_result = SourceLicenseSDK.activate_license
|
257
|
+
|
258
|
+
unless activation_result.success?
|
259
|
+
puts "Failed to activate license: #{activation_result.error_message}"
|
260
|
+
exit 1
|
261
|
+
end
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
def validate_license!
|
266
|
+
SourceLicenseSDK.enforce_license!(
|
267
|
+
custom_message: "This application requires a valid license."
|
268
|
+
)
|
269
|
+
end
|
270
|
+
end
|
271
|
+
```
|
272
|
+
|
273
|
+
## Error Types
|
274
|
+
|
275
|
+
The SDK defines several exception types for different error scenarios:
|
276
|
+
|
277
|
+
- `SourceLicenseSDK::ConfigurationError` - Invalid SDK configuration
|
278
|
+
- `SourceLicenseSDK::NetworkError` - HTTP/network related errors
|
279
|
+
- `SourceLicenseSDK::LicenseError` - General license validation errors
|
280
|
+
- `SourceLicenseSDK::RateLimitError` - API rate limiting errors
|
281
|
+
- `SourceLicenseSDK::LicenseNotFoundError` - License not found
|
282
|
+
- `SourceLicenseSDK::LicenseExpiredError` - License has expired
|
283
|
+
- `SourceLicenseSDK::ActivationError` - License activation errors
|
284
|
+
- `SourceLicenseSDK::MachineError` - Machine identification errors
|
285
|
+
|
286
|
+
## Configuration Options
|
287
|
+
|
288
|
+
| Option | Type | Default | Description |
|
289
|
+
|--------|------|---------|-------------|
|
290
|
+
| `server_url` | String | nil | Source-License server URL (required) |
|
291
|
+
| `license_key` | String | nil | License key to validate/activate |
|
292
|
+
| `machine_id` | String | nil | Unique machine identifier |
|
293
|
+
| `auto_generate_machine_id` | Boolean | true | Auto-generate machine ID if not provided |
|
294
|
+
| `timeout` | Integer | 30 | HTTP request timeout in seconds |
|
295
|
+
| `user_agent` | String | "SourceLicenseSDK/VERSION" | HTTP User-Agent header |
|
296
|
+
| `verify_ssl` | Boolean | true | Verify SSL certificates |
|
297
|
+
|
298
|
+
## Development
|
299
|
+
|
300
|
+
After checking out the repo, run:
|
301
|
+
|
302
|
+
```bash
|
303
|
+
bundle install
|
304
|
+
```
|
305
|
+
|
306
|
+
To build and install the gem locally:
|
307
|
+
|
308
|
+
```bash
|
309
|
+
gem build source_license_sdk.gemspec
|
310
|
+
gem install source_license_sdk-*.gem
|
311
|
+
```
|
312
|
+
|
313
|
+
## Contributing
|
314
|
+
|
315
|
+
1. Fork the repository
|
316
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
317
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
318
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
319
|
+
5. Create a new Pull Request
|
320
|
+
|
321
|
+
## License
|
322
|
+
|
323
|
+
This gem is available as open source under the terms of the [GPL-3.0 License](LICENSE).
|
324
|
+
|
325
|
+
## Support
|
326
|
+
|
327
|
+
For support with this SDK, please open an issue on the [Source-License repository](https://github.com/PixelRidgeSoftworks/Source-License).
|
328
|
+
|
329
|
+
For general Source-License platform support, please contact your license provider.
|
data/Rakefile
ADDED
@@ -0,0 +1,180 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'net/http'
|
4
|
+
require 'uri'
|
5
|
+
require 'json'
|
6
|
+
|
7
|
+
# HTTP client for communicating with Source-License API
|
8
|
+
class SourceLicenseSDK::Client
|
9
|
+
attr_reader :config
|
10
|
+
|
11
|
+
def initialize(config)
|
12
|
+
@config = config
|
13
|
+
validate_config!
|
14
|
+
end
|
15
|
+
|
16
|
+
# Validate a license key
|
17
|
+
def validate_license(license_key, machine_id: nil, machine_fingerprint: nil)
|
18
|
+
machine_fingerprint ||= MachineIdentifier.generate_fingerprint if machine_id
|
19
|
+
|
20
|
+
path = "/api/license/#{license_key}/validate"
|
21
|
+
params = {}
|
22
|
+
params[:machine_id] = machine_id if machine_id
|
23
|
+
params[:machine_fingerprint] = machine_fingerprint if machine_fingerprint
|
24
|
+
|
25
|
+
response = make_request(:get, path, params: params)
|
26
|
+
LicenseValidationResult.new(response)
|
27
|
+
rescue NetworkError => e
|
28
|
+
handle_network_error(e)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Activate a license on this machine
|
32
|
+
def activate_license(license_key, machine_id:, machine_fingerprint: nil)
|
33
|
+
machine_fingerprint ||= MachineIdentifier.generate_fingerprint
|
34
|
+
|
35
|
+
path = "/api/license/#{license_key}/activate"
|
36
|
+
body = {
|
37
|
+
machine_id: machine_id,
|
38
|
+
machine_fingerprint: machine_fingerprint,
|
39
|
+
}
|
40
|
+
|
41
|
+
response = make_request(:post, path, body: body)
|
42
|
+
LicenseValidationResult.new(response)
|
43
|
+
rescue NetworkError => e
|
44
|
+
handle_network_error(e)
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def validate_config!
|
50
|
+
raise ConfigurationError, 'Server URL is required' unless config.server_url
|
51
|
+
raise ConfigurationError, 'Invalid server URL format' unless valid_url?(config.server_url)
|
52
|
+
end
|
53
|
+
|
54
|
+
def valid_url?(url)
|
55
|
+
uri = URI.parse(url)
|
56
|
+
uri.is_a?(URI::HTTP) || uri.is_a?(URI::HTTPS)
|
57
|
+
rescue URI::InvalidURIError
|
58
|
+
false
|
59
|
+
end
|
60
|
+
|
61
|
+
def make_request(method, path, params: {}, body: nil)
|
62
|
+
uri = build_uri(path, params)
|
63
|
+
http = create_http_client(uri)
|
64
|
+
request = create_request(method, uri, body)
|
65
|
+
|
66
|
+
response = http.request(request)
|
67
|
+
handle_response(response)
|
68
|
+
end
|
69
|
+
|
70
|
+
def build_uri(path, params = {})
|
71
|
+
base_uri = URI.parse(config.server_url)
|
72
|
+
base_uri.path = path
|
73
|
+
|
74
|
+
base_uri.query = URI.encode_www_form(params) if params.any?
|
75
|
+
|
76
|
+
base_uri
|
77
|
+
end
|
78
|
+
|
79
|
+
def create_http_client(uri)
|
80
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
81
|
+
http.use_ssl = uri.scheme == 'https'
|
82
|
+
http.verify_mode = config.verify_ssl ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE
|
83
|
+
http.read_timeout = config.timeout
|
84
|
+
http.open_timeout = config.timeout
|
85
|
+
http
|
86
|
+
end
|
87
|
+
|
88
|
+
def create_request(method, uri, body)
|
89
|
+
request_class = case method
|
90
|
+
when :get then Net::HTTP::Get
|
91
|
+
when :post then Net::HTTP::Post
|
92
|
+
when :put then Net::HTTP::Put
|
93
|
+
when :delete then Net::HTTP::Delete
|
94
|
+
else raise ArgumentError, "Unsupported HTTP method: #{method}"
|
95
|
+
end
|
96
|
+
|
97
|
+
request = request_class.new(uri)
|
98
|
+
request['User-Agent'] = config.user_agent
|
99
|
+
request['Accept'] = 'application/json'
|
100
|
+
request['Content-Type'] = 'application/json' if body
|
101
|
+
|
102
|
+
if body
|
103
|
+
request.body = body.is_a?(String) ? body : JSON.generate(body)
|
104
|
+
end
|
105
|
+
|
106
|
+
request
|
107
|
+
end
|
108
|
+
|
109
|
+
def handle_response(response)
|
110
|
+
case response.code.to_i
|
111
|
+
when 200..299
|
112
|
+
parse_json_response(response.body)
|
113
|
+
when 400
|
114
|
+
data = parse_json_response(response.body)
|
115
|
+
raise_license_error(data, response.code.to_i)
|
116
|
+
when 404
|
117
|
+
data = parse_json_response(response.body)
|
118
|
+
raise LicenseNotFoundError, data['error'] || 'License not found'
|
119
|
+
when 429
|
120
|
+
data = parse_json_response(response.body)
|
121
|
+
retry_after = response['Retry-After']&.to_i || data['retry_after']
|
122
|
+
raise RateLimitError.new(data['error'] || 'Rate limit exceeded', retry_after: retry_after)
|
123
|
+
when 500..599
|
124
|
+
raise NetworkError.new('Server error occurred', response_code: response.code.to_i, response_body: response.body)
|
125
|
+
else
|
126
|
+
raise NetworkError.new("Unexpected response: #{response.code}", response_code: response.code.to_i,
|
127
|
+
response_body: response.body)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def parse_json_response(body)
|
132
|
+
return {} if body.nil? || body.empty?
|
133
|
+
|
134
|
+
JSON.parse(body)
|
135
|
+
rescue JSON::ParserError
|
136
|
+
raise NetworkError.new('Invalid JSON response from server', response_body: body)
|
137
|
+
end
|
138
|
+
|
139
|
+
def raise_license_error(data, _status_code)
|
140
|
+
error_message = data['error'] || data['message'] || 'License validation failed'
|
141
|
+
|
142
|
+
case error_message.downcase
|
143
|
+
when /expired/
|
144
|
+
raise LicenseExpiredError, error_message
|
145
|
+
when /rate limit/
|
146
|
+
retry_after = data['retry_after']
|
147
|
+
raise RateLimitError.new(error_message, retry_after: retry_after)
|
148
|
+
when /not found/
|
149
|
+
raise LicenseNotFoundError, error_message
|
150
|
+
when /activation/
|
151
|
+
raise ActivationError, error_message
|
152
|
+
else
|
153
|
+
raise LicenseError.new(error_message, error_code: data['error_code'])
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def handle_network_error(error)
|
158
|
+
# Convert network errors to license validation results for consistency
|
159
|
+
case error
|
160
|
+
when RateLimitError
|
161
|
+
LicenseValidationResult.new(
|
162
|
+
valid: false,
|
163
|
+
success: false,
|
164
|
+
error: error.message,
|
165
|
+
error_code: error.error_code,
|
166
|
+
retry_after: error.retry_after
|
167
|
+
)
|
168
|
+
when LicenseNotFoundError, LicenseExpiredError, ActivationError
|
169
|
+
LicenseValidationResult.new(
|
170
|
+
valid: false,
|
171
|
+
success: false,
|
172
|
+
error: error.message,
|
173
|
+
error_code: error.error_code
|
174
|
+
)
|
175
|
+
else
|
176
|
+
# Re-raise other network errors
|
177
|
+
raise error
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SourceLicenseSDK
|
4
|
+
# Base exception class for all SDK errors
|
5
|
+
class Error < StandardError; end
|
6
|
+
|
7
|
+
# Configuration related errors
|
8
|
+
class ConfigurationError < Error; end
|
9
|
+
|
10
|
+
# Network and HTTP related errors
|
11
|
+
class NetworkError < Error
|
12
|
+
attr_reader :response_code, :response_body
|
13
|
+
|
14
|
+
def initialize(message, response_code: nil, response_body: nil)
|
15
|
+
super(message)
|
16
|
+
@response_code = response_code
|
17
|
+
@response_body = response_body
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# License validation errors
|
22
|
+
class LicenseError < Error
|
23
|
+
attr_reader :error_code, :retry_after
|
24
|
+
|
25
|
+
def initialize(message, error_code: nil, retry_after: nil)
|
26
|
+
super(message)
|
27
|
+
@error_code = error_code
|
28
|
+
@retry_after = retry_after
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Rate limiting errors
|
33
|
+
class RateLimitError < LicenseError
|
34
|
+
def initialize(message = 'Rate limit exceeded', retry_after: nil)
|
35
|
+
super(message, error_code: 'RATE_LIMIT_EXCEEDED', retry_after: retry_after)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# License not found errors
|
40
|
+
class LicenseNotFoundError < LicenseError
|
41
|
+
def initialize(message = 'License not found')
|
42
|
+
super(message, error_code: 'LICENSE_NOT_FOUND')
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# License expired errors
|
47
|
+
class LicenseExpiredError < LicenseError
|
48
|
+
def initialize(message = 'License has expired')
|
49
|
+
super(message, error_code: 'LICENSE_EXPIRED')
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# License activation errors
|
54
|
+
class ActivationError < LicenseError
|
55
|
+
def initialize(message, error_code: 'ACTIVATION_FAILED')
|
56
|
+
super
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# Machine ID related errors
|
61
|
+
class MachineError < Error; end
|
62
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Represents the result of a license validation or activation request
|
4
|
+
class SourceLicenseSDK::LicenseValidationResult
|
5
|
+
attr_reader :valid, :success, :error_message, :error_code, :expires_at,
|
6
|
+
:activations_remaining, :retry_after, :token, :timestamp,
|
7
|
+
:rate_limit_remaining, :rate_limit_reset_at
|
8
|
+
|
9
|
+
def initialize(data)
|
10
|
+
@data = data
|
11
|
+
|
12
|
+
initialize_validation_fields
|
13
|
+
initialize_activation_fields
|
14
|
+
initialize_common_fields
|
15
|
+
initialize_rate_limit_fields
|
16
|
+
end
|
17
|
+
|
18
|
+
def valid?
|
19
|
+
@valid == true
|
20
|
+
end
|
21
|
+
|
22
|
+
def success?
|
23
|
+
@success == true
|
24
|
+
end
|
25
|
+
|
26
|
+
def expired?
|
27
|
+
return false unless @expires_at
|
28
|
+
|
29
|
+
@expires_at < Time.now
|
30
|
+
end
|
31
|
+
|
32
|
+
def rate_limited?
|
33
|
+
@error_message&.downcase&.include?('rate limit') ||
|
34
|
+
@error_code == 'RATE_LIMIT_EXCEEDED'
|
35
|
+
end
|
36
|
+
|
37
|
+
def to_h
|
38
|
+
{
|
39
|
+
valid: @valid,
|
40
|
+
success: @success,
|
41
|
+
error_message: @error_message,
|
42
|
+
error_code: @error_code,
|
43
|
+
expires_at: @expires_at,
|
44
|
+
activations_remaining: @activations_remaining,
|
45
|
+
retry_after: @retry_after,
|
46
|
+
token: @token,
|
47
|
+
timestamp: @timestamp,
|
48
|
+
rate_limit_remaining: @rate_limit_remaining,
|
49
|
+
rate_limit_reset_at: @rate_limit_reset_at,
|
50
|
+
}
|
51
|
+
end
|
52
|
+
|
53
|
+
def inspect
|
54
|
+
"#<#{self.class.name} valid=#{@valid} success=#{@success} error='#{@error_message}'>"
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def initialize_validation_fields
|
60
|
+
@valid = extract_value(:valid, false)
|
61
|
+
@token = extract_value(:token)
|
62
|
+
end
|
63
|
+
|
64
|
+
def initialize_activation_fields
|
65
|
+
@success = extract_value(:success, false)
|
66
|
+
@activations_remaining = extract_value(:activations_remaining)
|
67
|
+
end
|
68
|
+
|
69
|
+
def initialize_common_fields
|
70
|
+
@error_message = extract_error_message
|
71
|
+
@error_code = extract_value(:error_code)
|
72
|
+
@expires_at = parse_datetime(extract_value(:expires_at))
|
73
|
+
@retry_after = extract_value(:retry_after)
|
74
|
+
@timestamp = parse_datetime(extract_value(:timestamp))
|
75
|
+
end
|
76
|
+
|
77
|
+
def initialize_rate_limit_fields
|
78
|
+
@rate_limit_remaining = extract_rate_limit_value(:remaining)
|
79
|
+
@rate_limit_reset_at = parse_datetime(extract_rate_limit_value(:reset_at))
|
80
|
+
end
|
81
|
+
|
82
|
+
def extract_value(key, default = nil)
|
83
|
+
@data[key] || @data[key.to_s] || default
|
84
|
+
end
|
85
|
+
|
86
|
+
def extract_error_message
|
87
|
+
extract_value(:error) || extract_value(:message)
|
88
|
+
end
|
89
|
+
|
90
|
+
def extract_rate_limit_value(key)
|
91
|
+
@data[:rate_limit]&.dig(key) || @data['rate_limit']&.dig(key.to_s)
|
92
|
+
end
|
93
|
+
|
94
|
+
def parse_datetime(value)
|
95
|
+
return nil if value.nil?
|
96
|
+
return value if value.is_a?(Time)
|
97
|
+
return Time.at(value) if value.is_a?(Numeric)
|
98
|
+
|
99
|
+
Time.parse(value.to_s)
|
100
|
+
rescue ArgumentError
|
101
|
+
nil
|
102
|
+
end
|
103
|
+
end
|