vibex-sh 0.9.7 → 0.9.8

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.
Files changed (2) hide show
  1. package/index.js +127 -38
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -437,19 +437,89 @@ async function main() {
437
437
  let reconnectAttempts = 0;
438
438
  const maxReconnectDelay = 5000;
439
439
 
440
+ // Connection state management
441
+ let connectionState = 'disconnected'; // disconnected, connecting, connected, closing, closed
442
+ let connectionEstablished = false;
443
+ let connectionLock = false;
444
+ let connectionStartTime = null;
445
+
440
446
  // Store auth code received from socket
441
447
  let receivedAuthCode = authCode;
442
448
 
443
449
  // Track if this is a new session (not reusing an existing one)
444
450
  const isNewSession = !options.sessionId;
445
451
 
452
+ // Graceful shutdown function
453
+ const closeWebSocket = () => {
454
+ return new Promise((resolve) => {
455
+ if (!socket || socket.readyState !== WebSocket.OPEN) {
456
+ if (connectionState === 'connected' || connectionState === 'closing') {
457
+ connectionState = 'closed';
458
+ }
459
+ resolve();
460
+ return;
461
+ }
462
+
463
+ connectionState = 'closing';
464
+ const closeStartTime = Date.now();
465
+ console.log(' 🔄 Initiating graceful WebSocket close...');
466
+
467
+ const closeTimeout = setTimeout(() => {
468
+ const elapsed = Date.now() - closeStartTime;
469
+ console.log(` ⚠️ Close handshake timeout after ${elapsed}ms, forcing exit`);
470
+ connectionState = 'closed';
471
+ resolve();
472
+ }, 2000);
473
+
474
+ // Store original onclose handler
475
+ const originalOnClose = socket.onclose;
476
+
477
+ socket.onclose = (event) => {
478
+ clearTimeout(closeTimeout);
479
+ const elapsed = Date.now() - closeStartTime;
480
+ connectionState = 'closed';
481
+ connectionEstablished = false;
482
+ console.log(` ✓ WebSocket closed gracefully (code: ${event.code}, reason: ${event.reason || 'none'}, time: ${elapsed}ms)`);
483
+
484
+ // Call original handler if it exists
485
+ if (originalOnClose) {
486
+ originalOnClose(event);
487
+ }
488
+
489
+ resolve();
490
+ };
491
+
492
+ socket.close(1000, 'Stream ended');
493
+ });
494
+ };
495
+
446
496
  const connectWebSocket = () => {
497
+ // Prevent multiple simultaneous connections
498
+ if (connectionLock) {
499
+ console.log(' ⚠️ Connection already in progress, skipping...');
500
+ return;
501
+ }
502
+
503
+ if (connectionState === 'connected' || connectionState === 'connecting') {
504
+ console.log(` ⚠️ Already ${connectionState}, skipping new connection...`);
505
+ return;
506
+ }
507
+
508
+ connectionLock = true;
509
+ connectionState = 'connecting';
510
+ connectionStartTime = Date.now();
511
+ console.log(' 🔄 Connecting to WebSocket...');
512
+
447
513
  try {
448
514
  socket = new WebSocket(`${socketUrl}?sessionId=${sessionId}`);
449
515
 
450
516
  socket.onopen = () => {
517
+ const connectTime = Date.now() - connectionStartTime;
518
+ connectionLock = false;
519
+ connectionState = 'connected';
451
520
  isConnected = true;
452
- console.log(' ✓ Connected to server\n');
521
+ connectionEstablished = true;
522
+ console.log(` ✓ Connected to server (${connectTime}ms)\n`);
453
523
  reconnectAttempts = 0;
454
524
 
455
525
  // Join session
@@ -458,17 +528,15 @@ async function main() {
458
528
  sessionId,
459
529
  }));
460
530
 
461
- // Wait a bit for join-session to be processed
462
- setTimeout(() => {
463
- hasJoinedSession = true;
464
- // Process any queued logs
465
- while (logQueue.length > 0) {
466
- const logData = logQueue.shift();
467
- // Send logs via HTTP POST (non-blocking) instead of WebSocket
468
- // WebSocket is only for receiving logs
469
- sendLogViaHTTP(logData);
470
- }
471
- }, 100);
531
+ // Set hasJoinedSession immediately - HTTP POST doesn't need WebSocket
532
+ hasJoinedSession = true;
533
+ // Process any queued logs immediately
534
+ while (logQueue.length > 0) {
535
+ const logData = logQueue.shift();
536
+ // Send logs via HTTP POST (non-blocking) instead of WebSocket
537
+ // WebSocket is only for receiving logs
538
+ sendLogViaHTTP(logData);
539
+ }
472
540
  };
473
541
 
474
542
  socket.onmessage = (event) => {
@@ -607,11 +675,17 @@ async function main() {
607
675
  };
608
676
 
609
677
  socket.onclose = (event) => {
678
+ connectionLock = false;
679
+ connectionState = 'closed';
680
+ connectionEstablished = false;
610
681
  isConnected = false;
611
682
  hasJoinedSession = false;
683
+
684
+ const connectionDuration = connectionStartTime ? Date.now() - connectionStartTime : 0;
685
+ console.log(` 📊 Connection closed (code: ${event.code}, reason: ${event.reason || 'none'}, duration: ${connectionDuration}ms)`);
612
686
 
613
- // Reconnect logic
614
- if (event.code !== 1000) { // Not a normal closure
687
+ // Reconnect logic - only if not a normal closure and not already closing
688
+ if (event.code !== 1000 && connectionState !== 'closing') {
615
689
  const delay = Math.min(1000 * Math.pow(2, reconnectAttempts), maxReconnectDelay);
616
690
  reconnectAttempts++;
617
691
  console.log(` ↻ Reconnecting in ${delay}ms (attempt ${reconnectAttempts})...\n`);
@@ -619,9 +693,14 @@ async function main() {
619
693
  reconnectTimeout = setTimeout(() => {
620
694
  connectWebSocket();
621
695
  }, delay);
696
+ } else if (event.code === 1000) {
697
+ console.log(' ✓ Normal closure, no reconnect needed\n');
622
698
  }
623
699
  };
624
700
  } catch (error) {
701
+ connectionLock = false;
702
+ connectionState = 'disconnected';
703
+ connectionEstablished = false;
625
704
  console.error(` ✗ Error creating WebSocket: ${error.message}`);
626
705
  console.error(` ↻ URL: ${socketUrl}`);
627
706
  // Retry connection
@@ -634,6 +713,7 @@ async function main() {
634
713
  // Send logs via HTTP POST (non-blocking, same as SDKs)
635
714
  // Use Cloudflare Worker endpoint (port 8787 for local, or Worker URL for production)
636
715
  // Token is optional - anonymous sessions can send logs without authentication
716
+ // HTTP POST works independently of WebSocket - don't wait for WebSocket connection
637
717
  const sendLogViaHTTP = async (logData) => {
638
718
  try {
639
719
  // Determine ingest URL
@@ -788,43 +868,52 @@ async function main() {
788
868
  };
789
869
  }
790
870
 
791
- // Send logs via HTTP POST (non-blocking, same as SDKs)
792
- // WebSocket is only for receiving logs and auth codes
793
- if (hasJoinedSession) {
794
- sendLogViaHTTP(logData);
795
- } else {
796
- logQueue.push(logData);
797
- }
871
+ // Send logs via HTTP POST immediately - don't wait for WebSocket
872
+ // WebSocket is only for receiving logs and auth codes, not required for sending
873
+ sendLogViaHTTP(logData);
798
874
  });
799
875
 
800
- rl.on('close', () => {
876
+ rl.on('close', async () => {
801
877
  // Wait for queued logs to be sent
802
878
  const waitForQueue = () => {
803
- if (logQueue.length === 0) {
804
- console.log('\n Stream ended. Closing connection...\n');
805
- if (socket && socket.readyState === 1) { // WebSocket.OPEN = 1
806
- socket.close(1000, 'Stream ended');
807
- }
808
- if (reconnectTimeout) {
809
- clearTimeout(reconnectTimeout);
879
+ return new Promise((resolve) => {
880
+ if (logQueue.length === 0) {
881
+ resolve();
882
+ } else {
883
+ setTimeout(() => waitForQueue().then(resolve), 100);
810
884
  }
811
- setTimeout(() => process.exit(0), 100);
812
- } else {
813
- setTimeout(waitForQueue, 100);
814
- }
885
+ });
815
886
  };
816
887
 
817
- waitForQueue();
888
+ await waitForQueue();
889
+
890
+ console.log('\n Stream ended. Closing connection...\n');
891
+
892
+ // Cancel any pending reconnection attempts
893
+ if (reconnectTimeout) {
894
+ clearTimeout(reconnectTimeout);
895
+ reconnectTimeout = null;
896
+ }
897
+
898
+ // Graceful shutdown - wait for close handshake
899
+ await closeWebSocket();
900
+
901
+ // Give a moment for any final cleanup
902
+ setTimeout(() => process.exit(0), 100);
818
903
  });
819
904
 
820
- process.on('SIGINT', () => {
905
+ process.on('SIGINT', async () => {
821
906
  console.log('\n Interrupted. Closing connection...\n');
822
- if (socket && socket.readyState === 1) { // WebSocket.OPEN = 1
823
- socket.close(1000, 'Interrupted');
824
- }
907
+
908
+ // Cancel any pending reconnection attempts
825
909
  if (reconnectTimeout) {
826
910
  clearTimeout(reconnectTimeout);
911
+ reconnectTimeout = null;
827
912
  }
913
+
914
+ // Graceful shutdown
915
+ await closeWebSocket();
916
+
828
917
  process.exit(0);
829
918
  });
830
919
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibex-sh",
3
- "version": "0.9.7",
3
+ "version": "0.9.8",
4
4
  "description": "Zero-config observability CLI - pipe logs and visualize instantly",
5
5
  "type": "module",
6
6
  "bin": {