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 +1 -1
- package/server/browser.generated.js +1 -1
- package/server/handler.js +27 -5
- package/server/presentations.js +48 -2
- package/server/shared.generated.js +10 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "discovery-media-player",
|
|
3
|
-
"version": "0.1.
|
|
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,\"&\").replace(/</g,\"<\").replace(/>/g,\">\").replace(/\"/g,\""\")}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,\"&\").replace(/</g,\"<\").replace(/>/g,\">\").replace(/\"/g,\""\")}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
|
-
|
|
479
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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); }
|
package/server/presentations.js
CHANGED
|
@@ -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: {
|
|
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: {
|
|
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
|
|
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,
|