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
@@ -0,0 +1,581 @@
1
+ 'use strict';
2
+ /* global document, fetch, $, window, moment, confirm */
3
+
4
+ function showToast(message, icon) {
5
+ let template = `<div class="toast-header">
6
+ <img src="/static/icons/${icon ? icon : 'info'}.svg" class="rounded mr-2">
7
+ <strong class="mr-auto">EmailEngine</strong>
8
+ <button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
9
+ <span aria-hidden="true">&times;</span>
10
+ </button>
11
+ </div>
12
+ <div class="toast-body"></div>`;
13
+
14
+ let toast = document.createElement('div');
15
+ toast.classList.add('toast', 'show');
16
+ toast.dataset.delay = '5000';
17
+ toast.dataset.autohide = 'true';
18
+
19
+ toast.innerHTML = template;
20
+ toast.querySelector('.toast-body').textContent = message;
21
+ document.getElementById('toastContainer').appendChild(toast);
22
+
23
+ $(toast).toast('show');
24
+ }
25
+
26
+ function checkStatus() {
27
+ // calculate seconds from the start of current day (in local time)
28
+ let now = new Date();
29
+ let today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
30
+ let seconds = Math.max(Math.ceil((now.getTime() - today.getTime()) / 1000), 1);
31
+
32
+ fetch(`/v1/stats?seconds=${seconds}`)
33
+ .then(result => result.json())
34
+ .then(result => {
35
+ for (let elm of document.querySelectorAll('.app-version')) {
36
+ elm.textContent = 'v' + result.version;
37
+ }
38
+
39
+ for (let elm of document.querySelectorAll('.app-license')) {
40
+ elm.textContent = result.license;
41
+ }
42
+
43
+ if (!/\bMIT\b/.test(result.license)) {
44
+ for (let elm of document.querySelectorAll('.no-mit-license')) {
45
+ elm.classList.remove('d-none');
46
+ }
47
+ }
48
+
49
+ for (let elm of document.querySelectorAll('.stats-accounts')) {
50
+ elm.textContent = result.accounts || 0;
51
+ }
52
+
53
+ ['connecting', 'connected', 'authenticationError', 'connectError'].forEach(key => {
54
+ for (let elm of document.querySelectorAll('.stats-conn-' + key)) {
55
+ elm.textContent = (result.connections && result.connections[key]) || 0;
56
+ }
57
+ });
58
+
59
+ for (let key of ['events:messageNew', 'events:messageDeleted', 'webhooks:success', 'webhooks:fail', 'apiCall:success', 'apiCall:fail']) {
60
+ for (let elm of document.querySelectorAll('.stats-counter-' + key.replace(/:/g, '_'))) {
61
+ elm.textContent = (result.counters && result.counters[key]) || 0;
62
+ }
63
+ }
64
+
65
+ setTimeout(checkStatus, 5000);
66
+ })
67
+ .catch(err => {
68
+ console.error(err);
69
+ showToast(err.message);
70
+ setTimeout(checkStatus, 5000);
71
+ });
72
+ }
73
+
74
+ let fetchingAccountList = false;
75
+ function showAccounts(e, state) {
76
+ e.preventDefault();
77
+ if (fetchingAccountList) {
78
+ e.stopPropagation();
79
+ return;
80
+ }
81
+ fetchingAccountList = true;
82
+ fetch('/v1/accounts?page=0&pageSize=1000' + (state ? '&state=' + state : ''))
83
+ .then(result => result.json())
84
+ .then(result => {
85
+ fetchingAccountList = false;
86
+
87
+ let table = document.getElementById('accountsTableBody');
88
+ table.innerHTML = '';
89
+
90
+ for (let accounData of result.accounts) {
91
+ let row = document.createElement('tr');
92
+
93
+ let thAccount = document.createElement('th');
94
+ thAccount.textContent = accounData.account;
95
+ row.appendChild(thAccount);
96
+
97
+ let tdName = document.createElement('td');
98
+ tdName.textContent = accounData.name || '';
99
+ row.appendChild(tdName);
100
+
101
+ let tdState = document.createElement('td');
102
+ let state;
103
+ switch (accounData.state) {
104
+ case 'authenticationError':
105
+ state = 'Authentication failed';
106
+ break;
107
+ case 'connectError':
108
+ state = 'Connection failed';
109
+ break;
110
+ default:
111
+ state = accounData.state.replace(/^./, c => c.toUpperCase());
112
+ break;
113
+ }
114
+
115
+ tdState.textContent = state;
116
+
117
+ if (accounData.lastError) {
118
+ row.appendChild(tdState);
119
+
120
+ let tdDescription = document.createElement('td');
121
+ let tdDescriptionCode = document.createElement('code');
122
+ let description = accounData.lastError.response || accounData.lastError.serverResponseCode || '';
123
+ tdDescriptionCode.textContent = description;
124
+ tdDescriptionCode.title = description;
125
+ tdDescription.appendChild(tdDescriptionCode);
126
+ row.appendChild(tdDescription);
127
+ } else if (accounData.syncTime) {
128
+ row.appendChild(tdState);
129
+
130
+ let tdDescription = document.createElement('td');
131
+ let tdDescriptionCode = document.createElement('em');
132
+
133
+ let description = `Last change ${moment(accounData.syncTime).fromNow()}`;
134
+
135
+ tdDescriptionCode.textContent = description;
136
+ tdDescriptionCode.title = moment(accounData.syncTime).format('LLL');
137
+ tdDescription.appendChild(tdDescriptionCode);
138
+ row.appendChild(tdDescription);
139
+ } else {
140
+ tdState.setAttribute('colspan', '2');
141
+ row.appendChild(tdState);
142
+ }
143
+
144
+ let createButton = options => {
145
+ let btn = document.createElement('button');
146
+ btn.classList.add('btn', `btn-${options.style}`, 'btn-sm');
147
+ btn.title = options.title;
148
+
149
+ let ico = document.createElement('img');
150
+ ico.alt = '';
151
+ ico.setAttribute('src', `/static/icons/${options.icon}.svg`);
152
+ btn.appendChild(ico);
153
+
154
+ btn.style.marginLeft = '5px';
155
+
156
+ return btn;
157
+ };
158
+
159
+ let createReconnectButton = () => {
160
+ let btn = createButton({
161
+ title: 'Request reconnecting IMAP client for this account',
162
+ icon: 'arrow-clockwise',
163
+ style: 'warning'
164
+ });
165
+
166
+ btn.addEventListener('click', e => {
167
+ e.preventDefault();
168
+ $('#accountsModal').modal('hide');
169
+ showToast('Reconnect requested for ' + accounData.name || accounData.account);
170
+ fetch('/v1/account/' + encodeURIComponent(accounData.account) + '/reconnect', {
171
+ method: 'PUT',
172
+ headers: {
173
+ 'Content-Type': 'application/json'
174
+ },
175
+ body: JSON.stringify({
176
+ reconnect: true
177
+ })
178
+ }).catch(err => {
179
+ showToast(err.message);
180
+ console.error(err);
181
+ });
182
+ });
183
+
184
+ return btn;
185
+ };
186
+
187
+ let createDeleteButton = () => {
188
+ let btn = createButton({
189
+ title: 'Request deletion of this account',
190
+ icon: 'trash',
191
+ style: 'danger'
192
+ });
193
+
194
+ btn.addEventListener('click', e => {
195
+ e.preventDefault();
196
+ if (!confirm('Are you sure?')) {
197
+ return;
198
+ }
199
+ $('#accountsModal').modal('hide');
200
+ showToast('Requested account deletion for ' + accounData.name || accounData.account);
201
+ fetch('/v1/account/' + encodeURIComponent(accounData.account), {
202
+ method: 'DELETE'
203
+ }).catch(err => {
204
+ showToast(err.message);
205
+ console.error(err);
206
+ });
207
+ });
208
+
209
+ return btn;
210
+ };
211
+
212
+ let tdReconnect = document.createElement('td');
213
+ tdReconnect.classList.add('text-right');
214
+
215
+ tdReconnect.appendChild(createReconnectButton());
216
+ tdReconnect.appendChild(createDeleteButton());
217
+
218
+ row.appendChild(tdReconnect);
219
+
220
+ table.appendChild(row);
221
+ }
222
+
223
+ $('#accountsModal').modal('show');
224
+ })
225
+ .catch(err => {
226
+ fetchingAccountList = false;
227
+
228
+ console.error(err);
229
+ showToast(err.message);
230
+ setTimeout(checkStatus, 5000);
231
+ });
232
+ }
233
+
234
+ function showAddAccount() {
235
+ document.getElementById('addAccountForm').classList.remove('was-validated');
236
+ $('#addAccountModal').modal('show');
237
+ }
238
+
239
+ function submitAddAccount() {
240
+ let isOauthAccount = document.getElementById('AddAccountOAuth2Enable').checked;
241
+
242
+ let account = {
243
+ account: document.getElementById('addAccountFormId').value.trim(),
244
+ name: document.getElementById('addAccountFormName').value.trim(),
245
+ imap: !isOauthAccount
246
+ ? {
247
+ auth: {
248
+ user: document.getElementById('addAccountIMAPUser').value.trim(),
249
+ pass: document.getElementById('addAccountIMAPPass').value.trim()
250
+ },
251
+ host: document.getElementById('addAccountIMAPHost').value.trim(),
252
+ port: Number(document.getElementById('addAccountIMAPPort').value.trim()),
253
+ secure: document.getElementById('addAccountIMAPSecure').checked,
254
+ tls: {
255
+ rejectUnauthorized: !document.getElementById('addAccountIMAPSecure').checked
256
+ },
257
+ resyncDelay: Number(document.getElementById('addAccountIMAPResyncDelay').value.trim())
258
+ }
259
+ : false,
260
+ smtp:
261
+ document.getElementById('addAccountSMTPEnable').checked && !isOauthAccount
262
+ ? {
263
+ auth: {
264
+ user: document.getElementById('addAccountSMTPUser').value.trim(),
265
+ pass: document.getElementById('addAccountSMTPPass').value.trim()
266
+ },
267
+ host: document.getElementById('addAccountSMTPHost').value.trim(),
268
+ port: Number(document.getElementById('addAccountSMTPPort').value.trim()),
269
+ secure: document.getElementById('addAccountSMTPSecure').checked,
270
+ tls: {
271
+ rejectUnauthorized: !document.getElementById('addAccountSMTPSecure').checked
272
+ }
273
+ }
274
+ : false,
275
+ oauth2: isOauthAccount
276
+ ? {
277
+ // ask for the redirection link to the authorization page
278
+ authorize: true
279
+ }
280
+ : false
281
+ };
282
+
283
+ $('#addAccountModal').modal('hide');
284
+
285
+ fetch('/v1/account', {
286
+ method: 'POST',
287
+ headers: {
288
+ 'Content-Type': 'application/json'
289
+ },
290
+ body: JSON.stringify(account)
291
+ })
292
+ .then(result => result.json())
293
+ .then(result => {
294
+ // reset all fields
295
+ document.getElementById('addAccountFormId').value = '';
296
+ document.getElementById('addAccountFormName').value = '';
297
+ document.getElementById('addAccountIMAPUser').value = '';
298
+ document.getElementById('addAccountIMAPPass').value = '';
299
+ document.getElementById('addAccountIMAPHost').value = '';
300
+ document.getElementById('addAccountIMAPPort').value = '';
301
+ document.getElementById('addAccountIMAPSecure').checked = false;
302
+ document.getElementById('addAccountIMAPResyncDelay').value = '900';
303
+ document.getElementById('addAccountSMTPEnable').checked = false;
304
+ document.getElementById('addAccountSMTPUser').value = '';
305
+ document.getElementById('addAccountSMTPPass').value = '';
306
+ document.getElementById('addAccountSMTPHost').value = '';
307
+ document.getElementById('addAccountSMTPPort').value = '';
308
+ document.getElementById('addAccountSMTPSecure').checked = false;
309
+
310
+ document.getElementById('AddAccountOAuth2Enable').checked = false;
311
+
312
+ document.getElementById('add-account-imap-tab').classList.remove('disabled');
313
+ document.getElementById('add-account-smtp-tab').classList.remove('disabled');
314
+
315
+ // select imap tab by default
316
+ document.getElementById('add-account-oauth2-tab').classList.remove('active');
317
+ document.getElementById('add-account-smtp-tab').classList.remove('active');
318
+ document.getElementById('add-account-imap-tab').classList.add('active');
319
+
320
+ document.getElementById('add-account-oauth2').classList.remove('active');
321
+ document.getElementById('add-account-smtp').classList.remove('active');
322
+ document.getElementById('add-account-imap').classList.add('active');
323
+
324
+ document.getElementById('addAccountIMAPSection').disabled = false;
325
+ document.getElementById('addAccountSMTPSection').disabled = true;
326
+ document.getElementById('addAccountOAuth2Section').disabled = true;
327
+
328
+ if (result.error) {
329
+ showToast(`Failed to create an account (${result.message})`, 'alert-triangle');
330
+ return;
331
+ }
332
+
333
+ if (result.redirect) {
334
+ // Most probably OAuth2 redirect
335
+ window.location = result.redirect;
336
+ return;
337
+ }
338
+
339
+ showToast('Account created');
340
+ })
341
+ .catch(err => {
342
+ console.error(err);
343
+ showToast(err.message);
344
+ });
345
+ }
346
+
347
+ document.addEventListener('DOMContentLoaded', () => {
348
+ const settingsForm = document.getElementById('settingsForm');
349
+ const settingsNotifyText = document.getElementById('settingsNotifyText');
350
+ const settingsNotifyTextSize = document.getElementById('settingsNotifyTextSize');
351
+ const settingsNotifyHeaders = document.getElementById('settingsNotifyHeaders');
352
+ const settingsWebhookEvents = document.getElementById('settingsWebhookEvents');
353
+ const infoEventTypes = document.getElementById('infoEventTypes');
354
+
355
+ settingsForm.addEventListener('submit', e => {
356
+ e.preventDefault();
357
+ settingsForm.classList.add('was-validated');
358
+ if (settingsForm.checkValidity() === false) {
359
+ e.stopPropagation();
360
+ return;
361
+ }
362
+
363
+ let logs = {
364
+ all: !!document.getElementById('settingsLogsAll').checked,
365
+ accounts: Array.from(
366
+ new Set(
367
+ document
368
+ .getElementById('settingsLogsAccounts')
369
+ .value.trim()
370
+ .split(/\r?\n/)
371
+ .map(a => a.trim())
372
+ .filter(a => a)
373
+ )
374
+ ),
375
+ resetLoggedAccounts: !!document.getElementById('settingsResetLoggedAccounts').checked,
376
+ maxLogLines: Number(document.getElementById('settingsLogsMaxLogLines').value)
377
+ };
378
+
379
+ const payload = {
380
+ webhooks: document.getElementById('settingsWebhooks').value,
381
+ logs,
382
+ notifyText: settingsNotifyTextSize ? !!settingsNotifyText.checked : false,
383
+ notifyTextSize: settingsNotifyTextSize ? Number(settingsNotifyTextSize.value) : 0,
384
+
385
+ notifyHeaders: settingsNotifyHeaders
386
+ ? settingsNotifyHeaders.value
387
+ .trim()
388
+ .split(',')
389
+ .map(entry => entry.trim())
390
+ .filter(entry => entry)
391
+ : undefined,
392
+
393
+ webhookEvents: settingsWebhookEvents
394
+ ? settingsWebhookEvents.value
395
+ .trim()
396
+ .split(',')
397
+ .map(entry => entry.trim())
398
+ .filter(entry => entry)
399
+ : undefined,
400
+
401
+ gmailClientId: document.getElementById('settingsGmailClientId').value,
402
+ gmailClientSecret: document.getElementById('settingsGmailClientSecret').value,
403
+ gmailRedirectUrl: document.getElementById('settingsGmailRedirectUrl').value
404
+ };
405
+
406
+ fetch('/v1/settings', {
407
+ method: 'POST', // or 'PUT'
408
+ headers: {
409
+ 'Content-Type': 'application/json'
410
+ },
411
+ body: JSON.stringify(payload)
412
+ })
413
+ .then(result => result.json())
414
+ .then(result => {
415
+ if (result.error) {
416
+ showToast(`Failed to store settings (${result.message})`, 'alert-triangle');
417
+ return;
418
+ }
419
+ showToast('Settings updated');
420
+ })
421
+ .catch(err => {
422
+ console.error(err);
423
+ showToast(err.message);
424
+ });
425
+ });
426
+
427
+ let keysToFetch = [
428
+ 'webhooks',
429
+ 'authServer',
430
+ 'logs',
431
+ 'notifyText',
432
+ 'notifyTextSize',
433
+ 'notifyHeaders',
434
+ 'webhookEvents',
435
+ 'eventTypes',
436
+ 'gmailClientId',
437
+ 'gmailClientSecret',
438
+ 'gmailRedirectUrl'
439
+ ];
440
+
441
+ fetch(`/v1/settings?${keysToFetch.map(key => `${key}=true`).join('&')}`)
442
+ .then(result => result.json())
443
+ .then(result => {
444
+ document.getElementById('settingsWebhooks').value = (result && result.webhooks) || '';
445
+ document.getElementById('settingsAuthServer').value = (result && result.authServer) || '';
446
+
447
+ document.getElementById('settingsGmailClientId').value = (result && result.gmailClientId) || '';
448
+ document.getElementById('settingsGmailClientSecret').value = '';
449
+ if (result.gmailClientSecret) {
450
+ document.getElementById('settingsGmailClientSecret').placeholder = '(client secret is set but not disclosed)';
451
+ }
452
+ document.getElementById('settingsGmailRedirectUrl').value = (result && result.gmailRedirectUrl) || window.location.origin + '/oauth';
453
+
454
+ if (settingsNotifyText) {
455
+ settingsNotifyText.checked = !!(result && result.notifyText);
456
+ }
457
+
458
+ if (settingsNotifyTextSize) {
459
+ settingsNotifyTextSize.value = (result && result.notifyTextSize) || '';
460
+ }
461
+
462
+ if (settingsNotifyHeaders) {
463
+ settingsNotifyHeaders.value = (result && result.notifyHeaders && result.notifyHeaders.join(', ')) || '';
464
+ }
465
+
466
+ if (settingsWebhookEvents) {
467
+ settingsWebhookEvents.value = (result && result.webhookEvents && result.webhookEvents.join(', ')) || '*';
468
+ }
469
+
470
+ if (result.eventTypes && infoEventTypes) {
471
+ infoEventTypes.textContent = result.eventTypes.join(', ');
472
+ }
473
+
474
+ let logs = (result && result.logs) || {};
475
+ let maxLogLines = 'maxLogLines' in logs ? logs.maxLogLines : 10000;
476
+
477
+ document.getElementById('settingsLogsAll').checked = !!logs.all;
478
+ document.getElementById('settingsLogsAccounts').value = logs.accounts ? logs.accounts.join('\n') : '';
479
+ document.getElementById('settingsLogsMaxLogLines').value = maxLogLines;
480
+ })
481
+ .catch(err => {
482
+ console.error(err);
483
+ showToast(err.message, 'alert-triangle');
484
+ });
485
+
486
+ const logsForm = document.getElementById('logsForm');
487
+ logsForm.addEventListener('submit', e => {
488
+ e.preventDefault();
489
+ logsForm.classList.add('was-validated');
490
+ if (logsForm.checkValidity() === false) {
491
+ e.stopPropagation();
492
+ return;
493
+ }
494
+
495
+ const account = document.getElementById('logsAccount').value.trim();
496
+ fetch(`/v1/logs/${encodeURIComponent(account)}`, {
497
+ method: 'GET'
498
+ })
499
+ .then(result => result.blob())
500
+ .then(blob => {
501
+ const a = document.createElement('a');
502
+ a.style = 'display: none';
503
+
504
+ const url = window.URL.createObjectURL(blob);
505
+ a.href = url;
506
+ document.body.appendChild(a);
507
+
508
+ a.download = `logs-${account}.log`;
509
+ a.click();
510
+ window.URL.revokeObjectURL(url);
511
+ })
512
+ .catch(err => {
513
+ console.error(err);
514
+ showToast(err.message);
515
+ });
516
+ });
517
+
518
+ for (let elm of document.querySelectorAll('.domainName')) {
519
+ elm.textContent = `${window.location.protocol}//${window.location.host}`;
520
+ }
521
+
522
+ for (let elm of document.querySelectorAll('.stats-accounts')) {
523
+ elm.addEventListener('click', e => showAccounts(e, false));
524
+ }
525
+
526
+ ['connecting', 'connected', 'authenticationError', 'connectError'].forEach(key => {
527
+ for (let elm of document.querySelectorAll('.stats-conn-' + key)) {
528
+ elm.addEventListener('click', e => showAccounts(e, key));
529
+ }
530
+ });
531
+
532
+ document.getElementById('addAccountButton').addEventListener('click', e => {
533
+ e.preventDefault();
534
+ showAddAccount();
535
+ });
536
+
537
+ let addAccountSMTPEnableElm = document.getElementById('addAccountSMTPEnable');
538
+ let toggleAddAccountSMTPSection = () => {
539
+ let section = document.getElementById('addAccountSMTPSection');
540
+ if (addAccountSMTPEnableElm.checked) {
541
+ section.disabled = false;
542
+ } else {
543
+ section.disabled = true;
544
+ }
545
+ };
546
+ addAccountSMTPEnableElm.addEventListener('click', toggleAddAccountSMTPSection);
547
+
548
+ let addAccountOAuth2EnableElm = document.getElementById('AddAccountOAuth2Enable');
549
+ let toggleAddAccountOAuth2Tabs = () => {
550
+ if (addAccountOAuth2EnableElm.checked) {
551
+ document.getElementById('add-account-imap-tab').classList.add('disabled');
552
+ document.getElementById('add-account-smtp-tab').classList.add('disabled');
553
+
554
+ // force imap/smtp fieldsets to disabled, otherwise form does not pass validation
555
+ document.getElementById('addAccountIMAPSection').disabled = true;
556
+ document.getElementById('addAccountSMTPSection').disabled = true;
557
+ document.getElementById('addAccountOAuth2Section').disabled = false;
558
+ } else {
559
+ document.getElementById('add-account-imap-tab').classList.remove('disabled');
560
+ document.getElementById('add-account-smtp-tab').classList.remove('disabled');
561
+
562
+ document.getElementById('addAccountIMAPSection').disabled = false;
563
+ toggleAddAccountSMTPSection();
564
+ document.getElementById('addAccountOAuth2Section').disabled = true;
565
+ }
566
+ };
567
+ addAccountOAuth2EnableElm.addEventListener('click', toggleAddAccountOAuth2Tabs);
568
+
569
+ let addAccountForm = document.getElementById('addAccountForm');
570
+ addAccountForm.addEventListener('submit', e => {
571
+ e.preventDefault();
572
+ addAccountForm.classList.add('was-validated');
573
+ if (addAccountForm.checkValidity() === false) {
574
+ e.stopPropagation();
575
+ return;
576
+ }
577
+ submitAddAccount();
578
+ });
579
+
580
+ checkStatus();
581
+ });