@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
|
@@ -16,6 +16,11 @@ export function configure(config: import("./types.js").LicenseSeatConfig, force?
|
|
|
16
16
|
* @returns {void}
|
|
17
17
|
*/
|
|
18
18
|
export function resetSharedInstance(): void;
|
|
19
|
+
/**
|
|
20
|
+
* SDK version constant
|
|
21
|
+
* @type {string}
|
|
22
|
+
*/
|
|
23
|
+
export const SDK_VERSION: string;
|
|
19
24
|
/**
|
|
20
25
|
* LicenseSeat SDK Main Class
|
|
21
26
|
*
|
|
@@ -61,6 +66,12 @@ export class LicenseSeatSDK {
|
|
|
61
66
|
* @private
|
|
62
67
|
*/
|
|
63
68
|
private validationTimer;
|
|
69
|
+
/**
|
|
70
|
+
* Heartbeat timer ID (separate from auto-validation)
|
|
71
|
+
* @type {ReturnType<typeof setInterval>|null}
|
|
72
|
+
* @private
|
|
73
|
+
*/
|
|
74
|
+
private heartbeatTimer;
|
|
64
75
|
/**
|
|
65
76
|
* License cache manager
|
|
66
77
|
* @type {LicenseCache}
|
|
@@ -192,13 +203,26 @@ export class LicenseSeatSDK {
|
|
|
192
203
|
*/
|
|
193
204
|
getStatus(): import("./types.js").LicenseStatus;
|
|
194
205
|
/**
|
|
195
|
-
* Test
|
|
196
|
-
*
|
|
197
|
-
*
|
|
198
|
-
* @
|
|
199
|
-
* @throws {
|
|
206
|
+
* Test API connectivity
|
|
207
|
+
* Makes a request to the health endpoint to verify connectivity.
|
|
208
|
+
* Note: To fully verify API key validity, attempt an actual operation like activate() or validateLicense().
|
|
209
|
+
* @returns {Promise<{authenticated: boolean, healthy: boolean, api_version: string}>}
|
|
210
|
+
* @throws {ConfigurationError} If API key is not configured
|
|
211
|
+
* @throws {APIError} If the health check fails
|
|
212
|
+
*/
|
|
213
|
+
testAuth(): Promise<{
|
|
214
|
+
authenticated: boolean;
|
|
215
|
+
healthy: boolean;
|
|
216
|
+
api_version: string;
|
|
217
|
+
}>;
|
|
218
|
+
/**
|
|
219
|
+
* Send a heartbeat for the current license.
|
|
220
|
+
* Heartbeats let the server know the device is still active.
|
|
221
|
+
* @returns {Promise<Object|undefined>} Heartbeat response, or undefined if no active license
|
|
222
|
+
* @throws {ConfigurationError} When productSlug is not configured
|
|
223
|
+
* @throws {APIError} When the API request fails
|
|
200
224
|
*/
|
|
201
|
-
|
|
225
|
+
heartbeat(): Promise<any | undefined>;
|
|
202
226
|
/**
|
|
203
227
|
* Clear all data and reset SDK state
|
|
204
228
|
* @returns {void}
|
|
@@ -246,6 +270,19 @@ export class LicenseSeatSDK {
|
|
|
246
270
|
* @private
|
|
247
271
|
*/
|
|
248
272
|
private stopAutoValidation;
|
|
273
|
+
/**
|
|
274
|
+
* Start separate heartbeat timer
|
|
275
|
+
* Sends periodic heartbeats between auto-validation cycles.
|
|
276
|
+
* @returns {void}
|
|
277
|
+
* @private
|
|
278
|
+
*/
|
|
279
|
+
private startHeartbeat;
|
|
280
|
+
/**
|
|
281
|
+
* Stop the separate heartbeat timer
|
|
282
|
+
* @returns {void}
|
|
283
|
+
* @private
|
|
284
|
+
*/
|
|
285
|
+
private stopHeartbeat;
|
|
249
286
|
/**
|
|
250
287
|
* Start connectivity polling (when offline)
|
|
251
288
|
* @returns {void}
|
|
@@ -259,12 +296,12 @@ export class LicenseSeatSDK {
|
|
|
259
296
|
*/
|
|
260
297
|
private stopConnectivityPolling;
|
|
261
298
|
/**
|
|
262
|
-
*
|
|
263
|
-
*
|
|
299
|
+
* Download and cache the offline token and its corresponding public signing key.
|
|
300
|
+
* Emits `offlineToken:ready` on success. Safe to call multiple times — concurrent
|
|
301
|
+
* calls are deduplicated automatically.
|
|
264
302
|
* @returns {Promise<void>}
|
|
265
|
-
* @private
|
|
266
303
|
*/
|
|
267
|
-
|
|
304
|
+
syncOfflineAssets(): Promise<void>;
|
|
268
305
|
/**
|
|
269
306
|
* Schedule periodic offline license refresh
|
|
270
307
|
* @returns {void}
|
|
@@ -272,11 +309,12 @@ export class LicenseSeatSDK {
|
|
|
272
309
|
*/
|
|
273
310
|
private scheduleOfflineRefresh;
|
|
274
311
|
/**
|
|
275
|
-
* Verify cached offline token
|
|
312
|
+
* Verify the cached offline token and return a validation result.
|
|
313
|
+
* Use this to validate the license when the device is offline.
|
|
314
|
+
* The offline token must have been previously downloaded via {@link syncOfflineAssets}.
|
|
276
315
|
* @returns {Promise<import('./types.js').ValidationResult>}
|
|
277
|
-
* @private
|
|
278
316
|
*/
|
|
279
|
-
|
|
317
|
+
verifyCachedOffline(): Promise<import("./types.js").ValidationResult>;
|
|
280
318
|
/**
|
|
281
319
|
* Quick offline verification using only local data (no network)
|
|
282
320
|
* Performs signature verification plus basic validity checks (expiry, license key match)
|
|
@@ -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":"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"}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
export { LicenseCache } from "./cache.js";
|
|
2
|
+
export { collectTelemetry } from "./telemetry.js";
|
|
2
3
|
export default LicenseSeatSDK;
|
|
3
4
|
import { LicenseSeatSDK } from "./LicenseSeat.js";
|
|
4
|
-
export { LicenseSeatSDK, getSharedInstance, configure, resetSharedInstance } from "./LicenseSeat.js";
|
|
5
|
+
export { LicenseSeatSDK, SDK_VERSION, getSharedInstance, configure, resetSharedInstance } from "./LicenseSeat.js";
|
|
5
6
|
export { APIError, ConfigurationError, LicenseError, CryptoError } from "./errors.js";
|
|
6
7
|
export { parseActiveEntitlements, constantTimeEqual, canonicalJsonStringify, base64UrlDecode, generateDeviceId, getCsrfToken } from "./utils.js";
|
|
7
8
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.js"],"names":[],"mappings":";;;+BAiE+B,kBAAkB"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Collect telemetry data for the current environment.
|
|
3
|
+
* Returns a plain object with snake_case keys matching the server schema.
|
|
4
|
+
* Null/undefined values are filtered out.
|
|
5
|
+
*
|
|
6
|
+
* @param {string} sdkVersion - The SDK version string
|
|
7
|
+
* @param {Object} [options] - Additional options
|
|
8
|
+
* @param {string} [options.appVersion] - User-provided app version
|
|
9
|
+
* @param {string} [options.appBuild] - User-provided app build
|
|
10
|
+
* @returns {Object} Telemetry data object
|
|
11
|
+
*/
|
|
12
|
+
export function collectTelemetry(sdkVersion: string, options?: {
|
|
13
|
+
appVersion?: string;
|
|
14
|
+
appBuild?: string;
|
|
15
|
+
}): any;
|
|
16
|
+
//# sourceMappingURL=telemetry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"telemetry.d.ts","sourceRoot":"","sources":["../../src/telemetry.js"],"names":[],"mappings":"AAwdA;;;;;;;;;;GAUG;AACH,6CANW,MAAM,YAEd;IAAyB,UAAU,GAA3B,MAAM;IACW,QAAQ,GAAzB,MAAM;CACd,OAoCF"}
|
package/dist/types/types.d.ts
CHANGED
|
@@ -58,6 +58,22 @@ export type LicenseSeatConfig = {
|
|
|
58
58
|
* - Automatically initialize and validate cached license on construction
|
|
59
59
|
*/
|
|
60
60
|
autoInitialize?: boolean;
|
|
61
|
+
/**
|
|
62
|
+
* - Enable telemetry collection on POST requests (set false for GDPR compliance)
|
|
63
|
+
*/
|
|
64
|
+
telemetryEnabled?: boolean;
|
|
65
|
+
/**
|
|
66
|
+
* - Interval in ms between automatic heartbeats (default: 5 minutes, set 0 to disable)
|
|
67
|
+
*/
|
|
68
|
+
heartbeatInterval?: number;
|
|
69
|
+
/**
|
|
70
|
+
* - User-provided app version string, sent as app_version in telemetry
|
|
71
|
+
*/
|
|
72
|
+
appVersion?: string;
|
|
73
|
+
/**
|
|
74
|
+
* - User-provided app build identifier, sent as app_build in telemetry
|
|
75
|
+
*/
|
|
76
|
+
appBuild?: string;
|
|
61
77
|
};
|
|
62
78
|
/**
|
|
63
79
|
* License activation options
|
|
@@ -504,6 +520,23 @@ export type SigningKey = {
|
|
|
504
520
|
*/
|
|
505
521
|
status: string;
|
|
506
522
|
};
|
|
523
|
+
/**
|
|
524
|
+
* Heartbeat response from the API
|
|
525
|
+
*/
|
|
526
|
+
export type HeartbeatResponse = {
|
|
527
|
+
/**
|
|
528
|
+
* - Object type ("heartbeat")
|
|
529
|
+
*/
|
|
530
|
+
object: string;
|
|
531
|
+
/**
|
|
532
|
+
* - ISO8601 timestamp of when the heartbeat was received
|
|
533
|
+
*/
|
|
534
|
+
received_at: string;
|
|
535
|
+
/**
|
|
536
|
+
* - The license object
|
|
537
|
+
*/
|
|
538
|
+
license: LicenseObject;
|
|
539
|
+
};
|
|
507
540
|
/**
|
|
508
541
|
* Health check response
|
|
509
542
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.js"],"names":[],"mappings":";;;;;;;iBASc,MAAM;;;;kBACN,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;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.js"],"names":[],"mappings":";;;;;;;iBASc,MAAM;;;;kBACN,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;;;;uBACP,OAAO;;;;wBACP,MAAM;;;;iBACN,MAAM;;;;eACN,MAAM;;;;;;;;;eAMN,MAAM;;;;iBACN,MAAM;;;;;;;;;;;;;eAON,MAAM;;;;;;;;;YAMN,MAAM;;;;QACN,MAAM;;;;eACN,MAAM;;;;kBACN,MAAM;;;;iBACN,MAAM;;;;kBACN,MAAM;;;;qBACN,MAAM,GAAC,IAAI;;;;iBACX,MAAM;;;;;;;;aAEN,aAAa;;;;;;;;;YAMb,MAAM;;;;mBACN,MAAM;;;;oBACN,MAAM;;;;;;;;;SAMN,MAAM;;;;YACN,MAAM;;;;gBACN,MAAM;;;;iBACN,MAAM,GAAC,IAAI;;;;UACX,MAAM;;;;cACN,MAAM;;;;iBACN,MAAM;;;;kBACN,MAAM;;;;yBACN,WAAW,EAAE;;;;;;;;aAEb,WAAW;;;;;;;;;UAMX,MAAM;;;;UACN,MAAM;;;;;;;;;iBAMN,MAAM;;;;eACN,MAAM;;;;iBACN,kBAAkB;;;;kBAClB,MAAM;;;;oBACN,MAAM;;;;iBACN,gBAAgB;;;;;;;;;SAMhB,MAAM;;;;gBACN,MAAM,GAAC,IAAI;;;;cACX,MAAO,IAAI;;;;;;;;;WAMX,OAAO;;;;cACP,OAAO;;;;WACP,MAAM;;;;cACN,MAAM;;;;eACN,iBAAiB,EAAE;;;;cACnB,aAAa;;;;iBACb,kBAAkB;;;;0BAClB,WAAW,EAAE;;;;iBACb,OAAO;;;;;;;;;UAMP,MAAM;;;;aACN,MAAM;;;;;;;;;YAMN,OAAO;;;;aACP,MAAM;;;;iBACN,MAAM;;;;kBACN,WAAW;;;;;;;;;YAMX,MAAM;;;;cACN,MAAM;;;;cACN,MAAM;;;;aACN,MAAM;;;;mBACN,MAAM;;;;qBACN,MAAM;;;;mBACN,WAAW,EAAE;;;;;;;;;YAMb,MAAM;;;;WACN,mBAAmB;;;;eACnB,qBAAqB;;;;eACrB,MAAM;;;;;;;;;oBAMN,MAAM;;;;iBACN,MAAM;;;;kBACN,MAAM;;;;cACN,MAAM;;;;UACN,MAAM;;;;iBACN,MAAM,GAAC,IAAI;;;;gBACX,MAAM,GAAC,IAAI;;;;SACX,MAAM;;;;SACN,MAAM;;;;SACN,MAAM;;;;yBACN,MAAM,GAAC,IAAI;;;;SACX,MAAM;;;;kBACN,kBAAkB,EAAE;;;;;;;;;;;;;SAOpB,MAAM;;;;iBACN,MAAM,GAAC,IAAI;;;;;;;;;eAMX,MAAM;;;;YACN,MAAM;;;;WACN,MAAM;;;;;;;;;YAMN,MAAM;;;;YACN,MAAM;;;;eACN,MAAM;;;;gBACN,MAAM;;;;iBACN,MAAM;;;;YACN,MAAM;;;;;;;;;YAMN,MAAM;;;;iBACN,MAAM;;;;aACN,aAAa;;;;;;;;;YAMb,MAAM;;;;YACN,MAAM;;;;iBACN,MAAM;;;;eACN,MAAM;;;;;mCAMT,GAAC,KACC,IAAI;;;;qCAMJ,IAAI;;;;;;;;YAMH,cAAc;;;;WACd,MAAM;;;;cACN,MAAM;;;;;;;;;UAMN,MAAM;;;;aACN,MAAM"}
|
package/package.json
CHANGED
package/src/LicenseSeat.js
CHANGED
|
@@ -31,6 +31,13 @@ import {
|
|
|
31
31
|
sleep,
|
|
32
32
|
getCsrfToken,
|
|
33
33
|
} from "./utils.js";
|
|
34
|
+
import { collectTelemetry } from "./telemetry.js";
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* SDK version constant
|
|
38
|
+
* @type {string}
|
|
39
|
+
*/
|
|
40
|
+
export const SDK_VERSION = "0.4.1";
|
|
34
41
|
|
|
35
42
|
/**
|
|
36
43
|
* Default configuration values
|
|
@@ -41,6 +48,7 @@ const DEFAULT_CONFIG = {
|
|
|
41
48
|
productSlug: null, // Required: Product slug for API calls (e.g., "my-app")
|
|
42
49
|
storagePrefix: "licenseseat_",
|
|
43
50
|
autoValidateInterval: 3600000, // 1 hour
|
|
51
|
+
heartbeatInterval: 300000, // 5 minutes
|
|
44
52
|
networkRecheckInterval: 30000, // 30 seconds
|
|
45
53
|
maxRetries: 3,
|
|
46
54
|
retryDelay: 1000,
|
|
@@ -51,6 +59,9 @@ const DEFAULT_CONFIG = {
|
|
|
51
59
|
maxOfflineDays: 0, // 0 = disabled
|
|
52
60
|
maxClockSkewMs: 5 * 60 * 1000, // 5 minutes
|
|
53
61
|
autoInitialize: true,
|
|
62
|
+
telemetryEnabled: true, // Set false to disable telemetry (e.g. for GDPR compliance)
|
|
63
|
+
appVersion: null, // User-provided app version, sent as app_version in telemetry
|
|
64
|
+
appBuild: null, // User-provided app build, sent as app_build in telemetry
|
|
54
65
|
};
|
|
55
66
|
|
|
56
67
|
/**
|
|
@@ -104,6 +115,13 @@ export class LicenseSeatSDK {
|
|
|
104
115
|
*/
|
|
105
116
|
this.validationTimer = null;
|
|
106
117
|
|
|
118
|
+
/**
|
|
119
|
+
* Heartbeat timer ID (separate from auto-validation)
|
|
120
|
+
* @type {ReturnType<typeof setInterval>|null}
|
|
121
|
+
* @private
|
|
122
|
+
*/
|
|
123
|
+
this.heartbeatTimer = null;
|
|
124
|
+
|
|
107
125
|
/**
|
|
108
126
|
* License cache manager
|
|
109
127
|
* @type {LicenseCache}
|
|
@@ -205,9 +223,10 @@ export class LicenseSeatSDK {
|
|
|
205
223
|
.catch(() => {});
|
|
206
224
|
}
|
|
207
225
|
|
|
208
|
-
// Start auto-validation if API key is configured
|
|
226
|
+
// Start auto-validation and heartbeat if API key is configured
|
|
209
227
|
if (this.config.apiKey) {
|
|
210
228
|
this.startAutoValidation(cachedLicense.license_key);
|
|
229
|
+
this.startHeartbeat();
|
|
211
230
|
|
|
212
231
|
// Validate in background
|
|
213
232
|
this.validateLicense(cachedLicense.license_key).catch((err) => {
|
|
@@ -278,6 +297,7 @@ export class LicenseSeatSDK {
|
|
|
278
297
|
this.cache.setLicense(licenseData);
|
|
279
298
|
this.cache.updateValidation({ valid: true, optimistic: true });
|
|
280
299
|
this.startAutoValidation(licenseKey);
|
|
300
|
+
this.startHeartbeat();
|
|
281
301
|
this.syncOfflineAssets();
|
|
282
302
|
this.scheduleOfflineRefresh();
|
|
283
303
|
|
|
@@ -323,6 +343,7 @@ export class LicenseSeatSDK {
|
|
|
323
343
|
this.cache.clearLicense();
|
|
324
344
|
this.cache.clearOfflineToken();
|
|
325
345
|
this.stopAutoValidation();
|
|
346
|
+
this.stopHeartbeat();
|
|
326
347
|
|
|
327
348
|
this.emit("deactivation:success", response);
|
|
328
349
|
return response;
|
|
@@ -527,8 +548,8 @@ export class LicenseSeatSDK {
|
|
|
527
548
|
body.ttl_days = options.ttlDays;
|
|
528
549
|
}
|
|
529
550
|
|
|
530
|
-
// New v1 API: POST /products/{slug}/licenses/{key}/
|
|
531
|
-
const path = `/products/${this.config.productSlug}/licenses/${encodeURIComponent(license.license_key)}/
|
|
551
|
+
// New v1 API: POST /products/{slug}/licenses/{key}/offline_token
|
|
552
|
+
const path = `/products/${this.config.productSlug}/licenses/${encodeURIComponent(license.license_key)}/offline_token`;
|
|
532
553
|
|
|
533
554
|
const response = await this.apiCall(path, {
|
|
534
555
|
method: "POST",
|
|
@@ -565,8 +586,8 @@ export class LicenseSeatSDK {
|
|
|
565
586
|
}
|
|
566
587
|
try {
|
|
567
588
|
this.log(`Fetching signing key for kid: ${keyId}`);
|
|
568
|
-
// New v1 API: GET /
|
|
569
|
-
const response = await this.apiCall(`/
|
|
589
|
+
// New v1 API: GET /signing_keys/{key_id}
|
|
590
|
+
const response = await this.apiCall(`/signing_keys/${encodeURIComponent(keyId)}`, {
|
|
570
591
|
method: "GET",
|
|
571
592
|
});
|
|
572
593
|
if (response && response.public_key) {
|
|
@@ -684,11 +705,12 @@ export class LicenseSeatSDK {
|
|
|
684
705
|
}
|
|
685
706
|
|
|
686
707
|
/**
|
|
687
|
-
* Test
|
|
688
|
-
*
|
|
689
|
-
*
|
|
690
|
-
* @
|
|
691
|
-
* @throws {
|
|
708
|
+
* Test API connectivity
|
|
709
|
+
* Makes a request to the health endpoint to verify connectivity.
|
|
710
|
+
* Note: To fully verify API key validity, attempt an actual operation like activate() or validateLicense().
|
|
711
|
+
* @returns {Promise<{authenticated: boolean, healthy: boolean, api_version: string}>}
|
|
712
|
+
* @throws {ConfigurationError} If API key is not configured
|
|
713
|
+
* @throws {APIError} If the health check fails
|
|
692
714
|
*/
|
|
693
715
|
async testAuth() {
|
|
694
716
|
if (!this.config.apiKey) {
|
|
@@ -699,21 +721,61 @@ export class LicenseSeatSDK {
|
|
|
699
721
|
|
|
700
722
|
try {
|
|
701
723
|
this.emit("auth_test:start");
|
|
702
|
-
|
|
703
|
-
this.
|
|
704
|
-
|
|
724
|
+
// Use health endpoint to verify API connectivity
|
|
725
|
+
const response = await this.apiCall("/health", { method: "GET" });
|
|
726
|
+
const result = {
|
|
727
|
+
authenticated: true, // API key was included in request
|
|
728
|
+
healthy: response.status === "healthy",
|
|
729
|
+
api_version: response.api_version,
|
|
730
|
+
};
|
|
731
|
+
this.emit("auth_test:success", result);
|
|
732
|
+
return result;
|
|
705
733
|
} catch (error) {
|
|
706
734
|
this.emit("auth_test:error", { error });
|
|
707
735
|
throw error;
|
|
708
736
|
}
|
|
709
737
|
}
|
|
710
738
|
|
|
739
|
+
/**
|
|
740
|
+
* Send a heartbeat for the current license.
|
|
741
|
+
* Heartbeats let the server know the device is still active.
|
|
742
|
+
* @returns {Promise<Object|undefined>} Heartbeat response, or undefined if no active license
|
|
743
|
+
* @throws {ConfigurationError} When productSlug is not configured
|
|
744
|
+
* @throws {APIError} When the API request fails
|
|
745
|
+
*/
|
|
746
|
+
async heartbeat() {
|
|
747
|
+
if (!this.config.productSlug) {
|
|
748
|
+
throw new ConfigurationError("productSlug is required for heartbeat");
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
const cached = this.cache.getLicense();
|
|
752
|
+
if (!cached) {
|
|
753
|
+
this.log("No active license for heartbeat");
|
|
754
|
+
return;
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
const body = { device_id: cached.device_id };
|
|
758
|
+
|
|
759
|
+
const response = await this.apiCall(
|
|
760
|
+
`/products/${this.config.productSlug}/licenses/${encodeURIComponent(cached.license_key)}/heartbeat`,
|
|
761
|
+
{
|
|
762
|
+
method: "POST",
|
|
763
|
+
body: body,
|
|
764
|
+
}
|
|
765
|
+
);
|
|
766
|
+
|
|
767
|
+
this.emit("heartbeat:success", response);
|
|
768
|
+
this.log("Heartbeat sent successfully");
|
|
769
|
+
return response;
|
|
770
|
+
}
|
|
771
|
+
|
|
711
772
|
/**
|
|
712
773
|
* Clear all data and reset SDK state
|
|
713
774
|
* @returns {void}
|
|
714
775
|
*/
|
|
715
776
|
reset() {
|
|
716
777
|
this.stopAutoValidation();
|
|
778
|
+
this.stopHeartbeat();
|
|
717
779
|
this.stopConnectivityPolling();
|
|
718
780
|
if (this.offlineRefreshTimer) {
|
|
719
781
|
clearInterval(this.offlineRefreshTimer);
|
|
@@ -734,6 +796,7 @@ export class LicenseSeatSDK {
|
|
|
734
796
|
destroy() {
|
|
735
797
|
this.destroyed = true;
|
|
736
798
|
this.stopAutoValidation();
|
|
799
|
+
this.stopHeartbeat();
|
|
737
800
|
this.stopConnectivityPolling();
|
|
738
801
|
if (this.offlineRefreshTimer) {
|
|
739
802
|
clearInterval(this.offlineRefreshTimer);
|
|
@@ -814,11 +877,21 @@ export class LicenseSeatSDK {
|
|
|
814
877
|
this.currentAutoLicenseKey = licenseKey;
|
|
815
878
|
const validationInterval = this.config.autoValidateInterval;
|
|
816
879
|
|
|
880
|
+
// Don't start auto-validation if interval is 0 or negative
|
|
881
|
+
if (!validationInterval || validationInterval <= 0) {
|
|
882
|
+
this.log("Auto-validation disabled (interval:", validationInterval, ")");
|
|
883
|
+
return;
|
|
884
|
+
}
|
|
885
|
+
|
|
817
886
|
const performAndReschedule = () => {
|
|
818
|
-
this.validateLicense(licenseKey)
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
887
|
+
this.validateLicense(licenseKey)
|
|
888
|
+
.then(() => {
|
|
889
|
+
this.heartbeat().catch((err) => this.log("Heartbeat failed:", err));
|
|
890
|
+
})
|
|
891
|
+
.catch((err) => {
|
|
892
|
+
this.log("Auto-validation failed:", err);
|
|
893
|
+
this.emit("validation:auto-failed", { licenseKey, error: err });
|
|
894
|
+
});
|
|
822
895
|
this.emit("autovalidation:cycle", {
|
|
823
896
|
nextRunAt: new Date(Date.now() + validationInterval),
|
|
824
897
|
});
|
|
@@ -844,6 +917,42 @@ export class LicenseSeatSDK {
|
|
|
844
917
|
}
|
|
845
918
|
}
|
|
846
919
|
|
|
920
|
+
/**
|
|
921
|
+
* Start separate heartbeat timer
|
|
922
|
+
* Sends periodic heartbeats between auto-validation cycles.
|
|
923
|
+
* @returns {void}
|
|
924
|
+
* @private
|
|
925
|
+
*/
|
|
926
|
+
startHeartbeat() {
|
|
927
|
+
this.stopHeartbeat();
|
|
928
|
+
|
|
929
|
+
const interval = this.config.heartbeatInterval;
|
|
930
|
+
if (!interval || interval <= 0) {
|
|
931
|
+
this.log("Heartbeat timer disabled (interval:", interval, ")");
|
|
932
|
+
return;
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
this.heartbeatTimer = setInterval(() => {
|
|
936
|
+
this.heartbeat()
|
|
937
|
+
.then(() => this.emit("heartbeat:cycle", { nextRunAt: new Date(Date.now() + interval) }))
|
|
938
|
+
.catch((err) => this.log("Heartbeat timer failed:", err));
|
|
939
|
+
}, interval);
|
|
940
|
+
|
|
941
|
+
this.log("Heartbeat timer started (interval:", interval, "ms)");
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
/**
|
|
945
|
+
* Stop the separate heartbeat timer
|
|
946
|
+
* @returns {void}
|
|
947
|
+
* @private
|
|
948
|
+
*/
|
|
949
|
+
stopHeartbeat() {
|
|
950
|
+
if (this.heartbeatTimer) {
|
|
951
|
+
clearInterval(this.heartbeatTimer);
|
|
952
|
+
this.heartbeatTimer = null;
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
|
|
847
956
|
/**
|
|
848
957
|
* Start connectivity polling (when offline)
|
|
849
958
|
* @returns {void}
|
|
@@ -855,10 +964,12 @@ export class LicenseSeatSDK {
|
|
|
855
964
|
const healthCheck = async () => {
|
|
856
965
|
try {
|
|
857
966
|
// New v1 API: GET /health
|
|
858
|
-
await fetch(`${this.config.apiBaseUrl}/health`, {
|
|
967
|
+
const res = await fetch(`${this.config.apiBaseUrl}/health`, {
|
|
859
968
|
method: "GET",
|
|
860
969
|
credentials: "omit",
|
|
861
970
|
});
|
|
971
|
+
// Consume the response body to release the connection
|
|
972
|
+
await res.text().catch(() => {});
|
|
862
973
|
|
|
863
974
|
if (!this.online) {
|
|
864
975
|
this.online = true;
|
|
@@ -897,10 +1008,10 @@ export class LicenseSeatSDK {
|
|
|
897
1008
|
// ============================================================
|
|
898
1009
|
|
|
899
1010
|
/**
|
|
900
|
-
*
|
|
901
|
-
*
|
|
1011
|
+
* Download and cache the offline token and its corresponding public signing key.
|
|
1012
|
+
* Emits `offlineToken:ready` on success. Safe to call multiple times — concurrent
|
|
1013
|
+
* calls are deduplicated automatically.
|
|
902
1014
|
* @returns {Promise<void>}
|
|
903
|
-
* @private
|
|
904
1015
|
*/
|
|
905
1016
|
async syncOfflineAssets() {
|
|
906
1017
|
// Prevent concurrent syncs
|
|
@@ -958,9 +1069,10 @@ export class LicenseSeatSDK {
|
|
|
958
1069
|
}
|
|
959
1070
|
|
|
960
1071
|
/**
|
|
961
|
-
* Verify cached offline token
|
|
1072
|
+
* Verify the cached offline token and return a validation result.
|
|
1073
|
+
* Use this to validate the license when the device is offline.
|
|
1074
|
+
* The offline token must have been previously downloaded via {@link syncOfflineAssets}.
|
|
962
1075
|
* @returns {Promise<import('./types.js').ValidationResult>}
|
|
963
|
-
* @private
|
|
964
1076
|
*/
|
|
965
1077
|
async verifyCachedOffline() {
|
|
966
1078
|
const signed = this.cache.getOfflineToken();
|
|
@@ -1120,12 +1232,22 @@ export class LicenseSeatSDK {
|
|
|
1120
1232
|
);
|
|
1121
1233
|
}
|
|
1122
1234
|
|
|
1235
|
+
// Inject telemetry into POST request bodies
|
|
1236
|
+
const method = options.method || "GET";
|
|
1237
|
+
let body = options.body;
|
|
1238
|
+
if (method === "POST" && body && this.config.telemetryEnabled !== false) {
|
|
1239
|
+
body = { ...body, telemetry: collectTelemetry(SDK_VERSION, {
|
|
1240
|
+
appVersion: this.config.appVersion,
|
|
1241
|
+
appBuild: this.config.appBuild,
|
|
1242
|
+
}) };
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1123
1245
|
for (let attempt = 0; attempt <= this.config.maxRetries; attempt++) {
|
|
1124
1246
|
try {
|
|
1125
1247
|
const response = await fetch(url, {
|
|
1126
|
-
method:
|
|
1248
|
+
method: method,
|
|
1127
1249
|
headers: headers,
|
|
1128
|
-
body:
|
|
1250
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
1129
1251
|
credentials: "omit",
|
|
1130
1252
|
});
|
|
1131
1253
|
|
package/src/global.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type augmentations for non-standard browser APIs used by the telemetry module.
|
|
3
|
+
* These APIs exist in Chromium-based browsers but are not part of the standard
|
|
4
|
+
* TypeScript DOM lib.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
interface NavigatorUABrandVersion {
|
|
8
|
+
brand: string;
|
|
9
|
+
version: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface NavigatorUAData {
|
|
13
|
+
brands: NavigatorUABrandVersion[];
|
|
14
|
+
mobile: boolean;
|
|
15
|
+
platform: string;
|
|
16
|
+
architecture?: string;
|
|
17
|
+
model?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface Navigator {
|
|
21
|
+
userAgentData?: NavigatorUAData;
|
|
22
|
+
deviceMemory?: number;
|
|
23
|
+
}
|
package/src/index.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* for apps, games, and plugins.
|
|
6
6
|
*
|
|
7
7
|
* @module @licenseseat/js
|
|
8
|
-
* @version 0.
|
|
8
|
+
* @version 0.4.1
|
|
9
9
|
*
|
|
10
10
|
* @example
|
|
11
11
|
* ```js
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
*
|
|
14
14
|
* const sdk = new LicenseSeat({
|
|
15
15
|
* apiKey: 'your-api-key',
|
|
16
|
+
* productSlug: 'your-product', // Required: your product slug
|
|
16
17
|
* debug: true
|
|
17
18
|
* });
|
|
18
19
|
*
|
|
@@ -30,9 +31,10 @@
|
|
|
30
31
|
* ```
|
|
31
32
|
*/
|
|
32
33
|
|
|
33
|
-
// Re-export the main SDK class
|
|
34
|
+
// Re-export the main SDK class and version
|
|
34
35
|
export {
|
|
35
36
|
LicenseSeatSDK,
|
|
37
|
+
SDK_VERSION,
|
|
36
38
|
getSharedInstance,
|
|
37
39
|
configure,
|
|
38
40
|
resetSharedInstance,
|
|
@@ -57,6 +59,9 @@ export {
|
|
|
57
59
|
getCsrfToken,
|
|
58
60
|
} from "./utils.js";
|
|
59
61
|
|
|
62
|
+
// Re-export telemetry collection (for advanced use cases)
|
|
63
|
+
export { collectTelemetry } from "./telemetry.js";
|
|
64
|
+
|
|
60
65
|
// Default export - the main SDK class
|
|
61
66
|
import { LicenseSeatSDK } from "./LicenseSeat.js";
|
|
62
67
|
export default LicenseSeatSDK;
|