@zero-transfer/sftp 0.3.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +233 -33
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +69 -1
- package/dist/index.d.ts +69 -1
- package/dist/index.mjs +232 -33
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -64,6 +64,7 @@ __export(sftp_exports, {
|
|
|
64
64
|
createMemoryProviderFactory: () => createMemoryProviderFactory,
|
|
65
65
|
createNativeSftpProviderFactory: () => createNativeSftpProviderFactory,
|
|
66
66
|
createOAuthTokenSecretSource: () => createOAuthTokenSecretSource,
|
|
67
|
+
createPooledTransferClient: () => createPooledTransferClient,
|
|
67
68
|
createProgressEvent: () => createProgressEvent,
|
|
68
69
|
createProviderTransferExecutor: () => createProviderTransferExecutor,
|
|
69
70
|
createRemoteBrowser: () => createRemoteBrowser,
|
|
@@ -1883,6 +1884,186 @@ function summarizeDiagnosticError(error) {
|
|
|
1883
1884
|
return { message: String(error) };
|
|
1884
1885
|
}
|
|
1885
1886
|
|
|
1887
|
+
// src/core/ConnectionPool.ts
|
|
1888
|
+
var DEFAULT_MAX_IDLE_PER_KEY = 4;
|
|
1889
|
+
var DEFAULT_IDLE_TIMEOUT_MS = 6e4;
|
|
1890
|
+
function createPooledTransferClient(inner, options = {}) {
|
|
1891
|
+
const maxIdlePerKey = Math.max(1, options.maxIdlePerKey ?? DEFAULT_MAX_IDLE_PER_KEY);
|
|
1892
|
+
const idleTimeoutMs = Math.max(0, options.idleTimeoutMs ?? DEFAULT_IDLE_TIMEOUT_MS);
|
|
1893
|
+
const keyOf = options.keyOf ?? defaultKeyOf;
|
|
1894
|
+
const state = {
|
|
1895
|
+
drained: false,
|
|
1896
|
+
idle: /* @__PURE__ */ new Map()
|
|
1897
|
+
};
|
|
1898
|
+
const release = (key, session, tainted) => {
|
|
1899
|
+
if (tainted || state.drained) {
|
|
1900
|
+
return safelyDisconnect(session);
|
|
1901
|
+
}
|
|
1902
|
+
let bucket = state.idle.get(key);
|
|
1903
|
+
if (bucket === void 0) {
|
|
1904
|
+
bucket = [];
|
|
1905
|
+
state.idle.set(key, bucket);
|
|
1906
|
+
}
|
|
1907
|
+
const entry = { session };
|
|
1908
|
+
if (idleTimeoutMs > 0) {
|
|
1909
|
+
entry.idleTimer = setTimeout(() => {
|
|
1910
|
+
evictEntry(state, key, entry);
|
|
1911
|
+
}, idleTimeoutMs);
|
|
1912
|
+
const timer = entry.idleTimer;
|
|
1913
|
+
if (timer !== void 0 && typeof timer.unref === "function") {
|
|
1914
|
+
timer.unref();
|
|
1915
|
+
}
|
|
1916
|
+
}
|
|
1917
|
+
bucket.push(entry);
|
|
1918
|
+
while (bucket.length > maxIdlePerKey) {
|
|
1919
|
+
const dropped = bucket.shift();
|
|
1920
|
+
if (dropped !== void 0) {
|
|
1921
|
+
clearEntryTimer(dropped);
|
|
1922
|
+
void safelyDisconnect(dropped.session);
|
|
1923
|
+
}
|
|
1924
|
+
}
|
|
1925
|
+
return Promise.resolve();
|
|
1926
|
+
};
|
|
1927
|
+
const acquire = async (profile) => {
|
|
1928
|
+
const key = keyOf(profile);
|
|
1929
|
+
const bucket = state.idle.get(key);
|
|
1930
|
+
if (bucket !== void 0 && bucket.length > 0) {
|
|
1931
|
+
const entry = bucket.pop();
|
|
1932
|
+
if (entry !== void 0) {
|
|
1933
|
+
clearEntryTimer(entry);
|
|
1934
|
+
if (bucket.length === 0) state.idle.delete(key);
|
|
1935
|
+
return { key, session: entry.session };
|
|
1936
|
+
}
|
|
1937
|
+
}
|
|
1938
|
+
const session = await inner.connect(profile);
|
|
1939
|
+
return { key, session };
|
|
1940
|
+
};
|
|
1941
|
+
return {
|
|
1942
|
+
connect: async (profile) => {
|
|
1943
|
+
const { key, session } = await acquire(profile);
|
|
1944
|
+
return wrapPooledSession(session, key, release);
|
|
1945
|
+
},
|
|
1946
|
+
drainPool: async () => {
|
|
1947
|
+
state.drained = true;
|
|
1948
|
+
const entries = [];
|
|
1949
|
+
for (const bucket of state.idle.values()) {
|
|
1950
|
+
for (const entry of bucket) {
|
|
1951
|
+
clearEntryTimer(entry);
|
|
1952
|
+
entries.push(entry);
|
|
1953
|
+
}
|
|
1954
|
+
}
|
|
1955
|
+
state.idle.clear();
|
|
1956
|
+
await Promise.all(entries.map((entry) => safelyDisconnect(entry.session)));
|
|
1957
|
+
},
|
|
1958
|
+
getCapabilities: ((providerId) => {
|
|
1959
|
+
if (providerId === void 0) return inner.getCapabilities();
|
|
1960
|
+
return inner.getCapabilities(providerId);
|
|
1961
|
+
}),
|
|
1962
|
+
hasProvider: (providerId) => inner.hasProvider(providerId),
|
|
1963
|
+
poolSize: () => {
|
|
1964
|
+
let total = 0;
|
|
1965
|
+
for (const bucket of state.idle.values()) total += bucket.length;
|
|
1966
|
+
return total;
|
|
1967
|
+
}
|
|
1968
|
+
};
|
|
1969
|
+
}
|
|
1970
|
+
function defaultKeyOf(profile) {
|
|
1971
|
+
const provider = profile.provider ?? profile.protocol ?? "unknown";
|
|
1972
|
+
const host = profile.host ?? "";
|
|
1973
|
+
const port = profile.port ?? "";
|
|
1974
|
+
const username = typeof profile.username === "string" ? profile.username : "";
|
|
1975
|
+
return `${provider}|${host}|${String(port)}|${username}`;
|
|
1976
|
+
}
|
|
1977
|
+
function evictEntry(state, key, entry) {
|
|
1978
|
+
const bucket = state.idle.get(key);
|
|
1979
|
+
if (bucket === void 0) return;
|
|
1980
|
+
const index = bucket.indexOf(entry);
|
|
1981
|
+
if (index < 0) return;
|
|
1982
|
+
bucket.splice(index, 1);
|
|
1983
|
+
if (bucket.length === 0) state.idle.delete(key);
|
|
1984
|
+
clearEntryTimer(entry);
|
|
1985
|
+
void safelyDisconnect(entry.session);
|
|
1986
|
+
}
|
|
1987
|
+
function clearEntryTimer(entry) {
|
|
1988
|
+
if (entry.idleTimer !== void 0) {
|
|
1989
|
+
clearTimeout(entry.idleTimer);
|
|
1990
|
+
delete entry.idleTimer;
|
|
1991
|
+
}
|
|
1992
|
+
}
|
|
1993
|
+
async function safelyDisconnect(session) {
|
|
1994
|
+
try {
|
|
1995
|
+
await session.disconnect();
|
|
1996
|
+
} catch {
|
|
1997
|
+
}
|
|
1998
|
+
}
|
|
1999
|
+
function isTaintingError(error) {
|
|
2000
|
+
return error instanceof ConnectionError || error instanceof TimeoutError || error instanceof ProtocolError;
|
|
2001
|
+
}
|
|
2002
|
+
function wrapPooledSession(session, key, release) {
|
|
2003
|
+
let tainted = false;
|
|
2004
|
+
let released = false;
|
|
2005
|
+
const guard = (fn) => {
|
|
2006
|
+
let promise;
|
|
2007
|
+
try {
|
|
2008
|
+
promise = fn();
|
|
2009
|
+
} catch (error) {
|
|
2010
|
+
if (isTaintingError(error)) tainted = true;
|
|
2011
|
+
return Promise.reject(error instanceof Error ? error : new Error(String(error)));
|
|
2012
|
+
}
|
|
2013
|
+
return promise.catch((error) => {
|
|
2014
|
+
if (isTaintingError(error)) tainted = true;
|
|
2015
|
+
throw error;
|
|
2016
|
+
});
|
|
2017
|
+
};
|
|
2018
|
+
const fs = wrapFs(session.fs, guard);
|
|
2019
|
+
const transfers = session.transfers === void 0 ? void 0 : wrapTransfers(session.transfers, guard);
|
|
2020
|
+
const wrapped = {
|
|
2021
|
+
capabilities: session.capabilities,
|
|
2022
|
+
disconnect: async () => {
|
|
2023
|
+
if (released) return;
|
|
2024
|
+
released = true;
|
|
2025
|
+
await release(key, session, tainted);
|
|
2026
|
+
},
|
|
2027
|
+
fs,
|
|
2028
|
+
provider: session.provider,
|
|
2029
|
+
...transfers !== void 0 ? { transfers } : {}
|
|
2030
|
+
};
|
|
2031
|
+
if (typeof session.raw === "function") {
|
|
2032
|
+
const rawFn = session.raw.bind(session);
|
|
2033
|
+
wrapped.raw = () => rawFn();
|
|
2034
|
+
}
|
|
2035
|
+
return wrapped;
|
|
2036
|
+
}
|
|
2037
|
+
function wrapFs(fs, guard) {
|
|
2038
|
+
const wrapped = {
|
|
2039
|
+
list: (path2, options) => guard(() => options !== void 0 ? fs.list(path2, options) : fs.list(path2)),
|
|
2040
|
+
stat: (path2, options) => guard(() => options !== void 0 ? fs.stat(path2, options) : fs.stat(path2))
|
|
2041
|
+
};
|
|
2042
|
+
if (typeof fs.remove === "function") {
|
|
2043
|
+
const remove = fs.remove.bind(fs);
|
|
2044
|
+
wrapped.remove = (path2, options) => guard(() => options !== void 0 ? remove(path2, options) : remove(path2));
|
|
2045
|
+
}
|
|
2046
|
+
if (typeof fs.rename === "function") {
|
|
2047
|
+
const rename2 = fs.rename.bind(fs);
|
|
2048
|
+
wrapped.rename = (from, to, options) => guard(() => options !== void 0 ? rename2(from, to, options) : rename2(from, to));
|
|
2049
|
+
}
|
|
2050
|
+
if (typeof fs.mkdir === "function") {
|
|
2051
|
+
const mkdir2 = fs.mkdir.bind(fs);
|
|
2052
|
+
wrapped.mkdir = (path2, options) => guard(() => options !== void 0 ? mkdir2(path2, options) : mkdir2(path2));
|
|
2053
|
+
}
|
|
2054
|
+
if (typeof fs.rmdir === "function") {
|
|
2055
|
+
const rmdir = fs.rmdir.bind(fs);
|
|
2056
|
+
wrapped.rmdir = (path2, options) => guard(() => options !== void 0 ? rmdir(path2, options) : rmdir(path2));
|
|
2057
|
+
}
|
|
2058
|
+
return wrapped;
|
|
2059
|
+
}
|
|
2060
|
+
function wrapTransfers(transfers, guard) {
|
|
2061
|
+
return {
|
|
2062
|
+
read: (request) => guard(() => Promise.resolve(transfers.read(request))),
|
|
2063
|
+
write: (request) => guard(() => Promise.resolve(transfers.write(request)))
|
|
2064
|
+
};
|
|
2065
|
+
}
|
|
2066
|
+
|
|
1886
2067
|
// src/providers/local/LocalProvider.ts
|
|
1887
2068
|
var import_node_fs = require("fs");
|
|
1888
2069
|
var import_promises2 = require("fs/promises");
|
|
@@ -4725,7 +4906,7 @@ var SshDataWriter = class {
|
|
|
4725
4906
|
length = 0;
|
|
4726
4907
|
writeByte(value) {
|
|
4727
4908
|
this.assertByte(value, "byte");
|
|
4728
|
-
const chunk = import_node_buffer6.Buffer.
|
|
4909
|
+
const chunk = import_node_buffer6.Buffer.alloc(1);
|
|
4729
4910
|
chunk.writeUInt8(value, 0);
|
|
4730
4911
|
return this.push(chunk);
|
|
4731
4912
|
}
|
|
@@ -4743,7 +4924,7 @@ var SshDataWriter = class {
|
|
|
4743
4924
|
retryable: false
|
|
4744
4925
|
});
|
|
4745
4926
|
}
|
|
4746
|
-
const chunk = import_node_buffer6.Buffer.
|
|
4927
|
+
const chunk = import_node_buffer6.Buffer.alloc(4);
|
|
4747
4928
|
chunk.writeUInt32BE(value, 0);
|
|
4748
4929
|
return this.push(chunk);
|
|
4749
4930
|
}
|
|
@@ -4755,7 +4936,7 @@ var SshDataWriter = class {
|
|
|
4755
4936
|
retryable: false
|
|
4756
4937
|
});
|
|
4757
4938
|
}
|
|
4758
|
-
const chunk = import_node_buffer6.Buffer.
|
|
4939
|
+
const chunk = import_node_buffer6.Buffer.alloc(8);
|
|
4759
4940
|
chunk.writeBigUInt64BE(value, 0);
|
|
4760
4941
|
return this.push(chunk);
|
|
4761
4942
|
}
|
|
@@ -6311,7 +6492,7 @@ function encodeSshTransportPacket(payload, options = {}) {
|
|
|
6311
6492
|
}
|
|
6312
6493
|
const padding = options.randomPadding === false ? import_node_buffer13.Buffer.alloc(paddingLength) : (0, import_node_crypto6.randomBytes)(paddingLength);
|
|
6313
6494
|
const packetLength = 1 + body.length + paddingLength;
|
|
6314
|
-
const frame = import_node_buffer13.Buffer.
|
|
6495
|
+
const frame = import_node_buffer13.Buffer.alloc(4 + packetLength);
|
|
6315
6496
|
frame.writeUInt32BE(packetLength, 0);
|
|
6316
6497
|
frame.writeUInt8(paddingLength, 4);
|
|
6317
6498
|
body.copy(frame, 5);
|
|
@@ -7187,7 +7368,7 @@ function computeMac(macAlgorithm, macKey, sequence, packet, macLength) {
|
|
|
7187
7368
|
return import_node_buffer16.Buffer.alloc(0);
|
|
7188
7369
|
}
|
|
7189
7370
|
const hashName = macAlgorithm === "hmac-sha2-512" ? "sha512" : "sha256";
|
|
7190
|
-
const sequenceBuffer = import_node_buffer16.Buffer.
|
|
7371
|
+
const sequenceBuffer = import_node_buffer16.Buffer.alloc(4);
|
|
7191
7372
|
sequenceBuffer.writeUInt32BE(sequence >>> 0, 0);
|
|
7192
7373
|
return (0, import_node_crypto8.createHmac)(hashName, macKey).update(sequenceBuffer).update(packet).digest().subarray(0, macLength);
|
|
7193
7374
|
}
|
|
@@ -8139,7 +8320,7 @@ var SftpSession = class {
|
|
|
8139
8320
|
* serializes concurrent calls so byte ordering is preserved.
|
|
8140
8321
|
*/
|
|
8141
8322
|
sendRaw(encodedMessage, requestId) {
|
|
8142
|
-
const frame = import_node_buffer19.Buffer.
|
|
8323
|
+
const frame = import_node_buffer19.Buffer.alloc(4 + encodedMessage.length);
|
|
8143
8324
|
frame.writeUInt32BE(encodedMessage.length, 0);
|
|
8144
8325
|
encodedMessage.copy(frame, 4);
|
|
8145
8326
|
this.channel.sendData(frame).catch((err) => {
|
|
@@ -8257,44 +8438,54 @@ var NATIVE_SFTP_ALGORITHM_PREFERENCES = {
|
|
|
8257
8438
|
"rsa-sha2-256"
|
|
8258
8439
|
]
|
|
8259
8440
|
};
|
|
8260
|
-
var
|
|
8261
|
-
|
|
8262
|
-
|
|
8263
|
-
|
|
8264
|
-
|
|
8265
|
-
|
|
8266
|
-
|
|
8267
|
-
|
|
8268
|
-
|
|
8269
|
-
|
|
8270
|
-
|
|
8271
|
-
|
|
8272
|
-
|
|
8273
|
-
|
|
8274
|
-
|
|
8275
|
-
|
|
8276
|
-
|
|
8277
|
-
|
|
8278
|
-
|
|
8279
|
-
|
|
8280
|
-
|
|
8281
|
-
|
|
8282
|
-
|
|
8441
|
+
var NATIVE_SFTP_DEFAULT_MAX_CONCURRENCY = 8;
|
|
8442
|
+
function buildNativeSftpCapabilities(maxConcurrency) {
|
|
8443
|
+
return {
|
|
8444
|
+
provider: NATIVE_SFTP_PROVIDER_ID,
|
|
8445
|
+
authentication: ["password", "keyboard-interactive", "publickey"],
|
|
8446
|
+
list: true,
|
|
8447
|
+
stat: true,
|
|
8448
|
+
readStream: true,
|
|
8449
|
+
writeStream: true,
|
|
8450
|
+
serverSideCopy: false,
|
|
8451
|
+
serverSideMove: false,
|
|
8452
|
+
resumeDownload: true,
|
|
8453
|
+
resumeUpload: true,
|
|
8454
|
+
checksum: [],
|
|
8455
|
+
atomicRename: false,
|
|
8456
|
+
chmod: false,
|
|
8457
|
+
chown: false,
|
|
8458
|
+
symlink: true,
|
|
8459
|
+
metadata: ["accessedAt", "group", "modifiedAt", "owner", "permissions"],
|
|
8460
|
+
maxConcurrency,
|
|
8461
|
+
notes: [
|
|
8462
|
+
"Native SSH/SFTP provider using the project's own protocol stack (Waves 1\u20133).",
|
|
8463
|
+
"Supports password, keyboard-interactive, and public-key (Ed25519/RSA) authentication."
|
|
8464
|
+
]
|
|
8465
|
+
};
|
|
8466
|
+
}
|
|
8467
|
+
var NATIVE_SFTP_PROVIDER_CAPABILITIES = buildNativeSftpCapabilities(
|
|
8468
|
+
NATIVE_SFTP_DEFAULT_MAX_CONCURRENCY
|
|
8469
|
+
);
|
|
8283
8470
|
function createNativeSftpProviderFactory(options = {}) {
|
|
8284
8471
|
validateNativeSftpOptions(options);
|
|
8472
|
+
const capabilities = buildNativeSftpCapabilities(
|
|
8473
|
+
options.maxConcurrency ?? NATIVE_SFTP_DEFAULT_MAX_CONCURRENCY
|
|
8474
|
+
);
|
|
8285
8475
|
return {
|
|
8286
|
-
capabilities
|
|
8287
|
-
create: () => new NativeSftpProvider(options),
|
|
8476
|
+
capabilities,
|
|
8477
|
+
create: () => new NativeSftpProvider(options, capabilities),
|
|
8288
8478
|
id: NATIVE_SFTP_PROVIDER_ID
|
|
8289
8479
|
};
|
|
8290
8480
|
}
|
|
8291
8481
|
var NativeSftpProvider = class {
|
|
8292
|
-
constructor(options) {
|
|
8482
|
+
constructor(options, capabilities = NATIVE_SFTP_PROVIDER_CAPABILITIES) {
|
|
8293
8483
|
this.options = options;
|
|
8484
|
+
this.capabilities = capabilities;
|
|
8294
8485
|
}
|
|
8295
8486
|
options;
|
|
8296
8487
|
id = NATIVE_SFTP_PROVIDER_ID;
|
|
8297
|
-
capabilities
|
|
8488
|
+
capabilities;
|
|
8298
8489
|
async connect(profile) {
|
|
8299
8490
|
const resolved = await resolveConnectionProfileSecrets(profile);
|
|
8300
8491
|
const username = requireNativeSftpUsername(resolved);
|
|
@@ -8923,6 +9114,14 @@ function validateNativeSftpOptions(options) {
|
|
|
8923
9114
|
retryable: false
|
|
8924
9115
|
});
|
|
8925
9116
|
}
|
|
9117
|
+
if (options.maxConcurrency !== void 0 && (!Number.isInteger(options.maxConcurrency) || options.maxConcurrency <= 0)) {
|
|
9118
|
+
throw new ConfigurationError({
|
|
9119
|
+
details: { maxConcurrency: options.maxConcurrency },
|
|
9120
|
+
message: "Native SFTP provider maxConcurrency must be a positive integer",
|
|
9121
|
+
protocol: "sftp",
|
|
9122
|
+
retryable: false
|
|
9123
|
+
});
|
|
9124
|
+
}
|
|
8926
9125
|
}
|
|
8927
9126
|
// Annotate the CommonJS export names for ESM import in node:
|
|
8928
9127
|
0 && (module.exports = {
|
|
@@ -8960,6 +9159,7 @@ function validateNativeSftpOptions(options) {
|
|
|
8960
9159
|
createMemoryProviderFactory,
|
|
8961
9160
|
createNativeSftpProviderFactory,
|
|
8962
9161
|
createOAuthTokenSecretSource,
|
|
9162
|
+
createPooledTransferClient,
|
|
8963
9163
|
createProgressEvent,
|
|
8964
9164
|
createProviderTransferExecutor,
|
|
8965
9165
|
createRemoteBrowser,
|