@truenas/api-client 4.0.0 → 4.0.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.cjs +116 -20
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +40 -10
- package/dist/index.d.ts +40 -10
- package/dist/index.js +116 -20
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -2389,8 +2389,11 @@ var apiVersionConfig = {
|
|
|
2389
2389
|
* - DNS lookup failure
|
|
2390
2390
|
* - Server unreachable
|
|
2391
2391
|
*
|
|
2392
|
-
*
|
|
2393
|
-
*
|
|
2392
|
+
* Which is why it is no longer reached on all of them. A discovery failure is
|
|
2393
|
+
* now put to a reachability probe and a second attempt first, and only an
|
|
2394
|
+
* appliance that answers the probe and still will not serve `/api/versions`
|
|
2395
|
+
* is assumed to be this version. One that answers nothing is reported as
|
|
2396
|
+
* unreachable rather than pinned to a version nobody confirmed.
|
|
2394
2397
|
*/
|
|
2395
2398
|
FALLBACK_VERSION: "v25.10.0"
|
|
2396
2399
|
};
|
|
@@ -3753,6 +3756,12 @@ var InvalidVersionResponseError = class extends VersionDiscoveryError {
|
|
|
3753
3756
|
}
|
|
3754
3757
|
};
|
|
3755
3758
|
var discoveryTimeoutMs = 5e3;
|
|
3759
|
+
var probeTimeoutMs = 5e3;
|
|
3760
|
+
function corsIsEnforced() {
|
|
3761
|
+
const hasDocument = typeof window !== "undefined" && typeof window.document !== "undefined";
|
|
3762
|
+
const inWorker = typeof globalThis.WorkerGlobalScope !== "undefined";
|
|
3763
|
+
return hasDocument || inWorker;
|
|
3764
|
+
}
|
|
3756
3765
|
function hasErrorName(error, expected) {
|
|
3757
3766
|
return typeof error === "object" && error !== null && "name" in error && error.name === expected;
|
|
3758
3767
|
}
|
|
@@ -3765,6 +3774,42 @@ var VersionDiscovery = class {
|
|
|
3765
3774
|
versionsUrl(hostname) {
|
|
3766
3775
|
return `${httpScheme(this.protocol)}//${hostname}/api/versions`;
|
|
3767
3776
|
}
|
|
3777
|
+
/**
|
|
3778
|
+
* Whether the appliance answers at all, asked in a way CORS cannot block.
|
|
3779
|
+
*
|
|
3780
|
+
* A `no-cors` request yields a response the page may not read, but its
|
|
3781
|
+
* *arrival* is the whole answer here: something served it. That separates the
|
|
3782
|
+
* two failures `fetch` reports identically — a box that refused to share its
|
|
3783
|
+
* versions with this origin, and a box that is not there.
|
|
3784
|
+
*
|
|
3785
|
+
* `cannot-ask` where CORS is not enforced: nothing is blocking anything, so a
|
|
3786
|
+
* failed fetch already means unreachable and a probe would only repeat what
|
|
3787
|
+
* discovery just found. It is not a quieter way of saying "no".
|
|
3788
|
+
*/
|
|
3789
|
+
async probeReachable(hostname) {
|
|
3790
|
+
if (!corsIsEnforced()) return "cannot-ask";
|
|
3791
|
+
const url = this.versionsUrl(hostname);
|
|
3792
|
+
const controller = new AbortController();
|
|
3793
|
+
const timer2 = setTimeout(() => controller.abort(), probeTimeoutMs);
|
|
3794
|
+
try {
|
|
3795
|
+
await fetch(url, {
|
|
3796
|
+
mode: "no-cors",
|
|
3797
|
+
cache: "no-store",
|
|
3798
|
+
signal: controller.signal
|
|
3799
|
+
});
|
|
3800
|
+
this.logger.info("Reachability probe answered", { hostname, url });
|
|
3801
|
+
return "reachable";
|
|
3802
|
+
} catch (error) {
|
|
3803
|
+
this.logger.warn("Reachability probe got no answer", {
|
|
3804
|
+
hostname,
|
|
3805
|
+
url,
|
|
3806
|
+
error
|
|
3807
|
+
});
|
|
3808
|
+
return "silent";
|
|
3809
|
+
} finally {
|
|
3810
|
+
clearTimeout(timer2);
|
|
3811
|
+
}
|
|
3812
|
+
}
|
|
3768
3813
|
/**
|
|
3769
3814
|
* Discovers the API version for a given hostname.
|
|
3770
3815
|
*
|
|
@@ -4024,30 +4069,81 @@ async function createTrueNasClient(opts) {
|
|
|
4024
4069
|
});
|
|
4025
4070
|
throw error;
|
|
4026
4071
|
}
|
|
4027
|
-
const
|
|
4028
|
-
|
|
4029
|
-
|
|
4030
|
-
logger.error("Invalid fallback version configuration", {
|
|
4031
|
-
uuid: uuid.slice(0, 8),
|
|
4032
|
-
hostnames: hostnames.join(", "),
|
|
4033
|
-
fallbackVersion: fallbackVersionString
|
|
4034
|
-
});
|
|
4035
|
-
throw error;
|
|
4072
|
+
const reachable = await probeAnyHostname(hostnames, versionDiscovery);
|
|
4073
|
+
if (reachable === "silent") {
|
|
4074
|
+
await sleep(unreachableRetryDelayMs);
|
|
4036
4075
|
}
|
|
4037
|
-
|
|
4038
|
-
|
|
4039
|
-
|
|
4076
|
+
try {
|
|
4077
|
+
const retryWinner = await discoverVersionFromAnyHostname(
|
|
4078
|
+
hostnames,
|
|
4079
|
+
versionDiscovery
|
|
4080
|
+
);
|
|
4081
|
+
logger.info("Version discovery succeeded on retry", {
|
|
4040
4082
|
uuid: uuid.slice(0, 8),
|
|
4041
|
-
|
|
4042
|
-
|
|
4043
|
-
|
|
4044
|
-
|
|
4083
|
+
hostname: retryWinner.hostname,
|
|
4084
|
+
version: retryWinner.version.version,
|
|
4085
|
+
firstError: errorMessage
|
|
4086
|
+
});
|
|
4087
|
+
version = retryWinner.version;
|
|
4088
|
+
} catch (retryError) {
|
|
4089
|
+
if (!(retryError instanceof VersionDiscoveryNetworkError)) {
|
|
4090
|
+
throw retryError;
|
|
4045
4091
|
}
|
|
4046
|
-
|
|
4047
|
-
|
|
4092
|
+
if (reachable !== "reachable") {
|
|
4093
|
+
logger.error(
|
|
4094
|
+
"Version discovery failed and the appliance did not answer a reachability probe; not assuming a version",
|
|
4095
|
+
{
|
|
4096
|
+
uuid: uuid.slice(0, 8),
|
|
4097
|
+
hostnames: hostnames.join(", "),
|
|
4098
|
+
probe: reachable === "silent" ? "no answer" : "not applicable",
|
|
4099
|
+
error: errorMessageOrDefault(retryError, "Unknown error")
|
|
4100
|
+
}
|
|
4101
|
+
);
|
|
4102
|
+
throw retryError;
|
|
4103
|
+
}
|
|
4104
|
+
const fallbackVersionString = apiVersionConfig.FALLBACK_VERSION;
|
|
4105
|
+
const fallbackVersion = parseApiVersion(fallbackVersionString);
|
|
4106
|
+
if (!fallbackVersion) {
|
|
4107
|
+
logger.error("Invalid fallback version configuration", {
|
|
4108
|
+
uuid: uuid.slice(0, 8),
|
|
4109
|
+
hostnames: hostnames.join(", "),
|
|
4110
|
+
fallbackVersion: fallbackVersionString
|
|
4111
|
+
});
|
|
4112
|
+
throw retryError;
|
|
4113
|
+
}
|
|
4114
|
+
logger.warn(
|
|
4115
|
+
"Appliance is reachable but version discovery is still blocked; falling back to assumed version",
|
|
4116
|
+
{
|
|
4117
|
+
uuid: uuid.slice(0, 8),
|
|
4118
|
+
hostnames: hostnames.join(", "),
|
|
4119
|
+
fallbackVersion: fallbackVersionString,
|
|
4120
|
+
originalError: errorMessage,
|
|
4121
|
+
warning: "The appliance answered a probe but not /api/versions, which is what v25.10.0 looks like from a browser. If it is newer than that, this client is now pinned to the wrong surface."
|
|
4122
|
+
}
|
|
4123
|
+
);
|
|
4124
|
+
version = fallbackVersion;
|
|
4125
|
+
}
|
|
4048
4126
|
}
|
|
4049
4127
|
return instantiateClientForVersion(version, opts, logger);
|
|
4050
4128
|
}
|
|
4129
|
+
var unreachableRetryDelayMs = 2500;
|
|
4130
|
+
var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
4131
|
+
async function probeAnyHostname(hostnames, versionDiscovery) {
|
|
4132
|
+
const probes = hostnames.map(
|
|
4133
|
+
(hostname) => versionDiscovery.probeReachable(hostname)
|
|
4134
|
+
);
|
|
4135
|
+
const remaining = new Set(probes);
|
|
4136
|
+
let sawSilence = false;
|
|
4137
|
+
while (remaining.size > 0) {
|
|
4138
|
+
const settled = await Promise.race(
|
|
4139
|
+
[...remaining].map((probe) => probe.then((result) => ({ probe, result })))
|
|
4140
|
+
);
|
|
4141
|
+
if (settled.result === "reachable") return "reachable";
|
|
4142
|
+
if (settled.result === "silent") sawSilence = true;
|
|
4143
|
+
remaining.delete(settled.probe);
|
|
4144
|
+
}
|
|
4145
|
+
return sawSilence ? "silent" : "cannot-ask";
|
|
4146
|
+
}
|
|
4051
4147
|
async function discoverVersionFromAnyHostname(hostnames, versionDiscovery) {
|
|
4052
4148
|
const attempts = hostnames.map(
|
|
4053
4149
|
(hostname) => rxjs.firstValueFrom(versionDiscovery.discoverVersion(hostname)).then(
|