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.
Files changed (77) hide show
  1. package/CHANGELOG.md +173 -0
  2. package/README.md +42 -32
  3. package/dist/analytics.d.ts +59 -0
  4. package/dist/analytics.js +193 -0
  5. package/dist/analytics.js.map +1 -0
  6. package/dist/api.d.ts +20 -0
  7. package/dist/api.js +85 -0
  8. package/dist/api.js.map +1 -0
  9. package/dist/args.d.ts +44 -0
  10. package/dist/args.js +127 -0
  11. package/dist/args.js.map +1 -0
  12. package/dist/bin-manager.d.ts +1 -0
  13. package/dist/bin-manager.js +209 -0
  14. package/dist/bin-manager.js.map +1 -0
  15. package/dist/binary.d.ts +42 -0
  16. package/dist/binary.js +119 -0
  17. package/dist/binary.js.map +1 -0
  18. package/dist/config-manager.d.ts +54 -0
  19. package/dist/config-manager.js +129 -0
  20. package/dist/config-manager.js.map +1 -0
  21. package/dist/config.d.ts +25 -0
  22. package/dist/config.js +59 -0
  23. package/dist/config.js.map +1 -0
  24. package/dist/constants.d.ts +61 -0
  25. package/dist/constants.js +86 -0
  26. package/dist/constants.js.map +1 -0
  27. package/dist/index.d.ts +7 -0
  28. package/dist/index.js +92 -0
  29. package/dist/index.js.map +1 -0
  30. package/dist/lang.d.ts +38 -0
  31. package/dist/lang.js +217 -0
  32. package/dist/lang.js.map +1 -0
  33. package/dist/state.d.ts +82 -0
  34. package/dist/state.js +139 -0
  35. package/dist/state.js.map +1 -0
  36. package/dist/tunnel.d.ts +16 -0
  37. package/dist/tunnel.js +101 -0
  38. package/dist/tunnel.js.map +1 -0
  39. package/dist/types/analytics.d.ts +91 -0
  40. package/dist/types/analytics.js +8 -0
  41. package/dist/types/analytics.js.map +1 -0
  42. package/dist/types/config.d.ts +89 -0
  43. package/dist/types/config.js +8 -0
  44. package/dist/types/config.js.map +1 -0
  45. package/dist/types/i18n.d.ts +75 -0
  46. package/dist/types/i18n.js +5 -0
  47. package/dist/types/i18n.js.map +1 -0
  48. package/dist/types/index.d.ts +10 -0
  49. package/dist/types/index.js +7 -0
  50. package/dist/types/index.js.map +1 -0
  51. package/dist/types/tunnel.d.ts +74 -0
  52. package/dist/types/tunnel.js +8 -0
  53. package/dist/types/tunnel.js.map +1 -0
  54. package/dist/types/version.d.ts +25 -0
  55. package/dist/types/version.js +5 -0
  56. package/dist/types/version.js.map +1 -0
  57. package/dist/ui.d.ts +54 -0
  58. package/dist/ui.js +120 -0
  59. package/dist/ui.js.map +1 -0
  60. package/dist/version.d.ts +16 -0
  61. package/dist/version.js +49 -0
  62. package/dist/version.js.map +1 -0
  63. package/package.json +18 -7
  64. package/scripts/postinstall.js +25 -0
  65. package/index.js +0 -110
  66. package/src/analytics.js +0 -265
  67. package/src/api.js +0 -104
  68. package/src/args.js +0 -122
  69. package/src/bin-manager.js +0 -379
  70. package/src/binary.js +0 -126
  71. package/src/config-manager.js +0 -139
  72. package/src/config.js +0 -88
  73. package/src/lang.js +0 -293
  74. package/src/state.js +0 -115
  75. package/src/tunnel.js +0 -116
  76. package/src/ui.js +0 -103
  77. package/src/version.js +0 -56
package/CHANGELOG.md CHANGED
@@ -5,6 +5,137 @@ All notable changes to NPort will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [2.1.1] - 2026-01-27
9
+
10
+ ### Fixed
11
+ - ๐Ÿ› **NPM Package Build**: Fixed missing `dist/` folder in published package
12
+ - Added build step to npm-publish workflow before publishing
13
+ - Package now includes compiled JavaScript files correctly
14
+
15
+ ### Added
16
+ - ๐Ÿš€ **CI/CD Workflows**: Added GitHub Actions for automated deployments
17
+ - `ci.yml`: Runs tests on push to main and pull requests
18
+ - `deploy-server.yml`: Auto-deploys Cloudflare Worker on server changes
19
+ - `deploy-website.yml`: Auto-deploys website to Cloudflare Pages on changes
20
+ - ๐Ÿงช **Server Test Configuration**: Fixed Vitest config for CI environment
21
+ - Added miniflare bindings for test environment variables
22
+ - Tests now pass in CI without `.dev.vars` file
23
+ - โš™๏ธ **Wrangler Configuration**: Added `preview_urls` setting to suppress warnings
24
+
25
+ ## [2.1.0] - 2026-01-27
26
+
27
+ ### Added
28
+ - ๐Ÿ”ท **Full TypeScript Migration**: Complete rewrite of CLI and Server in TypeScript
29
+ - Strict type checking enabled across the entire codebase
30
+ - All modules converted from JavaScript to TypeScript
31
+ - Type-safe interfaces for configuration, tunnels, analytics, and i18n
32
+ - Better IDE support with IntelliSense and autocompletion
33
+ - ๐Ÿ“š **Comprehensive Documentation**: New docs folder with detailed guides
34
+ - `docs/ARCHITECTURE.md`: System overview, component diagrams, and data flow
35
+ - `docs/API.md`: Complete API reference with endpoints and examples
36
+ - `docs/CONTRIBUTING.md`: Contribution guidelines and development setup
37
+ - ๐Ÿค– **AI Context Support**: Added `.ai/context.md` for AI-assisted development
38
+ - Project structure and key patterns documented
39
+ - Coding conventions and architecture decisions
40
+ - Makes AI pair programming more effective
41
+ - ๐Ÿงช **Unit Testing with Vitest**: Comprehensive test suite for CLI
42
+ - Tests for argument parsing (`tests/args.test.ts`)
43
+ - Tests for version comparison (`tests/version.test.ts`)
44
+ - Tests for state management (`tests/state.test.ts`)
45
+ - Easy to run with `npm test`
46
+ - ๐Ÿ“‹ **Code Ownership**: Added `.github/CODEOWNERS` file
47
+ - Clear ownership for code review assignments
48
+ - Faster PR reviews with automatic reviewer assignment
49
+ - ๐Ÿ“ **Cursor Rules**: Added `.cursorrules` for consistent AI assistance
50
+ - Project-specific coding conventions
51
+ - TypeScript and naming guidelines
52
+ - Common patterns and anti-patterns
53
+ - ๐Ÿ”„ **Auto-download Cloudflared**: Binary downloads automatically on first run
54
+ - No need to run separate install commands
55
+ - Seamless first-time user experience
56
+ - Progress indicator during download
57
+ - ๐Ÿ”’ **Protected Subdomain Support**: Enhanced error handling for reserved subdomains
58
+ - User-friendly error message when trying to create protected subdomains (like `api`)
59
+ - Formatted error output matching existing error style
60
+ - Helpful suggestions to use alternative subdomain names
61
+ - Prevents accidental use of backend service subdomains
62
+ - ๐Ÿ“‹ **TODO Management**: Added `TODO.md` for tracking planned features
63
+ - Move time limit enforcement to backend
64
+ - Update README powered-by section
65
+ - Track terminal link clicks
66
+
67
+ ### Changed
68
+ - ๐Ÿ—๏ธ **Project Structure**: Reorganized for better maintainability
69
+ - All source code in `src/` directory
70
+ - Type definitions in `src/types/`
71
+ - Shared constants in `src/constants.ts`
72
+ - Tests in `tests/` directory
73
+ - ๐Ÿ“ฆ **Build System**: Updated to TypeScript compilation
74
+ - Uses `tsc` for compilation
75
+ - Output to `dist/` directory
76
+ - Source maps for debugging
77
+ - ๐Ÿ”ง **Development Workflow**: Improved developer experience
78
+ - `npm run dev` for watch mode
79
+ - `npm run build` for production
80
+ - `npm test` for running tests
81
+ - `npm run lint` for type checking
82
+ - โš™๏ธ **System Requirements**: Updated to Node.js 20+
83
+ - Minimum Node.js version: 20.0.0
84
+ - Minimum npm version: 10.0.0
85
+ - Better security and performance with latest LTS
86
+
87
+ ### Improved
88
+ - โœจ **Better Error Messages**: Enhanced user feedback for protected subdomains
89
+ - Catches `SUBDOMAIN_PROTECTED` errors from backend
90
+ - Formats error messages consistently with other error types
91
+ - Shows actionable options when subdomain is reserved
92
+
93
+ ### Fixed
94
+ - ๐Ÿ› **Intel Mac Binary Download**: Fixed cloudflared binary download on Intel Macs
95
+ - Node.js reports architecture as `x64`, not `amd64` - now correctly mapped
96
+ - Fixed ARM64 Macs to download the correct `cloudflared-darwin-arm64.tgz` binary
97
+ - Previously ARM64 Macs were incorrectly downloading the AMD64 binary
98
+
99
+ ### Server (v1.1.0)
100
+ - ๐Ÿ”ท **TypeScript Migration**: Server fully converted to TypeScript
101
+ - Type-safe Cloudflare Worker handlers
102
+ - Properly typed API responses
103
+ - Full type coverage for Cloudflare API interactions
104
+ - ๐Ÿงช **Server Tests**: Updated test suite for TypeScript
105
+ - Vitest with Cloudflare Workers pool
106
+ - Tests for all API endpoints
107
+ - Scheduled task testing
108
+ - ๐Ÿ”’ **Protected Subdomains**: Infrastructure to protect critical subdomains from deletion or overwriting
109
+ - New `PROTECTED_SUBDOMAINS` constant array for easy management of reserved subdomains
110
+ - Default protected subdomain: `api` (reserved for NPort backend service)
111
+ - Easy to add more protected subdomains by updating the array
112
+ - Protected subdomains are blocked at both creation and cleanup stages
113
+ - ๐Ÿ›ก๏ธ **Backend Service Protection**: Prevents accidental deletion or overwriting of production services
114
+ - `api` subdomain is now protected from user creation attempts
115
+ - Scheduled cleanup job skips protected subdomains
116
+ - Returns clear error message when users try to use protected names
117
+ - ๐Ÿ”ง **Manual Cleanup Endpoint**: Added `GET /scheduled` endpoint to manually trigger cleanup
118
+ - Useful for testing and on-demand cleanup
119
+ - Respects protected subdomains configuration
120
+ - Returns JSON response with cleanup results
121
+
122
+ ### Technical Details
123
+ - **Type System**:
124
+ - `TunnelConfig`, `TunnelState`, `ConnectionInfo` interfaces
125
+ - `AnalyticsParams`, `VersionInfo`, `I18nStrings` types
126
+ - `LogPatterns` with readonly arrays for constants
127
+ - **ESM Compliance**: All imports use `.js` extensions as required
128
+ - **Constants**: Centralized in `src/constants.ts` with `as const` assertions
129
+ - **Error Handling**: Type-safe error boundaries throughout
130
+
131
+ ### Migration
132
+ - Automatic migration - no manual steps required
133
+ - Existing configuration in `~/.nport/config.json` remains compatible
134
+ - All CLI flags and options unchanged
135
+
136
+ ### Breaking Changes
137
+ - None - fully backward compatible!
138
+
8
139
  ## [2.0.7] - 2026-01-14
9
140
 
10
141
  ### Added
@@ -186,6 +317,48 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
186
317
 
187
318
  ## Version Upgrade Guide
188
319
 
320
+ ### From 2.0.7 to 2.1.0
321
+
322
+ ```bash
323
+ npm install -g nport@latest
324
+ ```
325
+
326
+ **What's New:**
327
+
328
+ 1. **Full TypeScript Rewrite**
329
+ - Both CLI and Server now fully typed
330
+ - Better IDE support and autocompletion
331
+ - Catches errors at compile time
332
+
333
+ 2. **Comprehensive Documentation**
334
+ - Architecture guide in `docs/ARCHITECTURE.md`
335
+ - API reference in `docs/API.md`
336
+ - Contributing guide in `docs/CONTRIBUTING.md`
337
+
338
+ 3. **Unit Testing**
339
+ - Run tests with `npm test`
340
+ - Covers argument parsing, version checks, state management
341
+
342
+ 4. **Auto-download Cloudflared**
343
+ - Binary downloads automatically on first `nport` run
344
+ - No separate install step needed
345
+
346
+ 5. **AI-Friendly Codebase**
347
+ - `.ai/context.md` for AI assistants
348
+ - `.cursorrules` for consistent AI suggestions
349
+ - JSDoc comments throughout
350
+
351
+ **For Contributors:**
352
+ ```bash
353
+ git clone https://github.com/tuanngocptn/nport
354
+ cd nport
355
+ npm install
356
+ npm run build
357
+ npm run dev # Watch mode
358
+ ```
359
+
360
+ **Breaking Changes:** None - fully backward compatible!
361
+
189
362
  ### From 2.0.6 to 2.0.7
190
363
 
191
364
  ```bash
package/README.md CHANGED
@@ -6,6 +6,7 @@
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
8
  [![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
9
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.7-blue.svg)](https://www.typescriptlang.org/)
9
10
 
10
11
  ## What is NPort?
11
12
 
@@ -269,25 +270,30 @@ Internet โ†’ Cloudflare Edge โ†’ Cloudflare Tunnel โ†’ Your localhost:3000
269
270
 
270
271
  ## ๐Ÿ—๏ธ Project Structure
271
272
 
272
- NPort uses a modular architecture for better maintainability:
273
-
274
273
  ```
275
274
  nport/
276
- โ”œโ”€โ”€ index.js # Main entry point
277
- โ”œโ”€โ”€ src/
278
- โ”‚ โ”œโ”€โ”€ analytics.js # Analytics tracking
279
- โ”‚ โ”œโ”€โ”€ api.js # Backend API client
280
- โ”‚ โ”œโ”€โ”€ args.js # CLI argument parser
281
- โ”‚ โ”œโ”€โ”€ binary.js # Cloudflared binary manager
282
- โ”‚ โ”œโ”€โ”€ bin-manager.js # Binary download/installation
283
- โ”‚ โ”œโ”€โ”€ config.js # Configuration constants
284
- โ”‚ โ”œโ”€โ”€ lang.js # Multilingual support
285
- โ”‚ โ”œโ”€โ”€ state.js # Application state
286
- โ”‚ โ”œโ”€โ”€ tunnel.js # Tunnel orchestration
287
- โ”‚ โ”œโ”€โ”€ ui.js # User interface display
288
- โ”‚ โ””โ”€โ”€ version.js # Version management
289
- โ””โ”€โ”€ bin/
290
- โ””โ”€โ”€ cloudflared # Cloudflare tunnel binary
275
+ โ”œโ”€โ”€ src/ # TypeScript source files
276
+ โ”‚ โ”œโ”€โ”€ index.ts # Entry point
277
+ โ”‚ โ”œโ”€โ”€ tunnel.ts # Tunnel orchestration
278
+ โ”‚ โ”œโ”€โ”€ api.ts # Backend API client
279
+ โ”‚ โ”œโ”€โ”€ args.ts # CLI argument parser
280
+ โ”‚ โ”œโ”€โ”€ binary.ts # Cloudflared process manager
281
+ โ”‚ โ”œโ”€โ”€ ui.ts # Console UI components
282
+ โ”‚ โ”œโ”€โ”€ lang.ts # Multilingual support
283
+ โ”‚ โ”œโ”€โ”€ types/ # TypeScript type definitions
284
+ โ”‚ โ””โ”€โ”€ ...
285
+ โ”‚
286
+ โ”œโ”€โ”€ tests/ # Unit tests (vitest)
287
+ โ”œโ”€โ”€ dist/ # Compiled output
288
+ โ”œโ”€โ”€ bin/ # cloudflared binary (downloaded)
289
+ โ”‚
290
+ โ”œโ”€โ”€ server/ # Backend (Cloudflare Worker)
291
+ โ”œโ”€โ”€ website/ # Static landing page
292
+ โ”œโ”€โ”€ docs/ # Documentation
293
+ โ”‚ โ”œโ”€โ”€ ARCHITECTURE.md # Technical architecture
294
+ โ”‚ โ”œโ”€โ”€ API.md # API reference
295
+ โ”‚ โ””โ”€โ”€ CONTRIBUTING.md # Contribution guide
296
+ โ””โ”€โ”€ .ai/ # AI context files
291
297
  ```
292
298
 
293
299
  ## ๐Ÿ›ก๏ธ Security
@@ -370,7 +376,7 @@ Then select your preferred language from the menu.
370
376
  - ๐Ÿ‡บ๐Ÿ‡ธ **English** (`en`) - Default
371
377
  - ๐Ÿ‡ป๐Ÿ‡ณ **Vietnamese** (`vi`) - Tiแบฟng Viแป‡t
372
378
 
373
- Want to add your language? Contributions are welcome! Check out `src/lang.js` to see how easy it is to add translations.
379
+ Want to add your language? Contributions are welcome! Check out the [Contributing Guide](docs/CONTRIBUTING.md).
374
380
 
375
381
  ## ๐Ÿค Contributing
376
382
 
@@ -382,22 +388,26 @@ Contributions are welcome! Please feel free to submit a Pull Request.
382
388
  4. Push to the branch (`git push origin feature/amazing-feature`)
383
389
  5. Open a Pull Request
384
390
 
385
- ### Adding a New Language
391
+ See [docs/CONTRIBUTING.md](docs/CONTRIBUTING.md) for detailed guidelines.
392
+
393
+ ### Development Setup
394
+
395
+ ```bash
396
+ # Clone the repository
397
+ git clone https://github.com/tuanngocptn/nport.git
398
+ cd nport
399
+
400
+ # Install dependencies
401
+ npm install
386
402
 
387
- To add a new language:
403
+ # Build TypeScript
404
+ npm run build
388
405
 
389
- 1. Open `src/lang.js`
390
- 2. Add your language code to `availableLanguages` array
391
- 3. Add translations to the `TRANSLATIONS` object
392
- 4. Submit a PR!
406
+ # Run tests
407
+ npm test
393
408
 
394
- Example:
395
- ```javascript
396
- const TRANSLATIONS = {
397
- en: { /* English translations */ },
398
- vi: { /* Vietnamese translations */ },
399
- es: { /* Your Spanish translations */ },
400
- };
409
+ # Run CLI locally
410
+ node dist/index.js 3000 -s test
401
411
  ```
402
412
 
403
413
  ## ๐Ÿ’– Support
@@ -408,7 +418,7 @@ If you find NPort useful, please consider supporting the project:
408
418
  - โ˜• [Buy me a coffee](https://buymeacoffee.com/tuanngocptn)
409
419
  - ๐Ÿ’ฌ Share with your friends and colleagues
410
420
  - ๐Ÿ› [Report bugs](https://github.com/tuanngocptn/nport/issues)
411
- - ๐ŸŒ [Add translations](https://github.com/tuanngocptn/nport/blob/main/src/lang.js)
421
+ - ๐ŸŒ [Add translations](docs/CONTRIBUTING.md#adding-translations)
412
422
 
413
423
  ## ๐Ÿ“„ License
414
424
 
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Analytics Manager
3
+ */
4
+ declare class AnalyticsManager {
5
+ private userId;
6
+ private sessionId;
7
+ private disabled;
8
+ constructor();
9
+ /**
10
+ * Initializes analytics.
11
+ */
12
+ initialize(): Promise<void>;
13
+ /**
14
+ * Gets or creates a persistent user ID.
15
+ */
16
+ private getUserId;
17
+ /**
18
+ * Generates an anonymous user ID.
19
+ */
20
+ private generateAnonymousId;
21
+ /**
22
+ * Generates a session ID.
23
+ */
24
+ private generateSessionId;
25
+ /**
26
+ * Tracks an event.
27
+ */
28
+ private trackEvent;
29
+ /**
30
+ * Builds GA4 payload.
31
+ */
32
+ private buildPayload;
33
+ /**
34
+ * Gets system information.
35
+ */
36
+ private getSystemInfo;
37
+ /**
38
+ * Tracks CLI start.
39
+ */
40
+ trackCliStart(port: number, subdomain: string, version: string): void;
41
+ /**
42
+ * Tracks tunnel creation.
43
+ */
44
+ trackTunnelCreated(subdomain: string, port: number): void;
45
+ /**
46
+ * Tracks tunnel error.
47
+ */
48
+ trackTunnelError(errorType: string, errorMessage: string): void;
49
+ /**
50
+ * Tracks tunnel shutdown.
51
+ */
52
+ trackTunnelShutdown(reason: string, durationSeconds: number): void;
53
+ /**
54
+ * Tracks update available.
55
+ */
56
+ trackUpdateAvailable(currentVersion: string, latestVersion: string): void;
57
+ }
58
+ export declare const analytics: AnalyticsManager;
59
+ export {};
@@ -0,0 +1,193 @@
1
+ import axios from 'axios';
2
+ import { createHash, randomUUID } from 'crypto';
3
+ import os from 'os';
4
+ import fs from 'fs';
5
+ import path from 'path';
6
+ import { ANALYTICS_TIMEOUT } from './constants.js';
7
+ /**
8
+ * Firebase/GA4 Configuration
9
+ */
10
+ const FIREBASE_CONFIG = {
11
+ measurementId: 'G-8MYXZL6PGD',
12
+ apiSecret: process.env.NPORT_ANALYTICS_SECRET || 'YOUR_API_SECRET_HERE',
13
+ };
14
+ const ANALYTICS_CONFIG = {
15
+ enabled: true,
16
+ debug: process.env.NPORT_DEBUG === 'true',
17
+ timeout: ANALYTICS_TIMEOUT,
18
+ userIdFile: path.join(os.homedir(), '.nport', 'analytics-id'),
19
+ };
20
+ /**
21
+ * Analytics Manager
22
+ */
23
+ class AnalyticsManager {
24
+ userId = null;
25
+ sessionId = null;
26
+ disabled = false;
27
+ constructor() {
28
+ if (process.env.NPORT_ANALYTICS === 'false') {
29
+ this.disabled = true;
30
+ }
31
+ }
32
+ /**
33
+ * Initializes analytics.
34
+ */
35
+ async initialize() {
36
+ if (this.disabled)
37
+ return;
38
+ if (!FIREBASE_CONFIG.apiSecret || FIREBASE_CONFIG.apiSecret === 'YOUR_API_SECRET_HERE') {
39
+ if (ANALYTICS_CONFIG.debug) {
40
+ console.warn('[Analytics] API secret not configured. Analytics disabled.');
41
+ }
42
+ this.disabled = true;
43
+ return;
44
+ }
45
+ try {
46
+ this.userId = await this.getUserId();
47
+ this.sessionId = this.generateSessionId();
48
+ if (ANALYTICS_CONFIG.debug) {
49
+ console.log('[Analytics] Initialized successfully');
50
+ }
51
+ }
52
+ catch {
53
+ this.disabled = true;
54
+ }
55
+ }
56
+ /**
57
+ * Gets or creates a persistent user ID.
58
+ */
59
+ async getUserId() {
60
+ try {
61
+ const configDir = path.join(os.homedir(), '.nport');
62
+ if (!fs.existsSync(configDir)) {
63
+ fs.mkdirSync(configDir, { recursive: true });
64
+ }
65
+ if (fs.existsSync(ANALYTICS_CONFIG.userIdFile)) {
66
+ const userId = fs.readFileSync(ANALYTICS_CONFIG.userIdFile, 'utf8').trim();
67
+ if (userId)
68
+ return userId;
69
+ }
70
+ const userId = this.generateAnonymousId();
71
+ fs.writeFileSync(ANALYTICS_CONFIG.userIdFile, userId, 'utf8');
72
+ return userId;
73
+ }
74
+ catch {
75
+ return this.generateAnonymousId();
76
+ }
77
+ }
78
+ /**
79
+ * Generates an anonymous user ID.
80
+ */
81
+ generateAnonymousId() {
82
+ const machineId = [
83
+ os.hostname(),
84
+ os.platform(),
85
+ os.arch(),
86
+ os.homedir(),
87
+ ].join('-');
88
+ return createHash('sha256').update(machineId).digest('hex').substring(0, 32);
89
+ }
90
+ /**
91
+ * Generates a session ID.
92
+ */
93
+ generateSessionId() {
94
+ return randomUUID();
95
+ }
96
+ /**
97
+ * Tracks an event.
98
+ */
99
+ async trackEvent(eventName, params = {}) {
100
+ if (this.disabled || !ANALYTICS_CONFIG.enabled || !this.userId)
101
+ return;
102
+ try {
103
+ const payload = this.buildPayload(eventName, params);
104
+ axios.post(`https://www.google-analytics.com/mp/collect?measurement_id=${FIREBASE_CONFIG.measurementId}&api_secret=${FIREBASE_CONFIG.apiSecret}`, payload, {
105
+ timeout: ANALYTICS_CONFIG.timeout,
106
+ headers: { 'Content-Type': 'application/json' },
107
+ }).catch(() => {
108
+ // Silently fail
109
+ });
110
+ }
111
+ catch {
112
+ // Silently fail
113
+ }
114
+ }
115
+ /**
116
+ * Builds GA4 payload.
117
+ */
118
+ buildPayload(eventName, params) {
119
+ return {
120
+ client_id: this.userId,
121
+ events: [
122
+ {
123
+ name: eventName,
124
+ params: {
125
+ session_id: this.sessionId,
126
+ engagement_time_msec: '100',
127
+ ...this.getSystemInfo(),
128
+ ...params,
129
+ },
130
+ },
131
+ ],
132
+ };
133
+ }
134
+ /**
135
+ * Gets system information.
136
+ */
137
+ getSystemInfo() {
138
+ return {
139
+ os_platform: os.platform(),
140
+ os_version: os.release(),
141
+ os_arch: os.arch(),
142
+ node_version: process.version,
143
+ };
144
+ }
145
+ /**
146
+ * Tracks CLI start.
147
+ */
148
+ trackCliStart(port, subdomain, version) {
149
+ this.trackEvent('cli_start', {
150
+ port: String(port),
151
+ has_custom_subdomain: subdomain && !subdomain.startsWith('user-'),
152
+ cli_version: version,
153
+ });
154
+ }
155
+ /**
156
+ * Tracks tunnel creation.
157
+ */
158
+ trackTunnelCreated(subdomain, port) {
159
+ this.trackEvent('tunnel_created', {
160
+ subdomain_type: subdomain.startsWith('user-') ? 'random' : 'custom',
161
+ port: String(port),
162
+ });
163
+ }
164
+ /**
165
+ * Tracks tunnel error.
166
+ */
167
+ trackTunnelError(errorType, errorMessage) {
168
+ this.trackEvent('tunnel_error', {
169
+ error_type: errorType,
170
+ error_message: errorMessage.substring(0, 100),
171
+ });
172
+ }
173
+ /**
174
+ * Tracks tunnel shutdown.
175
+ */
176
+ trackTunnelShutdown(reason, durationSeconds) {
177
+ this.trackEvent('tunnel_shutdown', {
178
+ shutdown_reason: reason,
179
+ duration_seconds: String(Math.floor(durationSeconds)),
180
+ });
181
+ }
182
+ /**
183
+ * Tracks update available.
184
+ */
185
+ trackUpdateAvailable(currentVersion, latestVersion) {
186
+ this.trackEvent('update_available', {
187
+ current_version: currentVersion,
188
+ latest_version: latestVersion,
189
+ });
190
+ }
191
+ }
192
+ export const analytics = new AnalyticsManager();
193
+ //# sourceMappingURL=analytics.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analytics.js","sourceRoot":"","sources":["../src/analytics.ts"],"names":[],"mappings":"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"}
package/dist/api.d.ts ADDED
@@ -0,0 +1,20 @@
1
+ import type { TunnelResponse } from './types/index.js';
2
+ /**
3
+ * API Client
4
+ *
5
+ * Handles all communication with the NPort backend service.
6
+ */
7
+ export declare class APIClient {
8
+ /**
9
+ * Creates a new Cloudflare tunnel via the backend API.
10
+ */
11
+ static createTunnel(subdomain: string, backendUrl?: string | null): Promise<TunnelResponse>;
12
+ /**
13
+ * Deletes an existing tunnel.
14
+ */
15
+ static deleteTunnel(subdomain: string, tunnelId: string, backendUrl?: string | null): Promise<void>;
16
+ /**
17
+ * Transforms API errors into user-friendly messages.
18
+ */
19
+ private static handleError;
20
+ }
package/dist/api.js ADDED
@@ -0,0 +1,85 @@
1
+ import axios from 'axios';
2
+ import chalk from 'chalk';
3
+ import { CONFIG } from './config.js';
4
+ import { state } from './state.js';
5
+ /**
6
+ * API Client
7
+ *
8
+ * Handles all communication with the NPort backend service.
9
+ */
10
+ export class APIClient {
11
+ /**
12
+ * Creates a new Cloudflare tunnel via the backend API.
13
+ */
14
+ static async createTunnel(subdomain, backendUrl = null) {
15
+ const url = backendUrl || CONFIG.BACKEND_URL;
16
+ try {
17
+ const { data } = await axios.post(url, { subdomain });
18
+ if (!data.success) {
19
+ throw new Error(data.error || 'Unknown error from backend');
20
+ }
21
+ return {
22
+ tunnelId: data.tunnelId,
23
+ tunnelToken: data.tunnelToken,
24
+ url: data.url,
25
+ };
26
+ }
27
+ catch (error) {
28
+ throw this.handleError(error, subdomain);
29
+ }
30
+ }
31
+ /**
32
+ * Deletes an existing tunnel.
33
+ */
34
+ static async deleteTunnel(subdomain, tunnelId, backendUrl = null) {
35
+ const url = backendUrl || CONFIG.BACKEND_URL;
36
+ await axios.delete(url, {
37
+ data: { subdomain, tunnelId },
38
+ });
39
+ }
40
+ /**
41
+ * Transforms API errors into user-friendly messages.
42
+ */
43
+ static handleError(error, subdomain) {
44
+ const errorMsg = error.response?.data?.error;
45
+ if (errorMsg) {
46
+ if (errorMsg.includes('SUBDOMAIN_PROTECTED:')) {
47
+ return new Error(`Subdomain "${subdomain}" is already taken or in use.\n\n` +
48
+ chalk.yellow('๐Ÿ’ก Try one of these options:\n') +
49
+ chalk.gray(' 1. Choose a different subdomain: ') +
50
+ chalk.cyan(`nport ${state.port || CONFIG.DEFAULT_PORT} -s ${subdomain}-v2\n`) +
51
+ chalk.gray(' 2. Use a random subdomain: ') +
52
+ chalk.cyan(`nport ${state.port || CONFIG.DEFAULT_PORT}\n`) +
53
+ chalk.gray(' 3. Wait a few minutes and retry if you just stopped a tunnel with this name'));
54
+ }
55
+ if (errorMsg.includes('SUBDOMAIN_IN_USE:') ||
56
+ errorMsg.includes('currently in use') ||
57
+ errorMsg.includes('already exists and is currently active')) {
58
+ return new Error(chalk.red(`โœ— Subdomain "${subdomain}" is already in use!\n\n`) +
59
+ chalk.yellow('๐Ÿ’ก This subdomain is currently being used by another active tunnel.\n\n') +
60
+ chalk.white('Choose a different subdomain:\n') +
61
+ chalk.gray(' 1. Add a suffix: ') +
62
+ chalk.cyan(`nport ${state.port || CONFIG.DEFAULT_PORT} -s ${subdomain}-2\n`) +
63
+ chalk.gray(' 2. Try a variation: ') +
64
+ chalk.cyan(`nport ${state.port || CONFIG.DEFAULT_PORT} -s my-${subdomain}\n`) +
65
+ chalk.gray(' 3. Use random name: ') +
66
+ chalk.cyan(`nport ${state.port || CONFIG.DEFAULT_PORT}\n`));
67
+ }
68
+ if (errorMsg.includes('already have a tunnel') || errorMsg.includes('[1013]')) {
69
+ return new Error(`Subdomain "${subdomain}" is already taken or in use.\n\n` +
70
+ chalk.yellow('๐Ÿ’ก Try one of these options:\n') +
71
+ chalk.gray(' 1. Choose a different subdomain: ') +
72
+ chalk.cyan(`nport ${state.port || CONFIG.DEFAULT_PORT} -s ${subdomain}-v2\n`) +
73
+ chalk.gray(' 2. Use a random subdomain: ') +
74
+ chalk.cyan(`nport ${state.port || CONFIG.DEFAULT_PORT}\n`) +
75
+ chalk.gray(' 3. Wait a few minutes and retry if you just stopped a tunnel with this name'));
76
+ }
77
+ return new Error(`Backend Error: ${errorMsg}`);
78
+ }
79
+ if (error.response) {
80
+ return new Error(`Backend Error: ${JSON.stringify(error.response.data, null, 2)}`);
81
+ }
82
+ return error;
83
+ }
84
+ }
85
+ //# sourceMappingURL=api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"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"}