discovery-media-player 0.1.111 → 0.1.112
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/package.json +1 -1
- package/server/cache.js +61 -19
- package/server/handler.js +9 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "discovery-media-player",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.112",
|
|
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",
|
package/server/cache.js
CHANGED
|
@@ -37,10 +37,38 @@
|
|
|
37
37
|
*/
|
|
38
38
|
function creerCache(options) {
|
|
39
39
|
const ttl = Math.max(0, Number(options && options.ttlMs) || 0);
|
|
40
|
-
|
|
40
|
+
// ⚠️ DEUX PLAFONDS, ET LE SECOND EST CELUI QUI COMPTE. Un plafond en NOMBRE d'entrées ne borne rien
|
|
41
|
+
// quand la taille d'une entrée est choisie par l'appelant : 500 réponses de chat à 600 Ko font
|
|
42
|
+
// ~286 Mo, et la clé porte un curseur (`chatAfter`) que le visiteur fait varier à volonté — chaque
|
|
43
|
+
// valeur crée une entrée. On borne donc aussi le POIDS, et le poids est MESURÉ, pas estimé : les
|
|
44
|
+
// valeurs mises en cache ici sont des chaînes JSON. (Relevé par un audit externe.)
|
|
45
|
+
const max = Math.max(1, Number(options && options.max) || 100);
|
|
46
|
+
const maxOctets = Math.max(1, Number(options && options.maxOctets) || 8 * 1024 * 1024);
|
|
41
47
|
const now = (options && options.now) || (() => Date.now());
|
|
42
|
-
/** @type {Map<string, { echeance: number, promesse: Promise<unknown
|
|
48
|
+
/** @type {Map<string, { echeance: number, promesse: Promise<unknown>, poids: number, enVol: boolean }>} */
|
|
43
49
|
const entrees = new Map();
|
|
50
|
+
let poidsTotal = 0;
|
|
51
|
+
|
|
52
|
+
const oublier = (k) => {
|
|
53
|
+
const e = entrees.get(k);
|
|
54
|
+
if (!e) return;
|
|
55
|
+
poidsTotal -= e.poids;
|
|
56
|
+
entrees.delete(k);
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// ⚠️ ON N'ÉVINCE JAMAIS UNE PROMESSE EN VOL. L'éviction sert à borner la mémoire des RÉSULTATS ;
|
|
60
|
+
// retirer une demande en cours ne libère rien (elle est déjà lancée) et casse le regroupement : les
|
|
61
|
+
// appelants suivants relanceraient la même production, ce que ce cache existe pour éviter.
|
|
62
|
+
const borner = () => {
|
|
63
|
+
for (const [k, e] of entrees) {
|
|
64
|
+
if (entrees.size <= max && poidsTotal <= maxOctets) break;
|
|
65
|
+
if (!e.enVol) oublier(k);
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const purger = (t) => {
|
|
70
|
+
for (const [k, e] of entrees) if (!e.enVol && e.echeance <= t) oublier(k);
|
|
71
|
+
};
|
|
44
72
|
|
|
45
73
|
return {
|
|
46
74
|
/**
|
|
@@ -50,32 +78,46 @@ function creerCache(options) {
|
|
|
50
78
|
async lire(cle, produire) {
|
|
51
79
|
const k = String(cle);
|
|
52
80
|
const t = now();
|
|
81
|
+
// ⚠️ PURGE À CHAQUE ACCÈS. Sans elle, une entrée périmée restait en mémoire jusqu'à ce qu'une
|
|
82
|
+
// AUTRE la pousse dehors : le cache gardait indéfiniment ce qu'il ne servait plus.
|
|
83
|
+
purger(t);
|
|
53
84
|
const vue = entrees.get(k);
|
|
54
|
-
|
|
85
|
+
// Une entrée en vol se partage toujours : c'est tout l'objet du regroupement.
|
|
86
|
+
if (vue && (vue.enVol || vue.echeance > t)) return vue.promesse;
|
|
55
87
|
|
|
56
88
|
const promesse = Promise.resolve().then(produire);
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
//
|
|
60
|
-
//
|
|
61
|
-
|
|
62
|
-
const courante = entrees.get(k);
|
|
63
|
-
if (courante && courante.promesse === promesse) entrees.delete(k);
|
|
64
|
-
});
|
|
89
|
+
// ⚠️ L'ÉCHÉANCE PART DE LA RÉSOLUTION, PAS DE LA DEMANDE. Posée à la demande, elle expirait
|
|
90
|
+
// AVANT que la production ne réponde dès que celle-ci dépassait le TTL — et le regroupement ne
|
|
91
|
+
// servait alors plus à rien pour exactement les producteurs lents, les seuls qu'il valait la
|
|
92
|
+
// 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 });
|
|
65
94
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
95
|
+
promesse.then(
|
|
96
|
+
(valeur) => {
|
|
97
|
+
const courante = entrees.get(k);
|
|
98
|
+
if (!courante || courante.promesse !== promesse) return;
|
|
99
|
+
courante.enVol = false;
|
|
100
|
+
courante.echeance = now() + ttl;
|
|
101
|
+
// Le poids réel : ces valeurs sont des chaînes JSON. Une valeur d'une autre forme compte
|
|
102
|
+
// pour un poids nominal — mieux vaut sous-estimer que prétendre mesurer ce qu'on ignore.
|
|
103
|
+
courante.poids = typeof valeur === "string" ? valeur.length : 1;
|
|
104
|
+
poidsTotal += courante.poids;
|
|
105
|
+
borner();
|
|
106
|
+
},
|
|
107
|
+
() => {
|
|
108
|
+
// ⚠️ On oublie une promesse ROMPUE, jamais une promesse tenue : sinon un hoquet de base
|
|
109
|
+
// resterait servi pendant toute la fenêtre, et le rétablissement attendrait pour rien.
|
|
110
|
+
const courante = entrees.get(k);
|
|
111
|
+
if (courante && courante.promesse === promesse) oublier(k);
|
|
112
|
+
},
|
|
113
|
+
);
|
|
74
114
|
return promesse;
|
|
75
115
|
},
|
|
76
116
|
|
|
77
117
|
/** Pour les tests et l'exploitation : ce que la table contient réellement. */
|
|
78
118
|
taille: () => entrees.size,
|
|
119
|
+
/** Poids cumulé des résultats retenus, en caractères. */
|
|
120
|
+
poids: () => poidsTotal,
|
|
79
121
|
};
|
|
80
122
|
}
|
|
81
123
|
|
package/server/handler.js
CHANGED
|
@@ -728,7 +728,15 @@ async function handler(req, res) {
|
|
|
728
728
|
// requête base). Il entre dans la CLÉ DE CACHE : deux clients au même curseur — le cas normal,
|
|
729
729
|
// toute une salle à jour du même message — partagent la même lecture ; seuls ceux qui ont pris
|
|
730
730
|
// du retard divergent. Absent → relecture complète (client ancien, ou hôte non migré).
|
|
731
|
-
|
|
731
|
+
// ⚠️ ENTIER DÉCIMAL SÛR, ET RIEN D'AUTRE. Cette valeur entre dans une CLÉ DE CACHE : tout ce qui
|
|
732
|
+
// élargit l'espace des valeurs élargit l'espace des clés, et le cache est une ressource
|
|
733
|
+
// partagée. `+"1e20"` est fini et passait, produisant une clé qu'aucun message n'atteindra
|
|
734
|
+
// jamais ; `Number.isSafeInteger` ferme la plage, et le motif décimal ferme les écritures
|
|
735
|
+
// équivalentes (« 0x10 », « 1e3 », espaces). La normalisation par `String(...)` reste ce qui
|
|
736
|
+
// fait que « 05 » et « 5 » ne créent pas deux entrées pour la même question.
|
|
737
|
+
const brutAfter = String(q.chatAfter == null ? "" : q.chatAfter).trim();
|
|
738
|
+
const nAfter = /^[0-9]{1,15}$/.test(brutAfter) ? Number(brutAfter) : NaN;
|
|
739
|
+
const chatAfter = (Number.isSafeInteger(nAfter) && nAfter > 0) ? String(nAfter) : null;
|
|
732
740
|
const LECTURES_PARTAGEES = [
|
|
733
741
|
{ param: "state", produire: async (p) => ({ ok: true, state: {
|
|
734
742
|
active: p.active !== false,
|