@trap_stevo/filetide 0.0.46 → 0.0.48

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.
@@ -325,7 +325,7 @@ class FileMessager {
325
325
  ...data,
326
326
  transferType: "send"
327
327
  });
328
- this.transferQueue.waitForData(data.transferID, () => this.currentTransferBarrierTransfers.get(data.transferID), [true, false], 100, 50000).then(accepted => {
328
+ this.transferQueue.waitForData(data.transferID, () => this.currentTransferBarrierTransfers.get(data.transferID), [true, false], 100, data.transferExpirationTime ? data.transferExpirationTime : 50000).then(accepted => {
329
329
  console.log(`[FileTide ~ Net Barrier] ~ Transfer ${accepted ? "accepted!" : "denied."}`);
330
330
  this.fileNet.emitToTide(senderClient.tideID, "transfer-barrier-response", accepted);
331
331
  this.currentTransferBarrierTransfers.delete(data.transferID);
@@ -26,8 +26,8 @@ class FileMessagerClient {
26
26
  parallelChunks: 3,
27
27
  maxRetries: 3
28
28
  }, messagerSettings = {
29
- maxReconnectionAttempts: 5,
30
- reconnectionAttempts: 5,
29
+ maxReconnectionAttempts: 15,
30
+ reconnectionAttempts: 15,
31
31
  maxReconnectionDelay: 10000
32
32
  }) {
33
33
  const {
@@ -67,8 +67,9 @@ class FileMessagerClient {
67
67
  });
68
68
  };
69
69
  }
70
- async requestTransfer(clientID, senderID, filePath, destinationPath, filterContent = []) {
71
- const transferAllowed = await this.allowedTransfer(clientID, senderID, destinationPath, "N/A", "requestSend");
70
+ async requestTransfer(clientID, senderID, filePath, destinationPath, filterContent = [], transferRequestExpirationTime = 30000) {
71
+ const transferContentType = FileUtilityManager.inferPathType(filePath);
72
+ const transferAllowed = await this.allowedTransfer(clientID, senderID, destinationPath, filePath, "N/A", transferContentType === "file" ? 1 : "N/A", transferContentType, "requestSend", transferRequestExpirationTime);
72
73
  if (!transferAllowed) {
73
74
  return;
74
75
  }
@@ -89,7 +90,7 @@ class FileMessagerClient {
89
90
  });
90
91
  return;
91
92
  }
92
- async allowedTransfer(clientID, recipientID, filePath, fileSize, transferType = "send") {
93
+ async allowedTransfer(clientID, recipientID, filePath, originPath, fileSize, fileCount = 1, transferContentType = "file", transferType = "send", transferExpirationTime = 30000) {
93
94
  try {
94
95
  const currentDate = Date.now();
95
96
  const transferID = `${clientID}_${filePath}_${currentDate}`;
@@ -97,10 +98,16 @@ class FileMessagerClient {
97
98
  clientID,
98
99
  recipientID,
99
100
  transferID,
101
+ originPath,
100
102
  path: filePath,
101
103
  date: currentDate,
104
+ transferExpirationTime,
105
+ timeUntilTransferExpiration: transferExpirationTime,
106
+ itemCount: fileCount,
107
+ itemSize: fileSize,
108
+ transferContentType,
102
109
  transferType
103
- }, 30000);
110
+ }, transferExpirationTime);
104
111
  if (!transferAccepted) {
105
112
  FileNetUtilityManager.outputGradient(`[FileTide ~ File Messager] ${recipientID} denied your transfer.`, errorMessageColors);
106
113
  return false;
@@ -124,12 +131,13 @@ class FileMessagerClient {
124
131
  * @param {string} recipientId - The ID of the recipient.
125
132
  * @param {Array} filesData - Array of file data objects from the directory.
126
133
  * @param {string} baseDirectory - The base directory path that contains the files.
127
- * @param {string} destinationPath - The path to which the files should be sent.
128
- * @param {Number} tideSize - The tide size in sending files (500)
129
- * @param {Number} minTideSize - The minimum tide size in sending files (10)
130
- * @param {Number} maxTideSize - The maximum tide size in sending files (2000)
134
+ * @param {string} destinationPath - The path to which the files should get sent.
135
+ * @param {Number} tideSize - The tide size in sending files (500).
136
+ * @param {Number} minTideSize - The minimum tide size in sending files (10).
137
+ * @param {Number} maxTideSize - The maximum tide size in sending files (2000).
138
+ * @param {Number} transferRequestExpirationTime - The maximum time until the transfer barrier times out the transfer request.
131
139
  */
132
- async sendDirectoryFiles(clientID, recipientId, filesData, baseDirectory, destinationPath, tideSize = 500, minTideSize = 10, maxTideSize = 2000) {
140
+ async sendDirectoryFiles(clientID, recipientId, filesData, baseDirectory, destinationPath, tideSize = 500, minTideSize = 10, maxTideSize = 2000, transferRequestExpirationTime = 30000) {
133
141
  try {
134
142
  const baseDirectoryName = path.basename(baseDirectory);
135
143
  const adjustedDestinationPath = FileUtilityManager.normalizePath(path.join(destinationPath, baseDirectoryName));
@@ -138,7 +146,7 @@ class FileMessagerClient {
138
146
  totalSize += fileInfo.size;
139
147
  });
140
148
  const incomingType = this.activeTransferTypes.get(destinationPath);
141
- const transferAllowed = incomingType && incomingType === "requestedTransfer" ? true : await this.allowedTransfer(clientID, recipientId, adjustedDestinationPath, totalSize, "send");
149
+ const transferAllowed = incomingType && incomingType === "requestedTransfer" ? true : await this.allowedTransfer(clientID, recipientId, adjustedDestinationPath, baseDirectory, totalSize, filesData.length, "directory", "send", transferRequestExpirationTime);
142
150
  if (incomingType) {
143
151
  this.activeTransferTypes.delete(destinationPath);
144
152
  }
@@ -149,26 +157,27 @@ class FileMessagerClient {
149
157
  const relativeFilePath = path.dirname(path.relative(baseDirectory, fileInfo.filePath));
150
158
  const fileDestinationPath = FileUtilityManager.normalizePath(path.join(adjustedDestinationPath, relativeFilePath));
151
159
  if (fileInfo.largeFile) {
152
- return await this.sendDirectoryLargeFile(clientID, recipientId, fileInfo.fileName, fileInfo.filePath, fileDestinationPath, fileInfo.fileSize, index, tideSize, minTideSize, maxTideSize);
160
+ return await this.sendDirectoryLargeFile(clientID, recipientId, fileInfo.fileName, fileInfo.filePath, fileDestinationPath, fileInfo.fileSize, index, tideSize, minTideSize, maxTideSize, transferRequestExpirationTime);
153
161
  }
154
- return await this.sendDirectoryFile(clientID, recipientId, fileInfo.fileName, fileInfo.fileData, fileDestinationPath, fileInfo.size, adjustedDestinationPath, index);
162
+ return await this.sendDirectoryFile(clientID, recipientId, fileInfo.fileName, fileInfo.filePath, fileInfo.size, fileDestinationPath, index, tideSize, minTideSize, maxTideSize, transferRequestExpirationTime);
155
163
  });
156
164
  Promise.all(sendFilePromises).then(() => console.log("\nAll files in directory transferred successfully!")).catch(error => console.error("Did not transfer directory: ", error));
157
165
  } catch (error) {
158
166
  FileNetUtilityManager.outputGradient(`[FileTide ~ File Messager] ~ Refused directory transfer: ${error}`, errorMessageColors);
159
167
  }
160
168
  }
161
- async sendDirectoryLargeFile(clientID, recipientId, fileName, filePath = process.cwd(), destinationPath = null, fileSize, index = 0, tideSize = 500, minTideSize = 10, maxTideSize = 2000) {
169
+ async sendDirectoryLargeFile(clientID, recipientId, fileName, filePath = process.cwd(), destinationPath = null, fileSize, index = 0, tideSize = 500, minTideSize = 10, maxTideSize = 2000, transferRequestExpirationTime = 30000) {
162
170
  let initiatingTransfer = false;
163
171
  if (index === 0) {
164
172
  initiatingTransfer = true;
165
173
  }
166
- return await this.sendLargeFile(clientID, recipientId, fileName, filePath, destinationPath, fileSize, tideSize, minTideSize, maxTideSize, initiatingTransfer, true);
174
+ return await this.sendFile(clientID, recipientId, fileName, filePath, destinationPath, fileSize, tideSize, minTideSize, maxTideSize, initiatingTransfer, true, true, true, transferRequestExpirationTime);
167
175
  }
168
- sendDirectoryFile(clientID, recipientId, fileName, file, filePath = process.cwd(), fileSize, directoryPath = null, index = 0) {
169
- if (!file) {
176
+ async sendDirectoryFile(clientID, recipientId, fileName, filePath = process.cwd(), fileSize, directoryPath = null, index = 0, tideSize = 200, minTideSize = 10, maxTideSize = 2000, transferRequestExpirationTime = 30000) {
177
+ if (!filePath) {
170
178
  return;
171
179
  }
180
+ let initiatingTransfer = false;
172
181
  if (index === 0) {
173
182
  this.clientTide.emitEvent(clientID, "transfer-start", {
174
183
  fileName,
@@ -177,56 +186,16 @@ class FileMessagerClient {
177
186
  path: directoryPath || filePath,
178
187
  fileSize
179
188
  });
189
+ initiatingTransfer = true;
180
190
  }
181
- this.transporter.sendFile(file, {
182
- onSendChunk: (transferId, chunkIndex, chunkSize, chunkData, totalChunks, completedChunks) => {
183
- return new Promise((resolve, reject) => {
184
- this.clientTide.emitEvent(clientID, "client-to-client-transfer", {
185
- senderID: clientID,
186
- recipientId,
187
- fileName,
188
- fileChunk: chunkData,
189
- completedChunks,
190
- totalChunks,
191
- chunkIndex,
192
- chunkSize,
193
- filePath,
194
- fileSize
195
- });
196
- resolve();
197
- });
198
- },
199
- onComplete: () => {
200
- return new Promise((resolve, reject) => {
201
- FileTransferRecoveryManager.clearTransferState(clientID, recipientId, `${filePath}-${fileName}`);
202
- this.clientTide.emitEvent(clientID, "transfer-complete", {
203
- senderID: clientID,
204
- recipientId,
205
- fileName,
206
- filePath
207
- });
208
- resolve();
209
- });
210
- },
211
- originDetails: {
212
- recipientID: recipientId,
213
- senderID: clientID
214
- },
215
- recipientID: recipientId,
216
- fileDetails: {
217
- destinationPath: filePath,
218
- name: fileName,
219
- path: filePath,
220
- size: fileSize
221
- }
222
- }, this.clientTide).then(() => FileNetUtilityManager.outputGradient(`[FileTide ~ File Messager] ~ File ~ ${fileName} transfer complete!`, significantMessageColors)).catch(error => FileNetUtilityManager.outputGradient(`[FileTide ~ File Messager] ~ Refused file transfer: ${error}`, errorMessageColors));
191
+ return await this.sendFile(clientID, recipientId, fileName, filePath, directoryPath, fileSize, tideSize, minTideSize, maxTideSize, initiatingTransfer, true, true, false, transferRequestExpirationTime);
223
192
  }
224
- async sendLargeFile(clientID, recipientId, fileName, filePath = process.cwd(), destination = process.cwd(), fileSize, tideSize = 500, minTideSize = 10, maxTideSize = 2000, initiatingTransfer = true, inDirectory = false) {
193
+ async sendFile(clientID, recipientId, fileName, filePath = process.cwd(), destination = process.cwd(), fileSize, tideSize = 500, minTideSize = 10, maxTideSize = 2000, initiatingTransfer = true, inDirectory = false, largeFile = false, transferRequestExpirationTime = 30000) {
225
194
  const destinationPath = FileUtilityManager.normalizePath(path.join(destination, fileName));
226
- FileNetUtilityManager.outputGradient(`[FileTide ~ File Messager] ~ Requesting to transfer large file ~ ${destinationPath} to ${recipientId}!`, significantMessageColors);
195
+ FileNetUtilityManager.outputGradient(largeFile ? `[FileTide ~ File Messager] ~ Requesting to transfer large file ~ ${destinationPath} to ${recipientId}!` : `[FileTide ~ File Messager] ~ Requesting to transfer ~ ${destinationPath} to ${recipientId}!`, significantMessageColors);
227
196
  const incomingType = this.activeTransferTypes.get(filePath);
228
197
  if (!inDirectory && initiatingTransfer) {
229
- const transferAllowed = incomingType && incomingType === "requestedTransfer" ? true : await this.allowedTransfer(clientID, recipientId, destinationPath, fileSize, "send");
198
+ const transferAllowed = incomingType && incomingType === "requestedTransfer" ? true : await this.allowedTransfer(clientID, recipientId, destinationPath, filePath, fileSize, 1, "file", "send", transferRequestExpirationTime);
230
199
  if (incomingType) {
231
200
  this.activeTransferTypes.delete(filePath);
232
201
  }
@@ -257,6 +226,7 @@ class FileMessagerClient {
257
226
  const maxBatchSize = maxTideSize;
258
227
  const minBatchSize = minTideSize;
259
228
  let batchSize = tideSize > maxBatchSize || tideSize > minBatchSize ? tideSize : tideSize <= minBatchSize ? minBatchSize : 10;
229
+ let transferInitiated = false;
260
230
  const chunkBatch = [];
261
231
  await this.transporter.sendLargeFile(fileHandle, {
262
232
  recipientID: recipientId,
@@ -279,6 +249,13 @@ class FileMessagerClient {
279
249
  filePath: destination,
280
250
  fileSize
281
251
  });
252
+ if (!transferInitiated) {
253
+ this.clientTide.emitEvent(clientID, "client-to-client-transfer", {
254
+ chunkBatch
255
+ });
256
+ transferInitiated = true;
257
+ chunkBatch.length = 0;
258
+ }
282
259
  if (chunkBatch.length >= batchSize) {
283
260
  const now = Date.now();
284
261
  const rtt = now - lastSendTime;
@@ -321,76 +298,13 @@ class FileMessagerClient {
321
298
  });
322
299
  }
323
300
  }, this.clientTide);
324
- FileNetUtilityManager.outputGradient(`[FileTide ~ File Messager] ~ Large file ~ ${fileName} transfer complete!`, significantMessageColors);
301
+ FileNetUtilityManager.outputGradient(largeFile ? `[FileTide ~ File Messager] ~ Large file ~ ${fileName} transfer complete!` : `[FileTide ~ File Messager] ~ File ~ ${fileName} transfer complete!`, significantMessageColors);
325
302
  } catch (error) {
326
- FileNetUtilityManager.outputGradient(`[FileTide ~ File Messager] ~ Error transferring large file: ${error.message}`, errorMessageColors);
303
+ FileNetUtilityManager.outputGradient(largeFile ? `[FileTide ~ File Messager] ~ Error transferring large file: ${error.message}` : `[FileTide ~ File Messager] ~ Error transferring file ~ ${error.message}`, errorMessageColors);
327
304
  } finally {
328
305
  await fileHandle.close();
329
306
  }
330
307
  }
331
- async sendFile(clientID, recipientId, fileName, file, filePath = process.cwd(), fileSize) {
332
- if (!file) {
333
- return;
334
- }
335
- const destinationPath = FileUtilityManager.normalizePath(path.join(filePath, fileName));
336
- FileNetUtilityManager.outputGradient(`[FileTide ~ File Messager] ~ Requesting to transfer ~ ${destinationPath} to ${recipientId}!`, significantMessageColors);
337
- const incomingType = this.activeTransferTypes.get(filePath);
338
- const transferAllowed = incomingType && incomingType === "requestedTransfer" ? true : await this.allowedTransfer(clientID, recipientId, destinationPath, fileSize, "send");
339
- if (incomingType) {
340
- this.activeTransferTypes.delete(filePath);
341
- }
342
- if (!transferAllowed) {
343
- return;
344
- }
345
- this.clientTide.emitEvent(clientID, "transfer-start", {
346
- fileName,
347
- senderID: clientID,
348
- recipientID: recipientId,
349
- path: filePath,
350
- fileSize
351
- });
352
- await this.transporter.sendFile(file, {
353
- onSendChunk: (transferId, chunkIndex, chunkSize, chunkData, totalChunks, completedChunks) => {
354
- return new Promise((resolve, reject) => {
355
- this.clientTide.emitEvent(clientID, "client-to-client-transfer", {
356
- senderID: clientID,
357
- recipientId,
358
- fileName,
359
- fileChunk: chunkData,
360
- completedChunks,
361
- totalChunks,
362
- chunkIndex,
363
- chunkSize,
364
- filePath,
365
- fileSize
366
- });
367
- resolve();
368
- });
369
- },
370
- onComplete: () => {
371
- return new Promise((resolve, reject) => {
372
- FileTransferRecoveryManager.clearTransferState(clientID, recipientId, `${filePath}-${fileName}`);
373
- this.clientTide.emitEvent(clientID, "transfer-complete", {
374
- senderID: clientID,
375
- recipientId,
376
- fileName,
377
- filePath
378
- });
379
- resolve();
380
- });
381
- },
382
- originDetails: {
383
- recipientID: recipientId,
384
- senderID: clientID
385
- },
386
- fileDetails: {
387
- destinationPath,
388
- name: fileName,
389
- path: filePath,
390
- size: fileSize
391
- }
392
- }, this.clientTide).then(() => FileNetUtilityManager.outputGradient(`[FileTide ~ File Messager] ~ File ~ ${fileName} transfer complete!`, significantMessageColors)).catch(error => FileNetUtilityManager.outputGradient(`[FileTide ~ File Messager] ~ Refused file transfer ~ ${error}`, errorMessageColors));
393
- }
394
308
  }
395
309
  ;
396
310
  module.exports = FileMessagerClient;
@@ -176,50 +176,52 @@ class FileTide {
176
176
 
177
177
  /**
178
178
  * Send files / directories directly to a device.
179
- * @param {String} recipientID - The ID of the client device to send the data to
180
- * @param {Buffer} fileData - The path of the data to send
181
- * @param {String} destinationPath - The destination path on the device
182
- * @param {Array} filterDirectoryContent - List of paths to filter
183
- * @param {Number} tideSize - The tide size in sending files (500)
184
- * @param {Number} minTideSize - The minimum tide size in sending files (10)
185
- * @param {Number} maxTideSize - The maximum tide size in sending files (2000)
179
+ * @param {String} recipientID - The ID of the client device to send the data to.
180
+ * @param {Buffer} fileData - The path of the data to send.
181
+ * @param {String} destinationPath - The destination path on the device.
182
+ * @param {Array} filterDirectoryContent - List of paths to filter.
183
+ * @param {Number} tideSize - The tide size in sending smaller to medium files (200).
184
+ * @param {Number} tidalSize - The tide size in sending large files (500).
185
+ * @param {Number} minTideSize - The minimum tide size in sending files (10).
186
+ * @param {Number} maxTideSize - The maximum tide size in sending files (2000).
187
+ * @param {Number} transferRequestExpirationTime - The maximum time until the transfer barrier times out the transfer request.
186
188
  */
187
- async sendToDevice(recipientID, filePath, destinationPath, filterDirectoryContent = [], tideSize = 500, minTideSize = 10, maxTideSize = 2000) {
189
+ async sendToDevice(recipientID, filePath, destinationPath, filterDirectoryContent = [], tideSize = 200, tidalSize = 500, minTideSize = 10, maxTideSize = 2000, transferRequestExpirationTime = 30000) {
188
190
  const clientID = this.currentUserID;
189
191
  const clientMessager = this.clients.get(clientID);
190
192
  if (!clientMessager) {
191
193
  FileTide.outputGradient(`[FileTide] Client ~ ${clientID} not found.`, ["#F94144", "#F3722C"]);
192
- return;
194
+ return false;
193
195
  }
194
196
  const recipientOnline = await clientMessager.clientTide.emitEventWithResponse(clientID, "check-client-online", "retrieved-client-online", {
195
197
  clientID: recipientID
196
198
  }, 5000);
197
199
  if (!recipientOnline || !recipientOnline.online) {
198
200
  FileTide.outputGradient(`[FileTide] ${recipientID} currently not tiding.`, ["#F94144", "#F3722C"]);
199
- return;
201
+ return false;
200
202
  }
201
203
  const fileDetails = await this.getPathData(filePath, filterDirectoryContent);
202
204
  if (!fileDetails) {
203
205
  FileTide.outputGradient(`[${clientID}] File at path ${filePath} not found.`, ["#F94144", "#F3722C"]);
204
- return;
206
+ return false;
205
207
  }
206
208
  if (fileDetails.type === "file") {
207
209
  if (fileDetails.largeFile) {
208
210
  FileTide.outputGradient(`[${clientID}] Sending large file ~ ${fileDetails.fileName} to client ~ ${recipientID}...`, ["#00C897", "#00E0FF"]);
209
- await clientMessager.sendLargeFile(clientID, recipientID, fileDetails.fileName, fileDetails.filePath, destinationPath, fileDetails.fileSize, tideSize, minTideSize, maxTideSize);
210
- return;
211
+ await clientMessager.sendFile(clientID, recipientID, fileDetails.fileName, fileDetails.filePath, destinationPath, fileDetails.fileSize, tidalSize, minTideSize, maxTideSize, true, false, true, transferRequestExpirationTime);
212
+ return true;
211
213
  }
212
214
  FileTide.outputGradient(`[${clientID}] Sending file ~ ${fileDetails.content.fileName} to client ~ ${recipientID}...`, ["#00C897", "#00E0FF"]);
213
- clientMessager.sendFile(clientID, recipientID, fileDetails.content.fileName, fileDetails.content.fileData, destinationPath, fileDetails.content.size);
214
- return;
215
+ await clientMessager.sendFile(clientID, recipientID, fileDetails.content.fileName, fileDetails.content.filePath, destinationPath, fileDetails.content.size, tideSize, minTideSize, maxTideSize, transferRequestExpirationTime);
216
+ return true;
215
217
  }
216
218
  if (fileDetails.type === "directory") {
217
219
  FileTide.outputGradient(`[${clientID}] Sending ${fileDetails.content.length} files in directory ~ ${path.basename(filePath)} to client ~ ${recipientID}...`, ["#00C897", "#00E0FF"]);
218
- clientMessager.sendDirectoryFiles(clientID, recipientID, fileDetails.content, this.getTidePath(filePath), destinationPath, tideSize, minTideSize, maxTideSize);
219
- return;
220
+ await clientMessager.sendDirectoryFiles(clientID, recipientID, fileDetails.content, this.getTidePath(filePath), destinationPath, tideSize, minTideSize, maxTideSize, transferRequestExpirationTime);
221
+ return true;
220
222
  }
221
223
  FileTide.outputGradient(`[FileTide] ~ No active client ~ ${recipientID} found.`, errorMessageColors);
222
- return;
224
+ return false;
223
225
  }
224
226
 
225
227
  /**
@@ -24,10 +24,11 @@ class FileTransferManager {
24
24
  onSendChunk,
25
25
  onComplete,
26
26
  onProgress,
27
- onStart
27
+ onStart,
28
+ onTiding
28
29
  }, clientTide) {
29
30
  const transferId = _assertClassBrand(_FileTransferManager_brand, this, _generateTransferId).call(this, fileDetails);
30
- return await _assertClassBrand(_FileTransferManager_brand, this, _startTransfer).call(this, file, transferId, fileDetails, originDetails, onSendChunk, onComplete, onProgress, onStart, clientTide);
31
+ return await _assertClassBrand(_FileTransferManager_brand, this, _startTransfer).call(this, file, transferId, fileDetails, originDetails, onSendChunk, onComplete, onProgress, onStart, onTiding, clientTide);
31
32
  }
32
33
  async sendLargeFile(fileHandle, {
33
34
  fileDetails,
@@ -107,31 +108,36 @@ class FileTransferManager {
107
108
  const keepAliveInterval = setInterval(() => {
108
109
  if (onTiding) onTiding();
109
110
  }, 5000);
110
- try {
111
- let adaptiveBatchSize = 100;
112
- while (currentBatchStart < totalChunks) {
113
- if (transferState.activeChunks < this.parallelChunks) {
114
- adaptiveBatchSize = Math.min(adaptiveBatchSize + 10, 100);
115
- } else {
116
- adaptiveBatchSize = Math.max(adaptiveBatchSize - 10, 10);
111
+ return new Promise(async (resolve, reject) => {
112
+ try {
113
+ let adaptiveBatchSize = 100;
114
+ while (currentBatchStart < totalChunks) {
115
+ if (transferState.activeChunks < this.parallelChunks) {
116
+ adaptiveBatchSize = Math.min(adaptiveBatchSize + 10, 100);
117
+ } else {
118
+ adaptiveBatchSize = Math.max(adaptiveBatchSize - 10, 10);
119
+ }
120
+ await sendChunkBatch(currentBatchStart, adaptiveBatchSize);
121
+ currentBatchStart += adaptiveBatchSize;
122
+ await new Promise(resolve => setTimeout(resolve, 30));
123
+ }
124
+ } finally {
125
+ clearInterval(keepAliveInterval);
126
+ fileHandle.on("close", resolve);
127
+ fileHandle.on("error", error => {
128
+ this.activeTransfers.delete(transferId);
129
+ reject(error);
130
+ });
131
+ try {
132
+ await fileHandle.close();
133
+ } catch (error) {
134
+ console.error(`[FileTide ~ Transporter] ~ Error closing file handle ~ ${error.message}`);
117
135
  }
118
- await sendChunkBatch(currentBatchStart, adaptiveBatchSize);
119
- currentBatchStart += adaptiveBatchSize;
120
- await new Promise(resolve => setTimeout(resolve, 30));
121
136
  }
122
- } finally {
123
- clearInterval(keepAliveInterval);
124
- }
125
- return new Promise((resolve, reject) => {
126
- fileHandle.on("close", resolve);
127
- fileHandle.on("error", error => {
128
- this.activeTransfers.delete(transferId);
129
- reject(error);
130
- });
131
137
  });
132
138
  }
133
139
  }
134
- async function _startTransfer(file, transferId, fileDetails, originDetails, onSendChunk, onComplete, onProgress, onStart, clientTide) {
140
+ async function _startTransfer(file, transferId, fileDetails, originDetails, onSendChunk, onComplete, onProgress, onStart, onTiding, clientTide) {
135
141
  const recoveredTransferState = (await clientTide.emitEventWithResponse(originDetails.senderID, "get-client-transfer-state", "sent-current-transfer-state", {
136
142
  recipientID: originDetails.recipientID,
137
143
  senderID: originDetails.senderID,
@@ -142,6 +148,8 @@ async function _startTransfer(file, transferId, fileDetails, originDetails, onSe
142
148
  lastChunkSent: 0
143
149
  };
144
150
  const totalChunks = Math.ceil(file.length / this.chunkSize);
151
+ let nextExpectedChunk = recoveredTransferState.lastChunkSent;
152
+ const chunkBuffer = new Map();
145
153
  this.activeTransfers.set(transferId, {
146
154
  totalChunks,
147
155
  transferredSize: recoveredTransferState.transferredSize,
@@ -151,64 +159,187 @@ async function _startTransfer(file, transferId, fileDetails, originDetails, onSe
151
159
  currentChunk: recoveredTransferState.lastChunkSent,
152
160
  size: fileDetails.size
153
161
  });
162
+ const transferState = this.activeTransfers.get(transferId);
154
163
  const updateProgress = () => {
155
- const transferState = this.activeTransfers.get(transferId);
156
164
  if (onProgress) {
157
165
  onProgress(transferState.completedChunks / transferState.totalChunks * 100, this.chunkSize);
158
166
  }
159
167
  };
160
168
  const exponentialBackoff = retryCount => Math.min(1000 * 2 ** retryCount, 10000);
161
- return new Promise((resolve, reject) => {
162
- const sendChunk = async chunkIndex => {
163
- const transferState = this.activeTransfers.get(transferId);
164
- if (chunkIndex >= transferState.totalChunks) {
165
- return;
166
- }
167
- const start = chunkIndex * this.chunkSize;
168
- const end = Math.min(start + this.chunkSize, file.length);
169
- const chunk = file.slice(start, end);
170
- if (onStart && chunkIndex === 0) {
171
- onStart({
172
- name: fileDetails.name,
173
- path: fileDetails.path,
174
- size: fileDetails.size,
175
- totalChunks: transferState.totalChunks
176
- });
177
- }
178
- transferState.activeChunks++;
179
- try {
180
- await onSendChunk(transferId, chunkIndex, this.chunkSize, chunk, transferState.totalChunks, transferState.completedChunks + 1);
181
- transferState.transferredSize += this.chunkSize;
169
+ const sendChunk = async chunkIndex => {
170
+ const start = chunkIndex * this.chunkSize;
171
+ const end = Math.min(start + this.chunkSize, file.length);
172
+ const chunk = file.slice(start, end);
173
+ transferState.activeChunks++;
174
+ try {
175
+ await onSendChunk(transferId, chunkIndex, this.chunkSize, chunk, totalChunks, transferState.completedChunks + 1);
176
+ chunkBuffer.set(chunkIndex, chunk);
177
+ while (chunkBuffer.has(nextExpectedChunk)) {
178
+ chunkBuffer.delete(nextExpectedChunk);
179
+ nextExpectedChunk++;
182
180
  transferState.completedChunks++;
183
- transferState.activeChunks--;
184
181
  updateProgress();
185
- if (transferState.activeChunks < this.parallelChunks && transferState.currentChunk < transferState.totalChunks) {
186
- sendChunk(transferState.currentChunk++);
187
- }
188
- if (transferState.completedChunks === transferState.totalChunks) {
189
- await onComplete(transferId);
190
- this.activeTransfers.delete(transferId);
191
- resolve();
192
- }
193
- } catch (error) {
194
- console.error(`[FileTide ~ File Messager] ~ Did not send chunk ${chunkIndex} ~ ${error.message}`);
195
- transferState.activeChunks--;
196
- const retryCount = (transferState.failedChunks.get(chunkIndex) || 0) + 1;
197
- if (retryCount > this.maxRetries) {
198
- this.activeTransfers.delete(transferId);
199
- reject(new Error(`Did not send chunk ${chunkIndex} after ${this.maxRetries} retries.`));
200
- } else {
201
- transferState.failedChunks.set(chunkIndex, retryCount);
202
- setTimeout(() => sendChunk(chunkIndex), exponentialBackoff(retryCount));
203
- }
204
182
  }
205
- };
206
- const transferState = this.activeTransfers.get(transferId);
207
- for (let i = 0; i < Math.min(this.parallelChunks, transferState.totalChunks); i++) {
208
- sendChunk(transferState.currentChunk++);
183
+ if (transferState.completedChunks === transferState.totalChunks) {
184
+ clearInterval(keepAliveInterval);
185
+ await onComplete(transferId);
186
+ this.activeTransfers.delete(transferId);
187
+ }
188
+ } catch (error) {
189
+ console.error(`[FileTide ~ Transporter] ~ Did not send chunk ${chunkIndex} ~ ${error.message}`);
190
+ const retryCount = (transferState.failedChunks.get(chunkIndex) || 0) + 1;
191
+ if (retryCount > this.maxRetries) {
192
+ clearInterval(keepAliveInterval);
193
+ this.activeTransfers.delete(transferId);
194
+ throw new Error(`[FileTide ~ Transporter] ~ Did not send chunk ${chunkIndex} after ${this.maxRetries} retries.`);
195
+ }
196
+ transferState.failedChunks.set(chunkIndex, retryCount);
197
+ setTimeout(() => sendChunk(chunkIndex), exponentialBackoff(retryCount));
198
+ } finally {
199
+ transferState.activeChunks--;
209
200
  }
210
- });
201
+ };
202
+ const sendChunkBatch = async (startChunkIndex, batchSize = 100, miniBatchSize = 10) => {
203
+ for (let i = 0; i < batchSize && startChunkIndex + i < totalChunks; i += miniBatchSize) {
204
+ const miniBatchPromises = [];
205
+ for (let j = 0; j < miniBatchSize && startChunkIndex + i + j < totalChunks; j++) {
206
+ const chunkIndex = startChunkIndex + i + j;
207
+ miniBatchPromises.push(sendChunk(chunkIndex));
208
+ }
209
+ await Promise.all(miniBatchPromises);
210
+ await new Promise(resolve => setTimeout(resolve, 5));
211
+ }
212
+ };
213
+ const keepAliveInterval = setInterval(() => {
214
+ if (onTiding) onTiding();
215
+ }, 5000);
216
+ let currentBatchStart = transferState.currentChunk;
217
+ let adaptiveBatchSize = 100;
218
+ try {
219
+ while (currentBatchStart < totalChunks) {
220
+ adaptiveBatchSize = transferState.activeChunks < this.parallelChunks ? Math.min(adaptiveBatchSize + 10, 100) : Math.max(adaptiveBatchSize - 10, 10);
221
+ await sendChunkBatch(currentBatchStart, adaptiveBatchSize);
222
+ currentBatchStart += adaptiveBatchSize;
223
+ await new Promise(resolve => setTimeout(resolve, 30));
224
+ }
225
+ } finally {
226
+ clearInterval(keepAliveInterval);
227
+ }
211
228
  }
229
+ /*async #startTransfer(file, transferId, fileDetails, originDetails, onSendChunk, onComplete, onProgress, onStart, onTiding, clientTide)
230
+ {
231
+ const recoveredTransferState = await clientTide.emitEventWithResponse(originDetails.senderID, "get-client-transfer-state", "sent-current-transfer-state", { recipientID : originDetails.recipientID, senderID : originDetails.senderID, filePath : fileDetails.destinationPath, fileName : fileDetails.name }, 30000) || { transferredSize : 0, lastChunkSent : 0 };
232
+
233
+ const totalChunks = Math.ceil(file.length / this.chunkSize);
234
+
235
+ this.activeTransfers.set(transferId, {
236
+ totalChunks,
237
+ transferredSize : recoveredTransferState.transferredSize,
238
+ completedChunks : recoveredTransferState.lastChunkSent,
239
+ activeChunks : 0,
240
+ failedChunks : new Map(),
241
+ currentChunk : recoveredTransferState.lastChunkSent,
242
+ size : fileDetails.size
243
+ });
244
+ const updateProgress = () => {
245
+ const transferState = this.activeTransfers.get(transferId);
246
+
247
+ if (onProgress)
248
+ {
249
+ onProgress((transferState.completedChunks / transferState.totalChunks) * 100, this.chunkSize);
250
+ }
251
+ };
252
+
253
+ const keepAliveInterval = setInterval(() => {
254
+ if (onTiding) onTiding();
255
+ }, 5000);
256
+
257
+ const exponentialBackoff = (retryCount) => Math.min(1000 * (2 ** retryCount), 10000);
258
+
259
+ return new Promise((resolve, reject) => {
260
+ const sendChunk = async (chunkIndex) => {
261
+ const transferState = this.activeTransfers.get(transferId);
262
+
263
+ if (chunkIndex >= transferState.totalChunks) { return; }
264
+ const start = chunkIndex * this.chunkSize;
265
+
266
+ const end = Math.min(start + this.chunkSize, file.length);
267
+
268
+ const chunk = file.slice(start, end);
269
+
270
+ if (onStart && chunkIndex === 0)
271
+ {
272
+ onStart({
273
+ name : fileDetails.name,
274
+ path : fileDetails.path,
275
+ size : fileDetails.size,
276
+ totalChunks : transferState.totalChunks
277
+ });
278
+ }
279
+
280
+ transferState.activeChunks++;
281
+
282
+ try
283
+ {
284
+ await onSendChunk(transferId, chunkIndex, this.chunkSize, chunk, transferState.totalChunks, transferState.completedChunks + 1);
285
+
286
+ transferState.transferredSize += this.chunkSize;
287
+
288
+ transferState.completedChunks++;
289
+
290
+ transferState.activeChunks--;
291
+
292
+ updateProgress();
293
+
294
+ if (transferState.activeChunks < this.parallelChunks && transferState.currentChunk < transferState.totalChunks)
295
+ {
296
+ sendChunk(transferState.currentChunk++);
297
+ }
298
+
299
+ if (transferState.completedChunks === transferState.totalChunks)
300
+ {
301
+ await onComplete(transferId);
302
+
303
+ this.activeTransfers.delete(transferId);
304
+
305
+ clearInterval(keepAliveInterval);
306
+
307
+ resolve();
308
+ }
309
+ }
310
+ catch (error)
311
+ {
312
+ console.error(`[FileTide ~ File Messager] ~ Did not send chunk ${chunkIndex} ~ ${error.message}`);
313
+
314
+ transferState.activeChunks--;
315
+
316
+ const retryCount = (transferState.failedChunks.get(chunkIndex) || 0) + 1;
317
+
318
+ if (retryCount > this.maxRetries)
319
+ {
320
+ this.activeTransfers.delete(transferId);
321
+
322
+ clearInterval(keepAliveInterval);
323
+
324
+ reject(new Error(`Did not send chunk ${chunkIndex} after ${this.maxRetries} retries.`));
325
+ }
326
+ else
327
+ {
328
+ transferState.failedChunks.set(chunkIndex, retryCount);
329
+
330
+ setTimeout(() => sendChunk(chunkIndex), exponentialBackoff(retryCount));
331
+ }
332
+ }
333
+ };
334
+
335
+ const transferState = this.activeTransfers.get(transferId);
336
+
337
+ for (let i = 0; i < Math.min(this.parallelChunks, transferState.totalChunks); i++)
338
+ {
339
+ sendChunk(transferState.currentChunk++);
340
+ }
341
+ });
342
+ }*/
212
343
  function _generateTransferId(file) {
213
344
  return `${file.name}-${Date.now()}`;
214
345
  }
@@ -211,6 +211,19 @@ class FileUtilityManager {
211
211
  return stats.isDirectory();
212
212
  }
213
213
 
214
+ /**
215
+ * Determines the type of a given path (directory or file) based on conventions.
216
+ * @param {string} inputPath - The path to infer.
217
+ * @returns {string} - Returns "directory" if the path has no extension, "file" if it contains an extension.
218
+ */
219
+ static inferPathType(inputPath) {
220
+ const parsedPath = path.parse(inputPath);
221
+ if (parsedPath.ext) {
222
+ return "file";
223
+ }
224
+ return "directory";
225
+ }
226
+
214
227
  /**
215
228
  * Helper function to check if a path exists.
216
229
  * @param {string} inputPath - The path to check.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trap_stevo/filetide",
3
- "version": "0.0.46",
3
+ "version": "0.0.48",
4
4
  "description": "Revolutionizing real-time file transfer with seamless, instant communication across any device. Deliver files instantly, regardless of platform, and experience unparalleled speed and control in managing transfers. Elevate your file-sharing capabilities with a tool designed for precision, efficiency, and effortless connectivity.",
5
5
  "main": "dist/cjs/FileTide.js",
6
6
  "scripts": {