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,3805 @@
1
+ 'use strict';
2
+
3
+ const { BaseClient, metricsMeta } = require('./base-client');
4
+ const { Account } = require('../account');
5
+ const settings = require('../settings');
6
+ const { oauth2Apps } = require('../oauth2-apps');
7
+ const getSecret = require('../get-secret');
8
+ const msgpack = require('msgpack5')();
9
+ const he = require('he');
10
+ const { emitChangeEvent, prepareUrl } = require('../tools');
11
+ const { mimeHtml } = require('@postalsys/email-text-tools');
12
+ const crypto = require('crypto');
13
+ const { Gateway } = require('../gateway');
14
+ const { detectMimeType, detectExtension } = require('nodemailer/lib/mime-funcs/mime-types');
15
+
16
+ const {
17
+ REDIS_PREFIX,
18
+ AUTH_ERROR_NOTIFY,
19
+ AUTH_SUCCESS_NOTIFY,
20
+ EMAIL_SENT_NOTIFY,
21
+ OUTLOOK_EXPIRATION_TIME,
22
+ OUTLOOK_EXPIRATION_RENEW_TIME,
23
+ MESSAGE_UPDATED_NOTIFY,
24
+ MESSAGE_DELETED_NOTIFY,
25
+ MESSAGE_MISSING_NOTIFY
26
+ } = require('../consts');
27
+
28
+ // Maximum number of operations in a single batch request to Microsoft Graph API
29
+ const MAX_BATCH_SIZE = 20;
30
+
31
+ // Subscription is renewed automatically. But just in case, check once in an hour
32
+ const RENEW_WATCH_TTL = 60 * 60 * 1000; // 1h
33
+
34
+ /*
35
+ Supported operations status:
36
+ ✅ listMessages - with paging (cursor + page nr) and search queries (no support for to/cc/bcc queries)
37
+ ✅ getText
38
+ ✅ getMessage
39
+ ✅ updateMessage
40
+ ✅ updateMessages
41
+ ✅ listMailboxes
42
+ ✅ moveMessage
43
+ ✅ moveMessages
44
+ ✅ deleteMessage
45
+ ✅ deleteMessages
46
+ ✅ getRawMessage
47
+ 🟡 getQuota - not supported by MS Graph API
48
+ ✅ createMailbox
49
+ ✅ renameMailbox
50
+ ✅ deleteMailbox
51
+ ✅ getAttachment
52
+ ✅ submitMessage
53
+ ✅ uploadMessage - only drafts. Cannot change draft status
54
+ 🟡 subconnections - not supported
55
+ */
56
+
57
+ /**
58
+ * OutlookClient handles email operations through Microsoft Graph API
59
+ * Extends BaseClient to provide OAuth2-based email access for Outlook/Office365 accounts
60
+ */
61
+ class OutlookClient extends BaseClient {
62
+ constructor(account, options) {
63
+ super(account, options);
64
+
65
+ // Token caching to avoid repeated auth calls
66
+ this.cachedAccessToken = null;
67
+ this.cachedAccessTokenRaw = null;
68
+
69
+ // Pseudo path representing all messages across folders
70
+ this.path = '\\All';
71
+ this.listingEntry = { specialUse: '\\All' };
72
+
73
+ // Path for API calls - 'me' for current user or specific user email for delegated access
74
+ this.oauth2UserPath = 'me'; // can be changed to `users/${encodeURIComponent('shared@example.com')}` for delegated access
75
+
76
+ // Flags for background processing
77
+ this.processingHistory = null;
78
+ this.renewWatchTimer = null;
79
+
80
+ // MS Graph webhook subscription state (for metrics)
81
+ // Possible values: 'unset', 'valid', 'expired', 'failed', 'pending'
82
+ this.subscriptionState = 'unset';
83
+ }
84
+
85
+ /**
86
+ * Makes authenticated requests to Microsoft Graph API
87
+ * Handles token management and error responses
88
+ */
89
+ async request(...args) {
90
+ let result, accessToken;
91
+ try {
92
+ accessToken = await this.getToken();
93
+ } catch (err) {
94
+ this.logger.error({ msg: 'Failed to load access token', account: this.account, err });
95
+ throw err;
96
+ }
97
+
98
+ let [url, method, payload, options = {}] = args;
99
+
100
+ try {
101
+ if (!this.oAuth2Client) {
102
+ await this.getClient();
103
+ }
104
+
105
+ options.headers = options.headers || {};
106
+
107
+ // Build Prefer header with multiple preferences
108
+ // Request immutable IDs that don't change when messages are moved between folders
109
+ // https://learn.microsoft.com/en-us/graph/outlook-immutable-id
110
+ let preferValues = ['IdType="ImmutableId"'];
111
+
112
+ // If caller already set a Prefer header, merge it
113
+ if (options.headers.Prefer) {
114
+ preferValues.push(options.headers.Prefer);
115
+ }
116
+
117
+ options.headers.Prefer = preferValues.join(', ');
118
+
119
+ // Construct full API URL if not already absolute
120
+ let apiUrl = /^https:/.test(url) ? url : new URL(`/v1.0${url}`, this.oAuth2Client.apiBase).href;
121
+
122
+ result = await this.oAuth2Client.request(accessToken, apiUrl, method, payload, options);
123
+
124
+ // Track successful API request
125
+ metricsMeta({ account: this.account }, this.logger, 'oauth2ApiRequest', 'inc', { status: 'success', provider: 'outlook', statusCode: '200' });
126
+ } catch (err) {
127
+ // Track failed API request
128
+ const statusCode = String(err.oauthRequest?.status || 0);
129
+ metricsMeta({ account: this.account }, this.logger, 'oauth2ApiRequest', 'inc', { status: 'failure', provider: 'outlook', statusCode });
130
+
131
+ // Handle specific Graph API error codes
132
+ switch (err.oauthRequest?.response?.error?.code) {
133
+ case 'ErrorExecuteSearchStaleData': {
134
+ // Search cursor has expired
135
+ this.logger.error({ msg: 'Invalid or expired paging cursor', account: this.account, err });
136
+ let error = new Error('Invalid or expired paging cursor');
137
+ error.code = 'InvalidPagingCursor';
138
+ error.statusCode = err.oauthRequest?.status || 500;
139
+ throw error;
140
+ }
141
+ }
142
+
143
+ // Handle HTTP status codes
144
+ const status = err.oauthRequest?.status;
145
+ const isClientError = status >= 400 && status < 500;
146
+
147
+ switch (status) {
148
+ case 401:
149
+ this.logger.error({ msg: 'Failed to authenticate API request', account: this.account, accessToken, err });
150
+ throw err;
151
+
152
+ case 429:
153
+ // Rate limiting
154
+ this.logger.error({ msg: 'API request was throttled', account: this.account, err });
155
+ throw err;
156
+
157
+ default:
158
+ // Log client errors (4xx) at debug level - these are expected operational errors
159
+ // Log server errors (5xx) and other failures at error level
160
+ if (isClientError) {
161
+ this.logger.debug({ msg: 'API request failed with client error', account: this.account, err });
162
+ } else {
163
+ this.logger.error({ msg: 'Failed to run API request', account: this.account, err });
164
+ }
165
+ throw err;
166
+ }
167
+ }
168
+
169
+ return result;
170
+ }
171
+
172
+ // PUBLIC METHODS
173
+
174
+ /**
175
+ * Initialize the Outlook client connection
176
+ * Sets up OAuth2 authentication, validates access, and starts background processes
177
+ */
178
+ async init() {
179
+ this.state = 'connecting';
180
+ await this.setStateVal();
181
+
182
+ await this.getAccount();
183
+ await this.prepareDelegatedAccount();
184
+ await this.getClient(true);
185
+
186
+ let accountData = await this.accountObject.loadAccountData();
187
+
188
+ // Check if send-only mode
189
+ // Note: Scopes are checked at initialization and after account updates. If scopes change
190
+ // during token refresh (rare - typically requires re-authorization), the account must be
191
+ // reinitialized to detect the change. Consider checking scopes periodically if dynamic
192
+ // scope changes become common.
193
+ const scopes = accountData.oauth2?.accessToken?.scope || accountData.oauth2?.scope || [];
194
+ const { hasSendScope, hasReadScope } = this.accountObject.checkAccountScopes('outlook', scopes);
195
+ const isSendOnly = hasSendScope && !hasReadScope;
196
+
197
+ this.logger.debug({
198
+ msg: 'Account scopes loaded',
199
+ account: this.account,
200
+ scopes,
201
+ hasSendScope,
202
+ hasReadScope,
203
+ isSendOnly
204
+ });
205
+
206
+ // Verify authentication by fetching user profile
207
+ let profileRes;
208
+ try {
209
+ profileRes = await this.request(`/${this.oauth2UserPath}`, 'get', {});
210
+ } catch (err) {
211
+ this.state = 'authenticationError';
212
+ await this.setStateVal();
213
+
214
+ err.authenticationFailed = true;
215
+
216
+ if (!err.errorNotified) {
217
+ err.errorNotified = true;
218
+ await this.notify(false, AUTH_ERROR_NOTIFY, {
219
+ response: err.oauthRequest?.response?.error?.message || err.response,
220
+ serverResponseCode: 'ApiRequestError'
221
+ });
222
+ }
223
+
224
+ throw err;
225
+ }
226
+
227
+ let updates = {};
228
+
229
+ // Set locale from user preferences if not already set
230
+ if (!accountData.locale && !isSendOnly) {
231
+ let locale = (profileRes.preferredLanguage || '').toString().split(/[-_]/).shift().trim().toLowerCase();
232
+ if (locale) {
233
+ updates.locale = locale;
234
+ }
235
+ }
236
+
237
+ // Update username if it has changed (e.g., after email address change)
238
+ if (profileRes.userPrincipalName && accountData.oauth2.auth?.user !== profileRes.userPrincipalName && !accountData.oauth2.auth?.delegatedUser) {
239
+ updates.oauth2 = {
240
+ partial: true,
241
+ auth: Object.assign(accountData.oauth2.auth || {}, {
242
+ // update username
243
+ user: profileRes.userPrincipalName
244
+ })
245
+ };
246
+ }
247
+
248
+ if (Object.keys(updates).length) {
249
+ await this.accountObject.update(updates);
250
+ // Note: Metadata updates (name, locale) don't affect OAuth2 scopes
251
+ // No need to reload account data or recalculate scope-based flags
252
+ }
253
+
254
+ this.logger.debug({
255
+ msg: isSendOnly ? 'Initializing Outlook send-only account' : 'Initializing Outlook account',
256
+ provider: accountData.oauth2.provider,
257
+ user: accountData.oauth2.auth?.user,
258
+ sendOnly: isSendOnly
259
+ });
260
+
261
+ this.state = 'connected';
262
+ await this.setStateVal();
263
+
264
+ // Check if this is the first successful connection
265
+ let prevConnectedCount = await this.redis.hget(this.getAccountKey(), `state:count:connected`);
266
+ let isFirstSuccessfulConnection = prevConnectedCount === '0'; // string zero means the account has been initialized but not yet connected
267
+
268
+ let isiInitial = !!isFirstSuccessfulConnection;
269
+
270
+ if (!isFirstSuccessfulConnection) {
271
+ // check if the connection was previously in an errored state
272
+ let prevLastErrorState = await this.redis.hget(this.getAccountKey(), 'lastErrorState');
273
+ if (prevLastErrorState) {
274
+ try {
275
+ prevLastErrorState = JSON.parse(prevLastErrorState);
276
+ } catch (err) {
277
+ // ignore
278
+ }
279
+ }
280
+
281
+ if (prevLastErrorState && typeof prevLastErrorState === 'object' && Object.keys(prevLastErrorState).length) {
282
+ // was previously errored
283
+ isFirstSuccessfulConnection = true;
284
+ }
285
+ }
286
+
287
+ if (isFirstSuccessfulConnection) {
288
+ this.logger.info({ msg: 'Successful login without a previous active session', account: this.account, isiInitial, prevActive: false });
289
+ await this.notify(false, AUTH_SUCCESS_NOTIFY, {
290
+ user: accountData.oauth2?.auth?.user
291
+ });
292
+ } else {
293
+ this.logger.info({ msg: 'Successful login with a previous active session', account: this.account, isiInitial, prevActive: true });
294
+ }
295
+
296
+ // Clear any previous error states
297
+ await this.redis.hdel(this.getAccountKey(), 'lastErrorState', 'lastError:errorCount', 'lastError:first');
298
+ await emitChangeEvent(this.logger, this.account, 'state', this.state);
299
+
300
+ if (!isSendOnly) {
301
+ // additional operations for full access accounts
302
+
303
+ // Set up webhook subscription for real-time updates
304
+ await this.ensureSubscription();
305
+ this.setupRenewWatchTimer();
306
+
307
+ // Cache mailbox folder structure
308
+ try {
309
+ await this.listMailboxes();
310
+ } catch (err) {
311
+ this.logger.error({ msg: 'Failed to renew mailbox folder cache', err });
312
+ }
313
+
314
+ // Start processing any pending webhook notifications
315
+ this.triggerSync();
316
+ }
317
+ }
318
+
319
+ /**
320
+ * Close the client connection and clean up resources
321
+ */
322
+ async close() {
323
+ clearTimeout(this.renewWatchTimer);
324
+ this.closed = true;
325
+
326
+ if (['init', 'connecting', 'syncing', 'connected'].includes(this.state)) {
327
+ this.state = 'disconnected';
328
+ await this.setStateVal();
329
+ await emitChangeEvent(this.logger, this.account, 'state', this.state);
330
+ }
331
+
332
+ return null;
333
+ }
334
+
335
+ /**
336
+ * Get the current connection state from Redis
337
+ */
338
+ async currentState() {
339
+ return (await this.redis.hget(this.getAccountKey(), 'state')) || 'disconnected';
340
+ }
341
+
342
+ /**
343
+ * Delete the account and clean up resources
344
+ */
345
+ async delete() {
346
+ clearTimeout(this.renewWatchTimer);
347
+ this.closed = true;
348
+
349
+ if (['init', 'connecting', 'syncing', 'connected'].includes(this.state)) {
350
+ this.state = 'disconnected';
351
+ await this.setStateVal();
352
+ await emitChangeEvent(this.logger, this.account, 'state', this.state);
353
+ }
354
+
355
+ return null;
356
+ }
357
+
358
+ /**
359
+ * Reconnect to the account (re-initialize)
360
+ */
361
+ async reconnect() {
362
+ return await this.init();
363
+ }
364
+
365
+ /**
366
+ * List all mailbox folders with optional status information
367
+ * Caches results and only refreshes when changes are detected
368
+ */
369
+ async listMailboxes(options) {
370
+ await this.prepare();
371
+
372
+ let mailboxListing;
373
+
374
+ let cachedListing = await this.getCachedMailboxListing();
375
+
376
+ // Refresh cache if:
377
+ // 1. No cached listing exists
378
+ // 2. Message counts are requested
379
+ // 3. Folder structure has changed
380
+ if (!cachedListing || options?.statusQuery?.messages || (await this.renewMailboxFolderCache())) {
381
+ // Has changes or counters requested
382
+ mailboxListing = await this.getMailboxListing();
383
+ try {
384
+ await this.redis.hset(this.getAccountCacheKey(), 'outlookMailboxListing', JSON.stringify(mailboxListing));
385
+ } catch (err) {
386
+ this.logger.error({ msg: 'Failed to cache mailbox listing', err });
387
+ }
388
+
389
+ if (!cachedListing && !options?.statusQuery?.messages) {
390
+ // Force delta update as it was not called previously
391
+ await this.renewMailboxFolderCache();
392
+ }
393
+ } else {
394
+ // No changes, use cached listing
395
+ mailboxListing = cachedListing;
396
+ }
397
+
398
+ // Format mailbox data for API response
399
+ let mailboxes = mailboxListing
400
+ .map(entry => {
401
+ let folderData = {
402
+ id: entry.id,
403
+ path: entry.pathName,
404
+ delimiter: '/',
405
+ parentPath: entry.parentPath,
406
+ name: entry.displayName,
407
+ listed: true,
408
+ subscribed: true
409
+ };
410
+
411
+ if (entry.specialUse) {
412
+ folderData.specialUse = entry.specialUse;
413
+ folderData.specialUseSource = 'extension';
414
+ }
415
+
416
+ // Include message counts if requested
417
+ if (options?.statusQuery?.messages) {
418
+ folderData.status = {
419
+ messages: entry.totalItemCount,
420
+ unseen: entry.unreadItemCount
421
+ };
422
+ }
423
+
424
+ return folderData;
425
+ })
426
+ .sort((a, b) => {
427
+ // INBOX always comes first
428
+ if (a.path === 'INBOX' || a.specialUse === '\\Inbox') {
429
+ return -1;
430
+ } else if (b.path === 'INBOX' || b.specialUse === '\\Inbox') {
431
+ return 1;
432
+ }
433
+
434
+ // Special use folders come before regular folders
435
+ if (a.specialUse && !b.specialUse) {
436
+ return -1;
437
+ } else if (!a.specialUse && b.specialUse) {
438
+ return 1;
439
+ }
440
+
441
+ // Alphabetical sorting for the rest
442
+ return a.path.toLowerCase().localeCompare(b.path.toLowerCase());
443
+ });
444
+
445
+ return mailboxes;
446
+ }
447
+
448
+ /**
449
+ * List messages in a folder with search and pagination support
450
+ * Supports both folder-specific and cross-folder searches
451
+ */
452
+ async listMessages(query, options) {
453
+ options = options || {};
454
+
455
+ await this.prepare();
456
+
457
+ let path = [].concat(query.path || []).join('/');
458
+
459
+ let folder;
460
+ let cachedListing;
461
+ let mailboxListing;
462
+
463
+ // Handle special "\\All" path for cross-folder searches
464
+ if (path === '\\All') {
465
+ folder = null;
466
+ cachedListing = await this.getCachedMailboxListing();
467
+ mailboxListing = cachedListing || (await this.getMailboxListing());
468
+ } else {
469
+ folder = await this.resolveFolder(path);
470
+ if (!folder) {
471
+ let error = new Error('Listing failed');
472
+ error.info = {
473
+ response: 'Not able to find mailbox folder'
474
+ };
475
+ error.code = 'NotFound';
476
+ error.statusCode = 404;
477
+ throw error;
478
+ }
479
+ path = folder.pathName;
480
+ }
481
+
482
+ // Helper to resolve folder from parentFolderId
483
+ let quickResolveFolder = parentFolderId => {
484
+ if (folder) {
485
+ return folder;
486
+ }
487
+ return mailboxListing.find(entry => entry.id === parentFolderId);
488
+ };
489
+
490
+ // Pagination setup
491
+ let page = Number(query.page) || 0;
492
+ let pageSize = Math.abs(Number(query.pageSize) || 20);
493
+ let $skiptoken;
494
+
495
+ // Decode cursor if provided
496
+ if (query.cursor) {
497
+ let { cursorPage, skipToken } = this.decodeCursorStr(query.cursor);
498
+ if (typeof cursorPage === 'number' && cursorPage >= 0) {
499
+ page = cursorPage;
500
+ }
501
+ if (skipToken) {
502
+ $skiptoken = skipToken;
503
+ }
504
+ }
505
+
506
+ // Build Graph API query parameters
507
+ let requestQuery = {
508
+ $count: true,
509
+ $top: pageSize,
510
+ $skip: page * pageSize,
511
+ $skiptoken,
512
+ $orderBy: 'receivedDateTime desc',
513
+ $select: (options.metadataOnly
514
+ ? ['id', 'conversationId', 'receivedDateTime', 'isRead', 'isDraft', 'flag', 'body', 'subject', 'from', 'replyTo', 'sender', 'internetMessageId']
515
+ : [
516
+ 'id',
517
+ 'conversationId',
518
+ 'receivedDateTime',
519
+ 'isRead',
520
+ 'isDraft',
521
+ 'flag',
522
+ 'body',
523
+ 'subject',
524
+ 'from',
525
+ 'replyTo',
526
+ 'sender',
527
+ 'toRecipients',
528
+ 'ccRecipients',
529
+ 'bccRecipients',
530
+ 'internetMessageId',
531
+ 'bodyPreview'
532
+ ]
533
+ )
534
+ .concat(!folder ? 'parentFolderId' : [])
535
+ .join(','),
536
+ $expand: options.metadataOnly ? undefined : 'attachments($select=id,name,contentType,size,isInline,microsoft.graph.fileAttachment/contentId)'
537
+ };
538
+
539
+ let useOutlookSearch = false;
540
+ let skipToken = null;
541
+
542
+ // Handle search queries
543
+ if (query.search) {
544
+ if (query.useOutlookSearch) {
545
+ // Use Outlook's native search syntax
546
+ const $search = this.prepareSearchQuery(query.search);
547
+ if ($search) {
548
+ requestQuery.$search = `"${$search}"`;
549
+ // remove unsupported request arguments for search
550
+ for (let disabledParam of ['$skip', '$orderBy', '$count']) {
551
+ delete requestQuery[disabledParam];
552
+ }
553
+ useOutlookSearch = true;
554
+ }
555
+ } else {
556
+ // Use OData filters for more precise searching
557
+ const $filter = this.prepareFilterQuery(query.search);
558
+ if ($filter) {
559
+ // we need to have receivedDateTime as the first filtering property, otherwise ordering will fail
560
+ requestQuery.$filter = `receivedDateTime gt 1970-01-01T00:00:00.000Z and ${$filter}`;
561
+ }
562
+ }
563
+ }
564
+
565
+ let messages = [];
566
+ let totalMessages;
567
+
568
+ // Execute the message list request
569
+ try {
570
+ let listing = await this.request(`/${this.oauth2UserPath}/${folder ? `mailFolders/${folder.id}/` : ''}messages`, 'get', requestQuery);
571
+
572
+ totalMessages = !isNaN(listing['@odata.count']) ? Number(listing['@odata.count']) : undefined;
573
+
574
+ // Extract continuation token for search results
575
+ if (useOutlookSearch && listing['@odata.nextLink']) {
576
+ let nextLinkObj = new URL(listing['@odata.nextLink']);
577
+ skipToken = nextLinkObj.searchParams.get('$skiptoken');
578
+ }
579
+
580
+ messages =
581
+ listing?.value?.map(messageData =>
582
+ this.formatMessage(messageData, { path: quickResolveFolder(messageData.parentFolderId)?.pathName, showPath: !folder })
583
+ ) || [];
584
+ } catch (err) {
585
+ this.logger.error({
586
+ msg: 'Failed to list messages',
587
+ mailboxId: folder?.id,
588
+ path,
589
+ err
590
+ });
591
+ throw err;
592
+ }
593
+
594
+ // Calculate pagination info
595
+ let pages = typeof totalMessages === 'number' ? Math.ceil(totalMessages / pageSize) || 1 : undefined;
596
+
597
+ if (page < 0) {
598
+ page = 0;
599
+ }
600
+
601
+ let nextPageCursor = page < pages - 1 || skipToken ? this.encodeCursorString(page + 1, skipToken) : null;
602
+ // no previous page cursor if we are using skip token for paging
603
+ let prevPageCursor = skipToken ? undefined : page > 0 ? this.encodeCursorString(Math.min(page - 1, pages - 1)) : null;
604
+
605
+ return {
606
+ total: totalMessages,
607
+ page,
608
+ pages,
609
+ nextPageCursor,
610
+ prevPageCursor,
611
+ messages
612
+ };
613
+ }
614
+
615
+ /**
616
+ * Get the raw RFC822 message content
617
+ */
618
+ async getRawMessage(emailId) {
619
+ await this.prepare();
620
+
621
+ let raw;
622
+
623
+ try {
624
+ raw = await this.request(`/${this.oauth2UserPath}/messages/${emailId}/$value`, 'get', Buffer.alloc(0), { returnText: true });
625
+ } catch (err) {
626
+ switch (err.oauthRequest?.status) {
627
+ case 404: {
628
+ let error = new Error('Unknown message');
629
+ error.info = {
630
+ response: `Message does not exist`
631
+ };
632
+ error.code = 'NotFound';
633
+ error.statusCode = 404;
634
+ throw error;
635
+ }
636
+
637
+ case 400: {
638
+ let error = new Error('Invalid request');
639
+ error.info = {
640
+ response: err.oauthRequest?.response?.error?.message || `Invalid request`
641
+ };
642
+ error.code = err.oauthRequest?.response?.error?.code || 'InvalidRequest';
643
+ error.statusCode = 400;
644
+ throw error;
645
+ }
646
+
647
+ default:
648
+ this.logger.error({
649
+ msg: 'Failed to fetch raw message',
650
+ emailId,
651
+ err
652
+ });
653
+ throw err;
654
+ }
655
+ }
656
+
657
+ return raw ? Buffer.from(raw) : null;
658
+ }
659
+
660
+ /**
661
+ * Delete a message either permanently or by moving to trash
662
+ * @param {string} emailId - Message ID to delete
663
+ * @param {boolean} force - If true, permanently delete; otherwise move to trash
664
+ */
665
+ async deleteMessage(emailId, force) {
666
+ await this.prepare();
667
+
668
+ if (force) {
669
+ // Permanent deletion
670
+ try {
671
+ await this.request(`/${this.oauth2UserPath}/messages/${emailId}`, 'delete', Buffer.alloc(0), { returnText: true });
672
+ } catch (err) {
673
+ this.logger.error({
674
+ msg: 'Failed to delete message',
675
+ emailId,
676
+ err
677
+ });
678
+
679
+ switch (err?.oauthRequest?.response?.error?.code) {
680
+ case 'ErrorCannotDeleteObject':
681
+ // does not exist
682
+ return {
683
+ deleted: false
684
+ };
685
+
686
+ default:
687
+ throw err;
688
+ }
689
+ }
690
+
691
+ return {
692
+ deleted: true
693
+ };
694
+ }
695
+
696
+ // Move to trash (soft delete)
697
+ let messageData;
698
+ try {
699
+ messageData = await this.request(`/${this.oauth2UserPath}/messages/${emailId}/move`, 'post', {
700
+ destinationId: 'deleteditems'
701
+ });
702
+ if (!messageData) {
703
+ throw new Error('Failed to move message to Trash');
704
+ }
705
+ } catch (err) {
706
+ this.logger.error({
707
+ msg: 'Failed to move message to Trash',
708
+ emailId,
709
+ err
710
+ });
711
+ throw err;
712
+ }
713
+
714
+ // Resolve the destination folder for the response
715
+ let folder;
716
+ try {
717
+ folder = await this.resolveFolder(messageData.parentFolderId, { byId: true });
718
+ } catch (err) {
719
+ this.logger.error({
720
+ msg: 'Failed to resolve folder for message',
721
+ emailId,
722
+ err
723
+ });
724
+ }
725
+
726
+ return {
727
+ deleted: true,
728
+ moved: {
729
+ destination: folder?.pathName,
730
+ message: emailId
731
+ }
732
+ };
733
+ }
734
+
735
+ /**
736
+ * Delete multiple messages matching search criteria
737
+ * Uses batch operations for efficiency
738
+ */
739
+ async deleteMessages(path, search, force) {
740
+ await this.prepare();
741
+
742
+ let folder;
743
+ if (!force) {
744
+ try {
745
+ folder = await this.resolveFolder('\\Trash');
746
+
747
+ // If we're already in trash, force delete
748
+ if (!force && folder?.specialUse === '\\Trash') {
749
+ force = true;
750
+ }
751
+
752
+ if (!force && !folder) {
753
+ throw new Error('Trash folder was not found');
754
+ }
755
+ } catch (err) {
756
+ this.logger.error({
757
+ msg: 'Failed to resolve folder for Trash',
758
+ err
759
+ });
760
+ }
761
+ }
762
+
763
+ // Step 1. Resolve matching messages
764
+ let emailIds = search.emailIds || (await this.searchEmailIds(path, search));
765
+
766
+ if (!emailIds?.length) {
767
+ // nothing to do here
768
+ return { deleted: false };
769
+ }
770
+
771
+ // Prepare batch operation variables
772
+ let batch = [];
773
+ let idGen = 0;
774
+ let updatedEmailIds = [];
775
+ let messageMap = new Map();
776
+
777
+ /**
778
+ * Submit a batch of delete operations to Graph API
779
+ */
780
+ let submitBatch = async () => {
781
+ let responseData;
782
+ try {
783
+ responseData = await this.request(`/$batch`, 'post', {
784
+ requests: batch
785
+ });
786
+ for (let response of responseData?.responses || []) {
787
+ if (response?.status >= 200 && response?.status < 300) {
788
+ let emailId = messageMap.get(response.id);
789
+ if (emailId) {
790
+ updatedEmailIds.push(emailId);
791
+ }
792
+ }
793
+ }
794
+ } catch (err) {
795
+ this.logger.error({
796
+ msg: 'Failed to run batch operation',
797
+ err
798
+ });
799
+ throw err;
800
+ } finally {
801
+ batch = [];
802
+ messageMap = new Map();
803
+ }
804
+ };
805
+
806
+ /**
807
+ * Format a single delete request for the batch
808
+ */
809
+ let formatRequest = emailId => {
810
+ let reqId = `msg_${++idGen}`;
811
+ messageMap.set(reqId, emailId);
812
+
813
+ if (force) {
814
+ // Permanent delete
815
+ return {
816
+ id: reqId,
817
+ method: 'DELETE',
818
+ url: `/${this.oauth2UserPath}/messages/${emailId}`
819
+ };
820
+ } else {
821
+ // Move to trash
822
+ return {
823
+ id: reqId,
824
+ method: 'POST',
825
+ url: `/${this.oauth2UserPath}/messages/${emailId}/move`,
826
+ body: { destinationId: 'deleteditems' },
827
+ headers: {
828
+ 'Content-Type': 'application/json'
829
+ }
830
+ };
831
+ }
832
+ };
833
+
834
+ // Process messages in batches
835
+ for (let emailId of emailIds) {
836
+ batch.push(formatRequest(emailId));
837
+ // submit batch when it reaches max size
838
+ if (batch.length >= MAX_BATCH_SIZE) {
839
+ await submitBatch(batch);
840
+ }
841
+ }
842
+ if (batch.length) {
843
+ // submit remaining batch
844
+ await submitBatch(batch);
845
+ }
846
+
847
+ return Object.assign(
848
+ { deleted: true },
849
+ !force
850
+ ? {
851
+ moved: {
852
+ destination: folder.pathName,
853
+ emailIds: updatedEmailIds
854
+ }
855
+ }
856
+ : {
857
+ deletedMessages: {
858
+ emailIds: updatedEmailIds
859
+ }
860
+ }
861
+ );
862
+ }
863
+
864
+ /**
865
+ * Update message flags (Graph API only supports \Seen and \Flagged)
866
+ */
867
+ async updateMessage(emailId, updates) {
868
+ await this.prepare();
869
+ updates = updates || {};
870
+
871
+ let addFlags = updates.flags?.add || [];
872
+ let deleteFlags = updates.flags?.delete || [];
873
+
874
+ // Handle flag set operations
875
+ if (updates.flags?.set) {
876
+ for (let flag of ['\\Seen', '\\Flagged']) {
877
+ if (updates.flags.set.includes(flag)) {
878
+ addFlags.push(flag);
879
+ } else {
880
+ deleteFlags.push(flag);
881
+ }
882
+ }
883
+ }
884
+
885
+ // Map IMAP flags to Graph API properties
886
+ let flagUpdates = {};
887
+
888
+ if (addFlags.includes('\\Seen')) {
889
+ flagUpdates.isRead = true;
890
+ }
891
+ if (deleteFlags.includes('\\Seen')) {
892
+ flagUpdates.isRead = false;
893
+ }
894
+
895
+ if (addFlags.includes('\\Flagged')) {
896
+ flagUpdates.flag = { flagStatus: 'flagged' };
897
+ }
898
+ if (deleteFlags.includes('\\Flagged')) {
899
+ flagUpdates.flag = { flagStatus: 'notFlagged' };
900
+ }
901
+
902
+ // Handle label (category) operations
903
+ if (updates.labels) {
904
+ let categories = [];
905
+
906
+ if (updates.labels.set) {
907
+ // Set replaces all categories
908
+ categories = updates.labels.set;
909
+ } else {
910
+ // For add/delete, fetch current categories first
911
+ let currentMessage = await this.request(`/${this.oauth2UserPath}/messages/${emailId}`, 'get', {
912
+ $select: 'categories'
913
+ });
914
+
915
+ categories = currentMessage.categories || [];
916
+
917
+ if (updates.labels.add) {
918
+ for (let label of updates.labels.add) {
919
+ if (!categories.includes(label)) {
920
+ categories.push(label);
921
+ }
922
+ }
923
+ }
924
+
925
+ if (updates.labels.delete) {
926
+ categories = categories.filter(c => !updates.labels.delete.includes(c));
927
+ }
928
+ }
929
+
930
+ flagUpdates.categories = categories;
931
+ }
932
+
933
+ let modifyResult;
934
+
935
+ try {
936
+ modifyResult = await this.request(`/${this.oauth2UserPath}/messages/${emailId}`, 'patch', flagUpdates);
937
+ } catch (err) {
938
+ this.logger.error({
939
+ msg: 'Failed to update message',
940
+ emailId,
941
+ flagUpdates,
942
+ err
943
+ });
944
+
945
+ switch (err.oauthRequest?.status) {
946
+ case 400: {
947
+ let error = new Error('Invalid request');
948
+ error.info = {
949
+ response: err.oauthRequest?.response?.error?.message || `Invalid request`
950
+ };
951
+ error.code = err.oauthRequest?.response?.error?.code || 'InvalidRequest';
952
+ error.statusCode = 400;
953
+ throw error;
954
+ }
955
+
956
+ default:
957
+ throw err;
958
+ }
959
+ }
960
+
961
+ // Build the result flag set
962
+ const result = [];
963
+ if (modifyResult.isRead) {
964
+ result.push('\\Seen');
965
+ }
966
+ if (modifyResult.isDraft) {
967
+ result.push('\\Draft');
968
+ }
969
+ if (modifyResult.flag?.flagStatus === 'flagged') {
970
+ result.push('\\Flagged');
971
+ }
972
+
973
+ let response = {
974
+ flags: Object.assign({}, updates.flags || {}, { result })
975
+ };
976
+
977
+ // Include labels in response if they were updated
978
+ if (updates.labels) {
979
+ response.labels = Object.assign({}, updates.labels || {}, { result: modifyResult.categories || [] });
980
+ }
981
+
982
+ return response;
983
+ }
984
+
985
+ /**
986
+ * Update flags for multiple messages matching search criteria
987
+ * Uses batch operations for efficiency
988
+ */
989
+ async updateMessages(path, search, updates) {
990
+ await this.prepare();
991
+
992
+ updates = updates || {};
993
+
994
+ let addFlags = updates.flags?.add || [];
995
+ let deleteFlags = updates.flags?.delete || [];
996
+
997
+ // Handle flag set operations
998
+ if (updates.flags?.set) {
999
+ for (let flag of ['\\Seen', '\\Flagged']) {
1000
+ if (updates.flags.set.includes(flag)) {
1001
+ addFlags.push(flag);
1002
+ } else {
1003
+ deleteFlags.push(flag);
1004
+ }
1005
+ }
1006
+ }
1007
+
1008
+ // Map IMAP flags to Graph API properties
1009
+ let flagUpdates = {};
1010
+
1011
+ if (addFlags.includes('\\Seen')) {
1012
+ flagUpdates.isRead = true;
1013
+ }
1014
+ if (deleteFlags.includes('\\Seen')) {
1015
+ flagUpdates.isRead = false;
1016
+ }
1017
+
1018
+ if (addFlags.includes('\\Flagged')) {
1019
+ flagUpdates.flag = { flagStatus: 'flagged' };
1020
+ }
1021
+ if (deleteFlags.includes('\\Flagged')) {
1022
+ flagUpdates.flag = { flagStatus: 'notFlagged' };
1023
+ }
1024
+
1025
+ // Step 1. Resolve matching messages
1026
+ let emailIds = search.emailIds || (await this.searchEmailIds(path, search));
1027
+
1028
+ if (!emailIds?.length) {
1029
+ // nothing to do here
1030
+ return updates;
1031
+ }
1032
+
1033
+ // Handle label (category) operations
1034
+ // For add/delete, we need to fetch current categories first
1035
+ let needsCurrentCategories = updates.labels && !updates.labels.set;
1036
+ let currentCategories = new Map();
1037
+
1038
+ if (needsCurrentCategories) {
1039
+ // Fetch current categories for all messages in batches
1040
+ let fetchBatch = [];
1041
+ let fetchIdGen = 0;
1042
+ let fetchMessageMap = new Map();
1043
+
1044
+ let submitFetchBatch = async () => {
1045
+ let responseData;
1046
+ try {
1047
+ responseData = await this.request(`/$batch`, 'post', {
1048
+ requests: fetchBatch
1049
+ });
1050
+ for (let response of responseData?.responses || []) {
1051
+ if (response?.status >= 200 && response?.status < 300 && response.body) {
1052
+ let emailId = fetchMessageMap.get(response.id);
1053
+ if (emailId) {
1054
+ currentCategories.set(emailId, response.body.categories || []);
1055
+ }
1056
+ }
1057
+ }
1058
+ } catch (err) {
1059
+ this.logger.error({
1060
+ msg: 'Failed to fetch current categories',
1061
+ err
1062
+ });
1063
+ }
1064
+ fetchBatch = [];
1065
+ fetchMessageMap = new Map();
1066
+ };
1067
+
1068
+ for (let emailId of emailIds) {
1069
+ let reqId = `fetch_${++fetchIdGen}`;
1070
+ fetchMessageMap.set(reqId, emailId);
1071
+ fetchBatch.push({
1072
+ id: reqId,
1073
+ method: 'GET',
1074
+ url: `/${this.oauth2UserPath}/messages/${emailId}?$select=categories`
1075
+ });
1076
+
1077
+ if (fetchBatch.length >= MAX_BATCH_SIZE) {
1078
+ await submitFetchBatch();
1079
+ }
1080
+ }
1081
+ if (fetchBatch.length) {
1082
+ await submitFetchBatch();
1083
+ }
1084
+ }
1085
+
1086
+ // Batch operation setup
1087
+ let batch = [];
1088
+ let idGen = 0;
1089
+ let updatedEmailIds = [];
1090
+ let messageMap = new Map();
1091
+
1092
+ let submitBatch = async () => {
1093
+ let responseData;
1094
+ try {
1095
+ responseData = await this.request(`/$batch`, 'post', {
1096
+ requests: batch
1097
+ });
1098
+ for (let response of responseData?.responses || []) {
1099
+ if (response?.status >= 200 && response?.status < 300) {
1100
+ let emailId = messageMap.get(response.id);
1101
+ if (emailId) {
1102
+ updatedEmailIds.push(emailId);
1103
+ }
1104
+ }
1105
+ }
1106
+ } catch (err) {
1107
+ this.logger.error({
1108
+ msg: 'Failed to run batch operation',
1109
+ err
1110
+ });
1111
+ throw err;
1112
+ } finally {
1113
+ batch = [];
1114
+ messageMap = new Map();
1115
+ }
1116
+ };
1117
+
1118
+ let formatRequest = emailId => {
1119
+ let reqId = `msg_${++idGen}`;
1120
+ messageMap.set(reqId, emailId);
1121
+
1122
+ let bodyUpdates = { ...flagUpdates };
1123
+
1124
+ // Handle categories for this specific message
1125
+ if (updates.labels) {
1126
+ if (updates.labels.set) {
1127
+ bodyUpdates.categories = updates.labels.set;
1128
+ } else {
1129
+ let categories = currentCategories.get(emailId) || [];
1130
+
1131
+ if (updates.labels.add) {
1132
+ for (let label of updates.labels.add) {
1133
+ if (!categories.includes(label)) {
1134
+ categories.push(label);
1135
+ }
1136
+ }
1137
+ }
1138
+
1139
+ if (updates.labels.delete) {
1140
+ categories = categories.filter(c => !updates.labels.delete.includes(c));
1141
+ }
1142
+
1143
+ bodyUpdates.categories = categories;
1144
+ }
1145
+ }
1146
+
1147
+ return {
1148
+ id: reqId,
1149
+ method: 'PATCH',
1150
+ url: `/${this.oauth2UserPath}/messages/${emailId}`,
1151
+ body: bodyUpdates,
1152
+ headers: {
1153
+ 'Content-Type': 'application/json'
1154
+ }
1155
+ };
1156
+ };
1157
+
1158
+ // Process messages in batches
1159
+ for (let emailId of emailIds) {
1160
+ batch.push(formatRequest(emailId));
1161
+ // submit batch when it reaches max size
1162
+ if (batch.length >= MAX_BATCH_SIZE) {
1163
+ await submitBatch(batch);
1164
+ }
1165
+ }
1166
+ if (batch.length) {
1167
+ // submit remaining batch
1168
+ await submitBatch(batch);
1169
+ }
1170
+
1171
+ return Object.assign({}, updates, { emailIds: updatedEmailIds });
1172
+ }
1173
+
1174
+ /**
1175
+ * Move a single message to another folder
1176
+ */
1177
+ async moveMessage(emailId, target) {
1178
+ await this.prepare();
1179
+
1180
+ let path = [].concat(target?.path || []).join('/');
1181
+
1182
+ let targetFolder = await this.resolveFolder(path);
1183
+ if (!targetFolder) {
1184
+ let error = new Error('Move failed');
1185
+ error.info = {
1186
+ response: 'Not able to find target folder'
1187
+ };
1188
+ error.code = 'NotFound';
1189
+ error.statusCode = 404;
1190
+ throw error;
1191
+ }
1192
+
1193
+ let messageData;
1194
+ try {
1195
+ messageData = await this.request(`/${this.oauth2UserPath}/messages/${emailId}/move`, 'post', {
1196
+ destinationId: targetFolder.id
1197
+ });
1198
+ if (!messageData) {
1199
+ throw new Error('Failed to move message');
1200
+ }
1201
+ } catch (err) {
1202
+ this.logger.error({
1203
+ msg: 'Failed to move message',
1204
+ emailId,
1205
+ target: targetFolder.pathName,
1206
+ err
1207
+ });
1208
+
1209
+ switch (err?.oauthRequest?.response?.error?.code) {
1210
+ case 'ErrorItemNotFound': {
1211
+ let error = new Error('Move failed');
1212
+ error.info = {
1213
+ response: 'Not able to find source message'
1214
+ };
1215
+ error.code = 'NotFound';
1216
+ error.statusCode = 404;
1217
+ throw error;
1218
+ }
1219
+
1220
+ default: {
1221
+ let error = new Error('Move failed');
1222
+ error.info = {
1223
+ response: err?.oauthRequest?.response?.error?.message
1224
+ };
1225
+ error.code = err?.oauthRequest?.response?.error?.code;
1226
+ error.statusCode = 400;
1227
+ throw error;
1228
+ }
1229
+ }
1230
+ }
1231
+
1232
+ return {
1233
+ path: targetFolder.pathName,
1234
+ id: messageData.id
1235
+ };
1236
+ }
1237
+
1238
+ /**
1239
+ * Move multiple messages to another folder
1240
+ * Uses batch operations for efficiency
1241
+ */
1242
+ async moveMessages(source, search, target) {
1243
+ await this.prepare();
1244
+
1245
+ let path = [].concat(target?.path || []).join('/');
1246
+
1247
+ let targetFolder = await this.resolveFolder(path);
1248
+ if (!targetFolder) {
1249
+ let error = new Error('Move failed');
1250
+ error.info = {
1251
+ response: 'Not able to find target folder'
1252
+ };
1253
+ error.code = 'NotFound';
1254
+ error.statusCode = 404;
1255
+ throw error;
1256
+ }
1257
+
1258
+ // Step 1. Resolve matching messages
1259
+ let emailIds = search.emailIds || (await this.searchEmailIds(source, search));
1260
+
1261
+ if (!emailIds?.length) {
1262
+ // nothing to do here
1263
+ return { path: targetFolder.pathName };
1264
+ }
1265
+
1266
+ // Batch operation setup
1267
+ let batch = [];
1268
+ let idGen = 0;
1269
+ let updatedEmailIds = [];
1270
+ let messageMap = new Map();
1271
+
1272
+ let submitBatch = async () => {
1273
+ let responseData;
1274
+ try {
1275
+ responseData = await this.request(`/$batch`, 'post', {
1276
+ requests: batch
1277
+ });
1278
+ for (let response of responseData?.responses || []) {
1279
+ if (response?.status >= 200 && response?.status < 300) {
1280
+ let emailId = messageMap.get(response.id);
1281
+ if (emailId) {
1282
+ updatedEmailIds.push(emailId);
1283
+ }
1284
+ }
1285
+ }
1286
+ } catch (err) {
1287
+ this.logger.error({
1288
+ msg: 'Failed to run batch operation',
1289
+ err
1290
+ });
1291
+ throw err;
1292
+ } finally {
1293
+ batch = [];
1294
+ messageMap = new Map();
1295
+ }
1296
+ };
1297
+
1298
+ let formatRequest = emailId => {
1299
+ let reqId = `msg_${++idGen}`;
1300
+ messageMap.set(reqId, emailId);
1301
+ return {
1302
+ id: reqId,
1303
+ method: 'POST',
1304
+ url: `/${this.oauth2UserPath}/messages/${emailId}/move`,
1305
+ body: { destinationId: targetFolder.id },
1306
+ headers: {
1307
+ 'Content-Type': 'application/json'
1308
+ }
1309
+ };
1310
+ };
1311
+
1312
+ // Process messages in batches
1313
+ for (let emailId of emailIds) {
1314
+ batch.push(formatRequest(emailId));
1315
+ // submit batch
1316
+ if (batch.length >= MAX_BATCH_SIZE) {
1317
+ await submitBatch(batch);
1318
+ }
1319
+ }
1320
+ if (batch.length) {
1321
+ // submit batch
1322
+ await submitBatch(batch);
1323
+ }
1324
+
1325
+ return Object.assign({ path: targetFolder.pathName }, { emailIds: updatedEmailIds });
1326
+ }
1327
+
1328
+ /**
1329
+ * Download an attachment by its ID
1330
+ * Returns attachment data with appropriate headers for download
1331
+ */
1332
+ async getAttachment(attachmentId) {
1333
+ let attachmentData = await this.getAttachmentContent(attachmentId);
1334
+
1335
+ if (!attachmentData || !attachmentData.content) {
1336
+ return false;
1337
+ }
1338
+
1339
+ // Build content-disposition header with proper filename encoding
1340
+ let filenameParam = '';
1341
+ if (attachmentData.filename) {
1342
+ let isCleartextFilename = attachmentData.filename && /^[a-z0-9 _\-()^[\]~=,+*$]+$/i.test(attachmentData.filename);
1343
+ if (isCleartextFilename) {
1344
+ filenameParam = `; filename=${JSON.stringify(attachmentData.filename)}`;
1345
+ } else {
1346
+ // Use RFC 5987 encoding for non-ASCII filenames
1347
+ filenameParam = `; filename=${JSON.stringify(he.encode(attachmentData.filename))}; filename*=utf-8''${encodeURIComponent(
1348
+ attachmentData.filename
1349
+ )}`;
1350
+ }
1351
+ }
1352
+
1353
+ const contentResponse = {
1354
+ headers: {
1355
+ 'content-type': attachmentData.mimeType || 'application/octet-stream',
1356
+ 'content-disposition': 'attachment' + filenameParam
1357
+ },
1358
+ contentType: attachmentData.contentType,
1359
+ filename: attachmentData.filename,
1360
+ data: attachmentData.content
1361
+ };
1362
+
1363
+ return contentResponse;
1364
+ }
1365
+
1366
+ /**
1367
+ * Get full message details with optional formatting
1368
+ * Supports web-safe HTML generation and embedded image processing
1369
+ */
1370
+ async getMessage(emailId, options) {
1371
+ options = options || {};
1372
+
1373
+ if (options.webSafeHtml) {
1374
+ options.textType = '*';
1375
+ options.embedAttachedImages = true;
1376
+ options.preProcessHtml = true;
1377
+ }
1378
+
1379
+ await this.prepare();
1380
+
1381
+ let messageData, path, specialUse;
1382
+
1383
+ // Prepare request options with appropriate Prefer header for body content type
1384
+ let requestOptions = {};
1385
+ if (options.textType === 'plain') {
1386
+ // Request plain text format from MS Graph API
1387
+ requestOptions.headers = {
1388
+ Prefer: 'outlook.body-content-type="text"'
1389
+ };
1390
+ } else if (options.textType === 'html') {
1391
+ // Request HTML format from MS Graph API
1392
+ requestOptions.headers = {
1393
+ Prefer: 'outlook.body-content-type="html"'
1394
+ };
1395
+ }
1396
+ // If textType is '*' or undefined, don't set the preference and let MS Graph return the default format
1397
+
1398
+ try {
1399
+ messageData = await this.request(
1400
+ `/${this.oauth2UserPath}/messages/${emailId}`,
1401
+ 'get',
1402
+ {
1403
+ // 'internetMessageHeaders' is not included by default, so have to list all required fields
1404
+ $select: [
1405
+ 'id',
1406
+ 'conversationId',
1407
+ 'receivedDateTime',
1408
+ 'isRead',
1409
+ 'isDraft',
1410
+ 'flag',
1411
+ 'body',
1412
+ 'subject',
1413
+ 'from',
1414
+ 'replyTo',
1415
+ 'sender',
1416
+ 'toRecipients',
1417
+ 'ccRecipients',
1418
+ 'bccRecipients',
1419
+ 'internetMessageId',
1420
+ 'bodyPreview',
1421
+ 'internetMessageHeaders',
1422
+ 'parentFolderId',
1423
+ 'categories'
1424
+ ].join(','),
1425
+ $expand: 'attachments($select=id,name,contentType,size,isInline,microsoft.graph.fileAttachment/contentId)'
1426
+ },
1427
+ requestOptions
1428
+ );
1429
+ } catch (err) {
1430
+ switch (err.oauthRequest?.status) {
1431
+ case 404: {
1432
+ let error = new Error('Unknown message');
1433
+ error.info = {
1434
+ response: `Message does not exist`
1435
+ };
1436
+ error.code = 'NotFound';
1437
+ error.statusCode = 404;
1438
+ throw error;
1439
+ }
1440
+
1441
+ case 400: {
1442
+ let error = new Error('Invalid request');
1443
+ error.info = {
1444
+ response: err.oauthRequest?.response?.error?.message || `Invalid request`
1445
+ };
1446
+ error.code = err.oauthRequest?.response?.error?.code || 'InvalidRequest';
1447
+ error.statusCode = 400;
1448
+ throw error;
1449
+ }
1450
+
1451
+ default:
1452
+ this.logger.error({
1453
+ msg: 'Failed to fetch message data',
1454
+ emailId,
1455
+ err
1456
+ });
1457
+ throw err;
1458
+ }
1459
+ }
1460
+
1461
+ // Resolve the folder path and special use
1462
+ if (messageData.parentFolderId) {
1463
+ let folder = await this.resolveFolder(messageData.parentFolderId, { byId: true });
1464
+ if (!folder) {
1465
+ let error = new Error('Listing failed');
1466
+ error.info = {
1467
+ response: 'Not able to find mailbox folder'
1468
+ };
1469
+ error.code = 'NotFound';
1470
+ error.statusCode = 404;
1471
+ throw error;
1472
+ }
1473
+
1474
+ path = folder.pathName;
1475
+ specialUse = folder.specialUse;
1476
+ }
1477
+
1478
+ // Process email headers
1479
+ if (messageData.internetMessageHeaders) {
1480
+ let headers = {};
1481
+
1482
+ for (let header of messageData?.internetMessageHeaders || []) {
1483
+ let { name, value } = header;
1484
+ name = (name || '').toString().trim().toLowerCase();
1485
+
1486
+ value = (value || '').toString().trim();
1487
+ if (!(name in headers)) {
1488
+ headers[name] = [];
1489
+ }
1490
+ if (!Array.isArray(headers[name])) {
1491
+ continue;
1492
+ }
1493
+ headers[name].push(value);
1494
+
1495
+ // Extract specific headers for easier access
1496
+ switch (name) {
1497
+ case 'in-reply-to': {
1498
+ messageData.inReplyTo = value;
1499
+ break;
1500
+ }
1501
+ }
1502
+ }
1503
+
1504
+ messageData.headers = headers;
1505
+ }
1506
+
1507
+ const formattedMessage = this.formatMessage(messageData, {
1508
+ extended: true,
1509
+ path,
1510
+ textType: options.textType,
1511
+ showPath: options.showPath
1512
+ });
1513
+
1514
+ // Mark as seen if requested
1515
+ if (options.markAsSeen && (!formattedMessage.flags || !formattedMessage.flags.includes('\\Seen'))) {
1516
+ try {
1517
+ let response = await this.updateMessage(emailId, { flags: { add: ['\\Seen'] } });
1518
+ if (response?.flags?.result) {
1519
+ formattedMessage.flags = response?.flags?.result;
1520
+ }
1521
+ } catch (err) {
1522
+ this.logger.debug({ msg: 'Failed to mark message as Seen', message: emailId, err });
1523
+ }
1524
+ }
1525
+
1526
+ // Generate web-safe HTML if requested
1527
+ if (options.preProcessHtml && formattedMessage.text && (formattedMessage.text.html || formattedMessage.text.plain)) {
1528
+ formattedMessage.text.html = mimeHtml({
1529
+ html: formattedMessage.text.html,
1530
+ text: formattedMessage.text.plain
1531
+ });
1532
+
1533
+ formattedMessage.text.webSafe = true;
1534
+ }
1535
+
1536
+ // Embed attached images as data URLs
1537
+ if (options.embedAttachedImages && formattedMessage.text?.html && formattedMessage.attachments) {
1538
+ let attachmentMap = new Map();
1539
+
1540
+ // Collect all referenced inline attachments
1541
+ for (let attachment of formattedMessage.attachments) {
1542
+ let contentId = attachment.contentId && attachment.contentId.replace(/^<|>$/g, '');
1543
+ if (contentId && !attachmentMap.has(contentId) && formattedMessage.text.html.indexOf(`cid:${contentId}`) >= 0) {
1544
+ attachmentMap.set(contentId, {
1545
+ attachment,
1546
+ content: await this.getAttachmentContent(attachment.id, {
1547
+ returnBase64: true
1548
+ })
1549
+ });
1550
+ }
1551
+ }
1552
+
1553
+ // Replace cid: references with data URLs
1554
+ formattedMessage.text.html = formattedMessage.text.html.replace(/\bcid:([^"'\s>]+)/g, (fullMatch, cidMatch) => {
1555
+ if (attachmentMap.has(cidMatch)) {
1556
+ let { content } = attachmentMap.get(cidMatch);
1557
+ if (content.content) {
1558
+ return `data:${content.contentType || 'application/octet-stream'};base64,${content.content}`;
1559
+ }
1560
+ }
1561
+ return fullMatch;
1562
+ });
1563
+ }
1564
+
1565
+ if (specialUse) {
1566
+ formattedMessage.messageSpecialUse = specialUse;
1567
+ }
1568
+
1569
+ return formattedMessage;
1570
+ }
1571
+
1572
+ /**
1573
+ * Get message text content only
1574
+ * More efficient than getMessage when only text is needed
1575
+ */
1576
+ async getText(textId, options) {
1577
+ options = options || {};
1578
+
1579
+ await this.prepare();
1580
+
1581
+ let messageData;
1582
+
1583
+ // Prepare request options with appropriate Prefer header for body content type
1584
+ let requestOptions = {};
1585
+ if (options.textType === 'plain') {
1586
+ // Request plain text format from MS Graph API
1587
+ requestOptions.headers = {
1588
+ Prefer: 'outlook.body-content-type="text"'
1589
+ };
1590
+ } else if (options.textType === 'html') {
1591
+ // Request HTML format from MS Graph API
1592
+ requestOptions.headers = {
1593
+ Prefer: 'outlook.body-content-type="html"'
1594
+ };
1595
+ }
1596
+
1597
+ try {
1598
+ messageData = await this.request(
1599
+ `/${this.oauth2UserPath}/messages/${textId}`,
1600
+ 'get',
1601
+ {
1602
+ $select: 'body'
1603
+ },
1604
+ requestOptions
1605
+ );
1606
+ } catch (err) {
1607
+ switch (err.oauthRequest?.status) {
1608
+ case 404: {
1609
+ let error = new Error('Unknown message');
1610
+ error.info = {
1611
+ response: `Message does not exist`
1612
+ };
1613
+ error.code = 'NotFound';
1614
+ error.statusCode = 404;
1615
+ throw error;
1616
+ }
1617
+
1618
+ case 400: {
1619
+ let error = new Error('Invalid request');
1620
+ error.info = {
1621
+ response: err.oauthRequest?.response?.error?.message || `Invalid request`
1622
+ };
1623
+ error.code = err.oauthRequest?.response?.error?.code || 'InvalidRequest';
1624
+ error.statusCode = 400;
1625
+ throw error;
1626
+ }
1627
+
1628
+ default:
1629
+ this.logger.error({
1630
+ msg: 'Failed to fetch message data',
1631
+ emailId: textId,
1632
+ err
1633
+ });
1634
+ throw err;
1635
+ }
1636
+ }
1637
+
1638
+ let response = {};
1639
+
1640
+ if (options.textType && options.textType !== '*') {
1641
+ response[options.textType] = '';
1642
+ }
1643
+
1644
+ if (messageData?.body?.contentType) {
1645
+ let textContent = messageData.body.content || '';
1646
+ let textContentType;
1647
+ switch (messageData.body.contentType) {
1648
+ case 'text':
1649
+ textContentType = 'plain';
1650
+ break;
1651
+
1652
+ case 'html':
1653
+ default:
1654
+ textContentType = messageData.body.contentType;
1655
+ break;
1656
+ }
1657
+ if ([textContentType, '*'].includes(options.textType)) {
1658
+ response[textContentType] = textContent;
1659
+ }
1660
+ }
1661
+
1662
+ response.hasMore = false;
1663
+
1664
+ return response;
1665
+ }
1666
+
1667
+ /**
1668
+ * Convert EmailEngine message format to Graph API message format
1669
+ * Handles all message properties including headers and attachments
1670
+ */
1671
+ convertMessageToUploadObject(emailObject) {
1672
+ let messageUploadObj = {};
1673
+
1674
+ for (let key of Object.keys(emailObject)) {
1675
+ switch (key) {
1676
+ case 'from':
1677
+ messageUploadObj.from = {
1678
+ emailAddress: {
1679
+ address: emailObject.from.address
1680
+ }
1681
+ };
1682
+ if (emailObject.from.name) {
1683
+ messageUploadObj.from.emailAddress.name = emailObject.from.name;
1684
+ }
1685
+ break;
1686
+
1687
+ case 'to':
1688
+ case 'cc':
1689
+ case 'bcc': {
1690
+ let entryKey = `${key}Recipients`;
1691
+ messageUploadObj[entryKey] = [];
1692
+ for (let addressEntry of emailObject[key] || []) {
1693
+ let addressObj = {
1694
+ emailAddress: {
1695
+ address: addressEntry.address
1696
+ }
1697
+ };
1698
+ if (addressEntry.name) {
1699
+ addressObj.emailAddress.name = addressEntry.name;
1700
+ }
1701
+ messageUploadObj[entryKey].push(addressObj);
1702
+ }
1703
+ break;
1704
+ }
1705
+
1706
+ case 'subject':
1707
+ messageUploadObj[key] = emailObject[key];
1708
+ break;
1709
+
1710
+ case 'headers': {
1711
+ messageUploadObj.internetMessageHeaders = messageUploadObj.internetMessageHeaders || [];
1712
+ for (let header of Object.keys(emailObject.headers)) {
1713
+ messageUploadObj.internetMessageHeaders.push({
1714
+ name: header.toLowerCase(),
1715
+ value: emailObject.headers[header]
1716
+ });
1717
+ }
1718
+ break;
1719
+ }
1720
+
1721
+ case 'headerLines': {
1722
+ messageUploadObj.internetMessageHeaders = messageUploadObj.internetMessageHeaders || [];
1723
+ for (let i = emailObject.headerLines.length - 1; i >= 0; i--) {
1724
+ let header = emailObject.headerLines[i];
1725
+ // Skip headers that Graph API handles automatically
1726
+ if (
1727
+ [
1728
+ 'date',
1729
+ 'content-transfer-encoding',
1730
+ 'from',
1731
+ 'to',
1732
+ 'cc',
1733
+ 'bcc',
1734
+ 'subject',
1735
+ 'mime-version',
1736
+ 'content-type',
1737
+ 'content-disposition',
1738
+ 'message-id',
1739
+ 'content-id'
1740
+ ].includes(header.key) ||
1741
+ // MS Graph API only allows up to 5 custom headers
1742
+ messageUploadObj.internetMessageHeaders.length >= 5
1743
+ ) {
1744
+ continue;
1745
+ }
1746
+
1747
+ let name = header.key;
1748
+ let value = header.value ? header.value.substring(header.value.indexOf(':') + 1).trim() : '';
1749
+ if (name && value) {
1750
+ messageUploadObj.internetMessageHeaders.unshift({
1751
+ name,
1752
+ value
1753
+ });
1754
+ }
1755
+ }
1756
+ break;
1757
+ }
1758
+
1759
+ case 'messageId':
1760
+ messageUploadObj.internetMessageId = emailObject.messageId;
1761
+ break;
1762
+
1763
+ case 'attachments': {
1764
+ messageUploadObj.attachments = [];
1765
+ let attachmentCounter = 0;
1766
+ for (let attachment of emailObject.attachments) {
1767
+ let attachmentEntry = {
1768
+ '@odata.type': '#microsoft.graph.fileAttachment'
1769
+ };
1770
+ if (attachment.filename) {
1771
+ attachmentEntry.name = attachment.filename;
1772
+ } else {
1773
+ // generate a filename based on contentType as name is a required value
1774
+ let ext = detectExtension(attachment.contentType);
1775
+ attachmentEntry.name = `attachment_${++attachmentCounter}.${ext}`;
1776
+ }
1777
+
1778
+ attachmentEntry.contentType = attachment.contentType || detectMimeType(attachment.filename) || 'application/octet-stream';
1779
+ attachmentEntry.contentBytes = attachment.content;
1780
+ if (attachment.cid) {
1781
+ // make sure that cid links to not use <content-id> format, otherwise this will be replaced
1782
+ attachmentEntry.contentId = attachment.cid.replace(/^[\s<]*|[\s>]*$/g, '');
1783
+ if (emailObject.html?.indexOf(attachmentEntry.contentId) >= 0) {
1784
+ attachmentEntry.isInline = true;
1785
+ emailObject.html.replace(new RegExp(`cid:<${attachment.cid}>`, 'g'), `cid:${attachment.cid}`);
1786
+ }
1787
+ }
1788
+ if (attachment.contentDisposition === 'inline') {
1789
+ attachmentEntry.isInline = true;
1790
+ }
1791
+ messageUploadObj.attachments.push(attachmentEntry);
1792
+ }
1793
+ break;
1794
+ }
1795
+ }
1796
+ }
1797
+
1798
+ // Set message body content
1799
+ if (emailObject.html) {
1800
+ messageUploadObj.body = {
1801
+ contentType: 'html',
1802
+ content: emailObject.html
1803
+ };
1804
+ } else if (emailObject.text) {
1805
+ messageUploadObj.body = {
1806
+ contentType: 'text',
1807
+ content: emailObject.text
1808
+ };
1809
+ }
1810
+
1811
+ if (messageUploadObj.internetMessageHeaders && !messageUploadObj.internetMessageHeaders.length) {
1812
+ delete messageUploadObj.internetMessageHeaders;
1813
+ }
1814
+
1815
+ return messageUploadObj;
1816
+ }
1817
+
1818
+ /**
1819
+ * Upload a message to a folder (typically for drafts)
1820
+ * Supports custom flags and internal date via extended properties
1821
+ */
1822
+ async uploadMessage(data) {
1823
+ await this.prepare();
1824
+
1825
+ let path = [].concat(data.path || []).join('/');
1826
+
1827
+ let targetFolder = await this.resolveFolder(path);
1828
+ if (!targetFolder) {
1829
+ let error = new Error('Upload failed');
1830
+ error.info = {
1831
+ response: 'Not able to find mailbox folder'
1832
+ };
1833
+ error.code = 'NotFound';
1834
+ error.statusCode = 404;
1835
+ throw error;
1836
+ }
1837
+
1838
+ // Parse raw message into structured format
1839
+ let { emailObject, messageId, referencedMessage, documentStoreUsed } = await this.prepareRawMessage(data, {
1840
+ returnObject: true
1841
+ });
1842
+
1843
+ let messageUploadObj = this.convertMessageToUploadObject(emailObject);
1844
+
1845
+ messageUploadObj.singleValueExtendedProperties = [];
1846
+
1847
+ // Set message flags using MAPI properties
1848
+ // https://learn.microsoft.com/en-us/office/client-developer/outlook/mapi/pidtagmessageflags-canonical-property
1849
+ // PR_MESSAGE_FLAGS
1850
+ if (data.flags) {
1851
+ let flagValue = 0;
1852
+
1853
+ for (let flag of data.flags || []) {
1854
+ switch (flag) {
1855
+ case '\\Seen': // mfRead, MSGFLAG_READ
1856
+ flagValue |= 0x0001;
1857
+ break;
1858
+ case '\\Draft': // mfUnsent, MSGFLAG_UNSENT
1859
+ flagValue |= 0x0008;
1860
+ break;
1861
+ }
1862
+ }
1863
+
1864
+ messageUploadObj.singleValueExtendedProperties.push({ id: 'Integer 0x0E07', value: flagValue.toString(10) });
1865
+ } else {
1866
+ messageUploadObj.singleValueExtendedProperties.push({ id: 'Integer 0x0E07', value: '0' });
1867
+ }
1868
+
1869
+ // Set internal date using MAPI property
1870
+ // https://learn.microsoft.com/en-us/office/client-developer/outlook/mapi/pidtagmessagedeliverytime-canonical-property
1871
+ //PR_MESSAGE_DELIVERY_TIME
1872
+ if (data.internalDate) {
1873
+ messageUploadObj.singleValueExtendedProperties.push({ id: 'SystemTime 0x0E06', value: data.internalDate.toISOString() });
1874
+ }
1875
+
1876
+ let messageData;
1877
+ try {
1878
+ messageData = await this.request(`/${this.oauth2UserPath}/mailFolders/${targetFolder.id}/messages`, 'post', messageUploadObj);
1879
+ } catch (err) {
1880
+ switch (err.oauthRequest?.status) {
1881
+ case 400: {
1882
+ let error = new Error('Invalid message format');
1883
+ error.info = {
1884
+ response: err.oauthRequest?.response?.error?.message || `Invalid message format`
1885
+ };
1886
+ error.code = err.oauthRequest?.response?.error?.code || 'InvalidMessage';
1887
+ error.statusCode = 500; // do not retry sending
1888
+ throw error;
1889
+ }
1890
+
1891
+ default:
1892
+ this.logger.error({
1893
+ msg: 'Failed to upload message',
1894
+ messageId,
1895
+ err
1896
+ });
1897
+ throw err;
1898
+ }
1899
+ }
1900
+
1901
+ let response = {
1902
+ id: messageData?.id,
1903
+ path: targetFolder.pathName,
1904
+ messageId: messageData?.internetMessageId || messageId
1905
+ };
1906
+
1907
+ // Include reference info if this was a reply/forward
1908
+ if (data.reference && data.reference.message) {
1909
+ response.reference = {
1910
+ message: data.reference.message,
1911
+ documentStore: documentStoreUsed,
1912
+ success: referencedMessage ? true : false
1913
+ };
1914
+
1915
+ if (!referencedMessage) {
1916
+ response.reference.error = 'Referenced message was not found';
1917
+ }
1918
+ }
1919
+
1920
+ return response;
1921
+ }
1922
+
1923
+ /**
1924
+ * Send an email message
1925
+ * Can use either Graph API sendMail endpoint or fall back to SMTP
1926
+ */
1927
+ async submitMessage(data) {
1928
+ await this.prepare();
1929
+
1930
+ let accountData = await this.accountObject.loadAccountData();
1931
+ if (!accountData.smtp && !accountData.oauth2 && !data.gateway) {
1932
+ // can not make connection
1933
+ let err = new Error('SMTP configuration not found');
1934
+ err.code = 'SMTPUnavailable';
1935
+ err.statusCode = 404;
1936
+ throw err;
1937
+ }
1938
+
1939
+ let { raw, messageId, queueId, job: jobData, envelope } = data;
1940
+
1941
+ if (raw?.buffer) {
1942
+ // convert from a Uint8Array to a Buffer
1943
+ raw = Buffer.from(raw);
1944
+ }
1945
+
1946
+ // Check if we should use SMTP gateway instead
1947
+ let gatewayData;
1948
+ let gatewayObject;
1949
+ if (data.gateway) {
1950
+ gatewayObject = new Gateway({ gateway: data.gateway, redis: this.redis, secret: this.secret });
1951
+ try {
1952
+ gatewayData = await gatewayObject.loadGatewayData();
1953
+ } catch (err) {
1954
+ this.logger.info({ msg: 'Failed to load gateway data', messageId: data.messageId, gateway: data.gateway, err });
1955
+ }
1956
+ }
1957
+
1958
+ if (gatewayData) {
1959
+ // Send via SMTP (fall back to base class implementation)
1960
+ return await super.submitMessage(data);
1961
+ }
1962
+
1963
+ // Get job entry for progress updates
1964
+ const submitJobEntry = await this.submitQueue.getJob(jobData.id);
1965
+ if (!submitJobEntry) {
1966
+ // already failed?
1967
+ this.logger.error({
1968
+ msg: 'Submit job was not found',
1969
+ job: jobData.id
1970
+ });
1971
+ return false;
1972
+ }
1973
+
1974
+ // Check if structured format should be used (opt-in)
1975
+ // Default is raw MIME to preserve backward compatibility and calendar invites
1976
+ if (data.useStructuredFormat) {
1977
+ // Parse raw message into structured format to properly handle 'from' address
1978
+ // Microsoft Graph API ignores the From header in raw MIME messages and uses
1979
+ // the authenticated user's address instead. By parsing and sending as structured
1980
+ // JSON with explicit 'from' field, we enable proper "send on behalf of" behavior
1981
+ let emailObject;
1982
+ try {
1983
+ let parseResult = await this.prepareRawMessage(data, {
1984
+ returnObject: true
1985
+ });
1986
+ emailObject = parseResult.emailObject;
1987
+ } catch (err) {
1988
+ this.logger.error({
1989
+ msg: 'Failed to parse raw message',
1990
+ messageId,
1991
+ err
1992
+ });
1993
+ throw err;
1994
+ }
1995
+
1996
+ // Convert to Graph API message format
1997
+ let messagePayload = this.convertMessageToUploadObject(emailObject);
1998
+
1999
+ try {
2000
+ // Send via Graph API using structured message format
2001
+ // This ensures the 'from' field is properly respected for shared mailboxes
2002
+ // Note: sendMail returns 202 Accepted with empty body
2003
+ await this.request(
2004
+ `/${this.oauth2UserPath}/sendMail`,
2005
+ 'post',
2006
+ {
2007
+ message: messagePayload
2008
+ },
2009
+ {
2010
+ returnText: true
2011
+ }
2012
+ );
2013
+ } catch (err) {
2014
+ switch (err.oauthRequest?.status) {
2015
+ case 400: {
2016
+ let error = new Error('Invalid message format');
2017
+ error.info = {
2018
+ response: err.oauthRequest?.response?.error?.message || `Invalid message format`
2019
+ };
2020
+ error.code = err.oauthRequest?.response?.error?.code || 'InvalidMessage';
2021
+ error.statusCode = 500; // do not retry sending
2022
+ throw error;
2023
+ }
2024
+
2025
+ default:
2026
+ this.logger.error({
2027
+ msg: 'Failed to submit message',
2028
+ messageId,
2029
+ err
2030
+ });
2031
+ throw err;
2032
+ }
2033
+ }
2034
+ } else {
2035
+ // Use raw MIME format (default)
2036
+ // Preserves calendar invites and special MIME types, but ignores from field
2037
+ try {
2038
+ await this.request(`/${this.oauth2UserPath}/sendMail`, 'post', Buffer.from(raw.toString('base64')), {
2039
+ contentType: 'text/plain',
2040
+ returnText: true
2041
+ });
2042
+ } catch (err) {
2043
+ switch (err.oauthRequest?.status) {
2044
+ case 400: {
2045
+ let error = new Error('Invalid message format');
2046
+ error.info = {
2047
+ response: err.oauthRequest?.response?.error?.message || `Invalid message format`
2048
+ };
2049
+ error.code = err.oauthRequest?.response?.error?.code || 'InvalidMessage';
2050
+ error.statusCode = 500; // do not retry sending
2051
+ throw error;
2052
+ }
2053
+
2054
+ default:
2055
+ this.logger.error({
2056
+ msg: 'Failed to submit message',
2057
+ messageId,
2058
+ err
2059
+ });
2060
+ throw err;
2061
+ }
2062
+ }
2063
+ }
2064
+
2065
+ // Update job progress
2066
+ try {
2067
+ await submitJobEntry.updateProgress({
2068
+ status: 'smtp-completed',
2069
+ messageId,
2070
+ originalMessageId: messageId
2071
+ });
2072
+ } catch (err) {
2073
+ // ignore
2074
+ }
2075
+
2076
+ // Send completion notification
2077
+ await this.notify(false, EMAIL_SENT_NOTIFY, {
2078
+ messageId,
2079
+ originalMessageId: messageId,
2080
+ queueId,
2081
+ envelope
2082
+ });
2083
+
2084
+ // Mark as successful in Redis for webhook feedback
2085
+ if (data.feedbackKey) {
2086
+ await this.redis
2087
+ .multi()
2088
+ .hset(data.feedbackKey, 'success', 'true')
2089
+ .expire(1 * 60 * 60);
2090
+ }
2091
+
2092
+ return {
2093
+ messageId
2094
+ };
2095
+ }
2096
+
2097
+ /**
2098
+ * Create a new mailbox folder
2099
+ * Supports nested folders with path syntax
2100
+ */
2101
+ async createMailbox(path) {
2102
+ await this.prepare();
2103
+
2104
+ path = [].concat(path || []).join('/');
2105
+
2106
+ let subPaths = path.split('/');
2107
+
2108
+ let displayName = subPaths.pop();
2109
+ let parentPath = subPaths.join('/');
2110
+
2111
+ // Resolve parent folder if specified
2112
+ let parentFolder;
2113
+ if (parentPath) {
2114
+ parentFolder = await this.resolveFolder(parentPath);
2115
+ if (!parentFolder) {
2116
+ let error = new Error('Create failed');
2117
+ error.info = {
2118
+ response: 'Not able to find parent folder'
2119
+ };
2120
+ error.code = 'NotFound';
2121
+ error.statusCode = 404;
2122
+ throw error;
2123
+ }
2124
+ }
2125
+
2126
+ // Build API URL based on whether this is a root or child folder
2127
+ let reqUrl;
2128
+ if (parentFolder) {
2129
+ // child folder
2130
+ reqUrl = `/${this.oauth2UserPath}/mailFolders/${parentFolder.id}/childFolders`;
2131
+ } else {
2132
+ // root folder
2133
+ reqUrl = `/${this.oauth2UserPath}/mailFolders`;
2134
+ }
2135
+
2136
+ let mailbox;
2137
+ try {
2138
+ mailbox = await this.request(reqUrl, 'post', {
2139
+ displayName,
2140
+ isHidden: false
2141
+ });
2142
+ if (!mailbox) {
2143
+ throw new Error('Failed to create mailbox');
2144
+ }
2145
+ } catch (err) {
2146
+ switch (err?.oauthRequest?.response?.error?.code) {
2147
+ case 'ErrorFolderExists':
2148
+ // already exists
2149
+ return {
2150
+ path,
2151
+ created: false
2152
+ };
2153
+
2154
+ default: {
2155
+ let error = new Error('Create failed');
2156
+ error.info = {
2157
+ response: err?.oauthRequest?.response?.error?.message
2158
+ };
2159
+ error.code = err?.oauthRequest?.response?.error?.code;
2160
+ error.statusCode = 400;
2161
+ throw error;
2162
+ }
2163
+ }
2164
+ }
2165
+
2166
+ // Refresh cache asynchronously
2167
+ setImmediate(() => {
2168
+ // refresh mailbox listing cache after changes
2169
+ this.listMailboxes().catch(err => {
2170
+ this.logger.error({ msg: 'Failed to list mailboxes', err });
2171
+ });
2172
+ });
2173
+
2174
+ return {
2175
+ mailboxId: mailbox.id,
2176
+ path: []
2177
+ .concat(parentFolder?.pathName || [])
2178
+ .concat(mailbox.displayName)
2179
+ .join('/'),
2180
+ created: true
2181
+ };
2182
+ }
2183
+
2184
+ /**
2185
+ * Modifies a mailbox folder (rename only, subscription is ignored for Outlook)
2186
+ * @param {string} path - Current path
2187
+ * @param {string} newPath - New path (optional)
2188
+ * @param {boolean} subscribed - Ignored for MS Graph API
2189
+ * @returns {Object} Modify result
2190
+ */
2191
+ async modifyMailbox(path, newPath, subscribed) {
2192
+ // MS Graph API does not support subscription management, so we ignore the subscribed parameter
2193
+ // If no newPath provided, just return the current path without changes
2194
+ if (!newPath) {
2195
+ return {
2196
+ path: [].concat(path || []).join('/'),
2197
+ renamed: false
2198
+ };
2199
+ }
2200
+
2201
+ return await this.renameMailbox(path, newPath);
2202
+ }
2203
+
2204
+ /**
2205
+ * Rename and/or move a mailbox folder
2206
+ * Handles both simple renames and moves to different parent folders
2207
+ */
2208
+ async renameMailbox(path, newPath) {
2209
+ await this.prepare();
2210
+
2211
+ path = [].concat(path || []).join('/');
2212
+ newPath = [].concat(newPath || []).join('/');
2213
+
2214
+ let sourceFolder = await this.resolveFolder(path);
2215
+ if (!sourceFolder) {
2216
+ let error = new Error('Rename failed');
2217
+ error.info = {
2218
+ response: 'Not able to find mailbox folder'
2219
+ };
2220
+ error.code = 'NotFound';
2221
+ error.statusCode = 404;
2222
+ throw error;
2223
+ }
2224
+
2225
+ // Parse source and destination paths
2226
+ let sourceParts = sourceFolder.pathName.split('/');
2227
+ let sourceName = sourceParts.pop();
2228
+ let sourceParentPath = sourceParts.join('/');
2229
+
2230
+ let destinationFolder = await this.resolveFolder(newPath, { pathNameOnly: true });
2231
+
2232
+ let destinationParts = destinationFolder.pathName.split('/');
2233
+ let destinationName = destinationParts.pop();
2234
+ let destinationParentPath = destinationParts.join('/');
2235
+
2236
+ let destinationParentFolder;
2237
+ if (sourceParentPath !== destinationParentPath) {
2238
+ destinationParentFolder = await this.resolveFolder(destinationParentPath);
2239
+ }
2240
+
2241
+ // Step 1. Rename if name changed
2242
+ if (sourceName !== destinationName) {
2243
+ let mailbox;
2244
+ try {
2245
+ mailbox = await this.request(`/${this.oauth2UserPath}/mailFolders/${sourceFolder.id}`, 'patch', {
2246
+ displayName: destinationName
2247
+ });
2248
+ if (!mailbox) {
2249
+ throw new Error('Failed to rename mailbox');
2250
+ }
2251
+ } catch (err) {
2252
+ this.logger.error({
2253
+ msg: 'Failed to rename folder',
2254
+ mailboxId: sourceFolder.id,
2255
+ path: sourceFolder.pathName,
2256
+ newPath: destinationFolder.pathName,
2257
+ err
2258
+ });
2259
+ throw err;
2260
+ }
2261
+ }
2262
+
2263
+ // Step 2. Move if parent changed
2264
+ if (destinationParentFolder) {
2265
+ let mailbox;
2266
+ try {
2267
+ mailbox = await this.request(`/${this.oauth2UserPath}/mailFolders/${sourceFolder.id}/move`, 'post', {
2268
+ destinationId: destinationParentFolder.id
2269
+ });
2270
+ if (!mailbox) {
2271
+ throw new Error('Failed to move mailbox');
2272
+ }
2273
+ } catch (err) {
2274
+ this.logger.error({
2275
+ msg: 'Failed to move folder',
2276
+ mailboxId: sourceFolder.id,
2277
+ path: sourceFolder.pathName,
2278
+ newPath: destinationFolder.pathName,
2279
+ err
2280
+ });
2281
+ throw err;
2282
+ }
2283
+ }
2284
+
2285
+ // Refresh cache asynchronously
2286
+ setImmediate(() => {
2287
+ // refresh mailbox listing cache after changes
2288
+ this.listMailboxes().catch(err => {
2289
+ this.logger.error({ msg: 'Failed to list mailboxes', err });
2290
+ });
2291
+ });
2292
+
2293
+ return {
2294
+ mailboxId: sourceFolder.id,
2295
+ path: sourceFolder.pathName,
2296
+ newPath: destinationFolder.pathName,
2297
+ renamed: true
2298
+ };
2299
+ }
2300
+
2301
+ /**
2302
+ * Delete a mailbox folder
2303
+ */
2304
+ async deleteMailbox(path) {
2305
+ await this.prepare();
2306
+
2307
+ path = [].concat(path || []).join('/');
2308
+
2309
+ let folder = await this.resolveFolder(path);
2310
+ if (!folder) {
2311
+ let error = new Error('Delete failed');
2312
+ error.info = {
2313
+ response: 'Not able to find mailbox folder'
2314
+ };
2315
+ error.code = 'NotFound';
2316
+ error.statusCode = 404;
2317
+ throw error;
2318
+ }
2319
+
2320
+ try {
2321
+ await this.request(`/${this.oauth2UserPath}/mailFolders/${folder.id}`, 'delete', Buffer.alloc(0), { returnText: true });
2322
+ } catch (err) {
2323
+ this.logger.error({
2324
+ msg: 'Failed to delete folder',
2325
+ mailboxId: folder.id,
2326
+ path: folder.pathName,
2327
+ err
2328
+ });
2329
+ throw err;
2330
+ }
2331
+
2332
+ // Refresh cache asynchronously
2333
+ setImmediate(() => {
2334
+ // refresh mailbox listing cache after changes
2335
+ this.listMailboxes().catch(err => {
2336
+ this.logger.error({ msg: 'Failed to list mailboxes', err });
2337
+ });
2338
+ });
2339
+
2340
+ return {
2341
+ mailboxId: folder.id,
2342
+ path: folder.pathName,
2343
+ deleted: true
2344
+ };
2345
+ }
2346
+
2347
+ /**
2348
+ * Handle external notification (webhook) - triggers sync of pending changes
2349
+ */
2350
+ async externalNotify() {
2351
+ this.triggerSync();
2352
+
2353
+ return true;
2354
+ }
2355
+
2356
+ // PRIVATE METHODS
2357
+
2358
+ /**
2359
+ * Get Redis key for account data
2360
+ */
2361
+ getAccountKey() {
2362
+ return `${REDIS_PREFIX}iad:${this.account}`;
2363
+ }
2364
+
2365
+ /**
2366
+ * Get Redis key for account cache
2367
+ */
2368
+ getAccountCacheKey() {
2369
+ return `${REDIS_PREFIX}iac:${this.account}`;
2370
+ }
2371
+
2372
+ /**
2373
+ * Get or create account object
2374
+ */
2375
+ async getAccount() {
2376
+ if (this.accountObject) {
2377
+ return this.accountObject;
2378
+ }
2379
+ this.accountObject = new Account({ redis: this.redis, account: this.account, secret: await getSecret() });
2380
+
2381
+ return this.accountObject;
2382
+ }
2383
+
2384
+ /**
2385
+ * Set up delegated account access if configured
2386
+ * Allows accessing shared mailboxes or other user mailboxes
2387
+ */
2388
+ async prepareDelegatedAccount() {
2389
+ if (this.delegatedAccountObject) {
2390
+ return;
2391
+ }
2392
+
2393
+ let accountData = await this.accountObject.loadAccountData();
2394
+
2395
+ if (accountData?.oauth2?.auth?.delegatedUser && accountData?.oauth2?.auth?.delegatedAccount) {
2396
+ await this.getDelegatedAccount(accountData);
2397
+ if (this.delegatedAccountObject) {
2398
+ // Update API path to target delegated user
2399
+ this.oauth2UserPath = `users/${encodeURIComponent(accountData?.oauth2?.auth?.delegatedUser)}`;
2400
+ }
2401
+ } else if (accountData?.oauth2?.auth?.delegatedUser) {
2402
+ // Created with delegated:true
2403
+ // Update API path to target delegated user
2404
+ this.oauth2UserPath = `users/${encodeURIComponent(accountData?.oauth2?.auth?.delegatedUser)}`;
2405
+ }
2406
+ }
2407
+
2408
+ /**
2409
+ * Get OAuth2 access token from account or delegated account
2410
+ */
2411
+ async getToken() {
2412
+ let tokenData;
2413
+ try {
2414
+ tokenData = await (this.delegatedAccountObject || this.accountObject).getActiveAccessTokenData();
2415
+ if (!['init', 'connecting', 'connected'].includes(this.state)) {
2416
+ // We're in an error state (authenticationError, disconnected, etc.)
2417
+ // But we just got a valid token, so we've recovered
2418
+ this.state = 'connected';
2419
+ await this.setStateVal();
2420
+ }
2421
+
2422
+ // Track successful token refresh (only if token was actually refreshed, not cached)
2423
+ if (!tokenData.cached) {
2424
+ metricsMeta({ account: this.account }, this.logger, 'oauth2TokenRefresh', 'inc', { status: 'success', provider: 'outlook', statusCode: '200' });
2425
+ }
2426
+ } catch (E) {
2427
+ if (E.code === 'ETokenRefresh') {
2428
+ // Track failed token refresh
2429
+ const statusCode = String(E.statusCode || 0);
2430
+ metricsMeta({ account: this.account }, this.logger, 'oauth2TokenRefresh', 'inc', { status: 'failure', provider: 'outlook', statusCode });
2431
+
2432
+ // treat as authentication failure
2433
+ this.state = 'authenticationError';
2434
+ await this.setStateVal();
2435
+
2436
+ E.authenticationFailed = true;
2437
+
2438
+ if (!E.errorNotified) {
2439
+ E.errorNotified = true;
2440
+ await this.notify(false, AUTH_ERROR_NOTIFY, {
2441
+ response: E.oauthRequest?.response?.error?.message || E.response,
2442
+ serverResponseCode: 'TokenGenerationError'
2443
+ });
2444
+ }
2445
+ }
2446
+
2447
+ throw E;
2448
+ }
2449
+
2450
+ return tokenData.accessToken;
2451
+ }
2452
+
2453
+ /**
2454
+ * Get or create OAuth2 client
2455
+ */
2456
+ async getClient(force) {
2457
+ if (this.oAuth2Client && !force) {
2458
+ return this.oAuth2Client;
2459
+ }
2460
+ let accountData = await (this.delegatedAccountObject || this.accountObject).loadAccountData();
2461
+ this.oAuth2Client = await oauth2Apps.getClient(accountData.oauth2.provider, {
2462
+ logger: this.logger,
2463
+ logRaw: this.options.logRaw
2464
+ });
2465
+ return this.oAuth2Client;
2466
+ }
2467
+
2468
+ /**
2469
+ * Ensure account and OAuth2 client are ready
2470
+ */
2471
+ async prepare() {
2472
+ await this.getAccount();
2473
+ await this.prepareDelegatedAccount();
2474
+ await this.getClient();
2475
+ }
2476
+
2477
+ /**
2478
+ * Set up timer to periodically renew webhook subscription
2479
+ */
2480
+ setupRenewWatchTimer() {
2481
+ if (this.closed) {
2482
+ return;
2483
+ }
2484
+ clearTimeout(this.renewWatchTimer);
2485
+ this.renewWatchTimer = setTimeout(async () => {
2486
+ if (this.closed) {
2487
+ return;
2488
+ }
2489
+
2490
+ try {
2491
+ // First try to renew existing subscription
2492
+ const renewalResult = await this.renewSubscription(false);
2493
+
2494
+ if (!renewalResult.success && (renewalResult.reason === 'expired' || renewalResult.reason === 'no_subscription')) {
2495
+ // If subscription expired or doesn't exist, ensure we have one
2496
+ await this.ensureSubscription();
2497
+ }
2498
+ } catch (err) {
2499
+ this.logger.error({ msg: 'Failed to renew MS Graph change subscription', account: this.account, err });
2500
+ } finally {
2501
+ // restart timer
2502
+ this.setupRenewWatchTimer();
2503
+ }
2504
+ }, RENEW_WATCH_TTL);
2505
+ this.renewWatchTimer.unref();
2506
+ }
2507
+
2508
+ /**
2509
+ * Check if mailbox folder structure has changed using delta API
2510
+ * Returns true if changes detected, false otherwise
2511
+ */
2512
+ async renewMailboxFolderCache() {
2513
+ // cache last known mailbox change
2514
+ let deltaReqUrl = (await this.redis.hget(this.getAccountKey(), 'outlookMailFoldersDeltaUrl')) || `/${this.oauth2UserPath}/mailFolders/delta`;
2515
+
2516
+ let hasChanges = false;
2517
+
2518
+ let deltaCheckDone = false;
2519
+ while (!deltaCheckDone) {
2520
+ let deltaRes;
2521
+
2522
+ try {
2523
+ deltaRes = await this.request(deltaReqUrl);
2524
+ } catch (err) {
2525
+ this.logger.error({ msg: 'Failed to check mailbox folder delta', err });
2526
+ // might be faulty entry, so clear it
2527
+ await this.redis.hdel(this.getAccountKey(), 'outlookMailFoldersDeltaUrl');
2528
+
2529
+ return true;
2530
+ }
2531
+
2532
+ if (deltaRes.value?.length) {
2533
+ hasChanges = true;
2534
+ }
2535
+
2536
+ if (deltaRes['@odata.nextLink']) {
2537
+ deltaReqUrl = deltaRes['@odata.nextLink'];
2538
+ await this.redis.hSetExists(this.getAccountKey(), 'outlookMailFoldersDeltaUrl', deltaReqUrl);
2539
+ } else {
2540
+ deltaCheckDone = true;
2541
+ if (deltaRes['@odata.deltaLink']) {
2542
+ deltaReqUrl = deltaRes['@odata.deltaLink'];
2543
+ await this.redis.hSetExists(this.getAccountKey(), 'outlookMailFoldersDeltaUrl', deltaReqUrl);
2544
+ }
2545
+ }
2546
+ }
2547
+
2548
+ return hasChanges;
2549
+ }
2550
+
2551
+ /**
2552
+ * Get cached mailbox listing from Redis
2553
+ */
2554
+ async getCachedMailboxListing() {
2555
+ let cachedListing;
2556
+ let cachedListingValue = await this.redis.hget(this.getAccountCacheKey(), 'outlookMailboxListing');
2557
+ if (cachedListingValue) {
2558
+ try {
2559
+ cachedListing = JSON.parse(cachedListingValue);
2560
+ } catch (err) {
2561
+ this.logger.error({ msg: 'Failed to parse cached mailbox listing', err });
2562
+ }
2563
+ }
2564
+ return cachedListing;
2565
+ }
2566
+
2567
+ /**
2568
+ * Get complete mailbox folder listing from Graph API
2569
+ * Handles special use folders and nested folder structures
2570
+ */
2571
+ async getMailboxListing() {
2572
+ // Map of special folder types
2573
+ let specialTags = new Map([
2574
+ ['deleteditems', '\\Trash'],
2575
+ ['drafts', '\\Drafts'],
2576
+ ['inbox', '\\Inbox'],
2577
+ ['junkemail', '\\Junk'],
2578
+ ['sentitems', '\\Sent']
2579
+ ]);
2580
+
2581
+ let specialUseKeys = Array.from(specialTags.keys());
2582
+ let specialUseTagIds = new Map();
2583
+
2584
+ // Resolve special use folders and cache their IDs
2585
+ for (let specialUseKey of specialUseKeys) {
2586
+ // Use caching. Assuming that the ID of the special-use folder does not change, even if the display name does
2587
+ let cachedValue;
2588
+ let cachedValueStr = await this.redis.hget(this.getAccountCacheKey(), `outlookMailbox:${specialUseKey}`);
2589
+ if (cachedValueStr) {
2590
+ try {
2591
+ cachedValue = JSON.parse(cachedValueStr);
2592
+ } catch (err) {
2593
+ await this.redis.hdel(this.getAccountCacheKey(), `outlookMailbox:${specialUseKey}`);
2594
+ }
2595
+ }
2596
+
2597
+ if (cachedValue) {
2598
+ specialUseTagIds.set(cachedValue.id, specialTags.get(specialUseKey));
2599
+ continue;
2600
+ }
2601
+
2602
+ let reqUrl = `/${this.oauth2UserPath}/mailFolders/${specialUseKey}`;
2603
+ try {
2604
+ let mailbox = await this.request(reqUrl);
2605
+ if (mailbox) {
2606
+ await this.redis.hset(this.getAccountCacheKey(), `outlookMailbox:${specialUseKey}`, JSON.stringify(mailbox));
2607
+ specialUseTagIds.set(mailbox.id, specialTags.get(specialUseKey));
2608
+ }
2609
+ } catch (err) {
2610
+ this.logger.error({ msg: 'Failed to resolve mailbox for special use key', specialUseKey, err });
2611
+ }
2612
+ }
2613
+
2614
+ let mailboxListing = [];
2615
+
2616
+ /**
2617
+ * Recursively traverse folder hierarchy
2618
+ */
2619
+ let traverse = async (pathNamePrefix, folderId) => {
2620
+ let list = [];
2621
+ let done = false;
2622
+ let reqUrl = `/${this.oauth2UserPath}/mailFolders${folderId ? `/${folderId}/childFolders` : ''}`;
2623
+ while (!done) {
2624
+ let mailboxRes = await this.request(reqUrl);
2625
+ if (mailboxRes.value) {
2626
+ list.push(
2627
+ ...mailboxRes.value.map(entry => {
2628
+ if (!folderId) {
2629
+ entry.rootFolder = true;
2630
+ }
2631
+ let specialUse = specialUseTagIds.get(entry.id);
2632
+ if (specialUse) {
2633
+ entry.specialUse = specialUse;
2634
+ }
2635
+ if (pathNamePrefix) {
2636
+ entry.parentPath = pathNamePrefix;
2637
+ }
2638
+ entry.pathName = `${pathNamePrefix ? `${pathNamePrefix}/` : ''}${entry.displayName}`;
2639
+ return entry;
2640
+ })
2641
+ );
2642
+ }
2643
+ // Handle pagination
2644
+ if (!mailboxRes['@odata.nextLink']) {
2645
+ done = true;
2646
+ } else {
2647
+ reqUrl = mailboxRes['@odata.nextLink'];
2648
+ }
2649
+ }
2650
+
2651
+ mailboxListing.push(...list);
2652
+
2653
+ // Traverse child folders
2654
+ for (let entry of list) {
2655
+ // do not traverse subfolders for folders with a slash in the name (would create ambiguous paths)
2656
+ if (entry.childFolderCount && entry.displayName.indexOf('/') < 0) {
2657
+ await traverse(entry.pathName, entry.id);
2658
+ }
2659
+ }
2660
+ };
2661
+
2662
+ await traverse();
2663
+
2664
+ // keep only real folders and folders that do not contain slash in the name
2665
+ mailboxListing = mailboxListing.filter(
2666
+ entry => (!entry['@odata.type'] || /^#?microsoft\.graph\.mailFolder$/.test(entry['@odata.type'])) && entry.displayName.indexOf('/') < 0
2667
+ );
2668
+
2669
+ return mailboxListing;
2670
+ }
2671
+
2672
+ /**
2673
+ * Resolve folder path to folder object
2674
+ * Supports special use tags, folder IDs, and path names
2675
+ */
2676
+ async resolveFolder(path, options) {
2677
+ options = options || {};
2678
+
2679
+ path = [].concat(path || []).join('/');
2680
+
2681
+ let cachedListing = await this.getCachedMailboxListing();
2682
+ let mailboxListing = cachedListing || (await this.getMailboxListing());
2683
+
2684
+ if (options.byId) {
2685
+ return mailboxListing.find(entry => entry.id === path);
2686
+ }
2687
+
2688
+ // Map IMAP special use tags to Outlook folder types
2689
+ let specialUseTags = new Map([
2690
+ ['\\Trash', 'deleteditems'],
2691
+ ['\\Drafts', 'drafts'],
2692
+ ['\\Inbox', 'inbox'],
2693
+ ['\\Junk', 'junkemail'],
2694
+ ['\\Sent', 'sentitems']
2695
+ ]);
2696
+
2697
+ // Handle case-insensitive INBOX
2698
+ if (/^inbox$/i.test(path)) {
2699
+ path = '\\Inbox';
2700
+ }
2701
+
2702
+ // Resolve special use tag
2703
+ if (specialUseTags.has(path)) {
2704
+ // resolve special use tag folder
2705
+ let folderEntry = mailboxListing.find(entry => entry.specialUse === path);
2706
+ if (folderEntry) {
2707
+ return folderEntry;
2708
+ }
2709
+ }
2710
+
2711
+ // Handle INBOX prefix in paths
2712
+ let pathParts = path.split('/');
2713
+ if (/^inbox$/i.test(pathParts[0])) {
2714
+ let inboxFolder = mailboxListing.find(entry => entry.specialUse === '\\Inbox');
2715
+ if (inboxFolder) {
2716
+ pathParts[0] = inboxFolder.pathName;
2717
+ }
2718
+ }
2719
+ path = pathParts.join('/');
2720
+
2721
+ if (options.pathNameOnly) {
2722
+ return { pathName: path };
2723
+ }
2724
+
2725
+ let folderEntry = mailboxListing.find(entry => entry.pathName === path);
2726
+
2727
+ return folderEntry;
2728
+ }
2729
+
2730
+ /**
2731
+ * Unified method to renew MS Graph webhook subscription with proper locking
2732
+ * Can be called from timer or lifecycle notification handler
2733
+ * @param {boolean} force - Force renewal even if not expired soon
2734
+ * @returns {Object} Result with success status and details
2735
+ */
2736
+ async renewSubscription(force = false) {
2737
+ const lockKey = `${this.getAccountKey()}:subscription-renew-lock`;
2738
+ const lockTTL = 30; // 30 seconds TTL for the lock to prevent blocking on crash
2739
+
2740
+ // Try to acquire lock using Redis SET NX with expiry
2741
+ const lockAcquired = await this.redis.set(lockKey, Date.now(), 'EX', lockTTL, 'NX');
2742
+
2743
+ if (!lockAcquired) {
2744
+ this.logger.info({
2745
+ msg: 'Subscription renewal skipped',
2746
+ reason: 'lock_exists',
2747
+ account: this.account
2748
+ });
2749
+ return { success: false, reason: 'lock_exists' };
2750
+ }
2751
+
2752
+ try {
2753
+ // Get current subscription
2754
+ let outlookSubscription = await this.redis.hget(this.getAccountKey(), 'outlookSubscription');
2755
+ if (outlookSubscription) {
2756
+ try {
2757
+ outlookSubscription = JSON.parse(outlookSubscription);
2758
+ } catch (err) {
2759
+ outlookSubscription = {};
2760
+ }
2761
+ } else {
2762
+ outlookSubscription = {};
2763
+ }
2764
+
2765
+ // Check if subscription exists and is valid
2766
+ if (!outlookSubscription.id) {
2767
+ this.subscriptionState = 'unset';
2768
+ return { success: false, reason: 'no_subscription' };
2769
+ }
2770
+
2771
+ let existingExpirationDateTime = new Date(outlookSubscription.expirationDateTime);
2772
+ if (existingExpirationDateTime.toString() === 'Invalid Date') {
2773
+ existingExpirationDateTime = null;
2774
+ }
2775
+
2776
+ const now = Date.now();
2777
+
2778
+ // Check if already expired
2779
+ if (existingExpirationDateTime && existingExpirationDateTime.getTime() < now) {
2780
+ this.logger.warn({
2781
+ msg: 'Subscription already expired',
2782
+ subscriptionId: outlookSubscription.id,
2783
+ expirationDateTime: outlookSubscription.expirationDateTime,
2784
+ account: this.account
2785
+ });
2786
+ // Clear the expired subscription
2787
+ await this.redis.hdel(this.getAccountKey(), 'outlookSubscription');
2788
+ this.subscriptionState = 'expired';
2789
+ return { success: false, reason: 'expired' };
2790
+ }
2791
+
2792
+ // Check if renewal is needed
2793
+ const shouldRenew = force || (existingExpirationDateTime && existingExpirationDateTime.getTime() < now + OUTLOOK_EXPIRATION_RENEW_TIME);
2794
+
2795
+ if (!shouldRenew) {
2796
+ this.logger.debug({
2797
+ msg: 'Subscription renewal not needed',
2798
+ subscriptionId: outlookSubscription.id,
2799
+ expirationDateTime: outlookSubscription.expirationDateTime,
2800
+ account: this.account
2801
+ });
2802
+ this.subscriptionState = 'valid';
2803
+ return { success: true, reason: 'not_needed' };
2804
+ }
2805
+
2806
+ // Perform renewal
2807
+ const expirationDateTime = new Date(now + OUTLOOK_EXPIRATION_TIME);
2808
+ const subscriptionPayload = {
2809
+ expirationDateTime: expirationDateTime.toISOString()
2810
+ };
2811
+
2812
+ // Update state to renewing
2813
+ outlookSubscription.state = {
2814
+ state: 'renewing',
2815
+ time: Date.now()
2816
+ };
2817
+ await this.redis.hSetExists(this.getAccountKey(), 'outlookSubscription', JSON.stringify(outlookSubscription));
2818
+ this.subscriptionState = 'pending';
2819
+
2820
+ try {
2821
+ const subscriptionRes = await this.request(`/subscriptions/${outlookSubscription.id}`, 'PATCH', subscriptionPayload);
2822
+
2823
+ if (subscriptionRes?.expirationDateTime) {
2824
+ // Check if this was a retry before clearing state
2825
+ const wasRetry = outlookSubscription.state?.retryCount > 0;
2826
+ const previousRetryCount = outlookSubscription.state?.retryCount || 0;
2827
+
2828
+ outlookSubscription.expirationDateTime = subscriptionRes.expirationDateTime;
2829
+ outlookSubscription.state = {
2830
+ state: 'created',
2831
+ time: Date.now(),
2832
+ // Clear any previous error state and retry count
2833
+ retryCount: 0,
2834
+ error: null
2835
+ };
2836
+
2837
+ await this.redis.hSetExists(this.getAccountKey(), 'outlookSubscription', JSON.stringify(outlookSubscription));
2838
+ this.subscriptionState = 'valid';
2839
+
2840
+ // Log if this was a successful retry after errors
2841
+ this.logger.info({
2842
+ msg: wasRetry ? 'Subscription renewed successfully after retry' : 'Subscription renewed successfully',
2843
+ subscriptionId: outlookSubscription.id,
2844
+ newExpirationDateTime: subscriptionRes.expirationDateTime,
2845
+ account: this.account,
2846
+ wasRetry,
2847
+ previousRetryCount
2848
+ });
2849
+
2850
+ return { success: true, expirationDateTime: subscriptionRes.expirationDateTime };
2851
+ } else {
2852
+ throw new Error('Empty server response');
2853
+ }
2854
+ } catch (err) {
2855
+ this.logger.error({
2856
+ msg: 'Subscription renewal failed',
2857
+ subscriptionId: outlookSubscription.id,
2858
+ account: this.account,
2859
+ err
2860
+ });
2861
+
2862
+ outlookSubscription.state = {
2863
+ state: 'error',
2864
+ error: `Subscription renewal failed: ${err.oauthRequest?.response?.error?.message || err.message}`,
2865
+ time: Date.now(),
2866
+ retryCount: (outlookSubscription.state?.retryCount || 0) + 1
2867
+ };
2868
+ await this.redis.hSetExists(this.getAccountKey(), 'outlookSubscription', JSON.stringify(outlookSubscription));
2869
+ this.subscriptionState = 'failed';
2870
+
2871
+ // Schedule retry with exponential backoff (max 3 retries)
2872
+ const retryCount = outlookSubscription.state.retryCount;
2873
+ if (retryCount <= 3) {
2874
+ // Exponential backoff: 30s, 60s, 120s
2875
+ const retryDelay = Math.min(30 * Math.pow(2, retryCount - 1), 120) * 1000;
2876
+
2877
+ setTimeout(() => {
2878
+ // Retry will also acquire lock
2879
+ this.renewSubscription(force).catch(err => {
2880
+ this.logger.error({
2881
+ msg: 'Subscription renewal retry failed',
2882
+ account: this.account,
2883
+ retryAttempt: retryCount,
2884
+ err
2885
+ });
2886
+ });
2887
+ }, retryDelay);
2888
+
2889
+ this.logger.info({
2890
+ msg: 'Scheduling subscription renewal retry',
2891
+ account: this.account,
2892
+ retryAttempt: retryCount,
2893
+ retryDelayMs: retryDelay
2894
+ });
2895
+ } else {
2896
+ this.logger.error({
2897
+ msg: 'Max subscription renewal retries exceeded',
2898
+ account: this.account,
2899
+ retryCount
2900
+ });
2901
+ }
2902
+
2903
+ return { success: false, reason: 'renewal_failed', error: err.message };
2904
+ }
2905
+ } finally {
2906
+ // Always release the lock
2907
+ await this.redis.del(lockKey);
2908
+ }
2909
+ }
2910
+
2911
+ /**
2912
+ * Ensure webhook subscription exists and is valid
2913
+ * Creates new subscription or renews existing one
2914
+ */
2915
+ async ensureSubscription() {
2916
+ let serviceUrl = (await settings.get('serviceUrl')) || null;
2917
+ let notificationBaseUrl = (await settings.get('notificationBaseUrl')) || serviceUrl || '';
2918
+
2919
+ if (!serviceUrl) {
2920
+ this.logger.fatal({ msg: 'Service URL not set' });
2921
+ return false;
2922
+ }
2923
+
2924
+ let outlookSubscription = await this.redis.hget(this.getAccountKey(), 'outlookSubscription');
2925
+ if (outlookSubscription) {
2926
+ try {
2927
+ outlookSubscription = JSON.parse(outlookSubscription);
2928
+ } catch (err) {
2929
+ // ignore, I guess?
2930
+ }
2931
+ }
2932
+
2933
+ if (!outlookSubscription) {
2934
+ outlookSubscription = {};
2935
+ }
2936
+
2937
+ // Check if subscription is being created/renewed
2938
+ if (['creating', 'renewing'].includes(outlookSubscription.state?.state) && outlookSubscription.state.time > Date.now() - 30 * 60 * 1000) {
2939
+ // allow previous operation to finish (30 minute timeout)
2940
+ this.logger.info({
2941
+ msg: 'Subscription renewal skipped',
2942
+ reason: 'pending',
2943
+ subscriptionId: outlookSubscription?.id,
2944
+ state: outlookSubscription.state?.state,
2945
+ created: outlookSubscription.state?.time,
2946
+ expected: Date.now() - 30 * 60 * 1000
2947
+ });
2948
+ this.subscriptionState = 'pending';
2949
+ return;
2950
+ }
2951
+
2952
+ let now = Date.now();
2953
+ let expirationDateTime = new Date(now + OUTLOOK_EXPIRATION_TIME);
2954
+
2955
+ // Renew existing subscription if needed
2956
+ if (outlookSubscription.id) {
2957
+ let existingExpirationDateTime = new Date(outlookSubscription.expirationDateTime);
2958
+ if (existingExpirationDateTime.toString() === 'Invalid Date') {
2959
+ existingExpirationDateTime = null;
2960
+ }
2961
+
2962
+ if (existingExpirationDateTime && existingExpirationDateTime.getTime() < now) {
2963
+ // already expired
2964
+ outlookSubscription = {};
2965
+ } else if (existingExpirationDateTime && existingExpirationDateTime.getTime() < now + OUTLOOK_EXPIRATION_RENEW_TIME) {
2966
+ // Use the unified renewal method
2967
+ const renewalResult = await this.renewSubscription(false);
2968
+ if (!renewalResult.success && renewalResult.reason === 'expired') {
2969
+ // Subscription expired, clear it to create a new one
2970
+ outlookSubscription = {};
2971
+ } else {
2972
+ return renewalResult.success;
2973
+ }
2974
+ } else {
2975
+ // Subscription is valid, do nothing
2976
+ this.logger.info({
2977
+ msg: 'Subscription renewal skipped',
2978
+ reason: 'valid',
2979
+ subscriptionId: outlookSubscription?.id,
2980
+ state: outlookSubscription.state?.state,
2981
+ created: outlookSubscription.state?.time,
2982
+ expirationDateTime: outlookSubscription.expirationDateTime
2983
+ });
2984
+ this.subscriptionState = 'valid';
2985
+ return;
2986
+ }
2987
+ }
2988
+
2989
+ // Create new subscription if needed
2990
+ if (!outlookSubscription.id) {
2991
+ const notificationUrl = prepareUrl('/oauth/msg/notification', notificationBaseUrl, {
2992
+ account: this.account
2993
+ });
2994
+
2995
+ const lifecycleNotificationUrl = prepareUrl('/oauth/msg/lifecycle', notificationBaseUrl, {
2996
+ account: this.account
2997
+ });
2998
+
2999
+ const subscriptionPayload = {
3000
+ changeType: 'created,updated,deleted',
3001
+ notificationUrl,
3002
+ lifecycleNotificationUrl,
3003
+ resource: `/${this.oauth2UserPath}/messages`,
3004
+ expirationDateTime: expirationDateTime.toISOString(),
3005
+ clientState: crypto.randomUUID()
3006
+ };
3007
+
3008
+ outlookSubscription.state = {
3009
+ state: 'creating',
3010
+ time: Date.now()
3011
+ };
3012
+ await this.redis.hSetExists(this.getAccountKey(), 'outlookSubscription', JSON.stringify(outlookSubscription));
3013
+ this.subscriptionState = 'pending';
3014
+
3015
+ let subscriptionRes;
3016
+ try {
3017
+ subscriptionRes = await this.request(`/subscriptions`, 'post', subscriptionPayload);
3018
+ if (subscriptionRes?.expirationDateTime) {
3019
+ outlookSubscription = {
3020
+ id: subscriptionRes.id,
3021
+ expirationDateTime: subscriptionRes.expirationDateTime,
3022
+ clientState: subscriptionRes.clientState,
3023
+ state: {
3024
+ state: 'created',
3025
+ time: Date.now()
3026
+ }
3027
+ };
3028
+ this.subscriptionState = 'valid';
3029
+ } else {
3030
+ throw new Error('Empty server response');
3031
+ }
3032
+ } catch (err) {
3033
+ outlookSubscription.state = {
3034
+ state: 'error',
3035
+ error: `Subscription failed: ${err.oauthRequest?.response?.error?.message || err.message}`,
3036
+ time: Date.now()
3037
+ };
3038
+ this.subscriptionState = 'failed';
3039
+ } finally {
3040
+ await this.redis.hSetExists(this.getAccountKey(), 'outlookSubscription', JSON.stringify(outlookSubscription));
3041
+ }
3042
+ }
3043
+ }
3044
+
3045
+ /**
3046
+ * Format Graph API message data to EmailEngine format
3047
+ * Handles all message properties and attachments
3048
+ */
3049
+ formatMessage(messageData, options) {
3050
+ let { extended, path, textType, showPath } = options || {};
3051
+
3052
+ let date = messageData.receivedDateTime ? new Date(messageData.receivedDateTime) : undefined;
3053
+ if (date?.toString() === 'Invalid Date') {
3054
+ date = undefined;
3055
+ }
3056
+
3057
+ // Map Graph API flags to IMAP flags
3058
+ const flags = [];
3059
+ if (messageData.isRead) {
3060
+ flags.push('\\Seen');
3061
+ }
3062
+ if (messageData.isDraft) {
3063
+ flags.push('\\Draft');
3064
+ }
3065
+ if (messageData.flag?.flagStatus === 'flagged') {
3066
+ flags.push('\\Flagged');
3067
+ }
3068
+
3069
+ let encodedTextSize = {};
3070
+ let textContents = {};
3071
+
3072
+ // set defaults for requested text type
3073
+ if (textType && textType !== '*') {
3074
+ textContents[options.textType] = '';
3075
+ encodedTextSize[options.textType] = 0;
3076
+ }
3077
+
3078
+ // Process message body content
3079
+ if (messageData?.body?.contentType) {
3080
+ let textContent = messageData.body.content || '';
3081
+ let textContentType;
3082
+ switch (messageData.body.contentType) {
3083
+ case 'text':
3084
+ textContentType = 'plain';
3085
+ break;
3086
+
3087
+ case 'html':
3088
+ default:
3089
+ textContentType = messageData.body.contentType;
3090
+ break;
3091
+ }
3092
+ encodedTextSize[textContentType] = Buffer.byteLength(textContent);
3093
+ if ([textContentType, '*'].includes(textType)) {
3094
+ textContents[textContentType] = textContent;
3095
+ }
3096
+ }
3097
+
3098
+ let message = {
3099
+ id: messageData.id,
3100
+
3101
+ path: ((extended || showPath) && path) || undefined,
3102
+
3103
+ emailId: messageData.id,
3104
+ threadId: messageData.conversationId || undefined,
3105
+
3106
+ date: date ? date.toISOString() : undefined,
3107
+
3108
+ flags,
3109
+ labels: messageData.categories?.length ? messageData.categories : undefined,
3110
+
3111
+ // Convenience boolean flags
3112
+ unseen: !flags.includes('\\Seen') ? true : undefined,
3113
+ flagged: flags.includes('\\Flagged') ? true : undefined,
3114
+ draft: flags.includes('\\Draft') ? true : undefined,
3115
+
3116
+ subject: messageData.subject || undefined,
3117
+ from: messageData.from?.emailAddress || undefined,
3118
+
3119
+ replyTo: messageData.replyTo?.length ? messageData.replyTo.map(entry => entry.emailAddress).filter(entry => entry) : undefined,
3120
+ sender: (extended && messageData.sender?.emailAddress) || undefined,
3121
+
3122
+ to: messageData.toRecipients?.length ? messageData.toRecipients.map(entry => entry.emailAddress).filter(entry => entry) : undefined,
3123
+ cc: messageData.ccRecipients?.length ? messageData.ccRecipients.map(entry => entry.emailAddress).filter(entry => entry) : undefined,
3124
+ bcc: extended && messageData.bccRecipients?.length ? messageData.bccRecipients.map(entry => entry.emailAddress).filter(entry => entry) : undefined,
3125
+
3126
+ messageId: messageData.internetMessageId,
3127
+
3128
+ headers: (extended && messageData.headers) || undefined,
3129
+
3130
+ text: {
3131
+ id: messageData.id,
3132
+ encodedSize: encodedTextSize,
3133
+ plain: textContents?.plain?.toString(),
3134
+ html: textContents?.html?.toString(),
3135
+ hasMore: textContents?.plain || textContents?.html ? false : undefined
3136
+ },
3137
+
3138
+ preview: messageData.bodyPreview
3139
+ };
3140
+
3141
+ // Check for auto-reply based on headers
3142
+ if (message.headers && this.isAutoreply(message)) {
3143
+ message.isAutoReply = true;
3144
+ }
3145
+
3146
+ // Process attachments
3147
+ if (messageData.attachments?.length) {
3148
+ message.attachments = messageData.attachments.map(entry => {
3149
+ const attachment = {
3150
+ // Encode both message ID and attachment ID for later retrieval
3151
+ id: msgpack.encode([messageData.id, entry.id]).toString('base64url'),
3152
+ contentType: entry.contentType,
3153
+ encodedSize: entry.size,
3154
+ inline: !!entry.isInline
3155
+ };
3156
+
3157
+ if (entry.name) {
3158
+ attachment.filename = entry.name;
3159
+ }
3160
+
3161
+ if (entry.contentId) {
3162
+ // Normalize content ID format
3163
+ attachment.contentId = entry.contentId.replace(/^<*/, '<').replace(/>*$/, '>');
3164
+ // Check if attachment is embedded in HTML
3165
+ if (textContents?.html?.indexOf(`cid:${attachment.contentId.replace(/^[\s<]*|[\s>]*$/g, '')}`) >= 0) {
3166
+ attachment.embedded = true;
3167
+ }
3168
+ }
3169
+
3170
+ return attachment;
3171
+ });
3172
+ }
3173
+
3174
+ return message;
3175
+ }
3176
+
3177
+ /**
3178
+ * Get attachment content by attachment ID
3179
+ * Handles both regular attachments and message/rfc822 attachments
3180
+ */
3181
+ async getAttachmentContent(attachmentId, options) {
3182
+ options = options || {};
3183
+
3184
+ // Decode compound attachment ID
3185
+ const [emailId, id] = msgpack.decode(Buffer.from(attachmentId, 'base64url'));
3186
+
3187
+ await this.prepare();
3188
+
3189
+ const attachmentErrorHandler = err => {
3190
+ switch (err.oauthRequest?.status) {
3191
+ case 404: {
3192
+ let error = new Error('Unknown attachment');
3193
+ error.info = {
3194
+ response: `Attachment does not exist`
3195
+ };
3196
+ error.code = 'NotFound';
3197
+ error.statusCode = 404;
3198
+ return error;
3199
+ }
3200
+
3201
+ case 400: {
3202
+ let error = new Error('Invalid request');
3203
+ error.info = {
3204
+ response: err.oauthRequest?.response?.error?.message || `Invalid request`
3205
+ };
3206
+ error.code = err.oauthRequest?.response?.error?.code || 'InvalidRequest';
3207
+ error.statusCode = 400;
3208
+ return error;
3209
+ }
3210
+
3211
+ default:
3212
+ this.logger.error({
3213
+ msg: 'Failed to fetch attachment',
3214
+ emailId,
3215
+ err
3216
+ });
3217
+ return err;
3218
+ }
3219
+ };
3220
+
3221
+ let attachmentResponse;
3222
+
3223
+ try {
3224
+ attachmentResponse = await this.request(`/${this.oauth2UserPath}/messages/${emailId}/attachments/${id}`);
3225
+ } catch (err) {
3226
+ throw attachmentErrorHandler(err);
3227
+ }
3228
+
3229
+ let content = attachmentResponse?.contentBytes
3230
+ ? options.returnBase64
3231
+ ? attachmentResponse.contentBytes
3232
+ : Buffer.from(attachmentResponse.contentBytes, 'base64')
3233
+ : null;
3234
+
3235
+ // Some attachments (like message/rfc822) need special handling
3236
+ if (attachmentResponse?.contentType && !attachmentResponse?.contentBytes) {
3237
+ // fetch raw value instead, happens with message/rfc822 attachments
3238
+ try {
3239
+ let rawAttachmentContentStr = await this.request(
3240
+ `/${this.oauth2UserPath}/messages/${emailId}/attachments/${id}/$value`,
3241
+ 'get',
3242
+ Buffer.alloc(0),
3243
+ {
3244
+ returnText: true
3245
+ }
3246
+ );
3247
+ if (rawAttachmentContentStr) {
3248
+ if (options.returnBase64) {
3249
+ content = Buffer.from(rawAttachmentContentStr).toString('base64');
3250
+ } else {
3251
+ content = Buffer.from(rawAttachmentContentStr);
3252
+ }
3253
+ }
3254
+ } catch (err) {
3255
+ throw attachmentErrorHandler(err);
3256
+ }
3257
+ }
3258
+
3259
+ return options.contentOnly
3260
+ ? content
3261
+ : {
3262
+ content,
3263
+ contentType: attachmentResponse?.contentType,
3264
+ filename: attachmentResponse?.name
3265
+ };
3266
+ }
3267
+
3268
+ /**
3269
+ * Start processing pending webhook notifications
3270
+ * Prevents concurrent processing with a flag
3271
+ */
3272
+ triggerSync() {
3273
+ if (this.processingHistory) {
3274
+ return;
3275
+ }
3276
+ this.processingHistory = true;
3277
+ this.processHistory()
3278
+ .catch(err => {
3279
+ this.logger.error({ msg: 'Failed to process account history', account: this.account, err });
3280
+ })
3281
+ .finally(() => {
3282
+ this.processingHistory = false;
3283
+ });
3284
+ }
3285
+
3286
+ /**
3287
+ * Process queued webhook events
3288
+ * Handles created, updated, and deleted message events
3289
+ */
3290
+ async processHistory() {
3291
+ let event;
3292
+ let newMessageOptions;
3293
+
3294
+ while ((event = await this.accountObject.pullQueueEvent()) !== null) {
3295
+ switch (event.type) {
3296
+ case 'updated':
3297
+ await this.processUpdatedMessage(event.message);
3298
+ break;
3299
+
3300
+ case 'deleted': {
3301
+ await this.processDeletedMessage(event.message);
3302
+ break;
3303
+ }
3304
+
3305
+ case 'created': {
3306
+ if (!newMessageOptions) {
3307
+ // cache options for efficiency
3308
+ newMessageOptions = await this.getMessageFetchOptions();
3309
+ }
3310
+
3311
+ newMessageOptions.showPath = true;
3312
+
3313
+ let messageData = await this.prepareNewMessage(event.message, newMessageOptions);
3314
+ if (messageData) {
3315
+ // When an email is sent, multiple "created" events are triggered: one for the draft and one for the sent mail folder.
3316
+ // However, since we process the event with a delay, we only observe the message stored in the Sent Mail folder.
3317
+ // This happens because the message was already moved there by the time we began processing the initial event from the drafts folder.
3318
+
3319
+ // Check rolling bucket lock to see if we recently processed the same new email event for the same folder
3320
+ let recentlySeen = await this.rollingBucketLock(`${messageData.id}:created`, messageData.path);
3321
+ if (recentlySeen) {
3322
+ this.logger.debug({ msg: 'Ignore recently seen new email event', type: 'history-event', emailId: event.message });
3323
+ break;
3324
+ }
3325
+
3326
+ await this.processNew(messageData, newMessageOptions);
3327
+ }
3328
+ break;
3329
+ }
3330
+
3331
+ default:
3332
+ this.logger.debug({ msg: 'Future feature', type: 'history-event', event });
3333
+ break;
3334
+ }
3335
+ }
3336
+ }
3337
+
3338
+ /**
3339
+ * Process a deleted message event
3340
+ * Verifies if actually deleted or just moved
3341
+ */
3342
+ async processDeletedMessage(emailId) {
3343
+ // Verify if the message was actually deleted.
3344
+ // If the email was moved, we receive both a 'deleted' and a 'created' event with the same ID.
3345
+ let messageData;
3346
+ try {
3347
+ messageData = await this.request(`/${this.oauth2UserPath}/messages/${emailId}`, 'get', {
3348
+ $select: ['id', 'parentFolderId'].join(',')
3349
+ });
3350
+ } catch (err) {
3351
+ this.logger.error({
3352
+ msg: 'Failed to fetch message data',
3353
+ emailId,
3354
+ err
3355
+ });
3356
+ }
3357
+ if (messageData) {
3358
+ // The message still exists, so there's no need to notify about the deletion.
3359
+ // The message was likely moved, but since we cannot determine the previous mailbox folder,
3360
+ // there is no point in notifying about it.
3361
+ this.logger.debug({ msg: 'Ignore deleted email event. Still exists.', type: 'history-event', emailId, messageData });
3362
+ return;
3363
+ }
3364
+
3365
+ let messageUpdate = {
3366
+ id: emailId
3367
+ };
3368
+
3369
+ await this.notify(this, MESSAGE_DELETED_NOTIFY, messageUpdate);
3370
+ }
3371
+
3372
+ /**
3373
+ * Process an updated message event
3374
+ * Tracks flag changes and deduplicates repeated updates
3375
+ */
3376
+ async processUpdatedMessage(emailId) {
3377
+ let messageData;
3378
+
3379
+ try {
3380
+ messageData = await this.request(`/${this.oauth2UserPath}/messages/${emailId}`, 'get', {
3381
+ $select: ['id', 'isRead', 'isDraft', 'flag', 'conversationId', 'parentFolderId'].join(',')
3382
+ });
3383
+ } catch (err) {
3384
+ this.logger.error({
3385
+ msg: 'Failed to fetch message data',
3386
+ emailId,
3387
+ err
3388
+ });
3389
+ }
3390
+ if (!messageData) {
3391
+ // do nothing, message not found
3392
+ return;
3393
+ }
3394
+
3395
+ let folder;
3396
+ try {
3397
+ folder = await this.resolveFolder(messageData.parentFolderId, { byId: true });
3398
+ } catch (err) {
3399
+ this.logger.error({
3400
+ msg: 'Failed to resolve folder for message',
3401
+ emailId,
3402
+ err
3403
+ });
3404
+ }
3405
+
3406
+ let path = folder ? folder.pathName : this.path;
3407
+ let specialUse = folder ? folder.specialUse : this.listingEntry.specialUse;
3408
+
3409
+ // we do not know which flags were added or removed, so list the full value
3410
+
3411
+ const changes = { flags: { value: [] } };
3412
+
3413
+ if (messageData.isRead) {
3414
+ changes.flags.value.push('\\Seen');
3415
+ }
3416
+
3417
+ if (messageData.isDraft) {
3418
+ changes.flags.value.push('\\Draft');
3419
+ }
3420
+
3421
+ if (messageData.flag?.flagStatus === 'flagged') {
3422
+ changes.flags.value.push('\\Flagged');
3423
+ }
3424
+
3425
+ let messageUpdate = {
3426
+ id: messageData.id,
3427
+ threadId: messageData.conversationId,
3428
+ changes
3429
+ };
3430
+
3431
+ // Check rolling bucket lock to see if we recently processed the update event for the same email
3432
+ let recentlySeen = await this.rollingBucketLock(`${messageData.id}:updated`, JSON.stringify(changes.flags.value));
3433
+ if (recentlySeen) {
3434
+ this.logger.debug({ msg: 'Ignore recently seen updated email event', type: 'history-event', emailId: messageData.id, flags: changes.flags.value });
3435
+ return;
3436
+ }
3437
+
3438
+ await this.notify({ path, specialUse }, MESSAGE_UPDATED_NOTIFY, messageUpdate);
3439
+ }
3440
+
3441
+ /**
3442
+ * Prepare a new message for processing
3443
+ * Fetches full message details and checks if it's actually new
3444
+ */
3445
+ async prepareNewMessage(emailId, options) {
3446
+ this.logger.debug({ msg: 'New message', id: emailId, options });
3447
+
3448
+ if (options.fetchHeaders) {
3449
+ options.headers = options.fetchHeaders;
3450
+ } else {
3451
+ options.headers = 'headers' in options ? options.headers : false;
3452
+ }
3453
+
3454
+ let messageData = await this.getMessage(emailId, options);
3455
+
3456
+ if (!messageData) {
3457
+ await this.notify(this, MESSAGE_MISSING_NOTIFY, {
3458
+ id: emailId
3459
+ });
3460
+ return;
3461
+ }
3462
+
3463
+ // check if we have seen this message before or not (approximate estimation using HyperLogLog)
3464
+ messageData.seemsLikeNew = messageData.messageSpecialUse !== '\\Sent' && !!(await this.redis.pfadd(this.getSeenMessagesKey(), messageData.messageId));
3465
+
3466
+ return messageData;
3467
+ }
3468
+
3469
+ /**
3470
+ * Search for message IDs matching criteria
3471
+ * Used for bulk operations
3472
+ */
3473
+ async searchEmailIds(path, search) {
3474
+ let messages = [];
3475
+ let cursor;
3476
+
3477
+ let maxMessages = 1000;
3478
+ let notDone = true;
3479
+
3480
+ while (notDone) {
3481
+ let messageListResult = await this.listMessages(
3482
+ {
3483
+ path,
3484
+ pageSize: 250,
3485
+ search,
3486
+ cursor
3487
+ },
3488
+ { metadataOnly: true }
3489
+ );
3490
+
3491
+ if (messageListResult?.messages) {
3492
+ messages = messages.concat(messageListResult?.messages);
3493
+ if (messages.length >= maxMessages) {
3494
+ messages = messages.slice(0, maxMessages);
3495
+ notDone = false;
3496
+ break;
3497
+ }
3498
+ }
3499
+
3500
+ if (!messageListResult.nextPageCursor) {
3501
+ notDone = false;
3502
+ break;
3503
+ }
3504
+ cursor = messageListResult.nextPageCursor;
3505
+ }
3506
+
3507
+ return messages.map(message => message.id);
3508
+ }
3509
+
3510
+ /**
3511
+ * Convert IMAP SEARCH query to Outlook native search syntax
3512
+ * Uses KQL (Keyword Query Language)
3513
+ */
3514
+ prepareSearchQuery(search) {
3515
+ search = search || {};
3516
+
3517
+ const searchParts = [];
3518
+
3519
+ const enabledKeys = ['to', 'cc', 'bcc', 'larger', 'smaller', 'body', 'before', 'sentBefore', 'since', 'sentSince'];
3520
+
3521
+ // not supported search terms
3522
+ for (let key of Object.keys(search)) {
3523
+ if (!enabledKeys.includes(key)) {
3524
+ let error = new Error(`Unsupported search term "${key}" for Outlook Search`);
3525
+ error.code = 'UnsupportedSearchTerm';
3526
+ error.statusCode = 400;
3527
+ throw error;
3528
+ }
3529
+ }
3530
+
3531
+ /**
3532
+ * Escape and format search terms for KQL
3533
+ */
3534
+ let escapeString = term => {
3535
+ if (typeof term === 'object' && term && Object.prototype.toString.apply(term) === '[object Date]') {
3536
+ // convert dates to "MM/DD/YYYY"
3537
+ let d = term.getDate();
3538
+ let m = term.getMonth() + 1;
3539
+ let y = term.getFullYear();
3540
+ term = `${m < 10 ? '0' : ''}${m}/${d < 10 ? '0' : ''}${d}/${y}`;
3541
+ }
3542
+
3543
+ let str = term.replace(/[\s"']+/g, ' ').trim();
3544
+ if (str.indexOf(' ') >= 0) {
3545
+ str = `'${str}'`;
3546
+ }
3547
+
3548
+ return str;
3549
+ };
3550
+
3551
+ // Build KQL query parts
3552
+ for (let key of ['from', 'to', 'cc', 'bcc', 'subject', 'body']) {
3553
+ if (search[key]) {
3554
+ searchParts.push(`${key}:${escapeString(search[key])}`);
3555
+ }
3556
+ }
3557
+
3558
+ if (search.before || search.sentBefore) {
3559
+ searchParts.push(`received<=${escapeString(search.before || search.sentBefore)}`);
3560
+ }
3561
+
3562
+ if (search.since || search.sentSince) {
3563
+ searchParts.push(`sent>=${escapeString(search.since || search.sentSince)}`);
3564
+ }
3565
+
3566
+ if (search.smaller) {
3567
+ searchParts.push(`size<${Number(search.smaller) || 0}`);
3568
+ }
3569
+
3570
+ if (search.larger) {
3571
+ searchParts.push(`size>${Number(search.larger) || 0}`);
3572
+ }
3573
+
3574
+ return searchParts.join(' ');
3575
+ }
3576
+
3577
+ /**
3578
+ * Convert IMAP SEARCH query to OData filter syntax
3579
+ * More precise than KQL but limited in supported fields
3580
+ */
3581
+ prepareFilterQuery(search) {
3582
+ search = search || {};
3583
+
3584
+ const filterParts = [];
3585
+
3586
+ // Fields not supported by OData filters
3587
+ const disabledKeys = [
3588
+ 'seq',
3589
+ 'uid',
3590
+ 'paths',
3591
+ 'answered',
3592
+ 'deleted',
3593
+ 'modseq',
3594
+
3595
+ //
3596
+ 'to',
3597
+ 'cc',
3598
+ 'bcc',
3599
+ 'larger',
3600
+ 'smaller'
3601
+ ];
3602
+
3603
+ // not supported search terms
3604
+ for (let disabledKey of disabledKeys) {
3605
+ if (disabledKey in search) {
3606
+ let error = new Error(`Unsupported search term "${disabledKey}"`);
3607
+ error.code = 'UnsupportedSearchTerm';
3608
+ error.statusCode = 400;
3609
+ throw error;
3610
+ }
3611
+ }
3612
+
3613
+ // Build OData filter expressions
3614
+
3615
+ // unseen
3616
+ if (typeof search.unseen === 'boolean') {
3617
+ filterParts.push(`isRead eq ${search.unseen ? 'false' : 'true'}`);
3618
+ }
3619
+
3620
+ // seen
3621
+ if (typeof search.seen === 'boolean') {
3622
+ filterParts.push(`isRead eq ${search.seen ? 'true' : 'false'}`);
3623
+ }
3624
+
3625
+ // draft
3626
+ if (typeof search.draft === 'boolean') {
3627
+ filterParts.push(`isDraft eq ${search.draft ? 'true' : 'false'}`);
3628
+ }
3629
+
3630
+ // flagged
3631
+ if (typeof search.flagged === 'boolean') {
3632
+ filterParts.push(`flag/flagStatus eq '${search.flagged ? 'flagged' : 'notFlagged'}'`);
3633
+ }
3634
+
3635
+ // from
3636
+ if (search.from) {
3637
+ filterParts.push(
3638
+ `(from/emailAddress/address eq ${this.formatSearchTerm(search.from)} or contains(from/emailAddress/name, ${this.formatSearchTerm(
3639
+ search.from
3640
+ )}))`
3641
+ );
3642
+ }
3643
+
3644
+ if (search.subject) {
3645
+ filterParts.push(`contains(subject, ${this.formatSearchTerm(search.subject)})`);
3646
+ }
3647
+
3648
+ if (search.body) {
3649
+ filterParts.push(`contains(body/content, ${this.formatSearchTerm(search.body)})`);
3650
+ }
3651
+
3652
+ if (search.emailId) {
3653
+ filterParts.push(`id eq ${this.formatSearchTerm(search.emailId)}`);
3654
+ }
3655
+
3656
+ if (search.threadId) {
3657
+ filterParts.push(`conversationId eq ${this.formatSearchTerm(search.threadId)}`);
3658
+ }
3659
+
3660
+ if (search.before || search.sentBefore) {
3661
+ filterParts.push(`receivedDateTime lt ${this.formatSearchTerm(search.before || search.sentBefore, false)}`);
3662
+ }
3663
+
3664
+ if (search.since || search.sentSince) {
3665
+ filterParts.push(`receivedDateTime gt ${this.formatSearchTerm(search.since || search.sentSince, false)}`);
3666
+ }
3667
+
3668
+ // Limited header search support
3669
+ for (let headerKey of Object.keys(search.header || {})) {
3670
+ switch (headerKey.toLowerCase().trim()) {
3671
+ case 'message-id':
3672
+ filterParts.push(`internetMessageId eq ${this.formatSearchTerm(search.header[headerKey])}`);
3673
+ break;
3674
+ default: {
3675
+ let error = new Error(`Unsupported search header "${headerKey}"`);
3676
+ error.code = 'UnsupportedSearchTerm';
3677
+ error.statusCode = 400;
3678
+ throw error;
3679
+ }
3680
+ }
3681
+ }
3682
+
3683
+ return filterParts.join(' and ').trim();
3684
+ }
3685
+
3686
+ /**
3687
+ * Implement time-based deduplication using Redis buckets
3688
+ * Prevents processing duplicate events within a time window
3689
+ */
3690
+ async rollingBucketLock(key, value = '1') {
3691
+ // 10 minute buckets
3692
+ let currentBucketTime = new Date(`${new Date().toISOString().substring(0, 15)}0:00.000Z`).getTime();
3693
+
3694
+ let buckets = 2;
3695
+
3696
+ let pipeline = this.redis.multi();
3697
+ // Check one bucket to the future, current bucket, and one to the past
3698
+ // Always check newer first. In this case, if there is an outdated value in an older bucket, then we use the newer value
3699
+ for (let i = -1; i < buckets; i++) {
3700
+ let bucketTime = new Date(currentBucketTime - i * 10 * 60 * 1000).toISOString();
3701
+ let bucketKey = `${REDIS_PREFIX}bck:${bucketTime}`;
3702
+ pipeline = pipeline.hget(bucketKey, key);
3703
+ }
3704
+
3705
+ let pipelineRes = await pipeline.exec();
3706
+ for (let [, bucketRes] of pipelineRes) {
3707
+ if (bucketRes) {
3708
+ if (bucketRes === value) {
3709
+ return true;
3710
+ }
3711
+ // There is a value, but it does not match the known last value
3712
+ break;
3713
+ }
3714
+ }
3715
+
3716
+ // Add the key to current bucket
3717
+ let bucketTime = new Date(currentBucketTime).toISOString();
3718
+ let bucketKey = `${REDIS_PREFIX}bck:${bucketTime}`;
3719
+ await this.redis
3720
+ .multi()
3721
+ .hset(bucketKey, key, value)
3722
+ // make sure the bucket does not expire until the next one is already valid
3723
+ .expire(bucketKey, 12 * 60)
3724
+ .exec();
3725
+
3726
+ return false;
3727
+ }
3728
+
3729
+ /**
3730
+ * Format search terms for OData filters
3731
+ * Handles proper escaping and date formatting
3732
+ */
3733
+ formatSearchTerm(term, quot = "'") {
3734
+ if (typeof term === 'object' && term && Object.prototype.toString.apply(new Date()) === '[object Date]') {
3735
+ term = term.toISOString();
3736
+ }
3737
+
3738
+ term = (term || '')
3739
+ .toString()
3740
+ .replace(/[\s"]+/g, ' ')
3741
+ .trim();
3742
+
3743
+ if (quot === "'") {
3744
+ term = term.replace(/'/g, "''");
3745
+ }
3746
+
3747
+ return `${quot ? quot : ''}${term}${quot ? quot : ''}`;
3748
+ }
3749
+
3750
+ /**
3751
+ * Decode pagination cursor string
3752
+ * Supports both page numbers and Graph API skip tokens
3753
+ */
3754
+ decodeCursorStr(cursorStr) {
3755
+ let type = 'ms';
3756
+
3757
+ if (cursorStr) {
3758
+ let splitPos = cursorStr.indexOf('_');
3759
+ if (splitPos >= 0) {
3760
+ let cursorType = cursorStr.substring(0, splitPos);
3761
+ cursorStr = cursorStr.substring(splitPos + 1);
3762
+ if (cursorType && type !== cursorType) {
3763
+ let error = new Error('Invalid cursor');
3764
+ error.code = 'InvalidCursorType';
3765
+ throw error;
3766
+ }
3767
+ }
3768
+
3769
+ try {
3770
+ let { page: cursorPage, skipToken } = JSON.parse(Buffer.from(cursorStr, 'base64url'));
3771
+ if (typeof cursorPage === 'number' && cursorPage >= 0) {
3772
+ return { cursorPage, skipToken };
3773
+ }
3774
+ } catch (err) {
3775
+ this.logger.error({ msg: 'Cursor parsing error', cursorStr, err });
3776
+
3777
+ let error = new Error('Invalid paging cursor');
3778
+ error.code = 'InvalidCursorValue';
3779
+ error.statusCode = 400;
3780
+ throw error;
3781
+ }
3782
+ }
3783
+
3784
+ return null;
3785
+ }
3786
+
3787
+ /**
3788
+ * Encode pagination state into cursor string
3789
+ * Includes both page number and Graph API skip token
3790
+ */
3791
+ encodeCursorString(cursorPage, skipToken) {
3792
+ if ((typeof cursorPage !== 'number' && !skipToken) || cursorPage < 0) {
3793
+ return null;
3794
+ }
3795
+
3796
+ cursorPage = cursorPage || 0;
3797
+
3798
+ let type = 'ms';
3799
+ let encodedToken = `${type}_${Buffer.from(JSON.stringify({ page: cursorPage, skipToken })).toString('base64url')}`;
3800
+
3801
+ return encodedToken;
3802
+ }
3803
+ }
3804
+
3805
+ module.exports = { OutlookClient };