@yetech/estra7ah 3.3.1 → 3.3.3

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 +88 -13
  2. package/package.json +1 -1
package/dist/estra7ah.js CHANGED
@@ -10,7 +10,9 @@ const RUNNERS = {
10
10
  win64: "runner-win-x64.exe",
11
11
  linux64: "runner-linux-x64",
12
12
  linuxarm64: "runner-linux-arm64",
13
- linuxarmv7: "runner-linux-armv7",
13
+ // pkg has no node18 armv7 target, so 32-bit ARM (Raspberry Pi OS) gets the
14
+ // plain webpack bundle and runs it under the system Node instead.
15
+ linuxarmv7: "runner.js",
14
16
  };
15
17
  const DIR_NAME = __dirname;
16
18
 
@@ -51,17 +53,46 @@ function downloadRange(url, fd, start, end, progressBar, attempt = 0) {
51
53
  .then(
52
54
  (response) =>
53
55
  new Promise((resolve, reject) => {
56
+ // A 200 means the server (or a proxy on this plain-HTTP route) ignored
57
+ // the Range header and is sending the whole file: writing that at six
58
+ // different offsets corrupts the binary. Bail out to single-stream.
59
+ if (response.status !== 206) {
60
+ response.data.destroy();
61
+ return reject({ noRangeSupport: true });
62
+ }
54
63
  let position = start;
64
+ // With timeout: 0 a dead connection would hang the update forever
65
+ // (progress stuck at 0%). Kill the stream if no data arrives for 30s
66
+ // so the retry/fallback path can take over.
67
+ let watchdog;
68
+ const resetWatchdog = () => {
69
+ clearTimeout(watchdog);
70
+ watchdog = setTimeout(() => {
71
+ response.data.destroy(new Error("download stalled: no data for 30s"));
72
+ }, 30000);
73
+ };
74
+ resetWatchdog();
75
+ response.data.on("close", () => clearTimeout(watchdog));
55
76
  response.data.on("data", (chunk) => {
77
+ resetWatchdog();
56
78
  fs.writeSync(fd, chunk, 0, chunk.length, position);
57
79
  position += chunk.length;
58
80
  progressBar.tick(chunk.length);
59
81
  });
60
- response.data.on("end", () => resolve(position));
82
+ response.data.on("end", () => {
83
+ // "end" also fires on some premature disconnects; an incomplete
84
+ // chunk must be retried, never treated as success.
85
+ if (position !== end + 1) {
86
+ return reject({ err: new Error(`chunk ended at ${position}, expected ${end + 1}`), position });
87
+ }
88
+ resolve(position);
89
+ });
90
+ response.data.on("aborted", () => reject({ err: new Error("connection aborted"), position }));
61
91
  response.data.on("error", (err) => reject({ err, position }));
62
92
  })
63
93
  )
64
94
  .catch((failure) => {
95
+ if (failure && failure.noRangeSupport) throw failure;
65
96
  const resumeAt = failure.position || start;
66
97
  if (attempt < 2) return downloadRange(url, fd, resumeAt, end, progressBar, attempt + 1);
67
98
  throw failure.err || failure;
@@ -103,10 +134,17 @@ async function downloadFileFast(url, destPath) {
103
134
  await Promise.all(jobs);
104
135
  } catch (error) {
105
136
  fs.closeSync(fd);
106
- fs.unlinkSync(tmpPath);
137
+ try {
138
+ fs.unlinkSync(tmpPath);
139
+ } catch (e) {}
140
+ if (error && error.noRangeSupport) return downloadFileStream(url, destPath);
107
141
  throw error;
108
142
  }
109
143
  fs.closeSync(fd);
144
+ if (fs.statSync(tmpPath).size !== totalLength) {
145
+ fs.unlinkSync(tmpPath);
146
+ throw new Error("downloaded size does not match content-length");
147
+ }
110
148
  fs.renameSync(tmpPath, destPath);
111
149
  }
112
150
 
@@ -121,12 +159,40 @@ function downloadFileStream(url, destPath) {
121
159
  total: totalLength,
122
160
  })
123
161
  : null;
124
- const writer = fs.createWriteStream(destPath);
162
+ const tmpPath = `${destPath}.download`;
163
+ const writer = fs.createWriteStream(tmpPath);
125
164
  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);
165
+ let watchdog;
166
+ const fail = (err) => {
167
+ clearTimeout(watchdog);
168
+ writer.destroy();
169
+ try {
170
+ fs.unlinkSync(tmpPath);
171
+ } catch (e) {}
172
+ reject(err);
173
+ };
174
+ const resetWatchdog = () => {
175
+ clearTimeout(watchdog);
176
+ watchdog = setTimeout(() => {
177
+ response.data.destroy(new Error("download stalled: no data for 30s"));
178
+ }, 30000);
179
+ };
180
+ resetWatchdog();
181
+ response.data.on("data", (chunk) => {
182
+ resetWatchdog();
183
+ if (progressBar) progressBar.tick(chunk.length);
184
+ });
185
+ writer.on("error", fail);
186
+ writer.on("finish", () => {
187
+ clearTimeout(watchdog);
188
+ if (totalLength && fs.statSync(tmpPath).size !== totalLength) {
189
+ return fail(new Error("downloaded size does not match content-length"));
190
+ }
191
+ fs.renameSync(tmpPath, destPath);
192
+ resolve();
193
+ });
194
+ response.data.on("aborted", () => fail(new Error("connection aborted")));
195
+ response.data.on("error", fail);
130
196
  response.data.pipe(writer);
131
197
  });
132
198
  });
@@ -150,13 +216,18 @@ async function downloadRunner() {
150
216
  localVersion = fs.readFileSync(versionFilePath, "utf-8");
151
217
  }
152
218
 
153
- if (localVersion !== version) {
219
+ if (localVersion !== version || !fs.existsSync(`${DIR_NAME}/${getRunner()}`)) {
154
220
  console.log("** DOWNLOADING NEW RUNNER VERSION **");
155
221
  const serverFile = `${YETECH_URL}/runner/${getRunner()}`;
156
222
  const runnerPath = `${DIR_NAME}/${getRunner()}`;
157
223
 
158
224
  try {
159
- await downloadFileFast(serverFile, runnerPath);
225
+ try {
226
+ await downloadFileFast(serverFile, runnerPath);
227
+ } catch (error) {
228
+ console.error("Parallel download failed, retrying with a single stream:", error.message);
229
+ await downloadFileStream(serverFile, runnerPath);
230
+ }
160
231
  fs.writeFileSync(versionFilePath, version);
161
232
  console.log("** Finished DOWNLOADING NEW RUNNER VERSION **");
162
233
  } catch (error) {
@@ -167,20 +238,24 @@ async function downloadRunner() {
167
238
 
168
239
  // Start the runner executable
169
240
  function startRunner() {
170
- const runnerPath = `"${DIR_NAME}/${getRunner()}"`;
241
+ const runnerFile = getRunner();
242
+ const runnerPath = `"${DIR_NAME}/${runnerFile}"`;
171
243
  const args = process.argv.slice(2);
172
244
  const options = { stdio: "inherit", shell: true };
173
245
 
174
246
  setTimeout(() => {
175
247
  if (process.platform !== "win32") {
176
248
  try {
177
- fs.chmodSync(`${DIR_NAME}/${getRunner()}`, "755");
249
+ fs.chmodSync(`${DIR_NAME}/${runnerFile}`, "755");
178
250
  } catch (error) {
179
251
  console.error("Error setting permissions:", error.message);
180
252
  }
181
253
  }
182
254
 
183
- const runnerProcess = child_process.spawn(runnerPath, args, options);
255
+ // .js runner (armv7) runs under the same Node that launched us
256
+ const command = runnerFile.endsWith(".js") ? `"${process.execPath}"` : runnerPath;
257
+ const commandArgs = runnerFile.endsWith(".js") ? [runnerPath, ...args] : args;
258
+ const runnerProcess = child_process.spawn(command, commandArgs, options);
184
259
  runnerProcess.on("exit", (code) => {
185
260
  console.log(`Runner exited with code ${code}`);
186
261
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yetech/estra7ah",
3
- "version": "3.3.1",
3
+ "version": "3.3.3",
4
4
  "description": "Estra7ah Manager",
5
5
  "author": "Abdullah Al-taheri",
6
6
  "keywords": [