feat(sdk): add typescript types actionable errors and mock server tests
This commit is contained in:
+18
-1
@@ -5,8 +5,25 @@
|
|||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/signer-sdk.js",
|
"main": "src/signer-sdk.js",
|
||||||
"module": "src/signer-sdk.js",
|
"module": "src/signer-sdk.js",
|
||||||
|
"types": "src/signer-sdk.d.ts",
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"types": "./src/signer-sdk.d.ts",
|
||||||
|
"import": "./src/signer-sdk.js",
|
||||||
|
"default": "./src/signer-sdk.js"
|
||||||
|
},
|
||||||
|
"./errors": {
|
||||||
|
"types": "./src/errors.d.ts",
|
||||||
|
"import": "./src/errors.js",
|
||||||
|
"default": "./src/errors.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"src",
|
||||||
|
"README.md"
|
||||||
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "node --test test/signer-sdk.test.js"
|
"test": "node --test test/*.test.js"
|
||||||
},
|
},
|
||||||
"author": "CyberCore",
|
"author": "CyberCore",
|
||||||
"license": "Proprietary",
|
"license": "Proprietary",
|
||||||
|
|||||||
Vendored
+114
@@ -0,0 +1,114 @@
|
|||||||
|
/**
|
||||||
|
* Definições TypeScript para os erros estruturados do protocolo CCSP/1 (@cybercore/signer-sdk).
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type CcspErrorCode =
|
||||||
|
| 'AGENT_NOT_RUNNING'
|
||||||
|
| 'COMMUNICATION_ERROR'
|
||||||
|
| 'APPLICATION_NOT_AUTHORIZED'
|
||||||
|
| 'USER_CANCELLED'
|
||||||
|
| 'CERTIFICATE_NOT_FOUND'
|
||||||
|
| 'CERTIFICATE_EXPIRED'
|
||||||
|
| 'CERTIFICATE_INVALID'
|
||||||
|
| 'PRIVATE_KEY_NOT_AVAILABLE'
|
||||||
|
| 'PIN_REQUIRED'
|
||||||
|
| 'PIN_INCORRECT'
|
||||||
|
| 'TOKEN_NOT_FOUND'
|
||||||
|
| 'TOKEN_REMOVED'
|
||||||
|
| 'PKCS11_ERROR'
|
||||||
|
| 'REQUEST_EXPIRED'
|
||||||
|
| 'REPLAY_DETECTED'
|
||||||
|
| 'INVALID_ORIGIN'
|
||||||
|
| 'UNSUPPORTED_ALGORITHM'
|
||||||
|
| 'PROVIDER_NOT_AVAILABLE'
|
||||||
|
| 'SIGNATURE_FAILED'
|
||||||
|
| 'TIMEOUT_ERROR'
|
||||||
|
| 'BAD_REQUEST'
|
||||||
|
| 'SIGN_ERROR'
|
||||||
|
| 'SIGN_PDF_ERROR'
|
||||||
|
| 'SIGN_XML_ERROR'
|
||||||
|
| 'AUTH_ERROR'
|
||||||
|
| 'CONNECTION_FAILED'
|
||||||
|
| string;
|
||||||
|
|
||||||
|
export class CyberCoreError extends Error {
|
||||||
|
code: CcspErrorCode;
|
||||||
|
originalError?: unknown;
|
||||||
|
constructor(message: string, code?: CcspErrorCode, originalError?: unknown);
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AgentNotRunningOptions {
|
||||||
|
host?: string;
|
||||||
|
port?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AgentNotRunningError extends CyberCoreError {
|
||||||
|
host: string;
|
||||||
|
port: number;
|
||||||
|
actionableGuidance: string;
|
||||||
|
constructor(message?: string, options?: AgentNotRunningOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ApplicationNotAuthorizedError extends CyberCoreError {
|
||||||
|
constructor(message?: string);
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UserCancelledError extends CyberCoreError {
|
||||||
|
constructor(message?: string);
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CertificateNotFoundError extends CyberCoreError {
|
||||||
|
constructor(message?: string);
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CertificateExpiredError extends CyberCoreError {
|
||||||
|
constructor(message?: string);
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CertificateInvalidError extends CyberCoreError {
|
||||||
|
constructor(message?: string);
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PrivateKeyNotAvailableError extends CyberCoreError {
|
||||||
|
constructor(message?: string);
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PinRequiredError extends CyberCoreError {
|
||||||
|
constructor(message?: string);
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PinIncorrectError extends CyberCoreError {
|
||||||
|
constructor(message?: string);
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TokenNotFoundError extends CyberCoreError {
|
||||||
|
constructor(message?: string);
|
||||||
|
}
|
||||||
|
|
||||||
|
export class RequestExpiredError extends CyberCoreError {
|
||||||
|
constructor(message?: string);
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ReplayDetectedError extends CyberCoreError {
|
||||||
|
constructor(message?: string);
|
||||||
|
}
|
||||||
|
|
||||||
|
export class InvalidOriginError extends CyberCoreError {
|
||||||
|
constructor(message?: string);
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UnsupportedAlgorithmError extends CyberCoreError {
|
||||||
|
constructor(message?: string);
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ProviderNotAvailableError extends CyberCoreError {
|
||||||
|
constructor(message?: string);
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SignatureFailedError extends CyberCoreError {
|
||||||
|
constructor(message?: string);
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TimeoutError extends CyberCoreError {
|
||||||
|
constructor(message?: string);
|
||||||
|
}
|
||||||
+72
-6
@@ -12,9 +12,19 @@ export class CyberCoreError extends Error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class AgentNotRunningError extends CyberCoreError {
|
export class AgentNotRunningError extends CyberCoreError {
|
||||||
constructor(message = 'O agente desktop CyberCore Signer não está em execução ou não foi detectado em 127.0.0.1:21325.') {
|
constructor(
|
||||||
|
message = 'O agente desktop CyberCore Signer não está em execução ou não foi detectado em 127.0.0.1:21325. Certifique-se de que o aplicativo está instalado e aberto na bandeja do sistema (system tray).',
|
||||||
|
options = {}
|
||||||
|
) {
|
||||||
super(message, 'AGENT_NOT_RUNNING');
|
super(message, 'AGENT_NOT_RUNNING');
|
||||||
this.name = 'AgentNotRunningError';
|
this.name = 'AgentNotRunningError';
|
||||||
|
this.host = options.host || '127.0.0.1';
|
||||||
|
this.port = options.port || 21325;
|
||||||
|
this.actionableGuidance = [
|
||||||
|
'1. Verifique se o ícone do CyberCore Signer está ativo na barra de tarefas / bandeja do sistema.',
|
||||||
|
'2. Se o aplicativo ainda não estiver instalado, baixe e execute o instalador oficial do CyberCore Signer.',
|
||||||
|
'3. Certifique-se de que a porta local 21325 não está bloqueada por firewall ou softwares antivírus.'
|
||||||
|
].join('\n');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,21 +36,42 @@ export class ApplicationNotAuthorizedError extends CyberCoreError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class UserCancelledError extends CyberCoreError {
|
export class UserCancelledError extends CyberCoreError {
|
||||||
constructor(message = 'A operação de assinatura foi cancelada pelo usuário.') {
|
constructor(message = 'A operação de assinatura foi cancelada pelo usuário no diálogo nativo.') {
|
||||||
super(message, 'USER_CANCELLED');
|
super(message, 'USER_CANCELLED');
|
||||||
this.name = 'UserCancelledError';
|
this.name = 'UserCancelledError';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CertificateNotFoundError extends CyberCoreError {
|
export class CertificateNotFoundError extends CyberCoreError {
|
||||||
constructor(message = 'Certificado digital não encontrado ou não selecionado.') {
|
constructor(message = 'Certificado digital não encontrado ou não selecionado no agente.') {
|
||||||
super(message, 'CERTIFICATE_NOT_FOUND');
|
super(message, 'CERTIFICATE_NOT_FOUND');
|
||||||
this.name = 'CertificateNotFoundError';
|
this.name = 'CertificateNotFoundError';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class CertificateExpiredError extends CyberCoreError {
|
||||||
|
constructor(message = 'O certificado digital selecionado expirou e não pode ser utilizado para assinatura.') {
|
||||||
|
super(message, 'CERTIFICATE_EXPIRED');
|
||||||
|
this.name = 'CertificateExpiredError';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CertificateInvalidError extends CyberCoreError {
|
||||||
|
constructor(message = 'O certificado digital é inválido, foi revogado ou possui estrutura X.509 corrompida.') {
|
||||||
|
super(message, 'CERTIFICATE_INVALID');
|
||||||
|
this.name = 'CertificateInvalidError';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PrivateKeyNotAvailableError extends CyberCoreError {
|
||||||
|
constructor(message = 'A chave privada do certificado não está acessível no computador ou o dispositivo físico está ausente.') {
|
||||||
|
super(message, 'PRIVATE_KEY_NOT_AVAILABLE');
|
||||||
|
this.name = 'PrivateKeyNotAvailableError';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class PinRequiredError extends CyberCoreError {
|
export class PinRequiredError extends CyberCoreError {
|
||||||
constructor(message = 'PIN do token ou smart card A3 é obrigatório.') {
|
constructor(message = 'O PIN do token USB ou smart card A3 é obrigatório para autorizar a chave privada.') {
|
||||||
super(message, 'PIN_REQUIRED');
|
super(message, 'PIN_REQUIRED');
|
||||||
this.name = 'PinRequiredError';
|
this.name = 'PinRequiredError';
|
||||||
}
|
}
|
||||||
@@ -54,14 +85,14 @@ export class PinIncorrectError extends CyberCoreError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class TokenNotFoundError extends CyberCoreError {
|
export class TokenNotFoundError extends CyberCoreError {
|
||||||
constructor(message = 'Token USB ou smart card não foi detectado no computador.') {
|
constructor(message = 'Token USB ou smart card não foi detectado no computador ou foi removido.') {
|
||||||
super(message, 'TOKEN_NOT_FOUND');
|
super(message, 'TOKEN_NOT_FOUND');
|
||||||
this.name = 'TokenNotFoundError';
|
this.name = 'TokenNotFoundError';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class RequestExpiredError extends CyberCoreError {
|
export class RequestExpiredError extends CyberCoreError {
|
||||||
constructor(message = 'A solicitação de assinatura expirou.') {
|
constructor(message = 'A solicitação de assinatura expirou antes de ser processada.') {
|
||||||
super(message, 'REQUEST_EXPIRED');
|
super(message, 'REQUEST_EXPIRED');
|
||||||
this.name = 'RequestExpiredError';
|
this.name = 'RequestExpiredError';
|
||||||
}
|
}
|
||||||
@@ -73,3 +104,38 @@ export class ReplayDetectedError extends CyberCoreError {
|
|||||||
this.name = 'ReplayDetectedError';
|
this.name = 'ReplayDetectedError';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class InvalidOriginError extends CyberCoreError {
|
||||||
|
constructor(message = 'A origem web solicitante não confere com os domínios homologados para esta aplicação.') {
|
||||||
|
super(message, 'INVALID_ORIGIN');
|
||||||
|
this.name = 'InvalidOriginError';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UnsupportedAlgorithmError extends CyberCoreError {
|
||||||
|
constructor(message = 'O algoritmo criptográfico solicitado não é suportado pelo provedor de certificados.') {
|
||||||
|
super(message, 'UNSUPPORTED_ALGORITHM');
|
||||||
|
this.name = 'UnsupportedAlgorithmError';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ProviderNotAvailableError extends CyberCoreError {
|
||||||
|
constructor(message = 'O provedor criptográfico necessário não está disponível no sistema operacional.') {
|
||||||
|
super(message, 'PROVIDER_NOT_AVAILABLE');
|
||||||
|
this.name = 'ProviderNotAvailableError';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SignatureFailedError extends CyberCoreError {
|
||||||
|
constructor(message = 'Falha durante o cálculo da assinatura digital.') {
|
||||||
|
super(message, 'SIGNATURE_FAILED');
|
||||||
|
this.name = 'SignatureFailedError';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TimeoutError extends CyberCoreError {
|
||||||
|
constructor(message = 'Tempo limite esgotado na comunicação com o agente local.') {
|
||||||
|
super(message, 'TIMEOUT_ERROR');
|
||||||
|
this.name = 'TimeoutError';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Vendored
+2
@@ -0,0 +1,2 @@
|
|||||||
|
export * from './signer-sdk.js';
|
||||||
|
export { default } from './signer-sdk.js';
|
||||||
Vendored
+246
@@ -0,0 +1,246 @@
|
|||||||
|
/**
|
||||||
|
* CyberCore Signer Web SDK (@cybercore/signer-sdk)
|
||||||
|
* Protocolo: CCSP/1 (CyberCore Signing Protocol v1)
|
||||||
|
*/
|
||||||
|
|
||||||
|
export * from './errors.js';
|
||||||
|
|
||||||
|
export interface CyberCoreSignerOptions {
|
||||||
|
/** URL base completa do agente local (prevalece sobre host e port). Ex: 'http://127.0.0.1:21325' */
|
||||||
|
baseUrl?: string;
|
||||||
|
/** Host do agente local (padrão: '127.0.0.1') */
|
||||||
|
host?: string;
|
||||||
|
/** Porta HTTP do agente local (padrão: 21325) */
|
||||||
|
port?: number;
|
||||||
|
/** Timeout padrão para requisições em milissegundos (padrão: 30000) */
|
||||||
|
timeoutMs?: number;
|
||||||
|
/** UUID persistido da aplicação web */
|
||||||
|
applicationId?: string;
|
||||||
|
/** Nome legível da aplicação exibido na confirmação de assinatura */
|
||||||
|
applicationName?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface StatusResponse {
|
||||||
|
version: string;
|
||||||
|
status: string;
|
||||||
|
agent_version?: string;
|
||||||
|
os?: string;
|
||||||
|
ready?: boolean;
|
||||||
|
[key: string]: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface VersionInfo {
|
||||||
|
protocolVersion: string;
|
||||||
|
agentVersion: string;
|
||||||
|
os: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CapabilitiesResponse {
|
||||||
|
protocolVersion: string;
|
||||||
|
supportedAlgorithms: string[];
|
||||||
|
supportedHashAlgorithms: string[];
|
||||||
|
certificateTypes: ('A1' | 'A3')[];
|
||||||
|
hardwareSupport: string[];
|
||||||
|
padesSupport: boolean;
|
||||||
|
[key: string]: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CertificateDto {
|
||||||
|
id: string;
|
||||||
|
alias?: string;
|
||||||
|
subject_name: string;
|
||||||
|
cpf_cnpj?: string;
|
||||||
|
issuer_name?: string;
|
||||||
|
serial_number?: string;
|
||||||
|
valid_from?: string;
|
||||||
|
valid_to?: string;
|
||||||
|
certificate_type: 'A1' | 'A3';
|
||||||
|
has_private_key: boolean;
|
||||||
|
key_algorithm?: string;
|
||||||
|
provider_type?: string;
|
||||||
|
[key: string]: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SignParams {
|
||||||
|
certificateId?: string;
|
||||||
|
certificateHint?: string;
|
||||||
|
digest?: string;
|
||||||
|
data?: string | Uint8Array | ArrayBuffer;
|
||||||
|
algorithm?: 'SHA256withRSA' | 'SHA1withRSA' | string;
|
||||||
|
operation?: 'sign' | string;
|
||||||
|
documentName?: string;
|
||||||
|
fileName?: string;
|
||||||
|
documentType?: string;
|
||||||
|
fileType?: string;
|
||||||
|
purpose?: string;
|
||||||
|
expiresInSeconds?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SignResult {
|
||||||
|
requestId: string;
|
||||||
|
status: 'signed' | 'authenticated';
|
||||||
|
signature: string;
|
||||||
|
certificate: string;
|
||||||
|
certificateChain: string[];
|
||||||
|
algorithm: string;
|
||||||
|
digest: string;
|
||||||
|
subjectName: string | null;
|
||||||
|
subjectCpfCnpj: string | null;
|
||||||
|
documentName: string | null;
|
||||||
|
documentType: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SignPdfDocumentParams {
|
||||||
|
pdf: string | File | Blob | ArrayBuffer | Uint8Array;
|
||||||
|
certificateId?: string;
|
||||||
|
certificateHint?: string;
|
||||||
|
documentName?: string;
|
||||||
|
fileName?: string;
|
||||||
|
page?: number;
|
||||||
|
pageChoice?: 'LAST_PAGE' | 'FIRST_PAGE' | null;
|
||||||
|
position?: 'BOTTOM_RIGHT' | 'BOTTOM_LEFT' | 'TOP_RIGHT' | 'TOP_LEFT' | 'CUSTOM' | string;
|
||||||
|
x?: number;
|
||||||
|
y?: number;
|
||||||
|
scale?: number;
|
||||||
|
visibleSeal?: boolean;
|
||||||
|
sealThemeId?: string;
|
||||||
|
sealCustom?: Record<string, unknown>;
|
||||||
|
reason?: string;
|
||||||
|
location?: string;
|
||||||
|
cpfDisplayMode?: 'FULL' | 'MASKED' | 'NONE';
|
||||||
|
includeDateTime?: boolean;
|
||||||
|
customExtraInfo?: string;
|
||||||
|
expiresInSeconds?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SignPdfDocumentResult {
|
||||||
|
requestId: string;
|
||||||
|
status: 'signed';
|
||||||
|
signedPdf: string;
|
||||||
|
signed_pdf: string;
|
||||||
|
sha256?: string;
|
||||||
|
signature?: string;
|
||||||
|
certificate: string;
|
||||||
|
certificateChain: string[];
|
||||||
|
algorithm: string;
|
||||||
|
subjectName: string | null;
|
||||||
|
subjectCpfCnpj: string | null;
|
||||||
|
documentName: string;
|
||||||
|
getSignedPdfBlob(): Blob;
|
||||||
|
getSignedPdfBytes(): Uint8Array;
|
||||||
|
download(filename?: string): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SignXmlDocumentParams {
|
||||||
|
xml: string | File | Blob | ArrayBuffer;
|
||||||
|
certificateId?: string;
|
||||||
|
certificateHint?: string;
|
||||||
|
profile?: 'XADES_BES' | 'XMLDSIG' | string;
|
||||||
|
targetElementTag?: string;
|
||||||
|
targetElementId?: string;
|
||||||
|
documentName?: string;
|
||||||
|
fileName?: string;
|
||||||
|
algorithm?: string;
|
||||||
|
expiresInSeconds?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SignXmlDocumentResult {
|
||||||
|
requestId: string;
|
||||||
|
status: 'signed';
|
||||||
|
signedXml: string;
|
||||||
|
sha256?: string;
|
||||||
|
signature?: string;
|
||||||
|
certificate: string;
|
||||||
|
certificateChain: string[];
|
||||||
|
algorithm: string;
|
||||||
|
subjectName: string | null;
|
||||||
|
subjectCpfCnpj: string | null;
|
||||||
|
documentName: string;
|
||||||
|
getSignedXmlBlob(): Blob;
|
||||||
|
getSignedXmlBytes(): Uint8Array;
|
||||||
|
download(filename?: string): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AuthenticateParams {
|
||||||
|
certificateId?: string;
|
||||||
|
certificateHint?: string;
|
||||||
|
challenge?: string;
|
||||||
|
algorithm?: string;
|
||||||
|
expiresInSeconds?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AuthenticateResult {
|
||||||
|
requestId: string;
|
||||||
|
status: 'authenticated';
|
||||||
|
signature: string;
|
||||||
|
certificate: string;
|
||||||
|
certificateChain: string[];
|
||||||
|
algorithm: string;
|
||||||
|
challenge: string;
|
||||||
|
subjectName: string;
|
||||||
|
subjectCpfCnpj: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface VerifyParams {
|
||||||
|
signature: string;
|
||||||
|
digest: string;
|
||||||
|
certificate: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SealThemesResponse {
|
||||||
|
version: string;
|
||||||
|
status: string;
|
||||||
|
default_seal_id?: string;
|
||||||
|
seals: Array<{ id: string; name: string; [key: string]: unknown }>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SubjectInfo {
|
||||||
|
name: string;
|
||||||
|
cpfCnpj: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CyberCoreSigner {
|
||||||
|
baseUrl: string;
|
||||||
|
host: string;
|
||||||
|
port: number;
|
||||||
|
timeoutMs: number;
|
||||||
|
applicationId: string;
|
||||||
|
applicationName: string;
|
||||||
|
connected: boolean;
|
||||||
|
|
||||||
|
constructor(options?: CyberCoreSignerOptions);
|
||||||
|
|
||||||
|
isInstalled(timeoutMs?: number): Promise<boolean>;
|
||||||
|
getStatus(timeoutMs?: number): Promise<StatusResponse>;
|
||||||
|
getVersion(): Promise<VersionInfo>;
|
||||||
|
getCapabilities(): Promise<CapabilitiesResponse>;
|
||||||
|
connect(): Promise<boolean>;
|
||||||
|
getCertificates(): Promise<CertificateDto[]>;
|
||||||
|
sign(params: SignParams): Promise<SignResult>;
|
||||||
|
signFile(file: File | Blob | ArrayBuffer | Uint8Array, options?: Partial<SignParams>): Promise<SignResult>;
|
||||||
|
signPdf(pdfFile: File | Blob | ArrayBuffer | Uint8Array, options?: Partial<SignParams>): Promise<SignResult>;
|
||||||
|
signXml(xmlContent: string | File | Blob | ArrayBuffer, options?: Partial<SignParams>): Promise<SignResult>;
|
||||||
|
getSealThemes(): Promise<SealThemesResponse>;
|
||||||
|
signPdfDocument(params: SignPdfDocumentParams): Promise<SignPdfDocumentResult>;
|
||||||
|
signXmlDocument(params: SignXmlDocumentParams): Promise<SignXmlDocumentResult>;
|
||||||
|
authenticate(options?: AuthenticateParams): Promise<AuthenticateResult>;
|
||||||
|
login(options?: AuthenticateParams): Promise<AuthenticateResult>;
|
||||||
|
extractSubjectInfo(certificateBase64: string): SubjectInfo;
|
||||||
|
verify(params: VerifyParams): Promise<boolean>;
|
||||||
|
|
||||||
|
_fetch(endpoint: string, options?: Record<string, unknown>): Promise<any>;
|
||||||
|
_calculateSha256(data: string | ArrayBuffer | ArrayBufferView): Promise<string>;
|
||||||
|
_throwMappedError(code: string, message?: string): never;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PdfStampPicker {
|
||||||
|
constructor(options?: unknown);
|
||||||
|
open(options?: unknown): Promise<unknown>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PdfSealDesigner {
|
||||||
|
constructor(options?: unknown);
|
||||||
|
open(options?: unknown): Promise<unknown>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CyberCoreSigner;
|
||||||
+75
-11
@@ -9,29 +9,66 @@ import {
|
|||||||
ApplicationNotAuthorizedError,
|
ApplicationNotAuthorizedError,
|
||||||
UserCancelledError,
|
UserCancelledError,
|
||||||
CertificateNotFoundError,
|
CertificateNotFoundError,
|
||||||
|
CertificateExpiredError,
|
||||||
|
CertificateInvalidError,
|
||||||
|
PrivateKeyNotAvailableError,
|
||||||
PinRequiredError,
|
PinRequiredError,
|
||||||
PinIncorrectError,
|
PinIncorrectError,
|
||||||
TokenNotFoundError,
|
TokenNotFoundError,
|
||||||
RequestExpiredError,
|
RequestExpiredError,
|
||||||
ReplayDetectedError
|
ReplayDetectedError,
|
||||||
|
InvalidOriginError,
|
||||||
|
UnsupportedAlgorithmError,
|
||||||
|
ProviderNotAvailableError,
|
||||||
|
SignatureFailedError,
|
||||||
|
TimeoutError
|
||||||
} from './errors.js';
|
} from './errors.js';
|
||||||
|
|
||||||
import { PdfStampPicker } from './pdf-stamp-picker.js';
|
import { PdfStampPicker } from './pdf-stamp-picker.js';
|
||||||
import { PdfSealDesigner } from './pdf-seal-designer.js';
|
import { PdfSealDesigner } from './pdf-seal-designer.js';
|
||||||
|
|
||||||
export { PdfStampPicker, PdfSealDesigner };
|
export {
|
||||||
|
PdfStampPicker,
|
||||||
|
PdfSealDesigner,
|
||||||
|
CyberCoreError,
|
||||||
|
AgentNotRunningError,
|
||||||
|
ApplicationNotAuthorizedError,
|
||||||
|
UserCancelledError,
|
||||||
|
CertificateNotFoundError,
|
||||||
|
CertificateExpiredError,
|
||||||
|
CertificateInvalidError,
|
||||||
|
PrivateKeyNotAvailableError,
|
||||||
|
PinRequiredError,
|
||||||
|
PinIncorrectError,
|
||||||
|
TokenNotFoundError,
|
||||||
|
RequestExpiredError,
|
||||||
|
ReplayDetectedError,
|
||||||
|
InvalidOriginError,
|
||||||
|
UnsupportedAlgorithmError,
|
||||||
|
ProviderNotAvailableError,
|
||||||
|
SignatureFailedError,
|
||||||
|
TimeoutError
|
||||||
|
};
|
||||||
|
|
||||||
export class CyberCoreSigner {
|
export class CyberCoreSigner {
|
||||||
/**
|
/**
|
||||||
* Inicializa o cliente do SDK.
|
* Inicializa o cliente do SDK.
|
||||||
*
|
*
|
||||||
* @param {Object} options Configurações do SDK.
|
* @param {Object} [options={}] Configurações do SDK.
|
||||||
* @param {string} [options.baseUrl='http://127.0.0.1:21325'] URL base do agente local.
|
* @param {string} [options.baseUrl] URL base completa do agente local (prevalece sobre host e port).
|
||||||
* @param {string} [options.applicationId] UUID único da aplicação web (gerado se não fornecido).
|
* @param {string} [options.host='127.0.0.1'] Host do agente local.
|
||||||
|
* @param {number} [options.port=21325] Porta HTTP do agente local.
|
||||||
|
* @param {number} [options.timeoutMs=30000] Timeout padrão das operações em milissegundos.
|
||||||
|
* @param {string} [options.applicationId] UUID único da aplicação web (gerado e persistido se omitido).
|
||||||
* @param {string} [options.applicationName] Nome legível da aplicação apresentado ao usuário.
|
* @param {string} [options.applicationName] Nome legível da aplicação apresentado ao usuário.
|
||||||
*/
|
*/
|
||||||
constructor(options = {}) {
|
constructor(options = {}) {
|
||||||
this.baseUrl = (options.baseUrl || 'http://127.0.0.1:21325').replace(/\/+$/, '');
|
const host = options.host || '127.0.0.1';
|
||||||
|
const port = options.port !== undefined ? options.port : 21325;
|
||||||
|
this.host = host;
|
||||||
|
this.port = port;
|
||||||
|
this.baseUrl = (options.baseUrl || `http://${host}:${port}`).replace(/\/+$/, '');
|
||||||
|
this.timeoutMs = options.timeoutMs !== undefined ? options.timeoutMs : 30000;
|
||||||
this.applicationId = options.applicationId || this._getOrCreateApplicationId();
|
this.applicationId = options.applicationId || this._getOrCreateApplicationId();
|
||||||
this.applicationName = options.applicationName || (typeof document !== 'undefined' ? document.title : 'Aplicação Web');
|
this.applicationName = options.applicationName || (typeof document !== 'undefined' ? document.title : 'Aplicação Web');
|
||||||
this.connected = false;
|
this.connected = false;
|
||||||
@@ -63,8 +100,15 @@ export class CyberCoreSigner {
|
|||||||
const resp = await this._fetch('/ccsp/v1/status', { method: 'GET', timeoutMs });
|
const resp = await this._fetch('/ccsp/v1/status', { method: 'GET', timeoutMs });
|
||||||
return resp;
|
return resp;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err.name === 'AbortError' || err.message?.includes('Failed to fetch') || err.message?.includes('NetworkError')) {
|
if (
|
||||||
throw new AgentNotRunningError();
|
err.name === 'AbortError' ||
|
||||||
|
err instanceof TimeoutError ||
|
||||||
|
err instanceof AgentNotRunningError ||
|
||||||
|
err.message?.includes('Failed to fetch') ||
|
||||||
|
err.message?.includes('NetworkError') ||
|
||||||
|
err.code === 'ECONNREFUSED'
|
||||||
|
) {
|
||||||
|
throw new AgentNotRunningError(undefined, { host: this.host, port: this.port });
|
||||||
}
|
}
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
@@ -702,7 +746,7 @@ export class CyberCoreSigner {
|
|||||||
*/
|
*/
|
||||||
async _fetch(endpoint, options = {}) {
|
async _fetch(endpoint, options = {}) {
|
||||||
const url = `${this.baseUrl}${endpoint}`;
|
const url = `${this.baseUrl}${endpoint}`;
|
||||||
const timeout = options.timeoutMs || 15000;
|
const timeout = options.timeoutMs !== undefined ? options.timeoutMs : (this.timeoutMs || 30000);
|
||||||
const controller = typeof AbortController !== 'undefined' ? new AbortController() : null;
|
const controller = typeof AbortController !== 'undefined' ? new AbortController() : null;
|
||||||
const timeoutId = controller ? setTimeout(() => controller.abort(), timeout) : null;
|
const timeoutId = controller ? setTimeout(() => controller.abort(), timeout) : null;
|
||||||
|
|
||||||
@@ -738,12 +782,15 @@ export class CyberCoreSigner {
|
|||||||
return data;
|
return data;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err.name === 'AbortError') {
|
if (err.name === 'AbortError') {
|
||||||
throw new CyberCoreError(`Tempo limite esgotado (${timeout}ms) na comunicação com o agente.`, 'TIMEOUT_ERROR');
|
throw new TimeoutError(`Tempo limite esgotado (${timeout}ms) na comunicação com o agente local.`);
|
||||||
}
|
}
|
||||||
if (err instanceof CyberCoreError) {
|
if (err instanceof CyberCoreError) {
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
throw new AgentNotRunningError(`Falha ao conectar com o CyberCore Signer em ${this.baseUrl}: ${err.message}`);
|
throw new AgentNotRunningError(
|
||||||
|
`Falha ao conectar com o CyberCore Signer em ${this.baseUrl}: ${err.message}`,
|
||||||
|
{ host: this.host, port: this.port }
|
||||||
|
);
|
||||||
} finally {
|
} finally {
|
||||||
if (timeoutId) {
|
if (timeoutId) {
|
||||||
clearTimeout(timeoutId);
|
clearTimeout(timeoutId);
|
||||||
@@ -829,6 +876,12 @@ export class CyberCoreSigner {
|
|||||||
throw new UserCancelledError(message);
|
throw new UserCancelledError(message);
|
||||||
case 'CERTIFICATE_NOT_FOUND':
|
case 'CERTIFICATE_NOT_FOUND':
|
||||||
throw new CertificateNotFoundError(message);
|
throw new CertificateNotFoundError(message);
|
||||||
|
case 'CERTIFICATE_EXPIRED':
|
||||||
|
throw new CertificateExpiredError(message);
|
||||||
|
case 'CERTIFICATE_INVALID':
|
||||||
|
throw new CertificateInvalidError(message);
|
||||||
|
case 'PRIVATE_KEY_NOT_AVAILABLE':
|
||||||
|
throw new PrivateKeyNotAvailableError(message);
|
||||||
case 'APPLICATION_NOT_AUTHORIZED':
|
case 'APPLICATION_NOT_AUTHORIZED':
|
||||||
throw new ApplicationNotAuthorizedError(message);
|
throw new ApplicationNotAuthorizedError(message);
|
||||||
case 'PIN_REQUIRED':
|
case 'PIN_REQUIRED':
|
||||||
@@ -836,11 +889,22 @@ export class CyberCoreSigner {
|
|||||||
case 'PIN_INCORRECT':
|
case 'PIN_INCORRECT':
|
||||||
throw new PinIncorrectError(message);
|
throw new PinIncorrectError(message);
|
||||||
case 'TOKEN_NOT_FOUND':
|
case 'TOKEN_NOT_FOUND':
|
||||||
|
case 'TOKEN_REMOVED':
|
||||||
throw new TokenNotFoundError(message);
|
throw new TokenNotFoundError(message);
|
||||||
case 'REQUEST_EXPIRED':
|
case 'REQUEST_EXPIRED':
|
||||||
throw new RequestExpiredError(message);
|
throw new RequestExpiredError(message);
|
||||||
case 'REPLAY_DETECTED':
|
case 'REPLAY_DETECTED':
|
||||||
throw new ReplayDetectedError(message);
|
throw new ReplayDetectedError(message);
|
||||||
|
case 'INVALID_ORIGIN':
|
||||||
|
throw new InvalidOriginError(message);
|
||||||
|
case 'UNSUPPORTED_ALGORITHM':
|
||||||
|
throw new UnsupportedAlgorithmError(message);
|
||||||
|
case 'PROVIDER_NOT_AVAILABLE':
|
||||||
|
throw new ProviderNotAvailableError(message);
|
||||||
|
case 'SIGNATURE_FAILED':
|
||||||
|
throw new SignatureFailedError(message);
|
||||||
|
case 'TIMEOUT_ERROR':
|
||||||
|
throw new TimeoutError(message);
|
||||||
default:
|
default:
|
||||||
throw new CyberCoreError(message || `Erro do protocolo: ${code}`, code);
|
throw new CyberCoreError(message || `Erro do protocolo: ${code}`, code);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,256 @@
|
|||||||
|
import { describe, it, before, after } from 'node:test';
|
||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import http from 'node:http';
|
||||||
|
import {
|
||||||
|
CyberCoreSigner,
|
||||||
|
AgentNotRunningError,
|
||||||
|
ApplicationNotAuthorizedError,
|
||||||
|
CertificateExpiredError,
|
||||||
|
UserCancelledError,
|
||||||
|
TimeoutError
|
||||||
|
} from '../src/signer-sdk.js';
|
||||||
|
|
||||||
|
describe('@cybercore/signer-sdk - Testes de Integração com Mock HTTP Server', () => {
|
||||||
|
let server;
|
||||||
|
let serverPort;
|
||||||
|
let baseUrl;
|
||||||
|
|
||||||
|
// Servidor Mock HTTP emulando o protocolo CCSP/1
|
||||||
|
before(async () => {
|
||||||
|
server = http.createServer((req, res) => {
|
||||||
|
// CORS & Preflight
|
||||||
|
res.setHeader('Access-Control-Allow-Origin', '*');
|
||||||
|
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
||||||
|
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, X-CyberCore-AppId');
|
||||||
|
res.setHeader('Access-Control-Allow-Private-Network', 'true');
|
||||||
|
|
||||||
|
if (req.method === 'OPTIONS') {
|
||||||
|
res.writeHead(204);
|
||||||
|
res.end();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let body = '';
|
||||||
|
req.on('data', chunk => { body += chunk; });
|
||||||
|
req.on('end', () => {
|
||||||
|
const url = new URL(req.url, `http://127.0.0.1:${serverPort}`);
|
||||||
|
|
||||||
|
if (url.pathname === '/ccsp/v1/status') {
|
||||||
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||||
|
res.end(JSON.stringify({
|
||||||
|
version: '1',
|
||||||
|
status: 'ready',
|
||||||
|
agent_version: '0.1.0',
|
||||||
|
os: 'Linux x86_64'
|
||||||
|
}));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (url.pathname === '/ccsp/v1/delay') {
|
||||||
|
// Endpoint propositalmente lento para testar timeout
|
||||||
|
setTimeout(() => {
|
||||||
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||||
|
res.end(JSON.stringify({ status: 'slow_response' }));
|
||||||
|
}, 300);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (url.pathname === '/ccsp/v1/authorize') {
|
||||||
|
const payload = JSON.parse(body || '{}');
|
||||||
|
if (payload.application_id === 'blocked-app') {
|
||||||
|
res.writeHead(403, { 'Content-Type': 'application/json' });
|
||||||
|
res.end(JSON.stringify({
|
||||||
|
version: '1',
|
||||||
|
status: 'rejected',
|
||||||
|
error_code: 'APPLICATION_NOT_AUTHORIZED',
|
||||||
|
message: 'Aplicação bloqueada pelo usuário.'
|
||||||
|
}));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||||
|
res.end(JSON.stringify({
|
||||||
|
version: '1',
|
||||||
|
status: 'authorized',
|
||||||
|
application_id: payload.application_id
|
||||||
|
}));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (url.pathname === '/ccsp/v1/certificates') {
|
||||||
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||||
|
res.end(JSON.stringify({
|
||||||
|
version: '1',
|
||||||
|
status: 'success',
|
||||||
|
certificates: [
|
||||||
|
{
|
||||||
|
id: 'cert-a1-1',
|
||||||
|
alias: 'Certificado A1 Teste',
|
||||||
|
subject_name: 'JOAO DA SILVA:12345678900',
|
||||||
|
certificate_type: 'A1',
|
||||||
|
has_private_key: true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (url.pathname === '/ccsp/v1/sign') {
|
||||||
|
const payload = JSON.parse(body || '{}');
|
||||||
|
if (payload.certificate_hint === 'expired-cert') {
|
||||||
|
res.writeHead(400, { 'Content-Type': 'application/json' });
|
||||||
|
res.end(JSON.stringify({
|
||||||
|
version: '1',
|
||||||
|
request_id: payload.request_id,
|
||||||
|
status: 'error',
|
||||||
|
error_code: 'CERTIFICATE_EXPIRED',
|
||||||
|
message: 'Certificado expirou em 2025.'
|
||||||
|
}));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (payload.certificate_hint === 'cancel-cert') {
|
||||||
|
res.writeHead(400, { 'Content-Type': 'application/json' });
|
||||||
|
res.end(JSON.stringify({
|
||||||
|
version: '1',
|
||||||
|
request_id: payload.request_id,
|
||||||
|
status: 'error',
|
||||||
|
error_code: 'USER_CANCELLED',
|
||||||
|
message: 'Assinatura cancelada no diálogo.'
|
||||||
|
}));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||||
|
res.end(JSON.stringify({
|
||||||
|
version: '1',
|
||||||
|
request_id: payload.request_id,
|
||||||
|
status: 'signed',
|
||||||
|
signature: 'MOCK_SIGNATURE_BASE64',
|
||||||
|
certificate: 'MOCK_CERT_BASE64',
|
||||||
|
algorithm: payload.algorithm || 'SHA256withRSA',
|
||||||
|
document_name: payload.document_name
|
||||||
|
}));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
res.writeHead(404, { 'Content-Type': 'application/json' });
|
||||||
|
res.end(JSON.stringify({ error_code: 'NOT_FOUND', message: 'Endpoint não encontrado' }));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
server.listen(0, '127.0.0.1', () => {
|
||||||
|
serverPort = server.address().port;
|
||||||
|
baseUrl = `http://127.0.0.1:${serverPort}`;
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
server.on('error', reject);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
after(async () => {
|
||||||
|
if (server) {
|
||||||
|
await new Promise(resolve => server.close(resolve));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('deve confirmar que o agente está ativo com isInstalled()', async () => {
|
||||||
|
const signer = new CyberCoreSigner({ baseUrl });
|
||||||
|
const installed = await signer.isInstalled();
|
||||||
|
assert.equal(installed, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('deve retornar dados de status corretos em getStatus()', async () => {
|
||||||
|
const signer = new CyberCoreSigner({ baseUrl });
|
||||||
|
const status = await signer.getStatus();
|
||||||
|
assert.equal(status.version, '1');
|
||||||
|
assert.equal(status.status, 'ready');
|
||||||
|
assert.equal(status.agent_version, '0.1.0');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('deve realizar handshake com connect() com sucesso', async () => {
|
||||||
|
const signer = new CyberCoreSigner({ baseUrl, applicationName: 'ERP Teste' });
|
||||||
|
const connected = await signer.connect();
|
||||||
|
assert.equal(connected, true);
|
||||||
|
assert.equal(signer.connected, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('deve lançar ApplicationNotAuthorizedError se o agente rejeitar autorização', async () => {
|
||||||
|
const signer = new CyberCoreSigner({ baseUrl, applicationId: 'blocked-app' });
|
||||||
|
await assert.rejects(
|
||||||
|
async () => signer.connect(),
|
||||||
|
ApplicationNotAuthorizedError
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('deve listar certificados disponíveis em getCertificates()', async () => {
|
||||||
|
const signer = new CyberCoreSigner({ baseUrl });
|
||||||
|
const certs = await signer.getCertificates();
|
||||||
|
assert.ok(Array.isArray(certs));
|
||||||
|
assert.equal(certs.length, 1);
|
||||||
|
assert.equal(certs[0].id, 'cert-a1-1');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('deve assinar com sucesso via sign()', async () => {
|
||||||
|
const signer = new CyberCoreSigner({ baseUrl });
|
||||||
|
const result = await signer.sign({
|
||||||
|
certificateId: 'cert-a1-1',
|
||||||
|
digest: Buffer.alloc(32, 7).toString('base64'),
|
||||||
|
documentName: 'relatorio.pdf'
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(result.status, 'signed');
|
||||||
|
assert.equal(result.signature, 'MOCK_SIGNATURE_BASE64');
|
||||||
|
assert.equal(result.documentName, 'relatorio.pdf');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('deve tratar erro CERTIFICATE_EXPIRED lançando CertificateExpiredError', async () => {
|
||||||
|
const signer = new CyberCoreSigner({ baseUrl });
|
||||||
|
await assert.rejects(
|
||||||
|
async () => signer.sign({
|
||||||
|
certificateId: 'expired-cert',
|
||||||
|
digest: Buffer.alloc(32, 1).toString('base64')
|
||||||
|
}),
|
||||||
|
CertificateExpiredError
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('deve tratar cancelamento de usuário lançando UserCancelledError', async () => {
|
||||||
|
const signer = new CyberCoreSigner({ baseUrl });
|
||||||
|
await assert.rejects(
|
||||||
|
async () => signer.sign({
|
||||||
|
certificateId: 'cancel-cert',
|
||||||
|
digest: Buffer.alloc(32, 2).toString('base64')
|
||||||
|
}),
|
||||||
|
UserCancelledError
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('deve lançar TimeoutError quando a chamada exceder o timeoutMs configurado', async () => {
|
||||||
|
const signer = new CyberCoreSigner({ baseUrl, timeoutMs: 50 });
|
||||||
|
await assert.rejects(
|
||||||
|
async () => signer._fetch('/ccsp/v1/delay'),
|
||||||
|
TimeoutError
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('deve lançar AgentNotRunningError com mensagem acionável se porta estiver inativa', async () => {
|
||||||
|
// Porta inativa aleatória
|
||||||
|
const deadPort = 64999;
|
||||||
|
const signer = new CyberCoreSigner({ host: '127.0.0.1', port: deadPort });
|
||||||
|
|
||||||
|
const installed = await signer.isInstalled(300);
|
||||||
|
assert.equal(installed, false);
|
||||||
|
|
||||||
|
await assert.rejects(
|
||||||
|
async () => signer.getStatus(300),
|
||||||
|
err => {
|
||||||
|
assert.ok(err instanceof AgentNotRunningError);
|
||||||
|
assert.equal(err.code, 'AGENT_NOT_RUNNING');
|
||||||
|
assert.equal(err.port, deadPort);
|
||||||
|
assert.ok(err.actionableGuidance.includes('bandeja do sistema'));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -3,10 +3,22 @@ import assert from 'node:assert/strict';
|
|||||||
import { CyberCoreSigner } from '../src/signer-sdk.js';
|
import { CyberCoreSigner } from '../src/signer-sdk.js';
|
||||||
import {
|
import {
|
||||||
CyberCoreError,
|
CyberCoreError,
|
||||||
|
AgentNotRunningError,
|
||||||
UserCancelledError,
|
UserCancelledError,
|
||||||
CertificateNotFoundError,
|
CertificateNotFoundError,
|
||||||
|
CertificateExpiredError,
|
||||||
|
CertificateInvalidError,
|
||||||
|
PrivateKeyNotAvailableError,
|
||||||
|
PinRequiredError,
|
||||||
PinIncorrectError,
|
PinIncorrectError,
|
||||||
TokenNotFoundError,
|
TokenNotFoundError,
|
||||||
|
RequestExpiredError,
|
||||||
|
ReplayDetectedError,
|
||||||
|
InvalidOriginError,
|
||||||
|
UnsupportedAlgorithmError,
|
||||||
|
ProviderNotAvailableError,
|
||||||
|
SignatureFailedError,
|
||||||
|
TimeoutError,
|
||||||
ApplicationNotAuthorizedError
|
ApplicationNotAuthorizedError
|
||||||
} from '../src/errors.js';
|
} from '../src/errors.js';
|
||||||
|
|
||||||
@@ -25,12 +37,25 @@ describe('@cybercore/signer-sdk - Testes Unitários do SDK', () => {
|
|||||||
const signer = new CyberCoreSigner({
|
const signer = new CyberCoreSigner({
|
||||||
baseUrl: 'http://127.0.0.1:21325/',
|
baseUrl: 'http://127.0.0.1:21325/',
|
||||||
applicationId: customAppId,
|
applicationId: customAppId,
|
||||||
applicationName: 'Portal Minha Empresa'
|
applicationName: 'Portal Minha Empresa',
|
||||||
|
timeoutMs: 45000
|
||||||
});
|
});
|
||||||
|
|
||||||
assert.equal(signer.baseUrl, 'http://127.0.0.1:21325');
|
assert.equal(signer.baseUrl, 'http://127.0.0.1:21325');
|
||||||
assert.equal(signer.applicationId, customAppId);
|
assert.equal(signer.applicationId, customAppId);
|
||||||
assert.equal(signer.applicationName, 'Portal Minha Empresa');
|
assert.equal(signer.applicationName, 'Portal Minha Empresa');
|
||||||
|
assert.equal(signer.timeoutMs, 45000);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('deve montar baseUrl automaticamente quando host e port forem especificados', () => {
|
||||||
|
const signer = new CyberCoreSigner({
|
||||||
|
host: 'localhost',
|
||||||
|
port: 29999
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(signer.host, 'localhost');
|
||||||
|
assert.equal(signer.port, 29999);
|
||||||
|
assert.equal(signer.baseUrl, 'http://localhost:29999');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('deve retornar as capacidades criptográficas do agente em getCapabilities()', async () => {
|
it('deve retornar as capacidades criptográficas do agente em getCapabilities()', async () => {
|
||||||
@@ -69,6 +94,26 @@ describe('@cybercore/signer-sdk - Testes Unitários do SDK', () => {
|
|||||||
CertificateNotFoundError
|
CertificateNotFoundError
|
||||||
);
|
);
|
||||||
|
|
||||||
|
assert.throws(
|
||||||
|
() => signer._throwMappedError('CERTIFICATE_EXPIRED', 'Certificado expirado'),
|
||||||
|
CertificateExpiredError
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.throws(
|
||||||
|
() => signer._throwMappedError('CERTIFICATE_INVALID', 'Certificado inválido'),
|
||||||
|
CertificateInvalidError
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.throws(
|
||||||
|
() => signer._throwMappedError('PRIVATE_KEY_NOT_AVAILABLE', 'Sem chave privada'),
|
||||||
|
PrivateKeyNotAvailableError
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.throws(
|
||||||
|
() => signer._throwMappedError('PIN_REQUIRED', 'PIN obrigatório'),
|
||||||
|
PinRequiredError
|
||||||
|
);
|
||||||
|
|
||||||
assert.throws(
|
assert.throws(
|
||||||
() => signer._throwMappedError('PIN_INCORRECT', 'PIN errado'),
|
() => signer._throwMappedError('PIN_INCORRECT', 'PIN errado'),
|
||||||
PinIncorrectError
|
PinIncorrectError
|
||||||
@@ -79,6 +124,46 @@ describe('@cybercore/signer-sdk - Testes Unitários do SDK', () => {
|
|||||||
TokenNotFoundError
|
TokenNotFoundError
|
||||||
);
|
);
|
||||||
|
|
||||||
|
assert.throws(
|
||||||
|
() => signer._throwMappedError('TOKEN_REMOVED', 'Token removido'),
|
||||||
|
TokenNotFoundError
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.throws(
|
||||||
|
() => signer._throwMappedError('REQUEST_EXPIRED', 'Expirou'),
|
||||||
|
RequestExpiredError
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.throws(
|
||||||
|
() => signer._throwMappedError('REPLAY_DETECTED', 'Replay'),
|
||||||
|
ReplayDetectedError
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.throws(
|
||||||
|
() => signer._throwMappedError('INVALID_ORIGIN', 'Origem inválida'),
|
||||||
|
InvalidOriginError
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.throws(
|
||||||
|
() => signer._throwMappedError('UNSUPPORTED_ALGORITHM', 'Algoritmo inválido'),
|
||||||
|
UnsupportedAlgorithmError
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.throws(
|
||||||
|
() => signer._throwMappedError('PROVIDER_NOT_AVAILABLE', 'Provider indisponível'),
|
||||||
|
ProviderNotAvailableError
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.throws(
|
||||||
|
() => signer._throwMappedError('SIGNATURE_FAILED', 'Falha na assinatura'),
|
||||||
|
SignatureFailedError
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.throws(
|
||||||
|
() => signer._throwMappedError('TIMEOUT_ERROR', 'Timeout'),
|
||||||
|
TimeoutError
|
||||||
|
);
|
||||||
|
|
||||||
assert.throws(
|
assert.throws(
|
||||||
() => signer._throwMappedError('APPLICATION_NOT_AUTHORIZED', 'Não autorizada'),
|
() => signer._throwMappedError('APPLICATION_NOT_AUTHORIZED', 'Não autorizada'),
|
||||||
ApplicationNotAuthorizedError
|
ApplicationNotAuthorizedError
|
||||||
@@ -90,6 +175,16 @@ describe('@cybercore/signer-sdk - Testes Unitários do SDK', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('deve fornecer orientação acionável rica em AgentNotRunningError', () => {
|
||||||
|
const err = new AgentNotRunningError(undefined, { host: '127.0.0.1', port: 21325 });
|
||||||
|
assert.equal(err.code, 'AGENT_NOT_RUNNING');
|
||||||
|
assert.equal(err.host, '127.0.0.1');
|
||||||
|
assert.equal(err.port, 21325);
|
||||||
|
assert.ok(err.actionableGuidance.includes('bandeja do sistema'));
|
||||||
|
assert.ok(err.actionableGuidance.includes('instalador oficial'));
|
||||||
|
assert.ok(err.actionableGuidance.includes('21325'));
|
||||||
|
});
|
||||||
|
|
||||||
it('deve validar parâmetros obrigatórios no método sign()', async () => {
|
it('deve validar parâmetros obrigatórios no método sign()', async () => {
|
||||||
const signer = new CyberCoreSigner();
|
const signer = new CyberCoreSigner();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user