@lifeaitools/clauth 1.4.8 → 1.5.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.
@@ -1217,8 +1217,25 @@ async function loadServices() {
1217
1217
  err.style.display = "none";
1218
1218
  grid.innerHTML = '<p class="loading">Loading…</p>';
1219
1219
 
1220
+ let status;
1221
+ for (let attempt = 0; attempt < 3; attempt++) {
1222
+ try {
1223
+ status = await Promise.race([
1224
+ fetch(BASE + "/status").then(r => r.json()),
1225
+ new Promise((_, reject) => setTimeout(() => reject(new Error("timeout")), 8000))
1226
+ ]);
1227
+ break;
1228
+ } catch (e) {
1229
+ if (attempt === 2) {
1230
+ err.textContent = "⚠ Could not load services — " + e.message;
1231
+ err.style.display = "block";
1232
+ grid.innerHTML = "";
1233
+ return;
1234
+ }
1235
+ await new Promise(r => setTimeout(r, 1000));
1236
+ }
1237
+ }
1220
1238
  try {
1221
- const status = await fetch(BASE + "/status").then(r => r.json());
1222
1239
  if (status.locked) { showLockScreen(); return; }
1223
1240
  if (status.error) throw new Error(status.error);
1224
1241
 
@@ -1758,8 +1775,16 @@ let tunnelPollTimer = null;
1758
1775
 
1759
1776
  async function pollTunnel() {
1760
1777
  if (tunnelPollTimer) clearInterval(tunnelPollTimer);
1778
+ // Give server 4s grace to run fetchTunnelConfig before showing "not_started" UI
1761
1779
  const r = await apiFetch("/tunnel");
1762
- if (r) renderTunnelPanel(r.status, r.url, r.error);
1780
+ if (r && r.status !== "not_started") {
1781
+ renderTunnelPanel(r.status, r.url, r.error);
1782
+ } else {
1783
+ renderTunnelPanel("starting", null, null); // show spinner while server initialises
1784
+ await new Promise(res => setTimeout(res, 4000));
1785
+ const r2 = await apiFetch("/tunnel");
1786
+ if (r2) renderTunnelPanel(r2.status, r2.url, r2.error);
1787
+ }
1763
1788
  // Poll every 3s; slow to 10s once live
1764
1789
  tunnelPollTimer = setInterval(async () => {
1765
1790
  const r = await apiFetch("/tunnel");
@@ -2440,7 +2465,11 @@ function createServer(initPassword, whitelist, port, tunnelHostnameInit = null,
2440
2465
  }
2441
2466
 
2442
2467
  async function startTunnel() {
2443
- if (tunnelProc) return; // already running
2468
+ if (tunnelProc) {
2469
+ // Already running — ensure status reflects reality (caller may have reset it to "starting")
2470
+ tunnelStatus = "live";
2471
+ return;
2472
+ }
2444
2473
  tunnelUrl = null;
2445
2474
  tunnelError = null;
2446
2475
 
@@ -3408,7 +3437,10 @@ function createServer(initPassword, whitelist, port, tunnelHostnameInit = null,
3408
3437
  try {
3409
3438
  const pwEscaped = pw.replace(/'/g, "''");
3410
3439
  const psExpr = `[Convert]::ToBase64String([Security.Cryptography.ProtectedData]::Protect([Text.Encoding]::UTF8.GetBytes('${pwEscaped}'),$null,'CurrentUser'))`;
3411
- const encrypted = execSyncTop(`powershell -NoProfile -Command "${psExpr}"`, { encoding: "utf8", timeout: 5000 }).trim();
3440
+ const psExe = process.env.SystemRoot
3441
+ ? `${process.env.SystemRoot}\\System32\\WindowsPowerShell\\v1.0\\powershell.exe`
3442
+ : "powershell.exe";
3443
+ const encrypted = execSyncTop(`"${psExe}" -NoProfile -Command "${psExpr}"`, { encoding: "utf8", timeout: 5000 }).trim();
3412
3444
  fs.writeFileSync(bootKeyPath, encrypted, "utf8");
3413
3445
  const sealLog = `[${new Date().toISOString()}] Auto-sealed boot.key via DPAPI (crash recovery enabled)\n`;
3414
3446
  try { fs.appendFileSync(LOG_FILE, sealLog); } catch {}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lifeaitools/clauth",
3
- "version": "1.4.8",
3
+ "version": "1.5.0",
4
4
  "description": "Hardware-bound credential vault for the LIFEAI infrastructure stack",
5
5
  "type": "module",
6
6
  "bin": {