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