emailengine-app 1.14.7 → 2.60.1

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 (2139) hide show
  1. package/.env.development +49 -0
  2. package/.env.example +82 -0
  3. package/.env.production +87 -0
  4. package/.eslintignore +1 -0
  5. package/.github/workflows/deploy.yml +104 -0
  6. package/.github/workflows/release.yaml +107 -0
  7. package/.github/workflows/test.yml +82 -0
  8. package/.ncurc.js +19 -5
  9. package/.prettierignore +44 -0
  10. package/CHANGELOG.md +1110 -0
  11. package/DOCKER_DEPLOYMENT.md +495 -0
  12. package/Dockerfile +53 -6
  13. package/Dockerfile-legacy +18 -0
  14. package/Fluid-Attacks-Results.csv +1 -0
  15. package/Gruntfile.js +46 -5
  16. package/LICENSE_EMAILENGINE.txt +110 -0
  17. package/README.md +73 -339
  18. package/app.json +40 -0
  19. package/bin/emailengine.js +283 -38
  20. package/config/default.toml +9 -11
  21. package/config/test.toml +45 -0
  22. package/copy-static-files.sh +34 -0
  23. package/data/google-crawlers.json +797 -0
  24. package/docker-compose.yml +103 -31
  25. package/encrypt.js +85 -10
  26. package/eslint.config.js +110 -0
  27. package/examples/auth-server.js +121 -69
  28. package/examples/grafana-dashboard.json +2375 -0
  29. package/help.txt +84 -0
  30. package/install.sh +426 -0
  31. package/lib/account.js +2348 -124
  32. package/lib/add-trackers.js +119 -0
  33. package/lib/api-routes/bull-board-routes.js +60 -0
  34. package/lib/api-routes/chat-routes.js +519 -0
  35. package/lib/api-routes/template-routes.js +490 -0
  36. package/lib/append-list.js +9 -2
  37. package/lib/arf-detect.js +200 -0
  38. package/lib/autodetect-imap-settings.js +781 -0
  39. package/lib/bounce-detect.js +280 -37
  40. package/lib/capa.js +97 -0
  41. package/lib/consts.js +210 -1
  42. package/lib/db.js +227 -8
  43. package/lib/document-store.js +54 -0
  44. package/lib/email-client/base-client.js +3677 -0
  45. package/lib/email-client/gmail-client.js +2796 -0
  46. package/lib/email-client/imap/mailbox.js +3721 -0
  47. package/lib/email-client/imap/subconnection.js +269 -0
  48. package/lib/email-client/imap-client.js +2628 -0
  49. package/lib/email-client/outlook-client.js +3805 -0
  50. package/lib/encrypt.js +85 -14
  51. package/lib/es.js +784 -0
  52. package/lib/feature-flags.js +42 -0
  53. package/lib/gateway.js +271 -0
  54. package/lib/generate-text-preview.js +56 -0
  55. package/lib/get-raw-email.js +302 -42
  56. package/lib/get-secret.js +23 -67
  57. package/lib/headers-rewriter.js +33 -0
  58. package/lib/imapproxy/imap-core/index.js +4 -0
  59. package/lib/imapproxy/imap-core/lib/commands/append.js +187 -0
  60. package/lib/imapproxy/imap-core/lib/commands/authenticate-plain.js +145 -0
  61. package/lib/imapproxy/imap-core/lib/commands/capability.js +13 -0
  62. package/lib/imapproxy/imap-core/lib/commands/check.js +10 -0
  63. package/lib/imapproxy/imap-core/lib/commands/close.js +44 -0
  64. package/lib/imapproxy/imap-core/lib/commands/compress.js +102 -0
  65. package/lib/imapproxy/imap-core/lib/commands/copy.js +109 -0
  66. package/lib/imapproxy/imap-core/lib/commands/create.js +93 -0
  67. package/lib/imapproxy/imap-core/lib/commands/delete.js +84 -0
  68. package/lib/imapproxy/imap-core/lib/commands/enable.js +36 -0
  69. package/lib/imapproxy/imap-core/lib/commands/expunge.js +68 -0
  70. package/lib/imapproxy/imap-core/lib/commands/fetch.js +385 -0
  71. package/lib/imapproxy/imap-core/lib/commands/getquota.js +85 -0
  72. package/lib/imapproxy/imap-core/lib/commands/getquotaroot.js +111 -0
  73. package/lib/imapproxy/imap-core/lib/commands/id.js +111 -0
  74. package/lib/imapproxy/imap-core/lib/commands/idle.js +45 -0
  75. package/lib/imapproxy/imap-core/lib/commands/list.js +218 -0
  76. package/lib/imapproxy/imap-core/lib/commands/login.js +135 -0
  77. package/lib/imapproxy/imap-core/lib/commands/logout.js +26 -0
  78. package/lib/imapproxy/imap-core/lib/commands/lsub.js +102 -0
  79. package/lib/imapproxy/imap-core/lib/commands/move.js +106 -0
  80. package/lib/imapproxy/imap-core/lib/commands/namespace.js +14 -0
  81. package/lib/imapproxy/imap-core/lib/commands/noop.js +10 -0
  82. package/lib/imapproxy/imap-core/lib/commands/rename.js +102 -0
  83. package/lib/imapproxy/imap-core/lib/commands/search.js +306 -0
  84. package/lib/imapproxy/imap-core/lib/commands/select.js +248 -0
  85. package/lib/imapproxy/imap-core/lib/commands/setquota.js +24 -0
  86. package/lib/imapproxy/imap-core/lib/commands/starttls.js +100 -0
  87. package/lib/imapproxy/imap-core/lib/commands/status.js +149 -0
  88. package/lib/imapproxy/imap-core/lib/commands/store.js +208 -0
  89. package/lib/imapproxy/imap-core/lib/commands/subscribe.js +69 -0
  90. package/lib/imapproxy/imap-core/lib/commands/uid-expunge.js +71 -0
  91. package/lib/imapproxy/imap-core/lib/commands/uid-store.js +170 -0
  92. package/lib/imapproxy/imap-core/lib/commands/unselect.js +14 -0
  93. package/lib/imapproxy/imap-core/lib/commands/unsubscribe.js +69 -0
  94. package/lib/imapproxy/imap-core/lib/handler/README.md +146 -0
  95. package/lib/imapproxy/imap-core/lib/handler/imap-compile-stream.js +252 -0
  96. package/lib/imapproxy/imap-core/lib/handler/imap-compiler.js +134 -0
  97. package/lib/imapproxy/imap-core/lib/handler/imap-formal-syntax.js +147 -0
  98. package/lib/imapproxy/imap-core/lib/handler/imap-handler.js +11 -0
  99. package/lib/imapproxy/imap-core/lib/handler/imap-parser.js +678 -0
  100. package/lib/imapproxy/imap-core/lib/imap-command.js +381 -0
  101. package/lib/imapproxy/imap-core/lib/imap-composer.js +71 -0
  102. package/lib/imapproxy/imap-core/lib/imap-connection.js +929 -0
  103. package/lib/imapproxy/imap-core/lib/imap-server.js +426 -0
  104. package/lib/imapproxy/imap-core/lib/imap-stream.js +172 -0
  105. package/lib/imapproxy/imap-core/lib/imap-tools.js +789 -0
  106. package/lib/imapproxy/imap-core/lib/indexer/body-structure.js +295 -0
  107. package/lib/imapproxy/imap-core/lib/indexer/create-envelope.js +103 -0
  108. package/lib/imapproxy/imap-core/lib/indexer/indexer.js +904 -0
  109. package/lib/imapproxy/imap-core/lib/indexer/parse-mime-tree.js +340 -0
  110. package/lib/imapproxy/imap-core/lib/length-limiter.js +76 -0
  111. package/lib/imapproxy/imap-core/lib/parse-date.js +225 -0
  112. package/lib/imapproxy/imap-core/lib/search.js +330 -0
  113. package/lib/imapproxy/imap-core/lib/tls-options.js +69 -0
  114. package/lib/imapproxy/imap-core/memory-notifier.js +129 -0
  115. package/lib/imapproxy/imap-core/test/client.js +46 -0
  116. package/lib/imapproxy/imap-core/test/fixtures/append.eml +1196 -0
  117. package/lib/imapproxy/imap-core/test/fixtures/chunks.js +44 -0
  118. package/lib/imapproxy/imap-core/test/fixtures/fix1.eml +6 -0
  119. package/lib/imapproxy/imap-core/test/fixtures/fix2.eml +599 -0
  120. package/lib/imapproxy/imap-core/test/fixtures/fix3.eml +32 -0
  121. package/lib/imapproxy/imap-core/test/fixtures/fix4.eml +6 -0
  122. package/lib/imapproxy/imap-core/test/fixtures/mimetorture.eml +599 -0
  123. package/lib/imapproxy/imap-core/test/fixtures/mimetorture.js +2740 -0
  124. package/lib/imapproxy/imap-core/test/fixtures/mimetorture.json +1411 -0
  125. package/lib/imapproxy/imap-core/test/fixtures/mimetree.js +85 -0
  126. package/lib/imapproxy/imap-core/test/fixtures/nodemailer.eml +582 -0
  127. package/lib/imapproxy/imap-core/test/fixtures/ryan_finnie_mime_torture.eml +599 -0
  128. package/lib/imapproxy/imap-core/test/fixtures/simple.eml +42 -0
  129. package/lib/imapproxy/imap-core/test/fixtures/simple.json +164 -0
  130. package/lib/imapproxy/imap-core/test/imap-compile-stream-test.js +671 -0
  131. package/lib/imapproxy/imap-core/test/imap-compiler-test.js +272 -0
  132. package/lib/imapproxy/imap-core/test/imap-indexer-test.js +236 -0
  133. package/lib/imapproxy/imap-core/test/imap-parser-test.js +922 -0
  134. package/lib/imapproxy/imap-core/test/memory-notifier.js +129 -0
  135. package/lib/imapproxy/imap-core/test/prepare.sh +74 -0
  136. package/lib/imapproxy/imap-core/test/protocol-test.js +1756 -0
  137. package/lib/imapproxy/imap-core/test/search-test.js +1356 -0
  138. package/lib/imapproxy/imap-core/test/test-client.js +152 -0
  139. package/lib/imapproxy/imap-core/test/test-server.js +623 -0
  140. package/lib/imapproxy/imap-core/test/tools-test.js +22 -0
  141. package/lib/imapproxy/imap-server.js +577 -0
  142. package/lib/lists.js +92 -0
  143. package/lib/llm-pre-process.js +141 -0
  144. package/lib/logger.js +43 -4
  145. package/lib/lua/ee-get-idempotency.lua +74 -0
  146. package/lib/lua/ee-list-add.lua +34 -0
  147. package/lib/lua/ee-list-remove.lua +37 -0
  148. package/lib/lua/h-incrby-exists.lua +28 -0
  149. package/lib/lua/h-push.lua +32 -0
  150. package/lib/lua/h-set-bigger.lua +40 -0
  151. package/lib/lua/h-set-exists.lua +29 -0
  152. package/lib/lua/h-set-new.lua +29 -0
  153. package/lib/lua/h-update-bigger.lua +45 -0
  154. package/lib/lua/s-list-accounts.lua +64 -14
  155. package/lib/lua/z-expunge.lua +86 -10
  156. package/lib/lua/z-get-by-uid.lua +28 -5
  157. package/lib/lua/z-get-mailbox-id.lua +24 -2
  158. package/lib/lua/z-get-mailbox-path.lua +16 -0
  159. package/lib/lua/z-get.lua +27 -4
  160. package/lib/lua/z-set.lua +24 -2
  161. package/lib/metrics-collector.js +209 -0
  162. package/lib/oauth/gmail.js +663 -0
  163. package/lib/oauth/mail-ru.js +310 -0
  164. package/lib/oauth/outlook.js +541 -0
  165. package/lib/oauth/pubsub/google.js +247 -0
  166. package/lib/oauth2-apps.js +1420 -0
  167. package/lib/outbox.js +140 -0
  168. package/lib/payload-examples-documents.json +404 -0
  169. package/lib/payload-examples-webhooks.json +266 -0
  170. package/lib/pre-process.js +193 -0
  171. package/lib/rate-limit.js +32 -0
  172. package/lib/reconnection-manager.js +106 -0
  173. package/lib/redis-scan-delete.js +82 -0
  174. package/lib/redis-url.js +78 -0
  175. package/lib/rewrite-text-nodes.js +267 -0
  176. package/lib/routes-ui.js +10247 -0
  177. package/lib/schemas.js +1577 -187
  178. package/lib/settings.js +263 -12
  179. package/lib/sub-script.js +109 -0
  180. package/lib/templates.js +240 -0
  181. package/lib/threads.js +155 -0
  182. package/lib/tokens.js +353 -0
  183. package/lib/tools.js +1773 -41
  184. package/lib/translations.js +33 -0
  185. package/lib/webhooks.js +605 -0
  186. package/list-generate.js +96 -0
  187. package/package.json +130 -54
  188. package/render.yaml +44 -0
  189. package/sbom.json +1 -0
  190. package/scan.js +14 -2
  191. package/scripts/README.md +50 -0
  192. package/scripts/refresh-test-tokens.js +180 -0
  193. package/server.js +2902 -376
  194. package/setup-production.sh +201 -0
  195. package/static/{bootstrap-4.6.0-dist → bootstrap-4.6.2-dist}/css/bootstrap-grid.css +3 -3
  196. package/static/bootstrap-4.6.2-dist/css/bootstrap-grid.css.map +1 -0
  197. package/static/{bootstrap-4.6.0-dist → bootstrap-4.6.2-dist}/css/bootstrap-grid.min.css +3 -3
  198. package/static/{bootstrap-4.6.0-dist → bootstrap-4.6.2-dist}/css/bootstrap-grid.min.css.map +1 -1
  199. package/static/{bootstrap-4.6.0-dist → bootstrap-4.6.2-dist}/css/bootstrap-reboot.css +3 -3
  200. package/static/bootstrap-4.6.2-dist/css/bootstrap-reboot.css.map +1 -0
  201. package/static/{bootstrap-4.6.0-dist → bootstrap-4.6.2-dist}/css/bootstrap-reboot.min.css +3 -3
  202. package/static/bootstrap-4.6.2-dist/css/bootstrap-reboot.min.css.map +1 -0
  203. package/static/{bootstrap-4.6.0-dist → bootstrap-4.6.2-dist}/css/bootstrap.css +60 -26
  204. package/static/bootstrap-4.6.2-dist/css/bootstrap.css.map +1 -0
  205. package/static/bootstrap-4.6.2-dist/css/bootstrap.min.css +7 -0
  206. package/static/bootstrap-4.6.2-dist/css/bootstrap.min.css.map +1 -0
  207. package/static/bootstrap-4.6.2-dist/js/bootstrap.bundle.js +7155 -0
  208. package/static/bootstrap-4.6.2-dist/js/bootstrap.bundle.js.map +784 -0
  209. package/static/bootstrap-4.6.2-dist/js/bootstrap.bundle.min.js +7 -0
  210. package/static/bootstrap-4.6.2-dist/js/bootstrap.bundle.min.js.map +959 -0
  211. package/static/{bootstrap-4.6.0-dist → bootstrap-4.6.2-dist}/js/bootstrap.js +792 -868
  212. package/static/bootstrap-4.6.2-dist/js/bootstrap.js.map +1 -0
  213. package/static/bootstrap-4.6.2-dist/js/bootstrap.min.js +7 -0
  214. package/static/bootstrap-4.6.2-dist/js/bootstrap.min.js.map +1 -0
  215. package/static/css/app.css +146 -0
  216. package/static/css/arena.css +777 -0
  217. package/static/css/default.min.css +9 -0
  218. package/static/css/highlight.min.css +9 -0
  219. package/static/css/sb-admin-2.min.css +10 -0
  220. package/static/emailengine.ico +0 -0
  221. package/static/favicon/android-chrome-192x192.png +0 -0
  222. package/static/favicon/android-chrome-512x512.png +0 -0
  223. package/static/favicon/apple-touch-icon.png +0 -0
  224. package/static/favicon/favicon-16x16.png +0 -0
  225. package/static/favicon/favicon-32x32.png +0 -0
  226. package/static/favicon/favicon.ico +0 -0
  227. package/static/favicon.ico +0 -0
  228. package/static/fonts/nunito/OFL.txt +93 -0
  229. package/static/fonts/nunito/XRXV3I6Li01BKofINeaBTMnFcQ.woff2 +0 -0
  230. package/static/fonts/nunito-font.css +66 -0
  231. package/static/front/EmailEngine_logo_horiz.png +0 -0
  232. package/static/front/EmailEngine_logo_vert.png +0 -0
  233. package/static/front/index.html +57 -0
  234. package/static/front/logo.png +0 -0
  235. package/static/imap-capabilities-1.csv +71 -0
  236. package/static/index.html +30 -713
  237. package/static/js/ace/README.txt +1 -0
  238. package/static/js/ace/ace.js +23 -0
  239. package/static/js/ace/ext-language_tools.js +8 -0
  240. package/static/js/ace/ext-searchbox.js +8 -0
  241. package/static/js/ace/mode-handlebars.js +8 -0
  242. package/static/js/ace/mode-html.js +8 -0
  243. package/static/js/ace/mode-javascript.js +8 -0
  244. package/static/js/ace/mode-json.js +8 -0
  245. package/static/js/ace/mode-markdown.js +8 -0
  246. package/static/js/ace/snippets/javascript.js +8 -0
  247. package/static/js/ace/snippets/markdown.js +8 -0
  248. package/static/js/ace/theme-kuroir.js +8 -0
  249. package/static/js/ace/theme-xcode.js +8 -0
  250. package/static/js/ace/worker-html.js +1 -0
  251. package/static/js/ace/worker-javascript.js +1 -0
  252. package/static/js/ace/worker-json.js +1 -0
  253. package/static/js/app.js +526 -0
  254. package/static/js/bootstrap-autocomplete.min.js +1 -0
  255. package/static/js/clipboard.min.js +517 -0
  256. package/static/js/ee-client.js +1977 -0
  257. package/static/js/evaluation-worker.js +47 -0
  258. package/static/js/highlight.min.js +1173 -0
  259. package/static/js/jquery-3.6.0.min.js +2 -0
  260. package/static/js/sb-admin-2.min.js +7 -0
  261. package/static/licenses.html +6606 -50
  262. package/static/logo/EmailEngine_logo_horiz.png +0 -0
  263. package/static/logo/EmailEngine_logo_vert.png +0 -0
  264. package/static/logo.png +0 -0
  265. package/static/logo_transparent.png +0 -0
  266. package/static/logo_transparent_small.png +0 -0
  267. package/static/logo_wide.png +0 -0
  268. package/static/preview/header-template.png +0 -0
  269. package/static/preview/render.png +0 -0
  270. package/static/preview/translation.png +0 -0
  271. package/static/providers/google_dark.png +0 -0
  272. package/static/providers/google_dark_edited.png +0 -0
  273. package/static/providers/google_light.png +0 -0
  274. package/static/providers/ms_dark.svg +1 -0
  275. package/static/providers/ms_light.svg +1 -0
  276. package/static/robots.txt +4 -0
  277. package/static/undraw_profile.svg +38 -0
  278. package/static/vendor/fontawesome-free/LICENSE.txt +34 -0
  279. package/static/vendor/fontawesome-free/attribution.js +3 -0
  280. package/static/vendor/fontawesome-free/css/all.css +4619 -0
  281. package/static/vendor/fontawesome-free/css/all.min.css +5 -0
  282. package/static/vendor/fontawesome-free/css/brands.css +15 -0
  283. package/static/vendor/fontawesome-free/css/brands.min.css +5 -0
  284. package/static/vendor/fontawesome-free/css/fontawesome.css +4585 -0
  285. package/static/vendor/fontawesome-free/css/fontawesome.min.css +5 -0
  286. package/static/vendor/fontawesome-free/css/regular.css +15 -0
  287. package/static/vendor/fontawesome-free/css/regular.min.css +5 -0
  288. package/static/vendor/fontawesome-free/css/solid.css +16 -0
  289. package/static/vendor/fontawesome-free/css/solid.min.css +5 -0
  290. package/static/vendor/fontawesome-free/css/svg-with-js.css +371 -0
  291. package/static/vendor/fontawesome-free/css/svg-with-js.min.css +5 -0
  292. package/static/vendor/fontawesome-free/css/v4-shims.css +2172 -0
  293. package/static/vendor/fontawesome-free/css/v4-shims.min.css +5 -0
  294. package/static/vendor/fontawesome-free/js/all.js +4467 -0
  295. package/static/vendor/fontawesome-free/js/all.min.js +5 -0
  296. package/static/vendor/fontawesome-free/js/brands.js +586 -0
  297. package/static/vendor/fontawesome-free/js/brands.min.js +5 -0
  298. package/static/vendor/fontawesome-free/js/conflict-detection.js +998 -0
  299. package/static/vendor/fontawesome-free/js/conflict-detection.min.js +5 -0
  300. package/static/vendor/fontawesome-free/js/fontawesome.js +2483 -0
  301. package/static/vendor/fontawesome-free/js/fontawesome.min.js +5 -0
  302. package/static/vendor/fontawesome-free/js/regular.js +280 -0
  303. package/static/vendor/fontawesome-free/js/regular.min.js +5 -0
  304. package/static/vendor/fontawesome-free/js/solid.js +1130 -0
  305. package/static/vendor/fontawesome-free/js/solid.min.js +5 -0
  306. package/static/vendor/fontawesome-free/js/v4-shims.js +68 -0
  307. package/static/vendor/fontawesome-free/js/v4-shims.min.js +5 -0
  308. package/static/vendor/fontawesome-free/less/_animated.less +19 -0
  309. package/static/vendor/fontawesome-free/less/_bordered-pulled.less +16 -0
  310. package/static/vendor/fontawesome-free/less/_core.less +12 -0
  311. package/static/vendor/fontawesome-free/less/_fixed-width.less +6 -0
  312. package/static/vendor/fontawesome-free/less/_icons.less +1462 -0
  313. package/static/vendor/fontawesome-free/less/_larger.less +27 -0
  314. package/static/vendor/fontawesome-free/less/_list.less +18 -0
  315. package/static/vendor/fontawesome-free/less/_mixins.less +56 -0
  316. package/static/vendor/fontawesome-free/less/_rotated-flipped.less +24 -0
  317. package/static/vendor/fontawesome-free/less/_screen-reader.less +5 -0
  318. package/static/vendor/fontawesome-free/less/_shims.less +2066 -0
  319. package/static/vendor/fontawesome-free/less/_stacked.less +22 -0
  320. package/static/vendor/fontawesome-free/less/_variables.less +1474 -0
  321. package/static/vendor/fontawesome-free/less/brands.less +23 -0
  322. package/static/vendor/fontawesome-free/less/fontawesome.less +16 -0
  323. package/static/vendor/fontawesome-free/less/regular.less +23 -0
  324. package/static/vendor/fontawesome-free/less/solid.less +24 -0
  325. package/static/vendor/fontawesome-free/less/v4-shims.less +6 -0
  326. package/static/vendor/fontawesome-free/metadata/categories.yml +2572 -0
  327. package/static/vendor/fontawesome-free/metadata/icons.yml +21783 -0
  328. package/static/vendor/fontawesome-free/metadata/shims.yml +298 -0
  329. package/static/vendor/fontawesome-free/metadata/sponsors.yml +744 -0
  330. package/static/vendor/fontawesome-free/package.json +58 -0
  331. package/static/vendor/fontawesome-free/scss/_animated.scss +20 -0
  332. package/static/vendor/fontawesome-free/scss/_bordered-pulled.scss +20 -0
  333. package/static/vendor/fontawesome-free/scss/_core.scss +21 -0
  334. package/static/vendor/fontawesome-free/scss/_fixed-width.scss +6 -0
  335. package/static/vendor/fontawesome-free/scss/_icons.scss +1462 -0
  336. package/static/vendor/fontawesome-free/scss/_larger.scss +23 -0
  337. package/static/vendor/fontawesome-free/scss/_list.scss +18 -0
  338. package/static/vendor/fontawesome-free/scss/_mixins.scss +56 -0
  339. package/static/vendor/fontawesome-free/scss/_rotated-flipped.scss +24 -0
  340. package/static/vendor/fontawesome-free/scss/_screen-reader.scss +5 -0
  341. package/static/vendor/fontawesome-free/scss/_shims.scss +2066 -0
  342. package/static/vendor/fontawesome-free/scss/_stacked.scss +31 -0
  343. package/static/vendor/fontawesome-free/scss/_variables.scss +1479 -0
  344. package/static/vendor/fontawesome-free/scss/brands.scss +23 -0
  345. package/static/vendor/fontawesome-free/scss/fontawesome.scss +16 -0
  346. package/static/vendor/fontawesome-free/scss/regular.scss +23 -0
  347. package/static/vendor/fontawesome-free/scss/solid.scss +24 -0
  348. package/static/vendor/fontawesome-free/scss/v4-shims.scss +6 -0
  349. package/static/vendor/fontawesome-free/sprites/brands.svg +1381 -0
  350. package/static/vendor/fontawesome-free/sprites/regular.svg +463 -0
  351. package/static/vendor/fontawesome-free/sprites/solid.svg +3013 -0
  352. package/static/vendor/fontawesome-free/svgs/brands/500px.svg +1 -0
  353. package/static/vendor/fontawesome-free/svgs/brands/accessible-icon.svg +1 -0
  354. package/static/vendor/fontawesome-free/svgs/brands/accusoft.svg +1 -0
  355. package/static/vendor/fontawesome-free/svgs/brands/acquisitions-incorporated.svg +1 -0
  356. package/static/vendor/fontawesome-free/svgs/brands/adn.svg +1 -0
  357. package/static/vendor/fontawesome-free/svgs/brands/adversal.svg +1 -0
  358. package/static/vendor/fontawesome-free/svgs/brands/affiliatetheme.svg +1 -0
  359. package/static/vendor/fontawesome-free/svgs/brands/airbnb.svg +1 -0
  360. package/static/vendor/fontawesome-free/svgs/brands/algolia.svg +1 -0
  361. package/static/vendor/fontawesome-free/svgs/brands/alipay.svg +1 -0
  362. package/static/vendor/fontawesome-free/svgs/brands/amazon-pay.svg +1 -0
  363. package/static/vendor/fontawesome-free/svgs/brands/amazon.svg +1 -0
  364. package/static/vendor/fontawesome-free/svgs/brands/amilia.svg +1 -0
  365. package/static/vendor/fontawesome-free/svgs/brands/android.svg +1 -0
  366. package/static/vendor/fontawesome-free/svgs/brands/angellist.svg +1 -0
  367. package/static/vendor/fontawesome-free/svgs/brands/angrycreative.svg +1 -0
  368. package/static/vendor/fontawesome-free/svgs/brands/angular.svg +1 -0
  369. package/static/vendor/fontawesome-free/svgs/brands/app-store-ios.svg +1 -0
  370. package/static/vendor/fontawesome-free/svgs/brands/app-store.svg +1 -0
  371. package/static/vendor/fontawesome-free/svgs/brands/apper.svg +1 -0
  372. package/static/vendor/fontawesome-free/svgs/brands/apple-pay.svg +1 -0
  373. package/static/vendor/fontawesome-free/svgs/brands/apple.svg +1 -0
  374. package/static/vendor/fontawesome-free/svgs/brands/artstation.svg +1 -0
  375. package/static/vendor/fontawesome-free/svgs/brands/asymmetrik.svg +1 -0
  376. package/static/vendor/fontawesome-free/svgs/brands/atlassian.svg +1 -0
  377. package/static/vendor/fontawesome-free/svgs/brands/audible.svg +1 -0
  378. package/static/vendor/fontawesome-free/svgs/brands/autoprefixer.svg +1 -0
  379. package/static/vendor/fontawesome-free/svgs/brands/avianex.svg +1 -0
  380. package/static/vendor/fontawesome-free/svgs/brands/aviato.svg +1 -0
  381. package/static/vendor/fontawesome-free/svgs/brands/aws.svg +1 -0
  382. package/static/vendor/fontawesome-free/svgs/brands/bandcamp.svg +1 -0
  383. package/static/vendor/fontawesome-free/svgs/brands/battle-net.svg +1 -0
  384. package/static/vendor/fontawesome-free/svgs/brands/behance-square.svg +1 -0
  385. package/static/vendor/fontawesome-free/svgs/brands/behance.svg +1 -0
  386. package/static/vendor/fontawesome-free/svgs/brands/bimobject.svg +1 -0
  387. package/static/vendor/fontawesome-free/svgs/brands/bitbucket.svg +1 -0
  388. package/static/vendor/fontawesome-free/svgs/brands/bitcoin.svg +1 -0
  389. package/static/vendor/fontawesome-free/svgs/brands/bity.svg +1 -0
  390. package/static/vendor/fontawesome-free/svgs/brands/black-tie.svg +1 -0
  391. package/static/vendor/fontawesome-free/svgs/brands/blackberry.svg +1 -0
  392. package/static/vendor/fontawesome-free/svgs/brands/blogger-b.svg +1 -0
  393. package/static/vendor/fontawesome-free/svgs/brands/blogger.svg +1 -0
  394. package/static/vendor/fontawesome-free/svgs/brands/bluetooth-b.svg +1 -0
  395. package/static/vendor/fontawesome-free/svgs/brands/bluetooth.svg +1 -0
  396. package/static/vendor/fontawesome-free/svgs/brands/bootstrap.svg +1 -0
  397. package/static/vendor/fontawesome-free/svgs/brands/btc.svg +1 -0
  398. package/static/vendor/fontawesome-free/svgs/brands/buffer.svg +1 -0
  399. package/static/vendor/fontawesome-free/svgs/brands/buromobelexperte.svg +1 -0
  400. package/static/vendor/fontawesome-free/svgs/brands/buy-n-large.svg +1 -0
  401. package/static/vendor/fontawesome-free/svgs/brands/buysellads.svg +1 -0
  402. package/static/vendor/fontawesome-free/svgs/brands/canadian-maple-leaf.svg +1 -0
  403. package/static/vendor/fontawesome-free/svgs/brands/cc-amazon-pay.svg +1 -0
  404. package/static/vendor/fontawesome-free/svgs/brands/cc-amex.svg +1 -0
  405. package/static/vendor/fontawesome-free/svgs/brands/cc-apple-pay.svg +1 -0
  406. package/static/vendor/fontawesome-free/svgs/brands/cc-diners-club.svg +1 -0
  407. package/static/vendor/fontawesome-free/svgs/brands/cc-discover.svg +1 -0
  408. package/static/vendor/fontawesome-free/svgs/brands/cc-jcb.svg +1 -0
  409. package/static/vendor/fontawesome-free/svgs/brands/cc-mastercard.svg +1 -0
  410. package/static/vendor/fontawesome-free/svgs/brands/cc-paypal.svg +1 -0
  411. package/static/vendor/fontawesome-free/svgs/brands/cc-stripe.svg +1 -0
  412. package/static/vendor/fontawesome-free/svgs/brands/cc-visa.svg +1 -0
  413. package/static/vendor/fontawesome-free/svgs/brands/centercode.svg +1 -0
  414. package/static/vendor/fontawesome-free/svgs/brands/centos.svg +1 -0
  415. package/static/vendor/fontawesome-free/svgs/brands/chrome.svg +1 -0
  416. package/static/vendor/fontawesome-free/svgs/brands/chromecast.svg +1 -0
  417. package/static/vendor/fontawesome-free/svgs/brands/cloudflare.svg +1 -0
  418. package/static/vendor/fontawesome-free/svgs/brands/cloudscale.svg +1 -0
  419. package/static/vendor/fontawesome-free/svgs/brands/cloudsmith.svg +1 -0
  420. package/static/vendor/fontawesome-free/svgs/brands/cloudversify.svg +1 -0
  421. package/static/vendor/fontawesome-free/svgs/brands/codepen.svg +1 -0
  422. package/static/vendor/fontawesome-free/svgs/brands/codiepie.svg +1 -0
  423. package/static/vendor/fontawesome-free/svgs/brands/confluence.svg +1 -0
  424. package/static/vendor/fontawesome-free/svgs/brands/connectdevelop.svg +1 -0
  425. package/static/vendor/fontawesome-free/svgs/brands/contao.svg +1 -0
  426. package/static/vendor/fontawesome-free/svgs/brands/cotton-bureau.svg +1 -0
  427. package/static/vendor/fontawesome-free/svgs/brands/cpanel.svg +1 -0
  428. package/static/vendor/fontawesome-free/svgs/brands/creative-commons-by.svg +1 -0
  429. package/static/vendor/fontawesome-free/svgs/brands/creative-commons-nc-eu.svg +1 -0
  430. package/static/vendor/fontawesome-free/svgs/brands/creative-commons-nc-jp.svg +1 -0
  431. package/static/vendor/fontawesome-free/svgs/brands/creative-commons-nc.svg +1 -0
  432. package/static/vendor/fontawesome-free/svgs/brands/creative-commons-nd.svg +1 -0
  433. package/static/vendor/fontawesome-free/svgs/brands/creative-commons-pd-alt.svg +1 -0
  434. package/static/vendor/fontawesome-free/svgs/brands/creative-commons-pd.svg +1 -0
  435. package/static/vendor/fontawesome-free/svgs/brands/creative-commons-remix.svg +1 -0
  436. package/static/vendor/fontawesome-free/svgs/brands/creative-commons-sa.svg +1 -0
  437. package/static/vendor/fontawesome-free/svgs/brands/creative-commons-sampling-plus.svg +1 -0
  438. package/static/vendor/fontawesome-free/svgs/brands/creative-commons-sampling.svg +1 -0
  439. package/static/vendor/fontawesome-free/svgs/brands/creative-commons-share.svg +1 -0
  440. package/static/vendor/fontawesome-free/svgs/brands/creative-commons-zero.svg +1 -0
  441. package/static/vendor/fontawesome-free/svgs/brands/creative-commons.svg +1 -0
  442. package/static/vendor/fontawesome-free/svgs/brands/critical-role.svg +1 -0
  443. package/static/vendor/fontawesome-free/svgs/brands/css3-alt.svg +1 -0
  444. package/static/vendor/fontawesome-free/svgs/brands/css3.svg +1 -0
  445. package/static/vendor/fontawesome-free/svgs/brands/cuttlefish.svg +1 -0
  446. package/static/vendor/fontawesome-free/svgs/brands/d-and-d-beyond.svg +1 -0
  447. package/static/vendor/fontawesome-free/svgs/brands/d-and-d.svg +1 -0
  448. package/static/vendor/fontawesome-free/svgs/brands/dailymotion.svg +1 -0
  449. package/static/vendor/fontawesome-free/svgs/brands/dashcube.svg +1 -0
  450. package/static/vendor/fontawesome-free/svgs/brands/deezer.svg +1 -0
  451. package/static/vendor/fontawesome-free/svgs/brands/delicious.svg +1 -0
  452. package/static/vendor/fontawesome-free/svgs/brands/deploydog.svg +1 -0
  453. package/static/vendor/fontawesome-free/svgs/brands/deskpro.svg +1 -0
  454. package/static/vendor/fontawesome-free/svgs/brands/dev.svg +1 -0
  455. package/static/vendor/fontawesome-free/svgs/brands/deviantart.svg +1 -0
  456. package/static/vendor/fontawesome-free/svgs/brands/dhl.svg +1 -0
  457. package/static/vendor/fontawesome-free/svgs/brands/diaspora.svg +1 -0
  458. package/static/vendor/fontawesome-free/svgs/brands/digg.svg +1 -0
  459. package/static/vendor/fontawesome-free/svgs/brands/digital-ocean.svg +1 -0
  460. package/static/vendor/fontawesome-free/svgs/brands/discord.svg +1 -0
  461. package/static/vendor/fontawesome-free/svgs/brands/discourse.svg +1 -0
  462. package/static/vendor/fontawesome-free/svgs/brands/dochub.svg +1 -0
  463. package/static/vendor/fontawesome-free/svgs/brands/docker.svg +1 -0
  464. package/static/vendor/fontawesome-free/svgs/brands/draft2digital.svg +1 -0
  465. package/static/vendor/fontawesome-free/svgs/brands/dribbble-square.svg +1 -0
  466. package/static/vendor/fontawesome-free/svgs/brands/dribbble.svg +1 -0
  467. package/static/vendor/fontawesome-free/svgs/brands/dropbox.svg +1 -0
  468. package/static/vendor/fontawesome-free/svgs/brands/drupal.svg +1 -0
  469. package/static/vendor/fontawesome-free/svgs/brands/dyalog.svg +1 -0
  470. package/static/vendor/fontawesome-free/svgs/brands/earlybirds.svg +1 -0
  471. package/static/vendor/fontawesome-free/svgs/brands/ebay.svg +1 -0
  472. package/static/vendor/fontawesome-free/svgs/brands/edge-legacy.svg +1 -0
  473. package/static/vendor/fontawesome-free/svgs/brands/edge.svg +1 -0
  474. package/static/vendor/fontawesome-free/svgs/brands/elementor.svg +1 -0
  475. package/static/vendor/fontawesome-free/svgs/brands/ello.svg +1 -0
  476. package/static/vendor/fontawesome-free/svgs/brands/ember.svg +1 -0
  477. package/static/vendor/fontawesome-free/svgs/brands/empire.svg +1 -0
  478. package/static/vendor/fontawesome-free/svgs/brands/envira.svg +1 -0
  479. package/static/vendor/fontawesome-free/svgs/brands/erlang.svg +1 -0
  480. package/static/vendor/fontawesome-free/svgs/brands/ethereum.svg +1 -0
  481. package/static/vendor/fontawesome-free/svgs/brands/etsy.svg +1 -0
  482. package/static/vendor/fontawesome-free/svgs/brands/evernote.svg +1 -0
  483. package/static/vendor/fontawesome-free/svgs/brands/expeditedssl.svg +1 -0
  484. package/static/vendor/fontawesome-free/svgs/brands/facebook-f.svg +1 -0
  485. package/static/vendor/fontawesome-free/svgs/brands/facebook-messenger.svg +1 -0
  486. package/static/vendor/fontawesome-free/svgs/brands/facebook-square.svg +1 -0
  487. package/static/vendor/fontawesome-free/svgs/brands/facebook.svg +1 -0
  488. package/static/vendor/fontawesome-free/svgs/brands/fantasy-flight-games.svg +1 -0
  489. package/static/vendor/fontawesome-free/svgs/brands/fedex.svg +1 -0
  490. package/static/vendor/fontawesome-free/svgs/brands/fedora.svg +1 -0
  491. package/static/vendor/fontawesome-free/svgs/brands/figma.svg +1 -0
  492. package/static/vendor/fontawesome-free/svgs/brands/firefox-browser.svg +1 -0
  493. package/static/vendor/fontawesome-free/svgs/brands/firefox.svg +1 -0
  494. package/static/vendor/fontawesome-free/svgs/brands/first-order-alt.svg +1 -0
  495. package/static/vendor/fontawesome-free/svgs/brands/first-order.svg +1 -0
  496. package/static/vendor/fontawesome-free/svgs/brands/firstdraft.svg +1 -0
  497. package/static/vendor/fontawesome-free/svgs/brands/flickr.svg +1 -0
  498. package/static/vendor/fontawesome-free/svgs/brands/flipboard.svg +1 -0
  499. package/static/vendor/fontawesome-free/svgs/brands/fly.svg +1 -0
  500. package/static/vendor/fontawesome-free/svgs/brands/font-awesome-alt.svg +1 -0
  501. package/static/vendor/fontawesome-free/svgs/brands/font-awesome-flag.svg +1 -0
  502. package/static/vendor/fontawesome-free/svgs/brands/font-awesome-logo-full.svg +1 -0
  503. package/static/vendor/fontawesome-free/svgs/brands/font-awesome.svg +1 -0
  504. package/static/vendor/fontawesome-free/svgs/brands/fonticons-fi.svg +1 -0
  505. package/static/vendor/fontawesome-free/svgs/brands/fonticons.svg +1 -0
  506. package/static/vendor/fontawesome-free/svgs/brands/fort-awesome-alt.svg +1 -0
  507. package/static/vendor/fontawesome-free/svgs/brands/fort-awesome.svg +1 -0
  508. package/static/vendor/fontawesome-free/svgs/brands/forumbee.svg +1 -0
  509. package/static/vendor/fontawesome-free/svgs/brands/foursquare.svg +1 -0
  510. package/static/vendor/fontawesome-free/svgs/brands/free-code-camp.svg +1 -0
  511. package/static/vendor/fontawesome-free/svgs/brands/freebsd.svg +1 -0
  512. package/static/vendor/fontawesome-free/svgs/brands/fulcrum.svg +1 -0
  513. package/static/vendor/fontawesome-free/svgs/brands/galactic-republic.svg +1 -0
  514. package/static/vendor/fontawesome-free/svgs/brands/galactic-senate.svg +1 -0
  515. package/static/vendor/fontawesome-free/svgs/brands/get-pocket.svg +1 -0
  516. package/static/vendor/fontawesome-free/svgs/brands/gg-circle.svg +1 -0
  517. package/static/vendor/fontawesome-free/svgs/brands/gg.svg +1 -0
  518. package/static/vendor/fontawesome-free/svgs/brands/git-alt.svg +1 -0
  519. package/static/vendor/fontawesome-free/svgs/brands/git-square.svg +1 -0
  520. package/static/vendor/fontawesome-free/svgs/brands/git.svg +1 -0
  521. package/static/vendor/fontawesome-free/svgs/brands/github-alt.svg +1 -0
  522. package/static/vendor/fontawesome-free/svgs/brands/github-square.svg +1 -0
  523. package/static/vendor/fontawesome-free/svgs/brands/github.svg +1 -0
  524. package/static/vendor/fontawesome-free/svgs/brands/gitkraken.svg +1 -0
  525. package/static/vendor/fontawesome-free/svgs/brands/gitlab.svg +1 -0
  526. package/static/vendor/fontawesome-free/svgs/brands/gitter.svg +1 -0
  527. package/static/vendor/fontawesome-free/svgs/brands/glide-g.svg +1 -0
  528. package/static/vendor/fontawesome-free/svgs/brands/glide.svg +1 -0
  529. package/static/vendor/fontawesome-free/svgs/brands/gofore.svg +1 -0
  530. package/static/vendor/fontawesome-free/svgs/brands/goodreads-g.svg +1 -0
  531. package/static/vendor/fontawesome-free/svgs/brands/goodreads.svg +1 -0
  532. package/static/vendor/fontawesome-free/svgs/brands/google-drive.svg +1 -0
  533. package/static/vendor/fontawesome-free/svgs/brands/google-pay.svg +1 -0
  534. package/static/vendor/fontawesome-free/svgs/brands/google-play.svg +1 -0
  535. package/static/vendor/fontawesome-free/svgs/brands/google-plus-g.svg +1 -0
  536. package/static/vendor/fontawesome-free/svgs/brands/google-plus-square.svg +1 -0
  537. package/static/vendor/fontawesome-free/svgs/brands/google-plus.svg +1 -0
  538. package/static/vendor/fontawesome-free/svgs/brands/google-wallet.svg +1 -0
  539. package/static/vendor/fontawesome-free/svgs/brands/google.svg +1 -0
  540. package/static/vendor/fontawesome-free/svgs/brands/gratipay.svg +1 -0
  541. package/static/vendor/fontawesome-free/svgs/brands/grav.svg +1 -0
  542. package/static/vendor/fontawesome-free/svgs/brands/gripfire.svg +1 -0
  543. package/static/vendor/fontawesome-free/svgs/brands/grunt.svg +1 -0
  544. package/static/vendor/fontawesome-free/svgs/brands/guilded.svg +1 -0
  545. package/static/vendor/fontawesome-free/svgs/brands/gulp.svg +1 -0
  546. package/static/vendor/fontawesome-free/svgs/brands/hacker-news-square.svg +1 -0
  547. package/static/vendor/fontawesome-free/svgs/brands/hacker-news.svg +1 -0
  548. package/static/vendor/fontawesome-free/svgs/brands/hackerrank.svg +1 -0
  549. package/static/vendor/fontawesome-free/svgs/brands/hips.svg +1 -0
  550. package/static/vendor/fontawesome-free/svgs/brands/hire-a-helper.svg +1 -0
  551. package/static/vendor/fontawesome-free/svgs/brands/hive.svg +1 -0
  552. package/static/vendor/fontawesome-free/svgs/brands/hooli.svg +1 -0
  553. package/static/vendor/fontawesome-free/svgs/brands/hornbill.svg +1 -0
  554. package/static/vendor/fontawesome-free/svgs/brands/hotjar.svg +1 -0
  555. package/static/vendor/fontawesome-free/svgs/brands/houzz.svg +1 -0
  556. package/static/vendor/fontawesome-free/svgs/brands/html5.svg +1 -0
  557. package/static/vendor/fontawesome-free/svgs/brands/hubspot.svg +1 -0
  558. package/static/vendor/fontawesome-free/svgs/brands/ideal.svg +1 -0
  559. package/static/vendor/fontawesome-free/svgs/brands/imdb.svg +1 -0
  560. package/static/vendor/fontawesome-free/svgs/brands/innosoft.svg +1 -0
  561. package/static/vendor/fontawesome-free/svgs/brands/instagram-square.svg +1 -0
  562. package/static/vendor/fontawesome-free/svgs/brands/instagram.svg +1 -0
  563. package/static/vendor/fontawesome-free/svgs/brands/instalod.svg +1 -0
  564. package/static/vendor/fontawesome-free/svgs/brands/intercom.svg +1 -0
  565. package/static/vendor/fontawesome-free/svgs/brands/internet-explorer.svg +1 -0
  566. package/static/vendor/fontawesome-free/svgs/brands/invision.svg +1 -0
  567. package/static/vendor/fontawesome-free/svgs/brands/ioxhost.svg +1 -0
  568. package/static/vendor/fontawesome-free/svgs/brands/itch-io.svg +1 -0
  569. package/static/vendor/fontawesome-free/svgs/brands/itunes-note.svg +1 -0
  570. package/static/vendor/fontawesome-free/svgs/brands/itunes.svg +1 -0
  571. package/static/vendor/fontawesome-free/svgs/brands/java.svg +1 -0
  572. package/static/vendor/fontawesome-free/svgs/brands/jedi-order.svg +1 -0
  573. package/static/vendor/fontawesome-free/svgs/brands/jenkins.svg +1 -0
  574. package/static/vendor/fontawesome-free/svgs/brands/jira.svg +1 -0
  575. package/static/vendor/fontawesome-free/svgs/brands/joget.svg +1 -0
  576. package/static/vendor/fontawesome-free/svgs/brands/joomla.svg +1 -0
  577. package/static/vendor/fontawesome-free/svgs/brands/js-square.svg +1 -0
  578. package/static/vendor/fontawesome-free/svgs/brands/js.svg +1 -0
  579. package/static/vendor/fontawesome-free/svgs/brands/jsfiddle.svg +1 -0
  580. package/static/vendor/fontawesome-free/svgs/brands/kaggle.svg +1 -0
  581. package/static/vendor/fontawesome-free/svgs/brands/keybase.svg +1 -0
  582. package/static/vendor/fontawesome-free/svgs/brands/keycdn.svg +1 -0
  583. package/static/vendor/fontawesome-free/svgs/brands/kickstarter-k.svg +1 -0
  584. package/static/vendor/fontawesome-free/svgs/brands/kickstarter.svg +1 -0
  585. package/static/vendor/fontawesome-free/svgs/brands/korvue.svg +1 -0
  586. package/static/vendor/fontawesome-free/svgs/brands/laravel.svg +1 -0
  587. package/static/vendor/fontawesome-free/svgs/brands/lastfm-square.svg +1 -0
  588. package/static/vendor/fontawesome-free/svgs/brands/lastfm.svg +1 -0
  589. package/static/vendor/fontawesome-free/svgs/brands/leanpub.svg +1 -0
  590. package/static/vendor/fontawesome-free/svgs/brands/less.svg +1 -0
  591. package/static/vendor/fontawesome-free/svgs/brands/line.svg +1 -0
  592. package/static/vendor/fontawesome-free/svgs/brands/linkedin-in.svg +1 -0
  593. package/static/vendor/fontawesome-free/svgs/brands/linkedin.svg +1 -0
  594. package/static/vendor/fontawesome-free/svgs/brands/linode.svg +1 -0
  595. package/static/vendor/fontawesome-free/svgs/brands/linux.svg +1 -0
  596. package/static/vendor/fontawesome-free/svgs/brands/lyft.svg +1 -0
  597. package/static/vendor/fontawesome-free/svgs/brands/magento.svg +1 -0
  598. package/static/vendor/fontawesome-free/svgs/brands/mailchimp.svg +1 -0
  599. package/static/vendor/fontawesome-free/svgs/brands/mandalorian.svg +1 -0
  600. package/static/vendor/fontawesome-free/svgs/brands/markdown.svg +1 -0
  601. package/static/vendor/fontawesome-free/svgs/brands/mastodon.svg +1 -0
  602. package/static/vendor/fontawesome-free/svgs/brands/maxcdn.svg +1 -0
  603. package/static/vendor/fontawesome-free/svgs/brands/mdb.svg +1 -0
  604. package/static/vendor/fontawesome-free/svgs/brands/medapps.svg +1 -0
  605. package/static/vendor/fontawesome-free/svgs/brands/medium-m.svg +1 -0
  606. package/static/vendor/fontawesome-free/svgs/brands/medium.svg +1 -0
  607. package/static/vendor/fontawesome-free/svgs/brands/medrt.svg +1 -0
  608. package/static/vendor/fontawesome-free/svgs/brands/meetup.svg +1 -0
  609. package/static/vendor/fontawesome-free/svgs/brands/megaport.svg +1 -0
  610. package/static/vendor/fontawesome-free/svgs/brands/mendeley.svg +1 -0
  611. package/static/vendor/fontawesome-free/svgs/brands/microblog.svg +1 -0
  612. package/static/vendor/fontawesome-free/svgs/brands/microsoft.svg +1 -0
  613. package/static/vendor/fontawesome-free/svgs/brands/mix.svg +1 -0
  614. package/static/vendor/fontawesome-free/svgs/brands/mixcloud.svg +1 -0
  615. package/static/vendor/fontawesome-free/svgs/brands/mixer.svg +1 -0
  616. package/static/vendor/fontawesome-free/svgs/brands/mizuni.svg +1 -0
  617. package/static/vendor/fontawesome-free/svgs/brands/modx.svg +1 -0
  618. package/static/vendor/fontawesome-free/svgs/brands/monero.svg +1 -0
  619. package/static/vendor/fontawesome-free/svgs/brands/napster.svg +1 -0
  620. package/static/vendor/fontawesome-free/svgs/brands/neos.svg +1 -0
  621. package/static/vendor/fontawesome-free/svgs/brands/nimblr.svg +1 -0
  622. package/static/vendor/fontawesome-free/svgs/brands/node-js.svg +1 -0
  623. package/static/vendor/fontawesome-free/svgs/brands/node.svg +1 -0
  624. package/static/vendor/fontawesome-free/svgs/brands/npm.svg +1 -0
  625. package/static/vendor/fontawesome-free/svgs/brands/ns8.svg +1 -0
  626. package/static/vendor/fontawesome-free/svgs/brands/nutritionix.svg +1 -0
  627. package/static/vendor/fontawesome-free/svgs/brands/octopus-deploy.svg +1 -0
  628. package/static/vendor/fontawesome-free/svgs/brands/odnoklassniki-square.svg +1 -0
  629. package/static/vendor/fontawesome-free/svgs/brands/odnoklassniki.svg +1 -0
  630. package/static/vendor/fontawesome-free/svgs/brands/old-republic.svg +1 -0
  631. package/static/vendor/fontawesome-free/svgs/brands/opencart.svg +1 -0
  632. package/static/vendor/fontawesome-free/svgs/brands/openid.svg +1 -0
  633. package/static/vendor/fontawesome-free/svgs/brands/opera.svg +1 -0
  634. package/static/vendor/fontawesome-free/svgs/brands/optin-monster.svg +1 -0
  635. package/static/vendor/fontawesome-free/svgs/brands/orcid.svg +1 -0
  636. package/static/vendor/fontawesome-free/svgs/brands/osi.svg +1 -0
  637. package/static/vendor/fontawesome-free/svgs/brands/page4.svg +1 -0
  638. package/static/vendor/fontawesome-free/svgs/brands/pagelines.svg +1 -0
  639. package/static/vendor/fontawesome-free/svgs/brands/palfed.svg +1 -0
  640. package/static/vendor/fontawesome-free/svgs/brands/patreon.svg +1 -0
  641. package/static/vendor/fontawesome-free/svgs/brands/paypal.svg +1 -0
  642. package/static/vendor/fontawesome-free/svgs/brands/penny-arcade.svg +1 -0
  643. package/static/vendor/fontawesome-free/svgs/brands/perbyte.svg +1 -0
  644. package/static/vendor/fontawesome-free/svgs/brands/periscope.svg +1 -0
  645. package/static/vendor/fontawesome-free/svgs/brands/phabricator.svg +1 -0
  646. package/static/vendor/fontawesome-free/svgs/brands/phoenix-framework.svg +1 -0
  647. package/static/vendor/fontawesome-free/svgs/brands/phoenix-squadron.svg +1 -0
  648. package/static/vendor/fontawesome-free/svgs/brands/php.svg +1 -0
  649. package/static/vendor/fontawesome-free/svgs/brands/pied-piper-alt.svg +1 -0
  650. package/static/vendor/fontawesome-free/svgs/brands/pied-piper-hat.svg +1 -0
  651. package/static/vendor/fontawesome-free/svgs/brands/pied-piper-pp.svg +1 -0
  652. package/static/vendor/fontawesome-free/svgs/brands/pied-piper-square.svg +1 -0
  653. package/static/vendor/fontawesome-free/svgs/brands/pied-piper.svg +1 -0
  654. package/static/vendor/fontawesome-free/svgs/brands/pinterest-p.svg +1 -0
  655. package/static/vendor/fontawesome-free/svgs/brands/pinterest-square.svg +1 -0
  656. package/static/vendor/fontawesome-free/svgs/brands/pinterest.svg +1 -0
  657. package/static/vendor/fontawesome-free/svgs/brands/playstation.svg +1 -0
  658. package/static/vendor/fontawesome-free/svgs/brands/product-hunt.svg +1 -0
  659. package/static/vendor/fontawesome-free/svgs/brands/pushed.svg +1 -0
  660. package/static/vendor/fontawesome-free/svgs/brands/python.svg +1 -0
  661. package/static/vendor/fontawesome-free/svgs/brands/qq.svg +1 -0
  662. package/static/vendor/fontawesome-free/svgs/brands/quinscape.svg +1 -0
  663. package/static/vendor/fontawesome-free/svgs/brands/quora.svg +1 -0
  664. package/static/vendor/fontawesome-free/svgs/brands/r-project.svg +1 -0
  665. package/static/vendor/fontawesome-free/svgs/brands/raspberry-pi.svg +1 -0
  666. package/static/vendor/fontawesome-free/svgs/brands/ravelry.svg +1 -0
  667. package/static/vendor/fontawesome-free/svgs/brands/react.svg +1 -0
  668. package/static/vendor/fontawesome-free/svgs/brands/reacteurope.svg +1 -0
  669. package/static/vendor/fontawesome-free/svgs/brands/readme.svg +1 -0
  670. package/static/vendor/fontawesome-free/svgs/brands/rebel.svg +1 -0
  671. package/static/vendor/fontawesome-free/svgs/brands/red-river.svg +1 -0
  672. package/static/vendor/fontawesome-free/svgs/brands/reddit-alien.svg +1 -0
  673. package/static/vendor/fontawesome-free/svgs/brands/reddit-square.svg +1 -0
  674. package/static/vendor/fontawesome-free/svgs/brands/reddit.svg +1 -0
  675. package/static/vendor/fontawesome-free/svgs/brands/redhat.svg +1 -0
  676. package/static/vendor/fontawesome-free/svgs/brands/renren.svg +1 -0
  677. package/static/vendor/fontawesome-free/svgs/brands/replyd.svg +1 -0
  678. package/static/vendor/fontawesome-free/svgs/brands/researchgate.svg +1 -0
  679. package/static/vendor/fontawesome-free/svgs/brands/resolving.svg +1 -0
  680. package/static/vendor/fontawesome-free/svgs/brands/rev.svg +1 -0
  681. package/static/vendor/fontawesome-free/svgs/brands/rocketchat.svg +1 -0
  682. package/static/vendor/fontawesome-free/svgs/brands/rockrms.svg +1 -0
  683. package/static/vendor/fontawesome-free/svgs/brands/rust.svg +1 -0
  684. package/static/vendor/fontawesome-free/svgs/brands/safari.svg +1 -0
  685. package/static/vendor/fontawesome-free/svgs/brands/salesforce.svg +1 -0
  686. package/static/vendor/fontawesome-free/svgs/brands/sass.svg +1 -0
  687. package/static/vendor/fontawesome-free/svgs/brands/schlix.svg +1 -0
  688. package/static/vendor/fontawesome-free/svgs/brands/scribd.svg +1 -0
  689. package/static/vendor/fontawesome-free/svgs/brands/searchengin.svg +1 -0
  690. package/static/vendor/fontawesome-free/svgs/brands/sellcast.svg +1 -0
  691. package/static/vendor/fontawesome-free/svgs/brands/sellsy.svg +1 -0
  692. package/static/vendor/fontawesome-free/svgs/brands/servicestack.svg +1 -0
  693. package/static/vendor/fontawesome-free/svgs/brands/shirtsinbulk.svg +1 -0
  694. package/static/vendor/fontawesome-free/svgs/brands/shopify.svg +1 -0
  695. package/static/vendor/fontawesome-free/svgs/brands/shopware.svg +1 -0
  696. package/static/vendor/fontawesome-free/svgs/brands/simplybuilt.svg +1 -0
  697. package/static/vendor/fontawesome-free/svgs/brands/sistrix.svg +1 -0
  698. package/static/vendor/fontawesome-free/svgs/brands/sith.svg +1 -0
  699. package/static/vendor/fontawesome-free/svgs/brands/sketch.svg +1 -0
  700. package/static/vendor/fontawesome-free/svgs/brands/skyatlas.svg +1 -0
  701. package/static/vendor/fontawesome-free/svgs/brands/skype.svg +1 -0
  702. package/static/vendor/fontawesome-free/svgs/brands/slack-hash.svg +1 -0
  703. package/static/vendor/fontawesome-free/svgs/brands/slack.svg +1 -0
  704. package/static/vendor/fontawesome-free/svgs/brands/slideshare.svg +1 -0
  705. package/static/vendor/fontawesome-free/svgs/brands/snapchat-ghost.svg +1 -0
  706. package/static/vendor/fontawesome-free/svgs/brands/snapchat-square.svg +1 -0
  707. package/static/vendor/fontawesome-free/svgs/brands/snapchat.svg +1 -0
  708. package/static/vendor/fontawesome-free/svgs/brands/soundcloud.svg +1 -0
  709. package/static/vendor/fontawesome-free/svgs/brands/sourcetree.svg +1 -0
  710. package/static/vendor/fontawesome-free/svgs/brands/speakap.svg +1 -0
  711. package/static/vendor/fontawesome-free/svgs/brands/speaker-deck.svg +1 -0
  712. package/static/vendor/fontawesome-free/svgs/brands/spotify.svg +1 -0
  713. package/static/vendor/fontawesome-free/svgs/brands/squarespace.svg +1 -0
  714. package/static/vendor/fontawesome-free/svgs/brands/stack-exchange.svg +1 -0
  715. package/static/vendor/fontawesome-free/svgs/brands/stack-overflow.svg +1 -0
  716. package/static/vendor/fontawesome-free/svgs/brands/stackpath.svg +1 -0
  717. package/static/vendor/fontawesome-free/svgs/brands/staylinked.svg +1 -0
  718. package/static/vendor/fontawesome-free/svgs/brands/steam-square.svg +1 -0
  719. package/static/vendor/fontawesome-free/svgs/brands/steam-symbol.svg +1 -0
  720. package/static/vendor/fontawesome-free/svgs/brands/steam.svg +1 -0
  721. package/static/vendor/fontawesome-free/svgs/brands/sticker-mule.svg +1 -0
  722. package/static/vendor/fontawesome-free/svgs/brands/strava.svg +1 -0
  723. package/static/vendor/fontawesome-free/svgs/brands/stripe-s.svg +1 -0
  724. package/static/vendor/fontawesome-free/svgs/brands/stripe.svg +1 -0
  725. package/static/vendor/fontawesome-free/svgs/brands/studiovinari.svg +1 -0
  726. package/static/vendor/fontawesome-free/svgs/brands/stumbleupon-circle.svg +1 -0
  727. package/static/vendor/fontawesome-free/svgs/brands/stumbleupon.svg +1 -0
  728. package/static/vendor/fontawesome-free/svgs/brands/superpowers.svg +1 -0
  729. package/static/vendor/fontawesome-free/svgs/brands/supple.svg +1 -0
  730. package/static/vendor/fontawesome-free/svgs/brands/suse.svg +1 -0
  731. package/static/vendor/fontawesome-free/svgs/brands/swift.svg +1 -0
  732. package/static/vendor/fontawesome-free/svgs/brands/symfony.svg +1 -0
  733. package/static/vendor/fontawesome-free/svgs/brands/teamspeak.svg +1 -0
  734. package/static/vendor/fontawesome-free/svgs/brands/telegram-plane.svg +1 -0
  735. package/static/vendor/fontawesome-free/svgs/brands/telegram.svg +1 -0
  736. package/static/vendor/fontawesome-free/svgs/brands/tencent-weibo.svg +1 -0
  737. package/static/vendor/fontawesome-free/svgs/brands/the-red-yeti.svg +1 -0
  738. package/static/vendor/fontawesome-free/svgs/brands/themeco.svg +1 -0
  739. package/static/vendor/fontawesome-free/svgs/brands/themeisle.svg +1 -0
  740. package/static/vendor/fontawesome-free/svgs/brands/think-peaks.svg +1 -0
  741. package/static/vendor/fontawesome-free/svgs/brands/tiktok.svg +1 -0
  742. package/static/vendor/fontawesome-free/svgs/brands/trade-federation.svg +1 -0
  743. package/static/vendor/fontawesome-free/svgs/brands/trello.svg +1 -0
  744. package/static/vendor/fontawesome-free/svgs/brands/tripadvisor.svg +1 -0
  745. package/static/vendor/fontawesome-free/svgs/brands/tumblr-square.svg +1 -0
  746. package/static/vendor/fontawesome-free/svgs/brands/tumblr.svg +1 -0
  747. package/static/vendor/fontawesome-free/svgs/brands/twitch.svg +1 -0
  748. package/static/vendor/fontawesome-free/svgs/brands/twitter-square.svg +1 -0
  749. package/static/vendor/fontawesome-free/svgs/brands/twitter.svg +1 -0
  750. package/static/vendor/fontawesome-free/svgs/brands/typo3.svg +1 -0
  751. package/static/vendor/fontawesome-free/svgs/brands/uber.svg +1 -0
  752. package/static/vendor/fontawesome-free/svgs/brands/ubuntu.svg +1 -0
  753. package/static/vendor/fontawesome-free/svgs/brands/uikit.svg +1 -0
  754. package/static/vendor/fontawesome-free/svgs/brands/umbraco.svg +1 -0
  755. package/static/vendor/fontawesome-free/svgs/brands/uncharted.svg +1 -0
  756. package/static/vendor/fontawesome-free/svgs/brands/uniregistry.svg +1 -0
  757. package/static/vendor/fontawesome-free/svgs/brands/unity.svg +1 -0
  758. package/static/vendor/fontawesome-free/svgs/brands/unsplash.svg +1 -0
  759. package/static/vendor/fontawesome-free/svgs/brands/untappd.svg +1 -0
  760. package/static/vendor/fontawesome-free/svgs/brands/ups.svg +1 -0
  761. package/static/vendor/fontawesome-free/svgs/brands/usb.svg +1 -0
  762. package/static/vendor/fontawesome-free/svgs/brands/usps.svg +1 -0
  763. package/static/vendor/fontawesome-free/svgs/brands/ussunnah.svg +1 -0
  764. package/static/vendor/fontawesome-free/svgs/brands/vaadin.svg +1 -0
  765. package/static/vendor/fontawesome-free/svgs/brands/viacoin.svg +1 -0
  766. package/static/vendor/fontawesome-free/svgs/brands/viadeo-square.svg +1 -0
  767. package/static/vendor/fontawesome-free/svgs/brands/viadeo.svg +1 -0
  768. package/static/vendor/fontawesome-free/svgs/brands/viber.svg +1 -0
  769. package/static/vendor/fontawesome-free/svgs/brands/vimeo-square.svg +1 -0
  770. package/static/vendor/fontawesome-free/svgs/brands/vimeo-v.svg +1 -0
  771. package/static/vendor/fontawesome-free/svgs/brands/vimeo.svg +1 -0
  772. package/static/vendor/fontawesome-free/svgs/brands/vine.svg +1 -0
  773. package/static/vendor/fontawesome-free/svgs/brands/vk.svg +1 -0
  774. package/static/vendor/fontawesome-free/svgs/brands/vnv.svg +1 -0
  775. package/static/vendor/fontawesome-free/svgs/brands/vuejs.svg +1 -0
  776. package/static/vendor/fontawesome-free/svgs/brands/watchman-monitoring.svg +1 -0
  777. package/static/vendor/fontawesome-free/svgs/brands/waze.svg +1 -0
  778. package/static/vendor/fontawesome-free/svgs/brands/weebly.svg +1 -0
  779. package/static/vendor/fontawesome-free/svgs/brands/weibo.svg +1 -0
  780. package/static/vendor/fontawesome-free/svgs/brands/weixin.svg +1 -0
  781. package/static/vendor/fontawesome-free/svgs/brands/whatsapp-square.svg +1 -0
  782. package/static/vendor/fontawesome-free/svgs/brands/whatsapp.svg +1 -0
  783. package/static/vendor/fontawesome-free/svgs/brands/whmcs.svg +1 -0
  784. package/static/vendor/fontawesome-free/svgs/brands/wikipedia-w.svg +1 -0
  785. package/static/vendor/fontawesome-free/svgs/brands/windows.svg +1 -0
  786. package/static/vendor/fontawesome-free/svgs/brands/wix.svg +1 -0
  787. package/static/vendor/fontawesome-free/svgs/brands/wizards-of-the-coast.svg +1 -0
  788. package/static/vendor/fontawesome-free/svgs/brands/wodu.svg +1 -0
  789. package/static/vendor/fontawesome-free/svgs/brands/wolf-pack-battalion.svg +1 -0
  790. package/static/vendor/fontawesome-free/svgs/brands/wordpress-simple.svg +1 -0
  791. package/static/vendor/fontawesome-free/svgs/brands/wordpress.svg +1 -0
  792. package/static/vendor/fontawesome-free/svgs/brands/wpbeginner.svg +1 -0
  793. package/static/vendor/fontawesome-free/svgs/brands/wpexplorer.svg +1 -0
  794. package/static/vendor/fontawesome-free/svgs/brands/wpforms.svg +1 -0
  795. package/static/vendor/fontawesome-free/svgs/brands/wpressr.svg +1 -0
  796. package/static/vendor/fontawesome-free/svgs/brands/xbox.svg +1 -0
  797. package/static/vendor/fontawesome-free/svgs/brands/xing-square.svg +1 -0
  798. package/static/vendor/fontawesome-free/svgs/brands/xing.svg +1 -0
  799. package/static/vendor/fontawesome-free/svgs/brands/y-combinator.svg +1 -0
  800. package/static/vendor/fontawesome-free/svgs/brands/yahoo.svg +1 -0
  801. package/static/vendor/fontawesome-free/svgs/brands/yammer.svg +1 -0
  802. package/static/vendor/fontawesome-free/svgs/brands/yandex-international.svg +1 -0
  803. package/static/vendor/fontawesome-free/svgs/brands/yandex.svg +1 -0
  804. package/static/vendor/fontawesome-free/svgs/brands/yarn.svg +1 -0
  805. package/static/vendor/fontawesome-free/svgs/brands/yelp.svg +1 -0
  806. package/static/vendor/fontawesome-free/svgs/brands/yoast.svg +1 -0
  807. package/static/vendor/fontawesome-free/svgs/brands/youtube-square.svg +1 -0
  808. package/static/vendor/fontawesome-free/svgs/brands/youtube.svg +1 -0
  809. package/static/vendor/fontawesome-free/svgs/brands/zhihu.svg +1 -0
  810. package/static/vendor/fontawesome-free/svgs/regular/address-book.svg +1 -0
  811. package/static/vendor/fontawesome-free/svgs/regular/address-card.svg +1 -0
  812. package/static/vendor/fontawesome-free/svgs/regular/angry.svg +1 -0
  813. package/static/vendor/fontawesome-free/svgs/regular/arrow-alt-circle-down.svg +1 -0
  814. package/static/vendor/fontawesome-free/svgs/regular/arrow-alt-circle-left.svg +1 -0
  815. package/static/vendor/fontawesome-free/svgs/regular/arrow-alt-circle-right.svg +1 -0
  816. package/static/vendor/fontawesome-free/svgs/regular/arrow-alt-circle-up.svg +1 -0
  817. package/static/vendor/fontawesome-free/svgs/regular/bell-slash.svg +1 -0
  818. package/static/vendor/fontawesome-free/svgs/regular/bell.svg +1 -0
  819. package/static/vendor/fontawesome-free/svgs/regular/bookmark.svg +1 -0
  820. package/static/vendor/fontawesome-free/svgs/regular/building.svg +1 -0
  821. package/static/vendor/fontawesome-free/svgs/regular/calendar-alt.svg +1 -0
  822. package/static/vendor/fontawesome-free/svgs/regular/calendar-check.svg +1 -0
  823. package/static/vendor/fontawesome-free/svgs/regular/calendar-minus.svg +1 -0
  824. package/static/vendor/fontawesome-free/svgs/regular/calendar-plus.svg +1 -0
  825. package/static/vendor/fontawesome-free/svgs/regular/calendar-times.svg +1 -0
  826. package/static/vendor/fontawesome-free/svgs/regular/calendar.svg +1 -0
  827. package/static/vendor/fontawesome-free/svgs/regular/caret-square-down.svg +1 -0
  828. package/static/vendor/fontawesome-free/svgs/regular/caret-square-left.svg +1 -0
  829. package/static/vendor/fontawesome-free/svgs/regular/caret-square-right.svg +1 -0
  830. package/static/vendor/fontawesome-free/svgs/regular/caret-square-up.svg +1 -0
  831. package/static/vendor/fontawesome-free/svgs/regular/chart-bar.svg +1 -0
  832. package/static/vendor/fontawesome-free/svgs/regular/check-circle.svg +1 -0
  833. package/static/vendor/fontawesome-free/svgs/regular/check-square.svg +1 -0
  834. package/static/vendor/fontawesome-free/svgs/regular/circle.svg +1 -0
  835. package/static/vendor/fontawesome-free/svgs/regular/clipboard.svg +1 -0
  836. package/static/vendor/fontawesome-free/svgs/regular/clock.svg +1 -0
  837. package/static/vendor/fontawesome-free/svgs/regular/clone.svg +1 -0
  838. package/static/vendor/fontawesome-free/svgs/regular/closed-captioning.svg +1 -0
  839. package/static/vendor/fontawesome-free/svgs/regular/comment-alt.svg +1 -0
  840. package/static/vendor/fontawesome-free/svgs/regular/comment-dots.svg +1 -0
  841. package/static/vendor/fontawesome-free/svgs/regular/comment.svg +1 -0
  842. package/static/vendor/fontawesome-free/svgs/regular/comments.svg +1 -0
  843. package/static/vendor/fontawesome-free/svgs/regular/compass.svg +1 -0
  844. package/static/vendor/fontawesome-free/svgs/regular/copy.svg +1 -0
  845. package/static/vendor/fontawesome-free/svgs/regular/copyright.svg +1 -0
  846. package/static/vendor/fontawesome-free/svgs/regular/credit-card.svg +1 -0
  847. package/static/vendor/fontawesome-free/svgs/regular/dizzy.svg +1 -0
  848. package/static/vendor/fontawesome-free/svgs/regular/dot-circle.svg +1 -0
  849. package/static/vendor/fontawesome-free/svgs/regular/edit.svg +1 -0
  850. package/static/vendor/fontawesome-free/svgs/regular/envelope-open.svg +1 -0
  851. package/static/vendor/fontawesome-free/svgs/regular/envelope.svg +1 -0
  852. package/static/vendor/fontawesome-free/svgs/regular/eye-slash.svg +1 -0
  853. package/static/vendor/fontawesome-free/svgs/regular/eye.svg +1 -0
  854. package/static/vendor/fontawesome-free/svgs/regular/file-alt.svg +1 -0
  855. package/static/vendor/fontawesome-free/svgs/regular/file-archive.svg +1 -0
  856. package/static/vendor/fontawesome-free/svgs/regular/file-audio.svg +1 -0
  857. package/static/vendor/fontawesome-free/svgs/regular/file-code.svg +1 -0
  858. package/static/vendor/fontawesome-free/svgs/regular/file-excel.svg +1 -0
  859. package/static/vendor/fontawesome-free/svgs/regular/file-image.svg +1 -0
  860. package/static/vendor/fontawesome-free/svgs/regular/file-pdf.svg +1 -0
  861. package/static/vendor/fontawesome-free/svgs/regular/file-powerpoint.svg +1 -0
  862. package/static/vendor/fontawesome-free/svgs/regular/file-video.svg +1 -0
  863. package/static/vendor/fontawesome-free/svgs/regular/file-word.svg +1 -0
  864. package/static/vendor/fontawesome-free/svgs/regular/file.svg +1 -0
  865. package/static/vendor/fontawesome-free/svgs/regular/flag.svg +1 -0
  866. package/static/vendor/fontawesome-free/svgs/regular/flushed.svg +1 -0
  867. package/static/vendor/fontawesome-free/svgs/regular/folder-open.svg +1 -0
  868. package/static/vendor/fontawesome-free/svgs/regular/folder.svg +1 -0
  869. package/static/vendor/fontawesome-free/svgs/regular/font-awesome-logo-full.svg +1 -0
  870. package/static/vendor/fontawesome-free/svgs/regular/frown-open.svg +1 -0
  871. package/static/vendor/fontawesome-free/svgs/regular/frown.svg +1 -0
  872. package/static/vendor/fontawesome-free/svgs/regular/futbol.svg +1 -0
  873. package/static/vendor/fontawesome-free/svgs/regular/gem.svg +1 -0
  874. package/static/vendor/fontawesome-free/svgs/regular/grimace.svg +1 -0
  875. package/static/vendor/fontawesome-free/svgs/regular/grin-alt.svg +1 -0
  876. package/static/vendor/fontawesome-free/svgs/regular/grin-beam-sweat.svg +1 -0
  877. package/static/vendor/fontawesome-free/svgs/regular/grin-beam.svg +1 -0
  878. package/static/vendor/fontawesome-free/svgs/regular/grin-hearts.svg +1 -0
  879. package/static/vendor/fontawesome-free/svgs/regular/grin-squint-tears.svg +1 -0
  880. package/static/vendor/fontawesome-free/svgs/regular/grin-squint.svg +1 -0
  881. package/static/vendor/fontawesome-free/svgs/regular/grin-stars.svg +1 -0
  882. package/static/vendor/fontawesome-free/svgs/regular/grin-tears.svg +1 -0
  883. package/static/vendor/fontawesome-free/svgs/regular/grin-tongue-squint.svg +1 -0
  884. package/static/vendor/fontawesome-free/svgs/regular/grin-tongue-wink.svg +1 -0
  885. package/static/vendor/fontawesome-free/svgs/regular/grin-tongue.svg +1 -0
  886. package/static/vendor/fontawesome-free/svgs/regular/grin-wink.svg +1 -0
  887. package/static/vendor/fontawesome-free/svgs/regular/grin.svg +1 -0
  888. package/static/vendor/fontawesome-free/svgs/regular/hand-lizard.svg +1 -0
  889. package/static/vendor/fontawesome-free/svgs/regular/hand-paper.svg +1 -0
  890. package/static/vendor/fontawesome-free/svgs/regular/hand-peace.svg +1 -0
  891. package/static/vendor/fontawesome-free/svgs/regular/hand-point-down.svg +1 -0
  892. package/static/vendor/fontawesome-free/svgs/regular/hand-point-left.svg +1 -0
  893. package/static/vendor/fontawesome-free/svgs/regular/hand-point-right.svg +1 -0
  894. package/static/vendor/fontawesome-free/svgs/regular/hand-point-up.svg +1 -0
  895. package/static/vendor/fontawesome-free/svgs/regular/hand-pointer.svg +1 -0
  896. package/static/vendor/fontawesome-free/svgs/regular/hand-rock.svg +1 -0
  897. package/static/vendor/fontawesome-free/svgs/regular/hand-scissors.svg +1 -0
  898. package/static/vendor/fontawesome-free/svgs/regular/hand-spock.svg +1 -0
  899. package/static/vendor/fontawesome-free/svgs/regular/handshake.svg +1 -0
  900. package/static/vendor/fontawesome-free/svgs/regular/hdd.svg +1 -0
  901. package/static/vendor/fontawesome-free/svgs/regular/heart.svg +1 -0
  902. package/static/vendor/fontawesome-free/svgs/regular/hospital.svg +1 -0
  903. package/static/vendor/fontawesome-free/svgs/regular/hourglass.svg +1 -0
  904. package/static/vendor/fontawesome-free/svgs/regular/id-badge.svg +1 -0
  905. package/static/vendor/fontawesome-free/svgs/regular/id-card.svg +1 -0
  906. package/static/vendor/fontawesome-free/svgs/regular/image.svg +1 -0
  907. package/static/vendor/fontawesome-free/svgs/regular/images.svg +1 -0
  908. package/static/vendor/fontawesome-free/svgs/regular/keyboard.svg +1 -0
  909. package/static/vendor/fontawesome-free/svgs/regular/kiss-beam.svg +1 -0
  910. package/static/vendor/fontawesome-free/svgs/regular/kiss-wink-heart.svg +1 -0
  911. package/static/vendor/fontawesome-free/svgs/regular/kiss.svg +1 -0
  912. package/static/vendor/fontawesome-free/svgs/regular/laugh-beam.svg +1 -0
  913. package/static/vendor/fontawesome-free/svgs/regular/laugh-squint.svg +1 -0
  914. package/static/vendor/fontawesome-free/svgs/regular/laugh-wink.svg +1 -0
  915. package/static/vendor/fontawesome-free/svgs/regular/laugh.svg +1 -0
  916. package/static/vendor/fontawesome-free/svgs/regular/lemon.svg +1 -0
  917. package/static/vendor/fontawesome-free/svgs/regular/life-ring.svg +1 -0
  918. package/static/vendor/fontawesome-free/svgs/regular/lightbulb.svg +1 -0
  919. package/static/vendor/fontawesome-free/svgs/regular/list-alt.svg +1 -0
  920. package/static/vendor/fontawesome-free/svgs/regular/map.svg +1 -0
  921. package/static/vendor/fontawesome-free/svgs/regular/meh-blank.svg +1 -0
  922. package/static/vendor/fontawesome-free/svgs/regular/meh-rolling-eyes.svg +1 -0
  923. package/static/vendor/fontawesome-free/svgs/regular/meh.svg +1 -0
  924. package/static/vendor/fontawesome-free/svgs/regular/minus-square.svg +1 -0
  925. package/static/vendor/fontawesome-free/svgs/regular/money-bill-alt.svg +1 -0
  926. package/static/vendor/fontawesome-free/svgs/regular/moon.svg +1 -0
  927. package/static/vendor/fontawesome-free/svgs/regular/newspaper.svg +1 -0
  928. package/static/vendor/fontawesome-free/svgs/regular/object-group.svg +1 -0
  929. package/static/vendor/fontawesome-free/svgs/regular/object-ungroup.svg +1 -0
  930. package/static/vendor/fontawesome-free/svgs/regular/paper-plane.svg +1 -0
  931. package/static/vendor/fontawesome-free/svgs/regular/pause-circle.svg +1 -0
  932. package/static/vendor/fontawesome-free/svgs/regular/play-circle.svg +1 -0
  933. package/static/vendor/fontawesome-free/svgs/regular/plus-square.svg +1 -0
  934. package/static/vendor/fontawesome-free/svgs/regular/question-circle.svg +1 -0
  935. package/static/vendor/fontawesome-free/svgs/regular/registered.svg +1 -0
  936. package/static/vendor/fontawesome-free/svgs/regular/sad-cry.svg +1 -0
  937. package/static/vendor/fontawesome-free/svgs/regular/sad-tear.svg +1 -0
  938. package/static/vendor/fontawesome-free/svgs/regular/save.svg +1 -0
  939. package/static/vendor/fontawesome-free/svgs/regular/share-square.svg +1 -0
  940. package/static/vendor/fontawesome-free/svgs/regular/smile-beam.svg +1 -0
  941. package/static/vendor/fontawesome-free/svgs/regular/smile-wink.svg +1 -0
  942. package/static/vendor/fontawesome-free/svgs/regular/smile.svg +1 -0
  943. package/static/vendor/fontawesome-free/svgs/regular/snowflake.svg +1 -0
  944. package/static/vendor/fontawesome-free/svgs/regular/square.svg +1 -0
  945. package/static/vendor/fontawesome-free/svgs/regular/star-half.svg +1 -0
  946. package/static/vendor/fontawesome-free/svgs/regular/star.svg +1 -0
  947. package/static/vendor/fontawesome-free/svgs/regular/sticky-note.svg +1 -0
  948. package/static/vendor/fontawesome-free/svgs/regular/stop-circle.svg +1 -0
  949. package/static/vendor/fontawesome-free/svgs/regular/sun.svg +1 -0
  950. package/static/vendor/fontawesome-free/svgs/regular/surprise.svg +1 -0
  951. package/static/vendor/fontawesome-free/svgs/regular/thumbs-down.svg +1 -0
  952. package/static/vendor/fontawesome-free/svgs/regular/thumbs-up.svg +1 -0
  953. package/static/vendor/fontawesome-free/svgs/regular/times-circle.svg +1 -0
  954. package/static/vendor/fontawesome-free/svgs/regular/tired.svg +1 -0
  955. package/static/vendor/fontawesome-free/svgs/regular/trash-alt.svg +1 -0
  956. package/static/vendor/fontawesome-free/svgs/regular/user-circle.svg +1 -0
  957. package/static/vendor/fontawesome-free/svgs/regular/user.svg +1 -0
  958. package/static/vendor/fontawesome-free/svgs/regular/window-close.svg +1 -0
  959. package/static/vendor/fontawesome-free/svgs/regular/window-maximize.svg +1 -0
  960. package/static/vendor/fontawesome-free/svgs/regular/window-minimize.svg +1 -0
  961. package/static/vendor/fontawesome-free/svgs/regular/window-restore.svg +1 -0
  962. package/static/vendor/fontawesome-free/svgs/solid/ad.svg +1 -0
  963. package/static/vendor/fontawesome-free/svgs/solid/address-book.svg +1 -0
  964. package/static/vendor/fontawesome-free/svgs/solid/address-card.svg +1 -0
  965. package/static/vendor/fontawesome-free/svgs/solid/adjust.svg +1 -0
  966. package/static/vendor/fontawesome-free/svgs/solid/air-freshener.svg +1 -0
  967. package/static/vendor/fontawesome-free/svgs/solid/align-center.svg +1 -0
  968. package/static/vendor/fontawesome-free/svgs/solid/align-justify.svg +1 -0
  969. package/static/vendor/fontawesome-free/svgs/solid/align-left.svg +1 -0
  970. package/static/vendor/fontawesome-free/svgs/solid/align-right.svg +1 -0
  971. package/static/vendor/fontawesome-free/svgs/solid/allergies.svg +1 -0
  972. package/static/vendor/fontawesome-free/svgs/solid/ambulance.svg +1 -0
  973. package/static/vendor/fontawesome-free/svgs/solid/american-sign-language-interpreting.svg +1 -0
  974. package/static/vendor/fontawesome-free/svgs/solid/anchor.svg +1 -0
  975. package/static/vendor/fontawesome-free/svgs/solid/angle-double-down.svg +1 -0
  976. package/static/vendor/fontawesome-free/svgs/solid/angle-double-left.svg +1 -0
  977. package/static/vendor/fontawesome-free/svgs/solid/angle-double-right.svg +1 -0
  978. package/static/vendor/fontawesome-free/svgs/solid/angle-double-up.svg +1 -0
  979. package/static/vendor/fontawesome-free/svgs/solid/angle-down.svg +1 -0
  980. package/static/vendor/fontawesome-free/svgs/solid/angle-left.svg +1 -0
  981. package/static/vendor/fontawesome-free/svgs/solid/angle-right.svg +1 -0
  982. package/static/vendor/fontawesome-free/svgs/solid/angle-up.svg +1 -0
  983. package/static/vendor/fontawesome-free/svgs/solid/angry.svg +1 -0
  984. package/static/vendor/fontawesome-free/svgs/solid/ankh.svg +1 -0
  985. package/static/vendor/fontawesome-free/svgs/solid/apple-alt.svg +1 -0
  986. package/static/vendor/fontawesome-free/svgs/solid/archive.svg +1 -0
  987. package/static/vendor/fontawesome-free/svgs/solid/archway.svg +1 -0
  988. package/static/vendor/fontawesome-free/svgs/solid/arrow-alt-circle-down.svg +1 -0
  989. package/static/vendor/fontawesome-free/svgs/solid/arrow-alt-circle-left.svg +1 -0
  990. package/static/vendor/fontawesome-free/svgs/solid/arrow-alt-circle-right.svg +1 -0
  991. package/static/vendor/fontawesome-free/svgs/solid/arrow-alt-circle-up.svg +1 -0
  992. package/static/vendor/fontawesome-free/svgs/solid/arrow-circle-down.svg +1 -0
  993. package/static/vendor/fontawesome-free/svgs/solid/arrow-circle-left.svg +1 -0
  994. package/static/vendor/fontawesome-free/svgs/solid/arrow-circle-right.svg +1 -0
  995. package/static/vendor/fontawesome-free/svgs/solid/arrow-circle-up.svg +1 -0
  996. package/static/vendor/fontawesome-free/svgs/solid/arrow-down.svg +1 -0
  997. package/static/vendor/fontawesome-free/svgs/solid/arrow-left.svg +1 -0
  998. package/static/vendor/fontawesome-free/svgs/solid/arrow-right.svg +1 -0
  999. package/static/vendor/fontawesome-free/svgs/solid/arrow-up.svg +1 -0
  1000. package/static/vendor/fontawesome-free/svgs/solid/arrows-alt-h.svg +1 -0
  1001. package/static/vendor/fontawesome-free/svgs/solid/arrows-alt-v.svg +1 -0
  1002. package/static/vendor/fontawesome-free/svgs/solid/arrows-alt.svg +1 -0
  1003. package/static/vendor/fontawesome-free/svgs/solid/assistive-listening-systems.svg +1 -0
  1004. package/static/vendor/fontawesome-free/svgs/solid/asterisk.svg +1 -0
  1005. package/static/vendor/fontawesome-free/svgs/solid/at.svg +1 -0
  1006. package/static/vendor/fontawesome-free/svgs/solid/atlas.svg +1 -0
  1007. package/static/vendor/fontawesome-free/svgs/solid/atom.svg +1 -0
  1008. package/static/vendor/fontawesome-free/svgs/solid/audio-description.svg +1 -0
  1009. package/static/vendor/fontawesome-free/svgs/solid/award.svg +1 -0
  1010. package/static/vendor/fontawesome-free/svgs/solid/baby-carriage.svg +1 -0
  1011. package/static/vendor/fontawesome-free/svgs/solid/baby.svg +1 -0
  1012. package/static/vendor/fontawesome-free/svgs/solid/backspace.svg +1 -0
  1013. package/static/vendor/fontawesome-free/svgs/solid/backward.svg +1 -0
  1014. package/static/vendor/fontawesome-free/svgs/solid/bacon.svg +1 -0
  1015. package/static/vendor/fontawesome-free/svgs/solid/bacteria.svg +1 -0
  1016. package/static/vendor/fontawesome-free/svgs/solid/bacterium.svg +1 -0
  1017. package/static/vendor/fontawesome-free/svgs/solid/bahai.svg +1 -0
  1018. package/static/vendor/fontawesome-free/svgs/solid/balance-scale-left.svg +1 -0
  1019. package/static/vendor/fontawesome-free/svgs/solid/balance-scale-right.svg +1 -0
  1020. package/static/vendor/fontawesome-free/svgs/solid/balance-scale.svg +1 -0
  1021. package/static/vendor/fontawesome-free/svgs/solid/ban.svg +1 -0
  1022. package/static/vendor/fontawesome-free/svgs/solid/band-aid.svg +1 -0
  1023. package/static/vendor/fontawesome-free/svgs/solid/barcode.svg +1 -0
  1024. package/static/vendor/fontawesome-free/svgs/solid/bars.svg +1 -0
  1025. package/static/vendor/fontawesome-free/svgs/solid/baseball-ball.svg +1 -0
  1026. package/static/vendor/fontawesome-free/svgs/solid/basketball-ball.svg +1 -0
  1027. package/static/vendor/fontawesome-free/svgs/solid/bath.svg +1 -0
  1028. package/static/vendor/fontawesome-free/svgs/solid/battery-empty.svg +1 -0
  1029. package/static/vendor/fontawesome-free/svgs/solid/battery-full.svg +1 -0
  1030. package/static/vendor/fontawesome-free/svgs/solid/battery-half.svg +1 -0
  1031. package/static/vendor/fontawesome-free/svgs/solid/battery-quarter.svg +1 -0
  1032. package/static/vendor/fontawesome-free/svgs/solid/battery-three-quarters.svg +1 -0
  1033. package/static/vendor/fontawesome-free/svgs/solid/bed.svg +1 -0
  1034. package/static/vendor/fontawesome-free/svgs/solid/beer.svg +1 -0
  1035. package/static/vendor/fontawesome-free/svgs/solid/bell-slash.svg +1 -0
  1036. package/static/vendor/fontawesome-free/svgs/solid/bell.svg +1 -0
  1037. package/static/vendor/fontawesome-free/svgs/solid/bezier-curve.svg +1 -0
  1038. package/static/vendor/fontawesome-free/svgs/solid/bible.svg +1 -0
  1039. package/static/vendor/fontawesome-free/svgs/solid/bicycle.svg +1 -0
  1040. package/static/vendor/fontawesome-free/svgs/solid/biking.svg +1 -0
  1041. package/static/vendor/fontawesome-free/svgs/solid/binoculars.svg +1 -0
  1042. package/static/vendor/fontawesome-free/svgs/solid/biohazard.svg +1 -0
  1043. package/static/vendor/fontawesome-free/svgs/solid/birthday-cake.svg +1 -0
  1044. package/static/vendor/fontawesome-free/svgs/solid/blender-phone.svg +1 -0
  1045. package/static/vendor/fontawesome-free/svgs/solid/blender.svg +1 -0
  1046. package/static/vendor/fontawesome-free/svgs/solid/blind.svg +1 -0
  1047. package/static/vendor/fontawesome-free/svgs/solid/blog.svg +1 -0
  1048. package/static/vendor/fontawesome-free/svgs/solid/bold.svg +1 -0
  1049. package/static/vendor/fontawesome-free/svgs/solid/bolt.svg +1 -0
  1050. package/static/vendor/fontawesome-free/svgs/solid/bomb.svg +1 -0
  1051. package/static/vendor/fontawesome-free/svgs/solid/bone.svg +1 -0
  1052. package/static/vendor/fontawesome-free/svgs/solid/bong.svg +1 -0
  1053. package/static/vendor/fontawesome-free/svgs/solid/book-dead.svg +1 -0
  1054. package/static/vendor/fontawesome-free/svgs/solid/book-medical.svg +1 -0
  1055. package/static/vendor/fontawesome-free/svgs/solid/book-open.svg +1 -0
  1056. package/static/vendor/fontawesome-free/svgs/solid/book-reader.svg +1 -0
  1057. package/static/vendor/fontawesome-free/svgs/solid/book.svg +1 -0
  1058. package/static/vendor/fontawesome-free/svgs/solid/bookmark.svg +1 -0
  1059. package/static/vendor/fontawesome-free/svgs/solid/border-all.svg +1 -0
  1060. package/static/vendor/fontawesome-free/svgs/solid/border-none.svg +1 -0
  1061. package/static/vendor/fontawesome-free/svgs/solid/border-style.svg +1 -0
  1062. package/static/vendor/fontawesome-free/svgs/solid/bowling-ball.svg +1 -0
  1063. package/static/vendor/fontawesome-free/svgs/solid/box-open.svg +1 -0
  1064. package/static/vendor/fontawesome-free/svgs/solid/box-tissue.svg +1 -0
  1065. package/static/vendor/fontawesome-free/svgs/solid/box.svg +1 -0
  1066. package/static/vendor/fontawesome-free/svgs/solid/boxes.svg +1 -0
  1067. package/static/vendor/fontawesome-free/svgs/solid/braille.svg +1 -0
  1068. package/static/vendor/fontawesome-free/svgs/solid/brain.svg +1 -0
  1069. package/static/vendor/fontawesome-free/svgs/solid/bread-slice.svg +1 -0
  1070. package/static/vendor/fontawesome-free/svgs/solid/briefcase-medical.svg +1 -0
  1071. package/static/vendor/fontawesome-free/svgs/solid/briefcase.svg +1 -0
  1072. package/static/vendor/fontawesome-free/svgs/solid/broadcast-tower.svg +1 -0
  1073. package/static/vendor/fontawesome-free/svgs/solid/broom.svg +1 -0
  1074. package/static/vendor/fontawesome-free/svgs/solid/brush.svg +1 -0
  1075. package/static/vendor/fontawesome-free/svgs/solid/bug.svg +1 -0
  1076. package/static/vendor/fontawesome-free/svgs/solid/building.svg +1 -0
  1077. package/static/vendor/fontawesome-free/svgs/solid/bullhorn.svg +1 -0
  1078. package/static/vendor/fontawesome-free/svgs/solid/bullseye.svg +1 -0
  1079. package/static/vendor/fontawesome-free/svgs/solid/burn.svg +1 -0
  1080. package/static/vendor/fontawesome-free/svgs/solid/bus-alt.svg +1 -0
  1081. package/static/vendor/fontawesome-free/svgs/solid/bus.svg +1 -0
  1082. package/static/vendor/fontawesome-free/svgs/solid/business-time.svg +1 -0
  1083. package/static/vendor/fontawesome-free/svgs/solid/calculator.svg +1 -0
  1084. package/static/vendor/fontawesome-free/svgs/solid/calendar-alt.svg +1 -0
  1085. package/static/vendor/fontawesome-free/svgs/solid/calendar-check.svg +1 -0
  1086. package/static/vendor/fontawesome-free/svgs/solid/calendar-day.svg +1 -0
  1087. package/static/vendor/fontawesome-free/svgs/solid/calendar-minus.svg +1 -0
  1088. package/static/vendor/fontawesome-free/svgs/solid/calendar-plus.svg +1 -0
  1089. package/static/vendor/fontawesome-free/svgs/solid/calendar-times.svg +1 -0
  1090. package/static/vendor/fontawesome-free/svgs/solid/calendar-week.svg +1 -0
  1091. package/static/vendor/fontawesome-free/svgs/solid/calendar.svg +1 -0
  1092. package/static/vendor/fontawesome-free/svgs/solid/camera-retro.svg +1 -0
  1093. package/static/vendor/fontawesome-free/svgs/solid/camera.svg +1 -0
  1094. package/static/vendor/fontawesome-free/svgs/solid/campground.svg +1 -0
  1095. package/static/vendor/fontawesome-free/svgs/solid/candy-cane.svg +1 -0
  1096. package/static/vendor/fontawesome-free/svgs/solid/cannabis.svg +1 -0
  1097. package/static/vendor/fontawesome-free/svgs/solid/capsules.svg +1 -0
  1098. package/static/vendor/fontawesome-free/svgs/solid/car-alt.svg +1 -0
  1099. package/static/vendor/fontawesome-free/svgs/solid/car-battery.svg +1 -0
  1100. package/static/vendor/fontawesome-free/svgs/solid/car-crash.svg +1 -0
  1101. package/static/vendor/fontawesome-free/svgs/solid/car-side.svg +1 -0
  1102. package/static/vendor/fontawesome-free/svgs/solid/car.svg +1 -0
  1103. package/static/vendor/fontawesome-free/svgs/solid/caravan.svg +1 -0
  1104. package/static/vendor/fontawesome-free/svgs/solid/caret-down.svg +1 -0
  1105. package/static/vendor/fontawesome-free/svgs/solid/caret-left.svg +1 -0
  1106. package/static/vendor/fontawesome-free/svgs/solid/caret-right.svg +1 -0
  1107. package/static/vendor/fontawesome-free/svgs/solid/caret-square-down.svg +1 -0
  1108. package/static/vendor/fontawesome-free/svgs/solid/caret-square-left.svg +1 -0
  1109. package/static/vendor/fontawesome-free/svgs/solid/caret-square-right.svg +1 -0
  1110. package/static/vendor/fontawesome-free/svgs/solid/caret-square-up.svg +1 -0
  1111. package/static/vendor/fontawesome-free/svgs/solid/caret-up.svg +1 -0
  1112. package/static/vendor/fontawesome-free/svgs/solid/carrot.svg +1 -0
  1113. package/static/vendor/fontawesome-free/svgs/solid/cart-arrow-down.svg +1 -0
  1114. package/static/vendor/fontawesome-free/svgs/solid/cart-plus.svg +1 -0
  1115. package/static/vendor/fontawesome-free/svgs/solid/cash-register.svg +1 -0
  1116. package/static/vendor/fontawesome-free/svgs/solid/cat.svg +1 -0
  1117. package/static/vendor/fontawesome-free/svgs/solid/certificate.svg +1 -0
  1118. package/static/vendor/fontawesome-free/svgs/solid/chair.svg +1 -0
  1119. package/static/vendor/fontawesome-free/svgs/solid/chalkboard-teacher.svg +1 -0
  1120. package/static/vendor/fontawesome-free/svgs/solid/chalkboard.svg +1 -0
  1121. package/static/vendor/fontawesome-free/svgs/solid/charging-station.svg +1 -0
  1122. package/static/vendor/fontawesome-free/svgs/solid/chart-area.svg +1 -0
  1123. package/static/vendor/fontawesome-free/svgs/solid/chart-bar.svg +1 -0
  1124. package/static/vendor/fontawesome-free/svgs/solid/chart-line.svg +1 -0
  1125. package/static/vendor/fontawesome-free/svgs/solid/chart-pie.svg +1 -0
  1126. package/static/vendor/fontawesome-free/svgs/solid/check-circle.svg +1 -0
  1127. package/static/vendor/fontawesome-free/svgs/solid/check-double.svg +1 -0
  1128. package/static/vendor/fontawesome-free/svgs/solid/check-square.svg +1 -0
  1129. package/static/vendor/fontawesome-free/svgs/solid/check.svg +1 -0
  1130. package/static/vendor/fontawesome-free/svgs/solid/cheese.svg +1 -0
  1131. package/static/vendor/fontawesome-free/svgs/solid/chess-bishop.svg +1 -0
  1132. package/static/vendor/fontawesome-free/svgs/solid/chess-board.svg +1 -0
  1133. package/static/vendor/fontawesome-free/svgs/solid/chess-king.svg +1 -0
  1134. package/static/vendor/fontawesome-free/svgs/solid/chess-knight.svg +1 -0
  1135. package/static/vendor/fontawesome-free/svgs/solid/chess-pawn.svg +1 -0
  1136. package/static/vendor/fontawesome-free/svgs/solid/chess-queen.svg +1 -0
  1137. package/static/vendor/fontawesome-free/svgs/solid/chess-rook.svg +1 -0
  1138. package/static/vendor/fontawesome-free/svgs/solid/chess.svg +1 -0
  1139. package/static/vendor/fontawesome-free/svgs/solid/chevron-circle-down.svg +1 -0
  1140. package/static/vendor/fontawesome-free/svgs/solid/chevron-circle-left.svg +1 -0
  1141. package/static/vendor/fontawesome-free/svgs/solid/chevron-circle-right.svg +1 -0
  1142. package/static/vendor/fontawesome-free/svgs/solid/chevron-circle-up.svg +1 -0
  1143. package/static/vendor/fontawesome-free/svgs/solid/chevron-down.svg +1 -0
  1144. package/static/vendor/fontawesome-free/svgs/solid/chevron-left.svg +1 -0
  1145. package/static/vendor/fontawesome-free/svgs/solid/chevron-right.svg +1 -0
  1146. package/static/vendor/fontawesome-free/svgs/solid/chevron-up.svg +1 -0
  1147. package/static/vendor/fontawesome-free/svgs/solid/child.svg +1 -0
  1148. package/static/vendor/fontawesome-free/svgs/solid/church.svg +1 -0
  1149. package/static/vendor/fontawesome-free/svgs/solid/circle-notch.svg +1 -0
  1150. package/static/vendor/fontawesome-free/svgs/solid/circle.svg +1 -0
  1151. package/static/vendor/fontawesome-free/svgs/solid/city.svg +1 -0
  1152. package/static/vendor/fontawesome-free/svgs/solid/clinic-medical.svg +1 -0
  1153. package/static/vendor/fontawesome-free/svgs/solid/clipboard-check.svg +1 -0
  1154. package/static/vendor/fontawesome-free/svgs/solid/clipboard-list.svg +1 -0
  1155. package/static/vendor/fontawesome-free/svgs/solid/clipboard.svg +1 -0
  1156. package/static/vendor/fontawesome-free/svgs/solid/clock.svg +1 -0
  1157. package/static/vendor/fontawesome-free/svgs/solid/clone.svg +1 -0
  1158. package/static/vendor/fontawesome-free/svgs/solid/closed-captioning.svg +1 -0
  1159. package/static/vendor/fontawesome-free/svgs/solid/cloud-download-alt.svg +1 -0
  1160. package/static/vendor/fontawesome-free/svgs/solid/cloud-meatball.svg +1 -0
  1161. package/static/vendor/fontawesome-free/svgs/solid/cloud-moon-rain.svg +1 -0
  1162. package/static/vendor/fontawesome-free/svgs/solid/cloud-moon.svg +1 -0
  1163. package/static/vendor/fontawesome-free/svgs/solid/cloud-rain.svg +1 -0
  1164. package/static/vendor/fontawesome-free/svgs/solid/cloud-showers-heavy.svg +1 -0
  1165. package/static/vendor/fontawesome-free/svgs/solid/cloud-sun-rain.svg +1 -0
  1166. package/static/vendor/fontawesome-free/svgs/solid/cloud-sun.svg +1 -0
  1167. package/static/vendor/fontawesome-free/svgs/solid/cloud-upload-alt.svg +1 -0
  1168. package/static/vendor/fontawesome-free/svgs/solid/cloud.svg +1 -0
  1169. package/static/vendor/fontawesome-free/svgs/solid/cocktail.svg +1 -0
  1170. package/static/vendor/fontawesome-free/svgs/solid/code-branch.svg +1 -0
  1171. package/static/vendor/fontawesome-free/svgs/solid/code.svg +1 -0
  1172. package/static/vendor/fontawesome-free/svgs/solid/coffee.svg +1 -0
  1173. package/static/vendor/fontawesome-free/svgs/solid/cog.svg +1 -0
  1174. package/static/vendor/fontawesome-free/svgs/solid/cogs.svg +1 -0
  1175. package/static/vendor/fontawesome-free/svgs/solid/coins.svg +1 -0
  1176. package/static/vendor/fontawesome-free/svgs/solid/columns.svg +1 -0
  1177. package/static/vendor/fontawesome-free/svgs/solid/comment-alt.svg +1 -0
  1178. package/static/vendor/fontawesome-free/svgs/solid/comment-dollar.svg +1 -0
  1179. package/static/vendor/fontawesome-free/svgs/solid/comment-dots.svg +1 -0
  1180. package/static/vendor/fontawesome-free/svgs/solid/comment-medical.svg +1 -0
  1181. package/static/vendor/fontawesome-free/svgs/solid/comment-slash.svg +1 -0
  1182. package/static/vendor/fontawesome-free/svgs/solid/comment.svg +1 -0
  1183. package/static/vendor/fontawesome-free/svgs/solid/comments-dollar.svg +1 -0
  1184. package/static/vendor/fontawesome-free/svgs/solid/comments.svg +1 -0
  1185. package/static/vendor/fontawesome-free/svgs/solid/compact-disc.svg +1 -0
  1186. package/static/vendor/fontawesome-free/svgs/solid/compass.svg +1 -0
  1187. package/static/vendor/fontawesome-free/svgs/solid/compress-alt.svg +1 -0
  1188. package/static/vendor/fontawesome-free/svgs/solid/compress-arrows-alt.svg +1 -0
  1189. package/static/vendor/fontawesome-free/svgs/solid/compress.svg +1 -0
  1190. package/static/vendor/fontawesome-free/svgs/solid/concierge-bell.svg +1 -0
  1191. package/static/vendor/fontawesome-free/svgs/solid/cookie-bite.svg +1 -0
  1192. package/static/vendor/fontawesome-free/svgs/solid/cookie.svg +1 -0
  1193. package/static/vendor/fontawesome-free/svgs/solid/copy.svg +1 -0
  1194. package/static/vendor/fontawesome-free/svgs/solid/copyright.svg +1 -0
  1195. package/static/vendor/fontawesome-free/svgs/solid/couch.svg +1 -0
  1196. package/static/vendor/fontawesome-free/svgs/solid/credit-card.svg +1 -0
  1197. package/static/vendor/fontawesome-free/svgs/solid/crop-alt.svg +1 -0
  1198. package/static/vendor/fontawesome-free/svgs/solid/crop.svg +1 -0
  1199. package/static/vendor/fontawesome-free/svgs/solid/cross.svg +1 -0
  1200. package/static/vendor/fontawesome-free/svgs/solid/crosshairs.svg +1 -0
  1201. package/static/vendor/fontawesome-free/svgs/solid/crow.svg +1 -0
  1202. package/static/vendor/fontawesome-free/svgs/solid/crown.svg +1 -0
  1203. package/static/vendor/fontawesome-free/svgs/solid/crutch.svg +1 -0
  1204. package/static/vendor/fontawesome-free/svgs/solid/cube.svg +1 -0
  1205. package/static/vendor/fontawesome-free/svgs/solid/cubes.svg +1 -0
  1206. package/static/vendor/fontawesome-free/svgs/solid/cut.svg +1 -0
  1207. package/static/vendor/fontawesome-free/svgs/solid/database.svg +1 -0
  1208. package/static/vendor/fontawesome-free/svgs/solid/deaf.svg +1 -0
  1209. package/static/vendor/fontawesome-free/svgs/solid/democrat.svg +1 -0
  1210. package/static/vendor/fontawesome-free/svgs/solid/desktop.svg +1 -0
  1211. package/static/vendor/fontawesome-free/svgs/solid/dharmachakra.svg +1 -0
  1212. package/static/vendor/fontawesome-free/svgs/solid/diagnoses.svg +1 -0
  1213. package/static/vendor/fontawesome-free/svgs/solid/dice-d20.svg +1 -0
  1214. package/static/vendor/fontawesome-free/svgs/solid/dice-d6.svg +1 -0
  1215. package/static/vendor/fontawesome-free/svgs/solid/dice-five.svg +1 -0
  1216. package/static/vendor/fontawesome-free/svgs/solid/dice-four.svg +1 -0
  1217. package/static/vendor/fontawesome-free/svgs/solid/dice-one.svg +1 -0
  1218. package/static/vendor/fontawesome-free/svgs/solid/dice-six.svg +1 -0
  1219. package/static/vendor/fontawesome-free/svgs/solid/dice-three.svg +1 -0
  1220. package/static/vendor/fontawesome-free/svgs/solid/dice-two.svg +1 -0
  1221. package/static/vendor/fontawesome-free/svgs/solid/dice.svg +1 -0
  1222. package/static/vendor/fontawesome-free/svgs/solid/digital-tachograph.svg +1 -0
  1223. package/static/vendor/fontawesome-free/svgs/solid/directions.svg +1 -0
  1224. package/static/vendor/fontawesome-free/svgs/solid/disease.svg +1 -0
  1225. package/static/vendor/fontawesome-free/svgs/solid/divide.svg +1 -0
  1226. package/static/vendor/fontawesome-free/svgs/solid/dizzy.svg +1 -0
  1227. package/static/vendor/fontawesome-free/svgs/solid/dna.svg +1 -0
  1228. package/static/vendor/fontawesome-free/svgs/solid/dog.svg +1 -0
  1229. package/static/vendor/fontawesome-free/svgs/solid/dollar-sign.svg +1 -0
  1230. package/static/vendor/fontawesome-free/svgs/solid/dolly-flatbed.svg +1 -0
  1231. package/static/vendor/fontawesome-free/svgs/solid/dolly.svg +1 -0
  1232. package/static/vendor/fontawesome-free/svgs/solid/donate.svg +1 -0
  1233. package/static/vendor/fontawesome-free/svgs/solid/door-closed.svg +1 -0
  1234. package/static/vendor/fontawesome-free/svgs/solid/door-open.svg +1 -0
  1235. package/static/vendor/fontawesome-free/svgs/solid/dot-circle.svg +1 -0
  1236. package/static/vendor/fontawesome-free/svgs/solid/dove.svg +1 -0
  1237. package/static/vendor/fontawesome-free/svgs/solid/download.svg +1 -0
  1238. package/static/vendor/fontawesome-free/svgs/solid/drafting-compass.svg +1 -0
  1239. package/static/vendor/fontawesome-free/svgs/solid/dragon.svg +1 -0
  1240. package/static/vendor/fontawesome-free/svgs/solid/draw-polygon.svg +1 -0
  1241. package/static/vendor/fontawesome-free/svgs/solid/drum-steelpan.svg +1 -0
  1242. package/static/vendor/fontawesome-free/svgs/solid/drum.svg +1 -0
  1243. package/static/vendor/fontawesome-free/svgs/solid/drumstick-bite.svg +1 -0
  1244. package/static/vendor/fontawesome-free/svgs/solid/dumbbell.svg +1 -0
  1245. package/static/vendor/fontawesome-free/svgs/solid/dumpster-fire.svg +1 -0
  1246. package/static/vendor/fontawesome-free/svgs/solid/dumpster.svg +1 -0
  1247. package/static/vendor/fontawesome-free/svgs/solid/dungeon.svg +1 -0
  1248. package/static/vendor/fontawesome-free/svgs/solid/edit.svg +1 -0
  1249. package/static/vendor/fontawesome-free/svgs/solid/egg.svg +1 -0
  1250. package/static/vendor/fontawesome-free/svgs/solid/eject.svg +1 -0
  1251. package/static/vendor/fontawesome-free/svgs/solid/ellipsis-h.svg +1 -0
  1252. package/static/vendor/fontawesome-free/svgs/solid/ellipsis-v.svg +1 -0
  1253. package/static/vendor/fontawesome-free/svgs/solid/envelope-open-text.svg +1 -0
  1254. package/static/vendor/fontawesome-free/svgs/solid/envelope-open.svg +1 -0
  1255. package/static/vendor/fontawesome-free/svgs/solid/envelope-square.svg +1 -0
  1256. package/static/vendor/fontawesome-free/svgs/solid/envelope.svg +1 -0
  1257. package/static/vendor/fontawesome-free/svgs/solid/equals.svg +1 -0
  1258. package/static/vendor/fontawesome-free/svgs/solid/eraser.svg +1 -0
  1259. package/static/vendor/fontawesome-free/svgs/solid/ethernet.svg +1 -0
  1260. package/static/vendor/fontawesome-free/svgs/solid/euro-sign.svg +1 -0
  1261. package/static/vendor/fontawesome-free/svgs/solid/exchange-alt.svg +1 -0
  1262. package/static/vendor/fontawesome-free/svgs/solid/exclamation-circle.svg +1 -0
  1263. package/static/vendor/fontawesome-free/svgs/solid/exclamation-triangle.svg +1 -0
  1264. package/static/vendor/fontawesome-free/svgs/solid/exclamation.svg +1 -0
  1265. package/static/vendor/fontawesome-free/svgs/solid/expand-alt.svg +1 -0
  1266. package/static/vendor/fontawesome-free/svgs/solid/expand-arrows-alt.svg +1 -0
  1267. package/static/vendor/fontawesome-free/svgs/solid/expand.svg +1 -0
  1268. package/static/vendor/fontawesome-free/svgs/solid/external-link-alt.svg +1 -0
  1269. package/static/vendor/fontawesome-free/svgs/solid/external-link-square-alt.svg +1 -0
  1270. package/static/vendor/fontawesome-free/svgs/solid/eye-dropper.svg +1 -0
  1271. package/static/vendor/fontawesome-free/svgs/solid/eye-slash.svg +1 -0
  1272. package/static/vendor/fontawesome-free/svgs/solid/eye.svg +1 -0
  1273. package/static/vendor/fontawesome-free/svgs/solid/fan.svg +1 -0
  1274. package/static/vendor/fontawesome-free/svgs/solid/fast-backward.svg +1 -0
  1275. package/static/vendor/fontawesome-free/svgs/solid/fast-forward.svg +1 -0
  1276. package/static/vendor/fontawesome-free/svgs/solid/faucet.svg +1 -0
  1277. package/static/vendor/fontawesome-free/svgs/solid/fax.svg +1 -0
  1278. package/static/vendor/fontawesome-free/svgs/solid/feather-alt.svg +1 -0
  1279. package/static/vendor/fontawesome-free/svgs/solid/feather.svg +1 -0
  1280. package/static/vendor/fontawesome-free/svgs/solid/female.svg +1 -0
  1281. package/static/vendor/fontawesome-free/svgs/solid/fighter-jet.svg +1 -0
  1282. package/static/vendor/fontawesome-free/svgs/solid/file-alt.svg +1 -0
  1283. package/static/vendor/fontawesome-free/svgs/solid/file-archive.svg +1 -0
  1284. package/static/vendor/fontawesome-free/svgs/solid/file-audio.svg +1 -0
  1285. package/static/vendor/fontawesome-free/svgs/solid/file-code.svg +1 -0
  1286. package/static/vendor/fontawesome-free/svgs/solid/file-contract.svg +1 -0
  1287. package/static/vendor/fontawesome-free/svgs/solid/file-csv.svg +1 -0
  1288. package/static/vendor/fontawesome-free/svgs/solid/file-download.svg +1 -0
  1289. package/static/vendor/fontawesome-free/svgs/solid/file-excel.svg +1 -0
  1290. package/static/vendor/fontawesome-free/svgs/solid/file-export.svg +1 -0
  1291. package/static/vendor/fontawesome-free/svgs/solid/file-image.svg +1 -0
  1292. package/static/vendor/fontawesome-free/svgs/solid/file-import.svg +1 -0
  1293. package/static/vendor/fontawesome-free/svgs/solid/file-invoice-dollar.svg +1 -0
  1294. package/static/vendor/fontawesome-free/svgs/solid/file-invoice.svg +1 -0
  1295. package/static/vendor/fontawesome-free/svgs/solid/file-medical-alt.svg +1 -0
  1296. package/static/vendor/fontawesome-free/svgs/solid/file-medical.svg +1 -0
  1297. package/static/vendor/fontawesome-free/svgs/solid/file-pdf.svg +1 -0
  1298. package/static/vendor/fontawesome-free/svgs/solid/file-powerpoint.svg +1 -0
  1299. package/static/vendor/fontawesome-free/svgs/solid/file-prescription.svg +1 -0
  1300. package/static/vendor/fontawesome-free/svgs/solid/file-signature.svg +1 -0
  1301. package/static/vendor/fontawesome-free/svgs/solid/file-upload.svg +1 -0
  1302. package/static/vendor/fontawesome-free/svgs/solid/file-video.svg +1 -0
  1303. package/static/vendor/fontawesome-free/svgs/solid/file-word.svg +1 -0
  1304. package/static/vendor/fontawesome-free/svgs/solid/file.svg +1 -0
  1305. package/static/vendor/fontawesome-free/svgs/solid/fill-drip.svg +1 -0
  1306. package/static/vendor/fontawesome-free/svgs/solid/fill.svg +1 -0
  1307. package/static/vendor/fontawesome-free/svgs/solid/film.svg +1 -0
  1308. package/static/vendor/fontawesome-free/svgs/solid/filter.svg +1 -0
  1309. package/static/vendor/fontawesome-free/svgs/solid/fingerprint.svg +1 -0
  1310. package/static/vendor/fontawesome-free/svgs/solid/fire-alt.svg +1 -0
  1311. package/static/vendor/fontawesome-free/svgs/solid/fire-extinguisher.svg +1 -0
  1312. package/static/vendor/fontawesome-free/svgs/solid/fire.svg +1 -0
  1313. package/static/vendor/fontawesome-free/svgs/solid/first-aid.svg +1 -0
  1314. package/static/vendor/fontawesome-free/svgs/solid/fish.svg +1 -0
  1315. package/static/vendor/fontawesome-free/svgs/solid/fist-raised.svg +1 -0
  1316. package/static/vendor/fontawesome-free/svgs/solid/flag-checkered.svg +1 -0
  1317. package/static/vendor/fontawesome-free/svgs/solid/flag-usa.svg +1 -0
  1318. package/static/vendor/fontawesome-free/svgs/solid/flag.svg +1 -0
  1319. package/static/vendor/fontawesome-free/svgs/solid/flask.svg +1 -0
  1320. package/static/vendor/fontawesome-free/svgs/solid/flushed.svg +1 -0
  1321. package/static/vendor/fontawesome-free/svgs/solid/folder-minus.svg +1 -0
  1322. package/static/vendor/fontawesome-free/svgs/solid/folder-open.svg +1 -0
  1323. package/static/vendor/fontawesome-free/svgs/solid/folder-plus.svg +1 -0
  1324. package/static/vendor/fontawesome-free/svgs/solid/folder.svg +1 -0
  1325. package/static/vendor/fontawesome-free/svgs/solid/font-awesome-logo-full.svg +1 -0
  1326. package/static/vendor/fontawesome-free/svgs/solid/font.svg +1 -0
  1327. package/static/vendor/fontawesome-free/svgs/solid/football-ball.svg +1 -0
  1328. package/static/vendor/fontawesome-free/svgs/solid/forward.svg +1 -0
  1329. package/static/vendor/fontawesome-free/svgs/solid/frog.svg +1 -0
  1330. package/static/vendor/fontawesome-free/svgs/solid/frown-open.svg +1 -0
  1331. package/static/vendor/fontawesome-free/svgs/solid/frown.svg +1 -0
  1332. package/static/vendor/fontawesome-free/svgs/solid/funnel-dollar.svg +1 -0
  1333. package/static/vendor/fontawesome-free/svgs/solid/futbol.svg +1 -0
  1334. package/static/vendor/fontawesome-free/svgs/solid/gamepad.svg +1 -0
  1335. package/static/vendor/fontawesome-free/svgs/solid/gas-pump.svg +1 -0
  1336. package/static/vendor/fontawesome-free/svgs/solid/gavel.svg +1 -0
  1337. package/static/vendor/fontawesome-free/svgs/solid/gem.svg +1 -0
  1338. package/static/vendor/fontawesome-free/svgs/solid/genderless.svg +1 -0
  1339. package/static/vendor/fontawesome-free/svgs/solid/ghost.svg +1 -0
  1340. package/static/vendor/fontawesome-free/svgs/solid/gift.svg +1 -0
  1341. package/static/vendor/fontawesome-free/svgs/solid/gifts.svg +1 -0
  1342. package/static/vendor/fontawesome-free/svgs/solid/glass-cheers.svg +1 -0
  1343. package/static/vendor/fontawesome-free/svgs/solid/glass-martini-alt.svg +1 -0
  1344. package/static/vendor/fontawesome-free/svgs/solid/glass-martini.svg +1 -0
  1345. package/static/vendor/fontawesome-free/svgs/solid/glass-whiskey.svg +1 -0
  1346. package/static/vendor/fontawesome-free/svgs/solid/glasses.svg +1 -0
  1347. package/static/vendor/fontawesome-free/svgs/solid/globe-africa.svg +1 -0
  1348. package/static/vendor/fontawesome-free/svgs/solid/globe-americas.svg +1 -0
  1349. package/static/vendor/fontawesome-free/svgs/solid/globe-asia.svg +1 -0
  1350. package/static/vendor/fontawesome-free/svgs/solid/globe-europe.svg +1 -0
  1351. package/static/vendor/fontawesome-free/svgs/solid/globe.svg +1 -0
  1352. package/static/vendor/fontawesome-free/svgs/solid/golf-ball.svg +1 -0
  1353. package/static/vendor/fontawesome-free/svgs/solid/gopuram.svg +1 -0
  1354. package/static/vendor/fontawesome-free/svgs/solid/graduation-cap.svg +1 -0
  1355. package/static/vendor/fontawesome-free/svgs/solid/greater-than-equal.svg +1 -0
  1356. package/static/vendor/fontawesome-free/svgs/solid/greater-than.svg +1 -0
  1357. package/static/vendor/fontawesome-free/svgs/solid/grimace.svg +1 -0
  1358. package/static/vendor/fontawesome-free/svgs/solid/grin-alt.svg +1 -0
  1359. package/static/vendor/fontawesome-free/svgs/solid/grin-beam-sweat.svg +1 -0
  1360. package/static/vendor/fontawesome-free/svgs/solid/grin-beam.svg +1 -0
  1361. package/static/vendor/fontawesome-free/svgs/solid/grin-hearts.svg +1 -0
  1362. package/static/vendor/fontawesome-free/svgs/solid/grin-squint-tears.svg +1 -0
  1363. package/static/vendor/fontawesome-free/svgs/solid/grin-squint.svg +1 -0
  1364. package/static/vendor/fontawesome-free/svgs/solid/grin-stars.svg +1 -0
  1365. package/static/vendor/fontawesome-free/svgs/solid/grin-tears.svg +1 -0
  1366. package/static/vendor/fontawesome-free/svgs/solid/grin-tongue-squint.svg +1 -0
  1367. package/static/vendor/fontawesome-free/svgs/solid/grin-tongue-wink.svg +1 -0
  1368. package/static/vendor/fontawesome-free/svgs/solid/grin-tongue.svg +1 -0
  1369. package/static/vendor/fontawesome-free/svgs/solid/grin-wink.svg +1 -0
  1370. package/static/vendor/fontawesome-free/svgs/solid/grin.svg +1 -0
  1371. package/static/vendor/fontawesome-free/svgs/solid/grip-horizontal.svg +1 -0
  1372. package/static/vendor/fontawesome-free/svgs/solid/grip-lines-vertical.svg +1 -0
  1373. package/static/vendor/fontawesome-free/svgs/solid/grip-lines.svg +1 -0
  1374. package/static/vendor/fontawesome-free/svgs/solid/grip-vertical.svg +1 -0
  1375. package/static/vendor/fontawesome-free/svgs/solid/guitar.svg +1 -0
  1376. package/static/vendor/fontawesome-free/svgs/solid/h-square.svg +1 -0
  1377. package/static/vendor/fontawesome-free/svgs/solid/hamburger.svg +1 -0
  1378. package/static/vendor/fontawesome-free/svgs/solid/hammer.svg +1 -0
  1379. package/static/vendor/fontawesome-free/svgs/solid/hamsa.svg +1 -0
  1380. package/static/vendor/fontawesome-free/svgs/solid/hand-holding-heart.svg +1 -0
  1381. package/static/vendor/fontawesome-free/svgs/solid/hand-holding-medical.svg +1 -0
  1382. package/static/vendor/fontawesome-free/svgs/solid/hand-holding-usd.svg +1 -0
  1383. package/static/vendor/fontawesome-free/svgs/solid/hand-holding-water.svg +1 -0
  1384. package/static/vendor/fontawesome-free/svgs/solid/hand-holding.svg +1 -0
  1385. package/static/vendor/fontawesome-free/svgs/solid/hand-lizard.svg +1 -0
  1386. package/static/vendor/fontawesome-free/svgs/solid/hand-middle-finger.svg +1 -0
  1387. package/static/vendor/fontawesome-free/svgs/solid/hand-paper.svg +1 -0
  1388. package/static/vendor/fontawesome-free/svgs/solid/hand-peace.svg +1 -0
  1389. package/static/vendor/fontawesome-free/svgs/solid/hand-point-down.svg +1 -0
  1390. package/static/vendor/fontawesome-free/svgs/solid/hand-point-left.svg +1 -0
  1391. package/static/vendor/fontawesome-free/svgs/solid/hand-point-right.svg +1 -0
  1392. package/static/vendor/fontawesome-free/svgs/solid/hand-point-up.svg +1 -0
  1393. package/static/vendor/fontawesome-free/svgs/solid/hand-pointer.svg +1 -0
  1394. package/static/vendor/fontawesome-free/svgs/solid/hand-rock.svg +1 -0
  1395. package/static/vendor/fontawesome-free/svgs/solid/hand-scissors.svg +1 -0
  1396. package/static/vendor/fontawesome-free/svgs/solid/hand-sparkles.svg +1 -0
  1397. package/static/vendor/fontawesome-free/svgs/solid/hand-spock.svg +1 -0
  1398. package/static/vendor/fontawesome-free/svgs/solid/hands-helping.svg +1 -0
  1399. package/static/vendor/fontawesome-free/svgs/solid/hands-wash.svg +1 -0
  1400. package/static/vendor/fontawesome-free/svgs/solid/hands.svg +1 -0
  1401. package/static/vendor/fontawesome-free/svgs/solid/handshake-alt-slash.svg +1 -0
  1402. package/static/vendor/fontawesome-free/svgs/solid/handshake-slash.svg +1 -0
  1403. package/static/vendor/fontawesome-free/svgs/solid/handshake.svg +1 -0
  1404. package/static/vendor/fontawesome-free/svgs/solid/hanukiah.svg +1 -0
  1405. package/static/vendor/fontawesome-free/svgs/solid/hard-hat.svg +1 -0
  1406. package/static/vendor/fontawesome-free/svgs/solid/hashtag.svg +1 -0
  1407. package/static/vendor/fontawesome-free/svgs/solid/hat-cowboy-side.svg +1 -0
  1408. package/static/vendor/fontawesome-free/svgs/solid/hat-cowboy.svg +1 -0
  1409. package/static/vendor/fontawesome-free/svgs/solid/hat-wizard.svg +1 -0
  1410. package/static/vendor/fontawesome-free/svgs/solid/hdd.svg +1 -0
  1411. package/static/vendor/fontawesome-free/svgs/solid/head-side-cough-slash.svg +1 -0
  1412. package/static/vendor/fontawesome-free/svgs/solid/head-side-cough.svg +1 -0
  1413. package/static/vendor/fontawesome-free/svgs/solid/head-side-mask.svg +1 -0
  1414. package/static/vendor/fontawesome-free/svgs/solid/head-side-virus.svg +1 -0
  1415. package/static/vendor/fontawesome-free/svgs/solid/heading.svg +1 -0
  1416. package/static/vendor/fontawesome-free/svgs/solid/headphones-alt.svg +1 -0
  1417. package/static/vendor/fontawesome-free/svgs/solid/headphones.svg +1 -0
  1418. package/static/vendor/fontawesome-free/svgs/solid/headset.svg +1 -0
  1419. package/static/vendor/fontawesome-free/svgs/solid/heart-broken.svg +1 -0
  1420. package/static/vendor/fontawesome-free/svgs/solid/heart.svg +1 -0
  1421. package/static/vendor/fontawesome-free/svgs/solid/heartbeat.svg +1 -0
  1422. package/static/vendor/fontawesome-free/svgs/solid/helicopter.svg +1 -0
  1423. package/static/vendor/fontawesome-free/svgs/solid/highlighter.svg +1 -0
  1424. package/static/vendor/fontawesome-free/svgs/solid/hiking.svg +1 -0
  1425. package/static/vendor/fontawesome-free/svgs/solid/hippo.svg +1 -0
  1426. package/static/vendor/fontawesome-free/svgs/solid/history.svg +1 -0
  1427. package/static/vendor/fontawesome-free/svgs/solid/hockey-puck.svg +1 -0
  1428. package/static/vendor/fontawesome-free/svgs/solid/holly-berry.svg +1 -0
  1429. package/static/vendor/fontawesome-free/svgs/solid/home.svg +1 -0
  1430. package/static/vendor/fontawesome-free/svgs/solid/horse-head.svg +1 -0
  1431. package/static/vendor/fontawesome-free/svgs/solid/horse.svg +1 -0
  1432. package/static/vendor/fontawesome-free/svgs/solid/hospital-alt.svg +1 -0
  1433. package/static/vendor/fontawesome-free/svgs/solid/hospital-symbol.svg +1 -0
  1434. package/static/vendor/fontawesome-free/svgs/solid/hospital-user.svg +1 -0
  1435. package/static/vendor/fontawesome-free/svgs/solid/hospital.svg +1 -0
  1436. package/static/vendor/fontawesome-free/svgs/solid/hot-tub.svg +1 -0
  1437. package/static/vendor/fontawesome-free/svgs/solid/hotdog.svg +1 -0
  1438. package/static/vendor/fontawesome-free/svgs/solid/hotel.svg +1 -0
  1439. package/static/vendor/fontawesome-free/svgs/solid/hourglass-end.svg +1 -0
  1440. package/static/vendor/fontawesome-free/svgs/solid/hourglass-half.svg +1 -0
  1441. package/static/vendor/fontawesome-free/svgs/solid/hourglass-start.svg +1 -0
  1442. package/static/vendor/fontawesome-free/svgs/solid/hourglass.svg +1 -0
  1443. package/static/vendor/fontawesome-free/svgs/solid/house-damage.svg +1 -0
  1444. package/static/vendor/fontawesome-free/svgs/solid/house-user.svg +1 -0
  1445. package/static/vendor/fontawesome-free/svgs/solid/hryvnia.svg +1 -0
  1446. package/static/vendor/fontawesome-free/svgs/solid/i-cursor.svg +1 -0
  1447. package/static/vendor/fontawesome-free/svgs/solid/ice-cream.svg +1 -0
  1448. package/static/vendor/fontawesome-free/svgs/solid/icicles.svg +1 -0
  1449. package/static/vendor/fontawesome-free/svgs/solid/icons.svg +1 -0
  1450. package/static/vendor/fontawesome-free/svgs/solid/id-badge.svg +1 -0
  1451. package/static/vendor/fontawesome-free/svgs/solid/id-card-alt.svg +1 -0
  1452. package/static/vendor/fontawesome-free/svgs/solid/id-card.svg +1 -0
  1453. package/static/vendor/fontawesome-free/svgs/solid/igloo.svg +1 -0
  1454. package/static/vendor/fontawesome-free/svgs/solid/image.svg +1 -0
  1455. package/static/vendor/fontawesome-free/svgs/solid/images.svg +1 -0
  1456. package/static/vendor/fontawesome-free/svgs/solid/inbox.svg +1 -0
  1457. package/static/vendor/fontawesome-free/svgs/solid/indent.svg +1 -0
  1458. package/static/vendor/fontawesome-free/svgs/solid/industry.svg +1 -0
  1459. package/static/vendor/fontawesome-free/svgs/solid/infinity.svg +1 -0
  1460. package/static/vendor/fontawesome-free/svgs/solid/info-circle.svg +1 -0
  1461. package/static/vendor/fontawesome-free/svgs/solid/info.svg +1 -0
  1462. package/static/vendor/fontawesome-free/svgs/solid/italic.svg +1 -0
  1463. package/static/vendor/fontawesome-free/svgs/solid/jedi.svg +1 -0
  1464. package/static/vendor/fontawesome-free/svgs/solid/joint.svg +1 -0
  1465. package/static/vendor/fontawesome-free/svgs/solid/journal-whills.svg +1 -0
  1466. package/static/vendor/fontawesome-free/svgs/solid/kaaba.svg +1 -0
  1467. package/static/vendor/fontawesome-free/svgs/solid/key.svg +1 -0
  1468. package/static/vendor/fontawesome-free/svgs/solid/keyboard.svg +1 -0
  1469. package/static/vendor/fontawesome-free/svgs/solid/khanda.svg +1 -0
  1470. package/static/vendor/fontawesome-free/svgs/solid/kiss-beam.svg +1 -0
  1471. package/static/vendor/fontawesome-free/svgs/solid/kiss-wink-heart.svg +1 -0
  1472. package/static/vendor/fontawesome-free/svgs/solid/kiss.svg +1 -0
  1473. package/static/vendor/fontawesome-free/svgs/solid/kiwi-bird.svg +1 -0
  1474. package/static/vendor/fontawesome-free/svgs/solid/landmark.svg +1 -0
  1475. package/static/vendor/fontawesome-free/svgs/solid/language.svg +1 -0
  1476. package/static/vendor/fontawesome-free/svgs/solid/laptop-code.svg +1 -0
  1477. package/static/vendor/fontawesome-free/svgs/solid/laptop-house.svg +1 -0
  1478. package/static/vendor/fontawesome-free/svgs/solid/laptop-medical.svg +1 -0
  1479. package/static/vendor/fontawesome-free/svgs/solid/laptop.svg +1 -0
  1480. package/static/vendor/fontawesome-free/svgs/solid/laugh-beam.svg +1 -0
  1481. package/static/vendor/fontawesome-free/svgs/solid/laugh-squint.svg +1 -0
  1482. package/static/vendor/fontawesome-free/svgs/solid/laugh-wink.svg +1 -0
  1483. package/static/vendor/fontawesome-free/svgs/solid/laugh.svg +1 -0
  1484. package/static/vendor/fontawesome-free/svgs/solid/layer-group.svg +1 -0
  1485. package/static/vendor/fontawesome-free/svgs/solid/leaf.svg +1 -0
  1486. package/static/vendor/fontawesome-free/svgs/solid/lemon.svg +1 -0
  1487. package/static/vendor/fontawesome-free/svgs/solid/less-than-equal.svg +1 -0
  1488. package/static/vendor/fontawesome-free/svgs/solid/less-than.svg +1 -0
  1489. package/static/vendor/fontawesome-free/svgs/solid/level-down-alt.svg +1 -0
  1490. package/static/vendor/fontawesome-free/svgs/solid/level-up-alt.svg +1 -0
  1491. package/static/vendor/fontawesome-free/svgs/solid/life-ring.svg +1 -0
  1492. package/static/vendor/fontawesome-free/svgs/solid/lightbulb.svg +1 -0
  1493. package/static/vendor/fontawesome-free/svgs/solid/link.svg +1 -0
  1494. package/static/vendor/fontawesome-free/svgs/solid/lira-sign.svg +1 -0
  1495. package/static/vendor/fontawesome-free/svgs/solid/list-alt.svg +1 -0
  1496. package/static/vendor/fontawesome-free/svgs/solid/list-ol.svg +1 -0
  1497. package/static/vendor/fontawesome-free/svgs/solid/list-ul.svg +1 -0
  1498. package/static/vendor/fontawesome-free/svgs/solid/list.svg +1 -0
  1499. package/static/vendor/fontawesome-free/svgs/solid/location-arrow.svg +1 -0
  1500. package/static/vendor/fontawesome-free/svgs/solid/lock-open.svg +1 -0
  1501. package/static/vendor/fontawesome-free/svgs/solid/lock.svg +1 -0
  1502. package/static/vendor/fontawesome-free/svgs/solid/long-arrow-alt-down.svg +1 -0
  1503. package/static/vendor/fontawesome-free/svgs/solid/long-arrow-alt-left.svg +1 -0
  1504. package/static/vendor/fontawesome-free/svgs/solid/long-arrow-alt-right.svg +1 -0
  1505. package/static/vendor/fontawesome-free/svgs/solid/long-arrow-alt-up.svg +1 -0
  1506. package/static/vendor/fontawesome-free/svgs/solid/low-vision.svg +1 -0
  1507. package/static/vendor/fontawesome-free/svgs/solid/luggage-cart.svg +1 -0
  1508. package/static/vendor/fontawesome-free/svgs/solid/lungs-virus.svg +1 -0
  1509. package/static/vendor/fontawesome-free/svgs/solid/lungs.svg +1 -0
  1510. package/static/vendor/fontawesome-free/svgs/solid/magic.svg +1 -0
  1511. package/static/vendor/fontawesome-free/svgs/solid/magnet.svg +1 -0
  1512. package/static/vendor/fontawesome-free/svgs/solid/mail-bulk.svg +1 -0
  1513. package/static/vendor/fontawesome-free/svgs/solid/male.svg +1 -0
  1514. package/static/vendor/fontawesome-free/svgs/solid/map-marked-alt.svg +1 -0
  1515. package/static/vendor/fontawesome-free/svgs/solid/map-marked.svg +1 -0
  1516. package/static/vendor/fontawesome-free/svgs/solid/map-marker-alt.svg +1 -0
  1517. package/static/vendor/fontawesome-free/svgs/solid/map-marker.svg +1 -0
  1518. package/static/vendor/fontawesome-free/svgs/solid/map-pin.svg +1 -0
  1519. package/static/vendor/fontawesome-free/svgs/solid/map-signs.svg +1 -0
  1520. package/static/vendor/fontawesome-free/svgs/solid/map.svg +1 -0
  1521. package/static/vendor/fontawesome-free/svgs/solid/marker.svg +1 -0
  1522. package/static/vendor/fontawesome-free/svgs/solid/mars-double.svg +1 -0
  1523. package/static/vendor/fontawesome-free/svgs/solid/mars-stroke-h.svg +1 -0
  1524. package/static/vendor/fontawesome-free/svgs/solid/mars-stroke-v.svg +1 -0
  1525. package/static/vendor/fontawesome-free/svgs/solid/mars-stroke.svg +1 -0
  1526. package/static/vendor/fontawesome-free/svgs/solid/mars.svg +1 -0
  1527. package/static/vendor/fontawesome-free/svgs/solid/mask.svg +1 -0
  1528. package/static/vendor/fontawesome-free/svgs/solid/medal.svg +1 -0
  1529. package/static/vendor/fontawesome-free/svgs/solid/medkit.svg +1 -0
  1530. package/static/vendor/fontawesome-free/svgs/solid/meh-blank.svg +1 -0
  1531. package/static/vendor/fontawesome-free/svgs/solid/meh-rolling-eyes.svg +1 -0
  1532. package/static/vendor/fontawesome-free/svgs/solid/meh.svg +1 -0
  1533. package/static/vendor/fontawesome-free/svgs/solid/memory.svg +1 -0
  1534. package/static/vendor/fontawesome-free/svgs/solid/menorah.svg +1 -0
  1535. package/static/vendor/fontawesome-free/svgs/solid/mercury.svg +1 -0
  1536. package/static/vendor/fontawesome-free/svgs/solid/meteor.svg +1 -0
  1537. package/static/vendor/fontawesome-free/svgs/solid/microchip.svg +1 -0
  1538. package/static/vendor/fontawesome-free/svgs/solid/microphone-alt-slash.svg +1 -0
  1539. package/static/vendor/fontawesome-free/svgs/solid/microphone-alt.svg +1 -0
  1540. package/static/vendor/fontawesome-free/svgs/solid/microphone-slash.svg +1 -0
  1541. package/static/vendor/fontawesome-free/svgs/solid/microphone.svg +1 -0
  1542. package/static/vendor/fontawesome-free/svgs/solid/microscope.svg +1 -0
  1543. package/static/vendor/fontawesome-free/svgs/solid/minus-circle.svg +1 -0
  1544. package/static/vendor/fontawesome-free/svgs/solid/minus-square.svg +1 -0
  1545. package/static/vendor/fontawesome-free/svgs/solid/minus.svg +1 -0
  1546. package/static/vendor/fontawesome-free/svgs/solid/mitten.svg +1 -0
  1547. package/static/vendor/fontawesome-free/svgs/solid/mobile-alt.svg +1 -0
  1548. package/static/vendor/fontawesome-free/svgs/solid/mobile.svg +1 -0
  1549. package/static/vendor/fontawesome-free/svgs/solid/money-bill-alt.svg +1 -0
  1550. package/static/vendor/fontawesome-free/svgs/solid/money-bill-wave-alt.svg +1 -0
  1551. package/static/vendor/fontawesome-free/svgs/solid/money-bill-wave.svg +1 -0
  1552. package/static/vendor/fontawesome-free/svgs/solid/money-bill.svg +1 -0
  1553. package/static/vendor/fontawesome-free/svgs/solid/money-check-alt.svg +1 -0
  1554. package/static/vendor/fontawesome-free/svgs/solid/money-check.svg +1 -0
  1555. package/static/vendor/fontawesome-free/svgs/solid/monument.svg +1 -0
  1556. package/static/vendor/fontawesome-free/svgs/solid/moon.svg +1 -0
  1557. package/static/vendor/fontawesome-free/svgs/solid/mortar-pestle.svg +1 -0
  1558. package/static/vendor/fontawesome-free/svgs/solid/mosque.svg +1 -0
  1559. package/static/vendor/fontawesome-free/svgs/solid/motorcycle.svg +1 -0
  1560. package/static/vendor/fontawesome-free/svgs/solid/mountain.svg +1 -0
  1561. package/static/vendor/fontawesome-free/svgs/solid/mouse-pointer.svg +1 -0
  1562. package/static/vendor/fontawesome-free/svgs/solid/mouse.svg +1 -0
  1563. package/static/vendor/fontawesome-free/svgs/solid/mug-hot.svg +1 -0
  1564. package/static/vendor/fontawesome-free/svgs/solid/music.svg +1 -0
  1565. package/static/vendor/fontawesome-free/svgs/solid/network-wired.svg +1 -0
  1566. package/static/vendor/fontawesome-free/svgs/solid/neuter.svg +1 -0
  1567. package/static/vendor/fontawesome-free/svgs/solid/newspaper.svg +1 -0
  1568. package/static/vendor/fontawesome-free/svgs/solid/not-equal.svg +1 -0
  1569. package/static/vendor/fontawesome-free/svgs/solid/notes-medical.svg +1 -0
  1570. package/static/vendor/fontawesome-free/svgs/solid/object-group.svg +1 -0
  1571. package/static/vendor/fontawesome-free/svgs/solid/object-ungroup.svg +1 -0
  1572. package/static/vendor/fontawesome-free/svgs/solid/oil-can.svg +1 -0
  1573. package/static/vendor/fontawesome-free/svgs/solid/om.svg +1 -0
  1574. package/static/vendor/fontawesome-free/svgs/solid/otter.svg +1 -0
  1575. package/static/vendor/fontawesome-free/svgs/solid/outdent.svg +1 -0
  1576. package/static/vendor/fontawesome-free/svgs/solid/pager.svg +1 -0
  1577. package/static/vendor/fontawesome-free/svgs/solid/paint-brush.svg +1 -0
  1578. package/static/vendor/fontawesome-free/svgs/solid/paint-roller.svg +1 -0
  1579. package/static/vendor/fontawesome-free/svgs/solid/palette.svg +1 -0
  1580. package/static/vendor/fontawesome-free/svgs/solid/pallet.svg +1 -0
  1581. package/static/vendor/fontawesome-free/svgs/solid/paper-plane.svg +1 -0
  1582. package/static/vendor/fontawesome-free/svgs/solid/paperclip.svg +1 -0
  1583. package/static/vendor/fontawesome-free/svgs/solid/parachute-box.svg +1 -0
  1584. package/static/vendor/fontawesome-free/svgs/solid/paragraph.svg +1 -0
  1585. package/static/vendor/fontawesome-free/svgs/solid/parking.svg +1 -0
  1586. package/static/vendor/fontawesome-free/svgs/solid/passport.svg +1 -0
  1587. package/static/vendor/fontawesome-free/svgs/solid/pastafarianism.svg +1 -0
  1588. package/static/vendor/fontawesome-free/svgs/solid/paste.svg +1 -0
  1589. package/static/vendor/fontawesome-free/svgs/solid/pause-circle.svg +1 -0
  1590. package/static/vendor/fontawesome-free/svgs/solid/pause.svg +1 -0
  1591. package/static/vendor/fontawesome-free/svgs/solid/paw.svg +1 -0
  1592. package/static/vendor/fontawesome-free/svgs/solid/peace.svg +1 -0
  1593. package/static/vendor/fontawesome-free/svgs/solid/pen-alt.svg +1 -0
  1594. package/static/vendor/fontawesome-free/svgs/solid/pen-fancy.svg +1 -0
  1595. package/static/vendor/fontawesome-free/svgs/solid/pen-nib.svg +1 -0
  1596. package/static/vendor/fontawesome-free/svgs/solid/pen-square.svg +1 -0
  1597. package/static/vendor/fontawesome-free/svgs/solid/pen.svg +1 -0
  1598. package/static/vendor/fontawesome-free/svgs/solid/pencil-alt.svg +1 -0
  1599. package/static/vendor/fontawesome-free/svgs/solid/pencil-ruler.svg +1 -0
  1600. package/static/vendor/fontawesome-free/svgs/solid/people-arrows.svg +1 -0
  1601. package/static/vendor/fontawesome-free/svgs/solid/people-carry.svg +1 -0
  1602. package/static/vendor/fontawesome-free/svgs/solid/pepper-hot.svg +1 -0
  1603. package/static/vendor/fontawesome-free/svgs/solid/percent.svg +1 -0
  1604. package/static/vendor/fontawesome-free/svgs/solid/percentage.svg +1 -0
  1605. package/static/vendor/fontawesome-free/svgs/solid/person-booth.svg +1 -0
  1606. package/static/vendor/fontawesome-free/svgs/solid/phone-alt.svg +1 -0
  1607. package/static/vendor/fontawesome-free/svgs/solid/phone-slash.svg +1 -0
  1608. package/static/vendor/fontawesome-free/svgs/solid/phone-square-alt.svg +1 -0
  1609. package/static/vendor/fontawesome-free/svgs/solid/phone-square.svg +1 -0
  1610. package/static/vendor/fontawesome-free/svgs/solid/phone-volume.svg +1 -0
  1611. package/static/vendor/fontawesome-free/svgs/solid/phone.svg +1 -0
  1612. package/static/vendor/fontawesome-free/svgs/solid/photo-video.svg +1 -0
  1613. package/static/vendor/fontawesome-free/svgs/solid/piggy-bank.svg +1 -0
  1614. package/static/vendor/fontawesome-free/svgs/solid/pills.svg +1 -0
  1615. package/static/vendor/fontawesome-free/svgs/solid/pizza-slice.svg +1 -0
  1616. package/static/vendor/fontawesome-free/svgs/solid/place-of-worship.svg +1 -0
  1617. package/static/vendor/fontawesome-free/svgs/solid/plane-arrival.svg +1 -0
  1618. package/static/vendor/fontawesome-free/svgs/solid/plane-departure.svg +1 -0
  1619. package/static/vendor/fontawesome-free/svgs/solid/plane-slash.svg +1 -0
  1620. package/static/vendor/fontawesome-free/svgs/solid/plane.svg +1 -0
  1621. package/static/vendor/fontawesome-free/svgs/solid/play-circle.svg +1 -0
  1622. package/static/vendor/fontawesome-free/svgs/solid/play.svg +1 -0
  1623. package/static/vendor/fontawesome-free/svgs/solid/plug.svg +1 -0
  1624. package/static/vendor/fontawesome-free/svgs/solid/plus-circle.svg +1 -0
  1625. package/static/vendor/fontawesome-free/svgs/solid/plus-square.svg +1 -0
  1626. package/static/vendor/fontawesome-free/svgs/solid/plus.svg +1 -0
  1627. package/static/vendor/fontawesome-free/svgs/solid/podcast.svg +1 -0
  1628. package/static/vendor/fontawesome-free/svgs/solid/poll-h.svg +1 -0
  1629. package/static/vendor/fontawesome-free/svgs/solid/poll.svg +1 -0
  1630. package/static/vendor/fontawesome-free/svgs/solid/poo-storm.svg +1 -0
  1631. package/static/vendor/fontawesome-free/svgs/solid/poo.svg +1 -0
  1632. package/static/vendor/fontawesome-free/svgs/solid/poop.svg +1 -0
  1633. package/static/vendor/fontawesome-free/svgs/solid/portrait.svg +1 -0
  1634. package/static/vendor/fontawesome-free/svgs/solid/pound-sign.svg +1 -0
  1635. package/static/vendor/fontawesome-free/svgs/solid/power-off.svg +1 -0
  1636. package/static/vendor/fontawesome-free/svgs/solid/pray.svg +1 -0
  1637. package/static/vendor/fontawesome-free/svgs/solid/praying-hands.svg +1 -0
  1638. package/static/vendor/fontawesome-free/svgs/solid/prescription-bottle-alt.svg +1 -0
  1639. package/static/vendor/fontawesome-free/svgs/solid/prescription-bottle.svg +1 -0
  1640. package/static/vendor/fontawesome-free/svgs/solid/prescription.svg +1 -0
  1641. package/static/vendor/fontawesome-free/svgs/solid/print.svg +1 -0
  1642. package/static/vendor/fontawesome-free/svgs/solid/procedures.svg +1 -0
  1643. package/static/vendor/fontawesome-free/svgs/solid/project-diagram.svg +1 -0
  1644. package/static/vendor/fontawesome-free/svgs/solid/pump-medical.svg +1 -0
  1645. package/static/vendor/fontawesome-free/svgs/solid/pump-soap.svg +1 -0
  1646. package/static/vendor/fontawesome-free/svgs/solid/puzzle-piece.svg +1 -0
  1647. package/static/vendor/fontawesome-free/svgs/solid/qrcode.svg +1 -0
  1648. package/static/vendor/fontawesome-free/svgs/solid/question-circle.svg +1 -0
  1649. package/static/vendor/fontawesome-free/svgs/solid/question.svg +1 -0
  1650. package/static/vendor/fontawesome-free/svgs/solid/quidditch.svg +1 -0
  1651. package/static/vendor/fontawesome-free/svgs/solid/quote-left.svg +1 -0
  1652. package/static/vendor/fontawesome-free/svgs/solid/quote-right.svg +1 -0
  1653. package/static/vendor/fontawesome-free/svgs/solid/quran.svg +1 -0
  1654. package/static/vendor/fontawesome-free/svgs/solid/radiation-alt.svg +1 -0
  1655. package/static/vendor/fontawesome-free/svgs/solid/radiation.svg +1 -0
  1656. package/static/vendor/fontawesome-free/svgs/solid/rainbow.svg +1 -0
  1657. package/static/vendor/fontawesome-free/svgs/solid/random.svg +1 -0
  1658. package/static/vendor/fontawesome-free/svgs/solid/receipt.svg +1 -0
  1659. package/static/vendor/fontawesome-free/svgs/solid/record-vinyl.svg +1 -0
  1660. package/static/vendor/fontawesome-free/svgs/solid/recycle.svg +1 -0
  1661. package/static/vendor/fontawesome-free/svgs/solid/redo-alt.svg +1 -0
  1662. package/static/vendor/fontawesome-free/svgs/solid/redo.svg +1 -0
  1663. package/static/vendor/fontawesome-free/svgs/solid/registered.svg +1 -0
  1664. package/static/vendor/fontawesome-free/svgs/solid/remove-format.svg +1 -0
  1665. package/static/vendor/fontawesome-free/svgs/solid/reply-all.svg +1 -0
  1666. package/static/vendor/fontawesome-free/svgs/solid/reply.svg +1 -0
  1667. package/static/vendor/fontawesome-free/svgs/solid/republican.svg +1 -0
  1668. package/static/vendor/fontawesome-free/svgs/solid/restroom.svg +1 -0
  1669. package/static/vendor/fontawesome-free/svgs/solid/retweet.svg +1 -0
  1670. package/static/vendor/fontawesome-free/svgs/solid/ribbon.svg +1 -0
  1671. package/static/vendor/fontawesome-free/svgs/solid/ring.svg +1 -0
  1672. package/static/vendor/fontawesome-free/svgs/solid/road.svg +1 -0
  1673. package/static/vendor/fontawesome-free/svgs/solid/robot.svg +1 -0
  1674. package/static/vendor/fontawesome-free/svgs/solid/rocket.svg +1 -0
  1675. package/static/vendor/fontawesome-free/svgs/solid/route.svg +1 -0
  1676. package/static/vendor/fontawesome-free/svgs/solid/rss-square.svg +1 -0
  1677. package/static/vendor/fontawesome-free/svgs/solid/rss.svg +1 -0
  1678. package/static/vendor/fontawesome-free/svgs/solid/ruble-sign.svg +1 -0
  1679. package/static/vendor/fontawesome-free/svgs/solid/ruler-combined.svg +1 -0
  1680. package/static/vendor/fontawesome-free/svgs/solid/ruler-horizontal.svg +1 -0
  1681. package/static/vendor/fontawesome-free/svgs/solid/ruler-vertical.svg +1 -0
  1682. package/static/vendor/fontawesome-free/svgs/solid/ruler.svg +1 -0
  1683. package/static/vendor/fontawesome-free/svgs/solid/running.svg +1 -0
  1684. package/static/vendor/fontawesome-free/svgs/solid/rupee-sign.svg +1 -0
  1685. package/static/vendor/fontawesome-free/svgs/solid/sad-cry.svg +1 -0
  1686. package/static/vendor/fontawesome-free/svgs/solid/sad-tear.svg +1 -0
  1687. package/static/vendor/fontawesome-free/svgs/solid/satellite-dish.svg +1 -0
  1688. package/static/vendor/fontawesome-free/svgs/solid/satellite.svg +1 -0
  1689. package/static/vendor/fontawesome-free/svgs/solid/save.svg +1 -0
  1690. package/static/vendor/fontawesome-free/svgs/solid/school.svg +1 -0
  1691. package/static/vendor/fontawesome-free/svgs/solid/screwdriver.svg +1 -0
  1692. package/static/vendor/fontawesome-free/svgs/solid/scroll.svg +1 -0
  1693. package/static/vendor/fontawesome-free/svgs/solid/sd-card.svg +1 -0
  1694. package/static/vendor/fontawesome-free/svgs/solid/search-dollar.svg +1 -0
  1695. package/static/vendor/fontawesome-free/svgs/solid/search-location.svg +1 -0
  1696. package/static/vendor/fontawesome-free/svgs/solid/search-minus.svg +1 -0
  1697. package/static/vendor/fontawesome-free/svgs/solid/search-plus.svg +1 -0
  1698. package/static/vendor/fontawesome-free/svgs/solid/search.svg +1 -0
  1699. package/static/vendor/fontawesome-free/svgs/solid/seedling.svg +1 -0
  1700. package/static/vendor/fontawesome-free/svgs/solid/server.svg +1 -0
  1701. package/static/vendor/fontawesome-free/svgs/solid/shapes.svg +1 -0
  1702. package/static/vendor/fontawesome-free/svgs/solid/share-alt-square.svg +1 -0
  1703. package/static/vendor/fontawesome-free/svgs/solid/share-alt.svg +1 -0
  1704. package/static/vendor/fontawesome-free/svgs/solid/share-square.svg +1 -0
  1705. package/static/vendor/fontawesome-free/svgs/solid/share.svg +1 -0
  1706. package/static/vendor/fontawesome-free/svgs/solid/shekel-sign.svg +1 -0
  1707. package/static/vendor/fontawesome-free/svgs/solid/shield-alt.svg +1 -0
  1708. package/static/vendor/fontawesome-free/svgs/solid/shield-virus.svg +1 -0
  1709. package/static/vendor/fontawesome-free/svgs/solid/ship.svg +1 -0
  1710. package/static/vendor/fontawesome-free/svgs/solid/shipping-fast.svg +1 -0
  1711. package/static/vendor/fontawesome-free/svgs/solid/shoe-prints.svg +1 -0
  1712. package/static/vendor/fontawesome-free/svgs/solid/shopping-bag.svg +1 -0
  1713. package/static/vendor/fontawesome-free/svgs/solid/shopping-basket.svg +1 -0
  1714. package/static/vendor/fontawesome-free/svgs/solid/shopping-cart.svg +1 -0
  1715. package/static/vendor/fontawesome-free/svgs/solid/shower.svg +1 -0
  1716. package/static/vendor/fontawesome-free/svgs/solid/shuttle-van.svg +1 -0
  1717. package/static/vendor/fontawesome-free/svgs/solid/sign-in-alt.svg +1 -0
  1718. package/static/vendor/fontawesome-free/svgs/solid/sign-language.svg +1 -0
  1719. package/static/vendor/fontawesome-free/svgs/solid/sign-out-alt.svg +1 -0
  1720. package/static/vendor/fontawesome-free/svgs/solid/sign.svg +1 -0
  1721. package/static/vendor/fontawesome-free/svgs/solid/signal.svg +1 -0
  1722. package/static/vendor/fontawesome-free/svgs/solid/signature.svg +1 -0
  1723. package/static/vendor/fontawesome-free/svgs/solid/sim-card.svg +1 -0
  1724. package/static/vendor/fontawesome-free/svgs/solid/sink.svg +1 -0
  1725. package/static/vendor/fontawesome-free/svgs/solid/sitemap.svg +1 -0
  1726. package/static/vendor/fontawesome-free/svgs/solid/skating.svg +1 -0
  1727. package/static/vendor/fontawesome-free/svgs/solid/skiing-nordic.svg +1 -0
  1728. package/static/vendor/fontawesome-free/svgs/solid/skiing.svg +1 -0
  1729. package/static/vendor/fontawesome-free/svgs/solid/skull-crossbones.svg +1 -0
  1730. package/static/vendor/fontawesome-free/svgs/solid/skull.svg +1 -0
  1731. package/static/vendor/fontawesome-free/svgs/solid/slash.svg +1 -0
  1732. package/static/vendor/fontawesome-free/svgs/solid/sleigh.svg +1 -0
  1733. package/static/vendor/fontawesome-free/svgs/solid/sliders-h.svg +1 -0
  1734. package/static/vendor/fontawesome-free/svgs/solid/smile-beam.svg +1 -0
  1735. package/static/vendor/fontawesome-free/svgs/solid/smile-wink.svg +1 -0
  1736. package/static/vendor/fontawesome-free/svgs/solid/smile.svg +1 -0
  1737. package/static/vendor/fontawesome-free/svgs/solid/smog.svg +1 -0
  1738. package/static/vendor/fontawesome-free/svgs/solid/smoking-ban.svg +1 -0
  1739. package/static/vendor/fontawesome-free/svgs/solid/smoking.svg +1 -0
  1740. package/static/vendor/fontawesome-free/svgs/solid/sms.svg +1 -0
  1741. package/static/vendor/fontawesome-free/svgs/solid/snowboarding.svg +1 -0
  1742. package/static/vendor/fontawesome-free/svgs/solid/snowflake.svg +1 -0
  1743. package/static/vendor/fontawesome-free/svgs/solid/snowman.svg +1 -0
  1744. package/static/vendor/fontawesome-free/svgs/solid/snowplow.svg +1 -0
  1745. package/static/vendor/fontawesome-free/svgs/solid/soap.svg +1 -0
  1746. package/static/vendor/fontawesome-free/svgs/solid/socks.svg +1 -0
  1747. package/static/vendor/fontawesome-free/svgs/solid/solar-panel.svg +1 -0
  1748. package/static/vendor/fontawesome-free/svgs/solid/sort-alpha-down-alt.svg +1 -0
  1749. package/static/vendor/fontawesome-free/svgs/solid/sort-alpha-down.svg +1 -0
  1750. package/static/vendor/fontawesome-free/svgs/solid/sort-alpha-up-alt.svg +1 -0
  1751. package/static/vendor/fontawesome-free/svgs/solid/sort-alpha-up.svg +1 -0
  1752. package/static/vendor/fontawesome-free/svgs/solid/sort-amount-down-alt.svg +1 -0
  1753. package/static/vendor/fontawesome-free/svgs/solid/sort-amount-down.svg +1 -0
  1754. package/static/vendor/fontawesome-free/svgs/solid/sort-amount-up-alt.svg +1 -0
  1755. package/static/vendor/fontawesome-free/svgs/solid/sort-amount-up.svg +1 -0
  1756. package/static/vendor/fontawesome-free/svgs/solid/sort-down.svg +1 -0
  1757. package/static/vendor/fontawesome-free/svgs/solid/sort-numeric-down-alt.svg +1 -0
  1758. package/static/vendor/fontawesome-free/svgs/solid/sort-numeric-down.svg +1 -0
  1759. package/static/vendor/fontawesome-free/svgs/solid/sort-numeric-up-alt.svg +1 -0
  1760. package/static/vendor/fontawesome-free/svgs/solid/sort-numeric-up.svg +1 -0
  1761. package/static/vendor/fontawesome-free/svgs/solid/sort-up.svg +1 -0
  1762. package/static/vendor/fontawesome-free/svgs/solid/sort.svg +1 -0
  1763. package/static/vendor/fontawesome-free/svgs/solid/spa.svg +1 -0
  1764. package/static/vendor/fontawesome-free/svgs/solid/space-shuttle.svg +1 -0
  1765. package/static/vendor/fontawesome-free/svgs/solid/spell-check.svg +1 -0
  1766. package/static/vendor/fontawesome-free/svgs/solid/spider.svg +1 -0
  1767. package/static/vendor/fontawesome-free/svgs/solid/spinner.svg +1 -0
  1768. package/static/vendor/fontawesome-free/svgs/solid/splotch.svg +1 -0
  1769. package/static/vendor/fontawesome-free/svgs/solid/spray-can.svg +1 -0
  1770. package/static/vendor/fontawesome-free/svgs/solid/square-full.svg +1 -0
  1771. package/static/vendor/fontawesome-free/svgs/solid/square-root-alt.svg +1 -0
  1772. package/static/vendor/fontawesome-free/svgs/solid/square.svg +1 -0
  1773. package/static/vendor/fontawesome-free/svgs/solid/stamp.svg +1 -0
  1774. package/static/vendor/fontawesome-free/svgs/solid/star-and-crescent.svg +1 -0
  1775. package/static/vendor/fontawesome-free/svgs/solid/star-half-alt.svg +1 -0
  1776. package/static/vendor/fontawesome-free/svgs/solid/star-half.svg +1 -0
  1777. package/static/vendor/fontawesome-free/svgs/solid/star-of-david.svg +1 -0
  1778. package/static/vendor/fontawesome-free/svgs/solid/star-of-life.svg +1 -0
  1779. package/static/vendor/fontawesome-free/svgs/solid/star.svg +1 -0
  1780. package/static/vendor/fontawesome-free/svgs/solid/step-backward.svg +1 -0
  1781. package/static/vendor/fontawesome-free/svgs/solid/step-forward.svg +1 -0
  1782. package/static/vendor/fontawesome-free/svgs/solid/stethoscope.svg +1 -0
  1783. package/static/vendor/fontawesome-free/svgs/solid/sticky-note.svg +1 -0
  1784. package/static/vendor/fontawesome-free/svgs/solid/stop-circle.svg +1 -0
  1785. package/static/vendor/fontawesome-free/svgs/solid/stop.svg +1 -0
  1786. package/static/vendor/fontawesome-free/svgs/solid/stopwatch-20.svg +1 -0
  1787. package/static/vendor/fontawesome-free/svgs/solid/stopwatch.svg +1 -0
  1788. package/static/vendor/fontawesome-free/svgs/solid/store-alt-slash.svg +1 -0
  1789. package/static/vendor/fontawesome-free/svgs/solid/store-alt.svg +1 -0
  1790. package/static/vendor/fontawesome-free/svgs/solid/store-slash.svg +1 -0
  1791. package/static/vendor/fontawesome-free/svgs/solid/store.svg +1 -0
  1792. package/static/vendor/fontawesome-free/svgs/solid/stream.svg +1 -0
  1793. package/static/vendor/fontawesome-free/svgs/solid/street-view.svg +1 -0
  1794. package/static/vendor/fontawesome-free/svgs/solid/strikethrough.svg +1 -0
  1795. package/static/vendor/fontawesome-free/svgs/solid/stroopwafel.svg +1 -0
  1796. package/static/vendor/fontawesome-free/svgs/solid/subscript.svg +1 -0
  1797. package/static/vendor/fontawesome-free/svgs/solid/subway.svg +1 -0
  1798. package/static/vendor/fontawesome-free/svgs/solid/suitcase-rolling.svg +1 -0
  1799. package/static/vendor/fontawesome-free/svgs/solid/suitcase.svg +1 -0
  1800. package/static/vendor/fontawesome-free/svgs/solid/sun.svg +1 -0
  1801. package/static/vendor/fontawesome-free/svgs/solid/superscript.svg +1 -0
  1802. package/static/vendor/fontawesome-free/svgs/solid/surprise.svg +1 -0
  1803. package/static/vendor/fontawesome-free/svgs/solid/swatchbook.svg +1 -0
  1804. package/static/vendor/fontawesome-free/svgs/solid/swimmer.svg +1 -0
  1805. package/static/vendor/fontawesome-free/svgs/solid/swimming-pool.svg +1 -0
  1806. package/static/vendor/fontawesome-free/svgs/solid/synagogue.svg +1 -0
  1807. package/static/vendor/fontawesome-free/svgs/solid/sync-alt.svg +1 -0
  1808. package/static/vendor/fontawesome-free/svgs/solid/sync.svg +1 -0
  1809. package/static/vendor/fontawesome-free/svgs/solid/syringe.svg +1 -0
  1810. package/static/vendor/fontawesome-free/svgs/solid/table-tennis.svg +1 -0
  1811. package/static/vendor/fontawesome-free/svgs/solid/table.svg +1 -0
  1812. package/static/vendor/fontawesome-free/svgs/solid/tablet-alt.svg +1 -0
  1813. package/static/vendor/fontawesome-free/svgs/solid/tablet.svg +1 -0
  1814. package/static/vendor/fontawesome-free/svgs/solid/tablets.svg +1 -0
  1815. package/static/vendor/fontawesome-free/svgs/solid/tachometer-alt.svg +1 -0
  1816. package/static/vendor/fontawesome-free/svgs/solid/tag.svg +1 -0
  1817. package/static/vendor/fontawesome-free/svgs/solid/tags.svg +1 -0
  1818. package/static/vendor/fontawesome-free/svgs/solid/tape.svg +1 -0
  1819. package/static/vendor/fontawesome-free/svgs/solid/tasks.svg +1 -0
  1820. package/static/vendor/fontawesome-free/svgs/solid/taxi.svg +1 -0
  1821. package/static/vendor/fontawesome-free/svgs/solid/teeth-open.svg +1 -0
  1822. package/static/vendor/fontawesome-free/svgs/solid/teeth.svg +1 -0
  1823. package/static/vendor/fontawesome-free/svgs/solid/temperature-high.svg +1 -0
  1824. package/static/vendor/fontawesome-free/svgs/solid/temperature-low.svg +1 -0
  1825. package/static/vendor/fontawesome-free/svgs/solid/tenge.svg +1 -0
  1826. package/static/vendor/fontawesome-free/svgs/solid/terminal.svg +1 -0
  1827. package/static/vendor/fontawesome-free/svgs/solid/text-height.svg +1 -0
  1828. package/static/vendor/fontawesome-free/svgs/solid/text-width.svg +1 -0
  1829. package/static/vendor/fontawesome-free/svgs/solid/th-large.svg +1 -0
  1830. package/static/vendor/fontawesome-free/svgs/solid/th-list.svg +1 -0
  1831. package/static/vendor/fontawesome-free/svgs/solid/th.svg +1 -0
  1832. package/static/vendor/fontawesome-free/svgs/solid/theater-masks.svg +1 -0
  1833. package/static/vendor/fontawesome-free/svgs/solid/thermometer-empty.svg +1 -0
  1834. package/static/vendor/fontawesome-free/svgs/solid/thermometer-full.svg +1 -0
  1835. package/static/vendor/fontawesome-free/svgs/solid/thermometer-half.svg +1 -0
  1836. package/static/vendor/fontawesome-free/svgs/solid/thermometer-quarter.svg +1 -0
  1837. package/static/vendor/fontawesome-free/svgs/solid/thermometer-three-quarters.svg +1 -0
  1838. package/static/vendor/fontawesome-free/svgs/solid/thermometer.svg +1 -0
  1839. package/static/vendor/fontawesome-free/svgs/solid/thumbs-down.svg +1 -0
  1840. package/static/vendor/fontawesome-free/svgs/solid/thumbs-up.svg +1 -0
  1841. package/static/vendor/fontawesome-free/svgs/solid/thumbtack.svg +1 -0
  1842. package/static/vendor/fontawesome-free/svgs/solid/ticket-alt.svg +1 -0
  1843. package/static/vendor/fontawesome-free/svgs/solid/times-circle.svg +1 -0
  1844. package/static/vendor/fontawesome-free/svgs/solid/times.svg +1 -0
  1845. package/static/vendor/fontawesome-free/svgs/solid/tint-slash.svg +1 -0
  1846. package/static/vendor/fontawesome-free/svgs/solid/tint.svg +1 -0
  1847. package/static/vendor/fontawesome-free/svgs/solid/tired.svg +1 -0
  1848. package/static/vendor/fontawesome-free/svgs/solid/toggle-off.svg +1 -0
  1849. package/static/vendor/fontawesome-free/svgs/solid/toggle-on.svg +1 -0
  1850. package/static/vendor/fontawesome-free/svgs/solid/toilet-paper-slash.svg +1 -0
  1851. package/static/vendor/fontawesome-free/svgs/solid/toilet-paper.svg +1 -0
  1852. package/static/vendor/fontawesome-free/svgs/solid/toilet.svg +1 -0
  1853. package/static/vendor/fontawesome-free/svgs/solid/toolbox.svg +1 -0
  1854. package/static/vendor/fontawesome-free/svgs/solid/tools.svg +1 -0
  1855. package/static/vendor/fontawesome-free/svgs/solid/tooth.svg +1 -0
  1856. package/static/vendor/fontawesome-free/svgs/solid/torah.svg +1 -0
  1857. package/static/vendor/fontawesome-free/svgs/solid/torii-gate.svg +1 -0
  1858. package/static/vendor/fontawesome-free/svgs/solid/tractor.svg +1 -0
  1859. package/static/vendor/fontawesome-free/svgs/solid/trademark.svg +1 -0
  1860. package/static/vendor/fontawesome-free/svgs/solid/traffic-light.svg +1 -0
  1861. package/static/vendor/fontawesome-free/svgs/solid/trailer.svg +1 -0
  1862. package/static/vendor/fontawesome-free/svgs/solid/train.svg +1 -0
  1863. package/static/vendor/fontawesome-free/svgs/solid/tram.svg +1 -0
  1864. package/static/vendor/fontawesome-free/svgs/solid/transgender-alt.svg +1 -0
  1865. package/static/vendor/fontawesome-free/svgs/solid/transgender.svg +1 -0
  1866. package/static/vendor/fontawesome-free/svgs/solid/trash-alt.svg +1 -0
  1867. package/static/vendor/fontawesome-free/svgs/solid/trash-restore-alt.svg +1 -0
  1868. package/static/vendor/fontawesome-free/svgs/solid/trash-restore.svg +1 -0
  1869. package/static/vendor/fontawesome-free/svgs/solid/trash.svg +1 -0
  1870. package/static/vendor/fontawesome-free/svgs/solid/tree.svg +1 -0
  1871. package/static/vendor/fontawesome-free/svgs/solid/trophy.svg +1 -0
  1872. package/static/vendor/fontawesome-free/svgs/solid/truck-loading.svg +1 -0
  1873. package/static/vendor/fontawesome-free/svgs/solid/truck-monster.svg +1 -0
  1874. package/static/vendor/fontawesome-free/svgs/solid/truck-moving.svg +1 -0
  1875. package/static/vendor/fontawesome-free/svgs/solid/truck-pickup.svg +1 -0
  1876. package/static/vendor/fontawesome-free/svgs/solid/truck.svg +1 -0
  1877. package/static/vendor/fontawesome-free/svgs/solid/tshirt.svg +1 -0
  1878. package/static/vendor/fontawesome-free/svgs/solid/tty.svg +1 -0
  1879. package/static/vendor/fontawesome-free/svgs/solid/tv.svg +1 -0
  1880. package/static/vendor/fontawesome-free/svgs/solid/umbrella-beach.svg +1 -0
  1881. package/static/vendor/fontawesome-free/svgs/solid/umbrella.svg +1 -0
  1882. package/static/vendor/fontawesome-free/svgs/solid/underline.svg +1 -0
  1883. package/static/vendor/fontawesome-free/svgs/solid/undo-alt.svg +1 -0
  1884. package/static/vendor/fontawesome-free/svgs/solid/undo.svg +1 -0
  1885. package/static/vendor/fontawesome-free/svgs/solid/universal-access.svg +1 -0
  1886. package/static/vendor/fontawesome-free/svgs/solid/university.svg +1 -0
  1887. package/static/vendor/fontawesome-free/svgs/solid/unlink.svg +1 -0
  1888. package/static/vendor/fontawesome-free/svgs/solid/unlock-alt.svg +1 -0
  1889. package/static/vendor/fontawesome-free/svgs/solid/unlock.svg +1 -0
  1890. package/static/vendor/fontawesome-free/svgs/solid/upload.svg +1 -0
  1891. package/static/vendor/fontawesome-free/svgs/solid/user-alt-slash.svg +1 -0
  1892. package/static/vendor/fontawesome-free/svgs/solid/user-alt.svg +1 -0
  1893. package/static/vendor/fontawesome-free/svgs/solid/user-astronaut.svg +1 -0
  1894. package/static/vendor/fontawesome-free/svgs/solid/user-check.svg +1 -0
  1895. package/static/vendor/fontawesome-free/svgs/solid/user-circle.svg +1 -0
  1896. package/static/vendor/fontawesome-free/svgs/solid/user-clock.svg +1 -0
  1897. package/static/vendor/fontawesome-free/svgs/solid/user-cog.svg +1 -0
  1898. package/static/vendor/fontawesome-free/svgs/solid/user-edit.svg +1 -0
  1899. package/static/vendor/fontawesome-free/svgs/solid/user-friends.svg +1 -0
  1900. package/static/vendor/fontawesome-free/svgs/solid/user-graduate.svg +1 -0
  1901. package/static/vendor/fontawesome-free/svgs/solid/user-injured.svg +1 -0
  1902. package/static/vendor/fontawesome-free/svgs/solid/user-lock.svg +1 -0
  1903. package/static/vendor/fontawesome-free/svgs/solid/user-md.svg +1 -0
  1904. package/static/vendor/fontawesome-free/svgs/solid/user-minus.svg +1 -0
  1905. package/static/vendor/fontawesome-free/svgs/solid/user-ninja.svg +1 -0
  1906. package/static/vendor/fontawesome-free/svgs/solid/user-nurse.svg +1 -0
  1907. package/static/vendor/fontawesome-free/svgs/solid/user-plus.svg +1 -0
  1908. package/static/vendor/fontawesome-free/svgs/solid/user-secret.svg +1 -0
  1909. package/static/vendor/fontawesome-free/svgs/solid/user-shield.svg +1 -0
  1910. package/static/vendor/fontawesome-free/svgs/solid/user-slash.svg +1 -0
  1911. package/static/vendor/fontawesome-free/svgs/solid/user-tag.svg +1 -0
  1912. package/static/vendor/fontawesome-free/svgs/solid/user-tie.svg +1 -0
  1913. package/static/vendor/fontawesome-free/svgs/solid/user-times.svg +1 -0
  1914. package/static/vendor/fontawesome-free/svgs/solid/user.svg +1 -0
  1915. package/static/vendor/fontawesome-free/svgs/solid/users-cog.svg +1 -0
  1916. package/static/vendor/fontawesome-free/svgs/solid/users-slash.svg +1 -0
  1917. package/static/vendor/fontawesome-free/svgs/solid/users.svg +1 -0
  1918. package/static/vendor/fontawesome-free/svgs/solid/utensil-spoon.svg +1 -0
  1919. package/static/vendor/fontawesome-free/svgs/solid/utensils.svg +1 -0
  1920. package/static/vendor/fontawesome-free/svgs/solid/vector-square.svg +1 -0
  1921. package/static/vendor/fontawesome-free/svgs/solid/venus-double.svg +1 -0
  1922. package/static/vendor/fontawesome-free/svgs/solid/venus-mars.svg +1 -0
  1923. package/static/vendor/fontawesome-free/svgs/solid/venus.svg +1 -0
  1924. package/static/vendor/fontawesome-free/svgs/solid/vest-patches.svg +1 -0
  1925. package/static/vendor/fontawesome-free/svgs/solid/vest.svg +1 -0
  1926. package/static/vendor/fontawesome-free/svgs/solid/vial.svg +1 -0
  1927. package/static/vendor/fontawesome-free/svgs/solid/vials.svg +1 -0
  1928. package/static/vendor/fontawesome-free/svgs/solid/video-slash.svg +1 -0
  1929. package/static/vendor/fontawesome-free/svgs/solid/video.svg +1 -0
  1930. package/static/vendor/fontawesome-free/svgs/solid/vihara.svg +1 -0
  1931. package/static/vendor/fontawesome-free/svgs/solid/virus-slash.svg +1 -0
  1932. package/static/vendor/fontawesome-free/svgs/solid/virus.svg +1 -0
  1933. package/static/vendor/fontawesome-free/svgs/solid/viruses.svg +1 -0
  1934. package/static/vendor/fontawesome-free/svgs/solid/voicemail.svg +1 -0
  1935. package/static/vendor/fontawesome-free/svgs/solid/volleyball-ball.svg +1 -0
  1936. package/static/vendor/fontawesome-free/svgs/solid/volume-down.svg +1 -0
  1937. package/static/vendor/fontawesome-free/svgs/solid/volume-mute.svg +1 -0
  1938. package/static/vendor/fontawesome-free/svgs/solid/volume-off.svg +1 -0
  1939. package/static/vendor/fontawesome-free/svgs/solid/volume-up.svg +1 -0
  1940. package/static/vendor/fontawesome-free/svgs/solid/vote-yea.svg +1 -0
  1941. package/static/vendor/fontawesome-free/svgs/solid/vr-cardboard.svg +1 -0
  1942. package/static/vendor/fontawesome-free/svgs/solid/walking.svg +1 -0
  1943. package/static/vendor/fontawesome-free/svgs/solid/wallet.svg +1 -0
  1944. package/static/vendor/fontawesome-free/svgs/solid/warehouse.svg +1 -0
  1945. package/static/vendor/fontawesome-free/svgs/solid/water.svg +1 -0
  1946. package/static/vendor/fontawesome-free/svgs/solid/wave-square.svg +1 -0
  1947. package/static/vendor/fontawesome-free/svgs/solid/weight-hanging.svg +1 -0
  1948. package/static/vendor/fontawesome-free/svgs/solid/weight.svg +1 -0
  1949. package/static/vendor/fontawesome-free/svgs/solid/wheelchair.svg +1 -0
  1950. package/static/vendor/fontawesome-free/svgs/solid/wifi.svg +1 -0
  1951. package/static/vendor/fontawesome-free/svgs/solid/wind.svg +1 -0
  1952. package/static/vendor/fontawesome-free/svgs/solid/window-close.svg +1 -0
  1953. package/static/vendor/fontawesome-free/svgs/solid/window-maximize.svg +1 -0
  1954. package/static/vendor/fontawesome-free/svgs/solid/window-minimize.svg +1 -0
  1955. package/static/vendor/fontawesome-free/svgs/solid/window-restore.svg +1 -0
  1956. package/static/vendor/fontawesome-free/svgs/solid/wine-bottle.svg +1 -0
  1957. package/static/vendor/fontawesome-free/svgs/solid/wine-glass-alt.svg +1 -0
  1958. package/static/vendor/fontawesome-free/svgs/solid/wine-glass.svg +1 -0
  1959. package/static/vendor/fontawesome-free/svgs/solid/won-sign.svg +1 -0
  1960. package/static/vendor/fontawesome-free/svgs/solid/wrench.svg +1 -0
  1961. package/static/vendor/fontawesome-free/svgs/solid/x-ray.svg +1 -0
  1962. package/static/vendor/fontawesome-free/svgs/solid/yen-sign.svg +1 -0
  1963. package/static/vendor/fontawesome-free/svgs/solid/yin-yang.svg +1 -0
  1964. package/static/vendor/fontawesome-free/webfonts/fa-brands-400.eot +0 -0
  1965. package/static/vendor/fontawesome-free/webfonts/fa-brands-400.svg +3717 -0
  1966. package/static/vendor/fontawesome-free/webfonts/fa-brands-400.ttf +0 -0
  1967. package/static/vendor/fontawesome-free/webfonts/fa-brands-400.woff +0 -0
  1968. package/static/vendor/fontawesome-free/webfonts/fa-brands-400.woff2 +0 -0
  1969. package/static/vendor/fontawesome-free/webfonts/fa-regular-400.eot +0 -0
  1970. package/static/vendor/fontawesome-free/webfonts/fa-regular-400.svg +801 -0
  1971. package/static/vendor/fontawesome-free/webfonts/fa-regular-400.ttf +0 -0
  1972. package/static/vendor/fontawesome-free/webfonts/fa-regular-400.woff +0 -0
  1973. package/static/vendor/fontawesome-free/webfonts/fa-regular-400.woff2 +0 -0
  1974. package/static/vendor/fontawesome-free/webfonts/fa-solid-900.eot +0 -0
  1975. package/static/vendor/fontawesome-free/webfonts/fa-solid-900.svg +5034 -0
  1976. package/static/vendor/fontawesome-free/webfonts/fa-solid-900.ttf +0 -0
  1977. package/static/vendor/fontawesome-free/webfonts/fa-solid-900.woff +0 -0
  1978. package/static/vendor/fontawesome-free/webfonts/fa-solid-900.woff2 +0 -0
  1979. package/static/vendor/handlebars/handlebars.min-v4.7.7.js +29 -0
  1980. package/static/vendor/jquery/jquery.js +10881 -0
  1981. package/static/vendor/jquery/jquery.min.js +2 -0
  1982. package/static/vendor/jquery/jquery.min.map +1 -0
  1983. package/static/vendor/jquery/jquery.slim.js +8782 -0
  1984. package/static/vendor/jquery/jquery.slim.min.js +2 -0
  1985. package/static/vendor/jquery/jquery.slim.min.map +1 -0
  1986. package/static/vendor/jquery-easing/jquery.easing.min.js +1 -0
  1987. package/systemd/emailengine.service +11 -3
  1988. package/systemd/nginx-proxy.conf +1 -1
  1989. package/test/api-test.js +899 -0
  1990. package/test/bounce-test.js +151 -0
  1991. package/test/fixtures/bounces/163.eml +2521 -0
  1992. package/test/fixtures/bounces/fastmail.eml +242 -0
  1993. package/test/fixtures/bounces/gmail.eml +252 -0
  1994. package/test/fixtures/bounces/hotmail.eml +655 -0
  1995. package/test/fixtures/bounces/mailru.eml +121 -0
  1996. package/test/fixtures/bounces/outlook.eml +1107 -0
  1997. package/test/fixtures/bounces/postfix.eml +101 -0
  1998. package/test/fixtures/bounces/rambler.eml +116 -0
  1999. package/test/fixtures/bounces/workmail.eml +142 -0
  2000. package/test/fixtures/bounces/yahoo.eml +139 -0
  2001. package/test/fixtures/bounces/zoho.eml +83 -0
  2002. package/test/fixtures/bounces/zonemta.eml +100 -0
  2003. package/test/oauth2-apps-test.js +301 -0
  2004. package/test/sendonly-test.js +160 -0
  2005. package/test/test-config.js +34 -0
  2006. package/test/webhooks-server.js +39 -0
  2007. package/translations/README.md +16 -0
  2008. package/translations/de.mo +0 -0
  2009. package/translations/de.po +335 -0
  2010. package/translations/en.mo +0 -0
  2011. package/translations/en.po +310 -0
  2012. package/translations/et.mo +0 -0
  2013. package/translations/et.po +331 -0
  2014. package/translations/fr.mo +0 -0
  2015. package/translations/fr.po +333 -0
  2016. package/translations/ja.mo +0 -0
  2017. package/translations/ja.po +322 -0
  2018. package/translations/locales.json +43 -0
  2019. package/translations/messages.pot +323 -0
  2020. package/translations/nl.mo +0 -0
  2021. package/translations/nl.po +325 -0
  2022. package/translations/pl.mo +0 -0
  2023. package/translations/pl.po +328 -0
  2024. package/update-info.sh +10 -0
  2025. package/views/account/login.hbs +54 -0
  2026. package/views/account/password.hbs +88 -0
  2027. package/views/account/security.hbs +269 -0
  2028. package/views/account/totp.hbs +30 -0
  2029. package/views/accounts/account.hbs +1254 -0
  2030. package/views/accounts/browse.hbs +102 -0
  2031. package/views/accounts/edit.hbs +332 -0
  2032. package/views/accounts/index.hbs +143 -0
  2033. package/views/accounts/register/imap-server.hbs +507 -0
  2034. package/views/accounts/register/imap.hbs +56 -0
  2035. package/views/accounts/register/index.hbs +52 -0
  2036. package/views/arena/index.hbs +4 -0
  2037. package/views/config/ai.hbs +820 -0
  2038. package/views/config/document-store/chat.hbs +362 -0
  2039. package/views/config/document-store/index.hbs +231 -0
  2040. package/views/config/document-store/mappings/index.hbs +116 -0
  2041. package/views/config/document-store/mappings/new.hbs +95 -0
  2042. package/views/config/document-store/pre-processing/index.hbs +459 -0
  2043. package/views/config/imap-proxy.hbs +479 -0
  2044. package/views/config/license.hbs +256 -0
  2045. package/views/config/logging.hbs +61 -0
  2046. package/views/config/network.hbs +334 -0
  2047. package/views/config/oauth/app.hbs +309 -0
  2048. package/views/config/oauth/edit.hbs +92 -0
  2049. package/views/config/oauth/index.hbs +150 -0
  2050. package/views/config/oauth/new.hbs +90 -0
  2051. package/views/config/oauth.hbs +354 -0
  2052. package/views/config/service-preview.hbs +14 -0
  2053. package/views/config/service.hbs +718 -0
  2054. package/views/config/smtp.hbs +525 -0
  2055. package/views/config/webhooks.hbs +404 -0
  2056. package/views/dashboard.hbs +315 -0
  2057. package/views/error.hbs +6 -1
  2058. package/views/gateways/edit.hbs +52 -0
  2059. package/views/gateways/gateway.hbs +120 -0
  2060. package/views/gateways/index.hbs +152 -0
  2061. package/views/gateways/new.hbs +61 -0
  2062. package/views/index.hbs +21 -0
  2063. package/views/internals/index.hbs +170 -0
  2064. package/views/internals/thread.hbs +143 -0
  2065. package/views/layout/app.hbs +516 -0
  2066. package/views/layout/login.hbs +78 -0
  2067. package/views/layout/main.hbs +67 -0
  2068. package/views/layout/public.hbs +90 -0
  2069. package/views/legal.hbs +83 -0
  2070. package/views/license.hbs +5 -0
  2071. package/views/partials/accounts_header.hbs +6 -0
  2072. package/views/partials/add_account_modal.hbs +60 -0
  2073. package/views/partials/address_list.hbs +37 -0
  2074. package/views/partials/alerts.hbs +33 -0
  2075. package/views/partials/document_store_header.hbs +52 -0
  2076. package/views/partials/editor_scope_info.hbs +10 -0
  2077. package/views/partials/gateway_form.hbs +65 -0
  2078. package/views/partials/gateway_js.hbs +90 -0
  2079. package/views/partials/gateways_header.hbs +6 -0
  2080. package/views/partials/oauth_config_header.hbs +10 -0
  2081. package/views/partials/oauth_form.hbs +1204 -0
  2082. package/views/partials/scope_info.hbs +134 -0
  2083. package/views/partials/security_header.hbs +11 -0
  2084. package/views/partials/side_menu.hbs +114 -0
  2085. package/views/partials/template_form.hbs +121 -0
  2086. package/views/partials/templates_header.hbs +6 -0
  2087. package/views/partials/test_send.hbs +327 -0
  2088. package/views/partials/tokens_header.hbs +6 -0
  2089. package/views/partials/webhook_form.hbs +151 -0
  2090. package/views/partials/webhooks_editor_functions.hbs +372 -0
  2091. package/views/partials/webhooks_header.hbs +6 -0
  2092. package/views/redirect.hbs +1 -0
  2093. package/views/swagger/index.hbs +76 -0
  2094. package/views/templates/edit.hbs +87 -0
  2095. package/views/templates/index.hbs +208 -0
  2096. package/views/templates/new.hbs +85 -0
  2097. package/views/templates/template.hbs +423 -0
  2098. package/views/tokens/index.hbs +207 -0
  2099. package/views/tokens/new.hbs +230 -0
  2100. package/views/unsubscribe.hbs +93 -0
  2101. package/views/upgrade.hbs +56 -0
  2102. package/views/webhooks/edit.hbs +31 -0
  2103. package/views/webhooks/index.hbs +144 -0
  2104. package/views/webhooks/new.hbs +27 -0
  2105. package/views/webhooks/webhook.hbs +265 -0
  2106. package/winconf.js +93 -0
  2107. package/workers/api.js +8246 -1256
  2108. package/workers/documents.js +1120 -0
  2109. package/workers/imap-proxy.js +91 -0
  2110. package/workers/imap.js +552 -161
  2111. package/workers/smtp.js +355 -82
  2112. package/workers/submit.js +319 -54
  2113. package/workers/webhooks.js +542 -80
  2114. package/.eslintrc +0 -14
  2115. package/.github/FUNDING.yml +0 -4
  2116. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -38
  2117. package/.github/ISSUE_TEMPLATE/feature_request.md +0 -20
  2118. package/LICENSE.txt +0 -661
  2119. package/examples/api.md +0 -137
  2120. package/lib/connection.js +0 -1768
  2121. package/lib/lua/z-push.lua +0 -14
  2122. package/lib/mailbox.js +0 -1545
  2123. package/license-report-config.json +0 -3
  2124. package/licenses.txt +0 -37
  2125. package/static/bootstrap-4.6.0-dist/css/bootstrap-grid.css.map +0 -1
  2126. package/static/bootstrap-4.6.0-dist/css/bootstrap-reboot.css.map +0 -1
  2127. package/static/bootstrap-4.6.0-dist/css/bootstrap-reboot.min.css.map +0 -1
  2128. package/static/bootstrap-4.6.0-dist/css/bootstrap.css.map +0 -1
  2129. package/static/bootstrap-4.6.0-dist/css/bootstrap.min.css +0 -7
  2130. package/static/bootstrap-4.6.0-dist/css/bootstrap.min.css.map +0 -1
  2131. package/static/bootstrap-4.6.0-dist/js/bootstrap.bundle.js +0 -7045
  2132. package/static/bootstrap-4.6.0-dist/js/bootstrap.bundle.js.map +0 -1
  2133. package/static/bootstrap-4.6.0-dist/js/bootstrap.bundle.min.js +0 -7
  2134. package/static/bootstrap-4.6.0-dist/js/bootstrap.bundle.min.js.map +0 -1
  2135. package/static/bootstrap-4.6.0-dist/js/bootstrap.js.map +0 -1
  2136. package/static/bootstrap-4.6.0-dist/js/bootstrap.min.js +0 -7
  2137. package/static/bootstrap-4.6.0-dist/js/bootstrap.min.js.map +0 -1
  2138. package/static/js/emailengine.js +0 -581
  2139. package/workers/arena.js +0 -89
@@ -0,0 +1,3721 @@
1
+ 'use strict';
2
+
3
+ const crypto = require('crypto');
4
+ const {
5
+ serialize,
6
+ unserialize,
7
+ compareExisting,
8
+ normalizePath,
9
+ download,
10
+ filterEmptyObjectValues,
11
+ validUidValidity,
12
+ calculateFetchBackoff,
13
+ readEnvValue
14
+ } = require('../../tools');
15
+ const msgpack = require('msgpack5')();
16
+ const he = require('he');
17
+ const libmime = require('libmime');
18
+ const settings = require('../../settings');
19
+ const config = require('@zone-eu/wild-config');
20
+ const { bounceDetect } = require('../../bounce-detect');
21
+ const { arfDetect } = require('../../arf-detect');
22
+ const appendList = require('../../append-list');
23
+ const { mimeHtml } = require('@postalsys/email-text-tools');
24
+ const simpleParser = require('mailparser').simpleParser;
25
+ const ical = require('ical.js');
26
+ const addressparser = require('nodemailer/lib/addressparser');
27
+ const { llmPreProcess } = require('../../llm-pre-process');
28
+
29
+ const { getESClient } = require('../../document-store');
30
+
31
+ const {
32
+ MESSAGE_NEW_NOTIFY,
33
+ MAILBOX_DELETED_NOTIFY,
34
+ MESSAGE_DELETED_NOTIFY,
35
+ MESSAGE_UPDATED_NOTIFY,
36
+ MESSAGE_MISSING_NOTIFY,
37
+ MAILBOX_RESET_NOTIFY,
38
+ MAILBOX_NEW_NOTIFY,
39
+ EMAIL_BOUNCE_NOTIFY,
40
+ EMAIL_COMPLAINT_NOTIFY,
41
+ REDIS_PREFIX,
42
+ MAX_INLINE_ATTACHMENT_SIZE,
43
+ MAX_ALLOWED_DOWNLOAD_SIZE,
44
+ DEFAULT_FETCH_BATCH_SIZE,
45
+ MAILBOX_HASH
46
+ } = require('../../consts');
47
+
48
+ // Configurable batch size for fetching messages (default: 250)
49
+ const FETCH_BATCH_SIZE = Number(readEnvValue('EENGINE_FETCH_BATCH_SIZE') || config.service.fetchBatchSize) || DEFAULT_FETCH_BATCH_SIZE;
50
+
51
+ // Do not check for flag updates using full sync more often than this value (30 minutes)
52
+ const FULL_SYNC_DELAY = 30 * 60 * 1000;
53
+
54
+ /**
55
+ * Calculates the next range of sequence numbers to fetch based on the last fetched range
56
+ * @param {Number} totalMessages - Total number of messages in the mailbox
57
+ * @param {String} lastRange - Last fetched range in format "start:end" or "start:*"
58
+ * @returns {String|false} Next range to fetch or false if no more messages
59
+ */
60
+ function getFetchRange(totalMessages, lastRange) {
61
+ let lastEndMarker = lastRange ? lastRange.split(':').pop() : false;
62
+ if (lastEndMarker === '*') {
63
+ // Already fetched to the end
64
+ return false;
65
+ }
66
+ let lastSeq = lastRange ? Number(lastEndMarker) : 0;
67
+ let startSeq = lastSeq + 1;
68
+ if (startSeq > totalMessages) {
69
+ // No more messages to fetch
70
+ return false;
71
+ }
72
+ let endMarker = startSeq + FETCH_BATCH_SIZE - 1;
73
+ if (endMarker >= totalMessages) {
74
+ // Use * to fetch to the end
75
+ endMarker = '*';
76
+ }
77
+ return `${startSeq}:${endMarker}`;
78
+ }
79
+
80
+ /**
81
+ * Represents a single IMAP mailbox/folder and handles all operations on it
82
+ */
83
+ class Mailbox {
84
+ constructor(connection, entry) {
85
+ this.status = false;
86
+ this.connection = connection; // Parent connection object
87
+ this.path = entry.path; // Mailbox path (e.g., "INBOX", "Sent Mail")
88
+ this.listingEntry = entry; // Mailbox metadata from LIST command
89
+ this.syncDisabled = entry.syncDisabled; // Whether syncing is disabled for this mailbox
90
+
91
+ // Child logger with mailbox context
92
+ this.logger = this.connection.mainLogger.child({
93
+ sub: 'mailbox',
94
+ path: this.path
95
+ });
96
+
97
+ // Indexing strategy: 'full' maintains complete message list, 'fast' only tracks new messages
98
+ this.imapIndexer = connection.imapIndexer;
99
+
100
+ // Gmail-specific flags
101
+ this.isGmail = connection.isGmail;
102
+ this.isAllMail = this.isGmail && this.listingEntry.specialUse === '\\All';
103
+
104
+ // LarkSuite mail has unreliable ENVELOPE responses, needs special handling
105
+ this.isLarkSuite = connection.isLarkSuite;
106
+
107
+ this.selected = false; // Whether this mailbox is currently selected
108
+ // Does the mailbox open happen before or after initial syncing
109
+ this.previouslyConnected = false;
110
+
111
+ // Generate unique Redis key for this mailbox based on path hash
112
+ this.redisKey = BigInt('0x' + crypto.createHash(MAILBOX_HASH).update(normalizePath(this.path)).digest('hex')).toString(36);
113
+
114
+ this.runPartialSyncTimer = false; // Timer for delayed partial sync after EXISTS
115
+
116
+ this.synced = false; // Whether initial sync is complete
117
+ this.syncing = false; // Whether currently syncing
118
+ }
119
+
120
+ /**
121
+ * Gets current mailbox status from IMAP connection
122
+ * @param {Object} connectionClient - IMAP client to use (defaults to main connection)
123
+ * @returns {Object} Status object with path, highestModseq, uidValidity, uidNext, messages
124
+ */
125
+ getMailboxStatus(connectionClient) {
126
+ connectionClient = connectionClient || this.connection.imapClient;
127
+ if (!connectionClient) {
128
+ throw new Error('IMAP connection not available');
129
+ }
130
+
131
+ let mailboxInfo = connectionClient.mailbox;
132
+
133
+ let status = {
134
+ path: this.path
135
+ };
136
+
137
+ // MODSEQ for CONDSTORE extension (change tracking)
138
+ status.highestModseq = mailboxInfo.highestModseq ? mailboxInfo.highestModseq : false;
139
+ // UIDVALIDITY changes when mailbox is recreated
140
+ status.uidValidity = validUidValidity(mailboxInfo.uidValidity) ? mailboxInfo.uidValidity : false;
141
+ // Next UID to be assigned
142
+ status.uidNext = mailboxInfo.uidNext ? mailboxInfo.uidNext : false;
143
+ // Total message count
144
+ status.messages = mailboxInfo.exists ? mailboxInfo.exists : 0;
145
+
146
+ return status;
147
+ }
148
+
149
+ /**
150
+ * Loads last known mailbox state from Redis
151
+ * @returns {Object} mailbox state with stored metadata
152
+ */
153
+ async getStoredStatus() {
154
+ let data = await this.connection.redis.hgetall(this.getMailboxKey());
155
+ data = data || {};
156
+ return {
157
+ path: data.path || this.path,
158
+ uidValidity: validUidValidity(data.uidValidity) ? BigInt(data.uidValidity) : false,
159
+ highestModseq: data.highestModseq && !isNaN(data.highestModseq) ? BigInt(data.highestModseq) : false,
160
+ messages: data.messages && !isNaN(data.messages) ? Number(data.messages) : false,
161
+ uidNext: data.uidNext && !isNaN(data.uidNext) ? Number(data.uidNext) : false,
162
+ // First UID when mailbox was initially synced (used to detect old messages)
163
+ initialUidNext: data.initialUidNext && !isNaN(data.initialUidNext) ? Number(data.initialUidNext) : false,
164
+ noInferiors: !!data.noInferiors,
165
+ lastFullSync: data.lastFullSync ? new Date(data.lastFullSync) : false
166
+ };
167
+ }
168
+
169
+ /**
170
+ * Updates known mailbox state in Redis
171
+ * @param {Object} data - Status data to store
172
+ */
173
+ async updateStoredStatus(data) {
174
+ if (!data || typeof data !== 'object') {
175
+ return false;
176
+ }
177
+
178
+ // Convert all values to strings for Redis storage
179
+ let list = Object.keys(data)
180
+ .map(key => {
181
+ switch (key) {
182
+ case 'path':
183
+ case 'uidValidity':
184
+ case 'highestModseq':
185
+ case 'messages':
186
+ case 'uidNext':
187
+ return [key, data[key].toString()];
188
+
189
+ case 'lastFullSync':
190
+ return [key, data[key].toISOString()];
191
+ }
192
+ })
193
+ .filter(entry => entry);
194
+
195
+ if (!list.length) {
196
+ return;
197
+ }
198
+
199
+ let op = this.connection.redis.multi();
200
+
201
+ // Store initial UID on first sync
202
+ if (data.uidNext) {
203
+ op = op.hSetNew(this.getMailboxKey(), 'initialUidNext', data.uidNext.toString());
204
+ }
205
+
206
+ await op.hmset(this.getMailboxKey(), Object.fromEntries(list)).exec();
207
+ }
208
+
209
+ /**
210
+ * Sets message entry object in Redis sorted set. Entries are ordered by `uid` property
211
+ * @param {Object} data - Message data with uid, flags, etc.
212
+ * @returns {Number} Sequence number for the added entry
213
+ */
214
+ async entryListSet(data) {
215
+ if (isNaN(data.uid)) {
216
+ return null;
217
+ }
218
+
219
+ // Store in sorted set with UID as score for efficient range queries
220
+ return await this.connection.redis.zSet(this.getMessagesKey(), Number(data.uid), serialize(data));
221
+ }
222
+
223
+ /**
224
+ * Retrieves message entry object for the provided sequence value or UID
225
+ * @param {Number} seq - Sequence number or UID
226
+ * @param {Object} options - Options object
227
+ * @param {Boolean} options.uid - If true, seq is treated as UID
228
+ * @returns {Object|null} Message entry object with uid, entry, and seq
229
+ */
230
+ async entryListGet(seq, options) {
231
+ let range = Number(seq);
232
+ options = options || {};
233
+ // Use UID-based or sequence-based retrieval
234
+ let command = options.uid ? 'zGetByUidBuffer' : 'zGetBuffer';
235
+ let response = await this.connection.redis[command](this.getMessagesKey(), range);
236
+ if (response) {
237
+ try {
238
+ return {
239
+ uid: Number(response[0]),
240
+ entry: unserialize(response[1]),
241
+ seq: Number(response[2])
242
+ };
243
+ } catch (err) {
244
+ return null;
245
+ }
246
+ }
247
+ return null;
248
+ }
249
+
250
+ /**
251
+ * Deletes entry from message list for the provided sequence value or UID
252
+ * @param {Number} seq - Sequence number (0 if using UID)
253
+ * @param {Number} uid - UID number (0 if using sequence)
254
+ * @returns {Object|null} Message entry object that was deleted
255
+ */
256
+ async entryListExpunge(seq, uid) {
257
+ // Custom Redis command that removes and returns the entry
258
+ let response = await this.connection.redis.zExpungeBuffer(this.getMessagesKey(), this.getMailboxKey(), seq || 0, uid || 0);
259
+ if (response) {
260
+ try {
261
+ return unserialize(response[1]);
262
+ } catch (err) {
263
+ return null;
264
+ }
265
+ }
266
+ return null;
267
+ }
268
+
269
+ /**
270
+ * Checks if this mailbox is currently selected in the IMAP connection
271
+ * @returns {Boolean} True if selected
272
+ */
273
+ isSelected() {
274
+ return (
275
+ this.selected &&
276
+ this.connection.imapClient &&
277
+ this.connection.imapClient.mailbox &&
278
+ normalizePath(this.connection.imapClient.mailbox.path) === normalizePath(this.path)
279
+ );
280
+ }
281
+
282
+ // Redis key generators for different data types
283
+
284
+ /**
285
+ * Gets Redis key for storing message list (sorted set)
286
+ */
287
+ getMessagesKey() {
288
+ return `${REDIS_PREFIX}iam:${this.connection.account}:l:${this.redisKey}`;
289
+ }
290
+
291
+ /**
292
+ * Gets Redis key for storing mailbox metadata (hash)
293
+ */
294
+ getMailboxKey() {
295
+ return `${REDIS_PREFIX}iam:${this.connection.account}:h:${this.redisKey}`;
296
+ }
297
+
298
+ /**
299
+ * Gets Redis key for storing bounce information
300
+ */
301
+ getBounceKey() {
302
+ return `${REDIS_PREFIX}iar:b:${this.connection.account}`;
303
+ }
304
+
305
+ /**
306
+ * Gets Redis key for tracking seen messages (HyperLogLog)
307
+ */
308
+ getSeenMessagesKey() {
309
+ return `${REDIS_PREFIX}iar:s:${this.connection.account}`;
310
+ }
311
+
312
+ /**
313
+ * Gets Redis key for queued notifications (sorted set)
314
+ */
315
+ getNotificationsKey() {
316
+ return `${REDIS_PREFIX}iam:${this.connection.account}:n:${this.redisKey}`;
317
+ }
318
+
319
+ /**
320
+ * Starts IDLE mode to receive real-time updates
321
+ */
322
+ startIdle() {
323
+ if (!this.isSelected() || !this.connection.imapClient || this.connection.imapClient.idling) {
324
+ return;
325
+ }
326
+ this.connection.imapClient.idle().catch(err => {
327
+ this.logger.error({ msg: 'IDLE error', err });
328
+ });
329
+ }
330
+
331
+ /**
332
+ * Clears all mailbox records from Redis and notifies about deletion
333
+ * @param {Object} opts - Options
334
+ * @param {Boolean} opts.skipNotify - Skip sending deletion notification
335
+ */
336
+ async clear(opts) {
337
+ opts = opts || {};
338
+
339
+ clearTimeout(this.runPartialSyncTimer);
340
+
341
+ // Delete all Redis keys for this mailbox
342
+ await this.connection.redis.del(this.getMailboxKey());
343
+ await this.connection.redis.del(this.getMessagesKey());
344
+ await this.connection.redis.del(this.getNotificationsKey());
345
+
346
+ // Remove from connection's mailbox cache
347
+ this.connection.mailboxes.delete(normalizePath(this.path));
348
+
349
+ this.logger.debug({ msg: 'Deleted mailbox', path: this.listingEntry.path });
350
+
351
+ if (!opts.skipNotify) {
352
+ this.connection.notify(this, MAILBOX_DELETED_NOTIFY, {
353
+ path: this.listingEntry.path,
354
+ name: this.listingEntry.name,
355
+ specialUse: this.listingEntry.specialUse || false
356
+ });
357
+ }
358
+ }
359
+
360
+ /**
361
+ * Syncs mailbox without selecting it (using STATUS command)
362
+ * @param {Boolean} forceEmpty - Force sync even if mailbox appears unchanged
363
+ * @returns {Boolean} True if synced
364
+ */
365
+ async sync(forceEmpty) {
366
+ if (this.selected || !this.connection.imapClient) {
367
+ // expect current folder to be already synced
368
+ return false;
369
+ }
370
+
371
+ let status;
372
+ try {
373
+ // Get status without selecting the mailbox
374
+ status = await this.connection.imapClient.status(this.path, {
375
+ uidNext: true,
376
+ messages: true,
377
+ highestModseq: true,
378
+ uidValidity: true
379
+ });
380
+ } catch (err) {
381
+ if (err.code === 'NotFound') {
382
+ // folder is missing, refresh folder listing
383
+ await this.connection.getCurrentListing();
384
+ return;
385
+ } else {
386
+ throw err;
387
+ }
388
+ }
389
+
390
+ if (!status) {
391
+ // nothing to do here
392
+ return;
393
+ }
394
+
395
+ status.highestModseq = status.highestModseq || false;
396
+
397
+ if (this.syncDisabled) {
398
+ // only update counters, don't fetch messages
399
+ await this.updateStoredStatus(status);
400
+ return true;
401
+ }
402
+
403
+ // Check if we have unprocessed notifications that need to be sent
404
+ let hasQueuedNotifications = await this.connection.redis.exists(this.getNotificationsKey());
405
+
406
+ // Determine if we need to sync based on various conditions
407
+ if (!hasQueuedNotifications && !forceEmpty) {
408
+ let storedStatus = await this.getStoredStatus();
409
+ if (status.uidValidity === storedStatus.uidValidity) {
410
+ // Check if nothing has changed
411
+ if (
412
+ status.uidNext === storedStatus.uidNext &&
413
+ status.messages === storedStatus.messages &&
414
+ storedStatus.lastFullSync > new Date(Date.now() - FULL_SYNC_DELAY)
415
+ ) {
416
+ // no reason to sync - no new messages and recent full sync
417
+ return true;
418
+ }
419
+
420
+ // Check MODSEQ for CONDSTORE-enabled servers
421
+ if ((!status.messages && !storedStatus.messages) || (status.highestModseq && status.highestModseq === storedStatus.highestModseq)) {
422
+ // no reason to sync - empty or no changes
423
+ return true;
424
+ }
425
+ }
426
+ }
427
+
428
+ // Need to sync - create promise that resolves when sync is complete
429
+ let syncedPromise = new Promise((resolve, reject) => {
430
+ this.synced = resolve;
431
+ this.select(true).catch(err => reject(err));
432
+ });
433
+
434
+ await syncedPromise;
435
+ }
436
+
437
+ /**
438
+ * Selects mailbox and optionally starts IDLE
439
+ * @param {Boolean} skipIdle - Don't start IDLE after selecting
440
+ */
441
+ async select(skipIdle) {
442
+ const currentLock = this.connection.imapClient.currentLock;
443
+ // Avoid interfering with any active operations
444
+ if (currentLock) {
445
+ if (this.path === currentLock.path) {
446
+ // Already on the correct mailbox with an active lock
447
+ this.logger.trace({
448
+ msg: 'Skip extra lock on active mailbox',
449
+ activeLock: {
450
+ lockId: currentLock.lockId,
451
+ path: currentLock.path,
452
+ ...(currentLock.options?.description && { description: currentLock.options?.description })
453
+ }
454
+ });
455
+ return;
456
+ }
457
+ // Different mailbox is locked - queue this select to run after lock is released
458
+ // This ensures we don't interfere with ongoing operations
459
+ this.logger.trace({
460
+ msg: 'Queueing select - another mailbox is locked',
461
+ requestedPath: this.path,
462
+ lockedPath: currentLock.path,
463
+ activeLock: currentLock.lockId
464
+ });
465
+ }
466
+
467
+ // Get lock and wait our turn - this will queue if another operation is active
468
+ let lock = await this.getMailboxLock(null, { description: `Select mailbox: ${this.path}` });
469
+
470
+ // Check if we still need to select after getting the lock
471
+ // Another operation might have already selected this mailbox while we were waiting
472
+ if (this.connection.imapClient.mailbox && this.connection.imapClient.mailbox.path === this.path) {
473
+ this.logger.trace({
474
+ msg: 'Mailbox already selected after lock acquired',
475
+ path: this.path
476
+ });
477
+ lock.release();
478
+ return;
479
+ }
480
+
481
+ // Keep the lock briefly to ensure IDLE can start without interference
482
+ // Release after a short delay to allow IDLE to initialize
483
+ setTimeout(() => {
484
+ lock.release();
485
+ }, 100);
486
+
487
+ if (!skipIdle) {
488
+ // Do not wait until command finishes before proceeding
489
+ this.startIdle();
490
+ }
491
+ }
492
+
493
+ /**
494
+ * Acquires exclusive lock on mailbox to prevent concurrent operations
495
+ * @param {Object} connectionClient - IMAP client to use
496
+ * @param {Object} options - Lock options
497
+ * @returns {Object} Lock object with release() method
498
+ */
499
+ async getMailboxLock(connectionClient, options) {
500
+ connectionClient = connectionClient || this.connection.imapClient;
501
+
502
+ if (!connectionClient) {
503
+ throw new Error('IMAP connection not available');
504
+ }
505
+
506
+ let lock = await connectionClient.getMailboxLock(this.path, options || {});
507
+
508
+ // Reset idle timer when using main connection
509
+ if (connectionClient === this.connection.imapClient) {
510
+ clearTimeout(this.connection.completedTimer);
511
+ }
512
+
513
+ return lock;
514
+ }
515
+
516
+ /**
517
+ * Marks task as completed and potentially starts idle timer
518
+ * @param {Object} connectionClient - IMAP client that completed the task
519
+ */
520
+ onTaskCompleted(connectionClient) {
521
+ connectionClient = connectionClient || this.connection.imapClient;
522
+ if (connectionClient === this.connection.imapClient) {
523
+ this.connection.onTaskCompleted();
524
+ }
525
+ }
526
+
527
+ /**
528
+ * Helper to log IMAP events with proper formatting
529
+ * @param {String} msg - Log message
530
+ * @param {Object} event - Event data to log
531
+ */
532
+ logEvent(msg, event) {
533
+ const logObj = Object.assign({ msg }, event);
534
+ // Convert BigInts and Sets to loggable formats
535
+ Object.keys(logObj).forEach(key => {
536
+ if (typeof logObj[key] === 'bigint') {
537
+ logObj[key] = logObj[key].toString();
538
+ }
539
+ if (typeof logObj[key].has === 'function') {
540
+ logObj[key] = Array.from(logObj[key]);
541
+ }
542
+ });
543
+ this.logger.trace(logObj);
544
+ }
545
+
546
+ /**
547
+ * Handles untagged EXISTS response indicating new messages
548
+ * @param {Object} event - EXISTS event data
549
+ */
550
+ async onExists(event) {
551
+ this.logEvent('Untagged EXISTS', event);
552
+
553
+ // Debounce partial sync to avoid multiple syncs for rapid changes
554
+ clearTimeout(this.runPartialSyncTimer);
555
+ this.runPartialSyncTimer = setTimeout(() => {
556
+ this.runPartialSyncTimer = null;
557
+ this.shouldRunPartialSyncAfterExists()
558
+ .then(shouldRun => {
559
+ if (shouldRun) {
560
+ return this.partialSync();
561
+ }
562
+ return false;
563
+ })
564
+ .then(() => this.select())
565
+ .catch(err => this.logger.error({ msg: 'Sync error', err }));
566
+ }, 1000);
567
+ }
568
+
569
+ /**
570
+ * Handles untagged EXPUNGE/VANISHED response indicating deleted messages
571
+ * @param {Object} event - EXPUNGE event data
572
+ */
573
+ async onExpunge(event) {
574
+ const imapIndexer = this.imapIndexer;
575
+ event.imapIndexer = imapIndexer;
576
+ this.logEvent('Untagged EXPUNGE', event);
577
+
578
+ if (imapIndexer !== 'full') {
579
+ // ignore as we can not compare this value against the index
580
+ return null;
581
+ }
582
+
583
+ let deletedEntry;
584
+
585
+ if (event.seq) {
586
+ // * 123 EXPUNGE - sequence-based expunge
587
+ deletedEntry = await this.entryListExpunge(event.seq);
588
+ } else if (event.uid) {
589
+ // * VANISHED 123 - UID-based expunge (QRESYNC)
590
+ deletedEntry = await this.entryListExpunge(false, event.uid);
591
+ }
592
+
593
+ if (deletedEntry) {
594
+ await this.processDeleted(deletedEntry);
595
+ await this.markUpdated();
596
+ }
597
+ }
598
+
599
+ /**
600
+ * Handles untagged FETCH response indicating flag changes
601
+ * @param {Object} event - FETCH event data with flags
602
+ */
603
+ async onFlags(event) {
604
+ const imapIndexer = this.imapIndexer;
605
+ event.imapIndexer = imapIndexer;
606
+ this.logEvent('Untagged FETCH', event);
607
+
608
+ if (imapIndexer !== 'full') {
609
+ // ignore as we can not compare this value against the index
610
+ return null;
611
+ }
612
+
613
+ let storedMessage = await this.entryListGet(event.uid || event.seq, { uid: !!event.uid });
614
+ let changes;
615
+
616
+ // ignore Recent flag as it's session-specific
617
+ event.flags.delete('\\Recent');
618
+
619
+ if (!storedMessage) {
620
+ // New! There should not be new messages in a flags update.
621
+ // What should we do? Currently triggering partial sync.
622
+ return await this.onExists();
623
+ } else if ((changes = compareExisting(storedMessage.entry, event, ['flags']))) {
624
+ // Update stored flags
625
+ let messageData = storedMessage.entry;
626
+ messageData.flags = event.flags;
627
+ let seq = await this.entryListSet(messageData);
628
+
629
+ if (seq) {
630
+ await this.processChanges(storedMessage, changes);
631
+ }
632
+ }
633
+ }
634
+
635
+ /**
636
+ * Checks if partial sync should run after EXISTS event
637
+ * @returns {Boolean} True if message count differs from stored count
638
+ */
639
+ async shouldRunPartialSyncAfterExists() {
640
+ let storedStatus = await this.getStoredStatus();
641
+ let mailboxStatus = this.getMailboxStatus();
642
+ return mailboxStatus.messages !== storedStatus.messages;
643
+ }
644
+
645
+ /**
646
+ * Processes a deleted message - clears bounces and sends notification
647
+ * @param {Object} messageData - Deleted message data
648
+ */
649
+ async processDeleted(messageData) {
650
+ this.logger.debug({ msg: 'Deleted', uid: messageData.uid });
651
+
652
+ //FIXME: does not work as there is no messageId property
653
+ /*
654
+ if (messageData.messageId) {
655
+ try {
656
+ let deleted = await appendList.clear(this.connection.redis, this.getBounceKey(), messageData.messageId);
657
+ if (deleted) {
658
+ this.logger.error({
659
+ msg: 'Cleared bounce log for message',
660
+ id: messageData.id,
661
+ uid: messageData.uid,
662
+ messageId: messageData.messageId
663
+ });
664
+ }
665
+ } catch (err) {
666
+ this.logger.error({
667
+ msg: 'Failed to clear bounce log',
668
+ id: messageData.id,
669
+ uid: messageData.uid,
670
+ messageId: messageData.messageId,
671
+ err
672
+ });
673
+ }
674
+ }
675
+ */
676
+
677
+ // Generate packed UID for external reference
678
+ let packedUid = await this.connection.packUid(this, messageData.uid);
679
+ await this.connection.notify(this, MESSAGE_DELETED_NOTIFY, {
680
+ id: packedUid,
681
+ uid: messageData.uid
682
+ });
683
+
684
+ try {
685
+ // Remove from notifications queue since message no longer exists
686
+ await this.connection.redis.zremrangebyscore(this.getNotificationsKey(), messageData.uid, messageData.uid);
687
+ } catch (err) {
688
+ this.logger.error({ msg: 'Failed removing deleted message from notifications set', uid: messageData.uid, err });
689
+ }
690
+ }
691
+
692
+ /**
693
+ * Processes a new message - fetches details, detects bounces/complaints, sends notifications
694
+ * @param {Object} messageData - Basic message data (uid, flags, etc)
695
+ * @param {Object} options - Processing options
696
+ * @param {Boolean} canSync - Whether message should be synced to document store
697
+ * @param {Object} storedStatus - Current mailbox status
698
+ */
699
+ async processNew(messageData, options, canSync, storedStatus) {
700
+ this.logger.debug({ msg: 'New message', uid: messageData.uid, flags: Array.from(messageData.flags) });
701
+
702
+ options.skipLock = true;
703
+
704
+ // Handle header fetching options
705
+ let requestedHeaders = options.headers;
706
+ if (options.fetchHeaders) {
707
+ options.headers = options.fetchHeaders;
708
+ } else {
709
+ options.headers = 'headers' in options ? options.headers : false;
710
+ }
711
+
712
+ let messageInfo;
713
+
714
+ // Retry logic for messages that might not be immediately available (replication lag)
715
+ let missingDelay = 0;
716
+ let missingRetries = 0;
717
+ let maxRetries = 3;
718
+
719
+ while (!messageInfo) {
720
+ messageInfo = await this.getMessage(messageData, options);
721
+ if (!messageInfo) {
722
+ // NB! could be a replication lag with specific servers, so retry a few times
723
+ if (missingRetries < maxRetries) {
724
+ // Exponential backoff: 1.7^n seconds
725
+ let delay = Math.round(1000 * Math.pow(1.7, missingRetries));
726
+
727
+ this.logger.debug({ msg: 'Missing message', status: 'not found', uid: messageData.uid, missingRetries, missingDelay, nextRetry: delay });
728
+ await new Promise(r => setTimeout(r, delay));
729
+
730
+ missingRetries++;
731
+ missingDelay += delay;
732
+ } else {
733
+ this.logger.debug({ msg: 'Missing message', status: 'not found', uid: messageData.uid, missingRetries, missingDelay, nextRetry: null });
734
+ break;
735
+ }
736
+ }
737
+ }
738
+
739
+ if (!messageInfo) {
740
+ // Message not found after retries - send missing notification
741
+ let packedUid = await this.connection.packUid(this, messageData.uid);
742
+ await this.connection.notify(this, MESSAGE_MISSING_NOTIFY, {
743
+ id: packedUid,
744
+ uid: messageData.uid,
745
+ missingRetries,
746
+ missingDelay
747
+ });
748
+ return;
749
+ }
750
+
751
+ if (missingRetries) {
752
+ // Log retry statistics if message was eventually found
753
+ messageInfo.missingDelay = missingDelay;
754
+ messageInfo.missingRetries = missingRetries;
755
+
756
+ this.logger.debug({ msg: 'Missing message', status: 'found', uid: messageData.uid, missingRetries, missingDelay });
757
+ }
758
+
759
+ // Filter headers based on what was requested
760
+ if (options.headers && Array.isArray(requestedHeaders)) {
761
+ let filteredHeaders = {};
762
+ for (let key of Object.keys(messageInfo.headers)) {
763
+ if (requestedHeaders.includes(key)) {
764
+ filteredHeaders[key] = messageInfo.headers[key];
765
+ }
766
+ }
767
+ messageInfo.headers = filteredHeaders;
768
+ } else if (options.headers && requestedHeaders === false) {
769
+ delete messageInfo.headers;
770
+ }
771
+
772
+ // Check if message is too old to notify about
773
+ let date = new Date(messageInfo.date);
774
+ if (this.connection.notifyFrom && date < this.connection.notifyFrom && !canSync) {
775
+ // skip too old messages
776
+ return;
777
+ }
778
+
779
+ // Skip old messages in non-INBOX folders on reconnect
780
+ if (this.previouslyConnected && this.path !== 'INBOX' && !this.isAllMail && storedStatus.initialUidNext > messageData.uid) {
781
+ this.logger.debug({
782
+ msg: 'Skip old message',
783
+ action: 'webhook_ignore',
784
+ initialUidNext: storedStatus.initialUidNext,
785
+ id: messageInfo.id,
786
+ uid: messageInfo.uid,
787
+ connectCount: this.previouslyConnected
788
+ });
789
+ return;
790
+ }
791
+
792
+ let bounceNotifyInfo;
793
+ let complaintNotifyInfo;
794
+ let content;
795
+
796
+ // Check if this might be an ARF (Abuse Reporting Format) complaint
797
+ if (this.mightBeAComplaint(messageInfo)) {
798
+ try {
799
+ // Download relevant attachments for ARF parsing
800
+ for (let attachment of messageInfo.attachments) {
801
+ if (!['message/feedback-report', 'message/rfc822-headers', 'message/rfc822'].includes(attachment.contentType)) {
802
+ continue;
803
+ }
804
+
805
+ let buf = Buffer.from(attachment.id, 'base64url');
806
+ let part = buf.subarray(8).toString();
807
+
808
+ let { content: sourceStream } = await this.connection.imapClient.download(messageInfo.uid, part, {
809
+ uid: true,
810
+ // headers should fit into 1MB, don't need all contents
811
+ maxBytes: Math.min(1 * 1024 * 1024, MAX_ALLOWED_DOWNLOAD_SIZE),
812
+ chunkSize: options.chunkSize
813
+ });
814
+
815
+ if (sourceStream) {
816
+ Object.defineProperty(attachment, 'content', {
817
+ value: (await download(sourceStream)).toString(),
818
+ enumerable: false
819
+ });
820
+ }
821
+ }
822
+
823
+ // Parse ARF report
824
+ const report = await arfDetect(messageInfo);
825
+
826
+ if (report && report.arf && report.arf['original-rcpt-to'] && report.arf['original-rcpt-to'].length) {
827
+ // Valid complaint found - prepare notification data
828
+ let complaint = {};
829
+ for (let subKey of ['arf', 'headers']) {
830
+ for (let key of Object.keys(report[subKey])) {
831
+ if (!complaint[subKey]) {
832
+ complaint[subKey] = {};
833
+ }
834
+ // Convert kebab-case to camelCase
835
+ complaint[subKey][key.replace(/-(.)/g, (o, c) => c.toUpperCase())] = report[subKey][key];
836
+ }
837
+ }
838
+
839
+ complaintNotifyInfo = Object.assign({ complaintMessage: messageInfo.id }, complaint);
840
+
841
+ messageInfo.isComplaint = true;
842
+
843
+ if (complaint.headers && complaint.headers.messageId) {
844
+ messageInfo.relatedMessageId = complaint.headers.messageId;
845
+ }
846
+ }
847
+ } catch (err) {
848
+ this.logger.error({
849
+ msg: 'Failed to process ARF',
850
+ id: messageInfo.id,
851
+ uid: messageInfo.uid,
852
+ messageId: messageInfo.messageId,
853
+ err
854
+ });
855
+ }
856
+ }
857
+
858
+ // Check if this might be a DSN (Delivery Status Notification)
859
+ if (this.mightBeDSNResponse(messageInfo)) {
860
+ try {
861
+ let { content: sourceStream } = await this.connection.imapClient.download(messageInfo.uid, false, {
862
+ uid: true,
863
+ chunkSize: options.chunkSize,
864
+ maxBytes: MAX_ALLOWED_DOWNLOAD_SIZE
865
+ });
866
+
867
+ let parsed = await simpleParser(sourceStream, { keepDeliveryStatus: true });
868
+ if (parsed) {
869
+ content = { parsed };
870
+
871
+ // Extract delivery status information
872
+ let deliveryStatus = parsed.attachments.find(attachment => attachment.contentType === 'message/delivery-status');
873
+ if (deliveryStatus) {
874
+ let deliveryEntries = libmime.decodeHeaders((deliveryStatus.content || '').toString().trim());
875
+ let structured = {};
876
+
877
+ // Parse delivery status headers
878
+ for (let key of Object.keys(deliveryEntries)) {
879
+ if (!key) {
880
+ continue;
881
+ }
882
+ let displayKey = key.replace(/-(.)/g, (m, c) => c.toUpperCase());
883
+ let value = deliveryEntries[key].at(-1);
884
+ if (typeof value === 'string') {
885
+ // Parse structured values like "rfc822;example.com"
886
+ let m = value.match(/^([^\s;]+);/);
887
+ if (m) {
888
+ value = {
889
+ label: m[1],
890
+ value: value.substring(m[0].length).trim()
891
+ };
892
+ } else {
893
+ switch (key) {
894
+ case 'arrival-date': {
895
+ value.trim();
896
+ let date = new Date(value);
897
+ if (date.toString() !== 'Invalid Date') {
898
+ value = date.toISOString();
899
+ }
900
+ structured[displayKey] = value;
901
+ break;
902
+ }
903
+ default:
904
+ structured[displayKey] = value.trim();
905
+ }
906
+ }
907
+ } else {
908
+ // ???
909
+ structured[displayKey] = value;
910
+ }
911
+ }
912
+
913
+ // Only consider as delivery report if action indicates delivery or delay
914
+ if (/^delivered|^delayed/i.test(structured.action)) {
915
+ this.logger.debug({
916
+ msg: 'Detected delivery report',
917
+ id: messageInfo.id,
918
+ uid: messageInfo.uid,
919
+ messageId: messageInfo.messageId,
920
+ report: structured
921
+ });
922
+
923
+ messageInfo.deliveryReport = structured;
924
+ }
925
+ }
926
+ }
927
+ } catch (err) {
928
+ this.logger.error({
929
+ msg: 'Failed to process DSN',
930
+ id: messageInfo.id,
931
+ uid: messageInfo.uid,
932
+ messageId: messageInfo.messageId,
933
+ err
934
+ });
935
+ }
936
+ }
937
+
938
+ // Check if this could be a bounce message
939
+ if (this.mightBeABounce(messageInfo)) {
940
+ // Parse for bounce information
941
+ try {
942
+ if (!content) {
943
+ let result = await this.connection.imapClient.download(messageInfo.uid, false, {
944
+ uid: true,
945
+ chunkSize: options.chunkSize,
946
+ maxBytes: MAX_ALLOWED_DOWNLOAD_SIZE
947
+ });
948
+ content = result.content;
949
+ }
950
+
951
+ if (content) {
952
+ // Detect bounce details
953
+ let bounce = await bounceDetect(content);
954
+
955
+ if (bounce?.response?.message) {
956
+ try {
957
+ let bounceType = await this.connection.call({
958
+ cmd: 'bounceClassify',
959
+ data: {
960
+ message: bounce?.response?.message
961
+ },
962
+ timeout: 2 * 60 * 1000
963
+ });
964
+
965
+ if (bounceType?.label) {
966
+ bounce.response.category = bounceType.label;
967
+ }
968
+ if (bounceType?.action) {
969
+ bounce.response.recommendedAction = bounceType.action;
970
+ }
971
+ if (bounceType?.blocklist) {
972
+ bounce.response.blocklist = bounceType.blocklist;
973
+ }
974
+ if (bounceType?.retryAfter) {
975
+ bounce.response.retryAfter = bounceType.retryAfter;
976
+ }
977
+ } catch (err) {
978
+ // ignore, just do not include this information
979
+ this.logger.error({
980
+ msg: 'Failed to classify bounce response',
981
+ bounceResponse: bounce?.response?.message,
982
+ err
983
+ });
984
+ }
985
+ }
986
+
987
+ let stored = 0;
988
+ if (bounce.action && bounce.recipient && bounce.messageId) {
989
+ // Store bounce information for later retrieval
990
+ let storedBounce = {
991
+ i: messageInfo.id,
992
+ r: bounce.recipient,
993
+ t: Date.now(),
994
+ a: bounce.action
995
+ };
996
+
997
+ if (bounce.response && bounce.response.message) {
998
+ storedBounce.m = bounce.response.message;
999
+ }
1000
+
1001
+ if (bounce.response && bounce.response.status) {
1002
+ storedBounce.s = bounce.response.status;
1003
+ }
1004
+
1005
+ // Store bounce info associated with original message ID
1006
+ stored = await appendList.append(this.connection.redis, this.getBounceKey(), bounce.messageId, storedBounce);
1007
+
1008
+ bounceNotifyInfo = Object.assign({ bounceMessage: messageInfo.id }, bounce);
1009
+
1010
+ messageInfo.isBounce = true;
1011
+ messageInfo.relatedMessageId = bounce.messageId;
1012
+ }
1013
+
1014
+ this.logger.debug({
1015
+ msg: 'Detected bounce message',
1016
+ id: messageInfo.id,
1017
+ uid: messageInfo.uid,
1018
+ messageId: messageInfo.messageId,
1019
+ bounce,
1020
+ stored
1021
+ });
1022
+ }
1023
+ } catch (err) {
1024
+ this.logger.error({
1025
+ msg: 'Failed to process potential bounce',
1026
+ id: messageInfo.id,
1027
+ uid: messageInfo.uid,
1028
+ messageId: messageInfo.messageId,
1029
+ err
1030
+ });
1031
+ }
1032
+ }
1033
+
1034
+ // Resolve Gmail category for inbox messages
1035
+ if (
1036
+ this.connection.imapClient.capabilities.has('X-GM-EXT-1') &&
1037
+ this.isAllMail &&
1038
+ messageInfo.labels &&
1039
+ messageInfo.labels.includes('\\Inbox') &&
1040
+ (await settings.get('resolveGmailCategories'))
1041
+ ) {
1042
+ this.logger.trace({
1043
+ msg: 'Resolving category for incoming email',
1044
+ uid: messageData.uid,
1045
+ id: messageInfo.id
1046
+ });
1047
+
1048
+ // Try each Gmail category in order
1049
+ for (let category of ['primary', 'social', 'promotions', 'updates', 'forums', 'reservations', 'purchases']) {
1050
+ try {
1051
+ let results = await this.connection.imapClient.search(
1052
+ {
1053
+ uid: messageInfo.uid,
1054
+ gmraw: `category:${category}`
1055
+ },
1056
+ { uid: true }
1057
+ );
1058
+ if (results && results.includes(messageInfo.uid)) {
1059
+ messageInfo.category = category;
1060
+ this.logger.debug({
1061
+ msg: 'Resolved category for incoming email',
1062
+ category,
1063
+ uid: messageData.uid,
1064
+ id: messageInfo.id
1065
+ });
1066
+ break;
1067
+ }
1068
+ } catch (err) {
1069
+ this.logger.error({ msg: 'Failed to resolve category for message', err, category, uid: messageData.uid, id: messageInfo.id });
1070
+ break;
1071
+ }
1072
+ }
1073
+ }
1074
+
1075
+ // Download attachment content if configured
1076
+ let notifyAttachments = await settings.get('notifyAttachments');
1077
+ let notifyAttachmentSize = await settings.get('notifyAttachmentSize');
1078
+ if (notifyAttachments && messageInfo.attachments?.length) {
1079
+ for (let attachment of messageInfo.attachments || []) {
1080
+ if (notifyAttachmentSize && attachment.encodedSize && attachment.encodedSize > notifyAttachmentSize) {
1081
+ // skip large attachments
1082
+ continue;
1083
+ }
1084
+ if (!attachment.content) {
1085
+ try {
1086
+ let buf = Buffer.from(attachment.id, 'base64url');
1087
+ let part = buf.subarray(8).toString();
1088
+
1089
+ let { content: downloadStream } = await this.connection.imapClient.download(messageInfo.uid, part, {
1090
+ uid: true,
1091
+ chunkSize: options.chunkSize,
1092
+ maxBytes: MAX_ALLOWED_DOWNLOAD_SIZE
1093
+ });
1094
+
1095
+ if (downloadStream) {
1096
+ attachment.content = (await download(downloadStream)).toString('base64');
1097
+ }
1098
+ } catch (err) {
1099
+ this.logger.error({ msg: 'Failed to load attachment content', attachment, err });
1100
+ }
1101
+ }
1102
+ }
1103
+ }
1104
+
1105
+ // Fetch inline attachments referenced in HTML
1106
+ if (messageInfo.attachments?.length && messageInfo.text?.html) {
1107
+ // fetch inline attachments
1108
+ for (let attachment of messageInfo.attachments) {
1109
+ if (attachment.encodedSize && attachment.encodedSize > MAX_INLINE_ATTACHMENT_SIZE) {
1110
+ // skip large attachments
1111
+ continue;
1112
+ }
1113
+
1114
+ // Check if attachment is referenced by Content-ID in HTML
1115
+ if (!attachment.content && attachment.contentId && messageInfo.text.html.indexOf(`cid:${attachment.contentId.replace(/^<|>$/g, '')}`) >= 0) {
1116
+ try {
1117
+ let buf = Buffer.from(attachment.id, 'base64url');
1118
+ let part = buf.subarray(8).toString();
1119
+
1120
+ let { content: downloadStream } = await this.connection.imapClient.download(messageInfo.uid, part, {
1121
+ uid: true,
1122
+ chunkSize: options.chunkSize,
1123
+ maxBytes: MAX_ALLOWED_DOWNLOAD_SIZE
1124
+ });
1125
+
1126
+ if (downloadStream) {
1127
+ attachment.content = (await download(downloadStream)).toString('base64');
1128
+ }
1129
+ } catch (err) {
1130
+ this.logger.error({ msg: 'Failed to load attachment content', attachment, err });
1131
+ }
1132
+ }
1133
+ }
1134
+ }
1135
+
1136
+ // Fetch and process calendar events if needed
1137
+ let notifyCalendarEvents = await settings.get('notifyCalendarEvents');
1138
+ if (notifyCalendarEvents && messageInfo.attachments && messageInfo.attachments.length) {
1139
+ let calendarEventMap = new Map();
1140
+
1141
+ // Process text/calendar before application/ics
1142
+ let sortCalendarAttachments = (a, b) => {
1143
+ if (a.contentType !== b.contentType) {
1144
+ if (a.contentType === 'text/calendar') {
1145
+ return -1;
1146
+ }
1147
+ if (b.contentType === 'text/calendar') {
1148
+ return 1;
1149
+ }
1150
+ }
1151
+ return a.contentType.localeCompare(b.contentType);
1152
+ };
1153
+
1154
+ for (let attachment of [...messageInfo.attachments].sort(sortCalendarAttachments)) {
1155
+ if (['text/calendar', 'application/ics'].includes(attachment.contentType)) {
1156
+ // Download calendar attachment if not already loaded
1157
+ if (!attachment.content) {
1158
+ try {
1159
+ let buf = Buffer.from(attachment.id, 'base64url');
1160
+ let part = buf.subarray(8).toString();
1161
+
1162
+ let { content: downloadStream } = await this.connection.imapClient.download(messageInfo.uid, part, {
1163
+ uid: true,
1164
+ chunkSize: options.chunkSize,
1165
+ maxBytes: MAX_ALLOWED_DOWNLOAD_SIZE
1166
+ });
1167
+
1168
+ if (downloadStream) {
1169
+ let contentBuf = await download(downloadStream);
1170
+
1171
+ if (contentBuf && contentBuf.length) {
1172
+ attachment.content = contentBuf.toString('base64');
1173
+ }
1174
+ }
1175
+ } catch (err) {
1176
+ this.logger.error({ msg: 'Failed to load attachment content', attachment, err });
1177
+ }
1178
+ }
1179
+ if (attachment.content) {
1180
+ let contentBuf = Buffer.from(attachment.content, 'base64');
1181
+ try {
1182
+ // Parse iCalendar data
1183
+ const jcalData = ical.parse(contentBuf.toString());
1184
+
1185
+ const comp = new ical.Component(jcalData);
1186
+ if (!comp) {
1187
+ continue;
1188
+ }
1189
+
1190
+ const vevent = comp.getFirstSubcomponent('vevent');
1191
+ if (!vevent) {
1192
+ continue;
1193
+ }
1194
+
1195
+ // Extract method (REQUEST, CANCEL, etc.)
1196
+ let eventMethodProp = comp.getFirstProperty('method');
1197
+ let eventMethodValue = eventMethodProp ? eventMethodProp.getFirstValue() : null;
1198
+
1199
+ const event = new ical.Event(vevent);
1200
+
1201
+ if (!event || !event.uid) {
1202
+ continue;
1203
+ }
1204
+
1205
+ // Skip duplicate events, prefer ones with filenames
1206
+ if (calendarEventMap.has(event.uid)) {
1207
+ if (attachment.filename) {
1208
+ let existingEntry = calendarEventMap.get(event.uid);
1209
+ if (!existingEntry.filename) {
1210
+ // inject filename
1211
+ existingEntry.filename = attachment.filename;
1212
+ }
1213
+ }
1214
+ continue;
1215
+ }
1216
+
1217
+ // Extract timezone information
1218
+ let timezone;
1219
+ const vtz = comp.getFirstSubcomponent('vtimezone');
1220
+ if (vtz) {
1221
+ const tz = new ical.Timezone(vtz);
1222
+ timezone = tz && tz.tzid;
1223
+ }
1224
+
1225
+ let startDate = event.startDate && event.startDate.toJSDate();
1226
+ let endDate = event.endDate && event.endDate.toJSDate();
1227
+
1228
+ // Store parsed calendar event
1229
+ calendarEventMap.set(
1230
+ event.uid,
1231
+ filterEmptyObjectValues({
1232
+ eventId: event.uid,
1233
+ attachment: attachment.id,
1234
+ method: attachment.method || eventMethodValue || null,
1235
+
1236
+ summary: event.summary || null,
1237
+ description: event.description || null,
1238
+ timezone: timezone || null,
1239
+ startDate: startDate ? startDate.toISOString() : null,
1240
+ endDate: endDate ? endDate.toISOString() : null,
1241
+ organizer: event.organizer && typeof event.organizer === 'string' ? event.organizer : null,
1242
+
1243
+ filename: attachment.filename,
1244
+ contentType: attachment.contentType,
1245
+ encoding: 'base64',
1246
+ content: attachment.content
1247
+ })
1248
+ );
1249
+ } catch (err) {
1250
+ this.logger.error({
1251
+ msg: 'Failed to parse calendar event',
1252
+ attachment: Object.assign({}, attachment, { content: `${contentBuf.length} bytes` }),
1253
+ err
1254
+ });
1255
+ }
1256
+ }
1257
+ }
1258
+ }
1259
+
1260
+ if (calendarEventMap && calendarEventMap.size) {
1261
+ messageInfo.calendarEvents = Array.from(calendarEventMap.values()).map(calendarEvent => {
1262
+ // Generate default filename based on method
1263
+ if (!calendarEvent.filename) {
1264
+ switch (calendarEvent.method && calendarEvent.method.toUpperCase()) {
1265
+ case 'CANCEL':
1266
+ case 'REQUEST':
1267
+ calendarEvent.filename = 'invite.ics';
1268
+ break;
1269
+ default:
1270
+ calendarEvent.filename = 'event.ics';
1271
+ break;
1272
+ }
1273
+ }
1274
+ return calendarEvent;
1275
+ });
1276
+ }
1277
+ }
1278
+
1279
+ // Check if we have seen this message before using HyperLogLog
1280
+ messageInfo.seemsLikeNew =
1281
+ this.listingEntry.specialUse !== '\\Sent' &&
1282
+ !(messageInfo.labels && messageInfo.labels.includes('\\Sent')) &&
1283
+ !!(await this.connection.redis.pfadd(this.getSeenMessagesKey(), messageInfo.emailId || messageInfo.messageId));
1284
+
1285
+ // Determine special use folder for the message
1286
+ for (let specialUseTag of ['\\Junk', '\\Sent', '\\Trash', '\\Inbox', '\\Drafts']) {
1287
+ if (this.listingEntry.specialUse === specialUseTag || (messageInfo.labels && messageInfo.labels.includes(specialUseTag))) {
1288
+ messageInfo.messageSpecialUse = specialUseTag;
1289
+ break;
1290
+ }
1291
+ }
1292
+
1293
+ // Process with LLM if configured for inbox messages
1294
+ if (messageInfo.messageSpecialUse === '\\Inbox' && (!this.connection.notifyFrom || messageData.internalDate >= this.connection.notifyFrom)) {
1295
+ let messageData = Object.assign({ account: this.connection.account }, messageInfo);
1296
+
1297
+ let canUseLLM = await llmPreProcess.run(messageData);
1298
+
1299
+ if (canUseLLM && (messageInfo.text.plain || messageInfo.text.html)) {
1300
+ // Generate AI summary if enabled
1301
+ if (canUseLLM.generateEmailSummary) {
1302
+ try {
1303
+ messageInfo.summary = await this.connection.call({
1304
+ cmd: 'generateSummary',
1305
+ data: {
1306
+ message: {
1307
+ headers: Object.keys(messageInfo.headers || {}).map(key => ({ key, value: [].concat(messageInfo.headers[key] || []) })),
1308
+ attachments: messageInfo.attachments,
1309
+ from: messageInfo.from,
1310
+ subject: messageInfo.subject,
1311
+ text: messageInfo.text.plain,
1312
+ html: messageInfo.text.html
1313
+ },
1314
+ account: this.connection.account
1315
+ },
1316
+ timeout: 2 * 60 * 1000
1317
+ });
1318
+
1319
+ if (messageInfo.summary) {
1320
+ // Clean up summary output
1321
+ for (let key of Object.keys(messageInfo.summary)) {
1322
+ // remove meta keys from output
1323
+ if (key.charAt(0) === '_' || messageInfo.summary[key] === '') {
1324
+ delete messageInfo.summary[key];
1325
+ }
1326
+ if (key === 'riskAssessment') {
1327
+ messageInfo.riskAssessment = messageInfo.summary.riskAssessment;
1328
+ delete messageInfo.summary.riskAssessment;
1329
+ }
1330
+ }
1331
+
1332
+ this.logger.trace({ msg: 'Fetched summary from OpenAI', summary: messageInfo.summary });
1333
+ }
1334
+
1335
+ await this.connection.redis.del(`${REDIS_PREFIX}:openai:error`);
1336
+ } catch (err) {
1337
+ await this.connection.redis.set(
1338
+ `${REDIS_PREFIX}:openai:error`,
1339
+ JSON.stringify({
1340
+ message: err.message,
1341
+ code: err.code,
1342
+ statusCode: err.statusCode,
1343
+ created: Date.now()
1344
+ })
1345
+ );
1346
+ this.logger.error({ msg: 'Failed to fetch summary from OpenAI', err });
1347
+ }
1348
+ }
1349
+
1350
+ // Generate embeddings if enabled
1351
+ if (canUseLLM.generateEmbeddings) {
1352
+ try {
1353
+ messageInfo.embeddings = await this.connection.call({
1354
+ cmd: 'generateEmbeddings',
1355
+ data: {
1356
+ message: {
1357
+ headers: Object.keys(messageInfo.headers || {}).map(key => ({ key, value: [].concat(messageInfo.headers[key] || []) })),
1358
+ attachments: messageInfo.attachments,
1359
+ from: messageInfo.from,
1360
+ subject: messageInfo.subject,
1361
+ text: messageInfo.text.plain,
1362
+ html: messageInfo.text.html
1363
+ },
1364
+ account: this.connection.account
1365
+ },
1366
+ timeout: 2 * 60 * 1000
1367
+ });
1368
+ } catch (err) {
1369
+ await this.connection.redis.set(
1370
+ `${REDIS_PREFIX}:openai:error`,
1371
+ JSON.stringify({
1372
+ message: err.message,
1373
+ code: err.code,
1374
+ statusCode: err.statusCode,
1375
+ time: Date.now()
1376
+ })
1377
+ );
1378
+ this.logger.error({ msg: 'Failed to fetch embeddings OpenAI', err });
1379
+ }
1380
+ }
1381
+ }
1382
+ }
1383
+
1384
+ // Convert message HTML to web safe HTML
1385
+ let notifyWebSafeHtml = await settings.get('notifyWebSafeHtml');
1386
+ if (notifyWebSafeHtml && messageInfo.text && (messageInfo.text.html || messageInfo.text.plain)) {
1387
+ // convert to web safe
1388
+ messageInfo.text._generatedHtml = mimeHtml({
1389
+ html: messageInfo.text.html,
1390
+ text: messageInfo.text.plain
1391
+ });
1392
+ messageInfo.text.webSafe = true;
1393
+
1394
+ // Embed images referenced by Content-ID
1395
+ if (messageInfo.text.html && messageInfo.attachments) {
1396
+ let attachmentList = new Map();
1397
+ let partList = [];
1398
+
1399
+ // Collect CID-referenced attachments
1400
+ for (let attachment of messageInfo.attachments) {
1401
+ let contentId = attachment.contentId && attachment.contentId.replace(/^<|>$/g, '');
1402
+ if (contentId && messageInfo.text.html.indexOf(contentId) >= 0) {
1403
+ attachmentList.set(contentId, {
1404
+ attachment,
1405
+ content: attachment.content || null
1406
+ });
1407
+
1408
+ if (attachment.content) {
1409
+ // already downloaded in a previous step
1410
+ continue;
1411
+ }
1412
+
1413
+ let buf = Buffer.from(attachment.id, 'base64url');
1414
+ let part = buf.subarray(8).toString();
1415
+
1416
+ Object.defineProperty(attachment, 'part', {
1417
+ value: part,
1418
+ enumerable: false
1419
+ });
1420
+
1421
+ if (!partList.includes(part)) {
1422
+ partList.push(part);
1423
+ }
1424
+ }
1425
+ }
1426
+
1427
+ if (attachmentList.size) {
1428
+ if (partList.length) {
1429
+ // Download missing attachments in batch
1430
+ try {
1431
+ let contentParts = await this.connection.imapClient.downloadMany(messageInfo.uid, partList, {
1432
+ uid: true
1433
+ });
1434
+
1435
+ if (contentParts) {
1436
+ for (let [contentId, { attachment }] of attachmentList) {
1437
+ if (attachment.part && contentParts[attachment.part] && contentParts[attachment.part].content) {
1438
+ attachmentList.set(contentId, { attachment, content: contentParts[attachment.part].content.toString('base64') });
1439
+ }
1440
+ }
1441
+ }
1442
+ } catch (err) {
1443
+ this.logger.error({ msg: 'Attachment error', uid: messageInfo.uid, partList, err });
1444
+ }
1445
+ }
1446
+
1447
+ // Replace CID references with data URIs
1448
+ messageInfo.text.html = messageInfo.text.html.replace(/\bcid:([^"'\s>]+)/g, (fullMatch, cidMatch) => {
1449
+ if (attachmentList.has(cidMatch)) {
1450
+ let { attachment, content } = attachmentList.get(cidMatch);
1451
+ if (content) {
1452
+ return `data:${attachment.contentType || 'application/octet-stream'};base64,${content}`;
1453
+ }
1454
+ }
1455
+ return fullMatch;
1456
+ });
1457
+ }
1458
+ }
1459
+ }
1460
+
1461
+ // Send new message notification
1462
+ await this.connection.notify(this, MESSAGE_NEW_NOTIFY, messageInfo, {
1463
+ skipWebhook: this.connection.notifyFrom && date < this.connection.notifyFrom,
1464
+ canSync
1465
+ });
1466
+
1467
+ // Send bounce notification if detected
1468
+ if (bounceNotifyInfo) {
1469
+ let { index, client } = await getESClient(this.logger);
1470
+ if (client) {
1471
+ // Find the originating message this bounce applies for
1472
+ let searchResult = await client.search({
1473
+ index,
1474
+ size: 20,
1475
+ from: 0,
1476
+ query: {
1477
+ bool: {
1478
+ must: [
1479
+ {
1480
+ term: {
1481
+ account: this.connection.account
1482
+ }
1483
+ },
1484
+ {
1485
+ term: {
1486
+ messageId: bounceNotifyInfo.messageId
1487
+ }
1488
+ }
1489
+ ]
1490
+ }
1491
+ },
1492
+ sort: { uid: 'desc' },
1493
+ _source_excludes: 'headers,text'
1494
+ });
1495
+
1496
+ if (searchResult && searchResult.hits && searchResult.hits.hits && searchResult.hits.hits.length) {
1497
+ // Prefer sent messages, then earliest message
1498
+ let message = searchResult.hits.hits
1499
+ .sort((a, b) => {
1500
+ if (a._source.specialUse === '\\Sent') {
1501
+ return -1;
1502
+ }
1503
+ if (b._source.specialUse === '\\Sent') {
1504
+ return 1;
1505
+ }
1506
+ return new Date(a._source.date || a._source.created) - new Date(b._source.date || b._source.created);
1507
+ })
1508
+ .shift()._source;
1509
+ bounceNotifyInfo = Object.assign({ id: message.id }, bounceNotifyInfo);
1510
+ }
1511
+ }
1512
+
1513
+ // send bounce notification _after_ bounce email notification
1514
+ await this.connection.notify(false, EMAIL_BOUNCE_NOTIFY, bounceNotifyInfo, {
1515
+ skipWebhook: this.connection.notifyFrom && date < this.connection.notifyFrom
1516
+ });
1517
+ }
1518
+
1519
+ // Send complaint notification if detected
1520
+ if (complaintNotifyInfo) {
1521
+ // send complaint notification _after_ complaint email notification
1522
+ await this.connection.notify(false, EMAIL_COMPLAINT_NOTIFY, complaintNotifyInfo, {
1523
+ skipWebhook: this.connection.notifyFrom && date < this.connection.notifyFrom
1524
+ });
1525
+ }
1526
+ }
1527
+
1528
+ /**
1529
+ * Builds message info object from raw IMAP data
1530
+ * @param {Object} messageData - Raw message data from IMAP
1531
+ * @param {Boolean} extended - Include extended information
1532
+ * @returns {Object} Formatted message info
1533
+ */
1534
+ async getMessageInfo(messageData, extended) {
1535
+ if (!messageData) {
1536
+ return false;
1537
+ }
1538
+
1539
+ // Generate unique message ID
1540
+ let packedUid = await this.connection.packUid(this, messageData.uid);
1541
+
1542
+ if (!packedUid) {
1543
+ let storedStatus;
1544
+ try {
1545
+ storedStatus = await this.getStoredStatus();
1546
+ } catch (err) {
1547
+ storedStatus = { err };
1548
+ }
1549
+
1550
+ this.logger.error({
1551
+ msg: 'Failed to generate message ID',
1552
+ uid: messageData.uid,
1553
+ messageId: messageData.messageId,
1554
+ mailbox: this.path,
1555
+ storedStatus
1556
+ });
1557
+
1558
+ throw new Error(
1559
+ `Failed to generate message ID (uid=${messageData.uid};uv=${storedStatus.uidValidity};path=${this.path};n=${
1560
+ storedStatus.err ? 'err' : storedStatus.uidNext
1561
+ })`
1562
+ );
1563
+ }
1564
+
1565
+ // Extract attachment information from body structure
1566
+ let { attachments, textId, encodedTextSize } = this.getAttachmentList(packedUid, messageData.bodyStructure);
1567
+
1568
+ let envelope = messageData.envelope || {};
1569
+
1570
+ // Use envelope date or fall back to internal date
1571
+ let date =
1572
+ envelope.date && typeof envelope.date.toISOString === 'function' && envelope.date.toString() !== 'Invalid Date'
1573
+ ? envelope.date
1574
+ : messageData.internalDate;
1575
+
1576
+ let isDraft = false;
1577
+ if (messageData.flags && messageData.flags.has('\\Draft')) {
1578
+ isDraft = true;
1579
+ }
1580
+
1581
+ // Do not expose the \Recent flag as it is session specific
1582
+ if (messageData.flags && messageData.flags.has('\\Recent')) {
1583
+ messageData.flags.delete('\\Recent');
1584
+ }
1585
+
1586
+ if (messageData.labels && messageData.labels.has('\\Draft')) {
1587
+ isDraft = true;
1588
+ }
1589
+
1590
+ let headers;
1591
+
1592
+ // This section is needed for Lark Mail as some address fields might be missing
1593
+ // from the ENVELOPE section, so fall back to the header instead.
1594
+ // Normally, these headers are not fetched from the server and only ENVELOPE is used
1595
+ let parsedAddresses = {};
1596
+ if (messageData.headers) {
1597
+ headers = libmime.decodeHeaders(messageData.headers.toString().trim());
1598
+ for (let key of ['from', 'to', 'cc', 'bcc']) {
1599
+ if (headers[key]?.length) {
1600
+ try {
1601
+ const addressList = addressparser(headers[key])
1602
+ .filter(address => !!address.address)
1603
+ .map(address => {
1604
+ let name = address.name;
1605
+ try {
1606
+ name = libmime.decodeWords(name);
1607
+ } catch (err) {
1608
+ // ignore
1609
+ }
1610
+ return {
1611
+ name,
1612
+ address: address.address
1613
+ };
1614
+ });
1615
+ if (addressList?.length) {
1616
+ parsedAddresses[key] = addressList;
1617
+ }
1618
+ } catch (err) {
1619
+ // just ignore
1620
+ }
1621
+ }
1622
+ }
1623
+ }
1624
+
1625
+ // Build message info object
1626
+ const result = {
1627
+ id: packedUid,
1628
+ uid: messageData.uid,
1629
+
1630
+ path: (extended && this.path && normalizePath(this.path)) || undefined,
1631
+
1632
+ emailId: messageData.emailId || undefined,
1633
+ threadId: messageData.threadId || undefined,
1634
+
1635
+ date: (date && typeof date.toISOString === 'function' && date.toISOString()) || undefined,
1636
+
1637
+ flags: messageData.flags ? Array.from(messageData.flags) : undefined,
1638
+ labels: messageData.labels ? Array.from(messageData.labels) : undefined,
1639
+
1640
+ unseen: messageData.flags && !messageData.flags.has('\\Seen') ? true : undefined,
1641
+ flagged: messageData.flags && messageData.flags.has('\\Flagged') ? true : undefined,
1642
+ answered: messageData.flags && messageData.flags.has('\\Answered') ? true : undefined,
1643
+
1644
+ draft: isDraft ? true : undefined,
1645
+
1646
+ size: messageData.size || undefined,
1647
+ subject: envelope.subject || undefined,
1648
+ // Prefer envelope from, fall back to parsed header
1649
+ from: envelope.from?.[0] ? envelope.from[0] : parsedAddresses.from?.[0] || undefined,
1650
+
1651
+ replyTo: envelope.replyTo && envelope.replyTo.length ? envelope.replyTo : undefined,
1652
+ sender: extended && envelope.sender && envelope.sender[0] ? envelope.sender[0] : undefined,
1653
+
1654
+ to: envelope.to?.length ? envelope.to : parsedAddresses.to || undefined,
1655
+ cc: envelope.cc?.length ? envelope.cc : parsedAddresses.cc || undefined,
1656
+
1657
+ bcc: extended && envelope.bcc && envelope.bcc.length ? envelope.bcc : undefined,
1658
+
1659
+ attachments: attachments && attachments.length ? attachments : undefined,
1660
+ messageId: (envelope.messageId && envelope.messageId.toString().trim()) || undefined,
1661
+ inReplyTo: envelope.inReplyTo || undefined,
1662
+
1663
+ headers: (extended && headers) || undefined,
1664
+ text: textId
1665
+ ? {
1666
+ id: textId,
1667
+ encodedSize: encodedTextSize
1668
+ }
1669
+ : undefined
1670
+ };
1671
+
1672
+ // Remove undefined properties
1673
+ Object.keys(result).forEach(key => {
1674
+ if (typeof result[key] === 'undefined') {
1675
+ delete result[key];
1676
+ }
1677
+ });
1678
+
1679
+ // Check if message is an auto-reply
1680
+ if (result.headers && this.connection.isAutoreply(result)) {
1681
+ result.isAutoReply = true;
1682
+ }
1683
+
1684
+ // Fetch associated bounce information
1685
+ try {
1686
+ if (result.messageId) {
1687
+ let bounces = await appendList.list(this.connection.redis, this.getBounceKey(), result.messageId);
1688
+ if (bounces && bounces.length) {
1689
+ result.bounces = bounces.map(row => {
1690
+ let bounce = {
1691
+ message: row.i,
1692
+ recipient: row.r,
1693
+ action: row.a
1694
+ };
1695
+ if (row.m || row.s) {
1696
+ bounce.response = {};
1697
+ }
1698
+ if (row.m) {
1699
+ bounce.response.message = row.m;
1700
+ }
1701
+ if (row.s) {
1702
+ bounce.response.status = row.s;
1703
+ }
1704
+ bounce.date = new Date(row.t).toISOString();
1705
+ return bounce;
1706
+ });
1707
+ }
1708
+ }
1709
+ } catch (E) {
1710
+ this.logger.error({
1711
+ msg: 'Failed to fetch bounces',
1712
+ id: messageData.id,
1713
+ uid: messageData.uid,
1714
+ messageId: messageData.messageId,
1715
+ err: E
1716
+ });
1717
+ }
1718
+
1719
+ return result;
1720
+ }
1721
+
1722
+ /**
1723
+ * Parses body structure to extract attachment and text part information
1724
+ * @param {String} packedUid - Packed UID for generating attachment IDs
1725
+ * @param {Object} bodyStructure - IMAP BODYSTRUCTURE
1726
+ * @returns {Object} Attachments list and text part information
1727
+ */
1728
+ getAttachmentList(packedUid, bodyStructure) {
1729
+ let attachments = [];
1730
+ let textParts = [[], [], []]; // [plain, html, other]
1731
+ if (!bodyStructure) {
1732
+ return attachments;
1733
+ }
1734
+
1735
+ let idBuf = Buffer.from(packedUid, 'base64url');
1736
+
1737
+ let encodedTextSize = {};
1738
+
1739
+ // Recursively walk body structure tree
1740
+ let walk = (node, isRelated) => {
1741
+ if (node.type === 'multipart/related') {
1742
+ isRelated = true;
1743
+ }
1744
+
1745
+ if (!/^multipart\//.test(node.type)) {
1746
+ // Leaf node - either attachment or text
1747
+ if (node.disposition === 'attachment' || !/^text\/(plain|html)/.test(node.type)) {
1748
+ // Attachment
1749
+ let attachment = {
1750
+ // Append body part number to message ID
1751
+ id: Buffer.concat([idBuf, Buffer.from(node.part || '1')]).toString('base64url'),
1752
+ contentType: node.type,
1753
+ encodedSize: node.size,
1754
+
1755
+ embedded: isRelated,
1756
+ inline: node.disposition === 'inline' || (!node.disposition && isRelated)
1757
+ };
1758
+
1759
+ // Extract filename from disposition or content-type parameters
1760
+ let filename = (node.dispositionParameters && node.dispositionParameters.filename) || (node.parameters && node.parameters.name) || false;
1761
+ if (filename) {
1762
+ attachment.filename = filename;
1763
+ }
1764
+
1765
+ if (node.id) {
1766
+ attachment.contentId = node.id;
1767
+ }
1768
+
1769
+ // Calendar method parameter
1770
+ if (node.parameters && node.parameters.method && typeof node.parameters.method === 'string') {
1771
+ attachment.method = node.parameters.method;
1772
+ }
1773
+
1774
+ attachments.push(attachment);
1775
+ } else if ((!node.disposition || node.disposition === 'inline') && /^text\/(plain|html)/.test(node.type)) {
1776
+ // Text part
1777
+ let type = node.type.substr(5);
1778
+ if (!encodedTextSize[type]) {
1779
+ encodedTextSize[type] = 0;
1780
+ }
1781
+ encodedTextSize[type] += node.size;
1782
+
1783
+ // Group by type
1784
+ switch (type) {
1785
+ case 'plain':
1786
+ textParts[0].push(node.part || '1');
1787
+ break;
1788
+ case 'html':
1789
+ textParts[1].push(node.part || '1');
1790
+ break;
1791
+ default:
1792
+ textParts[2].push(node.part || '1');
1793
+ break;
1794
+ }
1795
+ }
1796
+ }
1797
+
1798
+ // Recursively process multipart children
1799
+ if (node.childNodes) {
1800
+ node.childNodes.forEach(childNode => walk(childNode, isRelated));
1801
+ }
1802
+ };
1803
+
1804
+ walk(bodyStructure, false);
1805
+
1806
+ return {
1807
+ attachments,
1808
+ // Encode text parts array into ID
1809
+ textId: Buffer.concat([idBuf, msgpack.encode(textParts)]).toString('base64url'),
1810
+ encodedTextSize
1811
+ };
1812
+ }
1813
+
1814
+ /**
1815
+ * Processes flag changes for a message
1816
+ * @param {Object} messageData - Message data with changes
1817
+ * @param {Object} changes - What changed
1818
+ */
1819
+ async processChanges(messageData, changes) {
1820
+ let packedUid = await this.connection.packUid(this, messageData.uid);
1821
+ await this.connection.notify(this, MESSAGE_UPDATED_NOTIFY, {
1822
+ id: packedUid,
1823
+ uid: messageData.uid,
1824
+ changes
1825
+ });
1826
+ await this.markUpdated();
1827
+ }
1828
+
1829
+ /**
1830
+ * Performs full synchronization based on indexer type
1831
+ */
1832
+ async fullSync() {
1833
+ const imapIndexer = this.imapIndexer;
1834
+
1835
+ this.logger.trace({ msg: 'Running full sync', imapIndexer });
1836
+
1837
+ switch (imapIndexer) {
1838
+ case 'fast':
1839
+ return this.runFastSync();
1840
+ case 'full':
1841
+ default:
1842
+ return this.runFullSync();
1843
+ }
1844
+ }
1845
+
1846
+ /**
1847
+ * Performs partial synchronization based on indexer type
1848
+ * @param {Object} storedStatus - Current stored mailbox status
1849
+ */
1850
+ async partialSync(storedStatus) {
1851
+ const imapIndexer = this.imapIndexer;
1852
+
1853
+ this.logger.trace({ msg: 'Running partial sync', imapIndexer });
1854
+
1855
+ switch (imapIndexer) {
1856
+ case 'fast':
1857
+ return this.runFastSync(storedStatus);
1858
+ case 'full':
1859
+ default:
1860
+ return this.runPartialSync(storedStatus);
1861
+ }
1862
+ }
1863
+
1864
+ /**
1865
+ * Fast sync mode - only tracks new messages, doesn't maintain full message list
1866
+ * More efficient for large mailboxes where we only care about new messages
1867
+ * @param {Object} storedStatus - Current stored mailbox status
1868
+ */
1869
+ async runFastSync(storedStatus) {
1870
+ storedStatus = storedStatus || (await this.getStoredStatus());
1871
+ let mailboxStatus = this.getMailboxStatus();
1872
+
1873
+ let lock = await this.getMailboxLock(null, { description: 'Fast sync' });
1874
+ this.connection.syncing = true;
1875
+ this.syncing = true;
1876
+ try {
1877
+ if (!this.connection.imapClient) {
1878
+ throw new Error('IMAP connection not available');
1879
+ }
1880
+
1881
+ let knownUidNext = typeof storedStatus.uidNext === 'number' ? storedStatus.uidNext || 1 : 1;
1882
+
1883
+ if (knownUidNext && mailboxStatus.messages) {
1884
+ // detected new emails
1885
+ let fields = { uid: true, flags: true, modseq: true, emailId: true, labels: true, internalDate: true };
1886
+
1887
+ let imapClient = this.connection.imapClient;
1888
+
1889
+ // If we have not yet scanned this folder, then start by finding the earliest matching email
1890
+ if (typeof storedStatus.uidNext !== 'number' && this.connection.notifyFrom && this.connection.notifyFrom < new Date()) {
1891
+ // Find first message after notifyFrom date
1892
+ let matchingMessages = await imapClient.search({ since: this.connection.notifyFrom }, { uid: true });
1893
+ if (matchingMessages) {
1894
+ let earliestUid = matchingMessages[0];
1895
+ if (earliestUid) {
1896
+ knownUidNext = earliestUid;
1897
+ } else if (mailboxStatus.uidNext) {
1898
+ // no match, start from newest
1899
+ knownUidNext = mailboxStatus.uidNext;
1900
+ }
1901
+ }
1902
+ }
1903
+
1904
+ let range = `${knownUidNext}:*`;
1905
+ let opts = {
1906
+ uid: true
1907
+ };
1908
+
1909
+ // Fetch messages with retry logic
1910
+ let fetchCompleted = false;
1911
+ let fetchRetryCount = 0;
1912
+
1913
+ while (!fetchCompleted) {
1914
+ try {
1915
+ let messages = [];
1916
+
1917
+ // Fetch all messages in range
1918
+ for await (let messageData of imapClient.fetch(range, fields, opts)) {
1919
+ if (!messageData || !messageData.uid) {
1920
+ //TODO: support partial responses
1921
+ this.logger.debug({ msg: 'Partial FETCH response', code: 'partial_fetch', query: { range, fields, opts } });
1922
+ continue;
1923
+ }
1924
+
1925
+ // ignore Recent flag
1926
+ messageData.flags.delete('\\Recent');
1927
+
1928
+ messages.push(messageData);
1929
+ }
1930
+ // ensure that messages are sorted by UID
1931
+ messages = messages.sort((a, b) => a.uid - b.uid);
1932
+
1933
+ // Process each new message
1934
+ for (let messageData of messages) {
1935
+ // Update uidNext if this is a new message
1936
+ let updated = await this.connection.redis.hUpdateBigger(this.getMailboxKey(), 'uidNext', messageData.uid + 1, messageData.uid + 1);
1937
+
1938
+ if (updated) {
1939
+ // new email! Queue for processing
1940
+ await this.connection.redis.zadd(
1941
+ this.getNotificationsKey(),
1942
+ messageData.uid,
1943
+ JSON.stringify({
1944
+ uid: messageData.uid,
1945
+ flags: messageData.flags,
1946
+ internalDate:
1947
+ (messageData.internalDate &&
1948
+ typeof messageData.internalDate.toISOString === 'function' &&
1949
+ messageData.internalDate.toISOString()) ||
1950
+ null
1951
+ })
1952
+ );
1953
+ }
1954
+ }
1955
+
1956
+ try {
1957
+ // clear failure flag
1958
+ await this.connection.redis.hdel(this.connection.getAccountKey(), 'syncError');
1959
+ } catch (err) {
1960
+ // ignore
1961
+ }
1962
+ fetchCompleted = true;
1963
+ } catch (err) {
1964
+ try {
1965
+ // set failure flag
1966
+ await this.connection.redis.hSetExists(
1967
+ this.connection.getAccountKey(),
1968
+ 'syncError',
1969
+ JSON.stringify({
1970
+ path: this.path,
1971
+ time: new Date().toISOString(),
1972
+ error: {
1973
+ error: err.message,
1974
+ responseStatus: err.responseStatus,
1975
+ responseText: err.responseText
1976
+ }
1977
+ })
1978
+ );
1979
+ } catch (err) {
1980
+ // ignore
1981
+ }
1982
+
1983
+ // Retry with exponential backoff
1984
+ if (!imapClient.usable) {
1985
+ // nothing to do here, connection closed
1986
+ this.logger.error({ msg: `FETCH failed, connection already closed, not retrying`, err });
1987
+ return;
1988
+ }
1989
+
1990
+ const fetchRetryDelay = calculateFetchBackoff(++fetchRetryCount);
1991
+ this.logger.error({ msg: `FETCH failed, retrying in ${Math.round(fetchRetryDelay / 1000)}s`, err });
1992
+ await new Promise(r => setTimeout(r, fetchRetryDelay));
1993
+
1994
+ if (!imapClient.usable) {
1995
+ // nothing to do here, connection closed
1996
+ this.logger.error({ msg: `FETCH failed, connection already closed, not retrying`, err });
1997
+ return;
1998
+ }
1999
+ }
2000
+ }
2001
+ }
2002
+
2003
+ await this.updateStoredStatus(this.getMailboxStatus());
2004
+
2005
+ await this.publishSyncedEvents(storedStatus);
2006
+ } finally {
2007
+ lock.release();
2008
+ this.connection.syncing = false;
2009
+ this.syncing = false;
2010
+ }
2011
+ }
2012
+
2013
+ /**
2014
+ * Partial sync - fetches only changed messages using MODSEQ or UID range
2015
+ * Used for incremental updates when we know something changed
2016
+ * @param {Object} storedStatus - Current stored mailbox status
2017
+ */
2018
+ async runPartialSync(storedStatus) {
2019
+ storedStatus = storedStatus || (await this.getStoredStatus());
2020
+ let mailboxStatus = this.getMailboxStatus();
2021
+
2022
+ let lock = await this.getMailboxLock(null, { description: 'Partial sync' });
2023
+ this.connection.syncing = true;
2024
+ this.syncing = true;
2025
+ try {
2026
+ if (!this.connection.imapClient) {
2027
+ throw new Error('IMAP connection not available');
2028
+ }
2029
+
2030
+ let fields = { uid: true, flags: true, modseq: true, emailId: true, labels: true, internalDate: true };
2031
+ let range = '1:*';
2032
+ let opts = {
2033
+ uid: true
2034
+ };
2035
+
2036
+ // Use CONDSTORE if available for efficient change detection
2037
+ if (this.connection.imapClient.enabled.has('CONDSTORE') && storedStatus.highestModseq) {
2038
+ // Only fetch messages changed since last known MODSEQ
2039
+ opts.changedSince = storedStatus.highestModseq;
2040
+ } else if (storedStatus.uidNext) {
2041
+ // Fall back to fetching new messages only
2042
+ range = `${storedStatus.uidNext}:*`;
2043
+ }
2044
+
2045
+ if (mailboxStatus.messages) {
2046
+ // only fetch messages if there are some
2047
+ let fetchCompleted = false;
2048
+ let fetchRetryCount = 0;
2049
+ while (!fetchCompleted) {
2050
+ // Get fresh imapClient reference inside retry loop
2051
+ let imapClient = this.connection.imapClient;
2052
+ if (!imapClient || !imapClient.usable) {
2053
+ this.logger.error({ msg: 'IMAP client not available for partial sync' });
2054
+ throw new Error('IMAP connection not available');
2055
+ }
2056
+
2057
+ try {
2058
+ // Fetch and process each message
2059
+ for await (let messageData of imapClient.fetch(range, fields, opts)) {
2060
+ if (!messageData || !messageData.uid) {
2061
+ //TODO: support partial responses
2062
+ this.logger.debug({ msg: 'Partial FETCH response', code: 'partial_fetch', query: { range, fields, opts } });
2063
+ continue;
2064
+ }
2065
+
2066
+ // ignore Recent flag
2067
+ messageData.flags.delete('\\Recent');
2068
+
2069
+ let storedMessage = await this.entryListGet(messageData.uid, { uid: true });
2070
+
2071
+ let changes;
2072
+ if (!storedMessage) {
2073
+ // New message
2074
+ let seq = await this.entryListSet(messageData);
2075
+ if (seq) {
2076
+ // Queue for processing
2077
+ await this.connection.redis.zadd(
2078
+ this.getNotificationsKey(),
2079
+ messageData.uid,
2080
+ JSON.stringify({
2081
+ uid: messageData.uid,
2082
+ flags: messageData.flags,
2083
+ internalDate:
2084
+ (messageData.internalDate &&
2085
+ typeof messageData.internalDate.toISOString === 'function' &&
2086
+ messageData.internalDate.toISOString()) ||
2087
+ null
2088
+ })
2089
+ );
2090
+ }
2091
+ } else if ((changes = compareExisting(storedMessage.entry, messageData))) {
2092
+ // Existing message with changes
2093
+ let seq = await this.entryListSet(messageData);
2094
+ if (seq) {
2095
+ await this.processChanges(messageData, changes);
2096
+ }
2097
+ }
2098
+ }
2099
+ try {
2100
+ // clear failure flag
2101
+ await this.connection.redis.hdel(this.connection.getAccountKey(), 'syncError');
2102
+ } catch (err) {
2103
+ // ignore
2104
+ }
2105
+ fetchCompleted = true;
2106
+ } catch (err) {
2107
+ try {
2108
+ // set failure flag
2109
+ await this.connection.redis.hSetExists(
2110
+ this.connection.getAccountKey(),
2111
+ 'syncError',
2112
+ JSON.stringify({
2113
+ path: this.path,
2114
+ time: new Date().toISOString(),
2115
+ error: {
2116
+ error: err.message,
2117
+ responseStatus: err.responseStatus,
2118
+ responseText: err.responseText
2119
+ }
2120
+ })
2121
+ );
2122
+ } catch (err) {
2123
+ // ignore
2124
+ }
2125
+
2126
+ // Retry with exponential backoff
2127
+ if (!imapClient.usable) {
2128
+ // nothing to do here, connection closed
2129
+ this.logger.error({ msg: `FETCH failed, connection already closed, not retrying` });
2130
+ return;
2131
+ }
2132
+
2133
+ const fetchRetryDelay = calculateFetchBackoff(++fetchRetryCount);
2134
+ this.logger.error({ msg: `FETCH failed, retrying in ${Math.round(fetchRetryDelay / 1000)}s` });
2135
+ await new Promise(r => setTimeout(r, fetchRetryDelay));
2136
+
2137
+ if (!imapClient.usable) {
2138
+ // nothing to do here, connection closed
2139
+ this.logger.error({ msg: `FETCH failed, connection already closed, not retrying` });
2140
+ return;
2141
+ }
2142
+ }
2143
+ }
2144
+ }
2145
+
2146
+ await this.updateStoredStatus(this.getMailboxStatus());
2147
+
2148
+ await this.publishSyncedEvents(storedStatus);
2149
+ } finally {
2150
+ lock.release();
2151
+ this.connection.syncing = false;
2152
+ this.syncing = false;
2153
+ }
2154
+ }
2155
+
2156
+ /**
2157
+ * Full sync - fetches all messages and detects additions, deletions, and changes
2158
+ * Most thorough but slowest sync method
2159
+ */
2160
+ async runFullSync() {
2161
+ let fields = { uid: true, flags: true, modseq: true, emailId: true, labels: true, internalDate: true };
2162
+ let opts = {};
2163
+
2164
+ let lock = await this.getMailboxLock(null, { description: 'Full sync' });
2165
+ this.connection.syncing = true;
2166
+ this.syncing = true;
2167
+ try {
2168
+ // Generate unique ID for this sync loop to track batch ordering
2169
+ const loopId = crypto.randomUUID();
2170
+
2171
+ // Wait for next tick to ensure ImapFlow has processed all untagged responses from SELECT
2172
+ await new Promise(resolve => setImmediate(resolve));
2173
+
2174
+ let mailboxStatus = this.getMailboxStatus();
2175
+
2176
+ this.logger.debug({
2177
+ msg: 'Starting full sync',
2178
+ code: 'full_sync_start',
2179
+ loopId,
2180
+ mailboxStatus,
2181
+ imapClientExists: mailboxStatus.messages
2182
+ });
2183
+
2184
+ // Track highest sequence number seen
2185
+ let seqMax = 0;
2186
+ let changes;
2187
+
2188
+ // Get current message count for deletion detection
2189
+ let storedMaxSeqOld = await this.connection.redis.zcard(this.getMessagesKey());
2190
+
2191
+ let responseCounters = {
2192
+ empty: 0,
2193
+ partial: 0,
2194
+ messages: 0
2195
+ };
2196
+
2197
+ if (mailboxStatus.messages) {
2198
+ this.logger.debug({
2199
+ msg: 'Running FETCH',
2200
+ code: 'run_fetch',
2201
+ query: { fields, opts },
2202
+ expectedMessages: mailboxStatus.messages,
2203
+ mailbox: mailboxStatus,
2204
+ maxBatchSize: FETCH_BATCH_SIZE,
2205
+ expectedBatches: Math.ceil(mailboxStatus.messages / FETCH_BATCH_SIZE)
2206
+ });
2207
+
2208
+ // Process messages in batches to avoid memory issues
2209
+ let range = false;
2210
+ let lastHighestUid = 0;
2211
+ let batchNumber = 0;
2212
+ // process messages in batches
2213
+ while ((range = getFetchRange(mailboxStatus.messages, range))) {
2214
+ batchNumber++;
2215
+ this.logger.debug({
2216
+ msg: 'Processing batch',
2217
+ code: 'fetch_batch',
2218
+ loopId,
2219
+ batchNumber,
2220
+ range,
2221
+ totalMessages: mailboxStatus.messages,
2222
+ previousRange: batchNumber > 1 ? 'calculated' : 'initial'
2223
+ });
2224
+ let fetchCompleted = false;
2225
+ let fetchRetryCount = 0;
2226
+ while (!fetchCompleted) {
2227
+ // Get fresh imapClient reference inside retry loop
2228
+ // This ensures we use the current connection state
2229
+ const imapClient = this.connection.imapClient;
2230
+ if (!imapClient || !imapClient.usable) {
2231
+ this.logger.error({ msg: 'IMAP client not available for FETCH' });
2232
+ throw new Error('IMAP connection not available');
2233
+ }
2234
+
2235
+ try {
2236
+ this.logger.debug({
2237
+ msg: 'Starting FETCH command',
2238
+ code: 'fetch_start',
2239
+ loopId,
2240
+ batchNumber,
2241
+ range,
2242
+ retryCount: fetchRetryCount,
2243
+ totalMessages: mailboxStatus.messages
2244
+ });
2245
+
2246
+ for await (let messageData of imapClient.fetch(range, fields, opts)) {
2247
+ if (!messageData) {
2248
+ this.logger.debug({ msg: 'Empty FETCH response', code: 'empty_fetch', query: { range, fields, opts } });
2249
+ responseCounters.empty++;
2250
+ continue;
2251
+ }
2252
+
2253
+ if (!messageData.uid || (fields.flags && !messageData.flags)) {
2254
+ // TODO: support partial responses
2255
+ // For now, without UID or FLAGS there's nothing to do
2256
+ this.logger.debug({
2257
+ msg: 'Partial FETCH response',
2258
+ code: 'partial_fetch',
2259
+ query: { range, fields, opts },
2260
+ responseKeys: Object.keys(messageData)
2261
+ });
2262
+ responseCounters.partial++;
2263
+ continue;
2264
+ }
2265
+
2266
+ if (messageData.uid <= lastHighestUid) {
2267
+ // already processed in the previous batch
2268
+ // probably an older email was deleted which shifted message entries
2269
+ continue;
2270
+ }
2271
+ lastHighestUid = messageData.uid;
2272
+
2273
+ responseCounters.messages++;
2274
+
2275
+ if (fields.internalDate && !messageData.internalDate) {
2276
+ this.logger.debug({
2277
+ msg: 'Missing INTERNALDATE',
2278
+ code: 'fetch_date_missing',
2279
+ query: { range, fields, opts },
2280
+ responseKeys: Object.keys(messageData)
2281
+ });
2282
+ }
2283
+
2284
+ // ignore Recent flag
2285
+ messageData.flags.delete('\\Recent');
2286
+
2287
+ if (messageData.seq > seqMax) {
2288
+ seqMax = messageData.seq;
2289
+ }
2290
+
2291
+ let storedMessage = await this.entryListGet(messageData.uid, { uid: true });
2292
+ if (!storedMessage) {
2293
+ // New message
2294
+ let seq = await this.entryListSet(messageData);
2295
+ if (seq) {
2296
+ await this.connection.redis.zadd(
2297
+ this.getNotificationsKey(),
2298
+ messageData.uid,
2299
+ JSON.stringify({
2300
+ uid: messageData.uid,
2301
+ flags: messageData.flags,
2302
+ internalDate:
2303
+ (messageData.internalDate &&
2304
+ typeof messageData.internalDate.toISOString === 'function' &&
2305
+ messageData.internalDate.toISOString()) ||
2306
+ null
2307
+ })
2308
+ );
2309
+ }
2310
+ } else {
2311
+ // Check for deleted messages between stored and current sequence
2312
+ let diff = storedMessage.seq - messageData.seq;
2313
+ if (diff) {
2314
+ this.logger.trace({ msg: 'Deleted range', inloop: true, diff, start: messageData.seq });
2315
+ }
2316
+ // Process deletions
2317
+ for (let i = diff - 1; i >= 0; i--) {
2318
+ let seq = messageData.seq + i;
2319
+ let deletedEntry = await this.entryListExpunge(seq);
2320
+ if (deletedEntry) {
2321
+ await this.processDeleted(deletedEntry);
2322
+ }
2323
+ }
2324
+
2325
+ // Check for changes
2326
+ if ((changes = compareExisting(storedMessage.entry, messageData))) {
2327
+ let seq = await this.entryListSet(messageData);
2328
+ if (seq) {
2329
+ await this.processChanges(messageData, changes);
2330
+ }
2331
+ }
2332
+ }
2333
+ }
2334
+
2335
+ try {
2336
+ // clear failure flag
2337
+ await this.connection.redis.hdel(this.connection.getAccountKey(), 'syncError');
2338
+ } catch (err) {
2339
+ // ignore
2340
+ }
2341
+
2342
+ this.logger.debug({
2343
+ msg: 'FETCH completed successfully',
2344
+ code: 'fetch_success',
2345
+ loopId,
2346
+ batchNumber,
2347
+ range,
2348
+ retryCount: fetchRetryCount
2349
+ });
2350
+
2351
+ fetchCompleted = true;
2352
+ } catch (err) {
2353
+ this.logger.error({
2354
+ msg: 'FETCH failed',
2355
+ code: 'fetch_error',
2356
+ loopId,
2357
+ batchNumber,
2358
+ range,
2359
+ retryCount: fetchRetryCount,
2360
+ totalMessages: mailboxStatus.messages,
2361
+ error: err.message,
2362
+ responseStatus: err.responseStatus,
2363
+ responseText: err.responseText
2364
+ });
2365
+
2366
+ if (!imapClient.usable) {
2367
+ // nothing to do here, connection closed
2368
+ this.logger.error({ msg: `FETCH failed, connection already closed, not retrying`, loopId });
2369
+ return;
2370
+ }
2371
+
2372
+ try {
2373
+ // set failure flag
2374
+ await this.connection.redis.hSetExists(
2375
+ this.connection.getAccountKey(),
2376
+ 'syncError',
2377
+ JSON.stringify({
2378
+ path: this.path,
2379
+ time: new Date().toISOString(),
2380
+ error: {
2381
+ error: err.message,
2382
+ responseStatus: err.responseStatus,
2383
+ responseText: err.responseText
2384
+ }
2385
+ })
2386
+ );
2387
+ } catch (err) {
2388
+ // ignore
2389
+ }
2390
+
2391
+ // Retry with exponential backoff
2392
+ const fetchRetryDelay = calculateFetchBackoff(++fetchRetryCount);
2393
+ this.logger.error({ msg: `FETCH failed, retrying in ${Math.round(fetchRetryDelay / 1000)}s`, loopId, batchNumber });
2394
+ await new Promise(r => setTimeout(r, fetchRetryDelay));
2395
+
2396
+ if (!imapClient.usable) {
2397
+ // nothing to do here, connection closed
2398
+ this.logger.error({ msg: `FETCH failed, connection already closed, not retrying`, loopId });
2399
+ return;
2400
+ }
2401
+
2402
+ // Verify we're still on the correct mailbox after the delay
2403
+ // Another operation might have changed the mailbox while we were waiting
2404
+ const currentMailbox = this.connection.imapClient.mailbox;
2405
+ if (!currentMailbox || currentMailbox.path !== this.path) {
2406
+ this.logger.error({
2407
+ msg: 'Mailbox changed during retry delay, aborting sync',
2408
+ expectedPath: this.path,
2409
+ currentPath: currentMailbox ? currentMailbox.path : 'none',
2410
+ loopId
2411
+ });
2412
+ throw new Error('Mailbox changed during sync operation');
2413
+ }
2414
+
2415
+ // Refresh mailbox status in case it changed
2416
+ const oldMailboxMessages = mailboxStatus.messages;
2417
+ mailboxStatus = this.getMailboxStatus();
2418
+
2419
+ this.logger.debug({
2420
+ msg: 'Refreshed mailbox status after error',
2421
+ code: 'mailbox_status_refresh',
2422
+ loopId,
2423
+ batchNumber,
2424
+ oldMessages: oldMailboxMessages,
2425
+ newMessages: mailboxStatus.messages,
2426
+ range
2427
+ });
2428
+ }
2429
+ }
2430
+ }
2431
+ }
2432
+
2433
+ // Delete any messages that weren't seen in this sync
2434
+ let storedMaxSeq = await this.connection.redis.zcard(this.getMessagesKey());
2435
+ let diff = storedMaxSeq - seqMax;
2436
+ if (diff) {
2437
+ this.logger.trace({
2438
+ msg: 'Deleted range',
2439
+ inloop: false,
2440
+ diff,
2441
+ start: seqMax + 1,
2442
+ messagesKey: this.getMessagesKey(),
2443
+ zcard: storedMaxSeq,
2444
+ zcardOld: storedMaxSeqOld,
2445
+ responseCounters
2446
+ });
2447
+ }
2448
+
2449
+ // Process remaining deletions
2450
+ for (let i = diff - 1; i >= 0; i--) {
2451
+ let seq = seqMax + i + 1;
2452
+ let deletedEntry = await this.entryListExpunge(seq);
2453
+ if (deletedEntry) {
2454
+ await this.processDeleted(deletedEntry);
2455
+ }
2456
+ }
2457
+
2458
+ // Update status with full sync timestamp
2459
+ let status = this.getMailboxStatus();
2460
+ status.lastFullSync = new Date();
2461
+
2462
+ await this.updateStoredStatus(status);
2463
+ let storedStatus = await this.getStoredStatus();
2464
+
2465
+ await this.publishSyncedEvents(storedStatus);
2466
+ } finally {
2467
+ this.connection.syncing = false;
2468
+ this.syncing = false;
2469
+ lock.release();
2470
+ }
2471
+ }
2472
+
2473
+ /**
2474
+ * Processes queued notification events after sync
2475
+ * Fetches full message details and sends notifications
2476
+ * @param {Object} storedStatus - Current mailbox status
2477
+ */
2478
+ async publishSyncedEvents(storedStatus) {
2479
+ let messageFetchOptions = {};
2480
+
2481
+ let documentStoreEnabled = await settings.get('documentStoreEnabled');
2482
+
2483
+ // Configure text fetching
2484
+ let notifyText = await settings.get('notifyText');
2485
+ if (documentStoreEnabled || notifyText) {
2486
+ messageFetchOptions.textType = '*';
2487
+ let notifyTextSize = await settings.get('notifyTextSize');
2488
+
2489
+ if (documentStoreEnabled && notifyTextSize) {
2490
+ // Ensure at least 1MB for document store
2491
+ notifyTextSize = Math.max(notifyTextSize, 1024 * 1024);
2492
+ }
2493
+
2494
+ if (notifyTextSize) {
2495
+ messageFetchOptions.maxBytes = notifyTextSize;
2496
+ }
2497
+ }
2498
+
2499
+ // Configure header fetching
2500
+ let notifyHeaders = (await settings.get('notifyHeaders')) || [];
2501
+ if (documentStoreEnabled || notifyHeaders.length) {
2502
+ messageFetchOptions.headers = notifyHeaders.includes('*') || documentStoreEnabled ? true : notifyHeaders.length ? notifyHeaders : false;
2503
+ }
2504
+
2505
+ // Also request autoresponse headers
2506
+ if (messageFetchOptions.headers !== true) {
2507
+ let fetchHeaders = new Set(messageFetchOptions.headers || []);
2508
+
2509
+ // Auto-reply detection headers
2510
+ fetchHeaders.add('x-autoreply');
2511
+ fetchHeaders.add('x-autorespond');
2512
+ fetchHeaders.add('auto-submitted');
2513
+ fetchHeaders.add('precedence');
2514
+
2515
+ // Threading headers
2516
+ fetchHeaders.add('in-reply-to');
2517
+ fetchHeaders.add('references');
2518
+
2519
+ // Content type for bounce/complaint detection
2520
+ fetchHeaders.add('content-type');
2521
+
2522
+ if (this.isLarkSuite) {
2523
+ // Add address headers as a fallback for unreliable ENVELOPE
2524
+ fetchHeaders.add('from');
2525
+ fetchHeaders.add('to');
2526
+ fetchHeaders.add('cc');
2527
+ }
2528
+
2529
+ messageFetchOptions.fetchHeaders = Array.from(fetchHeaders);
2530
+ }
2531
+
2532
+ // Process queued notifications
2533
+ let queuedEntry;
2534
+ let hadUpdates = false;
2535
+ while ((queuedEntry = await this.connection.redis.zpopmin(this.getNotificationsKey(), 1)) && queuedEntry.length) {
2536
+ hadUpdates = true;
2537
+
2538
+ let [messageData, uid] = queuedEntry;
2539
+ uid = Number(uid);
2540
+ try {
2541
+ messageData = JSON.parse(messageData);
2542
+ if (typeof messageData.internalDate === 'string') {
2543
+ messageData.internalDate = new Date(messageData.internalDate);
2544
+ }
2545
+ } catch (err) {
2546
+ continue;
2547
+ }
2548
+
2549
+ // Check if message should be synced to document store
2550
+ let canSync = documentStoreEnabled && (!this.connection.syncFrom || messageData.internalDate >= this.connection.syncFrom);
2551
+
2552
+ if (this.connection.notifyFrom && messageData.internalDate < this.connection.notifyFrom && !canSync) {
2553
+ // skip too old messages
2554
+ continue;
2555
+ }
2556
+
2557
+ await this.processNew(messageData, messageFetchOptions, canSync, storedStatus);
2558
+ }
2559
+
2560
+ if (hadUpdates) {
2561
+ await this.markUpdated();
2562
+ }
2563
+ }
2564
+
2565
+ /**
2566
+ * Called when mailbox is opened/selected
2567
+ * Determines what type of sync is needed based on current state
2568
+ */
2569
+ async onOpen() {
2570
+ clearTimeout(this.runPartialSyncTimer);
2571
+ this.selected = true;
2572
+
2573
+ // Track connection count to detect reconnects
2574
+ this.previouslyConnected = Number(await this.connection.redis.hget(this.connection.getAccountKey(), `state:count:connected`)) || 0;
2575
+
2576
+ let mailboxStatus = this.getMailboxStatus();
2577
+
2578
+ try {
2579
+ let storedStatus = await this.getStoredStatus();
2580
+
2581
+ // Store initial UID on first sync
2582
+ if (storedStatus.uidNext === false && typeof mailboxStatus.uidNext === 'number') {
2583
+ // update first UID
2584
+ await this.connection.redis.hSetNew(this.getMailboxKey(), 'initialUidNext', mailboxStatus.uidNext.toString());
2585
+ storedStatus = await this.getStoredStatus();
2586
+ }
2587
+
2588
+ // Process any queued notifications first
2589
+ let hasQueuedNotifications = await this.connection.redis.exists(this.getNotificationsKey());
2590
+ if (hasQueuedNotifications) {
2591
+ return await this.fullSync();
2592
+ }
2593
+
2594
+ // Check for UIDVALIDITY change (mailbox recreated)
2595
+ if ('uidValidity' in storedStatus && mailboxStatus.uidValidity !== storedStatus.uidValidity) {
2596
+ // UIDVALIDITY has changed, full sync is required!
2597
+ // delete mailbox status
2598
+ let result = await this.connection.redis.multi().zcard(this.getMessagesKey()).del(this.getMessagesKey()).del(this.getMailboxKey()).exec();
2599
+
2600
+ let deletedMessages = (result[0] && Number(result[0][1])) || 0;
2601
+ this.logger.info({
2602
+ msg: 'UIDVALIDITY change',
2603
+ deleted: deletedMessages,
2604
+ prevUidValidity: validUidValidity(storedStatus.uidValidity) ? storedStatus.uidValidity.toString() : false,
2605
+ uidValidity: validUidValidity(mailboxStatus.uidValidity) ? mailboxStatus.uidValidity.toString() : false
2606
+ });
2607
+
2608
+ this.logger.debug({ msg: 'Mailbox reset', path: this.listingEntry.path });
2609
+ await this.connection.notify(this, MAILBOX_RESET_NOTIFY, {
2610
+ path: this.listingEntry.path,
2611
+ name: this.listingEntry.name,
2612
+ specialUse: this.listingEntry.specialUse || false,
2613
+ uidValidity: validUidValidity(mailboxStatus.uidValidity) ? mailboxStatus.uidValidity.toString() : false,
2614
+ prevUidValidity: validUidValidity(storedStatus.uidValidity) ? storedStatus.uidValidity.toString() : false
2615
+ });
2616
+
2617
+ // do not advertise messages as new
2618
+ this.listingEntry.isNew = true;
2619
+
2620
+ // generates blank stored status as the Redis key was deleted
2621
+ storedStatus = await this.getStoredStatus();
2622
+ }
2623
+
2624
+ // Determine sync strategy based on various conditions
2625
+
2626
+ // No changes if MODSEQ hasn't changed
2627
+ if (storedStatus.highestModseq && storedStatus.highestModseq === mailboxStatus.highestModseq) {
2628
+ return false;
2629
+ }
2630
+
2631
+ // No changes if mailbox is empty
2632
+ if (storedStatus.messages === 0 && mailboxStatus.messages === 0) {
2633
+ return false;
2634
+ }
2635
+
2636
+ // Partial sync if we can detect only new messages or flag changes
2637
+ if (
2638
+ this.connection.imapClient.enabled.has('CONDSTORE') &&
2639
+ storedStatus.highestModseq < mailboxStatus.highestModseq &&
2640
+ storedStatus.messages <= mailboxStatus.messages &&
2641
+ mailboxStatus.uidNext - storedStatus.uidNext === mailboxStatus.messages - storedStatus.messages
2642
+ ) {
2643
+ // search for flag changes and new messages
2644
+ return await this.partialSync(storedStatus);
2645
+ }
2646
+
2647
+ // Partial sync if only new messages
2648
+ if (
2649
+ storedStatus.messages < mailboxStatus.messages &&
2650
+ mailboxStatus.uidNext - storedStatus.uidNext === mailboxStatus.messages - storedStatus.messages
2651
+ ) {
2652
+ // seem to have new messages only
2653
+ return await this.partialSync(storedStatus);
2654
+ }
2655
+
2656
+ // Skip if nothing changed and recent full sync
2657
+ if (
2658
+ storedStatus.messages === mailboxStatus.messages &&
2659
+ storedStatus.uidNext === mailboxStatus.uidNext &&
2660
+ storedStatus.lastFullSync &&
2661
+ storedStatus.lastFullSync >= new Date(Date.now() - FULL_SYNC_DELAY)
2662
+ ) {
2663
+ // too soon from last full sync, message count seems the same
2664
+ return false;
2665
+ }
2666
+
2667
+ // Perform full sync. Only way of getting flag changes from non-CONDSTORE servers
2668
+ return await this.fullSync();
2669
+ } catch (err) {
2670
+ if (err.mailboxMissing) {
2671
+ // this mailbox is missing, refresh listing
2672
+ try {
2673
+ await this.connection.getCurrentListing();
2674
+ } catch (E) {
2675
+ this.logger.error({ msg: 'Missing mailbox', err, E });
2676
+ }
2677
+ }
2678
+ throw err;
2679
+ } finally {
2680
+ // Send new mailbox notification if this is the first sync
2681
+ if (this.listingEntry.isNew) {
2682
+ // fully synced, so not new anymore
2683
+ this.listingEntry.isNew = false;
2684
+ this.logger.debug({ msg: 'New mailbox', path: this.listingEntry.path });
2685
+ this.connection.notify(this, MAILBOX_NEW_NOTIFY, {
2686
+ path: this.listingEntry.path,
2687
+ name: this.listingEntry.name,
2688
+ specialUse: this.listingEntry.specialUse || false,
2689
+ uidValidity: mailboxStatus.uidValidity.toString()
2690
+ });
2691
+ }
2692
+
2693
+ // Resolve sync promise or start IDLE
2694
+ if (this.synced) {
2695
+ this.synced();
2696
+ } else {
2697
+ await this.select();
2698
+ }
2699
+ }
2700
+ }
2701
+
2702
+ /**
2703
+ * Called when mailbox is closed/deselected
2704
+ */
2705
+ async onClose() {
2706
+ clearTimeout(this.runPartialSyncTimer);
2707
+ this.selected = false;
2708
+ }
2709
+
2710
+ // User methods
2711
+ // Call `clearTimeout(this.connection.completedTimer);` after locking mailbox
2712
+ // Call this.onTaskCompleted() after selected mailbox is processed and lock is released
2713
+
2714
+ /**
2715
+ * Fetches text content for a message
2716
+ * @param {Object} message - Message object with uid
2717
+ * @param {Array} textParts - Array of body part numbers to fetch
2718
+ * @param {Object} options - Fetch options
2719
+ * @param {Object} connectionOptions - Connection options
2720
+ * @returns {Object} Text content by type (plain, html, etc)
2721
+ */
2722
+ async getText(message, textParts, options, connectionOptions) {
2723
+ options = options || {};
2724
+
2725
+ const connectionClient = await this.connection.getImapConnection(connectionOptions);
2726
+
2727
+ let result = {};
2728
+
2729
+ let maxBytes = options.maxBytes || Infinity;
2730
+ // Request extra bytes to ensure complete UTF-8 sequences
2731
+ let reqMaxBytes = options.maxBytes && !isNaN(options.maxBytes) ? Number(options.maxBytes) + 4 : maxBytes;
2732
+
2733
+ let hasMore = false;
2734
+
2735
+ let lock;
2736
+ if (!options.skipLock) {
2737
+ lock = await this.getMailboxLock(connectionClient, { description: `Get text: ${message.uid}` });
2738
+ }
2739
+
2740
+ try {
2741
+ for (let part of textParts) {
2742
+ let { meta, content } = await connectionClient.download(message.uid, part, {
2743
+ uid: true,
2744
+ // make sure we request enough bytes so we would have complete utf-8 codepoints
2745
+ maxBytes: Math.min(reqMaxBytes, MAX_ALLOWED_DOWNLOAD_SIZE),
2746
+ chunkSize: options.chunkSize
2747
+ });
2748
+
2749
+ if (!content) {
2750
+ continue;
2751
+ }
2752
+ let text = await download(content);
2753
+ text = text.toString().replace(/\r?\n/g, '\n');
2754
+
2755
+ // Group by content type (plain, html, etc)
2756
+ let typeKey = (meta.contentType && meta.contentType.split('/')[1]) || 'plain';
2757
+ if (!result[typeKey]) {
2758
+ result[typeKey] = [];
2759
+ }
2760
+
2761
+ // Check size limits
2762
+ let typeSize = result[typeKey].reduce((sum, entry) => sum + entry.length, 0);
2763
+ if (typeSize >= maxBytes) {
2764
+ hasMore = true;
2765
+ continue;
2766
+ }
2767
+ if (typeSize + text.length > maxBytes) {
2768
+ text = text.substr(0, maxBytes - typeSize);
2769
+ hasMore = true;
2770
+ }
2771
+ result[typeKey].push(text);
2772
+ }
2773
+ } finally {
2774
+ if (lock) {
2775
+ lock.release();
2776
+ }
2777
+ }
2778
+
2779
+ // Join multiple parts of same type
2780
+ Object.keys(result).forEach(key => {
2781
+ result[key] = result[key].join('\n');
2782
+ });
2783
+
2784
+ result.hasMore = hasMore;
2785
+
2786
+ if (!options.skipLock) {
2787
+ this.onTaskCompleted(connectionClient);
2788
+ }
2789
+
2790
+ return result;
2791
+ }
2792
+
2793
+ /**
2794
+ * Downloads an attachment
2795
+ * @param {Object} message - Message object with uid
2796
+ * @param {String} part - Body part number
2797
+ * @param {Object} options - Download options
2798
+ * @param {Object} connectionOptions - Connection options
2799
+ * @returns {Stream} Readable stream of attachment content
2800
+ */
2801
+ async getAttachment(message, part, options, connectionOptions) {
2802
+ options = options || {};
2803
+
2804
+ const connectionClient = await this.connection.getImapConnection(connectionOptions);
2805
+
2806
+ let lock = await this.getMailboxLock(connectionClient, { description: `Get attachment: ${message.uid}/${part}` });
2807
+
2808
+ let streaming = false;
2809
+ let released = false;
2810
+ try {
2811
+ let { meta, content } = await connectionClient.download(message.uid, part, {
2812
+ uid: true,
2813
+ maxBytes: Math.min(options.maxBytes || 0, MAX_ALLOWED_DOWNLOAD_SIZE),
2814
+ chunkSize: options.chunkSize
2815
+ });
2816
+
2817
+ if (!meta) {
2818
+ return false;
2819
+ }
2820
+
2821
+ // Build content-disposition header with proper encoding
2822
+ let filenameParam = '';
2823
+ if (meta.filename) {
2824
+ let isCleartextFilename = meta.filename && /^[a-z0-9 _\-()^[\]~=,+*$]+$/i.test(meta.filename);
2825
+ if (isCleartextFilename) {
2826
+ filenameParam = `; filename=${JSON.stringify(meta.filename)}`;
2827
+ } else {
2828
+ // Use RFC 2231 encoding for non-ASCII filenames
2829
+ filenameParam = `; filename=${JSON.stringify(he.encode(meta.filename))}; filename*=utf-8''${encodeURIComponent(meta.filename)}`;
2830
+ }
2831
+ }
2832
+
2833
+ // Add HTTP headers to stream
2834
+ content.headers = {
2835
+ 'content-type': meta.contentType || 'application/octet-stream',
2836
+ 'content-disposition': 'attachment' + filenameParam
2837
+ };
2838
+
2839
+ content.contentType = meta.contentType;
2840
+ content.filename = meta.filename;
2841
+ content.disposition = meta.disposition;
2842
+ streaming = true;
2843
+
2844
+ // Release lock when stream ends
2845
+ content.once('end', () => {
2846
+ if (!released) {
2847
+ released = true;
2848
+ lock.release();
2849
+ }
2850
+ });
2851
+
2852
+ content.once('error', () => {
2853
+ if (!released) {
2854
+ released = true;
2855
+ lock.release();
2856
+ this.connection.onTaskCompleted(connectionClient);
2857
+ }
2858
+ });
2859
+
2860
+ return content;
2861
+ } finally {
2862
+ if (!streaming) {
2863
+ lock.release();
2864
+ this.connection.onTaskCompleted(connectionClient);
2865
+ }
2866
+ }
2867
+ }
2868
+
2869
+ /**
2870
+ * Fetches complete message details
2871
+ * @param {Object} message - Message object with uid
2872
+ * @param {Object} options - Fetch options
2873
+ * @param {Object} connectionOptions - Connection options
2874
+ * @returns {Object} Complete message information
2875
+ */
2876
+ async getMessage(message, options, connectionOptions) {
2877
+ options = options || {};
2878
+
2879
+ let messageInfo;
2880
+
2881
+ const connectionClient = await this.connection.getImapConnection(connectionOptions);
2882
+
2883
+ try {
2884
+ let lock;
2885
+ if (!options.skipLock) {
2886
+ lock = await this.getMailboxLock(connectionClient, { description: `Get message: ${message.uid}` });
2887
+ }
2888
+
2889
+ try {
2890
+ // Configure which fields to fetch
2891
+ let fields = options.fields || {
2892
+ uid: true,
2893
+ flags: true,
2894
+ size: true,
2895
+ bodyStructure: true,
2896
+ envelope: true,
2897
+ internalDate: true,
2898
+ headers: 'headers' in options ? options.headers : true,
2899
+ emailId: true,
2900
+ threadId: true,
2901
+ labels: true
2902
+ };
2903
+
2904
+ let messageData = await connectionClient.fetchOne(message.uid, fields, { uid: true });
2905
+
2906
+ // Mark as seen if requested
2907
+ if (options.markAsSeen && (!messageData.flags || !messageData.flags.has('\\Seen'))) {
2908
+ try {
2909
+ let res = await connectionClient.messageFlagsAdd(message.uid, ['\\Seen'], { uid: true });
2910
+ if (res) {
2911
+ messageData.flags.add('\\Seen');
2912
+ }
2913
+ } catch (err) {
2914
+ this.logger.debug({ msg: 'Failed to mark message as Seen', message: message.uid, err });
2915
+ }
2916
+ }
2917
+
2918
+ if (!messageData || !messageData.uid) {
2919
+ //TODO: support partial responses
2920
+ this.logger.debug({ msg: 'Partial FETCH response', code: 'partial_fetch', query: { range: message.uid, fields, opts: { uid: true } } });
2921
+ return false;
2922
+ }
2923
+
2924
+ messageInfo = await this.getMessageInfo(messageData, true);
2925
+ } finally {
2926
+ if (lock) {
2927
+ lock.release();
2928
+ }
2929
+ }
2930
+
2931
+ if (!messageInfo) {
2932
+ return false;
2933
+ }
2934
+
2935
+ // Merge decoded text content with message data (if requested)
2936
+ if (options.textType && messageInfo.text && messageInfo.text.id) {
2937
+ let { textParts } = await this.connection.getMessageTextPaths(messageInfo.text.id);
2938
+ if (textParts && textParts.length) {
2939
+ // Select which text parts to fetch
2940
+ switch (options.textType) {
2941
+ case 'plain':
2942
+ textParts = textParts[0];
2943
+ break;
2944
+ case 'html':
2945
+ textParts = textParts[1];
2946
+ break;
2947
+ default:
2948
+ textParts = textParts.flatMap(entry => entry);
2949
+ break;
2950
+ }
2951
+
2952
+ if (textParts && textParts.length) {
2953
+ let textContent = await this.getText(message, textParts, options, { connectionClient });
2954
+ if (options.textType && options.textType !== '*') {
2955
+ textContent = {
2956
+ [options.textType]: textContent[options.textType] || '',
2957
+ hasMore: textContent.hasMore
2958
+ };
2959
+ }
2960
+ messageInfo.text = Object.assign(messageInfo.text, textContent);
2961
+ }
2962
+ }
2963
+ }
2964
+
2965
+ // Convert to web-safe HTML if requested
2966
+ if (options.preProcessHtml && messageInfo.text && (messageInfo.text.html || messageInfo.text.plain)) {
2967
+ messageInfo.text.html = mimeHtml({
2968
+ html: messageInfo.text.html,
2969
+ text: messageInfo.text.plain
2970
+ });
2971
+ messageInfo.text.webSafe = true;
2972
+ }
2973
+
2974
+ // Embed attached images as data URIs if requested
2975
+ if (options.embedAttachedImages && messageInfo.text && messageInfo.text.html && messageInfo.attachments) {
2976
+ let attachmentList = new Map();
2977
+ let partList = [];
2978
+
2979
+ // Find images referenced by CID
2980
+ for (let attachment of messageInfo.attachments) {
2981
+ let contentId = attachment.contentId && attachment.contentId.replace(/^<|>$/g, '');
2982
+ if (contentId && messageInfo.text.html.indexOf(contentId) >= 0) {
2983
+ attachmentList.set(contentId, { attachment, content: null });
2984
+
2985
+ let buf = Buffer.from(attachment.id, 'base64url');
2986
+ let part = buf.subarray(8).toString();
2987
+
2988
+ Object.defineProperty(attachment, 'part', {
2989
+ value: part,
2990
+ enumerable: false
2991
+ });
2992
+
2993
+ if (!partList.includes(part)) {
2994
+ partList.push(part);
2995
+ }
2996
+ }
2997
+ }
2998
+
2999
+ if (partList.length) {
3000
+ try {
3001
+ // Download all referenced images in batch
3002
+ let contentParts = await connectionClient.downloadMany(messageInfo.uid, partList, {
3003
+ uid: true
3004
+ });
3005
+
3006
+ if (contentParts) {
3007
+ for (let { attachment } of attachmentList.values()) {
3008
+ if (attachment.part && contentParts[attachment.part] && contentParts[attachment.part].content) {
3009
+ Object.defineProperty(attachment, 'content', {
3010
+ value: contentParts[attachment.part].content,
3011
+ enumerable: false
3012
+ });
3013
+ }
3014
+ }
3015
+
3016
+ // Replace CID references with data URIs
3017
+ messageInfo.text.html = messageInfo.text.html.replace(/\bcid:([^"'\s>]+)/g, (fullMatch, cidMatch) => {
3018
+ if (attachmentList.has(cidMatch)) {
3019
+ let { attachment } = attachmentList.get(cidMatch);
3020
+ if (attachment.content) {
3021
+ return `data:${attachment.contentType || 'application/octet-stream'};base64,${attachment.content.toString('base64')}`;
3022
+ }
3023
+ }
3024
+ return fullMatch;
3025
+ });
3026
+ }
3027
+ } catch (err) {
3028
+ this.logger.error({ msg: 'Attachment error', uid: messageInfo.uid, partList, err });
3029
+ }
3030
+ }
3031
+ }
3032
+
3033
+ // Add mailbox special use information
3034
+ if (this.listingEntry.specialUse) {
3035
+ messageInfo.specialUse = this.listingEntry.specialUse;
3036
+ }
3037
+
3038
+ // Determine message's special use folder
3039
+ for (let specialUseTag of ['\\Junk', '\\Sent', '\\Trash', '\\Inbox', '\\Drafts']) {
3040
+ if (this.listingEntry.specialUse === specialUseTag || (messageInfo.labels && messageInfo.labels.includes(specialUseTag))) {
3041
+ messageInfo.messageSpecialUse = specialUseTag;
3042
+ break;
3043
+ }
3044
+ }
3045
+
3046
+ return messageInfo;
3047
+ } finally {
3048
+ if (!options.skipLock) {
3049
+ this.connection.onTaskCompleted(connectionClient);
3050
+ }
3051
+ }
3052
+ }
3053
+
3054
+ /**
3055
+ * Updates message flags and labels
3056
+ * @param {Object} message - Message object with uid
3057
+ * @param {Object} updates - Updates to apply (flags, labels)
3058
+ * @param {Object} connectionOptions - Connection options
3059
+ * @returns {Object} Result of updates
3060
+ */
3061
+ async updateMessage(message, updates, connectionOptions) {
3062
+ updates = updates || {};
3063
+
3064
+ const connectionClient = await this.connection.getImapConnection(connectionOptions);
3065
+
3066
+ let lock = await this.getMailboxLock(connectionClient, { description: `Update message: ${message.uid}` });
3067
+
3068
+ try {
3069
+ let result = {};
3070
+
3071
+ // Update flags
3072
+ if (updates.flags) {
3073
+ if (updates.flags.set) {
3074
+ // If set exists then ignore add/delete calls
3075
+ let value = await connectionClient.messageFlagsSet(message.uid, updates.flags.set, { uid: true });
3076
+ result.flags = {
3077
+ set: value
3078
+ };
3079
+ } else {
3080
+ if (updates.flags.add && updates.flags.add.length) {
3081
+ let value = await connectionClient.messageFlagsAdd(message.uid, updates.flags.add, { uid: true });
3082
+ if (!result.flags) {
3083
+ result.flags = {};
3084
+ }
3085
+ result.flags.add = value;
3086
+ }
3087
+
3088
+ if (updates.flags.delete && updates.flags.delete.length) {
3089
+ let value = await connectionClient.messageFlagsRemove(message.uid, updates.flags.delete, { uid: true });
3090
+ if (!result.flags) {
3091
+ result.flags = {};
3092
+ }
3093
+ result.flags.delete = value;
3094
+ }
3095
+ }
3096
+ }
3097
+
3098
+ // Update Gmail labels
3099
+ if (updates.labels && this.isGmail) {
3100
+ if (updates.labels.set) {
3101
+ // If set exists then ignore add/delete calls
3102
+ let value = await connectionClient.messageFlagsSet(message.uid, updates.labels.set, { uid: true, useLabels: true });
3103
+ result.labels = {
3104
+ set: value
3105
+ };
3106
+ } else {
3107
+ if (updates.labels.add && updates.labels.add.length) {
3108
+ let value = await connectionClient.messageFlagsAdd(message.uid, updates.labels.add, { uid: true, useLabels: true });
3109
+ if (!result.labels) {
3110
+ result.labels = {};
3111
+ }
3112
+ result.labels.add = value;
3113
+ }
3114
+
3115
+ if (updates.labels.delete && updates.labels.delete.length) {
3116
+ let value = await connectionClient.messageFlagsRemove(message.uid, updates.labels.delete, { uid: true, useLabels: true });
3117
+ if (!result.labels) {
3118
+ result.labels = {};
3119
+ }
3120
+ result.labels.delete = value;
3121
+ }
3122
+ }
3123
+ }
3124
+
3125
+ return result;
3126
+ } finally {
3127
+ lock.release();
3128
+ this.connection.onTaskCompleted(connectionClient);
3129
+ }
3130
+ }
3131
+
3132
+ /**
3133
+ * Updates multiple messages based on search criteria
3134
+ * @param {Object} search - Search criteria
3135
+ * @param {Object} updates - Updates to apply
3136
+ * @param {Object} connectionOptions - Connection options
3137
+ * @returns {Object} Result of updates
3138
+ */
3139
+ async updateMessages(search, updates, connectionOptions) {
3140
+ updates = updates || {};
3141
+
3142
+ const connectionClient = await this.connection.getImapConnection(connectionOptions);
3143
+
3144
+ let lock = await this.getMailboxLock(connectionClient, { description: `Update messages` });
3145
+
3146
+ try {
3147
+ let result = {};
3148
+
3149
+ // Update flags for matching messages
3150
+ if (updates.flags) {
3151
+ if (updates.flags.set) {
3152
+ // If set exists then ignore add/delete calls
3153
+ let value = await connectionClient.messageFlagsSet(search, updates.flags.set, { uid: true });
3154
+ result.flags = {
3155
+ set: value
3156
+ };
3157
+ } else {
3158
+ if (updates.flags.add && updates.flags.add.length) {
3159
+ let value = await connectionClient.messageFlagsAdd(search, updates.flags.add, { uid: true });
3160
+ if (!result.flags) {
3161
+ result.flags = {};
3162
+ }
3163
+ result.flags.add = value;
3164
+ }
3165
+
3166
+ if (updates.flags.delete && updates.flags.delete.length) {
3167
+ let value = await connectionClient.messageFlagsRemove(search, updates.flags.delete, { uid: true });
3168
+ if (!result.flags) {
3169
+ result.flags = {};
3170
+ }
3171
+ result.flags.delete = value;
3172
+ }
3173
+ }
3174
+ }
3175
+
3176
+ // Update Gmail labels for matching messages
3177
+ if (updates.labels && this.isGmail) {
3178
+ if (updates.labels.set) {
3179
+ // If set exists then ignore add/delete calls
3180
+ let value = await connectionClient.messageFlagsSet(search, updates.labels.set, { uid: true, useLabels: true });
3181
+ result.labels = {
3182
+ set: value
3183
+ };
3184
+ } else {
3185
+ if (updates.labels.add && updates.labels.add.length) {
3186
+ let value = await connectionClient.messageFlagsAdd(search, updates.labels.add, { uid: true, useLabels: true });
3187
+ if (!result.labels) {
3188
+ result.labels = {};
3189
+ }
3190
+ result.labels.add = value;
3191
+ }
3192
+
3193
+ if (updates.labels.delete && updates.labels.delete.length) {
3194
+ let value = await connectionClient.messageFlagsRemove(search, updates.labels.delete, { uid: true, useLabels: true });
3195
+ if (!result.labels) {
3196
+ result.labels = {};
3197
+ }
3198
+ result.labels.delete = value;
3199
+ }
3200
+ }
3201
+ }
3202
+
3203
+ return result;
3204
+ } finally {
3205
+ lock.release();
3206
+ this.connection.onTaskCompleted(connectionClient);
3207
+ }
3208
+ }
3209
+
3210
+ /**
3211
+ * Moves a message to another mailbox
3212
+ * @param {Object} message - Message object with uid
3213
+ * @param {Object} target - Target mailbox with path
3214
+ * @param {Object} options - Move options
3215
+ * @param {Object} connectionOptions - Connection options
3216
+ * @returns {Object} Result with new message ID and UID
3217
+ */
3218
+ async moveMessage(message, target, options, connectionOptions) {
3219
+ target = target || {};
3220
+
3221
+ const connectionClient = await this.connection.getImapConnection(connectionOptions);
3222
+
3223
+ let lock = await this.getMailboxLock(connectionClient, { description: `Move message: ${message.uid} to: ${target.path}` });
3224
+
3225
+ try {
3226
+ let result = {};
3227
+
3228
+ if (target.path) {
3229
+ // Perform the move
3230
+ let value = await connectionClient.messageMove(message.uid, target.path, { uid: true });
3231
+ result.path = target.path;
3232
+
3233
+ // Get new UID in target mailbox
3234
+ if (value && value.uidMap && value.uidMap.has(message.uid)) {
3235
+ let uid = value.uidMap.get(message.uid);
3236
+ let packed = await this.connection.packUid(target.path, uid);
3237
+ result.id = packed;
3238
+ result.uid = uid;
3239
+ }
3240
+ }
3241
+
3242
+ return result;
3243
+ } finally {
3244
+ lock.release();
3245
+ this.connection.onTaskCompleted(connectionClient);
3246
+ }
3247
+ }
3248
+
3249
+ /**
3250
+ * Moves multiple messages to another mailbox
3251
+ * @param {Object} search - Search criteria
3252
+ * @param {Object} target - Target mailbox with path
3253
+ * @param {Object} connectionOptions - Connection options
3254
+ * @returns {Object} Result with ID mappings
3255
+ */
3256
+ async moveMessages(search, target, connectionOptions) {
3257
+ target = target || {};
3258
+
3259
+ const connectionClient = await this.connection.getImapConnection(connectionOptions);
3260
+
3261
+ let lock = await this.getMailboxLock(connectionClient, { description: `Move messages to: ${target.path}` });
3262
+
3263
+ try {
3264
+ let result = {};
3265
+
3266
+ if (target.path) {
3267
+ // Perform the move
3268
+ let value = await connectionClient.messageMove(search, target.path, { uid: true });
3269
+ result.path = target.path;
3270
+
3271
+ // Build ID map for moved messages
3272
+ if (value && value.uidMap && value.uidMap.size) {
3273
+ let moveMap = [];
3274
+ for (let [suid, tuid] of value.uidMap) {
3275
+ moveMap.push([await this.connection.packUid(this.path, suid), await this.connection.packUid(target.path, tuid)]);
3276
+ }
3277
+ result.idMap = moveMap;
3278
+ }
3279
+ }
3280
+
3281
+ return result;
3282
+ } finally {
3283
+ lock.release();
3284
+ this.connection.onTaskCompleted(connectionClient);
3285
+ }
3286
+ }
3287
+
3288
+ /**
3289
+ * Deletes a message or moves it to trash
3290
+ * @param {Object} message - Message object with uid
3291
+ * @param {Boolean} force - Force permanent deletion
3292
+ * @param {Object} connectionOptions - Connection options
3293
+ * @returns {Object} Result of deletion
3294
+ */
3295
+ async deleteMessage(message, force, connectionOptions) {
3296
+ const connectionClient = await this.connection.getImapConnection(connectionOptions);
3297
+
3298
+ let lock = await this.getMailboxLock(connectionClient, { description: `Delete message: ${message.uid}` });
3299
+
3300
+ try {
3301
+ let result = {};
3302
+
3303
+ // Permanently delete if in Trash/Junk or forced
3304
+ if (['\\Trash', '\\Junk'].includes(this.listingEntry.specialUse) || force) {
3305
+ // delete
3306
+ result.deleted = await connectionClient.messageDelete(message.uid, { uid: true });
3307
+ } else {
3308
+ // Move to trash
3309
+ // Find Trash folder path
3310
+ let trashMailbox = await this.connection.getSpecialUseMailbox('\\Trash');
3311
+ if (!trashMailbox || normalizePath(trashMailbox.path) === normalizePath(this.path)) {
3312
+ // No Trash found or already in trash - delete permanently
3313
+ result.deleted = await connectionClient.messageDelete(message.uid, { uid: true });
3314
+ } else {
3315
+ result.deleted = false;
3316
+ // We have a destination, so can move message to there
3317
+ let moved = await connectionClient.messageMove(message.uid, trashMailbox.path, { uid: true });
3318
+ if (moved) {
3319
+ result.moved = {
3320
+ destination: moved.destination
3321
+ };
3322
+ if (moved && moved.uidMap && moved.uidMap.has(message.uid)) {
3323
+ result.moved.message = await this.connection.packUid(trashMailbox.path, moved.uidMap.get(message.uid));
3324
+ }
3325
+ }
3326
+ }
3327
+ }
3328
+
3329
+ return result;
3330
+ } finally {
3331
+ lock.release();
3332
+ this.connection.onTaskCompleted(connectionClient);
3333
+ }
3334
+ }
3335
+
3336
+ /**
3337
+ * Deletes multiple messages or moves them to trash
3338
+ * @param {Object} search - Search criteria
3339
+ * @param {Boolean} force - Force permanent deletion
3340
+ * @param {Object} connectionOptions - Connection options
3341
+ * @returns {Object} Result of deletion
3342
+ */
3343
+ async deleteMessages(search, force, connectionOptions) {
3344
+ const connectionClient = await this.connection.getImapConnection(connectionOptions);
3345
+
3346
+ let lock = await this.getMailboxLock(connectionClient, { description: `Delete messages` });
3347
+
3348
+ try {
3349
+ let result = {};
3350
+
3351
+ // Permanently delete if in Trash/Junk or forced
3352
+ if (['\\Trash', '\\Junk'].includes(this.listingEntry.specialUse) || force) {
3353
+ // delete
3354
+ result.deleted = await connectionClient.messageDelete(search, { uid: true });
3355
+ } else {
3356
+ // Move to trash
3357
+ // Find Trash folder path
3358
+ let trashMailbox = await this.connection.getSpecialUseMailbox('\\Trash');
3359
+ if (!trashMailbox || normalizePath(trashMailbox.path) === normalizePath(this.path)) {
3360
+ // No Trash found or already in trash - delete permanently
3361
+ result.deleted = await connectionClient.messageDelete(search, { uid: true });
3362
+ } else {
3363
+ result.deleted = false;
3364
+ // We have a destination, so can move messages to there
3365
+ let moved = await connectionClient.messageMove(search, trashMailbox.path, { uid: true });
3366
+ if (moved) {
3367
+ result.moved = {
3368
+ destination: moved.destination
3369
+ };
3370
+ if (moved && moved.uidMap && moved.uidMap.size) {
3371
+ let moveMap = [];
3372
+ for (let [suid, tuid] of moved.uidMap) {
3373
+ moveMap.push([await this.connection.packUid(this.path, suid), await this.connection.packUid(trashMailbox.path, tuid)]);
3374
+ }
3375
+ result.moved.idMap = moveMap;
3376
+ }
3377
+ }
3378
+ }
3379
+ }
3380
+
3381
+ return result;
3382
+ } finally {
3383
+ lock.release();
3384
+ this.connection.onTaskCompleted(connectionClient);
3385
+ }
3386
+ }
3387
+
3388
+ /**
3389
+ * Lists messages in the mailbox with pagination
3390
+ * @param {Object} options - List options
3391
+ * @param {Number} options.page - Page number (0-based)
3392
+ * @param {String} options.cursor - Cursor string for pagination
3393
+ * @param {Number} options.pageSize - Messages per page
3394
+ * @param {Object} options.search - Search criteria
3395
+ * @param {Object} connectionOptions - Connection options
3396
+ * @returns {Object} Paginated message list
3397
+ */
3398
+ async listMessages(options, connectionOptions) {
3399
+ options = options || {};
3400
+
3401
+ let page = Number(options.page) || 0;
3402
+
3403
+ // Handle cursor-based pagination
3404
+ if (options.cursor) {
3405
+ let cursorPage = this.decodeCursorStr(options.cursor);
3406
+ if (typeof cursorPage === 'number' && cursorPage >= 0) {
3407
+ page = cursorPage;
3408
+ }
3409
+ }
3410
+
3411
+ let pageSize = Math.abs(Number(options.pageSize) || 20);
3412
+
3413
+ const connectionClient = await this.connection.getImapConnection(connectionOptions);
3414
+
3415
+ let lock = await this.getMailboxLock(connectionClient, { description: `List messages from: ${this.path}` });
3416
+
3417
+ try {
3418
+ let mailboxStatus = this.getMailboxStatus(connectionClient);
3419
+
3420
+ let messageCount = mailboxStatus.messages;
3421
+ let uidList;
3422
+ let opts = {};
3423
+
3424
+ // Apply search filter if provided
3425
+ if (options.search) {
3426
+ uidList = await connectionClient.search(options.search, { uid: true });
3427
+ uidList = !uidList ? [] : uidList.sort((a, b) => b - a); // newer first
3428
+ messageCount = uidList.length;
3429
+ }
3430
+
3431
+ // Calculate pagination
3432
+ let pages = Math.ceil(messageCount / pageSize) || 1;
3433
+
3434
+ if (page < 0) {
3435
+ page = 0;
3436
+ }
3437
+
3438
+ let messages = [];
3439
+ let seqMax, seqMin, range;
3440
+
3441
+ // Generate pagination cursors
3442
+ let nextPageCursor = page < pages - 1 ? this.encodeCursorString(page + 1) : null;
3443
+ let prevPageCursor = page > 0 ? this.encodeCursorString(Math.min(page - 1, pages - 1)) : null;
3444
+
3445
+ // Return empty result if no messages or page out of bounds
3446
+ if (!messageCount || page >= pages) {
3447
+ return {
3448
+ total: messageCount,
3449
+ page,
3450
+ pages,
3451
+ nextPageCursor,
3452
+ prevPageCursor,
3453
+ messages
3454
+ };
3455
+ }
3456
+
3457
+ // Calculate range to fetch
3458
+ if (options.search && uidList) {
3459
+ // For search results, use specific UIDs
3460
+ let start = page * pageSize;
3461
+ let uidRange = uidList.slice(start, start + pageSize).reverse();
3462
+ range = uidRange.join(',');
3463
+ opts.uid = true;
3464
+ } else {
3465
+ // For full listing, use sequence range
3466
+ seqMax = messageCount - page * pageSize;
3467
+ seqMin = seqMax - pageSize + 1;
3468
+
3469
+ if (seqMax >= messageCount) {
3470
+ seqMax = '*';
3471
+ }
3472
+
3473
+ if (seqMin < 1) {
3474
+ seqMin = 1;
3475
+ }
3476
+
3477
+ range = seqMin === seqMax ? `${seqMin}` : `${seqMin}:${seqMax}`;
3478
+ }
3479
+
3480
+ // Configure fields to fetch
3481
+ let fields = {
3482
+ uid: true,
3483
+ flags: true,
3484
+ size: true,
3485
+ bodyStructure: true,
3486
+ envelope: true,
3487
+ internalDate: true,
3488
+ emailId: true,
3489
+ threadId: true,
3490
+ labels: true
3491
+ };
3492
+
3493
+ // LarkSuite specific handling - ensure address headers are fetched
3494
+ if (this.isLarkSuite) {
3495
+ if (!fields.headers && fields.headers !== true) {
3496
+ fields.headers = [];
3497
+ }
3498
+ if (Array.isArray(fields.headers)) {
3499
+ // ensure that the response includes header fields because Lark Mail ENVELOPE response is unreliable
3500
+ for (let key of ['from', 'to', 'cc']) {
3501
+ if (!fields.headers.includes(key)) {
3502
+ fields.headers.push(key);
3503
+ }
3504
+ }
3505
+ }
3506
+ }
3507
+
3508
+ // Fetch messages in the range
3509
+ for await (let messageData of connectionClient.fetch(range, fields, opts)) {
3510
+ if (!messageData || !messageData.uid) {
3511
+ //TODO: support partial responses
3512
+ this.logger.debug({ msg: 'Partial FETCH response', code: 'partial_fetch', query: { range, fields, opts } });
3513
+ continue;
3514
+ }
3515
+ let messageInfo;
3516
+ try {
3517
+ messageInfo = await this.getMessageInfo(messageData);
3518
+ } catch (err) {
3519
+ // Return error info for failed messages
3520
+ messageInfo = {
3521
+ uid: messageData.uid,
3522
+ status: 'failed',
3523
+ error: `Failed to process message entry ${err.message}`
3524
+ };
3525
+ }
3526
+
3527
+ messages.push(messageInfo);
3528
+ }
3529
+
3530
+ return {
3531
+ total: messageCount,
3532
+ page,
3533
+ pages,
3534
+ nextPageCursor,
3535
+ prevPageCursor,
3536
+ // List newer entries first. Servers like yahoo do not return ordered list, so we need to order manually
3537
+ messages: messages.sort((a, b) => b.uid - a.uid)
3538
+ };
3539
+ } finally {
3540
+ lock.release();
3541
+ this.connection.onTaskCompleted(connectionClient);
3542
+ }
3543
+ }
3544
+
3545
+ /**
3546
+ * Updates the last sync timestamp in Redis
3547
+ */
3548
+ async markUpdated() {
3549
+ try {
3550
+ await this.connection.redis.hSetExists(this.connection.getAccountKey(), 'sync', new Date().toISOString());
3551
+ } catch (err) {
3552
+ this.logger.error({ msg: 'Redis error', err });
3553
+ }
3554
+ }
3555
+
3556
+ /**
3557
+ * Heuristic check if message might be a bounce
3558
+ * @param {Object} messageInfo - Message information
3559
+ * @returns {Boolean} True if likely a bounce
3560
+ */
3561
+ mightBeABounce(messageInfo) {
3562
+ // Only check messages in Inbox or Junk
3563
+ if (
3564
+ !['\\Inbox', '\\Junk'].includes(this.listingEntry.specialUse) &&
3565
+ !(messageInfo.labels?.includes('\\Inbox') || messageInfo.labels?.includes('\\Junk'))
3566
+ ) {
3567
+ return false;
3568
+ }
3569
+
3570
+ // Skip if already identified as delivery report
3571
+ if (messageInfo.deliveryReport) {
3572
+ // already processed
3573
+ return false;
3574
+ }
3575
+
3576
+ let name = (messageInfo.from && messageInfo.from.name) || '';
3577
+ let address = (messageInfo.from && messageInfo.from.address) || '';
3578
+
3579
+ // Check common bounce sender names
3580
+ if (/Mail Delivery System|Mail Delivery Subsystem|Internet Mail Delivery/i.test(name)) {
3581
+ return true;
3582
+ }
3583
+
3584
+ // Check common bounce sender addresses
3585
+ if (/mailer-daemon@|postmaster@/i.test(address)) {
3586
+ return true;
3587
+ }
3588
+
3589
+ // Check for delivery-status attachment + subject pattern
3590
+ let hasDeliveryStatus = false;
3591
+ for (let attachment of messageInfo.attachments || []) {
3592
+ if (attachment.contentType === 'message/delivery-status') {
3593
+ hasDeliveryStatus = true;
3594
+ }
3595
+ }
3596
+
3597
+ if (hasDeliveryStatus && /Undeliver(able|ed)/i.test(messageInfo.subject)) {
3598
+ return true;
3599
+ }
3600
+
3601
+ return false;
3602
+ }
3603
+
3604
+ /**
3605
+ * Heuristic check if message might be an ARF complaint
3606
+ * @param {Object} messageInfo - Message information
3607
+ * @returns {Boolean} True if likely a complaint
3608
+ */
3609
+ mightBeAComplaint(messageInfo) {
3610
+ // Only check inbox messages
3611
+ if (this.path !== 'INBOX' && !(this.isAllMail && messageInfo.labels && messageInfo.labels.includes('\\Inbox'))) {
3612
+ return false;
3613
+ }
3614
+
3615
+ let hasEmbeddedMessage = false;
3616
+ for (let attachment of messageInfo.attachments || []) {
3617
+ // Direct ARF indicator
3618
+ if (attachment.contentType === 'message/feedback-report') {
3619
+ return true;
3620
+ }
3621
+
3622
+ // Check for embedded message (complaint might contain original)
3623
+ if (['message/rfc822', 'message/rfc822-headers'].includes(attachment.contentType)) {
3624
+ hasEmbeddedMessage = true;
3625
+ }
3626
+ }
3627
+
3628
+ let fromAddress = (messageInfo.from && messageInfo.from.address) || '';
3629
+
3630
+ // Hotmail-specific complaint pattern
3631
+ if (hasEmbeddedMessage && fromAddress === 'staff@hotmail.com' && /complaint/i.test(messageInfo.subject)) {
3632
+ return true;
3633
+ }
3634
+
3635
+ return false;
3636
+ }
3637
+
3638
+ /**
3639
+ * Heuristic check if message might be a DSN (Delivery Status Notification)
3640
+ * @param {Object} messageInfo - Message information
3641
+ * @returns {Boolean} True if likely a DSN
3642
+ */
3643
+ mightBeDSNResponse(messageInfo) {
3644
+ // Only check inbox messages
3645
+ if (this.path !== 'INBOX' && !(this.isAllMail && messageInfo.labels && messageInfo.labels.includes('\\Inbox'))) {
3646
+ return false;
3647
+ }
3648
+
3649
+ // Check Content-Type header for multipart/report with delivery-status
3650
+ if (messageInfo.headers && messageInfo.headers['content-type'] && messageInfo.headers['content-type'].length) {
3651
+ let parsedContentType = libmime.parseHeaderValue(messageInfo.headers['content-type'].at(-1));
3652
+ if (
3653
+ parsedContentType &&
3654
+ parsedContentType.value &&
3655
+ parsedContentType.value.toLowerCase().trim() === 'multipart/report' &&
3656
+ parsedContentType.params['report-type'] === 'delivery-status'
3657
+ ) {
3658
+ return true;
3659
+ }
3660
+ }
3661
+
3662
+ return false;
3663
+ }
3664
+
3665
+ /**
3666
+ * Decodes a cursor string for pagination
3667
+ * @param {String} cursorStr - Base64-encoded cursor string
3668
+ * @returns {Number|null} Page number or null if invalid
3669
+ */
3670
+ decodeCursorStr(cursorStr) {
3671
+ let type = 'imap';
3672
+
3673
+ if (cursorStr) {
3674
+ // Extract cursor type prefix
3675
+ let splitPos = cursorStr.indexOf('_');
3676
+ if (splitPos >= 0) {
3677
+ let cursorType = cursorStr.substring(0, splitPos);
3678
+ cursorStr = cursorStr.substring(splitPos + 1);
3679
+ if (cursorType && type !== cursorType) {
3680
+ let error = new Error('Invalid cursor');
3681
+ error.code = 'InvalidCursorType';
3682
+ throw error;
3683
+ }
3684
+ }
3685
+
3686
+ try {
3687
+ // Decode cursor data
3688
+ let { page: cursorPage } = JSON.parse(Buffer.from(cursorStr, 'base64url'));
3689
+ if (typeof cursorPage === 'number' && cursorPage >= 0) {
3690
+ return cursorPage;
3691
+ }
3692
+ } catch (err) {
3693
+ this.logger.error({ msg: 'Cursor parsing error', cursorStr, err });
3694
+
3695
+ let error = new Error('Invalid paging cursor');
3696
+ error.code = 'InvalidCursorValue';
3697
+ error.statusCode = 400;
3698
+ throw error;
3699
+ }
3700
+ }
3701
+
3702
+ return null;
3703
+ }
3704
+
3705
+ /**
3706
+ * Encodes a page number into a cursor string
3707
+ * @param {Number} cursorPage - Page number to encode
3708
+ * @returns {String|null} Base64-encoded cursor string
3709
+ */
3710
+ encodeCursorString(cursorPage) {
3711
+ if (typeof cursorPage !== 'number' || cursorPage < 0) {
3712
+ return null;
3713
+ }
3714
+ cursorPage = cursorPage || 0;
3715
+ let type = 'imap';
3716
+ // Prefix with type for future extensibility
3717
+ return `${type}_${Buffer.from(JSON.stringify({ page: cursorPage })).toString('base64url')}`;
3718
+ }
3719
+ }
3720
+
3721
+ module.exports.Mailbox = Mailbox;