@licenseseat/js 0.2.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 +13 -3
- package/dist/index.js +58 -5
- package/dist/types/LicenseSeat.d.ts +21 -0
- package/dist/types/LicenseSeat.d.ts.map +1 -1
- package/dist/types/types.d.ts +35 -14
- package/dist/types/types.d.ts.map +1 -1
- package/dist/types/utils.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/LicenseSeat.js +87 -3
- package/src/types.js +12 -8
- package/src/utils.js +0 -2
package/README.md
CHANGED
|
@@ -121,7 +121,7 @@ const sdk = new LicenseSeat({
|
|
|
121
121
|
apiKey: 'your-api-key',
|
|
122
122
|
|
|
123
123
|
// API Configuration
|
|
124
|
-
apiBaseUrl: 'https://
|
|
124
|
+
apiBaseUrl: 'https://licenseseat.com/api', // Default
|
|
125
125
|
|
|
126
126
|
// Storage
|
|
127
127
|
storagePrefix: 'licenseseat_', // localStorage key prefix
|
|
@@ -151,7 +151,7 @@ const sdk = new LicenseSeat({
|
|
|
151
151
|
| Option | Type | Default | Description |
|
|
152
152
|
|--------|------|---------|-------------|
|
|
153
153
|
| `apiKey` | `string` | `null` | API key for authentication (required for most operations) |
|
|
154
|
-
| `apiBaseUrl` | `string` | `'https://
|
|
154
|
+
| `apiBaseUrl` | `string` | `'https://licenseseat.com/api'` | API base URL |
|
|
155
155
|
| `storagePrefix` | `string` | `'licenseseat_'` | Prefix for localStorage keys |
|
|
156
156
|
| `autoValidateInterval` | `number` | `3600000` | Auto-validation interval in ms (1 hour) |
|
|
157
157
|
| `autoInitialize` | `boolean` | `true` | Auto-initialize and validate cached license |
|
|
@@ -296,6 +296,15 @@ Clear all cached data and reset SDK state.
|
|
|
296
296
|
sdk.reset();
|
|
297
297
|
```
|
|
298
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
|
+
|
|
299
308
|
#### `sdk.initialize()`
|
|
300
309
|
|
|
301
310
|
Manually initialize the SDK (only needed if `autoInitialize: false`).
|
|
@@ -335,6 +344,7 @@ sdk.off('activation:success', handler);
|
|
|
335
344
|
| **Lifecycle** | | |
|
|
336
345
|
| `license:loaded` | Cached license loaded on init | `CachedLicense` |
|
|
337
346
|
| `sdk:reset` | SDK was reset | – |
|
|
347
|
+
| `sdk:destroyed` | SDK was destroyed | – |
|
|
338
348
|
| `sdk:error` | General SDK error | `{ message, error? }` |
|
|
339
349
|
| **Activation** | | |
|
|
340
350
|
| `activation:start` | Activation started | `{ licenseKey, deviceId }` |
|
|
@@ -723,7 +733,7 @@ This project follows [Semantic Versioning](https://semver.org/):
|
|
|
723
733
|
|
|
724
734
|
| Change | Before | After | Migration |
|
|
725
735
|
|--------|--------|-------|-----------|
|
|
726
|
-
| `apiBaseUrl` default | `/api` | `https://
|
|
736
|
+
| `apiBaseUrl` default | `/api` | `https://licenseseat.com/api` | Set `apiBaseUrl` explicitly if using a relative URL |
|
|
727
737
|
| `offlineFallbackEnabled` default | `true` | `false` | Set `offlineFallbackEnabled: true` if you need offline fallback |
|
|
728
738
|
|
|
729
739
|
### New Features in v0.2.0
|
package/dist/index.js
CHANGED
|
@@ -221,8 +221,6 @@ function parseActiveEntitlements(payload = {}) {
|
|
|
221
221
|
const raw = payload.active_ents || payload.active_entitlements || [];
|
|
222
222
|
return raw.map((e) => ({
|
|
223
223
|
key: e.key,
|
|
224
|
-
name: e.name ?? null,
|
|
225
|
-
description: e.description ?? null,
|
|
226
224
|
expires_at: e.expires_at ?? null,
|
|
227
225
|
metadata: e.metadata ?? null
|
|
228
226
|
}));
|
|
@@ -314,7 +312,7 @@ function getCsrfToken() {
|
|
|
314
312
|
|
|
315
313
|
// src/LicenseSeat.js
|
|
316
314
|
var DEFAULT_CONFIG = {
|
|
317
|
-
apiBaseUrl: "https://
|
|
315
|
+
apiBaseUrl: "https://licenseseat.com/api",
|
|
318
316
|
storagePrefix: "licenseseat_",
|
|
319
317
|
autoValidateInterval: 36e5,
|
|
320
318
|
// 1 hour
|
|
@@ -352,6 +350,8 @@ var LicenseSeatSDK = class {
|
|
|
352
350
|
this.connectivityTimer = null;
|
|
353
351
|
this.offlineRefreshTimer = null;
|
|
354
352
|
this.lastOfflineValidation = null;
|
|
353
|
+
this.syncingOfflineAssets = false;
|
|
354
|
+
this.destroyed = false;
|
|
355
355
|
if (ed && ed.etc && sha512) {
|
|
356
356
|
ed.etc.sha512Sync = (...m) => sha512(ed.etc.concatBytes(...m));
|
|
357
357
|
} else {
|
|
@@ -488,7 +488,7 @@ var LicenseSeatSDK = class {
|
|
|
488
488
|
async validateLicense(licenseKey, options = {}) {
|
|
489
489
|
try {
|
|
490
490
|
this.emit("validation:start", { licenseKey });
|
|
491
|
-
const
|
|
491
|
+
const rawResponse = await this.apiCall("/licenses/validate", {
|
|
492
492
|
method: "POST",
|
|
493
493
|
body: {
|
|
494
494
|
license_key: licenseKey,
|
|
@@ -496,6 +496,10 @@ var LicenseSeatSDK = class {
|
|
|
496
496
|
product_slug: options.productSlug
|
|
497
497
|
}
|
|
498
498
|
});
|
|
499
|
+
const response = {
|
|
500
|
+
valid: rawResponse.valid,
|
|
501
|
+
...rawResponse.license || {}
|
|
502
|
+
};
|
|
499
503
|
const cachedLicense = this.cache.getLicense();
|
|
500
504
|
if ((!response.active_entitlements || response.active_entitlements.length === 0) && cachedLicense?.validation?.active_entitlements?.length) {
|
|
501
505
|
response.active_entitlements = cachedLicense.validation.active_entitlements;
|
|
@@ -769,10 +773,36 @@ var LicenseSeatSDK = class {
|
|
|
769
773
|
*/
|
|
770
774
|
reset() {
|
|
771
775
|
this.stopAutoValidation();
|
|
776
|
+
this.stopConnectivityPolling();
|
|
777
|
+
if (this.offlineRefreshTimer) {
|
|
778
|
+
clearInterval(this.offlineRefreshTimer);
|
|
779
|
+
this.offlineRefreshTimer = null;
|
|
780
|
+
}
|
|
772
781
|
this.cache.clear();
|
|
773
782
|
this.lastOfflineValidation = null;
|
|
783
|
+
this.currentAutoLicenseKey = null;
|
|
774
784
|
this.emit("sdk:reset");
|
|
775
785
|
}
|
|
786
|
+
/**
|
|
787
|
+
* Destroy the SDK instance and release all resources
|
|
788
|
+
* Call this when you no longer need the SDK to prevent memory leaks.
|
|
789
|
+
* After calling destroy(), the SDK instance should not be used.
|
|
790
|
+
* @returns {void}
|
|
791
|
+
*/
|
|
792
|
+
destroy() {
|
|
793
|
+
this.destroyed = true;
|
|
794
|
+
this.stopAutoValidation();
|
|
795
|
+
this.stopConnectivityPolling();
|
|
796
|
+
if (this.offlineRefreshTimer) {
|
|
797
|
+
clearInterval(this.offlineRefreshTimer);
|
|
798
|
+
this.offlineRefreshTimer = null;
|
|
799
|
+
}
|
|
800
|
+
this.eventListeners = {};
|
|
801
|
+
this.cache.clear();
|
|
802
|
+
this.lastOfflineValidation = null;
|
|
803
|
+
this.currentAutoLicenseKey = null;
|
|
804
|
+
this.emit("sdk:destroyed");
|
|
805
|
+
}
|
|
776
806
|
// ============================================================
|
|
777
807
|
// Event Handling
|
|
778
808
|
// ============================================================
|
|
@@ -907,10 +937,16 @@ var LicenseSeatSDK = class {
|
|
|
907
937
|
// ============================================================
|
|
908
938
|
/**
|
|
909
939
|
* Fetch and cache offline license and public key
|
|
940
|
+
* Uses a lock to prevent concurrent calls from causing race conditions
|
|
910
941
|
* @returns {Promise<void>}
|
|
911
942
|
* @private
|
|
912
943
|
*/
|
|
913
944
|
async syncOfflineAssets() {
|
|
945
|
+
if (this.syncingOfflineAssets || this.destroyed) {
|
|
946
|
+
this.log("Skipping syncOfflineAssets: already syncing or destroyed");
|
|
947
|
+
return;
|
|
948
|
+
}
|
|
949
|
+
this.syncingOfflineAssets = true;
|
|
914
950
|
try {
|
|
915
951
|
const offline = await this.getOfflineLicense();
|
|
916
952
|
this.cache.setOfflineLicense(offline);
|
|
@@ -936,6 +972,8 @@ var LicenseSeatSDK = class {
|
|
|
936
972
|
}
|
|
937
973
|
} catch (err) {
|
|
938
974
|
this.log("Failed to sync offline assets:", err);
|
|
975
|
+
} finally {
|
|
976
|
+
this.syncingOfflineAssets = false;
|
|
939
977
|
}
|
|
940
978
|
}
|
|
941
979
|
/**
|
|
@@ -1016,6 +1054,7 @@ var LicenseSeatSDK = class {
|
|
|
1016
1054
|
}
|
|
1017
1055
|
/**
|
|
1018
1056
|
* Quick offline verification using only local data (no network)
|
|
1057
|
+
* Performs signature verification plus basic validity checks (expiry, license key match)
|
|
1019
1058
|
* @returns {Promise<import('./types.js').ValidationResult|null>}
|
|
1020
1059
|
* @private
|
|
1021
1060
|
*/
|
|
@@ -1032,7 +1071,21 @@ var LicenseSeatSDK = class {
|
|
|
1032
1071
|
if (!ok) {
|
|
1033
1072
|
return { valid: false, offline: true, reason_code: "signature_invalid" };
|
|
1034
1073
|
}
|
|
1035
|
-
const
|
|
1074
|
+
const payload = signed.payload || {};
|
|
1075
|
+
const cached = this.cache.getLicense();
|
|
1076
|
+
if (!cached || !constantTimeEqual(payload.lic_k || "", cached.license_key || "")) {
|
|
1077
|
+
return { valid: false, offline: true, reason_code: "license_mismatch" };
|
|
1078
|
+
}
|
|
1079
|
+
const now = Date.now();
|
|
1080
|
+
const expAt = payload.exp_at ? Date.parse(payload.exp_at) : null;
|
|
1081
|
+
if (expAt && expAt < now) {
|
|
1082
|
+
return { valid: false, offline: true, reason_code: "expired" };
|
|
1083
|
+
}
|
|
1084
|
+
const lastSeen = this.cache.getLastSeenTimestamp();
|
|
1085
|
+
if (lastSeen && now + this.config.maxClockSkewMs < lastSeen) {
|
|
1086
|
+
return { valid: false, offline: true, reason_code: "clock_tamper" };
|
|
1087
|
+
}
|
|
1088
|
+
const active = parseActiveEntitlements(payload);
|
|
1036
1089
|
return {
|
|
1037
1090
|
valid: true,
|
|
1038
1091
|
offline: true,
|
|
@@ -97,6 +97,18 @@ export class LicenseSeatSDK {
|
|
|
97
97
|
* @private
|
|
98
98
|
*/
|
|
99
99
|
private lastOfflineValidation;
|
|
100
|
+
/**
|
|
101
|
+
* Flag to prevent concurrent syncOfflineAssets calls
|
|
102
|
+
* @type {boolean}
|
|
103
|
+
* @private
|
|
104
|
+
*/
|
|
105
|
+
private syncingOfflineAssets;
|
|
106
|
+
/**
|
|
107
|
+
* Flag indicating if SDK has been destroyed
|
|
108
|
+
* @type {boolean}
|
|
109
|
+
* @private
|
|
110
|
+
*/
|
|
111
|
+
private destroyed;
|
|
100
112
|
/**
|
|
101
113
|
* Initialize the SDK
|
|
102
114
|
* Loads cached license and starts auto-validation if configured.
|
|
@@ -182,6 +194,13 @@ export class LicenseSeatSDK {
|
|
|
182
194
|
* @returns {void}
|
|
183
195
|
*/
|
|
184
196
|
reset(): void;
|
|
197
|
+
/**
|
|
198
|
+
* Destroy the SDK instance and release all resources
|
|
199
|
+
* Call this when you no longer need the SDK to prevent memory leaks.
|
|
200
|
+
* After calling destroy(), the SDK instance should not be used.
|
|
201
|
+
* @returns {void}
|
|
202
|
+
*/
|
|
203
|
+
destroy(): void;
|
|
185
204
|
/**
|
|
186
205
|
* Subscribe to an event
|
|
187
206
|
* @param {string} event - Event name
|
|
@@ -231,6 +250,7 @@ export class LicenseSeatSDK {
|
|
|
231
250
|
private stopConnectivityPolling;
|
|
232
251
|
/**
|
|
233
252
|
* Fetch and cache offline license and public key
|
|
253
|
+
* Uses a lock to prevent concurrent calls from causing race conditions
|
|
234
254
|
* @returns {Promise<void>}
|
|
235
255
|
* @private
|
|
236
256
|
*/
|
|
@@ -249,6 +269,7 @@ export class LicenseSeatSDK {
|
|
|
249
269
|
private verifyCachedOffline;
|
|
250
270
|
/**
|
|
251
271
|
* Quick offline verification using only local data (no network)
|
|
272
|
+
* Performs signature verification plus basic validity checks (expiry, license key match)
|
|
252
273
|
* @returns {Promise<import('./types.js').ValidationResult|null>}
|
|
253
274
|
* @private
|
|
254
275
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LicenseSeat.d.ts","sourceRoot":"","sources":["../../src/LicenseSeat.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"LicenseSeat.d.ts","sourceRoot":"","sources":["../../src/LicenseSeat.js"],"names":[],"mappings":"AAwrCA;;;;GAIG;AACH,2CAHW,OAAO,YAAY,EAAE,iBAAiB,GACpC,cAAc,CAO1B;AAED;;;;;GAKG;AACH,kCAJW,OAAO,YAAY,EAAE,iBAAiB,UACtC,OAAO,GACL,cAAc,CAW1B;AAED;;;GAGG;AACH,uCAFa,IAAI,CAOhB;AAxqCD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH;IACE;;;OAGG;IACH,qBAFW,OAAO,YAAY,EAAE,iBAAiB,EA+FhD;IA5FC;;;OAGG;IACH,QAFU,OAAO,YAAY,EAAE,iBAAiB,CAK/C;IAED;;;;OAIG;IACH,uBAAwB;IAExB;;;;OAIG;IACH,wBAA2B;IAE3B;;;;OAIG;IACH,cAAwD;IAExD;;;;OAIG;IACH,eAAkB;IAElB;;;;OAIG;IACH,8BAAiC;IAEjC;;;;OAIG;IACH,0BAA6B;IAE7B;;;;OAIG;IACH,4BAA+B;IAE/B;;;;OAIG;IACH,8BAAiC;IAEjC;;;;OAIG;IACH,6BAAiC;IAEjC;;;;OAIG;IACH,kBAAsB;IAiBxB;;;;;OAKG;IACH,cAFa,IAAI,CAkDhB;IAED;;;;;;OAMG;IACH,qBALW,MAAM,YACN,OAAO,YAAY,EAAE,iBAAiB,GACpC,OAAO,CAAC,OAAO,YAAY,EAAE,aAAa,CAAC,CA4CvD;IAED;;;;;OAKG;IACH,cAJa,OAAO,KAAQ,CA+B3B;IAED;;;;;;OAMG;IACH,4BALW,MAAM,YACN,OAAO,YAAY,EAAE,iBAAiB,GACpC,OAAO,CAAC,OAAO,YAAY,EAAE,gBAAgB,CAAC,CAyF1D;IAED;;;;OAIG;IACH,iCAHW,MAAM,GACJ,OAAO,YAAY,EAAE,sBAAsB,CA6BvD;IAED;;;;;;OAMG;IACH,+BAHW,MAAM,GACJ,OAAO,CAInB;IAED;;;;;OAKG;IACH,qBAJa,OAAO,CAAC,OAAO,YAAY,EAAE,oBAAoB,CAAC,CAmC9D;IAED;;;;;OAKG;IACH,oBAJW,MAAM,GACJ,OAAO,CAAC,MAAM,CAAC,CAwB3B;IAED;;;;;;;OAOG;IACH,wCANW,OAAO,YAAY,EAAE,oBAAoB,gBACzC,MAAM,GACJ,OAAO,CAAC,OAAO,CAAC,CAuD5B;IAED;;;OAGG;IACH,aAFa,OAAO,YAAY,EAAE,aAAa,CA6C9C;IAED;;;;;;OAMG;IACH,YAJa,OAAO,KAAQ,CAoB3B;IAED;;;OAGG;IACH,SAFa,IAAI,CAahB;IAED;;;;;OAKG;IACH,WAFa,IAAI,CAehB;IAMD;;;;;OAKG;IACH,UAJW,MAAM,YACN,OAAO,YAAY,EAAE,aAAa,GAChC,OAAO,YAAY,EAAE,gBAAgB,CAQjD;IAED;;;;;OAKG;IACH,WAJW,MAAM,YACN,OAAO,YAAY,EAAE,aAAa,GAChC,IAAI,CAQhB;IAED;;;;;;OAMG;IACH,aAWC;IAMD;;;;;OAKG;IACH,4BAqBC;IAED;;;;OAIG;IACH,2BAMC;IAED;;;;OAIG;IACH,iCA4BC;IAED;;;;OAIG;IACH,gCAKC;IAMD;;;;;OAKG;IACH,0BAwCC;IAED;;;;OAIG;IACH,+BAMC;IAED;;;;OAIG;IACH,4BA0EC;IAED;;;;;OAKG;IACH,sCA+CC;IAMD;;;;;;;;;;OAUG;IACH,gBAiFC;IAED;;;;;OAKG;IACH,yBAsBC;IAMD;;;OAGG;IACH,gBAFa,MAAM,CAIlB;IAED;;;;;OAKG;IACH,YAIC;CACF"}
|
package/dist/types/types.d.ts
CHANGED
|
@@ -141,20 +141,13 @@ export type CachedLicense = {
|
|
|
141
141
|
};
|
|
142
142
|
/**
|
|
143
143
|
* Entitlement object
|
|
144
|
+
* Note: API returns only key, expires_at, and metadata. Name/description are not provided.
|
|
144
145
|
*/
|
|
145
146
|
export type Entitlement = {
|
|
146
147
|
/**
|
|
147
148
|
* - Unique entitlement key
|
|
148
149
|
*/
|
|
149
150
|
key: string;
|
|
150
|
-
/**
|
|
151
|
-
* - Human-readable name
|
|
152
|
-
*/
|
|
153
|
-
name: string | null;
|
|
154
|
-
/**
|
|
155
|
-
* - Description of the entitlement
|
|
156
|
-
*/
|
|
157
|
-
description: string | null;
|
|
158
151
|
/**
|
|
159
152
|
* - ISO8601 expiration timestamp
|
|
160
153
|
*/
|
|
@@ -251,26 +244,50 @@ export type LicenseStatus = {
|
|
|
251
244
|
* Offline license payload
|
|
252
245
|
*/
|
|
253
246
|
export type OfflineLicensePayload = {
|
|
247
|
+
/**
|
|
248
|
+
* - Payload version (currently 1)
|
|
249
|
+
*/
|
|
250
|
+
v?: number;
|
|
254
251
|
/**
|
|
255
252
|
* - License key
|
|
256
253
|
*/
|
|
257
254
|
lic_k?: string;
|
|
258
255
|
/**
|
|
259
|
-
* -
|
|
256
|
+
* - Product slug
|
|
257
|
+
*/
|
|
258
|
+
prod_s?: string;
|
|
259
|
+
/**
|
|
260
|
+
* - License plan key
|
|
260
261
|
*/
|
|
261
|
-
|
|
262
|
+
plan_k?: string;
|
|
262
263
|
/**
|
|
263
|
-
* -
|
|
264
|
+
* - ISO8601 expiration timestamp (null for perpetual)
|
|
265
|
+
*/
|
|
266
|
+
exp_at?: string | null;
|
|
267
|
+
/**
|
|
268
|
+
* - Seat limit (null for unlimited)
|
|
269
|
+
*/
|
|
270
|
+
sl?: number | null;
|
|
271
|
+
/**
|
|
272
|
+
* - Key ID for public key lookup
|
|
264
273
|
*/
|
|
265
274
|
kid?: string;
|
|
266
275
|
/**
|
|
267
276
|
* - Active entitlements
|
|
268
277
|
*/
|
|
269
|
-
active_ents?: Array<
|
|
278
|
+
active_ents?: Array<{
|
|
279
|
+
key: string;
|
|
280
|
+
expires_at: string | null;
|
|
281
|
+
metadata: any | null;
|
|
282
|
+
}>;
|
|
270
283
|
/**
|
|
271
284
|
* - Active entitlements (alternative key)
|
|
272
285
|
*/
|
|
273
|
-
active_entitlements?: Array<
|
|
286
|
+
active_entitlements?: Array<{
|
|
287
|
+
key: string;
|
|
288
|
+
expires_at: string | null;
|
|
289
|
+
metadata: any | null;
|
|
290
|
+
}>;
|
|
274
291
|
/**
|
|
275
292
|
* - Additional metadata
|
|
276
293
|
*/
|
|
@@ -310,7 +327,11 @@ export type APIErrorData = {
|
|
|
310
327
|
*/
|
|
311
328
|
error?: string;
|
|
312
329
|
/**
|
|
313
|
-
* -
|
|
330
|
+
* - Machine-readable reason code (e.g., "license_not_found", "expired", "revoked")
|
|
331
|
+
*/
|
|
332
|
+
reason_code?: string;
|
|
333
|
+
/**
|
|
334
|
+
* - Legacy error code (deprecated, use reason_code)
|
|
314
335
|
*/
|
|
315
336
|
code?: string;
|
|
316
337
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.js"],"names":[],"mappings":";;;;;;;iBASc,MAAM;;;;aACN,MAAM;;;;oBACN,MAAM;;;;2BACN,MAAM;;;;6BACN,MAAM;;;;iBACN,MAAM;;;;iBACN,MAAM;;;;YACN,OAAO;;;;oCACP,MAAM;;;;6BACN,OAAO;;;;qBACP,MAAM;;;;qBACN,MAAM;;;;qBACN,OAAO;;;;;;;;;uBAMP,MAAM;;;;0BACN,MAAM;;;;;;;;;;;;;uBAON,MAAM;;;;kBACN,MAAM;;;;;;;;;QAMN,MAAM;;;;iBACN,MAAM;;;;uBACN,MAAM;;;;kBACN,MAAM;;;;;;;;;;;;;iBAON,MAAM;;;;uBACN,MAAM;;;;iBACN,kBAAkB;;;;kBAClB,MAAM;;;;oBACN,MAAM;;;;iBACN,gBAAgB
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.js"],"names":[],"mappings":";;;;;;;iBASc,MAAM;;;;aACN,MAAM;;;;oBACN,MAAM;;;;2BACN,MAAM;;;;6BACN,MAAM;;;;iBACN,MAAM;;;;iBACN,MAAM;;;;YACN,OAAO;;;;oCACP,MAAM;;;;6BACN,OAAO;;;;qBACP,MAAM;;;;qBACN,MAAM;;;;qBACN,OAAO;;;;;;;;;uBAMP,MAAM;;;;0BACN,MAAM;;;;;;;;;;;;;uBAON,MAAM;;;;kBACN,MAAM;;;;;;;;;QAMN,MAAM;;;;iBACN,MAAM;;;;uBACN,MAAM;;;;kBACN,MAAM;;;;;;;;;;;;;iBAON,MAAM;;;;uBACN,MAAM;;;;iBACN,kBAAkB;;;;kBAClB,MAAM;;;;oBACN,MAAM;;;;iBACN,gBAAgB;;;;;;;;;;SAOhB,MAAM;;;;gBACN,MAAM,GAAC,IAAI;;;;cACX,MAAO,IAAI;;;;;;;;;WAMX,OAAO;;;;cACP,OAAO;;;;aACP,MAAM;;;;kBACN,MAAM;;;;0BACN,WAAW,EAAE;;;;iBACb,OAAO;;;;;;;;;YAMP,OAAO;;;;aACP,MAAM;;;;iBACN,MAAM;;;;kBACN,WAAW;;;;;;;;;YAMX,MAAM;;;;cACN,MAAM;;;;cACN,MAAM;;;;aACN,MAAM;;;;mBACN,MAAM;;;;qBACN,MAAM;;;;mBACN,WAAW,EAAE;;;;;;;;;QAMb,MAAM;;;;YACN,MAAM;;;;aACN,MAAM;;;;aACN,MAAM;;;;aACN,MAAM,GAAC,IAAI;;;;SACX,MAAM,GAAC,IAAI;;;;UACX,MAAM;;;;kBACN,KAAK,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,GAAC,IAAI,CAAC;QAAC,QAAQ,EAAE,MAAO,IAAI,CAAA;KAAC,CAAC;;;;0BACpE,KAAK,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,GAAC,IAAI,CAAC;QAAC,QAAQ,EAAE,MAAO,IAAI,CAAA;KAAC,CAAC;;;;;;;;;;;;;aAOpE,qBAAqB;;;;oBACrB,MAAM;;;;UACN,MAAM;;;;;mCAMT,GAAC,KACC,IAAI;;;;qCAMJ,IAAI;;;;;;;;YAMH,MAAM;;;;kBACN,MAAM;;;;WACN,MAAM"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/utils.js"],"names":[],"mappings":"AAQA;;;;GAIG;AACH,wDAFa,OAAO,YAAY,EAAE,WAAW,EAAE,
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/utils.js"],"names":[],"mappings":"AAQA;;;;GAIG;AACH,wDAFa,OAAO,YAAY,EAAE,WAAW,EAAE,CAS9C;AAED;;;;;GAKG;AACH,sCAJW,MAAM,MACN,MAAM,GACJ,OAAO,CASnB;AAED;;;;;GAKG;AACH,kDAFa,MAAM,CAuBlB;AAED;;;;GAIG;AACH,iDAHW,MAAM,GACJ,UAAU,CAatB;AAED;;;;GAIG;AACH,8BAHW,MAAM,GACJ,MAAM,CAUlB;AAED;;;GAGG;AACH,wCAFa,MAAM,CAalB;AAED;;;GAGG;AACH,oCAFa,MAAM,CAgBlB;AAED;;;;GAIG;AACH,0BAHW,MAAM,GACJ,OAAO,CAAC,IAAI,CAAC,CAIzB;AAED;;;GAGG;AACH,gCAFa,MAAM,CAMlB"}
|
package/package.json
CHANGED
package/src/LicenseSeat.js
CHANGED
|
@@ -37,7 +37,7 @@ import {
|
|
|
37
37
|
* @type {import('./types.js').LicenseSeatConfig}
|
|
38
38
|
*/
|
|
39
39
|
const DEFAULT_CONFIG = {
|
|
40
|
-
apiBaseUrl: "https://
|
|
40
|
+
apiBaseUrl: "https://licenseseat.com/api",
|
|
41
41
|
storagePrefix: "licenseseat_",
|
|
42
42
|
autoValidateInterval: 3600000, // 1 hour
|
|
43
43
|
networkRecheckInterval: 30000, // 30 seconds
|
|
@@ -145,6 +145,20 @@ export class LicenseSeatSDK {
|
|
|
145
145
|
*/
|
|
146
146
|
this.lastOfflineValidation = null;
|
|
147
147
|
|
|
148
|
+
/**
|
|
149
|
+
* Flag to prevent concurrent syncOfflineAssets calls
|
|
150
|
+
* @type {boolean}
|
|
151
|
+
* @private
|
|
152
|
+
*/
|
|
153
|
+
this.syncingOfflineAssets = false;
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Flag indicating if SDK has been destroyed
|
|
157
|
+
* @type {boolean}
|
|
158
|
+
* @private
|
|
159
|
+
*/
|
|
160
|
+
this.destroyed = false;
|
|
161
|
+
|
|
148
162
|
// Enable synchronous SHA512 for noble-ed25519
|
|
149
163
|
if (ed && ed.etc && sha512) {
|
|
150
164
|
ed.etc.sha512Sync = (...m) => sha512(ed.etc.concatBytes(...m));
|
|
@@ -312,7 +326,7 @@ export class LicenseSeatSDK {
|
|
|
312
326
|
try {
|
|
313
327
|
this.emit("validation:start", { licenseKey });
|
|
314
328
|
|
|
315
|
-
const
|
|
329
|
+
const rawResponse = await this.apiCall("/licenses/validate", {
|
|
316
330
|
method: "POST",
|
|
317
331
|
body: {
|
|
318
332
|
license_key: licenseKey,
|
|
@@ -321,6 +335,13 @@ export class LicenseSeatSDK {
|
|
|
321
335
|
},
|
|
322
336
|
});
|
|
323
337
|
|
|
338
|
+
// Normalize response: API returns { valid, license: { active_entitlements, ... } }
|
|
339
|
+
// SDK expects flat structure { valid, active_entitlements, ... }
|
|
340
|
+
const response = {
|
|
341
|
+
valid: rawResponse.valid,
|
|
342
|
+
...(rawResponse.license || {}),
|
|
343
|
+
};
|
|
344
|
+
|
|
324
345
|
// Preserve cached entitlements if server response omits them
|
|
325
346
|
const cachedLicense = this.cache.getLicense();
|
|
326
347
|
if (
|
|
@@ -643,11 +664,38 @@ export class LicenseSeatSDK {
|
|
|
643
664
|
*/
|
|
644
665
|
reset() {
|
|
645
666
|
this.stopAutoValidation();
|
|
667
|
+
this.stopConnectivityPolling();
|
|
668
|
+
if (this.offlineRefreshTimer) {
|
|
669
|
+
clearInterval(this.offlineRefreshTimer);
|
|
670
|
+
this.offlineRefreshTimer = null;
|
|
671
|
+
}
|
|
646
672
|
this.cache.clear();
|
|
647
673
|
this.lastOfflineValidation = null;
|
|
674
|
+
this.currentAutoLicenseKey = null;
|
|
648
675
|
this.emit("sdk:reset");
|
|
649
676
|
}
|
|
650
677
|
|
|
678
|
+
/**
|
|
679
|
+
* Destroy the SDK instance and release all resources
|
|
680
|
+
* Call this when you no longer need the SDK to prevent memory leaks.
|
|
681
|
+
* After calling destroy(), the SDK instance should not be used.
|
|
682
|
+
* @returns {void}
|
|
683
|
+
*/
|
|
684
|
+
destroy() {
|
|
685
|
+
this.destroyed = true;
|
|
686
|
+
this.stopAutoValidation();
|
|
687
|
+
this.stopConnectivityPolling();
|
|
688
|
+
if (this.offlineRefreshTimer) {
|
|
689
|
+
clearInterval(this.offlineRefreshTimer);
|
|
690
|
+
this.offlineRefreshTimer = null;
|
|
691
|
+
}
|
|
692
|
+
this.eventListeners = {};
|
|
693
|
+
this.cache.clear();
|
|
694
|
+
this.lastOfflineValidation = null;
|
|
695
|
+
this.currentAutoLicenseKey = null;
|
|
696
|
+
this.emit("sdk:destroyed");
|
|
697
|
+
}
|
|
698
|
+
|
|
651
699
|
// ============================================================
|
|
652
700
|
// Event Handling
|
|
653
701
|
// ============================================================
|
|
@@ -799,10 +847,18 @@ export class LicenseSeatSDK {
|
|
|
799
847
|
|
|
800
848
|
/**
|
|
801
849
|
* Fetch and cache offline license and public key
|
|
850
|
+
* Uses a lock to prevent concurrent calls from causing race conditions
|
|
802
851
|
* @returns {Promise<void>}
|
|
803
852
|
* @private
|
|
804
853
|
*/
|
|
805
854
|
async syncOfflineAssets() {
|
|
855
|
+
// Prevent concurrent syncs
|
|
856
|
+
if (this.syncingOfflineAssets || this.destroyed) {
|
|
857
|
+
this.log("Skipping syncOfflineAssets: already syncing or destroyed");
|
|
858
|
+
return;
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
this.syncingOfflineAssets = true;
|
|
806
862
|
try {
|
|
807
863
|
const offline = await this.getOfflineLicense();
|
|
808
864
|
this.cache.setOfflineLicense(offline);
|
|
@@ -832,6 +888,8 @@ export class LicenseSeatSDK {
|
|
|
832
888
|
}
|
|
833
889
|
} catch (err) {
|
|
834
890
|
this.log("Failed to sync offline assets:", err);
|
|
891
|
+
} finally {
|
|
892
|
+
this.syncingOfflineAssets = false;
|
|
835
893
|
}
|
|
836
894
|
}
|
|
837
895
|
|
|
@@ -931,6 +989,7 @@ export class LicenseSeatSDK {
|
|
|
931
989
|
|
|
932
990
|
/**
|
|
933
991
|
* Quick offline verification using only local data (no network)
|
|
992
|
+
* Performs signature verification plus basic validity checks (expiry, license key match)
|
|
934
993
|
* @returns {Promise<import('./types.js').ValidationResult|null>}
|
|
935
994
|
* @private
|
|
936
995
|
*/
|
|
@@ -947,7 +1006,32 @@ export class LicenseSeatSDK {
|
|
|
947
1006
|
return { valid: false, offline: true, reason_code: "signature_invalid" };
|
|
948
1007
|
}
|
|
949
1008
|
|
|
950
|
-
|
|
1009
|
+
/** @type {import('./types.js').OfflineLicensePayload} */
|
|
1010
|
+
const payload = signed.payload || {};
|
|
1011
|
+
const cached = this.cache.getLicense();
|
|
1012
|
+
|
|
1013
|
+
// License key match check
|
|
1014
|
+
if (
|
|
1015
|
+
!cached ||
|
|
1016
|
+
!constantTimeEqual(payload.lic_k || "", cached.license_key || "")
|
|
1017
|
+
) {
|
|
1018
|
+
return { valid: false, offline: true, reason_code: "license_mismatch" };
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
// Expiry check
|
|
1022
|
+
const now = Date.now();
|
|
1023
|
+
const expAt = payload.exp_at ? Date.parse(payload.exp_at) : null;
|
|
1024
|
+
if (expAt && expAt < now) {
|
|
1025
|
+
return { valid: false, offline: true, reason_code: "expired" };
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
// Clock tamper detection
|
|
1029
|
+
const lastSeen = this.cache.getLastSeenTimestamp();
|
|
1030
|
+
if (lastSeen && now + this.config.maxClockSkewMs < lastSeen) {
|
|
1031
|
+
return { valid: false, offline: true, reason_code: "clock_tamper" };
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
const active = parseActiveEntitlements(payload);
|
|
951
1035
|
return {
|
|
952
1036
|
valid: true,
|
|
953
1037
|
offline: true,
|
package/src/types.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
/**
|
|
8
8
|
* SDK Configuration options
|
|
9
9
|
* @typedef {Object} LicenseSeatConfig
|
|
10
|
-
* @property {string} [apiBaseUrl="https://
|
|
10
|
+
* @property {string} [apiBaseUrl="https://licenseseat.com/api"] - Base URL for the LicenseSeat API
|
|
11
11
|
* @property {string} [apiKey] - API key for authentication (required for most operations)
|
|
12
12
|
* @property {string} [storagePrefix="licenseseat_"] - Prefix for localStorage keys
|
|
13
13
|
* @property {number} [autoValidateInterval=3600000] - Interval in ms for automatic license validation (default: 1 hour)
|
|
@@ -60,10 +60,9 @@
|
|
|
60
60
|
|
|
61
61
|
/**
|
|
62
62
|
* Entitlement object
|
|
63
|
+
* Note: API returns only key, expires_at, and metadata. Name/description are not provided.
|
|
63
64
|
* @typedef {Object} Entitlement
|
|
64
65
|
* @property {string} key - Unique entitlement key
|
|
65
|
-
* @property {string|null} name - Human-readable name
|
|
66
|
-
* @property {string|null} description - Description of the entitlement
|
|
67
66
|
* @property {string|null} expires_at - ISO8601 expiration timestamp
|
|
68
67
|
* @property {Object|null} metadata - Additional metadata
|
|
69
68
|
*/
|
|
@@ -103,11 +102,15 @@
|
|
|
103
102
|
/**
|
|
104
103
|
* Offline license payload
|
|
105
104
|
* @typedef {Object} OfflineLicensePayload
|
|
105
|
+
* @property {number} [v] - Payload version (currently 1)
|
|
106
106
|
* @property {string} [lic_k] - License key
|
|
107
|
-
* @property {string} [
|
|
108
|
-
* @property {string} [
|
|
109
|
-
* @property {
|
|
110
|
-
* @property {
|
|
107
|
+
* @property {string} [prod_s] - Product slug
|
|
108
|
+
* @property {string} [plan_k] - License plan key
|
|
109
|
+
* @property {string|null} [exp_at] - ISO8601 expiration timestamp (null for perpetual)
|
|
110
|
+
* @property {number|null} [sl] - Seat limit (null for unlimited)
|
|
111
|
+
* @property {string} [kid] - Key ID for public key lookup
|
|
112
|
+
* @property {Array<{key: string, expires_at: string|null, metadata: Object|null}>} [active_ents] - Active entitlements
|
|
113
|
+
* @property {Array<{key: string, expires_at: string|null, metadata: Object|null}>} [active_entitlements] - Active entitlements (alternative key)
|
|
111
114
|
* @property {Object} [metadata] - Additional metadata
|
|
112
115
|
*/
|
|
113
116
|
|
|
@@ -136,7 +139,8 @@
|
|
|
136
139
|
* API Error data
|
|
137
140
|
* @typedef {Object} APIErrorData
|
|
138
141
|
* @property {string} [error] - Error message
|
|
139
|
-
* @property {string} [
|
|
142
|
+
* @property {string} [reason_code] - Machine-readable reason code (e.g., "license_not_found", "expired", "revoked")
|
|
143
|
+
* @property {string} [code] - Legacy error code (deprecated, use reason_code)
|
|
140
144
|
* @property {Object} [details] - Additional error details
|
|
141
145
|
*/
|
|
142
146
|
|
package/src/utils.js
CHANGED
|
@@ -15,8 +15,6 @@ export function parseActiveEntitlements(payload = {}) {
|
|
|
15
15
|
const raw = payload.active_ents || payload.active_entitlements || [];
|
|
16
16
|
return raw.map((e) => ({
|
|
17
17
|
key: e.key,
|
|
18
|
-
name: e.name ?? null,
|
|
19
|
-
description: e.description ?? null,
|
|
20
18
|
expires_at: e.expires_at ?? null,
|
|
21
19
|
metadata: e.metadata ?? null,
|
|
22
20
|
}));
|