feat(integration): components now play nicer with each other
This commit is contained in:
@ -209,10 +209,13 @@ export class BounceManager {
|
||||
expiresAt?: number; // undefined means permanent
|
||||
}> = new Map();
|
||||
|
||||
private storageManager?: any; // StorageManager instance
|
||||
|
||||
constructor(options?: {
|
||||
retryStrategy?: Partial<RetryStrategy>;
|
||||
maxCacheSize?: number;
|
||||
cacheTTL?: number;
|
||||
storageManager?: any;
|
||||
}) {
|
||||
// Set retry strategy with defaults
|
||||
if (options?.retryStrategy) {
|
||||
@ -228,8 +231,24 @@ export class BounceManager {
|
||||
ttl: options?.cacheTTL || 30 * 24 * 60 * 60 * 1000, // 30 days default
|
||||
});
|
||||
|
||||
// Store storage manager reference
|
||||
this.storageManager = options?.storageManager;
|
||||
|
||||
// If no storage manager provided, log warning
|
||||
if (!this.storageManager) {
|
||||
console.warn(
|
||||
'⚠️ WARNING: BounceManager initialized without StorageManager.\n' +
|
||||
' Bounce data will only be stored to filesystem.\n' +
|
||||
' Consider passing a StorageManager instance for better storage flexibility.'
|
||||
);
|
||||
}
|
||||
|
||||
// Load suppression list from storage
|
||||
this.loadSuppressionList();
|
||||
// Note: This is async but we can't await in constructor
|
||||
// The suppression list will be loaded asynchronously
|
||||
this.loadSuppressionList().catch(error => {
|
||||
logger.log('error', `Failed to load suppression list on startup: ${error.message}`);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -479,7 +498,7 @@ export class BounceManager {
|
||||
this.updateBounceCache(bounce);
|
||||
|
||||
// Save to permanent storage
|
||||
this.saveBounceRecord(bounce);
|
||||
await this.saveBounceRecord(bounce);
|
||||
|
||||
// Log hard bounce for monitoring
|
||||
logger.log('warn', `Hard bounce for ${bounce.recipient}: ${bounce.bounceType}`, {
|
||||
@ -545,7 +564,10 @@ export class BounceManager {
|
||||
expiresAt
|
||||
});
|
||||
|
||||
this.saveSuppressionList();
|
||||
// Save asynchronously without blocking
|
||||
this.saveSuppressionList().catch(error => {
|
||||
logger.log('error', `Failed to save suppression list after adding ${email}: ${error.message}`);
|
||||
});
|
||||
|
||||
logger.log('info', `Added ${email} to suppression list`, {
|
||||
reason,
|
||||
@ -561,7 +583,10 @@ export class BounceManager {
|
||||
const wasRemoved = this.suppressionList.delete(email.toLowerCase());
|
||||
|
||||
if (wasRemoved) {
|
||||
this.saveSuppressionList();
|
||||
// Save asynchronously without blocking
|
||||
this.saveSuppressionList().catch(error => {
|
||||
logger.log('error', `Failed to save suppression list after removing ${email}: ${error.message}`);
|
||||
});
|
||||
logger.log('info', `Removed ${email} from suppression list`);
|
||||
}
|
||||
}
|
||||
@ -582,7 +607,10 @@ export class BounceManager {
|
||||
// Check if suppression has expired
|
||||
if (suppression.expiresAt && Date.now() > suppression.expiresAt) {
|
||||
this.suppressionList.delete(lowercaseEmail);
|
||||
this.saveSuppressionList();
|
||||
// Save asynchronously without blocking
|
||||
this.saveSuppressionList().catch(error => {
|
||||
logger.log('error', `Failed to save suppression list after expiry cleanup: ${error.message}`);
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -609,7 +637,10 @@ export class BounceManager {
|
||||
// Check if suppression has expired
|
||||
if (suppression.expiresAt && Date.now() > suppression.expiresAt) {
|
||||
this.suppressionList.delete(lowercaseEmail);
|
||||
this.saveSuppressionList();
|
||||
// Save asynchronously without blocking
|
||||
this.saveSuppressionList().catch(error => {
|
||||
logger.log('error', `Failed to save suppression list after expiry cleanup: ${error.message}`);
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -619,13 +650,20 @@ export class BounceManager {
|
||||
/**
|
||||
* Save suppression list to disk
|
||||
*/
|
||||
private saveSuppressionList(): void {
|
||||
private async saveSuppressionList(): Promise<void> {
|
||||
try {
|
||||
const suppressionData = JSON.stringify(Array.from(this.suppressionList.entries()));
|
||||
plugins.smartfile.memory.toFsSync(
|
||||
suppressionData,
|
||||
plugins.path.join(paths.dataDir, 'emails', 'suppression_list.json')
|
||||
);
|
||||
|
||||
if (this.storageManager) {
|
||||
// Use storage manager
|
||||
await this.storageManager.set('/email/bounces/suppression-list.json', suppressionData);
|
||||
} else {
|
||||
// Fall back to filesystem
|
||||
plugins.smartfile.memory.toFsSync(
|
||||
suppressionData,
|
||||
plugins.path.join(paths.dataDir, 'emails', 'suppression_list.json')
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.log('error', `Failed to save suppression list: ${error.message}`);
|
||||
}
|
||||
@ -634,14 +672,40 @@ export class BounceManager {
|
||||
/**
|
||||
* Load suppression list from disk
|
||||
*/
|
||||
private loadSuppressionList(): void {
|
||||
private async loadSuppressionList(): Promise<void> {
|
||||
try {
|
||||
const suppressionPath = plugins.path.join(paths.dataDir, 'emails', 'suppression_list.json');
|
||||
let entries = null;
|
||||
let needsMigration = false;
|
||||
|
||||
if (plugins.fs.existsSync(suppressionPath)) {
|
||||
const data = plugins.fs.readFileSync(suppressionPath, 'utf8');
|
||||
const entries = JSON.parse(data);
|
||||
if (this.storageManager) {
|
||||
// Try to load from storage manager first
|
||||
const suppressionData = await this.storageManager.get('/email/bounces/suppression-list.json');
|
||||
|
||||
if (suppressionData) {
|
||||
entries = JSON.parse(suppressionData);
|
||||
} else {
|
||||
// Check if data exists in filesystem and migrate
|
||||
const suppressionPath = plugins.path.join(paths.dataDir, 'emails', 'suppression_list.json');
|
||||
|
||||
if (plugins.fs.existsSync(suppressionPath)) {
|
||||
const data = plugins.fs.readFileSync(suppressionPath, 'utf8');
|
||||
entries = JSON.parse(data);
|
||||
needsMigration = true;
|
||||
|
||||
logger.log('info', 'Migrating suppression list from filesystem to StorageManager');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// No storage manager, use filesystem directly
|
||||
const suppressionPath = plugins.path.join(paths.dataDir, 'emails', 'suppression_list.json');
|
||||
|
||||
if (plugins.fs.existsSync(suppressionPath)) {
|
||||
const data = plugins.fs.readFileSync(suppressionPath, 'utf8');
|
||||
entries = JSON.parse(data);
|
||||
}
|
||||
}
|
||||
|
||||
if (entries) {
|
||||
this.suppressionList = new Map(entries);
|
||||
|
||||
// Clean expired entries
|
||||
@ -655,9 +719,9 @@ export class BounceManager {
|
||||
}
|
||||
}
|
||||
|
||||
if (expiredCount > 0) {
|
||||
if (expiredCount > 0 || needsMigration) {
|
||||
logger.log('info', `Cleaned ${expiredCount} expired entries from suppression list`);
|
||||
this.saveSuppressionList();
|
||||
await this.saveSuppressionList();
|
||||
}
|
||||
|
||||
logger.log('info', `Loaded ${this.suppressionList.size} entries from suppression list`);
|
||||
@ -671,21 +735,28 @@ export class BounceManager {
|
||||
* Save bounce record to disk
|
||||
* @param bounce Bounce record to save
|
||||
*/
|
||||
private saveBounceRecord(bounce: BounceRecord): void {
|
||||
private async saveBounceRecord(bounce: BounceRecord): Promise<void> {
|
||||
try {
|
||||
const bounceData = JSON.stringify(bounce);
|
||||
const bouncePath = plugins.path.join(
|
||||
paths.dataDir,
|
||||
'emails',
|
||||
'bounces',
|
||||
`${bounce.id}.json`
|
||||
);
|
||||
const bounceData = JSON.stringify(bounce, null, 2);
|
||||
|
||||
// Ensure directory exists
|
||||
const bounceDir = plugins.path.join(paths.dataDir, 'emails', 'bounces');
|
||||
plugins.smartfile.fs.ensureDirSync(bounceDir);
|
||||
|
||||
plugins.smartfile.memory.toFsSync(bounceData, bouncePath);
|
||||
if (this.storageManager) {
|
||||
// Use storage manager
|
||||
await this.storageManager.set(`/email/bounces/records/${bounce.id}.json`, bounceData);
|
||||
} else {
|
||||
// Fall back to filesystem
|
||||
const bouncePath = plugins.path.join(
|
||||
paths.dataDir,
|
||||
'emails',
|
||||
'bounces',
|
||||
`${bounce.id}.json`
|
||||
);
|
||||
|
||||
// Ensure directory exists
|
||||
const bounceDir = plugins.path.join(paths.dataDir, 'emails', 'bounces');
|
||||
plugins.smartfile.fs.ensureDirSync(bounceDir);
|
||||
|
||||
plugins.smartfile.memory.toFsSync(bounceData, bouncePath);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.log('error', `Failed to save bounce record: ${error.message}`);
|
||||
}
|
||||
|
Reference in New Issue
Block a user