feat(wire): Add wire protocol, WireTarget & WireParser, Smartmail JSON serialization; refactor plugins and update dependencies
This commit is contained in:
@@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@push.rocks/smartmail',
|
||||
version: '2.1.0',
|
||||
version: '2.2.0',
|
||||
description: 'A unified format for representing and dealing with emails, with support for attachments and email validation.'
|
||||
}
|
||||
|
||||
@@ -1,2 +1,5 @@
|
||||
export * from './smartmail.classes.smartmail.js';
|
||||
export * from './smartmail.classes.emailaddressvalidator.js';
|
||||
export * from './smartmail.wire.js';
|
||||
export * from './smartmail.classes.wiretarget.js';
|
||||
export * from './smartmail.classes.wireparser.js';
|
||||
|
||||
@@ -251,8 +251,9 @@ export class EmailAddressValidator {
|
||||
*/
|
||||
public async fetchDomains() {
|
||||
if (!this.domainMap) {
|
||||
const localFileString = plugins.smartfile.fs.toStringSync(
|
||||
plugins.path.join(paths.assetDir, 'domains.json')
|
||||
const localFileString = plugins.fs.readFileSync(
|
||||
plugins.path.join(paths.assetDir, 'domains.json'),
|
||||
'utf8'
|
||||
);
|
||||
const localFileObject = JSON.parse(localFileString);
|
||||
|
||||
@@ -262,12 +263,10 @@ export class EmailAddressValidator {
|
||||
}
|
||||
|
||||
try {
|
||||
const onlineFileObject = (
|
||||
await plugins.smartrequest.getJson(
|
||||
'https://raw.githubusercontent.com/romainsimon/emailvalid/master/domains.json'
|
||||
)
|
||||
).body;
|
||||
this.domainMap = onlineFileObject;
|
||||
const response = await plugins.SmartRequest.create()
|
||||
.url('https://raw.githubusercontent.com/romainsimon/emailvalid/master/domains.json')
|
||||
.get();
|
||||
this.domainMap = await response.json();
|
||||
} catch (e) {
|
||||
this.domainMap = localFileObject;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import * as plugins from './smartmail.plugins.js';
|
||||
import { EmailAddressValidator } from './smartmail.classes.emailaddressvalidator.js';
|
||||
import type { IMailSendResponse } from './smartmail.wire.js';
|
||||
import type { WireTarget } from './smartmail.classes.wiretarget.js';
|
||||
|
||||
export type EmailAddress = string;
|
||||
export type EmailAddressList = EmailAddress[];
|
||||
@@ -19,6 +21,33 @@ export interface ISmartmailOptions<T> {
|
||||
validateEmails?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* JSON representation of an attachment for wire transmission
|
||||
*/
|
||||
export interface IAttachmentJson {
|
||||
filename: string;
|
||||
contentBase64: string;
|
||||
contentType: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* JSON representation of a Smartmail for wire transmission
|
||||
*/
|
||||
export interface ISmartmailJson<T = unknown> {
|
||||
from: string;
|
||||
to?: string[];
|
||||
cc?: string[];
|
||||
bcc?: string[];
|
||||
replyTo?: string;
|
||||
subject: string;
|
||||
body: string;
|
||||
htmlBody?: string;
|
||||
headers?: Record<string, string>;
|
||||
priority?: 'high' | 'normal' | 'low';
|
||||
creationObjectRef?: T;
|
||||
attachments: IAttachmentJson[];
|
||||
}
|
||||
|
||||
export interface IMimeAttachment {
|
||||
filename: string;
|
||||
content: Buffer;
|
||||
@@ -51,9 +80,11 @@ export class Smartmail<T> {
|
||||
/**
|
||||
* Adds an attachment to the email
|
||||
* @param smartfileArg The file to attach
|
||||
* @returns this for chaining
|
||||
*/
|
||||
public addAttachment(smartfileArg: plugins.smartfile.SmartFile) {
|
||||
public addAttachment(smartfileArg: plugins.smartfile.SmartFile): this {
|
||||
this.attachments.push(smartfileArg);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -77,27 +108,30 @@ export class Smartmail<T> {
|
||||
/**
|
||||
* Applies variables to all template strings in the email
|
||||
* @param variables Variables to apply to templates
|
||||
* @returns this for chaining
|
||||
*/
|
||||
public applyVariables(variables: Record<string, any>): void {
|
||||
public applyVariables(variables: Record<string, any>): this {
|
||||
if (!variables || typeof variables !== 'object') {
|
||||
return;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
// Process the subject, body, and HTML body with the provided variables
|
||||
if (this.options.subject) {
|
||||
const subjectMustache = new plugins.smartmustache.SmartMustache(this.options.subject);
|
||||
this.options.subject = subjectMustache.applyData(variables);
|
||||
}
|
||||
|
||||
|
||||
if (this.options.body) {
|
||||
const bodyMustache = new plugins.smartmustache.SmartMustache(this.options.body);
|
||||
this.options.body = bodyMustache.applyData(variables);
|
||||
}
|
||||
|
||||
|
||||
if (this.options.htmlBody) {
|
||||
const htmlBodyMustache = new plugins.smartmustache.SmartMustache(this.options.htmlBody);
|
||||
this.options.htmlBody = htmlBodyMustache.applyData(variables);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -128,55 +162,65 @@ export class Smartmail<T> {
|
||||
* Adds a recipient to the email
|
||||
* @param email Email address to add
|
||||
* @param type Type of recipient (to, cc, bcc)
|
||||
* @returns this for chaining
|
||||
*/
|
||||
public addRecipient(email: EmailAddress, type: 'to' | 'cc' | 'bcc' = 'to'): void {
|
||||
public addRecipient(email: EmailAddress, type: 'to' | 'cc' | 'bcc' = 'to'): this {
|
||||
if (!this.options[type]) {
|
||||
this.options[type] = [];
|
||||
}
|
||||
|
||||
|
||||
this.options[type]!.push(email);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds multiple recipients to the email
|
||||
* @param emails Email addresses to add
|
||||
* @param type Type of recipients (to, cc, bcc)
|
||||
* @returns this for chaining
|
||||
*/
|
||||
public addRecipients(emails: EmailAddressList, type: 'to' | 'cc' | 'bcc' = 'to'): void {
|
||||
public addRecipients(emails: EmailAddressList, type: 'to' | 'cc' | 'bcc' = 'to'): this {
|
||||
if (!this.options[type]) {
|
||||
this.options[type] = [];
|
||||
}
|
||||
|
||||
|
||||
this.options[type] = [...this.options[type]!, ...emails];
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the reply-to address
|
||||
* @param email Email address for reply-to
|
||||
* @returns this for chaining
|
||||
*/
|
||||
public setReplyTo(email: EmailAddress): void {
|
||||
public setReplyTo(email: EmailAddress): this {
|
||||
this.options.replyTo = email;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the priority of the email
|
||||
* @param priority Priority level
|
||||
* @returns this for chaining
|
||||
*/
|
||||
public setPriority(priority: 'high' | 'normal' | 'low'): void {
|
||||
public setPriority(priority: 'high' | 'normal' | 'low'): this {
|
||||
this.options.priority = priority;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a custom header to the email
|
||||
* @param name Header name
|
||||
* @param value Header value
|
||||
* @returns this for chaining
|
||||
*/
|
||||
public addHeader(name: string, value: string): void {
|
||||
public addHeader(name: string, value: string): this {
|
||||
if (!this.options.headers) {
|
||||
this.options.headers = {};
|
||||
}
|
||||
|
||||
|
||||
this.options.headers[name] = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -203,6 +247,102 @@ export class Smartmail<T> {
|
||||
return results;
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// Wire Format Serialization Methods
|
||||
// ==========================================
|
||||
|
||||
/**
|
||||
* Converts the email to a JSON-serializable object for wire transmission
|
||||
* @returns JSON-serializable object
|
||||
*/
|
||||
public toObject(): ISmartmailJson<T> {
|
||||
const attachmentsJson: IAttachmentJson[] = this.attachments.map((file) => ({
|
||||
filename: file.path.split('/').pop() || 'attachment',
|
||||
contentBase64: file.contentBuffer.toString('base64'),
|
||||
contentType: 'application/octet-stream',
|
||||
}));
|
||||
|
||||
return {
|
||||
from: this.options.from,
|
||||
to: this.options.to,
|
||||
cc: this.options.cc,
|
||||
bcc: this.options.bcc,
|
||||
replyTo: this.options.replyTo,
|
||||
subject: this.options.subject,
|
||||
body: this.options.body,
|
||||
htmlBody: this.options.htmlBody,
|
||||
headers: this.options.headers,
|
||||
priority: this.options.priority,
|
||||
creationObjectRef: this.options.creationObjectRef,
|
||||
attachments: attachmentsJson,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes the email to a JSON string for wire transmission
|
||||
* @returns JSON string
|
||||
*/
|
||||
public toJson(): string {
|
||||
return JSON.stringify(this.toObject());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Smartmail instance from a JSON-serializable object
|
||||
* @param obj JSON object representing the email
|
||||
* @returns Smartmail instance
|
||||
*/
|
||||
public static fromObject<T = unknown>(obj: ISmartmailJson<T>): Smartmail<T> {
|
||||
const email = new Smartmail<T>({
|
||||
from: obj.from,
|
||||
to: obj.to,
|
||||
cc: obj.cc,
|
||||
bcc: obj.bcc,
|
||||
replyTo: obj.replyTo,
|
||||
subject: obj.subject,
|
||||
body: obj.body,
|
||||
htmlBody: obj.htmlBody,
|
||||
headers: obj.headers,
|
||||
priority: obj.priority,
|
||||
creationObjectRef: obj.creationObjectRef,
|
||||
});
|
||||
|
||||
// Reconstruct attachments from base64
|
||||
for (const att of obj.attachments || []) {
|
||||
const buffer = Buffer.from(att.contentBase64, 'base64');
|
||||
const smartfile = new plugins.smartfile.SmartFile({
|
||||
path: att.filename,
|
||||
contentBuffer: buffer,
|
||||
base: './',
|
||||
});
|
||||
email.attachments.push(smartfile);
|
||||
}
|
||||
|
||||
return email;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserializes a Smartmail instance from a JSON string
|
||||
* @param json JSON string representing the email
|
||||
* @returns Smartmail instance
|
||||
*/
|
||||
public static fromJson<T = unknown>(json: string): Smartmail<T> {
|
||||
const obj = JSON.parse(json) as ISmartmailJson<T>;
|
||||
return Smartmail.fromObject<T>(obj);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send this email to a WireTarget
|
||||
* @param target The WireTarget to send the email to
|
||||
* @returns Promise resolving to the send response
|
||||
*/
|
||||
public async sendTo(target: WireTarget): Promise<IMailSendResponse> {
|
||||
return target.sendEmail(this);
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// MIME Format Methods
|
||||
// ==========================================
|
||||
|
||||
/**
|
||||
* Converts the email to a MIME format object for sending
|
||||
* @param dataArg Data to apply to templates
|
||||
|
||||
252
ts/smartmail.classes.wireparser.ts
Normal file
252
ts/smartmail.classes.wireparser.ts
Normal file
@@ -0,0 +1,252 @@
|
||||
import { Smartmail } from './smartmail.classes.smartmail.js';
|
||||
import {
|
||||
type IWireMessage,
|
||||
type IMailSendRequest,
|
||||
type IMailSendResponse,
|
||||
type IMailboxListRequest,
|
||||
type IMailboxListResponse,
|
||||
type IMailFetchRequest,
|
||||
type IMailFetchResponse,
|
||||
type IMailStatusRequest,
|
||||
type IMailStatusResponse,
|
||||
type ISettingsUpdateRequest,
|
||||
type ISettingsUpdateResponse,
|
||||
type IWireSettings,
|
||||
type TWireMessage,
|
||||
createMessageId,
|
||||
createTimestamp,
|
||||
} from './smartmail.wire.js';
|
||||
|
||||
/**
|
||||
* Handler functions for different wire message types
|
||||
*/
|
||||
export interface IWireHandlers {
|
||||
/** Handler for mail send requests */
|
||||
onMailSend?: (
|
||||
email: Smartmail<any>,
|
||||
options?: IMailSendRequest['options']
|
||||
) => Promise<IMailSendResponse>;
|
||||
|
||||
/** Handler for mailbox list requests */
|
||||
onMailboxList?: (
|
||||
mailbox: string,
|
||||
options?: { limit?: number; offset?: number }
|
||||
) => Promise<IMailboxListResponse>;
|
||||
|
||||
/** Handler for mail fetch requests */
|
||||
onMailFetch?: (mailbox: string, emailId: string) => Promise<IMailFetchResponse>;
|
||||
|
||||
/** Handler for mail status requests */
|
||||
onMailStatus?: (deliveryId: string) => Promise<IMailStatusResponse>;
|
||||
|
||||
/** Handler for settings update requests */
|
||||
onSettingsUpdate?: (settings: IWireSettings) => Promise<ISettingsUpdateResponse>;
|
||||
}
|
||||
|
||||
/**
|
||||
* WireParser is used by the SMTP service to parse and handle incoming wire messages.
|
||||
* It provides a handler-based approach for processing different message types.
|
||||
*/
|
||||
export class WireParser {
|
||||
private handlers: IWireHandlers;
|
||||
|
||||
constructor(handlers: IWireHandlers = {}) {
|
||||
this.handlers = handlers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a wire message from JSON string
|
||||
* @param json The JSON string to parse
|
||||
* @returns Parsed wire message
|
||||
*/
|
||||
public parse(json: string): TWireMessage {
|
||||
return JSON.parse(json) as TWireMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an incoming wire message and return the response
|
||||
* @param message The wire message to handle
|
||||
* @returns Promise resolving to the response message
|
||||
*/
|
||||
public async handle(message: TWireMessage): Promise<IWireMessage> {
|
||||
switch (message.type) {
|
||||
case 'mail.send':
|
||||
return this.handleMailSend(message);
|
||||
case 'mailbox.list':
|
||||
return this.handleMailboxList(message);
|
||||
case 'mail.fetch':
|
||||
return this.handleMailFetch(message);
|
||||
case 'mail.status':
|
||||
return this.handleMailStatus(message);
|
||||
case 'settings.update':
|
||||
return this.handleSettingsUpdate(message);
|
||||
default:
|
||||
return this.createErrorResponse(message, 'Unknown message type');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse and handle in one step (convenience method)
|
||||
* @param json The JSON string to parse and handle
|
||||
* @returns Promise resolving to JSON response string
|
||||
*/
|
||||
public async parseAndHandle(json: string): Promise<string> {
|
||||
const message = this.parse(json);
|
||||
const response = await this.handle(message);
|
||||
return JSON.stringify(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle mail send request
|
||||
*/
|
||||
private async handleMailSend(message: IMailSendRequest): Promise<IMailSendResponse> {
|
||||
if (!this.handlers.onMailSend) {
|
||||
return {
|
||||
type: 'mail.send.response',
|
||||
messageId: message.messageId,
|
||||
timestamp: createTimestamp(),
|
||||
success: false,
|
||||
error: 'Mail send not supported',
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const email = Smartmail.fromObject(message.email);
|
||||
return await this.handlers.onMailSend(email, message.options);
|
||||
} catch (error) {
|
||||
return {
|
||||
type: 'mail.send.response',
|
||||
messageId: message.messageId,
|
||||
timestamp: createTimestamp(),
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Unknown error',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle mailbox list request
|
||||
*/
|
||||
private async handleMailboxList(message: IMailboxListRequest): Promise<IMailboxListResponse> {
|
||||
if (!this.handlers.onMailboxList) {
|
||||
return {
|
||||
type: 'mailbox.list.response',
|
||||
messageId: message.messageId,
|
||||
timestamp: createTimestamp(),
|
||||
mailbox: message.mailbox,
|
||||
emails: [],
|
||||
total: 0,
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
return await this.handlers.onMailboxList(message.mailbox, {
|
||||
limit: message.limit,
|
||||
offset: message.offset,
|
||||
});
|
||||
} catch (error) {
|
||||
return {
|
||||
type: 'mailbox.list.response',
|
||||
messageId: message.messageId,
|
||||
timestamp: createTimestamp(),
|
||||
mailbox: message.mailbox,
|
||||
emails: [],
|
||||
total: 0,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle mail fetch request
|
||||
*/
|
||||
private async handleMailFetch(message: IMailFetchRequest): Promise<IMailFetchResponse> {
|
||||
if (!this.handlers.onMailFetch) {
|
||||
return {
|
||||
type: 'mail.fetch.response',
|
||||
messageId: message.messageId,
|
||||
timestamp: createTimestamp(),
|
||||
email: null,
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
return await this.handlers.onMailFetch(message.mailbox, message.emailId);
|
||||
} catch (error) {
|
||||
return {
|
||||
type: 'mail.fetch.response',
|
||||
messageId: message.messageId,
|
||||
timestamp: createTimestamp(),
|
||||
email: null,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle mail status request
|
||||
*/
|
||||
private async handleMailStatus(message: IMailStatusRequest): Promise<IMailStatusResponse> {
|
||||
if (!this.handlers.onMailStatus) {
|
||||
return {
|
||||
type: 'mail.status.response',
|
||||
messageId: message.messageId,
|
||||
timestamp: createTimestamp(),
|
||||
deliveryId: message.deliveryId,
|
||||
status: 'failed',
|
||||
error: 'Mail status not supported',
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
return await this.handlers.onMailStatus(message.deliveryId);
|
||||
} catch (error) {
|
||||
return {
|
||||
type: 'mail.status.response',
|
||||
messageId: message.messageId,
|
||||
timestamp: createTimestamp(),
|
||||
deliveryId: message.deliveryId,
|
||||
status: 'failed',
|
||||
error: error instanceof Error ? error.message : 'Unknown error',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle settings update request
|
||||
*/
|
||||
private async handleSettingsUpdate(
|
||||
message: ISettingsUpdateRequest
|
||||
): Promise<ISettingsUpdateResponse> {
|
||||
if (!this.handlers.onSettingsUpdate) {
|
||||
return {
|
||||
type: 'settings.update.response',
|
||||
messageId: message.messageId,
|
||||
timestamp: createTimestamp(),
|
||||
success: false,
|
||||
error: 'Settings update not supported',
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
return await this.handlers.onSettingsUpdate(message.settings);
|
||||
} catch (error) {
|
||||
return {
|
||||
type: 'settings.update.response',
|
||||
messageId: message.messageId,
|
||||
timestamp: createTimestamp(),
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Unknown error',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an error response for unknown message types
|
||||
*/
|
||||
private createErrorResponse(message: IWireMessage, error: string): IWireMessage {
|
||||
return {
|
||||
type: `${message.type}.response`,
|
||||
messageId: message.messageId,
|
||||
timestamp: createTimestamp(),
|
||||
};
|
||||
}
|
||||
}
|
||||
150
ts/smartmail.classes.wiretarget.ts
Normal file
150
ts/smartmail.classes.wiretarget.ts
Normal file
@@ -0,0 +1,150 @@
|
||||
import * as plugins from './smartmail.plugins.js';
|
||||
import { Smartmail } from './smartmail.classes.smartmail.js';
|
||||
import {
|
||||
type IWireMessage,
|
||||
type IMailSendRequest,
|
||||
type IMailSendResponse,
|
||||
type IMailboxListRequest,
|
||||
type IMailboxListResponse,
|
||||
type IMailFetchRequest,
|
||||
type IMailFetchResponse,
|
||||
type IMailStatusRequest,
|
||||
type IMailStatusResponse,
|
||||
type ISettingsUpdateRequest,
|
||||
type ISettingsUpdateResponse,
|
||||
type IWireSettings,
|
||||
createMessageId,
|
||||
createTimestamp,
|
||||
} from './smartmail.wire.js';
|
||||
|
||||
/**
|
||||
* Options for configuring a WireTarget
|
||||
*/
|
||||
export interface IWireTargetOptions {
|
||||
/** URL of the SMTP service endpoint */
|
||||
endpoint: string;
|
||||
/** Optional authentication token */
|
||||
authToken?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* WireTarget is used by the SaaS service to communicate with the SMTP service.
|
||||
* It provides methods for sending emails, updating settings, and managing mailboxes.
|
||||
*/
|
||||
export class WireTarget {
|
||||
private endpoint: string;
|
||||
private authToken?: string;
|
||||
|
||||
constructor(options: IWireTargetOptions) {
|
||||
this.endpoint = options.endpoint;
|
||||
this.authToken = options.authToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an email through this target
|
||||
* @param email The Smartmail instance to send
|
||||
* @returns Promise resolving to the send response
|
||||
*/
|
||||
public async sendEmail(email: Smartmail<any>): Promise<IMailSendResponse> {
|
||||
const request: IMailSendRequest = {
|
||||
type: 'mail.send',
|
||||
messageId: createMessageId(),
|
||||
timestamp: createTimestamp(),
|
||||
email: email.toObject(),
|
||||
};
|
||||
return this.send<IMailSendResponse>(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update settings on the target (SMTP config, etc.)
|
||||
* Settings are extensible - any key-value pairs can be sent
|
||||
* @param settings The settings to update
|
||||
* @returns Promise resolving to the update response
|
||||
*/
|
||||
public async updateSettings(settings: IWireSettings): Promise<ISettingsUpdateResponse> {
|
||||
const request: ISettingsUpdateRequest = {
|
||||
type: 'settings.update',
|
||||
messageId: createMessageId(),
|
||||
timestamp: createTimestamp(),
|
||||
settings,
|
||||
};
|
||||
return this.send<ISettingsUpdateResponse>(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* List emails in a mailbox
|
||||
* @param mailbox The mailbox to list (e.g., 'INBOX', 'Sent')
|
||||
* @param options Optional limit and offset for pagination
|
||||
* @returns Promise resolving to the mailbox list response
|
||||
*/
|
||||
public async listMailbox(
|
||||
mailbox: string,
|
||||
options?: { limit?: number; offset?: number }
|
||||
): Promise<IMailboxListResponse> {
|
||||
const request: IMailboxListRequest = {
|
||||
type: 'mailbox.list',
|
||||
messageId: createMessageId(),
|
||||
timestamp: createTimestamp(),
|
||||
mailbox,
|
||||
limit: options?.limit,
|
||||
offset: options?.offset,
|
||||
};
|
||||
return this.send<IMailboxListResponse>(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch a specific email from a mailbox
|
||||
* @param mailbox The mailbox containing the email
|
||||
* @param emailId The ID of the email to fetch
|
||||
* @returns Promise resolving to the Smartmail or null if not found
|
||||
*/
|
||||
public async fetchEmail(mailbox: string, emailId: string): Promise<Smartmail<any> | null> {
|
||||
const request: IMailFetchRequest = {
|
||||
type: 'mail.fetch',
|
||||
messageId: createMessageId(),
|
||||
timestamp: createTimestamp(),
|
||||
mailbox,
|
||||
emailId,
|
||||
};
|
||||
const response = await this.send<IMailFetchResponse>(request);
|
||||
if (response.email) {
|
||||
return Smartmail.fromObject(response.email);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check delivery status of a sent email
|
||||
* @param deliveryId The delivery ID returned from sendEmail
|
||||
* @returns Promise resolving to the status response
|
||||
*/
|
||||
public async getStatus(deliveryId: string): Promise<IMailStatusResponse> {
|
||||
const request: IMailStatusRequest = {
|
||||
type: 'mail.status',
|
||||
messageId: createMessageId(),
|
||||
timestamp: createTimestamp(),
|
||||
deliveryId,
|
||||
};
|
||||
return this.send<IMailStatusResponse>(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a wire message to the endpoint
|
||||
* @param message The message to send
|
||||
* @returns Promise resolving to the response
|
||||
*/
|
||||
private async send<T extends IWireMessage>(message: IWireMessage): Promise<T> {
|
||||
let request = plugins.SmartRequest.create()
|
||||
.url(this.endpoint)
|
||||
.header('Content-Type', 'application/json');
|
||||
|
||||
if (this.authToken) {
|
||||
request = request.header('Authorization', `Bearer ${this.authToken}`);
|
||||
}
|
||||
|
||||
const response = await request.json(message).post();
|
||||
const responseData = await response.json();
|
||||
|
||||
return responseData as T;
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,14 @@
|
||||
// node native scope
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
export { path };
|
||||
export { fs, path };
|
||||
|
||||
// pushrocks scope
|
||||
import * as smartdns from '@push.rocks/smartdns/client';
|
||||
import * as smartfile from '@push.rocks/smartfile';
|
||||
import * as smartmustache from '@push.rocks/smartmustache';
|
||||
import * as smartpath from '@push.rocks/smartpath';
|
||||
import * as smartrequest from '@push.rocks/smartrequest';
|
||||
import SmartRequest from '@push.rocks/smartrequest';
|
||||
|
||||
export { smartdns, smartfile, smartmustache, smartpath, smartrequest };
|
||||
export { smartdns, smartfile, smartmustache, smartpath, SmartRequest };
|
||||
|
||||
188
ts/smartmail.wire.ts
Normal file
188
ts/smartmail.wire.ts
Normal file
@@ -0,0 +1,188 @@
|
||||
import type { ISmartmailJson } from './smartmail.classes.smartmail.js';
|
||||
|
||||
// ==========================================
|
||||
// Base Message Structure
|
||||
// ==========================================
|
||||
|
||||
/**
|
||||
* Base interface for all wire messages
|
||||
*/
|
||||
export interface IWireMessage {
|
||||
type: string;
|
||||
messageId: string;
|
||||
timestamp: string;
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// Mail Send Operations
|
||||
// ==========================================
|
||||
|
||||
/**
|
||||
* Request to send an email
|
||||
*/
|
||||
export interface IMailSendRequest extends IWireMessage {
|
||||
type: 'mail.send';
|
||||
email: ISmartmailJson;
|
||||
options?: {
|
||||
validateBeforeSend?: boolean;
|
||||
templateVariables?: Record<string, unknown>;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Response after sending an email
|
||||
*/
|
||||
export interface IMailSendResponse extends IWireMessage {
|
||||
type: 'mail.send.response';
|
||||
success: boolean;
|
||||
error?: string;
|
||||
deliveryId?: string;
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// Mailbox Operations
|
||||
// ==========================================
|
||||
|
||||
/**
|
||||
* Request to list emails in a mailbox
|
||||
*/
|
||||
export interface IMailboxListRequest extends IWireMessage {
|
||||
type: 'mailbox.list';
|
||||
mailbox: string;
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Response with mailbox email list
|
||||
*/
|
||||
export interface IMailboxListResponse extends IWireMessage {
|
||||
type: 'mailbox.list.response';
|
||||
mailbox: string;
|
||||
emails: ISmartmailJson[];
|
||||
total: number;
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// Mail Fetch Operations
|
||||
// ==========================================
|
||||
|
||||
/**
|
||||
* Request to fetch a specific email
|
||||
*/
|
||||
export interface IMailFetchRequest extends IWireMessage {
|
||||
type: 'mail.fetch';
|
||||
mailbox: string;
|
||||
emailId: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Response with fetched email
|
||||
*/
|
||||
export interface IMailFetchResponse extends IWireMessage {
|
||||
type: 'mail.fetch.response';
|
||||
email: ISmartmailJson | null;
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// Mail Status Operations
|
||||
// ==========================================
|
||||
|
||||
/**
|
||||
* Request to check delivery status
|
||||
*/
|
||||
export interface IMailStatusRequest extends IWireMessage {
|
||||
type: 'mail.status';
|
||||
deliveryId: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Response with delivery status
|
||||
*/
|
||||
export interface IMailStatusResponse extends IWireMessage {
|
||||
type: 'mail.status.response';
|
||||
deliveryId: string;
|
||||
status: 'queued' | 'sending' | 'sent' | 'failed';
|
||||
error?: string;
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// Settings Operations (Extensible)
|
||||
// ==========================================
|
||||
|
||||
/**
|
||||
* SMTP server configuration
|
||||
*/
|
||||
export interface ISmtpSettings {
|
||||
host: string;
|
||||
port: number;
|
||||
secure: boolean;
|
||||
username?: string;
|
||||
password?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wire settings - extensible with arbitrary key-value pairs
|
||||
*/
|
||||
export interface IWireSettings {
|
||||
smtp?: ISmtpSettings;
|
||||
defaultFrom?: string;
|
||||
defaultReplyTo?: string;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
/**
|
||||
* Request to update settings
|
||||
*/
|
||||
export interface ISettingsUpdateRequest extends IWireMessage {
|
||||
type: 'settings.update';
|
||||
settings: IWireSettings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Response after updating settings
|
||||
*/
|
||||
export interface ISettingsUpdateResponse extends IWireMessage {
|
||||
type: 'settings.update.response';
|
||||
success: boolean;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// Union Type for Type Discrimination
|
||||
// ==========================================
|
||||
|
||||
/**
|
||||
* Union of all wire message types for type discrimination
|
||||
*/
|
||||
export type TWireMessage =
|
||||
| IMailSendRequest
|
||||
| IMailSendResponse
|
||||
| IMailboxListRequest
|
||||
| IMailboxListResponse
|
||||
| IMailFetchRequest
|
||||
| IMailFetchResponse
|
||||
| IMailStatusRequest
|
||||
| IMailStatusResponse
|
||||
| ISettingsUpdateRequest
|
||||
| ISettingsUpdateResponse;
|
||||
|
||||
// ==========================================
|
||||
// Helper Functions
|
||||
// ==========================================
|
||||
|
||||
/**
|
||||
* Creates a unique message ID
|
||||
* @returns UUID string
|
||||
*/
|
||||
export function createMessageId(): string {
|
||||
return crypto.randomUUID();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an ISO timestamp
|
||||
* @returns ISO 8601 timestamp string
|
||||
*/
|
||||
export function createTimestamp(): string {
|
||||
return new Date().toISOString();
|
||||
}
|
||||
Reference in New Issue
Block a user