discovery-media-player 0.1.98 → 0.1.99

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.98",
3
+ "version": "0.1.99",
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",
@@ -787,20 +787,30 @@ async function recordAttendance(slug, participant, { presentation = null, ipHash
787
787
  // lire-modifier-réécrire ci-dessous — toujours correcte, mais SANS le plafond — et on le dit une
788
788
  // fois. Même patron que player_rate_limit_bump : dégrader, jamais casser, jamais en silence.
789
789
  const capAnon = Number(anonCap) > 0 ? Math.trunc(Number(anonCap)) : PLAFOND_CREATION_ANON_DEFAUT;
790
+ // ⚠️ ON N'ENVOIE `p_has_token` QUE SI 0017 EST LÀ — ET C'EST LA MOITIÉ MANQUANTE DE LA COMPATIBILITÉ.
791
+ //
792
+ // PostgREST résout une RPC par JEU D'ARGUMENTS NOMMÉS : un argument en trop ne « prend pas son
793
+ // défaut », il ne correspond à AUCUNE fonction → 404. Le `DEFAULT null` de 0017 rend donc la base
794
+ // NEUVE compatible avec du CODE ANCIEN ; il ne peut rien pour le sens INVERSE — code neuf, base
795
+ // ancienne — qui est précisément l'ordre d'un déploiement réel : le code part avant la migration.
796
+ //
797
+ // Sans cette garde, un hôte à jour mais pas encore migré tombait dans le repli lire-modifier-
798
+ // réécrire, c'est-à-dire PERDAIT LE PLAFOND de création de faux participants apporté par 0015 : une
799
+ // garde disparaissait en silence entre deux migrations. On sonde donc la colonne (la réponse existe
800
+ // déjà) et on appelle à 10 arguments quand elle manque — l'ancien contrat, valide sur les DEUX bases.
801
+ // La dégradation redevient alors ce qu'on annonce : pas de compteur de transition, rien d'autre.
802
+ // (relevé du second hôte, qui l'a MESURÉ sur sa base au lieu de le supposer)
803
+ const transitionDispo = await require("./schema").attendue("jetonPresence");
804
+ const corpsRpc = {
805
+ p_slug: String(slug), p_key: String(key), p_ip_hash: ipHash || null, p_page: page,
806
+ p_name: (name || "").slice(0, 120), p_avatar: (avatar || "").slice(0, 600),
807
+ p_is_member: !!isMember, p_is_presenter: !!isPresenter,
808
+ p_max_gap_ms: ATTEND_MAX_GAP_MS, p_anon_cap: capAnon,
809
+ };
810
+ // true → last_token_at, false → last_no_token_at, null → ni l'un ni l'autre. 0017 seulement.
811
+ if (transitionDispo) corpsRpc.p_has_token = hasToken == null ? null : !!hasToken;
790
812
  try {
791
- const r = await PLAYER.db.request("rpc/player_attendance_bump", {
792
- method: "POST",
793
- body: {
794
- p_slug: String(slug), p_key: String(key), p_ip_hash: ipHash || null, p_page: page,
795
- p_name: (name || "").slice(0, 120), p_avatar: (avatar || "").slice(0, 600),
796
- p_is_member: !!isMember, p_is_presenter: !!isPresenter,
797
- p_max_gap_ms: ATTEND_MAX_GAP_MS, p_anon_cap: capAnon,
798
- // Transition du jeton (0017) : true → last_token_at, false → last_no_token_at, null → ni l'un ni
799
- // l'autre. La RPC 11-args (p_has_token DEFAULT null) accepte l'appel même sans la 0017 côté code
800
- // ancien ; sans la 0017 côté base, la 11-args n'existe pas → on tombe dans le repli ci-dessous.
801
- p_has_token: hasToken == null ? null : !!hasToken,
802
- },
803
- });
813
+ const r = await PLAYER.db.request("rpc/player_attendance_bump", { method: "POST", body: corpsRpc });
804
814
  const ligne = Array.isArray(r) ? r[0] : r;
805
815
  if (ligne && typeof ligne.ok === "boolean") {
806
816
  // Plafond de création atteint : on ne crée pas ce faux participant. 429 = « trop », pas une panne.
@@ -812,8 +822,17 @@ async function recordAttendance(slug, participant, { presentation = null, ipHash
812
822
  if (!_avertRpcPresence) {
813
823
  _avertRpcPresence = true;
814
824
  try {
825
+ // ⚠️ ON NOMME LE FICHIER QU'ON VIENT VRAIMENT D'ESSAYER — un nom FAUX est pire qu'un nom
826
+ // absent, parce qu'il est ACTIONNABLE : l'exploitant vérifie la migration nommée, la trouve
827
+ // appliquée, et conclut au faux positif. C'est ce qui arrivait quand ce message accusait 0015
828
+ // alors que l'échec venait de l'argument `p_has_token` de 0017 (relevé du second hôte). La
829
+ // règle « on nomme le fichier, pas l'erreur » ne tient que tant qu'UN SEUL fichier peut causer
830
+ // l'échec ; dès qu'un second emprunte le même chemin, le nom doit se DÉDUIRE de la tentative.
831
+ const fichier = transitionDispo
832
+ ? "supabase/migrations/0017-jeton-presence.sql (ou 0015-presence-atomique.sql)"
833
+ : "supabase/migrations/0015-presence-atomique.sql";
815
834
  PLAYER.errors.capture(new Error(
816
- "présence non atomique : appliquez supabase/migrations/0015-presence-atomique.sql. "
835
+ "présence non atomique : appliquez " + fichier + ". "
817
836
  + "Sans elle, la présence est écrite par lire-modifier-réécrire (correct) mais le plafond "
818
837
  + "de création de faux participants anonymes n'est pas appliqué. "
819
838
  + "(" + ((erreur && erreur.message) || erreur) + ")",