htn-tunnel 0.2.2 → 0.2.4

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 +59 -92
  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,112 +19,79 @@ 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
+ // Windows 10+ has tar that handles zip.
61
+ execSync(`tar -xf "${tmpFile}" -C "${tmpDir}"`, { stdio: "pipe" });
62
+ } else {
63
+ execSync(`tar -xzf "${tmpFile}" -C "${tmpDir}"`, { stdio: "pipe" });
77
64
  }
78
- });
79
- }, 0);
80
65
 
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
- if (name.endsWith(filename) || name === filename) {
93
- fs.writeFileSync(dest, tarData.subarray(offset, offset + size));
94
- return;
95
- }
96
- offset += Math.ceil(size / 512) * 512;
66
+ // Find the binary in extracted files.
67
+ const binaryName = `htn-tunnel${ext}`;
68
+ const extracted = findFile(tmpDir, binaryName);
69
+ if (!extracted) throw new Error(`${binaryName} not found in archive`);
70
+
71
+ fs.copyFileSync(extracted, binPath);
72
+ fs.chmodSync(binPath, 0o755);
73
+ console.log("htn-tunnel: installed successfully!");
74
+ } catch (err) {
75
+ console.error("htn-tunnel: install failed:", err.message);
76
+ console.error(`\nInstall manually: https://github.com/${REPO}/releases/tag/v${VERSION}`);
77
+ console.error(`Or: go install github.com/${REPO}/cmd/htn-tunnel@latest\n`);
78
+ process.exit(1);
79
+ } finally {
80
+ fs.rmSync(tmpDir, { recursive: true, force: true });
97
81
  }
98
- throw new Error(`${filename} not found in archive`);
99
82
  }
100
83
 
101
- function extractZip(zipData, filename, dest) {
102
- let offset = 0;
103
- while (offset < zipData.length - 4) {
104
- if (zipData.readUInt32LE(offset) !== 0x04034b50) break;
105
- const compMethod = zipData.readUInt16LE(offset + 8);
106
- const compSize = zipData.readUInt32LE(offset + 18);
107
- const nameLen = zipData.readUInt16LE(offset + 26);
108
- const extraLen = zipData.readUInt16LE(offset + 28);
109
- const name = zipData.toString("utf8", offset + 30, offset + 30 + nameLen);
110
- const dataStart = offset + 30 + nameLen + extraLen;
111
- if (name.endsWith(filename) || name === filename) {
112
- if (compMethod === 0) {
113
- fs.writeFileSync(dest, zipData.subarray(dataStart, dataStart + compSize));
114
- } else {
115
- const zlib = require("zlib");
116
- const raw = zipData.subarray(dataStart, dataStart + compSize);
117
- fs.writeFileSync(dest, zlib.inflateRawSync(raw));
118
- }
119
- return;
84
+ function findFile(dir, name) {
85
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
86
+ const full = path.join(dir, entry.name);
87
+ if (entry.isDirectory()) {
88
+ const found = findFile(full, name);
89
+ if (found) return found;
90
+ } else if (entry.name === name) {
91
+ return full;
120
92
  }
121
- offset = dataStart + compSize;
122
93
  }
123
- throw new Error(`${filename} not found in archive`);
94
+ return null;
124
95
  }
125
96
 
126
- function printManual() {
127
- console.error("\nInstall manually:");
128
- console.error(` https://github.com/${REPO}/releases/tag/v${VERSION}`);
129
- console.error("\nOr use: go install github.com/nhh0718/htn-tunnel/cmd/htn-tunnel@latest\n");
130
- }
97
+ main();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "htn-tunnel",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
4
  "description": "Self-hosted tunnel client — expose localhost to the internet",
5
5
  "bin": {
6
6
  "htn-tunnel": "./bin/htn-tunnel.js"