Files
ACORE/static/assets/js/kep-sign.js
T
lokses 03019898b0 reports: КЕП-подпись PDF отчётов; задачи: загрузка вложений.
Добавлена офлайн/EndUser-подпись «ознакомлен и принял в работу» для документов отчётов и файловые вложения к задачам через Nextcloud.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-06 21:13:28 +03:00

176 lines
7.1 KiB
JavaScript

/**
* КЕП-подпись отчётов: офлайн-загрузка + опциональный ІІТ EndUser в браузере.
*
* EndUser (euSign) — проприетарная библиотека ІІТ. Положите её в
* /static/vendor/eusign/ и подключите в layout, либо задайте window.KEP_ENDUSER_READY.
* Без неё работает сценарий: скачать PDF → подписать офлайн → загрузить.
*/
(function () {
function page() {
return document.getElementById('kep-sign-page');
}
function csrfToken() {
var el = document.querySelector('#kep-offline-form input[name=csrfmiddlewaretoken]');
return el ? el.value : '';
}
function setSha(hex) {
var input = document.getElementById('kep-content-sha');
var line = document.getElementById('kep-sha-line');
if (input) input.value = hex || '';
if (line) {
line.textContent = hex
? ('SHA-256: ' + hex)
: '';
}
}
function endUserAvailable() {
if (window.KEP_ENDUSER_READY) return true;
if (typeof window.EndUser !== 'undefined') return true;
if (typeof window.euSign !== 'undefined') return true;
if (typeof window.EUSignCP !== 'undefined') return true;
return false;
}
function updateEndUserUi() {
var status = document.getElementById('kep-enduser-status');
var btn = document.getElementById('kep-browser-sign');
if (!status || !btn) return;
if (endUserAvailable()) {
status.textContent = 'Библиотека EndUser найдена. Можно подписать ключом в браузере.';
btn.disabled = false;
} else {
status.textContent = 'Библиотека EndUser не обнаружена — используйте офлайн-подпись ниже.';
btn.disabled = true;
}
}
/**
* Хук для интеграции ІІТ: задайте window.kepSignWithEndUser = async function(pdfBytes) {
* return { pdf: signedBytes, meta: { subject, serial, issuer, format: 'pades' } };
* }
*/
async function signWithEndUser(pdfBytes) {
if (typeof window.kepSignWithEndUser === 'function') {
return window.kepSignWithEndUser(pdfBytes);
}
throw new Error(
'Подключите ІІТ EndUser и задайте window.kepSignWithEndUser(pdfBytes).'
);
}
async function fetchPreparePdf(url) {
var resp = await fetch(url, { credentials: 'same-origin' });
if (!resp.ok) {
var text = await resp.text();
throw new Error(text || ('HTTP ' + resp.status));
}
var sha = resp.headers.get('X-Content-SHA256') || '';
var buf = await resp.arrayBuffer();
return { bytes: new Uint8Array(buf), sha: sha };
}
async function uploadSigned(completeUrl, pdfBytes, meta, sha) {
var fd = new FormData();
fd.append('csrfmiddlewaretoken', csrfToken());
fd.append('content_sha256', sha || '');
fd.append('cert_meta', JSON.stringify(meta || {}));
if (meta && meta.subject) fd.append('cert_subject', meta.subject);
if (meta && meta.serial) fd.append('cert_serial', meta.serial);
if (meta && meta.issuer) fd.append('cert_issuer', meta.issuer);
var blob = new Blob([pdfBytes], { type: 'application/pdf' });
fd.append('file', blob, 'signed.pdf');
var resp = await fetch(completeUrl, {
method: 'POST',
body: fd,
credentials: 'same-origin',
headers: { 'HX-Request': 'true' },
redirect: 'follow',
});
// complete делает redirect на страницу подписи — перезагрузим фрагмент
if (resp.redirected) {
window.location.href = resp.url;
return;
}
var html = await resp.text();
var target = document.querySelector('#main-content');
if (target && typeof htmx !== 'undefined' && htmx.swap) {
htmx.swap(target, html, { swapStyle: 'innerHTML' });
} else {
window.location.reload();
}
}
document.addEventListener('click', function (e) {
var prep = e.target && e.target.closest ? e.target.closest('[data-kep-prepare]') : null;
if (prep) {
e.preventDefault();
var root = page();
if (!root) return;
var url = root.getAttribute('data-prepare-url');
fetchPreparePdf(url).then(function (r) {
setSha(r.sha);
var blob = new Blob([r.bytes], { type: 'application/pdf' });
var a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = 'report-for-sign.pdf';
document.body.appendChild(a);
a.click();
a.remove();
setTimeout(function () { URL.revokeObjectURL(a.href); }, 2000);
}).catch(function (err) {
var line = document.getElementById('kep-sha-line');
if (line) line.textContent = (err && err.message) ? err.message : 'Не удалось скачать PDF';
});
return;
}
if (e.target && e.target.id === 'kep-browser-sign') {
e.preventDefault();
var root = page();
if (!root) return;
var prepareUrl = root.getAttribute('data-prepare-url');
var completeUrl = root.getAttribute('data-complete-url');
var btn = e.target;
btn.disabled = true;
var status = document.getElementById('kep-enduser-status');
if (status) status.textContent = 'Готовим PDF…';
fetchPreparePdf(prepareUrl)
.then(function (r) {
setSha(r.sha);
if (status) status.textContent = 'Ожидание подписи ключом…';
return signWithEndUser(r.bytes).then(function (out) {
return uploadSigned(
completeUrl,
out.pdf || out.bytes || out,
out.meta || {},
r.sha
);
});
})
.catch(function (err) {
if (status) {
status.textContent = (err && err.message)
? err.message
: 'Не удалось подписать в браузере. Используйте офлайн-загрузку.';
}
btn.disabled = !endUserAvailable();
});
}
});
function boot() {
if (!page()) return;
updateEndUserUi();
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', boot);
} else {
boot();
}
document.addEventListener('htmx:afterSwap', boot);
})();