feat: update PDF stamp picker and server CORS settings for enhanced UI rendering

This commit is contained in:
Claudecio Martins
2026-10-11 19:45:30 -03:00
parent b46d8b665f
commit 994a27886e
9 changed files with 440 additions and 123 deletions
+49 -7
View File
@@ -209,6 +209,9 @@ export class PdfSealDesigner {
padding: 20px;
color: #f8fafc;
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
max-width: 100%;
box-sizing: border-box;
overflow: hidden;
}
.designer-header {
margin-bottom: 20px;
@@ -227,12 +230,13 @@ export class PdfSealDesigner {
}
.designer-body {
display: grid;
grid-template-columns: 1fr 1fr;
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
gap: 24px;
min-width: 0;
}
@media (max-width: 860px) {
.designer-body {
grid-template-columns: 1fr;
grid-template-columns: minmax(0, 1fr);
}
}
.form-section-title {
@@ -364,12 +368,17 @@ export class PdfSealDesigner {
display: flex;
flex-direction: column;
gap: 16px;
min-width: 0;
max-width: 100%;
}
.preview-card, .json-export-card {
background: #131c31;
border: 1px solid #24304f;
border-radius: 10px;
padding: 16px;
min-width: 0;
max-width: 100%;
box-sizing: border-box;
}
.preview-card h4, .json-export-card h4 {
font-size: 12px;
@@ -385,9 +394,14 @@ export class PdfSealDesigner {
align-items: center;
justify-content: center;
box-shadow: 0 4px 15px rgba(0,0,0,0.3);
max-width: 340px;
margin: 0 auto;
box-sizing: border-box;
overflow: hidden;
}
.seal-live-preview-box svg {
max-width: 100%;
width: 100%;
height: auto;
display: block;
}
@@ -426,8 +440,22 @@ export class PdfSealDesigner {
font-size: 10.5px;
color: #38bdf8;
max-height: 180px;
overflow: auto;
overflow-y: auto;
overflow-x: hidden;
margin: 0;
white-space: pre-wrap;
word-break: break-all;
word-wrap: break-word;
max-width: 100%;
min-width: 0;
box-sizing: border-box;
}
.theme-json-box code {
white-space: pre-wrap;
word-break: break-all;
word-wrap: break-word;
display: block;
max-width: 100%;
}
`;
document.head.appendChild(style);
@@ -541,7 +569,7 @@ export class PdfSealDesigner {
let iconGraphic = '';
if (t.customLogoBase64) {
iconGraphic = `<image href="${t.customLogoBase64}" x="12" y="14" width="34" height="34" preserveAspectRatio="xMidYMid meet" />`;
iconGraphic = `<image xlink:href="${t.customLogoBase64}" href="${t.customLogoBase64}" x="12" y="14" width="34" height="54" preserveAspectRatio="xMidYMid meet" />`;
} else {
switch (t.iconStyle) {
case 'star':
@@ -578,7 +606,7 @@ export class PdfSealDesigner {
}
const svg = `
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 82" width="320" height="82" style="border-radius:6px; background:${t.backgroundColor};">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 320 82" width="320" height="82" style="border-radius:6px; background:${t.backgroundColor};">
<rect x="1" y="1" width="318" height="80" rx="6" fill="${t.backgroundColor}" stroke="${t.borderColor}" stroke-width="1.5"/>
<rect x="1" y="1" width="6" height="80" rx="3" fill="${t.primaryColor}"/>
@@ -591,13 +619,20 @@ export class PdfSealDesigner {
<text x="54" y="19" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif" font-size="8.5" font-weight="bold" fill="${t.primaryColor}" letter-spacing="0.3">${t.customHeader || 'DOCUMENTO ASSINADO DIGITALMENTE'}</text>
<text x="54" y="34" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif" font-size="12" font-weight="bold" fill="${t.textColor}">MARIA DA SILVA</text>
<text x="54" y="47" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif" font-size="8" fill="#475569">${[docText, extraText].filter(Boolean).join(' • ')}</text>
<text x="54" y="58" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif" font-size="8" fill="#475569">${[dateText, 'AC SERPRO v5'].filter(Boolean).join(' • ')}</text>
<text x="54" y="58" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif" font-size="8" fill="#475569">${dateText ? `Data: ${dateText}` : ''}</text>
<text x="54" y="70" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif" font-size="7.5" fill="#64748b">${t.customFooter || 'ICP-Brasil • MP nº 2.200-2/2001'}</text>
</svg>
`;
this.previewBox.innerHTML = svg;
this.jsonBox.textContent = JSON.stringify(this.getThemeConfig(), null, 2);
// Na visualização do código, trunca se o Base64 for gigante para preservar performance da UI
const displayConfig = { ...this.getThemeConfig() };
if (displayConfig.customLogoBase64 && displayConfig.customLogoBase64.length > 100) {
const lenKb = (displayConfig.customLogoBase64.length / 1024).toFixed(1);
displayConfig.customLogoBase64 = `${displayConfig.customLogoBase64.substring(0, 32)}... [${lenKb} KB]`;
}
this.jsonBox.textContent = JSON.stringify(displayConfig, null, 2);
}
/**
@@ -625,6 +660,13 @@ export class PdfSealDesigner {
};
}
/**
* Alias conveniente para getThemeConfig().
*/
getConfig() {
return this.getThemeConfig();
}
/**
* Carrega um tema para edição.
*/
+234 -71
View File
@@ -25,7 +25,11 @@ export class PdfStampPicker {
this.onPositionChange = options.onPositionChange || null;
this.onSignRequest = options.onSignRequest || null;
this.sealTheme = options.sealTheme || null;
this.sealTheme = options.sealTheme || 'icp-brasil';
this.signerInfo = options.signerInfo || {
signerName: 'Nome do Titular do Certificado',
signerDoc: 'CPF: ***.456.789-**'
};
// Estado interno
this.pdfDocument = null;
@@ -42,8 +46,8 @@ export class PdfStampPicker {
// Estado do selo
this.sealScale = options.initialScale || 1.0;
this.baseSealWidth = 230; // pixels base no visualizador
this.baseSealHeight = 60;
this.baseSealWidth = 240; // Proporção 320x82 pt
this.baseSealHeight = 61.5;
this.sealPosX = null; // pixel relativo ao container da folha
this.sealPosY = null;
this.isDragging = false;
@@ -98,20 +102,9 @@ export class PdfStampPicker {
<div class="picker-sheet-wrapper">
<div class="sheet-container">
<canvas class="pdf-canvas"></canvas>
<!-- Selo Visual Interativo (Drag & Drop) -->
<div class="stamp-overlay" style="display:none;">
<div class="stamp-content">
<div class="stamp-icon-box">
<svg viewBox="0 0 24 24" class="stamp-svg-icon" fill="currentColor">
<path d="M12 1L3 5v6c0 5.55 3.84 10.74 9 12 5.16-1.26 9-6.45 9-12V5l-9-4zm-1 6h2v2h-2V7zm0 4h2v6h-2v-6z"/>
</svg>
</div>
<div class="stamp-info-box">
<div class="stamp-badge-text">ASSINADO DIGITALMENTE • ICP-BRASIL</div>
<div class="stamp-signer-name">NOME DO TITULAR DO CERTIFICADO</div>
<div class="stamp-signer-meta">CPF: ***.456.789-** • VALIDADE JURÍDICA</div>
</div>
</div>
<!-- Selo Visual Interativo Oficial (Drag & Drop) -->
<div class="stamp-overlay" style="display:none;" title="Clique e arraste para posicionar o selo na folha">
<div class="stamp-svg-container"></div>
<div class="stamp-resize-handle" title="Arraste para redimensionar o selo"></div>
</div>
</div>
@@ -208,6 +201,8 @@ export class PdfStampPicker {
this.payloadCode = this.root.querySelector('.payload-code-box code');
this.btnCopyPayload = this.root.querySelector('.btn-copy-payload');
this.btnTriggerSign = this.root.querySelector('.btn-trigger-sign');
this._renderSealGraphic();
}
_injectStyles() {
@@ -291,21 +286,22 @@ export class PdfStampPicker {
}
.picker-body {
display: flex;
min-height: 520px;
height: 600px;
min-height: 560px;
height: 680px;
}
.picker-viewport {
flex: 1;
background: #060911;
overflow: auto;
position: relative;
display: flex;
align-items: center;
justify-content: center;
padding: 24px;
padding: 30px;
display: block;
box-sizing: border-box;
}
.picker-sheet-wrapper {
display: none;
margin: 0 auto;
width: fit-content;
box-shadow: 0 12px 35px rgba(0,0,0,0.65);
border: 1px solid rgba(255,255,255,0.1);
background: #ffffff;
@@ -322,64 +318,53 @@ export class PdfStampPicker {
}
.stamp-overlay {
position: absolute;
width: 230px;
height: 60px;
border: 2px dashed #0284c7;
background: rgba(13, 71, 161, 0.08);
width: 240px;
height: 61.5px;
border: 1.5px solid #0284c7;
background: transparent;
border-radius: 6px;
box-shadow: 0 4px 15px rgba(0,0,0,0.25);
box-shadow: 0 4px 18px rgba(0,0,0,0.3);
cursor: grab;
box-sizing: border-box;
overflow: hidden;
z-index: 20;
user-select: none;
transition: box-shadow 0.2s, border-color 0.2s;
}
.stamp-overlay:hover {
border-color: #38bdf8;
box-shadow: 0 6px 22px rgba(56, 189, 248, 0.4);
}
.stamp-overlay:active {
cursor: grabbing;
border-color: #38bdf8;
box-shadow: 0 6px 20px rgba(56, 189, 248, 0.4);
border-color: #0ea5e9;
box-shadow: 0 8px 26px rgba(14, 165, 233, 0.55);
}
.stamp-content {
.stamp-svg-container {
width: 100%;
height: 100%;
display: flex;
align-items: center;
padding: 4px 8px;
box-sizing: border-box;
gap: 8px;
display: block;
pointer-events: none;
}
.stamp-icon-box {
width: 34px;
height: 34px;
color: #0d47a1;
flex-shrink: 0;
}
.stamp-svg-icon {
.stamp-svg-container svg {
width: 100%;
height: 100%;
display: block;
border-radius: 6px;
}
.stamp-info-box {
display: flex;
flex-direction: column;
justify-content: center;
line-height: 1.15;
.stamp-resize-handle {
position: absolute;
bottom: 0;
right: 0;
width: 14px;
height: 14px;
background: linear-gradient(135deg, transparent 50%, #0284c7 50%);
cursor: se-resize;
z-index: 25;
border-bottom-right-radius: 5px;
}
.stamp-badge-text {
font-size: 7.5px;
font-weight: 800;
color: #0d47a1;
letter-spacing: 0.3px;
}
.stamp-signer-name {
font-size: 10px;
font-weight: 800;
color: #0f172a;
margin: 1px 0;
}
.stamp-signer-meta {
font-size: 7.5px;
color: #475569;
.stamp-resize-handle:hover {
background: linear-gradient(135deg, transparent 50%, #38bdf8 50%);
}
.picker-empty-state {
text-align: center;
@@ -676,14 +661,16 @@ export class PdfStampPicker {
this.pdfBase64 = this._bufferToBase64(arrayBuffer);
}
this.pdfBytes = new Uint8Array(arrayBuffer);
this.pdfBytes = new Uint8Array(arrayBuffer.slice(0));
this.docNameLabel.textContent = this.documentName;
this.docNameLabel.title = this.documentName;
// Garante suporte ao PDF.js
await this._ensurePdfJsLoaded();
const loadingTask = window.pdfjsLib.getDocument({ data: this.pdfBytes });
// Envia uma fatia clonada ao Worker do PDF.js para não descolar (detach) o buffer original
const workerData = new Uint8Array(arrayBuffer.slice(0));
const loadingTask = window.pdfjsLib.getDocument({ data: workerData });
this.pdfDocument = await loadingTask.promise;
this.totalPages = this.pdfDocument.numPages;
this.totalPagesLabel.textContent = this.totalPages;
@@ -740,6 +727,15 @@ export class PdfStampPicker {
const ctx = this.canvas.getContext('2d');
await page.render({ canvasContext: ctx, viewport: viewport }).promise;
// Ajusta o tamanho base do selo (320x82 pt) proporcionalmente aos pixels da tela
const pixelScale = viewport.width / this.pagePdfWidth;
this.baseSealWidth = 320 * pixelScale;
this.baseSealHeight = 82 * pixelScale;
const stampW = this.baseSealWidth * this.sealScale;
const stampH = this.baseSealHeight * this.sealScale;
this.stampOverlay.style.width = `${stampW}px`;
this.stampOverlay.style.height = `${stampH}px`;
this.stampOverlay.style.display = 'block';
// Recalcula limites se já houver posição definida
@@ -881,9 +877,7 @@ export class PdfStampPicker {
scale: Number(this.sealScale.toFixed(2)),
visible_seal: true,
seal_theme_id: this.sealTheme?.id || 'icp-brasil',
seal_custom: this.sealTheme || null,
reason: 'Assinatura Digital ICP-Brasil',
location: 'Brasil',
seal_custom: typeof this.sealTheme === 'object' ? this.sealTheme : null,
pdf: this.pdfBase64 ? `${this.pdfBase64.substring(0, 32)}...[${this.pdfBytes?.length || 0} bytes]` : null
};
}
@@ -904,13 +898,182 @@ export class PdfStampPicker {
scale: Number(this.sealScale.toFixed(2)),
visible_seal: true,
seal_theme_id: this.sealTheme?.id || 'icp-brasil',
seal_custom: this.sealTheme || null,
reason: 'Assinatura Digital ICP-Brasil',
location: 'Brasil',
seal_custom: typeof this.sealTheme === 'object' ? this.sealTheme : null,
pdf: this.pdfBase64
};
}
/**
* Retorna os dados geométricos essenciais do posicionamento do selo.
*/
getStampConfig() {
return {
page: this.currentPageNumber,
x: this.calculatedPdfX,
y: this.calculatedPdfY,
scale: Number(this.sealScale.toFixed(2)),
position: this.selectedPositionEnum
};
}
/**
* Atualiza o tema visual ou os dados do titular estampados no selo em tempo real.
* @param {string|Object} [theme] ID do tema ('icp-brasil', 'gov-br', etc.) ou objeto completo de tema
* @param {Object} [signerInfo] Dados do titular: { signerName, signerDoc }
*/
updateSealTheme(theme, signerInfo) {
if (theme) {
this.sealTheme = theme;
}
if (signerInfo) {
this.signerInfo = { ...(this.signerInfo || {}), ...signerInfo };
}
this._renderSealGraphic();
}
_renderSealGraphic() {
const theme = typeof this.sealTheme === 'object' && this.sealTheme !== null
? this.sealTheme
: this._getPresetTheme(this.sealTheme);
const signer = this.signerInfo || {};
const rawName = signer.signerName || 'Nome do Titular do Certificado';
// Remove sufixo :CPF ou :CNPJ para exibir apenas o nome limpo do titular, idêntico ao assinador Java
const name = rawName.includes(':') ? rawName.split(':')[0].trim() : rawName.trim();
const doc = signer.signerDoc || 'CPF: ***.456.789-**';
const dateText = new Date().toLocaleDateString('pt-BR') + ' ' + new Date().toLocaleTimeString('pt-BR');
// Ajusta o tamanho da fonte proporcionalmente ao comprimento do nome
let nameFontSize = 11.5;
if (name.length > 36) nameFontSize = 8.8;
else if (name.length > 26) nameFontSize = 10.0;
let iconGraphic = '';
if (theme.customLogoBase64) {
iconGraphic = `<image xlink:href="${theme.customLogoBase64}" href="${theme.customLogoBase64}" x="12" y="14" width="34" height="54" preserveAspectRatio="xMidYMid meet" />`;
} else {
switch (theme.iconStyle || theme.id) {
case 'star':
case 'gov-br':
iconGraphic = `
<circle cx="28" cy="30" r="15" fill="${theme.primaryColor}" opacity="0.12" />
<circle cx="28" cy="30" r="15" fill="none" stroke="${theme.primaryColor}" stroke-width="1.5" />
<path d="M28 18 L31 26 L39 26 L33 31 L35 39 L28 34 L21 39 L23 31 L17 26 L25 26 Z" fill="${theme.secondaryColor || '#fbbf24'}" />
`;
break;
case 'crystal':
case 'cybercore-modern':
iconGraphic = `
<polygon points="28,14 42,22 42,38 28,46 14,38 14,22" fill="${theme.primaryColor}" opacity="0.15" />
<polygon points="28,14 42,22 42,38 28,46 14,38 14,22" fill="none" stroke="${theme.primaryColor}" stroke-width="1.5" />
<line x1="28" y1="14" x2="28" y2="46" stroke="${theme.secondaryColor || '#10b981'}" stroke-width="1.5" />
`;
break;
case 'notary':
case 'minimal-clean':
iconGraphic = `
<circle cx="28" cy="30" r="16" fill="none" stroke="${theme.primaryColor}" stroke-width="2" stroke-dasharray="3,2" />
<circle cx="28" cy="30" r="12" fill="${theme.primaryColor}" opacity="0.1" />
<path d="M22 30 L26 34 L34 26" fill="none" stroke="${theme.secondaryColor || '#64748b'}" stroke-width="2" stroke-linecap="round" />
`;
break;
case 'shield':
case 'icp-brasil':
default:
iconGraphic = `
<path d="M28 12 L40 17 V28 C40 38 33 46 28 49 C23 46 16 38 16 28 V17 Z" fill="${theme.primaryColor}" opacity="0.12" />
<path d="M28 12 L40 17 V28 C40 38 33 46 28 49 C23 46 16 38 16 28 V17 Z" fill="none" stroke="${theme.primaryColor}" stroke-width="1.8" />
<rect x="23" y="27" width="10" height="8" rx="2" fill="${theme.secondaryColor || theme.primaryColor}" />
<path d="M25 27 V23.5 C25 21.8 26.3 20.5 28 20.5 C29.7 20.5 31 21.8 31 23.5 V27" fill="none" stroke="${theme.secondaryColor || theme.primaryColor}" stroke-width="1.5" />
`;
break;
}
}
const header = theme.customHeader || (theme.id === 'cybercore-modern' ? 'CYBERCORE SIGNER • ASSINATURA ELETRÔNICA' : (theme.id === 'minimal-clean' ? 'Assinado Digitalmente' : 'DOCUMENTO ASSINADO DIGITALMENTE'));
const footer = theme.customFooter || (theme.id === 'minimal-clean' ? 'Conforme MP 2.200-2/2001 e ICP-Brasil' : 'ICP-Brasil • MP nº 2.200-2/2001');
const svg = `
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 320 82" width="100%" height="100%" style="background:${theme.backgroundColor || '#ffffff'}; border-radius:6px;">
<rect x="1" y="1" width="318" height="80" rx="6" fill="${theme.backgroundColor || '#ffffff'}" stroke="${theme.borderColor || '#cbd5e1'}" stroke-width="1.5"/>
<rect x="1" y="1" width="6" height="80" rx="3" fill="${theme.primaryColor || '#0d47a1'}"/>
<!-- Emblema / Brasão -->
<g id="seal-icon-group">
${iconGraphic}
</g>
<!-- Tipografia do Selo -->
<text x="54" y="19" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif" font-size="8.5" font-weight="bold" fill="${theme.primaryColor || '#0d47a1'}" letter-spacing="0.3">${header}</text>
<text x="54" y="34" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif" font-size="${nameFontSize}" font-weight="bold" fill="${theme.textColor || '#0f172a'}">${name}</text>
<text x="54" y="47" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif" font-size="8" fill="#475569">${doc}</text>
<text x="54" y="58" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif" font-size="8" fill="#475569">Data: ${dateText}</text>
<text x="54" y="70" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif" font-size="7.5" fill="#64748b">${footer}</text>
</svg>
`;
if (this.stampOverlay) {
const container = this.stampOverlay.querySelector('.stamp-svg-container');
if (container) {
container.innerHTML = svg;
}
}
}
_getPresetTheme(themeId) {
switch (themeId) {
case 'gov-br':
return {
id: 'gov-br',
primaryColor: '#005696',
secondaryColor: '#ffcc00',
backgroundColor: '#ffffff',
borderColor: '#93c5fd',
textColor: '#0f172a',
iconStyle: 'star',
customHeader: 'DOCUMENTO ASSINADO DIGITALMENTE • GOV.BR',
customFooter: 'Conforme Decreto nº 10.543/2020 e ICP-Brasil'
};
case 'cybercore-modern':
return {
id: 'cybercore-modern',
primaryColor: '#0284c7',
secondaryColor: '#10b981',
backgroundColor: '#ffffff',
borderColor: '#38bdf8',
textColor: '#0f172a',
iconStyle: 'crystal',
customHeader: 'CYBERCORE SIGNER • ASSINATURA ELETRÔNICA',
customFooter: 'Validação criptográfica com fé pública'
};
case 'minimal-clean':
return {
id: 'minimal-clean',
primaryColor: '#334155',
secondaryColor: '#64748b',
backgroundColor: '#fafafa',
borderColor: '#cbd5e1',
textColor: '#1e293b',
iconStyle: 'notary',
customHeader: 'Assinado Digitalmente',
customFooter: 'Conforme MP 2.200-2/2001 e ICP-Brasil'
};
case 'icp-brasil':
default:
return {
id: 'icp-brasil',
primaryColor: '#0d47a1',
secondaryColor: '#0284c7',
backgroundColor: '#ffffff',
borderColor: '#93c5fd',
textColor: '#0f172a',
iconStyle: 'shield',
customHeader: 'ASSINADO DIGITALMENTE • ICP-BRASIL',
customFooter: 'ICP-Brasil • MP nº 2.200-2/2001'
};
}
}
_bufferToBase64(buffer) {
const bytes = new Uint8Array(buffer);
let binary = '';
+64 -1
View File
@@ -691,8 +691,71 @@ export class CyberCoreSigner {
// Métodos Utilitários Internos
// =========================================================================
/**
* Executa requisições HTTP REST ao servidor local CCSP/1.
*
* @param {string} endpoint Caminho do endpoint (ex: '/ccsp/v1/status').
* @param {Object} [options={}] Opções do fetch (method, body, timeoutMs, etc).
* @returns {Promise<Object>} Resposta JSON decodificada.
*/
async _fetch(endpoint, options = {}) {
const url = `${this.baseUrl}${endpoint}`;
const timeout = options.timeoutMs || 15000;
const controller = typeof AbortController !== 'undefined' ? new AbortController() : null;
const timeoutId = controller ? setTimeout(() => controller.abort(), timeout) : null;
try {
const headers = {
'Accept': 'application/json',
'X-CyberCore-AppId': this.applicationId,
...(options.headers || {})
};
if (options.body && !headers['Content-Type']) {
headers['Content-Type'] = 'application/json';
}
const fetchOptions = {
...options,
headers
};
if (controller) {
fetchOptions.signal = controller.signal;
}
const res = await fetch(url, fetchOptions);
const data = await res.json().catch(() => ({}));
if (!res.ok) {
const errorCode = data.error_code || `HTTP_${res.status}`;
const message = data.message || `Erro retornado pelo agente (${res.status})`;
this._throwMappedError(errorCode, message);
}
return data;
} catch (err) {
if (err.name === 'AbortError') {
throw new CyberCoreError(`Tempo limite esgotado (${timeout}ms) na comunicação com o agente.`, 'TIMEOUT_ERROR');
}
if (err instanceof CyberCoreError) {
throw err;
}
throw new AgentNotRunningError(`Falha ao conectar com o CyberCore Signer em ${this.baseUrl}: ${err.message}`);
} finally {
if (timeoutId) {
clearTimeout(timeoutId);
}
}
}
async _fileToBuffer(file) {
if (file instanceof ArrayBuffer) return file;
if (file instanceof ArrayBuffer) {
if (file.byteLength === 0) {
throw new Error('O buffer binário está vazio ou foi desacoplado (detached). Forneça o objeto File ou Blob original.');
}
return file;
}
if (ArrayBuffer.isView(file)) {
return file.buffer.slice(file.byteOffset, file.byteOffset + file.byteLength);
}