api-response-manager 2.5.1 → 2.5.3
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 +1 -1
- package/bin/arm.js +7 -0
- package/commands/account.js +23 -11
- package/commands/login.js +25 -0
- package/commands/tunnel.js +104 -19
- package/package.json +1 -1
- package/utils/api.js +5 -0
package/README.md
CHANGED
package/bin/arm.js
CHANGED
|
@@ -38,6 +38,7 @@ program
|
|
|
38
38
|
.option('-e, --email <email>', 'Email address')
|
|
39
39
|
.option('-p, --password <password>', 'Password')
|
|
40
40
|
.option('--provider <provider>', 'OAuth provider (google, github, microsoft)')
|
|
41
|
+
.option('-t, --token <token>', 'API token for CI/CD automation')
|
|
41
42
|
.action(loginCommand);
|
|
42
43
|
|
|
43
44
|
// Logout command
|
|
@@ -58,6 +59,7 @@ program
|
|
|
58
59
|
.option('-p, --protocol <protocol>', 'Protocol (http, https, tcp, ws, wss)', 'http')
|
|
59
60
|
.option('--ssl', 'Enable SSL/HTTPS')
|
|
60
61
|
.option('-d, --domain <domain>', 'Custom domain')
|
|
62
|
+
.option('--json', 'Output in JSON format (for CI/CD automation)')
|
|
61
63
|
.action(tunnelCommand.start);
|
|
62
64
|
|
|
63
65
|
program
|
|
@@ -71,6 +73,11 @@ program
|
|
|
71
73
|
.argument('<tunnelId>', 'Tunnel ID to stop')
|
|
72
74
|
.action(tunnelCommand.stop);
|
|
73
75
|
|
|
76
|
+
program
|
|
77
|
+
.command('tunnel:stop-all')
|
|
78
|
+
.description('Stop all active tunnels')
|
|
79
|
+
.action(tunnelCommand.stopAll);
|
|
80
|
+
|
|
74
81
|
program
|
|
75
82
|
.command('tunnel:logs')
|
|
76
83
|
.description('View tunnel request logs')
|
package/commands/account.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const chalk = require('chalk');
|
|
2
2
|
const ora = require('ora');
|
|
3
3
|
const Table = require('cli-table3');
|
|
4
|
-
const
|
|
4
|
+
const api = require('../utils/api');
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Get current user info
|
|
@@ -10,10 +10,14 @@ async function info() {
|
|
|
10
10
|
const spinner = ora('Fetching account info...').start();
|
|
11
11
|
|
|
12
12
|
try {
|
|
13
|
-
const
|
|
14
|
-
|
|
13
|
+
const token = api.getToken();
|
|
14
|
+
if (!token) {
|
|
15
|
+
spinner.fail('Not authenticated');
|
|
16
|
+
console.error(chalk.red('Please run: arm login'));
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
15
19
|
|
|
16
|
-
const response = await api.get('/auth/me');
|
|
20
|
+
const response = await api.client.get('/auth/me');
|
|
17
21
|
const user = response.data.user;
|
|
18
22
|
|
|
19
23
|
spinner.stop();
|
|
@@ -52,10 +56,14 @@ async function features() {
|
|
|
52
56
|
const spinner = ora('Fetching feature access...').start();
|
|
53
57
|
|
|
54
58
|
try {
|
|
55
|
-
const
|
|
56
|
-
|
|
59
|
+
const token = api.getToken();
|
|
60
|
+
if (!token) {
|
|
61
|
+
spinner.fail('Not authenticated');
|
|
62
|
+
console.error(chalk.red('Please run: arm login'));
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
57
65
|
|
|
58
|
-
const response = await api.get('/auth/features');
|
|
66
|
+
const response = await api.client.get('/auth/features');
|
|
59
67
|
const { tier, features } = response.data;
|
|
60
68
|
|
|
61
69
|
spinner.stop();
|
|
@@ -102,12 +110,16 @@ async function usage() {
|
|
|
102
110
|
const spinner = ora('Fetching usage stats...').start();
|
|
103
111
|
|
|
104
112
|
try {
|
|
105
|
-
const
|
|
106
|
-
|
|
113
|
+
const token = api.getToken();
|
|
114
|
+
if (!token) {
|
|
115
|
+
spinner.fail('Not authenticated');
|
|
116
|
+
console.error(chalk.red('Please run: arm login'));
|
|
117
|
+
process.exit(1);
|
|
118
|
+
}
|
|
107
119
|
|
|
108
120
|
const [userRes, featuresRes] = await Promise.all([
|
|
109
|
-
api.get('/auth/me'),
|
|
110
|
-
api.get('/auth/features')
|
|
121
|
+
api.client.get('/auth/me'),
|
|
122
|
+
api.client.get('/auth/features')
|
|
111
123
|
]);
|
|
112
124
|
|
|
113
125
|
const user = userRes.data.user;
|
package/commands/login.js
CHANGED
|
@@ -6,6 +6,31 @@ const api = require('../utils/api');
|
|
|
6
6
|
const config = require('../utils/config');
|
|
7
7
|
|
|
8
8
|
async function login(options) {
|
|
9
|
+
// Token-based login for CI/CD automation
|
|
10
|
+
if (options.token) {
|
|
11
|
+
const spinner = ora('Authenticating with token...').start();
|
|
12
|
+
try {
|
|
13
|
+
// Validate token by making a test API call
|
|
14
|
+
config.set('token', options.token);
|
|
15
|
+
const response = await api.getUser();
|
|
16
|
+
|
|
17
|
+
if (response && response.user) {
|
|
18
|
+
config.set('userId', response.user._id || response.user.id);
|
|
19
|
+
config.set('email', response.user.email);
|
|
20
|
+
spinner.succeed(chalk.green('Authenticated successfully!'));
|
|
21
|
+
console.log(chalk.gray(`\nLogged in as: ${response.user.email}\n`));
|
|
22
|
+
} else {
|
|
23
|
+
throw new Error('Invalid token');
|
|
24
|
+
}
|
|
25
|
+
} catch (error) {
|
|
26
|
+
config.delete('token');
|
|
27
|
+
spinner.fail(chalk.red('Authentication failed'));
|
|
28
|
+
console.error(chalk.red(`\n✗ ${error.response?.data?.msg || error.message}\n`));
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
9
34
|
console.log(chalk.blue.bold('\n🔐 API Response Manager - Login\n'));
|
|
10
35
|
|
|
11
36
|
// Check if social login is requested
|
package/commands/tunnel.js
CHANGED
|
@@ -8,14 +8,20 @@ const config = require('../utils/config');
|
|
|
8
8
|
|
|
9
9
|
// Start tunnel
|
|
10
10
|
async function start(port, options) {
|
|
11
|
-
|
|
11
|
+
const jsonOutput = options.json || false;
|
|
12
|
+
|
|
13
|
+
if (!jsonOutput) {
|
|
14
|
+
console.log(chalk.blue.bold('\n🚇 Starting Tunnel...\n'));
|
|
15
|
+
}
|
|
12
16
|
|
|
13
17
|
if (!port) {
|
|
14
18
|
port = config.get('defaultTunnelPort');
|
|
15
|
-
|
|
19
|
+
if (!jsonOutput) {
|
|
20
|
+
console.log(chalk.gray(`Using default port: ${port}`));
|
|
21
|
+
}
|
|
16
22
|
}
|
|
17
23
|
|
|
18
|
-
const spinner = ora('Creating tunnel...').start();
|
|
24
|
+
const spinner = jsonOutput ? null : ora('Creating tunnel...').start();
|
|
19
25
|
|
|
20
26
|
try {
|
|
21
27
|
const tunnelData = {
|
|
@@ -37,7 +43,22 @@ async function start(port, options) {
|
|
|
37
43
|
const response = await api.createTunnel(tunnelData);
|
|
38
44
|
const tunnel = response.tunnel;
|
|
39
45
|
|
|
40
|
-
spinner.succeed(chalk.green('Tunnel created successfully!'));
|
|
46
|
+
if (spinner) spinner.succeed(chalk.green('Tunnel created successfully!'));
|
|
47
|
+
|
|
48
|
+
// JSON output for automation/CI
|
|
49
|
+
if (jsonOutput) {
|
|
50
|
+
console.log(JSON.stringify({
|
|
51
|
+
success: true,
|
|
52
|
+
id: tunnel.id || tunnel._id,
|
|
53
|
+
subdomain: tunnel.subdomain,
|
|
54
|
+
publicUrl: tunnel.publicUrl,
|
|
55
|
+
localPort: tunnel.localPort,
|
|
56
|
+
protocol: tunnel.protocol || 'https'
|
|
57
|
+
}));
|
|
58
|
+
// For JSON mode, connect silently
|
|
59
|
+
await connectTunnelClient(tunnel.id || tunnel._id, tunnel.subdomain, tunnel.localPort, true);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
41
62
|
|
|
42
63
|
// Safe string padding function
|
|
43
64
|
const safePadEnd = (str, length) => {
|
|
@@ -56,17 +77,30 @@ async function start(port, options) {
|
|
|
56
77
|
|
|
57
78
|
// Connect tunnel client
|
|
58
79
|
console.log(chalk.blue('Connecting tunnel client...\n'));
|
|
59
|
-
await connectTunnelClient(tunnel.id || tunnel._id, tunnel.subdomain, tunnel.localPort);
|
|
80
|
+
await connectTunnelClient(tunnel.id || tunnel._id, tunnel.subdomain, tunnel.localPort, false);
|
|
60
81
|
|
|
61
82
|
} catch (error) {
|
|
62
|
-
spinner.fail(chalk.red('Failed to create tunnel'));
|
|
63
|
-
|
|
83
|
+
if (spinner) spinner.fail(chalk.red('Failed to create tunnel'));
|
|
84
|
+
|
|
85
|
+
const errorMsg = error.response?.data?.msg || error.message;
|
|
86
|
+
|
|
87
|
+
// Handle subdomain already taken error
|
|
88
|
+
if (error.response?.status === 409 && errorMsg.toLowerCase().includes('subdomain')) {
|
|
89
|
+
console.error(chalk.yellow(`\n⚠ ${errorMsg}`));
|
|
90
|
+
console.log(chalk.gray('\nOptions:'));
|
|
91
|
+
console.log(chalk.gray(' 1. Try again with a different subdomain: ') + chalk.cyan(`arm tunnel ${port} -s <new-subdomain>`));
|
|
92
|
+
console.log(chalk.gray(' 2. Let the system generate a unique subdomain: ') + chalk.cyan(`arm tunnel ${port}`));
|
|
93
|
+
console.log();
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
console.error(chalk.red(`\n✗ ${errorMsg}\n`));
|
|
64
98
|
process.exit(1);
|
|
65
99
|
}
|
|
66
100
|
}
|
|
67
101
|
|
|
68
102
|
// Connect tunnel client
|
|
69
|
-
async function connectTunnelClient(tunnelId, subdomain, localPort) {
|
|
103
|
+
async function connectTunnelClient(tunnelId, subdomain, localPort, silent = false) {
|
|
70
104
|
const tunnelServerUrl = config.get('tunnelServerUrl') || 'ws://localhost:8080';
|
|
71
105
|
const token = api.getToken();
|
|
72
106
|
const userId = config.get('userId');
|
|
@@ -76,7 +110,7 @@ async function connectTunnelClient(tunnelId, subdomain, localPort) {
|
|
|
76
110
|
let heartbeatInterval;
|
|
77
111
|
|
|
78
112
|
ws.on('open', () => {
|
|
79
|
-
console.log(chalk.green('✓ Connected to tunnel server'));
|
|
113
|
+
if (!silent) console.log(chalk.green('✓ Connected to tunnel server'));
|
|
80
114
|
|
|
81
115
|
ws.send(JSON.stringify({
|
|
82
116
|
type: 'register',
|
|
@@ -99,29 +133,46 @@ async function connectTunnelClient(tunnelId, subdomain, localPort) {
|
|
|
99
133
|
const message = JSON.parse(data.toString());
|
|
100
134
|
|
|
101
135
|
if (message.type === 'registered') {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
136
|
+
if (!silent) {
|
|
137
|
+
console.log(chalk.green.bold('\n🎉 Tunnel Active!\n'));
|
|
138
|
+
console.log(chalk.white('Your local server is now accessible at:'));
|
|
139
|
+
console.log(chalk.cyan.bold(` ${message.publicUrl}\n`));
|
|
140
|
+
console.log(chalk.gray('Press Ctrl+C to stop the tunnel\n'));
|
|
141
|
+
}
|
|
106
142
|
} else if (message.type === 'request') {
|
|
107
|
-
|
|
108
|
-
|
|
143
|
+
if (!silent) {
|
|
144
|
+
const timestamp = new Date().toLocaleTimeString();
|
|
145
|
+
console.log(chalk.gray(`[${timestamp}]`), chalk.blue(message.method), chalk.white(message.path));
|
|
146
|
+
}
|
|
109
147
|
|
|
110
148
|
// Forward request to local server
|
|
111
149
|
try {
|
|
112
150
|
const localUrl = `http://localhost:${localPort}${message.path}`;
|
|
151
|
+
|
|
152
|
+
// Remove problematic headers that cause issues with local server
|
|
153
|
+
const forwardHeaders = { ...message.headers };
|
|
154
|
+
delete forwardHeaders['host'];
|
|
155
|
+
delete forwardHeaders['connection'];
|
|
156
|
+
delete forwardHeaders['accept-encoding']; // Prevent compression issues
|
|
157
|
+
|
|
113
158
|
const response = await axios({
|
|
114
159
|
method: message.method.toLowerCase(),
|
|
115
160
|
url: localUrl,
|
|
116
|
-
headers:
|
|
161
|
+
headers: forwardHeaders,
|
|
117
162
|
data: message.body,
|
|
118
|
-
validateStatus: () => true // Accept any status code
|
|
163
|
+
validateStatus: () => true, // Accept any status code
|
|
164
|
+
responseType: 'arraybuffer', // Handle binary data properly
|
|
165
|
+
maxRedirects: 0, // Don't follow redirects, let the client handle them
|
|
166
|
+
timeout: 25000 // 25 second timeout
|
|
119
167
|
});
|
|
120
168
|
|
|
121
169
|
// Clean up headers to avoid conflicts
|
|
122
170
|
const cleanHeaders = { ...response.headers };
|
|
123
171
|
delete cleanHeaders['transfer-encoding'];
|
|
124
|
-
delete cleanHeaders['
|
|
172
|
+
delete cleanHeaders['connection'];
|
|
173
|
+
|
|
174
|
+
// Convert binary data to base64 for JSON transport
|
|
175
|
+
const bodyBase64 = Buffer.from(response.data).toString('base64');
|
|
125
176
|
|
|
126
177
|
// Send response back to tunnel server
|
|
127
178
|
ws.send(JSON.stringify({
|
|
@@ -129,7 +180,8 @@ async function connectTunnelClient(tunnelId, subdomain, localPort) {
|
|
|
129
180
|
requestId: message.requestId,
|
|
130
181
|
statusCode: response.status,
|
|
131
182
|
headers: cleanHeaders,
|
|
132
|
-
body:
|
|
183
|
+
body: bodyBase64,
|
|
184
|
+
encoding: 'base64'
|
|
133
185
|
}));
|
|
134
186
|
} catch (error) {
|
|
135
187
|
console.error(chalk.red(`Error forwarding request: ${error.message}`));
|
|
@@ -245,6 +297,38 @@ async function stop(tunnelId) {
|
|
|
245
297
|
}
|
|
246
298
|
}
|
|
247
299
|
|
|
300
|
+
// Stop all tunnels
|
|
301
|
+
async function stopAll() {
|
|
302
|
+
const spinner = ora('Stopping all tunnels...').start();
|
|
303
|
+
|
|
304
|
+
try {
|
|
305
|
+
const response = await api.getTunnels();
|
|
306
|
+
const tunnels = response.tunnels || [];
|
|
307
|
+
|
|
308
|
+
if (tunnels.length === 0) {
|
|
309
|
+
spinner.info(chalk.yellow('No active tunnels to stop'));
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
let stopped = 0;
|
|
314
|
+
for (const tunnel of tunnels) {
|
|
315
|
+
try {
|
|
316
|
+
await api.deleteTunnel(tunnel._id || tunnel.id);
|
|
317
|
+
stopped++;
|
|
318
|
+
} catch (e) {
|
|
319
|
+
// Continue stopping other tunnels
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
spinner.succeed(chalk.green(`Stopped ${stopped} tunnel(s)`));
|
|
324
|
+
console.log();
|
|
325
|
+
} catch (error) {
|
|
326
|
+
spinner.fail(chalk.red('Failed to stop tunnels'));
|
|
327
|
+
console.error(chalk.red(`\n✗ ${error.response?.data?.msg || error.message}\n`));
|
|
328
|
+
process.exit(1);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
248
332
|
// View tunnel logs
|
|
249
333
|
async function logs(tunnelId, options) {
|
|
250
334
|
const spinner = ora('Fetching logs...').start();
|
|
@@ -423,6 +507,7 @@ module.exports = {
|
|
|
423
507
|
start,
|
|
424
508
|
list,
|
|
425
509
|
stop,
|
|
510
|
+
stopAll,
|
|
426
511
|
logs,
|
|
427
512
|
setDomain,
|
|
428
513
|
uploadSSL,
|
package/package.json
CHANGED
package/utils/api.js
CHANGED
|
@@ -59,6 +59,11 @@ class APIClient {
|
|
|
59
59
|
return response.data;
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
async getUser() {
|
|
63
|
+
const response = await this.client.get('/auth/me');
|
|
64
|
+
return response.data;
|
|
65
|
+
}
|
|
66
|
+
|
|
62
67
|
// OAuth Device Flow for CLI
|
|
63
68
|
async requestDeviceCode(provider) {
|
|
64
69
|
const response = await this.client.post('/auth/device/code', { provider });
|