@zero-transfer/core 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 +7 -1
- package/dist/index.cjs +266 -72
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +228 -24
- package/dist/index.d.ts +228 -24
- package/dist/index.mjs +263 -72
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -20,12 +20,13 @@ import { createLocalProviderFactory } from "@zero-transfer/core";
|
|
|
20
20
|
|
|
21
21
|
## Public surface
|
|
22
22
|
|
|
23
|
-
This package publishes a narrowed surface of **
|
|
23
|
+
This package publishes a narrowed surface of **97** exports. These symbols are also available from [`@zero-transfer/sdk`](https://www.npmjs.com/package/@zero-transfer/sdk); the table below links into the full API reference:
|
|
24
24
|
|
|
25
25
|
| Symbol | Kind | Notes |
|
|
26
26
|
| -------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | ------------------ |
|
|
27
27
|
| [`TransferClient`](https://github.com/tonywied17/zero-transfer/blob/main/docs/api-md/classes/TransferClient.md) | Class | See API reference. |
|
|
28
28
|
| [`TransferClientOptions`](https://github.com/tonywied17/zero-transfer/blob/main/docs/api-md/interfaces/TransferClientOptions.md) | Interface | See API reference. |
|
|
29
|
+
| [`TransferClientDefaults`](https://github.com/tonywied17/zero-transfer/blob/main/docs/api-md/interfaces/TransferClientDefaults.md) | Interface | See API reference. |
|
|
29
30
|
| [`createTransferClient`](https://github.com/tonywied17/zero-transfer/blob/main/docs/api-md/functions/createTransferClient.md) | Function | See API reference. |
|
|
30
31
|
| [`ProviderRegistry`](https://github.com/tonywied17/zero-transfer/blob/main/docs/api-md/classes/ProviderRegistry.md) | Class | See API reference. |
|
|
31
32
|
| [`TransferSession`](https://github.com/tonywied17/zero-transfer/blob/main/docs/api-md/interfaces/TransferSession.md) | Interface | See API reference. |
|
|
@@ -50,6 +51,8 @@ This package publishes a narrowed surface of **92** exports. These symbols are a
|
|
|
50
51
|
| [`TransferAttempt`](https://github.com/tonywied17/zero-transfer/blob/main/docs/api-md/interfaces/TransferAttempt.md) | Interface | See API reference. |
|
|
51
52
|
| [`TransferRetryPolicy`](https://github.com/tonywied17/zero-transfer/blob/main/docs/api-md/interfaces/TransferRetryPolicy.md) | Interface | See API reference. |
|
|
52
53
|
| [`TransferTimeoutPolicy`](https://github.com/tonywied17/zero-transfer/blob/main/docs/api-md/interfaces/TransferTimeoutPolicy.md) | Interface | See API reference. |
|
|
54
|
+
| [`createDefaultRetryPolicy`](https://github.com/tonywied17/zero-transfer/blob/main/docs/api-md/functions/createDefaultRetryPolicy.md) | Function | See API reference. |
|
|
55
|
+
| [`DefaultRetryPolicyOptions`](https://github.com/tonywied17/zero-transfer/blob/main/docs/api-md/interfaces/DefaultRetryPolicyOptions.md) | Interface | See API reference. |
|
|
53
56
|
| [`TransferBandwidthLimit`](https://github.com/tonywied17/zero-transfer/blob/main/docs/api-md/interfaces/TransferBandwidthLimit.md) | Interface | See API reference. |
|
|
54
57
|
| [`TransferVerificationResult`](https://github.com/tonywied17/zero-transfer/blob/main/docs/api-md/interfaces/TransferVerificationResult.md) | Interface | See API reference. |
|
|
55
58
|
| [`ProviderTransferSessionResolver`](https://github.com/tonywied17/zero-transfer/blob/main/docs/api-md/type-aliases/ProviderTransferSessionResolver.md) | Type | See API reference. |
|
|
@@ -107,6 +110,8 @@ This package publishes a narrowed surface of **92** exports. These symbols are a
|
|
|
107
110
|
| [`resolveConnectionProfileSecrets`](https://github.com/tonywied17/zero-transfer/blob/main/docs/api-md/functions/resolveConnectionProfileSecrets.md) | Function | See API reference. |
|
|
108
111
|
| [`redactConnectionProfile`](https://github.com/tonywied17/zero-transfer/blob/main/docs/api-md/functions/redactConnectionProfile.md) | Function | See API reference. |
|
|
109
112
|
| [`redactSecretSource`](https://github.com/tonywied17/zero-transfer/blob/main/docs/api-md/functions/redactSecretSource.md) | Function | See API reference. |
|
|
113
|
+
| [`redactErrorForLogging`](https://github.com/tonywied17/zero-transfer/blob/main/docs/api-md/functions/redactErrorForLogging.md) | Function | See API reference. |
|
|
114
|
+
| [`redactUrlForLogging`](https://github.com/tonywied17/zero-transfer/blob/main/docs/api-md/functions/redactUrlForLogging.md) | Function | See API reference. |
|
|
110
115
|
| [`resolveSecret`](https://github.com/tonywied17/zero-transfer/blob/main/docs/api-md/functions/resolveSecret.md) | Function | See API reference. |
|
|
111
116
|
| [`createOAuthTokenSecretSource`](https://github.com/tonywied17/zero-transfer/blob/main/docs/api-md/functions/createOAuthTokenSecretSource.md) | Function | See API reference. |
|
|
112
117
|
| [`ResolvedConnectionProfile`](https://github.com/tonywied17/zero-transfer/blob/main/docs/api-md/interfaces/ResolvedConnectionProfile.md) | Interface | See API reference. |
|
|
@@ -124,6 +129,7 @@ This package publishes a narrowed surface of **92** exports. These symbols are a
|
|
|
124
129
|
| [`examples/local-copy-file.ts`](https://github.com/tonywied17/zero-transfer/blob/main/examples/local-copy-file.ts) | Local-to-local file copy example. |
|
|
125
130
|
| [`examples/ftp-basic.ts`](https://github.com/tonywied17/zero-transfer/blob/main/examples/ftp-basic.ts) | Basic FTP upload + download example. |
|
|
126
131
|
| [`examples/transfer-queue.ts`](https://github.com/tonywied17/zero-transfer/blob/main/examples/transfer-queue.ts) | Transfer queue with concurrency, progress, and per-job receipts. |
|
|
132
|
+
| [`examples/retry-and-timeouts.ts`](https://github.com/tonywied17/zero-transfer/blob/main/examples/retry-and-timeouts.ts) | Retry, timeout, and stall-detection configuration. |
|
|
127
133
|
| [`examples/dry-run-sync.ts`](https://github.com/tonywied17/zero-transfer/blob/main/examples/dry-run-sync.ts) | Dry-run sync planner. |
|
|
128
134
|
| [`examples/diagnose-connection.ts`](https://github.com/tonywied17/zero-transfer/blob/main/examples/diagnose-connection.ts) | Diagnose a connection without exposing secrets. |
|
|
129
135
|
|
package/dist/index.cjs
CHANGED
|
@@ -60,6 +60,7 @@ __export(core_exports, {
|
|
|
60
60
|
copyBetween: () => copyBetween,
|
|
61
61
|
createAtomicDeployPlan: () => createAtomicDeployPlan,
|
|
62
62
|
createBandwidthThrottle: () => createBandwidthThrottle,
|
|
63
|
+
createDefaultRetryPolicy: () => createDefaultRetryPolicy,
|
|
63
64
|
createLocalProviderFactory: () => createLocalProviderFactory,
|
|
64
65
|
createMemoryProviderFactory: () => createMemoryProviderFactory,
|
|
65
66
|
createOAuthTokenSecretSource: () => createOAuthTokenSecretSource,
|
|
@@ -95,8 +96,10 @@ __export(core_exports, {
|
|
|
95
96
|
parseRemoteManifest: () => parseRemoteManifest,
|
|
96
97
|
redactCommand: () => redactCommand,
|
|
97
98
|
redactConnectionProfile: () => redactConnectionProfile,
|
|
99
|
+
redactErrorForLogging: () => redactErrorForLogging,
|
|
98
100
|
redactObject: () => redactObject,
|
|
99
101
|
redactSecretSource: () => redactSecretSource,
|
|
102
|
+
redactUrlForLogging: () => redactUrlForLogging,
|
|
100
103
|
redactValue: () => redactValue,
|
|
101
104
|
resolveConnectionProfileSecrets: () => resolveConnectionProfileSecrets,
|
|
102
105
|
resolveOpenSshHost: () => resolveOpenSshHost,
|
|
@@ -117,6 +120,68 @@ module.exports = __toCommonJS(core_exports);
|
|
|
117
120
|
// src/client/ZeroTransfer.ts
|
|
118
121
|
var import_node_events = require("events");
|
|
119
122
|
|
|
123
|
+
// src/logging/redaction.ts
|
|
124
|
+
var REDACTED = "[REDACTED]";
|
|
125
|
+
var SENSITIVE_KEY_PATTERN = /(?:password|passphrase|privatekey|token|secret|username|user)$/i;
|
|
126
|
+
var SECRET_COMMAND_PATTERN = /^(PASS|USER|ACCT)\s+(.+)$/i;
|
|
127
|
+
var URL_KEY_PATTERN = /(?:url|uri|href)$/i;
|
|
128
|
+
function isSensitiveKey(key) {
|
|
129
|
+
return SENSITIVE_KEY_PATTERN.test(key.replace(/[_-]/g, ""));
|
|
130
|
+
}
|
|
131
|
+
function redactCommand(command) {
|
|
132
|
+
return command.replace(SECRET_COMMAND_PATTERN, (_fullMatch, commandName) => {
|
|
133
|
+
return `${commandName.toUpperCase()} ${REDACTED}`;
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
function redactValue(value) {
|
|
137
|
+
if (typeof value === "string") {
|
|
138
|
+
return redactCommand(value);
|
|
139
|
+
}
|
|
140
|
+
if (Array.isArray(value)) {
|
|
141
|
+
return value.map((item) => redactValue(item));
|
|
142
|
+
}
|
|
143
|
+
if (value !== null && typeof value === "object") {
|
|
144
|
+
return redactObject(value);
|
|
145
|
+
}
|
|
146
|
+
return value;
|
|
147
|
+
}
|
|
148
|
+
function redactObject(input) {
|
|
149
|
+
return Object.fromEntries(
|
|
150
|
+
Object.entries(input).map(([key, value]) => {
|
|
151
|
+
if (isSensitiveKey(key)) {
|
|
152
|
+
return [key, REDACTED];
|
|
153
|
+
}
|
|
154
|
+
if (URL_KEY_PATTERN.test(key) && typeof value === "string") {
|
|
155
|
+
return [key, redactUrlForLogging(value)];
|
|
156
|
+
}
|
|
157
|
+
return [key, redactValue(value)];
|
|
158
|
+
})
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
function redactUrlForLogging(url) {
|
|
162
|
+
let parsed;
|
|
163
|
+
try {
|
|
164
|
+
parsed = typeof url === "string" ? new URL(url) : url;
|
|
165
|
+
} catch {
|
|
166
|
+
return REDACTED;
|
|
167
|
+
}
|
|
168
|
+
const origin = parsed.host.length > 0 ? `${parsed.protocol}//${parsed.host}` : parsed.protocol;
|
|
169
|
+
const query = parsed.search.length > 0 ? `?${REDACTED}` : "";
|
|
170
|
+
return `${origin}${parsed.pathname}${query}`;
|
|
171
|
+
}
|
|
172
|
+
function redactErrorForLogging(error) {
|
|
173
|
+
if (error !== null && typeof error === "object") {
|
|
174
|
+
const candidate = error;
|
|
175
|
+
if (typeof candidate.toJSON === "function") {
|
|
176
|
+
return redactObject(candidate.toJSON());
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
if (error instanceof Error) {
|
|
180
|
+
return redactObject({ message: error.message, name: error.name });
|
|
181
|
+
}
|
|
182
|
+
return { message: redactValue(typeof error === "string" ? error : String(error)) };
|
|
183
|
+
}
|
|
184
|
+
|
|
120
185
|
// src/errors/ZeroTransferError.ts
|
|
121
186
|
var ZeroTransferError = class extends Error {
|
|
122
187
|
/** Stable machine-readable error code. */
|
|
@@ -158,6 +223,11 @@ var ZeroTransferError = class extends Error {
|
|
|
158
223
|
/**
|
|
159
224
|
* Serializes the error into a plain object suitable for logs or API responses.
|
|
160
225
|
*
|
|
226
|
+
* `details` and `command` are passed through secret redaction so serialized
|
|
227
|
+
* errors never leak credentials, signed URLs, or raw protocol commands. The
|
|
228
|
+
* live {@link ZeroTransferError.details | details} property stays unredacted
|
|
229
|
+
* for programmatic consumers.
|
|
230
|
+
*
|
|
161
231
|
* @returns A JSON-safe object containing public structured error fields.
|
|
162
232
|
*/
|
|
163
233
|
toJSON() {
|
|
@@ -167,12 +237,12 @@ var ZeroTransferError = class extends Error {
|
|
|
167
237
|
message: this.message,
|
|
168
238
|
protocol: this.protocol,
|
|
169
239
|
host: this.host,
|
|
170
|
-
command: this.command,
|
|
240
|
+
command: this.command === void 0 ? void 0 : redactCommand(this.command),
|
|
171
241
|
ftpCode: this.ftpCode,
|
|
172
242
|
sftpCode: this.sftpCode,
|
|
173
243
|
path: this.path,
|
|
174
244
|
retryable: this.retryable,
|
|
175
|
-
details: this.details
|
|
245
|
+
details: this.details === void 0 ? void 0 : redactObject(this.details)
|
|
176
246
|
};
|
|
177
247
|
}
|
|
178
248
|
};
|
|
@@ -695,15 +765,20 @@ var ProviderRegistry = class {
|
|
|
695
765
|
var TransferClient = class {
|
|
696
766
|
/** Provider registry used by this client. */
|
|
697
767
|
registry;
|
|
768
|
+
/** Execution defaults applied when call sites omit their own values. */
|
|
769
|
+
defaults;
|
|
698
770
|
logger;
|
|
699
771
|
/**
|
|
700
772
|
* Creates a transfer client without opening any provider connections.
|
|
701
773
|
*
|
|
702
|
-
* @param options - Optional registry, provider factories, and
|
|
774
|
+
* @param options - Optional registry, provider factories, logger, and execution defaults.
|
|
703
775
|
*/
|
|
704
776
|
constructor(options = {}) {
|
|
705
777
|
this.registry = options.registry ?? new ProviderRegistry();
|
|
706
778
|
this.logger = options.logger ?? noopLogger;
|
|
779
|
+
if (options.defaults !== void 0) {
|
|
780
|
+
this.defaults = { ...options.defaults };
|
|
781
|
+
}
|
|
707
782
|
for (const provider of options.providers ?? []) {
|
|
708
783
|
this.registry.register(provider);
|
|
709
784
|
}
|
|
@@ -1276,18 +1351,25 @@ var TransferEngine = class {
|
|
|
1276
1351
|
for (let attemptNumber = 1; attemptNumber <= maxAttempts; attemptNumber += 1) {
|
|
1277
1352
|
this.throwIfAborted(abortScope.signal, job);
|
|
1278
1353
|
const attemptStartedAt = this.now();
|
|
1354
|
+
const attemptScope = createAttemptScope(
|
|
1355
|
+
abortScope.signal,
|
|
1356
|
+
options.timeout,
|
|
1357
|
+
job,
|
|
1358
|
+
attemptNumber
|
|
1359
|
+
);
|
|
1279
1360
|
const context = this.createExecutionContext(
|
|
1280
1361
|
job,
|
|
1281
1362
|
attemptNumber,
|
|
1282
1363
|
attemptStartedAt,
|
|
1283
1364
|
options,
|
|
1284
|
-
|
|
1365
|
+
attemptScope.signal,
|
|
1285
1366
|
(bytesTransferred) => {
|
|
1286
1367
|
latestBytesTransferred = bytesTransferred;
|
|
1287
|
-
}
|
|
1368
|
+
},
|
|
1369
|
+
attemptScope.notifyProgress
|
|
1288
1370
|
);
|
|
1289
1371
|
try {
|
|
1290
|
-
const result = await runExecutor(executor, context,
|
|
1372
|
+
const result = await runExecutor(executor, context, attemptScope.signal, job);
|
|
1291
1373
|
context.throwIfAborted();
|
|
1292
1374
|
latestBytesTransferred = result.bytesTransferred;
|
|
1293
1375
|
const completedAt = this.now();
|
|
@@ -1305,16 +1387,27 @@ var TransferEngine = class {
|
|
|
1305
1387
|
summarizeError(error)
|
|
1306
1388
|
);
|
|
1307
1389
|
attempts.push(attempt);
|
|
1308
|
-
if (error instanceof AbortError ||
|
|
1390
|
+
if (error instanceof AbortError || abortScope.signal?.aborted === true) {
|
|
1309
1391
|
throw error;
|
|
1310
1392
|
}
|
|
1311
|
-
const retryInput = {
|
|
1393
|
+
const retryInput = {
|
|
1394
|
+
attempt: attemptNumber,
|
|
1395
|
+
elapsedMs: Math.max(0, completedAt.getTime() - startedAt.getTime()),
|
|
1396
|
+
error,
|
|
1397
|
+
job
|
|
1398
|
+
};
|
|
1312
1399
|
const shouldRetry = attemptNumber < maxAttempts && (options.retry?.shouldRetry?.(retryInput) ?? isRetryable(error));
|
|
1313
1400
|
if (shouldRetry) {
|
|
1314
1401
|
options.retry?.onRetry?.(retryInput);
|
|
1402
|
+
const delayMs = normalizeDelayMs(options.retry?.getDelayMs?.(retryInput));
|
|
1403
|
+
if (delayMs > 0) {
|
|
1404
|
+
await sleepWithAbort(delayMs, abortScope.signal, job);
|
|
1405
|
+
}
|
|
1315
1406
|
continue;
|
|
1316
1407
|
}
|
|
1317
1408
|
throw createTransferFailure(job, error, attempts);
|
|
1409
|
+
} finally {
|
|
1410
|
+
attemptScope.dispose();
|
|
1318
1411
|
}
|
|
1319
1412
|
}
|
|
1320
1413
|
throw createTransferFailure(job, void 0, attempts);
|
|
@@ -1322,12 +1415,13 @@ var TransferEngine = class {
|
|
|
1322
1415
|
abortScope.dispose();
|
|
1323
1416
|
}
|
|
1324
1417
|
}
|
|
1325
|
-
createExecutionContext(job, attempt, startedAt, options, signal, updateBytesTransferred) {
|
|
1418
|
+
createExecutionContext(job, attempt, startedAt, options, signal, updateBytesTransferred, notifyProgress) {
|
|
1326
1419
|
const context = {
|
|
1327
1420
|
attempt,
|
|
1328
1421
|
job,
|
|
1329
1422
|
reportProgress: (bytesTransferred, totalBytes) => {
|
|
1330
1423
|
this.throwIfAborted(signal, job);
|
|
1424
|
+
notifyProgress();
|
|
1331
1425
|
updateBytesTransferred(bytesTransferred);
|
|
1332
1426
|
const progressInput = {
|
|
1333
1427
|
bytesTransferred,
|
|
@@ -1396,6 +1490,96 @@ function createAbortScope(parentSignal, timeout, job) {
|
|
|
1396
1490
|
signal: controller.signal
|
|
1397
1491
|
};
|
|
1398
1492
|
}
|
|
1493
|
+
function createAttemptScope(parentSignal, timeout, job, attempt) {
|
|
1494
|
+
const attemptTimeoutMs = normalizeTimeoutMs(timeout?.attemptTimeoutMs);
|
|
1495
|
+
const stallTimeoutMs = normalizeTimeoutMs(timeout?.stallTimeoutMs);
|
|
1496
|
+
if (attemptTimeoutMs === void 0 && stallTimeoutMs === void 0) {
|
|
1497
|
+
const scope = {
|
|
1498
|
+
dispose: () => void 0,
|
|
1499
|
+
notifyProgress: () => void 0
|
|
1500
|
+
};
|
|
1501
|
+
if (parentSignal !== void 0) scope.signal = parentSignal;
|
|
1502
|
+
return scope;
|
|
1503
|
+
}
|
|
1504
|
+
const controller = new AbortController();
|
|
1505
|
+
const retryable = timeout?.retryable ?? true;
|
|
1506
|
+
const abortFromParent = () => controller.abort(parentSignal?.reason);
|
|
1507
|
+
if (parentSignal?.aborted === true) {
|
|
1508
|
+
abortFromParent();
|
|
1509
|
+
} else {
|
|
1510
|
+
parentSignal?.addEventListener("abort", abortFromParent, { once: true });
|
|
1511
|
+
}
|
|
1512
|
+
const attemptTimer = attemptTimeoutMs === void 0 ? void 0 : setTimeout(() => {
|
|
1513
|
+
controller.abort(
|
|
1514
|
+
new TimeoutError({
|
|
1515
|
+
details: { attempt, attemptTimeoutMs, jobId: job.id, operation: job.operation },
|
|
1516
|
+
message: `Transfer attempt ${String(attempt)} timed out after ${String(attemptTimeoutMs)}ms: ${job.id}`,
|
|
1517
|
+
retryable
|
|
1518
|
+
})
|
|
1519
|
+
);
|
|
1520
|
+
}, attemptTimeoutMs);
|
|
1521
|
+
let stallTimer;
|
|
1522
|
+
const armStallWatchdog = () => {
|
|
1523
|
+
if (stallTimeoutMs === void 0 || controller.signal.aborted) return;
|
|
1524
|
+
if (stallTimer !== void 0) clearTimeout(stallTimer);
|
|
1525
|
+
stallTimer = setTimeout(() => {
|
|
1526
|
+
controller.abort(
|
|
1527
|
+
new TimeoutError({
|
|
1528
|
+
details: { attempt, jobId: job.id, operation: job.operation, stallTimeoutMs },
|
|
1529
|
+
message: `Transfer attempt ${String(attempt)} stalled (no progress for ${String(stallTimeoutMs)}ms): ${job.id}`,
|
|
1530
|
+
retryable
|
|
1531
|
+
})
|
|
1532
|
+
);
|
|
1533
|
+
}, stallTimeoutMs);
|
|
1534
|
+
};
|
|
1535
|
+
armStallWatchdog();
|
|
1536
|
+
return {
|
|
1537
|
+
dispose: () => {
|
|
1538
|
+
if (attemptTimer !== void 0) clearTimeout(attemptTimer);
|
|
1539
|
+
if (stallTimer !== void 0) clearTimeout(stallTimer);
|
|
1540
|
+
parentSignal?.removeEventListener("abort", abortFromParent);
|
|
1541
|
+
},
|
|
1542
|
+
notifyProgress: armStallWatchdog,
|
|
1543
|
+
signal: controller.signal
|
|
1544
|
+
};
|
|
1545
|
+
}
|
|
1546
|
+
function sleepWithAbort(delayMs, signal, job) {
|
|
1547
|
+
return new Promise((resolve, reject) => {
|
|
1548
|
+
if (signal === void 0) {
|
|
1549
|
+
setTimeout(resolve, delayMs);
|
|
1550
|
+
return;
|
|
1551
|
+
}
|
|
1552
|
+
if (signal.aborted) {
|
|
1553
|
+
reject(toAbortFailure(signal, job));
|
|
1554
|
+
return;
|
|
1555
|
+
}
|
|
1556
|
+
const rejectAbort = () => {
|
|
1557
|
+
clearTimeout(timer);
|
|
1558
|
+
reject(toAbortFailure(signal, job));
|
|
1559
|
+
};
|
|
1560
|
+
const timer = setTimeout(() => {
|
|
1561
|
+
signal.removeEventListener("abort", rejectAbort);
|
|
1562
|
+
resolve();
|
|
1563
|
+
}, delayMs);
|
|
1564
|
+
signal.addEventListener("abort", rejectAbort, { once: true });
|
|
1565
|
+
});
|
|
1566
|
+
}
|
|
1567
|
+
function toAbortFailure(signal, job) {
|
|
1568
|
+
if (signal.reason instanceof ZeroTransferError) {
|
|
1569
|
+
return signal.reason;
|
|
1570
|
+
}
|
|
1571
|
+
return new AbortError({
|
|
1572
|
+
details: { jobId: job.id, operation: job.operation },
|
|
1573
|
+
message: `Transfer job aborted: ${job.id}`,
|
|
1574
|
+
retryable: false
|
|
1575
|
+
});
|
|
1576
|
+
}
|
|
1577
|
+
function normalizeDelayMs(value) {
|
|
1578
|
+
if (value === void 0 || !Number.isFinite(value) || value <= 0) {
|
|
1579
|
+
return 0;
|
|
1580
|
+
}
|
|
1581
|
+
return Math.floor(value);
|
|
1582
|
+
}
|
|
1399
1583
|
function normalizeTimeoutMs(value) {
|
|
1400
1584
|
if (value === void 0 || !Number.isFinite(value) || value <= 0) {
|
|
1401
1585
|
return void 0;
|
|
@@ -1564,7 +1748,7 @@ async function runRoute(options) {
|
|
|
1564
1748
|
const executor = createProviderTransferExecutor({
|
|
1565
1749
|
resolveSession: ({ role }) => sessions.get(role)
|
|
1566
1750
|
});
|
|
1567
|
-
return await engine.execute(job, executor, buildExecuteOptions(options));
|
|
1751
|
+
return await engine.execute(job, executor, buildExecuteOptions(options, client));
|
|
1568
1752
|
} finally {
|
|
1569
1753
|
if (destinationSession !== void 0) {
|
|
1570
1754
|
await destinationSession.disconnect();
|
|
@@ -1601,12 +1785,14 @@ function defaultJobId(route, now) {
|
|
|
1601
1785
|
const timestamp = (now?.() ?? /* @__PURE__ */ new Date()).getTime();
|
|
1602
1786
|
return `route:${route.id}:${timestamp.toString(36)}`;
|
|
1603
1787
|
}
|
|
1604
|
-
function buildExecuteOptions(options) {
|
|
1788
|
+
function buildExecuteOptions(options, client) {
|
|
1605
1789
|
const execute = {};
|
|
1790
|
+
const retry = options.retry ?? client.defaults?.retry;
|
|
1791
|
+
const timeout = options.timeout ?? client.defaults?.timeout;
|
|
1606
1792
|
if (options.signal !== void 0) execute.signal = options.signal;
|
|
1607
|
-
if (
|
|
1793
|
+
if (retry !== void 0) execute.retry = retry;
|
|
1608
1794
|
if (options.onProgress !== void 0) execute.onProgress = options.onProgress;
|
|
1609
|
-
if (
|
|
1795
|
+
if (timeout !== void 0) execute.timeout = timeout;
|
|
1610
1796
|
if (options.bandwidthLimit !== void 0) execute.bandwidthLimit = options.bandwidthLimit;
|
|
1611
1797
|
return execute;
|
|
1612
1798
|
}
|
|
@@ -1663,41 +1849,6 @@ function defaultRouteSuffix(source, destination) {
|
|
|
1663
1849
|
return `${source}->${destination}`;
|
|
1664
1850
|
}
|
|
1665
1851
|
|
|
1666
|
-
// src/logging/redaction.ts
|
|
1667
|
-
var REDACTED = "[REDACTED]";
|
|
1668
|
-
var SENSITIVE_KEY_PATTERN = /(?:password|passphrase|privatekey|token|secret|username|user)$/i;
|
|
1669
|
-
var SECRET_COMMAND_PATTERN = /^(PASS|USER|ACCT)\s+(.+)$/i;
|
|
1670
|
-
function isSensitiveKey(key) {
|
|
1671
|
-
return SENSITIVE_KEY_PATTERN.test(key.replace(/[_-]/g, ""));
|
|
1672
|
-
}
|
|
1673
|
-
function redactCommand(command) {
|
|
1674
|
-
return command.replace(SECRET_COMMAND_PATTERN, (_fullMatch, commandName) => {
|
|
1675
|
-
return `${commandName.toUpperCase()} ${REDACTED}`;
|
|
1676
|
-
});
|
|
1677
|
-
}
|
|
1678
|
-
function redactValue(value) {
|
|
1679
|
-
if (typeof value === "string") {
|
|
1680
|
-
return redactCommand(value);
|
|
1681
|
-
}
|
|
1682
|
-
if (Array.isArray(value)) {
|
|
1683
|
-
return value.map((item) => redactValue(item));
|
|
1684
|
-
}
|
|
1685
|
-
if (value !== null && typeof value === "object") {
|
|
1686
|
-
return redactObject(value);
|
|
1687
|
-
}
|
|
1688
|
-
return value;
|
|
1689
|
-
}
|
|
1690
|
-
function redactObject(input) {
|
|
1691
|
-
return Object.fromEntries(
|
|
1692
|
-
Object.entries(input).map(([key, value]) => {
|
|
1693
|
-
if (isSensitiveKey(key)) {
|
|
1694
|
-
return [key, REDACTED];
|
|
1695
|
-
}
|
|
1696
|
-
return [key, redactValue(value)];
|
|
1697
|
-
})
|
|
1698
|
-
);
|
|
1699
|
-
}
|
|
1700
|
-
|
|
1701
1852
|
// src/profiles/SecretSource.ts
|
|
1702
1853
|
var import_node_buffer2 = require("buffer");
|
|
1703
1854
|
var import_promises = require("fs/promises");
|
|
@@ -2069,11 +2220,11 @@ var import_promises2 = require("fs/promises");
|
|
|
2069
2220
|
var import_node_path2 = __toESM(require("path"));
|
|
2070
2221
|
|
|
2071
2222
|
// src/utils/path.ts
|
|
2072
|
-
var UNSAFE_FTP_ARGUMENT_PATTERN = /[\r\n]/;
|
|
2223
|
+
var UNSAFE_FTP_ARGUMENT_PATTERN = /[\r\n\0]/;
|
|
2073
2224
|
function assertSafeFtpArgument(value, label = "path") {
|
|
2074
2225
|
if (UNSAFE_FTP_ARGUMENT_PATTERN.test(value)) {
|
|
2075
2226
|
throw new ConfigurationError({
|
|
2076
|
-
message: `Unsafe FTP ${label}: CR and
|
|
2227
|
+
message: `Unsafe FTP ${label}: CR, LF, and NUL characters are not allowed`,
|
|
2077
2228
|
retryable: false,
|
|
2078
2229
|
details: {
|
|
2079
2230
|
label
|
|
@@ -3375,7 +3526,6 @@ function expandAlgorithms(values) {
|
|
|
3375
3526
|
}
|
|
3376
3527
|
|
|
3377
3528
|
// src/profiles/importers/FileZillaImporter.ts
|
|
3378
|
-
var import_node_buffer5 = require("buffer");
|
|
3379
3529
|
function importFileZillaSites(xml) {
|
|
3380
3530
|
const events = tokenizeXml(xml);
|
|
3381
3531
|
if (events.length === 0) {
|
|
@@ -3391,7 +3541,6 @@ function importFileZillaSites(xml) {
|
|
|
3391
3541
|
const folderNamePending = [];
|
|
3392
3542
|
let inServer = false;
|
|
3393
3543
|
let serverFields = {};
|
|
3394
|
-
let serverPasswordEncoding;
|
|
3395
3544
|
let activeTag;
|
|
3396
3545
|
let captureFolderName = false;
|
|
3397
3546
|
for (const event of events) {
|
|
@@ -3404,13 +3553,9 @@ function importFileZillaSites(xml) {
|
|
|
3404
3553
|
if (event.name === "Server") {
|
|
3405
3554
|
inServer = true;
|
|
3406
3555
|
serverFields = {};
|
|
3407
|
-
serverPasswordEncoding = void 0;
|
|
3408
3556
|
continue;
|
|
3409
3557
|
}
|
|
3410
3558
|
activeTag = event.name;
|
|
3411
|
-
if (event.name === "Pass" && inServer) {
|
|
3412
|
-
serverPasswordEncoding = event.attributes["encoding"];
|
|
3413
|
-
}
|
|
3414
3559
|
if (event.name === "Name" && !inServer && folderNamePending.length > 0) {
|
|
3415
3560
|
captureFolderName = true;
|
|
3416
3561
|
}
|
|
@@ -3436,7 +3581,7 @@ function importFileZillaSites(xml) {
|
|
|
3436
3581
|
}
|
|
3437
3582
|
if (event.name === "Server") {
|
|
3438
3583
|
const folder = folderStack.filter((segment) => segment !== "");
|
|
3439
|
-
const result = buildSiteFromFields(serverFields
|
|
3584
|
+
const result = buildSiteFromFields(serverFields);
|
|
3440
3585
|
if (result.kind === "site") {
|
|
3441
3586
|
sites.push({ ...result.site, folder });
|
|
3442
3587
|
} else {
|
|
@@ -3448,7 +3593,6 @@ function importFileZillaSites(xml) {
|
|
|
3448
3593
|
}
|
|
3449
3594
|
inServer = false;
|
|
3450
3595
|
serverFields = {};
|
|
3451
|
-
serverPasswordEncoding = void 0;
|
|
3452
3596
|
activeTag = void 0;
|
|
3453
3597
|
continue;
|
|
3454
3598
|
}
|
|
@@ -3457,7 +3601,7 @@ function importFileZillaSites(xml) {
|
|
|
3457
3601
|
}
|
|
3458
3602
|
return { sites, skipped };
|
|
3459
3603
|
}
|
|
3460
|
-
function buildSiteFromFields(fields
|
|
3604
|
+
function buildSiteFromFields(fields) {
|
|
3461
3605
|
const name = (fields["Name"] ?? fields["Host"] ?? "Untitled").trim();
|
|
3462
3606
|
const host = (fields["Host"] ?? "").trim();
|
|
3463
3607
|
if (host === "") return { kind: "skipped", name };
|
|
@@ -3476,18 +3620,9 @@ function buildSiteFromFields(fields, passwordEncoding) {
|
|
|
3476
3620
|
}
|
|
3477
3621
|
const user = fields["User"]?.trim();
|
|
3478
3622
|
if (user !== void 0 && user !== "") profile.username = { value: user };
|
|
3479
|
-
let password;
|
|
3480
3623
|
const rawPass = fields["Pass"];
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
password = import_node_buffer5.Buffer.from(rawPass, "base64").toString("utf8");
|
|
3484
|
-
} else {
|
|
3485
|
-
password = rawPass;
|
|
3486
|
-
}
|
|
3487
|
-
if (password !== void 0 && password !== "") profile.password = { value: password };
|
|
3488
|
-
}
|
|
3489
|
-
const site = { name, profile };
|
|
3490
|
-
if (password !== void 0) site.password = password;
|
|
3624
|
+
const hasStoredPassword = rawPass !== void 0 && rawPass !== "";
|
|
3625
|
+
const site = { hasStoredPassword, name, profile };
|
|
3491
3626
|
const logonText = fields["Logontype"];
|
|
3492
3627
|
if (logonText !== void 0) {
|
|
3493
3628
|
const logonType = Number.parseInt(logonText.trim(), 10);
|
|
@@ -3730,6 +3865,62 @@ function mapFtp550(details) {
|
|
|
3730
3865
|
return new PermissionDeniedError(details);
|
|
3731
3866
|
}
|
|
3732
3867
|
|
|
3868
|
+
// src/transfers/createDefaultRetryPolicy.ts
|
|
3869
|
+
var DEFAULT_MAX_ATTEMPTS = 4;
|
|
3870
|
+
var DEFAULT_BASE_DELAY_MS = 250;
|
|
3871
|
+
var DEFAULT_MAX_DELAY_MS = 3e4;
|
|
3872
|
+
var DEFAULT_MAX_ELAPSED_MS = 3e5;
|
|
3873
|
+
function createDefaultRetryPolicy(options = {}) {
|
|
3874
|
+
const maxAttempts = normalizePositiveInteger(options.maxAttempts, DEFAULT_MAX_ATTEMPTS);
|
|
3875
|
+
const baseDelayMs = normalizeNonNegative(options.baseDelayMs, DEFAULT_BASE_DELAY_MS);
|
|
3876
|
+
const maxDelayMs = normalizeNonNegative(options.maxDelayMs, DEFAULT_MAX_DELAY_MS);
|
|
3877
|
+
const maxElapsedMs = normalizeNonNegative(options.maxElapsedMs, DEFAULT_MAX_ELAPSED_MS);
|
|
3878
|
+
const random = options.random ?? Math.random;
|
|
3879
|
+
return {
|
|
3880
|
+
getDelayMs(input) {
|
|
3881
|
+
const retryAfterMs = readRetryAfterMs(input.error);
|
|
3882
|
+
if (retryAfterMs !== void 0) {
|
|
3883
|
+
return retryAfterMs;
|
|
3884
|
+
}
|
|
3885
|
+
const exponentialMs = baseDelayMs * 2 ** (input.attempt - 1);
|
|
3886
|
+
const cappedMs = Math.min(maxDelayMs, exponentialMs);
|
|
3887
|
+
return Math.floor(random() * cappedMs);
|
|
3888
|
+
},
|
|
3889
|
+
maxAttempts,
|
|
3890
|
+
shouldRetry(input) {
|
|
3891
|
+
if (!(input.error instanceof ZeroTransferError) || !input.error.retryable) {
|
|
3892
|
+
return false;
|
|
3893
|
+
}
|
|
3894
|
+
if (input.elapsedMs >= maxElapsedMs) {
|
|
3895
|
+
return false;
|
|
3896
|
+
}
|
|
3897
|
+
const retryAfterMs = readRetryAfterMs(input.error);
|
|
3898
|
+
if (retryAfterMs !== void 0 && input.elapsedMs + retryAfterMs > maxElapsedMs) {
|
|
3899
|
+
return false;
|
|
3900
|
+
}
|
|
3901
|
+
return true;
|
|
3902
|
+
}
|
|
3903
|
+
};
|
|
3904
|
+
}
|
|
3905
|
+
function readRetryAfterMs(error) {
|
|
3906
|
+
if (!(error instanceof ZeroTransferError)) return void 0;
|
|
3907
|
+
const value = error.details?.["retryAfterMs"];
|
|
3908
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) return void 0;
|
|
3909
|
+
return Math.floor(value);
|
|
3910
|
+
}
|
|
3911
|
+
function normalizePositiveInteger(value, fallback) {
|
|
3912
|
+
if (value === void 0 || !Number.isFinite(value) || value < 1) {
|
|
3913
|
+
return fallback;
|
|
3914
|
+
}
|
|
3915
|
+
return Math.floor(value);
|
|
3916
|
+
}
|
|
3917
|
+
function normalizeNonNegative(value, fallback) {
|
|
3918
|
+
if (value === void 0 || !Number.isFinite(value) || value < 0) {
|
|
3919
|
+
return fallback;
|
|
3920
|
+
}
|
|
3921
|
+
return Math.floor(value);
|
|
3922
|
+
}
|
|
3923
|
+
|
|
3733
3924
|
// src/transfers/TransferPlan.ts
|
|
3734
3925
|
function createTransferPlan(input) {
|
|
3735
3926
|
const plan = {
|
|
@@ -3827,8 +4018,8 @@ var TransferQueue = class {
|
|
|
3827
4018
|
this.concurrency = normalizeConcurrency(options.concurrency);
|
|
3828
4019
|
this.defaultExecutor = options.executor;
|
|
3829
4020
|
this.resolveExecutor = options.resolveExecutor;
|
|
3830
|
-
this.retry = options.retry;
|
|
3831
|
-
this.timeout = options.timeout;
|
|
4021
|
+
this.retry = options.retry ?? options.client?.defaults?.retry;
|
|
4022
|
+
this.timeout = options.timeout ?? options.client?.defaults?.timeout;
|
|
3832
4023
|
this.bandwidthLimit = options.bandwidthLimit;
|
|
3833
4024
|
this.onProgress = options.onProgress;
|
|
3834
4025
|
this.onReceipt = options.onReceipt;
|
|
@@ -4935,6 +5126,7 @@ function isMainModule(importMetaUrl) {
|
|
|
4935
5126
|
copyBetween,
|
|
4936
5127
|
createAtomicDeployPlan,
|
|
4937
5128
|
createBandwidthThrottle,
|
|
5129
|
+
createDefaultRetryPolicy,
|
|
4938
5130
|
createLocalProviderFactory,
|
|
4939
5131
|
createMemoryProviderFactory,
|
|
4940
5132
|
createOAuthTokenSecretSource,
|
|
@@ -4970,8 +5162,10 @@ function isMainModule(importMetaUrl) {
|
|
|
4970
5162
|
parseRemoteManifest,
|
|
4971
5163
|
redactCommand,
|
|
4972
5164
|
redactConnectionProfile,
|
|
5165
|
+
redactErrorForLogging,
|
|
4973
5166
|
redactObject,
|
|
4974
5167
|
redactSecretSource,
|
|
5168
|
+
redactUrlForLogging,
|
|
4975
5169
|
redactValue,
|
|
4976
5170
|
resolveConnectionProfileSecrets,
|
|
4977
5171
|
resolveOpenSshHost,
|