discovery-media-player 0.1.30 → 0.1.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/server/handler.js +37 -5
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "discovery-media-player",
3
- "version": "0.1.30",
3
+ "version": "0.1.31",
4
4
  "description": "Self-hosted document viewer: per-recipient tracked links, reading analytics, live presentation. The core knows nothing about the application hosting it — everything it borrows arrives through an injected context.",
5
5
  "keywords": [
6
6
  "pdf-viewer",
package/server/handler.js CHANGED
@@ -246,7 +246,9 @@ var Live=(function(){
246
246
  function onMap(fn){_onMap=fn;}
247
247
  // État de la présentation diffusé par le présentateur — même canal que la carte. Sert à se
248
248
  // passer de la lecture anonyme des tables : l'audience n'a plus besoin de lire la ligne.
249
- function sendState(p){try{if(ch)ch.send({type:'broadcast',event:'state',payload:p});}catch(e){}}
249
+ // Un SIGNAL, pas un état. L'audience relit depuis 0.1.19 et ignore déjà cette charge ; la laisser
250
+ // partir donnait l'illusion qu'elle sert, et invitait le prochain à s'en resservir.
251
+ function sendState(){try{if(ch)ch.send({type:'broadcast',event:'state',payload:{}});}catch(e){}}
250
252
  // CHAT EN DIFFUSION. Les messages arrivaient jusqu'ici par la lecture de TABLE en temps réel,
251
253
  // qui exige que cette table soit lisible publiquement — donc, avec la clé publiable, les
252
254
  // conversations de TOUTES les présentations, pas seulement la sienne. C'était le dernier
@@ -1975,9 +1977,36 @@ ${LEGAL_CSS}
1975
1977
  function showBar(slug){ var lk=document.getElementById('pbarLink'); if(lk) lk.value=location.origin+'/present/'+slug; var pb=document.getElementById('pbar'); if(pb) pb.style.display='flex'; }
1976
1978
  // Le présentateur DIFFUSE l'état qu'il vient de persister. La base reste la vérité (les
1977
1979
  // arrivants tardifs la relisent) ; la diffusion évite à l'audience de lire la table.
1978
- function diffuserEtat(extra){ if(!PRES||!window.Live)return; try{ Live.sendState(Object.assign({active:true,current_page:cur||1,file_url:CFG.present&&CFG.present.url||null,updated_at:new Date().toISOString()},extra||{})); }catch(e){} }
1979
- function pushPage(){ if(!PRES)return; clearTimeout(_pushT); _pushT=setTimeout(function(){ if(!PRES)return; diffuserEtat({current_page:cur||1}); fetch('/api/doc',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({action:'present-page',slug:PRES.slug,control:PRES.control,page:cur||1})}).catch(function(){}); },250); }
1980
- function endPresent(){ if(!PRES)return; diffuserEtat({active:false}); var p=PRES; PRES=null; clearInterval(_hbIv); clearCtl(p.slug); var pb=document.getElementById('pbar'); if(pb)pb.style.display='none'; try{ if(window.Live) Live.disconnect(); }catch(e){} try{ var b=JSON.stringify({action:'present-end',slug:p.slug,control:p.control}); if(navigator.sendBeacon){navigator.sendBeacon('/api/doc',new Blob([b],{type:'application/json'}));} else {fetch('/api/doc',{method:'POST',headers:{'Content-Type':'application/json'},body:b,keepalive:true});} }catch(e){} }
1980
+ // ⚠️ ÉCRIRE, PUIS SIGNALER — ET PAS L'INVERSE.
1981
+ //
1982
+ // Le signal partait AVANT l'écriture. L'audience relisait donc l'état pendant que la base
1983
+ // portait encore l'ancien, et aucun second signal n'était garanti : la page tournée se perdait
1984
+ // jusqu'au filet de resynchronisation. Le commentaire d'origine affirmait pourtant l'ordre
1985
+ // inverse — il décrivait l'intention, pas le code.
1986
+ //
1987
+ // Depuis 0.1.19 le signal ne sert qu'à dire « relis » : le retarder d'un aller-retour ne coûte
1988
+ // rien, alors que l'émettre trop tôt fait relire pour rien ET perdre le changement.
1989
+ // (audit P0-3)
1990
+ function diffuserEtat(){ if(!PRES||!window.Live)return; try{ Live.sendState(); }catch(e){} }
1991
+ function pushPage(){ if(!PRES)return; clearTimeout(_pushT); _pushT=setTimeout(function(){ if(!PRES)return;
1992
+ fetch('/api/doc',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({action:'present-page',slug:PRES.slug,control:PRES.control,page:cur||1})})
1993
+ .then(function(r){ if(r&&r.ok)diffuserEtat(); })
1994
+ .catch(function(){});
1995
+ },250); }
1996
+ // ⚠️ TROIS GESTES, ET L'ORDRE ÉTAIT LE PIRE DES SIX. On signalait la fin, puis on COUPAIT LE
1997
+ // CANAL, puis on envoyait l'avis de fin. Le signal partait donc avant l'écriture (relecture sur
1998
+ // un état périmé), et la coupure avant l'envoi. L'audience pouvait ne jamais apprendre que la
1999
+ // présentation était terminée — jusqu'au filet, 25 s plus tard, ou jamais si elle s'était
2000
+ // rechargée entre-temps.
2001
+ //
2002
+ // sendBeacon ne s'attend pas, mais il rend la main une fois la requête MISE EN FILE : signaler
2003
+ // juste après, puis couper, respecte l'ordre autant que ce transport le permet.
2004
+ function endPresent(){ if(!PRES)return; var p=PRES; PRES=null; clearInterval(_hbIv); clearCtl(p.slug);
2005
+ var pb=document.getElementById('pbar'); if(pb)pb.style.display='none';
2006
+ try{ var b=JSON.stringify({action:'present-end',slug:p.slug,control:p.control});
2007
+ if(navigator.sendBeacon){navigator.sendBeacon('/api/doc',new Blob([b],{type:'application/json'}));}
2008
+ else {fetch('/api/doc',{method:'POST',headers:{'Content-Type':'application/json'},body:b,keepalive:true});} }catch(e){}
2009
+ try{ if(window.Live){ Live.sendState(); Live.disconnect(); } }catch(e){} }
1981
2010
  // Transfert : je passe la main → je cesse de piloter SANS clôturer la présentation (le nouvel owner reprendra).
1982
2011
  function stopPilotingLocally(){ if(!PRES)return; var s=PRES.slug; PRES=null; clearInterval(_hbIv); clearCtl(s); var pb=document.getElementById('pbar'); if(pb)pb.style.display='none'; try{ if(window.Live) Live.disconnect(); }catch(e){} }
1983
2012
  function liveConnect(slug,control){ try{ if(window.Live){ var P=CFG.present||{}; Live.connect(slug,{name:P.by||'Présentateur',email:P.email||'',avatar:P.av||'',role:'presenter',member:true},control); } }catch(e){} }
@@ -2008,7 +2037,10 @@ ${LEGAL_CSS}
2008
2037
  }).catch(function(){});
2009
2038
  }
2010
2039
  // Carte live : persiste le contenu (present-content, JWT) → l'audience bascule/suit via Realtime.
2011
- function presentContent(content){ if(!PRES)return; diffuserEtat({content:content||null}); var h={'Content-Type':'application/json'}; var tk=appToken(); if(tk) h['Authorization']='Bearer '+tk; fetch('/api/doc',{method:'POST',headers:h,body:JSON.stringify({action:'present-content',slug:PRES.slug,content:content})}).catch(function(){}); }
2040
+ function presentContent(content){ if(!PRES)return; var h={'Content-Type':'application/json'}; var tk=appToken(); if(tk) h['Authorization']='Bearer '+tk;
2041
+ fetch('/api/doc',{method:'POST',headers:h,body:JSON.stringify({action:'present-content',slug:PRES.slug,content:content})})
2042
+ .then(function(r){ if(r&&r.ok)diffuserEtat(); })
2043
+ .catch(function(){}); }
2012
2044
  function showMap(){ if(!PRES||!window.Map3DD)return; var wrap=document.getElementById('mapWrap'); if(wrap&&wrap.classList.contains('on')){ Map3DD.enter(null,true,presentContent); return; } var init=Player.presentation.initialMapContent(); presentContent(init); Map3DD.enter(init,true,presentContent); }
2013
2045
  function hideMap(){ if(!window.Map3DD)return; Map3DD.exit(); presentContent(null); }
2014
2046
  // Mode INTÉGRÉ : la page hôte délègue sa barre de titre à celle-ci. On lui dit