28 files
This commit is contained in:
1 parent
38964507bd
commit
1770e33ddc
28 files changed
+692
-232
No files matched your search
@@ -17,6 +17,13 @@ const css = LitElement.prototype.css;
|
||||
const tmSafePhotoUrl = (u) =>
|
||||
typeof u === "string" && u.startsWith("/api/taskmate/photo/") ? u : "";
|
||||
|
||||
// A pending-claims attribute is only ever usable as a list. The resolver used
|
||||
// to hand back the pending-approvals sensor's scalar COUNT under this name,
|
||||
// which turned .filter()/.some() into a TypeError and blanked the whole card
|
||||
// (#834). The name collision is fixed in the resolver; this keeps a stray
|
||||
// value from taking the card down again.
|
||||
const tmClaimList = (v) => (Array.isArray(v) ? v : []);
|
||||
|
||||
const _safeColor = (c, d) => (typeof c === "string" && /^#[0-9a-fA-F]{3,8}$/.test(c) ? c : d);
|
||||
|
||||
class TaskMateApprovalsCard extends LitElement {
|
||||
@@ -25,6 +32,7 @@ class TaskMateApprovalsCard extends LitElement {
|
||||
hass: { type: Object },
|
||||
config: { type: Object },
|
||||
_loading: { type: Object },
|
||||
_signed: { state: true },
|
||||
};
|
||||
}
|
||||
|
||||
@@ -40,6 +48,8 @@ class TaskMateApprovalsCard extends LitElement {
|
||||
constructor() {
|
||||
super();
|
||||
this._loading = {};
|
||||
this._signed = {}; // safe photo path -> per-viewer signed path
|
||||
this._inflight = new Set();
|
||||
}
|
||||
|
||||
_t(key, params) {
|
||||
@@ -47,6 +57,56 @@ class TaskMateApprovalsCard extends LitElement {
|
||||
return fn ? fn(this.hass, key, params) : key;
|
||||
}
|
||||
|
||||
// Evidence photos are served from an auth-gated view; an <img> carries no
|
||||
// bearer token, so each path is signed per-viewer via auth/sign_path. The
|
||||
// sensor now publishes bare paths (no self-authenticating URL in shared
|
||||
// state), so this binds photo access to the actual viewer.
|
||||
_photoHref(raw) {
|
||||
const safe = tmSafePhotoUrl(raw);
|
||||
return (safe && this._signed[safe]) || "";
|
||||
}
|
||||
|
||||
_collectPhotoUrls() {
|
||||
const out = [];
|
||||
const st = this.hass && this.hass.states;
|
||||
if (!st) return out;
|
||||
for (const eid in st) {
|
||||
if (eid.indexOf("sensor.taskmate") !== 0) continue;
|
||||
const attrs = (st[eid] && st[eid].attributes) || {};
|
||||
for (const k in attrs) {
|
||||
const v = attrs[k];
|
||||
if (!Array.isArray(v)) continue;
|
||||
for (const item of v) {
|
||||
if (item && typeof item === "object" && typeof item.photo_url === "string" && item.photo_url) {
|
||||
out.push(item.photo_url);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
async _ensureSignedPhotos(rawUrls) {
|
||||
for (const raw of rawUrls) {
|
||||
const url = tmSafePhotoUrl(raw);
|
||||
if (!url || this._signed[url] || this._inflight.has(url)) continue;
|
||||
this._inflight.add(url);
|
||||
try {
|
||||
const res = await this.hass.callWS({ type: "auth/sign_path", path: url, expires: 3600 });
|
||||
if (res && res.path) this._signed = { ...this._signed, [url]: res.path };
|
||||
} catch (e) {
|
||||
console.warn("taskmate: sign_path failed", e);
|
||||
} finally {
|
||||
this._inflight.delete(url);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updated() {
|
||||
const urls = this._collectPhotoUrls();
|
||||
if (urls.length) this._ensureSignedPhotos(urls);
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
const base = css`
|
||||
:host {
|
||||
@@ -530,10 +590,9 @@ class TaskMateApprovalsCard extends LitElement {
|
||||
|
||||
// Pending reward claims: supported via either the pending_approvals sensor
|
||||
// (reward_claims attribute) or the rewards sensor (pending_reward_claims).
|
||||
let rewardClaims =
|
||||
entity.attributes.reward_claims ||
|
||||
attrs.pending_reward_claims ||
|
||||
[];
|
||||
let rewardClaims = tmClaimList(
|
||||
entity.attributes.reward_claims || attrs.pending_reward_claims,
|
||||
);
|
||||
const filteredClaims = this._filterClaimsByChild(rewardClaims);
|
||||
|
||||
// Missed mandatory chores awaiting parent review (#532)
|
||||
@@ -611,7 +670,7 @@ class TaskMateApprovalsCard extends LitElement {
|
||||
if (!completions) completions = (attrs.todays_completions || []).filter(c => !c.approved);
|
||||
const filteredCompletions = this._filterByChild(completions);
|
||||
|
||||
const rewardClaims = entity.attributes.reward_claims || attrs.pending_reward_claims || [];
|
||||
const rewardClaims = tmClaimList(entity.attributes.reward_claims || attrs.pending_reward_claims);
|
||||
const filteredClaims = this._filterClaimsByChild(rewardClaims);
|
||||
|
||||
const misses = this._filterMissesByChild(attrs.mandatory_misses || []);
|
||||
@@ -805,7 +864,7 @@ class TaskMateApprovalsCard extends LitElement {
|
||||
}
|
||||
|
||||
_apPhotoDesigned(it, cls) {
|
||||
const photoUrl = tmSafePhotoUrl(it.photo);
|
||||
const photoUrl = this._photoHref(it.photo);
|
||||
if (it.kind !== "completion" || !photoUrl) {
|
||||
return it.kind === "claim" && it.icon
|
||||
? html`<div class="${cls}"><ha-icon icon="${it.icon}"></ha-icon></div>`
|
||||
@@ -1332,11 +1391,11 @@ class TaskMateApprovalsCard extends LitElement {
|
||||
${completion.points}
|
||||
</span>
|
||||
</div>
|
||||
${tmSafePhotoUrl(completion.photo_url) ? html`
|
||||
<a class="approval-photo" href="${tmSafePhotoUrl(completion.photo_url)}" target="_blank" rel="noopener"
|
||||
@click="${(e) => this._openPhoto(e, tmSafePhotoUrl(completion.photo_url), this._photoCaption(completion.chore_name, completion.child_name, completion.completed_at))}"
|
||||
${this._photoHref(completion.photo_url) ? html`
|
||||
<a class="approval-photo" href="${this._photoHref(completion.photo_url)}" target="_blank" rel="noopener"
|
||||
@click="${(e) => this._openPhoto(e, this._photoHref(completion.photo_url), this._photoCaption(completion.chore_name, completion.child_name, completion.completed_at))}"
|
||||
title="${this._t('approvals.view_photo') || 'View photo'}">
|
||||
<img src="${tmSafePhotoUrl(completion.photo_url)}" alt="" loading="lazy">
|
||||
<img src="${this._photoHref(completion.photo_url)}" alt="" loading="lazy">
|
||||
</a>
|
||||
` : ''}
|
||||
</div>
|
||||
|
||||
Reference in new issue
Block a user