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.
- package/index.js +194 -166
- 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.prop.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
|
|
|
@@ -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://
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
});
|
|
424
|
+
const connectWebSocket = () => {
|
|
425
|
+
try {
|
|
426
|
+
socket = new WebSocket(`${socketUrl}?sessionId=${sessionId}`);
|
|
445
427
|
|
|
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
|
-
});
|
|
428
|
+
socket.onopen = () => {
|
|
429
|
+
isConnected = true;
|
|
430
|
+
console.log(' ✓ Connected to server\n');
|
|
431
|
+
reconnectAttempts = 0;
|
|
461
432
|
|
|
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', {
|
|
433
|
+
// Join session
|
|
434
|
+
socket.send(JSON.stringify({
|
|
435
|
+
type: 'join-session',
|
|
473
436
|
sessionId,
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
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
|
-
|
|
485
|
-
|
|
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
|
-
|
|
489
|
-
|
|
490
|
-
|
|
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
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
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
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
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
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
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
|
-
|
|
532
|
-
// Don't exit - let user decide, but clear the queue
|
|
533
|
-
logQueue.length = 0;
|
|
534
|
-
});
|
|
593
|
+
};
|
|
535
594
|
|
|
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
|
-
});
|
|
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
|
-
//
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
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
|
|
636
|
+
// Wait for queued logs to be sent
|
|
612
637
|
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
|
-
}
|
|
638
|
+
if (logQueue.length === 0) {
|
|
619
639
|
console.log('\n Stream ended. Closing connection...\n');
|
|
620
|
-
if (socket.
|
|
621
|
-
socket.
|
|
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.
|
|
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.
|
|
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
|
-
"
|
|
31
|
-
"vibex-sh": "^0.2.3"
|
|
30
|
+
"ws": "^8.16.0"
|
|
32
31
|
}
|
|
33
32
|
}
|