nport 1.0.6 → 2.0.2

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/README.md CHANGED
@@ -1,68 +1,263 @@
1
1
  # <img src="https://nport.link/assets/imgs/nport-logo.png" height="30" style="vertical-align: middle;"> NPort
2
2
 
3
- > A Node.js tool for exposing local servers through Socket.IO tunnels
3
+ > 🚀 Free & open source ngrok alternative - Tunnel localhost to the internet via Cloudflare Edge
4
4
 
5
5
  [![GitHub](https://img.shields.io/github/stars/tuanngocptn/nport?style=social)](https://github.com/tuanngocptn/nport)
6
6
  [![NPM](https://img.shields.io/npm/v/nport?color=red&logo=npm)](https://www.npmjs.com/package/nport)
7
7
  [![Website](https://img.shields.io/website?url=https%3A%2F%2Fnport.link&up_message=nport.link&up_color=blue&down_color=lightgrey&down_message=offline)](https://nport.link)
8
+ [![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
8
9
 
9
10
  ## What is NPort?
10
11
 
11
12
  [![nport](https://github.com/user-attachments/assets/6e188d75-4633-4efa-a7f4-63df6a4a7ac8)](https://nport.link)
12
13
 
13
- NPort allows you to expose your local HTTP servers to the internet using Socket.IO streams. Perfect for:
14
- - Development environments
15
- - Testing webhooks
16
- - Sharing local projects
14
+ NPort is a powerful, lightweight **ngrok alternative** that creates secure HTTP/HTTPS tunnels from your localhost to public URLs using **Cloudflare's global edge network**. No configuration, no accounts, just instant tunnels with custom subdomains!
15
+
16
+ Perfect for:
17
+ - 🚀 **Development environments** - Share your local work instantly
18
+ - 🔒 **Testing webhooks** - Receive webhooks from GitHub, Stripe, PayPal, etc.
19
+ - 📱 **Mobile testing** - Test your web app on real devices
20
+ - 🛠️ **API development** - Debug integrations with external services
21
+ - 👥 **Demo to clients** - Show your progress without deployment
17
22
 
18
23
  ## ✨ Features
19
24
 
20
- - 🔒 **Secure Tunneling**: Share your local server safely using Socket.IO
21
- - 🚀 **Easy Setup**: Minimal configuration required
22
- - 🌐 **Custom Domains**: Get readable URLs like `https://yourname.nport.link`
23
- - 📡 **WebSocket Ready**: Full WebSocket connection support
24
- - 💻 **Cross-Platform**: Runs on Windows, macOS, and Linux
25
+ - **Instant Setup**: One command to expose your localhost
26
+ - 🌐 **Custom Subdomains**: Choose your own URL (e.g., `myapp.nport.link`)
27
+ - 🔒 **Automatic HTTPS**: SSL/TLS encryption via Cloudflare
28
+ - 🌍 **Global Edge Network**: Fast connections worldwide via Cloudflare
29
+ - 📡 **WebSocket Support**: Full WebSocket and Server-Sent Events support
30
+ - 🎯 **No Configuration**: Works out of the box
31
+ - 💻 **Cross-Platform**: Windows, macOS, and Linux support
32
+ - 🆓 **100% Free**: No accounts, no limits, no paywalls
33
+ - 🔓 **Open Source**: MIT licensed
25
34
 
26
35
  ## 📦 Installation
27
36
 
28
- **NPM Package**
29
- ```bash
30
- # Local installation
31
- npm install nport
37
+ ### NPM (Recommended)
32
38
 
39
+ ```bash
33
40
  # Global installation
34
41
  npm install -g nport
42
+
43
+ # Or use npx without installation
44
+ npx nport 3000 -s myapp
35
45
  ```
36
46
 
37
- **From GitHub**
38
- ```bash
39
- # Local installation
40
- npm install git+https://github.com/tuanngocptn/nport.git
47
+ ### From GitHub
41
48
 
42
- # Global installation
49
+ ```bash
43
50
  npm install -g git+https://github.com/tuanngocptn/nport.git
44
51
  ```
45
52
 
46
53
  ## 🚀 Quick Start
47
54
 
48
55
  ### Basic Usage
56
+
57
+ Expose port 3000 with a random subdomain:
49
58
  ```bash
50
- # Local installation
51
- npx nport -s myapp -p 3000
59
+ nport 3000
60
+ ```
52
61
 
53
- # Global installation
54
- nport -s myapp -p 3000
62
+ Output:
55
63
  ```
56
- This will create a tunnel at `https://myapp.nport.link`
64
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
65
+ NPort - Free & Open Source ngrok Alternative
66
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
67
+ 🌐 Website: https://nport.link
68
+ 📦 NPM: https://www.npmjs.com/package/nport
69
+ 💻 GitHub: https://github.com/tuanngocptn/nport
70
+ ☕ Support: https://buymeacoffee.com/tuanngocptn
71
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
57
72
 
58
- ### Advanced Options
73
+ 🚀 Starting Tunnel for port 3000...
74
+ ✔ Tunnel created!
75
+ 🌍 Public URL: https://user-1234.nport.link
76
+ ```
77
+
78
+ ### Custom Subdomain
79
+
80
+ Choose your own subdomain:
81
+ ```bash
82
+ nport 3000 -s myapp
83
+ # Creates: https://myapp.nport.link
84
+ ```
85
+
86
+ Or using long form:
59
87
  ```bash
60
- # Full configuration
61
- npx nport --server https://server.nport.link \
62
- --subdomain myapp \
63
- --hostname 127.0.0.1 \
64
- --port 3000
88
+ nport 3000 --subdomain myapp
65
89
  ```
66
90
 
67
- ## 📝 Credits
68
- Inspired by [Socket Tunnel](https://github.com/ericbarch/socket-tunnel). Read more about the concept in this [blog post](https://ericbarch.com/post/sockettunnel/).
91
+ ## 📖 Usage Examples
92
+
93
+ ### Web Development
94
+
95
+ ```bash
96
+ # Next.js
97
+ npm run dev
98
+ nport 3000 -s my-nextjs-app
99
+
100
+ # React (Create React App)
101
+ npm start
102
+ nport 3000 -s my-react-app
103
+
104
+ # Vue.js
105
+ npm run dev
106
+ nport 8080 -s my-vue-app
107
+
108
+ # Express.js
109
+ node server.js
110
+ nport 3000 -s my-api
111
+ ```
112
+
113
+ ### Webhook Testing
114
+
115
+ ```bash
116
+ # Start your local webhook receiver
117
+ node webhook-receiver.js
118
+
119
+ # Expose it to the internet
120
+ nport 4000 -s my-webhooks
121
+
122
+ # Use in GitHub webhook settings:
123
+ # https://my-webhooks.nport.link/webhook
124
+ ```
125
+
126
+ ### Mobile Device Testing
127
+
128
+ ```bash
129
+ # Start your local dev server
130
+ npm run dev
131
+
132
+ # Create tunnel
133
+ nport 3000 -s mobile-test
134
+
135
+ # Open on your phone:
136
+ # https://mobile-test.nport.link
137
+ ```
138
+
139
+ ## 🎯 CLI Options
140
+
141
+ ```bash
142
+ nport <port> [options]
143
+ ```
144
+
145
+ | Option | Short | Description | Example |
146
+ |--------|-------|-------------|---------|
147
+ | `<port>` | - | Local port to tunnel (default: 8080) | `nport 3000` |
148
+ | `--subdomain` | `-s` | Custom subdomain | `nport 3000 -s myapp` |
149
+ | `-s=value` | - | Alternative format | `nport 3000 -s=myapp` |
150
+
151
+ ## 🔧 How It Works
152
+
153
+ 1. **You run** `nport 3000 -s myapp`
154
+ 2. **NPort creates** a Cloudflare Tunnel
155
+ 3. **DNS record** is created: `myapp.nport.link` → Cloudflare Edge
156
+ 4. **Cloudflared binary** connects your localhost:3000 to Cloudflare
157
+ 5. **Traffic flows** through Cloudflare's global network to your machine
158
+ 6. **On exit** (Ctrl+C), tunnel and DNS are automatically cleaned up
159
+
160
+ ```
161
+ Internet → Cloudflare Edge → Cloudflare Tunnel → Your localhost:3000
162
+ (https://myapp.nport.link)
163
+ ```
164
+
165
+ ## 🛡️ Security
166
+
167
+ - **HTTPS by default**: All tunnels use SSL/TLS encryption
168
+ - **Cloudflare protection**: DDoS protection and security features
169
+ - **Automatic cleanup**: Tunnels are removed when you stop the process
170
+ - **No data logging**: We don't store or log your traffic
171
+
172
+ ## 🆚 Comparison with ngrok
173
+
174
+ | Feature | NPort | ngrok |
175
+ |---------|-------|-------|
176
+ | Price | 100% Free | Free tier limited |
177
+ | Custom subdomains | ✅ Always | ❌ Paid only |
178
+ | HTTPS | ✅ Always | ✅ |
179
+ | Account required | ❌ No | ✅ Yes |
180
+ | Time limits | ❌ None | ⚠️ Free tier limited |
181
+ | Open source | ✅ MIT | ❌ Proprietary |
182
+ | Global network | ✅ Cloudflare | ✅ ngrok Edge |
183
+
184
+ ## 🧹 Cleanup
185
+
186
+ NPort automatically cleans up resources when you:
187
+ - Press **Ctrl+C** to exit
188
+ - Kill the process
189
+ - Terminal closes
190
+
191
+ The cleanup process:
192
+ 1. ✅ Deletes DNS record (`myapp.nport.link`)
193
+ 2. ✅ Removes Cloudflare Tunnel
194
+ 3. ✅ Stops cloudflared process
195
+
196
+ ## 🐛 Troubleshooting
197
+
198
+ ### Binary not found
199
+
200
+ If you see "Cloudflared binary not found":
201
+ ```bash
202
+ npm install -g nport --force
203
+ ```
204
+
205
+ ### Port already in use
206
+
207
+ Make sure your local server is running on the specified port:
208
+ ```bash
209
+ # Check if something is listening on port 3000
210
+ lsof -i :3000 # macOS/Linux
211
+ netstat -ano | findstr :3000 # Windows
212
+ ```
213
+
214
+ ### Subdomain already taken
215
+
216
+ Choose a different subdomain name:
217
+ ```bash
218
+ nport 3000 -s myapp-v2
219
+ ```
220
+
221
+ ### Connection issues
222
+
223
+ The `ERR Cannot determine default origin certificate path` warning is harmless and can be ignored. It appears because cloudflared checks for certificate-based authentication (we use token-based instead).
224
+
225
+ ## 🤝 Contributing
226
+
227
+ Contributions are welcome! Please feel free to submit a Pull Request.
228
+
229
+ 1. Fork the repository
230
+ 2. Create your feature branch (`git checkout -b feature/amazing-feature`)
231
+ 3. Commit your changes (`git commit -m 'Add amazing feature'`)
232
+ 4. Push to the branch (`git push origin feature/amazing-feature`)
233
+ 5. Open a Pull Request
234
+
235
+ ## 💖 Support
236
+
237
+ If you find NPort useful, please consider supporting the project:
238
+
239
+ - ⭐ [Star on GitHub](https://github.com/tuanngocptn/nport)
240
+ - ☕ [Buy me a coffee](https://buymeacoffee.com/tuanngocptn)
241
+ - 💬 Share with your friends and colleagues
242
+ - 🐛 [Report bugs](https://github.com/tuanngocptn/nport/issues)
243
+
244
+ ## 📄 License
245
+
246
+ [MIT License](LICENSE) - Feel free to use NPort in your projects!
247
+
248
+ ## 🙏 Credits
249
+
250
+ - Created by [Nick Pham](https://github.com/tuanngocptn) 🇻🇳
251
+ - Powered by [Cloudflare Tunnels](https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/)
252
+ - Inspired by [ngrok](https://ngrok.com) and [localtunnel](https://github.com/localtunnel/localtunnel)
253
+
254
+ ## 🔗 Links
255
+
256
+ - 🌐 Website: [https://nport.link](https://nport.link)
257
+ - 📦 NPM: [https://www.npmjs.com/package/nport](https://www.npmjs.com/package/nport)
258
+ - 💻 GitHub: [https://github.com/tuanngocptn/nport](https://github.com/tuanngocptn/nport)
259
+ - 📧 Email: tuanngocptn@gmail.com
260
+
261
+ ---
262
+
263
+ Made with ❤️ by [Nick Pham](https://github.com/tuanngocptn)
package/analytics.js ADDED
@@ -0,0 +1,259 @@
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(__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"),
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
+ // Try to read existing user ID
96
+ if (fs.existsSync(ANALYTICS_CONFIG.userIdFile)) {
97
+ const userId = fs.readFileSync(ANALYTICS_CONFIG.userIdFile, "utf8").trim();
98
+ if (userId) return userId;
99
+ }
100
+
101
+ // Generate new anonymous user ID
102
+ const userId = this.generateAnonymousId();
103
+
104
+ // Save for future use
105
+ fs.writeFileSync(ANALYTICS_CONFIG.userIdFile, userId, "utf8");
106
+
107
+ return userId;
108
+ } catch (error) {
109
+ // If file operations fail, use session-based ID
110
+ return this.generateAnonymousId();
111
+ }
112
+ }
113
+
114
+ /**
115
+ * Generate anonymous user ID based on machine characteristics
116
+ */
117
+ generateAnonymousId() {
118
+ const machineId = [
119
+ os.hostname(),
120
+ os.platform(),
121
+ os.arch(),
122
+ os.homedir(),
123
+ ].join("-");
124
+
125
+ return createHash("sha256").update(machineId).digest("hex").substring(0, 32);
126
+ }
127
+
128
+ /**
129
+ * Generate session ID
130
+ */
131
+ generateSessionId() {
132
+ return randomUUID();
133
+ }
134
+
135
+ /**
136
+ * Track an event
137
+ */
138
+ async trackEvent(eventName, params = {}) {
139
+ if (this.disabled || !ANALYTICS_CONFIG.enabled) return;
140
+
141
+ try {
142
+ const payload = this.buildPayload(eventName, params);
143
+
144
+ // Send to GA4 Measurement Protocol (non-blocking)
145
+ axios.post(
146
+ `https://www.google-analytics.com/mp/collect?measurement_id=${FIREBASE_CONFIG.measurementId}&api_secret=${FIREBASE_CONFIG.apiSecret}`,
147
+ payload,
148
+ {
149
+ timeout: ANALYTICS_CONFIG.timeout,
150
+ headers: { "Content-Type": "application/json" },
151
+ }
152
+ ).catch((error) => {
153
+ // Silently fail - don't interrupt CLI operations
154
+ if (ANALYTICS_CONFIG.debug) {
155
+ console.error("[Analytics] Failed to send event:", error.message);
156
+ }
157
+ });
158
+
159
+ if (ANALYTICS_CONFIG.debug) {
160
+ console.log("[Analytics] Event tracked:", eventName, params);
161
+ }
162
+ } catch (error) {
163
+ // Silently fail
164
+ if (ANALYTICS_CONFIG.debug) {
165
+ console.error("[Analytics] Error tracking event:", error.message);
166
+ }
167
+ }
168
+ }
169
+
170
+ /**
171
+ * Build GA4 Measurement Protocol payload
172
+ */
173
+ buildPayload(eventName, params) {
174
+ return {
175
+ client_id: this.userId,
176
+ events: [
177
+ {
178
+ name: eventName,
179
+ params: {
180
+ session_id: this.sessionId,
181
+ engagement_time_msec: "100",
182
+ ...this.getSystemInfo(),
183
+ ...params,
184
+ },
185
+ },
186
+ ],
187
+ };
188
+ }
189
+
190
+ /**
191
+ * Get system information for context
192
+ */
193
+ getSystemInfo() {
194
+ return {
195
+ os_platform: os.platform(),
196
+ os_version: os.release(),
197
+ os_arch: os.arch(),
198
+ node_version: process.version,
199
+ };
200
+ }
201
+
202
+ /**
203
+ * Track CLI start
204
+ */
205
+ async trackCliStart(port, subdomain, version) {
206
+ await this.trackEvent("cli_start", {
207
+ port: String(port),
208
+ has_custom_subdomain: subdomain && !subdomain.startsWith("user-"),
209
+ cli_version: version,
210
+ });
211
+ }
212
+
213
+ /**
214
+ * Track tunnel creation
215
+ */
216
+ async trackTunnelCreated(subdomain, port) {
217
+ await this.trackEvent("tunnel_created", {
218
+ subdomain_type: subdomain.startsWith("user-") ? "random" : "custom",
219
+ port: String(port),
220
+ });
221
+ }
222
+
223
+ /**
224
+ * Track tunnel error
225
+ */
226
+ async trackTunnelError(errorType, errorMessage) {
227
+ await this.trackEvent("tunnel_error", {
228
+ error_type: errorType,
229
+ error_message: errorMessage.substring(0, 100), // Limit length
230
+ });
231
+ }
232
+
233
+ /**
234
+ * Track tunnel shutdown
235
+ */
236
+ async trackTunnelShutdown(reason, durationSeconds) {
237
+ await this.trackEvent("tunnel_shutdown", {
238
+ shutdown_reason: reason, // "manual", "timeout", "error"
239
+ duration_seconds: String(Math.floor(durationSeconds)),
240
+ });
241
+ }
242
+
243
+ /**
244
+ * Track CLI update notification shown
245
+ */
246
+ async trackUpdateAvailable(currentVersion, latestVersion) {
247
+ await this.trackEvent("update_available", {
248
+ current_version: currentVersion,
249
+ latest_version: latestVersion,
250
+ });
251
+ }
252
+ }
253
+
254
+ // ============================================================================
255
+ // Export singleton instance
256
+ // ============================================================================
257
+
258
+ export const analytics = new AnalyticsManager();
259
+