discovery-media-player 0.1.69 → 0.1.70
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.
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "discovery-media-player",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.70",
|
|
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",
|
|
@@ -55,12 +55,12 @@
|
|
|
55
55
|
"scripts": {
|
|
56
56
|
"prepare": "node tools/install-hooks.mjs",
|
|
57
57
|
"start": "node bin/serve.js",
|
|
58
|
-
"build": "node build/bundle.mjs && tsc -p tsconfig.build.json && node -e \"import('./build/bundle.mjs').then(m=>m.marquerDistEsm())\"",
|
|
58
|
+
"build": "node -e \"require('fs').existsSync('build/bundle.mjs')||(console.error('Ce script ne vit pas dans le paquet publié - le champ scripts annonce plus que le tarball ne contient. Clonez le depot puis npm ci : https://github.com/Juli1artha/discovery-media-player'),process.exit(1))\" && node build/bundle.mjs && tsc -p tsconfig.build.json && node -e \"import('./build/bundle.mjs').then(m=>m.marquerDistEsm())\"",
|
|
59
59
|
"test": "node -e \"require('fs').existsSync('server/__tests__')||(console.error('Ce banc ne vit pas dans le paquet publié - le champ scripts annonce plus que le tarball ne contient. Clonez le depot puis npm ci : https://github.com/Juli1artha/discovery-media-player'),process.exit(1))\" && vitest run",
|
|
60
60
|
"test:watch": "vitest",
|
|
61
|
-
"lint": "eslint bin context server src build",
|
|
61
|
+
"lint": "node -e \"require('fs').existsSync('eslint.config.mjs')||(console.error('Ce script ne vit pas dans le paquet publié - le champ scripts annonce plus que le tarball ne contient. Clonez le depot puis npm ci : https://github.com/Juli1artha/discovery-media-player'),process.exit(1))\" && eslint bin context server src build",
|
|
62
62
|
"lint:fix": "eslint bin context server src build --fix",
|
|
63
|
-
"typecheck": "tsc --noEmit",
|
|
63
|
+
"typecheck": "node -e \"require('fs').existsSync('tsconfig.json')||(console.error('Ce script ne vit pas dans le paquet publié - le champ scripts annonce plus que le tarball ne contient. Clonez le depot puis npm ci : https://github.com/Juli1artha/discovery-media-player'),process.exit(1))\" && tsc --noEmit",
|
|
64
64
|
"prepublishOnly": "npm run build && npm test",
|
|
65
65
|
"test:e2e": "node -e \"require('fs').existsSync('vitest.e2e.config.mjs')||(console.error('Ce banc ne vit pas dans le paquet publié - le champ scripts annonce plus que le tarball ne contient. Clonez le depot puis npm ci : https://github.com/Juli1artha/discovery-media-player'),process.exit(1))\" && vitest run --config vitest.e2e.config.mjs",
|
|
66
66
|
"test:base": "node -e \"require('fs').existsSync('vitest.base.config.mjs')||(console.error('Ce banc ne vit pas dans le paquet publié - le champ scripts annonce plus que le tarball ne contient. Clonez le depot puis npm ci : https://github.com/Juli1artha/discovery-media-player'),process.exit(1))\" && vitest run --config vitest.base.config.mjs"
|
package/server/handler.js
CHANGED
|
@@ -453,10 +453,21 @@ var Live=(function(){
|
|
|
453
453
|
var t=document.getElementById('lModalT'),d=document.getElementById('lModalD'),y=document.getElementById('lModalYes'),n=document.getElementById('lModalNo');
|
|
454
454
|
if(t)t.textContent=opts.title||'Confirmer ?';if(d){d.textContent=opts.desc||'';d.style.display=opts.desc?'block':'none';}
|
|
455
455
|
if(y)y.textContent=opts.ok||'Confirmer';
|
|
456
|
-
|
|
457
|
-
|
|
456
|
+
// ⚠️ UN role=dialog SANS PIÈGE DE FOCUS EST UNE DÉCLARATION SANS EFFET (septième audit) :
|
|
457
|
+
// Tab sortait vers la page derrière, et à la fermeture le focus tombait sur <body> — un
|
|
458
|
+
// utilisateur clavier repartait de zéro. Le piège boucle entre les DEUX boutons, Échap et
|
|
459
|
+
// Entrée existaient déjà, et l'élément qui avait le focus à l'ouverture le RETROUVE.
|
|
460
|
+
var avant=document.activeElement;
|
|
461
|
+
function close(){m.classList.remove('open');if(y)y.onclick=null;if(n)n.onclick=null;m.onclick=null;document.removeEventListener('keydown',key);
|
|
462
|
+
if(avant&&avant.focus)try{avant.focus();}catch(e){}}
|
|
463
|
+
function key(e){if(e.key==='Escape'){close();}else if(e.key==='Enter'){close();if(onOk)onOk();}
|
|
464
|
+
else if(e.key==='Tab'){var f=[n,y].filter(function(x){return x;});if(!f.length)return;e.preventDefault();
|
|
465
|
+
var i=f.indexOf(document.activeElement);var j=e.shiftKey?(i<=0?f.length-1:i-1):(i>=f.length-1?0:i+1);try{f[j].focus();}catch(err){}}}
|
|
458
466
|
if(y)y.onclick=function(){close();if(onOk)onOk();};if(n)n.onclick=close;m.onclick=function(e){if(e.target===m)close();};
|
|
459
467
|
document.addEventListener('keydown',key);m.classList.add('open');if(y)try{y.focus();}catch(e){}}
|
|
468
|
+
// Crochet interne (même famille que __presRelireEtat) : le piège de focus ne se PROUVE que
|
|
469
|
+
// piloté clavier dans un vrai navigateur, et la seule voie UI passe par un message à soi.
|
|
470
|
+
try{window.__confirmDialog=confirmDialog;}catch(e){}
|
|
460
471
|
function history(){fetch('/api/doc?present='+encodeURIComponent(SLUG)+'&chat=1').then(function(r){return r.json();}).then(function(d){var box=document.getElementById('chatMsgs');if(d&&d.messages&&d.messages.length){d.messages.forEach(function(m){addMsg(m);});}else if(box&&!box.children.length){box.innerHTML='<div class=chat-empty>Aucun message. Lancez la discussion.</div>';}if(d&&typeof d.locked!=='undefined')applyLock(d.locked);_histDone=true;}).catch(function(){_histDone=true;});}
|
|
461
472
|
function react(id,e){if(!ME||!id||!e)return;
|
|
462
473
|
// ⚠️ ON ENVOIE L'ÉTAT VOULU, PAS « INVERSE ». Basculer n'a de sens qu'une fois : un renvoi
|
|
@@ -847,7 +858,7 @@ var Map3DD=(function(){
|
|
|
847
858
|
var svb=document.getElementById('mapSV'); if(svb&&!svb._w){svb._w=1;svb.addEventListener('click',function(){ if(!map)return; var c=useG?[map.getCenter().lat(),map.getCenter().lng()]:[map.getCenter().lat,map.getCenter().lng]; goSV(c); });}
|
|
848
859
|
var tm=document.getElementById('mapToMap'); if(tm&&!tm._w){tm._w=1;tm.addEventListener('click',toMap);} }
|
|
849
860
|
function doSearch(text){ text=(text||'').trim(); var res=document.getElementById('mapRes'); if(!text||!res)return; res.innerHTML='<div style="padding:9px 14px;color:#888;font-size:12px">Recherche…</div>';
|
|
850
|
-
fetch('https://nominatim.openstreetmap.org/search?format=json&limit=5&q='+encodeURIComponent(text),{headers:{Accept:'application/json'}}).then(function(r){return r.json();}).then(function(l){ if(!l||!l.length){res.innerHTML='<div style="padding:9px 14px;color:#888;font-size:12px">Aucun résultat.</div>';return;} res.innerHTML=l.map(function(o){var la=Number(o.lat),lo=Number(o.lon);if(!isFinite(la)||!isFinite(lo))return '';return '<button data-lat="'+la+'" data-lng="'+lo+'">'+String(o.display_name||'').replace(/</g,'<')+'</button>';}).join(''); }).catch(function(){res.innerHTML='<div style="padding:9px 14px;color:#c0392b;font-size:12px">Recherche indisponible.</div>';}); }
|
|
861
|
+
fetch('https://nominatim.openstreetmap.org/search?format=json&limit=5&q='+encodeURIComponent(text),{headers:{Accept:'application/json'}}).then(function(r){return r.json();}).then(function(l){ if(!l||!l.length){res.innerHTML='<div style="padding:9px 14px;color:#888;font-size:12px">Aucun résultat.</div>';return;} res.innerHTML=l.map(function(o){var la=Number(o.lat),lo=Number(o.lon);if(!isFinite(la)||!isFinite(lo)||la<-90||la>90||lo<-180||lo>180)return '';return '<button data-lat="'+la+'" data-lng="'+lo+'">'+String(o.display_name||'').replace(/</g,'<')+'</button>';}).join(''); }).catch(function(){res.innerHTML='<div style="padding:9px 14px;color:#c0392b;font-size:12px">Recherche indisponible.</div>';}); }
|
|
851
862
|
// ── Street View (Google) ─────────────────────────────────────────────────────────────────────────
|
|
852
863
|
function tempHint(t){ var h=document.getElementById('mapHint'); if(h){h.textContent=t; setTimeout(function(){ if(h.textContent===t)h.textContent=(isPres?'':'Vue du présentateur — en direct'); },2600);} }
|
|
853
864
|
// Le présentateur passe en Street View depuis le centre de la carte : on cherche le panorama le plus proche.
|
|
@@ -3613,10 +3624,21 @@ async function handler(req, res) {
|
|
|
3613
3624
|
// demandes suivantes la trouveront par l'unicité, et les doublons d'avant 0011
|
|
3614
3625
|
// s'éteignent d'eux-mêmes faute d'être resservis.
|
|
3615
3626
|
const cleDispo = await require("./schema").attendue("liensUniques");
|
|
3616
|
-
|
|
3617
|
-
|
|
3618
|
-
|
|
3619
|
-
|
|
3627
|
+
// ⚠️ MÊME RATTRAPAGE QUE LE CHEMIN DE CRÉATION (septième audit : le correctif ne
|
|
3628
|
+
// s'était pas appliqué à lui-même). Deux doublons d'avant 0011 en concurrence :
|
|
3629
|
+
// l'index refuse la seconde pose de clé — on relit le gagnant au lieu de rendre 500.
|
|
3630
|
+
try {
|
|
3631
|
+
await PLAYER.db.request(`commercial_doc_shares?slug=eq.${encodeURIComponent(dejaLa[0].slug)}`, {
|
|
3632
|
+
method: "PATCH", headers: { Prefer: "return=minimal" },
|
|
3633
|
+
body: { doc_title: body.docTitle || null, file_url: String(body.fileUrl), file_name: body.fileName || null, revoked: false, ...(cleDispo ? { idem_key: cleHote } : {}) },
|
|
3634
|
+
});
|
|
3635
|
+
} catch (erreur) {
|
|
3636
|
+
if (!String((erreur && erreur.message) || "").includes("409")) throw erreur;
|
|
3637
|
+
try { PLAYER.errors.capture(new Error("backfill hôte : la clé était déjà posée ailleurs — " + docId), { route: "hostshare", benin: true }); } catch { /* jamais bloquant */ }
|
|
3638
|
+
const gagnant = await PLAYER.db.request(`commercial_doc_shares?idem_key=eq.${encodeURIComponent(cleHote)}&select=slug&limit=1`);
|
|
3639
|
+
if (!Array.isArray(gagnant) || !gagnant[0]) throw erreur;
|
|
3640
|
+
return jd(200, { ok: true, slug: gagnant[0].slug, reused: true });
|
|
3641
|
+
}
|
|
3620
3642
|
return jd(200, { ok: true, slug: dejaLa[0].slug, reused: true });
|
|
3621
3643
|
}
|
|
3622
3644
|
// `createdBy` reste NUL : personne ne l'a créé. Le filtre « mes liens » compare
|
|
@@ -3684,7 +3706,16 @@ async function handler(req, res) {
|
|
|
3684
3706
|
const cleTest = cleIdempotence("repetition", [docId]);
|
|
3685
3707
|
if (Array.isArray(ex) && ex[0]) {
|
|
3686
3708
|
const cleDispo = await require("./schema").attendue("liensUniques");
|
|
3687
|
-
|
|
3709
|
+
// Même rattrapage que ci-dessus : le backfill de la répétition pose la même clé.
|
|
3710
|
+
try {
|
|
3711
|
+
await PLAYER.db.request(`commercial_doc_shares?slug=eq.${encodeURIComponent(ex[0].slug)}`, { method: "PATCH", headers: { Prefer: "return=minimal" }, body: { doc_title: body.docTitle || null, file_url: String(body.fileUrl), file_name: body.fileName || null, bot_enabled: true, bot_guided: true, bot_profile_id: (body.profileId || "").trim() || null, revoked: false, ...(cleDispo ? { idem_key: cleTest } : {}) } });
|
|
3712
|
+
} catch (erreur) {
|
|
3713
|
+
if (!String((erreur && erreur.message) || "").includes("409")) throw erreur;
|
|
3714
|
+
try { PLAYER.errors.capture(new Error("backfill répétition : la clé était déjà posée ailleurs — " + docId), { route: "docshare-test", benin: true }); } catch { /* jamais bloquant */ }
|
|
3715
|
+
const gagnant = await PLAYER.db.request(`commercial_doc_shares?idem_key=eq.${encodeURIComponent(cleTest)}&select=slug&limit=1`);
|
|
3716
|
+
if (!Array.isArray(gagnant) || !gagnant[0]) throw erreur;
|
|
3717
|
+
return jd(200, { ok: true, slug: gagnant[0].slug, reused: true });
|
|
3718
|
+
}
|
|
3688
3719
|
return jd(200, { ok: true, slug: ex[0].slug });
|
|
3689
3720
|
}
|
|
3690
3721
|
// Deux ouvertures simultanées de la répétition : la contrainte tranche, le perdant relit.
|
package/supabase/init.sql
CHANGED
|
@@ -68,6 +68,12 @@ alter table public.commercial_doc_shares
|
|
|
68
68
|
create unique index if not exists cds_idem_key_uniq
|
|
69
69
|
on public.commercial_doc_shares (idem_key)
|
|
70
70
|
where idem_key is not null;
|
|
71
|
+
comment on column public.commercial_doc_shares.idem_key is
|
|
72
|
+
'Clé d''idempotence des liens SYSTÈME (hôte, répétition) : empreinte « genre:sha256(JSON des '
|
|
73
|
+
'composants) » — genre = hote | repetition. Les clés historiques au format concaténé '
|
|
74
|
+
'« hote:<doc>|<email> » se remplacent d''elles-mêmes au réemploi (backfill). Nulle sur les '
|
|
75
|
+
'liens ordinaires. Unique quand renseignée : deux demandes simultanées ne créent qu''un lien, '
|
|
76
|
+
'la seconde relit le gagnant.';
|
|
71
77
|
create index if not exists cds_parent_idx on public.commercial_doc_shares (parent_slug);
|
|
72
78
|
create index if not exists idx_doc_shares_brand_key
|
|
73
79
|
on public.commercial_doc_shares (brand_key) where brand_key is not null;
|
|
@@ -17,6 +17,9 @@
|
|
|
17
17
|
alter table public.commercial_doc_shares
|
|
18
18
|
add column if not exists idem_key text;
|
|
19
19
|
|
|
20
|
+
-- ⚠️ Le format décrit ci-dessous était celui de l'époque ; la clé est devenue une EMPREINTE
|
|
21
|
+
-- (genre:sha256) et la 0012 remet ce commentaire de colonne à jour EN BASE. Le texte d'origine
|
|
22
|
+
-- reste ici tel qu'appliqué — une migration ne se réécrit pas, elle se corrige par la suivante.
|
|
20
23
|
comment on column public.commercial_doc_shares.idem_key is
|
|
21
24
|
'Clé d''idempotence des liens SYSTÈME (hôte, répétition) : « hote:<doc>|<destinataire attesté> » '
|
|
22
25
|
'ou « repetition:<doc> ». Nulle sur les liens ordinaires. Unique quand renseignée : deux '
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
-- LE COMMENTAIRE DE COLONNE DÉCRIVAIT UN FORMAT MORT.
|
|
2
|
+
--
|
|
3
|
+
-- La 0011 documentait la clé sous sa forme concaténée « hote:<doc>|<destinataire attesté> » ;
|
|
4
|
+
-- depuis, la clé est une EMPREINTE — genre:sha256(JSON.stringify(composants)) — parce que la
|
|
5
|
+
-- concaténation portait trois défauts (troncature à l'écriture mais relecture entière, collision
|
|
6
|
+
-- de préfixe, frontière « | » déplaçable — septième audit). Un commentaire de colonne se lit dans
|
|
7
|
+
-- la base, longtemps après les fichiers : celui-ci aurait envoyé un exploitant chercher des clés
|
|
8
|
+
-- lisibles qui n'existent plus.
|
|
9
|
+
--
|
|
10
|
+
-- Sans lui : rien ne casse — seule la documentation EN BASE reste fausse.
|
|
11
|
+
comment on column public.commercial_doc_shares.idem_key is
|
|
12
|
+
'Clé d''idempotence des liens SYSTÈME (hôte, répétition) : empreinte « genre:sha256(JSON des '
|
|
13
|
+
'composants) » — genre = hote | repetition. Les clés historiques au format concaténé '
|
|
14
|
+
'« hote:<doc>|<email> » se remplacent d''elles-mêmes au réemploi (backfill). Nulle sur les '
|
|
15
|
+
'liens ordinaires. Unique quand renseignée : deux demandes simultanées ne créent qu''un lien, '
|
|
16
|
+
'la seconde relit le gagnant.';
|