vibex-sh 0.2.4 → 0.9.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.
Files changed (2) hide show
  1. package/index.js +194 -166
  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.prop.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
 
@@ -110,9 +112,11 @@ function getUrls(options) {
110
112
  }
111
113
 
112
114
  // Priority 5: Production defaults
115
+ // Use Worker WebSocket endpoint instead of old Socket.io server
116
+ const defaultWorkerUrl = process.env.VIBEX_WORKER_URL || 'https://vibex-ingest.prop.workers.dev';
113
117
  return {
114
118
  webUrl: 'https://vibex.sh',
115
- socketUrl: socket || 'https://socket.vibex.sh',
119
+ socketUrl: socket || defaultWorkerUrl.replace('https://', 'wss://').replace('http://', 'ws://'),
116
120
  };
117
121
  }
118
122
 
@@ -403,20 +407,13 @@ async function main() {
403
407
  console.log(` 🔍 Sending logs to session: ${sessionId}\n`);
404
408
  }
405
409
 
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
-
410
+ let socket = null;
417
411
  let isConnected = false;
418
412
  let hasJoinedSession = false;
419
413
  const logQueue = [];
414
+ let reconnectTimeout = null;
415
+ let reconnectAttempts = 0;
416
+ const maxReconnectDelay = 5000;
420
417
 
421
418
  // Store auth code received from socket
422
419
  let receivedAuthCode = authCode;
@@ -424,149 +421,179 @@ async function main() {
424
421
  // Track if this is a new session (not reusing an existing one)
425
422
  const isNewSession = !options.sessionId;
426
423
 
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
- });
424
+ const connectWebSocket = () => {
425
+ try {
426
+ socket = new WebSocket(`${socketUrl}?sessionId=${sessionId}`);
445
427
 
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
- });
428
+ socket.onopen = () => {
429
+ isConnected = true;
430
+ console.log(' Connected to server\n');
431
+ reconnectAttempts = 0;
461
432
 
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', {
433
+ // Join session
434
+ socket.send(JSON.stringify({
435
+ type: 'join-session',
473
436
  sessionId,
474
- ...logData,
475
- });
476
- }
477
- }, 100);
478
- });
479
-
480
- socket.on('reconnect_attempt', (attemptNumber) => {
481
- // Silent reconnection attempts - don't spam console
482
- });
437
+ }));
438
+
439
+ // Wait a bit for join-session to be processed
440
+ setTimeout(() => {
441
+ hasJoinedSession = true;
442
+ // Process any queued logs
443
+ while (logQueue.length > 0) {
444
+ const logData = logQueue.shift();
445
+ // Send logs via HTTP POST (non-blocking) instead of WebSocket
446
+ // WebSocket is only for receiving logs
447
+ sendLogViaHTTP(logData);
448
+ }
449
+ }, 100);
450
+ };
483
451
 
484
- socket.on('reconnect_error', (error) => {
485
- // Silent reconnection errors - will keep trying
486
- });
452
+ socket.onmessage = (event) => {
453
+ try {
454
+ const message = JSON.parse(event.data);
455
+
456
+ switch (message.type) {
457
+ case 'join-session-ack':
458
+ console.log(' ✓ Joined session\n');
459
+ break;
460
+
461
+ case 'session-auth-code':
462
+ if (message.data && message.data.sessionId === sessionId && message.data.authCode) {
463
+ if (!receivedAuthCode || receivedAuthCode !== message.data.authCode) {
464
+ receivedAuthCode = message.data.authCode;
465
+ if (isNewSession) {
466
+ console.log(` 🔑 Auth Code: ${receivedAuthCode}`);
467
+ console.log(` 📋 Dashboard: ${webUrl}/${sessionId}?auth=${receivedAuthCode}\n`);
468
+ }
469
+ }
470
+ }
471
+ break;
472
+
473
+ case 'log':
474
+ // Logs are received via WebSocket but sent via HTTP
475
+ break;
476
+
477
+ case 'error':
478
+ if (message.error === 'Rate Limit Exceeded') {
479
+ console.error('\n ⚠️ Rate Limit Exceeded');
480
+ console.error(` ${message.message || 'Too many requests. Please try again later.'}`);
481
+ console.error('');
482
+ logQueue.length = 0;
483
+ } else if (message.error === 'History Limit Reached') {
484
+ console.error('\n 🚫 History Limit Reached');
485
+ console.error(` ${message.message || 'Session history limit reached'}`);
486
+ if (message.limit !== undefined && message.current !== undefined) {
487
+ console.error(` Current: ${message.current} / ${message.limit} logs`);
488
+ }
489
+ if (message.upgradeRequired) {
490
+ console.error(' 💡 Upgrade to Pro to unlock 30 days retention');
491
+ console.error(' 🌐 Visit: https://vibex.sh/pricing');
492
+ }
493
+ console.error('');
494
+ logQueue.length = 0;
495
+ hasJoinedSession = false;
496
+ } else {
497
+ console.error('\n ✗ Server Error');
498
+ console.error(` ${message.error || message.message || 'An unexpected error occurred'}`);
499
+ console.error('');
500
+ }
501
+ break;
502
+
503
+ default:
504
+ // Ignore unknown message types
505
+ break;
506
+ }
507
+ } catch (error) {
508
+ console.error(' ✗ Error parsing message:', error.message);
509
+ }
510
+ };
487
511
 
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
- });
512
+ socket.onerror = (error) => {
513
+ if (!isConnected) {
514
+ console.error(` Connection error: ${error.message || 'websocket error'}`);
515
+ console.error(` ↻ Trying to connect to: ${socketUrl}`);
516
+ console.error(' ↻ Retrying connection...\n');
517
+ }
518
+ };
492
519
 
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');
520
+ socket.onclose = (event) => {
521
+ isConnected = false;
522
+ hasJoinedSession = false;
523
+
524
+ // Reconnect logic
525
+ if (event.code !== 1000) { // Not a normal closure
526
+ const delay = Math.min(1000 * Math.pow(2, reconnectAttempts), maxReconnectDelay);
527
+ reconnectAttempts++;
528
+ console.log(` ↻ Reconnecting in ${delay}ms (attempt ${reconnectAttempts})...\n`);
529
+
530
+ reconnectTimeout = setTimeout(() => {
531
+ connectWebSocket();
532
+ }, delay);
533
+ }
534
+ };
535
+ } catch (error) {
536
+ console.error(` ✗ Error creating WebSocket: ${error.message}`);
537
+ console.error(` ↻ URL: ${socketUrl}`);
538
+ // Retry connection
539
+ reconnectTimeout = setTimeout(() => {
540
+ connectWebSocket();
541
+ }, 1000);
498
542
  }
499
- });
543
+ };
500
544
 
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
545
+ // Send logs via HTTP POST (non-blocking, same as SDKs)
546
+ // Use Cloudflare Worker endpoint (port 8787 for local, or Worker URL for production)
547
+ const sendLogViaHTTP = async (logData) => {
548
+ if (!token) {
549
+ console.error(' ✗ No token available for sending logs');
550
+ return;
507
551
  }
508
- });
509
552
 
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`);
553
+ try {
554
+ // For local development, use Workers dev server (port 8787)
555
+ // For production, use Cloudflare Worker URL
556
+ const ingestUrl = webUrl.includes('localhost') || webUrl.includes('127.0.0.1')
557
+ ? 'http://localhost:8787/api/v1/ingest'
558
+ : process.env.VIBEX_WORKER_URL
559
+ ? `${process.env.VIBEX_WORKER_URL}/api/v1/ingest`
560
+ : `${webUrl}/api/v1/ingest`; // Fallback to web URL (should be proxied)
561
+
562
+ const response = await fetch(ingestUrl, {
563
+ method: 'POST',
564
+ headers: {
565
+ 'Authorization': `Bearer ${token}`,
566
+ 'Content-Type': 'application/json',
567
+ },
568
+ body: JSON.stringify({
569
+ sessionId,
570
+ logs: [logData],
571
+ }),
572
+ });
573
+
574
+ if (!response.ok) {
575
+ const errorData = await response.json().catch(() => ({}));
576
+ if (response.status === 429) {
577
+ console.error('\n ⚠️ Rate Limit Exceeded');
578
+ console.error(` ${errorData.message || 'Too many requests. Please try again later.'}`);
579
+ console.error('');
580
+ } else if (response.status === 403 && errorData.message?.includes('History Limit')) {
581
+ console.error('\n 🚫 History Limit Reached');
582
+ console.error(` ${errorData.message || 'Session history limit reached'}`);
583
+ if (errorData.upgradeRequired) {
584
+ console.error(' 💡 Upgrade to Pro to unlock 30 days retention');
585
+ console.error(' 🌐 Visit: https://vibex.sh/pricing');
586
+ }
587
+ console.error('');
528
588
  }
529
589
  }
590
+ } catch (error) {
591
+ console.error(' ✗ Error sending log:', error.message);
530
592
  }
531
- console.error('');
532
- // Don't exit - let user decide, but clear the queue
533
- logQueue.length = 0;
534
- });
593
+ };
535
594
 
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
- });
595
+ // Start WebSocket connection
596
+ connectWebSocket();
570
597
 
571
598
  const rl = readline.createInterface({
572
599
  input: process.stdin,
@@ -596,29 +623,25 @@ async function main() {
596
623
  };
597
624
  }
598
625
 
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
- });
626
+ // Send logs via HTTP POST (non-blocking, same as SDKs)
627
+ // WebSocket is only for receiving logs and auth codes
628
+ if (hasJoinedSession) {
629
+ sendLogViaHTTP(logData);
605
630
  } else {
606
631
  logQueue.push(logData);
607
632
  }
608
633
  });
609
634
 
610
635
  rl.on('close', () => {
611
- // Wait for connection and queued logs to be sent
636
+ // Wait for queued logs to be sent
612
637
  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
- }
638
+ if (logQueue.length === 0) {
619
639
  console.log('\n Stream ended. Closing connection...\n');
620
- if (socket.connected) {
621
- socket.disconnect();
640
+ if (socket && socket.readyState === 1) { // WebSocket.OPEN = 1
641
+ socket.close(1000, 'Stream ended');
642
+ }
643
+ if (reconnectTimeout) {
644
+ clearTimeout(reconnectTimeout);
622
645
  }
623
646
  setTimeout(() => process.exit(0), 100);
624
647
  } else {
@@ -631,7 +654,12 @@ async function main() {
631
654
 
632
655
  process.on('SIGINT', () => {
633
656
  console.log('\n Interrupted. Closing connection...\n');
634
- socket.disconnect();
657
+ if (socket && socket.readyState === 1) { // WebSocket.OPEN = 1
658
+ socket.close(1000, 'Interrupted');
659
+ }
660
+ if (reconnectTimeout) {
661
+ clearTimeout(reconnectTimeout);
662
+ }
635
663
  process.exit(0);
636
664
  });
637
665
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibex-sh",
3
- "version": "0.2.4",
3
+ "version": "0.9.1",
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
  }