chore: websocket

This commit is contained in:
mahyargdz
2025-07-10 15:45:26 +03:30
parent a761dc1718
commit 6933646a0e
18 changed files with 2104 additions and 42 deletions
@@ -0,0 +1,158 @@
import { Injectable, Logger } from "@nestjs/common";
import { firstValueFrom } from "rxjs";
import { MailServerService } from "../../mail-server/services/mail-server.service";
import { EmailGateway } from "../email.gateway";
import { MailboxResolverService } from "./mailbox-resolver.service";
import { EmailNotificationPayload } from "../interfaces/email-events.interface";
@Injectable()
export class EmailNotificationService {
private readonly logger = new Logger(EmailNotificationService.name);
constructor(
private readonly emailGateway: EmailGateway,
private readonly mailServerService: MailServerService,
private readonly mailboxResolverService: MailboxResolverService,
) {}
/**
* Handle new incoming message and emit WebSocket notification
* This method can be called by webhook handlers, polling services, etc.
*/
async handleNewMessage(userId: string, messageId: number, mailboxId?: string) {
try {
this.logger.log(`Processing new message notification for user ${userId}, message ${messageId}`);
// Get the mailbox ID if not provided
const targetMailboxId = mailboxId || (await this.mailboxResolverService.getInboxMailboxId(userId));
// Fetch the message details
const message = await firstValueFrom(this.mailServerService.messages.getMessage(userId, targetMailboxId, messageId));
// Determine mailbox name
const mailboxName = await this.getMailboxName(userId, targetMailboxId);
// Create notification payload
const notificationPayload: EmailNotificationPayload = {
messageId,
userId,
from: {
name: message.from?.name || message.from?.address?.split("@")[0] || "Unknown",
address: message.from?.address || "unknown@example.com",
},
to:
message.to?.map((recipient) => ({
name: recipient.name || recipient.address?.split("@")[0] || "Unknown",
address: recipient.address || "unknown@example.com",
})) || [],
subject: message.subject || "No Subject",
preview: this.extractPreview(
typeof message.text === "string" ? message.text : Array.isArray(message.html) ? message.html.join("") : message.html || "",
),
hasAttachments: Array.isArray(message.attachments) && message.attachments.length > 0,
received: message.idate || new Date().toISOString(),
mailboxId: targetMailboxId,
mailboxName,
isRead: message.seen || false,
isFlagged: message.flagged || false,
size: message.size || 0,
};
// Emit WebSocket notification
await this.emailGateway.notifyNewMessage(notificationPayload);
this.logger.log(`New message notification sent for user ${userId}, message ${messageId}`);
return notificationPayload;
} catch (error) {
this.logger.error(
`Failed to process new message notification for user ${userId}, message ${messageId}: ${
error instanceof Error ? error.message : "Unknown error"
}`,
);
throw error;
}
}
/**
* Handle bulk new messages (useful for initial sync or batch processing)
*/
async handleBulkNewMessages(userId: string, messageIds: number[], mailboxId?: string) {
this.logger.log(`Processing bulk new message notifications for user ${userId}, ${messageIds.length} messages`);
const results = [];
for (const messageId of messageIds) {
try {
const result = await this.handleNewMessage(userId, messageId, mailboxId);
results.push(result);
} catch (error) {
this.logger.error(`Failed to process message ${messageId} in bulk notification: ${error}`);
}
}
return results;
}
/**
* Simulate new message arrival (for testing purposes)
*/
async simulateNewMessage(userId: string, messageData: Partial<EmailNotificationPayload>) {
this.logger.log(`Simulating new message for user ${userId}`);
const simulatedPayload: EmailNotificationPayload = {
messageId: Math.floor(Math.random() * 10000),
userId,
from: {
name: "Test Sender",
address: "test@example.com",
},
to: [
{
name: "Test Recipient",
address: "recipient@example.com",
},
],
subject: "Test Message",
preview: "This is a test message for WebSocket notification",
hasAttachments: false,
received: new Date().toISOString(),
mailboxId: "inbox",
mailboxName: "Inbox",
isRead: false,
isFlagged: false,
size: 1024,
...messageData,
};
await this.emailGateway.notifyNewMessage(simulatedPayload);
return simulatedPayload;
}
private async getMailboxName(userId: string, mailboxId: string): Promise<string> {
try {
const mailboxIds = await this.mailboxResolverService.getUserMailboxIds(userId);
// Check which mailbox this ID corresponds to
if (mailboxIds.inbox === mailboxId) return "Inbox";
if (mailboxIds.sent === mailboxId) return "Sent";
if (mailboxIds.drafts === mailboxId) return "Drafts";
if (mailboxIds.trash === mailboxId) return "Trash";
if (mailboxIds.junk === mailboxId) return "Junk";
if (mailboxIds.archive === mailboxId) return "Archive";
if (mailboxIds.favorite === mailboxId) return "Favorite";
return "Unknown";
} catch (error) {
this.logger.error(`Failed to get mailbox name for ${mailboxId}: ${error}`);
return "Unknown";
}
}
private extractPreview(content: string): string {
// Remove HTML tags and get first 150 characters
const textContent = content.replace(/<[^>]*>/g, "").trim();
return textContent.length > 150 ? textContent.substring(0, 150) + "..." : textContent;
}
}