emailengine-app 1.14.0

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 (405) hide show
  1. package/.eslintrc +14 -0
  2. package/.github/CODE_OF_CONDUCT.md +76 -0
  3. package/.github/FUNDING.yml +4 -0
  4. package/.github/ISSUE_TEMPLATE/bug_report.md +38 -0
  5. package/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
  6. package/.github/contributing.md +17 -0
  7. package/.ncurc.js +10 -0
  8. package/.prettierrc.js +8 -0
  9. package/Dockerfile +17 -0
  10. package/Gruntfile.js +16 -0
  11. package/LICENSE.txt +661 -0
  12. package/README.md +524 -0
  13. package/bin/emailengine.js +14 -0
  14. package/config/default.toml +39 -0
  15. package/docker-compose.yml +47 -0
  16. package/encrypt.js +179 -0
  17. package/examples/api.md +137 -0
  18. package/examples/auth-server.js +104 -0
  19. package/getswagger.sh +8 -0
  20. package/lib/account.js +562 -0
  21. package/lib/append-list.js +67 -0
  22. package/lib/bounce-detect.js +380 -0
  23. package/lib/connection.js +1753 -0
  24. package/lib/consts.js +22 -0
  25. package/lib/db.js +72 -0
  26. package/lib/encrypt.js +100 -0
  27. package/lib/enum-message-flags.js +6 -0
  28. package/lib/get-raw-email.js +292 -0
  29. package/lib/get-secret.js +83 -0
  30. package/lib/logger.js +35 -0
  31. package/lib/lua/s-list-accounts.lua +51 -0
  32. package/lib/lua/z-expunge.lua +20 -0
  33. package/lib/lua/z-get-by-uid.lua +16 -0
  34. package/lib/lua/z-get-mailbox-id.lua +15 -0
  35. package/lib/lua/z-get-mailbox-path.lua +4 -0
  36. package/lib/lua/z-get.lua +15 -0
  37. package/lib/lua/z-push.lua +14 -0
  38. package/lib/lua/z-set.lua +17 -0
  39. package/lib/mailbox.js +1545 -0
  40. package/lib/message-port-stream.js +79 -0
  41. package/lib/schemas.js +311 -0
  42. package/lib/settings.js +63 -0
  43. package/lib/tools.js +488 -0
  44. package/package.json +79 -0
  45. package/scan.js +111 -0
  46. package/server.js +672 -0
  47. package/static/bootstrap-4.6.0-dist/css/bootstrap-grid.css +3872 -0
  48. package/static/bootstrap-4.6.0-dist/css/bootstrap-grid.css.map +1 -0
  49. package/static/bootstrap-4.6.0-dist/css/bootstrap-grid.min.css +7 -0
  50. package/static/bootstrap-4.6.0-dist/css/bootstrap-grid.min.css.map +1 -0
  51. package/static/bootstrap-4.6.0-dist/css/bootstrap-reboot.css +325 -0
  52. package/static/bootstrap-4.6.0-dist/css/bootstrap-reboot.css.map +1 -0
  53. package/static/bootstrap-4.6.0-dist/css/bootstrap-reboot.min.css +8 -0
  54. package/static/bootstrap-4.6.0-dist/css/bootstrap-reboot.min.css.map +1 -0
  55. package/static/bootstrap-4.6.0-dist/css/bootstrap.css +10298 -0
  56. package/static/bootstrap-4.6.0-dist/css/bootstrap.css.map +1 -0
  57. package/static/bootstrap-4.6.0-dist/css/bootstrap.min.css +7 -0
  58. package/static/bootstrap-4.6.0-dist/css/bootstrap.min.css.map +1 -0
  59. package/static/bootstrap-4.6.0-dist/js/bootstrap.bundle.js +7045 -0
  60. package/static/bootstrap-4.6.0-dist/js/bootstrap.bundle.js.map +1 -0
  61. package/static/bootstrap-4.6.0-dist/js/bootstrap.bundle.min.js +7 -0
  62. package/static/bootstrap-4.6.0-dist/js/bootstrap.bundle.min.js.map +1 -0
  63. package/static/bootstrap-4.6.0-dist/js/bootstrap.js +4432 -0
  64. package/static/bootstrap-4.6.0-dist/js/bootstrap.js.map +1 -0
  65. package/static/bootstrap-4.6.0-dist/js/bootstrap.min.js +7 -0
  66. package/static/bootstrap-4.6.0-dist/js/bootstrap.min.js.map +1 -0
  67. package/static/css/callout.css +63 -0
  68. package/static/css/emailengine.css +33 -0
  69. package/static/favicon/android-chrome-192x192.png +0 -0
  70. package/static/favicon/android-chrome-512x512.png +0 -0
  71. package/static/favicon/apple-touch-icon.png +0 -0
  72. package/static/favicon/favicon-16x16.png +0 -0
  73. package/static/favicon/favicon-32x32.png +0 -0
  74. package/static/favicon/manifest.json +20 -0
  75. package/static/favicon.ico +0 -0
  76. package/static/icons/alarm-fill.svg +3 -0
  77. package/static/icons/alarm.svg +7 -0
  78. package/static/icons/alert-circle-fill.svg +3 -0
  79. package/static/icons/alert-circle.svg +4 -0
  80. package/static/icons/alert-octagon-fill.svg +3 -0
  81. package/static/icons/alert-octagon.svg +5 -0
  82. package/static/icons/alert-square-fill.svg +3 -0
  83. package/static/icons/alert-square.svg +5 -0
  84. package/static/icons/alert-triangle-fill.svg +3 -0
  85. package/static/icons/alert-triangle.svg +5 -0
  86. package/static/icons/archive-fill.svg +3 -0
  87. package/static/icons/archive.svg +4 -0
  88. package/static/icons/arrow-bar-bottom.svg +4 -0
  89. package/static/icons/arrow-bar-left.svg +4 -0
  90. package/static/icons/arrow-bar-right.svg +4 -0
  91. package/static/icons/arrow-bar-up.svg +4 -0
  92. package/static/icons/arrow-clockwise.svg +4 -0
  93. package/static/icons/arrow-counterclockwise.svg +4 -0
  94. package/static/icons/arrow-down-left.svg +4 -0
  95. package/static/icons/arrow-down-right.svg +4 -0
  96. package/static/icons/arrow-down-short.svg +4 -0
  97. package/static/icons/arrow-down.svg +4 -0
  98. package/static/icons/arrow-left-right.svg +5 -0
  99. package/static/icons/arrow-left-short.svg +4 -0
  100. package/static/icons/arrow-left.svg +4 -0
  101. package/static/icons/arrow-repeat.svg +5 -0
  102. package/static/icons/arrow-right-short.svg +4 -0
  103. package/static/icons/arrow-right.svg +4 -0
  104. package/static/icons/arrow-up-down.svg +5 -0
  105. package/static/icons/arrow-up-left.svg +4 -0
  106. package/static/icons/arrow-up-right.svg +4 -0
  107. package/static/icons/arrow-up-short.svg +4 -0
  108. package/static/icons/arrow-up.svg +4 -0
  109. package/static/icons/arrows-angle-contract.svg +5 -0
  110. package/static/icons/arrows-angle-expand.svg +5 -0
  111. package/static/icons/arrows-collapse.svg +5 -0
  112. package/static/icons/arrows-expand.svg +5 -0
  113. package/static/icons/arrows-fullscreen.svg +7 -0
  114. package/static/icons/at.svg +3 -0
  115. package/static/icons/award.svg +4 -0
  116. package/static/icons/backspace-fill.svg +3 -0
  117. package/static/icons/backspace-reverse-fill.svg +3 -0
  118. package/static/icons/backspace-reverse.svg +5 -0
  119. package/static/icons/backspace.svg +5 -0
  120. package/static/icons/bar-chart-fill.svg +5 -0
  121. package/static/icons/bar-chart.svg +3 -0
  122. package/static/icons/battery-charging.svg +5 -0
  123. package/static/icons/battery-full.svg +4 -0
  124. package/static/icons/battery.svg +4 -0
  125. package/static/icons/bell-fill.svg +3 -0
  126. package/static/icons/bell.svg +4 -0
  127. package/static/icons/blockquote-left.svg +4 -0
  128. package/static/icons/blockquote-right.svg +4 -0
  129. package/static/icons/book-half-fill.svg +4 -0
  130. package/static/icons/book.svg +4 -0
  131. package/static/icons/bookmark-fill.svg +3 -0
  132. package/static/icons/bookmark.svg +3 -0
  133. package/static/icons/bootstrap-fill.svg +3 -0
  134. package/static/icons/bootstrap-reboot.svg +3 -0
  135. package/static/icons/bootstrap.svg +4 -0
  136. package/static/icons/box-arrow-bottom-left.svg +4 -0
  137. package/static/icons/box-arrow-bottom-right.svg +4 -0
  138. package/static/icons/box-arrow-down.svg +5 -0
  139. package/static/icons/box-arrow-left.svg +5 -0
  140. package/static/icons/box-arrow-right.svg +5 -0
  141. package/static/icons/box-arrow-up-left.svg +4 -0
  142. package/static/icons/box-arrow-up-right.svg +4 -0
  143. package/static/icons/box-arrow-up.svg +5 -0
  144. package/static/icons/braces.svg +3 -0
  145. package/static/icons/brightness-fill-high.svg +4 -0
  146. package/static/icons/brightness-fill-low.svg +11 -0
  147. package/static/icons/brightness-high.svg +3 -0
  148. package/static/icons/brightness-low.svg +11 -0
  149. package/static/icons/brush.svg +4 -0
  150. package/static/icons/bucket-fill.svg +4 -0
  151. package/static/icons/bucket.svg +4 -0
  152. package/static/icons/building.svg +5 -0
  153. package/static/icons/bullseye.svg +6 -0
  154. package/static/icons/calendar-fill.svg +4 -0
  155. package/static/icons/calendar.svg +4 -0
  156. package/static/icons/camera-video-fill.svg +4 -0
  157. package/static/icons/camera-video.svg +4 -0
  158. package/static/icons/camera.svg +5 -0
  159. package/static/icons/capslock-fill.svg +3 -0
  160. package/static/icons/capslock.svg +3 -0
  161. package/static/icons/chat-fill.svg +3 -0
  162. package/static/icons/chat.svg +3 -0
  163. package/static/icons/check-box.svg +4 -0
  164. package/static/icons/check-circle.svg +4 -0
  165. package/static/icons/check.svg +3 -0
  166. package/static/icons/chevron-compact-down.svg +3 -0
  167. package/static/icons/chevron-compact-left.svg +3 -0
  168. package/static/icons/chevron-compact-right.svg +3 -0
  169. package/static/icons/chevron-compact-up.svg +3 -0
  170. package/static/icons/chevron-down.svg +3 -0
  171. package/static/icons/chevron-left.svg +3 -0
  172. package/static/icons/chevron-right.svg +3 -0
  173. package/static/icons/chevron-up.svg +3 -0
  174. package/static/icons/circle-fill.svg +3 -0
  175. package/static/icons/circle-half.svg +3 -0
  176. package/static/icons/circle-slash.svg +3 -0
  177. package/static/icons/circle.svg +3 -0
  178. package/static/icons/clock-fill.svg +3 -0
  179. package/static/icons/clock.svg +4 -0
  180. package/static/icons/cloud-download.svg +5 -0
  181. package/static/icons/cloud-fill.svg +3 -0
  182. package/static/icons/cloud-upload.svg +5 -0
  183. package/static/icons/cloud.svg +3 -0
  184. package/static/icons/code-slash.svg +3 -0
  185. package/static/icons/code.svg +3 -0
  186. package/static/icons/columns-gutters.svg +3 -0
  187. package/static/icons/columns.svg +4 -0
  188. package/static/icons/command.svg +4 -0
  189. package/static/icons/compass.svg +5 -0
  190. package/static/icons/cone-striped.svg +4 -0
  191. package/static/icons/cone.svg +4 -0
  192. package/static/icons/controller.svg +5 -0
  193. package/static/icons/credit-card.svg +5 -0
  194. package/static/icons/cursor-fill.svg +3 -0
  195. package/static/icons/cursor.svg +3 -0
  196. package/static/icons/dash.svg +3 -0
  197. package/static/icons/diamond-half.svg +3 -0
  198. package/static/icons/diamond.svg +3 -0
  199. package/static/icons/display-fill.svg +5 -0
  200. package/static/icons/display.svg +4 -0
  201. package/static/icons/document-code.svg +4 -0
  202. package/static/icons/document-diff.svg +5 -0
  203. package/static/icons/document-richtext.svg +4 -0
  204. package/static/icons/document-spreadsheet.svg +5 -0
  205. package/static/icons/document-text.svg +4 -0
  206. package/static/icons/document.svg +3 -0
  207. package/static/icons/documents-alt.svg +4 -0
  208. package/static/icons/documents.svg +4 -0
  209. package/static/icons/dot.svg +3 -0
  210. package/static/icons/download.svg +5 -0
  211. package/static/icons/egg-fried.svg +4 -0
  212. package/static/icons/eject-fill.svg +3 -0
  213. package/static/icons/eject.svg +3 -0
  214. package/static/icons/envelope-fill.svg +3 -0
  215. package/static/icons/envelope-open-fill.svg +3 -0
  216. package/static/icons/envelope-open.svg +5 -0
  217. package/static/icons/envelope.svg +4 -0
  218. package/static/icons/eye-fill.svg +4 -0
  219. package/static/icons/eye-slash-fill.svg +5 -0
  220. package/static/icons/eye-slash.svg +6 -0
  221. package/static/icons/eye.svg +4 -0
  222. package/static/icons/filter.svg +3 -0
  223. package/static/icons/flag-fill.svg +4 -0
  224. package/static/icons/flag.svg +4 -0
  225. package/static/icons/folder-fill.svg +3 -0
  226. package/static/icons/folder-symlink-fill.svg +3 -0
  227. package/static/icons/folder-symlink.svg +5 -0
  228. package/static/icons/folder.svg +4 -0
  229. package/static/icons/fonts.svg +3 -0
  230. package/static/icons/forward-fill.svg +3 -0
  231. package/static/icons/forward.svg +3 -0
  232. package/static/icons/gear-fill.svg +3 -0
  233. package/static/icons/gear-wide-connected.svg +4 -0
  234. package/static/icons/gear-wide.svg +3 -0
  235. package/static/icons/gear.svg +4 -0
  236. package/static/icons/geo.svg +5 -0
  237. package/static/icons/graph-down.svg +5 -0
  238. package/static/icons/graph-up.svg +5 -0
  239. package/static/icons/grid-fill.svg +6 -0
  240. package/static/icons/grid.svg +3 -0
  241. package/static/icons/hammer.svg +4 -0
  242. package/static/icons/hash.svg +3 -0
  243. package/static/icons/heart-fill.svg +3 -0
  244. package/static/icons/heart.svg +3 -0
  245. package/static/icons/house-fill.svg +4 -0
  246. package/static/icons/house.svg +4 -0
  247. package/static/icons/image-alt.svg +4 -0
  248. package/static/icons/image-fill.svg +3 -0
  249. package/static/icons/image.svg +5 -0
  250. package/static/icons/images.svg +5 -0
  251. package/static/icons/inbox-fill.svg +4 -0
  252. package/static/icons/inbox.svg +4 -0
  253. package/static/icons/inboxes-fill.svg +4 -0
  254. package/static/icons/inboxes.svg +4 -0
  255. package/static/icons/info-fill.svg +3 -0
  256. package/static/icons/info-square-fill.svg +3 -0
  257. package/static/icons/info-square.svg +5 -0
  258. package/static/icons/info.svg +5 -0
  259. package/static/icons/justify-left.svg +3 -0
  260. package/static/icons/justify-right.svg +3 -0
  261. package/static/icons/justify.svg +3 -0
  262. package/static/icons/kanban-fill.svg +3 -0
  263. package/static/icons/kanban.svg +6 -0
  264. package/static/icons/laptop.svg +4 -0
  265. package/static/icons/layout-sidebar-reverse.svg +4 -0
  266. package/static/icons/layout-sidebar.svg +4 -0
  267. package/static/icons/layout-split.svg +3 -0
  268. package/static/icons/list-check.svg +3 -0
  269. package/static/icons/list-ol.svg +4 -0
  270. package/static/icons/list-task.svg +5 -0
  271. package/static/icons/list-ul.svg +3 -0
  272. package/static/icons/list.svg +3 -0
  273. package/static/icons/lock-fill.svg +4 -0
  274. package/static/icons/lock.svg +3 -0
  275. package/static/icons/map.svg +3 -0
  276. package/static/icons/mic.svg +4 -0
  277. package/static/icons/moon.svg +3 -0
  278. package/static/icons/music-player-fill.svg +4 -0
  279. package/static/icons/music-player.svg +5 -0
  280. package/static/icons/option.svg +3 -0
  281. package/static/icons/outlet.svg +5 -0
  282. package/static/icons/pause-fill.svg +3 -0
  283. package/static/icons/pause.svg +3 -0
  284. package/static/icons/pen.svg +5 -0
  285. package/static/icons/pencil.svg +4 -0
  286. package/static/icons/people-fill.svg +3 -0
  287. package/static/icons/people.svg +3 -0
  288. package/static/icons/person-fill.svg +3 -0
  289. package/static/icons/person.svg +3 -0
  290. package/static/icons/phone-landscape.svg +4 -0
  291. package/static/icons/phone.svg +4 -0
  292. package/static/icons/pie-chart-fill.svg +3 -0
  293. package/static/icons/pie-chart.svg +4 -0
  294. package/static/icons/play-fill.svg +3 -0
  295. package/static/icons/play.svg +3 -0
  296. package/static/icons/plug.svg +4 -0
  297. package/static/icons/plus.svg +4 -0
  298. package/static/icons/power.svg +4 -0
  299. package/static/icons/question-fill.svg +3 -0
  300. package/static/icons/question-square-fill.svg +3 -0
  301. package/static/icons/question-square.svg +4 -0
  302. package/static/icons/question.svg +4 -0
  303. package/static/icons/reply-all-fill.svg +4 -0
  304. package/static/icons/reply-all.svg +4 -0
  305. package/static/icons/reply-fill.svg +3 -0
  306. package/static/icons/reply.svg +3 -0
  307. package/static/icons/screwdriver.svg +3 -0
  308. package/static/icons/search.svg +4 -0
  309. package/static/icons/shield-fill.svg +3 -0
  310. package/static/icons/shield-lock-fill.svg +3 -0
  311. package/static/icons/shield-lock.svg +5 -0
  312. package/static/icons/shield-shaded.svg +4 -0
  313. package/static/icons/shield.svg +3 -0
  314. package/static/icons/shift-fill.svg +3 -0
  315. package/static/icons/shift.svg +3 -0
  316. package/static/icons/skip-backward-fill.svg +4 -0
  317. package/static/icons/skip-backward.svg +3 -0
  318. package/static/icons/skip-end-fill.svg +5 -0
  319. package/static/icons/skip-end.svg +4 -0
  320. package/static/icons/skip-forward-fill.svg +5 -0
  321. package/static/icons/skip-forward.svg +3 -0
  322. package/static/icons/skip-start-fill.svg +4 -0
  323. package/static/icons/skip-start.svg +4 -0
  324. package/static/icons/speaker.svg +4 -0
  325. package/static/icons/square-fill.svg +3 -0
  326. package/static/icons/square-half.svg +3 -0
  327. package/static/icons/square.svg +3 -0
  328. package/static/icons/star-fill.svg +3 -0
  329. package/static/icons/star-half.svg +3 -0
  330. package/static/icons/star.svg +3 -0
  331. package/static/icons/stop-fill.svg +3 -0
  332. package/static/icons/stop.svg +3 -0
  333. package/static/icons/stopwatch-fill.svg +3 -0
  334. package/static/icons/stopwatch.svg +5 -0
  335. package/static/icons/sun.svg +4 -0
  336. package/static/icons/table.svg +7 -0
  337. package/static/icons/tablet-landscape.svg +4 -0
  338. package/static/icons/tablet.svg +4 -0
  339. package/static/icons/tag-fill.svg +3 -0
  340. package/static/icons/tag.svg +4 -0
  341. package/static/icons/terminal-fill.svg +3 -0
  342. package/static/icons/terminal.svg +4 -0
  343. package/static/icons/text-center.svg +3 -0
  344. package/static/icons/text-indent-left.svg +3 -0
  345. package/static/icons/text-indent-right.svg +3 -0
  346. package/static/icons/text-left.svg +3 -0
  347. package/static/icons/text-right.svg +3 -0
  348. package/static/icons/three-dots-vertical.svg +3 -0
  349. package/static/icons/three-dots.svg +3 -0
  350. package/static/icons/toggle-off.svg +3 -0
  351. package/static/icons/toggle-on.svg +3 -0
  352. package/static/icons/toggles.svg +4 -0
  353. package/static/icons/tools.svg +4 -0
  354. package/static/icons/trash-fill.svg +3 -0
  355. package/static/icons/trash.svg +4 -0
  356. package/static/icons/triangle-fill.svg +3 -0
  357. package/static/icons/triangle-half.svg +3 -0
  358. package/static/icons/triangle.svg +3 -0
  359. package/static/icons/trophy.svg +6 -0
  360. package/static/icons/tv-fill.svg +3 -0
  361. package/static/icons/tv.svg +3 -0
  362. package/static/icons/type-bold.svg +3 -0
  363. package/static/icons/type-h1.svg +3 -0
  364. package/static/icons/type-h2.svg +3 -0
  365. package/static/icons/type-h3.svg +3 -0
  366. package/static/icons/type-italic.svg +3 -0
  367. package/static/icons/type-strikethrough.svg +4 -0
  368. package/static/icons/type-underline.svg +4 -0
  369. package/static/icons/type.svg +3 -0
  370. package/static/icons/unlock-fill.svg +4 -0
  371. package/static/icons/unlock.svg +3 -0
  372. package/static/icons/upload.svg +4 -0
  373. package/static/icons/volume-down-fill.svg +4 -0
  374. package/static/icons/volume-down.svg +4 -0
  375. package/static/icons/volume-mute-fill.svg +4 -0
  376. package/static/icons/volume-mute.svg +4 -0
  377. package/static/icons/volume-up-fill.svg +6 -0
  378. package/static/icons/volume-up.svg +6 -0
  379. package/static/icons/wallet.svg +3 -0
  380. package/static/icons/watch.svg +5 -0
  381. package/static/icons/wifi.svg +5 -0
  382. package/static/icons/window.svg +5 -0
  383. package/static/icons/wrench.svg +3 -0
  384. package/static/icons/x-circle-fill.svg +3 -0
  385. package/static/icons/x-circle.svg +5 -0
  386. package/static/icons/x-octagon-fill.svg +3 -0
  387. package/static/icons/x-octagon.svg +4 -0
  388. package/static/icons/x-square-fill.svg +3 -0
  389. package/static/icons/x-square.svg +4 -0
  390. package/static/icons/x.svg +4 -0
  391. package/static/index.html +752 -0
  392. package/static/js/emailengine.js +581 -0
  393. package/static/js/jquery-3.4.1.slim.min.js +2 -0
  394. package/static/js/moment-with-locales-2.24.0.min.js +1 -0
  395. package/static/js/popper.min.js +5 -0
  396. package/static/logo.png +0 -0
  397. package/systemd/emailengine.service +89 -0
  398. package/systemd/nginx-proxy.conf +77 -0
  399. package/views/error.hbs +2 -0
  400. package/workers/api.js +2266 -0
  401. package/workers/arena.js +89 -0
  402. package/workers/imap.js +611 -0
  403. package/workers/smtp.js +278 -0
  404. package/workers/submit.js +214 -0
  405. package/workers/webhooks.js +134 -0
package/server.js ADDED
@@ -0,0 +1,672 @@
1
+ 'use strict';
2
+
3
+ require('dotenv').config();
4
+ try {
5
+ process.chdir(__dirname);
6
+ } catch (err) {
7
+ // ignore
8
+ }
9
+
10
+ process.title = 'emailengine';
11
+
12
+ // cache before wild-config
13
+ const argv = process.argv.slice(2);
14
+
15
+ const logger = require('./lib/logger');
16
+ const packageData = require('./package.json');
17
+
18
+ const pathlib = require('path');
19
+ const { Worker, SHARE_ENV } = require('worker_threads');
20
+ const { redis } = require('./lib/db');
21
+ const promClient = require('prom-client');
22
+
23
+ const Joi = require('joi');
24
+ const { settingsSchema } = require('./lib/schemas');
25
+ const settings = require('./lib/settings');
26
+
27
+ const config = require('wild-config');
28
+ const getSecret = require('./lib/get-secret');
29
+
30
+ const { getDuration, getByteSize, selectRendezvousNode } = require('./lib/tools');
31
+ const { MAX_DAYS_STATS, MESSAGE_NEW_NOTIFY, MESSAGE_DELETED_NOTIFY, CONNECT_ERROR_NOTIFY } = require('./lib/consts');
32
+
33
+ config.service = config.service || {};
34
+
35
+ config.workers = config.workers || {
36
+ imap: 4,
37
+ webhooks: 1,
38
+ submit: 1
39
+ };
40
+
41
+ config.dbs = config.dbs || {
42
+ redis: 'redis://127.0.0.1:6379/8'
43
+ };
44
+
45
+ config.log = config.log || {
46
+ level: 'trace'
47
+ };
48
+
49
+ config.api = config.api || {
50
+ port: 3000,
51
+ host: '127.0.0.1'
52
+ };
53
+
54
+ config.smtp = config.smtp || {
55
+ enabled: false,
56
+ port: 2525,
57
+ host: '127.0.0.1',
58
+ secret: '',
59
+ proxy: false
60
+ };
61
+
62
+ config.arena = config.arena || {
63
+ enabled: false,
64
+ port: 2525,
65
+ host: '127.0.0.1'
66
+ };
67
+
68
+ const DEFAULT_EENGINE_TIMEOUT = 10 * 1000;
69
+ const EENGINE_TIMEOUT = getDuration(process.env.EENGINE_TIMEOUT || config.service.commandTimeout) || DEFAULT_EENGINE_TIMEOUT;
70
+ const DEFAULT_MAX_ATTACHMENT_SIZE = 5 * 1024 * 1024;
71
+
72
+ config.api.maxSize = getByteSize(process.env.EENGINE_MAX_SIZE || config.api.maxSize) || DEFAULT_MAX_ATTACHMENT_SIZE;
73
+ config.dbs.redis = process.env.EENGINE_REDIS || config.dbs.redis;
74
+
75
+ config.workers.imap = Number(process.env.EENGINE_WORKERS) || config.workers.imap || 4;
76
+ config.workers.webhooks = Number(process.env.EENGINE_WORKERS_WEBHOOKS) || config.workers.webhooks || 1;
77
+ config.workers.submit = Number(process.env.EENGINE_WORKERS_SUBMIT) || config.workers.submit || 1;
78
+
79
+ config.api.port = (process.env.EENGINE_PORT && Number(process.env.EENGINE_PORT)) || config.api.port;
80
+ config.api.host = process.env.EENGINE_HOST || config.api.host;
81
+ config.log.level = process.env.EENGINE_LOG_LEVEL || config.log.level;
82
+
83
+ const SMTP_ENABLED = process.env.EENGINE_SMTP_ENABLED
84
+ ? /^\s*(true|y|yes|1)\s*$/i.test(process.env.EENGINE_SMTP_ENABLED)
85
+ : config.smtp.enabled === true || /^\s*(true|y|yes|1)\s*$/i.test(config.smtp.enabled);
86
+
87
+ const ARENA_ENABLED = process.env.EENGINE_ARENA_ENABLED
88
+ ? /^\s*(true|y|yes|1)\s*$/i.test(process.env.EENGINE_ARENA_ENABLED)
89
+ : config.arena.enabled === true || /^\s*(true|y|yes|1)\s*$/i.test(config.arena.enabled);
90
+
91
+ logger.info({ msg: 'Starting EmailEngine', version: packageData.version, node: process.versions.node });
92
+
93
+ const NO_ACTIVE_HANDLER_RESP = {
94
+ error: 'No active handler for requested account. Try again later.',
95
+ statusCode: 503
96
+ };
97
+
98
+ let preparedSettings = false;
99
+ const preparedSettingsString = process.env.EENGINE_SETTINGS || config.settings;
100
+ if (preparedSettingsString) {
101
+ // received a configuration block
102
+ try {
103
+ const { error, value } = Joi.object(settingsSchema).validate(JSON.parse(preparedSettingsString), {
104
+ abortEarly: false,
105
+ stripUnknown: true,
106
+ convert: true
107
+ });
108
+
109
+ if (error) {
110
+ throw error;
111
+ }
112
+
113
+ preparedSettings = value;
114
+ } catch (err) {
115
+ logger.error({ msg: 'Received invalid settings string', input: preparedSettingsString, err });
116
+ process.exit(1);
117
+ }
118
+ }
119
+
120
+ logger.debug({ msg: 'IMAP Worker Count', workersImap: config.workers.imap });
121
+ logger.debug({ msg: 'Webhooks Worker Count', workersWebhooks: config.workers.webhooks });
122
+ logger.debug({ msg: 'Submission Worker Count', workersWebhooks: config.workers.submit });
123
+
124
+ const metrics = {
125
+ threadStarts: new promClient.Counter({
126
+ name: 'thread_starts',
127
+ help: 'Number of started threads'
128
+ }),
129
+
130
+ threadStops: new promClient.Counter({
131
+ name: 'thread_stops',
132
+ help: 'Number of stopped threads'
133
+ }),
134
+
135
+ apiCall: new promClient.Counter({
136
+ name: 'api_call',
137
+ help: 'Number of API calls',
138
+ labelNames: ['method', 'statusCode', 'route']
139
+ }),
140
+
141
+ imapConnections: new promClient.Gauge({
142
+ name: 'imap_connections',
143
+ help: 'Current IMAP connection state',
144
+ labelNames: ['status']
145
+ }),
146
+
147
+ webhooks: new promClient.Counter({
148
+ name: 'webhooks',
149
+ help: 'Webhooks sent',
150
+ labelNames: ['status', 'event']
151
+ }),
152
+
153
+ events: new promClient.Counter({
154
+ name: 'events',
155
+ help: 'Events fired',
156
+ labelNames: ['event']
157
+ }),
158
+
159
+ webhook_req: new promClient.Histogram({
160
+ name: 'webhook_req',
161
+ help: 'Duration of webhook requests',
162
+ buckets: [100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000, 60 * 1000]
163
+ })
164
+ };
165
+
166
+ let callQueue = new Map();
167
+ let mids = 0;
168
+
169
+ let closing = false;
170
+ let assigning = false;
171
+
172
+ let unassigned = false;
173
+ let assigned = new Map();
174
+ let unassignCounter = new Map();
175
+ let workerAssigned = new WeakMap();
176
+
177
+ let workers = new Map();
178
+ let availableIMAPWorkers = new Set();
179
+
180
+ let countUnassignment = async account => {
181
+ if (!unassignCounter.has(account)) {
182
+ unassignCounter.set(account, []);
183
+ }
184
+ let arr = unassignCounter.get(account);
185
+ arr.push(Date.now());
186
+
187
+ if (arr.length > 10) {
188
+ arr = arr.slice(-10);
189
+ unassignCounter.set(account, arr);
190
+ }
191
+
192
+ let delay = 0;
193
+ if (arr.length === 1) {
194
+ delay = 0;
195
+ } else {
196
+ let lastDelay = arr[arr.length - 1] - arr[arr.length - 2];
197
+ // if last delay was longer than a minute, then reset
198
+ if (lastDelay >= 60 * 1000) {
199
+ delay = 0;
200
+ } else {
201
+ delay = Math.min(Math.ceil(lastDelay * 1.5), 60 * 1000);
202
+ }
203
+ }
204
+
205
+ if (delay) {
206
+ await new Promise(resolve => setTimeout(resolve, delay));
207
+ return true;
208
+ } else {
209
+ return false;
210
+ }
211
+ };
212
+
213
+ let clearUnassignmentCounter = account => {
214
+ unassignCounter.delete(account);
215
+ };
216
+
217
+ let spawnWorker = type => {
218
+ if (closing) {
219
+ return;
220
+ }
221
+
222
+ if (!workers.has(type)) {
223
+ workers.set(type, new Set());
224
+ }
225
+
226
+ let worker = new Worker(pathlib.join(__dirname, 'workers', `${type}.js`), {
227
+ argv,
228
+ env: SHARE_ENV,
229
+ trackUnmanagedFds: true
230
+ });
231
+ metrics.threadStarts.inc();
232
+
233
+ workers.get(type).add(worker);
234
+
235
+ worker.on('exit', exitCode => {
236
+ metrics.threadStops.inc();
237
+
238
+ workers.get(type).delete(worker);
239
+ availableIMAPWorkers.delete(worker);
240
+
241
+ if (workerAssigned.has(worker)) {
242
+ let accountList = workerAssigned.get(worker);
243
+ workerAssigned.delete(worker);
244
+ accountList.forEach(account => {
245
+ assigned.delete(account);
246
+ let shouldReassign = false;
247
+ // graceful reconnect
248
+ countUnassignment(account)
249
+ .then(sr => {
250
+ shouldReassign = sr;
251
+ })
252
+ .catch(() => {
253
+ shouldReassign = true;
254
+ })
255
+ .finally(() => {
256
+ unassigned.add(account);
257
+ if (shouldReassign) {
258
+ assignAccounts().catch(err => logger.error(err));
259
+ }
260
+ });
261
+ });
262
+ }
263
+
264
+ if (closing) {
265
+ return;
266
+ }
267
+
268
+ // spawning a new worker trigger reassign
269
+ logger.error({ msg: 'Worker exited', exitCode, type });
270
+ setTimeout(() => spawnWorker(type), 1000);
271
+ });
272
+
273
+ worker.on('message', message => {
274
+ if (!message) {
275
+ return;
276
+ }
277
+
278
+ if (message.cmd === 'resp' && message.mid && callQueue.has(message.mid)) {
279
+ let { resolve, reject, timer } = callQueue.get(message.mid);
280
+ clearTimeout(timer);
281
+ callQueue.delete(message.mid);
282
+ if (message.error) {
283
+ let err = new Error(message.error);
284
+ if (message.code) {
285
+ err.code = message.code;
286
+ }
287
+ if (message.statusCode) {
288
+ err.statusCode = message.statusCode;
289
+ }
290
+ return reject(err);
291
+ } else {
292
+ return resolve(message.response);
293
+ }
294
+ }
295
+
296
+ if (message.cmd === 'call' && message.mid) {
297
+ return onCommand(worker, message.message)
298
+ .then(response => {
299
+ worker.postMessage({
300
+ cmd: 'resp',
301
+ mid: message.mid,
302
+ response
303
+ });
304
+ })
305
+ .catch(err => {
306
+ worker.postMessage({
307
+ cmd: 'resp',
308
+ mid: message.mid,
309
+ error: err.message,
310
+ code: err.code,
311
+ statusCode: err.statusCode
312
+ });
313
+ });
314
+ }
315
+
316
+ switch (message.cmd) {
317
+ case 'metrics': {
318
+ let statUpdateKey = false;
319
+
320
+ switch (message.key) {
321
+ // gather for dashboard counter
322
+ case 'webhooks': {
323
+ let { status } = message.args[0] || {};
324
+ statUpdateKey = `${message.key}:${status}`;
325
+ break;
326
+ }
327
+
328
+ case 'webhook_req': {
329
+ break;
330
+ }
331
+
332
+ case 'events': {
333
+ let { event } = message.args[0] || {};
334
+ switch (event) {
335
+ case MESSAGE_NEW_NOTIFY:
336
+ case MESSAGE_DELETED_NOTIFY:
337
+ case CONNECT_ERROR_NOTIFY:
338
+ statUpdateKey = `${message.key}:${event}`;
339
+ break;
340
+ }
341
+ break;
342
+ }
343
+
344
+ case 'apiCall': {
345
+ let { statusCode } = message.args[0] || {};
346
+ let success = statusCode >= 200 && statusCode < 300;
347
+ statUpdateKey = `${message.key}:${success ? 'success' : 'fail'}`;
348
+ break;
349
+ }
350
+ }
351
+
352
+ if (statUpdateKey) {
353
+ // increment counter in redis
354
+
355
+ let now = new Date();
356
+
357
+ // we keep a separate hash value for each ISO day
358
+ let dateStr = `${now
359
+ .toISOString()
360
+ .substr(0, 10)
361
+ .replace(/[^0-9]+/g, '')}`;
362
+
363
+ // hash key for bucket
364
+ let timeStr = `${now
365
+ .toISOString()
366
+ // bucket includes 1 minute
367
+ .substr(0, 16)
368
+ .replace(/[^0-9]+/g, '')}`;
369
+
370
+ let hkey = `stats:${statUpdateKey}:${dateStr}`;
371
+
372
+ redis
373
+ .multi()
374
+ .hincrby(hkey, timeStr, 1)
375
+ .sadd('stats:keys', statUpdateKey)
376
+ // keep alive at most 2 days
377
+ .expire(hkey, MAX_DAYS_STATS + 1 * 24 * 3600)
378
+ .exec()
379
+ .catch(() => false);
380
+ }
381
+
382
+ if (message.key && metrics[message.key] && typeof metrics[message.key][message.method] === 'function') {
383
+ metrics[message.key][message.method](...message.args);
384
+ }
385
+
386
+ return;
387
+ }
388
+
389
+ case 'settings':
390
+ availableIMAPWorkers.forEach(worker => {
391
+ worker.postMessage(message);
392
+ });
393
+ return;
394
+ }
395
+
396
+ switch (type) {
397
+ case 'imap':
398
+ return processImapWorkerMessage(worker, message);
399
+ }
400
+ });
401
+ };
402
+
403
+ function processImapWorkerMessage(worker, message) {
404
+ if (!message || !message.cmd) {
405
+ logger.debug({ msg: 'Unexpected message', type: 'imap', message });
406
+
407
+ return;
408
+ }
409
+
410
+ switch (message.cmd) {
411
+ case 'ready':
412
+ availableIMAPWorkers.add(worker);
413
+ // assign pending accounts
414
+ assignAccounts().catch(err => logger.error(err));
415
+ break;
416
+ }
417
+ }
418
+
419
+ async function call(worker, message, transferList) {
420
+ return new Promise((resolve, reject) => {
421
+ let mid = `${Date.now()}:${++mids}`;
422
+
423
+ let timer = setTimeout(() => {
424
+ let err = new Error('Timeout waiting for command response');
425
+ err.statusCode = 504;
426
+ err.code = 'Timeout';
427
+ reject(err);
428
+ }, message.timeout || EENGINE_TIMEOUT);
429
+
430
+ callQueue.set(mid, { resolve, reject, timer });
431
+ worker.postMessage(
432
+ {
433
+ cmd: 'call',
434
+ mid,
435
+ message
436
+ },
437
+ transferList
438
+ );
439
+ });
440
+ }
441
+
442
+ async function assignAccounts() {
443
+ if (assigning) {
444
+ return false;
445
+ }
446
+ assigning = true;
447
+ try {
448
+ if (!unassigned) {
449
+ // first run
450
+ // list all available accounts and assign to worker threads
451
+ let accounts = await redis.smembers('ia:accounts');
452
+ unassigned = new Set(accounts);
453
+ }
454
+
455
+ if (!availableIMAPWorkers.size || !unassigned.size) {
456
+ // nothing to do here
457
+ return;
458
+ }
459
+
460
+ for (let account of unassigned) {
461
+ if (!availableIMAPWorkers.size) {
462
+ // out of workers
463
+ break;
464
+ }
465
+
466
+ let worker = selectRendezvousNode(account, Array.from(availableIMAPWorkers));
467
+
468
+ if (!workerAssigned.has(worker)) {
469
+ workerAssigned.set(worker, new Set());
470
+ }
471
+
472
+ workerAssigned.get(worker).add(account);
473
+ assigned.set(account, worker);
474
+ unassigned.delete(account);
475
+
476
+ await call(worker, {
477
+ cmd: 'assign',
478
+ account
479
+ });
480
+ }
481
+ } finally {
482
+ assigning = false;
483
+ }
484
+ }
485
+
486
+ async function onCommand(worker, message) {
487
+ switch (message.cmd) {
488
+ case 'metrics':
489
+ return promClient.register.metrics();
490
+
491
+ case 'structuredMetrics': {
492
+ let connections = {};
493
+
494
+ for (let key of Object.keys(metrics.imapConnections.hashMap)) {
495
+ if (key.indexOf('status:') === 0) {
496
+ let metric = metrics.imapConnections.hashMap[key];
497
+ connections[metric.labels.status] = metric.value;
498
+ }
499
+ }
500
+ return { connections };
501
+ }
502
+
503
+ case 'new':
504
+ unassigned.add(message.account);
505
+ assignAccounts().catch(err => logger.error(err));
506
+ return;
507
+
508
+ case 'delete':
509
+ unassigned.delete(message.account); // if set
510
+ clearUnassignmentCounter(message.account);
511
+ if (assigned.has(message.account)) {
512
+ let assignedWorker = assigned.get(message.account);
513
+ if (workerAssigned.has(assignedWorker)) {
514
+ workerAssigned.get(assignedWorker).delete(message.account);
515
+ if (!workerAssigned.get(assignedWorker).size) {
516
+ // last item in the worker accounts
517
+ workerAssigned.delete(assignedWorker);
518
+ }
519
+ }
520
+
521
+ call(assignedWorker, message)
522
+ .then(() => logger.debug('worker processed'))
523
+ .catch(err => logger.error(err));
524
+ }
525
+ return;
526
+
527
+ case 'update':
528
+ if (assigned.has(message.account)) {
529
+ let assignedWorker = assigned.get(message.account);
530
+ call(assignedWorker, message)
531
+ .then(() => logger.debug('worker processed'))
532
+ .catch(err => logger.error(err));
533
+ }
534
+ return;
535
+
536
+ case 'listMessages':
537
+ case 'buildContacts':
538
+ case 'getRawMessage':
539
+ case 'getText':
540
+ case 'getMessage':
541
+ case 'updateMessage':
542
+ case 'moveMessage':
543
+ case 'deleteMessage':
544
+ case 'createMailbox':
545
+ case 'deleteMailbox':
546
+ case 'submitMessage':
547
+ case 'queueMessage':
548
+ case 'uploadMessage':
549
+ case 'getAttachment': {
550
+ if (!assigned.has(message.account)) {
551
+ return NO_ACTIVE_HANDLER_RESP;
552
+ }
553
+
554
+ let assignedWorker = assigned.get(message.account);
555
+
556
+ let transferList = [];
557
+ if (['getRawMessage', 'getAttachment'].includes(message.cmd) && message.port) {
558
+ transferList.push(message.port);
559
+ }
560
+
561
+ if (['submitMessage', 'queueMessage'].includes(message.cmd) && typeof message.raw === 'object') {
562
+ transferList.push(message.raw);
563
+ }
564
+
565
+ return await call(assignedWorker, message, transferList);
566
+ }
567
+ }
568
+ return 999;
569
+ }
570
+
571
+ let metricsResult = {};
572
+ async function collectMetrics() {
573
+ // reset all counters
574
+ Object.keys(metricsResult || {}).forEach(key => {
575
+ metricsResult[key] = 0;
576
+ });
577
+
578
+ if (workers.has('imap')) {
579
+ let imapWorkers = workers.get('imap');
580
+ for (let imapWorker of imapWorkers) {
581
+ try {
582
+ let workerStats = await call(imapWorker, { cmd: 'countConnections' });
583
+ Object.keys(workerStats || {}).forEach(status => {
584
+ if (!metricsResult[status]) {
585
+ metricsResult[status] = 0;
586
+ }
587
+ metricsResult[status] += Number(workerStats[status]) || 0;
588
+ });
589
+ } catch (err) {
590
+ logger.error(err);
591
+ }
592
+ }
593
+ }
594
+
595
+ Object.keys(metricsResult).forEach(status => {
596
+ metrics.imapConnections.set({ status }, metricsResult[status]);
597
+ });
598
+ }
599
+
600
+ process.on('SIGTERM', () => {
601
+ if (closing) {
602
+ return;
603
+ }
604
+ closing = true;
605
+ setImmediate(() => {
606
+ process.exit();
607
+ });
608
+ });
609
+
610
+ process.on('SIGINT', () => {
611
+ if (closing) {
612
+ return;
613
+ }
614
+ closing = true;
615
+ setImmediate(() => {
616
+ process.exit();
617
+ });
618
+ });
619
+
620
+ // START APPLICATION
621
+
622
+ const startApplication = async () => {
623
+ if (preparedSettings) {
624
+ // set up configuration
625
+ logger.debug({ msg: 'Updating application settings', settings: preparedSettings });
626
+
627
+ for (let key of Object.keys(preparedSettings)) {
628
+ await settings.set(key, preparedSettings[key]);
629
+ }
630
+ }
631
+
632
+ // renew encryiption secret, if needed
633
+ await getSecret();
634
+
635
+ // multiple IMAP connection handlers
636
+ for (let i = 0; i < config.workers.imap; i++) {
637
+ spawnWorker('imap');
638
+ }
639
+
640
+ for (let i = 0; i < config.workers.webhooks; i++) {
641
+ spawnWorker('webhooks');
642
+ }
643
+
644
+ for (let i = 0; i < config.workers.submit; i++) {
645
+ spawnWorker('submit');
646
+ }
647
+
648
+ if (SMTP_ENABLED) {
649
+ // single SMTP interface worker
650
+ spawnWorker('smtp');
651
+ }
652
+
653
+ if (ARENA_ENABLED) {
654
+ // single Bull UI interface worker
655
+ spawnWorker('arena');
656
+ }
657
+
658
+ // single worker for HTTP
659
+ spawnWorker('api');
660
+ };
661
+
662
+ startApplication()
663
+ .then(() => {
664
+ // start collecting metrics
665
+ setInterval(() => {
666
+ collectMetrics().catch(err => logger.error({ msg: 'Failed to collect metrics', err }));
667
+ }, 1000).unref();
668
+ })
669
+ .catch(err => {
670
+ logger.error({ msg: 'Failed to start application', err });
671
+ process.exit(1);
672
+ });