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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "discovery-media-player",
3
- "version": "0.1.36",
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 { /* best-effort */ }
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 { /* best-effort */ }
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
- async function upsertInternalSession(p, { ip, ua }) {
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
- ua: String(ua || "").slice(0, 300), ip: String(ip || "").slice(0, 60), device, os, browser, last_at: new Date().toISOString(),
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
  }