@yetech/estra7ah 3.3.1 → 3.3.2

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.
Files changed (2) hide show
  1. package/dist/estra7ah.js +77 -8
  2. package/package.json +1 -1
package/dist/estra7ah.js CHANGED
@@ -51,17 +51,46 @@ function downloadRange(url, fd, start, end, progressBar, attempt = 0) {
51
51
  .then(
52
52
  (response) =>
53
53
  new Promise((resolve, reject) => {
54
+ // A 200 means the server (or a proxy on this plain-HTTP route) ignored
55
+ // the Range header and is sending the whole file: writing that at six
56
+ // different offsets corrupts the binary. Bail out to single-stream.
57
+ if (response.status !== 206) {
58
+ response.data.destroy();
59
+ return reject({ noRangeSupport: true });
60
+ }
54
61
  let position = start;
62
+ // With timeout: 0 a dead connection would hang the update forever
63
+ // (progress stuck at 0%). Kill the stream if no data arrives for 30s
64
+ // so the retry/fallback path can take over.
65
+ let watchdog;
66
+ const resetWatchdog = () => {
67
+ clearTimeout(watchdog);
68
+ watchdog = setTimeout(() => {
69
+ response.data.destroy(new Error("download stalled: no data for 30s"));
70
+ }, 30000);
71
+ };
72
+ resetWatchdog();
73
+ response.data.on("close", () => clearTimeout(watchdog));
55
74
  response.data.on("data", (chunk) => {
75
+ resetWatchdog();
56
76
  fs.writeSync(fd, chunk, 0, chunk.length, position);
57
77
  position += chunk.length;
58
78
  progressBar.tick(chunk.length);
59
79
  });
60
- response.data.on("end", () => resolve(position));
80
+ response.data.on("end", () => {
81
+ // "end" also fires on some premature disconnects; an incomplete
82
+ // chunk must be retried, never treated as success.
83
+ if (position !== end + 1) {
84
+ return reject({ err: new Error(`chunk ended at ${position}, expected ${end + 1}`), position });
85
+ }
86
+ resolve(position);
87
+ });
88
+ response.data.on("aborted", () => reject({ err: new Error("connection aborted"), position }));
61
89
  response.data.on("error", (err) => reject({ err, position }));
62
90
  })
63
91
  )
64
92
  .catch((failure) => {
93
+ if (failure && failure.noRangeSupport) throw failure;
65
94
  const resumeAt = failure.position || start;
66
95
  if (attempt < 2) return downloadRange(url, fd, resumeAt, end, progressBar, attempt + 1);
67
96
  throw failure.err || failure;
@@ -103,10 +132,17 @@ async function downloadFileFast(url, destPath) {
103
132
  await Promise.all(jobs);
104
133
  } catch (error) {
105
134
  fs.closeSync(fd);
106
- fs.unlinkSync(tmpPath);
135
+ try {
136
+ fs.unlinkSync(tmpPath);
137
+ } catch (e) {}
138
+ if (error && error.noRangeSupport) return downloadFileStream(url, destPath);
107
139
  throw error;
108
140
  }
109
141
  fs.closeSync(fd);
142
+ if (fs.statSync(tmpPath).size !== totalLength) {
143
+ fs.unlinkSync(tmpPath);
144
+ throw new Error("downloaded size does not match content-length");
145
+ }
110
146
  fs.renameSync(tmpPath, destPath);
111
147
  }
112
148
 
@@ -121,12 +157,40 @@ function downloadFileStream(url, destPath) {
121
157
  total: totalLength,
122
158
  })
123
159
  : null;
124
- const writer = fs.createWriteStream(destPath);
160
+ const tmpPath = `${destPath}.download`;
161
+ const writer = fs.createWriteStream(tmpPath);
125
162
  return new Promise((resolve, reject) => {
126
- if (progressBar) response.data.on("data", (chunk) => progressBar.tick(chunk.length));
127
- writer.on("error", reject);
128
- writer.on("finish", resolve);
129
- response.data.on("error", reject);
163
+ let watchdog;
164
+ const fail = (err) => {
165
+ clearTimeout(watchdog);
166
+ writer.destroy();
167
+ try {
168
+ fs.unlinkSync(tmpPath);
169
+ } catch (e) {}
170
+ reject(err);
171
+ };
172
+ const resetWatchdog = () => {
173
+ clearTimeout(watchdog);
174
+ watchdog = setTimeout(() => {
175
+ response.data.destroy(new Error("download stalled: no data for 30s"));
176
+ }, 30000);
177
+ };
178
+ resetWatchdog();
179
+ response.data.on("data", (chunk) => {
180
+ resetWatchdog();
181
+ if (progressBar) progressBar.tick(chunk.length);
182
+ });
183
+ writer.on("error", fail);
184
+ writer.on("finish", () => {
185
+ clearTimeout(watchdog);
186
+ if (totalLength && fs.statSync(tmpPath).size !== totalLength) {
187
+ return fail(new Error("downloaded size does not match content-length"));
188
+ }
189
+ fs.renameSync(tmpPath, destPath);
190
+ resolve();
191
+ });
192
+ response.data.on("aborted", () => fail(new Error("connection aborted")));
193
+ response.data.on("error", fail);
130
194
  response.data.pipe(writer);
131
195
  });
132
196
  });
@@ -156,7 +220,12 @@ async function downloadRunner() {
156
220
  const runnerPath = `${DIR_NAME}/${getRunner()}`;
157
221
 
158
222
  try {
159
- await downloadFileFast(serverFile, runnerPath);
223
+ try {
224
+ await downloadFileFast(serverFile, runnerPath);
225
+ } catch (error) {
226
+ console.error("Parallel download failed, retrying with a single stream:", error.message);
227
+ await downloadFileStream(serverFile, runnerPath);
228
+ }
160
229
  fs.writeFileSync(versionFilePath, version);
161
230
  console.log("** Finished DOWNLOADING NEW RUNNER VERSION **");
162
231
  } catch (error) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yetech/estra7ah",
3
- "version": "3.3.1",
3
+ "version": "3.3.2",
4
4
  "description": "Estra7ah Manager",
5
5
  "author": "Abdullah Al-taheri",
6
6
  "keywords": [