@antondziuin/talos-sdk 0.2.1 → 0.4.0
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 +22 -3
- package/dist/index.cjs +215 -123
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +15 -4
- package/dist/index.d.ts +15 -4
- package/dist/index.js +215 -123
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -457,6 +457,9 @@ declare class TalosClient {
|
|
|
457
457
|
* for weeks. A key revoked in the portal would never reach it.
|
|
458
458
|
*/
|
|
459
459
|
private keysetIssuedAt;
|
|
460
|
+
private keysetToken;
|
|
461
|
+
/** Invalidates online work started before a local licensing-mode change. */
|
|
462
|
+
private licensingGeneration;
|
|
460
463
|
/** The policy's heartbeat cadence, from the last heartbeat's signed claims. */
|
|
461
464
|
private heartbeatInterval;
|
|
462
465
|
private checkInTimer;
|
|
@@ -623,13 +626,13 @@ declare class TalosClient {
|
|
|
623
626
|
*/
|
|
624
627
|
createOfflineRequest(licenseKey?: string): SignedDoc;
|
|
625
628
|
/**
|
|
626
|
-
* Adopt
|
|
629
|
+
* Adopt a machine-bound or portable offline licence file.
|
|
627
630
|
*
|
|
628
631
|
* Everything is verified locally, because there is nothing to ask: the
|
|
629
632
|
* anchor-signed keyset travels inside the file, the verdict is signed by a key
|
|
630
|
-
* that keyset lists
|
|
631
|
-
*
|
|
632
|
-
* so later starts are licensed with no further ceremony.
|
|
633
|
+
* that keyset lists. Machine-bound files name this machine's fingerprint and
|
|
634
|
+
* public key; explicitly portable files need no machine request. The verdict
|
|
635
|
+
* is kept, so later starts are licensed with no further ceremony.
|
|
633
636
|
*
|
|
634
637
|
* **Throws** only for a file that is not a licence: unreadable, not one of
|
|
635
638
|
* ours, addressed to another product, or a broken signature. A file that *is*
|
|
@@ -881,6 +884,8 @@ declare class TalosClient {
|
|
|
881
884
|
*/
|
|
882
885
|
private fetchVerified;
|
|
883
886
|
private requireActivated;
|
|
887
|
+
/** Keep a newly imported offline licence authoritative over older requests. */
|
|
888
|
+
private offlineSince;
|
|
884
889
|
private post;
|
|
885
890
|
/**
|
|
886
891
|
* The abort signal for one licensing call, or `undefined` when disabled.
|
|
@@ -1088,6 +1093,12 @@ interface StoredBinding {
|
|
|
1088
1093
|
* a clock its owner controls, and it is the price of offline.
|
|
1089
1094
|
*/
|
|
1090
1095
|
offlineSeen?: number;
|
|
1096
|
+
/** Public on-chain registry manifest; never a license code or an authorization cache. */
|
|
1097
|
+
registryManifest?: string;
|
|
1098
|
+
/** Public identifier of a device-authenticated activation job that can be resumed without a code. */
|
|
1099
|
+
registryActivationId?: string;
|
|
1100
|
+
/** Public canonical head and per-license revision/revocation observations; never an authorization cache. */
|
|
1101
|
+
registryChainState?: string;
|
|
1091
1102
|
}
|
|
1092
1103
|
/**
|
|
1093
1104
|
* Where the binding lives, per product, following each OS's convention.
|
package/dist/index.d.ts
CHANGED
|
@@ -457,6 +457,9 @@ declare class TalosClient {
|
|
|
457
457
|
* for weeks. A key revoked in the portal would never reach it.
|
|
458
458
|
*/
|
|
459
459
|
private keysetIssuedAt;
|
|
460
|
+
private keysetToken;
|
|
461
|
+
/** Invalidates online work started before a local licensing-mode change. */
|
|
462
|
+
private licensingGeneration;
|
|
460
463
|
/** The policy's heartbeat cadence, from the last heartbeat's signed claims. */
|
|
461
464
|
private heartbeatInterval;
|
|
462
465
|
private checkInTimer;
|
|
@@ -623,13 +626,13 @@ declare class TalosClient {
|
|
|
623
626
|
*/
|
|
624
627
|
createOfflineRequest(licenseKey?: string): SignedDoc;
|
|
625
628
|
/**
|
|
626
|
-
* Adopt
|
|
629
|
+
* Adopt a machine-bound or portable offline licence file.
|
|
627
630
|
*
|
|
628
631
|
* Everything is verified locally, because there is nothing to ask: the
|
|
629
632
|
* anchor-signed keyset travels inside the file, the verdict is signed by a key
|
|
630
|
-
* that keyset lists
|
|
631
|
-
*
|
|
632
|
-
* so later starts are licensed with no further ceremony.
|
|
633
|
+
* that keyset lists. Machine-bound files name this machine's fingerprint and
|
|
634
|
+
* public key; explicitly portable files need no machine request. The verdict
|
|
635
|
+
* is kept, so later starts are licensed with no further ceremony.
|
|
633
636
|
*
|
|
634
637
|
* **Throws** only for a file that is not a licence: unreadable, not one of
|
|
635
638
|
* ours, addressed to another product, or a broken signature. A file that *is*
|
|
@@ -881,6 +884,8 @@ declare class TalosClient {
|
|
|
881
884
|
*/
|
|
882
885
|
private fetchVerified;
|
|
883
886
|
private requireActivated;
|
|
887
|
+
/** Keep a newly imported offline licence authoritative over older requests. */
|
|
888
|
+
private offlineSince;
|
|
884
889
|
private post;
|
|
885
890
|
/**
|
|
886
891
|
* The abort signal for one licensing call, or `undefined` when disabled.
|
|
@@ -1088,6 +1093,12 @@ interface StoredBinding {
|
|
|
1088
1093
|
* a clock its owner controls, and it is the price of offline.
|
|
1089
1094
|
*/
|
|
1090
1095
|
offlineSeen?: number;
|
|
1096
|
+
/** Public on-chain registry manifest; never a license code or an authorization cache. */
|
|
1097
|
+
registryManifest?: string;
|
|
1098
|
+
/** Public identifier of a device-authenticated activation job that can be resumed without a code. */
|
|
1099
|
+
registryActivationId?: string;
|
|
1100
|
+
/** Public canonical head and per-license revision/revocation observations; never an authorization cache. */
|
|
1101
|
+
registryChainState?: string;
|
|
1091
1102
|
}
|
|
1092
1103
|
/**
|
|
1093
1104
|
* Where the binding lives, per product, following each OS's convention.
|
package/dist/index.js
CHANGED
|
@@ -195,6 +195,9 @@ var StateStore = class {
|
|
|
195
195
|
}
|
|
196
196
|
if (typeof o["offlineToken"] === "string") b.offlineToken = o["offlineToken"];
|
|
197
197
|
if (typeof o["offlineSeen"] === "number") b.offlineSeen = o["offlineSeen"];
|
|
198
|
+
if (typeof o["registryManifest"] === "string" && o["registryManifest"].length <= 65536) b.registryManifest = o["registryManifest"];
|
|
199
|
+
if (typeof o["registryActivationId"] === "string" && o["registryActivationId"].length <= 36) b.registryActivationId = o["registryActivationId"];
|
|
200
|
+
if (typeof o["registryChainState"] === "string") b.registryChainState = o["registryChainState"];
|
|
198
201
|
return b.activationSecret || b.offlineToken || b.machineKey ? b : null;
|
|
199
202
|
} catch {
|
|
200
203
|
return null;
|
|
@@ -236,7 +239,7 @@ var StateStore = class {
|
|
|
236
239
|
};
|
|
237
240
|
|
|
238
241
|
// src/version.ts
|
|
239
|
-
var SDK_VERSION = "0.
|
|
242
|
+
var SDK_VERSION = "0.4.0";
|
|
240
243
|
var SDK_ID = `node/${SDK_VERSION}`;
|
|
241
244
|
|
|
242
245
|
// src/diagnostics.ts
|
|
@@ -464,6 +467,9 @@ var TalosClient = class {
|
|
|
464
467
|
* for weeks. A key revoked in the portal would never reach it.
|
|
465
468
|
*/
|
|
466
469
|
keysetIssuedAt = 0;
|
|
470
|
+
keysetToken = null;
|
|
471
|
+
/** Invalidates online work started before a local licensing-mode change. */
|
|
472
|
+
licensingGeneration = 0;
|
|
467
473
|
/** The policy's heartbeat cadence, from the last heartbeat's signed claims. */
|
|
468
474
|
heartbeatInterval = null;
|
|
469
475
|
checkInTimer = null;
|
|
@@ -586,12 +592,14 @@ var TalosClient = class {
|
|
|
586
592
|
*/
|
|
587
593
|
async refreshKeyset() {
|
|
588
594
|
if (!this.anchorKey) return;
|
|
595
|
+
const generation = this.licensingGeneration;
|
|
589
596
|
const body = await this.post("/v1/client/keyset", {
|
|
590
597
|
product_token: this.options.productToken
|
|
591
598
|
});
|
|
599
|
+
if (generation !== this.licensingGeneration) return;
|
|
592
600
|
this.adoptKeyset(body["keyset"]);
|
|
593
601
|
this.keysetFromCache = false;
|
|
594
|
-
if (
|
|
602
|
+
if (this.keysetToken) this.persist({ keyset: this.keysetToken });
|
|
595
603
|
}
|
|
596
604
|
/**
|
|
597
605
|
* Make sure a verification key exists before a response has to be verified.
|
|
@@ -642,7 +650,8 @@ var TalosClient = class {
|
|
|
642
650
|
if (!this.anchorKey || typeof token !== "string") return;
|
|
643
651
|
const claims = verifyKeyset(this.anchorKey, this.options.productToken, token);
|
|
644
652
|
const iat = claims["iat"];
|
|
645
|
-
|
|
653
|
+
const issuedAt = typeof iat === "number" ? iat : 0;
|
|
654
|
+
if (issuedAt < this.keysetIssuedAt || issuedAt === this.keysetIssuedAt && this.keysetToken !== null && token !== this.keysetToken) return;
|
|
646
655
|
const licenseKeys = [];
|
|
647
656
|
const releaseKeys = [];
|
|
648
657
|
for (const entry of asArray(claims["keys"])) {
|
|
@@ -654,6 +663,8 @@ var TalosClient = class {
|
|
|
654
663
|
}
|
|
655
664
|
this.keysetLicenseKeys = licenseKeys;
|
|
656
665
|
this.keysetReleaseKeys = releaseKeys;
|
|
666
|
+
this.keysetIssuedAt = issuedAt;
|
|
667
|
+
this.keysetToken = token;
|
|
657
668
|
}
|
|
658
669
|
/** Keys to verify license tokens with: the keyset if loaded, else the direct pin. */
|
|
659
670
|
licenseVerifyKeys() {
|
|
@@ -685,62 +696,71 @@ var TalosClient = class {
|
|
|
685
696
|
* elsewhere.
|
|
686
697
|
*/
|
|
687
698
|
async activate(licenseKey) {
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
699
|
+
const generation = this.licensingGeneration;
|
|
700
|
+
try {
|
|
701
|
+
if (this.isActivated && this.licenseKeyHash === hashLicenseKey(licenseKey)) {
|
|
702
|
+
try {
|
|
703
|
+
const state = await this.validate();
|
|
704
|
+
if (state.decision !== "deactivated") return state;
|
|
705
|
+
} catch (e) {
|
|
706
|
+
const stale = e instanceof TalosApiError && (e.statusCode === 401 || e.statusCode === 404);
|
|
707
|
+
if (!stale) throw e;
|
|
708
|
+
}
|
|
709
|
+
this.forgetBinding();
|
|
695
710
|
}
|
|
696
|
-
|
|
711
|
+
const nonce = newNonce();
|
|
712
|
+
const body = await this.post("/v1/client/activate", {
|
|
713
|
+
product_token: this.options.productToken,
|
|
714
|
+
license_key: licenseKey,
|
|
715
|
+
fingerprint: { composite: this.fingerprint, components: this.fp.components },
|
|
716
|
+
nonce,
|
|
717
|
+
hostname: this.reportedHostname(),
|
|
718
|
+
os: `${os3.type()} ${os3.release()}`,
|
|
719
|
+
arch: os3.arch(),
|
|
720
|
+
app_version: this.options.appVersion,
|
|
721
|
+
sdk_version: SDK_ID
|
|
722
|
+
});
|
|
723
|
+
const switched = this.offlineSince(generation);
|
|
724
|
+
if (switched) return switched;
|
|
725
|
+
this.adoptKeyset(body["keyset"]);
|
|
726
|
+
const claims = verifyToken(
|
|
727
|
+
this.licenseVerifyKeys(),
|
|
728
|
+
"activation",
|
|
729
|
+
this.options.productToken,
|
|
730
|
+
nonce,
|
|
731
|
+
body["activation_token"]
|
|
732
|
+
);
|
|
733
|
+
this.licenseKeyHash = hashLicenseKey(licenseKey);
|
|
734
|
+
this.activationSecret = body["activation_secret"];
|
|
735
|
+
this.machineId = body["machine_id"] ?? null;
|
|
736
|
+
this.binding = {
|
|
737
|
+
licenseKeyHash: this.licenseKeyHash,
|
|
738
|
+
activationSecret: this.activationSecret,
|
|
739
|
+
machineId: this.machineId ?? "",
|
|
740
|
+
fingerprint: this.fingerprint,
|
|
741
|
+
// A fresh activation invalidates any cached verdict for the previous
|
|
742
|
+
// binding; keep only the keyset, which is per product, not per machine.
|
|
743
|
+
keyset: this.keysetToken ?? this.binding?.keyset,
|
|
744
|
+
timeHighWater: this.binding?.timeHighWater,
|
|
745
|
+
// The machine's own identity survives an online activation, because it is
|
|
746
|
+
// an identity and not a verdict: an install that reaches the network once
|
|
747
|
+
// must not have to repeat the whole air-gapped ceremony with its vendor
|
|
748
|
+
// the next time it is unplugged. The clock mark survives for the same
|
|
749
|
+
// reason a mark exists at all — it only moves forward.
|
|
750
|
+
machineKey: this.binding?.machineKey,
|
|
751
|
+
machinePublicKey: this.binding?.machinePublicKey,
|
|
752
|
+
offlineSeen: this.binding?.offlineSeen
|
|
753
|
+
// The offline *file* does not: a live activation supersedes it, and
|
|
754
|
+
// keeping it would leave two answers to "what licenses this machine".
|
|
755
|
+
};
|
|
756
|
+
this.advanceClock(claims);
|
|
757
|
+
this.store?.save(this.binding);
|
|
758
|
+
return this.adopt(stateFromClaims(claims, "valid", false));
|
|
759
|
+
} catch (e) {
|
|
760
|
+
const switched = this.offlineSince(generation);
|
|
761
|
+
if (switched) return switched;
|
|
762
|
+
throw e;
|
|
697
763
|
}
|
|
698
|
-
const nonce = newNonce();
|
|
699
|
-
const body = await this.post("/v1/client/activate", {
|
|
700
|
-
product_token: this.options.productToken,
|
|
701
|
-
license_key: licenseKey,
|
|
702
|
-
fingerprint: { composite: this.fingerprint, components: this.fp.components },
|
|
703
|
-
nonce,
|
|
704
|
-
hostname: this.reportedHostname(),
|
|
705
|
-
os: `${os3.type()} ${os3.release()}`,
|
|
706
|
-
arch: os3.arch(),
|
|
707
|
-
app_version: this.options.appVersion,
|
|
708
|
-
sdk_version: SDK_ID
|
|
709
|
-
});
|
|
710
|
-
this.adoptKeyset(body["keyset"]);
|
|
711
|
-
const claims = verifyToken(
|
|
712
|
-
this.licenseVerifyKeys(),
|
|
713
|
-
"activation",
|
|
714
|
-
this.options.productToken,
|
|
715
|
-
nonce,
|
|
716
|
-
body["activation_token"]
|
|
717
|
-
);
|
|
718
|
-
this.licenseKeyHash = hashLicenseKey(licenseKey);
|
|
719
|
-
this.activationSecret = body["activation_secret"];
|
|
720
|
-
this.machineId = body["machine_id"] ?? null;
|
|
721
|
-
this.binding = {
|
|
722
|
-
licenseKeyHash: this.licenseKeyHash,
|
|
723
|
-
activationSecret: this.activationSecret,
|
|
724
|
-
machineId: this.machineId ?? "",
|
|
725
|
-
fingerprint: this.fingerprint,
|
|
726
|
-
// A fresh activation invalidates any cached verdict for the previous
|
|
727
|
-
// binding; keep only the keyset, which is per product, not per machine.
|
|
728
|
-
keyset: typeof body["keyset"] === "string" ? body["keyset"] : this.binding?.keyset,
|
|
729
|
-
timeHighWater: this.binding?.timeHighWater,
|
|
730
|
-
// The machine's own identity survives an online activation, because it is
|
|
731
|
-
// an identity and not a verdict: an install that reaches the network once
|
|
732
|
-
// must not have to repeat the whole air-gapped ceremony with its vendor
|
|
733
|
-
// the next time it is unplugged. The clock mark survives for the same
|
|
734
|
-
// reason a mark exists at all — it only moves forward.
|
|
735
|
-
machineKey: this.binding?.machineKey,
|
|
736
|
-
machinePublicKey: this.binding?.machinePublicKey,
|
|
737
|
-
offlineSeen: this.binding?.offlineSeen
|
|
738
|
-
// The offline *file* does not: a live activation supersedes it, and
|
|
739
|
-
// keeping it would leave two answers to "what licenses this machine".
|
|
740
|
-
};
|
|
741
|
-
this.advanceClock(claims);
|
|
742
|
-
this.store?.save(this.binding);
|
|
743
|
-
return this.adopt(stateFromClaims(claims, "valid", false));
|
|
744
764
|
}
|
|
745
765
|
/**
|
|
746
766
|
* This machine's own keypair, generated once and kept in the state file.
|
|
@@ -815,13 +835,13 @@ var TalosClient = class {
|
|
|
815
835
|
});
|
|
816
836
|
}
|
|
817
837
|
/**
|
|
818
|
-
* Adopt
|
|
838
|
+
* Adopt a machine-bound or portable offline licence file.
|
|
819
839
|
*
|
|
820
840
|
* Everything is verified locally, because there is nothing to ask: the
|
|
821
841
|
* anchor-signed keyset travels inside the file, the verdict is signed by a key
|
|
822
|
-
* that keyset lists
|
|
823
|
-
*
|
|
824
|
-
* so later starts are licensed with no further ceremony.
|
|
842
|
+
* that keyset lists. Machine-bound files name this machine's fingerprint and
|
|
843
|
+
* public key; explicitly portable files need no machine request. The verdict
|
|
844
|
+
* is kept, so later starts are licensed with no further ceremony.
|
|
825
845
|
*
|
|
826
846
|
* **Throws** only for a file that is not a licence: unreadable, not one of
|
|
827
847
|
* ours, addressed to another product, or a broken signature. A file that *is*
|
|
@@ -844,8 +864,27 @@ var TalosClient = class {
|
|
|
844
864
|
if (doc["kind"] !== LICENSE_FILE_KIND || typeof doc["token"] !== "string") {
|
|
845
865
|
throw new TalosError("not a Talos offline licence file");
|
|
846
866
|
}
|
|
847
|
-
|
|
848
|
-
|
|
867
|
+
const previous = {
|
|
868
|
+
license: this.keysetLicenseKeys,
|
|
869
|
+
release: this.keysetReleaseKeys,
|
|
870
|
+
iat: this.keysetIssuedAt,
|
|
871
|
+
token: this.keysetToken
|
|
872
|
+
};
|
|
873
|
+
const restore = () => {
|
|
874
|
+
this.keysetLicenseKeys = previous.license;
|
|
875
|
+
this.keysetReleaseKeys = previous.release;
|
|
876
|
+
this.keysetIssuedAt = previous.iat;
|
|
877
|
+
this.keysetToken = previous.token;
|
|
878
|
+
};
|
|
879
|
+
try {
|
|
880
|
+
if (typeof doc["keyset"] === "string") this.adoptKeyset(doc["keyset"]);
|
|
881
|
+
const state = this.adoptOfflineToken(doc["token"], this.keysetToken);
|
|
882
|
+
if (!state.isValid) restore();
|
|
883
|
+
return state;
|
|
884
|
+
} catch (e) {
|
|
885
|
+
restore();
|
|
886
|
+
throw e;
|
|
887
|
+
}
|
|
849
888
|
}
|
|
850
889
|
/**
|
|
851
890
|
* Hand this machine's air-gapped seat back.
|
|
@@ -867,9 +906,7 @@ var TalosClient = class {
|
|
|
867
906
|
*/
|
|
868
907
|
createOfflineDeactivation() {
|
|
869
908
|
const token = this.binding?.offlineToken;
|
|
870
|
-
|
|
871
|
-
const pub = this.binding?.machinePublicKey;
|
|
872
|
-
if (!token || !key || !pub) {
|
|
909
|
+
if (!token) {
|
|
873
910
|
throw new TalosError("no offline licence to hand back");
|
|
874
911
|
}
|
|
875
912
|
let claims;
|
|
@@ -883,6 +920,12 @@ var TalosClient = class {
|
|
|
883
920
|
} catch {
|
|
884
921
|
throw new TalosError("the stored offline licence no longer verifies");
|
|
885
922
|
}
|
|
923
|
+
if (claims["binding"] === "portable") {
|
|
924
|
+
throw new TalosError("portable offline licences have no bound seat to return");
|
|
925
|
+
}
|
|
926
|
+
const key = this.binding?.machineKey;
|
|
927
|
+
const pub = this.binding?.machinePublicKey;
|
|
928
|
+
if (!key || !pub) throw new TalosError("no offline licence to hand back");
|
|
886
929
|
this.binding = { ...this.bindingBase(), offlineToken: void 0 };
|
|
887
930
|
this.current = null;
|
|
888
931
|
this.store?.save(this.binding);
|
|
@@ -907,38 +950,58 @@ var TalosClient = class {
|
|
|
907
950
|
token
|
|
908
951
|
);
|
|
909
952
|
const mismatch = () => this.adopt(new LicenseState("machine_mismatch", {}, null, true, null, null));
|
|
910
|
-
|
|
953
|
+
const lic = claims["lic"] ?? {};
|
|
954
|
+
const features = lic["features"] ?? {};
|
|
955
|
+
const licExp = num(lic["lic_exp"]);
|
|
956
|
+
const exp = num(claims["exp"]);
|
|
957
|
+
const perpetual = claims["perpetual"] === true && claims["exp"] === null && lic["lic_exp"] === null;
|
|
958
|
+
const portable = claims["binding"] === "portable";
|
|
959
|
+
if (portable) {
|
|
960
|
+
const present = (key) => Object.hasOwn(claims, key);
|
|
961
|
+
const named = (key) => typeof claims[key] === "string" && claims[key].trim().length > 0;
|
|
962
|
+
if (!perpetual || claims["decision"] !== "valid" || present("fph") || present("mpk") || present("mid") || !named("fid") || !named("sub")) return mismatch();
|
|
963
|
+
} else if (claims["binding"] !== void 0 && claims["binding"] !== "machine") {
|
|
964
|
+
return mismatch();
|
|
965
|
+
}
|
|
966
|
+
if (!portable && (typeof claims["mpk"] !== "string" || claims["mpk"] !== this.binding?.machinePublicKey)) {
|
|
911
967
|
return mismatch();
|
|
912
968
|
}
|
|
913
|
-
if (claims["fph"] !== this.fingerprint) return mismatch();
|
|
969
|
+
if (!portable && claims["fph"] !== this.fingerprint) return mismatch();
|
|
914
970
|
const now = nowSeconds();
|
|
915
971
|
const mark = Math.max(this.binding?.timeHighWater ?? 0, this.binding?.offlineSeen ?? 0);
|
|
916
972
|
if (mark > 0 && now + CLOCK_SKEW_SECONDS2 < mark) {
|
|
917
973
|
return this.adopt(new LicenseState("clock_tampered", {}, null, true, null, null));
|
|
918
974
|
}
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
975
|
+
if (!perpetual && (exp === null || now > exp)) {
|
|
976
|
+
return this.adopt(
|
|
977
|
+
new LicenseState("offline_file_expired", features, licExp, true, null, exp)
|
|
978
|
+
);
|
|
979
|
+
}
|
|
980
|
+
if (licExp !== null && now > licExp) {
|
|
981
|
+
return this.adopt(new LicenseState("expired", features, licExp, true, null, exp));
|
|
982
|
+
}
|
|
983
|
+
this.licensingGeneration += 1;
|
|
984
|
+
this.licenseKeyHash = null;
|
|
985
|
+
this.activationSecret = null;
|
|
986
|
+
this.heartbeatInterval = null;
|
|
987
|
+
this.leaseGeneration += 1;
|
|
988
|
+
this.stopRenew();
|
|
989
|
+
this.lease = null;
|
|
990
|
+
this.machineId = !portable && typeof claims["mid"] === "string" ? claims["mid"] : null;
|
|
924
991
|
const base = this.bindingBase();
|
|
925
992
|
this.binding = {
|
|
926
993
|
...base,
|
|
927
|
-
|
|
994
|
+
licenseKeyHash: "",
|
|
995
|
+
activationSecret: "",
|
|
996
|
+
validation: void 0,
|
|
997
|
+
machineId: this.machineId ?? "",
|
|
998
|
+
fingerprint: this.fingerprint,
|
|
928
999
|
offlineToken: token,
|
|
929
1000
|
offlineSeen: Math.max(base.offlineSeen ?? 0, now),
|
|
930
1001
|
...typeof keyset === "string" ? { keyset } : {}
|
|
931
1002
|
};
|
|
932
1003
|
this.advanceClock(claims);
|
|
933
1004
|
this.store?.save(this.binding);
|
|
934
|
-
if (exp === null || now > exp) {
|
|
935
|
-
return this.adopt(
|
|
936
|
-
new LicenseState("offline_file_expired", features, licExp, true, null, exp)
|
|
937
|
-
);
|
|
938
|
-
}
|
|
939
|
-
if (licExp !== null && now > licExp) {
|
|
940
|
-
return this.adopt(new LicenseState("expired", features, licExp, true, null, exp));
|
|
941
|
-
}
|
|
942
1005
|
return this.adopt(new LicenseState("valid", features, licExp, true, null, exp));
|
|
943
1006
|
}
|
|
944
1007
|
/**
|
|
@@ -1051,6 +1114,7 @@ var TalosClient = class {
|
|
|
1051
1114
|
}
|
|
1052
1115
|
/** Drop the machine binding, in memory and on disk. */
|
|
1053
1116
|
forgetBinding() {
|
|
1117
|
+
this.licensingGeneration += 1;
|
|
1054
1118
|
this.licenseKeyHash = null;
|
|
1055
1119
|
this.activationSecret = null;
|
|
1056
1120
|
this.machineId = null;
|
|
@@ -1084,8 +1148,11 @@ var TalosClient = class {
|
|
|
1084
1148
|
return this.adoptOfflineToken(this.binding.offlineToken);
|
|
1085
1149
|
}
|
|
1086
1150
|
this.requireActivated();
|
|
1151
|
+
const generation = this.licensingGeneration;
|
|
1087
1152
|
try {
|
|
1088
1153
|
await this.ensureKeyset();
|
|
1154
|
+
const switchedBeforeRequest = this.offlineSince(generation);
|
|
1155
|
+
if (switchedBeforeRequest) return switchedBeforeRequest;
|
|
1089
1156
|
const nonce = newNonce();
|
|
1090
1157
|
const body = await this.post("/v1/client/validate", {
|
|
1091
1158
|
product_token: this.options.productToken,
|
|
@@ -1099,6 +1166,8 @@ var TalosClient = class {
|
|
|
1099
1166
|
app_version: this.options.appVersion,
|
|
1100
1167
|
sdk_version: SDK_ID
|
|
1101
1168
|
});
|
|
1169
|
+
const switched = this.offlineSince(generation);
|
|
1170
|
+
if (switched) return switched;
|
|
1102
1171
|
const token = body["validation_token"];
|
|
1103
1172
|
const claims = verifyToken(
|
|
1104
1173
|
this.licenseVerifyKeys(),
|
|
@@ -1112,6 +1181,8 @@ var TalosClient = class {
|
|
|
1112
1181
|
this.persist({ validation: token, timeHighWater: this.binding?.timeHighWater });
|
|
1113
1182
|
return this.adopt(stateFromClaims(claims, decision, false));
|
|
1114
1183
|
} catch (e) {
|
|
1184
|
+
const switched = this.offlineSince(generation);
|
|
1185
|
+
if (switched) return switched;
|
|
1115
1186
|
if (e instanceof TalosApiError) throw e;
|
|
1116
1187
|
const offline = this.offlineDecision();
|
|
1117
1188
|
if (!offline) throw e;
|
|
@@ -1184,12 +1255,13 @@ var TalosClient = class {
|
|
|
1184
1255
|
}
|
|
1185
1256
|
async deactivate() {
|
|
1186
1257
|
this.requireActivated();
|
|
1258
|
+
const generation = this.licensingGeneration;
|
|
1187
1259
|
await this.post("/v1/client/deactivate", {
|
|
1188
1260
|
product_token: this.options.productToken,
|
|
1189
1261
|
fingerprint: { composite: this.fingerprint, components: this.fp.components },
|
|
1190
1262
|
activation_secret: this.activationSecret
|
|
1191
1263
|
});
|
|
1192
|
-
this.forgetBinding();
|
|
1264
|
+
if (generation === this.licensingGeneration) this.forgetBinding();
|
|
1193
1265
|
}
|
|
1194
1266
|
/**
|
|
1195
1267
|
* Send a heartbeat: refreshes this machine's last-seen state, feeds the
|
|
@@ -1213,46 +1285,59 @@ var TalosClient = class {
|
|
|
1213
1285
|
}
|
|
1214
1286
|
async heartbeat() {
|
|
1215
1287
|
this.requireActivated();
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1288
|
+
const generation = this.licensingGeneration;
|
|
1289
|
+
try {
|
|
1290
|
+
await this.ensureKeyset();
|
|
1291
|
+
const switchedBeforeRequest = this.offlineSince(generation);
|
|
1292
|
+
if (switchedBeforeRequest) return switchedBeforeRequest;
|
|
1293
|
+
const nonce = newNonce();
|
|
1294
|
+
const body = await this.post("/v1/client/heartbeat", {
|
|
1295
|
+
product_token: this.options.productToken,
|
|
1296
|
+
fingerprint: { composite: this.fingerprint, components: this.fp.components },
|
|
1297
|
+
activation_secret: this.activationSecret,
|
|
1298
|
+
nonce,
|
|
1299
|
+
app_version: this.options.appVersion,
|
|
1300
|
+
os: `${os3.type()} ${os3.release()}`,
|
|
1301
|
+
arch: os3.arch(),
|
|
1302
|
+
sdk_version: SDK_ID,
|
|
1303
|
+
// Naming the channel is what asks the server for the update hint, and it
|
|
1304
|
+
// costs two queries there — so it is asked for only when somebody is
|
|
1305
|
+
// listening for the answer.
|
|
1306
|
+
update_channel: this.listeners.get("updateAvailable")?.size ? this.updateChannel() : void 0
|
|
1307
|
+
});
|
|
1308
|
+
const switched = this.offlineSince(generation);
|
|
1309
|
+
if (switched) return switched;
|
|
1310
|
+
const claims = verifyToken(
|
|
1311
|
+
this.licenseVerifyKeys(),
|
|
1312
|
+
"heartbeat",
|
|
1313
|
+
this.options.productToken,
|
|
1314
|
+
nonce,
|
|
1315
|
+
body["heartbeat_token"]
|
|
1316
|
+
);
|
|
1317
|
+
const decision = typeof claims["decision"] === "string" ? claims["decision"] : "invalid";
|
|
1318
|
+
const interval = num(claims["heartbeat_interval_s"]);
|
|
1319
|
+
if (interval !== null && interval > 0) this.heartbeatInterval = interval;
|
|
1320
|
+
this.advanceClock(claims);
|
|
1321
|
+
this.persist({ timeHighWater: this.binding?.timeHighWater });
|
|
1322
|
+
const state = this.adopt(
|
|
1323
|
+
new LicenseState(
|
|
1324
|
+
decision,
|
|
1325
|
+
this.current?.features ?? {},
|
|
1326
|
+
this.current?.licenseExpiresAt ?? null,
|
|
1327
|
+
false,
|
|
1328
|
+
this.current?.revalidateAfter ?? null,
|
|
1329
|
+
this.current?.graceUntil ?? null
|
|
1330
|
+
)
|
|
1331
|
+
);
|
|
1332
|
+
await this.announceUpdate(claims);
|
|
1333
|
+
const switchedAfterUpdate = this.offlineSince(generation);
|
|
1334
|
+
if (switchedAfterUpdate) return switchedAfterUpdate;
|
|
1335
|
+
return state;
|
|
1336
|
+
} catch (e) {
|
|
1337
|
+
const switched = this.offlineSince(generation);
|
|
1338
|
+
if (switched) return switched;
|
|
1339
|
+
throw e;
|
|
1340
|
+
}
|
|
1256
1341
|
}
|
|
1257
1342
|
/** The channel this install follows; `stable` unless set. */
|
|
1258
1343
|
updateChannel() {
|
|
@@ -1637,6 +1722,13 @@ var TalosClient = class {
|
|
|
1637
1722
|
throw new TalosError("not activated: call activate() first");
|
|
1638
1723
|
}
|
|
1639
1724
|
}
|
|
1725
|
+
/** Keep a newly imported offline licence authoritative over older requests. */
|
|
1726
|
+
offlineSince(generation) {
|
|
1727
|
+
if (generation === this.licensingGeneration || this.activationSecret || !this.binding?.offlineToken) {
|
|
1728
|
+
return null;
|
|
1729
|
+
}
|
|
1730
|
+
return this.adoptOfflineToken(this.binding.offlineToken);
|
|
1731
|
+
}
|
|
1640
1732
|
post(path, body) {
|
|
1641
1733
|
return this.request("POST", path, body);
|
|
1642
1734
|
}
|