@dignetwork/dig-sdk 0.0.1-alpha.51 → 0.0.1-alpha.54
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/DataIntegrityTree/DataIntegrityTree.js +1 -1
- package/dist/DigNetwork/DigNetwork.d.ts +2 -4
- package/dist/DigNetwork/DigNetwork.d.ts.map +1 -1
- package/dist/DigNetwork/DigNetwork.js +26 -48
- package/dist/DigNetwork/DigPeer.d.ts +2 -1
- package/dist/DigNetwork/DigPeer.d.ts.map +1 -1
- package/dist/DigNetwork/DigPeer.js +13 -57
- package/dist/DigNetwork/PropagationServer.d.ts +70 -42
- package/dist/DigNetwork/PropagationServer.d.ts.map +1 -1
- package/dist/DigNetwork/PropagationServer.js +308 -409
- package/dist/blockchain/DataStore.d.ts.map +1 -1
- package/dist/blockchain/DataStore.js +15 -5
- package/dist/utils/ContentScanner.d.ts +63 -0
- package/dist/utils/ContentScanner.d.ts.map +1 -0
- package/dist/utils/ContentScanner.js +175 -0
- package/package.json +5 -1
|
@@ -4,444 +4,343 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.PropagationServer = void 0;
|
|
7
|
+
const axios_1 = __importDefault(require("axios"));
|
|
7
8
|
const fs_1 = __importDefault(require("fs"));
|
|
8
|
-
const
|
|
9
|
-
const
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const form_data_1 = __importDefault(require("form-data"));
|
|
11
|
+
const blockchain_1 = require("../blockchain");
|
|
10
12
|
const ssl_1 = require("../utils/ssl");
|
|
11
13
|
const credentialsUtils_1 = require("../utils/credentialsUtils");
|
|
12
|
-
const
|
|
14
|
+
const https_1 = __importDefault(require("https"));
|
|
15
|
+
const cli_progress_1 = __importDefault(require("cli-progress"));
|
|
16
|
+
const chalk_1 = __importDefault(require("chalk")); // For colored output
|
|
17
|
+
const ora_1 = __importDefault(require("ora")); // For spinners
|
|
18
|
+
const config_1 = require("../utils/config");
|
|
19
|
+
const hashUtils_1 = require("../utils/hashUtils");
|
|
13
20
|
class PropagationServer {
|
|
14
|
-
constructor(
|
|
15
|
-
this.ipAddress = ipAddress;
|
|
21
|
+
constructor(storeId, ipAddress) {
|
|
16
22
|
this.storeId = storeId;
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
async pushFile(fileLocation, dataPath) {
|
|
25
|
-
const { nonce, username, password } = await this.getUploadDetails();
|
|
26
|
-
const wallet = await blockchain_1.Wallet.load("default");
|
|
27
|
-
const keyOwnershipSig = await wallet.createKeyOwnershipSignature(nonce);
|
|
28
|
-
const publicKey = await wallet.getPublicSyntheticKey();
|
|
29
|
-
const uploadUrl = `https://${this.ipAddress}:${PropagationServer.port}/${this.storeId}/${dataPath}`;
|
|
30
|
-
await this.retryOperation(() => this.uploadFileDirect(fileLocation, uploadUrl, username, password, keyOwnershipSig, publicKey.toString("hex"), nonce), `Upload failed for ${dataPath}`);
|
|
31
|
-
}
|
|
32
|
-
// Method to subscribe to a store on the propagation server
|
|
33
|
-
async subscribeToStore() {
|
|
34
|
-
const url = `https://${this.ipAddress}:${PropagationServer.port}/subscribe`;
|
|
35
|
-
const data = JSON.stringify({ storeId: this.storeId });
|
|
36
|
-
await this.postRequest(url, data);
|
|
37
|
-
}
|
|
38
|
-
// Method to unsubscribe from a store on the propagation server
|
|
39
|
-
async unsubscribeFromStore() {
|
|
40
|
-
const url = `https://${this.ipAddress}:${PropagationServer.port}/unsubscribe`;
|
|
41
|
-
const data = JSON.stringify({ storeId: this.storeId });
|
|
42
|
-
await this.postRequest(url, data);
|
|
43
|
-
}
|
|
44
|
-
// Method to check the status of a store on the propagation server
|
|
45
|
-
async getStoreStatus() {
|
|
46
|
-
const url = `https://${this.ipAddress}:${PropagationServer.port}/status/${this.storeId}`;
|
|
47
|
-
return this.fetchJson(url);
|
|
48
|
-
}
|
|
49
|
-
async streamStoreData(dataPath) {
|
|
50
|
-
const url = `https://${this.ipAddress}:${PropagationServer.port}/${this.storeId}/${dataPath}`;
|
|
51
|
-
return this.createReadStreamWithRetries(url);
|
|
52
|
-
}
|
|
53
|
-
// Method to retrieve a store's key
|
|
54
|
-
async getStoreData(dataPath) {
|
|
55
|
-
const url = `https://${this.ipAddress}:${PropagationServer.port}/${this.storeId}/${dataPath}`;
|
|
56
|
-
return this.fetch(url);
|
|
57
|
-
}
|
|
58
|
-
async getStatus() {
|
|
59
|
-
const url = `https://${this.ipAddress}:${PropagationServer.port}/status/${this.storeId}`;
|
|
60
|
-
const statusJson = await this.fetch(url);
|
|
61
|
-
return JSON.parse(statusJson);
|
|
62
|
-
}
|
|
63
|
-
// Method to check if a specific store exists (HEAD request)
|
|
64
|
-
async headStore(options) {
|
|
65
|
-
let url = `https://${this.ipAddress}:${PropagationServer.port}/${this.storeId}`;
|
|
66
|
-
if (options?.hasRootHash) {
|
|
67
|
-
url += `?hasRootHash=${options.hasRootHash}`;
|
|
68
|
-
}
|
|
69
|
-
return this.head(url);
|
|
70
|
-
}
|
|
71
|
-
// In PropagationServer.ts:
|
|
72
|
-
async isStoreSynced() {
|
|
73
|
-
const status = await this.getStatus();
|
|
74
|
-
return status.synced === true;
|
|
75
|
-
}
|
|
76
|
-
// Method to handle upload details including nonce
|
|
77
|
-
async getUploadDetails() {
|
|
78
|
-
let username;
|
|
79
|
-
let password;
|
|
80
|
-
if (!username || !password) {
|
|
81
|
-
const credentials = await (0, credentialsUtils_1.promptCredentials)(this.ipAddress);
|
|
82
|
-
username = credentials.username;
|
|
83
|
-
password = credentials.password;
|
|
84
|
-
}
|
|
85
|
-
const uploadDetails = await this.fetchUploadDetails(username, password);
|
|
86
|
-
if (!uploadDetails) {
|
|
87
|
-
throw new Error("Failed to retrieve upload details.");
|
|
88
|
-
}
|
|
89
|
-
return { ...uploadDetails, username, password };
|
|
23
|
+
this.sessionId = ""; // Session ID will be set after starting the upload session
|
|
24
|
+
this.publicKey = ""; // Public key will be set after initializing the wallet
|
|
25
|
+
this.ipAddress = ipAddress;
|
|
26
|
+
// Get or create SSL certificates
|
|
27
|
+
const { certPath, keyPath } = (0, ssl_1.getOrCreateSSLCerts)();
|
|
28
|
+
this.certPath = certPath;
|
|
29
|
+
this.keyPath = keyPath;
|
|
90
30
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
port: urlObj.port || PropagationServer.port,
|
|
98
|
-
path: urlObj.pathname,
|
|
99
|
-
method: "POST",
|
|
100
|
-
headers: {
|
|
101
|
-
"Content-Type": "application/json",
|
|
102
|
-
"Content-Length": Buffer.byteLength(data),
|
|
103
|
-
},
|
|
104
|
-
key: fs_1.default.readFileSync(PropagationServer.keyPath),
|
|
105
|
-
cert: fs_1.default.readFileSync(PropagationServer.certPath),
|
|
106
|
-
rejectUnauthorized: false,
|
|
107
|
-
};
|
|
108
|
-
const req = https_1.default.request(options, (res) => {
|
|
109
|
-
if (res.statusCode === 200) {
|
|
110
|
-
resolve();
|
|
111
|
-
}
|
|
112
|
-
else {
|
|
113
|
-
reject(new Error(`POST request failed with status ${res.statusCode}: ${res.statusMessage}`));
|
|
114
|
-
}
|
|
115
|
-
});
|
|
116
|
-
req.on("error", (err) => {
|
|
117
|
-
reject(err);
|
|
118
|
-
});
|
|
119
|
-
req.write(data);
|
|
120
|
-
req.end();
|
|
121
|
-
});
|
|
31
|
+
/**
|
|
32
|
+
* Initialize the Wallet instance.
|
|
33
|
+
*/
|
|
34
|
+
async initializeWallet() {
|
|
35
|
+
this.wallet = await blockchain_1.Wallet.load("default");
|
|
36
|
+
this.publicKey = await this.wallet.getPrivateSyntheticKey();
|
|
122
37
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
return new
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
port: url.port,
|
|
132
|
-
path: url.pathname,
|
|
133
|
-
method: "HEAD",
|
|
134
|
-
headers: {
|
|
135
|
-
Authorization: `Basic ${Buffer.from(`${username}:${password}`).toString("base64")}`,
|
|
136
|
-
},
|
|
137
|
-
key: fs_1.default.readFileSync(PropagationServer.keyPath),
|
|
138
|
-
cert: fs_1.default.readFileSync(PropagationServer.certPath),
|
|
139
|
-
rejectUnauthorized: false, // Allow self-signed certificates
|
|
140
|
-
};
|
|
141
|
-
const req = https_1.default.request(options, (res) => {
|
|
142
|
-
if (res.statusCode === 200) {
|
|
143
|
-
// Check if the headers are present and valid
|
|
144
|
-
const nonce = res.headers["x-nonce"];
|
|
145
|
-
const lastUploadedHash = res.headers?.["x-last-uploaded-hash"] ||
|
|
146
|
-
"0000000000000000000000000000000000000000000000000000000000000000";
|
|
147
|
-
const generationIndex = res.headers?.["x-generation-index"] || 0;
|
|
148
|
-
console.log({ nonce, lastUploadedHash, generationIndex });
|
|
149
|
-
if (nonce) {
|
|
150
|
-
resolve({
|
|
151
|
-
nonce: nonce,
|
|
152
|
-
lastUploadedHash: lastUploadedHash,
|
|
153
|
-
generationIndex: Number(generationIndex),
|
|
154
|
-
});
|
|
155
|
-
}
|
|
156
|
-
else {
|
|
157
|
-
reject(new Error("Missing required headers in the response."));
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
else {
|
|
161
|
-
reject(new Error(`Failed to perform preflight check: ${res.statusCode} ${res.statusMessage}`));
|
|
162
|
-
}
|
|
163
|
-
});
|
|
164
|
-
req.on("error", (err) => {
|
|
165
|
-
console.error(err.message);
|
|
166
|
-
resolve(false); // Resolve to false if there is an error
|
|
167
|
-
});
|
|
168
|
-
req.end();
|
|
38
|
+
/**
|
|
39
|
+
* Create an Axios HTTPS Agent with self-signed certificate allowance.
|
|
40
|
+
*/
|
|
41
|
+
createHttpsAgent() {
|
|
42
|
+
return new https_1.default.Agent({
|
|
43
|
+
cert: fs_1.default.readFileSync(this.certPath),
|
|
44
|
+
key: fs_1.default.readFileSync(this.keyPath),
|
|
45
|
+
rejectUnauthorized: false, // Allow self-signed certificates
|
|
169
46
|
});
|
|
170
47
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
headers: {
|
|
183
|
-
"Content-Type": "application/octet-stream",
|
|
184
|
-
"Content-Length": fileSize,
|
|
185
|
-
Authorization: `Basic ${Buffer.from(`${username}:${password}`).toString("base64")}`,
|
|
186
|
-
"x-key-ownership-sig": keyOwnershipSig,
|
|
187
|
-
"x-public-key": publicKey,
|
|
188
|
-
"x-nonce": nonce,
|
|
189
|
-
},
|
|
190
|
-
key: fs_1.default.readFileSync(PropagationServer.keyPath),
|
|
191
|
-
cert: fs_1.default.readFileSync(PropagationServer.certPath),
|
|
192
|
-
rejectUnauthorized: false,
|
|
48
|
+
/**
|
|
49
|
+
* Check if the store and optional root hash exist by making a HEAD request.
|
|
50
|
+
*
|
|
51
|
+
* @param {string} [rootHash] - Optional root hash to check for existence.
|
|
52
|
+
* @returns {Promise<{ storeExists: boolean, rootHashExists: boolean }>} - An object indicating if the store and root hash exist.
|
|
53
|
+
*/
|
|
54
|
+
async checkStoreExists(rootHash) {
|
|
55
|
+
const spinner = (0, ora_1.default)(`Checking if store ${this.storeId} exists...`).start();
|
|
56
|
+
try {
|
|
57
|
+
const config = {
|
|
58
|
+
httpsAgent: this.createHttpsAgent(),
|
|
193
59
|
};
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
});
|
|
205
|
-
fileStream.pipe(req);
|
|
206
|
-
fileStream.on("error", (err) => {
|
|
207
|
-
reject(err);
|
|
208
|
-
});
|
|
209
|
-
req.on("end", () => {
|
|
210
|
-
resolve();
|
|
211
|
-
});
|
|
212
|
-
});
|
|
213
|
-
}
|
|
214
|
-
// Generic retry operation handler to reduce redundancy
|
|
215
|
-
async retryOperation(operation, errorMessage) {
|
|
216
|
-
let attempt = 0;
|
|
217
|
-
let delay = PropagationServer.initialDelay;
|
|
218
|
-
while (attempt < PropagationServer.maxRetries) {
|
|
219
|
-
try {
|
|
220
|
-
return await operation();
|
|
60
|
+
let url = `https://${this.ipAddress}:${PropagationServer.port}/stores/${this.storeId}`;
|
|
61
|
+
if (rootHash) {
|
|
62
|
+
url += `?hasRootHash=${rootHash}`;
|
|
63
|
+
}
|
|
64
|
+
const response = await axios_1.default.head(url, config);
|
|
65
|
+
// Extract store existence and root hash existence from headers
|
|
66
|
+
const storeExists = response.headers["x-store-exists"] === "true";
|
|
67
|
+
const rootHashExists = response.headers["x-has-root-hash"] === "true";
|
|
68
|
+
if (storeExists) {
|
|
69
|
+
spinner.succeed(chalk_1.default.green(`Store ${this.storeId} exists!`));
|
|
221
70
|
}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
71
|
+
else {
|
|
72
|
+
spinner.fail(chalk_1.default.red(`Store ${this.storeId} does not exist.`));
|
|
73
|
+
}
|
|
74
|
+
if (rootHash) {
|
|
75
|
+
if (rootHashExists) {
|
|
76
|
+
console.log(chalk_1.default.green(`Root hash ${rootHash} exists in the store.`));
|
|
227
77
|
}
|
|
228
78
|
else {
|
|
229
|
-
console.
|
|
230
|
-
throw error;
|
|
79
|
+
console.log(chalk_1.default.red(`Root hash ${rootHash} does not exist in the store.`));
|
|
231
80
|
}
|
|
232
81
|
}
|
|
233
|
-
|
|
82
|
+
return { storeExists, rootHashExists };
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
spinner.fail(chalk_1.default.red("Error checking if store exists:"));
|
|
86
|
+
console.error(chalk_1.default.red(error));
|
|
87
|
+
throw error;
|
|
234
88
|
}
|
|
235
|
-
throw new Error(errorMessage);
|
|
236
89
|
}
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
method: "GET",
|
|
246
|
-
key: fs_1.default.readFileSync(PropagationServer.keyPath),
|
|
247
|
-
cert: fs_1.default.readFileSync(PropagationServer.certPath),
|
|
248
|
-
rejectUnauthorized: false,
|
|
90
|
+
/**
|
|
91
|
+
* Start an upload session by sending a POST request to the server.
|
|
92
|
+
*/
|
|
93
|
+
async startUploadSession() {
|
|
94
|
+
const spinner = (0, ora_1.default)(`Starting upload session for store ${this.storeId}...`).start();
|
|
95
|
+
try {
|
|
96
|
+
const config = {
|
|
97
|
+
httpsAgent: this.createHttpsAgent(),
|
|
249
98
|
};
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
});
|
|
256
|
-
response.on("end", () => {
|
|
257
|
-
resolve(data);
|
|
258
|
-
});
|
|
259
|
-
}
|
|
260
|
-
else if (response.statusCode === 301 || response.statusCode === 302) {
|
|
261
|
-
const redirectUrl = response.headers.location;
|
|
262
|
-
if (redirectUrl) {
|
|
263
|
-
this.fetch(redirectUrl).then(resolve).catch(reject);
|
|
264
|
-
}
|
|
265
|
-
else {
|
|
266
|
-
reject(new Error("Redirected without a location header"));
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
else {
|
|
270
|
-
reject(new Error(`Failed to retrieve data from ${url}. Status code: ${response.statusCode}`));
|
|
271
|
-
}
|
|
272
|
-
});
|
|
273
|
-
request.on("error", (error) => {
|
|
274
|
-
console.error(`GET Request error for ${url}:`, error);
|
|
275
|
-
reject(error);
|
|
276
|
-
});
|
|
277
|
-
request.end();
|
|
278
|
-
});
|
|
279
|
-
}
|
|
280
|
-
// Helper method to perform HEAD requests
|
|
281
|
-
async head(url, maxRedirects = 5) {
|
|
282
|
-
return new Promise((resolve, reject) => {
|
|
283
|
-
try {
|
|
284
|
-
// Parse the input URL
|
|
285
|
-
const urlObj = new url_1.URL(url);
|
|
286
|
-
const requestOptions = {
|
|
287
|
-
hostname: urlObj.hostname,
|
|
288
|
-
port: urlObj.port || (urlObj.protocol === "https:" ? 443 : undefined),
|
|
289
|
-
path: urlObj.pathname + urlObj.search,
|
|
290
|
-
method: "HEAD",
|
|
291
|
-
key: fs_1.default.readFileSync(PropagationServer.keyPath),
|
|
292
|
-
cert: fs_1.default.readFileSync(PropagationServer.certPath),
|
|
293
|
-
rejectUnauthorized: false,
|
|
99
|
+
// Add Basic Auth headers if credentials are provided
|
|
100
|
+
if (this.username && this.password) {
|
|
101
|
+
config.auth = {
|
|
102
|
+
username: this.username,
|
|
103
|
+
password: this.password,
|
|
294
104
|
};
|
|
295
|
-
const request = https_1.default.request(requestOptions, (response) => {
|
|
296
|
-
const { statusCode, headers } = response;
|
|
297
|
-
// If status code is 2xx, return success
|
|
298
|
-
if (statusCode && statusCode >= 200 && statusCode < 300) {
|
|
299
|
-
resolve({ success: true, headers });
|
|
300
|
-
}
|
|
301
|
-
// Handle 3xx redirection
|
|
302
|
-
else if (statusCode && statusCode >= 300 && statusCode < 400 && headers.location) {
|
|
303
|
-
if (maxRedirects > 0) {
|
|
304
|
-
let redirectUrl = headers.location;
|
|
305
|
-
// Check if the redirect URL is relative
|
|
306
|
-
if (!/^https?:\/\//i.test(redirectUrl)) {
|
|
307
|
-
// Resolve the relative URL based on the original URL
|
|
308
|
-
redirectUrl = new url_1.URL(redirectUrl, url).toString();
|
|
309
|
-
console.log(`Resolved relative redirect to: ${redirectUrl}`);
|
|
310
|
-
}
|
|
311
|
-
else {
|
|
312
|
-
console.log(`Redirecting to: ${redirectUrl}`);
|
|
313
|
-
}
|
|
314
|
-
// Recursively follow the redirection
|
|
315
|
-
this.head(redirectUrl, maxRedirects - 1)
|
|
316
|
-
.then(resolve)
|
|
317
|
-
.catch(reject);
|
|
318
|
-
}
|
|
319
|
-
else {
|
|
320
|
-
reject({ success: false, message: "Too many redirects" });
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
else {
|
|
324
|
-
// For other status codes, consider it a failure
|
|
325
|
-
resolve({ success: false });
|
|
326
|
-
}
|
|
327
|
-
});
|
|
328
|
-
request.on("error", (error) => {
|
|
329
|
-
console.error(`HEAD ${url}:`, error.message);
|
|
330
|
-
reject({ success: false });
|
|
331
|
-
});
|
|
332
|
-
request.end();
|
|
333
|
-
}
|
|
334
|
-
catch (err) {
|
|
335
|
-
console.error(`Invalid URL: ${url}`, err.message);
|
|
336
|
-
reject({ success: false, message: "Invalid URL" });
|
|
337
105
|
}
|
|
106
|
+
const url = `https://${this.ipAddress}:${PropagationServer.port}/upload/${this.storeId}`;
|
|
107
|
+
const response = await axios_1.default.post(url, { publicKey: this.publicKey }, config);
|
|
108
|
+
this.sessionId = response.data.sessionId;
|
|
109
|
+
spinner.succeed(chalk_1.default.green(`Upload session started for DataStore ${this.storeId} with session ID ${this.sessionId}`));
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
spinner.fail(chalk_1.default.red("Error starting upload session:"));
|
|
113
|
+
console.error(chalk_1.default.red(error));
|
|
114
|
+
throw error;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Request a nonce for a file by sending a HEAD request to the server.
|
|
119
|
+
*/
|
|
120
|
+
async getFileNonce(filename) {
|
|
121
|
+
try {
|
|
122
|
+
const config = {
|
|
123
|
+
httpsAgent: this.createHttpsAgent(),
|
|
124
|
+
};
|
|
125
|
+
const url = `https://${this.ipAddress}:${PropagationServer.port}/upload/${this.storeId}/${this.sessionId}/${filename}`;
|
|
126
|
+
const response = await axios_1.default.head(url, config);
|
|
127
|
+
const nonce = response.headers["x-nonce"];
|
|
128
|
+
console.log(chalk_1.default.blue(`Nonce received for file ${filename}: ${nonce}`));
|
|
129
|
+
return nonce;
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
console.error(chalk_1.default.red(`Error generating nonce for file ${filename}:`), error);
|
|
133
|
+
throw error;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Upload a file to the server by sending a PUT request.
|
|
138
|
+
* Logs progress using cli-progress for each file.
|
|
139
|
+
*/
|
|
140
|
+
async uploadFile(filePath) {
|
|
141
|
+
const filename = path_1.default.basename(filePath);
|
|
142
|
+
const nonce = await this.getFileNonce(filename);
|
|
143
|
+
const keyOwnershipSig = await this.wallet.createKeyOwnershipSignature(nonce);
|
|
144
|
+
const formData = new form_data_1.default();
|
|
145
|
+
formData.append("file", fs_1.default.createReadStream(filePath));
|
|
146
|
+
const fileSize = fs_1.default.statSync(filePath).size;
|
|
147
|
+
// Create a new progress bar for each file
|
|
148
|
+
const progressBar = PropagationServer.multiBar.create(fileSize, 0, {
|
|
149
|
+
filename: chalk_1.default.yellow(filename),
|
|
150
|
+
percentage: 0,
|
|
338
151
|
});
|
|
152
|
+
let uploadedBytes = 0;
|
|
153
|
+
const config = {
|
|
154
|
+
headers: {
|
|
155
|
+
"Content-Type": "multipart/form-data",
|
|
156
|
+
"x-nonce": nonce,
|
|
157
|
+
"x-public-key": this.publicKey,
|
|
158
|
+
"x-key-ownership-sig": keyOwnershipSig,
|
|
159
|
+
...formData.getHeaders(),
|
|
160
|
+
},
|
|
161
|
+
httpsAgent: this.createHttpsAgent(),
|
|
162
|
+
// Tracking upload progress and updating the progress bar
|
|
163
|
+
onUploadProgress: (progressEvent) => {
|
|
164
|
+
uploadedBytes += progressEvent.loaded;
|
|
165
|
+
const percentage = Math.round((uploadedBytes / fileSize) * 100);
|
|
166
|
+
progressBar.update(uploadedBytes, { percentage });
|
|
167
|
+
},
|
|
168
|
+
};
|
|
169
|
+
try {
|
|
170
|
+
const url = `https://${this.ipAddress}:${PropagationServer.port}/upload/${this.storeId}/${this.sessionId}/${filename}`;
|
|
171
|
+
const response = await axios_1.default.put(url, formData, config);
|
|
172
|
+
console.log(chalk_1.default.green(`✔ File ${filename} uploaded successfully.`));
|
|
173
|
+
// Complete the progress bar
|
|
174
|
+
progressBar.update(fileSize, { percentage: 100 });
|
|
175
|
+
progressBar.stop();
|
|
176
|
+
return response.data;
|
|
177
|
+
}
|
|
178
|
+
catch (error) {
|
|
179
|
+
console.error(chalk_1.default.red(`✖ Error uploading file ${filename}:`), error);
|
|
180
|
+
progressBar.stop(); // Stop the progress bar in case of error
|
|
181
|
+
throw error;
|
|
182
|
+
}
|
|
339
183
|
}
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
184
|
+
/**
|
|
185
|
+
* Static function to handle the entire upload process for multiple files based on rootHash.
|
|
186
|
+
* @param {string} storeId - The ID of the DataStore.
|
|
187
|
+
* @param {string} rootHash - The root hash used to derive the file set.
|
|
188
|
+
* @param {string} publicKey - The public key of the user.
|
|
189
|
+
* @param {string} ipAddress - The IP address of the server.
|
|
190
|
+
*/
|
|
191
|
+
static async uploadStore(storeId, rootHash, ipAddress) {
|
|
192
|
+
const propagationServer = new PropagationServer(storeId, ipAddress);
|
|
193
|
+
// Initialize wallet
|
|
194
|
+
await propagationServer.initializeWallet();
|
|
195
|
+
// Check if the store exists
|
|
196
|
+
const storeExists = await propagationServer.checkStoreExists();
|
|
197
|
+
// If the store does not exist, prompt for credentials
|
|
198
|
+
if (!storeExists) {
|
|
199
|
+
console.log(chalk_1.default.red(`Store ${storeId} does not exist. Prompting for credentials...`));
|
|
200
|
+
const credentials = await (0, credentialsUtils_1.promptCredentials)(propagationServer.ipAddress);
|
|
201
|
+
propagationServer.username = credentials.username;
|
|
202
|
+
propagationServer.password = credentials.password;
|
|
203
|
+
}
|
|
204
|
+
// Start the upload session
|
|
205
|
+
await propagationServer.startUploadSession();
|
|
206
|
+
const dataStore = blockchain_1.DataStore.from(storeId);
|
|
207
|
+
const filePaths = await dataStore.getFileSetForRootHash(rootHash);
|
|
208
|
+
// Upload each file
|
|
209
|
+
for (const filePath of filePaths) {
|
|
210
|
+
await propagationServer.uploadFile(filePath);
|
|
211
|
+
}
|
|
212
|
+
// Stop all progress bars after the files are uploaded
|
|
213
|
+
PropagationServer.multiBar.stop();
|
|
214
|
+
console.log(chalk_1.default.green(`✔ All files have been uploaded to DataStore ${storeId}.`));
|
|
344
215
|
}
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
216
|
+
/**
|
|
217
|
+
* Fetch a file from the server by sending a GET request and return its content in memory.
|
|
218
|
+
* Logs progress using cli-progress.
|
|
219
|
+
* @param {string} dataPath - The data path of the file to download.
|
|
220
|
+
* @returns {Promise<Buffer>} - The file content in memory as a Buffer.
|
|
221
|
+
*/
|
|
222
|
+
async fetchFile(dataPath) {
|
|
223
|
+
const config = {
|
|
224
|
+
responseType: "arraybuffer", // To store the file content in memory
|
|
225
|
+
httpsAgent: this.createHttpsAgent(),
|
|
226
|
+
};
|
|
227
|
+
const url = `https://${this.ipAddress}:${PropagationServer.port}/fetch/${this.storeId}/${dataPath}`;
|
|
355
228
|
try {
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
);
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
229
|
+
const response = await axios_1.default.get(url, config);
|
|
230
|
+
const totalLength = parseInt(response.headers["content-length"], 10);
|
|
231
|
+
console.log(chalk_1.default.cyan(`Starting fetch for ${dataPath}...`));
|
|
232
|
+
// Create a progress bar for the download
|
|
233
|
+
const progressBar = PropagationServer.multiBar.create(totalLength, 0, {
|
|
234
|
+
dataPath: chalk_1.default.yellow(dataPath),
|
|
235
|
+
percentage: 0,
|
|
236
|
+
});
|
|
237
|
+
let downloadedBytes = 0;
|
|
238
|
+
// Track progress of downloading the file
|
|
239
|
+
response.data.on("data", (chunk) => {
|
|
240
|
+
downloadedBytes += chunk.length;
|
|
241
|
+
progressBar.update(downloadedBytes, {
|
|
242
|
+
percentage: Math.round((downloadedBytes / totalLength) * 100),
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
// Complete the progress bar once done
|
|
246
|
+
progressBar.update(totalLength, { percentage: 100 });
|
|
247
|
+
progressBar.stop();
|
|
248
|
+
console.log(chalk_1.default.green(`✔ File ${dataPath} fetched successfully.`));
|
|
249
|
+
// Return the file contents as a Buffer
|
|
250
|
+
return Buffer.from(response.data);
|
|
370
251
|
}
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
`Failed to retrieve data from ${url} after ${maxRetries} attempts.`
|
|
375
|
-
);
|
|
376
|
-
}*/
|
|
377
|
-
// Helper method to create a stream with retry logic
|
|
378
|
-
async createReadStreamWithRetries(url) {
|
|
379
|
-
let attempt = 0;
|
|
380
|
-
const maxRetries = 1;
|
|
381
|
-
const initialDelay = PropagationServer.initialDelay;
|
|
382
|
-
const maxDelay = PropagationServer.maxDelay;
|
|
383
|
-
const delayMultiplier = PropagationServer.delayMultiplier;
|
|
384
|
-
let delay = initialDelay;
|
|
385
|
-
while (attempt < maxRetries) {
|
|
386
|
-
try {
|
|
387
|
-
return await this.createReadStream(url); // Stream data chunk by chunk
|
|
388
|
-
}
|
|
389
|
-
catch (error) {
|
|
390
|
-
if (attempt < maxRetries - 1) {
|
|
391
|
-
console.warn(`Attempt ${attempt + 1} failed: ${error.message}. Retrying in ${delay / 1000} seconds...`);
|
|
392
|
-
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
393
|
-
delay = Math.min(maxDelay, delay * delayMultiplier);
|
|
394
|
-
}
|
|
395
|
-
else {
|
|
396
|
-
console.error(`Failed to create stream from ${url}. Aborting.`);
|
|
397
|
-
throw new Error(`Failed to create stream: ${error.message}`);
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
attempt++;
|
|
252
|
+
catch (error) {
|
|
253
|
+
console.error(chalk_1.default.red(`✖ Error fetching file ${dataPath}:`), error);
|
|
254
|
+
throw error;
|
|
401
255
|
}
|
|
402
|
-
throw new Error(`Failed to create stream from ${url} after ${maxRetries} attempts.`);
|
|
403
256
|
}
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
reject(new Error("Redirected without a location header"));
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
else {
|
|
431
|
-
reject(new Error(`Failed to retrieve stream from ${url}. Status code: ${response.statusCode}`));
|
|
432
|
-
}
|
|
257
|
+
/**
|
|
258
|
+
* Download a file from the server by sending a GET request.
|
|
259
|
+
* Logs progress using cli-progress.
|
|
260
|
+
* @param {string} dataPath - The data path of the file to download.
|
|
261
|
+
*/
|
|
262
|
+
async downloadFile(dataPath) {
|
|
263
|
+
const config = {
|
|
264
|
+
responseType: "stream", // Make sure the response is streamed
|
|
265
|
+
httpsAgent: this.createHttpsAgent(),
|
|
266
|
+
};
|
|
267
|
+
const url = `https://${this.ipAddress}:${PropagationServer.port}/fetch/${this.storeId}/${dataPath}`;
|
|
268
|
+
const downloadPath = path_1.default.join(config_1.STORE_PATH, this.storeId, dataPath); // Save to the correct store directory
|
|
269
|
+
// Ensure that the directory for the file exists
|
|
270
|
+
fs_1.default.mkdirSync(path_1.default.dirname(downloadPath), { recursive: true });
|
|
271
|
+
const fileWriteStream = fs_1.default.createWriteStream(downloadPath);
|
|
272
|
+
try {
|
|
273
|
+
const response = await axios_1.default.get(url, config);
|
|
274
|
+
const totalLength = parseInt(response.headers["content-length"], 10);
|
|
275
|
+
console.log(chalk_1.default.cyan(`Starting download for ${dataPath}...`));
|
|
276
|
+
// Create a progress bar for the download
|
|
277
|
+
const progressBar = PropagationServer.multiBar.create(totalLength, 0, {
|
|
278
|
+
dataPath: chalk_1.default.yellow(dataPath),
|
|
279
|
+
percentage: 0,
|
|
433
280
|
});
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
281
|
+
let downloadedBytes = 0;
|
|
282
|
+
// Pipe the response data to the file stream and track progress
|
|
283
|
+
response.data.on("data", (chunk) => {
|
|
284
|
+
downloadedBytes += chunk.length;
|
|
285
|
+
progressBar.update(downloadedBytes, {
|
|
286
|
+
percentage: Math.round((downloadedBytes / totalLength) * 100),
|
|
287
|
+
});
|
|
437
288
|
});
|
|
438
|
-
|
|
439
|
-
|
|
289
|
+
// Pipe the data into the file and finalize
|
|
290
|
+
response.data.pipe(fileWriteStream);
|
|
291
|
+
return new Promise((resolve, reject) => {
|
|
292
|
+
fileWriteStream.on("finish", () => {
|
|
293
|
+
progressBar.update(totalLength, { percentage: 100 });
|
|
294
|
+
progressBar.stop();
|
|
295
|
+
console.log(chalk_1.default.green(`✔ File ${dataPath} downloaded successfully to ${downloadPath}.`));
|
|
296
|
+
resolve();
|
|
297
|
+
});
|
|
298
|
+
fileWriteStream.on("error", (error) => {
|
|
299
|
+
progressBar.stop();
|
|
300
|
+
console.error(chalk_1.default.red(`✖ Error downloading file ${dataPath}:`), error);
|
|
301
|
+
reject(error);
|
|
302
|
+
});
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
catch (error) {
|
|
306
|
+
console.error(chalk_1.default.red(`✖ Error downloading file ${dataPath}:`), error);
|
|
307
|
+
throw error;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Static function to handle downloading multiple files from a DataStore based on file paths.
|
|
312
|
+
* @param {string} storeId - The ID of the DataStore.
|
|
313
|
+
* @param {string[]} dataPaths - The list of data paths to download.
|
|
314
|
+
* @param {string} ipAddress - The IP address of the server.
|
|
315
|
+
*/
|
|
316
|
+
static async downloadStore(storeId, rootHash, ipAddress) {
|
|
317
|
+
const propagationServer = new PropagationServer(storeId, ipAddress);
|
|
318
|
+
// Initialize wallet
|
|
319
|
+
await propagationServer.initializeWallet();
|
|
320
|
+
// Check if the store exists
|
|
321
|
+
const storeExists = await propagationServer.checkStoreExists();
|
|
322
|
+
if (!storeExists) {
|
|
323
|
+
throw new Error(`Store ${storeId} does not exist.`);
|
|
324
|
+
}
|
|
325
|
+
await propagationServer.downloadFile("height.json");
|
|
326
|
+
const datFileContent = await propagationServer.fetchFile(`${rootHash}.dat`);
|
|
327
|
+
const root = JSON.parse(datFileContent.toString());
|
|
328
|
+
for (const [fileKey, fileData] of Object.entries(root.files)) {
|
|
329
|
+
const dataPath = (0, hashUtils_1.getFilePathFromSha256)(root.files[fileKey].sha256, "data");
|
|
330
|
+
await propagationServer.downloadFile(dataPath);
|
|
331
|
+
}
|
|
332
|
+
console.log(chalk_1.default.green(`✔ All files have been downloaded to ${storeId}.`));
|
|
440
333
|
}
|
|
441
334
|
}
|
|
442
335
|
exports.PropagationServer = PropagationServer;
|
|
443
|
-
PropagationServer.port = 4159;
|
|
444
|
-
|
|
445
|
-
PropagationServer.
|
|
446
|
-
|
|
447
|
-
|
|
336
|
+
PropagationServer.port = 4159; // Static port used for all requests
|
|
337
|
+
// MultiBar for handling multiple progress bars
|
|
338
|
+
PropagationServer.multiBar = new cli_progress_1.default.MultiBar({
|
|
339
|
+
clearOnComplete: false,
|
|
340
|
+
hideCursor: true,
|
|
341
|
+
format: chalk_1.default.green(" {bar} ") +
|
|
342
|
+
chalk_1.default.cyan(" | {filename} | {percentage}% | {value}/{total} bytes"),
|
|
343
|
+
barsize: 40,
|
|
344
|
+
stopOnComplete: true,
|
|
345
|
+
align: "center",
|
|
346
|
+
}, cli_progress_1.default.Presets.shades_classic);
|