Qusly-core is an API wrapper around ssh2 and basic-ftp for building FTP/FTPS/SFTP clients. It's used in Qusly.
- Supports FTP, FTPS, SFTP
- Promises
- Lots of utilities like
createBlank
- Splited transfer
- Automatically calculates ETA and transfer speed
- MS-dos support
Checkout roadmap to see what's coming.
$ npm install qusly-core
An example of listing files:
import { Client } from 'qusly-core';
async function init() {
const client = new Client();
await client.connect({
host: 'www.example.com',
user: 'root',
password: 'password'
protocol: 'sftp',
port: 22,
});
const files = await client.readDir('/documents');
console.log(files);
await client.disconnect();
}
init();
Example output:
[
{
name: 'projects',
type: 'directory',
size: 4096,
ext: ''
user: 'root',
group: 'root',
date: '2019-05-10T18:52:00.000Z', // obj Date
permissions: {
user: 6,
group: 6
},
},
{
name: 'logs.txt',
type: 'file',
ext: 'txt'
size: 43,
user: 'root',
group: 'root',
date: '2019-05-29T22:00:00.000Z', // obj Date
permissions: {
user: 6,
group: 6
},
},
]
Class Client
:
Client.abort
Client.connect
Client.createBlank
Client.delete
Client.disconnect
Client.download
Client.exists
Client.mkdir
Client.move
Client.pwd
Client.readDir
Client.rimraf
Client.send
Client.size
Client.stat
Client.touch
Client.unlink
Client.upload
Class TransferClient
:
TransferClient
TransferClient.connect
TransferClient.getSplits
TransferClient.setSplits
TransferClient.transfer
Interfaces:
IConfig
IDownloadOptions
IFile
IProgress
IStats
ITransferClientNew
ITransferClientProgress
ITransferOptions
Types:
Events:
Client.on('abort')
Client.on('connect')
Client.on('disconnect')
Client.on('progress')
TransferClient.on('new')
TransferClient.on('progress')
-
Client.abort(): Promise<void>
Aborts current data transfer. It closes all used file streams.const bytes = await client.abort(); console.log(`Aborted at ${bytes} bytes`);
-
Client.connect(config: IConfig): Promise<void>
Connects to server. You can use it to reload session.try { await client.connect({ host: 'www.example.com', user: 'root', // default anonymous password: 'password', // default @anonymous protocol: 'ftp', // default ftp port: 21, // default 21 }); console.log('Connected!'); } catch (error) { console.log('Failed to connect!', res.error); }
-
Client.createBlank(type: 'folder' | 'file', path = './', files?: IFile[]): Promise<string>
Creates an empty folder or file with unique name. If you've fetched files already, you can provide last argument to don't refetch files.const res = await client.createBlank('folder'); console.log(`Created new folder - ${res.fileName}`);
-
Client.delete(path: string): Promise<void>
An universal method to remove both files and folders.await client.delete('folder'); console.log('Deleted');
-
Client.disconnect(): Promise<void>
Disconnects from server. Closes all opened sockets and file streams.await client.disconnect(); console.log('Disconnected!');
-
Client.download(path: string, dest: Writable, options?: IDownloadOptions): Promise<void>
Downloads a file. You can start at given offset by setting options to{ startAt: 65.536; }
import { createWriteStream } from 'fs'; import { resolve } from 'path'; const localPath = resolve('downloads', 'downloaded.rar'); client.on('progress', e => { const rate = ((e.buffered / e.size) * 100).toFixed(2); console.log(`${rate}% ETA: ${e.eta}s`); }); await client.download('file.rar', createWriteStream(localPath)); console.log('Downloaded!');
-
Client.exists(path: string): Promise<boolean>
Checks if file exists.const exists = await client.exists('/home/image.png'); if (exists) { console.log('File exists'); } else { console.log("File doesn't exists"); }
-
Client.mkdir(path: string): Promise<void>
Creates a directory.await client.mkdir('/home/documents/new folder'); console.log(`Created a new directory`);
-
Client.move(srcPath: string, destPath: string): Promise<void>
Moves a file fromsrcPath
todestPath
.await client.move('music/film.mp4', 'videos/film.mp4'); console.log('Moved');
-
Client.pwd(): Promise<IPwdRes>
Returns path of current working directory.const path = await client.pwd(); console.log(`You're at ${path}`);
-
Client.readDir(path?: string): Promise<IFile[]>
Reads content of a directory. If you don't provide path, it'll use working directory.const files = await client.readDir('/root/'); console.log(files);
-
Client.rimraf(path: string): Promise<void>
Removes a directory and all of its content, recursively.await client.rimraf('videos'); console.log('Removed all files');
-
Client.send(command: string): Promise<string>
Sends a raw command. Output depends on a protocol and server support!// It'll probably work on SFTP const res = await client.send('whoami'); console.log(res);
-
Client.size(path: string): Promise<number>
Returns size of a file or folder in bytes.const size = await client.size('file.rar'); console.log(`Size: ${size} bytes`);
-
Client.stat(path: string): Promise<IStats>
Returns info about file at given path.const res = await client.stat('/documents/unknown'); console.log(`${res.type} - ${res.size} bytes`);
-
Client.touch(path: string): Promise<IRes>
Creates an empty file.await client.touch('./empty file.txt'); console.log('Created a new file!');
-
Client.unlink(path: string): Promise<IRes>
Removes a file atpath
.await client.unlink('videos/file.mp4'); console.log('Removed a file');
-
Client.upload(path: string, source: Readable, options?: ITransferOptions): Promise<void>
Uploads a file.import { createReadStream, statSync } from 'fs'; import { resolve } from 'path'; const localPath = resolve('uploads', 'file.jpg'); const fileSize = statSync(localPath).size; client.on('progress', e => { const rate = ((e.buffered / e.size) * 100).toFixed(2); console.log(`${rate}% ETA: ${e.eta}s`); }); await client.upload('uploaded file.jpg', createReadStream(path)); console.log('Uploaded');
An utility class to split transfers.
TransferClient(type: ITransferType, splits = 1)
-
TransferClient.connect(config: IConfig): Promise<void>
Connects clients to server.await client.connect({ host: 'www.example.com', }); console.log(`All clients are connected!`);
TransferClient.getSplits(): number
Gets splits length.
-
TransferClient.setSplits(count: number, config?: IConfig): Promise<void>
Sets splits. If you're setting more than you had, you must provide config. If you're setting less than you had, it will automatically close rest of client.const client = new TransferClient('download', 2); console.log(client.getSplits()); // 2 await client.setSplits(6, { host: 'www.example.com', }); console.log(client.getSplits()); // 6 await client.setSplits(4); console.log(client.getSplits()); // 4
-
TransferClient.transfer(localPath: string, remotePath: string, id?: string): Promise<void>
Transfers a file. You can set your ownid
or it'll be unique hash. To track progress, use eventprogress
. With 2 splits, you can transfer files twice as fast.await client.transfer('logs.txt', '/documents/logs.txt'); await client.transfer('video.mp4', '/content/video.mp4');
interface IConfig {
protocol?: IProtocol;
host?: string;
port?: number;
user?: string;
password?: string;
}
interface IDownloadOptions extends ITransferOptions {
startAt?: number;
}
interface IFile {
name?: string;
type?: IFileType;
size?: number;
user?: string;
group?: string;
date?: Date;
ext?: string;
permissions?: {
user?: number;
group?: number;
};
}
interface IProgress {
chunkSize?: number; // single chunk size in bytes
buffered?: number; // already buffered size in bytes
size?: number; // file size in bytes
localPath?: string; // local file path
remotePath?: string; // remote file path
eta?: number; // estimated time arrival in seconds
speed?: number; // transfer speed in bytes/s
startAt?: Date; // transfer start
percent?: number; // percent of buffered size
context?: Client;
}
interface IStats {
size?: number;
type?: IFileType;
}
interface ITransferClientNew {
id?: string;
type?: ITransferType;
localPath?: string;
remotePath?: string;
context?: Client;
}
interface ITransferClientProgress extends IProgress {
id?: string;
type?: ITransferType;
}
interface ITransferOptions {
quiet?: boolean;
}
type IFileType = 'unknown' | 'file' | 'folder' | 'symbolic-link';
type IProtocol = 'sftp' | 'ftp' | 'ftps';
type ITransferType = 'download' | 'upload';
Client
Client.on('abort')
- File transfer has been aborted.
Client.on('connect')
- Client has connected to server.
Client.on('disconnect')
- Client has disconnected from server.
-
Client.on('progress', e: IProgress)
- Triggered while transfering a file.client.on('progress', (e: IProgress) => { const { buffered, size, eta, speed } = data; const percent = (bytes / size * 100).toFixed(2); console.log(`${buffered}/${size}, ETA: ${eta}s, speed: ${speed}KB/s`); }); client.download(...);
TransferClient
TransferClient.on('new', e: ITransferClientNew)
- Invoked on every new file transfer.
TransferClient.on('progress', e: ITransferClientProgress)
- Triggered while transfering a file. This event comes with a lot of information. Some of that are:- Id, which you can use to identify transfer.
- Type of transfer
ITransferType
- Single chunk size in bytes
- Buffered size
- File size
- Estimated time arrival in seconds
- Transfer speed in bytes/s
- Percent of buffered size
- Start time
client.on('progress', (e: ITransferClientProgress) => {
const { id, type, eta } = data;
console.log(`${id}: ${eta}s (${type}`);
});
client.transfer(...);
- Qusly - Elegant, full-featured FTP client.