discovery-media-player 0.1.115 → 0.1.117
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/context/standalone.js +10 -0
- package/package.json +1 -1
- package/server/browser.generated.js +1 -1
- package/server/cache.js +60 -8
- package/server/handler.js +27 -6
- package/server/presentations.js +26 -9
- package/server/shared.generated.js +10 -0
package/context/standalone.js
CHANGED
|
@@ -53,8 +53,18 @@ function creerDb(env) {
|
|
|
53
53
|
);
|
|
54
54
|
}
|
|
55
55
|
const methode = String(options.method || "GET").toUpperCase();
|
|
56
|
+
// ⚠️ UN ABANDON RÉEL, PAS UNE COURSE DE PROMESSES. Sans signal, une base qui ne répond pas
|
|
57
|
+
// immobilise la requête, sa socket ET la place d'admission du cache jusqu'à ce que la plateforme
|
|
58
|
+
// tue la fonction : le plafond d'admission se remplit alors et n'est jamais rendu, ce qui
|
|
59
|
+
// transforme un ralentissement en refus général. Une course `Promise.race` ne suffirait pas —
|
|
60
|
+
// elle rendrait la main sans ANNULER le fetch, donc sans libérer quoi que ce soit. L'hôte peut
|
|
61
|
+
// fournir son propre `signal` (opérations longues : purges, transferts). (Audit externe.)
|
|
62
|
+
const delai = Number(options.timeoutMs) > 0 ? Number(options.timeoutMs) : 15000;
|
|
63
|
+
const signal = options.signal || (typeof AbortSignal !== "undefined" && AbortSignal.timeout
|
|
64
|
+
? AbortSignal.timeout(delai) : undefined);
|
|
56
65
|
const r = await fetch(`${url}/rest/v1/${chemin}`, {
|
|
57
66
|
method: methode,
|
|
67
|
+
...(signal ? { signal } : {}),
|
|
58
68
|
headers: {
|
|
59
69
|
apikey: cle,
|
|
60
70
|
Authorization: `Bearer ${cle}`,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "discovery-media-player",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.117",
|
|
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 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: "9b51a94c89b8d54f" };
|
|
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" };
|
package/server/cache.js
CHANGED
|
@@ -24,6 +24,14 @@
|
|
|
24
24
|
// 3. UN ÉCHEC NE SE MÉMORISE PAS. Servir une erreur pendant toute la fenêtre transformerait un
|
|
25
25
|
// hoquet en panne visible, et retarderait le rétablissement. On oublie la promesse rompue.
|
|
26
26
|
//
|
|
27
|
+
// 4. ⚠️ LE PLAFOND DES RÉSULTATS NE BORNE PAS LES DEMANDES EN VOL — et c'est par là que ça cédait.
|
|
28
|
+
// Le piège 2 était traité pour les valeurs RETENUES, mais une entrée en vol n'est jamais évincée
|
|
29
|
+
// (piège 1 : l'évincer casserait le regroupement). Tant que la base ne répond pas, RIEN ne
|
|
30
|
+
// bornait donc l'admission : 10 000 clés distinctes pendantes donnaient 10 000 entrées ET 10 000
|
|
31
|
+
// producteurs — donc 10 000 requêtes, sockets et promesses ouvertes, avec `max: 100`. Mesuré, pas
|
|
32
|
+
// supposé. Il faut un plafond SÉPARÉ sur les demandes en vol, et un REFUS quand il est atteint :
|
|
33
|
+
// borner l'admission est la seule chose qui borne une base lente. (Relevé par un audit externe.)
|
|
34
|
+
//
|
|
27
35
|
// ⚠️ CE QUE CE CACHE NE FAIT PAS : il vit dans la mémoire du PROCESSUS. En serverless, plusieurs
|
|
28
36
|
// instances servent en parallèle et démarrent à froid — l'effondrement est donc « une lecture par
|
|
29
37
|
// fenêtre ET PAR INSTANCE », pas « une lecture ». C'est la même limite que celle d'un compteur de
|
|
@@ -35,6 +43,21 @@
|
|
|
35
43
|
*
|
|
36
44
|
* @param {{ ttlMs: number, max?: number, now?: () => number }} options
|
|
37
45
|
*/
|
|
46
|
+
/**
|
|
47
|
+
* ⚠️ UNE ERREUR TYPÉE, PAS UN `null` NI UN THROW ANONYME. La route doit pouvoir distinguer « la base
|
|
48
|
+
* a échoué » (500) de « nous refusons d'admettre une demande de plus » (503, réessayable) : ce sont
|
|
49
|
+
* deux situations opposées pour l'appelant, et les confondre lui ferait abandonner une requête qui
|
|
50
|
+
* n'attendait qu'une seconde.
|
|
51
|
+
*/
|
|
52
|
+
const CODE_SATURATION = "cache-sature";
|
|
53
|
+
function erreurSaturation(plafond) {
|
|
54
|
+
const e = new Error(`cache saturé : ${plafond} demandes déjà en vol`);
|
|
55
|
+
e.code = CODE_SATURATION;
|
|
56
|
+
e.statusCode = 503;
|
|
57
|
+
e.retryAfter = 1;
|
|
58
|
+
return e;
|
|
59
|
+
}
|
|
60
|
+
|
|
38
61
|
function creerCache(options) {
|
|
39
62
|
const ttl = Math.max(0, Number(options && options.ttlMs) || 0);
|
|
40
63
|
// ⚠️ DEUX PLAFONDS, ET LE SECOND EST CELUI QUI COMPTE. Un plafond en NOMBRE d'entrées ne borne rien
|
|
@@ -44,15 +67,25 @@ function creerCache(options) {
|
|
|
44
67
|
// valeurs mises en cache ici sont des chaînes JSON. (Relevé par un audit externe.)
|
|
45
68
|
const max = Math.max(1, Number(options && options.max) || 100);
|
|
46
69
|
const maxOctets = Math.max(1, Number(options && options.maxOctets) || 8 * 1024 * 1024);
|
|
70
|
+
// ⚠️ LE TROISIÈME PLAFOND, ET C'EST LUI QUI TIENT SOUS UNE BASE LENTE. Les deux autres ne bornent
|
|
71
|
+
// que ce qui est RÉSOLU ; celui-ci borne ce qui est ADMIS. Une valeur volontairement basse devant
|
|
72
|
+
// `max` : une entrée en vol coûte une requête, une socket et une promesse, bien plus cher qu'un
|
|
73
|
+
// résultat en mémoire.
|
|
74
|
+
const maxEnVol = Math.max(1, Number(options && options.maxEnVol) || 128);
|
|
47
75
|
const now = (options && options.now) || (() => Date.now());
|
|
48
|
-
/** @type {Map<string, { echeance: number, promesse: Promise<unknown>, poids: number, enVol: boolean }>} */
|
|
76
|
+
/** @type {Map<string, { echeance: number, promesse: Promise<unknown>, poids: number, enVol: boolean, rendrePlace?: () => void }>} */
|
|
49
77
|
const entrees = new Map();
|
|
50
78
|
let poidsTotal = 0;
|
|
79
|
+
let nEnVol = 0; // tenu à jour à chaque transition — compter la Map à la demande serait O(n)
|
|
51
80
|
|
|
52
81
|
const oublier = (k) => {
|
|
53
82
|
const e = entrees.get(k);
|
|
54
83
|
if (!e) return;
|
|
55
84
|
poidsTotal -= e.poids;
|
|
85
|
+
// Une entrée en vol qu'on retire (promesse rompue) libère sa place d'admission — sans quoi le
|
|
86
|
+
// plafond se remplirait définitivement au premier incident de base. Le rendu passe par le verrou
|
|
87
|
+
// de la promesse : c'est lui qui garantit qu'une place n'est rendue qu'une fois.
|
|
88
|
+
if (e.enVol && typeof e.rendrePlace === "function") e.rendrePlace();
|
|
56
89
|
entrees.delete(k);
|
|
57
90
|
};
|
|
58
91
|
|
|
@@ -82,25 +115,41 @@ function creerCache(options) {
|
|
|
82
115
|
// AUTRE la pousse dehors : le cache gardait indéfiniment ce qu'il ne servait plus.
|
|
83
116
|
purger(t);
|
|
84
117
|
const vue = entrees.get(k);
|
|
85
|
-
//
|
|
118
|
+
// ⚠️ UNE CLÉ DÉJÀ EN VOL SE PARTAGE TOUJOURS, MÊME AU PLAFOND. C'est l'ordre de ces deux tests
|
|
119
|
+
// qui décide : refuser d'abord ferait échouer des appelants que le regroupement pouvait servir
|
|
120
|
+
// gratuitement — la saturation punirait alors la rafale légitime, exactement ce que ce cache
|
|
121
|
+
// existe pour absorber. On ne refuse que ce qui coûterait une requête DE PLUS.
|
|
86
122
|
if (vue && (vue.enVol || vue.echeance > t)) return vue.promesse;
|
|
87
123
|
|
|
124
|
+
if (nEnVol >= maxEnVol) throw erreurSaturation(maxEnVol);
|
|
125
|
+
|
|
88
126
|
const promesse = Promise.resolve().then(produire);
|
|
127
|
+
// ⚠️ UN DÉCOMPTE IDEMPOTENT PAR PROMESSE. Deux chemins libèrent la place (résolution, et
|
|
128
|
+
// `oublier` sur rejet) : sans ce verrou, une place pouvait être rendue DEUX fois, `nEnVol`
|
|
129
|
+
// passait sous zéro et le plafond cessait silencieusement de refuser — un plafond qui ne peut
|
|
130
|
+
// plus être atteint ne se distingue pas d'un plafond absent.
|
|
131
|
+
let placeRendue = false;
|
|
132
|
+
const rendrePlace = () => { if (!placeRendue) { placeRendue = true; nEnVol -= 1; } };
|
|
133
|
+
nEnVol += 1;
|
|
89
134
|
// ⚠️ L'ÉCHÉANCE PART DE LA RÉSOLUTION, PAS DE LA DEMANDE. Posée à la demande, elle expirait
|
|
90
135
|
// AVANT que la production ne réponde dès que celle-ci dépassait le TTL — et le regroupement ne
|
|
91
136
|
// servait alors plus à rien pour exactement les producteurs lents, les seuls qu'il valait la
|
|
92
137
|
// peine de mutualiser. Tant qu'elle est en vol, l'entrée ne périme pas.
|
|
93
|
-
entrees.set(k, { echeance: 0, promesse, poids: 0, enVol: true });
|
|
138
|
+
entrees.set(k, { echeance: 0, promesse, poids: 0, enVol: true, rendrePlace });
|
|
94
139
|
|
|
95
140
|
promesse.then(
|
|
96
141
|
(valeur) => {
|
|
97
142
|
const courante = entrees.get(k);
|
|
143
|
+
rendrePlace();
|
|
98
144
|
if (!courante || courante.promesse !== promesse) return;
|
|
99
145
|
courante.enVol = false;
|
|
100
146
|
courante.echeance = now() + ttl;
|
|
101
|
-
//
|
|
102
|
-
//
|
|
103
|
-
|
|
147
|
+
// ⚠️ DES OCTETS, PAS DES CARACTÈRES. `.length` compte des unités UTF-16 : « 界 » y vaut 1
|
|
148
|
+
// et pèse 3 octets en mémoire comme sur le fil. Un contenu non latin sous-estimait donc le
|
|
149
|
+
// poids d'un facteur 3 — un plafond « 8 Mo » en laissait passer 24. La valeur d'une autre
|
|
150
|
+
// forme compte pour un poids nominal : mieux vaut sous-estimer que prétendre mesurer ce
|
|
151
|
+
// qu'on ignore. (Relevé par un audit externe.)
|
|
152
|
+
courante.poids = typeof valeur === "string" ? Buffer.byteLength(valeur, "utf8") : 1;
|
|
104
153
|
poidsTotal += courante.poids;
|
|
105
154
|
borner();
|
|
106
155
|
},
|
|
@@ -109,6 +158,7 @@ function creerCache(options) {
|
|
|
109
158
|
// resterait servi pendant toute la fenêtre, et le rétablissement attendrait pour rien.
|
|
110
159
|
const courante = entrees.get(k);
|
|
111
160
|
if (courante && courante.promesse === promesse) oublier(k);
|
|
161
|
+
rendrePlace(); // idempotent : `oublier` a pu le faire, ou l'entrée avoir été remplacée
|
|
112
162
|
},
|
|
113
163
|
);
|
|
114
164
|
return promesse;
|
|
@@ -116,9 +166,11 @@ function creerCache(options) {
|
|
|
116
166
|
|
|
117
167
|
/** Pour les tests et l'exploitation : ce que la table contient réellement. */
|
|
118
168
|
taille: () => entrees.size,
|
|
119
|
-
/** Poids cumulé des résultats retenus, en
|
|
169
|
+
/** Poids cumulé des résultats retenus, en OCTETS UTF-8. */
|
|
120
170
|
poids: () => poidsTotal,
|
|
171
|
+
/** Demandes actuellement en vol — ce que le plafond d'admission borne. */
|
|
172
|
+
enVol: () => nEnVol,
|
|
121
173
|
};
|
|
122
174
|
}
|
|
123
175
|
|
|
124
|
-
module.exports = { creerCache };
|
|
176
|
+
module.exports = { creerCache, CODE_SATURATION };
|
package/server/handler.js
CHANGED
|
@@ -7,8 +7,8 @@ const crypto = require("crypto");
|
|
|
7
7
|
const { Readable } = require("node:stream");
|
|
8
8
|
const { pipeline } = require("node:stream/promises");
|
|
9
9
|
const { getShareBySlug } = require("./shares");
|
|
10
|
-
const { PRESENT_QUOTA_PER_HOUR, PRESENT_CACHE_MS } = require("./shared.generated.js");
|
|
11
|
-
const { creerCache } = require("./cache.js");
|
|
10
|
+
const { PRESENT_QUOTA_PER_HOUR, PRESENT_CACHE_MS, estSlug } = require("./shared.generated.js");
|
|
11
|
+
const { creerCache, CODE_SATURATION } = require("./cache.js");
|
|
12
12
|
|
|
13
13
|
// ⚠️ UN SEUL CACHE POUR TOUT LE PROCESSUS, ET C'EST LE POINT. Le créer par requête reviendrait à
|
|
14
14
|
// n'en avoir aucun : chaque appelant repartirait d'une table vide, et l'effondrement qu'on
|
|
@@ -797,10 +797,31 @@ async function handler(req, res) {
|
|
|
797
797
|
for (const lecture of LECTURES_PARTAGEES) {
|
|
798
798
|
if (String(q[lecture.param] || "") !== "1") continue;
|
|
799
799
|
const slugLu = String(q.present);
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
800
|
+
// ⚠️ LE CONTRAT S'APPLIQUE AVANT LE CACHE ET AVANT LA BASE. Le slug vient de l'URL : non
|
|
801
|
+
// validé, il entrait tel quel dans une CLÉ DE CACHE et dans une requête base. Un lecteur
|
|
802
|
+
// faisait donc varier les clés à volonté — c'est ce qui rendait l'admission du cache
|
|
803
|
+
// actionnable de l'extérieur. Le motif vit dans `shared.ts`, en un seul exemplaire.
|
|
804
|
+
if (!estSlug(slugLu)) return sendRefusal(res, "ended", embed);
|
|
805
|
+
let corps;
|
|
806
|
+
try {
|
|
807
|
+
corps = await cacheLecture.lire(`${lecture.param}:${slugLu}${lecture.suffixeCle ? lecture.suffixeCle() : ""}`, async () => {
|
|
808
|
+
const p = await getPresentation(slugLu);
|
|
809
|
+
return p ? JSON.stringify(await lecture.produire(p, slugLu)) : null;
|
|
810
|
+
});
|
|
811
|
+
} catch (e) {
|
|
812
|
+
// ⚠️ « NOUS REFUSONS D'ADMETTRE UNE DEMANDE DE PLUS » N'EST PAS « LA BASE A ÉCHOUÉ ». Un
|
|
813
|
+
// 503 réessayable dit à l'appelant d'attendre une seconde ; un 500 lui dit d'abandonner.
|
|
814
|
+
// Les confondre ferait renoncer une requête que le prochain battement aurait servie.
|
|
815
|
+
if (e && e.code === CODE_SATURATION) {
|
|
816
|
+
res.statusCode = 503;
|
|
817
|
+
res.setHeader("Retry-After", String(e.retryAfter || 1));
|
|
818
|
+
res.setHeader("Cache-Control", "no-store");
|
|
819
|
+
res.setHeader("Content-Type", "application/json");
|
|
820
|
+
res.end(JSON.stringify({ ok: false, error: "busy" }));
|
|
821
|
+
return;
|
|
822
|
+
}
|
|
823
|
+
throw e;
|
|
824
|
+
}
|
|
804
825
|
if (!corps) return sendRefusal(res, "ended", embed);
|
|
805
826
|
res.statusCode = 200; res.setHeader("Content-Type", "application/json"); res.setHeader("Cache-Control", "no-store");
|
|
806
827
|
res.end(corps);
|
package/server/presentations.js
CHANGED
|
@@ -6,7 +6,7 @@ const crypto = require("crypto");
|
|
|
6
6
|
// ⚠️ Le contexte est REÇU, pas construit. Ce module ne doit pas savoir d'où il vient : c'est ce
|
|
7
7
|
// qui lui permettra de partir dans le dépôt du player sans emporter le studio avec lui.
|
|
8
8
|
let PLAYER = null;
|
|
9
|
-
function init(ctx) { PLAYER = ctx; _bumpSansDurcissementJusqua = 0; _avertRpcPresence = false;
|
|
9
|
+
function init(ctx) { PLAYER = ctx; _bumpSansDurcissementJusqua = 0; _avertRpcPresence = false; _dernierSuccesDurcissement = 0; _dernierEchecDurcissement = 0; }
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* L'état OBSERVÉ du durcissement des bootstraps — pas sa configuration.
|
|
@@ -23,7 +23,14 @@ function init(ctx) { PLAYER = ctx; _bumpSansDurcissementJusqua = 0; _avertRpcPre
|
|
|
23
23
|
*/
|
|
24
24
|
function etatDurcissementBootstrap() {
|
|
25
25
|
if (Date.now() < _bumpSansDurcissementJusqua) return "degrade";
|
|
26
|
-
|
|
26
|
+
// ⚠️ « actif » EXIGE UN SUCCÈS PLUS RÉCENT QUE LE DERNIER ÉCHEC — pas un simple drapeau. Deux
|
|
27
|
+
// défauts tenaient dans la version précédente (relevés par le second hôte) : (1) le drapeau était
|
|
28
|
+
// posé AVANT l'appel, donc il enregistrait une TENTATIVE et non un succès ; (2) l'expiration du
|
|
29
|
+
// mémo ne rétrogradait pas l'état, elle le PROMOUVAIT — un hôte sans 0018 rendait « degrade »
|
|
30
|
+
// 60 s, puis « actif », sur la foi d'une tentative dont la seule chose prouvée était l'échec.
|
|
31
|
+
// Comparer les deux instants traite les deux d'un coup : le repli d'une preuve périmée est
|
|
32
|
+
// l'IGNORANCE, jamais la confiance.
|
|
33
|
+
return _dernierSuccesDurcissement > _dernierEchecDurcissement ? "actif" : "inconnu";
|
|
27
34
|
}
|
|
28
35
|
|
|
29
36
|
|
|
@@ -829,20 +836,29 @@ function signatureAbsente(erreur) {
|
|
|
829
836
|
// silence. Un « oui » (la signature existe) n'a pas besoin d'expirer : une fonction ne disparaît pas.
|
|
830
837
|
let _bumpSansDurcissementJusqua = 0;
|
|
831
838
|
const MEMO_SANS_DURCISSEMENT_MS = 60 * 1000;
|
|
839
|
+
// ⚠️ DEUX INSTANTS, PAS UN DRAPEAU — parce que l'ORDRE est ce qui distingue « réparé » de « cassé ».
|
|
840
|
+
// Un booléen « on a essayé » ne peut pas dire si le dernier mot fut un succès ou un échec, et c'est
|
|
841
|
+
// exactement le mot qui décide. Un succès plus récent que le dernier échec ⇒ la signature existe
|
|
842
|
+
// (une fonction ne disparaît pas toute seule) ; un échec plus récent, mémo expiré ⇒ on ne sait plus.
|
|
843
|
+
let _dernierSuccesDurcissement = 0;
|
|
844
|
+
let _dernierEchecDurcissement = 0;
|
|
832
845
|
// ⚠️ A-T-ON SEULEMENT ESSAYÉ ? « Pas dégradé » n'est pas « vérifié » : un processus qui vient de
|
|
833
|
-
// démarrer n'a rien tenté, et rendre
|
|
834
|
-
// absence d'observation. Trois états, donc — la même règle que le verdict du schéma, où « rien
|
|
835
|
-
// manquant » se lisait « tout va bien » tant qu'on ne distinguait pas « rien demandé ».
|
|
836
|
-
let _bumpDurcissementTente = false;
|
|
846
|
+
// démarrer n'a rien tenté, et rendre « actif » reviendrait à annoncer une garde active sur la foi
|
|
847
|
+
// d'une absence d'observation. Trois états, donc — la même règle que le verdict du schéma, où « rien
|
|
848
|
+
// de manquant » se lisait « tout va bien » tant qu'on ne distinguait pas « rien demandé ».
|
|
837
849
|
async function appelerBump(corps, durcissementVoulu) {
|
|
838
850
|
const appel = (b) => PLAYER.db.request("rpc/player_attendance_bump", { method: "POST", body: b });
|
|
839
851
|
if (durcissementVoulu && Date.now() < _bumpSansDurcissementJusqua) {
|
|
840
852
|
const { p_only_if_unclaimed: _retire, ...sansDurcissement } = corps;
|
|
841
853
|
return appel(sansDurcissement);
|
|
842
854
|
}
|
|
843
|
-
if (durcissementVoulu) _bumpDurcissementTente = true;
|
|
844
855
|
try {
|
|
845
|
-
|
|
856
|
+
const r = await appel(corps);
|
|
857
|
+
// ⚠️ ON NE MARQUE LE SUCCÈS QU'ICI — l'appel est REVENU, donc la signature à 12 arguments existe.
|
|
858
|
+
// Un rendu `{ok:false, usurpe:true}` compte : seule 0018 sait répondre ça. Ce qui se mesure est
|
|
859
|
+
// le RETOUR, jamais l'intention de partir.
|
|
860
|
+
if (durcissementVoulu) _dernierSuccesDurcissement = Date.now();
|
|
861
|
+
return r;
|
|
846
862
|
} catch (erreur) {
|
|
847
863
|
if (!durcissementVoulu) throw erreur; // rien à retirer : l'échec est réel
|
|
848
864
|
// ⚠️ ON NE SE REPLIE QUE SUR LA PREUVE. Toute autre erreur remonte telle quelle : l'appelant
|
|
@@ -850,7 +866,8 @@ async function appelerBump(corps, durcissementVoulu) {
|
|
|
850
866
|
if (!signatureAbsente(erreur)) throw erreur;
|
|
851
867
|
// La signature à 12 arguments n'existe pas (0018 non appliquée) : on retire l'argument et on
|
|
852
868
|
// réessaie. Le durcissement n'est alors PAS appliqué — et ça se dit, une fois, plus bas.
|
|
853
|
-
|
|
869
|
+
_dernierEchecDurcissement = Date.now();
|
|
870
|
+
_bumpSansDurcissementJusqua = _dernierEchecDurcissement + MEMO_SANS_DURCISSEMENT_MS;
|
|
854
871
|
// ⚠️ CE QUI DISPARAÎT SE DIT. Le durcissement demandé n'est pas appliqué : sous
|
|
855
872
|
// PLAYER_PRESENCE_STRICT, la porte se fermerait sur les battements legacy tout en laissant un
|
|
856
873
|
// bootstrap auto-déclaré s'emparer d'une présence réclamée — c'est-à-dire une fermeture qui
|
|
@@ -44,9 +44,11 @@ __export(shared_exports, {
|
|
|
44
44
|
SESSION_INTERVAL_MS: () => SESSION_INTERVAL_MS,
|
|
45
45
|
SESSION_QUOTA_PER_HOUR: () => SESSION_QUOTA_PER_HOUR,
|
|
46
46
|
SESSION_WRITES_PER_HOUR: () => SESSION_WRITES_PER_HOUR,
|
|
47
|
+
SLUG_RE: () => SLUG_RE,
|
|
47
48
|
VIEW_EVENTS_PER_READER_HOUR: () => VIEW_EVENTS_PER_READER_HOUR,
|
|
48
49
|
VIEW_QUOTA_PER_HOUR: () => VIEW_QUOTA_PER_HOUR,
|
|
49
50
|
cycleMapType: () => cycleMapType,
|
|
51
|
+
estSlug: () => estSlug,
|
|
50
52
|
initialMapContent: () => initialMapContent,
|
|
51
53
|
mapTypeLabel: () => mapTypeLabel,
|
|
52
54
|
sanitizeContent: () => sanitizeContent
|
|
@@ -121,6 +123,12 @@ var PRESENT_READ_COALESCE_MS = 400;
|
|
|
121
123
|
var PRESENT_CACHE_MS = PRESENT_READ_COALESCE_MS;
|
|
122
124
|
var PRESENT_QUOTA_MARGIN = 4;
|
|
123
125
|
var PRESENT_QUOTA_PER_HOUR = (PRESENT_READS_PER_HOUR + PRESENT_READ_BURST) * READERS_PER_EGRESS * PRESENT_QUOTA_MARGIN;
|
|
126
|
+
|
|
127
|
+
// src/shared.ts
|
|
128
|
+
var SLUG_RE = /^[A-Za-z0-9_-]{1,64}$/;
|
|
129
|
+
function estSlug(v) {
|
|
130
|
+
return typeof v === "string" && SLUG_RE.test(v);
|
|
131
|
+
}
|
|
124
132
|
// Annotate the CommonJS export names for ESM import in node:
|
|
125
133
|
0 && (module.exports = {
|
|
126
134
|
DEFAULT_CENTER,
|
|
@@ -142,9 +150,11 @@ var PRESENT_QUOTA_PER_HOUR = (PRESENT_READS_PER_HOUR + PRESENT_READ_BURST) * REA
|
|
|
142
150
|
SESSION_INTERVAL_MS,
|
|
143
151
|
SESSION_QUOTA_PER_HOUR,
|
|
144
152
|
SESSION_WRITES_PER_HOUR,
|
|
153
|
+
SLUG_RE,
|
|
145
154
|
VIEW_EVENTS_PER_READER_HOUR,
|
|
146
155
|
VIEW_QUOTA_PER_HOUR,
|
|
147
156
|
cycleMapType,
|
|
157
|
+
estSlug,
|
|
148
158
|
initialMapContent,
|
|
149
159
|
mapTypeLabel,
|
|
150
160
|
sanitizeContent
|