nport 2.0.5 → 2.0.6

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.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,55 @@ All notable changes to NPort will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [2.0.6] - 2026-01-13
9
+
10
+ ### Added
11
+ - 🔧 **Backend URL Configuration**: Full control over backend server
12
+ - `--backend` / `-b` flag for temporary backend override
13
+ - `--set-backend` command to save backend URL permanently
14
+ - `NPORT_BACKEND_URL` environment variable support
15
+ - Saved backend configuration persists across sessions
16
+ - Priority system: CLI flag > Saved config > Env var > Default
17
+ - 🗂️ **Unified Configuration System**: All settings in one place
18
+ - New centralized `config-manager.js` module
19
+ - All preferences stored in `~/.nport/config.json`
20
+ - Automatic migration from old format (v2.0.5)
21
+ - Easy to read and manually edit JSON format
22
+ - 🌐 **New Default Backend**: Updated to `api.nport.link`
23
+ - Professional domain structure
24
+ - Better branding alignment
25
+ - Shorter and easier to remember
26
+
27
+ ### Changed
28
+ - 📝 **Language Configuration**: Now uses unified config system
29
+ - Language setting moved from `~/.nport/lang` to `~/.nport/config.json`
30
+ - Automatic migration from old file format
31
+ - Consistent configuration approach across all settings
32
+ - 📚 **Documentation Updates**: Complete overhaul
33
+ - Updated `README.md` with backend configuration options
34
+ - New `CLIENT_SETUP.md` focused on npm installation and backend setup
35
+ - Comprehensive backend URL documentation
36
+ - Clear priority order explanation
37
+
38
+ ### Improved
39
+ - 🎯 **Consistency**: Unified approach to all configuration
40
+ - Backend URL and language now use same storage system
41
+ - Single config file for all preferences
42
+ - Cleaner architecture and code organization
43
+ - 💾 **Configuration File Structure**:
44
+ ```json
45
+ {
46
+ "backendUrl": "https://api.nport.link",
47
+ "language": "en"
48
+ }
49
+ ```
50
+
51
+ ### Migration
52
+ - Automatic migration from v2.0.5 configuration files
53
+ - Old `~/.nport/lang` file automatically migrated to `config.json`
54
+ - No manual steps required
55
+ - Old files removed after successful migration
56
+
8
57
  ## [2.0.5] - 2026-01-13
9
58
 
10
59
  ### Added
@@ -61,6 +110,39 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
61
110
 
62
111
  ## Version Upgrade Guide
63
112
 
113
+ ### From 2.0.5 to 2.0.6
114
+
115
+ ```bash
116
+ npm install -g nport@latest
117
+ ```
118
+
119
+ **New Features to Try:**
120
+
121
+ 1. **Set Your Backend Permanently**
122
+ ```bash
123
+ nport --set-backend https://api.nport.link
124
+ ```
125
+
126
+ 2. **Use Custom Backend Temporarily**
127
+ ```bash
128
+ nport 3000 -b https://your-backend.com
129
+ ```
130
+
131
+ 3. **Check Current Configuration**
132
+ ```bash
133
+ cat ~/.nport/config.json
134
+ ```
135
+
136
+ 4. **Use Environment Variable**
137
+ ```bash
138
+ export NPORT_BACKEND_URL=https://your-backend.com
139
+ nport 3000
140
+ ```
141
+
142
+ **Breaking Changes:** None - fully backward compatible!
143
+
144
+ **Migration:** Your language preference from v2.0.5 will be automatically migrated to the new unified config format.
145
+
64
146
  ### From 2.0.4 to 2.0.5
65
147
 
66
148
  ```bash
package/README.md CHANGED
@@ -158,6 +158,8 @@ nport <port> [options]
158
158
  |--------|-------|-------------|---------|
159
159
  | `<port>` | - | Local port to tunnel (default: 8080) | `nport 3000` |
160
160
  | `--subdomain` | `-s` | Custom subdomain | `nport 3000 -s myapp` |
161
+ | `--backend` | `-b` | Custom backend URL (temporary) | `nport 3000 -b https://your-backend.com` |
162
+ | `--set-backend` | - | Save backend URL permanently | `nport --set-backend https://your-backend.com` |
161
163
  | `--language` | `-l` | Set language (en/vi) or prompt | `nport 3000 -l vi` |
162
164
  | `--version` | `-v` | Show version information | `nport -v` |
163
165
 
@@ -177,6 +179,61 @@ nport -l # Interactive prompt
177
179
 
178
180
  On first run or when using `--language` without a value, you'll see an interactive language picker. Your choice is automatically saved for future sessions.
179
181
 
182
+ ### Backend URL Options
183
+
184
+ NPort uses a default backend at `https://api.nport.link`, but you can use your own backend server.
185
+
186
+ #### Temporary Backend (One-time Use)
187
+
188
+ Use a custom backend for just the current session:
189
+
190
+ ```bash
191
+ # Use custom backend via CLI flag
192
+ nport 3000 --backend https://your-backend.com
193
+ nport 3000 -b https://your-backend.com
194
+
195
+ # Use custom backend via environment variable
196
+ export NPORT_BACKEND_URL=https://your-backend.com
197
+ nport 3000
198
+
199
+ # Combine with other options
200
+ nport 3000 -s myapp -b https://your-backend.com
201
+ ```
202
+
203
+ #### Persistent Backend (Saved Configuration)
204
+
205
+ Save a backend URL to use automatically in all future sessions:
206
+
207
+ ```bash
208
+ # Save backend URL permanently
209
+ nport --set-backend https://your-backend.com
210
+
211
+ # Now all future commands will use this backend
212
+ nport 3000 # Uses saved backend
213
+ nport 3000 -s myapp # Uses saved backend
214
+
215
+ # Clear saved backend (return to default)
216
+ nport --set-backend
217
+
218
+ # Override saved backend temporarily
219
+ nport 3000 -b https://different-backend.com
220
+ ```
221
+
222
+ **Configuration Priority:**
223
+ 1. CLI flag (`--backend` or `-b`) - Highest priority
224
+ 2. Saved config (`--set-backend`)
225
+ 3. Environment variable (`NPORT_BACKEND_URL`)
226
+ 4. Default (`https://api.nport.link`) - Lowest priority
227
+
228
+ **Configuration Storage:**
229
+ Your backend preference is saved in `~/.nport/config.json`
230
+
231
+ This is useful if you want to:
232
+ - **Self-host**: Run your own NPort backend (see [server/](server/) directory)
233
+ - **Development**: Test against a local backend
234
+ - **Custom domains**: Use your own domain for tunnel URLs
235
+ - **Enterprise**: Use a company-hosted backend server
236
+
180
237
  ### Version Information
181
238
 
182
239
  ```bash
package/index.js CHANGED
@@ -1,12 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import ora from "ora";
4
+ import chalk from "chalk";
4
5
  import { ArgumentParser } from "./src/args.js";
5
6
  import { TunnelOrchestrator } from "./src/tunnel.js";
6
7
  import { VersionManager } from "./src/version.js";
7
8
  import { UI } from "./src/ui.js";
8
9
  import { CONFIG } from "./src/config.js";
9
10
  import { lang } from "./src/lang.js";
11
+ import { configManager } from "./src/config-manager.js";
10
12
 
11
13
  /**
12
14
  * NPort - Free & Open Source ngrok Alternative
@@ -26,6 +28,32 @@ async function displayVersion() {
26
28
  UI.displayVersion(CONFIG.CURRENT_VERSION, updateInfo);
27
29
  }
28
30
 
31
+ /**
32
+ * Handle --set-backend command
33
+ */
34
+ function handleSetBackend(value) {
35
+ if (value === 'clear') {
36
+ // Clear saved backend URL
37
+ configManager.setBackendUrl(null);
38
+ console.log(chalk.green('✔ Backend URL cleared. Using default backend.'));
39
+ console.log(chalk.gray(' Default: https://api.nport.link\n'));
40
+ } else {
41
+ // Save new backend URL
42
+ configManager.setBackendUrl(value);
43
+ console.log(chalk.green('✔ Backend URL saved successfully!'));
44
+ console.log(chalk.cyan(` Backend: ${value}`));
45
+ console.log(chalk.gray('\n This backend will be used for all future sessions.'));
46
+ console.log(chalk.gray(' To clear: nport --set-backend\n'));
47
+ }
48
+
49
+ // Show current configuration
50
+ const savedUrl = configManager.getBackendUrl();
51
+ if (savedUrl) {
52
+ console.log(chalk.white('Current configuration:'));
53
+ console.log(chalk.cyan(` Saved backend: ${savedUrl}`));
54
+ }
55
+ }
56
+
29
57
  /**
30
58
  * Main application entry point
31
59
  */
@@ -45,6 +73,12 @@ async function main() {
45
73
  process.exit(0);
46
74
  }
47
75
 
76
+ // Handle --set-backend command
77
+ if (config.setBackend) {
78
+ handleSetBackend(config.setBackend);
79
+ process.exit(0);
80
+ }
81
+
48
82
  // If only --language flag was used, show success message and exit
49
83
  if (config.language === 'prompt' &&
50
84
  (args.includes('--language') || args.includes('--lang') || args.includes('-l'))) {
@@ -52,6 +86,14 @@ async function main() {
52
86
  process.exit(0);
53
87
  }
54
88
 
89
+ // Load saved backend URL if no CLI backend specified
90
+ if (!config.backendUrl) {
91
+ const savedBackend = configManager.getBackendUrl();
92
+ if (savedBackend) {
93
+ config.backendUrl = savedBackend;
94
+ }
95
+ }
96
+
55
97
  // Start tunnel
56
98
  await TunnelOrchestrator.start(config);
57
99
  } catch (error) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nport",
3
- "version": "2.0.5",
3
+ "version": "2.0.6",
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": "index.js",
package/src/api.js CHANGED
@@ -8,9 +8,10 @@ import { state } from "./state.js";
8
8
  * Handles communication with the NPort backend service
9
9
  */
10
10
  export class APIClient {
11
- static async createTunnel(subdomain) {
11
+ static async createTunnel(subdomain, backendUrl = null) {
12
+ const url = backendUrl || CONFIG.BACKEND_URL;
12
13
  try {
13
- const { data } = await axios.post(CONFIG.BACKEND_URL, { subdomain });
14
+ const { data } = await axios.post(url, { subdomain });
14
15
 
15
16
  if (!data.success) {
16
17
  throw new Error(data.error || "Unknown error from backend");
@@ -26,8 +27,9 @@ export class APIClient {
26
27
  }
27
28
  }
28
29
 
29
- static async deleteTunnel(subdomain, tunnelId) {
30
- await axios.delete(CONFIG.BACKEND_URL, {
30
+ static async deleteTunnel(subdomain, tunnelId, backendUrl = null) {
31
+ const url = backendUrl || CONFIG.BACKEND_URL;
32
+ await axios.delete(url, {
31
33
  data: { subdomain, tunnelId },
32
34
  });
33
35
  }
package/src/args.js CHANGED
@@ -2,14 +2,16 @@ import { CONFIG } from "./config.js";
2
2
 
3
3
  /**
4
4
  * Command Line Argument Parser
5
- * Handles parsing of CLI arguments for port, subdomain, and language
5
+ * Handles parsing of CLI arguments for port, subdomain, language, and backend URL
6
6
  */
7
7
  export class ArgumentParser {
8
8
  static parse(argv) {
9
9
  const port = this.parsePort(argv);
10
10
  const subdomain = this.parseSubdomain(argv);
11
11
  const language = this.parseLanguage(argv);
12
- return { port, subdomain, language };
12
+ const backendUrl = this.parseBackendUrl(argv);
13
+ const setBackend = this.parseSetBackend(argv);
14
+ return { port, subdomain, language, backendUrl, setBackend };
13
15
  }
14
16
 
15
17
  static parsePort(argv) {
@@ -66,6 +68,43 @@ export class ArgumentParser {
66
68
  return null; // No language specified
67
69
  }
68
70
 
71
+ static parseBackendUrl(argv) {
72
+ // Try backend URL flag formats: --backend=url, --backend url, -b url
73
+ const formats = [
74
+ () => this.findFlagWithEquals(argv, "--backend="),
75
+ () => this.findFlagWithEquals(argv, "-b="),
76
+ () => this.findFlagWithValue(argv, "--backend"),
77
+ () => this.findFlagWithValue(argv, "-b"),
78
+ ];
79
+
80
+ for (const format of formats) {
81
+ const url = format();
82
+ if (url) return url;
83
+ }
84
+
85
+ return null; // No backend URL specified
86
+ }
87
+
88
+ static parseSetBackend(argv) {
89
+ // Try set-backend flag formats: --set-backend=url, --set-backend url
90
+ const formats = [
91
+ () => this.findFlagWithEquals(argv, "--set-backend="),
92
+ () => this.findFlagWithValue(argv, "--set-backend"),
93
+ ];
94
+
95
+ for (const format of formats) {
96
+ const url = format();
97
+ if (url) return url;
98
+ }
99
+
100
+ // Check if --set-backend flag exists without value (to clear)
101
+ if (argv.includes('--set-backend')) {
102
+ return 'clear';
103
+ }
104
+
105
+ return null; // No set-backend specified
106
+ }
107
+
69
108
  static findFlagWithEquals(argv, flag) {
70
109
  const arg = argv.find((a) => a.startsWith(flag));
71
110
  return arg ? arg.split("=")[1] : null;
@@ -0,0 +1,139 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import os from "os";
4
+
5
+ /**
6
+ * Configuration Manager
7
+ * Handles persistent storage of user preferences (backend URL, language, etc.)
8
+ */
9
+ class ConfigManager {
10
+ constructor() {
11
+ this.configDir = path.join(os.homedir(), ".nport");
12
+ this.configFile = path.join(this.configDir, "config.json");
13
+ this.oldLangFile = path.join(this.configDir, "lang"); // For migration
14
+ this.config = this.loadConfig();
15
+ this.migrateOldConfig();
16
+ }
17
+
18
+ /**
19
+ * Load configuration from file
20
+ * @returns {object} Configuration object
21
+ */
22
+ loadConfig() {
23
+ try {
24
+ if (fs.existsSync(this.configFile)) {
25
+ const data = fs.readFileSync(this.configFile, "utf8");
26
+ return JSON.parse(data);
27
+ }
28
+ } catch (error) {
29
+ // If config is corrupted or invalid, return default
30
+ console.warn("Warning: Could not load config file, using defaults");
31
+ }
32
+ return {};
33
+ }
34
+
35
+ /**
36
+ * Migrate old configuration files to new unified format from version 2.0.5
37
+ */
38
+ migrateOldConfig() {
39
+ try {
40
+ // Migrate old language file if it exists and no language in config
41
+ if (!this.config.language && fs.existsSync(this.oldLangFile)) {
42
+ const oldLang = fs.readFileSync(this.oldLangFile, "utf8").trim();
43
+ if (oldLang && ["en", "vi"].includes(oldLang)) {
44
+ this.config.language = oldLang;
45
+ this.saveConfig();
46
+ // Optionally delete old file
47
+ try {
48
+ fs.unlinkSync(this.oldLangFile);
49
+ } catch (err) {
50
+ // Ignore if can't delete
51
+ }
52
+ }
53
+ }
54
+ } catch (error) {
55
+ // Ignore migration errors
56
+ }
57
+ }
58
+
59
+ /**
60
+ * Save configuration to file
61
+ */
62
+ saveConfig() {
63
+ try {
64
+ // Ensure .nport directory exists
65
+ if (!fs.existsSync(this.configDir)) {
66
+ fs.mkdirSync(this.configDir, { recursive: true });
67
+ }
68
+ fs.writeFileSync(this.configFile, JSON.stringify(this.config, null, 2), "utf8");
69
+ return true;
70
+ } catch (error) {
71
+ console.warn("Warning: Could not save configuration");
72
+ return false;
73
+ }
74
+ }
75
+
76
+ /**
77
+ * Get backend URL from config
78
+ * @returns {string|null} Saved backend URL or null
79
+ */
80
+ getBackendUrl() {
81
+ return this.config.backendUrl || null;
82
+ }
83
+
84
+ /**
85
+ * Set backend URL in config
86
+ * @param {string} url - Backend URL to save
87
+ * @returns {boolean} Success status
88
+ */
89
+ setBackendUrl(url) {
90
+ if (!url) {
91
+ delete this.config.backendUrl;
92
+ } else {
93
+ this.config.backendUrl = url;
94
+ }
95
+ return this.saveConfig();
96
+ }
97
+
98
+ /**
99
+ * Get language from config
100
+ * @returns {string|null} Saved language code or null
101
+ */
102
+ getLanguage() {
103
+ return this.config.language || null;
104
+ }
105
+
106
+ /**
107
+ * Set language in config
108
+ * @param {string} lang - Language code to save (e.g., 'en', 'vi')
109
+ * @returns {boolean} Success status
110
+ */
111
+ setLanguage(lang) {
112
+ if (!lang) {
113
+ delete this.config.language;
114
+ } else {
115
+ this.config.language = lang;
116
+ }
117
+ return this.saveConfig();
118
+ }
119
+
120
+ /**
121
+ * Get all configuration
122
+ * @returns {object} All configuration
123
+ */
124
+ getAll() {
125
+ return { ...this.config };
126
+ }
127
+
128
+ /**
129
+ * Clear all configuration
130
+ * @returns {boolean} Success status
131
+ */
132
+ clear() {
133
+ this.config = {};
134
+ return this.saveConfig();
135
+ }
136
+ }
137
+
138
+ // Export singleton instance
139
+ export const configManager = new ConfigManager();
package/src/config.js CHANGED
@@ -7,11 +7,23 @@ const __dirname = path.dirname(path.dirname(__filename));
7
7
  const require = createRequire(import.meta.url);
8
8
  const packageJson = require("../package.json");
9
9
 
10
+ // Helper function to get backend URL with priority order
11
+ function getBackendUrl() {
12
+ // Priority 1: Environment variable
13
+ if (process.env.NPORT_BACKEND_URL) {
14
+ return process.env.NPORT_BACKEND_URL;
15
+ }
16
+
17
+ // Priority 2: Saved config (will be set by config-manager if available)
18
+ // Priority 3: Default
19
+ return "https://api.nport.link";
20
+ }
21
+
10
22
  // Application constants
11
23
  export const CONFIG = {
12
24
  PACKAGE_NAME: packageJson.name,
13
25
  CURRENT_VERSION: packageJson.version,
14
- BACKEND_URL: "https://nport.tuanngocptn.workers.dev",
26
+ BACKEND_URL: getBackendUrl(),
15
27
  DEFAULT_PORT: 8080,
16
28
  SUBDOMAIN_PREFIX: "user-",
17
29
  TUNNEL_TIMEOUT_HOURS: 4,
package/src/lang.js CHANGED
@@ -2,6 +2,7 @@ import fs from "fs";
2
2
  import path from "path";
3
3
  import os from "os";
4
4
  import readline from "readline";
5
+ import { configManager } from "./config-manager.js";
5
6
 
6
7
  // ============================================================================
7
8
  // Language Translations
@@ -10,7 +11,7 @@ import readline from "readline";
10
11
  const TRANSLATIONS = {
11
12
  en: {
12
13
  // Header
13
- header: "N P O R T ⚡️ Free & Open Source from Vietnam",
14
+ header: "N P O R T ⚡️ Free & Open Source from Vietnam ❤️",
14
15
 
15
16
  // Spinners
16
17
  creatingTunnel: "Creating tunnel for port {port}...",
@@ -24,7 +25,7 @@ const TRANSLATIONS = {
24
25
 
25
26
  // Footer
26
27
  footerTitle: "🔥 KEEP THE VIBE ALIVE?",
27
- footerSubtitle: "(Made with ❤️ in Vietnam)",
28
+ footerSubtitle: "(Made with ❤️ in Vietnam)",
28
29
  dropStar: "⭐️ Drop a Star: ",
29
30
  sendCoffee: "☕️ Buy Coffee: ",
30
31
  newVersion: "🚨 NEW VERSION (v{version}) detected!",
@@ -78,7 +79,7 @@ const TRANSLATIONS = {
78
79
  timeRemaining: "⏱️ Tăng tốc thần sầu: Còn {hours}h để quẩy!",
79
80
 
80
81
  // Footer
81
- footerTitle: "🔥 LƯU DANH SỬ SÁCH! KHÔNG QUÊN STAR",
82
+ footerTitle: "🔥 LƯU DANH SỬ SÁCH! ĐỪNG QUÊN STAR ⭐️",
82
83
  footerSubtitle: "(Made in Việt Nam, chuẩn không cần chỉnh! ❤️)",
83
84
  dropStar: "⭐️ Thả Star: ",
84
85
  sendCoffee: "☕️ Tặng Coffee: ",
@@ -97,7 +98,7 @@ const TRANSLATIONS = {
97
98
  goodbyeTitle: "👋 GẶP LẠI BẠN Ở ĐƯỜNG BĂNG KHÁC...",
98
99
  goodbyeMessage: "Cảm ơn đã quẩy NPort! Lần sau chơi tiếp nha 😘",
99
100
  website: "🌐 Sân chơi chính: ",
100
- author: "👤 Nhà tài trợ chương trình: ",
101
+ author: "👤 Nhà tài trợ: ",
101
102
  changeLanguage: "🌍 Đổi ngôn ngữ: ",
102
103
  changeLanguageHint: "nport --language",
103
104
 
@@ -126,8 +127,6 @@ const TRANSLATIONS = {
126
127
  class LanguageManager {
127
128
  constructor() {
128
129
  this.currentLanguage = "en";
129
- this.configDir = path.join(os.homedir(), ".nport");
130
- this.configFile = path.join(this.configDir, "lang");
131
130
  this.availableLanguages = ["en", "vi"];
132
131
  }
133
132
 
@@ -154,15 +153,9 @@ class LanguageManager {
154
153
  * @returns {string|null} Saved language code or null
155
154
  */
156
155
  loadLanguagePreference() {
157
- try {
158
- if (fs.existsSync(this.configFile)) {
159
- const lang = fs.readFileSync(this.configFile, "utf8").trim();
160
- if (this.availableLanguages.includes(lang)) {
161
- return lang;
162
- }
163
- }
164
- } catch (error) {
165
- // Ignore errors, will prompt user
156
+ const lang = configManager.getLanguage();
157
+ if (lang && this.availableLanguages.includes(lang)) {
158
+ return lang;
166
159
  }
167
160
  return null;
168
161
  }
@@ -172,16 +165,7 @@ class LanguageManager {
172
165
  * @param {string} lang - Language code to save
173
166
  */
174
167
  saveLanguagePreference(lang) {
175
- try {
176
- // Ensure .nport directory exists
177
- if (!fs.existsSync(this.configDir)) {
178
- fs.mkdirSync(this.configDir, { recursive: true });
179
- }
180
- fs.writeFileSync(this.configFile, lang, "utf8");
181
- } catch (error) {
182
- // Silently fail if can't save
183
- console.warn("Warning: Could not save language preference");
184
- }
168
+ configManager.setLanguage(lang);
185
169
  }
186
170
 
187
171
  /**
package/src/state.js CHANGED
@@ -7,6 +7,7 @@ class TunnelState {
7
7
  this.tunnelId = null;
8
8
  this.subdomain = null;
9
9
  this.port = null;
10
+ this.backendUrl = null;
10
11
  this.tunnelProcess = null;
11
12
  this.timeoutId = null;
12
13
  this.connectionCount = 0;
@@ -14,10 +15,11 @@ class TunnelState {
14
15
  this.updateInfo = null;
15
16
  }
16
17
 
17
- setTunnel(tunnelId, subdomain, port) {
18
+ setTunnel(tunnelId, subdomain, port, backendUrl = null) {
18
19
  this.tunnelId = tunnelId;
19
20
  this.subdomain = subdomain;
20
21
  this.port = port;
22
+ this.backendUrl = backendUrl;
21
23
  if (!this.startTime) {
22
24
  this.startTime = Date.now();
23
25
  }
@@ -65,6 +67,7 @@ class TunnelState {
65
67
  this.tunnelId = null;
66
68
  this.subdomain = null;
67
69
  this.port = null;
70
+ this.backendUrl = null;
68
71
  this.tunnelProcess = null;
69
72
  this.connectionCount = 0;
70
73
  this.startTime = null;
package/src/tunnel.js CHANGED
@@ -15,7 +15,7 @@ import { lang } from "./lang.js";
15
15
  */
16
16
  export class TunnelOrchestrator {
17
17
  static async start(config) {
18
- state.setTunnel(null, config.subdomain, config.port);
18
+ state.setTunnel(null, config.subdomain, config.port, config.backendUrl);
19
19
 
20
20
  // Initialize analytics
21
21
  await analytics.initialize();
@@ -42,8 +42,8 @@ export class TunnelOrchestrator {
42
42
 
43
43
  try {
44
44
  // Create tunnel
45
- const tunnel = await APIClient.createTunnel(config.subdomain);
46
- state.setTunnel(tunnel.tunnelId, config.subdomain, config.port);
45
+ const tunnel = await APIClient.createTunnel(config.subdomain, config.backendUrl);
46
+ state.setTunnel(tunnel.tunnelId, config.subdomain, config.port, config.backendUrl);
47
47
 
48
48
  // Track successful tunnel creation
49
49
  analytics.trackTunnelCreated(config.subdomain, config.port);
@@ -101,7 +101,7 @@ export class TunnelOrchestrator {
101
101
  }
102
102
 
103
103
  // Delete tunnel
104
- await APIClient.deleteTunnel(state.subdomain, state.tunnelId);
104
+ await APIClient.deleteTunnel(state.subdomain, state.tunnelId, state.backendUrl);
105
105
  UI.displayCleanupSuccess();
106
106
  } catch (err) {
107
107
  UI.displayCleanupError();
package/src/ui.js CHANGED
@@ -9,8 +9,13 @@ import { lang } from "./lang.js";
9
9
  export class UI {
10
10
  static displayProjectInfo() {
11
11
  const line = "─".repeat(56);
12
+ const headerText = lang.t("header");
13
+ // Calculate proper padding (accounting for emojis which take visual space)
14
+ const visualLength = 59; // Target visual width
15
+ const padding = " ".repeat(Math.max(0, visualLength - headerText.length - 4));
16
+
12
17
  console.log(chalk.gray(`\n ╭${line}╮`));
13
- console.log(chalk.cyan.bold(` │ ${lang.t("header")}`) + " ".repeat(56 - lang.t("header").length - 4) + chalk.gray("│"));
18
+ console.log(chalk.cyan.bold(` │ ${headerText}`) + padding + chalk.gray("│"));
14
19
  console.log(chalk.gray(` ╰${line}╯\n`));
15
20
  }
16
21
 
@@ -95,4 +100,4 @@ export class UI {
95
100
 
96
101
  console.log(chalk.gray(lang.t("learnMore")) + chalk.cyan("https://nport.link\n"));
97
102
  }
98
- }
103
+ }