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.
- package/context/standalone.js +27 -0
- package/docs/HOST-CONTRACT.md +2 -1
- package/package.json +1 -1
- package/server/browser.generated.js +1 -1
- package/server/handler.js +66 -0
package/context/standalone.js
CHANGED
|
@@ -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
|
}
|
package/docs/HOST-CONTRACT.md
CHANGED
|
@@ -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.
|
|
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,\"&\").replace(/</g,\"<\").replace(/>/g,\">\").replace(/\"/g,\""\")}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,\"&\").replace(/</g,\"<\").replace(/>/g,\">\").replace(/\"/g,\""\")}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,
|