discovery-media-player 0.1.171 → 0.1.172
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/docs/HOST-CONTRACT.md +26 -1
- package/package.json +1 -1
- package/server/handler.js +28 -4
- package/server/page-mur.js +9 -3
package/docs/HOST-CONTRACT.md
CHANGED
|
@@ -512,6 +512,30 @@ said the player holds no server secret at all — too absolute: the standalone c
|
|
|
512
512
|
The player's counters bound the *rate*; your plugin bounds the *code*. Both are needed, and neither
|
|
513
513
|
replaces the other.
|
|
514
514
|
|
|
515
|
+
## Who may open a restricted document (`plugins.documentAccess`, 0.1.172)
|
|
516
|
+
|
|
517
|
+
The visitor wall answers one question: *has this person proven an address?* A host that restricts a document to
|
|
518
|
+
**its own team**, or to one partner organisation, could not express it — any proven address opened it. Provide
|
|
519
|
+
`plugins.documentAccess` with:
|
|
520
|
+
|
|
521
|
+
```js
|
|
522
|
+
decide({ share, visitor }) → Promise<{ ok: true } | { ok: false, reason?: "denied" | "unavailable" }>
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
It is called for a document whose link carries `require_auth`, once the visitor is signed in (`visitor` is what
|
|
526
|
+
`plugins.visitors.currentVisitor(req)` returned). It is **not** called for an open document, nor before the visitor
|
|
527
|
+
signs in — the wall handles that.
|
|
528
|
+
|
|
529
|
+
| your answer | what the reader gets |
|
|
530
|
+
|---|---|
|
|
531
|
+
| `{ ok: true }` | the document |
|
|
532
|
+
| `{ ok: false }` | the wall again, saying *this address* has no access, so they can sign in with the one the document was sent to; `?file=1` answers `403 { error: "denied" }` without streaming; embedded, the bridge reports `denied` |
|
|
533
|
+
| an exception, anything unreadable, or `{ ok: false, reason: "unavailable" }` | a **refusal** (`auth-unavailable`; `?file=1` → `503`) — never an opening. The exception goes to `errors.capture` |
|
|
534
|
+
|
|
535
|
+
Without the plugin, nothing changes: a proven address opens the document, as before. ⚠️ **That is also why its
|
|
536
|
+
absence is silent** — a host that upgrades the player without wiring the plugin keeps the old behaviour and sees no
|
|
537
|
+
error. Test that your host actually provides it.
|
|
538
|
+
|
|
515
539
|
## ⚠️ What `limits.allow` promises changed
|
|
516
540
|
|
|
517
541
|
It used to promise *best effort, per process*. The standalone context now counts in a **shared
|
|
@@ -919,7 +943,8 @@ closed.
|
|
|
919
943
|
|---|---|---|
|
|
920
944
|
| `revoked` | unknown or revoked link | do not open |
|
|
921
945
|
| `auth-required` | restricted document, visitor not signed in | do not open — the wall stays up |
|
|
922
|
-
| `auth-unavailable` | restricted document, access wall missing from this instance | do not open |
|
|
946
|
+
| `auth-unavailable` | restricted document, access wall missing from this instance — or the host's `documentAccess` plugin failed | do not open |
|
|
947
|
+
| `denied` | restricted document, visitor signed in but the host's `documentAccess` plugin says this address has no access (0.1.172) | do not open — the wall stays up and offers another address |
|
|
923
948
|
| `expired` | the link's expiry date has passed (migration `0028`) | do not open — the page tells the reader to ask for a new link |
|
|
924
949
|
| `password-required` | the link is password-protected and this browser has not entered it (migration `0028`) | do not open — the password page stays up in the frame |
|
|
925
950
|
| `ended` | presentation over or unknown | do not open |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "discovery-media-player",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.172",
|
|
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
|
@@ -1179,8 +1179,31 @@ async function handlerMesure(req, res) {
|
|
|
1179
1179
|
if (share.require_auth === true && !visitors) return sendRefusal(res, "auth-unavailable", embed);
|
|
1180
1180
|
const gated = share.require_auth === true && !visitor;
|
|
1181
1181
|
|
|
1182
|
+
// L'ACCÈS PAR DOCUMENT (0.1.172) — greffon facultatif `documentAccess`. Le mur ne savait dire qu'une chose :
|
|
1183
|
+
// « cette personne a prouvé son adresse ». Un hôte qui réserve un document à SON équipe, ou à une organisation,
|
|
1184
|
+
// ne pouvait pas l'exprimer : toute adresse prouvée l'ouvrait. Le greffon répond, pour un document réservé et un
|
|
1185
|
+
// visiteur identifié, « oui » ou « non » — c'est l'HÔTE qui sait qui a droit à quoi.
|
|
1186
|
+
// · absent : l'ancien comportement (une adresse prouvée suffit) — rien ne change pour un hôte qui ne l'a pas ;
|
|
1187
|
+
// · « non » : le mur revient, en disant que CETTE adresse n'a pas accès, et propose d'en utiliser une autre ;
|
|
1188
|
+
// · une panne (exception, réponse illisible, `reason: "unavailable"`) : REFUS — jamais d'ouverture par défaut.
|
|
1189
|
+
let refusAcces = null;
|
|
1190
|
+
if (share.require_auth === true && visitor && PLAYER.plugins.documentAccess) {
|
|
1191
|
+
let d = null;
|
|
1192
|
+
try { d = await PLAYER.plugins.documentAccess.decide({ share, visitor }); } catch (e) {
|
|
1193
|
+
try { await PLAYER.errors.capture(e, { route: "doc", etape: "documentAccess" }); } catch { /* la capture ne décide rien */ }
|
|
1194
|
+
}
|
|
1195
|
+
if (!d || d.ok !== true) refusAcces = d && d.ok === false && d.reason !== "unavailable" ? "denied" : "auth-unavailable";
|
|
1196
|
+
}
|
|
1197
|
+
if (refusAcces === "auth-unavailable") {
|
|
1198
|
+
if (String(q.file || "") === "1") { repondreJson(res, 503, { ok: false, error: "auth-unavailable" }); return; }
|
|
1199
|
+
return sendRefusal(res, "auth-unavailable", embed);
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1182
1202
|
if (String(q.file || "") === "1") {
|
|
1183
1203
|
if (gated) { repondreJson(res, 401, { ok: false, error: "auth" }); return; }
|
|
1204
|
+
// Le FICHIER reste derrière un refus d'accès, comme derrière le mur : une page qui dit non en laissant ?file=1
|
|
1205
|
+
// streamer le PDF serait une porte de décor.
|
|
1206
|
+
if (refusAcces === "denied") { repondreJson(res, 403, { ok: false, error: "denied" }); return; }
|
|
1184
1207
|
// Stream depuis le Storage en RELAYANT les requêtes Range → pdf.js charge progressivement (les 1res
|
|
1185
1208
|
// pages s'affichent sans télécharger tout le PDF) → affichage bien plus rapide.
|
|
1186
1209
|
const range = req.headers["range"];
|
|
@@ -1193,15 +1216,16 @@ async function handlerMesure(req, res) {
|
|
|
1193
1216
|
|
|
1194
1217
|
// Soft wall : contenu réservé → on sert la page de connexion visiteur (email + code)
|
|
1195
1218
|
// AVANT de charger le lecteur. À la vérification, le cookie est posé → un reload lève le mur.
|
|
1196
|
-
if (gated) {
|
|
1219
|
+
if (gated || refusAcces === "denied") {
|
|
1197
1220
|
let wlogo = ""; try { wlogo = await PLAYER.branding.logo(); } catch { /* sans logo */ }
|
|
1198
1221
|
const wnonce = crypto.randomBytes(16).toString("base64");
|
|
1199
1222
|
const gcid = visitors.googleClientId();
|
|
1200
1223
|
// Intégré : le mur reste affiché (le visiteur peut s'y connecter sur place) mais il DIT à
|
|
1201
1224
|
// l'hôte que le document est retenu — sinon l'hôte croit à une panne et replie sur son
|
|
1202
|
-
// lecteur, qui lui ouvrirait le document que ce mur protège.
|
|
1203
|
-
const
|
|
1204
|
-
|
|
1225
|
+
// lecteur, qui lui ouvrirait le document que ce mur protège. Refusé à CETTE adresse : `denied`.
|
|
1226
|
+
const raison = refusAcces === "denied" ? "denied" : "auth-required";
|
|
1227
|
+
const wall = softWallHtml(share, wnonce, wlogo, gcid, refusAcces === "denied" ? { refuse: String(visitor.email || "") } : null)
|
|
1228
|
+
+ (embed ? `<script nonce="${wnonce}">try{parent.postMessage({type:"3dd-doc-embed-denied",reason:${jsonPourScript(raison)}},"*")}catch(e){}</script>` : "");
|
|
1205
1229
|
return sendSoftWallHtml(res, wall, wnonce, [originOf(wlogo), originOf(share.brand_logo)].filter(Boolean).join(" "), embed ? embedFrameAncestors() : null);
|
|
1206
1230
|
}
|
|
1207
1231
|
|
package/server/page-mur.js
CHANGED
|
@@ -14,7 +14,10 @@ function notFoundHtml() {
|
|
|
14
14
|
// Page « soft wall » : accès à un document réservé (require_auth). On ne demande PAS un compte,
|
|
15
15
|
// on propose de RECEVOIR le document — l'email est l'action pour débloquer, pas un péage.
|
|
16
16
|
// Email → code à 6 chiffres → cookie posé → reload → le lecteur s'ouvre. (Google = Lot B.)
|
|
17
|
-
function softWallHtml(share, nonce, logoUrl, googleClientId) {
|
|
17
|
+
function softWallHtml(share, nonce, logoUrl, googleClientId, opts) {
|
|
18
|
+
// Refusé à CETTE adresse (greffon `documentAccess`, 0.1.172) : on le dit, et on garde le formulaire — l'issue est
|
|
19
|
+
// de s'identifier avec l'adresse à laquelle le document a été envoyé.
|
|
20
|
+
const refuse = opts && typeof opts.refuse === "string" ? opts.refuse : null;
|
|
18
21
|
const title = esc(share.doc_title || share.file_name || "ce document");
|
|
19
22
|
const brandLogo = esc(share.brand_logo || "");
|
|
20
23
|
const dark = !!share.brand_dark;
|
|
@@ -56,8 +59,11 @@ function softWallHtml(share, nonce, logoUrl, googleClientId) {
|
|
|
56
59
|
<body>
|
|
57
60
|
<div class=card>
|
|
58
61
|
${logo ? `<img class=logo src="${logo}" alt="${logoAlt}">` : ""}
|
|
59
|
-
|
|
60
|
-
|
|
62
|
+
${refuse !== null
|
|
63
|
+
? `<h1>Ce document vous est réservé</h1>
|
|
64
|
+
<p class=sub><span class=doc>${title}</span><br>${refuse ? `L'adresse ${esc(refuse)} n'y a pas accès.` : "Cette adresse n'y a pas accès."} Identifiez-vous avec l'adresse à laquelle il vous a été envoyé.</p>`
|
|
65
|
+
: `<h1>Accédez à votre document</h1>
|
|
66
|
+
<p class=sub><span class=doc>${title}</span><br>Débloquez-le en un instant.</p>`}
|
|
61
67
|
|
|
62
68
|
${gcid ? `<div id=gbtn class=gbtn></div><div class=orsep><span>ou par email</span></div>` : ""}
|
|
63
69
|
<div id=s1>
|