@alizadefaig/pos-server 1.2.34 → 1.2.35

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/db/driver.js CHANGED
@@ -179,6 +179,12 @@ const openPostgres = () => {
179
179
  driverName = 'pg';
180
180
 
181
181
  const pool = new pg.Pool(pgConfig());
182
+ // Простаивающее соединение оборвалось (рестарт Postgres, сеть): pg шлёт 'error' на пул, и без
183
+ // слушателя это необработанное исключение — процесс облачного Node падал. Пул сам выбросит
184
+ // мёртвое соединение и откроет новое на следующем запросе.
185
+ pool.on('error', (e) => {
186
+ console.error('[db] idle pg client error:', e && e.message);
187
+ });
182
188
 
183
189
  /** Обёртка над клиентом/пулом — одинаковая, чтобы транзакция давала тот же интерфейс. */
184
190
  const wrap = (executor, isTransaction = false) => ({
@@ -59,14 +59,24 @@ const verify = async (auth) => {
59
59
  validateStatus: () => true,
60
60
  });
61
61
  } catch (e) {
62
- return {ok: false, reason: 'verify_unavailable'};
62
+ return {ok: false, reason: 'verify_unavailable', transient: true};
63
63
  }
64
64
  if (response.status !== 200 || !response.data || response.data.token_matches !== true) {
65
- return {ok: false, reason: `verify_failed_${response.status}`};
65
+ // Laravel недоступен / в деплое / перегружен / лицензия — пройдёт само; 401/403 и
66
+ // чужой токен — нет (касса переподключится с новой сессией сама).
67
+ const transient = response.status >= 500 || [402, 404, 408, 429].includes(response.status);
68
+ return {ok: false, reason: `verify_failed_${response.status}`, transient};
66
69
  }
67
70
  return {ok: true};
68
71
  };
69
72
 
73
+ // MASTER_RELAY_VERIFY_RETRY (по умолчанию выкл. = как раньше): разовый сбой проверки больше не
74
+ // оставляет подключённый сокет мастера вне реестра до его следующего обрыва — проверка
75
+ // повторяется с паузой, пока сокет жив. Без этого после рестарта облака или деплоя Laravel
76
+ // все клиенты заведения через облако получали master_offline часами.
77
+ const VERIFY_RETRY_DELAYS_MS = [3000, 10000, 30000, 60000];
78
+ const verifyRetryEnabled = () => (process.env.MASTER_RELAY_VERIFY_RETRY ?? 'false') === 'true';
79
+
70
80
  /**
71
81
  * Сигнальный сокет с handshake.auth.master. company_id в query сокета обязан совпасть с
72
82
  * тем, что проверено токеном, иначе касса слушала бы комнату чужой компании.
@@ -78,9 +88,18 @@ const register = (socket) => {
78
88
  socket.on('disconnect', () => unregister(socket));
79
89
 
80
90
  (async () => {
81
- const result = String(auth.company_id || '') === queryCompanyId
91
+ let result = String(auth.company_id || '') === queryCompanyId
82
92
  ? await verify(auth)
83
93
  : {ok: false, reason: 'company_mismatch'};
94
+ for (let attempt = 0; !result.ok && result.transient && verifyRetryEnabled() && socket.connected; attempt++) {
95
+ const delay = VERIFY_RETRY_DELAYS_MS[Math.min(attempt, VERIFY_RETRY_DELAYS_MS.length - 1)];
96
+ console.warn(`[relay] master verify failed (${result.reason}) terminal=${auth.terminal_id}, retry in ${delay / 1000}s`);
97
+ await new Promise((resolve) => setTimeout(resolve, delay));
98
+ if (!socket.connected) {
99
+ return;
100
+ }
101
+ result = await verify(auth);
102
+ }
84
103
  if (!socket.connected) {
85
104
  return;
86
105
  }
package/app/sync/index.js CHANGED
@@ -580,6 +580,8 @@ const applyCompanyMode = async (data) => {
580
580
  await state.store.block((s) => kv.set(s, 'company_mode', state.company));
581
581
  };
582
582
 
583
+ const MODE_NOT_FOUND_TO_ONLINE = 3;
584
+
583
585
  const pollMode = async () => {
584
586
  state.lastModePoll = Date.now();
585
587
  state.modeRequested = false;
@@ -589,12 +591,18 @@ const pollMode = async () => {
589
591
  } catch (e) {
590
592
  state.lastModeError = e.message;
591
593
  if (e.name === 'CloudRequestError' && e.status === 404) {
592
- // Старый Laravel или Sync API выключен: модели M нет — заведение в онлайне.
593
- await applyCompanyMode({state: 'online', master_terminal_id: null, epoch: 0, enabled: false});
594
+ // Старый Laravel или Sync API выключен: модели M нет — заведение в онлайне. Но 404 отдаёт
595
+ // и Traefik, пока контейнер облака пересоздаётся при деплое: разовый 404 снимал мастера
596
+ // на ~10 с (фронт уходил в облако, relay — not_master). Решаем по нескольким подряд.
597
+ state.modeNotFound = (state.modeNotFound || 0) + 1;
598
+ if (state.modeNotFound >= MODE_NOT_FOUND_TO_ONLINE) {
599
+ await applyCompanyMode({state: 'online', master_terminal_id: null, epoch: 0, enabled: false});
600
+ }
594
601
  return;
595
602
  }
596
603
  throw e;
597
604
  }
605
+ state.modeNotFound = 0;
598
606
  state.lastModeError = null;
599
607
  await applyCompanyMode(response);
600
608
  };
package/package.json CHANGED
@@ -1,45 +1,45 @@
1
1
  {
2
- "name": "@alizadefaig/pos-server",
3
- "version": "1.2.34",
4
- "description": "Server in express js with Proxy to Main server if need and will work with Electron JS",
5
- "homepage": "https://gitlab.com/faigalizade/pos-server#readme",
6
- "bugs": {
7
- "url": "https://gitlab.com/faigalizade/pos-server/issues"
8
- },
9
- "repository": {
10
- "type": "git",
11
- "url": "git+ssh://git@gitlab.com/faigalizade/pos-server.git"
12
- },
13
- "dependencies": {
14
- "@alizadefaig/cafetap-escpos-usb": "^1.0.1",
15
- "axios": "^1.13.2",
16
- "bcryptjs": "^3.0.3",
17
- "cors": "^2.8.5",
18
- "escpos": "^3.0.0-alpha.6",
19
- "escpos-network": "^3.0.0-alpha.5",
20
- "express": "^5.1.0",
21
- "http-proxy-middleware": "^3.0.5",
22
- "lodash": "^4.17.21",
23
- "moment": "^2.30.1",
24
- "multer": "^2.0.2",
25
- "pg": "^8.22.0",
26
- "redis": "^5.10.0",
27
- "sequelize": "^6.37.7",
28
- "sharp": "^0.34.5",
29
- "socket.io": "^4.8.1",
30
- "socket.io-client": "^4.8.1",
31
- "uuid": "^13.0.0"
32
- },
33
- "license": "ISC",
34
- "author": "faigalizade@newave.az",
35
- "type": "commonjs",
36
- "main": "server.js",
37
- "directories": {
38
- "lib": "lib"
39
- },
40
- "scripts": {
41
- "test": "node --test --test-concurrency=1 \"test/**/*.test.js\"",
42
- "test:watch": "node --test --watch --test-concurrency=1 \"test/**/*.test.js\"",
43
- "start": "node server.js"
44
- }
2
+ "name": "@alizadefaig/pos-server",
3
+ "version": "1.2.35",
4
+ "description": "Server in express js with Proxy to Main server if need and will work with Electron JS",
5
+ "homepage": "https://gitlab.com/faigalizade/pos-server#readme",
6
+ "bugs": {
7
+ "url": "https://gitlab.com/faigalizade/pos-server/issues"
8
+ },
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+ssh://git@gitlab.com/faigalizade/pos-server.git"
12
+ },
13
+ "dependencies": {
14
+ "@alizadefaig/cafetap-escpos-usb": "^1.0.1",
15
+ "axios": "^1.13.2",
16
+ "bcryptjs": "^3.0.3",
17
+ "cors": "^2.8.5",
18
+ "escpos": "^3.0.0-alpha.6",
19
+ "escpos-network": "^3.0.0-alpha.5",
20
+ "express": "^5.1.0",
21
+ "http-proxy-middleware": "^3.0.5",
22
+ "lodash": "^4.17.21",
23
+ "moment": "^2.30.1",
24
+ "multer": "^2.0.2",
25
+ "pg": "^8.22.0",
26
+ "redis": "^5.10.0",
27
+ "sequelize": "^6.37.7",
28
+ "sharp": "^0.34.5",
29
+ "socket.io": "^4.8.1",
30
+ "socket.io-client": "^4.8.1",
31
+ "uuid": "^13.0.0"
32
+ },
33
+ "license": "ISC",
34
+ "author": "faigalizade@newave.az",
35
+ "type": "commonjs",
36
+ "main": "server.js",
37
+ "directories": {
38
+ "lib": "lib"
39
+ },
40
+ "scripts": {
41
+ "test": "node --test --test-concurrency=1 \"test/**/*.test.js\"",
42
+ "test:watch": "node --test --watch --test-concurrency=1 \"test/**/*.test.js\"",
43
+ "start": "node server.js"
44
+ }
45
45
  }
package/server.js CHANGED
@@ -182,6 +182,18 @@ const runExpress = (conf = null) => {
182
182
  // ==== Start Listen ====
183
183
  const hostname = appConfig.host || 'localhost';
184
184
  netstats.attach(server, {host: hostname, port});
185
+ // Порт занят (прежний процесс ещё не отпустил его после аварийного выхода, вторая копия) —
186
+ // раньше необработанное исключение: на кассе диалог Electron, локального Node нет до ручного
187
+ // перезапуска. Теперь повторяем, пока порт не освободится.
188
+ const LISTEN_RETRY_MS = 3000;
189
+ server.on('error', (e) => {
190
+ if (e && (e.code === 'EADDRINUSE' || e.code === 'EACCES') && !server.listening) {
191
+ console.error(`[server] port ${port} unavailable (${e.code}), retry in ${LISTEN_RETRY_MS / 1000}s`);
192
+ setTimeout(() => server.listen(port, hostname), LISTEN_RETRY_MS);
193
+ return;
194
+ }
195
+ console.error('[server] error:', e && e.message);
196
+ });
185
197
  server.listen(port, hostname, async () => {
186
198
  if (hostname === 'localhost') {
187
199
  console.log(`Server started on ${hostname} http://${hostname}:${port}`);