import { DelegationManager } from '../delegation'; export class MessageValidator { constructor(delegationManager) { this.delegationManager = delegationManager; } getDelegationManager() { if (!this.delegationManager) { this.delegationManager = new DelegationManager(); } return this.delegationManager; } /** * Validates that a message has required signature fields and valid signature */ async isValidMessage(message) { // Check basic structure if (!this.hasRequiredFields(message)) { return false; } // Verify signature and delegation proof - we know it's safe to cast here since hasRequiredFields passed try { return await this.getDelegationManager().verify(message); } catch { return false; } } /** * Checks if message has required signature and browserPubKey fields */ hasRequiredFields(message) { if (!message || typeof message !== 'object') { return false; } const msg = message; return (typeof msg.signature === 'string' && typeof msg.browserPubKey === 'string' && typeof msg.id === 'string' && typeof msg.type === 'string' && typeof msg.timestamp === 'number' && typeof msg.author === 'string'); } /** * Validates multiple messages and returns validation report */ async validateMessages(messages) { const validMessages = []; const invalidMessages = []; const validationErrors = []; for (const message of messages) { try { // Check basic structure first if (!this.hasRequiredFields(message)) { invalidMessages.push(message); validationErrors.push('Missing required fields'); continue; } // Verify signature try { const isValid = await this.getDelegationManager().verify(message); if (!isValid) { invalidMessages.push(message); validationErrors.push('Invalid signature'); continue; } validMessages.push(message); } catch { invalidMessages.push(message); validationErrors.push('Signature verification failed'); } } catch (error) { invalidMessages.push(message); validationErrors.push(error instanceof Error ? error.message : 'Unknown validation error'); } } return { validMessages, invalidMessages, totalProcessed: messages.length, validationErrors, }; } /** * Validates and returns a single message if valid */ async validateSingleMessage(message) { // Check basic structure if (!this.hasRequiredFields(message)) { throw new Error('Message missing required fields'); } // Verify signature and delegation proof try { const isValid = await this.getDelegationManager().verify(message); if (!isValid) { throw new Error('Invalid message signature'); } return message; } catch (error) { throw new Error(`Message validation failed: ${error}`); } } /** * Batch validation with performance optimization */ async batchValidate(messages, options = {}) { const { maxConcurrent = 10, skipInvalid = true } = options; const validMessages = []; const invalidMessages = []; const validationErrors = []; // Process messages in batches to avoid overwhelming the system for (let i = 0; i < messages.length; i += maxConcurrent) { const batch = messages.slice(i, i + maxConcurrent); const batchPromises = batch.map(async (message, index) => { try { const isValid = await this.isValidMessage(message); return { message, isValid, index: i + index, error: null }; } catch (error) { return { message, isValid: false, index: i + index, error: error instanceof Error ? error.message : 'Unknown error', }; } }); const batchResults = await Promise.allSettled(batchPromises); for (const result of batchResults) { if (result.status === 'fulfilled') { const { message, isValid, error } = result.value; if (isValid) { validMessages.push(message); } else { if (!skipInvalid) { invalidMessages.push(message); if (error) validationErrors.push(error); } } } else { if (!skipInvalid) { validationErrors.push(result.reason?.message || 'Batch validation failed'); } } } } return { validMessages, invalidMessages, totalProcessed: messages.length, validationErrors, }; } /** * Quick validation check without full verification (for performance) */ quickValidate(message) { return this.hasRequiredFields(message); } /** * Get validation statistics */ getValidationStats(report) { const validCount = report.validMessages.length; const invalidCount = report.invalidMessages.length; const successRate = report.totalProcessed > 0 ? validCount / report.totalProcessed : 0; return { validCount, invalidCount, totalProcessed: report.totalProcessed, successRate, errorCount: report.validationErrors.length, hasErrors: report.validationErrors.length > 0, }; } /** * Filter messages by type after validation */ filterByType(messages, messageType) { return messages.filter((msg) => msg.type === messageType); } /** * Sort messages by timestamp */ sortByTimestamp(messages, ascending = true) { return [...messages].sort((a, b) => ascending ? a.timestamp - b.timestamp : b.timestamp - a.timestamp); } /** * Group messages by author */ groupByAuthor(messages) { const grouped = {}; for (const message of messages) { if (!grouped[message.author]) { grouped[message.author] = []; } const authorMessages = grouped[message.author]; if (authorMessages) { authorMessages.push(message); } } return grouped; } /** * Get validation report for a message (for backward compatibility) */ async getValidationReport(message) { const structureReport = this.validateStructure(message); const hasValidSignature = structureReport.isValid ? await this.isValidMessage(message) : false; return { ...structureReport, hasValidSignature, errors: [ ...structureReport.missingFields, ...structureReport.invalidFields, ], }; } /** * Validate message structure and return detailed report */ validateStructure(message) { const missingFields = []; const invalidFields = []; const warnings = []; if (!message || typeof message !== 'object') { return { isValid: false, missingFields: ['message'], invalidFields: [], warnings: ['Message is not an object'], }; } const msg = message; const requiredFields = [ 'signature', 'browserPubKey', 'id', 'type', 'timestamp', 'author', ]; for (const field of requiredFields) { if (!(field in msg)) { missingFields.push(field); } else if (typeof msg[field] !== (field === 'timestamp' ? 'number' : 'string')) { invalidFields.push(field); } } // Additional validation warnings if (typeof msg.timestamp === 'number') { const age = Date.now() - msg.timestamp; if (age > 24 * 60 * 60 * 1000) { // Older than 24 hours warnings.push('Message is older than 24 hours'); } if (msg.timestamp > Date.now() + 5 * 60 * 1000) { // More than 5 minutes in future warnings.push('Message timestamp is in the future'); } } const isValid = missingFields.length === 0 && invalidFields.length === 0; return { isValid, missingFields, invalidFields, warnings, }; } } //# sourceMappingURL=MessageValidator.js.map