@zero-transfer/mft 0.4.7 → 0.4.8
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 +2 -1
- package/dist/index.cjs +326 -73
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +267 -29
- package/dist/index.d.ts +267 -29
- package/dist/index.mjs +322 -73
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,68 @@
|
|
|
1
1
|
// src/client/ZeroTransfer.ts
|
|
2
2
|
import { EventEmitter } from "events";
|
|
3
3
|
|
|
4
|
+
// src/logging/redaction.ts
|
|
5
|
+
var REDACTED = "[REDACTED]";
|
|
6
|
+
var SENSITIVE_KEY_PATTERN = /(?:password|passphrase|privatekey|token|secret|username|user)$/i;
|
|
7
|
+
var SECRET_COMMAND_PATTERN = /^(PASS|USER|ACCT)\s+(.+)$/i;
|
|
8
|
+
var URL_KEY_PATTERN = /(?:url|uri|href)$/i;
|
|
9
|
+
function isSensitiveKey(key) {
|
|
10
|
+
return SENSITIVE_KEY_PATTERN.test(key.replace(/[_-]/g, ""));
|
|
11
|
+
}
|
|
12
|
+
function redactCommand(command) {
|
|
13
|
+
return command.replace(SECRET_COMMAND_PATTERN, (_fullMatch, commandName) => {
|
|
14
|
+
return `${commandName.toUpperCase()} ${REDACTED}`;
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
function redactValue(value) {
|
|
18
|
+
if (typeof value === "string") {
|
|
19
|
+
return redactCommand(value);
|
|
20
|
+
}
|
|
21
|
+
if (Array.isArray(value)) {
|
|
22
|
+
return value.map((item) => redactValue(item));
|
|
23
|
+
}
|
|
24
|
+
if (value !== null && typeof value === "object") {
|
|
25
|
+
return redactObject(value);
|
|
26
|
+
}
|
|
27
|
+
return value;
|
|
28
|
+
}
|
|
29
|
+
function redactObject(input) {
|
|
30
|
+
return Object.fromEntries(
|
|
31
|
+
Object.entries(input).map(([key, value]) => {
|
|
32
|
+
if (isSensitiveKey(key)) {
|
|
33
|
+
return [key, REDACTED];
|
|
34
|
+
}
|
|
35
|
+
if (URL_KEY_PATTERN.test(key) && typeof value === "string") {
|
|
36
|
+
return [key, redactUrlForLogging(value)];
|
|
37
|
+
}
|
|
38
|
+
return [key, redactValue(value)];
|
|
39
|
+
})
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
function redactUrlForLogging(url) {
|
|
43
|
+
let parsed;
|
|
44
|
+
try {
|
|
45
|
+
parsed = typeof url === "string" ? new URL(url) : url;
|
|
46
|
+
} catch {
|
|
47
|
+
return REDACTED;
|
|
48
|
+
}
|
|
49
|
+
const origin = parsed.host.length > 0 ? `${parsed.protocol}//${parsed.host}` : parsed.protocol;
|
|
50
|
+
const query = parsed.search.length > 0 ? `?${REDACTED}` : "";
|
|
51
|
+
return `${origin}${parsed.pathname}${query}`;
|
|
52
|
+
}
|
|
53
|
+
function redactErrorForLogging(error) {
|
|
54
|
+
if (error !== null && typeof error === "object") {
|
|
55
|
+
const candidate = error;
|
|
56
|
+
if (typeof candidate.toJSON === "function") {
|
|
57
|
+
return redactObject(candidate.toJSON());
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (error instanceof Error) {
|
|
61
|
+
return redactObject({ message: error.message, name: error.name });
|
|
62
|
+
}
|
|
63
|
+
return { message: redactValue(typeof error === "string" ? error : String(error)) };
|
|
64
|
+
}
|
|
65
|
+
|
|
4
66
|
// src/errors/ZeroTransferError.ts
|
|
5
67
|
var ZeroTransferError = class extends Error {
|
|
6
68
|
/** Stable machine-readable error code. */
|
|
@@ -42,6 +104,11 @@ var ZeroTransferError = class extends Error {
|
|
|
42
104
|
/**
|
|
43
105
|
* Serializes the error into a plain object suitable for logs or API responses.
|
|
44
106
|
*
|
|
107
|
+
* `details` and `command` are passed through secret redaction so serialized
|
|
108
|
+
* errors never leak credentials, signed URLs, or raw protocol commands. The
|
|
109
|
+
* live {@link ZeroTransferError.details | details} property stays unredacted
|
|
110
|
+
* for programmatic consumers.
|
|
111
|
+
*
|
|
45
112
|
* @returns A JSON-safe object containing public structured error fields.
|
|
46
113
|
*/
|
|
47
114
|
toJSON() {
|
|
@@ -51,12 +118,12 @@ var ZeroTransferError = class extends Error {
|
|
|
51
118
|
message: this.message,
|
|
52
119
|
protocol: this.protocol,
|
|
53
120
|
host: this.host,
|
|
54
|
-
command: this.command,
|
|
121
|
+
command: this.command === void 0 ? void 0 : redactCommand(this.command),
|
|
55
122
|
ftpCode: this.ftpCode,
|
|
56
123
|
sftpCode: this.sftpCode,
|
|
57
124
|
path: this.path,
|
|
58
125
|
retryable: this.retryable,
|
|
59
|
-
details: this.details
|
|
126
|
+
details: this.details === void 0 ? void 0 : redactObject(this.details)
|
|
60
127
|
};
|
|
61
128
|
}
|
|
62
129
|
};
|
|
@@ -579,15 +646,20 @@ var ProviderRegistry = class {
|
|
|
579
646
|
var TransferClient = class {
|
|
580
647
|
/** Provider registry used by this client. */
|
|
581
648
|
registry;
|
|
649
|
+
/** Execution defaults applied when call sites omit their own values. */
|
|
650
|
+
defaults;
|
|
582
651
|
logger;
|
|
583
652
|
/**
|
|
584
653
|
* Creates a transfer client without opening any provider connections.
|
|
585
654
|
*
|
|
586
|
-
* @param options - Optional registry, provider factories, and
|
|
655
|
+
* @param options - Optional registry, provider factories, logger, and execution defaults.
|
|
587
656
|
*/
|
|
588
657
|
constructor(options = {}) {
|
|
589
658
|
this.registry = options.registry ?? new ProviderRegistry();
|
|
590
659
|
this.logger = options.logger ?? noopLogger;
|
|
660
|
+
if (options.defaults !== void 0) {
|
|
661
|
+
this.defaults = { ...options.defaults };
|
|
662
|
+
}
|
|
591
663
|
for (const provider of options.providers ?? []) {
|
|
592
664
|
this.registry.register(provider);
|
|
593
665
|
}
|
|
@@ -1160,18 +1232,25 @@ var TransferEngine = class {
|
|
|
1160
1232
|
for (let attemptNumber = 1; attemptNumber <= maxAttempts; attemptNumber += 1) {
|
|
1161
1233
|
this.throwIfAborted(abortScope.signal, job);
|
|
1162
1234
|
const attemptStartedAt = this.now();
|
|
1235
|
+
const attemptScope = createAttemptScope(
|
|
1236
|
+
abortScope.signal,
|
|
1237
|
+
options.timeout,
|
|
1238
|
+
job,
|
|
1239
|
+
attemptNumber
|
|
1240
|
+
);
|
|
1163
1241
|
const context = this.createExecutionContext(
|
|
1164
1242
|
job,
|
|
1165
1243
|
attemptNumber,
|
|
1166
1244
|
attemptStartedAt,
|
|
1167
1245
|
options,
|
|
1168
|
-
|
|
1246
|
+
attemptScope.signal,
|
|
1169
1247
|
(bytesTransferred) => {
|
|
1170
1248
|
latestBytesTransferred = bytesTransferred;
|
|
1171
|
-
}
|
|
1249
|
+
},
|
|
1250
|
+
attemptScope.notifyProgress
|
|
1172
1251
|
);
|
|
1173
1252
|
try {
|
|
1174
|
-
const result = await runExecutor(executor, context,
|
|
1253
|
+
const result = await runExecutor(executor, context, attemptScope.signal, job);
|
|
1175
1254
|
context.throwIfAborted();
|
|
1176
1255
|
latestBytesTransferred = result.bytesTransferred;
|
|
1177
1256
|
const completedAt = this.now();
|
|
@@ -1189,16 +1268,27 @@ var TransferEngine = class {
|
|
|
1189
1268
|
summarizeError(error)
|
|
1190
1269
|
);
|
|
1191
1270
|
attempts.push(attempt);
|
|
1192
|
-
if (error instanceof AbortError ||
|
|
1271
|
+
if (error instanceof AbortError || abortScope.signal?.aborted === true) {
|
|
1193
1272
|
throw error;
|
|
1194
1273
|
}
|
|
1195
|
-
const retryInput = {
|
|
1274
|
+
const retryInput = {
|
|
1275
|
+
attempt: attemptNumber,
|
|
1276
|
+
elapsedMs: Math.max(0, completedAt.getTime() - startedAt.getTime()),
|
|
1277
|
+
error,
|
|
1278
|
+
job
|
|
1279
|
+
};
|
|
1196
1280
|
const shouldRetry = attemptNumber < maxAttempts && (options.retry?.shouldRetry?.(retryInput) ?? isRetryable(error));
|
|
1197
1281
|
if (shouldRetry) {
|
|
1198
1282
|
options.retry?.onRetry?.(retryInput);
|
|
1283
|
+
const delayMs = normalizeDelayMs(options.retry?.getDelayMs?.(retryInput));
|
|
1284
|
+
if (delayMs > 0) {
|
|
1285
|
+
await sleepWithAbort(delayMs, abortScope.signal, job);
|
|
1286
|
+
}
|
|
1199
1287
|
continue;
|
|
1200
1288
|
}
|
|
1201
1289
|
throw createTransferFailure(job, error, attempts);
|
|
1290
|
+
} finally {
|
|
1291
|
+
attemptScope.dispose();
|
|
1202
1292
|
}
|
|
1203
1293
|
}
|
|
1204
1294
|
throw createTransferFailure(job, void 0, attempts);
|
|
@@ -1206,12 +1296,13 @@ var TransferEngine = class {
|
|
|
1206
1296
|
abortScope.dispose();
|
|
1207
1297
|
}
|
|
1208
1298
|
}
|
|
1209
|
-
createExecutionContext(job, attempt, startedAt, options, signal, updateBytesTransferred) {
|
|
1299
|
+
createExecutionContext(job, attempt, startedAt, options, signal, updateBytesTransferred, notifyProgress) {
|
|
1210
1300
|
const context = {
|
|
1211
1301
|
attempt,
|
|
1212
1302
|
job,
|
|
1213
1303
|
reportProgress: (bytesTransferred, totalBytes) => {
|
|
1214
1304
|
this.throwIfAborted(signal, job);
|
|
1305
|
+
notifyProgress();
|
|
1215
1306
|
updateBytesTransferred(bytesTransferred);
|
|
1216
1307
|
const progressInput = {
|
|
1217
1308
|
bytesTransferred,
|
|
@@ -1280,6 +1371,96 @@ function createAbortScope(parentSignal, timeout, job) {
|
|
|
1280
1371
|
signal: controller.signal
|
|
1281
1372
|
};
|
|
1282
1373
|
}
|
|
1374
|
+
function createAttemptScope(parentSignal, timeout, job, attempt) {
|
|
1375
|
+
const attemptTimeoutMs = normalizeTimeoutMs(timeout?.attemptTimeoutMs);
|
|
1376
|
+
const stallTimeoutMs = normalizeTimeoutMs(timeout?.stallTimeoutMs);
|
|
1377
|
+
if (attemptTimeoutMs === void 0 && stallTimeoutMs === void 0) {
|
|
1378
|
+
const scope = {
|
|
1379
|
+
dispose: () => void 0,
|
|
1380
|
+
notifyProgress: () => void 0
|
|
1381
|
+
};
|
|
1382
|
+
if (parentSignal !== void 0) scope.signal = parentSignal;
|
|
1383
|
+
return scope;
|
|
1384
|
+
}
|
|
1385
|
+
const controller = new AbortController();
|
|
1386
|
+
const retryable = timeout?.retryable ?? true;
|
|
1387
|
+
const abortFromParent = () => controller.abort(parentSignal?.reason);
|
|
1388
|
+
if (parentSignal?.aborted === true) {
|
|
1389
|
+
abortFromParent();
|
|
1390
|
+
} else {
|
|
1391
|
+
parentSignal?.addEventListener("abort", abortFromParent, { once: true });
|
|
1392
|
+
}
|
|
1393
|
+
const attemptTimer = attemptTimeoutMs === void 0 ? void 0 : setTimeout(() => {
|
|
1394
|
+
controller.abort(
|
|
1395
|
+
new TimeoutError({
|
|
1396
|
+
details: { attempt, attemptTimeoutMs, jobId: job.id, operation: job.operation },
|
|
1397
|
+
message: `Transfer attempt ${String(attempt)} timed out after ${String(attemptTimeoutMs)}ms: ${job.id}`,
|
|
1398
|
+
retryable
|
|
1399
|
+
})
|
|
1400
|
+
);
|
|
1401
|
+
}, attemptTimeoutMs);
|
|
1402
|
+
let stallTimer;
|
|
1403
|
+
const armStallWatchdog = () => {
|
|
1404
|
+
if (stallTimeoutMs === void 0 || controller.signal.aborted) return;
|
|
1405
|
+
if (stallTimer !== void 0) clearTimeout(stallTimer);
|
|
1406
|
+
stallTimer = setTimeout(() => {
|
|
1407
|
+
controller.abort(
|
|
1408
|
+
new TimeoutError({
|
|
1409
|
+
details: { attempt, jobId: job.id, operation: job.operation, stallTimeoutMs },
|
|
1410
|
+
message: `Transfer attempt ${String(attempt)} stalled (no progress for ${String(stallTimeoutMs)}ms): ${job.id}`,
|
|
1411
|
+
retryable
|
|
1412
|
+
})
|
|
1413
|
+
);
|
|
1414
|
+
}, stallTimeoutMs);
|
|
1415
|
+
};
|
|
1416
|
+
armStallWatchdog();
|
|
1417
|
+
return {
|
|
1418
|
+
dispose: () => {
|
|
1419
|
+
if (attemptTimer !== void 0) clearTimeout(attemptTimer);
|
|
1420
|
+
if (stallTimer !== void 0) clearTimeout(stallTimer);
|
|
1421
|
+
parentSignal?.removeEventListener("abort", abortFromParent);
|
|
1422
|
+
},
|
|
1423
|
+
notifyProgress: armStallWatchdog,
|
|
1424
|
+
signal: controller.signal
|
|
1425
|
+
};
|
|
1426
|
+
}
|
|
1427
|
+
function sleepWithAbort(delayMs, signal, job) {
|
|
1428
|
+
return new Promise((resolve, reject) => {
|
|
1429
|
+
if (signal === void 0) {
|
|
1430
|
+
setTimeout(resolve, delayMs);
|
|
1431
|
+
return;
|
|
1432
|
+
}
|
|
1433
|
+
if (signal.aborted) {
|
|
1434
|
+
reject(toAbortFailure(signal, job));
|
|
1435
|
+
return;
|
|
1436
|
+
}
|
|
1437
|
+
const rejectAbort = () => {
|
|
1438
|
+
clearTimeout(timer);
|
|
1439
|
+
reject(toAbortFailure(signal, job));
|
|
1440
|
+
};
|
|
1441
|
+
const timer = setTimeout(() => {
|
|
1442
|
+
signal.removeEventListener("abort", rejectAbort);
|
|
1443
|
+
resolve();
|
|
1444
|
+
}, delayMs);
|
|
1445
|
+
signal.addEventListener("abort", rejectAbort, { once: true });
|
|
1446
|
+
});
|
|
1447
|
+
}
|
|
1448
|
+
function toAbortFailure(signal, job) {
|
|
1449
|
+
if (signal.reason instanceof ZeroTransferError) {
|
|
1450
|
+
return signal.reason;
|
|
1451
|
+
}
|
|
1452
|
+
return new AbortError({
|
|
1453
|
+
details: { jobId: job.id, operation: job.operation },
|
|
1454
|
+
message: `Transfer job aborted: ${job.id}`,
|
|
1455
|
+
retryable: false
|
|
1456
|
+
});
|
|
1457
|
+
}
|
|
1458
|
+
function normalizeDelayMs(value) {
|
|
1459
|
+
if (value === void 0 || !Number.isFinite(value) || value <= 0) {
|
|
1460
|
+
return 0;
|
|
1461
|
+
}
|
|
1462
|
+
return Math.floor(value);
|
|
1463
|
+
}
|
|
1283
1464
|
function normalizeTimeoutMs(value) {
|
|
1284
1465
|
if (value === void 0 || !Number.isFinite(value) || value <= 0) {
|
|
1285
1466
|
return void 0;
|
|
@@ -1448,7 +1629,7 @@ async function runRoute(options) {
|
|
|
1448
1629
|
const executor = createProviderTransferExecutor({
|
|
1449
1630
|
resolveSession: ({ role }) => sessions.get(role)
|
|
1450
1631
|
});
|
|
1451
|
-
return await engine.execute(job, executor, buildExecuteOptions(options));
|
|
1632
|
+
return await engine.execute(job, executor, buildExecuteOptions(options, client));
|
|
1452
1633
|
} finally {
|
|
1453
1634
|
if (destinationSession !== void 0) {
|
|
1454
1635
|
await destinationSession.disconnect();
|
|
@@ -1485,12 +1666,14 @@ function defaultJobId(route, now) {
|
|
|
1485
1666
|
const timestamp = (now?.() ?? /* @__PURE__ */ new Date()).getTime();
|
|
1486
1667
|
return `route:${route.id}:${timestamp.toString(36)}`;
|
|
1487
1668
|
}
|
|
1488
|
-
function buildExecuteOptions(options) {
|
|
1669
|
+
function buildExecuteOptions(options, client) {
|
|
1489
1670
|
const execute = {};
|
|
1671
|
+
const retry = options.retry ?? client.defaults?.retry;
|
|
1672
|
+
const timeout = options.timeout ?? client.defaults?.timeout;
|
|
1490
1673
|
if (options.signal !== void 0) execute.signal = options.signal;
|
|
1491
|
-
if (
|
|
1674
|
+
if (retry !== void 0) execute.retry = retry;
|
|
1492
1675
|
if (options.onProgress !== void 0) execute.onProgress = options.onProgress;
|
|
1493
|
-
if (
|
|
1676
|
+
if (timeout !== void 0) execute.timeout = timeout;
|
|
1494
1677
|
if (options.bandwidthLimit !== void 0) execute.bandwidthLimit = options.bandwidthLimit;
|
|
1495
1678
|
return execute;
|
|
1496
1679
|
}
|
|
@@ -1547,41 +1730,6 @@ function defaultRouteSuffix(source, destination) {
|
|
|
1547
1730
|
return `${source}->${destination}`;
|
|
1548
1731
|
}
|
|
1549
1732
|
|
|
1550
|
-
// src/logging/redaction.ts
|
|
1551
|
-
var REDACTED = "[REDACTED]";
|
|
1552
|
-
var SENSITIVE_KEY_PATTERN = /(?:password|passphrase|privatekey|token|secret|username|user)$/i;
|
|
1553
|
-
var SECRET_COMMAND_PATTERN = /^(PASS|USER|ACCT)\s+(.+)$/i;
|
|
1554
|
-
function isSensitiveKey(key) {
|
|
1555
|
-
return SENSITIVE_KEY_PATTERN.test(key.replace(/[_-]/g, ""));
|
|
1556
|
-
}
|
|
1557
|
-
function redactCommand(command) {
|
|
1558
|
-
return command.replace(SECRET_COMMAND_PATTERN, (_fullMatch, commandName) => {
|
|
1559
|
-
return `${commandName.toUpperCase()} ${REDACTED}`;
|
|
1560
|
-
});
|
|
1561
|
-
}
|
|
1562
|
-
function redactValue(value) {
|
|
1563
|
-
if (typeof value === "string") {
|
|
1564
|
-
return redactCommand(value);
|
|
1565
|
-
}
|
|
1566
|
-
if (Array.isArray(value)) {
|
|
1567
|
-
return value.map((item) => redactValue(item));
|
|
1568
|
-
}
|
|
1569
|
-
if (value !== null && typeof value === "object") {
|
|
1570
|
-
return redactObject(value);
|
|
1571
|
-
}
|
|
1572
|
-
return value;
|
|
1573
|
-
}
|
|
1574
|
-
function redactObject(input) {
|
|
1575
|
-
return Object.fromEntries(
|
|
1576
|
-
Object.entries(input).map(([key, value]) => {
|
|
1577
|
-
if (isSensitiveKey(key)) {
|
|
1578
|
-
return [key, REDACTED];
|
|
1579
|
-
}
|
|
1580
|
-
return [key, redactValue(value)];
|
|
1581
|
-
})
|
|
1582
|
-
);
|
|
1583
|
-
}
|
|
1584
|
-
|
|
1585
1733
|
// src/profiles/SecretSource.ts
|
|
1586
1734
|
import { Buffer as Buffer2 } from "buffer";
|
|
1587
1735
|
import { readFile } from "fs/promises";
|
|
@@ -1963,11 +2111,11 @@ import {
|
|
|
1963
2111
|
import path from "path";
|
|
1964
2112
|
|
|
1965
2113
|
// src/utils/path.ts
|
|
1966
|
-
var UNSAFE_FTP_ARGUMENT_PATTERN = /[\r\n]/;
|
|
2114
|
+
var UNSAFE_FTP_ARGUMENT_PATTERN = /[\r\n\0]/;
|
|
1967
2115
|
function assertSafeFtpArgument(value, label = "path") {
|
|
1968
2116
|
if (UNSAFE_FTP_ARGUMENT_PATTERN.test(value)) {
|
|
1969
2117
|
throw new ConfigurationError({
|
|
1970
|
-
message: `Unsafe FTP ${label}: CR and
|
|
2118
|
+
message: `Unsafe FTP ${label}: CR, LF, and NUL characters are not allowed`,
|
|
1971
2119
|
retryable: false,
|
|
1972
2120
|
details: {
|
|
1973
2121
|
label
|
|
@@ -3269,7 +3417,6 @@ function expandAlgorithms(values) {
|
|
|
3269
3417
|
}
|
|
3270
3418
|
|
|
3271
3419
|
// src/profiles/importers/FileZillaImporter.ts
|
|
3272
|
-
import { Buffer as Buffer5 } from "buffer";
|
|
3273
3420
|
function importFileZillaSites(xml) {
|
|
3274
3421
|
const events = tokenizeXml(xml);
|
|
3275
3422
|
if (events.length === 0) {
|
|
@@ -3285,7 +3432,6 @@ function importFileZillaSites(xml) {
|
|
|
3285
3432
|
const folderNamePending = [];
|
|
3286
3433
|
let inServer = false;
|
|
3287
3434
|
let serverFields = {};
|
|
3288
|
-
let serverPasswordEncoding;
|
|
3289
3435
|
let activeTag;
|
|
3290
3436
|
let captureFolderName = false;
|
|
3291
3437
|
for (const event of events) {
|
|
@@ -3298,13 +3444,9 @@ function importFileZillaSites(xml) {
|
|
|
3298
3444
|
if (event.name === "Server") {
|
|
3299
3445
|
inServer = true;
|
|
3300
3446
|
serverFields = {};
|
|
3301
|
-
serverPasswordEncoding = void 0;
|
|
3302
3447
|
continue;
|
|
3303
3448
|
}
|
|
3304
3449
|
activeTag = event.name;
|
|
3305
|
-
if (event.name === "Pass" && inServer) {
|
|
3306
|
-
serverPasswordEncoding = event.attributes["encoding"];
|
|
3307
|
-
}
|
|
3308
3450
|
if (event.name === "Name" && !inServer && folderNamePending.length > 0) {
|
|
3309
3451
|
captureFolderName = true;
|
|
3310
3452
|
}
|
|
@@ -3330,7 +3472,7 @@ function importFileZillaSites(xml) {
|
|
|
3330
3472
|
}
|
|
3331
3473
|
if (event.name === "Server") {
|
|
3332
3474
|
const folder = folderStack.filter((segment) => segment !== "");
|
|
3333
|
-
const result = buildSiteFromFields(serverFields
|
|
3475
|
+
const result = buildSiteFromFields(serverFields);
|
|
3334
3476
|
if (result.kind === "site") {
|
|
3335
3477
|
sites.push({ ...result.site, folder });
|
|
3336
3478
|
} else {
|
|
@@ -3342,7 +3484,6 @@ function importFileZillaSites(xml) {
|
|
|
3342
3484
|
}
|
|
3343
3485
|
inServer = false;
|
|
3344
3486
|
serverFields = {};
|
|
3345
|
-
serverPasswordEncoding = void 0;
|
|
3346
3487
|
activeTag = void 0;
|
|
3347
3488
|
continue;
|
|
3348
3489
|
}
|
|
@@ -3351,7 +3492,7 @@ function importFileZillaSites(xml) {
|
|
|
3351
3492
|
}
|
|
3352
3493
|
return { sites, skipped };
|
|
3353
3494
|
}
|
|
3354
|
-
function buildSiteFromFields(fields
|
|
3495
|
+
function buildSiteFromFields(fields) {
|
|
3355
3496
|
const name = (fields["Name"] ?? fields["Host"] ?? "Untitled").trim();
|
|
3356
3497
|
const host = (fields["Host"] ?? "").trim();
|
|
3357
3498
|
if (host === "") return { kind: "skipped", name };
|
|
@@ -3370,18 +3511,9 @@ function buildSiteFromFields(fields, passwordEncoding) {
|
|
|
3370
3511
|
}
|
|
3371
3512
|
const user = fields["User"]?.trim();
|
|
3372
3513
|
if (user !== void 0 && user !== "") profile.username = { value: user };
|
|
3373
|
-
let password;
|
|
3374
3514
|
const rawPass = fields["Pass"];
|
|
3375
|
-
|
|
3376
|
-
|
|
3377
|
-
password = Buffer5.from(rawPass, "base64").toString("utf8");
|
|
3378
|
-
} else {
|
|
3379
|
-
password = rawPass;
|
|
3380
|
-
}
|
|
3381
|
-
if (password !== void 0 && password !== "") profile.password = { value: password };
|
|
3382
|
-
}
|
|
3383
|
-
const site = { name, profile };
|
|
3384
|
-
if (password !== void 0) site.password = password;
|
|
3515
|
+
const hasStoredPassword = rawPass !== void 0 && rawPass !== "";
|
|
3516
|
+
const site = { hasStoredPassword, name, profile };
|
|
3385
3517
|
const logonText = fields["Logontype"];
|
|
3386
3518
|
if (logonText !== void 0) {
|
|
3387
3519
|
const logonType = Number.parseInt(logonText.trim(), 10);
|
|
@@ -3624,6 +3756,62 @@ function mapFtp550(details) {
|
|
|
3624
3756
|
return new PermissionDeniedError(details);
|
|
3625
3757
|
}
|
|
3626
3758
|
|
|
3759
|
+
// src/transfers/createDefaultRetryPolicy.ts
|
|
3760
|
+
var DEFAULT_MAX_ATTEMPTS = 4;
|
|
3761
|
+
var DEFAULT_BASE_DELAY_MS = 250;
|
|
3762
|
+
var DEFAULT_MAX_DELAY_MS = 3e4;
|
|
3763
|
+
var DEFAULT_MAX_ELAPSED_MS = 3e5;
|
|
3764
|
+
function createDefaultRetryPolicy(options = {}) {
|
|
3765
|
+
const maxAttempts = normalizePositiveInteger(options.maxAttempts, DEFAULT_MAX_ATTEMPTS);
|
|
3766
|
+
const baseDelayMs = normalizeNonNegative(options.baseDelayMs, DEFAULT_BASE_DELAY_MS);
|
|
3767
|
+
const maxDelayMs = normalizeNonNegative(options.maxDelayMs, DEFAULT_MAX_DELAY_MS);
|
|
3768
|
+
const maxElapsedMs = normalizeNonNegative(options.maxElapsedMs, DEFAULT_MAX_ELAPSED_MS);
|
|
3769
|
+
const random = options.random ?? Math.random;
|
|
3770
|
+
return {
|
|
3771
|
+
getDelayMs(input) {
|
|
3772
|
+
const retryAfterMs = readRetryAfterMs(input.error);
|
|
3773
|
+
if (retryAfterMs !== void 0) {
|
|
3774
|
+
return retryAfterMs;
|
|
3775
|
+
}
|
|
3776
|
+
const exponentialMs = baseDelayMs * 2 ** (input.attempt - 1);
|
|
3777
|
+
const cappedMs = Math.min(maxDelayMs, exponentialMs);
|
|
3778
|
+
return Math.floor(random() * cappedMs);
|
|
3779
|
+
},
|
|
3780
|
+
maxAttempts,
|
|
3781
|
+
shouldRetry(input) {
|
|
3782
|
+
if (!(input.error instanceof ZeroTransferError) || !input.error.retryable) {
|
|
3783
|
+
return false;
|
|
3784
|
+
}
|
|
3785
|
+
if (input.elapsedMs >= maxElapsedMs) {
|
|
3786
|
+
return false;
|
|
3787
|
+
}
|
|
3788
|
+
const retryAfterMs = readRetryAfterMs(input.error);
|
|
3789
|
+
if (retryAfterMs !== void 0 && input.elapsedMs + retryAfterMs > maxElapsedMs) {
|
|
3790
|
+
return false;
|
|
3791
|
+
}
|
|
3792
|
+
return true;
|
|
3793
|
+
}
|
|
3794
|
+
};
|
|
3795
|
+
}
|
|
3796
|
+
function readRetryAfterMs(error) {
|
|
3797
|
+
if (!(error instanceof ZeroTransferError)) return void 0;
|
|
3798
|
+
const value = error.details?.["retryAfterMs"];
|
|
3799
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) return void 0;
|
|
3800
|
+
return Math.floor(value);
|
|
3801
|
+
}
|
|
3802
|
+
function normalizePositiveInteger(value, fallback) {
|
|
3803
|
+
if (value === void 0 || !Number.isFinite(value) || value < 1) {
|
|
3804
|
+
return fallback;
|
|
3805
|
+
}
|
|
3806
|
+
return Math.floor(value);
|
|
3807
|
+
}
|
|
3808
|
+
function normalizeNonNegative(value, fallback) {
|
|
3809
|
+
if (value === void 0 || !Number.isFinite(value) || value < 0) {
|
|
3810
|
+
return fallback;
|
|
3811
|
+
}
|
|
3812
|
+
return Math.floor(value);
|
|
3813
|
+
}
|
|
3814
|
+
|
|
3627
3815
|
// src/transfers/TransferPlan.ts
|
|
3628
3816
|
function createTransferPlan(input) {
|
|
3629
3817
|
const plan = {
|
|
@@ -3721,8 +3909,8 @@ var TransferQueue = class {
|
|
|
3721
3909
|
this.concurrency = normalizeConcurrency(options.concurrency);
|
|
3722
3910
|
this.defaultExecutor = options.executor;
|
|
3723
3911
|
this.resolveExecutor = options.resolveExecutor;
|
|
3724
|
-
this.retry = options.retry;
|
|
3725
|
-
this.timeout = options.timeout;
|
|
3912
|
+
this.retry = options.retry ?? options.client?.defaults?.retry;
|
|
3913
|
+
this.timeout = options.timeout ?? options.client?.defaults?.timeout;
|
|
3726
3914
|
this.bandwidthLimit = options.bandwidthLimit;
|
|
3727
3915
|
this.onProgress = options.onProgress;
|
|
3728
3916
|
this.onReceipt = options.onReceipt;
|
|
@@ -5135,6 +5323,7 @@ async function dispatchWebhook(options) {
|
|
|
5135
5323
|
return { attempts: attempt, delivered: false, status: lastStatus };
|
|
5136
5324
|
}
|
|
5137
5325
|
function createWebhookAuditLog(options) {
|
|
5326
|
+
validateTarget(options.target);
|
|
5138
5327
|
return {
|
|
5139
5328
|
list: () => Promise.resolve([]),
|
|
5140
5329
|
record: async (entry) => {
|
|
@@ -5157,6 +5346,24 @@ function validateTarget(target) {
|
|
|
5157
5346
|
retryable: false
|
|
5158
5347
|
});
|
|
5159
5348
|
}
|
|
5349
|
+
let parsed;
|
|
5350
|
+
try {
|
|
5351
|
+
parsed = new URL(target.url);
|
|
5352
|
+
} catch (error) {
|
|
5353
|
+
throw new ConfigurationError({
|
|
5354
|
+
cause: error,
|
|
5355
|
+
details: { url: target.url },
|
|
5356
|
+
message: "Webhook target url must be an absolute URL",
|
|
5357
|
+
retryable: false
|
|
5358
|
+
});
|
|
5359
|
+
}
|
|
5360
|
+
if (parsed.protocol === "https:") return;
|
|
5361
|
+
if (parsed.protocol === "http:" && target.allowInsecureUrl === true) return;
|
|
5362
|
+
throw new ConfigurationError({
|
|
5363
|
+
details: { protocol: parsed.protocol, url: target.url },
|
|
5364
|
+
message: parsed.protocol === "http:" ? "Webhook target url must use https; set allowInsecureUrl: true to permit cleartext http delivery" : `Webhook target url must use https, got "${parsed.protocol}"`,
|
|
5365
|
+
retryable: false
|
|
5366
|
+
});
|
|
5160
5367
|
}
|
|
5161
5368
|
function normalizeRetry(retry) {
|
|
5162
5369
|
return {
|
|
@@ -5193,6 +5400,26 @@ var ApprovalRejectedError = class _ApprovalRejectedError extends ZeroTransferErr
|
|
|
5193
5400
|
}
|
|
5194
5401
|
request;
|
|
5195
5402
|
};
|
|
5403
|
+
var ApprovalTimeoutError = class _ApprovalTimeoutError extends ZeroTransferError {
|
|
5404
|
+
/**
|
|
5405
|
+
* Creates an approval timeout error.
|
|
5406
|
+
*
|
|
5407
|
+
* @param request - The approval request that timed out while pending.
|
|
5408
|
+
* @param timeoutMs - Configured timeout window in milliseconds.
|
|
5409
|
+
*/
|
|
5410
|
+
constructor(request, timeoutMs) {
|
|
5411
|
+
super({
|
|
5412
|
+
code: "approval_timeout",
|
|
5413
|
+
details: { approvalId: request.id, routeId: request.routeId, timeoutMs },
|
|
5414
|
+
message: `Approval "${request.id}" for route "${request.routeId}" timed out after ${String(timeoutMs)}ms`,
|
|
5415
|
+
retryable: false
|
|
5416
|
+
});
|
|
5417
|
+
this.request = request;
|
|
5418
|
+
Object.setPrototypeOf(this, _ApprovalTimeoutError.prototype);
|
|
5419
|
+
this.name = "ApprovalTimeoutError";
|
|
5420
|
+
}
|
|
5421
|
+
request;
|
|
5422
|
+
};
|
|
5196
5423
|
var ApprovalRegistry = class {
|
|
5197
5424
|
requests = /* @__PURE__ */ new Map();
|
|
5198
5425
|
pending = /* @__PURE__ */ new Map();
|
|
@@ -5318,9 +5545,27 @@ function createApprovalGate(options) {
|
|
|
5318
5545
|
};
|
|
5319
5546
|
if (input.signal.aborted) onAbort();
|
|
5320
5547
|
input.signal.addEventListener("abort", onAbort);
|
|
5548
|
+
let timeoutTimer;
|
|
5549
|
+
const timeoutMs = options.timeoutMs;
|
|
5550
|
+
const pendingPromises = [settled];
|
|
5551
|
+
if (timeoutMs !== void 0) {
|
|
5552
|
+
pendingPromises.push(
|
|
5553
|
+
new Promise((_resolve, reject) => {
|
|
5554
|
+
timeoutTimer = setTimeout(() => {
|
|
5555
|
+
const current = options.registry.get(approvalId) ?? request;
|
|
5556
|
+
reject(new ApprovalTimeoutError(current, timeoutMs));
|
|
5557
|
+
if (current.status === "pending") {
|
|
5558
|
+
settled.catch(() => void 0);
|
|
5559
|
+
options.registry.reject(approvalId, { reason: "timeout" }, now());
|
|
5560
|
+
}
|
|
5561
|
+
}, timeoutMs);
|
|
5562
|
+
})
|
|
5563
|
+
);
|
|
5564
|
+
}
|
|
5321
5565
|
try {
|
|
5322
|
-
await
|
|
5566
|
+
await Promise.race(pendingPromises);
|
|
5323
5567
|
} finally {
|
|
5568
|
+
if (timeoutTimer !== void 0) clearTimeout(timeoutTimer);
|
|
5324
5569
|
input.signal.removeEventListener("abort", onAbort);
|
|
5325
5570
|
}
|
|
5326
5571
|
return options.runner(input);
|
|
@@ -5679,6 +5924,7 @@ export {
|
|
|
5679
5924
|
AbortError,
|
|
5680
5925
|
ApprovalRegistry,
|
|
5681
5926
|
ApprovalRejectedError,
|
|
5927
|
+
ApprovalTimeoutError,
|
|
5682
5928
|
AuthenticationError,
|
|
5683
5929
|
AuthorizationError,
|
|
5684
5930
|
CLASSIC_PROVIDER_IDS,
|
|
@@ -5716,6 +5962,7 @@ export {
|
|
|
5716
5962
|
createApprovalGate,
|
|
5717
5963
|
createAtomicDeployPlan,
|
|
5718
5964
|
createBandwidthThrottle,
|
|
5965
|
+
createDefaultRetryPolicy,
|
|
5719
5966
|
createInboxRoute,
|
|
5720
5967
|
createJsonlAuditLog,
|
|
5721
5968
|
createLocalProviderFactory,
|
|
@@ -5763,8 +6010,10 @@ export {
|
|
|
5763
6010
|
parseRemoteManifest,
|
|
5764
6011
|
redactCommand,
|
|
5765
6012
|
redactConnectionProfile,
|
|
6013
|
+
redactErrorForLogging,
|
|
5766
6014
|
redactObject,
|
|
5767
6015
|
redactSecretSource,
|
|
6016
|
+
redactUrlForLogging,
|
|
5768
6017
|
redactValue,
|
|
5769
6018
|
resolveConnectionProfileSecrets,
|
|
5770
6019
|
resolveOpenSshHost,
|