vibelet 1.0.0 → 1.0.2

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/bin/vibelet.mjs CHANGED
@@ -586,6 +586,7 @@ async function stopRunningDaemon(backend) {
586
586
  }
587
587
 
588
588
  function createCompactPairingPayload(pairingPayload) {
589
+ const connections = normalizePairingConnections(pairingPayload);
589
590
  const compactPayload = {
590
591
  t: 'vp',
591
592
  d: pairingPayload.daemonId,
@@ -596,9 +597,170 @@ function createCompactPairingPayload(pairingPayload) {
596
597
  e: pairingPayload.expiresAt,
597
598
  };
598
599
  if (pairingPayload.fallbackHosts) compactPayload.f = pairingPayload.fallbackHosts;
600
+ if (connections.length > 0) {
601
+ compactPayload.o = connections.map((target) => [
602
+ target.kind,
603
+ target.host,
604
+ target.port,
605
+ target.source,
606
+ target.stability,
607
+ ]);
608
+ }
599
609
  return compactPayload;
600
610
  }
601
611
 
612
+ function normalizeHostValue(host) {
613
+ if (typeof host !== 'string') {
614
+ return '';
615
+ }
616
+ return host.trim().replace(/\.$/, '').toLowerCase();
617
+ }
618
+
619
+ function isIpv4Host(host) {
620
+ const parts = host.split('.');
621
+ return parts.length === 4 && parts.every((part) => /^\d+$/.test(part) && Number(part) >= 0 && Number(part) <= 255);
622
+ }
623
+
624
+ function isTailscaleHost(host) {
625
+ if (host.endsWith('.ts.net')) {
626
+ return true;
627
+ }
628
+ if (!isIpv4Host(host)) {
629
+ return false;
630
+ }
631
+ const [first, second] = host.split('.').map(Number);
632
+ return first === 100 && second >= 64 && second <= 127;
633
+ }
634
+
635
+ function isLocalNetworkHost(host) {
636
+ if (host.endsWith('.local')) {
637
+ return true;
638
+ }
639
+ if (!isIpv4Host(host)) {
640
+ return false;
641
+ }
642
+ const [first, second] = host.split('.').map(Number);
643
+ return first === 10
644
+ || (first === 172 && second >= 16 && second <= 31)
645
+ || (first === 192 && second === 168);
646
+ }
647
+
648
+ function isQuickTunnelHost(host) {
649
+ return host.endsWith('.trycloudflare.com');
650
+ }
651
+
652
+ function buildLegacyConnection(host, port, isPrimary) {
653
+ if (isQuickTunnelHost(host)) {
654
+ return {
655
+ kind: 'relay',
656
+ host,
657
+ port,
658
+ source: 'quick_tunnel',
659
+ stability: 'ephemeral',
660
+ };
661
+ }
662
+ if (isTailscaleHost(host)) {
663
+ return {
664
+ kind: 'direct',
665
+ host,
666
+ port,
667
+ source: 'tailscale',
668
+ stability: 'stable',
669
+ };
670
+ }
671
+ if (isLocalNetworkHost(host)) {
672
+ return {
673
+ kind: 'direct',
674
+ host,
675
+ port,
676
+ source: 'local_network',
677
+ stability: 'stable',
678
+ };
679
+ }
680
+ return {
681
+ kind: 'direct',
682
+ host,
683
+ port,
684
+ source: isPrimary ? 'configured_host' : 'fallback',
685
+ stability: 'stable',
686
+ };
687
+ }
688
+
689
+ function normalizePairingConnections(pairingPayload) {
690
+ const explicitConnections = Array.isArray(pairingPayload.connections)
691
+ ? pairingPayload.connections
692
+ .filter((target) => (
693
+ target
694
+ && typeof target.host === 'string'
695
+ && Number.isFinite(target.port)
696
+ && typeof target.source === 'string'
697
+ && typeof target.stability === 'string'
698
+ ))
699
+ .map((target) => ({
700
+ kind: target.kind === 'relay' ? 'relay' : 'direct',
701
+ host: normalizeHostValue(target.host),
702
+ port: Math.floor(Number(target.port)),
703
+ source: target.source,
704
+ stability: target.stability,
705
+ }))
706
+ .filter((target) => target.host && target.port > 0)
707
+ : [];
708
+
709
+ const dedupe = (targets) => {
710
+ const seen = new Set();
711
+ return targets.filter((target) => {
712
+ const key = `${target.host}:${target.port}`;
713
+ if (seen.has(key)) {
714
+ return false;
715
+ }
716
+ seen.add(key);
717
+ return true;
718
+ });
719
+ };
720
+
721
+ if (explicitConnections.length > 0) {
722
+ return dedupe(explicitConnections);
723
+ }
724
+
725
+ const canonicalHost = normalizeHostValue(pairingPayload.canonicalHost) || 'localhost';
726
+ const port = Number.isFinite(pairingPayload.port) && pairingPayload.port > 0
727
+ ? Math.floor(Number(pairingPayload.port))
728
+ : 9876;
729
+ const fallbackHosts = Array.isArray(pairingPayload.fallbackHosts)
730
+ ? pairingPayload.fallbackHosts
731
+ .map((host) => normalizeHostValue(host))
732
+ .filter((host) => host && host !== canonicalHost)
733
+ : [];
734
+
735
+ return dedupe([
736
+ buildLegacyConnection(canonicalHost, port, true),
737
+ ...fallbackHosts.map((host) => buildLegacyConnection(host, port, false)),
738
+ ]);
739
+ }
740
+
741
+ function formatConnectionSourceLabel(source) {
742
+ switch (source) {
743
+ case 'configured_relay':
744
+ return 'Custom Relay';
745
+ case 'quick_tunnel':
746
+ return 'Quick Tunnel';
747
+ case 'configured_host':
748
+ return 'Manual Host';
749
+ case 'tailscale':
750
+ return 'Tailscale';
751
+ case 'local_network':
752
+ return 'LAN';
753
+ case 'fallback':
754
+ default:
755
+ return 'Fallback';
756
+ }
757
+ }
758
+
759
+ function formatConnectionSummary(target) {
760
+ const stabilityLabel = target.stability === 'ephemeral' ? 'ephemeral' : 'stable';
761
+ return `${target.host}:${target.port} [${formatConnectionSourceLabel(target.source)}, ${stabilityLabel}]`;
762
+ }
763
+
602
764
  async function printPairingQr(pairingPayload) {
603
765
  const payload = JSON.stringify(createCompactPairingPayload(pairingPayload));
604
766
  mkdirSync(vibeletDir, { recursive: true });
@@ -622,12 +784,23 @@ async function printPairingQr(pairingPayload) {
622
784
  async function printPairingSummary(existingHealth = null) {
623
785
  const health = existingHealth ?? await waitForHealth();
624
786
  const pairingPayload = await postJson('/pair/open');
787
+ const connections = normalizePairingConnections(pairingPayload);
788
+ const [preferredConnection, ...otherConnections] = connections;
625
789
 
626
790
  process.stdout.write(`Vibelet daemon is ready.\n\n`);
627
791
  process.stdout.write(`Device: ${health.displayName}\n`);
628
792
  process.stdout.write(`Daemon ID: ${health.daemonId}\n`);
629
793
  process.stdout.write(`Host: ${pairingPayload.canonicalHost}\n`);
630
794
  process.stdout.write(`Port: ${pairingPayload.port}\n`);
795
+ if (preferredConnection) {
796
+ process.stdout.write(`Preferred path: ${formatConnectionSummary(preferredConnection)}\n`);
797
+ }
798
+ if (otherConnections.length > 0) {
799
+ process.stdout.write(`Other paths:\n`);
800
+ otherConnections.forEach((target) => {
801
+ process.stdout.write(` - ${formatConnectionSummary(target)}\n`);
802
+ });
803
+ }
631
804
  process.stdout.write(`Paired devices: ${health.pairedDevices}\n`);
632
805
  await printPairingQr(pairingPayload);
633
806
  }