fix #1

Merged
romtuck merged 1 commits from dd into main 2026-05-31 19:05:28 +03:00
3 changed files with 62 additions and 14 deletions

View File

@ -17,3 +17,5 @@ SMTP_MAIL_FROM=noreply@example.com # Email отправителя
API_PASS=your_secure_password_here # Пароль для доступа к API (используйте сложный!)
PORT=3000 # Порт, на котором будет работать сервер
HOST=0.0.0.0 # Хост для облачного деплоя
FNS_TIMEOUT_MS=30000 # Таймаут создания чека в ФНС
SMTP_TIMEOUT_MS=15000 # Таймаут отправки email

View File

@ -43,6 +43,8 @@ SMTP_MAIL_FROM=noreply@example.com
API_PASS=strong_api_password
PORT=3000
HOST=0.0.0.0
FNS_TIMEOUT_MS=30000
SMTP_TIMEOUT_MS=15000
```
`API_PASS` должен совпадать с `api_pass` в запросах к `POST /api/v1/create-receipt`.

View File

@ -19,6 +19,8 @@ app.use(express.json({ limit: "1mb" }));
const PORT = process.env.PORT || 4000;
const HOST = process.env.HOST || "0.0.0.0";
const MAX_RETRIES = 3;
const FNS_TIMEOUT_MS = Number(process.env.FNS_TIMEOUT_MS || 30000);
const SMTP_TIMEOUT_MS = Number(process.env.SMTP_TIMEOUT_MS || 15000);
const ERROR_FILE = path.join(__dirname, "error.json");
const ADMIN_EMAIL = process.env.ADMIN_EMAIL;
@ -29,7 +31,10 @@ const transporter = nodemailer.createTransport({
auth: {
user: process.env.SMTP_USER,
pass: process.env.SMTP_PASS
}
},
connectionTimeout: SMTP_TIMEOUT_MS,
greetingTimeout: SMTP_TIMEOUT_MS,
socketTimeout: SMTP_TIMEOUT_MS
});
let nalogApi;
@ -57,10 +62,28 @@ function calculateTotal(items = []) {
}, 0);
}
function withTimeout(promise, timeoutMs, label) {
let timeoutId;
const timeout = new Promise((_, reject) => {
timeoutId = setTimeout(() => {
reject(new Error(`${label} timeout after ${timeoutMs}ms`));
}, timeoutMs);
});
return Promise.race([promise, timeout]).finally(() => {
clearTimeout(timeoutId);
});
}
async function createReceiptWithRetry(income, retries = MAX_RETRIES) {
for (let attempt = 1; attempt <= retries; attempt++) {
try {
return await getNalogApi().addIncome(income);
return await withTimeout(
getNalogApi().addIncome(income),
FNS_TIMEOUT_MS,
"FNS receipt creation"
);
} catch (err) {
console.error(`Попытка ${attempt} не удалась`, err.message || err);
if (attempt === retries) throw err;
@ -118,12 +141,16 @@ async function notifyAdmin(errorData) {
</html>
`;
await transporter.sendMail({
from: process.env.SMTP_MAIL_FROM,
to: ADMIN_EMAIL,
subject: `Ошибка создания чека ${process.env.APPNAME}`,
html
});
await withTimeout(
transporter.sendMail({
from: process.env.SMTP_MAIL_FROM,
to: ADMIN_EMAIL,
subject: `Ошибка создания чека ${process.env.APPNAME}`,
html
}),
SMTP_TIMEOUT_MS,
"Admin email sending"
);
console.log(`Администратор ${ADMIN_EMAIL} уведомлен об ошибке`);
} catch (err) {
@ -176,6 +203,19 @@ app.get("/health/deep", async (req, res) => {
res.json(result);
});
app.get("/health/smtp", async (req, res) => {
try {
await withTimeout(transporter.verify(), SMTP_TIMEOUT_MS, "SMTP health check");
res.json({ status: "ok", smtp: "ok" });
} catch (err) {
res.status(500).json({
status: "error",
smtp: "error",
message: err.message || "SMTP check failed"
});
}
});
app.post("/api/v1/create-receipt", async (req, res) => {
try {
const { api_pass, email, items } = req.body;
@ -260,12 +300,16 @@ ${process.env.APPNAME}
</html>
`;
await transporter.sendMail({
from: process.env.SMTP_MAIL_FROM,
to: email,
subject: `Чек ${process.env.APPNAME}`,
html
});
await withTimeout(
transporter.sendMail({
from: process.env.SMTP_MAIL_FROM,
to: email,
subject: `Чек ${process.env.APPNAME}`,
html
}),
SMTP_TIMEOUT_MS,
"Client email sending"
);
res.json({
success: true,