diff --git a/src/dashboards/hacs-dashboard.ts b/src/dashboards/hacs-dashboard.ts
index a42c4bb4..c8f94a5b 100644
--- a/src/dashboards/hacs-dashboard.ts
+++ b/src/dashboards/hacs-dashboard.ts
@@ -10,7 +10,7 @@ import {
mdiInformation,
mdiNewBox,
} from "@mdi/js";
-import type { CSSResultGroup, TemplateResult } from "lit";
+import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit";
import { LitElement, html, nothing } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import memoize from "memoize-one";
@@ -63,6 +63,7 @@ const defaultKeyData = {
};
const STATUS_ORDER = ["pending-restart", "pending-upgrade", "installed", "new", "default"];
+const BRANDS_TOKEN_REFRESH_INTERVAL = 30 * 60 * 1000;
const TABS: PageNavigation[] = [
{
@@ -114,6 +115,28 @@ export class HacsDashboard extends LitElement {
@state()
private _overflowMenuRepository?: RepositoryBase;
+ @state()
+ private _brandsAccessToken?: string;
+
+ private _brandsTokenRefreshInterval?: number;
+
+ public connectedCallback(): void {
+ super.connectedCallback();
+ if (this.hasUpdated) {
+ this._startBrandsTokenRefresh();
+ }
+ }
+
+ public disconnectedCallback(): void {
+ super.disconnectedCallback();
+ this._stopBrandsTokenRefresh();
+ }
+
+ protected firstUpdated(changedProperties: PropertyValues): void {
+ super.firstUpdated(changedProperties);
+ this._startBrandsTokenRefresh();
+ }
+
protected render = (): TemplateResult | void => {
const repositories = this._filterRepositories(
this.hacs.repositories,
@@ -327,13 +350,20 @@ export class HacsDashboard extends LitElement {
+ this._handleRepositoryIconError(event, repository.domain)}
/>
`
: html`
@@ -581,6 +611,46 @@ export class HacsDashboard extends LitElement {
this._activeFilters = undefined;
}
+ private _startBrandsTokenRefresh(): void {
+ this._stopBrandsTokenRefresh();
+ void this._fetchBrandsAccessToken();
+ this._brandsTokenRefreshInterval = window.setInterval(
+ () => void this._fetchBrandsAccessToken(),
+ BRANDS_TOKEN_REFRESH_INTERVAL,
+ );
+ }
+
+ private _stopBrandsTokenRefresh(): void {
+ if (this._brandsTokenRefreshInterval !== undefined) {
+ window.clearInterval(this._brandsTokenRefreshInterval);
+ this._brandsTokenRefreshInterval = undefined;
+ }
+ }
+
+ private async _fetchBrandsAccessToken(): Promise {
+ try {
+ const { token } = await this.hass.callWS<{ token: string }>({
+ type: "brands/access_token",
+ });
+ this._brandsAccessToken = token;
+ } catch {
+ this._brandsAccessToken = undefined;
+ }
+ }
+
+ private _handleRepositoryIconError(event: Event, domain?: string): void {
+ const image = event.currentTarget as HTMLImageElement;
+ const fallback = brandsUrl({
+ domain: domain || "invalid",
+ type: "icon",
+ useFallback: true,
+ darkOptimized: this.hass.themes?.darkMode,
+ });
+ if (image.src !== fallback) {
+ image.src = fallback;
+ }
+ }
+
static get styles(): CSSResultGroup {
return [haStyle, HacsStyles];
}