vibex-sh 0.2.3 β†’ 0.9.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.
Files changed (2) hide show
  1. package/index.js +191 -182
  2. package/package.json +2 -2
package/index.js CHANGED
@@ -1,5 +1,4 @@
1
1
  import readline from 'readline';
2
- import { io } from 'socket.io-client';
3
2
  import { program, Command } from 'commander';
4
3
  import { readFile, writeFile, mkdir } from 'fs/promises';
5
4
  import { existsSync, readFileSync } from 'fs';
@@ -9,6 +8,7 @@ import { spawn } from 'child_process';
9
8
  import http from 'http';
10
9
  import https from 'https';
11
10
  import { fileURLToPath } from 'url';
11
+ import WebSocket from 'ws';
12
12
 
13
13
  // Get version from package.json
14
14
  const __filename = fileURLToPath(import.meta.url);
@@ -59,18 +59,20 @@ function normalizeSessionId(sessionId) {
59
59
  function deriveSocketUrl(webUrl) {
60
60
  const url = new URL(webUrl);
61
61
 
62
- // For localhost, socket is typically on port 3001
62
+ // For localhost, use Workers dev server
63
63
  if (url.hostname === 'localhost' || url.hostname === '127.0.0.1') {
64
- const port = url.port === '3000' || !url.port ? '3001' : String(parseInt(url.port) + 1);
65
- return `${url.protocol}//${url.hostname}:${port}`;
64
+ return 'ws://localhost:8787';
66
65
  }
67
- // For vibex.sh domains, use socket subdomain
66
+ // For vibex.sh domains, use Workers WebSocket endpoint
68
67
  else if (url.hostname.includes('vibex.sh')) {
69
- return webUrl.replace(url.hostname, `socket.${url.hostname}`);
68
+ // Use Cloudflare Workers WebSocket endpoint
69
+ const workerUrl = process.env.VIBEX_WORKER_URL || 'https://vibex-ingest.your-subdomain.workers.dev';
70
+ return workerUrl.replace('https://', 'wss://').replace('http://', 'ws://');
70
71
  }
71
- // For other domains, try to use socket subdomain
72
+ // For other domains, derive from web URL
72
73
  else {
73
- return webUrl.replace(url.hostname, `socket.${url.hostname}`);
74
+ const workerUrl = process.env.VIBEX_WORKER_URL || webUrl.replace(url.hostname, `ingest.${url.hostname}`);
75
+ return workerUrl.replace('https://', 'wss://').replace('http://', 'ws://');
74
76
  }
75
77
  }
76
78
 
@@ -97,7 +99,7 @@ function getUrls(options) {
97
99
  if (local) {
98
100
  return {
99
101
  webUrl: process.env.VIBEX_WEB_URL || 'http://localhost:3000',
100
- socketUrl: process.env.VIBEX_SOCKET_URL || socket || 'http://localhost:3001',
102
+ socketUrl: process.env.VIBEX_SOCKET_URL || socket || 'ws://localhost:8787',
101
103
  };
102
104
  }
103
105
 
@@ -403,20 +405,13 @@ async function main() {
403
405
  console.log(` πŸ” Sending logs to session: ${sessionId}\n`);
404
406
  }
405
407
 
406
- const socket = io(socketUrl, {
407
- transports: ['websocket', 'polling'],
408
- autoConnect: true,
409
- // Reconnection settings for Cloud Run
410
- reconnection: true,
411
- reconnectionDelay: 1000,
412
- reconnectionDelayMax: 5000,
413
- reconnectionAttempts: Infinity, // Keep trying forever
414
- timeout: 20000,
415
- });
416
-
408
+ let socket = null;
417
409
  let isConnected = false;
418
410
  let hasJoinedSession = false;
419
411
  const logQueue = [];
412
+ let reconnectTimeout = null;
413
+ let reconnectAttempts = 0;
414
+ const maxReconnectDelay = 5000;
420
415
 
421
416
  // Store auth code received from socket
422
417
  let receivedAuthCode = authCode;
@@ -424,155 +419,179 @@ async function main() {
424
419
  // Track if this is a new session (not reusing an existing one)
425
420
  const isNewSession = !options.sessionId;
426
421
 
427
- socket.on('connect', () => {
428
- isConnected = true;
429
- console.log(' βœ“ Connected to server\n');
430
- console.error(`[CLI DEBUG] Socket connected, socket.id: ${socket.id}`);
431
- console.error(`[CLI DEBUG] About to emit join-session for: ${sessionId}`);
432
- // Rejoin session on reconnect
433
- socket.emit('join-session', sessionId);
434
- console.error(`[CLI DEBUG] βœ… join-session emitted, waiting 100ms before setting hasJoinedSession`);
435
- // Wait a tiny bit for join-session to be processed
436
- setTimeout(() => {
437
- hasJoinedSession = true;
438
- console.error(`[CLI DEBUG] hasJoinedSession set to true, processing ${logQueue.length} queued logs`);
439
- // Process any queued logs
440
- while (logQueue.length > 0) {
441
- const logData = logQueue.shift();
442
- console.error(`[CLI DEBUG] Emitting queued cli-emit (connect) for sessionId: ${sessionId}`);
443
- socket.emit('cli-emit', {
444
- sessionId,
445
- ...logData,
446
- });
447
- }
448
- }, 100);
449
- });
422
+ const connectWebSocket = () => {
423
+ try {
424
+ socket = new WebSocket(`${socketUrl}?sessionId=${sessionId}`);
450
425
 
451
- // Listen for auth code from socket.io (for unclaimed sessions)
452
- // Only display auth code if this is a new session (not when reusing existing session)
453
- socket.on('session-auth-code', (data) => {
454
- if (data.sessionId === sessionId && data.authCode) {
455
- // Update received auth code
456
- if (!receivedAuthCode || receivedAuthCode !== data.authCode) {
457
- receivedAuthCode = data.authCode;
458
- // Only display auth code for new sessions, not when reusing existing sessions
459
- if (isNewSession) {
460
- console.log(` πŸ”‘ Auth Code: ${receivedAuthCode}`);
461
- console.log(` πŸ“‹ Dashboard: ${webUrl}/${sessionId}?auth=${receivedAuthCode}\n`);
462
- }
463
- }
464
- }
465
- });
426
+ socket.onopen = () => {
427
+ isConnected = true;
428
+ console.log(' βœ“ Connected to server\n');
429
+ reconnectAttempts = 0;
466
430
 
467
- socket.on('reconnect', (attemptNumber) => {
468
- console.log(` ↻ Reconnected (attempt ${attemptNumber})\n`);
469
- isConnected = true;
470
- // Rejoin session after reconnection
471
- socket.emit('join-session', sessionId);
472
- setTimeout(() => {
473
- hasJoinedSession = true;
474
- // Process any queued logs
475
- while (logQueue.length > 0) {
476
- const logData = logQueue.shift();
477
- console.error(`[CLI DEBUG] Emitting queued cli-emit (reconnect) for sessionId: ${sessionId}`);
478
- socket.emit('cli-emit', {
431
+ // Join session
432
+ socket.send(JSON.stringify({
433
+ type: 'join-session',
479
434
  sessionId,
480
- ...logData,
481
- });
482
- }
483
- }, 100);
484
- });
485
-
486
- socket.on('reconnect_attempt', (attemptNumber) => {
487
- // Silent reconnection attempts - don't spam console
488
- });
435
+ }));
436
+
437
+ // Wait a bit for join-session to be processed
438
+ setTimeout(() => {
439
+ hasJoinedSession = true;
440
+ // Process any queued logs
441
+ while (logQueue.length > 0) {
442
+ const logData = logQueue.shift();
443
+ // Send logs via HTTP POST (non-blocking) instead of WebSocket
444
+ // WebSocket is only for receiving logs
445
+ sendLogViaHTTP(logData);
446
+ }
447
+ }, 100);
448
+ };
489
449
 
490
- socket.on('reconnect_error', (error) => {
491
- // Silent reconnection errors - will keep trying
492
- });
450
+ socket.onmessage = (event) => {
451
+ try {
452
+ const message = JSON.parse(event.data);
453
+
454
+ switch (message.type) {
455
+ case 'join-session-ack':
456
+ console.log(' βœ“ Joined session\n');
457
+ break;
458
+
459
+ case 'session-auth-code':
460
+ if (message.data && message.data.sessionId === sessionId && message.data.authCode) {
461
+ if (!receivedAuthCode || receivedAuthCode !== message.data.authCode) {
462
+ receivedAuthCode = message.data.authCode;
463
+ if (isNewSession) {
464
+ console.log(` πŸ”‘ Auth Code: ${receivedAuthCode}`);
465
+ console.log(` πŸ“‹ Dashboard: ${webUrl}/${sessionId}?auth=${receivedAuthCode}\n`);
466
+ }
467
+ }
468
+ }
469
+ break;
470
+
471
+ case 'log':
472
+ // Logs are received via WebSocket but sent via HTTP
473
+ break;
474
+
475
+ case 'error':
476
+ if (message.error === 'Rate Limit Exceeded') {
477
+ console.error('\n ⚠️ Rate Limit Exceeded');
478
+ console.error(` ${message.message || 'Too many requests. Please try again later.'}`);
479
+ console.error('');
480
+ logQueue.length = 0;
481
+ } else if (message.error === 'History Limit Reached') {
482
+ console.error('\n 🚫 History Limit Reached');
483
+ console.error(` ${message.message || 'Session history limit reached'}`);
484
+ if (message.limit !== undefined && message.current !== undefined) {
485
+ console.error(` Current: ${message.current} / ${message.limit} logs`);
486
+ }
487
+ if (message.upgradeRequired) {
488
+ console.error(' πŸ’‘ Upgrade to Pro to unlock 30 days retention');
489
+ console.error(' 🌐 Visit: https://vibex.sh/pricing');
490
+ }
491
+ console.error('');
492
+ logQueue.length = 0;
493
+ hasJoinedSession = false;
494
+ } else {
495
+ console.error('\n βœ— Server Error');
496
+ console.error(` ${message.error || message.message || 'An unexpected error occurred'}`);
497
+ console.error('');
498
+ }
499
+ break;
500
+
501
+ default:
502
+ // Ignore unknown message types
503
+ break;
504
+ }
505
+ } catch (error) {
506
+ console.error(' βœ— Error parsing message:', error.message);
507
+ }
508
+ };
493
509
 
494
- socket.on('reconnect_failed', () => {
495
- console.error(' βœ— Failed to reconnect after all attempts');
496
- console.error(' Stream will continue, but logs may be lost until reconnection\n');
497
- });
510
+ socket.onerror = (error) => {
511
+ if (!isConnected) {
512
+ console.error(` βœ— Connection error: ${error.message || 'websocket error'}`);
513
+ console.error(` ↻ Trying to connect to: ${socketUrl}`);
514
+ console.error(' ↻ Retrying connection...\n');
515
+ }
516
+ };
498
517
 
499
- socket.on('connect_error', (error) => {
500
- // Don't exit on first connection error - allow reconnection
501
- if (!isConnected) {
502
- console.error(' βœ— Connection error:', error.message);
503
- console.error(' ↻ Retrying connection...\n');
518
+ socket.onclose = (event) => {
519
+ isConnected = false;
520
+ hasJoinedSession = false;
521
+
522
+ // Reconnect logic
523
+ if (event.code !== 1000) { // Not a normal closure
524
+ const delay = Math.min(1000 * Math.pow(2, reconnectAttempts), maxReconnectDelay);
525
+ reconnectAttempts++;
526
+ console.log(` ↻ Reconnecting in ${delay}ms (attempt ${reconnectAttempts})...\n`);
527
+
528
+ reconnectTimeout = setTimeout(() => {
529
+ connectWebSocket();
530
+ }, delay);
531
+ }
532
+ };
533
+ } catch (error) {
534
+ console.error(` βœ— Error creating WebSocket: ${error.message}`);
535
+ console.error(` ↻ URL: ${socketUrl}`);
536
+ // Retry connection
537
+ reconnectTimeout = setTimeout(() => {
538
+ connectWebSocket();
539
+ }, 1000);
504
540
  }
505
- });
541
+ };
506
542
 
507
- socket.on('disconnect', (reason) => {
508
- isConnected = false;
509
- hasJoinedSession = false;
510
- // Don't exit - allow reconnection
511
- if (reason === 'io server disconnect') {
512
- // Server disconnected, will reconnect automatically
543
+ // Send logs via HTTP POST (non-blocking, same as SDKs)
544
+ // Use Cloudflare Worker endpoint (port 8787 for local, or Worker URL for production)
545
+ const sendLogViaHTTP = async (logData) => {
546
+ if (!token) {
547
+ console.error(' βœ— No token available for sending logs');
548
+ return;
513
549
  }
514
- });
515
550
 
516
- // Handle rate limit errors from server
517
- socket.on('rate-limit-exceeded', (data) => {
518
- console.error('\n ⚠️ Rate Limit Exceeded');
519
- console.error(` ${data.message || 'Too many requests. Please try again later.'}`);
520
- if (data.rateLimit) {
521
- const { limit, remaining, resetAt, windowSeconds } = data.rateLimit;
522
- if (limit !== undefined) {
523
- console.error(` Limit: ${limit} requests`);
524
- }
525
- if (remaining !== undefined) {
526
- console.error(` Remaining: ${remaining} requests`);
527
- }
528
- if (resetAt) {
529
- const resetDate = new Date(resetAt);
530
- const now = new Date();
531
- const secondsUntilReset = Math.ceil((resetDate - now) / 1000);
532
- if (secondsUntilReset > 0) {
533
- console.error(` Resets in: ${secondsUntilReset} seconds`);
551
+ try {
552
+ // For local development, use Workers dev server (port 8787)
553
+ // For production, use Cloudflare Worker URL
554
+ const ingestUrl = webUrl.includes('localhost') || webUrl.includes('127.0.0.1')
555
+ ? 'http://localhost:8787/api/v1/ingest'
556
+ : process.env.VIBEX_WORKER_URL
557
+ ? `${process.env.VIBEX_WORKER_URL}/api/v1/ingest`
558
+ : `${webUrl}/api/v1/ingest`; // Fallback to web URL (should be proxied)
559
+
560
+ const response = await fetch(ingestUrl, {
561
+ method: 'POST',
562
+ headers: {
563
+ 'Authorization': `Bearer ${token}`,
564
+ 'Content-Type': 'application/json',
565
+ },
566
+ body: JSON.stringify({
567
+ sessionId,
568
+ logs: [logData],
569
+ }),
570
+ });
571
+
572
+ if (!response.ok) {
573
+ const errorData = await response.json().catch(() => ({}));
574
+ if (response.status === 429) {
575
+ console.error('\n ⚠️ Rate Limit Exceeded');
576
+ console.error(` ${errorData.message || 'Too many requests. Please try again later.'}`);
577
+ console.error('');
578
+ } else if (response.status === 403 && errorData.message?.includes('History Limit')) {
579
+ console.error('\n 🚫 History Limit Reached');
580
+ console.error(` ${errorData.message || 'Session history limit reached'}`);
581
+ if (errorData.upgradeRequired) {
582
+ console.error(' πŸ’‘ Upgrade to Pro to unlock 30 days retention');
583
+ console.error(' 🌐 Visit: https://vibex.sh/pricing');
584
+ }
585
+ console.error('');
534
586
  }
535
587
  }
588
+ } catch (error) {
589
+ console.error(' βœ— Error sending log:', error.message);
536
590
  }
537
- console.error('');
538
- // Don't exit - let user decide, but clear the queue
539
- logQueue.length = 0;
540
- });
591
+ };
541
592
 
542
- // Handle general errors from server
543
- socket.on('error', (data) => {
544
- // Check if it's a history limit error
545
- if (data && data.error === 'History Limit Reached') {
546
- console.error('\n 🚫 History Limit Reached');
547
- console.error(` ${data.message || 'Session history limit reached'}`);
548
- if (data.limit !== undefined && data.current !== undefined) {
549
- console.error(` Current: ${data.current} / ${data.limit} logs`);
550
- }
551
- if (data.upgradeRequired) {
552
- console.error(' πŸ’‘ Upgrade to Pro to unlock 30 days retention');
553
- console.error(' 🌐 Visit: https://vibex.sh/pricing');
554
- }
555
- console.error('');
556
- // Clear the queue and stop processing
557
- logQueue.length = 0;
558
- hasJoinedSession = false; // Prevent further logs from being sent
559
- return;
560
- }
561
-
562
- // Handle other errors
563
- console.error('\n βœ— Server Error');
564
- if (typeof data === 'string') {
565
- console.error(` ${data}`);
566
- } else if (data && data.message) {
567
- console.error(` ${data.message}`);
568
- if (data.error) {
569
- console.error(` Error: ${data.error}`);
570
- }
571
- } else {
572
- console.error(' An unexpected error occurred');
573
- }
574
- console.error('');
575
- });
593
+ // Start WebSocket connection
594
+ connectWebSocket();
576
595
 
577
596
  const rl = readline.createInterface({
578
597
  input: process.stdin,
@@ -581,10 +600,8 @@ async function main() {
581
600
  });
582
601
 
583
602
  rl.on('line', (line) => {
584
- console.error(`[CLI DEBUG] Received line from stdin: "${line}"`);
585
603
  const trimmedLine = line.trim();
586
604
  if (!trimmedLine) {
587
- console.error(`[CLI DEBUG] Line is empty, skipping`);
588
605
  return;
589
606
  }
590
607
 
@@ -596,46 +613,33 @@ async function main() {
596
613
  payload: parsed,
597
614
  timestamp: Date.now(),
598
615
  };
599
- console.error(`[CLI DEBUG] Parsed as JSON log`);
600
616
  } catch (e) {
601
617
  logData = {
602
618
  type: 'text',
603
619
  payload: trimmedLine,
604
620
  timestamp: Date.now(),
605
621
  };
606
- console.error(`[CLI DEBUG] Parsed as text log`);
607
622
  }
608
623
 
609
- console.error(`[CLI DEBUG] Connection state - isConnected: ${isConnected}, hasJoinedSession: ${hasJoinedSession}, socket.connected: ${socket?.connected}`);
610
-
611
- // If connected and joined session, send immediately; otherwise queue it
612
- if (isConnected && hasJoinedSession && socket.connected) {
613
- console.error(`[CLI DEBUG] βœ… Ready to emit - Emitting cli-emit for sessionId: ${sessionId}`);
614
- console.error(`[CLI DEBUG] Log data:`, JSON.stringify(logData, null, 2));
615
- socket.emit('cli-emit', {
616
- sessionId,
617
- ...logData,
618
- });
619
- console.error(`[CLI DEBUG] βœ… cli-emit event emitted successfully`);
624
+ // Send logs via HTTP POST (non-blocking, same as SDKs)
625
+ // WebSocket is only for receiving logs and auth codes
626
+ if (hasJoinedSession) {
627
+ sendLogViaHTTP(logData);
620
628
  } else {
621
- console.error(`[CLI DEBUG] ⏸️ Queueing log - isConnected: ${isConnected}, hasJoinedSession: ${hasJoinedSession}, socket.connected: ${socket?.connected}`);
622
629
  logQueue.push(logData);
623
- console.error(`[CLI DEBUG] Queue now has ${logQueue.length} items`);
624
630
  }
625
631
  });
626
632
 
627
633
  rl.on('close', () => {
628
- // Wait for connection and queued logs to be sent
634
+ // Wait for queued logs to be sent
629
635
  const waitForQueue = () => {
630
- if (logQueue.length === 0 || (!isConnected && logQueue.length > 0)) {
631
- // If not connected and we have queued logs, wait a bit more
632
- if (!isConnected && logQueue.length > 0) {
633
- setTimeout(waitForQueue, 200);
634
- return;
635
- }
636
+ if (logQueue.length === 0) {
636
637
  console.log('\n Stream ended. Closing connection...\n');
637
- if (socket.connected) {
638
- socket.disconnect();
638
+ if (socket && socket.readyState === 1) { // WebSocket.OPEN = 1
639
+ socket.close(1000, 'Stream ended');
640
+ }
641
+ if (reconnectTimeout) {
642
+ clearTimeout(reconnectTimeout);
639
643
  }
640
644
  setTimeout(() => process.exit(0), 100);
641
645
  } else {
@@ -648,7 +652,12 @@ async function main() {
648
652
 
649
653
  process.on('SIGINT', () => {
650
654
  console.log('\n Interrupted. Closing connection...\n');
651
- socket.disconnect();
655
+ if (socket && socket.readyState === 1) { // WebSocket.OPEN = 1
656
+ socket.close(1000, 'Interrupted');
657
+ }
658
+ if (reconnectTimeout) {
659
+ clearTimeout(reconnectTimeout);
660
+ }
652
661
  process.exit(0);
653
662
  });
654
663
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibex-sh",
3
- "version": "0.2.3",
3
+ "version": "0.9.0",
4
4
  "description": "Zero-config observability CLI - pipe logs and visualize instantly",
5
5
  "type": "module",
6
6
  "bin": {
@@ -27,6 +27,6 @@
27
27
  "homepage": "https://vibex.sh",
28
28
  "dependencies": {
29
29
  "commander": "^11.1.0",
30
- "socket.io-client": "^4.7.2"
30
+ "ws": "^8.16.0"
31
31
  }
32
32
  }