@licenseseat/js 0.3.0 → 0.4.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 +312 -9
- package/dist/index.global.js +2339 -0
- package/dist/index.js +479 -23
- package/dist/types/LicenseSeat.d.ts +51 -13
- package/dist/types/LicenseSeat.d.ts.map +1 -1
- package/dist/types/index.d.ts +2 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/telemetry.d.ts +16 -0
- package/dist/types/telemetry.d.ts.map +1 -0
- package/dist/types/types.d.ts +33 -0
- package/dist/types/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/LicenseSeat.js +147 -25
- package/src/global.d.ts +23 -0
- package/src/index.js +7 -2
- package/src/telemetry.js +518 -0
- package/src/types.js +12 -0
package/README.md
CHANGED
|
@@ -13,6 +13,8 @@ The official JavaScript/TypeScript SDK for [LicenseSeat](https://licenseseat.com
|
|
|
13
13
|
- **License activation & deactivation** – Activate licenses with automatic device fingerprinting
|
|
14
14
|
- **Online & offline validation** – Validate licenses with optional offline fallback
|
|
15
15
|
- **Entitlement checking** – Check feature access with `hasEntitlement()` and `checkEntitlement()`
|
|
16
|
+
- **Heartbeat** – Automatic periodic heartbeats to report device activity
|
|
17
|
+
- **Telemetry** – Auto-collected device and environment data sent with each API request
|
|
16
18
|
- **Local caching** – Secure localStorage-based caching with clock tamper detection
|
|
17
19
|
- **Auto-retry with exponential backoff** – Resilient network handling
|
|
18
20
|
- **Event-driven architecture** – Subscribe to SDK lifecycle events
|
|
@@ -138,6 +140,14 @@ const sdk = new LicenseSeat({
|
|
|
138
140
|
autoValidateInterval: 3600000, // 1 hour (in ms)
|
|
139
141
|
autoInitialize: true, // Auto-validate cached license on init
|
|
140
142
|
|
|
143
|
+
// Heartbeat
|
|
144
|
+
heartbeatInterval: 300000, // 5 minutes (in ms), 0 to disable
|
|
145
|
+
|
|
146
|
+
// Telemetry
|
|
147
|
+
telemetryEnabled: true, // Set false to disable (e.g. GDPR)
|
|
148
|
+
appVersion: '1.2.0', // Your app version (sent in telemetry)
|
|
149
|
+
appBuild: '42', // Your app build number (sent in telemetry)
|
|
150
|
+
|
|
141
151
|
// Offline Support
|
|
142
152
|
offlineFallbackEnabled: false, // Enable offline validation fallback
|
|
143
153
|
maxOfflineDays: 0, // Max days offline (0 = disabled)
|
|
@@ -164,6 +174,10 @@ const sdk = new LicenseSeat({
|
|
|
164
174
|
| `storagePrefix` | `string` | `'licenseseat_'` | Prefix for localStorage keys |
|
|
165
175
|
| `autoValidateInterval` | `number` | `3600000` | Auto-validation interval in ms (1 hour) |
|
|
166
176
|
| `autoInitialize` | `boolean` | `true` | Auto-initialize and validate cached license |
|
|
177
|
+
| `heartbeatInterval` | `number` | `300000` | Heartbeat interval in ms (5 minutes). Set `0` to disable |
|
|
178
|
+
| `telemetryEnabled` | `boolean` | `true` | Enable telemetry collection. Set `false` for GDPR compliance |
|
|
179
|
+
| `appVersion` | `string` | `null` | Your app version string (sent as `app_version` in telemetry) |
|
|
180
|
+
| `appBuild` | `string` | `null` | Your app build identifier (sent as `app_build` in telemetry) |
|
|
167
181
|
| `offlineFallbackEnabled` | `boolean` | `false` | Enable offline validation on network errors |
|
|
168
182
|
| `maxOfflineDays` | `number` | `0` | Maximum days license works offline (0 = disabled) |
|
|
169
183
|
| `maxRetries` | `number` | `3` | Max retry attempts for failed API calls |
|
|
@@ -248,9 +262,11 @@ console.log(result);
|
|
|
248
262
|
|
|
249
263
|
### Entitlement Methods
|
|
250
264
|
|
|
265
|
+
> **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.
|
|
266
|
+
|
|
251
267
|
#### `sdk.hasEntitlement(key)`
|
|
252
268
|
|
|
253
|
-
Check if an entitlement is active. Returns a simple boolean.
|
|
269
|
+
Check if an entitlement is active. Returns a simple boolean. Returns `false` if no entitlements exist.
|
|
254
270
|
|
|
255
271
|
```javascript
|
|
256
272
|
if (sdk.hasEntitlement('pro')) {
|
|
@@ -308,17 +324,38 @@ console.log(status);
|
|
|
308
324
|
|
|
309
325
|
#### `sdk.testAuth()`
|
|
310
326
|
|
|
311
|
-
Test API
|
|
327
|
+
Test API connectivity by calling the `/health` endpoint. Returns health status and API version.
|
|
312
328
|
|
|
313
329
|
```javascript
|
|
314
330
|
try {
|
|
315
331
|
const result = await sdk.testAuth();
|
|
316
|
-
console.log('Authenticated:', result.authenticated);
|
|
332
|
+
console.log('Authenticated:', result.authenticated); // Always true if request succeeds
|
|
333
|
+
console.log('Healthy:', result.healthy); // API health status
|
|
334
|
+
console.log('API Version:', result.api_version); // e.g., '1.0.0'
|
|
317
335
|
} catch (error) {
|
|
318
|
-
console.error('
|
|
336
|
+
console.error('Connection failed:', error);
|
|
319
337
|
}
|
|
320
338
|
```
|
|
321
339
|
|
|
340
|
+
> **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()`.
|
|
341
|
+
|
|
342
|
+
#### `sdk.heartbeat()`
|
|
343
|
+
|
|
344
|
+
Send a heartbeat to report that the current device is still active. Heartbeats are sent automatically at the configured `heartbeatInterval`, but you can also send one manually.
|
|
345
|
+
|
|
346
|
+
```javascript
|
|
347
|
+
try {
|
|
348
|
+
const result = await sdk.heartbeat();
|
|
349
|
+
console.log('Heartbeat received at:', result.received_at);
|
|
350
|
+
} catch (error) {
|
|
351
|
+
console.error('Heartbeat failed:', error);
|
|
352
|
+
}
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
Returns `undefined` if no active license is cached. When auto-heartbeat is enabled (the default), the SDK sends heartbeats every 5 minutes while a license is active. Auto-heartbeat starts automatically after `activate()` or when the SDK initializes with a cached license.
|
|
356
|
+
|
|
357
|
+
To disable auto-heartbeat, set `heartbeatInterval: 0` in the configuration.
|
|
358
|
+
|
|
322
359
|
#### `sdk.reset()`
|
|
323
360
|
|
|
324
361
|
Clear all cached data and reset SDK state.
|
|
@@ -397,6 +434,9 @@ sdk.off('activation:success', handler);
|
|
|
397
434
|
| **Auto-Validation** | | |
|
|
398
435
|
| `autovalidation:cycle` | Auto-validation scheduled | `{ nextRunAt: Date }` |
|
|
399
436
|
| `autovalidation:stopped` | Auto-validation stopped | – |
|
|
437
|
+
| **Heartbeat** | | |
|
|
438
|
+
| `heartbeat:success` | Heartbeat acknowledged by server | `HeartbeatResponse` |
|
|
439
|
+
| `heartbeat:cycle` | Auto-heartbeat tick completed | `{ nextRunAt: Date }` |
|
|
400
440
|
| **Network** | | |
|
|
401
441
|
| `network:online` | Network connectivity restored | – |
|
|
402
442
|
| `network:offline` | Network connectivity lost | `{ error }` |
|
|
@@ -465,6 +505,70 @@ if (result.offline) {
|
|
|
465
505
|
3. When offline, the SDK verifies the signature locally
|
|
466
506
|
4. Clock tamper detection prevents users from bypassing expiration
|
|
467
507
|
|
|
508
|
+
### Offline Methods
|
|
509
|
+
|
|
510
|
+
#### `sdk.syncOfflineAssets()`
|
|
511
|
+
|
|
512
|
+
Fetches the offline token and signing key from the server. Uses the currently cached license. Call this after activation to prepare for offline usage.
|
|
513
|
+
|
|
514
|
+
```javascript
|
|
515
|
+
// First activate (caches the license)
|
|
516
|
+
await sdk.activate('LICENSE-KEY');
|
|
517
|
+
|
|
518
|
+
// Then sync offline assets (uses cached license)
|
|
519
|
+
const assets = await sdk.syncOfflineAssets();
|
|
520
|
+
console.log('Offline token key ID:', assets.kid);
|
|
521
|
+
console.log('Expires at:', assets.exp_at);
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
#### `sdk.getOfflineToken()`
|
|
525
|
+
|
|
526
|
+
Fetches a signed offline token for the currently cached license. Returns the token structure containing the license data and Ed25519 signature.
|
|
527
|
+
|
|
528
|
+
```javascript
|
|
529
|
+
// Must have an active license cached first
|
|
530
|
+
const token = await sdk.getOfflineToken();
|
|
531
|
+
console.log(token);
|
|
532
|
+
// {
|
|
533
|
+
// object: 'offline_token',
|
|
534
|
+
// token: { license_key, product_slug, plan_key, ... },
|
|
535
|
+
// signature: { algorithm: 'Ed25519', key_id, value },
|
|
536
|
+
// canonical: '...'
|
|
537
|
+
// }
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
#### `sdk.getSigningKey(keyId)`
|
|
541
|
+
|
|
542
|
+
Fetches the Ed25519 public key used for verifying offline token signatures.
|
|
543
|
+
|
|
544
|
+
```javascript
|
|
545
|
+
const signingKey = await sdk.getSigningKey('key-id-001');
|
|
546
|
+
console.log(signingKey);
|
|
547
|
+
// {
|
|
548
|
+
// object: 'signing_key',
|
|
549
|
+
// kid: 'key-id-001',
|
|
550
|
+
// public_key: 'base64-encoded-public-key',
|
|
551
|
+
// algorithm: 'Ed25519',
|
|
552
|
+
// created_at: '2024-01-01T00:00:00Z'
|
|
553
|
+
// }
|
|
554
|
+
```
|
|
555
|
+
|
|
556
|
+
#### `sdk.verifyOfflineToken(token, publicKeyB64)`
|
|
557
|
+
|
|
558
|
+
Verifies an offline token's Ed25519 signature locally. **Both parameters are required.**
|
|
559
|
+
|
|
560
|
+
```javascript
|
|
561
|
+
// Fetch the token and signing key first
|
|
562
|
+
const token = await sdk.getOfflineToken();
|
|
563
|
+
const signingKey = await sdk.getSigningKey(token.signature.key_id);
|
|
564
|
+
|
|
565
|
+
// Verify the signature
|
|
566
|
+
const isValid = await sdk.verifyOfflineToken(token, signingKey.public_key);
|
|
567
|
+
console.log('Signature valid:', isValid);
|
|
568
|
+
```
|
|
569
|
+
|
|
570
|
+
> **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.
|
|
571
|
+
|
|
468
572
|
### Offline Token Structure
|
|
469
573
|
|
|
470
574
|
```javascript
|
|
@@ -498,6 +602,126 @@ if (result.offline) {
|
|
|
498
602
|
|
|
499
603
|
---
|
|
500
604
|
|
|
605
|
+
## Telemetry
|
|
606
|
+
|
|
607
|
+
The SDK automatically collects non-PII (non-personally-identifiable) device and environment data and includes it with every POST request sent to the LicenseSeat API. This data helps you understand what platforms and environments your customers use.
|
|
608
|
+
|
|
609
|
+
Telemetry is **enabled by default** and can be disabled at any time.
|
|
610
|
+
|
|
611
|
+
### Collected Fields
|
|
612
|
+
|
|
613
|
+
| Field | Type | Example | Description |
|
|
614
|
+
| -------------------- | -------- | ---------------------- | ------------------------------------------------ |
|
|
615
|
+
| `sdk_name` | `string` | `"js"` | Always `"js"` for this SDK |
|
|
616
|
+
| `sdk_version` | `string` | `"0.4.0"` | SDK version |
|
|
617
|
+
| `os_name` | `string` | `"macOS"` | Operating system name |
|
|
618
|
+
| `os_version` | `string` | `"14.2.1"` | Operating system version |
|
|
619
|
+
| `platform` | `string` | `"browser"` | Runtime platform (`browser`, `node`, `electron`, `react-native`, `deno`, `bun`) |
|
|
620
|
+
| `device_model` | `string` | `null` | Device model (Chromium userAgentData only) |
|
|
621
|
+
| `device_type` | `string` | `"desktop"` | Device type (`desktop`, `phone`, `tablet`, `server`) |
|
|
622
|
+
| `locale` | `string` | `"en-US"` | Full locale string |
|
|
623
|
+
| `timezone` | `string` | `"America/New_York"` | IANA timezone |
|
|
624
|
+
| `language` | `string` | `"en"` | 2-letter language code |
|
|
625
|
+
| `architecture` | `string` | `"arm64"` | CPU architecture |
|
|
626
|
+
| `cpu_cores` | `number` | `10` | Number of logical CPU cores |
|
|
627
|
+
| `memory_gb` | `number` | `16` | Approximate RAM in GB (Chrome/Node.js only) |
|
|
628
|
+
| `screen_resolution` | `string` | `"1920x1080"` | Screen resolution |
|
|
629
|
+
| `display_scale` | `number` | `2` | Device pixel ratio |
|
|
630
|
+
| `browser_name` | `string` | `"Chrome"` | Browser name (browser environments only) |
|
|
631
|
+
| `browser_version` | `string` | `"123.0"` | Browser version (browser environments only) |
|
|
632
|
+
| `runtime_version` | `string` | `"20.11.0"` | Runtime version (Node.js, Deno, Bun, Electron) |
|
|
633
|
+
| `app_version` | `string` | `"1.2.0"` | Your app version (from `appVersion` config) |
|
|
634
|
+
| `app_build` | `string` | `"42"` | Your app build (from `appBuild` config) |
|
|
635
|
+
|
|
636
|
+
Fields that cannot be detected in the current environment are omitted (not sent as `null`).
|
|
637
|
+
|
|
638
|
+
### Providing App Version
|
|
639
|
+
|
|
640
|
+
Pass your own app version and build number via configuration so they appear in telemetry:
|
|
641
|
+
|
|
642
|
+
```javascript
|
|
643
|
+
const sdk = new LicenseSeat({
|
|
644
|
+
apiKey: 'your-key',
|
|
645
|
+
productSlug: 'your-product',
|
|
646
|
+
appVersion: '1.2.0',
|
|
647
|
+
appBuild: '42'
|
|
648
|
+
});
|
|
649
|
+
```
|
|
650
|
+
|
|
651
|
+
### Disabling Telemetry
|
|
652
|
+
|
|
653
|
+
To disable telemetry collection entirely (for example, to comply with GDPR or other privacy regulations):
|
|
654
|
+
|
|
655
|
+
```javascript
|
|
656
|
+
const sdk = new LicenseSeat({
|
|
657
|
+
apiKey: 'your-key',
|
|
658
|
+
productSlug: 'your-product',
|
|
659
|
+
telemetryEnabled: false
|
|
660
|
+
});
|
|
661
|
+
```
|
|
662
|
+
|
|
663
|
+
When telemetry is disabled, no device or environment data is attached to API requests.
|
|
664
|
+
|
|
665
|
+
### Privacy
|
|
666
|
+
|
|
667
|
+
Telemetry collects only non-personally-identifiable information. No IP addresses, user names, email addresses, or other PII are collected by the SDK. The data is used solely to help you understand the platforms and environments where your software is used.
|
|
668
|
+
|
|
669
|
+
Telemetry is opt-out: set `telemetryEnabled: false` to disable it completely.
|
|
670
|
+
|
|
671
|
+
---
|
|
672
|
+
|
|
673
|
+
## Heartbeat
|
|
674
|
+
|
|
675
|
+
The SDK sends periodic heartbeat signals to let the server know a device is still actively using the license. This enables usage analytics and helps detect inactive seats.
|
|
676
|
+
|
|
677
|
+
### How It Works
|
|
678
|
+
|
|
679
|
+
- After a license is activated (or when the SDK initializes with a cached license), a heartbeat timer starts automatically.
|
|
680
|
+
- The default interval is **5 minutes** (`300000` ms), configurable via `heartbeatInterval`.
|
|
681
|
+
- Each heartbeat sends the current `device_id` to the server.
|
|
682
|
+
- Heartbeats also run alongside auto-validation cycles.
|
|
683
|
+
|
|
684
|
+
### Manual Heartbeat
|
|
685
|
+
|
|
686
|
+
You can send a heartbeat at any time:
|
|
687
|
+
|
|
688
|
+
```javascript
|
|
689
|
+
await sdk.heartbeat();
|
|
690
|
+
```
|
|
691
|
+
|
|
692
|
+
### Configuring the Interval
|
|
693
|
+
|
|
694
|
+
```javascript
|
|
695
|
+
const sdk = new LicenseSeat({
|
|
696
|
+
apiKey: 'your-key',
|
|
697
|
+
productSlug: 'your-product',
|
|
698
|
+
heartbeatInterval: 600000 // 10 minutes
|
|
699
|
+
});
|
|
700
|
+
```
|
|
701
|
+
|
|
702
|
+
Set `heartbeatInterval: 0` to disable auto-heartbeat entirely. You can still call `sdk.heartbeat()` manually.
|
|
703
|
+
|
|
704
|
+
### Heartbeat Events
|
|
705
|
+
|
|
706
|
+
| Event | Description | Data |
|
|
707
|
+
| -------------------- | ---------------------------------- | ----------------------- |
|
|
708
|
+
| `heartbeat:success` | Heartbeat acknowledged by server | `HeartbeatResponse` |
|
|
709
|
+
| `heartbeat:cycle` | Auto-heartbeat tick completed | `{ nextRunAt: Date }` |
|
|
710
|
+
|
|
711
|
+
```javascript
|
|
712
|
+
sdk.on('heartbeat:success', (data) => {
|
|
713
|
+
console.log('Heartbeat received at:', data.received_at);
|
|
714
|
+
});
|
|
715
|
+
```
|
|
716
|
+
|
|
717
|
+
### Heartbeat Lifecycle
|
|
718
|
+
|
|
719
|
+
- **Starts** automatically after `sdk.activate()` succeeds, or on SDK init if a cached license exists.
|
|
720
|
+
- **Stops** automatically when `sdk.deactivate()`, `sdk.reset()`, or `sdk.destroy()` is called.
|
|
721
|
+
- Heartbeat failures are logged (in debug mode) but do not throw or interrupt the SDK.
|
|
722
|
+
|
|
723
|
+
---
|
|
724
|
+
|
|
501
725
|
## Error Handling
|
|
502
726
|
|
|
503
727
|
The SDK exports custom error classes for precise error handling:
|
|
@@ -564,7 +788,39 @@ Common error codes:
|
|
|
564
788
|
|
|
565
789
|
- **Modern browsers**: Chrome 80+, Firefox 75+, Safari 14+, Edge 80+
|
|
566
790
|
- **Bundlers**: Vite, Webpack, Rollup, esbuild, Parcel
|
|
567
|
-
- **Node.js**: 18+ (requires polyfills
|
|
791
|
+
- **Node.js**: 18+ (requires polyfills - see below)
|
|
792
|
+
|
|
793
|
+
### Node.js Usage
|
|
794
|
+
|
|
795
|
+
The SDK is designed for browsers but works in Node.js with polyfills. Add these before importing the SDK:
|
|
796
|
+
|
|
797
|
+
```javascript
|
|
798
|
+
// Required polyfills for Node.js
|
|
799
|
+
const storage = {};
|
|
800
|
+
globalThis.localStorage = {
|
|
801
|
+
getItem(key) { return Object.prototype.hasOwnProperty.call(storage, key) ? storage[key] : null; },
|
|
802
|
+
setItem(key, value) { storage[key] = String(value); },
|
|
803
|
+
removeItem(key) { delete storage[key]; },
|
|
804
|
+
clear() { for (const key in storage) delete storage[key]; },
|
|
805
|
+
};
|
|
806
|
+
|
|
807
|
+
// Override Object.keys to support localStorage iteration (used by cache.getAllKeys())
|
|
808
|
+
const originalKeys = Object.keys;
|
|
809
|
+
Object.keys = function(obj) {
|
|
810
|
+
if (obj === globalThis.localStorage) return originalKeys(storage);
|
|
811
|
+
return originalKeys(obj);
|
|
812
|
+
};
|
|
813
|
+
|
|
814
|
+
// Device fingerprinting polyfills (provides stable fallback values)
|
|
815
|
+
globalThis.document = { createElement: () => ({ getContext: () => null }), querySelector: () => null };
|
|
816
|
+
globalThis.window = { navigator: {}, screen: {} };
|
|
817
|
+
globalThis.navigator = { userAgent: 'Node.js', language: 'en', hardwareConcurrency: 4 };
|
|
818
|
+
|
|
819
|
+
// Now import the SDK
|
|
820
|
+
const { default: LicenseSeat } = await import('@licenseseat/js');
|
|
821
|
+
```
|
|
822
|
+
|
|
823
|
+
> **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()`.
|
|
568
824
|
|
|
569
825
|
---
|
|
570
826
|
|
|
@@ -680,6 +936,50 @@ npm install
|
|
|
680
936
|
| `npm run test:coverage` | Run tests with coverage report |
|
|
681
937
|
| `npm run typecheck` | Type-check without emitting |
|
|
682
938
|
|
|
939
|
+
### Integration Tests
|
|
940
|
+
|
|
941
|
+
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.
|
|
942
|
+
|
|
943
|
+
#### Running Integration Tests (Node.js)
|
|
944
|
+
|
|
945
|
+
```bash
|
|
946
|
+
# Set environment variables
|
|
947
|
+
export LICENSESEAT_API_KEY="ls_your_api_key_here"
|
|
948
|
+
export LICENSESEAT_PRODUCT_SLUG="your-product"
|
|
949
|
+
export LICENSESEAT_LICENSE_KEY="YOUR-LICENSE-KEY"
|
|
950
|
+
|
|
951
|
+
# Run the tests
|
|
952
|
+
node test-live.mjs
|
|
953
|
+
```
|
|
954
|
+
|
|
955
|
+
Or with inline environment variables:
|
|
956
|
+
|
|
957
|
+
```bash
|
|
958
|
+
LICENSESEAT_API_KEY=ls_xxx LICENSESEAT_PRODUCT_SLUG=my-app LICENSESEAT_LICENSE_KEY=XXX-XXX node test-live.mjs
|
|
959
|
+
```
|
|
960
|
+
|
|
961
|
+
#### Running Integration Tests (Browser)
|
|
962
|
+
|
|
963
|
+
Open `test-live.html` in a browser. You'll be prompted to enter your credentials:
|
|
964
|
+
|
|
965
|
+
1. **API Key** - Your LicenseSeat API key (starts with `ls_`)
|
|
966
|
+
2. **Product Slug** - Your product identifier
|
|
967
|
+
3. **License Key** - A valid license key for testing
|
|
968
|
+
|
|
969
|
+
Credentials are stored in `localStorage` for convenience during development.
|
|
970
|
+
|
|
971
|
+
#### What the Integration Tests Cover
|
|
972
|
+
|
|
973
|
+
| Category | Tests |
|
|
974
|
+
|----------|-------|
|
|
975
|
+
| **Initialization** | SDK setup, configuration defaults |
|
|
976
|
+
| **Activation** | License activation, device ID generation |
|
|
977
|
+
| **Validation** | Online validation, entitlement checking |
|
|
978
|
+
| **Deactivation** | License deactivation, cache clearing |
|
|
979
|
+
| **Offline Crypto** | Ed25519 signature verification, offline token fetching, tamper detection |
|
|
980
|
+
| **Error Handling** | Invalid licenses, missing config |
|
|
981
|
+
| **Singleton** | Shared instance pattern |
|
|
982
|
+
|
|
683
983
|
### Project Structure
|
|
684
984
|
|
|
685
985
|
```
|
|
@@ -687,15 +987,18 @@ licenseseat-js/
|
|
|
687
987
|
├── src/
|
|
688
988
|
│ ├── index.js # Entry point, exports
|
|
689
989
|
│ ├── LicenseSeat.js # Main SDK class
|
|
990
|
+
│ ├── telemetry.js # Telemetry collection (device/environment data)
|
|
690
991
|
│ ├── cache.js # LicenseCache (localStorage)
|
|
691
992
|
│ ├── errors.js # Error classes
|
|
692
993
|
│ ├── types.js # JSDoc type definitions
|
|
693
994
|
│ └── utils.js # Utility functions
|
|
694
|
-
├── tests/
|
|
995
|
+
├── tests/ # Unit tests (mocked API)
|
|
695
996
|
│ ├── setup.js # Test setup
|
|
696
997
|
│ ├── mocks/ # MSW handlers
|
|
697
998
|
│ ├── LicenseSeat.test.js
|
|
698
999
|
│ └── utils.test.js
|
|
1000
|
+
├── test-live.mjs # Integration tests (Node.js)
|
|
1001
|
+
├── test-live.html # Integration tests (Browser)
|
|
699
1002
|
├── dist/ # Build output
|
|
700
1003
|
│ ├── index.js # ESM bundle
|
|
701
1004
|
│ └── types/ # TypeScript declarations
|
|
@@ -788,7 +1091,7 @@ Once published to npm, the package is automatically available on CDNs:
|
|
|
788
1091
|
**Version pinning** (recommended for production):
|
|
789
1092
|
```html
|
|
790
1093
|
<script type="module">
|
|
791
|
-
import LicenseSeat from 'https://esm.sh/@licenseseat/js@0.
|
|
1094
|
+
import LicenseSeat from 'https://esm.sh/@licenseseat/js@0.4.0';
|
|
792
1095
|
</script>
|
|
793
1096
|
```
|
|
794
1097
|
|
|
@@ -907,8 +1210,8 @@ This version introduces the v1 API with significant changes:
|
|
|
907
1210
|
await sdk.getOfflineLicense(key);
|
|
908
1211
|
await sdk.getPublicKey(keyId);
|
|
909
1212
|
|
|
910
|
-
// After
|
|
911
|
-
await sdk.getOfflineToken(
|
|
1213
|
+
// After (note: getOfflineToken uses cached license, no parameter needed)
|
|
1214
|
+
await sdk.getOfflineToken();
|
|
912
1215
|
await sdk.getSigningKey(keyId);
|
|
913
1216
|
```
|
|
914
1217
|
|