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 +82 -0
- package/README.md +57 -0
- package/index.js +42 -0
- package/package.json +1 -1
- package/src/api.js +6 -4
- package/src/args.js +41 -2
- package/src/config-manager.js +139 -0
- package/src/config.js +13 -1
- package/src/lang.js +9 -25
- package/src/state.js +4 -1
- package/src/tunnel.js +4 -4
- package/src/ui.js +7 -2
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
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(
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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:
|
|
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 ❤️
|
|
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!
|
|
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
|
|
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
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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
|
-
|
|
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(` │ ${
|
|
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
|
+
}
|