@resolveio/server-lib 22.2.33 → 22.2.34

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 (649) hide show
  1. package/.github/workflows/ai-assistant-nightly-eval.yml +224 -0
  2. package/.github/workflows/ai-assistant-pr-guardrails.yml +60 -0
  3. package/.nodemon.json +5 -0
  4. package/.vscode/settings.json +21 -0
  5. package/AGENTS.md +179 -0
  6. package/README.md +22 -0
  7. package/build_package.sh +5 -0
  8. package/compileDTS.pl +64 -0
  9. package/docs/ai-assistant-nightly-eval.md +65 -0
  10. package/docs/ai-assistant-preflight-checklist.md +23 -0
  11. package/docs/ai-assistant-report-builder-bridge-playbook.md +115 -0
  12. package/eslint-plugin-custom/index.js +7 -0
  13. package/eslint-plugin-custom/rules/no-filter-zero-index.js +44 -0
  14. package/eslint.config.js +103 -0
  15. package/gulpfile.js +216 -0
  16. package/methodAndPublicationListGenerator.py +319 -0
  17. package/mongodbensurers.js +2 -0
  18. package/mongostop.js +3 -0
  19. package/package.json +1 -1
  20. package/settings.development.json +25 -0
  21. package/settings.development.redacted.json +25 -0
  22. package/src/.env +12 -0
  23. package/src/ai/assistant-core-heuristics.ts +577 -0
  24. package/src/client-server-app.ts +12 -0
  25. package/src/collections/ai-terminal-conversation.collection.ts +91 -0
  26. package/src/collections/ai-terminal-issue-report.collection.ts +99 -0
  27. package/src/collections/ai-terminal-message.collection.ts +77 -0
  28. package/src/collections/app-setting.collection.ts +104 -0
  29. package/src/collections/app-status.collection.ts +58 -0
  30. package/src/collections/communication-metric.collection.ts +84 -0
  31. package/src/collections/counter.collection.ts +56 -0
  32. package/src/collections/cron-job-history.collection.ts +94 -0
  33. package/src/collections/cron-job.collection.ts +92 -0
  34. package/src/collections/customer-notification.collection.ts +131 -0
  35. package/src/collections/customer-portal-password.collection.ts +76 -0
  36. package/src/collections/email-history.collection.ts +121 -0
  37. package/src/collections/email-verified.collection.ts +61 -0
  38. package/src/collections/file.collection.ts +74 -0
  39. package/src/collections/flag-update.collection.ts +57 -0
  40. package/src/collections/flag.collection.ts +57 -0
  41. package/src/collections/log-method-latency.collection.ts +77 -0
  42. package/src/collections/log-subscription.collection.ts +80 -0
  43. package/src/collections/log.collection.ts +93 -0
  44. package/src/collections/logged-in-users.collection.ts +67 -0
  45. package/src/collections/monitor-cpu.collection.ts +65 -0
  46. package/src/collections/monitor-function.collection.ts +74 -0
  47. package/src/collections/monitor-memory.collection.ts +77 -0
  48. package/src/collections/monitor-mongo.collection.ts +71 -0
  49. package/src/collections/notification.collection.ts +57 -0
  50. package/src/collections/openai-usage-ledger.collection.ts +77 -0
  51. package/src/collections/report-builder-dashboard-builder.collection.ts +109 -0
  52. package/src/collections/report-builder-library.collection.ts +89 -0
  53. package/src/collections/report-builder-report.collection.ts +180 -0
  54. package/src/collections/user-group.collection.ts +89 -0
  55. package/src/collections/user-guide.collection.ts +57 -0
  56. package/src/collections/user.collection.ts +181 -0
  57. package/src/cron/cron.ts +117 -0
  58. package/src/fixtures/cron-jobs.ts +95 -0
  59. package/src/fixtures/init.ts +35 -0
  60. package/src/http/auth.ts +764 -0
  61. package/src/http/health.ts +7 -0
  62. package/src/http/home.ts +90 -0
  63. package/src/http/slow-query-publication.ts +49 -0
  64. package/src/index.ts +1 -0
  65. package/src/managers/communication-metric.manager.ts +82 -0
  66. package/src/managers/cron.manager.ts +333 -0
  67. package/src/managers/customer-notification-content.manager.ts +236 -0
  68. package/src/managers/diagnostic-manager-bootstrap.ts +165 -0
  69. package/src/managers/error-auto-fix.manager.ts +2767 -0
  70. package/src/managers/local-log.manager.ts +113 -0
  71. package/src/managers/method.manager.ts +1557 -0
  72. package/src/managers/mongo.manager.ts +4566 -0
  73. package/src/managers/monitor.manager.ts +489 -0
  74. package/src/managers/openai-usage-ledger.manager.ts +116 -0
  75. package/src/managers/slow-query-verifier.manager.ts +3590 -0
  76. package/src/managers/slow-query.manager.ts +519 -0
  77. package/src/managers/subscription.manager.ts +3120 -0
  78. package/src/managers/websocket.manager.ts +746 -0
  79. package/src/managers/worker-dispatcher.manager.ts +1318 -0
  80. package/src/managers/worker-server.manager.ts +468 -0
  81. package/src/methods/accounts.ts +532 -0
  82. package/src/methods/ai-terminal.ts +25505 -0
  83. package/src/methods/app-settings.ts +114 -0
  84. package/src/methods/aws.ts +646 -0
  85. package/src/methods/collections.ts +544 -0
  86. package/src/methods/counters.ts +67 -0
  87. package/src/methods/cron-jobs.ts +2610 -0
  88. package/src/methods/customer-notifications.ts +458 -0
  89. package/src/methods/diagnostics.ts +447 -0
  90. package/src/methods/flag-updates.ts +7 -0
  91. package/src/methods/flags.ts +7 -0
  92. package/src/methods/logs.ts +656 -0
  93. package/src/methods/mongo-explorer.ts +1883 -0
  94. package/src/methods/monitor.ts +540 -0
  95. package/src/methods/pdf.ts +1210 -0
  96. package/src/methods/publications.ts +128 -0
  97. package/src/methods/report-builder.ts +3305 -0
  98. package/src/methods/support.ts +210 -0
  99. package/src/models/ai-terminal-conversation.model.ts +19 -0
  100. package/src/models/ai-terminal-issue-report.model.ts +21 -0
  101. package/src/models/ai-terminal-message.model.ts +24 -0
  102. package/src/models/app-setting.model.ts +17 -0
  103. package/{models/app-status.model.d.ts → src/models/app-status.model.ts} +3 -2
  104. package/{models/billing-logged-in-users.model.d.ts → src/models/billing-logged-in-users.model.ts} +5 -4
  105. package/src/models/collection-document.model.ts +24 -0
  106. package/src/models/communication-metric.model.ts +23 -0
  107. package/{models/counter.model.d.ts → src/models/counter.model.ts} +4 -3
  108. package/src/models/cron-job-history.model.ts +16 -0
  109. package/src/models/cron-job.model.ts +15 -0
  110. package/src/models/customer-notification.model.ts +28 -0
  111. package/src/models/customer-portal-password.model.ts +12 -0
  112. package/src/models/dialog.model.ts +25 -0
  113. package/{models/email-history.model.js → src/models/email-history.model.ts} +34 -4
  114. package/{models/email-verified.model.d.ts → src/models/email-verified.model.ts} +6 -5
  115. package/{models/file.model.d.ts → src/models/file.model.ts} +8 -7
  116. package/{models/flag-update.model.d.ts → src/models/flag-update.model.ts} +4 -3
  117. package/{models/flag.model.d.ts → src/models/flag.model.ts} +4 -3
  118. package/src/models/log-method-latency.model.ts +11 -0
  119. package/{models/log-subscription.model.d.ts → src/models/log-subscription.model.ts} +11 -9
  120. package/src/models/log.model.ts +19 -0
  121. package/{models/logged-in-users.model.d.ts → src/models/logged-in-users.model.ts} +6 -5
  122. package/{models/method-response.model.d.ts → src/models/method-response.model.ts} +7 -6
  123. package/src/models/method.model.ts +23 -0
  124. package/{models/monitor-cpu.model.d.ts → src/models/monitor-cpu.model.ts} +9 -7
  125. package/src/models/monitor-function.model.ts +16 -0
  126. package/src/models/monitor-memory.model.ts +17 -0
  127. package/src/models/monitor-mongo.model.ts +15 -0
  128. package/{models/notification.model.d.ts → src/models/notification.model.ts} +6 -4
  129. package/src/models/openai-usage-ledger.model.ts +16 -0
  130. package/src/models/pagination.model.ts +35 -0
  131. package/src/models/permission.model.ts +14 -0
  132. package/src/models/report-builder-dashboard-builder.model.ts +29 -0
  133. package/src/models/report-builder-library.model.ts +20 -0
  134. package/src/models/report-builder-report.model.ts +135 -0
  135. package/src/models/report-builder.model.ts +68 -0
  136. package/src/models/select-data-label.model.ts +9 -0
  137. package/src/models/server-message.model.ts +31 -0
  138. package/src/models/slow-query-report.model.ts +23 -0
  139. package/src/models/subscription.model.ts +73 -0
  140. package/src/models/support-ticket.model.ts +96 -0
  141. package/src/models/user-group.model.ts +24 -0
  142. package/{models/user-guide.model.d.ts → src/models/user-guide.model.ts} +5 -4
  143. package/src/models/user.model.ts +96 -0
  144. package/src/private/images/ResolveIO.png +0 -0
  145. package/src/publications/ai-terminal.ts +73 -0
  146. package/src/publications/app-settings.ts +25 -0
  147. package/src/publications/app-status.ts +13 -0
  148. package/src/publications/cron-jobs.ts +29 -0
  149. package/src/publications/customer-notifications.ts +101 -0
  150. package/src/publications/files.ts +33 -0
  151. package/src/publications/flags-update.ts +19 -0
  152. package/src/publications/flags.ts +19 -0
  153. package/src/publications/logs.ts +163 -0
  154. package/src/publications/notifications.ts +13 -0
  155. package/src/publications/report-builder-dashboard-builders.ts +39 -0
  156. package/src/publications/report-builder-libraries.ts +41 -0
  157. package/src/publications/report-builder-reports.ts +47 -0
  158. package/src/publications/super-admin.ts +13 -0
  159. package/src/publications/user-groups.ts +12 -0
  160. package/src/publications/user-guides.ts +12 -0
  161. package/src/resolveio-server-app.ts +617 -0
  162. package/src/server-app.ts +2616 -0
  163. package/src/services/codex-client.ts +1117 -0
  164. package/src/services/openai-client.ts +265 -0
  165. package/src/types/error-report.ts +26 -0
  166. package/src/types/js-tiktoken.d.ts +11 -0
  167. package/src/types/slow-query-report.ts +28 -0
  168. package/src/util/common.ts +649 -0
  169. package/src/util/customer-portal-password.ts +183 -0
  170. package/src/util/error-reporter.ts +332 -0
  171. package/src/util/error-tracking.ts +79 -0
  172. package/src/util/report-builder-unwinds.ts +180 -0
  173. package/src/util/schema-report-builder.ts +448 -0
  174. package/src/util/slow-query-reporter.ts +216 -0
  175. package/src/util/subscription-dependency-context.ts +1096 -0
  176. package/src/util/tokenizer.ts +38 -0
  177. package/src/workers/codex-runner.worker.ts +142 -0
  178. package/start_server.sh +5 -0
  179. package/tests/ai-assistant-corpus-build.ts +484 -0
  180. package/tests/ai-assistant-corpus-replay-e2e.ts +773 -0
  181. package/tests/ai-assistant-data-parity-e2e.ts +2018 -0
  182. package/tests/ai-assistant-eval-triage.ts +831 -0
  183. package/tests/ai-assistant-openai-e2e.ts +1061 -0
  184. package/tests/ai-assistant-openai-git-e2e.ts +155 -0
  185. package/tests/ai-assistant-preflight-matrix.ts +215 -0
  186. package/tests/ai-assistant-routing-eval.test.ts +560 -0
  187. package/tests/ai-assistant-snf-live-eval.ts +921 -0
  188. package/tests/ai-assistant-utils.test.ts +2165 -0
  189. package/tests/error-reporter.test.ts +145 -0
  190. package/tests/report-builder-linking.test.ts +79 -0
  191. package/tests/subscription-connect-race.test.ts +157 -0
  192. package/tests/subscription-dependency-context.test.ts +324 -0
  193. package/tests/subscription-manager-collection-tracking.test.ts +86 -0
  194. package/tests/subscription-manager-invalidation.test.ts +85 -0
  195. package/tsconfig.json +34 -0
  196. package/ai/assistant-core-heuristics.d.ts +0 -11
  197. package/ai/assistant-core-heuristics.js +0 -531
  198. package/ai/assistant-core-heuristics.js.map +0 -1
  199. package/client-server-app.d.ts +0 -1
  200. package/client-server-app.js +0 -68
  201. package/client-server-app.js.map +0 -1
  202. package/collections/ai-terminal-conversation.collection.d.ts +0 -2
  203. package/collections/ai-terminal-conversation.collection.js +0 -140
  204. package/collections/ai-terminal-conversation.collection.js.map +0 -1
  205. package/collections/ai-terminal-issue-report.collection.d.ts +0 -2
  206. package/collections/ai-terminal-issue-report.collection.js +0 -148
  207. package/collections/ai-terminal-issue-report.collection.js.map +0 -1
  208. package/collections/ai-terminal-message.collection.d.ts +0 -2
  209. package/collections/ai-terminal-message.collection.js +0 -121
  210. package/collections/ai-terminal-message.collection.js.map +0 -1
  211. package/collections/app-setting.collection.d.ts +0 -3
  212. package/collections/app-setting.collection.js +0 -103
  213. package/collections/app-setting.collection.js.map +0 -1
  214. package/collections/app-status.collection.d.ts +0 -3
  215. package/collections/app-status.collection.js +0 -57
  216. package/collections/app-status.collection.js.map +0 -1
  217. package/collections/communication-metric.collection.d.ts +0 -2
  218. package/collections/communication-metric.collection.js +0 -133
  219. package/collections/communication-metric.collection.js.map +0 -1
  220. package/collections/counter.collection.d.ts +0 -3
  221. package/collections/counter.collection.js +0 -56
  222. package/collections/counter.collection.js.map +0 -1
  223. package/collections/cron-job-history.collection.d.ts +0 -3
  224. package/collections/cron-job-history.collection.js +0 -137
  225. package/collections/cron-job-history.collection.js.map +0 -1
  226. package/collections/cron-job.collection.d.ts +0 -3
  227. package/collections/cron-job.collection.js +0 -92
  228. package/collections/cron-job.collection.js.map +0 -1
  229. package/collections/customer-notification.collection.d.ts +0 -3
  230. package/collections/customer-notification.collection.js +0 -130
  231. package/collections/customer-notification.collection.js.map +0 -1
  232. package/collections/customer-portal-password.collection.d.ts +0 -3
  233. package/collections/customer-portal-password.collection.js +0 -75
  234. package/collections/customer-portal-password.collection.js.map +0 -1
  235. package/collections/email-history.collection.d.ts +0 -3
  236. package/collections/email-history.collection.js +0 -121
  237. package/collections/email-history.collection.js.map +0 -1
  238. package/collections/email-verified.collection.d.ts +0 -3
  239. package/collections/email-verified.collection.js +0 -61
  240. package/collections/email-verified.collection.js.map +0 -1
  241. package/collections/file.collection.d.ts +0 -3
  242. package/collections/file.collection.js +0 -74
  243. package/collections/file.collection.js.map +0 -1
  244. package/collections/flag-update.collection.d.ts +0 -3
  245. package/collections/flag-update.collection.js +0 -57
  246. package/collections/flag-update.collection.js.map +0 -1
  247. package/collections/flag.collection.d.ts +0 -3
  248. package/collections/flag.collection.js +0 -57
  249. package/collections/flag.collection.js.map +0 -1
  250. package/collections/log-method-latency.collection.d.ts +0 -3
  251. package/collections/log-method-latency.collection.js +0 -77
  252. package/collections/log-method-latency.collection.js.map +0 -1
  253. package/collections/log-subscription.collection.d.ts +0 -3
  254. package/collections/log-subscription.collection.js +0 -80
  255. package/collections/log-subscription.collection.js.map +0 -1
  256. package/collections/log.collection.d.ts +0 -3
  257. package/collections/log.collection.js +0 -93
  258. package/collections/log.collection.js.map +0 -1
  259. package/collections/logged-in-users.collection.d.ts +0 -3
  260. package/collections/logged-in-users.collection.js +0 -67
  261. package/collections/logged-in-users.collection.js.map +0 -1
  262. package/collections/monitor-cpu.collection.d.ts +0 -3
  263. package/collections/monitor-cpu.collection.js +0 -65
  264. package/collections/monitor-cpu.collection.js.map +0 -1
  265. package/collections/monitor-function.collection.d.ts +0 -3
  266. package/collections/monitor-function.collection.js +0 -74
  267. package/collections/monitor-function.collection.js.map +0 -1
  268. package/collections/monitor-memory.collection.d.ts +0 -3
  269. package/collections/monitor-memory.collection.js +0 -77
  270. package/collections/monitor-memory.collection.js.map +0 -1
  271. package/collections/monitor-mongo.collection.d.ts +0 -3
  272. package/collections/monitor-mongo.collection.js +0 -71
  273. package/collections/monitor-mongo.collection.js.map +0 -1
  274. package/collections/notification.collection.d.ts +0 -3
  275. package/collections/notification.collection.js +0 -57
  276. package/collections/notification.collection.js.map +0 -1
  277. package/collections/openai-usage-ledger.collection.d.ts +0 -2
  278. package/collections/openai-usage-ledger.collection.js +0 -124
  279. package/collections/openai-usage-ledger.collection.js.map +0 -1
  280. package/collections/report-builder-dashboard-builder.collection.d.ts +0 -3
  281. package/collections/report-builder-dashboard-builder.collection.js +0 -109
  282. package/collections/report-builder-dashboard-builder.collection.js.map +0 -1
  283. package/collections/report-builder-library.collection.d.ts +0 -3
  284. package/collections/report-builder-library.collection.js +0 -87
  285. package/collections/report-builder-library.collection.js.map +0 -1
  286. package/collections/report-builder-report.collection.d.ts +0 -4
  287. package/collections/report-builder-report.collection.js +0 -180
  288. package/collections/report-builder-report.collection.js.map +0 -1
  289. package/collections/user-group.collection.d.ts +0 -4
  290. package/collections/user-group.collection.js +0 -89
  291. package/collections/user-group.collection.js.map +0 -1
  292. package/collections/user-guide.collection.d.ts +0 -3
  293. package/collections/user-guide.collection.js +0 -57
  294. package/collections/user-guide.collection.js.map +0 -1
  295. package/collections/user.collection.d.ts +0 -4
  296. package/collections/user.collection.js +0 -180
  297. package/collections/user.collection.js.map +0 -1
  298. package/cron/cron.d.ts +0 -14
  299. package/cron/cron.js +0 -216
  300. package/cron/cron.js.map +0 -1
  301. package/fixtures/cron-jobs.d.ts +0 -1
  302. package/fixtures/cron-jobs.js +0 -150
  303. package/fixtures/cron-jobs.js.map +0 -1
  304. package/fixtures/init.d.ts +0 -1
  305. package/fixtures/init.js +0 -91
  306. package/fixtures/init.js.map +0 -1
  307. package/http/auth.d.ts +0 -2
  308. package/http/auth.js +0 -903
  309. package/http/auth.js.map +0 -1
  310. package/http/health.d.ts +0 -1
  311. package/http/health.js +0 -11
  312. package/http/health.js.map +0 -1
  313. package/http/home.d.ts +0 -1
  314. package/http/home.js +0 -134
  315. package/http/home.js.map +0 -1
  316. package/http/slow-query-publication.d.ts +0 -2
  317. package/http/slow-query-publication.js +0 -99
  318. package/http/slow-query-publication.js.map +0 -1
  319. package/index.d.ts +0 -1
  320. package/index.js +0 -19
  321. package/index.js.map +0 -1
  322. package/managers/communication-metric.manager.d.ts +0 -16
  323. package/managers/communication-metric.manager.js +0 -134
  324. package/managers/communication-metric.manager.js.map +0 -1
  325. package/managers/cron.manager.d.ts +0 -20
  326. package/managers/cron.manager.js +0 -534
  327. package/managers/cron.manager.js.map +0 -1
  328. package/managers/customer-notification-content.manager.d.ts +0 -55
  329. package/managers/customer-notification-content.manager.js +0 -158
  330. package/managers/customer-notification-content.manager.js.map +0 -1
  331. package/managers/diagnostic-manager-bootstrap.d.ts +0 -9
  332. package/managers/diagnostic-manager-bootstrap.js +0 -260
  333. package/managers/diagnostic-manager-bootstrap.js.map +0 -1
  334. package/managers/error-auto-fix.manager.d.ts +0 -149
  335. package/managers/error-auto-fix.manager.js +0 -3064
  336. package/managers/error-auto-fix.manager.js.map +0 -1
  337. package/managers/local-log.manager.d.ts +0 -18
  338. package/managers/local-log.manager.js +0 -88
  339. package/managers/local-log.manager.js.map +0 -1
  340. package/managers/method.manager.d.ts +0 -77
  341. package/managers/method.manager.js +0 -1701
  342. package/managers/method.manager.js.map +0 -1
  343. package/managers/mongo.manager.d.ts +0 -222
  344. package/managers/mongo.manager.js +0 -4984
  345. package/managers/mongo.manager.js.map +0 -1
  346. package/managers/monitor.manager.d.ts +0 -69
  347. package/managers/monitor.manager.js +0 -534
  348. package/managers/monitor.manager.js.map +0 -1
  349. package/managers/openai-usage-ledger.manager.d.ts +0 -15
  350. package/managers/openai-usage-ledger.manager.js +0 -144
  351. package/managers/openai-usage-ledger.manager.js.map +0 -1
  352. package/managers/slow-query-verifier.manager.d.ts +0 -144
  353. package/managers/slow-query-verifier.manager.js +0 -3857
  354. package/managers/slow-query-verifier.manager.js.map +0 -1
  355. package/managers/slow-query.manager.d.ts +0 -28
  356. package/managers/slow-query.manager.js +0 -468
  357. package/managers/slow-query.manager.js.map +0 -1
  358. package/managers/subscription.manager.d.ts +0 -169
  359. package/managers/subscription.manager.js +0 -3422
  360. package/managers/subscription.manager.js.map +0 -1
  361. package/managers/websocket.manager.d.ts +0 -73
  362. package/managers/websocket.manager.js +0 -673
  363. package/managers/websocket.manager.js.map +0 -1
  364. package/managers/worker-dispatcher.manager.d.ts +0 -117
  365. package/managers/worker-dispatcher.manager.js +0 -1210
  366. package/managers/worker-dispatcher.manager.js.map +0 -1
  367. package/managers/worker-server.manager.d.ts +0 -16
  368. package/managers/worker-server.manager.js +0 -530
  369. package/managers/worker-server.manager.js.map +0 -1
  370. package/methods/accounts.d.ts +0 -2
  371. package/methods/accounts.js +0 -624
  372. package/methods/accounts.js.map +0 -1
  373. package/methods/ai-terminal.d.ts +0 -304
  374. package/methods/ai-terminal.js +0 -25096
  375. package/methods/ai-terminal.js.map +0 -1
  376. package/methods/app-settings.d.ts +0 -2
  377. package/methods/app-settings.js +0 -169
  378. package/methods/app-settings.js.map +0 -1
  379. package/methods/aws.d.ts +0 -2
  380. package/methods/aws.js +0 -874
  381. package/methods/aws.js.map +0 -1
  382. package/methods/collections.d.ts +0 -2
  383. package/methods/collections.js +0 -626
  384. package/methods/collections.js.map +0 -1
  385. package/methods/counters.d.ts +0 -2
  386. package/methods/counters.js +0 -111
  387. package/methods/counters.js.map +0 -1
  388. package/methods/cron-jobs.d.ts +0 -2
  389. package/methods/cron-jobs.js +0 -2471
  390. package/methods/cron-jobs.js.map +0 -1
  391. package/methods/customer-notifications.d.ts +0 -2
  392. package/methods/customer-notifications.js +0 -528
  393. package/methods/customer-notifications.js.map +0 -1
  394. package/methods/diagnostics.d.ts +0 -2
  395. package/methods/diagnostics.js +0 -514
  396. package/methods/diagnostics.js.map +0 -1
  397. package/methods/flag-updates.d.ts +0 -2
  398. package/methods/flag-updates.js +0 -8
  399. package/methods/flag-updates.js.map +0 -1
  400. package/methods/flags.d.ts +0 -2
  401. package/methods/flags.js +0 -8
  402. package/methods/flags.js.map +0 -1
  403. package/methods/logs.d.ts +0 -2
  404. package/methods/logs.js +0 -750
  405. package/methods/logs.js.map +0 -1
  406. package/methods/mongo-explorer.d.ts +0 -2
  407. package/methods/mongo-explorer.js +0 -1811
  408. package/methods/mongo-explorer.js.map +0 -1
  409. package/methods/monitor.d.ts +0 -2
  410. package/methods/monitor.js +0 -543
  411. package/methods/monitor.js.map +0 -1
  412. package/methods/pdf.d.ts +0 -2
  413. package/methods/pdf.js +0 -1195
  414. package/methods/pdf.js.map +0 -1
  415. package/methods/publications.d.ts +0 -1
  416. package/methods/publications.js +0 -183
  417. package/methods/publications.js.map +0 -1
  418. package/methods/report-builder.d.ts +0 -2
  419. package/methods/report-builder.js +0 -2960
  420. package/methods/report-builder.js.map +0 -1
  421. package/methods/support.d.ts +0 -2
  422. package/methods/support.js +0 -313
  423. package/methods/support.js.map +0 -1
  424. package/models/ai-terminal-conversation.model.d.ts +0 -17
  425. package/models/ai-terminal-conversation.model.js +0 -4
  426. package/models/ai-terminal-conversation.model.js.map +0 -1
  427. package/models/ai-terminal-issue-report.model.d.ts +0 -19
  428. package/models/ai-terminal-issue-report.model.js +0 -4
  429. package/models/ai-terminal-issue-report.model.js.map +0 -1
  430. package/models/ai-terminal-message.model.d.ts +0 -22
  431. package/models/ai-terminal-message.model.js +0 -4
  432. package/models/ai-terminal-message.model.js.map +0 -1
  433. package/models/app-setting.model.d.ts +0 -16
  434. package/models/app-setting.model.js +0 -4
  435. package/models/app-setting.model.js.map +0 -1
  436. package/models/app-status.model.js +0 -4
  437. package/models/app-status.model.js.map +0 -1
  438. package/models/billing-logged-in-users.model.js +0 -4
  439. package/models/billing-logged-in-users.model.js.map +0 -1
  440. package/models/collection-document.model.d.ts +0 -21
  441. package/models/collection-document.model.js +0 -4
  442. package/models/collection-document.model.js.map +0 -1
  443. package/models/communication-metric.model.d.ts +0 -20
  444. package/models/communication-metric.model.js +0 -4
  445. package/models/communication-metric.model.js.map +0 -1
  446. package/models/counter.model.js +0 -4
  447. package/models/counter.model.js.map +0 -1
  448. package/models/cron-job-history.model.d.ts +0 -15
  449. package/models/cron-job-history.model.js +0 -4
  450. package/models/cron-job-history.model.js.map +0 -1
  451. package/models/cron-job.model.d.ts +0 -14
  452. package/models/cron-job.model.js +0 -4
  453. package/models/cron-job.model.js.map +0 -1
  454. package/models/customer-notification.model.d.ts +0 -26
  455. package/models/customer-notification.model.js +0 -4
  456. package/models/customer-notification.model.js.map +0 -1
  457. package/models/customer-portal-password.model.d.ts +0 -11
  458. package/models/customer-portal-password.model.js +0 -4
  459. package/models/customer-portal-password.model.js.map +0 -1
  460. package/models/dialog.model.d.ts +0 -23
  461. package/models/dialog.model.js +0 -4
  462. package/models/dialog.model.js.map +0 -1
  463. package/models/email-history.model.d.ts +0 -30
  464. package/models/email-history.model.js.map +0 -1
  465. package/models/email-verified.model.js +0 -4
  466. package/models/email-verified.model.js.map +0 -1
  467. package/models/file.model.js +0 -4
  468. package/models/file.model.js.map +0 -1
  469. package/models/flag-update.model.js +0 -4
  470. package/models/flag-update.model.js.map +0 -1
  471. package/models/flag.model.js +0 -4
  472. package/models/flag.model.js.map +0 -1
  473. package/models/log-method-latency.model.d.ts +0 -10
  474. package/models/log-method-latency.model.js +0 -4
  475. package/models/log-method-latency.model.js.map +0 -1
  476. package/models/log-subscription.model.js +0 -4
  477. package/models/log-subscription.model.js.map +0 -1
  478. package/models/log.model.d.ts +0 -17
  479. package/models/log.model.js +0 -4
  480. package/models/log.model.js.map +0 -1
  481. package/models/logged-in-users.model.js +0 -4
  482. package/models/logged-in-users.model.js.map +0 -1
  483. package/models/method-response.model.js +0 -4
  484. package/models/method-response.model.js.map +0 -1
  485. package/models/method.model.d.ts +0 -24
  486. package/models/method.model.js +0 -4
  487. package/models/method.model.js.map +0 -1
  488. package/models/monitor-cpu.model.js +0 -4
  489. package/models/monitor-cpu.model.js.map +0 -1
  490. package/models/monitor-function.model.d.ts +0 -14
  491. package/models/monitor-function.model.js +0 -4
  492. package/models/monitor-function.model.js.map +0 -1
  493. package/models/monitor-memory.model.d.ts +0 -15
  494. package/models/monitor-memory.model.js +0 -4
  495. package/models/monitor-memory.model.js.map +0 -1
  496. package/models/monitor-mongo.model.d.ts +0 -13
  497. package/models/monitor-mongo.model.js +0 -4
  498. package/models/monitor-mongo.model.js.map +0 -1
  499. package/models/notification.model.js +0 -4
  500. package/models/notification.model.js.map +0 -1
  501. package/models/openai-usage-ledger.model.d.ts +0 -15
  502. package/models/openai-usage-ledger.model.js +0 -4
  503. package/models/openai-usage-ledger.model.js.map +0 -1
  504. package/models/pagination.model.d.ts +0 -11
  505. package/models/pagination.model.js +0 -28
  506. package/models/pagination.model.js.map +0 -1
  507. package/models/permission.model.d.ts +0 -12
  508. package/models/permission.model.js +0 -4
  509. package/models/permission.model.js.map +0 -1
  510. package/models/report-builder-dashboard-builder.model.d.ts +0 -25
  511. package/models/report-builder-dashboard-builder.model.js +0 -4
  512. package/models/report-builder-dashboard-builder.model.js.map +0 -1
  513. package/models/report-builder-library.model.d.ts +0 -17
  514. package/models/report-builder-library.model.js +0 -4
  515. package/models/report-builder-library.model.js.map +0 -1
  516. package/models/report-builder-report.model.d.ts +0 -120
  517. package/models/report-builder-report.model.js +0 -4
  518. package/models/report-builder-report.model.js.map +0 -1
  519. package/models/report-builder.model.d.ts +0 -61
  520. package/models/report-builder.model.js +0 -4
  521. package/models/report-builder.model.js.map +0 -1
  522. package/models/select-data-label.model.d.ts +0 -9
  523. package/models/select-data-label.model.js +0 -4
  524. package/models/select-data-label.model.js.map +0 -1
  525. package/models/server-message.model.d.ts +0 -32
  526. package/models/server-message.model.js +0 -4
  527. package/models/server-message.model.js.map +0 -1
  528. package/models/slow-query-report.model.d.ts +0 -23
  529. package/models/slow-query-report.model.js +0 -4
  530. package/models/slow-query-report.model.js.map +0 -1
  531. package/models/subscription.model.d.ts +0 -31
  532. package/models/subscription.model.js +0 -4
  533. package/models/subscription.model.js.map +0 -1
  534. package/models/support-ticket.model.d.ts +0 -86
  535. package/models/support-ticket.model.js +0 -4
  536. package/models/support-ticket.model.js.map +0 -1
  537. package/models/user-group.model.d.ts +0 -20
  538. package/models/user-group.model.js +0 -4
  539. package/models/user-group.model.js.map +0 -1
  540. package/models/user-guide.model.js +0 -4
  541. package/models/user-guide.model.js.map +0 -1
  542. package/models/user.model.d.ts +0 -84
  543. package/models/user.model.js +0 -4
  544. package/models/user.model.js.map +0 -1
  545. package/private/images/ResolveIO.png +0 -0
  546. package/public_api.js +0 -107
  547. package/public_api.js.map +0 -1
  548. package/publications/ai-terminal.d.ts +0 -1
  549. package/publications/ai-terminal.js +0 -122
  550. package/publications/ai-terminal.js.map +0 -1
  551. package/publications/app-settings.d.ts +0 -2
  552. package/publications/app-settings.js +0 -28
  553. package/publications/app-settings.js.map +0 -1
  554. package/publications/app-status.d.ts +0 -2
  555. package/publications/app-status.js +0 -16
  556. package/publications/app-status.js.map +0 -1
  557. package/publications/cron-jobs.d.ts +0 -2
  558. package/publications/cron-jobs.js +0 -32
  559. package/publications/cron-jobs.js.map +0 -1
  560. package/publications/customer-notifications.d.ts +0 -2
  561. package/publications/customer-notifications.js +0 -161
  562. package/publications/customer-notifications.js.map +0 -1
  563. package/publications/files.d.ts +0 -2
  564. package/publications/files.js +0 -36
  565. package/publications/files.js.map +0 -1
  566. package/publications/flags-update.d.ts +0 -2
  567. package/publications/flags-update.js +0 -22
  568. package/publications/flags-update.js.map +0 -1
  569. package/publications/flags.d.ts +0 -2
  570. package/publications/flags.js +0 -22
  571. package/publications/flags.js.map +0 -1
  572. package/publications/logs.d.ts +0 -2
  573. package/publications/logs.js +0 -164
  574. package/publications/logs.js.map +0 -1
  575. package/publications/notifications.d.ts +0 -2
  576. package/publications/notifications.js +0 -16
  577. package/publications/notifications.js.map +0 -1
  578. package/publications/report-builder-dashboard-builders.d.ts +0 -2
  579. package/publications/report-builder-dashboard-builders.js +0 -42
  580. package/publications/report-builder-dashboard-builders.js.map +0 -1
  581. package/publications/report-builder-libraries.d.ts +0 -2
  582. package/publications/report-builder-libraries.js +0 -90
  583. package/publications/report-builder-libraries.js.map +0 -1
  584. package/publications/report-builder-reports.d.ts +0 -2
  585. package/publications/report-builder-reports.js +0 -50
  586. package/publications/report-builder-reports.js.map +0 -1
  587. package/publications/super-admin.d.ts +0 -2
  588. package/publications/super-admin.js +0 -16
  589. package/publications/super-admin.js.map +0 -1
  590. package/publications/user-groups.d.ts +0 -1
  591. package/publications/user-groups.js +0 -16
  592. package/publications/user-groups.js.map +0 -1
  593. package/publications/user-guides.d.ts +0 -1
  594. package/publications/user-guides.js +0 -16
  595. package/publications/user-guides.js.map +0 -1
  596. package/resolveio-server-app.d.ts +0 -70
  597. package/resolveio-server-app.js +0 -801
  598. package/resolveio-server-app.js.map +0 -1
  599. package/server-app.d.ts +0 -167
  600. package/server-app.js +0 -2784
  601. package/server-app.js.map +0 -1
  602. package/services/codex-client.d.ts +0 -119
  603. package/services/codex-client.js +0 -1470
  604. package/services/codex-client.js.map +0 -1
  605. package/services/openai-client.d.ts +0 -46
  606. package/services/openai-client.js +0 -318
  607. package/services/openai-client.js.map +0 -1
  608. package/types/error-report.d.ts +0 -25
  609. package/types/error-report.js +0 -4
  610. package/types/error-report.js.map +0 -1
  611. package/types/slow-query-report.d.ts +0 -27
  612. package/types/slow-query-report.js +0 -6
  613. package/types/slow-query-report.js.map +0 -1
  614. package/util/common.d.ts +0 -31
  615. package/util/common.js +0 -683
  616. package/util/common.js.map +0 -1
  617. package/util/customer-portal-password.d.ts +0 -13
  618. package/util/customer-portal-password.js +0 -209
  619. package/util/customer-portal-password.js.map +0 -1
  620. package/util/error-reporter.d.ts +0 -52
  621. package/util/error-reporter.js +0 -326
  622. package/util/error-reporter.js.map +0 -1
  623. package/util/error-tracking.d.ts +0 -13
  624. package/util/error-tracking.js +0 -120
  625. package/util/error-tracking.js.map +0 -1
  626. package/util/report-builder-unwinds.d.ts +0 -15
  627. package/util/report-builder-unwinds.js +0 -156
  628. package/util/report-builder-unwinds.js.map +0 -1
  629. package/util/schema-report-builder.d.ts +0 -6
  630. package/util/schema-report-builder.js +0 -481
  631. package/util/schema-report-builder.js.map +0 -1
  632. package/util/slow-query-reporter.d.ts +0 -28
  633. package/util/slow-query-reporter.js +0 -226
  634. package/util/slow-query-reporter.js.map +0 -1
  635. package/util/subscription-dependency-context.d.ts +0 -34
  636. package/util/subscription-dependency-context.js +0 -1283
  637. package/util/subscription-dependency-context.js.map +0 -1
  638. package/util/tokenizer.d.ts +0 -5
  639. package/util/tokenizer.js +0 -41
  640. package/util/tokenizer.js.map +0 -1
  641. package/workers/codex-runner.worker.d.ts +0 -1
  642. package/workers/codex-runner.worker.js +0 -192
  643. package/workers/codex-runner.worker.js.map +0 -1
  644. /package/{private → src/private}/email-templates/enrollment.html +0 -0
  645. /package/{private → src/private}/email-templates/forgot-password.html +0 -0
  646. /package/{private → src/private}/email-templates/support-ticket-deleted.html +0 -0
  647. /package/{private → src/private}/email-templates/support-ticket-modified.html +0 -0
  648. /package/{private → src/private}/email-templates/support-ticket.html +0 -0
  649. /package/{public_api.d.ts → src/public_api.ts} +0 -0
@@ -0,0 +1,2616 @@
1
+ import * as express from 'express';
2
+ import * as xmlParser from 'express-xml-bodyparser';
3
+ import { createServer, Server } from 'http';
4
+ import * as crypto from 'crypto';
5
+ import * as fs from 'fs';
6
+ import * as jwt from 'jsonwebtoken';
7
+ import * as moment from 'moment-timezone';
8
+ import { unpack } from 'msgpackr';
9
+ import * as os from 'os';
10
+ import * as path from 'path';
11
+ import { monitorEventLoopDelay } from 'perf_hooks';
12
+ import * as inspector from 'inspector';
13
+ import { URL } from 'url';
14
+ import * as WebSocket from 'ws';
15
+
16
+ import { Logs } from './collections/log.collection';
17
+ import { Users } from './collections/user.collection';
18
+ import { CronManager } from './managers/cron.manager';
19
+ import { MethodManager } from './managers/method.manager';
20
+ import { MonitorManager, MonitorManagerFunction } from './managers/monitor.manager';
21
+ import { SubscriptionManager } from './managers/subscription.manager';
22
+ import { ServerResponseModel } from './models/server-message.model';
23
+ import { dateReviver, getBinarySize, isAllowedOrigin, objectIdHexString, round } from './util/common';
24
+ import { ErrorReporter } from './util/error-reporter';
25
+ import { ensureErrorWithCorrelation } from './util/error-tracking';
26
+
27
+ import { MongoNetworkTimeoutError } from 'mongodb';
28
+ import { setupAuthRoutes } from './http/auth';
29
+ import { setupHealthRoutes } from './http/health';
30
+ import { setupHomeRoutes } from './http/home';
31
+ import { setupSlowQueryPublicationRoutes } from './http/slow-query-publication';
32
+
33
+ import { WebSocketManager } from './managers/websocket.manager';
34
+ import { WorkerDispatcherManager } from './managers/worker-dispatcher.manager';
35
+ import { WorkerServerManager } from './managers/worker-server.manager';
36
+ import { ResolveIOServer } from './resolveio-server-app';
37
+
38
+ export class ResolveIOMainServer {
39
+ private _app: express.Application;
40
+ private _serverHTTP: Server;
41
+ private _portHTTP: number;
42
+ private _serverWSS: WebSocket.Server;
43
+ private _offlineUpdates = [];
44
+ public sesMail = false;
45
+ private publicProgram = false;
46
+ private _rebootFlag = false;
47
+
48
+ private LOGGER = 'ERROR'; //ERROR / DEBUG
49
+
50
+ private _websocketManager: WebSocketManager;
51
+ private _monitorManager: MonitorManager;
52
+ private _monitorManagerFunction: MonitorManagerFunction;
53
+ private _subscriptionManager: SubscriptionManager;
54
+ private _methodManager: MethodManager;
55
+ private _cronManager: CronManager;
56
+ private _clientRoutes: string[] = [];
57
+ private _workerDispatcherManager: WorkerDispatcherManager;
58
+ private _workerServerManager: WorkerServerManager;
59
+ private _httpServerClosePromise: Promise<void> | null = null;
60
+ private _websocketServerClosePromise: Promise<void> | null = null;
61
+ private _wsConnectDebug = false;
62
+ private _perfDebug = false;
63
+ private _perfDebugIntervalMs = 2000;
64
+ private _perfDebugTimer: NodeJS.Timeout | null = null;
65
+ private _perfDebugLastCpu: NodeJS.CpuUsage | null = null;
66
+ private _perfDebugLastTs = 0;
67
+ private _eventLoopHistogram: ReturnType<typeof monitorEventLoopDelay> | null = null;
68
+ private _cpuProfileOnStart = false;
69
+ private _cpuProfileAuto = false;
70
+ private _cpuProfileDurationMs = 15000;
71
+ private _cpuProfileThresholdPct = 90;
72
+ private _cpuProfileTriggerCount = 3;
73
+ private _cpuProfileHighCount = 0;
74
+ private _cpuProfileDir: string | null = null;
75
+ private _cpuProfileSession: inspector.Session | null = null;
76
+ private _timerDebug = false;
77
+ private _timerDebugThresholdMs = 50;
78
+ private _timerDebugMinDelayMs = 5;
79
+ private _timerDebugSampleRate = 1;
80
+ private _timerDebugLogLimit = 100;
81
+ private _timerDebugLogCount = 0;
82
+ private _aiWorkerDebug = false;
83
+
84
+ private _serverStartTime: Date;
85
+ private _lastErrorMsg: Date = null;
86
+
87
+ private _debugMsgRecv = 0;
88
+ private _debugMsgQueue = 0;
89
+
90
+ private _isWorkersEnabled = false;
91
+ private _isWorkerInstance = false;
92
+
93
+ private _safeShutdown = false;
94
+ private _dynamicAppGatewayEnabled = false;
95
+ private _dynamicAppGatewayCache = new Map<string, { expiresAt: number; app: any }>();
96
+ private _socketTier = '';
97
+ private _maxClientSockets = 0;
98
+ private _singleIpPerUser = false;
99
+ private _socketPolicyUpgradeUrl = '';
100
+
101
+ private readonly _clientHeartbeatIntervalMs = 20000;
102
+ private readonly _clientHeartbeatInitialDelayMs = 5000;
103
+ private readonly _clientHeartbeatBackpressureBytes = 5 * 1024 * 1024;
104
+ private readonly _dynamicAppGatewayCacheMs = 30 * 1000;
105
+
106
+ constructor() {}
107
+
108
+ static async create() {
109
+ const resolveioMainServer = new ResolveIOMainServer();
110
+ await resolveioMainServer.initialize();
111
+ return resolveioMainServer;
112
+ }
113
+
114
+ private async initialize() {
115
+ this._serverStartTime = new Date();
116
+ this._lastErrorMsg = null;
117
+ this._wsConnectDebug = this.resolveConnectDebug();
118
+ this._perfDebug = this.resolvePerfDebug();
119
+ this._cpuProfileOnStart = this.resolveCpuProfileOnStart();
120
+ this._cpuProfileAuto = this.resolveCpuProfileAuto();
121
+ this._cpuProfileDurationMs = this.resolveCpuProfileDurationMs();
122
+ this._cpuProfileThresholdPct = this.resolveCpuProfileThresholdPct();
123
+ this._cpuProfileTriggerCount = this.resolveCpuProfileTriggerCount();
124
+ this._cpuProfileDir = this.resolveCpuProfileDir();
125
+ this._timerDebug = this.resolveTimerDebug();
126
+ this._timerDebugThresholdMs = this.resolveTimerDebugThresholdMs();
127
+ this._timerDebugMinDelayMs = this.resolveTimerDebugMinDelayMs();
128
+ this._timerDebugSampleRate = this.resolveTimerDebugSampleRate();
129
+ this._timerDebugLogLimit = this.resolveTimerDebugLogLimit();
130
+ this._aiWorkerDebug = this.parseDebugFlag(process.env.AI_ASSISTANT_WORKER_DEBUG);
131
+ this._dynamicAppGatewayEnabled = this.resolveDynamicAppGatewayEnabled();
132
+ this._socketTier = this.resolveSocketTier();
133
+ this._maxClientSockets = this.resolveMaxClientSockets(this._socketTier);
134
+ this._singleIpPerUser = this.resolveSingleIpPerUserPolicy(this._socketTier);
135
+ this._socketPolicyUpgradeUrl = this.resolveSocketPolicyUpgradeUrl();
136
+ if (this._maxClientSockets > 0 || this._singleIpPerUser) {
137
+ console.info(new Date(), '[Socket Policy] configured', {
138
+ tier: this._socketTier || 'none',
139
+ maxClientSockets: this._maxClientSockets,
140
+ singleIpPerUser: this._singleIpPerUser
141
+ });
142
+ }
143
+ this._monitorManager = await MonitorManager.create();
144
+ this._monitorManagerFunction = new MonitorManagerFunction();
145
+ this.installTimerDebug();
146
+ this.startPerfDebug();
147
+ if (this._cpuProfileOnStart) {
148
+ this.startCpuProfile('on-start');
149
+ }
150
+
151
+ // Check for workers and decide what to start
152
+ this._isWorkersEnabled = process.env.IS_WORKERS_ENABLED === 'true';
153
+ this._isWorkerInstance = process.env.IS_WORKER_INSTANCE === 'true';
154
+
155
+ setInterval(() => {
156
+ if (this._methodManager && this._methodManager.getEnableDebug()) {
157
+ console.log(new Date(), 'Server App', 'Msg Recv Hits', this._debugMsgRecv);
158
+ console.log(new Date(), 'Server App', 'Msg Queue Hits', this._debugMsgQueue);
159
+ }
160
+
161
+ this._debugMsgQueue = 0;
162
+ this._debugMsgRecv = 0;
163
+ }, 60000);
164
+
165
+ process.removeAllListeners('unhandledRejection');
166
+
167
+ process.on('unhandledRejection', async (error, rej) => {
168
+ const { error: normalizedError, correlationId } = ensureErrorWithCorrelation(error);
169
+
170
+ if (this._methodManager.getEnableDebug()) {
171
+ console.error(new Date(), 'ERROR DETECTED w/ Debug Flag Active: unhandledRejection', [normalizedError, rej, { correlationId }]);
172
+ }
173
+
174
+ // Condition to filter out the MongoError with specific codes
175
+ if (normalizedError && normalizedError['name'] === 'MongoError' && (normalizedError['code'] === 48 || normalizedError['code'] === 26 || normalizedError['code'] === 11000 || normalizedError['code'] === 251)) {
176
+ return; // Simply return without doing anything further
177
+ }
178
+
179
+ // if (normalizedError && normalizedError['name'] === 'MongoServerError' && (!initServerFlag || normalizedError['code'] === 26 || normalizedError['code'] === 11000 || normalizedError['code'] === 86 || normalizedError['code'] === 251)) {
180
+ // return; // Simply return without doing anything further
181
+ // }
182
+
183
+ if (normalizedError && normalizedError['name'] === 'MongoServerError') {
184
+ return; // Simply return without doing anything further
185
+ }
186
+
187
+ const errorDetails = {
188
+ id: correlationId,
189
+ name: normalizedError?.name,
190
+ message: normalizedError?.message,
191
+ stack: normalizedError?.stack,
192
+ code: normalizedError?.code,
193
+ codeName: normalizedError?.codeName
194
+ };
195
+
196
+ console.error(new Date(), 'Unhandled Rejection at Promise', [errorDetails]);
197
+
198
+ let diffTimeSec = moment().diff(this._serverStartTime, 'seconds');
199
+
200
+ // If this is a MongoNetworkTimeoutError, handle it specifically
201
+ if (normalizedError && (normalizedError['name'] === 'MongoNetworkTimeoutError' || normalizedError instanceof MongoNetworkTimeoutError)) {
202
+ if (diffTimeSec > 60 && !this._lastErrorMsg) {
203
+ this._lastErrorMsg = new Date();
204
+ setTimeout(() => {
205
+ this._lastErrorMsg = null;
206
+ }, 60000);
207
+
208
+ // Exiting the process
209
+ process.exit(1);
210
+ }
211
+ }
212
+ else if (normalizedError && normalizedError['name'] === 'MongoError' && normalizedError['message'] === 'not master') {
213
+ if (diffTimeSec > 60 && !this._lastErrorMsg) {
214
+ this._lastErrorMsg = new Date();
215
+
216
+ setTimeout(() => {
217
+ this._lastErrorMsg = null;
218
+ }, 60000);
219
+ }
220
+
221
+ process.exit(1);
222
+ }
223
+ else if (normalizedError && normalizedError['name'] === 'MongoError' && normalizedError['message'] === 'not master and slaveOk=false') {
224
+ if (diffTimeSec > 60 && !this._lastErrorMsg) {
225
+ this._lastErrorMsg = new Date();
226
+
227
+ setTimeout(() => {
228
+ this._lastErrorMsg = null;
229
+ }, 60000);
230
+ }
231
+
232
+ process.exit(1);
233
+ }
234
+ else if (normalizedError && normalizedError['name'] !== 'StatusError' && normalizedError['message'] !== '') {
235
+ if (diffTimeSec > 60 && !this._lastErrorMsg) {
236
+ this._lastErrorMsg = new Date();
237
+
238
+ setTimeout(() => {
239
+ this._lastErrorMsg = null;
240
+ }, 60000);
241
+
242
+ await this.reportServerError(
243
+ 'SERVER - Unhandled Rejection - ' + ResolveIOServer.getServerConfig()['CLIENT_NAME'],
244
+ correlationId,
245
+ errorDetails,
246
+ {
247
+ context: 'unhandledRejection',
248
+ scenario: 'General'
249
+ }
250
+ );
251
+ }
252
+ }
253
+ });
254
+
255
+ process.on('uncaughtException', async error => {
256
+ const { error: normalizedError, correlationId } = ensureErrorWithCorrelation(error);
257
+ console.error(normalizedError, 'Uncaught Exception thrown', { correlationId });
258
+
259
+ let diffTimeSec = moment().diff(this._serverStartTime, 'seconds');
260
+
261
+ if (diffTimeSec > 60 && !this._lastErrorMsg) {
262
+ this._lastErrorMsg = new Date();
263
+
264
+ setTimeout(() => {
265
+ this._lastErrorMsg = null;
266
+ }, 60000);
267
+
268
+ const errorDetails = {
269
+ id: correlationId,
270
+ name: normalizedError?.name,
271
+ message: normalizedError?.message,
272
+ stack: normalizedError?.stack,
273
+ code: normalizedError?.code,
274
+ codeName: normalizedError?.codeName
275
+ };
276
+
277
+ await this.reportServerError(
278
+ 'SERVER - Unhandled Exception - ' + ResolveIOServer.getServerConfig()['CLIENT_NAME'],
279
+ correlationId,
280
+ errorDetails,
281
+ {
282
+ context: 'uncaughtException'
283
+ }
284
+ );
285
+ }
286
+ });
287
+
288
+ //PM2 wants to reboot/restart
289
+ process.on('SIGINT', async () => {
290
+ this._rebootFlag = true;
291
+ try {
292
+ await this.shutdownNetworkServers();
293
+ }
294
+ catch (error) {
295
+ console.error(new Date(), 'Error closing network servers (SIGINT)', error);
296
+ }
297
+ await this.safeShutdown();
298
+ });
299
+
300
+ process.on('SIGTERM', async () => {
301
+ this._rebootFlag = true;
302
+ try {
303
+ await this.shutdownNetworkServers();
304
+ }
305
+ catch (error) {
306
+ console.error(new Date(), 'Error closing network servers (SIGTERM)', error);
307
+ }
308
+ await this.safeShutdown();
309
+ });
310
+
311
+ process.on('SIGQUIT', async () => {
312
+ this._rebootFlag = true;
313
+ try {
314
+ await this.shutdownNetworkServers();
315
+ }
316
+ catch (error) {
317
+ console.error(new Date(), 'Error closing network servers (SIGQUIT)', error);
318
+ }
319
+ await this.safeShutdown();
320
+ });
321
+
322
+ if (this.LOGGER === 'DEBUG') {
323
+ console.log('Starting ResolveIO Server');
324
+ }
325
+
326
+ if (this._isWorkersEnabled) {
327
+ if (this._isWorkerInstance) {
328
+ const workerRole = this.resolveWorkerRole();
329
+ const workerIndex = this.normalizeWorkerSelectorValue(process.env.WORKER_INDEX) || 'UNKNOWN';
330
+ const workerInstance = this.normalizeWorkerSelectorValue(process.env.NODE_APP_INSTANCE) || 'UNKNOWN';
331
+ console.log(`Running as Worker: ${workerRole}`, workerIndex, workerInstance);
332
+ this._methodManager = MethodManager.create(null, this._monitorManagerFunction, this._isWorkersEnabled, this._isWorkerInstance);
333
+ this._subscriptionManager = SubscriptionManager.createPublicationRegistry(ResolveIOServer.getServerConfig());
334
+ this._workerServerManager = WorkerServerManager.create(this._methodManager, this.getServerConfig());
335
+
336
+ if (process.env.WORKER_INDEX === '0') {
337
+ this._cronManager = CronManager.create();
338
+ }
339
+ }
340
+ else {
341
+ console.log('Running as a Server instance', process.env.NODE_APP_INSTANCE);
342
+ this._websocketManager = WebSocketManager.create(this);
343
+ this._methodManager = MethodManager.create(this._websocketManager, this._monitorManagerFunction, this._isWorkersEnabled, this._isWorkerInstance);
344
+ this._workerDispatcherManager = WorkerDispatcherManager.create(this._websocketManager, this._methodManager);
345
+ this._subscriptionManager = SubscriptionManager.create(this._serverWSS, ResolveIOServer.getServerConfig(), this._monitorManagerFunction);
346
+ this.startServerInstance();
347
+ this.listen();
348
+ }
349
+ }
350
+ else {
351
+ console.log('Running with Workers Disabled', process.env.NODE_APP_INSTANCE);
352
+ this._websocketManager = WebSocketManager.create(this);
353
+ this._methodManager = MethodManager.create(this._websocketManager, this._monitorManagerFunction, this._isWorkersEnabled, this._isWorkerInstance);
354
+ this._subscriptionManager = SubscriptionManager.create(this._serverWSS, ResolveIOServer.getServerConfig(), this._monitorManagerFunction);
355
+ this._cronManager = CronManager.create();
356
+ this.startServerInstance();
357
+ this.listen();
358
+ }
359
+ }
360
+
361
+ private async shutdownNetworkServers() {
362
+ await this.closeWebSocketServerGracefully();
363
+ await this.closeHttpServerGracefully();
364
+ }
365
+
366
+ private async closeHttpServerGracefully() {
367
+ if (!this._serverHTTP) {
368
+ return;
369
+ }
370
+
371
+ if (this._httpServerClosePromise !== null) {
372
+ await this._httpServerClosePromise;
373
+ return;
374
+ }
375
+
376
+ // eslint-disable-next-line no-restricted-syntax
377
+ this._httpServerClosePromise = new Promise(resolve => {
378
+ try {
379
+ this._serverHTTP.close(error => {
380
+ if (error && error['code'] !== 'ERR_SERVER_NOT_RUNNING') {
381
+ console.error(new Date(), 'Error closing HTTP server before shutdown', error);
382
+ }
383
+ resolve();
384
+ });
385
+ }
386
+ catch (error) {
387
+ if (error && error['code'] !== 'ERR_SERVER_NOT_RUNNING') {
388
+ console.error(new Date(), 'Error closing HTTP server before shutdown', error);
389
+ }
390
+ resolve();
391
+ }
392
+ });
393
+
394
+ await this._httpServerClosePromise;
395
+ }
396
+
397
+ private async closeWebSocketServerGracefully() {
398
+ if (!this._serverWSS) {
399
+ return;
400
+ }
401
+
402
+ if (this._websocketServerClosePromise !== null) {
403
+ await this._websocketServerClosePromise;
404
+ return;
405
+ }
406
+
407
+ this._serverWSS.clients.forEach(ws => {
408
+ try {
409
+ ws.close(1001, 'Server restarting');
410
+ }
411
+ catch (error) {
412
+ console.error(new Date(), 'Error closing WebSocket client before shutdown', error);
413
+ }
414
+ });
415
+
416
+ // eslint-disable-next-line no-restricted-syntax
417
+ this._websocketServerClosePromise = new Promise(resolve => {
418
+ try {
419
+ this._serverWSS.close(error => {
420
+ if (error) {
421
+ console.error(new Date(), 'Error closing WebSocket server before shutdown', error);
422
+ }
423
+
424
+ resolve();
425
+ });
426
+ }
427
+ catch (error) {
428
+ console.error(new Date(), 'Error closing WebSocket server before shutdown', error);
429
+ resolve();
430
+ }
431
+ });
432
+
433
+ await this._websocketServerClosePromise;
434
+ }
435
+
436
+ private startServerInstance() {
437
+ // Start express app
438
+ this._app = express();
439
+
440
+ // Use built-in express JSON parser
441
+ this._app.use(express.json({
442
+ limit: '50mb',
443
+ reviver: dateReviver // Note: 'reviver' is an option for JSON.parse, which can be passed here
444
+ }));
445
+
446
+ // Use built-in express URL-encoded parser
447
+ this._app.use(express.urlencoded({
448
+ limit: '50mb',
449
+ extended: true, // `extended` must be explicitly true or false
450
+ parameterLimit: 1000000
451
+ }));
452
+
453
+
454
+ this._app.use(xmlParser());
455
+
456
+ // Set port
457
+ this._portHTTP = process.env.NODE_APP_INSTANCE ? parseInt('808' + process.env.NODE_APP_INSTANCE) : 8080;
458
+
459
+ if (this.LOGGER === 'DEBUG') {
460
+ console.log('Setup ports');
461
+ }
462
+
463
+ // Create http server and websock server
464
+ this.createServer();
465
+
466
+ if (this.LOGGER === 'DEBUG') {
467
+ console.log('Create server');
468
+ }
469
+
470
+ // Set CORS
471
+ this._app.use(function (req, res, next) {
472
+ res.setHeader('Access-Control-Allow-Origin', '*');
473
+ res.setHeader('Access-Control-Allow-Methods', 'GET, POST');
474
+ res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,content-type');
475
+ res.setHeader('Access-Control-Allow-Credentials', 'false');
476
+ next();
477
+ });
478
+
479
+ if (this.LOGGER === 'DEBUG') {
480
+ console.log('Setup cors');
481
+ }
482
+
483
+ this.installDynamicAppGatewayMiddleware();
484
+
485
+ // Set up http login route
486
+ setupAuthRoutes(this, this._app, ResolveIOServer.getServerConfig());
487
+ setupHealthRoutes(this._app);
488
+ setupSlowQueryPublicationRoutes(this._app);
489
+
490
+ if (ResolveIOServer.getServerConfig()['CLIENT_NAME'] === 'ResolveIO' || this.publicProgram) {
491
+ setupHomeRoutes(this, this._app, ResolveIOServer.getServerConfig());
492
+ }
493
+
494
+ if (this.LOGGER === 'DEBUG') {
495
+ console.log('Setup express routes');
496
+ }
497
+ }
498
+
499
+ private installDynamicAppGatewayMiddleware(): void {
500
+ if (!this._dynamicAppGatewayEnabled) {
501
+ return;
502
+ }
503
+
504
+ this._app.use(async (req, res, next) => {
505
+ const appId = this.extractDynamicAppGatewayId(req);
506
+ if (!appId) {
507
+ next();
508
+ return;
509
+ }
510
+
511
+ try {
512
+ const appDoc = await this.resolveCachedDynamicAppGatewayApp(appId);
513
+ if (!appDoc) {
514
+ res.status(404).send(JSON.stringify({
515
+ error: true,
516
+ result: 'App not found.'
517
+ }));
518
+ return;
519
+ }
520
+
521
+ const allowedHosts = this.resolveDynamicAppAllowedHosts(appDoc);
522
+ const controlHosts = this.resolveDynamicAppControlHosts();
523
+ const requestHost = this.normalizeHostname(
524
+ this.normalizeHeaderValue(req.headers?.['x-forwarded-host'])
525
+ || this.normalizeHeaderValue(req.headers?.host)
526
+ );
527
+ const originHeader = this.normalizeHeaderValue(req.headers?.origin);
528
+ const originHost = this.resolveOriginHostname(originHeader);
529
+
530
+ if (requestHost && controlHosts.has(requestHost) && !this.isLocalHostname(requestHost)) {
531
+ res.status(403).send(JSON.stringify({
532
+ error: true,
533
+ result: 'App API must be requested from an app domain.'
534
+ }));
535
+ return;
536
+ }
537
+
538
+ if (requestHost && !allowedHosts.has(requestHost) && !this.isLocalHostname(requestHost)) {
539
+ res.status(403).send(JSON.stringify({
540
+ error: true,
541
+ result: 'Host not allowed for app API.'
542
+ }));
543
+ return;
544
+ }
545
+
546
+ if (originHost && controlHosts.has(originHost) && !this.isLocalHostname(originHost)) {
547
+ res.status(403).send(JSON.stringify({
548
+ error: true,
549
+ result: 'App API origin must be an app domain.'
550
+ }));
551
+ return;
552
+ }
553
+
554
+ if (originHost && !allowedHosts.has(originHost)) {
555
+ res.status(403).send(JSON.stringify({
556
+ error: true,
557
+ result: 'Origin not allowed for app API.'
558
+ }));
559
+ return;
560
+ }
561
+
562
+ if (originHeader) {
563
+ this.applyDynamicAppGatewayCorsHeaders(res, originHeader);
564
+ }
565
+
566
+ if (req.method === 'OPTIONS') {
567
+ res.status(204).end();
568
+ return;
569
+ }
570
+
571
+ const expectedToken = this.normalizeHeaderValue(appDoc?.rio_token);
572
+ if (!expectedToken) {
573
+ res.status(500).send(JSON.stringify({
574
+ error: true,
575
+ result: 'App token is not configured.'
576
+ }));
577
+ return;
578
+ }
579
+
580
+ const providedToken = this.resolveDynamicAppGatewayToken(req);
581
+ if (!providedToken || providedToken !== expectedToken) {
582
+ res.status(401).send(JSON.stringify({
583
+ error: true,
584
+ result: 'Invalid or missing RIO token.'
585
+ }));
586
+ return;
587
+ }
588
+
589
+ next();
590
+ }
591
+ catch (error) {
592
+ console.error(new Date(), '[DynamicAppGateway] middleware failure', error);
593
+ res.status(500).send(JSON.stringify({
594
+ error: true,
595
+ result: (error as Error)?.message || 'Dynamic app gateway error.'
596
+ }));
597
+ }
598
+ });
599
+ }
600
+
601
+ private resolveDynamicAppGatewayEnabled(): boolean {
602
+ const config = ResolveIOServer.getServerConfig() || {};
603
+ const raw = config['AI_CODER_DYNAMIC_APP_GATEWAY'] ?? process.env.AI_CODER_DYNAMIC_APP_GATEWAY;
604
+ if (raw !== undefined && raw !== null && `${raw}`.trim() !== '') {
605
+ return this.parseDebugFlag(raw);
606
+ }
607
+
608
+ const urls = [
609
+ config['ROOT_URL'],
610
+ config['SEC_ROOT_URL'],
611
+ config['SERVER_URL'],
612
+ process.env.ROOT_URL,
613
+ process.env.SEC_ROOT_URL,
614
+ process.env.SERVER_URL
615
+ ]
616
+ .map(value => `${value || ''}`.trim().toLowerCase())
617
+ .filter(Boolean);
618
+
619
+ return urls.some(value => value.includes('aicoder'));
620
+ }
621
+
622
+ private extractDynamicAppGatewayId(req: express.Request): string {
623
+ const rawPath = `${req.originalUrl || req.url || req.path || ''}`;
624
+ const normalizedPath = rawPath.split('?')[0];
625
+ if (!normalizedPath.startsWith('/api/apps/')) {
626
+ return '';
627
+ }
628
+
629
+ const parts = normalizedPath.split('/').filter(Boolean);
630
+ if (parts.length < 3) {
631
+ return '';
632
+ }
633
+
634
+ return `${parts[2] || ''}`.trim();
635
+ }
636
+
637
+ private resolveDynamicAppGatewayToken(req: express.Request): string {
638
+ const authHeader = this.normalizeHeaderValue(req.headers?.authorization || req.headers?.Authorization);
639
+ if (authHeader) {
640
+ const bearerMatch = authHeader.match(/^bearer\s+(.+)$/i);
641
+ if (bearerMatch && bearerMatch[1]) {
642
+ return bearerMatch[1].trim();
643
+ }
644
+ }
645
+
646
+ const headerCandidates = [
647
+ req.headers?.['x-rio-token'],
648
+ req.headers?.['x-app-token'],
649
+ req.headers?.['x-api-key']
650
+ ];
651
+ for (const candidate of headerCandidates) {
652
+ const value = this.normalizeHeaderValue(candidate);
653
+ if (value) {
654
+ return value;
655
+ }
656
+ }
657
+
658
+ const body: any = req.body || {};
659
+ const query: any = req.query || {};
660
+ const bodyCandidates = [body.rioToken, body.rio_token, body.apiKey, body.token];
661
+ for (const candidate of bodyCandidates) {
662
+ const value = this.normalizeHeaderValue(candidate);
663
+ if (value) {
664
+ return value;
665
+ }
666
+ }
667
+ const queryCandidates = [query.rioToken, query.rio_token, query.apiKey, query.token];
668
+ for (const candidate of queryCandidates) {
669
+ const value = this.normalizeHeaderValue(candidate);
670
+ if (value) {
671
+ return value;
672
+ }
673
+ }
674
+
675
+ return '';
676
+ }
677
+
678
+ private normalizeHeaderValue(value: any): string {
679
+ if (Array.isArray(value)) {
680
+ return value.map(entry => `${entry || ''}`.trim()).filter(Boolean).join(',');
681
+ }
682
+ return `${value || ''}`.trim();
683
+ }
684
+
685
+ private normalizeHostname(value: string): string {
686
+ const raw = `${value || ''}`.trim();
687
+ if (!raw) {
688
+ return '';
689
+ }
690
+
691
+ let candidate = raw.split(',')[0].trim();
692
+ if (!candidate) {
693
+ return '';
694
+ }
695
+
696
+ try {
697
+ if (candidate.startsWith('http://') || candidate.startsWith('https://')) {
698
+ return new URL(candidate).hostname.toLowerCase();
699
+ }
700
+ if (candidate.includes('/')) {
701
+ return new URL(`http://${candidate}`).hostname.toLowerCase();
702
+ }
703
+ }
704
+ catch {}
705
+
706
+ candidate = candidate.replace(/^\[/, '').replace(/\]$/, '');
707
+ const lastColon = candidate.lastIndexOf(':');
708
+ if (lastColon > -1 && candidate.indexOf(':') === lastColon) {
709
+ candidate = candidate.slice(0, lastColon);
710
+ }
711
+ return candidate.toLowerCase();
712
+ }
713
+
714
+ private resolveOriginHostname(origin: string): string {
715
+ const raw = `${origin || ''}`.trim();
716
+ if (!raw) {
717
+ return '';
718
+ }
719
+ try {
720
+ return new URL(raw).hostname.toLowerCase();
721
+ }
722
+ catch {
723
+ return this.normalizeHostname(raw);
724
+ }
725
+ }
726
+
727
+ private isLocalHostname(hostname: string): boolean {
728
+ const normalized = this.normalizeHostname(hostname);
729
+ return normalized === 'localhost' || normalized === '127.0.0.1' || normalized === '::1';
730
+ }
731
+
732
+ private resolveDynamicAppControlHosts(): Set<string> {
733
+ const config = ResolveIOServer.getServerConfig() || {};
734
+ const hosts = new Set<string>();
735
+ const candidates = [
736
+ config['ROOT_URL'],
737
+ config['SEC_ROOT_URL'],
738
+ config['SERVER_URL'],
739
+ process.env.ROOT_URL,
740
+ process.env.SEC_ROOT_URL,
741
+ process.env.SERVER_URL,
742
+ process.env.AI_CODER_ROOT_URL,
743
+ process.env.AI_CODER_SEC_ROOT_URL,
744
+ process.env.AI_CODER_SERVER_URL
745
+ ];
746
+ for (const candidate of candidates) {
747
+ const normalized = this.normalizeHostname(`${candidate || ''}`);
748
+ if (normalized) {
749
+ hosts.add(normalized);
750
+ }
751
+ }
752
+ return hosts;
753
+ }
754
+
755
+ private resolveDynamicAppAllowedHosts(appDoc: any): Set<string> {
756
+ const hosts = new Set<string>();
757
+ const candidates = [
758
+ appDoc?.domain,
759
+ appDoc?.backend_domain
760
+ ];
761
+ if (appDoc?.subdomain && appDoc?.domain_base) {
762
+ candidates.push(`${appDoc.subdomain}.${appDoc.domain_base}`);
763
+ }
764
+
765
+ for (const candidate of candidates) {
766
+ const normalized = this.normalizeHostname(`${candidate || ''}`);
767
+ if (normalized) {
768
+ hosts.add(normalized);
769
+ }
770
+ }
771
+ return hosts;
772
+ }
773
+
774
+ private applyDynamicAppGatewayCorsHeaders(res: express.Response, origin: string): void {
775
+ res.setHeader('Access-Control-Allow-Origin', origin);
776
+ res.setHeader('Vary', 'Origin');
777
+ res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE, OPTIONS');
778
+ res.setHeader('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, Authorization, X-Rio-Token, X-API-Key');
779
+ res.setHeader('Access-Control-Allow-Credentials', 'true');
780
+ res.setHeader('Access-Control-Max-Age', '600');
781
+ }
782
+
783
+ private async resolveCachedDynamicAppGatewayApp(appId: string): Promise<any | null> {
784
+ const now = Date.now();
785
+ const cached = this._dynamicAppGatewayCache.get(appId);
786
+ if (cached && cached.expiresAt > now) {
787
+ return cached.app;
788
+ }
789
+
790
+ const db = ResolveIOServer.getMainDB();
791
+ if (!db) {
792
+ return null;
793
+ }
794
+
795
+ const appCollection: any = db.collection('ai-coder-apps');
796
+ const appDoc = await appCollection.findOne(
797
+ { _id: appId },
798
+ {
799
+ projection: {
800
+ _id: 1,
801
+ domain: 1,
802
+ backend_domain: 1,
803
+ subdomain: 1,
804
+ domain_base: 1,
805
+ rio_token: 1
806
+ }
807
+ }
808
+ );
809
+ if (!appDoc) {
810
+ this._dynamicAppGatewayCache.delete(appId);
811
+ return null;
812
+ }
813
+
814
+ if (!this.normalizeHeaderValue(appDoc.rio_token)) {
815
+ const generated = crypto.randomBytes(32).toString('hex');
816
+ await appCollection.updateOne(
817
+ { _id: appId },
818
+ {
819
+ $set: {
820
+ rio_token: generated,
821
+ updatedAt: new Date()
822
+ }
823
+ }
824
+ );
825
+ appDoc.rio_token = generated;
826
+ }
827
+
828
+ this._dynamicAppGatewayCache.set(appId, {
829
+ expiresAt: now + this._dynamicAppGatewayCacheMs,
830
+ app: appDoc
831
+ });
832
+ return appDoc;
833
+ }
834
+
835
+ private async safeShutdown() {
836
+ if (!this._safeShutdown) {
837
+ console.log(new Date(), 'Safe Shutdown Command Received');
838
+ }
839
+
840
+ if (
841
+ !this._monitorManagerFunction.getActiveMonitorFunctions().length
842
+ && !this._offlineUpdates.length && (!this._workerDispatcherManager || this._workerDispatcherManager.isSafeShutdown())
843
+ ) {
844
+ if (ResolveIOServer.getMongoConnection()) {
845
+ try {
846
+ await ResolveIOServer.getMongoConnection().close(false);
847
+ console.log(new Date(), 'Safe Exit Complete, Process Exit');
848
+ process.exit(0);
849
+ }
850
+ catch {
851
+ process.exit(1);
852
+ };
853
+ }
854
+ else {
855
+ process.exit(0);
856
+ }
857
+ }
858
+ else {
859
+ if (!this._safeShutdown) {
860
+ this._safeShutdown = true;
861
+
862
+ setTimeout(() => {
863
+ this._safeShutdown = false;
864
+ }, 1000);
865
+
866
+ console.log(new Date(), 'Safe Exit In Progress',
867
+ this._monitorManagerFunction.getActiveMonitorFunctions().length,
868
+ this._offlineUpdates.length
869
+ );
870
+ }
871
+
872
+ setImmediate(async () => {
873
+ await this.safeShutdown();
874
+ });
875
+ }
876
+ }
877
+
878
+ getIsWorkersEnabled() {
879
+ return this._isWorkersEnabled;
880
+ }
881
+
882
+ getIsWorkerInstance() {
883
+ return this._isWorkerInstance;
884
+ }
885
+
886
+ public getWSList() {
887
+ let res = [];
888
+ this._serverWSS.clients.forEach((ws: WebSocket) => {
889
+ res.push(ws['id_socket']);
890
+ });
891
+ return res;
892
+ }
893
+
894
+ public getWSUserList() {
895
+ let res = [];
896
+ this._serverWSS.clients.forEach((ws: WebSocket) => {
897
+ res.push(ws['id_user']);
898
+ });
899
+ return res;
900
+ }
901
+
902
+ public getHTTPServer() {
903
+ return this._serverHTTP;
904
+ }
905
+
906
+ public getCronManager() {
907
+ return this._cronManager;
908
+ }
909
+
910
+ private async reportServerError(
911
+ subject: string,
912
+ correlationId: string,
913
+ context: Record<string, any>,
914
+ meta?: Record<string, any>,
915
+ severity = 'error',
916
+ stackOverride?: string
917
+ ) {
918
+ const config = ResolveIOServer.getServerConfig();
919
+ const metadata = Object.assign({}, meta || {});
920
+ if (correlationId && !metadata.correlationId) {
921
+ metadata.correlationId = correlationId;
922
+ }
923
+
924
+ await ErrorReporter.report({
925
+ sourceApp: 'server-app',
926
+ message: subject,
927
+ environment: config?.ROOT_URL || process.env.NODE_ENV || 'unknown',
928
+ clientSlug: ResolveIOServer.getClientName(),
929
+ clientName: config?.CLIENT_NAME,
930
+ severity,
931
+ stack: stackOverride || (typeof context?.stack === 'string' ? context.stack : undefined),
932
+ context,
933
+ metadata,
934
+ correlationId
935
+ });
936
+ }
937
+
938
+ public getMethodManager() {
939
+ return this._methodManager;
940
+ }
941
+
942
+ public getSubscriptionManager() {
943
+ return this._subscriptionManager;
944
+ }
945
+
946
+ public getMonitorManager() {
947
+ return this._monitorManager;
948
+ }
949
+
950
+ public getRebootFlag() {
951
+ return this._rebootFlag;
952
+ }
953
+
954
+ public getWebSocketManager(): WebSocketManager {
955
+ return this._websocketManager;
956
+ }
957
+
958
+ private createServer(): void {
959
+ this._serverHTTP = createServer(this._app);
960
+ this._serverHTTP.keepAliveTimeout = 65000;
961
+ this._serverHTTP.headersTimeout = 66000;
962
+
963
+ this._serverWSS = new WebSocket.Server({
964
+ server: this._serverHTTP,
965
+ verifyClient: this.publicProgram ? null : (info, cb) => {
966
+ if (this._rebootFlag) {
967
+ cb(false, 409, 'Unable To Process');
968
+ }
969
+ else {
970
+ if (this.LOGGER === 'DEBUG') {
971
+ console.log('Verify Client', info, cb);
972
+ }
973
+
974
+ // If it's a worker, we might skip token checks or do a simple check:
975
+ if (info.req.url && info.req.url.includes('workerToken=')) {
976
+ let requestUrl: URL;
977
+ let rootUrl = ResolveIOServer.getServerConfig()['ROOT_URL'] || 'http://localhost';
978
+ try {
979
+ requestUrl = new URL(info.req.url, rootUrl);
980
+ }
981
+ catch {
982
+ cb(false, 400, 'Bad Request');
983
+ return;
984
+ }
985
+
986
+ let workerToken = requestUrl.searchParams.get('workerToken') || '';
987
+ let workerIndex = requestUrl.searchParams.get('workerIndex');
988
+ let workerInstance = requestUrl.searchParams.get('workerInstance');
989
+ let expectedWorkerToken = String(ResolveIOServer.getServerConfig()['WORKER_TOKEN'] || '');
990
+
991
+ if (workerToken === expectedWorkerToken) {
992
+ if (workerIndex) {
993
+ info.req['workerIndex'] = workerIndex;
994
+ }
995
+ if (workerInstance) {
996
+ info.req['workerInstance'] = workerInstance;
997
+ }
998
+
999
+ cb(true);
1000
+ }
1001
+ else {
1002
+ cb(false, 401, 'Unauthorized');
1003
+ }
1004
+
1005
+ return;
1006
+ }
1007
+
1008
+ const protocolsHeader = info.req.headers['sec-websocket-protocol'];
1009
+ if (!protocolsHeader || typeof protocolsHeader !== 'string') {
1010
+ cb(false, 401, 'Unauthorized');
1011
+ return;
1012
+ }
1013
+
1014
+ let infoData = protocolsHeader.split(/,/);
1015
+
1016
+ if (!isAllowedOrigin(info.origin, ResolveIOServer.getServerConfig())) {
1017
+ cb(false, 401, 'Unauthorized');
1018
+ }
1019
+ else {
1020
+ let token = infoData[0];
1021
+ if (!token) {
1022
+ cb(false, 401, 'Unauthorized');
1023
+ }
1024
+ else {
1025
+ jwt.verify(token, ResolveIOServer.getServerConfig()['JWT_SECRET'], async (err, decoded) => {
1026
+ if (err) {
1027
+ cb(false, 401, 'Unauthorized');
1028
+ }
1029
+ else {
1030
+ info.req['id_user'] = decoded['id_user'];
1031
+ try {
1032
+ let user = await Users.findById(decoded['id_user']);
1033
+ if (user) {
1034
+ const socketAdmission = await this.evaluateClientSocketAdmission(decoded['id_user'], info.req);
1035
+ if (!socketAdmission.allowed) {
1036
+ cb(false, socketAdmission.statusCode, socketAdmission.message || 'Socket connection rejected.');
1037
+ return;
1038
+ }
1039
+ info.req['user'] = user.fullname;
1040
+ info.req['user_readonly'] = user.readonly || false;
1041
+ info.req['doc_user'] = user;
1042
+ info.req['client_ip'] = socketAdmission.clientIp;
1043
+ cb(true);
1044
+ }
1045
+ else {
1046
+ cb(false);
1047
+ }
1048
+ }
1049
+ catch {
1050
+ cb(false);
1051
+ }
1052
+ }
1053
+ });
1054
+ }
1055
+ }
1056
+ }
1057
+ }
1058
+ });
1059
+ }
1060
+
1061
+ /**
1062
+ * Listen for connections from clients or workers.
1063
+ */
1064
+ private listen(): void {
1065
+ const host = process.env.RESOLVEIO_BIND_HOST || '127.0.0.1';
1066
+ this._serverHTTP.listen(this._portHTTP, host, () => {
1067
+ console.log('Running HTTP/WS server on port %s', this._portHTTP);
1068
+ });
1069
+
1070
+ this._serverWSS.on('connection', async (ws, req) => {
1071
+ if (req.url && req.url.includes('workerToken=')) {
1072
+ // It's a WORKER
1073
+ let workerId = objectIdHexString();
1074
+ ws['id_worker'] = workerId;
1075
+ let workerIndex = null;
1076
+ let workerInstance = null;
1077
+ ws['supportsBinary'] = true;
1078
+
1079
+ if (req.url) {
1080
+ let rootUrl = ResolveIOServer.getServerConfig()['ROOT_URL'] || 'http://localhost';
1081
+ try {
1082
+ let requestUrl = new URL(req.url, rootUrl);
1083
+ workerIndex = requestUrl.searchParams.get('workerIndex');
1084
+ workerInstance = requestUrl.searchParams.get('workerInstance');
1085
+ }
1086
+ catch {
1087
+ workerIndex = null;
1088
+ workerInstance = null;
1089
+ }
1090
+ }
1091
+
1092
+ if (!workerIndex && req['workerIndex']) {
1093
+ workerIndex = req['workerIndex'];
1094
+ }
1095
+ if (!workerInstance && req['workerInstance']) {
1096
+ workerInstance = req['workerInstance'];
1097
+ }
1098
+
1099
+ if (workerIndex !== null && workerIndex !== undefined) {
1100
+ ws['workerIndex'] = workerIndex;
1101
+ }
1102
+ if (workerInstance !== null && workerInstance !== undefined) {
1103
+ ws['workerInstance'] = workerInstance;
1104
+ }
1105
+
1106
+ let workerIndexForLog = ws['workerIndex'] || 'UNKNOWN';
1107
+ let workerInstanceForLog = ws['workerInstance'] || 'UNKNOWN';
1108
+ console.log(new Date(), 'Worker Connected', workerIndexForLog, workerInstanceForLog);
1109
+
1110
+ this._workerDispatcherManager.addWorker(ws);
1111
+
1112
+ let interval = null;
1113
+ let lastComm = new Date();
1114
+ let missedPongs = 0;
1115
+ const heartbeatIntervalMs = 30000;
1116
+ const maxMissedPongs = 2;
1117
+ const maxSilenceMs = heartbeatIntervalMs * (maxMissedPongs + 1);
1118
+
1119
+ this._workerDispatcherManager.sendWorkerPayload(ws, 'ping');
1120
+
1121
+ interval = setInterval(() => {
1122
+ const now = Date.now();
1123
+ const last = lastComm ? lastComm.getTime() : 0;
1124
+ const silenceMs = last ? now - last : maxSilenceMs + 1;
1125
+ if (silenceMs > maxSilenceMs || missedPongs > maxMissedPongs) {
1126
+ this._workerDispatcherManager.disconnectWorker(ws['id_worker']);
1127
+ ws.close();
1128
+ return;
1129
+ }
1130
+ missedPongs += 1;
1131
+ this._workerDispatcherManager.sendWorkerPayload(ws, 'ping');
1132
+ }, heartbeatIntervalMs);
1133
+
1134
+ ws.on('message', (message: WebSocket.RawData) => {
1135
+ lastComm = new Date();
1136
+ if (typeof message === 'string') {
1137
+ if (message === 'ping') {
1138
+ this._workerDispatcherManager.sendWorkerPayload(ws, 'pong');
1139
+ }
1140
+ else if (message === 'pong') {
1141
+ missedPongs = 0;
1142
+ }
1143
+ else {
1144
+ this._workerDispatcherManager.handleWorkerMessage(ws['id_worker'], message);
1145
+ }
1146
+
1147
+ return;
1148
+ }
1149
+
1150
+ let buffer: Buffer;
1151
+
1152
+ if (Buffer.isBuffer(message)) {
1153
+ buffer = message;
1154
+ }
1155
+ else if (Array.isArray(message)) {
1156
+ const chunks = message as unknown as ReadonlyArray<Uint8Array>;
1157
+ buffer = Buffer.concat(chunks);
1158
+ }
1159
+ else if (message instanceof ArrayBuffer) {
1160
+ buffer = Buffer.from(message);
1161
+ }
1162
+ else if (ArrayBuffer.isView(message)) {
1163
+ const view = message as NodeJS.ArrayBufferView;
1164
+ buffer = Buffer.from(view.buffer, view.byteOffset, view.byteLength);
1165
+ }
1166
+ else {
1167
+ buffer = Buffer.from(message as any);
1168
+ }
1169
+
1170
+ if (buffer.length === 4) {
1171
+ let heartbeat = buffer.toString('utf8');
1172
+
1173
+ if (heartbeat === 'ping') {
1174
+ this._workerDispatcherManager.sendWorkerPayload(ws, 'pong');
1175
+ return;
1176
+ }
1177
+ else if (heartbeat === 'pong') {
1178
+ missedPongs = 0;
1179
+ return;
1180
+ }
1181
+ }
1182
+
1183
+ this._workerDispatcherManager.handleWorkerMessage(ws['id_worker'], buffer);
1184
+ });
1185
+
1186
+ ws.on('close', () => {
1187
+ this._workerDispatcherManager.disconnectWorker(ws['id_worker']);
1188
+
1189
+ console.log(new Date(), 'Worker disconnected:', workerId);
1190
+
1191
+ if (interval) {
1192
+ clearInterval(interval);
1193
+ }
1194
+ });
1195
+
1196
+ ws.on('error', (error) => {
1197
+ this._workerDispatcherManager.disconnectWorker(ws['id_worker']);
1198
+
1199
+ console.error('Error on WS Worker', error);
1200
+ ws.close();
1201
+ });
1202
+ }
1203
+ else {
1204
+ // Normal client
1205
+ ws['id_socket'] = objectIdHexString();
1206
+ ws['supportsBinary'] = true;
1207
+ ws['id_user'] = req['id_user'];
1208
+ ws['user'] = req['user'];
1209
+ ws['user_readonly'] = req['user_readonly'];
1210
+ ws['doc_user'] = req['doc_user'];
1211
+ ws['client_ip'] = this.resolveClientIp(req);
1212
+
1213
+ let socketAdmission: { allowed: boolean, statusCode: number, message: string, clientIp: string };
1214
+ try {
1215
+ socketAdmission = await this.evaluateClientSocketAdmission(ws['id_user'], req);
1216
+ }
1217
+ catch (socketPolicyError) {
1218
+ this.logConnectDebug('WS socket policy evaluation failed', {
1219
+ id_socket: ws['id_socket'],
1220
+ id_user: ws['id_user'],
1221
+ user: ws['user'],
1222
+ ip: ws['client_ip'],
1223
+ error: (socketPolicyError as Error)?.message || socketPolicyError
1224
+ });
1225
+ try {
1226
+ ws.close(1011, 'Socket policy error');
1227
+ }
1228
+ catch {}
1229
+ return;
1230
+ }
1231
+
1232
+ if (!socketAdmission.allowed) {
1233
+ this.logConnectDebug('WS client rejected', {
1234
+ id_socket: ws['id_socket'],
1235
+ id_user: ws['id_user'],
1236
+ user: ws['user'],
1237
+ ip: ws['client_ip'],
1238
+ reason: socketAdmission.message
1239
+ });
1240
+ try {
1241
+ ws.close(1008, this.buildSocketLimitCloseReason());
1242
+ }
1243
+ catch {}
1244
+ return;
1245
+ }
1246
+ ws['client_ip'] = socketAdmission.clientIp || ws['client_ip'];
1247
+
1248
+ this._websocketManager.addWebSocket(ws);
1249
+
1250
+ this.logConnectDebug('WS client connected', {
1251
+ id_socket: ws['id_socket'],
1252
+ id_user: ws['id_user'],
1253
+ user: ws['user'],
1254
+ url: req?.url,
1255
+ ip: ws['client_ip'],
1256
+ origin: req?.headers?.origin
1257
+ });
1258
+
1259
+ setTimeout(async () => {
1260
+ await this.triggerClientHeartbeat(ws);
1261
+ }, this._clientHeartbeatInitialDelayMs);
1262
+
1263
+ if (this.LOGGER === 'DEBUG') {
1264
+ console.log('Connection from user: ' + req['user']);
1265
+ }
1266
+
1267
+ ws['isAlive'] = true;
1268
+ ws['retryCnt'] = 0;
1269
+ ws.on('pong', () => {
1270
+ ws['isAlive'] = true;
1271
+ ws['pongTime'] = new Date();
1272
+ if (ws['pingTime']) {
1273
+ ws['latency'] = moment.duration(moment(ws['pongTime']).diff(ws['pingTime'])).asMilliseconds();
1274
+ this._subscriptionManager.loggedInLatency(ws);
1275
+ }
1276
+ });
1277
+
1278
+ ws.on('message', async (message: WebSocket.RawData) => {
1279
+ this._debugMsgRecv += 1;
1280
+ let socketData = [];
1281
+ let usedBinary = false;
1282
+ let bufferPayload: Buffer;
1283
+
1284
+ try {
1285
+ if (typeof message === 'string') {
1286
+ if (message === 'ping' || message === 'pong') {
1287
+ socketData = message;
1288
+ }
1289
+ else {
1290
+ socketData = JSON.parse(message, dateReviver);
1291
+ }
1292
+ }
1293
+ else if (Buffer.isBuffer(message)) {
1294
+ bufferPayload = message;
1295
+ let decodeResult = this.decodeBufferPayload(bufferPayload);
1296
+ socketData = decodeResult.data;
1297
+ usedBinary = decodeResult.usedBinary;
1298
+ }
1299
+ else if (Array.isArray(message)) {
1300
+ bufferPayload = Buffer.concat(message as unknown as ReadonlyArray<Uint8Array>);
1301
+ let decodeResult = this.decodeBufferPayload(bufferPayload);
1302
+ socketData = decodeResult.data;
1303
+ usedBinary = decodeResult.usedBinary;
1304
+ }
1305
+ else if (message instanceof ArrayBuffer) {
1306
+ bufferPayload = Buffer.from(message);
1307
+ let decodeResult = this.decodeBufferPayload(bufferPayload);
1308
+ socketData = decodeResult.data;
1309
+ usedBinary = decodeResult.usedBinary;
1310
+ }
1311
+ else if (ArrayBuffer.isView(message)) {
1312
+ const view = message as NodeJS.ArrayBufferView;
1313
+ bufferPayload = Buffer.from(view.buffer, view.byteOffset, view.byteLength);
1314
+ let decodeResult = this.decodeBufferPayload(bufferPayload);
1315
+ socketData = decodeResult.data;
1316
+ usedBinary = decodeResult.usedBinary;
1317
+ }
1318
+ else {
1319
+ throw new Error('Unsupported WebSocket message type: ' + typeof message);
1320
+ }
1321
+ }
1322
+ catch (e) {
1323
+ console.log('Error - WS message parse', e);
1324
+ const correlationId = objectIdHexString();
1325
+ const context = {
1326
+ rawBinary: bufferPayload ? bufferPayload.toString('base64') : undefined,
1327
+ rawMessage: typeof message === 'string' ? message : undefined,
1328
+ error: e instanceof Error ? { name: e.name, message: e.message, stack: e.stack } : e
1329
+ };
1330
+ await this.reportServerError(
1331
+ 'SERVER - JSON Parse Error - ' + ResolveIOServer.getServerConfig()['CLIENT_NAME'],
1332
+ correlationId,
1333
+ context,
1334
+ { context: 'websocket-message-parse' },
1335
+ 'error',
1336
+ e instanceof Error ? e.stack : undefined
1337
+ );
1338
+ return;
1339
+ }
1340
+
1341
+ if (usedBinary) {
1342
+ ws['supportsBinary'] = true;
1343
+ }
1344
+
1345
+ // call our existing processSocketMessage
1346
+ await this.processSocketMessage(ws, socketData);
1347
+ })
1348
+ .on('end', () => {
1349
+ ws.close();
1350
+ })
1351
+ .on('error', () => {
1352
+ ws.close()
1353
+ })
1354
+ .on('close', async () => {
1355
+ this.logConnectDebug('WS client closed', {
1356
+ id_socket: ws['id_socket'],
1357
+ id_user: ws['id_user'],
1358
+ user: ws['user']
1359
+ });
1360
+ await this.unsubscribeWS(ws);
1361
+ });
1362
+
1363
+ // Do not block message handler registration on DB write; this avoids losing
1364
+ // very-early subscription messages sent immediately after websocket open.
1365
+ setTimeout(async () => {
1366
+ try {
1367
+ await this._subscriptionManager.createLoggedInUser(ws['id_socket']);
1368
+ }
1369
+ catch (error) {
1370
+ console.error(new Date(), 'Error creating logged-in user', ws['id_socket'], error);
1371
+ this.logConnectDebug('Create logged-in user failed', {
1372
+ id_socket: ws['id_socket'],
1373
+ id_user: ws['id_user'],
1374
+ user: ws['user'],
1375
+ error: error?.message || error
1376
+ });
1377
+ }
1378
+ }, 0);
1379
+ }
1380
+ });
1381
+
1382
+ // Keep alive timer
1383
+ setInterval(async () => {
1384
+ for (let ws of this._serverWSS.clients) {
1385
+ if (ws['pingTime'] && Date.now() - ws['pingTime'].getTime() >= this._clientHeartbeatIntervalMs) {
1386
+ if (this.shouldDeferHeartbeat(ws)) {
1387
+ ws['isAlive'] = true;
1388
+ ws['retryCnt'] = 0;
1389
+ ws['pingTime'] = new Date();
1390
+ continue;
1391
+ }
1392
+
1393
+ if (ws['isAlive'] === false) {
1394
+ ws['retryCnt']++;
1395
+ if (ws['retryCnt'] >= 3) {
1396
+ await this.unsubscribeWS(ws);
1397
+ }
1398
+ else {
1399
+ await this.triggerClientHeartbeat(ws);
1400
+ }
1401
+ }
1402
+ else {
1403
+ ws['retryCnt'] = 0;
1404
+ ws['isAlive'] = false;
1405
+ await this.triggerClientHeartbeat(ws);
1406
+ }
1407
+ }
1408
+ }
1409
+ }, this._clientHeartbeatIntervalMs);
1410
+ }
1411
+
1412
+ private async processSocketMessage(ws: WebSocket, socketData: any) {
1413
+ if (typeof socketData === 'string' && socketData === 'ping') {
1414
+ if (ws && ws.readyState === WebSocket.OPEN) {
1415
+ ws.send('pong');
1416
+ }
1417
+ return;
1418
+ }
1419
+ else if (typeof socketData === 'string' && socketData === 'pong') {
1420
+ ws['isAlive'] = true;
1421
+ ws['pongTime'] = new Date();
1422
+ ws['latency'] = moment.duration(moment(ws['pongTime']).diff(ws['pingTime'])).asMilliseconds();
1423
+ this._subscriptionManager.loggedInLatency(ws);
1424
+ return;
1425
+ }
1426
+
1427
+ // If the top level is not an array, let's skip
1428
+ if (!Array.isArray(socketData[0])) {
1429
+ console.log('Invalid message format (expected array of arrays)', socketData);
1430
+ this.logConnectDebug('Invalid message format', {
1431
+ id_socket: ws ? ws['id_socket'] : null,
1432
+ user: ws ? ws['user'] : null,
1433
+ preview: Array.isArray(socketData) ? socketData.slice(0, 3) : socketData
1434
+ });
1435
+ return;
1436
+ }
1437
+
1438
+ // Handle each sub-message
1439
+ for (let message of socketData) {
1440
+ await this.handleClientMessage(ws, message);
1441
+ }
1442
+ }
1443
+
1444
+ private decodeBufferPayload(buffer: Buffer): { data: any; usedBinary: boolean } {
1445
+ const textPayload = buffer.toString('utf8');
1446
+
1447
+ if (this.looksLikeTextPayload(textPayload)) {
1448
+ try {
1449
+ return { data: this.parseTextFallback(textPayload), usedBinary: false };
1450
+ }
1451
+ catch {
1452
+ // fall through to attempt MessagePack decode
1453
+ }
1454
+ }
1455
+
1456
+ try {
1457
+ return { data: unpack(buffer), usedBinary: true };
1458
+ }
1459
+ catch (binaryErr) {
1460
+ try {
1461
+ return { data: this.parseTextFallback(textPayload), usedBinary: false };
1462
+ }
1463
+ catch {
1464
+ throw binaryErr;
1465
+ }
1466
+ }
1467
+ }
1468
+
1469
+ private parseTextFallback(rawMessage: string): any {
1470
+ if (rawMessage === 'ping' || rawMessage === 'pong') {
1471
+ return rawMessage;
1472
+ }
1473
+
1474
+ try {
1475
+ return JSON.parse(rawMessage, dateReviver);
1476
+ }
1477
+ catch (err) {
1478
+ throw err;
1479
+ }
1480
+ }
1481
+
1482
+ private looksLikeTextPayload(text: string): boolean {
1483
+ if (!text) {
1484
+ return false;
1485
+ }
1486
+
1487
+ const trimmed = text.trim();
1488
+ if (!trimmed) {
1489
+ return false;
1490
+ }
1491
+
1492
+ const first = trimmed[0];
1493
+ return first === '[' || first === '{' || first === '"' || first === 'p' || first === 'P';
1494
+ }
1495
+
1496
+ private resolveConnectDebug(): boolean {
1497
+ const config = ResolveIOServer.getServerConfig ? ResolveIOServer.getServerConfig() : null;
1498
+ const raw = process.env.WS_CONNECT_DEBUG
1499
+ ?? process.env.CONNECT_DEBUG
1500
+ ?? config?.['WS_CONNECT_DEBUG']
1501
+ ?? config?.['CONNECT_DEBUG'];
1502
+ return this.parseDebugFlag(raw);
1503
+ }
1504
+
1505
+ private resolvePerfDebug(): boolean {
1506
+ const config = ResolveIOServer.getServerConfig ? ResolveIOServer.getServerConfig() : null;
1507
+ const raw = process.env.PERF_DEBUG
1508
+ ?? process.env.CPU_DEBUG
1509
+ ?? config?.['PERF_DEBUG']
1510
+ ?? config?.['CPU_DEBUG'];
1511
+ return this.parseDebugFlag(raw);
1512
+ }
1513
+
1514
+ private resolveCpuProfileOnStart(): boolean {
1515
+ const config = ResolveIOServer.getServerConfig ? ResolveIOServer.getServerConfig() : null;
1516
+ const raw = process.env.CPU_PROFILE_ON_START
1517
+ ?? process.env.CPU_PROFILE_START
1518
+ ?? config?.['CPU_PROFILE_ON_START']
1519
+ ?? config?.['CPU_PROFILE_START'];
1520
+ return this.parseDebugFlag(raw);
1521
+ }
1522
+
1523
+ private resolveCpuProfileAuto(): boolean {
1524
+ const config = ResolveIOServer.getServerConfig ? ResolveIOServer.getServerConfig() : null;
1525
+ const raw = process.env.CPU_PROFILE_AUTO
1526
+ ?? config?.['CPU_PROFILE_AUTO'];
1527
+ return this.parseDebugFlag(raw);
1528
+ }
1529
+
1530
+ private resolveCpuProfileDurationMs(): number {
1531
+ const config = ResolveIOServer.getServerConfig ? ResolveIOServer.getServerConfig() : null;
1532
+ const raw = process.env.CPU_PROFILE_DURATION_MS
1533
+ ?? process.env.PERF_PROFILE_DURATION_MS
1534
+ ?? config?.['CPU_PROFILE_DURATION_MS']
1535
+ ?? config?.['PERF_PROFILE_DURATION_MS'];
1536
+ return this.parsePositiveInt(raw, this._cpuProfileDurationMs);
1537
+ }
1538
+
1539
+ private resolveCpuProfileThresholdPct(): number {
1540
+ const config = ResolveIOServer.getServerConfig ? ResolveIOServer.getServerConfig() : null;
1541
+ const raw = process.env.CPU_PROFILE_THRESHOLD_PCT
1542
+ ?? process.env.PERF_PROFILE_THRESHOLD_PCT
1543
+ ?? config?.['CPU_PROFILE_THRESHOLD_PCT']
1544
+ ?? config?.['PERF_PROFILE_THRESHOLD_PCT'];
1545
+ return this.parsePositiveFloat(raw, this._cpuProfileThresholdPct);
1546
+ }
1547
+
1548
+ private resolveCpuProfileTriggerCount(): number {
1549
+ const config = ResolveIOServer.getServerConfig ? ResolveIOServer.getServerConfig() : null;
1550
+ const raw = process.env.CPU_PROFILE_TRIGGER_COUNT
1551
+ ?? config?.['CPU_PROFILE_TRIGGER_COUNT'];
1552
+ return this.parsePositiveInt(raw, this._cpuProfileTriggerCount);
1553
+ }
1554
+
1555
+ private resolveCpuProfileDir(): string | null {
1556
+ const config = ResolveIOServer.getServerConfig ? ResolveIOServer.getServerConfig() : null;
1557
+ const raw = process.env.CPU_PROFILE_DIR
1558
+ ?? config?.['CPU_PROFILE_DIR'];
1559
+ return typeof raw === 'string' && raw.trim() ? raw.trim() : null;
1560
+ }
1561
+
1562
+ private resolveTimerDebug(): boolean {
1563
+ const config = ResolveIOServer.getServerConfig ? ResolveIOServer.getServerConfig() : null;
1564
+ const raw = process.env.TIMER_DEBUG
1565
+ ?? config?.['TIMER_DEBUG'];
1566
+ return this.parseDebugFlag(raw);
1567
+ }
1568
+
1569
+ private resolveTimerDebugThresholdMs(): number {
1570
+ const config = ResolveIOServer.getServerConfig ? ResolveIOServer.getServerConfig() : null;
1571
+ const raw = process.env.TIMER_DEBUG_THRESHOLD_MS
1572
+ ?? config?.['TIMER_DEBUG_THRESHOLD_MS'];
1573
+ return this.parsePositiveFloat(raw, this._timerDebugThresholdMs);
1574
+ }
1575
+
1576
+ private resolveTimerDebugMinDelayMs(): number {
1577
+ const config = ResolveIOServer.getServerConfig ? ResolveIOServer.getServerConfig() : null;
1578
+ const raw = process.env.TIMER_DEBUG_MIN_DELAY_MS
1579
+ ?? config?.['TIMER_DEBUG_MIN_DELAY_MS'];
1580
+ return this.parsePositiveFloat(raw, this._timerDebugMinDelayMs);
1581
+ }
1582
+
1583
+ private resolveTimerDebugSampleRate(): number {
1584
+ const config = ResolveIOServer.getServerConfig ? ResolveIOServer.getServerConfig() : null;
1585
+ const raw = process.env.TIMER_DEBUG_SAMPLE_RATE
1586
+ ?? config?.['TIMER_DEBUG_SAMPLE_RATE'];
1587
+ const parsed = parseFloat(raw ?? '');
1588
+ if (Number.isFinite(parsed) && parsed > 0 && parsed <= 1) {
1589
+ return parsed;
1590
+ }
1591
+ return this._timerDebugSampleRate;
1592
+ }
1593
+
1594
+ private resolveTimerDebugLogLimit(): number {
1595
+ const config = ResolveIOServer.getServerConfig ? ResolveIOServer.getServerConfig() : null;
1596
+ const raw = process.env.TIMER_DEBUG_LOG_LIMIT
1597
+ ?? config?.['TIMER_DEBUG_LOG_LIMIT'];
1598
+ return this.parsePositiveInt(raw, this._timerDebugLogLimit);
1599
+ }
1600
+
1601
+ private normalizeWorkerSelectorValue(value?: string | number | null): string | null {
1602
+ if (value === null || value === undefined) {
1603
+ return null;
1604
+ }
1605
+
1606
+ const normalized = String(value).trim();
1607
+ return normalized.length ? normalized : null;
1608
+ }
1609
+
1610
+ private parseWorkerSelector(value: any): Set<string> | null {
1611
+ if (value === null || value === undefined) {
1612
+ return null;
1613
+ }
1614
+
1615
+ const raw = Array.isArray(value) ? value.join(',') : String(value);
1616
+ const parts = raw.split(',').map(part => part.trim()).filter(Boolean);
1617
+ if (!parts.length) {
1618
+ return null;
1619
+ }
1620
+
1621
+ return new Set(parts);
1622
+ }
1623
+
1624
+ private workerMatchesSelector(
1625
+ workerIndex: string | null,
1626
+ workerInstance: string | null,
1627
+ indexes: Set<string> | null,
1628
+ instances: Set<string> | null
1629
+ ): boolean {
1630
+ if (!indexes && !instances) {
1631
+ return false;
1632
+ }
1633
+
1634
+ const indexMatch = indexes ? (workerIndex ? indexes.has(workerIndex) : false) : true;
1635
+ const instanceMatch = instances ? (workerInstance ? instances.has(workerInstance) : false) : true;
1636
+ return indexMatch && instanceMatch;
1637
+ }
1638
+
1639
+ private resolveWorkerRole(): 'Codex' | 'Subscription' | 'Other' {
1640
+ const workerIndex = this.normalizeWorkerSelectorValue(process.env.WORKER_INDEX);
1641
+ const workerInstance = this.normalizeWorkerSelectorValue(process.env.NODE_APP_INSTANCE);
1642
+ const publicationIndexes = this.parseWorkerSelector(
1643
+ process.env.PUBLICATION_WORKER_INDEX
1644
+ || process.env.SUBSCRIPTION_WORKER_INDEX
1645
+ || process.env.WORKER_PUBLICATION_INDEX
1646
+ );
1647
+ const publicationInstances = this.parseWorkerSelector(
1648
+ process.env.PUBLICATION_WORKER_INSTANCE
1649
+ || process.env.SUBSCRIPTION_WORKER_INSTANCE
1650
+ || process.env.WORKER_PUBLICATION_INSTANCE
1651
+ );
1652
+ const codexIndexes = this.parseWorkerSelector(
1653
+ process.env.AI_ASSISTANT_CODEX_WORKER_INDEX
1654
+ || process.env.CODEX_WORKER_INDEX
1655
+ || process.env.WORKER_CODEX_INDEX
1656
+ );
1657
+ const codexInstances = this.parseWorkerSelector(
1658
+ process.env.AI_ASSISTANT_CODEX_WORKER_INSTANCE
1659
+ || process.env.CODEX_WORKER_INSTANCE
1660
+ || process.env.WORKER_CODEX_INSTANCE
1661
+ );
1662
+
1663
+ if (this.workerMatchesSelector(workerIndex, workerInstance, publicationIndexes, publicationInstances)) {
1664
+ return 'Subscription';
1665
+ }
1666
+
1667
+ if (this.workerMatchesSelector(workerIndex, workerInstance, codexIndexes, codexInstances)) {
1668
+ return 'Codex';
1669
+ }
1670
+
1671
+ return 'Other';
1672
+ }
1673
+
1674
+ private parsePositiveInt(value: any, fallback: number): number {
1675
+ const parsed = parseInt(value ?? '', 10);
1676
+ if (Number.isNaN(parsed) || parsed <= 0) {
1677
+ return fallback;
1678
+ }
1679
+ return parsed;
1680
+ }
1681
+
1682
+ private parsePositiveFloat(value: any, fallback: number): number {
1683
+ const parsed = parseFloat(value ?? '');
1684
+ if (Number.isNaN(parsed) || parsed <= 0) {
1685
+ return fallback;
1686
+ }
1687
+ return parsed;
1688
+ }
1689
+
1690
+ private startPerfDebug(): void {
1691
+ if (!this._perfDebug || this._perfDebugTimer) {
1692
+ return;
1693
+ }
1694
+
1695
+ this._perfDebugIntervalMs = this.parsePositiveInt(
1696
+ process.env.PERF_DEBUG_INTERVAL_MS ?? process.env.CPU_DEBUG_INTERVAL_MS,
1697
+ this._perfDebugIntervalMs
1698
+ );
1699
+
1700
+ this._perfDebugLastCpu = process.cpuUsage();
1701
+ this._perfDebugLastTs = Date.now();
1702
+ this._eventLoopHistogram = monitorEventLoopDelay({ resolution: 20 });
1703
+ this._eventLoopHistogram.enable();
1704
+
1705
+ this._perfDebugTimer = setInterval(() => {
1706
+ const now = Date.now();
1707
+ const elapsedMs = now - this._perfDebugLastTs;
1708
+ const cpuDiff = this._perfDebugLastCpu ? process.cpuUsage(this._perfDebugLastCpu) : process.cpuUsage();
1709
+ const cpuMs = (cpuDiff.user + cpuDiff.system) / 1000;
1710
+ const cpuPct = elapsedMs > 0 ? (cpuMs / elapsedMs) * 100 : 0;
1711
+
1712
+ const mem = process.memoryUsage();
1713
+ const heapUsedMb = round((mem.heapUsed / 1024 / 1024) * 10) / 10;
1714
+ const rssMb = round((mem.rss / 1024 / 1024) * 10) / 10;
1715
+
1716
+ const handles = typeof (process as any)._getActiveHandles === 'function'
1717
+ ? (process as any)._getActiveHandles().length
1718
+ : null;
1719
+ const requests = typeof (process as any)._getActiveRequests === 'function'
1720
+ ? (process as any)._getActiveRequests().length
1721
+ : null;
1722
+
1723
+ const histogram = this._eventLoopHistogram;
1724
+ const eventLoop = histogram ? {
1725
+ meanMs: round((histogram.mean / 1e6) * 100) / 100,
1726
+ p50Ms: round((histogram.percentile(50) / 1e6) * 100) / 100,
1727
+ p95Ms: round((histogram.percentile(95) / 1e6) * 100) / 100,
1728
+ p99Ms: round((histogram.percentile(99) / 1e6) * 100) / 100,
1729
+ maxMs: round((histogram.max / 1e6) * 100) / 100
1730
+ } : null;
1731
+ histogram?.reset();
1732
+
1733
+ console.log(new Date(), '[Perf Debug]', JSON.stringify({
1734
+ cpuPct: round(cpuPct * 10) / 10,
1735
+ cpuMs: round(cpuMs),
1736
+ elapsedMs,
1737
+ eventLoop,
1738
+ heapUsedMb,
1739
+ rssMb,
1740
+ activeHandles: handles,
1741
+ activeRequests: requests,
1742
+ msgRecv: this._debugMsgRecv,
1743
+ msgQueue: this._debugMsgQueue
1744
+ }));
1745
+
1746
+ if (this._cpuProfileAuto) {
1747
+ if (cpuPct >= this._cpuProfileThresholdPct) {
1748
+ this._cpuProfileHighCount += 1;
1749
+ }
1750
+ else {
1751
+ this._cpuProfileHighCount = 0;
1752
+ }
1753
+
1754
+ if (this._cpuProfileHighCount >= this._cpuProfileTriggerCount) {
1755
+ this._cpuProfileHighCount = 0;
1756
+ this.startCpuProfile('auto-high-cpu');
1757
+ }
1758
+ }
1759
+
1760
+ this._perfDebugLastCpu = process.cpuUsage();
1761
+ this._perfDebugLastTs = now;
1762
+ }, this._perfDebugIntervalMs);
1763
+ }
1764
+
1765
+ private installTimerDebug(): void {
1766
+ if (!this._timerDebug) {
1767
+ return;
1768
+ }
1769
+
1770
+ const g = global as unknown as { __resolveioTimerDebugInstalled?: boolean };
1771
+ if (g.__resolveioTimerDebugInstalled) {
1772
+ return;
1773
+ }
1774
+ g.__resolveioTimerDebugInstalled = true;
1775
+
1776
+ const originalSetTimeout = global.setTimeout;
1777
+ const originalSetInterval = global.setInterval;
1778
+ const thresholdMs = this._timerDebugThresholdMs;
1779
+ const minDelayMs = this._timerDebugMinDelayMs;
1780
+ const sampleRate = this._timerDebugSampleRate;
1781
+
1782
+ const shouldSample = () => sampleRate >= 1 || Math.random() <= sampleRate;
1783
+
1784
+ const logTimer = (type: string, durationMs: number, delayMs: number | undefined, createdStack: string | undefined) => {
1785
+ if (this._timerDebugLogLimit > 0 && this._timerDebugLogCount >= this._timerDebugLogLimit) {
1786
+ return;
1787
+ }
1788
+ this._timerDebugLogCount += 1;
1789
+ const stackLines = createdStack
1790
+ ? createdStack.split('\n').slice(1, 6).map(line => line.trim()).join(' | ')
1791
+ : undefined;
1792
+ console.log(new Date(), '[Timer Debug]', JSON.stringify({
1793
+ type,
1794
+ durationMs: round(durationMs * 10) / 10,
1795
+ delayMs: delayMs ?? null,
1796
+ createdAt: stackLines
1797
+ }));
1798
+ };
1799
+
1800
+ const wrapTimer = (handler: any, type: string, delayMs?: number) => {
1801
+ if (typeof handler !== 'function') {
1802
+ return handler;
1803
+ }
1804
+
1805
+ const createdStack = new Error().stack;
1806
+ const shouldFlagShortDelay = typeof delayMs === 'number' && delayMs <= minDelayMs;
1807
+
1808
+ return function wrappedTimer(this: any, ...args: any[]) {
1809
+ const start = process.hrtime.bigint();
1810
+ try {
1811
+ return handler.apply(this, args);
1812
+ }
1813
+ finally {
1814
+ const elapsedMs = Number(process.hrtime.bigint() - start) / 1e6;
1815
+ if (shouldSample() && (elapsedMs >= thresholdMs || shouldFlagShortDelay)) {
1816
+ logTimer(type, elapsedMs, delayMs, createdStack);
1817
+ }
1818
+ }
1819
+ };
1820
+ };
1821
+
1822
+ global.setTimeout = ((handler: any, timeout?: number, ...args: any[]) => {
1823
+ return originalSetTimeout(wrapTimer(handler, 'setTimeout', timeout), timeout as any, ...args);
1824
+ }) as typeof setTimeout;
1825
+
1826
+ global.setInterval = ((handler: any, timeout?: number, ...args: any[]) => {
1827
+ return originalSetInterval(wrapTimer(handler, 'setInterval', timeout), timeout as any, ...args);
1828
+ }) as typeof setInterval;
1829
+ }
1830
+
1831
+ private startCpuProfile(trigger: string): void {
1832
+ if (this._cpuProfileSession) {
1833
+ return;
1834
+ }
1835
+
1836
+ try {
1837
+ const session = new inspector.Session();
1838
+ session.connect();
1839
+ session.post('Profiler.enable', () => {
1840
+ session.post('Profiler.start', () => {
1841
+ this._cpuProfileSession = session;
1842
+ console.log(new Date(), '[Perf Debug]', 'CPU profile started', trigger);
1843
+ setTimeout(() => {
1844
+ this.stopCpuProfile(trigger);
1845
+ }, this._cpuProfileDurationMs);
1846
+ });
1847
+ });
1848
+ }
1849
+ catch (error) {
1850
+ console.error(new Date(), '[Perf Debug]', 'CPU profile start failed', error);
1851
+ }
1852
+ }
1853
+
1854
+ private stopCpuProfile(trigger: string): void {
1855
+ const session = this._cpuProfileSession;
1856
+ if (!session) {
1857
+ return;
1858
+ }
1859
+
1860
+ session.post('Profiler.stop', (err, res: { profile?: unknown }) => {
1861
+ if (err) {
1862
+ console.error(new Date(), '[Perf Debug]', 'CPU profile stop failed', err);
1863
+ }
1864
+ else {
1865
+ try {
1866
+ const dir = this.resolveWritableProfileDir();
1867
+ const filename = `resolveio-cpuprofile-${process.pid}-${Date.now()}.cpuprofile`;
1868
+ const filePath = path.join(dir, filename);
1869
+ fs.writeFileSync(filePath, JSON.stringify(res?.profile ?? {}));
1870
+ console.log(new Date(), '[Perf Debug]', 'CPU profile saved', filePath, trigger);
1871
+ }
1872
+ catch (writeErr) {
1873
+ console.error(new Date(), '[Perf Debug]', 'CPU profile write failed', writeErr);
1874
+ }
1875
+ }
1876
+
1877
+ try {
1878
+ session.disconnect();
1879
+ }
1880
+ catch {}
1881
+
1882
+ this._cpuProfileSession = null;
1883
+ });
1884
+ }
1885
+
1886
+ private resolveWritableProfileDir(): string {
1887
+ const preferred = this._cpuProfileDir;
1888
+ if (preferred) {
1889
+ try {
1890
+ fs.mkdirSync(preferred, { recursive: true });
1891
+ return preferred;
1892
+ }
1893
+ catch {}
1894
+ }
1895
+
1896
+ return os.tmpdir();
1897
+ }
1898
+
1899
+ private parseDebugFlag(value: any): boolean {
1900
+ if (value === true) {
1901
+ return true;
1902
+ }
1903
+
1904
+ if (value === false || value === null || value === undefined) {
1905
+ return false;
1906
+ }
1907
+
1908
+ if (typeof value === 'number') {
1909
+ return value === 1;
1910
+ }
1911
+
1912
+ if (typeof value === 'string') {
1913
+ const normalized = value.trim().toLowerCase();
1914
+ return ['1', 'true', 'yes', 'y', 'on'].includes(normalized);
1915
+ }
1916
+
1917
+ return false;
1918
+ }
1919
+
1920
+ private parseOptionalBoolean(value: any): boolean | null {
1921
+ if (value === null || value === undefined) {
1922
+ return null;
1923
+ }
1924
+ const normalized = `${value}`.trim();
1925
+ if (!normalized) {
1926
+ return null;
1927
+ }
1928
+ return this.parseDebugFlag(normalized);
1929
+ }
1930
+
1931
+ private parseNonNegativeInt(value: any, fallback: number): number {
1932
+ const parsed = parseInt(`${value ?? ''}`, 10);
1933
+ if (Number.isNaN(parsed) || parsed < 0) {
1934
+ return fallback;
1935
+ }
1936
+ return parsed;
1937
+ }
1938
+
1939
+ private resolveSocketTier(): string {
1940
+ const config = ResolveIOServer.getServerConfig() || {};
1941
+ return `${process.env.AI_CODER_PLAN_TIER || config['AI_CODER_PLAN_TIER'] || ''}`.trim().toLowerCase();
1942
+ }
1943
+
1944
+ private resolveSocketTierKeySuffix(planTier: string): string {
1945
+ return `${planTier || ''}`
1946
+ .trim()
1947
+ .toUpperCase()
1948
+ .replace(/[^A-Z0-9]+/g, '_');
1949
+ }
1950
+
1951
+ private resolveMaxClientSockets(planTier: string): number {
1952
+ const config = ResolveIOServer.getServerConfig() || {};
1953
+ const explicitLimit = this.parseNonNegativeInt(
1954
+ config['AI_CODER_MAX_SOCKETS'] ?? process.env.AI_CODER_MAX_SOCKETS,
1955
+ -1
1956
+ );
1957
+ if (explicitLimit >= 0) {
1958
+ return explicitLimit;
1959
+ }
1960
+
1961
+ const normalizedTier = `${planTier || ''}`.trim().toLowerCase();
1962
+ const tierKeySuffix = this.resolveSocketTierKeySuffix(normalizedTier);
1963
+ if (tierKeySuffix) {
1964
+ const tierLimitKey = `AI_CODER_MAX_SOCKETS_${tierKeySuffix}`;
1965
+ const tierLimit = this.parseNonNegativeInt(
1966
+ config[tierLimitKey] ?? process.env[tierLimitKey],
1967
+ -1
1968
+ );
1969
+ if (tierLimit >= 0) {
1970
+ return tierLimit;
1971
+ }
1972
+ }
1973
+
1974
+ const maxUsers = this.parseNonNegativeInt(
1975
+ config['AI_CODER_MAX_USERS'] ?? process.env.AI_CODER_MAX_USERS,
1976
+ -1
1977
+ );
1978
+ if (maxUsers > 0) {
1979
+ return maxUsers === 1 ? 1 : maxUsers * 2;
1980
+ }
1981
+
1982
+ if (normalizedTier === 'tool') {
1983
+ return 1;
1984
+ }
1985
+ if (normalizedTier === 'small') {
1986
+ return 10;
1987
+ }
1988
+ if (normalizedTier === 'medium') {
1989
+ return 50;
1990
+ }
1991
+ if (normalizedTier === 'large') {
1992
+ return 200;
1993
+ }
1994
+ if (normalizedTier === 'enterprise') {
1995
+ return 0;
1996
+ }
1997
+
1998
+ return 0;
1999
+ }
2000
+
2001
+ private resolveSingleIpPerUserPolicy(planTier: string): boolean {
2002
+ const config = ResolveIOServer.getServerConfig() || {};
2003
+ const normalizedTier = `${planTier || ''}`.trim().toLowerCase();
2004
+ const tierKeySuffix = this.resolveSocketTierKeySuffix(normalizedTier);
2005
+ if (tierKeySuffix) {
2006
+ const tierPolicyKey = `AI_CODER_SINGLE_IP_PER_USER_${tierKeySuffix}`;
2007
+ const tierPolicy = this.parseOptionalBoolean(
2008
+ config[tierPolicyKey] ?? process.env[tierPolicyKey]
2009
+ );
2010
+ if (tierPolicy !== null) {
2011
+ return tierPolicy;
2012
+ }
2013
+ }
2014
+
2015
+ const policy = this.parseOptionalBoolean(
2016
+ config['AI_CODER_SINGLE_IP_PER_USER'] ?? process.env.AI_CODER_SINGLE_IP_PER_USER
2017
+ );
2018
+ if (policy !== null) {
2019
+ return policy;
2020
+ }
2021
+
2022
+ return !!normalizedTier;
2023
+ }
2024
+
2025
+ private resolveSocketPolicyUpgradeUrl(): string {
2026
+ const config = ResolveIOServer.getServerConfig() || {};
2027
+ const direct = `${config['AI_CODER_SOCKET_UPGRADE_URL'] || process.env.AI_CODER_SOCKET_UPGRADE_URL || ''}`.trim();
2028
+ if (direct) {
2029
+ return direct.replace(/\/$/, '');
2030
+ }
2031
+
2032
+ const dashboard = `${config['AI_CODER_CLIENT_DASHBOARD_URL'] || process.env.AI_CODER_CLIENT_DASHBOARD_URL || ''}`.trim();
2033
+ if (dashboard) {
2034
+ return dashboard.replace(/\/$/, '');
2035
+ }
2036
+
2037
+ const root = `${config['AI_CODER_ROOT_URL'] || process.env.AI_CODER_ROOT_URL || config['ROOT_URL'] || process.env.ROOT_URL || ''}`.trim();
2038
+ if (!root) {
2039
+ return '';
2040
+ }
2041
+
2042
+ return `${root.replace(/\/$/, '')}/dashboard/client`;
2043
+ }
2044
+
2045
+ private resolveSocketUpgradeUrlWithAppId(): string {
2046
+ const base = `${this._socketPolicyUpgradeUrl || ''}`.trim();
2047
+ if (!base) {
2048
+ return '';
2049
+ }
2050
+ const appId = `${process.env.AI_CODER_APP_ID || ''}`.trim();
2051
+ if (!appId) {
2052
+ return base;
2053
+ }
2054
+ const separator = base.includes('?') ? '&' : '?';
2055
+ return `${base}${separator}appId=${encodeURIComponent(appId)}`;
2056
+ }
2057
+
2058
+ private normalizeIpAddress(value: any): string {
2059
+ let ip = `${value || ''}`.trim();
2060
+ if (!ip) {
2061
+ return '';
2062
+ }
2063
+ if (ip.includes(',')) {
2064
+ ip = ip.split(',')[0].trim();
2065
+ }
2066
+ if (ip.startsWith('::ffff:')) {
2067
+ ip = ip.slice(7);
2068
+ }
2069
+ if (/^\d+\.\d+\.\d+\.\d+:\d+$/.test(ip)) {
2070
+ ip = ip.split(':')[0].trim();
2071
+ }
2072
+ if (ip === '::1') {
2073
+ return '127.0.0.1';
2074
+ }
2075
+ return ip.toLowerCase();
2076
+ }
2077
+
2078
+ private resolveClientIp(req: any): string {
2079
+ const forwardedFor = this.normalizeHeaderValue(req?.headers?.['x-forwarded-for']);
2080
+ if (forwardedFor) {
2081
+ return this.normalizeIpAddress(forwardedFor);
2082
+ }
2083
+ const realIp = this.normalizeHeaderValue(req?.headers?.['x-real-ip']);
2084
+ if (realIp) {
2085
+ return this.normalizeIpAddress(realIp);
2086
+ }
2087
+ const socketIp = req?.socket?.remoteAddress || req?.connection?.remoteAddress || req?.ip || '';
2088
+ return this.normalizeIpAddress(socketIp);
2089
+ }
2090
+
2091
+ private buildSocketLimitMessage(activeSockets: number): string {
2092
+ const tierLabel = this._socketTier ? this._socketTier[0].toUpperCase() + this._socketTier.slice(1) : 'Current';
2093
+ const upgradeUrl = this.resolveSocketUpgradeUrlWithAppId();
2094
+ const base = `Socket connection limit reached (${activeSockets}/${this._maxClientSockets}) for the ${tierLabel} tier.`;
2095
+ if (!upgradeUrl) {
2096
+ return `${base} Upgrade to increase capacity.`;
2097
+ }
2098
+ return `${base} Upgrade at ${upgradeUrl}.`;
2099
+ }
2100
+
2101
+ private buildSocketLimitCloseReason(): string {
2102
+ return `Socket limit reached (${this._maxClientSockets})`;
2103
+ }
2104
+
2105
+ private async disconnectUserSocketsFromDifferentIps(idUser: string, incomingIp: string): Promise<number> {
2106
+ if (!this._websocketManager) {
2107
+ return 0;
2108
+ }
2109
+
2110
+ const normalizedUser = `${idUser || ''}`.trim();
2111
+ const normalizedIncomingIp = this.normalizeIpAddress(incomingIp);
2112
+ if (!normalizedUser || !normalizedIncomingIp) {
2113
+ return 0;
2114
+ }
2115
+
2116
+ const userSockets = this._websocketManager.getUserWebSockets(normalizedUser);
2117
+ let disconnected = 0;
2118
+ for (const existingSocket of userSockets) {
2119
+ const existingIp = this.normalizeIpAddress(existingSocket?.['client_ip']);
2120
+ if (!existingIp || existingIp === normalizedIncomingIp) {
2121
+ continue;
2122
+ }
2123
+
2124
+ this.logConnectDebug('WS single-ip enforcement disconnect', {
2125
+ id_socket: existingSocket?.['id_socket'],
2126
+ id_user: normalizedUser,
2127
+ existingIp,
2128
+ incomingIp: normalizedIncomingIp
2129
+ });
2130
+
2131
+ await this.unsubscribeWS(existingSocket);
2132
+ if (existingSocket.readyState === WebSocket.OPEN || existingSocket.readyState === WebSocket.CONNECTING) {
2133
+ try {
2134
+ existingSocket.close(1008, 'Signed in from another IP');
2135
+ }
2136
+ catch {}
2137
+ }
2138
+ disconnected += 1;
2139
+ }
2140
+
2141
+ return disconnected;
2142
+ }
2143
+
2144
+ private async evaluateClientSocketAdmission(idUser: string, req: any): Promise<{ allowed: boolean, statusCode: number, message: string, clientIp: string }> {
2145
+ const normalizedUser = `${idUser || ''}`.trim();
2146
+ const clientIp = this.resolveClientIp(req);
2147
+
2148
+ if (this._singleIpPerUser) {
2149
+ await this.disconnectUserSocketsFromDifferentIps(normalizedUser, clientIp);
2150
+ }
2151
+
2152
+ if (this._maxClientSockets > 0 && this._websocketManager) {
2153
+ const activeSockets = this._websocketManager.getActiveWebSocketCount();
2154
+ if (activeSockets >= this._maxClientSockets) {
2155
+ const message = this.buildSocketLimitMessage(activeSockets);
2156
+ this.logConnectDebug('WS socket limit blocked', {
2157
+ id_user: normalizedUser,
2158
+ clientIp,
2159
+ activeSockets,
2160
+ maxClientSockets: this._maxClientSockets
2161
+ });
2162
+ return {
2163
+ allowed: false,
2164
+ statusCode: 429,
2165
+ message,
2166
+ clientIp
2167
+ };
2168
+ }
2169
+ }
2170
+
2171
+ return {
2172
+ allowed: true,
2173
+ statusCode: 200,
2174
+ message: '',
2175
+ clientIp
2176
+ };
2177
+ }
2178
+
2179
+ private logConnectDebug(message: string, details?: Record<string, unknown>): void {
2180
+ if (!this._wsConnectDebug) {
2181
+ return;
2182
+ }
2183
+
2184
+ if (details) {
2185
+ console.log(new Date(), '[Connect Debug]', message, JSON.stringify(details));
2186
+ }
2187
+ else {
2188
+ console.log(new Date(), '[Connect Debug]', message);
2189
+ }
2190
+ }
2191
+
2192
+ private async triggerClientHeartbeat(ws: WebSocket): Promise<void> {
2193
+ if (!ws || ws.readyState !== WebSocket.OPEN) {
2194
+ return;
2195
+ }
2196
+
2197
+ ws['pingTime'] = new Date();
2198
+
2199
+ try {
2200
+ if (typeof ws.ping === 'function') {
2201
+ ws.ping();
2202
+ }
2203
+ }
2204
+ catch (err) {
2205
+ if (this._methodManager?.getEnableDebug()) {
2206
+ console.log(new Date(), 'Server App', 'Error WS Ping Frame', err);
2207
+ }
2208
+ await this.unsubscribeWS(ws);
2209
+ return;
2210
+ }
2211
+
2212
+ ws.send('ping', async (error) => {
2213
+ if (error) {
2214
+ if (this._methodManager?.getEnableDebug()) {
2215
+ console.log(new Date(), 'Server App', 'Error WS Ping');
2216
+ }
2217
+ await this.unsubscribeWS(ws);
2218
+ }
2219
+ });
2220
+ }
2221
+
2222
+ private shouldDeferHeartbeat(ws: WebSocket): boolean {
2223
+ if (!ws || ws.readyState !== WebSocket.OPEN) {
2224
+ return false;
2225
+ }
2226
+
2227
+ const bufferedAmount = typeof ws.bufferedAmount === 'number' ? ws.bufferedAmount : 0;
2228
+ return bufferedAmount >= this._clientHeartbeatBackpressureBytes;
2229
+ }
2230
+
2231
+ private async handleClientMessage(ws: WebSocket, msg: any[]): Promise<void> {
2232
+ // This is basically your old logic from processSocketMessage,
2233
+ // but we'll insert our worker-queue logic for "method" calls.
2234
+ try {
2235
+ let messageRoute = msg[0];
2236
+ let messageDate = msg[1];
2237
+ let messageId = msg[2];
2238
+ let type = msg[3];
2239
+
2240
+ if (!this.publicProgram && this._clientRoutes.some(a => messageRoute.includes(a)) && !ws['doc_user'].roles.groups.some(a => a.views.some(b => messageRoute.includes(b) || b.includes(messageRoute))) && !ws['doc_user'].roles.super_admin) {
2241
+ return;
2242
+ }
2243
+
2244
+ if (type === 'subscription') {
2245
+ let subType = msg[4];
2246
+ let pub = msg[5];
2247
+ this.logConnectDebug('Subscription message', {
2248
+ subType,
2249
+ publication: pub,
2250
+ messageId,
2251
+ messageRoute,
2252
+ args: Math.max(0, msg.length - 6),
2253
+ id_socket: ws ? ws['id_socket'] : null,
2254
+ user: ws ? ws['user'] : null
2255
+ });
2256
+
2257
+ if (subType === 'sub') {
2258
+ await this._subscriptionManager.subscribe(messageRoute, messageDate, ws, messageId, pub, msg.slice(6));
2259
+ }
2260
+ else {
2261
+ this._subscriptionManager.unsubscribe(messageRoute, messageDate, ws, messageId, pub, msg.slice(6));
2262
+ }
2263
+ }
2264
+ else if (!this.publicProgram && type === 'offline') {
2265
+ let serverRes: ServerResponseModel = {
2266
+ messageId: messageId,
2267
+ hasError: false,
2268
+ data: 'ACK'
2269
+ };
2270
+
2271
+ if (ws && ws.readyState === WebSocket.OPEN) {
2272
+ this._websocketManager.send(ws, serverRes);
2273
+ }
2274
+
2275
+ this._offlineUpdates.push(ws);
2276
+ let offlineUpdates = msg[4];
2277
+
2278
+ for (let i = 0; i < offlineUpdates.length; i++) {
2279
+ let update = offlineUpdates[i];
2280
+
2281
+ let data = update.data;
2282
+
2283
+ // eslint-disable-next-line no-unused-vars
2284
+ let updateRoute = data.shift();
2285
+ // eslint-disable-next-line no-unused-vars
2286
+ let updateDate = data.shift();
2287
+ let updateMessageId = data.shift();
2288
+ // eslint-disable-next-line no-unused-vars
2289
+ let updateType = data.shift();
2290
+ let method = data.shift();
2291
+
2292
+ let serverResMethod: ServerResponseModel = {
2293
+ messageId: updateMessageId,
2294
+ hasError: false,
2295
+ data: 'ACK'
2296
+ };
2297
+
2298
+ if (ws && ws.readyState === WebSocket.OPEN) {
2299
+ this._websocketManager.send(ws, serverResMethod);
2300
+ }
2301
+
2302
+ if (method === 'insertDocument' && data[0] === 'driver-gps') {
2303
+ continue;
2304
+ }
2305
+
2306
+ if (method !== 'reportBuilderGetResults' && method !== 'reportBuilderGetDistinctValue' && method !== 'reportBuilderBuildTree' && method !== 'generatePDF' && method !== 'getWOOfflineData' && method !== 'countQuery' && method !== 'countWithQuery' && method !== 'countCollectionWithQuery' && method !== 'find' && method !== 'findOne' && method !== 'findWithOptions' && method !== 'getDrivers' && method !== 'processAirdropDistribution' && method !== 'qbHandleResponse') {
2307
+ if (
2308
+ ResolveIOServer.shouldWriteLogsOffline()
2309
+ ) {
2310
+ ResolveIOServer.getLocalLogManager().writeLog({
2311
+ type: 'log',
2312
+ data: {
2313
+ _id: objectIdHexString(),
2314
+ createdAt: new Date(),
2315
+ type: 'client-request',
2316
+ collection: '',
2317
+ id_document: '',
2318
+ payload: getBinarySize(JSON.stringify([data])) < 1000000 ? JSON.stringify([data], null, 2) : 'Too Big',
2319
+ method: method,
2320
+ id_user: ws['id_user'] || '',
2321
+ user: ws['user'] || '',
2322
+ messageId: messageId,
2323
+ route: messageRoute,
2324
+ instance_index: process.env.NODE_APP_INSTANCE || '0'
2325
+ }
2326
+ });
2327
+ }
2328
+ else {
2329
+ await Logs.insertOne({
2330
+ _id: objectIdHexString(),
2331
+ type: 'client-request',
2332
+ collection: '',
2333
+ id_document: '',
2334
+ payload: getBinarySize(JSON.stringify([data])) < 1000000 ? JSON.stringify([data], null, 2) : 'Too Big',
2335
+ method: method,
2336
+ id_user: ws['id_user'] || '',
2337
+ user: ws['user'] || '',
2338
+ messageId: messageId,
2339
+ route: messageRoute,
2340
+ client: 'ResolveIO',
2341
+ instance: ResolveIOServer.getInstanceHost(),
2342
+ instance_index: process.env.NODE_APP_INSTANCE || '0'
2343
+ });
2344
+ }
2345
+ }
2346
+
2347
+ if (this._methodManager._methods[method]) {
2348
+ try {
2349
+ await this._methodManager.callMethod.call(Object.assign({}, this._methodManager, MethodManager.prototype, {id_user: ws['id_user'], user: ws['user'], id_ws: ws['id_socket']}), method, ...data);
2350
+ }
2351
+ catch (err) {
2352
+ console.log(new Date(), 'Offline Error', JSON.stringify(err, null, 2));
2353
+ }
2354
+
2355
+ if (method === 'updateDocumentOffline' || method === 'updateDocumentPropsOffline') {
2356
+ ResolveIOServer.getMongoManager().invalidateQueryCache(data[0]);
2357
+ }
2358
+ }
2359
+ else {
2360
+ console.log('Offline - Could not find method: ' + method);
2361
+ }
2362
+ }
2363
+
2364
+ this._offlineUpdates.splice(this._offlineUpdates.map(a => a['id_socket']).indexOf(ws['id_socket']), 1);
2365
+ }
2366
+ else {
2367
+ // It's presumably a 'method' or something else
2368
+ let dataCopy = [...msg];
2369
+
2370
+ dataCopy.shift();
2371
+ // eslint-disable-next-line no-unused-vars
2372
+ let date = dataCopy.shift();
2373
+ let msgId = dataCopy.shift();
2374
+ let msgType = dataCopy.shift();
2375
+
2376
+ if (msgType === 'method') {
2377
+ let methodName = dataCopy.shift();
2378
+
2379
+ if (ws['user_readonly']) {
2380
+ return;
2381
+ }
2382
+
2383
+ if (methodName !== 'reportBuilderGetResults' && methodName !== 'reportBuilderGetDistinctValue' && methodName !== 'reportBuilderBuildTree' && methodName !== 'generatePDF' && methodName !== 'getWOOfflineData' && methodName !== 'countQuery' && methodName !== 'countWithQuery' && methodName !== 'countCollectionWithQuery' && methodName !== 'find' && methodName !== 'findOne' && methodName !== 'findWithOptions' && methodName !== 'getDrivers' && methodName !== 'processAirdropDistribution') {
2384
+ if (
2385
+ ResolveIOServer.shouldWriteLogsOffline()
2386
+ ) {
2387
+ ResolveIOServer.getLocalLogManager().writeLog({
2388
+ type: 'log',
2389
+ data: {
2390
+ _id: objectIdHexString(),
2391
+ createdAt: new Date(),
2392
+ type: 'client-request',
2393
+ collection: '',
2394
+ id_document: '',
2395
+ payload: getBinarySize(JSON.stringify([dataCopy])) < 1000000 ? JSON.stringify([dataCopy], null, 2) : 'Too Big',
2396
+ method: methodName,
2397
+ id_user: ws['id_user'] || '',
2398
+ user: ws['user'] || '',
2399
+ messageId: messageId,
2400
+ route: messageRoute,
2401
+ instance_index: process.env.NODE_APP_INSTANCE || '0'
2402
+ }
2403
+ });
2404
+ }
2405
+ else {
2406
+ await Logs.insertOne({
2407
+ _id: objectIdHexString(),
2408
+ type: 'client-request',
2409
+ collection: '',
2410
+ id_document: '',
2411
+ payload: getBinarySize(JSON.stringify([dataCopy])) < 1000000 ? JSON.stringify([dataCopy], null, 2) : 'Too Big',
2412
+ method: methodName,
2413
+ id_user: ws['id_user'] || '',
2414
+ user: ws['user'] || '',
2415
+ messageId: messageId,
2416
+ route: messageRoute,
2417
+ client: 'ResolveIO',
2418
+ instance: ResolveIOServer.getInstanceHost(),
2419
+ instance_index: process.env.NODE_APP_INSTANCE || '0'
2420
+ });
2421
+ }
2422
+ }
2423
+
2424
+ // Immediately ACK
2425
+ let ack: ServerResponseModel = {
2426
+ messageId: msgId,
2427
+ hasError: false,
2428
+ data: 'ACK'
2429
+ };
2430
+
2431
+ if (ws && ws.readyState === WebSocket.OPEN) {
2432
+ this._websocketManager.send(ws, ack);
2433
+ }
2434
+
2435
+ let method = this._methodManager.getMethod(methodName);
2436
+ const forceWorker = this._isWorkersEnabled && !!method?.forceWorker;
2437
+ const targetWorkerIndex = this._isWorkersEnabled && method ? method.targetWorkerIndex : null;
2438
+ const targetWorkerInstance = this._isWorkersEnabled && method ? method.targetWorkerInstance : null;
2439
+ const hasWorkerForMethod = this._workerDispatcherManager ? this._workerDispatcherManager.hasWorkersForMethod(methodName) : false;
2440
+ const isAiCodex = methodName === 'aiCoderTerminalRunCodex' || methodName === 'aiCoderAppRunCodex';
2441
+ const isExcludedFromWorker = (
2442
+ methodName === 'find' ||
2443
+ methodName === 'insertDocument' ||
2444
+ methodName === 'countWithQuery' ||
2445
+ methodName === 'findOne' ||
2446
+ methodName === 'updateDocumentProps' ||
2447
+ methodName === 'findWithOptions' ||
2448
+ methodName === 'updateDocument' ||
2449
+ methodName === 'insertErrorLog' ||
2450
+ methodName === 'removeDocument' ||
2451
+ methodName === 'supportCreateBillingUser' ||
2452
+ methodName === 'getSignedUrl' ||
2453
+ methodName === 'getSignedUrls' ||
2454
+ methodName === 'getSignedUrlWithId' ||
2455
+ methodName === 'incorrectUser' ||
2456
+ methodName === 'reloadWS' ||
2457
+ methodName === 'reconnectWS' ||
2458
+ methodName === 'disconnectWS'
2459
+ );
2460
+
2461
+ if ((targetWorkerIndex || targetWorkerInstance || forceWorker) && this._isWorkersEnabled && !hasWorkerForMethod && this._methodManager.getEnableDebug()) {
2462
+ console.warn(new Date(), '[WorkerDispatcher] Worker unavailable, running method locally', {
2463
+ method: methodName,
2464
+ targetWorkerIndex: targetWorkerIndex || null,
2465
+ targetWorkerInstance: targetWorkerInstance || null,
2466
+ forceWorker
2467
+ });
2468
+ }
2469
+
2470
+ const shouldDispatchToWorker = (
2471
+ method &&
2472
+ !method.skipWorker &&
2473
+ this._isWorkersEnabled &&
2474
+ this._workerDispatcherManager &&
2475
+ hasWorkerForMethod &&
2476
+ (forceWorker || !isExcludedFromWorker)
2477
+ );
2478
+
2479
+ if (isAiCodex && this._aiWorkerDebug) {
2480
+ const queueSnapshot = this._workerDispatcherManager ? this._workerDispatcherManager.getQueueSnapshot() : null;
2481
+ console.log(new Date(), '[AI Worker Debug] dispatch check', {
2482
+ isWorkersEnabled: this._isWorkersEnabled,
2483
+ isWorkerInstance: this._isWorkerInstance,
2484
+ forceWorker,
2485
+ hasWorkerForMethod,
2486
+ targetWorkerIndex: targetWorkerIndex || null,
2487
+ targetWorkerInstance: targetWorkerInstance || null,
2488
+ shouldDispatchToWorker,
2489
+ queueSnapshot
2490
+ });
2491
+ }
2492
+
2493
+ if (shouldDispatchToWorker) {
2494
+ this._workerDispatcherManager.sendClientTask(msgId, methodName, dataCopy, {
2495
+ id_user: ws['id_user'],
2496
+ user: ws['user'],
2497
+ id_ws: ws['id_socket']
2498
+ });
2499
+ }
2500
+ else {
2501
+ // No worker available: do method locally
2502
+ if (methodName === 'aiCoderTerminalRunCodex' || methodName === 'aiCoderAppRunCodex') {
2503
+ if (this._aiWorkerDebug) {
2504
+ console.warn(new Date(), '[AI Worker Debug] AI execution running locally', {
2505
+ isWorkersEnabled: this._isWorkersEnabled,
2506
+ isWorkerInstance: this._isWorkerInstance,
2507
+ targetWorkerIndex: targetWorkerIndex || null,
2508
+ targetWorkerInstance: targetWorkerInstance || null,
2509
+ hasWorkerForMethod
2510
+ });
2511
+ }
2512
+ setTimeout(async () => {
2513
+ try {
2514
+ await this.callMethodLocally(ws, msgId, methodName, dataCopy);
2515
+ }
2516
+ catch (error) {
2517
+ console.error(new Date(), 'AI execution run failed:', error);
2518
+ }
2519
+ }, 0);
2520
+ }
2521
+ else {
2522
+ await this.callMethodLocally(ws, msgId, methodName, dataCopy);
2523
+ }
2524
+ }
2525
+ }
2526
+ }
2527
+ }
2528
+ catch (err) {
2529
+ throw err;
2530
+ }
2531
+ }
2532
+
2533
+ /**
2534
+ * callMethodLocally is your old approach for invoking the method in-process.
2535
+ */
2536
+ private async callMethodLocally(ws: WebSocket, messageId: number, method: string, params: any[]) {
2537
+ let serverRes: ServerResponseModel = {
2538
+ messageId: messageId,
2539
+ hasError: false,
2540
+ data: null
2541
+ };
2542
+ try {
2543
+ // You can keep your logging code (LogMethodLatencies, Logs.insertOne, etc.)
2544
+ let result = await this._methodManager.callMethod.call(
2545
+ Object.assign({}, this._methodManager, MethodManager.prototype, {
2546
+ id_user: ws['id_user'],
2547
+ user: ws['user'],
2548
+ id_ws: ws['id_socket']
2549
+ }),
2550
+ method,
2551
+ ...params
2552
+ );
2553
+
2554
+ serverRes.data = result;
2555
+ if (this._aiWorkerDebug && typeof method === 'string' && method.startsWith('ai')) {
2556
+ let resultBytes: number | null = null;
2557
+ try {
2558
+ resultBytes = Buffer.byteLength(JSON.stringify(result));
2559
+ }
2560
+ catch {
2561
+ resultBytes = null;
2562
+ }
2563
+ console.log(new Date(), '[AI Worker Debug] local method result', {
2564
+ method,
2565
+ messageId,
2566
+ id_socket: ws ? ws['id_socket'] : null,
2567
+ id_user: ws ? ws['id_user'] : null,
2568
+ resultBytes
2569
+ });
2570
+ }
2571
+ }
2572
+ catch (err) {
2573
+ serverRes.hasError = true;
2574
+ serverRes.data = err || 'Unknown error';
2575
+ }
2576
+
2577
+ if (ws && ws.readyState === WebSocket.OPEN) {
2578
+ this._websocketManager.send(ws, serverRes);
2579
+ }
2580
+ }
2581
+
2582
+
2583
+
2584
+ /**
2585
+ * Cleanly remove a client from the subscription manager, etc.
2586
+ */
2587
+ public async unsubscribeWS(ws: WebSocket) {
2588
+ if (this._subscriptionManager && this._methodManager.getEnableDebug()) {
2589
+ console.log(new Date(), 'Server App', 'Unsub WS', ws['user'], ws['id_socket']);
2590
+ }
2591
+ this.logConnectDebug('WS unsubscribe', {
2592
+ id_socket: ws ? ws['id_socket'] : null,
2593
+ id_user: ws ? ws['id_user'] : null,
2594
+ user: ws ? ws['user'] : null
2595
+ });
2596
+ await this._subscriptionManager.unsubscribeAll(ws);
2597
+ ws.removeAllListeners();
2598
+ ws = null;
2599
+ }
2600
+
2601
+ public getApp() {
2602
+ return this._app;
2603
+ }
2604
+
2605
+ public getServerConfig() {
2606
+ return ResolveIOServer.getServerConfig();
2607
+ }
2608
+
2609
+ public getWorkerDispatcherManager() {
2610
+ return this._workerDispatcherManager;
2611
+ }
2612
+
2613
+ public getWorkerServerManager() {
2614
+ return this._workerServerManager;
2615
+ }
2616
+ }