@floless/app 0.34.0 → 0.34.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.
@@ -52454,6 +52454,24 @@ function isTrustedFlolessHost(url) {
52454
52454
  var OFFLINE_GRACE_MS = 24 * 60 * 60 * 1e3;
52455
52455
  var MEM_CACHE_MS = 60 * 1e3;
52456
52456
  var REFRESH_SKEW_MS = 60 * 1e3;
52457
+ function normalizeSeatReason(raw) {
52458
+ switch ((raw ?? "").toLowerCase().replace(/-/g, "_")) {
52459
+ case "self_takeover":
52460
+ case "takeover":
52461
+ return "takeover";
52462
+ case "inactivity":
52463
+ case "inactive":
52464
+ case "timeout":
52465
+ return "inactivity";
52466
+ case "admin_revoke":
52467
+ case "admin_revoked":
52468
+ case "revoked":
52469
+ case "revoke":
52470
+ return "revoked";
52471
+ default:
52472
+ return "unknown";
52473
+ }
52474
+ }
52457
52475
  function isSea() {
52458
52476
  try {
52459
52477
  return (0, import_node_module2.createRequire)(__import_meta_url)("node:sea").isSea();
@@ -52584,11 +52602,12 @@ async function accessToken() {
52584
52602
  }
52585
52603
  var mem = null;
52586
52604
  var seatLost = false;
52605
+ var seatLostReason;
52587
52606
  async function getLicenseStatus(opts = {}) {
52588
52607
  const url = signInUrl();
52589
52608
  if (seatLost) {
52590
52609
  mem = null;
52591
- return { state: "unlicensed", reason: "seat-taken", signInUrl: url };
52610
+ return { state: "unlicensed", reason: "seat-taken", seatReason: seatLostReason, signInUrl: url };
52592
52611
  }
52593
52612
  if (!opts.force && mem && Date.now() - mem.at < MEM_CACHE_MS) return mem.status;
52594
52613
  const finish = (s) => {
@@ -52671,7 +52690,7 @@ async function seatHeartbeat() {
52671
52690
  return;
52672
52691
  }
52673
52692
  if (res.status === 401) {
52674
- let reason = "self_takeover";
52693
+ let reason = "unknown";
52675
52694
  try {
52676
52695
  const b = await res.json();
52677
52696
  reason = b.details?.reason ?? b.reason ?? reason;
@@ -52701,16 +52720,18 @@ async function seatHeartbeat() {
52701
52720
  }
52702
52721
  function loseSeat(reason) {
52703
52722
  seatLost = true;
52723
+ seatLostReason = normalizeSeatReason(reason);
52704
52724
  seat = null;
52705
52725
  mem = null;
52706
52726
  if (hbTimer) {
52707
52727
  clearInterval(hbTimer);
52708
52728
  hbTimer = null;
52709
52729
  }
52710
- onSeatLostCb?.(reason);
52730
+ onSeatLostCb?.(seatLostReason);
52711
52731
  }
52712
52732
  function resetSeat() {
52713
52733
  seatLost = false;
52734
+ seatLostReason = void 0;
52714
52735
  seat = null;
52715
52736
  if (hbTimer) {
52716
52737
  clearInterval(hbTimer);
@@ -52835,7 +52856,7 @@ function appVersion() {
52835
52856
  return resolveVersion({
52836
52857
  isSea: isSea2(),
52837
52858
  sqVersionXml: readSqVersionXml(),
52838
- define: true ? "0.34.0" : void 0,
52859
+ define: true ? "0.34.1" : void 0,
52839
52860
  pkgVersion: readPkgVersion()
52840
52861
  });
52841
52862
  }
@@ -52845,7 +52866,7 @@ function resolveChannel(s) {
52845
52866
  return "dev";
52846
52867
  }
52847
52868
  function appChannel() {
52848
- return resolveChannel({ isSea: isSea2(), define: true ? "0.34.0" : void 0 });
52869
+ return resolveChannel({ isSea: isSea2(), define: true ? "0.34.1" : void 0 });
52849
52870
  }
52850
52871
 
52851
52872
  // oauth-presets.ts
@@ -58425,7 +58446,10 @@ async function startServer() {
58425
58446
  }
58426
58447
  return { ok: true, bootstrap: getBootstrapState().status };
58427
58448
  });
58428
- onSeatLost((reason) => broadcast({ type: "seat-taken", reason }));
58449
+ onSeatLost((reason) => {
58450
+ app.log.warn({ reason }, "license seat lost \u2014 gating this session until re-login");
58451
+ broadcast({ type: "seat-taken", reason });
58452
+ });
58429
58453
  app.get("/api/apps", async () => {
58430
58454
  const apps = await aware.list();
58431
58455
  return { ok: true, apps: apps.map((a) => ({ ...a, provider: appProvider(a.id), baked: appBaked(a.id) })) };
package/dist/web/aware.js CHANGED
@@ -3742,8 +3742,14 @@
3742
3742
  loadApp(currentId).catch(reportErr);
3743
3743
  }, 250);
3744
3744
  } else if (m.type === 'seat-taken') {
3745
- // This session's seat was claimed by another device (newest-login-wins).
3746
- showToast('Signed in on another device sign in here to continue.', 'err');
3745
+ // This session's seat was lost. m.reason (normalized server-side) says why — only a
3746
+ // genuine takeover should claim "another device"; an inactivity/revoke reads neutrally (#142).
3747
+ const SEAT_TOAST = {
3748
+ takeover: 'Signed in on another device — sign in here to continue.',
3749
+ inactivity: 'Your session timed out — sign in here to continue.',
3750
+ revoked: 'Your session was ended by an administrator — sign in here to continue.',
3751
+ };
3752
+ showToast(SEAT_TOAST[m.reason] || 'Your session ended — sign in here to continue.', 'err');
3747
3753
  recheckLicenseAndGate();
3748
3754
  }
3749
3755
  };
@@ -5573,7 +5579,21 @@
5573
5579
  body: 'Sign in with your FloLess account to use floless.app.',
5574
5580
  secondary: { label: 'No subscription yet? Subscribe →', href: subUrl } },
5575
5581
  };
5576
- const copy = COPY[reason] || COPY['signed-out']; // neutral fallback for an unknown/absent reason
5582
+ let copy = COPY[reason] || COPY['signed-out']; // neutral fallback for an unknown/absent reason
5583
+ // A lost seat carries a sub-reason; only a genuine takeover should claim "another device".
5584
+ // Inactivity/revoke/unknown read neutrally so the gate stops misattributing the cause (#142).
5585
+ if (reason === 'seat-taken') {
5586
+ const SEAT_COPY = {
5587
+ inactivity: { headline: 'Session timed out',
5588
+ body: 'Your session ended after a period of inactivity. Sign in to continue.' },
5589
+ revoked: { headline: 'Session ended by an administrator',
5590
+ body: 'Your session was ended by an administrator. Sign in again to continue.' },
5591
+ unknown: { headline: 'Session ended',
5592
+ body: 'Your session has ended. Sign in to continue using floless.app.' },
5593
+ };
5594
+ const sc = SEAT_COPY[status.seatReason]; // 'takeover'/absent → keep the default takeover copy
5595
+ if (sc) copy = { ...copy, ...sc };
5596
+ }
5577
5597
  const style = document.createElement('style');
5578
5598
  style.textContent = `
5579
5599
  #license-gate{position:fixed;inset:0;z-index:99999;display:flex;align-items:center;justify-content:center;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@floless/app",
3
- "version": "0.34.0",
3
+ "version": "0.34.1",
4
4
  "type": "module",
5
5
  "description": "Thin localhost host for floless.app — serves web/ and shells the aware CLI. No engine, no LLM.",
6
6
  "bin": {