discovery-media-player 0.1.22 → 0.1.24
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/storage.js +23 -14
- package/dist/bridge.d.ts +12 -3
- package/dist/bridge.js +42 -3
- package/package.json +1 -1
- package/server/browser.generated.js +1 -1
- package/server/handler.js +7 -1
- package/server/shares.js +10 -1
package/context/storage.js
CHANGED
|
@@ -142,10 +142,24 @@ const TYPES = {
|
|
|
142
142
|
* des premières pages avant la fin du téléchargement.
|
|
143
143
|
*/
|
|
144
144
|
async function readLocal(cible, range) {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
145
|
+
// ⚠️ ON OUVRE D'ABORD, ON INTERROGE ENSUITE. `stat(chemin)` puis `open(chemin)` sont deux
|
|
146
|
+
// résolutions du même NOM à deux instants : entre les deux, le fichier peut avoir été remplacé.
|
|
147
|
+
// On lisait alors les octets du NOUVEAU fichier avec la taille de l'ANCIEN — un `Content-Range`
|
|
148
|
+
// qui ne décrit pas ce qu'il transporte, donc un document faux sans qu'aucune erreur ne sorte.
|
|
149
|
+
// Un descripteur, lui, désigne un objet et non un nom : `fh.stat()` parle du même fichier que
|
|
150
|
+
// `fh.read()`, quoi qu'il advienne du chemin entre-temps.
|
|
151
|
+
let fh;
|
|
152
|
+
try { fh = await fsp.open(cible, "r"); } catch { return null; }
|
|
153
|
+
try {
|
|
154
|
+
const stat = await fh.stat();
|
|
155
|
+
if (!stat.isFile()) return null;
|
|
156
|
+
return await lireDepuis(fh, stat, cible, range);
|
|
157
|
+
} finally {
|
|
158
|
+
await fh.close();
|
|
159
|
+
}
|
|
160
|
+
}
|
|
148
161
|
|
|
162
|
+
async function lireDepuis(fh, stat, cible, range) {
|
|
149
163
|
const type = TYPES[path.extname(cible).toLowerCase()] || "application/octet-stream";
|
|
150
164
|
const total = stat.size;
|
|
151
165
|
const m = /^bytes=(\d*)-(\d*)$/.exec(String(range || ""));
|
|
@@ -159,17 +173,12 @@ async function readLocal(cible, range) {
|
|
|
159
173
|
statut = 206;
|
|
160
174
|
}
|
|
161
175
|
|
|
162
|
-
const
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
if (statut === 206) entetes["content-range"] = `bytes ${debut}-${fin}/${total}`;
|
|
169
|
-
return reponse(statut, entetes, corps);
|
|
170
|
-
} finally {
|
|
171
|
-
await fh.close();
|
|
172
|
-
}
|
|
176
|
+
const buf = Buffer.alloc(fin - debut + 1);
|
|
177
|
+
const { bytesRead } = await fh.read(buf, 0, buf.length, debut);
|
|
178
|
+
const corps = bytesRead === buf.length ? buf : buf.subarray(0, bytesRead);
|
|
179
|
+
const entetes = { "content-type": type };
|
|
180
|
+
if (statut === 206) entetes["content-range"] = `bytes ${debut}-${fin}/${total}`;
|
|
181
|
+
return reponse(statut, entetes, corps);
|
|
173
182
|
}
|
|
174
183
|
|
|
175
184
|
function reponse(status, entetes, corps) {
|
package/dist/bridge.d.ts
CHANGED
|
@@ -41,8 +41,17 @@ export declare function parseHostMessage(data: unknown): HostMessage | null;
|
|
|
41
41
|
export declare function sendToHost(msg: PlayerMessage, target?: Window | null): void;
|
|
42
42
|
/** Côté HÔTE : envoie un message au player. */
|
|
43
43
|
export declare function sendToPlayer(frame: HTMLIFrameElement | null | undefined, msg: HostMessage): void;
|
|
44
|
-
/**
|
|
45
|
-
|
|
46
|
-
|
|
44
|
+
/**
|
|
45
|
+
* Côté HÔTE : écoute les messages du player. Renvoie la fonction de désabonnement.
|
|
46
|
+
*
|
|
47
|
+
* `expected` — l'iframe du player, ou sa fenêtre. **Recommandé** : sans lui, un autre cadre peut
|
|
48
|
+
* se faire passer pour le player.
|
|
49
|
+
*/
|
|
50
|
+
export declare function onPlayerMessage(cb: (msg: PlayerMessage) => void, win?: Window, expected?: HTMLIFrameElement | Window | null): () => void;
|
|
51
|
+
/**
|
|
52
|
+
* Côté PLAYER : écoute les messages de l'hôte. Renvoie la fonction de désabonnement.
|
|
53
|
+
*
|
|
54
|
+
* N'accepte que `window.parent` — le seul émetteur qui ait un sens dans un cadre.
|
|
55
|
+
*/
|
|
47
56
|
export declare function onHostMessage(cb: (msg: HostMessage) => void, win?: Window): () => void;
|
|
48
57
|
export {};
|
package/dist/bridge.js
CHANGED
|
@@ -88,12 +88,42 @@ export function sendToPlayer(frame, msg) {
|
|
|
88
88
|
}
|
|
89
89
|
catch { /* iframe déjà détruite */ }
|
|
90
90
|
}
|
|
91
|
-
/**
|
|
92
|
-
|
|
91
|
+
/**
|
|
92
|
+
* ⚠️ POURQUOI ON NE VÉRIFIE PAS L'ORIGINE, ET CE QU'ON VÉRIFIE À LA PLACE.
|
|
93
|
+
*
|
|
94
|
+
* Un contrôle d'origine est impossible ici : le player est encadré par des hôtes sur des domaines
|
|
95
|
+
* quelconques, déclarés à l'exploitation (`DOC_FRAME_ANCESTORS`), et il ne connaît pas celui de son
|
|
96
|
+
* hôte au moment où il écoute. C'est pour cette raison que le contrôle avait été écarté.
|
|
97
|
+
*
|
|
98
|
+
* Mais comparer la FENÊTRE SOURCE ne demande aucune origine. `e.source` est une référence : soit
|
|
99
|
+
* elle est celle qu'on attend, soit elle vient d'ailleurs. Sans ce test, n'importe quel onglet ou
|
|
100
|
+
* cadre détenant une référence à la fenêtre pouvait envoyer `close`, `share` ou `handover-done` —
|
|
101
|
+
* et la page les traitait comme venant de son hôte.
|
|
102
|
+
*
|
|
103
|
+
* Côté player, la fermeture est PAR DÉFAUT : le seul émetteur légitime est `window.parent`, aucun
|
|
104
|
+
* hôte n'a de code à changer. Côté hôte, le paramètre est optionnel — imposer une fermeture par
|
|
105
|
+
* défaut ferait taire les messages chez tous ceux qui ne l'auraient pas encore passé, et un
|
|
106
|
+
* message qui n'arrive plus est la pire façon d'annoncer un durcissement.
|
|
107
|
+
*/
|
|
108
|
+
function memeFenetre(attendue, source) {
|
|
109
|
+
if (!attendue)
|
|
110
|
+
return true; // rien d'attendu : on n'exclut rien
|
|
111
|
+
const w = attendue.contentWindow ?? attendue;
|
|
112
|
+
return source === w;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Côté HÔTE : écoute les messages du player. Renvoie la fonction de désabonnement.
|
|
116
|
+
*
|
|
117
|
+
* `expected` — l'iframe du player, ou sa fenêtre. **Recommandé** : sans lui, un autre cadre peut
|
|
118
|
+
* se faire passer pour le player.
|
|
119
|
+
*/
|
|
120
|
+
export function onPlayerMessage(cb, win, expected) {
|
|
93
121
|
const target = win || (typeof window !== "undefined" ? window : null);
|
|
94
122
|
if (!target)
|
|
95
123
|
return () => { };
|
|
96
124
|
const handler = (e) => {
|
|
125
|
+
if (!memeFenetre(expected, e.source))
|
|
126
|
+
return;
|
|
97
127
|
const msg = parsePlayerMessage(e.data);
|
|
98
128
|
if (msg)
|
|
99
129
|
cb(msg);
|
|
@@ -101,12 +131,21 @@ export function onPlayerMessage(cb, win) {
|
|
|
101
131
|
target.addEventListener("message", handler);
|
|
102
132
|
return () => target.removeEventListener("message", handler);
|
|
103
133
|
}
|
|
104
|
-
/**
|
|
134
|
+
/**
|
|
135
|
+
* Côté PLAYER : écoute les messages de l'hôte. Renvoie la fonction de désabonnement.
|
|
136
|
+
*
|
|
137
|
+
* N'accepte que `window.parent` — le seul émetteur qui ait un sens dans un cadre.
|
|
138
|
+
*/
|
|
105
139
|
export function onHostMessage(cb, win) {
|
|
106
140
|
const target = win || (typeof window !== "undefined" ? window : null);
|
|
107
141
|
if (!target)
|
|
108
142
|
return () => { };
|
|
143
|
+
const parent = target.parent ?? null;
|
|
109
144
|
const handler = (e) => {
|
|
145
|
+
// ⚠️ `parent === target` quand la page n'est PAS encadrée : il n'y a alors pas d'hôte, donc
|
|
146
|
+
// rien à accepter — et surtout pas ses propres messages renvoyés par un script de la page.
|
|
147
|
+
if (!parent || parent === target || e.source !== parent)
|
|
148
|
+
return;
|
|
110
149
|
const msg = parseHostMessage(e.data);
|
|
111
150
|
if (msg)
|
|
112
151
|
cb(msg);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "discovery-media-player",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.24",
|
|
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 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" };
|
|
7
|
+
module.exports = { PLAYER_BROWSER_JS: "\"use strict\";var Player=(()=>{var z=Object.defineProperty;var ve=Object.getOwnPropertyDescriptor;var Me=Object.getOwnPropertyNames;var xe=Object.prototype.hasOwnProperty;var p=(e,n)=>{for(var t in n)z(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&&z(e,o,{get:()=>n[o],enumerable:!(r=ve(n,o))||r.enumerable});return e};var ke=e=>we(z({},\"__esModule\",{value:!0}),e);var gn={};p(gn,{bridge:()=>I,chat:()=>C,live:()=>A,presentation:()=>L,presentationState:()=>$,tracking:()=>S,viewer:()=>R});var I={};p(I,{onHostMessage:()=>Re,onPlayerMessage:()=>Le,parseHostMessage:()=>de,parsePlayerMessage:()=>ce,sendToHost:()=>Se,sendToPlayer:()=>Ae,toWire:()=>j});var V=\"3dd-doc-\",Te=/^[A-Za-z0-9_-]{1,64}$/,_e=[\"close\",\"share\",\"embed-ready\",\"present-left\",\"present-denied\"],Ee=[\"embed-denied\"],Pe=[\"present-invite\",\"present-handover\",\"present-switch\"],Ie=[\"handover-done\"];function j(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(Ee.includes(o)){let a=e.reason;return{type:o,reason:/^[a-z-]{1,40}$/.test(String(a||\"\"))?String(a):\"unknown\"}}if(!t.includes(o))return null;let i=e.slug;return typeof i!=\"string\"||!Te.test(i)?null:{type:o,slug:i}}function ce(e){return ue(e,_e,Pe)}function de(e){return ue(e,Ie,[])}function Se(e,n){try{let t=n||(typeof window!=\"undefined\"?window.parent:null);t&&t.postMessage(j(e),\"*\")}catch(t){}}function Ae(e,n){var t;try{(t=e==null?void 0:e.contentWindow)==null||t.postMessage(j(n),\"*\")}catch(r){}}function Ce(e,n){var r;if(!e)return!0;let t=(r=e.contentWindow)!=null?r:e;return n===t}function Le(e,n,t){let r=n||(typeof window!=\"undefined\"?window:null);if(!r)return()=>{};let o=i=>{if(!Ce(t,i.source))return;let a=ce(i.data);a&&e(a)};return r.addEventListener(\"message\",o),()=>r.removeEventListener(\"message\",o)}function Re(e,n){var i;let t=n||(typeof window!=\"undefined\"?window:null);if(!t)return()=>{};let r=(i=t.parent)!=null?i:null,o=a=>{if(!r||r===t||a.source!==r)return;let d=de(a.data);d&&e(d)};return t.addEventListener(\"message\",o),()=>t.removeEventListener(\"message\",o)}var S={};p(S,{createTracker:()=>De});var $e=[\"mousemove\",\"mousedown\",\"keydown\",\"wheel\",\"touchstart\",\"pointerdown\"];function He(){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 We(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 De(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,a=(ae=e.sessionEveryMs)!=null?ae:12e3,d=e.slug||\"\",f=e.internal||null,H=e.sessionId||He(),ye=e.send||We(o,n),g={},v=0,W=0,M=0,ee=0,c=null,_=r(),x=!1,D=!1,E=[],he=()=>!!d||!!f,ne=s=>{if(he())try{ye(s)}catch(l){}},te=()=>!!t&&t.visibilityState===\"visible\"&&(typeof t.hasFocus==\"function\"?t.hasFocus():!0)&&!x,re=s=>{let l=r()-s,b=Math.max(0,_-s)+i;return Math.max(0,Math.min(l,b))},N=()=>{c!=null&&v>0&&(g[v]=(g[v]||0)+re(c)/1e3),c=te()?r():null},O=()=>{N(),c=null},U=()=>{c==null&&te()&&(c=r())},F=()=>{let s=0;for(let l in g)s+=g[l];return c!=null&&(s+=re(c)/1e3),Math.round(s)},oe=()=>{let s={};for(let l in g)s[l]=Math.round(g[l]);return s},w=()=>{_=r(),x&&(x=!1,U())},se=(s,l)=>{f||ne({slug:d,event:s,page:l||W,maxPage:M,seconds:F(),sessionId:H})},P=()=>{N();let s={event:\"session\",sessionId:H,numPages:ee,maxPage:M,totalSeconds:F(),pagesTime:oe()};f?(s.internal=!0,s.docId=f.docId,s.email=f.email,s.name=f.name):s.slug=d,ne(s)},m=(s,l,b,le)=>{!s||typeof s.addEventListener!=\"function\"||(s.addEventListener(l,b,le),E.push(()=>{s.removeEventListener(l,b,le)}))};return{sessionId:H,totalSeconds:F,maxPage:()=>M,pageTimes:oe,start(){if(D)return;D=!0,se(\"open\",1),v=1,c=r(),_=r(),m(t,\"visibilitychange\",()=>{t.visibilityState===\"hidden\"?(O(),P()):(w(),U())}),m(n,\"blur\",O),m(n,\"focus\",()=>{w(),U()});for(let b of $e)m(n,b,w,{passive:!0});m(e.scrollElement,\"scroll\",w,{passive:!0}),m(n,\"beforeunload\",P);let s=n.setInterval(()=>{!x&&r()-_>i&&(x=!0,O())},5e3),l=n.setInterval(P,a);E.push(()=>{n.clearInterval(s),n.clearInterval(l)})},setPage(s){!s||s===W||(N(),v=s,W=s,s>M&&(M=s,se(\"page\",s)))},setPageCount(s){ee=s||0},noteActivity:w,flush:P,stop(){for(;E.length;){let s=E.pop();try{s==null||s()}catch(l){}}D=!1}}}var A={};p(A,{STORAGE_KEYS:()=>k,attendeeKey:()=>Fe,authorToken:()=>Ve,avatarHtml:()=>K,canModerate:()=>Z,escapeHtml:()=>u,flattenPresence:()=>Ne,formatMessageBody:()=>G,initials:()=>pe,isMentioned:()=>je,isMine:()=>y,reactorId:()=>B,shouldNotify:()=>Ke,unreadLabel:()=>Ge});var k={attendeeKey:\"3dd-present-attkey\",authorToken:\"3dd-present-authtoken\",chatMuted:\"3dd-present-mute\"};function u(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 K(e,n){return e?`<img src=\"${u(e)}\" alt=\"\">`:u(pe(n))}function G(e){let n=u(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 Ue(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(){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 Ve(e,n=ze){let t=Oe(e,k.authorToken);if(t)return t;let r=n();return Ue(e,k.authorToken,r),r}function B(e){return String(e&&(e.email||e.name)||\"\").toLowerCase()}function y(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 je(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}`)&&!y(e,n):!1}function Ke(e){let{msg:n,me:t,historyLoaded:r,chatHidden:o}=e;return!r||!n||n.deleted||y(n,t)?!1:o}function Ge(e){return!e||e<=0?\"\":e>9?\"9+\":String(e)}var C={};p(C,{messageClassName:()=>Ze,renderActions:()=>me,renderAttachment:()=>ge,renderMessage:()=>Be,renderReactions:()=>fe});function fe(e,n){let t=e&&e.reactions||{},r=B(n),o=\"\";for(let i in t){let a=t[i]||[];if(!a.length)continue;let d=a.indexOf(r)>=0;o+=`<button class=\"re-chip${d?\" on\":\"\"}\" data-e=\"${u(i)}\">${u(i)} ${a.length}</button>`}return o}function ge(e){if(!e||!e.url)return\"\";let n=u(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=u(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=y(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 Be(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>${u(e.reply_name||\"\")}</b> ${u(e.reply_text||\"\")}</div>`:\"\",r=e.edited?\" <span class=cm-ed>(modifi\\xE9)</span>\":\"\",o=e.body?`<div class=txt>${G(e.body)}${r}</div>`:\"\",i=e.is_presenter?\" <span class=tag>pr\\xE9sentateur</span>\":\"\";return`<span class=a>${K(e.author_avatar,e.author_name)}</span><span class=b><div class=who><b>${u(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 Ze(e,n,t){return\"cm\"+(y(e,n)?\" me\":\"\")+(e.deleted?\" isdel\":\"\")+(t?\" mentioned\":\"\")}var L={};p(L,{DEFAULT_CENTER:()=>X,DEFAULT_ZOOM:()=>J,MAP_TYPES:()=>T,cycleMapType:()=>Ye,initialMapContent:()=>Xe,mapTypeLabel:()=>qe,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 Ye(e){let n=T.indexOf(e);return T[(n+1)%T.length]}function qe(e){return e===\"roadmap\"?\"\\u{1F6F0} Satellite\":e===\"satellite\"?\"\\u{1F5FA} Hybride\":\"\\u{1F5FA} Plan\"}function Xe(){return{kind:\"map\",center:[...X],zoom:J,marker:null,mapType:null,label:null}}var R={};p(R,{CURRENT_PAGE_MARGIN:()=>rn,DEFAULT_ASPECT:()=>en,MAX_ZOOM:()=>Qe,MIN_PAGE_WIDTH:()=>nn,MIN_ZOOM:()=>Je,PRERENDER_MARGIN:()=>tn,arrowState:()=>an,averageColor:()=>cn,clampPage:()=>be,clampZoom:()=>on,fitWidth:()=>un,isImageDocument:()=>dn,progressPercent:()=>sn});var Je=.5,Qe=3,en=1.35,nn=280,tn=\"500px 0px\",rn=\"-48% 0px -48% 0px\";function on(e){let n=Math.round((Number(e)||0)*10)/10;return Math.max(.5,Math.min(3,n))}function be(e,n){return Math.max(1,Math.min(n||1,Number(e)||1))}function sn(e,n){return n?Math.round(be(e,n)/n*100):0}function an(e,n){return{prevDisabled:e<=1,nextDisabled:!!n&&e>=n}}var ln=28;function un(e){var a;let n=(a=e.zoom)!=null?a:1,t=Math.max(280,e.containerWidth||900)*n;if(!e.onePage)return t;let r=e.aspect||1.35,i=((e.containerHeight||600)-ln-(e.overlap||0)-(e.reserve||0))/r;return i>120?Math.min(t,i):t}function cn(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 dn(e,n){return/[.](png|jpe?g|webp|gif|avif)($|[?])/i.test(String(e||n||\"\"))}var $={};p($,{presentationTransition:()=>pn,switchDocUrl:()=>fn});function pn(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 fn(e,n){let t=encodeURIComponent(n||String(Date.now()));return`${e}${e.includes(\"?\")?\"&\":\"?\"}v=${t}`}return ke(gn);})();", SOURCE_HASH: "951763e203aba2b4" };
|
package/server/handler.js
CHANGED
|
@@ -2755,7 +2755,13 @@ async function handler(req, res) {
|
|
|
2755
2755
|
if (body.action === "reshare") {
|
|
2756
2756
|
const mail = String(body.email || "").trim().toLowerCase();
|
|
2757
2757
|
const j = (status, obj) => { res.statusCode = status; res.setHeader("Content-Type", "application/json"); res.end(JSON.stringify(obj)); };
|
|
2758
|
-
|
|
2758
|
+
// ⚠️ LA LONGUEUR AVANT LE MOTIF — et ici ce n'est pas de la prudence, c'est mesuré :
|
|
2759
|
+
// `.+@.+\..+` reprend à chaque position de départ, coût 49 ms sur 10 000 caractères,
|
|
2760
|
+
// 3 900 ms sur 100 000. Une seule requête, sur une route ouverte au lecteur, bloquait la
|
|
2761
|
+
// boucle d'événements quatre secondes — l'instance entière, pas seulement l'appelant.
|
|
2762
|
+
// Le débit (8/h par IP) ne protégeait rien : il est vérifié APRÈS, deux lignes plus bas.
|
|
2763
|
+
// 254 est le maximum d'une adresse (RFC 5321) : au-delà ce n'est pas « long », c'est faux.
|
|
2764
|
+
if (mail.length > 254 || !/.+@.+\..+/.test(mail)) return j(400, { ok: false, error: "email" });
|
|
2759
2765
|
const ip = adresseAppelant(req) || "anon";
|
|
2760
2766
|
const allowed = await PLAYER.limits.allow(`reshare:${ip}`, 8, 3600);
|
|
2761
2767
|
if (!allowed) return j(429, { ok: false, error: "rate", message: "Trop de partages, réessayez plus tard." });
|
package/server/shares.js
CHANGED
|
@@ -58,6 +58,10 @@ async function createReshare(parentSlug, { email, name }) {
|
|
|
58
58
|
// rapport qui a mené ici.
|
|
59
59
|
// • `allow_download` (défaut `true`) — le bouton Télécharger revenait sur un document où il
|
|
60
60
|
// avait été refusé.
|
|
61
|
+
// ⚠️ À ne pas prendre pour une protection : c'est une PRÉFÉRENCE D'AFFICHAGE. Le lecteur qui
|
|
62
|
+
// voit le document en a déjà les octets ; masquer le bouton retire une commodité, pas un
|
|
63
|
+
// accès. Un document qu'on ne veut pas voir sortir ne doit pas être partagé, ou doit l'être
|
|
64
|
+
// derrière `require_auth` — celui-là, lui, décide de qui obtient les octets.
|
|
61
65
|
// • `brand_key` — la marque se perdait à l'endroit exact où le document commence à circuler :
|
|
62
66
|
// le lecteur d'un document VALONEUF transmettait un lien qui s'ouvre sous une autre marque.
|
|
63
67
|
//
|
|
@@ -202,7 +206,12 @@ async function overview() {
|
|
|
202
206
|
|
|
203
207
|
// Parse minimal d'un User-Agent → appareil / OS / navigateur (sans dépendance).
|
|
204
208
|
function parseUa(ua) {
|
|
205
|
-
|
|
209
|
+
// ⚠️ Bornée AVANT analyse. L'analyse statique signale `Android.*Mobile` comme pouvant revenir en
|
|
210
|
+
// arrière ; mesuré avant de corriger, V8 traite ce motif en temps linéaire même sur 200 000
|
|
211
|
+
// caractères — ce n'était donc pas une lenteur réelle. On borne quand même : la colonne stockée
|
|
212
|
+
// était déjà tronquée à 300, seule l'analyse recevait la chaîne entière. Faire entrer une
|
|
213
|
+
// longueur non bornée dans une expression régulière est une habitude qui finit par coûter.
|
|
214
|
+
const s = String(ua || "").slice(0, 300);
|
|
206
215
|
let device = /Mobile|iPhone|Android.*Mobile/.test(s) ? "Mobile" : /iPad|Tablet/.test(s) ? "Tablette" : "Ordinateur";
|
|
207
216
|
let os = /Windows/.test(s) ? "Windows" : /iPhone|iPad|iOS/.test(s) ? "iOS" : /Mac OS X|Macintosh/.test(s) ? "macOS" : /Android/.test(s) ? "Android" : /Linux/.test(s) ? "Linux" : "—";
|
|
208
217
|
let browser = /Edg\//.test(s) ? "Edge" : /OPR\/|Opera/.test(s) ? "Opera" : /Chrome\//.test(s) ? "Chrome" : /Firefox\//.test(s) ? "Firefox" : /Safari\//.test(s) ? "Safari" : "—";
|