nport 2.0.7 → 2.1.0

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/src/config.js DELETED
@@ -1,88 +0,0 @@
1
- import path from "path";
2
- import { fileURLToPath } from "url";
3
- import { createRequire } from "module";
4
-
5
- const __filename = fileURLToPath(import.meta.url);
6
- const __dirname = path.dirname(path.dirname(__filename));
7
- const require = createRequire(import.meta.url);
8
- const packageJson = require("../package.json");
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
-
22
- // Application constants
23
- export const CONFIG = {
24
- PACKAGE_NAME: packageJson.name,
25
- CURRENT_VERSION: packageJson.version,
26
- BACKEND_URL: getBackendUrl(),
27
- DEFAULT_PORT: 8080,
28
- SUBDOMAIN_PREFIX: "user-",
29
- TUNNEL_TIMEOUT_HOURS: 4,
30
- UPDATE_CHECK_TIMEOUT: 3000,
31
- };
32
-
33
- // Platform-specific configuration
34
- export const PLATFORM = {
35
- IS_WINDOWS: process.platform === "win32",
36
- BIN_NAME: process.platform === "win32" ? "cloudflared.exe" : "cloudflared",
37
- };
38
-
39
- // Paths
40
- export const PATHS = {
41
- BIN_DIR: path.join(__dirname, "bin"),
42
- BIN_PATH: path.join(__dirname, "bin", PLATFORM.BIN_NAME),
43
- };
44
-
45
- // Log patterns for filtering cloudflared output
46
- export const LOG_PATTERNS = {
47
- SUCCESS: ["Registered tunnel connection"],
48
- ERROR: ["ERR", "error"],
49
-
50
- // Network-related warnings (not critical errors)
51
- NETWORK_WARNING: [
52
- "failed to accept QUIC stream",
53
- "failed to dial to edge with quic",
54
- "failed to accept incoming stream requests",
55
- "Failed to dial a quic connection",
56
- "timeout: no recent network activity",
57
- "failed to dial to edge",
58
- "quic:",
59
- ],
60
-
61
- IGNORE: [
62
- "Cannot determine default origin certificate path",
63
- "No file cert.pem",
64
- "origincert option",
65
- "TUNNEL_ORIGIN_CERT",
66
- "context canceled",
67
- "failed to run the datagram handler",
68
- "failed to serve tunnel connection",
69
- "Connection terminated",
70
- "no more connections active and exiting",
71
- "Serve tunnel error",
72
- "accept stream listener encountered a failure",
73
- "Retrying connection",
74
- "icmp router terminated",
75
- "use of closed network connection",
76
- "Application error 0x0",
77
- ],
78
- };
79
-
80
- // Network warning configuration
81
- export const NETWORK_CONFIG = {
82
- WARNING_THRESHOLD: 5, // Show warning after 5 network errors
83
- WARNING_COOLDOWN: 30000, // Only show warning every 30 seconds
84
- };
85
-
86
- // Computed constants
87
- export const TUNNEL_TIMEOUT_MS = CONFIG.TUNNEL_TIMEOUT_HOURS * 60 * 60 * 1000;
88
-
package/src/lang.js DELETED
@@ -1,293 +0,0 @@
1
- import fs from "fs";
2
- import path from "path";
3
- import os from "os";
4
- import readline from "readline";
5
- import { configManager } from "./config-manager.js";
6
-
7
- // ============================================================================
8
- // Language Translations
9
- // ============================================================================
10
-
11
- const TRANSLATIONS = {
12
- en: {
13
- // Header
14
- header: "N P O R T ⚡️ Free & Open Source from Vietnam ❤️",
15
-
16
- // Spinners
17
- creatingTunnel: "Creating tunnel for port {port}...",
18
- checkingUpdates: "Checking for updates...",
19
-
20
- // Success messages
21
- tunnelLive: "🚀 WE LIVE BABY!",
22
- connection1: " ✔ [1/2] Connection established...",
23
- connection2: " ✔ [2/2] Compression enabled...",
24
- timeRemaining: "⏱️ Time: {hours}h remaining",
25
-
26
- // Footer
27
- footerTitle: "🔥 KEEP THE VIBE ALIVE?",
28
- footerSubtitle: "(Made with ❤️ in Vietnam)",
29
- dropStar: "⭐️ Drop a Star: ",
30
- sendCoffee: "☕️ Buy Coffee: ",
31
- newVersion: "🚨 NEW VERSION (v{version}) detected!",
32
- updateCommand: "> npm install -g nport@latest",
33
-
34
- // Cleanup
35
- tunnelShutdown: "🛑 TUNNEL SHUTDOWN.",
36
- cleaningUp: "Cleaning up... ",
37
- cleanupDone: "Done.",
38
- cleanupFailed: "Failed.",
39
- subdomainReleased: "Subdomain... Released. 🗑️",
40
- serverBusy: "(Server might be down or busy)",
41
-
42
- // Goodbye
43
- goodbyeTitle: "👋 BEFORE YOU GO...",
44
- goodbyeMessage: "Thanks for using NPort!",
45
- website: "🌐 Website: ",
46
- author: "👤 Author: ",
47
- changeLanguage: "🌍 Language: ",
48
- changeLanguageHint: "nport --language",
49
-
50
- // Version
51
- versionTitle: "NPort v{version}",
52
- versionSubtitle: "Free & open source ngrok alternative",
53
- versionLatest: "✔ You're running the latest version!",
54
- versionAvailable: "🚨 New version available: v{version}",
55
- versionUpdate: "Update now: ",
56
- learnMore: "Learn more: ",
57
-
58
- // Language selection
59
- languagePrompt: "\n🌍 Language Selection / Chọn ngôn ngữ\n",
60
- languageQuestion: "Choose your language (1-2): ",
61
- languageEnglish: "1. English",
62
- languageVietnamese: "2. Tiếng Việt (Vietnamese)",
63
- languageInvalid: "Invalid choice. Using English by default.",
64
- languageSaved: "✔ Language preference saved!",
65
-
66
- // Network warnings
67
- networkIssueTitle: "\n⚠️ NETWORK CONNECTIVITY ISSUE DETECTED",
68
- networkIssueDesc: " Cloudflared is having trouble maintaining a stable connection to Cloudflare's edge servers.",
69
- networkIssueTunnel: " 📡 Your tunnel is still working, but connection quality may be affected.",
70
- networkIssueReasons: "\n 💡 Possible reasons:",
71
- networkIssueReason1: " • Unstable internet connection or high packet loss",
72
- networkIssueReason2: " • Firewall/Router blocking UDP traffic (QUIC protocol)",
73
- networkIssueReason3: " • ISP throttling or network congestion",
74
- networkIssueFix: "\n 🔧 What to try:",
75
- networkIssueFix1: " • Check your internet connection stability",
76
- networkIssueFix2: " • Try connecting from a different network",
77
- networkIssueFix3: " • Disable VPN/Proxy if you're using one",
78
- networkIssueFix4: " • The tunnel will automatically fallback to HTTP/2 if QUIC fails",
79
- networkIssueIgnore: "\n ℹ️ This is usually not critical - your tunnel should continue working normally.\n",
80
- },
81
-
82
- vi: {
83
- // Header
84
- header: "N P O R T ⚡️ Việt Nam Mãi Đỉnh ❤️",
85
-
86
- // Spinners
87
- creatingTunnel: "🛠️ Đang khởi động cổng {port}... Chuẩn bị bay nào!",
88
- checkingUpdates: "🔍 Đang dò la bản cập nhật mới... Đợi tí sắp có quà!",
89
-
90
- // Success messages
91
- tunnelLive: "🚀 BẬT MODE TỐC HÀNH! ĐANG BAY RỒI NÈ!",
92
- connection1: " ✔ [1/2] Đang cắm dây mạng vũ trụ...",
93
- connection2: " ✔ [2/2] Đang bơm siêu nén khí tốc độ ánh sáng...",
94
- timeRemaining: "⏱️ Tăng tốc thần sầu: Còn {hours}h để quẩy!",
95
-
96
- // Footer
97
- footerTitle: "🔥 LƯU DANH SỬ SÁCH! ĐỪNG QUÊN STAR ⭐️",
98
- footerSubtitle: "(Made in Việt Nam, chuẩn không cần chỉnh! ❤️)",
99
- dropStar: "⭐️ Thả Star: ",
100
- sendCoffee: "☕️ Tặng Coffee: ",
101
- newVersion: "🚀 BẢN MỚI (v{version}) vừa hạ cánh!",
102
- updateCommand: "💡 Gõ liền: npm install -g nport@latest",
103
-
104
- // Cleanup
105
- tunnelShutdown: "🛑 Đã tới giờ 'chốt' deal rồi cả nhà ơi...",
106
- cleaningUp: "Đang dọn dẹp chiến trường... 🧹",
107
- cleanupDone: "Xịn xò! Đã dọn xong rồi nè.",
108
- cleanupFailed: "Oằn trời, dọn không nổi!",
109
- subdomainReleased: "Subdomain... Xí xoá! Tạm biệt nhé 🗑️✨",
110
- serverBusy: "(Có thể server đang bận order trà sữa)",
111
-
112
- // Goodbye
113
- goodbyeTitle: "👋 GẶP LẠI BẠN Ở ĐƯỜNG BĂNG KHÁC...",
114
- goodbyeMessage: "Cảm ơn đã quẩy NPort! Lần sau chơi tiếp nha 😘",
115
- website: "🌐 Sân chơi chính: ",
116
- author: "👤 Nhà tài trợ: ",
117
- changeLanguage: "🌍 Đổi ngôn ngữ: ",
118
- changeLanguageHint: "nport --language",
119
-
120
- // Version
121
- versionTitle: "NPort v{version}",
122
- versionSubtitle: "Hơn cả Ngrok - Ma-de in Việt Nam",
123
- versionLatest: "🎉 Chúc mừng! Đang cùng server với bản mới nhất!",
124
- versionAvailable: "🌟 Vèo vèo: Có bản mới v{version} vừa cập bến!",
125
- versionUpdate: "Update khẩn trương lẹ làng: ",
126
- learnMore: "Khám phá thêm cho nóng: ",
127
-
128
- // Language selection
129
- languagePrompt: "\n🌍 Chọn lựa ngôn ngữ ngay bên dưới nào!\n",
130
- languageQuestion: "Chớp lấy một lựa chọn nha (1-2): ",
131
- languageEnglish: "1. English (Chuẩn quốc tế!)",
132
- languageVietnamese: "2. Tiếng Việt (Đỉnh của chóp)",
133
- languageInvalid: "Ơ hơ, chọn sai rồi! Mặc định Tiếng Việt luôn cho nóng.",
134
- languageSaved: "🎯 Xong rồi! Lưu ngôn ngữ thành công!",
135
-
136
- // Network warnings
137
- networkIssueTitle: "\n⚠️ PHÁT HIỆN VẤN ĐỀ MẠNG",
138
- networkIssueDesc: " Cloudflared đang gặp khó khăn khi giữ kết nối ổn định tới Cloudflare edge servers.",
139
- networkIssueTunnel: " 📡 Tunnel của bạn vẫn hoạt động, nhưng chất lượng kết nối có thể bị ảnh hưởng.",
140
- networkIssueReasons: "\n 💡 Có thể do:",
141
- networkIssueReason1: " • Mạng internet không ổn định hoặc mất gói tin",
142
- networkIssueReason2: " • Firewall/Router chặn UDP traffic (giao thức QUIC)",
143
- networkIssueReason3: " • Nhà mạng throttle hoặc tắc nghẽn mạng",
144
- networkIssueFix: "\n 🔧 Thử các cách sau:",
145
- networkIssueFix1: " • Kiểm tra kết nối internet của bạn",
146
- networkIssueFix2: " • Thử đổi sang mạng khác (ví dụ: 4G/5G)",
147
- networkIssueFix3: " • Tắt VPN/Proxy nếu đang bật",
148
- networkIssueFix4: " • Tunnel sẽ tự động chuyển sang HTTP/2 nếu QUIC fail",
149
- networkIssueIgnore: "\n ℹ️ Lỗi này thường không nghiêm trọng - tunnel vẫn hoạt động bình thường.\n",
150
- }
151
- };
152
-
153
- // ============================================================================
154
- // Language Manager
155
- // ============================================================================
156
-
157
- class LanguageManager {
158
- constructor() {
159
- this.currentLanguage = "en";
160
- this.availableLanguages = ["en", "vi"];
161
- }
162
-
163
- /**
164
- * Get translation string with variable substitution
165
- * @param {string} key - Translation key
166
- * @param {object} vars - Variables to substitute
167
- * @returns {string} Translated string
168
- */
169
- t(key, vars = {}) {
170
- const translations = TRANSLATIONS[this.currentLanguage] || TRANSLATIONS.en;
171
- let text = translations[key] || TRANSLATIONS.en[key] || key;
172
-
173
- // Replace variables like {port}, {version}, etc.
174
- Object.keys(vars).forEach(varKey => {
175
- text = text.replace(`{${varKey}}`, vars[varKey]);
176
- });
177
-
178
- return text;
179
- }
180
-
181
- /**
182
- * Load saved language preference
183
- * @returns {string|null} Saved language code or null
184
- */
185
- loadLanguagePreference() {
186
- const lang = configManager.getLanguage();
187
- if (lang && this.availableLanguages.includes(lang)) {
188
- return lang;
189
- }
190
- return null;
191
- }
192
-
193
- /**
194
- * Save language preference
195
- * @param {string} lang - Language code to save
196
- */
197
- saveLanguagePreference(lang) {
198
- configManager.setLanguage(lang);
199
- }
200
-
201
- /**
202
- * Set current language
203
- * @param {string} lang - Language code
204
- */
205
- setLanguage(lang) {
206
- if (this.availableLanguages.includes(lang)) {
207
- this.currentLanguage = lang;
208
- return true;
209
- }
210
- return false;
211
- }
212
-
213
- /**
214
- * Get current language
215
- * @returns {string} Current language code
216
- */
217
- getLanguage() {
218
- return this.currentLanguage;
219
- }
220
-
221
- /**
222
- * Prompt user to select language
223
- * @returns {Promise<string>} Selected language code
224
- */
225
- async promptLanguageSelection() {
226
- return new Promise((resolve) => {
227
- const rl = readline.createInterface({
228
- input: process.stdin,
229
- output: process.stdout
230
- });
231
-
232
- console.log(this.t("languagePrompt"));
233
- console.log(` ${this.t("languageEnglish")}`);
234
- console.log(` ${this.t("languageVietnamese")}\n`);
235
-
236
- rl.question(`${this.t("languageQuestion")}`, (answer) => {
237
- rl.close();
238
-
239
- const choice = answer.trim();
240
- let selectedLang = "en";
241
-
242
- if (choice === "1") {
243
- selectedLang = "en";
244
- } else if (choice === "2") {
245
- selectedLang = "vi";
246
- } else {
247
- console.log(`\n${this.t("languageInvalid")}\n`);
248
- }
249
-
250
- this.setLanguage(selectedLang);
251
- this.saveLanguagePreference(selectedLang);
252
- console.log(`${this.t("languageSaved")}\n`);
253
-
254
- resolve(selectedLang);
255
- });
256
- });
257
- }
258
-
259
- /**
260
- * Initialize language - load from config or prompt user
261
- * @param {string|null} cliLanguage - Language from CLI argument (or 'prompt' to force prompt)
262
- * @returns {Promise<string>} Selected language code
263
- */
264
- async initialize(cliLanguage = null) {
265
- // Priority 1: CLI argument with value (e.g., --language en)
266
- if (cliLanguage && cliLanguage !== 'prompt' && this.setLanguage(cliLanguage)) {
267
- this.saveLanguagePreference(cliLanguage);
268
- return cliLanguage;
269
- }
270
-
271
- // Priority 2: Force prompt if --language flag without value
272
- if (cliLanguage === 'prompt') {
273
- return await this.promptLanguageSelection();
274
- }
275
-
276
- // Priority 3: Saved preference
277
- const savedLang = this.loadLanguagePreference();
278
- if (savedLang) {
279
- this.setLanguage(savedLang);
280
- return savedLang;
281
- }
282
-
283
- // Priority 4: Prompt user on first run
284
- return await this.promptLanguageSelection();
285
- }
286
- }
287
-
288
- // ============================================================================
289
- // Export singleton instance
290
- // ============================================================================
291
-
292
- export const lang = new LanguageManager();
293
-
package/src/state.js DELETED
@@ -1,115 +0,0 @@
1
- /**
2
- * Application State Manager
3
- * Manages tunnel state including connection info, process, and timers
4
- */
5
- class TunnelState {
6
- constructor() {
7
- this.tunnelId = null;
8
- this.subdomain = null;
9
- this.port = null;
10
- this.backendUrl = null;
11
- this.tunnelProcess = null;
12
- this.timeoutId = null;
13
- this.connectionCount = 0;
14
- this.startTime = null;
15
- this.updateInfo = null;
16
-
17
- // Network issue tracking
18
- this.networkIssueCount = 0;
19
- this.lastNetworkWarningTime = 0;
20
- this.networkWarningShown = false;
21
- }
22
-
23
- setTunnel(tunnelId, subdomain, port, backendUrl = null) {
24
- this.tunnelId = tunnelId;
25
- this.subdomain = subdomain;
26
- this.port = port;
27
- this.backendUrl = backendUrl;
28
- if (!this.startTime) {
29
- this.startTime = Date.now();
30
- }
31
- }
32
-
33
- setUpdateInfo(updateInfo) {
34
- this.updateInfo = updateInfo;
35
- }
36
-
37
- setProcess(process) {
38
- this.tunnelProcess = process;
39
- }
40
-
41
- setTimeout(timeoutId) {
42
- this.timeoutId = timeoutId;
43
- }
44
-
45
- clearTimeout() {
46
- if (this.timeoutId) {
47
- clearTimeout(this.timeoutId);
48
- this.timeoutId = null;
49
- }
50
- }
51
-
52
- incrementConnection() {
53
- this.connectionCount++;
54
- return this.connectionCount;
55
- }
56
-
57
- hasTunnel() {
58
- return this.tunnelId !== null;
59
- }
60
-
61
- hasProcess() {
62
- return this.tunnelProcess && !this.tunnelProcess.killed;
63
- }
64
-
65
- getDurationSeconds() {
66
- if (!this.startTime) return 0;
67
- return (Date.now() - this.startTime) / 1000;
68
- }
69
-
70
- // Network issue tracking methods
71
- incrementNetworkIssue() {
72
- this.networkIssueCount++;
73
- return this.networkIssueCount;
74
- }
75
-
76
- resetNetworkIssues() {
77
- this.networkIssueCount = 0;
78
- }
79
-
80
- shouldShowNetworkWarning(threshold, cooldown) {
81
- const now = Date.now();
82
- if (
83
- this.networkIssueCount >= threshold &&
84
- now - this.lastNetworkWarningTime > cooldown
85
- ) {
86
- this.lastNetworkWarningTime = now;
87
- return true;
88
- }
89
- return false;
90
- }
91
-
92
- setNetworkWarningShown(value) {
93
- this.networkWarningShown = value;
94
- }
95
-
96
- reset() {
97
- this.clearTimeout();
98
- this.tunnelId = null;
99
- this.subdomain = null;
100
- this.port = null;
101
- this.backendUrl = null;
102
- this.tunnelProcess = null;
103
- this.connectionCount = 0;
104
- this.startTime = null;
105
- this.updateInfo = null;
106
-
107
- // Reset network tracking
108
- this.networkIssueCount = 0;
109
- this.lastNetworkWarningTime = 0;
110
- this.networkWarningShown = false;
111
- }
112
- }
113
-
114
- export const state = new TunnelState();
115
-
package/src/tunnel.js DELETED
@@ -1,116 +0,0 @@
1
- import ora from "ora";
2
- import chalk from "chalk";
3
- import { CONFIG, PATHS, TUNNEL_TIMEOUT_MS } from "./config.js";
4
- import { state } from "./state.js";
5
- import { BinaryManager } from "./binary.js";
6
- import { APIClient } from "./api.js";
7
- import { VersionManager } from "./version.js";
8
- import { UI } from "./ui.js";
9
- import { analytics } from "./analytics.js";
10
- import { lang } from "./lang.js";
11
-
12
- /**
13
- * Tunnel Orchestrator
14
- * Main controller for tunnel lifecycle management
15
- */
16
- export class TunnelOrchestrator {
17
- static async start(config) {
18
- state.setTunnel(null, config.subdomain, config.port, config.backendUrl);
19
-
20
- // Initialize analytics
21
- await analytics.initialize();
22
-
23
- // Track CLI start
24
- analytics.trackCliStart(config.port, config.subdomain, CONFIG.CURRENT_VERSION);
25
-
26
- // Display UI
27
- UI.displayStartupBanner(config.port);
28
-
29
- // Check for updates
30
- const updateInfo = await VersionManager.checkForUpdates();
31
- state.setUpdateInfo(updateInfo);
32
-
33
- // Validate binary
34
- if (!BinaryManager.validate(PATHS.BIN_PATH)) {
35
- analytics.trackTunnelError("binary_missing", "Cloudflared binary not found");
36
- // Give analytics a moment to send before exiting
37
- await new Promise(resolve => setTimeout(resolve, 100));
38
- process.exit(1);
39
- }
40
-
41
- const spinner = ora(lang.t("creatingTunnel", { port: config.port })).start();
42
-
43
- try {
44
- // Create tunnel
45
- const tunnel = await APIClient.createTunnel(config.subdomain, config.backendUrl);
46
- state.setTunnel(tunnel.tunnelId, config.subdomain, config.port, config.backendUrl);
47
-
48
- // Track successful tunnel creation
49
- analytics.trackTunnelCreated(config.subdomain, config.port);
50
-
51
- spinner.stop();
52
- console.log(chalk.green(` ${lang.t("tunnelLive")}`));
53
- UI.displayTunnelSuccess(tunnel.url, config.port, updateInfo);
54
-
55
- // Spawn cloudflared
56
- const process = BinaryManager.spawn(
57
- PATHS.BIN_PATH,
58
- tunnel.tunnelToken,
59
- config.port
60
- );
61
- state.setProcess(process);
62
- BinaryManager.attachHandlers(process, spinner);
63
-
64
- // Set timeout
65
- const timeoutId = setTimeout(() => {
66
- UI.displayTimeoutWarning();
67
- this.cleanup("timeout");
68
- }, TUNNEL_TIMEOUT_MS);
69
- state.setTimeout(timeoutId);
70
- } catch (error) {
71
- // Track tunnel creation error
72
- const errorType = error.message.includes("already taken")
73
- ? "subdomain_taken"
74
- : "tunnel_creation_failed";
75
- analytics.trackTunnelError(errorType, error.message);
76
-
77
- UI.displayError(error, spinner);
78
- // Give analytics a moment to send before exiting
79
- await new Promise(resolve => setTimeout(resolve, 100));
80
- process.exit(1);
81
- }
82
- }
83
-
84
- static async cleanup(reason = "manual") {
85
- state.clearTimeout();
86
-
87
- if (!state.hasTunnel()) {
88
- process.exit(0);
89
- }
90
-
91
- UI.displayCleanupStart();
92
-
93
- // Track tunnel shutdown with duration
94
- const duration = state.getDurationSeconds();
95
- analytics.trackTunnelShutdown(reason, duration);
96
-
97
- try {
98
- // Kill process
99
- if (state.hasProcess()) {
100
- state.tunnelProcess.kill();
101
- }
102
-
103
- // Delete tunnel
104
- await APIClient.deleteTunnel(state.subdomain, state.tunnelId, state.backendUrl);
105
- UI.displayCleanupSuccess();
106
- } catch (err) {
107
- UI.displayCleanupError();
108
- }
109
-
110
- // Give analytics a moment to send (non-blocking)
111
- await new Promise(resolve => setTimeout(resolve, 100));
112
-
113
- process.exit(0);
114
- }
115
- }
116
-