feat(core): Introduce ImapClient and ImapServer classes for enhanced IMAP support
This commit is contained in:
@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@push.rocks/smartimap',
|
||||
version: '1.1.0',
|
||||
version: '1.2.0',
|
||||
description: 'A Node.js library for event-driven streaming and parsing of IMAP email messages.'
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import * as plugins from './smartimap.plugins.js';
|
||||
|
||||
export interface SmartImapConfig {
|
||||
export interface ImapClientConfig {
|
||||
host: string;
|
||||
port?: number; // Defaults to 993 if secure, else 143
|
||||
secure?: boolean; // Defaults to true
|
||||
@ -12,7 +12,9 @@ export interface SmartImapConfig {
|
||||
filter?: plugins.imapflow.SearchObject; // IMAP search criteria object
|
||||
}
|
||||
|
||||
export class SmartImap extends plugins.events.EventEmitter {
|
||||
export type SmartImapMessage = plugins.mailparser.ParsedMail;
|
||||
|
||||
export class ImapClient extends plugins.events.EventEmitter {
|
||||
private client: plugins.imapflow.ImapFlow;
|
||||
private mailbox: string;
|
||||
private filter: plugins.imapflow.SearchObject;
|
||||
@ -20,7 +22,7 @@ export class SmartImap extends plugins.events.EventEmitter {
|
||||
private processing: boolean = false;
|
||||
private seenUids: Set<number> = new Set();
|
||||
|
||||
constructor(private config: SmartImapConfig) {
|
||||
constructor(private config: ImapClientConfig) {
|
||||
super();
|
||||
|
||||
this.mailbox = config.mailbox || 'INBOX';
|
162
ts/classes.imapserver.ts
Normal file
162
ts/classes.imapserver.ts
Normal file
@ -0,0 +1,162 @@
|
||||
import * as net from "net";
|
||||
|
||||
export interface IImapServerMessage {
|
||||
id: string;
|
||||
subject: string;
|
||||
sender: string;
|
||||
recipient: string;
|
||||
date: Date;
|
||||
body: string;
|
||||
}
|
||||
|
||||
export interface IImapServerInbox {
|
||||
name: string;
|
||||
messages: IImapServerMessage[];
|
||||
}
|
||||
|
||||
export interface IImapServerUser {
|
||||
username: string;
|
||||
password: string;
|
||||
inboxes: Map<string, IImapServerInbox>;
|
||||
}
|
||||
|
||||
export class ImapServer {
|
||||
public users: Map<string, IImapServerUser>;
|
||||
private server: net.Server;
|
||||
|
||||
constructor() {
|
||||
this.users = new Map();
|
||||
this.server = net.createServer(this.handleConnection.bind(this));
|
||||
}
|
||||
|
||||
// Add a user for authentication
|
||||
public addUser(username: string, password: string): void {
|
||||
if (this.users.has(username)) {
|
||||
throw new Error(`User "${username}" already exists.`);
|
||||
}
|
||||
this.users.set(username, { username, password, inboxes: new Map() });
|
||||
}
|
||||
|
||||
// Add an inbox for a user
|
||||
public createInbox(username: string, inboxName: string): void {
|
||||
const user = this.users.get(username);
|
||||
if (!user) {
|
||||
throw new Error(`User "${username}" does not exist.`);
|
||||
}
|
||||
if (user.inboxes.has(inboxName)) {
|
||||
throw new Error(`Inbox "${inboxName}" already exists for user "${username}".`);
|
||||
}
|
||||
user.inboxes.set(inboxName, { name: inboxName, messages: [] });
|
||||
}
|
||||
|
||||
// Start the server
|
||||
public start(port: number): void {
|
||||
this.server.listen(port, () => {
|
||||
console.log(`IMAP Server started on port ${port}`);
|
||||
});
|
||||
}
|
||||
|
||||
// Stop the server
|
||||
public stop(): void {
|
||||
this.server.close(() => {
|
||||
console.log("IMAP Server stopped.");
|
||||
});
|
||||
}
|
||||
|
||||
// Handle a new client connection
|
||||
private handleConnection(socket: net.Socket): void {
|
||||
let currentUser: IImapServerUser | null = null;
|
||||
let selectedInbox: IImapServerInbox | null = null;
|
||||
|
||||
socket.write("* OK IMAP4rev1 Service Ready\r\n");
|
||||
|
||||
socket.on("data", (data) => {
|
||||
const command = data.toString().trim();
|
||||
console.log(`Received command: ${command}`);
|
||||
|
||||
const [tag, keyword, ...args] = command.split(" ");
|
||||
let response = "";
|
||||
|
||||
try {
|
||||
switch (keyword.toUpperCase()) {
|
||||
case "LOGIN": {
|
||||
const [username, password] = args;
|
||||
const user = this.users.get(username);
|
||||
if (user && user.password === password) {
|
||||
currentUser = user;
|
||||
response = `${tag} OK LOGIN completed`;
|
||||
} else {
|
||||
response = `${tag} NO LOGIN failed`;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case "LIST": {
|
||||
if (!currentUser) {
|
||||
response = `${tag} NO Not authenticated`;
|
||||
break;
|
||||
}
|
||||
const inboxNames = Array.from(currentUser.inboxes.keys()).map((inbox) => `* LIST () "/" ${inbox}`);
|
||||
response = `${inboxNames.join("\r\n")}\r\n${tag} OK LIST completed`;
|
||||
break;
|
||||
}
|
||||
|
||||
case "SELECT": {
|
||||
if (!currentUser) {
|
||||
response = `${tag} NO Not authenticated`;
|
||||
break;
|
||||
}
|
||||
const inboxName = args[0];
|
||||
const inbox = currentUser.inboxes.get(inboxName);
|
||||
if (inbox) {
|
||||
selectedInbox = inbox;
|
||||
response = `* FLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft)\r\n* EXISTS ${inbox.messages.length}\r\n${tag} OK [READ-WRITE] SELECT completed`;
|
||||
} else {
|
||||
response = `${tag} NO SELECT failed: No such mailbox`;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case "FETCH": {
|
||||
if (!selectedInbox) {
|
||||
response = `${tag} NO No mailbox selected`;
|
||||
break;
|
||||
}
|
||||
const [id] = args;
|
||||
const message = selectedInbox.messages.find((msg) => msg.id === id);
|
||||
if (message) {
|
||||
response = `* ${id} FETCH (BODY[TEXT] {${message.body.length}}\r\n${message.body}\r\n)\r\n${tag} OK FETCH completed`;
|
||||
} else {
|
||||
response = `${tag} NO FETCH failed: No such message`;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case "LOGOUT": {
|
||||
response = `* BYE IMAP4rev1 Server logging out\r\n${tag} OK LOGOUT completed`;
|
||||
socket.write(response + "\r\n");
|
||||
socket.end();
|
||||
return;
|
||||
}
|
||||
|
||||
default: {
|
||||
response = `${tag} BAD Unknown command`;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
response = `${tag} BAD Error: ${error.message}`;
|
||||
}
|
||||
|
||||
socket.write(response + "\r\n");
|
||||
});
|
||||
|
||||
socket.on("close", () => {
|
||||
console.log("Client disconnected.");
|
||||
});
|
||||
|
||||
socket.on("error", (err) => {
|
||||
console.error("Socket error:", err);
|
||||
});
|
||||
}
|
||||
}
|
@ -1 +1,2 @@
|
||||
export * from './classes.smartimap.js';
|
||||
export * from './classes.imapclient.js';
|
||||
export * from './classes.imapserver.js';
|
||||
|
Reference in New Issue
Block a user