Auto-detect HA auth from parent frame — no manual token entry needed
When loaded as panel_iframe inside HA, the dashboard now automatically grabs the session token from the parent frame. This means: - No setup screen on new devices — just log into HA and it works - Token refreshes automatically on reconnect (session tokens rotate) - If auto-auth fails, retries with fresh token before showing error - Falls back to localStorage/manual entry if not inside HA iframe - Settings modal shows auto-auth status indicator
This commit is contained in:
@@ -126,6 +126,7 @@ input[type=time]::-webkit-calendar-picker-indicator{filter:invert(.5)}
|
|||||||
</div>
|
</div>
|
||||||
<div id="settings-modal" class="ov" onclick="ovc(event,'settings-modal')">
|
<div id="settings-modal" class="ov" onclick="ovc(event,'settings-modal')">
|
||||||
<div class="modal"><h2>⚙ Settings</h2>
|
<div class="modal"><h2>⚙ Settings</h2>
|
||||||
|
<div id="auto-auth-info" class="ibox" style="display:none"><strong>✓ Auto-authenticated</strong> via Home Assistant session. No token needed — works on any device logged into HA.</div>
|
||||||
<div class="fg"><label>Home Assistant URL</label><input id="set-url" class="fi" type="text"/></div>
|
<div class="fg"><label>Home Assistant URL</label><input id="set-url" class="fi" type="text"/></div>
|
||||||
<div class="fg"><label>Long-Lived Access Token</label><input id="set-tok" class="fi" type="password"/></div>
|
<div class="fg"><label>Long-Lived Access Token</label><input id="set-tok" class="fi" type="password"/></div>
|
||||||
<div class="ibox"><strong>Config not saving?</strong> Go to HA → Developer Tools → Actions → call <code>input_text.reload</code>, then hard-refresh this page.</div>
|
<div class="ibox"><strong>Config not saving?</strong> Go to HA → Developer Tools → Actions → call <code>input_text.reload</code>, then hard-refresh this page.</div>
|
||||||
@@ -145,7 +146,25 @@ let ws,wsId=1,pending={},retryTimer;
|
|||||||
let states={},cfg={users:[]},prefs={url:'',token:''};
|
let states={},cfg={users:[]},prefs={url:'',token:''};
|
||||||
let devTargetUser=null,schedOpen={},renderTimer=null,lastSaveTime=0;
|
let devTargetUser=null,schedOpen={},renderTimer=null,lastSaveTime=0;
|
||||||
|
|
||||||
function loadPrefs(){try{const p=localStorage.getItem('pc2_prefs');if(p){prefs=JSON.parse(p);return true;}}catch(e){}return false;}
|
let autoAuth=false; // true when auth was auto-detected from HA parent frame
|
||||||
|
function getHAAuthFromParent(){
|
||||||
|
// When loaded as panel_iframe inside HA, grab the session token from the parent frame
|
||||||
|
try{
|
||||||
|
const ha=window.parent.document.querySelector('home-assistant');
|
||||||
|
if(ha&&ha.hass&&ha.hass.auth&&ha.hass.auth.data&&ha.hass.auth.data.access_token){
|
||||||
|
return{url:window.location.origin,token:ha.hass.auth.data.access_token};
|
||||||
|
}
|
||||||
|
}catch(e){/* cross-origin or not in HA iframe */}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
function loadPrefs(){
|
||||||
|
// 1. Try auto-detect from HA parent frame (works on any device if logged into HA)
|
||||||
|
const ha=getHAAuthFromParent();
|
||||||
|
if(ha){prefs=ha;autoAuth=true;return true;}
|
||||||
|
// 2. Fallback to localStorage (legacy / standalone use)
|
||||||
|
try{const p=localStorage.getItem('pc2_prefs');if(p){prefs=JSON.parse(p);return true;}}catch(e){}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
function savePrefs(){localStorage.setItem('pc2_prefs',JSON.stringify(prefs));}
|
function savePrefs(){localStorage.setItem('pc2_prefs',JSON.stringify(prefs));}
|
||||||
function doSetup(){
|
function doSetup(){
|
||||||
const url=document.getElementById('s-url').value.trim().replace(/\/+$/,'');
|
const url=document.getElementById('s-url').value.trim().replace(/\/+$/,'');
|
||||||
@@ -165,6 +184,9 @@ function saveSettings(){
|
|||||||
}
|
}
|
||||||
function connect(){
|
function connect(){
|
||||||
setHA('conn');clearTimeout(retryTimer);
|
setHA('conn');clearTimeout(retryTimer);
|
||||||
|
// Refresh token from parent frame on each connect (session tokens rotate)
|
||||||
|
const ha=getHAAuthFromParent();
|
||||||
|
if(ha){prefs.url=ha.url;prefs.token=ha.token;autoAuth=true;}
|
||||||
const wsUrl=prefs.url.replace(/^https?/,m=>m==='https'?'wss':'ws')+'/api/websocket';
|
const wsUrl=prefs.url.replace(/^https?/,m=>m==='https'?'wss':'ws')+'/api/websocket';
|
||||||
try{
|
try{
|
||||||
ws=new WebSocket(wsUrl);
|
ws=new WebSocket(wsUrl);
|
||||||
@@ -177,7 +199,13 @@ function dispatch(msg){
|
|||||||
switch(msg.type){
|
switch(msg.type){
|
||||||
case 'auth_required': ws.send(JSON.stringify({type:'auth',access_token:prefs.token})); break;
|
case 'auth_required': ws.send(JSON.stringify({type:'auth',access_token:prefs.token})); break;
|
||||||
case 'auth_ok': onAuth(); break;
|
case 'auth_ok': onAuth(); break;
|
||||||
case 'auth_invalid': setHA('err'); toast('HA auth failed — check token','e'); break;
|
case 'auth_invalid': {
|
||||||
|
// If auto-auth failed, try refreshing token from parent once
|
||||||
|
const fresh=getHAAuthFromParent();
|
||||||
|
if(fresh&&fresh.token!==prefs.token){prefs.token=fresh.token;ws.close();connect();}
|
||||||
|
else{setHA('err');toast('HA auth failed — check token','e');}
|
||||||
|
break;
|
||||||
|
}
|
||||||
case 'result':
|
case 'result':
|
||||||
if(pending[msg.id]){const{res,rej}=pending[msg.id];delete pending[msg.id];msg.success?res(msg):rej(new Error(msg.error?.message||'HA error'));}
|
if(pending[msg.id]){const{res,rej}=pending[msg.id];delete pending[msg.id];msg.success?res(msg):rej(new Error(msg.error?.message||'HA error'));}
|
||||||
break;
|
break;
|
||||||
@@ -396,7 +424,7 @@ function renderDev(userId,dev){
|
|||||||
function toggleSched(userId){schedOpen[userId]=!schedOpen[userId];render();}
|
function toggleSched(userId){schedOpen[userId]=!schedOpen[userId];render();}
|
||||||
function buildColorPicker(presel){document.getElementById('col-picker').innerHTML=COLORS.map(c=>`<span class="copt${c===presel?' sel':''}" data-c="${c}" style="background:${c}" onclick="pickColor(this)"></span>`).join('');}
|
function buildColorPicker(presel){document.getElementById('col-picker').innerHTML=COLORS.map(c=>`<span class="copt${c===presel?' sel':''}" data-c="${c}" style="background:${c}" onclick="pickColor(this)"></span>`).join('');}
|
||||||
function pickColor(el){document.querySelectorAll('.copt').forEach(o=>o.classList.remove('sel'));el.classList.add('sel');}
|
function pickColor(el){document.querySelectorAll('.copt').forEach(o=>o.classList.remove('sel'));el.classList.add('sel');}
|
||||||
function openModal(id){if(id==='add-user-modal')openAddUserModal();else{if(id==='settings-modal'){document.getElementById('set-url').value=prefs.url;document.getElementById('set-tok').value=prefs.token;}document.getElementById(id).classList.add('open');}}
|
function openModal(id){if(id==='add-user-modal')openAddUserModal();else{if(id==='settings-modal'){document.getElementById('set-url').value=prefs.url;document.getElementById('set-tok').value=prefs.token;document.getElementById('auto-auth-info').style.display=autoAuth?'block':'none';}document.getElementById(id).classList.add('open');}}
|
||||||
function closeModal(id){document.getElementById(id).classList.remove('open');}
|
function closeModal(id){document.getElementById(id).classList.remove('open');}
|
||||||
function ovc(e,id){if(e.target===e.currentTarget)closeModal(id);}
|
function ovc(e,id){if(e.target===e.currentTarget)closeModal(id);}
|
||||||
function setHA(state){
|
function setHA(state){
|
||||||
|
|||||||
Reference in New Issue
Block a user