discovery-media-player 0.1.43 → 0.1.45

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "discovery-media-player",
3
- "version": "0.1.43",
3
+ "version": "0.1.45",
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 j=Object.defineProperty;var _e=Object.getOwnPropertyDescriptor;var we=Object.getOwnPropertyNames;var Te=Object.prototype.hasOwnProperty;var y=(e,n)=>{for(var t in n)j(e,t,{get:n[t],enumerable:!0})},Ee=(e,n,t,r)=>{if(n&&typeof n==\"object\"||typeof n==\"function\")for(let o of we(n))!Te.call(e,o)&&o!==t&&j(e,o,{get:()=>n[o],enumerable:!(r=_e(n,o))||r.enumerable});return e};var Se=e=>Ee(j({},\"__esModule\",{value:!0}),e);var wn={};y(wn,{bridge:()=>C,chat:()=>N,live:()=>O,presentation:()=>$,presentationState:()=>U,tracking:()=>L,viewer:()=>H});var C={};y(C,{onHostMessage:()=>$e,onPlayerMessage:()=>Ne,parseHostMessage:()=>pe,parsePlayerMessage:()=>de,sendToHost:()=>Ce,sendToPlayer:()=>Le,toWire:()=>K});var G=\"3dd-doc-\",ke=/^[A-Za-z0-9_-]{1,64}$/,Re=[\"close\",\"share\",\"embed-ready\",\"present-left\",\"present-denied\"],Ie=[\"embed-denied\"],Pe=[\"present-invite\",\"present-handover\",\"present-switch\"],Ae=[\"handover-done\"];function K(e){let n={type:G+e.type};return\"slug\"in e&&e.slug&&(n.slug=e.slug),\"reason\"in e&&e.reason&&(n.reason=String(e.reason).slice(0,40)),n}function ce(e,n,t){if(!e||typeof e!=\"object\")return null;let r=e.type;if(typeof r!=\"string\"||!r.startsWith(G))return null;let o=r.slice(G.length);if(n.includes(o))return{type:o};if(Ie.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\"||!ke.test(s)?null:{type:o,slug:s}}function de(e){return ce(e,Re,Pe)}function pe(e){return ce(e,Ae,[])}function Ce(e,n){try{let t=n||(typeof window!=\"undefined\"?window.parent:null);t&&t.postMessage(K(e),\"*\")}catch(t){}}function Le(e,n){var t;try{(t=e==null?void 0:e.contentWindow)==null||t.postMessage(K(n),\"*\")}catch(r){}}function Oe(e,n){var r;if(!e)return!0;let t=(r=e.contentWindow)!=null?r:e;return n===t}function Ne(e,n,t){let r=n||(typeof window!=\"undefined\"?window:null);if(!r)return()=>{};let o=s=>{if(!Oe(t,s.source))return;let a=de(s.data);a&&e(a)};return r.addEventListener(\"message\",o),()=>r.removeEventListener(\"message\",o)}function $e(e,n){var s;let t=n||(typeof window!=\"undefined\"?window:null);if(!t)return()=>{};let r=(s=t.parent)!=null?s:null,o=a=>{if(!r||r===t||a.source!==r)return;let c=pe(a.data);c&&e(c)};return t.addEventListener(\"message\",o),()=>t.removeEventListener(\"message\",o)}var L={};y(L,{createTracker:()=>Ke});var He=Math.ceil(300),fe=25,En=He*fe,me=18e4,Ue=25e3,De=Math.ceil(36e5/Ue),We=720,Ve=De+We,Sn=Ve*fe;var ze=[\"mousemove\",\"mousedown\",\"keydown\",\"wheel\",\"touchstart\",\"pointerdown\"];function je(){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 Ge(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 Ke(e={}){var ae,le;let n=e.win||(typeof window!=\"undefined\"?window:null),t=e.doc||n&&n.document,r=e.now||(()=>Date.now()),o=e.endpoint||\"/api/doc\",s=(ae=e.idleMs)!=null?ae:me,a=(le=e.sessionEveryMs)!=null?le:12e3,c=e.slug||\"\",d=e.internal||null,m=e.sessionId||je(),b=e.send||Ge(o,n),f={},h=0,M=0,u=0,v=0,g=null,T=r(),E=!1,D=!1,P=[],xe=()=>!!c||!!d,te=i=>{if(xe())try{b(i)}catch(l){}},W=()=>!!t&&t.visibilityState===\"visible\"&&!E,re=i=>{let l=r()-i,x=Math.max(0,T-i)+s;return Math.max(0,Math.min(l,x))},V=()=>{g!=null&&h>0&&(f[h]=(f[h]||0)+re(g)/1e3),g=W()?r():null},oe=()=>{V(),g=null},F=()=>{g==null&&W()&&(g=r())},z=()=>{let i=0;for(let l in f)i+=f[l];return g!=null&&(i+=re(g)/1e3),Math.round(i)},ie=()=>{let i={};for(let l in f)i[l]=Math.round(f[l]);return i},S=()=>{T=r(),E&&(E=!1,F())},se=(i,l)=>{d||te({slug:c,event:i,page:l||M,maxPage:u,seconds:z(),sessionId:m})},A=()=>{V();let i={event:\"session\",sessionId:m,numPages:v,maxPage:u,totalSeconds:z(),pagesTime:ie()};d?(i.internal=!0,i.docId=d.docId,i.email=d.email,i.name=d.name,d.it&&(i.it=d.it)):i.slug=c,te(i)},k=(i,l,x,ue)=>{!i||typeof i.addEventListener!=\"function\"||(i.addEventListener(l,x,ue),P.push(()=>{i.removeEventListener(l,x,ue)}))};return{sessionId:m,totalSeconds:z,maxPage:()=>u,pageTimes:ie,start(){if(D)return;D=!0,se(\"open\",1),h=1,T=r(),g=W()?r():null,k(t,\"visibilitychange\",()=>{t.visibilityState===\"hidden\"?(oe(),A()):(S(),F())}),k(n,\"focus\",()=>{S(),F()});for(let x of ze)k(n,x,S,{passive:!0});k(e.scrollElement,\"scroll\",S,{passive:!0}),k(n,\"beforeunload\",A);let i=n.setInterval(()=>{!E&&r()-T>s&&(E=!0,oe())},5e3),l=n.setInterval(A,a);P.push(()=>{n.clearInterval(i),n.clearInterval(l)})},setPage(i){!i||i===M||(T=r(),V(),h=i,M=i,i>u&&(u=i,se(\"page\",i)))},setPageCount(i){v=i||0},noteActivity:S,flush:A,stop(){for(;P.length;){let i=P.pop();try{i==null||i()}catch(l){}}D=!1}}}var O={};y(O,{STORAGE_KEYS:()=>R,attendeeKey:()=>qe,authorToken:()=>Xe,avatarHtml:()=>Y,canModerate:()=>q,createScheduler:()=>nn,escapeHtml:()=>p,flattenPresence:()=>Ye,formatMessageBody:()=>B,initials:()=>ge,isMentioned:()=>Qe,isMine:()=>_,reactorId:()=>Z,shouldNotify:()=>Je,unreadLabel:()=>en});var R={attendeeKey:\"3dd-present-attkey\",authorToken:\"3dd-present-authtoken\",chatMuted:\"3dd-present-mute\"};function p(e){return String(e==null?\"\":e).replace(/&/g,\"&amp;\").replace(/</g,\"&lt;\").replace(/>/g,\"&gt;\").replace(/\"/g,\"&quot;\")}function ge(e){let n=(e||\"?\").trim().split(/\\s+/);return(((n[0]||\"\")[0]||\"?\")+((n[1]||\"\")[0]||\"\")).toUpperCase()}function Y(e,n){return e?`<img src=\"${p(e)}\" alt=\"\">`:p(ge(n))}function B(e){let n=p(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 Ye(e){let n={},t=[];for(let r in e||{})for(let o of(e||{})[r]||[]){let s=String(o.uid||o.email||o.name||\"\").toLowerCase()||`_${r}_${t.length}`;n[s]?o.role===\"presenter\"&&(n[s]=o):(n[s]=o,t.push(s))}return t.map(r=>n[r])}function Be(e,n){try{return e?e.getItem(n):null}catch(t){return null}}function Ze(e,n,t){try{e==null||e.setItem(n,t)}catch(r){}}function qe(e,n=\"anon\",t=be){try{if(!e)return`anon-${n}`;let r=e.getItem(R.attendeeKey);if(r)return r;let o=`anon-${t()}`;return e.setItem(R.attendeeKey,o),o}catch(r){return`anon-${n}`}}function be(){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 Xe(e,n=be){let t=Be(e,R.authorToken);if(t)return t;let r=n();return Ze(e,R.authorToken,r),r}function Z(e){return String(e&&(e.email||e.name)||\"\").toLowerCase()}function _(e,n){return!e||!n?!1:e.author_email&&n.email?e.author_email===n.email:!e.author_email&&!n.email?e.author_name===n.name:!1}function q(e){return!!(e&&e.role===\"presenter\")}function Qe(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}`)&&!_(e,n):!1}function Je(e){let{msg:n,me:t,historyLoaded:r,chatHidden:o}=e;return!r||!n||n.deleted||_(n,t)?!1:o}function en(e){return!e||e<=0?\"\":e>9?\"9+\":String(e)}function nn(e,n={}){var M;let t=(M=n.minMs)!=null?M:300,r=n.now||(()=>Date.now()),o=n.setTimer||((u,v)=>setTimeout(u,v)),s=n.clearTimer||(u=>clearTimeout(u)),a=!1,c=!1,d=-1/0,m=null,b=!1;function f(){if(b||a){c=!0;return}let u=t-(r()-d);if(u>0){h(u);return}a=!0,c=!1,d=r();let v=!1;e(()=>{v||(v=!0,a=!1,c&&!b&&f())})}function h(u){b||m!==null||(m=o(()=>{m=null,f()},Math.max(0,u)))}return{signaler(){b||f()},maintenant(){b||(d=-1/0,f())},arreter(){b=!0,m!==null&&(s(m),m=null)}}}var N={};y(N,{messageClassName:()=>rn,renderActions:()=>ve,renderAttachment:()=>he,renderMessage:()=>tn,renderReactions:()=>ye});function ye(e,n){let t=e&&e.reactions||{},r=Z(n),o=\"\";for(let s in t){let a=t[s]||[];if(!a.length)continue;let c=a.indexOf(r)>=0;o+=`<button class=\"re-chip${c?\" on\":\"\"}\" data-e=\"${p(s)}\">${p(s)} ${a.length}</button>`}return o}function he(e){if(!e||!e.url)return\"\";let n=p(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=p(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 ve(e,n){let 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||q(n.me))&&(r+='<button class=cm-del-btn title=\"Supprimer\">\\u{1F5D1}</button>'),r}function tn(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>${p(e.reply_name||\"\")}</b> ${p(e.reply_text||\"\")}</div>`:\"\",r=e.edited?\" <span class=cm-ed>(modifi\\xE9)</span>\":\"\",o=e.body?`<div class=txt>${B(e.body)}${r}</div>`:\"\",s=e.is_presenter?\" <span class=tag>pr\\xE9sentateur</span>\":\"\";return`<span class=a>${Y(e.author_avatar,e.author_name)}</span><span class=b><div class=who><b>${p(e.author_name||\"Invit\\xE9\")}</b>${s}</div>`+t+o+he(e.attachment)+`<div class=cm-re>${ye(e,n.me)}</div></span><span class=cm-act>${ve(e,n)}</span>`}function rn(e,n,t){return\"cm\"+(_(e,n)?\" me\":\"\")+(e.deleted?\" isdel\":\"\")+(t?\" mentioned\":\"\")}var $={};y($,{DEFAULT_CENTER:()=>J,DEFAULT_ZOOM:()=>ee,MAP_TYPES:()=>I,cycleMapType:()=>on,initialMapContent:()=>an,mapTypeLabel:()=>sn,sanitizeContent:()=>ne});var I=[\"roadmap\",\"satellite\",\"hybrid\"],J=[46.6,2.5],ee=6;function w(e){let n=Number(e);return Number.isFinite(n)?n:null}function X(e){if(!Array.isArray(e))return null;let n=w(e[0]),t=w(e[1]);return n!=null&&t!=null?[n,t]:null}var Q=(e,n,t)=>Math.max(n,Math.min(t,e));function ne(e){if(!e||typeof e!=\"object\")return null;let n=e;if(n.kind===\"pdf\"||n.kind==null)return null;if(n.kind===\"streetview\"){let t=X(n.position);if(!t)return null;let r=n.pov&&typeof n.pov==\"object\"?n.pov:null,o=r?{heading:w(r.heading)||0,pitch:Q(w(r.pitch)||0,-90,90)}:{heading:0,pitch:0};return{kind:\"streetview\",position:t,pov:o,zoom:Q(w(n.zoom)||1,0,5)}}return n.kind!==\"map\"?null:{kind:\"map\",center:X(n.center)||[...J],zoom:Q(Math.trunc(w(n.zoom)||ee),1,21),marker:X(n.marker),mapType:I.includes(n.mapType)?n.mapType:null,label:String(n.label||\"\").slice(0,160)||null}}function on(e){let n=I.indexOf(e);return I[(n+1)%I.length]}function sn(e){return e===\"roadmap\"?\"\\u{1F6F0} Satellite\":e===\"satellite\"?\"\\u{1F5FA} Hybride\":\"\\u{1F5FA} Plan\"}function an(){return{kind:\"map\",center:[...J],zoom:ee,marker:null,mapType:null,label:null}}var H={};y(H,{CURRENT_PAGE_MARGIN:()=>fn,DEFAULT_ASPECT:()=>cn,MAX_ZOOM:()=>un,MIN_PAGE_WIDTH:()=>dn,MIN_ZOOM:()=>ln,PRERENDER_MARGIN:()=>pn,arrowState:()=>bn,averageColor:()=>vn,clampPage:()=>Me,clampZoom:()=>mn,fitWidth:()=>hn,isImageDocument:()=>Mn,progressPercent:()=>gn});var ln=.5,un=3,cn=1.35,dn=280,pn=\"500px 0px\",fn=\"-48% 0px -48% 0px\";function mn(e){let n=Math.round((Number(e)||0)*10)/10;return Math.max(.5,Math.min(3,n))}function Me(e,n){return Math.max(1,Math.min(n||1,Number(e)||1))}function gn(e,n){return n?Math.round(Me(e,n)/n*100):0}function bn(e,n){return{prevDisabled:e<=1,nextDisabled:!!n&&e>=n}}var yn=28;function hn(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)-yn-(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 Mn(e,n){return/[.](png|jpe?g|webp|gif|avif)($|[?])/i.test(String(e||n||\"\"))}var U={};y(U,{presentationTransition:()=>xn,switchDocUrl:()=>_n});function xn(e,n={}){if(!e)return[{kind:\"nothing\"}];if(e.active===!1)return[{kind:\"ended\"}];let t=ne(e.content);if(t)return[{kind:\"show-map\",content:t}];let r=[{kind:\"leave-map\"}];return e.file_url&&e.file_url!==n.docUrl?(r.push({kind:\"switch-doc\",url:e.file_url,name:e.file_name||void 0,title:e.doc_title||void 0,updatedAt:e.updated_at||void 0}),r):(e.current_page&&r.push({kind:\"show-page\",page:e.current_page}),r)}function _n(e,n){let t=encodeURIComponent(n||String(Date.now()));return`${e}${e.includes(\"?\")?\"&\":\"?\"}v=${t}`}return Se(wn);})();", SOURCE_HASH: "bcdddbf20bceaa1b" };
7
+ module.exports = { PLAYER_BROWSER_JS: "\"use strict\";var Player=(()=>{var C=Object.defineProperty,$e=Object.defineProperties,He=Object.getOwnPropertyDescriptor,Ue=Object.getOwnPropertyDescriptors,De=Object.getOwnPropertyNames,he=Object.getOwnPropertySymbols;var ve=Object.prototype.hasOwnProperty,We=Object.prototype.propertyIsEnumerable;var ye=(e,n,t)=>n in e?C(e,n,{enumerable:!0,configurable:!0,writable:!0,value:t}):e[n]=t,B=(e,n)=>{for(var t in n||(n={}))ve.call(n,t)&&ye(e,t,n[t]);if(he)for(var t of he(n))We.call(n,t)&&ye(e,t,n[t]);return e},Me=(e,n)=>$e(e,Ue(n));var v=(e,n)=>{for(var t in n)C(e,t,{get:n[t],enumerable:!0})},Ve=(e,n,t,r)=>{if(n&&typeof n==\"object\"||typeof n==\"function\")for(let o of De(n))!ve.call(e,o)&&o!==t&&C(e,o,{get:()=>n[o],enumerable:!(r=He(n,o))||r.enumerable});return e};var Fe=e=>Ve(C({},\"__esModule\",{value:!0}),e);var Un={};v(Un,{bridge:()=>L,cadence:()=>N,chat:()=>H,live:()=>$,presentation:()=>U,presentationState:()=>W,tracking:()=>O,viewer:()=>D});var L={};v(L,{onHostMessage:()=>Qe,onPlayerMessage:()=>Xe,parseHostMessage:()=>Te,parsePlayerMessage:()=>_e,sendToHost:()=>Ye,sendToPlayer:()=>Ze,toWire:()=>Y});var K=\"3dd-doc-\",ze=/^[A-Za-z0-9_-]{1,64}$/,je=[\"close\",\"share\",\"embed-ready\",\"present-left\",\"present-denied\"],Ge=[\"embed-denied\"],Be=[\"present-invite\",\"present-handover\",\"present-switch\"],Ke=[\"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(Ge.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\"||!ze.test(s)?null:{type:o,slug:s}}function _e(e){return xe(e,je,Be)}function Te(e){return xe(e,Ke,[])}function Ye(e,n){try{let t=n||(typeof window!=\"undefined\"?window.parent:null);t&&t.postMessage(Y(e),\"*\")}catch(t){}}function Ze(e,n){var t;try{(t=e==null?void 0:e.contentWindow)==null||t.postMessage(Y(n),\"*\")}catch(r){}}function qe(e,n){var r;if(!e)return!0;let t=(r=e.contentWindow)!=null?r:e;return n===t}function Xe(e,n,t){let r=n||(typeof window!=\"undefined\"?window:null);if(!r)return()=>{};let o=s=>{if(!qe(t,s.source))return;let a=_e(s.data);a&&e(a)};return r.addEventListener(\"message\",o),()=>r.removeEventListener(\"message\",o)}function Qe(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=Te(a.data);l&&e(l)};return t.addEventListener(\"message\",o),()=>t.removeEventListener(\"message\",o)}var O={};v(O,{createTracker:()=>sn});var N={};v(N,{PRESENTER_ACTIONS_PER_HOUR:()=>Q,PRESENT_QUOTA_PER_HOUR:()=>nn,PRESENT_READS_PER_HOUR:()=>ke,PRESENT_READ_BURST:()=>Re,PRESENT_RESYNC_MS:()=>Ee,PRESENT_SIGNAL_BUDGET_PER_HOUR:()=>en,PRESENT_WRITE_TIMEOUT_MS:()=>J,READERS_PER_EGRESS:()=>q,RESYNC_READS_PER_HOUR:()=>Se,SESSION_IDLE_MS:()=>X,SESSION_INTERVAL_MS:()=>Z,SESSION_QUOTA_PER_HOUR:()=>Je,SESSION_WRITES_PER_HOUR:()=>we});var Z=12e3,we=Math.ceil(300),q=25,Je=we*q,X=18e4,Ee=25e3,Se=Math.ceil(36e5/Ee),Q=720,ke=Se+Q,en=Q,Re=20,nn=(ke+Re)*q,J=1e4;var tn=[\"mousemove\",\"mousedown\",\"keydown\",\"wheel\",\"touchstart\",\"pointerdown\"];function rn(){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 on(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 sn(e={}){var me,ge;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=(me=e.idleMs)!=null?me:X,a=(ge=e.sessionEveryMs)!=null?ge:12e3,l=e.slug||\"\",p=e.internal||null,m=e.sessionId||rn(),b=e.send||on(o,n),f={},u=0,h=0,c=0,M=0,y=null,w=r(),E=!1,V=!1,P=[],Oe=()=>!!l||!!p,ue=i=>{if(Oe())try{b(i)}catch(d){}},F=()=>!!t&&t.visibilityState===\"visible\"&&!E,ce=i=>{let d=r()-i,x=Math.max(0,w-i)+s;return Math.max(0,Math.min(d,x))},z=()=>{y!=null&&u>0&&(f[u]=(f[u]||0)+ce(y)/1e3),y=F()?r():null},de=()=>{z(),y=null},j=()=>{y==null&&F()&&(y=r())},G=()=>{let i=0;for(let d in f)i+=f[d];return y!=null&&(i+=ce(y)/1e3),Math.round(i)},pe=()=>{let i={};for(let d in f)i[d]=Math.round(f[d]);return i},S=()=>{w=r(),E&&(E=!1,j())},fe=(i,d)=>{p||ue({slug:l,event:i,page:d||h,maxPage:c,seconds:G(),sessionId:m})},A=()=>{z();let i={event:\"session\",sessionId:m,numPages:M,maxPage:c,totalSeconds:G(),pagesTime:pe()};p?(i.internal=!0,i.docId=p.docId,i.email=p.email,i.name=p.name,p.it&&(i.it=p.it)):i.slug=l,ue(i)},k=(i,d,x,be)=>{!i||typeof i.addEventListener!=\"function\"||(i.addEventListener(d,x,be),P.push(()=>{i.removeEventListener(d,x,be)}))};return{sessionId:m,totalSeconds:G,maxPage:()=>c,pageTimes:pe,start(){if(V)return;V=!0,fe(\"open\",1),u=1,w=r(),y=F()?r():null,k(t,\"visibilitychange\",()=>{t.visibilityState===\"hidden\"?(de(),A()):(S(),j())}),k(n,\"focus\",()=>{S(),j()});for(let x of tn)k(n,x,S,{passive:!0});k(e.scrollElement,\"scroll\",S,{passive:!0}),k(n,\"beforeunload\",A);let i=n.setInterval(()=>{!E&&r()-w>s&&(E=!0,de())},5e3),d=n.setInterval(A,a);P.push(()=>{n.clearInterval(i),n.clearInterval(d)})},setPage(i){!i||i===h||(w=r(),z(),u=i,h=i,i>c&&(c=i,fe(\"page\",i)))},setPageCount(i){M=i||0},noteActivity:S,flush:A,stop(){for(;P.length;){let i=P.pop();try{i==null||i()}catch(d){}}V=!1}}}var $={};v($,{STORAGE_KEYS:()=>R,attendeeKey:()=>cn,authorToken:()=>dn,avatarHtml:()=>ee,canModerate:()=>re,createBudget:()=>hn,createScheduler:()=>gn,escapeHtml:()=>g,fetchBorne:()=>bn,flattenPresence:()=>an,formatMessageBody:()=>ne,initials:()=>Ie,isMentioned:()=>pn,isMine:()=>_,reactorId:()=>te,shouldNotify:()=>fn,unreadLabel:()=>mn});var R={attendeeKey:\"3dd-present-attkey\",authorToken:\"3dd-present-authtoken\",chatMuted:\"3dd-present-mute\"};function g(e){return String(e==null?\"\":e).replace(/&/g,\"&amp;\").replace(/</g,\"&lt;\").replace(/>/g,\"&gt;\").replace(/\"/g,\"&quot;\")}function Ie(e){let n=(e||\"?\").trim().split(/\\s+/);return(((n[0]||\"\")[0]||\"?\")+((n[1]||\"\")[0]||\"\")).toUpperCase()}function ee(e,n){return e?`<img src=\"${g(e)}\" alt=\"\">`:g(Ie(n))}function ne(e){let n=g(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 an(e){let n={},t=[];for(let r in e||{})for(let o of(e||{})[r]||[]){let s=String(o.uid||o.email||o.name||\"\").toLowerCase()||`_${r}_${t.length}`;n[s]?o.role===\"presenter\"&&(n[s]=o):(n[s]=o,t.push(s))}return t.map(r=>n[r])}function ln(e,n){try{return e?e.getItem(n):null}catch(t){return null}}function un(e,n,t){try{e==null||e.setItem(n,t)}catch(r){}}function cn(e,n=\"anon\",t=Pe){try{if(!e)return`anon-${n}`;let r=e.getItem(R.attendeeKey);if(r)return r;let o=`anon-${t()}`;return e.setItem(R.attendeeKey,o),o}catch(r){return`anon-${n}`}}function Pe(){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 dn(e,n=Pe){let t=ln(e,R.authorToken);if(t)return t;let r=n();return un(e,R.authorToken,r),r}function te(e){return String(e&&(e.email||e.name)||\"\").toLowerCase()}function _(e,n){return!e||!n?!1:e.author_email&&n.email?e.author_email===n.email:!e.author_email&&!n.email?e.author_name===n.name:!1}function re(e){return!!(e&&e.role===\"presenter\")}function pn(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}`)&&!_(e,n):!1}function fn(e){let{msg:n,me:t,historyLoaded:r,chatHidden:o}=e;return!r||!n||n.deleted||_(n,t)?!1:o}function mn(e){return!e||e<=0?\"\":e>9?\"9+\":String(e)}function gn(e,n={}){var h;let t=(h=n.minMs)!=null?h:300,r=n.now||(()=>Date.now()),o=n.setTimer||((c,M)=>setTimeout(c,M)),s=n.clearTimer||(c=>clearTimeout(c)),a=!1,l=!1,p=-1/0,m=null,b=!1;function f(){if(b||a){l=!0;return}let c=t-(r()-p);if(c>0){u(c);return}a=!0,l=!1,p=r();let M=!1;e(()=>{M||(M=!0,a=!1,l&&!b&&f())})}function u(c){b||m!==null||(m=o(()=>{m=null,f()},Math.max(0,c)))}return{signaler(){b||f()},maintenant(){b||(p=-1/0,f())},arreter(){b=!0,m!==null&&(s(m),m=null)}}}function bn(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,h)=>setTimeout(u,h)),a=r.clearTimer||(u=>clearTimeout(u)),l=null;try{l=new AbortController}catch(u){}let p=l?Me(B({},n||{}),{signal:l.signal}):B({},n||{}),m=null,b=new Promise((u,h)=>{m=s(()=>{try{l==null||l.abort()}catch(c){}h(new Error(\"delai\"))},t)}),f=()=>{try{a(m)}catch(u){}};return Promise.race([o(e,p),b]).then(u=>(f(),u),u=>{throw f(),u})}function hn(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}}var H={};v(H,{messageClassName:()=>vn,renderActions:()=>Le,renderAttachment:()=>Ce,renderMessage:()=>yn,renderReactions:()=>Ae});function Ae(e,n){let t=e&&e.reactions||{},r=te(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=\"${g(s)}\">${g(s)} ${a.length}</button>`}return o}function Ce(e){if(!e||!e.url)return\"\";let n=g(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=g(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 Le(e,n){let 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||re(n.me))&&(r+='<button class=cm-del-btn title=\"Supprimer\">\\u{1F5D1}</button>'),r}function yn(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>${g(e.reply_name||\"\")}</b> ${g(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>${g(e.author_name||\"Invit\\xE9\")}</b>${s}</div>`+t+o+Ce(e.attachment)+`<div class=cm-re>${Ae(e,n.me)}</div></span><span class=cm-act>${Le(e,n)}</span>`}function vn(e,n,t){return\"cm\"+(_(e,n)?\" me\":\"\")+(e.deleted?\" isdel\":\"\")+(t?\" mentioned\":\"\")}var U={};v(U,{DEFAULT_CENTER:()=>se,DEFAULT_ZOOM:()=>ae,MAP_TYPES:()=>I,cycleMapType:()=>Mn,initialMapContent:()=>_n,mapTypeLabel:()=>xn,sanitizeContent:()=>le});var I=[\"roadmap\",\"satellite\",\"hybrid\"],se=[46.6,2.5],ae=6;function T(e){let n=Number(e);return Number.isFinite(n)?n:null}function oe(e){if(!Array.isArray(e))return null;let n=T(e[0]),t=T(e[1]);return n!=null&&t!=null?[n,t]:null}var 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=oe(n.position);if(!t)return null;let r=n.pov&&typeof n.pov==\"object\"?n.pov:null,o=r?{heading:T(r.heading)||0,pitch:ie(T(r.pitch)||0,-90,90)}:{heading:0,pitch:0};return{kind:\"streetview\",position:t,pov:o,zoom:ie(T(n.zoom)||1,0,5)}}return n.kind!==\"map\"?null:{kind:\"map\",center:oe(n.center)||[...se],zoom:ie(Math.trunc(T(n.zoom)||ae),1,21),marker:oe(n.marker),mapType:I.includes(n.mapType)?n.mapType:null,label:String(n.label||\"\").slice(0,160)||null}}function Mn(e){let n=I.indexOf(e);return I[(n+1)%I.length]}function xn(e){return e===\"roadmap\"?\"\\u{1F6F0} Satellite\":e===\"satellite\"?\"\\u{1F5FA} Hybride\":\"\\u{1F5FA} Plan\"}function _n(){return{kind:\"map\",center:[...se],zoom:ae,marker:null,mapType:null,label:null}}var D={};v(D,{CURRENT_PAGE_MARGIN:()=>Rn,DEFAULT_ASPECT:()=>En,MAX_ZOOM:()=>wn,MIN_PAGE_WIDTH:()=>Sn,MIN_ZOOM:()=>Tn,PRERENDER_MARGIN:()=>kn,arrowState:()=>An,averageColor:()=>Nn,clampPage:()=>Ne,clampZoom:()=>In,fitWidth:()=>Ln,isImageDocument:()=>On,progressPercent:()=>Pn});var Tn=.5,wn=3,En=1.35,Sn=280,kn=\"500px 0px\",Rn=\"-48% 0px -48% 0px\";function In(e){let n=Math.round((Number(e)||0)*10)/10;return Math.max(.5,Math.min(3,n))}function Ne(e,n){return Math.max(1,Math.min(n||1,Number(e)||1))}function Pn(e,n){return n?Math.round(Ne(e,n)/n*100):0}function An(e,n){return{prevDisabled:e<=1,nextDisabled:!!n&&e>=n}}var Cn=28;function Ln(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)-Cn-(e.overlap||0)-(e.reserve||0))/r;return s>120?Math.min(t,s):t}function Nn(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 On(e,n){return/[.](png|jpe?g|webp|gif|avif)($|[?])/i.test(String(e||n||\"\"))}var W={};v(W,{presentationTransition:()=>$n,switchDocUrl:()=>Hn});function $n(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 Hn(e,n){let t=encodeURIComponent(n||String(Date.now()));return`${e}${e.includes(\"?\")?\"&\":\"?\"}v=${t}`}return Fe(Un);})();", SOURCE_HASH: "5d92c853f7d8ba79" };
package/server/handler.js CHANGED
@@ -474,14 +474,36 @@ var Live=(function(){
474
474
  // Et à l'inverse, des signaux un peu plus espacés produisaient une requête chacun, POUR
475
475
  // CHAQUE SPECTATEUR : le canal public devenait un amplificateur vers l'API.
476
476
  // Détail et propriétés : src/live.ts + src/__tests__/ordonnanceur.test.ts. (audit P0-2)
477
+ // ⚠️ LE BUDGET GATE LE SIGNAL, JAMAIS LE FILET — ET C'EST TOUTE LA DIFFÉRENCE.
478
+ //
479
+ // « signaler() » est déclenché par le canal, donc par n'importe quel participant : c'est la
480
+ // porte par laquelle un diffuseur hostile faisait relire toute une salle jusqu'au quota, et
481
+ // au-delà. On la rationne.
482
+ //
483
+ // « maintenant() » est le filet périodique, déclenché par NOUS toutes les 25 s. Le rationner
484
+ // rendrait une audience à budget épuisé définitivement muette — on aurait remplacé un déni de
485
+ // service venu du dehors par un déni de service maison. C'est le plancher : quoi qu'il arrive,
486
+ // l'audience finit toujours par se resynchroniser.
487
+ //
488
+ // Refuser un signal ne perd donc rien : la relecture suivante lira l'état le plus récent. On
489
+ // arrive en retard, jamais à côté.
477
490
  function relireAvec(url,applique){
478
- return Player.live.createScheduler(function(fini){
479
- fetch('/api/doc?present='+encodeURIComponent(SLUG)+url)
491
+ var ord=Player.live.createScheduler(function(fini){
492
+ Player.live.fetchBorne('/api/doc?present='+encodeURIComponent(SLUG)+url)
480
493
  .then(function(r){return r.json();})
481
494
  .then(function(d){if(d&&d.ok)applique(d);})
482
495
  .catch(function(){})
483
496
  .then(fini,fini);
484
497
  },{minMs:400});
498
+ var budget=Player.live.createBudget({
499
+ parHeure:Player.cadence.PRESENT_SIGNAL_BUDGET_PER_HOUR,
500
+ rafale:Player.cadence.PRESENT_READ_BURST,
501
+ });
502
+ return {
503
+ signaler:function(){ if(budget.prendre()) ord.signaler(); },
504
+ maintenant:function(){ ord.maintenant(); },
505
+ arreter:function(){ ord.arreter(); },
506
+ };
485
507
  }
486
508
  var _ordEtat=relireAvec('&state=1',function(d){if(d.state)etatDuServeur(d.state);});
487
509
  // ⚠️ Exposé hors de cette fermeture : la carte vit dans un AUTRE bloc de script et doit
@@ -2054,7 +2076,7 @@ ${LEGAL_CSS}
2054
2076
  // (audit P0-3)
2055
2077
  function diffuserEtat(){ if(!PRES||!window.Live)return; try{ Live.sendState(); }catch(e){} }
2056
2078
  function pushPage(){ if(!PRES)return; clearTimeout(_pushT); _pushT=setTimeout(function(){ if(!PRES)return;
2057
- fetch('/api/doc',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({action:'present-page',slug:PRES.slug,control:PRES.control,page:cur||1})})
2079
+ Player.live.fetchBorne('/api/doc',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({action:'present-page',slug:PRES.slug,control:PRES.control,page:cur||1})})
2058
2080
  .then(function(r){ if(r&&r.ok)diffuserEtat(); })
2059
2081
  .catch(function(){});
2060
2082
  },250); }
@@ -2090,7 +2112,7 @@ ${LEGAL_CSS}
2090
2112
  if(!PRES) return Promise.resolve();
2091
2113
  var p=PRES, btn=document.getElementById('pbarEnd'), libelle=btn?btn.textContent:'';
2092
2114
  if(btn){ btn.disabled=true; btn.textContent='…'; }
2093
- return fetch('/api/doc',{method:'POST',headers:{'Content-Type':'application/json'},keepalive:true,
2115
+ return Player.live.fetchBorne('/api/doc',{method:'POST',headers:{'Content-Type':'application/json'},keepalive:true,
2094
2116
  body:JSON.stringify({action:'present-end',slug:p.slug,control:p.control})})
2095
2117
  .then(function(r){ if(!r||!r.ok) throw new Error('present-end'); })
2096
2118
  .then(function(){
@@ -2151,7 +2173,7 @@ ${LEGAL_CSS}
2151
2173
  // s'appliquait qu'à l'ordre des APPELS, pas à celui des écritures — plusieurs PATCH pouvaient
2152
2174
  // voler ensemble, et une position ancienne atterrir après une récente.
2153
2175
  function presentContent(content){ if(!PRES)return Promise.resolve(); var h={'Content-Type':'application/json'}; var tk=appToken(); if(tk) h['Authorization']='Bearer '+tk;
2154
- return fetch('/api/doc',{method:'POST',headers:h,body:JSON.stringify({action:'present-content',slug:PRES.slug,control:PRES.control,content:content})})
2176
+ return Player.live.fetchBorne('/api/doc',{method:'POST',headers:h,body:JSON.stringify({action:'present-content',slug:PRES.slug,control:PRES.control,content:content})})
2155
2177
  .then(function(r){ if(r&&r.ok)diffuserEtat(); })
2156
2178
  .catch(function(){}); }
2157
2179
  function showMap(){ if(!PRES||!window.Map3DD)return; var wrap=document.getElementById('mapWrap'); if(wrap&&wrap.classList.contains('on')){ Map3DD.enter(null,true,presentContent); return; } var init=Player.presentation.initialMapContent(); presentContent(init); Map3DD.enter(init,true,presentContent); }
@@ -77,6 +77,33 @@ async function listActivePresentations(email) {
77
77
  return live.map((r) => ({ slug: r.slug, docId: r.doc_id, fileUrl: r.file_url, fileName: r.file_name, docTitle: r.doc_title, presenterName: r.presenter_name, ownerName: r.owner_name, ownerAvatar: r.owner_avatar, currentPage: r.current_page || 1, mine: !!(me && r.owner_email && r.owner_email === me), updatedAt: r.updated_at }));
78
78
  }
79
79
 
80
+ /**
81
+ * Terminer une présentation RÉVOQUE son jeton de contrôle.
82
+ *
83
+ * ⚠️ SANS ÇA, « terminer » ne terminait rien de définitif. Les fonctions de pilotage écrivent
84
+ * « active: true » — c'est voulu, on y revient — et le jeton de contrôle survivait à la clôture. Un
85
+ * second onglet resté ouvert, ou une écriture encore en vol, remettait donc la présentation en ligne
86
+ * pour l'audience alors que le présentateur la croyait close. Le jeton est persisté en localStorage :
87
+ * ce n'était pas une course étroite, c'était une porte ouverte à volonté.
88
+ *
89
+ * ⚠️ POURQUOI RÉVOQUER LE JETON PLUTÔT QUE REFUSER TOUTE ÉCRITURE SUR « active=false » — la règle
90
+ * générale que l'audit proposait. Parce que « active:false » recouvre DEUX situations qui n'ont rien
91
+ * à voir :
92
+ *
93
+ * 1. une fin DÉCIDÉE (ces deux chemins) — plus rien ne doit piloter ;
94
+ * 2. une péremption CONSTATÉE (listActivePresentations, 3 min sans battement) — le portable du
95
+ * présentateur a dormi, et sa page suivante doit le remettre en ligne. La « résurrection » EST
96
+ * la reprise.
97
+ *
98
+ * Une règle qui refuse les deux condamnerait un présentateur ANONYME à ne jamais revenir : la
99
+ * reprise exige la propriété, et « present-start » n'exige aucune session. On distingue donc les
100
+ * deux cas par ce qui les sépare vraiment — la décision — plutôt que par l'état qu'ils partagent.
101
+ *
102
+ * Le propriétaire garde « present-reclaim » : reprendre une présentation close est une décision, et
103
+ * elle regénère un jeton frais.
104
+ */
105
+ const CLOTURE = { active: false, control_hash: null };
106
+
80
107
  // Clôture sans control_token → utilisée par le panneau « Présentations en direct » pour terminer à distance
81
108
  // une présentation dont on a perdu l'onglet. Autorisée au PROPRIÉTAIRE (email du JWT) OU à un ADMIN (modération).
82
109
  async function endPresentationByOwner(slug, email, isAdmin) {
@@ -84,7 +111,7 @@ async function endPresentationByOwner(slug, email, isAdmin) {
84
111
  const row = await getPresentation(slug);
85
112
  if (!row) return { ok: false, status: 404 };
86
113
  if (!isAdmin && (!row.owner_email || row.owner_email !== lc(email))) return { ok: false, status: 403 };
87
- await PLAYER.db.request(`doc_presentations?slug=eq.${enc(slug)}`, { method: "PATCH", headers: { Prefer: "return=minimal" }, body: { active: false, updated_at: new Date().toISOString() } });
114
+ await PLAYER.db.request(`doc_presentations?slug=eq.${enc(slug)}`, { method: "PATCH", headers: { Prefer: "return=minimal" }, body: { ...CLOTURE, updated_at: new Date().toISOString() } });
88
115
  return { ok: true };
89
116
  }
90
117
 
@@ -121,7 +148,7 @@ async function endPresentation(slug, control) {
121
148
  const row = await getPresentation(slug);
122
149
  if (!row) return { ok: false, status: 404 };
123
150
  if (!tokenMatches(control, row.control_hash)) return { ok: false, status: 403 };
124
- await PLAYER.db.request(`doc_presentations?slug=eq.${enc(slug)}`, { method: "PATCH", headers: { Prefer: "return=minimal" }, body: { active: false, updated_at: new Date().toISOString() } });
151
+ await PLAYER.db.request(`doc_presentations?slug=eq.${enc(slug)}`, { method: "PATCH", headers: { Prefer: "return=minimal" }, body: { ...CLOTURE, updated_at: new Date().toISOString() } });
125
152
  return { ok: true };
126
153
  }
127
154
 
@@ -349,6 +376,7 @@ async function switchPresentationDoc(slug, email, isAdmin, { fileUrl, fileName,
349
376
  if (!slug || !fileUrl) return { ok: false, status: 400 };
350
377
  const row = await getPresentation(slug);
351
378
  if (!row) return { ok: false, status: 404 };
379
+ if (estClose(row)) return { ok: false, status: 409, error: "ended" };
352
380
  if (!isAdmin && (!row.owner_email || row.owner_email !== lc(email))) return { ok: false, status: 403 };
353
381
  await PLAYER.db.request(`doc_presentations?slug=eq.${enc(slug)}`, { method: "PATCH", headers: { Prefer: "return=minimal" }, body: {
354
382
  file_url: String(fileUrl), file_name: fileName || null, doc_title: docTitle || null, doc_id: docId ? String(docId) : null,
@@ -364,6 +392,22 @@ async function switchPresentationDoc(slug, email, isAdmin, { fileUrl, fileName,
364
392
  // finissaient par diverger, et une audience qui ne voit pas la bonne carte n'émet aucune erreur.
365
393
  const { sanitizeContent } = require("./shared.generated.js");
366
394
 
395
+ /**
396
+ * Une présentation close ne se pilote plus par le chemin PROPRIÉTAIRE.
397
+ *
398
+ * ⚠️ La révocation du jeton ferme le pilotage de qui détient un « control_token ». Elle ne ferme pas
399
+ * les deux actions qu'un propriétaire exerce avec sa seule session — changer le contenu affiché, et
400
+ * changer le document montré. Sans cette garde, celui qui vient de terminer sa présentation la
401
+ * remettrait en ligne en déplaçant une carte.
402
+ *
403
+ * ⚠️ Ici, et pas sur les chemins à jeton : un propriétaire dispose de « present-reclaim » pour
404
+ * rouvrir délibérément, ce qui n'est pas le cas d'un présentateur anonyme dont la présentation a
405
+ * simplement été marquée périmée.
406
+ */
407
+ function estClose(row) {
408
+ return row && row.active === false;
409
+ }
410
+
367
411
  /**
368
412
  * Ce que la présentation AFFICHE : carte, Street View, ou rien.
369
413
  *
@@ -390,6 +434,8 @@ async function setPresentationContent(slug, email, isAdmin, content, control) {
390
434
  const pilote = control && tokenMatches(control, row.control_hash);
391
435
  const proprietaire = isAdmin || (row.owner_email && row.owner_email === lc(email));
392
436
  if (!pilote && !proprietaire) return { ok: false, status: 403 };
437
+ // Le pilote a déjà été filtré par la révocation du jeton ; c'est le propriétaire qu'on arrête ici.
438
+ if (!pilote && estClose(row)) return { ok: false, status: 409, error: "ended" };
393
439
  await PLAYER.db.request(`doc_presentations?slug=eq.${enc(slug)}`, { method: "PATCH", headers: { Prefer: "return=minimal" }, body: { content: sanitizeContent(content), active: true, last_seen: new Date().toISOString(), updated_at: new Date().toISOString() } });
394
440
  return { ok: true };
395
441
  }
@@ -31,7 +31,10 @@ __export(shared_exports, {
31
31
  PRESENTER_ACTIONS_PER_HOUR: () => PRESENTER_ACTIONS_PER_HOUR,
32
32
  PRESENT_QUOTA_PER_HOUR: () => PRESENT_QUOTA_PER_HOUR,
33
33
  PRESENT_READS_PER_HOUR: () => PRESENT_READS_PER_HOUR,
34
+ PRESENT_READ_BURST: () => PRESENT_READ_BURST,
34
35
  PRESENT_RESYNC_MS: () => PRESENT_RESYNC_MS,
36
+ PRESENT_SIGNAL_BUDGET_PER_HOUR: () => PRESENT_SIGNAL_BUDGET_PER_HOUR,
37
+ PRESENT_WRITE_TIMEOUT_MS: () => PRESENT_WRITE_TIMEOUT_MS,
35
38
  READERS_PER_EGRESS: () => READERS_PER_EGRESS,
36
39
  RESYNC_READS_PER_HOUR: () => RESYNC_READS_PER_HOUR,
37
40
  SESSION_IDLE_MS: () => SESSION_IDLE_MS,
@@ -104,7 +107,10 @@ var PRESENT_RESYNC_MS = 25e3;
104
107
  var RESYNC_READS_PER_HOUR = Math.ceil(36e5 / PRESENT_RESYNC_MS);
105
108
  var PRESENTER_ACTIONS_PER_HOUR = 720;
106
109
  var PRESENT_READS_PER_HOUR = RESYNC_READS_PER_HOUR + PRESENTER_ACTIONS_PER_HOUR;
107
- var PRESENT_QUOTA_PER_HOUR = PRESENT_READS_PER_HOUR * READERS_PER_EGRESS;
110
+ var PRESENT_SIGNAL_BUDGET_PER_HOUR = PRESENTER_ACTIONS_PER_HOUR;
111
+ var PRESENT_READ_BURST = 20;
112
+ var PRESENT_QUOTA_PER_HOUR = (PRESENT_READS_PER_HOUR + PRESENT_READ_BURST) * READERS_PER_EGRESS;
113
+ var PRESENT_WRITE_TIMEOUT_MS = 1e4;
108
114
  // Annotate the CommonJS export names for ESM import in node:
109
115
  0 && (module.exports = {
110
116
  DEFAULT_CENTER,
@@ -113,7 +119,10 @@ var PRESENT_QUOTA_PER_HOUR = PRESENT_READS_PER_HOUR * READERS_PER_EGRESS;
113
119
  PRESENTER_ACTIONS_PER_HOUR,
114
120
  PRESENT_QUOTA_PER_HOUR,
115
121
  PRESENT_READS_PER_HOUR,
122
+ PRESENT_READ_BURST,
116
123
  PRESENT_RESYNC_MS,
124
+ PRESENT_SIGNAL_BUDGET_PER_HOUR,
125
+ PRESENT_WRITE_TIMEOUT_MS,
117
126
  READERS_PER_EGRESS,
118
127
  RESYNC_READS_PER_HOUR,
119
128
  SESSION_IDLE_MS,