@trap_stevo/filetide 0.0.84 → 0.0.86

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.
@@ -4,18 +4,43 @@ function _classPrivateMethodInitSpec(e, a) { _checkPrivateRedeclaration(e, a), a
4
4
  function _checkPrivateRedeclaration(e, t) { if (t.has(e)) throw new TypeError("Cannot initialize the same private elements twice on an object"); }
5
5
  function _assertClassBrand(e, t, n) { if ("function" == typeof e ? e === t : e.has(t)) return arguments.length < 3 ? t : n; throw new TypeError("Private element is not present on this object"); }
6
6
  const fs = require("fs");
7
+ const {
8
+ FileTideNetworkTrafficManager
9
+ } = require("./FileTideNetworkTrafficManager.js");
7
10
  var _FileTransferManager_brand = /*#__PURE__*/new WeakSet();
8
11
  class FileTransferManager {
9
12
  constructor({
10
- chunkSize = 512 * 1024,
11
- parallelChunks = 5,
12
- maxRetries = 3
13
+ chunkSize: _chunkSize = 512 * 1024,
14
+ parallelChunks = 3,
15
+ maxRetries = 3,
16
+ networkTraffic = {}
13
17
  } = {}) {
14
18
  _classPrivateMethodInitSpec(this, _FileTransferManager_brand);
15
- this.parallelChunks = parallelChunks;
19
+ this.parallelChunks = Math.max(1, parallelChunks);
16
20
  this.activeTransfers = new Map();
17
21
  this.maxRetries = maxRetries;
18
- this.chunkSize = chunkSize;
22
+ this.chunkSize = _chunkSize;
23
+ this.networkTraffic = new FileTideNetworkTrafficManager({
24
+ defaultBandwidthBps: 32 * 1024 * 1024,
25
+ minimumBandwidthBps: 512 * 1024,
26
+ targetUtilization: 0.90,
27
+ congestedUtilizationFactor: 0.60,
28
+ targetChunkDurationMs: 250,
29
+ minChunkSize: _chunkSize,
30
+ maxChunkSize: _chunkSize,
31
+ burstSeconds: 0.20,
32
+ congestedLatencyMs: 250,
33
+ unstableLatencyMs: 1000,
34
+ congestedFailureRate: 0.10,
35
+ unstableFailureRate: 0.30,
36
+ sampleWindowSize: 20,
37
+ smoothingFactor: 0.25,
38
+ throughputProbeIntervalMs: 750,
39
+ throughputProbeGrowthFactor: 1.18,
40
+ healthyThroughputRatio: 0.90,
41
+ degradedThroughputRatio: 0.72,
42
+ ...networkTraffic
43
+ });
19
44
  }
20
45
  async sendFile(file, {
21
46
  fileDetails,
@@ -35,6 +60,7 @@ class FileTransferManager {
35
60
  recipientID,
36
61
  onSendChunk,
37
62
  onComplete,
63
+ onFailure,
38
64
  onProgress,
39
65
  onStart,
40
66
  onTiding
@@ -50,8 +76,8 @@ class FileTransferManager {
50
76
  currentChunk: 0,
51
77
  size: fileDetails.size
52
78
  });
79
+ const transferState = this.activeTransfers.get(transferId);
53
80
  const updateProgress = () => {
54
- const transferState = this.activeTransfers.get(transferId);
55
81
  if (onProgress) {
56
82
  onProgress(transferState.completedChunks / transferState.totalChunks * 100, this.chunkSize);
57
83
  }
@@ -62,31 +88,34 @@ class FileTransferManager {
62
88
  const sendChunk = async chunkIndex => {
63
89
  const start = chunkIndex * this.chunkSize;
64
90
  const end = Math.min(start + this.chunkSize, fileDetails.size);
91
+ const actualChunkSize = end - start;
92
+ await _assertClassBrand(_FileTransferManager_brand, this, _waitForChunkAdmission).call(this, actualChunkSize);
65
93
  const {
66
94
  buffer
67
- } = await fileHandle.read(Buffer.alloc(end - start), 0, end - start, start);
68
- const transferState = this.activeTransfers.get(transferId);
95
+ } = await fileHandle.read(Buffer.alloc(actualChunkSize), 0, actualChunkSize, start);
96
+ transferState.activeChunks++;
69
97
  chunkBuffer.set(chunkIndex, buffer);
70
98
  try {
71
99
  while (chunkBuffer.has(nextExpectedChunk)) {
72
100
  const nextChunkBuffer = chunkBuffer.get(nextExpectedChunk);
73
- await onSendChunk(transferId, nextExpectedChunk, this.chunkSize, nextChunkBuffer, totalChunks, this.activeTransfers.get(transferId).completedChunks + 1);
101
+ const nextChunkSize = nextChunkBuffer.length;
102
+ await onSendChunk(transferId, nextExpectedChunk, nextChunkSize, nextChunkBuffer, totalChunks, transferState.completedChunks + 1);
74
103
  chunkBuffer.delete(nextExpectedChunk);
75
104
  nextExpectedChunk++;
76
- transferState.transferredSize += this.chunkSize;
105
+ transferState.transferredSize += nextChunkSize;
77
106
  transferState.completedChunks++;
78
107
  updateProgress();
79
108
  }
80
109
  if (transferState.completedChunks === transferState.totalChunks) {
81
- return onComplete(transferId).then(() => {
82
- this.activeTransfers.delete(transferId);
83
- });
110
+ if (onComplete) {
111
+ await onComplete(transferId);
112
+ }
84
113
  }
85
114
  } catch (error) {
86
115
  console.error(`[FileTide ~ Transporter] ~ Did not send chunk ${chunkIndex} ~ ${error.message}`);
87
- return onComplete(transferId).then(() => {
88
- this.activeTransfers.delete(transferId);
89
- });
116
+ throw error;
117
+ } finally {
118
+ transferState.activeChunks--;
90
119
  }
91
120
  };
92
121
  const sendChunkBatch = async (startChunkIndex, batchSize = 100, miniBatchSize = 10) => {
@@ -97,41 +126,51 @@ class FileTransferManager {
97
126
  miniBatchPromises.push(sendChunk(chunkIndex));
98
127
  }
99
128
  await Promise.all(miniBatchPromises);
100
- await new Promise(resolve => setTimeout(resolve, 5));
129
+ await new Promise(resolve => setImmediate(resolve));
101
130
  }
102
131
  };
103
- const transferState = this.activeTransfers.get(transferId);
104
132
  let currentBatchStart = transferState.currentChunk;
105
133
  const keepAliveInterval = setInterval(() => {
106
- if (onTiding) onTiding();
134
+ if (onTiding) {
135
+ onTiding();
136
+ }
107
137
  }, 5000);
108
- return new Promise(async (resolve, reject) => {
109
- try {
110
- let adaptiveBatchSize = 100;
111
- while (currentBatchStart < totalChunks) {
112
- if (transferState.activeChunks < this.parallelChunks) {
113
- adaptiveBatchSize = Math.min(adaptiveBatchSize + 10, 100);
114
- } else {
115
- adaptiveBatchSize = Math.max(adaptiveBatchSize - 10, 10);
116
- }
117
- await sendChunkBatch(currentBatchStart, adaptiveBatchSize);
118
- currentBatchStart += adaptiveBatchSize;
119
- await new Promise(resolve => setTimeout(resolve, 30));
120
- }
121
- } finally {
122
- clearInterval(keepAliveInterval);
123
- fileHandle.on("close", resolve);
124
- fileHandle.on("error", error => {
125
- this.activeTransfers.delete(transferId);
126
- reject(error);
127
- });
128
- try {
129
- await fileHandle.close();
130
- } catch (error) {
131
- console.error(`[FileTide ~ Transporter] ~ Error closing file handle ~ ${error.message}`);
138
+ try {
139
+ let adaptiveBatchSize = 100;
140
+ while (currentBatchStart < totalChunks) {
141
+ if (transferState.activeChunks < this.parallelChunks) {
142
+ adaptiveBatchSize = Math.min(adaptiveBatchSize + 10, 100);
143
+ } else {
144
+ adaptiveBatchSize = Math.max(adaptiveBatchSize - 10, 10);
132
145
  }
146
+ await sendChunkBatch(currentBatchStart, adaptiveBatchSize);
147
+ currentBatchStart += adaptiveBatchSize;
148
+ await new Promise(resolve => setImmediate(resolve));
133
149
  }
134
- });
150
+ } catch (error) {
151
+ if (onFailure) {
152
+ await onFailure(transferId, error);
153
+ }
154
+ throw error;
155
+ } finally {
156
+ clearInterval(keepAliveInterval);
157
+ this.activeTransfers.delete(transferId);
158
+ }
159
+ }
160
+ getNetworkTrafficStatus() {
161
+ return this.networkTraffic.getStatus();
162
+ }
163
+ updateNetworkTraffic(sample = {}) {
164
+ return this.networkTraffic.updateNetworkSample(sample);
165
+ }
166
+ recordNetworkThroughput(throughputBps) {
167
+ return this.networkTraffic.recordDeliveredThroughput(throughputBps);
168
+ }
169
+ pauseTransfers(reason = "requested") {
170
+ return this.networkTraffic.pause(reason);
171
+ }
172
+ resumeTransfers(reason = "requested") {
173
+ return this.networkTraffic.resume(reason);
135
174
  }
136
175
  }
137
176
  async function _startTransfer(file, transferId, fileDetails, originDetails, onSendChunk, onComplete, onProgress, onStart, onTiding, clientTide) {
@@ -158,9 +197,11 @@ async function _startTransfer(file, transferId, fileDetails, originDetails, onSe
158
197
  const start = chunkIndex * this.chunkSize;
159
198
  const end = Math.min(start + this.chunkSize, file.length);
160
199
  const chunk = file.slice(start, end);
200
+ const actualChunkSize = chunk.length;
201
+ await _assertClassBrand(_FileTransferManager_brand, this, _waitForChunkAdmission).call(this, actualChunkSize);
161
202
  transferState.activeChunks++;
162
203
  try {
163
- await onSendChunk(transferId, chunkIndex, this.chunkSize, chunk, totalChunks, transferState.completedChunks + 1);
204
+ await onSendChunk(transferId, chunkIndex, actualChunkSize, chunk, totalChunks, transferState.completedChunks + 1);
164
205
  chunkBuffer.set(chunkIndex, chunk);
165
206
  while (chunkBuffer.has(nextExpectedChunk)) {
166
207
  chunkBuffer.delete(nextExpectedChunk);
@@ -214,121 +255,13 @@ async function _startTransfer(file, transferId, fileDetails, originDetails, onSe
214
255
  clearInterval(keepAliveInterval);
215
256
  }
216
257
  }
217
- /*async #startTransfer(file, transferId, fileDetails, originDetails, onSendChunk, onComplete, onProgress, onStart, onTiding, clientTide)
218
- {
219
- const totalChunks = Math.ceil(file.length / this.chunkSize);
220
-
221
- this.activeTransfers.set(transferId, {
222
- totalChunks,
223
- transferredSize : 0,
224
- completedChunks : 0,
225
- activeChunks : 0,
226
- failedChunks : new Map(),
227
- currentChunk : 0,
228
- size : fileDetails.size
229
- });
230
- const updateProgress = () => {
231
- const transferState = this.activeTransfers.get(transferId);
232
-
233
- if (onProgress)
234
- {
235
- onProgress((transferState.completedChunks / transferState.totalChunks) * 100, this.chunkSize);
236
- }
237
- };
238
-
239
- const keepAliveInterval = setInterval(() => {
240
- if (onTiding) onTiding();
241
- }, 5000);
242
-
243
- const exponentialBackoff = (retryCount) => Math.min(1000 * (2 ** retryCount), 10000);
244
-
245
- return new Promise((resolve, reject) => {
246
- const sendChunk = async (chunkIndex) => {
247
- const transferState = this.activeTransfers.get(transferId);
248
-
249
- if (chunkIndex >= transferState.totalChunks) { return; }
250
- const start = chunkIndex * this.chunkSize;
251
-
252
- const end = Math.min(start + this.chunkSize, file.length);
253
-
254
- const chunk = file.slice(start, end);
255
-
256
- if (onStart && chunkIndex === 0)
257
- {
258
- onStart({
259
- name : fileDetails.name,
260
- path : fileDetails.path,
261
- size : fileDetails.size,
262
- totalChunks : transferState.totalChunks
263
- });
264
- }
265
-
266
- transferState.activeChunks++;
267
-
268
- try
269
- {
270
- await onSendChunk(transferId, chunkIndex, this.chunkSize, chunk, transferState.totalChunks, transferState.completedChunks + 1);
271
-
272
- transferState.transferredSize += this.chunkSize;
273
-
274
- transferState.completedChunks++;
275
-
276
- transferState.activeChunks--;
277
-
278
- updateProgress();
279
-
280
- if (transferState.activeChunks < this.parallelChunks && transferState.currentChunk < transferState.totalChunks)
281
- {
282
- sendChunk(transferState.currentChunk++);
283
- }
284
-
285
- if (transferState.completedChunks === transferState.totalChunks)
286
- {
287
- await onComplete(transferId);
288
-
289
- this.activeTransfers.delete(transferId);
290
-
291
- clearInterval(keepAliveInterval);
292
-
293
- resolve();
294
- }
295
- }
296
- catch (error)
297
- {
298
- console.error(`[FileTide ~ File Messager] ~ Did not send chunk ${chunkIndex} ~ ${error.message}`);
299
-
300
- transferState.activeChunks--;
301
-
302
- const retryCount = (transferState.failedChunks.get(chunkIndex) || 0) + 1;
303
-
304
- if (retryCount > this.maxRetries)
305
- {
306
- this.activeTransfers.delete(transferId);
307
-
308
- clearInterval(keepAliveInterval);
309
-
310
- reject(new Error(`Did not send chunk ${chunkIndex} after ${this.maxRetries} retries.`));
311
- }
312
- else
313
- {
314
- transferState.failedChunks.set(chunkIndex, retryCount);
315
-
316
- setTimeout(() => sendChunk(chunkIndex), exponentialBackoff(retryCount));
317
- }
318
- }
319
- };
320
-
321
- const transferState = this.activeTransfers.get(transferId);
322
-
323
- for (let i = 0; i < Math.min(this.parallelChunks, transferState.totalChunks); i++)
324
- {
325
- sendChunk(transferState.currentChunk++);
326
- }
327
- });
328
- }*/
329
258
  function _generateTransferId(file) {
330
259
  return `${file.name}-${Date.now()}`;
331
260
  }
261
+ function _waitForChunkAdmission(chunkSize) {
262
+ return this.networkTraffic.waitForChunkAdmission(chunkSize);
263
+ }
264
+ ;
332
265
  module.exports = {
333
266
  FileTransferManager
334
267
  };
@@ -1,5 +1,8 @@
1
1
  "use strict";
2
2
 
3
+ function _classPrivateMethodInitSpec(e, a) { _checkPrivateRedeclaration(e, a), a.add(e); }
4
+ function _checkPrivateRedeclaration(e, t) { if (t.has(e)) throw new TypeError("Cannot initialize the same private elements twice on an object"); }
5
+ function _assertClassBrand(e, t, n) { if ("function" == typeof e ? e === t : e.has(t)) return arguments.length < 3 ? t : n; throw new TypeError("Private element is not present on this object"); }
3
6
  const path = require("path");
4
7
  const fs = require("fs");
5
8
  const {
@@ -11,14 +14,18 @@ const {
11
14
  const {
12
15
  FileUtilityManager
13
16
  } = require("./FileUtilityManager.js");
17
+ var _FileTransferReceiverManager_brand = /*#__PURE__*/new WeakSet();
14
18
  class FileTransferReceiverManager {
15
- constructor() {}
19
+ constructor() {
20
+ _classPrivateMethodInitSpec(this, _FileTransferReceiverManager_brand);
21
+ this.tideQueues = new Map();
22
+ }
16
23
  async initializeTransfer(data, onError) {
17
24
  const savePath = FileUtilityManager.getTidePath(path.join(data.path || data.filePath, data.fileName));
18
25
  FileTideDebugUtilityManager.log("notice", true, `Initializing transfer ~ ${data.fileName} | ${savePath}...`);
19
26
  if (FileTransferTrackingManager.containsTransfer(data.fileName)) {
20
27
  FileTideDebugUtilityManager.log("notice", true, `Transfer ~ ${data.fileName} | ${savePath} already initialized.`);
21
- return;
28
+ return FileTransferTrackingManager.getTransfer(data.fileName);
22
29
  }
23
30
  try {
24
31
  const dir = path.dirname(savePath);
@@ -31,29 +38,70 @@ class FileTransferReceiverManager {
31
38
  flags: "w"
32
39
  });
33
40
  FileTransferTrackingManager.trackTransfer(data, fileStream);
41
+ const transferState = FileTransferTrackingManager.getTransfer(data.fileName);
42
+ if (transferState && transferState.highestContiguousChunk === undefined) {
43
+ transferState.highestContiguousChunk = -1;
44
+ }
34
45
  FileTideDebugUtilityManager.log("notice", true, `Initialized transfer ~ ${data.fileName} | ${savePath}!`);
46
+ return transferState;
35
47
  } catch (error) {
36
48
  FileTideDebugUtilityManager.log("error", true, `Did not initialize transfer ~ ${data.fileName} | ${savePath}.\n\t\t${error}`);
37
49
  if (onError) {
38
50
  onError(`[${data.fileName}] Did not initialize transfer ~ ${error.message}`);
39
51
  }
52
+ throw error;
40
53
  }
41
54
  }
42
55
  async handleTransferProgress(data, onError) {
43
56
  let transferState = FileTransferTrackingManager.getTransfer(data.fileName);
44
57
  if (!transferState) {
45
- await this.initializeTransfer(data);
46
- transferState = tFileTransferTrackingManager.getTransfer(data.fileName);
58
+ transferState = await this.initializeTransfer(data, onError);
59
+ }
60
+ if (transferState.highestContiguousChunk === undefined) {
61
+ transferState.highestContiguousChunk = -1;
47
62
  }
48
- const chunkBuffer = Buffer.from(data.fileChunk);
49
63
  try {
50
- transferState.fileStream.write(chunkBuffer);
51
- transferState.receivedChunks.add(data.chunkIndex);
64
+ const expectedChunkIndex = transferState.highestContiguousChunk + 1;
65
+ if (data.chunkIndex !== expectedChunkIndex) {
66
+ throw new Error(`[${data.fileName}] Out-of-order chunk. Expected ${expectedChunkIndex}, received ${data.chunkIndex}.`);
67
+ }
68
+ await _assertClassBrand(_FileTransferReceiverManager_brand, this, _writeChunk).call(this, transferState, data);
69
+ return {
70
+ highestContiguousChunk: transferState.highestContiguousChunk,
71
+ receivedChunks: transferState.receivedChunks.size,
72
+ totalChunks: transferState.totalChunks
73
+ };
52
74
  } catch (error) {
53
75
  FileTideDebugUtilityManager.log("error", true, `Did not write chunk ${data.chunkIndex} ~ ${data.fileName}.\n\t\t${error}`);
54
76
  if (onError) {
55
77
  onError(`[${data.fileName}] Did not write chunk ${data.chunkIndex} ~ ${error.message}`);
56
78
  }
79
+ throw error;
80
+ }
81
+ }
82
+ async handleTransferTide(tide, onError) {
83
+ const transferKey = tide?.transferId || tide?.fileName;
84
+ if (!transferKey) {
85
+ throw new Error("Received Tide without a transfer identifier.");
86
+ }
87
+ const previousTide = this.tideQueues.get(transferKey) || Promise.resolve();
88
+
89
+ /*
90
+ * Serialize Tides for the same transfer, but do not allow one
91
+ * rejected Tide promise to poison the queue chain itself. The
92
+ * next Tide still runs its own validation against the authoritative
93
+ * contiguous receiver state and will fail explicitly if a gap remains.
94
+ */
95
+ const currentTide = previousTide.catch(() => undefined).then(() => {
96
+ return _assertClassBrand(_FileTransferReceiverManager_brand, this, _processTransferTide).call(this, tide, onError);
97
+ });
98
+ this.tideQueues.set(transferKey, currentTide);
99
+ try {
100
+ return await currentTide;
101
+ } finally {
102
+ if (this.tideQueues.get(transferKey) === currentTide) {
103
+ this.tideQueues.delete(transferKey);
104
+ }
57
105
  }
58
106
  }
59
107
  async completeTransfer(data, onComplete) {
@@ -67,8 +115,18 @@ class FileTransferReceiverManager {
67
115
  try {
68
116
  FileTideDebugUtilityManager.log("notice", true, `Verifying transfer state ~ ${data.fileName} completion (${transferState.receivedChunks.size}/${transferState.totalChunks})...`);
69
117
  FileTideDebugUtilityManager.log("notice", true, `Transfer state ~ ${data.fileName} completed ~ (${transferState.receivedChunks.size === transferState.totalChunks}).`);
70
- if (transferState.receivedChunks.size === transferState.totalChunks) {
71
- transferState.fileStream.end();
118
+ if (transferState.receivedChunks.size === transferState.totalChunks && transferState.highestContiguousChunk === transferState.totalChunks - 1) {
119
+ await new Promise((resolve, reject) => {
120
+ const onError = error => {
121
+ transferState.fileStream.removeListener("error", onError);
122
+ reject(error);
123
+ };
124
+ transferState.fileStream.once("error", onError);
125
+ transferState.fileStream.end(() => {
126
+ transferState.fileStream.removeListener("error", onError);
127
+ resolve();
128
+ });
129
+ });
72
130
  console.log(`[${data.fileName}] Transfer complete. All chunks received and file written.`);
73
131
  FileTransferTrackingManager.untrackTransfer(data.fileName);
74
132
  FileTideDebugUtilityManager.log("notice", true, `Cleared transfer state ~ ${data.fileName}.`);
@@ -86,6 +144,108 @@ class FileTransferReceiverManager {
86
144
  }
87
145
  }
88
146
  }
147
+ async function _waitForDrain(fileStream) {
148
+ return await new Promise((resolve, reject) => {
149
+ const onDrain = () => {
150
+ cleanup();
151
+ resolve();
152
+ };
153
+ const onError = error => {
154
+ cleanup();
155
+ reject(error);
156
+ };
157
+ const cleanup = () => {
158
+ fileStream.removeListener("drain", onDrain);
159
+ fileStream.removeListener("error", onError);
160
+ };
161
+ fileStream.once("drain", onDrain);
162
+ fileStream.once("error", onError);
163
+ });
164
+ }
165
+ async function _writeChunk(transferState, data) {
166
+ const chunkBuffer = Buffer.isBuffer(data.fileChunk) ? data.fileChunk : Buffer.from(data.fileChunk);
167
+ const canContinue = transferState.fileStream.write(chunkBuffer);
168
+ if (!canContinue) {
169
+ await _assertClassBrand(_FileTransferReceiverManager_brand, this, _waitForDrain).call(this, transferState.fileStream);
170
+ }
171
+ transferState.receivedChunks.add(data.chunkIndex);
172
+ while (transferState.receivedChunks.has(transferState.highestContiguousChunk + 1)) {
173
+ transferState.highestContiguousChunk++;
174
+ }
175
+ }
176
+ async function _processTransferTide(tide, onError) {
177
+ const {
178
+ transferId,
179
+ senderID,
180
+ recipientId,
181
+ fileName,
182
+ filePath,
183
+ fileSize,
184
+ totalChunks,
185
+ firstChunkIndex,
186
+ lastChunkIndex,
187
+ chunks = [],
188
+ metadata = {}
189
+ } = tide;
190
+ if (!Array.isArray(chunks) || chunks.length <= 0) {
191
+ throw new Error(`[${fileName}] Received an empty Tide.`);
192
+ }
193
+ if (firstChunkIndex !== chunks[0].chunkIndex || lastChunkIndex !== chunks[chunks.length - 1].chunkIndex) {
194
+ throw new Error(`[${fileName}] Tide boundary metadata does not match its chunks.`);
195
+ }
196
+ let expectedChunkIndex = firstChunkIndex;
197
+ for (const chunk of chunks) {
198
+ if (chunk.chunkIndex !== expectedChunkIndex) {
199
+ throw new Error(`[${fileName}] Tide ordering violation. Expected chunk ${expectedChunkIndex}, received ${chunk.chunkIndex}.`);
200
+ }
201
+ expectedChunkIndex++;
202
+ }
203
+ let transferState = FileTransferTrackingManager.getTransfer(fileName);
204
+ if (!transferState) {
205
+ transferState = await this.initializeTransfer({
206
+ transferId,
207
+ senderID,
208
+ recipientId,
209
+ fileName,
210
+ filePath,
211
+ path: filePath,
212
+ fileSize,
213
+ totalChunks,
214
+ chunkSize: chunks[0].chunkSize,
215
+ metadata
216
+ }, onError);
217
+ }
218
+ const expectedTideStart = transferState.highestContiguousChunk + 1;
219
+ if (firstChunkIndex !== expectedTideStart) {
220
+ throw new Error(`[${fileName}] Tide ordering violation. Expected Tide to begin at chunk ${expectedTideStart}, received ${firstChunkIndex}.`);
221
+ }
222
+ let tideBytes = 0;
223
+ for (const chunk of chunks) {
224
+ await _assertClassBrand(_FileTransferReceiverManager_brand, this, _writeChunk).call(this, transferState, {
225
+ fileName,
226
+ fileChunk: chunk.fileChunk,
227
+ chunkIndex: chunk.chunkIndex
228
+ });
229
+ tideBytes += chunk.chunkSize;
230
+ }
231
+ const missingChunks = [];
232
+ for (let index = firstChunkIndex; index <= lastChunkIndex; index++) {
233
+ if (!transferState.receivedChunks.has(index)) {
234
+ missingChunks.push(index);
235
+ }
236
+ }
237
+ return {
238
+ transferId,
239
+ firstChunkIndex,
240
+ lastChunkIndex,
241
+ highestContiguousChunk: transferState.highestContiguousChunk,
242
+ tideChunkCount: chunks.length,
243
+ tideBytes,
244
+ completedChunks: transferState.highestContiguousChunk + 1,
245
+ totalChunks: transferState.totalChunks,
246
+ missingChunks
247
+ };
248
+ }
89
249
  module.exports = {
90
250
  FileTransferReceiverManager
91
251
  };