discovery-media-player 0.1.43 → 0.1.44

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.44",
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,Ne=Object.defineProperties,$e=Object.getOwnPropertyDescriptor,He=Object.getOwnPropertyDescriptors,Ue=Object.getOwnPropertyNames,be=Object.getOwnPropertySymbols;var he=Object.prototype.hasOwnProperty,De=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,K=(e,n)=>{for(var t in n||(n={}))he.call(n,t)&&ye(e,t,n[t]);if(be)for(var t of be(n))De.call(n,t)&&ye(e,t,n[t]);return e},ve=(e,n)=>Ne(e,He(n));var v=(e,n)=>{for(var t in n)C(e,t,{get:n[t],enumerable:!0})},We=(e,n,t,r)=>{if(n&&typeof n==\"object\"||typeof n==\"function\")for(let o of Ue(n))!he.call(e,o)&&o!==t&&C(e,o,{get:()=>n[o],enumerable:!(r=$e(n,o))||r.enumerable});return e};var Ve=e=>We(C({},\"__esModule\",{value:!0}),e);var Nn={};v(Nn,{bridge:()=>L,cadence:()=>O,chat:()=>H,live:()=>$,presentation:()=>U,presentationState:()=>W,tracking:()=>N,viewer:()=>D});var L={};v(L,{onHostMessage:()=>Xe,onPlayerMessage:()=>qe,parseHostMessage:()=>_e,parsePlayerMessage:()=>xe,sendToHost:()=>Be,sendToPlayer:()=>Ye,toWire:()=>Y});var B=\"3dd-doc-\",Fe=/^[A-Za-z0-9_-]{1,64}$/,ze=[\"close\",\"share\",\"embed-ready\",\"present-left\",\"present-denied\"],je=[\"embed-denied\"],Ge=[\"present-invite\",\"present-handover\",\"present-switch\"],Ke=[\"handover-done\"];function Y(e){let n={type:B+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 Me(e,n,t){if(!e||typeof e!=\"object\")return null;let r=e.type;if(typeof r!=\"string\"||!r.startsWith(B))return null;let o=r.slice(B.length);if(n.includes(o))return{type:o};if(je.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\"||!Fe.test(s)?null:{type:o,slug:s}}function xe(e){return Me(e,ze,Ge)}function _e(e){return Me(e,Ke,[])}function Be(e,n){try{let t=n||(typeof window!=\"undefined\"?window.parent:null);t&&t.postMessage(Y(e),\"*\")}catch(t){}}function Ye(e,n){var t;try{(t=e==null?void 0:e.contentWindow)==null||t.postMessage(Y(n),\"*\")}catch(r){}}function Ze(e,n){var r;if(!e)return!0;let t=(r=e.contentWindow)!=null?r:e;return n===t}function qe(e,n,t){let r=n||(typeof window!=\"undefined\"?window:null);if(!r)return()=>{};let o=s=>{if(!Ze(t,s.source))return;let a=xe(s.data);a&&e(a)};return r.addEventListener(\"message\",o),()=>r.removeEventListener(\"message\",o)}function Xe(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=_e(a.data);l&&e(l)};return t.addEventListener(\"message\",o),()=>t.removeEventListener(\"message\",o)}var N={};v(N,{createTracker:()=>rn});var O={};v(O,{PRESENTER_ACTIONS_PER_HOUR:()=>Se,PRESENT_QUOTA_PER_HOUR:()=>Je,PRESENT_READS_PER_HOUR:()=>ke,PRESENT_RESYNC_MS:()=>we,PRESENT_WRITE_TIMEOUT_MS:()=>Q,READERS_PER_EGRESS:()=>q,RESYNC_READS_PER_HOUR:()=>Ee,SESSION_IDLE_MS:()=>X,SESSION_INTERVAL_MS:()=>Z,SESSION_QUOTA_PER_HOUR:()=>Qe,SESSION_WRITES_PER_HOUR:()=>Te});var Z=12e3,Te=Math.ceil(300),q=25,Qe=Te*q,X=18e4,we=25e3,Ee=Math.ceil(36e5/we),Se=720,ke=Ee+Se,Je=ke*q,Q=1e4;var en=[\"mousemove\",\"mousedown\",\"keydown\",\"wheel\",\"touchstart\",\"pointerdown\"];function nn(){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 tn(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 rn(e={}){var fe,me;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=(fe=e.idleMs)!=null?fe:X,a=(me=e.sessionEveryMs)!=null?me:12e3,l=e.slug||\"\",p=e.internal||null,m=e.sessionId||nn(),b=e.send||tn(o,n),f={},u=0,y=0,c=0,M=0,h=null,w=r(),E=!1,V=!1,P=[],Oe=()=>!!l||!!p,le=i=>{if(Oe())try{b(i)}catch(d){}},F=()=>!!t&&t.visibilityState===\"visible\"&&!E,ue=i=>{let d=r()-i,x=Math.max(0,w-i)+s;return Math.max(0,Math.min(d,x))},z=()=>{h!=null&&u>0&&(f[u]=(f[u]||0)+ue(h)/1e3),h=F()?r():null},ce=()=>{z(),h=null},j=()=>{h==null&&F()&&(h=r())},G=()=>{let i=0;for(let d in f)i+=f[d];return h!=null&&(i+=ue(h)/1e3),Math.round(i)},de=()=>{let i={};for(let d in f)i[d]=Math.round(f[d]);return i},S=()=>{w=r(),E&&(E=!1,j())},pe=(i,d)=>{p||le({slug:l,event:i,page:d||y,maxPage:c,seconds:G(),sessionId:m})},A=()=>{z();let i={event:\"session\",sessionId:m,numPages:M,maxPage:c,totalSeconds:G(),pagesTime:de()};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,le(i)},k=(i,d,x,ge)=>{!i||typeof i.addEventListener!=\"function\"||(i.addEventListener(d,x,ge),P.push(()=>{i.removeEventListener(d,x,ge)}))};return{sessionId:m,totalSeconds:G,maxPage:()=>c,pageTimes:de,start(){if(V)return;V=!0,pe(\"open\",1),u=1,w=r(),h=F()?r():null,k(t,\"visibilitychange\",()=>{t.visibilityState===\"hidden\"?(ce(),A()):(S(),j())}),k(n,\"focus\",()=>{S(),j()});for(let x of en)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,ce())},5e3),d=n.setInterval(A,a);P.push(()=>{n.clearInterval(i),n.clearInterval(d)})},setPage(i){!i||i===y||(w=r(),z(),u=i,y=i,i>c&&(c=i,pe(\"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:()=>ln,authorToken:()=>un,avatarHtml:()=>J,canModerate:()=>te,createScheduler:()=>fn,escapeHtml:()=>g,fetchBorne:()=>mn,flattenPresence:()=>on,formatMessageBody:()=>ee,initials:()=>Re,isMentioned:()=>cn,isMine:()=>_,reactorId:()=>ne,shouldNotify:()=>dn,unreadLabel:()=>pn});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 Re(e){let n=(e||\"?\").trim().split(/\\s+/);return(((n[0]||\"\")[0]||\"?\")+((n[1]||\"\")[0]||\"\")).toUpperCase()}function J(e,n){return e?`<img src=\"${g(e)}\" alt=\"\">`:g(Re(n))}function ee(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 on(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 sn(e,n){try{return e?e.getItem(n):null}catch(t){return null}}function an(e,n,t){try{e==null||e.setItem(n,t)}catch(r){}}function ln(e,n=\"anon\",t=Ie){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 Ie(){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 un(e,n=Ie){let t=sn(e,R.authorToken);if(t)return t;let r=n();return an(e,R.authorToken,r),r}function ne(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 te(e){return!!(e&&e.role===\"presenter\")}function cn(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 dn(e){let{msg:n,me:t,historyLoaded:r,chatHidden:o}=e;return!r||!n||n.deleted||_(n,t)?!1:o}function pn(e){return!e||e<=0?\"\":e>9?\"9+\":String(e)}function fn(e,n={}){var y;let t=(y=n.minMs)!=null?y: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 mn(e,n,t=Q,r={}){let o=r.fetch||(typeof fetch==\"function\"?fetch:void 0);if(!o)return Promise.reject(new Error(\"fetch indisponible\"));let s=r.setTimer||((u,y)=>setTimeout(u,y)),a=r.clearTimer||(u=>clearTimeout(u)),l=null;try{l=new AbortController}catch(u){}let p=l?ve(K({},n||{}),{signal:l.signal}):K({},n||{}),m=null,b=new Promise((u,y)=>{m=s(()=>{try{l==null||l.abort()}catch(c){}y(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})}var H={};v(H,{messageClassName:()=>bn,renderActions:()=>Ce,renderAttachment:()=>Ae,renderMessage:()=>gn,renderReactions:()=>Pe});function Pe(e,n){let t=e&&e.reactions||{},r=ne(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 Ae(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 Ce(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||te(n.me))&&(r+='<button class=cm-del-btn title=\"Supprimer\">\\u{1F5D1}</button>'),r}function gn(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>${ee(e.body)}${r}</div>`:\"\",s=e.is_presenter?\" <span class=tag>pr\\xE9sentateur</span>\":\"\";return`<span class=a>${J(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+Ae(e.attachment)+`<div class=cm-re>${Pe(e,n.me)}</div></span><span class=cm-act>${Ce(e,n)}</span>`}function bn(e,n,t){return\"cm\"+(_(e,n)?\" me\":\"\")+(e.deleted?\" isdel\":\"\")+(t?\" mentioned\":\"\")}var U={};v(U,{DEFAULT_CENTER:()=>ie,DEFAULT_ZOOM:()=>se,MAP_TYPES:()=>I,cycleMapType:()=>yn,initialMapContent:()=>vn,mapTypeLabel:()=>hn,sanitizeContent:()=>ae});var I=[\"roadmap\",\"satellite\",\"hybrid\"],ie=[46.6,2.5],se=6;function T(e){let n=Number(e);return Number.isFinite(n)?n:null}function re(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 oe=(e,n,t)=>Math.max(n,Math.min(t,e));function ae(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=re(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:oe(T(r.pitch)||0,-90,90)}:{heading:0,pitch:0};return{kind:\"streetview\",position:t,pov:o,zoom:oe(T(n.zoom)||1,0,5)}}return n.kind!==\"map\"?null:{kind:\"map\",center:re(n.center)||[...ie],zoom:oe(Math.trunc(T(n.zoom)||se),1,21),marker:re(n.marker),mapType:I.includes(n.mapType)?n.mapType:null,label:String(n.label||\"\").slice(0,160)||null}}function yn(e){let n=I.indexOf(e);return I[(n+1)%I.length]}function hn(e){return e===\"roadmap\"?\"\\u{1F6F0} Satellite\":e===\"satellite\"?\"\\u{1F5FA} Hybride\":\"\\u{1F5FA} Plan\"}function vn(){return{kind:\"map\",center:[...ie],zoom:se,marker:null,mapType:null,label:null}}var D={};v(D,{CURRENT_PAGE_MARGIN:()=>En,DEFAULT_ASPECT:()=>_n,MAX_ZOOM:()=>xn,MIN_PAGE_WIDTH:()=>Tn,MIN_ZOOM:()=>Mn,PRERENDER_MARGIN:()=>wn,arrowState:()=>Rn,averageColor:()=>An,clampPage:()=>Le,clampZoom:()=>Sn,fitWidth:()=>Pn,isImageDocument:()=>Cn,progressPercent:()=>kn});var Mn=.5,xn=3,_n=1.35,Tn=280,wn=\"500px 0px\",En=\"-48% 0px -48% 0px\";function Sn(e){let n=Math.round((Number(e)||0)*10)/10;return Math.max(.5,Math.min(3,n))}function Le(e,n){return Math.max(1,Math.min(n||1,Number(e)||1))}function kn(e,n){return n?Math.round(Le(e,n)/n*100):0}function Rn(e,n){return{prevDisabled:e<=1,nextDisabled:!!n&&e>=n}}var In=28;function Pn(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)-In-(e.overlap||0)-(e.reserve||0))/r;return s>120?Math.min(t,s):t}function An(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 Cn(e,n){return/[.](png|jpe?g|webp|gif|avif)($|[?])/i.test(String(e||n||\"\"))}var W={};v(W,{presentationTransition:()=>Ln,switchDocUrl:()=>On});function Ln(e,n={}){if(!e)return[{kind:\"nothing\"}];if(e.active===!1)return[{kind:\"ended\"}];let t=ae(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 On(e,n){let t=encodeURIComponent(n||String(Date.now()));return`${e}${e.includes(\"?\")?\"&\":\"?\"}v=${t}`}return Ve(Nn);})();", SOURCE_HASH: "23da6b6df4f91661" };
package/server/handler.js CHANGED
@@ -474,9 +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
+ // UNE REQUÊTE QUI NE REVIENT PAS BLOQUE TOUT CE QUI L'ATTEND.
478
+ //
479
+ // ⚠️ Ce risque est né d'un correctif. Avant 0.1.41 l'ordonnanceur appelait « fini() » tout de
480
+ // suite : le désordre était possible, le blocage non. En rendant les écritures séquentielles on a
481
+ // échangé un défaut de correction contre un risque de disponibilité — une requête suspendue ne
482
+ // règle jamais sa promesse, la file ne repart pas, et le présentateur pilote dans le vide sans
483
+ // qu'aucun message ne le dise. Un navigateur ne garantit aucun délai de son côté.
484
+ //
485
+ // ⚠️ Ce que ça restaure : la VIVACITÉ. Pas l'ORDRE — une requête abandonnée peut être arrivée au
486
+ // serveur et atterrir après celle qui l'a remplacée. L'ordre malgré un abandon demande un numéro
487
+ // de version porté par l'écriture, et c'est le chantier de la file unique.
488
+ //
489
+ // L'abandon est BEST-EFFORT : sans AbortController on laisse la requête vivre, on cesse seulement
490
+ // de l'attendre. Rendre la main compte plus que couper le fil.
491
+ function fetchBorne(url,opts,ms){
492
+ var limite=ms||(window.Player&&Player.cadence&&Player.cadence.PRESENT_WRITE_TIMEOUT_MS)||10000;
493
+ var ctl=null; try{ ctl=new AbortController(); }catch(e){}
494
+ var o=opts||{};
495
+ if(ctl){ o=Object.assign({},o); o.signal=ctl.signal; }
496
+ var minuteur=null;
497
+ var expiration=new Promise(function(_,ko){
498
+ minuteur=setTimeout(function(){ try{ if(ctl)ctl.abort(); }catch(e){} ko(new Error('delai')); },limite);
499
+ });
500
+ function rendreLaMain(v,echec){ try{ clearTimeout(minuteur); }catch(e){} if(echec)throw v; return v; }
501
+ return Promise.race([fetch(url,o),expiration])
502
+ .then(function(r){return rendreLaMain(r,false);},function(e){return rendreLaMain(e,true);});
503
+ }
477
504
  function relireAvec(url,applique){
478
505
  return Player.live.createScheduler(function(fini){
479
- fetch('/api/doc?present='+encodeURIComponent(SLUG)+url)
506
+ fetchBorne('/api/doc?present='+encodeURIComponent(SLUG)+url)
480
507
  .then(function(r){return r.json();})
481
508
  .then(function(d){if(d&&d.ok)applique(d);})
482
509
  .catch(function(){})
@@ -2054,7 +2081,7 @@ ${LEGAL_CSS}
2054
2081
  // (audit P0-3)
2055
2082
  function diffuserEtat(){ if(!PRES||!window.Live)return; try{ Live.sendState(); }catch(e){} }
2056
2083
  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})})
2084
+ 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
2085
  .then(function(r){ if(r&&r.ok)diffuserEtat(); })
2059
2086
  .catch(function(){});
2060
2087
  },250); }
@@ -2090,7 +2117,7 @@ ${LEGAL_CSS}
2090
2117
  if(!PRES) return Promise.resolve();
2091
2118
  var p=PRES, btn=document.getElementById('pbarEnd'), libelle=btn?btn.textContent:'';
2092
2119
  if(btn){ btn.disabled=true; btn.textContent='…'; }
2093
- return fetch('/api/doc',{method:'POST',headers:{'Content-Type':'application/json'},keepalive:true,
2120
+ return Player.live.fetchBorne('/api/doc',{method:'POST',headers:{'Content-Type':'application/json'},keepalive:true,
2094
2121
  body:JSON.stringify({action:'present-end',slug:p.slug,control:p.control})})
2095
2122
  .then(function(r){ if(!r||!r.ok) throw new Error('present-end'); })
2096
2123
  .then(function(){
@@ -2151,7 +2178,7 @@ ${LEGAL_CSS}
2151
2178
  // s'appliquait qu'à l'ordre des APPELS, pas à celui des écritures — plusieurs PATCH pouvaient
2152
2179
  // voler ensemble, et une position ancienne atterrir après une récente.
2153
2180
  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})})
2181
+ 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
2182
  .then(function(r){ if(r&&r.ok)diffuserEtat(); })
2156
2183
  .catch(function(){}); }
2157
2184
  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
  }
@@ -32,6 +32,7 @@ __export(shared_exports, {
32
32
  PRESENT_QUOTA_PER_HOUR: () => PRESENT_QUOTA_PER_HOUR,
33
33
  PRESENT_READS_PER_HOUR: () => PRESENT_READS_PER_HOUR,
34
34
  PRESENT_RESYNC_MS: () => PRESENT_RESYNC_MS,
35
+ PRESENT_WRITE_TIMEOUT_MS: () => PRESENT_WRITE_TIMEOUT_MS,
35
36
  READERS_PER_EGRESS: () => READERS_PER_EGRESS,
36
37
  RESYNC_READS_PER_HOUR: () => RESYNC_READS_PER_HOUR,
37
38
  SESSION_IDLE_MS: () => SESSION_IDLE_MS,
@@ -105,6 +106,7 @@ var RESYNC_READS_PER_HOUR = Math.ceil(36e5 / PRESENT_RESYNC_MS);
105
106
  var PRESENTER_ACTIONS_PER_HOUR = 720;
106
107
  var PRESENT_READS_PER_HOUR = RESYNC_READS_PER_HOUR + PRESENTER_ACTIONS_PER_HOUR;
107
108
  var PRESENT_QUOTA_PER_HOUR = PRESENT_READS_PER_HOUR * READERS_PER_EGRESS;
109
+ var PRESENT_WRITE_TIMEOUT_MS = 1e4;
108
110
  // Annotate the CommonJS export names for ESM import in node:
109
111
  0 && (module.exports = {
110
112
  DEFAULT_CENTER,
@@ -114,6 +116,7 @@ var PRESENT_QUOTA_PER_HOUR = PRESENT_READS_PER_HOUR * READERS_PER_EGRESS;
114
116
  PRESENT_QUOTA_PER_HOUR,
115
117
  PRESENT_READS_PER_HOUR,
116
118
  PRESENT_RESYNC_MS,
119
+ PRESENT_WRITE_TIMEOUT_MS,
117
120
  READERS_PER_EGRESS,
118
121
  RESYNC_READS_PER_HOUR,
119
122
  SESSION_IDLE_MS,