@push.rocks/smartmta 5.1.2 → 5.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/changelog.md +14 -0
- package/dist_ts/00_commitinfo_data.d.ts +8 -0
- package/dist_ts/00_commitinfo_data.js +9 -0
- package/dist_ts/index.d.ts +3 -0
- package/dist_ts/index.js +4 -0
- package/dist_ts/logger.d.ts +17 -0
- package/dist_ts/logger.js +76 -0
- package/dist_ts/mail/core/classes.bouncemanager.d.ts +185 -0
- package/dist_ts/mail/core/classes.bouncemanager.js +569 -0
- package/dist_ts/mail/core/classes.email.d.ts +291 -0
- package/dist_ts/mail/core/classes.email.js +802 -0
- package/dist_ts/mail/core/classes.emailvalidator.d.ts +61 -0
- package/dist_ts/mail/core/classes.emailvalidator.js +184 -0
- package/dist_ts/mail/core/classes.templatemanager.d.ts +95 -0
- package/dist_ts/mail/core/classes.templatemanager.js +240 -0
- package/dist_ts/mail/core/index.d.ts +4 -0
- package/dist_ts/mail/core/index.js +6 -0
- package/dist_ts/mail/delivery/classes.delivery.queue.d.ts +163 -0
- package/dist_ts/mail/delivery/classes.delivery.queue.js +488 -0
- package/dist_ts/mail/delivery/classes.delivery.system.d.ts +160 -0
- package/dist_ts/mail/delivery/classes.delivery.system.js +630 -0
- package/dist_ts/mail/delivery/classes.unified.rate.limiter.d.ts +200 -0
- package/dist_ts/mail/delivery/classes.unified.rate.limiter.js +820 -0
- package/dist_ts/mail/delivery/index.d.ts +4 -0
- package/dist_ts/mail/delivery/index.js +6 -0
- package/dist_ts/mail/delivery/interfaces.d.ts +140 -0
- package/dist_ts/mail/delivery/interfaces.js +17 -0
- package/dist_ts/mail/index.d.ts +7 -0
- package/dist_ts/mail/index.js +12 -0
- package/dist_ts/mail/routing/classes.dkim.manager.d.ts +25 -0
- package/dist_ts/mail/routing/classes.dkim.manager.js +127 -0
- package/dist_ts/mail/routing/classes.dns.manager.d.ts +79 -0
- package/dist_ts/mail/routing/classes.dns.manager.js +415 -0
- package/dist_ts/mail/routing/classes.domain.registry.d.ts +54 -0
- package/dist_ts/mail/routing/classes.domain.registry.js +119 -0
- package/dist_ts/mail/routing/classes.email.action.executor.d.ts +33 -0
- package/dist_ts/mail/routing/classes.email.action.executor.js +137 -0
- package/dist_ts/mail/routing/classes.email.router.d.ts +171 -0
- package/dist_ts/mail/routing/classes.email.router.js +494 -0
- package/dist_ts/mail/routing/classes.unified.email.server.d.ts +241 -0
- package/dist_ts/mail/routing/classes.unified.email.server.js +935 -0
- package/dist_ts/mail/routing/index.d.ts +7 -0
- package/dist_ts/mail/routing/index.js +9 -0
- package/dist_ts/mail/routing/interfaces.d.ts +187 -0
- package/dist_ts/mail/routing/interfaces.js +2 -0
- package/dist_ts/mail/security/classes.dkimcreator.d.ts +72 -0
- package/dist_ts/mail/security/classes.dkimcreator.js +360 -0
- package/dist_ts/mail/security/classes.spfverifier.d.ts +62 -0
- package/dist_ts/mail/security/classes.spfverifier.js +87 -0
- package/dist_ts/mail/security/index.d.ts +2 -0
- package/dist_ts/mail/security/index.js +4 -0
- package/dist_ts/paths.d.ts +14 -0
- package/dist_ts/paths.js +39 -0
- package/dist_ts/plugins.d.ts +24 -0
- package/dist_ts/plugins.js +28 -0
- package/dist_ts/security/classes.contentscanner.d.ts +130 -0
- package/dist_ts/security/classes.contentscanner.js +338 -0
- package/dist_ts/security/classes.ipreputationchecker.d.ts +73 -0
- package/dist_ts/security/classes.ipreputationchecker.js +263 -0
- package/dist_ts/security/classes.rustsecuritybridge.d.ts +398 -0
- package/dist_ts/security/classes.rustsecuritybridge.js +484 -0
- package/dist_ts/security/classes.securitylogger.d.ts +140 -0
- package/dist_ts/security/classes.securitylogger.js +235 -0
- package/dist_ts/security/index.d.ts +4 -0
- package/dist_ts/security/index.js +5 -0
- package/package.json +6 -1
- package/readme.md +52 -9
- package/ts/00_commitinfo_data.ts +8 -0
- package/ts/index.ts +3 -0
- package/ts/logger.ts +91 -0
- package/ts/mail/core/classes.bouncemanager.ts +731 -0
- package/ts/mail/core/classes.email.ts +942 -0
- package/ts/mail/core/classes.emailvalidator.ts +239 -0
- package/ts/mail/core/classes.templatemanager.ts +320 -0
- package/ts/mail/core/index.ts +5 -0
- package/ts/mail/delivery/classes.delivery.queue.ts +645 -0
- package/ts/mail/delivery/classes.delivery.system.ts +816 -0
- package/ts/mail/delivery/classes.unified.rate.limiter.ts +1053 -0
- package/ts/mail/delivery/index.ts +5 -0
- package/ts/mail/delivery/interfaces.ts +167 -0
- package/ts/mail/index.ts +17 -0
- package/ts/mail/routing/classes.dkim.manager.ts +157 -0
- package/ts/mail/routing/classes.dns.manager.ts +573 -0
- package/ts/mail/routing/classes.domain.registry.ts +139 -0
- package/ts/mail/routing/classes.email.action.executor.ts +175 -0
- package/ts/mail/routing/classes.email.router.ts +575 -0
- package/ts/mail/routing/classes.unified.email.server.ts +1207 -0
- package/ts/mail/routing/index.ts +9 -0
- package/ts/mail/routing/interfaces.ts +202 -0
- package/ts/mail/security/classes.dkimcreator.ts +447 -0
- package/ts/mail/security/classes.spfverifier.ts +126 -0
- package/ts/mail/security/index.ts +3 -0
- package/ts/paths.ts +48 -0
- package/ts/plugins.ts +53 -0
- package/ts/security/classes.contentscanner.ts +400 -0
- package/ts/security/classes.ipreputationchecker.ts +315 -0
- package/ts/security/classes.rustsecuritybridge.ts +943 -0
- package/ts/security/classes.securitylogger.ts +299 -0
- package/ts/security/index.ts +40 -0
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { EventEmitter } from 'node:events';
|
|
2
|
+
import { type EmailProcessingMode } from './interfaces.js';
|
|
3
|
+
import type { IEmailRoute } from '../routing/interfaces.js';
|
|
4
|
+
/**
|
|
5
|
+
* Queue item status
|
|
6
|
+
*/
|
|
7
|
+
export type QueueItemStatus = 'pending' | 'processing' | 'delivered' | 'failed' | 'deferred';
|
|
8
|
+
/**
|
|
9
|
+
* Queue item interface
|
|
10
|
+
*/
|
|
11
|
+
export interface IQueueItem {
|
|
12
|
+
id: string;
|
|
13
|
+
processingMode: EmailProcessingMode;
|
|
14
|
+
processingResult: any;
|
|
15
|
+
route: IEmailRoute;
|
|
16
|
+
status: QueueItemStatus;
|
|
17
|
+
attempts: number;
|
|
18
|
+
nextAttempt: Date;
|
|
19
|
+
lastError?: string;
|
|
20
|
+
createdAt: Date;
|
|
21
|
+
updatedAt: Date;
|
|
22
|
+
deliveredAt?: Date;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Queue options interface
|
|
26
|
+
*/
|
|
27
|
+
export interface IQueueOptions {
|
|
28
|
+
storageType?: 'memory' | 'disk';
|
|
29
|
+
persistentPath?: string;
|
|
30
|
+
checkInterval?: number;
|
|
31
|
+
maxQueueSize?: number;
|
|
32
|
+
maxPerDestination?: number;
|
|
33
|
+
maxRetries?: number;
|
|
34
|
+
baseRetryDelay?: number;
|
|
35
|
+
maxRetryDelay?: number;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Queue statistics interface
|
|
39
|
+
*/
|
|
40
|
+
export interface IQueueStats {
|
|
41
|
+
queueSize: number;
|
|
42
|
+
status: {
|
|
43
|
+
pending: number;
|
|
44
|
+
processing: number;
|
|
45
|
+
delivered: number;
|
|
46
|
+
failed: number;
|
|
47
|
+
deferred: number;
|
|
48
|
+
};
|
|
49
|
+
modes: {
|
|
50
|
+
forward: number;
|
|
51
|
+
mta: number;
|
|
52
|
+
process: number;
|
|
53
|
+
};
|
|
54
|
+
oldestItem?: Date;
|
|
55
|
+
newestItem?: Date;
|
|
56
|
+
averageAttempts: number;
|
|
57
|
+
totalProcessed: number;
|
|
58
|
+
processingActive: boolean;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* A unified queue for all email modes
|
|
62
|
+
*/
|
|
63
|
+
export declare class UnifiedDeliveryQueue extends EventEmitter {
|
|
64
|
+
private options;
|
|
65
|
+
private queue;
|
|
66
|
+
private checkTimer?;
|
|
67
|
+
private stats;
|
|
68
|
+
private processing;
|
|
69
|
+
private totalProcessed;
|
|
70
|
+
/**
|
|
71
|
+
* Create a new unified delivery queue
|
|
72
|
+
* @param options Queue options
|
|
73
|
+
*/
|
|
74
|
+
constructor(options: IQueueOptions);
|
|
75
|
+
/**
|
|
76
|
+
* Initialize the queue
|
|
77
|
+
*/
|
|
78
|
+
initialize(): Promise<void>;
|
|
79
|
+
/**
|
|
80
|
+
* Start queue processing
|
|
81
|
+
*/
|
|
82
|
+
private startProcessing;
|
|
83
|
+
/**
|
|
84
|
+
* Stop queue processing
|
|
85
|
+
*/
|
|
86
|
+
private stopProcessing;
|
|
87
|
+
/**
|
|
88
|
+
* Check for items that need to be processed
|
|
89
|
+
*/
|
|
90
|
+
private processQueue;
|
|
91
|
+
/**
|
|
92
|
+
* Add an item to the queue
|
|
93
|
+
* @param processingResult Processing result to queue
|
|
94
|
+
* @param mode Processing mode
|
|
95
|
+
* @param route Email route
|
|
96
|
+
*/
|
|
97
|
+
enqueue(processingResult: any, mode: EmailProcessingMode, route: IEmailRoute): Promise<string>;
|
|
98
|
+
/**
|
|
99
|
+
* Get an item from the queue
|
|
100
|
+
* @param id Item ID
|
|
101
|
+
*/
|
|
102
|
+
getItem(id: string): IQueueItem | undefined;
|
|
103
|
+
/**
|
|
104
|
+
* Mark an item as being processed
|
|
105
|
+
* @param id Item ID
|
|
106
|
+
*/
|
|
107
|
+
markProcessing(id: string): Promise<boolean>;
|
|
108
|
+
/**
|
|
109
|
+
* Mark an item as delivered
|
|
110
|
+
* @param id Item ID
|
|
111
|
+
*/
|
|
112
|
+
markDelivered(id: string): Promise<boolean>;
|
|
113
|
+
/**
|
|
114
|
+
* Mark an item as failed
|
|
115
|
+
* @param id Item ID
|
|
116
|
+
* @param error Error message
|
|
117
|
+
*/
|
|
118
|
+
markFailed(id: string, error: string): Promise<boolean>;
|
|
119
|
+
/**
|
|
120
|
+
* Remove an item from the queue
|
|
121
|
+
* @param id Item ID
|
|
122
|
+
*/
|
|
123
|
+
removeItem(id: string): Promise<boolean>;
|
|
124
|
+
/**
|
|
125
|
+
* Persist an item to disk
|
|
126
|
+
* @param item Item to persist
|
|
127
|
+
*/
|
|
128
|
+
private persistItem;
|
|
129
|
+
/**
|
|
130
|
+
* Remove an item from disk
|
|
131
|
+
* @param id Item ID
|
|
132
|
+
*/
|
|
133
|
+
private removeItemFromDisk;
|
|
134
|
+
/**
|
|
135
|
+
* Load queue items from disk
|
|
136
|
+
*/
|
|
137
|
+
private loadFromDisk;
|
|
138
|
+
/**
|
|
139
|
+
* Update queue statistics
|
|
140
|
+
*/
|
|
141
|
+
private updateStats;
|
|
142
|
+
/**
|
|
143
|
+
* Get queue statistics
|
|
144
|
+
*/
|
|
145
|
+
getStats(): IQueueStats;
|
|
146
|
+
/**
|
|
147
|
+
* Pause queue processing
|
|
148
|
+
*/
|
|
149
|
+
pause(): void;
|
|
150
|
+
/**
|
|
151
|
+
* Resume queue processing
|
|
152
|
+
*/
|
|
153
|
+
resume(): void;
|
|
154
|
+
/**
|
|
155
|
+
* Clean up old delivered and failed items
|
|
156
|
+
* @param maxAge Maximum age in milliseconds (default: 7 days)
|
|
157
|
+
*/
|
|
158
|
+
cleanupOldItems(maxAge?: number): Promise<number>;
|
|
159
|
+
/**
|
|
160
|
+
* Shutdown the queue
|
|
161
|
+
*/
|
|
162
|
+
shutdown(): Promise<void>;
|
|
163
|
+
}
|
|
@@ -0,0 +1,488 @@
|
|
|
1
|
+
import * as plugins from '../../plugins.js';
|
|
2
|
+
import { EventEmitter } from 'node:events';
|
|
3
|
+
import * as fs from 'node:fs';
|
|
4
|
+
import * as path from 'node:path';
|
|
5
|
+
import { logger } from '../../logger.js';
|
|
6
|
+
import {} from './interfaces.js';
|
|
7
|
+
/**
|
|
8
|
+
* A unified queue for all email modes
|
|
9
|
+
*/
|
|
10
|
+
export class UnifiedDeliveryQueue extends EventEmitter {
|
|
11
|
+
options;
|
|
12
|
+
queue = new Map();
|
|
13
|
+
checkTimer;
|
|
14
|
+
stats;
|
|
15
|
+
processing = false;
|
|
16
|
+
totalProcessed = 0;
|
|
17
|
+
/**
|
|
18
|
+
* Create a new unified delivery queue
|
|
19
|
+
* @param options Queue options
|
|
20
|
+
*/
|
|
21
|
+
constructor(options) {
|
|
22
|
+
super();
|
|
23
|
+
// Set default options
|
|
24
|
+
this.options = {
|
|
25
|
+
storageType: options.storageType || 'memory',
|
|
26
|
+
persistentPath: options.persistentPath || path.join(process.cwd(), 'email-queue'),
|
|
27
|
+
checkInterval: options.checkInterval || 30000, // 30 seconds
|
|
28
|
+
maxQueueSize: options.maxQueueSize || 10000,
|
|
29
|
+
maxPerDestination: options.maxPerDestination || 100,
|
|
30
|
+
maxRetries: options.maxRetries || 5,
|
|
31
|
+
baseRetryDelay: options.baseRetryDelay || 60000, // 1 minute
|
|
32
|
+
maxRetryDelay: options.maxRetryDelay || 3600000 // 1 hour
|
|
33
|
+
};
|
|
34
|
+
// Initialize statistics
|
|
35
|
+
this.stats = {
|
|
36
|
+
queueSize: 0,
|
|
37
|
+
status: {
|
|
38
|
+
pending: 0,
|
|
39
|
+
processing: 0,
|
|
40
|
+
delivered: 0,
|
|
41
|
+
failed: 0,
|
|
42
|
+
deferred: 0
|
|
43
|
+
},
|
|
44
|
+
modes: {
|
|
45
|
+
forward: 0,
|
|
46
|
+
mta: 0,
|
|
47
|
+
process: 0
|
|
48
|
+
},
|
|
49
|
+
averageAttempts: 0,
|
|
50
|
+
totalProcessed: 0,
|
|
51
|
+
processingActive: false
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Initialize the queue
|
|
56
|
+
*/
|
|
57
|
+
async initialize() {
|
|
58
|
+
logger.log('info', 'Initializing UnifiedDeliveryQueue');
|
|
59
|
+
try {
|
|
60
|
+
// Create persistent storage directory if using disk storage
|
|
61
|
+
if (this.options.storageType === 'disk') {
|
|
62
|
+
if (!fs.existsSync(this.options.persistentPath)) {
|
|
63
|
+
fs.mkdirSync(this.options.persistentPath, { recursive: true });
|
|
64
|
+
}
|
|
65
|
+
// Load existing items from disk
|
|
66
|
+
await this.loadFromDisk();
|
|
67
|
+
}
|
|
68
|
+
// Start the queue processing timer
|
|
69
|
+
this.startProcessing();
|
|
70
|
+
// Emit initialized event
|
|
71
|
+
this.emit('initialized');
|
|
72
|
+
logger.log('info', 'UnifiedDeliveryQueue initialized successfully');
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
logger.log('error', `Failed to initialize queue: ${error.message}`);
|
|
76
|
+
throw error;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Start queue processing
|
|
81
|
+
*/
|
|
82
|
+
startProcessing() {
|
|
83
|
+
if (this.checkTimer) {
|
|
84
|
+
clearInterval(this.checkTimer);
|
|
85
|
+
}
|
|
86
|
+
this.checkTimer = setInterval(() => this.processQueue(), this.options.checkInterval);
|
|
87
|
+
this.processing = true;
|
|
88
|
+
this.stats.processingActive = true;
|
|
89
|
+
this.emit('processingStarted');
|
|
90
|
+
logger.log('info', 'Queue processing started');
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Stop queue processing
|
|
94
|
+
*/
|
|
95
|
+
stopProcessing() {
|
|
96
|
+
if (this.checkTimer) {
|
|
97
|
+
clearInterval(this.checkTimer);
|
|
98
|
+
this.checkTimer = undefined;
|
|
99
|
+
}
|
|
100
|
+
this.processing = false;
|
|
101
|
+
this.stats.processingActive = false;
|
|
102
|
+
this.emit('processingStopped');
|
|
103
|
+
logger.log('info', 'Queue processing stopped');
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Check for items that need to be processed
|
|
107
|
+
*/
|
|
108
|
+
async processQueue() {
|
|
109
|
+
try {
|
|
110
|
+
const now = new Date();
|
|
111
|
+
let readyItems = [];
|
|
112
|
+
// Find items ready for processing
|
|
113
|
+
for (const item of this.queue.values()) {
|
|
114
|
+
if (item.status === 'pending' || (item.status === 'deferred' && item.nextAttempt <= now)) {
|
|
115
|
+
readyItems.push(item);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (readyItems.length === 0) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
// Sort by oldest first
|
|
122
|
+
readyItems.sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime());
|
|
123
|
+
// Emit event for ready items
|
|
124
|
+
this.emit('itemsReady', readyItems);
|
|
125
|
+
logger.log('info', `Found ${readyItems.length} items ready for processing`);
|
|
126
|
+
// Update statistics
|
|
127
|
+
this.updateStats();
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
logger.log('error', `Error processing queue: ${error.message}`);
|
|
131
|
+
this.emit('error', error);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Add an item to the queue
|
|
136
|
+
* @param processingResult Processing result to queue
|
|
137
|
+
* @param mode Processing mode
|
|
138
|
+
* @param route Email route
|
|
139
|
+
*/
|
|
140
|
+
async enqueue(processingResult, mode, route) {
|
|
141
|
+
// Check if queue is full
|
|
142
|
+
if (this.queue.size >= this.options.maxQueueSize) {
|
|
143
|
+
throw new Error('Queue is full');
|
|
144
|
+
}
|
|
145
|
+
// Generate a unique ID
|
|
146
|
+
const id = `${Date.now()}-${Math.random().toString(36).substring(2, 15)}`;
|
|
147
|
+
// Create queue item
|
|
148
|
+
const item = {
|
|
149
|
+
id,
|
|
150
|
+
processingMode: mode,
|
|
151
|
+
processingResult,
|
|
152
|
+
route,
|
|
153
|
+
status: 'pending',
|
|
154
|
+
attempts: 0,
|
|
155
|
+
nextAttempt: new Date(),
|
|
156
|
+
createdAt: new Date(),
|
|
157
|
+
updatedAt: new Date()
|
|
158
|
+
};
|
|
159
|
+
// Add to queue
|
|
160
|
+
this.queue.set(id, item);
|
|
161
|
+
// Persist to disk if using disk storage
|
|
162
|
+
if (this.options.storageType === 'disk') {
|
|
163
|
+
await this.persistItem(item);
|
|
164
|
+
}
|
|
165
|
+
// Update statistics
|
|
166
|
+
this.updateStats();
|
|
167
|
+
// Emit event
|
|
168
|
+
this.emit('itemEnqueued', item);
|
|
169
|
+
logger.log('info', `Item enqueued with ID ${id}, mode: ${mode}`);
|
|
170
|
+
return id;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Get an item from the queue
|
|
174
|
+
* @param id Item ID
|
|
175
|
+
*/
|
|
176
|
+
getItem(id) {
|
|
177
|
+
return this.queue.get(id);
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Mark an item as being processed
|
|
181
|
+
* @param id Item ID
|
|
182
|
+
*/
|
|
183
|
+
async markProcessing(id) {
|
|
184
|
+
const item = this.queue.get(id);
|
|
185
|
+
if (!item) {
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
188
|
+
// Update status
|
|
189
|
+
item.status = 'processing';
|
|
190
|
+
item.attempts++;
|
|
191
|
+
item.updatedAt = new Date();
|
|
192
|
+
// Persist changes if using disk storage
|
|
193
|
+
if (this.options.storageType === 'disk') {
|
|
194
|
+
await this.persistItem(item);
|
|
195
|
+
}
|
|
196
|
+
// Update statistics
|
|
197
|
+
this.updateStats();
|
|
198
|
+
// Emit event
|
|
199
|
+
this.emit('itemProcessing', item);
|
|
200
|
+
logger.log('info', `Item ${id} marked as processing, attempt ${item.attempts}`);
|
|
201
|
+
return true;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Mark an item as delivered
|
|
205
|
+
* @param id Item ID
|
|
206
|
+
*/
|
|
207
|
+
async markDelivered(id) {
|
|
208
|
+
const item = this.queue.get(id);
|
|
209
|
+
if (!item) {
|
|
210
|
+
return false;
|
|
211
|
+
}
|
|
212
|
+
// Update status
|
|
213
|
+
item.status = 'delivered';
|
|
214
|
+
item.updatedAt = new Date();
|
|
215
|
+
item.deliveredAt = new Date();
|
|
216
|
+
// Persist changes if using disk storage
|
|
217
|
+
if (this.options.storageType === 'disk') {
|
|
218
|
+
await this.persistItem(item);
|
|
219
|
+
}
|
|
220
|
+
// Update statistics
|
|
221
|
+
this.totalProcessed++;
|
|
222
|
+
this.updateStats();
|
|
223
|
+
// Emit event
|
|
224
|
+
this.emit('itemDelivered', item);
|
|
225
|
+
logger.log('info', `Item ${id} marked as delivered after ${item.attempts} attempts`);
|
|
226
|
+
return true;
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Mark an item as failed
|
|
230
|
+
* @param id Item ID
|
|
231
|
+
* @param error Error message
|
|
232
|
+
*/
|
|
233
|
+
async markFailed(id, error) {
|
|
234
|
+
const item = this.queue.get(id);
|
|
235
|
+
if (!item) {
|
|
236
|
+
return false;
|
|
237
|
+
}
|
|
238
|
+
// Determine if we should retry
|
|
239
|
+
if (item.attempts < this.options.maxRetries) {
|
|
240
|
+
// Calculate next retry time with exponential backoff
|
|
241
|
+
const delay = Math.min(this.options.baseRetryDelay * Math.pow(2, item.attempts - 1), this.options.maxRetryDelay);
|
|
242
|
+
// Update status
|
|
243
|
+
item.status = 'deferred';
|
|
244
|
+
item.lastError = error;
|
|
245
|
+
item.nextAttempt = new Date(Date.now() + delay);
|
|
246
|
+
item.updatedAt = new Date();
|
|
247
|
+
// Persist changes if using disk storage
|
|
248
|
+
if (this.options.storageType === 'disk') {
|
|
249
|
+
await this.persistItem(item);
|
|
250
|
+
}
|
|
251
|
+
// Emit event
|
|
252
|
+
this.emit('itemDeferred', item);
|
|
253
|
+
logger.log('info', `Item ${id} deferred for ${delay}ms, attempt ${item.attempts}, error: ${error}`);
|
|
254
|
+
}
|
|
255
|
+
else {
|
|
256
|
+
// Mark as permanently failed
|
|
257
|
+
item.status = 'failed';
|
|
258
|
+
item.lastError = error;
|
|
259
|
+
item.updatedAt = new Date();
|
|
260
|
+
// Persist changes if using disk storage
|
|
261
|
+
if (this.options.storageType === 'disk') {
|
|
262
|
+
await this.persistItem(item);
|
|
263
|
+
}
|
|
264
|
+
// Update statistics
|
|
265
|
+
this.totalProcessed++;
|
|
266
|
+
// Emit event
|
|
267
|
+
this.emit('itemFailed', item);
|
|
268
|
+
logger.log('warn', `Item ${id} permanently failed after ${item.attempts} attempts, error: ${error}`);
|
|
269
|
+
}
|
|
270
|
+
// Update statistics
|
|
271
|
+
this.updateStats();
|
|
272
|
+
return true;
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Remove an item from the queue
|
|
276
|
+
* @param id Item ID
|
|
277
|
+
*/
|
|
278
|
+
async removeItem(id) {
|
|
279
|
+
const item = this.queue.get(id);
|
|
280
|
+
if (!item) {
|
|
281
|
+
return false;
|
|
282
|
+
}
|
|
283
|
+
// Remove from queue
|
|
284
|
+
this.queue.delete(id);
|
|
285
|
+
// Remove from disk if using disk storage
|
|
286
|
+
if (this.options.storageType === 'disk') {
|
|
287
|
+
await this.removeItemFromDisk(id);
|
|
288
|
+
}
|
|
289
|
+
// Update statistics
|
|
290
|
+
this.updateStats();
|
|
291
|
+
// Emit event
|
|
292
|
+
this.emit('itemRemoved', item);
|
|
293
|
+
logger.log('info', `Item ${id} removed from queue`);
|
|
294
|
+
return true;
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Persist an item to disk
|
|
298
|
+
* @param item Item to persist
|
|
299
|
+
*/
|
|
300
|
+
async persistItem(item) {
|
|
301
|
+
try {
|
|
302
|
+
const filePath = path.join(this.options.persistentPath, `${item.id}.json`);
|
|
303
|
+
await fs.promises.writeFile(filePath, JSON.stringify(item, null, 2), 'utf8');
|
|
304
|
+
}
|
|
305
|
+
catch (error) {
|
|
306
|
+
logger.log('error', `Failed to persist item ${item.id}: ${error.message}`);
|
|
307
|
+
this.emit('error', error);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Remove an item from disk
|
|
312
|
+
* @param id Item ID
|
|
313
|
+
*/
|
|
314
|
+
async removeItemFromDisk(id) {
|
|
315
|
+
try {
|
|
316
|
+
const filePath = path.join(this.options.persistentPath, `${id}.json`);
|
|
317
|
+
if (fs.existsSync(filePath)) {
|
|
318
|
+
await fs.promises.unlink(filePath);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
catch (error) {
|
|
322
|
+
logger.log('error', `Failed to remove item ${id} from disk: ${error.message}`);
|
|
323
|
+
this.emit('error', error);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Load queue items from disk
|
|
328
|
+
*/
|
|
329
|
+
async loadFromDisk() {
|
|
330
|
+
try {
|
|
331
|
+
// Check if directory exists
|
|
332
|
+
if (!fs.existsSync(this.options.persistentPath)) {
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
// Get all JSON files
|
|
336
|
+
const files = fs.readdirSync(this.options.persistentPath).filter(file => file.endsWith('.json'));
|
|
337
|
+
// Load each file
|
|
338
|
+
for (const file of files) {
|
|
339
|
+
try {
|
|
340
|
+
const filePath = path.join(this.options.persistentPath, file);
|
|
341
|
+
const data = await fs.promises.readFile(filePath, 'utf8');
|
|
342
|
+
const item = JSON.parse(data);
|
|
343
|
+
// Convert date strings to Date objects
|
|
344
|
+
item.createdAt = new Date(item.createdAt);
|
|
345
|
+
item.updatedAt = new Date(item.updatedAt);
|
|
346
|
+
item.nextAttempt = new Date(item.nextAttempt);
|
|
347
|
+
if (item.deliveredAt) {
|
|
348
|
+
item.deliveredAt = new Date(item.deliveredAt);
|
|
349
|
+
}
|
|
350
|
+
// Add to queue
|
|
351
|
+
this.queue.set(item.id, item);
|
|
352
|
+
}
|
|
353
|
+
catch (error) {
|
|
354
|
+
logger.log('error', `Failed to load item from ${file}: ${error.message}`);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
// Update statistics
|
|
358
|
+
this.updateStats();
|
|
359
|
+
logger.log('info', `Loaded ${this.queue.size} items from disk`);
|
|
360
|
+
}
|
|
361
|
+
catch (error) {
|
|
362
|
+
logger.log('error', `Failed to load items from disk: ${error.message}`);
|
|
363
|
+
throw error;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* Update queue statistics
|
|
368
|
+
*/
|
|
369
|
+
updateStats() {
|
|
370
|
+
// Reset counters
|
|
371
|
+
this.stats.queueSize = this.queue.size;
|
|
372
|
+
this.stats.status = {
|
|
373
|
+
pending: 0,
|
|
374
|
+
processing: 0,
|
|
375
|
+
delivered: 0,
|
|
376
|
+
failed: 0,
|
|
377
|
+
deferred: 0
|
|
378
|
+
};
|
|
379
|
+
this.stats.modes = {
|
|
380
|
+
forward: 0,
|
|
381
|
+
mta: 0,
|
|
382
|
+
process: 0
|
|
383
|
+
};
|
|
384
|
+
let totalAttempts = 0;
|
|
385
|
+
let oldestTime = Date.now();
|
|
386
|
+
let newestTime = 0;
|
|
387
|
+
// Count by status and mode
|
|
388
|
+
for (const item of this.queue.values()) {
|
|
389
|
+
// Count by status
|
|
390
|
+
this.stats.status[item.status]++;
|
|
391
|
+
// Count by mode
|
|
392
|
+
this.stats.modes[item.processingMode]++;
|
|
393
|
+
// Track total attempts
|
|
394
|
+
totalAttempts += item.attempts;
|
|
395
|
+
// Track oldest and newest
|
|
396
|
+
const itemTime = item.createdAt.getTime();
|
|
397
|
+
if (itemTime < oldestTime) {
|
|
398
|
+
oldestTime = itemTime;
|
|
399
|
+
}
|
|
400
|
+
if (itemTime > newestTime) {
|
|
401
|
+
newestTime = itemTime;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
// Calculate average attempts
|
|
405
|
+
this.stats.averageAttempts = this.queue.size > 0 ? totalAttempts / this.queue.size : 0;
|
|
406
|
+
// Set oldest and newest
|
|
407
|
+
this.stats.oldestItem = this.queue.size > 0 ? new Date(oldestTime) : undefined;
|
|
408
|
+
this.stats.newestItem = this.queue.size > 0 ? new Date(newestTime) : undefined;
|
|
409
|
+
// Set total processed
|
|
410
|
+
this.stats.totalProcessed = this.totalProcessed;
|
|
411
|
+
// Set processing active
|
|
412
|
+
this.stats.processingActive = this.processing;
|
|
413
|
+
// Emit statistics event
|
|
414
|
+
this.emit('statsUpdated', this.stats);
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* Get queue statistics
|
|
418
|
+
*/
|
|
419
|
+
getStats() {
|
|
420
|
+
return { ...this.stats };
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* Pause queue processing
|
|
424
|
+
*/
|
|
425
|
+
pause() {
|
|
426
|
+
if (this.processing) {
|
|
427
|
+
this.stopProcessing();
|
|
428
|
+
logger.log('info', 'Queue processing paused');
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* Resume queue processing
|
|
433
|
+
*/
|
|
434
|
+
resume() {
|
|
435
|
+
if (!this.processing) {
|
|
436
|
+
this.startProcessing();
|
|
437
|
+
logger.log('info', 'Queue processing resumed');
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* Clean up old delivered and failed items
|
|
442
|
+
* @param maxAge Maximum age in milliseconds (default: 7 days)
|
|
443
|
+
*/
|
|
444
|
+
async cleanupOldItems(maxAge = 7 * 24 * 60 * 60 * 1000) {
|
|
445
|
+
const cutoff = new Date(Date.now() - maxAge);
|
|
446
|
+
let removedCount = 0;
|
|
447
|
+
// Find old items
|
|
448
|
+
for (const item of this.queue.values()) {
|
|
449
|
+
if (['delivered', 'failed'].includes(item.status) && item.updatedAt < cutoff) {
|
|
450
|
+
// Remove item
|
|
451
|
+
await this.removeItem(item.id);
|
|
452
|
+
removedCount++;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
logger.log('info', `Cleaned up ${removedCount} old items`);
|
|
456
|
+
return removedCount;
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* Shutdown the queue
|
|
460
|
+
*/
|
|
461
|
+
async shutdown() {
|
|
462
|
+
logger.log('info', 'Shutting down UnifiedDeliveryQueue');
|
|
463
|
+
// Stop processing
|
|
464
|
+
this.stopProcessing();
|
|
465
|
+
// Clear the check timer to prevent memory leaks
|
|
466
|
+
if (this.checkTimer) {
|
|
467
|
+
clearInterval(this.checkTimer);
|
|
468
|
+
this.checkTimer = undefined;
|
|
469
|
+
}
|
|
470
|
+
// If using disk storage, make sure all items are persisted
|
|
471
|
+
if (this.options.storageType === 'disk') {
|
|
472
|
+
const pendingWrites = [];
|
|
473
|
+
for (const item of this.queue.values()) {
|
|
474
|
+
pendingWrites.push(this.persistItem(item));
|
|
475
|
+
}
|
|
476
|
+
// Wait for all writes to complete
|
|
477
|
+
await Promise.all(pendingWrites);
|
|
478
|
+
}
|
|
479
|
+
// Clear the queue (memory only)
|
|
480
|
+
this.queue.clear();
|
|
481
|
+
// Update statistics
|
|
482
|
+
this.updateStats();
|
|
483
|
+
// Emit shutdown event
|
|
484
|
+
this.emit('shutdown');
|
|
485
|
+
logger.log('info', 'UnifiedDeliveryQueue shut down successfully');
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5kZWxpdmVyeS5xdWV1ZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3RzL21haWwvZGVsaXZlcnkvY2xhc3Nlcy5kZWxpdmVyeS5xdWV1ZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLGtCQUFrQixDQUFDO0FBQzVDLE9BQU8sRUFBRSxZQUFZLEVBQUUsTUFBTSxhQUFhLENBQUM7QUFDM0MsT0FBTyxLQUFLLEVBQUUsTUFBTSxTQUFTLENBQUM7QUFDOUIsT0FBTyxLQUFLLElBQUksTUFBTSxXQUFXLENBQUM7QUFDbEMsT0FBTyxFQUFFLE1BQU0sRUFBRSxNQUFNLGlCQUFpQixDQUFDO0FBQ3pDLE9BQU8sRUFBNEIsTUFBTSxpQkFBaUIsQ0FBQztBQW9FM0Q7O0dBRUc7QUFDSCxNQUFNLE9BQU8sb0JBQXFCLFNBQVEsWUFBWTtJQUM1QyxPQUFPLENBQTBCO0lBQ2pDLEtBQUssR0FBNEIsSUFBSSxHQUFHLEVBQUUsQ0FBQztJQUMzQyxVQUFVLENBQWtCO0lBQzVCLEtBQUssQ0FBYztJQUNuQixVQUFVLEdBQVksS0FBSyxDQUFDO0lBQzVCLGNBQWMsR0FBVyxDQUFDLENBQUM7SUFFbkM7OztPQUdHO0lBQ0gsWUFBWSxPQUFzQjtRQUNoQyxLQUFLLEVBQUUsQ0FBQztRQUVSLHNCQUFzQjtRQUN0QixJQUFJLENBQUMsT0FBTyxHQUFHO1lBQ2IsV0FBVyxFQUFFLE9BQU8sQ0FBQyxXQUFXLElBQUksUUFBUTtZQUM1QyxjQUFjLEVBQUUsT0FBTyxDQUFDLGNBQWMsSUFBSSxJQUFJLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxHQUFHLEVBQUUsRUFBRSxhQUFhLENBQUM7WUFDakYsYUFBYSxFQUFFLE9BQU8sQ0FBQyxhQUFhLElBQUksS0FBSyxFQUFFLGFBQWE7WUFDNUQsWUFBWSxFQUFFLE9BQU8sQ0FBQyxZQUFZLElBQUksS0FBSztZQUMzQyxpQkFBaUIsRUFBRSxPQUFPLENB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
|