@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.
- package/README.md +156 -1
- package/dist/index.js +462 -13
- package/dist/types/LicenseSeat.d.ts +40 -7
- 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 +128 -13
- package/src/global.d.ts +23 -0
- package/src/index.js +6 -2
- package/src/telemetry.js +518 -0
- package/src/types.js +12 -0
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
|
|
303
|
+
const screen2 = window.screen;
|
|
304
304
|
const data = [
|
|
305
305
|
nav.userAgent,
|
|
306
306
|
nav.language,
|
|
307
|
-
|
|
308
|
-
|
|
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) {
|
|
@@ -825,12 +1196,41 @@ var LicenseSeatSDK = class {
|
|
|
825
1196
|
throw error;
|
|
826
1197
|
}
|
|
827
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
|
+
}
|
|
828
1227
|
/**
|
|
829
1228
|
* Clear all data and reset SDK state
|
|
830
1229
|
* @returns {void}
|
|
831
1230
|
*/
|
|
832
1231
|
reset() {
|
|
833
1232
|
this.stopAutoValidation();
|
|
1233
|
+
this.stopHeartbeat();
|
|
834
1234
|
this.stopConnectivityPolling();
|
|
835
1235
|
if (this.offlineRefreshTimer) {
|
|
836
1236
|
clearInterval(this.offlineRefreshTimer);
|
|
@@ -850,6 +1250,7 @@ var LicenseSeatSDK = class {
|
|
|
850
1250
|
destroy() {
|
|
851
1251
|
this.destroyed = true;
|
|
852
1252
|
this.stopAutoValidation();
|
|
1253
|
+
this.stopHeartbeat();
|
|
853
1254
|
this.stopConnectivityPolling();
|
|
854
1255
|
if (this.offlineRefreshTimer) {
|
|
855
1256
|
clearInterval(this.offlineRefreshTimer);
|
|
@@ -922,8 +1323,14 @@ var LicenseSeatSDK = class {
|
|
|
922
1323
|
this.stopAutoValidation();
|
|
923
1324
|
this.currentAutoLicenseKey = licenseKey;
|
|
924
1325
|
const validationInterval = this.config.autoValidateInterval;
|
|
1326
|
+
if (!validationInterval || validationInterval <= 0) {
|
|
1327
|
+
this.log("Auto-validation disabled (interval:", validationInterval, ")");
|
|
1328
|
+
return;
|
|
1329
|
+
}
|
|
925
1330
|
const performAndReschedule = () => {
|
|
926
|
-
this.validateLicense(licenseKey).
|
|
1331
|
+
this.validateLicense(licenseKey).then(() => {
|
|
1332
|
+
this.heartbeat().catch((err) => this.log("Heartbeat failed:", err));
|
|
1333
|
+
}).catch((err) => {
|
|
927
1334
|
this.log("Auto-validation failed:", err);
|
|
928
1335
|
this.emit("validation:auto-failed", { licenseKey, error: err });
|
|
929
1336
|
});
|
|
@@ -948,6 +1355,35 @@ var LicenseSeatSDK = class {
|
|
|
948
1355
|
this.emit("autovalidation:stopped");
|
|
949
1356
|
}
|
|
950
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
|
+
}
|
|
951
1387
|
/**
|
|
952
1388
|
* Start connectivity polling (when offline)
|
|
953
1389
|
* @returns {void}
|
|
@@ -958,10 +1394,12 @@ var LicenseSeatSDK = class {
|
|
|
958
1394
|
return;
|
|
959
1395
|
const healthCheck = async () => {
|
|
960
1396
|
try {
|
|
961
|
-
await fetch(`${this.config.apiBaseUrl}/health`, {
|
|
1397
|
+
const res = await fetch(`${this.config.apiBaseUrl}/health`, {
|
|
962
1398
|
method: "GET",
|
|
963
1399
|
credentials: "omit"
|
|
964
1400
|
});
|
|
1401
|
+
await res.text().catch(() => {
|
|
1402
|
+
});
|
|
965
1403
|
if (!this.online) {
|
|
966
1404
|
this.online = true;
|
|
967
1405
|
this.emit("network:online");
|
|
@@ -994,10 +1432,10 @@ var LicenseSeatSDK = class {
|
|
|
994
1432
|
// Offline License Management
|
|
995
1433
|
// ============================================================
|
|
996
1434
|
/**
|
|
997
|
-
*
|
|
998
|
-
*
|
|
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.
|
|
999
1438
|
* @returns {Promise<void>}
|
|
1000
|
-
* @private
|
|
1001
1439
|
*/
|
|
1002
1440
|
async syncOfflineAssets() {
|
|
1003
1441
|
if (this.syncingOfflineAssets || this.destroyed) {
|
|
@@ -1048,9 +1486,10 @@ var LicenseSeatSDK = class {
|
|
|
1048
1486
|
);
|
|
1049
1487
|
}
|
|
1050
1488
|
/**
|
|
1051
|
-
* 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}.
|
|
1052
1492
|
* @returns {Promise<import('./types.js').ValidationResult>}
|
|
1053
|
-
* @private
|
|
1054
1493
|
*/
|
|
1055
1494
|
async verifyCachedOffline() {
|
|
1056
1495
|
const signed = this.cache.getOfflineToken();
|
|
@@ -1183,12 +1622,20 @@ var LicenseSeatSDK = class {
|
|
|
1183
1622
|
"[Warning] No API key configured for LicenseSeat SDK. Authenticated endpoints will fail."
|
|
1184
1623
|
);
|
|
1185
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
|
+
}
|
|
1186
1633
|
for (let attempt = 0; attempt <= this.config.maxRetries; attempt++) {
|
|
1187
1634
|
try {
|
|
1188
1635
|
const response = await fetch(url, {
|
|
1189
|
-
method
|
|
1636
|
+
method,
|
|
1190
1637
|
headers,
|
|
1191
|
-
body:
|
|
1638
|
+
body: body ? JSON.stringify(body) : void 0,
|
|
1192
1639
|
credentials: "omit"
|
|
1193
1640
|
});
|
|
1194
1641
|
const data = await response.json();
|
|
@@ -1312,8 +1759,10 @@ export {
|
|
|
1312
1759
|
LicenseCache,
|
|
1313
1760
|
LicenseError,
|
|
1314
1761
|
LicenseSeatSDK,
|
|
1762
|
+
SDK_VERSION,
|
|
1315
1763
|
base64UrlDecode,
|
|
1316
1764
|
canonicalJsonStringify,
|
|
1765
|
+
collectTelemetry,
|
|
1317
1766
|
configure,
|
|
1318
1767
|
constantTimeEqual,
|
|
1319
1768
|
src_default as default,
|
|
@@ -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}
|
|
@@ -204,6 +215,14 @@ export class LicenseSeatSDK {
|
|
|
204
215
|
healthy: boolean;
|
|
205
216
|
api_version: string;
|
|
206
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
|
|
224
|
+
*/
|
|
225
|
+
heartbeat(): Promise<any | undefined>;
|
|
207
226
|
/**
|
|
208
227
|
* Clear all data and reset SDK state
|
|
209
228
|
* @returns {void}
|
|
@@ -251,6 +270,19 @@ export class LicenseSeatSDK {
|
|
|
251
270
|
* @private
|
|
252
271
|
*/
|
|
253
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;
|
|
254
286
|
/**
|
|
255
287
|
* Start connectivity polling (when offline)
|
|
256
288
|
* @returns {void}
|
|
@@ -264,12 +296,12 @@ export class LicenseSeatSDK {
|
|
|
264
296
|
*/
|
|
265
297
|
private stopConnectivityPolling;
|
|
266
298
|
/**
|
|
267
|
-
*
|
|
268
|
-
*
|
|
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.
|
|
269
302
|
* @returns {Promise<void>}
|
|
270
|
-
* @private
|
|
271
303
|
*/
|
|
272
|
-
|
|
304
|
+
syncOfflineAssets(): Promise<void>;
|
|
273
305
|
/**
|
|
274
306
|
* Schedule periodic offline license refresh
|
|
275
307
|
* @returns {void}
|
|
@@ -277,11 +309,12 @@ export class LicenseSeatSDK {
|
|
|
277
309
|
*/
|
|
278
310
|
private scheduleOfflineRefresh;
|
|
279
311
|
/**
|
|
280
|
-
* 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}.
|
|
281
315
|
* @returns {Promise<import('./types.js').ValidationResult>}
|
|
282
|
-
* @private
|
|
283
316
|
*/
|
|
284
|
-
|
|
317
|
+
verifyCachedOffline(): Promise<import("./types.js").ValidationResult>;
|
|
285
318
|
/**
|
|
286
319
|
* Quick offline verification using only local data (no network)
|
|
287
320
|
* Performs signature verification plus basic validity checks (expiry, license key match)
|