discovery-media-player 0.1.65 → 0.1.67

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.65",
3
+ "version": "0.1.67",
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",
@@ -77,5 +77,8 @@
77
77
  "typescript": "^5.5.4",
78
78
  "typescript-eslint": "^8.46.4",
79
79
  "vitest": "^4.1.10"
80
+ },
81
+ "dependencies": {
82
+ "pdfjs-dist": "6.2.108"
80
83
  }
81
84
  }
@@ -4,4 +4,4 @@
4
4
  // Injecté par api/doc.js dans un <script nonce=…> de la visionneuse. Le code navigateur du
5
5
  // player vit en modules TypeScript testés sous player/src/ — c'est ici qu'il atterrit.
6
6
  "use strict";
7
- module.exports = { PLAYER_BROWSER_JS: "\"use strict\";var Player=(()=>{var C=Object.defineProperty,He=Object.defineProperties,De=Object.getOwnPropertyDescriptor,We=Object.getOwnPropertyDescriptors,Be=Object.getOwnPropertyNames,he=Object.getOwnPropertySymbols;var we=Object.prototype.hasOwnProperty,Ve=Object.prototype.propertyIsEnumerable;var ve=(e,n,t)=>n in e?C(e,n,{enumerable:!0,configurable:!0,writable:!0,value:t}):e[n]=t,G=(e,n)=>{for(var t in n||(n={}))we.call(n,t)&&ve(e,t,n[t]);if(he)for(var t of he(n))Ve.call(n,t)&&ve(e,t,n[t]);return e},Me=(e,n)=>He(e,We(n));var v=(e,n)=>{for(var t in n)C(e,t,{get:n[t],enumerable:!0})},ze=(e,n,t,r)=>{if(n&&typeof n==\"object\"||typeof n==\"function\")for(let o of Be(n))!we.call(e,o)&&o!==t&&C(e,o,{get:()=>n[o],enumerable:!(r=De(n,o))||r.enumerable});return e};var Fe=e=>ze(C({},\"__esModule\",{value:!0}),e);var Gn={};v(Gn,{bridge:()=>L,cadence:()=>N,chat:()=>$,live:()=>U,presentation:()=>H,presentationState:()=>W,tracking:()=>O,viewer:()=>D});var L={};v(L,{onHostMessage:()=>en,onPlayerMessage:()=>Je,parseHostMessage:()=>_e,parsePlayerMessage:()=>Te,sendToHost:()=>qe,sendToPlayer:()=>Qe,toWire:()=>Y});var K=\"3dd-doc-\",je=/^[A-Za-z0-9_-]{1,64}$/,Ge=[\"close\",\"share\",\"embed-ready\",\"present-left\",\"present-denied\"],Ke=[\"embed-denied\"],Ye=[\"present-invite\",\"present-handover\",\"present-switch\"],Ze=[\"handover-done\"];function Y(e){let n={type:K+e.type};return\"slug\"in e&&e.slug&&(n.slug=e.slug),\"reason\"in e&&e.reason&&(n.reason=String(e.reason).slice(0,40)),n}function xe(e,n,t){if(!e||typeof e!=\"object\")return null;let r=e.type;if(typeof r!=\"string\"||!r.startsWith(K))return null;let o=r.slice(K.length);if(n.includes(o))return{type:o};if(Ke.includes(o)){let a=e.reason;return{type:o,reason:/^[a-z-]{1,40}$/.test(String(a||\"\"))?String(a):\"unknown\"}}if(!t.includes(o))return null;let s=e.slug;return typeof s!=\"string\"||!je.test(s)?null:{type:o,slug:s}}function Te(e){return xe(e,Ge,Ye)}function _e(e){return xe(e,Ze,[])}function qe(e,n){try{let t=n||(typeof window!=\"undefined\"?window.parent:null);t&&t.postMessage(Y(e),\"*\")}catch(t){}}function Qe(e,n){var t;try{(t=e==null?void 0:e.contentWindow)==null||t.postMessage(Y(n),\"*\")}catch(r){}}function Xe(e,n){var r;if(!e)return!0;let t=(r=e.contentWindow)!=null?r:e;return n===t}function Je(e,n,t){let r=n||(typeof window!=\"undefined\"?window:null);if(!r)return()=>{};let o=s=>{if(!Xe(t,s.source))return;let a=Te(s.data);a&&e(a)};return r.addEventListener(\"message\",o),()=>r.removeEventListener(\"message\",o)}function en(e,n){var s;let t=n||(typeof window!=\"undefined\"?window:null);if(!t)return()=>{};let r=(s=t.parent)!=null?s:null,o=a=>{if(!r||r===t||a.source!==r)return;let u=_e(a.data);u&&e(u)};return t.addEventListener(\"message\",o),()=>t.removeEventListener(\"message\",o)}var O={};v(O,{createTracker:()=>ln});var N={};v(N,{PRESENTER_ACTIONS_PER_HOUR:()=>X,PRESENT_CACHE_MS:()=>rn,PRESENT_QUOTA_MARGIN:()=>Ie,PRESENT_QUOTA_PER_HOUR:()=>on,PRESENT_READS_PER_HOUR:()=>Re,PRESENT_READ_BURST:()=>Pe,PRESENT_READ_COALESCE_MS:()=>Ae,PRESENT_RESYNC_MS:()=>Se,PRESENT_SIGNAL_BUDGET_PER_HOUR:()=>tn,PRESENT_WRITE_TIMEOUT_MS:()=>J,READERS_PER_EGRESS:()=>q,RESYNC_READS_PER_HOUR:()=>ke,SESSION_IDLE_MS:()=>Q,SESSION_INTERVAL_MS:()=>Z,SESSION_QUOTA_PER_HOUR:()=>nn,SESSION_WRITES_PER_HOUR:()=>Ee});var Z=12e3,Ee=Math.ceil(300),q=25,nn=Ee*q,Q=18e4,Se=25e3,ke=Math.ceil(36e5/Se),X=720,Re=ke+X,tn=X,Pe=20,J=1e4,Ae=400,rn=Ae,Ie=4,on=(Re+Pe)*q*Ie;var sn=[\"mousemove\",\"mousedown\",\"keydown\",\"wheel\",\"touchstart\",\"pointerdown\"];function an(){let e=typeof globalThis!=\"undefined\"?globalThis.crypto:void 0;if(e&&typeof e.randomUUID==\"function\")return e.randomUUID().replace(/-/g,\"\");if(e&&typeof e.getRandomValues==\"function\"){let n=e.getRandomValues(new Uint8Array(16));return Array.from(n,t=>t.toString(16).padStart(2,\"0\")).join(\"\")}return Math.random().toString(36).slice(2)+Date.now().toString(36)}function un(e,n){return t=>{try{let r=JSON.stringify(t),o=n.navigator;o&&typeof o.sendBeacon==\"function\"?o.sendBeacon(e,new Blob([r],{type:\"application/json\"})):n.fetch(e,{method:\"POST\",headers:{\"Content-Type\":\"application/json\"},body:r,keepalive:!0})}catch(r){}}}function ln(e={}){var ge,be;let n=e.win||(typeof window!=\"undefined\"?window:null),t=e.doc||n&&n.document,r=e.now||(()=>Date.now()),o=e.endpoint||\"/api/doc\",s=(ge=e.idleMs)!=null?ge:Q,a=(be=e.sessionEveryMs)!=null?be:12e3,u=e.slug||\"\",d=e.internal||null,m=e.sessionId||an(),p=e.send||un(o,n),c={},l=0,g=0,f=0,w=0,h=null,E=r(),S=!1,B=!1,A=[],$e=()=>!!u||!!d,ce=i=>{if($e())try{p(i)}catch(b){}},V=()=>!!t&&t.visibilityState===\"visible\"&&!S,de=i=>{let b=r()-i,x=Math.max(0,E-i)+s;return Math.max(0,Math.min(b,x))},z=()=>{h!=null&&l>0&&(c[l]=(c[l]||0)+de(h)/1e3),h=V()?r():null},pe=()=>{z(),h=null},F=()=>{h==null&&V()&&(h=r())},j=()=>{let i=0;for(let b in c)i+=c[b];return h!=null&&(i+=de(h)/1e3),Math.round(i)},fe=()=>{let i={};for(let b in c)i[b]=Math.round(c[b]);return i},k=()=>{E=r(),S&&(S=!1,F())},me=(i,b)=>{d||ce({slug:u,event:i,page:b||g,maxPage:f,seconds:j(),sessionId:m})},I=()=>{z();let i={event:\"session\",sessionId:m,numPages:w,maxPage:f,totalSeconds:j(),pagesTime:fe()};d?(i.internal=!0,i.docId=d.docId,i.email=d.email,i.name=d.name,d.it&&(i.it=d.it)):i.slug=u,ce(i)},R=(i,b,x,ye)=>{!i||typeof i.addEventListener!=\"function\"||(i.addEventListener(b,x,ye),A.push(()=>{i.removeEventListener(b,x,ye)}))};return{sessionId:m,totalSeconds:j,maxPage:()=>f,pageTimes:fe,start(){if(B)return;B=!0,me(\"open\",1),l=1,E=r(),h=V()?r():null,R(t,\"visibilitychange\",()=>{t.visibilityState===\"hidden\"?(pe(),I()):(k(),F())}),R(n,\"focus\",()=>{k(),F()});for(let x of sn)R(n,x,k,{passive:!0});R(e.scrollElement,\"scroll\",k,{passive:!0}),R(n,\"beforeunload\",I);let i=n.setInterval(()=>{!S&&r()-E>s&&(S=!0,pe())},5e3),b=n.setInterval(I,a);A.push(()=>{n.clearInterval(i),n.clearInterval(b)})},setPage(i){!i||i===g||(E=r(),z(),l=i,g=i,i>f&&(f=i,me(\"page\",i)))},setPageCount(i){w=i||0},noteActivity:k,flush:I,stop(){for(;A.length;){let i=A.pop();try{i==null||i()}catch(b){}}B=!1}}}var U={};v(U,{STORAGE_KEYS:()=>M,attendeeKey:()=>fn,authorToken:()=>mn,avatarHtml:()=>ee,canModerate:()=>oe,createBudget:()=>Mn,createFileEcritures:()=>xn,createScheduler:()=>vn,escapeHtml:()=>y,fetchBorne:()=>wn,flattenPresence:()=>cn,formatMessageBody:()=>ne,initials:()=>Ce,isMentioned:()=>bn,isMine:()=>T,presenceId:()=>Tn,reactorId:()=>re,referenceAuteur:()=>gn,shouldNotify:()=>yn,unreadLabel:()=>hn});var M={attendeeKey:\"3dd-present-attkey\",authorToken:\"3dd-present-authtoken\",chatMuted:\"3dd-present-mute\",presenceId:\"3dd-present-uid\"};function y(e){return String(e==null?\"\":e).replace(/&/g,\"&amp;\").replace(/</g,\"&lt;\").replace(/>/g,\"&gt;\").replace(/\"/g,\"&quot;\")}function Ce(e){let n=(e||\"?\").trim().split(/\\s+/);return(((n[0]||\"\")[0]||\"?\")+((n[1]||\"\")[0]||\"\")).toUpperCase()}function ee(e,n){return e?`<img src=\"${y(e)}\" alt=\"\">`:y(Ce(n))}function ne(e){let n=y(e);return n=n.replace(/(https?:\\/\\/[^\\s<]+)/g,t=>`<a href=\"${t}\" target=_blank rel=noreferrer class=cm-link>${t}</a>`),n=n.replace(/(^|\\s)@([\\p{L}0-9_'.-]+)/gu,(t,r,o)=>`${r}<span class=cm-mention>@${o}</span>`),n}function cn(e){let n=new Map;for(let t in e||{})for(let r of(e||{})[t]||[]){let o=String(r.uid||r.email||r.name||\"\").toLowerCase()||`_${t}_${n.size}`;(!n.get(o)||r.role===\"presenter\")&&n.set(o,r)}return[...n.values()]}function dn(e,n){try{return e?e.getItem(n):null}catch(t){return null}}function pn(e,n,t){try{e==null||e.setItem(n,t)}catch(r){}}function fn(e,n=\"anon\",t=te){try{if(!e)return`anon-${n}`;let r=e.getItem(M.attendeeKey);if(r)return r;let o=`anon-${t()}`;return e.setItem(M.attendeeKey,o),o}catch(r){return`anon-${n}`}}function te(){let e=typeof globalThis!=\"undefined\"?globalThis.crypto:void 0;if(e&&typeof e.randomUUID==\"function\")return e.randomUUID().replace(/-/g,\"\");if(e&&typeof e.getRandomValues==\"function\"){let n=e.getRandomValues(new Uint8Array(24));return Array.from(n,t=>t.toString(16).padStart(2,\"0\")).join(\"\")}try{console.warn(\"[player] crypto indisponible : jeton d'auteur affaibli\")}catch(n){}return Math.random().toString(36).slice(2)+Math.random().toString(36).slice(2)}function mn(e,n=te){let t=dn(e,M.authorToken);if(t)return t;let r=n();return pn(e,M.authorToken,r),r}async function gn(e,n){let t=n!==void 0?n:typeof globalThis!=\"undefined\"&&globalThis.crypto?globalThis.crypto.subtle:null;if(!t||!e)return\"\";try{let r=async o=>{let s=await t.digest(\"SHA-256\",new TextEncoder().encode(o));return Array.from(new Uint8Array(s),a=>a.toString(16).padStart(2,\"0\")).join(\"\")};return(await r(\"ref:\"+await r(e))).slice(0,16)}catch(r){return\"\"}}function re(e){return String(e&&e.ref||\"\")}function T(e,n){return!e||!n||!e.author_ref||!n.ref?!1:e.author_ref===n.ref}function oe(e){return!!(e&&e.role===\"presenter\")}function bn(e,n){if(!n||!n.name)return!1;let t=(n.name.split(/\\s+/)[0]||\"\").toLowerCase();return t?String(e&&e.body||\"\").toLowerCase().includes(`@${t}`)&&!T(e,n):!1}function yn(e){let{msg:n,me:t,historyLoaded:r,chatHidden:o}=e;return!r||!n||n.deleted||T(n,t)?!1:o}function hn(e){return!e||e<=0?\"\":e>9?\"9+\":String(e)}function vn(e,n={}){var g;let t=(g=n.minMs)!=null?g:300,r=n.now||(()=>Date.now()),o=n.setTimer||((f,w)=>setTimeout(f,w)),s=n.clearTimer||(f=>clearTimeout(f)),a=!1,u=!1,d=-1/0,m=null,p=!1;function c(){if(p||a){u=!0;return}let f=t-(r()-d);if(f>0){l(f);return}a=!0,u=!1,d=r();let w=!1;e(()=>{w||(w=!0,a=!1,u&&!p&&c())})}function l(f){p||m!==null||(m=o(()=>{m=null,c()},Math.max(0,f)))}return{signaler(){p||c()},maintenant(){p||(d=-1/0,c())},arreter(){p=!0,m!==null&&(s(m),m=null)}}}function wn(e,n,t=J,r={}){let o=r.fetch||(typeof fetch==\"function\"?fetch:void 0);if(!o)return Promise.reject(new Error(\"fetch indisponible\"));let s=r.setTimer||((l,g)=>setTimeout(l,g)),a=r.clearTimer||(l=>clearTimeout(l)),u=null;try{u=new AbortController}catch(l){}let d=u?Me(G({},n||{}),{signal:u.signal}):G({},n||{}),m=null,p=new Promise((l,g)=>{m=s(()=>{try{u==null||u.abort()}catch(f){}g(new Error(\"delai\"))},t)}),c=()=>{try{a(m)}catch(l){}};return Promise.race([o(e,d),p]).then(l=>(c(),l),l=>{throw c(),l})}function Mn(e){let n=e.now||(()=>Date.now()),t=Math.max(1,e.rafale),r=t,o=n();return{prendre(){let s=n();return r=Math.min(t,r+(s-o)*e.parHeure/36e5),o=s,r<1?!1:(r-=1,!0)},restants:()=>r}}function xn(e={}){var m;let n=(m=e.minMs)!=null?m:0,t=e.now||(()=>Date.now()),r=e.setTimer||((p,c)=>setTimeout(p,c)),o=new Map,s=!1,a=-1/0,u=!1;function d(){if(s||o.size===0)return;let p=n-(t()-a);if(p>0){if(u)return;u=!0,r(()=>{u=!1,d()},p);return}let c=o.keys().next().value,l=o.get(c);o.delete(c),s=!0,a=t(),Promise.resolve().then(l.tache).then(g=>l.regler(g),()=>l.regler(null)).then(()=>{s=!1,d()})}return{poser(p,c){return new Promise(l=>{let g=o.get(p);o.set(p,{tache:c,regler:f=>{g&&g.regler(f),l(f)}}),d()})},enAttente:()=>o.size}}function Tn(e,n=te){try{if(e){let t=e.getItem(M.presenceId);if(t)return t;let r=`p-${n()}`;return e.setItem(M.presenceId,r),r}}catch(t){}return`p-${n()}`}var $={};v($,{messageClassName:()=>En,renderActions:()=>Oe,renderAttachment:()=>Ne,renderMessage:()=>_n,renderReactions:()=>Le});function Le(e,n){let t=e&&e.reactions||{},r=re(n),o=\"\";for(let s in t){let a=t[s]||[];if(!a.length)continue;let u=a.indexOf(r)>=0;o+=`<button class=\"re-chip${u?\" on\":\"\"}\" data-e=\"${y(s)}\">${y(s)} ${a.length}</button>`}return o}function Ne(e){if(!e||!e.url)return\"\";let n=y(e.url);if(e.kind===\"image\")return`<a class=cm-att href=\"${n}\" target=_blank rel=noreferrer><img src=\"${n}\" alt=\"\" loading=lazy></a>`;let t=y(e.name||\"Document.pdf\");return`<a class=cm-att-pdf href=\"${n}\" target=_blank rel=noreferrer data-pdf=\"${n}\"><span class=cm-att-ph></span><span class=cm-pdflabel>${t}</span></a>`}function Oe(e,n){let t=T(e,n.me),r=`<button class=cm-react title=\"R\\xE9agir\">${n.reactIcon||\"\"}</button><button class=cm-reply title=\"R\\xE9pondre\">\\u21A9</button>`;return t&&(r+='<button class=cm-edit title=\"Modifier\">\\u270E</button>'),(t||oe(n.me))&&(r+='<button class=cm-del-btn title=\"Supprimer\">\\u{1F5D1}</button>'),r}function _n(e,n){if(e.deleted)return'<span class=a></span><span class=b><div class=\"txt cm-del\">Message supprim\\xE9</div></span>';let t=e.reply_to?`<div class=cm-q><b>${y(e.reply_name||\"\")}</b> ${y(e.reply_text||\"\")}</div>`:\"\",r=e.edited?\" <span class=cm-ed>(modifi\\xE9)</span>\":\"\",o=e.body?`<div class=txt>${ne(e.body)}${r}</div>`:\"\",s=e.is_presenter?\" <span class=tag>pr\\xE9sentateur</span>\":\"\";return`<span class=a>${ee(e.author_avatar,e.author_name)}</span><span class=b><div class=who><b>${y(e.author_name||\"Invit\\xE9\")}</b>${s}</div>`+t+o+Ne(e.attachment)+`<div class=cm-re>${Le(e,n.me)}</div></span><span class=cm-act>${Oe(e,n)}</span>`}function En(e,n,t){return\"cm\"+(T(e,n)?\" me\":\"\")+(e.deleted?\" isdel\":\"\")+(t?\" mentioned\":\"\")}var H={};v(H,{DEFAULT_CENTER:()=>ae,DEFAULT_ZOOM:()=>ue,MAP_TYPES:()=>P,cycleMapType:()=>Sn,initialMapContent:()=>Rn,mapTypeLabel:()=>kn,sanitizeContent:()=>le});var P=[\"roadmap\",\"satellite\",\"hybrid\"],ae=[46.6,2.5],ue=6;function _(e){let n=Number(e);return Number.isFinite(n)?n:null}function se(e){if(!Array.isArray(e))return null;let n=_(e[0]),t=_(e[1]);return n!=null&&t!=null?[n,t]:null}var ie=(e,n,t)=>Math.max(n,Math.min(t,e));function le(e){if(!e||typeof e!=\"object\")return null;let n=e;if(n.kind===\"pdf\"||n.kind==null)return null;if(n.kind===\"streetview\"){let t=se(n.position);if(!t)return null;let r=n.pov&&typeof n.pov==\"object\"?n.pov:null,o=r?{heading:_(r.heading)||0,pitch:ie(_(r.pitch)||0,-90,90)}:{heading:0,pitch:0};return{kind:\"streetview\",position:t,pov:o,zoom:ie(_(n.zoom)||1,0,5)}}return n.kind!==\"map\"?null:{kind:\"map\",center:se(n.center)||[...ae],zoom:ie(Math.trunc(_(n.zoom)||ue),1,21),marker:se(n.marker),mapType:P.includes(n.mapType)?n.mapType:null,label:String(n.label||\"\").slice(0,160)||null}}function Sn(e){let n=P.indexOf(e);return P[(n+1)%P.length]}function kn(e){return e===\"roadmap\"?\"\\u{1F6F0} Satellite\":e===\"satellite\"?\"\\u{1F5FA} Hybride\":\"\\u{1F5FA} Plan\"}function Rn(){return{kind:\"map\",center:[...ae],zoom:ue,marker:null,mapType:null,label:null}}var D={};v(D,{CURRENT_PAGE_MARGIN:()=>Nn,DEFAULT_ASPECT:()=>In,MAX_ZOOM:()=>An,MIN_PAGE_WIDTH:()=>Cn,MIN_ZOOM:()=>Pn,PRERENDER_MARGIN:()=>Ln,arrowState:()=>$n,averageColor:()=>Wn,clampPage:()=>Ue,clampZoom:()=>On,fitWidth:()=>Dn,isImageDocument:()=>Bn,progressPercent:()=>Un,workerBlobUrl:()=>Vn});var Pn=.5,An=3,In=1.35,Cn=280,Ln=\"500px 0px\",Nn=\"-48% 0px -48% 0px\";function On(e){let n=Math.round((Number(e)||0)*10)/10;return Math.max(.5,Math.min(3,n))}function Ue(e,n){return Math.max(1,Math.min(n||1,Number(e)||1))}function Un(e,n){return n?Math.round(Ue(e,n)/n*100):0}function $n(e,n){return{prevDisabled:e<=1,nextDisabled:!!n&&e>=n}}var Hn=28;function Dn(e){var a;let n=(a=e.zoom)!=null?a:1,t=Math.max(280,e.containerWidth||900)*n;if(!e.onePage)return t;let r=e.aspect||1.35,s=((e.containerHeight||600)-Hn-(e.overlap||0)-(e.reserve||0))/r;return s>120?Math.min(t,s):t}function Wn(e){let n=e.length/4;if(!n)return\"rgb(0,0,0)\";let t=0,r=0,o=0;for(let s=0;s<e.length;s+=4)t+=e[s],r+=e[s+1],o+=e[s+2];return`rgb(${Math.round(t/n)},${Math.round(r/n)},${Math.round(o/n)})`}function Bn(e,n){return/[.](png|jpe?g|webp|gif|avif)($|[?])/i.test(String(e||n||\"\"))}async function Vn(e,n,t={}){let r=t.fetch||(typeof fetch==\"function\"?(a=>fetch(a)):null),o=t.subtle!==void 0?t.subtle:typeof globalThis!=\"undefined\"&&globalThis.crypto?globalThis.crypto.subtle:null,s=t.creerUrl||(a=>URL.createObjectURL(a));if(!r||!o||!n)return null;try{let a=await r(e);if(!a.ok)return null;let u=await a.arrayBuffer(),d=await o.digest(\"SHA-384\",u);return`sha384-${zn(new Uint8Array(d))}`!==n?null:s(new Blob([u],{type:\"application/javascript\"}))}catch(a){return null}}function zn(e){let n=\"\";for(let t of e)n+=String.fromCharCode(t);return btoa(n)}var W={};v(W,{presentationTransition:()=>Fn,switchDocUrl:()=>jn});function Fn(e,n={}){if(!e)return[{kind:\"nothing\"}];if(e.active===!1)return[{kind:\"ended\"}];let t=le(e.content);if(t)return[{kind:\"show-map\",content:t}];let r=[{kind:\"leave-map\"}];return e.file_url&&e.file_url!==n.docUrl?(r.push({kind:\"switch-doc\",url:e.file_url,name:e.file_name||void 0,title:e.doc_title||void 0,updatedAt:e.updated_at||void 0}),r):(e.current_page&&r.push({kind:\"show-page\",page:e.current_page}),r)}function jn(e,n){let t=encodeURIComponent(n||String(Date.now()));return`${e}${e.includes(\"?\")?\"&\":\"?\"}v=${t}`}return Fe(Gn);})();", SOURCE_HASH: "23c99a1e3e559f8e" };
7
+ module.exports = { PLAYER_BROWSER_JS: "\"use strict\";var Player=(()=>{var C=Object.defineProperty,Ue=Object.defineProperties,De=Object.getOwnPropertyDescriptor,We=Object.getOwnPropertyDescriptors,Ve=Object.getOwnPropertyNames,he=Object.getOwnPropertySymbols;var Me=Object.prototype.hasOwnProperty,ze=Object.prototype.propertyIsEnumerable;var ve=(e,n,t)=>n in e?C(e,n,{enumerable:!0,configurable:!0,writable:!0,value:t}):e[n]=t,G=(e,n)=>{for(var t in n||(n={}))Me.call(n,t)&&ve(e,t,n[t]);if(he)for(var t of he(n))ze.call(n,t)&&ve(e,t,n[t]);return e},we=(e,n)=>Ue(e,We(n));var v=(e,n)=>{for(var t in n)C(e,t,{get:n[t],enumerable:!0})},Fe=(e,n,t,r)=>{if(n&&typeof n==\"object\"||typeof n==\"function\")for(let o of Ve(n))!Me.call(e,o)&&o!==t&&C(e,o,{get:()=>n[o],enumerable:!(r=De(n,o))||r.enumerable});return e};var Be=e=>Fe(C({},\"__esModule\",{value:!0}),e);var Bn={};v(Bn,{bridge:()=>N,cadence:()=>L,chat:()=>H,live:()=>$,presentation:()=>U,presentationState:()=>W,tracking:()=>O,viewer:()=>D});var N={};v(N,{onHostMessage:()=>en,onPlayerMessage:()=>Je,parseHostMessage:()=>_e,parsePlayerMessage:()=>Te,sendToHost:()=>qe,sendToPlayer:()=>Qe,toWire:()=>Y});var K=\"3dd-doc-\",je=/^[A-Za-z0-9_-]{1,64}$/,Ge=[\"close\",\"share\",\"embed-ready\",\"present-left\",\"present-denied\"],Ke=[\"embed-denied\"],Ye=[\"present-invite\",\"present-handover\",\"present-switch\"],Ze=[\"handover-done\"];function Y(e){let n={type:K+e.type};return\"slug\"in e&&e.slug&&(n.slug=e.slug),\"reason\"in e&&e.reason&&(n.reason=String(e.reason).slice(0,40)),n}function xe(e,n,t){if(!e||typeof e!=\"object\")return null;let r=e.type;if(typeof r!=\"string\"||!r.startsWith(K))return null;let o=r.slice(K.length);if(n.includes(o))return{type:o};if(Ke.includes(o)){let a=e.reason;return{type:o,reason:/^[a-z-]{1,40}$/.test(String(a||\"\"))?String(a):\"unknown\"}}if(!t.includes(o))return null;let i=e.slug;return typeof i!=\"string\"||!je.test(i)?null:{type:o,slug:i}}function Te(e){return xe(e,Ge,Ye)}function _e(e){return xe(e,Ze,[])}function qe(e,n){try{let t=n||(typeof window!=\"undefined\"?window.parent:null);t&&t.postMessage(Y(e),\"*\")}catch(t){}}function Qe(e,n){var t;try{(t=e==null?void 0:e.contentWindow)==null||t.postMessage(Y(n),\"*\")}catch(r){}}function Xe(e,n){var r;if(!e)return!0;let t=(r=e.contentWindow)!=null?r:e;return n===t}function Je(e,n,t){let r=n||(typeof window!=\"undefined\"?window:null);if(!r)return()=>{};let o=i=>{if(!Xe(t,i.source))return;let a=Te(i.data);a&&e(a)};return r.addEventListener(\"message\",o),()=>r.removeEventListener(\"message\",o)}function en(e,n){var i;let t=n||(typeof window!=\"undefined\"?window:null);if(!t)return()=>{};let r=(i=t.parent)!=null?i:null,o=a=>{if(!r||r===t||a.source!==r)return;let l=_e(a.data);l&&e(l)};return t.addEventListener(\"message\",o),()=>t.removeEventListener(\"message\",o)}var O={};v(O,{createTracker:()=>ln});var L={};v(L,{PRESENTER_ACTIONS_PER_HOUR:()=>X,PRESENT_CACHE_MS:()=>rn,PRESENT_QUOTA_MARGIN:()=>Ae,PRESENT_QUOTA_PER_HOUR:()=>on,PRESENT_READS_PER_HOUR:()=>Re,PRESENT_READ_BURST:()=>Pe,PRESENT_READ_COALESCE_MS:()=>Ie,PRESENT_RESYNC_MS:()=>Se,PRESENT_SIGNAL_BUDGET_PER_HOUR:()=>tn,PRESENT_WRITE_TIMEOUT_MS:()=>J,READERS_PER_EGRESS:()=>q,RESYNC_READS_PER_HOUR:()=>ke,SESSION_IDLE_MS:()=>Q,SESSION_INTERVAL_MS:()=>Z,SESSION_QUOTA_PER_HOUR:()=>nn,SESSION_WRITES_PER_HOUR:()=>Ee});var Z=12e3,Ee=Math.ceil(300),q=25,nn=Ee*q,Q=18e4,Se=25e3,ke=Math.ceil(36e5/Se),X=720,Re=ke+X,tn=X,Pe=20,J=1e4,Ie=400,rn=Ie,Ae=4,on=(Re+Pe)*q*Ae;var sn=[\"mousemove\",\"mousedown\",\"keydown\",\"wheel\",\"touchstart\",\"pointerdown\"];function an(){let e=typeof globalThis!=\"undefined\"?globalThis.crypto:void 0;if(e&&typeof e.randomUUID==\"function\")return e.randomUUID().replace(/-/g,\"\");if(e&&typeof e.getRandomValues==\"function\"){let n=e.getRandomValues(new Uint8Array(16));return Array.from(n,t=>t.toString(16).padStart(2,\"0\")).join(\"\")}return Math.random().toString(36).slice(2)+Date.now().toString(36)}function un(e,n){return t=>{try{let r=JSON.stringify(t),o=n.navigator;o&&typeof o.sendBeacon==\"function\"?o.sendBeacon(e,new Blob([r],{type:\"application/json\"})):n.fetch(e,{method:\"POST\",headers:{\"Content-Type\":\"application/json\"},body:r,keepalive:!0})}catch(r){}}}function ln(e={}){var ge,be;let n=e.win||(typeof window!=\"undefined\"?window:null),t=e.doc||n&&n.document,r=e.now||(()=>Date.now()),o=e.endpoint||\"/api/doc\",i=(ge=e.idleMs)!=null?ge:Q,a=(be=e.sessionEveryMs)!=null?be:12e3,l=e.slug||\"\",d=e.internal||null,m=e.sessionId||an(),p=e.send||un(o,n),c={},u=0,g=0,f=0,M=0,h=null,E=r(),S=!1,V=!1,I=[],He=()=>!!l||!!d,ce=s=>{if(He())try{p(s)}catch(b){}},z=()=>!!t&&t.visibilityState===\"visible\"&&!S,de=s=>{let b=r()-s,x=Math.max(0,E-s)+i;return Math.max(0,Math.min(b,x))},F=()=>{h!=null&&u>0&&(c[u]=(c[u]||0)+de(h)/1e3),h=z()?r():null},pe=()=>{F(),h=null},B=()=>{h==null&&z()&&(h=r())},j=()=>{let s=0;for(let b in c)s+=c[b];return h!=null&&(s+=de(h)/1e3),Math.round(s)},fe=()=>{let s={};for(let b in c)s[b]=Math.round(c[b]);return s},k=()=>{E=r(),S&&(S=!1,B())},me=(s,b)=>{d||ce({slug:l,event:s,page:b||g,maxPage:f,seconds:j(),sessionId:m})},A=()=>{F();let s={event:\"session\",sessionId:m,numPages:M,maxPage:f,totalSeconds:j(),pagesTime:fe()};d?(s.internal=!0,s.docId=d.docId,s.email=d.email,s.name=d.name,d.it&&(s.it=d.it)):s.slug=l,ce(s)},R=(s,b,x,ye)=>{!s||typeof s.addEventListener!=\"function\"||(s.addEventListener(b,x,ye),I.push(()=>{s.removeEventListener(b,x,ye)}))};return{sessionId:m,totalSeconds:j,maxPage:()=>f,pageTimes:fe,start(){if(V)return;V=!0,me(\"open\",1),u=1,E=r(),h=z()?r():null,R(t,\"visibilitychange\",()=>{t.visibilityState===\"hidden\"?(pe(),A()):(k(),B())}),R(n,\"focus\",()=>{k(),B()});for(let x of sn)R(n,x,k,{passive:!0});R(e.scrollElement,\"scroll\",k,{passive:!0}),R(n,\"beforeunload\",A);let s=n.setInterval(()=>{!S&&r()-E>i&&(S=!0,pe())},5e3),b=n.setInterval(A,a);I.push(()=>{n.clearInterval(s),n.clearInterval(b)})},setPage(s){!s||s===g||(E=r(),F(),u=s,g=s,s>f&&(f=s,me(\"page\",s)))},setPageCount(s){M=s||0},noteActivity:k,flush:A,stop(){for(;I.length;){let s=I.pop();try{s==null||s()}catch(b){}}V=!1}}}var $={};v($,{STORAGE_KEYS:()=>w,attendeeKey:()=>fn,authorToken:()=>mn,avatarHtml:()=>ee,canModerate:()=>oe,createBudget:()=>wn,createFileEcritures:()=>xn,createScheduler:()=>vn,escapeHtml:()=>y,fetchBorne:()=>Mn,flattenPresence:()=>cn,formatMessageBody:()=>ne,initials:()=>Ce,isMentioned:()=>bn,isMine:()=>T,presenceId:()=>Tn,reactorId:()=>re,referenceAuteur:()=>gn,shouldNotify:()=>yn,unreadLabel:()=>hn});var w={attendeeKey:\"3dd-present-attkey\",authorToken:\"3dd-present-authtoken\",chatMuted:\"3dd-present-mute\",presenceId:\"3dd-present-uid\"};function y(e){return String(e==null?\"\":e).replace(/&/g,\"&amp;\").replace(/</g,\"&lt;\").replace(/>/g,\"&gt;\").replace(/\"/g,\"&quot;\")}function Ce(e){let n=(e||\"?\").trim().split(/\\s+/);return(((n[0]||\"\")[0]||\"?\")+((n[1]||\"\")[0]||\"\")).toUpperCase()}function ee(e,n){return e?`<img src=\"${y(e)}\" alt=\"\">`:y(Ce(n))}function ne(e){let n=y(e);return n=n.replace(/(https?:\\/\\/[^\\s<]+)/g,t=>`<a href=\"${t}\" target=_blank rel=noreferrer class=cm-link>${t}</a>`),n=n.replace(/(^|\\s)@([\\p{L}0-9_'.-]+)/gu,(t,r,o)=>`${r}<span class=cm-mention>@${o}</span>`),n}function cn(e){let n=new Map;for(let t in e||{})for(let r of(e||{})[t]||[]){let o=String(r.uid||r.email||r.name||\"\").toLowerCase()||`_${t}_${n.size}`;(!n.get(o)||r.role===\"presenter\")&&n.set(o,r)}return[...n.values()]}function dn(e,n){try{return e?e.getItem(n):null}catch(t){return null}}function pn(e,n,t){try{e==null||e.setItem(n,t)}catch(r){}}function fn(e,n=\"anon\",t=te){try{if(!e)return`anon-${n}`;let r=e.getItem(w.attendeeKey);if(r)return r;let o=`anon-${t()}`;return e.setItem(w.attendeeKey,o),o}catch(r){return`anon-${n}`}}function te(){let e=typeof globalThis!=\"undefined\"?globalThis.crypto:void 0;if(e&&typeof e.randomUUID==\"function\")return e.randomUUID().replace(/-/g,\"\");if(e&&typeof e.getRandomValues==\"function\"){let n=e.getRandomValues(new Uint8Array(24));return Array.from(n,t=>t.toString(16).padStart(2,\"0\")).join(\"\")}try{console.warn(\"[player] crypto indisponible : jeton d'auteur affaibli\")}catch(n){}return Math.random().toString(36).slice(2)+Math.random().toString(36).slice(2)}function mn(e,n=te){let t=dn(e,w.authorToken);if(t)return t;let r=n();return pn(e,w.authorToken,r),r}async function gn(e,n){let t=n!==void 0?n:typeof globalThis!=\"undefined\"&&globalThis.crypto?globalThis.crypto.subtle:null;if(!t||!e)return\"\";try{let r=async o=>{let i=await t.digest(\"SHA-256\",new TextEncoder().encode(o));return Array.from(new Uint8Array(i),a=>a.toString(16).padStart(2,\"0\")).join(\"\")};return(await r(\"ref:\"+await r(e))).slice(0,16)}catch(r){return\"\"}}function re(e){return String(e&&e.ref||\"\")}function T(e,n){return!e||!n||!e.author_ref||!n.ref?!1:e.author_ref===n.ref}function oe(e){return!!(e&&e.role===\"presenter\")}function bn(e,n){if(!n||!n.name)return!1;let t=(n.name.split(/\\s+/)[0]||\"\").toLowerCase();return t?String(e&&e.body||\"\").toLowerCase().includes(`@${t}`)&&!T(e,n):!1}function yn(e){let{msg:n,me:t,historyLoaded:r,chatHidden:o}=e;return!r||!n||n.deleted||T(n,t)?!1:o}function hn(e){return!e||e<=0?\"\":e>9?\"9+\":String(e)}function vn(e,n={}){var g;let t=(g=n.minMs)!=null?g:300,r=n.now||(()=>Date.now()),o=n.setTimer||((f,M)=>setTimeout(f,M)),i=n.clearTimer||(f=>clearTimeout(f)),a=!1,l=!1,d=-1/0,m=null,p=!1;function c(){if(p||a){l=!0;return}let f=t-(r()-d);if(f>0){u(f);return}a=!0,l=!1,d=r();let M=!1;e(()=>{M||(M=!0,a=!1,l&&!p&&c())})}function u(f){p||m!==null||(m=o(()=>{m=null,c()},Math.max(0,f)))}return{signaler(){p||c()},maintenant(){p||(d=-1/0,c())},arreter(){p=!0,m!==null&&(i(m),m=null)}}}function Mn(e,n,t=J,r={}){let o=r.fetch||(typeof fetch==\"function\"?fetch:void 0);if(!o)return Promise.reject(new Error(\"fetch indisponible\"));let i=r.setTimer||((u,g)=>setTimeout(u,g)),a=r.clearTimer||(u=>clearTimeout(u)),l=null;try{l=new AbortController}catch(u){}let d=l?we(G({},n||{}),{signal:l.signal}):G({},n||{}),m=null,p=new Promise((u,g)=>{m=i(()=>{try{l==null||l.abort()}catch(f){}g(new Error(\"delai\"))},t)}),c=()=>{try{a(m)}catch(u){}};return Promise.race([o(e,d),p]).then(u=>(c(),u),u=>{throw c(),u})}function wn(e){let n=e.now||(()=>Date.now()),t=Math.max(1,e.rafale),r=t,o=n();return{prendre(){let i=n();return r=Math.min(t,r+(i-o)*e.parHeure/36e5),o=i,r<1?!1:(r-=1,!0)},restants:()=>r}}function xn(e={}){var m;let n=(m=e.minMs)!=null?m:0,t=e.now||(()=>Date.now()),r=e.setTimer||((p,c)=>setTimeout(p,c)),o=new Map,i=!1,a=-1/0,l=!1;function d(){if(i||o.size===0)return;let p=n-(t()-a);if(p>0){if(l)return;l=!0,r(()=>{l=!1,d()},p);return}let c=o.keys().next().value,u=o.get(c);o.delete(c),i=!0,a=t(),Promise.resolve().then(u.tache).then(g=>u.regler(g),()=>u.regler(null)).then(()=>{i=!1,d()})}return{poser(p,c){return new Promise(u=>{let g=o.get(p);o.set(p,{tache:c,regler:f=>{g&&g.regler(f),u(f)}}),d()})},enAttente:()=>o.size}}function Tn(e,n=te){try{if(e){let t=e.getItem(w.presenceId);if(t)return t;let r=`p-${n()}`;return e.setItem(w.presenceId,r),r}}catch(t){}return`p-${n()}`}var H={};v(H,{messageClassName:()=>En,renderActions:()=>Oe,renderAttachment:()=>Le,renderMessage:()=>_n,renderReactions:()=>Ne});function Ne(e,n){let t=e&&e.reactions||{},r=re(n),o=\"\";for(let i in t){let a=t[i]||[];if(!a.length)continue;let l=a.indexOf(r)>=0;o+=`<button class=\"re-chip${l?\" on\":\"\"}\" data-e=\"${y(i)}\">${y(i)} ${a.length}</button>`}return o}function Le(e){if(!e||!e.url)return\"\";let n=y(e.url);if(e.kind===\"image\")return`<a class=cm-att href=\"${n}\" target=_blank rel=noreferrer><img src=\"${n}\" alt=\"\" loading=lazy></a>`;let t=y(e.name||\"Document.pdf\");return`<a class=cm-att-pdf href=\"${n}\" target=_blank rel=noreferrer data-pdf=\"${n}\"><span class=cm-att-ph></span><span class=cm-pdflabel>${t}</span></a>`}function Oe(e,n){let t=T(e,n.me),r=`<button class=cm-react title=\"R\\xE9agir\">${n.reactIcon||\"\"}</button><button class=cm-reply title=\"R\\xE9pondre\">\\u21A9</button>`;return t&&(r+='<button class=cm-edit title=\"Modifier\">\\u270E</button>'),(t||oe(n.me))&&(r+='<button class=cm-del-btn title=\"Supprimer\">\\u{1F5D1}</button>'),r}function _n(e,n){if(e.deleted)return'<span class=a></span><span class=b><div class=\"txt cm-del\">Message supprim\\xE9</div></span>';let t=e.reply_to?`<div class=cm-q><b>${y(e.reply_name||\"\")}</b> ${y(e.reply_text||\"\")}</div>`:\"\",r=e.edited?\" <span class=cm-ed>(modifi\\xE9)</span>\":\"\",o=e.body?`<div class=txt>${ne(e.body)}${r}</div>`:\"\",i=e.is_presenter?\" <span class=tag>pr\\xE9sentateur</span>\":\"\";return`<span class=a>${ee(e.author_avatar,e.author_name)}</span><span class=b><div class=who><b>${y(e.author_name||\"Invit\\xE9\")}</b>${i}</div>`+t+o+Le(e.attachment)+`<div class=cm-re>${Ne(e,n.me)}</div></span><span class=cm-act>${Oe(e,n)}</span>`}function En(e,n,t){return\"cm\"+(T(e,n)?\" me\":\"\")+(e.deleted?\" isdel\":\"\")+(t?\" mentioned\":\"\")}var U={};v(U,{DEFAULT_CENTER:()=>ae,DEFAULT_ZOOM:()=>ue,MAP_TYPES:()=>P,cycleMapType:()=>Sn,initialMapContent:()=>Rn,mapTypeLabel:()=>kn,sanitizeContent:()=>le});var P=[\"roadmap\",\"satellite\",\"hybrid\"],ae=[46.6,2.5],ue=6;function _(e){let n=Number(e);return Number.isFinite(n)?n:null}function se(e){if(!Array.isArray(e))return null;let n=_(e[0]),t=_(e[1]);return n!=null&&t!=null?[n,t]:null}var ie=(e,n,t)=>Math.max(n,Math.min(t,e));function le(e){if(!e||typeof e!=\"object\")return null;let n=e;if(n.kind===\"pdf\"||n.kind==null)return null;if(n.kind===\"streetview\"){let t=se(n.position);if(!t)return null;let r=n.pov&&typeof n.pov==\"object\"?n.pov:null,o=r?{heading:_(r.heading)||0,pitch:ie(_(r.pitch)||0,-90,90)}:{heading:0,pitch:0};return{kind:\"streetview\",position:t,pov:o,zoom:ie(_(n.zoom)||1,0,5)}}return n.kind!==\"map\"?null:{kind:\"map\",center:se(n.center)||[...ae],zoom:ie(Math.trunc(_(n.zoom)||ue),1,21),marker:se(n.marker),mapType:P.includes(n.mapType)?n.mapType:null,label:String(n.label||\"\").slice(0,160)||null}}function Sn(e){let n=P.indexOf(e);return P[(n+1)%P.length]}function kn(e){return e===\"roadmap\"?\"\\u{1F6F0} Satellite\":e===\"satellite\"?\"\\u{1F5FA} Hybride\":\"\\u{1F5FA} Plan\"}function Rn(){return{kind:\"map\",center:[...ae],zoom:ue,marker:null,mapType:null,label:null}}var D={};v(D,{CURRENT_PAGE_MARGIN:()=>Ln,DEFAULT_ASPECT:()=>An,MAX_ZOOM:()=>In,MIN_PAGE_WIDTH:()=>Cn,MIN_ZOOM:()=>Pn,PRERENDER_MARGIN:()=>Nn,arrowState:()=>Hn,averageColor:()=>Wn,clampPage:()=>$e,clampZoom:()=>On,fitWidth:()=>Dn,isImageDocument:()=>Vn,progressPercent:()=>$n});var Pn=.5,In=3,An=1.35,Cn=280,Nn=\"500px 0px\",Ln=\"-48% 0px -48% 0px\";function On(e){let n=Math.round((Number(e)||0)*10)/10;return Math.max(.5,Math.min(3,n))}function $e(e,n){return Math.max(1,Math.min(n||1,Number(e)||1))}function $n(e,n){return n?Math.round($e(e,n)/n*100):0}function Hn(e,n){return{prevDisabled:e<=1,nextDisabled:!!n&&e>=n}}var Un=28;function Dn(e){var a;let n=(a=e.zoom)!=null?a:1,t=Math.max(280,e.containerWidth||900)*n;if(!e.onePage)return t;let r=e.aspect||1.35,i=((e.containerHeight||600)-Un-(e.overlap||0)-(e.reserve||0))/r;return i>120?Math.min(t,i):t}function Wn(e){let n=e.length/4;if(!n)return\"rgb(0,0,0)\";let t=0,r=0,o=0;for(let i=0;i<e.length;i+=4)t+=e[i],r+=e[i+1],o+=e[i+2];return`rgb(${Math.round(t/n)},${Math.round(r/n)},${Math.round(o/n)})`}function Vn(e,n){return/[.](png|jpe?g|webp|gif|avif)($|[?])/i.test(String(e||n||\"\"))}var W={};v(W,{presentationTransition:()=>zn,switchDocUrl:()=>Fn});function zn(e,n={}){if(!e)return[{kind:\"nothing\"}];if(e.active===!1)return[{kind:\"ended\"}];let t=le(e.content);if(t)return[{kind:\"show-map\",content:t}];let r=[{kind:\"leave-map\"}];return e.file_url&&e.file_url!==n.docUrl?(r.push({kind:\"switch-doc\",url:e.file_url,name:e.file_name||void 0,title:e.doc_title||void 0,updatedAt:e.updated_at||void 0}),r):(e.current_page&&r.push({kind:\"show-page\",page:e.current_page}),r)}function Fn(e,n){let t=encodeURIComponent(n||String(Date.now()));return`${e}${e.includes(\"?\")?\"&\":\"?\"}v=${t}`}return Be(Bn);})();", SOURCE_HASH: "6e69736fc03cc5f0" };
package/server/handler.js CHANGED
@@ -1,13 +1,13 @@
1
1
  // Page publique de consultation d'un document commercial : /doc/:slug → visionneuse pdf.js qui TRACE
2
2
  // l'ouverture et les PAGES VUES (un lien par destinataire → on sait qui a lu, combien de pages).
3
- // - GET /doc/:slug → HTML visionneuse (pdf.js depuis cdnjs, nonce CSP)
3
+ // - GET /doc/:slug → HTML visionneuse (pdf.js EMBARQUÉ, servi par ?asset=…, nonce CSP)
4
4
  // - GET /doc/:slug?file=1 → stream le PDF depuis le Storage (MÊME ORIGINE → pas de souci CORS pour pdf.js)
5
5
  // - POST /api/doc {slug,event…}→ journalise un événement (open / page / heartbeat) — best-effort
6
6
  const crypto = require("crypto");
7
7
  const { Readable } = require("node:stream");
8
8
  const { pipeline } = require("node:stream/promises");
9
9
  const { getShareBySlug, logView, upsertSession, createReshare, sendReshareEmail, upsertInternalSession,
10
- createShare, revokeShare, setShareAuth, overview: docOverview, listSharesForDoc, listSessionsForDoc, internalStatsForDoc } = require("./shares");
10
+ createShare, revokeShare, setShareAuth, overview: docOverview, listSharesForDoc, listSessionsForDoc, internalStatsForDoc , cleIdempotence } = require("./shares");
11
11
  const { SESSION_QUOTA_PER_HOUR, PRESENT_QUOTA_PER_HOUR, PRESENT_CACHE_MS } = require("./shared.generated.js");
12
12
  const { creerCache } = require("./cache.js");
13
13
 
@@ -71,14 +71,23 @@ const originOf = (u) => { try { return new URL(u).origin; } catch { return ""; }
71
71
  // atténuation était IMPLICITE — une modification de CSP la rouvrait sans que rien ne le dise.
72
72
  // Les quatre appels à `getDocument` forcent donc désormais `isEvalSupported: false`, en
73
73
  // défense en profondeur : la protection ne dépend plus d'un en-tête écrit ailleurs.
74
- // 2. La montée vers 4.x n'est PAS un changement de numéro : cdnjs ne publie plus que des modules
75
- // ES (`pdf.min.mjs`) à partir de 4.0, alors qu'on charge un script classique et qu'on
76
- // configure le worker à la main. C'est une migration, elle est suivie séparément dans
77
- // docs/AUDIT-2026-08-14-SUIVI.md (P1-3) avec l'embarquement de la bibliothèque, qui règle
78
- // aussi la dépendance CDN sans intégrité (P2-4).
74
+ // 2. La montée vers 4.x+ a été FAITE (0.1.66) : bibliothèque embarquée, modules ES servis par
75
+ // notre route ?asset=…, worker de même origine. L'historique de la contrainte cdnjs vit dans
76
+ // le CHANGELOG ; ce commentaire a longtemps décrit la migration au futur.
79
77
  //
80
78
  // Signalé par un audit externe. Fermer le chemin d'abord, migrer ensuite — dans cet ordre.
81
- const PDFJS = "https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174";
79
+ // ⚠️ PDF.JS EST EMBARQUÉ (pdfjs-dist, épinglé exact) ET SERVI PAR NOTRE ROUTE `?asset=…`. Trois
80
+ // ans de CDN ont coûté : un tiers dans la CSP, un worker impossible à couvrir par SRI (il n'entre
81
+ // pas par une balise), le ballet d'empreintes qui le contournait, et un épinglage suspendu à ce
82
+ // que cdnjs continuait de publier. Un actif de même origine EST nos octets.
83
+ //
84
+ // ⚠️ LE SCRIPTING PDF EST STRUCTURELLEMENT IMPOSSIBLE, pas désactivé par une option : il exige le
85
+ // sandbox (pdf.sandbox.min.mjs), que nous ne servons ni ne chargeons nulle part. Une option
86
+ // « enableScripting:false » ici serait un placebo — elle appartient au viewer de Mozilla, pas à
87
+ // getDocument. `isEvalSupported:false` reste sur chaque appel : lui est réel (CVE-2024-4367).
88
+ const PDFJS_VERSION = require("pdfjs-dist/package.json").version;
89
+ const PDFJS = "/api/doc?asset=pdf&v=" + PDFJS_VERSION;
90
+ const PDFJS_WORKER = "/api/doc?asset=pdfworker&v=" + PDFJS_VERSION;
82
91
  // ⚠️ VERSION EXACTE, PAS `@2` (constat P2-4). L'étiquette `@2` de jsdelivr suit la dernière 2.x :
83
92
  // la page servait donc, aux visiteurs, le code que Supabase avait publié le matin même — sans que
84
93
  // personne n'ait rien déployé, ni relu, ni pu revenir en arrière. Le jour où elle résolvait vers
@@ -122,14 +131,12 @@ const MAPS_VERSION = "3.65";
122
131
  * doivent être comparés par quelqu'un au moins une fois.
123
132
  */
124
133
  const TIERS = {
125
- pdf: { url: PDFJS + "/pdf.min.js", sri: "sha384-/1qUCSGwTur9vjf/z9lmu/eCUYbpOTgSjmpbMQZ1/CtX2v/WcAIKqRv+U1DUCG6e" },
126
134
  // ⚠️ Le worker N'A PAS DE BALISE, donc pas d'attribut `integrity` : il est chargé par pdf.js,
127
135
  // pas par le document. Mais ses octets passent déjà par notre code (ils sont récupérés en texte
128
136
  // puis transformés en blob de même origine, sans quoi le navigateur refuserait un worker
129
137
  // distant) — on les vérifie donc à la main, là où ils passent. Sans ça, l'empreinte de
130
138
  // `pdf.min.js` protégerait la petite moitié et laisserait la grande (1 Mo contre 300 ko) entrer
131
139
  // sans contrôle, alors même qu'elle voit tout le contenu du document.
132
- pdfWorker: { url: PDFJS + "/pdf.worker.min.js", sri: "sha384-SnzOobpRMLXZ52iJvZm/C0fYw0OQemTXzTjIsdsfMcrCtCEe9qgzxTd3RSklO5x2" },
133
140
  supa: { url: SUPAJS, sri: "sha384-qafw21c/iciq0VXsi9FzkfoQv5I/V0iqE4lSNcKXPnW9/UTJLnv5CcN4FHxVLnKg" },
134
141
  leaflet: { url: LEAFLET, sri: "sha384-cxOPjt7s7Iz04uaHJceBmS+qpjv2JkIHNVcuOrM+YHwZOmJGBXI00mdUXEq65HTH" },
135
142
  // ⚠️ UNE FEUILLE DE STYLE TIERCE N'EST PAS INOFFENSIVE, et celle-ci n'était comptée nulle part.
@@ -840,7 +847,7 @@ var Map3DD=(function(){
840
847
  var svb=document.getElementById('mapSV'); if(svb&&!svb._w){svb._w=1;svb.addEventListener('click',function(){ if(!map)return; var c=useG?[map.getCenter().lat(),map.getCenter().lng()]:[map.getCenter().lat,map.getCenter().lng]; goSV(c); });}
841
848
  var tm=document.getElementById('mapToMap'); if(tm&&!tm._w){tm._w=1;tm.addEventListener('click',toMap);} }
842
849
  function doSearch(text){ text=(text||'').trim(); var res=document.getElementById('mapRes'); if(!text||!res)return; res.innerHTML='<div style="padding:9px 14px;color:#888;font-size:12px">Recherche…</div>';
843
- fetch('https://nominatim.openstreetmap.org/search?format=json&limit=5&q='+encodeURIComponent(text),{headers:{Accept:'application/json'}}).then(function(r){return r.json();}).then(function(l){ if(!l||!l.length){res.innerHTML='<div style="padding:9px 14px;color:#888;font-size:12px">Aucun résultat.</div>';return;} res.innerHTML=l.map(function(o){return '<button data-lat="'+o.lat+'" data-lng="'+o.lon+'">'+String(o.display_name||'').replace(/</g,'&lt;')+'</button>';}).join(''); }).catch(function(){res.innerHTML='<div style="padding:9px 14px;color:#c0392b;font-size:12px">Recherche indisponible.</div>';}); }
850
+ fetch('https://nominatim.openstreetmap.org/search?format=json&limit=5&q='+encodeURIComponent(text),{headers:{Accept:'application/json'}}).then(function(r){return r.json();}).then(function(l){ if(!l||!l.length){res.innerHTML='<div style="padding:9px 14px;color:#888;font-size:12px">Aucun résultat.</div>';return;} res.innerHTML=l.map(function(o){var la=Number(o.lat),lo=Number(o.lon);if(!isFinite(la)||!isFinite(lo))return '';return '<button data-lat="'+la+'" data-lng="'+lo+'">'+String(o.display_name||'').replace(/</g,'&lt;')+'</button>';}).join(''); }).catch(function(){res.innerHTML='<div style="padding:9px 14px;color:#c0392b;font-size:12px">Recherche indisponible.</div>';}); }
844
851
  // ── Street View (Google) ─────────────────────────────────────────────────────────────────────────
845
852
  function tempHint(t){ var h=document.getElementById('mapHint'); if(h){h.textContent=t; setTimeout(function(){ if(h.textContent===t)h.textContent=(isPres?'':'Vue du présentateur — en direct'); },2600);} }
846
853
  // Le présentateur passe en Street View depuis le centre de la carte : on cherche le panorama le plus proche.
@@ -1750,9 +1757,11 @@ function sendHtml(res, status, html, scriptSrc, imgExtra, frameAncestors) {
1750
1757
  res.setHeader("Referrer-Policy", "strict-origin-when-cross-origin");
1751
1758
  res.setHeader("Content-Security-Policy", [
1752
1759
  "default-src 'none'",
1753
- `script-src ${scriptSrc || "'none'"} https://cdnjs.cloudflare.com`,
1754
- "worker-src 'self' blob: https://cdnjs.cloudflare.com",
1755
- `connect-src 'self' https://cdnjs.cloudflare.com${supaOrigin ? " " + supaOrigin : ""}`,
1760
+ // ⚠️ `'none'` DOIT ÊTRE SEUL ou la directive est invalide — « 'none' 'self' » a fait rejeter
1761
+ // la CSP entière par Chrome (vu à la sonde du banc, pas en relisant le code).
1762
+ `script-src ${scriptSrc ? scriptSrc + " 'self'" : "'self'"}`,
1763
+ "worker-src 'self'",
1764
+ `connect-src 'self'${supaOrigin ? " " + supaOrigin : ""}`,
1756
1765
  `img-src 'self' data: blob:${imgExtra ? " " + imgExtra : ""}`,
1757
1766
  `media-src 'self'${supaOrigin ? " " + supaOrigin : ""}`,
1758
1767
  "style-src 'unsafe-inline'",
@@ -2029,12 +2038,11 @@ function viewerHtml(share, nonce, logoUrl, pitch) {
2029
2038
  // En aperçu interne, on embarque de quoi démarrer une présentation live (URL Storage brute + métadonnées).
2030
2039
  // `fileName` : c'est LUI qui dit la nature du document côté page. L'URL publique est
2031
2040
  // `/api/doc?slug=…&file=1`, sans extension — sans ce champ, une image partait dans pdf.js.
2032
- const cfg = jsonPourScript({ brand: PLAYER.branding.name, slug: preview ? "" : share.slug, fileUrl, fileName: share.file_name || "", pdfjs: PDFJS, title, preview, embed, embedded, bot: botOn, botGuided: !preview && !!share.bot_enabled && share.bot_guided !== false, botAv: (!preview && share.bot_enabled && share.bot_avatar) || "", botName: (!preview && share.bot_enabled && share.bot_name) || "", botGreet: (!preview && share.bot_enabled && share.bot_greeting) || "", botGreetDoc: (!preview && share.bot_enabled && share.bot_greeting_doc) || "", dl: share.allow_download !== false, autoPresent: !!share.auto_present, botAnim: share.bot_page_anim !== false, botVoice: !preview && !!share.bot_enabled && !!process.env.ELEVENLABS_API_KEY, vIcOn: ICONS.sound, vIcOff: ICONS.mute, kStyle: (!preview && share.bot_enabled && share.bot_karaoke) || "classic", vLayout: (!preview && share.bot_enabled && share.video_layout) || "", vClips: !preview && !!share.bot_vclips, botVAv: (!preview && share.bot_enabled && share.bot_vphoto) || "", resumeSlug: preview ? (share.resume_slug || "") : "", supaUrl: preview ? (share.supa_url || "") : "", supaKey: preview ? (share.supa_key || "") : "", internal: preview && share.internal_email ? { email: share.internal_email, name: share.presenter_name || "", docId: share.doc_id || "", it: share.internal_token || "" } : null, present: preview ? { url: share.raw_url || "", name: share.file_name || "", title: share.doc_title || "", docId: share.doc_id || "", by: share.presenter_name || "", email: share.internal_email || "", av: share.presenter_avatar || "" } : null });
2041
+ const cfg = jsonPourScript({ brand: PLAYER.branding.name, slug: preview ? "" : share.slug, fileUrl, fileName: share.file_name || "", pdfjs: PDFJS, pdfjsWorker: PDFJS_WORKER, title, preview, embed, embedded, bot: botOn, botGuided: !preview && !!share.bot_enabled && share.bot_guided !== false, botAv: (!preview && share.bot_enabled && share.bot_avatar) || "", botName: (!preview && share.bot_enabled && share.bot_name) || "", botGreet: (!preview && share.bot_enabled && share.bot_greeting) || "", botGreetDoc: (!preview && share.bot_enabled && share.bot_greeting_doc) || "", dl: share.allow_download !== false, autoPresent: !!share.auto_present, botAnim: share.bot_page_anim !== false, botVoice: !preview && !!share.bot_enabled && !!process.env.ELEVENLABS_API_KEY, vIcOn: ICONS.sound, vIcOff: ICONS.mute, kStyle: (!preview && share.bot_enabled && share.bot_karaoke) || "classic", vLayout: (!preview && share.bot_enabled && share.video_layout) || "", vClips: !preview && !!share.bot_vclips, botVAv: (!preview && share.bot_enabled && share.bot_vphoto) || "", resumeSlug: preview ? (share.resume_slug || "") : "", supaUrl: preview ? (share.supa_url || "") : "", supaKey: preview ? (share.supa_key || "") : "", internal: preview && share.internal_email ? { email: share.internal_email, name: share.presenter_name || "", docId: share.doc_id || "", it: share.internal_token || "" } : null, present: preview ? { url: share.raw_url || "", name: share.file_name || "", title: share.doc_title || "", docId: share.doc_id || "", by: share.presenter_name || "", email: share.internal_email || "", av: share.presenter_avatar || "" } : null });
2033
2042
  return `<!doctype html><html lang=fr><head><meta charset=utf-8>
2034
2043
  <meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=3,viewport-fit=cover,interactive-widget=resizes-content">
2035
2044
  <meta name=robots content="noindex,nofollow">
2036
2045
  <link rel=icon href="data:,">
2037
- <link rel=preconnect href="https://cdnjs.cloudflare.com" crossorigin>
2038
2046
  <title>${esc(titreOnglet(share))}</title>
2039
2047
  <style>
2040
2048
  :root{--bg:#33312e;--bar:#26241f}
@@ -2229,7 +2237,6 @@ ${LEGAL_CSS}
2229
2237
  ${legalFooter({ tracked: !preview && !!share.slug, sansExpediteur: !share.recipient_email && !share.created_by })}
2230
2238
  ${brandLogo || !brandIntroRuntime ? "" : `<script nonce="${nonce}">(${brandIntroRuntime.toString()})();</script>`}
2231
2239
  <script nonce="${nonce}">${PLAYER_BROWSER_JS}</script>
2232
- ${balise(nonce, TIERS.pdf)}
2233
2240
  ${preview ? `${balise(nonce, TIERS.supa)}
2234
2241
  <script nonce="${nonce}">var LIVECFG=${jsonPourScript({ supaUrl: share.supa_url || "", supaKey: share.supa_key || "", hostAuthKey: cleSessionHote(), liveAuthKey: CLE_SESSION_PLAYER, guestKey: CLE_INVITE })};var GMAPS_KEY=${jsonPourScript((PLAYER.config && PLAYER.config.mapsKey) || "")};${LIVE_JS}
2235
2242
  ${MAP_JS}</script>` : ""}
@@ -2534,7 +2541,10 @@ ${LEGAL_CSS}
2534
2541
  }
2535
2542
  function loadError(m){ var l=document.getElementById('lpct'); if(l){ l.textContent=m; l.className='lerr'; } var b=document.getElementById('lbar'); if(b)b.style.display='none'; }
2536
2543
  function hideLoader(){ var l=document.getElementById('load'); if(l&&!l.classList.contains('hide')){ l.classList.add('hide'); setTimeout(function(){ if(l.parentNode) l.parentNode.removeChild(l); },450); } }
2537
- if(!window.pdfjsLib){ loadError("Impossible de charger la visionneuse."); return; }
2544
+ // (Ici vivait « if(!window.pdfjsLib) return » — sentinelle de l'époque pdf.js entrait par
2545
+ // une balise AVANT ce script. La bibliothèque s'importe désormais dans le boot lui-même : la
2546
+ // garde ne protégeait plus rien et faisait sortir la visionneuse ENTIÈRE, sans un mot — vu à
2547
+ // la sonde du banc : zéro requête d'actif, zéro erreur, zéro document.)
2538
2548
  function start(){
2539
2549
  // Ouverture journalisée, chrono lancé, écouteurs de visibilité / focus / inactivité posés.
2540
2550
  T.start();
@@ -2575,11 +2585,13 @@ ${LEGAL_CSS}
2575
2585
  if(IS_IMG){ start(); return; }
2576
2586
  loadError("Document non affiche : une dependance n a pas pu etre verifiee.");
2577
2587
  }
2578
- var wsrc=CFG.pdfjs+'/pdf.worker.min.js';
2588
+ // La bibliothèque vient de NOTRE origine, en module ES : l'import qui échoue est un échec de
2589
+ // NOTRE serveur — même refus fermé qu'avant (l'image, elle, n'a pas besoin du worker).
2579
2590
  try{
2580
- Player.viewer.workerBlobUrl(wsrc,'${TIERS.pdfWorker.sri}').then(function(u){
2581
- if(!u){ refuserWorker(); return; }
2582
- pdfjsLib.GlobalWorkerOptions.workerSrc=u; start();
2591
+ import(CFG.pdfjs).then(function(m){
2592
+ window.pdfjsLib=m;
2593
+ pdfjsLib.GlobalWorkerOptions.workerSrc=CFG.pdfjsWorker;
2594
+ start();
2583
2595
  }).catch(refuserWorker);
2584
2596
  }catch(e){ refuserWorker(); }
2585
2597
  // Bord à bord en une-page mobile : chaque millimètre compte, surtout pour un PDF paysage.
@@ -2695,8 +2707,9 @@ ${LEGAL_CSS}
2695
2707
  var tl=document.createElement('div'); tl.className='textLayer';
2696
2708
  tl.style.width=v.width+'px'; tl.style.height=v.height+'px'; tl.style.setProperty('--scale-factor', scale);
2697
2709
  el.appendChild(tl);
2698
- try{ pdfjsLib.renderTextLayer({textContentSource:tc,container:tl,viewport:v}); }
2699
- catch(e){ try{ pdfjsLib.renderTextLayer({textContent:tc,container:tl,viewport:v}); }catch(e2){} }
2710
+ try{ new pdfjsLib.TextLayer({textContentSource:tc,container:tl,viewport:v}).render(); }
2711
+ catch(e){ try{ pdfjsLib.renderTextLayer({textContentSource:tc,container:tl,viewport:v}); }
2712
+ catch(e2){ try{ pdfjsLib.renderTextLayer({textContent:tc,container:tl,viewport:v}); }catch(e3){} } }
2700
2713
  }); }catch(e){}
2701
2714
  }); }
2702
2715
  // ── Assistant IA « présentateur » : chat requête/réponse (bot-start/bot-say) + saut de page piloté ──
@@ -2706,7 +2719,7 @@ ${botOn && botBrowser ? botBrowser.botViewerJs(ICONS) : ""}
2706
2719
  </body></html>`;
2707
2720
  }
2708
2721
 
2709
- // CSP de la page audience (Présenter) : autorise pdf.js (cdnjs), supabase-js (jsdelivr) et la connexion
2722
+ // CSP de la page audience (Présenter) : pdf.js vient de NOTRE origine ; supabase-js (jsdelivr) et la connexion
2710
2723
  // Realtime (https + wss vers le projet Supabase). Plus permissive que la visionneuse, limitée à cette page.
2711
2724
  /**
2712
2725
  * Les origines d'images qu'une page a le droit de charger.
@@ -2764,9 +2777,9 @@ function sendPresentHtml(res, html, nonce, supaUrl, imgExtra, frameAncestors) {
2764
2777
  res.setHeader("Referrer-Policy", "strict-origin-when-cross-origin");
2765
2778
  res.setHeader("Content-Security-Policy", [
2766
2779
  "default-src 'none'",
2767
- `script-src 'nonce-${nonce}' https://cdnjs.cloudflare.com https://cdn.jsdelivr.net https://unpkg.com https://maps.googleapis.com https://maps.gstatic.com`,
2768
- "worker-src 'self' blob: https://cdnjs.cloudflare.com",
2769
- `connect-src 'self' https://cdnjs.cloudflare.com https://cdn.jsdelivr.net https://nominatim.openstreetmap.org https://*.googleapis.com https://*.gstatic.com ${supaUrl} ${wss}`,
2780
+ `script-src 'nonce-${nonce}' 'self' https://cdn.jsdelivr.net https://unpkg.com https://maps.googleapis.com https://maps.gstatic.com`,
2781
+ "worker-src 'self'",
2782
+ `connect-src 'self' https://cdn.jsdelivr.net https://nominatim.openstreetmap.org https://*.googleapis.com https://*.gstatic.com ${supaUrl} ${wss}`,
2770
2783
  `img-src 'self' data: blob: https://*.tile.openstreetmap.org https://unpkg.com https://*.googleapis.com https://*.gstatic.com https://*.ggpht.com https://*.googleusercontent.com ${supaUrl}${imgExtra ? " " + imgExtra : ""}`,
2771
2784
  "style-src 'unsafe-inline' https://unpkg.com https://fonts.googleapis.com",
2772
2785
  "font-src https://fonts.gstatic.com data:",
@@ -2794,11 +2807,10 @@ function presentHtml(pres, nonce, logoUrl, supaUrl, supaKey) {
2794
2807
  const presenter = esc(pres.presenter_name || "");
2795
2808
  const logo = esc(logoUrl || "");
2796
2809
  const fileUrl = `/api/doc?present=${encodeURIComponent(pres.slug)}&file=1`;
2797
- const cfg = jsonPourScript({ fileUrl, docUrl: pres.file_url, pdfjs: PDFJS, slug: pres.slug, fileName: pres.file_name || "", page: pres.current_page || 1, active: pres.active !== false, content: pres.content || null, supaUrl, supaKey, title: pres.doc_title || pres.file_name || "Document" });
2810
+ const cfg = jsonPourScript({ fileUrl, docUrl: pres.file_url, pdfjs: PDFJS, pdfjsWorker: PDFJS_WORKER, slug: pres.slug, fileName: pres.file_name || "", page: pres.current_page || 1, active: pres.active !== false, content: pres.content || null, supaUrl, supaKey, title: pres.doc_title || pres.file_name || "Document" });
2798
2811
  return `<!doctype html><html lang=fr><head><meta charset=utf-8>
2799
2812
  <meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=3">
2800
2813
  <meta name=robots content="noindex,nofollow">
2801
- <link rel=preconnect href="https://cdnjs.cloudflare.com" crossorigin>
2802
2814
  <title>${esc(PLAYER.branding.title(pres.doc_title || pres.file_name || "Document", "Présentation"))}</title>
2803
2815
  <style>
2804
2816
  :root{--bg:#23211e;--bar:#1a1916}
@@ -2864,7 +2876,6 @@ function presentHtml(pres, nonce, logoUrl, supaUrl, supaKey) {
2864
2876
  ${PLAYER.branding.poweredBy ? `<div class=brand>Propulsé par ${esc(PLAYER.branding.poweredBy)}</div>` : ""}
2865
2877
  ${legalFooter({ tracked: true })}
2866
2878
  <script nonce="${nonce}">${PLAYER_BROWSER_JS}</script>
2867
- ${balise(nonce, TIERS.pdf)}
2868
2879
  ${balise(nonce, TIERS.supa)}
2869
2880
  <script nonce="${nonce}">
2870
2881
  (function(){
@@ -2900,11 +2911,11 @@ function presentHtml(pres, nonce, logoUrl, supaUrl, supaKey) {
2900
2911
  pageEl.innerHTML=''; pageEl.appendChild(cv);
2901
2912
  });
2902
2913
  }
2903
- var wsrc=CFG.pdfjs+'/pdf.worker.min.js';
2904
2914
  function boot(){
2905
- Player.viewer.workerBlobUrl(wsrc,'${TIERS.pdfWorker.sri}').then(function(u){
2906
- if(!u){ window.__workerRefuse=1; var _e=document.getElementById('pg'); if(_e)_e.textContent="Document non affiche : une dependance n a pas pu etre verifiee."; return; }
2907
- pdfjsLib.GlobalWorkerOptions.workerSrc=u; load();
2915
+ import(CFG.pdfjs).then(function(m){
2916
+ window.pdfjsLib=m;
2917
+ pdfjsLib.GlobalWorkerOptions.workerSrc=CFG.pdfjsWorker;
2918
+ load();
2908
2919
  }).catch(function(){ window.__workerRefuse=1; var _e=document.getElementById('pg'); if(_e)_e.textContent="Document non affiche : une dependance n a pas pu etre verifiee."; });
2909
2920
  }
2910
2921
  // ⚠️ UNE PRÉSENTATION PEUT PORTER UNE IMAGE, ET CETTE VUE NE LE SAVAIT PAS. Le bouton
@@ -2974,7 +2985,8 @@ function presentHtml(pres, nonce, logoUrl, supaUrl, supaKey) {
2974
2985
  CFG.fileUrl='/api/doc?present='+encodeURIComponent(CFG.slug)+'&file=1&v='+encodeURIComponent(row.updated_at||String(row.current_page||1));
2975
2986
  load();
2976
2987
  }
2977
- if(window.pdfjsLib) boot(); else { var s=document.querySelector('script[src*="pdf.min.js"]'); if(s)s.addEventListener('load',boot); }
2988
+ // La balise pdf.min.js n'existe plus : boot() importe lui-même la bibliothèque.
2989
+ boot();
2978
2990
  window.__refit=function(){ if(ready) show(cur); };
2979
2991
  var _rzA; window.addEventListener('resize',function(){ clearTimeout(_rzA); _rzA=setTimeout(function(){ if(ready) show(cur); },140); });
2980
2992
  // Ce que l'audience fait d'un état reçu — player/src/presentation-state.ts, testé.
@@ -3588,7 +3600,7 @@ async function handler(req, res) {
3588
3600
  const dejaLa = await PLAYER.db.request(
3589
3601
  `commercial_doc_shares?doc_id=eq.${encodeURIComponent(docId)}&created_by=is.null&recipient_email=is.null${filtreAtteste}&select=slug&limit=1`,
3590
3602
  );
3591
- const cleHote = `hote:${docId}|${atteste || ""}`;
3603
+ const cleHote = cleIdempotence("hote", [docId, atteste || ""]);
3592
3604
  if (Array.isArray(dejaLa) && dejaLa[0]) {
3593
3605
  // Réemploi d'une ligne historique (sans clé) : on lui POSE la clé au passage — les
3594
3606
  // demandes suivantes la trouveront par l'unicité, et les doublons d'avant 0011
@@ -3662,7 +3674,7 @@ async function handler(req, res) {
3662
3674
  const docId = String(body.docId || "");
3663
3675
  if (!docId || !body.fileUrl) return jd(400, { ok: false, error: "docId/fileUrl requis" });
3664
3676
  const ex = await PLAYER.db.request(`commercial_doc_shares?doc_id=eq.${encodeURIComponent(docId)}&is_test=eq.true&select=slug&limit=1`);
3665
- const cleTest = `repetition:${docId}`;
3677
+ const cleTest = cleIdempotence("repetition", [docId]);
3666
3678
  if (Array.isArray(ex) && ex[0]) {
3667
3679
  const cleDispo = await require("./schema").attendue("liensUniques");
3668
3680
  await PLAYER.db.request(`commercial_doc_shares?slug=eq.${encodeURIComponent(ex[0].slug)}`, { method: "PATCH", headers: { Prefer: "return=minimal" }, body: { doc_title: body.docTitle || null, file_url: String(body.fileUrl), file_name: body.fileName || null, bot_enabled: true, bot_guided: true, bot_profile_id: (body.profileId || "").trim() || null, revoked: false, ...(cleDispo ? { idem_key: cleTest } : {}) } });
@@ -3896,14 +3908,39 @@ async function handler(req, res) {
3896
3908
  // AUCUN DOCUMENT DEMANDÉ. Ni slug, ni présentation, ni aperçu, ni carte d'identité — il n'y a
3897
3909
  // rien à afficher, et ce n'est pas un refus. Le dire franchement évite qu'un intégrateur
3898
3910
  // cherche un lien révoqué là où il lui manque un paramètre.
3899
- if (req.method === "GET" && !slug && !q.present && !q.preview && !q.contract) {
3911
+ if (req.method === "GET" && !slug && !q.present && !q.preview && !q.contract && !q.asset) {
3900
3912
  res.statusCode = 400;
3901
3913
  res.setHeader("Content-Type", "text/plain; charset=utf-8");
3902
- res.end("Aucun document demandé. Attendu : ?slug=… , ?present=… , ?preview=1 ou ?contract=1.\n" +
3914
+ res.end("Aucun document demandé. Attendu : ?slug=… , ?present=… , ?preview=1, ?contract=1 ou ?asset=….\n" +
3903
3915
  "Si vous intégrez le player, vérifiez que la plateforme fournit les paramètres de requête.");
3904
3916
  return;
3905
3917
  }
3906
3918
 
3919
+ // ── ACTIFS PDF.JS (`?asset=pdf` / `?asset=pdfworker`) ────────────────────────────────────
3920
+ // La bibliothèque est EMBARQUÉE (pdfjs-dist, épinglée exacte) et servie depuis notre origine.
3921
+ // Ce que ça ferme d'un coup : le tiers CDN dans la CSP, le script sans SRI possible (le worker
3922
+ // n'entre pas par une balise), et le ballet d'empreintes qui l'entourait — un actif de même
3923
+ // origine EST nos octets. Public et sans base, comme la carte : un actif qui exigerait une
3924
+ // session casserait la page avant qu'elle existe. L'URL porte la version → cache immuable.
3925
+ if (req.method === "GET" && (String(q.asset || "") === "pdf" || String(q.asset || "") === "pdfworker")) {
3926
+ const fichier = q.asset === "pdf" ? "pdfjs-dist/build/pdf.min.mjs" : "pdfjs-dist/build/pdf.worker.min.mjs";
3927
+ // Lu UNE fois par processus : la dépendance est épinglée exacte, les octets ne changent
3928
+ // qu'avec un déploiement — relire 1,7 Mo sur disque à chaque requête ne prouvait rien.
3929
+ if (!global.__actifsPdfjs) global.__actifsPdfjs = Object.create(null);
3930
+ let octets = global.__actifsPdfjs[fichier] || null;
3931
+ if (!octets) {
3932
+ try { octets = global.__actifsPdfjs[fichier] = require("node:fs").readFileSync(require.resolve(fichier)); } catch { /* dépendance absente */ }
3933
+ }
3934
+ if (!octets) { res.statusCode = 404; res.end("actif indisponible"); return; }
3935
+ res.statusCode = 200;
3936
+ res.setHeader("Content-Type", "text/javascript; charset=utf-8");
3937
+ res.setHeader("X-Content-Type-Options", "nosniff");
3938
+ res.setHeader("Cache-Control", "public, max-age=31536000, immutable");
3939
+ res.setHeader("Content-Length", String(octets.length));
3940
+ res.end(octets);
3941
+ return;
3942
+ }
3943
+
3907
3944
  // ── CARTE D'IDENTITÉ (`?contract=1`) ─────────────────────────────────────────────────────
3908
3945
  // La règle 4 du contrat demande à l'hôte d'épingler la version qu'il vise et de le VÉRIFIER.
3909
3946
  // Sans point d'interrogation, cette règle était une intention : un hôte ne pouvait pas écrire
package/server/shares.js CHANGED
@@ -10,6 +10,17 @@ function init(ctx) { PLAYER = ctx; }
10
10
 
11
11
 
12
12
  const enc = encodeURIComponent;
13
+
14
+ // ⚠️ LA CLÉ D'IDEMPOTENCE EST UNE EMPREINTE, PAS UNE CONCATÉNATION (sixième audit). La forme
15
+ // historique `hote:<docId>|<email>` portait trois défauts : tronquée à 300 caractères à
16
+ // l'ÉCRITURE mais relue ENTIÈRE après un 409 — le perdant légitime finissait en 500 faute de
17
+ // trouver son gagnant ; deux docId partageant le préfixe se volaient leur lien ; et un `|` dans
18
+ // une composante déplaçait la frontière. JSON.stringify fixe les frontières, sha256 fixe la
19
+ // longueur (genre + 65 caractères, loin de toute limite). Une seule fonction pour ÉCRIRE et
20
+ // RELIRE : les deux exemplaires du fait ne peuvent plus diverger. Les clés ancien format en
21
+ // base s'éteignent d'elles-mêmes — le réemploi passe par doc_id et re-pose la clé canonique.
22
+ const cleIdempotence = (genre, composants) =>
23
+ genre + ":" + crypto.createHash("sha256").update(JSON.stringify(composants)).digest("hex");
13
24
  const low = (s) => String(s || "").trim().toLowerCase();
14
25
  function newSlug() { return crypto.randomBytes(9).toString("base64url"); } // ~12 chars URL-safe
15
26
 
@@ -23,7 +34,7 @@ async function createShare({ docId, docTitle, fileUrl, fileName, recipientEmail,
23
34
  // CRÉATION de liens. Même sonde que la clé des messages (0005).
24
35
  const cleDispo = idemKey ? await require("./schema").attendue("liensUniques") : false;
25
36
  const row = {
26
- ...(cleDispo ? { idem_key: String(idemKey).slice(0, 300) } : {}),
37
+ ...(cleDispo ? { idem_key: String(idemKey) } : {}),
27
38
  slug, doc_id: String(docId), doc_title: docTitle || null, file_url: String(fileUrl), file_name: fileName || null,
28
39
  recipient_email: low(recipientEmail) || null, recipient_name: (recipientName || "").trim() || null, created_by: low(createdBy) || null,
29
40
  // ⚠️ DEUX CHAMPS, DEUX FAITS. `recipient_email` dit qui a le droit d'EXPÉDIER en son nom au
@@ -450,4 +461,5 @@ async function internalStatsForDoc(docId) {
450
461
  return { opens: list.length, readers: users.length, lastAt: list[0]?.last_at || null, users };
451
462
  }
452
463
 
453
- module.exports = { init, createShare, createReshare, sendReshareEmail, getShareBySlug, logView, upsertSession, listSharesForDoc, listSessionsForDoc, revokeShare, setShareAuth, overview, upsertInternalSession, internalStatsForDoc };
464
+ module.exports = {
465
+ cleIdempotence, init, createShare, createReshare, sendReshareEmail, getShareBySlug, logView, upsertSession, listSharesForDoc, listSessionsForDoc, revokeShare, setShareAuth, overview, upsertInternalSession, internalStatsForDoc };
package/supabase/init.sql CHANGED
@@ -59,6 +59,12 @@ create table if not exists public.commercial_doc_shares (
59
59
  attested_recipient_email text
60
60
  );
61
61
  create index if not exists cds_doc_id_idx on public.commercial_doc_shares (doc_id);
62
+ -- ⚠️ RÈGLE (sixième audit) : tout index sur une colonne apparue APRÈS un init déjà publié est
63
+ -- précédé de son ALTER, ICI MÊME. Le rattrapage de fin de fichier ne suffit pas : sur une base
64
+ -- d'hier, `create table if not exists` saute le corps de table, et l'index s'exécutait AVANT le
65
+ -- rattrapage — le rejeu cassait sur cette ligne. Garde : scénario CI « init v0.1.64 → rejeu ».
66
+ alter table public.commercial_doc_shares
67
+ add column if not exists idem_key text;
62
68
  create unique index if not exists cds_idem_key_uniq
63
69
  on public.commercial_doc_shares (idem_key)
64
70
  where idem_key is not null;
@@ -190,6 +196,9 @@ create table if not exists public.doc_presentation_messages (
190
196
  create index if not exists dpm_slug_idx on public.doc_presentation_messages (slug, created_at);
191
197
  -- ⚠️ Portée sur (slug, client_key), pas sur la clé seule : deux présentations n'ont aucune raison
192
198
  -- de partager un espace de clés. Partiel, pour que les lignes sans clé ne se gênent pas entre elles.
199
+ -- Même règle que cds_idem_key_uniq : la colonne est assurée avant son index.
200
+ alter table public.doc_presentation_messages
201
+ add column if not exists client_key text;
193
202
  create unique index if not exists dpm_client_key_uniq
194
203
  on public.doc_presentation_messages (slug, client_key)
195
204
  where client_key is not null;