htn-tunnel 0.2.3 → 0.2.5

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/lib/install.js +62 -94
  2. package/package.json +1 -1
package/lib/install.js CHANGED
@@ -5,12 +5,12 @@ const os = require("os");
5
5
  const https = require("https");
6
6
  const { execSync } = require("child_process");
7
7
 
8
- // Binary version from GitHub Releases (may differ from npm package version).
8
+ // Binary version from GitHub Releases.
9
9
  const VERSION = "0.2.0";
10
10
  const REPO = "nhh0718/htn-tunnel";
11
11
 
12
- const platform = os.platform(); // darwin, linux, win32
13
- const arch = os.arch(); // x64, arm64
12
+ const platform = os.platform();
13
+ const arch = os.arch();
14
14
  const goOS = platform === "win32" ? "windows" : platform;
15
15
  const goArch = arch === "x64" ? "amd64" : arch;
16
16
  const ext = platform === "win32" ? ".exe" : "";
@@ -19,114 +19,82 @@ const archiveExt = platform === "win32" ? ".zip" : ".tar.gz";
19
19
  const binDir = path.join(__dirname, "..", "bin");
20
20
  const binPath = path.join(binDir, `htn-tunnel${ext}`);
21
21
 
22
- // Skip if binary already exists and is correct version
23
- if (fs.existsSync(binPath) && fs.statSync(binPath).size > 0) {
24
- try {
25
- const out = execSync(`"${binPath}" --version`, { encoding: "utf8", timeout: 5000 });
26
- if (out.includes(VERSION)) {
27
- process.exit(0);
28
- }
29
- } catch { /* re-download */ }
22
+ // Skip if binary exists and works.
23
+ if (fs.existsSync(binPath) && fs.statSync(binPath).size > 1000) {
24
+ console.log("htn-tunnel: binary already installed.");
25
+ process.exit(0);
30
26
  }
31
27
 
32
28
  const archiveName = `htn-tunnel_${VERSION}_${goOS}_${goArch}${archiveExt}`;
33
29
  const url = `https://github.com/${REPO}/releases/download/v${VERSION}/${archiveName}`;
34
30
 
35
31
  console.log(`htn-tunnel: downloading v${VERSION} for ${platform}/${arch}...`);
36
- console.log(` ${url}`);
37
32
 
38
- function follow(url, cb, redirects) {
39
- if (redirects > 5) { console.error("htn-tunnel: too many redirects"); process.exit(1); }
40
- https.get(url, { headers: { "User-Agent": "htn-tunnel-npm" } }, (res) => {
41
- if (res.statusCode === 301 || res.statusCode === 302) {
42
- return follow(res.headers.location, cb, redirects + 1);
43
- }
44
- cb(res);
45
- }).on("error", (err) => {
46
- console.error("htn-tunnel: download failed:", err.message);
47
- printManual();
48
- process.exit(1);
33
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "htn-"));
34
+ const tmpFile = path.join(tmpDir, archiveName);
35
+
36
+ function follow(url, dest, redirects) {
37
+ return new Promise((resolve, reject) => {
38
+ if (redirects > 5) return reject(new Error("too many redirects"));
39
+ https.get(url, { headers: { "User-Agent": "htn-tunnel-npm" } }, (res) => {
40
+ if (res.statusCode === 301 || res.statusCode === 302) {
41
+ return follow(res.headers.location, dest, redirects + 1).then(resolve, reject);
42
+ }
43
+ if (res.statusCode !== 200) {
44
+ return reject(new Error(`HTTP ${res.statusCode}`));
45
+ }
46
+ const file = fs.createWriteStream(dest);
47
+ res.pipe(file);
48
+ file.on("finish", () => { file.close(); resolve(); });
49
+ file.on("error", reject);
50
+ }).on("error", reject);
49
51
  });
50
52
  }
51
53
 
52
- fs.mkdirSync(binDir, { recursive: true });
53
-
54
- follow(url, (res) => {
55
- if (res.statusCode !== 200) {
56
- console.error(`htn-tunnel: download failed (HTTP ${res.statusCode})`);
57
- printManual();
58
- process.exit(1);
59
- }
54
+ async function main() {
55
+ try {
56
+ await follow(url, tmpFile, 0);
57
+ fs.mkdirSync(binDir, { recursive: true });
60
58
 
61
- const chunks = [];
62
- res.on("data", (chunk) => chunks.push(chunk));
63
- res.on("end", () => {
64
- const data = Buffer.concat(chunks);
65
- try {
66
- if (archiveExt === ".zip") {
67
- extractZip(data, `htn-tunnel${ext}`, binPath);
68
- } else {
69
- extractTarGz(data, `htn-tunnel${ext}`, binPath);
70
- }
71
- fs.chmodSync(binPath, 0o755);
72
- console.log("htn-tunnel: installed successfully!");
73
- } catch (err) {
74
- console.error("htn-tunnel: extract failed:", err.message);
75
- printManual();
76
- process.exit(1);
59
+ if (archiveExt === ".zip") {
60
+ // Use PowerShell on Windows for reliable zip extraction.
61
+ execSync(
62
+ `powershell -NoProfile -Command "Expand-Archive -Path '${tmpFile}' -DestinationPath '${tmpDir}' -Force"`,
63
+ { stdio: "pipe" }
64
+ );
65
+ } else {
66
+ execSync(`tar -xzf "${tmpFile}" -C "${tmpDir}"`, { stdio: "pipe" });
77
67
  }
78
- });
79
- }, 0);
80
68
 
81
- function extractTarGz(gzData, filename, dest) {
82
- const zlib = require("zlib");
83
- const tarData = zlib.gunzipSync(gzData);
84
- let offset = 0;
85
- while (offset < tarData.length) {
86
- const header = tarData.subarray(offset, offset + 512);
87
- if (header[0] === 0) break;
88
- const name = header.toString("utf8", 0, 100).replace(/\0/g, "");
89
- const sizeStr = header.toString("utf8", 124, 136).replace(/\0/g, "").trim();
90
- const size = parseInt(sizeStr, 8) || 0;
91
- offset += 512;
92
- const baseName = name.split("/").pop();
93
- if (baseName === filename) {
94
- fs.writeFileSync(dest, tarData.subarray(offset, offset + size));
95
- return;
96
- }
97
- offset += Math.ceil(size / 512) * 512;
69
+ // Find the binary in extracted files.
70
+ const binaryName = `htn-tunnel${ext}`;
71
+ const extracted = findFile(tmpDir, binaryName);
72
+ if (!extracted) throw new Error(`${binaryName} not found in archive`);
73
+
74
+ fs.copyFileSync(extracted, binPath);
75
+ fs.chmodSync(binPath, 0o755);
76
+ console.log("htn-tunnel: installed successfully!");
77
+ } catch (err) {
78
+ console.error("htn-tunnel: install failed:", err.message);
79
+ console.error(`\nInstall manually: https://github.com/${REPO}/releases/tag/v${VERSION}`);
80
+ console.error(`Or: go install github.com/${REPO}/cmd/htn-tunnel@latest\n`);
81
+ process.exit(1);
82
+ } finally {
83
+ fs.rmSync(tmpDir, { recursive: true, force: true });
98
84
  }
99
- throw new Error(`${filename} not found in archive`);
100
85
  }
101
86
 
102
- function extractZip(zipData, filename, dest) {
103
- let offset = 0;
104
- while (offset < zipData.length - 4) {
105
- if (zipData.readUInt32LE(offset) !== 0x04034b50) break;
106
- const compMethod = zipData.readUInt16LE(offset + 8);
107
- const compSize = zipData.readUInt32LE(offset + 18);
108
- const nameLen = zipData.readUInt16LE(offset + 26);
109
- const extraLen = zipData.readUInt16LE(offset + 28);
110
- const name = zipData.toString("utf8", offset + 30, offset + 30 + nameLen);
111
- const dataStart = offset + 30 + nameLen + extraLen;
112
- const baseName = name.split("/").pop();
113
- if (baseName === filename) {
114
- if (compMethod === 0) {
115
- fs.writeFileSync(dest, zipData.subarray(dataStart, dataStart + compSize));
116
- } else {
117
- const zlib = require("zlib");
118
- const raw = zipData.subarray(dataStart, dataStart + compSize);
119
- fs.writeFileSync(dest, zlib.inflateRawSync(raw));
120
- }
121
- return;
87
+ function findFile(dir, name) {
88
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
89
+ const full = path.join(dir, entry.name);
90
+ if (entry.isDirectory()) {
91
+ const found = findFile(full, name);
92
+ if (found) return found;
93
+ } else if (entry.name === name) {
94
+ return full;
122
95
  }
123
- offset = dataStart + compSize;
124
96
  }
125
- throw new Error(`${filename} not found in archive`);
97
+ return null;
126
98
  }
127
99
 
128
- function printManual() {
129
- console.error("\nInstall manually:");
130
- console.error(` https://github.com/${REPO}/releases/tag/v${VERSION}`);
131
- console.error("\nOr use: go install github.com/nhh0718/htn-tunnel/cmd/htn-tunnel@latest\n");
132
- }
100
+ main();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "htn-tunnel",
3
- "version": "0.2.3",
3
+ "version": "0.2.5",
4
4
  "description": "Self-hosted tunnel client — expose localhost to the internet",
5
5
  "bin": {
6
6
  "htn-tunnel": "./bin/htn-tunnel.js"