nport 2.0.7 → 2.1.1
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 +173 -0
- package/README.md +42 -32
- package/dist/analytics.d.ts +59 -0
- package/dist/analytics.js +193 -0
- package/dist/analytics.js.map +1 -0
- package/dist/api.d.ts +20 -0
- package/dist/api.js +85 -0
- package/dist/api.js.map +1 -0
- package/dist/args.d.ts +44 -0
- package/dist/args.js +127 -0
- package/dist/args.js.map +1 -0
- package/dist/bin-manager.d.ts +1 -0
- package/dist/bin-manager.js +209 -0
- package/dist/bin-manager.js.map +1 -0
- package/dist/binary.d.ts +42 -0
- package/dist/binary.js +119 -0
- package/dist/binary.js.map +1 -0
- package/dist/config-manager.d.ts +54 -0
- package/dist/config-manager.js +129 -0
- package/dist/config-manager.js.map +1 -0
- package/dist/config.d.ts +25 -0
- package/dist/config.js +59 -0
- package/dist/config.js.map +1 -0
- package/dist/constants.d.ts +61 -0
- package/dist/constants.js +86 -0
- package/dist/constants.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +92 -0
- package/dist/index.js.map +1 -0
- package/dist/lang.d.ts +38 -0
- package/dist/lang.js +217 -0
- package/dist/lang.js.map +1 -0
- package/dist/state.d.ts +82 -0
- package/dist/state.js +139 -0
- package/dist/state.js.map +1 -0
- package/dist/tunnel.d.ts +16 -0
- package/dist/tunnel.js +101 -0
- package/dist/tunnel.js.map +1 -0
- package/dist/types/analytics.d.ts +91 -0
- package/dist/types/analytics.js +8 -0
- package/dist/types/analytics.js.map +1 -0
- package/dist/types/config.d.ts +89 -0
- package/dist/types/config.js +8 -0
- package/dist/types/config.js.map +1 -0
- package/dist/types/i18n.d.ts +75 -0
- package/dist/types/i18n.js +5 -0
- package/dist/types/i18n.js.map +1 -0
- package/dist/types/index.d.ts +10 -0
- package/dist/types/index.js +7 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/tunnel.d.ts +74 -0
- package/dist/types/tunnel.js +8 -0
- package/dist/types/tunnel.js.map +1 -0
- package/dist/types/version.d.ts +25 -0
- package/dist/types/version.js +5 -0
- package/dist/types/version.js.map +1 -0
- package/dist/ui.d.ts +54 -0
- package/dist/ui.js +120 -0
- package/dist/ui.js.map +1 -0
- package/dist/version.d.ts +16 -0
- package/dist/version.js +49 -0
- package/dist/version.js.map +1 -0
- 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
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Post-install script
|
|
5
|
+
*
|
|
6
|
+
* Runs bin-manager to download cloudflared binary if dist/ exists.
|
|
7
|
+
* Silently skips if dist/ doesn't exist (e.g., during development before build).
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { existsSync } from 'fs';
|
|
11
|
+
import { fileURLToPath } from 'url';
|
|
12
|
+
import { dirname, join } from 'path';
|
|
13
|
+
|
|
14
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
15
|
+
const binManagerPath = join(__dirname, '..', 'dist', 'bin-manager.js');
|
|
16
|
+
|
|
17
|
+
if (existsSync(binManagerPath)) {
|
|
18
|
+
// Dynamic import to run the bin-manager
|
|
19
|
+
import(binManagerPath).catch(() => {
|
|
20
|
+
// Silently fail if import fails
|
|
21
|
+
});
|
|
22
|
+
} else {
|
|
23
|
+
// For development: dist/ doesn't exist yet, skip silently
|
|
24
|
+
console.log('ℹ️ Skipping binary download (run "npm run build" first for development)');
|
|
25
|
+
}
|
package/index.js
DELETED
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import ora from "ora";
|
|
4
|
-
import chalk from "chalk";
|
|
5
|
-
import { ArgumentParser } from "./src/args.js";
|
|
6
|
-
import { TunnelOrchestrator } from "./src/tunnel.js";
|
|
7
|
-
import { VersionManager } from "./src/version.js";
|
|
8
|
-
import { UI } from "./src/ui.js";
|
|
9
|
-
import { CONFIG } from "./src/config.js";
|
|
10
|
-
import { lang } from "./src/lang.js";
|
|
11
|
-
import { configManager } from "./src/config-manager.js";
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* NPort - Free & Open Source ngrok Alternative
|
|
15
|
-
*
|
|
16
|
-
* Main entry point for the NPort CLI application.
|
|
17
|
-
* Handles command-line arguments and orchestrates tunnel creation.
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Display version information with update check
|
|
22
|
-
*/
|
|
23
|
-
async function displayVersion() {
|
|
24
|
-
const spinner = ora(lang.t("checkingUpdates")).start();
|
|
25
|
-
const updateInfo = await VersionManager.checkForUpdates();
|
|
26
|
-
spinner.stop();
|
|
27
|
-
|
|
28
|
-
UI.displayVersion(CONFIG.CURRENT_VERSION, updateInfo);
|
|
29
|
-
}
|
|
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
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Main application entry point
|
|
59
|
-
*/
|
|
60
|
-
async function main() {
|
|
61
|
-
try {
|
|
62
|
-
const args = process.argv.slice(2);
|
|
63
|
-
|
|
64
|
-
// Parse arguments
|
|
65
|
-
const config = ArgumentParser.parse(args);
|
|
66
|
-
|
|
67
|
-
// Initialize language first (may prompt user)
|
|
68
|
-
await lang.initialize(config.language);
|
|
69
|
-
|
|
70
|
-
// Check for version flag (after language is set)
|
|
71
|
-
if (args.includes('-v') || args.includes('--version')) {
|
|
72
|
-
await displayVersion();
|
|
73
|
-
process.exit(0);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// Handle --set-backend command
|
|
77
|
-
if (config.setBackend) {
|
|
78
|
-
handleSetBackend(config.setBackend);
|
|
79
|
-
process.exit(0);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// If only --language flag was used, show success message and exit
|
|
83
|
-
if (config.language === 'prompt' &&
|
|
84
|
-
(args.includes('--language') || args.includes('--lang') || args.includes('-l'))) {
|
|
85
|
-
// Language was already selected in initialize(), just exit
|
|
86
|
-
process.exit(0);
|
|
87
|
-
}
|
|
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
|
-
|
|
97
|
-
// Start tunnel
|
|
98
|
-
await TunnelOrchestrator.start(config);
|
|
99
|
-
} catch (error) {
|
|
100
|
-
console.error(`Fatal Error: ${error.message}`);
|
|
101
|
-
process.exit(1);
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// Register cleanup handlers for graceful shutdown
|
|
106
|
-
process.on("SIGINT", () => TunnelOrchestrator.cleanup());
|
|
107
|
-
process.on("SIGTERM", () => TunnelOrchestrator.cleanup());
|
|
108
|
-
|
|
109
|
-
// Start application
|
|
110
|
-
main();
|
package/src/analytics.js
DELETED
|
@@ -1,265 +0,0 @@
|
|
|
1
|
-
// ============================================================================
|
|
2
|
-
// Firebase Analytics for CLI
|
|
3
|
-
// Using Google Analytics 4 Measurement Protocol
|
|
4
|
-
// ============================================================================
|
|
5
|
-
|
|
6
|
-
import axios from "axios";
|
|
7
|
-
import { createHash, randomUUID } from "crypto";
|
|
8
|
-
import os from "os";
|
|
9
|
-
import fs from "fs";
|
|
10
|
-
import path from "path";
|
|
11
|
-
import { fileURLToPath } from "url";
|
|
12
|
-
|
|
13
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
14
|
-
const __dirname = path.dirname(path.dirname(__filename));
|
|
15
|
-
|
|
16
|
-
// Firebase/GA4 Configuration (from website/home.html)
|
|
17
|
-
// Full Firebase config for reference (if needed for future features)
|
|
18
|
-
const FIREBASE_WEB_CONFIG = {
|
|
19
|
-
apiKey: "AIzaSyArRxHZJUt4o2RxiLqX1yDSkuUd6ZFy45I",
|
|
20
|
-
authDomain: "nport-link.firebaseapp.com",
|
|
21
|
-
projectId: "nport-link",
|
|
22
|
-
storageBucket: "nport-link.firebasestorage.app",
|
|
23
|
-
messagingSenderId: "515584605320",
|
|
24
|
-
appId: "1:515584605320:web:88daabc8d77146c6e7f33d",
|
|
25
|
-
measurementId: "G-8MYXZL6PGD"
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
// Analytics-specific config (for GA4 Measurement Protocol)
|
|
29
|
-
const FIREBASE_CONFIG = {
|
|
30
|
-
measurementId: FIREBASE_WEB_CONFIG.measurementId,
|
|
31
|
-
apiSecret: process.env.NPORT_ANALYTICS_SECRET || "YOUR_API_SECRET_HERE", // Get from Firebase Console
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
// Analytics Configuration
|
|
35
|
-
const ANALYTICS_CONFIG = {
|
|
36
|
-
enabled: true, // Can be disabled by environment variable
|
|
37
|
-
debug: process.env.NPORT_DEBUG === "true",
|
|
38
|
-
timeout: 2000, // Don't block CLI for too long
|
|
39
|
-
userIdFile: path.join(os.homedir(), ".nport", "analytics-id"),
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
// ============================================================================
|
|
43
|
-
// Analytics Manager
|
|
44
|
-
// ============================================================================
|
|
45
|
-
|
|
46
|
-
class AnalyticsManager {
|
|
47
|
-
constructor() {
|
|
48
|
-
this.userId = null;
|
|
49
|
-
this.sessionId = null;
|
|
50
|
-
this.disabled = false;
|
|
51
|
-
|
|
52
|
-
// Disable analytics if environment variable is set
|
|
53
|
-
if (process.env.NPORT_ANALYTICS === "false") {
|
|
54
|
-
this.disabled = true;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Initialize analytics - must be called before tracking
|
|
60
|
-
*/
|
|
61
|
-
async initialize() {
|
|
62
|
-
if (this.disabled) return;
|
|
63
|
-
|
|
64
|
-
// Check if API secret is configured
|
|
65
|
-
if (!FIREBASE_CONFIG.apiSecret || FIREBASE_CONFIG.apiSecret === "YOUR_API_SECRET_HERE") {
|
|
66
|
-
if (ANALYTICS_CONFIG.debug) {
|
|
67
|
-
console.warn("[Analytics] API secret not configured. Analytics disabled.");
|
|
68
|
-
console.warn("[Analytics] Set NPORT_ANALYTICS_SECRET environment variable.");
|
|
69
|
-
}
|
|
70
|
-
this.disabled = true;
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
try {
|
|
75
|
-
this.userId = await this.getUserId();
|
|
76
|
-
this.sessionId = this.generateSessionId();
|
|
77
|
-
|
|
78
|
-
if (ANALYTICS_CONFIG.debug) {
|
|
79
|
-
console.log("[Analytics] Initialized successfully");
|
|
80
|
-
console.log("[Analytics] User ID:", this.userId.substring(0, 8) + "...");
|
|
81
|
-
}
|
|
82
|
-
} catch (error) {
|
|
83
|
-
if (ANALYTICS_CONFIG.debug) {
|
|
84
|
-
console.error("[Analytics] Initialization failed:", error.message);
|
|
85
|
-
}
|
|
86
|
-
this.disabled = true;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Get or create a persistent user ID
|
|
92
|
-
*/
|
|
93
|
-
async getUserId() {
|
|
94
|
-
try {
|
|
95
|
-
// Ensure .nport directory exists
|
|
96
|
-
const configDir = path.join(os.homedir(), ".nport");
|
|
97
|
-
if (!fs.existsSync(configDir)) {
|
|
98
|
-
fs.mkdirSync(configDir, { recursive: true });
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// Try to read existing user ID
|
|
102
|
-
if (fs.existsSync(ANALYTICS_CONFIG.userIdFile)) {
|
|
103
|
-
const userId = fs.readFileSync(ANALYTICS_CONFIG.userIdFile, "utf8").trim();
|
|
104
|
-
if (userId) return userId;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// Generate new anonymous user ID
|
|
108
|
-
const userId = this.generateAnonymousId();
|
|
109
|
-
|
|
110
|
-
// Save for future use
|
|
111
|
-
fs.writeFileSync(ANALYTICS_CONFIG.userIdFile, userId, "utf8");
|
|
112
|
-
|
|
113
|
-
return userId;
|
|
114
|
-
} catch (error) {
|
|
115
|
-
// If file operations fail, use session-based ID
|
|
116
|
-
return this.generateAnonymousId();
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* Generate anonymous user ID based on machine characteristics
|
|
122
|
-
*/
|
|
123
|
-
generateAnonymousId() {
|
|
124
|
-
const machineId = [
|
|
125
|
-
os.hostname(),
|
|
126
|
-
os.platform(),
|
|
127
|
-
os.arch(),
|
|
128
|
-
os.homedir(),
|
|
129
|
-
].join("-");
|
|
130
|
-
|
|
131
|
-
return createHash("sha256").update(machineId).digest("hex").substring(0, 32);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* Generate session ID
|
|
136
|
-
*/
|
|
137
|
-
generateSessionId() {
|
|
138
|
-
return randomUUID();
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* Track an event
|
|
143
|
-
*/
|
|
144
|
-
async trackEvent(eventName, params = {}) {
|
|
145
|
-
if (this.disabled || !ANALYTICS_CONFIG.enabled) return;
|
|
146
|
-
|
|
147
|
-
try {
|
|
148
|
-
const payload = this.buildPayload(eventName, params);
|
|
149
|
-
|
|
150
|
-
// Send to GA4 Measurement Protocol (non-blocking)
|
|
151
|
-
axios.post(
|
|
152
|
-
`https://www.google-analytics.com/mp/collect?measurement_id=${FIREBASE_CONFIG.measurementId}&api_secret=${FIREBASE_CONFIG.apiSecret}`,
|
|
153
|
-
payload,
|
|
154
|
-
{
|
|
155
|
-
timeout: ANALYTICS_CONFIG.timeout,
|
|
156
|
-
headers: { "Content-Type": "application/json" },
|
|
157
|
-
}
|
|
158
|
-
).catch((error) => {
|
|
159
|
-
// Silently fail - don't interrupt CLI operations
|
|
160
|
-
if (ANALYTICS_CONFIG.debug) {
|
|
161
|
-
console.error("[Analytics] Failed to send event:", error.message);
|
|
162
|
-
}
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
if (ANALYTICS_CONFIG.debug) {
|
|
166
|
-
console.log("[Analytics] Event tracked:", eventName, params);
|
|
167
|
-
}
|
|
168
|
-
} catch (error) {
|
|
169
|
-
// Silently fail
|
|
170
|
-
if (ANALYTICS_CONFIG.debug) {
|
|
171
|
-
console.error("[Analytics] Error tracking event:", error.message);
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
/**
|
|
177
|
-
* Build GA4 Measurement Protocol payload
|
|
178
|
-
*/
|
|
179
|
-
buildPayload(eventName, params) {
|
|
180
|
-
return {
|
|
181
|
-
client_id: this.userId,
|
|
182
|
-
events: [
|
|
183
|
-
{
|
|
184
|
-
name: eventName,
|
|
185
|
-
params: {
|
|
186
|
-
session_id: this.sessionId,
|
|
187
|
-
engagement_time_msec: "100",
|
|
188
|
-
...this.getSystemInfo(),
|
|
189
|
-
...params,
|
|
190
|
-
},
|
|
191
|
-
},
|
|
192
|
-
],
|
|
193
|
-
};
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
/**
|
|
197
|
-
* Get system information for context
|
|
198
|
-
*/
|
|
199
|
-
getSystemInfo() {
|
|
200
|
-
return {
|
|
201
|
-
os_platform: os.platform(),
|
|
202
|
-
os_version: os.release(),
|
|
203
|
-
os_arch: os.arch(),
|
|
204
|
-
node_version: process.version,
|
|
205
|
-
};
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
/**
|
|
209
|
-
* Track CLI start
|
|
210
|
-
*/
|
|
211
|
-
async trackCliStart(port, subdomain, version) {
|
|
212
|
-
await this.trackEvent("cli_start", {
|
|
213
|
-
port: String(port),
|
|
214
|
-
has_custom_subdomain: subdomain && !subdomain.startsWith("user-"),
|
|
215
|
-
cli_version: version,
|
|
216
|
-
});
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
/**
|
|
220
|
-
* Track tunnel creation
|
|
221
|
-
*/
|
|
222
|
-
async trackTunnelCreated(subdomain, port) {
|
|
223
|
-
await this.trackEvent("tunnel_created", {
|
|
224
|
-
subdomain_type: subdomain.startsWith("user-") ? "random" : "custom",
|
|
225
|
-
port: String(port),
|
|
226
|
-
});
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
/**
|
|
230
|
-
* Track tunnel error
|
|
231
|
-
*/
|
|
232
|
-
async trackTunnelError(errorType, errorMessage) {
|
|
233
|
-
await this.trackEvent("tunnel_error", {
|
|
234
|
-
error_type: errorType,
|
|
235
|
-
error_message: errorMessage.substring(0, 100), // Limit length
|
|
236
|
-
});
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
/**
|
|
240
|
-
* Track tunnel shutdown
|
|
241
|
-
*/
|
|
242
|
-
async trackTunnelShutdown(reason, durationSeconds) {
|
|
243
|
-
await this.trackEvent("tunnel_shutdown", {
|
|
244
|
-
shutdown_reason: reason, // "manual", "timeout", "error"
|
|
245
|
-
duration_seconds: String(Math.floor(durationSeconds)),
|
|
246
|
-
});
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
/**
|
|
250
|
-
* Track CLI update notification shown
|
|
251
|
-
*/
|
|
252
|
-
async trackUpdateAvailable(currentVersion, latestVersion) {
|
|
253
|
-
await this.trackEvent("update_available", {
|
|
254
|
-
current_version: currentVersion,
|
|
255
|
-
latest_version: latestVersion,
|
|
256
|
-
});
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
// ============================================================================
|
|
261
|
-
// Export singleton instance
|
|
262
|
-
// ============================================================================
|
|
263
|
-
|
|
264
|
-
export const analytics = new AnalyticsManager();
|
|
265
|
-
|
package/src/api.js
DELETED
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
import axios from "axios";
|
|
2
|
-
import chalk from "chalk";
|
|
3
|
-
import { CONFIG } from "./config.js";
|
|
4
|
-
import { state } from "./state.js";
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* API Client
|
|
8
|
-
* Handles communication with the NPort backend service
|
|
9
|
-
*/
|
|
10
|
-
export class APIClient {
|
|
11
|
-
static async createTunnel(subdomain, backendUrl = null) {
|
|
12
|
-
const url = backendUrl || CONFIG.BACKEND_URL;
|
|
13
|
-
try {
|
|
14
|
-
const { data } = await axios.post(url, { subdomain });
|
|
15
|
-
|
|
16
|
-
if (!data.success) {
|
|
17
|
-
throw new Error(data.error || "Unknown error from backend");
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
return {
|
|
21
|
-
tunnelId: data.tunnelId,
|
|
22
|
-
tunnelToken: data.tunnelToken,
|
|
23
|
-
url: data.url,
|
|
24
|
-
};
|
|
25
|
-
} catch (error) {
|
|
26
|
-
throw this.handleError(error, subdomain);
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
static async deleteTunnel(subdomain, tunnelId, backendUrl = null) {
|
|
31
|
-
const url = backendUrl || CONFIG.BACKEND_URL;
|
|
32
|
-
await axios.delete(url, {
|
|
33
|
-
data: { subdomain, tunnelId },
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
static handleError(error, subdomain) {
|
|
38
|
-
if (error.response?.data?.error) {
|
|
39
|
-
const errorMsg = error.response.data.error;
|
|
40
|
-
|
|
41
|
-
// Check for protected subdomain (reserved for production services)
|
|
42
|
-
if (errorMsg.includes("SUBDOMAIN_PROTECTED:")) {
|
|
43
|
-
return new Error(
|
|
44
|
-
`Subdomain "${subdomain}" is already taken or in use.\n\n` +
|
|
45
|
-
chalk.yellow(`💡 Try one of these options:\n`) +
|
|
46
|
-
chalk.gray(` 1. Choose a different subdomain: `) +
|
|
47
|
-
chalk.cyan(`nport ${state.port || CONFIG.DEFAULT_PORT} -s ${subdomain}-v2\n`) +
|
|
48
|
-
chalk.gray(` 2. Use a random subdomain: `) +
|
|
49
|
-
chalk.cyan(`nport ${state.port || CONFIG.DEFAULT_PORT}\n`) +
|
|
50
|
-
chalk.gray(
|
|
51
|
-
` 3. Wait a few minutes and retry if you just stopped a tunnel with this name`
|
|
52
|
-
)
|
|
53
|
-
);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// Check for subdomain in use (active tunnel)
|
|
57
|
-
if (
|
|
58
|
-
errorMsg.includes("SUBDOMAIN_IN_USE:") ||
|
|
59
|
-
errorMsg.includes("currently in use") ||
|
|
60
|
-
errorMsg.includes("already exists and is currently active")
|
|
61
|
-
) {
|
|
62
|
-
return new Error(
|
|
63
|
-
chalk.red(`✗ Subdomain "${subdomain}" is already in use!\n\n`) +
|
|
64
|
-
chalk.yellow(`💡 This subdomain is currently being used by another active tunnel.\n\n`) +
|
|
65
|
-
chalk.white(`Choose a different subdomain:\n`) +
|
|
66
|
-
chalk.gray(` 1. Add a suffix: `) +
|
|
67
|
-
chalk.cyan(`nport ${state.port || CONFIG.DEFAULT_PORT} -s ${subdomain}-2\n`) +
|
|
68
|
-
chalk.gray(` 2. Try a variation: `) +
|
|
69
|
-
chalk.cyan(`nport ${state.port || CONFIG.DEFAULT_PORT} -s my-${subdomain}\n`) +
|
|
70
|
-
chalk.gray(` 3. Use random name: `) +
|
|
71
|
-
chalk.cyan(`nport ${state.port || CONFIG.DEFAULT_PORT}\n`)
|
|
72
|
-
);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// Check for duplicate tunnel error (other Cloudflare errors)
|
|
76
|
-
if (
|
|
77
|
-
errorMsg.includes("already have a tunnel") ||
|
|
78
|
-
errorMsg.includes("[1013]")
|
|
79
|
-
) {
|
|
80
|
-
return new Error(
|
|
81
|
-
`Subdomain "${subdomain}" is already taken or in use.\n\n` +
|
|
82
|
-
chalk.yellow(`💡 Try one of these options:\n`) +
|
|
83
|
-
chalk.gray(` 1. Choose a different subdomain: `) +
|
|
84
|
-
chalk.cyan(`nport ${state.port || CONFIG.DEFAULT_PORT} -s ${subdomain}-v2\n`) +
|
|
85
|
-
chalk.gray(` 2. Use a random subdomain: `) +
|
|
86
|
-
chalk.cyan(`nport ${state.port || CONFIG.DEFAULT_PORT}\n`) +
|
|
87
|
-
chalk.gray(
|
|
88
|
-
` 3. Wait a few minutes and retry if you just stopped a tunnel with this name`
|
|
89
|
-
)
|
|
90
|
-
);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
return new Error(`Backend Error: ${errorMsg}`);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
if (error.response) {
|
|
97
|
-
const errorMsg = JSON.stringify(error.response.data, null, 2);
|
|
98
|
-
return new Error(`Backend Error: ${errorMsg}`);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
return error;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
package/src/args.js
DELETED
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
import { CONFIG } from "./config.js";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Command Line Argument Parser
|
|
5
|
-
* Handles parsing of CLI arguments for port, subdomain, language, and backend URL
|
|
6
|
-
*/
|
|
7
|
-
export class ArgumentParser {
|
|
8
|
-
static parse(argv) {
|
|
9
|
-
const port = this.parsePort(argv);
|
|
10
|
-
const subdomain = this.parseSubdomain(argv);
|
|
11
|
-
const language = this.parseLanguage(argv);
|
|
12
|
-
const backendUrl = this.parseBackendUrl(argv);
|
|
13
|
-
const setBackend = this.parseSetBackend(argv);
|
|
14
|
-
return { port, subdomain, language, backendUrl, setBackend };
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
static parsePort(argv) {
|
|
18
|
-
const portArg = parseInt(argv[0]);
|
|
19
|
-
return portArg || CONFIG.DEFAULT_PORT;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
static parseSubdomain(argv) {
|
|
23
|
-
// Try all subdomain formats
|
|
24
|
-
const formats = [
|
|
25
|
-
() => this.findFlagWithEquals(argv, "--subdomain="),
|
|
26
|
-
() => this.findFlagWithEquals(argv, "-s="),
|
|
27
|
-
() => this.findFlagWithValue(argv, "--subdomain"),
|
|
28
|
-
() => this.findFlagWithValue(argv, "-s"),
|
|
29
|
-
];
|
|
30
|
-
|
|
31
|
-
for (const format of formats) {
|
|
32
|
-
const subdomain = format();
|
|
33
|
-
if (subdomain) return subdomain;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
return this.generateRandomSubdomain();
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
static parseLanguage(argv) {
|
|
40
|
-
// Check if --language flag exists without value (to trigger prompt)
|
|
41
|
-
if (argv.includes('--language') || argv.includes('--lang') || argv.includes('-l')) {
|
|
42
|
-
const langIndex = argv.indexOf('--language') !== -1 ? argv.indexOf('--language') :
|
|
43
|
-
argv.indexOf('--lang') !== -1 ? argv.indexOf('--lang') :
|
|
44
|
-
argv.indexOf('-l');
|
|
45
|
-
|
|
46
|
-
// If flag is present but next arg is another flag or doesn't exist, return 'prompt'
|
|
47
|
-
const nextArg = argv[langIndex + 1];
|
|
48
|
-
if (!nextArg || nextArg.startsWith('-')) {
|
|
49
|
-
return 'prompt';
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// Try language flag formats: --language=en, --lang=en, -l en
|
|
54
|
-
const formats = [
|
|
55
|
-
() => this.findFlagWithEquals(argv, "--language="),
|
|
56
|
-
() => this.findFlagWithEquals(argv, "--lang="),
|
|
57
|
-
() => this.findFlagWithEquals(argv, "-l="),
|
|
58
|
-
() => this.findFlagWithValue(argv, "--language"),
|
|
59
|
-
() => this.findFlagWithValue(argv, "--lang"),
|
|
60
|
-
() => this.findFlagWithValue(argv, "-l"),
|
|
61
|
-
];
|
|
62
|
-
|
|
63
|
-
for (const format of formats) {
|
|
64
|
-
const language = format();
|
|
65
|
-
if (language) return language;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
return null; // No language specified
|
|
69
|
-
}
|
|
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
|
-
|
|
108
|
-
static findFlagWithEquals(argv, flag) {
|
|
109
|
-
const arg = argv.find((a) => a.startsWith(flag));
|
|
110
|
-
return arg ? arg.split("=")[1] : null;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
static findFlagWithValue(argv, flag) {
|
|
114
|
-
const index = argv.indexOf(flag);
|
|
115
|
-
return index !== -1 && argv[index + 1] ? argv[index + 1] : null;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
static generateRandomSubdomain() {
|
|
119
|
-
return `${CONFIG.SUBDOMAIN_PREFIX}${Math.floor(Math.random() * 10000)}`;
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|