vibex-sh 0.2.4 → 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 -165
  2. package/package.json +2 -3
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,149 +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
- // Rejoin session on reconnect
431
- socket.emit('join-session', sessionId);
432
- // Wait a tiny bit for join-session to be processed
433
- setTimeout(() => {
434
- hasJoinedSession = true;
435
- // Process any queued logs
436
- while (logQueue.length > 0) {
437
- const logData = logQueue.shift();
438
- socket.emit('cli-emit', {
439
- sessionId,
440
- ...logData,
441
- });
442
- }
443
- }, 100);
444
- });
422
+ const connectWebSocket = () => {
423
+ try {
424
+ socket = new WebSocket(`${socketUrl}?sessionId=${sessionId}`);
445
425
 
446
- // Listen for auth code from socket.io (for unclaimed sessions)
447
- // Only display auth code if this is a new session (not when reusing existing session)
448
- socket.on('session-auth-code', (data) => {
449
- if (data.sessionId === sessionId && data.authCode) {
450
- // Update received auth code
451
- if (!receivedAuthCode || receivedAuthCode !== data.authCode) {
452
- receivedAuthCode = data.authCode;
453
- // Only display auth code for new sessions, not when reusing existing sessions
454
- if (isNewSession) {
455
- console.log(` 🔑 Auth Code: ${receivedAuthCode}`);
456
- console.log(` 📋 Dashboard: ${webUrl}/${sessionId}?auth=${receivedAuthCode}\n`);
457
- }
458
- }
459
- }
460
- });
426
+ socket.onopen = () => {
427
+ isConnected = true;
428
+ console.log(' Connected to server\n');
429
+ reconnectAttempts = 0;
461
430
 
462
- socket.on('reconnect', (attemptNumber) => {
463
- console.log(` ↻ Reconnected (attempt ${attemptNumber})\n`);
464
- isConnected = true;
465
- // Rejoin session after reconnection
466
- socket.emit('join-session', sessionId);
467
- setTimeout(() => {
468
- hasJoinedSession = true;
469
- // Process any queued logs
470
- while (logQueue.length > 0) {
471
- const logData = logQueue.shift();
472
- socket.emit('cli-emit', {
431
+ // Join session
432
+ socket.send(JSON.stringify({
433
+ type: 'join-session',
473
434
  sessionId,
474
- ...logData,
475
- });
476
- }
477
- }, 100);
478
- });
479
-
480
- socket.on('reconnect_attempt', (attemptNumber) => {
481
- // Silent reconnection attempts - don't spam console
482
- });
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
+ };
483
449
 
484
- socket.on('reconnect_error', (error) => {
485
- // Silent reconnection errors - will keep trying
486
- });
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
+ };
487
509
 
488
- socket.on('reconnect_failed', () => {
489
- console.error(' ✗ Failed to reconnect after all attempts');
490
- console.error(' Stream will continue, but logs may be lost until reconnection\n');
491
- });
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
+ };
492
517
 
493
- socket.on('connect_error', (error) => {
494
- // Don't exit on first connection error - allow reconnection
495
- if (!isConnected) {
496
- console.error(' ✗ Connection error:', error.message);
497
- 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);
498
540
  }
499
- });
541
+ };
500
542
 
501
- socket.on('disconnect', (reason) => {
502
- isConnected = false;
503
- hasJoinedSession = false;
504
- // Don't exit - allow reconnection
505
- if (reason === 'io server disconnect') {
506
- // 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;
507
549
  }
508
- });
509
550
 
510
- // Handle rate limit errors from server
511
- socket.on('rate-limit-exceeded', (data) => {
512
- console.error('\n ⚠️ Rate Limit Exceeded');
513
- console.error(` ${data.message || 'Too many requests. Please try again later.'}`);
514
- if (data.rateLimit) {
515
- const { limit, remaining, resetAt, windowSeconds } = data.rateLimit;
516
- if (limit !== undefined) {
517
- console.error(` Limit: ${limit} requests`);
518
- }
519
- if (remaining !== undefined) {
520
- console.error(` Remaining: ${remaining} requests`);
521
- }
522
- if (resetAt) {
523
- const resetDate = new Date(resetAt);
524
- const now = new Date();
525
- const secondsUntilReset = Math.ceil((resetDate - now) / 1000);
526
- if (secondsUntilReset > 0) {
527
- 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('');
528
586
  }
529
587
  }
588
+ } catch (error) {
589
+ console.error(' ✗ Error sending log:', error.message);
530
590
  }
531
- console.error('');
532
- // Don't exit - let user decide, but clear the queue
533
- logQueue.length = 0;
534
- });
591
+ };
535
592
 
536
- // Handle general errors from server
537
- socket.on('error', (data) => {
538
- // Check if it's a history limit error
539
- if (data && data.error === 'History Limit Reached') {
540
- console.error('\n 🚫 History Limit Reached');
541
- console.error(` ${data.message || 'Session history limit reached'}`);
542
- if (data.limit !== undefined && data.current !== undefined) {
543
- console.error(` Current: ${data.current} / ${data.limit} logs`);
544
- }
545
- if (data.upgradeRequired) {
546
- console.error(' 💡 Upgrade to Pro to unlock 30 days retention');
547
- console.error(' 🌐 Visit: https://vibex.sh/pricing');
548
- }
549
- console.error('');
550
- // Clear the queue and stop processing
551
- logQueue.length = 0;
552
- hasJoinedSession = false; // Prevent further logs from being sent
553
- return;
554
- }
555
-
556
- // Handle other errors
557
- console.error('\n ✗ Server Error');
558
- if (typeof data === 'string') {
559
- console.error(` ${data}`);
560
- } else if (data && data.message) {
561
- console.error(` ${data.message}`);
562
- if (data.error) {
563
- console.error(` Error: ${data.error}`);
564
- }
565
- } else {
566
- console.error(' An unexpected error occurred');
567
- }
568
- console.error('');
569
- });
593
+ // Start WebSocket connection
594
+ connectWebSocket();
570
595
 
571
596
  const rl = readline.createInterface({
572
597
  input: process.stdin,
@@ -596,29 +621,25 @@ async function main() {
596
621
  };
597
622
  }
598
623
 
599
- // If connected and joined session, send immediately; otherwise queue it
600
- if (isConnected && hasJoinedSession && socket.connected) {
601
- socket.emit('cli-emit', {
602
- sessionId,
603
- ...logData,
604
- });
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);
605
628
  } else {
606
629
  logQueue.push(logData);
607
630
  }
608
631
  });
609
632
 
610
633
  rl.on('close', () => {
611
- // Wait for connection and queued logs to be sent
634
+ // Wait for queued logs to be sent
612
635
  const waitForQueue = () => {
613
- if (logQueue.length === 0 || (!isConnected && logQueue.length > 0)) {
614
- // If not connected and we have queued logs, wait a bit more
615
- if (!isConnected && logQueue.length > 0) {
616
- setTimeout(waitForQueue, 200);
617
- return;
618
- }
636
+ if (logQueue.length === 0) {
619
637
  console.log('\n Stream ended. Closing connection...\n');
620
- if (socket.connected) {
621
- socket.disconnect();
638
+ if (socket && socket.readyState === 1) { // WebSocket.OPEN = 1
639
+ socket.close(1000, 'Stream ended');
640
+ }
641
+ if (reconnectTimeout) {
642
+ clearTimeout(reconnectTimeout);
622
643
  }
623
644
  setTimeout(() => process.exit(0), 100);
624
645
  } else {
@@ -631,7 +652,12 @@ async function main() {
631
652
 
632
653
  process.on('SIGINT', () => {
633
654
  console.log('\n Interrupted. Closing connection...\n');
634
- socket.disconnect();
655
+ if (socket && socket.readyState === 1) { // WebSocket.OPEN = 1
656
+ socket.close(1000, 'Interrupted');
657
+ }
658
+ if (reconnectTimeout) {
659
+ clearTimeout(reconnectTimeout);
660
+ }
635
661
  process.exit(0);
636
662
  });
637
663
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibex-sh",
3
- "version": "0.2.4",
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,7 +27,6 @@
27
27
  "homepage": "https://vibex.sh",
28
28
  "dependencies": {
29
29
  "commander": "^11.1.0",
30
- "socket.io-client": "^4.7.2",
31
- "vibex-sh": "^0.2.3"
30
+ "ws": "^8.16.0"
32
31
  }
33
32
  }