discovery-media-player 0.1.36 → 0.1.37
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/handler.js +36 -2
- package/server/shares.js +23 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "discovery-media-player",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.37",
|
|
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/handler.js
CHANGED
|
@@ -3139,7 +3139,29 @@ async function handler(req, res) {
|
|
|
3139
3139
|
userName: jeton ? (jeton.name || body.name) : body.name,
|
|
3140
3140
|
numPages: body.numPages, maxPage: body.maxPage, totalSeconds: body.totalSeconds, pagesTime: body.pagesTime,
|
|
3141
3141
|
}, { ip: ip0, ua: ua0 });
|
|
3142
|
-
} catch {
|
|
3142
|
+
} catch (e) {
|
|
3143
|
+
// ⚠️ CE `catch` A COÛTÉ UNE JOURNÉE À UN HÔTE, ET IL ÉTAIT À TROIS LIGNES DU COMMENTAIRE
|
|
3144
|
+
// QUI DIT DE NE PAS FAIRE ÇA.
|
|
3145
|
+
//
|
|
3146
|
+
// La ligne écrite portait `ua` et `ip` ; la table des sessions internes ne les a pas.
|
|
3147
|
+
// PostgREST refusait, ce `catch` avalait le refus, et la route répondait `{"ok":true}`.
|
|
3148
|
+
// Ni le second hôte ni nous n'avions une seule ligne mesurée — notre table de production
|
|
3149
|
+
// en comptait ZÉRO — et rien, nulle part, ne le disait.
|
|
3150
|
+
//
|
|
3151
|
+
// « best-effort » est une intention juste : une mesure ne doit jamais empêcher de lire un
|
|
3152
|
+
// document. Mais best-effort ne veut pas dire MUET. Ce qu'on rattrape ici, c'est le droit
|
|
3153
|
+
// de continuer — pas le droit de ne rien dire.
|
|
3154
|
+
//
|
|
3155
|
+
// ⚠️ La leçon dépasse ce bloc : une règle écrite dans un commentaire ne protège pas le
|
|
3156
|
+
// code qui la suit. C'est pourquoi elle est aussi devenue un test —
|
|
3157
|
+
// server/__tests__/ecritureMuette.test.js refuse tout `catch` vide autour d'une écriture
|
|
3158
|
+
// de mesure.
|
|
3159
|
+
try {
|
|
3160
|
+
if (await PLAYER.limits.allow("intsess:echec", 1, 3600)) {
|
|
3161
|
+
PLAYER.errors.capture(new Error(`écriture de session interne refusée : ${e && e.message ? e.message : "cause inconnue"} — la mesure ne s'enregistre pas`), { route: "internal-session" });
|
|
3162
|
+
}
|
|
3163
|
+
} catch { /* un journal ne doit jamais empêcher une lecture */ }
|
|
3164
|
+
}
|
|
3143
3165
|
res.statusCode = 200; res.setHeader("Content-Type", "application/json"); res.end('{"ok":true}');
|
|
3144
3166
|
return;
|
|
3145
3167
|
}
|
|
@@ -3149,7 +3171,19 @@ async function handler(req, res) {
|
|
|
3149
3171
|
// 'session' = résumé riche (temps par page, appareil) → upsert ; open/page/heartbeat → journal léger (funnel/overview).
|
|
3150
3172
|
if (body.event === "session") await upsertSession(share, { sessionId: body.sessionId, numPages: body.numPages, maxPage: body.maxPage, totalSeconds: body.totalSeconds, pagesTime: body.pagesTime }, { ip: ip0, ua: ua0 });
|
|
3151
3173
|
else await logView(share, { event: body.event, page: body.page, maxPage: body.maxPage, seconds: body.seconds, sessionId: body.sessionId, ua: ua0 });
|
|
3152
|
-
} catch {
|
|
3174
|
+
} catch (e) {
|
|
3175
|
+
// ⚠️ LE MÊME `catch` MUET, SUR LE CHEMIN EXTERNE. Trouvé par la garde écrite pour
|
|
3176
|
+
// l'interne : une fois la règle devenue un test, elle a désigné son jumeau.
|
|
3177
|
+
//
|
|
3178
|
+
// Celui-ci n'a rien cassé jusqu'ici — la table des sessions externes a bien les colonnes
|
|
3179
|
+
// qu'on lui envoie. Mais il aurait avalé le prochain écart de la même façon, et personne
|
|
3180
|
+
// n'aurait rien vu : c'est ce qui rend la classe dangereuse, pas l'instance.
|
|
3181
|
+
try {
|
|
3182
|
+
if (await PLAYER.limits.allow("sess:echec", 1, 3600)) {
|
|
3183
|
+
PLAYER.errors.capture(new Error(`écriture de mesure refusée : ${e && e.message ? e.message : "cause inconnue"} — la lecture n'est pas comptée`), { route: "track" });
|
|
3184
|
+
}
|
|
3185
|
+
} catch { /* un journal ne doit jamais empêcher une lecture */ }
|
|
3186
|
+
}
|
|
3153
3187
|
}
|
|
3154
3188
|
res.statusCode = 200; res.setHeader("Content-Type", "application/json"); res.end('{"ok":true}');
|
|
3155
3189
|
return;
|
package/server/shares.js
CHANGED
|
@@ -344,7 +344,11 @@ async function setShareAuth(slug, requireAuth) {
|
|
|
344
344
|
// signalé au point d'entrée plutôt qu'ici.
|
|
345
345
|
const BORNES = { pages: 10_000, secondes: 24 * 3600, entreesPagesTime: 2_000 };
|
|
346
346
|
|
|
347
|
-
|
|
347
|
+
// ⚠️ `ip` N'EST PLUS LU, ET LA SIGNATURE LE DIT. L'appelant continue de le passer — il ne sait pas
|
|
348
|
+
// ce que chaque table conserve, et ce n'est pas à lui de le savoir. Mais le garder en paramètre
|
|
349
|
+
// nommé laisserait croire qu'il sert : c'est comme ça qu'une donnée revient dans une ligne où elle
|
|
350
|
+
// n'a rien à faire. Une lecture interne, c'est un collègue ; on ne conserve pas son adresse.
|
|
351
|
+
async function upsertInternalSession(p, { ip: _ip, ua }) {
|
|
348
352
|
const num = (v) => (Number.isFinite(+v) ? Math.trunc(+v) : null);
|
|
349
353
|
const borne = (v, max) => { const n = num(v); return n == null ? null : Math.max(0, Math.min(n, max)); };
|
|
350
354
|
const sessionId = String(p.sessionId || "").slice(0, 64);
|
|
@@ -390,7 +394,24 @@ async function upsertInternalSession(p, { ip, ua }) {
|
|
|
390
394
|
}
|
|
391
395
|
return out;
|
|
392
396
|
})(),
|
|
393
|
-
|
|
397
|
+
// ⚠️ NI `ua` NI `ip` ICI, ET C'EST LE CORRECTIF — PAS UNE COLONNE À AJOUTER.
|
|
398
|
+
//
|
|
399
|
+
// Ces deux champs partaient vers une table qui ne les a pas. PostgREST refusait l'insertion
|
|
400
|
+
// (« column "ua" ... does not exist »), le `catch` de l'appelant avalait le refus, et la route
|
|
401
|
+
// répondait `{"ok":true}`. Le suivi interne n'a donc JAMAIS rien écrit — ni chez le second
|
|
402
|
+
// hôte, ni chez nous : notre table de production comptait zéro ligne.
|
|
403
|
+
//
|
|
404
|
+
// ⚠️ Le schéma avait raison, c'est le code qui mentait. Une lecture INTERNE, c'est un collègue :
|
|
405
|
+
// `device`, `os` et `browser` — dérivés — suffisent, et on ne conserve pas l'agent complet ni
|
|
406
|
+
// l'adresse de ses propres équipes. La table des sessions EXTERNES les porte, elle, parce que
|
|
407
|
+
// ce n'est ni la même population ni la même promesse.
|
|
408
|
+
//
|
|
409
|
+
// Corriger en AJOUTANT les colonnes aurait fait l'inverse : mettre le schéma au niveau du code
|
|
410
|
+
// au lieu du code au niveau de l'intention. Ce qu'on garde sur ses propres équipes ne se décide
|
|
411
|
+
// pas par un message d'erreur PostgREST.
|
|
412
|
+
//
|
|
413
|
+
// Trouvé par le second hôte, vérifié en rejouant l'insertion.
|
|
414
|
+
device, os, browser, last_at: new Date().toISOString(),
|
|
394
415
|
};
|
|
395
416
|
await PLAYER.db.request("commercial_doc_internal_sessions?on_conflict=session_id", { method: "POST", headers: { Prefer: "resolution=merge-duplicates,return=minimal" }, body: [row] });
|
|
396
417
|
}
|