discovery-media-player 0.1.136 → 0.1.138
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/bin/serve.js +11 -6
- package/context/standalone.js +2 -0
- package/context/storage.js +2 -0
- package/dist/bridge.js +1 -0
- package/docs/HOST-CONTRACT.md +15 -0
- package/package.json +3 -2
- package/server/appelant.js +2 -0
- package/server/brands.js +2 -0
- package/server/browser.generated.js +3 -1
- package/server/cache.js +2 -0
- package/server/erreurs-base.js +2 -0
- package/server/gabarit-agent.js +2 -0
- package/server/gabarit-carte.js +2 -0
- package/server/gabarit-legal.js +2 -0
- package/server/gabarit-live.js +2 -0
- package/server/handler.js +35 -26
- package/server/page-audience.js +2 -0
- package/server/page-mur.js +2 -0
- package/server/page-visionneuse.js +2 -0
- package/server/presentations.js +2 -0
- package/server/publier.js +2 -0
- package/server/reponses.js +84 -0
- package/server/retention.js +2 -0
- package/server/routes-agent.js +13 -4
- package/server/routes-direct.js +16 -13
- package/server/routes-liens.js +14 -11
- package/server/routes-visiteur.js +4 -1
- package/server/schema.js +2 -0
- package/server/session-cles.js +2 -0
- package/server/shared.generated.js +2 -0
- package/server/shares.js +61 -13
- package/server/texte.js +2 -0
- package/server/tiers.js +2 -0
- package/supabase/init.sql +52 -6
- package/supabase/migrations/0020-mesures-bornees.sql +101 -0
- package/types/context.d.ts +2 -0
- package/types/index.d.ts +2 -0
- package/types/standalone.d.ts +2 -0
package/bin/serve.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
3
|
+
// Copyright © 2026 3D Discovery
|
|
2
4
|
// SERVEUR AUTONOME — le player sans plateforme.
|
|
3
5
|
//
|
|
4
6
|
// Le cœur est un gestionnaire `(req, res)` sans framework : c'est ce qui lui permet de tourner
|
|
@@ -112,8 +114,7 @@ const serveur = http.createServer(async (req, res) => {
|
|
|
112
114
|
// Point de santé : un orchestrateur doit pouvoir savoir si le processus répond sans ouvrir un
|
|
113
115
|
// document ni toucher la base.
|
|
114
116
|
if (url.pathname === "/healthz") {
|
|
115
|
-
|
|
116
|
-
res.end(JSON.stringify({ ok: true }));
|
|
117
|
+
player.repondreJson(res, 200, { ok: true });
|
|
117
118
|
return;
|
|
118
119
|
}
|
|
119
120
|
|
|
@@ -142,8 +143,11 @@ const serveur = http.createServer(async (req, res) => {
|
|
|
142
143
|
url.pathname === "/api/doc" || url.pathname === "/doc" || url.pathname === "/present" ||
|
|
143
144
|
q.slug || q.present || q.preview || q.contract;
|
|
144
145
|
if (!routeConnue) {
|
|
145
|
-
|
|
146
|
-
|
|
146
|
+
// ⚠️ LA MÊME FONCTION QUE LE PLAYER, PAS LA MÊME RECETTE ÉCRITE DEUX FOIS. Ces deux réponses
|
|
147
|
+
// posaient leur `Content-Type` mais PAS `nosniff` — la moitié de la règle, appliquée à la
|
|
148
|
+
// main, dans le seul fichier où le player ne pouvait pas la poser lui-même. Même raison que
|
|
149
|
+
// POLITIQUE_PERMISSIONS quelques lignes plus haut : deux exemplaires d'un fait divergent.
|
|
150
|
+
player.refuserEnTexte(res, 404, "Not found");
|
|
147
151
|
return;
|
|
148
152
|
}
|
|
149
153
|
|
|
@@ -157,8 +161,9 @@ const serveur = http.createServer(async (req, res) => {
|
|
|
157
161
|
await player.handler(req, res);
|
|
158
162
|
} catch (error) {
|
|
159
163
|
console.error("[player] erreur non rattrapée", error);
|
|
160
|
-
|
|
161
|
-
|
|
164
|
+
// `refuserEnTexte` porte elle-même la garde `headersSent` : une erreur survenue APRÈS le début
|
|
165
|
+
// de l'écriture ne peut plus rien poser, et tenter de le faire jetterait dans le rattrapage.
|
|
166
|
+
player.refuserEnTexte(res, 500, "Erreur");
|
|
162
167
|
}
|
|
163
168
|
});
|
|
164
169
|
|
package/context/standalone.js
CHANGED
package/context/storage.js
CHANGED
package/dist/bridge.js
CHANGED
package/docs/HOST-CONTRACT.md
CHANGED
|
@@ -31,6 +31,7 @@ need.
|
|
|
31
31
|
"product": "discovery-media-player",
|
|
32
32
|
"contract": 1,
|
|
33
33
|
"version": "<the running version>",
|
|
34
|
+
"runtime": { "node": "<what this instance runs on>", "nodeRequired": ">=22.13.0" },
|
|
34
35
|
"capabilities": ["docshare", "presentations", "embed-denied", "host-fetch", "brand-reference", "host-auth", "host-share", "host-mail", "retention"],
|
|
35
36
|
"frameAncestors": ["'self'", "https://*.vercel.app", "https://app.example.com"],
|
|
36
37
|
"separateIssuer": true,
|
|
@@ -51,6 +52,20 @@ need.
|
|
|
51
52
|
never by order. `plugins` lets you refuse to start when you depend on an optional module this
|
|
52
53
|
instance does not have.
|
|
53
54
|
|
|
55
|
+
⚠️ **`runtime` is the only way to see what the player is actually running on.** `nodeRequired` is
|
|
56
|
+
the floor the package declares, `node` is what the process reports — two numbers, no verdict:
|
|
57
|
+
compare them with your own semver rather than trusting a field we compute for you.
|
|
58
|
+
|
|
59
|
+
The floor is **Node ≥ 22.13.0**, and it is not ours: it is what `pdfjs-dist` — the one production
|
|
60
|
+
dependency, the one that renders your documents — requires. npm will not stop you below it. It
|
|
61
|
+
prints an `EBADENGINE` line in the noise of an install and installs anyway, so an instance can run
|
|
62
|
+
for months on a Node its rendering engine calls unsupported, and nothing says so.
|
|
63
|
+
|
|
64
|
+
⚠️ **Read `node` rather than your platform's setting; they are not the same fact.** Measured on
|
|
65
|
+
25/08 at an integrating host: the project setting said `24.x` while the deployment serving
|
|
66
|
+
production ran `nodejs 22`. A configured runtime is an intention, and no amount of reading it back
|
|
67
|
+
tells you what executed. This field is the only place the two can be confronted.
|
|
68
|
+
|
|
54
69
|
⚠️ **`schema` tells you which migrations this instance is still waiting for.** The player never
|
|
55
70
|
applies migrations — it cannot, it only speaks PostgREST — so it *detects* instead, and a missing
|
|
56
71
|
column makes the feature that needs it **degrade silently**, by design, so as not to break a host
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "discovery-media-player",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.138",
|
|
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,7 +77,7 @@
|
|
|
77
77
|
"test:campagne": "node -e \"require('fs').existsSync('vitest.campagne.config.mjs')||(console.error('Ce banc ne vit pas dans le paquet publié - le champ scripts annonce plus que le tarball ne contient. Clonez le depot puis npm ci : https://github.com/Juli1artha/discovery-media-player'),process.exit(1))\" && vitest run --config vitest.campagne.config.mjs"
|
|
78
78
|
},
|
|
79
79
|
"engines": {
|
|
80
|
-
"node": ">=22"
|
|
80
|
+
"node": ">=22.13.0"
|
|
81
81
|
},
|
|
82
82
|
"devDependencies": {
|
|
83
83
|
"@eslint/js": "^10.0.1",
|
|
@@ -89,6 +89,7 @@
|
|
|
89
89
|
"fast-check": "4.9.0",
|
|
90
90
|
"jsdom": "^30.0.1",
|
|
91
91
|
"playwright-core": "^1.62.1",
|
|
92
|
+
"semver": "^7.8.5",
|
|
92
93
|
"typescript": "^5.5.4",
|
|
93
94
|
"typescript-eslint": "^8.46.4",
|
|
94
95
|
"vitest": "^4.1.10",
|
package/server/appelant.js
CHANGED
package/server/brands.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// Copyright © 2026 3D Discovery
|
|
1
3
|
// Marque affichée par le loader d'un document — celle du CLIENT dont on montre le document.
|
|
2
4
|
//
|
|
3
5
|
// ⚠️ **Résolue à l'affichage, jamais recopiée dans le lien.** Un lien tracé vit des semaines dans
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// Copyright © 2026 3D Discovery
|
|
1
3
|
// GÉNÉRÉ par `npm run build:player` — NE PAS ÉDITER À LA MAIN.
|
|
2
4
|
// Sources : src/index.ts, src/bridge.ts, src/tracking.ts, src/live.ts, src/chat.ts, src/presentation-content.ts, src/viewer.ts, src/presentation-state.ts
|
|
3
5
|
//
|
|
4
6
|
// Injecté par api/doc.js dans un <script nonce=…> de la visionneuse. Le code navigateur du
|
|
5
7
|
// player vit en modules TypeScript testés sous player/src/ — c'est ici qu'il atterrit.
|
|
6
8
|
"use strict";
|
|
7
|
-
module.exports = { PLAYER_BROWSER_JS: "\"use strict\";var Player=(()=>{var C=Object.defineProperty,We=Object.defineProperties,Ve=Object.getOwnPropertyDescriptor,ze=Object.getOwnPropertyDescriptors,Fe=Object.getOwnPropertyNames,ve=Object.getOwnPropertySymbols;var we=Object.prototype.hasOwnProperty,Be=Object.prototype.propertyIsEnumerable;var Me=(e,n,t)=>n in e?C(e,n,{enumerable:!0,configurable:!0,writable:!0,value:t}):e[n]=t,K=(e,n)=>{for(var t in n||(n={}))we.call(n,t)&&Me(e,t,n[t]);if(ve)for(var t of ve(n))Be.call(n,t)&&Me(e,t,n[t]);return e},xe=(e,n)=>We(e,ze(n));var M=(e,n)=>{for(var t in n)C(e,t,{get:n[t],enumerable:!0})},je=(e,n,t,r)=>{if(n&&typeof n==\"object\"||typeof n==\"function\")for(let o of Fe(n))!we.call(e,o)&&o!==t&&C(e,o,{get:()=>n[o],enumerable:!(r=Ve(n,o))||r.enumerable});return e};var Ge=e=>je(C({},\"__esModule\",{value:!0}),e);var Kn={};M(Kn,{bridge:()=>N,cadence:()=>L,chat:()=>U,live:()=>H,presentation:()=>D,presentationState:()=>V,tracking:()=>$,viewer:()=>W});var N={};M(N,{onHostMessage:()=>tn,onPlayerMessage:()=>nn,parseHostMessage:()=>Te,parsePlayerMessage:()=>Ee,sendToHost:()=>Xe,sendToPlayer:()=>Je,toWire:()=>Z});var Y=\"3dd-doc-\",Ke=/^[A-Za-z0-9_-]{1,64}$/,Ye=[\"close\",\"share\",\"embed-ready\",\"present-left\",\"present-denied\"],Ze=[\"embed-denied\"],qe=[\"present-invite\",\"present-handover\",\"present-switch\"],Qe=[\"handover-done\"];function Z(e){let n={type:Y+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 _e(e,n,t){if(!e||typeof e!=\"object\")return null;let r=e.type;if(typeof r!=\"string\"||!r.startsWith(Y))return null;let o=r.slice(Y.length);if(n.includes(o))return{type:o};if(Ze.includes(o)){let a=e.reason;return{type:o,reason:/^[a-z-]{1,40}$/.test(String(a||\"\"))?String(a):\"unknown\"}}if(!t.includes(o))return null;let s=e.slug;return typeof s!=\"string\"||!Ke.test(s)?null:{type:o,slug:s}}function Ee(e){return _e(e,Ye,qe)}function Te(e){return _e(e,Qe,[])}function Xe(e,n){try{let t=n||(typeof window!=\"undefined\"?window.parent:null);t&&t.postMessage(Z(e),\"*\")}catch(t){}}function Je(e,n){var t;try{(t=e==null?void 0:e.contentWindow)==null||t.postMessage(Z(n),\"*\")}catch(r){}}function en(e,n){var r;if(!e)return!0;let t=(r=e.contentWindow)!=null?r:e;return n===t}function nn(e,n,t){let r=n||(typeof window!=\"undefined\"?window:null);if(!r)return()=>{};let o=s=>{if(!en(t,s.source))return;let a=Ee(s.data);a&&e(a)};return r.addEventListener(\"message\",o),()=>r.removeEventListener(\"message\",o)}function tn(e,n){var s;let t=n||(typeof window!=\"undefined\"?window:null);if(!t)return()=>{};let r=(s=t.parent)!=null?s:null,o=a=>{if(!r||r===t||a.source!==r)return;let l=Te(a.data);l&&e(l)};return t.addEventListener(\"message\",o),()=>t.removeEventListener(\"message\",o)}var $={};M($,{createTracker:()=>pn});var L={};M(L,{PRESENTER_ACTIONS_PER_HOUR:()=>X,PRESENT_CACHE_MS:()=>an,PRESENT_QUOTA_MARGIN:()=>Ne,PRESENT_QUOTA_PER_HOUR:()=>un,PRESENT_READS_PER_HOUR:()=>Ie,PRESENT_READ_BURST:()=>Ae,PRESENT_READ_COALESCE_MS:()=>Ce,PRESENT_RESYNC_MS:()=>Re,PRESENT_SIGNAL_BUDGET_PER_HOUR:()=>sn,PRESENT_WRITE_TIMEOUT_MS:()=>J,READERS_PER_EGRESS:()=>O,RESYNC_READS_PER_HOUR:()=>Pe,SESSION_IDLE_MS:()=>Q,SESSION_INTERVAL_MS:()=>q,SESSION_QUOTA_PER_HOUR:()=>rn,SESSION_WRITES_PER_HOUR:()=>Se,VIEW_EVENTS_PER_READER_HOUR:()=>ke,VIEW_QUOTA_PER_HOUR:()=>on});var q=3e4,Se=Math.ceil(120),O=25,rn=Se*O,ke=720,on=ke*O,Q=18e4,Re=25e3,Pe=Math.ceil(36e5/Re),X=720,Ie=Pe+X,sn=X,Ae=20,J=1e4,Ce=400,an=Ce,Ne=4,un=(Ie+Ae)*O*Ne;var ln=[\"mousemove\",\"mousedown\",\"keydown\",\"wheel\",\"touchstart\",\"pointerdown\"];function cn(){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 dn(e,n){return t=>{try{let r=JSON.stringify(t),o=n.navigator;return 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}),!0)}catch(r){return!1}}}function pn(e={}){var be,ye;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=(be=e.idleMs)!=null?be:Q,a=(ye=e.sessionEveryMs)!=null?ye:3e4,l=e.slug||\"\",p=e.internal||null,g=e.sessionId||cn(),f=e.send||dn(o,n),c={},u=0,b=0,d=0,w=0,v=null,T=r(),S=!1,z=!1,I=[],De=()=>!!l||!!p,ce=i=>{if(!De())return!1;try{return f(i)!==!1}catch(m){return!1}},F=()=>!!t&&t.visibilityState===\"visible\"&&!S,de=i=>{let m=r()-i,y=Math.max(0,T-i)+s;return Math.max(0,Math.min(m,y))},B=()=>{v!=null&&u>0&&(c[u]=(c[u]||0)+de(v)/1e3),v=F()?r():null},pe=()=>{B(),v=null},j=()=>{v==null&&F()&&(v=r())},G=()=>{let i=0;for(let m in c)i+=c[m];return v!=null&&(i+=de(v)/1e3),Math.round(i)},fe=()=>{let i={};for(let m in c)i[m]=Math.round(c[m]);return i},k=()=>{T=r(),S&&(S=!1,j())},me=(i,m)=>{p||ce({slug:l,event:i,page:m||b,maxPage:d,seconds:G(),sessionId:g})},ge=\"\",A=()=>{B();let i=G(),m=`${i}|${d}|${w}`;if(m===ge)return;let y={event:\"session\",sessionId:g,numPages:w,maxPage:d,totalSeconds:i,pagesTime:fe()};p?(y.internal=!0,y.docId=p.docId,y.email=p.email,y.name=p.name,p.it&&(y.it=p.it)):y.slug=l,ce(y)&&(ge=m)},R=(i,m,y,he)=>{!i||typeof i.addEventListener!=\"function\"||(i.addEventListener(m,y,he),I.push(()=>{i.removeEventListener(m,y,he)}))};return{sessionId:g,totalSeconds:G,maxPage:()=>d,pageTimes:fe,start(){if(z)return;z=!0,me(\"open\",1),u=1,T=r(),v=F()?r():null,R(t,\"visibilitychange\",()=>{t.visibilityState===\"hidden\"?(pe(),A()):(k(),j())}),R(n,\"focus\",()=>{k(),j()});for(let y of ln)R(n,y,k,{passive:!0});R(e.scrollElement,\"scroll\",k,{passive:!0}),R(n,\"beforeunload\",A);let i=n.setInterval(()=>{!S&&r()-T>s&&(S=!0,pe())},5e3),m=n.setInterval(A,a);I.push(()=>{n.clearInterval(i),n.clearInterval(m)})},setPage(i){!i||i===b||(T=r(),B(),u=i,b=i,i>d&&(d=i,me(\"page\",i)))},setPageCount(i){w=i||0},noteActivity:k,flush:A,stop(){for(;I.length;){let i=I.pop();try{i==null||i()}catch(m){}}z=!1}}}var H={};M(H,{STORAGE_KEYS:()=>x,attendeeKey:()=>bn,authorToken:()=>yn,avatarHtml:()=>ee,canModerate:()=>oe,createBudget:()=>En,createFileEcritures:()=>Tn,createScheduler:()=>xn,escapeHtml:()=>h,fetchBorne:()=>_n,flattenPresence:()=>fn,formatMessageBody:()=>ne,initials:()=>Oe,isMentioned:()=>vn,isMine:()=>_,presenceId:()=>Sn,reactorId:()=>re,referenceAuteur:()=>hn,shouldNotify:()=>Mn,unreadLabel:()=>wn});var x={attendeeKey:\"3dd-present-attkey\",authorToken:\"3dd-present-authtoken\",chatMuted:\"3dd-present-mute\",presenceId:\"3dd-present-uid\"};function h(e){return String(e==null?\"\":e).replace(/&/g,\"&\").replace(/</g,\"<\").replace(/>/g,\">\").replace(/\"/g,\""\")}function Oe(e){let n=(e||\"?\").trim().split(/\\s+/);return(((n[0]||\"\")[0]||\"?\")+((n[1]||\"\")[0]||\"\")).toUpperCase()}function ee(e,n){return e?`<img src=\"${h(e)}\" alt=\"\">`:h(Oe(n))}function ne(e){let n=h(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 fn(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 mn(e,n){try{return e?e.getItem(n):null}catch(t){return null}}function gn(e,n,t){try{e==null||e.setItem(n,t)}catch(r){}}function bn(e,n=\"anon\",t=te){try{if(!e)return`anon-${n}`;let r=e.getItem(x.attendeeKey);if(r)return r;let o=`anon-${t()}`;return e.setItem(x.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 yn(e,n=te){let t=mn(e,x.authorToken);if(t)return t;let r=n();return gn(e,x.authorToken,r),r}async function hn(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 _(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 vn(e,n){if(!n||!n.name)return!1;let t=(n.name.split(/\\s+/)[0]||\"\").toLowerCase();return t?String(e&&e.body||\"\").toLowerCase().includes(`@${t}`)&&!_(e,n):!1}function Mn(e){let{msg:n,me:t,historyLoaded:r,chatHidden:o}=e;return!r||!n||n.deleted||_(n,t)?!1:o}function wn(e){return!e||e<=0?\"\":e>9?\"9+\":String(e)}function xn(e,n={}){var b;let t=(b=n.minMs)!=null?b:300,r=n.now||(()=>Date.now()),o=n.setTimer||((d,w)=>setTimeout(d,w)),s=n.clearTimer||(d=>clearTimeout(d)),a=!1,l=!1,p=-1/0,g=null,f=!1;function c(){if(f||a){l=!0;return}let d=t-(r()-p);if(d>0){u(d);return}a=!0,l=!1,p=r();let w=!1;e(()=>{w||(w=!0,a=!1,l&&!f&&c())})}function u(d){f||g!==null||(g=o(()=>{g=null,c()},Math.max(0,d)))}return{signaler(){f||c()},maintenant(){f||(p=-1/0,c())},arreter(){f=!0,g!==null&&(s(g),g=null)}}}function _n(e,n,t=J,r={}){let o=r.fetch||(typeof fetch==\"function\"?fetch:void 0);if(!o)return Promise.reject(new Error(\"fetch indisponible\"));let s=r.setTimer||((u,b)=>setTimeout(u,b)),a=r.clearTimer||(u=>clearTimeout(u)),l=null;try{l=new AbortController}catch(u){}let p=l?xe(K({},n||{}),{signal:l.signal}):K({},n||{}),g=null,f=new Promise((u,b)=>{g=s(()=>{try{l==null||l.abort()}catch(d){}b(new Error(\"delai\"))},t)}),c=()=>{try{a(g)}catch(u){}};return Promise.race([o(e,p),f]).then(u=>(c(),u),u=>{throw c(),u})}function En(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 Tn(e={}){var g;let n=(g=e.minMs)!=null?g:0,t=e.now||(()=>Date.now()),r=e.setTimer||((f,c)=>setTimeout(f,c)),o=new Map,s=!1,a=-1/0,l=!1;function p(){if(s||o.size===0)return;let f=n-(t()-a);if(f>0){if(l)return;l=!0,r(()=>{l=!1,p()},f);return}let c=o.keys().next().value,u=o.get(c);o.delete(c),s=!0,a=t(),Promise.resolve().then(u.tache).then(b=>u.regler(b),()=>u.regler(null)).then(()=>{s=!1,p()})}return{poser(f,c){return new Promise(u=>{let b=o.get(f);o.set(f,{tache:c,regler:d=>{b&&b.regler(d),u(d)}}),p()})},enAttente:()=>o.size}}function Sn(e,n=te){try{if(e){let t=e.getItem(x.presenceId);if(t)return t;let r=`p-${n()}`;return e.setItem(x.presenceId,r),r}}catch(t){}return`p-${n()}`}var U={};M(U,{messageClassName:()=>Rn,renderActions:()=>He,renderAttachment:()=>$e,renderMessage:()=>kn,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 l=a.indexOf(r)>=0;o+=`<button class=\"re-chip${l?\" on\":\"\"}\" data-e=\"${h(s)}\">${h(s)} ${a.length}</button>`}return o}function $e(e){if(!e||!e.url)return\"\";let n=h(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=h(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 He(e,n){let t=_(e,n.me),r=`<button class=cm-react title=\"R\\xE9agir\">${n.reactIcon||\"\"}</button><button class=cm-reply title=\"R\\xE9pondre\">\\u21A9</button>`;return t&&(r+='<button class=cm-edit title=\"Modifier\">\\u270E</button>'),(t||oe(n.me))&&(r+='<button class=cm-del-btn title=\"Supprimer\">\\u{1F5D1}</button>'),r}function kn(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>${h(e.reply_name||\"\")}</b> ${h(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>${h(e.author_name||\"Invit\\xE9\")}</b>${s}</div>`+t+o+$e(e.attachment)+`<div class=cm-re>${Le(e,n.me)}</div></span><span class=cm-act>${He(e,n)}</span>`}function Rn(e,n,t){return\"cm\"+(_(e,n)?\" me\":\"\")+(e.deleted?\" isdel\":\"\")+(t?\" mentioned\":\"\")}var D={};M(D,{DEFAULT_CENTER:()=>ae,DEFAULT_ZOOM:()=>ue,MAP_TYPES:()=>P,cycleMapType:()=>Pn,initialMapContent:()=>An,mapTypeLabel:()=>In,sanitizeContent:()=>le});var P=[\"roadmap\",\"satellite\",\"hybrid\"],ae=[46.6,2.5],ue=6;function E(e){let n=Number(e);return Number.isFinite(n)?n:null}function se(e){if(!Array.isArray(e))return null;let n=E(e[0]),t=E(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:E(r.heading)||0,pitch:ie(E(r.pitch)||0,-90,90)}:{heading:0,pitch:0};return{kind:\"streetview\",position:t,pov:o,zoom:ie(E(n.zoom)||1,0,5)}}return n.kind!==\"map\"?null:{kind:\"map\",center:se(n.center)||[...ae],zoom:ie(Math.trunc(E(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 Pn(e){let n=P.indexOf(e);return P[(n+1)%P.length]}function In(e){return e===\"roadmap\"?\"\\u{1F6F0} Satellite\":e===\"satellite\"?\"\\u{1F5FA} Hybride\":\"\\u{1F5FA} Plan\"}function An(){return{kind:\"map\",center:[...ae],zoom:ue,marker:null,mapType:null,label:null}}var W={};M(W,{CURRENT_PAGE_MARGIN:()=>Hn,DEFAULT_ASPECT:()=>On,MAX_ZOOM:()=>Nn,MIN_PAGE_WIDTH:()=>Ln,MIN_ZOOM:()=>Cn,PRERENDER_MARGIN:()=>$n,arrowState:()=>Wn,averageColor:()=>Fn,clampPage:()=>Ue,clampZoom:()=>Un,fitWidth:()=>zn,isImageDocument:()=>Bn,progressPercent:()=>Dn});var Cn=.5,Nn=3,On=1.35,Ln=280,$n=\"500px 0px\",Hn=\"-48% 0px -48% 0px\";function Un(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 Dn(e,n){return n?Math.round(Ue(e,n)/n*100):0}function Wn(e,n){return{prevDisabled:e<=1,nextDisabled:!!n&&e>=n}}var Vn=28;function zn(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)-Vn-(e.overlap||0)-(e.reserve||0))/r;return s>120?Math.min(t,s):t}function Fn(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||\"\"))}var V={};M(V,{presentationTransition:()=>jn,switchDocUrl:()=>Gn});function jn(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 Gn(e,n){let t=encodeURIComponent(n||String(Date.now()));return`${e}${e.includes(\"?\")?\"&\":\"?\"}v=${t}`}return Ge(Kn);})();", SOURCE_HASH: "24b1b68db3671718" };
|
|
9
|
+
module.exports = { PLAYER_BROWSER_JS: "\"use strict\";var Player=(()=>{var C=Object.defineProperty,We=Object.defineProperties,Ve=Object.getOwnPropertyDescriptor,ze=Object.getOwnPropertyDescriptors,Fe=Object.getOwnPropertyNames,ve=Object.getOwnPropertySymbols;var we=Object.prototype.hasOwnProperty,Be=Object.prototype.propertyIsEnumerable;var Me=(e,n,t)=>n in e?C(e,n,{enumerable:!0,configurable:!0,writable:!0,value:t}):e[n]=t,K=(e,n)=>{for(var t in n||(n={}))we.call(n,t)&&Me(e,t,n[t]);if(ve)for(var t of ve(n))Be.call(n,t)&&Me(e,t,n[t]);return e},xe=(e,n)=>We(e,ze(n));var M=(e,n)=>{for(var t in n)C(e,t,{get:n[t],enumerable:!0})},je=(e,n,t,r)=>{if(n&&typeof n==\"object\"||typeof n==\"function\")for(let o of Fe(n))!we.call(e,o)&&o!==t&&C(e,o,{get:()=>n[o],enumerable:!(r=Ve(n,o))||r.enumerable});return e};var Ge=e=>je(C({},\"__esModule\",{value:!0}),e);var Kn={};M(Kn,{bridge:()=>N,cadence:()=>L,chat:()=>U,live:()=>H,presentation:()=>D,presentationState:()=>V,tracking:()=>$,viewer:()=>W});var N={};M(N,{onHostMessage:()=>tn,onPlayerMessage:()=>nn,parseHostMessage:()=>Te,parsePlayerMessage:()=>Ee,sendToHost:()=>Xe,sendToPlayer:()=>Je,toWire:()=>Z});var Y=\"3dd-doc-\",Ke=/^[A-Za-z0-9_-]{1,64}$/,Ye=[\"close\",\"share\",\"embed-ready\",\"present-left\",\"present-denied\"],Ze=[\"embed-denied\"],qe=[\"present-invite\",\"present-handover\",\"present-switch\"],Qe=[\"handover-done\"];function Z(e){let n={type:Y+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 _e(e,n,t){if(!e||typeof e!=\"object\")return null;let r=e.type;if(typeof r!=\"string\"||!r.startsWith(Y))return null;let o=r.slice(Y.length);if(n.includes(o))return{type:o};if(Ze.includes(o)){let a=e.reason;return{type:o,reason:/^[a-z-]{1,40}$/.test(String(a||\"\"))?String(a):\"unknown\"}}if(!t.includes(o))return null;let s=e.slug;return typeof s!=\"string\"||!Ke.test(s)?null:{type:o,slug:s}}function Ee(e){return _e(e,Ye,qe)}function Te(e){return _e(e,Qe,[])}function Xe(e,n){try{let t=n||(typeof window!=\"undefined\"?window.parent:null);t&&t.postMessage(Z(e),\"*\")}catch(t){}}function Je(e,n){var t;try{(t=e==null?void 0:e.contentWindow)==null||t.postMessage(Z(n),\"*\")}catch(r){}}function en(e,n){var r;if(!e)return!0;let t=(r=e.contentWindow)!=null?r:e;return n===t}function nn(e,n,t){let r=n||(typeof window!=\"undefined\"?window:null);if(!r)return()=>{};let o=s=>{if(!en(t,s.source))return;let a=Ee(s.data);a&&e(a)};return r.addEventListener(\"message\",o),()=>r.removeEventListener(\"message\",o)}function tn(e,n){var s;let t=n||(typeof window!=\"undefined\"?window:null);if(!t)return()=>{};let r=(s=t.parent)!=null?s:null,o=a=>{if(!r||r===t||a.source!==r)return;let l=Te(a.data);l&&e(l)};return t.addEventListener(\"message\",o),()=>t.removeEventListener(\"message\",o)}var $={};M($,{createTracker:()=>pn});var L={};M(L,{PRESENTER_ACTIONS_PER_HOUR:()=>X,PRESENT_CACHE_MS:()=>an,PRESENT_QUOTA_MARGIN:()=>Ne,PRESENT_QUOTA_PER_HOUR:()=>un,PRESENT_READS_PER_HOUR:()=>Ie,PRESENT_READ_BURST:()=>Ae,PRESENT_READ_COALESCE_MS:()=>Ce,PRESENT_RESYNC_MS:()=>Re,PRESENT_SIGNAL_BUDGET_PER_HOUR:()=>sn,PRESENT_WRITE_TIMEOUT_MS:()=>J,READERS_PER_EGRESS:()=>O,RESYNC_READS_PER_HOUR:()=>Pe,SESSION_IDLE_MS:()=>Q,SESSION_INTERVAL_MS:()=>q,SESSION_QUOTA_PER_HOUR:()=>rn,SESSION_WRITES_PER_HOUR:()=>Se,VIEW_EVENTS_PER_READER_HOUR:()=>ke,VIEW_QUOTA_PER_HOUR:()=>on});var q=3e4,Se=Math.ceil(120),O=25,rn=Se*O,ke=720,on=ke*O,Q=18e4,Re=25e3,Pe=Math.ceil(36e5/Re),X=720,Ie=Pe+X,sn=X,Ae=20,J=1e4,Ce=400,an=Ce,Ne=4,un=(Ie+Ae)*O*Ne;var ln=[\"mousemove\",\"mousedown\",\"keydown\",\"wheel\",\"touchstart\",\"pointerdown\"];function cn(){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 dn(e,n){return t=>{try{let r=JSON.stringify(t),o=n.navigator;return 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}),!0)}catch(r){return!1}}}function pn(e={}){var be,ye;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=(be=e.idleMs)!=null?be:Q,a=(ye=e.sessionEveryMs)!=null?ye:3e4,l=e.slug||\"\",p=e.internal||null,g=e.sessionId||cn(),f=e.send||dn(o,n),c={},u=0,b=0,d=0,w=0,v=null,T=r(),S=!1,z=!1,I=[],De=()=>!!l||!!p,ce=i=>{if(!De())return!1;try{return f(i)!==!1}catch(m){return!1}},F=()=>!!t&&t.visibilityState===\"visible\"&&!S,de=i=>{let m=r()-i,y=Math.max(0,T-i)+s;return Math.max(0,Math.min(m,y))},B=()=>{v!=null&&u>0&&(c[u]=(c[u]||0)+de(v)/1e3),v=F()?r():null},pe=()=>{B(),v=null},j=()=>{v==null&&F()&&(v=r())},G=()=>{let i=0;for(let m in c)i+=c[m];return v!=null&&(i+=de(v)/1e3),Math.round(i)},fe=()=>{let i={};for(let m in c)i[m]=Math.round(c[m]);return i},k=()=>{T=r(),S&&(S=!1,j())},me=(i,m)=>{p||ce({slug:l,event:i,page:m||b,maxPage:d,seconds:G(),sessionId:g})},ge=\"\",A=()=>{B();let i=G(),m=`${i}|${d}|${w}`;if(m===ge)return;let y={event:\"session\",sessionId:g,numPages:w,maxPage:d,totalSeconds:i,pagesTime:fe()};p?(y.internal=!0,y.docId=p.docId,y.email=p.email,y.name=p.name,p.it&&(y.it=p.it)):y.slug=l,ce(y)&&(ge=m)},R=(i,m,y,he)=>{!i||typeof i.addEventListener!=\"function\"||(i.addEventListener(m,y,he),I.push(()=>{i.removeEventListener(m,y,he)}))};return{sessionId:g,totalSeconds:G,maxPage:()=>d,pageTimes:fe,start(){if(z)return;z=!0,me(\"open\",1),u=1,T=r(),v=F()?r():null,R(t,\"visibilitychange\",()=>{t.visibilityState===\"hidden\"?(pe(),A()):(k(),j())}),R(n,\"focus\",()=>{k(),j()});for(let y of ln)R(n,y,k,{passive:!0});R(e.scrollElement,\"scroll\",k,{passive:!0}),R(n,\"beforeunload\",A);let i=n.setInterval(()=>{!S&&r()-T>s&&(S=!0,pe())},5e3),m=n.setInterval(A,a);I.push(()=>{n.clearInterval(i),n.clearInterval(m)})},setPage(i){!i||i===b||(T=r(),B(),u=i,b=i,i>d&&(d=i,me(\"page\",i)))},setPageCount(i){w=i||0},noteActivity:k,flush:A,stop(){for(;I.length;){let i=I.pop();try{i==null||i()}catch(m){}}z=!1}}}var H={};M(H,{STORAGE_KEYS:()=>x,attendeeKey:()=>bn,authorToken:()=>yn,avatarHtml:()=>ee,canModerate:()=>oe,createBudget:()=>En,createFileEcritures:()=>Tn,createScheduler:()=>xn,escapeHtml:()=>h,fetchBorne:()=>_n,flattenPresence:()=>fn,formatMessageBody:()=>ne,initials:()=>Oe,isMentioned:()=>vn,isMine:()=>_,presenceId:()=>Sn,reactorId:()=>re,referenceAuteur:()=>hn,shouldNotify:()=>Mn,unreadLabel:()=>wn});var x={attendeeKey:\"3dd-present-attkey\",authorToken:\"3dd-present-authtoken\",chatMuted:\"3dd-present-mute\",presenceId:\"3dd-present-uid\"};function h(e){return String(e==null?\"\":e).replace(/&/g,\"&\").replace(/</g,\"<\").replace(/>/g,\">\").replace(/\"/g,\""\")}function Oe(e){let n=(e||\"?\").trim().split(/\\s+/);return(((n[0]||\"\")[0]||\"?\")+((n[1]||\"\")[0]||\"\")).toUpperCase()}function ee(e,n){return e?`<img src=\"${h(e)}\" alt=\"\">`:h(Oe(n))}function ne(e){let n=h(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 fn(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 mn(e,n){try{return e?e.getItem(n):null}catch(t){return null}}function gn(e,n,t){try{e==null||e.setItem(n,t)}catch(r){}}function bn(e,n=\"anon\",t=te){try{if(!e)return`anon-${n}`;let r=e.getItem(x.attendeeKey);if(r)return r;let o=`anon-${t()}`;return e.setItem(x.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 yn(e,n=te){let t=mn(e,x.authorToken);if(t)return t;let r=n();return gn(e,x.authorToken,r),r}async function hn(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 _(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 vn(e,n){if(!n||!n.name)return!1;let t=(n.name.split(/\\s+/)[0]||\"\").toLowerCase();return t?String(e&&e.body||\"\").toLowerCase().includes(`@${t}`)&&!_(e,n):!1}function Mn(e){let{msg:n,me:t,historyLoaded:r,chatHidden:o}=e;return!r||!n||n.deleted||_(n,t)?!1:o}function wn(e){return!e||e<=0?\"\":e>9?\"9+\":String(e)}function xn(e,n={}){var b;let t=(b=n.minMs)!=null?b:300,r=n.now||(()=>Date.now()),o=n.setTimer||((d,w)=>setTimeout(d,w)),s=n.clearTimer||(d=>clearTimeout(d)),a=!1,l=!1,p=-1/0,g=null,f=!1;function c(){if(f||a){l=!0;return}let d=t-(r()-p);if(d>0){u(d);return}a=!0,l=!1,p=r();let w=!1;e(()=>{w||(w=!0,a=!1,l&&!f&&c())})}function u(d){f||g!==null||(g=o(()=>{g=null,c()},Math.max(0,d)))}return{signaler(){f||c()},maintenant(){f||(p=-1/0,c())},arreter(){f=!0,g!==null&&(s(g),g=null)}}}function _n(e,n,t=J,r={}){let o=r.fetch||(typeof fetch==\"function\"?fetch:void 0);if(!o)return Promise.reject(new Error(\"fetch indisponible\"));let s=r.setTimer||((u,b)=>setTimeout(u,b)),a=r.clearTimer||(u=>clearTimeout(u)),l=null;try{l=new AbortController}catch(u){}let p=l?xe(K({},n||{}),{signal:l.signal}):K({},n||{}),g=null,f=new Promise((u,b)=>{g=s(()=>{try{l==null||l.abort()}catch(d){}b(new Error(\"delai\"))},t)}),c=()=>{try{a(g)}catch(u){}};return Promise.race([o(e,p),f]).then(u=>(c(),u),u=>{throw c(),u})}function En(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 Tn(e={}){var g;let n=(g=e.minMs)!=null?g:0,t=e.now||(()=>Date.now()),r=e.setTimer||((f,c)=>setTimeout(f,c)),o=new Map,s=!1,a=-1/0,l=!1;function p(){if(s||o.size===0)return;let f=n-(t()-a);if(f>0){if(l)return;l=!0,r(()=>{l=!1,p()},f);return}let c=o.keys().next().value,u=o.get(c);o.delete(c),s=!0,a=t(),Promise.resolve().then(u.tache).then(b=>u.regler(b),()=>u.regler(null)).then(()=>{s=!1,p()})}return{poser(f,c){return new Promise(u=>{let b=o.get(f);o.set(f,{tache:c,regler:d=>{b&&b.regler(d),u(d)}}),p()})},enAttente:()=>o.size}}function Sn(e,n=te){try{if(e){let t=e.getItem(x.presenceId);if(t)return t;let r=`p-${n()}`;return e.setItem(x.presenceId,r),r}}catch(t){}return`p-${n()}`}var U={};M(U,{messageClassName:()=>Rn,renderActions:()=>He,renderAttachment:()=>$e,renderMessage:()=>kn,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 l=a.indexOf(r)>=0;o+=`<button class=\"re-chip${l?\" on\":\"\"}\" data-e=\"${h(s)}\">${h(s)} ${a.length}</button>`}return o}function $e(e){if(!e||!e.url)return\"\";let n=h(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=h(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 He(e,n){let t=_(e,n.me),r=`<button class=cm-react title=\"R\\xE9agir\">${n.reactIcon||\"\"}</button><button class=cm-reply title=\"R\\xE9pondre\">\\u21A9</button>`;return t&&(r+='<button class=cm-edit title=\"Modifier\">\\u270E</button>'),(t||oe(n.me))&&(r+='<button class=cm-del-btn title=\"Supprimer\">\\u{1F5D1}</button>'),r}function kn(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>${h(e.reply_name||\"\")}</b> ${h(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>${h(e.author_name||\"Invit\\xE9\")}</b>${s}</div>`+t+o+$e(e.attachment)+`<div class=cm-re>${Le(e,n.me)}</div></span><span class=cm-act>${He(e,n)}</span>`}function Rn(e,n,t){return\"cm\"+(_(e,n)?\" me\":\"\")+(e.deleted?\" isdel\":\"\")+(t?\" mentioned\":\"\")}var D={};M(D,{DEFAULT_CENTER:()=>ae,DEFAULT_ZOOM:()=>ue,MAP_TYPES:()=>P,cycleMapType:()=>Pn,initialMapContent:()=>An,mapTypeLabel:()=>In,sanitizeContent:()=>le});var P=[\"roadmap\",\"satellite\",\"hybrid\"],ae=[46.6,2.5],ue=6;function E(e){let n=Number(e);return Number.isFinite(n)?n:null}function se(e){if(!Array.isArray(e))return null;let n=E(e[0]),t=E(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:E(r.heading)||0,pitch:ie(E(r.pitch)||0,-90,90)}:{heading:0,pitch:0};return{kind:\"streetview\",position:t,pov:o,zoom:ie(E(n.zoom)||1,0,5)}}return n.kind!==\"map\"?null:{kind:\"map\",center:se(n.center)||[...ae],zoom:ie(Math.trunc(E(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 Pn(e){let n=P.indexOf(e);return P[(n+1)%P.length]}function In(e){return e===\"roadmap\"?\"\\u{1F6F0} Satellite\":e===\"satellite\"?\"\\u{1F5FA} Hybride\":\"\\u{1F5FA} Plan\"}function An(){return{kind:\"map\",center:[...ae],zoom:ue,marker:null,mapType:null,label:null}}var W={};M(W,{CURRENT_PAGE_MARGIN:()=>Hn,DEFAULT_ASPECT:()=>On,MAX_ZOOM:()=>Nn,MIN_PAGE_WIDTH:()=>Ln,MIN_ZOOM:()=>Cn,PRERENDER_MARGIN:()=>$n,arrowState:()=>Wn,averageColor:()=>Fn,clampPage:()=>Ue,clampZoom:()=>Un,fitWidth:()=>zn,isImageDocument:()=>Bn,progressPercent:()=>Dn});var Cn=.5,Nn=3,On=1.35,Ln=280,$n=\"500px 0px\",Hn=\"-48% 0px -48% 0px\";function Un(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 Dn(e,n){return n?Math.round(Ue(e,n)/n*100):0}function Wn(e,n){return{prevDisabled:e<=1,nextDisabled:!!n&&e>=n}}var Vn=28;function zn(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)-Vn-(e.overlap||0)-(e.reserve||0))/r;return s>120?Math.min(t,s):t}function Fn(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||\"\"))}var V={};M(V,{presentationTransition:()=>jn,switchDocUrl:()=>Gn});function jn(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 Gn(e,n){let t=encodeURIComponent(n||String(Date.now()));return`${e}${e.includes(\"?\")?\"&\":\"?\"}v=${t}`}return Ge(Kn);})();", SOURCE_HASH: "483d392a214f6bf8" };
|
package/server/cache.js
CHANGED
package/server/erreurs-base.js
CHANGED
package/server/gabarit-agent.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// Copyright © 2026 3D Discovery
|
|
1
3
|
// EXTRAIT DE handler.js (refactor 19/08/2026) — texte déplacé À L'IDENTIQUE, aucun changement
|
|
2
4
|
// de comportement. Reste à PLAT dans server/ : plusieurs gardes de forge ciblent server/*.js,
|
|
3
5
|
// un sous-dossier les viderait en silence (la garde qui énumère maigrit quand on range).
|
package/server/gabarit-carte.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// Copyright © 2026 3D Discovery
|
|
1
3
|
// EXTRAIT DE handler.js (refactor 19/08/2026) — texte déplacé À L'IDENTIQUE, aucun changement
|
|
2
4
|
// de comportement. Reste à PLAT dans server/ : plusieurs gardes de forge ciblent server/*.js,
|
|
3
5
|
// un sous-dossier les viderait en silence (la garde qui énumère maigrit quand on range).
|
package/server/gabarit-legal.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// Copyright © 2026 3D Discovery
|
|
1
3
|
// EXTRAIT DE handler.js (refactor lot 2, 19/08/2026) — texte déplacé À L'IDENTIQUE. Reste à
|
|
2
4
|
// PLAT dans server/ (les gardes de forge ciblent server/*.js).
|
|
3
5
|
const { esc } = require("./texte");
|
package/server/gabarit-live.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// Copyright © 2026 3D Discovery
|
|
1
3
|
// EXTRAIT DE handler.js (refactor 19/08/2026) — texte déplacé À L'IDENTIQUE, aucun changement
|
|
2
4
|
// de comportement. Reste à PLAT dans server/ : plusieurs gardes de forge ciblent server/*.js,
|
|
3
5
|
// un sous-dossier les viderait en silence (la garde qui énumère maigrit quand on range).
|
package/server/handler.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// Copyright © 2026 3D Discovery
|
|
1
3
|
// Page publique de consultation d'un document commercial : /doc/:slug → visionneuse pdf.js qui TRACE
|
|
2
4
|
// l'ouverture et les PAGES VUES (un lien par destinataire → on sait qui a lu, combien de pages).
|
|
3
5
|
// - GET /doc/:slug → HTML visionneuse (pdf.js EMBARQUÉ, servi par ?asset=…, nonce CSP)
|
|
@@ -50,6 +52,11 @@ let docbot = null;
|
|
|
50
52
|
// Version publiée du player — lue là où elle est déjà déclarée, pour qu'elle ne puisse pas
|
|
51
53
|
// diverger de ce que l'hôte a réellement installé.
|
|
52
54
|
const PLAYER_VERSION = require("../package.json").version;
|
|
55
|
+
// ⚠️ LE PLANCHER, LU LÀ OÙ IL EST DÉCLARÉ. Un hôte ne peut pas confronter le Node qu'il CROIT
|
|
56
|
+
// déployer à celui qui TOURNE : mesuré le 25/08 chez un intégrateur dont le réglage de plateforme
|
|
57
|
+
// disait « 24.x » pendant que la fonction servant la production tournait en 22. Il n'avait aucune
|
|
58
|
+
// route à interroger — ni chez lui, ni chez nous. Le player le sait, lui ; il ne le disait pas.
|
|
59
|
+
const NODE_REQUIS = require("../package.json").engines.node;
|
|
53
60
|
// Registre des marques : le loader porte celle du CLIENT dont on montre le document.
|
|
54
61
|
const brands = require("./brands");
|
|
55
62
|
|
|
@@ -186,16 +193,10 @@ const { adresseAppelant } = require("./appelant");
|
|
|
186
193
|
*/
|
|
187
194
|
const PLAFOND_RELAIS = Number(process.env.PLAYER_MAX_RELAY_BYTES || 0) || 60 * 1024 * 1024;
|
|
188
195
|
|
|
189
|
-
// ⚠️
|
|
190
|
-
//
|
|
191
|
-
//
|
|
192
|
-
|
|
193
|
-
function refuserEnTexte(res, statut, message) {
|
|
194
|
-
res.statusCode = statut;
|
|
195
|
-
res.setHeader("Content-Type", "text/plain; charset=utf-8");
|
|
196
|
-
res.setHeader("X-Content-Type-Options", "nosniff");
|
|
197
|
-
res.end(message);
|
|
198
|
-
}
|
|
196
|
+
// ⚠️ LES PORTES DE SORTIE VIVENT DANS UN MODULE À PART, et le pourquoi est écrit là-bas : les
|
|
197
|
+
// familles de routes en ont besoin AUSSI, et elles ne peuvent pas recharger `handler.js` sans
|
|
198
|
+
// fermer un cycle. Une seule définition, requise par tout le monde.
|
|
199
|
+
const { refuserEnTexte, repondreJson, repondreJsonTexte } = require("./reponses.js");
|
|
199
200
|
|
|
200
201
|
// ⚠️ CE QUE LES PAGES N'UTILISENT PAS EST REFUSÉ PAR ÉCRIT. Sans Permissions-Policy, un script
|
|
201
202
|
// tiers compromis (le risque exact que l'inventaire TIERS épingle) hérite de tout ce que le
|
|
@@ -533,10 +534,9 @@ async function handler(req, res) {
|
|
|
533
534
|
// rien à afficher, et ce n'est pas un refus. Le dire franchement évite qu'un intégrateur
|
|
534
535
|
// cherche un lien révoqué là où il lui manque un paramètre.
|
|
535
536
|
if (req.method === "GET" && !slug && !q.present && !q.preview && !q.contract && !q.asset) {
|
|
536
|
-
res
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
"Si vous intégrez le player, vérifiez que la plateforme fournit les paramètres de requête.");
|
|
537
|
+
refuserEnTexte(res, 400,
|
|
538
|
+
"Aucun document demandé. Attendu : ?slug=… , ?present=… , ?preview=1, ?contract=1 ou ?asset=….\n" +
|
|
539
|
+
"Si vous intégrez le player, vérifiez que la plateforme fournit les paramètres de requête.");
|
|
540
540
|
return;
|
|
541
541
|
}
|
|
542
542
|
|
|
@@ -590,6 +590,18 @@ async function handler(req, res) {
|
|
|
590
590
|
// un paramètre ou un motif de refus ne le change pas.
|
|
591
591
|
contract: 1,
|
|
592
592
|
version: PLAYER_VERSION,
|
|
593
|
+
// ⚠️ SUR QUOI CETTE INSTANCE TOURNE, ET CE QU'ELLE EXIGE. Deux nombres, aucune décision :
|
|
594
|
+
// l'hôte compare avec son propre semver. Écrire ici « supporté : oui » demanderait un
|
|
595
|
+
// évaluateur d'intervalles dans le serveur, et ce dépôt a déjà payé deux fois d'avoir
|
|
596
|
+
// analysé un format structuré à la main.
|
|
597
|
+
//
|
|
598
|
+
// ⚠️ ON DONNE LE CORRECTIF, PAS SEULEMENT LA MINEURE, et ce n'est pas un oubli. Le plancher
|
|
599
|
+
// est de niveau correctif (`>=22.13.0`), donc une majeure.mineure ne permet PAS de répondre
|
|
600
|
+
// à la seule question posée. Et la carte publie déjà `version`, qui désigne notre code à
|
|
601
|
+
// l'exemplaire près : taire un chiffre de Node en publiant celui-là serait cohérent avec
|
|
602
|
+
// rien. La carte reste muette sur ce qui aide à ATTEIGNER l'instance — URL, secrets, noms
|
|
603
|
+
// d'hôte ; une version de moteur ne fait entrer personne.
|
|
604
|
+
runtime: { node: process.versions.node, nodeRequired: NODE_REQUIS },
|
|
593
605
|
// Ce que cette instance sait faire. Un hôte teste la présence, jamais l'ordre.
|
|
594
606
|
// `host-auth` : cette instance sait vérifier les jetons auprès d'un émetteur DISTINCT de
|
|
595
607
|
// sa base (PLAYER_AUTH_URL). Un hôte tiers en a besoin pour savoir si ses membres
|
|
@@ -752,8 +764,7 @@ async function handler(req, res) {
|
|
|
752
764
|
console.warn(`[player] relectures de présentation refusées : quota horaire par adresse atteint (${PRESENT_QUOTA_PER_HOUR}/h). Une audience nombreuse derrière une sortie unique verra ses pages cesser de tourner.`);
|
|
753
765
|
} catch { /* sans console */ }
|
|
754
766
|
}
|
|
755
|
-
res
|
|
756
|
-
res.end(JSON.stringify({ ok: false, error: "rate" }));
|
|
767
|
+
repondreJson(res, 429, { ok: false, error: "rate" }, { "Cache-Control": "no-store" });
|
|
757
768
|
return;
|
|
758
769
|
}
|
|
759
770
|
}
|
|
@@ -846,18 +857,16 @@ async function handler(req, res) {
|
|
|
846
857
|
// 503 réessayable dit à l'appelant d'attendre une seconde ; un 500 lui dit d'abandonner.
|
|
847
858
|
// Les confondre ferait renoncer une requête que le prochain battement aurait servie.
|
|
848
859
|
if (e && e.code === CODE_SATURATION) {
|
|
849
|
-
res
|
|
850
|
-
|
|
851
|
-
res.setHeader("Cache-Control", "no-store");
|
|
852
|
-
res.setHeader("Content-Type", "application/json");
|
|
853
|
-
res.end(JSON.stringify({ ok: false, error: "busy" }));
|
|
860
|
+
repondreJson(res, 503, { ok: false, error: "busy" },
|
|
861
|
+
{ "Retry-After": String(e.retryAfter || 1), "Cache-Control": "no-store" });
|
|
854
862
|
return;
|
|
855
863
|
}
|
|
856
864
|
throw e;
|
|
857
865
|
}
|
|
858
866
|
if (!corps) return sendRefusal(res, "ended", embed);
|
|
859
|
-
|
|
860
|
-
|
|
867
|
+
// ⚠️ `corps` est DÉJÀ du texte JSON, construit en amont et relu à chaque battement :
|
|
868
|
+
// l'analyser pour le re-sérialiser serait un aller-retour pur sur un chemin chaud.
|
|
869
|
+
repondreJsonTexte(res, 200, corps, { "Cache-Control": "no-store" });
|
|
861
870
|
return;
|
|
862
871
|
}
|
|
863
872
|
// ⚠️ LA LIGNE N'EST LUE QU'ICI, ET C'EST CE QUI DONNE SON SENS AU CACHE. Elle était lue
|
|
@@ -940,7 +949,7 @@ async function handler(req, res) {
|
|
|
940
949
|
const gated = share.require_auth === true && !visitor;
|
|
941
950
|
|
|
942
951
|
if (String(q.file || "") === "1") {
|
|
943
|
-
if (gated) { res
|
|
952
|
+
if (gated) { repondreJson(res, 401, { ok: false, error: "auth" }); return; }
|
|
944
953
|
// Stream depuis le Storage en RELAYANT les requêtes Range → pdf.js charge progressivement (les 1res
|
|
945
954
|
// pages s'affichent sans télécharger tout le PDF) → affichage bien plus rapide.
|
|
946
955
|
const range = req.headers["range"];
|
|
@@ -1026,7 +1035,7 @@ async function handler(req, res) {
|
|
|
1026
1035
|
return sendHtml(res, 200, viewerHtml(share, nonce, logoUrl, pitch), `'nonce-${nonce}'`, originesImages(logoUrl, share), frameAncestors);
|
|
1027
1036
|
} catch (error) {
|
|
1028
1037
|
try { await PLAYER.errors.capture(error, { route: "doc", method: req.method }); } catch { /* ignore */ }
|
|
1029
|
-
res
|
|
1038
|
+
refuserEnTexte(res, 500, "Erreur");
|
|
1030
1039
|
}
|
|
1031
1040
|
}
|
|
1032
1041
|
|
|
@@ -1035,6 +1044,6 @@ async function handler(req, res) {
|
|
|
1035
1044
|
// tenir une seconde liste — c'est la seule façon qu'une empreinte périmée finisse par se voir.
|
|
1036
1045
|
// ⚠️ Exporté pour être ÉPROUVÉ, pas pour être appelé : le plafond du relais ne se vérifie qu en
|
|
1037
1046
|
// regardant si le corps a été lu, ce qu aucune route ne peut montrer de l extérieur.
|
|
1038
|
-
module.exports = { handler, init, TIERS, POLITIQUE_PERMISSIONS, __relayerFichier: relayerFichier, __jsonPourScript: jsonPourScript };
|
|
1047
|
+
module.exports = { handler, init, TIERS, POLITIQUE_PERMISSIONS, refuserEnTexte, repondreJson, __relayerFichier: relayerFichier, __jsonPourScript: jsonPourScript };
|
|
1039
1048
|
|
|
1040
1049
|
// redeploy: forcer le build production (Vercel a sauté la prod du merge #463 — wording re-partage).
|
package/server/page-audience.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// Copyright © 2026 3D Discovery
|
|
1
3
|
// EXTRAIT DE handler.js (refactor lot 2, 19/08/2026) — texte déplacé À L'IDENTIQUE. Reste à
|
|
2
4
|
// PLAT dans server/ (les gardes de forge ciblent server/*.js).
|
|
3
5
|
const { esc, jsonPourScript } = require("./texte");
|
package/server/page-mur.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// Copyright © 2026 3D Discovery
|
|
1
3
|
// EXTRAIT DE handler.js (refactor lot 2, 19/08/2026) — texte déplacé À L'IDENTIQUE. Reste à
|
|
2
4
|
// PLAT dans server/ (les gardes de forge ciblent server/*.js).
|
|
3
5
|
const { esc, jsonPourScript } = require("./texte");
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// Copyright © 2026 3D Discovery
|
|
1
3
|
// EXTRAIT DE handler.js (refactor lot 2, 19/08/2026) — texte déplacé À L'IDENTIQUE. Reste à
|
|
2
4
|
// PLAT dans server/ (les gardes de forge ciblent server/*.js).
|
|
3
5
|
const { esc, jsonPourScript } = require("./texte");
|
package/server/presentations.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// Copyright © 2026 3D Discovery
|
|
1
3
|
// Mode « Présenter » : sessions de présentation live (page synchronisée). Table doc_presentations
|
|
2
4
|
// (cf. migration v12324) — écriture service role only. Le présentateur détient un control_token ; on en
|
|
3
5
|
// stocke le HASH (sha256) → l'audience peut lire la ligne (Realtime) sans pouvoir piloter.
|
package/server/publier.js
CHANGED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// Copyright © 2026 3D Discovery
|
|
3
|
+
// LES PORTES PAR LESQUELLES UNE RÉPONSE QUITTE CE SERVEUR.
|
|
4
|
+
//
|
|
5
|
+
// ⚠️ POURQUOI UN MODULE, ET PAS UNE FONCTION DE PLUS DANS handler.js. `handler.js` charge les
|
|
6
|
+
// familles de routes ; si elles le rechargeaient pour y prendre une aide, le cycle serait fermé.
|
|
7
|
+
// Un module qui ne dépend de rien peut être requis par tout le monde — c'est la seule forme qui
|
|
8
|
+
// permet qu'il n'y ait QU'UNE définition.
|
|
9
|
+
//
|
|
10
|
+
// ⚠️ CE QUI A RENDU CE FICHIER NÉCESSAIRE, deux fois de suite.
|
|
11
|
+
//
|
|
12
|
+
// 1. LE TEXTE (audit CODEX 5.6, §3). Le `500` du bout de `/doc` posait un code et un corps et
|
|
13
|
+
// RIEN d'autre : le seul corps de ce serveur qu'un navigateur avait le droit de deviner. La
|
|
14
|
+
// règle existait pourtant depuis un mois — le premier scan ZAP baseline (règle 10019, 24/08)
|
|
15
|
+
// avait fait naître `refuserEnTexte`. Elle n'avait tenu que là où elle était écrite : trois
|
|
16
|
+
// autres réponses en texte lui avaient échappé.
|
|
17
|
+
//
|
|
18
|
+
// 2. LE JSON, trouvé en mesurant le premier. L'aide de réponse JSON était définie TREIZE fois,
|
|
19
|
+
// à l'identique, sous quatre noms (`jp`, `jd`, `j`, `jv`), dans quatre fichiers de routes —
|
|
20
|
+
// plus sept corps écrits à la main et cinq réponses inline ailleurs. Vingt endroits, et
|
|
21
|
+
// AUCUN ne posait `nosniff`. Ce n'est pas vingt oublis : c'est ce qu'une recette recopiée
|
|
22
|
+
// devient. La copie initiale était juste ; c'est la CORRECTION qui ne se propage pas.
|
|
23
|
+
//
|
|
24
|
+
// ⚠️ `nosniff` SUR DU JSON N'EST PAS DE LA SUPERSTITION. Sans lui, un navigateur garde le droit de
|
|
25
|
+
// requalifier un corps d'après ce qu'il contient plutôt que d'après ce qu'on annonce — et un
|
|
26
|
+
// contenu qui vient d'un tiers (le nom d'un document, un message de chat) voyage dans ces
|
|
27
|
+
// réponses. Ce dépôt écrit `nosniff` partout ailleurs depuis la 0.1.7 ; les routes API étaient le
|
|
28
|
+
// seul endroit où la règle s'arrêtait, parce qu'aucun scan ne les visite.
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Un corps en TEXTE, avec son type et l'interdiction de le deviner.
|
|
32
|
+
*
|
|
33
|
+
* ⚠️ ELLE DOIT SURVIVRE À UN EN-TÊTE DÉJÀ PARTI. Son premier appelant est le `catch` de `/doc`, et
|
|
34
|
+
* une erreur peut y arriver APRÈS que la page ait commencé à s'écrire : `setHeader` jette alors
|
|
35
|
+
* `ERR_HTTP_HEADERS_SENT`, dans le rattrapage lui-même, ce qui transforme une erreur signalée en
|
|
36
|
+
* rejet non rattrapé. On ne peut plus rien poser à ce stade — le statut est parti — donc on ferme
|
|
37
|
+
* le flux et on se tait, ce qui est exactement ce que le client verra de toute façon. Se taire ici
|
|
38
|
+
* n'efface rien : l'erreur a déjà été confiée à `errors.capture`.
|
|
39
|
+
*/
|
|
40
|
+
function refuserEnTexte(res, statut, message) {
|
|
41
|
+
if (res.headersSent) { try { res.end(); } catch { /* flux déjà clos */ } return; }
|
|
42
|
+
res.statusCode = statut;
|
|
43
|
+
res.setHeader("Content-Type", "text/plain; charset=utf-8");
|
|
44
|
+
res.setHeader("X-Content-Type-Options", "nosniff");
|
|
45
|
+
res.end(message);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Un corps JSON DÉJÀ SÉRIALISÉ, pour les rares appelants qui en tiennent un.
|
|
50
|
+
*
|
|
51
|
+
* ⚠️ DEUX ENTRÉES, UNE SEULE PORTE — et ce n'est pas la même chose que deux copies. L'état d'une
|
|
52
|
+
* présentation est construit en amont sous forme de texte JSON et relu à chaque battement : le
|
|
53
|
+
* ré-analyser pour le re-sérialiser serait un aller-retour pur sur un chemin chaud. Les en-têtes,
|
|
54
|
+
* eux, ne sont posés qu'ici — c'est le fait qui ne doit exister qu'en un exemplaire.
|
|
55
|
+
*
|
|
56
|
+
* `entetes` couvre ce qui varie légitimement d'une réponse à l'autre : `Cache-Control: no-store`
|
|
57
|
+
* sur un état vivant, `Retry-After` sur une saturation, `Set-Cookie` sur une entrée de visiteur.
|
|
58
|
+
*/
|
|
59
|
+
function repondreJsonTexte(res, statut, texte, entetes) {
|
|
60
|
+
if (res.headersSent) { try { res.end(); } catch { /* flux déjà clos */ } return; }
|
|
61
|
+
res.statusCode = statut;
|
|
62
|
+
res.setHeader("Content-Type", "application/json");
|
|
63
|
+
res.setHeader("X-Content-Type-Options", "nosniff");
|
|
64
|
+
for (const [cle, valeur] of Object.entries(entetes || {})) if (valeur != null) res.setHeader(cle, valeur);
|
|
65
|
+
res.end(texte);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Un corps JSON à sérialiser — la forme de très loin la plus courante. */
|
|
69
|
+
function repondreJson(res, statut, valeur, entetes) {
|
|
70
|
+
repondreJsonTexte(res, statut, JSON.stringify(valeur), entetes);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* La porte JSON LIÉE à une réponse — ce que les familles de routes tiennent en main.
|
|
75
|
+
*
|
|
76
|
+
* ⚠️ C'EST CETTE FORME QUI REMPLACE LES TREIZE COPIES, et la nuance compte. Chaque famille gardait
|
|
77
|
+
* une aide locale au nom court (`jp`, `jd`, `j`, `jv`) : la commodité était légitime, c'est la
|
|
78
|
+
* RECETTE recopiée à l'intérieur qui ne l'était pas. Le nom court reste, en une ligne qui délègue ;
|
|
79
|
+
* les en-têtes, eux, n'existent plus qu'en un exemplaire. Les 95 appels ne bougent pas — un
|
|
80
|
+
* correctif qui réécrit 95 lignes pour en corriger 13 se relit mal et se vérifie encore moins bien.
|
|
81
|
+
*/
|
|
82
|
+
const jsonPour = (res) => (statut, valeur, entetes) => repondreJson(res, statut, valeur, entetes);
|
|
83
|
+
|
|
84
|
+
module.exports = { refuserEnTexte, repondreJson, repondreJsonTexte, jsonPour };
|
package/server/retention.js
CHANGED
package/server/routes-agent.js
CHANGED
|
@@ -1,7 +1,16 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// Copyright © 2026 3D Discovery
|
|
1
3
|
// EXTRAIT DE handler.js (refactor lot 3 — routes, 19/08/2026) — blocs déplacés À L'IDENTIQUE.
|
|
2
4
|
// Reste à PLAT dans server/ (les gardes de forge ciblent server/*.js).
|
|
3
5
|
|
|
6
|
+
// ⚠️ EXPLICITE, PARCE QUE LE GLOBAL N'EST PAS LE MODULE. L'extraction depuis handler.js (lot 3)
|
|
7
|
+
// a déplacé bot-tts sans importer `crypto` : la route s'appuyait sur `globalThis.crypto`, dont la
|
|
8
|
+
// forme varie selon le runtime — certains y exposent `createHash`, d'autres (dont l'environnement
|
|
9
|
+
// des bancs) n'ont que WebCrypto, et la route rendait alors 500 à la première synthèse. Le banc
|
|
10
|
+
// agentVoix.test.js échoue sans cette ligne.
|
|
11
|
+
const crypto = require("node:crypto");
|
|
4
12
|
const { adresseAppelant } = require("./appelant");
|
|
13
|
+
const { jsonPour } = require("./reponses.js");
|
|
5
14
|
|
|
6
15
|
const { getShareBySlug } = require("./shares");
|
|
7
16
|
let PLAYER = null; let docbot = null;
|
|
@@ -24,7 +33,7 @@ const ACTIONS_LIEES_A_UNE_SESSION = new Set([
|
|
|
24
33
|
|
|
25
34
|
async function traiter(req, res, body, _slug) {
|
|
26
35
|
if (body.action === "bot-tts") {
|
|
27
|
-
const jp = (
|
|
36
|
+
const jp = jsonPour(res);
|
|
28
37
|
if (!docbot) return jp(404, { ok: false, error: "disabled" });
|
|
29
38
|
try {
|
|
30
39
|
const apiKey = process.env.ELEVENLABS_API_KEY;
|
|
@@ -100,10 +109,10 @@ async function traiter(req, res, body, _slug) {
|
|
|
100
109
|
}
|
|
101
110
|
} catch { /* sans alignement → synchro estimée côté client */ }
|
|
102
111
|
return jp(200, { ok: true, url: pub, align: hasAlign ? pubAlign : null, spoken: spoken !== text ? spoken : undefined });
|
|
103
|
-
} catch { return jp(500, { ok: false }); }
|
|
112
|
+
} catch (e) { try { PLAYER.errors.capture(e, { route: String(body.action || "(sans action)") }); } catch { /* jamais bloquant */ } return jp(500, { ok: false }); }
|
|
104
113
|
}
|
|
105
114
|
if (body.action === "bot-start" || body.action === "bot-say" || body.action === "bot-history" || body.action === "bot-nudge" || body.action === "bot-book" || body.action === "bot-contact" || body.action === "bot-rate" || body.action === "bot-script") {
|
|
106
|
-
const jp = (
|
|
115
|
+
const jp = jsonPour(res);
|
|
107
116
|
if (!docbot) return jp(404, { ok: false, error: "disabled" });
|
|
108
117
|
try {
|
|
109
118
|
const ip = adresseAppelant(req) || "anon";
|
|
@@ -216,7 +225,7 @@ async function traiter(req, res, body, _slug) {
|
|
|
216
225
|
const r = await docbot.botSay(String(body.sessionId || ""), share, text, pages, mobile, blang);
|
|
217
226
|
if (r.error) return jp(400, { ok: false, error: r.error });
|
|
218
227
|
return jp(200, { ok: true, ...r });
|
|
219
|
-
} catch { return jp(500, { ok: false }); }
|
|
228
|
+
} catch (e) { try { PLAYER.errors.capture(e, { route: String(body.action || "(sans action)") }); } catch { /* jamais bloquant */ } return jp(500, { ok: false }); }
|
|
220
229
|
}
|
|
221
230
|
// Assistance (heartbeat) : PUBLIC (l'audience est anonyme). Journalise qui suit / combien de temps / pages vues.
|
|
222
231
|
// Rate-limit généreux par IP (heartbeat ≈ 145/h/participant) : bloque le spam d'assistants factices sans
|