discovery-media-player 0.1.119 → 0.1.120
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/README.md +7 -0
- package/docs/HOST-CONTRACT.md +17 -0
- package/docs/README.md +42 -0
- package/package.json +1 -1
- package/server/presentations.js +1 -1
- package/server/schema.js +60 -0
package/README.md
CHANGED
|
@@ -14,6 +14,8 @@ and live presentation — for teams who would rather not hand their commercial d
|
|
|
14
14
|
to a third-party SaaS.
|
|
15
15
|
|
|
16
16
|
[](https://github.com/Juli1artha/discovery-media-player/actions/workflows/ci.yml)
|
|
17
|
+
[](https://github.com/Juli1artha/discovery-media-player/actions/workflows/codeql.yml)
|
|
18
|
+
[](https://scorecard.dev/viewer/?uri=github.com/Juli1artha/discovery-media-player)
|
|
17
19
|
[](https://www.npmjs.com/package/discovery-media-player)
|
|
18
20
|
[](https://github.com/Juli1artha/discovery-media-player/pkgs/container/discovery-media-player)
|
|
19
21
|
[](package.json)
|
|
@@ -161,6 +163,11 @@ on an allow-listed storage origin, one explicitly configured route of your own a
|
|
|
161
163
|
or a file under a configured local root. No credentials in URLs, no redirect following into
|
|
162
164
|
your private network.
|
|
163
165
|
|
|
166
|
+
The project has been through repeated external audits. The reports are published in the
|
|
167
|
+
repository, unedited, with their follow-up ledgers — findings, fixes, and what was rejected
|
|
168
|
+
with its reason: see [`docs/`](docs/README.md). Fixes are traced version by version in the
|
|
169
|
+
[CHANGELOG](CHANGELOG.md).
|
|
170
|
+
|
|
164
171
|
Found a hole? [`SECURITY.md`](SECURITY.md) — please do not open a public issue.
|
|
165
172
|
|
|
166
173
|
---
|
package/docs/HOST-CONTRACT.md
CHANGED
|
@@ -74,6 +74,23 @@ The three `presence*` fields report what the host has **observed**, not what it
|
|
|
74
74
|
| `presenceStrict` | **effective** — `PLAYER_PRESENCE_STRICT` is set *and* tokens can be issued. A closed door announced over an open one would be the worse failure |
|
|
75
75
|
| `presenceDurcissement` | `actif` (a hardened call came back), `degrade` (migration 0018 is missing), `inconnu` (nothing attempted in this process — **not** a green light, and process-local: another instance may have seen otherwise) |
|
|
76
76
|
|
|
77
|
+
⚠️ **Before you upgrade, do not read `presenceDurcissement`.** It is a *report of execution*: on an
|
|
78
|
+
instance where nothing is running it says `inconnu`, which means *nobody looked* — not *the migration
|
|
79
|
+
is there*. A pre-flight check built on it silently passes on every idle host, and the missing
|
|
80
|
+
migration is then discovered at the first presentation, i.e. at the worst moment. Ask
|
|
81
|
+
`GET /api/doc?contract=1&schema=1` and read **`schema.durcissementBase`** instead: it asks the
|
|
82
|
+
database, so it answers a global fact.
|
|
83
|
+
|
|
84
|
+
| `durcissementBase` | meaning |
|
|
85
|
+
|---|---|
|
|
86
|
+
| `applique` | migration 0018 is in the database — safe to run with the strict door closed |
|
|
87
|
+
| `absente` | 0018 is missing: apply it **before** setting `PLAYER_PRESENCE_STRICT`, or bootstraps will be refused with `503` |
|
|
88
|
+
| `indetermine` | the question could not be asked — neither a yes nor a no |
|
|
89
|
+
|
|
90
|
+
The probe writes nothing: it calls the function with `p_anon_cap = 0`, so a non-existent row exits
|
|
91
|
+
through the *capped* branch and returns before the insert. A real-Postgres test asserts that no row
|
|
92
|
+
appears. A host missing 0018 is also logged once an hour, so an idle instance still finds out.
|
|
93
|
+
|
|
77
94
|
That parameter **is** the one part of this card that needs the database, and only when you ask for
|
|
78
95
|
it. `verdict` is then one of:
|
|
79
96
|
|
package/docs/README.md
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Documentation
|
|
2
|
+
|
|
3
|
+
Nine documents, three readers. Start with the one that matches what you are trying to do —
|
|
4
|
+
none of them assumes you have read the others.
|
|
5
|
+
|
|
6
|
+
## You are integrating the player into an application
|
|
7
|
+
|
|
8
|
+
| Document | What it gives you |
|
|
9
|
+
|---|---|
|
|
10
|
+
| [`ARCHITECTURE.md`](ARCHITECTURE.md) | The one idea that holds the design: the core knows nothing about its host. Read this first. |
|
|
11
|
+
| [`API.md`](API.md) | What a host can call, and what it must implement. |
|
|
12
|
+
| [`HOST-CONTRACT.md`](HOST-CONTRACT.md) | The binding contract, with the dated journal of every boundary change. It ships **inside the package**: `require.resolve("discovery-media-player/contrat")`. |
|
|
13
|
+
|
|
14
|
+
## You are running an instance
|
|
15
|
+
|
|
16
|
+
| Document | What it gives you |
|
|
17
|
+
|---|---|
|
|
18
|
+
| [`CONFIGURATION.md`](CONFIGURATION.md) | Every environment variable. An instance is described entirely by its environment — there is no configuration file, on purpose. |
|
|
19
|
+
| [`MIGRATIONS.md`](MIGRATIONS.md) | What happens to a database **already in service** when the player expects a newer schema. (French.) |
|
|
20
|
+
| [`RETENTION.md`](RETENTION.md) | The declared perimeter of data retention: every personal-data column has a written policy, and CI enforces that the list is complete. Also an export of the package: `require.resolve("discovery-media-player/retention")`. (French.) |
|
|
21
|
+
|
|
22
|
+
## You are evaluating the project
|
|
23
|
+
|
|
24
|
+
The external audit trail is public, unedited, and kept in the state it was received —
|
|
25
|
+
an audit rewritten after the fact is no longer a trace. Findings and their fixes are
|
|
26
|
+
tracked version by version in the [CHANGELOG](../CHANGELOG.md).
|
|
27
|
+
|
|
28
|
+
| Document | What it is |
|
|
29
|
+
|---|---|
|
|
30
|
+
| [`AUDIT-2026-08-14-RAPPORT.md`](AUDIT-2026-08-14-RAPPORT.md) | First external audit, on `0.1.17`. Historical. (French.) |
|
|
31
|
+
| [`AUDIT-2026-08-14-SUIVI.md`](AUDIT-2026-08-14-SUIVI.md) | The follow-up ledger: done, decided-but-not-done, and rejected-with-reason. Historical. (French.) |
|
|
32
|
+
| [`AUDIT-2026-08-15-SECONDE-PASSE.md`](AUDIT-2026-08-15-SECONDE-PASSE.md) | Second pass, on `0.1.26` — including what the first follow-up had marked too optimistically. Historical. (French.) |
|
|
33
|
+
|
|
34
|
+
## Work in progress
|
|
35
|
+
|
|
36
|
+
| Document | What it is |
|
|
37
|
+
|---|---|
|
|
38
|
+
| [`SPEC-MEMBRE-INJECTE.md`](SPEC-MEMBRE-INJECTE.md) | A specification sent to a host for agreement **before** the contract moves. Nothing in it is implemented. (French.) |
|
|
39
|
+
|
|
40
|
+
Documents marked *(French)* are internal working documents; everything an integrator or an
|
|
41
|
+
operator needs day to day is in English. The reasoning behind that split is at the end of the
|
|
42
|
+
[README](../README.md#contributing).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "discovery-media-player",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.120",
|
|
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/presentations.js
CHANGED
|
@@ -1245,4 +1245,4 @@ async function listPresentationsForDoc(docId, email, isAdmin, autoriseLarge) {
|
|
|
1245
1245
|
module.exports = {
|
|
1246
1246
|
reacteurDepuisJeton,
|
|
1247
1247
|
purgerPerimees,
|
|
1248
|
-
messagePublic, CHAMPS_PUBLICS, etatDurcissementBootstrap, cheminPieceJointe, init, createPresentation, getPresentation, setPage, endPresentation, addMessage, listMessages, toggleReaction, editMessage, deleteMessage, setChatLock, createUploadUrl, reclaimPresentation, touchPresentation, listActivePresentations, handoverPresentation, endPresentationByOwner, recordAttendance, presentationStats, listPresentationsForDoc, switchPresentationDoc, setPresentationContent , STALE_MS};
|
|
1248
|
+
messagePublic, CHAMPS_PUBLICS, etatDurcissementBootstrap, signatureAbsente, cheminPieceJointe, init, createPresentation, getPresentation, setPage, endPresentation, addMessage, listMessages, toggleReaction, editMessage, deleteMessage, setChatLock, createUploadUrl, reclaimPresentation, touchPresentation, listActivePresentations, handoverPresentation, endPresentationByOwner, recordAttendance, presentationStats, listPresentationsForDoc, switchPresentationDoc, setPresentationContent , STALE_MS};
|
package/server/schema.js
CHANGED
|
@@ -315,6 +315,7 @@ async function vraimentSonderTout() {
|
|
|
315
315
|
const etat = etatDuSchema();
|
|
316
316
|
await ajouterSansRang(etat);
|
|
317
317
|
await ajouterPresence(etat);
|
|
318
|
+
await ajouterDurcissement(etat);
|
|
318
319
|
return etat;
|
|
319
320
|
}
|
|
320
321
|
|
|
@@ -343,6 +344,65 @@ async function vraimentSonderTout() {
|
|
|
343
344
|
// DÉCLARE la condition de validité, plutôt qu'un qu'on croirait inconditionnel. (relevé 2e hôte)
|
|
344
345
|
const PLAFOND_SANS_RANG = 1000;
|
|
345
346
|
|
|
347
|
+
// ⚠️ 0018 EST UNE PROPRIÉTÉ DE LA BASE — ELLE DOIT SE DEMANDER À LA BASE.
|
|
348
|
+
//
|
|
349
|
+
// `presenceDurcissement` (carte) est un RAPPORT D'EXÉCUTION : il dit ce que CE processus a constaté
|
|
350
|
+
// en servant des bootstraps. Sur une instance au repos il rend « inconnu », et c'est juste — mais
|
|
351
|
+
// inutilisable comme contrôle avant déploiement, où la question est « la migration est-elle là ? ».
|
|
352
|
+
// Nous avions même écrit une consigne de pré-vol qui l'exigeait : un hôte la suivant à la lettre
|
|
353
|
+
// aurait lu « pas degrade, donc j'y vais » et découvert le refus à la première présentation, c'est-
|
|
354
|
+
// à-dire au pire moment. On avait bâti un champ qui refuse de se prononcer sans observation, puis
|
|
355
|
+
// placé ce champ au centre d'une procédure qui exige une réponse. (Relevé par le second hôte.)
|
|
356
|
+
//
|
|
357
|
+
// ⚠️ LA SONDE N'ÉCRIT RIEN, ET CE N'EST PAS UN ESPOIR : avec `p_anon_cap = 0`, une ligne inexistante
|
|
358
|
+
// part par la branche « capped » de 0018 (`if v_count >= p_anon_cap then return ... ; return; end if;`)
|
|
359
|
+
// et la fonction rend AVANT son `insert`. Le slug de sonde porte une espace — donc il ne peut jamais
|
|
360
|
+
// être un slug réel (contrat `^[A-Za-z0-9_-]{1,64}$`), et ne peut pas heurter une vraie présence.
|
|
361
|
+
// Un test de base (Postgres réel) vérifie qu'aucune ligne n'apparaît.
|
|
362
|
+
const SLUG_SONDE_DURCISSEMENT = "sonde durcissement";
|
|
363
|
+
|
|
364
|
+
async function ajouterDurcissement(etat) {
|
|
365
|
+
const corps = {
|
|
366
|
+
p_slug: SLUG_SONDE_DURCISSEMENT, p_key: "sonde", p_ip_hash: null, p_page: 1,
|
|
367
|
+
p_name: "", p_avatar: "", p_is_member: false, p_is_presenter: false,
|
|
368
|
+
p_max_gap_ms: 0, p_anon_cap: 0, p_has_token: null, p_only_if_unclaimed: true,
|
|
369
|
+
};
|
|
370
|
+
try {
|
|
371
|
+
await PLAYER.db.request("rpc/player_attendance_bump", { method: "POST", body: corps });
|
|
372
|
+
etat.durcissementBase = "applique";
|
|
373
|
+
} catch (erreur) {
|
|
374
|
+
// ⚠️ TROIS ISSUES, ET LA TROISIÈME N'EST PAS UNE DES DEUX AUTRES. Seule la signature absente
|
|
375
|
+
// prouve que 0018 manque ; une panne réseau ou un 500 ne prouvent RIEN, et les compter comme
|
|
376
|
+
// « absente » serait le défaut qu'on a mis trois versions à retirer d'ailleurs.
|
|
377
|
+
let absente = false;
|
|
378
|
+
try { absente = require("./presentations.js").signatureAbsente(erreur); } catch { /* module absent */ }
|
|
379
|
+
etat.durcissementBase = absente ? "absente" : "indetermine";
|
|
380
|
+
// ⚠️ ET ON LE DIT, PAS SEULEMENT DANS LA CARTE. Une garde de ce dépôt refuse qu'une écriture soit
|
|
381
|
+
// rattrapée en silence, et elle a raison ici pour une raison qu'elle ne pouvait pas connaître :
|
|
382
|
+
// c'est ce journal qui prévient un hôte à qui 0018 manque SANS qu'aucune présentation ne tourne
|
|
383
|
+
// — exactement le cas où le rapport d'exécution reste muet, et où l'exploitant ne saura rien
|
|
384
|
+
// avant la première présentation. Une fois par heure : la carte est publique, donc appelable en
|
|
385
|
+
// boucle, et un journal sans frein deviendrait une arme.
|
|
386
|
+
try {
|
|
387
|
+
if (await PLAYER.limits.allow("schema:durcissement-absent", 1, 3600)) {
|
|
388
|
+
PLAYER.errors.capture(new Error(
|
|
389
|
+
absente
|
|
390
|
+
? "migration 0018-bootstrap-non-usurpable.sql ABSENTE : les bootstraps de présence ne "
|
|
391
|
+
+ "sont pas contrôlés. N'armez pas PLAYER_PRESENCE_STRICT avant de l'appliquer — il "
|
|
392
|
+
+ "refuserait alors les bootstraps en 503."
|
|
393
|
+
: "sonde de durcissement (0018) sans réponse exploitable : ni confirmée, ni infirmée — "
|
|
394
|
+
+ ((erreur && erreur.message) || erreur),
|
|
395
|
+
), { route: "schema" });
|
|
396
|
+
}
|
|
397
|
+
} catch { /* jamais bloquant */ }
|
|
398
|
+
}
|
|
399
|
+
etat.durcissementBaseCouvre =
|
|
400
|
+
"propriété de la BASE (migration 0018), globale à toutes les instances — à ne pas confondre avec "
|
|
401
|
+
+ "presenceDurcissement, qui est ce que CE processus a constaté en servant des bootstraps. "
|
|
402
|
+
+ "C'est ce champ-ci qu'on lit AVANT un déploiement ; « indetermine » = la question n'a pas pu "
|
|
403
|
+
+ "être posée, ce n'est ni un oui ni un non.";
|
|
404
|
+
}
|
|
405
|
+
|
|
346
406
|
async function ajouterSansRang(etat) {
|
|
347
407
|
const r = reponses.get("doc_presentation_messages.mod_seq");
|
|
348
408
|
if (!r || !r.present) return;
|