@omnizap-system/omnizap 2.6.2 → 2.6.3

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.
Files changed (48) hide show
  1. package/.env.example +24 -0
  2. package/app/config/index.js +4 -0
  3. package/app/configParts/adminIdentity.js +29 -0
  4. package/app/configParts/baileysConfig.js +116 -0
  5. package/app/configParts/groupUtils.js +221 -0
  6. package/app/configParts/loggerConfig.js +185 -0
  7. package/app/configParts/messagePersistenceService.js +169 -7
  8. package/app/configParts/sessionConfig.js +85 -0
  9. package/app/connection/baileysCompatibility.test.js +9 -0
  10. package/app/connection/baileysDbAuthState.js +205 -9
  11. package/app/connection/baileysLibsignalPatch.js +210 -0
  12. package/app/connection/groupOwnerWriteStateResolver.js +53 -21
  13. package/app/connection/socketController.js +95 -25
  14. package/app/connection/socketController.multiSession.test.js +20 -0
  15. package/app/controllers/messagePipeline/preProcessingMiddlewares.js +17 -3
  16. package/app/controllers/messageProcessingPipeline.js +2 -0
  17. package/app/controllers/messageProcessingPipeline.test.js +15 -13
  18. package/app/services/multiSession/assignmentBalancerService.js +1 -6
  19. package/app/services/multiSession/groupOwnershipRepository.js +9 -44
  20. package/app/services/multiSession/groupOwnershipService.js +9 -90
  21. package/app/services/multiSession/groupOwnershipService.test.js +12 -4
  22. package/app/services/multiSession/sessionRegistryService.js +6 -60
  23. package/app/utils/antiLink/antiLinkModule.js +54 -24
  24. package/docs/security/omnizap-static-security-headers.conf +3 -3
  25. package/package.json +3 -2
  26. package/public/comandos/commands-catalog.json +1 -1
  27. package/public/css/payments-react.css +478 -0
  28. package/public/js/apps/homeReactApp.js +2 -2
  29. package/public/js/apps/paymentsCancelReactApp.js +45 -0
  30. package/public/js/apps/paymentsReactApp.js +399 -0
  31. package/public/js/apps/paymentsSuccessReactApp.js +148 -0
  32. package/public/pages/pagamentos-cancelado.html +21 -0
  33. package/public/pages/pagamentos-sucesso.html +21 -0
  34. package/public/pages/pagamentos.html +30 -0
  35. package/scripts/deploy.sh +3 -0
  36. package/scripts/new-whatsapp-session.sh +247 -0
  37. package/server/controllers/admin/systemAdminController.js +4 -17
  38. package/server/controllers/payments/paymentsController.js +731 -0
  39. package/server/controllers/system/systemController.js +4 -30
  40. package/server/email/emailAutomationRuntime.js +36 -1
  41. package/server/email/emailAutomationService.js +42 -1
  42. package/server/email/emailTemplateService.js +137 -31
  43. package/server/http/httpRequestUtils.js +18 -14
  44. package/server/middleware/securityHeaders.js +15 -2
  45. package/server/routes/indexRouter.js +27 -7
  46. package/server/routes/payments/paymentsRouter.js +47 -0
  47. package/server/routes/static/staticPageRouter.js +3 -0
  48. package/vite.config.mjs +3 -0
@@ -15,6 +15,14 @@ CONNECT_NOW=1
15
15
  RESET_AUTH=0
16
16
  CLEAR_AUTH_FILES=0
17
17
  ALLOW_REUSE=0
18
+ PM2_SYNC=1
19
+ PM2_TARGET_APP=""
20
+ PM2_DRY_RUN=0
21
+ RUN_HEALTHCHECK=1
22
+ PM2_WAIT_TIMEOUT_SECONDS=45
23
+ PM2_WAIT_INTERVAL_SECONDS=3
24
+ HEALTHCHECK_WAIT_SECONDS=45
25
+ HEALTHCHECK_INTERVAL_SECONDS=3
18
26
 
19
27
  log() {
20
28
  printf '[new-whatsapp-session] %s\n' "$*"
@@ -45,12 +53,17 @@ Opcoes:
45
53
  --reset-auth Limpa credenciais atuais da sessao no MySQL antes do QR.
46
54
  --clear-auth-files Junto com --reset-auth, remove app/connection/auth/*.json.
47
55
  --no-connect Apenas atualiza .env (nao abre conexao para QR agora).
56
+ --no-pm2-sync Nao sincroniza/reinicia app PM2 automaticamente.
57
+ --pm2-app <nome> Nome do app PM2 alvo (padrao: <PM2_APP_NAME>-production).
58
+ --pm2-dry-run Exibe a sincronizacao PM2 sem executar restart real.
59
+ --no-healthcheck Nao executa healthcheck HTTP apos sincronizar PM2.
48
60
  --help Mostra esta ajuda.
49
61
 
50
62
  Exemplos:
51
63
  npm run new:work
52
64
  npm run new:work -- --session suporte_2 --primary
53
65
  bash scripts/new-whatsapp-session.sh --prefix operador --no-connect
66
+ bash scripts/new-whatsapp-session.sh --no-connect --pm2-app omnizap-system-production
54
67
  EOF
55
68
  }
56
69
 
@@ -124,6 +137,222 @@ is_valid_weight() {
124
137
  return 0
125
138
  }
126
139
 
140
+ normalize_bool() {
141
+ local value
142
+ value="$(printf '%s' "${1:-}" | tr '[:upper:]' '[:lower:]' | xargs)"
143
+ if [[ -z "$value" ]]; then
144
+ printf 'unset'
145
+ return 0
146
+ fi
147
+ case "$value" in
148
+ 1|true|yes|y|on)
149
+ printf 'true'
150
+ ;;
151
+ 0|false|no|n|off)
152
+ printf 'false'
153
+ ;;
154
+ *)
155
+ printf 'unknown'
156
+ ;;
157
+ esac
158
+ }
159
+
160
+ pm2_get_status() {
161
+ local app_name="$1"
162
+ pm2 jlist 2>/dev/null | node -e "
163
+ const appName = String(process.argv[1] || '').trim();
164
+ let raw = '';
165
+ process.stdin.setEncoding('utf8');
166
+ process.stdin.on('data', (chunk) => {
167
+ raw += chunk;
168
+ });
169
+ process.stdin.on('end', () => {
170
+ try {
171
+ const list = JSON.parse(raw || '[]');
172
+ const target = Array.isArray(list) ? list.find((entry) => String(entry?.name || '') === appName) : null;
173
+ if (!target) {
174
+ process.stdout.write('missing');
175
+ return;
176
+ }
177
+ const status = String(target?.pm2_env?.status || '').trim().toLowerCase();
178
+ process.stdout.write(status || 'unknown');
179
+ } catch {
180
+ process.stdout.write('error');
181
+ }
182
+ });
183
+ " "$app_name"
184
+ }
185
+
186
+ pm2_detect_app_name() {
187
+ local preferred="$1"
188
+ pm2 jlist 2>/dev/null | node -e "
189
+ const preferred = String(process.argv[1] || '').trim();
190
+ let raw = '';
191
+ process.stdin.setEncoding('utf8');
192
+ process.stdin.on('data', (chunk) => {
193
+ raw += chunk;
194
+ });
195
+ process.stdin.on('end', () => {
196
+ try {
197
+ const list = JSON.parse(raw || '[]');
198
+ const names = (Array.isArray(list) ? list : [])
199
+ .map((entry) => String(entry?.name || '').trim())
200
+ .filter(Boolean);
201
+
202
+ if (preferred && names.includes(preferred)) {
203
+ process.stdout.write(preferred);
204
+ return;
205
+ }
206
+
207
+ if (names.includes('omnizap-production')) {
208
+ process.stdout.write('omnizap-production');
209
+ return;
210
+ }
211
+
212
+ const omnizapProduction = names.find((name) => /omnizap.*-production$/i.test(name));
213
+ if (omnizapProduction) {
214
+ process.stdout.write(omnizapProduction);
215
+ return;
216
+ }
217
+
218
+ const anyProduction = names.find((name) => /-production$/i.test(name));
219
+ if (anyProduction) {
220
+ process.stdout.write(anyProduction);
221
+ return;
222
+ }
223
+
224
+ process.stdout.write('');
225
+ } catch {
226
+ process.stdout.write('');
227
+ }
228
+ });
229
+ " "$preferred"
230
+ }
231
+
232
+ resolve_pm2_target_app() {
233
+ if [[ -n "$PM2_TARGET_APP" ]]; then
234
+ printf '%s' "$PM2_TARGET_APP"
235
+ return 0
236
+ fi
237
+
238
+ local pm2_base_name
239
+ pm2_base_name="${PM2_APP_NAME:-}"
240
+ if [[ -z "$pm2_base_name" ]]; then
241
+ pm2_base_name="$(read_env_value PM2_APP_NAME)"
242
+ fi
243
+ if [[ -n "$pm2_base_name" ]]; then
244
+ printf '%s' "${pm2_base_name}-production"
245
+ return 0
246
+ fi
247
+
248
+ printf '%s' "omnizap-production"
249
+ }
250
+
251
+ wait_pm2_online() {
252
+ local app_name="$1"
253
+ local waited=0
254
+
255
+ while (( waited <= PM2_WAIT_TIMEOUT_SECONDS )); do
256
+ local status
257
+ status="$(pm2_get_status "$app_name")"
258
+ if [[ "$status" == "online" ]]; then
259
+ log "PM2 app '$app_name' online."
260
+ return 0
261
+ fi
262
+ if [[ "$status" == "missing" ]]; then
263
+ fail "PM2 app '$app_name' nao encontrada durante sincronizacao."
264
+ fi
265
+ if [[ "$status" == "error" ]]; then
266
+ fail "Nao foi possivel consultar status do PM2 app '$app_name'."
267
+ fi
268
+
269
+ sleep "$PM2_WAIT_INTERVAL_SECONDS"
270
+ waited=$((waited + PM2_WAIT_INTERVAL_SECONDS))
271
+ done
272
+
273
+ fail "Timeout aguardando PM2 app '$app_name' ficar online."
274
+ }
275
+
276
+ run_http_healthcheck() {
277
+ local metrics_enabled_raw
278
+ metrics_enabled_raw="$(read_env_value METRICS_ENABLED)"
279
+ local metrics_enabled_state
280
+ metrics_enabled_state="$(normalize_bool "$metrics_enabled_raw")"
281
+ if [[ "$metrics_enabled_state" == "false" ]]; then
282
+ log "Healthcheck ignorado: METRICS_ENABLED=false."
283
+ return 0
284
+ fi
285
+
286
+ if ! command -v curl >/dev/null 2>&1; then
287
+ log "Healthcheck ignorado: comando 'curl' nao encontrado."
288
+ return 0
289
+ fi
290
+
291
+ local host port url
292
+ host="$(read_env_value METRICS_HOST)"
293
+ port="$(read_env_value METRICS_PORT)"
294
+ [[ -n "$host" ]] || host="127.0.0.1"
295
+ if [[ "$host" == "0.0.0.0" || "$host" == "::" ]]; then
296
+ host="127.0.0.1"
297
+ fi
298
+ if [[ ! "$port" =~ ^[0-9]+$ ]]; then
299
+ port="9102"
300
+ fi
301
+ url="http://${host}:${port}/healthz"
302
+
303
+ local waited=0
304
+ local last_code="000"
305
+ while (( waited <= HEALTHCHECK_WAIT_SECONDS )); do
306
+ local code
307
+ code="$(curl --silent --show-error --max-time 5 --output /dev/null --write-out '%{http_code}' "$url" || true)"
308
+ if [[ "$code" == "200" ]]; then
309
+ log "Healthcheck HTTP ok em $url."
310
+ return 0
311
+ fi
312
+ last_code="$code"
313
+ sleep "$HEALTHCHECK_INTERVAL_SECONDS"
314
+ waited=$((waited + HEALTHCHECK_INTERVAL_SECONDS))
315
+ done
316
+
317
+ fail "Healthcheck falhou em $url (ultimo HTTP: $last_code)."
318
+ }
319
+
320
+ sync_pm2_if_needed() {
321
+ if [[ "$PM2_SYNC" != "1" ]]; then
322
+ log "Sincronizacao PM2 desativada (--no-pm2-sync)."
323
+ return 0
324
+ fi
325
+
326
+ if ! command -v pm2 >/dev/null 2>&1; then
327
+ log "PM2 nao encontrado; sincronizacao automatica ignorada."
328
+ return 0
329
+ fi
330
+
331
+ local preferred_target
332
+ preferred_target="$(resolve_pm2_target_app)"
333
+ local app_target
334
+ app_target="$(pm2_detect_app_name "$preferred_target")"
335
+ if [[ -z "$app_target" ]]; then
336
+ log "Nenhum app PM2 de producao detectado para sincronizar."
337
+ return 0
338
+ fi
339
+
340
+ if [[ "$PM2_DRY_RUN" == "1" ]]; then
341
+ log "PM2 dry-run: executaria 'pm2 restart $app_target --update-env'."
342
+ return 0
343
+ fi
344
+
345
+ log "Sincronizando PM2 app '$app_target'..."
346
+ pm2 restart "$app_target" --update-env >/dev/null
347
+ wait_pm2_online "$app_target"
348
+
349
+ if [[ "$RUN_HEALTHCHECK" == "1" ]]; then
350
+ run_http_healthcheck
351
+ else
352
+ log "Healthcheck desativado (--no-healthcheck)."
353
+ fi
354
+ }
355
+
127
356
  while [[ $# -gt 0 ]]; do
128
357
  case "$1" in
129
358
  --session)
@@ -161,6 +390,23 @@ while [[ $# -gt 0 ]]; do
161
390
  CONNECT_NOW=0
162
391
  shift
163
392
  ;;
393
+ --no-pm2-sync)
394
+ PM2_SYNC=0
395
+ shift
396
+ ;;
397
+ --pm2-app)
398
+ [[ $# -ge 2 ]] || fail "faltou valor para --pm2-app"
399
+ PM2_TARGET_APP="$2"
400
+ shift 2
401
+ ;;
402
+ --pm2-dry-run)
403
+ PM2_DRY_RUN=1
404
+ shift
405
+ ;;
406
+ --no-healthcheck)
407
+ RUN_HEALTHCHECK=0
408
+ shift
409
+ ;;
164
410
  -h|--help)
165
411
  usage
166
412
  exit 0
@@ -298,6 +544,7 @@ log "Sessao preparada com sucesso."
298
544
  log "session_id: $SESSION_ID"
299
545
  log "BAILEYS_SESSION_IDS: $session_ids_value"
300
546
  log "BAILEYS_PRIMARY_SESSION_ID: $current_primary_session_id"
547
+ sync_pm2_if_needed
301
548
 
302
549
  if [[ "$CONNECT_NOW" == "1" ]]; then
303
550
  require_cmd node
@@ -4,14 +4,7 @@ import { timingSafeEqual } from 'node:crypto';
4
4
  import { URL } from 'node:url';
5
5
 
6
6
  import logger from '#logger';
7
- import {
8
- forceSystemAdminGroupFailover,
9
- listSystemAdminAssignmentHistory,
10
- listSystemAdminAssignments,
11
- listSystemAdminSessions,
12
- setSystemAdminGroupPin,
13
- triggerSystemAdminManualRebalance,
14
- } from '../system/systemController.js';
7
+ import { forceSystemAdminGroupFailover, listSystemAdminAssignmentHistory, listSystemAdminAssignments, listSystemAdminSessions, setSystemAdminGroupPin, triggerSystemAdminManualRebalance } from '../system/systemController.js';
15
8
 
16
9
  const parseEnvBool = (value, fallback) => {
17
10
  if (value === undefined || value === null || value === '') return fallback;
@@ -48,9 +41,7 @@ const SITE_ORIGIN = String(process.env.SITE_ORIGIN || 'https://omnizap.shop')
48
41
 
49
42
  const USER_SYSTEMADM_TEMPLATE_PATH = path.join(process.cwd(), 'public', 'pages', 'user-systemadm.html');
50
43
  const LEGACY_STICKER_ADMIN_TEMPLATE_PATH = path.join(process.cwd(), 'public', 'pages', 'stickers-admin.html');
51
- const SYSTEM_ADMIN_OPS_TOKEN = String(
52
- process.env.SYSTEM_ADMIN_OPS_TOKEN || process.env.USER_INTERNAL_API_TOKEN || process.env.ADMIN_TOKEN || process.env.ADMIN_API_TOKEN || '',
53
- ).trim();
44
+ const SYSTEM_ADMIN_OPS_TOKEN = String(process.env.SYSTEM_ADMIN_OPS_TOKEN || process.env.USER_INTERNAL_API_TOKEN || process.env.ADMIN_TOKEN || process.env.ADMIN_API_TOKEN || '').trim();
54
45
 
55
46
  let stickerCatalogControllerPromise = null;
56
47
  const loadStickerCatalogController = async () => {
@@ -151,9 +142,7 @@ const extractBearerToken = (req) => {
151
142
  return authHeader.slice(7).trim();
152
143
  };
153
144
 
154
- const resolveOpsTokenFromRequest = (req) =>
155
- String(req?.headers?.['x-system-admin-token'] || req?.headers?.['x-internal-api-token'] || req?.headers?.['x-admin-token'] || '').trim() ||
156
- extractBearerToken(req);
145
+ const resolveOpsTokenFromRequest = (req) => String(req?.headers?.['x-system-admin-token'] || req?.headers?.['x-internal-api-token'] || req?.headers?.['x-admin-token'] || '').trim() || extractBearerToken(req);
157
146
 
158
147
  const hasValidOpsToken = (req) => {
159
148
  if (!SYSTEM_ADMIN_OPS_TOKEN) return true;
@@ -247,9 +236,7 @@ const requireSystemAdminOpsAccess = (req, res) => {
247
236
  const normalizeMultiSessionSubPath = (value) => {
248
237
  const raw = String(value || '/').trim();
249
238
  if (!raw || raw === '/') return '/';
250
- return `/${raw
251
- .replace(/^\/+/g, '')
252
- .replace(/\/+$/g, '')}`;
239
+ return `/${raw.replace(/^\/+/g, '').replace(/\/+$/g, '')}`;
253
240
  };
254
241
 
255
242
  const handleMultiSessionOpsRequest = async (req, res, { pathname, url }) => {