OpChan/packages/core/dist/utils/MessageValidator.js
2025-09-11 14:29:55 +05:30

279 lines
9.4 KiB
JavaScript

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