discovery-media-player 0.1.26 → 0.1.27

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.
@@ -457,6 +457,20 @@ function createStandaloneContext(env = process.env) {
457
457
  supabasePublishableKey: env.SUPABASE_PUBLISHABLE_KEY || "",
458
458
  mapsKey: env.GOOGLE_MAPS_API_KEY || "",
459
459
  extraFrameAncestors: String(env.DOC_FRAME_ANCESTORS || "").split(/\s+/).filter(Boolean),
460
+
461
+ /**
462
+ * Clé de `localStorage` sous laquelle VOTRE application range la session de ses membres.
463
+ *
464
+ * ⚠️ Vide par défaut, donc la détection des membres est inactive : aucun membre reconnu,
465
+ * aucune usurpation possible. Ce paquet portait `3dd-supabase-auth` en dur — la clé d'un seul
466
+ * hôte, qui ne trouvait rien chez tous les autres, et qui depuis 0.1.25 porte une propriété
467
+ * de sécurité : c'est par elle que l'appartenance se prouve.
468
+ *
469
+ * ⚠️ Et ça ne marche QUE si votre application et le player partagent la même origine. Deux
470
+ * sous-domaines, ce sont deux `localStorage` : aucune valeur ici n'y changera rien. C'est une
471
+ * transition — le bon mécanisme est que l'hôte injecte son membre au rendu de la page.
472
+ */
473
+ hostAuthStorageKey: String(env.PLAYER_HOST_AUTH_STORAGE_KEY || "").trim(),
460
474
  // ⚠️ Un BOOLÉEN, jamais l'URL. La carte d'identité doit pouvoir dire « un émetteur distinct
461
475
  // est configuré » sans dire lequel : c'est le dernier endroit où un hôte devait deviner.
462
476
  // `host-auth` dit que le code SAIT faire la séparation ; ceci dit qu'elle est POSÉE. Sans
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "discovery-media-player",
3
- "version": "0.1.26",
3
+ "version": "0.1.27",
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
@@ -415,7 +415,13 @@ var Live=(function(){
415
415
  var pb=document.getElementById('presBtn');if(pb)pb.style.display='inline-flex';
416
416
  var cb=document.getElementById('chatBtn');if(cb)cb.style.display='inline-flex';var _fb=document.getElementById('chatFab');if(_fb)_fb.classList.add('on');
417
417
  wire();history();
418
- try{sb=window.supabase.createClient(LIVECFG.supaUrl,LIVECFG.supaKey,{realtime:{params:{eventsPerSecond:10}}});
418
+ try{sb=window.supabase.createClient(LIVECFG.supaUrl,LIVECFG.supaKey,{realtime:{params:{eventsPerSecond:10}},
419
+ // ⚠️ UNE CLÉ DÉCLARÉE, PAS CELLE PAR DÉFAUT. Ce client vivra un jour une session anonyme
420
+ // (canal Realtime prive). S'il ecrit sous la cle par defaut et que l'application de l'hote
421
+ // l'utilise aussi sur la meme origine, la session anonyme ECRASE celle du membre connecte.
422
+ // Chez nous les deux cles different deja, mais par heureux hasard : le declarer rend
423
+ // intentionnel ce qui n'etait qu'une consequence, et une topologie peut changer.
424
+ auth:{storageKey:LIVECFG.liveAuthKey||'dmp-live-auth',persistSession:true,autoRefreshToken:true}});
419
425
  ch=sb.channel('plive-'+slug,{config:{presence:{key:(me.email||me.name||'x')+':'+MYID}}});
420
426
  ch.on('presence',{event:'sync'},function(){renderPres(ch.presenceState());});
421
427
  // Plus d'abonnement à la table des messages : elle n'est plus publiée ni lisible
@@ -503,9 +509,9 @@ var Live=(function(){
503
509
  // Membre de l'équipe reconnu via la session app (MÊME ORIGINE, localStorage) → avatar + nom auto.
504
510
  // Le jeton d'acces de la session locale, quand il y en a une. C'est la SEULE chose qui prouve au
505
511
  // serveur qu'on est un membre ; 'member:true' dans la page ne prouve rien, il ne sert qu'a l'affichage.
506
- function accessToken(){try{var raw=localStorage.getItem('3dd-supabase-auth');if(!raw)return '';var s=JSON.parse(raw);
512
+ function accessToken(){try{var raw=localStorage.getItem(LIVECFG.hostAuthKey||'');if(!raw)return '';var s=JSON.parse(raw);
507
513
  return String((s&&(s.access_token||(s.currentSession&&s.currentSession.access_token)||(s.session&&s.session.access_token)))||'');}catch(e){return '';}}
508
- function detectMember(){try{var raw=localStorage.getItem('3dd-supabase-auth');if(!raw)return null;var s=JSON.parse(raw);var u=s&&(s.user||(s.currentSession&&s.currentSession.user)||(s.session&&s.session.user));if(u&&u.email){var m=u.user_metadata||{};return{name:m.name||u.email,email:u.email,avatar:m.avatarUrl||'',member:true,role:'viewer'};}}catch(e){}return null;}
514
+ function detectMember(){try{var raw=localStorage.getItem(LIVECFG.hostAuthKey||'');if(!raw)return null;var s=JSON.parse(raw);var u=s&&(s.user||(s.currentSession&&s.currentSession.user)||(s.session&&s.session.user));if(u&&u.email){var m=u.user_metadata||{};return{name:m.name||u.email,email:u.email,avatar:m.avatarUrl||'',member:true,role:'viewer'};}}catch(e){}return null;}
509
515
  return {connect:connect,disconnect:disconnect,detectMember:detectMember,sendMap:sendMap,onMap:onMap,sendState:sendState,onState:onState};
510
516
  })();`;
511
517
 
@@ -1243,6 +1249,34 @@ function adresseAppelant(req) {
1243
1249
  * Le jeton d'accès, lui, est vérifié par l'hôte. Absent ou invalide : pas membre. Jamais de repli
1244
1250
  * sur ce que l'appelant affirme, sinon la vérification ne sert qu'aux honnêtes.
1245
1251
  */
1252
+ // ── Clés de `localStorage`, et pourquoi ce ne sont plus des constantes ─────────────────────────
1253
+ //
1254
+ // ⚠️ `3dd-supabase-auth` — la clé de session du studio 3D Discovery — était écrite EN DUR à cinq
1255
+ // endroits de ce paquet open source. Conséquence pour tout autre hôte : `detectMember()` et
1256
+ // `accessToken()` ne trouvent jamais rien, donc AUCUN de ses membres n'est reconnu comme tel. La
1257
+ // séparation des populations interne/externe, que ce produit vend, ne fonctionnait que chez nous.
1258
+ //
1259
+ // ⚠️ Et depuis 0.1.25 cette clé porte une propriété de sécurité : c'est par elle que l'appartenance
1260
+ // se prouve. Une constante d'un hôte devenue porteuse pour tous les autres.
1261
+ //
1262
+ // Défaut VIDE, et c'est délibéré : sans clé déclarée, la détection est simplement inactive — aucun
1263
+ // membre, donc aucune usurpation. Un défaut à `3dd-supabase-auth` aurait gardé notre instance en
1264
+ // marche en laissant le défaut de conception intact, et le prochain hôte l'aurait découvert comme
1265
+ // ADV : en constatant que ses statistiques ne séparent rien.
1266
+ //
1267
+ // ⚠️ CE N'EST QU'UNE TRANSITION. Lire le `localStorage` d'une autre application ne peut PAS marcher
1268
+ // quand les origines diffèrent — l'instance ADV est sur `doc.adnfamily.com` et son application sur
1269
+ // `app.adnfamily.com` : deux `localStorage`, aucune configuration n'y changera rien. Le bon
1270
+ // mécanisme est que l'hôte INJECTE son membre au rendu de la page, comme il injecte déjà sa marque.
1271
+ // Tracé dans docs/AUDIT-2026-08-14-SUIVI.md.
1272
+ function cleSessionHote() {
1273
+ return String((PLAYER.config && PLAYER.config.hostAuthStorageKey) || "");
1274
+ }
1275
+ // La session du client Realtime du player, et l'identité d'un invité : à lui, sous son nom. Elles
1276
+ // ne dépendent d'aucun hôte, donc elles restent des constantes — mais plus des constantes d'AUTRUI.
1277
+ const CLE_SESSION_PLAYER = "dmp-live-auth";
1278
+ const CLE_INVITE = "dmp-present-me";
1279
+
1246
1280
  async function membreVerifie(req) {
1247
1281
  try {
1248
1282
  const u = await PLAYER.identity.verifyToken((req.headers && req.headers.authorization) || "");
@@ -1796,7 +1830,7 @@ ${LEGAL_CSS}
1796
1830
  <script nonce="${nonce}">${PLAYER_BROWSER_JS}</script>
1797
1831
  <script nonce="${nonce}" src="${PDFJS}/pdf.min.js"></script>
1798
1832
  ${preview ? `<script nonce="${nonce}" src="${SUPAJS}"></script>
1799
- <script nonce="${nonce}">var LIVECFG={supaUrl:${JSON.stringify(share.supa_url || "")},supaKey:${JSON.stringify(share.supa_key || "")}};var GMAPS_KEY=${JSON.stringify((PLAYER.config && PLAYER.config.mapsKey) || "" || "")};${LIVE_JS}
1833
+ <script nonce="${nonce}">var LIVECFG={supaUrl:${JSON.stringify(share.supa_url || "")},supaKey:${JSON.stringify(share.supa_key || "")},hostAuthKey:${JSON.stringify(cleSessionHote())},liveAuthKey:${JSON.stringify(CLE_SESSION_PLAYER)},guestKey:${JSON.stringify(CLE_INVITE)}};var GMAPS_KEY=${JSON.stringify((PLAYER.config && PLAYER.config.mapsKey) || "" || "")};${LIVE_JS}
1800
1834
  ${MAP_JS}</script>` : ""}
1801
1835
  <script nonce="${nonce}">
1802
1836
  (function(){
@@ -1871,7 +1905,7 @@ ${LEGAL_CSS}
1871
1905
  var PRES=null, _pushT=null, _hbIv=0;
1872
1906
  // JWT de la session app (MÊME ORIGINE, localStorage) → autorise le rattachement de la présentation au membre
1873
1907
  // (reprise / liste / transfert) et les actions authentifiées (reclaim).
1874
- function appToken(){ try{ var raw=localStorage.getItem('3dd-supabase-auth'); if(!raw)return''; var s=JSON.parse(raw); var t=(s&&(s.access_token||(s.currentSession&&s.currentSession.access_token)||(s.session&&s.session.access_token)))||''; return t; }catch(e){ return ''; } }
1908
+ function appToken(){ try{ var raw=localStorage.getItem(LIVECFG.hostAuthKey||''); if(!raw)return''; var s=JSON.parse(raw); var t=(s&&(s.access_token||(s.currentSession&&s.currentSession.access_token)||(s.session&&s.session.access_token)))||''; return t; }catch(e){ return ''; } }
1875
1909
  function ctlKey(slug){ return '3dd-pres-ctl-'+slug; }
1876
1910
  function saveCtl(slug,control){ try{ localStorage.setItem(ctlKey(slug),control); }catch(e){} }
1877
1911
  function clearCtl(slug){ try{ localStorage.removeItem(ctlKey(slug)); }catch(e){} }
@@ -2314,7 +2348,7 @@ function presentHtml(pres, nonce, logoUrl, supaUrl, supaKey) {
2314
2348
  })();
2315
2349
  </script>
2316
2350
  <script nonce="${nonce}">
2317
- var LIVECFG={supaUrl:${JSON.stringify(supaUrl || "")},supaKey:${JSON.stringify(supaKey || "")}};var GMAPS_KEY=${JSON.stringify((PLAYER.config && PLAYER.config.mapsKey) || "" || "")};
2351
+ var LIVECFG={supaUrl:${JSON.stringify(supaUrl || "")},supaKey:${JSON.stringify(supaKey || "")},hostAuthKey:${JSON.stringify(cleSessionHote())},liveAuthKey:${JSON.stringify(CLE_SESSION_PLAYER)},guestKey:${JSON.stringify(CLE_INVITE)}};var GMAPS_KEY=${JSON.stringify((PLAYER.config && PLAYER.config.mapsKey) || "" || "")};
2318
2352
  ${LIVE_JS}
2319
2353
  ${MAP_JS}
2320
2354
  (function(){
@@ -2337,19 +2371,19 @@ function presentHtml(pres, nonce, logoUrl, supaUrl, supaKey) {
2337
2371
  // « Reprendre la main » : si le membre connecté (même origine) devient propriétaire de CETTE présentation
2338
2372
  // (après un transfert), on affiche un bouton pour ouvrir la visionneuse en pilotage — sinon on ne peut pas
2339
2373
  // piloter depuis la page audience.
2340
- function appTok(){ try{ var raw=localStorage.getItem('3dd-supabase-auth'); if(!raw)return''; var s=JSON.parse(raw); return (s&&(s.access_token||(s.currentSession&&s.currentSession.access_token)||(s.session&&s.session.access_token)))||''; }catch(e){return'';} }
2374
+ function appTok(){ try{ var raw=localStorage.getItem(LIVECFG.hostAuthKey||''); if(!raw)return''; var s=JSON.parse(raw); return (s&&(s.access_token||(s.currentSession&&s.currentSession.access_token)||(s.session&&s.session.access_token)))||''; }catch(e){return'';} }
2341
2375
  function checkOwner(){ var tk=appTok(); if(!tk)return; fetch('/api/doc',{method:'POST',headers:{'Content-Type':'application/json','Authorization':'Bearer '+tk},body:JSON.stringify({action:'present-list'})}).then(function(r){return r.json();}).then(function(d){ var mine=false; if(d&&d.presentations){ for(var i=0;i<d.presentations.length;i++){ if(d.presentations[i].slug===slug&&d.presentations[i].mine){mine=true;break;} } } var b=document.getElementById('takeOver'); if(b)b.style.display=mine?'inline-flex':'none'; }).catch(function(){}); }
2342
2376
  function startOwnerWatch(){ var b=document.getElementById('takeOver'); if(b&&!b._w){b._w=1;b.addEventListener('click',function(){ location.href='/app/documents?resume='+encodeURIComponent(slug); });} checkOwner(); setInterval(checkOwner,12000); }
2343
2377
  var me=Live.detectMember();
2344
2378
  if(me){ Live.connect(slug, me); startOwnerWatch(); return; }
2345
- var saved=null; try{ saved=JSON.parse(localStorage.getItem('3dd-present-me')||'null'); }catch(e){}
2379
+ var saved=null; try{ saved=JSON.parse(localStorage.getItem(LIVECFG.guestKey||'dmp-present-me')||'null'); }catch(e){}
2346
2380
  if(saved&&saved.name){ Live.connect(slug, saved); return; }
2347
2381
  // Externe : on demande le nom pour participer.
2348
2382
  var o=document.createElement('div'); o.className='join';
2349
2383
  o.innerHTML='<div class=join-card><h4>Rejoindre la présentation</h4><p>Votre nom pour participer à la discussion.</p><input id=jName placeholder="Votre nom" maxlength=60 autocomplete=name><input id=jMail placeholder="Email (facultatif)" maxlength=120 autocomplete=email><button id=jGo>Rejoindre</button></div>';
2350
2384
  document.body.appendChild(o);
2351
2385
  var n=o.querySelector('#jName'); try{ n.focus(); }catch(e){}
2352
- function go(){ var name=(n.value||'').trim()||'Invité'; var email=(o.querySelector('#jMail').value||'').trim(); var me2={name:name,email:email,avatar:'',member:false,role:'viewer'}; try{ localStorage.setItem('3dd-present-me',JSON.stringify(me2)); }catch(e){} o.parentNode&&o.parentNode.removeChild(o); Live.connect(slug, me2); }
2386
+ function go(){ var name=(n.value||'').trim()||'Invité'; var email=(o.querySelector('#jMail').value||'').trim(); var me2={name:name,email:email,avatar:'',member:false,role:'viewer'}; try{ localStorage.setItem(LIVECFG.guestKey||'dmp-present-me',JSON.stringify(me2)); }catch(e){} o.parentNode&&o.parentNode.removeChild(o); Live.connect(slug, me2); }
2353
2387
  o.querySelector('#jGo').addEventListener('click',go);
2354
2388
  n.addEventListener('keydown',function(e){ if(e.key==='Enter') go(); });
2355
2389
  })();