discovery-media-player 0.1.3 → 0.1.4
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 +3 -0
- package/package.json +1 -1
- package/server/__tests__/plateforme.test.js +82 -0
- package/server/handler.js +37 -1
package/README.md
CHANGED
|
@@ -87,6 +87,9 @@ forking it. A fix lands once and reaches every instance on its next deploy.
|
|
|
87
87
|
- **Node / Express / Next.js** — same handler, mounted on a route
|
|
88
88
|
- **Standalone** — `npm start`, or the Docker image
|
|
89
89
|
|
|
90
|
+
It reads `req.query` when the platform provides it (serverless, Express) and falls back to parsing
|
|
91
|
+
`req.url` when it does not — so a bare `http.createServer` works too, without a shim.
|
|
92
|
+
|
|
90
93
|
See [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) for the boundary, and
|
|
91
94
|
[`docs/API.md`](docs/API.md) for the surface an integrator implements.
|
|
92
95
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "discovery-media-player",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
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",
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// LE GESTIONNAIRE NE DOIT DÉPENDRE D'AUCUNE PLATEFORME.
|
|
2
|
+
//
|
|
3
|
+
// Le README promet « Vercel, Next.js, Express, ou le serveur HTTP de Node — le même gestionnaire ».
|
|
4
|
+
// C'était vrai à une convention près, jamais écrite : il lisait `req.query`, que les plateformes
|
|
5
|
+
// serverless et Express remplissent, mais qu'un serveur HTTP nu laisse indéfini.
|
|
6
|
+
//
|
|
7
|
+
// ⚠️ ET LE SYMPTÔME ÉTAIT LE PIRE POSSIBLE. Sans paramètres, la requête cherchait un partage nommé
|
|
8
|
+
// « rien » et rendait « Ce lien n'est plus valide ou a été révoqué ». Un intégrateur voyait un
|
|
9
|
+
// REFUS là où il lui manquait un branchement — l'inversion exacte qu'on passe notre temps à
|
|
10
|
+
// corriger. Signalé par un hôte qui montait le player sur http.createServer : chez lui, ça aurait
|
|
11
|
+
// marché en production par chance, pas par construction.
|
|
12
|
+
|
|
13
|
+
const player = require("../handler.js");
|
|
14
|
+
|
|
15
|
+
function contexteMinimal() {
|
|
16
|
+
return {
|
|
17
|
+
plugins: {}, has: () => false,
|
|
18
|
+
storage: { isAllowedUrl: () => false, async fetchFile() { return null; }, async put() {} },
|
|
19
|
+
db: { async request() { return []; }, async selectAll() { return []; } },
|
|
20
|
+
mail: { async send() {} },
|
|
21
|
+
identity: { async verifyToken() { return null; }, roleOf: () => "", isAdmin: () => false, async canManageShares() { return false; } },
|
|
22
|
+
limits: { async allow() { return true; } },
|
|
23
|
+
branding: { async logo() { return ""; }, name: "", poweredBy: "", loaderName: "", async forKey() { return null; }, title: (b) => b },
|
|
24
|
+
errors: { async capture() {} },
|
|
25
|
+
legal: { sourceUrl: "", legalUrl: "", privacyUrl: "", trackingNotice: "" },
|
|
26
|
+
config: { supabaseUrl: "", supabasePublishableKey: "", mapsKey: "", extraFrameAncestors: [] },
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function appel(req) {
|
|
31
|
+
player.init(contexteMinimal());
|
|
32
|
+
const res = {
|
|
33
|
+
statusCode: 0, headers: {}, body: "",
|
|
34
|
+
setHeader(k, v) { this.headers[k.toLowerCase()] = v; },
|
|
35
|
+
end(b) { this.body = String(b == null ? "" : b); },
|
|
36
|
+
};
|
|
37
|
+
await player.handler({ method: "GET", headers: {}, socket: {}, ...req }, res);
|
|
38
|
+
return res;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
describe("d'où viennent les paramètres", () => {
|
|
42
|
+
// La convention serverless : la plateforme a déjà analysé l'URL.
|
|
43
|
+
it("utilise req.query quand la plateforme le fournit", async () => {
|
|
44
|
+
const res = await appel({ query: { contract: "1" } });
|
|
45
|
+
expect(res.statusCode).toBe(200);
|
|
46
|
+
expect(JSON.parse(res.body).contract).toBe(1);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// ⚠️ Le cas qui produisait « lien révoqué ». Un serveur HTTP nu ne remplit pas req.query.
|
|
50
|
+
it("les retrouve dans req.url quand elle ne le fournit pas", async () => {
|
|
51
|
+
const res = await appel({ url: "/api/doc?contract=1" });
|
|
52
|
+
expect(res.statusCode).toBe(200);
|
|
53
|
+
expect(JSON.parse(res.body).contract).toBe(1);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("ne se laisse pas troubler par une URL absurde", async () => {
|
|
57
|
+
const res = await appel({ url: "pas une url du tout" });
|
|
58
|
+
expect(res.statusCode).toBe(400);
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
describe("quand rien n'est demandé", () => {
|
|
63
|
+
// ⚠️ LE POINT. Ni slug, ni présentation, ni aperçu : il n'y a rien à afficher, et ce n'est PAS
|
|
64
|
+
// un refus. Rendre la page de révocation envoyait l'intégrateur chercher un lien mort.
|
|
65
|
+
it("le dit, au lieu d'afficher « lien révoqué »", async () => {
|
|
66
|
+
const res = await appel({ query: {} });
|
|
67
|
+
expect(res.statusCode).toBe(400);
|
|
68
|
+
expect(res.body).toContain("Aucun document demandé");
|
|
69
|
+
expect(res.body).not.toContain("révoqué");
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("oriente vers la cause réelle : les paramètres de requête", async () => {
|
|
73
|
+
expect((await appel({ query: {} })).body).toMatch(/paramètres de requête/);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// Un slug inconnu, LUI, est bien un refus : la distinction doit rester nette dans les deux sens.
|
|
77
|
+
it("un slug inconnu reste un refus, pas une erreur de branchement", async () => {
|
|
78
|
+
const res = await appel({ query: { slug: "Inconnu-1234" } });
|
|
79
|
+
expect(res.statusCode).toBe(404);
|
|
80
|
+
expect(res.body).toContain("révoqué");
|
|
81
|
+
});
|
|
82
|
+
});
|
package/server/handler.js
CHANGED
|
@@ -2113,9 +2113,34 @@ async function readJsonBody(req) {
|
|
|
2113
2113
|
});
|
|
2114
2114
|
}
|
|
2115
2115
|
|
|
2116
|
+
/**
|
|
2117
|
+
* Paramètres de la requête, quelle que soit la plateforme.
|
|
2118
|
+
*
|
|
2119
|
+
* Le gestionnaire lit `req.query` — la convention des plateformes serverless (Vercel, Next.js) et
|
|
2120
|
+
* d'Express. Un serveur HTTP nu ne la remplit pas : `req.query` est alors `undefined`, et TOUT
|
|
2121
|
+
* paramètre disparaît.
|
|
2122
|
+
*
|
|
2123
|
+
* ⚠️ CE QUE ÇA DONNAIT, ET POURQUOI C'ÉTAIT LE PIRE DES SYMPTÔMES. Sans paramètres, la requête
|
|
2124
|
+
* partait chercher un partage nommé « rien », n'en trouvait pas, et affichait « Ce lien n'est plus
|
|
2125
|
+
* valide ou a été révoqué ». Un intégrateur voyait donc un REFUS là où il n'avait simplement pas
|
|
2126
|
+
* branché la plateforme. C'est exactement l'inversion qu'on passe notre temps à corriger : une
|
|
2127
|
+
* erreur de câblage ne doit jamais ressembler à une décision.
|
|
2128
|
+
*
|
|
2129
|
+
* Signalé par un hôte qui montait le player sur `http.createServer` : chez lui ça aurait marché en
|
|
2130
|
+
* production (Vercel remplit `req.query`) — par chance, pas par construction.
|
|
2131
|
+
*/
|
|
2132
|
+
function parametres(req) {
|
|
2133
|
+
if (req.query && typeof req.query === "object") return req.query;
|
|
2134
|
+
try {
|
|
2135
|
+
return Object.fromEntries(new URL(req.url || "/", "http://interne").searchParams);
|
|
2136
|
+
} catch {
|
|
2137
|
+
return {};
|
|
2138
|
+
}
|
|
2139
|
+
}
|
|
2140
|
+
|
|
2116
2141
|
async function handler(req, res) {
|
|
2117
2142
|
try {
|
|
2118
|
-
const q = req
|
|
2143
|
+
const q = parametres(req);
|
|
2119
2144
|
const slug = String(q.slug || "").trim();
|
|
2120
2145
|
|
|
2121
2146
|
if (req.method === "POST") {
|
|
@@ -2496,6 +2521,17 @@ async function handler(req, res) {
|
|
|
2496
2521
|
return;
|
|
2497
2522
|
}
|
|
2498
2523
|
|
|
2524
|
+
// AUCUN DOCUMENT DEMANDÉ. Ni slug, ni présentation, ni aperçu, ni carte d'identité — il n'y a
|
|
2525
|
+
// rien à afficher, et ce n'est pas un refus. Le dire franchement évite qu'un intégrateur
|
|
2526
|
+
// cherche un lien révoqué là où il lui manque un paramètre.
|
|
2527
|
+
if (req.method === "GET" && !slug && !q.present && !q.preview && !q.contract) {
|
|
2528
|
+
res.statusCode = 400;
|
|
2529
|
+
res.setHeader("Content-Type", "text/plain; charset=utf-8");
|
|
2530
|
+
res.end("Aucun document demandé. Attendu : ?slug=… , ?present=… , ?preview=1 ou ?contract=1.\n" +
|
|
2531
|
+
"Si vous intégrez le player, vérifiez que la plateforme fournit les paramètres de requête.");
|
|
2532
|
+
return;
|
|
2533
|
+
}
|
|
2534
|
+
|
|
2499
2535
|
// ── CARTE D'IDENTITÉ (`?contract=1`) ─────────────────────────────────────────────────────
|
|
2500
2536
|
// La règle 4 du contrat demande à l'hôte d'épingler la version qu'il vise et de le VÉRIFIER.
|
|
2501
2537
|
// Sans point d'interrogation, cette règle était une intention : un hôte ne pouvait pas écrire
|