@licenseseat/js 0.2.2 → 0.3.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 +381 -72
- package/dist/index.global.js +2339 -0
- package/dist/index.js +196 -146
- package/dist/types/LicenseSeat.d.ts +33 -18
- package/dist/types/LicenseSeat.d.ts.map +1 -1
- package/dist/types/cache.d.ts +10 -10
- package/dist/types/cache.d.ts.map +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/types.d.ts +291 -63
- package/dist/types/types.d.ts.map +1 -1
- package/dist/types/utils.d.ts +1 -1
- package/dist/types/utils.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/LicenseSeat.js +203 -146
- package/src/cache.js +16 -18
- package/src/index.js +2 -1
- package/src/types.js +126 -34
- package/src/utils.js +3 -2
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
[](https://www.npmjs.com/package/@licenseseat/js)
|
|
5
5
|
[](https://opensource.org/licenses/MIT)
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
The official JavaScript/TypeScript SDK for [LicenseSeat](https://licenseseat.com) – the simple, secure licensing platform for apps, games, and plugins.
|
|
8
8
|
|
|
9
9
|
---
|
|
10
10
|
|
|
@@ -43,7 +43,10 @@ pnpm add @licenseseat/js
|
|
|
43
43
|
<script type="module">
|
|
44
44
|
import LicenseSeat from 'https://esm.sh/@licenseseat/js';
|
|
45
45
|
|
|
46
|
-
const sdk = new LicenseSeat({
|
|
46
|
+
const sdk = new LicenseSeat({
|
|
47
|
+
apiKey: 'your-api-key',
|
|
48
|
+
productSlug: 'your-product'
|
|
49
|
+
});
|
|
47
50
|
</script>
|
|
48
51
|
|
|
49
52
|
<!-- ESM via unpkg -->
|
|
@@ -69,6 +72,7 @@ import LicenseSeat from '@licenseseat/js';
|
|
|
69
72
|
// Create SDK instance
|
|
70
73
|
const sdk = new LicenseSeat({
|
|
71
74
|
apiKey: 'your-api-key',
|
|
75
|
+
productSlug: 'your-product', // Required: Your product slug
|
|
72
76
|
debug: true
|
|
73
77
|
});
|
|
74
78
|
|
|
@@ -98,6 +102,7 @@ import LicenseSeat, {
|
|
|
98
102
|
|
|
99
103
|
const config: LicenseSeatConfig = {
|
|
100
104
|
apiKey: 'your-api-key',
|
|
105
|
+
productSlug: 'your-product',
|
|
101
106
|
debug: true
|
|
102
107
|
};
|
|
103
108
|
|
|
@@ -117,11 +122,14 @@ TypeScript users get full type support automatically – the package includes ge
|
|
|
117
122
|
|
|
118
123
|
```javascript
|
|
119
124
|
const sdk = new LicenseSeat({
|
|
125
|
+
// Required
|
|
126
|
+
productSlug: 'your-product', // Your product slug from LicenseSeat dashboard
|
|
127
|
+
|
|
120
128
|
// Required for authenticated operations
|
|
121
129
|
apiKey: 'your-api-key',
|
|
122
130
|
|
|
123
131
|
// API Configuration
|
|
124
|
-
apiBaseUrl: 'https://licenseseat.com/api', // Default
|
|
132
|
+
apiBaseUrl: 'https://licenseseat.com/api/v1', // Default
|
|
125
133
|
|
|
126
134
|
// Storage
|
|
127
135
|
storagePrefix: 'licenseseat_', // localStorage key prefix
|
|
@@ -148,18 +156,19 @@ const sdk = new LicenseSeat({
|
|
|
148
156
|
|
|
149
157
|
### Configuration Options
|
|
150
158
|
|
|
151
|
-
| Option | Type | Default
|
|
152
|
-
| ------------------------ | --------- |
|
|
153
|
-
| `
|
|
154
|
-
| `
|
|
155
|
-
| `
|
|
156
|
-
| `
|
|
157
|
-
| `
|
|
158
|
-
| `
|
|
159
|
-
| `
|
|
160
|
-
| `
|
|
161
|
-
| `
|
|
162
|
-
| `
|
|
159
|
+
| Option | Type | Default | Description |
|
|
160
|
+
| ------------------------ | --------- | ---------------------------------- | --------------------------------------------------------- |
|
|
161
|
+
| `productSlug` | `string` | – | **Required.** Your product slug from the dashboard |
|
|
162
|
+
| `apiKey` | `string` | `null` | API key for authentication (required for most operations) |
|
|
163
|
+
| `apiBaseUrl` | `string` | `'https://licenseseat.com/api/v1'` | API base URL |
|
|
164
|
+
| `storagePrefix` | `string` | `'licenseseat_'` | Prefix for localStorage keys |
|
|
165
|
+
| `autoValidateInterval` | `number` | `3600000` | Auto-validation interval in ms (1 hour) |
|
|
166
|
+
| `autoInitialize` | `boolean` | `true` | Auto-initialize and validate cached license |
|
|
167
|
+
| `offlineFallbackEnabled` | `boolean` | `false` | Enable offline validation on network errors |
|
|
168
|
+
| `maxOfflineDays` | `number` | `0` | Maximum days license works offline (0 = disabled) |
|
|
169
|
+
| `maxRetries` | `number` | `3` | Max retry attempts for failed API calls |
|
|
170
|
+
| `retryDelay` | `number` | `1000` | Initial retry delay in ms (exponential backoff) |
|
|
171
|
+
| `debug` | `boolean` | `false` | Enable debug logging to console |
|
|
163
172
|
|
|
164
173
|
---
|
|
165
174
|
|
|
@@ -173,17 +182,24 @@ Activates a license key on this device.
|
|
|
173
182
|
|
|
174
183
|
```javascript
|
|
175
184
|
const result = await sdk.activate('LICENSE-KEY', {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
metadata: { version: '1.0.0' }
|
|
185
|
+
deviceId: 'custom-device-id', // Optional: auto-generated if not provided
|
|
186
|
+
deviceName: "John's MacBook Pro", // Optional: human-readable device name
|
|
187
|
+
metadata: { version: '1.0.0' } // Optional: custom metadata
|
|
179
188
|
});
|
|
180
189
|
|
|
181
190
|
console.log(result);
|
|
182
191
|
// {
|
|
183
192
|
// license_key: 'LICENSE-KEY',
|
|
184
|
-
//
|
|
193
|
+
// device_id: 'web-abc123',
|
|
185
194
|
// activated_at: '2024-01-15T10:30:00Z',
|
|
186
|
-
// activation: {
|
|
195
|
+
// activation: {
|
|
196
|
+
// object: 'activation',
|
|
197
|
+
// id: 123,
|
|
198
|
+
// device_id: 'web-abc123',
|
|
199
|
+
// license_key: 'LICENSE-KEY',
|
|
200
|
+
// activated_at: '2024-01-15T10:30:00Z',
|
|
201
|
+
// license: { ... }
|
|
202
|
+
// }
|
|
187
203
|
// }
|
|
188
204
|
```
|
|
189
205
|
|
|
@@ -192,7 +208,13 @@ console.log(result);
|
|
|
192
208
|
Deactivates the current license and clears cached data.
|
|
193
209
|
|
|
194
210
|
```javascript
|
|
195
|
-
await sdk.deactivate();
|
|
211
|
+
const result = await sdk.deactivate();
|
|
212
|
+
console.log(result);
|
|
213
|
+
// {
|
|
214
|
+
// object: 'deactivation',
|
|
215
|
+
// activation_id: 123,
|
|
216
|
+
// deactivated_at: '2024-01-15T12:00:00Z'
|
|
217
|
+
// }
|
|
196
218
|
```
|
|
197
219
|
|
|
198
220
|
#### `sdk.validateLicense(licenseKey, options?)`
|
|
@@ -201,25 +223,36 @@ Validates a license with the server.
|
|
|
201
223
|
|
|
202
224
|
```javascript
|
|
203
225
|
const result = await sdk.validateLicense('LICENSE-KEY', {
|
|
204
|
-
|
|
205
|
-
productSlug: 'my-product' // Optional
|
|
226
|
+
deviceId: 'device-id' // Optional: required for hardware_locked mode
|
|
206
227
|
});
|
|
207
228
|
|
|
208
229
|
console.log(result);
|
|
209
230
|
// {
|
|
210
231
|
// valid: true,
|
|
211
|
-
//
|
|
212
|
-
//
|
|
213
|
-
//
|
|
214
|
-
//
|
|
232
|
+
// license: {
|
|
233
|
+
// key: 'LICENSE-KEY',
|
|
234
|
+
// status: 'active',
|
|
235
|
+
// mode: 'hardware_locked',
|
|
236
|
+
// plan_key: 'pro',
|
|
237
|
+
// active_seats: 1,
|
|
238
|
+
// seat_limit: 3,
|
|
239
|
+
// active_entitlements: [
|
|
240
|
+
// { key: 'pro', expires_at: null, metadata: null },
|
|
241
|
+
// { key: 'beta', expires_at: '2024-12-31T23:59:59Z', metadata: null }
|
|
242
|
+
// ],
|
|
243
|
+
// product: { slug: 'your-product', name: 'Your Product' }
|
|
244
|
+
// },
|
|
245
|
+
// active_entitlements: [...]
|
|
215
246
|
// }
|
|
216
247
|
```
|
|
217
248
|
|
|
218
249
|
### Entitlement Methods
|
|
219
250
|
|
|
251
|
+
> **Note:** Entitlements are optional. A license may have zero entitlements if the associated plan has no entitlements configured. The `active_entitlements` array may be empty or the field may be undefined/null.
|
|
252
|
+
|
|
220
253
|
#### `sdk.hasEntitlement(key)`
|
|
221
254
|
|
|
222
|
-
Check if an entitlement is active. Returns a simple boolean.
|
|
255
|
+
Check if an entitlement is active. Returns a simple boolean. Returns `false` if no entitlements exist.
|
|
223
256
|
|
|
224
257
|
```javascript
|
|
225
258
|
if (sdk.hasEntitlement('pro')) {
|
|
@@ -268,7 +301,7 @@ console.log(status);
|
|
|
268
301
|
// {
|
|
269
302
|
// status: 'active',
|
|
270
303
|
// license: 'LICENSE-KEY',
|
|
271
|
-
// device: 'web-abc123
|
|
304
|
+
// device: 'web-abc123',
|
|
272
305
|
// activated_at: '2024-01-15T10:30:00Z',
|
|
273
306
|
// last_validated: '2024-01-15T11:30:00Z',
|
|
274
307
|
// entitlements: [...]
|
|
@@ -277,17 +310,21 @@ console.log(status);
|
|
|
277
310
|
|
|
278
311
|
#### `sdk.testAuth()`
|
|
279
312
|
|
|
280
|
-
Test API
|
|
313
|
+
Test API connectivity by calling the `/health` endpoint. Returns health status and API version.
|
|
281
314
|
|
|
282
315
|
```javascript
|
|
283
316
|
try {
|
|
284
317
|
const result = await sdk.testAuth();
|
|
285
|
-
console.log('Authenticated:', result.authenticated);
|
|
318
|
+
console.log('Authenticated:', result.authenticated); // Always true if request succeeds
|
|
319
|
+
console.log('Healthy:', result.healthy); // API health status
|
|
320
|
+
console.log('API Version:', result.api_version); // e.g., '1.0.0'
|
|
286
321
|
} catch (error) {
|
|
287
|
-
console.error('
|
|
322
|
+
console.error('Connection failed:', error);
|
|
288
323
|
}
|
|
289
324
|
```
|
|
290
325
|
|
|
326
|
+
> **Note:** This method tests API connectivity, not API key validity. A successful response means the API is reachable. Authentication errors will surface when calling protected endpoints like `activate()` or `validateLicense()`.
|
|
327
|
+
|
|
291
328
|
#### `sdk.reset()`
|
|
292
329
|
|
|
293
330
|
Clear all cached data and reset SDK state.
|
|
@@ -312,6 +349,7 @@ Manually initialize the SDK (only needed if `autoInitialize: false`).
|
|
|
312
349
|
```javascript
|
|
313
350
|
const sdk = new LicenseSeat({
|
|
314
351
|
apiKey: 'key',
|
|
352
|
+
productSlug: 'your-product',
|
|
315
353
|
autoInitialize: false // Don't auto-initialize
|
|
316
354
|
});
|
|
317
355
|
|
|
@@ -352,7 +390,7 @@ sdk.off('activation:success', handler);
|
|
|
352
390
|
| `activation:error` | Activation failed | `{ licenseKey, error }` |
|
|
353
391
|
| **Deactivation** | | |
|
|
354
392
|
| `deactivation:start` | Deactivation started | `CachedLicense` |
|
|
355
|
-
| `deactivation:success` | Deactivation succeeded | `
|
|
393
|
+
| `deactivation:success` | Deactivation succeeded | `DeactivationResponse` |
|
|
356
394
|
| `deactivation:error` | Deactivation failed | `{ error, license }` |
|
|
357
395
|
| **Validation** | | |
|
|
358
396
|
| `validation:start` | Validation started | `{ licenseKey }` |
|
|
@@ -368,13 +406,13 @@ sdk.off('activation:success', handler);
|
|
|
368
406
|
| **Network** | | |
|
|
369
407
|
| `network:online` | Network connectivity restored | – |
|
|
370
408
|
| `network:offline` | Network connectivity lost | `{ error }` |
|
|
371
|
-
| **Offline
|
|
372
|
-
| `
|
|
373
|
-
| `
|
|
374
|
-
| `
|
|
375
|
-
| `
|
|
376
|
-
| `
|
|
377
|
-
| `
|
|
409
|
+
| **Offline Token** | | |
|
|
410
|
+
| `offlineToken:fetching` | Fetching offline token | `{ licenseKey }` |
|
|
411
|
+
| `offlineToken:fetched` | Offline token fetched | `{ licenseKey, data }` |
|
|
412
|
+
| `offlineToken:fetchError` | Offline token fetch failed | `{ licenseKey, error }` |
|
|
413
|
+
| `offlineToken:ready` | Offline assets synced | `{ kid, exp_at }` |
|
|
414
|
+
| `offlineToken:verified` | Offline signature verified | `{ payload }` |
|
|
415
|
+
| `offlineToken:verificationFailed` | Offline signature invalid | `{ payload }` |
|
|
378
416
|
|
|
379
417
|
---
|
|
380
418
|
|
|
@@ -386,7 +424,10 @@ For applications that need a shared SDK instance:
|
|
|
386
424
|
import { configure, getSharedInstance, resetSharedInstance } from '@licenseseat/js';
|
|
387
425
|
|
|
388
426
|
// Configure once at app startup
|
|
389
|
-
configure({
|
|
427
|
+
configure({
|
|
428
|
+
apiKey: 'your-key',
|
|
429
|
+
productSlug: 'your-product'
|
|
430
|
+
});
|
|
390
431
|
|
|
391
432
|
// Use anywhere in your app
|
|
392
433
|
const sdk = getSharedInstance();
|
|
@@ -400,11 +441,12 @@ resetSharedInstance();
|
|
|
400
441
|
|
|
401
442
|
## Offline Support
|
|
402
443
|
|
|
403
|
-
The SDK supports offline license validation using cryptographically signed offline
|
|
444
|
+
The SDK supports offline license validation using cryptographically signed offline tokens (Ed25519).
|
|
404
445
|
|
|
405
446
|
```javascript
|
|
406
447
|
const sdk = new LicenseSeat({
|
|
407
448
|
apiKey: 'your-key',
|
|
449
|
+
productSlug: 'your-product',
|
|
408
450
|
offlineFallbackEnabled: true, // Enable offline fallback
|
|
409
451
|
maxOfflineDays: 7 // Allow 7 days offline
|
|
410
452
|
});
|
|
@@ -421,11 +463,109 @@ if (result.offline) {
|
|
|
421
463
|
|
|
422
464
|
### How Offline Validation Works
|
|
423
465
|
|
|
424
|
-
1. On activation, the SDK fetches a signed offline
|
|
425
|
-
2. The offline
|
|
466
|
+
1. On activation, the SDK fetches a signed offline token from the server
|
|
467
|
+
2. The offline token contains:
|
|
468
|
+
- License data (key, plan, entitlements, expiration)
|
|
469
|
+
- Ed25519 signature
|
|
470
|
+
- Canonical JSON for verification
|
|
426
471
|
3. When offline, the SDK verifies the signature locally
|
|
427
472
|
4. Clock tamper detection prevents users from bypassing expiration
|
|
428
473
|
|
|
474
|
+
### Offline Methods
|
|
475
|
+
|
|
476
|
+
#### `sdk.syncOfflineAssets()`
|
|
477
|
+
|
|
478
|
+
Fetches the offline token and signing key from the server. Uses the currently cached license. Call this after activation to prepare for offline usage.
|
|
479
|
+
|
|
480
|
+
```javascript
|
|
481
|
+
// First activate (caches the license)
|
|
482
|
+
await sdk.activate('LICENSE-KEY');
|
|
483
|
+
|
|
484
|
+
// Then sync offline assets (uses cached license)
|
|
485
|
+
const assets = await sdk.syncOfflineAssets();
|
|
486
|
+
console.log('Offline token key ID:', assets.kid);
|
|
487
|
+
console.log('Expires at:', assets.exp_at);
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
#### `sdk.getOfflineToken()`
|
|
491
|
+
|
|
492
|
+
Fetches a signed offline token for the currently cached license. Returns the token structure containing the license data and Ed25519 signature.
|
|
493
|
+
|
|
494
|
+
```javascript
|
|
495
|
+
// Must have an active license cached first
|
|
496
|
+
const token = await sdk.getOfflineToken();
|
|
497
|
+
console.log(token);
|
|
498
|
+
// {
|
|
499
|
+
// object: 'offline_token',
|
|
500
|
+
// token: { license_key, product_slug, plan_key, ... },
|
|
501
|
+
// signature: { algorithm: 'Ed25519', key_id, value },
|
|
502
|
+
// canonical: '...'
|
|
503
|
+
// }
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
#### `sdk.getSigningKey(keyId)`
|
|
507
|
+
|
|
508
|
+
Fetches the Ed25519 public key used for verifying offline token signatures.
|
|
509
|
+
|
|
510
|
+
```javascript
|
|
511
|
+
const signingKey = await sdk.getSigningKey('key-id-001');
|
|
512
|
+
console.log(signingKey);
|
|
513
|
+
// {
|
|
514
|
+
// object: 'signing_key',
|
|
515
|
+
// kid: 'key-id-001',
|
|
516
|
+
// public_key: 'base64-encoded-public-key',
|
|
517
|
+
// algorithm: 'Ed25519',
|
|
518
|
+
// created_at: '2024-01-01T00:00:00Z'
|
|
519
|
+
// }
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
#### `sdk.verifyOfflineToken(token, publicKeyB64)`
|
|
523
|
+
|
|
524
|
+
Verifies an offline token's Ed25519 signature locally. **Both parameters are required.**
|
|
525
|
+
|
|
526
|
+
```javascript
|
|
527
|
+
// Fetch the token and signing key first
|
|
528
|
+
const token = await sdk.getOfflineToken();
|
|
529
|
+
const signingKey = await sdk.getSigningKey(token.signature.key_id);
|
|
530
|
+
|
|
531
|
+
// Verify the signature
|
|
532
|
+
const isValid = await sdk.verifyOfflineToken(token, signingKey.public_key);
|
|
533
|
+
console.log('Signature valid:', isValid);
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
> **Important:** The `verifyOfflineToken()` method requires you to pass both the token and the public key. Fetch the signing key using `getSigningKey()` with the `key_id` from the token's signature.
|
|
537
|
+
|
|
538
|
+
### Offline Token Structure
|
|
539
|
+
|
|
540
|
+
```javascript
|
|
541
|
+
{
|
|
542
|
+
object: 'offline_token',
|
|
543
|
+
token: {
|
|
544
|
+
schema_version: 1,
|
|
545
|
+
license_key: 'LICENSE-KEY',
|
|
546
|
+
product_slug: 'your-product',
|
|
547
|
+
plan_key: 'pro',
|
|
548
|
+
mode: 'hardware_locked',
|
|
549
|
+
device_id: 'web-abc123',
|
|
550
|
+
iat: 1704067200, // Issued at (Unix timestamp)
|
|
551
|
+
exp: 1706659200, // Expires at (Unix timestamp)
|
|
552
|
+
nbf: 1704067200, // Not before (Unix timestamp)
|
|
553
|
+
license_expires_at: null,
|
|
554
|
+
kid: 'key-id-001',
|
|
555
|
+
entitlements: [
|
|
556
|
+
{ key: 'pro', expires_at: null }
|
|
557
|
+
],
|
|
558
|
+
metadata: {}
|
|
559
|
+
},
|
|
560
|
+
signature: {
|
|
561
|
+
algorithm: 'Ed25519',
|
|
562
|
+
key_id: 'key-id-001',
|
|
563
|
+
value: 'base64url-encoded-signature'
|
|
564
|
+
},
|
|
565
|
+
canonical: '{"entitlements":[...],"exp":...}'
|
|
566
|
+
}
|
|
567
|
+
```
|
|
568
|
+
|
|
429
569
|
---
|
|
430
570
|
|
|
431
571
|
## Error Handling
|
|
@@ -445,9 +585,12 @@ try {
|
|
|
445
585
|
} catch (error) {
|
|
446
586
|
if (error instanceof APIError) {
|
|
447
587
|
console.log('HTTP Status:', error.status);
|
|
448
|
-
console.log('
|
|
588
|
+
console.log('Error Code:', error.data?.error?.code);
|
|
589
|
+
console.log('Error Message:', error.data?.error?.message);
|
|
449
590
|
} else if (error instanceof LicenseError) {
|
|
450
591
|
console.log('License error:', error.code);
|
|
592
|
+
} else if (error instanceof ConfigurationError) {
|
|
593
|
+
console.log('Config error:', error.message);
|
|
451
594
|
}
|
|
452
595
|
}
|
|
453
596
|
```
|
|
@@ -458,16 +601,72 @@ try {
|
|
|
458
601
|
| -------------------- | ---------------------------------------------------- |
|
|
459
602
|
| `APIError` | HTTP request failures (includes `status` and `data`) |
|
|
460
603
|
| `LicenseError` | License operation failures (includes `code`) |
|
|
461
|
-
| `ConfigurationError` | SDK misconfiguration
|
|
604
|
+
| `ConfigurationError` | SDK misconfiguration (e.g., missing `productSlug`) |
|
|
462
605
|
| `CryptoError` | Cryptographic operation failures |
|
|
463
606
|
|
|
607
|
+
### API Error Format
|
|
608
|
+
|
|
609
|
+
API errors follow this structure:
|
|
610
|
+
|
|
611
|
+
```javascript
|
|
612
|
+
{
|
|
613
|
+
error: {
|
|
614
|
+
code: 'license_not_found', // Machine-readable error code
|
|
615
|
+
message: 'License not found.', // Human-readable message
|
|
616
|
+
details: { ... } // Optional additional details
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
```
|
|
620
|
+
|
|
621
|
+
Common error codes:
|
|
622
|
+
- `unauthorized` - Invalid or missing API key
|
|
623
|
+
- `license_not_found` - License key doesn't exist
|
|
624
|
+
- `license_expired` - License has expired
|
|
625
|
+
- `license_suspended` - License is suspended
|
|
626
|
+
- `license_revoked` - License has been revoked
|
|
627
|
+
- `seat_limit_reached` - No more seats available
|
|
628
|
+
- `device_already_activated` - Device is already activated
|
|
629
|
+
- `activation_not_found` - Activation doesn't exist (for deactivation)
|
|
630
|
+
|
|
464
631
|
---
|
|
465
632
|
|
|
466
633
|
## Browser Support
|
|
467
634
|
|
|
468
635
|
- **Modern browsers**: Chrome 80+, Firefox 75+, Safari 14+, Edge 80+
|
|
469
636
|
- **Bundlers**: Vite, Webpack, Rollup, esbuild, Parcel
|
|
470
|
-
- **Node.js**: 18+ (requires polyfills
|
|
637
|
+
- **Node.js**: 18+ (requires polyfills - see below)
|
|
638
|
+
|
|
639
|
+
### Node.js Usage
|
|
640
|
+
|
|
641
|
+
The SDK is designed for browsers but works in Node.js with polyfills. Add these before importing the SDK:
|
|
642
|
+
|
|
643
|
+
```javascript
|
|
644
|
+
// Required polyfills for Node.js
|
|
645
|
+
const storage = {};
|
|
646
|
+
globalThis.localStorage = {
|
|
647
|
+
getItem(key) { return Object.prototype.hasOwnProperty.call(storage, key) ? storage[key] : null; },
|
|
648
|
+
setItem(key, value) { storage[key] = String(value); },
|
|
649
|
+
removeItem(key) { delete storage[key]; },
|
|
650
|
+
clear() { for (const key in storage) delete storage[key]; },
|
|
651
|
+
};
|
|
652
|
+
|
|
653
|
+
// Override Object.keys to support localStorage iteration (used by cache.getAllKeys())
|
|
654
|
+
const originalKeys = Object.keys;
|
|
655
|
+
Object.keys = function(obj) {
|
|
656
|
+
if (obj === globalThis.localStorage) return originalKeys(storage);
|
|
657
|
+
return originalKeys(obj);
|
|
658
|
+
};
|
|
659
|
+
|
|
660
|
+
// Device fingerprinting polyfills (provides stable fallback values)
|
|
661
|
+
globalThis.document = { createElement: () => ({ getContext: () => null }), querySelector: () => null };
|
|
662
|
+
globalThis.window = { navigator: {}, screen: {} };
|
|
663
|
+
globalThis.navigator = { userAgent: 'Node.js', language: 'en', hardwareConcurrency: 4 };
|
|
664
|
+
|
|
665
|
+
// Now import the SDK
|
|
666
|
+
const { default: LicenseSeat } = await import('@licenseseat/js');
|
|
667
|
+
```
|
|
668
|
+
|
|
669
|
+
> **Note:** In Node.js, device fingerprinting will use fallback values since browser APIs aren't available. For consistent device identification across restarts, pass an explicit `deviceId` to `activate()`.
|
|
471
670
|
|
|
472
671
|
---
|
|
473
672
|
|
|
@@ -480,7 +679,10 @@ Simply import and use:
|
|
|
480
679
|
```javascript
|
|
481
680
|
import LicenseSeat from '@licenseseat/js';
|
|
482
681
|
|
|
483
|
-
const sdk = new LicenseSeat({
|
|
682
|
+
const sdk = new LicenseSeat({
|
|
683
|
+
apiKey: 'your-key',
|
|
684
|
+
productSlug: 'your-product'
|
|
685
|
+
});
|
|
484
686
|
```
|
|
485
687
|
|
|
486
688
|
### For TypeScript Users
|
|
@@ -491,7 +693,10 @@ The package includes TypeScript declarations (`.d.ts` files) automatically. No a
|
|
|
491
693
|
import LicenseSeat from '@licenseseat/js';
|
|
492
694
|
|
|
493
695
|
// Types are automatically available
|
|
494
|
-
const sdk = new LicenseSeat({
|
|
696
|
+
const sdk = new LicenseSeat({
|
|
697
|
+
apiKey: 'your-key',
|
|
698
|
+
productSlug: 'your-product'
|
|
699
|
+
});
|
|
495
700
|
|
|
496
701
|
// Import specific types if needed
|
|
497
702
|
import type {
|
|
@@ -500,7 +705,10 @@ import type {
|
|
|
500
705
|
EntitlementCheckResult,
|
|
501
706
|
LicenseStatus,
|
|
502
707
|
Entitlement,
|
|
503
|
-
CachedLicense
|
|
708
|
+
CachedLicense,
|
|
709
|
+
ActivationResponse,
|
|
710
|
+
DeactivationResponse,
|
|
711
|
+
OfflineToken
|
|
504
712
|
} from '@licenseseat/js';
|
|
505
713
|
```
|
|
506
714
|
|
|
@@ -520,6 +728,7 @@ Use ES modules via CDN:
|
|
|
520
728
|
|
|
521
729
|
const sdk = new LicenseSeat({
|
|
522
730
|
apiKey: 'your-api-key',
|
|
731
|
+
productSlug: 'your-product',
|
|
523
732
|
debug: true
|
|
524
733
|
});
|
|
525
734
|
|
|
@@ -573,6 +782,50 @@ npm install
|
|
|
573
782
|
| `npm run test:coverage` | Run tests with coverage report |
|
|
574
783
|
| `npm run typecheck` | Type-check without emitting |
|
|
575
784
|
|
|
785
|
+
### Integration Tests
|
|
786
|
+
|
|
787
|
+
The SDK includes comprehensive integration tests that run against the live LicenseSeat API. These tests verify real-world functionality including activation, validation, deactivation, and offline cryptographic operations.
|
|
788
|
+
|
|
789
|
+
#### Running Integration Tests (Node.js)
|
|
790
|
+
|
|
791
|
+
```bash
|
|
792
|
+
# Set environment variables
|
|
793
|
+
export LICENSESEAT_API_KEY="ls_your_api_key_here"
|
|
794
|
+
export LICENSESEAT_PRODUCT_SLUG="your-product"
|
|
795
|
+
export LICENSESEAT_LICENSE_KEY="YOUR-LICENSE-KEY"
|
|
796
|
+
|
|
797
|
+
# Run the tests
|
|
798
|
+
node test-live.mjs
|
|
799
|
+
```
|
|
800
|
+
|
|
801
|
+
Or with inline environment variables:
|
|
802
|
+
|
|
803
|
+
```bash
|
|
804
|
+
LICENSESEAT_API_KEY=ls_xxx LICENSESEAT_PRODUCT_SLUG=my-app LICENSESEAT_LICENSE_KEY=XXX-XXX node test-live.mjs
|
|
805
|
+
```
|
|
806
|
+
|
|
807
|
+
#### Running Integration Tests (Browser)
|
|
808
|
+
|
|
809
|
+
Open `test-live.html` in a browser. You'll be prompted to enter your credentials:
|
|
810
|
+
|
|
811
|
+
1. **API Key** - Your LicenseSeat API key (starts with `ls_`)
|
|
812
|
+
2. **Product Slug** - Your product identifier
|
|
813
|
+
3. **License Key** - A valid license key for testing
|
|
814
|
+
|
|
815
|
+
Credentials are stored in `localStorage` for convenience during development.
|
|
816
|
+
|
|
817
|
+
#### What the Integration Tests Cover
|
|
818
|
+
|
|
819
|
+
| Category | Tests |
|
|
820
|
+
|----------|-------|
|
|
821
|
+
| **Initialization** | SDK setup, configuration defaults |
|
|
822
|
+
| **Activation** | License activation, device ID generation |
|
|
823
|
+
| **Validation** | Online validation, entitlement checking |
|
|
824
|
+
| **Deactivation** | License deactivation, cache clearing |
|
|
825
|
+
| **Offline Crypto** | Ed25519 signature verification, offline token fetching, tamper detection |
|
|
826
|
+
| **Error Handling** | Invalid licenses, missing config |
|
|
827
|
+
| **Singleton** | Shared instance pattern |
|
|
828
|
+
|
|
576
829
|
### Project Structure
|
|
577
830
|
|
|
578
831
|
```
|
|
@@ -584,11 +837,13 @@ licenseseat-js/
|
|
|
584
837
|
│ ├── errors.js # Error classes
|
|
585
838
|
│ ├── types.js # JSDoc type definitions
|
|
586
839
|
│ └── utils.js # Utility functions
|
|
587
|
-
├── tests/
|
|
840
|
+
├── tests/ # Unit tests (mocked API)
|
|
588
841
|
│ ├── setup.js # Test setup
|
|
589
842
|
│ ├── mocks/ # MSW handlers
|
|
590
843
|
│ ├── LicenseSeat.test.js
|
|
591
844
|
│ └── utils.test.js
|
|
845
|
+
├── test-live.mjs # Integration tests (Node.js)
|
|
846
|
+
├── test-live.html # Integration tests (Browser)
|
|
592
847
|
├── dist/ # Build output
|
|
593
848
|
│ ├── index.js # ESM bundle
|
|
594
849
|
│ └── types/ # TypeScript declarations
|
|
@@ -681,7 +936,7 @@ Once published to npm, the package is automatically available on CDNs:
|
|
|
681
936
|
**Version pinning** (recommended for production):
|
|
682
937
|
```html
|
|
683
938
|
<script type="module">
|
|
684
|
-
import LicenseSeat from 'https://esm.sh/@licenseseat/js@0.
|
|
939
|
+
import LicenseSeat from 'https://esm.sh/@licenseseat/js@0.3.0';
|
|
685
940
|
</script>
|
|
686
941
|
```
|
|
687
942
|
|
|
@@ -711,7 +966,10 @@ This creates `dist/index.global.js`:
|
|
|
711
966
|
```html
|
|
712
967
|
<script src="/path/to/index.global.js"></script>
|
|
713
968
|
<script>
|
|
714
|
-
const sdk = new LicenseSeat({
|
|
969
|
+
const sdk = new LicenseSeat({
|
|
970
|
+
apiKey: 'your-key',
|
|
971
|
+
productSlug: 'your-product'
|
|
972
|
+
});
|
|
715
973
|
</script>
|
|
716
974
|
```
|
|
717
975
|
|
|
@@ -727,22 +985,80 @@ This project follows [Semantic Versioning](https://semver.org/):
|
|
|
727
985
|
|
|
728
986
|
---
|
|
729
987
|
|
|
730
|
-
## Migration from v0.
|
|
988
|
+
## Migration from v0.2.x
|
|
989
|
+
|
|
990
|
+
### Breaking Changes in v0.3.0
|
|
991
|
+
|
|
992
|
+
This version introduces the v1 API with significant changes:
|
|
993
|
+
|
|
994
|
+
| Change | Before (v0.2.x) | After (v0.3.0) |
|
|
995
|
+
| -------------------------------- | ------------------------------- | ----------------------------------------- |
|
|
996
|
+
| `productSlug` config | Not required | **Required** for all API operations |
|
|
997
|
+
| `apiBaseUrl` default | `https://licenseseat.com/api` | `https://licenseseat.com/api/v1` |
|
|
998
|
+
| `deviceIdentifier` option | `deviceIdentifier` | `deviceId` |
|
|
999
|
+
| `device_identifier` field | `device_identifier` | `device_id` |
|
|
1000
|
+
| Deactivation response | Returns full activation object | Returns `{ object, activation_id, deactivated_at }` |
|
|
1001
|
+
| `getOfflineLicense()` method | Available | Renamed to `getOfflineToken()` |
|
|
1002
|
+
| `getPublicKey()` method | Available | Renamed to `getSigningKey()` |
|
|
1003
|
+
| Offline license structure | Legacy format | New token/signature/canonical format |
|
|
1004
|
+
| Error format | Various | `{ error: { code, message, details? } }` |
|
|
1005
|
+
|
|
1006
|
+
### Migration Steps
|
|
1007
|
+
|
|
1008
|
+
1. **Add `productSlug` to configuration:**
|
|
1009
|
+
```javascript
|
|
1010
|
+
// Before
|
|
1011
|
+
const sdk = new LicenseSeat({ apiKey: 'key' });
|
|
1012
|
+
|
|
1013
|
+
// After
|
|
1014
|
+
const sdk = new LicenseSeat({
|
|
1015
|
+
apiKey: 'key',
|
|
1016
|
+
productSlug: 'your-product' // Required!
|
|
1017
|
+
});
|
|
1018
|
+
```
|
|
1019
|
+
|
|
1020
|
+
2. **Update activation options:**
|
|
1021
|
+
```javascript
|
|
1022
|
+
// Before
|
|
1023
|
+
await sdk.activate('KEY', { deviceIdentifier: 'id' });
|
|
731
1024
|
|
|
732
|
-
|
|
1025
|
+
// After
|
|
1026
|
+
await sdk.activate('KEY', { deviceId: 'id' });
|
|
1027
|
+
```
|
|
733
1028
|
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
1029
|
+
3. **Update response field access:**
|
|
1030
|
+
```javascript
|
|
1031
|
+
// Before
|
|
1032
|
+
const result = await sdk.activate('KEY');
|
|
1033
|
+
console.log(result.device_identifier);
|
|
738
1034
|
|
|
739
|
-
|
|
1035
|
+
// After
|
|
1036
|
+
const result = await sdk.activate('KEY');
|
|
1037
|
+
console.log(result.device_id);
|
|
1038
|
+
```
|
|
1039
|
+
|
|
1040
|
+
4. **Update deactivation handling:**
|
|
1041
|
+
```javascript
|
|
1042
|
+
// Before
|
|
1043
|
+
const result = await sdk.deactivate();
|
|
1044
|
+
console.log(result.license_key);
|
|
1045
|
+
|
|
1046
|
+
// After
|
|
1047
|
+
const result = await sdk.deactivate();
|
|
1048
|
+
console.log(result.activation_id);
|
|
1049
|
+
console.log(result.deactivated_at);
|
|
1050
|
+
```
|
|
740
1051
|
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
1052
|
+
5. **Update offline method calls:**
|
|
1053
|
+
```javascript
|
|
1054
|
+
// Before
|
|
1055
|
+
await sdk.getOfflineLicense(key);
|
|
1056
|
+
await sdk.getPublicKey(keyId);
|
|
1057
|
+
|
|
1058
|
+
// After (note: getOfflineToken uses cached license, no parameter needed)
|
|
1059
|
+
await sdk.getOfflineToken();
|
|
1060
|
+
await sdk.getSigningKey(keyId);
|
|
1061
|
+
```
|
|
746
1062
|
|
|
747
1063
|
---
|
|
748
1064
|
|
|
@@ -760,10 +1076,3 @@ MIT License – see [LICENSE](LICENSE) for details.
|
|
|
760
1076
|
- [GitHub Repository](https://github.com/licenseseat/licenseseat-js)
|
|
761
1077
|
- [npm Package](https://www.npmjs.com/package/@licenseseat/js)
|
|
762
1078
|
- [Report Issues](https://github.com/licenseseat/licenseseat-js/issues)
|
|
763
|
-
|
|
764
|
-
---
|
|
765
|
-
|
|
766
|
-
## Support
|
|
767
|
-
|
|
768
|
-
- **Email**: support@licenseseat.com
|
|
769
|
-
- **GitHub Issues**: [Report a bug](https://github.com/licenseseat/licenseseat-js/issues/new)
|