feat: email client prototype

This commit is contained in:
2024-06-26 20:40:00 +02:00
committed by Rainer Killinger
parent 07e5c80223
commit 31c54083a9
44 changed files with 2597 additions and 196 deletions

View File

@@ -0,0 +1,2 @@
#!/usr/bin/env node
import './lib/cli.js';

View File

@@ -0,0 +1,68 @@
{
"name": "@openstapps/mail-plugin",
"description": "Mail Plugin",
"version": "3.2.0",
"private": true,
"type": "module",
"license": "GPL-3.0-only",
"author": "Thea Schöbl",
"bin": "app.js",
"files": [
"app.js",
"lib",
"README.md",
"CHANGELOG.md",
"Dockerfile"
],
"scripts": {
"build": "tsup-node --dts",
"deploy": "pnpm --prod --filter=@openstapps/minimal-plugin deploy ../../.deploy/minimal-plugin",
"dev": "tsup-node --watch --onSuccess \"pnpm run start\"",
"format": "prettier . -c --ignore-path ../../.gitignore",
"format:fix": "prettier --write . --ignore-path ../../.gitignore",
"lint": "eslint --ext .ts src/",
"lint:fix": "eslint --fix --ext .ts src/",
"start": "node app.js"
},
"dependencies": {
"@openstapps/core": "workspace:*",
"@openstapps/core-tools": "workspace:*",
"@openstapps/logger": "workspace:*",
"commander": "10.0.0",
"dotenv": "16.4.5",
"express": "4.18.2",
"imapflow": "1.0.162",
"mailparser": "3.7.1",
"node-forge": "1.3.1",
"nodemailer": "6.9.14",
"ts-node": "10.9.2"
},
"devDependencies": {
"@openstapps/eslint-config": "workspace:*",
"@openstapps/prettier-config": "workspace:*",
"@openstapps/tsconfig": "workspace:*",
"@types/express": "4.17.17",
"@types/imapflow": "1.0.18",
"@types/mailparser": "3.4.4",
"@types/node": "18.15.3",
"@types/node-forge": "1.3.11",
"@types/nodemailer": "6.4.15",
"tsup": "6.7.0",
"typescript": "5.4.2"
},
"tsup": {
"entry": [
"src/cli.ts"
],
"sourcemap": true,
"clean": true,
"format": "esm",
"outDir": "lib"
},
"prettier": "@openstapps/prettier-config",
"eslintConfig": {
"extends": [
"@openstapps"
]
}
}

View File

@@ -0,0 +1,123 @@
import {config} from 'dotenv';
import {ImapFlow} from 'imapflow';
import express from 'express';
config({path: '.env.local'});
const app = express();
const port = process.env.PORT || 4000;
app.use(async (request, response, next) => {
try {
const [user, pass] = Buffer.from(request.headers['authorization']!.replace(/^Basic /, ''), 'base64')
.toString('utf8')
.split(':');
const client = new ImapFlow({
host: 'imap.server.uni-frankfurt.de',
port: 993,
secure: true,
emitLogs: false,
auth: {user, pass},
});
response.locals.client = client;
await client.connect();
response.on('finish', async () => {
await client.logout();
client.close();
});
next();
} catch {
response.status(401).send();
}
});
app.get('/', async (_request, response) => {
const result = await response.locals.client.listTree();
response.json(result);
});
app.get('/:mailbox', async (request, response) => {
try {
await response.locals.client.mailboxOpen(request.params.mailbox);
const since = Number(request.query.since) || undefined;
const preData = await response.locals.client.status(request.params.mailbox, {messages: true});
if (preData.messages === 0) {
response.json([]);
return;
}
const data = response.locals.client.fetch(
'1:*',
{},
{
// caution, BigInt can throw
changedSince: typeof since === 'string' ? BigInt(since) : undefined,
},
);
const messages = [];
for await (const message of data) {
messages.push(message.seq.toString());
}
response.json(messages);
} catch (error) {
console.error(error);
response.status(404).send();
}
});
app.get('/:mailbox/:id', async (request, response) => {
try {
await response.locals.client.mailboxOpen(request.params.mailbox);
const message = await response.locals.client.fetchOne(request.params.id, {
envelope: true,
labels: true,
flags: true,
bodyStructure: true,
});
response.json({
bodyStructure: message.bodyStructure,
labels: [...(message.labels ?? [])],
flags: [...(message.flags ?? [])],
envelope: message.envelope,
seq: message.seq,
});
} catch (error) {
console.error(error);
response.status(404).send();
}
});
app.get('/:mailbox/:id/:part', async (request, response) => {
try {
await response.locals.client.mailboxOpen(request.params.mailbox, {readOnly: true});
if (request.query.raw) {
const message = await response.locals.client.fetchOne(request.params.id, {
bodyParts: [`${request.params.part}.mime`, request.params.part],
});
response.write(message.bodyParts.get(`${request.params.part}.mime`));
response.write(message.bodyParts.get(request.params.part));
response.end();
} else {
const message = await response.locals.client.download(request.params.id, request.params.part);
message.content.on('data', chunk => {
response.write(chunk);
});
message.content.on('end', () => {
response.end();
});
}
} catch (error) {
console.error(error);
response.status(404).send();
}
});
app.listen(port, () => {
console.log(`Server listening on port ${port}`);
});

9
backend/mail-plugin/src/types.d.ts vendored Normal file
View File

@@ -0,0 +1,9 @@
import {ImapFlow} from 'imapflow';
declare global {
namespace Express {
interface Locals {
client: ImapFlow;
}
}
}

View File

@@ -0,0 +1,4 @@
{
"extends": "@openstapps/tsconfig",
"exclude": ["lib", "app.js"]
}