@persistica/flux-agent 0.0.35 → 0.0.36
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/index.js +93 -26
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -1223,8 +1223,12 @@ var retryOrThrow = async (fn, shouldRetry, options) => {
|
|
|
1223
1223
|
if (attempt >= options.retries || !shouldRetry(err)) {
|
|
1224
1224
|
throw err;
|
|
1225
1225
|
}
|
|
1226
|
-
|
|
1227
|
-
|
|
1226
|
+
const backedOff = Math.min(
|
|
1227
|
+
options.maxDelayMs ?? Number.POSITIVE_INFINITY,
|
|
1228
|
+
options.delayMs * (options.backoffFactor ?? 1) ** attempt
|
|
1229
|
+
);
|
|
1230
|
+
const delay = overrideDelay ?? backedOff;
|
|
1231
|
+
if (delay > 0) {
|
|
1228
1232
|
await new Promise((res) => setTimeout(
|
|
1229
1233
|
res,
|
|
1230
1234
|
delay
|
|
@@ -1235,6 +1239,35 @@ var retryOrThrow = async (fn, shouldRetry, options) => {
|
|
|
1235
1239
|
throw new Error("Retry loop exited unexpectedly.");
|
|
1236
1240
|
};
|
|
1237
1241
|
|
|
1242
|
+
// libs/flux/shared/utils/src/lib/connection-errors.ts
|
|
1243
|
+
var RetryableError = class extends Error {
|
|
1244
|
+
};
|
|
1245
|
+
var ConnectionError = class extends RetryableError {
|
|
1246
|
+
constructor(message, statusCode) {
|
|
1247
|
+
super(message);
|
|
1248
|
+
this.statusCode = statusCode;
|
|
1249
|
+
this.name = "ConnectionError";
|
|
1250
|
+
}
|
|
1251
|
+
statusCode;
|
|
1252
|
+
};
|
|
1253
|
+
var AuthenticationError = class extends Error {
|
|
1254
|
+
constructor(message) {
|
|
1255
|
+
super(message);
|
|
1256
|
+
this.name = "AuthenticationError";
|
|
1257
|
+
}
|
|
1258
|
+
};
|
|
1259
|
+
var EndpointNotFoundError = class extends Error {
|
|
1260
|
+
constructor(message) {
|
|
1261
|
+
super(message);
|
|
1262
|
+
this.name = "EndpointNotFoundError";
|
|
1263
|
+
}
|
|
1264
|
+
};
|
|
1265
|
+
var isRetryableConnectionError = (error) => error instanceof RetryableError;
|
|
1266
|
+
var isRetryableAuthStatus = (status) => status >= 500 || status === 429;
|
|
1267
|
+
var asConnectionError = (error, origin) => new ConnectionError(
|
|
1268
|
+
`Failed to reach mesh server at ${origin}: ${error instanceof Error ? error.message : "Unknown connection error"}`
|
|
1269
|
+
);
|
|
1270
|
+
|
|
1238
1271
|
// libs/flux/shared/utils/src/lib/machine-id.util.ts
|
|
1239
1272
|
var validateMachineUID = (machineUID) => {
|
|
1240
1273
|
if (typeof machineUID !== "string") {
|
|
@@ -1293,32 +1326,47 @@ var authenticateAgentOrThrow = async (networkId, domain, unknownIdentificationPa
|
|
|
1293
1326
|
if (clientInfo.machineUID && validateMachineUID(clientInfo.machineUID)) {
|
|
1294
1327
|
url.searchParams.set("machineUID", clientInfo.machineUID);
|
|
1295
1328
|
}
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
if (
|
|
1309
|
-
|
|
1329
|
+
try {
|
|
1330
|
+
const response = await fetch(url, {
|
|
1331
|
+
method: "POST",
|
|
1332
|
+
headers: {
|
|
1333
|
+
"Content-Type": "text/plain",
|
|
1334
|
+
"x-flux-content-type": typeof unknownIdentificationPayload === "string" ? "text/plain" : "application/json",
|
|
1335
|
+
"Accept": "text/plain"
|
|
1336
|
+
},
|
|
1337
|
+
body: customPayload
|
|
1338
|
+
});
|
|
1339
|
+
if (!response.ok) {
|
|
1340
|
+
const textResponse = await response.text();
|
|
1341
|
+
if (response.status === 401) {
|
|
1342
|
+
if (textResponse.startsWith(NetworkAuthorityNotFoundError.message)) {
|
|
1343
|
+
throw new NetworkAuthorityNotFoundError(networkId);
|
|
1344
|
+
}
|
|
1345
|
+
throw new AuthenticationError(textResponse ? `Auth failed: 401 - ${textResponse}` : "Auth failed: 401");
|
|
1346
|
+
}
|
|
1347
|
+
if (response.status === 404) {
|
|
1348
|
+
throw new EndpointNotFoundError(`Mesh server not found at ${url.origin}.`);
|
|
1310
1349
|
}
|
|
1350
|
+
const message = textResponse ? `Auth failed: ${response.status} - ${textResponse}` : `Auth failed: ${response.status}`;
|
|
1351
|
+
if (isRetryableAuthStatus(response.status)) {
|
|
1352
|
+
throw new ConnectionError(message, response.status);
|
|
1353
|
+
}
|
|
1354
|
+
throw new Error(message);
|
|
1311
1355
|
}
|
|
1312
|
-
|
|
1313
|
-
|
|
1356
|
+
const result = await response.text();
|
|
1357
|
+
if (!checkAuthTicketShape(result)) {
|
|
1358
|
+
throw new Error("Auth failed: unexpected response");
|
|
1314
1359
|
}
|
|
1315
|
-
|
|
1316
|
-
}
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1360
|
+
return result;
|
|
1361
|
+
} catch (error) {
|
|
1362
|
+
if (error instanceof NetworkAuthorityNotFoundError || error instanceof AuthenticationError || error instanceof EndpointNotFoundError || error instanceof RetryableError) {
|
|
1363
|
+
throw error;
|
|
1364
|
+
}
|
|
1365
|
+
if (error instanceof Error && error.message.startsWith("Auth failed:")) {
|
|
1366
|
+
throw error;
|
|
1367
|
+
}
|
|
1368
|
+
throw asConnectionError(error, url.origin);
|
|
1320
1369
|
}
|
|
1321
|
-
return result;
|
|
1322
1370
|
};
|
|
1323
1371
|
|
|
1324
1372
|
// packages/flux/agent/src/lib/connector/flux-client-data.class.ts
|
|
@@ -1369,6 +1417,8 @@ var FluxAgent = class {
|
|
|
1369
1417
|
throw new Error("Will never be thrown");
|
|
1370
1418
|
}
|
|
1371
1419
|
let warnedWaitingForAuthority = false;
|
|
1420
|
+
let warnedMeshUnreachable = false;
|
|
1421
|
+
let lastError;
|
|
1372
1422
|
const ticket = await retryOrThrow(
|
|
1373
1423
|
async () => {
|
|
1374
1424
|
return authenticateAgentOrThrow(
|
|
@@ -1381,8 +1431,15 @@ var FluxAgent = class {
|
|
|
1381
1431
|
}
|
|
1382
1432
|
);
|
|
1383
1433
|
},
|
|
1384
|
-
// Retry
|
|
1385
|
-
|
|
1434
|
+
// Retry while no Authority has appeared yet, and while the mesh
|
|
1435
|
+
// itself is unreachable. The latter used to fall straight through
|
|
1436
|
+
// to the caller, so a network blip during startup left the Agent
|
|
1437
|
+
// permanently disconnected while an Authority in the same
|
|
1438
|
+
// situation would have retried through it.
|
|
1439
|
+
(error) => {
|
|
1440
|
+
lastError = error;
|
|
1441
|
+
return error instanceof NetworkAuthorityNotFoundError || isRetryableConnectionError(error);
|
|
1442
|
+
},
|
|
1386
1443
|
{
|
|
1387
1444
|
// Fixed by the SDK, not the consuming app: Agents run on untrusted client
|
|
1388
1445
|
// machines, so the mesh operator — not the app — controls how long an Agent
|
|
@@ -1390,6 +1447,16 @@ var FluxAgent = class {
|
|
|
1390
1447
|
retries: MAX_CONNECT_ATTEMPTS,
|
|
1391
1448
|
delayMs: 500,
|
|
1392
1449
|
onRetry: (attempt, retries) => {
|
|
1450
|
+
if (isRetryableConnectionError(lastError)) {
|
|
1451
|
+
this.stateManager.emitNetworkState("authorizing");
|
|
1452
|
+
if (!warnedMeshUnreachable) {
|
|
1453
|
+
warnedMeshUnreachable = true;
|
|
1454
|
+
console.warn(
|
|
1455
|
+
`[flux-agent] Cannot reach the mesh at '${domain}'. Retrying in the background up to ${retries} times; the Agent connects on its own once the mesh answers. If this never resolves, check the 'domain' option points at the Mesh (not the Portal).`
|
|
1456
|
+
);
|
|
1457
|
+
}
|
|
1458
|
+
return Math.min(3e4, 500 * 2 ** attempt);
|
|
1459
|
+
}
|
|
1393
1460
|
this.stateManager.emitNetworkState("waiting-for-authority");
|
|
1394
1461
|
if (!warnedWaitingForAuthority) {
|
|
1395
1462
|
warnedWaitingForAuthority = true;
|