v1.4.3 - persistent device ID via localStorage so refresh reuses same client slot
This commit is contained in:
+9
-17
@@ -9,30 +9,22 @@ services:
|
|||||||
- "3030:3000"
|
- "3030:3000"
|
||||||
environment:
|
environment:
|
||||||
# REQUIRED
|
# REQUIRED
|
||||||
- IMMICH_URL=http://your-immich-server:2283
|
- IMMICH_URL=http://10.0.0.40:2283
|
||||||
- IMMICH_API_KEY=your-api-key-here
|
- IMMICH_API_KEY=aQAm76nwCi5I68vFWz18irMmnyrzI5yzcb74rXvt4
|
||||||
|
- ADMIN_USERNAME=jessikitty
|
||||||
|
- ADMIN_PASSWORD=23Pinkpr!ncesses
|
||||||
|
- FRAMBE_API_TOKEN=whosethatgirl-itsjess
|
||||||
|
|
||||||
# Slideshow
|
# Slideshow
|
||||||
- SLIDESHOW_INTERVAL=30
|
- SLIDESHOW_INTERVAL=300
|
||||||
- TRANSITION_DURATION=2
|
- TRANSITION_DURATION=5
|
||||||
- IMAGE_FIT=contain
|
- IMAGE_FIT=contain
|
||||||
- SHUFFLE=true
|
- SHUFFLE=true
|
||||||
- BACKGROUND_BLUR=true
|
- BACKGROUND_BLUR=true
|
||||||
- REFRESH_INTERVAL=300 # Seconds between album/person refresh checks
|
- REFRESH_INTERVAL=300 # Seconds between album/person refresh checks
|
||||||
|
|
||||||
# Overlays
|
# Overlays
|
||||||
- SHOW_CLOCK=true
|
- SHOW_CLOCK=true
|
||||||
- SHOW_DATE=true
|
- SHOW_DATE=true
|
||||||
- SHOW_EXIF=true
|
- SHOW_EXIF=false
|
||||||
- SHOW_PROGRESS=true
|
- SHOW_PROGRESS=true
|
||||||
|
|
||||||
# Admin Authentication (leave ADMIN_PASSWORD blank to disable login)
|
|
||||||
- ADMIN_USERNAME=admin
|
|
||||||
# - ADMIN_PASSWORD=changeme
|
|
||||||
|
|
||||||
# API Token for external access (Home Assistant, scripts, etc.)
|
|
||||||
# - FRAMBE_API_TOKEN=your-secret-token-here
|
|
||||||
|
|
||||||
# Auto-start (optional — or use URL params instead)
|
|
||||||
# - ALBUM_ID=
|
|
||||||
# - SHOW_FAVORITES_ONLY=false
|
|
||||||
|
|||||||
+2
-1
@@ -7,10 +7,11 @@
|
|||||||
var currentVideoPlaying = false, pileCanvas, pileCtx;
|
var currentVideoPlaying = false, pileCanvas, pileCtx;
|
||||||
var FRAME_PAD_RATIO = 0.03, FRAME_BOTTOM_RATIO = 0.10, FRAME_COLOR = '#ede8df';
|
var FRAME_PAD_RATIO = 0.03, FRAME_BOTTOM_RATIO = 0.10, FRAME_COLOR = '#ede8df';
|
||||||
var wsConn = null, clientId = null, isSleeping = false;
|
var wsConn = null, clientId = null, isSleeping = false;
|
||||||
|
var persistentId = (function(){ var k='frambe_pid'; var v=localStorage.getItem(k); if(vi docker-compose.yml ){ v='fp-'+Math.random().toString(36).substr(2,9)+'-'+Date.now().toString(36); localStorage.setItem(k,v); } return v; })();
|
||||||
var $setupScreen=document.getElementById('setup-screen'),$slideshowScreen=document.getElementById('slideshow-screen'),$connectionStatus=document.getElementById('connection-status'),$setupContent=document.getElementById('setup-content'),$setupError=document.getElementById('setup-error'),$errorDetail=document.getElementById('error-detail'),$albumsList=document.getElementById('albums-list'),$btnStart=document.getElementById('btn-start'),$bgBlur=document.getElementById('bg-blur'),$mainFrame=document.getElementById('main-frame'),$mainPhoto=document.getElementById('main-photo'),$mainVideo=document.getElementById('main-video'),$clock=document.getElementById('clock'),$dateDisplay=document.getElementById('date-display'),$exifInfo=document.getElementById('exif-info'),$progressFill=document.getElementById('progress-fill'),$overlay=document.getElementById('overlay'),$btnSettings=document.getElementById('btn-settings'),$progressBar=document.getElementById('progress-bar');
|
var $setupScreen=document.getElementById('setup-screen'),$slideshowScreen=document.getElementById('slideshow-screen'),$connectionStatus=document.getElementById('connection-status'),$setupContent=document.getElementById('setup-content'),$setupError=document.getElementById('setup-error'),$errorDetail=document.getElementById('error-detail'),$albumsList=document.getElementById('albums-list'),$btnStart=document.getElementById('btn-start'),$bgBlur=document.getElementById('bg-blur'),$mainFrame=document.getElementById('main-frame'),$mainPhoto=document.getElementById('main-photo'),$mainVideo=document.getElementById('main-video'),$clock=document.getElementById('clock'),$dateDisplay=document.getElementById('date-display'),$exifInfo=document.getElementById('exif-info'),$progressFill=document.getElementById('progress-fill'),$overlay=document.getElementById('overlay'),$btnSettings=document.getElementById('btn-settings'),$progressBar=document.getElementById('progress-bar');
|
||||||
|
|
||||||
// === WEBSOCKET ===
|
// === WEBSOCKET ===
|
||||||
function connectWebSocket(){var proto=location.protocol==='https:'?'wss:':'ws:';wsConn=new WebSocket(proto+'//'+location.host+'/ws');wsConn.onopen=function(){console.log('[Frambe] WebSocket connected');wsConn.send(JSON.stringify({type:'register',role:'frame',status:isRunning?'playing':(isSleeping?'sleeping':'idle'),config:getCurrentConfig()}));};wsConn.onmessage=function(e){try{var msg=JSON.parse(e.data);if(msg.type==='welcome'){clientId=msg.clientId;console.log('[Frambe] Registered as '+clientId);}else if(msg.type==='command'){handleRemoteCommand(msg.action,msg.payload||{});}}catch(err){}};wsConn.onclose=function(){setTimeout(connectWebSocket,5000);};}
|
function connectWebSocket(){var proto=location.protocol==='https:'?'wss:':'ws:';wsConn=new WebSocket(proto+'//'+location.host+'/ws');wsConn.onopen=function(){console.log('[Frambe] WebSocket connected');wsConn.send(JSON.stringify({type:'register',role:'frame',persistentId:persistentId,status:isRunning?'playing':(isSleeping?'sleeping':'idle'),config:getCurrentConfig()}));};wsConn.onmessage=function(e){try{var msg=JSON.parse(e.data);if(msg.type==='welcome'){clientId=msg.clientId;console.log('[Frambe] Registered as '+clientId);}else if(msg.type==='command'){handleRemoteCommand(msg.action,msg.payload||{});}}catch(err){}};wsConn.onclose=function(){setTimeout(connectWebSocket,5000);};}
|
||||||
function sendStatus(s){if(wsConn&&wsConn.readyState===WebSocket.OPEN)wsConn.send(JSON.stringify({type:'status',status:s,currentAlbum:selectedAlbumId,config:getCurrentConfig()}));}
|
function sendStatus(s){if(wsConn&&wsConn.readyState===WebSocket.OPEN)wsConn.send(JSON.stringify({type:'status',status:s,currentAlbum:selectedAlbumId,config:getCurrentConfig()}));}
|
||||||
function getCurrentConfig(){return{slideshowInterval:config.slideshowInterval,showClock:config.showClock,showDate:config.showDate,showExif:config.showExif,showProgress:config.showProgress};}
|
function getCurrentConfig(){return{slideshowInterval:config.slideshowInterval,showClock:config.showClock,showDate:config.showDate,showExif:config.showExif,showProgress:config.showProgress};}
|
||||||
function handleRemoteCommand(action,payload){console.log('[Frambe] Remote: '+action);switch(action){case'setSource':selectedSource=payload.source;selectedAlbumId=payload.albumId||null;selectedPersonId=payload.personId||null;if(isSleeping)wakeUp();if(isRunning){clearTimeout(slideshowTimer);stopVideo();}doStartSlideshow();break;case'start':if(isSleeping)wakeUp();if(!isRunning&&selectedSource)doStartSlideshow();break;case'stop':if(isRunning)exitSlideshowInternal();sendStatus('idle');break;case'next':if(isRunning)showNextAsset();break;case'prev':if(isRunning)showPrevAsset();break;case'sleep':goToSleep();break;case'wake':wakeUp();break;case'refresh':location.reload();break;case'setConfig':applyConfigChange(payload);break;}}
|
function handleRemoteCommand(action,payload){console.log('[Frambe] Remote: '+action);switch(action){case'setSource':selectedSource=payload.source;selectedAlbumId=payload.albumId||null;selectedPersonId=payload.personId||null;if(isSleeping)wakeUp();if(isRunning){clearTimeout(slideshowTimer);stopVideo();}doStartSlideshow();break;case'start':if(isSleeping)wakeUp();if(!isRunning&&selectedSource)doStartSlideshow();break;case'stop':if(isRunning)exitSlideshowInternal();sendStatus('idle');break;case'next':if(isRunning)showNextAsset();break;case'prev':if(isRunning)showPrevAsset();break;case'sleep':goToSleep();break;case'wake':wakeUp();break;case'refresh':location.reload();break;case'setConfig':applyConfigChange(payload);break;}}
|
||||||
|
|||||||
@@ -90,8 +90,14 @@ wss.on('connection', (ws, req) => {
|
|||||||
log('WS admin: ' + ip);
|
log('WS admin: ' + ip);
|
||||||
ws.send(JSON.stringify({ type: 'clientList', clients: getClientList() }));
|
ws.send(JSON.stringify({ type: 'clientList', clients: getClientList() }));
|
||||||
} else {
|
} else {
|
||||||
clients.set(id, { id, ws, name:'', ip, userAgent:ua, connectedAt:now, firstSeen:now, lastSeen:Date.now(), status:msg.status||'idle', config:msg.config||{}, source:msg.source||null });
|
const pid = msg.persistentId || null;
|
||||||
log('WS frame: ' + id + ' (' + ip + ')');
|
const existing = pid ? Array.from(clients.values()).find(c => c.persistentId === pid) : null;
|
||||||
|
const effectiveId = existing ? existing.id : id;
|
||||||
|
const firstName = existing ? existing.name : '';
|
||||||
|
const firstSeen = existing ? existing.firstSeen : now;
|
||||||
|
if (existing) { clients.delete(Array.from(clients.entries()).find(([,c]) => c.persistentId === pid)?.[0]); }
|
||||||
|
clients.set(effectiveId, { id:effectiveId, persistentId:pid, ws, name:firstName, ip, userAgent:ua, connectedAt:now, firstSeen, lastSeen:Date.now(), status:msg.status||'idle', config:msg.config||{}, source:msg.source||null });
|
||||||
|
log('WS frame: ' + effectiveId + (pid?' [persistent]':'') + ' (' + ip + ')');
|
||||||
broadcastAdminClients();
|
broadcastAdminClients();
|
||||||
}
|
}
|
||||||
} else if (msg.type === 'ping') {
|
} else if (msg.type === 'ping') {
|
||||||
|
|||||||
Reference in New Issue
Block a user