fix #1
@ -17,3 +17,5 @@ SMTP_MAIL_FROM=noreply@example.com # Email отправителя
|
|||||||
API_PASS=your_secure_password_here # Пароль для доступа к API (используйте сложный!)
|
API_PASS=your_secure_password_here # Пароль для доступа к API (используйте сложный!)
|
||||||
PORT=3000 # Порт, на котором будет работать сервер
|
PORT=3000 # Порт, на котором будет работать сервер
|
||||||
HOST=0.0.0.0 # Хост для облачного деплоя
|
HOST=0.0.0.0 # Хост для облачного деплоя
|
||||||
|
FNS_TIMEOUT_MS=30000 # Таймаут создания чека в ФНС
|
||||||
|
SMTP_TIMEOUT_MS=15000 # Таймаут отправки email
|
||||||
|
|||||||
@ -43,6 +43,8 @@ SMTP_MAIL_FROM=noreply@example.com
|
|||||||
API_PASS=strong_api_password
|
API_PASS=strong_api_password
|
||||||
PORT=3000
|
PORT=3000
|
||||||
HOST=0.0.0.0
|
HOST=0.0.0.0
|
||||||
|
FNS_TIMEOUT_MS=30000
|
||||||
|
SMTP_TIMEOUT_MS=15000
|
||||||
```
|
```
|
||||||
|
|
||||||
`API_PASS` должен совпадать с `api_pass` в запросах к `POST /api/v1/create-receipt`.
|
`API_PASS` должен совпадать с `api_pass` в запросах к `POST /api/v1/create-receipt`.
|
||||||
|
|||||||
72
server.js
72
server.js
@ -19,6 +19,8 @@ app.use(express.json({ limit: "1mb" }));
|
|||||||
const PORT = process.env.PORT || 4000;
|
const PORT = process.env.PORT || 4000;
|
||||||
const HOST = process.env.HOST || "0.0.0.0";
|
const HOST = process.env.HOST || "0.0.0.0";
|
||||||
const MAX_RETRIES = 3;
|
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 ERROR_FILE = path.join(__dirname, "error.json");
|
||||||
const ADMIN_EMAIL = process.env.ADMIN_EMAIL;
|
const ADMIN_EMAIL = process.env.ADMIN_EMAIL;
|
||||||
|
|
||||||
@ -29,7 +31,10 @@ const transporter = nodemailer.createTransport({
|
|||||||
auth: {
|
auth: {
|
||||||
user: process.env.SMTP_USER,
|
user: process.env.SMTP_USER,
|
||||||
pass: process.env.SMTP_PASS
|
pass: process.env.SMTP_PASS
|
||||||
}
|
},
|
||||||
|
connectionTimeout: SMTP_TIMEOUT_MS,
|
||||||
|
greetingTimeout: SMTP_TIMEOUT_MS,
|
||||||
|
socketTimeout: SMTP_TIMEOUT_MS
|
||||||
});
|
});
|
||||||
|
|
||||||
let nalogApi;
|
let nalogApi;
|
||||||
@ -57,10 +62,28 @@ function calculateTotal(items = []) {
|
|||||||
}, 0);
|
}, 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) {
|
async function createReceiptWithRetry(income, retries = MAX_RETRIES) {
|
||||||
for (let attempt = 1; attempt <= retries; attempt++) {
|
for (let attempt = 1; attempt <= retries; attempt++) {
|
||||||
try {
|
try {
|
||||||
return await getNalogApi().addIncome(income);
|
return await withTimeout(
|
||||||
|
getNalogApi().addIncome(income),
|
||||||
|
FNS_TIMEOUT_MS,
|
||||||
|
"FNS receipt creation"
|
||||||
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(`Попытка ${attempt} не удалась`, err.message || err);
|
console.error(`Попытка ${attempt} не удалась`, err.message || err);
|
||||||
if (attempt === retries) throw err;
|
if (attempt === retries) throw err;
|
||||||
@ -118,12 +141,16 @@ async function notifyAdmin(errorData) {
|
|||||||
</html>
|
</html>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
await transporter.sendMail({
|
await withTimeout(
|
||||||
from: process.env.SMTP_MAIL_FROM,
|
transporter.sendMail({
|
||||||
to: ADMIN_EMAIL,
|
from: process.env.SMTP_MAIL_FROM,
|
||||||
subject: `Ошибка создания чека ${process.env.APPNAME}`,
|
to: ADMIN_EMAIL,
|
||||||
html
|
subject: `Ошибка создания чека ${process.env.APPNAME}`,
|
||||||
});
|
html
|
||||||
|
}),
|
||||||
|
SMTP_TIMEOUT_MS,
|
||||||
|
"Admin email sending"
|
||||||
|
);
|
||||||
|
|
||||||
console.log(`Администратор ${ADMIN_EMAIL} уведомлен об ошибке`);
|
console.log(`Администратор ${ADMIN_EMAIL} уведомлен об ошибке`);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -176,6 +203,19 @@ app.get("/health/deep", async (req, res) => {
|
|||||||
res.json(result);
|
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) => {
|
app.post("/api/v1/create-receipt", async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { api_pass, email, items } = req.body;
|
const { api_pass, email, items } = req.body;
|
||||||
@ -260,12 +300,16 @@ ${process.env.APPNAME}
|
|||||||
</html>
|
</html>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
await transporter.sendMail({
|
await withTimeout(
|
||||||
from: process.env.SMTP_MAIL_FROM,
|
transporter.sendMail({
|
||||||
to: email,
|
from: process.env.SMTP_MAIL_FROM,
|
||||||
subject: `Чек ${process.env.APPNAME}`,
|
to: email,
|
||||||
html
|
subject: `Чек ${process.env.APPNAME}`,
|
||||||
});
|
html
|
||||||
|
}),
|
||||||
|
SMTP_TIMEOUT_MS,
|
||||||
|
"Client email sending"
|
||||||
|
);
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
success: true,
|
success: true,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user