This commit is contained in:
parent
bc07c01ba7
commit
4131c21645
|
@ -18,6 +18,7 @@
|
||||||
"@types/mailparser": "^2.7.0",
|
"@types/mailparser": "^2.7.0",
|
||||||
"@types/node": "^13.9.1",
|
"@types/node": "^13.9.1",
|
||||||
"@types/node-fetch": "^2.5.5",
|
"@types/node-fetch": "^2.5.5",
|
||||||
|
"@types/nodemailer": "^6.4.0",
|
||||||
"@types/react": "^16.9.23",
|
"@types/react": "^16.9.23",
|
||||||
"@types/react-dom": "^16.9.5",
|
"@types/react-dom": "^16.9.5",
|
||||||
"@types/smtp-server": "^3.5.3",
|
"@types/smtp-server": "^3.5.3",
|
||||||
|
@ -25,6 +26,7 @@
|
||||||
"concurrently": "^5.1.0",
|
"concurrently": "^5.1.0",
|
||||||
"jest": "^25.1.0",
|
"jest": "^25.1.0",
|
||||||
"node-fetch": "^2.6.0",
|
"node-fetch": "^2.6.0",
|
||||||
|
"nodemailer": "^6.4.5",
|
||||||
"nodemon": "^2.0.2",
|
"nodemon": "^2.0.2",
|
||||||
"prettier": "^1.19.1",
|
"prettier": "^1.19.1",
|
||||||
"typescript": "^3.8.3"
|
"typescript": "^3.8.3"
|
||||||
|
|
|
@ -33,6 +33,7 @@ const webApp = express()
|
||||||
});
|
});
|
||||||
|
|
||||||
export const emailServer = new SMTPServer({
|
export const emailServer = new SMTPServer({
|
||||||
|
authOptional: true,
|
||||||
async onData(stream, session, callback) {
|
async onData(stream, session, callback) {
|
||||||
const paths = session.envelope.rcptTo.flatMap(({ address }) => {
|
const paths = session.envelope.rcptTo.flatMap(({ address }) => {
|
||||||
const match = address.match(/^(\w+)@kill-the-newsletter.com$/);
|
const match = address.match(/^(\w+)@kill-the-newsletter.com$/);
|
||||||
|
@ -44,7 +45,7 @@ export const emailServer = new SMTPServer({
|
||||||
});
|
});
|
||||||
if (paths.length === 0) return callback();
|
if (paths.length === 0) return callback();
|
||||||
const email = await simpleParser(stream);
|
const email = await simpleParser(stream);
|
||||||
const entry = Entry({
|
const { entry } = Entry({
|
||||||
title: email.subject,
|
title: email.subject,
|
||||||
author: email.from.text,
|
author: email.from.text,
|
||||||
content: typeof email.html !== "boolean" ? email.html : email.textAsHtml
|
content: typeof email.html !== "boolean" ? email.html : email.textAsHtml
|
||||||
|
@ -54,8 +55,10 @@ export const emailServer = new SMTPServer({
|
||||||
fs.readFileSync(path).toString()
|
fs.readFileSync(path).toString()
|
||||||
);
|
);
|
||||||
xml.feed.updated = now();
|
xml.feed.updated = now();
|
||||||
|
if (xml.feed.entry === undefined) xml.feed.entry = [];
|
||||||
xml.feed.entry.unshift(entry);
|
xml.feed.entry.unshift(entry);
|
||||||
while (renderXML(xml).length > 500_000) xml.feed.entry.pop();
|
while (xml.feed.entry.length > 0 && renderXML(xml).length > 500_000)
|
||||||
|
xml.feed.entry.pop();
|
||||||
fs.writeFileSync(path, renderXML(xml));
|
fs.writeFileSync(path, renderXML(xml));
|
||||||
}
|
}
|
||||||
callback();
|
callback();
|
||||||
|
@ -275,7 +278,7 @@ function feedURL(token: string) {
|
||||||
return `https://www.kill-the-newsletter.com/feeds/${token}.xml`;
|
return `https://www.kill-the-newsletter.com/feeds/${token}.xml`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function feedEmail(token: string) {
|
export function feedEmail(token: string) {
|
||||||
return `${token}@kill-the-newsletter.com`;
|
return `${token}@kill-the-newsletter.com`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
58
src/test.ts
58
src/test.ts
|
@ -1,17 +1,61 @@
|
||||||
import { developmentWebServer, emailServer, feedPath } from ".";
|
import { developmentWebServer, emailServer, feedPath, feedEmail } from ".";
|
||||||
|
import { createTransport } from "nodemailer";
|
||||||
import fetch from "node-fetch";
|
import fetch from "node-fetch";
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
|
|
||||||
test("create feed", async () => {
|
test("create feed", async () => {
|
||||||
const token = await createFeed();
|
const token = await createFeed();
|
||||||
const feed = fs.readFileSync(feedPath(token)).toString();
|
|
||||||
|
|
||||||
expect(feed).toMatch("My Feed");
|
expect(readFeed(token)).toMatch("My Feed");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("receive email", async () => {
|
describe("receive email", () => {
|
||||||
|
const transporter = createTransport({
|
||||||
|
host: "localhost",
|
||||||
|
port: 2525,
|
||||||
|
tls: { rejectUnauthorized: false }
|
||||||
|
});
|
||||||
|
|
||||||
|
test("HTML content", async () => {
|
||||||
const token = await createFeed();
|
const token = await createFeed();
|
||||||
})
|
await transporter.sendMail({
|
||||||
|
from: "publisher@example.com",
|
||||||
|
to: feedEmail(token),
|
||||||
|
subject: "New Message",
|
||||||
|
html: "<p>HTML content</p>"
|
||||||
|
});
|
||||||
|
const feed = readFeed(token);
|
||||||
|
expect(feed).toMatch("publisher@example.com");
|
||||||
|
expect(feed).toMatch("New Message");
|
||||||
|
expect(feed).toMatch("HTML content");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("text content", async () => {
|
||||||
|
const token = await createFeed();
|
||||||
|
await transporter.sendMail({
|
||||||
|
from: "publisher@example.com",
|
||||||
|
to: feedEmail(token),
|
||||||
|
subject: "New Message",
|
||||||
|
text: "TEXT content"
|
||||||
|
});
|
||||||
|
const feed = readFeed(token);
|
||||||
|
expect(feed).toMatch("TEXT content");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("truncation", async () => {
|
||||||
|
const token = await createFeed();
|
||||||
|
for (const repetition of [...new Array(4).keys()])
|
||||||
|
await transporter.sendMail({
|
||||||
|
from: "publisher@example.com",
|
||||||
|
to: feedEmail(token),
|
||||||
|
subject: "New Message",
|
||||||
|
text: `REPETITION ${repetition} `.repeat(10_000)
|
||||||
|
});
|
||||||
|
const feed = readFeed(token);
|
||||||
|
expect(feed).toMatch("REPETITION 3");
|
||||||
|
expect(feed).not.toMatch("REPETITION 0");
|
||||||
|
}, 10_000);
|
||||||
|
});
|
||||||
|
|
||||||
afterAll(() => {
|
afterAll(() => {
|
||||||
developmentWebServer.close();
|
developmentWebServer.close();
|
||||||
|
@ -26,3 +70,7 @@ async function createFeed(): Promise<string> {
|
||||||
const responseText = await response.text();
|
const responseText = await response.text();
|
||||||
return responseText.match(/(\w{20}).xml/)![1];
|
return responseText.match(/(\w{20}).xml/)![1];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function readFeed(token: string): string {
|
||||||
|
return fs.readFileSync(feedPath(token)).toString();
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue