@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/dist/index.js CHANGED
@@ -300,12 +300,12 @@ function generateDeviceId() {
300
300
  return `node-${hashCode(os + "|" + arch)}`;
301
301
  }
302
302
  const nav = window.navigator;
303
- const screen = window.screen;
303
+ const screen2 = window.screen;
304
304
  const data = [
305
305
  nav.userAgent,
306
306
  nav.language,
307
- screen.colorDepth,
308
- screen.width + "x" + screen.height,
307
+ screen2.colorDepth,
308
+ screen2.width + "x" + screen2.height,
309
309
  (/* @__PURE__ */ new Date()).getTimezoneOffset(),
310
310
  nav.hardwareConcurrency,
311
311
  getCanvasFingerprint()
@@ -320,7 +320,366 @@ function getCsrfToken() {
320
320
  return token ? token.content : "";
321
321
  }
322
322
 
323
+ // src/telemetry.js
324
+ function detectOSName() {
325
+ if (typeof process !== "undefined" && process.platform) {
326
+ const map = {
327
+ darwin: "macOS",
328
+ win32: "Windows",
329
+ linux: "Linux",
330
+ freebsd: "FreeBSD",
331
+ sunos: "SunOS"
332
+ };
333
+ return map[process.platform] || process.platform;
334
+ }
335
+ if (typeof navigator !== "undefined") {
336
+ if (navigator.userAgentData && navigator.userAgentData.platform) {
337
+ return navigator.userAgentData.platform;
338
+ }
339
+ const ua = navigator.userAgent || "";
340
+ if (/Android/i.test(ua))
341
+ return "Android";
342
+ if (/iPhone|iPad|iPod/i.test(ua))
343
+ return "iOS";
344
+ if (/Mac/i.test(ua))
345
+ return "macOS";
346
+ if (/Win/i.test(ua))
347
+ return "Windows";
348
+ if (/Linux/i.test(ua))
349
+ return "Linux";
350
+ }
351
+ return "Unknown";
352
+ }
353
+ function detectOSVersion() {
354
+ if (typeof process !== "undefined" && process.version) {
355
+ try {
356
+ const os = await_free_os_release();
357
+ if (os)
358
+ return os;
359
+ } catch (_) {
360
+ }
361
+ return process.version;
362
+ }
363
+ if (typeof navigator !== "undefined") {
364
+ const ua = navigator.userAgent || "";
365
+ const macMatch = ua.match(/Mac OS X\s+([\d._]+)/);
366
+ if (macMatch)
367
+ return macMatch[1].replace(/_/g, ".");
368
+ const winMatch = ua.match(/Windows NT\s+([\d.]+)/);
369
+ if (winMatch)
370
+ return winMatch[1];
371
+ const androidMatch = ua.match(/Android\s+([\d.]+)/);
372
+ if (androidMatch)
373
+ return androidMatch[1];
374
+ const iosMatch = ua.match(/OS\s+([\d._]+)/);
375
+ if (iosMatch)
376
+ return iosMatch[1].replace(/_/g, ".");
377
+ }
378
+ return null;
379
+ }
380
+ function await_free_os_release() {
381
+ try {
382
+ const os = new Function("try { return require('os') } catch(e) { return null }")();
383
+ if (os && os.release)
384
+ return os.release();
385
+ } catch (_) {
386
+ }
387
+ return null;
388
+ }
389
+ function dynamicRequire(moduleName) {
390
+ try {
391
+ return new Function("m", "try { return require(m) } catch(e) { return null }")(moduleName);
392
+ } catch (_) {
393
+ return null;
394
+ }
395
+ }
396
+ function detectPlatform() {
397
+ if (typeof process !== "undefined") {
398
+ if (process.versions && process.versions.electron)
399
+ return "electron";
400
+ if (process.versions && process.versions.bun)
401
+ return "bun";
402
+ if (process.versions && process.versions.node)
403
+ return "node";
404
+ }
405
+ if (typeof Deno !== "undefined")
406
+ return "deno";
407
+ if (typeof navigator !== "undefined" && navigator.product === "ReactNative")
408
+ return "react-native";
409
+ if (typeof window !== "undefined")
410
+ return "browser";
411
+ return "unknown";
412
+ }
413
+ function detectDeviceModel() {
414
+ try {
415
+ if (typeof navigator !== "undefined" && navigator.userAgentData) {
416
+ return navigator.userAgentData.model || null;
417
+ }
418
+ } catch (_) {
419
+ }
420
+ return null;
421
+ }
422
+ function detectLocale() {
423
+ if (typeof navigator !== "undefined" && navigator.language) {
424
+ return navigator.language;
425
+ }
426
+ if (typeof Intl !== "undefined") {
427
+ try {
428
+ return Intl.DateTimeFormat().resolvedOptions().locale || null;
429
+ } catch (_) {
430
+ }
431
+ }
432
+ if (typeof process !== "undefined" && process.env) {
433
+ return process.env.LANG || process.env.LC_ALL || null;
434
+ }
435
+ return null;
436
+ }
437
+ function detectTimezone() {
438
+ if (typeof Intl !== "undefined") {
439
+ try {
440
+ return Intl.DateTimeFormat().resolvedOptions().timeZone || null;
441
+ } catch (_) {
442
+ }
443
+ }
444
+ return null;
445
+ }
446
+ function detectDeviceType() {
447
+ try {
448
+ const platform = detectPlatform();
449
+ if (platform === "node" || platform === "bun" || platform === "deno")
450
+ return "server";
451
+ if (platform === "electron")
452
+ return "desktop";
453
+ if (platform === "react-native") {
454
+ if (typeof screen !== "undefined" && screen.width) {
455
+ return screen.width < 768 ? "phone" : "tablet";
456
+ }
457
+ return "phone";
458
+ }
459
+ if (typeof navigator !== "undefined") {
460
+ if (navigator.userAgentData && typeof navigator.userAgentData.mobile === "boolean") {
461
+ if (navigator.userAgentData.mobile) {
462
+ if (typeof screen !== "undefined" && screen.width >= 768)
463
+ return "tablet";
464
+ return "phone";
465
+ }
466
+ return "desktop";
467
+ }
468
+ if (navigator.maxTouchPoints > 0) {
469
+ if (typeof screen !== "undefined" && screen.width >= 768)
470
+ return "tablet";
471
+ return "phone";
472
+ }
473
+ return "desktop";
474
+ }
475
+ } catch (_) {
476
+ }
477
+ return "unknown";
478
+ }
479
+ function detectArchitecture() {
480
+ try {
481
+ if (typeof process !== "undefined" && process.arch) {
482
+ const map = { ia32: "x86", x64: "x64", arm: "arm", arm64: "arm64" };
483
+ return map[process.arch] || process.arch;
484
+ }
485
+ if (typeof navigator !== "undefined" && navigator.userAgentData) {
486
+ if (navigator.userAgentData.architecture) {
487
+ return navigator.userAgentData.architecture;
488
+ }
489
+ }
490
+ } catch (_) {
491
+ }
492
+ return null;
493
+ }
494
+ function detectCpuCores() {
495
+ try {
496
+ if (typeof navigator !== "undefined" && navigator.hardwareConcurrency) {
497
+ return navigator.hardwareConcurrency;
498
+ }
499
+ if (typeof process !== "undefined" && process.versions && process.versions.node) {
500
+ const os = dynamicRequire("os");
501
+ if (os && os.cpus) {
502
+ const cpus = os.cpus();
503
+ if (cpus && cpus.length)
504
+ return cpus.length;
505
+ }
506
+ }
507
+ } catch (_) {
508
+ }
509
+ return null;
510
+ }
511
+ function detectMemoryGb() {
512
+ try {
513
+ if (typeof navigator !== "undefined" && navigator.deviceMemory) {
514
+ return navigator.deviceMemory;
515
+ }
516
+ if (typeof process !== "undefined" && process.versions && process.versions.node) {
517
+ const os = dynamicRequire("os");
518
+ if (os && os.totalmem) {
519
+ return Math.round(os.totalmem() / (1024 * 1024 * 1024));
520
+ }
521
+ }
522
+ } catch (_) {
523
+ }
524
+ return null;
525
+ }
526
+ function detectLanguage() {
527
+ try {
528
+ const locale = detectLocale();
529
+ if (locale) {
530
+ const lang = locale.split(/[-_]/)[0];
531
+ if (lang && lang.length >= 2)
532
+ return lang.toLowerCase();
533
+ }
534
+ } catch (_) {
535
+ }
536
+ return null;
537
+ }
538
+ function detectScreenResolution() {
539
+ try {
540
+ if (typeof screen !== "undefined" && screen.width && screen.height) {
541
+ return `${screen.width}x${screen.height}`;
542
+ }
543
+ } catch (_) {
544
+ }
545
+ return null;
546
+ }
547
+ function detectDisplayScale() {
548
+ try {
549
+ if (typeof window !== "undefined" && window.devicePixelRatio) {
550
+ return window.devicePixelRatio;
551
+ }
552
+ } catch (_) {
553
+ }
554
+ return null;
555
+ }
556
+ function detectBrowserName() {
557
+ try {
558
+ if (typeof navigator === "undefined")
559
+ return null;
560
+ if (navigator.userAgentData && navigator.userAgentData.brands) {
561
+ const brands = navigator.userAgentData.brands;
562
+ for (const b of brands) {
563
+ const name = b.brand || "";
564
+ if (/^(Google Chrome|Microsoft Edge|Opera|Brave|Vivaldi|Samsung Internet)$/i.test(name)) {
565
+ return name;
566
+ }
567
+ }
568
+ for (const b of brands) {
569
+ if ((b.brand || "").toLowerCase() === "chromium")
570
+ return "Chrome";
571
+ }
572
+ }
573
+ const ua = navigator.userAgent || "";
574
+ if (/Edg\//i.test(ua))
575
+ return "Edge";
576
+ if (/OPR\//i.test(ua) || /Opera/i.test(ua))
577
+ return "Opera";
578
+ if (/Brave/i.test(ua))
579
+ return "Brave";
580
+ if (/Vivaldi/i.test(ua))
581
+ return "Vivaldi";
582
+ if (/Firefox/i.test(ua))
583
+ return "Firefox";
584
+ if (/SamsungBrowser/i.test(ua))
585
+ return "Samsung Internet";
586
+ if (/CriOS/i.test(ua))
587
+ return "Chrome";
588
+ if (/Chrome/i.test(ua))
589
+ return "Chrome";
590
+ if (/Safari/i.test(ua))
591
+ return "Safari";
592
+ } catch (_) {
593
+ }
594
+ return null;
595
+ }
596
+ function detectBrowserVersion() {
597
+ try {
598
+ if (typeof navigator === "undefined")
599
+ return null;
600
+ if (navigator.userAgentData && navigator.userAgentData.brands) {
601
+ const brands = navigator.userAgentData.brands;
602
+ for (const b of brands) {
603
+ const name = b.brand || "";
604
+ if (/^(Google Chrome|Microsoft Edge|Opera|Brave|Vivaldi|Samsung Internet)$/i.test(name)) {
605
+ return b.version || null;
606
+ }
607
+ }
608
+ for (const b of brands) {
609
+ if ((b.brand || "").toLowerCase() === "chromium")
610
+ return b.version || null;
611
+ }
612
+ }
613
+ const ua = navigator.userAgent || "";
614
+ const patterns = [
615
+ /Edg\/([\d.]+)/,
616
+ /OPR\/([\d.]+)/,
617
+ /Firefox\/([\d.]+)/,
618
+ /SamsungBrowser\/([\d.]+)/,
619
+ /CriOS\/([\d.]+)/,
620
+ /Chrome\/([\d.]+)/,
621
+ /Version\/([\d.]+).*Safari/
622
+ ];
623
+ for (const re of patterns) {
624
+ const m = ua.match(re);
625
+ if (m)
626
+ return m[1];
627
+ }
628
+ } catch (_) {
629
+ }
630
+ return null;
631
+ }
632
+ function detectRuntimeVersion() {
633
+ try {
634
+ if (typeof process !== "undefined" && process.versions) {
635
+ if (process.versions.bun)
636
+ return process.versions.bun;
637
+ if (process.versions.electron)
638
+ return process.versions.electron;
639
+ if (process.versions.node)
640
+ return process.versions.node;
641
+ }
642
+ if (typeof Deno !== "undefined" && Deno.version)
643
+ return Deno.version.deno;
644
+ } catch (_) {
645
+ }
646
+ return null;
647
+ }
648
+ function collectTelemetry(sdkVersion, options) {
649
+ const locale = detectLocale();
650
+ const raw = {
651
+ sdk_version: sdkVersion,
652
+ sdk_name: "js",
653
+ os_name: detectOSName(),
654
+ os_version: detectOSVersion(),
655
+ platform: detectPlatform(),
656
+ device_model: detectDeviceModel(),
657
+ device_type: detectDeviceType(),
658
+ locale,
659
+ timezone: detectTimezone(),
660
+ language: detectLanguage(),
661
+ architecture: detectArchitecture(),
662
+ cpu_cores: detectCpuCores(),
663
+ memory_gb: detectMemoryGb(),
664
+ screen_resolution: detectScreenResolution(),
665
+ display_scale: detectDisplayScale(),
666
+ browser_name: detectBrowserName(),
667
+ browser_version: detectBrowserVersion(),
668
+ runtime_version: detectRuntimeVersion(),
669
+ app_version: options && options.appVersion || null,
670
+ app_build: options && options.appBuild || null
671
+ };
672
+ const result = {};
673
+ for (const [key, value] of Object.entries(raw)) {
674
+ if (value != null) {
675
+ result[key] = value;
676
+ }
677
+ }
678
+ return result;
679
+ }
680
+
323
681
  // src/LicenseSeat.js
682
+ var SDK_VERSION = "0.4.1";
324
683
  var DEFAULT_CONFIG = {
325
684
  apiBaseUrl: "https://licenseseat.com/api/v1",
326
685
  productSlug: null,
@@ -328,6 +687,8 @@ var DEFAULT_CONFIG = {
328
687
  storagePrefix: "licenseseat_",
329
688
  autoValidateInterval: 36e5,
330
689
  // 1 hour
690
+ heartbeatInterval: 3e5,
691
+ // 5 minutes
331
692
  networkRecheckInterval: 3e4,
332
693
  // 30 seconds
333
694
  maxRetries: 3,
@@ -342,7 +703,13 @@ var DEFAULT_CONFIG = {
342
703
  // 0 = disabled
343
704
  maxClockSkewMs: 5 * 60 * 1e3,
344
705
  // 5 minutes
345
- autoInitialize: true
706
+ autoInitialize: true,
707
+ telemetryEnabled: true,
708
+ // Set false to disable telemetry (e.g. for GDPR compliance)
709
+ appVersion: null,
710
+ // User-provided app version, sent as app_version in telemetry
711
+ appBuild: null
712
+ // User-provided app build, sent as app_build in telemetry
346
713
  };
347
714
  var LicenseSeatSDK = class {
348
715
  /**
@@ -356,6 +723,7 @@ var LicenseSeatSDK = class {
356
723
  };
357
724
  this.eventListeners = {};
358
725
  this.validationTimer = null;
726
+ this.heartbeatTimer = null;
359
727
  this.cache = new LicenseCache(this.config.storagePrefix);
360
728
  this.online = true;
361
729
  this.currentAutoLicenseKey = null;
@@ -402,6 +770,7 @@ var LicenseSeatSDK = class {
402
770
  }
403
771
  if (this.config.apiKey) {
404
772
  this.startAutoValidation(cachedLicense.license_key);
773
+ this.startHeartbeat();
405
774
  this.validateLicense(cachedLicense.license_key).catch((err) => {
406
775
  this.log("Background validation failed:", err);
407
776
  if (err instanceof APIError && (err.status === 401 || err.status === 501)) {
@@ -457,6 +826,7 @@ var LicenseSeatSDK = class {
457
826
  this.cache.setLicense(licenseData);
458
827
  this.cache.updateValidation({ valid: true, optimistic: true });
459
828
  this.startAutoValidation(licenseKey);
829
+ this.startHeartbeat();
460
830
  this.syncOfflineAssets();
461
831
  this.scheduleOfflineRefresh();
462
832
  this.emit("activation:success", licenseData);
@@ -495,6 +865,7 @@ var LicenseSeatSDK = class {
495
865
  this.cache.clearLicense();
496
866
  this.cache.clearOfflineToken();
497
867
  this.stopAutoValidation();
868
+ this.stopHeartbeat();
498
869
  this.emit("deactivation:success", response);
499
870
  return response;
500
871
  } catch (error) {
@@ -656,7 +1027,7 @@ var LicenseSeatSDK = class {
656
1027
  if (options.ttlDays) {
657
1028
  body.ttl_days = options.ttlDays;
658
1029
  }
659
- const path = `/products/${this.config.productSlug}/licenses/${encodeURIComponent(license.license_key)}/offline-token`;
1030
+ const path = `/products/${this.config.productSlug}/licenses/${encodeURIComponent(license.license_key)}/offline_token`;
660
1031
  const response = await this.apiCall(path, {
661
1032
  method: "POST",
662
1033
  body: Object.keys(body).length > 0 ? body : void 0
@@ -690,7 +1061,7 @@ var LicenseSeatSDK = class {
690
1061
  }
691
1062
  try {
692
1063
  this.log(`Fetching signing key for kid: ${keyId}`);
693
- const response = await this.apiCall(`/signing-keys/${encodeURIComponent(keyId)}`, {
1064
+ const response = await this.apiCall(`/signing_keys/${encodeURIComponent(keyId)}`, {
694
1065
  method: "GET"
695
1066
  });
696
1067
  if (response && response.public_key) {
@@ -796,11 +1167,12 @@ var LicenseSeatSDK = class {
796
1167
  };
797
1168
  }
798
1169
  /**
799
- * Test server authentication
800
- * Useful for verifying API key/session is valid.
801
- * @returns {Promise<Object>} Result from the server
802
- * @throws {ConfigurationError} When API key is not configured
803
- * @throws {APIError} When authentication fails
1170
+ * Test API connectivity
1171
+ * Makes a request to the health endpoint to verify connectivity.
1172
+ * Note: To fully verify API key validity, attempt an actual operation like activate() or validateLicense().
1173
+ * @returns {Promise<{authenticated: boolean, healthy: boolean, api_version: string}>}
1174
+ * @throws {ConfigurationError} If API key is not configured
1175
+ * @throws {APIError} If the health check fails
804
1176
  */
805
1177
  async testAuth() {
806
1178
  if (!this.config.apiKey) {
@@ -810,20 +1182,55 @@ var LicenseSeatSDK = class {
810
1182
  }
811
1183
  try {
812
1184
  this.emit("auth_test:start");
813
- const response = await this.apiCall("/auth_test", { method: "GET" });
814
- this.emit("auth_test:success", response);
815
- return response;
1185
+ const response = await this.apiCall("/health", { method: "GET" });
1186
+ const result = {
1187
+ authenticated: true,
1188
+ // API key was included in request
1189
+ healthy: response.status === "healthy",
1190
+ api_version: response.api_version
1191
+ };
1192
+ this.emit("auth_test:success", result);
1193
+ return result;
816
1194
  } catch (error) {
817
1195
  this.emit("auth_test:error", { error });
818
1196
  throw error;
819
1197
  }
820
1198
  }
1199
+ /**
1200
+ * Send a heartbeat for the current license.
1201
+ * Heartbeats let the server know the device is still active.
1202
+ * @returns {Promise<Object|undefined>} Heartbeat response, or undefined if no active license
1203
+ * @throws {ConfigurationError} When productSlug is not configured
1204
+ * @throws {APIError} When the API request fails
1205
+ */
1206
+ async heartbeat() {
1207
+ if (!this.config.productSlug) {
1208
+ throw new ConfigurationError("productSlug is required for heartbeat");
1209
+ }
1210
+ const cached = this.cache.getLicense();
1211
+ if (!cached) {
1212
+ this.log("No active license for heartbeat");
1213
+ return;
1214
+ }
1215
+ const body = { device_id: cached.device_id };
1216
+ const response = await this.apiCall(
1217
+ `/products/${this.config.productSlug}/licenses/${encodeURIComponent(cached.license_key)}/heartbeat`,
1218
+ {
1219
+ method: "POST",
1220
+ body
1221
+ }
1222
+ );
1223
+ this.emit("heartbeat:success", response);
1224
+ this.log("Heartbeat sent successfully");
1225
+ return response;
1226
+ }
821
1227
  /**
822
1228
  * Clear all data and reset SDK state
823
1229
  * @returns {void}
824
1230
  */
825
1231
  reset() {
826
1232
  this.stopAutoValidation();
1233
+ this.stopHeartbeat();
827
1234
  this.stopConnectivityPolling();
828
1235
  if (this.offlineRefreshTimer) {
829
1236
  clearInterval(this.offlineRefreshTimer);
@@ -843,6 +1250,7 @@ var LicenseSeatSDK = class {
843
1250
  destroy() {
844
1251
  this.destroyed = true;
845
1252
  this.stopAutoValidation();
1253
+ this.stopHeartbeat();
846
1254
  this.stopConnectivityPolling();
847
1255
  if (this.offlineRefreshTimer) {
848
1256
  clearInterval(this.offlineRefreshTimer);
@@ -915,8 +1323,14 @@ var LicenseSeatSDK = class {
915
1323
  this.stopAutoValidation();
916
1324
  this.currentAutoLicenseKey = licenseKey;
917
1325
  const validationInterval = this.config.autoValidateInterval;
1326
+ if (!validationInterval || validationInterval <= 0) {
1327
+ this.log("Auto-validation disabled (interval:", validationInterval, ")");
1328
+ return;
1329
+ }
918
1330
  const performAndReschedule = () => {
919
- this.validateLicense(licenseKey).catch((err) => {
1331
+ this.validateLicense(licenseKey).then(() => {
1332
+ this.heartbeat().catch((err) => this.log("Heartbeat failed:", err));
1333
+ }).catch((err) => {
920
1334
  this.log("Auto-validation failed:", err);
921
1335
  this.emit("validation:auto-failed", { licenseKey, error: err });
922
1336
  });
@@ -941,6 +1355,35 @@ var LicenseSeatSDK = class {
941
1355
  this.emit("autovalidation:stopped");
942
1356
  }
943
1357
  }
1358
+ /**
1359
+ * Start separate heartbeat timer
1360
+ * Sends periodic heartbeats between auto-validation cycles.
1361
+ * @returns {void}
1362
+ * @private
1363
+ */
1364
+ startHeartbeat() {
1365
+ this.stopHeartbeat();
1366
+ const interval = this.config.heartbeatInterval;
1367
+ if (!interval || interval <= 0) {
1368
+ this.log("Heartbeat timer disabled (interval:", interval, ")");
1369
+ return;
1370
+ }
1371
+ this.heartbeatTimer = setInterval(() => {
1372
+ this.heartbeat().then(() => this.emit("heartbeat:cycle", { nextRunAt: new Date(Date.now() + interval) })).catch((err) => this.log("Heartbeat timer failed:", err));
1373
+ }, interval);
1374
+ this.log("Heartbeat timer started (interval:", interval, "ms)");
1375
+ }
1376
+ /**
1377
+ * Stop the separate heartbeat timer
1378
+ * @returns {void}
1379
+ * @private
1380
+ */
1381
+ stopHeartbeat() {
1382
+ if (this.heartbeatTimer) {
1383
+ clearInterval(this.heartbeatTimer);
1384
+ this.heartbeatTimer = null;
1385
+ }
1386
+ }
944
1387
  /**
945
1388
  * Start connectivity polling (when offline)
946
1389
  * @returns {void}
@@ -951,10 +1394,12 @@ var LicenseSeatSDK = class {
951
1394
  return;
952
1395
  const healthCheck = async () => {
953
1396
  try {
954
- await fetch(`${this.config.apiBaseUrl}/health`, {
1397
+ const res = await fetch(`${this.config.apiBaseUrl}/health`, {
955
1398
  method: "GET",
956
1399
  credentials: "omit"
957
1400
  });
1401
+ await res.text().catch(() => {
1402
+ });
958
1403
  if (!this.online) {
959
1404
  this.online = true;
960
1405
  this.emit("network:online");
@@ -987,10 +1432,10 @@ var LicenseSeatSDK = class {
987
1432
  // Offline License Management
988
1433
  // ============================================================
989
1434
  /**
990
- * Fetch and cache offline token and signing key
991
- * Uses a lock to prevent concurrent calls from causing race conditions
1435
+ * Download and cache the offline token and its corresponding public signing key.
1436
+ * Emits `offlineToken:ready` on success. Safe to call multiple times concurrent
1437
+ * calls are deduplicated automatically.
992
1438
  * @returns {Promise<void>}
993
- * @private
994
1439
  */
995
1440
  async syncOfflineAssets() {
996
1441
  if (this.syncingOfflineAssets || this.destroyed) {
@@ -1041,9 +1486,10 @@ var LicenseSeatSDK = class {
1041
1486
  );
1042
1487
  }
1043
1488
  /**
1044
- * Verify cached offline token
1489
+ * Verify the cached offline token and return a validation result.
1490
+ * Use this to validate the license when the device is offline.
1491
+ * The offline token must have been previously downloaded via {@link syncOfflineAssets}.
1045
1492
  * @returns {Promise<import('./types.js').ValidationResult>}
1046
- * @private
1047
1493
  */
1048
1494
  async verifyCachedOffline() {
1049
1495
  const signed = this.cache.getOfflineToken();
@@ -1176,12 +1622,20 @@ var LicenseSeatSDK = class {
1176
1622
  "[Warning] No API key configured for LicenseSeat SDK. Authenticated endpoints will fail."
1177
1623
  );
1178
1624
  }
1625
+ const method = options.method || "GET";
1626
+ let body = options.body;
1627
+ if (method === "POST" && body && this.config.telemetryEnabled !== false) {
1628
+ body = { ...body, telemetry: collectTelemetry(SDK_VERSION, {
1629
+ appVersion: this.config.appVersion,
1630
+ appBuild: this.config.appBuild
1631
+ }) };
1632
+ }
1179
1633
  for (let attempt = 0; attempt <= this.config.maxRetries; attempt++) {
1180
1634
  try {
1181
1635
  const response = await fetch(url, {
1182
- method: options.method || "GET",
1636
+ method,
1183
1637
  headers,
1184
- body: options.body ? JSON.stringify(options.body) : void 0,
1638
+ body: body ? JSON.stringify(body) : void 0,
1185
1639
  credentials: "omit"
1186
1640
  });
1187
1641
  const data = await response.json();
@@ -1305,8 +1759,10 @@ export {
1305
1759
  LicenseCache,
1306
1760
  LicenseError,
1307
1761
  LicenseSeatSDK,
1762
+ SDK_VERSION,
1308
1763
  base64UrlDecode,
1309
1764
  canonicalJsonStringify,
1765
+ collectTelemetry,
1310
1766
  configure,
1311
1767
  constantTimeEqual,
1312
1768
  src_default as default,