feat: implement in-memory log buffer, add ICP-Brasil seal assets, and improve SDK parameter handling

This commit is contained in:
Claudecio Martins
2026-10-11 20:58:49 -03:00
parent 2a50704a7f
commit 5f4a212786
14 changed files with 399 additions and 213 deletions
+82 -29
View File
@@ -107,6 +107,7 @@
letter-spacing: 0.5px;
background: linear-gradient(to right, #ffffff, #cbd5e1);
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
}
@@ -671,11 +672,8 @@
<div class="form-group">
<label class="form-label">Tema do Selo Visual:</label>
<select id="pdfSealThemeSelect" class="cert-bar-select">
<option value="icp-brasil">ICP-Brasil Institucional (Padrão)</option>
<option value="gov-br">Governamental (Gov.br / ITI)</option>
<option value="cybercore-modern">CyberCore Moderno</option>
<option value="minimal-clean">Minimalista Clássico</option>
<option value="custom">🎨 Selo Customizado do Designer</option>
<option value="icp-brasil">Selo Padrão ICP-Brasil (Oficial)</option>
<option value="custom">🎨 Selo Personalizado do Designer</option>
</select>
</div>
</div>
@@ -1031,7 +1029,31 @@
certs.forEach((c) => {
const opt = document.createElement('option');
opt.value = c.id;
opt.textContent = `${c.common_name || c.alias} [${c.type}] (${c.device_name || c.provider_id})`;
let rawName = c.common_name || c.alias || 'Certificado';
let displayName = rawName;
let docNum = c.cpf || c.cnpj || c.cpf_cnpj || '';
if (rawName.includes(':')) {
const parts = rawName.split(':');
displayName = parts[0].trim();
docNum = parts[1].trim();
}
}
const digits = docNum ? String(docNum).replace(/\D/g, '') : '';
let docFormatted = '';
if (digits.length === 14) {
docFormatted = `CNPJ ${digits.replace(/^(\d{2})(\d{3})(\d{3})(\d{4})(\d{2})$/, '../-')}`;
} else if (digits.length === 11) {
docFormatted = `CPF ${digits.replace(/^(\d{3})(\d{3})(\d{3})(\d{2})$/, '..-')}`;
} else if (docNum) {
docFormatted = docNum;
}
const typeStr = `[${c.type || 'A1'}]`;
const text = docFormatted ? `${displayName}, ${docFormatted} ${typeStr}` : `${displayName} ${typeStr}`;
opt.textContent = text;
certSelect.appendChild(opt);
});
@@ -1054,9 +1076,25 @@
certValidityText.textContent = `Validade: ${cert.valid_to ? new Date(cert.valid_to).toLocaleDateString() : 'N/D'}`;
if (pdfStampPicker) {
const rawDoc = cert.cpf || cert.cnpj || cert.cpf_cnpj || '';
const digits = String(rawDoc).replace(/\D/g, '');
let docStr = '';
if (digits.length === 14) {
docStr = `CNPJ: ${digits.replace(/^(\d{2})(\d{3})(\d{3})(\d{4})(\d{2})$/, '../-')}`;
} else if (digits.length === 11) {
docStr = `CPF: ${digits.replace(/^(\d{3})(\d{3})(\d{3})(\d{2})$/, '..-')}`;
} else {
docStr = cert.type === 'A3' ? 'Token Físico A3' : 'ICP-Brasil';
}
let cleanSignerName = cert.common_name || cert.alias || 'Titular';
if (cleanSignerName && cleanSignerName.includes(':')) {
cleanSignerName = cleanSignerName.split(':')[0].trim();
}
pdfStampPicker.updateSealTheme(null, {
signerName: cert.common_name || cert.alias,
signerDoc: cert.cpf_cnpj ? `CPF: ${cert.cpf_cnpj}` : (cert.type === 'A3' ? 'Token Físico A3' : 'ICP-Brasil')
signerName: cleanSignerName,
signerDoc: docStr
});
}
} else {
@@ -1209,10 +1247,15 @@
// Sincroniza alteração de tema com o selo sobre o PDF
pdfSealThemeSelect.addEventListener('change', () => {
const themeVal = pdfSealThemeSelect.value;
if (pdfStampPicker) {
if (themeVal === 'custom' && currentCustomSealConfig) {
if (themeVal === 'custom') {
if (!currentCustomSealConfig && typeof pdfSealDesigner !== 'undefined' && pdfSealDesigner) {
currentCustomSealConfig = pdfSealDesigner.getConfig();
}
if (pdfStampPicker && currentCustomSealConfig) {
pdfStampPicker.updateSealTheme(currentCustomSealConfig);
} else {
}
} else {
if (pdfStampPicker) {
pdfStampPicker.updateSealTheme(themeVal);
}
}
@@ -1230,21 +1273,29 @@
const stampConfig = pdfStampPicker ? pdfStampPicker.getStampConfig() : { page: 1, x: 50, y: 50, scale: 1.0 };
const selectedTheme = pdfSealThemeSelect.value;
let effectiveCustomSeal = null;
if (selectedTheme === 'custom') {
if (currentCustomSealConfig) {
effectiveCustomSeal = currentCustomSealConfig;
} else if (typeof pdfSealDesigner !== 'undefined' && pdfSealDesigner) {
effectiveCustomSeal = pdfSealDesigner.getConfig();
currentCustomSealConfig = effectiveCustomSeal;
}
}
const signParams = {
certificateId: selectedCertificateId,
pdf: currentPdfFile || currentPdfBytes,
page: stampConfig.page,
x: stampConfig.x,
y: stampConfig.y,
scale: stampConfig.scale
scale: stampConfig.scale,
sealCustom: effectiveCustomSeal,
seal_custom: effectiveCustomSeal,
sealThemeId: (selectedTheme === 'custom' ? null : selectedTheme),
seal_theme_id: (selectedTheme === 'custom' ? null : selectedTheme)
};
if (selectedTheme === 'custom' && currentCustomSealConfig) {
signParams.seal_custom = currentCustomSealConfig;
} else {
signParams.seal_theme_id = selectedTheme === 'custom' ? 'icp-brasil' : selectedTheme;
}
const result = await signer.signPdfDocument(signParams);
pdfAlert.className = 'alert alert-success';
@@ -1322,22 +1373,24 @@
const mount = document.getElementById('sealDesignerMount');
pdfSealDesigner = new PdfSealDesigner({
container: mount,
initialConfig: {
initialTheme: {
name: 'Selo Customizado',
title: 'ASSINADO DIGITALMENTE',
primary_color: '#0284c7',
secondary_color: '#10b981',
background_color: '#ffffff',
text_color: '#0f172a',
border_color: '#38bdf8',
icon_style: 'shield',
default_cpf_mode: 'MASKED',
default_include_date_time: true
customHeader: 'DOCUMENTO ASSINADO DIGITALMENTE',
primaryColor: '#0284c7',
secondaryColor: '#10b981',
backgroundColor: '#ffffff',
textColor: '#0f172a',
borderColor: '#38bdf8',
iconStyle: 'shield',
defaultCpfMode: 'MASKED',
defaultIncludeDateTime: true
},
onConfigChange: (cfg) => {
onChange: (cfg) => {
currentCustomSealConfig = cfg;
}
});
// Inicializa currentCustomSealConfig com o estado atual do designer
currentCustomSealConfig = pdfSealDesigner.getConfig();
// Botão para aplicar o selo na aba de PDF
const applyBtnWrap = document.createElement('div');
+28 -4
View File
@@ -20,7 +20,31 @@ export class PdfSealDesigner {
throw new Error('Container DOM obrigatório para instanciar PdfSealDesigner.');
}
this.onChange = options.onChange || null;
this.onChange = options.onChange || options.onConfigChange || null;
const rawInitial = options.initialTheme || options.initialConfig || {};
const normalizedInitial = {
name: rawInitial.name,
description: rawInitial.description,
width: rawInitial.width,
height: rawInitial.height,
primaryColor: rawInitial.primaryColor || rawInitial.primary_color,
secondaryColor: rawInitial.secondaryColor || rawInitial.secondary_color,
backgroundColor: rawInitial.backgroundColor || rawInitial.background_color,
borderColor: rawInitial.borderColor || rawInitial.border_color,
textColor: rawInitial.textColor || rawInitial.text_color,
iconStyle: rawInitial.iconStyle || rawInitial.icon_style,
customLogoBase64: rawInitial.customLogoBase64 || rawInitial.custom_logo_base64,
customHeader: rawInitial.customHeader || rawInitial.custom_header || rawInitial.title,
customFooter: rawInitial.customFooter || rawInitial.custom_footer,
customFieldLabel: rawInitial.customFieldLabel || rawInitial.custom_field_label,
customFieldValue: rawInitial.customFieldValue || rawInitial.custom_field_value,
defaultCpfMode: rawInitial.defaultCpfMode || rawInitial.default_cpf_mode,
defaultIncludeDateTime: rawInitial.defaultIncludeDateTime !== undefined ? rawInitial.defaultIncludeDateTime : rawInitial.default_include_date_time
};
// Remove chaves indefinidas para não sobrescrever os defaults
Object.keys(normalizedInitial).forEach(k => normalizedInitial[k] === undefined && delete normalizedInitial[k]);
// Estado do tema
this.theme = {
@@ -37,11 +61,11 @@ export class PdfSealDesigner {
customLogoBase64: null,
customHeader: 'DOCUMENTO ASSINADO DIGITALMENTE',
customFooter: 'Verifique a autenticidade da assinatura eletrônica em https://validar.iti.gov.br',
customFieldLabel: 'Cargo',
customFieldValue: 'Diretor Responsável',
customFieldLabel: '',
customFieldValue: '',
defaultCpfMode: 'MASKED',
defaultIncludeDateTime: true,
...(options.initialTheme || {})
...normalizedInitial
};
this._initDom();
+5 -5
View File
@@ -367,13 +367,13 @@ export class CyberCoreSigner {
y: params.y !== undefined ? Number(params.y) : null,
scale: params.scale !== undefined ? Number(params.scale) : 1.0,
visible_seal: params.visibleSeal !== undefined ? Boolean(params.visibleSeal) : true,
seal_theme_id: params.sealThemeId || params.sealTheme || null,
seal_custom: params.sealCustom || null,
seal_theme_id: params.sealThemeId || params.seal_theme_id || params.sealTheme || null,
seal_custom: params.sealCustom || params.seal_custom || null,
reason: params.reason || 'Assinatura Digital ICP-Brasil',
location: params.location || 'Brasil',
cpf_display_mode: params.cpfDisplayMode || null,
include_date_time: params.includeDateTime !== undefined ? Boolean(params.includeDateTime) : null,
custom_extra_info: params.customExtraInfo || null,
cpf_display_mode: params.cpfDisplayMode || params.cpf_display_mode || null,
include_date_time: params.includeDateTime !== undefined ? Boolean(params.includeDateTime) : (params.include_date_time !== undefined ? Boolean(params.include_date_time) : null),
custom_extra_info: params.customExtraInfo || params.custom_extra_info || null,
nonce: nonce,
expires_at: expiresAt
};