@mostajs/pdf-sign-ui 0.3.3 → 0.4.0
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/CHANGELOG.md +14 -0
- package/README.md +11 -0
- package/package.json +2 -2
- package/src/html.js +105 -0
- package/src/index.js +2 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,20 @@
|
|
|
4
4
|
|
|
5
5
|
Le format suit [Keep a Changelog](https://keepachangelog.com/fr/) ; versionnage [SemVer](https://semver.org/lang/fr/).
|
|
6
6
|
|
|
7
|
+
|
|
8
|
+
## 0.4.0 — 11/10/2026
|
|
9
|
+
|
|
10
|
+
- **La vue HTML de la vérification d'un PDF, en sept langues** (fr, en, ar, de, es, it, zh) :
|
|
11
|
+
- `verifyFormHtml({ lang, action })` donne le formulaire de dépôt (`pdf`, `motdepasse`) ;
|
|
12
|
+
- `verifyReportHtml(résultat, { lang })` donne le rapport : le fichier, puis chaque signature et chaque
|
|
13
|
+
horodatage de document, avec les dates déclarée et attestée, l'horodateur et le certificat ;
|
|
14
|
+
- `verifyText`, `VERIFY_TEXTS`, `VERIFY_LANGS` donnent les textes ;
|
|
15
|
+
- `VERIFY_CSS` est une feuille minimale.
|
|
16
|
+
- Tout est échappé. « Présente » n'est jamais affiché « valide ».
|
|
17
|
+
- La vue attend le résultat de `verifyPdfBytes` (@mostajs/pdf-sign ≥ 0.7.0).
|
|
18
|
+
- Écrite d'abord dans mdseal (fr, en, ar), demandée par qatrax.dev : elle descend ici (R-G).
|
|
19
|
+
- Essais V-1 à V-4 ; `npm test` lance aussi `html.test.mjs`.
|
|
20
|
+
|
|
7
21
|
## [0.3.3] — 2026-10-07
|
|
8
22
|
|
|
9
23
|
### Modifié
|
package/README.md
CHANGED
|
@@ -90,3 +90,14 @@ Le `service` est le même contrat que le contrôleur (`signPdf`/`verifyPdf`/`det
|
|
|
90
90
|
npm test # logique headless (@mostajs/mjs-unit)
|
|
91
91
|
npm run test:react # rendu SSR des composants (react + react-dom requis)
|
|
92
92
|
```
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
## Vue HTML de la vérification (0.4.0)
|
|
96
|
+
|
|
97
|
+
```js
|
|
98
|
+
import { verifyPdfBytes } from "@mostajs/pdf-sign"; // ≥ 0.7.0
|
|
99
|
+
import { verifyFormHtml, verifyReportHtml, VERIFY_CSS } from "@mostajs/pdf-sign-ui";
|
|
100
|
+
const corps = `<style>${VERIFY_CSS}</style>${verifyFormHtml({ lang: "zh" })}${verifyReportHtml(verifyPdfBytes(octets), { lang: "zh" })}`;
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Sept langues (fr, en, ar, de, es, it, zh) ; fragments sans gabarit, tout échappé.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mostajs/pdf-sign-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Couche de présentation de @mostajs/pdf-sign — logique headless (validation du formulaire de signature, préréglages de documents officiels/SIH, view-models de vérification, contrôleur). Framework-agnostique, zéro dépendance.",
|
|
5
5
|
"license": "AGPL-3.0-or-later",
|
|
6
6
|
"author": "Dr Hamid MADANI <drmdh@msn.com>",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"CHANGELOG.md"
|
|
18
18
|
],
|
|
19
19
|
"scripts": {
|
|
20
|
-
"test": "node test-scripts/headless.test.mjs",
|
|
20
|
+
"test": "node test-scripts/headless.test.mjs && node test-scripts/html.test.mjs",
|
|
21
21
|
"test:react": "node --experimental-loader ./test-scripts/jsx-loader.mjs test-scripts/react.test.mjs",
|
|
22
22
|
"example": "node examples/verify-viewmodel.mjs"
|
|
23
23
|
},
|
package/src/html.js
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// @mostajs/pdf-sign-ui 0.4.0 — LA VUE HTML DE LA VÉRIFICATION D'UN PDF, en sept langues (11/10/2026).
|
|
2
|
+
// Écrite d'abord dans mdseal (page /verifier, 07/10/2026, fr/en/ar), demandée ensuite par qatrax.dev : la vue et ses
|
|
3
|
+
// textes descendent ici (R-G). Fragments HTML sans gabarit : l'application les pose dans sa page. Tout est échappé.
|
|
4
|
+
// Le résultat attendu est celui de `verifyPdfBytes` de @mostajs/pdf-sign ≥ 0.7.0 ({ file, signed, signatures, dss }).
|
|
5
|
+
// Author: Dr Hamid MADANI <drmdh@msn.com> · AGPL-3.0-or-later
|
|
6
|
+
|
|
7
|
+
export const VERIFY_LANGS = Object.freeze(["fr", "en", "ar", "de", "es", "it", "zh"]);
|
|
8
|
+
|
|
9
|
+
/** Les textes, par langue (le français fait référence ; les clés sont celles de mdseal). */
|
|
10
|
+
export const VERIFY_TEXTS = Object.freeze({
|
|
11
|
+
fr: {"titre": "Vérifier un document signé", "aide": "Déposez un PDF : chaque signature est vérifiée, avec ses dates, ses horodatages et ses certificats. Le fichier n’est pas conservé.", "fichier": "Document PDF", "motDePasse": "Mot de passe d’ouverture (si le document est verrouillé)", "bouton": "Vérifier", "fichierInfo": "Fichier vérifié", "taille": "Taille", "octets": "{n} octets", "empreinte": "Empreinte SHA-256", "nonSigne": "Ce document ne porte aucune signature électronique.", "nbSignatures": "Signatures trouvées : {n}", "signature": "Signature {n}", "horodatageDocument": "Horodatage de document {n}", "verdict_confiance": "Intacte, valide, et identité reconnue", "verdict_valide": "Intacte et valide", "verdict_altere": "ALTÉRÉE : le document a été modifié après la signature", "verdict_invalide": "Invalide", "niveau": "Niveau", "nomAffiche": "Nom affiché par le lecteur", "nomAfficheAide": "déclaratif : seul le certificat engage", "motif": "Motif", "lieu": "Lieu", "couverture": "Portée", "couverture_ENTIRE_FILE": "tout le fichier", "couverture_ENTIRE_REVISION": "la révision signée — des ajouts ont suivi", "algorithme": "Algorithme d’empreinte", "format": "Format", "dateDeclaree": "Date déclarée", "dateDeclareeAide": "par le signataire lui-même — sans horodatage, elle peut être antidatée", "dateAttestee": "Date attestée", "dateAttesteeAide": "par un horodateur tiers (RFC 3161)", "aucunHorodatage": "Aucun horodatage : la date de signature n’est attestée par personne.", "horodateur": "Horodateur", "politique": "Politique d’horodatage (OID)", "numeroJeton": "Numéro du jeton", "chaine": "Chaîne embarquée", "jeton": "Jeton", "jetonIntact": "intact et valide", "jetonAltere": "NON valide", "certificat": "Certificat du signataire", "emetteur": "Émetteur", "validite": "Validité", "numero": "Numéro de série", "autoSigne": "auto-signé : personne d’autre que le signataire ne se porte garant de son identité", "confianceNon": "identité non établie par ce vérificateur (chaîne non reconnue)", "confianceOui": "identité reconnue", "erreur": "Motif du refus", "dss": "Données de validation à long terme (DSS)", "oui": "présentes", "non": "absentes", "frein": "Trop de vérifications depuis cette adresse : réessayez dans une heure.", "moteurAbsent": "Vérification complète indisponible sur ce serveur (moteur pyhanko absent).", "pasPdf": "Ce fichier n’est pas un PDF (en-tête %PDF- absent).", "aucunFichier": "Aucun fichier fourni."},
|
|
12
|
+
en: {"titre": "Verify a signed document", "aide": "Upload a PDF: every signature is checked, with its dates, timestamps and certificates. The file is not kept.", "fichier": "PDF document", "motDePasse": "Opening password (if the document is locked)", "bouton": "Verify", "fichierInfo": "File checked", "taille": "Size", "octets": "{n} bytes", "empreinte": "SHA-256 fingerprint", "nonSigne": "This document carries no electronic signature.", "nbSignatures": "Signatures found: {n}", "signature": "Signature {n}", "horodatageDocument": "Document timestamp {n}", "verdict_confiance": "Intact, valid, and identity recognised", "verdict_valide": "Intact and valid", "verdict_altere": "ALTERED: the document was modified after signing", "verdict_invalide": "Invalid", "niveau": "Level", "nomAffiche": "Name shown by the reader", "nomAfficheAide": "declarative: only the certificate binds", "motif": "Reason", "lieu": "Location", "couverture": "Scope", "couverture_ENTIRE_FILE": "the whole file", "couverture_ENTIRE_REVISION": "the signed revision — additions followed", "algorithme": "Digest algorithm", "format": "Format", "dateDeclaree": "Declared date", "dateDeclareeAide": "by the signer themself — without a timestamp, it can be backdated", "dateAttestee": "Attested date", "dateAttesteeAide": "by a third-party timestamping authority (RFC 3161)", "aucunHorodatage": "No timestamp: nobody attests the signing date.", "horodateur": "Timestamping authority", "politique": "Timestamp policy (OID)", "numeroJeton": "Token serial number", "chaine": "Embedded chain", "jeton": "Token", "jetonIntact": "intact and valid", "jetonAltere": "NOT valid", "certificat": "Signer certificate", "emetteur": "Issuer", "validite": "Validity", "numero": "Serial number", "autoSigne": "self-signed: nobody but the signer vouches for their identity", "confianceNon": "identity not established by this verifier (chain not recognised)", "confianceOui": "identity recognised", "erreur": "Reason for rejection", "dss": "Long-term validation data (DSS)", "oui": "present", "non": "absent", "frein": "Too many verifications from this address: try again in an hour.", "moteurAbsent": "Full verification is unavailable on this server (pyhanko engine missing).", "pasPdf": "This file is not a PDF (%PDF- header missing).", "aucunFichier": "No file provided."},
|
|
13
|
+
ar: {"titre": "التحقّق من وثيقة موقَّعة", "aide": "أودِع ملف PDF: يُتحقَّق من كلّ توقيع، مع تواريخه وأختامه الزمنية وشهاداته. لا يُحتفظ بالملف.", "fichier": "وثيقة PDF", "motDePasse": "كلمة مرور الفتح (إن كانت الوثيقة مقفلة)", "bouton": "تحقّق", "fichierInfo": "الملف المُتحقَّق منه", "taille": "الحجم", "octets": "{n} بايت", "empreinte": "بصمة SHA-256", "nonSigne": "لا تحمل هذه الوثيقة أيّ توقيع إلكتروني.", "nbSignatures": "التواقيع الموجودة: {n}", "signature": "التوقيع {n}", "horodatageDocument": "الختم الزمني للوثيقة {n}", "verdict_confiance": "سليم وصالح، والهويّة معترَف بها", "verdict_valide": "سليم وصالح", "verdict_altere": "مُعدَّل: عُدّلت الوثيقة بعد التوقيع", "verdict_invalide": "غير صالح", "niveau": "المستوى", "nomAffiche": "الاسم الذي يعرضه القارئ", "nomAfficheAide": "تصريحي: الشهادة وحدها مُلزِمة", "motif": "السبب", "lieu": "المكان", "couverture": "النطاق", "couverture_ENTIRE_FILE": "الملف كلّه", "couverture_ENTIRE_REVISION": "المراجعة الموقَّعة — تلتها إضافات", "algorithme": "خوارزمية البصمة", "format": "الصيغة", "dateDeclaree": "التاريخ المُصرَّح به", "dateDeclareeAide": "من الموقِّع نفسه — دون ختم زمني يمكن تقديمه", "dateAttestee": "التاريخ المشهود", "dateAttesteeAide": "من جهة ختم زمني مستقلّة (RFC 3161)", "aucunHorodatage": "لا ختم زمني: لا أحد يشهد على تاريخ التوقيع.", "horodateur": "جهة الختم الزمني", "politique": "سياسة الختم الزمني (OID)", "numeroJeton": "رقم الرمز", "chaine": "السلسلة المضمَّنة", "jeton": "الرمز", "jetonIntact": "سليم وصالح", "jetonAltere": "غير صالح", "certificat": "شهادة الموقِّع", "emetteur": "المُصدِر", "validite": "الصلاحية", "numero": "الرقم التسلسلي", "autoSigne": "موقَّعة ذاتيًّا: لا أحد غير الموقِّع يضمن هويّته", "confianceNon": "لم يُثبت هذا المُتحقِّق الهويّة (سلسلة غير معترَف بها)", "confianceOui": "الهويّة معترَف بها", "erreur": "سبب الرفض", "dss": "بيانات التحقّق طويل الأمد (DSS)", "oui": "موجودة", "non": "غائبة", "frein": "تحقّقات كثيرة من هذا العنوان: أعِد المحاولة بعد ساعة.", "moteurAbsent": "التحقّق الكامل غير متاح على هذا الخادم (محرّك pyhanko غائب).", "pasPdf": "هذا الملف ليس PDF (ترويسة %PDF- غائبة).", "aucunFichier": "لم يُقدَّم أيّ ملف."},
|
|
14
|
+
de: {"titre": "Ein signiertes Dokument prüfen", "aide": "Laden Sie ein PDF hoch: Jede Signatur wird geprüft, mit ihren Daten, Zeitstempeln und Zertifikaten. Die Datei wird nicht gespeichert.", "fichier": "PDF-Dokument", "motDePasse": "Öffnungspasswort (falls das Dokument gesperrt ist)", "bouton": "Prüfen", "fichierInfo": "Geprüfte Datei", "taille": "Größe", "octets": "{n} Bytes", "empreinte": "SHA-256-Fingerabdruck", "nonSigne": "Dieses Dokument trägt keine elektronische Signatur.", "nbSignatures": "Gefundene Signaturen: {n}", "signature": "Signatur {n}", "horodatageDocument": "Dokumentzeitstempel {n}", "verdict_confiance": "Unversehrt, gültig, Identität anerkannt", "verdict_valide": "Unversehrt und gültig", "verdict_altere": "VERÄNDERT: Das Dokument wurde nach der Signatur geändert", "verdict_invalide": "Ungültig", "niveau": "Stufe", "nomAffiche": "Vom Leseprogramm angezeigter Name", "nomAfficheAide": "nur deklarativ: verbindlich ist allein das Zertifikat", "motif": "Grund", "lieu": "Ort", "couverture": "Umfang", "couverture_ENTIRE_FILE": "die gesamte Datei", "couverture_ENTIRE_REVISION": "die signierte Revision — danach folgten Ergänzungen", "algorithme": "Hash-Algorithmus", "format": "Format", "dateDeclaree": "Angegebenes Datum", "dateDeclareeAide": "vom Unterzeichner selbst — ohne Zeitstempel kann es rückdatiert sein", "dateAttestee": "Bestätigtes Datum", "dateAttesteeAide": "durch einen unabhängigen Zeitstempeldienst (RFC 3161)", "aucunHorodatage": "Kein Zeitstempel: Niemand bestätigt das Signaturdatum.", "horodateur": "Zeitstempeldienst", "politique": "Zeitstempelrichtlinie (OID)", "numeroJeton": "Seriennummer des Tokens", "chaine": "Eingebettete Kette", "jeton": "Token", "jetonIntact": "unversehrt und gültig", "jetonAltere": "NICHT gültig", "certificat": "Zertifikat des Unterzeichners", "emetteur": "Aussteller", "validite": "Gültigkeit", "numero": "Seriennummer", "autoSigne": "selbstsigniert: Niemand außer dem Unterzeichner bürgt für seine Identität", "confianceNon": "Identität von dieser Prüfung nicht bestätigt (Kette nicht anerkannt)", "confianceOui": "Identität anerkannt", "erreur": "Ablehnungsgrund", "dss": "Daten zur Langzeitvalidierung (DSS)", "oui": "vorhanden", "non": "nicht vorhanden", "frein": "Zu viele Prüfungen von dieser Adresse: Versuchen Sie es in einer Stunde erneut.", "moteurAbsent": "Die vollständige Prüfung ist auf diesem Server nicht verfügbar (pyhanko fehlt).", "pasPdf": "Diese Datei ist kein PDF (Kopfzeile %PDF- fehlt).", "aucunFichier": "Keine Datei übermittelt."},
|
|
15
|
+
es: {"titre": "Verificar un documento firmado", "aide": "Cargue un PDF: se verifica cada firma, con sus fechas, sellos de tiempo y certificados. El archivo no se conserva.", "fichier": "Documento PDF", "motDePasse": "Contraseña de apertura (si el documento está bloqueado)", "bouton": "Verificar", "fichierInfo": "Archivo verificado", "taille": "Tamaño", "octets": "{n} bytes", "empreinte": "Huella SHA-256", "nonSigne": "Este documento no lleva ninguna firma electrónica.", "nbSignatures": "Firmas encontradas: {n}", "signature": "Firma {n}", "horodatageDocument": "Sello de tiempo del documento {n}", "verdict_confiance": "Íntegra, válida e identidad reconocida", "verdict_valide": "Íntegra y válida", "verdict_altere": "ALTERADA: el documento se modificó después de la firma", "verdict_invalide": "No válida", "niveau": "Nivel", "nomAffiche": "Nombre mostrado por el lector", "nomAfficheAide": "declarativo: solo el certificado compromete", "motif": "Motivo", "lieu": "Lugar", "couverture": "Alcance", "couverture_ENTIRE_FILE": "todo el archivo", "couverture_ENTIRE_REVISION": "la revisión firmada — siguieron añadidos", "algorithme": "Algoritmo de huella", "format": "Formato", "dateDeclaree": "Fecha declarada", "dateDeclareeAide": "por el propio firmante — sin sello de tiempo, puede estar antedatada", "dateAttestee": "Fecha acreditada", "dateAttesteeAide": "por una autoridad de sellado de tiempo independiente (RFC 3161)", "aucunHorodatage": "Sin sello de tiempo: nadie acredita la fecha de la firma.", "horodateur": "Autoridad de sellado de tiempo", "politique": "Política de sellado de tiempo (OID)", "numeroJeton": "Número de serie del sello", "chaine": "Cadena incorporada", "jeton": "Sello", "jetonIntact": "íntegro y válido", "jetonAltere": "NO válido", "certificat": "Certificado del firmante", "emetteur": "Emisor", "validite": "Validez", "numero": "Número de serie", "autoSigne": "autofirmado: nadie salvo el firmante responde de su identidad", "confianceNon": "identidad no establecida por este verificador (cadena no reconocida)", "confianceOui": "identidad reconocida", "erreur": "Motivo del rechazo", "dss": "Datos de validación a largo plazo (DSS)", "oui": "presentes", "non": "ausentes", "frein": "Demasiadas verificaciones desde esta dirección: inténtelo de nuevo dentro de una hora.", "moteurAbsent": "La verificación completa no está disponible en este servidor (falta el motor pyhanko).", "pasPdf": "Este archivo no es un PDF (falta la cabecera %PDF-).", "aucunFichier": "No se ha enviado ningún archivo."},
|
|
16
|
+
it: {"titre": "Verificare un documento firmato", "aide": "Carichi un PDF: ogni firma viene verificata, con le sue date, marche temporali e certificati. Il file non viene conservato.", "fichier": "Documento PDF", "motDePasse": "Password di apertura (se il documento è bloccato)", "bouton": "Verifica", "fichierInfo": "File verificato", "taille": "Dimensione", "octets": "{n} byte", "empreinte": "Impronta SHA-256", "nonSigne": "Questo documento non reca alcuna firma elettronica.", "nbSignatures": "Firme trovate: {n}", "signature": "Firma {n}", "horodatageDocument": "Marca temporale del documento {n}", "verdict_confiance": "Integra, valida e identità riconosciuta", "verdict_valide": "Integra e valida", "verdict_altere": "ALTERATA: il documento è stato modificato dopo la firma", "verdict_invalide": "Non valida", "niveau": "Livello", "nomAffiche": "Nome mostrato dal lettore", "nomAfficheAide": "dichiarativo: impegna solo il certificato", "motif": "Motivo", "lieu": "Luogo", "couverture": "Ambito", "couverture_ENTIRE_FILE": "l’intero file", "couverture_ENTIRE_REVISION": "la revisione firmata — sono seguite aggiunte", "algorithme": "Algoritmo di impronta", "format": "Formato", "dateDeclaree": "Data dichiarata", "dateDeclareeAide": "dal firmatario stesso — senza marca temporale può essere retrodatata", "dateAttestee": "Data attestata", "dateAttesteeAide": "da un’autorità di marcatura temporale indipendente (RFC 3161)", "aucunHorodatage": "Nessuna marca temporale: nessuno attesta la data della firma.", "horodateur": "Autorità di marcatura temporale", "politique": "Politica di marcatura temporale (OID)", "numeroJeton": "Numero di serie della marca", "chaine": "Catena incorporata", "jeton": "Marca", "jetonIntact": "integra e valida", "jetonAltere": "NON valida", "certificat": "Certificato del firmatario", "emetteur": "Emittente", "validite": "Validità", "numero": "Numero di serie", "autoSigne": "autofirmato: nessuno oltre al firmatario garantisce la sua identità", "confianceNon": "identità non stabilita da questo verificatore (catena non riconosciuta)", "confianceOui": "identità riconosciuta", "erreur": "Motivo del rifiuto", "dss": "Dati di convalida a lungo termine (DSS)", "oui": "presenti", "non": "assenti", "frein": "Troppe verifiche da questo indirizzo: riprovi tra un’ora.", "moteurAbsent": "La verifica completa non è disponibile su questo server (motore pyhanko assente).", "pasPdf": "Questo file non è un PDF (intestazione %PDF- assente).", "aucunFichier": "Nessun file fornito."},
|
|
17
|
+
zh: {"titre": "核验已签名文件", "aide": "上传一份 PDF:逐一核验每个签名及其日期、时间戳和证书。文件不会被保存。", "fichier": "PDF 文件", "motDePasse": "打开密码(如文件已加密)", "bouton": "核验", "fichierInfo": "已核验的文件", "taille": "大小", "octets": "{n} 字节", "empreinte": "SHA-256 指纹", "nonSigne": "该文件不含任何电子签名。", "nbSignatures": "找到的签名:{n} 个", "signature": "签名 {n}", "horodatageDocument": "文件时间戳 {n}", "verdict_confiance": "完整、有效,且身份已获认可", "verdict_valide": "完整且有效", "verdict_altere": "已被篡改:文件在签名后被修改", "verdict_invalide": "无效", "niveau": "级别", "nomAffiche": "阅读器显示的名称", "nomAfficheAide": "仅为声明:只有证书具有约束力", "motif": "原因", "lieu": "地点", "couverture": "覆盖范围", "couverture_ENTIRE_FILE": "整个文件", "couverture_ENTIRE_REVISION": "已签名的修订版——其后有追加内容", "algorithme": "摘要算法", "format": "格式", "dateDeclaree": "声明日期", "dateDeclareeAide": "由签名人自行声明——没有时间戳时可能被倒签", "dateAttestee": "证明日期", "dateAttesteeAide": "由独立的时间戳机构证明(RFC 3161)", "aucunHorodatage": "无时间戳:签名日期无人证明。", "horodateur": "时间戳机构", "politique": "时间戳策略(OID)", "numeroJeton": "时间戳序列号", "chaine": "内嵌证书链", "jeton": "时间戳", "jetonIntact": "完整且有效", "jetonAltere": "无效", "certificat": "签名人证书", "emetteur": "签发机构", "validite": "有效期", "numero": "序列号", "autoSigne": "自签名:除签名人自己外,无人为其身份担保", "confianceNon": "本核验未能确认身份(证书链未获认可)", "confianceOui": "身份已获认可", "erreur": "拒绝原因", "dss": "长期验证数据(DSS)", "oui": "存在", "non": "不存在", "frein": "该地址的核验次数过多:请一小时后再试。", "moteurAbsent": "本服务器无法进行完整核验(缺少 pyhanko 引擎)。", "pasPdf": "该文件不是 PDF(缺少 %PDF- 文件头)。", "aucunFichier": "未提供文件。"},
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const esc = (s = "") => String(s).replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]);
|
|
21
|
+
|
|
22
|
+
/** Un texte dans la langue (repli : le français), `{x}` rempli. */
|
|
23
|
+
export function verifyText(lang, cle, vars = {}) {
|
|
24
|
+
const d = VERIFY_TEXTS[lang] ?? VERIFY_TEXTS.fr;
|
|
25
|
+
return String(d[cle] ?? VERIFY_TEXTS.fr[cle] ?? cle).replace(/\{(\w+)\}/g, (m, k) => (k in vars ? String(vars[k]) : m));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Le verdict d'une signature : l'intégrité d'abord, la confiance ensuite (même règle que `signatureVerdict` de pdf-sign). */
|
|
29
|
+
const verdictDe = (s) => (s.intact === false ? "altere" : s.intact === true && s.valid === true ? (s.trusted ? "confiance" : "valide") : "invalide");
|
|
30
|
+
const dateUtc = (iso) => (iso ? `${new Date(iso).toISOString().replace("T", " ").replace(/\.\d+Z$/, "")} UTC` : "—");
|
|
31
|
+
const ligne = (libelle, valeur, aide = "") => (valeur == null || valeur === "" ? "" : `<tr><th scope="row">${esc(libelle)}</th><td>${valeur}${aide ? `<br><span class="pdfv-aide">${esc(aide)}</span>` : ""}</td></tr>`);
|
|
32
|
+
|
|
33
|
+
/** Le formulaire de dépôt (multipart : `pdf`, `motdepasse`). */
|
|
34
|
+
export function verifyFormHtml({ lang = "fr", action = "/verifier" } = {}) {
|
|
35
|
+
const t = (k) => esc(verifyText(lang, k));
|
|
36
|
+
return `<form class="pdfv-form" method="post" action="${esc(action)}" enctype="multipart/form-data">
|
|
37
|
+
<fieldset><legend>${t("fichier")}</legend><input type="file" name="pdf" accept=".pdf,application/pdf" required></fieldset>
|
|
38
|
+
<p><label>${t("motDePasse")}<br><input type="password" name="motdepasse" autocomplete="off"></label></p>
|
|
39
|
+
<button type="submit">${t("bouton")}</button>
|
|
40
|
+
</form>`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function blocCertificat(lang, c, trusted) {
|
|
44
|
+
if (!c) return "";
|
|
45
|
+
const t = (k) => verifyText(lang, k);
|
|
46
|
+
return `<table class="pdfv-table"><tbody>
|
|
47
|
+
${ligne(t("certificat"), `<b>${esc(c.subject)}</b>`)}
|
|
48
|
+
${ligne(t("emetteur"), esc(c.issuer ?? ""), c.selfSigned ? t("autoSigne") : "")}
|
|
49
|
+
${ligne(t("validite"), c.notBefore ? `${esc(dateUtc(c.notBefore))} → ${esc(dateUtc(c.notAfter))}` : "")}
|
|
50
|
+
${ligne(t("numero"), c.serial ? `<code>${esc(c.serial)}</code>` : "")}
|
|
51
|
+
${trusted == null ? "" : `<tr><td colspan="2">${esc(t(trusted ? "confianceOui" : "confianceNon"))}</td></tr>`}
|
|
52
|
+
</tbody></table>`;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function blocSignature(lang, s, n) {
|
|
56
|
+
const t = (k, v) => verifyText(lang, k, v);
|
|
57
|
+
const v = verdictDe(s);
|
|
58
|
+
const ts = s.timestamp;
|
|
59
|
+
const couverture = s.coverage ? (VERIFY_TEXTS.fr[`couverture_${s.coverage}`] ? t(`couverture_${s.coverage}`) : s.coverage) : "";
|
|
60
|
+
return `<section class="pdfv-signature pdfv-${v}">
|
|
61
|
+
<h3>${esc(t(s.type === "horodatage-document" ? "horodatageDocument" : "signature", { n }))}</h3>
|
|
62
|
+
<p class="pdfv-verdict">${esc(t(`verdict_${v}`))}</p>
|
|
63
|
+
<table class="pdfv-table"><tbody>
|
|
64
|
+
${ligne(t("niveau"), esc(s.level ?? ""))}
|
|
65
|
+
${ligne(t("nomAffiche"), esc(s.name ?? ""), s.name ? t("nomAfficheAide") : "")}
|
|
66
|
+
${ligne(t("motif"), esc(s.reason ?? ""))}
|
|
67
|
+
${ligne(t("lieu"), esc(s.location ?? ""))}
|
|
68
|
+
${ligne(t("dateDeclaree"), s.claimedSigningTime ? esc(dateUtc(s.claimedSigningTime)) : "", t("dateDeclareeAide"))}
|
|
69
|
+
${ts ? ligne(t("dateAttestee"), `<b>${esc(dateUtc(ts.genTime))}</b>`, t("dateAttesteeAide")) : ""}
|
|
70
|
+
${ligne(t("couverture"), esc(couverture))}
|
|
71
|
+
${ligne(t("algorithme"), esc(s.digestAlgorithm ?? ""))}
|
|
72
|
+
${ligne(t("format"), s.subFilter ? `<code>${esc(s.subFilter)}</code>` : "")}
|
|
73
|
+
${ligne(t("erreur"), esc(s.error ?? ""))}
|
|
74
|
+
</tbody></table>
|
|
75
|
+
${ts ? `<h4>${esc(t("horodateur"))}</h4><table class="pdfv-table"><tbody>
|
|
76
|
+
${ligne(t("horodateur"), `<b>${esc(ts.tsa?.subject ?? "")}</b>`)}
|
|
77
|
+
${ligne(t("emetteur"), esc(ts.tsa?.issuer ?? ""))}
|
|
78
|
+
${ligne(t("politique"), ts.policy ? `<code>${esc(ts.policy)}</code>` : "")}
|
|
79
|
+
${ligne(t("numeroJeton"), ts.serial ? `<code>${esc(ts.serial)}</code>` : "")}
|
|
80
|
+
${ligne(t("jeton"), esc(t(ts.intact && ts.valid ? "jetonIntact" : "jetonAltere")))}
|
|
81
|
+
${ligne(t("chaine"), ts.chain?.length ? ts.chain.map((c) => esc(c)).join("<br>") : "")}
|
|
82
|
+
</tbody></table>` : (s.type === "signature" ? `<p class="pdfv-msg">${esc(t("aucunHorodatage"))}</p>` : "")}
|
|
83
|
+
${s.type === "signature" && s.signerCertificate ? `<h4>${esc(t("certificat"))}</h4>${blocCertificat(lang, s.signerCertificate, s.trusted)}` : ""}
|
|
84
|
+
</section>`;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Le rapport d'une vérification : le fichier, puis chaque signature et chaque horodatage de document. */
|
|
88
|
+
export function verifyReportHtml(r, { lang = "fr" } = {}) {
|
|
89
|
+
if (!r) return "";
|
|
90
|
+
const t = (k, v) => verifyText(lang, k, v);
|
|
91
|
+
const fichier = `<h2>${esc(t("fichierInfo"))}</h2><table class="pdfv-table"><tbody>
|
|
92
|
+
${ligne(t("taille"), esc(t("octets", { n: r.file?.bytes ?? 0 })))}
|
|
93
|
+
${ligne(t("empreinte"), `<code class="pdfv-empreinte">${esc(r.file?.sha256 ?? "")}</code>`)}
|
|
94
|
+
${r.signed ? ligne(t("dss"), esc(t(r.dss ? "oui" : "non"))) : ""}
|
|
95
|
+
</tbody></table>`;
|
|
96
|
+
if (!r.signed) return `<div class="pdfv">${fichier}<p class="pdfv-msg">${esc(t("nonSigne"))}</p></div>`;
|
|
97
|
+
return `<div class="pdfv">${fichier}<p><b>${esc(t("nbSignatures", { n: r.signatures.length }))}</b></p>${r.signatures.map((s, i) => blocSignature(lang, s, i + 1)).join("")}</div>`;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Une feuille de style minimale, à poser une fois dans la page (les couleurs suivent les variables du thème). */
|
|
101
|
+
export const VERIFY_CSS = `.pdfv-table{border-collapse:collapse;width:100%;margin:.4em 0 1em}.pdfv-table th,.pdfv-table td{text-align:start;vertical-align:top;padding:.35em .6em;border-bottom:1px solid var(--line,#e2e8f0)}
|
|
102
|
+
.pdfv-table th{width:34%;font-weight:600}.pdfv-aide{color:var(--doux,#475569);font-size:.9em}.pdfv-empreinte{word-break:break-all}
|
|
103
|
+
.pdfv-signature{border:1px solid var(--line,#e2e8f0);border-radius:8px;padding:.6em 1em;margin:1em 0}.pdfv-verdict{font-weight:700}
|
|
104
|
+
.pdfv-confiance .pdfv-verdict,.pdfv-valide .pdfv-verdict{color:#166534}.pdfv-altere .pdfv-verdict,.pdfv-invalide .pdfv-verdict{color:#991b1b}
|
|
105
|
+
.pdfv-msg{background:var(--fond-doux,#f1f5f9);border-radius:6px;padding:.5em .8em}.pdfv-form fieldset{border:1px solid var(--line,#e2e8f0);border-radius:8px}`;
|
package/src/index.js
CHANGED
|
@@ -8,3 +8,5 @@ export {
|
|
|
8
8
|
createPdfSignUiController,
|
|
9
9
|
} from "./headless.js";
|
|
10
10
|
export { DOCUMENT_PRESETS, listPresets, getPreset } from "./presets.js";
|
|
11
|
+
// 0.4.0 — la vue HTML de la vérification d'un PDF, en sept langues (descendue de mdseal).
|
|
12
|
+
export { VERIFY_LANGS, VERIFY_TEXTS, VERIFY_CSS, verifyText, verifyFormHtml, verifyReportHtml } from "./html.js";
|