diff --git a/custom_components/bahmcloud_store/panel/panel.js b/custom_components/bahmcloud_store/panel/panel.js index c2100ab..d24ebb6 100644 --- a/custom_components/bahmcloud_store/panel/panel.js +++ b/custom_components/bahmcloud_store/panel/panel.js @@ -21,9 +21,9 @@ class BahmcloudStorePanel extends HTMLElement { this._detailRepo = null; this._readmeLoading = false; - this._readmeText = null; - this._readmeHtml = null; - this._readmeError = null; + this._readmeText = null; // markdown string + this._readmeHtml = null; // sanitized html string + this._readmeError = null; // string } set hass(hass) { @@ -46,7 +46,7 @@ class BahmcloudStorePanel extends HTMLElement { const data = await this._hass.callApi("get", "bcs"); this._data = data; } catch (e) { - this._error = e?.message ? String(e.message) : String(e); + this._error = this._toErrString(e); } finally { this._loading = false; this._update(); @@ -106,7 +106,7 @@ class BahmcloudStorePanel extends HTMLElement { this._view = "manage"; this._update(); } catch (e) { - this._error = e?.message ? String(e.message) : String(e); + this._error = this._toErrString(e); this._update(); } } @@ -120,7 +120,7 @@ class BahmcloudStorePanel extends HTMLElement { this._view = "manage"; this._update(); } catch (e) { - this._error = e?.message ? String(e.message) : String(e); + this._error = this._toErrString(e); this._update(); } } @@ -142,9 +142,13 @@ class BahmcloudStorePanel extends HTMLElement { this._loadReadme(repoId); } + // --- README fetching (hardened) --- async _loadReadme(repoId) { if (!this._hass) return; + this._readmeLoading = true; + this._readmeText = null; + this._readmeHtml = null; this._readmeError = null; this._update(); @@ -154,18 +158,37 @@ class BahmcloudStorePanel extends HTMLElement { `bcs/readme?repo_id=${encodeURIComponent(repoId)}` ); - if (resp?.ok && typeof resp.readme === "string" && resp.readme.trim()) { - this._readmeText = resp.readme; - this._readmeHtml = typeof resp.html === "string" && resp.html.trim() ? resp.html : null; + // Normalize fields strictly (avoid [object Object]) + const ok = resp && resp.ok === true; + + const readme = (resp && typeof resp.readme === "string") ? resp.readme : null; + const html = (resp && typeof resp.html === "string") ? resp.html : null; + + if (ok && readme && readme.trim()) { + this._readmeText = readme; + this._readmeHtml = html && html.trim() ? html : null; + this._readmeError = null; } else { + // If backend provided a message, convert it safely + const msg = + (resp && typeof resp.message === "string" && resp.message.trim()) + ? resp.message.trim() + : "README not found."; + + // Extra hint if backend returned unexpected types + if (ok && resp && resp.readme && typeof resp.readme !== "string") { + this._readmeError = "README has an unsupported format (expected text)."; + } else { + this._readmeError = msg; + } + this._readmeText = null; this._readmeHtml = null; - this._readmeError = this._safeText(resp?.message) || "README not found."; } } catch (e) { this._readmeText = null; this._readmeHtml = null; - this._readmeError = e?.message ? String(e.message) : String(e); + this._readmeError = this._toErrString(e) || "README not found."; } finally { this._readmeLoading = false; this._update(); @@ -544,10 +567,10 @@ class BahmcloudStorePanel extends HTMLElement { const creator = this._safeText(r?.owner) ? `Creator: ${this._safeText(r?.owner)}` : "Creator: -"; const latest = this._safeText(r?.latest_version) ? `Latest: ${this._safeText(r?.latest_version)}` : "Latest: unknown"; - const cat = this._safeText(r?.category) ? `Category: ${this._safeText(r?.category)}` : null; + const prov = this._safeText(r?.provider) ? `Provider: ${this._safeText(r?.provider)}` : null; const metaSrc = this._safeText(r?.meta_source) ? `Meta: ${this._safeText(r?.meta_source)}` : null; - const lineBits = [creator, latest, cat, metaSrc].filter(Boolean); + const lineBits = [creator, latest, prov, metaSrc].filter(Boolean); return `
@@ -652,7 +675,6 @@ class BahmcloudStorePanel extends HTMLElement { for (const r of repos) { s.total += 1; - if (r?.source === "custom") s.custom += 1; const p = this._safeLower(r?.provider) || "other"; @@ -683,9 +705,6 @@ class BahmcloudStorePanel extends HTMLElement { this._safeText(r?.owner) ? `Creator: ${this._safeText(r?.owner)}` : "Creator: -", latest, this._safeText(r?.provider) ? `Provider: ${this._safeText(r?.provider)}` : null, - this._safeText(r?.category) ? `Category: ${this._safeText(r?.category)}` : null, - this._safeText(r?.meta_author) ? `Author: ${this._safeText(r?.meta_author)}` : null, - this._safeText(r?.meta_maintainer) ? `Maintainer: ${this._safeText(r?.meta_maintainer)}` : null, this._safeText(r?.meta_source) ? `Meta: ${this._safeText(r?.meta_source)}` : null, ].filter(Boolean); @@ -758,8 +777,8 @@ class BahmcloudStorePanel extends HTMLElement { if (!mount) return; if (this._readmeText) { - // Client renderer may be unavailable; prefer server-provided HTML - if (this._readmeHtml) { + // We render server-side; only accept a real string html + if (typeof this._readmeHtml === "string" && this._readmeHtml.trim()) { mount.innerHTML = this._readmeHtml; this._postprocessRenderedMarkdown(mount); return; @@ -914,13 +933,13 @@ class BahmcloudStorePanel extends HTMLElement { return Array.from(set).sort(); } - // --- HARDENING HELPERS (fixes [object Object]) --- + // --- helpers (prevent [object Object]) --- _safeText(v) { if (v === null || v === undefined) return ""; const t = typeof v; if (t === "string") return v; if (t === "number" || t === "boolean") return String(v); - return ""; // objects/arrays/functions => empty (prevents [object Object]) + return ""; } _safeLower(v) { @@ -929,8 +948,18 @@ class BahmcloudStorePanel extends HTMLElement { } _safeId(v) { - const s = this._safeText(v); - return s || ""; + return this._safeText(v) || ""; + } + + _toErrString(e) { + if (!e) return "Unknown error"; + if (typeof e === "string") return e; + if (typeof e?.message === "string") return e.message; + try { + return JSON.stringify(e); + } catch (_) { + return "Unknown error"; + } } _esc(s) {