@licenseseat/js 0.1.0 โ 0.2.1
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.
- package/README.md +684 -83
- package/dist/index.js +775 -1526
- package/dist/types/LicenseSeat.d.ts +309 -0
- package/dist/types/LicenseSeat.d.ts.map +1 -0
- package/dist/types/cache.d.ts +93 -0
- package/dist/types/cache.d.ts.map +1 -0
- package/dist/types/errors.d.ts +58 -0
- package/dist/types/errors.d.ts.map +1 -0
- package/dist/types/index.d.ts +7 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/types.d.ts +342 -0
- package/dist/types/types.d.ts.map +1 -0
- package/dist/types/utils.d.ts +54 -0
- package/dist/types/utils.d.ts.map +1 -0
- package/package.json +44 -8
- package/src/LicenseSeat.js +1247 -0
- package/src/cache.js +189 -0
- package/src/errors.js +77 -0
- package/src/index.js +62 -0
- package/src/types.js +148 -0
- package/src/utils.js +154 -0
package/README.md
CHANGED
|
@@ -1,168 +1,769 @@
|
|
|
1
|
-
#
|
|
1
|
+
# LicenseSeat JavaScript SDK
|
|
2
2
|
|
|
3
|
-
Official JavaScript
|
|
3
|
+
Official JavaScript/TypeScript SDK for [LicenseSeat](https://licenseseat.com) โ the simple, secure licensing platform for apps, games, and plugins.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
[](https://github.com/licenseseat/licenseseat-js/actions/workflows/ci.yml)
|
|
6
|
+
[](https://www.npmjs.com/package/@licenseseat/js)
|
|
7
|
+
[](https://opensource.org/licenses/MIT)
|
|
6
8
|
|
|
7
9
|
---
|
|
8
10
|
|
|
9
|
-
##
|
|
11
|
+
## Features
|
|
10
12
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
- **License activation & deactivation** โ Activate licenses with automatic device fingerprinting
|
|
14
|
+
- **Online & offline validation** โ Validate licenses with optional offline fallback
|
|
15
|
+
- **Entitlement checking** โ Check feature access with `hasEntitlement()` and `checkEntitlement()`
|
|
16
|
+
- **Local caching** โ Secure localStorage-based caching with clock tamper detection
|
|
17
|
+
- **Auto-retry with exponential backoff** โ Resilient network handling
|
|
18
|
+
- **Event-driven architecture** โ Subscribe to SDK lifecycle events
|
|
19
|
+
- **TypeScript support** โ Full type definitions included (auto-generated from JSDoc)
|
|
20
|
+
- **Modern ESM package** โ Native ES modules, tree-shakeable
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
14
25
|
|
|
15
|
-
|
|
26
|
+
### npm / yarn / pnpm
|
|
16
27
|
|
|
17
28
|
```bash
|
|
29
|
+
# npm
|
|
30
|
+
npm install @licenseseat/js
|
|
31
|
+
|
|
32
|
+
# yarn
|
|
18
33
|
yarn add @licenseseat/js
|
|
34
|
+
|
|
35
|
+
# pnpm
|
|
36
|
+
pnpm add @licenseseat/js
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### CDN (Browser)
|
|
40
|
+
|
|
41
|
+
```html
|
|
42
|
+
<!-- ESM via esm.sh -->
|
|
43
|
+
<script type="module">
|
|
44
|
+
import LicenseSeat from 'https://esm.sh/@licenseseat/js';
|
|
45
|
+
|
|
46
|
+
const sdk = new LicenseSeat({ apiKey: 'your-api-key' });
|
|
47
|
+
</script>
|
|
48
|
+
|
|
49
|
+
<!-- ESM via unpkg -->
|
|
50
|
+
<script type="module">
|
|
51
|
+
import LicenseSeat from 'https://unpkg.com/@licenseseat/js/dist/index.js';
|
|
52
|
+
</script>
|
|
53
|
+
|
|
54
|
+
<!-- ESM via jsDelivr -->
|
|
55
|
+
<script type="module">
|
|
56
|
+
import LicenseSeat from 'https://cdn.jsdelivr.net/npm/@licenseseat/js/dist/index.js';
|
|
57
|
+
</script>
|
|
19
58
|
```
|
|
20
59
|
|
|
21
60
|
---
|
|
22
61
|
|
|
23
|
-
##
|
|
62
|
+
## Quick Start
|
|
24
63
|
|
|
25
|
-
|
|
64
|
+
### JavaScript (ESM)
|
|
65
|
+
|
|
66
|
+
```javascript
|
|
26
67
|
import LicenseSeat from '@licenseseat/js';
|
|
27
68
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
69
|
+
// Create SDK instance
|
|
70
|
+
const sdk = new LicenseSeat({
|
|
71
|
+
apiKey: 'your-api-key',
|
|
31
72
|
debug: true
|
|
32
73
|
});
|
|
33
74
|
|
|
34
|
-
// Activate a license
|
|
35
|
-
await
|
|
75
|
+
// Activate a license
|
|
76
|
+
await sdk.activate('YOUR-LICENSE-KEY');
|
|
36
77
|
|
|
37
|
-
// Check
|
|
38
|
-
if (
|
|
39
|
-
//
|
|
78
|
+
// Check entitlements (simple boolean)
|
|
79
|
+
if (sdk.hasEntitlement('pro')) {
|
|
80
|
+
// Enable pro features
|
|
40
81
|
}
|
|
82
|
+
|
|
83
|
+
// Get current status
|
|
84
|
+
const status = sdk.getStatus();
|
|
85
|
+
console.log(status);
|
|
86
|
+
// { status: 'active', license: '...', entitlements: [...] }
|
|
41
87
|
```
|
|
42
88
|
|
|
43
|
-
|
|
89
|
+
### TypeScript
|
|
44
90
|
|
|
45
|
-
|
|
91
|
+
```typescript
|
|
92
|
+
import LicenseSeat, {
|
|
93
|
+
type LicenseSeatConfig,
|
|
94
|
+
type ValidationResult,
|
|
95
|
+
type EntitlementCheckResult,
|
|
96
|
+
type LicenseStatus
|
|
97
|
+
} from '@licenseseat/js';
|
|
46
98
|
|
|
47
|
-
|
|
99
|
+
const config: LicenseSeatConfig = {
|
|
100
|
+
apiKey: 'your-api-key',
|
|
101
|
+
debug: true
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const sdk = new LicenseSeat(config);
|
|
48
105
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
106
|
+
// Full type inference
|
|
107
|
+
const result: ValidationResult = await sdk.validateLicense('LICENSE-KEY');
|
|
108
|
+
const status: LicenseStatus = sdk.getStatus();
|
|
109
|
+
const hasPro: boolean = sdk.hasEntitlement('pro');
|
|
110
|
+
```
|
|
52
111
|
|
|
53
|
-
|
|
112
|
+
TypeScript users get full type support automatically โ the package includes generated `.d.ts` declaration files.
|
|
54
113
|
|
|
55
114
|
---
|
|
56
115
|
|
|
57
|
-
##
|
|
116
|
+
## Configuration
|
|
58
117
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
* ๐ฅ **Encrypted local caching**
|
|
64
|
-
* ๐ฏ **Auto-retry on network failure**
|
|
65
|
-
* ๐ก **Event emitters for reactive state**
|
|
66
|
-
* โ๏ธ **Fully ESM-compatible**
|
|
118
|
+
```javascript
|
|
119
|
+
const sdk = new LicenseSeat({
|
|
120
|
+
// Required for authenticated operations
|
|
121
|
+
apiKey: 'your-api-key',
|
|
67
122
|
|
|
68
|
-
|
|
123
|
+
// API Configuration
|
|
124
|
+
apiBaseUrl: 'https://licenseseat.com/api', // Default
|
|
69
125
|
|
|
70
|
-
|
|
126
|
+
// Storage
|
|
127
|
+
storagePrefix: 'licenseseat_', // localStorage key prefix
|
|
71
128
|
|
|
72
|
-
|
|
129
|
+
// Auto-Validation
|
|
130
|
+
autoValidateInterval: 3600000, // 1 hour (in ms)
|
|
131
|
+
autoInitialize: true, // Auto-validate cached license on init
|
|
73
132
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
133
|
+
// Offline Support
|
|
134
|
+
offlineFallbackEnabled: false, // Enable offline validation fallback
|
|
135
|
+
maxOfflineDays: 0, // Max days offline (0 = disabled)
|
|
136
|
+
offlineLicenseRefreshInterval: 259200000, // 72 hours
|
|
137
|
+
maxClockSkewMs: 300000, // 5 minutes
|
|
79
138
|
|
|
80
|
-
|
|
139
|
+
// Network
|
|
140
|
+
maxRetries: 3, // Retry attempts for failed requests
|
|
141
|
+
retryDelay: 1000, // Initial retry delay (ms)
|
|
142
|
+
networkRecheckInterval: 30000, // Check connectivity every 30s when offline
|
|
81
143
|
|
|
82
|
-
|
|
144
|
+
// Debug
|
|
145
|
+
debug: false // Enable console logging
|
|
146
|
+
});
|
|
147
|
+
```
|
|
83
148
|
|
|
84
|
-
|
|
149
|
+
### Configuration Options
|
|
150
|
+
|
|
151
|
+
| Option | Type | Default | Description |
|
|
152
|
+
|--------|------|---------|-------------|
|
|
153
|
+
| `apiKey` | `string` | `null` | API key for authentication (required for most operations) |
|
|
154
|
+
| `apiBaseUrl` | `string` | `'https://licenseseat.com/api'` | API base URL |
|
|
155
|
+
| `storagePrefix` | `string` | `'licenseseat_'` | Prefix for localStorage keys |
|
|
156
|
+
| `autoValidateInterval` | `number` | `3600000` | Auto-validation interval in ms (1 hour) |
|
|
157
|
+
| `autoInitialize` | `boolean` | `true` | Auto-initialize and validate cached license |
|
|
158
|
+
| `offlineFallbackEnabled` | `boolean` | `false` | Enable offline validation on network errors |
|
|
159
|
+
| `maxOfflineDays` | `number` | `0` | Maximum days license works offline (0 = disabled) |
|
|
160
|
+
| `maxRetries` | `number` | `3` | Max retry attempts for failed API calls |
|
|
161
|
+
| `retryDelay` | `number` | `1000` | Initial retry delay in ms (exponential backoff) |
|
|
162
|
+
| `debug` | `boolean` | `false` | Enable debug logging to console |
|
|
85
163
|
|
|
86
164
|
---
|
|
87
165
|
|
|
88
|
-
|
|
166
|
+
## API Reference
|
|
89
167
|
|
|
90
|
-
|
|
168
|
+
### Core Methods
|
|
91
169
|
|
|
92
|
-
|
|
170
|
+
#### `sdk.activate(licenseKey, options?)`
|
|
171
|
+
|
|
172
|
+
Activates a license key on this device.
|
|
173
|
+
|
|
174
|
+
```javascript
|
|
175
|
+
const result = await sdk.activate('LICENSE-KEY', {
|
|
176
|
+
deviceIdentifier: 'custom-device-id', // Optional: auto-generated if not provided
|
|
177
|
+
softwareReleaseDate: '2024-01-15', // Optional: for version-aware licensing
|
|
178
|
+
metadata: { version: '1.0.0' } // Optional: custom metadata
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
console.log(result);
|
|
182
|
+
// {
|
|
183
|
+
// license_key: 'LICENSE-KEY',
|
|
184
|
+
// device_identifier: 'web-abc123-xyz',
|
|
185
|
+
// activated_at: '2024-01-15T10:30:00Z',
|
|
186
|
+
// activation: { ... }
|
|
187
|
+
// }
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
#### `sdk.deactivate()`
|
|
191
|
+
|
|
192
|
+
Deactivates the current license and clears cached data.
|
|
93
193
|
|
|
94
|
-
|
|
194
|
+
```javascript
|
|
195
|
+
await sdk.deactivate();
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
#### `sdk.validateLicense(licenseKey, options?)`
|
|
199
|
+
|
|
200
|
+
Validates a license with the server.
|
|
201
|
+
|
|
202
|
+
```javascript
|
|
203
|
+
const result = await sdk.validateLicense('LICENSE-KEY', {
|
|
204
|
+
deviceIdentifier: 'device-id', // Optional
|
|
205
|
+
productSlug: 'my-product' // Optional
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
console.log(result);
|
|
209
|
+
// {
|
|
210
|
+
// valid: true,
|
|
211
|
+
// active_entitlements: [
|
|
212
|
+
// { key: 'pro', name: 'Pro Features', expires_at: null },
|
|
213
|
+
// { key: 'beta', name: 'Beta Access', expires_at: '2024-12-31T23:59:59Z' }
|
|
214
|
+
// ]
|
|
215
|
+
// }
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### Entitlement Methods
|
|
219
|
+
|
|
220
|
+
#### `sdk.hasEntitlement(key)`
|
|
95
221
|
|
|
96
|
-
|
|
222
|
+
Check if an entitlement is active. Returns a simple boolean.
|
|
223
|
+
|
|
224
|
+
```javascript
|
|
225
|
+
if (sdk.hasEntitlement('pro')) {
|
|
226
|
+
enableProFeatures();
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (sdk.hasEntitlement('beta')) {
|
|
230
|
+
showBetaUI();
|
|
231
|
+
}
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
#### `sdk.checkEntitlement(key)`
|
|
235
|
+
|
|
236
|
+
Check entitlement with detailed information.
|
|
237
|
+
|
|
238
|
+
```javascript
|
|
239
|
+
const result = sdk.checkEntitlement('pro');
|
|
240
|
+
|
|
241
|
+
if (result.active) {
|
|
242
|
+
console.log('Entitlement:', result.entitlement);
|
|
243
|
+
console.log('Expires:', result.entitlement.expires_at);
|
|
244
|
+
} else {
|
|
245
|
+
console.log('Reason:', result.reason);
|
|
246
|
+
// Possible reasons: 'no_license', 'not_found', 'expired'
|
|
247
|
+
}
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
### Status Methods
|
|
251
|
+
|
|
252
|
+
#### `sdk.getStatus()`
|
|
253
|
+
|
|
254
|
+
Get current license status.
|
|
255
|
+
|
|
256
|
+
```javascript
|
|
257
|
+
const status = sdk.getStatus();
|
|
258
|
+
|
|
259
|
+
// Possible status values:
|
|
260
|
+
// - 'inactive': No license activated
|
|
261
|
+
// - 'pending': License pending validation
|
|
262
|
+
// - 'active': License valid (online)
|
|
263
|
+
// - 'invalid': License invalid
|
|
264
|
+
// - 'offline-valid': License valid (offline verification)
|
|
265
|
+
// - 'offline-invalid': License invalid (offline verification)
|
|
266
|
+
|
|
267
|
+
console.log(status);
|
|
268
|
+
// {
|
|
269
|
+
// status: 'active',
|
|
270
|
+
// license: 'LICENSE-KEY',
|
|
271
|
+
// device: 'web-abc123-xyz',
|
|
272
|
+
// activated_at: '2024-01-15T10:30:00Z',
|
|
273
|
+
// last_validated: '2024-01-15T11:30:00Z',
|
|
274
|
+
// entitlements: [...]
|
|
275
|
+
// }
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
#### `sdk.testAuth()`
|
|
279
|
+
|
|
280
|
+
Test API authentication (useful for verifying API key).
|
|
281
|
+
|
|
282
|
+
```javascript
|
|
283
|
+
try {
|
|
284
|
+
const result = await sdk.testAuth();
|
|
285
|
+
console.log('Authenticated:', result.authenticated);
|
|
286
|
+
} catch (error) {
|
|
287
|
+
console.error('Auth failed:', error);
|
|
288
|
+
}
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
#### `sdk.reset()`
|
|
292
|
+
|
|
293
|
+
Clear all cached data and reset SDK state.
|
|
294
|
+
|
|
295
|
+
```javascript
|
|
296
|
+
sdk.reset();
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
#### `sdk.destroy()`
|
|
300
|
+
|
|
301
|
+
Destroy the SDK instance and release all resources. Call this when you no longer need the SDK to prevent memory leaks. After calling `destroy()`, the SDK instance should not be used.
|
|
302
|
+
|
|
303
|
+
```javascript
|
|
304
|
+
// When unmounting a component or closing an app
|
|
305
|
+
sdk.destroy();
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
#### `sdk.initialize()`
|
|
309
|
+
|
|
310
|
+
Manually initialize the SDK (only needed if `autoInitialize: false`).
|
|
311
|
+
|
|
312
|
+
```javascript
|
|
313
|
+
const sdk = new LicenseSeat({
|
|
314
|
+
apiKey: 'key',
|
|
315
|
+
autoInitialize: false // Don't auto-initialize
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
// Later, when ready:
|
|
319
|
+
sdk.initialize();
|
|
320
|
+
```
|
|
97
321
|
|
|
98
322
|
---
|
|
99
323
|
|
|
100
|
-
|
|
324
|
+
## Events
|
|
101
325
|
|
|
102
|
-
Subscribe to SDK lifecycle events
|
|
326
|
+
Subscribe to SDK lifecycle events for reactive UIs.
|
|
103
327
|
|
|
104
|
-
```
|
|
105
|
-
|
|
328
|
+
```javascript
|
|
329
|
+
// Subscribe
|
|
330
|
+
const unsubscribe = sdk.on('activation:success', (data) => {
|
|
106
331
|
console.log('License activated:', data);
|
|
107
332
|
});
|
|
108
333
|
|
|
109
|
-
|
|
110
|
-
|
|
334
|
+
// Unsubscribe
|
|
335
|
+
unsubscribe();
|
|
336
|
+
// or
|
|
337
|
+
sdk.off('activation:success', handler);
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
### Available Events
|
|
341
|
+
|
|
342
|
+
| Event | Description | Data |
|
|
343
|
+
|-------|-------------|------|
|
|
344
|
+
| **Lifecycle** | | |
|
|
345
|
+
| `license:loaded` | Cached license loaded on init | `CachedLicense` |
|
|
346
|
+
| `sdk:reset` | SDK was reset | โ |
|
|
347
|
+
| `sdk:destroyed` | SDK was destroyed | โ |
|
|
348
|
+
| `sdk:error` | General SDK error | `{ message, error? }` |
|
|
349
|
+
| **Activation** | | |
|
|
350
|
+
| `activation:start` | Activation started | `{ licenseKey, deviceId }` |
|
|
351
|
+
| `activation:success` | Activation succeeded | `CachedLicense` |
|
|
352
|
+
| `activation:error` | Activation failed | `{ licenseKey, error }` |
|
|
353
|
+
| **Deactivation** | | |
|
|
354
|
+
| `deactivation:start` | Deactivation started | `CachedLicense` |
|
|
355
|
+
| `deactivation:success` | Deactivation succeeded | `Object` |
|
|
356
|
+
| `deactivation:error` | Deactivation failed | `{ error, license }` |
|
|
357
|
+
| **Validation** | | |
|
|
358
|
+
| `validation:start` | Validation started | `{ licenseKey }` |
|
|
359
|
+
| `validation:success` | Online validation succeeded | `ValidationResult` |
|
|
360
|
+
| `validation:failed` | Validation failed (invalid license) | `ValidationResult` |
|
|
361
|
+
| `validation:error` | Validation error (network, etc.) | `{ licenseKey, error }` |
|
|
362
|
+
| `validation:offline-success` | Offline validation succeeded | `ValidationResult` |
|
|
363
|
+
| `validation:offline-failed` | Offline validation failed | `ValidationResult` |
|
|
364
|
+
| `validation:auth-failed` | Auth failed during validation | `{ licenseKey, error, cached }` |
|
|
365
|
+
| **Auto-Validation** | | |
|
|
366
|
+
| `autovalidation:cycle` | Auto-validation scheduled | `{ nextRunAt: Date }` |
|
|
367
|
+
| `autovalidation:stopped` | Auto-validation stopped | โ |
|
|
368
|
+
| **Network** | | |
|
|
369
|
+
| `network:online` | Network connectivity restored | โ |
|
|
370
|
+
| `network:offline` | Network connectivity lost | `{ error }` |
|
|
371
|
+
| **Offline License** | | |
|
|
372
|
+
| `offlineLicense:fetching` | Fetching offline license | `{ licenseKey }` |
|
|
373
|
+
| `offlineLicense:fetched` | Offline license fetched | `{ licenseKey, data }` |
|
|
374
|
+
| `offlineLicense:fetchError` | Offline license fetch failed | `{ licenseKey, error }` |
|
|
375
|
+
| `offlineLicense:ready` | Offline assets synced | `{ kid, exp_at }` |
|
|
376
|
+
| `offlineLicense:verified` | Offline signature verified | `{ payload }` |
|
|
377
|
+
| `offlineLicense:verificationFailed` | Offline signature invalid | `{ payload }` |
|
|
378
|
+
|
|
379
|
+
---
|
|
380
|
+
|
|
381
|
+
## Singleton Pattern
|
|
382
|
+
|
|
383
|
+
For applications that need a shared SDK instance:
|
|
384
|
+
|
|
385
|
+
```javascript
|
|
386
|
+
import { configure, getSharedInstance, resetSharedInstance } from '@licenseseat/js';
|
|
387
|
+
|
|
388
|
+
// Configure once at app startup
|
|
389
|
+
configure({ apiKey: 'your-key' });
|
|
390
|
+
|
|
391
|
+
// Use anywhere in your app
|
|
392
|
+
const sdk = getSharedInstance();
|
|
393
|
+
await sdk.activate('LICENSE-KEY');
|
|
394
|
+
|
|
395
|
+
// Reset if needed
|
|
396
|
+
resetSharedInstance();
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
---
|
|
400
|
+
|
|
401
|
+
## Offline Support
|
|
402
|
+
|
|
403
|
+
The SDK supports offline license validation using cryptographically signed offline licenses (Ed25519).
|
|
404
|
+
|
|
405
|
+
```javascript
|
|
406
|
+
const sdk = new LicenseSeat({
|
|
407
|
+
apiKey: 'your-key',
|
|
408
|
+
offlineFallbackEnabled: true, // Enable offline fallback
|
|
409
|
+
maxOfflineDays: 7 // Allow 7 days offline
|
|
111
410
|
});
|
|
411
|
+
|
|
412
|
+
// After activation, offline assets are automatically synced
|
|
413
|
+
await sdk.activate('LICENSE-KEY');
|
|
414
|
+
|
|
415
|
+
// Later, even offline, validation will work using cached data
|
|
416
|
+
const result = await sdk.validateLicense('LICENSE-KEY');
|
|
417
|
+
if (result.offline) {
|
|
418
|
+
console.log('Validated offline');
|
|
419
|
+
}
|
|
112
420
|
```
|
|
113
421
|
|
|
114
|
-
|
|
422
|
+
### How Offline Validation Works
|
|
115
423
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
* `validated`
|
|
424
|
+
1. On activation, the SDK fetches a signed offline license from the server
|
|
425
|
+
2. The offline license contains the license data + Ed25519 signature
|
|
426
|
+
3. When offline, the SDK verifies the signature locally
|
|
427
|
+
4. Clock tamper detection prevents users from bypassing expiration
|
|
121
428
|
|
|
122
429
|
---
|
|
123
430
|
|
|
124
|
-
##
|
|
431
|
+
## Error Handling
|
|
432
|
+
|
|
433
|
+
The SDK exports custom error classes for precise error handling:
|
|
434
|
+
|
|
435
|
+
```javascript
|
|
436
|
+
import LicenseSeat, {
|
|
437
|
+
APIError,
|
|
438
|
+
LicenseError,
|
|
439
|
+
ConfigurationError,
|
|
440
|
+
CryptoError
|
|
441
|
+
} from '@licenseseat/js';
|
|
442
|
+
|
|
443
|
+
try {
|
|
444
|
+
await sdk.activate('INVALID-KEY');
|
|
445
|
+
} catch (error) {
|
|
446
|
+
if (error instanceof APIError) {
|
|
447
|
+
console.log('HTTP Status:', error.status);
|
|
448
|
+
console.log('Response:', error.data);
|
|
449
|
+
} else if (error instanceof LicenseError) {
|
|
450
|
+
console.log('License error:', error.code);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
```
|
|
125
454
|
|
|
126
|
-
|
|
455
|
+
### Error Types
|
|
127
456
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
});
|
|
457
|
+
| Error | Description |
|
|
458
|
+
|-------|-------------|
|
|
459
|
+
| `APIError` | HTTP request failures (includes `status` and `data`) |
|
|
460
|
+
| `LicenseError` | License operation failures (includes `code`) |
|
|
461
|
+
| `ConfigurationError` | SDK misconfiguration |
|
|
462
|
+
| `CryptoError` | Cryptographic operation failures |
|
|
135
463
|
|
|
136
|
-
|
|
137
|
-
|
|
464
|
+
---
|
|
465
|
+
|
|
466
|
+
## Browser Support
|
|
467
|
+
|
|
468
|
+
- **Modern browsers**: Chrome 80+, Firefox 75+, Safari 14+, Edge 80+
|
|
469
|
+
- **Bundlers**: Vite, Webpack, Rollup, esbuild, Parcel
|
|
470
|
+
- **Node.js**: 18+ (requires polyfills for `localStorage`, `document`)
|
|
471
|
+
|
|
472
|
+
---
|
|
473
|
+
|
|
474
|
+
## Usage Guide
|
|
475
|
+
|
|
476
|
+
### For JavaScript Users
|
|
477
|
+
|
|
478
|
+
Simply import and use:
|
|
479
|
+
|
|
480
|
+
```javascript
|
|
481
|
+
import LicenseSeat from '@licenseseat/js';
|
|
482
|
+
|
|
483
|
+
const sdk = new LicenseSeat({ apiKey: 'your-key' });
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
### For TypeScript Users
|
|
487
|
+
|
|
488
|
+
The package includes TypeScript declarations (`.d.ts` files) automatically. No additional `@types/` package needed.
|
|
489
|
+
|
|
490
|
+
```typescript
|
|
491
|
+
import LicenseSeat from '@licenseseat/js';
|
|
492
|
+
|
|
493
|
+
// Types are automatically available
|
|
494
|
+
const sdk = new LicenseSeat({ apiKey: 'your-key' });
|
|
495
|
+
|
|
496
|
+
// Import specific types if needed
|
|
497
|
+
import type {
|
|
498
|
+
LicenseSeatConfig,
|
|
499
|
+
ValidationResult,
|
|
500
|
+
EntitlementCheckResult,
|
|
501
|
+
LicenseStatus,
|
|
502
|
+
Entitlement,
|
|
503
|
+
CachedLicense
|
|
504
|
+
} from '@licenseseat/js';
|
|
138
505
|
```
|
|
139
506
|
|
|
140
|
-
|
|
507
|
+
### For CDN/Browser Users
|
|
508
|
+
|
|
509
|
+
Use ES modules via CDN:
|
|
510
|
+
|
|
511
|
+
```html
|
|
512
|
+
<!DOCTYPE html>
|
|
513
|
+
<html>
|
|
514
|
+
<head>
|
|
515
|
+
<title>LicenseSeat Demo</title>
|
|
516
|
+
</head>
|
|
517
|
+
<body>
|
|
518
|
+
<script type="module">
|
|
519
|
+
import LicenseSeat from 'https://esm.sh/@licenseseat/js';
|
|
520
|
+
|
|
521
|
+
const sdk = new LicenseSeat({
|
|
522
|
+
apiKey: 'your-api-key',
|
|
523
|
+
debug: true
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
// Check for existing license
|
|
527
|
+
const status = sdk.getStatus();
|
|
528
|
+
if (status.status === 'active') {
|
|
529
|
+
console.log('Already licensed!');
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// Activate (example with user input)
|
|
533
|
+
document.getElementById('activate-btn').onclick = async () => {
|
|
534
|
+
const key = document.getElementById('license-key').value;
|
|
535
|
+
try {
|
|
536
|
+
await sdk.activate(key);
|
|
537
|
+
alert('License activated!');
|
|
538
|
+
} catch (e) {
|
|
539
|
+
alert('Activation failed: ' + e.message);
|
|
540
|
+
}
|
|
541
|
+
};
|
|
542
|
+
</script>
|
|
543
|
+
|
|
544
|
+
<input id="license-key" placeholder="Enter license key" />
|
|
545
|
+
<button id="activate-btn">Activate</button>
|
|
546
|
+
</body>
|
|
547
|
+
</html>
|
|
548
|
+
```
|
|
141
549
|
|
|
142
550
|
---
|
|
143
551
|
|
|
144
|
-
##
|
|
552
|
+
## Development
|
|
145
553
|
|
|
146
|
-
|
|
554
|
+
### Setup
|
|
147
555
|
|
|
148
556
|
```bash
|
|
149
557
|
git clone https://github.com/licenseseat/licenseseat-js.git
|
|
150
558
|
cd licenseseat-js
|
|
151
559
|
npm install
|
|
152
|
-
npm run build
|
|
153
560
|
```
|
|
154
561
|
|
|
155
|
-
|
|
562
|
+
### Scripts
|
|
563
|
+
|
|
564
|
+
| Command | Description |
|
|
565
|
+
|---------|-------------|
|
|
566
|
+
| `npm run build` | Build JS bundle + TypeScript declarations |
|
|
567
|
+
| `npm run build:js` | Build JavaScript bundle only |
|
|
568
|
+
| `npm run build:types` | Generate TypeScript declarations |
|
|
569
|
+
| `npm run build:iife` | Build global/IIFE bundle |
|
|
570
|
+
| `npm run dev` | Watch mode for development |
|
|
571
|
+
| `npm test` | Run tests |
|
|
572
|
+
| `npm run test:watch` | Run tests in watch mode |
|
|
573
|
+
| `npm run test:coverage` | Run tests with coverage report |
|
|
574
|
+
| `npm run typecheck` | Type-check without emitting |
|
|
575
|
+
|
|
576
|
+
### Project Structure
|
|
577
|
+
|
|
578
|
+
```
|
|
579
|
+
licenseseat-js/
|
|
580
|
+
โโโ src/
|
|
581
|
+
โ โโโ index.js # Entry point, exports
|
|
582
|
+
โ โโโ LicenseSeat.js # Main SDK class
|
|
583
|
+
โ โโโ cache.js # LicenseCache (localStorage)
|
|
584
|
+
โ โโโ errors.js # Error classes
|
|
585
|
+
โ โโโ types.js # JSDoc type definitions
|
|
586
|
+
โ โโโ utils.js # Utility functions
|
|
587
|
+
โโโ tests/
|
|
588
|
+
โ โโโ setup.js # Test setup
|
|
589
|
+
โ โโโ mocks/ # MSW handlers
|
|
590
|
+
โ โโโ LicenseSeat.test.js
|
|
591
|
+
โ โโโ utils.test.js
|
|
592
|
+
โโโ dist/ # Build output
|
|
593
|
+
โ โโโ index.js # ESM bundle
|
|
594
|
+
โ โโโ types/ # TypeScript declarations
|
|
595
|
+
โโโ package.json
|
|
596
|
+
โโโ tsconfig.json
|
|
597
|
+
โโโ vitest.config.js
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
---
|
|
601
|
+
|
|
602
|
+
## Publishing
|
|
603
|
+
|
|
604
|
+
### Publishing to npm
|
|
605
|
+
|
|
606
|
+
1. **Update version** in `package.json`:
|
|
607
|
+
```bash
|
|
608
|
+
npm version patch # or minor, major
|
|
609
|
+
```
|
|
610
|
+
|
|
611
|
+
2. **Build the package**:
|
|
612
|
+
```bash
|
|
613
|
+
npm run build
|
|
614
|
+
```
|
|
615
|
+
|
|
616
|
+
3. **Verify the build**:
|
|
617
|
+
```bash
|
|
618
|
+
# Check what will be published
|
|
619
|
+
npm pack --dry-run
|
|
620
|
+
|
|
621
|
+
# Verify TypeScript types
|
|
622
|
+
ls dist/types/
|
|
623
|
+
```
|
|
624
|
+
|
|
625
|
+
4. **Publish**:
|
|
626
|
+
```bash
|
|
627
|
+
# Login if needed
|
|
628
|
+
npm login
|
|
629
|
+
|
|
630
|
+
# Publish (public package)
|
|
631
|
+
npm publish --access public
|
|
632
|
+
```
|
|
633
|
+
|
|
634
|
+
### What Gets Published
|
|
635
|
+
|
|
636
|
+
The `files` field in `package.json` controls what's included:
|
|
637
|
+
|
|
638
|
+
```json
|
|
639
|
+
{
|
|
640
|
+
"files": ["dist/", "src/"]
|
|
641
|
+
}
|
|
642
|
+
```
|
|
643
|
+
|
|
644
|
+
Users receive:
|
|
645
|
+
- `dist/index.js` โ ESM bundle (JavaScript)
|
|
646
|
+
- `dist/types/*.d.ts` โ TypeScript declarations
|
|
647
|
+
- `src/*.js` โ Source files (for debugging/reference)
|
|
648
|
+
|
|
649
|
+
### Package Exports
|
|
650
|
+
|
|
651
|
+
```json
|
|
652
|
+
{
|
|
653
|
+
"main": "dist/index.js",
|
|
654
|
+
"module": "dist/index.js",
|
|
655
|
+
"types": "dist/types/index.d.ts",
|
|
656
|
+
"exports": {
|
|
657
|
+
".": {
|
|
658
|
+
"import": "./dist/index.js",
|
|
659
|
+
"types": "./dist/types/index.d.ts"
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
```
|
|
664
|
+
|
|
665
|
+
This ensures:
|
|
666
|
+
- JavaScript users get `dist/index.js`
|
|
667
|
+
- TypeScript users get type definitions from `dist/types/index.d.ts`
|
|
668
|
+
- Both ESM `import` and bundlers work correctly
|
|
669
|
+
|
|
670
|
+
### CDN Distribution
|
|
671
|
+
|
|
672
|
+
Once published to npm, the package is automatically available on CDNs:
|
|
673
|
+
|
|
674
|
+
| CDN | URL |
|
|
675
|
+
|-----|-----|
|
|
676
|
+
| **esm.sh** | `https://esm.sh/@licenseseat/js` |
|
|
677
|
+
| **unpkg** | `https://unpkg.com/@licenseseat/js/dist/index.js` |
|
|
678
|
+
| **jsDelivr** | `https://cdn.jsdelivr.net/npm/@licenseseat/js/dist/index.js` |
|
|
679
|
+
| **Skypack** | `https://cdn.skypack.dev/@licenseseat/js` |
|
|
680
|
+
|
|
681
|
+
**Version pinning** (recommended for production):
|
|
682
|
+
```html
|
|
683
|
+
<script type="module">
|
|
684
|
+
import LicenseSeat from 'https://esm.sh/@licenseseat/js@0.2.0';
|
|
685
|
+
</script>
|
|
686
|
+
```
|
|
687
|
+
|
|
688
|
+
### Self-Hosting
|
|
689
|
+
|
|
690
|
+
To host the SDK yourself:
|
|
691
|
+
|
|
692
|
+
1. Build the package:
|
|
693
|
+
```bash
|
|
694
|
+
npm run build
|
|
695
|
+
```
|
|
696
|
+
|
|
697
|
+
2. Copy `dist/index.js` to your CDN/server
|
|
698
|
+
|
|
699
|
+
3. Serve with correct MIME type (`application/javascript`) and CORS headers
|
|
700
|
+
|
|
701
|
+
### Building an IIFE Bundle (Legacy Browsers)
|
|
702
|
+
|
|
703
|
+
For a global `LicenseSeat` variable (non-module script tags):
|
|
704
|
+
|
|
705
|
+
```bash
|
|
706
|
+
npm run build:iife
|
|
707
|
+
```
|
|
708
|
+
|
|
709
|
+
This creates `dist/index.global.js`:
|
|
710
|
+
|
|
711
|
+
```html
|
|
712
|
+
<script src="/path/to/index.global.js"></script>
|
|
713
|
+
<script>
|
|
714
|
+
const sdk = new LicenseSeat({ apiKey: 'your-key' });
|
|
715
|
+
</script>
|
|
716
|
+
```
|
|
717
|
+
|
|
718
|
+
---
|
|
719
|
+
|
|
720
|
+
## Versioning
|
|
721
|
+
|
|
722
|
+
This project follows [Semantic Versioning](https://semver.org/):
|
|
723
|
+
|
|
724
|
+
- **MAJOR** (1.0.0 โ 2.0.0): Breaking changes
|
|
725
|
+
- **MINOR** (1.0.0 โ 1.1.0): New features (backward compatible)
|
|
726
|
+
- **PATCH** (1.0.0 โ 1.0.1): Bug fixes
|
|
727
|
+
|
|
728
|
+
---
|
|
729
|
+
|
|
730
|
+
## Migration from v0.1.x
|
|
731
|
+
|
|
732
|
+
### Breaking Changes in v0.2.0
|
|
733
|
+
|
|
734
|
+
| Change | Before | After | Migration |
|
|
735
|
+
|--------|--------|-------|-----------|
|
|
736
|
+
| `apiBaseUrl` default | `/api` | `https://licenseseat.com/api` | Set `apiBaseUrl` explicitly if using a relative URL |
|
|
737
|
+
| `offlineFallbackEnabled` default | `true` | `false` | Set `offlineFallbackEnabled: true` if you need offline fallback |
|
|
738
|
+
|
|
739
|
+
### New Features in v0.2.0
|
|
740
|
+
|
|
741
|
+
- `hasEntitlement(key)` method for simple boolean checks
|
|
742
|
+
- `autoInitialize` config option for lazy initialization
|
|
743
|
+
- Full TypeScript support with auto-generated `.d.ts` files
|
|
744
|
+
- Singleton pattern with `configure()` and `getSharedInstance()`
|
|
745
|
+
- New error classes: `LicenseError`, `ConfigurationError`, `CryptoError`
|
|
746
|
+
|
|
747
|
+
---
|
|
748
|
+
|
|
749
|
+
## License
|
|
750
|
+
|
|
751
|
+
MIT License โ see [LICENSE](LICENSE) for details.
|
|
156
752
|
|
|
157
753
|
---
|
|
158
754
|
|
|
159
|
-
##
|
|
755
|
+
## Links
|
|
160
756
|
|
|
161
|
-
|
|
162
|
-
|
|
757
|
+
- [LicenseSeat Website](https://licenseseat.com)
|
|
758
|
+
- [Documentation](https://licenseseat.com/docs)
|
|
759
|
+
- [API Reference](https://licenseseat.com/docs/api)
|
|
760
|
+
- [GitHub Repository](https://github.com/licenseseat/licenseseat-js)
|
|
761
|
+
- [npm Package](https://www.npmjs.com/package/@licenseseat/js)
|
|
762
|
+
- [Report Issues](https://github.com/licenseseat/licenseseat-js/issues)
|
|
163
763
|
|
|
164
764
|
---
|
|
165
765
|
|
|
166
|
-
##
|
|
766
|
+
## Support
|
|
167
767
|
|
|
168
|
-
|
|
768
|
+
- **Email**: support@licenseseat.com
|
|
769
|
+
- **GitHub Issues**: [Report a bug](https://github.com/licenseseat/licenseseat-js/issues/new)
|