@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/README.md
CHANGED
|
@@ -20,7 +20,7 @@ import { MftRoute, RouteRegistry, runRoute } from "@zero-transfer/mft";
|
|
|
20
20
|
|
|
21
21
|
## Public surface
|
|
22
22
|
|
|
23
|
-
This package publishes a narrowed surface of **
|
|
23
|
+
This package publishes a narrowed surface of **31** 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
|
| ------------------------------------------------------------------------------------------------------------------------------- | --------- | ------------------ |
|
|
@@ -54,6 +54,7 @@ This package publishes a narrowed surface of **30** exports. These symbols are a
|
|
|
54
54
|
| [`ApprovalRegistry`](https://github.com/tonywied17/zero-transfer/blob/main/docs/api-md/classes/ApprovalRegistry.md) | Class | See API reference. |
|
|
55
55
|
| [`createApprovalGate`](https://github.com/tonywied17/zero-transfer/blob/main/docs/api-md/functions/createApprovalGate.md) | Function | See API reference. |
|
|
56
56
|
| [`ApprovalRejectedError`](https://github.com/tonywied17/zero-transfer/blob/main/docs/api-md/classes/ApprovalRejectedError.md) | Class | See API reference. |
|
|
57
|
+
| [`ApprovalTimeoutError`](https://github.com/tonywied17/zero-transfer/blob/main/docs/api-md/classes/ApprovalTimeoutError.md) | Class | See API reference. |
|
|
57
58
|
|
|
58
59
|
## Examples
|
|
59
60
|
|
package/dist/index.cjs
CHANGED
|
@@ -33,6 +33,7 @@ __export(mft_exports, {
|
|
|
33
33
|
AbortError: () => AbortError,
|
|
34
34
|
ApprovalRegistry: () => ApprovalRegistry,
|
|
35
35
|
ApprovalRejectedError: () => ApprovalRejectedError,
|
|
36
|
+
ApprovalTimeoutError: () => ApprovalTimeoutError,
|
|
36
37
|
AuthenticationError: () => AuthenticationError,
|
|
37
38
|
AuthorizationError: () => AuthorizationError,
|
|
38
39
|
CLASSIC_PROVIDER_IDS: () => CLASSIC_PROVIDER_IDS,
|
|
@@ -70,6 +71,7 @@ __export(mft_exports, {
|
|
|
70
71
|
createApprovalGate: () => createApprovalGate,
|
|
71
72
|
createAtomicDeployPlan: () => createAtomicDeployPlan,
|
|
72
73
|
createBandwidthThrottle: () => createBandwidthThrottle,
|
|
74
|
+
createDefaultRetryPolicy: () => createDefaultRetryPolicy,
|
|
73
75
|
createInboxRoute: () => createInboxRoute,
|
|
74
76
|
createJsonlAuditLog: () => createJsonlAuditLog,
|
|
75
77
|
createLocalProviderFactory: () => createLocalProviderFactory,
|
|
@@ -117,8 +119,10 @@ __export(mft_exports, {
|
|
|
117
119
|
parseRemoteManifest: () => parseRemoteManifest,
|
|
118
120
|
redactCommand: () => redactCommand,
|
|
119
121
|
redactConnectionProfile: () => redactConnectionProfile,
|
|
122
|
+
redactErrorForLogging: () => redactErrorForLogging,
|
|
120
123
|
redactObject: () => redactObject,
|
|
121
124
|
redactSecretSource: () => redactSecretSource,
|
|
125
|
+
redactUrlForLogging: () => redactUrlForLogging,
|
|
122
126
|
redactValue: () => redactValue,
|
|
123
127
|
resolveConnectionProfileSecrets: () => resolveConnectionProfileSecrets,
|
|
124
128
|
resolveOpenSshHost: () => resolveOpenSshHost,
|
|
@@ -143,6 +147,68 @@ module.exports = __toCommonJS(mft_exports);
|
|
|
143
147
|
// src/client/ZeroTransfer.ts
|
|
144
148
|
var import_node_events = require("events");
|
|
145
149
|
|
|
150
|
+
// src/logging/redaction.ts
|
|
151
|
+
var REDACTED = "[REDACTED]";
|
|
152
|
+
var SENSITIVE_KEY_PATTERN = /(?:password|passphrase|privatekey|token|secret|username|user)$/i;
|
|
153
|
+
var SECRET_COMMAND_PATTERN = /^(PASS|USER|ACCT)\s+(.+)$/i;
|
|
154
|
+
var URL_KEY_PATTERN = /(?:url|uri|href)$/i;
|
|
155
|
+
function isSensitiveKey(key) {
|
|
156
|
+
return SENSITIVE_KEY_PATTERN.test(key.replace(/[_-]/g, ""));
|
|
157
|
+
}
|
|
158
|
+
function redactCommand(command) {
|
|
159
|
+
return command.replace(SECRET_COMMAND_PATTERN, (_fullMatch, commandName) => {
|
|
160
|
+
return `${commandName.toUpperCase()} ${REDACTED}`;
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
function redactValue(value) {
|
|
164
|
+
if (typeof value === "string") {
|
|
165
|
+
return redactCommand(value);
|
|
166
|
+
}
|
|
167
|
+
if (Array.isArray(value)) {
|
|
168
|
+
return value.map((item) => redactValue(item));
|
|
169
|
+
}
|
|
170
|
+
if (value !== null && typeof value === "object") {
|
|
171
|
+
return redactObject(value);
|
|
172
|
+
}
|
|
173
|
+
return value;
|
|
174
|
+
}
|
|
175
|
+
function redactObject(input) {
|
|
176
|
+
return Object.fromEntries(
|
|
177
|
+
Object.entries(input).map(([key, value]) => {
|
|
178
|
+
if (isSensitiveKey(key)) {
|
|
179
|
+
return [key, REDACTED];
|
|
180
|
+
}
|
|
181
|
+
if (URL_KEY_PATTERN.test(key) && typeof value === "string") {
|
|
182
|
+
return [key, redactUrlForLogging(value)];
|
|
183
|
+
}
|
|
184
|
+
return [key, redactValue(value)];
|
|
185
|
+
})
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
function redactUrlForLogging(url) {
|
|
189
|
+
let parsed;
|
|
190
|
+
try {
|
|
191
|
+
parsed = typeof url === "string" ? new URL(url) : url;
|
|
192
|
+
} catch {
|
|
193
|
+
return REDACTED;
|
|
194
|
+
}
|
|
195
|
+
const origin = parsed.host.length > 0 ? `${parsed.protocol}//${parsed.host}` : parsed.protocol;
|
|
196
|
+
const query = parsed.search.length > 0 ? `?${REDACTED}` : "";
|
|
197
|
+
return `${origin}${parsed.pathname}${query}`;
|
|
198
|
+
}
|
|
199
|
+
function redactErrorForLogging(error) {
|
|
200
|
+
if (error !== null && typeof error === "object") {
|
|
201
|
+
const candidate = error;
|
|
202
|
+
if (typeof candidate.toJSON === "function") {
|
|
203
|
+
return redactObject(candidate.toJSON());
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
if (error instanceof Error) {
|
|
207
|
+
return redactObject({ message: error.message, name: error.name });
|
|
208
|
+
}
|
|
209
|
+
return { message: redactValue(typeof error === "string" ? error : String(error)) };
|
|
210
|
+
}
|
|
211
|
+
|
|
146
212
|
// src/errors/ZeroTransferError.ts
|
|
147
213
|
var ZeroTransferError = class extends Error {
|
|
148
214
|
/** Stable machine-readable error code. */
|
|
@@ -184,6 +250,11 @@ var ZeroTransferError = class extends Error {
|
|
|
184
250
|
/**
|
|
185
251
|
* Serializes the error into a plain object suitable for logs or API responses.
|
|
186
252
|
*
|
|
253
|
+
* `details` and `command` are passed through secret redaction so serialized
|
|
254
|
+
* errors never leak credentials, signed URLs, or raw protocol commands. The
|
|
255
|
+
* live {@link ZeroTransferError.details | details} property stays unredacted
|
|
256
|
+
* for programmatic consumers.
|
|
257
|
+
*
|
|
187
258
|
* @returns A JSON-safe object containing public structured error fields.
|
|
188
259
|
*/
|
|
189
260
|
toJSON() {
|
|
@@ -193,12 +264,12 @@ var ZeroTransferError = class extends Error {
|
|
|
193
264
|
message: this.message,
|
|
194
265
|
protocol: this.protocol,
|
|
195
266
|
host: this.host,
|
|
196
|
-
command: this.command,
|
|
267
|
+
command: this.command === void 0 ? void 0 : redactCommand(this.command),
|
|
197
268
|
ftpCode: this.ftpCode,
|
|
198
269
|
sftpCode: this.sftpCode,
|
|
199
270
|
path: this.path,
|
|
200
271
|
retryable: this.retryable,
|
|
201
|
-
details: this.details
|
|
272
|
+
details: this.details === void 0 ? void 0 : redactObject(this.details)
|
|
202
273
|
};
|
|
203
274
|
}
|
|
204
275
|
};
|
|
@@ -721,15 +792,20 @@ var ProviderRegistry = class {
|
|
|
721
792
|
var TransferClient = class {
|
|
722
793
|
/** Provider registry used by this client. */
|
|
723
794
|
registry;
|
|
795
|
+
/** Execution defaults applied when call sites omit their own values. */
|
|
796
|
+
defaults;
|
|
724
797
|
logger;
|
|
725
798
|
/**
|
|
726
799
|
* Creates a transfer client without opening any provider connections.
|
|
727
800
|
*
|
|
728
|
-
* @param options - Optional registry, provider factories, and
|
|
801
|
+
* @param options - Optional registry, provider factories, logger, and execution defaults.
|
|
729
802
|
*/
|
|
730
803
|
constructor(options = {}) {
|
|
731
804
|
this.registry = options.registry ?? new ProviderRegistry();
|
|
732
805
|
this.logger = options.logger ?? noopLogger;
|
|
806
|
+
if (options.defaults !== void 0) {
|
|
807
|
+
this.defaults = { ...options.defaults };
|
|
808
|
+
}
|
|
733
809
|
for (const provider of options.providers ?? []) {
|
|
734
810
|
this.registry.register(provider);
|
|
735
811
|
}
|
|
@@ -1302,18 +1378,25 @@ var TransferEngine = class {
|
|
|
1302
1378
|
for (let attemptNumber = 1; attemptNumber <= maxAttempts; attemptNumber += 1) {
|
|
1303
1379
|
this.throwIfAborted(abortScope.signal, job);
|
|
1304
1380
|
const attemptStartedAt = this.now();
|
|
1381
|
+
const attemptScope = createAttemptScope(
|
|
1382
|
+
abortScope.signal,
|
|
1383
|
+
options.timeout,
|
|
1384
|
+
job,
|
|
1385
|
+
attemptNumber
|
|
1386
|
+
);
|
|
1305
1387
|
const context = this.createExecutionContext(
|
|
1306
1388
|
job,
|
|
1307
1389
|
attemptNumber,
|
|
1308
1390
|
attemptStartedAt,
|
|
1309
1391
|
options,
|
|
1310
|
-
|
|
1392
|
+
attemptScope.signal,
|
|
1311
1393
|
(bytesTransferred) => {
|
|
1312
1394
|
latestBytesTransferred = bytesTransferred;
|
|
1313
|
-
}
|
|
1395
|
+
},
|
|
1396
|
+
attemptScope.notifyProgress
|
|
1314
1397
|
);
|
|
1315
1398
|
try {
|
|
1316
|
-
const result = await runExecutor(executor, context,
|
|
1399
|
+
const result = await runExecutor(executor, context, attemptScope.signal, job);
|
|
1317
1400
|
context.throwIfAborted();
|
|
1318
1401
|
latestBytesTransferred = result.bytesTransferred;
|
|
1319
1402
|
const completedAt = this.now();
|
|
@@ -1331,16 +1414,27 @@ var TransferEngine = class {
|
|
|
1331
1414
|
summarizeError(error)
|
|
1332
1415
|
);
|
|
1333
1416
|
attempts.push(attempt);
|
|
1334
|
-
if (error instanceof AbortError ||
|
|
1417
|
+
if (error instanceof AbortError || abortScope.signal?.aborted === true) {
|
|
1335
1418
|
throw error;
|
|
1336
1419
|
}
|
|
1337
|
-
const retryInput = {
|
|
1420
|
+
const retryInput = {
|
|
1421
|
+
attempt: attemptNumber,
|
|
1422
|
+
elapsedMs: Math.max(0, completedAt.getTime() - startedAt.getTime()),
|
|
1423
|
+
error,
|
|
1424
|
+
job
|
|
1425
|
+
};
|
|
1338
1426
|
const shouldRetry = attemptNumber < maxAttempts && (options.retry?.shouldRetry?.(retryInput) ?? isRetryable(error));
|
|
1339
1427
|
if (shouldRetry) {
|
|
1340
1428
|
options.retry?.onRetry?.(retryInput);
|
|
1429
|
+
const delayMs = normalizeDelayMs(options.retry?.getDelayMs?.(retryInput));
|
|
1430
|
+
if (delayMs > 0) {
|
|
1431
|
+
await sleepWithAbort(delayMs, abortScope.signal, job);
|
|
1432
|
+
}
|
|
1341
1433
|
continue;
|
|
1342
1434
|
}
|
|
1343
1435
|
throw createTransferFailure(job, error, attempts);
|
|
1436
|
+
} finally {
|
|
1437
|
+
attemptScope.dispose();
|
|
1344
1438
|
}
|
|
1345
1439
|
}
|
|
1346
1440
|
throw createTransferFailure(job, void 0, attempts);
|
|
@@ -1348,12 +1442,13 @@ var TransferEngine = class {
|
|
|
1348
1442
|
abortScope.dispose();
|
|
1349
1443
|
}
|
|
1350
1444
|
}
|
|
1351
|
-
createExecutionContext(job, attempt, startedAt, options, signal, updateBytesTransferred) {
|
|
1445
|
+
createExecutionContext(job, attempt, startedAt, options, signal, updateBytesTransferred, notifyProgress) {
|
|
1352
1446
|
const context = {
|
|
1353
1447
|
attempt,
|
|
1354
1448
|
job,
|
|
1355
1449
|
reportProgress: (bytesTransferred, totalBytes) => {
|
|
1356
1450
|
this.throwIfAborted(signal, job);
|
|
1451
|
+
notifyProgress();
|
|
1357
1452
|
updateBytesTransferred(bytesTransferred);
|
|
1358
1453
|
const progressInput = {
|
|
1359
1454
|
bytesTransferred,
|
|
@@ -1422,6 +1517,96 @@ function createAbortScope(parentSignal, timeout, job) {
|
|
|
1422
1517
|
signal: controller.signal
|
|
1423
1518
|
};
|
|
1424
1519
|
}
|
|
1520
|
+
function createAttemptScope(parentSignal, timeout, job, attempt) {
|
|
1521
|
+
const attemptTimeoutMs = normalizeTimeoutMs(timeout?.attemptTimeoutMs);
|
|
1522
|
+
const stallTimeoutMs = normalizeTimeoutMs(timeout?.stallTimeoutMs);
|
|
1523
|
+
if (attemptTimeoutMs === void 0 && stallTimeoutMs === void 0) {
|
|
1524
|
+
const scope = {
|
|
1525
|
+
dispose: () => void 0,
|
|
1526
|
+
notifyProgress: () => void 0
|
|
1527
|
+
};
|
|
1528
|
+
if (parentSignal !== void 0) scope.signal = parentSignal;
|
|
1529
|
+
return scope;
|
|
1530
|
+
}
|
|
1531
|
+
const controller = new AbortController();
|
|
1532
|
+
const retryable = timeout?.retryable ?? true;
|
|
1533
|
+
const abortFromParent = () => controller.abort(parentSignal?.reason);
|
|
1534
|
+
if (parentSignal?.aborted === true) {
|
|
1535
|
+
abortFromParent();
|
|
1536
|
+
} else {
|
|
1537
|
+
parentSignal?.addEventListener("abort", abortFromParent, { once: true });
|
|
1538
|
+
}
|
|
1539
|
+
const attemptTimer = attemptTimeoutMs === void 0 ? void 0 : setTimeout(() => {
|
|
1540
|
+
controller.abort(
|
|
1541
|
+
new TimeoutError({
|
|
1542
|
+
details: { attempt, attemptTimeoutMs, jobId: job.id, operation: job.operation },
|
|
1543
|
+
message: `Transfer attempt ${String(attempt)} timed out after ${String(attemptTimeoutMs)}ms: ${job.id}`,
|
|
1544
|
+
retryable
|
|
1545
|
+
})
|
|
1546
|
+
);
|
|
1547
|
+
}, attemptTimeoutMs);
|
|
1548
|
+
let stallTimer;
|
|
1549
|
+
const armStallWatchdog = () => {
|
|
1550
|
+
if (stallTimeoutMs === void 0 || controller.signal.aborted) return;
|
|
1551
|
+
if (stallTimer !== void 0) clearTimeout(stallTimer);
|
|
1552
|
+
stallTimer = setTimeout(() => {
|
|
1553
|
+
controller.abort(
|
|
1554
|
+
new TimeoutError({
|
|
1555
|
+
details: { attempt, jobId: job.id, operation: job.operation, stallTimeoutMs },
|
|
1556
|
+
message: `Transfer attempt ${String(attempt)} stalled (no progress for ${String(stallTimeoutMs)}ms): ${job.id}`,
|
|
1557
|
+
retryable
|
|
1558
|
+
})
|
|
1559
|
+
);
|
|
1560
|
+
}, stallTimeoutMs);
|
|
1561
|
+
};
|
|
1562
|
+
armStallWatchdog();
|
|
1563
|
+
return {
|
|
1564
|
+
dispose: () => {
|
|
1565
|
+
if (attemptTimer !== void 0) clearTimeout(attemptTimer);
|
|
1566
|
+
if (stallTimer !== void 0) clearTimeout(stallTimer);
|
|
1567
|
+
parentSignal?.removeEventListener("abort", abortFromParent);
|
|
1568
|
+
},
|
|
1569
|
+
notifyProgress: armStallWatchdog,
|
|
1570
|
+
signal: controller.signal
|
|
1571
|
+
};
|
|
1572
|
+
}
|
|
1573
|
+
function sleepWithAbort(delayMs, signal, job) {
|
|
1574
|
+
return new Promise((resolve, reject) => {
|
|
1575
|
+
if (signal === void 0) {
|
|
1576
|
+
setTimeout(resolve, delayMs);
|
|
1577
|
+
return;
|
|
1578
|
+
}
|
|
1579
|
+
if (signal.aborted) {
|
|
1580
|
+
reject(toAbortFailure(signal, job));
|
|
1581
|
+
return;
|
|
1582
|
+
}
|
|
1583
|
+
const rejectAbort = () => {
|
|
1584
|
+
clearTimeout(timer);
|
|
1585
|
+
reject(toAbortFailure(signal, job));
|
|
1586
|
+
};
|
|
1587
|
+
const timer = setTimeout(() => {
|
|
1588
|
+
signal.removeEventListener("abort", rejectAbort);
|
|
1589
|
+
resolve();
|
|
1590
|
+
}, delayMs);
|
|
1591
|
+
signal.addEventListener("abort", rejectAbort, { once: true });
|
|
1592
|
+
});
|
|
1593
|
+
}
|
|
1594
|
+
function toAbortFailure(signal, job) {
|
|
1595
|
+
if (signal.reason instanceof ZeroTransferError) {
|
|
1596
|
+
return signal.reason;
|
|
1597
|
+
}
|
|
1598
|
+
return new AbortError({
|
|
1599
|
+
details: { jobId: job.id, operation: job.operation },
|
|
1600
|
+
message: `Transfer job aborted: ${job.id}`,
|
|
1601
|
+
retryable: false
|
|
1602
|
+
});
|
|
1603
|
+
}
|
|
1604
|
+
function normalizeDelayMs(value) {
|
|
1605
|
+
if (value === void 0 || !Number.isFinite(value) || value <= 0) {
|
|
1606
|
+
return 0;
|
|
1607
|
+
}
|
|
1608
|
+
return Math.floor(value);
|
|
1609
|
+
}
|
|
1425
1610
|
function normalizeTimeoutMs(value) {
|
|
1426
1611
|
if (value === void 0 || !Number.isFinite(value) || value <= 0) {
|
|
1427
1612
|
return void 0;
|
|
@@ -1590,7 +1775,7 @@ async function runRoute(options) {
|
|
|
1590
1775
|
const executor = createProviderTransferExecutor({
|
|
1591
1776
|
resolveSession: ({ role }) => sessions.get(role)
|
|
1592
1777
|
});
|
|
1593
|
-
return await engine.execute(job, executor, buildExecuteOptions(options));
|
|
1778
|
+
return await engine.execute(job, executor, buildExecuteOptions(options, client));
|
|
1594
1779
|
} finally {
|
|
1595
1780
|
if (destinationSession !== void 0) {
|
|
1596
1781
|
await destinationSession.disconnect();
|
|
@@ -1627,12 +1812,14 @@ function defaultJobId(route, now) {
|
|
|
1627
1812
|
const timestamp = (now?.() ?? /* @__PURE__ */ new Date()).getTime();
|
|
1628
1813
|
return `route:${route.id}:${timestamp.toString(36)}`;
|
|
1629
1814
|
}
|
|
1630
|
-
function buildExecuteOptions(options) {
|
|
1815
|
+
function buildExecuteOptions(options, client) {
|
|
1631
1816
|
const execute = {};
|
|
1817
|
+
const retry = options.retry ?? client.defaults?.retry;
|
|
1818
|
+
const timeout = options.timeout ?? client.defaults?.timeout;
|
|
1632
1819
|
if (options.signal !== void 0) execute.signal = options.signal;
|
|
1633
|
-
if (
|
|
1820
|
+
if (retry !== void 0) execute.retry = retry;
|
|
1634
1821
|
if (options.onProgress !== void 0) execute.onProgress = options.onProgress;
|
|
1635
|
-
if (
|
|
1822
|
+
if (timeout !== void 0) execute.timeout = timeout;
|
|
1636
1823
|
if (options.bandwidthLimit !== void 0) execute.bandwidthLimit = options.bandwidthLimit;
|
|
1637
1824
|
return execute;
|
|
1638
1825
|
}
|
|
@@ -1689,41 +1876,6 @@ function defaultRouteSuffix(source, destination) {
|
|
|
1689
1876
|
return `${source}->${destination}`;
|
|
1690
1877
|
}
|
|
1691
1878
|
|
|
1692
|
-
// src/logging/redaction.ts
|
|
1693
|
-
var REDACTED = "[REDACTED]";
|
|
1694
|
-
var SENSITIVE_KEY_PATTERN = /(?:password|passphrase|privatekey|token|secret|username|user)$/i;
|
|
1695
|
-
var SECRET_COMMAND_PATTERN = /^(PASS|USER|ACCT)\s+(.+)$/i;
|
|
1696
|
-
function isSensitiveKey(key) {
|
|
1697
|
-
return SENSITIVE_KEY_PATTERN.test(key.replace(/[_-]/g, ""));
|
|
1698
|
-
}
|
|
1699
|
-
function redactCommand(command) {
|
|
1700
|
-
return command.replace(SECRET_COMMAND_PATTERN, (_fullMatch, commandName) => {
|
|
1701
|
-
return `${commandName.toUpperCase()} ${REDACTED}`;
|
|
1702
|
-
});
|
|
1703
|
-
}
|
|
1704
|
-
function redactValue(value) {
|
|
1705
|
-
if (typeof value === "string") {
|
|
1706
|
-
return redactCommand(value);
|
|
1707
|
-
}
|
|
1708
|
-
if (Array.isArray(value)) {
|
|
1709
|
-
return value.map((item) => redactValue(item));
|
|
1710
|
-
}
|
|
1711
|
-
if (value !== null && typeof value === "object") {
|
|
1712
|
-
return redactObject(value);
|
|
1713
|
-
}
|
|
1714
|
-
return value;
|
|
1715
|
-
}
|
|
1716
|
-
function redactObject(input) {
|
|
1717
|
-
return Object.fromEntries(
|
|
1718
|
-
Object.entries(input).map(([key, value]) => {
|
|
1719
|
-
if (isSensitiveKey(key)) {
|
|
1720
|
-
return [key, REDACTED];
|
|
1721
|
-
}
|
|
1722
|
-
return [key, redactValue(value)];
|
|
1723
|
-
})
|
|
1724
|
-
);
|
|
1725
|
-
}
|
|
1726
|
-
|
|
1727
1879
|
// src/profiles/SecretSource.ts
|
|
1728
1880
|
var import_node_buffer2 = require("buffer");
|
|
1729
1881
|
var import_promises = require("fs/promises");
|
|
@@ -2095,11 +2247,11 @@ var import_promises2 = require("fs/promises");
|
|
|
2095
2247
|
var import_node_path2 = __toESM(require("path"));
|
|
2096
2248
|
|
|
2097
2249
|
// src/utils/path.ts
|
|
2098
|
-
var UNSAFE_FTP_ARGUMENT_PATTERN = /[\r\n]/;
|
|
2250
|
+
var UNSAFE_FTP_ARGUMENT_PATTERN = /[\r\n\0]/;
|
|
2099
2251
|
function assertSafeFtpArgument(value, label = "path") {
|
|
2100
2252
|
if (UNSAFE_FTP_ARGUMENT_PATTERN.test(value)) {
|
|
2101
2253
|
throw new ConfigurationError({
|
|
2102
|
-
message: `Unsafe FTP ${label}: CR and
|
|
2254
|
+
message: `Unsafe FTP ${label}: CR, LF, and NUL characters are not allowed`,
|
|
2103
2255
|
retryable: false,
|
|
2104
2256
|
details: {
|
|
2105
2257
|
label
|
|
@@ -3401,7 +3553,6 @@ function expandAlgorithms(values) {
|
|
|
3401
3553
|
}
|
|
3402
3554
|
|
|
3403
3555
|
// src/profiles/importers/FileZillaImporter.ts
|
|
3404
|
-
var import_node_buffer5 = require("buffer");
|
|
3405
3556
|
function importFileZillaSites(xml) {
|
|
3406
3557
|
const events = tokenizeXml(xml);
|
|
3407
3558
|
if (events.length === 0) {
|
|
@@ -3417,7 +3568,6 @@ function importFileZillaSites(xml) {
|
|
|
3417
3568
|
const folderNamePending = [];
|
|
3418
3569
|
let inServer = false;
|
|
3419
3570
|
let serverFields = {};
|
|
3420
|
-
let serverPasswordEncoding;
|
|
3421
3571
|
let activeTag;
|
|
3422
3572
|
let captureFolderName = false;
|
|
3423
3573
|
for (const event of events) {
|
|
@@ -3430,13 +3580,9 @@ function importFileZillaSites(xml) {
|
|
|
3430
3580
|
if (event.name === "Server") {
|
|
3431
3581
|
inServer = true;
|
|
3432
3582
|
serverFields = {};
|
|
3433
|
-
serverPasswordEncoding = void 0;
|
|
3434
3583
|
continue;
|
|
3435
3584
|
}
|
|
3436
3585
|
activeTag = event.name;
|
|
3437
|
-
if (event.name === "Pass" && inServer) {
|
|
3438
|
-
serverPasswordEncoding = event.attributes["encoding"];
|
|
3439
|
-
}
|
|
3440
3586
|
if (event.name === "Name" && !inServer && folderNamePending.length > 0) {
|
|
3441
3587
|
captureFolderName = true;
|
|
3442
3588
|
}
|
|
@@ -3462,7 +3608,7 @@ function importFileZillaSites(xml) {
|
|
|
3462
3608
|
}
|
|
3463
3609
|
if (event.name === "Server") {
|
|
3464
3610
|
const folder = folderStack.filter((segment) => segment !== "");
|
|
3465
|
-
const result = buildSiteFromFields(serverFields
|
|
3611
|
+
const result = buildSiteFromFields(serverFields);
|
|
3466
3612
|
if (result.kind === "site") {
|
|
3467
3613
|
sites.push({ ...result.site, folder });
|
|
3468
3614
|
} else {
|
|
@@ -3474,7 +3620,6 @@ function importFileZillaSites(xml) {
|
|
|
3474
3620
|
}
|
|
3475
3621
|
inServer = false;
|
|
3476
3622
|
serverFields = {};
|
|
3477
|
-
serverPasswordEncoding = void 0;
|
|
3478
3623
|
activeTag = void 0;
|
|
3479
3624
|
continue;
|
|
3480
3625
|
}
|
|
@@ -3483,7 +3628,7 @@ function importFileZillaSites(xml) {
|
|
|
3483
3628
|
}
|
|
3484
3629
|
return { sites, skipped };
|
|
3485
3630
|
}
|
|
3486
|
-
function buildSiteFromFields(fields
|
|
3631
|
+
function buildSiteFromFields(fields) {
|
|
3487
3632
|
const name = (fields["Name"] ?? fields["Host"] ?? "Untitled").trim();
|
|
3488
3633
|
const host = (fields["Host"] ?? "").trim();
|
|
3489
3634
|
if (host === "") return { kind: "skipped", name };
|
|
@@ -3502,18 +3647,9 @@ function buildSiteFromFields(fields, passwordEncoding) {
|
|
|
3502
3647
|
}
|
|
3503
3648
|
const user = fields["User"]?.trim();
|
|
3504
3649
|
if (user !== void 0 && user !== "") profile.username = { value: user };
|
|
3505
|
-
let password;
|
|
3506
3650
|
const rawPass = fields["Pass"];
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
password = import_node_buffer5.Buffer.from(rawPass, "base64").toString("utf8");
|
|
3510
|
-
} else {
|
|
3511
|
-
password = rawPass;
|
|
3512
|
-
}
|
|
3513
|
-
if (password !== void 0 && password !== "") profile.password = { value: password };
|
|
3514
|
-
}
|
|
3515
|
-
const site = { name, profile };
|
|
3516
|
-
if (password !== void 0) site.password = password;
|
|
3651
|
+
const hasStoredPassword = rawPass !== void 0 && rawPass !== "";
|
|
3652
|
+
const site = { hasStoredPassword, name, profile };
|
|
3517
3653
|
const logonText = fields["Logontype"];
|
|
3518
3654
|
if (logonText !== void 0) {
|
|
3519
3655
|
const logonType = Number.parseInt(logonText.trim(), 10);
|
|
@@ -3756,6 +3892,62 @@ function mapFtp550(details) {
|
|
|
3756
3892
|
return new PermissionDeniedError(details);
|
|
3757
3893
|
}
|
|
3758
3894
|
|
|
3895
|
+
// src/transfers/createDefaultRetryPolicy.ts
|
|
3896
|
+
var DEFAULT_MAX_ATTEMPTS = 4;
|
|
3897
|
+
var DEFAULT_BASE_DELAY_MS = 250;
|
|
3898
|
+
var DEFAULT_MAX_DELAY_MS = 3e4;
|
|
3899
|
+
var DEFAULT_MAX_ELAPSED_MS = 3e5;
|
|
3900
|
+
function createDefaultRetryPolicy(options = {}) {
|
|
3901
|
+
const maxAttempts = normalizePositiveInteger(options.maxAttempts, DEFAULT_MAX_ATTEMPTS);
|
|
3902
|
+
const baseDelayMs = normalizeNonNegative(options.baseDelayMs, DEFAULT_BASE_DELAY_MS);
|
|
3903
|
+
const maxDelayMs = normalizeNonNegative(options.maxDelayMs, DEFAULT_MAX_DELAY_MS);
|
|
3904
|
+
const maxElapsedMs = normalizeNonNegative(options.maxElapsedMs, DEFAULT_MAX_ELAPSED_MS);
|
|
3905
|
+
const random = options.random ?? Math.random;
|
|
3906
|
+
return {
|
|
3907
|
+
getDelayMs(input) {
|
|
3908
|
+
const retryAfterMs = readRetryAfterMs(input.error);
|
|
3909
|
+
if (retryAfterMs !== void 0) {
|
|
3910
|
+
return retryAfterMs;
|
|
3911
|
+
}
|
|
3912
|
+
const exponentialMs = baseDelayMs * 2 ** (input.attempt - 1);
|
|
3913
|
+
const cappedMs = Math.min(maxDelayMs, exponentialMs);
|
|
3914
|
+
return Math.floor(random() * cappedMs);
|
|
3915
|
+
},
|
|
3916
|
+
maxAttempts,
|
|
3917
|
+
shouldRetry(input) {
|
|
3918
|
+
if (!(input.error instanceof ZeroTransferError) || !input.error.retryable) {
|
|
3919
|
+
return false;
|
|
3920
|
+
}
|
|
3921
|
+
if (input.elapsedMs >= maxElapsedMs) {
|
|
3922
|
+
return false;
|
|
3923
|
+
}
|
|
3924
|
+
const retryAfterMs = readRetryAfterMs(input.error);
|
|
3925
|
+
if (retryAfterMs !== void 0 && input.elapsedMs + retryAfterMs > maxElapsedMs) {
|
|
3926
|
+
return false;
|
|
3927
|
+
}
|
|
3928
|
+
return true;
|
|
3929
|
+
}
|
|
3930
|
+
};
|
|
3931
|
+
}
|
|
3932
|
+
function readRetryAfterMs(error) {
|
|
3933
|
+
if (!(error instanceof ZeroTransferError)) return void 0;
|
|
3934
|
+
const value = error.details?.["retryAfterMs"];
|
|
3935
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) return void 0;
|
|
3936
|
+
return Math.floor(value);
|
|
3937
|
+
}
|
|
3938
|
+
function normalizePositiveInteger(value, fallback) {
|
|
3939
|
+
if (value === void 0 || !Number.isFinite(value) || value < 1) {
|
|
3940
|
+
return fallback;
|
|
3941
|
+
}
|
|
3942
|
+
return Math.floor(value);
|
|
3943
|
+
}
|
|
3944
|
+
function normalizeNonNegative(value, fallback) {
|
|
3945
|
+
if (value === void 0 || !Number.isFinite(value) || value < 0) {
|
|
3946
|
+
return fallback;
|
|
3947
|
+
}
|
|
3948
|
+
return Math.floor(value);
|
|
3949
|
+
}
|
|
3950
|
+
|
|
3759
3951
|
// src/transfers/TransferPlan.ts
|
|
3760
3952
|
function createTransferPlan(input) {
|
|
3761
3953
|
const plan = {
|
|
@@ -3853,8 +4045,8 @@ var TransferQueue = class {
|
|
|
3853
4045
|
this.concurrency = normalizeConcurrency(options.concurrency);
|
|
3854
4046
|
this.defaultExecutor = options.executor;
|
|
3855
4047
|
this.resolveExecutor = options.resolveExecutor;
|
|
3856
|
-
this.retry = options.retry;
|
|
3857
|
-
this.timeout = options.timeout;
|
|
4048
|
+
this.retry = options.retry ?? options.client?.defaults?.retry;
|
|
4049
|
+
this.timeout = options.timeout ?? options.client?.defaults?.timeout;
|
|
3858
4050
|
this.bandwidthLimit = options.bandwidthLimit;
|
|
3859
4051
|
this.onProgress = options.onProgress;
|
|
3860
4052
|
this.onReceipt = options.onReceipt;
|
|
@@ -5267,6 +5459,7 @@ async function dispatchWebhook(options) {
|
|
|
5267
5459
|
return { attempts: attempt, delivered: false, status: lastStatus };
|
|
5268
5460
|
}
|
|
5269
5461
|
function createWebhookAuditLog(options) {
|
|
5462
|
+
validateTarget(options.target);
|
|
5270
5463
|
return {
|
|
5271
5464
|
list: () => Promise.resolve([]),
|
|
5272
5465
|
record: async (entry) => {
|
|
@@ -5289,6 +5482,24 @@ function validateTarget(target) {
|
|
|
5289
5482
|
retryable: false
|
|
5290
5483
|
});
|
|
5291
5484
|
}
|
|
5485
|
+
let parsed;
|
|
5486
|
+
try {
|
|
5487
|
+
parsed = new URL(target.url);
|
|
5488
|
+
} catch (error) {
|
|
5489
|
+
throw new ConfigurationError({
|
|
5490
|
+
cause: error,
|
|
5491
|
+
details: { url: target.url },
|
|
5492
|
+
message: "Webhook target url must be an absolute URL",
|
|
5493
|
+
retryable: false
|
|
5494
|
+
});
|
|
5495
|
+
}
|
|
5496
|
+
if (parsed.protocol === "https:") return;
|
|
5497
|
+
if (parsed.protocol === "http:" && target.allowInsecureUrl === true) return;
|
|
5498
|
+
throw new ConfigurationError({
|
|
5499
|
+
details: { protocol: parsed.protocol, url: target.url },
|
|
5500
|
+
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}"`,
|
|
5501
|
+
retryable: false
|
|
5502
|
+
});
|
|
5292
5503
|
}
|
|
5293
5504
|
function normalizeRetry(retry) {
|
|
5294
5505
|
return {
|
|
@@ -5325,6 +5536,26 @@ var ApprovalRejectedError = class _ApprovalRejectedError extends ZeroTransferErr
|
|
|
5325
5536
|
}
|
|
5326
5537
|
request;
|
|
5327
5538
|
};
|
|
5539
|
+
var ApprovalTimeoutError = class _ApprovalTimeoutError extends ZeroTransferError {
|
|
5540
|
+
/**
|
|
5541
|
+
* Creates an approval timeout error.
|
|
5542
|
+
*
|
|
5543
|
+
* @param request - The approval request that timed out while pending.
|
|
5544
|
+
* @param timeoutMs - Configured timeout window in milliseconds.
|
|
5545
|
+
*/
|
|
5546
|
+
constructor(request, timeoutMs) {
|
|
5547
|
+
super({
|
|
5548
|
+
code: "approval_timeout",
|
|
5549
|
+
details: { approvalId: request.id, routeId: request.routeId, timeoutMs },
|
|
5550
|
+
message: `Approval "${request.id}" for route "${request.routeId}" timed out after ${String(timeoutMs)}ms`,
|
|
5551
|
+
retryable: false
|
|
5552
|
+
});
|
|
5553
|
+
this.request = request;
|
|
5554
|
+
Object.setPrototypeOf(this, _ApprovalTimeoutError.prototype);
|
|
5555
|
+
this.name = "ApprovalTimeoutError";
|
|
5556
|
+
}
|
|
5557
|
+
request;
|
|
5558
|
+
};
|
|
5328
5559
|
var ApprovalRegistry = class {
|
|
5329
5560
|
requests = /* @__PURE__ */ new Map();
|
|
5330
5561
|
pending = /* @__PURE__ */ new Map();
|
|
@@ -5450,9 +5681,27 @@ function createApprovalGate(options) {
|
|
|
5450
5681
|
};
|
|
5451
5682
|
if (input.signal.aborted) onAbort();
|
|
5452
5683
|
input.signal.addEventListener("abort", onAbort);
|
|
5684
|
+
let timeoutTimer;
|
|
5685
|
+
const timeoutMs = options.timeoutMs;
|
|
5686
|
+
const pendingPromises = [settled];
|
|
5687
|
+
if (timeoutMs !== void 0) {
|
|
5688
|
+
pendingPromises.push(
|
|
5689
|
+
new Promise((_resolve, reject) => {
|
|
5690
|
+
timeoutTimer = setTimeout(() => {
|
|
5691
|
+
const current = options.registry.get(approvalId) ?? request;
|
|
5692
|
+
reject(new ApprovalTimeoutError(current, timeoutMs));
|
|
5693
|
+
if (current.status === "pending") {
|
|
5694
|
+
settled.catch(() => void 0);
|
|
5695
|
+
options.registry.reject(approvalId, { reason: "timeout" }, now());
|
|
5696
|
+
}
|
|
5697
|
+
}, timeoutMs);
|
|
5698
|
+
})
|
|
5699
|
+
);
|
|
5700
|
+
}
|
|
5453
5701
|
try {
|
|
5454
|
-
await
|
|
5702
|
+
await Promise.race(pendingPromises);
|
|
5455
5703
|
} finally {
|
|
5704
|
+
if (timeoutTimer !== void 0) clearTimeout(timeoutTimer);
|
|
5456
5705
|
input.signal.removeEventListener("abort", onAbort);
|
|
5457
5706
|
}
|
|
5458
5707
|
return options.runner(input);
|
|
@@ -5812,6 +6061,7 @@ var defaultRunner = ({ client, route, signal }) => {
|
|
|
5812
6061
|
AbortError,
|
|
5813
6062
|
ApprovalRegistry,
|
|
5814
6063
|
ApprovalRejectedError,
|
|
6064
|
+
ApprovalTimeoutError,
|
|
5815
6065
|
AuthenticationError,
|
|
5816
6066
|
AuthorizationError,
|
|
5817
6067
|
CLASSIC_PROVIDER_IDS,
|
|
@@ -5849,6 +6099,7 @@ var defaultRunner = ({ client, route, signal }) => {
|
|
|
5849
6099
|
createApprovalGate,
|
|
5850
6100
|
createAtomicDeployPlan,
|
|
5851
6101
|
createBandwidthThrottle,
|
|
6102
|
+
createDefaultRetryPolicy,
|
|
5852
6103
|
createInboxRoute,
|
|
5853
6104
|
createJsonlAuditLog,
|
|
5854
6105
|
createLocalProviderFactory,
|
|
@@ -5896,8 +6147,10 @@ var defaultRunner = ({ client, route, signal }) => {
|
|
|
5896
6147
|
parseRemoteManifest,
|
|
5897
6148
|
redactCommand,
|
|
5898
6149
|
redactConnectionProfile,
|
|
6150
|
+
redactErrorForLogging,
|
|
5899
6151
|
redactObject,
|
|
5900
6152
|
redactSecretSource,
|
|
6153
|
+
redactUrlForLogging,
|
|
5901
6154
|
redactValue,
|
|
5902
6155
|
resolveConnectionProfileSecrets,
|
|
5903
6156
|
resolveOpenSshHost,
|