@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.
Files changed (261) hide show
  1. package/.env.example +58 -13
  2. package/.github/workflows/ci.yml +5 -5
  3. package/.github/workflows/codeql.yml +1 -1
  4. package/.github/workflows/db-migration-check.yml +2 -2
  5. package/.github/workflows/dependency-review.yml +1 -1
  6. package/.github/workflows/deploy.yml +2 -2
  7. package/.github/workflows/release.yml +2 -2
  8. package/.github/workflows/security-attest-provenance.yml +2 -2
  9. package/.github/workflows/security-gitleaks.yml +13 -4
  10. package/.github/workflows/security-runner-hardening.yml +2 -2
  11. package/.github/workflows/security-scorecard.yml +1 -1
  12. package/.github/workflows/security-zap-baseline.yml +1 -1
  13. package/.github/workflows/security-zap-full-scan.yml +2 -1
  14. package/.github/workflows/security-zizmor.yml +1 -1
  15. package/.github/workflows/wiki-sync.yml +1 -1
  16. package/.gitleaksignore +9 -0
  17. package/CODE_OF_CONDUCT.md +2 -2
  18. package/GEMINI.md +64 -0
  19. package/README.md +52 -82
  20. package/SECURITY.md +1 -1
  21. package/app/config/index.js +2 -0
  22. package/app/configParts/adminIdentity.js +5 -5
  23. package/app/configParts/baileysConfig.js +230 -58
  24. package/app/configParts/groupUtils.js +5 -0
  25. package/app/configParts/messagePersistenceService.js +145 -4
  26. package/app/configParts/sessionConfig.js +157 -0
  27. package/app/connection/baileysCompatibility.test.js +1 -1
  28. package/app/connection/groupOwnerWriteStateResolver.js +109 -0
  29. package/app/connection/socketController.js +660 -158
  30. package/app/connection/socketController.multiSession.test.js +108 -0
  31. package/app/controllers/messageController.js +1 -1
  32. package/app/controllers/messagePipeline/commandMiddleware.js +12 -10
  33. package/app/controllers/messagePipeline/conversationMiddleware.js +2 -1
  34. package/app/controllers/messagePipeline/messagePipelineMiddlewares.test.js +104 -0
  35. package/app/controllers/messagePipeline/preProcessingMiddlewares.js +80 -2
  36. package/app/controllers/messageProcessingPipeline.js +93 -13
  37. package/app/controllers/messageProcessingPipeline.test.js +200 -0
  38. package/app/modules/adminModule/AGENT.md +1 -1
  39. package/app/modules/adminModule/commandConfig.json +3318 -1347
  40. package/app/modules/adminModule/groupCommandHandlers.js +858 -15
  41. package/app/modules/adminModule/groupCommandHandlers.test.js +378 -11
  42. package/app/modules/adminModule/groupWarningRepository.js +152 -0
  43. package/app/modules/aiModule/AGENT.md +47 -30
  44. package/app/modules/aiModule/aiConfigRuntime.js +1 -0
  45. package/app/modules/aiModule/catCommand.js +135 -27
  46. package/app/modules/aiModule/commandConfig.json +114 -28
  47. package/app/modules/analyticsModule/messageAnalysisEventRepository.js +54 -6
  48. package/app/modules/gameModule/AGENT.md +1 -1
  49. package/app/modules/gameModule/commandConfig.json +29 -0
  50. package/app/modules/menuModule/AGENT.md +1 -1
  51. package/app/modules/menuModule/commandConfig.json +45 -10
  52. package/app/modules/menuModule/menuCatalogService.js +190 -0
  53. package/app/modules/menuModule/menuCommandUsageRepository.js +109 -0
  54. package/app/modules/menuModule/menuDynamicService.js +511 -0
  55. package/app/modules/menuModule/menuDynamicService.test.js +141 -0
  56. package/app/modules/menuModule/menus.js +36 -5
  57. package/app/modules/playModule/AGENT.md +10 -5
  58. package/app/modules/playModule/commandConfig.json +140 -12
  59. package/app/modules/playModule/playCommand.js +1 -1417
  60. package/app/modules/playModule/playCommandConstants.js +80 -0
  61. package/app/modules/playModule/playCommandCore.js +361 -0
  62. package/app/modules/playModule/playCommandHandlers.js +41 -0
  63. package/app/modules/playModule/playCommandMediaClient.js +1872 -0
  64. package/app/modules/playModule/playConfigRuntime.js +245 -4
  65. package/app/modules/playModule/playModuleCriticalFlows.test.js +152 -0
  66. package/app/modules/quoteModule/AGENT.md +1 -1
  67. package/app/modules/quoteModule/commandConfig.json +29 -0
  68. package/app/modules/quoteModule/quoteCommand.js +3 -2
  69. package/app/modules/rpgPokemonModule/AGENT.md +1 -1
  70. package/app/modules/rpgPokemonModule/commandConfig.json +29 -0
  71. package/app/modules/rpgPokemonModule/rpgBattleCanvasRenderer.js +5 -4
  72. package/app/modules/rpgPokemonModule/rpgBattleService.test.js +2 -1
  73. package/app/modules/rpgPokemonModule/rpgPokemonDomain.js +2 -1
  74. package/app/modules/rpgPokemonModule/rpgPokemonService.js +38 -37
  75. package/app/modules/rpgPokemonModule/rpgProfileCanvasRenderer.js +4 -3
  76. package/app/modules/statsModule/AGENT.md +1 -1
  77. package/app/modules/statsModule/commandConfig.json +58 -0
  78. package/app/modules/statsModule/rankingCommon.js +5 -4
  79. package/app/modules/stickerModule/AGENT.md +1 -1
  80. package/app/modules/stickerModule/addStickerMetadata.js +4 -3
  81. package/app/modules/stickerModule/commandConfig.json +145 -0
  82. package/app/modules/stickerModule/stickerCommand.js +1 -1
  83. package/app/modules/stickerPackModule/AGENT.md +1 -1
  84. package/app/modules/stickerPackModule/autoPackCollectorService.js +5 -1
  85. package/app/modules/stickerPackModule/commandConfig.json +29 -0
  86. package/app/modules/stickerPackModule/semanticThemeClusterService.js +7 -6
  87. package/app/modules/stickerPackModule/stickerAutoPackByTagsRuntime.js +10 -9
  88. package/app/modules/stickerPackModule/stickerClassificationBackgroundRuntime.js +9 -8
  89. package/app/modules/stickerPackModule/stickerDomainEventConsumerRuntime.js +3 -2
  90. package/app/modules/stickerPackModule/stickerMarketplaceDriftService.js +2 -1
  91. package/app/modules/stickerPackModule/stickerPackCommandHandlers.js +80 -58
  92. package/app/modules/stickerPackModule/stickerPackMarketplaceService.js +2 -1
  93. package/app/modules/stickerPackModule/stickerPackRepository.js +2 -1
  94. package/app/modules/stickerPackModule/stickerPackScoreSnapshotRuntime.js +5 -4
  95. package/app/modules/stickerPackModule/stickerPackService.js +13 -6
  96. package/app/modules/stickerPackModule/stickerStorageService.js +3 -2
  97. package/app/modules/stickerPackModule/stickerWorkerPipelineRuntime.js +2 -1
  98. package/app/modules/systemMetricsModule/AGENT.md +1 -1
  99. package/app/modules/systemMetricsModule/commandConfig.json +29 -0
  100. package/app/modules/systemMetricsModule/pingCommand.js +6 -5
  101. package/app/modules/tiktokModule/AGENT.md +1 -1
  102. package/app/modules/tiktokModule/commandConfig.json +29 -0
  103. package/app/modules/tiktokModule/tiktokCommand.js +2 -1
  104. package/app/modules/userModule/AGENT.md +1 -1
  105. package/app/modules/userModule/commandConfig.json +29 -0
  106. package/app/modules/userModule/userCommand.js +72 -23
  107. package/app/modules/waifuPicsModule/AGENT.md +57 -27
  108. package/app/modules/waifuPicsModule/commandConfig.json +87 -0
  109. package/app/modules/waifuPicsModule/waifuPicsCommand.js +3 -2
  110. package/app/observability/metrics.js +136 -0
  111. package/app/services/ai/commandConfigEnrichmentService.js +229 -47
  112. package/app/services/ai/conversationRouterService.js +4 -3
  113. package/app/services/ai/geminiService.js +132 -7
  114. package/app/services/ai/geminiService.test.js +59 -2
  115. package/app/services/ai/globalModuleAiHelpService.js +3 -2
  116. package/app/services/ai/messageCommandExecutionService.js +2 -1
  117. package/app/services/ai/moduleAiHelpCoreService.js +45 -14
  118. package/app/services/ai/moduleToolExecutorService.js +3 -2
  119. package/app/services/ai/moduleToolRegistryService.js +2 -1
  120. package/app/services/ai/toolCandidateSelectorService.js +6 -5
  121. package/app/services/auth/googleWebLinkService.js +3 -2
  122. package/app/services/auth/whatsappLoginLinkService.js +3 -2
  123. package/app/services/external/pokeApiService.js +4 -3
  124. package/app/services/group/groupMetadataService.js +24 -1
  125. package/app/services/infra/dbWriteQueue.js +57 -26
  126. package/app/services/infra/featureFlagService.js +2 -1
  127. package/app/services/messaging/captchaService.js +3 -2
  128. package/app/services/messaging/newsBroadcastService.js +846 -29
  129. package/app/services/multiSession/assignmentBalancerService.js +457 -0
  130. package/app/services/multiSession/groupOwnershipRepository.js +381 -0
  131. package/app/services/multiSession/groupOwnershipService.js +890 -0
  132. package/app/services/multiSession/groupOwnershipService.test.js +309 -0
  133. package/app/services/multiSession/sessionRegistryService.js +293 -0
  134. package/app/services/sticker/stickerFocusService.js +11 -10
  135. package/app/store/aiPromptStore.js +36 -19
  136. package/app/store/conversationSessionStore.js +7 -6
  137. package/app/store/groupConfigStore.js +41 -5
  138. package/app/store/premiumUserStore.js +21 -7
  139. package/app/utils/antiLink/antiLinkModule.js +352 -16
  140. package/app/workers/aiHelperContinuousLearningWorker.js +512 -0
  141. package/app/workers/aiLearningWorker.js +6 -5
  142. package/app/workers/commandConfigEnrichmentWorker.js +4 -3
  143. package/database/index.js +14 -8
  144. package/database/migrations/20260307_d0_hardening_down.sql +1 -1
  145. package/database/migrations/20260314_d7_canonical_sender_down.sql +1 -1
  146. package/database/migrations/20260406_d30_security_analytics_down.sql +1 -1
  147. package/database/migrations/20260411_d35_group_community_metadata_down.sql +59 -0
  148. package/database/migrations/20260411_d35_group_community_metadata_up.sql +62 -0
  149. package/database/migrations/20260412_d36_system_config_tables_down.sql +32 -0
  150. package/database/migrations/20260412_d36_system_config_tables_up.sql +66 -0
  151. package/database/migrations/20260413_d37_group_user_warnings_down.sql +11 -0
  152. package/database/migrations/20260413_d37_group_user_warnings_up.sql +24 -0
  153. package/database/migrations/20260414_d38_multi_session_foundation_down.sql +72 -0
  154. package/database/migrations/20260414_d38_multi_session_foundation_up.sql +125 -0
  155. package/database/migrations/20260414_d39_multi_session_cutover_down.sql +103 -0
  156. package/database/migrations/20260414_d39_multi_session_cutover_up.sql +83 -0
  157. package/database/schema.sql +102 -1
  158. package/docker-compose.yml +4 -1
  159. package/docs/compliance/acceptable-use-policy-2026-03-07.md +1 -1
  160. package/docs/compliance/dpa-b2b-standard-2026-03-07.md +1 -1
  161. package/docs/compliance/privacy-policy-2026-03-07.md +4 -4
  162. package/docs/security/dsar-lgpd-runbook-2026-03-07.md +1 -1
  163. package/docs/security/incident-response-lgpd-anpd-runbook-2026-03-07.md +1 -1
  164. package/docs/security/network-hardening-runbook-2026-03-07.md +53 -0
  165. package/docs/security/omnizap-static-security-headers.conf +25 -0
  166. package/docs/wiki/Home.md +1 -1
  167. package/ecosystem.prod.config.cjs +32 -12
  168. package/index.js +57 -23
  169. package/observability/alert-rules.yml +20 -0
  170. package/observability/grafana/dashboards/omnizap-system-admin.json +229 -0
  171. package/observability/mysql-setup.sql +4 -4
  172. package/observability/system-admin-observability.md +26 -0
  173. package/package.json +20 -6
  174. package/public/apple-touch-icon.png +0 -0
  175. package/public/comandos/commands-catalog.json +2853 -3326
  176. package/public/favicon-16x16.png +0 -0
  177. package/public/favicon-32x32.png +0 -0
  178. package/public/favicon.ico +0 -0
  179. package/public/js/apps/apiDocsApp.js +3 -2
  180. package/public/js/apps/commandsReactApp.js +280 -99
  181. package/public/js/apps/createPackApp.js +11 -10
  182. package/public/js/apps/homeReactApp.js +181 -130
  183. package/public/js/apps/loginReactApp.js +1 -1
  184. package/public/js/apps/stickersApp.js +263 -110
  185. package/public/js/apps/termsReactApp.js +73 -24
  186. package/public/js/apps/userApp.js +4 -3
  187. package/public/js/apps/userPasswordResetReactApp.js +406 -0
  188. package/public/js/apps/userReactApp.js +355 -280
  189. package/public/js/apps/userSystemAdmReactApp.js +1506 -0
  190. package/public/pages/api-docs.html +1 -1
  191. package/public/pages/aup.html +2 -2
  192. package/public/pages/dpa.html +3 -3
  193. package/public/pages/licenca.html +4 -4
  194. package/public/pages/login.html +1 -1
  195. package/public/pages/notice-and-takedown.html +2 -2
  196. package/public/pages/politica-de-privacidade.html +6 -6
  197. package/public/pages/seo-bot-whatsapp-para-grupo.html +3 -3
  198. package/public/pages/seo-bot-whatsapp-sem-programar.html +3 -3
  199. package/public/pages/seo-como-automatizar-avisos-no-whatsapp.html +3 -3
  200. package/public/pages/seo-como-criar-comandos-whatsapp.html +3 -3
  201. package/public/pages/seo-como-evitar-spam-no-whatsapp.html +3 -3
  202. package/public/pages/seo-como-moderar-grupo-whatsapp.html +3 -3
  203. package/public/pages/seo-como-organizar-comunidade-whatsapp.html +3 -3
  204. package/public/pages/seo-melhor-bot-whatsapp-para-grupos.html +3 -3
  205. package/public/pages/stickers-admin.html +1 -1
  206. package/public/pages/stickers-create.html +1 -1
  207. package/public/pages/stickers.html +6 -6
  208. package/public/pages/suboperadores.html +2 -2
  209. package/public/pages/termos-de-uso-texto-integral.html +6 -6
  210. package/public/pages/termos-de-uso.html +3 -3
  211. package/public/pages/user-password-reset.html +4 -5
  212. package/public/pages/user-systemadm.html +9 -463
  213. package/public/pages/user.html +2 -2
  214. package/scripts/clear-whatsapp-session.sh +123 -0
  215. package/scripts/core-ai-mode.mjs +163 -0
  216. package/scripts/deploy.sh +11 -1
  217. package/scripts/email-broadcast-terms-update.mjs +2 -1
  218. package/scripts/enrich-command-config-ux-openai.mjs +492 -0
  219. package/scripts/generate-commands-catalog.mjs +166 -2
  220. package/scripts/generate-module-agents.mjs +2 -1
  221. package/scripts/generate-seo-satellite-pages.mjs +5 -4
  222. package/scripts/github-deploy-notify.mjs +2 -1
  223. package/scripts/github-release-notify.mjs +25 -10
  224. package/scripts/new-whatsapp-session.sh +317 -0
  225. package/scripts/release.sh +2 -19
  226. package/scripts/security-smoketest.mjs +6 -5
  227. package/scripts/security-web-surface-check.mjs +218 -0
  228. package/scripts/sticker-catalog-loadtest.mjs +5 -4
  229. package/server/auth/googleWebAuth/googleWebAuthService.js +8 -7
  230. package/server/auth/jwt/webJwtService.js +1 -1
  231. package/server/auth/stickerCatalogAuthContext.js +2 -1
  232. package/server/auth/termsAcceptance/termsAcceptanceHandler.js +2 -1
  233. package/server/auth/userPassword/userPasswordAuthService.js +2 -1
  234. package/server/auth/userPassword/userPasswordRecoveryService.js +4 -3
  235. package/server/auth/webAccount/webAccountHandlers.js +9 -10
  236. package/server/controllers/admin/adminPanelHandlers.js +267 -16
  237. package/server/controllers/admin/systemAdminController.js +267 -0
  238. package/server/controllers/seo/stickerCatalogSeoContext.js +10 -9
  239. package/server/controllers/sticker/nonCatalogHandlers.js +2 -1
  240. package/server/controllers/sticker/stickerCatalogController.js +23 -36
  241. package/server/controllers/system/contactController.js +9 -17
  242. package/server/controllers/system/githubController.js +3 -2
  243. package/server/controllers/system/stickerCatalogSystemContext.js +41 -19
  244. package/server/controllers/system/systemController.js +254 -1
  245. package/server/controllers/system/systemMetricsController.js +2 -1
  246. package/server/controllers/userController.js +6 -0
  247. package/server/email/emailTemplateService.js +5 -3
  248. package/server/http/httpServer.js +11 -6
  249. package/server/middleware/rateLimit.js +2 -1
  250. package/server/middleware/securityHeaders.js +20 -1
  251. package/server/routes/admin/systemAdminRouter.js +6 -0
  252. package/server/routes/indexRouter.js +30 -6
  253. package/server/routes/observability/grafanaProxyRouter.js +254 -0
  254. package/server/routes/static/staticPageRouter.js +27 -1
  255. package/server/utils/publicContact.js +31 -0
  256. package/utils/time/timeModule.js +135 -0
  257. package/utils/time/timeModule.test.js +65 -0
  258. package/utils/whatsapp/contactEnv.js +39 -0
  259. package/vite.config.mjs +7 -1
  260. package/public/assets/images/brand-icon-192.png +0 -0
  261. package/scripts/sync-readme-snapshot.mjs +0 -133
@@ -0,0 +1,406 @@
1
+ import React, { useEffect, useMemo, useState } from 'react';
2
+ import { createRoot } from 'react-dom/client';
3
+ import htm from 'htm';
4
+
5
+ const html = htm.bind(React.createElement);
6
+
7
+ const DEFAULT_API_BASE_PATH = '/api';
8
+ const DEFAULT_LOGIN_PATH = '/login';
9
+ const DEFAULT_PANEL_PATH = '/user';
10
+ const DEFAULT_PASSWORD_RESET_WEB_PATH = '/user/password-reset';
11
+
12
+ const PASSWORD_RECOVERY_SESSION_QUERY_KEYS = Object.freeze(['session_token', 'recovery_session_token', 'password_recovery_session', 'session', 'token']);
13
+
14
+ const normalizeRoutePath = (value, fallback) => {
15
+ const raw = String(value || '').trim();
16
+ if (!raw) return fallback;
17
+ if (!raw.startsWith('/')) return fallback;
18
+ if (/^\/\//.test(raw)) return fallback;
19
+ return raw;
20
+ };
21
+
22
+ const normalizeSessionToken = (value) =>
23
+ String(value || '')
24
+ .trim()
25
+ .slice(0, 4096);
26
+
27
+ const normalizeCode = (value) =>
28
+ String(value || '')
29
+ .replace(/\D+/g, '')
30
+ .slice(0, 6);
31
+
32
+ const readSessionTokenFromLocation = () => {
33
+ const url = new URL(window.location.href);
34
+ for (const key of PASSWORD_RECOVERY_SESSION_QUERY_KEYS) {
35
+ const token = normalizeSessionToken(url.searchParams.get(key));
36
+ if (token) return token;
37
+ }
38
+ return '';
39
+ };
40
+
41
+ const persistSessionTokenInUrl = (sessionToken) => {
42
+ const normalizedToken = normalizeSessionToken(sessionToken);
43
+ if (!normalizedToken) return;
44
+ const url = new URL(window.location.href);
45
+ const current = normalizeSessionToken(url.searchParams.get('session_token'));
46
+ if (current === normalizedToken) return;
47
+ url.searchParams.set('session_token', normalizedToken);
48
+ window.history.replaceState(null, '', `${url.pathname}${url.search}`);
49
+ };
50
+
51
+ const formatDateTime = (value) => {
52
+ const parsedMs = Date.parse(String(value || ''));
53
+ if (!Number.isFinite(parsedMs)) return 'n/d';
54
+ return new Intl.DateTimeFormat('pt-BR', { dateStyle: 'short', timeStyle: 'short' }).format(new Date(parsedMs));
55
+ };
56
+
57
+ const buildLoginRedirectPath = (loginPath, nextPath) => {
58
+ const safeLoginPath = normalizeRoutePath(loginPath, DEFAULT_LOGIN_PATH);
59
+ const safeNextPath = normalizeRoutePath(nextPath, DEFAULT_PANEL_PATH);
60
+ const loginUrl = new URL(safeLoginPath, window.location.origin);
61
+ loginUrl.searchParams.set('next', safeNextPath);
62
+ return `${loginUrl.pathname}${loginUrl.search}`;
63
+ };
64
+
65
+ const resolvePasswordResetConfig = (rootElement) => {
66
+ const apiBasePath = String(rootElement?.dataset?.apiBasePath || DEFAULT_API_BASE_PATH).trim() || DEFAULT_API_BASE_PATH;
67
+ const loginPath = normalizeRoutePath(rootElement?.dataset?.loginPath, DEFAULT_LOGIN_PATH);
68
+ const panelPath = normalizeRoutePath(rootElement?.dataset?.panelPath, DEFAULT_PANEL_PATH);
69
+ const passwordResetWebPath = normalizeRoutePath(rootElement?.dataset?.passwordResetWebPath, DEFAULT_PASSWORD_RESET_WEB_PATH);
70
+ return {
71
+ apiBasePath,
72
+ loginPath,
73
+ panelPath,
74
+ passwordResetWebPath,
75
+ };
76
+ };
77
+
78
+ const createPasswordResetApi = (apiBasePath) => {
79
+ const sessionPath = `${apiBasePath}/auth/password/recovery/session`;
80
+ const sessionRequestPath = `${sessionPath}/request`;
81
+ const sessionVerifyPath = `${sessionPath}/verify`;
82
+
83
+ const fetchJson = async (url, init = {}, { sessionToken = '' } = {}) => {
84
+ const headers = {
85
+ ...(init?.headers || {}),
86
+ };
87
+ const normalizedSessionToken = normalizeSessionToken(sessionToken);
88
+ if (normalizedSessionToken) {
89
+ headers['x-password-recovery-session'] = normalizedSessionToken;
90
+ }
91
+
92
+ const response = await fetch(url, {
93
+ credentials: 'include',
94
+ ...init,
95
+ headers,
96
+ });
97
+
98
+ let payload = null;
99
+ try {
100
+ payload = await response.json();
101
+ } catch {
102
+ payload = null;
103
+ }
104
+
105
+ if (!response.ok) {
106
+ const error = new Error(payload?.error || `Falha HTTP ${response.status}`);
107
+ error.statusCode = response.status;
108
+ error.code = payload?.code || null;
109
+ error.details = payload?.details || null;
110
+ throw error;
111
+ }
112
+ return payload || {};
113
+ };
114
+
115
+ return {
116
+ createSession: () => fetchJson(sessionPath, { method: 'POST' }),
117
+ getSessionStatus: (sessionToken) => fetchJson(sessionPath, { method: 'GET' }, { sessionToken }),
118
+ requestCode: (sessionToken) =>
119
+ fetchJson(
120
+ sessionRequestPath,
121
+ {
122
+ method: 'POST',
123
+ headers: {
124
+ 'Content-Type': 'application/json; charset=utf-8',
125
+ },
126
+ body: JSON.stringify({}),
127
+ },
128
+ { sessionToken },
129
+ ),
130
+ verifyCode: (sessionToken, { code = '', password = '' } = {}) =>
131
+ fetchJson(
132
+ sessionVerifyPath,
133
+ {
134
+ method: 'POST',
135
+ headers: {
136
+ 'Content-Type': 'application/json; charset=utf-8',
137
+ },
138
+ body: JSON.stringify({
139
+ code: normalizeCode(code),
140
+ password: String(password || ''),
141
+ }),
142
+ },
143
+ { sessionToken },
144
+ ),
145
+ };
146
+ };
147
+
148
+ const PasswordResetApp = ({ config }) => {
149
+ const api = useMemo(() => createPasswordResetApi(config.apiBasePath), [config.apiBasePath]);
150
+
151
+ const [bootstrapAttempt, setBootstrapAttempt] = useState(0);
152
+ const [isBootstrapping, setIsBootstrapping] = useState(true);
153
+ const [requiresLogin, setRequiresLogin] = useState(false);
154
+ const [sessionToken, setSessionToken] = useState('');
155
+ const [maskedEmail, setMaskedEmail] = useState('');
156
+ const [expiresAt, setExpiresAt] = useState('');
157
+ const [errorMessage, setErrorMessage] = useState('');
158
+ const [successMessage, setSuccessMessage] = useState('');
159
+ const [requestBusy, setRequestBusy] = useState(false);
160
+ const [verifyBusy, setVerifyBusy] = useState(false);
161
+ const [code, setCode] = useState('');
162
+ const [password, setPassword] = useState('');
163
+ const [passwordConfirm, setPasswordConfirm] = useState('');
164
+
165
+ useEffect(() => {
166
+ let active = true;
167
+
168
+ const bootstrap = async () => {
169
+ setIsBootstrapping(true);
170
+ setRequiresLogin(false);
171
+ setErrorMessage('');
172
+ setSuccessMessage('');
173
+
174
+ let resolvedSessionToken = readSessionTokenFromLocation();
175
+ if (!resolvedSessionToken) {
176
+ try {
177
+ const createPayload = await api.createSession();
178
+ const sessionData = createPayload?.data || {};
179
+ resolvedSessionToken = normalizeSessionToken(sessionData?.session_token);
180
+ if (!resolvedSessionToken) {
181
+ throw new Error('Sessão de redefinição não foi criada corretamente.');
182
+ }
183
+ if (!active) return;
184
+ persistSessionTokenInUrl(resolvedSessionToken);
185
+ setMaskedEmail(String(sessionData?.masked_email || '').trim());
186
+ setExpiresAt(String(sessionData?.expires_at || '').trim());
187
+ } catch (error) {
188
+ if (!active) return;
189
+ setRequiresLogin(Number(error?.statusCode || 0) === 401);
190
+ setErrorMessage(error?.message || 'Não foi possível iniciar a sessão de redefinição.');
191
+ setIsBootstrapping(false);
192
+ return;
193
+ }
194
+ }
195
+
196
+ try {
197
+ const statusPayload = await api.getSessionStatus(resolvedSessionToken);
198
+ const statusData = statusPayload?.data || {};
199
+ if (!active) return;
200
+ setSessionToken(resolvedSessionToken);
201
+ setMaskedEmail(String(statusData?.masked_email || '').trim());
202
+ setExpiresAt(String(statusData?.expires_at || '').trim());
203
+ setErrorMessage('');
204
+ } catch (error) {
205
+ if (!active) return;
206
+ setErrorMessage(error?.message || 'Sessão de redefinição inválida ou expirada.');
207
+ setRequiresLogin(Number(error?.statusCode || 0) === 401);
208
+ } finally {
209
+ if (active) {
210
+ setIsBootstrapping(false);
211
+ }
212
+ }
213
+ };
214
+
215
+ void bootstrap();
216
+ return () => {
217
+ active = false;
218
+ };
219
+ }, [api, bootstrapAttempt]);
220
+
221
+ const handleSendCode = async () => {
222
+ if (!sessionToken || requestBusy) return;
223
+ setErrorMessage('');
224
+ setSuccessMessage('');
225
+ setRequestBusy(true);
226
+
227
+ try {
228
+ const payload = await api.requestCode(sessionToken);
229
+ const data = payload?.data || {};
230
+ const emailHint = String(data?.masked_email || maskedEmail || '').trim();
231
+ const cooldownActive = Boolean(data?.cooldown_active);
232
+ const expiresInSeconds = Number(data?.expires_in_seconds || 0);
233
+ const expiresInMinutes = Number.isFinite(expiresInSeconds) && expiresInSeconds > 0 ? Math.max(1, Math.ceil(expiresInSeconds / 60)) : null;
234
+
235
+ let message = emailHint ? `Código enviado para ${emailHint}.` : 'Código de verificação enviado.';
236
+ if (cooldownActive) {
237
+ message = 'Já existe um código ativo. Use o código mais recente enviado por e-mail.';
238
+ } else if (expiresInMinutes) {
239
+ message = `${message} Validade aproximada: ${expiresInMinutes} minuto(s).`;
240
+ }
241
+ setSuccessMessage(message);
242
+ } catch (error) {
243
+ setErrorMessage(error?.message || 'Não foi possível enviar o código de verificação.');
244
+ } finally {
245
+ setRequestBusy(false);
246
+ }
247
+ };
248
+
249
+ const handleVerify = async (event) => {
250
+ event.preventDefault();
251
+ if (!sessionToken || verifyBusy) return;
252
+
253
+ const normalizedCode = normalizeCode(code);
254
+ const safePassword = String(password || '');
255
+ const safePasswordConfirm = String(passwordConfirm || '');
256
+
257
+ if (!/^\d{6}$/.test(normalizedCode)) {
258
+ setErrorMessage('Informe o código de 6 dígitos enviado por e-mail.');
259
+ setSuccessMessage('');
260
+ return;
261
+ }
262
+
263
+ if (safePassword.trim().length < 8) {
264
+ setErrorMessage('Use uma senha com pelo menos 8 caracteres.');
265
+ setSuccessMessage('');
266
+ return;
267
+ }
268
+
269
+ if (safePassword !== safePasswordConfirm) {
270
+ setErrorMessage('A confirmação da senha não confere.');
271
+ setSuccessMessage('');
272
+ return;
273
+ }
274
+
275
+ setErrorMessage('');
276
+ setSuccessMessage('');
277
+ setVerifyBusy(true);
278
+
279
+ try {
280
+ const payload = await api.verifyCode(sessionToken, {
281
+ code: normalizedCode,
282
+ password: safePassword,
283
+ });
284
+ const data = payload?.data || {};
285
+ const isAuthenticated = Boolean(data?.session?.authenticated);
286
+ setSuccessMessage('Senha atualizada com sucesso. Redirecionando...');
287
+ setPassword('');
288
+ setPasswordConfirm('');
289
+ setCode('');
290
+
291
+ const destination = isAuthenticated ? config.panelPath : buildLoginRedirectPath(config.loginPath, config.panelPath);
292
+ window.setTimeout(() => {
293
+ window.location.assign(destination);
294
+ }, 900);
295
+ } catch (error) {
296
+ setErrorMessage(error?.message || 'Falha ao validar o código.');
297
+ } finally {
298
+ setVerifyBusy(false);
299
+ }
300
+ };
301
+
302
+ const loginRedirectPath = useMemo(() => buildLoginRedirectPath(config.loginPath, config.passwordResetWebPath), [config.loginPath, config.passwordResetWebPath]);
303
+
304
+ return html`
305
+ <div className="min-h-screen bg-base-100 font-sans selection:bg-primary selection:text-primary-content">
306
+ <header className="sticky top-0 z-40 border-b border-base-200 bg-base-100/80 backdrop-blur-xl">
307
+ <div className="container mx-auto px-4">
308
+ <div className="flex h-16 items-center justify-between gap-4">
309
+ <a href="/" className="flex items-center gap-2.5 hover:opacity-80 transition-opacity">
310
+ <img src="/assets/images/brand-logo-128.webp" className="w-8 h-8 rounded-xl shadow-sm" alt="Logo" />
311
+ <span className="text-base sm:text-lg font-black tracking-tight">OmniZap<span className="text-primary">.</span></span>
312
+ </a>
313
+ <a href=${config.panelPath} className="btn btn-ghost btn-sm h-9 min-h-0 rounded-xl border border-base-300 hover:border-primary transition-all px-3">
314
+ <span className="text-[10px] sm:text-xs font-bold uppercase tracking-wider">Voltar ao Painel</span>
315
+ </a>
316
+ </div>
317
+ </div>
318
+ </header>
319
+
320
+ <main className="container mx-auto px-4 py-12 lg:py-20 flex flex-col items-center">
321
+ <div className="w-full max-w-md space-y-8">
322
+ <div className="text-center space-y-4">
323
+ <div className="inline-flex items-center gap-2 px-3 py-1.5 rounded-full bg-primary/10 border border-primary/20 text-primary text-[10px] font-bold uppercase tracking-widest">Segurança da Conta</div>
324
+ <h1 className="text-4xl font-black tracking-tight text-balance">Redefinir <span className="text-transparent bg-clip-text bg-gradient-to-r from-primary to-secondary">Senha</span></h1>
325
+ <p className="text-base-content/60 leading-relaxed">Solicite um código por e-mail e confirme sua nova senha com validação segura.</p>
326
+ </div>
327
+
328
+ <div className="glass-card rounded-[2.5rem] p-8 space-y-6 relative overflow-hidden">
329
+ <div className="absolute top-0 right-0 w-32 h-32 bg-primary/5 rounded-full blur-3xl -mr-16 -mt-16"></div>
330
+
331
+ ${isBootstrapping
332
+ ? html`
333
+ <div className="relative z-10 py-8 text-center space-y-4">
334
+ <span className="loading loading-ring loading-lg text-primary"></span>
335
+ <p className="text-sm text-base-content/60">Preparando sessão de redefinição...</p>
336
+ </div>
337
+ `
338
+ : html`
339
+ <div className="relative z-10 space-y-6">
340
+ ${sessionToken
341
+ ? html`
342
+ <div className="rounded-2xl border border-base-300 bg-base-200/50 p-4 space-y-2">
343
+ <p className="text-[10px] font-bold uppercase tracking-widest text-base-content/40">Sessão ativa</p>
344
+ <p className="text-sm text-base-content/80">E-mail de destino: <b>${maskedEmail || 'não informado'}</b></p>
345
+ <p className="text-xs text-base-content/55">Expira em: <b>${formatDateTime(expiresAt)}</b></p>
346
+ </div>
347
+
348
+ <button type="button" className="btn btn-outline btn-primary btn-block rounded-2xl h-12 font-bold" disabled=${requestBusy || verifyBusy} onClick=${handleSendCode}>${requestBusy ? 'Enviando código...' : 'Enviar código por e-mail'}</button>
349
+
350
+ <form className="space-y-4" onSubmit=${handleVerify}>
351
+ <div className="form-control gap-2">
352
+ <label className="label py-0">
353
+ <span className="label-text text-[11px] font-bold uppercase tracking-widest text-base-content/50">Código de verificação</span>
354
+ </label>
355
+ <input type="text" inputmode="numeric" maxlength="6" value=${code} onInput=${(event) => setCode(normalizeCode(event.target.value))} placeholder="000000" className="input input-bordered h-12 rounded-2xl font-mono tracking-[0.3em] text-center text-lg" />
356
+ </div>
357
+ <div className="form-control gap-2">
358
+ <label className="label py-0">
359
+ <span className="label-text text-[11px] font-bold uppercase tracking-widest text-base-content/50">Nova senha</span>
360
+ </label>
361
+ <input type="password" autocomplete="new-password" value=${password} onInput=${(event) => setPassword(String(event.target.value || ''))} placeholder="Pelo menos 8 caracteres" className="input input-bordered h-12 rounded-2xl" />
362
+ </div>
363
+ <div className="form-control gap-2">
364
+ <label className="label py-0">
365
+ <span className="label-text text-[11px] font-bold uppercase tracking-widest text-base-content/50">Confirmar senha</span>
366
+ </label>
367
+ <input type="password" autocomplete="new-password" value=${passwordConfirm} onInput=${(event) => setPasswordConfirm(String(event.target.value || ''))} placeholder="Repita a nova senha" className="input input-bordered h-12 rounded-2xl" />
368
+ </div>
369
+ <button type="submit" className="btn btn-primary btn-block h-12 rounded-2xl font-black uppercase tracking-widest text-xs" disabled=${verifyBusy || requestBusy}>${verifyBusy ? 'Validando...' : 'Confirmar nova senha'}</button>
370
+ </form>
371
+ `
372
+ : html`
373
+ <div className="alert alert-warning rounded-2xl bg-warning/15 border border-warning/30 text-warning-content text-sm">
374
+ <span>Não foi possível carregar uma sessão de redefinição válida.</span>
375
+ </div>
376
+ <button type="button" className="btn btn-outline btn-block rounded-2xl" onClick=${() => setBootstrapAttempt((value) => value + 1)}>Tentar novamente</button>
377
+ `}
378
+ ${errorMessage
379
+ ? html`
380
+ <div className="alert alert-error rounded-2xl bg-error/20 border-none text-error-content text-sm">
381
+ <span>${errorMessage}</span>
382
+ </div>
383
+ `
384
+ : null}
385
+ ${successMessage
386
+ ? html`
387
+ <div className="alert alert-success rounded-2xl bg-success/20 border-none text-success-content text-sm">
388
+ <span>${successMessage}</span>
389
+ </div>
390
+ `
391
+ : null}
392
+ ${requiresLogin ? html` <a href=${loginRedirectPath} className="btn btn-ghost btn-block rounded-2xl border border-base-300"> Entrar para continuar </a> ` : null}
393
+ </div>
394
+ `}
395
+ </div>
396
+ </div>
397
+ </main>
398
+ </div>
399
+ `;
400
+ };
401
+
402
+ const rootElement = document.getElementById('user-password-reset-root');
403
+ if (rootElement) {
404
+ const config = resolvePasswordResetConfig(rootElement);
405
+ createRoot(rootElement).render(html`<${PasswordResetApp} config=${config} />`);
406
+ }