feat!: Token-Schluessel und Wer-bin-ich entfernt, Version 1.0
Beides brachte allein einen Anzeigenamen. Seit der Benutzer als Widget auf dem Schreibtisch steht, kommt der aus der HoneyOS-Anmeldung — keine App muss mehr etwas dafuer bereitstellen, und beide Leisten tragen ueberall dasselbe. Der Token-Schluessel hatte zudem eine scharfe Kante: Zeigte das Feld Wer-bin-ich auf eine fremde Adresse, ging der Ausweis der App dorthin. Weg sind damit auch checkAuth, askMe und die Anmeldezeile auf den Kacheln. Alte honeyos.json-Dateien werden weiter gelesen; die Felder verfallen beim naechsten Speichern still. 1.0, weil die Bedienung sich aendert und der Stand rund ist: zwei Leisten mit gleichem Inhalt, Widgets fuer Benutzer und Wetter, die HoneyBar auch in fremden Apps. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
b999a66fed
commit
fc4dd12c5e
7 files changed
+22
-168
No files matched your search
@@ -153,8 +153,6 @@
|
||||
background: var(--p); border-color: var(--p); color: var(--bg); text-shadow: none;
|
||||
}
|
||||
.right { display: flex; align-items: center; gap: 8px; }
|
||||
.user { color: var(--p); max-width: 160px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.user[hidden] { display: none; }
|
||||
.clock { color: var(--p-mid); font-variant-numeric: tabular-nums; }
|
||||
/* Anzeige, kein Knopf: Wer darauf klickt, erwartet, dass etwas passiert.
|
||||
Eingestellt wird das Wetter dort, wo alles eingestellt wird. */
|
||||
@@ -187,7 +185,6 @@
|
||||
<a class="home" id="home" title="HoneyOS">■ HONEY<b>OS</b></a>
|
||||
<nav class="apps" id="apps"></nav>
|
||||
<div class="right">
|
||||
<span class="user" id="user" hidden></span>
|
||||
<span class="wx" id="wx" hidden></span>
|
||||
<span class="clock" id="clock"></span>
|
||||
<button class="menu-btn" id="menuBtn" aria-haspopup="true" aria-expanded="false">▾</button>
|
||||
@@ -279,41 +276,6 @@
|
||||
el.hidden = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Zeigt an, wer angemeldet ist. Die HoneyBar steckt in der App selbst, der
|
||||
* Aufruf geht also an denselben Ursprung — kein CORS, keine Freigabe nötig.
|
||||
* Antwortet die App nicht wie erwartet, bleibt das Feld leer und niemand
|
||||
* merkt etwas: die Anzeige ist eine Zugabe, keine Zusicherung.
|
||||
*/
|
||||
async function showUser() {
|
||||
const me = apps.find((a) => a.id === current);
|
||||
if (!me || !me.me) return;
|
||||
try {
|
||||
const headers = {};
|
||||
if (me.tokenKey) {
|
||||
const tok = localStorage.getItem(me.tokenKey);
|
||||
if (tok) headers.Authorization = "Bearer " + tok;
|
||||
}
|
||||
const res = await fetch(url(me, me.me), { credentials: "same-origin", cache: "no-store", headers });
|
||||
if (!res.ok) return;
|
||||
const data = await res.json();
|
||||
const u = (data && (data.user || data.profile)) || data || {};
|
||||
const name = u.display_name || u.displayName || u.name || u.username || u.preferred_username;
|
||||
if (!name) return;
|
||||
const el = root.getElementById("user");
|
||||
el.textContent = name;
|
||||
el.hidden = false;
|
||||
} catch {
|
||||
/* nicht angemeldet, kein Endpunkt, kein Netz — alles kein Grund zu lärmen */
|
||||
}
|
||||
}
|
||||
|
||||
function url(app, path) {
|
||||
if (!path) return app.url;
|
||||
if (/^https?:\/\//i.test(path)) return path;
|
||||
return app.url.replace(/\/+$/, "") + (path.startsWith("/") ? path : "/" + path);
|
||||
}
|
||||
|
||||
function clock() {
|
||||
root.getElementById("clock").textContent = new Date().toLocaleTimeString("de-DE", {
|
||||
hour: "2-digit",
|
||||
@@ -427,7 +389,6 @@
|
||||
nub.remove();
|
||||
nub = null;
|
||||
mount();
|
||||
showUser();
|
||||
});
|
||||
document.body.appendChild(nub);
|
||||
}
|
||||
@@ -492,7 +453,6 @@
|
||||
if (document.body) mount();
|
||||
else document.addEventListener("DOMContentLoaded", mount, { once: true });
|
||||
|
||||
showUser();
|
||||
wetter();
|
||||
}
|
||||
|
||||
|
||||
@@ -43,7 +43,6 @@ const state = {
|
||||
oidc: false,
|
||||
settings: { ...DEFAULT_SETTINGS },
|
||||
probes: {}, // appId -> "up" | "down" | "unknown"
|
||||
auth: {}, // appId -> {status: "in"|"out"|"blind", name}
|
||||
wins: {}, // id -> {el, task, min}
|
||||
zTop: 10,
|
||||
};
|
||||
@@ -650,7 +649,6 @@ async function probeApps() {
|
||||
);
|
||||
paintProbes();
|
||||
renderStats();
|
||||
checkAuth();
|
||||
}
|
||||
|
||||
async function probe(url) {
|
||||
@@ -666,91 +664,11 @@ async function probe(url) {
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Anmeldestand ──────────────────────────────────────────────────────────
|
||||
* Wenn eine App einen "me"-Endpunkt in der Konfiguration hat, fragen wir ihn
|
||||
* mit den Keksen des Browsers. Das funktioniert nur, weil alle Adressen unter
|
||||
* derselben Domain liegen — sonst schickt der Browser die Sitzung nicht mit.
|
||||
*
|
||||
* Antwortet die App nicht oder verbietet CORS, ist der Stand "blind": dann
|
||||
* zeigt HoneyOS eben nur die Lampe wie bisher. Kein Fehler, keine Meldung —
|
||||
* die Anzeige ist eine Zugabe, nichts, worauf sich jemand verlassen muss.
|
||||
*/
|
||||
async function checkAuth() {
|
||||
await Promise.all(
|
||||
state.cfg.apps
|
||||
.filter((a) => a.me)
|
||||
.map(async (app) => {
|
||||
state.auth[app.id] = await askMe(app);
|
||||
})
|
||||
);
|
||||
paintProbes();
|
||||
applyAuthName();
|
||||
}
|
||||
|
||||
async function askMe(app) {
|
||||
const ctl = new AbortController();
|
||||
const t = setTimeout(() => ctl.abort(), 5000);
|
||||
try {
|
||||
const headers = {};
|
||||
// Apps mit Bearer-Token statt Cookie: der Schlüssel steht in der
|
||||
// Konfiguration, der Wert im localStorage — und der ist pro Domain.
|
||||
// Von hier aus lesbar ist er nur, wenn HoneyOS dieselbe Domain ist.
|
||||
if (app.tokenKey) {
|
||||
try {
|
||||
const tok = localStorage.getItem(app.tokenKey);
|
||||
if (tok) headers.Authorization = `Bearer ${tok}`;
|
||||
} catch {
|
||||
/* kein Speicherzugriff — dann ohne */
|
||||
}
|
||||
}
|
||||
const res = await fetch(resolveUrl(app, app.me), {
|
||||
credentials: "include",
|
||||
cache: "no-store",
|
||||
headers,
|
||||
signal: ctl.signal,
|
||||
});
|
||||
if (res.status === 401 || res.status === 403) return { status: "out" };
|
||||
if (!res.ok) return { status: "blind" };
|
||||
return { status: "in", name: extractName(await res.json()) };
|
||||
} catch {
|
||||
return { status: "blind" };
|
||||
} finally {
|
||||
clearTimeout(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Relative Pfade beziehen sich auf die App-Adresse, absolute gelten wie sie sind. */
|
||||
function resolveUrl(app, path) {
|
||||
if (!path) return app.url;
|
||||
if (/^https?:\/\//i.test(path)) return path;
|
||||
return app.url.replace(/\/+$/, "") + (path.startsWith("/") ? path : "/" + path);
|
||||
}
|
||||
|
||||
/** Jede App antwortet anders — hier wird der Anzeigename herausgefischt. */
|
||||
function extractName(data) {
|
||||
const u = (data && (data.user || data.profile)) || data || {};
|
||||
return u.display_name || u.displayName || u.name || u.username || u.preferred_username || "";
|
||||
}
|
||||
|
||||
/** Nennt eine App einen Namen, gilt der — er kommt vom Anbieter, die
|
||||
* Einstellung ist nur der Platzhalter, solange niemand antwortet. */
|
||||
function applyAuthName() {
|
||||
const named = Object.values(state.auth).find((a) => a.status === "in" && a.name);
|
||||
if (named) {
|
||||
state.cfg.user = named.name;
|
||||
renderUser();
|
||||
renderStats();
|
||||
}
|
||||
}
|
||||
|
||||
const STATE_TEXT = { up: "online", down: "keine antwort", unknown: "prüfe …" };
|
||||
const AUTH_TEXT = { in: "angemeldet", out: "nicht angemeldet" };
|
||||
|
||||
function stateLine(appId) {
|
||||
const s = state.probes[appId] || "unknown";
|
||||
const a = state.auth[appId];
|
||||
const extra = a && AUTH_TEXT[a.status] ? ` · ${a.status === "in" && a.name ? a.name : AUTH_TEXT[a.status]}` : "";
|
||||
return { s, text: STATE_TEXT[s] + (s === "up" ? extra : "") };
|
||||
return { s, text: STATE_TEXT[s] };
|
||||
}
|
||||
|
||||
function paintProbes() {
|
||||
@@ -1211,12 +1129,6 @@ function openApps() {
|
||||
</select></label>
|
||||
<label class="ae-field"><span>Taste</span>
|
||||
<input data-i="${i}" data-f="key" value="${escapeAttr(a.key || "")}" maxlength="1" placeholder="1" /></label>
|
||||
<label class="ae-field"><span>Wer-bin-ich</span>
|
||||
<input data-i="${i}" data-f="me" value="${escapeAttr(a.me || "")}" placeholder="/api/v1/auth/me" spellcheck="false" /></label>
|
||||
<p class="ae-hint">optional — zeigt an, ob du dort angemeldet bist</p>
|
||||
<label class="ae-field"><span>Token-Schlüssel</span>
|
||||
<input data-i="${i}" data-f="tokenKey" value="${escapeAttr(a.tokenKey || "")}" placeholder="leer lassen" spellcheck="false" /></label>
|
||||
<p class="ae-hint">nur für Apps mit Bearer-Token im localStorage statt Cookie</p>
|
||||
<div class="ae-actions">
|
||||
<button class="btn btn-danger" data-del="${i}">löschen</button>
|
||||
</div>
|
||||
|
||||
Reference in new issue
Block a user