discovery-media-player 0.1.64 → 0.1.66
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/README.md +1 -1
- package/package.json +4 -1
- package/server/browser.generated.js +1 -1
- package/server/handler.js +117 -49
- package/server/schema.js +5 -0
- package/server/shares.js +6 -1
- package/supabase/init.sql +8 -3
- package/supabase/migrations/0011-liens-uniques.sql +27 -0
package/README.md
CHANGED
|
@@ -70,7 +70,7 @@ This player answers the same questions and runs on your own infrastructure.
|
|
|
70
70
|
| | What you get |
|
|
71
71
|
|---|---|
|
|
72
72
|
| **Tracked links** | One link per recipient. Revocable. Re-shares are chained to their parent, so you see when a document travels. |
|
|
73
|
-
| **Reading analytics** | Time per page, furthest page reached, device. Counted only while the tab is visible and
|
|
73
|
+
| **Reading analytics** | Time per page, furthest page reached, device. Counted only while the tab is visible and recently active — a hidden tab, or one idle past the threshold, is not reading. |
|
|
74
74
|
| **Live presentation** | Present a document to a remote audience, with chat, presence, and handover. The audience follows your page without a video call. |
|
|
75
75
|
| **Access wall** | Optional: a document can require an email + code before it opens. |
|
|
76
76
|
| **Brand per client** | The loader carries your client's logo, resolved at display time — fix a logo and links already in inboxes follow. |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "discovery-media-player",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.66",
|
|
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,\"&\").replace(/</g,\"<\").replace(/>/g,\">\").replace(/\"/g,\""\")}function Ce(e){let n=(e||\"?\").trim().split(/\\s+/);return(((n[0]||\"\")[0]||\"?\")+((n[1]||\"\")[0]||\"\")).toUpperCase()}function ee(e,n){return e?`<img src=\"${y(e)}\" alt=\"\">`:y(Ce(n))}function ne(e){let n=y(e);return n=n.replace(/(https?:\\/\\/[^\\s<]+)/g,t=>`<a href=\"${t}\" target=_blank rel=noreferrer class=cm-link>${t}</a>`),n=n.replace(/(^|\\s)@([\\p{L}0-9_'.-]+)/gu,(t,r,o)=>`${r}<span class=cm-mention>@${o}</span>`),n}function cn(e){let n=new Map;for(let t in e||{})for(let r of(e||{})[t]||[]){let o=String(r.uid||r.email||r.name||\"\").toLowerCase()||`_${t}_${n.size}`;(!n.get(o)||r.role===\"presenter\")&&n.set(o,r)}return[...n.values()]}function dn(e,n){try{return e?e.getItem(n):null}catch(t){return null}}function pn(e,n,t){try{e==null||e.setItem(n,t)}catch(r){}}function fn(e,n=\"anon\",t=te){try{if(!e)return`anon-${n}`;let r=e.getItem(M.attendeeKey);if(r)return r;let o=`anon-${t()}`;return e.setItem(M.attendeeKey,o),o}catch(r){return`anon-${n}`}}function te(){let e=typeof globalThis!=\"undefined\"?globalThis.crypto:void 0;if(e&&typeof e.randomUUID==\"function\")return e.randomUUID().replace(/-/g,\"\");if(e&&typeof e.getRandomValues==\"function\"){let n=e.getRandomValues(new Uint8Array(24));return Array.from(n,t=>t.toString(16).padStart(2,\"0\")).join(\"\")}try{console.warn(\"[player] crypto indisponible : jeton d'auteur affaibli\")}catch(n){}return Math.random().toString(36).slice(2)+Math.random().toString(36).slice(2)}function mn(e,n=te){let t=dn(e,M.authorToken);if(t)return t;let r=n();return pn(e,M.authorToken,r),r}async function gn(e,n){let t=n!==void 0?n:typeof globalThis!=\"undefined\"&&globalThis.crypto?globalThis.crypto.subtle:null;if(!t||!e)return\"\";try{let r=async o=>{let s=await t.digest(\"SHA-256\",new TextEncoder().encode(o));return Array.from(new Uint8Array(s),a=>a.toString(16).padStart(2,\"0\")).join(\"\")};return(await r(\"ref:\"+await r(e))).slice(0,16)}catch(r){return\"\"}}function re(e){return String(e&&e.ref||\"\")}function T(e,n){return!e||!n||!e.author_ref||!n.ref?!1:e.author_ref===n.ref}function oe(e){return!!(e&&e.role===\"presenter\")}function bn(e,n){if(!n||!n.name)return!1;let t=(n.name.split(/\\s+/)[0]||\"\").toLowerCase();return t?String(e&&e.body||\"\").toLowerCase().includes(`@${t}`)&&!T(e,n):!1}function yn(e){let{msg:n,me:t,historyLoaded:r,chatHidden:o}=e;return!r||!n||n.deleted||T(n,t)?!1:o}function hn(e){return!e||e<=0?\"\":e>9?\"9+\":String(e)}function vn(e,n={}){var g;let t=(g=n.minMs)!=null?g:300,r=n.now||(()=>Date.now()),o=n.setTimer||((f,w)=>setTimeout(f,w)),s=n.clearTimer||(f=>clearTimeout(f)),a=!1,u=!1,d=-1/0,m=null,p=!1;function c(){if(p||a){u=!0;return}let f=t-(r()-d);if(f>0){l(f);return}a=!0,u=!1,d=r();let w=!1;e(()=>{w||(w=!0,a=!1,u&&!p&&c())})}function l(f){p||m!==null||(m=o(()=>{m=null,c()},Math.max(0,f)))}return{signaler(){p||c()},maintenant(){p||(d=-1/0,c())},arreter(){p=!0,m!==null&&(s(m),m=null)}}}function wn(e,n,t=J,r={}){let o=r.fetch||(typeof fetch==\"function\"?fetch:void 0);if(!o)return Promise.reject(new Error(\"fetch indisponible\"));let s=r.setTimer||((l,g)=>setTimeout(l,g)),a=r.clearTimer||(l=>clearTimeout(l)),u=null;try{u=new AbortController}catch(l){}let d=u?Me(G({},n||{}),{signal:u.signal}):G({},n||{}),m=null,p=new Promise((l,g)=>{m=s(()=>{try{u==null||u.abort()}catch(f){}g(new Error(\"delai\"))},t)}),c=()=>{try{a(m)}catch(l){}};return Promise.race([o(e,d),p]).then(l=>(c(),l),l=>{throw c(),l})}function Mn(e){let n=e.now||(()=>Date.now()),t=Math.max(1,e.rafale),r=t,o=n();return{prendre(){let s=n();return r=Math.min(t,r+(s-o)*e.parHeure/36e5),o=s,r<1?!1:(r-=1,!0)},restants:()=>r}}function xn(e={}){var m;let n=(m=e.minMs)!=null?m:0,t=e.now||(()=>Date.now()),r=e.setTimer||((p,c)=>setTimeout(p,c)),o=new Map,s=!1,a=-1/0,u=!1;function d(){if(s||o.size===0)return;let p=n-(t()-a);if(p>0){if(u)return;u=!0,r(()=>{u=!1,d()},p);return}let c=o.keys().next().value,l=o.get(c);o.delete(c),s=!0,a=t(),Promise.resolve().then(l.tache).then(g=>l.regler(g),()=>l.regler(null)).then(()=>{s=!1,d()})}return{poser(p,c){return new Promise(l=>{let g=o.get(p);o.set(p,{tache:c,regler:f=>{g&&g.regler(f),l(f)}}),d()})},enAttente:()=>o.size}}function Tn(e,n=te){try{if(e){let t=e.getItem(M.presenceId);if(t)return t;let r=`p-${n()}`;return e.setItem(M.presenceId,r),r}}catch(t){}return`p-${n()}`}var $={};v($,{messageClassName:()=>En,renderActions:()=>Oe,renderAttachment:()=>Ne,renderMessage:()=>_n,renderReactions:()=>Le});function Le(e,n){let t=e&&e.reactions||{},r=re(n),o=\"\";for(let s in t){let a=t[s]||[];if(!a.length)continue;let u=a.indexOf(r)>=0;o+=`<button class=\"re-chip${u?\" on\":\"\"}\" data-e=\"${y(s)}\">${y(s)} ${a.length}</button>`}return o}function Ne(e){if(!e||!e.url)return\"\";let n=y(e.url);if(e.kind===\"image\")return`<a class=cm-att href=\"${n}\" target=_blank rel=noreferrer><img src=\"${n}\" alt=\"\" loading=lazy></a>`;let t=y(e.name||\"Document.pdf\");return`<a class=cm-att-pdf href=\"${n}\" target=_blank rel=noreferrer data-pdf=\"${n}\"><span class=cm-att-ph></span><span class=cm-pdflabel>${t}</span></a>`}function Oe(e,n){let t=T(e,n.me),r=`<button class=cm-react title=\"R\\xE9agir\">${n.reactIcon||\"\"}</button><button class=cm-reply title=\"R\\xE9pondre\">\\u21A9</button>`;return t&&(r+='<button class=cm-edit title=\"Modifier\">\\u270E</button>'),(t||oe(n.me))&&(r+='<button class=cm-del-btn title=\"Supprimer\">\\u{1F5D1}</button>'),r}function _n(e,n){if(e.deleted)return'<span class=a></span><span class=b><div class=\"txt cm-del\">Message supprim\\xE9</div></span>';let t=e.reply_to?`<div class=cm-q><b>${y(e.reply_name||\"\")}</b> ${y(e.reply_text||\"\")}</div>`:\"\",r=e.edited?\" <span class=cm-ed>(modifi\\xE9)</span>\":\"\",o=e.body?`<div class=txt>${ne(e.body)}${r}</div>`:\"\",s=e.is_presenter?\" <span class=tag>pr\\xE9sentateur</span>\":\"\";return`<span class=a>${ee(e.author_avatar,e.author_name)}</span><span class=b><div class=who><b>${y(e.author_name||\"Invit\\xE9\")}</b>${s}</div>`+t+o+Ne(e.attachment)+`<div class=cm-re>${Le(e,n.me)}</div></span><span class=cm-act>${Oe(e,n)}</span>`}function En(e,n,t){return\"cm\"+(T(e,n)?\" me\":\"\")+(e.deleted?\" isdel\":\"\")+(t?\" mentioned\":\"\")}var H={};v(H,{DEFAULT_CENTER:()=>ae,DEFAULT_ZOOM:()=>ue,MAP_TYPES:()=>P,cycleMapType:()=>Sn,initialMapContent:()=>Rn,mapTypeLabel:()=>kn,sanitizeContent:()=>le});var P=[\"roadmap\",\"satellite\",\"hybrid\"],ae=[46.6,2.5],ue=6;function _(e){let n=Number(e);return Number.isFinite(n)?n:null}function se(e){if(!Array.isArray(e))return null;let n=_(e[0]),t=_(e[1]);return n!=null&&t!=null?[n,t]:null}var ie=(e,n,t)=>Math.max(n,Math.min(t,e));function le(e){if(!e||typeof e!=\"object\")return null;let n=e;if(n.kind===\"pdf\"||n.kind==null)return null;if(n.kind===\"streetview\"){let t=se(n.position);if(!t)return null;let r=n.pov&&typeof n.pov==\"object\"?n.pov:null,o=r?{heading:_(r.heading)||0,pitch:ie(_(r.pitch)||0,-90,90)}:{heading:0,pitch:0};return{kind:\"streetview\",position:t,pov:o,zoom:ie(_(n.zoom)||1,0,5)}}return n.kind!==\"map\"?null:{kind:\"map\",center:se(n.center)||[...ae],zoom:ie(Math.trunc(_(n.zoom)||ue),1,21),marker:se(n.marker),mapType:P.includes(n.mapType)?n.mapType:null,label:String(n.label||\"\").slice(0,160)||null}}function Sn(e){let n=P.indexOf(e);return P[(n+1)%P.length]}function kn(e){return e===\"roadmap\"?\"\\u{1F6F0} Satellite\":e===\"satellite\"?\"\\u{1F5FA} Hybride\":\"\\u{1F5FA} Plan\"}function Rn(){return{kind:\"map\",center:[...ae],zoom:ue,marker:null,mapType:null,label:null}}var D={};v(D,{CURRENT_PAGE_MARGIN:()=>Nn,DEFAULT_ASPECT:()=>In,MAX_ZOOM:()=>An,MIN_PAGE_WIDTH:()=>Cn,MIN_ZOOM:()=>Pn,PRERENDER_MARGIN:()=>Ln,arrowState:()=>$n,averageColor:()=>Wn,clampPage:()=>Ue,clampZoom:()=>On,fitWidth:()=>Dn,isImageDocument:()=>Bn,progressPercent:()=>Un,workerBlobUrl:()=>Vn});var Pn=.5,An=3,In=1.35,Cn=280,Ln=\"500px 0px\",Nn=\"-48% 0px -48% 0px\";function On(e){let n=Math.round((Number(e)||0)*10)/10;return Math.max(.5,Math.min(3,n))}function Ue(e,n){return Math.max(1,Math.min(n||1,Number(e)||1))}function Un(e,n){return n?Math.round(Ue(e,n)/n*100):0}function $n(e,n){return{prevDisabled:e<=1,nextDisabled:!!n&&e>=n}}var Hn=28;function Dn(e){var a;let n=(a=e.zoom)!=null?a:1,t=Math.max(280,e.containerWidth||900)*n;if(!e.onePage)return t;let r=e.aspect||1.35,s=((e.containerHeight||600)-Hn-(e.overlap||0)-(e.reserve||0))/r;return s>120?Math.min(t,s):t}function Wn(e){let n=e.length/4;if(!n)return\"rgb(0,0,0)\";let t=0,r=0,o=0;for(let s=0;s<e.length;s+=4)t+=e[s],r+=e[s+1],o+=e[s+2];return`rgb(${Math.round(t/n)},${Math.round(r/n)},${Math.round(o/n)})`}function Bn(e,n){return/[.](png|jpe?g|webp|gif|avif)($|[?])/i.test(String(e||n||\"\"))}async function Vn(e,n,t={}){let r=t.fetch||(typeof fetch==\"function\"?(a=>fetch(a)):null),o=t.subtle!==void 0?t.subtle:typeof globalThis!=\"undefined\"&&globalThis.crypto?globalThis.crypto.subtle:null,s=t.creerUrl||(a=>URL.createObjectURL(a));if(!r||!o||!n)return null;try{let a=await r(e);if(!a.ok)return null;let u=await a.arrayBuffer(),d=await o.digest(\"SHA-384\",u);return`sha384-${zn(new Uint8Array(d))}`!==n?null:s(new Blob([u],{type:\"application/javascript\"}))}catch(a){return null}}function zn(e){let n=\"\";for(let t of e)n+=String.fromCharCode(t);return btoa(n)}var W={};v(W,{presentationTransition:()=>Fn,switchDocUrl:()=>jn});function Fn(e,n={}){if(!e)return[{kind:\"nothing\"}];if(e.active===!1)return[{kind:\"ended\"}];let t=le(e.content);if(t)return[{kind:\"show-map\",content:t}];let r=[{kind:\"leave-map\"}];return e.file_url&&e.file_url!==n.docUrl?(r.push({kind:\"switch-doc\",url:e.file_url,name:e.file_name||void 0,title:e.doc_title||void 0,updatedAt:e.updated_at||void 0}),r):(e.current_page&&r.push({kind:\"show-page\",page:e.current_page}),r)}function jn(e,n){let t=encodeURIComponent(n||String(Date.now()));return`${e}${e.includes(\"?\")?\"&\":\"?\"}v=${t}`}return Fe(Gn);})();", SOURCE_HASH: "0ba592364fc3eb81" };
|
|
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,\"&\").replace(/</g,\"<\").replace(/>/g,\">\").replace(/\"/g,\""\")}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,6 +1,6 @@
|
|
|
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
|
|
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");
|
|
@@ -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
|
|
75
|
-
//
|
|
76
|
-
//
|
|
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
|
-
|
|
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
|
|
@@ -93,7 +102,13 @@ const LEAFLET = "https://unpkg.com/leaflet@1.9.4/dist/leaflet.js";
|
|
|
93
102
|
// scripts, donc l'intégrité restera hors de portée ici ; épingler la version trimestrielle rend
|
|
94
103
|
// au moins le changement DÉCIDÉ, et daté. À relever à chaque montée — Google retire les versions
|
|
95
104
|
// après environ un an.
|
|
96
|
-
|
|
105
|
+
// ⚠️ UNE VERSION INVALIDE N'EST PAS UNE ERREUR, C'EST UN CANAL PAR DÉFAUT SILENCIEUX. Google ne
|
|
106
|
+
// sert qu'une fenêtre glissante de versions (~4 trimestres) ; en dehors, le paramètre est IGNORÉ
|
|
107
|
+
// et le canal hebdomadaire se charge — l'épinglage devient une illusion sans qu'aucune erreur ne
|
|
108
|
+
// le dise. « 3.58 » a menti ainsi pendant des mois (cinquième audit). À vérifier à chaque
|
|
109
|
+
// trimestre — le commentaire d'à côté doit porter la date du dernier contrôle.
|
|
110
|
+
// Contrôlé le 18/08/2026 : fenêtre servie 3.62 → 3.65.
|
|
111
|
+
const MAPS_VERSION = "3.65";
|
|
97
112
|
|
|
98
113
|
/**
|
|
99
114
|
* Empreintes des dépendances tierces (constat P2-4).
|
|
@@ -116,14 +131,12 @@ const MAPS_VERSION = "3.58";
|
|
|
116
131
|
* doivent être comparés par quelqu'un au moins une fois.
|
|
117
132
|
*/
|
|
118
133
|
const TIERS = {
|
|
119
|
-
pdf: { url: PDFJS + "/pdf.min.js", sri: "sha384-/1qUCSGwTur9vjf/z9lmu/eCUYbpOTgSjmpbMQZ1/CtX2v/WcAIKqRv+U1DUCG6e" },
|
|
120
134
|
// ⚠️ Le worker N'A PAS DE BALISE, donc pas d'attribut `integrity` : il est chargé par pdf.js,
|
|
121
135
|
// pas par le document. Mais ses octets passent déjà par notre code (ils sont récupérés en texte
|
|
122
136
|
// puis transformés en blob de même origine, sans quoi le navigateur refuserait un worker
|
|
123
137
|
// distant) — on les vérifie donc à la main, là où ils passent. Sans ça, l'empreinte de
|
|
124
138
|
// `pdf.min.js` protégerait la petite moitié et laisserait la grande (1 Mo contre 300 ko) entrer
|
|
125
139
|
// sans contrôle, alors même qu'elle voit tout le contenu du document.
|
|
126
|
-
pdfWorker: { url: PDFJS + "/pdf.worker.min.js", sri: "sha384-SnzOobpRMLXZ52iJvZm/C0fYw0OQemTXzTjIsdsfMcrCtCEe9qgzxTd3RSklO5x2" },
|
|
127
140
|
supa: { url: SUPAJS, sri: "sha384-qafw21c/iciq0VXsi9FzkfoQv5I/V0iqE4lSNcKXPnW9/UTJLnv5CcN4FHxVLnKg" },
|
|
128
141
|
leaflet: { url: LEAFLET, sri: "sha384-cxOPjt7s7Iz04uaHJceBmS+qpjv2JkIHNVcuOrM+YHwZOmJGBXI00mdUXEq65HTH" },
|
|
129
142
|
// ⚠️ UNE FEUILLE DE STYLE TIERCE N'EST PAS INOFFENSIVE, et celle-ci n'était comptée nulle part.
|
|
@@ -1744,9 +1757,11 @@ function sendHtml(res, status, html, scriptSrc, imgExtra, frameAncestors) {
|
|
|
1744
1757
|
res.setHeader("Referrer-Policy", "strict-origin-when-cross-origin");
|
|
1745
1758
|
res.setHeader("Content-Security-Policy", [
|
|
1746
1759
|
"default-src 'none'",
|
|
1747
|
-
`
|
|
1748
|
-
|
|
1749
|
-
`
|
|
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 : ""}`,
|
|
1750
1765
|
`img-src 'self' data: blob:${imgExtra ? " " + imgExtra : ""}`,
|
|
1751
1766
|
`media-src 'self'${supaOrigin ? " " + supaOrigin : ""}`,
|
|
1752
1767
|
"style-src 'unsafe-inline'",
|
|
@@ -2023,12 +2038,11 @@ function viewerHtml(share, nonce, logoUrl, pitch) {
|
|
|
2023
2038
|
// En aperçu interne, on embarque de quoi démarrer une présentation live (URL Storage brute + métadonnées).
|
|
2024
2039
|
// `fileName` : c'est LUI qui dit la nature du document côté page. L'URL publique est
|
|
2025
2040
|
// `/api/doc?slug=…&file=1`, sans extension — sans ce champ, une image partait dans pdf.js.
|
|
2026
|
-
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 });
|
|
2027
2042
|
return `<!doctype html><html lang=fr><head><meta charset=utf-8>
|
|
2028
2043
|
<meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=3,viewport-fit=cover,interactive-widget=resizes-content">
|
|
2029
2044
|
<meta name=robots content="noindex,nofollow">
|
|
2030
2045
|
<link rel=icon href="data:,">
|
|
2031
|
-
<link rel=preconnect href="https://cdnjs.cloudflare.com" crossorigin>
|
|
2032
2046
|
<title>${esc(titreOnglet(share))}</title>
|
|
2033
2047
|
<style>
|
|
2034
2048
|
:root{--bg:#33312e;--bar:#26241f}
|
|
@@ -2223,7 +2237,6 @@ ${LEGAL_CSS}
|
|
|
2223
2237
|
${legalFooter({ tracked: !preview && !!share.slug, sansExpediteur: !share.recipient_email && !share.created_by })}
|
|
2224
2238
|
${brandLogo || !brandIntroRuntime ? "" : `<script nonce="${nonce}">(${brandIntroRuntime.toString()})();</script>`}
|
|
2225
2239
|
<script nonce="${nonce}">${PLAYER_BROWSER_JS}</script>
|
|
2226
|
-
${balise(nonce, TIERS.pdf)}
|
|
2227
2240
|
${preview ? `${balise(nonce, TIERS.supa)}
|
|
2228
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}
|
|
2229
2242
|
${MAP_JS}</script>` : ""}
|
|
@@ -2528,7 +2541,10 @@ ${LEGAL_CSS}
|
|
|
2528
2541
|
}
|
|
2529
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'; }
|
|
2530
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); } }
|
|
2531
|
-
if(!window.pdfjsLib)
|
|
2544
|
+
// (Ici vivait « if(!window.pdfjsLib) return » — sentinelle de l'époque où 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.)
|
|
2532
2548
|
function start(){
|
|
2533
2549
|
// Ouverture journalisée, chrono lancé, écouteurs de visibilité / focus / inactivité posés.
|
|
2534
2550
|
T.start();
|
|
@@ -2569,11 +2585,13 @@ ${LEGAL_CSS}
|
|
|
2569
2585
|
if(IS_IMG){ start(); return; }
|
|
2570
2586
|
loadError("Document non affiche : une dependance n a pas pu etre verifiee.");
|
|
2571
2587
|
}
|
|
2572
|
-
|
|
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).
|
|
2573
2590
|
try{
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
pdfjsLib.GlobalWorkerOptions.workerSrc=
|
|
2591
|
+
import(CFG.pdfjs).then(function(m){
|
|
2592
|
+
window.pdfjsLib=m;
|
|
2593
|
+
pdfjsLib.GlobalWorkerOptions.workerSrc=CFG.pdfjsWorker;
|
|
2594
|
+
start();
|
|
2577
2595
|
}).catch(refuserWorker);
|
|
2578
2596
|
}catch(e){ refuserWorker(); }
|
|
2579
2597
|
// Bord à bord en une-page mobile : chaque millimètre compte, surtout pour un PDF paysage.
|
|
@@ -2689,8 +2707,9 @@ ${LEGAL_CSS}
|
|
|
2689
2707
|
var tl=document.createElement('div'); tl.className='textLayer';
|
|
2690
2708
|
tl.style.width=v.width+'px'; tl.style.height=v.height+'px'; tl.style.setProperty('--scale-factor', scale);
|
|
2691
2709
|
el.appendChild(tl);
|
|
2692
|
-
try{ pdfjsLib.
|
|
2693
|
-
catch(e){ try{ pdfjsLib.renderTextLayer({
|
|
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){} } }
|
|
2694
2713
|
}); }catch(e){}
|
|
2695
2714
|
}); }
|
|
2696
2715
|
// ── Assistant IA « présentateur » : chat requête/réponse (bot-start/bot-say) + saut de page piloté ──
|
|
@@ -2700,7 +2719,7 @@ ${botOn && botBrowser ? botBrowser.botViewerJs(ICONS) : ""}
|
|
|
2700
2719
|
</body></html>`;
|
|
2701
2720
|
}
|
|
2702
2721
|
|
|
2703
|
-
// CSP de la page audience (Présenter) :
|
|
2722
|
+
// CSP de la page audience (Présenter) : pdf.js vient de NOTRE origine ; supabase-js (jsdelivr) et la connexion
|
|
2704
2723
|
// Realtime (https + wss vers le projet Supabase). Plus permissive que la visionneuse, limitée à cette page.
|
|
2705
2724
|
/**
|
|
2706
2725
|
* Les origines d'images qu'une page a le droit de charger.
|
|
@@ -2758,9 +2777,9 @@ function sendPresentHtml(res, html, nonce, supaUrl, imgExtra, frameAncestors) {
|
|
|
2758
2777
|
res.setHeader("Referrer-Policy", "strict-origin-when-cross-origin");
|
|
2759
2778
|
res.setHeader("Content-Security-Policy", [
|
|
2760
2779
|
"default-src 'none'",
|
|
2761
|
-
`script-src 'nonce-${nonce}'
|
|
2762
|
-
"worker-src 'self'
|
|
2763
|
-
`connect-src 'self' https://
|
|
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}`,
|
|
2764
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 : ""}`,
|
|
2765
2784
|
"style-src 'unsafe-inline' https://unpkg.com https://fonts.googleapis.com",
|
|
2766
2785
|
"font-src https://fonts.gstatic.com data:",
|
|
@@ -2788,11 +2807,10 @@ function presentHtml(pres, nonce, logoUrl, supaUrl, supaKey) {
|
|
|
2788
2807
|
const presenter = esc(pres.presenter_name || "");
|
|
2789
2808
|
const logo = esc(logoUrl || "");
|
|
2790
2809
|
const fileUrl = `/api/doc?present=${encodeURIComponent(pres.slug)}&file=1`;
|
|
2791
|
-
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" });
|
|
2792
2811
|
return `<!doctype html><html lang=fr><head><meta charset=utf-8>
|
|
2793
2812
|
<meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=3">
|
|
2794
2813
|
<meta name=robots content="noindex,nofollow">
|
|
2795
|
-
<link rel=preconnect href="https://cdnjs.cloudflare.com" crossorigin>
|
|
2796
2814
|
<title>${esc(PLAYER.branding.title(pres.doc_title || pres.file_name || "Document", "Présentation"))}</title>
|
|
2797
2815
|
<style>
|
|
2798
2816
|
:root{--bg:#23211e;--bar:#1a1916}
|
|
@@ -2858,7 +2876,6 @@ function presentHtml(pres, nonce, logoUrl, supaUrl, supaKey) {
|
|
|
2858
2876
|
${PLAYER.branding.poweredBy ? `<div class=brand>Propulsé par ${esc(PLAYER.branding.poweredBy)}</div>` : ""}
|
|
2859
2877
|
${legalFooter({ tracked: true })}
|
|
2860
2878
|
<script nonce="${nonce}">${PLAYER_BROWSER_JS}</script>
|
|
2861
|
-
${balise(nonce, TIERS.pdf)}
|
|
2862
2879
|
${balise(nonce, TIERS.supa)}
|
|
2863
2880
|
<script nonce="${nonce}">
|
|
2864
2881
|
(function(){
|
|
@@ -2894,11 +2911,11 @@ function presentHtml(pres, nonce, logoUrl, supaUrl, supaKey) {
|
|
|
2894
2911
|
pageEl.innerHTML=''; pageEl.appendChild(cv);
|
|
2895
2912
|
});
|
|
2896
2913
|
}
|
|
2897
|
-
var wsrc=CFG.pdfjs+'/pdf.worker.min.js';
|
|
2898
2914
|
function boot(){
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
pdfjsLib.GlobalWorkerOptions.workerSrc=
|
|
2915
|
+
import(CFG.pdfjs).then(function(m){
|
|
2916
|
+
window.pdfjsLib=m;
|
|
2917
|
+
pdfjsLib.GlobalWorkerOptions.workerSrc=CFG.pdfjsWorker;
|
|
2918
|
+
load();
|
|
2902
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."; });
|
|
2903
2920
|
}
|
|
2904
2921
|
// ⚠️ UNE PRÉSENTATION PEUT PORTER UNE IMAGE, ET CETTE VUE NE LE SAVAIT PAS. Le bouton
|
|
@@ -2968,7 +2985,8 @@ function presentHtml(pres, nonce, logoUrl, supaUrl, supaKey) {
|
|
|
2968
2985
|
CFG.fileUrl='/api/doc?present='+encodeURIComponent(CFG.slug)+'&file=1&v='+encodeURIComponent(row.updated_at||String(row.current_page||1));
|
|
2969
2986
|
load();
|
|
2970
2987
|
}
|
|
2971
|
-
|
|
2988
|
+
// La balise pdf.min.js n'existe plus : boot() importe lui-même la bibliothèque.
|
|
2989
|
+
boot();
|
|
2972
2990
|
window.__refit=function(){ if(ready) show(cur); };
|
|
2973
2991
|
var _rzA; window.addEventListener('resize',function(){ clearTimeout(_rzA); _rzA=setTimeout(function(){ if(ready) show(cur); },140); });
|
|
2974
2992
|
// Ce que l'audience fait d'un état reçu — player/src/presentation-state.ts, testé.
|
|
@@ -3582,24 +3600,43 @@ async function handler(req, res) {
|
|
|
3582
3600
|
const dejaLa = await PLAYER.db.request(
|
|
3583
3601
|
`commercial_doc_shares?doc_id=eq.${encodeURIComponent(docId)}&created_by=is.null&recipient_email=is.null${filtreAtteste}&select=slug&limit=1`,
|
|
3584
3602
|
);
|
|
3603
|
+
const cleHote = `hote:${docId}|${atteste || ""}`;
|
|
3585
3604
|
if (Array.isArray(dejaLa) && dejaLa[0]) {
|
|
3605
|
+
// Réemploi d'une ligne historique (sans clé) : on lui POSE la clé au passage — les
|
|
3606
|
+
// demandes suivantes la trouveront par l'unicité, et les doublons d'avant 0011
|
|
3607
|
+
// s'éteignent d'eux-mêmes faute d'être resservis.
|
|
3608
|
+
const cleDispo = await require("./schema").attendue("liensUniques");
|
|
3586
3609
|
await PLAYER.db.request(`commercial_doc_shares?slug=eq.${encodeURIComponent(dejaLa[0].slug)}`, {
|
|
3587
3610
|
method: "PATCH", headers: { Prefer: "return=minimal" },
|
|
3588
|
-
body: { doc_title: body.docTitle || null, file_url: String(body.fileUrl), file_name: body.fileName || null, revoked: false },
|
|
3611
|
+
body: { doc_title: body.docTitle || null, file_url: String(body.fileUrl), file_name: body.fileName || null, revoked: false, ...(cleDispo ? { idem_key: cleHote } : {}) },
|
|
3589
3612
|
});
|
|
3590
3613
|
return jd(200, { ok: true, slug: dejaLa[0].slug, reused: true });
|
|
3591
3614
|
}
|
|
3592
3615
|
// `createdBy` reste NUL : personne ne l'a créé. Le filtre « mes liens » compare
|
|
3593
3616
|
// `created_by=eq.<email>`, qui exclut les NUL — ce lien n'apparaît donc dans la liste
|
|
3594
3617
|
// de personne, et reste visible en administration (`list.all`, qui ne filtre pas).
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3618
|
+
// ⚠️ LA LECTURE-PUIS-ÉCRITURE NE PROMET RIEN : deux demandes dans la même seconde
|
|
3619
|
+
// passaient toutes deux le « déjà là ? » et DEUX liens naissaient — les statistiques du
|
|
3620
|
+
// document se fragmentent entre eux. La contrainte (0011) promet ; le 409 qu'elle rend
|
|
3621
|
+
// est une CONFIRMATION : l'autre demande a gagné, on relit son lien et on le rend
|
|
3622
|
+
// `reused` — même règle que le renvoi d'un message (0005).
|
|
3623
|
+
try {
|
|
3624
|
+
const neuf = await createShare({
|
|
3625
|
+
brandKey: body.brandKey, docId, docTitle: body.docTitle, fileUrl: body.fileUrl,
|
|
3626
|
+
fileName: body.fileName, createdBy: null, attestedRecipientEmail: atteste || null,
|
|
3627
|
+
bot: body.bot, botScript: body.botScript,
|
|
3628
|
+
guided: body.guided, profileId: body.profileId, allowDownload: body.allowDownload,
|
|
3629
|
+
videoLayout: body.videoLayout, logo: body.logo, logoDark: body.logoDark,
|
|
3630
|
+
idemKey: cleHote,
|
|
3631
|
+
});
|
|
3632
|
+
return jd(200, { ok: true, slug: neuf.slug, reused: false });
|
|
3633
|
+
} catch (erreur) {
|
|
3634
|
+
if (!String((erreur && erreur.message) || "").includes("409")) throw erreur;
|
|
3635
|
+
try { PLAYER.errors.capture(new Error("lien hôte déjà créé par une demande simultanée : " + docId), { route: "hostshare", benin: true }); } catch { /* jamais bloquant */ }
|
|
3636
|
+
const gagnant = await PLAYER.db.request(`commercial_doc_shares?idem_key=eq.${encodeURIComponent(cleHote)}&select=slug&limit=1`);
|
|
3637
|
+
if (!Array.isArray(gagnant) || !gagnant[0]) throw erreur; // 409 d'autre chose : on ne l'invente pas
|
|
3638
|
+
return jd(200, { ok: true, slug: gagnant[0].slug, reused: true });
|
|
3639
|
+
}
|
|
3603
3640
|
}
|
|
3604
3641
|
|
|
3605
3642
|
const u = await PLAYER.identity.verifyToken(req.headers.authorization);
|
|
@@ -3637,12 +3674,23 @@ async function handler(req, res) {
|
|
|
3637
3674
|
const docId = String(body.docId || "");
|
|
3638
3675
|
if (!docId || !body.fileUrl) return jd(400, { ok: false, error: "docId/fileUrl requis" });
|
|
3639
3676
|
const ex = await PLAYER.db.request(`commercial_doc_shares?doc_id=eq.${encodeURIComponent(docId)}&is_test=eq.true&select=slug&limit=1`);
|
|
3677
|
+
const cleTest = `repetition:${docId}`;
|
|
3640
3678
|
if (Array.isArray(ex) && ex[0]) {
|
|
3641
|
-
|
|
3679
|
+
const cleDispo = await require("./schema").attendue("liensUniques");
|
|
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 } : {}) } });
|
|
3642
3681
|
return jd(200, { ok: true, slug: ex[0].slug });
|
|
3643
3682
|
}
|
|
3644
|
-
|
|
3645
|
-
|
|
3683
|
+
// Deux ouvertures simultanées de la répétition : la contrainte tranche, le perdant relit.
|
|
3684
|
+
try {
|
|
3685
|
+
const t = await createShare({ docId, docTitle: body.docTitle, fileUrl: body.fileUrl, fileName: body.fileName, recipientName: "Répétition (test)", createdBy: u.email, bot: true, guided: true, profileId: body.profileId, isTest: true, idemKey: cleTest });
|
|
3686
|
+
return jd(200, { ok: true, slug: t.slug });
|
|
3687
|
+
} catch (erreur) {
|
|
3688
|
+
if (!String((erreur && erreur.message) || "").includes("409")) throw erreur;
|
|
3689
|
+
try { PLAYER.errors.capture(new Error("lien de répétition déjà créé par une demande simultanée : " + docId), { route: "docshare-test", benin: true }); } catch { /* jamais bloquant */ }
|
|
3690
|
+
const gagnant = await PLAYER.db.request(`commercial_doc_shares?idem_key=eq.${encodeURIComponent(cleTest)}&select=slug&limit=1`);
|
|
3691
|
+
if (!Array.isArray(gagnant) || !gagnant[0]) throw erreur;
|
|
3692
|
+
return jd(200, { ok: true, slug: gagnant[0].slug, reused: true });
|
|
3693
|
+
}
|
|
3646
3694
|
}
|
|
3647
3695
|
if (body.action === "docshare.revoke") {
|
|
3648
3696
|
await revokeShare(String(body.slug || ""));
|
|
@@ -3860,14 +3908,34 @@ async function handler(req, res) {
|
|
|
3860
3908
|
// AUCUN DOCUMENT DEMANDÉ. Ni slug, ni présentation, ni aperçu, ni carte d'identité — il n'y a
|
|
3861
3909
|
// rien à afficher, et ce n'est pas un refus. Le dire franchement évite qu'un intégrateur
|
|
3862
3910
|
// cherche un lien révoqué là où il lui manque un paramètre.
|
|
3863
|
-
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) {
|
|
3864
3912
|
res.statusCode = 400;
|
|
3865
3913
|
res.setHeader("Content-Type", "text/plain; charset=utf-8");
|
|
3866
|
-
res.end("Aucun document demandé. Attendu : ?slug=… , ?present=… , ?preview=1
|
|
3914
|
+
res.end("Aucun document demandé. Attendu : ?slug=… , ?present=… , ?preview=1, ?contract=1 ou ?asset=….\n" +
|
|
3867
3915
|
"Si vous intégrez le player, vérifiez que la plateforme fournit les paramètres de requête.");
|
|
3868
3916
|
return;
|
|
3869
3917
|
}
|
|
3870
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
|
+
let octets = null;
|
|
3928
|
+
try { octets = require("node:fs").readFileSync(require.resolve(fichier)); } catch { /* dépendance absente */ }
|
|
3929
|
+
if (!octets) { res.statusCode = 404; res.end("actif indisponible"); return; }
|
|
3930
|
+
res.statusCode = 200;
|
|
3931
|
+
res.setHeader("Content-Type", "text/javascript; charset=utf-8");
|
|
3932
|
+
res.setHeader("X-Content-Type-Options", "nosniff");
|
|
3933
|
+
res.setHeader("Cache-Control", "public, max-age=31536000, immutable");
|
|
3934
|
+
res.setHeader("Content-Length", String(octets.length));
|
|
3935
|
+
res.end(octets);
|
|
3936
|
+
return;
|
|
3937
|
+
}
|
|
3938
|
+
|
|
3871
3939
|
// ── CARTE D'IDENTITÉ (`?contract=1`) ─────────────────────────────────────────────────────
|
|
3872
3940
|
// La règle 4 du contrat demande à l'hôte d'épingler la version qu'il vise et de le VÉRIFIER.
|
|
3873
3941
|
// Sans point d'interrogation, cette règle était une intention : un hôte ne pouvait pas écrire
|
package/server/schema.js
CHANGED
|
@@ -42,6 +42,11 @@ const ATTENDUES = {
|
|
|
42
42
|
migration: "supabase/migrations/0005-envoi-unique.sql",
|
|
43
43
|
fonction: "empêcher qu'un renvoi crée un second message",
|
|
44
44
|
},
|
|
45
|
+
liensUniques: {
|
|
46
|
+
table: "commercial_doc_shares", colonne: "idem_key",
|
|
47
|
+
migration: "supabase/migrations/0011-liens-uniques.sql",
|
|
48
|
+
fonction: "empêcher deux demandes simultanées de créer deux liens système pour le même usage",
|
|
49
|
+
},
|
|
45
50
|
reactionsOrdonnees: {
|
|
46
51
|
table: "doc_presentation_messages", colonne: "reactions_seq",
|
|
47
52
|
migration: "supabase/migrations/0006-reactions-ordonnees.sql",
|
package/server/shares.js
CHANGED
|
@@ -15,10 +15,15 @@ function newSlug() { return crypto.randomBytes(9).toString("base64url"); } // ~1
|
|
|
15
15
|
|
|
16
16
|
// Crée un lien de partage (un par destinataire). Dénormalise titre/URL/nom pour résilience (le doc vit dans
|
|
17
17
|
// un snapshot). Renvoie le slug.
|
|
18
|
-
async function createShare({ docId, docTitle, fileUrl, fileName, recipientEmail, recipientName, attestedRecipientEmail, createdBy, bot, botScript, guided, profileId, allowDownload, isTest, videoLayout, logo, logoDark, brandKey}) {
|
|
18
|
+
async function createShare({ docId, docTitle, fileUrl, fileName, recipientEmail, recipientName, attestedRecipientEmail, createdBy, bot, botScript, guided, profileId, allowDownload, isTest, videoLayout, logo, logoDark, brandKey, idemKey}) {
|
|
19
19
|
if (!docId || !fileUrl) throw Object.assign(new Error("doc invalide"), { statusCode: 400 });
|
|
20
20
|
const slug = newSlug();
|
|
21
|
+
// ⚠️ LA CLÉ N'EST ÉCRITE QUE LÀ OÙ LA COLONNE EXISTE — PostgREST rejette le POST ENTIER sur une
|
|
22
|
+
// colonne inconnue : chez un hôte non migré, ce n'est pas l'unicité qu'on perdrait, c'est la
|
|
23
|
+
// CRÉATION de liens. Même sonde que la clé des messages (0005).
|
|
24
|
+
const cleDispo = idemKey ? await require("./schema").attendue("liensUniques") : false;
|
|
21
25
|
const row = {
|
|
26
|
+
...(cleDispo ? { idem_key: String(idemKey).slice(0, 300) } : {}),
|
|
22
27
|
slug, doc_id: String(docId), doc_title: docTitle || null, file_url: String(fileUrl), file_name: fileName || null,
|
|
23
28
|
recipient_email: low(recipientEmail) || null, recipient_name: (recipientName || "").trim() || null, created_by: low(createdBy) || null,
|
|
24
29
|
// ⚠️ DEUX CHAMPS, DEUX FAITS. `recipient_email` dit qui a le droit d'EXPÉDIER en son nom au
|
package/supabase/init.sql
CHANGED
|
@@ -51,11 +51,17 @@ create table if not exists public.commercial_doc_shares (
|
|
|
51
51
|
brand_dark boolean not null default false,
|
|
52
52
|
require_auth boolean not null default false,
|
|
53
53
|
brand_key text, -- ⚠️ RÉFÉRENCE résolue à l'affichage (branding.forKey)
|
|
54
|
+
-- Clé d'idempotence des liens SYSTÈME (hôte, répétition) — nulle sur les liens ordinaires.
|
|
55
|
+
-- Unique quand renseignée : deux demandes simultanées ne créent qu'un lien.
|
|
56
|
+
idem_key text,
|
|
54
57
|
-- Destinataire attesté par l'hôte : sert à ATTRIBUER une lecture, jamais à expédier en son nom.
|
|
55
58
|
-- `recipient_email`, elle, dit qui peut expédier — vide quand personne ne le peut.
|
|
56
59
|
attested_recipient_email text
|
|
57
60
|
);
|
|
58
61
|
create index if not exists cds_doc_id_idx on public.commercial_doc_shares (doc_id);
|
|
62
|
+
create unique index if not exists cds_idem_key_uniq
|
|
63
|
+
on public.commercial_doc_shares (idem_key)
|
|
64
|
+
where idem_key is not null;
|
|
59
65
|
create index if not exists cds_parent_idx on public.commercial_doc_shares (parent_slug);
|
|
60
66
|
create index if not exists idx_doc_shares_brand_key
|
|
61
67
|
on public.commercial_doc_shares (brand_key) where brand_key is not null;
|
|
@@ -329,9 +335,6 @@ alter table public.doc_bot_sessions enable row level security;
|
|
|
329
335
|
-- n'est pas un détail : il pousse un mainteneur à « réparer » un différé qui n'existe plus, en
|
|
330
336
|
-- ajoutant précisément la politique de lecture que ce fichier refuse.)
|
|
331
337
|
--
|
|
332
|
-
-- La publication de la table reste utile aux INSTANCES HISTORIQUES encore sur `postgres_changes`,
|
|
333
|
-
-- et inoffensive ici : publiée sans politique de lecture, elle ne livre rien à personne.
|
|
334
|
-
--
|
|
335
338
|
-- ⚠️ Ne mettez de toute façon jamais de donnée confidentielle dans un message de chat ni dans un
|
|
336
339
|
-- nom d'auteur.
|
|
337
340
|
-- ⚠️ AUCUNE TABLE DU PLAYER N'EST PUBLIÉE DANS `supabase_realtime`, ET C'EST UN CHOIX. Le direct
|
|
@@ -403,6 +406,8 @@ alter table public.doc_presentations
|
|
|
403
406
|
add column if not exists write_seq bigint not null default 0;
|
|
404
407
|
alter table public.doc_presentation_messages
|
|
405
408
|
add column if not exists client_key text;
|
|
409
|
+
alter table public.commercial_doc_shares
|
|
410
|
+
add column if not exists idem_key text;
|
|
406
411
|
-- Une base née d'un init.sql d'avant la 0008 porte un NOT NULL qui rend la clôture impossible.
|
|
407
412
|
alter table public.doc_presentations
|
|
408
413
|
alter column control_hash drop not null;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
-- DEUX DEMANDES SIMULTANÉES CRÉAIENT DEUX LIENS POUR LE MÊME USAGE.
|
|
2
|
+
--
|
|
3
|
+
-- Le lien « de l'hôte » (un par document et destinataire attesté) et le lien de répétition (un par
|
|
4
|
+
-- document) sont censés être UNIQUES : le code lisait « existe-t-il déjà ? » puis insérait — deux
|
|
5
|
+
-- requêtes dans la même seconde passaient toutes deux la lecture, et deux liens naissaient. Les
|
|
6
|
+
-- statistiques du document se fragmentent entre eux, sans que rien ne le dise. Constat P2 du
|
|
7
|
+
-- cinquième audit, même famille que l'idempotence des messages (0005) : la lecture-puis-écriture
|
|
8
|
+
-- ne promet rien, seule une contrainte promet.
|
|
9
|
+
--
|
|
10
|
+
-- ⚠️ La clé est NULLE sur les liens ordinaires (un membre peut envoyer le même document à autant
|
|
11
|
+
-- de destinataires qu'il veut) — l'index partiel ne contraint que les liens SYSTÈME, qui la
|
|
12
|
+
-- portent. Les doublons historiques restent en base, clé nulle : leurs URL sont distribuées, on
|
|
13
|
+
-- ne retire rien — le premier trouvé est réutilisé et reçoit la clé, les autres s'éteignent
|
|
14
|
+
-- d'eux-mêmes faute d'être resservis.
|
|
15
|
+
--
|
|
16
|
+
-- Sans lui : rien ne casse — la lecture-puis-écriture continue, avec sa course, comme avant.
|
|
17
|
+
alter table public.commercial_doc_shares
|
|
18
|
+
add column if not exists idem_key text;
|
|
19
|
+
|
|
20
|
+
comment on column public.commercial_doc_shares.idem_key is
|
|
21
|
+
'Clé d''idempotence des liens SYSTÈME (hôte, répétition) : « hote:<doc>|<destinataire attesté> » '
|
|
22
|
+
'ou « repetition:<doc> ». Nulle sur les liens ordinaires. Unique quand renseignée : deux '
|
|
23
|
+
'demandes simultanées ne créent qu''un lien, la seconde relit le gagnant.';
|
|
24
|
+
|
|
25
|
+
create unique index if not exists cds_idem_key_uniq
|
|
26
|
+
on public.commercial_doc_shares (idem_key)
|
|
27
|
+
where idem_key is not null;
|