discovery-media-player 0.1.83 → 0.1.85

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.
@@ -159,16 +159,27 @@ function creerLimites(db, journal) {
159
159
  let partageDisponible = null; // null = pas encore demandé
160
160
  let signale = false;
161
161
 
162
- /** Le compteur local : sert de refus rapide, et de repli complet quand la table manque. */
162
+ // ⚠️ FENÊTRE FIXE, O(1) PAR DÉCISION (P1 performance). L'ancien compteur gardait un TABLEAU de
163
+ // timestamps par clé et le re-filtrait à chaque appel — quadratique quand une clé accumule (une
164
+ // clé à 88 400 entrées scannait 88 400 valeurs même pour un REFUS). Ici, par clé : { debut,
165
+ // compte }. La fenêtre écoulée remet le compteur à zéro ; sinon on compare et on incrémente.
166
+ // Une fenêtre fixe laisse au pire 2×max au JOINT de deux fenêtres — acceptable pour un plafond
167
+ // anti-inondation, et c'est le compromis explicitement recommandé.
168
+ const PLAFOND_CLES = 5000;
163
169
  function localAutorise(cle, max, fenetreSecondes) {
164
170
  const maintenant = Date.now();
165
- const debut = maintenant - fenetreSecondes * 1000;
166
- const vus = (seaux.get(cle) || []).filter((t) => t > debut);
167
- if (vus.length >= max) { seaux.set(cle, vus); return false; }
168
- vus.push(maintenant);
169
- seaux.set(cle, vus);
170
- if (seaux.size > 5000) for (const [k, v] of seaux) if (!v.some((t) => t > debut)) seaux.delete(k);
171
- return true;
171
+ const fenetreMs = fenetreSecondes * 1000;
172
+ let e = seaux.get(cle);
173
+ if (!e || maintenant - e.debut >= fenetreMs) e = { debut: maintenant, compte: 0 };
174
+ // LRU : re-poser la clé la remet en fin d'ordre d'insertion (la Map préserve cet ordre).
175
+ seaux.delete(cle);
176
+ const permis = e.compte < max;
177
+ if (permis) e.compte += 1;
178
+ seaux.set(cle, e);
179
+ // ⚠️ PLAFOND DUR : on évince la plus ANCIENNE (tête de la Map) tant qu'on dépasse — l'ancien
180
+ // code n'enlevait que les clés expirées, donc 5 001 clés actives restaient en mémoire.
181
+ while (seaux.size > PLAFOND_CLES) seaux.delete(seaux.keys().next().value);
182
+ return permis;
172
183
  }
173
184
 
174
185
  let deja = "";
@@ -624,4 +635,4 @@ function createStandaloneContext(env = process.env) {
624
635
  };
625
636
  }
626
637
 
627
- module.exports = { createStandaloneContext };
638
+ module.exports = { createStandaloneContext, creerLimites };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "discovery-media-player",
3
- "version": "0.1.83",
3
+ "version": "0.1.85",
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 C=Object.defineProperty,Ue=Object.defineProperties,De=Object.getOwnPropertyDescriptor,We=Object.getOwnPropertyDescriptors,Ve=Object.getOwnPropertyNames,he=Object.getOwnPropertySymbols;var Me=Object.prototype.hasOwnProperty,ze=Object.prototype.propertyIsEnumerable;var ve=(e,n,t)=>n in e?C(e,n,{enumerable:!0,configurable:!0,writable:!0,value:t}):e[n]=t,G=(e,n)=>{for(var t in n||(n={}))Me.call(n,t)&&ve(e,t,n[t]);if(he)for(var t of he(n))ze.call(n,t)&&ve(e,t,n[t]);return e},we=(e,n)=>Ue(e,We(n));var v=(e,n)=>{for(var t in n)C(e,t,{get:n[t],enumerable:!0})},Fe=(e,n,t,r)=>{if(n&&typeof n==\"object\"||typeof n==\"function\")for(let o of Ve(n))!Me.call(e,o)&&o!==t&&C(e,o,{get:()=>n[o],enumerable:!(r=De(n,o))||r.enumerable});return e};var Be=e=>Fe(C({},\"__esModule\",{value:!0}),e);var Bn={};v(Bn,{bridge:()=>N,cadence:()=>L,chat:()=>H,live:()=>$,presentation:()=>U,presentationState:()=>W,tracking:()=>O,viewer:()=>D});var N={};v(N,{onHostMessage:()=>en,onPlayerMessage:()=>Je,parseHostMessage:()=>_e,parsePlayerMessage:()=>Te,sendToHost:()=>qe,sendToPlayer:()=>Qe,toWire:()=>Y});var K=\"3dd-doc-\",je=/^[A-Za-z0-9_-]{1,64}$/,Ge=[\"close\",\"share\",\"embed-ready\",\"present-left\",\"present-denied\"],Ke=[\"embed-denied\"],Ye=[\"present-invite\",\"present-handover\",\"present-switch\"],Ze=[\"handover-done\"];function Y(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 xe(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(Ke.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\"||!je.test(i)?null:{type:o,slug:i}}function Te(e){return xe(e,Ge,Ye)}function _e(e){return xe(e,Ze,[])}function qe(e,n){try{let t=n||(typeof window!=\"undefined\"?window.parent:null);t&&t.postMessage(Y(e),\"*\")}catch(t){}}function Qe(e,n){var t;try{(t=e==null?void 0:e.contentWindow)==null||t.postMessage(Y(n),\"*\")}catch(r){}}function Xe(e,n){var r;if(!e)return!0;let t=(r=e.contentWindow)!=null?r:e;return n===t}function Je(e,n,t){let r=n||(typeof window!=\"undefined\"?window:null);if(!r)return()=>{};let o=i=>{if(!Xe(t,i.source))return;let a=Te(i.data);a&&e(a)};return r.addEventListener(\"message\",o),()=>r.removeEventListener(\"message\",o)}function en(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 l=_e(a.data);l&&e(l)};return t.addEventListener(\"message\",o),()=>t.removeEventListener(\"message\",o)}var O={};v(O,{createTracker:()=>ln});var L={};v(L,{PRESENTER_ACTIONS_PER_HOUR:()=>X,PRESENT_CACHE_MS:()=>rn,PRESENT_QUOTA_MARGIN:()=>Ae,PRESENT_QUOTA_PER_HOUR:()=>on,PRESENT_READS_PER_HOUR:()=>Re,PRESENT_READ_BURST:()=>Pe,PRESENT_READ_COALESCE_MS:()=>Ie,PRESENT_RESYNC_MS:()=>Se,PRESENT_SIGNAL_BUDGET_PER_HOUR:()=>tn,PRESENT_WRITE_TIMEOUT_MS:()=>J,READERS_PER_EGRESS:()=>q,RESYNC_READS_PER_HOUR:()=>ke,SESSION_IDLE_MS:()=>Q,SESSION_INTERVAL_MS:()=>Z,SESSION_QUOTA_PER_HOUR:()=>nn,SESSION_WRITES_PER_HOUR:()=>Ee});var Z=12e3,Ee=Math.ceil(300),q=25,nn=Ee*q,Q=18e4,Se=25e3,ke=Math.ceil(36e5/Se),X=720,Re=ke+X,tn=X,Pe=20,J=1e4,Ie=400,rn=Ie,Ae=4,on=(Re+Pe)*q*Ae;var sn=[\"mousemove\",\"mousedown\",\"keydown\",\"wheel\",\"touchstart\",\"pointerdown\"];function an(){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 un(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 ln(e={}){var ge,be;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=(ge=e.idleMs)!=null?ge:Q,a=(be=e.sessionEveryMs)!=null?be:12e3,l=e.slug||\"\",d=e.internal||null,m=e.sessionId||an(),p=e.send||un(o,n),c={},u=0,g=0,f=0,M=0,h=null,E=r(),S=!1,V=!1,I=[],He=()=>!!l||!!d,ce=s=>{if(He())try{p(s)}catch(b){}},z=()=>!!t&&t.visibilityState===\"visible\"&&!S,de=s=>{let b=r()-s,x=Math.max(0,E-s)+i;return Math.max(0,Math.min(b,x))},F=()=>{h!=null&&u>0&&(c[u]=(c[u]||0)+de(h)/1e3),h=z()?r():null},pe=()=>{F(),h=null},B=()=>{h==null&&z()&&(h=r())},j=()=>{let s=0;for(let b in c)s+=c[b];return h!=null&&(s+=de(h)/1e3),Math.round(s)},fe=()=>{let s={};for(let b in c)s[b]=Math.round(c[b]);return s},k=()=>{E=r(),S&&(S=!1,B())},me=(s,b)=>{d||ce({slug:l,event:s,page:b||g,maxPage:f,seconds:j(),sessionId:m})},A=()=>{F();let s={event:\"session\",sessionId:m,numPages:M,maxPage:f,totalSeconds:j(),pagesTime:fe()};d?(s.internal=!0,s.docId=d.docId,s.email=d.email,s.name=d.name,d.it&&(s.it=d.it)):s.slug=l,ce(s)},R=(s,b,x,ye)=>{!s||typeof s.addEventListener!=\"function\"||(s.addEventListener(b,x,ye),I.push(()=>{s.removeEventListener(b,x,ye)}))};return{sessionId:m,totalSeconds:j,maxPage:()=>f,pageTimes:fe,start(){if(V)return;V=!0,me(\"open\",1),u=1,E=r(),h=z()?r():null,R(t,\"visibilitychange\",()=>{t.visibilityState===\"hidden\"?(pe(),A()):(k(),B())}),R(n,\"focus\",()=>{k(),B()});for(let x of sn)R(n,x,k,{passive:!0});R(e.scrollElement,\"scroll\",k,{passive:!0}),R(n,\"beforeunload\",A);let s=n.setInterval(()=>{!S&&r()-E>i&&(S=!0,pe())},5e3),b=n.setInterval(A,a);I.push(()=>{n.clearInterval(s),n.clearInterval(b)})},setPage(s){!s||s===g||(E=r(),F(),u=s,g=s,s>f&&(f=s,me(\"page\",s)))},setPageCount(s){M=s||0},noteActivity:k,flush:A,stop(){for(;I.length;){let s=I.pop();try{s==null||s()}catch(b){}}V=!1}}}var $={};v($,{STORAGE_KEYS:()=>w,attendeeKey:()=>fn,authorToken:()=>mn,avatarHtml:()=>ee,canModerate:()=>oe,createBudget:()=>wn,createFileEcritures:()=>xn,createScheduler:()=>vn,escapeHtml:()=>y,fetchBorne:()=>Mn,flattenPresence:()=>cn,formatMessageBody:()=>ne,initials:()=>Ce,isMentioned:()=>bn,isMine:()=>T,presenceId:()=>Tn,reactorId:()=>re,referenceAuteur:()=>gn,shouldNotify:()=>yn,unreadLabel:()=>hn});var w={attendeeKey:\"3dd-present-attkey\",authorToken:\"3dd-present-authtoken\",chatMuted:\"3dd-present-mute\",presenceId:\"3dd-present-uid\"};function y(e){return String(e==null?\"\":e).replace(/&/g,\"&amp;\").replace(/</g,\"&lt;\").replace(/>/g,\"&gt;\").replace(/\"/g,\"&quot;\")}function Ce(e){let n=(e||\"?\").trim().split(/\\s+/);return(((n[0]||\"\")[0]||\"?\")+((n[1]||\"\")[0]||\"\")).toUpperCase()}function ee(e,n){return e?`<img src=\"${y(e)}\" alt=\"\">`:y(Ce(n))}function ne(e){let n=y(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 cn(e){let n=new Map;for(let t in e||{})for(let r of(e||{})[t]||[]){let o=String(r.uid||r.email||r.name||\"\").toLowerCase()||`_${t}_${n.size}`;(!n.get(o)||r.role===\"presenter\")&&n.set(o,r)}return[...n.values()]}function dn(e,n){try{return e?e.getItem(n):null}catch(t){return null}}function pn(e,n,t){try{e==null||e.setItem(n,t)}catch(r){}}function fn(e,n=\"anon\",t=te){try{if(!e)return`anon-${n}`;let r=e.getItem(w.attendeeKey);if(r)return r;let o=`anon-${t()}`;return e.setItem(w.attendeeKey,o),o}catch(r){return`anon-${n}`}}function te(){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 mn(e,n=te){let t=dn(e,w.authorToken);if(t)return t;let r=n();return pn(e,w.authorToken,r),r}async function gn(e,n){let t=n!==void 0?n:typeof globalThis!=\"undefined\"&&globalThis.crypto?globalThis.crypto.subtle:null;if(!t||!e)return\"\";try{let r=async o=>{let i=await t.digest(\"SHA-256\",new TextEncoder().encode(o));return Array.from(new Uint8Array(i),a=>a.toString(16).padStart(2,\"0\")).join(\"\")};return(await r(\"ref:\"+await r(e))).slice(0,16)}catch(r){return\"\"}}function re(e){return String(e&&e.ref||\"\")}function T(e,n){return!e||!n||!e.author_ref||!n.ref?!1:e.author_ref===n.ref}function oe(e){return!!(e&&e.role===\"presenter\")}function bn(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}`)&&!T(e,n):!1}function yn(e){let{msg:n,me:t,historyLoaded:r,chatHidden:o}=e;return!r||!n||n.deleted||T(n,t)?!1:o}function hn(e){return!e||e<=0?\"\":e>9?\"9+\":String(e)}function vn(e,n={}){var g;let t=(g=n.minMs)!=null?g:300,r=n.now||(()=>Date.now()),o=n.setTimer||((f,M)=>setTimeout(f,M)),i=n.clearTimer||(f=>clearTimeout(f)),a=!1,l=!1,d=-1/0,m=null,p=!1;function c(){if(p||a){l=!0;return}let f=t-(r()-d);if(f>0){u(f);return}a=!0,l=!1,d=r();let M=!1;e(()=>{M||(M=!0,a=!1,l&&!p&&c())})}function u(f){p||m!==null||(m=o(()=>{m=null,c()},Math.max(0,f)))}return{signaler(){p||c()},maintenant(){p||(d=-1/0,c())},arreter(){p=!0,m!==null&&(i(m),m=null)}}}function Mn(e,n,t=J,r={}){let o=r.fetch||(typeof fetch==\"function\"?fetch:void 0);if(!o)return Promise.reject(new Error(\"fetch indisponible\"));let i=r.setTimer||((u,g)=>setTimeout(u,g)),a=r.clearTimer||(u=>clearTimeout(u)),l=null;try{l=new AbortController}catch(u){}let d=l?we(G({},n||{}),{signal:l.signal}):G({},n||{}),m=null,p=new Promise((u,g)=>{m=i(()=>{try{l==null||l.abort()}catch(f){}g(new Error(\"delai\"))},t)}),c=()=>{try{a(m)}catch(u){}};return Promise.race([o(e,d),p]).then(u=>(c(),u),u=>{throw c(),u})}function wn(e){let n=e.now||(()=>Date.now()),t=Math.max(1,e.rafale),r=t,o=n();return{prendre(){let i=n();return r=Math.min(t,r+(i-o)*e.parHeure/36e5),o=i,r<1?!1:(r-=1,!0)},restants:()=>r}}function xn(e={}){var m;let n=(m=e.minMs)!=null?m:0,t=e.now||(()=>Date.now()),r=e.setTimer||((p,c)=>setTimeout(p,c)),o=new Map,i=!1,a=-1/0,l=!1;function d(){if(i||o.size===0)return;let p=n-(t()-a);if(p>0){if(l)return;l=!0,r(()=>{l=!1,d()},p);return}let c=o.keys().next().value,u=o.get(c);o.delete(c),i=!0,a=t(),Promise.resolve().then(u.tache).then(g=>u.regler(g),()=>u.regler(null)).then(()=>{i=!1,d()})}return{poser(p,c){return new Promise(u=>{let g=o.get(p);o.set(p,{tache:c,regler:f=>{g&&g.regler(f),u(f)}}),d()})},enAttente:()=>o.size}}function Tn(e,n=te){try{if(e){let t=e.getItem(w.presenceId);if(t)return t;let r=`p-${n()}`;return e.setItem(w.presenceId,r),r}}catch(t){}return`p-${n()}`}var H={};v(H,{messageClassName:()=>En,renderActions:()=>Oe,renderAttachment:()=>Le,renderMessage:()=>_n,renderReactions:()=>Ne});function Ne(e,n){let t=e&&e.reactions||{},r=re(n),o=\"\";for(let i in t){let a=t[i]||[];if(!a.length)continue;let l=a.indexOf(r)>=0;o+=`<button class=\"re-chip${l?\" on\":\"\"}\" data-e=\"${y(i)}\">${y(i)} ${a.length}</button>`}return o}function Le(e){if(!e||!e.url)return\"\";let n=y(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=y(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 Oe(e,n){let t=T(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||oe(n.me))&&(r+='<button class=cm-del-btn title=\"Supprimer\">\\u{1F5D1}</button>'),r}function _n(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>${y(e.reply_name||\"\")}</b> ${y(e.reply_text||\"\")}</div>`:\"\",r=e.edited?\" <span class=cm-ed>(modifi\\xE9)</span>\":\"\",o=e.body?`<div class=txt>${ne(e.body)}${r}</div>`:\"\",i=e.is_presenter?\" <span class=tag>pr\\xE9sentateur</span>\":\"\";return`<span class=a>${ee(e.author_avatar,e.author_name)}</span><span class=b><div class=who><b>${y(e.author_name||\"Invit\\xE9\")}</b>${i}</div>`+t+o+Le(e.attachment)+`<div class=cm-re>${Ne(e,n.me)}</div></span><span class=cm-act>${Oe(e,n)}</span>`}function En(e,n,t){return\"cm\"+(T(e,n)?\" me\":\"\")+(e.deleted?\" isdel\":\"\")+(t?\" mentioned\":\"\")}var U={};v(U,{DEFAULT_CENTER:()=>ae,DEFAULT_ZOOM:()=>ue,MAP_TYPES:()=>P,cycleMapType:()=>Sn,initialMapContent:()=>Rn,mapTypeLabel:()=>kn,sanitizeContent:()=>le});var P=[\"roadmap\",\"satellite\",\"hybrid\"],ae=[46.6,2.5],ue=6;function _(e){let n=Number(e);return Number.isFinite(n)?n:null}function se(e){if(!Array.isArray(e))return null;let n=_(e[0]),t=_(e[1]);return n!=null&&t!=null?[n,t]:null}var ie=(e,n,t)=>Math.max(n,Math.min(t,e));function le(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=se(n.position);if(!t)return null;let r=n.pov&&typeof n.pov==\"object\"?n.pov:null,o=r?{heading:_(r.heading)||0,pitch:ie(_(r.pitch)||0,-90,90)}:{heading:0,pitch:0};return{kind:\"streetview\",position:t,pov:o,zoom:ie(_(n.zoom)||1,0,5)}}return n.kind!==\"map\"?null:{kind:\"map\",center:se(n.center)||[...ae],zoom:ie(Math.trunc(_(n.zoom)||ue),1,21),marker:se(n.marker),mapType:P.includes(n.mapType)?n.mapType:null,label:String(n.label||\"\").slice(0,160)||null}}function Sn(e){let n=P.indexOf(e);return P[(n+1)%P.length]}function kn(e){return e===\"roadmap\"?\"\\u{1F6F0} Satellite\":e===\"satellite\"?\"\\u{1F5FA} Hybride\":\"\\u{1F5FA} Plan\"}function Rn(){return{kind:\"map\",center:[...ae],zoom:ue,marker:null,mapType:null,label:null}}var D={};v(D,{CURRENT_PAGE_MARGIN:()=>Ln,DEFAULT_ASPECT:()=>An,MAX_ZOOM:()=>In,MIN_PAGE_WIDTH:()=>Cn,MIN_ZOOM:()=>Pn,PRERENDER_MARGIN:()=>Nn,arrowState:()=>Hn,averageColor:()=>Wn,clampPage:()=>$e,clampZoom:()=>On,fitWidth:()=>Dn,isImageDocument:()=>Vn,progressPercent:()=>$n});var Pn=.5,In=3,An=1.35,Cn=280,Nn=\"500px 0px\",Ln=\"-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 $e(e,n){return Math.max(1,Math.min(n||1,Number(e)||1))}function $n(e,n){return n?Math.round($e(e,n)/n*100):0}function Hn(e,n){return{prevDisabled:e<=1,nextDisabled:!!n&&e>=n}}var Un=28;function Dn(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)-Un-(e.overlap||0)-(e.reserve||0))/r;return i>120?Math.min(t,i):t}function Wn(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 Vn(e,n){return/[.](png|jpe?g|webp|gif|avif)($|[?])/i.test(String(e||n||\"\"))}var W={};v(W,{presentationTransition:()=>zn,switchDocUrl:()=>Fn});function zn(e,n={}){if(!e)return[{kind:\"nothing\"}];if(e.active===!1)return[{kind:\"ended\"}];let t=le(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 Be(Bn);})();", SOURCE_HASH: "6e69736fc03cc5f0" };
7
+ module.exports = { PLAYER_BROWSER_JS: "\"use strict\";var Player=(()=>{var C=Object.defineProperty,De=Object.defineProperties,We=Object.getOwnPropertyDescriptor,Ve=Object.getOwnPropertyDescriptors,ze=Object.getOwnPropertyNames,ve=Object.getOwnPropertySymbols;var we=Object.prototype.hasOwnProperty,Fe=Object.prototype.propertyIsEnumerable;var Me=(e,n,t)=>n in e?C(e,n,{enumerable:!0,configurable:!0,writable:!0,value:t}):e[n]=t,G=(e,n)=>{for(var t in n||(n={}))we.call(n,t)&&Me(e,t,n[t]);if(ve)for(var t of ve(n))Fe.call(n,t)&&Me(e,t,n[t]);return e},xe=(e,n)=>De(e,Ve(n));var M=(e,n)=>{for(var t in n)C(e,t,{get:n[t],enumerable:!0})},Be=(e,n,t,r)=>{if(n&&typeof n==\"object\"||typeof n==\"function\")for(let o of ze(n))!we.call(e,o)&&o!==t&&C(e,o,{get:()=>n[o],enumerable:!(r=We(n,o))||r.enumerable});return e};var je=e=>Be(C({},\"__esModule\",{value:!0}),e);var jn={};M(jn,{bridge:()=>N,cadence:()=>L,chat:()=>H,live:()=>$,presentation:()=>U,presentationState:()=>W,tracking:()=>O,viewer:()=>D});var N={};M(N,{onHostMessage:()=>nn,onPlayerMessage:()=>en,parseHostMessage:()=>Ee,parsePlayerMessage:()=>_e,sendToHost:()=>Qe,sendToPlayer:()=>Xe,toWire:()=>Y});var K=\"3dd-doc-\",Ge=/^[A-Za-z0-9_-]{1,64}$/,Ke=[\"close\",\"share\",\"embed-ready\",\"present-left\",\"present-denied\"],Ye=[\"embed-denied\"],Ze=[\"present-invite\",\"present-handover\",\"present-switch\"],qe=[\"handover-done\"];function Y(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 Te(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(Ye.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\"||!Ge.test(s)?null:{type:o,slug:s}}function _e(e){return Te(e,Ke,Ze)}function Ee(e){return Te(e,qe,[])}function Qe(e,n){try{let t=n||(typeof window!=\"undefined\"?window.parent:null);t&&t.postMessage(Y(e),\"*\")}catch(t){}}function Xe(e,n){var t;try{(t=e==null?void 0:e.contentWindow)==null||t.postMessage(Y(n),\"*\")}catch(r){}}function Je(e,n){var r;if(!e)return!0;let t=(r=e.contentWindow)!=null?r:e;return n===t}function en(e,n,t){let r=n||(typeof window!=\"undefined\"?window:null);if(!r)return()=>{};let o=s=>{if(!Je(t,s.source))return;let a=_e(s.data);a&&e(a)};return r.addEventListener(\"message\",o),()=>r.removeEventListener(\"message\",o)}function nn(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 l=Ee(a.data);l&&e(l)};return t.addEventListener(\"message\",o),()=>t.removeEventListener(\"message\",o)}var O={};M(O,{createTracker:()=>cn});var L={};M(L,{PRESENTER_ACTIONS_PER_HOUR:()=>X,PRESENT_CACHE_MS:()=>on,PRESENT_QUOTA_MARGIN:()=>Ce,PRESENT_QUOTA_PER_HOUR:()=>sn,PRESENT_READS_PER_HOUR:()=>Pe,PRESENT_READ_BURST:()=>Ie,PRESENT_READ_COALESCE_MS:()=>Ae,PRESENT_RESYNC_MS:()=>ke,PRESENT_SIGNAL_BUDGET_PER_HOUR:()=>rn,PRESENT_WRITE_TIMEOUT_MS:()=>J,READERS_PER_EGRESS:()=>q,RESYNC_READS_PER_HOUR:()=>Re,SESSION_IDLE_MS:()=>Q,SESSION_INTERVAL_MS:()=>Z,SESSION_QUOTA_PER_HOUR:()=>tn,SESSION_WRITES_PER_HOUR:()=>Se});var Z=3e4,Se=Math.ceil(120),q=25,tn=Se*q,Q=18e4,ke=25e3,Re=Math.ceil(36e5/ke),X=720,Pe=Re+X,rn=X,Ie=20,J=1e4,Ae=400,on=Ae,Ce=4,sn=(Pe+Ie)*q*Ce;var an=[\"mousemove\",\"mousedown\",\"keydown\",\"wheel\",\"touchstart\",\"pointerdown\"];function un(){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 ln(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 cn(e={}){var ye,be;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=(ye=e.idleMs)!=null?ye:Q,a=(be=e.sessionEveryMs)!=null?be:3e4,l=e.slug||\"\",p=e.internal||null,g=e.sessionId||un(),f=e.send||ln(o,n),c={},u=0,y=0,d=0,w=0,v=null,E=r(),S=!1,V=!1,I=[],Ue=()=>!!l||!!p,ce=i=>{if(Ue())try{f(i)}catch(m){}},z=()=>!!t&&t.visibilityState===\"visible\"&&!S,de=i=>{let m=r()-i,b=Math.max(0,E-i)+s;return Math.max(0,Math.min(m,b))},F=()=>{v!=null&&u>0&&(c[u]=(c[u]||0)+de(v)/1e3),v=z()?r():null},pe=()=>{F(),v=null},B=()=>{v==null&&z()&&(v=r())},j=()=>{let i=0;for(let m in c)i+=c[m];return v!=null&&(i+=de(v)/1e3),Math.round(i)},fe=()=>{let i={};for(let m in c)i[m]=Math.round(c[m]);return i},k=()=>{E=r(),S&&(S=!1,B())},me=(i,m)=>{p||ce({slug:l,event:i,page:m||y,maxPage:d,seconds:j(),sessionId:g})},ge=\"\",A=()=>{F();let i=j(),m=`${i}|${d}|${w}`;if(m===ge)return;let b={event:\"session\",sessionId:g,numPages:w,maxPage:d,totalSeconds:i,pagesTime:fe()};p?(b.internal=!0,b.docId=p.docId,b.email=p.email,b.name=p.name,p.it&&(b.it=p.it)):b.slug=l,ge=m,ce(b)},R=(i,m,b,he)=>{!i||typeof i.addEventListener!=\"function\"||(i.addEventListener(m,b,he),I.push(()=>{i.removeEventListener(m,b,he)}))};return{sessionId:g,totalSeconds:j,maxPage:()=>d,pageTimes:fe,start(){if(V)return;V=!0,me(\"open\",1),u=1,E=r(),v=z()?r():null,R(t,\"visibilitychange\",()=>{t.visibilityState===\"hidden\"?(pe(),A()):(k(),B())}),R(n,\"focus\",()=>{k(),B()});for(let b of an)R(n,b,k,{passive:!0});R(e.scrollElement,\"scroll\",k,{passive:!0}),R(n,\"beforeunload\",A);let i=n.setInterval(()=>{!S&&r()-E>s&&(S=!0,pe())},5e3),m=n.setInterval(A,a);I.push(()=>{n.clearInterval(i),n.clearInterval(m)})},setPage(i){!i||i===y||(E=r(),F(),u=i,y=i,i>d&&(d=i,me(\"page\",i)))},setPageCount(i){w=i||0},noteActivity:k,flush:A,stop(){for(;I.length;){let i=I.pop();try{i==null||i()}catch(m){}}V=!1}}}var $={};M($,{STORAGE_KEYS:()=>x,attendeeKey:()=>mn,authorToken:()=>gn,avatarHtml:()=>ee,canModerate:()=>oe,createBudget:()=>xn,createFileEcritures:()=>Tn,createScheduler:()=>Mn,escapeHtml:()=>h,fetchBorne:()=>wn,flattenPresence:()=>dn,formatMessageBody:()=>ne,initials:()=>Ne,isMentioned:()=>bn,isMine:()=>T,presenceId:()=>_n,reactorId:()=>re,referenceAuteur:()=>yn,shouldNotify:()=>hn,unreadLabel:()=>vn});var x={attendeeKey:\"3dd-present-attkey\",authorToken:\"3dd-present-authtoken\",chatMuted:\"3dd-present-mute\",presenceId:\"3dd-present-uid\"};function h(e){return String(e==null?\"\":e).replace(/&/g,\"&amp;\").replace(/</g,\"&lt;\").replace(/>/g,\"&gt;\").replace(/\"/g,\"&quot;\")}function Ne(e){let n=(e||\"?\").trim().split(/\\s+/);return(((n[0]||\"\")[0]||\"?\")+((n[1]||\"\")[0]||\"\")).toUpperCase()}function ee(e,n){return e?`<img src=\"${h(e)}\" alt=\"\">`:h(Ne(n))}function ne(e){let n=h(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 dn(e){let n=new Map;for(let t in e||{})for(let r of(e||{})[t]||[]){let o=String(r.uid||r.email||r.name||\"\").toLowerCase()||`_${t}_${n.size}`;(!n.get(o)||r.role===\"presenter\")&&n.set(o,r)}return[...n.values()]}function pn(e,n){try{return e?e.getItem(n):null}catch(t){return null}}function fn(e,n,t){try{e==null||e.setItem(n,t)}catch(r){}}function mn(e,n=\"anon\",t=te){try{if(!e)return`anon-${n}`;let r=e.getItem(x.attendeeKey);if(r)return r;let o=`anon-${t()}`;return e.setItem(x.attendeeKey,o),o}catch(r){return`anon-${n}`}}function te(){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 gn(e,n=te){let t=pn(e,x.authorToken);if(t)return t;let r=n();return fn(e,x.authorToken,r),r}async function yn(e,n){let t=n!==void 0?n:typeof globalThis!=\"undefined\"&&globalThis.crypto?globalThis.crypto.subtle:null;if(!t||!e)return\"\";try{let r=async o=>{let s=await t.digest(\"SHA-256\",new TextEncoder().encode(o));return Array.from(new Uint8Array(s),a=>a.toString(16).padStart(2,\"0\")).join(\"\")};return(await r(\"ref:\"+await r(e))).slice(0,16)}catch(r){return\"\"}}function re(e){return String(e&&e.ref||\"\")}function T(e,n){return!e||!n||!e.author_ref||!n.ref?!1:e.author_ref===n.ref}function oe(e){return!!(e&&e.role===\"presenter\")}function bn(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}`)&&!T(e,n):!1}function hn(e){let{msg:n,me:t,historyLoaded:r,chatHidden:o}=e;return!r||!n||n.deleted||T(n,t)?!1:o}function vn(e){return!e||e<=0?\"\":e>9?\"9+\":String(e)}function Mn(e,n={}){var y;let t=(y=n.minMs)!=null?y:300,r=n.now||(()=>Date.now()),o=n.setTimer||((d,w)=>setTimeout(d,w)),s=n.clearTimer||(d=>clearTimeout(d)),a=!1,l=!1,p=-1/0,g=null,f=!1;function c(){if(f||a){l=!0;return}let d=t-(r()-p);if(d>0){u(d);return}a=!0,l=!1,p=r();let w=!1;e(()=>{w||(w=!0,a=!1,l&&!f&&c())})}function u(d){f||g!==null||(g=o(()=>{g=null,c()},Math.max(0,d)))}return{signaler(){f||c()},maintenant(){f||(p=-1/0,c())},arreter(){f=!0,g!==null&&(s(g),g=null)}}}function wn(e,n,t=J,r={}){let o=r.fetch||(typeof fetch==\"function\"?fetch:void 0);if(!o)return Promise.reject(new Error(\"fetch indisponible\"));let s=r.setTimer||((u,y)=>setTimeout(u,y)),a=r.clearTimer||(u=>clearTimeout(u)),l=null;try{l=new AbortController}catch(u){}let p=l?xe(G({},n||{}),{signal:l.signal}):G({},n||{}),g=null,f=new Promise((u,y)=>{g=s(()=>{try{l==null||l.abort()}catch(d){}y(new Error(\"delai\"))},t)}),c=()=>{try{a(g)}catch(u){}};return Promise.race([o(e,p),f]).then(u=>(c(),u),u=>{throw c(),u})}function xn(e){let n=e.now||(()=>Date.now()),t=Math.max(1,e.rafale),r=t,o=n();return{prendre(){let s=n();return r=Math.min(t,r+(s-o)*e.parHeure/36e5),o=s,r<1?!1:(r-=1,!0)},restants:()=>r}}function Tn(e={}){var g;let n=(g=e.minMs)!=null?g:0,t=e.now||(()=>Date.now()),r=e.setTimer||((f,c)=>setTimeout(f,c)),o=new Map,s=!1,a=-1/0,l=!1;function p(){if(s||o.size===0)return;let f=n-(t()-a);if(f>0){if(l)return;l=!0,r(()=>{l=!1,p()},f);return}let c=o.keys().next().value,u=o.get(c);o.delete(c),s=!0,a=t(),Promise.resolve().then(u.tache).then(y=>u.regler(y),()=>u.regler(null)).then(()=>{s=!1,p()})}return{poser(f,c){return new Promise(u=>{let y=o.get(f);o.set(f,{tache:c,regler:d=>{y&&y.regler(d),u(d)}}),p()})},enAttente:()=>o.size}}function _n(e,n=te){try{if(e){let t=e.getItem(x.presenceId);if(t)return t;let r=`p-${n()}`;return e.setItem(x.presenceId,r),r}}catch(t){}return`p-${n()}`}var H={};M(H,{messageClassName:()=>Sn,renderActions:()=>$e,renderAttachment:()=>Oe,renderMessage:()=>En,renderReactions:()=>Le});function Le(e,n){let t=e&&e.reactions||{},r=re(n),o=\"\";for(let s in t){let a=t[s]||[];if(!a.length)continue;let l=a.indexOf(r)>=0;o+=`<button class=\"re-chip${l?\" on\":\"\"}\" data-e=\"${h(s)}\">${h(s)} ${a.length}</button>`}return o}function Oe(e){if(!e||!e.url)return\"\";let n=h(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=h(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 $e(e,n){let t=T(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||oe(n.me))&&(r+='<button class=cm-del-btn title=\"Supprimer\">\\u{1F5D1}</button>'),r}function En(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>${h(e.reply_name||\"\")}</b> ${h(e.reply_text||\"\")}</div>`:\"\",r=e.edited?\" <span class=cm-ed>(modifi\\xE9)</span>\":\"\",o=e.body?`<div class=txt>${ne(e.body)}${r}</div>`:\"\",s=e.is_presenter?\" <span class=tag>pr\\xE9sentateur</span>\":\"\";return`<span class=a>${ee(e.author_avatar,e.author_name)}</span><span class=b><div class=who><b>${h(e.author_name||\"Invit\\xE9\")}</b>${s}</div>`+t+o+Oe(e.attachment)+`<div class=cm-re>${Le(e,n.me)}</div></span><span class=cm-act>${$e(e,n)}</span>`}function Sn(e,n,t){return\"cm\"+(T(e,n)?\" me\":\"\")+(e.deleted?\" isdel\":\"\")+(t?\" mentioned\":\"\")}var U={};M(U,{DEFAULT_CENTER:()=>ae,DEFAULT_ZOOM:()=>ue,MAP_TYPES:()=>P,cycleMapType:()=>kn,initialMapContent:()=>Pn,mapTypeLabel:()=>Rn,sanitizeContent:()=>le});var P=[\"roadmap\",\"satellite\",\"hybrid\"],ae=[46.6,2.5],ue=6;function _(e){let n=Number(e);return Number.isFinite(n)?n:null}function se(e){if(!Array.isArray(e))return null;let n=_(e[0]),t=_(e[1]);return n!=null&&t!=null?[n,t]:null}var ie=(e,n,t)=>Math.max(n,Math.min(t,e));function le(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=se(n.position);if(!t)return null;let r=n.pov&&typeof n.pov==\"object\"?n.pov:null,o=r?{heading:_(r.heading)||0,pitch:ie(_(r.pitch)||0,-90,90)}:{heading:0,pitch:0};return{kind:\"streetview\",position:t,pov:o,zoom:ie(_(n.zoom)||1,0,5)}}return n.kind!==\"map\"?null:{kind:\"map\",center:se(n.center)||[...ae],zoom:ie(Math.trunc(_(n.zoom)||ue),1,21),marker:se(n.marker),mapType:P.includes(n.mapType)?n.mapType:null,label:String(n.label||\"\").slice(0,160)||null}}function kn(e){let n=P.indexOf(e);return P[(n+1)%P.length]}function Rn(e){return e===\"roadmap\"?\"\\u{1F6F0} Satellite\":e===\"satellite\"?\"\\u{1F5FA} Hybride\":\"\\u{1F5FA} Plan\"}function Pn(){return{kind:\"map\",center:[...ae],zoom:ue,marker:null,mapType:null,label:null}}var D={};M(D,{CURRENT_PAGE_MARGIN:()=>On,DEFAULT_ASPECT:()=>Cn,MAX_ZOOM:()=>An,MIN_PAGE_WIDTH:()=>Nn,MIN_ZOOM:()=>In,PRERENDER_MARGIN:()=>Ln,arrowState:()=>Un,averageColor:()=>Vn,clampPage:()=>He,clampZoom:()=>$n,fitWidth:()=>Wn,isImageDocument:()=>zn,progressPercent:()=>Hn});var In=.5,An=3,Cn=1.35,Nn=280,Ln=\"500px 0px\",On=\"-48% 0px -48% 0px\";function $n(e){let n=Math.round((Number(e)||0)*10)/10;return Math.max(.5,Math.min(3,n))}function He(e,n){return Math.max(1,Math.min(n||1,Number(e)||1))}function Hn(e,n){return n?Math.round(He(e,n)/n*100):0}function Un(e,n){return{prevDisabled:e<=1,nextDisabled:!!n&&e>=n}}var Dn=28;function Wn(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)-Dn-(e.overlap||0)-(e.reserve||0))/r;return s>120?Math.min(t,s):t}function Vn(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 zn(e,n){return/[.](png|jpe?g|webp|gif|avif)($|[?])/i.test(String(e||n||\"\"))}var W={};M(W,{presentationTransition:()=>Fn,switchDocUrl:()=>Bn});function Fn(e,n={}){if(!e)return[{kind:\"nothing\"}];if(e.active===!1)return[{kind:\"ended\"}];let t=le(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 Bn(e,n){let t=encodeURIComponent(n||String(Date.now()));return`${e}${e.includes(\"?\")?\"&\":\"?\"}v=${t}`}return je(jn);})();", SOURCE_HASH: "95b81425c70778a0" };
@@ -568,7 +568,11 @@ var Live=(function(){
568
568
  ch.on('broadcast',{event:'map'},function(){if(_onMap)_onMap();});
569
569
  ch.subscribe(function(st){if(st==='SUBSCRIBED'){REFPRETE.then(function(){ME.ref=MOIREF;ch.track({name:me.name,ref:MOIREF,avatar:me.avatar,role:me.role,member:!!me.member,uid:presId()});sendAttend();});}});
570
570
  _tyIv=setInterval(renderTyping,1500);
571
- _atIv=setInterval(sendAttend,25000);
571
+ // ⚠️ Battement JITTERÉ (±15 %), pas un setInterval fixe (P1 performance) : 250 onglets
572
+ // ouverts à la même seconde produiraient une rafale synchronisée toutes les 25 s. Un
573
+ // setTimeout qui se replanifie avec un délai varié étale la charge. clearInterval(_atIv)
574
+ // efface aussi un id de setTimeout (même espace de timers dans le navigateur).
575
+ (function planAttend(){ _atIv=setTimeout(function(){ try{sendAttend();}catch(e){} planAttend(); }, 25000*(0.85+Math.random()*0.30)); })();
572
576
  // Filet de sécurité : au déchargement de la page/iframe (fermeture, reload, switch), on retire la présence
573
577
  // → évite les fantômes (« je me vois deux fois » au retour). Une seule fois.
574
578
  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){}});}
@@ -582,8 +582,12 @@ async function addMessage(slug, { name, email, avatar, isPresenter, isMember, bo
582
582
  * garde qui dépend de ce qu'on n'a pas demandé cède au premier champ qu'on demande.
583
583
  */
584
584
  async function listMessages(slug) {
585
- const rows = await PLAYER.db.request(`doc_presentation_messages?slug=eq.${enc(String(slug || ""))}&select=id,author_name,author_avatar,author_hash,is_presenter,is_member,body,attachment,reactions,reply_to,reply_name,reply_text,deleted,edited,created_at&order=created_at.asc&limit=300`);
586
- return Array.isArray(rows) ? rows.map(messagePublic) : [];
585
+ // ⚠️ LES 300 PLUS RÉCENTS, PAS LES 300 PLUS ANCIENS (P1 performance). Avec `asc&limit=300`, au
586
+ // 301e message les nouveaux ne revenaient JAMAIS à ceux qui relisaient — seul l'auteur le voyait,
587
+ // par la réponse du POST. On trie `desc` (id départage created_at à la milliseconde) puis on
588
+ // REND en ordre chronologique, comme l'affichage l'attend.
589
+ const rows = await PLAYER.db.request(`doc_presentation_messages?slug=eq.${enc(String(slug || ""))}&select=id,author_name,author_avatar,author_hash,is_presenter,is_member,body,attachment,reactions,reply_to,reply_name,reply_text,deleted,edited,created_at&order=created_at.desc,id.desc&limit=300`);
590
+ return Array.isArray(rows) ? rows.slice().reverse().map(messagePublic) : [];
587
591
  }
588
592
 
589
593
  // Éditer son message (jeton d'auteur requis). Ne touche pas aux messages supprimés.
@@ -727,9 +731,12 @@ async function toggleReaction(slug, msgId, emoji, reactor, etat) {
727
731
  // Heartbeat d'un participant : upsert de sa ligne d'assistance. On accumule le temps de présence (intervalles
728
732
  // < 60 s → un aller-retour ne gonfle pas total_ms) et l'ensemble des pages vues (page courante de la présentation).
729
733
  const ATTEND_MAX_GAP_MS = 60 * 1000;
730
- async function recordAttendance(slug, { key, name, email, avatar, isMember, isPresenter }) {
734
+ async function recordAttendance(slug, presDejaCharge, { key, name, email, avatar, isMember, isPresenter }) {
731
735
  if (!slug || !key) return { ok: false, status: 400 };
732
- const pres = await getPresentation(slug);
736
+ // ⚠️ La route vient DÉJÀ de charger la présentation (contrôle présentateur) : on la reçoit plutôt
737
+ // que d'en refaire une lecture par battement (P1 performance — un battement = un aller-retour DB
738
+ // de moins). Repli sur une lecture si l'appelant ne la fournit pas.
739
+ const pres = presDejaCharge || await getPresentation(slug);
733
740
  if (!pres) return { ok: false, status: 404 };
734
741
  // ⚠️ La lecture est DÉJÀ faite ici : on s'en sert plutôt que d'en refaire une. Un battement de
735
742
  // présence sur une session close n'a rien à mettre à jour — et il en arrive à chaque onglet
@@ -4,6 +4,10 @@
4
4
  const { adresseAppelant, lcMembre, cleAnonyme, profilDuJeton } = require("./appelant");
5
5
 
6
6
  const { createPresentation, getPresentation, setPage, endPresentation, addMessage, toggleReaction, editMessage, deleteMessage, setChatLock, createUploadUrl, reclaimPresentation, touchPresentation, listActivePresentations, handoverPresentation, endPresentationByOwner, recordAttendance, presentationStats, listPresentationsForDoc, switchPresentationDoc, setPresentationContent } = require("./presentations");
7
+ // Cadence de présence et cible de mutualisation par IP (P1 performance). L'intervalle DOIT
8
+ // refléter celui du navigateur (gabarit-live.js) ; la cible est un vrai événement en salle.
9
+ const ATTENDANCE_INTERVAL_MS = 25_000;
10
+ const ATTENDEES_PER_EGRESS = 250;
7
11
  let PLAYER = null;
8
12
  const init = (ctx) => { PLAYER = ctx; };
9
13
  // Même délégation que dans handler : la règle vit chez l'hôte.
@@ -55,7 +59,14 @@ async function traiter(req, res, body, _slug) {
55
59
  const jp = (status, obj) => { res.statusCode = status; res.setHeader("Content-Type", "application/json"); res.end(JSON.stringify(obj)); };
56
60
  try {
57
61
  const ip = adresseAppelant(req) || "anon";
58
- const allowed = await PLAYER.limits.allow(`patt:${ip}`, 1000, 3600);
62
+ // ⚠️ QUOTA DÉRIVÉ DE LA CADENCE, PAS 1000 EN DUR (P1 performance). Un participant émet un
63
+ // battement toutes les ATTENDANCE_INTERVAL_MS ; le quota vise ATTENDEES_PER_EGRESS
64
+ // participants derrière une même IP (bureau, Wi-Fi d'événement partagent une sortie).
65
+ // 1000/h ne couvrait que ~6 participants — le 7e prenait des 429. Marge ×1,3 pour les
66
+ // battements immédiats (arrivée, changement de page, pagehide).
67
+ const battementsParH = Math.ceil(3_600_000 / ATTENDANCE_INTERVAL_MS);
68
+ const quotaPresence = Math.ceil(battementsParH * ATTENDEES_PER_EGRESS * 1.3);
69
+ const allowed = await PLAYER.limits.allow(`patt:${ip}`, quotaPresence, 3600);
59
70
  if (!allowed) return jp(429, { ok: false, error: "rate" });
60
71
  // ⚠️ CES DEUX BOOLÉENS ÉTAIENT CEUX DE L'APPELANT. `isMember` sépare la population interne
61
72
  // de celle des prospects — c'est la promesse même du produit — et `isPresenter` accorde le
@@ -83,7 +94,7 @@ async function traiter(req, res, body, _slug) {
83
94
  // On dérive donc la clé de ce qui est prouvé, et jamais de ce qui est affirmé. Un anonyme,
84
95
  // lui, ne peut rien prouver : sa clé reste la sienne, mais enfermée dans un espace de noms
85
96
  // dont elle ne peut pas sortir — elle ne pourra jamais ressembler à l'e-mail d'un membre.
86
- const r = await recordAttendance(String(body.slug || ""), {
97
+ const r = await recordAttendance(String(body.slug || ""), pres, {
87
98
  // ⚠️ MINUSCULÉE, ET CE DÉTAIL EST UNE LIGNE DE PRÉSENCE. La clé cliente que 0.1.42 a
88
99
  // remplacée l'était (`me.email.toLowerCase()`) ; la clé dérivée ne l'était pas, et la
89
100
  // ligne se retrouve par `attendee_key=eq.` — une correspondance EXACTE. Un hôte dont
@@ -443,7 +443,16 @@ async function traiter(req, res, body, slug) {
443
443
  return;
444
444
  }
445
445
  const share = await getShareBySlug(body.slug || slug);
446
- if (share && !share.is_test) { // répétition générale : la lecture de test ne compte pas dans les stats
446
+ // ⚠️ PLAFOND ANTI-INONDATION PAR IP (P1 performance). Le chemin analytique externe écrivait
447
+ // SANS quota : un slug public permettait des écritures illimitées (base gonflée, stats
448
+ // faussées). Même quota dérivé de la cadence que l'interne (SESSION_QUOTA_PER_HOUR par IP).
449
+ // Dépassé : on n'écrit pas — mais on répond 200, car une mesure ne doit pas casser une lecture.
450
+ const ipTrack = ip0 || "anon";
451
+ const sousQuota = await PLAYER.limits.allow(`sess:${ipTrack}`, SESSION_QUOTA_PER_HOUR, 3600);
452
+ if (share && !share.is_test && !sousQuota) {
453
+ try { if (await PLAYER.limits.allow("sess:quota-avert", 1, 3600)) PLAYER.errors.capture(new Error(`télémétrie externe refusée : quota horaire atteint (${SESSION_QUOTA_PER_HOUR}/h par adresse)`), { route: "track" }); } catch { /* jamais bloquant */ }
454
+ }
455
+ if (share && !share.is_test && sousQuota) { // répétition générale : la lecture de test ne compte pas dans les stats
447
456
  try {
448
457
  // 'session' = résumé riche (temps par page, appareil) → upsert ; open/page/heartbeat → journal léger (funnel/overview).
449
458
  if (body.event === "session") await upsertSession(share, { sessionId: body.sessionId, numPages: body.numPages, maxPage: body.maxPage, totalSeconds: body.totalSeconds, pagesTime: body.pagesTime }, { ip: ip0, ua: ua0 });
@@ -101,7 +101,7 @@ function initialMapContent() {
101
101
  }
102
102
 
103
103
  // src/cadence.ts
104
- var SESSION_INTERVAL_MS = 12e3;
104
+ var SESSION_INTERVAL_MS = 3e4;
105
105
  var SESSION_WRITES_PER_HOUR = Math.ceil(36e5 / SESSION_INTERVAL_MS);
106
106
  var READERS_PER_EGRESS = 25;
107
107
  var SESSION_QUOTA_PER_HOUR = SESSION_WRITES_PER_HOUR * READERS_PER_EGRESS;
package/server/shares.js CHANGED
@@ -277,14 +277,14 @@ function parseUa(ua) {
277
277
  // Upsert d'une session de consultation (résumé envoyé périodiquement par la visionneuse). Stocke le temps
278
278
  // PAR page (cumulatif côté client → on remplace), totaux, appareil. Conserve started_at (insert) via merge.
279
279
  async function upsertSession(share, p, { ip, ua }) {
280
- const num = (v) => (Number.isFinite(+v) ? Math.trunc(+v) : null);
281
280
  const sessionId = String(p.sessionId || "").slice(0, 64);
282
281
  if (!sessionId) return;
283
- const pagesTime = (p.pagesTime && typeof p.pagesTime === "object") ? p.pagesTime : {};
284
282
  const { device, os, browser } = parseUa(ua);
285
283
  const row = {
286
284
  session_id: sessionId, slug: share.slug, doc_id: share.doc_id, recipient_email: share.recipient_email,
287
- num_pages: num(p.numPages), max_page: num(p.maxPage), total_seconds: num(p.totalSeconds) || 0, pages_time: pagesTime,
285
+ // Bornées comme la session INTERNE : plafond d'entrées, clés/valeurs numériques, totaux capés.
286
+ num_pages: bornerNombre(p.numPages, BORNES.pages), max_page: bornerNombre(p.maxPage, BORNES.pages),
287
+ total_seconds: bornerNombre(p.totalSeconds, BORNES.secondes) || 0, pages_time: bornerPagesTime(p.pagesTime),
288
288
  ua: String(ua || "").slice(0, 300), ip: String(ip || "").slice(0, 60), device, os, browser, last_at: new Date().toISOString(),
289
289
  };
290
290
  // started_at non touché par l'upsert (default à l'insert ; merge ne l'écrase pas car absent du body).
@@ -370,6 +370,21 @@ async function setShareAuth(slug, requireAuth) {
370
370
  // que l'hôte se porte garant de l'identité (jeton signé) : c'est le second volet, et il est
371
371
  // signalé au point d'entrée plutôt qu'ici.
372
372
  const BORNES = { pages: 10_000, secondes: 24 * 3600, entreesPagesTime: 2_000 };
373
+ // ⚠️ BORNER CE QUI VIENT DU DEHORS — les DEUX chemins de session (interne ET externe). Un objet
374
+ // libre sans plafond laisse un seul appel écrire un JSON de la taille qu'il veut (P1 performance).
375
+ const bornerNombre = (v, max) => { const n = Number.isFinite(+v) ? Math.trunc(+v) : null; return n == null ? null : Math.max(0, Math.min(n, max)); };
376
+ function bornerPagesTime(brut) {
377
+ const src = (brut && typeof brut === "object" && !Array.isArray(brut)) ? brut : {};
378
+ const out = {};
379
+ let n = 0;
380
+ for (const k in src) {
381
+ if (++n > BORNES.entreesPagesTime) break;
382
+ const page = Number.isFinite(+k) ? Math.trunc(+k) : null;
383
+ if (page == null || page < 0 || page > BORNES.pages) continue;
384
+ out[String(page)] = bornerNombre(src[k], BORNES.secondes) || 0;
385
+ }
386
+ return out;
387
+ }
373
388
 
374
389
  // ⚠️ `ip` N'EST PLUS LU, ET LA SIGNATURE LE DIT. L'appelant continue de le passer — il ne sait pas
375
390
  // ce que chaque table conserve, et ce n'est pas à lui de le savoir. Mais le garder en paramètre