class WallPanelPanel extends HTMLElement { constructor() { super(); this._hass = null; this._panel = null; this._narrow = false; this._pollTimer = null; this._activePanelUrl = ''; this.attachShadow({ mode: 'open' }); } set hass(hass) { this._hass = hass; this._render(); } set panel(panel) { this._panel = panel; this._render(); } set narrow(narrow) { this._narrow = Boolean(narrow); this._render(); } connectedCallback() { this._render(); } disconnectedCallback() { if (this._pollTimer) { window.clearInterval(this._pollTimer); this._pollTimer = null; } } _resolveUrl(rawUrl) { const value = String(rawUrl || '').trim(); if (!value) { return ''; } try { const url = new URL(value, window.location.href); if (!url.searchParams.has('embed')) { url.searchParams.set('embed', '1'); } if (!url.searchParams.has('mode')) { url.searchParams.set('mode', 'ha'); } return url.toString(); } catch (error) { return value; } } _panelConfig() { const config = this._panel?.config || {}; const customConfig = config._panel_custom || {}; return customConfig.config || customConfig || config; } _configUrl() { const payload = this._panelConfig(); const entryId = String(payload.entry_id || '').trim(); if (!entryId) { return ''; } return `/api/wall_panel/config/${encodeURIComponent(entryId)}`; } async _fetchPanelUrl() { const payload = this._panelConfig(); const configUrl = this._configUrl(); const syncToken = String(payload.sync_token || '').trim(); if (!configUrl || !syncToken) { return ''; } try { const response = await fetch(configUrl, { method: 'GET', headers: { 'X-Wall-Panel-Token': syncToken, Accept: 'application/json', }, credentials: 'same-origin', cache: 'no-store', }); if (!response.ok) { return ''; } const data = await response.json(); const panelUrl = String(data?.panel?.panel_url || data?.panel_url || '').trim(); return panelUrl; } catch (error) { return ''; } } _renderMessage(title, body, extra = '') { if (!this.shadowRoot) { return; } this.shadowRoot.innerHTML = `
${title}
${body}
${extra}
`; } _renderIframe(panelUrl) { if (!this.shadowRoot) { return; } const wrap = this.shadowRoot.querySelector('.wrap'); if (!wrap) { return; } const iframe = document.createElement('iframe'); iframe.src = panelUrl; iframe.loading = 'eager'; iframe.referrerPolicy = 'no-referrer'; iframe.allow = 'autoplay; fullscreen; picture-in-picture'; iframe.style.width = '100%'; iframe.style.height = '100%'; iframe.style.border = '0'; iframe.style.display = 'block'; iframe.style.background = 'transparent'; wrap.replaceChildren(iframe); this._activePanelUrl = panelUrl; } async _tryAttachPanel() { const payload = this._panelConfig(); const initialUrl = this._resolveUrl( payload.panel_url || payload.ingress_url || '' ); if (initialUrl && initialUrl === this._activePanelUrl) { return; } if (initialUrl && !(window.location.protocol === 'https:' && initialUrl.startsWith('http://'))) { this._renderIframe(initialUrl); return; } const panelUrl = this._resolveUrl(await this._fetchPanelUrl()); if (panelUrl && panelUrl === this._activePanelUrl) { return; } if (panelUrl && !(window.location.protocol === 'https:' && panelUrl.startsWith('http://'))) { this._renderIframe(panelUrl); return; } const configUrl = this._configUrl(); this._renderMessage( 'Waiting for Wall Panel', 'The add-on will publish a secure HTTPS ingress URL here.', configUrl ? `${configUrl}` : '' ); } _render() { if (!this.shadowRoot) { return; } if (this._pollTimer) { window.clearInterval(this._pollTimer); this._pollTimer = null; } this._tryAttachPanel(); this._pollTimer = window.setInterval(() => { this._tryAttachPanel(); }, 2000); } } if (!customElements.get('wall-panel-panel')) { customElements.define('wall-panel-panel', WallPanelPanel); }