mail-mime-builder
Version:
RFC-2822, RFC-2045 and RFC-2049 compliant zero dependency raw email message generator.
262 lines (261 loc) • 11 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.MIMEMessage = void 0;
const base64_js_1 = require("./base64.js");
const MIMETextError_js_1 = require("./MIMETextError.js");
const MIMEMessageHeader_js_1 = require("./MIMEMessageHeader.js");
const Mailbox_js_1 = require("./Mailbox.js");
const MIMEMessageContent_js_1 = require("./MIMEMessageContent.js");
class MIMEMessage {
headers;
boundaries = { mixed: '', alt: '', related: '' };
validTypes = ['text/html', 'text/plain'];
validContentTransferEncodings = ['7bit', '8bit', 'binary', 'quoted-printable', 'base64'];
messages = [];
constructor() {
this.headers = new MIMEMessageHeader_js_1.MIMEMessageHeader();
this.messages = [];
this.generateBoundaries();
}
asRaw() {
const eol = '\r\n';
const lines = this.headers.dump();
const plaintext = this.getMessageByType('text/plain');
const html = this.getMessageByType('text/html');
const primaryMessage = html ? html : plaintext ? plaintext : undefined;
if (primaryMessage === undefined) {
throw new MIMETextError_js_1.MIMETextError('MIMETEXT_MISSING_BODY', 'No content added to the message.');
}
const hasAttachments = this.hasAttachments();
const hasInlineAttachments = this.hasInlineAttachments();
const structure = hasInlineAttachments && hasAttachments ? 'mixed+related'
: hasAttachments ? 'mixed'
: hasInlineAttachments ? 'related'
: plaintext && html ? 'alternative'
: '';
if (structure === 'mixed+related') {
const attachments = this.getAttachments()
.map((a) => '--' + this.boundaries.mixed + eol + a.dump() + eol + eol)
.join('')
.slice(0, -1 * eol.length);
const inlineAttachments = this.getInlineAttachments()
.map((a) => '--' + this.boundaries.related + eol + a.dump() + eol + eol)
.join('')
.slice(0, -1 * eol.length);
return lines + eol
+ 'Content-Type: multipart/mixed; boundary=' + this.boundaries.mixed + eol
+ eol
+ '--' + this.boundaries.mixed + eol
+ 'Content-Type: multipart/related; boundary=' + this.boundaries.related + eol
+ eol
+ this.dumpTextContent(plaintext, html, this.boundaries.related) + eol
+ eol
+ inlineAttachments
+ '--' + this.boundaries.related + '--' + eol
+ attachments
+ '--' + this.boundaries.mixed + '--';
}
else if (structure === 'mixed') {
const attachments = this.getAttachments()
.map((a) => '--' + this.boundaries.mixed + eol + a.dump() + eol + eol)
.join('')
.slice(0, -1 * eol.length);
return lines + eol
+ 'Content-Type: multipart/mixed; boundary=' + this.boundaries.mixed + eol
+ eol
+ this.dumpTextContent(plaintext, html, this.boundaries.mixed) + eol
+ (plaintext && html ? '' : eol)
+ attachments
+ '--' + this.boundaries.mixed + '--';
}
else if (structure === 'related') {
const inlineAttachments = this.getInlineAttachments()
.map((a) => '--' + this.boundaries.related + eol + a.dump() + eol + eol)
.join('')
.slice(0, -1 * eol.length);
return lines + eol
+ 'Content-Type: multipart/related; boundary=' + this.boundaries.related + eol
+ eol
+ this.dumpTextContent(plaintext, html, this.boundaries.related) + eol
+ eol
+ inlineAttachments
+ '--' + this.boundaries.related + '--';
}
else if (structure === 'alternative') {
return lines + eol
+ 'Content-Type: multipart/alternative; boundary=' + this.boundaries.alt + eol
+ eol
+ this.dumpTextContent(plaintext, html, this.boundaries.alt) + eol
+ eol
+ '--' + this.boundaries.alt + '--';
}
else {
return lines + eol + primaryMessage.dump();
}
}
asEncoded() {
return (0, base64_js_1.toBase64EncodeURI)(this.asRaw());
}
dumpTextContent(plaintext, html, boundary) {
const eol = '\r\n';
const primaryMessage = html ? html : plaintext;
let data = '';
if (plaintext && html && !this.hasInlineAttachments() && this.hasAttachments())
data = '--' + boundary + eol
+ 'Content-Type: multipart/alternative; boundary=' + this.boundaries.alt + eol
+ eol
+ '--' + this.boundaries.alt + eol
+ plaintext.dump() + eol
+ eol
+ '--' + this.boundaries.alt + eol
+ html.dump() + eol
+ eol
+ '--' + this.boundaries.alt + '--';
else if (plaintext && html && this.hasInlineAttachments())
data = '--' + boundary + eol
+ html.dump();
else if (plaintext && html)
data = '--' + boundary + eol
+ plaintext.dump() + eol
+ eol
+ '--' + boundary + eol
+ html.dump();
else
data = '--' + boundary + eol
+ primaryMessage.dump();
return data;
}
hasInlineAttachments() {
return this.messages.some((msg) => msg.isInlineAttachment());
}
hasAttachments() {
return this.messages.some((msg) => msg.isAttachment());
}
getAttachments() {
const matcher = (msg) => msg.isAttachment();
return this.messages.some(matcher) ? this.messages.filter(matcher) : [];
}
getInlineAttachments() {
const matcher = (msg) => msg.isInlineAttachment();
return this.messages.some(matcher) ? this.messages.filter(matcher) : [];
}
getMessageByType(type) {
const matcher = (msg) => !msg.isAttachment() && !msg.isInlineAttachment() && (msg.getHeader('Content-Type') || '').includes(type);
return this.messages.some(matcher) ? this.messages.filter(matcher)[0] : undefined;
}
addAttachment(opts) {
if (!this.isObject(opts.headers))
opts.headers = {};
if (typeof opts.filename !== 'string') {
throw new MIMETextError_js_1.MIMETextError('MIMETEXT_MISSING_FILENAME', 'The property filename must exist while adding attachments.');
}
let type = opts.headers['Content-Type'] || opts.contentType || 'none';
if (type.length === 0) {
throw new MIMETextError_js_1.MIMETextError('MIMETEXT_INVALID_MESSAGE_TYPE', `You specified an invalid content type "${type}".`);
}
const encoding = opts.headers['Content-Transfer-Encoding'] || opts.encoding || 'base64';
if (!this.validContentTransferEncodings.includes(encoding)) {
type = 'application/octet-stream';
}
const contentId = opts.headers['Content-ID'];
if (typeof contentId === 'string' && contentId.length > 2 && contentId.slice(0, 1) !== '<' && contentId.slice(-1) !== '>') {
opts.headers['Content-ID'] = '<' + opts.headers['Content-ID'] + '>';
}
const disposition = opts.inline ? 'inline' : 'attachment';
opts.headers = Object.assign({}, opts.headers, {
'Content-Type': `${type}; name="${opts.filename}"`,
'Content-Transfer-Encoding': encoding,
'Content-Disposition': `${disposition}; filename="${opts.filename}"`
});
return this._addMessage({ data: opts.data, headers: opts.headers });
}
addMessage(opts) {
if (!this.isObject(opts.headers))
opts.headers = {};
let type = opts.headers['Content-Type'] || opts.contentType || 'none';
if (!this.validTypes.includes(type)) {
throw new MIMETextError_js_1.MIMETextError('MIMETEXT_INVALID_MESSAGE_TYPE', `Valid content types are ${this.validTypes.join(', ')} but you specified "${type}".`);
}
const encoding = opts.headers['Content-Transfer-Encoding'] || opts.encoding || '7bit';
if (!this.validContentTransferEncodings.includes(encoding)) {
type = 'application/octet-stream';
}
const charset = opts.charset || 'UTF-8';
opts.headers = Object.assign({}, opts.headers, {
'Content-Type': `${type}; charset=${charset}`,
'Content-Transfer-Encoding': encoding
});
return this._addMessage({ data: opts.data, headers: opts.headers });
}
_addMessage(opts) {
const msg = new MIMEMessageContent_js_1.MIMEMessageContent(opts.data, opts.headers);
this.messages.push(msg);
return msg;
}
setSender(input, config = { type: 'From' }) {
const mailbox = new Mailbox_js_1.Mailbox(input, config);
this.setHeader('From', mailbox);
return mailbox;
}
getSender() {
return this.getHeader('From');
}
setRecipients(input, config = { type: 'To' }) {
const arr = !this.isArray(input) ? [input] : input;
const recs = arr.map((_input) => new Mailbox_js_1.Mailbox(_input, config));
this.setHeader(config.type, recs);
return recs;
}
getRecipients(config = { type: 'To' }) {
return this.getHeader(config.type);
}
setRecipient(input) {
return this.setRecipients(input, { type: 'To' });
}
setTo(input) {
return this.setRecipients(input, { type: 'To' });
}
setCc(input) {
return this.setRecipients(input, { type: 'Cc' });
}
setReplyTo(input) {
return this.setRecipients(input, { type: 'Reply-To' });
}
setBcc(input) {
return this.setRecipients(input, { type: 'Bcc' });
}
setSubject(value) {
this.setHeader('subject', value);
return value;
}
getSubject() {
return this.getHeader('subject');
}
setHeader(name, value) {
this.headers.set(name, value);
return name;
}
getHeader(name) {
return this.headers.get(name);
}
setHeaders(obj) {
return Object.keys(obj).map((prop) => this.setHeader(prop, obj[prop]));
}
getHeaders() {
return this.headers.toObject();
}
generateBoundaries() {
this.boundaries = {
mixed: Math.random().toString(36).slice(2),
alt: Math.random().toString(36).slice(2),
related: Math.random().toString(36).slice(2)
};
}
isArray(v) {
return (!!v) && (v.constructor === Array);
}
isObject(v) {
return (!!v) && (v.constructor === Object);
}
}
exports.MIMEMessage = MIMEMessage;