Files
houseplan-card/demo/smoke_plan_signed.mjs
T
Matysh 018b37940f v1.44.7: plan backgrounds never displayed (regression from v1.44.5)
The card signs content urls because a browser cannot authenticate an <image
href>. But _display() was called inside _buildModel(), and the space model is
memoized on the config fingerprint — so the UNSIGNED url froze in the cache and
the signature, which did arrive, never reached the element. The plan never
loaded, and the browser kept hitting the unsigned path: 401, which Home
Assistant reports as a failed login attempt from the viewer's own IP (that is
how the owner spotted it). PDF links were unaffected: they already resolved at
render time.

- _buildModel() keeps the raw plan_url; the render pass calls _display().
- _display() returns '' for an unsigned content url instead of the plain path,
  and the <image> is not emitted at all until the signature lands — no 401, no
  spurious login-attempt warning.
- _resign() replaces 'drop everything and re-request': the previous urls are
  kept until the new ones arrive, so a wall tablet never blanks.
- demo/smoke_plan_signed.mjs: reproduces on v1.44.6 (href stays ?v=..., never
  ?authSig=), passes here. TESTING.md row added.
- docs: CHANGELOG.md + CHANGELOG.ru.md + STATUS.md.
2026-07-27 15:05:46 +03:00

77 lines
3.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Подложка (фон плана) лежит за requires_auth-эндпоинтом: браузер не умеет
// авторизовать <image href>, поэтому карточка просит бэкенд подписать путь.
// Регрессия 2026-07-27: _display() вызывался внутри _buildModel(), а модель
// мемоизируется по отпечатку конфига — неподписанный url «замерзал» в кэше,
// подпись до <image> не доезжала. План не отображался никогда, а браузер
// продолжал дёргать неподписанный путь → 401 → HA писал «неудачный вход»
// с собственного IP пользователя.
import { launch, checkAll, finish } from './serve.mjs';
const { page, browser } = await launch();
const res = await page.evaluate(async () => {
const out = {};
const c = window.__card;
const sr = () => c.shadowRoot || c.renderRoot;
const bgHref = () => {
const im = sr().querySelector('.stage svg image');
return im ? im.getAttribute('href') : null;
};
let signCalls = 0;
let release;
const gate = new Promise((r) => { release = r; });
const base = c.hass.callWS;
c.hass = { ...c.hass, callWS: async (m) => {
if (m.type === 'houseplan/content/sign') {
signCalls++;
const n = signCalls;
if (n === 1) await gate;
const urls = {};
for (const p of m.paths) urls[p] = p.split('?')[0] + '?authSig=SIG' + n;
return { urls };
}
return base(m);
} };
c._serverCfg = { ...c._serverCfg, spaces: c._serverCfg.spaces.map((s) => s.id !== 'f1' ? s : {
...s, plan_url: '/api/houseplan/content/plans/_/f1.svg?v=17831509',
})};
c._cfgEpoch++;
c.requestUpdate(); await c.updateComplete;
// до подписи ничего не рисуем: неподписанный запрос вернул бы 401
out.hrefBeforeSign = bgHref();
release();
await new Promise((r) => setTimeout(r, 150));
await c.updateComplete;
// подпись доехала до атрибута, а не осела в кэше модели
out.signRequested = signCalls;
out.hrefSigned = bgHref();
// перерисовка по состоянию HA не теряет подпись и не просит её заново
c.requestUpdate(); await c.updateComplete;
out.hrefAfterRerender = bgHref();
out.signRequestedAfterRerender = signCalls;
// ре-подпись на долгоживущем экране: старый url держится до нового ответа
const before = bgHref();
c._resign();
out.resignKeepsPlan = bgHref() === before;
await new Promise((r) => setTimeout(r, 80));
await c.updateComplete;
out.hrefAfterResign = bgHref();
return out;
});
// зафиксировано прогоном на v1.44.7 и сверено с кодом
checkAll(res, {
hrefBeforeSign: null,
signRequested: 1,
hrefSigned: '/api/houseplan/content/plans/_/f1.svg?authSig=SIG1',
hrefAfterRerender: '/api/houseplan/content/plans/_/f1.svg?authSig=SIG1',
signRequestedAfterRerender: 1,
resignKeepsPlan: true,
hrefAfterResign: '/api/houseplan/content/plans/_/f1.svg?authSig=SIG2',
});
await finish(browser);