api-response-manager 2.6.0 ā 2.6.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.
- package/commands/serve.js +148 -104
- package/package.json +1 -1
package/commands/serve.js
CHANGED
|
@@ -443,125 +443,169 @@ async function connectFileTunnel(tunnelId, subdomain, localPort) {
|
|
|
443
443
|
const token = api.getToken();
|
|
444
444
|
const userId = config.get('userId');
|
|
445
445
|
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
ws.on('open', () => {
|
|
449
|
-
console.log(chalk.green('ā Connected to tunnel server'));
|
|
450
|
-
|
|
451
|
-
ws.send(JSON.stringify({
|
|
452
|
-
type: 'register',
|
|
453
|
-
tunnelId,
|
|
454
|
-
subdomain,
|
|
455
|
-
localPort,
|
|
456
|
-
protocol: 'http',
|
|
457
|
-
authToken: token,
|
|
458
|
-
userId
|
|
459
|
-
}));
|
|
460
|
-
|
|
461
|
-
// Heartbeat
|
|
462
|
-
setInterval(() => {
|
|
463
|
-
if (ws.readyState === WebSocket.OPEN) {
|
|
464
|
-
ws.send(JSON.stringify({ type: 'heartbeat', tunnelId, subdomain }));
|
|
465
|
-
}
|
|
466
|
-
}, 30000);
|
|
467
|
-
});
|
|
468
|
-
|
|
469
|
-
// Track total bytes transferred
|
|
446
|
+
// Track total bytes transferred (persists across reconnects)
|
|
470
447
|
let totalBytesTransferred = 0;
|
|
471
448
|
let limitReached = false;
|
|
449
|
+
let heartbeatInterval = null;
|
|
450
|
+
let reconnectAttempts = 0;
|
|
451
|
+
const maxReconnectAttempts = 50; // Allow many reconnects
|
|
452
|
+
let isShuttingDown = false;
|
|
472
453
|
|
|
473
|
-
|
|
474
|
-
|
|
454
|
+
function connect() {
|
|
455
|
+
if (isShuttingDown) return;
|
|
475
456
|
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
const timestamp = new Date().toLocaleTimeString();
|
|
457
|
+
const ws = new WebSocket(tunnelServerUrl);
|
|
458
|
+
|
|
459
|
+
ws.on('open', () => {
|
|
460
|
+
reconnectAttempts = 0; // Reset on successful connection
|
|
461
|
+
console.log(chalk.green('ā Connected to tunnel server'));
|
|
482
462
|
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
return;
|
|
463
|
+
ws.send(JSON.stringify({
|
|
464
|
+
type: 'register',
|
|
465
|
+
tunnelId,
|
|
466
|
+
subdomain,
|
|
467
|
+
localPort,
|
|
468
|
+
protocol: 'http',
|
|
469
|
+
authToken: token,
|
|
470
|
+
userId,
|
|
471
|
+
persistent: true // File serving tunnels should not have idle timeout
|
|
472
|
+
}));
|
|
473
|
+
|
|
474
|
+
// Clear any existing heartbeat
|
|
475
|
+
if (heartbeatInterval) {
|
|
476
|
+
clearInterval(heartbeatInterval);
|
|
498
477
|
}
|
|
499
478
|
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
479
|
+
// Heartbeat every 15 seconds to keep connection alive
|
|
480
|
+
heartbeatInterval = setInterval(() => {
|
|
481
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
482
|
+
ws.send(JSON.stringify({ type: 'heartbeat', tunnelId, subdomain }));
|
|
483
|
+
}
|
|
484
|
+
}, 15000);
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
ws.on('message', async (data) => {
|
|
488
|
+
const message = JSON.parse(data.toString());
|
|
489
|
+
|
|
490
|
+
if (message.type === 'registered') {
|
|
491
|
+
console.log(chalk.green.bold('\nš File Server is now publicly accessible!\n'));
|
|
492
|
+
console.log(chalk.white('Share this URL:'));
|
|
493
|
+
console.log(chalk.cyan.bold(` ${message.publicUrl}\n`));
|
|
494
|
+
} else if (message.type === 'request') {
|
|
495
|
+
const timestamp = new Date().toLocaleTimeString();
|
|
496
|
+
|
|
497
|
+
// Check if limit was reached
|
|
498
|
+
if (limitReached) {
|
|
499
|
+
console.log(chalk.red(`[${timestamp}]`), chalk.red('BLOCKED'), chalk.white(message.path), chalk.red('- Transfer limit reached'));
|
|
500
|
+
ws.send(JSON.stringify({
|
|
501
|
+
type: 'response',
|
|
502
|
+
requestId: message.requestId,
|
|
503
|
+
statusCode: 429,
|
|
504
|
+
headers: { 'content-type': 'application/json' },
|
|
505
|
+
body: Buffer.from(JSON.stringify({
|
|
506
|
+
error: 'Monthly file transfer limit reached',
|
|
507
|
+
message: 'Upgrade your plan at https://tunnelapi.in/pricing for more transfer quota.'
|
|
508
|
+
})).toString('base64'),
|
|
509
|
+
encoding: 'base64'
|
|
510
|
+
}));
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
console.log(chalk.gray(`[${timestamp}]`), chalk.blue(message.method), chalk.white(message.path));
|
|
504
515
|
|
|
505
|
-
const response = await axios({
|
|
506
|
-
method: message.method.toLowerCase(),
|
|
507
|
-
url: localUrl,
|
|
508
|
-
headers: { ...message.headers, host: `localhost:${localPort}` },
|
|
509
|
-
data: message.body,
|
|
510
|
-
validateStatus: () => true,
|
|
511
|
-
responseType: 'arraybuffer',
|
|
512
|
-
maxRedirects: 0,
|
|
513
|
-
timeout: 30000
|
|
514
|
-
});
|
|
515
|
-
|
|
516
|
-
const cleanHeaders = { ...response.headers };
|
|
517
|
-
delete cleanHeaders['transfer-encoding'];
|
|
518
|
-
delete cleanHeaders['connection'];
|
|
519
|
-
|
|
520
|
-
const responseData = Buffer.from(response.data);
|
|
521
|
-
const bodyBase64 = responseData.toString('base64');
|
|
522
|
-
const bytesTransferred = responseData.length;
|
|
523
|
-
|
|
524
|
-
// Track file transfer usage
|
|
525
516
|
try {
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
517
|
+
const localUrl = `http://localhost:${localPort}${message.path}`;
|
|
518
|
+
|
|
519
|
+
const response = await axios({
|
|
520
|
+
method: message.method.toLowerCase(),
|
|
521
|
+
url: localUrl,
|
|
522
|
+
headers: { ...message.headers, host: `localhost:${localPort}` },
|
|
523
|
+
data: message.body,
|
|
524
|
+
validateStatus: () => true,
|
|
525
|
+
responseType: 'arraybuffer',
|
|
526
|
+
maxRedirects: 0,
|
|
527
|
+
timeout: 30000
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
const cleanHeaders = { ...response.headers };
|
|
531
|
+
delete cleanHeaders['transfer-encoding'];
|
|
532
|
+
delete cleanHeaders['connection'];
|
|
533
|
+
|
|
534
|
+
const responseData = Buffer.from(response.data);
|
|
535
|
+
const bodyBase64 = responseData.toString('base64');
|
|
536
|
+
const bytesTransferred = responseData.length;
|
|
537
|
+
|
|
538
|
+
// Track file transfer usage
|
|
539
|
+
try {
|
|
540
|
+
await api.trackFileTransfer(userId, bytesTransferred);
|
|
541
|
+
totalBytesTransferred += bytesTransferred;
|
|
542
|
+
} catch (trackError) {
|
|
543
|
+
if (trackError.response?.data?.limitReached) {
|
|
544
|
+
limitReached = true;
|
|
545
|
+
console.log(chalk.red.bold('\nā ļø Monthly file transfer limit reached!'));
|
|
546
|
+
console.log(chalk.yellow('š” Upgrade your plan at https://tunnelapi.in/pricing for more transfer quota.\n'));
|
|
547
|
+
}
|
|
533
548
|
}
|
|
549
|
+
|
|
550
|
+
ws.send(JSON.stringify({
|
|
551
|
+
type: 'response',
|
|
552
|
+
requestId: message.requestId,
|
|
553
|
+
statusCode: response.status,
|
|
554
|
+
headers: cleanHeaders,
|
|
555
|
+
body: bodyBase64,
|
|
556
|
+
encoding: 'base64'
|
|
557
|
+
}));
|
|
558
|
+
} catch (error) {
|
|
559
|
+
console.error(chalk.red(`Error: ${error.message}`));
|
|
560
|
+
ws.send(JSON.stringify({
|
|
561
|
+
type: 'response',
|
|
562
|
+
requestId: message.requestId,
|
|
563
|
+
statusCode: 502,
|
|
564
|
+
headers: { 'content-type': 'application/json' },
|
|
565
|
+
body: Buffer.from(JSON.stringify({ error: 'File server error' })).toString('base64'),
|
|
566
|
+
encoding: 'base64'
|
|
567
|
+
}));
|
|
534
568
|
}
|
|
569
|
+
}
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
ws.on('error', (error) => {
|
|
573
|
+
console.error(chalk.red(`WebSocket error: ${error.message}`));
|
|
574
|
+
});
|
|
535
575
|
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
console.error(chalk.red(`Error: ${error.message}`));
|
|
546
|
-
ws.send(JSON.stringify({
|
|
547
|
-
type: 'response',
|
|
548
|
-
requestId: message.requestId,
|
|
549
|
-
statusCode: 502,
|
|
550
|
-
headers: { 'content-type': 'application/json' },
|
|
551
|
-
body: Buffer.from(JSON.stringify({ error: 'File server error' })).toString('base64'),
|
|
552
|
-
encoding: 'base64'
|
|
553
|
-
}));
|
|
576
|
+
ws.on('close', (code, reason) => {
|
|
577
|
+
if (heartbeatInterval) {
|
|
578
|
+
clearInterval(heartbeatInterval);
|
|
579
|
+
heartbeatInterval = null;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
if (isShuttingDown) {
|
|
583
|
+
console.log(chalk.yellow('Tunnel connection closed'));
|
|
584
|
+
return;
|
|
554
585
|
}
|
|
555
|
-
}
|
|
556
|
-
});
|
|
557
586
|
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
587
|
+
reconnectAttempts++;
|
|
588
|
+
if (reconnectAttempts <= maxReconnectAttempts) {
|
|
589
|
+
const delay = Math.min(1000 * Math.pow(2, reconnectAttempts - 1), 30000); // Exponential backoff, max 30s
|
|
590
|
+
console.log(chalk.yellow(`Connection lost. Reconnecting in ${delay / 1000}s... (attempt ${reconnectAttempts}/${maxReconnectAttempts})`));
|
|
591
|
+
setTimeout(connect, delay);
|
|
592
|
+
} else {
|
|
593
|
+
console.log(chalk.red('Max reconnection attempts reached. Please restart the serve command.'));
|
|
594
|
+
}
|
|
595
|
+
});
|
|
596
|
+
|
|
597
|
+
// Handle process shutdown
|
|
598
|
+
process.on('SIGINT', () => {
|
|
599
|
+
isShuttingDown = true;
|
|
600
|
+
if (heartbeatInterval) {
|
|
601
|
+
clearInterval(heartbeatInterval);
|
|
602
|
+
}
|
|
603
|
+
ws.close();
|
|
604
|
+
});
|
|
605
|
+
}
|
|
561
606
|
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
});
|
|
607
|
+
// Start initial connection
|
|
608
|
+
connect();
|
|
565
609
|
}
|
|
566
610
|
|
|
567
611
|
module.exports = {
|