nport 2.1.4 → 2.1.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.
@@ -1 +1 @@
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};
1
+ var I=Object.defineProperty;var o=(r,n)=>I(r,"name",{value:n,configurable:!0});import e 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),A=u.dirname(u.dirname(_)),m=u.join(A,"bin"),p="cloudflared",v=".tgz",R="cloudflared.tgz",f=w.platform(),g=w.arch(),h=f==="win32",T=h?`${p}.exe`:p,a=u.join(m,T),N="https://github.com/cloudflare/cloudflared/releases/latest/download",b=[301,302],M=200,D="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 P(r){return{x64:"x64",amd64:"amd64",arm64:"arm64",ia32:"ia32",arm:"arm"}[r]||r}o(P,"normalizeArch");function B(){let r=P(g),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: ${g} for platform ${f}. Supported architectures: ${Object.keys(n).join(", ")}`);return`${N}/${t}`}o(B,"getDownloadUrl");function O(r){return r.endsWith(v)}o(O,"isCompressedArchive");function L(r){e.existsSync(r)||e.mkdirSync(r,{recursive:!0})}o(L,"ensureDirectory");function l(r){try{e.existsSync(r)&&e.unlinkSync(r)}catch{}}o(l,"safeUnlink");function y(r,n=D){if(!h){try{e.accessSync(r,e.constants.X_OK);return}catch{}try{e.chmodSync(r,n)}catch(t){let s=t;if(s.code==="EPERM"||s.code==="EACCES")console.warn(`\u26A0\uFE0F Could not set executable permissions on ${r} (permission denied). This usually happens when nport was installed with sudo. The binary should still work. If it doesn't, try: chmod 755 ${r}`);else throw t}}}o(y,"setExecutablePermissions");function z(r,n){if(!e.existsSync(r))throw new Error(n||`File not found: ${r}`)}o(z,"validateFileExists");async function x(r,n){return new Promise((t,s)=>{let i=e.createWriteStream(n);S.get(r,c=>{if(b.includes(c.statusCode)){i.close(),l(n),x(c.headers.location,n).then(t).catch(s);return}if(c.statusCode!==M){i.close(),l(n),s(new Error(`Download failed with status code ${c.statusCode} from ${r}`));return}c.pipe(i),i.on("finish",()=>{i.close(()=>t(n))}),i.on("error",C=>{i.close(),l(n),s(C)})}).on("error",c=>{i.close(),l(n),s(new Error(`Network error: ${c.message}`))})})}o(x,"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 d={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(){d.progress("Cloudflared binary not found. Downloading... (This happens only once)");let r=B(),n=O(r),t=n?u.join(m,R):a;try{return await x(r,t),n&&(d.extract("Extracting binary..."),F(t,m),l(t),z(a,"Extraction failed: Binary not found after extraction")),y(a),d.success("Download complete."),a}catch(s){throw l(t),l(a),s}}o(k,"installBinary");async function G(){if(L(m),e.existsSync(a))return y(a),a;try{return await k()}catch(r){d.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()){d.info("Running in CI environment - skipping binary download");return}try{let r=await G();d.success(`Cloudflared binary is ready at: ${r}`)}catch(r){d.error(r.message),process.exit(1)}}o(W,"main");var X=E(import.meta.url);(process.argv[1]===X||process.argv[1]?.endsWith("bin-manager.js"))&&W();export{G as ensureCloudflared};
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- var ye=Object.defineProperty;var i=(r,e)=>ye(r,"name",{value:e,configurable:!0});import tn from"ora";import b from"chalk";import x from"path";import{fileURLToPath as Ee}from"url";import{createRequire as be}from"module";var Q="https://api.nport.link";var Z="user-";var $={THRESHOLD:5,COOLDOWN:3e4},ee={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"]},ne=["en","vi"],te="https://github.com/tuanngocptn/nport",W="https://nport.link",oe="https://buymeacoffee.com/tuanngocptn";var Ce=Ee(import.meta.url),re=x.dirname(x.dirname(Ce)),ke=be(import.meta.url),se=ke("../package.json");function ve(){return process.env.NPORT_BACKEND_URL?process.env.NPORT_BACKEND_URL:Q}i(ve,"getBackendUrl");var g={PACKAGE_NAME:se.name,CURRENT_VERSION:se.version,BACKEND_URL:ve(),DEFAULT_PORT:8080,SUBDOMAIN_PREFIX:Z,TUNNEL_TIMEOUT_HOURS:4,UPDATE_CHECK_TIMEOUT:3e3},B={IS_WINDOWS:process.platform==="win32",BIN_NAME:process.platform==="win32"?"cloudflared.exe":"cloudflared"},M={BIN_DIR:x.join(re,"bin"),BIN_PATH:x.join(re,"bin",B.BIN_NAME)},R=ee,G={WARNING_THRESHOLD:$.THRESHOLD,WARNING_COOLDOWN:$.COOLDOWN},ie=g.TUNNEL_TIMEOUT_HOURS*60*60*1e3;var P=class{static{i(this,"ArgumentParser")}static parse(e){let n=this.parsePort(e),t=this.parseSubdomain(e),o=this.parseLanguage(e),l=this.parseBackendUrl(e),d=this.parseSetBackend(e);return{port:n,subdomain:t,language:o,backendUrl:l,setBackend:d}}static parsePort(e){return parseInt(e[0],10)||g.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 o=t();if(o)return o}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"),o=e[t+1];if(!o||o.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 o=t();if(o)return o}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 o=t();if(o)return o}return null}static parseSetBackend(e){let n=[()=>this.findFlagWithEquals(e,"--set-backend="),()=>this.findFlagWithValue(e,"--set-backend")];for(let t of n){let o=t();if(o)return o}return e.includes("--set-backend")?"clear":null}static findFlagWithEquals(e,n){let t=e.find(o=>o.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`${g.SUBDOMAIN_PREFIX}${Math.floor(Math.random()*1e4)}`}};import nn from"ora";import fe from"chalk";var V=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,o=null){this.tunnelId=e,this.subdomain=n,this.port=t,this.backendUrl=o,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}},c=new V;import{spawn as Re}from"child_process";import u from"chalk";import O from"fs";import a from"chalk";import Se from"readline";import T from"fs";import H from"path";import Ne from"os";var j=class{static{i(this,"ConfigManager")}configDir;configFile;oldLangFile;config;constructor(){this.configDir=H.join(Ne.homedir(),".nport"),this.configFile=H.join(this.configDir,"config.json"),this.oldLangFile=H.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 j;var K={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:`
2
+ var ye=Object.defineProperty;var i=(r,e)=>ye(r,"name",{value:e,configurable:!0});import tn from"ora";import b from"chalk";import x from"path";import{fileURLToPath as Ee}from"url";import{createRequire as be}from"module";var Q="https://api.nport.link";var Z="user-";var $={THRESHOLD:5,COOLDOWN:3e4},ee={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"]},ne=["en","vi"],te="https://github.com/tuanngocptn/nport",W="https://nport.link",oe="https://buymeacoffee.com/tuanngocptn";var Ce=Ee(import.meta.url),re=x.dirname(x.dirname(Ce)),ke=be(import.meta.url),se=ke("../package.json");function ve(){return process.env.NPORT_BACKEND_URL?process.env.NPORT_BACKEND_URL:Q}i(ve,"getBackendUrl");var g={PACKAGE_NAME:se.name,CURRENT_VERSION:se.version,BACKEND_URL:ve(),DEFAULT_PORT:8080,SUBDOMAIN_PREFIX:Z,TUNNEL_TIMEOUT_HOURS:4,UPDATE_CHECK_TIMEOUT:3e3},B={IS_WINDOWS:process.platform==="win32",BIN_NAME:process.platform==="win32"?"cloudflared.exe":"cloudflared"},M={BIN_DIR:x.join(re,"bin"),BIN_PATH:x.join(re,"bin",B.BIN_NAME)},R=ee,G={WARNING_THRESHOLD:$.THRESHOLD,WARNING_COOLDOWN:$.COOLDOWN},ie=g.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),o=this.parseLanguage(e),l=this.parseBackendUrl(e),d=this.parseSetBackend(e);return{port:n,subdomain:t,language:o,backendUrl:l,setBackend:d}}static parsePort(e){return parseInt(e[0],10)||g.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 o=t();if(o)return o}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"),o=e[t+1];if(!o||o.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 o=t();if(o)return o}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 o=t();if(o)return o}return null}static parseSetBackend(e){let n=[()=>this.findFlagWithEquals(e,"--set-backend="),()=>this.findFlagWithValue(e,"--set-backend")];for(let t of n){let o=t();if(o)return o}return e.includes("--set-backend")?"clear":null}static findFlagWithEquals(e,n){let t=e.find(o=>o.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`${g.SUBDOMAIN_PREFIX}${Math.floor(Math.random()*1e4)}`}};import nn from"ora";import fe from"chalk";var V=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,o=null){this.tunnelId=e,this.subdomain=n,this.port=t,this.backendUrl=o,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}},c=new V;import{spawn as Re}from"child_process";import u from"chalk";import P from"fs";import a from"chalk";import Se from"readline";import w from"fs";import H from"path";import Ne from"os";var j=class{static{i(this,"ConfigManager")}configDir;configFile;oldLangFile;config;constructor(){this.configDir=H.join(Ne.homedir(),".nport"),this.configFile=H.join(this.configDir,"config.json"),this.oldLangFile=H.join(this.configDir,"lang"),this.config=this.loadConfig(),this.migrateOldConfig()}loadConfig(){try{if(w.existsSync(this.configFile)){let e=w.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&&w.existsSync(this.oldLangFile)){let e=w.readFileSync(this.oldLangFile,"utf8").trim();if(e&&["en","vi"].includes(e)){this.config.language=e,this.saveConfig();try{w.unlinkSync(this.oldLangFile)}catch{}}}}catch{}}saveConfig(){try{return w.existsSync(this.configDir)||w.mkdirSync(this.configDir,{recursive:!0}),w.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()}},I=new j;var K={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
3
  \u{1F30D} Language Selection / Ch\u1ECDn ng\xF4n ng\u1EEF
4
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
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:`
@@ -13,7 +13,7 @@ var ye=Object.defineProperty;var i=(r,e)=>ye(r,"name",{value:e,configurable:!0})
13
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
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
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
- `}},q=class{static{i(this,"LanguageManager")}currentLanguage="en";availableLanguages=ne;t(e,n={}){let o=(K[this.currentLanguage]||K.en)[e]||K.en[e]||e;return Object.keys(n).forEach(l=>{let d=n[l];d!==void 0&&(o=o.replace(`{${l}}`,String(d)))}),o}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=Se.createInterface({input:process.stdin,output:process.stdout});console.log(this.t("languagePrompt")),console.log(` ${this.t("languageEnglish")}`),console.log(` ${this.t("languageVietnamese")}
16
+ `}},q=class{static{i(this,"LanguageManager")}currentLanguage="en";availableLanguages=ne;t(e,n={}){let o=(K[this.currentLanguage]||K.en)[e]||K.en[e]||e;return Object.keys(n).forEach(l=>{let d=n[l];d!==void 0&&(o=o.replace(`{${l}}`,String(d)))}),o}loadLanguagePreference(){let e=I.getLanguage();return e&&this.availableLanguages.includes(e)?e:null}saveLanguagePreference(e){I.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=Se.createInterface({input:process.stdin,output:process.stdout});console.log(this.t("languagePrompt")),console.log(` ${this.t("languageEnglish")}`),console.log(` ${this.t("languageVietnamese")}
17
17
  `),n.question(`${this.t("languageQuestion")}`,t=>{n.close();let o=t.trim(),l="en";o==="1"?l="en":o==="2"?l="vi":console.log(`
18
18
  ${this.t("languageInvalid")}
19
19
  `),this.setLanguage(l),this.saveLanguagePreference(l),console.log(`${this.t("languageSaved")}
@@ -40,13 +40,13 @@ ${s.t("versionTitle",{version:e})}`)),console.log(a.gray(`${s.t("versionSubtitle
40
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
41
  `))):console.log(a.green(`${s.t("versionLatest")}
42
42
  `)),console.log(a.gray(s.t("learnMore"))+a.cyan(`${W}
43
- `))}};var v=class{static{i(this,"BinaryManager")}static validate(e){if(!O.existsSync(e))return console.error(u.red(`
43
+ `))}};var v=class{static{i(this,"BinaryManager")}static validate(e){if(!P.existsSync(e))return console.error(u.red(`
44
44
  \u274C Error: Cloudflared binary not found at: ${e}`)),console.error(u.yellow(`\u{1F449} Please run 'npm install' again to download the binary.
45
- `)),!1;if(!B.IS_WINDOWS)try{O.accessSync(e,O.constants.X_OK)}catch{try{O.chmodSync(e,493)}catch{return console.error(u.red(`
45
+ `)),!1;if(!B.IS_WINDOWS)try{P.accessSync(e,P.constants.X_OK)}catch{try{P.chmodSync(e,493)}catch{return console.error(u.red(`
46
46
  \u274C Error: Cloudflared binary is not executable: ${e}`)),console.error(u.yellow(`\u{1F449} Fix it manually: chmod +x ${e}
47
47
  `)),!1}}return!0}static spawn(e,n,t){let o=process.platform==="win32";return Re(e,["tunnel","run","--token",n,"--url",`http://localhost:${t}`],{windowsHide:!0,shell:o,stdio:["ignore","pipe","pipe"]})}static attachHandlers(e,n=null,t){e.stderr?.on("data",o=>this.handleStderr(o)),e.on("error",o=>this.handleError(o,n,t)),e.on("close",o=>this.handleClose(o))}static handleStderr(e){let n=e.toString();if(!R.IGNORE.some(t=>n.includes(t))){if(R.NETWORK_WARNING.some(t=>n.includes(t))){this.handleNetworkWarning();return}if(R.SUCCESS.some(t=>n.includes(t))){let t=c.incrementConnection();t===1?(c.resetNetworkIssues(),console.log(u.green(s.t("connection1")))):t===4&&(console.log(u.green(s.t("connection2"))),m.displayFooter(c.updateInfo));return}R.ERROR.some(t=>n.includes(t))&&console.error(u.red(`[Cloudflared] ${n.trim()}`))}}static handleNetworkWarning(){c.incrementNetworkIssue(),c.shouldShowNetworkWarning(G.WARNING_THRESHOLD,G.WARNING_COOLDOWN)&&this.displayNetworkWarning()}static displayNetworkWarning(){console.log(u.yellow(s.t("networkIssueTitle"))),console.log(u.gray(s.t("networkIssueDesc"))),console.log(u.cyan(s.t("networkIssueTunnel"))),console.log(u.yellow(s.t("networkIssueReasons"))),console.log(u.gray(s.t("networkIssueReason1"))),console.log(u.gray(s.t("networkIssueReason2"))),console.log(u.gray(s.t("networkIssueReason3"))),console.log(u.yellow(s.t("networkIssueFix"))),console.log(u.gray(s.t("networkIssueFix1"))),console.log(u.gray(s.t("networkIssueFix2"))),console.log(u.gray(s.t("networkIssueFix3"))),console.log(u.gray(s.t("networkIssueFix4"))),console.log(u.blue(s.t("networkIssueIgnore")))}static handleError(e,n,t){n&&n.fail("Failed to spawn cloudflared process."),console.error(u.red(`Process Error: ${e.message}`)),process.platform==="win32"?(e.message.includes("UNKNOWN")||e.message.includes("ENOENT"))&&(console.error(u.yellow(`
48
48
  \u{1F4A1} Windows troubleshooting tips:`)),console.error(u.gray(" 1. Check if Windows Defender/antivirus is blocking cloudflared.exe")),console.error(u.gray(" 2. Try running the terminal as Administrator")),console.error(u.gray(" 3. Reinstall nport: npm uninstall -g nport && npm install -g nport"))):e.message.includes("EACCES")&&(console.error(u.yellow(`
49
- \u{1F4A1} Permission denied troubleshooting:`)),console.error(u.gray(" 1. Run: chmod +x <path-to-nport>/bin/cloudflared")),console.error(u.gray(" 2. Or reinstall: npm uninstall -g nport && npm install -g nport"))),t&&t()}static handleClose(e){e!==0&&e!==null&&console.log(u.red(`Tunnel process exited with code ${e}`))}};import E from"fs";import U from"path";import Ue from"https";import ce from"os";import{execSync as _e}from"child_process";import{fileURLToPath as ue}from"url";var Ae=ue(import.meta.url),Le=U.dirname(U.dirname(Ae)),D=U.join(Le,"bin"),ae="cloudflared",xe=".tgz",Pe="cloudflared.tgz",F=ce.platform(),le=ce.arch(),ge=F==="win32",Oe=ge?`${ae}.exe`:ae,I=U.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(r){return{x64:"x64",amd64:"amd64",arm64:"arm64",ia32:"ia32",arm:"arm"}[r]||r}i(Me,"normalizeArch");function Ge(){let r=Me(le),e=Be[F];if(!e)throw new Error(`Unsupported platform: ${F}. Supported platforms: darwin, win32, linux`);let n=e[r];if(!n)throw new Error(`Unsupported architecture: ${le} for platform ${F}. Supported architectures: ${Object.keys(e).join(", ")}`);return`${Fe}/${n}`}i(Ge,"getDownloadUrl");function Ve(r){return r.endsWith(xe)}i(Ve,"isCompressedArchive");function He(r){E.existsSync(r)||E.mkdirSync(r,{recursive:!0})}i(He,"ensureDirectory");function C(r){try{E.existsSync(r)&&E.unlinkSync(r)}catch{}}i(C,"safeUnlink");function de(r,e=We){ge||E.chmodSync(r,e)}i(de,"setExecutablePermissions");function je(r,e){if(!E.existsSync(r))throw new Error(e||`File not found: ${r}`)}i(je,"validateFileExists");async function pe(r,e){return new Promise((n,t)=>{let o=E.createWriteStream(e);Ue.get(r,l=>{if(De.includes(l.statusCode)){o.close(),C(e),pe(l.headers.location,e).then(n).catch(t);return}if(l.statusCode!==$e){o.close(),C(e),t(new Error(`Download failed with status code ${l.statusCode} from ${r}`));return}l.pipe(o),o.on("finish",()=>{o.close(()=>n(e))}),o.on("error",d=>{o.close(),C(e),t(d)})}).on("error",l=>{o.close(),C(e),t(new Error(`Network error: ${l.message}`))})})}i(pe,"downloadFile");function Ke(r,e){try{_e(`tar -xzf "${r}" -C "${e}"`,{stdio:"pipe"})}catch(n){throw new Error(`Extraction failed: ${n.message}`)}}i(Ke,"extractTarGz");var k={info:i(r=>console.log(`\u2139\uFE0F ${r}`),"info"),success:i(r=>console.log(`\u2705 ${r}`),"success"),warn:i(r=>console.warn(`\u26A0\uFE0F ${r}`),"warn"),error:i(r=>console.error(`\u274C ${r}`),"error"),progress:i(r=>console.log(`\u{1F6A7} ${r}`),"progress"),extract:i(r=>console.log(`\u{1F4E6} ${r}`),"extract")};async function qe(){k.progress("Cloudflared binary not found. Downloading... (This happens only once)");let r=Ge(),e=Ve(r),n=e?U.join(D,Pe):I;try{return await pe(r,n),e&&(k.extract("Extracting binary..."),Ke(n,D),C(n),je(I,"Extraction failed: Binary not found after extraction")),de(I),k.success("Download complete."),I}catch(t){throw C(n),C(I),t}}i(qe,"installBinary");async function z(){if(He(D),E.existsSync(I))return de(I),I;try{return await qe()}catch(r){k.error(`Installation failed: ${r.message}`),process.exit(1)}}i(z,"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()){k.info("Running in CI environment - skipping binary download");return}try{let r=await z();k.success(`Cloudflared binary is ready at: ${r}`)}catch(r){k.error(r.message),process.exit(1)}}i(Je,"main");var Ye=ue(import.meta.url);(process.argv[1]===Ye||process.argv[1]?.endsWith("bin-manager.js"))&&Je();import me from"axios";import p from"chalk";var _=class{static{i(this,"APIClient")}static async createTunnel(e,n=null){let t=n||g.BACKEND_URL;try{let{data:o}=await me.post(t,{subdomain:e});if(!o.success)throw new Error(o.error||"Unknown error from backend");return{tunnelId:o.tunnelId,tunnelToken:o.tunnelToken,url:o.url}}catch(o){throw this.handleError(o,e)}}static async deleteTunnel(e,n,t=null){let o=t||g.BACKEND_URL;await me.delete(o,{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.
49
+ \u{1F4A1} Permission denied troubleshooting:`)),console.error(u.gray(" 1. Run: chmod +x <path-to-nport>/bin/cloudflared")),console.error(u.gray(" 2. Or reinstall: npm uninstall -g nport && npm install -g nport"))),t&&t()}static handleClose(e){e!==0&&e!==null&&console.log(u.red(`Tunnel process exited with code ${e}`))}};import f from"fs";import U from"path";import Ue from"https";import ce from"os";import{execSync as _e}from"child_process";import{fileURLToPath as ue}from"url";var Ae=ue(import.meta.url),Le=U.dirname(U.dirname(Ae)),D=U.join(Le,"bin"),ae="cloudflared",xe=".tgz",Oe="cloudflared.tgz",F=ce.platform(),le=ce.arch(),ge=F==="win32",Pe=ge?`${ae}.exe`:ae,E=U.join(D,Pe),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(r){return{x64:"x64",amd64:"amd64",arm64:"arm64",ia32:"ia32",arm:"arm"}[r]||r}i(Me,"normalizeArch");function Ge(){let r=Me(le),e=Be[F];if(!e)throw new Error(`Unsupported platform: ${F}. Supported platforms: darwin, win32, linux`);let n=e[r];if(!n)throw new Error(`Unsupported architecture: ${le} for platform ${F}. Supported architectures: ${Object.keys(e).join(", ")}`);return`${Fe}/${n}`}i(Ge,"getDownloadUrl");function Ve(r){return r.endsWith(xe)}i(Ve,"isCompressedArchive");function He(r){f.existsSync(r)||f.mkdirSync(r,{recursive:!0})}i(He,"ensureDirectory");function C(r){try{f.existsSync(r)&&f.unlinkSync(r)}catch{}}i(C,"safeUnlink");function de(r,e=We){if(!ge){try{f.accessSync(r,f.constants.X_OK);return}catch{}try{f.chmodSync(r,e)}catch(n){let t=n;if(t.code==="EPERM"||t.code==="EACCES")console.warn(`\u26A0\uFE0F Could not set executable permissions on ${r} (permission denied). This usually happens when nport was installed with sudo. The binary should still work. If it doesn't, try: chmod 755 ${r}`);else throw n}}}i(de,"setExecutablePermissions");function je(r,e){if(!f.existsSync(r))throw new Error(e||`File not found: ${r}`)}i(je,"validateFileExists");async function pe(r,e){return new Promise((n,t)=>{let o=f.createWriteStream(e);Ue.get(r,l=>{if(De.includes(l.statusCode)){o.close(),C(e),pe(l.headers.location,e).then(n).catch(t);return}if(l.statusCode!==$e){o.close(),C(e),t(new Error(`Download failed with status code ${l.statusCode} from ${r}`));return}l.pipe(o),o.on("finish",()=>{o.close(()=>n(e))}),o.on("error",d=>{o.close(),C(e),t(d)})}).on("error",l=>{o.close(),C(e),t(new Error(`Network error: ${l.message}`))})})}i(pe,"downloadFile");function Ke(r,e){try{_e(`tar -xzf "${r}" -C "${e}"`,{stdio:"pipe"})}catch(n){throw new Error(`Extraction failed: ${n.message}`)}}i(Ke,"extractTarGz");var k={info:i(r=>console.log(`\u2139\uFE0F ${r}`),"info"),success:i(r=>console.log(`\u2705 ${r}`),"success"),warn:i(r=>console.warn(`\u26A0\uFE0F ${r}`),"warn"),error:i(r=>console.error(`\u274C ${r}`),"error"),progress:i(r=>console.log(`\u{1F6A7} ${r}`),"progress"),extract:i(r=>console.log(`\u{1F4E6} ${r}`),"extract")};async function qe(){k.progress("Cloudflared binary not found. Downloading... (This happens only once)");let r=Ge(),e=Ve(r),n=e?U.join(D,Oe):E;try{return await pe(r,n),e&&(k.extract("Extracting binary..."),Ke(n,D),C(n),je(E,"Extraction failed: Binary not found after extraction")),de(E),k.success("Download complete."),E}catch(t){throw C(n),C(E),t}}i(qe,"installBinary");async function z(){if(He(D),f.existsSync(E))return de(E),E;try{return await qe()}catch(r){k.error(`Installation failed: ${r.message}`),process.exit(1)}}i(z,"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()){k.info("Running in CI environment - skipping binary download");return}try{let r=await z();k.success(`Cloudflared binary is ready at: ${r}`)}catch(r){k.error(r.message),process.exit(1)}}i(Je,"main");var Xe=ue(import.meta.url);(process.argv[1]===Xe||process.argv[1]?.endsWith("bin-manager.js"))&&Je();import me from"axios";import p from"chalk";var _=class{static{i(this,"APIClient")}static async createTunnel(e,n=null){let t=n||g.BACKEND_URL;try{let{data:o}=await me.post(t,{subdomain:e});if(!o.success)throw new Error(o.error||"Unknown error from backend");return{tunnelId:o.tunnelId,tunnelToken:o.tunnelToken,url:o.url}}catch(o){throw this.handleError(o,e)}}static async deleteTunnel(e,n,t=null){let o=t||g.BACKEND_URL;await me.delete(o,{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.
50
50
 
51
51
  `+p.yellow(`\u{1F4A1} Try one of these options:
52
52
  `)+p.gray(" 1. Choose a different subdomain: ")+p.cyan(`nport ${c.port||g.DEFAULT_PORT} -s ${n}-v2
@@ -64,8 +64,8 @@ ${s.t("versionTitle",{version:e})}`)),console.log(a.gray(`${s.t("versionSubtitle
64
64
  `+p.yellow(`\u{1F4A1} Try one of these options:
65
65
  `)+p.gray(" 1. Choose a different subdomain: ")+p.cyan(`nport ${c.port||g.DEFAULT_PORT} -s ${n}-v2
66
66
  `)+p.gray(" 2. Use a random subdomain: ")+p.cyan(`nport ${c.port||g.DEFAULT_PORT}
67
- `)+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 Qe from"axios";import{createHash as Ze}from"crypto";import y from"os";import A from"fs";import he from"path";var J={measurementId:"G-JJHG4DP1K9",apiSecret:"NjNID8jtRJe9s8uSBz2jfw"},h={enabled:!0,debug:process.env.NPORT_DEBUG==="true",timeout:2e3,userIdFile:he.join(y.homedir(),".nport","analytics-id")},Y=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(!J.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=he.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),l=`https://www.google-analytics.com/mp/collect?measurement_id=${J.measurementId}&api_secret=${J.apiSecret}`;h.debug&&(console.log(`[Analytics] Sending event: ${e}`),console.log("[Analytics] Payload:",JSON.stringify(t,null,2))),Qe.post(l,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 Y;var N=class{static{i(this,"VersionManager")}static async checkForUpdates(){try{let n=(await en.get(`https://registry.npmjs.org/${g.PACKAGE_NAME}/latest`,{timeout:g.UPDATE_CHECK_TIMEOUT})).data.version,t=this.compareVersions(n,g.CURRENT_VERSION)>0;return t&&f.trackUpdateAvailable(g.CURRENT_VERSION,n),{current:g.CURRENT_VERSION,latest:n,shouldUpdate:t}}catch{return null}}static compareVersions(e,n){let t=e.split(".").map(Number),o=n.split(".").map(Number),l=Math.max(t.length,o.length);for(let d=0;d<l;d++){let L=t[d]||0,X=o[d]||0;if(L>X)return 1;if(L<X)return-1}return 0}};var S=class{static{i(this,"TunnelOrchestrator")}static async start(e){c.setTunnel(null,e.subdomain,e.port,e.backendUrl),await f.initialize(),f.trackCliStart(e.port,e.subdomain,g.CURRENT_VERSION),m.displayStartupBanner(e.port);let n=await N.checkForUpdates();c.setUpdateInfo(n);try{await z()}catch(o){f.trackTunnelError("binary_download_failed",o.message),console.error(fe.red(`
68
- \u274C Failed to prepare cloudflared: ${o.message}`)),await new Promise(l=>setTimeout(l,100)),process.exit(1)}v.validate(M.BIN_PATH)||(f.trackTunnelError("binary_missing","Cloudflared binary not found"),await new Promise(o=>setTimeout(o,100)),process.exit(1));let t=nn(s.t("creatingTunnel",{port:e.port})).start();try{let o=await _.createTunnel(e.subdomain,e.backendUrl);c.setTunnel(o.tunnelId,e.subdomain,e.port,e.backendUrl),f.trackTunnelCreated(e.subdomain,e.port),t.stop(),console.log(fe.green(` ${s.t("tunnelLive")}`)),m.displayTunnelSuccess(o.url,e.port,n);let l=v.spawn(M.BIN_PATH,o.tunnelToken,e.port);c.setProcess(l),v.attachHandlers(l,t,()=>this.cleanup("error"));let d=setTimeout(()=>{m.displayTimeoutWarning(),this.cleanup("timeout")},ie);c.setTimeout(d)}catch(o){let l=o,d=l.message.includes("already taken")?"subdomain_taken":"tunnel_creation_failed";f.trackTunnelError(d,l.message),m.displayError(l,t),await new Promise(L=>setTimeout(L,100)),process.exit(1)}}static async cleanup(e="manual"){c.clearTimeout(),c.hasTunnel()||process.exit(0),m.displayCleanupStart();let n=c.getDurationSeconds();f.trackTunnelShutdown(e,n);try{c.hasProcess()&&c.tunnelProcess&&c.tunnelProcess.kill(),c.subdomain&&c.tunnelId&&await _.deleteTunnel(c.subdomain,c.tunnelId,c.backendUrl),m.displayCleanupSuccess()}catch{m.displayCleanupError()}await new Promise(t=>setTimeout(t,100)),process.exit(0)}};async function on(){let r=tn(s.t("checkingUpdates")).start(),e=await N.checkForUpdates();r.stop(),m.displayVersion(g.CURRENT_VERSION,e)}i(on,"displayVersion");function rn(r){r==="clear"?(w.setBackendUrl(null),console.log(b.green("\u2714 Backend URL cleared. Using default backend.")),console.log(b.gray(` Default: https://api.nport.link
69
- `))):(w.setBackendUrl(r),console.log(b.green("\u2714 Backend URL saved successfully!")),console.log(b.cyan(` Backend: ${r}`)),console.log(b.gray(`
67
+ `)+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 Qe from"axios";import{createHash as Ze}from"crypto";import T from"os";import A from"fs";import he from"path";var J={measurementId:"G-JJHG4DP1K9",apiSecret:"NjNID8jtRJe9s8uSBz2jfw"},h={enabled:!0,debug:process.env.NPORT_DEBUG==="true",timeout:2e3,userIdFile:he.join(T.homedir(),".nport","analytics-id")},X=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(!J.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=he.join(T.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=[T.hostname(),T.platform(),T.arch(),T.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),l=`https://www.google-analytics.com/mp/collect?measurement_id=${J.measurementId}&api_secret=${J.apiSecret}`;h.debug&&(console.log(`[Analytics] Sending event: ${e}`),console.log("[Analytics] Payload:",JSON.stringify(t,null,2))),Qe.post(l,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:T.platform(),os_version:T.release(),os_arch:T.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})}},y=new X;var N=class{static{i(this,"VersionManager")}static async checkForUpdates(){try{let n=(await en.get(`https://registry.npmjs.org/${g.PACKAGE_NAME}/latest`,{timeout:g.UPDATE_CHECK_TIMEOUT})).data.version,t=this.compareVersions(n,g.CURRENT_VERSION)>0;return t&&y.trackUpdateAvailable(g.CURRENT_VERSION,n),{current:g.CURRENT_VERSION,latest:n,shouldUpdate:t}}catch{return null}}static compareVersions(e,n){let t=e.split(".").map(Number),o=n.split(".").map(Number),l=Math.max(t.length,o.length);for(let d=0;d<l;d++){let L=t[d]||0,Y=o[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){c.setTunnel(null,e.subdomain,e.port,e.backendUrl),await y.initialize(),y.trackCliStart(e.port,e.subdomain,g.CURRENT_VERSION),m.displayStartupBanner(e.port);let n=await N.checkForUpdates();c.setUpdateInfo(n);try{await z()}catch(o){y.trackTunnelError("binary_download_failed",o.message),console.error(fe.red(`
68
+ \u274C Failed to prepare cloudflared: ${o.message}`)),await new Promise(l=>setTimeout(l,100)),process.exit(1)}v.validate(M.BIN_PATH)||(y.trackTunnelError("binary_missing","Cloudflared binary not found"),await new Promise(o=>setTimeout(o,100)),process.exit(1));let t=nn(s.t("creatingTunnel",{port:e.port})).start();try{let o=await _.createTunnel(e.subdomain,e.backendUrl);c.setTunnel(o.tunnelId,e.subdomain,e.port,e.backendUrl),y.trackTunnelCreated(e.subdomain,e.port),t.stop(),console.log(fe.green(` ${s.t("tunnelLive")}`)),m.displayTunnelSuccess(o.url,e.port,n);let l=v.spawn(M.BIN_PATH,o.tunnelToken,e.port);c.setProcess(l),v.attachHandlers(l,t,()=>this.cleanup("error"));let d=setTimeout(()=>{m.displayTimeoutWarning(),this.cleanup("timeout")},ie);c.setTimeout(d)}catch(o){let l=o,d=l.message.includes("already taken")?"subdomain_taken":"tunnel_creation_failed";y.trackTunnelError(d,l.message),m.displayError(l,t),await new Promise(L=>setTimeout(L,100)),process.exit(1)}}static async cleanup(e="manual"){c.clearTimeout(),c.hasTunnel()||process.exit(0),m.displayCleanupStart();let n=c.getDurationSeconds();y.trackTunnelShutdown(e,n);try{c.hasProcess()&&c.tunnelProcess&&c.tunnelProcess.kill(),c.subdomain&&c.tunnelId&&await _.deleteTunnel(c.subdomain,c.tunnelId,c.backendUrl),m.displayCleanupSuccess()}catch{m.displayCleanupError()}await new Promise(t=>setTimeout(t,100)),process.exit(0)}};async function on(){let r=tn(s.t("checkingUpdates")).start(),e=await N.checkForUpdates();r.stop(),m.displayVersion(g.CURRENT_VERSION,e)}i(on,"displayVersion");function rn(r){r==="clear"?(I.setBackendUrl(null),console.log(b.green("\u2714 Backend URL cleared. Using default backend.")),console.log(b.gray(` Default: https://api.nport.link
69
+ `))):(I.setBackendUrl(r),console.log(b.green("\u2714 Backend URL saved successfully!")),console.log(b.cyan(` Backend: ${r}`)),console.log(b.gray(`
70
70
  This backend will be used for all future sessions.`)),console.log(b.gray(` To clear: nport --set-backend
71
- `)));let e=w.getBackendUrl();e&&(console.log(b.white("Current configuration:")),console.log(b.cyan(` Saved backend: ${e}`)))}i(rn,"handleSetBackend");async function sn(){try{let r=process.argv.slice(2),e=P.parse(r);await s.initialize(e.language),(r.includes("-v")||r.includes("--version"))&&(await on(),process.exit(0)),e.setBackend&&(rn(e.setBackend),process.exit(0)),e.language==="prompt"&&(r.includes("--language")||r.includes("--lang")||r.includes("-l"))&&process.exit(0);let n=e.backendUrl;if(!n){let o=w.getBackendUrl();o&&(n=o)}let t={port:e.port,subdomain:e.subdomain,backendUrl:n,language:e.language};await S.start(t)}catch(r){console.error(`Fatal Error: ${r.message}`),process.exit(1)}}i(sn,"main");process.on("SIGINT",()=>S.cleanup());process.on("SIGTERM",()=>S.cleanup());sn();
71
+ `)));let e=I.getBackendUrl();e&&(console.log(b.white("Current configuration:")),console.log(b.cyan(` Saved backend: ${e}`)))}i(rn,"handleSetBackend");async function sn(){try{let r=process.argv.slice(2),e=O.parse(r);await s.initialize(e.language),(r.includes("-v")||r.includes("--version"))&&(await on(),process.exit(0)),e.setBackend&&(rn(e.setBackend),process.exit(0)),e.language==="prompt"&&(r.includes("--language")||r.includes("--lang")||r.includes("-l"))&&process.exit(0);let n=e.backendUrl;if(!n){let o=I.getBackendUrl();o&&(n=o)}let t={port:e.port,subdomain:e.subdomain,backendUrl:n,language:e.language};await S.start(t)}catch(r){console.error(`Fatal Error: ${r.message}`),process.exit(1)}}i(sn,"main");process.on("SIGINT",()=>S.cleanup());process.on("SIGTERM",()=>S.cleanup());sn();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nport",
3
- "version": "2.1.4",
3
+ "version": "2.1.5",
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",