@unchainedshop/plugins
Version:
Official plugin collection for the Unchained Engine with payment, delivery, and pricing adapters
137 lines (136 loc) • 4.8 kB
JavaScript
import { WorkerDirector, WorkerAdapter } from '@unchainedshop/core';
import { spawn } from 'node:child_process';
import { writeFile, mkdtemp } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { createLogger } from '@unchainedshop/logger';
const logger = createLogger('unchained:worker:email');
export const checkEmailInterceptionEnabled = () => {
return process.env.NODE_ENV !== 'production' && !process.env.UNCHAINED_DISABLE_EMAIL_INTERCEPTION;
};
const buildLink = async ({ filename, content, href, contentType, encoding, path }) => {
if (path) {
return `<a href="file:/${path.startsWith('/') ? path : `${process.cwd()}/${path}`}">${filename}</a>`;
}
if (href) {
return `<a href="${href}">${filename}</a>`;
}
if (content && encoding === 'base64') {
return `<a target="_blank" href="${`data:${contentType};base64,${content}`}">${filename}</a>`;
}
return '';
};
let nodemailer;
try {
const nodemailerModule = await import('nodemailer');
nodemailer = nodemailerModule.default;
}
catch {
logger.warn(`optional peer npm package 'nodemailer' not installed, emails can't be sent`);
}
const openInBrowser = async (options) => {
const command = {
darwin: 'open',
win32: 'explorer.exe',
linux: 'xdg-open',
}[process.platform];
if (!command) {
return false;
}
const messageBody = options.html || options.text.replace(/(\r\n|\n|\r)/gm, '<br/>');
const attachmentLinks = await Promise.all((options.attachments || []).map(buildLink));
const content = `
<!DOCTYPE html>
<html lang="en" xmlns="https://www.w3.org/1999/xhtml" xmlns:o="urn:schemas-microsoft-com:office:office">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width,initial-scale=1">
</head>
<body>
<b>From: </b>${options.from}<br/>
<b>To: </b>${options.to}<br/>
<b>Cc: </b>${options.cc}<br/>
<b>Bcc: </b>${options.bcc}<br/>
<b>Reply-To: </b>${options.replyTo}<br/>
<br/>
<b>subject: </b>${options.subject}<br/>
<b>attachments: </b>${attachmentLinks.join(', ')}<br/>
<hr/>
${messageBody}
</body>
</html>`;
const tempDir = await mkdtemp(join(tmpdir(), 'unchained-email-'));
const tempFile = join(tempDir, 'email-preview.html');
await writeFile(tempFile, content, 'utf8');
return new Promise((resolve) => {
const child = spawn(command, [tempFile], {
detached: true,
stdio: 'ignore',
});
child.unref();
resolve(true);
});
};
const EmailWorkerPlugin = {
...WorkerAdapter,
key: 'shop.unchained.worker-plugin.email',
label: 'Send a Mail through Nodemailer',
version: '1.0.0',
type: 'EMAIL',
doWork: async ({ from, to, subject, ...rest }) => {
if (!to) {
return {
success: false,
error: {
name: 'RECIPIENT_REQUIRED',
message: 'EMAIL requires a to',
},
};
}
try {
const sendMailOptions = {
from,
to,
subject,
...rest,
};
if (checkEmailInterceptionEnabled()) {
const opened = await openInBrowser(sendMailOptions);
return {
success: opened,
result: opened ? { intercepted: true } : undefined,
error: !opened ? { message: "Interception failed due to missing package 'open'" } : undefined,
};
}
if (!process.env.MAIL_URL) {
return {
success: false,
error: { name: 'NO_MAIL_URL_SET', message: 'MAIL_URL is not set' },
};
}
if (!nodemailer) {
return {
success: false,
error: {
name: 'NODEMAILER_NOT_INSTALLED',
message: 'npm dependency nodemailer is not installed, please install it to use email features',
},
};
}
const transporter = nodemailer.createTransport(process.env.MAIL_URL);
const result = await transporter.sendMail(sendMailOptions);
return { success: true, result };
}
catch (err) {
return {
success: false,
error: {
name: err.name,
message: err.message,
stack: err.stack,
},
};
}
},
};
WorkerDirector.registerAdapter(EmailWorkerPlugin);