mirror of
https://github.com/Trollobot/Telemax.git
synced 2026-08-29 11:01:41 +00:00
- redactSecrets: маскируются password/trackId/phone и любые строки от 512 символов — пароль MAX больше не попадает в лог веб-панели открытым текстом - Веб-панель по умолчанию работает по HTTPS с самоподписанным сертификатом (генерируется при первом старте в .data/tls); старые http-ссылки получают 301-редирект на том же порту (polyglot по первому байту; resume строго в process.nextTick — иначе TLS-хендшейк зависает); /apikey даёт https-ссылку; PANEL_TLS=off для установки за своим reverse proxy - Сертификаты Минцифры больше не доверяются всему контейнеру через NODE_EXTRA_CA_CERTS — src/max/ca.ts скоупит их только на соединения с MAX (ca в tls.connect + undici-агент для CDN-загрузок); Telegram и GitHub проверяются только по стандартным корням Mozilla - Запросы одного опкода к MAX сериализуются (request() в client.ts) — два конкурентных MSG_SEND больше не перепутают messageId-связки редактирования и удаления - Починена гонка первого чтения в ChatMapStore: конкурентные обращения форкали кэш, и часть upsert'ов молча терялась на диске - Graceful shutdown по SIGTERM/SIGINT; в панели — реконнект WebSocket и периодическое обновление метрик; сравнение id в patchCachedChatLastMessage переведено на String() (BigInt-чаты не совпадали) - Удалён мёртвый код: эндпоинты /api/debug/*, getPollVoters, createTelegramBot, цепочка historySynced, getChats(marker) - React/Tailwind/lucide перенесены в devDependencies — рантайм-образ легче; vitest 4, npm audit: 0 уязвимостей; chmod 600 для .env в setup.sh/update.sh - Новые тесты: redactSecrets, сторы (эвикшн/нормализация/конкурентность), сериализация запросов — всего 41
59 lines
2.2 KiB
TypeScript
59 lines
2.2 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
import { MaxClient } from '../src/max/client.js';
|
|
|
|
/**
|
|
* Fakes the socket layer: every send() gets a distinct async response for its
|
|
* own opcode, mimicking a server that answers strictly one response per request.
|
|
*/
|
|
class FakeMaxClient extends MaxClient {
|
|
readonly sentPayloads: unknown[] = [];
|
|
private responseCounter = 0;
|
|
|
|
override send(opcode: number, payload?: unknown): void {
|
|
this.sentPayloads.push(payload);
|
|
const n = ++this.responseCounter;
|
|
queueMicrotask(() => {
|
|
this.emit('message', {
|
|
dir: 1,
|
|
seq: n,
|
|
opcode,
|
|
payload: { message: { id: BigInt(n * 100), attaches: [] } },
|
|
length: 0,
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
describe('MaxClient request serialization', () => {
|
|
it('concurrent same-opcode requests each get their own response, in send order', async () => {
|
|
const client = new FakeMaxClient();
|
|
|
|
// Two concurrent MSG_SENDs — exactly what Telegraf produces for two quick
|
|
// Telegram messages (it handles a poll batch's updates concurrently).
|
|
// Before serialization both promises resolved on the FIRST response frame,
|
|
// cross-wiring the messageId links that edit/delete depend on.
|
|
const [first, second] = await Promise.all([client.sendMessage(1, 'первое'), client.sendMessage(1, 'второе')]);
|
|
|
|
expect(first.messageId).toBe(100n);
|
|
expect(second.messageId).toBe(200n);
|
|
|
|
const texts = client.sentPayloads.map((p) => (p as { message: { text: string } }).message.text);
|
|
expect(texts).toEqual(['первое', 'второе']);
|
|
});
|
|
|
|
it('a failed request does not wedge the queue for the next one', async () => {
|
|
const client = new FakeMaxClient();
|
|
// First call throws at send time (e.g. socket gone) — the chain must survive.
|
|
const realSend = FakeMaxClient.prototype.send;
|
|
let calls = 0;
|
|
client.send = (opcode: number, payload?: unknown): void => {
|
|
calls += 1;
|
|
if (calls === 1) throw new Error('not connected');
|
|
realSend.call(client, opcode, payload);
|
|
};
|
|
|
|
await expect(client.sendMessage(1, 'сломается')).rejects.toThrow('not connected');
|
|
const ok = await client.sendMessage(1, 'пройдёт');
|
|
expect(ok.messageId).toBe(100n);
|
|
});
|
|
});
|