@antondziuin/talos-sdk 0.2.1 → 0.3.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 +212 -123
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +9 -4
- package/dist/index.d.ts +9 -4
- package/dist/index.js +212 -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.
|
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.
|
package/dist/index.js
CHANGED
|
@@ -236,7 +236,7 @@ var StateStore = class {
|
|
|
236
236
|
};
|
|
237
237
|
|
|
238
238
|
// src/version.ts
|
|
239
|
-
var SDK_VERSION = "0.
|
|
239
|
+
var SDK_VERSION = "0.3.0";
|
|
240
240
|
var SDK_ID = `node/${SDK_VERSION}`;
|
|
241
241
|
|
|
242
242
|
// src/diagnostics.ts
|
|
@@ -464,6 +464,9 @@ var TalosClient = class {
|
|
|
464
464
|
* for weeks. A key revoked in the portal would never reach it.
|
|
465
465
|
*/
|
|
466
466
|
keysetIssuedAt = 0;
|
|
467
|
+
keysetToken = null;
|
|
468
|
+
/** Invalidates online work started before a local licensing-mode change. */
|
|
469
|
+
licensingGeneration = 0;
|
|
467
470
|
/** The policy's heartbeat cadence, from the last heartbeat's signed claims. */
|
|
468
471
|
heartbeatInterval = null;
|
|
469
472
|
checkInTimer = null;
|
|
@@ -586,12 +589,14 @@ var TalosClient = class {
|
|
|
586
589
|
*/
|
|
587
590
|
async refreshKeyset() {
|
|
588
591
|
if (!this.anchorKey) return;
|
|
592
|
+
const generation = this.licensingGeneration;
|
|
589
593
|
const body = await this.post("/v1/client/keyset", {
|
|
590
594
|
product_token: this.options.productToken
|
|
591
595
|
});
|
|
596
|
+
if (generation !== this.licensingGeneration) return;
|
|
592
597
|
this.adoptKeyset(body["keyset"]);
|
|
593
598
|
this.keysetFromCache = false;
|
|
594
|
-
if (
|
|
599
|
+
if (this.keysetToken) this.persist({ keyset: this.keysetToken });
|
|
595
600
|
}
|
|
596
601
|
/**
|
|
597
602
|
* Make sure a verification key exists before a response has to be verified.
|
|
@@ -642,7 +647,8 @@ var TalosClient = class {
|
|
|
642
647
|
if (!this.anchorKey || typeof token !== "string") return;
|
|
643
648
|
const claims = verifyKeyset(this.anchorKey, this.options.productToken, token);
|
|
644
649
|
const iat = claims["iat"];
|
|
645
|
-
|
|
650
|
+
const issuedAt = typeof iat === "number" ? iat : 0;
|
|
651
|
+
if (issuedAt < this.keysetIssuedAt || issuedAt === this.keysetIssuedAt && this.keysetToken !== null && token !== this.keysetToken) return;
|
|
646
652
|
const licenseKeys = [];
|
|
647
653
|
const releaseKeys = [];
|
|
648
654
|
for (const entry of asArray(claims["keys"])) {
|
|
@@ -654,6 +660,8 @@ var TalosClient = class {
|
|
|
654
660
|
}
|
|
655
661
|
this.keysetLicenseKeys = licenseKeys;
|
|
656
662
|
this.keysetReleaseKeys = releaseKeys;
|
|
663
|
+
this.keysetIssuedAt = issuedAt;
|
|
664
|
+
this.keysetToken = token;
|
|
657
665
|
}
|
|
658
666
|
/** Keys to verify license tokens with: the keyset if loaded, else the direct pin. */
|
|
659
667
|
licenseVerifyKeys() {
|
|
@@ -685,62 +693,71 @@ var TalosClient = class {
|
|
|
685
693
|
* elsewhere.
|
|
686
694
|
*/
|
|
687
695
|
async activate(licenseKey) {
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
696
|
+
const generation = this.licensingGeneration;
|
|
697
|
+
try {
|
|
698
|
+
if (this.isActivated && this.licenseKeyHash === hashLicenseKey(licenseKey)) {
|
|
699
|
+
try {
|
|
700
|
+
const state = await this.validate();
|
|
701
|
+
if (state.decision !== "deactivated") return state;
|
|
702
|
+
} catch (e) {
|
|
703
|
+
const stale = e instanceof TalosApiError && (e.statusCode === 401 || e.statusCode === 404);
|
|
704
|
+
if (!stale) throw e;
|
|
705
|
+
}
|
|
706
|
+
this.forgetBinding();
|
|
695
707
|
}
|
|
696
|
-
|
|
708
|
+
const nonce = newNonce();
|
|
709
|
+
const body = await this.post("/v1/client/activate", {
|
|
710
|
+
product_token: this.options.productToken,
|
|
711
|
+
license_key: licenseKey,
|
|
712
|
+
fingerprint: { composite: this.fingerprint, components: this.fp.components },
|
|
713
|
+
nonce,
|
|
714
|
+
hostname: this.reportedHostname(),
|
|
715
|
+
os: `${os3.type()} ${os3.release()}`,
|
|
716
|
+
arch: os3.arch(),
|
|
717
|
+
app_version: this.options.appVersion,
|
|
718
|
+
sdk_version: SDK_ID
|
|
719
|
+
});
|
|
720
|
+
const switched = this.offlineSince(generation);
|
|
721
|
+
if (switched) return switched;
|
|
722
|
+
this.adoptKeyset(body["keyset"]);
|
|
723
|
+
const claims = verifyToken(
|
|
724
|
+
this.licenseVerifyKeys(),
|
|
725
|
+
"activation",
|
|
726
|
+
this.options.productToken,
|
|
727
|
+
nonce,
|
|
728
|
+
body["activation_token"]
|
|
729
|
+
);
|
|
730
|
+
this.licenseKeyHash = hashLicenseKey(licenseKey);
|
|
731
|
+
this.activationSecret = body["activation_secret"];
|
|
732
|
+
this.machineId = body["machine_id"] ?? null;
|
|
733
|
+
this.binding = {
|
|
734
|
+
licenseKeyHash: this.licenseKeyHash,
|
|
735
|
+
activationSecret: this.activationSecret,
|
|
736
|
+
machineId: this.machineId ?? "",
|
|
737
|
+
fingerprint: this.fingerprint,
|
|
738
|
+
// A fresh activation invalidates any cached verdict for the previous
|
|
739
|
+
// binding; keep only the keyset, which is per product, not per machine.
|
|
740
|
+
keyset: this.keysetToken ?? this.binding?.keyset,
|
|
741
|
+
timeHighWater: this.binding?.timeHighWater,
|
|
742
|
+
// The machine's own identity survives an online activation, because it is
|
|
743
|
+
// an identity and not a verdict: an install that reaches the network once
|
|
744
|
+
// must not have to repeat the whole air-gapped ceremony with its vendor
|
|
745
|
+
// the next time it is unplugged. The clock mark survives for the same
|
|
746
|
+
// reason a mark exists at all — it only moves forward.
|
|
747
|
+
machineKey: this.binding?.machineKey,
|
|
748
|
+
machinePublicKey: this.binding?.machinePublicKey,
|
|
749
|
+
offlineSeen: this.binding?.offlineSeen
|
|
750
|
+
// The offline *file* does not: a live activation supersedes it, and
|
|
751
|
+
// keeping it would leave two answers to "what licenses this machine".
|
|
752
|
+
};
|
|
753
|
+
this.advanceClock(claims);
|
|
754
|
+
this.store?.save(this.binding);
|
|
755
|
+
return this.adopt(stateFromClaims(claims, "valid", false));
|
|
756
|
+
} catch (e) {
|
|
757
|
+
const switched = this.offlineSince(generation);
|
|
758
|
+
if (switched) return switched;
|
|
759
|
+
throw e;
|
|
697
760
|
}
|
|
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
761
|
}
|
|
745
762
|
/**
|
|
746
763
|
* This machine's own keypair, generated once and kept in the state file.
|
|
@@ -815,13 +832,13 @@ var TalosClient = class {
|
|
|
815
832
|
});
|
|
816
833
|
}
|
|
817
834
|
/**
|
|
818
|
-
* Adopt
|
|
835
|
+
* Adopt a machine-bound or portable offline licence file.
|
|
819
836
|
*
|
|
820
837
|
* Everything is verified locally, because there is nothing to ask: the
|
|
821
838
|
* 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.
|
|
839
|
+
* that keyset lists. Machine-bound files name this machine's fingerprint and
|
|
840
|
+
* public key; explicitly portable files need no machine request. The verdict
|
|
841
|
+
* is kept, so later starts are licensed with no further ceremony.
|
|
825
842
|
*
|
|
826
843
|
* **Throws** only for a file that is not a licence: unreadable, not one of
|
|
827
844
|
* ours, addressed to another product, or a broken signature. A file that *is*
|
|
@@ -844,8 +861,27 @@ var TalosClient = class {
|
|
|
844
861
|
if (doc["kind"] !== LICENSE_FILE_KIND || typeof doc["token"] !== "string") {
|
|
845
862
|
throw new TalosError("not a Talos offline licence file");
|
|
846
863
|
}
|
|
847
|
-
|
|
848
|
-
|
|
864
|
+
const previous = {
|
|
865
|
+
license: this.keysetLicenseKeys,
|
|
866
|
+
release: this.keysetReleaseKeys,
|
|
867
|
+
iat: this.keysetIssuedAt,
|
|
868
|
+
token: this.keysetToken
|
|
869
|
+
};
|
|
870
|
+
const restore = () => {
|
|
871
|
+
this.keysetLicenseKeys = previous.license;
|
|
872
|
+
this.keysetReleaseKeys = previous.release;
|
|
873
|
+
this.keysetIssuedAt = previous.iat;
|
|
874
|
+
this.keysetToken = previous.token;
|
|
875
|
+
};
|
|
876
|
+
try {
|
|
877
|
+
if (typeof doc["keyset"] === "string") this.adoptKeyset(doc["keyset"]);
|
|
878
|
+
const state = this.adoptOfflineToken(doc["token"], this.keysetToken);
|
|
879
|
+
if (!state.isValid) restore();
|
|
880
|
+
return state;
|
|
881
|
+
} catch (e) {
|
|
882
|
+
restore();
|
|
883
|
+
throw e;
|
|
884
|
+
}
|
|
849
885
|
}
|
|
850
886
|
/**
|
|
851
887
|
* Hand this machine's air-gapped seat back.
|
|
@@ -867,9 +903,7 @@ var TalosClient = class {
|
|
|
867
903
|
*/
|
|
868
904
|
createOfflineDeactivation() {
|
|
869
905
|
const token = this.binding?.offlineToken;
|
|
870
|
-
|
|
871
|
-
const pub = this.binding?.machinePublicKey;
|
|
872
|
-
if (!token || !key || !pub) {
|
|
906
|
+
if (!token) {
|
|
873
907
|
throw new TalosError("no offline licence to hand back");
|
|
874
908
|
}
|
|
875
909
|
let claims;
|
|
@@ -883,6 +917,12 @@ var TalosClient = class {
|
|
|
883
917
|
} catch {
|
|
884
918
|
throw new TalosError("the stored offline licence no longer verifies");
|
|
885
919
|
}
|
|
920
|
+
if (claims["binding"] === "portable") {
|
|
921
|
+
throw new TalosError("portable offline licences have no bound seat to return");
|
|
922
|
+
}
|
|
923
|
+
const key = this.binding?.machineKey;
|
|
924
|
+
const pub = this.binding?.machinePublicKey;
|
|
925
|
+
if (!key || !pub) throw new TalosError("no offline licence to hand back");
|
|
886
926
|
this.binding = { ...this.bindingBase(), offlineToken: void 0 };
|
|
887
927
|
this.current = null;
|
|
888
928
|
this.store?.save(this.binding);
|
|
@@ -907,38 +947,58 @@ var TalosClient = class {
|
|
|
907
947
|
token
|
|
908
948
|
);
|
|
909
949
|
const mismatch = () => this.adopt(new LicenseState("machine_mismatch", {}, null, true, null, null));
|
|
910
|
-
|
|
950
|
+
const lic = claims["lic"] ?? {};
|
|
951
|
+
const features = lic["features"] ?? {};
|
|
952
|
+
const licExp = num(lic["lic_exp"]);
|
|
953
|
+
const exp = num(claims["exp"]);
|
|
954
|
+
const perpetual = claims["perpetual"] === true && claims["exp"] === null && lic["lic_exp"] === null;
|
|
955
|
+
const portable = claims["binding"] === "portable";
|
|
956
|
+
if (portable) {
|
|
957
|
+
const present = (key) => Object.hasOwn(claims, key);
|
|
958
|
+
const named = (key) => typeof claims[key] === "string" && claims[key].trim().length > 0;
|
|
959
|
+
if (!perpetual || claims["decision"] !== "valid" || present("fph") || present("mpk") || present("mid") || !named("fid") || !named("sub")) return mismatch();
|
|
960
|
+
} else if (claims["binding"] !== void 0 && claims["binding"] !== "machine") {
|
|
961
|
+
return mismatch();
|
|
962
|
+
}
|
|
963
|
+
if (!portable && (typeof claims["mpk"] !== "string" || claims["mpk"] !== this.binding?.machinePublicKey)) {
|
|
911
964
|
return mismatch();
|
|
912
965
|
}
|
|
913
|
-
if (claims["fph"] !== this.fingerprint) return mismatch();
|
|
966
|
+
if (!portable && claims["fph"] !== this.fingerprint) return mismatch();
|
|
914
967
|
const now = nowSeconds();
|
|
915
968
|
const mark = Math.max(this.binding?.timeHighWater ?? 0, this.binding?.offlineSeen ?? 0);
|
|
916
969
|
if (mark > 0 && now + CLOCK_SKEW_SECONDS2 < mark) {
|
|
917
970
|
return this.adopt(new LicenseState("clock_tampered", {}, null, true, null, null));
|
|
918
971
|
}
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
972
|
+
if (!perpetual && (exp === null || now > exp)) {
|
|
973
|
+
return this.adopt(
|
|
974
|
+
new LicenseState("offline_file_expired", features, licExp, true, null, exp)
|
|
975
|
+
);
|
|
976
|
+
}
|
|
977
|
+
if (licExp !== null && now > licExp) {
|
|
978
|
+
return this.adopt(new LicenseState("expired", features, licExp, true, null, exp));
|
|
979
|
+
}
|
|
980
|
+
this.licensingGeneration += 1;
|
|
981
|
+
this.licenseKeyHash = null;
|
|
982
|
+
this.activationSecret = null;
|
|
983
|
+
this.heartbeatInterval = null;
|
|
984
|
+
this.leaseGeneration += 1;
|
|
985
|
+
this.stopRenew();
|
|
986
|
+
this.lease = null;
|
|
987
|
+
this.machineId = !portable && typeof claims["mid"] === "string" ? claims["mid"] : null;
|
|
924
988
|
const base = this.bindingBase();
|
|
925
989
|
this.binding = {
|
|
926
990
|
...base,
|
|
927
|
-
|
|
991
|
+
licenseKeyHash: "",
|
|
992
|
+
activationSecret: "",
|
|
993
|
+
validation: void 0,
|
|
994
|
+
machineId: this.machineId ?? "",
|
|
995
|
+
fingerprint: this.fingerprint,
|
|
928
996
|
offlineToken: token,
|
|
929
997
|
offlineSeen: Math.max(base.offlineSeen ?? 0, now),
|
|
930
998
|
...typeof keyset === "string" ? { keyset } : {}
|
|
931
999
|
};
|
|
932
1000
|
this.advanceClock(claims);
|
|
933
1001
|
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
1002
|
return this.adopt(new LicenseState("valid", features, licExp, true, null, exp));
|
|
943
1003
|
}
|
|
944
1004
|
/**
|
|
@@ -1051,6 +1111,7 @@ var TalosClient = class {
|
|
|
1051
1111
|
}
|
|
1052
1112
|
/** Drop the machine binding, in memory and on disk. */
|
|
1053
1113
|
forgetBinding() {
|
|
1114
|
+
this.licensingGeneration += 1;
|
|
1054
1115
|
this.licenseKeyHash = null;
|
|
1055
1116
|
this.activationSecret = null;
|
|
1056
1117
|
this.machineId = null;
|
|
@@ -1084,8 +1145,11 @@ var TalosClient = class {
|
|
|
1084
1145
|
return this.adoptOfflineToken(this.binding.offlineToken);
|
|
1085
1146
|
}
|
|
1086
1147
|
this.requireActivated();
|
|
1148
|
+
const generation = this.licensingGeneration;
|
|
1087
1149
|
try {
|
|
1088
1150
|
await this.ensureKeyset();
|
|
1151
|
+
const switchedBeforeRequest = this.offlineSince(generation);
|
|
1152
|
+
if (switchedBeforeRequest) return switchedBeforeRequest;
|
|
1089
1153
|
const nonce = newNonce();
|
|
1090
1154
|
const body = await this.post("/v1/client/validate", {
|
|
1091
1155
|
product_token: this.options.productToken,
|
|
@@ -1099,6 +1163,8 @@ var TalosClient = class {
|
|
|
1099
1163
|
app_version: this.options.appVersion,
|
|
1100
1164
|
sdk_version: SDK_ID
|
|
1101
1165
|
});
|
|
1166
|
+
const switched = this.offlineSince(generation);
|
|
1167
|
+
if (switched) return switched;
|
|
1102
1168
|
const token = body["validation_token"];
|
|
1103
1169
|
const claims = verifyToken(
|
|
1104
1170
|
this.licenseVerifyKeys(),
|
|
@@ -1112,6 +1178,8 @@ var TalosClient = class {
|
|
|
1112
1178
|
this.persist({ validation: token, timeHighWater: this.binding?.timeHighWater });
|
|
1113
1179
|
return this.adopt(stateFromClaims(claims, decision, false));
|
|
1114
1180
|
} catch (e) {
|
|
1181
|
+
const switched = this.offlineSince(generation);
|
|
1182
|
+
if (switched) return switched;
|
|
1115
1183
|
if (e instanceof TalosApiError) throw e;
|
|
1116
1184
|
const offline = this.offlineDecision();
|
|
1117
1185
|
if (!offline) throw e;
|
|
@@ -1184,12 +1252,13 @@ var TalosClient = class {
|
|
|
1184
1252
|
}
|
|
1185
1253
|
async deactivate() {
|
|
1186
1254
|
this.requireActivated();
|
|
1255
|
+
const generation = this.licensingGeneration;
|
|
1187
1256
|
await this.post("/v1/client/deactivate", {
|
|
1188
1257
|
product_token: this.options.productToken,
|
|
1189
1258
|
fingerprint: { composite: this.fingerprint, components: this.fp.components },
|
|
1190
1259
|
activation_secret: this.activationSecret
|
|
1191
1260
|
});
|
|
1192
|
-
this.forgetBinding();
|
|
1261
|
+
if (generation === this.licensingGeneration) this.forgetBinding();
|
|
1193
1262
|
}
|
|
1194
1263
|
/**
|
|
1195
1264
|
* Send a heartbeat: refreshes this machine's last-seen state, feeds the
|
|
@@ -1213,46 +1282,59 @@ var TalosClient = class {
|
|
|
1213
1282
|
}
|
|
1214
1283
|
async heartbeat() {
|
|
1215
1284
|
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
|
-
|
|
1285
|
+
const generation = this.licensingGeneration;
|
|
1286
|
+
try {
|
|
1287
|
+
await this.ensureKeyset();
|
|
1288
|
+
const switchedBeforeRequest = this.offlineSince(generation);
|
|
1289
|
+
if (switchedBeforeRequest) return switchedBeforeRequest;
|
|
1290
|
+
const nonce = newNonce();
|
|
1291
|
+
const body = await this.post("/v1/client/heartbeat", {
|
|
1292
|
+
product_token: this.options.productToken,
|
|
1293
|
+
fingerprint: { composite: this.fingerprint, components: this.fp.components },
|
|
1294
|
+
activation_secret: this.activationSecret,
|
|
1295
|
+
nonce,
|
|
1296
|
+
app_version: this.options.appVersion,
|
|
1297
|
+
os: `${os3.type()} ${os3.release()}`,
|
|
1298
|
+
arch: os3.arch(),
|
|
1299
|
+
sdk_version: SDK_ID,
|
|
1300
|
+
// Naming the channel is what asks the server for the update hint, and it
|
|
1301
|
+
// costs two queries there — so it is asked for only when somebody is
|
|
1302
|
+
// listening for the answer.
|
|
1303
|
+
update_channel: this.listeners.get("updateAvailable")?.size ? this.updateChannel() : void 0
|
|
1304
|
+
});
|
|
1305
|
+
const switched = this.offlineSince(generation);
|
|
1306
|
+
if (switched) return switched;
|
|
1307
|
+
const claims = verifyToken(
|
|
1308
|
+
this.licenseVerifyKeys(),
|
|
1309
|
+
"heartbeat",
|
|
1310
|
+
this.options.productToken,
|
|
1311
|
+
nonce,
|
|
1312
|
+
body["heartbeat_token"]
|
|
1313
|
+
);
|
|
1314
|
+
const decision = typeof claims["decision"] === "string" ? claims["decision"] : "invalid";
|
|
1315
|
+
const interval = num(claims["heartbeat_interval_s"]);
|
|
1316
|
+
if (interval !== null && interval > 0) this.heartbeatInterval = interval;
|
|
1317
|
+
this.advanceClock(claims);
|
|
1318
|
+
this.persist({ timeHighWater: this.binding?.timeHighWater });
|
|
1319
|
+
const state = this.adopt(
|
|
1320
|
+
new LicenseState(
|
|
1321
|
+
decision,
|
|
1322
|
+
this.current?.features ?? {},
|
|
1323
|
+
this.current?.licenseExpiresAt ?? null,
|
|
1324
|
+
false,
|
|
1325
|
+
this.current?.revalidateAfter ?? null,
|
|
1326
|
+
this.current?.graceUntil ?? null
|
|
1327
|
+
)
|
|
1328
|
+
);
|
|
1329
|
+
await this.announceUpdate(claims);
|
|
1330
|
+
const switchedAfterUpdate = this.offlineSince(generation);
|
|
1331
|
+
if (switchedAfterUpdate) return switchedAfterUpdate;
|
|
1332
|
+
return state;
|
|
1333
|
+
} catch (e) {
|
|
1334
|
+
const switched = this.offlineSince(generation);
|
|
1335
|
+
if (switched) return switched;
|
|
1336
|
+
throw e;
|
|
1337
|
+
}
|
|
1256
1338
|
}
|
|
1257
1339
|
/** The channel this install follows; `stable` unless set. */
|
|
1258
1340
|
updateChannel() {
|
|
@@ -1637,6 +1719,13 @@ var TalosClient = class {
|
|
|
1637
1719
|
throw new TalosError("not activated: call activate() first");
|
|
1638
1720
|
}
|
|
1639
1721
|
}
|
|
1722
|
+
/** Keep a newly imported offline licence authoritative over older requests. */
|
|
1723
|
+
offlineSince(generation) {
|
|
1724
|
+
if (generation === this.licensingGeneration || this.activationSecret || !this.binding?.offlineToken) {
|
|
1725
|
+
return null;
|
|
1726
|
+
}
|
|
1727
|
+
return this.adoptOfflineToken(this.binding.offlineToken);
|
|
1728
|
+
}
|
|
1640
1729
|
post(path, body) {
|
|
1641
1730
|
return this.request("POST", path, body);
|
|
1642
1731
|
}
|