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 +141 -6
- package/package.json +1 -1
- package/supervisor/widget.js +13 -8
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
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
1658
|
-
|
|
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
package/supervisor/widget.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
658
|
-
|
|
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
|