api-response-manager 2.3.1 → 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/README.md +42 -10
- package/bin/arm.js +0 -0
- package/commands/tunnel.js +68 -6
- package/package.json +2 -2
- package/utils/api.js +2 -1
- package/utils/config.js +6 -1
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
|
File without changes
|
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": {
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"developer-tools"
|
|
30
30
|
],
|
|
31
31
|
"author": "Vijay Singh Purohit",
|
|
32
|
-
"license": "
|
|
32
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
33
33
|
"dependencies": {
|
|
34
34
|
"axios": "^1.6.2",
|
|
35
35
|
"boxen": "^5.1.2",
|
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,
|
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
|
},
|