@zero-transfer/sftp 0.3.1 → 0.4.2
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 +315 -5
- package/dist/index.d.ts +315 -5
- package/dist/index.mjs +232 -33
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1767,6 +1767,186 @@ function summarizeDiagnosticError(error) {
|
|
|
1767
1767
|
return { message: String(error) };
|
|
1768
1768
|
}
|
|
1769
1769
|
|
|
1770
|
+
// src/core/ConnectionPool.ts
|
|
1771
|
+
var DEFAULT_MAX_IDLE_PER_KEY = 4;
|
|
1772
|
+
var DEFAULT_IDLE_TIMEOUT_MS = 6e4;
|
|
1773
|
+
function createPooledTransferClient(inner, options = {}) {
|
|
1774
|
+
const maxIdlePerKey = Math.max(1, options.maxIdlePerKey ?? DEFAULT_MAX_IDLE_PER_KEY);
|
|
1775
|
+
const idleTimeoutMs = Math.max(0, options.idleTimeoutMs ?? DEFAULT_IDLE_TIMEOUT_MS);
|
|
1776
|
+
const keyOf = options.keyOf ?? defaultKeyOf;
|
|
1777
|
+
const state = {
|
|
1778
|
+
drained: false,
|
|
1779
|
+
idle: /* @__PURE__ */ new Map()
|
|
1780
|
+
};
|
|
1781
|
+
const release = (key, session, tainted) => {
|
|
1782
|
+
if (tainted || state.drained) {
|
|
1783
|
+
return safelyDisconnect(session);
|
|
1784
|
+
}
|
|
1785
|
+
let bucket = state.idle.get(key);
|
|
1786
|
+
if (bucket === void 0) {
|
|
1787
|
+
bucket = [];
|
|
1788
|
+
state.idle.set(key, bucket);
|
|
1789
|
+
}
|
|
1790
|
+
const entry = { session };
|
|
1791
|
+
if (idleTimeoutMs > 0) {
|
|
1792
|
+
entry.idleTimer = setTimeout(() => {
|
|
1793
|
+
evictEntry(state, key, entry);
|
|
1794
|
+
}, idleTimeoutMs);
|
|
1795
|
+
const timer = entry.idleTimer;
|
|
1796
|
+
if (timer !== void 0 && typeof timer.unref === "function") {
|
|
1797
|
+
timer.unref();
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1800
|
+
bucket.push(entry);
|
|
1801
|
+
while (bucket.length > maxIdlePerKey) {
|
|
1802
|
+
const dropped = bucket.shift();
|
|
1803
|
+
if (dropped !== void 0) {
|
|
1804
|
+
clearEntryTimer(dropped);
|
|
1805
|
+
void safelyDisconnect(dropped.session);
|
|
1806
|
+
}
|
|
1807
|
+
}
|
|
1808
|
+
return Promise.resolve();
|
|
1809
|
+
};
|
|
1810
|
+
const acquire = async (profile) => {
|
|
1811
|
+
const key = keyOf(profile);
|
|
1812
|
+
const bucket = state.idle.get(key);
|
|
1813
|
+
if (bucket !== void 0 && bucket.length > 0) {
|
|
1814
|
+
const entry = bucket.pop();
|
|
1815
|
+
if (entry !== void 0) {
|
|
1816
|
+
clearEntryTimer(entry);
|
|
1817
|
+
if (bucket.length === 0) state.idle.delete(key);
|
|
1818
|
+
return { key, session: entry.session };
|
|
1819
|
+
}
|
|
1820
|
+
}
|
|
1821
|
+
const session = await inner.connect(profile);
|
|
1822
|
+
return { key, session };
|
|
1823
|
+
};
|
|
1824
|
+
return {
|
|
1825
|
+
connect: async (profile) => {
|
|
1826
|
+
const { key, session } = await acquire(profile);
|
|
1827
|
+
return wrapPooledSession(session, key, release);
|
|
1828
|
+
},
|
|
1829
|
+
drainPool: async () => {
|
|
1830
|
+
state.drained = true;
|
|
1831
|
+
const entries = [];
|
|
1832
|
+
for (const bucket of state.idle.values()) {
|
|
1833
|
+
for (const entry of bucket) {
|
|
1834
|
+
clearEntryTimer(entry);
|
|
1835
|
+
entries.push(entry);
|
|
1836
|
+
}
|
|
1837
|
+
}
|
|
1838
|
+
state.idle.clear();
|
|
1839
|
+
await Promise.all(entries.map((entry) => safelyDisconnect(entry.session)));
|
|
1840
|
+
},
|
|
1841
|
+
getCapabilities: ((providerId) => {
|
|
1842
|
+
if (providerId === void 0) return inner.getCapabilities();
|
|
1843
|
+
return inner.getCapabilities(providerId);
|
|
1844
|
+
}),
|
|
1845
|
+
hasProvider: (providerId) => inner.hasProvider(providerId),
|
|
1846
|
+
poolSize: () => {
|
|
1847
|
+
let total = 0;
|
|
1848
|
+
for (const bucket of state.idle.values()) total += bucket.length;
|
|
1849
|
+
return total;
|
|
1850
|
+
}
|
|
1851
|
+
};
|
|
1852
|
+
}
|
|
1853
|
+
function defaultKeyOf(profile) {
|
|
1854
|
+
const provider = profile.provider ?? profile.protocol ?? "unknown";
|
|
1855
|
+
const host = profile.host ?? "";
|
|
1856
|
+
const port = profile.port ?? "";
|
|
1857
|
+
const username = typeof profile.username === "string" ? profile.username : "";
|
|
1858
|
+
return `${provider}|${host}|${String(port)}|${username}`;
|
|
1859
|
+
}
|
|
1860
|
+
function evictEntry(state, key, entry) {
|
|
1861
|
+
const bucket = state.idle.get(key);
|
|
1862
|
+
if (bucket === void 0) return;
|
|
1863
|
+
const index = bucket.indexOf(entry);
|
|
1864
|
+
if (index < 0) return;
|
|
1865
|
+
bucket.splice(index, 1);
|
|
1866
|
+
if (bucket.length === 0) state.idle.delete(key);
|
|
1867
|
+
clearEntryTimer(entry);
|
|
1868
|
+
void safelyDisconnect(entry.session);
|
|
1869
|
+
}
|
|
1870
|
+
function clearEntryTimer(entry) {
|
|
1871
|
+
if (entry.idleTimer !== void 0) {
|
|
1872
|
+
clearTimeout(entry.idleTimer);
|
|
1873
|
+
delete entry.idleTimer;
|
|
1874
|
+
}
|
|
1875
|
+
}
|
|
1876
|
+
async function safelyDisconnect(session) {
|
|
1877
|
+
try {
|
|
1878
|
+
await session.disconnect();
|
|
1879
|
+
} catch {
|
|
1880
|
+
}
|
|
1881
|
+
}
|
|
1882
|
+
function isTaintingError(error) {
|
|
1883
|
+
return error instanceof ConnectionError || error instanceof TimeoutError || error instanceof ProtocolError;
|
|
1884
|
+
}
|
|
1885
|
+
function wrapPooledSession(session, key, release) {
|
|
1886
|
+
let tainted = false;
|
|
1887
|
+
let released = false;
|
|
1888
|
+
const guard = (fn) => {
|
|
1889
|
+
let promise;
|
|
1890
|
+
try {
|
|
1891
|
+
promise = fn();
|
|
1892
|
+
} catch (error) {
|
|
1893
|
+
if (isTaintingError(error)) tainted = true;
|
|
1894
|
+
return Promise.reject(error instanceof Error ? error : new Error(String(error)));
|
|
1895
|
+
}
|
|
1896
|
+
return promise.catch((error) => {
|
|
1897
|
+
if (isTaintingError(error)) tainted = true;
|
|
1898
|
+
throw error;
|
|
1899
|
+
});
|
|
1900
|
+
};
|
|
1901
|
+
const fs = wrapFs(session.fs, guard);
|
|
1902
|
+
const transfers = session.transfers === void 0 ? void 0 : wrapTransfers(session.transfers, guard);
|
|
1903
|
+
const wrapped = {
|
|
1904
|
+
capabilities: session.capabilities,
|
|
1905
|
+
disconnect: async () => {
|
|
1906
|
+
if (released) return;
|
|
1907
|
+
released = true;
|
|
1908
|
+
await release(key, session, tainted);
|
|
1909
|
+
},
|
|
1910
|
+
fs,
|
|
1911
|
+
provider: session.provider,
|
|
1912
|
+
...transfers !== void 0 ? { transfers } : {}
|
|
1913
|
+
};
|
|
1914
|
+
if (typeof session.raw === "function") {
|
|
1915
|
+
const rawFn = session.raw.bind(session);
|
|
1916
|
+
wrapped.raw = () => rawFn();
|
|
1917
|
+
}
|
|
1918
|
+
return wrapped;
|
|
1919
|
+
}
|
|
1920
|
+
function wrapFs(fs, guard) {
|
|
1921
|
+
const wrapped = {
|
|
1922
|
+
list: (path2, options) => guard(() => options !== void 0 ? fs.list(path2, options) : fs.list(path2)),
|
|
1923
|
+
stat: (path2, options) => guard(() => options !== void 0 ? fs.stat(path2, options) : fs.stat(path2))
|
|
1924
|
+
};
|
|
1925
|
+
if (typeof fs.remove === "function") {
|
|
1926
|
+
const remove = fs.remove.bind(fs);
|
|
1927
|
+
wrapped.remove = (path2, options) => guard(() => options !== void 0 ? remove(path2, options) : remove(path2));
|
|
1928
|
+
}
|
|
1929
|
+
if (typeof fs.rename === "function") {
|
|
1930
|
+
const rename2 = fs.rename.bind(fs);
|
|
1931
|
+
wrapped.rename = (from, to, options) => guard(() => options !== void 0 ? rename2(from, to, options) : rename2(from, to));
|
|
1932
|
+
}
|
|
1933
|
+
if (typeof fs.mkdir === "function") {
|
|
1934
|
+
const mkdir2 = fs.mkdir.bind(fs);
|
|
1935
|
+
wrapped.mkdir = (path2, options) => guard(() => options !== void 0 ? mkdir2(path2, options) : mkdir2(path2));
|
|
1936
|
+
}
|
|
1937
|
+
if (typeof fs.rmdir === "function") {
|
|
1938
|
+
const rmdir = fs.rmdir.bind(fs);
|
|
1939
|
+
wrapped.rmdir = (path2, options) => guard(() => options !== void 0 ? rmdir(path2, options) : rmdir(path2));
|
|
1940
|
+
}
|
|
1941
|
+
return wrapped;
|
|
1942
|
+
}
|
|
1943
|
+
function wrapTransfers(transfers, guard) {
|
|
1944
|
+
return {
|
|
1945
|
+
read: (request) => guard(() => Promise.resolve(transfers.read(request))),
|
|
1946
|
+
write: (request) => guard(() => Promise.resolve(transfers.write(request)))
|
|
1947
|
+
};
|
|
1948
|
+
}
|
|
1949
|
+
|
|
1770
1950
|
// src/providers/local/LocalProvider.ts
|
|
1771
1951
|
import { createReadStream } from "fs";
|
|
1772
1952
|
import {
|
|
@@ -4619,7 +4799,7 @@ var SshDataWriter = class {
|
|
|
4619
4799
|
length = 0;
|
|
4620
4800
|
writeByte(value) {
|
|
4621
4801
|
this.assertByte(value, "byte");
|
|
4622
|
-
const chunk = Buffer7.
|
|
4802
|
+
const chunk = Buffer7.alloc(1);
|
|
4623
4803
|
chunk.writeUInt8(value, 0);
|
|
4624
4804
|
return this.push(chunk);
|
|
4625
4805
|
}
|
|
@@ -4637,7 +4817,7 @@ var SshDataWriter = class {
|
|
|
4637
4817
|
retryable: false
|
|
4638
4818
|
});
|
|
4639
4819
|
}
|
|
4640
|
-
const chunk = Buffer7.
|
|
4820
|
+
const chunk = Buffer7.alloc(4);
|
|
4641
4821
|
chunk.writeUInt32BE(value, 0);
|
|
4642
4822
|
return this.push(chunk);
|
|
4643
4823
|
}
|
|
@@ -4649,7 +4829,7 @@ var SshDataWriter = class {
|
|
|
4649
4829
|
retryable: false
|
|
4650
4830
|
});
|
|
4651
4831
|
}
|
|
4652
|
-
const chunk = Buffer7.
|
|
4832
|
+
const chunk = Buffer7.alloc(8);
|
|
4653
4833
|
chunk.writeBigUInt64BE(value, 0);
|
|
4654
4834
|
return this.push(chunk);
|
|
4655
4835
|
}
|
|
@@ -6205,7 +6385,7 @@ function encodeSshTransportPacket(payload, options = {}) {
|
|
|
6205
6385
|
}
|
|
6206
6386
|
const padding = options.randomPadding === false ? Buffer14.alloc(paddingLength) : randomBytes2(paddingLength);
|
|
6207
6387
|
const packetLength = 1 + body.length + paddingLength;
|
|
6208
|
-
const frame = Buffer14.
|
|
6388
|
+
const frame = Buffer14.alloc(4 + packetLength);
|
|
6209
6389
|
frame.writeUInt32BE(packetLength, 0);
|
|
6210
6390
|
frame.writeUInt8(paddingLength, 4);
|
|
6211
6391
|
body.copy(frame, 5);
|
|
@@ -7086,7 +7266,7 @@ function computeMac(macAlgorithm, macKey, sequence, packet, macLength) {
|
|
|
7086
7266
|
return Buffer17.alloc(0);
|
|
7087
7267
|
}
|
|
7088
7268
|
const hashName = macAlgorithm === "hmac-sha2-512" ? "sha512" : "sha256";
|
|
7089
|
-
const sequenceBuffer = Buffer17.
|
|
7269
|
+
const sequenceBuffer = Buffer17.alloc(4);
|
|
7090
7270
|
sequenceBuffer.writeUInt32BE(sequence >>> 0, 0);
|
|
7091
7271
|
return createHmac2(hashName, macKey).update(sequenceBuffer).update(packet).digest().subarray(0, macLength);
|
|
7092
7272
|
}
|
|
@@ -8038,7 +8218,7 @@ var SftpSession = class {
|
|
|
8038
8218
|
* serializes concurrent calls so byte ordering is preserved.
|
|
8039
8219
|
*/
|
|
8040
8220
|
sendRaw(encodedMessage, requestId) {
|
|
8041
|
-
const frame = Buffer20.
|
|
8221
|
+
const frame = Buffer20.alloc(4 + encodedMessage.length);
|
|
8042
8222
|
frame.writeUInt32BE(encodedMessage.length, 0);
|
|
8043
8223
|
encodedMessage.copy(frame, 4);
|
|
8044
8224
|
this.channel.sendData(frame).catch((err) => {
|
|
@@ -8156,44 +8336,54 @@ var NATIVE_SFTP_ALGORITHM_PREFERENCES = {
|
|
|
8156
8336
|
"rsa-sha2-256"
|
|
8157
8337
|
]
|
|
8158
8338
|
};
|
|
8159
|
-
var
|
|
8160
|
-
|
|
8161
|
-
|
|
8162
|
-
|
|
8163
|
-
|
|
8164
|
-
|
|
8165
|
-
|
|
8166
|
-
|
|
8167
|
-
|
|
8168
|
-
|
|
8169
|
-
|
|
8170
|
-
|
|
8171
|
-
|
|
8172
|
-
|
|
8173
|
-
|
|
8174
|
-
|
|
8175
|
-
|
|
8176
|
-
|
|
8177
|
-
|
|
8178
|
-
|
|
8179
|
-
|
|
8180
|
-
|
|
8181
|
-
|
|
8339
|
+
var NATIVE_SFTP_DEFAULT_MAX_CONCURRENCY = 8;
|
|
8340
|
+
function buildNativeSftpCapabilities(maxConcurrency) {
|
|
8341
|
+
return {
|
|
8342
|
+
provider: NATIVE_SFTP_PROVIDER_ID,
|
|
8343
|
+
authentication: ["password", "keyboard-interactive", "publickey"],
|
|
8344
|
+
list: true,
|
|
8345
|
+
stat: true,
|
|
8346
|
+
readStream: true,
|
|
8347
|
+
writeStream: true,
|
|
8348
|
+
serverSideCopy: false,
|
|
8349
|
+
serverSideMove: false,
|
|
8350
|
+
resumeDownload: true,
|
|
8351
|
+
resumeUpload: true,
|
|
8352
|
+
checksum: [],
|
|
8353
|
+
atomicRename: false,
|
|
8354
|
+
chmod: false,
|
|
8355
|
+
chown: false,
|
|
8356
|
+
symlink: true,
|
|
8357
|
+
metadata: ["accessedAt", "group", "modifiedAt", "owner", "permissions"],
|
|
8358
|
+
maxConcurrency,
|
|
8359
|
+
notes: [
|
|
8360
|
+
"Native SSH/SFTP provider using the project's own protocol stack (Waves 1\u20133).",
|
|
8361
|
+
"Supports password, keyboard-interactive, and public-key (Ed25519/RSA) authentication."
|
|
8362
|
+
]
|
|
8363
|
+
};
|
|
8364
|
+
}
|
|
8365
|
+
var NATIVE_SFTP_PROVIDER_CAPABILITIES = buildNativeSftpCapabilities(
|
|
8366
|
+
NATIVE_SFTP_DEFAULT_MAX_CONCURRENCY
|
|
8367
|
+
);
|
|
8182
8368
|
function createNativeSftpProviderFactory(options = {}) {
|
|
8183
8369
|
validateNativeSftpOptions(options);
|
|
8370
|
+
const capabilities = buildNativeSftpCapabilities(
|
|
8371
|
+
options.maxConcurrency ?? NATIVE_SFTP_DEFAULT_MAX_CONCURRENCY
|
|
8372
|
+
);
|
|
8184
8373
|
return {
|
|
8185
|
-
capabilities
|
|
8186
|
-
create: () => new NativeSftpProvider(options),
|
|
8374
|
+
capabilities,
|
|
8375
|
+
create: () => new NativeSftpProvider(options, capabilities),
|
|
8187
8376
|
id: NATIVE_SFTP_PROVIDER_ID
|
|
8188
8377
|
};
|
|
8189
8378
|
}
|
|
8190
8379
|
var NativeSftpProvider = class {
|
|
8191
|
-
constructor(options) {
|
|
8380
|
+
constructor(options, capabilities = NATIVE_SFTP_PROVIDER_CAPABILITIES) {
|
|
8192
8381
|
this.options = options;
|
|
8382
|
+
this.capabilities = capabilities;
|
|
8193
8383
|
}
|
|
8194
8384
|
options;
|
|
8195
8385
|
id = NATIVE_SFTP_PROVIDER_ID;
|
|
8196
|
-
capabilities
|
|
8386
|
+
capabilities;
|
|
8197
8387
|
async connect(profile) {
|
|
8198
8388
|
const resolved = await resolveConnectionProfileSecrets(profile);
|
|
8199
8389
|
const username = requireNativeSftpUsername(resolved);
|
|
@@ -8822,6 +9012,14 @@ function validateNativeSftpOptions(options) {
|
|
|
8822
9012
|
retryable: false
|
|
8823
9013
|
});
|
|
8824
9014
|
}
|
|
9015
|
+
if (options.maxConcurrency !== void 0 && (!Number.isInteger(options.maxConcurrency) || options.maxConcurrency <= 0)) {
|
|
9016
|
+
throw new ConfigurationError({
|
|
9017
|
+
details: { maxConcurrency: options.maxConcurrency },
|
|
9018
|
+
message: "Native SFTP provider maxConcurrency must be a positive integer",
|
|
9019
|
+
protocol: "sftp",
|
|
9020
|
+
retryable: false
|
|
9021
|
+
});
|
|
9022
|
+
}
|
|
8825
9023
|
}
|
|
8826
9024
|
export {
|
|
8827
9025
|
AbortError,
|
|
@@ -8858,6 +9056,7 @@ export {
|
|
|
8858
9056
|
createMemoryProviderFactory,
|
|
8859
9057
|
createNativeSftpProviderFactory,
|
|
8860
9058
|
createOAuthTokenSecretSource,
|
|
9059
|
+
createPooledTransferClient,
|
|
8861
9060
|
createProgressEvent,
|
|
8862
9061
|
createProviderTransferExecutor,
|
|
8863
9062
|
createRemoteBrowser,
|