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:
marcandClaude Opus 5 committed 2026-08-29 20:17:18 +02:00
1 parent b999a66fed
commit fc4dd12c5e
7 files changed
+22 -168

No files matched your search

-40
View File
@@ -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">&#9632; 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">&#9662;</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();
}
+1 -89
View File
@@ -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>