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.
- package/index.js +191 -165
- 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,
|
|
62
|
+
// For localhost, use Workers dev server
|
|
63
63
|
if (url.hostname === 'localhost' || url.hostname === '127.0.0.1') {
|
|
64
|
-
|
|
65
|
-
return `${url.protocol}//${url.hostname}:${port}`;
|
|
64
|
+
return 'ws://localhost:8787';
|
|
66
65
|
}
|
|
67
|
-
// For vibex.sh domains, use
|
|
66
|
+
// For vibex.sh domains, use Workers WebSocket endpoint
|
|
68
67
|
else if (url.hostname.includes('vibex.sh')) {
|
|
69
|
-
|
|
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,
|
|
72
|
+
// For other domains, derive from web URL
|
|
72
73
|
else {
|
|
73
|
-
|
|
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 || '
|
|
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
|
-
|
|
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
|
-
|
|
428
|
-
|
|
429
|
-
|
|
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
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
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
|
-
|
|
463
|
-
|
|
464
|
-
|
|
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
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
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
|
-
|
|
485
|
-
|
|
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
|
-
|
|
489
|
-
|
|
490
|
-
|
|
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
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
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
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
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
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
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
|
-
|
|
532
|
-
// Don't exit - let user decide, but clear the queue
|
|
533
|
-
logQueue.length = 0;
|
|
534
|
-
});
|
|
591
|
+
};
|
|
535
592
|
|
|
536
|
-
//
|
|
537
|
-
|
|
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
|
-
//
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
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
|
|
634
|
+
// Wait for queued logs to be sent
|
|
612
635
|
const waitForQueue = () => {
|
|
613
|
-
if (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.
|
|
621
|
-
socket.
|
|
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.
|
|
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.
|
|
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
|
-
"
|
|
31
|
-
"vibex-sh": "^0.2.3"
|
|
30
|
+
"ws": "^8.16.0"
|
|
32
31
|
}
|
|
33
32
|
}
|