nport 2.1.1 → 2.1.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 (62) hide show
  1. package/dist/bin-manager.js +1 -209
  2. package/dist/index.js +69 -91
  3. package/package.json +7 -7
  4. package/CHANGELOG.md +0 -449
  5. package/dist/analytics.d.ts +0 -59
  6. package/dist/analytics.js +0 -193
  7. package/dist/analytics.js.map +0 -1
  8. package/dist/api.d.ts +0 -20
  9. package/dist/api.js +0 -85
  10. package/dist/api.js.map +0 -1
  11. package/dist/args.d.ts +0 -44
  12. package/dist/args.js +0 -127
  13. package/dist/args.js.map +0 -1
  14. package/dist/bin-manager.d.ts +0 -1
  15. package/dist/bin-manager.js.map +0 -1
  16. package/dist/binary.d.ts +0 -42
  17. package/dist/binary.js +0 -119
  18. package/dist/binary.js.map +0 -1
  19. package/dist/config-manager.d.ts +0 -54
  20. package/dist/config-manager.js +0 -129
  21. package/dist/config-manager.js.map +0 -1
  22. package/dist/config.d.ts +0 -25
  23. package/dist/config.js +0 -59
  24. package/dist/config.js.map +0 -1
  25. package/dist/constants.d.ts +0 -61
  26. package/dist/constants.js +0 -86
  27. package/dist/constants.js.map +0 -1
  28. package/dist/index.d.ts +0 -7
  29. package/dist/index.js.map +0 -1
  30. package/dist/lang.d.ts +0 -38
  31. package/dist/lang.js +0 -217
  32. package/dist/lang.js.map +0 -1
  33. package/dist/state.d.ts +0 -82
  34. package/dist/state.js +0 -139
  35. package/dist/state.js.map +0 -1
  36. package/dist/tunnel.d.ts +0 -16
  37. package/dist/tunnel.js +0 -101
  38. package/dist/tunnel.js.map +0 -1
  39. package/dist/types/analytics.d.ts +0 -91
  40. package/dist/types/analytics.js +0 -8
  41. package/dist/types/analytics.js.map +0 -1
  42. package/dist/types/config.d.ts +0 -89
  43. package/dist/types/config.js +0 -8
  44. package/dist/types/config.js.map +0 -1
  45. package/dist/types/i18n.d.ts +0 -75
  46. package/dist/types/i18n.js +0 -5
  47. package/dist/types/i18n.js.map +0 -1
  48. package/dist/types/index.d.ts +0 -10
  49. package/dist/types/index.js +0 -7
  50. package/dist/types/index.js.map +0 -1
  51. package/dist/types/tunnel.d.ts +0 -74
  52. package/dist/types/tunnel.js +0 -8
  53. package/dist/types/tunnel.js.map +0 -1
  54. package/dist/types/version.d.ts +0 -25
  55. package/dist/types/version.js +0 -5
  56. package/dist/types/version.js.map +0 -1
  57. package/dist/ui.d.ts +0 -54
  58. package/dist/ui.js +0 -120
  59. package/dist/ui.js.map +0 -1
  60. package/dist/version.d.ts +0 -16
  61. package/dist/version.js +0 -49
  62. package/dist/version.js.map +0 -1
@@ -1,209 +1 @@
1
- import fs from 'fs';
2
- import path from 'path';
3
- import https from 'https';
4
- import os from 'os';
5
- import { execSync } from 'child_process';
6
- import { fileURLToPath } from 'url';
7
- const __filename = fileURLToPath(import.meta.url);
8
- const __dirname = path.dirname(path.dirname(__filename));
9
- const BIN_DIR = path.join(__dirname, 'bin');
10
- const BINARY_NAME = 'cloudflared';
11
- const COMPRESSED_SUFFIX = '.tgz';
12
- const TEMP_ARCHIVE_NAME = 'cloudflared.tgz';
13
- const PLATFORM = os.platform();
14
- const ARCH = os.arch();
15
- const IS_WINDOWS = PLATFORM === 'win32';
16
- const BIN_NAME = IS_WINDOWS ? `${BINARY_NAME}.exe` : BINARY_NAME;
17
- const BIN_PATH = path.join(BIN_DIR, BIN_NAME);
18
- const GITHUB_BASE_URL = 'https://github.com/cloudflare/cloudflared/releases/latest/download';
19
- const REDIRECT_CODES = [301, 302];
20
- const SUCCESS_CODE = 200;
21
- const UNIX_EXECUTABLE_MODE = '755';
22
- const PLATFORM_MAPPINGS = {
23
- darwin: {
24
- x64: 'cloudflared-darwin-amd64.tgz',
25
- arm64: 'cloudflared-darwin-arm64.tgz',
26
- },
27
- win32: {
28
- x64: 'cloudflared-windows-amd64.exe',
29
- ia32: 'cloudflared-windows-386.exe',
30
- },
31
- linux: {
32
- x64: 'cloudflared-linux-amd64',
33
- arm64: 'cloudflared-linux-arm64',
34
- arm: 'cloudflared-linux-arm',
35
- },
36
- };
37
- function normalizeArch(arch) {
38
- const archMap = {
39
- x64: 'x64',
40
- amd64: 'amd64',
41
- arm64: 'arm64',
42
- ia32: 'ia32',
43
- arm: 'arm',
44
- };
45
- return archMap[arch] || arch;
46
- }
47
- function getDownloadUrl() {
48
- const normalizedArch = normalizeArch(ARCH);
49
- const platformMapping = PLATFORM_MAPPINGS[PLATFORM];
50
- if (!platformMapping) {
51
- throw new Error(`Unsupported platform: ${PLATFORM}. Supported platforms: darwin, win32, linux`);
52
- }
53
- const binaryName = platformMapping[normalizedArch];
54
- if (!binaryName) {
55
- throw new Error(`Unsupported architecture: ${ARCH} for platform ${PLATFORM}. ` +
56
- `Supported architectures: ${Object.keys(platformMapping).join(', ')}`);
57
- }
58
- return `${GITHUB_BASE_URL}/${binaryName}`;
59
- }
60
- function isCompressedArchive(url) {
61
- return url.endsWith(COMPRESSED_SUFFIX);
62
- }
63
- function ensureDirectory(dirPath) {
64
- if (!fs.existsSync(dirPath)) {
65
- fs.mkdirSync(dirPath, { recursive: true });
66
- }
67
- }
68
- function safeUnlink(filePath) {
69
- try {
70
- if (fs.existsSync(filePath)) {
71
- fs.unlinkSync(filePath);
72
- }
73
- }
74
- catch {
75
- // Ignore
76
- }
77
- }
78
- function setExecutablePermissions(filePath, mode = UNIX_EXECUTABLE_MODE) {
79
- if (!IS_WINDOWS) {
80
- fs.chmodSync(filePath, mode);
81
- }
82
- }
83
- function validateFileExists(filePath, errorMessage) {
84
- if (!fs.existsSync(filePath)) {
85
- throw new Error(errorMessage || `File not found: ${filePath}`);
86
- }
87
- }
88
- async function downloadFile(url, dest) {
89
- return new Promise((resolve, reject) => {
90
- const file = fs.createWriteStream(dest);
91
- https
92
- .get(url, (response) => {
93
- if (REDIRECT_CODES.includes(response.statusCode)) {
94
- file.close();
95
- safeUnlink(dest);
96
- downloadFile(response.headers.location, dest)
97
- .then(resolve)
98
- .catch(reject);
99
- return;
100
- }
101
- if (response.statusCode !== SUCCESS_CODE) {
102
- file.close();
103
- safeUnlink(dest);
104
- reject(new Error(`Download failed with status code ${response.statusCode} from ${url}`));
105
- return;
106
- }
107
- response.pipe(file);
108
- file.on('finish', () => {
109
- file.close(() => resolve(dest));
110
- });
111
- file.on('error', (err) => {
112
- file.close();
113
- safeUnlink(dest);
114
- reject(err);
115
- });
116
- })
117
- .on('error', (err) => {
118
- file.close();
119
- safeUnlink(dest);
120
- reject(new Error(`Network error: ${err.message}`));
121
- });
122
- });
123
- }
124
- function extractTarGz(archivePath, targetDir) {
125
- try {
126
- execSync(`tar -xzf "${archivePath}" -C "${targetDir}"`, {
127
- stdio: 'pipe',
128
- });
129
- }
130
- catch (err) {
131
- throw new Error(`Extraction failed: ${err.message}`);
132
- }
133
- }
134
- const logger = {
135
- info: (msg) => console.log(`ℹ️ ${msg}`),
136
- success: (msg) => console.log(`✅ ${msg}`),
137
- warn: (msg) => console.warn(`⚠️ ${msg}`),
138
- error: (msg) => console.error(`❌ ${msg}`),
139
- progress: (msg) => console.log(`🚧 ${msg}`),
140
- extract: (msg) => console.log(`📦 ${msg}`),
141
- };
142
- async function installBinary() {
143
- logger.progress('Cloudflared binary not found. Downloading... (This happens only once)');
144
- const url = getDownloadUrl();
145
- const isArchive = isCompressedArchive(url);
146
- const downloadDest = isArchive
147
- ? path.join(BIN_DIR, TEMP_ARCHIVE_NAME)
148
- : BIN_PATH;
149
- try {
150
- await downloadFile(url, downloadDest);
151
- if (isArchive) {
152
- logger.extract('Extracting binary...');
153
- extractTarGz(downloadDest, BIN_DIR);
154
- safeUnlink(downloadDest);
155
- validateFileExists(BIN_PATH, 'Extraction failed: Binary not found after extraction');
156
- }
157
- setExecutablePermissions(BIN_PATH);
158
- logger.success('Download complete.');
159
- return BIN_PATH;
160
- }
161
- catch (error) {
162
- safeUnlink(downloadDest);
163
- safeUnlink(BIN_PATH);
164
- throw error;
165
- }
166
- }
167
- export async function ensureCloudflared() {
168
- ensureDirectory(BIN_DIR);
169
- if (fs.existsSync(BIN_PATH)) {
170
- setExecutablePermissions(BIN_PATH);
171
- return BIN_PATH;
172
- }
173
- try {
174
- return await installBinary();
175
- }
176
- catch (error) {
177
- logger.error(`Installation failed: ${error.message}`);
178
- process.exit(1);
179
- }
180
- }
181
- function isCI() {
182
- return !!(process.env.CI ||
183
- process.env.GITHUB_ACTIONS ||
184
- process.env.GITLAB_CI ||
185
- process.env.CIRCLECI ||
186
- process.env.TRAVIS ||
187
- process.env.JENKINS_URL ||
188
- process.env.BUILDKITE);
189
- }
190
- async function main() {
191
- if (isCI()) {
192
- logger.info('Running in CI environment - skipping binary download');
193
- return;
194
- }
195
- try {
196
- const binaryPath = await ensureCloudflared();
197
- logger.success(`Cloudflared binary is ready at: ${binaryPath}`);
198
- }
199
- catch (error) {
200
- logger.error(error.message);
201
- process.exit(1);
202
- }
203
- }
204
- // Run if executed directly
205
- const currentFilePath = fileURLToPath(import.meta.url);
206
- if (process.argv[1] === currentFilePath || process.argv[1]?.endsWith('bin-manager.js')) {
207
- main();
208
- }
209
- //# sourceMappingURL=bin-manager.js.map
1
+ var C=Object.defineProperty;var o=(r,n)=>C(r,"name",{value:n,configurable:!0});import s from"fs";import u from"path";import S from"https";import w from"os";import{execSync as _}from"child_process";import{fileURLToPath as E}from"url";var $=E(import.meta.url),v=u.dirname(u.dirname($)),m=u.join(v,"bin"),g="cloudflared",A=".tgz",R="cloudflared.tgz",f=w.platform(),p=w.arch(),h=f==="win32",N=h?`${g}.exe`:g,i=u.join(m,N),P="https://github.com/cloudflare/cloudflared/releases/latest/download",T=[301,302],D=200,M="755",U={darwin:{x64:"cloudflared-darwin-amd64.tgz",arm64:"cloudflared-darwin-arm64.tgz"},win32:{x64:"cloudflared-windows-amd64.exe",ia32:"cloudflared-windows-386.exe"},linux:{x64:"cloudflared-linux-amd64",arm64:"cloudflared-linux-arm64",arm:"cloudflared-linux-arm"}};function b(r){return{x64:"x64",amd64:"amd64",arm64:"arm64",ia32:"ia32",arm:"arm"}[r]||r}o(b,"normalizeArch");function B(){let r=b(p),n=U[f];if(!n)throw new Error(`Unsupported platform: ${f}. Supported platforms: darwin, win32, linux`);let t=n[r];if(!t)throw new Error(`Unsupported architecture: ${p} for platform ${f}. Supported architectures: ${Object.keys(n).join(", ")}`);return`${P}/${t}`}o(B,"getDownloadUrl");function L(r){return r.endsWith(A)}o(L,"isCompressedArchive");function O(r){s.existsSync(r)||s.mkdirSync(r,{recursive:!0})}o(O,"ensureDirectory");function c(r){try{s.existsSync(r)&&s.unlinkSync(r)}catch{}}o(c,"safeUnlink");function x(r,n=M){h||s.chmodSync(r,n)}o(x,"setExecutablePermissions");function z(r,n){if(!s.existsSync(r))throw new Error(n||`File not found: ${r}`)}o(z,"validateFileExists");async function y(r,n){return new Promise((t,d)=>{let e=s.createWriteStream(n);S.get(r,a=>{if(T.includes(a.statusCode)){e.close(),c(n),y(a.headers.location,n).then(t).catch(d);return}if(a.statusCode!==D){e.close(),c(n),d(new Error(`Download failed with status code ${a.statusCode} from ${r}`));return}a.pipe(e),e.on("finish",()=>{e.close(()=>t(n))}),e.on("error",I=>{e.close(),c(n),d(I)})}).on("error",a=>{e.close(),c(n),d(new Error(`Network error: ${a.message}`))})})}o(y,"downloadFile");function F(r,n){try{_(`tar -xzf "${r}" -C "${n}"`,{stdio:"pipe"})}catch(t){throw new Error(`Extraction failed: ${t.message}`)}}o(F,"extractTarGz");var l={info:o(r=>console.log(`\u2139\uFE0F ${r}`),"info"),success:o(r=>console.log(`\u2705 ${r}`),"success"),warn:o(r=>console.warn(`\u26A0\uFE0F ${r}`),"warn"),error:o(r=>console.error(`\u274C ${r}`),"error"),progress:o(r=>console.log(`\u{1F6A7} ${r}`),"progress"),extract:o(r=>console.log(`\u{1F4E6} ${r}`),"extract")};async function k(){l.progress("Cloudflared binary not found. Downloading... (This happens only once)");let r=B(),n=L(r),t=n?u.join(m,R):i;try{return await y(r,t),n&&(l.extract("Extracting binary..."),F(t,m),c(t),z(i,"Extraction failed: Binary not found after extraction")),x(i),l.success("Download complete."),i}catch(d){throw c(t),c(i),d}}o(k,"installBinary");async function G(){if(O(m),s.existsSync(i))return x(i),i;try{return await k()}catch(r){l.error(`Installation failed: ${r.message}`),process.exit(1)}}o(G,"ensureCloudflared");function H(){return!!(process.env.CI||process.env.GITHUB_ACTIONS||process.env.GITLAB_CI||process.env.CIRCLECI||process.env.TRAVIS||process.env.JENKINS_URL||process.env.BUILDKITE)}o(H,"isCI");async function W(){if(H()){l.info("Running in CI environment - skipping binary download");return}try{let r=await G();l.success(`Cloudflared binary is ready at: ${r}`)}catch(r){l.error(r.message),process.exit(1)}}o(W,"main");var j=E(import.meta.url);(process.argv[1]===j||process.argv[1]?.endsWith("bin-manager.js"))&&W();export{G as ensureCloudflared};
package/dist/index.js CHANGED
@@ -1,92 +1,70 @@
1
1
  #!/usr/bin/env node
2
- /**
3
- * NPort - Free & Open Source ngrok Alternative
4
- *
5
- * Main entry point for the NPort CLI application.
6
- */
7
- import ora from 'ora';
8
- import chalk from 'chalk';
9
- import { ArgumentParser } from './args.js';
10
- import { TunnelOrchestrator } from './tunnel.js';
11
- import { VersionManager } from './version.js';
12
- import { UI } from './ui.js';
13
- import { CONFIG } from './config.js';
14
- import { lang } from './lang.js';
15
- import { configManager } from './config-manager.js';
16
- /**
17
- * Displays version information with update check.
18
- */
19
- async function displayVersion() {
20
- const spinner = ora(lang.t('checkingUpdates')).start();
21
- const updateInfo = await VersionManager.checkForUpdates();
22
- spinner.stop();
23
- UI.displayVersion(CONFIG.CURRENT_VERSION, updateInfo);
24
- }
25
- /**
26
- * Handles the --set-backend command.
27
- */
28
- function handleSetBackend(value) {
29
- if (value === 'clear') {
30
- configManager.setBackendUrl(null);
31
- console.log(chalk.green('✔ Backend URL cleared. Using default backend.'));
32
- console.log(chalk.gray(' Default: https://api.nport.link\n'));
33
- }
34
- else {
35
- configManager.setBackendUrl(value);
36
- console.log(chalk.green('✔ Backend URL saved successfully!'));
37
- console.log(chalk.cyan(` Backend: ${value}`));
38
- console.log(chalk.gray('\n This backend will be used for all future sessions.'));
39
- console.log(chalk.gray(' To clear: nport --set-backend\n'));
40
- }
41
- const savedUrl = configManager.getBackendUrl();
42
- if (savedUrl) {
43
- console.log(chalk.white('Current configuration:'));
44
- console.log(chalk.cyan(` Saved backend: ${savedUrl}`));
45
- }
46
- }
47
- /**
48
- * Main application entry point.
49
- */
50
- async function main() {
51
- try {
52
- const args = process.argv.slice(2);
53
- const parsedArgs = ArgumentParser.parse(args);
54
- await lang.initialize(parsedArgs.language);
55
- if (args.includes('-v') || args.includes('--version')) {
56
- await displayVersion();
57
- process.exit(0);
58
- }
59
- if (parsedArgs.setBackend) {
60
- handleSetBackend(parsedArgs.setBackend);
61
- process.exit(0);
62
- }
63
- if (parsedArgs.language === 'prompt' &&
64
- (args.includes('--language') || args.includes('--lang') || args.includes('-l'))) {
65
- process.exit(0);
66
- }
67
- let backendUrl = parsedArgs.backendUrl;
68
- if (!backendUrl) {
69
- const savedBackend = configManager.getBackendUrl();
70
- if (savedBackend) {
71
- backendUrl = savedBackend;
72
- }
73
- }
74
- const config = {
75
- port: parsedArgs.port,
76
- subdomain: parsedArgs.subdomain,
77
- backendUrl,
78
- language: parsedArgs.language,
79
- };
80
- await TunnelOrchestrator.start(config);
81
- }
82
- catch (error) {
83
- console.error(`Fatal Error: ${error.message}`);
84
- process.exit(1);
85
- }
86
- }
87
- // Register cleanup handlers
88
- process.on('SIGINT', () => TunnelOrchestrator.cleanup());
89
- process.on('SIGTERM', () => TunnelOrchestrator.cleanup());
90
- // Start application
91
- main();
92
- //# sourceMappingURL=index.js.map
2
+ var he=Object.defineProperty;var i=(o,e)=>he(o,"name",{value:e,configurable:!0});import on from"ora";import b from"chalk";import P from"path";import{fileURLToPath as we}from"url";import{createRequire as Ie}from"module";var Q="https://api.nport.link";var X="user-";var $={THRESHOLD:5,COOLDOWN:3e4},Z={SUCCESS:["Registered tunnel connection"],ERROR:["ERR","error"],NETWORK_WARNING:["failed to accept QUIC stream","failed to dial to edge with quic","failed to accept incoming stream requests","Failed to dial a quic connection","timeout: no recent network activity","failed to dial to edge","quic:"],IGNORE:["Cannot determine default origin certificate path","No file cert.pem","origincert option","TUNNEL_ORIGIN_CERT","context canceled","failed to run the datagram handler","failed to serve tunnel connection","Connection terminated","no more connections active and exiting","Serve tunnel error","accept stream listener encountered a failure","Retrying connection","icmp router terminated","use of closed network connection","Application error 0x0"]},ee=["en","vi"],ne="https://github.com/tuanngocptn/nport",W="https://nport.link",te="https://buymeacoffee.com/tuanngocptn";var Ee=we(import.meta.url),oe=P.dirname(P.dirname(Ee)),be=Ie(import.meta.url),re=be("../package.json");function ke(){return process.env.NPORT_BACKEND_URL?process.env.NPORT_BACKEND_URL:Q}i(ke,"getBackendUrl");var u={PACKAGE_NAME:re.name,CURRENT_VERSION:re.version,BACKEND_URL:ke(),DEFAULT_PORT:8080,SUBDOMAIN_PREFIX:X,TUNNEL_TIMEOUT_HOURS:4,UPDATE_CHECK_TIMEOUT:3e3},Ce={IS_WINDOWS:process.platform==="win32",BIN_NAME:process.platform==="win32"?"cloudflared.exe":"cloudflared"},x={BIN_DIR:P.join(oe,"bin"),BIN_PATH:P.join(oe,"bin",Ce.BIN_NAME)},U=Z,B={WARNING_THRESHOLD:$.THRESHOLD,WARNING_COOLDOWN:$.COOLDOWN},se=u.TUNNEL_TIMEOUT_HOURS*60*60*1e3;var O=class{static{i(this,"ArgumentParser")}static parse(e){let n=this.parsePort(e),t=this.parseSubdomain(e),r=this.parseLanguage(e),c=this.parseBackendUrl(e),d=this.parseSetBackend(e);return{port:n,subdomain:t,language:r,backendUrl:c,setBackend:d}}static parsePort(e){return parseInt(e[0],10)||u.DEFAULT_PORT}static parseSubdomain(e){let n=[()=>this.findFlagWithEquals(e,"--subdomain="),()=>this.findFlagWithEquals(e,"-s="),()=>this.findFlagWithValue(e,"--subdomain"),()=>this.findFlagWithValue(e,"-s")];for(let t of n){let r=t();if(r)return r}return this.generateRandomSubdomain()}static parseLanguage(e){if(e.includes("--language")||e.includes("--lang")||e.includes("-l")){let t=e.indexOf("--language")!==-1?e.indexOf("--language"):e.indexOf("--lang")!==-1?e.indexOf("--lang"):e.indexOf("-l"),r=e[t+1];if(!r||r.startsWith("-"))return"prompt"}let n=[()=>this.findFlagWithEquals(e,"--language="),()=>this.findFlagWithEquals(e,"--lang="),()=>this.findFlagWithEquals(e,"-l="),()=>this.findFlagWithValue(e,"--language"),()=>this.findFlagWithValue(e,"--lang"),()=>this.findFlagWithValue(e,"-l")];for(let t of n){let r=t();if(r)return r}return null}static parseBackendUrl(e){let n=[()=>this.findFlagWithEquals(e,"--backend="),()=>this.findFlagWithEquals(e,"-b="),()=>this.findFlagWithValue(e,"--backend"),()=>this.findFlagWithValue(e,"-b")];for(let t of n){let r=t();if(r)return r}return null}static parseSetBackend(e){let n=[()=>this.findFlagWithEquals(e,"--set-backend="),()=>this.findFlagWithValue(e,"--set-backend")];for(let t of n){let r=t();if(r)return r}return e.includes("--set-backend")?"clear":null}static findFlagWithEquals(e,n){let t=e.find(r=>r.startsWith(n));return t?t.split("=")[1]:null}static findFlagWithValue(e,n){let t=e.indexOf(n);return t!==-1&&e[t+1]?e[t+1]:null}static generateRandomSubdomain(){return`${u.SUBDOMAIN_PREFIX}${Math.floor(Math.random()*1e4)}`}};import nn from"ora";import J from"chalk";import tn from"fs";var M=class{static{i(this,"TunnelState")}tunnelId=null;subdomain=null;port=null;backendUrl=null;tunnelProcess=null;timeoutId=null;connectionCount=0;startTime=null;updateInfo=null;networkIssueCount=0;lastNetworkWarningTime=0;networkWarningShown=!1;setTunnel(e,n,t,r=null){this.tunnelId=e,this.subdomain=n,this.port=t,this.backendUrl=r,this.startTime||(this.startTime=Date.now())}setUpdateInfo(e){this.updateInfo=e}setProcess(e){this.tunnelProcess=e}setTimeout(e){this.timeoutId=e}clearTimeout(){this.timeoutId&&(clearTimeout(this.timeoutId),this.timeoutId=null)}incrementConnection(){return this.connectionCount++,this.connectionCount}hasTunnel(){return this.tunnelId!==null}hasProcess(){return this.tunnelProcess!==null&&!this.tunnelProcess.killed}getDurationSeconds(){return this.startTime?(Date.now()-this.startTime)/1e3:0}incrementNetworkIssue(){return this.networkIssueCount++,this.networkIssueCount}resetNetworkIssues(){this.networkIssueCount=0}shouldShowNetworkWarning(e,n){let t=Date.now();return this.networkIssueCount>=e&&t-this.lastNetworkWarningTime>n?(this.lastNetworkWarningTime=t,!0):!1}setNetworkWarningShown(e){this.networkWarningShown=e}reset(){this.clearTimeout(),this.tunnelId=null,this.subdomain=null,this.port=null,this.backendUrl=null,this.tunnelProcess=null,this.connectionCount=0,this.startTime=null,this.updateInfo=null,this.networkIssueCount=0,this.lastNetworkWarningTime=0,this.networkWarningShown=!1}},l=new M;import{spawn as Se}from"child_process";import g from"chalk";import Ue from"fs";import a from"chalk";import Ne from"readline";import T from"fs";import G from"path";import ve from"os";var V=class{static{i(this,"ConfigManager")}configDir;configFile;oldLangFile;config;constructor(){this.configDir=G.join(ve.homedir(),".nport"),this.configFile=G.join(this.configDir,"config.json"),this.oldLangFile=G.join(this.configDir,"lang"),this.config=this.loadConfig(),this.migrateOldConfig()}loadConfig(){try{if(T.existsSync(this.configFile)){let e=T.readFileSync(this.configFile,"utf8");return JSON.parse(e)}}catch{console.warn("Warning: Could not load config file, using defaults")}return{}}migrateOldConfig(){try{if(!this.config.language&&T.existsSync(this.oldLangFile)){let e=T.readFileSync(this.oldLangFile,"utf8").trim();if(e&&["en","vi"].includes(e)){this.config.language=e,this.saveConfig();try{T.unlinkSync(this.oldLangFile)}catch{}}}}catch{}}saveConfig(){try{return T.existsSync(this.configDir)||T.mkdirSync(this.configDir,{recursive:!0}),T.writeFileSync(this.configFile,JSON.stringify(this.config,null,2),"utf8"),!0}catch{return console.warn("Warning: Could not save configuration"),!1}}getBackendUrl(){return this.config.backendUrl??null}setBackendUrl(e){return e?this.config.backendUrl=e:delete this.config.backendUrl,this.saveConfig()}getLanguage(){return this.config.language??null}setLanguage(e){return e?this.config.language=e:delete this.config.language,this.saveConfig()}getAll(){return{...this.config}}clear(){return this.config={},this.saveConfig()}},w=new V;var H={en:{header:"N P O R T \u26A1\uFE0F Free & Open Source from Vietnam \u2764\uFE0F",creatingTunnel:"Creating tunnel for port {port}...",checkingUpdates:"Checking for updates...",tunnelLive:"\u{1F680} WE LIVE BABY!",connection1:" \u2714 [1/2] Connection established...",connection2:" \u2714 [2/2] Compression enabled...",timeRemaining:"\u23F1\uFE0F Time: {hours}h remaining",footerTitle:"\u{1F525} KEEP THE VIBE ALIVE?",footerSubtitle:"(Made with \u2764\uFE0F in Vietnam)",dropStar:"\u2B50\uFE0F Drop a Star: ",sendCoffee:"\u2615\uFE0F Buy Coffee: ",newVersion:"\u{1F6A8} NEW VERSION (v{version}) detected!",updateCommand:"> npm install -g nport@latest",tunnelShutdown:"\u{1F6D1} TUNNEL SHUTDOWN.",cleaningUp:"Cleaning up... ",cleanupDone:"Done.",cleanupFailed:"Failed.",subdomainReleased:"Subdomain... Released. \u{1F5D1}\uFE0F",serverBusy:"(Server might be down or busy)",goodbyeTitle:"\u{1F44B} BEFORE YOU GO...",goodbyeMessage:"Thanks for using NPort!",website:"\u{1F310} Website: ",author:"\u{1F464} Author: ",changeLanguage:"\u{1F30D} Language: ",changeLanguageHint:"nport --language",versionTitle:"NPort v{version}",versionSubtitle:"Free & open source ngrok alternative",versionLatest:"\u2714 You're running the latest version!",versionAvailable:"\u{1F6A8} New version available: v{version}",versionUpdate:"Update now: ",learnMore:"Learn more: ",languagePrompt:`
3
+ \u{1F30D} Language Selection / Ch\u1ECDn ng\xF4n ng\u1EEF
4
+ `,languageQuestion:"Choose your language (1-2): ",languageEnglish:"1. English",languageVietnamese:"2. Ti\u1EBFng Vi\u1EC7t (Vietnamese)",languageInvalid:"Invalid choice. Using English by default.",languageSaved:"\u2714 Language preference saved!",networkIssueTitle:`
5
+ \u26A0\uFE0F NETWORK CONNECTIVITY ISSUE DETECTED`,networkIssueDesc:" Cloudflared is having trouble maintaining a stable connection to Cloudflare's edge servers.",networkIssueTunnel:" \u{1F4E1} Your tunnel is still working, but connection quality may be affected.",networkIssueReasons:`
6
+ \u{1F4A1} Possible reasons:`,networkIssueReason1:" \u2022 Unstable internet connection or high packet loss",networkIssueReason2:" \u2022 Firewall/Router blocking UDP traffic (QUIC protocol)",networkIssueReason3:" \u2022 ISP throttling or network congestion",networkIssueFix:`
7
+ \u{1F527} What to try:`,networkIssueFix1:" \u2022 Check your internet connection stability",networkIssueFix2:" \u2022 Try connecting from a different network",networkIssueFix3:" \u2022 Disable VPN/Proxy if you're using one",networkIssueFix4:" \u2022 The tunnel will automatically fallback to HTTP/2 if QUIC fails",networkIssueIgnore:`
8
+ \u2139\uFE0F This is usually not critical - your tunnel should continue working normally.
9
+ `},vi:{header:"N P O R T \u26A1\uFE0F Vi\u1EC7t Nam M\xE3i \u0110\u1EC9nh \u2764\uFE0F",creatingTunnel:"\u{1F6E0}\uFE0F \u0110ang kh\u1EDFi \u0111\u1ED9ng c\u1ED5ng {port}... Chu\u1EA9n b\u1ECB bay n\xE0o!",checkingUpdates:"\u{1F50D} \u0110ang d\xF2 la b\u1EA3n c\u1EADp nh\u1EADt m\u1EDBi... \u0110\u1EE3i t\xED s\u1EAFp c\xF3 qu\xE0!",tunnelLive:"\u{1F680} B\u1EACT MODE T\u1ED0C H\xC0NH! \u0110ANG BAY R\u1ED2I N\xC8!",connection1:" \u2714 [1/2] \u0110ang c\u1EAFm d\xE2y m\u1EA1ng v\u0169 tr\u1EE5...",connection2:" \u2714 [2/2] \u0110ang b\u01A1m si\xEAu n\xE9n kh\xED t\u1ED1c \u0111\u1ED9 \xE1nh s\xE1ng...",timeRemaining:"\u23F1\uFE0F T\u0103ng t\u1ED1c th\u1EA7n s\u1EA7u: C\xF2n {hours}h \u0111\u1EC3 qu\u1EA9y!",footerTitle:"\u{1F525} L\u01AFU DANH S\u1EEC S\xC1CH! \u0110\u1EEANG QU\xCAN STAR \u2B50\uFE0F",footerSubtitle:"(Made in Vi\u1EC7t Nam, chu\u1EA9n kh\xF4ng c\u1EA7n ch\u1EC9nh! \u2764\uFE0F)",dropStar:"\u2B50\uFE0F Th\u1EA3 Star: ",sendCoffee:"\u2615\uFE0F T\u1EB7ng Coffee: ",newVersion:"\u{1F680} B\u1EA2N M\u1EDAI (v{version}) v\u1EEBa h\u1EA1 c\xE1nh!",updateCommand:"\u{1F4A1} G\xF5 li\u1EC1n: npm install -g nport@latest",tunnelShutdown:"\u{1F6D1} \u0110\xE3 t\u1EDBi gi\u1EDD 'ch\u1ED1t' deal r\u1ED3i c\u1EA3 nh\xE0 \u01A1i...",cleaningUp:"\u0110ang d\u1ECDn d\u1EB9p chi\u1EBFn tr\u01B0\u1EDDng... \u{1F9F9}",cleanupDone:"X\u1ECBn x\xF2! \u0110\xE3 d\u1ECDn xong r\u1ED3i n\xE8.",cleanupFailed:"O\u1EB1n tr\u1EDDi, d\u1ECDn kh\xF4ng n\u1ED5i!",subdomainReleased:"Subdomain... X\xED xo\xE1! T\u1EA1m bi\u1EC7t nh\xE9 \u{1F5D1}\uFE0F\u2728",serverBusy:"(C\xF3 th\u1EC3 server \u0111ang b\u1EADn order tr\xE0 s\u1EEFa)",goodbyeTitle:"\u{1F44B} G\u1EB6P L\u1EA0I B\u1EA0N \u1EDE \u0110\u01AF\u1EDCNG B\u0102NG KH\xC1C...",goodbyeMessage:"C\u1EA3m \u01A1n \u0111\xE3 qu\u1EA9y NPort! L\u1EA7n sau ch\u01A1i ti\u1EBFp nha \u{1F618}",website:"\u{1F310} S\xE2n ch\u01A1i ch\xEDnh: ",author:"\u{1F464} Nh\xE0 t\xE0i tr\u1EE3: ",changeLanguage:"\u{1F30D} \u0110\u1ED5i ng\xF4n ng\u1EEF: ",changeLanguageHint:"nport --language",versionTitle:"NPort v{version}",versionSubtitle:"H\u01A1n c\u1EA3 Ngrok - Ma-de in Vi\u1EC7t Nam",versionLatest:"\u{1F389} Ch\xFAc m\u1EEBng! \u0110ang c\xF9ng server v\u1EDBi b\u1EA3n m\u1EDBi nh\u1EA5t!",versionAvailable:"\u{1F31F} V\xE8o v\xE8o: C\xF3 b\u1EA3n m\u1EDBi v{version} v\u1EEBa c\u1EADp b\u1EBFn!",versionUpdate:"Update kh\u1EA9n tr\u01B0\u01A1ng l\u1EB9 l\xE0ng: ",learnMore:"Kh\xE1m ph\xE1 th\xEAm cho n\xF3ng: ",languagePrompt:`
10
+ \u{1F30D} Ch\u1ECDn l\u1EF1a ng\xF4n ng\u1EEF ngay b\xEAn d\u01B0\u1EDBi n\xE0o!
11
+ `,languageQuestion:"Ch\u1EDBp l\u1EA5y m\u1ED9t l\u1EF1a ch\u1ECDn nha (1-2): ",languageEnglish:"1. English (Chu\u1EA9n qu\u1ED1c t\u1EBF!)",languageVietnamese:"2. Ti\u1EBFng Vi\u1EC7t (\u0110\u1EC9nh c\u1EE7a ch\xF3p)",languageInvalid:"\u01A0 h\u01A1, ch\u1ECDn sai r\u1ED3i! M\u1EB7c \u0111\u1ECBnh Ti\u1EBFng Vi\u1EC7t lu\xF4n cho n\xF3ng.",languageSaved:"\u{1F3AF} Xong r\u1ED3i! L\u01B0u ng\xF4n ng\u1EEF th\xE0nh c\xF4ng!",networkIssueTitle:`
12
+ \u26A0\uFE0F PH\xC1T HI\u1EC6N V\u1EA4N \u0110\u1EC0 M\u1EA0NG`,networkIssueDesc:" Cloudflared \u0111ang g\u1EB7p kh\xF3 kh\u0103n khi gi\u1EEF k\u1EBFt n\u1ED1i \u1ED5n \u0111\u1ECBnh t\u1EDBi Cloudflare edge servers.",networkIssueTunnel:" \u{1F4E1} Tunnel c\u1EE7a b\u1EA1n v\u1EABn ho\u1EA1t \u0111\u1ED9ng, nh\u01B0ng ch\u1EA5t l\u01B0\u1EE3ng k\u1EBFt n\u1ED1i c\xF3 th\u1EC3 b\u1ECB \u1EA3nh h\u01B0\u1EDFng.",networkIssueReasons:`
13
+ \u{1F4A1} C\xF3 th\u1EC3 do:`,networkIssueReason1:" \u2022 M\u1EA1ng internet kh\xF4ng \u1ED5n \u0111\u1ECBnh ho\u1EB7c m\u1EA5t g\xF3i tin",networkIssueReason2:" \u2022 Firewall/Router ch\u1EB7n UDP traffic (giao th\u1EE9c QUIC)",networkIssueReason3:" \u2022 Nh\xE0 m\u1EA1ng throttle ho\u1EB7c t\u1EAFc ngh\u1EBDn m\u1EA1ng",networkIssueFix:`
14
+ \u{1F527} Th\u1EED c\xE1c c\xE1ch sau:`,networkIssueFix1:" \u2022 Ki\u1EC3m tra k\u1EBFt n\u1ED1i internet c\u1EE7a b\u1EA1n",networkIssueFix2:" \u2022 Th\u1EED \u0111\u1ED5i sang m\u1EA1ng kh\xE1c (v\xED d\u1EE5: 4G/5G)",networkIssueFix3:" \u2022 T\u1EAFt VPN/Proxy n\u1EBFu \u0111ang b\u1EADt",networkIssueFix4:" \u2022 Tunnel s\u1EBD t\u1EF1 \u0111\u1ED9ng chuy\u1EC3n sang HTTP/2 n\u1EBFu QUIC fail",networkIssueIgnore:`
15
+ \u2139\uFE0F L\u1ED7i n\xE0y th\u01B0\u1EDDng kh\xF4ng nghi\xEAm tr\u1ECDng - tunnel v\u1EABn ho\u1EA1t \u0111\u1ED9ng b\xECnh th\u01B0\u1EDDng.
16
+ `}},j=class{static{i(this,"LanguageManager")}currentLanguage="en";availableLanguages=ee;t(e,n={}){let r=(H[this.currentLanguage]||H.en)[e]||H.en[e]||e;return Object.keys(n).forEach(c=>{let d=n[c];d!==void 0&&(r=r.replace(`{${c}}`,String(d)))}),r}loadLanguagePreference(){let e=w.getLanguage();return e&&this.availableLanguages.includes(e)?e:null}saveLanguagePreference(e){w.setLanguage(e)}setLanguage(e){return this.availableLanguages.includes(e)?(this.currentLanguage=e,!0):!1}getLanguage(){return this.currentLanguage}async promptLanguageSelection(){return new Promise(e=>{let n=Ne.createInterface({input:process.stdin,output:process.stdout});console.log(this.t("languagePrompt")),console.log(` ${this.t("languageEnglish")}`),console.log(` ${this.t("languageVietnamese")}
17
+ `),n.question(`${this.t("languageQuestion")}`,t=>{n.close();let r=t.trim(),c="en";r==="1"?c="en":r==="2"?c="vi":console.log(`
18
+ ${this.t("languageInvalid")}
19
+ `),this.setLanguage(c),this.saveLanguagePreference(c),console.log(`${this.t("languageSaved")}
20
+ `),e(c)})})}async initialize(e=null){if(e&&e!=="prompt"&&this.setLanguage(e))return this.saveLanguagePreference(e),e;if(e==="prompt")return await this.promptLanguageSelection();let n=this.loadLanguagePreference();return n?(this.setLanguage(n),n):await this.promptLanguageSelection()}},s=new j;var m=class{static{i(this,"UI")}static displayProjectInfo(){let e="\u2500".repeat(56),n=s.t("header"),r=" ".repeat(Math.max(0,59-n.length-4));console.log(a.gray(`
21
+ \u256D${e}\u256E`)),console.log(a.cyan.bold(` \u2502 ${n}`)+r+a.gray("\u2502")),console.log(a.gray(` \u2570${e}\u256F
22
+ `))}static displayStartupBanner(e){this.displayProjectInfo()}static displayTunnelSuccess(e,n,t){console.log(),console.log(a.cyan.bold(` \u{1F449} ${e} \u{1F448}
23
+ `)),console.log(a.gray(" "+"\u2500".repeat(54)+`
24
+ `)),console.log(a.gray(` ${s.t("timeRemaining",{hours:u.TUNNEL_TIMEOUT_HOURS})}
25
+ `))}static displayFooter(e){console.log(a.gray(" "+"\u2500".repeat(54)+`
26
+ `)),console.log(a.yellow.bold(` ${s.t("footerTitle")}
27
+ `)),console.log(a.gray(` ${s.t("footerSubtitle")}
28
+ `)),console.log(a.cyan(` ${s.t("dropStar")}`)+a.white(ne)),console.log(a.yellow(` ${s.t("sendCoffee")}`)+a.white(te)),e?.shouldUpdate&&(console.log(a.red.bold(`
29
+ ${s.t("newVersion",{version:e.latest})}`)),console.log(a.gray(" ")+a.cyan(s.t("updateCommand")))),console.log()}static displayTimeoutWarning(){console.log(a.yellow(`
30
+ \u23F0 Tunnel has been running for ${u.TUNNEL_TIMEOUT_HOURS} hours.`)),console.log(a.yellow(" Automatically shutting down..."))}static displayError(e,n=null){n&&n.fail("Failed to connect to server."),console.error(a.red(e.message))}static displayCleanupStart(){console.log(a.red.bold(`
31
+
32
+ ${s.t("tunnelShutdown")}
33
+ `)),process.stdout.write(a.gray(` ${s.t("cleaningUp")}`))}static displayCleanupSuccess(){console.log(a.green(s.t("cleanupDone"))),console.log(a.gray(` ${s.t("subdomainReleased")}
34
+ `)),this.displayGoodbye()}static displayCleanupError(){console.log(a.red(s.t("cleanupFailed"))),console.log(a.gray(` ${s.t("serverBusy")}
35
+ `)),this.displayGoodbye()}static displayGoodbye(){console.log(a.gray(" "+"\u2500".repeat(54)+`
36
+ `)),console.log(a.cyan.bold(` ${s.t("goodbyeTitle")}
37
+ `)),console.log(a.gray(` ${s.t("goodbyeMessage")}
38
+ `)),console.log(a.cyan(` ${s.t("website")}`)+a.white(W)),console.log(a.cyan(` ${s.t("author")}`)+a.white("Nick Pham (https://github.com/tuanngocptn)")),console.log(a.cyan(` ${s.t("changeLanguage")}`)+a.yellow(s.t("changeLanguageHint"))),console.log()}static displayVersion(e,n){console.log(a.cyan.bold(`
39
+ ${s.t("versionTitle",{version:e})}`)),console.log(a.gray(`${s.t("versionSubtitle")}
40
+ `)),n?.shouldUpdate?(console.log(a.yellow(s.t("versionAvailable",{version:n.latest}))),console.log(a.cyan(s.t("versionUpdate"))+a.white(`npm install -g nport@latest
41
+ `))):console.log(a.green(`${s.t("versionLatest")}
42
+ `)),console.log(a.gray(s.t("learnMore"))+a.cyan(`${W}
43
+ `))}};var v=class{static{i(this,"BinaryManager")}static validate(e){return Ue.existsSync(e)?!0:(console.error(g.red(`
44
+ \u274C Error: Cloudflared binary not found at: ${e}`)),console.error(g.yellow(`\u{1F449} Please run 'npm install' again to download the binary.
45
+ `)),!1)}static spawn(e,n,t){let r=process.platform==="win32";return Se(e,["tunnel","run","--token",n,"--url",`http://localhost:${t}`],{windowsHide:!0,shell:r,stdio:["ignore","pipe","pipe"]})}static attachHandlers(e,n=null){e.stderr?.on("data",t=>this.handleStderr(t)),e.on("error",t=>this.handleError(t,n)),e.on("close",t=>this.handleClose(t))}static handleStderr(e){let n=e.toString();if(!U.IGNORE.some(t=>n.includes(t))){if(U.NETWORK_WARNING.some(t=>n.includes(t))){this.handleNetworkWarning();return}if(U.SUCCESS.some(t=>n.includes(t))){let t=l.incrementConnection();t===1?(l.resetNetworkIssues(),console.log(g.green(s.t("connection1")))):t===4&&(console.log(g.green(s.t("connection2"))),m.displayFooter(l.updateInfo));return}U.ERROR.some(t=>n.includes(t))&&console.error(g.red(`[Cloudflared] ${n.trim()}`))}}static handleNetworkWarning(){l.incrementNetworkIssue(),l.shouldShowNetworkWarning(B.WARNING_THRESHOLD,B.WARNING_COOLDOWN)&&this.displayNetworkWarning()}static displayNetworkWarning(){console.log(g.yellow(s.t("networkIssueTitle"))),console.log(g.gray(s.t("networkIssueDesc"))),console.log(g.cyan(s.t("networkIssueTunnel"))),console.log(g.yellow(s.t("networkIssueReasons"))),console.log(g.gray(s.t("networkIssueReason1"))),console.log(g.gray(s.t("networkIssueReason2"))),console.log(g.gray(s.t("networkIssueReason3"))),console.log(g.yellow(s.t("networkIssueFix"))),console.log(g.gray(s.t("networkIssueFix1"))),console.log(g.gray(s.t("networkIssueFix2"))),console.log(g.gray(s.t("networkIssueFix3"))),console.log(g.gray(s.t("networkIssueFix4"))),console.log(g.blue(s.t("networkIssueIgnore")))}static handleError(e,n){n&&n.fail("Failed to spawn cloudflared process."),console.error(g.red(`Process Error: ${e.message}`)),process.platform==="win32"&&(e.message.includes("UNKNOWN")||e.message.includes("ENOENT"))&&(console.error(g.yellow(`
46
+ \u{1F4A1} Windows troubleshooting tips:`)),console.error(g.gray(" 1. Check if Windows Defender/antivirus is blocking cloudflared.exe")),console.error(g.gray(" 2. Try running the terminal as Administrator")),console.error(g.gray(" 3. Reinstall nport: npm uninstall -g nport && npm install -g nport")))}static handleClose(e){e!==0&&e!==null&&console.log(g.red(`Tunnel process exited with code ${e}`))}};import E from"fs";import R from"path";import Re from"https";import le from"os";import{execSync as _e}from"child_process";import{fileURLToPath as ce}from"url";var Ae=ce(import.meta.url),Le=R.dirname(R.dirname(Ae)),D=R.join(Le,"bin"),ie="cloudflared",Pe=".tgz",xe="cloudflared.tgz",F=le.platform(),ae=le.arch(),ue=F==="win32",Oe=ue?`${ie}.exe`:ie,I=R.join(D,Oe),Fe="https://github.com/cloudflare/cloudflared/releases/latest/download",De=[301,302],$e=200,We="755",Be={darwin:{x64:"cloudflared-darwin-amd64.tgz",arm64:"cloudflared-darwin-arm64.tgz"},win32:{x64:"cloudflared-windows-amd64.exe",ia32:"cloudflared-windows-386.exe"},linux:{x64:"cloudflared-linux-amd64",arm64:"cloudflared-linux-arm64",arm:"cloudflared-linux-arm"}};function Me(o){return{x64:"x64",amd64:"amd64",arm64:"arm64",ia32:"ia32",arm:"arm"}[o]||o}i(Me,"normalizeArch");function Ge(){let o=Me(ae),e=Be[F];if(!e)throw new Error(`Unsupported platform: ${F}. Supported platforms: darwin, win32, linux`);let n=e[o];if(!n)throw new Error(`Unsupported architecture: ${ae} for platform ${F}. Supported architectures: ${Object.keys(e).join(", ")}`);return`${Fe}/${n}`}i(Ge,"getDownloadUrl");function Ve(o){return o.endsWith(Pe)}i(Ve,"isCompressedArchive");function He(o){E.existsSync(o)||E.mkdirSync(o,{recursive:!0})}i(He,"ensureDirectory");function k(o){try{E.existsSync(o)&&E.unlinkSync(o)}catch{}}i(k,"safeUnlink");function ge(o,e=We){ue||E.chmodSync(o,e)}i(ge,"setExecutablePermissions");function je(o,e){if(!E.existsSync(o))throw new Error(e||`File not found: ${o}`)}i(je,"validateFileExists");async function de(o,e){return new Promise((n,t)=>{let r=E.createWriteStream(e);Re.get(o,c=>{if(De.includes(c.statusCode)){r.close(),k(e),de(c.headers.location,e).then(n).catch(t);return}if(c.statusCode!==$e){r.close(),k(e),t(new Error(`Download failed with status code ${c.statusCode} from ${o}`));return}c.pipe(r),r.on("finish",()=>{r.close(()=>n(e))}),r.on("error",d=>{r.close(),k(e),t(d)})}).on("error",c=>{r.close(),k(e),t(new Error(`Network error: ${c.message}`))})})}i(de,"downloadFile");function Ke(o,e){try{_e(`tar -xzf "${o}" -C "${e}"`,{stdio:"pipe"})}catch(n){throw new Error(`Extraction failed: ${n.message}`)}}i(Ke,"extractTarGz");var C={info:i(o=>console.log(`\u2139\uFE0F ${o}`),"info"),success:i(o=>console.log(`\u2705 ${o}`),"success"),warn:i(o=>console.warn(`\u26A0\uFE0F ${o}`),"warn"),error:i(o=>console.error(`\u274C ${o}`),"error"),progress:i(o=>console.log(`\u{1F6A7} ${o}`),"progress"),extract:i(o=>console.log(`\u{1F4E6} ${o}`),"extract")};async function qe(){C.progress("Cloudflared binary not found. Downloading... (This happens only once)");let o=Ge(),e=Ve(o),n=e?R.join(D,xe):I;try{return await de(o,n),e&&(C.extract("Extracting binary..."),Ke(n,D),k(n),je(I,"Extraction failed: Binary not found after extraction")),ge(I),C.success("Download complete."),I}catch(t){throw k(n),k(I),t}}i(qe,"installBinary");async function K(){if(He(D),E.existsSync(I))return ge(I),I;try{return await qe()}catch(o){C.error(`Installation failed: ${o.message}`),process.exit(1)}}i(K,"ensureCloudflared");function ze(){return!!(process.env.CI||process.env.GITHUB_ACTIONS||process.env.GITLAB_CI||process.env.CIRCLECI||process.env.TRAVIS||process.env.JENKINS_URL||process.env.BUILDKITE)}i(ze,"isCI");async function Je(){if(ze()){C.info("Running in CI environment - skipping binary download");return}try{let o=await K();C.success(`Cloudflared binary is ready at: ${o}`)}catch(o){C.error(o.message),process.exit(1)}}i(Je,"main");var Ye=ce(import.meta.url);(process.argv[1]===Ye||process.argv[1]?.endsWith("bin-manager.js"))&&Je();import pe from"axios";import p from"chalk";var _=class{static{i(this,"APIClient")}static async createTunnel(e,n=null){let t=n||u.BACKEND_URL;try{let{data:r}=await pe.post(t,{subdomain:e});if(!r.success)throw new Error(r.error||"Unknown error from backend");return{tunnelId:r.tunnelId,tunnelToken:r.tunnelToken,url:r.url}}catch(r){throw this.handleError(r,e)}}static async deleteTunnel(e,n,t=null){let r=t||u.BACKEND_URL;await pe.delete(r,{data:{subdomain:e,tunnelId:n}})}static handleError(e,n){let t=e.response?.data?.error;return t?t.includes("SUBDOMAIN_PROTECTED:")?new Error(`Subdomain "${n}" is already taken or in use.
47
+
48
+ `+p.yellow(`\u{1F4A1} Try one of these options:
49
+ `)+p.gray(" 1. Choose a different subdomain: ")+p.cyan(`nport ${l.port||u.DEFAULT_PORT} -s ${n}-v2
50
+ `)+p.gray(" 2. Use a random subdomain: ")+p.cyan(`nport ${l.port||u.DEFAULT_PORT}
51
+ `)+p.gray(" 3. Wait a few minutes and retry if you just stopped a tunnel with this name")):t.includes("SUBDOMAIN_IN_USE:")||t.includes("currently in use")||t.includes("already exists and is currently active")?new Error(p.red(`\u2717 Subdomain "${n}" is already in use!
52
+
53
+ `)+p.yellow(`\u{1F4A1} This subdomain is currently being used by another active tunnel.
54
+
55
+ `)+p.white(`Choose a different subdomain:
56
+ `)+p.gray(" 1. Add a suffix: ")+p.cyan(`nport ${l.port||u.DEFAULT_PORT} -s ${n}-2
57
+ `)+p.gray(" 2. Try a variation: ")+p.cyan(`nport ${l.port||u.DEFAULT_PORT} -s my-${n}
58
+ `)+p.gray(" 3. Use random name: ")+p.cyan(`nport ${l.port||u.DEFAULT_PORT}
59
+ `)):t.includes("already have a tunnel")||t.includes("[1013]")?new Error(`Subdomain "${n}" is already taken or in use.
60
+
61
+ `+p.yellow(`\u{1F4A1} Try one of these options:
62
+ `)+p.gray(" 1. Choose a different subdomain: ")+p.cyan(`nport ${l.port||u.DEFAULT_PORT} -s ${n}-v2
63
+ `)+p.gray(" 2. Use a random subdomain: ")+p.cyan(`nport ${l.port||u.DEFAULT_PORT}
64
+ `)+p.gray(" 3. Wait a few minutes and retry if you just stopped a tunnel with this name")):new Error(`Backend Error: ${t}`):e.response?new Error(`Backend Error: ${JSON.stringify(e.response.data,null,2)}`):e}};import en from"axios";import Xe from"axios";import{createHash as Ze}from"crypto";import y from"os";import A from"fs";import me from"path";var q={measurementId:"G-JJHG4DP1K9",apiSecret:"NjNID8jtRJe9s8uSBz2jfw"},h={enabled:!0,debug:process.env.NPORT_DEBUG==="true",timeout:2e3,userIdFile:me.join(y.homedir(),".nport","analytics-id")},z=class{static{i(this,"AnalyticsManager")}userId=null;sessionId=null;sessionStartTime=null;disabled=!1;constructor(){process.env.NPORT_ANALYTICS==="false"&&(this.disabled=!0)}async initialize(){if(!this.disabled){if(!q.apiSecret){h.debug&&console.warn("[Analytics] API secret not configured. Analytics disabled."),this.disabled=!0;return}try{this.userId=await this.getUserId(),this.sessionId=this.generateSessionId(),this.sessionStartTime=Date.now(),h.debug&&(console.log("[Analytics] Initialized successfully"),console.log(`[Analytics] User ID: ${this.userId}`),console.log(`[Analytics] Session ID: ${this.sessionId}`))}catch(e){h.debug&&console.warn("[Analytics] Failed to initialize:",e),this.disabled=!0}}}async getUserId(){try{let e=me.join(y.homedir(),".nport");if(A.existsSync(e)||A.mkdirSync(e,{recursive:!0}),A.existsSync(h.userIdFile)){let t=A.readFileSync(h.userIdFile,"utf8").trim();if(t)return t}let n=this.generateAnonymousId();return A.writeFileSync(h.userIdFile,n,"utf8"),n}catch{return this.generateAnonymousId()}}generateAnonymousId(){let e=[y.hostname(),y.platform(),y.arch(),y.homedir()].join("-");return Ze("sha256").update(e).digest("hex").substring(0,32)}generateSessionId(){return Math.floor(Date.now()/1e3)}async trackEvent(e,n={}){if(!(this.disabled||!h.enabled||!this.userId))try{let t=this.buildPayload(e,n),c=`https://www.google-analytics.com/mp/collect?measurement_id=${q.measurementId}&api_secret=${q.apiSecret}`;h.debug&&(console.log(`[Analytics] Sending event: ${e}`),console.log("[Analytics] Payload:",JSON.stringify(t,null,2))),Xe.post(c,t,{timeout:h.timeout,headers:{"Content-Type":"application/json"}}).then(d=>{h.debug&&(console.log(`[Analytics] Response status: ${d.status}`),d.data&&console.log("[Analytics] Response:",JSON.stringify(d.data,null,2)))}).catch(d=>{h.debug&&console.warn("[Analytics] Request failed:",d.message)})}catch(t){h.debug&&console.warn("[Analytics] Error tracking event:",t)}}getEngagementTime(){return this.sessionStartTime?Math.max(100,Date.now()-this.sessionStartTime):100}buildPayload(e,n){return{client_id:this.userId,timestamp_micros:Date.now()*1e3,events:[{name:e,params:{session_id:String(this.sessionId),engagement_time_msec:this.getEngagementTime(),...this.getSystemInfo(),...n}}]}}getSystemInfo(){return{os_platform:y.platform(),os_version:y.release(),os_arch:y.arch(),node_version:process.version}}trackCliStart(e,n,t){this.trackEvent("cli_start",{port:String(e),has_custom_subdomain:n&&!n.startsWith("user-"),cli_version:t})}trackTunnelCreated(e,n){this.trackEvent("tunnel_created",{subdomain_type:e.startsWith("user-")?"random":"custom",port:String(n)})}trackTunnelError(e,n){this.trackEvent("tunnel_error",{error_type:e,error_message:n.substring(0,100)})}trackTunnelShutdown(e,n){this.trackEvent("tunnel_shutdown",{shutdown_reason:e,duration_seconds:String(Math.floor(n))})}trackUpdateAvailable(e,n){this.trackEvent("update_available",{current_version:e,latest_version:n})}},f=new z;var N=class{static{i(this,"VersionManager")}static async checkForUpdates(){try{let n=(await en.get(`https://registry.npmjs.org/${u.PACKAGE_NAME}/latest`,{timeout:u.UPDATE_CHECK_TIMEOUT})).data.version,t=this.compareVersions(n,u.CURRENT_VERSION)>0;return t&&f.trackUpdateAvailable(u.CURRENT_VERSION,n),{current:u.CURRENT_VERSION,latest:n,shouldUpdate:t}}catch{return null}}static compareVersions(e,n){let t=e.split(".").map(Number),r=n.split(".").map(Number),c=Math.max(t.length,r.length);for(let d=0;d<c;d++){let L=t[d]||0,Y=r[d]||0;if(L>Y)return 1;if(L<Y)return-1}return 0}};var S=class{static{i(this,"TunnelOrchestrator")}static async start(e){l.setTunnel(null,e.subdomain,e.port,e.backendUrl),await f.initialize(),f.trackCliStart(e.port,e.subdomain,u.CURRENT_VERSION),m.displayStartupBanner(e.port);let n=await N.checkForUpdates();if(l.setUpdateInfo(n),!tn.existsSync(x.BIN_PATH)){console.log(J.yellow(`
65
+ \u{1F4E6} Cloudflared binary not found. Downloading...
66
+ `));try{await K()}catch(r){f.trackTunnelError("binary_download_failed",r.message),console.error(J.red(`
67
+ \u274C Failed to download cloudflared: ${r.message}`)),process.exit(1)}}v.validate(x.BIN_PATH)||(f.trackTunnelError("binary_missing","Cloudflared binary not found"),await new Promise(r=>setTimeout(r,100)),process.exit(1));let t=nn(s.t("creatingTunnel",{port:e.port})).start();try{let r=await _.createTunnel(e.subdomain,e.backendUrl);l.setTunnel(r.tunnelId,e.subdomain,e.port,e.backendUrl),f.trackTunnelCreated(e.subdomain,e.port),t.stop(),console.log(J.green(` ${s.t("tunnelLive")}`)),m.displayTunnelSuccess(r.url,e.port,n);let c=v.spawn(x.BIN_PATH,r.tunnelToken,e.port);l.setProcess(c),v.attachHandlers(c,t);let d=setTimeout(()=>{m.displayTimeoutWarning(),this.cleanup("timeout")},se);l.setTimeout(d)}catch(r){let c=r,d=c.message.includes("already taken")?"subdomain_taken":"tunnel_creation_failed";f.trackTunnelError(d,c.message),m.displayError(c,t),await new Promise(L=>setTimeout(L,100)),process.exit(1)}}static async cleanup(e="manual"){l.clearTimeout(),l.hasTunnel()||process.exit(0),m.displayCleanupStart();let n=l.getDurationSeconds();f.trackTunnelShutdown(e,n);try{l.hasProcess()&&l.tunnelProcess&&l.tunnelProcess.kill(),l.subdomain&&l.tunnelId&&await _.deleteTunnel(l.subdomain,l.tunnelId,l.backendUrl),m.displayCleanupSuccess()}catch{m.displayCleanupError()}await new Promise(t=>setTimeout(t,100)),process.exit(0)}};async function rn(){let o=on(s.t("checkingUpdates")).start(),e=await N.checkForUpdates();o.stop(),m.displayVersion(u.CURRENT_VERSION,e)}i(rn,"displayVersion");function sn(o){o==="clear"?(w.setBackendUrl(null),console.log(b.green("\u2714 Backend URL cleared. Using default backend.")),console.log(b.gray(` Default: https://api.nport.link
68
+ `))):(w.setBackendUrl(o),console.log(b.green("\u2714 Backend URL saved successfully!")),console.log(b.cyan(` Backend: ${o}`)),console.log(b.gray(`
69
+ This backend will be used for all future sessions.`)),console.log(b.gray(` To clear: nport --set-backend
70
+ `)));let e=w.getBackendUrl();e&&(console.log(b.white("Current configuration:")),console.log(b.cyan(` Saved backend: ${e}`)))}i(sn,"handleSetBackend");async function an(){try{let o=process.argv.slice(2),e=O.parse(o);await s.initialize(e.language),(o.includes("-v")||o.includes("--version"))&&(await rn(),process.exit(0)),e.setBackend&&(sn(e.setBackend),process.exit(0)),e.language==="prompt"&&(o.includes("--language")||o.includes("--lang")||o.includes("-l"))&&process.exit(0);let n=e.backendUrl;if(!n){let r=w.getBackendUrl();r&&(n=r)}let t={port:e.port,subdomain:e.subdomain,backendUrl:n,language:e.language};await S.start(t)}catch(o){console.error(`Fatal Error: ${o.message}`),process.exit(1)}}i(an,"main");process.on("SIGINT",()=>S.cleanup());process.on("SIGTERM",()=>S.cleanup());an();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nport",
3
- "version": "2.1.1",
3
+ "version": "2.1.3",
4
4
  "description": "Free & open source ngrok alternative - Tunnel HTTP/HTTPS connections via Cloudflare Edge with custom subdomains",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -63,13 +63,14 @@
63
63
  }
64
64
  ],
65
65
  "scripts": {
66
- "build": "tsc",
67
- "dev": "tsc --watch",
66
+ "build": "node scripts/build.js",
67
+ "dev": "node scripts/build.js --watch",
68
68
  "start": "node dist/index.js",
69
69
  "postinstall": "node scripts/postinstall.js",
70
70
  "test": "vitest run",
71
71
  "test:watch": "vitest",
72
- "lint": "tsc --noEmit"
72
+ "lint": "tsc --noEmit",
73
+ "typecheck": "tsc --noEmit"
73
74
  },
74
75
  "dependencies": {
75
76
  "axios": "^1.13.2",
@@ -78,15 +79,14 @@
78
79
  },
79
80
  "devDependencies": {
80
81
  "@types/node": "^22.0.0",
82
+ "esbuild": "^0.24.0",
81
83
  "typescript": "^5.7.0",
82
84
  "vitest": "~3.2.0"
83
85
  },
84
86
  "files": [
85
87
  "dist/",
86
- "bin/",
87
- "scripts/",
88
+ "scripts/postinstall.js",
88
89
  "README.md",
89
- "CHANGELOG.md",
90
90
  "LICENSE"
91
91
  ],
92
92
  "os": [