api-response-manager 2.3.0 → 2.5.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/LICENSE +21 -0
- package/README.md +42 -10
- package/bin/arm.js +83 -0
- package/commands/health.js +114 -0
- package/commands/ipBlacklist.js +121 -0
- package/commands/ipWhitelist.js +121 -0
- package/commands/rateLimit.js +69 -0
- package/commands/tunnel.js +68 -6
- package/package.json +15 -4
- package/utils/api.js +19 -1
- package/utils/config.js +6 -1
- package/CLI_UPDATES.md +0 -196
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Vijay Singh Purohit
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
# ARM CLI - API Response Manager Command Line Interface
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/api-response-manager)
|
|
4
|
+
[](https://www.npmjs.com/package/api-response-manager)
|
|
5
|
+
[](https://github.com/vijaypurohit322/api-response-manager/blob/main/LICENSE)
|
|
6
|
+
|
|
3
7
|
Command-line interface for API Response Manager. Manage tunnels, webhooks, and projects from your terminal.
|
|
4
8
|
|
|
9
|
+
**Version:** 2.5.0 | **Live Service:** https://tunnelapi.in
|
|
10
|
+
|
|
5
11
|
## Installation
|
|
6
12
|
|
|
7
13
|
### Option 1: Install from npm (Recommended)
|
|
8
14
|
```bash
|
|
9
|
-
npm install -g
|
|
15
|
+
npm install -g api-response-manager
|
|
10
16
|
```
|
|
11
17
|
|
|
12
18
|
After installation, verify:
|
|
@@ -33,9 +39,9 @@ arm --version
|
|
|
33
39
|
|
|
34
40
|
### Option 3: Use with npx (No Installation)
|
|
35
41
|
```bash
|
|
36
|
-
npx
|
|
37
|
-
npx
|
|
38
|
-
npx
|
|
42
|
+
npx api-response-manager login
|
|
43
|
+
npx api-response-manager tunnel 3000
|
|
44
|
+
npx api-response-manager webhook
|
|
39
45
|
```
|
|
40
46
|
|
|
41
47
|
## Quick Start
|
|
@@ -444,12 +450,31 @@ Default configuration:
|
|
|
444
450
|
# Login
|
|
445
451
|
arm login
|
|
446
452
|
|
|
447
|
-
# Start
|
|
448
|
-
arm tunnel 3000 --
|
|
453
|
+
# Start tunnel on port 3000 with custom subdomain
|
|
454
|
+
arm tunnel 3000 --subdomain myapp
|
|
449
455
|
|
|
450
456
|
# Your local server is now accessible at:
|
|
451
|
-
# https://myapp.
|
|
452
|
-
|
|
457
|
+
# https://myapp.free-tunnelapi.app
|
|
458
|
+
|
|
459
|
+
# Output:
|
|
460
|
+
# 🚇 Starting Tunnel...
|
|
461
|
+
# ✔ Tunnel created successfully!
|
|
462
|
+
# ┌─────────────────────────────────────────────┐
|
|
463
|
+
# │ Tunnel Information │
|
|
464
|
+
# ├─────────────────────────────────────────────┤
|
|
465
|
+
# │ Name: myapp │
|
|
466
|
+
# │ Public URL: https://myapp.free-tunnelapi.app│
|
|
467
|
+
# │ Local Port: 3000 │
|
|
468
|
+
# └─────────────────────────────────────────────┘
|
|
469
|
+
# 🎉 Tunnel Active!
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
### Tunnel Timeouts (Industry Standard)
|
|
473
|
+
| Setting | Value | Description |
|
|
474
|
+
|---------|-------|-------------|
|
|
475
|
+
| Heartbeat | 30 seconds | CLI sends keepalive every 30s |
|
|
476
|
+
| Idle Timeout | 2 hours | Closes after 2 hours of no requests |
|
|
477
|
+
| Max Session | 24 hours | Requires reconnect after 24 hours |
|
|
453
478
|
|
|
454
479
|
### Secure Tunnel with OAuth Authentication
|
|
455
480
|
```bash
|
|
@@ -563,7 +588,7 @@ source ~/.bashrc
|
|
|
563
588
|
|
|
564
589
|
**Alternative - Use npx:**
|
|
565
590
|
```bash
|
|
566
|
-
npx
|
|
591
|
+
npx api-response-manager login
|
|
567
592
|
```
|
|
568
593
|
|
|
569
594
|
## Publishing to npm
|
|
@@ -591,4 +616,11 @@ npm publish --access public
|
|
|
591
616
|
|
|
592
617
|
## License
|
|
593
618
|
|
|
594
|
-
|
|
619
|
+
This software is proprietary. See [LICENSE](https://github.com/vijaypurohit322/api-response-manager/blob/main/LICENSE) for details.
|
|
620
|
+
|
|
621
|
+
**Key Points:**
|
|
622
|
+
- ✅ Personal and educational use allowed
|
|
623
|
+
- ✅ Self-hosting for non-commercial use allowed
|
|
624
|
+
- ❌ Commercial use requires separate license
|
|
625
|
+
- ❌ Resale or redistribution prohibited
|
|
626
|
+
- 📧 Contact: vijaypurohit322@gmail.com
|
package/bin/arm.js
CHANGED
|
@@ -17,6 +17,10 @@ const webhookCommand = require('../commands/webhook');
|
|
|
17
17
|
const projectCommand = require('../commands/project');
|
|
18
18
|
const logsCommand = require('../commands/logs');
|
|
19
19
|
const configCommand = require('../commands/config');
|
|
20
|
+
const ipWhitelistCommand = require('../commands/ipWhitelist');
|
|
21
|
+
const ipBlacklistCommand = require('../commands/ipBlacklist');
|
|
22
|
+
const rateLimitCommand = require('../commands/rateLimit');
|
|
23
|
+
const healthCommand = require('../commands/health');
|
|
20
24
|
|
|
21
25
|
// CLI setup
|
|
22
26
|
program
|
|
@@ -127,6 +131,85 @@ program
|
|
|
127
131
|
.option('--tls', 'Enable TLS for ingress')
|
|
128
132
|
.action(tunnelCommand.configureIngress);
|
|
129
133
|
|
|
134
|
+
// IP Whitelist commands
|
|
135
|
+
program
|
|
136
|
+
.command('tunnel:ip-whitelist:add')
|
|
137
|
+
.description('Add IP to tunnel whitelist')
|
|
138
|
+
.argument('<tunnelId>', 'Tunnel ID')
|
|
139
|
+
.argument('<ip>', 'IP address or CIDR range (e.g., 192.168.1.100 or 10.0.0.0/8)')
|
|
140
|
+
.action(ipWhitelistCommand.add);
|
|
141
|
+
|
|
142
|
+
program
|
|
143
|
+
.command('tunnel:ip-whitelist:remove')
|
|
144
|
+
.description('Remove IP from tunnel whitelist')
|
|
145
|
+
.argument('<tunnelId>', 'Tunnel ID')
|
|
146
|
+
.argument('<ip>', 'IP address or CIDR range')
|
|
147
|
+
.action(ipWhitelistCommand.remove);
|
|
148
|
+
|
|
149
|
+
program
|
|
150
|
+
.command('tunnel:ip-whitelist:list')
|
|
151
|
+
.description('List tunnel IP whitelist')
|
|
152
|
+
.argument('<tunnelId>', 'Tunnel ID')
|
|
153
|
+
.action(ipWhitelistCommand.list);
|
|
154
|
+
|
|
155
|
+
program
|
|
156
|
+
.command('tunnel:ip-whitelist:clear')
|
|
157
|
+
.description('Clear tunnel IP whitelist')
|
|
158
|
+
.argument('<tunnelId>', 'Tunnel ID')
|
|
159
|
+
.action(ipWhitelistCommand.clear);
|
|
160
|
+
|
|
161
|
+
// IP Blacklist commands
|
|
162
|
+
program
|
|
163
|
+
.command('tunnel:ip-blacklist:add')
|
|
164
|
+
.description('Add IP to tunnel blacklist')
|
|
165
|
+
.argument('<tunnelId>', 'Tunnel ID')
|
|
166
|
+
.argument('<ip>', 'IP address or CIDR range')
|
|
167
|
+
.action(ipBlacklistCommand.add);
|
|
168
|
+
|
|
169
|
+
program
|
|
170
|
+
.command('tunnel:ip-blacklist:remove')
|
|
171
|
+
.description('Remove IP from tunnel blacklist')
|
|
172
|
+
.argument('<tunnelId>', 'Tunnel ID')
|
|
173
|
+
.argument('<ip>', 'IP address or CIDR range')
|
|
174
|
+
.action(ipBlacklistCommand.remove);
|
|
175
|
+
|
|
176
|
+
program
|
|
177
|
+
.command('tunnel:ip-blacklist:list')
|
|
178
|
+
.description('List tunnel IP blacklist')
|
|
179
|
+
.argument('<tunnelId>', 'Tunnel ID')
|
|
180
|
+
.action(ipBlacklistCommand.list);
|
|
181
|
+
|
|
182
|
+
program
|
|
183
|
+
.command('tunnel:ip-blacklist:clear')
|
|
184
|
+
.description('Clear tunnel IP blacklist')
|
|
185
|
+
.argument('<tunnelId>', 'Tunnel ID')
|
|
186
|
+
.action(ipBlacklistCommand.clear);
|
|
187
|
+
|
|
188
|
+
// Rate Limit commands
|
|
189
|
+
program
|
|
190
|
+
.command('tunnel:rate-limit')
|
|
191
|
+
.description('Configure tunnel rate limits')
|
|
192
|
+
.argument('<tunnelId>', 'Tunnel ID')
|
|
193
|
+
.option('--rpm <number>', 'Requests per minute')
|
|
194
|
+
.option('--rph <number>', 'Requests per hour')
|
|
195
|
+
.option('--rpd <number>', 'Requests per day')
|
|
196
|
+
.option('--enable', 'Enable rate limiting')
|
|
197
|
+
.option('--disable', 'Disable rate limiting')
|
|
198
|
+
.action(rateLimitCommand.configure);
|
|
199
|
+
|
|
200
|
+
program
|
|
201
|
+
.command('tunnel:rate-limit:show')
|
|
202
|
+
.description('Show tunnel rate limits')
|
|
203
|
+
.argument('<tunnelId>', 'Tunnel ID')
|
|
204
|
+
.action(rateLimitCommand.show);
|
|
205
|
+
|
|
206
|
+
// Health check command
|
|
207
|
+
program
|
|
208
|
+
.command('health')
|
|
209
|
+
.description('Check server health')
|
|
210
|
+
.option('-d, --detailed', 'Show detailed health information')
|
|
211
|
+
.action(healthCommand.check);
|
|
212
|
+
|
|
130
213
|
// Webhook commands
|
|
131
214
|
program
|
|
132
215
|
.command('webhook')
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
const ora = require('ora');
|
|
3
|
+
const axios = require('axios');
|
|
4
|
+
const config = require('../utils/config');
|
|
5
|
+
|
|
6
|
+
// Check basic health
|
|
7
|
+
async function check(options) {
|
|
8
|
+
const apiUrl = config.get('apiUrl') || 'http://localhost:5000/api';
|
|
9
|
+
// Remove /api prefix since apiUrl already includes it
|
|
10
|
+
const endpoint = options.detailed ? '/health/detailed' : '/health';
|
|
11
|
+
|
|
12
|
+
const spinner = ora('Checking server health...').start();
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
const response = await axios.get(`${apiUrl}${endpoint}`, {
|
|
16
|
+
validateStatus: (status) => status < 500 || status === 503 // Accept 503 for degraded status
|
|
17
|
+
});
|
|
18
|
+
const health = response.data;
|
|
19
|
+
|
|
20
|
+
spinner.stop();
|
|
21
|
+
|
|
22
|
+
if (options.detailed) {
|
|
23
|
+
displayDetailedHealth(health);
|
|
24
|
+
} else {
|
|
25
|
+
displayBasicHealth(health);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Exit with 0 for ok/degraded, 1 for error
|
|
29
|
+
process.exit(health.status === 'ok' || health.status === 'degraded' ? 0 : 1);
|
|
30
|
+
} catch (error) {
|
|
31
|
+
spinner.fail(chalk.red('Server is unreachable'));
|
|
32
|
+
console.error(chalk.red(`\n✗ ${error.message}\n`));
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function displayBasicHealth(health) {
|
|
38
|
+
console.log(chalk.blue.bold('\n🏥 Server Health Check\n'));
|
|
39
|
+
|
|
40
|
+
const statusColor = health.status === 'ok' ? chalk.green : chalk.red;
|
|
41
|
+
const statusIcon = health.status === 'ok' ? '✓' : '✗';
|
|
42
|
+
|
|
43
|
+
console.log(chalk.gray('┌─────────────────────────────────────┐'));
|
|
44
|
+
console.log(chalk.gray('│') + chalk.white(' Status: ') + statusColor(`${statusIcon} ${health.status.toUpperCase()}`).padEnd(28) + chalk.gray('│'));
|
|
45
|
+
console.log(chalk.gray('│') + chalk.white(' Uptime: ') + chalk.cyan(formatUptime(health.uptime)).padEnd(20) + chalk.gray('│'));
|
|
46
|
+
console.log(chalk.gray('│') + chalk.white(' Environment: ') + chalk.cyan(health.environment).padEnd(20) + chalk.gray('│'));
|
|
47
|
+
console.log(chalk.gray('│') + chalk.white(' Timestamp: ') + chalk.gray(new Date(health.timestamp).toLocaleString()).padEnd(20) + chalk.gray('│'));
|
|
48
|
+
console.log(chalk.gray('└─────────────────────────────────────┘\n'));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function displayDetailedHealth(health) {
|
|
52
|
+
console.log(chalk.blue.bold('\n🏥 Detailed Server Health Check\n'));
|
|
53
|
+
|
|
54
|
+
const statusColor = health.status === 'ok' ? chalk.green : (health.status === 'degraded' ? chalk.yellow : chalk.red);
|
|
55
|
+
const statusIcon = health.status === 'ok' ? '✓' : (health.status === 'degraded' ? '⚠' : '✗');
|
|
56
|
+
|
|
57
|
+
console.log(chalk.gray('┌─────────────────────────────────────────────────────┐'));
|
|
58
|
+
console.log(chalk.gray('│') + chalk.white.bold(' Overall Status ') + chalk.gray('│'));
|
|
59
|
+
console.log(chalk.gray('├─────────────────────────────────────────────────────┤'));
|
|
60
|
+
console.log(chalk.gray('│') + chalk.white(' Status: ') + statusColor(`${statusIcon} ${health.status.toUpperCase()}`).padEnd(42) + chalk.gray('│'));
|
|
61
|
+
console.log(chalk.gray('│') + chalk.white(' Uptime: ') + chalk.cyan(formatUptime(health.uptime)).padEnd(34) + chalk.gray('│'));
|
|
62
|
+
console.log(chalk.gray('│') + chalk.white(' Environment: ') + chalk.cyan(health.environment).padEnd(34) + chalk.gray('│'));
|
|
63
|
+
console.log(chalk.gray('│') + chalk.white(' Version: ') + chalk.cyan(health.version || 'N/A').padEnd(34) + chalk.gray('│'));
|
|
64
|
+
console.log(chalk.gray('└─────────────────────────────────────────────────────┘\n'));
|
|
65
|
+
|
|
66
|
+
if (health.checks) {
|
|
67
|
+
// Database
|
|
68
|
+
console.log(chalk.white.bold('Database:'));
|
|
69
|
+
displayCheckStatus(health.checks.database);
|
|
70
|
+
|
|
71
|
+
// Redis
|
|
72
|
+
console.log(chalk.white.bold('\nRedis:'));
|
|
73
|
+
displayCheckStatus(health.checks.redis);
|
|
74
|
+
|
|
75
|
+
// Memory
|
|
76
|
+
console.log(chalk.white.bold('\nMemory:'));
|
|
77
|
+
displayCheckStatus(health.checks.memory);
|
|
78
|
+
|
|
79
|
+
console.log();
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function displayCheckStatus(check) {
|
|
84
|
+
const statusColor = check.status === 'ok' ? chalk.green :
|
|
85
|
+
check.status === 'warning' ? chalk.yellow :
|
|
86
|
+
check.status === 'not configured' ? chalk.gray : chalk.red;
|
|
87
|
+
const statusIcon = check.status === 'ok' ? '✓' :
|
|
88
|
+
check.status === 'warning' ? '⚠' :
|
|
89
|
+
check.status === 'not configured' ? '○' : '✗';
|
|
90
|
+
|
|
91
|
+
console.log(` ${statusColor(statusIcon)} ${statusColor(check.status.toUpperCase())}`);
|
|
92
|
+
|
|
93
|
+
Object.keys(check).forEach(key => {
|
|
94
|
+
if (key !== 'status') {
|
|
95
|
+
console.log(` ${chalk.gray(key + ':')} ${chalk.white(check[key])}`);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function formatUptime(seconds) {
|
|
101
|
+
const days = Math.floor(seconds / 86400);
|
|
102
|
+
const hours = Math.floor((seconds % 86400) / 3600);
|
|
103
|
+
const minutes = Math.floor((seconds % 3600) / 60);
|
|
104
|
+
const secs = Math.floor(seconds % 60);
|
|
105
|
+
|
|
106
|
+
if (days > 0) return `${days}d ${hours}h ${minutes}m`;
|
|
107
|
+
if (hours > 0) return `${hours}h ${minutes}m ${secs}s`;
|
|
108
|
+
if (minutes > 0) return `${minutes}m ${secs}s`;
|
|
109
|
+
return `${secs}s`;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
module.exports = {
|
|
113
|
+
check
|
|
114
|
+
};
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
const ora = require('ora');
|
|
3
|
+
const Table = require('cli-table3');
|
|
4
|
+
const api = require('../utils/api');
|
|
5
|
+
|
|
6
|
+
// Add IP to blacklist
|
|
7
|
+
async function add(tunnelId, ip) {
|
|
8
|
+
const spinner = ora('Adding IP to blacklist...').start();
|
|
9
|
+
|
|
10
|
+
try {
|
|
11
|
+
// Get current tunnel
|
|
12
|
+
const tunnel = await api.getTunnel(tunnelId);
|
|
13
|
+
const currentBlacklist = tunnel.tunnel.ipBlacklist || [];
|
|
14
|
+
|
|
15
|
+
// Check if IP already exists
|
|
16
|
+
if (currentBlacklist.includes(ip)) {
|
|
17
|
+
spinner.fail(chalk.yellow(`IP ${ip} is already in the blacklist`));
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Add new IP
|
|
22
|
+
const updatedBlacklist = [...currentBlacklist, ip];
|
|
23
|
+
await api.updateIPBlacklist(tunnelId, updatedBlacklist);
|
|
24
|
+
|
|
25
|
+
spinner.succeed(chalk.green(`IP ${ip} added to blacklist`));
|
|
26
|
+
console.log(chalk.gray(`\nTotal blacklisted IPs: ${updatedBlacklist.length}\n`));
|
|
27
|
+
} catch (error) {
|
|
28
|
+
spinner.fail(chalk.red('Failed to add IP to blacklist'));
|
|
29
|
+
console.error(chalk.red(`\n✗ ${error.response?.data?.msg || error.message}\n`));
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Remove IP from blacklist
|
|
35
|
+
async function remove(tunnelId, ip) {
|
|
36
|
+
const spinner = ora('Removing IP from blacklist...').start();
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
// Get current tunnel
|
|
40
|
+
const tunnel = await api.getTunnel(tunnelId);
|
|
41
|
+
const currentBlacklist = tunnel.tunnel.ipBlacklist || [];
|
|
42
|
+
|
|
43
|
+
// Check if IP exists
|
|
44
|
+
if (!currentBlacklist.includes(ip)) {
|
|
45
|
+
spinner.fail(chalk.yellow(`IP ${ip} is not in the blacklist`));
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Remove IP
|
|
50
|
+
const updatedBlacklist = currentBlacklist.filter(item => item !== ip);
|
|
51
|
+
await api.updateIPBlacklist(tunnelId, updatedBlacklist);
|
|
52
|
+
|
|
53
|
+
spinner.succeed(chalk.green(`IP ${ip} removed from blacklist`));
|
|
54
|
+
console.log(chalk.gray(`\nTotal blacklisted IPs: ${updatedBlacklist.length}\n`));
|
|
55
|
+
} catch (error) {
|
|
56
|
+
spinner.fail(chalk.red('Failed to remove IP from blacklist'));
|
|
57
|
+
console.error(chalk.red(`\n✗ ${error.response?.data?.msg || error.message}\n`));
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// List blacklisted IPs
|
|
63
|
+
async function list(tunnelId) {
|
|
64
|
+
const spinner = ora('Fetching IP blacklist...').start();
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
const tunnel = await api.getTunnel(tunnelId);
|
|
68
|
+
const blacklist = tunnel.tunnel.ipBlacklist || [];
|
|
69
|
+
|
|
70
|
+
spinner.stop();
|
|
71
|
+
|
|
72
|
+
if (blacklist.length === 0) {
|
|
73
|
+
console.log(chalk.yellow('\n⚠ No IP blacklist configured. No IPs are blocked.\n'));
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
console.log(chalk.blue.bold(`\n🚫 IP Blacklist (${blacklist.length} IPs)\n`));
|
|
78
|
+
|
|
79
|
+
const table = new Table({
|
|
80
|
+
head: [chalk.white('IP Address / CIDR')],
|
|
81
|
+
style: {
|
|
82
|
+
head: ['cyan'],
|
|
83
|
+
border: ['gray']
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
blacklist.forEach(ip => {
|
|
88
|
+
table.push([chalk.red(ip)]);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
console.log(table.toString());
|
|
92
|
+
console.log();
|
|
93
|
+
} catch (error) {
|
|
94
|
+
spinner.fail(chalk.red('Failed to fetch IP blacklist'));
|
|
95
|
+
console.error(chalk.red(`\n✗ ${error.response?.data?.msg || error.message}\n`));
|
|
96
|
+
process.exit(1);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Clear all blacklisted IPs
|
|
101
|
+
async function clear(tunnelId) {
|
|
102
|
+
const spinner = ora('Clearing IP blacklist...').start();
|
|
103
|
+
|
|
104
|
+
try {
|
|
105
|
+
await api.updateIPBlacklist(tunnelId, []);
|
|
106
|
+
|
|
107
|
+
spinner.succeed(chalk.green('IP blacklist cleared'));
|
|
108
|
+
console.log(chalk.gray('\nNo IPs are blocked.\n'));
|
|
109
|
+
} catch (error) {
|
|
110
|
+
spinner.fail(chalk.red('Failed to clear IP blacklist'));
|
|
111
|
+
console.error(chalk.red(`\n✗ ${error.response?.data?.msg || error.message}\n`));
|
|
112
|
+
process.exit(1);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
module.exports = {
|
|
117
|
+
add,
|
|
118
|
+
remove,
|
|
119
|
+
list,
|
|
120
|
+
clear
|
|
121
|
+
};
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
const ora = require('ora');
|
|
3
|
+
const Table = require('cli-table3');
|
|
4
|
+
const api = require('../utils/api');
|
|
5
|
+
|
|
6
|
+
// Add IP to whitelist
|
|
7
|
+
async function add(tunnelId, ip) {
|
|
8
|
+
const spinner = ora('Adding IP to whitelist...').start();
|
|
9
|
+
|
|
10
|
+
try {
|
|
11
|
+
// Get current tunnel
|
|
12
|
+
const tunnel = await api.getTunnel(tunnelId);
|
|
13
|
+
const currentWhitelist = tunnel.tunnel.ipWhitelist || [];
|
|
14
|
+
|
|
15
|
+
// Check if IP already exists
|
|
16
|
+
if (currentWhitelist.includes(ip)) {
|
|
17
|
+
spinner.fail(chalk.yellow(`IP ${ip} is already in the whitelist`));
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Add new IP
|
|
22
|
+
const updatedWhitelist = [...currentWhitelist, ip];
|
|
23
|
+
await api.updateIPWhitelist(tunnelId, updatedWhitelist);
|
|
24
|
+
|
|
25
|
+
spinner.succeed(chalk.green(`IP ${ip} added to whitelist`));
|
|
26
|
+
console.log(chalk.gray(`\nTotal whitelisted IPs: ${updatedWhitelist.length}\n`));
|
|
27
|
+
} catch (error) {
|
|
28
|
+
spinner.fail(chalk.red('Failed to add IP to whitelist'));
|
|
29
|
+
console.error(chalk.red(`\n✗ ${error.response?.data?.msg || error.message}\n`));
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Remove IP from whitelist
|
|
35
|
+
async function remove(tunnelId, ip) {
|
|
36
|
+
const spinner = ora('Removing IP from whitelist...').start();
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
// Get current tunnel
|
|
40
|
+
const tunnel = await api.getTunnel(tunnelId);
|
|
41
|
+
const currentWhitelist = tunnel.tunnel.ipWhitelist || [];
|
|
42
|
+
|
|
43
|
+
// Check if IP exists
|
|
44
|
+
if (!currentWhitelist.includes(ip)) {
|
|
45
|
+
spinner.fail(chalk.yellow(`IP ${ip} is not in the whitelist`));
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Remove IP
|
|
50
|
+
const updatedWhitelist = currentWhitelist.filter(item => item !== ip);
|
|
51
|
+
await api.updateIPWhitelist(tunnelId, updatedWhitelist);
|
|
52
|
+
|
|
53
|
+
spinner.succeed(chalk.green(`IP ${ip} removed from whitelist`));
|
|
54
|
+
console.log(chalk.gray(`\nTotal whitelisted IPs: ${updatedWhitelist.length}\n`));
|
|
55
|
+
} catch (error) {
|
|
56
|
+
spinner.fail(chalk.red('Failed to remove IP from whitelist'));
|
|
57
|
+
console.error(chalk.red(`\n✗ ${error.response?.data?.msg || error.message}\n`));
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// List whitelisted IPs
|
|
63
|
+
async function list(tunnelId) {
|
|
64
|
+
const spinner = ora('Fetching IP whitelist...').start();
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
const tunnel = await api.getTunnel(tunnelId);
|
|
68
|
+
const whitelist = tunnel.tunnel.ipWhitelist || [];
|
|
69
|
+
|
|
70
|
+
spinner.stop();
|
|
71
|
+
|
|
72
|
+
if (whitelist.length === 0) {
|
|
73
|
+
console.log(chalk.yellow('\n⚠ No IP whitelist configured. All IPs are allowed.\n'));
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
console.log(chalk.blue.bold(`\n🔒 IP Whitelist (${whitelist.length} IPs)\n`));
|
|
78
|
+
|
|
79
|
+
const table = new Table({
|
|
80
|
+
head: [chalk.white('IP Address / CIDR')],
|
|
81
|
+
style: {
|
|
82
|
+
head: ['cyan'],
|
|
83
|
+
border: ['gray']
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
whitelist.forEach(ip => {
|
|
88
|
+
table.push([chalk.green(ip)]);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
console.log(table.toString());
|
|
92
|
+
console.log();
|
|
93
|
+
} catch (error) {
|
|
94
|
+
spinner.fail(chalk.red('Failed to fetch IP whitelist'));
|
|
95
|
+
console.error(chalk.red(`\n✗ ${error.response?.data?.msg || error.message}\n`));
|
|
96
|
+
process.exit(1);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Clear all whitelisted IPs
|
|
101
|
+
async function clear(tunnelId) {
|
|
102
|
+
const spinner = ora('Clearing IP whitelist...').start();
|
|
103
|
+
|
|
104
|
+
try {
|
|
105
|
+
await api.updateIPWhitelist(tunnelId, []);
|
|
106
|
+
|
|
107
|
+
spinner.succeed(chalk.green('IP whitelist cleared'));
|
|
108
|
+
console.log(chalk.gray('\nAll IPs are now allowed.\n'));
|
|
109
|
+
} catch (error) {
|
|
110
|
+
spinner.fail(chalk.red('Failed to clear IP whitelist'));
|
|
111
|
+
console.error(chalk.red(`\n✗ ${error.response?.data?.msg || error.message}\n`));
|
|
112
|
+
process.exit(1);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
module.exports = {
|
|
117
|
+
add,
|
|
118
|
+
remove,
|
|
119
|
+
list,
|
|
120
|
+
clear
|
|
121
|
+
};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
const ora = require('ora');
|
|
3
|
+
const api = require('../utils/api');
|
|
4
|
+
|
|
5
|
+
// Configure rate limits
|
|
6
|
+
async function configure(tunnelId, options) {
|
|
7
|
+
const spinner = ora('Updating rate limits...').start();
|
|
8
|
+
|
|
9
|
+
try {
|
|
10
|
+
// Get current tunnel
|
|
11
|
+
const tunnel = await api.getTunnel(tunnelId);
|
|
12
|
+
const currentRateLimit = tunnel.tunnel.rateLimit || {};
|
|
13
|
+
|
|
14
|
+
// Build update object
|
|
15
|
+
const updates = {
|
|
16
|
+
rateLimit: {
|
|
17
|
+
enabled: options.disable ? false : (options.enable ? true : currentRateLimit.enabled),
|
|
18
|
+
requestsPerMinute: options.rpm || currentRateLimit.requestsPerMinute,
|
|
19
|
+
requestsPerHour: options.rph || currentRateLimit.requestsPerHour,
|
|
20
|
+
requestsPerDay: options.rpd || currentRateLimit.requestsPerDay
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
await api.updateTunnel(tunnelId, updates);
|
|
25
|
+
|
|
26
|
+
spinner.succeed(chalk.green('Rate limits updated successfully'));
|
|
27
|
+
|
|
28
|
+
console.log(chalk.blue.bold('\n⚡ Rate Limit Configuration\n'));
|
|
29
|
+
console.log(chalk.gray('┌─────────────────────────────────────┐'));
|
|
30
|
+
console.log(chalk.gray('│') + chalk.white(' Status: ') + (updates.rateLimit.enabled ? chalk.green('Enabled') : chalk.red('Disabled')).padEnd(28) + chalk.gray('│'));
|
|
31
|
+
console.log(chalk.gray('│') + chalk.white(' Per Minute: ') + chalk.cyan(String(updates.rateLimit.requestsPerMinute)).padEnd(20) + chalk.gray('│'));
|
|
32
|
+
console.log(chalk.gray('│') + chalk.white(' Per Hour: ') + chalk.cyan(String(updates.rateLimit.requestsPerHour)).padEnd(20) + chalk.gray('│'));
|
|
33
|
+
console.log(chalk.gray('│') + chalk.white(' Per Day: ') + chalk.cyan(String(updates.rateLimit.requestsPerDay)).padEnd(20) + chalk.gray('│'));
|
|
34
|
+
console.log(chalk.gray('└─────────────────────────────────────┘\n'));
|
|
35
|
+
} catch (error) {
|
|
36
|
+
spinner.fail(chalk.red('Failed to update rate limits'));
|
|
37
|
+
console.error(chalk.red(`\n✗ ${error.response?.data?.msg || error.message}\n`));
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Show current rate limits
|
|
43
|
+
async function show(tunnelId) {
|
|
44
|
+
const spinner = ora('Fetching rate limits...').start();
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
const tunnel = await api.getTunnel(tunnelId);
|
|
48
|
+
const rateLimit = tunnel.tunnel.rateLimit || {};
|
|
49
|
+
|
|
50
|
+
spinner.stop();
|
|
51
|
+
|
|
52
|
+
console.log(chalk.blue.bold('\n⚡ Rate Limit Configuration\n'));
|
|
53
|
+
console.log(chalk.gray('┌─────────────────────────────────────┐'));
|
|
54
|
+
console.log(chalk.gray('│') + chalk.white(' Status: ') + (rateLimit.enabled ? chalk.green('Enabled') : chalk.red('Disabled')).padEnd(28) + chalk.gray('│'));
|
|
55
|
+
console.log(chalk.gray('│') + chalk.white(' Per Minute: ') + chalk.cyan(String(rateLimit.requestsPerMinute || 60)).padEnd(20) + chalk.gray('│'));
|
|
56
|
+
console.log(chalk.gray('│') + chalk.white(' Per Hour: ') + chalk.cyan(String(rateLimit.requestsPerHour || 1000)).padEnd(20) + chalk.gray('│'));
|
|
57
|
+
console.log(chalk.gray('│') + chalk.white(' Per Day: ') + chalk.cyan(String(rateLimit.requestsPerDay || 10000)).padEnd(20) + chalk.gray('│'));
|
|
58
|
+
console.log(chalk.gray('└─────────────────────────────────────┘\n'));
|
|
59
|
+
} catch (error) {
|
|
60
|
+
spinner.fail(chalk.red('Failed to fetch rate limits'));
|
|
61
|
+
console.error(chalk.red(`\n✗ ${error.response?.data?.msg || error.message}\n`));
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
module.exports = {
|
|
67
|
+
configure,
|
|
68
|
+
show
|
|
69
|
+
};
|
package/commands/tunnel.js
CHANGED
|
@@ -2,6 +2,7 @@ const chalk = require('chalk');
|
|
|
2
2
|
const ora = require('ora');
|
|
3
3
|
const WebSocket = require('ws');
|
|
4
4
|
const Table = require('cli-table3');
|
|
5
|
+
const axios = require('axios');
|
|
5
6
|
const api = require('../utils/api');
|
|
6
7
|
const config = require('../utils/config');
|
|
7
8
|
|
|
@@ -38,18 +39,24 @@ async function start(port, options) {
|
|
|
38
39
|
|
|
39
40
|
spinner.succeed(chalk.green('Tunnel created successfully!'));
|
|
40
41
|
|
|
42
|
+
// Safe string padding function
|
|
43
|
+
const safePadEnd = (str, length) => {
|
|
44
|
+
const s = String(str || '');
|
|
45
|
+
return s.length >= length ? s : s + ' '.repeat(length - s.length);
|
|
46
|
+
};
|
|
47
|
+
|
|
41
48
|
console.log(chalk.gray('\n┌─────────────────────────────────────────────┐'));
|
|
42
49
|
console.log(chalk.gray('│') + chalk.white.bold(' Tunnel Information ') + chalk.gray('│'));
|
|
43
50
|
console.log(chalk.gray('├─────────────────────────────────────────────┤'));
|
|
44
|
-
console.log(chalk.gray('│') + chalk.gray(' Name: ') + chalk.white(tunnel.
|
|
45
|
-
console.log(chalk.gray('│') + chalk.gray(' Public URL: ') + chalk.cyan(tunnel.publicUrl
|
|
46
|
-
console.log(chalk.gray('│') + chalk.gray(' Local Port: ') + chalk.white(
|
|
47
|
-
console.log(chalk.gray('│') + chalk.gray(' Tunnel ID: ') + chalk.yellow(tunnel._id
|
|
51
|
+
console.log(chalk.gray('│') + chalk.gray(' Name: ') + chalk.white(safePadEnd(tunnel.subdomain || 'Unknown', 28)) + chalk.gray('│'));
|
|
52
|
+
console.log(chalk.gray('│') + chalk.gray(' Public URL: ') + chalk.cyan(safePadEnd(tunnel.publicUrl || 'Unknown', 28)) + chalk.gray('│'));
|
|
53
|
+
console.log(chalk.gray('│') + chalk.gray(' Local Port: ') + chalk.white(safePadEnd(tunnel.localPort || 'Unknown', 28)) + chalk.gray('│'));
|
|
54
|
+
console.log(chalk.gray('│') + chalk.gray(' Tunnel ID: ') + chalk.yellow(safePadEnd(tunnel.id || tunnel._id || 'Unknown', 28)) + chalk.gray('│'));
|
|
48
55
|
console.log(chalk.gray('└─────────────────────────────────────────────┘\n'));
|
|
49
56
|
|
|
50
57
|
// Connect tunnel client
|
|
51
58
|
console.log(chalk.blue('Connecting tunnel client...\n'));
|
|
52
|
-
await connectTunnelClient(tunnel._id, tunnel.subdomain, tunnel.localPort);
|
|
59
|
+
await connectTunnelClient(tunnel.id || tunnel._id, tunnel.subdomain, tunnel.localPort);
|
|
53
60
|
|
|
54
61
|
} catch (error) {
|
|
55
62
|
spinner.fail(chalk.red('Failed to create tunnel'));
|
|
@@ -60,12 +67,14 @@ async function start(port, options) {
|
|
|
60
67
|
|
|
61
68
|
// Connect tunnel client
|
|
62
69
|
async function connectTunnelClient(tunnelId, subdomain, localPort) {
|
|
63
|
-
const tunnelServerUrl = config.get('tunnelServerUrl') || 'ws://localhost:
|
|
70
|
+
const tunnelServerUrl = config.get('tunnelServerUrl') || 'ws://localhost:8080';
|
|
64
71
|
const token = api.getToken();
|
|
65
72
|
const userId = config.get('userId');
|
|
66
73
|
|
|
67
74
|
const ws = new WebSocket(tunnelServerUrl);
|
|
68
75
|
|
|
76
|
+
let heartbeatInterval;
|
|
77
|
+
|
|
69
78
|
ws.on('open', () => {
|
|
70
79
|
console.log(chalk.green('✓ Connected to tunnel server'));
|
|
71
80
|
|
|
@@ -77,6 +86,13 @@ async function connectTunnelClient(tunnelId, subdomain, localPort) {
|
|
|
77
86
|
authToken: token,
|
|
78
87
|
userId
|
|
79
88
|
}));
|
|
89
|
+
|
|
90
|
+
// Send heartbeat every 30 seconds to keep connection alive (industry standard)
|
|
91
|
+
heartbeatInterval = setInterval(() => {
|
|
92
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
93
|
+
ws.send(JSON.stringify({ type: 'heartbeat', tunnelId, subdomain }));
|
|
94
|
+
}
|
|
95
|
+
}, 30000);
|
|
80
96
|
});
|
|
81
97
|
|
|
82
98
|
ws.on('message', async (data) => {
|
|
@@ -90,23 +106,69 @@ async function connectTunnelClient(tunnelId, subdomain, localPort) {
|
|
|
90
106
|
} else if (message.type === 'request') {
|
|
91
107
|
const timestamp = new Date().toLocaleTimeString();
|
|
92
108
|
console.log(chalk.gray(`[${timestamp}]`), chalk.blue(message.method), chalk.white(message.path));
|
|
109
|
+
|
|
110
|
+
// Forward request to local server
|
|
111
|
+
try {
|
|
112
|
+
const localUrl = `http://localhost:${localPort}${message.path}`;
|
|
113
|
+
const response = await axios({
|
|
114
|
+
method: message.method.toLowerCase(),
|
|
115
|
+
url: localUrl,
|
|
116
|
+
headers: message.headers || {},
|
|
117
|
+
data: message.body,
|
|
118
|
+
validateStatus: () => true // Accept any status code
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// Clean up headers to avoid conflicts
|
|
122
|
+
const cleanHeaders = { ...response.headers };
|
|
123
|
+
delete cleanHeaders['transfer-encoding'];
|
|
124
|
+
delete cleanHeaders['content-length'];
|
|
125
|
+
|
|
126
|
+
// Send response back to tunnel server
|
|
127
|
+
ws.send(JSON.stringify({
|
|
128
|
+
type: 'response',
|
|
129
|
+
requestId: message.requestId,
|
|
130
|
+
statusCode: response.status,
|
|
131
|
+
headers: cleanHeaders,
|
|
132
|
+
body: response.data
|
|
133
|
+
}));
|
|
134
|
+
} catch (error) {
|
|
135
|
+
console.error(chalk.red(`Error forwarding request: ${error.message}`));
|
|
136
|
+
ws.send(JSON.stringify({
|
|
137
|
+
type: 'response',
|
|
138
|
+
requestId: message.requestId,
|
|
139
|
+
statusCode: 500,
|
|
140
|
+
headers: { 'Content-Type': 'application/json' },
|
|
141
|
+
body: { error: 'Tunnel client error', message: error.message }
|
|
142
|
+
}));
|
|
143
|
+
}
|
|
144
|
+
} else if (message.type === 'timeout') {
|
|
145
|
+
// Handle server-side timeout notifications
|
|
146
|
+
if (message.reason === 'idle') {
|
|
147
|
+
console.log(chalk.yellow('\n⏰ Tunnel closed due to 2 hours of inactivity.'));
|
|
148
|
+
} else if (message.reason === 'max_session') {
|
|
149
|
+
console.log(chalk.yellow('\n⏰ Tunnel session expired after 24 hours.'));
|
|
150
|
+
}
|
|
151
|
+
console.log(chalk.gray(message.message || 'Please reconnect to continue.'));
|
|
93
152
|
} else if (message.type === 'error') {
|
|
94
153
|
console.error(chalk.red(`Error: ${message.error}`));
|
|
95
154
|
}
|
|
96
155
|
});
|
|
97
156
|
|
|
98
157
|
ws.on('close', () => {
|
|
158
|
+
if (heartbeatInterval) clearInterval(heartbeatInterval);
|
|
99
159
|
console.log(chalk.yellow('\n⚠ Tunnel disconnected'));
|
|
100
160
|
process.exit(0);
|
|
101
161
|
});
|
|
102
162
|
|
|
103
163
|
ws.on('error', (error) => {
|
|
164
|
+
if (heartbeatInterval) clearInterval(heartbeatInterval);
|
|
104
165
|
console.error(chalk.red(`\n✗ Connection error: ${error.message}`));
|
|
105
166
|
process.exit(1);
|
|
106
167
|
});
|
|
107
168
|
|
|
108
169
|
// Handle Ctrl+C
|
|
109
170
|
process.on('SIGINT', () => {
|
|
171
|
+
if (heartbeatInterval) clearInterval(heartbeatInterval);
|
|
110
172
|
console.log(chalk.yellow('\n\n⚠ Stopping tunnel...'));
|
|
111
173
|
ws.close();
|
|
112
174
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "api-response-manager",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.5.0",
|
|
4
4
|
"description": "Command-line interface for API Response Manager",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -8,7 +8,17 @@
|
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
10
|
"test": "jest",
|
|
11
|
-
"link": "npm link"
|
|
11
|
+
"link": "npm link",
|
|
12
|
+
"prepublishOnly": "echo 'Publishing api-response-manager CLI...'"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"bin/",
|
|
16
|
+
"commands/",
|
|
17
|
+
"utils/",
|
|
18
|
+
"README.md"
|
|
19
|
+
],
|
|
20
|
+
"publishConfig": {
|
|
21
|
+
"access": "public"
|
|
12
22
|
},
|
|
13
23
|
"keywords": [
|
|
14
24
|
"api",
|
|
@@ -19,7 +29,7 @@
|
|
|
19
29
|
"developer-tools"
|
|
20
30
|
],
|
|
21
31
|
"author": "Vijay Singh Purohit",
|
|
22
|
-
"license": "
|
|
32
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
23
33
|
"dependencies": {
|
|
24
34
|
"axios": "^1.6.2",
|
|
25
35
|
"boxen": "^5.1.2",
|
|
@@ -47,5 +57,6 @@
|
|
|
47
57
|
"bugs": {
|
|
48
58
|
"url": "https://github.com/vijaypurohit322/api-response-manager/issues"
|
|
49
59
|
},
|
|
50
|
-
"homepage": "https://github.com/vijaypurohit322/api-response-manager#readme"
|
|
60
|
+
"homepage": "https://github.com/vijaypurohit322/api-response-manager#readme",
|
|
61
|
+
"preferGlobal": true
|
|
51
62
|
}
|
package/utils/api.js
CHANGED
|
@@ -4,7 +4,8 @@ const chalk = require('chalk');
|
|
|
4
4
|
|
|
5
5
|
class APIClient {
|
|
6
6
|
constructor() {
|
|
7
|
-
|
|
7
|
+
// Check both API_URL and apiUrl for backwards compatibility
|
|
8
|
+
this.baseURL = config.get('API_URL') || config.get('apiUrl') || 'https://api.tunnelapi.in/api';
|
|
8
9
|
this.client = axios.create({
|
|
9
10
|
baseURL: this.baseURL,
|
|
10
11
|
timeout: 30000,
|
|
@@ -185,6 +186,23 @@ class APIClient {
|
|
|
185
186
|
const response = await this.client.post(`/tunnels/${id}/ingress`, data);
|
|
186
187
|
return response.data;
|
|
187
188
|
}
|
|
189
|
+
|
|
190
|
+
// IP Management
|
|
191
|
+
async updateIPWhitelist(id, ipWhitelist) {
|
|
192
|
+
const response = await this.client.put(`/tunnels/${id}/ip-whitelist`, { ipWhitelist });
|
|
193
|
+
return response.data;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
async updateIPBlacklist(id, ipBlacklist) {
|
|
197
|
+
const response = await this.client.put(`/tunnels/${id}/ip-blacklist`, { ipBlacklist });
|
|
198
|
+
return response.data;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Update Tunnel
|
|
202
|
+
async updateTunnel(id, updates) {
|
|
203
|
+
const response = await this.client.put(`/tunnels/${id}`, updates);
|
|
204
|
+
return response.data;
|
|
205
|
+
}
|
|
188
206
|
}
|
|
189
207
|
|
|
190
208
|
module.exports = new APIClient();
|
package/utils/config.js
CHANGED
|
@@ -4,7 +4,8 @@ const path = require('path');
|
|
|
4
4
|
const config = new Conf({
|
|
5
5
|
projectName: 'arm-cli',
|
|
6
6
|
defaults: {
|
|
7
|
-
apiUrl: '
|
|
7
|
+
apiUrl: 'https://api.tunnelapi.in/api',
|
|
8
|
+
API_URL: 'https://api.tunnelapi.in/api', // Support both camelCase and UPPER_CASE
|
|
8
9
|
token: null,
|
|
9
10
|
userId: null,
|
|
10
11
|
email: null,
|
|
@@ -16,6 +17,10 @@ const config = new Conf({
|
|
|
16
17
|
type: 'string',
|
|
17
18
|
format: 'uri'
|
|
18
19
|
},
|
|
20
|
+
API_URL: {
|
|
21
|
+
type: 'string',
|
|
22
|
+
format: 'uri'
|
|
23
|
+
},
|
|
19
24
|
token: {
|
|
20
25
|
type: ['string', 'null']
|
|
21
26
|
},
|
package/CLI_UPDATES.md
DELETED
|
@@ -1,196 +0,0 @@
|
|
|
1
|
-
# CLI Updates for Advanced Tunnel Features
|
|
2
|
-
|
|
3
|
-
## ✅ Changes Made
|
|
4
|
-
|
|
5
|
-
### 1. Enhanced `tunnel` Command
|
|
6
|
-
Added new options to the main tunnel command:
|
|
7
|
-
```bash
|
|
8
|
-
arm tunnel [port] [options]
|
|
9
|
-
|
|
10
|
-
Options:
|
|
11
|
-
-s, --subdomain <subdomain> Custom subdomain
|
|
12
|
-
-n, --name <name> Tunnel name
|
|
13
|
-
-a, --auth Enable basic authentication
|
|
14
|
-
-r, --rate-limit <limit> Rate limit (requests per minute)
|
|
15
|
-
-p, --protocol <protocol> Protocol (http, https, tcp, ws, wss)
|
|
16
|
-
--ssl Enable SSL/HTTPS
|
|
17
|
-
-d, --domain <domain> Custom domain
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
### 2. New Commands Added
|
|
21
|
-
|
|
22
|
-
#### Custom Domain
|
|
23
|
-
```bash
|
|
24
|
-
arm tunnel:domain <tunnelId> <domain>
|
|
25
|
-
```
|
|
26
|
-
Example:
|
|
27
|
-
```bash
|
|
28
|
-
arm tunnel:domain abc123 api.yourdomain.com
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
#### SSL Certificate Upload
|
|
32
|
-
```bash
|
|
33
|
-
arm tunnel:ssl <tunnelId> --cert <path> --key <path> [--ca <path>]
|
|
34
|
-
```
|
|
35
|
-
Example:
|
|
36
|
-
```bash
|
|
37
|
-
arm tunnel:ssl abc123 --cert cert.pem --key key.pem --ca ca.pem
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
#### OAuth Configuration
|
|
41
|
-
```bash
|
|
42
|
-
arm tunnel:auth:oauth <tunnelId> [options]
|
|
43
|
-
|
|
44
|
-
Options:
|
|
45
|
-
--provider <provider> OAuth provider (google, github, microsoft, custom)
|
|
46
|
-
--client-id <id> OAuth client ID
|
|
47
|
-
--client-secret <secret> OAuth client secret
|
|
48
|
-
--callback-url <url> OAuth callback URL
|
|
49
|
-
--scope <scope> OAuth scope (comma-separated)
|
|
50
|
-
```
|
|
51
|
-
Example:
|
|
52
|
-
```bash
|
|
53
|
-
arm tunnel:auth:oauth abc123 \
|
|
54
|
-
--provider google \
|
|
55
|
-
--client-id YOUR_CLIENT_ID \
|
|
56
|
-
--client-secret YOUR_SECRET \
|
|
57
|
-
--callback-url https://yourtunnel.arm.dev/auth/callback \
|
|
58
|
-
--scope openid,email,profile
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
#### OIDC Configuration
|
|
62
|
-
```bash
|
|
63
|
-
arm tunnel:auth:oidc <tunnelId> [options]
|
|
64
|
-
|
|
65
|
-
Options:
|
|
66
|
-
--issuer <url> OIDC issuer URL
|
|
67
|
-
--client-id <id> OIDC client ID
|
|
68
|
-
--client-secret <secret> OIDC client secret
|
|
69
|
-
--callback-url <url> OIDC callback URL
|
|
70
|
-
```
|
|
71
|
-
Example:
|
|
72
|
-
```bash
|
|
73
|
-
arm tunnel:auth:oidc abc123 \
|
|
74
|
-
--issuer https://accounts.google.com \
|
|
75
|
-
--client-id YOUR_CLIENT_ID \
|
|
76
|
-
--client-secret YOUR_SECRET \
|
|
77
|
-
--callback-url https://yourtunnel.arm.dev/auth/callback
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
#### SAML Configuration
|
|
81
|
-
```bash
|
|
82
|
-
arm tunnel:auth:saml <tunnelId> [options]
|
|
83
|
-
|
|
84
|
-
Options:
|
|
85
|
-
--entry-point <url> SAML entry point URL
|
|
86
|
-
--issuer <issuer> SAML issuer
|
|
87
|
-
--cert <path> Path to IdP certificate file
|
|
88
|
-
--callback-url <url> SAML callback URL
|
|
89
|
-
```
|
|
90
|
-
Example:
|
|
91
|
-
```bash
|
|
92
|
-
arm tunnel:auth:saml abc123 \
|
|
93
|
-
--entry-point https://idp.example.com/saml/sso \
|
|
94
|
-
--issuer https://yourtunnel.arm.dev \
|
|
95
|
-
--cert idp-cert.pem \
|
|
96
|
-
--callback-url https://yourtunnel.arm.dev/auth/saml/callback
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
#### Ingress Configuration
|
|
100
|
-
```bash
|
|
101
|
-
arm tunnel:ingress <tunnelId> <rules> [--tls]
|
|
102
|
-
|
|
103
|
-
Rules format: "/path=host:port,/path2=host:port"
|
|
104
|
-
```
|
|
105
|
-
Example:
|
|
106
|
-
```bash
|
|
107
|
-
arm tunnel:ingress abc123 "/api=localhost:3000,/admin=localhost:4000" --tls
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
## 📝 Usage Examples
|
|
111
|
-
|
|
112
|
-
### Example 1: Create HTTPS Tunnel
|
|
113
|
-
```bash
|
|
114
|
-
arm tunnel 3000 --protocol https --ssl --subdomain myapi
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
### Example 2: Create TCP Tunnel
|
|
118
|
-
```bash
|
|
119
|
-
arm tunnel 5432 --protocol tcp --subdomain postgres
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
### Example 3: Create Tunnel with Custom Domain
|
|
123
|
-
```bash
|
|
124
|
-
# Create tunnel
|
|
125
|
-
arm tunnel 3000 --protocol https --ssl
|
|
126
|
-
|
|
127
|
-
# Set custom domain
|
|
128
|
-
arm tunnel:domain <tunnel-id> api.mycompany.com
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
### Example 4: Create Tunnel with OAuth
|
|
132
|
-
```bash
|
|
133
|
-
# Create tunnel
|
|
134
|
-
arm tunnel 3000 --protocol https --ssl --subdomain myapi
|
|
135
|
-
|
|
136
|
-
# Configure OAuth
|
|
137
|
-
arm tunnel:auth:oauth <tunnel-id> \
|
|
138
|
-
--provider google \
|
|
139
|
-
--client-id YOUR_CLIENT_ID \
|
|
140
|
-
--client-secret YOUR_SECRET \
|
|
141
|
-
--callback-url https://myapi.tunnel.arm.dev/auth/callback
|
|
142
|
-
```
|
|
143
|
-
|
|
144
|
-
### Example 5: Create Tunnel with Ingress
|
|
145
|
-
```bash
|
|
146
|
-
# Create tunnel
|
|
147
|
-
arm tunnel 3000 --protocol https --ssl
|
|
148
|
-
|
|
149
|
-
# Configure ingress rules
|
|
150
|
-
arm tunnel:ingress <tunnel-id> \
|
|
151
|
-
"/v1=localhost:3000,/v2=localhost:4000" \
|
|
152
|
-
--tls
|
|
153
|
-
```
|
|
154
|
-
|
|
155
|
-
## 🔧 Updated Files
|
|
156
|
-
|
|
157
|
-
1. **cli/bin/arm.js** - Added new commands
|
|
158
|
-
2. **cli/commands/tunnel.js** - Added new functions
|
|
159
|
-
3. **cli/utils/api.js** - Added new API methods
|
|
160
|
-
|
|
161
|
-
## 📦 No New Dependencies Required
|
|
162
|
-
|
|
163
|
-
All new features use existing dependencies.
|
|
164
|
-
|
|
165
|
-
## 🚀 Testing
|
|
166
|
-
|
|
167
|
-
Test the new commands:
|
|
168
|
-
```bash
|
|
169
|
-
# Test HTTPS tunnel
|
|
170
|
-
arm tunnel 3000 --protocol https --ssl
|
|
171
|
-
|
|
172
|
-
# Test custom domain
|
|
173
|
-
arm tunnel:domain <tunnel-id> test.example.com
|
|
174
|
-
|
|
175
|
-
# Test SSL upload
|
|
176
|
-
arm tunnel:ssl <tunnel-id> --cert cert.pem --key key.pem
|
|
177
|
-
|
|
178
|
-
# Test OAuth
|
|
179
|
-
arm tunnel:auth:oauth <tunnel-id> --provider google --client-id test --client-secret test --callback-url http://localhost/callback
|
|
180
|
-
|
|
181
|
-
# Test ingress
|
|
182
|
-
arm tunnel:ingress <tunnel-id> "/api=localhost:3000" --tls
|
|
183
|
-
```
|
|
184
|
-
|
|
185
|
-
## 📖 Help Output
|
|
186
|
-
|
|
187
|
-
```bash
|
|
188
|
-
arm --help
|
|
189
|
-
arm tunnel --help
|
|
190
|
-
arm tunnel:domain --help
|
|
191
|
-
arm tunnel:ssl --help
|
|
192
|
-
arm tunnel:auth:oauth --help
|
|
193
|
-
arm tunnel:auth:oidc --help
|
|
194
|
-
arm tunnel:auth:saml --help
|
|
195
|
-
arm tunnel:ingress --help
|
|
196
|
-
```
|