Merge pull request '2.0.0-r1' (#2) from 2.0.0-r1 into master

Reviewed-on: #2
This commit was merged in pull request #2.
This commit is contained in:
2025-02-14 14:33:14 -06:00
18 changed files with 149 additions and 356 deletions

7
.cspell/words.txt Normal file
View File

@@ -0,0 +1,7 @@
alloc
blockstorage
fuse_fgetattr
fuse_getattr
stablelib
uuidv4
xchacha20poly1305

1
.gitattributes vendored Normal file
View File

@@ -0,0 +1 @@
fixup text eol=lf

View File

@@ -1,3 +1,2 @@
set path+=.,src/** set path+=.,src/**
let &makeprg="npm run build" let &makeprg="npm run build"

View File

@@ -1,14 +1,22 @@
# Changelog # Changelog
## 2.0.0-r1
- Integrated `repertory` v2.0 changes
- Removed connection pool
## 1.4.0-r1 ## 1.4.0-r1
- Switched packet encryption to XChaCha20-Poly1305
- Allow external XChaCha20-Poly1305 encryption/decryption - Switched packet encryption to `XChaCha20-Poly1305`
- Allow external `XChaCha20-Poly1305` encryption/decryption
- Support writing base64 string data - Support writing base64 string data
- Renamed 'delete/delete_file' to 'remove/remove_file' - Renamed 'delete/delete_file' to 'remove/remove_file'
## 1.3.1-r3 ## 1.3.1-r3
- Added directory/file exists - Added directory/file exists
- Fix unit tests - Fix unit tests
## 1.3.1-r2 ## 1.3.1-r2
- Initial release - Initial release

View File

@@ -1,6 +1,6 @@
# `repertory-js` MIT License # `repertory-js` MIT License
### Copyright <2021> <scott.e.graves@protonmail.com> ### Copyright <2021-2025> <scott.e.graves@protonmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
associated documentation files (the "Software"), to deal in the Software without restriction, associated documentation files (the "Software"), to deal in the Software without restriction,

View File

@@ -1,6 +1,7 @@
# About # About
`repertory-js` is a Node.js module for interfacing with `repertory's` remote mount API. `repertory-js` is a Node.js module for interfacing with `repertory's` remote
mount API.
## Installing ## Installing
@@ -10,43 +11,44 @@ npm i @blockstorage/repertory-js
## Repertory Configuration ## Repertory Configuration
A Repertory mount must be active with the `EnableRemoteMount` setting enabled. `RemoteToken` should A Repertory mount must be active with the `RemoteMount.Enable` setting enabled.
also be set to a strong, random password. `RemoteMount.EncryptionToken` should also be set to a strong, random password.
### Enabling Sia Remote Mount API on Windows Systems ### Enabling Sia Remote Mount API on Windows Systems
```shell ```shell
repertory.exe -unmount repertory.exe -unmount
repertory.exe -set RemoteMount.EnableRemoteMount true repertory.exe -set RemoteMount.Enable true
repertory.exe -set RemoteMount.RemoteToken "my password" repertory.exe -set RemoteMount.EncryptionToken 'my password'
[Optional - change listening port] [Optional - change listening port]
repertory.exe -set RemoteMount.RemotePort 20202 repertory.exe -set RemoteMount.ApiPort 20202
``` ```
### Enabling Sia Remote Mount API on *NIX Systems ### Enabling Sia Remote Mount API on *NIX Systems
```shell ```shell
./repertory -unmount ./repertory -unmount
./repertory -set RemoteMount.EnableRemoteMount true ./repertory -set RemoteMount.Enable true
./repertory -set RemoteMount.RemoteToken "my password" ./repertory -set RemoteMount.EncryptionToken 'my password'
[Optional - change listening port] [Optional - change listening port]
./repertory -set RemoteMount.RemotePort 20202 ./repertory -set RemoteMount.ApiPort 20202
``` ```
### Skynet and ScPrime Mounts ### S3 Mounts
* For Skynet mounts, add `-sk` argument to all commands listed above. * For S3 mounts, add `-s3` and `--name '<my config name>'` argument to all
* For ScPrime mounts, add `-sp` argument to all commands listed above. commands listed above.
## Module Environment Variables ## Module Environment Variables
* To successfully complete unit tests, a `repertory` mount supporting remote mount needs to be * To successfully complete unit tests, a `repertory` mount supporting remote
active. Set the following environment variables prior to running tests: mount needs to be active. Set the following environment variables prior to
* `TEST_HOST` running tests:
* `TEST_PASSWORD` * `TEST_HOST`
* `TEST_PORT` * `TEST_PASSWORD`
* `TEST_PORT`
## Example API Usage ## Example API Usage
@@ -58,7 +60,7 @@ import * as rep from "@blockstorage/repertory-js";
// Repertory host settings // Repertory host settings
const MY_HOST_OR_IP = 'localhost'; const MY_HOST_OR_IP = 'localhost';
const MY_PORT = 20000; const MY_PORT = 20000;
const MY_TOKEN = 'password'; const MY_PASSWORD = 'password';
// Progress callback for uploads / downloads // Progress callback for uploads / downloads
const progress_cb = (local_path, remote_path, progress, completed) => { const progress_cb = (local_path, remote_path, progress, completed) => {
@@ -66,33 +68,29 @@ const progress_cb = (local_path, remote_path, progress, completed) => {
}; };
//************************************************************************************************// //****************************************************************************//
// Step 1. Create a connection pool (recommended) // // Step 1. Create a connection //
//************************************************************************************************// //****************************************************************************//
const conn = await rep.connect(MY_HOST_OR_IP, MY_PORT, MY_PASSWORD);
const conn = await rep.create_pool(8, MY_HOST_OR_IP, MY_PORT, MY_TOKEN);
/* Or create a single connection for light operations
const conn = await rep.connect(MY_HOST_OR_IP, MY_PORT, MY_TOKEN);
*/
/* Disconnect when complete /* Disconnect when complete
await conn.disconnect(); await conn.disconnect();
*/ */
//************************************************************************************************// //****************************************************************************//
// Step 2. Create an 'api' instance using the connection pool / connection // // Step 2. Create an 'api' instance using the connection //
//************************************************************************************************// //****************************************************************************//
const api = rep.create_api(conn); const api = rep.create_api(conn);
//************************************************************************************************// //****************************************************************************//
// Step 3. Use 'api' // // Step 3. Use 'api' //
//************************************************************************************************// //****************************************************************************//
//------------------------------------------------------------------------------------------------// //----------------------------------------------------------------------------//
// *********** Directory Operations *********** // // *********** Directory Operations *********** //
//------------------------------------------------------------------------------------------------// //----------------------------------------------------------------------------//
// Check if directory exists // Check if directory exists
const exists = await api.directory.exists('/my_directory'); const exists = await api.directory.exists('/my_directory');
@@ -125,9 +123,9 @@ await api.directory.create('/test');
await api.directory.remove('/test') await api.directory.remove('/test')
//------------------------------------------------------------------------------------------------// //----------------------------------------------------------------------------//
// *********** File Operations *********** // // *********** File Operations *********** //
//------------------------------------------------------------------------------------------------// //----------------------------------------------------------------------------//
// Check if file exists // Check if file exists
const exists = await api.file.exists('/my_file.txt') const exists = await api.file.exists('/my_file.txt')
@@ -154,9 +152,9 @@ await api.file.upload('C:\\my_file.txt', '/my_file.txt', progress_cb, true);
await api.file.upload('C:\\my_file.txt', '/my_file.txt', progress_cb, false, true); await api.file.upload('C:\\my_file.txt', '/my_file.txt', progress_cb, false, true);
//------------------------------------------------------------------------------------------------// //----------------------------------------------------------------------------//
// *********** Low-Level File Operations *********** // // *********** Low-Level File Operations *********** //
//------------------------------------------------------------------------------------------------// //----------------------------------------------------------------------------//
// Create or open a remote file // Create or open a remote file
{ {

16
cspell.json Normal file
View File

@@ -0,0 +1,16 @@
{
"$schema": "https://raw.githubusercontent.com/streetsidesoftware/cspell/main/cspell.schema.json",
"version": "0.2",
"dictionaries": ["workspace-words", "user-words"],
"dictionaryDefinitions": [{
"name": "workspace-words",
"path": "./.cspell/words.txt",
"addWords": true
},
{
"name": "user-words",
"path": "C:\\.desktop\\.cspell\\user_words.txt",
"addWords": true
}
]
}

View File

@@ -37,15 +37,16 @@
], ],
"scripts": { "scripts": {
"build": "rollup -c && ./fixup", "build": "rollup -c && ./fixup",
"build_win": "rollup -c && mingw64 ./fixup",
"test": "jest", "test": "jest",
"prepublish": "rollup -c --silent && ./fixup" "prepublish": "rollup -c --silent && ./fixup"
}, },
"dependencies": { "dependencies": {
"@stablelib/xchacha20poly1305": "^1.0.1", "@stablelib/xchacha20poly1305": "^1.0.1",
"int64-buffer": "^1.0.0", "blake2b": "^2.1.4",
"socket-pool": "^1.2.3", "int64-buffer": "^1.1.0",
"text-encoding": "^0.7.0", "text-encoding": "^0.7.0",
"uuid": "^8.3.2" "uuid": "^11.0.5"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.14.3", "@babel/core": "^7.14.3",

View File

@@ -1,59 +0,0 @@
import connection_pool from '../networking/connection_pool';
import packet from '../networking/packet';
import connection from '../networking/connection';
jest.mock('../networking/connection');
test(`construction fails if pool size is <= 1`, () => {
expect(() => new connection_pool(1)).toThrow(Error);
expect(() => new connection_pool(0)).toThrow(Error);
expect(() => new connection_pool(-1)).toThrow(Error);
});
test(`error on socket release is ignored`, async () => {
const conn = new connection_pool(2, '', 20000);
let invoked = false;
jest.spyOn(conn.pool, 'acquire').mockImplementation(() => {
return {
release: () => {
invoked = true;
throw new Error('mock release error');
},
};
});
const mock_send = jest.fn();
connection.prototype.send = async () => {
return mock_send();
};
mock_send.mockResolvedValue(0);
expect(await conn.send('', new packet())).toEqual(0);
expect(invoked).toBeTruthy();
});
test(`connection pool send fails if socket acquire fails`, async () => {
const conn = new connection_pool(2, '', 20000);
jest.spyOn(conn.pool, 'acquire').mockImplementation(() => {
throw new Error('mock acquire exception');
});
await expect(conn.send('', new packet())).rejects.toThrow(Error);
});
test(`connection pool send fails when connection send fails`, async () => {
const conn = new connection_pool(2, '', 20000);
jest.spyOn(conn.pool, 'acquire').mockImplementation(() => {
return {
release: () => {},
};
});
const mock_send = jest.fn();
connection.prototype.send = async () => {
return mock_send();
};
mock_send.mockRejectedValue(new Error('mock send failed'));
await expect(conn.send('', new packet())).rejects.toThrow(Error);
});

View File

@@ -14,5 +14,5 @@ test(`'instance_id' is valid`, () => {
test(`'version' can be read from 'package.json'`, () => { test(`'version' can be read from 'package.json'`, () => {
console.log(get_version()); console.log(get_version());
expect(get_version()).toBe('1.4.0-r1'); expect(get_version()).toBe('2.0.0-r1');
}); });

View File

@@ -1,10 +1,9 @@
import crypto from 'crypto'; import blake2b from 'blake2b';
import fs from 'fs'; import fs from 'fs';
import { Uint64BE } from 'int64-buffer'; import { Uint64BE } from 'int64-buffer';
import * as repertory from '../index.js'; import * as repertory from '../index.js';
import connection from '../networking/connection'; import connection from '../networking/connection';
import connection_pool from '../networking/connection_pool';
const TEST_HOST = process.env.TEST_HOST || 'localhost'; const TEST_HOST = process.env.TEST_HOST || 'localhost';
const TEST_PASSWORD = process.env.TEST_PASSWORD || ''; const TEST_PASSWORD = process.env.TEST_PASSWORD || '';
@@ -12,10 +11,12 @@ const TEST_PORT = process.env.TEST_PORT || 20000;
const calculate_sha256 = (path) => { const calculate_sha256 = (path) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const hash = crypto.createHash('sha256'); const hash = blake2b(32);
fs.createReadStream(path) fs.createReadStream(path)
.on('data', (data) => hash.update(data)) .on('data', (data) => {
return hash.update(new Uint8Array(data));
})
.on('error', (err) => reject(err)) .on('error', (err) => reject(err))
.on('end', () => { .on('end', () => {
const h = hash.digest('hex'); const h = hash.digest('hex');
@@ -35,56 +36,16 @@ const test_connection = (conn, should_be_connected) => {
}; };
test('can create a connection to repertory api', async () => { test('can create a connection to repertory api', async () => {
console.log('TEST_PASSWORD', TEST_PASSWORD);
const conn = await repertory.connect(TEST_HOST, TEST_PORT, TEST_PASSWORD); const conn = await repertory.connect(TEST_HOST, TEST_PORT, TEST_PASSWORD);
test_connection(conn, true); test_connection(conn, true);
await conn.disconnect();
});
test('create_pool returns a connection if pool size is <=1', async () => {
for (let i = 0; i < 2; i++) {
const conn = await repertory.create_pool(
i,
TEST_HOST,
TEST_PORT,
TEST_PASSWORD
);
expect(conn).toBeInstanceOf(connection);
test_connection(conn, true);
await conn.disconnect();
}
});
test('can create a connection pool', async () => {
const conn = await repertory.create_pool(
2,
TEST_HOST,
TEST_PORT,
TEST_PASSWORD
);
console.log(conn);
expect(conn).toBeInstanceOf(connection_pool);
expect(conn.host_or_ip).toEqual(TEST_HOST);
expect(conn.port).toEqual(TEST_PORT);
expect(conn.password).toEqual(TEST_PASSWORD);
expect(conn.shutdown).toEqual(false);
expect(conn.pool._pool.max).toEqual(2);
expect(conn.pool._pool.min).toEqual(2);
await conn.disconnect(); await conn.disconnect();
}); });
test('can get drive information using api', async () => { test('can get drive information using api', async () => {
const conn = await repertory.create_pool( const conn = await repertory.create(TEST_HOST, TEST_PORT, TEST_PASSWORD);
2,
TEST_HOST,
TEST_PORT,
TEST_PASSWORD
);
const api = repertory.create_api(conn); const api = repertory.create_api(conn);
const di = await api.get_drive_information(); const di = await api.get_drive_information();
console.log(di);
expect(di.free).toBeDefined(); expect(di.free).toBeDefined();
expect(di.total).toBeDefined(); expect(di.total).toBeDefined();
@@ -94,12 +55,7 @@ test('can get drive information using api', async () => {
}); });
test('can create and remove a directory using api', async () => { test('can create and remove a directory using api', async () => {
const conn = await repertory.create_pool( const conn = await repertory.create(TEST_HOST, TEST_PORT, TEST_PASSWORD);
2,
TEST_HOST,
TEST_PORT,
TEST_PASSWORD
);
const api = repertory.create_api(conn); const api = repertory.create_api(conn);
expect(await api.directory.create('/repertory_js')).toEqual(0); expect(await api.directory.create('/repertory_js')).toEqual(0);
expect(await api.directory.exists('/repertory_js')).toEqual(true); expect(await api.directory.exists('/repertory_js')).toEqual(true);
@@ -110,12 +66,7 @@ test('can create and remove a directory using api', async () => {
}); });
test('can get directory list and snapshot using api', async () => { test('can get directory list and snapshot using api', async () => {
const conn = await repertory.create_pool( const conn = await repertory.create(TEST_HOST, TEST_PORT, TEST_PASSWORD);
2,
TEST_HOST,
TEST_PORT,
TEST_PASSWORD
);
const api = repertory.create_api(conn); const api = repertory.create_api(conn);
const test_results = async (remote_path, page_count, get_page) => { const test_results = async (remote_path, page_count, get_page) => {
@@ -127,10 +78,11 @@ test('can get directory list and snapshot using api', async () => {
console.log(items); console.log(items);
expect(items.length).toBeGreaterThanOrEqual(2); expect(items.length).toBeGreaterThanOrEqual(2);
expect(items[0].directory).toBeTruthy(); expect(items[0].Directory).toBeTruthy();
expect(items[0].path).toEqual('.'); expect(items[0].ApiPath).toEqual('.');
expect(items[1].directory).toBeTruthy();
expect(items[1].path).toEqual('..'); expect(items[1].Directory).toBeTruthy();
expect(items[1].ApiPath).toEqual('..');
} }
}; };
@@ -153,12 +105,7 @@ test('can get directory list and snapshot using api', async () => {
}); });
test('can create, close and remove a file using api', async () => { test('can create, close and remove a file using api', async () => {
const conn = await repertory.create_pool( const conn = await repertory.create(TEST_HOST, TEST_PORT, TEST_PASSWORD);
2,
TEST_HOST,
TEST_PORT,
TEST_PASSWORD
);
const api = repertory.create_api(conn); const api = repertory.create_api(conn);
const f = await api.file.create_or_open('/repertory_file.dat'); const f = await api.file.create_or_open('/repertory_file.dat');
console.log(f); console.log(f);
@@ -175,12 +122,7 @@ test('can create, close and remove a file using api', async () => {
}); });
test('can open, close and remove a file using api', async () => { test('can open, close and remove a file using api', async () => {
const conn = await repertory.create_pool( const conn = await repertory.create(TEST_HOST, TEST_PORT, TEST_PASSWORD);
2,
TEST_HOST,
TEST_PORT,
TEST_PASSWORD
);
const api = repertory.create_api(conn); const api = repertory.create_api(conn);
let f = await api.file.create_or_open('/repertory_file.dat'); let f = await api.file.create_or_open('/repertory_file.dat');
expect(await f.close()).toEqual(0); expect(await f.close()).toEqual(0);
@@ -200,12 +142,7 @@ test('can open, close and remove a file using api', async () => {
}); });
test('can write to and read from a file using api', async () => { test('can write to and read from a file using api', async () => {
const conn = await repertory.create_pool( const conn = await repertory.create(TEST_HOST, TEST_PORT, TEST_PASSWORD);
2,
TEST_HOST,
TEST_PORT,
TEST_PASSWORD
);
const api = repertory.create_api(conn); const api = repertory.create_api(conn);
const f = await api.file.create_or_open('/repertory_file.dat'); const f = await api.file.create_or_open('/repertory_file.dat');
@@ -227,12 +164,7 @@ test('can write to and read from a file using api', async () => {
}); });
test('can truncate a file using api', async () => { test('can truncate a file using api', async () => {
const conn = await repertory.create_pool( const conn = await repertory.create(TEST_HOST, TEST_PORT, TEST_PASSWORD);
2,
TEST_HOST,
TEST_PORT,
TEST_PASSWORD
);
const api = repertory.create_api(conn); const api = repertory.create_api(conn);
const f = await api.file.create_or_open('/repertory_file.dat'); const f = await api.file.create_or_open('/repertory_file.dat');
@@ -253,12 +185,7 @@ test('can upload and download a file using api', async () => {
fs.unlinkSync('repertory_test.dat'); fs.unlinkSync('repertory_test.dat');
} catch {} } catch {}
const conn = await repertory.create_pool( const conn = await repertory.create(TEST_HOST, TEST_PORT, TEST_PASSWORD);
2,
TEST_HOST,
TEST_PORT,
TEST_PASSWORD
);
const api = repertory.create_api(conn); const api = repertory.create_api(conn);
expect( expect(
await api.file.upload('test.dat', '/repertory_test.dat', (l, r, p, c) => { await api.file.upload('test.dat', '/repertory_test.dat', (l, r, p, c) => {
@@ -289,12 +216,7 @@ test('can upload and download a file using api', async () => {
}, 60000); }, 60000);
test('can download and overwrite a file using api', async () => { test('can download and overwrite a file using api', async () => {
const conn = await repertory.create_pool( const conn = await repertory.create(TEST_HOST, TEST_PORT, TEST_PASSWORD);
2,
TEST_HOST,
TEST_PORT,
TEST_PASSWORD
);
const api = repertory.create_api(conn); const api = repertory.create_api(conn);
expect( expect(
await api.file.upload('test.dat', '/repertory_test.dat', (l, r, p, c) => { await api.file.upload('test.dat', '/repertory_test.dat', (l, r, p, c) => {
@@ -330,12 +252,7 @@ test('can download and overwrite a file using api', async () => {
}, 60000); }, 60000);
test('download fails if overwrite is false using api', async () => { test('download fails if overwrite is false using api', async () => {
const conn = await repertory.create_pool( const conn = await repertory.create(TEST_HOST, TEST_PORT, TEST_PASSWORD);
2,
TEST_HOST,
TEST_PORT,
TEST_PASSWORD
);
const api = repertory.create_api(conn); const api = repertory.create_api(conn);
expect( expect(
await api.file.upload('test.dat', '/repertory_test.dat', (l, r, p, c) => { await api.file.upload('test.dat', '/repertory_test.dat', (l, r, p, c) => {
@@ -371,12 +288,7 @@ test('download fails if overwrite is false using api', async () => {
}, 60000); }, 60000);
test('can upload and overwrite a file using api', async () => { test('can upload and overwrite a file using api', async () => {
const conn = await repertory.create_pool( const conn = await repertory.create(TEST_HOST, TEST_PORT, TEST_PASSWORD);
2,
TEST_HOST,
TEST_PORT,
TEST_PASSWORD
);
const api = repertory.create_api(conn); const api = repertory.create_api(conn);
expect( expect(
await api.file.upload('test.dat', '/repertory_test.dat', (l, r, p, c) => { await api.file.upload('test.dat', '/repertory_test.dat', (l, r, p, c) => {
@@ -401,12 +313,7 @@ test('can upload and overwrite a file using api', async () => {
}, 60000); }, 60000);
test('upload fails if overwrite is false using api', async () => { test('upload fails if overwrite is false using api', async () => {
const conn = await repertory.create_pool( const conn = await repertory.create(TEST_HOST, TEST_PORT, TEST_PASSWORD);
2,
TEST_HOST,
TEST_PORT,
TEST_PASSWORD
);
const api = repertory.create_api(conn); const api = repertory.create_api(conn);
expect( expect(
await api.file.upload('test.dat', '/repertory_test.dat', (l, r, p, c) => { await api.file.upload('test.dat', '/repertory_test.dat', (l, r, p, c) => {
@@ -431,12 +338,7 @@ test('upload fails if overwrite is false using api', async () => {
}, 60000); }, 60000);
test('can resume download using api', async () => { test('can resume download using api', async () => {
const conn = await repertory.create_pool( const conn = await repertory.create(TEST_HOST, TEST_PORT, TEST_PASSWORD);
2,
TEST_HOST,
TEST_PORT,
TEST_PASSWORD
);
const api = repertory.create_api(conn); const api = repertory.create_api(conn);
expect( expect(
await api.file.upload('test.dat', '/repertory_test.dat', (l, r, p, c) => { await api.file.upload('test.dat', '/repertory_test.dat', (l, r, p, c) => {
@@ -474,12 +376,7 @@ test('can resume download using api', async () => {
}, 60000); }, 60000);
test('can resume upload using api', async () => { test('can resume upload using api', async () => {
const conn = await repertory.create_pool( const conn = await repertory.create(TEST_HOST, TEST_PORT, TEST_PASSWORD);
2,
TEST_HOST,
TEST_PORT,
TEST_PASSWORD
);
const api = repertory.create_api(conn); const api = repertory.create_api(conn);
const fd = fs.openSync('test.dat', 'r'); const fd = fs.openSync('test.dat', 'r');
@@ -524,24 +421,14 @@ test('can resume upload using api', async () => {
}, 60000); }, 60000);
test('exists returns false if directory is not found', async () => { test('exists returns false if directory is not found', async () => {
const conn = await repertory.create_pool( const conn = await repertory.create(TEST_HOST, TEST_PORT, TEST_PASSWORD);
2,
TEST_HOST,
TEST_PORT,
TEST_PASSWORD
);
const api = repertory.create_api(conn); const api = repertory.create_api(conn);
expect(await api.directory.exists('/cow')).toEqual(false); expect(await api.directory.exists('/cow')).toEqual(false);
await conn.disconnect(); await conn.disconnect();
}); });
test('exists returns false if file is not found', async () => { test('exists returns false if file is not found', async () => {
const conn = await repertory.create_pool( const conn = await repertory.create(TEST_HOST, TEST_PORT, TEST_PASSWORD);
2,
TEST_HOST,
TEST_PORT,
TEST_PASSWORD
);
const api = repertory.create_api(conn); const api = repertory.create_api(conn);
expect(await api.file.exists('/cow')).toEqual(false); expect(await api.file.exists('/cow')).toEqual(false);
await conn.disconnect(); await conn.disconnect();

View File

@@ -1,6 +1,5 @@
import file from './io/file'; import file from './io/file';
import connection from './networking/connection'; import connection from './networking/connection';
import connection_pool from './networking/connection_pool';
import * as ops from './ops'; import * as ops from './ops';
export * as byte_order from './utils/byte_order'; export * as byte_order from './utils/byte_order';
@@ -9,7 +8,13 @@ export { default as packet } from './networking/packet';
export const connect = async (host_or_ip, port, password) => { export const connect = async (host_or_ip, port, password) => {
const conn = new connection(host_or_ip, port, password); const conn = new connection(host_or_ip, port, password);
await conn.connect(); try {
await conn.connect();
} catch (e) {
await conn.disconnect();
throw e;
}
return conn; return conn;
}; };
@@ -85,10 +90,6 @@ export const create_api = (conn) => {
}; };
}; };
export const create_pool = async (pool_size, host_or_ip, port, password) => { export const create = async (host_or_ip, port, password) => {
if (pool_size <= 1) { return connect(host_or_ip, port, password);
return connect(host_or_ip, port, password);
}
return new connection_pool(pool_size, host_or_ip, port, password);
}; };

View File

@@ -39,21 +39,21 @@ export default class connection {
this.host_or_ip, this.host_or_ip,
(err) => { (err) => {
if (err) { if (err) {
console.log(err); console.error(err);
return reject(err); return reject(err);
} }
this.reject = reject; this.reject = reject;
this.resolve = resolve; this.resolve = resolve;
this.connected = true;
this.setup_socket();
} }
); );
}); });
} catch (err) { } catch (err) {
return Promise.reject(new Error(`'connect()' failed: ${err}`)); return Promise.reject(new Error(`'connect()' failed: ${err}`));
} }
this.connected = true;
this.setup_socket();
} }
} }
@@ -88,6 +88,7 @@ export default class connection {
}; };
}; };
this.buffer = null;
const response = new packet(this.password); const response = new packet(this.password);
response.buffer = new Uint8Array(packet_data); response.buffer = new Uint8Array(packet_data);
response response
@@ -100,7 +101,7 @@ export default class connection {
} }
}) })
.catch((e) => { .catch((e) => {
console.log(e); console.error(e);
const { reject } = complete(); const { reject } = complete();
if (reject) { if (reject) {
reject(e); reject(e);
@@ -118,7 +119,7 @@ export default class connection {
cleanup(); cleanup();
this.connected = false; this.connected = false;
console.log(e); console.error(e);
if (reject) { if (reject) {
reject(e); reject(e);
} }
@@ -132,7 +133,7 @@ export default class connection {
cleanup(); cleanup();
this.connected = false; this.connected = false;
console.log('socket closed'); console.warn('socket closed');
if (reject) { if (reject) {
reject(new Error('socket closed')); reject(new Error('socket closed'));
} }
@@ -149,7 +150,7 @@ export default class connection {
this.connected = false; this.connected = false;
} }
} catch (e) { } catch (e) {
console.log(e); console.error(e);
} }
} }
@@ -160,7 +161,7 @@ export default class connection {
packet.encode_top_utf8(constants.instance_id); packet.encode_top_utf8(constants.instance_id);
packet.encode_top_ui32(0); // Service flags packet.encode_top_ui32(0); // Service flags
packet.encode_top_utf8(constants.get_version()); packet.encode_top_utf8(constants.get_version());
packet.encode_top_utf8(nonce); packet.encode_top_utf8(this.nonce);
await packet.encrypt(); await packet.encrypt();
packet.encode_top_ui32(packet.buffer.length); packet.encode_top_ui32(packet.buffer.length);
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {

View File

@@ -1,69 +0,0 @@
import Pool from 'socket-pool';
import connection from './connection';
export default class connection_pool {
constructor(pool_size, host_or_ip, port, password) {
this.host_or_ip = host_or_ip;
this.port = port;
this.password = password;
if (pool_size > 1) {
this.pool = new Pool({
connect: { host: host_or_ip, port: port },
connectTimeout: 5000,
pool: { max: pool_size, min: 2 },
});
} else {
throw new Error("'pool_size' must be > 1");
}
}
host_or_ip = '';
next_thread_id = 1;
password = '';
port = 20000;
pool;
shutdown = false;
async disconnect() {
await this.pool._pool.drain();
await this.pool._pool.clear();
this.pool = null;
this.shutdown = true;
}
async send(method_name, packet, optional_thread_id) {
try {
const socket = await this.pool.acquire();
if (!socket.thread_id) {
socket.thread_id = this.next_thread_id++;
}
const cleanup = () => {
try {
socket.release();
} catch (err) {
console.log(`'release()' failed: ${err}`);
}
};
try {
const result = await new connection(
this.host_or_ip,
this.port,
this.password,
socket
).send(method_name, packet, optional_thread_id || socket.thread_id);
cleanup();
return result;
} catch (err) {
cleanup();
return Promise.reject(
new Error(`'send(${method_name})' failed: ${err}`)
);
}
} catch (err) {
return Promise.reject(new Error(`'acquire()' socket failed: ${err}`));
}
}
}

View File

@@ -1,6 +1,6 @@
import { randomBytes } from 'crypto'; import { randomBytes } from 'crypto';
import { Int64BE, Uint64BE } from 'int64-buffer'; import { Int64BE, Uint64BE } from 'int64-buffer';
import crypto from 'crypto'; import blake2b from 'blake2b';
import { TextEncoder } from 'text-encoding'; import { TextEncoder } from 'text-encoding';
import { getCustomEncryption } from '../utils/constants'; import { getCustomEncryption } from '../utils/constants';
import { import {
@@ -156,7 +156,7 @@ export default class packet {
decrypt = async () => { decrypt = async () => {
try { try {
let hash = crypto.createHash('sha256'); let hash = blake2b(32);
hash = hash.update(new TextEncoder().encode(this.token)); hash = hash.update(new TextEncoder().encode(this.token));
const key = Uint8Array.from(hash.digest()); const key = Uint8Array.from(hash.digest());
@@ -282,7 +282,7 @@ export default class packet {
encrypt = async (nonce) => { encrypt = async (nonce) => {
try { try {
let hash = crypto.createHash('sha256'); let hash = blake2b(32);
hash = hash.update(new TextEncoder().encode(this.token)); hash = hash.update(new TextEncoder().encode(this.token));
const key = Uint8Array.from(hash.digest()); const key = Uint8Array.from(hash.digest());

View File

@@ -3,6 +3,7 @@ import { Uint64BE } from 'int64-buffer';
import file from '../io/file'; import file from '../io/file';
import packet from '../networking/packet'; import packet from '../networking/packet';
import { RW_BUFFER_SIZE } from '../utils/constants';
const _snapshot_directory = async (conn, remote_path) => { const _snapshot_directory = async (conn, remote_path) => {
try { try {
@@ -167,14 +168,14 @@ export const download_file = async (
try { try {
await src.close(); await src.close();
} catch (err) { } catch (err) {
console.log(err); console.error(err);
} }
try { try {
if (fd !== undefined) { if (fd !== undefined) {
fs.closeSync(fd); fs.closeSync(fd);
} }
} catch (err) { } catch (err) {
console.log(err); console.error(err);
} }
}; };
@@ -218,7 +219,7 @@ export const download_file = async (
let remain = src_size - offset; let remain = src_size - offset;
while (remain > 0) { while (remain > 0) {
const to_write = remain >= 65536 ? 65536 : remain; const to_write = remain >= RW_BUFFER_SIZE ? RW_BUFFER_SIZE : remain;
const buffer = await src.read(offset, to_write); const buffer = await src.read(offset, to_write);
const written = fs.writeSync(dst_fd, buffer, 0, to_write, offset); const written = fs.writeSync(dst_fd, buffer, 0, to_write, offset);
if (written > 0) { if (written > 0) {
@@ -466,14 +467,14 @@ export const upload_file = async (
try { try {
fs.closeSync(src_fd); fs.closeSync(src_fd);
} catch (err) { } catch (err) {
console.log(err); console.error(err);
} }
try { try {
if (f) { if (f) {
await f.close(); await f.close();
} }
} catch (err) { } catch (err) {
console.log(err); console.error(err);
} }
}; };
try { try {
@@ -531,7 +532,7 @@ export const upload_file = async (
} }
let remain = src_st.size - offset; let remain = src_st.size - offset;
const default_buffer = Buffer.alloc(65536 * 2); const default_buffer = Buffer.alloc(RW_BUFFER_SIZE);
while (remain > 0) { while (remain > 0) {
const to_write = const to_write =
remain >= default_buffer.length ? default_buffer.length : remain; remain >= default_buffer.length ? default_buffer.length : remain;

View File

@@ -14,3 +14,4 @@ export const setCustomEncryption = (ce) => {
export const instance_id = uuidv4(); export const instance_id = uuidv4();
export const package_json = _package_json; export const package_json = _package_json;
export const get_version = () => _package_json.version; export const get_version = () => _package_json.version;
export const RW_BUFFER_SIZE = 1024 * 1024;