nport 2.0.6 → 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/lang.js DELETED
@@ -1,263 +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
-
67
- vi: {
68
- // Header
69
- header: "N P O R T ⚡️ Việt Nam Mãi Đỉnh ❤️",
70
-
71
- // Spinners
72
- creatingTunnel: "🛠️ Đang khởi động cổng {port}... Chuẩn bị bay nào!",
73
- checkingUpdates: "🔍 Đang dò la bản cập nhật mới... Đợi tí sắp có quà!",
74
-
75
- // Success messages
76
- tunnelLive: "🚀 BẬT MODE TỐC HÀNH! ĐANG BAY RỒI NÈ!",
77
- connection1: " ✔ [1/2] Đang cắm dây mạng vũ trụ...",
78
- connection2: " ✔ [2/2] Đang bơm siêu nén khí tốc độ ánh sáng...",
79
- timeRemaining: "⏱️ Tăng tốc thần sầu: Còn {hours}h để quẩy!",
80
-
81
- // Footer
82
- footerTitle: "🔥 LƯU DANH SỬ SÁCH! ĐỪNG QUÊN STAR ⭐️",
83
- footerSubtitle: "(Made in Việt Nam, chuẩn không cần chỉnh! ❤️)",
84
- dropStar: "⭐️ Thả Star: ",
85
- sendCoffee: "☕️ Tặng Coffee: ",
86
- newVersion: "🚀 BẢN MỚI (v{version}) vừa hạ cánh!",
87
- updateCommand: "💡 Gõ liền: npm install -g nport@latest",
88
-
89
- // Cleanup
90
- tunnelShutdown: "🛑 Đã tới giờ 'chốt' deal rồi cả nhà ơi...",
91
- cleaningUp: "Đang dọn dẹp chiến trường... 🧹",
92
- cleanupDone: "Xịn xò! Đã dọn xong rồi nè.",
93
- cleanupFailed: "Oằn trời, dọn không nổi!",
94
- subdomainReleased: "Subdomain... Xí xoá! Tạm biệt nhé 🗑️✨",
95
- serverBusy: "(Có thể server đang bận order trà sữa)",
96
-
97
- // Goodbye
98
- goodbyeTitle: "👋 GẶP LẠI BẠN Ở ĐƯỜNG BĂNG KHÁC...",
99
- goodbyeMessage: "Cảm ơn đã quẩy NPort! Lần sau chơi tiếp nha 😘",
100
- website: "🌐 Sân chơi chính: ",
101
- author: "👤 Nhà tài trợ: ",
102
- changeLanguage: "🌍 Đổi ngôn ngữ: ",
103
- changeLanguageHint: "nport --language",
104
-
105
- // Version
106
- versionTitle: "NPort v{version}",
107
- versionSubtitle: "Hơn cả Ngrok - Ma-de in Ziệt Nam",
108
- versionLatest: "🎉 Chúc mừng! Đang cùng server với bản mới nhất!",
109
- versionAvailable: "🌟 Vèo vèo: Có bản mới v{version} vừa cập bến!",
110
- versionUpdate: "Update khẩn trương lẹ làng: ",
111
- learnMore: "Khám phá thêm cho nóng: ",
112
-
113
- // Language selection
114
- languagePrompt: "\n🌍 Chọn lựa ngôn ngữ ngay bên dưới nào!\n",
115
- languageQuestion: "Chớp lấy một lựa chọn nha (1-2): ",
116
- languageEnglish: "1. English (Chuẩn quốc tế!)",
117
- languageVietnamese: "2. Tiếng Việt (Đỉnh của chóp)",
118
- languageInvalid: "Ơ hơ, chọn sai rồi! Mặc định Tiếng Việt luôn cho nóng.",
119
- languageSaved: "🎯 Xong rồi! Lưu ngôn ngữ thành công!",
120
- }
121
- };
122
-
123
- // ============================================================================
124
- // Language Manager
125
- // ============================================================================
126
-
127
- class LanguageManager {
128
- constructor() {
129
- this.currentLanguage = "en";
130
- this.availableLanguages = ["en", "vi"];
131
- }
132
-
133
- /**
134
- * Get translation string with variable substitution
135
- * @param {string} key - Translation key
136
- * @param {object} vars - Variables to substitute
137
- * @returns {string} Translated string
138
- */
139
- t(key, vars = {}) {
140
- const translations = TRANSLATIONS[this.currentLanguage] || TRANSLATIONS.en;
141
- let text = translations[key] || TRANSLATIONS.en[key] || key;
142
-
143
- // Replace variables like {port}, {version}, etc.
144
- Object.keys(vars).forEach(varKey => {
145
- text = text.replace(`{${varKey}}`, vars[varKey]);
146
- });
147
-
148
- return text;
149
- }
150
-
151
- /**
152
- * Load saved language preference
153
- * @returns {string|null} Saved language code or null
154
- */
155
- loadLanguagePreference() {
156
- const lang = configManager.getLanguage();
157
- if (lang && this.availableLanguages.includes(lang)) {
158
- return lang;
159
- }
160
- return null;
161
- }
162
-
163
- /**
164
- * Save language preference
165
- * @param {string} lang - Language code to save
166
- */
167
- saveLanguagePreference(lang) {
168
- configManager.setLanguage(lang);
169
- }
170
-
171
- /**
172
- * Set current language
173
- * @param {string} lang - Language code
174
- */
175
- setLanguage(lang) {
176
- if (this.availableLanguages.includes(lang)) {
177
- this.currentLanguage = lang;
178
- return true;
179
- }
180
- return false;
181
- }
182
-
183
- /**
184
- * Get current language
185
- * @returns {string} Current language code
186
- */
187
- getLanguage() {
188
- return this.currentLanguage;
189
- }
190
-
191
- /**
192
- * Prompt user to select language
193
- * @returns {Promise<string>} Selected language code
194
- */
195
- async promptLanguageSelection() {
196
- return new Promise((resolve) => {
197
- const rl = readline.createInterface({
198
- input: process.stdin,
199
- output: process.stdout
200
- });
201
-
202
- console.log(this.t("languagePrompt"));
203
- console.log(` ${this.t("languageEnglish")}`);
204
- console.log(` ${this.t("languageVietnamese")}\n`);
205
-
206
- rl.question(`${this.t("languageQuestion")}`, (answer) => {
207
- rl.close();
208
-
209
- const choice = answer.trim();
210
- let selectedLang = "en";
211
-
212
- if (choice === "1") {
213
- selectedLang = "en";
214
- } else if (choice === "2") {
215
- selectedLang = "vi";
216
- } else {
217
- console.log(`\n${this.t("languageInvalid")}\n`);
218
- }
219
-
220
- this.setLanguage(selectedLang);
221
- this.saveLanguagePreference(selectedLang);
222
- console.log(`${this.t("languageSaved")}\n`);
223
-
224
- resolve(selectedLang);
225
- });
226
- });
227
- }
228
-
229
- /**
230
- * Initialize language - load from config or prompt user
231
- * @param {string|null} cliLanguage - Language from CLI argument (or 'prompt' to force prompt)
232
- * @returns {Promise<string>} Selected language code
233
- */
234
- async initialize(cliLanguage = null) {
235
- // Priority 1: CLI argument with value (e.g., --language en)
236
- if (cliLanguage && cliLanguage !== 'prompt' && this.setLanguage(cliLanguage)) {
237
- this.saveLanguagePreference(cliLanguage);
238
- return cliLanguage;
239
- }
240
-
241
- // Priority 2: Force prompt if --language flag without value
242
- if (cliLanguage === 'prompt') {
243
- return await this.promptLanguageSelection();
244
- }
245
-
246
- // Priority 3: Saved preference
247
- const savedLang = this.loadLanguagePreference();
248
- if (savedLang) {
249
- this.setLanguage(savedLang);
250
- return savedLang;
251
- }
252
-
253
- // Priority 4: Prompt user on first run
254
- return await this.promptLanguageSelection();
255
- }
256
- }
257
-
258
- // ============================================================================
259
- // Export singleton instance
260
- // ============================================================================
261
-
262
- export const lang = new LanguageManager();
263
-
package/src/state.js DELETED
@@ -1,79 +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
-
18
- setTunnel(tunnelId, subdomain, port, backendUrl = null) {
19
- this.tunnelId = tunnelId;
20
- this.subdomain = subdomain;
21
- this.port = port;
22
- this.backendUrl = backendUrl;
23
- if (!this.startTime) {
24
- this.startTime = Date.now();
25
- }
26
- }
27
-
28
- setUpdateInfo(updateInfo) {
29
- this.updateInfo = updateInfo;
30
- }
31
-
32
- setProcess(process) {
33
- this.tunnelProcess = process;
34
- }
35
-
36
- setTimeout(timeoutId) {
37
- this.timeoutId = timeoutId;
38
- }
39
-
40
- clearTimeout() {
41
- if (this.timeoutId) {
42
- clearTimeout(this.timeoutId);
43
- this.timeoutId = null;
44
- }
45
- }
46
-
47
- incrementConnection() {
48
- this.connectionCount++;
49
- return this.connectionCount;
50
- }
51
-
52
- hasTunnel() {
53
- return this.tunnelId !== null;
54
- }
55
-
56
- hasProcess() {
57
- return this.tunnelProcess && !this.tunnelProcess.killed;
58
- }
59
-
60
- getDurationSeconds() {
61
- if (!this.startTime) return 0;
62
- return (Date.now() - this.startTime) / 1000;
63
- }
64
-
65
- reset() {
66
- this.clearTimeout();
67
- this.tunnelId = null;
68
- this.subdomain = null;
69
- this.port = null;
70
- this.backendUrl = null;
71
- this.tunnelProcess = null;
72
- this.connectionCount = 0;
73
- this.startTime = null;
74
- this.updateInfo = null;
75
- }
76
- }
77
-
78
- export const state = new TunnelState();
79
-
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
-
package/src/ui.js DELETED
@@ -1,103 +0,0 @@
1
- import chalk from "chalk";
2
- import { CONFIG } from "./config.js";
3
- import { lang } from "./lang.js";
4
-
5
- /**
6
- * UI Display Manager
7
- * Handles all console output and user interface with multilingual support
8
- */
9
- export class UI {
10
- static displayProjectInfo() {
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
-
17
- console.log(chalk.gray(`\n ╭${line}╮`));
18
- console.log(chalk.cyan.bold(` │ ${headerText}`) + padding + chalk.gray("│"));
19
- console.log(chalk.gray(` ╰${line}╯\n`));
20
- }
21
-
22
- static displayStartupBanner(port) {
23
- this.displayProjectInfo();
24
- }
25
-
26
- static displayTunnelSuccess(url, port, updateInfo) {
27
- console.log(); // Extra spacing
28
- console.log(chalk.cyan.bold(` 👉 ${url} 👈\n`));
29
- console.log(chalk.gray(" " + "─".repeat(54) + "\n"));
30
- console.log(chalk.gray(` ${lang.t("timeRemaining", { hours: CONFIG.TUNNEL_TIMEOUT_HOURS })}\n`));
31
- }
32
-
33
- static displayFooter(updateInfo) {
34
- console.log(chalk.gray(" " + "─".repeat(54) + "\n"));
35
- console.log(chalk.yellow.bold(` ${lang.t("footerTitle")}\n`));
36
- console.log(chalk.gray(` ${lang.t("footerSubtitle")}\n`));
37
- console.log(chalk.cyan(` ${lang.t("dropStar")}`) + chalk.white("https://github.com/tuanngocptn/nport"));
38
- console.log(chalk.yellow(` ${lang.t("sendCoffee")}`) + chalk.white("https://buymeacoffee.com/tuanngocptn"));
39
-
40
- if (updateInfo && updateInfo.shouldUpdate) {
41
- console.log(chalk.red.bold(`\n ${lang.t("newVersion", { version: updateInfo.latest })}`));
42
- console.log(chalk.gray(" ") + chalk.cyan(lang.t("updateCommand")));
43
- }
44
- console.log();
45
- }
46
-
47
- static displayTimeoutWarning() {
48
- console.log(
49
- chalk.yellow(
50
- `\n⏰ Tunnel has been running for ${CONFIG.TUNNEL_TIMEOUT_HOURS} hours.`
51
- )
52
- );
53
- console.log(chalk.yellow(" Automatically shutting down..."));
54
- }
55
-
56
- static displayError(error, spinner = null) {
57
- if (spinner) {
58
- spinner.fail("Failed to connect to server.");
59
- }
60
- console.error(chalk.red(error.message));
61
- }
62
-
63
- static displayCleanupStart() {
64
- console.log(chalk.red.bold(`\n\n ${lang.t("tunnelShutdown")}\n`));
65
- process.stdout.write(chalk.gray(` ${lang.t("cleaningUp")}`));
66
- }
67
-
68
- static displayCleanupSuccess() {
69
- console.log(chalk.green(lang.t("cleanupDone")));
70
- console.log(chalk.gray(` ${lang.t("subdomainReleased")}\n`));
71
- this.displayGoodbye();
72
- }
73
-
74
- static displayCleanupError() {
75
- console.log(chalk.red(lang.t("cleanupFailed")));
76
- console.log(chalk.gray(` ${lang.t("serverBusy")}\n`));
77
- this.displayGoodbye();
78
- }
79
-
80
- static displayGoodbye() {
81
- console.log(chalk.gray(" " + "─".repeat(54) + "\n"));
82
- console.log(chalk.cyan.bold(` ${lang.t("goodbyeTitle")}\n`));
83
- console.log(chalk.gray(` ${lang.t("goodbyeMessage")}\n`));
84
- console.log(chalk.cyan(` ${lang.t("website")}`) + chalk.white("https://nport.link"));
85
- console.log(chalk.cyan(` ${lang.t("author")}`) + chalk.white("Nick Pham (https://github.com/tuanngocptn)"));
86
- console.log(chalk.cyan(` ${lang.t("changeLanguage")}`) + chalk.yellow(lang.t("changeLanguageHint")));
87
- console.log();
88
- }
89
-
90
- static displayVersion(current, updateInfo) {
91
- console.log(chalk.cyan.bold(`\n${lang.t("versionTitle", { version: current })}`));
92
- console.log(chalk.gray(`${lang.t("versionSubtitle")}\n`));
93
-
94
- if (updateInfo && updateInfo.shouldUpdate) {
95
- console.log(chalk.yellow(lang.t("versionAvailable", { version: updateInfo.latest })));
96
- console.log(chalk.cyan(lang.t("versionUpdate")) + chalk.white(`npm install -g nport@latest\n`));
97
- } else {
98
- console.log(chalk.green(`${lang.t("versionLatest")}\n`));
99
- }
100
-
101
- console.log(chalk.gray(lang.t("learnMore")) + chalk.cyan("https://nport.link\n"));
102
- }
103
- }
package/src/version.js DELETED
@@ -1,56 +0,0 @@
1
- import axios from "axios";
2
- import { CONFIG } from "./config.js";
3
- import { analytics } from "./analytics.js";
4
-
5
- /**
6
- * Version Manager
7
- * Handles version checking and update notifications
8
- */
9
- export class VersionManager {
10
- static async checkForUpdates() {
11
- try {
12
- const response = await axios.get(
13
- `https://registry.npmjs.org/${CONFIG.PACKAGE_NAME}/latest`,
14
- { timeout: CONFIG.UPDATE_CHECK_TIMEOUT }
15
- );
16
-
17
- const latestVersion = response.data.version;
18
- const shouldUpdate =
19
- this.compareVersions(latestVersion, CONFIG.CURRENT_VERSION) > 0;
20
-
21
- // Track update notification if available
22
- if (shouldUpdate) {
23
- analytics.trackUpdateAvailable(CONFIG.CURRENT_VERSION, latestVersion);
24
- }
25
-
26
- return {
27
- current: CONFIG.CURRENT_VERSION,
28
- latest: latestVersion,
29
- shouldUpdate,
30
- };
31
- } catch (error) {
32
- // Silently fail if can't check for updates
33
- return null;
34
- }
35
- }
36
-
37
- static compareVersions(v1, v2) {
38
- const parts1 = v1.split(".").map(Number);
39
- const parts2 = v2.split(".").map(Number);
40
-
41
- // Compare up to the maximum length of both version arrays
42
- const maxLength = Math.max(parts1.length, parts2.length);
43
-
44
- for (let i = 0; i < maxLength; i++) {
45
- // Treat missing parts as 0 (e.g., "1.0" is "1.0.0")
46
- const part1 = parts1[i] || 0;
47
- const part2 = parts2[i] || 0;
48
-
49
- if (part1 > part2) return 1;
50
- if (part1 < part2) return -1;
51
- }
52
-
53
- return 0;
54
- }
55
- }
56
-