@omnizap-system/omnizap 2.6.0 → 2.6.2
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/.env.example +58 -13
- package/.github/workflows/ci.yml +5 -5
- package/.github/workflows/codeql.yml +1 -1
- package/.github/workflows/db-migration-check.yml +2 -2
- package/.github/workflows/dependency-review.yml +1 -1
- package/.github/workflows/deploy.yml +2 -2
- package/.github/workflows/release.yml +2 -2
- package/.github/workflows/security-attest-provenance.yml +2 -2
- package/.github/workflows/security-gitleaks.yml +13 -4
- package/.github/workflows/security-runner-hardening.yml +2 -2
- package/.github/workflows/security-scorecard.yml +1 -1
- package/.github/workflows/security-zap-baseline.yml +1 -1
- package/.github/workflows/security-zap-full-scan.yml +2 -1
- package/.github/workflows/security-zizmor.yml +1 -1
- package/.github/workflows/wiki-sync.yml +1 -1
- package/.gitleaksignore +9 -0
- package/CODE_OF_CONDUCT.md +2 -2
- package/GEMINI.md +64 -0
- package/README.md +52 -82
- package/SECURITY.md +1 -1
- package/app/config/index.js +2 -0
- package/app/configParts/adminIdentity.js +5 -5
- package/app/configParts/baileysConfig.js +230 -58
- package/app/configParts/groupUtils.js +5 -0
- package/app/configParts/messagePersistenceService.js +145 -4
- package/app/configParts/sessionConfig.js +157 -0
- package/app/connection/baileysCompatibility.test.js +1 -1
- package/app/connection/groupOwnerWriteStateResolver.js +109 -0
- package/app/connection/socketController.js +660 -158
- package/app/connection/socketController.multiSession.test.js +108 -0
- package/app/controllers/messageController.js +1 -1
- package/app/controllers/messagePipeline/commandMiddleware.js +12 -10
- package/app/controllers/messagePipeline/conversationMiddleware.js +2 -1
- package/app/controllers/messagePipeline/messagePipelineMiddlewares.test.js +104 -0
- package/app/controllers/messagePipeline/preProcessingMiddlewares.js +80 -2
- package/app/controllers/messageProcessingPipeline.js +93 -13
- package/app/controllers/messageProcessingPipeline.test.js +200 -0
- package/app/modules/adminModule/AGENT.md +1 -1
- package/app/modules/adminModule/commandConfig.json +3318 -1347
- package/app/modules/adminModule/groupCommandHandlers.js +858 -15
- package/app/modules/adminModule/groupCommandHandlers.test.js +378 -11
- package/app/modules/adminModule/groupWarningRepository.js +152 -0
- package/app/modules/aiModule/AGENT.md +47 -30
- package/app/modules/aiModule/aiConfigRuntime.js +1 -0
- package/app/modules/aiModule/catCommand.js +135 -27
- package/app/modules/aiModule/commandConfig.json +114 -28
- package/app/modules/analyticsModule/messageAnalysisEventRepository.js +54 -6
- package/app/modules/gameModule/AGENT.md +1 -1
- package/app/modules/gameModule/commandConfig.json +29 -0
- package/app/modules/menuModule/AGENT.md +1 -1
- package/app/modules/menuModule/commandConfig.json +45 -10
- package/app/modules/menuModule/menuCatalogService.js +190 -0
- package/app/modules/menuModule/menuCommandUsageRepository.js +109 -0
- package/app/modules/menuModule/menuDynamicService.js +511 -0
- package/app/modules/menuModule/menuDynamicService.test.js +141 -0
- package/app/modules/menuModule/menus.js +36 -5
- package/app/modules/playModule/AGENT.md +10 -5
- package/app/modules/playModule/commandConfig.json +140 -12
- package/app/modules/playModule/playCommand.js +1 -1417
- package/app/modules/playModule/playCommandConstants.js +80 -0
- package/app/modules/playModule/playCommandCore.js +361 -0
- package/app/modules/playModule/playCommandHandlers.js +41 -0
- package/app/modules/playModule/playCommandMediaClient.js +1872 -0
- package/app/modules/playModule/playConfigRuntime.js +245 -4
- package/app/modules/playModule/playModuleCriticalFlows.test.js +152 -0
- package/app/modules/quoteModule/AGENT.md +1 -1
- package/app/modules/quoteModule/commandConfig.json +29 -0
- package/app/modules/quoteModule/quoteCommand.js +3 -2
- package/app/modules/rpgPokemonModule/AGENT.md +1 -1
- package/app/modules/rpgPokemonModule/commandConfig.json +29 -0
- package/app/modules/rpgPokemonModule/rpgBattleCanvasRenderer.js +5 -4
- package/app/modules/rpgPokemonModule/rpgBattleService.test.js +2 -1
- package/app/modules/rpgPokemonModule/rpgPokemonDomain.js +2 -1
- package/app/modules/rpgPokemonModule/rpgPokemonService.js +38 -37
- package/app/modules/rpgPokemonModule/rpgProfileCanvasRenderer.js +4 -3
- package/app/modules/statsModule/AGENT.md +1 -1
- package/app/modules/statsModule/commandConfig.json +58 -0
- package/app/modules/statsModule/rankingCommon.js +5 -4
- package/app/modules/stickerModule/AGENT.md +1 -1
- package/app/modules/stickerModule/addStickerMetadata.js +4 -3
- package/app/modules/stickerModule/commandConfig.json +145 -0
- package/app/modules/stickerModule/stickerCommand.js +1 -1
- package/app/modules/stickerPackModule/AGENT.md +1 -1
- package/app/modules/stickerPackModule/autoPackCollectorService.js +5 -1
- package/app/modules/stickerPackModule/commandConfig.json +29 -0
- package/app/modules/stickerPackModule/semanticThemeClusterService.js +7 -6
- package/app/modules/stickerPackModule/stickerAutoPackByTagsRuntime.js +10 -9
- package/app/modules/stickerPackModule/stickerClassificationBackgroundRuntime.js +9 -8
- package/app/modules/stickerPackModule/stickerDomainEventConsumerRuntime.js +3 -2
- package/app/modules/stickerPackModule/stickerMarketplaceDriftService.js +2 -1
- package/app/modules/stickerPackModule/stickerPackCommandHandlers.js +80 -58
- package/app/modules/stickerPackModule/stickerPackMarketplaceService.js +2 -1
- package/app/modules/stickerPackModule/stickerPackRepository.js +2 -1
- package/app/modules/stickerPackModule/stickerPackScoreSnapshotRuntime.js +5 -4
- package/app/modules/stickerPackModule/stickerPackService.js +13 -6
- package/app/modules/stickerPackModule/stickerStorageService.js +3 -2
- package/app/modules/stickerPackModule/stickerWorkerPipelineRuntime.js +2 -1
- package/app/modules/systemMetricsModule/AGENT.md +1 -1
- package/app/modules/systemMetricsModule/commandConfig.json +29 -0
- package/app/modules/systemMetricsModule/pingCommand.js +6 -5
- package/app/modules/tiktokModule/AGENT.md +1 -1
- package/app/modules/tiktokModule/commandConfig.json +29 -0
- package/app/modules/tiktokModule/tiktokCommand.js +2 -1
- package/app/modules/userModule/AGENT.md +1 -1
- package/app/modules/userModule/commandConfig.json +29 -0
- package/app/modules/userModule/userCommand.js +72 -23
- package/app/modules/waifuPicsModule/AGENT.md +57 -27
- package/app/modules/waifuPicsModule/commandConfig.json +87 -0
- package/app/modules/waifuPicsModule/waifuPicsCommand.js +3 -2
- package/app/observability/metrics.js +136 -0
- package/app/services/ai/commandConfigEnrichmentService.js +229 -47
- package/app/services/ai/conversationRouterService.js +4 -3
- package/app/services/ai/geminiService.js +132 -7
- package/app/services/ai/geminiService.test.js +59 -2
- package/app/services/ai/globalModuleAiHelpService.js +3 -2
- package/app/services/ai/messageCommandExecutionService.js +2 -1
- package/app/services/ai/moduleAiHelpCoreService.js +45 -14
- package/app/services/ai/moduleToolExecutorService.js +3 -2
- package/app/services/ai/moduleToolRegistryService.js +2 -1
- package/app/services/ai/toolCandidateSelectorService.js +6 -5
- package/app/services/auth/googleWebLinkService.js +3 -2
- package/app/services/auth/whatsappLoginLinkService.js +3 -2
- package/app/services/external/pokeApiService.js +4 -3
- package/app/services/group/groupMetadataService.js +24 -1
- package/app/services/infra/dbWriteQueue.js +57 -26
- package/app/services/infra/featureFlagService.js +2 -1
- package/app/services/messaging/captchaService.js +3 -2
- package/app/services/messaging/newsBroadcastService.js +846 -29
- package/app/services/multiSession/assignmentBalancerService.js +457 -0
- package/app/services/multiSession/groupOwnershipRepository.js +381 -0
- package/app/services/multiSession/groupOwnershipService.js +890 -0
- package/app/services/multiSession/groupOwnershipService.test.js +309 -0
- package/app/services/multiSession/sessionRegistryService.js +293 -0
- package/app/services/sticker/stickerFocusService.js +11 -10
- package/app/store/aiPromptStore.js +36 -19
- package/app/store/conversationSessionStore.js +7 -6
- package/app/store/groupConfigStore.js +41 -5
- package/app/store/premiumUserStore.js +21 -7
- package/app/utils/antiLink/antiLinkModule.js +352 -16
- package/app/workers/aiHelperContinuousLearningWorker.js +512 -0
- package/app/workers/aiLearningWorker.js +6 -5
- package/app/workers/commandConfigEnrichmentWorker.js +4 -3
- package/database/index.js +14 -8
- package/database/migrations/20260307_d0_hardening_down.sql +1 -1
- package/database/migrations/20260314_d7_canonical_sender_down.sql +1 -1
- package/database/migrations/20260406_d30_security_analytics_down.sql +1 -1
- package/database/migrations/20260411_d35_group_community_metadata_down.sql +59 -0
- package/database/migrations/20260411_d35_group_community_metadata_up.sql +62 -0
- package/database/migrations/20260412_d36_system_config_tables_down.sql +32 -0
- package/database/migrations/20260412_d36_system_config_tables_up.sql +66 -0
- package/database/migrations/20260413_d37_group_user_warnings_down.sql +11 -0
- package/database/migrations/20260413_d37_group_user_warnings_up.sql +24 -0
- package/database/migrations/20260414_d38_multi_session_foundation_down.sql +72 -0
- package/database/migrations/20260414_d38_multi_session_foundation_up.sql +125 -0
- package/database/migrations/20260414_d39_multi_session_cutover_down.sql +103 -0
- package/database/migrations/20260414_d39_multi_session_cutover_up.sql +83 -0
- package/database/schema.sql +102 -1
- package/docker-compose.yml +4 -1
- package/docs/compliance/acceptable-use-policy-2026-03-07.md +1 -1
- package/docs/compliance/dpa-b2b-standard-2026-03-07.md +1 -1
- package/docs/compliance/privacy-policy-2026-03-07.md +4 -4
- package/docs/security/dsar-lgpd-runbook-2026-03-07.md +1 -1
- package/docs/security/incident-response-lgpd-anpd-runbook-2026-03-07.md +1 -1
- package/docs/security/network-hardening-runbook-2026-03-07.md +53 -0
- package/docs/security/omnizap-static-security-headers.conf +25 -0
- package/docs/wiki/Home.md +1 -1
- package/ecosystem.prod.config.cjs +32 -12
- package/index.js +57 -23
- package/observability/alert-rules.yml +20 -0
- package/observability/grafana/dashboards/omnizap-system-admin.json +229 -0
- package/observability/mysql-setup.sql +4 -4
- package/observability/system-admin-observability.md +26 -0
- package/package.json +20 -6
- package/public/apple-touch-icon.png +0 -0
- package/public/comandos/commands-catalog.json +2853 -3326
- package/public/favicon-16x16.png +0 -0
- package/public/favicon-32x32.png +0 -0
- package/public/favicon.ico +0 -0
- package/public/js/apps/apiDocsApp.js +3 -2
- package/public/js/apps/commandsReactApp.js +280 -99
- package/public/js/apps/createPackApp.js +11 -10
- package/public/js/apps/homeReactApp.js +181 -130
- package/public/js/apps/loginReactApp.js +1 -1
- package/public/js/apps/stickersApp.js +263 -110
- package/public/js/apps/termsReactApp.js +73 -24
- package/public/js/apps/userApp.js +4 -3
- package/public/js/apps/userPasswordResetReactApp.js +406 -0
- package/public/js/apps/userReactApp.js +355 -280
- package/public/js/apps/userSystemAdmReactApp.js +1506 -0
- package/public/pages/api-docs.html +1 -1
- package/public/pages/aup.html +2 -2
- package/public/pages/dpa.html +3 -3
- package/public/pages/licenca.html +4 -4
- package/public/pages/login.html +1 -1
- package/public/pages/notice-and-takedown.html +2 -2
- package/public/pages/politica-de-privacidade.html +6 -6
- package/public/pages/seo-bot-whatsapp-para-grupo.html +3 -3
- package/public/pages/seo-bot-whatsapp-sem-programar.html +3 -3
- package/public/pages/seo-como-automatizar-avisos-no-whatsapp.html +3 -3
- package/public/pages/seo-como-criar-comandos-whatsapp.html +3 -3
- package/public/pages/seo-como-evitar-spam-no-whatsapp.html +3 -3
- package/public/pages/seo-como-moderar-grupo-whatsapp.html +3 -3
- package/public/pages/seo-como-organizar-comunidade-whatsapp.html +3 -3
- package/public/pages/seo-melhor-bot-whatsapp-para-grupos.html +3 -3
- package/public/pages/stickers-admin.html +1 -1
- package/public/pages/stickers-create.html +1 -1
- package/public/pages/stickers.html +6 -6
- package/public/pages/suboperadores.html +2 -2
- package/public/pages/termos-de-uso-texto-integral.html +6 -6
- package/public/pages/termos-de-uso.html +3 -3
- package/public/pages/user-password-reset.html +4 -5
- package/public/pages/user-systemadm.html +9 -463
- package/public/pages/user.html +2 -2
- package/scripts/clear-whatsapp-session.sh +123 -0
- package/scripts/core-ai-mode.mjs +163 -0
- package/scripts/deploy.sh +11 -1
- package/scripts/email-broadcast-terms-update.mjs +2 -1
- package/scripts/enrich-command-config-ux-openai.mjs +492 -0
- package/scripts/generate-commands-catalog.mjs +166 -2
- package/scripts/generate-module-agents.mjs +2 -1
- package/scripts/generate-seo-satellite-pages.mjs +5 -4
- package/scripts/github-deploy-notify.mjs +2 -1
- package/scripts/github-release-notify.mjs +25 -10
- package/scripts/new-whatsapp-session.sh +317 -0
- package/scripts/release.sh +2 -19
- package/scripts/security-smoketest.mjs +6 -5
- package/scripts/security-web-surface-check.mjs +218 -0
- package/scripts/sticker-catalog-loadtest.mjs +5 -4
- package/server/auth/googleWebAuth/googleWebAuthService.js +8 -7
- package/server/auth/jwt/webJwtService.js +1 -1
- package/server/auth/stickerCatalogAuthContext.js +2 -1
- package/server/auth/termsAcceptance/termsAcceptanceHandler.js +2 -1
- package/server/auth/userPassword/userPasswordAuthService.js +2 -1
- package/server/auth/userPassword/userPasswordRecoveryService.js +4 -3
- package/server/auth/webAccount/webAccountHandlers.js +9 -10
- package/server/controllers/admin/adminPanelHandlers.js +267 -16
- package/server/controllers/admin/systemAdminController.js +267 -0
- package/server/controllers/seo/stickerCatalogSeoContext.js +10 -9
- package/server/controllers/sticker/nonCatalogHandlers.js +2 -1
- package/server/controllers/sticker/stickerCatalogController.js +23 -36
- package/server/controllers/system/contactController.js +9 -17
- package/server/controllers/system/githubController.js +3 -2
- package/server/controllers/system/stickerCatalogSystemContext.js +41 -19
- package/server/controllers/system/systemController.js +254 -1
- package/server/controllers/system/systemMetricsController.js +2 -1
- package/server/controllers/userController.js +6 -0
- package/server/email/emailTemplateService.js +5 -3
- package/server/http/httpServer.js +11 -6
- package/server/middleware/rateLimit.js +2 -1
- package/server/middleware/securityHeaders.js +20 -1
- package/server/routes/admin/systemAdminRouter.js +6 -0
- package/server/routes/indexRouter.js +30 -6
- package/server/routes/observability/grafanaProxyRouter.js +254 -0
- package/server/routes/static/staticPageRouter.js +27 -1
- package/server/utils/publicContact.js +31 -0
- package/utils/time/timeModule.js +135 -0
- package/utils/time/timeModule.test.js +65 -0
- package/utils/whatsapp/contactEnv.js +39 -0
- package/vite.config.mjs +7 -1
- package/public/assets/images/brand-icon-192.png +0 -0
- package/scripts/sync-readme-snapshot.mjs +0 -133
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { now as __timeNow, nowIso as __timeNowIso, toUnixMs as __timeNowMs } from '#time';
|
|
1
2
|
import { withTimeout } from '../../http/httpRequestUtils.js';
|
|
3
|
+
import { resolveBotPhoneFromEnv } from '../../../utils/whatsapp/contactEnv.js';
|
|
2
4
|
|
|
3
5
|
export const createStickerCatalogSystemContext = ({ executeQuery, tables, logger, getSystemMetrics, getActiveSocket, resolveSocketReadyState, resolveActiveSocketBotJid, resolveCatalogBotPhone, fetchPrometheusSummary, metricsEndpoint, systemSummaryCache, systemSummaryCacheSeconds, readmeSummaryCache, readmeSummaryCacheSeconds, readmeMessageTypeSampleLimit, readmeCommandPrefix, buildMenuCaption, buildStickerMenu, buildMediaMenu, buildQuoteMenu, buildAnimeMenu, buildAiMenu, buildStatsMenu, buildAdminMenu, profilePictureUrlFromActiveSocket, normalizeJid, getJidUser, globalRankCache, globalRankRefreshSeconds, marketplaceGlobalStatsCache, marketplaceGlobalStatsCacheSeconds }) => {
|
|
4
6
|
let globalRankRefreshTimer = null;
|
|
@@ -125,7 +127,7 @@ export const createStickerCatalogSystemContext = ({ executeQuery, tables, logger
|
|
|
125
127
|
http_latency_p95_ms: prometheus?.http_latency_p95_ms ?? null,
|
|
126
128
|
queue_peak: prometheus?.queue_peak ?? null,
|
|
127
129
|
},
|
|
128
|
-
updated_at:
|
|
130
|
+
updated_at: __timeNowIso(),
|
|
129
131
|
},
|
|
130
132
|
meta: {
|
|
131
133
|
metrics_endpoint: metricsEndpoint,
|
|
@@ -138,7 +140,7 @@ export const createStickerCatalogSystemContext = ({ executeQuery, tables, logger
|
|
|
138
140
|
};
|
|
139
141
|
|
|
140
142
|
const getSystemSummaryCached = async () => {
|
|
141
|
-
const now =
|
|
143
|
+
const now = __timeNowMs();
|
|
142
144
|
const hasValue = Boolean(systemSummaryCache.value);
|
|
143
145
|
|
|
144
146
|
if (hasValue && now < systemSummaryCache.expiresAt) {
|
|
@@ -149,9 +151,15 @@ export const createStickerCatalogSystemContext = ({ executeQuery, tables, logger
|
|
|
149
151
|
systemSummaryCache.pending = withTimeout(buildSystemSummarySnapshot(), 5000)
|
|
150
152
|
.then((payload) => {
|
|
151
153
|
systemSummaryCache.value = payload;
|
|
152
|
-
systemSummaryCache.expiresAt =
|
|
154
|
+
systemSummaryCache.expiresAt = __timeNowMs() + systemSummaryCacheSeconds * 1000;
|
|
153
155
|
return payload;
|
|
154
156
|
})
|
|
157
|
+
.catch((error) => {
|
|
158
|
+
if (hasValue && systemSummaryCache.value) {
|
|
159
|
+
return systemSummaryCache.value;
|
|
160
|
+
}
|
|
161
|
+
throw error;
|
|
162
|
+
})
|
|
155
163
|
.finally(() => {
|
|
156
164
|
systemSummaryCache.pending = null;
|
|
157
165
|
});
|
|
@@ -278,7 +286,7 @@ export const createStickerCatalogSystemContext = ({ executeQuery, tables, logger
|
|
|
278
286
|
.slice(0, 8);
|
|
279
287
|
|
|
280
288
|
const commands = collectAvailableMenuCommands(readmeCommandPrefix);
|
|
281
|
-
const generatedAt =
|
|
289
|
+
const generatedAt = __timeNowIso();
|
|
282
290
|
|
|
283
291
|
const totals = {
|
|
284
292
|
total_users: Number(lidMapTotals?.total_users || 0),
|
|
@@ -314,7 +322,7 @@ export const createStickerCatalogSystemContext = ({ executeQuery, tables, logger
|
|
|
314
322
|
};
|
|
315
323
|
|
|
316
324
|
const getReadmeSummaryCached = async () => {
|
|
317
|
-
const now =
|
|
325
|
+
const now = __timeNowMs();
|
|
318
326
|
const hasValue = Boolean(readmeSummaryCache.value);
|
|
319
327
|
|
|
320
328
|
if (hasValue && now < readmeSummaryCache.expiresAt) {
|
|
@@ -325,9 +333,15 @@ export const createStickerCatalogSystemContext = ({ executeQuery, tables, logger
|
|
|
325
333
|
readmeSummaryCache.pending = withTimeout(buildReadmeSummarySnapshot(), 7000)
|
|
326
334
|
.then((payload) => {
|
|
327
335
|
readmeSummaryCache.value = payload;
|
|
328
|
-
readmeSummaryCache.expiresAt =
|
|
336
|
+
readmeSummaryCache.expiresAt = __timeNowMs() + readmeSummaryCacheSeconds * 1000;
|
|
329
337
|
return payload;
|
|
330
338
|
})
|
|
339
|
+
.catch((error) => {
|
|
340
|
+
if (hasValue && readmeSummaryCache.value) {
|
|
341
|
+
return readmeSummaryCache.value;
|
|
342
|
+
}
|
|
343
|
+
throw error;
|
|
344
|
+
})
|
|
331
345
|
.finally(() => {
|
|
332
346
|
readmeSummaryCache.pending = null;
|
|
333
347
|
});
|
|
@@ -345,12 +359,8 @@ export const createStickerCatalogSystemContext = ({ executeQuery, tables, logger
|
|
|
345
359
|
const botPhoneFromCatalog = String(resolveCatalogBotPhone() || '').replace(/\D+/g, '');
|
|
346
360
|
if (botPhoneFromCatalog) candidates.add(botPhoneFromCatalog);
|
|
347
361
|
|
|
348
|
-
const
|
|
349
|
-
|
|
350
|
-
for (const candidate of envCandidates) {
|
|
351
|
-
const digits = String(candidate || '').replace(/\D+/g, '');
|
|
352
|
-
if (digits) candidates.add(digits);
|
|
353
|
-
}
|
|
362
|
+
const configuredBotPhone = String(resolveBotPhoneFromEnv({ fallback: '' }) || '').replace(/\D+/g, '');
|
|
363
|
+
if (configuredBotPhone) candidates.add(configuredBotPhone);
|
|
354
364
|
|
|
355
365
|
return Array.from(candidates).filter((value) => value.length >= 8);
|
|
356
366
|
};
|
|
@@ -522,12 +532,12 @@ export const createStickerCatalogSystemContext = ({ executeQuery, tables, logger
|
|
|
522
532
|
top_type: topType,
|
|
523
533
|
top_type_count: topTypeCount,
|
|
524
534
|
rows: rowsEnriched,
|
|
525
|
-
updated_at:
|
|
535
|
+
updated_at: __timeNowIso(),
|
|
526
536
|
};
|
|
527
537
|
};
|
|
528
538
|
|
|
529
539
|
const getGlobalRankingSummaryCached = async () => {
|
|
530
|
-
const now =
|
|
540
|
+
const now = __timeNowMs();
|
|
531
541
|
const hasValue = Boolean(globalRankCache.value);
|
|
532
542
|
|
|
533
543
|
if (hasValue && now < globalRankCache.expiresAt) {
|
|
@@ -538,9 +548,15 @@ export const createStickerCatalogSystemContext = ({ executeQuery, tables, logger
|
|
|
538
548
|
globalRankCache.pending = withTimeout(buildGlobalRankingSummary(), 5000)
|
|
539
549
|
.then((data) => {
|
|
540
550
|
globalRankCache.value = data;
|
|
541
|
-
globalRankCache.expiresAt =
|
|
551
|
+
globalRankCache.expiresAt = __timeNowMs() + globalRankRefreshSeconds * 1000;
|
|
542
552
|
return data;
|
|
543
553
|
})
|
|
554
|
+
.catch((error) => {
|
|
555
|
+
if (hasValue && globalRankCache.value) {
|
|
556
|
+
return globalRankCache.value;
|
|
557
|
+
}
|
|
558
|
+
throw error;
|
|
559
|
+
})
|
|
544
560
|
.finally(() => {
|
|
545
561
|
globalRankCache.pending = null;
|
|
546
562
|
});
|
|
@@ -579,7 +595,7 @@ export const createStickerCatalogSystemContext = ({ executeQuery, tables, logger
|
|
|
579
595
|
};
|
|
580
596
|
|
|
581
597
|
const buildLastSevenUtcDateKeys = () => {
|
|
582
|
-
const now =
|
|
598
|
+
const now = __timeNow();
|
|
583
599
|
const todayUtc = Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate());
|
|
584
600
|
return Array.from({ length: 7 }).map((_, index) => {
|
|
585
601
|
const date = new Date(todayUtc - (6 - index) * 24 * 60 * 60 * 1000);
|
|
@@ -718,12 +734,12 @@ export const createStickerCatalogSystemContext = ({ executeQuery, tables, logger
|
|
|
718
734
|
clicks_last_7_days: Number(clicksLast7Days || 0),
|
|
719
735
|
likes_last_7_days: Number(likesLast7Days || 0),
|
|
720
736
|
series_last_7_days: seriesLast7Days,
|
|
721
|
-
updated_at:
|
|
737
|
+
updated_at: __timeNowIso(),
|
|
722
738
|
};
|
|
723
739
|
};
|
|
724
740
|
|
|
725
741
|
const getMarketplaceGlobalStatsCached = async () => {
|
|
726
|
-
const now =
|
|
742
|
+
const now = __timeNowMs();
|
|
727
743
|
const hasValue = Boolean(marketplaceGlobalStatsCache.value);
|
|
728
744
|
if (hasValue && now < marketplaceGlobalStatsCache.expiresAt) {
|
|
729
745
|
return marketplaceGlobalStatsCache.value;
|
|
@@ -733,9 +749,15 @@ export const createStickerCatalogSystemContext = ({ executeQuery, tables, logger
|
|
|
733
749
|
marketplaceGlobalStatsCache.pending = withTimeout(buildMarketplaceGlobalStatsSnapshot(), 5000)
|
|
734
750
|
.then((data) => {
|
|
735
751
|
marketplaceGlobalStatsCache.value = data;
|
|
736
|
-
marketplaceGlobalStatsCache.expiresAt =
|
|
752
|
+
marketplaceGlobalStatsCache.expiresAt = __timeNowMs() + marketplaceGlobalStatsCacheSeconds * 1000;
|
|
737
753
|
return data;
|
|
738
754
|
})
|
|
755
|
+
.catch((error) => {
|
|
756
|
+
if (hasValue && marketplaceGlobalStatsCache.value) {
|
|
757
|
+
return marketplaceGlobalStatsCache.value;
|
|
758
|
+
}
|
|
759
|
+
throw error;
|
|
760
|
+
})
|
|
739
761
|
.finally(() => {
|
|
740
762
|
marketplaceGlobalStatsCache.pending = null;
|
|
741
763
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import logger from '#logger';
|
|
2
2
|
import { executeQuery, TABLES } from '../../../database/index.js';
|
|
3
|
-
import { getActiveSocket, getJidUser, normalizeJid, profilePictureUrlFromActiveSocket } from '../../../app/config/index.js';
|
|
3
|
+
import { getActiveSocket, getActiveSocketsBySession, getJidUser, getMultiSessionRuntimeConfig, isSocketOpen, normalizeJid, profilePictureUrlFromActiveSocket } from '../../../app/config/index.js';
|
|
4
4
|
import { getSystemMetrics } from '../../../app/utils/systemMetrics/systemMetricsModule.js';
|
|
5
5
|
import { createStickerCatalogSystemContext } from './stickerCatalogSystemContext.js';
|
|
6
6
|
import { createStickerCatalogNonCatalogHandlers } from '../sticker/nonCatalogHandlers.js';
|
|
@@ -10,6 +10,9 @@ import { fetchPrometheusSummary } from './systemMetricsController.js';
|
|
|
10
10
|
import { buildBotContactInfo, buildSupportInfo, resolveCatalogBotPhone } from './contactController.js';
|
|
11
11
|
import { buildAdminMenu, buildAiMenu, buildAnimeMenu, buildMediaMenu, buildMenuCaption, buildQuoteMenu, buildStatsMenu, buildStickerMenu } from '../../../app/modules/menuModule/common.js';
|
|
12
12
|
import { trackWebVisitMetric } from './visitController.js';
|
|
13
|
+
import groupOwnershipService from '../../../app/services/multiSession/groupOwnershipService.js';
|
|
14
|
+
import sessionRegistryService from '../../../app/services/multiSession/sessionRegistryService.js';
|
|
15
|
+
import { runGroupAssignmentBalancerCycle } from '../../../app/services/multiSession/assignmentBalancerService.js';
|
|
13
16
|
|
|
14
17
|
const SYSTEM_SUMMARY_CACHE_SECONDS = Number(process.env.SYSTEM_SUMMARY_CACHE_SECONDS || 20);
|
|
15
18
|
const README_SUMMARY_CACHE_SECONDS = Number(process.env.README_SUMMARY_CACHE_SECONDS || 1800);
|
|
@@ -132,4 +135,254 @@ export const systemHandlers = createStickerCatalogNonCatalogHandlers({
|
|
|
132
135
|
isAuthenticatedGoogleSession: (sess) => Boolean(sess?.sub && (sess?.ownerJid || sess?.ownerPhone || sess?.email)),
|
|
133
136
|
});
|
|
134
137
|
|
|
138
|
+
const clampLimit = (value, fallback = 200, min = 1, max = 5_000) => {
|
|
139
|
+
const parsed = Number.parseInt(String(value ?? ''), 10);
|
|
140
|
+
if (!Number.isFinite(parsed)) return fallback;
|
|
141
|
+
return Math.max(min, Math.min(max, parsed));
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const normalizeOptional = (value, maxLength = 255) => {
|
|
145
|
+
const normalized = String(value || '')
|
|
146
|
+
.trim()
|
|
147
|
+
.slice(0, maxLength);
|
|
148
|
+
return normalized || null;
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
const normalizeBoolean = (value, fallback = false) => {
|
|
152
|
+
if (value === undefined || value === null || value === '') return fallback;
|
|
153
|
+
const normalized = String(value).trim().toLowerCase();
|
|
154
|
+
if (['1', 'true', 'yes', 'y', 'on'].includes(normalized)) return true;
|
|
155
|
+
if (['0', 'false', 'no', 'n', 'off'].includes(normalized)) return false;
|
|
156
|
+
return fallback;
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
const toIso = (value) => {
|
|
160
|
+
if (!value) return null;
|
|
161
|
+
const parsed = new Date(value);
|
|
162
|
+
if (Number.isNaN(parsed.getTime())) return null;
|
|
163
|
+
return parsed.toISOString();
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
const resolveSocketRuntimeState = (socket) => {
|
|
167
|
+
const open = isSocketOpen(socket);
|
|
168
|
+
const readyState = resolveSocketReadyState(socket);
|
|
169
|
+
const botJid = resolveActiveSocketBotJid(socket);
|
|
170
|
+
return {
|
|
171
|
+
socket_open: open,
|
|
172
|
+
socket_ready_state: readyState,
|
|
173
|
+
socket_bot_jid: botJid || null,
|
|
174
|
+
};
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
export const listSystemAdminSessions = async ({ status = null, limit = 200 } = {}) => {
|
|
178
|
+
const runtimeConfig = getMultiSessionRuntimeConfig();
|
|
179
|
+
const safeStatus = normalizeOptional(status, 24);
|
|
180
|
+
const safeLimit = clampLimit(limit, 200, 1, 5_000);
|
|
181
|
+
const registryRows = await sessionRegistryService.listSessions({
|
|
182
|
+
status: safeStatus,
|
|
183
|
+
limit: safeLimit,
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
const socketsBySession = getActiveSocketsBySession();
|
|
187
|
+
const knownSessionIds = new Set();
|
|
188
|
+
for (const sessionId of runtimeConfig.sessionIds || []) knownSessionIds.add(sessionId);
|
|
189
|
+
for (const row of registryRows || []) {
|
|
190
|
+
if (row?.sessionId) knownSessionIds.add(row.sessionId);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const selectedSessionIds = Array.from(knownSessionIds)
|
|
194
|
+
.filter(Boolean)
|
|
195
|
+
.slice(0, safeLimit);
|
|
196
|
+
const registryBySession = new Map((registryRows || []).map((row) => [row.sessionId, row]));
|
|
197
|
+
|
|
198
|
+
const sessions = selectedSessionIds.map((sessionId) => {
|
|
199
|
+
const row = registryBySession.get(sessionId) || null;
|
|
200
|
+
const socket = socketsBySession.get(sessionId) || null;
|
|
201
|
+
const socketRuntime = resolveSocketRuntimeState(socket);
|
|
202
|
+
|
|
203
|
+
return {
|
|
204
|
+
session_id: sessionId,
|
|
205
|
+
is_primary: sessionId === runtimeConfig.primarySessionId,
|
|
206
|
+
configured: (runtimeConfig.sessionIds || []).includes(sessionId),
|
|
207
|
+
configured_weight: Number(runtimeConfig.sessionWeights?.[sessionId] || 1),
|
|
208
|
+
status: row?.status || (socketRuntime.socket_open ? 'online' : 'offline'),
|
|
209
|
+
bot_jid: row?.botJid || socketRuntime.socket_bot_jid || null,
|
|
210
|
+
capacity_weight: Number(row?.capacityWeight || runtimeConfig.sessionWeights?.[sessionId] || 1),
|
|
211
|
+
current_score: Number(row?.currentScore || 0),
|
|
212
|
+
last_heartbeat_at: toIso(row?.lastHeartbeatAt),
|
|
213
|
+
last_connected_at: toIso(row?.lastConnectedAt),
|
|
214
|
+
last_disconnected_at: toIso(row?.lastDisconnectedAt),
|
|
215
|
+
updated_at: toIso(row?.updatedAt),
|
|
216
|
+
metadata: row?.metadata || null,
|
|
217
|
+
...socketRuntime,
|
|
218
|
+
};
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
return {
|
|
222
|
+
generated_at: new Date().toISOString(),
|
|
223
|
+
primary_session_id: runtimeConfig.primarySessionId,
|
|
224
|
+
configured_session_ids: runtimeConfig.sessionIds || [],
|
|
225
|
+
sessions,
|
|
226
|
+
};
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
export const listSystemAdminAssignments = async (
|
|
230
|
+
{
|
|
231
|
+
groupJid = null,
|
|
232
|
+
ownerSessionId = null,
|
|
233
|
+
includeExpired = false,
|
|
234
|
+
limit = 200,
|
|
235
|
+
} = {},
|
|
236
|
+
) => {
|
|
237
|
+
const safeGroupJid = normalizeOptional(groupJid, 255);
|
|
238
|
+
const safeOwnerSessionId = normalizeOptional(ownerSessionId, 64);
|
|
239
|
+
const safeIncludeExpired = normalizeBoolean(includeExpired, false);
|
|
240
|
+
const safeLimit = clampLimit(limit, 200, 1, 5_000);
|
|
241
|
+
|
|
242
|
+
const assignments = await groupOwnershipService.listAssignments({
|
|
243
|
+
groupJid: safeGroupJid,
|
|
244
|
+
ownerSessionId: safeOwnerSessionId,
|
|
245
|
+
includeExpired: safeIncludeExpired,
|
|
246
|
+
limit: safeLimit,
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
return {
|
|
250
|
+
generated_at: new Date().toISOString(),
|
|
251
|
+
filters: {
|
|
252
|
+
group_jid: safeGroupJid,
|
|
253
|
+
owner_session_id: safeOwnerSessionId,
|
|
254
|
+
include_expired: safeIncludeExpired,
|
|
255
|
+
limit: safeLimit,
|
|
256
|
+
},
|
|
257
|
+
assignments: (assignments || []).map((assignment) => ({
|
|
258
|
+
group_jid: assignment?.groupJid || null,
|
|
259
|
+
owner_session_id: assignment?.ownerSessionId || null,
|
|
260
|
+
lease_expires_at: toIso(assignment?.leaseExpiresAt),
|
|
261
|
+
cooldown_until: toIso(assignment?.cooldownUntil),
|
|
262
|
+
assignment_version: Number(assignment?.assignmentVersion || 1),
|
|
263
|
+
pinned: assignment?.pinned === true,
|
|
264
|
+
active: assignment?.active !== false,
|
|
265
|
+
last_reason: assignment?.lastReason || null,
|
|
266
|
+
created_at: toIso(assignment?.createdAt),
|
|
267
|
+
updated_at: toIso(assignment?.updatedAt),
|
|
268
|
+
})),
|
|
269
|
+
};
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
export const setSystemAdminGroupPin = async (
|
|
273
|
+
{
|
|
274
|
+
groupJid,
|
|
275
|
+
pinned,
|
|
276
|
+
sessionId = null,
|
|
277
|
+
reason = null,
|
|
278
|
+
changedBy = 'admin_api',
|
|
279
|
+
metadata = null,
|
|
280
|
+
} = {},
|
|
281
|
+
) => {
|
|
282
|
+
const outcome = await groupOwnershipService.setPinned({
|
|
283
|
+
groupJid,
|
|
284
|
+
pinned,
|
|
285
|
+
sessionId,
|
|
286
|
+
reason: reason || (pinned ? 'admin_pin_group' : 'admin_unpin_group'),
|
|
287
|
+
changedBy,
|
|
288
|
+
metadata,
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
return {
|
|
292
|
+
updated: Boolean(outcome?.updated),
|
|
293
|
+
reason: outcome?.reason || null,
|
|
294
|
+
assignment_version: Number(outcome?.assignmentVersion || 0) || null,
|
|
295
|
+
previous_owner_session_id: outcome?.previousOwnerSessionId || null,
|
|
296
|
+
owner: outcome?.owner
|
|
297
|
+
? {
|
|
298
|
+
group_jid: outcome.owner.groupJid,
|
|
299
|
+
owner_session_id: outcome.owner.ownerSessionId,
|
|
300
|
+
lease_expires_at: toIso(outcome.owner.leaseExpiresAt),
|
|
301
|
+
cooldown_until: toIso(outcome.owner.cooldownUntil),
|
|
302
|
+
assignment_version: Number(outcome.owner.assignmentVersion || 1),
|
|
303
|
+
pinned: outcome.owner.pinned === true,
|
|
304
|
+
last_reason: outcome.owner.lastReason || null,
|
|
305
|
+
}
|
|
306
|
+
: null,
|
|
307
|
+
};
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
export const forceSystemAdminGroupFailover = async (
|
|
311
|
+
{
|
|
312
|
+
groupJid,
|
|
313
|
+
targetSessionId,
|
|
314
|
+
reason = 'admin_force_failover',
|
|
315
|
+
changedBy = 'admin_api',
|
|
316
|
+
metadata = null,
|
|
317
|
+
} = {},
|
|
318
|
+
) => {
|
|
319
|
+
const outcome = await groupOwnershipService.forceAssign({
|
|
320
|
+
groupJid,
|
|
321
|
+
sessionId: targetSessionId,
|
|
322
|
+
reason,
|
|
323
|
+
changedBy,
|
|
324
|
+
metadata,
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
return {
|
|
328
|
+
reassigned: Boolean(outcome?.reassigned),
|
|
329
|
+
reason: outcome?.reason || null,
|
|
330
|
+
assignment_version: Number(outcome?.assignmentVersion || 0) || null,
|
|
331
|
+
previous_owner_session_id: outcome?.previousOwnerSessionId || null,
|
|
332
|
+
owner: outcome?.owner
|
|
333
|
+
? {
|
|
334
|
+
group_jid: outcome.owner.groupJid,
|
|
335
|
+
owner_session_id: outcome.owner.ownerSessionId,
|
|
336
|
+
lease_expires_at: toIso(outcome.owner.leaseExpiresAt),
|
|
337
|
+
assignment_version: Number(outcome.owner.assignmentVersion || 1),
|
|
338
|
+
pinned: outcome.owner.pinned === true,
|
|
339
|
+
last_reason: outcome.owner.lastReason || null,
|
|
340
|
+
}
|
|
341
|
+
: null,
|
|
342
|
+
};
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
export const triggerSystemAdminManualRebalance = async () => {
|
|
346
|
+
const cycle = await runGroupAssignmentBalancerCycle();
|
|
347
|
+
return {
|
|
348
|
+
generated_at: new Date().toISOString(),
|
|
349
|
+
cycle,
|
|
350
|
+
};
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
export const listSystemAdminAssignmentHistory = async ({ groupJid = null, limit = 100 } = {}) => {
|
|
354
|
+
const safeLimit = clampLimit(limit, 100, 1, 5_000);
|
|
355
|
+
const safeGroupJid = normalizeOptional(groupJid, 255);
|
|
356
|
+
const params = [];
|
|
357
|
+
const where = [];
|
|
358
|
+
if (safeGroupJid) {
|
|
359
|
+
where.push('group_jid = ?');
|
|
360
|
+
params.push(safeGroupJid);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
const rows = await executeQuery(
|
|
364
|
+
`SELECT id, group_jid, previous_session_id, new_session_id, change_reason, changed_by, assignment_version, metadata, created_at
|
|
365
|
+
FROM ${TABLES.GROUP_ASSIGNMENT_HISTORY}
|
|
366
|
+
${where.length > 0 ? `WHERE ${where.join(' AND ')}` : ''}
|
|
367
|
+
ORDER BY id DESC
|
|
368
|
+
LIMIT ${safeLimit}`,
|
|
369
|
+
params,
|
|
370
|
+
);
|
|
371
|
+
|
|
372
|
+
return {
|
|
373
|
+
generated_at: new Date().toISOString(),
|
|
374
|
+
history: (Array.isArray(rows) ? rows : []).map((row) => ({
|
|
375
|
+
id: Number(row?.id || 0),
|
|
376
|
+
group_jid: row?.group_jid || null,
|
|
377
|
+
previous_session_id: row?.previous_session_id || null,
|
|
378
|
+
new_session_id: row?.new_session_id || null,
|
|
379
|
+
change_reason: row?.change_reason || null,
|
|
380
|
+
changed_by: row?.changed_by || null,
|
|
381
|
+
assignment_version: Number(row?.assignment_version || 0) || null,
|
|
382
|
+
metadata: row?.metadata || null,
|
|
383
|
+
created_at: toIso(row?.created_at),
|
|
384
|
+
})),
|
|
385
|
+
};
|
|
386
|
+
};
|
|
387
|
+
|
|
135
388
|
export { scheduleGlobalRankingPreload };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { now as __timeNow, nowIso as __timeNowIso, toUnixMs as __timeNowMs } from '#time';
|
|
1
2
|
import { formatDuration } from '../../http/httpRequestUtils.js';
|
|
2
3
|
|
|
3
4
|
const METRICS_ENDPOINT = process.env.METRICS_ENDPOINT || `http://127.0.0.1:${process.env.METRICS_PORT || 9102}${process.env.METRICS_PATH || '/metrics'}`;
|
|
@@ -124,7 +125,7 @@ export const fetchPrometheusSummary = async () => {
|
|
|
124
125
|
const series = parsePrometheusText(text);
|
|
125
126
|
|
|
126
127
|
const processStart = pickMetricValue(series, 'omnizap_process_start_time_seconds');
|
|
127
|
-
const nowSeconds =
|
|
128
|
+
const nowSeconds = __timeNowMs() / 1000;
|
|
128
129
|
const processUptimeSeconds = Number.isFinite(processStart) ? Math.max(0, nowSeconds - processStart) : null;
|
|
129
130
|
|
|
130
131
|
const lagP99 = pickMetricValue(series, 'omnizap_nodejs_eventloop_lag_p99_seconds');
|
|
@@ -3,6 +3,7 @@ import path from 'node:path';
|
|
|
3
3
|
|
|
4
4
|
import logger from '#logger';
|
|
5
5
|
import { DEFAULT_LEGACY_STICKER_API_BASE_PATH, DEFAULT_USER_API_BASE_PATH, isUserApiPath, normalizeBasePath, resolveLegacyUserApiPath } from '../routes/user/userApiPaths.js';
|
|
6
|
+
import { buildWhatsappUrl, resolvePublicWhatsappNumber } from '../utils/publicContact.js';
|
|
6
7
|
|
|
7
8
|
const LEGACY_STICKER_API_BASE_PATH = normalizeBasePath(process.env.STICKER_API_BASE_PATH, DEFAULT_LEGACY_STICKER_API_BASE_PATH);
|
|
8
9
|
const USER_API_BASE_PATH = normalizeBasePath(process.env.USER_API_BASE_PATH || process.env.AUTH_API_BASE_PATH, DEFAULT_USER_API_BASE_PATH);
|
|
@@ -11,6 +12,8 @@ const USER_PROFILE_WEB_PATH = normalizeBasePath(process.env.USER_PROFILE_WEB_PAT
|
|
|
11
12
|
const USER_PASSWORD_RESET_WEB_PATH = normalizeBasePath(process.env.USER_PASSWORD_RESET_WEB_PATH, '/user/password-reset');
|
|
12
13
|
const USER_DASHBOARD_TEMPLATE_PATH = path.join(process.cwd(), 'public', 'pages', 'user.html');
|
|
13
14
|
const USER_PASSWORD_RESET_TEMPLATE_PATH = path.join(process.cwd(), 'public', 'pages', 'user-password-reset.html');
|
|
15
|
+
const PUBLIC_WHATSAPP_NUMBER = resolvePublicWhatsappNumber();
|
|
16
|
+
const PUBLIC_WHATSAPP_URL = buildWhatsappUrl(PUBLIC_WHATSAPP_NUMBER);
|
|
14
17
|
|
|
15
18
|
const hasPathPrefix = (pathname, prefix) => pathname === prefix || pathname.startsWith(`${prefix}/`);
|
|
16
19
|
const escapeHtmlAttribute = (value) =>
|
|
@@ -52,7 +55,10 @@ const renderUserDashboardHtml = async ({ passwordReset = false } = {}) => {
|
|
|
52
55
|
const dataAttributes = {
|
|
53
56
|
'data-api-base-path': USER_API_BASE_PATH,
|
|
54
57
|
'data-login-path': STICKER_LOGIN_WEB_PATH,
|
|
58
|
+
'data-panel-path': USER_PROFILE_WEB_PATH,
|
|
55
59
|
'data-password-reset-web-path': USER_PASSWORD_RESET_WEB_PATH,
|
|
60
|
+
'data-support-whatsapp-number': PUBLIC_WHATSAPP_NUMBER,
|
|
61
|
+
'data-support-whatsapp-url': PUBLIC_WHATSAPP_URL,
|
|
56
62
|
};
|
|
57
63
|
|
|
58
64
|
let html = template;
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { now as __timeNow, nowIso as __timeNowIso, toUnixMs as __timeNowMs } from '#time';
|
|
2
|
+
import { resolveAdminPhoneFromEnv, resolveBotPhoneFromEnv, resolveSupportPhoneFromEnv } from '../../utils/whatsapp/contactEnv.js';
|
|
1
3
|
const DEFAULT_SITE_ORIGIN = 'https://omnizap.shop';
|
|
2
4
|
const DEFAULT_BRAND_NAME = 'OmniZap';
|
|
3
5
|
|
|
@@ -76,7 +78,7 @@ const resolveBrandConfig = (payload = {}) => {
|
|
|
76
78
|
const supportFallback = `${siteOrigin}/termos-de-uso/`;
|
|
77
79
|
const replyToAddress = normalizeEmailAddress(payload?.replyTo || process.env.SMTP_REPLY_TO || process.env.EMAIL_REPLY_TO || process.env.MAIL_REPLY_TO || '');
|
|
78
80
|
const fromAddress = normalizeEmailAddress(process.env.SMTP_FROM || process.env.EMAIL_FROM || process.env.MAIL_FROM || process.env.SMTP_USER || process.env.EMAIL_USER || process.env.MAIL_USER || '');
|
|
79
|
-
const supportPhoneCandidate = normalizePhoneDigits(payload?.supportPhone ||
|
|
81
|
+
const supportPhoneCandidate = normalizePhoneDigits(payload?.supportPhone || resolveSupportPhoneFromEnv({ fallback: resolveAdminPhoneFromEnv({ fallback: '' }) }) || '', 20);
|
|
80
82
|
const supportPhoneDigits = isLikelyPhoneDigits(supportPhoneCandidate) ? supportPhoneCandidate : '';
|
|
81
83
|
const supportPhonePn = formatPhonePn(supportPhoneDigits);
|
|
82
84
|
const supportWhatsappUrl = supportPhoneDigits ? `https://wa.me/${supportPhoneDigits}` : '';
|
|
@@ -125,7 +127,7 @@ const renderEmailLayout = ({ payload = {}, preheader = '', heading = '', greetin
|
|
|
125
127
|
const safeSecondaryCtaUrl = normalizeHttpUrl(secondaryCtaUrl, '');
|
|
126
128
|
const safeSecurityNote = normalizeText(securityNote, 220);
|
|
127
129
|
const safeFooterMessage = normalizeText(footerMessage, 220);
|
|
128
|
-
const year =
|
|
130
|
+
const year = __timeNow().getUTCFullYear();
|
|
129
131
|
|
|
130
132
|
const logoBlock = brand.brandLogoUrl ? `<img src="${escapeHtml(brand.brandLogoUrl)}" alt="${escapeHtml(brand.brandName)}" width="132" style="display:block;border:0;outline:none;text-decoration:none;height:auto;margin:0 auto;" />` : `<div style="display:inline-block;font-size:26px;font-weight:800;color:#0f172a;letter-spacing:0.2px;">${escapeHtml(brand.brandName)}</div>`;
|
|
131
133
|
|
|
@@ -215,7 +217,7 @@ const resolveNavigationLinks = (payload = {}) => {
|
|
|
215
217
|
};
|
|
216
218
|
|
|
217
219
|
const resolveWelcomeBotWhatsApp = (payload = {}) => {
|
|
218
|
-
const botPhoneCandidate = normalizePhoneDigits(payload?.botPhone || payload?.botNumber || process.env.EMAIL_WELCOME_BOT_PHONE ||
|
|
220
|
+
const botPhoneCandidate = normalizePhoneDigits(payload?.botPhone || payload?.botNumber || process.env.EMAIL_WELCOME_BOT_PHONE || resolveBotPhoneFromEnv({ fallback: resolveSupportPhoneFromEnv({ fallback: '' }) }) || '', 20);
|
|
219
221
|
const botPhoneDigits = isLikelyPhoneDigits(botPhoneCandidate) ? botPhoneCandidate : '';
|
|
220
222
|
const botPhonePn = formatPhonePn(botPhoneDigits);
|
|
221
223
|
const botWhatsAppUrl = botPhoneDigits ? `https://wa.me/${botPhoneDigits}` : '';
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { now as __timeNow, nowIso as __timeNowIso, toUnixMs as __timeNowMs } from '#time';
|
|
1
2
|
import http from 'node:http';
|
|
2
3
|
|
|
3
4
|
import logger from '#logger';
|
|
@@ -5,6 +6,7 @@ import { getMetricsServerConfig, isMetricsEnabled, recordHttpRequest, resolveRou
|
|
|
5
6
|
import { applyCachePolicy } from '../middleware/cachePolicy.js';
|
|
6
7
|
import { applySensitiveRouteRateLimit } from '../middleware/endpointRateLimit.js';
|
|
7
8
|
import { applySecurityHeaders } from '../middleware/securityHeaders.js';
|
|
9
|
+
import { shouldHandleGrafanaProxyPath } from '../routes/observability/grafanaProxyRouter.js';
|
|
8
10
|
import { getIndexRouteConfigs, routeRequest } from '../routes/indexRouter.js';
|
|
9
11
|
import { parseRequestUrl, normalizeRequestId } from './requestContext.js';
|
|
10
12
|
|
|
@@ -41,7 +43,7 @@ export const startHttpServer = () => {
|
|
|
41
43
|
const { host, port, path: metricsPath } = getMetricsServerConfig();
|
|
42
44
|
|
|
43
45
|
server = http.createServer(async (req, res) => {
|
|
44
|
-
const requestStartedAt =
|
|
46
|
+
const requestStartedAt = __timeNowMs();
|
|
45
47
|
const requestId = normalizeRequestId(req.headers['x-request-id']);
|
|
46
48
|
res.setHeader('X-Request-Id', requestId);
|
|
47
49
|
|
|
@@ -64,10 +66,11 @@ export const startHttpServer = () => {
|
|
|
64
66
|
userConfig: routeConfigs?.userConfig || null,
|
|
65
67
|
systemAdminConfig: routeConfigs?.systemAdminConfig || null,
|
|
66
68
|
});
|
|
69
|
+
const isGrafanaProxyRequest = shouldHandleGrafanaProxyPath(pathname, routeConfigs?.grafanaProxyConfig || null);
|
|
67
70
|
|
|
68
71
|
res.once('finish', () => {
|
|
69
72
|
recordHttpRequest({
|
|
70
|
-
durationMs:
|
|
73
|
+
durationMs: __timeNowMs() - requestStartedAt,
|
|
71
74
|
method: req.method,
|
|
72
75
|
statusCode: res.statusCode,
|
|
73
76
|
routeGroup,
|
|
@@ -75,10 +78,12 @@ export const startHttpServer = () => {
|
|
|
75
78
|
});
|
|
76
79
|
|
|
77
80
|
try {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
81
|
+
if (!isGrafanaProxyRequest) {
|
|
82
|
+
applySecurityHeaders(req, res);
|
|
83
|
+
applyCachePolicy(req, res, { pathname });
|
|
84
|
+
const allowedByRateLimit = await applySensitiveRouteRateLimit(req, res, { pathname });
|
|
85
|
+
if (!allowedByRateLimit) return;
|
|
86
|
+
}
|
|
82
87
|
|
|
83
88
|
await routeRequest(req, res, {
|
|
84
89
|
pathname,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { now as __timeNow, nowIso as __timeNowIso, toUnixMs as __timeNowMs } from '#time';
|
|
1
2
|
import { resolveClientIp } from '../http/clientIp.js';
|
|
2
3
|
|
|
3
4
|
const rateLimitBuckets = new Map();
|
|
@@ -38,7 +39,7 @@ export const createRateLimit = ({ windowMs = 60_000, max = 60, keyPrefix = 'glob
|
|
|
38
39
|
const safeKeyPrefix = String(keyPrefix || 'global').trim() || 'global';
|
|
39
40
|
|
|
40
41
|
return (req, res) => {
|
|
41
|
-
const nowMs =
|
|
42
|
+
const nowMs = __timeNowMs();
|
|
42
43
|
pruneBuckets(safeWindowMs, nowMs);
|
|
43
44
|
|
|
44
45
|
const ip = resolveClientIp(req);
|
|
@@ -10,10 +10,29 @@ const parseEnvBool = (value, fallback = false) => {
|
|
|
10
10
|
return fallback;
|
|
11
11
|
};
|
|
12
12
|
|
|
13
|
+
const parseEnvList = (value) =>
|
|
14
|
+
String(value || '')
|
|
15
|
+
.split(',')
|
|
16
|
+
.map((item) => String(item || '').trim())
|
|
17
|
+
.filter(Boolean);
|
|
18
|
+
|
|
19
|
+
const toHttpOrigin = (value) => {
|
|
20
|
+
const raw = String(value || '').trim();
|
|
21
|
+
if (!raw) return '';
|
|
22
|
+
try {
|
|
23
|
+
const parsed = new URL(raw);
|
|
24
|
+
if (!['http:', 'https:'].includes(parsed.protocol)) return '';
|
|
25
|
+
return parsed.origin;
|
|
26
|
+
} catch {
|
|
27
|
+
return '';
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
|
|
13
31
|
const HELMET_CSP_ENFORCE = parseEnvBool(process.env.HELMET_CONTENT_SECURITY_POLICY_ENABLED, true);
|
|
14
32
|
const BACKEND_BUILD_ID = String(process.env.OMNIZAP_BUILD_ID || '')
|
|
15
33
|
.trim()
|
|
16
34
|
.slice(0, 80);
|
|
35
|
+
const FRAME_SRC_EXTRA = Array.from(new Set([...parseEnvList(process.env.HELMET_CSP_FRAME_SRC_EXTRA), process.env.SYSTEM_ADMIN_GRAFANA_URL, process.env.GRAFANA_PUBLIC_URL].map((item) => toHttpOrigin(item)).filter(Boolean)));
|
|
17
36
|
|
|
18
37
|
const HELMET_CSP_DIRECTIVES = {
|
|
19
38
|
defaultSrc: ["'self'"],
|
|
@@ -26,7 +45,7 @@ const HELMET_CSP_DIRECTIVES = {
|
|
|
26
45
|
imgSrc: ["'self'", 'data:', 'blob:', 'https:'],
|
|
27
46
|
fontSrc: ["'self'", 'data:', 'https://fonts.gstatic.com', 'https://cdnjs.cloudflare.com'],
|
|
28
47
|
connectSrc: ["'self'", 'https://accounts.google.com', 'https://oauth2.googleapis.com', 'https://api.github.com'],
|
|
29
|
-
frameSrc: ["'self'", 'https://accounts.google.com'],
|
|
48
|
+
frameSrc: ["'self'", 'https://accounts.google.com', ...FRAME_SRC_EXTRA],
|
|
30
49
|
workerSrc: ["'self'", 'blob:'],
|
|
31
50
|
manifestSrc: ["'self'"],
|
|
32
51
|
};
|
|
@@ -24,8 +24,10 @@ const DEFAULT_USER_SYSTEM_ADMIN_WEB_PATH = '/user/systemadm';
|
|
|
24
24
|
const DEFAULT_LEGACY_STICKER_ADMIN_WEB_PATH = '/stickers/admin';
|
|
25
25
|
const DEFAULT_SYSTEM_ADMIN_API_BASE_PATH = '/api/admin';
|
|
26
26
|
const DEFAULT_SYSTEM_ADMIN_API_SESSION_PATH = '/api/admin/session';
|
|
27
|
+
const DEFAULT_SYSTEM_ADMIN_API_MULTI_SESSION_PATH = '/api/admin/multi-session';
|
|
27
28
|
const DEFAULT_LEGACY_STICKER_ADMIN_API_BASE_PATH = '/api/sticker-packs/admin';
|
|
28
29
|
const DEFAULT_LEGACY_STICKER_ADMIN_API_SESSION_PATH = '/api/sticker-packs/admin/session';
|
|
30
|
+
const DEFAULT_LEGACY_STICKER_ADMIN_API_MULTI_SESSION_PATH = '/api/sticker-packs/admin/multi-session';
|
|
29
31
|
|
|
30
32
|
export const getSystemAdminRouterConfig = async () => {
|
|
31
33
|
const controller = await loadSystemAdminController();
|
|
@@ -35,8 +37,10 @@ export const getSystemAdminRouterConfig = async () => {
|
|
|
35
37
|
legacyWebPath: normalizeBasePath(legacyConfig.legacyWebPath, DEFAULT_LEGACY_STICKER_ADMIN_WEB_PATH),
|
|
36
38
|
apiAdminBasePath: normalizeBasePath(legacyConfig.apiAdminBasePath, DEFAULT_SYSTEM_ADMIN_API_BASE_PATH),
|
|
37
39
|
apiAdminSessionPath: normalizeBasePath(legacyConfig.apiAdminSessionPath, DEFAULT_SYSTEM_ADMIN_API_SESSION_PATH),
|
|
40
|
+
apiAdminMultiSessionPath: normalizeBasePath(legacyConfig.apiAdminMultiSessionPath, DEFAULT_SYSTEM_ADMIN_API_MULTI_SESSION_PATH),
|
|
38
41
|
legacyApiAdminBasePath: normalizeBasePath(legacyConfig.legacyApiAdminBasePath, DEFAULT_LEGACY_STICKER_ADMIN_API_BASE_PATH),
|
|
39
42
|
legacyApiAdminSessionPath: normalizeBasePath(legacyConfig.legacyApiAdminSessionPath, DEFAULT_LEGACY_STICKER_ADMIN_API_SESSION_PATH),
|
|
43
|
+
legacyApiAdminMultiSessionPath: normalizeBasePath(legacyConfig.legacyApiAdminMultiSessionPath, DEFAULT_LEGACY_STICKER_ADMIN_API_MULTI_SESSION_PATH),
|
|
40
44
|
};
|
|
41
45
|
};
|
|
42
46
|
|
|
@@ -46,8 +50,10 @@ export const shouldHandleSystemAdminPath = (pathname, systemAdminConfig = null)
|
|
|
46
50
|
legacyWebPath: DEFAULT_LEGACY_STICKER_ADMIN_WEB_PATH,
|
|
47
51
|
apiAdminBasePath: DEFAULT_SYSTEM_ADMIN_API_BASE_PATH,
|
|
48
52
|
apiAdminSessionPath: DEFAULT_SYSTEM_ADMIN_API_SESSION_PATH,
|
|
53
|
+
apiAdminMultiSessionPath: DEFAULT_SYSTEM_ADMIN_API_MULTI_SESSION_PATH,
|
|
49
54
|
legacyApiAdminBasePath: DEFAULT_LEGACY_STICKER_ADMIN_API_BASE_PATH,
|
|
50
55
|
legacyApiAdminSessionPath: DEFAULT_LEGACY_STICKER_ADMIN_API_SESSION_PATH,
|
|
56
|
+
legacyApiAdminMultiSessionPath: DEFAULT_LEGACY_STICKER_ADMIN_API_MULTI_SESSION_PATH,
|
|
51
57
|
};
|
|
52
58
|
|
|
53
59
|
if (startsWithPath(pathname, resolvedConfig.webPath)) return true;
|