@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.
@@ -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 server authentication
196
- * Useful for verifying API key/session is valid.
197
- * @returns {Promise<Object>} Result from the server
198
- * @throws {ConfigurationError} When API key is not configured
199
- * @throws {APIError} When authentication fails
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
- testAuth(): Promise<any>;
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
- * Fetch and cache offline token and signing key
263
- * Uses a lock to prevent concurrent calls from causing race conditions
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
- private syncOfflineAssets;
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
- private verifyCachedOffline;
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":"AA0uCA;;;;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;AAztCD;;;;;;;;;;;;;;;;;;;;;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;;;;;;;OAOG;IACH,qBANW,MAAM,YACN,OAAO,YAAY,EAAE,iBAAiB,GACpC,OAAO,CAAC,OAAO,YAAY,EAAE,aAAa,CAAC,CAoDvD;IAED;;;;;;OAMG;IACH,cALa,OAAO,KAAQ,CAuC3B;IAED;;;;;;;OAOG;IACH,4BANW,MAAM,YACN,OAAO,YAAY,EAAE,iBAAiB,GACpC,OAAO,CAAC,OAAO,YAAY,EAAE,gBAAgB,CAAC,CA6G1D;IAED;;;;OAIG;IACH,iCAHW,MAAM,GACJ,OAAO,YAAY,EAAE,sBAAsB,CA6BvD;IAED;;;;;;OAMG;IACH,+BAHW,MAAM,GACJ,OAAO,CAInB;IAED;;;;;;;;;OASG;IACH,0BAPG;QAAyB,QAAQ,GAAzB,MAAM;QACW,OAAO,GAAxB,MAAM;KACd,GAAU,OAAO,CAAC,OAAO,YAAY,EAAE,YAAY,CAAC,CAsDtD;IAED;;;;;OAKG;IACH,qBAJW,MAAM,GACJ,OAAO,CAAC,OAAO,YAAY,EAAE,UAAU,CAAC,CAyBpD;IAED;;;;;;;OAOG;IACH,qCANW,OAAO,YAAY,EAAE,YAAY,gBACjC,MAAM,GACJ,OAAO,CAAC,OAAO,CAAC,CA6C5B;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,iCA6BC;IAED;;;;OAIG;IACH,gCAKC;IAMD;;;;;OAKG;IACH,0BAwCC;IAED;;;;OAIG;IACH,+BAMC;IAED;;;;OAIG;IACH,4BAuEC;IAED;;;;;OAKG;IACH,sCA4CC;IAMD;;;;;;;;;;OAUG;IACH,gBAsFC;IAED;;;;;OAKG;IACH,yBAsBC;IAMD;;;OAGG;IACH,gBAFa,MAAM,CAIlB;IAED;;;;;OAKG;IACH,YAIC;CACF"}
1
+ {"version":3,"file":"LicenseSeat.d.ts","sourceRoot":"","sources":["../../src/LicenseSeat.js"],"names":[],"mappings":"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"}
@@ -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":";;+BA4D+B,kBAAkB"}
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"}
@@ -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;;;;;;;;;eAMP,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;;;;YACN,MAAM;;;;iBACN,MAAM;;;;eACN,MAAM;;;;;mCAMT,GAAC,KACC,IAAI;;;;qCAMJ,IAAI;;;;;;;;YAMH,cAAc;;;;WACd,MAAM;;;;cACN,MAAM;;;;;;;;;UAMN,MAAM;;;;aACN,MAAM"}
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@licenseseat/js",
3
- "version": "0.3.0",
3
+ "version": "0.4.1",
4
4
  "description": "Official JavaScript SDK for LicenseSeat – simple, secure software licensing.",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",
@@ -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}/offline-token
531
- const path = `/products/${this.config.productSlug}/licenses/${encodeURIComponent(license.license_key)}/offline-token`;
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 /signing-keys/{key_id}
569
- const response = await this.apiCall(`/signing-keys/${encodeURIComponent(keyId)}`, {
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 server authentication
688
- * Useful for verifying API key/session is valid.
689
- * @returns {Promise<Object>} Result from the server
690
- * @throws {ConfigurationError} When API key is not configured
691
- * @throws {APIError} When authentication fails
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
- const response = await this.apiCall("/auth_test", { method: "GET" });
703
- this.emit("auth_test:success", response);
704
- return response;
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).catch((err) => {
819
- this.log("Auto-validation failed:", err);
820
- this.emit("validation:auto-failed", { licenseKey, error: err });
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
- * Fetch and cache offline token and signing key
901
- * Uses a lock to prevent concurrent calls from causing race conditions
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: options.method || "GET",
1248
+ method: method,
1127
1249
  headers: headers,
1128
- body: options.body ? JSON.stringify(options.body) : undefined,
1250
+ body: body ? JSON.stringify(body) : undefined,
1129
1251
  credentials: "omit",
1130
1252
  });
1131
1253
 
@@ -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.2.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;