discovery-media-player 0.1.27 → 0.1.29
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/browser.generated.js +1 -1
- package/server/handler.js +55 -19
- package/server/presentations.js +1 -24
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "discovery-media-player",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.29",
|
|
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",
|
|
@@ -4,4 +4,4 @@
|
|
|
4
4
|
// Injecté par api/doc.js dans un <script nonce=…> de la visionneuse. Le code navigateur du
|
|
5
5
|
// player vit en modules TypeScript testés sous player/src/ — c'est ici qu'il atterrit.
|
|
6
6
|
"use strict";
|
|
7
|
-
module.exports = { PLAYER_BROWSER_JS: "\"use strict\";var Player=(()=>{var z=Object.defineProperty;var ve=Object.getOwnPropertyDescriptor;var Me=Object.getOwnPropertyNames;var xe=Object.prototype.hasOwnProperty;var p=(e,n)=>{for(var t in n)z(e,t,{get:n[t],enumerable:!0})},we=(e,n,t,r)=>{if(n&&typeof n==\"object\"||typeof n==\"function\")for(let o of Me(n))!xe.call(e,o)&&o!==t&&z(e,o,{get:()=>n[o],enumerable:!(r=ve(n,o))||r.enumerable});return e};var ke=e=>we(z({},\"__esModule\",{value:!0}),e);var gn={};p(gn,{bridge:()=>I,chat:()=>C,live:()=>A,presentation:()=>L,presentationState:()=>$,tracking:()=>S,viewer:()=>R});var I={};p(I,{onHostMessage:()=>Re,onPlayerMessage:()=>Le,parseHostMessage:()=>de,parsePlayerMessage:()=>ce,sendToHost:()=>Se,sendToPlayer:()=>Ae,toWire:()=>j});var V=\"3dd-doc-\",Te=/^[A-Za-z0-9_-]{1,64}$/,_e=[\"close\",\"share\",\"embed-ready\",\"present-left\",\"present-denied\"],Ee=[\"embed-denied\"],Pe=[\"present-invite\",\"present-handover\",\"present-switch\"],Ie=[\"handover-done\"];function j(e){let n={type:V+e.type};return\"slug\"in e&&e.slug&&(n.slug=e.slug),\"reason\"in e&&e.reason&&(n.reason=String(e.reason).slice(0,40)),n}function ue(e,n,t){if(!e||typeof e!=\"object\")return null;let r=e.type;if(typeof r!=\"string\"||!r.startsWith(V))return null;let o=r.slice(V.length);if(n.includes(o))return{type:o};if(Ee.includes(o)){let a=e.reason;return{type:o,reason:/^[a-z-]{1,40}$/.test(String(a||\"\"))?String(a):\"unknown\"}}if(!t.includes(o))return null;let i=e.slug;return typeof i!=\"string\"||!Te.test(i)?null:{type:o,slug:i}}function ce(e){return ue(e,_e,Pe)}function de(e){return ue(e,Ie,[])}function Se(e,n){try{let t=n||(typeof window!=\"undefined\"?window.parent:null);t&&t.postMessage(j(e),\"*\")}catch(t){}}function Ae(e,n){var t;try{(t=e==null?void 0:e.contentWindow)==null||t.postMessage(j(n),\"*\")}catch(r){}}function Ce(e,n){var r;if(!e)return!0;let t=(r=e.contentWindow)!=null?r:e;return n===t}function Le(e,n,t){let r=n||(typeof window!=\"undefined\"?window:null);if(!r)return()=>{};let o=i=>{if(!Ce(t,i.source))return;let a=ce(i.data);a&&e(a)};return r.addEventListener(\"message\",o),()=>r.removeEventListener(\"message\",o)}function Re(e,n){var i;let t=n||(typeof window!=\"undefined\"?window:null);if(!t)return()=>{};let r=(i=t.parent)!=null?i:null,o=a=>{if(!r||r===t||a.source!==r)return;let d=de(a.data);d&&e(d)};return t.addEventListener(\"message\",o),()=>t.removeEventListener(\"message\",o)}var S={};p(S,{createTracker:()=>De});var $e=[\"mousemove\",\"mousedown\",\"keydown\",\"wheel\",\"touchstart\",\"pointerdown\"];function He(){let e=typeof globalThis!=\"undefined\"?globalThis.crypto:void 0;if(e&&typeof e.randomUUID==\"function\")return e.randomUUID().replace(/-/g,\"\");if(e&&typeof e.getRandomValues==\"function\"){let n=e.getRandomValues(new Uint8Array(16));return Array.from(n,t=>t.toString(16).padStart(2,\"0\")).join(\"\")}return Math.random().toString(36).slice(2)+Date.now().toString(36)}function We(e,n){return t=>{try{let r=JSON.stringify(t),o=n.navigator;o&&typeof o.sendBeacon==\"function\"?o.sendBeacon(e,new Blob([r],{type:\"application/json\"})):n.fetch(e,{method:\"POST\",headers:{\"Content-Type\":\"application/json\"},body:r,keepalive:!0})}catch(r){}}}function De(e={}){var ie,ae;let n=e.win||(typeof window!=\"undefined\"?window:null),t=e.doc||n&&n.document,r=e.now||(()=>Date.now()),o=e.endpoint||\"/api/doc\",i=(ie=e.idleMs)!=null?ie:6e4,a=(ae=e.sessionEveryMs)!=null?ae:12e3,d=e.slug||\"\",f=e.internal||null,H=e.sessionId||He(),ye=e.send||We(o,n),g={},v=0,W=0,M=0,ee=0,c=null,_=r(),x=!1,D=!1,E=[],he=()=>!!d||!!f,ne=s=>{if(he())try{ye(s)}catch(l){}},te=()=>!!t&&t.visibilityState===\"visible\"&&(typeof t.hasFocus==\"function\"?t.hasFocus():!0)&&!x,re=s=>{let l=r()-s,b=Math.max(0,_-s)+i;return Math.max(0,Math.min(l,b))},N=()=>{c!=null&&v>0&&(g[v]=(g[v]||0)+re(c)/1e3),c=te()?r():null},O=()=>{N(),c=null},U=()=>{c==null&&te()&&(c=r())},F=()=>{let s=0;for(let l in g)s+=g[l];return c!=null&&(s+=re(c)/1e3),Math.round(s)},oe=()=>{let s={};for(let l in g)s[l]=Math.round(g[l]);return s},w=()=>{_=r(),x&&(x=!1,U())},se=(s,l)=>{f||ne({slug:d,event:s,page:l||W,maxPage:M,seconds:F(),sessionId:H})},P=()=>{N();let s={event:\"session\",sessionId:H,numPages:ee,maxPage:M,totalSeconds:F(),pagesTime:oe()};f?(s.internal=!0,s.docId=f.docId,s.email=f.email,s.name=f.name):s.slug=d,ne(s)},m=(s,l,b,le)=>{!s||typeof s.addEventListener!=\"function\"||(s.addEventListener(l,b,le),E.push(()=>{s.removeEventListener(l,b,le)}))};return{sessionId:H,totalSeconds:F,maxPage:()=>M,pageTimes:oe,start(){if(D)return;D=!0,se(\"open\",1),v=1,c=r(),_=r(),m(t,\"visibilitychange\",()=>{t.visibilityState===\"hidden\"?(O(),P()):(w(),U())}),m(n,\"blur\",O),m(n,\"focus\",()=>{w(),U()});for(let b of $e)m(n,b,w,{passive:!0});m(e.scrollElement,\"scroll\",w,{passive:!0}),m(n,\"beforeunload\",P);let s=n.setInterval(()=>{!x&&r()-_>i&&(x=!0,O())},5e3),l=n.setInterval(P,a);E.push(()=>{n.clearInterval(s),n.clearInterval(l)})},setPage(s){!s||s===W||(N(),v=s,W=s,s>M&&(M=s,se(\"page\",s)))},setPageCount(s){ee=s||0},noteActivity:w,flush:P,stop(){for(;E.length;){let s=E.pop();try{s==null||s()}catch(l){}}D=!1}}}var A={};p(A,{STORAGE_KEYS:()=>k,attendeeKey:()=>Fe,authorToken:()=>Ve,avatarHtml:()=>K,canModerate:()=>Z,escapeHtml:()=>u,flattenPresence:()=>Ne,formatMessageBody:()=>G,initials:()=>pe,isMentioned:()=>je,isMine:()=>y,reactorId:()=>B,shouldNotify:()=>Ke,unreadLabel:()=>Ge});var k={attendeeKey:\"3dd-present-attkey\",authorToken:\"3dd-present-authtoken\",chatMuted:\"3dd-present-mute\"};function u(e){return String(e==null?\"\":e).replace(/&/g,\"&\").replace(/</g,\"<\").replace(/>/g,\">\").replace(/\"/g,\""\")}function pe(e){let n=(e||\"?\").trim().split(/\\s+/);return(((n[0]||\"\")[0]||\"?\")+((n[1]||\"\")[0]||\"\")).toUpperCase()}function K(e,n){return e?`<img src=\"${u(e)}\" alt=\"\">`:u(pe(n))}function G(e){let n=u(e);return n=n.replace(/(https?:\\/\\/[^\\s<]+)/g,t=>`<a href=\"${t}\" target=_blank rel=noreferrer class=cm-link>${t}</a>`),n=n.replace(/(^|\\s)@([\\p{L}0-9_'.-]+)/gu,(t,r,o)=>`${r}<span class=cm-mention>@${o}</span>`),n}function Ne(e){let n={},t=[];for(let r in e||{})for(let o of(e||{})[r]||[]){let i=String(o.uid||o.email||o.name||\"\").toLowerCase()||`_${r}_${t.length}`;n[i]?o.role===\"presenter\"&&(n[i]=o):(n[i]=o,t.push(i))}return t.map(r=>n[r])}function Oe(e,n){try{return e?e.getItem(n):null}catch(t){return null}}function Ue(e,n,t){try{e==null||e.setItem(n,t)}catch(r){}}function Fe(e,n,t=\"anon\",r=()=>Math.random().toString(36).slice(2,10)){if(e!=null&&e.email)return String(e.email).toLowerCase();try{if(!n)return`anon-${t}`;let o=n.getItem(k.attendeeKey);if(o)return o;let i=`anon-${r()}`;return n.setItem(k.attendeeKey,i),i}catch(o){return`anon-${t}`}}function ze(){let e=typeof globalThis!=\"undefined\"?globalThis.crypto:void 0;if(e&&typeof e.randomUUID==\"function\")return e.randomUUID().replace(/-/g,\"\");if(e&&typeof e.getRandomValues==\"function\"){let n=e.getRandomValues(new Uint8Array(24));return Array.from(n,t=>t.toString(16).padStart(2,\"0\")).join(\"\")}try{console.warn(\"[player] crypto indisponible : jeton d'auteur affaibli\")}catch(n){}return Math.random().toString(36).slice(2)+Math.random().toString(36).slice(2)}function Ve(e,n=ze){let t=Oe(e,k.authorToken);if(t)return t;let r=n();return Ue(e,k.authorToken,r),r}function B(e){return String(e&&(e.email||e.name)||\"\").toLowerCase()}function y(e,n){return!e||!n?!1:e.author_email&&n.email?e.author_email===n.email:!e.author_email&&!n.email?e.author_name===n.name:!1}function Z(e){return!!(e&&e.role===\"presenter\")}function je(e,n){if(!n||!n.name)return!1;let t=(n.name.split(/\\s+/)[0]||\"\").toLowerCase();return t?String(e&&e.body||\"\").toLowerCase().includes(`@${t}`)&&!y(e,n):!1}function Ke(e){let{msg:n,me:t,historyLoaded:r,chatHidden:o}=e;return!r||!n||n.deleted||y(n,t)?!1:o}function Ge(e){return!e||e<=0?\"\":e>9?\"9+\":String(e)}var C={};p(C,{messageClassName:()=>Ze,renderActions:()=>me,renderAttachment:()=>ge,renderMessage:()=>Be,renderReactions:()=>fe});function fe(e,n){let t=e&&e.reactions||{},r=B(n),o=\"\";for(let i in t){let a=t[i]||[];if(!a.length)continue;let d=a.indexOf(r)>=0;o+=`<button class=\"re-chip${d?\" on\":\"\"}\" data-e=\"${u(i)}\">${u(i)} ${a.length}</button>`}return o}function ge(e){if(!e||!e.url)return\"\";let n=u(e.url);if(e.kind===\"image\")return`<a class=cm-att href=\"${n}\" target=_blank rel=noreferrer><img src=\"${n}\" alt=\"\" loading=lazy></a>`;let t=u(e.name||\"Document.pdf\");return`<a class=cm-att-pdf href=\"${n}\" target=_blank rel=noreferrer data-pdf=\"${n}\"><span class=cm-att-ph></span><span class=cm-pdflabel>${t}</span></a>`}function me(e,n){let t=y(e,n.me),r=`<button class=cm-react title=\"R\\xE9agir\">${n.reactIcon||\"\"}</button><button class=cm-reply title=\"R\\xE9pondre\">\\u21A9</button>`;return t&&(r+='<button class=cm-edit title=\"Modifier\">\\u270E</button>'),(t||Z(n.me))&&(r+='<button class=cm-del-btn title=\"Supprimer\">\\u{1F5D1}</button>'),r}function Be(e,n){if(e.deleted)return'<span class=a></span><span class=b><div class=\"txt cm-del\">Message supprim\\xE9</div></span>';let t=e.reply_to?`<div class=cm-q><b>${u(e.reply_name||\"\")}</b> ${u(e.reply_text||\"\")}</div>`:\"\",r=e.edited?\" <span class=cm-ed>(modifi\\xE9)</span>\":\"\",o=e.body?`<div class=txt>${G(e.body)}${r}</div>`:\"\",i=e.is_presenter?\" <span class=tag>pr\\xE9sentateur</span>\":\"\";return`<span class=a>${K(e.author_avatar,e.author_name)}</span><span class=b><div class=who><b>${u(e.author_name||\"Invit\\xE9\")}</b>${i}</div>`+t+o+ge(e.attachment)+`<div class=cm-re>${fe(e,n.me)}</div></span><span class=cm-act>${me(e,n)}</span>`}function Ze(e,n,t){return\"cm\"+(y(e,n)?\" me\":\"\")+(e.deleted?\" isdel\":\"\")+(t?\" mentioned\":\"\")}var L={};p(L,{DEFAULT_CENTER:()=>X,DEFAULT_ZOOM:()=>J,MAP_TYPES:()=>T,cycleMapType:()=>Ye,initialMapContent:()=>Xe,mapTypeLabel:()=>qe,sanitizeContent:()=>Q});var T=[\"roadmap\",\"satellite\",\"hybrid\"],X=[46.6,2.5],J=6;function h(e){let n=Number(e);return Number.isFinite(n)?n:null}function Y(e){if(!Array.isArray(e))return null;let n=h(e[0]),t=h(e[1]);return n!=null&&t!=null?[n,t]:null}var q=(e,n,t)=>Math.max(n,Math.min(t,e));function Q(e){if(!e||typeof e!=\"object\")return null;let n=e;if(n.kind===\"pdf\"||n.kind==null)return null;if(n.kind===\"streetview\"){let t=Y(n.position);if(!t)return null;let r=n.pov&&typeof n.pov==\"object\"?n.pov:null,o=r?{heading:h(r.heading)||0,pitch:q(h(r.pitch)||0,-90,90)}:{heading:0,pitch:0};return{kind:\"streetview\",position:t,pov:o,zoom:q(h(n.zoom)||1,0,5)}}return n.kind!==\"map\"?null:{kind:\"map\",center:Y(n.center)||[...X],zoom:q(Math.trunc(h(n.zoom)||J),1,21),marker:Y(n.marker),mapType:T.includes(n.mapType)?n.mapType:null,label:String(n.label||\"\").slice(0,160)||null}}function Ye(e){let n=T.indexOf(e);return T[(n+1)%T.length]}function qe(e){return e===\"roadmap\"?\"\\u{1F6F0} Satellite\":e===\"satellite\"?\"\\u{1F5FA} Hybride\":\"\\u{1F5FA} Plan\"}function Xe(){return{kind:\"map\",center:[...X],zoom:J,marker:null,mapType:null,label:null}}var R={};p(R,{CURRENT_PAGE_MARGIN:()=>rn,DEFAULT_ASPECT:()=>en,MAX_ZOOM:()=>Qe,MIN_PAGE_WIDTH:()=>nn,MIN_ZOOM:()=>Je,PRERENDER_MARGIN:()=>tn,arrowState:()=>an,averageColor:()=>cn,clampPage:()=>be,clampZoom:()=>on,fitWidth:()=>un,isImageDocument:()=>dn,progressPercent:()=>sn});var Je=.5,Qe=3,en=1.35,nn=280,tn=\"500px 0px\",rn=\"-48% 0px -48% 0px\";function on(e){let n=Math.round((Number(e)||0)*10)/10;return Math.max(.5,Math.min(3,n))}function be(e,n){return Math.max(1,Math.min(n||1,Number(e)||1))}function sn(e,n){return n?Math.round(be(e,n)/n*100):0}function an(e,n){return{prevDisabled:e<=1,nextDisabled:!!n&&e>=n}}var ln=28;function un(e){var a;let n=(a=e.zoom)!=null?a:1,t=Math.max(280,e.containerWidth||900)*n;if(!e.onePage)return t;let r=e.aspect||1.35,i=((e.containerHeight||600)-ln-(e.overlap||0)-(e.reserve||0))/r;return i>120?Math.min(t,i):t}function cn(e){let n=e.length/4;if(!n)return\"rgb(0,0,0)\";let t=0,r=0,o=0;for(let i=0;i<e.length;i+=4)t+=e[i],r+=e[i+1],o+=e[i+2];return`rgb(${Math.round(t/n)},${Math.round(r/n)},${Math.round(o/n)})`}function dn(e,n){return/[.](png|jpe?g|webp|gif|avif)($|[?])/i.test(String(e||n||\"\"))}var $={};p($,{presentationTransition:()=>pn,switchDocUrl:()=>fn});function pn(e,n={}){if(!e)return[{kind:\"nothing\"}];if(e.active===!1)return[{kind:\"ended\"}];let t=Q(e.content);if(t)return[{kind:\"show-map\",content:t}];let r=[{kind:\"leave-map\"}];return e.file_url&&e.file_url!==n.docUrl?(r.push({kind:\"switch-doc\",url:e.file_url,name:e.file_name||void 0,title:e.doc_title||void 0,updatedAt:e.updated_at||void 0}),r):(e.current_page&&r.push({kind:\"show-page\",page:e.current_page}),r)}function fn(e,n){let t=encodeURIComponent(n||String(Date.now()));return`${e}${e.includes(\"?\")?\"&\":\"?\"}v=${t}`}return ke(gn);})();", SOURCE_HASH: "951763e203aba2b4" };
|
|
7
|
+
module.exports = { PLAYER_BROWSER_JS: "\"use strict\";var Player=(()=>{var j=Object.defineProperty;var ve=Object.getOwnPropertyDescriptor;var Me=Object.getOwnPropertyNames;var we=Object.prototype.hasOwnProperty;var y=(e,n)=>{for(var t in n)j(e,t,{get:n[t],enumerable:!0})},xe=(e,n,t,r)=>{if(n&&typeof n==\"object\"||typeof n==\"function\")for(let o of Me(n))!we.call(e,o)&&o!==t&&j(e,o,{get:()=>n[o],enumerable:!(r=ve(n,o))||r.enumerable});return e};var ke=e=>xe(j({},\"__esModule\",{value:!0}),e);var gn={};y(gn,{bridge:()=>R,chat:()=>H,live:()=>$,presentation:()=>O,presentationState:()=>W,tracking:()=>L,viewer:()=>D});var R={};y(R,{onHostMessage:()=>Le,onPlayerMessage:()=>Re,parseHostMessage:()=>pe,parsePlayerMessage:()=>de,sendToHost:()=>Se,sendToPlayer:()=>Ae,toWire:()=>G});var K=\"3dd-doc-\",Te=/^[A-Za-z0-9_-]{1,64}$/,_e=[\"close\",\"share\",\"embed-ready\",\"present-left\",\"present-denied\"],Ee=[\"embed-denied\"],Ie=[\"present-invite\",\"present-handover\",\"present-switch\"],Pe=[\"handover-done\"];function G(e){let n={type:K+e.type};return\"slug\"in e&&e.slug&&(n.slug=e.slug),\"reason\"in e&&e.reason&&(n.reason=String(e.reason).slice(0,40)),n}function ce(e,n,t){if(!e||typeof e!=\"object\")return null;let r=e.type;if(typeof r!=\"string\"||!r.startsWith(K))return null;let o=r.slice(K.length);if(n.includes(o))return{type:o};if(Ee.includes(o)){let a=e.reason;return{type:o,reason:/^[a-z-]{1,40}$/.test(String(a||\"\"))?String(a):\"unknown\"}}if(!t.includes(o))return null;let s=e.slug;return typeof s!=\"string\"||!Te.test(s)?null:{type:o,slug:s}}function de(e){return ce(e,_e,Ie)}function pe(e){return ce(e,Pe,[])}function Se(e,n){try{let t=n||(typeof window!=\"undefined\"?window.parent:null);t&&t.postMessage(G(e),\"*\")}catch(t){}}function Ae(e,n){var t;try{(t=e==null?void 0:e.contentWindow)==null||t.postMessage(G(n),\"*\")}catch(r){}}function Ce(e,n){var r;if(!e)return!0;let t=(r=e.contentWindow)!=null?r:e;return n===t}function Re(e,n,t){let r=n||(typeof window!=\"undefined\"?window:null);if(!r)return()=>{};let o=s=>{if(!Ce(t,s.source))return;let a=de(s.data);a&&e(a)};return r.addEventListener(\"message\",o),()=>r.removeEventListener(\"message\",o)}function Le(e,n){var s;let t=n||(typeof window!=\"undefined\"?window:null);if(!t)return()=>{};let r=(s=t.parent)!=null?s:null,o=a=>{if(!r||r===t||a.source!==r)return;let c=pe(a.data);c&&e(c)};return t.addEventListener(\"message\",o),()=>t.removeEventListener(\"message\",o)}var L={};y(L,{createTracker:()=>De});var $e=[\"mousemove\",\"mousedown\",\"keydown\",\"wheel\",\"touchstart\",\"pointerdown\"];function He(){let e=typeof globalThis!=\"undefined\"?globalThis.crypto:void 0;if(e&&typeof e.randomUUID==\"function\")return e.randomUUID().replace(/-/g,\"\");if(e&&typeof e.getRandomValues==\"function\"){let n=e.getRandomValues(new Uint8Array(16));return Array.from(n,t=>t.toString(16).padStart(2,\"0\")).join(\"\")}return Math.random().toString(36).slice(2)+Date.now().toString(36)}function Oe(e,n){return t=>{try{let r=JSON.stringify(t),o=n.navigator;o&&typeof o.sendBeacon==\"function\"?o.sendBeacon(e,new Blob([r],{type:\"application/json\"})):n.fetch(e,{method:\"POST\",headers:{\"Content-Type\":\"application/json\"},body:r,keepalive:!0})}catch(r){}}}function De(e={}){var ae,le;let n=e.win||(typeof window!=\"undefined\"?window:null),t=e.doc||n&&n.document,r=e.now||(()=>Date.now()),o=e.endpoint||\"/api/doc\",s=(ae=e.idleMs)!=null?ae:6e4,a=(le=e.sessionEveryMs)!=null?le:12e3,c=e.slug||\"\",f=e.internal||null,m=e.sessionId||He(),b=e.send||Oe(o,n),p={},h=0,M=0,u=0,v=0,g=null,S=r(),_=!1,N=!1,A=[],he=()=>!!c||!!f,te=i=>{if(he())try{b(i)}catch(l){}},re=()=>!!t&&t.visibilityState===\"visible\"&&(typeof t.hasFocus==\"function\"?t.hasFocus():!0)&&!_,oe=i=>{let l=r()-i,x=Math.max(0,S-i)+s;return Math.max(0,Math.min(l,x))},U=()=>{g!=null&&h>0&&(p[h]=(p[h]||0)+oe(g)/1e3),g=re()?r():null},F=()=>{U(),g=null},V=()=>{g==null&&re()&&(g=r())},z=()=>{let i=0;for(let l in p)i+=p[l];return g!=null&&(i+=oe(g)/1e3),Math.round(i)},ie=()=>{let i={};for(let l in p)i[l]=Math.round(p[l]);return i},E=()=>{S=r(),_&&(_=!1,V())},se=(i,l)=>{f||te({slug:c,event:i,page:l||M,maxPage:u,seconds:z(),sessionId:m})},C=()=>{U();let i={event:\"session\",sessionId:m,numPages:v,maxPage:u,totalSeconds:z(),pagesTime:ie()};f?(i.internal=!0,i.docId=f.docId,i.email=f.email,i.name=f.name):i.slug=c,te(i)},w=(i,l,x,ue)=>{!i||typeof i.addEventListener!=\"function\"||(i.addEventListener(l,x,ue),A.push(()=>{i.removeEventListener(l,x,ue)}))};return{sessionId:m,totalSeconds:z,maxPage:()=>u,pageTimes:ie,start(){if(N)return;N=!0,se(\"open\",1),h=1,g=r(),S=r(),w(t,\"visibilitychange\",()=>{t.visibilityState===\"hidden\"?(F(),C()):(E(),V())}),w(n,\"blur\",F),w(n,\"focus\",()=>{E(),V()});for(let x of $e)w(n,x,E,{passive:!0});w(e.scrollElement,\"scroll\",E,{passive:!0}),w(n,\"beforeunload\",C);let i=n.setInterval(()=>{!_&&r()-S>s&&(_=!0,F())},5e3),l=n.setInterval(C,a);A.push(()=>{n.clearInterval(i),n.clearInterval(l)})},setPage(i){!i||i===M||(U(),h=i,M=i,i>u&&(u=i,se(\"page\",i)))},setPageCount(i){v=i||0},noteActivity:E,flush:C,stop(){for(;A.length;){let i=A.pop();try{i==null||i()}catch(l){}}N=!1}}}var $={};y($,{STORAGE_KEYS:()=>I,attendeeKey:()=>Fe,authorToken:()=>ze,avatarHtml:()=>B,canModerate:()=>q,createScheduler:()=>Be,escapeHtml:()=>d,flattenPresence:()=>We,formatMessageBody:()=>Z,initials:()=>fe,isMentioned:()=>je,isMine:()=>k,reactorId:()=>Y,shouldNotify:()=>Ke,unreadLabel:()=>Ge});var I={attendeeKey:\"3dd-present-attkey\",authorToken:\"3dd-present-authtoken\",chatMuted:\"3dd-present-mute\"};function d(e){return String(e==null?\"\":e).replace(/&/g,\"&\").replace(/</g,\"<\").replace(/>/g,\">\").replace(/\"/g,\""\")}function fe(e){let n=(e||\"?\").trim().split(/\\s+/);return(((n[0]||\"\")[0]||\"?\")+((n[1]||\"\")[0]||\"\")).toUpperCase()}function B(e,n){return e?`<img src=\"${d(e)}\" alt=\"\">`:d(fe(n))}function Z(e){let n=d(e);return n=n.replace(/(https?:\\/\\/[^\\s<]+)/g,t=>`<a href=\"${t}\" target=_blank rel=noreferrer class=cm-link>${t}</a>`),n=n.replace(/(^|\\s)@([\\p{L}0-9_'.-]+)/gu,(t,r,o)=>`${r}<span class=cm-mention>@${o}</span>`),n}function We(e){let n={},t=[];for(let r in e||{})for(let o of(e||{})[r]||[]){let s=String(o.uid||o.email||o.name||\"\").toLowerCase()||`_${r}_${t.length}`;n[s]?o.role===\"presenter\"&&(n[s]=o):(n[s]=o,t.push(s))}return t.map(r=>n[r])}function Ne(e,n){try{return e?e.getItem(n):null}catch(t){return null}}function Ue(e,n,t){try{e==null||e.setItem(n,t)}catch(r){}}function Fe(e,n,t=\"anon\",r=()=>Math.random().toString(36).slice(2,10)){if(e!=null&&e.email)return String(e.email).toLowerCase();try{if(!n)return`anon-${t}`;let o=n.getItem(I.attendeeKey);if(o)return o;let s=`anon-${r()}`;return n.setItem(I.attendeeKey,s),s}catch(o){return`anon-${t}`}}function Ve(){let e=typeof globalThis!=\"undefined\"?globalThis.crypto:void 0;if(e&&typeof e.randomUUID==\"function\")return e.randomUUID().replace(/-/g,\"\");if(e&&typeof e.getRandomValues==\"function\"){let n=e.getRandomValues(new Uint8Array(24));return Array.from(n,t=>t.toString(16).padStart(2,\"0\")).join(\"\")}try{console.warn(\"[player] crypto indisponible : jeton d'auteur affaibli\")}catch(n){}return Math.random().toString(36).slice(2)+Math.random().toString(36).slice(2)}function ze(e,n=Ve){let t=Ne(e,I.authorToken);if(t)return t;let r=n();return Ue(e,I.authorToken,r),r}function Y(e){return String(e&&(e.email||e.name)||\"\").toLowerCase()}function k(e,n){return!e||!n?!1:e.author_email&&n.email?e.author_email===n.email:!e.author_email&&!n.email?e.author_name===n.name:!1}function q(e){return!!(e&&e.role===\"presenter\")}function je(e,n){if(!n||!n.name)return!1;let t=(n.name.split(/\\s+/)[0]||\"\").toLowerCase();return t?String(e&&e.body||\"\").toLowerCase().includes(`@${t}`)&&!k(e,n):!1}function Ke(e){let{msg:n,me:t,historyLoaded:r,chatHidden:o}=e;return!r||!n||n.deleted||k(n,t)?!1:o}function Ge(e){return!e||e<=0?\"\":e>9?\"9+\":String(e)}function Be(e,n={}){var M;let t=(M=n.minMs)!=null?M:300,r=n.now||(()=>Date.now()),o=n.setTimer||((u,v)=>setTimeout(u,v)),s=n.clearTimer||(u=>clearTimeout(u)),a=!1,c=!1,f=-1/0,m=null,b=!1;function p(){if(b||a){c=!0;return}let u=t-(r()-f);if(u>0){h(u);return}a=!0,c=!1,f=r();let v=!1;e(()=>{v||(v=!0,a=!1,c&&!b&&p())})}function h(u){b||m!==null||(m=o(()=>{m=null,p()},Math.max(0,u)))}return{signaler(){b||p()},maintenant(){b||(f=-1/0,p())},arreter(){b=!0,m!==null&&(s(m),m=null)}}}var H={};y(H,{messageClassName:()=>Ye,renderActions:()=>be,renderAttachment:()=>ge,renderMessage:()=>Ze,renderReactions:()=>me});function me(e,n){let t=e&&e.reactions||{},r=Y(n),o=\"\";for(let s in t){let a=t[s]||[];if(!a.length)continue;let c=a.indexOf(r)>=0;o+=`<button class=\"re-chip${c?\" on\":\"\"}\" data-e=\"${d(s)}\">${d(s)} ${a.length}</button>`}return o}function ge(e){if(!e||!e.url)return\"\";let n=d(e.url);if(e.kind===\"image\")return`<a class=cm-att href=\"${n}\" target=_blank rel=noreferrer><img src=\"${n}\" alt=\"\" loading=lazy></a>`;let t=d(e.name||\"Document.pdf\");return`<a class=cm-att-pdf href=\"${n}\" target=_blank rel=noreferrer data-pdf=\"${n}\"><span class=cm-att-ph></span><span class=cm-pdflabel>${t}</span></a>`}function be(e,n){let t=k(e,n.me),r=`<button class=cm-react title=\"R\\xE9agir\">${n.reactIcon||\"\"}</button><button class=cm-reply title=\"R\\xE9pondre\">\\u21A9</button>`;return t&&(r+='<button class=cm-edit title=\"Modifier\">\\u270E</button>'),(t||q(n.me))&&(r+='<button class=cm-del-btn title=\"Supprimer\">\\u{1F5D1}</button>'),r}function Ze(e,n){if(e.deleted)return'<span class=a></span><span class=b><div class=\"txt cm-del\">Message supprim\\xE9</div></span>';let t=e.reply_to?`<div class=cm-q><b>${d(e.reply_name||\"\")}</b> ${d(e.reply_text||\"\")}</div>`:\"\",r=e.edited?\" <span class=cm-ed>(modifi\\xE9)</span>\":\"\",o=e.body?`<div class=txt>${Z(e.body)}${r}</div>`:\"\",s=e.is_presenter?\" <span class=tag>pr\\xE9sentateur</span>\":\"\";return`<span class=a>${B(e.author_avatar,e.author_name)}</span><span class=b><div class=who><b>${d(e.author_name||\"Invit\\xE9\")}</b>${s}</div>`+t+o+ge(e.attachment)+`<div class=cm-re>${me(e,n.me)}</div></span><span class=cm-act>${be(e,n)}</span>`}function Ye(e,n,t){return\"cm\"+(k(e,n)?\" me\":\"\")+(e.deleted?\" isdel\":\"\")+(t?\" mentioned\":\"\")}var O={};y(O,{DEFAULT_CENTER:()=>Q,DEFAULT_ZOOM:()=>ee,MAP_TYPES:()=>P,cycleMapType:()=>qe,initialMapContent:()=>Je,mapTypeLabel:()=>Xe,sanitizeContent:()=>ne});var P=[\"roadmap\",\"satellite\",\"hybrid\"],Q=[46.6,2.5],ee=6;function T(e){let n=Number(e);return Number.isFinite(n)?n:null}function X(e){if(!Array.isArray(e))return null;let n=T(e[0]),t=T(e[1]);return n!=null&&t!=null?[n,t]:null}var J=(e,n,t)=>Math.max(n,Math.min(t,e));function ne(e){if(!e||typeof e!=\"object\")return null;let n=e;if(n.kind===\"pdf\"||n.kind==null)return null;if(n.kind===\"streetview\"){let t=X(n.position);if(!t)return null;let r=n.pov&&typeof n.pov==\"object\"?n.pov:null,o=r?{heading:T(r.heading)||0,pitch:J(T(r.pitch)||0,-90,90)}:{heading:0,pitch:0};return{kind:\"streetview\",position:t,pov:o,zoom:J(T(n.zoom)||1,0,5)}}return n.kind!==\"map\"?null:{kind:\"map\",center:X(n.center)||[...Q],zoom:J(Math.trunc(T(n.zoom)||ee),1,21),marker:X(n.marker),mapType:P.includes(n.mapType)?n.mapType:null,label:String(n.label||\"\").slice(0,160)||null}}function qe(e){let n=P.indexOf(e);return P[(n+1)%P.length]}function Xe(e){return e===\"roadmap\"?\"\\u{1F6F0} Satellite\":e===\"satellite\"?\"\\u{1F5FA} Hybride\":\"\\u{1F5FA} Plan\"}function Je(){return{kind:\"map\",center:[...Q],zoom:ee,marker:null,mapType:null,label:null}}var D={};y(D,{CURRENT_PAGE_MARGIN:()=>on,DEFAULT_ASPECT:()=>nn,MAX_ZOOM:()=>en,MIN_PAGE_WIDTH:()=>tn,MIN_ZOOM:()=>Qe,PRERENDER_MARGIN:()=>rn,arrowState:()=>ln,averageColor:()=>dn,clampPage:()=>ye,clampZoom:()=>sn,fitWidth:()=>cn,isImageDocument:()=>pn,progressPercent:()=>an});var Qe=.5,en=3,nn=1.35,tn=280,rn=\"500px 0px\",on=\"-48% 0px -48% 0px\";function sn(e){let n=Math.round((Number(e)||0)*10)/10;return Math.max(.5,Math.min(3,n))}function ye(e,n){return Math.max(1,Math.min(n||1,Number(e)||1))}function an(e,n){return n?Math.round(ye(e,n)/n*100):0}function ln(e,n){return{prevDisabled:e<=1,nextDisabled:!!n&&e>=n}}var un=28;function cn(e){var a;let n=(a=e.zoom)!=null?a:1,t=Math.max(280,e.containerWidth||900)*n;if(!e.onePage)return t;let r=e.aspect||1.35,s=((e.containerHeight||600)-un-(e.overlap||0)-(e.reserve||0))/r;return s>120?Math.min(t,s):t}function dn(e){let n=e.length/4;if(!n)return\"rgb(0,0,0)\";let t=0,r=0,o=0;for(let s=0;s<e.length;s+=4)t+=e[s],r+=e[s+1],o+=e[s+2];return`rgb(${Math.round(t/n)},${Math.round(r/n)},${Math.round(o/n)})`}function pn(e,n){return/[.](png|jpe?g|webp|gif|avif)($|[?])/i.test(String(e||n||\"\"))}var W={};y(W,{presentationTransition:()=>fn,switchDocUrl:()=>mn});function fn(e,n={}){if(!e)return[{kind:\"nothing\"}];if(e.active===!1)return[{kind:\"ended\"}];let t=ne(e.content);if(t)return[{kind:\"show-map\",content:t}];let r=[{kind:\"leave-map\"}];return e.file_url&&e.file_url!==n.docUrl?(r.push({kind:\"switch-doc\",url:e.file_url,name:e.file_name||void 0,title:e.doc_title||void 0,updatedAt:e.updated_at||void 0}),r):(e.current_page&&r.push({kind:\"show-page\",page:e.current_page}),r)}function mn(e,n){let t=encodeURIComponent(n||String(Date.now()));return`${e}${e.includes(\"?\")?\"&\":\"?\"}v=${t}`}return ke(gn);})();", SOURCE_HASH: "4238739f09f2f24c" };
|
package/server/handler.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
const crypto = require("crypto");
|
|
7
7
|
const { getShareBySlug, logView, upsertSession, createReshare, sendReshareEmail, upsertInternalSession,
|
|
8
8
|
createShare, revokeShare, setShareAuth, overview: docOverview, listSharesForDoc, listSessionsForDoc, internalStatsForDoc } = require("./shares");
|
|
9
|
-
const { createPresentation, getPresentation, setPage, endPresentation, addMessage, listMessages, toggleReaction, editMessage, deleteMessage, setChatLock, createUploadUrl, reclaimPresentation, touchPresentation, listActivePresentations, handoverPresentation, endPresentationByOwner, recordAttendance,
|
|
9
|
+
const { createPresentation, getPresentation, setPage, endPresentation, addMessage, listMessages, toggleReaction, editMessage, deleteMessage, setChatLock, createUploadUrl, reclaimPresentation, touchPresentation, listActivePresentations, handoverPresentation, endPresentationByOwner, recordAttendance, presentationStats, listPresentationsForDoc, switchPresentationDoc, setPresentationContent } = require("./presentations");
|
|
10
10
|
// CONTEXTE INJECTÉ : tout ce que le player emprunte à l'application hôte passe par ici — stockage,
|
|
11
11
|
// base, identité, limites, marque, journalisation — et rien d'autre. C'est la frontière qui permettra
|
|
12
12
|
// de brancher un second projet, puis d'ouvrir le cœur. Cf. api/_player-context.js.
|
|
@@ -119,6 +119,7 @@ const LIVE_CSS = `
|
|
|
119
119
|
.pres-item{display:flex;align-items:center;gap:9px;padding:6px 8px;border-radius:8px}
|
|
120
120
|
.pres-item .a,.cm .a{width:28px;height:28px;border-radius:50%;flex:none;background:#e6e2db;color:#555;font-size:11px;font-weight:700;display:inline-flex;align-items:center;justify-content:center;overflow:hidden}
|
|
121
121
|
.pres-item .a img,.cm .a img{width:100%;height:100%;object-fit:cover}
|
|
122
|
+
.pres-by{font-size:12px;opacity:.72;margin:0 0 8px;padding:0 2px}
|
|
122
123
|
.pres-item .n{font-size:13px;font-weight:600}
|
|
123
124
|
.pres-item .e{font-size:11px;color:#888}
|
|
124
125
|
.tag{font-size:9.5px;font-weight:800;color:#e5384d;text-transform:uppercase;letter-spacing:.02em}
|
|
@@ -233,7 +234,7 @@ const LIVE_PANEL = `<div class="chat hidden" id=chatPanel><div class=chat-grip i
|
|
|
233
234
|
// JS partagé : présence + chat via Supabase Realtime. Live.connect(slug, me) / Live.disconnect().
|
|
234
235
|
const LIVE_JS = `
|
|
235
236
|
var Live=(function(){
|
|
236
|
-
var sb=null,ch=null,ME=null,SLUG=null,CONTROL=null,LOCKED=false,AUTHTOK=null,PRESENT=[],
|
|
237
|
+
var sb=null,ch=null,ME=null,SLUG=null,CONTROL=null,LOCKED=false,AUTHTOK=null,PRESENT=[],PRESNAME='',seen={},msgEls={},msgData={},replyCtx=null,typers={},pdfCache={},_tyT=0,_tyIv=0,_atIv=0,unread=0,autoOpened=false,_histDone=false,_phWired=false,_onMap=null,_onState=null,_peekT=0,MUTED=false;
|
|
237
238
|
try{ MUTED=localStorage.getItem('3dd-present-mute')==='1'; }catch(e){}
|
|
238
239
|
// Couper/rétablir les notifications du chat (cloche) : coupé = plus de ticker ni de pulse (badge silencieux gardé).
|
|
239
240
|
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(); }
|
|
@@ -298,7 +299,7 @@ var Live=(function(){
|
|
|
298
299
|
function isMentioned(m){return Player.live.isMentioned(m,ME);}
|
|
299
300
|
function renderPres(st){var l=flat(st),c=l.length;PRESENT=l;var e=document.getElementById('presCount');if(e)e.textContent=c;
|
|
300
301
|
var a=document.getElementById('presAvs');if(a)a.innerHTML=l.slice(0,4).map(function(m){return '<span class=pres-av>'+av(m.avatar,m.name)+'</span>';}).join('');
|
|
301
|
-
var p=document.getElementById('presList');if(p)p.innerHTML='<h5>'+c+' en ligne</h5>'+l.map(function(m){return '<div class=pres-item><span class=a>'+av(m.avatar,m.name)+'</span><span class=b><div class=n>'+esc(m.name||'Invité')+
|
|
302
|
+
var p=document.getElementById('presList');if(p)p.innerHTML='<h5>'+c+' en ligne</h5>'+(PRESNAME?'<div class=pres-by>Présenté par '+esc(PRESNAME)+'</div>':'')+l.map(function(m){return '<div class=pres-item><span class=a>'+av(m.avatar,m.name)+'</span><span class=b><div class=n>'+esc(m.name||'Invité')+'</div>'+(m.email?'<div class=e>'+esc(m.email)+'</div>':'')+'</span></div>';}).join('');}
|
|
302
303
|
// Rendu d'un message → player/src/chat.ts (échappement testé sous jsdom : on y vérifie ce que
|
|
303
304
|
// le NAVIGATEUR fabrique, pas seulement la chaîne produite).
|
|
304
305
|
function renderRe(m){return Player.chat.renderReactions(m,ME);}
|
|
@@ -458,13 +459,26 @@ var Live=(function(){
|
|
|
458
459
|
.then(function(d){if(d&&d.ok)applique(d);})
|
|
459
460
|
.catch(function(){});
|
|
460
461
|
}
|
|
461
|
-
|
|
462
|
-
//
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
462
|
+
// ⚠️ ORDONNANCEUR BORNÉ, PAS UN DEBOUNCE. Ce qui était écrit ici repoussait l'échéance à
|
|
463
|
+
// chaque signal : un participant diffusant toutes les 100 ms empêchait la relecture
|
|
464
|
+
// INDÉFINIMENT. Toute la défense de 0.1.19 repose sur cette relecture — l'affamer ne
|
|
465
|
+
// falsifie rien, ça fige simplement l'audience, sans qu'aucune erreur ne le dise.
|
|
466
|
+
// Et à l'inverse, des signaux un peu plus espacés produisaient une requête chacun, POUR
|
|
467
|
+
// CHAQUE SPECTATEUR : le canal public devenait un amplificateur vers l'API.
|
|
468
|
+
// Détail et propriétés : src/live.ts + src/__tests__/ordonnanceur.test.ts. (audit P0-2)
|
|
469
|
+
function relireAvec(url,applique){
|
|
470
|
+
return Player.live.createScheduler(function(fini){
|
|
471
|
+
fetch('/api/doc?present='+encodeURIComponent(SLUG)+url)
|
|
472
|
+
.then(function(r){return r.json();})
|
|
473
|
+
.then(function(d){if(d&&d.ok)applique(d);})
|
|
474
|
+
.catch(function(){})
|
|
475
|
+
.then(fini,fini);
|
|
476
|
+
},{minMs:400});
|
|
477
|
+
}
|
|
478
|
+
var _ordEtat=relireAvec('&state=1',function(d){if(d.state)etatDuServeur(d.state);});
|
|
479
|
+
function relireEtat(){_ordEtat.signaler();}
|
|
480
|
+
function relireChat(){_ordChat.signaler();}
|
|
481
|
+
var _ordChat=relireAvec('&chat=1',function(d){
|
|
468
482
|
// ⚠️ NOTIFIER CE QUI VIENT D'ARRIVER, ET SEULEMENT ÇA. 'addMsg' rend faux pour un
|
|
469
483
|
// message déjà connu — la relecture ramène tout l'historique, donc sans cette condition
|
|
470
484
|
// la pastille de non-lus compterait chaque message à chaque relecture. Et sans l'appel,
|
|
@@ -472,8 +486,14 @@ var Live=(function(){
|
|
|
472
486
|
// lisant le source de ce paquet, à travers la frontière de deux dépôts.
|
|
473
487
|
if(d.messages)d.messages.forEach(function(m){if(addMsg(m))notifyMsg(m);else updateMsg(m);});
|
|
474
488
|
if(typeof d.locked!=='undefined')applyLock(d.locked);
|
|
475
|
-
|
|
476
|
-
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
// ⚠️ LE FILET. Borner la cadence ouvre la possibilité qu'un signal se perde — un WebSocket
|
|
492
|
+
// qui tombe, un onglet endormi, un message jamais délivré. Une resynchronisation lente
|
|
493
|
+
// rattrape ce cas : elle ne coûte presque rien et évite qu'une audience reste figée sur un
|
|
494
|
+
// état périmé en croyant être à jour. C'est l'inverse d'une optimisation : c'est le prix de
|
|
495
|
+
// la borne.
|
|
496
|
+
var _filet=setInterval(function(){_ordEtat.maintenant();_ordChat.maintenant();},25000);
|
|
477
497
|
|
|
478
498
|
// ⚠️ LE TITRE VIENT D'ICI, PAS DE LA PRÉSENCE. La liste des participants tirait
|
|
479
499
|
// « présentateur » de la charge de présence, que chacun compose lui-même : un
|
|
@@ -484,8 +504,8 @@ var Live=(function(){
|
|
|
484
504
|
// Le serveur renvoie la CLÉ de celui qui a prouvé le control_token ; l'audience compare. Pas
|
|
485
505
|
// de clé, pas de titre : mieux vaut aucun titre qu'un titre usurpé.
|
|
486
506
|
function etatDuServeur(st){
|
|
487
|
-
if(typeof st.
|
|
488
|
-
if(
|
|
507
|
+
if(typeof st.presenter_name!=='undefined'){var n2=st.presenter_name||'';
|
|
508
|
+
if(n2!==PRESNAME){PRESNAME=n2;try{if(ch)renderPres(ch.presenceState());}catch(e){}}}
|
|
489
509
|
if(_onState)_onState(st);
|
|
490
510
|
}
|
|
491
511
|
relire('&state=1',function(d){if(d.state)etatDuServeur(d.state);});
|
|
@@ -503,7 +523,7 @@ var Live=(function(){
|
|
|
503
523
|
_atIv=setInterval(sendAttend,25000);
|
|
504
524
|
// Filet de sécurité : au déchargement de la page/iframe (fermeture, reload, switch), on retire la présence
|
|
505
525
|
// → évite les fantômes (« je me vois deux fois » au retour). Une seule fois.
|
|
506
|
-
if(!_phWired){_phWired=true;window.addEventListener('pagehide',function(){try{if(ch){ch.untrack();ch.unsubscribe();ch=null;}}catch(e){}});}
|
|
526
|
+
if(!_phWired){_phWired=true;window.addEventListener('pagehide',function(){try{clearInterval(_filet);_ordEtat.arreter();_ordChat.arreter();}catch(e){}try{if(ch){ch.untrack();ch.unsubscribe();ch=null;}}catch(e){}});}
|
|
507
527
|
}catch(e){}}
|
|
508
528
|
function disconnect(){try{clearInterval(_tyIv);}catch(e){}try{clearInterval(_atIv);}catch(e){}try{sendAttend();}catch(e){}try{if(ch){ch.untrack();ch.unsubscribe();ch=null;}}catch(e){}var pb=document.getElementById('presBtn');if(pb)pb.style.display='none';var cb=document.getElementById('chatBtn');if(cb)cb.style.display='none';var _fb=document.getElementById('chatFab');if(_fb)_fb.classList.remove('on');var pn=document.getElementById('chatPanel');if(pn)pn.classList.add('hidden');}
|
|
509
529
|
// Membre de l'équipe reconnu via la session app (MÊME ORIGINE, localStorage) → avatar + nom auto.
|
|
@@ -3073,10 +3093,26 @@ async function handler(req, res) {
|
|
|
3073
3093
|
file_name: pres.file_name || null,
|
|
3074
3094
|
doc_title: pres.doc_title || null,
|
|
3075
3095
|
updated_at: pres.updated_at || null,
|
|
3076
|
-
//
|
|
3077
|
-
//
|
|
3078
|
-
//
|
|
3079
|
-
|
|
3096
|
+
// ⚠️ CE CHAMP A PORTÉ `presenter_key` PENDANT UNE JOURNÉE, ET C'ÉTAIT DEUX FAUTES.
|
|
3097
|
+
//
|
|
3098
|
+
// 1. FUITE. `attendeeKey()` renvoie l'ADRESSE E-MAIL quand le participant en a une. La clé
|
|
3099
|
+
// du présentateur était donc son e-mail, servi à tout visiteur anonyme du lien — sur la
|
|
3100
|
+
// route même dont le commentaire ci-dessus promet « rien que ce que l'audience doit
|
|
3101
|
+
// savoir ». Le champ portait un nom technique, et personne (moi compris) n'est allé
|
|
3102
|
+
// voir ce qu'il contenait.
|
|
3103
|
+
//
|
|
3104
|
+
// 2. CE N'ÉTAIT PAS UNE PREUVE. Le badge comparait cette clé à l'`uid` de la charge de
|
|
3105
|
+
// présence — que le client COMPOSE. Lire la clé publique puis s'annoncer avec elle
|
|
3106
|
+
// suffisait à porter le titre. On avait remplacé « le client déclare son rôle » par
|
|
3107
|
+
// « le client déclare une valeur que le serveur lui a donnée » : plus laborieux à
|
|
3108
|
+
// exploiter, pas plus vrai.
|
|
3109
|
+
//
|
|
3110
|
+
// ⚠️ Comparer deux valeurs que le client choisit ou connaît n'est pas une preuve. Le nom
|
|
3111
|
+
// du présentateur vient de l'hôte, il ne se compare à rien, et il s'affiche à part de la
|
|
3112
|
+
// liste des participants — laquelle ne porte plus aucun titre.
|
|
3113
|
+
//
|
|
3114
|
+
// Signalé par la seconde passe d'audit (P0-4) ; la fuite n'y était pas.
|
|
3115
|
+
presenter_name: String(pres.presenter_name || "") || null,
|
|
3080
3116
|
} }));
|
|
3081
3117
|
return;
|
|
3082
3118
|
}
|
package/server/presentations.js
CHANGED
|
@@ -281,29 +281,6 @@ async function toggleReaction(slug, msgId, emoji, reactor) {
|
|
|
281
281
|
return { ok: true, message: premierPublic(maj) };
|
|
282
282
|
}
|
|
283
283
|
|
|
284
|
-
/**
|
|
285
|
-
* La clé du participant qui porte le titre de présentateur, telle que le SERVEUR la connaît.
|
|
286
|
-
*
|
|
287
|
-
* ⚠️ Elle existe parce que la liste des participants affichait le titre d'après la charge de
|
|
288
|
-
* PRÉSENCE, que chacun compose lui-même : `track({role:'presenter'})` suffisait à apparaître comme
|
|
289
|
-
* présentateur devant toute l'audience, avec le nom et l'avatar de son choix. Le canal ne peut pas
|
|
290
|
-
* arbitrer ça — un participant légitime a le droit d'y écrire sa présence.
|
|
291
|
-
*
|
|
292
|
-
* Le titre est désormais accordé ici, à partir de `is_presenter`, lui-même dérivé du `control_token`.
|
|
293
|
-
* Null quand personne ne l'a prouvé : mieux vaut aucun titre qu'un titre usurpé.
|
|
294
|
-
*/
|
|
295
|
-
async function presenterKey(slug) {
|
|
296
|
-
if (!slug) return null;
|
|
297
|
-
// ⚠️ NE JAMAIS FAIRE ÉCHOUER LA RELECTURE D'ÉTAT. Cette recherche est un ajout à une route dont
|
|
298
|
-
// l'audience dépend pour savoir quelle page est affichée. Une requête de plus, c'est une raison
|
|
299
|
-
// de plus de renvoyer 500 — et perdre TOUT l'état parce qu'on n'a pas su dire qui porte un badge
|
|
300
|
-
// serait un très mauvais échange. Sans réponse : pas de clé, donc pas de titre, et le reste passe.
|
|
301
|
-
try {
|
|
302
|
-
const rows = await PLAYER.db.request(`doc_presentation_attendees?slug=eq.${enc(slug)}&is_presenter=is.true&select=attendee_key&order=last_seen.desc&limit=1`);
|
|
303
|
-
return (Array.isArray(rows) && rows[0] && rows[0].attendee_key) || null;
|
|
304
|
-
} catch { return null; }
|
|
305
|
-
}
|
|
306
|
-
|
|
307
284
|
// ── Statistiques de présentation (assistance) ────────────────────────────────────────────────────────────
|
|
308
285
|
// Heartbeat d'un participant : upsert de sa ligne d'assistance. On accumule le temps de présence (intervalles
|
|
309
286
|
// < 60 s → un aller-retour ne gonfle pas total_ms) et l'ensemble des pages vues (page courante de la présentation).
|
|
@@ -410,4 +387,4 @@ async function listPresentationsForDoc(docId) {
|
|
|
410
387
|
}
|
|
411
388
|
|
|
412
389
|
module.exports = {
|
|
413
|
-
messagePublic, CHAMPS_PUBLICS, init, createPresentation, getPresentation, setPage, endPresentation, addMessage, listMessages, toggleReaction, editMessage, deleteMessage, setChatLock, createUploadUrl, reclaimPresentation, touchPresentation, listActivePresentations, handoverPresentation, endPresentationByOwner, recordAttendance,
|
|
390
|
+
messagePublic, CHAMPS_PUBLICS, init, createPresentation, getPresentation, setPage, endPresentation, addMessage, listMessages, toggleReaction, editMessage, deleteMessage, setChatLock, createUploadUrl, reclaimPresentation, touchPresentation, listActivePresentations, handoverPresentation, endPresentationByOwner, recordAttendance, presentationStats, listPresentationsForDoc, switchPresentationDoc, setPresentationContent };
|