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/CHANGELOG.md +156 -0
- package/README.md +42 -32
- package/package.json +18 -7
- package/scripts/postinstall.js +25 -0
- package/index.js +0 -110
- package/src/analytics.js +0 -265
- package/src/api.js +0 -104
- package/src/args.js +0 -122
- package/src/bin-manager.js +0 -379
- package/src/binary.js +0 -126
- package/src/config-manager.js +0 -139
- package/src/config.js +0 -88
- package/src/lang.js +0 -293
- package/src/state.js +0 -115
- package/src/tunnel.js +0 -116
- package/src/ui.js +0 -103
- package/src/version.js +0 -56
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
|
-
|