feat(integration): components now play nicer with each other
This commit is contained in:
@ -171,12 +171,14 @@ export class SenderReputationMonitor {
|
||||
private reputationData: Map<string, IDomainReputationMetrics> = new Map();
|
||||
private updateTimer: NodeJS.Timeout = null;
|
||||
private isInitialized: boolean = false;
|
||||
private storageManager?: any; // StorageManager instance
|
||||
|
||||
/**
|
||||
* Constructor for SenderReputationMonitor
|
||||
* @param config Configuration options
|
||||
* @param storageManager Optional StorageManager instance
|
||||
*/
|
||||
constructor(config: IReputationMonitorConfig = {}) {
|
||||
constructor(config: IReputationMonitorConfig = {}, storageManager?: any) {
|
||||
// Merge with default config
|
||||
this.config = {
|
||||
...DEFAULT_CONFIG,
|
||||
@ -191,18 +193,34 @@ export class SenderReputationMonitor {
|
||||
}
|
||||
};
|
||||
|
||||
// Initialize
|
||||
this.initialize();
|
||||
this.storageManager = storageManager;
|
||||
|
||||
// If no storage manager provided, log warning
|
||||
if (!storageManager) {
|
||||
logger.log('warn',
|
||||
'⚠️ WARNING: SenderReputationMonitor initialized without StorageManager.\n' +
|
||||
' Reputation data will only be stored to filesystem.\n' +
|
||||
' Consider passing a StorageManager instance for better storage flexibility.'
|
||||
);
|
||||
}
|
||||
|
||||
// Initialize (async, but we don't await here to avoid blocking constructor)
|
||||
this.initialize().catch(error => {
|
||||
logger.log('error', `Failed to initialize SenderReputationMonitor: ${error.message}`, {
|
||||
stack: error.stack
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the singleton instance
|
||||
* @param config Configuration options
|
||||
* @param storageManager Optional StorageManager instance
|
||||
* @returns Singleton instance
|
||||
*/
|
||||
public static getInstance(config: IReputationMonitorConfig = {}): SenderReputationMonitor {
|
||||
public static getInstance(config: IReputationMonitorConfig = {}, storageManager?: any): SenderReputationMonitor {
|
||||
if (!SenderReputationMonitor.instance) {
|
||||
SenderReputationMonitor.instance = new SenderReputationMonitor(config);
|
||||
SenderReputationMonitor.instance = new SenderReputationMonitor(config, storageManager);
|
||||
}
|
||||
return SenderReputationMonitor.instance;
|
||||
}
|
||||
@ -210,7 +228,7 @@ export class SenderReputationMonitor {
|
||||
/**
|
||||
* Initialize the reputation monitor
|
||||
*/
|
||||
private initialize(): void {
|
||||
private async initialize(): Promise<void> {
|
||||
if (this.isInitialized) return;
|
||||
|
||||
try {
|
||||
@ -219,7 +237,7 @@ export class SenderReputationMonitor {
|
||||
|
||||
if (!isTestEnvironment) {
|
||||
// Load existing reputation data
|
||||
this.loadReputationData();
|
||||
await this.loadReputationData();
|
||||
}
|
||||
|
||||
// Initialize data for any new domains
|
||||
@ -354,7 +372,7 @@ export class SenderReputationMonitor {
|
||||
}
|
||||
|
||||
// Save all updated data
|
||||
this.saveReputationData();
|
||||
await this.saveReputationData();
|
||||
|
||||
logger.log('info', 'Completed reputation metrics update for all domains');
|
||||
}
|
||||
@ -977,7 +995,11 @@ export class SenderReputationMonitor {
|
||||
// Skip in test environment
|
||||
const isTestEnvironment = process.env.NODE_ENV === 'test' || !!process.env.JEST_WORKER_ID;
|
||||
if (!isTestEnvironment && Math.random() < 0.01) { // ~1% chance to save on each event
|
||||
this.saveReputationData();
|
||||
this.saveReputationData().catch(error => {
|
||||
logger.log('error', `Failed to save reputation data: ${error.message}`, {
|
||||
stack: error.stack
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -1037,7 +1059,11 @@ export class SenderReputationMonitor {
|
||||
|
||||
this.config.domains.push(domain);
|
||||
this.initializeDomainData(domain);
|
||||
this.saveReputationData();
|
||||
this.saveReputationData().catch(error => {
|
||||
logger.log('error', `Failed to save reputation data after adding domain: ${error.message}`, {
|
||||
stack: error.stack
|
||||
});
|
||||
});
|
||||
|
||||
logger.log('info', `Added ${domain} to reputation monitoring`);
|
||||
}
|
||||
@ -1055,7 +1081,11 @@ export class SenderReputationMonitor {
|
||||
|
||||
this.config.domains.splice(index, 1);
|
||||
this.reputationData.delete(domain);
|
||||
this.saveReputationData();
|
||||
this.saveReputationData().catch(error => {
|
||||
logger.log('error', `Failed to save reputation data after removing domain: ${error.message}`, {
|
||||
stack: error.stack
|
||||
});
|
||||
});
|
||||
|
||||
logger.log('info', `Removed ${domain} from reputation monitoring`);
|
||||
}
|
||||
@ -1063,7 +1093,7 @@ export class SenderReputationMonitor {
|
||||
/**
|
||||
* Load reputation data from storage
|
||||
*/
|
||||
private loadReputationData(): void {
|
||||
private async loadReputationData(): Promise<void> {
|
||||
// Skip loading in test environment to prevent file system race conditions
|
||||
const isTestEnvironment = process.env.NODE_ENV === 'test' || !!process.env.JEST_WORKER_ID;
|
||||
if (isTestEnvironment) {
|
||||
@ -1071,32 +1101,98 @@ export class SenderReputationMonitor {
|
||||
}
|
||||
|
||||
try {
|
||||
const reputationDir = plugins.path.join(paths.dataDir, 'reputation');
|
||||
plugins.smartfile.fs.ensureDirSync(reputationDir);
|
||||
|
||||
const dataFile = plugins.path.join(reputationDir, 'domain_reputation.json');
|
||||
|
||||
if (plugins.fs.existsSync(dataFile)) {
|
||||
const data = plugins.fs.readFileSync(dataFile, 'utf8');
|
||||
const reputationEntries = JSON.parse(data);
|
||||
|
||||
for (const entry of reputationEntries) {
|
||||
// Restore Date objects
|
||||
entry.lastUpdated = new Date(entry.lastUpdated);
|
||||
|
||||
for (const listing of entry.blocklist.activeListings) {
|
||||
listing.listedSince = new Date(listing.listedSince);
|
||||
// Try to load from storage manager first
|
||||
if (this.storageManager) {
|
||||
try {
|
||||
const data = await this.storageManager.get('/email/reputation/domain-reputation.json');
|
||||
if (data) {
|
||||
const reputationEntries = JSON.parse(data);
|
||||
|
||||
for (const entry of reputationEntries) {
|
||||
// Restore Date objects
|
||||
entry.lastUpdated = new Date(entry.lastUpdated);
|
||||
|
||||
for (const listing of entry.blocklist.activeListings) {
|
||||
listing.listedSince = new Date(listing.listedSince);
|
||||
}
|
||||
|
||||
for (const delisting of entry.blocklist.recentDelistings) {
|
||||
delisting.listedFrom = new Date(delisting.listedFrom);
|
||||
delisting.listedTo = new Date(delisting.listedTo);
|
||||
}
|
||||
|
||||
this.reputationData.set(entry.domain, entry);
|
||||
}
|
||||
|
||||
logger.log('info', `Loaded reputation data for ${this.reputationData.size} domains from StorageManager`);
|
||||
return;
|
||||
}
|
||||
|
||||
for (const delisting of entry.blocklist.recentDelistings) {
|
||||
delisting.listedFrom = new Date(delisting.listedFrom);
|
||||
delisting.listedTo = new Date(delisting.listedTo);
|
||||
}
|
||||
|
||||
this.reputationData.set(entry.domain, entry);
|
||||
} catch (error) {
|
||||
// Fall through to filesystem migration check
|
||||
}
|
||||
|
||||
logger.log('info', `Loaded reputation data for ${this.reputationData.size} domains`);
|
||||
// Check if data exists in filesystem and migrate it to storage manager
|
||||
const reputationDir = plugins.path.join(paths.dataDir, 'reputation');
|
||||
const dataFile = plugins.path.join(reputationDir, 'domain_reputation.json');
|
||||
|
||||
if (plugins.fs.existsSync(dataFile)) {
|
||||
const data = plugins.fs.readFileSync(dataFile, 'utf8');
|
||||
const reputationEntries = JSON.parse(data);
|
||||
|
||||
for (const entry of reputationEntries) {
|
||||
// Restore Date objects
|
||||
entry.lastUpdated = new Date(entry.lastUpdated);
|
||||
|
||||
for (const listing of entry.blocklist.activeListings) {
|
||||
listing.listedSince = new Date(listing.listedSince);
|
||||
}
|
||||
|
||||
for (const delisting of entry.blocklist.recentDelistings) {
|
||||
delisting.listedFrom = new Date(delisting.listedFrom);
|
||||
delisting.listedTo = new Date(delisting.listedTo);
|
||||
}
|
||||
|
||||
this.reputationData.set(entry.domain, entry);
|
||||
}
|
||||
|
||||
// Migrate to storage manager
|
||||
logger.log('info', `Migrating reputation data for ${this.reputationData.size} domains from filesystem to StorageManager`);
|
||||
await this.storageManager.set(
|
||||
'/email/reputation/domain-reputation.json',
|
||||
JSON.stringify(Array.from(this.reputationData.values()), null, 2)
|
||||
);
|
||||
|
||||
logger.log('info', `Loaded and migrated reputation data for ${this.reputationData.size} domains`);
|
||||
}
|
||||
} else {
|
||||
// No storage manager, use filesystem directly
|
||||
const reputationDir = plugins.path.join(paths.dataDir, 'reputation');
|
||||
plugins.smartfile.fs.ensureDirSync(reputationDir);
|
||||
|
||||
const dataFile = plugins.path.join(reputationDir, 'domain_reputation.json');
|
||||
|
||||
if (plugins.fs.existsSync(dataFile)) {
|
||||
const data = plugins.fs.readFileSync(dataFile, 'utf8');
|
||||
const reputationEntries = JSON.parse(data);
|
||||
|
||||
for (const entry of reputationEntries) {
|
||||
// Restore Date objects
|
||||
entry.lastUpdated = new Date(entry.lastUpdated);
|
||||
|
||||
for (const listing of entry.blocklist.activeListings) {
|
||||
listing.listedSince = new Date(listing.listedSince);
|
||||
}
|
||||
|
||||
for (const delisting of entry.blocklist.recentDelistings) {
|
||||
delisting.listedFrom = new Date(delisting.listedFrom);
|
||||
delisting.listedTo = new Date(delisting.listedTo);
|
||||
}
|
||||
|
||||
this.reputationData.set(entry.domain, entry);
|
||||
}
|
||||
|
||||
logger.log('info', `Loaded reputation data for ${this.reputationData.size} domains from filesystem`);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
logger.log('error', `Failed to load reputation data: ${error.message}`, {
|
||||
@ -1108,7 +1204,7 @@ export class SenderReputationMonitor {
|
||||
/**
|
||||
* Save reputation data to storage
|
||||
*/
|
||||
private saveReputationData(): void {
|
||||
private async saveReputationData(): Promise<void> {
|
||||
// Skip saving in test environment to prevent file system race conditions
|
||||
const isTestEnvironment = process.env.NODE_ENV === 'test' || !!process.env.JEST_WORKER_ID;
|
||||
if (isTestEnvironment) {
|
||||
@ -1116,18 +1212,29 @@ export class SenderReputationMonitor {
|
||||
}
|
||||
|
||||
try {
|
||||
const reputationDir = plugins.path.join(paths.dataDir, 'reputation');
|
||||
plugins.smartfile.fs.ensureDirSync(reputationDir);
|
||||
|
||||
const dataFile = plugins.path.join(reputationDir, 'domain_reputation.json');
|
||||
const reputationEntries = Array.from(this.reputationData.values());
|
||||
|
||||
plugins.smartfile.memory.toFsSync(
|
||||
JSON.stringify(reputationEntries, null, 2),
|
||||
dataFile
|
||||
);
|
||||
|
||||
logger.log('debug', `Saved reputation data for ${reputationEntries.length} domains`);
|
||||
// Save to storage manager if available
|
||||
if (this.storageManager) {
|
||||
await this.storageManager.set(
|
||||
'/email/reputation/domain-reputation.json',
|
||||
JSON.stringify(reputationEntries, null, 2)
|
||||
);
|
||||
logger.log('debug', `Saved reputation data for ${reputationEntries.length} domains to StorageManager`);
|
||||
} else {
|
||||
// No storage manager, use filesystem directly
|
||||
const reputationDir = plugins.path.join(paths.dataDir, 'reputation');
|
||||
plugins.smartfile.fs.ensureDirSync(reputationDir);
|
||||
|
||||
const dataFile = plugins.path.join(reputationDir, 'domain_reputation.json');
|
||||
|
||||
plugins.smartfile.memory.toFsSync(
|
||||
JSON.stringify(reputationEntries, null, 2),
|
||||
dataFile
|
||||
);
|
||||
|
||||
logger.log('debug', `Saved reputation data for ${reputationEntries.length} domains to filesystem`);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.log('error', `Failed to save reputation data: ${error.message}`, {
|
||||
stack: error.stack
|
||||
|
Reference in New Issue
Block a user