@alizadefaig/pos-server 1.2.23 → 1.2.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/app/socket/server.js +80 -19
- package/package.json +1 -1
package/app/socket/server.js
CHANGED
|
@@ -6,30 +6,85 @@ const {getConfig} = require("../config");
|
|
|
6
6
|
const {getLogger} = require("../logger/Log");
|
|
7
7
|
const {socketSendByFilter} = require("../../utils");
|
|
8
8
|
|
|
9
|
-
// Keep-alive agent — переиспользуем TCP-соединения к Laravel вместо новых на каждый
|
|
10
|
-
|
|
11
|
-
const
|
|
9
|
+
// Keep-alive agent — переиспользуем TCP-соединения к Laravel вместо новых на каждый запрос.
|
|
10
|
+
// БАГ C: maxSockets 64→128 (запас; после async-печати длинных холдов почти нет). env-настраиваемо.
|
|
11
|
+
const MAX_SOCKETS = Number(process.env.UPSTREAM_MAX_SOCKETS || 128);
|
|
12
|
+
const httpAgent = new http.Agent({ keepAlive: true, maxSockets: MAX_SOCKETS });
|
|
13
|
+
const httpsAgent = new https.Agent({ keepAlive: true, maxSockets: MAX_SOCKETS });
|
|
14
|
+
|
|
15
|
+
// БАГ D: горячие per-request логи за флагом (в проде ENABLE_LOGGER=false убирает оверхед).
|
|
16
|
+
const HOT_LOGS = (process.env.ENABLE_LOGGER ?? 'true') !== 'false';
|
|
17
|
+
|
|
18
|
+
// БАГ B: heartbeat за флагом (Шаг 5). По умолчанию OFF → нативные socket.io 25000/20000
|
|
19
|
+
// (поведение как сейчас в проде — дефолт обязан быть no-op). При WS_HEARTBEAT_ENABLED=true:
|
|
20
|
+
// 10000/5000 → «зомби»-сокет детектится ~15с вместо ~45с. Откат — снять флаг.
|
|
21
|
+
const WS_HEARTBEAT_ENABLED = (process.env.WS_HEARTBEAT_ENABLED ?? 'false') === 'true';
|
|
22
|
+
const WS_PING_INTERVAL = Number(process.env.WS_PING_INTERVAL || 10000);
|
|
23
|
+
const WS_PING_TIMEOUT = Number(process.env.WS_PING_TIMEOUT || 5000);
|
|
24
|
+
// connectionStateRecovery меняет адаптер на SessionAware + буферизует бродкасты компании
|
|
25
|
+
// (память) и ре-фаер 'connection' при recovery. За ОТДЕЛЬНЫМ флагом, по умолчанию OFF.
|
|
26
|
+
const WS_RECOVERY_ENABLED = (process.env.WS_RECOVERY_ENABLED ?? 'false') === 'true';
|
|
27
|
+
const WS_RECOVERY_MS = Number(process.env.WS_RECOVERY_MS || 30000);
|
|
28
|
+
// БАГ D: межрепличный бродкаст. Выкл по умолчанию → одиночный инстанс работает как сейчас.
|
|
29
|
+
// ВНИМАНИЕ: перед включением нужен `npm i @socket.io/redis-adapter` (пакет не в deps,
|
|
30
|
+
// чтобы не ломать `npm ci`); lazy-require ниже деградирует в одиночный режим, если его нет.
|
|
31
|
+
const REDIS_ADAPTER_ENABLED = (process.env.REDIS_ADAPTER_ENABLED ?? 'false') === 'true';
|
|
32
|
+
const REDIS_URL = process.env.REDIS_URL || 'redis://127.0.0.1:6379';
|
|
12
33
|
|
|
13
34
|
// Таймаут axios по классу события (ms)
|
|
14
35
|
const PRINT_EVENTS = new Set(['orders.sendToWorkshops', 'orders.printBillReceipt']);
|
|
15
36
|
const FISCAL_EVENTS = new Set(['fiscal.register', 'fiscal.close']);
|
|
16
37
|
function getUpstreamTimeout(event) {
|
|
17
38
|
if (FISCAL_EVENTS.has(event)) return 180000;
|
|
18
|
-
|
|
39
|
+
// В СИНХРОННОМ режиме печати этот вызов оборачивает весь круг печати на Laravel
|
|
40
|
+
// (pool 120с + Node /socket/request 90с). Должен пережить его → 150с.
|
|
41
|
+
// В АСИНХРОННОМ режиме Laravel отвечает мгновенно, запас не мешает.
|
|
42
|
+
if (PRINT_EVENTS.has(event)) return 150000;
|
|
19
43
|
return 15000;
|
|
20
44
|
}
|
|
21
45
|
|
|
22
46
|
let serverSocket = null;
|
|
23
47
|
module.exports = (server = null) => {
|
|
24
48
|
if (server && !serverSocket) {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
49
|
+
// Дефолт = поведение как сейчас (нативные heartbeat/адаптер). Опции добавляются
|
|
50
|
+
// только при явном включении флагов — это и есть «правка под флагом с откатом».
|
|
51
|
+
const serverOptions = { cors: { origin: '*' } };
|
|
52
|
+
if (WS_HEARTBEAT_ENABLED) {
|
|
53
|
+
serverOptions.pingInterval = WS_PING_INTERVAL; // быстрый детект мёртвого сокета (~15с)
|
|
54
|
+
serverOptions.pingTimeout = WS_PING_TIMEOUT;
|
|
55
|
+
}
|
|
56
|
+
if (WS_RECOVERY_ENABLED) {
|
|
57
|
+
// Восстановление сессии/комнат при кратком разрыве (Wi-Fi-флап) без полного reconnect.
|
|
58
|
+
serverOptions.connectionStateRecovery = {
|
|
59
|
+
maxDisconnectionDuration: WS_RECOVERY_MS,
|
|
60
|
+
skipMiddlewares: true,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
serverSocket = new Server(server, serverOptions);
|
|
30
64
|
|
|
31
65
|
const logger = getLogger();
|
|
32
66
|
|
|
67
|
+
// БАГ D: межрепличный бродкаст для нескольких инстансов Node. За флагом и lazy-require —
|
|
68
|
+
// при выключенном флаге (по умолчанию) пакет не требуется, одиночный инстанс работает
|
|
69
|
+
// как раньше. При сбое подключения деградируем в одиночный режим, не падаем.
|
|
70
|
+
if (REDIS_ADAPTER_ENABLED) {
|
|
71
|
+
(async () => {
|
|
72
|
+
try {
|
|
73
|
+
const { createAdapter } = require('@socket.io/redis-adapter');
|
|
74
|
+
const { createClient } = require('redis');
|
|
75
|
+
const pubClient = createClient({ url: REDIS_URL });
|
|
76
|
+
const subClient = pubClient.duplicate();
|
|
77
|
+
pubClient.on('error', (e) => logger.error('redis pub error: ' + e.message));
|
|
78
|
+
subClient.on('error', (e) => logger.error('redis sub error: ' + e.message));
|
|
79
|
+
await Promise.all([pubClient.connect(), subClient.connect()]);
|
|
80
|
+
serverSocket.adapter(createAdapter(pubClient, subClient));
|
|
81
|
+
console.log('[socket] redis-adapter enabled (multi-replica broadcast)');
|
|
82
|
+
} catch (e) {
|
|
83
|
+
console.error('[socket] redis-adapter init failed → single-instance mode:', e.message);
|
|
84
|
+
}
|
|
85
|
+
})();
|
|
86
|
+
}
|
|
87
|
+
|
|
33
88
|
|
|
34
89
|
serverSocket.on('connection', (socket) => {
|
|
35
90
|
const companyId = socket.handshake.query.company_id;
|
|
@@ -46,13 +101,19 @@ module.exports = (server = null) => {
|
|
|
46
101
|
socket.isProxy = isProxy;
|
|
47
102
|
|
|
48
103
|
if (!isProxy) {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
104
|
+
// При connectionStateRecovery 'connection' ре-фаерится на восстановленном
|
|
105
|
+
// сокете (socket.recovered=true) — повторный presence-POST не нужен (терминал
|
|
106
|
+
// уже зарегистрирован, комнаты восстановлены). Обработчики ниже регистрируем
|
|
107
|
+
// всегда (объект сокета новый).
|
|
108
|
+
if (!socket.recovered) {
|
|
109
|
+
axios.post(`${PROXY_TARGET}/api/socket/connection`, {
|
|
110
|
+
companyId,
|
|
111
|
+
terminalId,
|
|
112
|
+
userId,
|
|
113
|
+
}).catch(error => {
|
|
114
|
+
logger.error(error);
|
|
115
|
+
});
|
|
116
|
+
}
|
|
56
117
|
|
|
57
118
|
// API-over-socket: frontend sends API calls over the existing WS connection
|
|
58
119
|
socket.on('api:request', async ({ request_id, event, data, headers }) => {
|
|
@@ -60,7 +121,7 @@ module.exports = (server = null) => {
|
|
|
60
121
|
const ackEvent = `api:ack:${request_id}`;
|
|
61
122
|
const responseEvent = `api:response:${request_id}`;
|
|
62
123
|
const t0 = Date.now();
|
|
63
|
-
console.log(`[api:req] ${event} id=${request_id.slice(0, 8)} terminal=${terminalId?.slice(0, 8)}`);
|
|
124
|
+
if (HOT_LOGS) console.log(`[api:req] ${event} id=${request_id.slice(0, 8)} terminal=${terminalId?.slice(0, 8)}`);
|
|
64
125
|
|
|
65
126
|
// Немедленно подтверждаем получение — фронт знает что запрос дошёл
|
|
66
127
|
socket.emit(ackEvent);
|
|
@@ -85,11 +146,11 @@ module.exports = (server = null) => {
|
|
|
85
146
|
}
|
|
86
147
|
);
|
|
87
148
|
const ms = Date.now() - t0;
|
|
88
|
-
console.log(`[api:res] ${event} ✓ ${ms}ms id=${request_id.slice(0, 8)}`);
|
|
149
|
+
if (HOT_LOGS) console.log(`[api:res] ${event} ✓ ${ms}ms id=${request_id.slice(0, 8)}`);
|
|
89
150
|
socket.emit(responseEvent, { success: true, data: response.data });
|
|
90
151
|
} catch (err) {
|
|
91
152
|
const ms = Date.now() - t0;
|
|
92
|
-
console.log(`[api:res] ${event} ✗ ${ms}ms status=${err?.response?.status} id=${request_id.slice(0, 8)}`);
|
|
153
|
+
if (HOT_LOGS) console.log(`[api:res] ${event} ✗ ${ms}ms status=${err?.response?.status} id=${request_id.slice(0, 8)}`);
|
|
93
154
|
socket.emit(responseEvent, { success: false, error: err?.response?.data ?? { message: err.message } });
|
|
94
155
|
}
|
|
95
156
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alizadefaig/pos-server",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.25",
|
|
4
4
|
"description": "Server in express js with Proxy to Main server if need and will work with Electron JS",
|
|
5
5
|
"homepage": "https://gitlab.com/faigalizade/pos-server#readme",
|
|
6
6
|
"bugs": {
|