@licenseseat/js 0.3.1 → 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.
@@ -1 +1 @@
1
- {"version":3,"file":"LicenseSeat.d.ts","sourceRoot":"","sources":["../../src/LicenseSeat.js"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"LicenseSeat.d.ts","sourceRoot":"","sources":["../../src/LicenseSeat.js"],"names":[],"mappings":"AAo2CA;;;;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;AAv2CD;;;GAGG;AACH,0BAFU,MAAM,CAEmB;AA2BnC;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH;IACE;;;OAGG;IACH,qBAFW,OAAO,YAAY,EAAE,iBAAiB,EAsGhD;IAnGC;;;OAGG;IACH,QAFU,OAAO,YAAY,EAAE,iBAAiB,CAK/C;IAED;;;;OAIG;IACH,uBAAwB;IAExB;;;;OAIG;IACH,wBAA2B;IAE3B;;;;OAIG;IACH,uBAA0B;IAE1B;;;;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,CAmDhB;IAED;;;;;;;OAOG;IACH,qBANW,MAAM,YACN,OAAO,YAAY,EAAE,iBAAiB,GACpC,OAAO,CAAC,OAAO,YAAY,EAAE,aAAa,CAAC,CAqDvD;IAED;;;;;;OAMG;IACH,cALa,OAAO,KAAQ,CAwC3B;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;;;;;;;OAOG;IACH,YAJa,OAAO,CAAC;QAAC,aAAa,EAAE,OAAO,CAAC;QAAC,OAAO,EAAE,OAAO,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAC,CAAC,CA0BpF;IAED;;;;;;OAMG;IACH,aAJa,OAAO,CAAC,MAAO,SAAS,CAAC,CA4BrC;IAED;;;OAGG;IACH,SAFa,IAAI,CAchB;IAED;;;;;OAKG;IACH,WAFa,IAAI,CAgBhB;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,4BA+BC;IAED;;;;OAIG;IACH,2BAMC;IAED;;;;;OAKG;IACH,uBAgBC;IAED;;;;OAIG;IACH,sBAKC;IAED;;;;OAIG;IACH,iCA+BC;IAED;;;;OAIG;IACH,gCAKC;IAMD;;;;;OAKG;IACH,qBAFa,OAAO,CAAC,IAAI,CAAC,CA0CzB;IAED;;;;OAIG;IACH,+BAMC;IAED;;;;;OAKG;IACH,uBAFa,OAAO,CAAC,OAAO,YAAY,EAAE,gBAAgB,CAAC,CAyE1D;IAED;;;;;OAKG;IACH,sCA4CC;IAMD;;;;;;;;;;OAUG;IACH,gBAgGC;IAED;;;;;OAKG;IACH,yBAsBC;IAMD;;;OAGG;IACH,gBAFa,MAAM,CAIlB;IAED;;;;;OAKG;IACH,YAIC;CACF"}
@@ -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":";;+BA6D+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.1",
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;
@@ -715,12 +736,46 @@ export class LicenseSeatSDK {
715
736
  }
716
737
  }
717
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
+
718
772
  /**
719
773
  * Clear all data and reset SDK state
720
774
  * @returns {void}
721
775
  */
722
776
  reset() {
723
777
  this.stopAutoValidation();
778
+ this.stopHeartbeat();
724
779
  this.stopConnectivityPolling();
725
780
  if (this.offlineRefreshTimer) {
726
781
  clearInterval(this.offlineRefreshTimer);
@@ -741,6 +796,7 @@ export class LicenseSeatSDK {
741
796
  destroy() {
742
797
  this.destroyed = true;
743
798
  this.stopAutoValidation();
799
+ this.stopHeartbeat();
744
800
  this.stopConnectivityPolling();
745
801
  if (this.offlineRefreshTimer) {
746
802
  clearInterval(this.offlineRefreshTimer);
@@ -821,11 +877,21 @@ export class LicenseSeatSDK {
821
877
  this.currentAutoLicenseKey = licenseKey;
822
878
  const validationInterval = this.config.autoValidateInterval;
823
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
+
824
886
  const performAndReschedule = () => {
825
- this.validateLicense(licenseKey).catch((err) => {
826
- this.log("Auto-validation failed:", err);
827
- this.emit("validation:auto-failed", { licenseKey, error: err });
828
- });
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
+ });
829
895
  this.emit("autovalidation:cycle", {
830
896
  nextRunAt: new Date(Date.now() + validationInterval),
831
897
  });
@@ -851,6 +917,42 @@ export class LicenseSeatSDK {
851
917
  }
852
918
  }
853
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
+
854
956
  /**
855
957
  * Start connectivity polling (when offline)
856
958
  * @returns {void}
@@ -862,10 +964,12 @@ export class LicenseSeatSDK {
862
964
  const healthCheck = async () => {
863
965
  try {
864
966
  // New v1 API: GET /health
865
- await fetch(`${this.config.apiBaseUrl}/health`, {
967
+ const res = await fetch(`${this.config.apiBaseUrl}/health`, {
866
968
  method: "GET",
867
969
  credentials: "omit",
868
970
  });
971
+ // Consume the response body to release the connection
972
+ await res.text().catch(() => {});
869
973
 
870
974
  if (!this.online) {
871
975
  this.online = true;
@@ -904,10 +1008,10 @@ export class LicenseSeatSDK {
904
1008
  // ============================================================
905
1009
 
906
1010
  /**
907
- * Fetch and cache offline token and signing key
908
- * 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.
909
1014
  * @returns {Promise<void>}
910
- * @private
911
1015
  */
912
1016
  async syncOfflineAssets() {
913
1017
  // Prevent concurrent syncs
@@ -965,9 +1069,10 @@ export class LicenseSeatSDK {
965
1069
  }
966
1070
 
967
1071
  /**
968
- * 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}.
969
1075
  * @returns {Promise<import('./types.js').ValidationResult>}
970
- * @private
971
1076
  */
972
1077
  async verifyCachedOffline() {
973
1078
  const signed = this.cache.getOfflineToken();
@@ -1127,12 +1232,22 @@ export class LicenseSeatSDK {
1127
1232
  );
1128
1233
  }
1129
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
+
1130
1245
  for (let attempt = 0; attempt <= this.config.maxRetries; attempt++) {
1131
1246
  try {
1132
1247
  const response = await fetch(url, {
1133
- method: options.method || "GET",
1248
+ method: method,
1134
1249
  headers: headers,
1135
- body: options.body ? JSON.stringify(options.body) : undefined,
1250
+ body: body ? JSON.stringify(body) : undefined,
1136
1251
  credentials: "omit",
1137
1252
  });
1138
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.3.0
8
+ * @version 0.4.1
9
9
  *
10
10
  * @example
11
11
  * ```js
@@ -31,9 +31,10 @@
31
31
  * ```
32
32
  */
33
33
 
34
- // Re-export the main SDK class
34
+ // Re-export the main SDK class and version
35
35
  export {
36
36
  LicenseSeatSDK,
37
+ SDK_VERSION,
37
38
  getSharedInstance,
38
39
  configure,
39
40
  resetSharedInstance,
@@ -58,6 +59,9 @@ export {
58
59
  getCsrfToken,
59
60
  } from "./utils.js";
60
61
 
62
+ // Re-export telemetry collection (for advanced use cases)
63
+ export { collectTelemetry } from "./telemetry.js";
64
+
61
65
  // Default export - the main SDK class
62
66
  import { LicenseSeatSDK } from "./LicenseSeat.js";
63
67
  export default LicenseSeatSDK;