discovery-media-player 0.1.60 → 0.1.62

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.
@@ -65,6 +65,25 @@ it. `verdict` is then one of:
65
65
  | `partiel` | some expectations checked, none of them missing |
66
66
  | `complet` | all checked, all present |
67
67
  | `incomplet` | at least one is missing — `manquant` names the file and the sleeping feature |
68
+
69
+ Each `manquant` entry has **exactly this shape** — pin your parser to it, not to what a schema
70
+ probe "should" return:
71
+
72
+ ```json
73
+ { "migration": "supabase/migrations/0006-reactions-ordonnees.sql",
74
+ "fonction": "empêcher deux réactions simultanées de s'écraser" }
75
+ ```
76
+
77
+ ⚠️ This shape went **undocumented** for two releases, and the second host typed it from memory as
78
+ `{table, colonne, migration}` — their monitoring filter then discarded every real entry and would
79
+ have shown an empty table on a database that was actually missing something. **No mutation on
80
+ their side could catch it: their test built the card the same wrong way, so the bench validated
81
+ the assumption instead of the behaviour.** The only cure was reading data they had not fabricated.
82
+ If you consume this card, test your parser against the JSON above, not against a fixture you wrote.
83
+
84
+ ⚠️ **A card without a `schema` field is an alert, not a success**: it signals an instance older
85
+ than 0.1.58 — a version that cannot answer the question. (Rule contributed by the second host, for
86
+ exactly the monitoring case where "no data" would otherwise read as "all clear".)
68
87
  | `indetermine` | the database did not answer; this measurement did not happen |
69
88
 
70
89
  ⚠️ **`incomplet` wins over `partiel`**: a missing column is a positive fact and settles the verdict
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "discovery-media-player",
3
- "version": "0.1.60",
3
+ "version": "0.1.62",
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,He=Object.defineProperties,De=Object.getOwnPropertyDescriptor,We=Object.getOwnPropertyDescriptors,Be=Object.getOwnPropertyNames,he=Object.getOwnPropertySymbols;var we=Object.prototype.hasOwnProperty,Ve=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={}))we.call(n,t)&&ve(e,t,n[t]);if(he)for(var t of he(n))Ve.call(n,t)&&ve(e,t,n[t]);return e},Me=(e,n)=>He(e,We(n));var v=(e,n)=>{for(var t in n)C(e,t,{get:n[t],enumerable:!0})},ze=(e,n,t,r)=>{if(n&&typeof n==\"object\"||typeof n==\"function\")for(let o of Be(n))!we.call(e,o)&&o!==t&&C(e,o,{get:()=>n[o],enumerable:!(r=De(n,o))||r.enumerable});return e};var Fe=e=>ze(C({},\"__esModule\",{value:!0}),e);var Gn={};v(Gn,{bridge:()=>L,cadence:()=>N,chat:()=>$,live:()=>U,presentation:()=>H,presentationState:()=>W,tracking:()=>O,viewer:()=>D});var L={};v(L,{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 s=e.slug;return typeof s!=\"string\"||!je.test(s)?null:{type:o,slug:s}}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=s=>{if(!Xe(t,s.source))return;let a=Te(s.data);a&&e(a)};return r.addEventListener(\"message\",o),()=>r.removeEventListener(\"message\",o)}function en(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 u=_e(a.data);u&&e(u)};return t.addEventListener(\"message\",o),()=>t.removeEventListener(\"message\",o)}var O={};v(O,{createTracker:()=>ln});var N={};v(N,{PRESENTER_ACTIONS_PER_HOUR:()=>X,PRESENT_CACHE_MS:()=>rn,PRESENT_QUOTA_MARGIN:()=>Ie,PRESENT_QUOTA_PER_HOUR:()=>on,PRESENT_READS_PER_HOUR:()=>Re,PRESENT_READ_BURST:()=>Pe,PRESENT_READ_COALESCE_MS:()=>Ae,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,Ae=400,rn=Ae,Ie=4,on=(Re+Pe)*q*Ie;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\",s=(ge=e.idleMs)!=null?ge:Q,a=(be=e.sessionEveryMs)!=null?be:12e3,u=e.slug||\"\",d=e.internal||null,m=e.sessionId||an(),p=e.send||un(o,n),c={},l=0,g=0,f=0,w=0,h=null,E=r(),S=!1,B=!1,A=[],$e=()=>!!u||!!d,ce=i=>{if($e())try{p(i)}catch(b){}},V=()=>!!t&&t.visibilityState===\"visible\"&&!S,de=i=>{let b=r()-i,x=Math.max(0,E-i)+s;return Math.max(0,Math.min(b,x))},z=()=>{h!=null&&l>0&&(c[l]=(c[l]||0)+de(h)/1e3),h=V()?r():null},pe=()=>{z(),h=null},F=()=>{h==null&&V()&&(h=r())},j=()=>{let i=0;for(let b in c)i+=c[b];return h!=null&&(i+=de(h)/1e3),Math.round(i)},fe=()=>{let i={};for(let b in c)i[b]=Math.round(c[b]);return i},k=()=>{E=r(),S&&(S=!1,F())},me=(i,b)=>{d||ce({slug:u,event:i,page:b||g,maxPage:f,seconds:j(),sessionId:m})},I=()=>{z();let i={event:\"session\",sessionId:m,numPages:w,maxPage:f,totalSeconds:j(),pagesTime:fe()};d?(i.internal=!0,i.docId=d.docId,i.email=d.email,i.name=d.name,d.it&&(i.it=d.it)):i.slug=u,ce(i)},R=(i,b,x,ye)=>{!i||typeof i.addEventListener!=\"function\"||(i.addEventListener(b,x,ye),A.push(()=>{i.removeEventListener(b,x,ye)}))};return{sessionId:m,totalSeconds:j,maxPage:()=>f,pageTimes:fe,start(){if(B)return;B=!0,me(\"open\",1),l=1,E=r(),h=V()?r():null,R(t,\"visibilitychange\",()=>{t.visibilityState===\"hidden\"?(pe(),I()):(k(),F())}),R(n,\"focus\",()=>{k(),F()});for(let x of sn)R(n,x,k,{passive:!0});R(e.scrollElement,\"scroll\",k,{passive:!0}),R(n,\"beforeunload\",I);let i=n.setInterval(()=>{!S&&r()-E>s&&(S=!0,pe())},5e3),b=n.setInterval(I,a);A.push(()=>{n.clearInterval(i),n.clearInterval(b)})},setPage(i){!i||i===g||(E=r(),z(),l=i,g=i,i>f&&(f=i,me(\"page\",i)))},setPageCount(i){w=i||0},noteActivity:k,flush:I,stop(){for(;A.length;){let i=A.pop();try{i==null||i()}catch(b){}}B=!1}}}var U={};v(U,{STORAGE_KEYS:()=>M,attendeeKey:()=>fn,authorToken:()=>mn,avatarHtml:()=>ee,canModerate:()=>oe,createBudget:()=>Mn,createFileEcritures:()=>xn,createScheduler:()=>vn,escapeHtml:()=>y,fetchBorne:()=>wn,flattenPresence:()=>cn,formatMessageBody:()=>ne,initials:()=>Ce,isMentioned:()=>bn,isMine:()=>T,presenceId:()=>Tn,reactorId:()=>re,referenceAuteur:()=>gn,shouldNotify:()=>yn,unreadLabel:()=>hn});var M={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(M.attendeeKey);if(r)return r;let o=`anon-${t()}`;return e.setItem(M.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,M.authorToken);if(t)return t;let r=n();return pn(e,M.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 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 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,w)=>setTimeout(f,w)),s=n.clearTimer||(f=>clearTimeout(f)),a=!1,u=!1,d=-1/0,m=null,p=!1;function c(){if(p||a){u=!0;return}let f=t-(r()-d);if(f>0){l(f);return}a=!0,u=!1,d=r();let w=!1;e(()=>{w||(w=!0,a=!1,u&&!p&&c())})}function l(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&&(s(m),m=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||((l,g)=>setTimeout(l,g)),a=r.clearTimer||(l=>clearTimeout(l)),u=null;try{u=new AbortController}catch(l){}let d=u?Me(G({},n||{}),{signal:u.signal}):G({},n||{}),m=null,p=new Promise((l,g)=>{m=s(()=>{try{u==null||u.abort()}catch(f){}g(new Error(\"delai\"))},t)}),c=()=>{try{a(m)}catch(l){}};return Promise.race([o(e,d),p]).then(l=>(c(),l),l=>{throw c(),l})}function Mn(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 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,s=!1,a=-1/0,u=!1;function d(){if(s||o.size===0)return;let p=n-(t()-a);if(p>0){if(u)return;u=!0,r(()=>{u=!1,d()},p);return}let c=o.keys().next().value,l=o.get(c);o.delete(c),s=!0,a=t(),Promise.resolve().then(l.tache).then(g=>l.regler(g),()=>l.regler(null)).then(()=>{s=!1,d()})}return{poser(p,c){return new Promise(l=>{let g=o.get(p);o.set(p,{tache:c,regler:f=>{g&&g.regler(f),l(f)}}),d()})},enAttente:()=>o.size}}function Tn(e,n=te){try{if(e){let t=e.getItem(M.presenceId);if(t)return t;let r=`p-${n()}`;return e.setItem(M.presenceId,r),r}}catch(t){}return`p-${n()}`}var $={};v($,{messageClassName:()=>En,renderActions:()=>Oe,renderAttachment:()=>Ne,renderMessage:()=>_n,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 u=a.indexOf(r)>=0;o+=`<button class=\"re-chip${u?\" on\":\"\"}\" data-e=\"${y(s)}\">${y(s)} ${a.length}</button>`}return o}function Ne(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>`:\"\",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>${y(e.author_name||\"Invit\\xE9\")}</b>${s}</div>`+t+o+Ne(e.attachment)+`<div class=cm-re>${Le(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 H={};v(H,{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:()=>Nn,DEFAULT_ASPECT:()=>In,MAX_ZOOM:()=>An,MIN_PAGE_WIDTH:()=>Cn,MIN_ZOOM:()=>Pn,PRERENDER_MARGIN:()=>Ln,arrowState:()=>$n,averageColor:()=>Wn,clampPage:()=>Ue,clampZoom:()=>On,fitWidth:()=>Dn,isImageDocument:()=>Bn,progressPercent:()=>Un,workerBlobUrl:()=>Vn});var Pn=.5,An=3,In=1.35,Cn=280,Ln=\"500px 0px\",Nn=\"-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 Ue(e,n){return Math.max(1,Math.min(n||1,Number(e)||1))}function Un(e,n){return n?Math.round(Ue(e,n)/n*100):0}function $n(e,n){return{prevDisabled:e<=1,nextDisabled:!!n&&e>=n}}var Hn=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,s=((e.containerHeight||600)-Hn-(e.overlap||0)-(e.reserve||0))/r;return s>120?Math.min(t,s):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 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 Bn(e,n){return/[.](png|jpe?g|webp|gif|avif)($|[?])/i.test(String(e||n||\"\"))}async function Vn(e,n,t={}){let r=t.fetch||(typeof fetch==\"function\"?(a=>fetch(a)):null),o=t.subtle!==void 0?t.subtle:typeof globalThis!=\"undefined\"&&globalThis.crypto?globalThis.crypto.subtle:null,s=t.creerUrl||(a=>URL.createObjectURL(a));if(!r||!o||!n)return null;try{let a=await r(e);if(!a.ok)return null;let u=await a.arrayBuffer(),d=await o.digest(\"SHA-384\",u);return`sha384-${zn(new Uint8Array(d))}`!==n?null:s(new Blob([u],{type:\"application/javascript\"}))}catch(a){return null}}function zn(e){let n=\"\";for(let t of e)n+=String.fromCharCode(t);return btoa(n)}var W={};v(W,{presentationTransition:()=>Fn,switchDocUrl:()=>jn});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 jn(e,n){let t=encodeURIComponent(n||String(Date.now()));return`${e}${e.includes(\"?\")?\"&\":\"?\"}v=${t}`}return Fe(Gn);})();", SOURCE_HASH: "0e1b2ed50563dde4" };
7
+ module.exports = { PLAYER_BROWSER_JS: "\"use strict\";var Player=(()=>{var C=Object.defineProperty,He=Object.defineProperties,De=Object.getOwnPropertyDescriptor,We=Object.getOwnPropertyDescriptors,Be=Object.getOwnPropertyNames,he=Object.getOwnPropertySymbols;var we=Object.prototype.hasOwnProperty,Ve=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={}))we.call(n,t)&&ve(e,t,n[t]);if(he)for(var t of he(n))Ve.call(n,t)&&ve(e,t,n[t]);return e},Me=(e,n)=>He(e,We(n));var v=(e,n)=>{for(var t in n)C(e,t,{get:n[t],enumerable:!0})},ze=(e,n,t,r)=>{if(n&&typeof n==\"object\"||typeof n==\"function\")for(let o of Be(n))!we.call(e,o)&&o!==t&&C(e,o,{get:()=>n[o],enumerable:!(r=De(n,o))||r.enumerable});return e};var Fe=e=>ze(C({},\"__esModule\",{value:!0}),e);var Gn={};v(Gn,{bridge:()=>L,cadence:()=>N,chat:()=>$,live:()=>U,presentation:()=>H,presentationState:()=>W,tracking:()=>O,viewer:()=>D});var L={};v(L,{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 s=e.slug;return typeof s!=\"string\"||!je.test(s)?null:{type:o,slug:s}}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=s=>{if(!Xe(t,s.source))return;let a=Te(s.data);a&&e(a)};return r.addEventListener(\"message\",o),()=>r.removeEventListener(\"message\",o)}function en(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 u=_e(a.data);u&&e(u)};return t.addEventListener(\"message\",o),()=>t.removeEventListener(\"message\",o)}var O={};v(O,{createTracker:()=>ln});var N={};v(N,{PRESENTER_ACTIONS_PER_HOUR:()=>X,PRESENT_CACHE_MS:()=>rn,PRESENT_QUOTA_MARGIN:()=>Ie,PRESENT_QUOTA_PER_HOUR:()=>on,PRESENT_READS_PER_HOUR:()=>Re,PRESENT_READ_BURST:()=>Pe,PRESENT_READ_COALESCE_MS:()=>Ae,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,Ae=400,rn=Ae,Ie=4,on=(Re+Pe)*q*Ie;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\",s=(ge=e.idleMs)!=null?ge:Q,a=(be=e.sessionEveryMs)!=null?be:12e3,u=e.slug||\"\",d=e.internal||null,m=e.sessionId||an(),p=e.send||un(o,n),c={},l=0,g=0,f=0,w=0,h=null,E=r(),S=!1,B=!1,A=[],$e=()=>!!u||!!d,ce=i=>{if($e())try{p(i)}catch(b){}},V=()=>!!t&&t.visibilityState===\"visible\"&&!S,de=i=>{let b=r()-i,x=Math.max(0,E-i)+s;return Math.max(0,Math.min(b,x))},z=()=>{h!=null&&l>0&&(c[l]=(c[l]||0)+de(h)/1e3),h=V()?r():null},pe=()=>{z(),h=null},F=()=>{h==null&&V()&&(h=r())},j=()=>{let i=0;for(let b in c)i+=c[b];return h!=null&&(i+=de(h)/1e3),Math.round(i)},fe=()=>{let i={};for(let b in c)i[b]=Math.round(c[b]);return i},k=()=>{E=r(),S&&(S=!1,F())},me=(i,b)=>{d||ce({slug:u,event:i,page:b||g,maxPage:f,seconds:j(),sessionId:m})},I=()=>{z();let i={event:\"session\",sessionId:m,numPages:w,maxPage:f,totalSeconds:j(),pagesTime:fe()};d?(i.internal=!0,i.docId=d.docId,i.email=d.email,i.name=d.name,d.it&&(i.it=d.it)):i.slug=u,ce(i)},R=(i,b,x,ye)=>{!i||typeof i.addEventListener!=\"function\"||(i.addEventListener(b,x,ye),A.push(()=>{i.removeEventListener(b,x,ye)}))};return{sessionId:m,totalSeconds:j,maxPage:()=>f,pageTimes:fe,start(){if(B)return;B=!0,me(\"open\",1),l=1,E=r(),h=V()?r():null,R(t,\"visibilitychange\",()=>{t.visibilityState===\"hidden\"?(pe(),I()):(k(),F())}),R(n,\"focus\",()=>{k(),F()});for(let x of sn)R(n,x,k,{passive:!0});R(e.scrollElement,\"scroll\",k,{passive:!0}),R(n,\"beforeunload\",I);let i=n.setInterval(()=>{!S&&r()-E>s&&(S=!0,pe())},5e3),b=n.setInterval(I,a);A.push(()=>{n.clearInterval(i),n.clearInterval(b)})},setPage(i){!i||i===g||(E=r(),z(),l=i,g=i,i>f&&(f=i,me(\"page\",i)))},setPageCount(i){w=i||0},noteActivity:k,flush:I,stop(){for(;A.length;){let i=A.pop();try{i==null||i()}catch(b){}}B=!1}}}var U={};v(U,{STORAGE_KEYS:()=>M,attendeeKey:()=>fn,authorToken:()=>mn,avatarHtml:()=>ee,canModerate:()=>oe,createBudget:()=>Mn,createFileEcritures:()=>xn,createScheduler:()=>vn,escapeHtml:()=>y,fetchBorne:()=>wn,flattenPresence:()=>cn,formatMessageBody:()=>ne,initials:()=>Ce,isMentioned:()=>bn,isMine:()=>T,presenceId:()=>Tn,reactorId:()=>re,referenceAuteur:()=>gn,shouldNotify:()=>yn,unreadLabel:()=>hn});var M={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(M.attendeeKey);if(r)return r;let o=`anon-${t()}`;return e.setItem(M.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,M.authorToken);if(t)return t;let r=n();return pn(e,M.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 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 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,w)=>setTimeout(f,w)),s=n.clearTimer||(f=>clearTimeout(f)),a=!1,u=!1,d=-1/0,m=null,p=!1;function c(){if(p||a){u=!0;return}let f=t-(r()-d);if(f>0){l(f);return}a=!0,u=!1,d=r();let w=!1;e(()=>{w||(w=!0,a=!1,u&&!p&&c())})}function l(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&&(s(m),m=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||((l,g)=>setTimeout(l,g)),a=r.clearTimer||(l=>clearTimeout(l)),u=null;try{u=new AbortController}catch(l){}let d=u?Me(G({},n||{}),{signal:u.signal}):G({},n||{}),m=null,p=new Promise((l,g)=>{m=s(()=>{try{u==null||u.abort()}catch(f){}g(new Error(\"delai\"))},t)}),c=()=>{try{a(m)}catch(l){}};return Promise.race([o(e,d),p]).then(l=>(c(),l),l=>{throw c(),l})}function Mn(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 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,s=!1,a=-1/0,u=!1;function d(){if(s||o.size===0)return;let p=n-(t()-a);if(p>0){if(u)return;u=!0,r(()=>{u=!1,d()},p);return}let c=o.keys().next().value,l=o.get(c);o.delete(c),s=!0,a=t(),Promise.resolve().then(l.tache).then(g=>l.regler(g),()=>l.regler(null)).then(()=>{s=!1,d()})}return{poser(p,c){return new Promise(l=>{let g=o.get(p);o.set(p,{tache:c,regler:f=>{g&&g.regler(f),l(f)}}),d()})},enAttente:()=>o.size}}function Tn(e,n=te){try{if(e){let t=e.getItem(M.presenceId);if(t)return t;let r=`p-${n()}`;return e.setItem(M.presenceId,r),r}}catch(t){}return`p-${n()}`}var $={};v($,{messageClassName:()=>En,renderActions:()=>Oe,renderAttachment:()=>Ne,renderMessage:()=>_n,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 u=a.indexOf(r)>=0;o+=`<button class=\"re-chip${u?\" on\":\"\"}\" data-e=\"${y(s)}\">${y(s)} ${a.length}</button>`}return o}function Ne(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>`:\"\",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>${y(e.author_name||\"Invit\\xE9\")}</b>${s}</div>`+t+o+Ne(e.attachment)+`<div class=cm-re>${Le(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 H={};v(H,{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:()=>Nn,DEFAULT_ASPECT:()=>In,MAX_ZOOM:()=>An,MIN_PAGE_WIDTH:()=>Cn,MIN_ZOOM:()=>Pn,PRERENDER_MARGIN:()=>Ln,arrowState:()=>$n,averageColor:()=>Wn,clampPage:()=>Ue,clampZoom:()=>On,fitWidth:()=>Dn,isImageDocument:()=>Bn,progressPercent:()=>Un,workerBlobUrl:()=>Vn});var Pn=.5,An=3,In=1.35,Cn=280,Ln=\"500px 0px\",Nn=\"-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 Ue(e,n){return Math.max(1,Math.min(n||1,Number(e)||1))}function Un(e,n){return n?Math.round(Ue(e,n)/n*100):0}function $n(e,n){return{prevDisabled:e<=1,nextDisabled:!!n&&e>=n}}var Hn=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,s=((e.containerHeight||600)-Hn-(e.overlap||0)-(e.reserve||0))/r;return s>120?Math.min(t,s):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 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 Bn(e,n){return/[.](png|jpe?g|webp|gif|avif)($|[?])/i.test(String(e||n||\"\"))}async function Vn(e,n,t={}){let r=t.fetch||(typeof fetch==\"function\"?(a=>fetch(a)):null),o=t.subtle!==void 0?t.subtle:typeof globalThis!=\"undefined\"&&globalThis.crypto?globalThis.crypto.subtle:null,s=t.creerUrl||(a=>URL.createObjectURL(a));if(!r||!o||!n)return null;try{let a=await r(e);if(!a.ok)return null;let u=await a.arrayBuffer(),d=await o.digest(\"SHA-384\",u);return`sha384-${zn(new Uint8Array(d))}`!==n?null:s(new Blob([u],{type:\"application/javascript\"}))}catch(a){return null}}function zn(e){let n=\"\";for(let t of e)n+=String.fromCharCode(t);return btoa(n)}var W={};v(W,{presentationTransition:()=>Fn,switchDocUrl:()=>jn});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 jn(e,n){let t=encodeURIComponent(n||String(Date.now()));return`${e}${e.includes(\"?\")?\"&\":\"?\"}v=${t}`}return Fe(Gn);})();", SOURCE_HASH: "0ba592364fc3eb81" };
package/server/handler.js CHANGED
@@ -1430,6 +1430,28 @@ function botMarkup(share, pitch) {
1430
1430
  // formats du README ne connaît que le PDF et les images bitmap).
1431
1431
  const TYPES_EXECUTABLES = /^(image\/svg|text\/html|application\/xhtml|application\/xml|text\/xml)/i;
1432
1432
 
1433
+ /**
1434
+ * ⚠️ TOUTE DONNÉE SERVEUR QUI ENTRE DANS UN <script> PASSE PAR ICI — c'est une règle, pas une
1435
+ * commodité, et une garde d'essai la fait respecter.
1436
+ *
1437
+ * `JSON.stringify` seul ne suffit pas : le PARSEUR HTML lit la page avant JavaScript, et un
1438
+ * `</script>` DANS une chaîne JSON ferme l'élément pour lui — la suite du document devient du
1439
+ * balisage. La CSP à nonce bloque l'exécution du script injecté (il n'a pas le nonce), mais pas
1440
+ * la casse de la page : l'élément fermé trop tôt suffit à tout démonter. La protection vivait
1441
+ * éparpillée (`cfg.replace(/</g, …)` à l'interpolation, et six interpolations NUES à côté) —
1442
+ * appliquée à la sérialisation, elle ne peut plus être oubliée champ par champ.
1443
+ *
1444
+ * ⚠️ `undefined` LÈVE au lieu de devenir « undefined » dans la page : c'est une erreur de
1445
+ * programmation, pas une valeur — la convertir en douce la masquerait. U+2028/U+2029 : les
1446
+ * séparateurs Unicode, légaux en JSON et illégaux dans les littéraux JS d'anciens moteurs —
1447
+ * durcissement de compatibilité, pas une faille des navigateurs modernes.
1448
+ */
1449
+ function jsonPourScript(valeur) {
1450
+ const json = JSON.stringify(valeur);
1451
+ if (json === undefined) throw new TypeError("valeur impossible à sérialiser dans un script");
1452
+ return json.replace(/</g, "\\u003c").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
1453
+ }
1454
+
1433
1455
  // ⚠️ `X-Forwarded-For` EST UN EN-TÊTE, DONC UNE AFFIRMATION DU CLIENT.
1434
1456
  //
1435
1457
  // Onze endroits en prenaient la première valeur pour identifier l'appelant, et toutes les limites
@@ -1799,7 +1821,7 @@ function embedFrameAncestors() {
1799
1821
  function sendRefusal(res, reason, embed) {
1800
1822
  if (!embed) return sendHtml(res, 404, notFoundHtml());
1801
1823
  const nonce = crypto.randomBytes(16).toString("base64");
1802
- const html = notFoundHtml() + `<script nonce="${nonce}">try{parent.postMessage({type:"3dd-doc-embed-denied",reason:${JSON.stringify(String(reason))}},"*")}catch(e){}</script>`;
1824
+ const html = notFoundHtml() + `<script nonce="${nonce}">try{parent.postMessage({type:"3dd-doc-embed-denied",reason:${jsonPourScript(String(reason))}},"*")}catch(e){}</script>`;
1803
1825
  return sendHtml(res, 404, html, `'nonce-${nonce}'`, "", embedFrameAncestors());
1804
1826
  }
1805
1827
 
@@ -1875,8 +1897,8 @@ function softWallHtml(share, nonce, logoUrl, googleClientId) {
1875
1897
  </div>
1876
1898
  ${gcid ? `<script nonce="${nonce}" src="https://accounts.google.com/gsi/client" async></script>` : ""}
1877
1899
  <script nonce="${nonce}">
1878
- var SLUG=${JSON.stringify(share.slug || "")};
1879
- var GCID=${JSON.stringify(gcid)};
1900
+ var SLUG=${jsonPourScript(share.slug || "")};
1901
+ var GCID=${jsonPourScript(gcid)};
1880
1902
  var $=function(id){return document.getElementById(id);};
1881
1903
  function err(m){$('err').className='err';$('err').textContent=m||'';}
1882
1904
  function post(o){return fetch('/api/doc',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(o)}).then(function(r){return r.json().then(function(j){return{s:r.status,j:j};});});}
@@ -1983,7 +2005,7 @@ function viewerHtml(share, nonce, logoUrl, pitch) {
1983
2005
  // En aperçu interne, on embarque de quoi démarrer une présentation live (URL Storage brute + métadonnées).
1984
2006
  // `fileName` : c'est LUI qui dit la nature du document côté page. L'URL publique est
1985
2007
  // `/api/doc?slug=…&file=1`, sans extension — sans ce champ, une image partait dans pdf.js.
1986
- const cfg = JSON.stringify({ brand: PLAYER.branding.name, slug: preview ? "" : share.slug, fileUrl, fileName: share.file_name || "", pdfjs: PDFJS, title, preview, embed, embedded, bot: botOn, botGuided: !preview && !!share.bot_enabled && share.bot_guided !== false, botAv: (!preview && share.bot_enabled && share.bot_avatar) || "", botName: (!preview && share.bot_enabled && share.bot_name) || "", botGreet: (!preview && share.bot_enabled && share.bot_greeting) || "", botGreetDoc: (!preview && share.bot_enabled && share.bot_greeting_doc) || "", dl: share.allow_download !== false, autoPresent: !!share.auto_present, botAnim: share.bot_page_anim !== false, botVoice: !preview && !!share.bot_enabled && !!process.env.ELEVENLABS_API_KEY, vIcOn: ICONS.sound, vIcOff: ICONS.mute, kStyle: (!preview && share.bot_enabled && share.bot_karaoke) || "classic", vLayout: (!preview && share.bot_enabled && share.video_layout) || "", vClips: !preview && !!share.bot_vclips, botVAv: (!preview && share.bot_enabled && share.bot_vphoto) || "", resumeSlug: preview ? (share.resume_slug || "") : "", supaUrl: preview ? (share.supa_url || "") : "", supaKey: preview ? (share.supa_key || "") : "", internal: preview && share.internal_email ? { email: share.internal_email, name: share.presenter_name || "", docId: share.doc_id || "", it: share.internal_token || "" } : null, present: preview ? { url: share.raw_url || "", name: share.file_name || "", title: share.doc_title || "", docId: share.doc_id || "", by: share.presenter_name || "", email: share.internal_email || "", av: share.presenter_avatar || "" } : null });
2008
+ const cfg = jsonPourScript({ brand: PLAYER.branding.name, slug: preview ? "" : share.slug, fileUrl, fileName: share.file_name || "", pdfjs: PDFJS, title, preview, embed, embedded, bot: botOn, botGuided: !preview && !!share.bot_enabled && share.bot_guided !== false, botAv: (!preview && share.bot_enabled && share.bot_avatar) || "", botName: (!preview && share.bot_enabled && share.bot_name) || "", botGreet: (!preview && share.bot_enabled && share.bot_greeting) || "", botGreetDoc: (!preview && share.bot_enabled && share.bot_greeting_doc) || "", dl: share.allow_download !== false, autoPresent: !!share.auto_present, botAnim: share.bot_page_anim !== false, botVoice: !preview && !!share.bot_enabled && !!process.env.ELEVENLABS_API_KEY, vIcOn: ICONS.sound, vIcOff: ICONS.mute, kStyle: (!preview && share.bot_enabled && share.bot_karaoke) || "classic", vLayout: (!preview && share.bot_enabled && share.video_layout) || "", vClips: !preview && !!share.bot_vclips, botVAv: (!preview && share.bot_enabled && share.bot_vphoto) || "", resumeSlug: preview ? (share.resume_slug || "") : "", supaUrl: preview ? (share.supa_url || "") : "", supaKey: preview ? (share.supa_key || "") : "", internal: preview && share.internal_email ? { email: share.internal_email, name: share.presenter_name || "", docId: share.doc_id || "", it: share.internal_token || "" } : null, present: preview ? { url: share.raw_url || "", name: share.file_name || "", title: share.doc_title || "", docId: share.doc_id || "", by: share.presenter_name || "", email: share.internal_email || "", av: share.presenter_avatar || "" } : null });
1987
2009
  return `<!doctype html><html lang=fr><head><meta charset=utf-8>
1988
2010
  <meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=3,viewport-fit=cover,interactive-widget=resizes-content">
1989
2011
  <meta name=robots content="noindex,nofollow">
@@ -2102,6 +2124,8 @@ ${LEGAL_CSS}
2102
2124
  .pbar-link{border:1px solid #e4e0d9;border-radius:8px;padding:6px 9px;font:inherit;font-size:12px;color:#555;width:240px;max-width:32vw;background:#f8f6f2}
2103
2125
  .pbar-btn{border:1px solid #e4e0d9;background:#f6f4ef;color:#1c1c1c;border-radius:8px;padding:7px 11px;font:inherit;font-size:12.5px;font-weight:600;cursor:pointer;white-space:nowrap}
2104
2126
  .pbar-btn:hover{background:#eceae5}
2127
+ .pbar-err{display:none;color:#b3261e;font-size:12px;font-weight:600;max-width:260px;line-height:1.25}
2128
+ .pbar-err.on{display:inline-block}
2105
2129
  #pbarEnd{color:#c0392b}
2106
2130
  /* Barre d'outils responsive : sous 860px, zoom/plein écran/Présenter/Partager/Télécharger passent dans le
2107
2131
  menu « ⋯ » ; le titre se tronque puis disparaît. */
@@ -2149,6 +2173,7 @@ ${LEGAL_CSS}
2149
2173
  <button class=pbar-btn id=pbarInvite>Inviter l'équipe</button>
2150
2174
  <button class=pbar-btn id=pbarHandover>Passer la main</button>
2151
2175
  <button class=pbar-btn id=pbarEnd>Terminer</button>
2176
+ <span class=pbar-err id=pbarErr role=alert></span>
2152
2177
  </div>` : ""}
2153
2178
  <div class=pop id=pop>
2154
2179
  <h4>Transmettre le document</h4>
@@ -2182,11 +2207,11 @@ ${LEGAL_CSS}
2182
2207
  <script nonce="${nonce}">${PLAYER_BROWSER_JS}</script>
2183
2208
  ${balise(nonce, TIERS.pdf)}
2184
2209
  ${preview ? `${balise(nonce, TIERS.supa)}
2185
- <script nonce="${nonce}">var LIVECFG={supaUrl:${JSON.stringify(share.supa_url || "")},supaKey:${JSON.stringify(share.supa_key || "")},hostAuthKey:${JSON.stringify(cleSessionHote())},liveAuthKey:${JSON.stringify(CLE_SESSION_PLAYER)},guestKey:${JSON.stringify(CLE_INVITE)}};var GMAPS_KEY=${JSON.stringify((PLAYER.config && PLAYER.config.mapsKey) || "" || "")};${LIVE_JS}
2210
+ <script nonce="${nonce}">var LIVECFG=${jsonPourScript({ supaUrl: share.supa_url || "", supaKey: share.supa_key || "", hostAuthKey: cleSessionHote(), liveAuthKey: CLE_SESSION_PLAYER, guestKey: CLE_INVITE })};var GMAPS_KEY=${jsonPourScript((PLAYER.config && PLAYER.config.mapsKey) || "")};${LIVE_JS}
2186
2211
  ${MAP_JS}</script>` : ""}
2187
2212
  <script nonce="${nonce}">
2188
2213
  (function(){
2189
- var CFG=${cfg.replace(/</g, "\\u003c")};
2214
+ var CFG=${cfg};
2190
2215
  var cur=0, numPages=0;
2191
2216
  // Nature du document : le nom de fichier fait foi (le type MIME n'est pas toujours
2192
2217
  // renvoyé par le stockage). Une image = une page, sans pdf.js.
@@ -2348,15 +2373,39 @@ ${LEGAL_CSS}
2348
2373
  if(!PRES) return Promise.resolve();
2349
2374
  var p=PRES, btn=document.getElementById('pbarEnd'), libelle=btn?btn.textContent:'';
2350
2375
  if(btn){ btn.disabled=true; btn.textContent='…'; }
2376
+ var errAvant=document.getElementById('pbarErr'); if(errAvant){ errAvant.classList.remove('on'); }
2351
2377
  return Player.live.fetchBorne('/api/doc',{method:'POST',headers:{'Content-Type':'application/json'},keepalive:true,
2352
2378
  body:JSON.stringify({action:'present-end',slug:p.slug,control:p.control})})
2353
2379
  .then(function(r){ if(!r||!r.ok) throw new Error('present-end'); })
2354
2380
  .then(function(){
2381
+ // RELIRE, PAS REUTILISER : un second aller-retour, indépendant de l'écriture. Réserve
2382
+ // posée par le second hôte, et elle est juste — relire la réponse du PATCH ferait ce que
2383
+ // faisait le cache négatif : la mesure confirmerait ce que l'écriture CROIT avoir fait.
2384
+ // C'est le seul moyen de rendre observable la classe entière des succès silencieux, y
2385
+ // compris contre un futur serveur qui répondrait ok sans avoir écrit.
2386
+ return Player.live.fetchBorne('/api/doc?present='+encodeURIComponent(p.slug)+'&state=1')
2387
+ .then(function(r){ return r&&r.ok?r.json():null; })
2388
+ .catch(function(){ return null; }); // relecture INDISPONIBLE n'est pas « encore active »
2389
+ })
2390
+ .then(function(d){
2391
+ if(d&&d.state&&d.state.active){
2392
+ // Le serveur a dit oui, la base dit encore active : fin non prise, ou reprise ailleurs.
2393
+ // Dans les deux cas, fermer l'interface mentirait au présentateur.
2394
+ if(btn){ btn.disabled=false; btn.textContent=libelle||'Terminer'; }
2395
+ var err2=document.getElementById('pbarErr');
2396
+ if(err2){ err2.textContent='La présentation est ENCORE ACTIVE après la fin — reprise ailleurs, ou fin non prise. Réessayez.'; err2.classList.add('on'); }
2397
+ return;
2398
+ }
2355
2399
  PRES=null; clearInterval(_hbIv); clearCtl(p.slug);
2356
2400
  var pb=document.getElementById('pbar'); if(pb)pb.style.display='none';
2357
2401
  try{ if(window.Live){ Live.sendState(); Live.disconnect(); } }catch(e){}
2358
2402
  }, function(){
2359
- if(btn){ btn.disabled=false; btn.textContent=libelle||'Terminer'; btn.title='La fin n’a pas été enregistrée — réessayez.'; }
2403
+ // L'échec d'une clôture ne prive de rien qu'on regarde : son succès ne produit rien,
2404
+ // donc son échec non plus. La première version le disait dans un TITLE — un tooltip que
2405
+ // personne ne survole. Le second hôte a eu des présentations « actives » trois jours.
2406
+ if(btn){ btn.disabled=false; btn.textContent=libelle||'Terminer'; }
2407
+ var err=document.getElementById('pbarErr');
2408
+ if(err){ err.textContent='La présentation est TOUJOURS ACTIVE — la fin n’a pas été enregistrée. Réessayez.'; err.classList.add('on'); }
2360
2409
  });
2361
2410
  }
2362
2411
  // ⚠️ LE SEUL ENDROIT OÙ « sendBeacon » A UN SENS : la page s'en va, aucune réponse ne pourra être
@@ -2721,7 +2770,7 @@ function presentHtml(pres, nonce, logoUrl, supaUrl, supaKey) {
2721
2770
  const presenter = esc(pres.presenter_name || "");
2722
2771
  const logo = esc(logoUrl || "");
2723
2772
  const fileUrl = `/api/doc?present=${encodeURIComponent(pres.slug)}&file=1`;
2724
- const cfg = JSON.stringify({ fileUrl, docUrl: pres.file_url, pdfjs: PDFJS, slug: pres.slug, fileName: pres.file_name || "", page: pres.current_page || 1, active: pres.active !== false, content: pres.content || null, supaUrl, supaKey, title: pres.doc_title || pres.file_name || "Document" });
2773
+ const cfg = jsonPourScript({ fileUrl, docUrl: pres.file_url, pdfjs: PDFJS, slug: pres.slug, fileName: pres.file_name || "", page: pres.current_page || 1, active: pres.active !== false, content: pres.content || null, supaUrl, supaKey, title: pres.doc_title || pres.file_name || "Document" });
2725
2774
  return `<!doctype html><html lang=fr><head><meta charset=utf-8>
2726
2775
  <meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=3">
2727
2776
  <meta name=robots content="noindex,nofollow">
@@ -2795,7 +2844,7 @@ function presentHtml(pres, nonce, logoUrl, supaUrl, supaKey) {
2795
2844
  ${balise(nonce, TIERS.supa)}
2796
2845
  <script nonce="${nonce}">
2797
2846
  (function(){
2798
- var CFG=${cfg.replace(/</g, "\\u003c")};
2847
+ var CFG=${cfg};
2799
2848
  var PDF=null, total=0, cur=CFG.page||1, ready=false;
2800
2849
  var stage=document.getElementById('stage'), pageEl=document.getElementById('page');
2801
2850
  function hideLoader(){ var l=document.getElementById('load'); if(l){ l.classList.add('hide'); setTimeout(function(){ if(l.parentNode)l.parentNode.removeChild(l); },450);} }
@@ -2953,11 +3002,11 @@ function presentHtml(pres, nonce, logoUrl, supaUrl, supaKey) {
2953
3002
  })();
2954
3003
  </script>
2955
3004
  <script nonce="${nonce}">
2956
- var LIVECFG={supaUrl:${JSON.stringify(supaUrl || "")},supaKey:${JSON.stringify(supaKey || "")},hostAuthKey:${JSON.stringify(cleSessionHote())},liveAuthKey:${JSON.stringify(CLE_SESSION_PLAYER)},guestKey:${JSON.stringify(CLE_INVITE)}};var GMAPS_KEY=${JSON.stringify((PLAYER.config && PLAYER.config.mapsKey) || "" || "")};
3005
+ var LIVECFG=${jsonPourScript({ supaUrl: supaUrl || "", supaKey: supaKey || "", hostAuthKey: cleSessionHote(), liveAuthKey: CLE_SESSION_PLAYER, guestKey: CLE_INVITE })};var GMAPS_KEY=${jsonPourScript((PLAYER.config && PLAYER.config.mapsKey) || "")};
2957
3006
  ${LIVE_JS}
2958
3007
  ${MAP_JS}
2959
3008
  (function(){
2960
- var slug=${JSON.stringify(pres.slug)};
3009
+ var slug=${jsonPourScript(pres.slug)};
2961
3010
  // Carte live : suivre en direct les mouvements du présentateur (broadcast).
2962
3011
  // ⚠️ ON NE CROIT PLUS LA CHARGE. Le signal dit « quelque chose a bougé » ; ce qui a bougé vient
2963
3012
  // de la relecture d'état, gatée par l'écriture du présentateur. Un participant hostile peut
@@ -3107,7 +3156,14 @@ async function handler(req, res) {
3107
3156
  ? await touchPresentation(String(body.slug || ""), String(body.control || ""))
3108
3157
  : await endPresentation(String(body.slug || ""), String(body.control || ""));
3109
3158
  return jp(r.ok ? 200 : (r.status || 400), r);
3110
- } catch { return jp(500, { ok: false }); }
3159
+ } catch (erreur) {
3160
+ // ⚠️ CE CATCH A AVALÉ TROIS JOURS DE CLÔTURES IMPOSSIBLES SANS UNE TRACE. Chez le second
3161
+ // hôte, chaque « Terminer » échouait en 23502 (marqueur d'archive NOT NULL) — et ce 500
3162
+ // muet ne laissait RIEN, même pas une ligne dans le journal d'erreurs. Un journal que
3163
+ // personne ne lit vaut peu ; aucun journal ne vaut rien du tout.
3164
+ try { PLAYER.errors.capture(erreur instanceof Error ? erreur : new Error(String(erreur)), { route: "present-" + String(body.action || "").replace(/^present-/, "") }); } catch { /* jamais bloquant */ }
3165
+ return jp(500, { ok: false });
3166
+ }
3111
3167
  }
3112
3168
  // Assistant IA « présentateur » (bot) sur un lien tracé bot_enabled. PUBLIC (prospect anonyme) → rate-limit IP.
3113
3169
  // Synthèse vocale (ElevenLabs) : Léa lit ses messages. PUBLIC (audience anonyme), mais gated : la clé
@@ -4151,6 +4207,6 @@ async function handler(req, res) {
4151
4207
  // tenir une seconde liste — c'est la seule façon qu'une empreinte périmée finisse par se voir.
4152
4208
  // ⚠️ Exporté pour être ÉPROUVÉ, pas pour être appelé : le plafond du relais ne se vérifie qu en
4153
4209
  // regardant si le corps a été lu, ce qu aucune route ne peut montrer de l extérieur.
4154
- module.exports = { handler, init, TIERS, __relayerFichier: relayerFichier };
4210
+ module.exports = { handler, init, TIERS, __relayerFichier: relayerFichier, __jsonPourScript: jsonPourScript };
4155
4211
 
4156
4212
  // redeploy: forcer le build production (Vercel a sauté la prod du merge #463 — wording re-partage).
@@ -24,8 +24,29 @@ const tokenMatches = (token, storedHash) => !!(token && storedHash && sameHash(s
24
24
  // owner = { id, email, name, avatar } (membre authentifié). La CLÉ de propriété est l'EMAIL (issu du JWT
25
25
  // vérifié) → permet reprise / liste / transfert, y compris vers un membre choisi par email.
26
26
  const lc = (s) => String(s || "").trim().toLowerCase();
27
+ /**
28
+ * ⚠️ LA PURGE NE DOIT PAS DÉPENDRE DE L'OUVERTURE D'UN PANNEAU. Elle ne vivait que dans
29
+ * `listActivePresentations` — c'est-à-dire qu'elle ne tournait que si quelqu'un ouvrait la liste.
30
+ * Le second hôte a eu QUATRE présentations bloquées « actives » pendant TROIS JOURS : personne
31
+ * n'avait ouvert le panneau, donc personne n'avait déclenché le seul mécanisme qui les fermait.
32
+ * Un échec de clôture ne prive de rien qu'on regarde — la purge doit donc s'accrocher à un geste
33
+ * qui arrive de lui-même. Démarrer une présentation en est un : ce sont les présentateurs qui
34
+ * créent les orphelines, et le suivant nettoie celles d'avant.
35
+ *
36
+ * Conditionnée à `active=eq.true&last_seen=lte.seuil` : une session qui vient de battre n'est pas
37
+ * touchée (même garde que la purge du panneau, chantier des écritures conditionnées).
38
+ */
39
+ async function purgerPerimees(now) {
40
+ const seuil = new Date(now - STALE_MS).toISOString();
41
+ await PLAYER.db.request(
42
+ `doc_presentations?active=eq.true&last_seen=lte.${enc(seuil)}`,
43
+ { method: "PATCH", headers: { Prefer: "return=minimal" }, body: { active: false, updated_at: new Date(now).toISOString() } },
44
+ ).catch(() => { /* la purge est un service, jamais un préalable */ });
45
+ }
46
+
27
47
  async function createPresentation({ docId, fileUrl, fileName, docTitle, presenterName, owner }) {
28
48
  if (!fileUrl) throw Object.assign(new Error("doc invalide"), { statusCode: 400 });
49
+ await purgerPerimees(Date.now());
29
50
  const slug = newToken(9); // ~12 chars URL-safe
30
51
  const control = newToken(18); // secret pilotage
31
52
  const o = owner && typeof owner === "object" ? owner : {};
@@ -846,4 +867,5 @@ async function listPresentationsForDoc(docId, email, isAdmin, autoriseLarge) {
846
867
 
847
868
  module.exports = {
848
869
  reacteurDepuisJeton,
870
+ purgerPerimees,
849
871
  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 };
package/supabase/init.sql CHANGED
@@ -331,19 +331,15 @@ alter table public.doc_bot_sessions enable row level security;
331
331
  --
332
332
  -- ⚠️ Ne mettez de toute façon jamais de donnée confidentielle dans un message de chat ni dans un
333
333
  -- nom d'auteur.
334
- do $$
335
- begin
336
- if exists (select 1 from pg_publication where pubname = 'supabase_realtime') then
337
- if not exists (
338
- select 1 from pg_publication_tables
339
- where pubname = 'supabase_realtime' and schemaname = 'public'
340
- and tablename = 'doc_presentation_messages'
341
- ) then
342
- execute 'alter publication supabase_realtime add table public.doc_presentation_messages';
343
- end if;
344
- end if;
345
- end $$;
346
- alter table public.doc_presentation_messages replica identity full;
334
+ -- ⚠️ AUCUNE TABLE DU PLAYER N'EST PUBLIÉE DANS `supabase_realtime`, ET C'EST UN CHOIX. Le direct
335
+ -- passe par BROADCAST (un signal d'invalidation, jamais un contenu) suivi d'une relecture HTTP qui
336
+ -- sert la PROJECTION publique. Publier la table n'alimenterait personne tant que RLS reste sans
337
+ -- politique — mais laisserait armée la combinaison « quelqu'un ajoute un SELECT public un jour »
338
+ -- → la ligne ENTIÈRE part au navigateur, author_hash compris, en contournant la projection.
339
+ -- N'ajoutez pas non plus de politique SELECT publique « pour que le direct fonctionne » : c'est
340
+ -- l'erreur exacte dont l'hôte historique a dû sortir. (Jusqu'à la 0009, ce fichier ajoutait la
341
+ -- table à la publication avec REPLICA IDENTITY FULL — chaque réaction payait l'image complète de
342
+ -- la ligne dans le WAL, pour un canal que plus rien n'écoutait.)
347
343
 
348
344
  -- ── Le scellé de l'archive ─────────────────────────────────────────────────────────────────────
349
345
  -- Sept chemins d'écriture vérifient l'archive PUIS écrivent — dans une AUTRE table que celle qui
@@ -0,0 +1,42 @@
1
+ -- LE CHAT NE PASSE PLUS PAR POSTGRES CHANGES — LA BASE DOIT CESSER DE LE PROPOSER.
2
+ --
3
+ -- Les messages voyagent en broadcast (signal d'invalidation) suivi d'une relecture HTTP bornée,
4
+ -- qui sert la PROJECTION publique — jamais la ligne brute. Publier la table dans
5
+ -- `supabase_realtime` n'alimentait donc plus personne : RLS sans politique de lecture, rien ne
6
+ -- sortait. Mais une surface qui ne sert plus n'est pas une surface neutre : le jour où quelqu'un
7
+ -- ajoute une politique SELECT « pour que le direct fonctionne » (l'erreur exacte dont l'hôte
8
+ -- historique a dû sortir), la publication redevient un canal — qui livre la ligne ENTIÈRE,
9
+ -- author_hash compris, en contournant la projection.
10
+ --
11
+ -- ⚠️ ET `REPLICA IDENTITY FULL` COÛTE À CHAQUE ÉCRITURE, PAS SEULEMENT EN THÉORIE : chaque
12
+ -- réaction, chaque édition écrit l'image complète de la ligne dans le WAL. On revient à DEFAULT.
13
+ --
14
+ -- ⚠️ LE CAS `FOR ALL TABLES` REFUSE BRUYAMMENT. PostgreSQL ne sait pas exclure UNE table d'une
15
+ -- publication totale, et reconstruire la publication toucherait des tables qui appartiennent à
16
+ -- l'exploitant. On échoue avec la marche à suivre — un échec net vaut mieux qu'un « réussi » qui
17
+ -- n'a rien retiré.
18
+ --
19
+ -- Sans lui : rien ne casse — le chat fonctionne déjà sans ce canal. La surface reste simplement
20
+ -- ouverte, et le WAL continue de payer le plein tarif sur chaque réaction.
21
+ do $$
22
+ declare
23
+ v_toutes boolean;
24
+ begin
25
+ select puballtables into v_toutes from pg_publication where pubname = 'supabase_realtime';
26
+ if v_toutes is null then
27
+ return; -- pas de publication : rien à retirer (Postgres nu, ou realtime jamais activé)
28
+ end if;
29
+ if v_toutes then
30
+ raise exception 'supabase_realtime est FOR ALL TABLES : impossible d''en exclure doc_presentation_messages seule. Recréez la publication en liste explicite (opération de l''exploitant), puis rejouez cette migration.';
31
+ end if;
32
+ if exists (
33
+ select 1 from pg_publication_tables
34
+ where pubname = 'supabase_realtime' and schemaname = 'public'
35
+ and tablename = 'doc_presentation_messages'
36
+ ) then
37
+ execute 'alter publication supabase_realtime drop table public.doc_presentation_messages';
38
+ end if;
39
+ end
40
+ $$;
41
+
42
+ alter table public.doc_presentation_messages replica identity default;