smtp-server-as-promised
Version:
Promisify smtp-server module
212 lines (177 loc) • 5.93 kB
text/typescript
/// <reference types="node" />
/// <reference types="nodemailer" />
import isStreamEnded from "is-stream-ended"
import net from "net"
export {Logger, LoggerLevel} from "nodemailer/lib/shared"
import NullWritable from "null-writable"
import finished from "stream.finished"
import tls from "tls"
import {
SMTPServer,
SMTPServerAddress,
SMTPServerAuthentication,
SMTPServerAuthenticationResponse,
SMTPServerDataStream,
SMTPServerOptions,
SMTPServerSession,
} from "smtp-server"
export * from "smtp-server"
export interface SMTPServerAsPromisedServerAddress {
address: string
family: string
port: number
}
export interface SMTPServerAsPromisedOptions extends SMTPServerOptions {
onConnect?: never
onAuth?: never
onMailFrom?: never
onRcptTo?: never
onData?: never
onClose?: never
onError?: never
}
export class SMTPServerAsPromised {
server: SMTPServer
protected closed?: boolean = false
protected errorHandler?: (error: Error) => Promise<void>
constructor(options: SMTPServerAsPromisedOptions = {}) {
const newOptions: SMTPServerOptions = {}
newOptions.onConnect = (session: SMTPServerSession, callback: (err?: Error) => void) =>
this.onConnect(session)
.then(() => callback())
.catch((err: Error) => callback(err))
newOptions.onAuth = (
auth: SMTPServerAuthentication,
session: SMTPServerSession,
callback: (err: Error | null, response?: SMTPServerAuthenticationResponse) => void,
) =>
this.onAuth(auth, session)
.then((response: SMTPServerAuthenticationResponse) => callback(null, response))
.catch((err: Error) => callback(err))
newOptions.onMailFrom = (
address: SMTPServerAddress,
session: SMTPServerSession,
callback: (err?: Error | null) => void,
) =>
this.onMailFrom(address, session)
.then(() => callback())
.catch((err: Error) => callback(err))
newOptions.onRcptTo = (
address: SMTPServerAddress,
session: SMTPServerSession,
callback: (err?: Error | null) => void,
) =>
this.onRcptTo(address, session)
.then(() => callback())
.catch((err: Error) => callback(err))
newOptions.onData = (
stream: SMTPServerDataStream,
session: SMTPServerSession,
callback: (err?: Error | null) => void,
) => {
const promiseStream = new Promise((resolve, reject) => {
if (isStreamEnded(stream)) {
return resolve()
}
finished(stream, err => {
if (err) reject(err)
else resolve()
})
})
if (isStreamEnded(stream)) {
return callback(new Error("SMTP data stream is already ended"))
}
return this.onData(stream, session)
.then(() => promiseStream)
.then(() => callback())
.catch((err: Error) => {
stream.pipe(new NullWritable())
callback(err)
})
}
newOptions.onClose = (session: SMTPServerSession) => this.onClose(session)
this.server = new SMTPServer({...(options as SMTPServerOptions), ...newOptions})
this.server.on("error", (err: Error) => this.onError(err))
}
listen(options: net.ListenOptions = {}): Promise<SMTPServerAsPromisedServerAddress> {
return new Promise((resolve, reject) => {
const netServer = this.server.server
const listeningHandler = () => {
netServer.removeListener("error", errorHandler)
const address = netServer.address() as SMTPServerAsPromisedServerAddress
resolve(address)
}
const errorHandler = (err: Error) => {
netServer.removeListener("listening", listeningHandler)
reject(err)
}
netServer.once("listening", listeningHandler)
netServer.once("error", errorHandler)
this.server.listen(options)
})
}
close(): Promise<void> {
return new Promise((resolve, reject) => {
this.server.close((err?: Error | null) => {
this.closed = true
if (this.errorHandler) {
this.server.removeListener("error", this.errorHandler)
this.errorHandler = undefined
}
if (err) {
return reject(err)
} else {
resolve()
}
})
})
}
updateSecureContext(options: tls.TlsOptions): void {
this.server.updateSecureContext(options)
}
destroy(): Promise<void> {
if (!this.closed) {
return this.close()
} else {
return Promise.resolve()
}
}
/** This method can be overriden in subclass */
// prettier-ignore
// @ts-ignore
protected onAuth(auth: SMTPServerAuthentication, session: SMTPServerSession): Promise<SMTPServerAuthenticationResponse> {
return Promise.reject(new Error('onAuth method not overriden in subclass'))
}
/** This method can be overriden in subclass */
// @ts-ignore
protected onClose(session: SMTPServerSession): Promise<void> {
return Promise.resolve()
}
/** This method can be overriden in subclass */
// @ts-ignore
protected onConnect(session: SMTPServerSession): Promise<void> {
return Promise.resolve()
}
/** This method can be overriden in subclass */
// @ts-ignore
protected onData(stream: SMTPServerDataStream, session: SMTPServerSession): Promise<void> {
stream.pipe(new NullWritable())
return Promise.resolve()
}
/** This method can be overriden in subclass */
// @ts-ignore
protected onMailFrom(address: SMTPServerAddress, session: SMTPServerSession): Promise<void> {
return Promise.resolve()
}
/** This method can be overriden in subclass */
// @ts-ignore
protected onRcptTo(address: SMTPServerAddress, session: SMTPServerSession): Promise<void> {
return Promise.resolve()
}
/** This method can be overriden in subclass */
// @ts-ignore
protected onError(error: Error): Promise<void> {
return Promise.resolve()
}
}
export default SMTPServerAsPromised