feat(storage): add comprehensive tests for StorageManager with memory, filesystem, and custom function backends
feat(email): implement EmailSendJob class for robust email delivery with retry logic and MX record resolution feat(mail): restructure mail module exports for simplified access to core and delivery functionalities
This commit is contained in:
@@ -1,298 +0,0 @@
|
||||
/**
|
||||
* Connection Wrapper Utility
|
||||
* Wraps Deno.Conn to provide Node.js net.Socket-compatible interface
|
||||
* This allows the SMTP server to use Deno's native networking while maintaining
|
||||
* compatibility with existing Socket-based code
|
||||
*/
|
||||
|
||||
import { EventEmitter } from '../../../../plugins.ts';
|
||||
|
||||
/**
|
||||
* Wraps a Deno.Conn or Deno.TlsConn to provide a Node.js Socket-compatible interface
|
||||
*/
|
||||
export class ConnectionWrapper extends EventEmitter {
|
||||
private conn: Deno.Conn | Deno.TlsConn;
|
||||
private _destroyed = false;
|
||||
private _reading = false;
|
||||
private _remoteAddr: Deno.NetAddr;
|
||||
private _localAddr: Deno.NetAddr;
|
||||
|
||||
constructor(conn: Deno.Conn | Deno.TlsConn) {
|
||||
super();
|
||||
this.conn = conn;
|
||||
this._remoteAddr = conn.remoteAddr as Deno.NetAddr;
|
||||
this._localAddr = conn.localAddr as Deno.NetAddr;
|
||||
|
||||
// Start reading from the connection
|
||||
this._reading = true;
|
||||
this._startReading();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get remote address (Node.js net.Socket compatible)
|
||||
*/
|
||||
get remoteAddress(): string {
|
||||
return this._remoteAddr.hostname;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get remote port (Node.js net.Socket compatible)
|
||||
*/
|
||||
get remotePort(): number {
|
||||
return this._remoteAddr.port;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get local address (Node.js net.Socket compatible)
|
||||
*/
|
||||
get localAddress(): string {
|
||||
return this._localAddr.hostname;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get local port (Node.js net.Socket compatible)
|
||||
*/
|
||||
get localPort(): number {
|
||||
return this._localAddr.port;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if connection is destroyed
|
||||
*/
|
||||
get destroyed(): boolean {
|
||||
return this._destroyed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check ready state (Node.js compatible)
|
||||
*/
|
||||
get readyState(): string {
|
||||
if (this._destroyed) {
|
||||
return 'closed';
|
||||
}
|
||||
return 'open';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if writable (Node.js compatible)
|
||||
*/
|
||||
get writable(): boolean {
|
||||
return !this._destroyed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this is a secure (TLS) connection
|
||||
*/
|
||||
get encrypted(): boolean {
|
||||
return 'handshake' in this.conn; // TlsConn has handshake property
|
||||
}
|
||||
|
||||
/**
|
||||
* Write data to the connection (Node.js net.Socket compatible)
|
||||
*/
|
||||
write(data: string | Uint8Array, encoding?: string | ((err?: Error) => void), callback?: (err?: Error) => void): boolean {
|
||||
// Handle overloaded signatures (encoding is optional)
|
||||
if (typeof encoding === 'function') {
|
||||
callback = encoding;
|
||||
encoding = undefined;
|
||||
}
|
||||
|
||||
if (this._destroyed) {
|
||||
const error = new Error('Connection is destroyed');
|
||||
if (callback) {
|
||||
setTimeout(() => callback(error), 0);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const bytes = typeof data === 'string'
|
||||
? new TextEncoder().encode(data)
|
||||
: data;
|
||||
|
||||
// Use a promise-based approach that Node.js compatibility expects
|
||||
// Write happens async but we return true immediately (buffered)
|
||||
this.conn.write(bytes)
|
||||
.then(() => {
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
const error = err instanceof Error ? err : new Error(String(err));
|
||||
if (callback) {
|
||||
callback(error);
|
||||
} else {
|
||||
this.emit('error', error);
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* End the connection (Node.js net.Socket compatible)
|
||||
*/
|
||||
end(data?: string | Uint8Array, encoding?: string, callback?: () => void): void {
|
||||
if (data) {
|
||||
this.write(data, encoding, () => {
|
||||
this.destroy();
|
||||
if (callback) callback();
|
||||
});
|
||||
} else {
|
||||
this.destroy();
|
||||
if (callback) callback();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy the connection (Node.js net.Socket compatible)
|
||||
*/
|
||||
destroy(error?: Error): void {
|
||||
if (this._destroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._destroyed = true;
|
||||
this._reading = false;
|
||||
|
||||
try {
|
||||
this.conn.close();
|
||||
} catch (closeError) {
|
||||
// Ignore close errors
|
||||
}
|
||||
|
||||
if (error) {
|
||||
this.emit('error', error);
|
||||
}
|
||||
|
||||
this.emit('close', !!error);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set TCP_NODELAY option (Node.js net.Socket compatible)
|
||||
*/
|
||||
setNoDelay(noDelay: boolean = true): this {
|
||||
try {
|
||||
// @ts-ignore - Deno.Conn has setNoDelay
|
||||
if (typeof this.conn.setNoDelay === 'function') {
|
||||
// @ts-ignore
|
||||
this.conn.setNoDelay(noDelay);
|
||||
}
|
||||
} catch {
|
||||
// Ignore if not supported
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set keep-alive option (Node.js net.Socket compatible)
|
||||
*/
|
||||
setKeepAlive(enable: boolean = true, initialDelay?: number): this {
|
||||
try {
|
||||
// @ts-ignore - Deno.Conn has setKeepAlive
|
||||
if (typeof this.conn.setKeepAlive === 'function') {
|
||||
// @ts-ignore
|
||||
this.conn.setKeepAlive(enable);
|
||||
}
|
||||
} catch {
|
||||
// Ignore if not supported
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set timeout (Node.js net.Socket compatible)
|
||||
*/
|
||||
setTimeout(timeout: number, callback?: () => void): this {
|
||||
// Deno doesn't have built-in socket timeout, but we can implement it
|
||||
// For now, just accept the call without error (most timeout handling is done elsewhere)
|
||||
if (callback) {
|
||||
// If callback provided, we could set up a timer, but for now just ignore
|
||||
// The SMTP server handles timeouts at a higher level
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pause reading from the connection
|
||||
*/
|
||||
pause(): this {
|
||||
this._reading = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resume reading from the connection
|
||||
*/
|
||||
resume(): this {
|
||||
if (!this._reading && !this._destroyed) {
|
||||
this._reading = true;
|
||||
this._startReading();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the underlying Deno.Conn
|
||||
*/
|
||||
getDenoConn(): Deno.Conn | Deno.TlsConn {
|
||||
return this.conn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace the underlying connection (for STARTTLS upgrade)
|
||||
*/
|
||||
replaceConnection(newConn: Deno.TlsConn): void {
|
||||
this.conn = newConn;
|
||||
this._remoteAddr = newConn.remoteAddr as Deno.NetAddr;
|
||||
this._localAddr = newConn.localAddr as Deno.NetAddr;
|
||||
|
||||
// Restart reading from the new TLS connection
|
||||
if (!this._destroyed) {
|
||||
this._reading = true;
|
||||
this._startReading();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal method to read data from the connection
|
||||
*/
|
||||
private async _startReading(): Promise<void> {
|
||||
if (!this._reading || this._destroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const buffer = new Uint8Array(4096);
|
||||
|
||||
while (this._reading && !this._destroyed) {
|
||||
const n = await this.conn.read(buffer);
|
||||
|
||||
if (n === null) {
|
||||
// EOF
|
||||
this._destroyed = true;
|
||||
this.emit('end');
|
||||
this.emit('close', false);
|
||||
break;
|
||||
}
|
||||
|
||||
const data = buffer.subarray(0, n);
|
||||
this.emit('data', data);
|
||||
}
|
||||
} catch (error) {
|
||||
if (!this._destroyed) {
|
||||
this._destroyed = true;
|
||||
this.emit('error', error instanceof Error ? error : new Error(String(error)));
|
||||
this.emit('close', true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all listeners (cleanup helper)
|
||||
*/
|
||||
removeAllListeners(event?: string): this {
|
||||
super.removeAllListeners(event);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user