bloby-bot 0.22.11 → 0.22.12

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/cli.js CHANGED
@@ -714,6 +714,72 @@ function writeVersionFile(version) {
714
714
  try { fs.writeFileSync(path.join(DATA_DIR, 'VERSION'), version); } catch {}
715
715
  }
716
716
 
717
+ /**
718
+ * Poll health endpoint and config.json until the daemon is ready.
719
+ * Advances the stepper through tunnel + verification steps.
720
+ */
721
+ async function waitForDaemonHealth(stepper, config, hasTunnel) {
722
+ const port = config.port || 7400;
723
+ const relayUrl = config.relay?.url || null;
724
+ let tunnelUrl = null;
725
+ let tunnelShown = false;
726
+ let healthOk = false;
727
+
728
+ // Clear any stale tunnelUrl from config so we detect the fresh one
729
+ const oldTunnelUrl = config.tunnelUrl || null;
730
+
731
+ for (let i = 0; i < 120; i++) {
732
+ await new Promise(r => setTimeout(r, 1000));
733
+
734
+ // Check for new tunnel URL in config
735
+ if (hasTunnel && !tunnelShown) {
736
+ try {
737
+ const cfg = JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf-8'));
738
+ if (cfg.tunnelUrl && cfg.tunnelUrl !== oldTunnelUrl) {
739
+ tunnelUrl = cfg.tunnelUrl;
740
+ stepper.advance(); // Connecting tunnel done
741
+ if (relayUrl) {
742
+ stepper.setInfo([
743
+ ` ${c.dim}Waiting for ${c.reset}${c.white}${relayUrl.replace('https://', '')}${c.reset}${c.dim} to become reachable (can take up to 2 min)${c.reset}`,
744
+ ` ${c.dim}In the meanwhile you can access:${c.reset} ${c.blue}${link(tunnelUrl)}${c.reset}`,
745
+ ]);
746
+ }
747
+ tunnelShown = true;
748
+ }
749
+ } catch {}
750
+ }
751
+
752
+ // Check health
753
+ try {
754
+ const res = await fetch(`http://127.0.0.1:${port}/api/health`);
755
+ if (res.ok) {
756
+ healthOk = true;
757
+ break;
758
+ }
759
+ } catch {}
760
+ }
761
+
762
+ // If tunnel never came up, advance past tunnel step anyway
763
+ if (hasTunnel && !tunnelShown) {
764
+ stepper.advance();
765
+ }
766
+
767
+ if (tunnelShown) {
768
+ stepper.setInfo([]);
769
+ }
770
+
771
+ // Advance past "Verifying connection"
772
+ stepper.advance();
773
+
774
+ // Re-read config for final URLs
775
+ try {
776
+ const cfg = JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf-8'));
777
+ if (cfg.tunnelUrl) tunnelUrl = cfg.tunnelUrl;
778
+ } catch {}
779
+
780
+ return { tunnelUrl, relayUrl, healthOk };
781
+ }
782
+
717
783
  // ── Steps ──
718
784
 
719
785
  function createConfig() {
@@ -1289,13 +1355,19 @@ async function update() {
1289
1355
  const selfUpdate = !!process.env.BLOBY_SELF_UPDATE;
1290
1356
  const daemonWasRunning = !selfUpdate && isDaemonInstalled() && isDaemonActive();
1291
1357
 
1358
+ const updateConfig = fs.existsSync(CONFIG_PATH) ? JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf-8')) : {};
1359
+ const updateTunnelMode = updateConfig.tunnel?.mode ?? (updateConfig.tunnel?.enabled === false ? 'off' : 'quick');
1360
+ const updateHasTunnel = updateTunnelMode !== 'off';
1361
+
1292
1362
  const steps = [
1293
1363
  'Downloading update',
1294
1364
  ...(daemonWasRunning ? ['Stopping daemon'] : []),
1295
1365
  'Updating files',
1296
1366
  'Installing dependencies',
1297
1367
  'Building interface',
1298
- ...(daemonWasRunning ? ['Restarting daemon'] : []),
1368
+ ...(daemonWasRunning
1369
+ ? ['Restarting daemon', ...(updateHasTunnel ? ['Connecting tunnel', 'Verifying connection'] : ['Verifying connection'])]
1370
+ : []),
1299
1371
  ];
1300
1372
 
1301
1373
  const stepper = new Stepper(steps);
@@ -1410,6 +1482,7 @@ async function update() {
1410
1482
  fs.rmSync(tmpDir, { recursive: true, force: true });
1411
1483
 
1412
1484
  // Restart daemon if it was running (skipped during self-update)
1485
+ let updateResult = null;
1413
1486
  if (daemonWasRunning) {
1414
1487
  try {
1415
1488
  if (PLATFORM === 'darwin') {
@@ -1419,7 +1492,10 @@ async function update() {
1419
1492
  execSync(cmd, { stdio: 'ignore' });
1420
1493
  }
1421
1494
  } catch {}
1422
- stepper.advance();
1495
+ stepper.advance(); // Restarting daemon done
1496
+
1497
+ // Wait for daemon to become healthy and tunnel to connect
1498
+ updateResult = await waitForDaemonHealth(stepper, updateConfig, updateHasTunnel);
1423
1499
  }
1424
1500
 
1425
1501
  stepper.finish();
@@ -1442,7 +1518,9 @@ async function update() {
1442
1518
  }
1443
1519
 
1444
1520
  if (daemonWasRunning) {
1445
- if (isDaemonActive()) {
1521
+ if (updateResult && updateResult.tunnelUrl && updateHasTunnel) {
1522
+ finalMessage(updateResult.tunnelUrl, updateResult.relayUrl);
1523
+ } else if (updateResult && updateResult.healthOk) {
1446
1524
  console.log(` ${c.blue}✔${c.reset} Daemon restarted with new version.\n`);
1447
1525
  } else {
1448
1526
  console.log(` ${c.yellow}⚠${c.reset} Daemon may still be starting. Check ${c.pink}bloby daemon status${c.reset}\n`);
@@ -1544,9 +1622,37 @@ async function daemon(sub) {
1544
1622
  console.log(`\n ${c.yellow}⚠${c.reset} Daemon not installed. Run ${c.pink}bloby daemon install${c.reset} first.\n`);
1545
1623
  process.exit(1);
1546
1624
  }
1625
+
1626
+ banner();
1627
+
1628
+ const restartConfig = fs.existsSync(CONFIG_PATH) ? JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf-8')) : {};
1629
+ const restartTunnelMode = restartConfig.tunnel?.mode ?? (restartConfig.tunnel?.enabled === false ? 'off' : 'quick');
1630
+ const restartHasTunnel = restartTunnelMode !== 'off';
1631
+
1632
+ const restartSteps = [
1633
+ 'Stopping daemon',
1634
+ 'Starting daemon',
1635
+ ...(restartHasTunnel ? ['Connecting tunnel', 'Verifying connection'] : ['Verifying connection']),
1636
+ ];
1637
+ const restartStepper = new Stepper(restartSteps);
1638
+ restartStepper.start();
1639
+
1547
1640
  try { execSync(`launchctl unload "${LAUNCHD_PLIST_PATH}" 2>/dev/null`, { stdio: 'ignore' }); } catch {}
1641
+ restartStepper.advance(); // Stopping daemon done
1642
+
1548
1643
  execSync(`launchctl load "${LAUNCHD_PLIST_PATH}"`, { stdio: 'ignore' });
1549
- console.log(`\n ${c.blue}✔${c.reset} Bloby daemon restarted.\n`);
1644
+ restartStepper.advance(); // Starting daemon done
1645
+
1646
+ const restartResult = await waitForDaemonHealth(restartStepper, restartConfig, restartHasTunnel);
1647
+ restartStepper.finish();
1648
+
1649
+ if (!restartHasTunnel) {
1650
+ privateNetworkMessage(restartConfig.port);
1651
+ } else if (restartResult.tunnelUrl) {
1652
+ finalMessage(restartResult.tunnelUrl, restartResult.relayUrl);
1653
+ } else {
1654
+ console.log(`\n ${c.blue}✔${c.reset} Bloby daemon restarted.\n`);
1655
+ }
1550
1656
  break;
1551
1657
  }
1552
1658
 
@@ -1654,8 +1760,37 @@ async function daemon(sub) {
1654
1760
 
1655
1761
  case 'restart': {
1656
1762
  if (needsSudo()) sudoReExec();
1657
- execSync(`systemctl restart ${SERVICE_NAME}`, { stdio: 'inherit' });
1658
- console.log(`\n ${c.blue}✔${c.reset} Bloby daemon restarted.\n`);
1763
+
1764
+ banner();
1765
+
1766
+ const sysRestartConfig = fs.existsSync(CONFIG_PATH) ? JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf-8')) : {};
1767
+ const sysRestartTunnelMode = sysRestartConfig.tunnel?.mode ?? (sysRestartConfig.tunnel?.enabled === false ? 'off' : 'quick');
1768
+ const sysRestartHasTunnel = sysRestartTunnelMode !== 'off';
1769
+
1770
+ const sysRestartSteps = [
1771
+ 'Stopping daemon',
1772
+ 'Starting daemon',
1773
+ ...(sysRestartHasTunnel ? ['Connecting tunnel', 'Verifying connection'] : ['Verifying connection']),
1774
+ ];
1775
+ const sysRestartStepper = new Stepper(sysRestartSteps);
1776
+ sysRestartStepper.start();
1777
+
1778
+ execSync(`systemctl stop ${SERVICE_NAME}`, { stdio: 'ignore' });
1779
+ sysRestartStepper.advance(); // Stopping daemon done
1780
+
1781
+ execSync(`systemctl start ${SERVICE_NAME}`, { stdio: 'ignore' });
1782
+ sysRestartStepper.advance(); // Starting daemon done
1783
+
1784
+ const sysRestartResult = await waitForDaemonHealth(sysRestartStepper, sysRestartConfig, sysRestartHasTunnel);
1785
+ sysRestartStepper.finish();
1786
+
1787
+ if (!sysRestartHasTunnel) {
1788
+ privateNetworkMessage(sysRestartConfig.port);
1789
+ } else if (sysRestartResult.tunnelUrl) {
1790
+ finalMessage(sysRestartResult.tunnelUrl, sysRestartResult.relayUrl);
1791
+ } else {
1792
+ console.log(`\n ${c.blue}✔${c.reset} Bloby daemon restarted.\n`);
1793
+ }
1659
1794
  break;
1660
1795
  }
1661
1796
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bloby-bot",
3
- "version": "0.22.11",
3
+ "version": "0.22.12",
4
4
  "releaseNotes": [
5
5
  "1. testing chrome extension",
6
6
  "2. ",
@@ -89,7 +89,7 @@
89
89
  var HP_HOLD_MS = 500;
90
90
 
91
91
  // ── Bubble CSS (shared) ──
92
- var BUBBLE_CSS = 'position:fixed;bottom:' + BUBBLE_MARGIN + 'px;right:' + BUBBLE_MARGIN + 'px;width:' + BUBBLE_SIZE + 'px;height:' + BUBBLE_SIZE + 'px;z-index:99998;cursor:pointer;border-radius:50%;-webkit-tap-highlight-color:transparent;touch-action:none;';
92
+ var BUBBLE_CSS = 'position:fixed;bottom:' + BUBBLE_MARGIN + 'px;right:' + BUBBLE_MARGIN + 'px;width:' + BUBBLE_SIZE + 'px;height:' + BUBBLE_SIZE + 'px;z-index:99998;cursor:pointer;border-radius:50%;-webkit-tap-highlight-color:transparent;touch-action:none;-webkit-user-select:none;user-select:none;-webkit-touch-callout:none;';
93
93
 
94
94
  // ── Easing ──
95
95
  function easeInOutCubic(t) { return t < 0.5 ? 4*t*t*t : 1 - Math.pow(-2*t + 2, 3) / 2; }
@@ -622,7 +622,7 @@
622
622
  bubble.addEventListener('pointerdown', function(e) {
623
623
  console.log('[widget] pointerdown', { canvasPhase: canvasPhase, hpState: hpState, type: e.pointerType });
624
624
  if (canvasPhase !== 'bubble') return;
625
- // NOTE: no preventDefault it suppresses click synthesis on mobile
625
+ e.preventDefault(); // Prevent iOS long-press text selection
626
626
  hpPointerDown = true;
627
627
  hpWasHold = false;
628
628
 
@@ -642,7 +642,12 @@
642
642
  if (hpState === 'activating' || hpState === 'recording') {
643
643
  stopHpRecording(false);
644
644
  }
645
- // Taps are handled by the 'click' event below
645
+ // Tap detection: preventDefault on pointerdown suppresses click on mobile,
646
+ // so we handle taps here instead.
647
+ if (!hpWasHold) {
648
+ toggle();
649
+ }
650
+ hpWasHold = false;
646
651
  });
647
652
 
648
653
  bubble.addEventListener('pointercancel', function() {
@@ -652,12 +657,12 @@
652
657
  if (hpState === 'activating' || hpState === 'recording') stopHpRecording(true);
653
658
  });
654
659
 
655
- bubble.addEventListener('click', function() {
660
+ // click still fires on desktop (pointerdown preventDefault does not suppress it there).
661
+ // On mobile, pointerup already handled the tap, so this is a no-op guard.
662
+ bubble.addEventListener('click', function(e) {
656
663
  console.log('[widget] click', { canvasPhase: canvasPhase, hpWasHold: hpWasHold, hpState: hpState, isOpen: isOpen });
657
- if (!hpWasHold) {
658
- toggle();
659
- }
660
- hpWasHold = false;
664
+ // Prevent double-toggle: pointerup already handled taps.
665
+ e.stopPropagation();
661
666
  });
662
667
 
663
668
  // Click on blob during splash → skip and open