discovery-media-player 0.1.10 → 0.1.12

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.
@@ -13,6 +13,7 @@
13
13
  // ⚠️ Il ne remplace pas un câblage : il ne sait rien de vos rôles. Ce qu'il ne sait pas, il le
14
14
  // REFUSE — jamais il n'accorde par défaut. Cf. docs/CONFIGURATION.md, « Decisions that are yours ».
15
15
 
16
+ const crypto = require("node:crypto");
16
17
  const storage = require("./storage");
17
18
 
18
19
  /**
@@ -252,6 +253,29 @@ function createStandaloneContext(env = process.env) {
252
253
  return r.ok ? await r.json() : null;
253
254
  } catch { return null; }
254
255
  },
256
+ /**
257
+ * Cet appel vient-il de l'HÔTE lui-même, et non d'un de ses membres ?
258
+ *
259
+ * ⚠️ UN SECRET DISTINCT DE `PLAYER_HOST_FETCH_SECRET`, ET C'EST LE POINT. Celui-là ne
260
+ * circule QUE dans un sens — le player l'envoie à l'hôte, à chaque fichier. Il traîne donc
261
+ * dans les journaux d'accès de l'hôte, ses proxys, son traceur d'erreurs. Aujourd'hui, qui
262
+ * l'obtient peut se faire passer pour le player AUPRÈS de l'hôte. L'accepter en entrée lui
263
+ * ajouterait le droit d'écrire ICI — créer des liens tracés qui collecteront des lectures.
264
+ * Une variable de plus contre un rayon d'explosion qui ne grandit pas : un secret ne suit
265
+ * ni un changement de destinataire, ni un changement de direction.
266
+ *
267
+ * ⚠️ Le cœur ne voit jamais le secret : il pose une QUESTION, l'adaptateur répond oui ou
268
+ * non. Non configuré ⇒ non — un pouvoir qu'on ne sait pas accorder ne s'accorde pas.
269
+ */
270
+ isTrustedHostCall(headers) {
271
+ const attendu = String(env.PLAYER_HOST_SHARE_SECRET || "");
272
+ const recu = String((headers && (headers["x-player-share-secret"] || headers["X-Player-Share-Secret"])) || "");
273
+ if (!attendu || !recu) return false;
274
+ // Comparaison à temps constant : une comparaison naïve fuit la longueur du préfixe commun.
275
+ const a = Buffer.from(attendu, "utf8"); const b = Buffer.from(recu, "utf8");
276
+ return a.length === b.length && crypto.timingSafeEqual(a, b);
277
+ },
278
+
255
279
  // ⚠️ Le rôle vient d'`app_metadata`, JAMAIS d'`user_metadata` : ce dernier est modifiable par
256
280
  // l'utilisateur lui-même. S'y fier revient à laisser chacun choisir ses droits.
257
281
  roleOf: (user) => String(((user || {}).app_metadata || {}).role || "").trim().toLowerCase(),
@@ -329,6 +353,9 @@ function createStandaloneContext(env = process.env) {
329
353
  // les deux, une variable oubliée redonne exactement le symptôme que 0.1.8 a retiré — des
330
354
  // membres « non authentifiés », ce qui ressemble à un droit manquant.
331
355
  separateIssuer: !!sansBarreFinale(env.PLAYER_AUTH_URL),
356
+ // Même raison que ci-dessus : « la capacité existe » et « elle est configurée »
357
+ // sont deux questions, et seule la seconde explique un refus.
358
+ hostShare: !!String(env.PLAYER_HOST_SHARE_SECRET || ""),
332
359
  },
333
360
  };
334
361
  }
@@ -26,9 +26,10 @@ need.
26
26
  "product": "discovery-media-player",
27
27
  "contract": 1,
28
28
  "version": "0.1.9",
29
- "capabilities": ["docshare", "presentations", "embed-denied", "host-fetch", "brand-reference", "host-auth"],
29
+ "capabilities": ["docshare", "presentations", "embed-denied", "host-fetch", "brand-reference", "host-auth", "host-share"],
30
30
  "frameAncestors": ["'self'", "https://*.vercel.app", "https://app.example.com"],
31
31
  "separateIssuer": true,
32
+ "hostShare": true,
32
33
  "plugins": { "bot": false, "visitors": false, "brandIntro": false, "botBrowser": false, "providerQuotas": false }
33
34
  }
34
35
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "discovery-media-player",
3
- "version": "0.1.10",
3
+ "version": "0.1.12",
4
4
  "description": "Self-hosted document viewer: per-recipient tracked links, reading analytics, live presentation. The core knows nothing about the application hosting it — everything it borrows arrives through an injected context.",
5
5
  "keywords": [
6
6
  "pdf-viewer",
@@ -4,4 +4,4 @@
4
4
  // Injecté par api/doc.js dans un <script nonce=…> de la visionneuse. Le code navigateur du
5
5
  // player vit en modules TypeScript testés sous player/src/ — c'est ici qu'il atterrit.
6
6
  "use strict";
7
- module.exports = { PLAYER_BROWSER_JS: "\"use strict\";var Player=(()=>{var U=Object.defineProperty;var ye=Object.getOwnPropertyDescriptor;var ve=Object.getOwnPropertyNames;var Me=Object.prototype.hasOwnProperty;var d=(e,n)=>{for(var t in n)U(e,t,{get:n[t],enumerable:!0})},xe=(e,n,t,r)=>{if(n&&typeof n==\"object\"||typeof n==\"function\")for(let o of ve(n))!Me.call(e,o)&&o!==t&&U(e,o,{get:()=>n[o],enumerable:!(r=ye(n,o))||r.enumerable});return e};var we=e=>xe(U({},\"__esModule\",{value:!0}),e);var dn={};d(dn,{bridge:()=>E,chat:()=>I,live:()=>A,presentation:()=>C,presentationState:()=>$,tracking:()=>S,viewer:()=>L});var E={};d(E,{onHostMessage:()=>Ce,onPlayerMessage:()=>Ie,parseHostMessage:()=>ce,parsePlayerMessage:()=>ue,sendToHost:()=>Se,sendToPlayer:()=>Ae,toWire:()=>K});var V=\"3dd-doc-\",ke=/^[A-Za-z0-9_-]{1,64}$/,Te=[\"close\",\"share\",\"embed-ready\",\"present-left\",\"present-denied\"],_e=[\"embed-denied\"],Pe=[\"present-invite\",\"present-handover\",\"present-switch\"],Ee=[\"handover-done\"];function K(e){let n={type:V+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 le(e,n,t){if(!e||typeof e!=\"object\")return null;let r=e.type;if(typeof r!=\"string\"||!r.startsWith(V))return null;let o=r.slice(V.length);if(n.includes(o))return{type:o};if(_e.includes(o)){let u=e.reason;return{type:o,reason:/^[a-z-]{1,40}$/.test(String(u||\"\"))?String(u):\"unknown\"}}if(!t.includes(o))return null;let i=e.slug;return typeof i!=\"string\"||!ke.test(i)?null:{type:o,slug:i}}function ue(e){return le(e,Te,Pe)}function ce(e){return le(e,Ee,[])}function Se(e,n){try{let t=n||(typeof window!=\"undefined\"?window.parent:null);t&&t.postMessage(K(e),\"*\")}catch(t){}}function Ae(e,n){var t;try{(t=e==null?void 0:e.contentWindow)==null||t.postMessage(K(n),\"*\")}catch(r){}}function Ie(e,n){let t=n||(typeof window!=\"undefined\"?window:null);if(!t)return()=>{};let r=o=>{let i=ue(o.data);i&&e(i)};return t.addEventListener(\"message\",r),()=>t.removeEventListener(\"message\",r)}function Ce(e,n){let t=n||(typeof window!=\"undefined\"?window:null);if(!t)return()=>{};let r=o=>{let i=ce(o.data);i&&e(i)};return t.addEventListener(\"message\",r),()=>t.removeEventListener(\"message\",r)}var S={};d(S,{createTracker:()=>He});var Le=[\"mousemove\",\"mousedown\",\"keydown\",\"wheel\",\"touchstart\",\"pointerdown\"];function $e(){return Math.random().toString(36).slice(2)+Date.now().toString(36)}function Re(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 He(e={}){var se,ie;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=(se=e.idleMs)!=null?se:6e4,u=(ie=e.sessionEveryMs)!=null?ie:12e3,h=e.slug||\"\",p=e.internal||null,R=e.sessionId||$e(),be=e.send||Re(o,n),g={},y=0,H=0,v=0,ee=0,c=null,N=r(),M=!1,O=!1,T=[],he=()=>!!h||!!p,ne=s=>{if(he())try{be(s)}catch(a){}},te=()=>!!t&&t.visibilityState===\"visible\"&&(typeof t.hasFocus==\"function\"?t.hasFocus():!0)&&!M,W=()=>{c!=null&&y>0&&(g[y]=(g[y]||0)+(r()-c)/1e3),c=te()?r():null},D=()=>{W(),c=null},F=()=>{c==null&&te()&&(c=r())},z=()=>{let s=0;for(let a in g)s+=g[a];return c!=null&&(s+=(r()-c)/1e3),Math.round(s)},re=()=>{let s={};for(let a in g)s[a]=Math.round(g[a]);return s},x=()=>{N=r(),M&&(M=!1,F())},oe=(s,a)=>{p||ne({slug:h,event:s,page:a||H,maxPage:v,seconds:z(),sessionId:R})},_=()=>{W();let s={event:\"session\",sessionId:R,numPages:ee,maxPage:v,totalSeconds:z(),pagesTime:re()};p?(s.internal=!0,s.docId=p.docId,s.email=p.email,s.name=p.name):s.slug=h,ne(s)},f=(s,a,P,ae)=>{!s||typeof s.addEventListener!=\"function\"||(s.addEventListener(a,P,ae),T.push(()=>{s.removeEventListener(a,P,ae)}))};return{sessionId:R,totalSeconds:z,maxPage:()=>v,pageTimes:re,start(){if(O)return;O=!0,oe(\"open\",1),y=1,c=r(),N=r(),f(t,\"visibilitychange\",()=>{t.visibilityState===\"hidden\"?(D(),_()):(x(),F())}),f(n,\"blur\",D),f(n,\"focus\",()=>{x(),F()});for(let P of Le)f(n,P,x,{passive:!0});f(e.scrollElement,\"scroll\",x,{passive:!0}),f(n,\"beforeunload\",_);let s=n.setInterval(()=>{!M&&r()-N>i&&(M=!0,D())},5e3),a=n.setInterval(_,u);T.push(()=>{n.clearInterval(s),n.clearInterval(a)})},setPage(s){!s||s===H||(W(),y=s,H=s,s>v&&(v=s,oe(\"page\",s)))},setPageCount(s){ee=s||0},noteActivity:x,flush:_,stop(){for(;T.length;){let s=T.pop();try{s==null||s()}catch(a){}}O=!1}}}var A={};d(A,{STORAGE_KEYS:()=>w,attendeeKey:()=>De,authorToken:()=>Fe,avatarHtml:()=>G,canModerate:()=>Z,escapeHtml:()=>l,flattenPresence:()=>Ne,formatMessageBody:()=>j,initials:()=>de,isMentioned:()=>ze,isMine:()=>m,reactorId:()=>B,shouldNotify:()=>Ue,unreadLabel:()=>Ve});var w={attendeeKey:\"3dd-present-attkey\",authorToken:\"3dd-present-authtoken\",chatMuted:\"3dd-present-mute\"};function l(e){return String(e==null?\"\":e).replace(/&/g,\"&amp;\").replace(/</g,\"&lt;\").replace(/>/g,\"&gt;\").replace(/\"/g,\"&quot;\")}function de(e){let n=(e||\"?\").trim().split(/\\s+/);return(((n[0]||\"\")[0]||\"?\")+((n[1]||\"\")[0]||\"\")).toUpperCase()}function G(e,n){return e?`<img src=\"${l(e)}\" alt=\"\">`:l(de(n))}function j(e){let n=l(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 Ne(e){let n={},t=[];for(let r in e||{})for(let o of(e||{})[r]||[]){let i=String(o.uid||o.email||o.name||\"\").toLowerCase()||`_${r}_${t.length}`;n[i]?o.role===\"presenter\"&&(n[i]=o):(n[i]=o,t.push(i))}return t.map(r=>n[r])}function Oe(e,n){try{return e?e.getItem(n):null}catch(t){return null}}function We(e,n,t){try{e==null||e.setItem(n,t)}catch(r){}}function De(e,n,t=\"anon\",r=()=>Math.random().toString(36).slice(2,10)){if(e!=null&&e.email)return String(e.email).toLowerCase();try{if(!n)return`anon-${t}`;let o=n.getItem(w.attendeeKey);if(o)return o;let i=`anon-${r()}`;return n.setItem(w.attendeeKey,i),i}catch(o){return`anon-${t}`}}function Fe(e,n=()=>Math.random().toString(36).slice(2)+Math.random().toString(36).slice(2)){let t=Oe(e,w.authorToken);if(t)return t;let r=n();return We(e,w.authorToken,r),r}function B(e){return String(e&&(e.email||e.name)||\"\").toLowerCase()}function m(e,n){return!e||!n?!1:e.author_email&&n.email?e.author_email===n.email:!e.author_email&&!n.email?e.author_name===n.name:!1}function Z(e){return!!(e&&e.role===\"presenter\")}function ze(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}`)&&!m(e,n):!1}function Ue(e){let{msg:n,me:t,historyLoaded:r,chatHidden:o}=e;return!r||!n||n.deleted||m(n,t)?!1:o}function Ve(e){return!e||e<=0?\"\":e>9?\"9+\":String(e)}var I={};d(I,{messageClassName:()=>Ge,renderActions:()=>fe,renderAttachment:()=>ge,renderMessage:()=>Ke,renderReactions:()=>pe});function pe(e,n){let t=e&&e.reactions||{},r=B(n),o=\"\";for(let i in t){let u=t[i]||[];if(!u.length)continue;let h=u.indexOf(r)>=0;o+=`<button class=\"re-chip${h?\" on\":\"\"}\" data-e=\"${l(i)}\">${l(i)} ${u.length}</button>`}return o}function ge(e){if(!e||!e.url)return\"\";let n=l(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=l(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 fe(e,n){let t=m(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||Z(n.me))&&(r+='<button class=cm-del-btn title=\"Supprimer\">\\u{1F5D1}</button>'),r}function Ke(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>${l(e.reply_name||\"\")}</b> ${l(e.reply_text||\"\")}</div>`:\"\",r=e.edited?\" <span class=cm-ed>(modifi\\xE9)</span>\":\"\",o=e.body?`<div class=txt>${j(e.body)}${r}</div>`:\"\",i=e.is_presenter?\" <span class=tag>pr\\xE9sentateur</span>\":\"\";return`<span class=a>${G(e.author_avatar,e.author_name)}</span><span class=b><div class=who><b>${l(e.author_name||\"Invit\\xE9\")}</b>${i}</div>`+t+o+ge(e.attachment)+`<div class=cm-re>${pe(e,n.me)}</div></span><span class=cm-act>${fe(e,n)}</span>`}function Ge(e,n,t){return\"cm\"+(m(e,n)?\" me\":\"\")+(e.deleted?\" isdel\":\"\")+(t?\" mentioned\":\"\")}var C={};d(C,{DEFAULT_CENTER:()=>X,DEFAULT_ZOOM:()=>J,MAP_TYPES:()=>k,cycleMapType:()=>je,initialMapContent:()=>Ze,mapTypeLabel:()=>Be,sanitizeContent:()=>Q});var k=[\"roadmap\",\"satellite\",\"hybrid\"],X=[46.6,2.5],J=6;function b(e){let n=Number(e);return Number.isFinite(n)?n:null}function Y(e){if(!Array.isArray(e))return null;let n=b(e[0]),t=b(e[1]);return n!=null&&t!=null?[n,t]:null}var q=(e,n,t)=>Math.max(n,Math.min(t,e));function Q(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=Y(n.position);if(!t)return null;let r=n.pov&&typeof n.pov==\"object\"?n.pov:null,o=r?{heading:b(r.heading)||0,pitch:q(b(r.pitch)||0,-90,90)}:{heading:0,pitch:0};return{kind:\"streetview\",position:t,pov:o,zoom:q(b(n.zoom)||1,0,5)}}return n.kind!==\"map\"?null:{kind:\"map\",center:Y(n.center)||[...X],zoom:q(Math.trunc(b(n.zoom)||J),1,21),marker:Y(n.marker),mapType:k.includes(n.mapType)?n.mapType:null,label:String(n.label||\"\").slice(0,160)||null}}function je(e){let n=k.indexOf(e);return k[(n+1)%k.length]}function Be(e){return e===\"roadmap\"?\"\\u{1F6F0} Satellite\":e===\"satellite\"?\"\\u{1F5FA} Hybride\":\"\\u{1F5FA} Plan\"}function Ze(){return{kind:\"map\",center:[...X],zoom:J,marker:null,mapType:null,label:null}}var L={};d(L,{CURRENT_PAGE_MARGIN:()=>en,DEFAULT_ASPECT:()=>Xe,MAX_ZOOM:()=>qe,MIN_PAGE_WIDTH:()=>Je,MIN_ZOOM:()=>Ye,PRERENDER_MARGIN:()=>Qe,arrowState:()=>rn,averageColor:()=>an,clampPage:()=>me,clampZoom:()=>nn,fitWidth:()=>sn,isImageDocument:()=>ln,progressPercent:()=>tn});var Ye=.5,qe=3,Xe=1.35,Je=280,Qe=\"500px 0px\",en=\"-48% 0px -48% 0px\";function nn(e){let n=Math.round((Number(e)||0)*10)/10;return Math.max(.5,Math.min(3,n))}function me(e,n){return Math.max(1,Math.min(n||1,Number(e)||1))}function tn(e,n){return n?Math.round(me(e,n)/n*100):0}function rn(e,n){return{prevDisabled:e<=1,nextDisabled:!!n&&e>=n}}var on=28;function sn(e){var u;let n=(u=e.zoom)!=null?u:1,t=Math.max(280,e.containerWidth||900)*n;if(!e.onePage)return t;let r=e.aspect||1.35,i=((e.containerHeight||600)-on-(e.overlap||0)-(e.reserve||0))/r;return i>120?Math.min(t,i):t}function an(e){let n=e.length/4;if(!n)return\"rgb(0,0,0)\";let t=0,r=0,o=0;for(let 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 ln(e,n){return/[.](png|jpe?g|webp|gif|avif)($|[?])/i.test(String(e||n||\"\"))}var $={};d($,{presentationTransition:()=>un,switchDocUrl:()=>cn});function un(e,n={}){if(!e)return[{kind:\"nothing\"}];if(e.active===!1)return[{kind:\"ended\"}];let t=Q(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 cn(e,n){let t=encodeURIComponent(n||String(Date.now()));return`${e}${e.includes(\"?\")?\"&\":\"?\"}v=${t}`}return we(dn);})();", SOURCE_HASH: "dbdf24ce25f84ac7" };
7
+ module.exports = { PLAYER_BROWSER_JS: "\"use strict\";var Player=(()=>{var U=Object.defineProperty;var ve=Object.getOwnPropertyDescriptor;var Me=Object.getOwnPropertyNames;var xe=Object.prototype.hasOwnProperty;var d=(e,n)=>{for(var t in n)U(e,t,{get:n[t],enumerable:!0})},we=(e,n,t,r)=>{if(n&&typeof n==\"object\"||typeof n==\"function\")for(let o of Me(n))!xe.call(e,o)&&o!==t&&U(e,o,{get:()=>n[o],enumerable:!(r=ve(n,o))||r.enumerable});return e};var ke=e=>we(U({},\"__esModule\",{value:!0}),e);var pn={};d(pn,{bridge:()=>S,chat:()=>C,live:()=>I,presentation:()=>L,presentationState:()=>R,tracking:()=>A,viewer:()=>$});var S={};d(S,{onHostMessage:()=>Le,onPlayerMessage:()=>Ce,parseHostMessage:()=>de,parsePlayerMessage:()=>ce,sendToHost:()=>Ae,sendToPlayer:()=>Ie,toWire:()=>K});var V=\"3dd-doc-\",Te=/^[A-Za-z0-9_-]{1,64}$/,_e=[\"close\",\"share\",\"embed-ready\",\"present-left\",\"present-denied\"],Pe=[\"embed-denied\"],Ee=[\"present-invite\",\"present-handover\",\"present-switch\"],Se=[\"handover-done\"];function K(e){let n={type:V+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 ue(e,n,t){if(!e||typeof e!=\"object\")return null;let r=e.type;if(typeof r!=\"string\"||!r.startsWith(V))return null;let o=r.slice(V.length);if(n.includes(o))return{type:o};if(Pe.includes(o)){let u=e.reason;return{type:o,reason:/^[a-z-]{1,40}$/.test(String(u||\"\"))?String(u):\"unknown\"}}if(!t.includes(o))return null;let i=e.slug;return typeof i!=\"string\"||!Te.test(i)?null:{type:o,slug:i}}function ce(e){return ue(e,_e,Ee)}function de(e){return ue(e,Se,[])}function Ae(e,n){try{let t=n||(typeof window!=\"undefined\"?window.parent:null);t&&t.postMessage(K(e),\"*\")}catch(t){}}function Ie(e,n){var t;try{(t=e==null?void 0:e.contentWindow)==null||t.postMessage(K(n),\"*\")}catch(r){}}function Ce(e,n){let t=n||(typeof window!=\"undefined\"?window:null);if(!t)return()=>{};let r=o=>{let i=ce(o.data);i&&e(i)};return t.addEventListener(\"message\",r),()=>t.removeEventListener(\"message\",r)}function Le(e,n){let t=n||(typeof window!=\"undefined\"?window:null);if(!t)return()=>{};let r=o=>{let i=de(o.data);i&&e(i)};return t.addEventListener(\"message\",r),()=>t.removeEventListener(\"message\",r)}var A={};d(A,{createTracker:()=>Ne});var $e=[\"mousemove\",\"mousedown\",\"keydown\",\"wheel\",\"touchstart\",\"pointerdown\"];function Re(){return Math.random().toString(36).slice(2)+Date.now().toString(36)}function He(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 Ne(e={}){var ie,ae;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=(ie=e.idleMs)!=null?ie:6e4,u=(ae=e.sessionEveryMs)!=null?ae:12e3,y=e.slug||\"\",p=e.internal||null,H=e.sessionId||Re(),he=e.send||He(o,n),f={},v=0,N=0,M=0,ee=0,c=null,_=r(),x=!1,O=!1,P=[],ye=()=>!!y||!!p,ne=s=>{if(ye())try{he(s)}catch(a){}},te=()=>!!t&&t.visibilityState===\"visible\"&&(typeof t.hasFocus==\"function\"?t.hasFocus():!0)&&!x,re=s=>{let a=r()-s,m=Math.max(0,_-s)+i;return Math.max(0,Math.min(a,m))},W=()=>{c!=null&&v>0&&(f[v]=(f[v]||0)+re(c)/1e3),c=te()?r():null},D=()=>{W(),c=null},F=()=>{c==null&&te()&&(c=r())},z=()=>{let s=0;for(let a in f)s+=f[a];return c!=null&&(s+=re(c)/1e3),Math.round(s)},oe=()=>{let s={};for(let a in f)s[a]=Math.round(f[a]);return s},w=()=>{_=r(),x&&(x=!1,F())},se=(s,a)=>{p||ne({slug:y,event:s,page:a||N,maxPage:M,seconds:z(),sessionId:H})},E=()=>{W();let s={event:\"session\",sessionId:H,numPages:ee,maxPage:M,totalSeconds:z(),pagesTime:oe()};p?(s.internal=!0,s.docId=p.docId,s.email=p.email,s.name=p.name):s.slug=y,ne(s)},g=(s,a,m,le)=>{!s||typeof s.addEventListener!=\"function\"||(s.addEventListener(a,m,le),P.push(()=>{s.removeEventListener(a,m,le)}))};return{sessionId:H,totalSeconds:z,maxPage:()=>M,pageTimes:oe,start(){if(O)return;O=!0,se(\"open\",1),v=1,c=r(),_=r(),g(t,\"visibilitychange\",()=>{t.visibilityState===\"hidden\"?(D(),E()):(w(),F())}),g(n,\"blur\",D),g(n,\"focus\",()=>{w(),F()});for(let m of $e)g(n,m,w,{passive:!0});g(e.scrollElement,\"scroll\",w,{passive:!0}),g(n,\"beforeunload\",E);let s=n.setInterval(()=>{!x&&r()-_>i&&(x=!0,D())},5e3),a=n.setInterval(E,u);P.push(()=>{n.clearInterval(s),n.clearInterval(a)})},setPage(s){!s||s===N||(W(),v=s,N=s,s>M&&(M=s,se(\"page\",s)))},setPageCount(s){ee=s||0},noteActivity:w,flush:E,stop(){for(;P.length;){let s=P.pop();try{s==null||s()}catch(a){}}O=!1}}}var I={};d(I,{STORAGE_KEYS:()=>k,attendeeKey:()=>Fe,authorToken:()=>ze,avatarHtml:()=>G,canModerate:()=>Z,escapeHtml:()=>l,flattenPresence:()=>Oe,formatMessageBody:()=>j,initials:()=>pe,isMentioned:()=>Ue,isMine:()=>b,reactorId:()=>B,shouldNotify:()=>Ve,unreadLabel:()=>Ke});var k={attendeeKey:\"3dd-present-attkey\",authorToken:\"3dd-present-authtoken\",chatMuted:\"3dd-present-mute\"};function l(e){return String(e==null?\"\":e).replace(/&/g,\"&amp;\").replace(/</g,\"&lt;\").replace(/>/g,\"&gt;\").replace(/\"/g,\"&quot;\")}function pe(e){let n=(e||\"?\").trim().split(/\\s+/);return(((n[0]||\"\")[0]||\"?\")+((n[1]||\"\")[0]||\"\")).toUpperCase()}function G(e,n){return e?`<img src=\"${l(e)}\" alt=\"\">`:l(pe(n))}function j(e){let n=l(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 Oe(e){let n={},t=[];for(let r in e||{})for(let o of(e||{})[r]||[]){let i=String(o.uid||o.email||o.name||\"\").toLowerCase()||`_${r}_${t.length}`;n[i]?o.role===\"presenter\"&&(n[i]=o):(n[i]=o,t.push(i))}return t.map(r=>n[r])}function We(e,n){try{return e?e.getItem(n):null}catch(t){return null}}function De(e,n,t){try{e==null||e.setItem(n,t)}catch(r){}}function Fe(e,n,t=\"anon\",r=()=>Math.random().toString(36).slice(2,10)){if(e!=null&&e.email)return String(e.email).toLowerCase();try{if(!n)return`anon-${t}`;let o=n.getItem(k.attendeeKey);if(o)return o;let i=`anon-${r()}`;return n.setItem(k.attendeeKey,i),i}catch(o){return`anon-${t}`}}function ze(e,n=()=>Math.random().toString(36).slice(2)+Math.random().toString(36).slice(2)){let t=We(e,k.authorToken);if(t)return t;let r=n();return De(e,k.authorToken,r),r}function B(e){return String(e&&(e.email||e.name)||\"\").toLowerCase()}function b(e,n){return!e||!n?!1:e.author_email&&n.email?e.author_email===n.email:!e.author_email&&!n.email?e.author_name===n.name:!1}function Z(e){return!!(e&&e.role===\"presenter\")}function Ue(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}`)&&!b(e,n):!1}function Ve(e){let{msg:n,me:t,historyLoaded:r,chatHidden:o}=e;return!r||!n||n.deleted||b(n,t)?!1:o}function Ke(e){return!e||e<=0?\"\":e>9?\"9+\":String(e)}var C={};d(C,{messageClassName:()=>je,renderActions:()=>me,renderAttachment:()=>ge,renderMessage:()=>Ge,renderReactions:()=>fe});function fe(e,n){let t=e&&e.reactions||{},r=B(n),o=\"\";for(let i in t){let u=t[i]||[];if(!u.length)continue;let y=u.indexOf(r)>=0;o+=`<button class=\"re-chip${y?\" on\":\"\"}\" data-e=\"${l(i)}\">${l(i)} ${u.length}</button>`}return o}function ge(e){if(!e||!e.url)return\"\";let n=l(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=l(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 me(e,n){let t=b(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||Z(n.me))&&(r+='<button class=cm-del-btn title=\"Supprimer\">\\u{1F5D1}</button>'),r}function Ge(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>${l(e.reply_name||\"\")}</b> ${l(e.reply_text||\"\")}</div>`:\"\",r=e.edited?\" <span class=cm-ed>(modifi\\xE9)</span>\":\"\",o=e.body?`<div class=txt>${j(e.body)}${r}</div>`:\"\",i=e.is_presenter?\" <span class=tag>pr\\xE9sentateur</span>\":\"\";return`<span class=a>${G(e.author_avatar,e.author_name)}</span><span class=b><div class=who><b>${l(e.author_name||\"Invit\\xE9\")}</b>${i}</div>`+t+o+ge(e.attachment)+`<div class=cm-re>${fe(e,n.me)}</div></span><span class=cm-act>${me(e,n)}</span>`}function je(e,n,t){return\"cm\"+(b(e,n)?\" me\":\"\")+(e.deleted?\" isdel\":\"\")+(t?\" mentioned\":\"\")}var L={};d(L,{DEFAULT_CENTER:()=>X,DEFAULT_ZOOM:()=>J,MAP_TYPES:()=>T,cycleMapType:()=>Be,initialMapContent:()=>Ye,mapTypeLabel:()=>Ze,sanitizeContent:()=>Q});var T=[\"roadmap\",\"satellite\",\"hybrid\"],X=[46.6,2.5],J=6;function h(e){let n=Number(e);return Number.isFinite(n)?n:null}function Y(e){if(!Array.isArray(e))return null;let n=h(e[0]),t=h(e[1]);return n!=null&&t!=null?[n,t]:null}var q=(e,n,t)=>Math.max(n,Math.min(t,e));function Q(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=Y(n.position);if(!t)return null;let r=n.pov&&typeof n.pov==\"object\"?n.pov:null,o=r?{heading:h(r.heading)||0,pitch:q(h(r.pitch)||0,-90,90)}:{heading:0,pitch:0};return{kind:\"streetview\",position:t,pov:o,zoom:q(h(n.zoom)||1,0,5)}}return n.kind!==\"map\"?null:{kind:\"map\",center:Y(n.center)||[...X],zoom:q(Math.trunc(h(n.zoom)||J),1,21),marker:Y(n.marker),mapType:T.includes(n.mapType)?n.mapType:null,label:String(n.label||\"\").slice(0,160)||null}}function Be(e){let n=T.indexOf(e);return T[(n+1)%T.length]}function Ze(e){return e===\"roadmap\"?\"\\u{1F6F0} Satellite\":e===\"satellite\"?\"\\u{1F5FA} Hybride\":\"\\u{1F5FA} Plan\"}function Ye(){return{kind:\"map\",center:[...X],zoom:J,marker:null,mapType:null,label:null}}var $={};d($,{CURRENT_PAGE_MARGIN:()=>nn,DEFAULT_ASPECT:()=>Je,MAX_ZOOM:()=>Xe,MIN_PAGE_WIDTH:()=>Qe,MIN_ZOOM:()=>qe,PRERENDER_MARGIN:()=>en,arrowState:()=>on,averageColor:()=>ln,clampPage:()=>be,clampZoom:()=>tn,fitWidth:()=>an,isImageDocument:()=>un,progressPercent:()=>rn});var qe=.5,Xe=3,Je=1.35,Qe=280,en=\"500px 0px\",nn=\"-48% 0px -48% 0px\";function tn(e){let n=Math.round((Number(e)||0)*10)/10;return Math.max(.5,Math.min(3,n))}function be(e,n){return Math.max(1,Math.min(n||1,Number(e)||1))}function rn(e,n){return n?Math.round(be(e,n)/n*100):0}function on(e,n){return{prevDisabled:e<=1,nextDisabled:!!n&&e>=n}}var sn=28;function an(e){var u;let n=(u=e.zoom)!=null?u:1,t=Math.max(280,e.containerWidth||900)*n;if(!e.onePage)return t;let r=e.aspect||1.35,i=((e.containerHeight||600)-sn-(e.overlap||0)-(e.reserve||0))/r;return i>120?Math.min(t,i):t}function ln(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 un(e,n){return/[.](png|jpe?g|webp|gif|avif)($|[?])/i.test(String(e||n||\"\"))}var R={};d(R,{presentationTransition:()=>cn,switchDocUrl:()=>dn});function cn(e,n={}){if(!e)return[{kind:\"nothing\"}];if(e.active===!1)return[{kind:\"ended\"}];let t=Q(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 dn(e,n){let t=encodeURIComponent(n||String(Date.now()));return`${e}${e.includes(\"?\")?\"&\":\"?\"}v=${t}`}return ke(pn);})();", SOURCE_HASH: "51c05545eb241d37" };
package/server/handler.js CHANGED
@@ -2461,6 +2461,67 @@ async function handler(req, res) {
2461
2461
  if (String(body.action || "").startsWith("docshare.")) {
2462
2462
  const jd = (status, obj) => { res.statusCode = status; res.setHeader("Content-Type", "application/json"); res.end(JSON.stringify(obj)); };
2463
2463
  try {
2464
+ // ── L'HÔTE PARLE EN SON NOM PROPRE ────────────────────────────────────────────────
2465
+ //
2466
+ // Un lien ANONYME — la plaquette publique d'un programme, lue par un prospect sans
2467
+ // compte — n'a pas de membre derrière lui. Exiger un JWT forcerait l'hôte à inventer
2468
+ // une identité qui n'existe pas : un compte de service dont le mot de passe ouvre bien
2469
+ // plus que la création d'un lien, ou pire, l'aperçu interne détourné — ce qui rangerait
2470
+ // un prospect dans la population INTERNE et ferait mentir la seule phrase qui compte
2471
+ // ici : « ce client a lu pendant douze minutes ».
2472
+ //
2473
+ // C'est la même nature que `/authz` et `/branding`, que l'hôte sert déjà en serveur à
2474
+ // serveur. Demandé par le second hôte, qui avait écarté les trois contournements
2475
+ // lui-même avant d'écrire.
2476
+ //
2477
+ // ⚠️ TROIS VERROUS, et chacun ferme une porte différente :
2478
+ // 1. `create` UNIQUEMENT — révoquer, lister, lire des statistiques restent des actes
2479
+ // de membre. Un secret de serveur ne doit pas donner à voir qui a lu quoi.
2480
+ // 2. AUCUN destinataire — le nominatif a un membre. L'admettre ici rouvrirait par la
2481
+ // bande ce que le JWT protège.
2482
+ // 3. IDEMPOTENT par `docId` — sans ça, un redéploiement, une reprise sur erreur ou un
2483
+ // double clic donnent trois liens pour la même plaquette, donc des statistiques
2484
+ // fragmentées en trois, découvertes en les lisant six mois plus tard.
2485
+ if (typeof PLAYER.identity.isTrustedHostCall === "function"
2486
+ && PLAYER.identity.isTrustedHostCall(req.headers)) {
2487
+ if (body.action !== "docshare.create") {
2488
+ return jd(403, { ok: false, error: "L'appel serveur à serveur ne crée que des liens sans destinataire." });
2489
+ }
2490
+ if (body.recipientEmail) {
2491
+ return jd(400, { ok: false, error: "Un lien avec destinataire appartient à un membre : il exige son jeton." });
2492
+ }
2493
+ const docId = String(body.docId || "").trim();
2494
+ if (!docId || !body.fileUrl) return jd(400, { ok: false, error: "docId/fileUrl requis" });
2495
+
2496
+ const ipH = String(req.headers["x-forwarded-for"] || "").split(",")[0].trim() || req.socket?.remoteAddress || "hote";
2497
+ if (!(await PLAYER.limits.allow(`hshare:${ipH}`, 120, 3600))) return jd(429, { ok: false, error: "rate" });
2498
+
2499
+ // ⚠️ La clé d'idempotence n'a PAS demandé de colonne : « le lien de l'hôte pour ce
2500
+ // document » est exactement la ligne sans créateur ET sans destinataire. Seul ce
2501
+ // chemin en produit, donc elle est sans ambiguïté — et une instance déjà en service
2502
+ // n'a aucune migration à passer.
2503
+ const dejaLa = await PLAYER.db.request(
2504
+ `commercial_doc_shares?doc_id=eq.${encodeURIComponent(docId)}&created_by=is.null&recipient_email=is.null&select=slug&limit=1`,
2505
+ );
2506
+ if (Array.isArray(dejaLa) && dejaLa[0]) {
2507
+ await PLAYER.db.request(`commercial_doc_shares?slug=eq.${encodeURIComponent(dejaLa[0].slug)}`, {
2508
+ method: "PATCH", headers: { Prefer: "return=minimal" },
2509
+ body: { doc_title: body.docTitle || null, file_url: String(body.fileUrl), file_name: body.fileName || null, revoked: false },
2510
+ });
2511
+ return jd(200, { ok: true, slug: dejaLa[0].slug, reused: true });
2512
+ }
2513
+ // `createdBy` reste NUL : personne ne l'a créé. Le filtre « mes liens » compare
2514
+ // `created_by=eq.<email>`, qui exclut les NUL — ce lien n'apparaît donc dans la liste
2515
+ // de personne, et reste visible en administration (`list.all`, qui ne filtre pas).
2516
+ const neuf = await createShare({
2517
+ brandKey: body.brandKey, docId, docTitle: body.docTitle, fileUrl: body.fileUrl,
2518
+ fileName: body.fileName, createdBy: null, bot: body.bot, botScript: body.botScript,
2519
+ guided: body.guided, profileId: body.profileId, allowDownload: body.allowDownload,
2520
+ videoLayout: body.videoLayout, logo: body.logo, logoDark: body.logoDark,
2521
+ });
2522
+ return jd(200, { ok: true, slug: neuf.slug, reused: false });
2523
+ }
2524
+
2464
2525
  const u = await PLAYER.identity.verifyToken(req.headers.authorization);
2465
2526
  if (!u || !u.email) return jd(401, { ok: false, error: "auth" });
2466
2527
  // L'action est transmise telle quelle (`create`, `revoke`…) : l'hôte peut séparer
@@ -2595,6 +2656,7 @@ async function handler(req, res) {
2595
2656
  // muette sur les URL.
2596
2657
  capabilities: [
2597
2658
  "docshare", "presentations", "embed-denied", "host-fetch", "brand-reference", "host-auth",
2659
+ "host-share",
2598
2660
  ],
2599
2661
  // ⚠️ POUR QUELLES ORIGINES cette instance accepte d'être encadrée. Un booléen ne
2600
2662
  // suffisait pas : un hôte a besoin de voir que SON domaine manque, pas seulement que
@@ -2607,6 +2669,10 @@ async function handler(req, res) {
2607
2669
  // l'hôte connaît déjà son émetteur — il veut seulement savoir si l'instance le regarde.
2608
2670
  // Dire lequel n'aiderait personne et renseignerait qui sonde.
2609
2671
  separateIssuer: !!(PLAYER.config && PLAYER.config.separateIssuer),
2672
+ // « L'hôte peut-il créer un lien en son nom propre ? » — configuré, pas seulement
2673
+ // possible. Un hôte qui oublie le secret reçoit un 401 qui ressemble à un droit
2674
+ // manquant ; ce booléen le lui dit sans qu'il ait à essayer.
2675
+ hostShare: !!(PLAYER.config && PLAYER.config.hostShare),
2610
2676
  // Greffons de l'hôte : présents ou coupés (PLAYER_PLUGINS_OFF). Booléens uniquement.
2611
2677
  plugins: {
2612
2678
  bot: !!p.bot, visitors: !!p.visitors, brandIntro: !!p.brandIntro,