discovery-media-player 0.1.97 → 0.1.99
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 +1 -1
- package/server/gabarit-live.js +15 -2
- package/server/presentations.js +33 -14
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "discovery-media-player",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.99",
|
|
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/gabarit-live.js
CHANGED
|
@@ -179,7 +179,7 @@ var Live=(function(){
|
|
|
179
179
|
// battait deux fois. On les déclare au niveau du CYCLE DE VIE : ce qui doit être arrêté doit
|
|
180
180
|
// être visible depuis l'endroit qui arrête.
|
|
181
181
|
var _ordEtat=null,_ordChat=null,_filet=null;
|
|
182
|
-
var sb=null,ch=null,ME=null,SLUG=null,CONTROL=null,LOCKED=false,AUTHTOK=null,PRESENT=[],PRESNAME='',seen=Object.create(null),msgEls=Object.create(null),msgData=Object.create(null),replyCtx=null,typers=Object.create(null),pdfCache=Object.create(null),_tyT=0,_tyIv=0,_atIv=0,unread=0,autoOpened=false,_histDone=false,_phWired=false,_onMap=null,_onState=null,_peekT=0,MUTED=false,_maxSeq=0;
|
|
182
|
+
var sb=null,ch=null,ME=null,SLUG=null,CONTROL=null,LOCKED=false,AUTHTOK=null,PRESENT=[],PRESNAME='',seen=Object.create(null),msgEls=Object.create(null),msgData=Object.create(null),replyCtx=null,typers=Object.create(null),pdfCache=Object.create(null),_tyT=0,_tyIv=0,_atIv=0,unread=0,autoOpened=false,_histDone=false,_phWired=false,_onMap=null,_onState=null,_peekT=0,MUTED=false,_maxSeq=0,_ptJeton='';
|
|
183
183
|
try{ MUTED=localStorage.getItem('3dd-present-mute')==='1'; }catch(e){}
|
|
184
184
|
// Couper/rétablir les notifications du chat (cloche) : coupé = plus de ticker ni de pulse (badge silencieux gardé).
|
|
185
185
|
function applyMute(){ var b=document.getElementById('chatMute'); if(b){b.classList.toggle('muted',MUTED);b.title=MUTED?'Réactiver les notifications du chat':'Couper les notifications du chat';} setBadge(); }
|
|
@@ -253,8 +253,21 @@ var Live=(function(){
|
|
|
253
253
|
// Le serveur ne croit plus 'isMember' ni 'isPresenter' sur parole : l'appartenance se prouve par
|
|
254
254
|
// le jeton d'acces de la session, le titre de presentateur par le control_token. Ce qui part d'ici
|
|
255
255
|
// n'est plus qu'une AFFIRMATION, et le serveur la remplace par ce qu'il a verifie.
|
|
256
|
+
// ⚠️ JETON DE PRÉSENCE (P1c étape 2, incrément 3). Le battement PORTE le jeton reçu au précédent
|
|
257
|
+
// (champ pt) : le serveur en dérive la clé de la ligne, donc un tiers ne peut plus poster notre clé
|
|
258
|
+
// pour écraser notre présence. Tant qu'on n'en a pas, on demande l'émission (wantToken) — ce marqueur
|
|
259
|
+
// distingue un BOOTSTRAP moderne d'un client LEGACY, sans quoi sansJeton ne retomberait jamais à
|
|
260
|
+
// zéro et la porte ne pourrait jamais se fermer. La réponse est lue pour ranger le jeton émis ; un
|
|
261
|
+
// hôte sans PLAYER_PRESENCE_SECRET n'en renvoie pas, et tout continue comme avant (legacy).
|
|
262
|
+
// ⚠️ AUCUN BACKTICK DANS CE COMMENTAIRE : ce fichier ÉMET du JS via des template literals — un
|
|
263
|
+
// backtick ici ferme la chaîne et casse tout le module (vu à l'instant, 33 essais rouges d'un coup).
|
|
256
264
|
function sendAttend(){ if(!SLUG||!ME)return; try{ var h={'Content-Type':'application/json'};var jw=accessToken();if(jw)h.Authorization='Bearer '+jw;
|
|
257
|
-
|
|
265
|
+
var corps={action:'present-attend',slug:SLUG,control:CONTROL,key:attKey(),name:ME.name||'',email:ME.email||'',avatar:ME.avatar||''};
|
|
266
|
+
if(_ptJeton)corps.pt=_ptJeton;else corps.wantToken='1';
|
|
267
|
+
fetch('/api/doc',{method:'POST',headers:h,body:JSON.stringify(corps)})
|
|
268
|
+
.then(function(r){return r.json();})
|
|
269
|
+
.then(function(d){if(d&&d.pt)_ptJeton=d.pt;})
|
|
270
|
+
.catch(function(){}); }catch(e){} }
|
|
258
271
|
var EMOJIS=['👍','❤️','😂','😮','👏','🎉'];
|
|
259
272
|
var RSVG='<svg viewBox="0 0 24 24" fill=none stroke=currentColor stroke-width=2 stroke-linecap=round><circle cx=12 cy=12 r=9 /><path d="M8.5 14.5s1.4 1.7 3.5 1.7 3.5-1.7 3.5-1.7"/><line x1=9 y1=9.2 x2=9.01 y2=9.2 /><line x1=15 y1=9.2 x2=15.01 y2=9.2 /></svg>';
|
|
260
273
|
function esc(s){return Player.live.escapeHtml(s);}
|
package/server/presentations.js
CHANGED
|
@@ -787,20 +787,30 @@ async function recordAttendance(slug, participant, { presentation = null, ipHash
|
|
|
787
787
|
// lire-modifier-réécrire ci-dessous — toujours correcte, mais SANS le plafond — et on le dit une
|
|
788
788
|
// fois. Même patron que player_rate_limit_bump : dégrader, jamais casser, jamais en silence.
|
|
789
789
|
const capAnon = Number(anonCap) > 0 ? Math.trunc(Number(anonCap)) : PLAFOND_CREATION_ANON_DEFAUT;
|
|
790
|
+
// ⚠️ ON N'ENVOIE `p_has_token` QUE SI 0017 EST LÀ — ET C'EST LA MOITIÉ MANQUANTE DE LA COMPATIBILITÉ.
|
|
791
|
+
//
|
|
792
|
+
// PostgREST résout une RPC par JEU D'ARGUMENTS NOMMÉS : un argument en trop ne « prend pas son
|
|
793
|
+
// défaut », il ne correspond à AUCUNE fonction → 404. Le `DEFAULT null` de 0017 rend donc la base
|
|
794
|
+
// NEUVE compatible avec du CODE ANCIEN ; il ne peut rien pour le sens INVERSE — code neuf, base
|
|
795
|
+
// ancienne — qui est précisément l'ordre d'un déploiement réel : le code part avant la migration.
|
|
796
|
+
//
|
|
797
|
+
// Sans cette garde, un hôte à jour mais pas encore migré tombait dans le repli lire-modifier-
|
|
798
|
+
// réécrire, c'est-à-dire PERDAIT LE PLAFOND de création de faux participants apporté par 0015 : une
|
|
799
|
+
// garde disparaissait en silence entre deux migrations. On sonde donc la colonne (la réponse existe
|
|
800
|
+
// déjà) et on appelle à 10 arguments quand elle manque — l'ancien contrat, valide sur les DEUX bases.
|
|
801
|
+
// La dégradation redevient alors ce qu'on annonce : pas de compteur de transition, rien d'autre.
|
|
802
|
+
// (relevé du second hôte, qui l'a MESURÉ sur sa base au lieu de le supposer)
|
|
803
|
+
const transitionDispo = await require("./schema").attendue("jetonPresence");
|
|
804
|
+
const corpsRpc = {
|
|
805
|
+
p_slug: String(slug), p_key: String(key), p_ip_hash: ipHash || null, p_page: page,
|
|
806
|
+
p_name: (name || "").slice(0, 120), p_avatar: (avatar || "").slice(0, 600),
|
|
807
|
+
p_is_member: !!isMember, p_is_presenter: !!isPresenter,
|
|
808
|
+
p_max_gap_ms: ATTEND_MAX_GAP_MS, p_anon_cap: capAnon,
|
|
809
|
+
};
|
|
810
|
+
// true → last_token_at, false → last_no_token_at, null → ni l'un ni l'autre. 0017 seulement.
|
|
811
|
+
if (transitionDispo) corpsRpc.p_has_token = hasToken == null ? null : !!hasToken;
|
|
790
812
|
try {
|
|
791
|
-
const r = await PLAYER.db.request("rpc/player_attendance_bump", {
|
|
792
|
-
method: "POST",
|
|
793
|
-
body: {
|
|
794
|
-
p_slug: String(slug), p_key: String(key), p_ip_hash: ipHash || null, p_page: page,
|
|
795
|
-
p_name: (name || "").slice(0, 120), p_avatar: (avatar || "").slice(0, 600),
|
|
796
|
-
p_is_member: !!isMember, p_is_presenter: !!isPresenter,
|
|
797
|
-
p_max_gap_ms: ATTEND_MAX_GAP_MS, p_anon_cap: capAnon,
|
|
798
|
-
// Transition du jeton (0017) : true → last_token_at, false → last_no_token_at, null → ni l'un ni
|
|
799
|
-
// l'autre. La RPC 11-args (p_has_token DEFAULT null) accepte l'appel même sans la 0017 côté code
|
|
800
|
-
// ancien ; sans la 0017 côté base, la 11-args n'existe pas → on tombe dans le repli ci-dessous.
|
|
801
|
-
p_has_token: hasToken == null ? null : !!hasToken,
|
|
802
|
-
},
|
|
803
|
-
});
|
|
813
|
+
const r = await PLAYER.db.request("rpc/player_attendance_bump", { method: "POST", body: corpsRpc });
|
|
804
814
|
const ligne = Array.isArray(r) ? r[0] : r;
|
|
805
815
|
if (ligne && typeof ligne.ok === "boolean") {
|
|
806
816
|
// Plafond de création atteint : on ne crée pas ce faux participant. 429 = « trop », pas une panne.
|
|
@@ -812,8 +822,17 @@ async function recordAttendance(slug, participant, { presentation = null, ipHash
|
|
|
812
822
|
if (!_avertRpcPresence) {
|
|
813
823
|
_avertRpcPresence = true;
|
|
814
824
|
try {
|
|
825
|
+
// ⚠️ ON NOMME LE FICHIER QU'ON VIENT VRAIMENT D'ESSAYER — un nom FAUX est pire qu'un nom
|
|
826
|
+
// absent, parce qu'il est ACTIONNABLE : l'exploitant vérifie la migration nommée, la trouve
|
|
827
|
+
// appliquée, et conclut au faux positif. C'est ce qui arrivait quand ce message accusait 0015
|
|
828
|
+
// alors que l'échec venait de l'argument `p_has_token` de 0017 (relevé du second hôte). La
|
|
829
|
+
// règle « on nomme le fichier, pas l'erreur » ne tient que tant qu'UN SEUL fichier peut causer
|
|
830
|
+
// l'échec ; dès qu'un second emprunte le même chemin, le nom doit se DÉDUIRE de la tentative.
|
|
831
|
+
const fichier = transitionDispo
|
|
832
|
+
? "supabase/migrations/0017-jeton-presence.sql (ou 0015-presence-atomique.sql)"
|
|
833
|
+
: "supabase/migrations/0015-presence-atomique.sql";
|
|
815
834
|
PLAYER.errors.capture(new Error(
|
|
816
|
-
"présence non atomique : appliquez
|
|
835
|
+
"présence non atomique : appliquez " + fichier + ". "
|
|
817
836
|
+ "Sans elle, la présence est écrite par lire-modifier-réécrire (correct) mais le plafond "
|
|
818
837
|
+ "de création de faux participants anonymes n'est pas appliqué. "
|
|
819
838
|
+ "(" + ((erreur && erreur.message) || erreur) + ")",
|