@vandenberghinc/volt 1.1.26 → 1.1.28

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 (502) hide show
  1. package/backend/dist/cjs/{blacklist.d.ts → backend/src/blacklist.d.ts} +5 -3
  2. package/backend/dist/cjs/{blacklist.js → backend/src/blacklist.js} +8 -5
  3. package/backend/dist/cjs/{cli.js → backend/src/cli.js} +29 -47
  4. package/backend/dist/cjs/backend/src/database/collection.d.ts +1543 -0
  5. package/backend/dist/cjs/backend/src/database/collection.js +3042 -0
  6. package/backend/dist/cjs/backend/src/database/database.d.ts +66 -0
  7. package/backend/dist/cjs/{database → backend/src/database}/database.js +48 -43
  8. package/backend/dist/cjs/backend/src/database/filters/filters.d.ts +6 -0
  9. package/backend/dist/cjs/backend/src/database/filters/filters.js +15 -0
  10. package/backend/dist/cjs/backend/src/database/filters/strict_filter.d.ts +223 -0
  11. package/backend/dist/cjs/backend/src/database/filters/strict_filter.js +15 -0
  12. package/backend/dist/cjs/backend/src/database/filters/strict_filter_test.js +443 -0
  13. package/backend/dist/cjs/backend/src/database/filters/strict_filter_test_v0.js +15 -0
  14. package/backend/dist/cjs/backend/src/database/filters/strict_filter_v0.d.ts +50 -0
  15. package/backend/dist/cjs/backend/src/database/filters/strict_filter_v0.js +15 -0
  16. package/backend/dist/cjs/backend/src/database/filters/strict_filter_v1.d.ts +76 -0
  17. package/backend/dist/cjs/backend/src/database/filters/strict_filter_v1.js +15 -0
  18. package/backend/dist/cjs/backend/src/database/filters/strict_filter_v2.d.ts +75 -0
  19. package/backend/dist/cjs/backend/src/database/filters/strict_filter_v2.js +15 -0
  20. package/backend/dist/cjs/backend/src/database/filters/strict_filter_v3.d.ts +219 -0
  21. package/backend/dist/cjs/backend/src/database/filters/strict_filter_v3.js +15 -0
  22. package/backend/dist/cjs/backend/src/database/filters/strict_update_filter.d.ts +165 -0
  23. package/backend/dist/cjs/backend/src/database/filters/strict_update_filter.js +15 -0
  24. package/backend/dist/cjs/backend/src/database/filters/strict_update_filter_test.d.ts +5 -0
  25. package/backend/dist/cjs/backend/src/database/filters/strict_update_filter_test.js +355 -0
  26. package/backend/dist/cjs/backend/src/database/flatten.d.ts +75 -0
  27. package/backend/dist/cjs/{logger.js → backend/src/database/flatten.js} +18 -7
  28. package/backend/dist/cjs/backend/src/database/flatten_test.js +175 -0
  29. package/backend/dist/cjs/backend/src/database/quota/quota.d.ts +461 -0
  30. package/backend/dist/cjs/backend/src/database/quota/quota.js +1014 -0
  31. package/backend/dist/cjs/backend/src/database/quota/quota_v1.d.ts +534 -0
  32. package/backend/dist/cjs/backend/src/database/quota/quota_v1.js +1087 -0
  33. package/backend/dist/cjs/backend/src/database/quota/safe_int.d.ts +293 -0
  34. package/backend/dist/cjs/backend/src/database/quota/safe_int.js +573 -0
  35. package/backend/dist/{esm → cjs/backend/src}/endpoint.d.ts +69 -46
  36. package/backend/dist/cjs/{endpoint.js → backend/src/endpoint.js} +87 -101
  37. package/backend/dist/cjs/backend/src/errors/index.d.ts +7 -0
  38. package/backend/dist/cjs/backend/src/errors/index.js +25 -0
  39. package/backend/dist/{esm/utils.d.ts → cjs/backend/src/errors/internal_external.d.ts} +14 -22
  40. package/backend/dist/cjs/backend/src/errors/internal_external.js +85 -0
  41. package/backend/dist/cjs/backend/src/errors/invalid_usage_error.d.ts +38 -0
  42. package/backend/dist/cjs/{mutex.js → backend/src/errors/invalid_usage_error.js} +20 -37
  43. package/backend/dist/cjs/backend/src/errors/system_error.d.ts +230 -0
  44. package/backend/dist/cjs/backend/src/errors/system_error.js +393 -0
  45. package/backend/dist/cjs/backend/src/events.d.ts +54 -0
  46. package/backend/dist/cjs/backend/src/events.js +15 -0
  47. package/backend/dist/cjs/{frontend.js → backend/src/frontend.js} +1 -1
  48. package/backend/dist/cjs/{image_endpoint.d.ts → backend/src/image_endpoint.d.ts} +16 -1
  49. package/backend/dist/cjs/{image_endpoint.js → backend/src/image_endpoint.js} +3 -5
  50. package/backend/dist/cjs/backend/src/logger.d.ts +5 -0
  51. package/backend/dist/cjs/backend/src/logger.js +15 -0
  52. package/backend/dist/cjs/backend/src/meta.d.ts +64 -0
  53. package/backend/dist/cjs/{meta.js → backend/src/meta.js} +9 -12
  54. package/backend/dist/cjs/backend/src/payments/paddle.d.ts +326 -0
  55. package/backend/dist/cjs/{payments → backend/src/payments}/paddle.js +377 -327
  56. package/backend/dist/cjs/backend/src/plugins/browser.d.ts +1 -0
  57. package/backend/dist/cjs/backend/src/plugins/browser.js +15 -0
  58. package/backend/dist/cjs/backend/src/plugins/mail/mail.d.ts +248 -0
  59. package/backend/dist/cjs/backend/src/plugins/mail/mail.js +379 -0
  60. package/backend/dist/{esm → cjs/backend/src}/plugins/mail/ui.d.ts +23 -0
  61. package/backend/dist/cjs/backend/src/plugins/pdf.d.ts +1 -0
  62. package/backend/dist/cjs/backend/src/rate_limit.d.ts +145 -0
  63. package/backend/dist/cjs/backend/src/rate_limit.js +549 -0
  64. package/backend/dist/cjs/{route.d.ts → backend/src/route.d.ts} +3 -10
  65. package/backend/dist/cjs/{route.js → backend/src/route.js} +23 -21
  66. package/backend/dist/cjs/backend/src/server.d.ts +485 -0
  67. package/backend/dist/cjs/{server.js → backend/src/server.js} +688 -873
  68. package/backend/dist/cjs/backend/src/splash_screen.d.ts +80 -0
  69. package/backend/dist/cjs/{splash_screen.js → backend/src/splash_screen.js} +24 -3
  70. package/backend/dist/cjs/backend/src/status.d.ts +74 -0
  71. package/backend/dist/cjs/{status.js → backend/src/status.js} +64 -64
  72. package/backend/dist/cjs/backend/src/stream.d.ts +376 -0
  73. package/backend/dist/cjs/{stream.js → backend/src/stream.js} +299 -276
  74. package/backend/dist/cjs/backend/src/users.d.ts +807 -0
  75. package/backend/dist/cjs/backend/src/users.js +1971 -0
  76. package/backend/dist/cjs/backend/src/utils.d.ts +16 -0
  77. package/backend/dist/cjs/{utils.js → backend/src/utils.js} +14 -77
  78. package/backend/dist/{esm → cjs/backend/src}/view.d.ts +33 -11
  79. package/backend/dist/cjs/backend/src/view.js +508 -0
  80. package/backend/dist/{esm → cjs/backend/src}/volt.d.ts +10 -1
  81. package/backend/dist/cjs/{volt.js → backend/src/volt.js} +8 -5
  82. package/backend/dist/cjs/frontend/src/modules/request.d.ts +70 -0
  83. package/backend/dist/cjs/frontend/src/modules/request.js +99 -0
  84. package/backend/dist/esm/{blacklist.d.ts → backend/src/blacklist.d.ts} +5 -3
  85. package/backend/dist/esm/{blacklist.js → backend/src/blacklist.js} +9 -6
  86. package/backend/dist/esm/{cli.js → backend/src/cli.js} +43 -60
  87. package/backend/dist/esm/backend/src/database/collection.d.ts +1543 -0
  88. package/backend/dist/esm/backend/src/database/collection.js +3510 -0
  89. package/backend/dist/esm/backend/src/database/database.d.ts +66 -0
  90. package/backend/dist/esm/{database → backend/src/database}/database.js +62 -103
  91. package/backend/dist/esm/backend/src/database/document.d.ts +1 -0
  92. package/backend/dist/esm/backend/src/database/document.js +558 -0
  93. package/backend/dist/esm/backend/src/database/filters/filters.d.ts +6 -0
  94. package/backend/dist/esm/backend/src/database/filters/filters.js +1 -0
  95. package/backend/dist/esm/backend/src/database/filters/strict_filter.d.ts +223 -0
  96. package/backend/dist/esm/backend/src/database/filters/strict_filter.js +3 -0
  97. package/backend/dist/esm/backend/src/database/filters/strict_filter_test.d.ts +1 -0
  98. package/backend/dist/esm/backend/src/database/filters/strict_filter_test.js +505 -0
  99. package/backend/dist/esm/backend/src/database/filters/strict_filter_test_v0.d.ts +1 -0
  100. package/backend/dist/esm/backend/src/database/filters/strict_filter_test_v0.js +712 -0
  101. package/backend/dist/esm/backend/src/database/filters/strict_filter_v0.d.ts +50 -0
  102. package/backend/dist/esm/backend/src/database/filters/strict_filter_v0.js +5 -0
  103. package/backend/dist/esm/backend/src/database/filters/strict_filter_v1.d.ts +76 -0
  104. package/backend/dist/esm/backend/src/database/filters/strict_filter_v1.js +44 -0
  105. package/backend/dist/esm/backend/src/database/filters/strict_filter_v2.d.ts +75 -0
  106. package/backend/dist/esm/backend/src/database/filters/strict_filter_v2.js +5 -0
  107. package/backend/dist/esm/backend/src/database/filters/strict_filter_v3.d.ts +219 -0
  108. package/backend/dist/esm/backend/src/database/filters/strict_filter_v3.js +1 -0
  109. package/backend/dist/esm/backend/src/database/filters/strict_update_filter.d.ts +165 -0
  110. package/backend/dist/esm/backend/src/database/filters/strict_update_filter.js +5 -0
  111. package/backend/dist/esm/backend/src/database/filters/strict_update_filter_test.d.ts +5 -0
  112. package/backend/dist/esm/backend/src/database/filters/strict_update_filter_test.js +405 -0
  113. package/backend/dist/esm/backend/src/database/flatten.d.ts +75 -0
  114. package/backend/dist/esm/backend/src/database/flatten.js +22 -0
  115. package/backend/dist/esm/backend/src/database/flatten_test.d.ts +1 -0
  116. package/backend/dist/esm/backend/src/database/flatten_test.js +174 -0
  117. package/backend/dist/esm/backend/src/database/quota/quota.d.ts +461 -0
  118. package/backend/dist/esm/backend/src/database/quota/quota.js +1118 -0
  119. package/backend/dist/esm/backend/src/database/quota/quota_v1.d.ts +534 -0
  120. package/backend/dist/esm/backend/src/database/quota/quota_v1.js +1242 -0
  121. package/backend/dist/esm/backend/src/database/quota/safe_int.d.ts +293 -0
  122. package/backend/dist/esm/backend/src/database/quota/safe_int.js +602 -0
  123. package/backend/dist/{cjs → esm/backend/src}/endpoint.d.ts +69 -46
  124. package/backend/dist/esm/{endpoint.js → backend/src/endpoint.js} +136 -127
  125. package/backend/dist/esm/backend/src/errors/index.d.ts +7 -0
  126. package/backend/dist/esm/backend/src/errors/index.js +7 -0
  127. package/backend/dist/{cjs/utils.d.ts → esm/backend/src/errors/internal_external.d.ts} +14 -22
  128. package/backend/dist/esm/backend/src/errors/internal_external.js +70 -0
  129. package/backend/dist/esm/backend/src/errors/invalid_usage_error.d.ts +38 -0
  130. package/backend/dist/esm/backend/src/errors/invalid_usage_error.js +30 -0
  131. package/backend/dist/esm/backend/src/errors/system_error.d.ts +230 -0
  132. package/backend/dist/esm/backend/src/errors/system_error.js +402 -0
  133. package/backend/dist/esm/backend/src/events.d.ts +54 -0
  134. package/backend/dist/esm/backend/src/events.js +5 -0
  135. package/backend/dist/esm/{frontend.js → backend/src/frontend.js} +1 -1
  136. package/backend/dist/esm/{image_endpoint.d.ts → backend/src/image_endpoint.d.ts} +16 -1
  137. package/backend/dist/esm/{image_endpoint.js → backend/src/image_endpoint.js} +16 -20
  138. package/backend/dist/esm/backend/src/logger.d.ts +5 -0
  139. package/backend/dist/esm/backend/src/logger.js +8 -0
  140. package/backend/dist/esm/backend/src/meta.d.ts +64 -0
  141. package/backend/dist/esm/{meta.js → backend/src/meta.js} +15 -54
  142. package/backend/dist/esm/backend/src/payments/paddle.d.ts +326 -0
  143. package/backend/dist/esm/{payments → backend/src/payments}/paddle.js +417 -452
  144. package/backend/dist/esm/backend/src/plugins/browser.d.ts +1 -0
  145. package/backend/dist/esm/backend/src/plugins/browser.js +170 -0
  146. package/backend/dist/esm/backend/src/plugins/mail/mail.d.ts +248 -0
  147. package/backend/dist/esm/backend/src/plugins/mail/mail.js +389 -0
  148. package/backend/dist/{cjs → esm/backend/src}/plugins/mail/ui.d.ts +23 -0
  149. package/backend/dist/esm/{plugins → backend/src/plugins}/mail/ui.js +3 -6
  150. package/backend/dist/esm/backend/src/plugins/pdf.d.ts +1 -0
  151. package/backend/dist/esm/{plugins → backend/src/plugins}/pdf.js +3 -3
  152. package/backend/dist/esm/backend/src/rate_limit.d.ts +145 -0
  153. package/backend/dist/esm/backend/src/rate_limit.js +667 -0
  154. package/backend/dist/esm/{route.d.ts → backend/src/route.d.ts} +3 -10
  155. package/backend/dist/esm/{route.js → backend/src/route.js} +26 -21
  156. package/backend/dist/esm/backend/src/server.d.ts +485 -0
  157. package/backend/dist/esm/{server.js → backend/src/server.js} +891 -1441
  158. package/backend/dist/esm/backend/src/splash_screen.d.ts +80 -0
  159. package/backend/dist/esm/{splash_screen.js → backend/src/splash_screen.js} +42 -55
  160. package/backend/dist/esm/backend/src/status.d.ts +74 -0
  161. package/backend/dist/esm/backend/src/status.js +199 -0
  162. package/backend/dist/esm/backend/src/stream.d.ts +376 -0
  163. package/backend/dist/esm/{stream.js → backend/src/stream.js} +327 -292
  164. package/backend/dist/esm/backend/src/users.d.ts +809 -0
  165. package/backend/dist/esm/backend/src/users.js +2140 -0
  166. package/backend/dist/esm/backend/src/utils.d.ts +16 -0
  167. package/backend/dist/esm/{utils.js → backend/src/utils.js} +20 -81
  168. package/backend/dist/{cjs → esm/backend/src}/view.d.ts +33 -11
  169. package/backend/dist/esm/{view.js → backend/src/view.js} +266 -86
  170. package/backend/dist/{cjs → esm/backend/src}/volt.d.ts +10 -1
  171. package/backend/dist/esm/{volt.js → backend/src/volt.js} +7 -4
  172. package/backend/dist/esm/frontend/src/modules/request.d.ts +70 -0
  173. package/backend/dist/esm/frontend/src/modules/request.js +117 -0
  174. package/frontend/dist/backend/src/database/collection.d.ts +1543 -0
  175. package/frontend/dist/backend/src/database/collection.js +3510 -0
  176. package/frontend/dist/backend/src/database/database.d.ts +66 -0
  177. package/frontend/dist/backend/src/database/database.js +196 -0
  178. package/frontend/dist/backend/src/database/filters/filters.d.ts +6 -0
  179. package/frontend/dist/backend/src/database/filters/filters.js +1 -0
  180. package/frontend/dist/backend/src/database/filters/strict_filter.d.ts +223 -0
  181. package/frontend/dist/backend/src/database/filters/strict_filter.js +3 -0
  182. package/frontend/dist/backend/src/database/filters/strict_update_filter.d.ts +165 -0
  183. package/frontend/dist/backend/src/database/filters/strict_update_filter.js +5 -0
  184. package/frontend/dist/backend/src/database/flatten.d.ts +75 -0
  185. package/frontend/dist/backend/src/database/flatten.js +22 -0
  186. package/frontend/dist/backend/src/endpoint.d.ts +204 -0
  187. package/frontend/dist/backend/src/endpoint.js +570 -0
  188. package/frontend/dist/backend/src/errors/index.d.ts +7 -0
  189. package/frontend/dist/backend/src/errors/index.js +7 -0
  190. package/frontend/dist/backend/src/errors/internal_external.d.ts +38 -0
  191. package/frontend/dist/backend/src/errors/internal_external.js +70 -0
  192. package/frontend/dist/backend/src/errors/invalid_usage_error.d.ts +38 -0
  193. package/frontend/dist/backend/src/errors/invalid_usage_error.js +30 -0
  194. package/frontend/dist/backend/src/errors/system_error.d.ts +230 -0
  195. package/frontend/dist/backend/src/errors/system_error.js +402 -0
  196. package/frontend/dist/backend/src/events.d.ts +54 -0
  197. package/frontend/dist/backend/src/events.js +5 -0
  198. package/frontend/dist/backend/src/frontend.d.ts +11 -0
  199. package/frontend/dist/backend/src/frontend.js +12 -0
  200. package/frontend/dist/backend/src/image_endpoint.d.ts +39 -0
  201. package/frontend/dist/backend/src/image_endpoint.js +202 -0
  202. package/frontend/dist/backend/src/meta.d.ts +64 -0
  203. package/frontend/dist/backend/src/meta.js +110 -0
  204. package/frontend/dist/backend/src/payments/paddle.d.ts +326 -0
  205. package/frontend/dist/backend/src/payments/paddle.js +2256 -0
  206. package/frontend/dist/backend/src/plugins/mail/mail.d.ts +248 -0
  207. package/frontend/dist/backend/src/plugins/mail/mail.js +389 -0
  208. package/{backend/dist/esm/plugins/mail.d.ts → frontend/dist/backend/src/plugins/mail/ui.d.ts} +23 -0
  209. package/{backend/dist/esm/plugins/mail.js → frontend/dist/backend/src/plugins/mail/ui.js} +3 -6
  210. package/frontend/dist/backend/src/rate_limit.d.ts +145 -0
  211. package/frontend/dist/backend/src/rate_limit.js +673 -0
  212. package/frontend/dist/backend/src/route.d.ts +35 -0
  213. package/frontend/dist/backend/src/route.js +212 -0
  214. package/frontend/dist/backend/src/server.d.ts +485 -0
  215. package/frontend/dist/backend/src/server.js +2670 -0
  216. package/frontend/dist/backend/src/splash_screen.d.ts +80 -0
  217. package/frontend/dist/backend/src/splash_screen.js +135 -0
  218. package/frontend/dist/backend/src/status.d.ts +74 -0
  219. package/frontend/dist/backend/src/status.js +199 -0
  220. package/frontend/dist/backend/src/stream.d.ts +376 -0
  221. package/frontend/dist/backend/src/stream.js +1007 -0
  222. package/frontend/dist/backend/src/users.d.ts +807 -0
  223. package/frontend/dist/backend/src/users.js +2118 -0
  224. package/frontend/dist/backend/src/utils.d.ts +16 -0
  225. package/frontend/dist/backend/src/utils.js +241 -0
  226. package/frontend/dist/backend/src/view.d.ts +162 -0
  227. package/frontend/dist/backend/src/view.js +720 -0
  228. package/frontend/dist/frontend/src/elements/base.d.ts +4414 -0
  229. package/frontend/dist/{elements → frontend/src/elements}/base.js +3624 -260
  230. package/frontend/dist/frontend/src/elements/module.d.ts +95 -0
  231. package/frontend/dist/{elements → frontend/src/elements}/module.js +53 -52
  232. package/frontend/dist/frontend/src/elements/types.d.ts +52 -0
  233. package/frontend/dist/frontend/src/elements/types.js +5 -0
  234. package/frontend/dist/frontend/src/modules/attachment.d.ts +126 -0
  235. package/frontend/dist/frontend/src/modules/attachment.js +306 -0
  236. package/frontend/dist/frontend/src/modules/auth.d.ts +44 -0
  237. package/frontend/dist/frontend/src/modules/auth.js +80 -0
  238. package/frontend/dist/{modules → frontend/src/modules}/color.js +2 -2
  239. package/frontend/dist/frontend/src/modules/compression.d.ts +39 -0
  240. package/frontend/dist/frontend/src/modules/compression.js +102 -0
  241. package/frontend/dist/frontend/src/modules/cookies.d.ts +44 -0
  242. package/frontend/dist/frontend/src/modules/cookies.js +143 -0
  243. package/frontend/dist/frontend/src/modules/events.d.ts +31 -0
  244. package/frontend/dist/frontend/src/modules/events.js +74 -0
  245. package/frontend/dist/frontend/src/modules/google.d.ts +23 -0
  246. package/frontend/dist/frontend/src/modules/google.js +52 -0
  247. package/frontend/dist/frontend/src/modules/meta.d.ts +14 -0
  248. package/frontend/dist/{modules → frontend/src/modules}/meta.js +9 -7
  249. package/frontend/dist/{modules → frontend/src/modules}/paddle.d.ts +37 -134
  250. package/frontend/dist/{modules → frontend/src/modules}/paddle.js +620 -568
  251. package/frontend/dist/frontend/src/modules/request.d.ts +70 -0
  252. package/frontend/dist/frontend/src/modules/request.js +117 -0
  253. package/frontend/dist/frontend/src/modules/settings.d.ts +3 -0
  254. package/frontend/dist/frontend/src/modules/settings.js +5 -0
  255. package/frontend/dist/frontend/src/modules/statics.d.ts +21 -0
  256. package/frontend/dist/{modules → frontend/src/modules}/statics.js +15 -18
  257. package/frontend/dist/frontend/src/modules/support.d.ts +30 -0
  258. package/frontend/dist/frontend/src/modules/support.js +53 -0
  259. package/frontend/dist/{modules → frontend/src/modules}/theme.d.ts +67 -0
  260. package/frontend/dist/{modules → frontend/src/modules}/theme.js +68 -38
  261. package/frontend/dist/frontend/src/modules/themes.d.ts +12 -0
  262. package/frontend/dist/frontend/src/modules/themes.js +22 -0
  263. package/frontend/dist/frontend/src/modules/user.d.ts +164 -0
  264. package/frontend/dist/frontend/src/modules/user.js +268 -0
  265. package/frontend/dist/frontend/src/modules/utils.d.ts +176 -0
  266. package/frontend/dist/frontend/src/modules/utils.js +569 -0
  267. package/frontend/dist/frontend/src/types/gradient.d.ts +29 -0
  268. package/frontend/dist/{types → frontend/src/types}/gradient.js +14 -18
  269. package/frontend/dist/frontend/src/ui/border_button.d.ts +94 -0
  270. package/frontend/dist/{ui → frontend/src/ui}/border_button.js +7 -13
  271. package/frontend/dist/frontend/src/ui/button.d.ts +28 -0
  272. package/frontend/dist/{ui → frontend/src/ui}/button.js +21 -12
  273. package/frontend/dist/frontend/src/ui/canvas.d.ts +138 -0
  274. package/frontend/dist/{ui → frontend/src/ui}/canvas.js +88 -55
  275. package/frontend/dist/frontend/src/ui/checkbox.d.ts +74 -0
  276. package/frontend/dist/{ui → frontend/src/ui}/checkbox.js +80 -41
  277. package/frontend/dist/{ui → frontend/src/ui}/code.d.ts +73 -6
  278. package/frontend/dist/{ui → frontend/src/ui}/code.js +55 -52
  279. package/frontend/dist/{ui → frontend/src/ui}/context_menu.d.ts +4 -0
  280. package/frontend/dist/{ui → frontend/src/ui}/context_menu.js +12 -17
  281. package/frontend/dist/{ui → frontend/src/ui}/css.d.ts +4 -0
  282. package/frontend/dist/{ui → frontend/src/ui}/css.js +3 -3
  283. package/frontend/dist/{ui → frontend/src/ui}/divider.d.ts +4 -0
  284. package/frontend/dist/{ui → frontend/src/ui}/divider.js +3 -3
  285. package/frontend/dist/{ui → frontend/src/ui}/dropdown.d.ts +57 -2
  286. package/frontend/dist/{ui → frontend/src/ui}/dropdown.js +87 -94
  287. package/frontend/dist/{ui → frontend/src/ui}/for_each.d.ts +4 -0
  288. package/frontend/dist/{ui → frontend/src/ui}/for_each.js +3 -3
  289. package/frontend/dist/{ui → frontend/src/ui}/form.d.ts +6 -2
  290. package/frontend/dist/{ui → frontend/src/ui}/form.js +10 -7
  291. package/frontend/dist/frontend/src/ui/frame_modes.d.ts +37 -0
  292. package/frontend/dist/{ui → frontend/src/ui}/frame_modes.js +16 -22
  293. package/frontend/dist/{ui → frontend/src/ui}/google_map.d.ts +4 -0
  294. package/frontend/dist/{ui → frontend/src/ui}/google_map.js +4 -4
  295. package/frontend/dist/{ui → frontend/src/ui}/gradient.d.ts +4 -0
  296. package/frontend/dist/{ui → frontend/src/ui}/gradient.js +3 -3
  297. package/frontend/dist/{ui → frontend/src/ui}/image.d.ts +4 -0
  298. package/frontend/dist/{ui → frontend/src/ui}/image.js +5 -5
  299. package/frontend/dist/frontend/src/ui/input.d.ts +392 -0
  300. package/frontend/dist/{ui → frontend/src/ui}/input.js +346 -360
  301. package/frontend/dist/{ui → frontend/src/ui}/link.d.ts +4 -0
  302. package/frontend/dist/{ui → frontend/src/ui}/link.js +3 -3
  303. package/frontend/dist/{ui → frontend/src/ui}/list.d.ts +4 -0
  304. package/frontend/dist/{ui → frontend/src/ui}/list.js +12 -6
  305. package/frontend/dist/frontend/src/ui/loader_button.d.ts +80 -0
  306. package/frontend/dist/{ui → frontend/src/ui}/loader_button.js +35 -47
  307. package/frontend/dist/frontend/src/ui/loaders.d.ts +57 -0
  308. package/frontend/dist/{ui → frontend/src/ui}/loaders.js +11 -11
  309. package/frontend/dist/{ui → frontend/src/ui}/popup.d.ts +11 -6
  310. package/frontend/dist/{ui → frontend/src/ui}/popup.js +32 -18
  311. package/frontend/dist/frontend/src/ui/pseudo.d.ts +44 -0
  312. package/frontend/dist/{ui → frontend/src/ui}/pseudo.js +84 -8
  313. package/frontend/dist/{ui → frontend/src/ui}/scroller.d.ts +14 -2
  314. package/frontend/dist/{ui → frontend/src/ui}/scroller.js +37 -43
  315. package/frontend/dist/{ui → frontend/src/ui}/slider.d.ts +5 -1
  316. package/frontend/dist/{ui → frontend/src/ui}/slider.js +4 -4
  317. package/frontend/dist/{ui → frontend/src/ui}/spacer.d.ts +4 -0
  318. package/frontend/dist/{ui → frontend/src/ui}/spacer.js +3 -3
  319. package/frontend/dist/{ui → frontend/src/ui}/span.d.ts +4 -0
  320. package/frontend/dist/{ui → frontend/src/ui}/span.js +3 -3
  321. package/frontend/dist/{ui → frontend/src/ui}/stack.d.ts +4 -0
  322. package/frontend/dist/{ui → frontend/src/ui}/stack.js +3 -9
  323. package/frontend/dist/frontend/src/ui/steps.d.ts +131 -0
  324. package/frontend/dist/{ui → frontend/src/ui}/steps.js +30 -45
  325. package/frontend/dist/{ui → frontend/src/ui}/style.d.ts +4 -0
  326. package/frontend/dist/{ui → frontend/src/ui}/style.js +3 -3
  327. package/frontend/dist/{ui → frontend/src/ui}/switch.d.ts +5 -1
  328. package/frontend/dist/{ui → frontend/src/ui}/switch.js +4 -4
  329. package/frontend/dist/{ui → frontend/src/ui}/table.d.ts +4 -0
  330. package/frontend/dist/{ui → frontend/src/ui}/table.js +6 -6
  331. package/frontend/dist/{ui → frontend/src/ui}/tabs.d.ts +45 -3
  332. package/frontend/dist/{ui → frontend/src/ui}/tabs.js +65 -40
  333. package/frontend/dist/{ui → frontend/src/ui}/text.d.ts +4 -0
  334. package/frontend/dist/{ui → frontend/src/ui}/text.js +3 -3
  335. package/frontend/dist/frontend/src/ui/title.d.ts +91 -0
  336. package/frontend/dist/frontend/src/ui/title.js +272 -0
  337. package/frontend/dist/{ui → frontend/src/ui}/view.d.ts +4 -0
  338. package/frontend/dist/{ui → frontend/src/ui}/view.js +3 -3
  339. package/frontend/dist/{volt.d.ts → frontend/src/volt.d.ts} +3 -0
  340. package/frontend/dist/{volt.js → frontend/src/volt.js} +4 -0
  341. package/frontend/tools/bundle_d_ts.js +71 -0
  342. package/frontend/tools/convert_to_jsdoc_input.txt +9452 -0
  343. package/frontend/tools/convert_to_jsdoc_output.txt +7626 -0
  344. package/frontend/tools/convert_to_jsdoc_tmp.js +345 -0
  345. package/package.json +11 -12
  346. package/backend/dist/cjs/database/collection.d.ts +0 -160
  347. package/backend/dist/cjs/database/collection.js +0 -842
  348. package/backend/dist/cjs/database/database.d.ts +0 -121
  349. package/backend/dist/cjs/database/document.d.ts +0 -131
  350. package/backend/dist/cjs/database/document.js +0 -224
  351. package/backend/dist/cjs/database.d.ts +0 -502
  352. package/backend/dist/cjs/database.js +0 -2248
  353. package/backend/dist/cjs/logger.d.ts +0 -3
  354. package/backend/dist/cjs/meta.d.ts +0 -50
  355. package/backend/dist/cjs/mutex.d.ts +0 -24
  356. package/backend/dist/cjs/payments/paddle.d.ts +0 -160
  357. package/backend/dist/cjs/plugins/browser.d.ts +0 -36
  358. package/backend/dist/cjs/plugins/browser.js +0 -198
  359. package/backend/dist/cjs/plugins/css.d.ts +0 -11
  360. package/backend/dist/cjs/plugins/css.js +0 -80
  361. package/backend/dist/cjs/plugins/mail.d.ts +0 -277
  362. package/backend/dist/cjs/plugins/mail.js +0 -1370
  363. package/backend/dist/cjs/plugins/ts/compiler.d.ts +0 -139
  364. package/backend/dist/cjs/plugins/ts/compiler.js +0 -750
  365. package/backend/dist/cjs/plugins/ts/preprocessing.d.ts +0 -14
  366. package/backend/dist/cjs/plugins/ts/preprocessing.js +0 -440
  367. package/backend/dist/cjs/rate_limit.d.ts +0 -63
  368. package/backend/dist/cjs/rate_limit.js +0 -348
  369. package/backend/dist/cjs/request.deprc.d.ts +0 -48
  370. package/backend/dist/cjs/request.deprc.js +0 -572
  371. package/backend/dist/cjs/response.deprc.d.ts +0 -55
  372. package/backend/dist/cjs/response.deprc.js +0 -275
  373. package/backend/dist/cjs/server.d.ts +0 -342
  374. package/backend/dist/cjs/splash_screen.d.ts +0 -35
  375. package/backend/dist/cjs/status.d.ts +0 -61
  376. package/backend/dist/cjs/stream.d.ts +0 -79
  377. package/backend/dist/cjs/users.d.ts +0 -111
  378. package/backend/dist/cjs/users.js +0 -1817
  379. package/backend/dist/cjs/view.js +0 -352
  380. package/backend/dist/cjs/vinc.dev.d.ts +0 -3
  381. package/backend/dist/cjs/vinc.dev.js +0 -7
  382. package/backend/dist/css/adyen.css +0 -92
  383. package/backend/dist/css/volt.css +0 -70
  384. package/backend/dist/esm/database/collection.d.ts +0 -160
  385. package/backend/dist/esm/database/collection.js +0 -1328
  386. package/backend/dist/esm/database/database.d.ts +0 -121
  387. package/backend/dist/esm/database/document.d.ts +0 -131
  388. package/backend/dist/esm/database/document.js +0 -247
  389. package/backend/dist/esm/database.d.ts +0 -502
  390. package/backend/dist/esm/database.js +0 -2423
  391. package/backend/dist/esm/file_watcher.js +0 -329
  392. package/backend/dist/esm/logger.d.ts +0 -3
  393. package/backend/dist/esm/logger.js +0 -11
  394. package/backend/dist/esm/meta.d.ts +0 -50
  395. package/backend/dist/esm/mutex.d.ts +0 -24
  396. package/backend/dist/esm/mutex.js +0 -48
  397. package/backend/dist/esm/payments/paddle.d.ts +0 -160
  398. package/backend/dist/esm/plugins/browser.d.ts +0 -36
  399. package/backend/dist/esm/plugins/browser.js +0 -176
  400. package/backend/dist/esm/plugins/css.d.ts +0 -11
  401. package/backend/dist/esm/plugins/css.js +0 -90
  402. package/backend/dist/esm/plugins/ts/compiler.d.ts +0 -139
  403. package/backend/dist/esm/plugins/ts/compiler.js +0 -1194
  404. package/backend/dist/esm/plugins/ts/preprocessing.d.ts +0 -14
  405. package/backend/dist/esm/plugins/ts/preprocessing.js +0 -726
  406. package/backend/dist/esm/rate_limit.d.ts +0 -63
  407. package/backend/dist/esm/rate_limit.js +0 -417
  408. package/backend/dist/esm/request.deprc.d.ts +0 -48
  409. package/backend/dist/esm/request.deprc.js +0 -572
  410. package/backend/dist/esm/response.deprc.d.ts +0 -55
  411. package/backend/dist/esm/response.deprc.js +0 -275
  412. package/backend/dist/esm/server.d.ts +0 -342
  413. package/backend/dist/esm/splash_screen.d.ts +0 -35
  414. package/backend/dist/esm/status.d.ts +0 -61
  415. package/backend/dist/esm/status.js +0 -197
  416. package/backend/dist/esm/stream.d.ts +0 -79
  417. package/backend/dist/esm/users.d.ts +0 -111
  418. package/backend/dist/esm/users.js +0 -1935
  419. package/backend/dist/esm/vinc.dev.d.ts +0 -3
  420. package/backend/dist/esm/vinc.dev.js +0 -7
  421. package/frontend/dist/elements/base.d.ts +0 -9889
  422. package/frontend/dist/elements/module.d.ts +0 -30
  423. package/frontend/dist/modules/array.d.ts +0 -94
  424. package/frontend/dist/modules/array.js +0 -634
  425. package/frontend/dist/modules/auth.d.ts +0 -46
  426. package/frontend/dist/modules/auth.js +0 -139
  427. package/frontend/dist/modules/colors.d.ts +0 -1
  428. package/frontend/dist/modules/colors.js +0 -417
  429. package/frontend/dist/modules/compression.d.ts +0 -6
  430. package/frontend/dist/modules/compression.js +0 -999
  431. package/frontend/dist/modules/cookies.d.ts +0 -18
  432. package/frontend/dist/modules/cookies.js +0 -167
  433. package/frontend/dist/modules/date.d.ts +0 -142
  434. package/frontend/dist/modules/date.js +0 -493
  435. package/frontend/dist/modules/events.d.ts +0 -8
  436. package/frontend/dist/modules/events.js +0 -91
  437. package/frontend/dist/modules/google.d.ts +0 -11
  438. package/frontend/dist/modules/google.js +0 -54
  439. package/frontend/dist/modules/meta.d.ts +0 -10
  440. package/frontend/dist/modules/mutex.d.ts +0 -7
  441. package/frontend/dist/modules/mutex.js +0 -51
  442. package/frontend/dist/modules/number.d.ts +0 -16
  443. package/frontend/dist/modules/number.js +0 -23
  444. package/frontend/dist/modules/object.d.ts +0 -52
  445. package/frontend/dist/modules/object.js +0 -383
  446. package/frontend/dist/modules/scheme.d.ts +0 -227
  447. package/frontend/dist/modules/scheme.js +0 -531
  448. package/frontend/dist/modules/settings.d.ts +0 -3
  449. package/frontend/dist/modules/settings.js +0 -4
  450. package/frontend/dist/modules/statics.d.ts +0 -5
  451. package/frontend/dist/modules/string.d.ts +0 -124
  452. package/frontend/dist/modules/string.js +0 -745
  453. package/frontend/dist/modules/support.d.ts +0 -19
  454. package/frontend/dist/modules/support.js +0 -103
  455. package/frontend/dist/modules/themes.d.ts +0 -8
  456. package/frontend/dist/modules/themes.js +0 -18
  457. package/frontend/dist/modules/user.d.ts +0 -59
  458. package/frontend/dist/modules/user.js +0 -280
  459. package/frontend/dist/modules/utils.d.ts +0 -87
  460. package/frontend/dist/modules/utils.js +0 -923
  461. package/frontend/dist/types/gradient.d.ts +0 -12
  462. package/frontend/dist/ui/border_button.d.ts +0 -152
  463. package/frontend/dist/ui/button.d.ts +0 -21
  464. package/frontend/dist/ui/canvas.d.ts +0 -56
  465. package/frontend/dist/ui/checkbox.d.ts +0 -52
  466. package/frontend/dist/ui/frame_modes.d.ts +0 -25
  467. package/frontend/dist/ui/input.d.ts +0 -241
  468. package/frontend/dist/ui/loader_button.d.ts +0 -93
  469. package/frontend/dist/ui/loaders.d.ts +0 -57
  470. package/frontend/dist/ui/pseudo.d.ts +0 -16
  471. package/frontend/dist/ui/steps.d.ts +0 -59
  472. package/frontend/dist/ui/title.d.ts +0 -21
  473. package/frontend/dist/ui/title.js +0 -121
  474. package/frontend/examples/dashboard/dashboard.ts +0 -776
  475. /package/backend/dist/cjs/{cli.d.ts → backend/src/cli.d.ts} +0 -0
  476. /package/backend/dist/cjs/{file_watcher.d.ts → backend/src/database/document.d.ts} +0 -0
  477. /package/backend/dist/cjs/{file_watcher.js → backend/src/database/document.js} +0 -0
  478. /package/backend/dist/cjs/{plugins/pdf.d.ts → backend/src/database/filters/strict_filter_test.d.ts} +0 -0
  479. /package/backend/dist/{esm/file_watcher.d.ts → cjs/backend/src/database/filters/strict_filter_test_v0.d.ts} +0 -0
  480. /package/backend/dist/{esm/plugins/pdf.d.ts → cjs/backend/src/database/flatten_test.d.ts} +0 -0
  481. /package/backend/dist/cjs/{frontend.d.ts → backend/src/frontend.d.ts} +0 -0
  482. /package/backend/dist/cjs/{plugins → backend/src/plugins}/communication.d.ts +0 -0
  483. /package/backend/dist/cjs/{plugins → backend/src/plugins}/communication.js +0 -0
  484. /package/backend/dist/cjs/{plugins → backend/src/plugins}/mail/ui.js +0 -0
  485. /package/backend/dist/cjs/{plugins → backend/src/plugins}/pdf.js +0 -0
  486. /package/backend/dist/cjs/{plugins → backend/src/plugins}/thread_monitor.d.ts +0 -0
  487. /package/backend/dist/cjs/{plugins → backend/src/plugins}/thread_monitor.js +0 -0
  488. /package/backend/dist/cjs/{vinc.d.ts → backend/src/vinc.d.ts} +0 -0
  489. /package/backend/dist/cjs/{vinc.js → backend/src/vinc.js} +0 -0
  490. /package/backend/dist/esm/{cli.d.ts → backend/src/cli.d.ts} +0 -0
  491. /package/backend/dist/esm/{frontend.d.ts → backend/src/frontend.d.ts} +0 -0
  492. /package/backend/dist/esm/{plugins → backend/src/plugins}/communication.d.ts +0 -0
  493. /package/backend/dist/esm/{plugins → backend/src/plugins}/communication.js +0 -0
  494. /package/backend/dist/esm/{plugins → backend/src/plugins}/thread_monitor.d.ts +0 -0
  495. /package/backend/dist/esm/{plugins → backend/src/plugins}/thread_monitor.js +0 -0
  496. /package/backend/dist/esm/{vinc.d.ts → backend/src/vinc.d.ts} +0 -0
  497. /package/backend/dist/esm/{vinc.js → backend/src/vinc.js} +0 -0
  498. /package/frontend/dist/{elements → frontend/src/elements}/register_element.d.ts +0 -0
  499. /package/frontend/dist/{elements → frontend/src/elements}/register_element.js +0 -0
  500. /package/frontend/dist/{modules → frontend/src/modules}/color.d.ts +0 -0
  501. /package/frontend/dist/{ui → frontend/src/ui}/ui.d.ts +0 -0
  502. /package/frontend/dist/{ui → frontend/src/ui}/ui.js +0 -0
@@ -0,0 +1,2670 @@
1
+ /**
2
+ * @author Daan van den Bergh
3
+ * @copyright © 2022 - 2025 Daan van den Bergh.
4
+ */
5
+ import { fileURLToPath } from 'url';
6
+ import * as path from 'path';
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = path.dirname(__filename);
9
+ // ---------------------------------------------------------
10
+ // Libraries.
11
+ import * as http from "http";
12
+ import * as http2 from "http2";
13
+ import * as crypto from "crypto";
14
+ import libcluster from 'cluster';
15
+ import * as os from 'os';
16
+ import * as vlib from "@vandenberghinc/vlib";
17
+ const { debug } = vlib;
18
+ // ---------------------------------------------------------
19
+ // Imports.
20
+ import { Utils } from "./utils.js";
21
+ import { Meta } from './meta.js';
22
+ import * as MailUI from './plugins/mail/ui.js';
23
+ import { Mail } from "./plugins/mail/mail.js";
24
+ import { Status } from "./status.js";
25
+ import { Endpoint } from "./endpoint.js";
26
+ import { ImageEndpoint } from "./image_endpoint.js";
27
+ import { Stream } from "./stream.js";
28
+ import { Database } from "./database/database.js";
29
+ import { Users } from "./users.js";
30
+ import { Paddle } from "./payments/paddle.js";
31
+ import { RateLimits, RateLimitServer, RateLimitClient } from "./rate_limit.js";
32
+ import { Route } from "./route.js";
33
+ import { ExternalError } from '@vandenberghinc/volt';
34
+ /**
35
+ * The backend server class.
36
+ *
37
+ * When the HTTPS parameters `certificate` and `private_key` are defined, the server will run automatically on HTTP and HTTPS.
38
+ *
39
+ * @property users The initialized {@link Users} instance.
40
+ */
41
+ // @tdo implement 3D secure "requires_action" status for a refund and payment intent.
42
+ // https://stripe.com/docs/payments/3d-secure
43
+ // @ts-ignore
44
+ export class Server {
45
+ // ---------------------------------------------------------
46
+ // Static attributes.
47
+ // ---------------------------------------------------------
48
+ /** Content type per mime. */
49
+ static content_type_mimes = new Map([
50
+ [".html", "text/html"],
51
+ [".htm", "text/html"],
52
+ [".shtml", "text/html"],
53
+ [".css", "text/css"],
54
+ [".xml", "application/xml"],
55
+ [".gif", "image/gif"],
56
+ [".jpeg", "image/jpeg"],
57
+ [".jpg", "image/jpeg"],
58
+ [".js", "application/javascript"],
59
+ [".ts", "application/typescript"],
60
+ [".atom", "application/atom+xml"],
61
+ [".rss", "application/rss+xml"],
62
+ [".mml", "text/mathml"],
63
+ [".txt", "text/plain"],
64
+ [".jad", "text/vnd.sun.j2me.app-descriptor"],
65
+ [".wml", "text/vnd.wap.wml"],
66
+ [".htc", "text/x-component"],
67
+ [".png", "image/png"],
68
+ [".tif", "image/tiff"],
69
+ [".tiff", "image/tiff"],
70
+ [".wbmp", "image/vnd.wap.wbmp"],
71
+ [".ico", "image/x-icon"],
72
+ [".jng", "image/x-jng"],
73
+ [".bmp", "image/x-ms-bmp"],
74
+ [".svg", "image/svg+xml"],
75
+ [".svgz", "image/svg+xml"],
76
+ [".webp", "image/webp"],
77
+ [".woff", "font/woff"],
78
+ [".woff2", "font/woff2"],
79
+ [".jar", "application/java-archive"],
80
+ [".war", "application/java-archive"],
81
+ [".ear", "application/java-archive"],
82
+ [".json", "application/json"],
83
+ [".hqx", "application/mac-binhex40"],
84
+ [".doc", "application/msword"],
85
+ [".pdf", "application/pdf"],
86
+ [".ps", "application/postscript"],
87
+ [".eps", "application/postscript"],
88
+ [".ai", "application/postscript"],
89
+ [".rtf", "application/rtf"],
90
+ [".m3u8", "application/vnd.apple.mpegurl"],
91
+ [".xls", "application/vnd.ms-excel"],
92
+ [".eot", "application/vnd.ms-fontobject"],
93
+ [".ppt", "application/vnd.ms-powerpoint"],
94
+ [".wmlc", "application/vnd.wap.wmlc"],
95
+ [".kml", "application/vnd.google-earth.kml+xml"],
96
+ [".kmz", "application/vnd.google-earth.kmz"],
97
+ [".7z", "application/x-7z-compressed"],
98
+ [".cco", "application/x-cocoa"],
99
+ [".jardiff", "application/x-java-archive-diff"],
100
+ [".jnlp", "application/x-java-jnlp-file"],
101
+ [".run", "application/x-makeself"],
102
+ [".pl", "application/x-perl"],
103
+ [".pm", "application/x-perl"],
104
+ [".prc", "application/x-pilot"],
105
+ [".pdb", "application/x-pilot"],
106
+ [".rar", "application/x-rar-compressed"],
107
+ [".rpm", "application/x-redhat-package-manager"],
108
+ [".sea", "application/x-sea"],
109
+ [".swf", "application/x-shockwave-flash"],
110
+ [".sit", "application/x-stuffit"],
111
+ [".tcl", "application/x-tcl"],
112
+ [".tk", "application/x-tcl"],
113
+ [".der", "application/x-x509-ca-cert"],
114
+ [".pem", "application/x-x509-ca-cert"],
115
+ [".crt", "application/x-x509-ca-cert"],
116
+ [".xpi", "application/x-xpinstall"],
117
+ [".xhtml", "application/xhtml+xml"],
118
+ [".xspf", "application/xspf+xml"],
119
+ [".zip", "application/zip"],
120
+ [".bin", "application/octet-stream"],
121
+ [".exe", "application/octet-stream"],
122
+ [".dll", "application/octet-stream"],
123
+ [".deb", "application/octet-stream"],
124
+ [".dmg", "application/octet-stream"],
125
+ [".iso", "application/octet-stream"],
126
+ [".img", "application/octet-stream"],
127
+ [".msi", "application/octet-stream"],
128
+ [".msp", "application/octet-stream"],
129
+ [".msm", "application/octet-stream"],
130
+ [".docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"],
131
+ [".xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"],
132
+ [".pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation"],
133
+ [".mid", "audio/midi"],
134
+ [".midi", "audio/midi"],
135
+ [".kar", "audio/midi"],
136
+ [".mp3", "audio/mpeg"],
137
+ [".ogg", "audio/ogg"],
138
+ [".m4a", "audio/x-m4a"],
139
+ [".ra", "audio/x-realaudio"],
140
+ [".3gpp", "video/3gpp"],
141
+ [".3gp", "video/3gpp"],
142
+ // [".ts", "video/mp2t"],
143
+ [".mp4", "video/mp4"],
144
+ [".mpeg", "video/mpeg"],
145
+ [".mpg", "video/mpeg"],
146
+ [".mov", "video/quicktime"],
147
+ [".webm", "video/webm"],
148
+ [".flv", "video/x-flv"],
149
+ [".m4v", "video/x-m4v"],
150
+ [".mng", "video/x-mng"],
151
+ [".asx", "video/x-ms-asf"],
152
+ [".asf", "video/x-ms-asf"],
153
+ [".wmv", "video/x-ms-wmv"],
154
+ [".avi", "video/x-msvideo"],
155
+ ]);
156
+ /** All file path extensions that are already compressed. */
157
+ static compressed_extensions = new Set([
158
+ ".png",
159
+ ".jpg",
160
+ ".jpeg",
161
+ ".gif",
162
+ ".webp",
163
+ ".bmp",
164
+ ".tiff",
165
+ ".ico",
166
+ // ".svg",
167
+ ".svgz",
168
+ ".mng",
169
+ ".apng",
170
+ ".jfif",
171
+ ".jp2",
172
+ ".jpx",
173
+ ".j2k",
174
+ ".jpm",
175
+ ".jpf",
176
+ ".heif",
177
+ ".mp3",
178
+ ".ogg",
179
+ ".wav",
180
+ ".flac",
181
+ ".m4a",
182
+ ".aac",
183
+ ".wma",
184
+ ".ra",
185
+ ".mid",
186
+ ".mp4",
187
+ ".webm",
188
+ ".mkv",
189
+ ".mov",
190
+ ".avi",
191
+ ".wmv",
192
+ ".mpg",
193
+ ".mpeg",
194
+ ".flv",
195
+ ]);
196
+ // ---------------------------------------------------------
197
+ // Attributes.
198
+ // ---------------------------------------------------------
199
+ /** The binded ip address. */
200
+ ip;
201
+ /** The binded http port. */
202
+ port;
203
+ /** The binded https port. */
204
+ https_port;
205
+ /** The raw domain. */
206
+ domain;
207
+ /** The full domain name with http/https depending if tls is enabled. */
208
+ full_domain;
209
+ /** The persistent storage source directory. */
210
+ source;
211
+ /** Is the primary thread. */
212
+ is_primary;
213
+ /** Is in production mode. */
214
+ production;
215
+ /** The company information. */
216
+ company;
217
+ /** The default meta information. */
218
+ meta;
219
+ /** Is running in offline mode. */
220
+ offline;
221
+ /** The database instance. */
222
+ db;
223
+ /** The rate limit instance. */
224
+ rate_limit;
225
+ /** The added endpoints. */
226
+ endpoints = new Map();
227
+ /** The added error endpoints. */
228
+ err_endpoints = new Map();
229
+ /** A record of keys used for hashing. */
230
+ keys = {};
231
+ /** Alias for the `Status` module. */
232
+ status;
233
+ /** Alias for the `RateLimits` module. */
234
+ rate_limits;
235
+ /** The file logger. */
236
+ log;
237
+ /** The users instance. */
238
+ users;
239
+ /** The payments instance. */
240
+ payments;
241
+ /** Daemon instance to manage a live daemon. */
242
+ daemon;
243
+ /** The mail instance. */
244
+ mail;
245
+ // Public for internal use:
246
+ csp;
247
+ statics_aspect_ratios;
248
+ google_tag;
249
+ rate_limit_api_key;
250
+ performance;
251
+ /** The events map @internal */
252
+ events = new vlib.Events();
253
+ // Private.
254
+ favicon;
255
+ statics;
256
+ _user_keys_opts;
257
+ additional_sitemap_endpoints;
258
+ tls;
259
+ default_headers;
260
+ http;
261
+ https;
262
+ threading;
263
+ // Private ollections.
264
+ _keys_db;
265
+ _sys_keys_db;
266
+ _website_status_db;
267
+ /** Construct a new server instance. */
268
+ constructor({ ip = "127.0.0.1", port, // leave undefined for blank detection.
269
+ domain, is_primary = true, source, database, statics = [], favicon, company, meta = new Meta(), tls, mail, rate_limit = {
270
+ server: {
271
+ ip: undefined,
272
+ port: RateLimitServer.default_port,
273
+ https: undefined,
274
+ },
275
+ client: {
276
+ ip: undefined,
277
+ port: RateLimitServer.default_port,
278
+ url: undefined,
279
+ },
280
+ }, keys = [], payments, default_headers, google_tag = undefined, users, production = false, threading = {
281
+ enabled: false,
282
+ threads: undefined,
283
+ }, offline = false, additional_sitemap_endpoints = [], log_level = 0, daemon = false,
284
+ // admin = {
285
+ // password: null,
286
+ // ips: [],
287
+ // },
288
+ // ts = {
289
+ // compiler_opts: {},
290
+ // output: undefined,
291
+ // },
292
+ // browser_preview = undefined,
293
+ }) {
294
+ // // Verify args.
295
+ // vlib.schema.validate(arguments[0], {
296
+ // throw: true,
297
+ // error_prefix: "Server: ", unknown: false,
298
+ // schema: {
299
+ // ip: { type: "string", required: false },
300
+ // port: { type: "number", required: false },
301
+ // domain: "string",
302
+ // statics: { type: "array", default: [] },
303
+ // is_primary: { type: "boolean", default: true },
304
+ // source: "string",
305
+ // database: {
306
+ // type: ["string", "object"],
307
+ // required: true,
308
+ // scheme: { ...(Database.constructor_scheme as any), _server: undefined },
309
+ // },
310
+ // favicon: { type: "string", required: false },
311
+ // company: {
312
+ // type: "object",
313
+ // default: {},
314
+ // scheme: {
315
+ // name: "string",
316
+ // legal_name: "string",
317
+ // street: "string",
318
+ // house_number: "string",
319
+ // postal_code: "string",
320
+ // city: "string",
321
+ // province: "string",
322
+ // country: "string",
323
+ // country_code: "string",
324
+ // tax_id: { type: "string", default: null },
325
+ // icon: { type: "string", default: null },
326
+ // icon_path: { type: "string", default: null },
327
+ // stroke_icon: { type: "string", default: null },
328
+ // stroke_icon_path: { type: "string", default: null },
329
+ // }
330
+ // },
331
+ // meta: { type: "object", required: false },
332
+ // tls: {
333
+ // type: ["object"],
334
+ // required: false,
335
+ // scheme: {
336
+ // cert: "string",
337
+ // key: "string",
338
+ // ca: { type: "string", default: null },
339
+ // passphrase: { type: "string", default: null },
340
+ // }
341
+ // },
342
+ // rate_limit: {
343
+ // type: ["boolean", "object"],
344
+ // default: false,
345
+ // scheme: {
346
+ // server: {
347
+ // type: "object", default: {}, scheme: {
348
+ // ip: { type: "string", default: null },
349
+ // port: { type: "number", default: RateLimitServer.default_port },
350
+ // https: { type: "object", default: null },
351
+ // }
352
+ // },
353
+ // client: {
354
+ // type: "object", default: {}, scheme: {
355
+ // ip: { type: "string", default: null },
356
+ // port: { type: "number", default: RateLimitServer.default_port },
357
+ // url: { type: "string", default: null },
358
+ // }
359
+ // },
360
+ // },
361
+ // },
362
+ // keys: { type: "array", default: [] },
363
+ // smtp: { type: ["null", "object"], required: false },
364
+ // mail_style: {
365
+ // type: "object",
366
+ // required: false,
367
+ // scheme: {
368
+ // font: { type: "string", default: '"Helvetica", sans-serif' },
369
+ // title_fg: { type: "string", default: "#121B23" },
370
+ // subtitle_fg: { type: "string", default: "#121B23" },
371
+ // text_fg: { type: "string", default: "#1F2F3D" },
372
+ // button_fg: { type: "string", default: "#FFFFFF" },
373
+ // footer_fg: { type: "string", default: "#686B80" },
374
+ // bg: { type: "string", default: "#EEEEEE" },
375
+ // widget_bg: { type: "string", default: "#FFFFFF" },
376
+ // button_bg: { type: "string", default: "#421959" },
377
+ // widget_border: { type: "string", default: "#E6E6E6" },
378
+ // divider_bg: { type: "string", default: "#E6E6E6" },
379
+ // }
380
+ // },
381
+ // payments: { type: ["null", "object"], required: false },
382
+ // default_headers: { type: ["null", "object"], required: false },
383
+ // google_tag: { type: "string", required: false },
384
+ // token_expiration: { type: "number", required: false },
385
+ // enable_2fa: { type: "boolean", required: false },
386
+ // enable_account_activation: { type: "boolean", required: false },
387
+ // production: { type: "boolean", required: false },
388
+ // multiprocessing: { type: "boolean", required: false, default: true },
389
+ // processes: { type: "number", required: false, default: null },
390
+ // offline: { type: "boolean", default: false },
391
+ // additional_sitemap_endpoints: { type: "array", default: [] },
392
+ // log_level: { type: "number", default: 0 },
393
+ // daemon: { type: ["object", "boolean"], default: {} },
394
+ // // admin: {type: "object", default: {}, attributes: {
395
+ // // ips: {type: "array", default: []},
396
+ // // password: {
397
+ // // type: "string",
398
+ // // verify: (param: string, attrs) => (param.length < 10 ? `Parameter "Server.admin.password" must have a length of at least 10 characters.` : undefined),
399
+ // // },
400
+ // // }},
401
+ // // ts: {
402
+ // // type: "object",
403
+ // // required: false,
404
+ // // scheme: {
405
+ // // compiler_opts: {type: "object", default: {}},
406
+ // // output: "string",
407
+ // // },
408
+ // // },
409
+ // // browser_preview: {type: ["string", "undefined"], required: false, default: undefined},
410
+ // },
411
+ // });
412
+ // Assign attributes directly.
413
+ if (production || port == null) {
414
+ this.port = 80;
415
+ this.https_port = 443;
416
+ }
417
+ else {
418
+ this.port = port;
419
+ this.https_port = port + 1;
420
+ }
421
+ this.ip = ip ?? "127.0.0.1";
422
+ this.is_primary = is_primary && libcluster.isPrimary;
423
+ this.source = new vlib.Path(source);
424
+ this.favicon = favicon;
425
+ this.google_tag = google_tag;
426
+ this.production = production;
427
+ this.company = company;
428
+ this.offline = offline;
429
+ this._user_keys_opts = keys;
430
+ this.additional_sitemap_endpoints = additional_sitemap_endpoints;
431
+ this.tls = tls;
432
+ // this.admin = admin as AdminConfig;
433
+ // Set threading.
434
+ if (typeof threading === "boolean") {
435
+ this.threading = {
436
+ enabled: threading,
437
+ threads: os.cpus().length,
438
+ };
439
+ }
440
+ else {
441
+ this.threading = {
442
+ enabled: threading.enabled ?? true,
443
+ threads: threading.threads ?? os.cpus().length,
444
+ };
445
+ }
446
+ // Module aliases.
447
+ this.status = Status;
448
+ this.rate_limits = RateLimits;
449
+ /* @performance */ this.performance = new vlib.Performance("Server performance");
450
+ // Create logs directory.
451
+ const log_source = this.source.join("logs");
452
+ if (!log_source.exists()) {
453
+ log_source.mkdir_sync({ recursive: true });
454
+ }
455
+ this.log = new vlib.logging.FileLogger({
456
+ level: log_level,
457
+ log_path: log_source.join("logs").str(),
458
+ error_path: log_source.join("errors").str(),
459
+ });
460
+ // Check source.
461
+ if (!this.source.exists()) {
462
+ throw Error(`Source directory "${this.source.str()}" does not exist.`);
463
+ }
464
+ this.source = this.source.abs();
465
+ // Set domain.
466
+ this.domain = domain.replace("https://", "").replace("http://", "");
467
+ while (this.domain.length > 0 && this.domain.charAt(this.domain.length - 1) === "/") {
468
+ this.domain = this.domain.substr(0, this.domain.length - 1);
469
+ }
470
+ // Set full domain.
471
+ this.full_domain = `http${this.tls ? "s" : ""}://${this.domain}`;
472
+ while (this.full_domain.endsWith("/")) {
473
+ this.full_domain = this.full_domain.slice(0, -1);
474
+ }
475
+ // Set statics.
476
+ this.statics = statics;
477
+ this.statics_aspect_ratios = new Map();
478
+ // Add the default static to statics.
479
+ this.statics.push({
480
+ path: `${__dirname}/../../../frontend/src/static/`,
481
+ endpoint: "/volt_static",
482
+ });
483
+ // Set meta.
484
+ if (!(meta instanceof Meta)) {
485
+ meta = new Meta(meta);
486
+ }
487
+ if (favicon != null && meta.favicon == null) {
488
+ meta.favicon = this.full_domain + "/favicon.ico";
489
+ }
490
+ if (favicon != null && meta.image == null) {
491
+ meta.image = this.full_domain + "/favicon.ico";
492
+ }
493
+ else if (meta.image != null && !meta.image.startsWith("http")) {
494
+ meta.image = this.full_domain + meta.image;
495
+ }
496
+ this.meta = meta;
497
+ // Default headers.
498
+ const base_default_headers = {
499
+ // Cache correctness for CORS/preflight:
500
+ "Vary": "Origin, Access-Control-Request-Method, Access-Control-Request-Headers",
501
+ // Safer default than same-origin, still keeps useful referrers:
502
+ "Referrer-Policy": "strict-origin-when-cross-origin",
503
+ "Access-Control-Allow-Methods": "GET, POST, PUT, PATCH, DELETE, OPTIONS",
504
+ "Access-Control-Allow-Headers": "Content-Type, Authorization",
505
+ // Let browsers read our rate-limit hint:
506
+ "Access-Control-Expose-Headers": "X-RateLimit-Reset",
507
+ "X-Content-Type-Options": "nosniff",
508
+ "X-Frame-Options": "DENY",
509
+ // Helpful isolation defaults (safe for most apps):
510
+ "Cross-Origin-Opener-Policy": "same-origin",
511
+ "Cross-Origin-Resource-Policy": "same-site",
512
+ // If you need SharedArrayBuffer, add COEP below (can break some embeds):
513
+ // "Cross-Origin-Embedder-Policy": "require-corp",
514
+ "Strict-Transport-Security": "max-age=63072000; includeSubDomains; preload",
515
+ // Lock down powerful APIs by default.
516
+ // If you need one on a third-party origin, add it beside (self).
517
+ "Permissions-Policy": "accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), midi=(), payment=(), usb=(), hid=(), serial=(), xr-spatial-tracking=(), display-capture=(), screen-wake-lock=(), sync-xhr=(), publickey-credentials-get=(self), encrypted-media=(self), autoplay=(self 'https://www.youtube-nocookie.com') fullscreen=(self 'https://www.youtube-nocookie.com'), browsing-topics=()",
518
+ // Do NOT set Allow-Origin / Credentials statically; set them per-request below.
519
+ // "X-XSS-Protection": "1; mode=block", // deprecated
520
+ };
521
+ const default_csp = {
522
+ "default-src": "'self'",
523
+ "base-uri": "'none'",
524
+ "object-src": "'none'",
525
+ "form-action": "'self'",
526
+ "frame-ancestors": "'none'",
527
+ // Keep GA images; drop explicit http:// to avoid mixed content.
528
+ "img-src": "'self' data: blob: https://*.google-analytics.com",
529
+ "script-src": "'self' https://ajax.googleapis.com https://www.googletagmanager.com https://*.google-analytics.com",
530
+ // Needed for GA/GTAG beacons/fetch:
531
+ "connect-src": "'self' https://*.google-analytics.com",
532
+ "style-src": "'self'",
533
+ "font-src": "'self' data:",
534
+ // Auto-upgrade stray http URLs where possible:
535
+ "upgrade-insecure-requests": "",
536
+ };
537
+ if (default_headers == null) {
538
+ this.csp = default_csp;
539
+ this.default_headers = { ...base_default_headers };
540
+ }
541
+ else {
542
+ if (default_headers["Content-Security-Policy"] != null && typeof default_headers["Content-Security-Policy"] !== "object") {
543
+ throw Error("The Content-Security-Policy of the default headers must be an object with values for each csp key, e.g. \"{'script-src': '...'}\".");
544
+ }
545
+ this.csp = default_headers["Content-Security-Policy"] != null ? default_headers["Content-Security-Policy"] : default_csp;
546
+ Object.keys(base_default_headers).forEach(key => {
547
+ if (default_headers[key] === undefined) {
548
+ default_headers[key] = base_default_headers[key];
549
+ }
550
+ });
551
+ this.default_headers = default_headers;
552
+ }
553
+ if (!this.tls) {
554
+ // Always drop HSTS if TLS is not active.
555
+ delete this.default_headers["Strict-Transport-Security"];
556
+ }
557
+ // Initialize payments.
558
+ if (payments) {
559
+ if (payments.type === "paddle") {
560
+ this.payments = new Paddle({
561
+ _server: this,
562
+ ...payments,
563
+ });
564
+ }
565
+ else {
566
+ throw Error(`Invalid payment processor type "${payments.type}", valid types are ["paddle"].`);
567
+ }
568
+ }
569
+ // Initialize the service daemon.
570
+ // Must be initialized before initializing the database.
571
+ if (daemon !== false) {
572
+ const log_source = this.source.join("daemon");
573
+ if (!log_source.exists()) {
574
+ log_source.mkdir_sync({ recursive: true });
575
+ }
576
+ this.daemon = new vlib.Daemon({
577
+ name: this.domain.replaceAll(".", ""),
578
+ logs: daemon.logs || log_source.join("logs").str(),
579
+ errors: daemon.errors || log_source.join("errors").str(),
580
+ ...daemon,
581
+ // user: (daemon as Record<string, any>).user || os.userInfo().username,
582
+ // group: (daemon as Record<string, any>).group || null,
583
+ // command: "volt --service --start",
584
+ // cwd: this.source.str(),
585
+ // args: (daemon as Record<string, any>).args || [],
586
+ // env: (daemon as Record<string, any>).env || {},
587
+ // description: (daemon as Record<string, any>).description || `Service daemon for website ${this.domain}.`,
588
+ // auto_restart: true,
589
+ });
590
+ }
591
+ // Initialize the database class.
592
+ if (typeof database === "string") {
593
+ this.db = new Database({ uri: database, _server: this });
594
+ }
595
+ else {
596
+ this.db = new Database({ ...database, _server: this });
597
+ }
598
+ // Database collections.
599
+ this._keys_db = this.db.collection({
600
+ name: "Volt.Keys",
601
+ indexes: ["id"],
602
+ });
603
+ this._sys_keys_db = this.db.collection({
604
+ name: "Volt.SystemKeys",
605
+ indexes: ["id"],
606
+ });
607
+ this._website_status_db = this.db.collection({
608
+ name: "Volt.WebsiteStatus",
609
+ indexes: ["id"],
610
+ });
611
+ // Initialize the users class.
612
+ this.users = new Users({
613
+ ...users,
614
+ _server: this,
615
+ });
616
+ // The mail instance.
617
+ if (mail) {
618
+ this.mail = new Mail(mail);
619
+ }
620
+ // The rate limit server/client.
621
+ if (rate_limit) {
622
+ if (this.is_primary) {
623
+ this.rate_limit = new RateLimitServer({ ...(rate_limit.server ?? {}), _server: this });
624
+ }
625
+ else {
626
+ if (rate_limit.server?.https) {
627
+ rate_limit.client.https = true;
628
+ }
629
+ this.rate_limit = new RateLimitClient({ ...(rate_limit.client ?? {}), _server: this });
630
+ }
631
+ }
632
+ }
633
+ // ---------------------------------------------------------
634
+ // Utils.
635
+ /** Get a content type (MIME) from a file extension. */
636
+ get_content_type(extension) {
637
+ return Server.content_type_mimes.get(extension.toLowerCase()) ?? "application/octet-stream";
638
+ }
639
+ /** Set the logging verbosity level. */
640
+ set_log_level(level) {
641
+ this.log.level.set(level);
642
+ }
643
+ // ---------------------------------------------------------
644
+ // Crypto (private).
645
+ /** Generate a cryptographically secure random key as a hex string. */
646
+ generate_crypto_key(length = 32) {
647
+ return crypto.randomBytes(length).toString('hex');
648
+ }
649
+ /** Create an HMAC hash using the provided key and data. */
650
+ hmac(key, data, algo = "sha256") {
651
+ const hmac = crypto.createHmac(algo, key);
652
+ hmac.update(data);
653
+ return hmac.digest("hex");
654
+ }
655
+ // /** Create an HMAC hash using the server's master hash key. */
656
+ // hmac_with_master(data: string): string {
657
+ // if (!this._master_hash_key) {
658
+ // throw new Error("Hash key not initialized");
659
+ // }
660
+ // const hmac = crypto.createHmac("sha256", this._master_hash_key);
661
+ // hmac.update(data);
662
+ // return hmac.digest("hex");
663
+ // }
664
+ /** Create a hash (no key) of the given data using the specified algorithm. */
665
+ hash(data, algo = "sha256") {
666
+ if (typeof data !== "string") {
667
+ data = JSON.stringify(data);
668
+ }
669
+ return crypto.createHash(algo).update(data).digest('hex');
670
+ }
671
+ // ---------------------------------------------------------
672
+ // Headers (private).
673
+ // Initialize the default headers.
674
+ _init_default_headers() {
675
+ let csp = [];
676
+ Object.entries(this.csp).forEach(([key, value]) => {
677
+ csp.push(key);
678
+ if (typeof value === "string" && value.length > 0) {
679
+ csp.push(" ");
680
+ csp.push(value);
681
+ }
682
+ csp.push(";");
683
+ });
684
+ this.default_headers["Content-Security-Policy"] = csp.join("");
685
+ }
686
+ // Add header defaults.
687
+ _set_header_defaults(stream) {
688
+ stream.set_headers(this.default_headers);
689
+ const origin = stream.headers.origin;
690
+ if (origin) {
691
+ const same_http = `http://${this.domain}`;
692
+ const same_https = `https://${this.domain}`;
693
+ if (origin === same_http || origin === same_https) {
694
+ stream.set_header("Access-Control-Allow-Origin", origin);
695
+ stream.set_header("Access-Control-Allow-Credentials", "true");
696
+ }
697
+ else {
698
+ stream.set_header("Access-Control-Allow-Origin", "*");
699
+ // Do not send Access-Control-Allow-Credentials with a wildcard origin.
700
+ }
701
+ // Improve preflight reflection for caches and correctness.
702
+ const req_hdrs = stream.headers["access-control-request-headers"];
703
+ if (req_hdrs)
704
+ stream.set_header("Access-Control-Allow-Headers", String(req_hdrs));
705
+ const req_method = stream.headers["access-control-request-method"];
706
+ if (req_method)
707
+ stream.set_header("Access-Control-Allow-Methods", String(req_method));
708
+ }
709
+ }
710
+ _find_endpoint(endpoint, method) {
711
+ let route;
712
+ if (endpoint instanceof Route) {
713
+ route = endpoint;
714
+ endpoint = route.endpoint_str;
715
+ method = route.method;
716
+ }
717
+ method ??= "GET";
718
+ const result = this.endpoints.get(`${method}:${endpoint}`);
719
+ if (!result) {
720
+ if (!route)
721
+ route = new Route(method, endpoint);
722
+ for (const e of this.endpoints.values()) {
723
+ if (e.route.is_regex && e.route.match(route)) {
724
+ return e;
725
+ }
726
+ }
727
+ }
728
+ return result;
729
+ }
730
+ // Create default endpoints.
731
+ _create_default_endpoints() {
732
+ // Add favicon.
733
+ if (this.favicon != null) {
734
+ const favicon = new vlib.Path(this.favicon);
735
+ if (favicon.exists() === false) {
736
+ throw Error(`Specified favicon path "${favicon}" does not exist.`);
737
+ }
738
+ this.endpoint({
739
+ method: "GET",
740
+ endpoint: "/favicon.ico",
741
+ data: favicon.load_sync({ type: "buffer" }),
742
+ content_type: this.get_content_type(favicon.extension()),
743
+ _is_static: true,
744
+ server: this,
745
+ });
746
+ }
747
+ // Create status endpoint.
748
+ const status_dir = this.source.join(".status");
749
+ if (!status_dir.exists()) {
750
+ status_dir.mkdir_sync({ recursive: true });
751
+ }
752
+ const status_key_path = status_dir.join("key");
753
+ let status_key;
754
+ if (!status_key_path.exists()) {
755
+ status_key = this.generate_crypto_key(32);
756
+ status_key_path.save_sync(status_key);
757
+ }
758
+ else {
759
+ status_key = status_key_path.load_sync();
760
+ }
761
+ this.endpoint({
762
+ method: "GET",
763
+ endpoint: "/.status",
764
+ content_type: "application/json",
765
+ params: {
766
+ key: "string",
767
+ },
768
+ callback: async (stream, params) => {
769
+ // Check key.
770
+ if (params.key !== status_key) {
771
+ return stream.send({
772
+ status: 403,
773
+ headers: { "Content-Type": "text/plain" },
774
+ data: "Access Denied",
775
+ });
776
+ }
777
+ // Default status info.
778
+ const status = {};
779
+ status.ip = this.ip;
780
+ if (this.http) {
781
+ status.http_port = this.port;
782
+ }
783
+ if (this.https) {
784
+ status.https_port = this.https_port;
785
+ }
786
+ // Load data.
787
+ const data = await this._website_status_db.load({ id: "status" }, {
788
+ default: {
789
+ id: "status",
790
+ running_since: undefined,
791
+ running_threads: 0,
792
+ total_threads: 0,
793
+ }
794
+ });
795
+ Object.assign(status, data);
796
+ // Response.
797
+ return stream.send({
798
+ status: 200,
799
+ headers: { "Content-Type": "application/json" },
800
+ data: status,
801
+ });
802
+ },
803
+ });
804
+ // Default static endpoints.
805
+ // const defaults = [
806
+ // {
807
+ // method: "GET",
808
+ // endpoint: "/vhighlight/vhighlight.js",
809
+ // content_type: "application/javascript",
810
+ // path: new vlib.Path(vhighlight.web_exports.js),
811
+ // },
812
+ // ]
813
+ // defaults.forEach((item) => {
814
+ // this.endpoint(
815
+ // new Endpoint({
816
+ // method: item.method,
817
+ // endpoint: item.endpoint,
818
+ // content_type: item.content_type,
819
+ // compress: (item as any).compress,
820
+ // _static_path: item.path.str(),
821
+ // _templates: (item as any).templates,
822
+ // _server: this,
823
+ // })
824
+ // ._load_data_by_path(this)
825
+ // )
826
+ // })
827
+ }
828
+ // Create the sitemap endpoint.
829
+ async _create_sitemap() {
830
+ // Logs.
831
+ this.log(2, "Creating sitemap.");
832
+ let sitemap = "";
833
+ sitemap += "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
834
+ sitemap += "<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n";
835
+ for (const endpoint of this.endpoints.values()) {
836
+ if (endpoint.allow_sitemap) {
837
+ if (endpoint.route.is_regex)
838
+ continue; // skip regex routes
839
+ const ep = encodeURI(endpoint.route.endpoint_str.startsWith("/")
840
+ ? endpoint.route.endpoint_str
841
+ : `/${endpoint.route.endpoint_str}`);
842
+ sitemap += `<url>\n <loc>${this.full_domain}${ep}</loc>\n</url>\n`;
843
+ }
844
+ }
845
+ this.additional_sitemap_endpoints.forEach((endpoint) => {
846
+ while (endpoint.length > 0 && endpoint.charAt(0) === "/") {
847
+ endpoint = endpoint.substr(1);
848
+ }
849
+ sitemap += `<url>\n <loc>${this.full_domain}/${endpoint}</loc>\n</url>\n`;
850
+ });
851
+ sitemap += "</urlset>\n";
852
+ this.endpoint({
853
+ method: "GET",
854
+ endpoint: "/sitemap.xml",
855
+ data: sitemap,
856
+ content_type: "application/xml",
857
+ _compress: false,
858
+ });
859
+ }
860
+ // Create the robots.txt endpoint.
861
+ async _create_robots_txt() {
862
+ // Logs.
863
+ this.log(2, "Creating robots.txt.");
864
+ // Proceed.
865
+ let robots = "User-agent: *\n";
866
+ let disallowed = 0;
867
+ for (const endpoint of this.endpoints.values()) {
868
+ if (!endpoint.allow_robots) {
869
+ robots += `Disallow: ${endpoint.route.endpoint_str}\n`; // @todo not compatiable with regex endpoints
870
+ disallowed++;
871
+ }
872
+ }
873
+ if (disallowed === 0) {
874
+ robots += `Disallow: \n`;
875
+ }
876
+ robots += `\nSitemap: ${this.full_domain}/sitemap.xml`;
877
+ this.endpoint({
878
+ method: "GET",
879
+ endpoint: "/robots.txt",
880
+ content_type: "text/plain",
881
+ data: robots,
882
+ _compress: false,
883
+ });
884
+ }
885
+ // Create admin endpoint.
886
+ // @deprecated use MongoDB Atlas instead!
887
+ /* private _create_admin_endpoint(): void {
888
+
889
+ // Logs.
890
+ this.log(2, "Creating admin endpoint.");
891
+
892
+ // Add admin tokens.
893
+ this.admin.tokens = [];
894
+
895
+ // Verify token.
896
+ const verify_token = (token: string): boolean => {
897
+ const now = Date.now();
898
+ let new_tokens: Array<{token: string, expiration: number}> = [];
899
+ let verified = false;
900
+ this.admin.tokens!.forEach((i) => {
901
+ if (now < i.expiration) {
902
+ if (i.token === token) {
903
+ verified = true;
904
+ }
905
+ new_tokens.push(i);
906
+ }
907
+ })
908
+ this.admin.tokens = new_tokens;
909
+ return verified;
910
+ }
911
+
912
+ // Admin data.
913
+ this.endpoint({
914
+ method: "POST",
915
+ endpoint: "/admin/auth",
916
+ content_type: "application/json",
917
+ rate_limit: {
918
+ group: "volt.admin.auth",
919
+ limit: 5,
920
+ interval: 60,
921
+ },
922
+ params: {
923
+ password: "string",
924
+ },
925
+ ip_whitelist: this.admin.ips,
926
+ callback: async (stream: Stream, params: {password: string}) => {
927
+ // Check key.
928
+ if (params.password !== this.admin.password) {
929
+ return stream.send({
930
+ status: 403,
931
+ headers: {"Content-Type": "text/plain"},
932
+ data: "Access Denied",
933
+ })
934
+ }
935
+
936
+ // Generate token.
937
+ const token = {
938
+ token: String.random(32),
939
+ expiration: Date.now() + 3600 * 1000,
940
+ };
941
+ this.admin.tokens!.push(token)
942
+
943
+ // Response.
944
+ return stream.send({
945
+ status: 200,
946
+ headers: {"Content-Type": "application/json"},
947
+ data: token,
948
+ })
949
+ },
950
+ })
951
+
952
+ // Admin data.
953
+ this.endpoint({
954
+ method: "GET",
955
+ endpoint: "/admin/data",
956
+ content_type: "application/json",
957
+ rate_limit: "global",
958
+ params: {
959
+ token: "string",
960
+ },
961
+ ip_whitelist: this.admin.ips,
962
+ callback: async (stream: Stream, params: {token: string}) => {
963
+ // Verify token.
964
+ if (!verify_token(params.token)) {
965
+ return stream.send({
966
+ status: 403,
967
+ headers: {"Content-Type": "text/plain"},
968
+ data: "Access Denied",
969
+ })
970
+ }
971
+
972
+ // Data.
973
+ const data: Record<string, any> = {};
974
+
975
+ // Parse subscriptions.
976
+ const subscriptions = await this.payments._get_all_active_subscriptions();
977
+ data.subscriptions = subscriptions.length;
978
+
979
+ // Load data.
980
+ const status = await this._sys_db.load("status", {
981
+ default: {
982
+ running_since: null,
983
+ running_threads: 0,
984
+ total_threads: 0,
985
+ }
986
+ });
987
+ Object.assign(data, status);
988
+
989
+ // System data.
990
+ data.cpu_usage = vlib.System.cpu_usage();
991
+ data.memory_usage = vlib.System.memory_usage();
992
+ data.network_usage = await vlib.System.network_usage();
993
+
994
+ // Users.
995
+ data.users = (await this.users.list()).length;
996
+
997
+ // Response.
998
+ return stream.send({
999
+ status: 200,
1000
+ headers: {"Content-Type": "application/json"},
1001
+ data: data,
1002
+ })
1003
+ },
1004
+ })
1005
+
1006
+ // Admin view.
1007
+ this.endpoint({
1008
+ method: "GET",
1009
+ endpoint: "/admin",
1010
+ content_type: "application/json",
1011
+ rate_limit: "global",
1012
+ params: {
1013
+ password: "string",
1014
+ },
1015
+ ip_whitelist: this.admin.ips,
1016
+ sitemap: false,
1017
+ robots: false,
1018
+ view: {
1019
+ templates: {
1020
+ DOMAIN: this.domain,
1021
+ },
1022
+ callback: () => {
1023
+ // Style.
1024
+ const style = {
1025
+ bg: "#F2F3F6",
1026
+ sub_bg: "#FAFAFA",
1027
+ fg: "#000000",
1028
+ sub_fg: "#9099B4",
1029
+ border: "#D6D6D6",
1030
+ tint: "#64B878", //"#8EB8EB", //"#4E9CF7",
1031
+ }
1032
+
1033
+ // ... rest of the admin view implementation remains the same as it's client-side JavaScript ...
1034
+ },
1035
+ },
1036
+ })
1037
+ } */
1038
+ // Initialize statics.
1039
+ async _initialize_statics() {
1040
+ // Logs.
1041
+ this.log(2, "Initializing static directories.");
1042
+ // Static paths for the file watcher.
1043
+ const static_paths = [];
1044
+ // Add static file.
1045
+ const add_static_file = async (path, // vlib.Path type
1046
+ endpoint, cache = true) => {
1047
+ // Add to static paths.
1048
+ static_paths.push(path.str());
1049
+ // Get content type.
1050
+ const content_type = this.get_content_type(path.extension());
1051
+ // console.log("Add static file", endpoint, path.str())
1052
+ // Image endpoint with supported transformation.
1053
+ if (ImageEndpoint.supported_images.includes(path.extension())) {
1054
+ const e = new ImageEndpoint({
1055
+ endpoint,
1056
+ content_type,
1057
+ path,
1058
+ cache,
1059
+ rate_limit: "global",
1060
+ _is_static: true,
1061
+ });
1062
+ const aspect_ratio = await e.get_aspect_ratio();
1063
+ if (aspect_ratio != null) {
1064
+ this.statics_aspect_ratios.set(e.route.endpoint_str, aspect_ratio);
1065
+ }
1066
+ this.endpoint(e);
1067
+ }
1068
+ // Default static endpoint.
1069
+ else {
1070
+ // Create endpoint.
1071
+ this.endpoint(new Endpoint({
1072
+ method: "GET",
1073
+ endpoint,
1074
+ content_type,
1075
+ compress: !Server.compressed_extensions.has(path.extension().toLowerCase()),
1076
+ cache,
1077
+ rate_limit: "global",
1078
+ file_path: path,
1079
+ _is_static: true,
1080
+ }));
1081
+ }
1082
+ };
1083
+ // Initialize statics.
1084
+ const add_static = async (opts) => {
1085
+ if (opts == null) {
1086
+ return;
1087
+ }
1088
+ if (typeof opts === "object") {
1089
+ // Check object.
1090
+ vlib.schema.validate(opts, {
1091
+ unknown: false,
1092
+ throw: true,
1093
+ schema: {
1094
+ path: "string",
1095
+ endpoint: { type: "string", default: null },
1096
+ cache: { type: ["boolean", "number"], default: true },
1097
+ endpoints_cache: { type: "object", default: {} },
1098
+ exclude: { type: "array", default: [] },
1099
+ }
1100
+ });
1101
+ // Vars.
1102
+ const paths = [];
1103
+ const source = new vlib.Path(opts.path).abs();
1104
+ if (!source.exists()) {
1105
+ this.log(1, `Static path "${source.str()}" does not exist; skipping.`);
1106
+ return;
1107
+ }
1108
+ const source_len = source.str().length;
1109
+ const is_dir = source.is_dir();
1110
+ // Is excluded.
1111
+ const exclude = [/\.DS_Store$/, /\.cache(?:\/|$)/, /\.old(?:\/|$)/, /\.ignore$/, ...(opts.exclude || [])];
1112
+ const is_excluded = (p) => {
1113
+ const s = typeof p === "string" ? p : p.str();
1114
+ return exclude.some(pattern => pattern instanceof RegExp ? pattern.test(s) : s === String(pattern));
1115
+ };
1116
+ // Initialize endpoint.
1117
+ opts.endpoint = opts.endpoint || `/${source.full_name()}`;
1118
+ if (opts.endpoint.charAt(0) != "/") {
1119
+ opts.endpoint = "/" + opts.endpoint;
1120
+ }
1121
+ while (opts.endpoint.charAt(opts.endpoint.length - 1) == "/") {
1122
+ opts.endpoint = opts.endpoint.slice(0, -1);
1123
+ }
1124
+ // Not a directory.
1125
+ if (!is_dir) {
1126
+ return await add_static_file(source, opts.endpoint, opts.cache);
1127
+ }
1128
+ // First extract all paths recursively.
1129
+ // non recursive to ignore .old etc dirs.
1130
+ const read_dir = async (path) => {
1131
+ const dir_paths = await path.paths();
1132
+ const promises = [];
1133
+ for (let i = 0; i < dir_paths.length; i++) {
1134
+ if (!is_excluded(dir_paths[i])) {
1135
+ // @todo excluded does not work `.old` etc is still included and DS_Store.
1136
+ if (dir_paths[i].is_dir()) {
1137
+ promises.push(read_dir(dir_paths[i]));
1138
+ }
1139
+ else {
1140
+ paths.push(dir_paths[i]);
1141
+ }
1142
+ }
1143
+ }
1144
+ ;
1145
+ await Promise.all(promises);
1146
+ };
1147
+ if (is_dir) {
1148
+ await read_dir(source);
1149
+ }
1150
+ // Convert paths into a static object.
1151
+ for (const path of paths) {
1152
+ const endpoint = `${opts.endpoint}${path.str().substr(source_len)}`;
1153
+ await add_static_file(path, endpoint, opts.endpoints_cache === undefined ? opts.cache : opts.endpoints_cache[endpoint] ?? opts.cache);
1154
+ }
1155
+ }
1156
+ else if (typeof opts === "string") {
1157
+ await add_static({ path: opts });
1158
+ }
1159
+ };
1160
+ // Iterate.
1161
+ for (let i = 0; i < this.statics.length; i++) {
1162
+ if (this.statics[i] instanceof vlib.Path) {
1163
+ this.statics[i] = this.statics[i].str();
1164
+ }
1165
+ await add_static(this.statics[i]);
1166
+ }
1167
+ // Response.
1168
+ return static_paths;
1169
+ }
1170
+ /** Initialize the system and user defined keys. */
1171
+ async _initialize_keys() {
1172
+ // Await database initialization.
1173
+ const start = Date.now();
1174
+ await this._db_init_promise;
1175
+ /* @performance */ this.performance.end("_initialize_keys():await-db-init", start);
1176
+ // Load system keys.
1177
+ const sys_keys = await this._sys_keys_db.load({ id: "sys_keys" }, {
1178
+ default: {
1179
+ id: "sys_keys",
1180
+ rate_limit_api_key: undefined,
1181
+ }
1182
+ });
1183
+ let perform_sys_keys_save = false;
1184
+ // Check rate limit api key.
1185
+ if (sys_keys.rate_limit_api_key == null) {
1186
+ this.rate_limit_api_key = this.generate_crypto_key(32);
1187
+ sys_keys.rate_limit_api_key = this.rate_limit_api_key;
1188
+ perform_sys_keys_save = true;
1189
+ }
1190
+ else {
1191
+ this.rate_limit_api_key = sys_keys.rate_limit_api_key;
1192
+ }
1193
+ // Save.
1194
+ if (perform_sys_keys_save) {
1195
+ await this._sys_keys_db.set({ id: "sys_keys" }, sys_keys);
1196
+ }
1197
+ // Check user defined crypto keys.
1198
+ const user_keys = await this._keys_db.load({ id: "user_keys" }, {
1199
+ default: {
1200
+ id: "user_keys",
1201
+ keys: {},
1202
+ }
1203
+ });
1204
+ let perform_user_keys_save = false;
1205
+ for (const key of this._user_keys_opts) {
1206
+ const name = typeof key === "string" ? key : key.name;
1207
+ if (user_keys[name]) {
1208
+ this.keys[name] = user_keys[name];
1209
+ }
1210
+ else {
1211
+ perform_user_keys_save = true;
1212
+ if (typeof key === "string") {
1213
+ if (!key) {
1214
+ throw Error(`Crypto key "${key}" is an invalid key name.`);
1215
+ }
1216
+ const generated_key = this.generate_crypto_key(32);
1217
+ user_keys.keys[key] = generated_key;
1218
+ this.keys[key] = generated_key;
1219
+ }
1220
+ else {
1221
+ if (!key.name) {
1222
+ throw Error(`Crypto key "${key.name}" is an invalid key name.`);
1223
+ }
1224
+ if (key.length == null) {
1225
+ throw Error(`Crypto key "${key.name}" does not contain a "length" attribute.`);
1226
+ }
1227
+ if (typeof key.length !== "number") {
1228
+ throw Error(`Crypto key "${key.name}" has an invalid type for attribute "length", the valid type is "number".`);
1229
+ }
1230
+ const generated_key = this.generate_crypto_key(key.length);
1231
+ user_keys.keys[key.name] = generated_key;
1232
+ this.keys[key.name] = generated_key;
1233
+ }
1234
+ }
1235
+ }
1236
+ if (perform_user_keys_save) {
1237
+ await this._keys_db.set({ id: "user_keys" }, user_keys);
1238
+ }
1239
+ }
1240
+ /**
1241
+ * Checks if an endpoint route already exists.
1242
+ * @param method HTTP method
1243
+ * @param endpoint String path or RegExp
1244
+ */
1245
+ _check_duplicate_route(route) {
1246
+ const e = this._find_endpoint(route);
1247
+ if (e) {
1248
+ throw new Error(`Duplicate "${route.method}:${route.endpoint_str}" endpoint route, it is already defined by endpoint "${e.id}".`);
1249
+ }
1250
+ }
1251
+ // Serve a client.
1252
+ // @todo implement rate limiting.
1253
+ // @todo save internal server errors.
1254
+ async _serve(http2_stream, headers, req, res) {
1255
+ try {
1256
+ // Convert stream.
1257
+ const stream = new Stream(http2_stream, headers, req, res);
1258
+ // Vars.
1259
+ let endpoint;
1260
+ let method;
1261
+ let endpoint_url;
1262
+ // Log endpoint result.
1263
+ const log_endpoint_result = (message, status) => {
1264
+ let log_level = endpoint && endpoint.is_static ? 3 : 0;
1265
+ if (status == null) {
1266
+ status = stream.status_code;
1267
+ }
1268
+ this.log(log_level, `${method}:${endpoint_url}: ${message ? message : Status.get_description(status ?? "unknown")} [${status}] (${stream.ip}).`);
1269
+ };
1270
+ // Serve error endpoint.
1271
+ const serve_error_endpoint = async (status_code) => {
1272
+ // Get default response.
1273
+ const is_api_endpoint = endpoint && endpoint.callback != null;
1274
+ let default_response;
1275
+ switch (status_code) {
1276
+ case 400:
1277
+ default_response = {
1278
+ status: 400,
1279
+ headers: { "Content-Type": is_api_endpoint ? "application/json" : "text/plain" },
1280
+ data: is_api_endpoint ? { error: "Bad Request" } : "Bad Request",
1281
+ };
1282
+ break;
1283
+ case 403:
1284
+ default_response = {
1285
+ status: 403,
1286
+ headers: { "Content-Type": is_api_endpoint ? "application/json" : "text/plain" },
1287
+ data: is_api_endpoint ? { error: "Access Denied" } : "Access Denied",
1288
+ };
1289
+ break;
1290
+ case 404:
1291
+ default_response = {
1292
+ status: 404,
1293
+ headers: { "Content-Type": is_api_endpoint ? "application/json" : "text/plain" },
1294
+ data: is_api_endpoint ? { error: "Not Found" } : "Not Found",
1295
+ };
1296
+ break;
1297
+ case 500:
1298
+ default:
1299
+ default_response = {
1300
+ status: 500,
1301
+ headers: { "Content-Type": is_api_endpoint ? "application/json" : "text/plain" },
1302
+ data: is_api_endpoint ? { error: "Internal Server Error" } : "Internal Server Error",
1303
+ };
1304
+ break;
1305
+ }
1306
+ // Serve error endpoint or default response.
1307
+ if (!this.err_endpoints.has(status_code)) {
1308
+ stream.send(default_response);
1309
+ }
1310
+ else {
1311
+ const err_endpoint = this.err_endpoints.get(status_code);
1312
+ if (err_endpoint) {
1313
+ try {
1314
+ await err_endpoint._serve(stream, status_code);
1315
+ }
1316
+ catch (err) {
1317
+ this.log.error(`Error endpoint ${status_code}: `, err);
1318
+ stream.send(default_response);
1319
+ }
1320
+ }
1321
+ // @todo also serve something here.
1322
+ }
1323
+ };
1324
+ // Check ip against blacklist.
1325
+ // if (!this.offline && this.blacklist !== undefined && !this.blacklist.verify(stream.ip)) {
1326
+ // await serve_error_endpoint(403);
1327
+ // this.log_endpoint_result();
1328
+ // return;
1329
+ // }
1330
+ // Check if the request matches any of the defined endpoints.
1331
+ method = stream.method;
1332
+ endpoint_url = stream.endpoint;
1333
+ // endpoint = this._find_endpoint(endpoint_url, method);
1334
+ // Find endpoint manually so the optional path params can be extracted.
1335
+ this.log(3, "Searching for endpoint: ", `${method}:${endpoint_url}`);
1336
+ endpoint = this.endpoints.get(`${method}:${endpoint_url}`);
1337
+ if (!endpoint) {
1338
+ // Check regex endpoints.
1339
+ const route = new Route(method, endpoint_url);
1340
+ for (const e of this.endpoints.values()) {
1341
+ if (e.route.is_regex) {
1342
+ const matched_params = e.route.match(route);
1343
+ if (matched_params !== false) {
1344
+ this.log(3, "Matched regex route: ", e.route.id);
1345
+ endpoint = e;
1346
+ // insert path params into the stream when not already defined.
1347
+ Object.keys(matched_params).walk((k) => {
1348
+ if (stream.params[k] == null) {
1349
+ stream.params[k] = matched_params[k];
1350
+ }
1351
+ });
1352
+ break;
1353
+ }
1354
+ }
1355
+ }
1356
+ }
1357
+ else {
1358
+ this.log(3, "Matched route: ", endpoint.route.id);
1359
+ }
1360
+ // No endpoint found.
1361
+ if (!endpoint) {
1362
+ // Check OPTIONS request.
1363
+ if (method === "OPTIONS") {
1364
+ const original_method = stream.headers['access-control-request-method'];
1365
+ const original_endpoint = this._find_endpoint(endpoint_url, original_method);
1366
+ if (original_endpoint) {
1367
+ // Set headers.
1368
+ this._set_header_defaults(stream);
1369
+ original_endpoint._set_headers(stream);
1370
+ // Send.
1371
+ stream.send({ status: Status.no_content });
1372
+ log_endpoint_result();
1373
+ return;
1374
+ }
1375
+ }
1376
+ // Respond with 404.
1377
+ await serve_error_endpoint(404);
1378
+ log_endpoint_result();
1379
+ return;
1380
+ }
1381
+ // ------------------------------------------
1382
+ // Header & options
1383
+ // Set all headers so we can send options.
1384
+ // Set default headers.
1385
+ this._set_header_defaults(stream);
1386
+ // Serve options request.
1387
+ if (method === "OPTIONS") {
1388
+ try {
1389
+ await endpoint._serve_options(stream);
1390
+ }
1391
+ catch (err) {
1392
+ this.log.error(`${method}:${endpoint_url}: `, err);
1393
+ if (!stream.destroyed && !stream.finished) {
1394
+ await serve_error_endpoint(500);
1395
+ log_endpoint_result();
1396
+ }
1397
+ return;
1398
+ }
1399
+ log_endpoint_result();
1400
+ return;
1401
+ }
1402
+ // Check rate limit.
1403
+ if (!this.offline && this.production && this.rate_limit !== undefined && endpoint.rate_limit_groups.length > 0) {
1404
+ const result = await this.rate_limit.limit(stream.ip, endpoint.rate_limit_groups);
1405
+ if (result != null) {
1406
+ stream.send({
1407
+ status: 429,
1408
+ headers: {
1409
+ "Content-Type": "text/plain",
1410
+ "X-RateLimit-Reset": result,
1411
+ },
1412
+ data: `Rate limit exceeded, please try again in ${Math.floor((result - Date.now()) / 1000)} seconds.`,
1413
+ });
1414
+ log_endpoint_result();
1415
+ return;
1416
+ }
1417
+ }
1418
+ // Parse the request parameters.
1419
+ try {
1420
+ await stream.join();
1421
+ }
1422
+ catch (err) {
1423
+ this.log.error(`${method}:${endpoint_url}: `, err);
1424
+ await serve_error_endpoint(500);
1425
+ log_endpoint_result();
1426
+ return;
1427
+ }
1428
+ try {
1429
+ stream._parse_params();
1430
+ }
1431
+ catch (err) {
1432
+ this.log.error(`${method}:${endpoint_url}: `, err);
1433
+ await serve_error_endpoint(400);
1434
+ log_endpoint_result();
1435
+ return;
1436
+ }
1437
+ // Do not authenticate on static endpoints, unless "authenticated" flag is somehow enabled.
1438
+ if (!endpoint.is_static || endpoint.authenticated) {
1439
+ // Always perform authentication so the stream.uid will also be assigned even when the endpoint is not authenticated.
1440
+ const auth_result = await this.users._authenticate(stream);
1441
+ // Reset cookies when authentication has failed.
1442
+ if (auth_result != null && !endpoint.is_static) {
1443
+ this.users._reset_cookies(stream);
1444
+ }
1445
+ // When the endpoint has a view or is text/html then redirect to signin page.
1446
+ if (auth_result != null && !endpoint.is_static && (endpoint.view != null || endpoint.content_type === "text/html")) {
1447
+ stream.set_header("Location", `/signin?next=${encodeURIComponent(stream.endpoint)}`);
1448
+ }
1449
+ // When the endpoint is authenticated and the authentication has failed then send the error response.
1450
+ if (auth_result != null && endpoint.authenticated) {
1451
+ stream.send(auth_result);
1452
+ log_endpoint_result();
1453
+ return;
1454
+ }
1455
+ }
1456
+ // Serve endpoint.
1457
+ try {
1458
+ await endpoint._serve(stream);
1459
+ }
1460
+ catch (err) {
1461
+ this.log.error(`${method}:${endpoint_url}: `, err);
1462
+ if (!stream.destroyed && !stream.finished) {
1463
+ await serve_error_endpoint(500);
1464
+ log_endpoint_result();
1465
+ }
1466
+ return;
1467
+ }
1468
+ // Check if the response has been sent.
1469
+ if (!stream.finished) {
1470
+ this.log.error(`${method}:${endpoint_url}: `, "Unfinished response.");
1471
+ await serve_error_endpoint(500);
1472
+ log_endpoint_result();
1473
+ return;
1474
+ }
1475
+ // Log.
1476
+ log_endpoint_result();
1477
+ }
1478
+ catch (err) {
1479
+ this.log.error(err);
1480
+ }
1481
+ }
1482
+ // ---------------------------------------------------------
1483
+ // Server (private).
1484
+ /** The promise of database initialization and connecting. */
1485
+ _db_init_promise;
1486
+ // Initialize.
1487
+ // Initialize.
1488
+ async initialize() {
1489
+ // Logs.
1490
+ this.log(1, "Initializing server.");
1491
+ /* @performance */ const initialize_start = Date.now();
1492
+ /* @performance */ this.performance.start();
1493
+ // Initialize the database & connect first since this takes the longest.
1494
+ this._db_init_promise = (async () => {
1495
+ /* @performance */ let start = Date.now();
1496
+ await this.db.initialize();
1497
+ /* @performance */ this.performance.end("init-db", start);
1498
+ /* @performance */ start = Date.now();
1499
+ await this.db.connect();
1500
+ /* @performance */ this.performance.end("connect-db", start);
1501
+ })();
1502
+ // Create HTTPS server.
1503
+ if (this.tls) {
1504
+ this.https = http2.createSecureServer({
1505
+ key: new vlib.Path(this.tls.key).load_sync({ encoding: 'utf8' }),
1506
+ cert: new vlib.Path(this.tls.cert).load_sync({ encoding: 'utf8' }),
1507
+ ca: this.tls.ca == null ? undefined : new vlib.Path(this.tls.ca).load_sync({ encoding: 'utf8' }),
1508
+ passphrase: this.tls.passphrase,
1509
+ allowHTTP1: true,
1510
+ });
1511
+ this.https.on('stream', (stream, headers) => {
1512
+ this._serve(stream, headers, undefined, undefined);
1513
+ });
1514
+ // HTTP/1.1 (compatibility 'request' is also emitted for HTTP/2; filter it out)
1515
+ this.https.on("request", (req, res) => {
1516
+ if (req.httpVersionMajor === 1) {
1517
+ this._serve(undefined, undefined, req, res);
1518
+ }
1519
+ });
1520
+ }
1521
+ // Payments require HTTPS in production.
1522
+ else if (this.production && this.payments) {
1523
+ throw Error("Accepting payments in production mode requires HTTPS.");
1524
+ }
1525
+ /* @performance */ this.performance.end("create-https-server");
1526
+ // Create http server.
1527
+ if (this.tls) {
1528
+ // Redirect HTTP requests to HTTPS.
1529
+ this.http = http.createServer((request, response) => {
1530
+ const reqUrl = typeof request.url === "string" ? request.url : "/";
1531
+ // Build redirect using the canonical configured domain, not the untrusted Host header.
1532
+ const location = `https://${this.domain}${reqUrl}`;
1533
+ // 308 preserves method and body; safe for non-GET as well.
1534
+ response.writeHead(308, { Location: location });
1535
+ response.end();
1536
+ });
1537
+ }
1538
+ else {
1539
+ // Serve http.
1540
+ this.http = http.createServer((req, res) => {
1541
+ this._serve(undefined, undefined, req, res);
1542
+ });
1543
+ }
1544
+ /* @performance */ this.performance.end("create-http-server");
1545
+ // Initialize default headers.
1546
+ this._init_default_headers();
1547
+ /* @performance */ this.performance.end("init-default-headers");
1548
+ // Create default endpoints.
1549
+ this._create_default_endpoints();
1550
+ /* @performance */ this.performance.end("create-default-endpoints");
1551
+ // Create admin endpoints.
1552
+ // this._create_admin_endpoint();
1553
+ // /* @performance */ this.performance.end("create-admin-endpoints");
1554
+ // Create static endpoints.
1555
+ await this._initialize_statics();
1556
+ /* @performance */ this.performance.end("_initialize_statics()");
1557
+ // Add promises using the database.
1558
+ const promises = [];
1559
+ /* @performance */ this.performance.start();
1560
+ // Initialize keys,
1561
+ // uses the database so dont await in non production,
1562
+ // to speed up restarts.
1563
+ if (this.production) {
1564
+ promises.push(this._initialize_keys());
1565
+ }
1566
+ else {
1567
+ this._initialize_keys().then(() => {
1568
+ this.log(1, "Finished loading keys.");
1569
+ }).catch((err) => {
1570
+ this.log(0, `Error while loading keys.`);
1571
+ this.log.error(err);
1572
+ });
1573
+ }
1574
+ // /* @performance */ this.performance.end("load-keys");
1575
+ // Initialize users.
1576
+ promises.push(this.users._initialize());
1577
+ // /* @performance */ this.performance.end("users._initialize()");
1578
+ // Payments.
1579
+ if (this.payments !== undefined) {
1580
+ promises.push(this.payments._initialize());
1581
+ // /* @performance */ this.performance.end("payments._initialize()");
1582
+ }
1583
+ // Create sitemap when it does not exist.
1584
+ // Must be done at the end of initialization func since some funcs might still create endpoints.
1585
+ if (this._find_endpoint("/sitemap.xml") == null) {
1586
+ promises.push(this._create_sitemap());
1587
+ // /* @performance */ this.performance.end("_create_sitemap()");
1588
+ }
1589
+ // Create robots.txt when it does not exist.
1590
+ // Must be done at the end of initialization func since some funcs might still create endpoints.
1591
+ if (this._find_endpoint("/robots.txt") == null) {
1592
+ promises.push(this._create_robots_txt());
1593
+ // /* @performance */ this.performance.end("_create_robots_txt()");
1594
+ }
1595
+ // Get the icon and stroke icon file paths when defined.
1596
+ if (this.company.stroke_icon || this.company.icon) {
1597
+ for (const endpoint of this.endpoints.values()) {
1598
+ if (this.company.stroke_icon_path == null && endpoint.route.endpoint === this.company.stroke_icon) {
1599
+ this.company.stroke_icon_path = endpoint.file_path?.str() || undefined;
1600
+ }
1601
+ if (this.company.icon_path == null && endpoint.route.endpoint === this.company.icon) {
1602
+ this.company.icon_path = endpoint.file_path?.str() || undefined;
1603
+ }
1604
+ }
1605
+ if (this.company.stroke_icon != null && this.company.stroke_icon_path == null) {
1606
+ throw Error(`Unable to find the company's stroke icon endpoint "${this.company.stroke_icon}".`);
1607
+ }
1608
+ if (this.company.icon != null && this.company.icon_path == null) {
1609
+ throw Error(`Unable to find the company's icon endpoint "${this.company.icon}".`);
1610
+ }
1611
+ }
1612
+ // Await all promises.
1613
+ await Promise.all(promises);
1614
+ /* @performance */ this.performance.end("awaiting-promise-list");
1615
+ // Initialize all endpoints.
1616
+ this.performance.start();
1617
+ for (const endpoint of this.endpoints.values()) {
1618
+ endpoint._initialize(this);
1619
+ }
1620
+ for (const endpoint of this.err_endpoints.values()) {
1621
+ endpoint._initialize(this);
1622
+ }
1623
+ /* @performance */ this.performance.end("initialize-endpoints");
1624
+ // On initialize callbacks.
1625
+ for (const callback of this.events.get("initialize")) {
1626
+ const res = callback();
1627
+ if (res instanceof Promise) {
1628
+ await res;
1629
+ }
1630
+ }
1631
+ /* @performance */ this.performance.end("on-initialize-callbacks");
1632
+ /* @performance */ this.performance.end("initialize()", initialize_start);
1633
+ }
1634
+ // ---------------------------------------------------------
1635
+ // Server.
1636
+ /**
1637
+ * Start the server.
1638
+ * @example
1639
+ * ...
1640
+ * server.start();
1641
+ */
1642
+ async start() {
1643
+ // Always initialize, even when forking.
1644
+ await this.initialize();
1645
+ // On production bundle all view endpoints.
1646
+ if (this.production) {
1647
+ for (const endpoint of this.endpoints.values()) {
1648
+ if (endpoint.view) {
1649
+ await endpoint.view.ensure_bundle();
1650
+ }
1651
+ }
1652
+ }
1653
+ // Start the rate limiting client/server, also when forking.
1654
+ if (this.rate_limit) {
1655
+ /* @performance */ this.performance.start();
1656
+ await this.rate_limit.start();
1657
+ /* @performance */ this.performance.end("init-rate-limit");
1658
+ }
1659
+ // Production & Master.
1660
+ let forked = false;
1661
+ if (this.production && this.threading.enabled && libcluster.isPrimary && this.threading.threads > 1) {
1662
+ this.log(0, `Starting ${this.threading.threads} threads.`);
1663
+ // Vars.
1664
+ let active_threads = 0;
1665
+ const thread_ids = {};
1666
+ const restart_limiters = {};
1667
+ // Start thread.
1668
+ const start_thread = (thread_id, restart = false) => {
1669
+ // Fork.
1670
+ const worker = libcluster.fork();
1671
+ // Log.
1672
+ this.log(restart ? 0 : 1, `Starting thread ${worker.process.pid}.`);
1673
+ // Cache thread id.
1674
+ thread_ids[worker.process.pid] = thread_id;
1675
+ // Increment active threads.
1676
+ ++active_threads;
1677
+ };
1678
+ // Fork workers.
1679
+ for (let i = 0; i < this.threading.threads; i++) {
1680
+ // Generate thread id.
1681
+ let thread_id;
1682
+ while ((thread_id = vlib.String.random(8)) && Object.values(thread_ids).includes(thread_id)) { }
1683
+ // Create limiter.
1684
+ restart_limiters[thread_id] = new vlib.TimeLimiter({ limit: 3, duration: 60 * 1000 });
1685
+ // Start thread.
1686
+ start_thread(thread_id);
1687
+ }
1688
+ // Save status.
1689
+ await this._website_status_db.set({ id: "status" }, {
1690
+ running_since: Date.now(),
1691
+ total_threads: active_threads,
1692
+ running_threads: active_threads,
1693
+ });
1694
+ // On exit.
1695
+ libcluster.addListener('exit', async (worker, code, signal) => {
1696
+ // Fetch thread id.
1697
+ const thread_id = thread_ids[worker.process.pid];
1698
+ delete thread_ids[worker.process.pid];
1699
+ // Logs.
1700
+ this.log.error(`Thread ${worker.process.pid} crashed.`);
1701
+ // Restart with limit.
1702
+ const limiter = restart_limiters[thread_id];
1703
+ if (limiter != null && limiter.limit()) {
1704
+ --active_threads;
1705
+ start_thread(thread_id, true);
1706
+ }
1707
+ // Reached limit, shutdown thread.
1708
+ else {
1709
+ this.log.error(`Thread ${worker.process.pid} is being shut down due to its periodic restart limit.`);
1710
+ --active_threads;
1711
+ await this._website_status_db.save({ id: "status" }, { $inc: { running_threads: -1 } });
1712
+ if (active_threads === 0) {
1713
+ this.log.error(`All threads died, stopping server.`);
1714
+ process.exit(0);
1715
+ }
1716
+ }
1717
+ });
1718
+ }
1719
+ else {
1720
+ forked = this.production && this.threading.enabled;
1721
+ // Load worker class modules.
1722
+ // if (libcluster.isWorker) {
1723
+ // const worker = new WorkerClass();
1724
+ // worker.start();
1725
+ // }
1726
+ // Callbacks.
1727
+ let is_running = false;
1728
+ const on_running = () => {
1729
+ if (!is_running) {
1730
+ is_running = true;
1731
+ if (this.https !== undefined) {
1732
+ this.log(0, `Running on http://${this.ip}:${this.port} and https://${this.ip}:${this.https_port}.`);
1733
+ }
1734
+ else {
1735
+ this.log(0, `Running on http://${this.ip}:${this.port}.`);
1736
+ }
1737
+ }
1738
+ };
1739
+ const on_error = (error) => {
1740
+ if (error.syscall !== 'listen') {
1741
+ throw error;
1742
+ }
1743
+ switch (error.code) {
1744
+ case 'EACCES':
1745
+ console.error(`Error: Address ${this.ip}:${this.port} requires elevated privileges.`);
1746
+ process.exit(1);
1747
+ break;
1748
+ case 'EADDRINUSE':
1749
+ console.error(`Error: Address ${this.ip}:${this.port} is already in use.`);
1750
+ process.exit(1);
1751
+ break;
1752
+ default:
1753
+ throw error;
1754
+ }
1755
+ };
1756
+ // Listen.
1757
+ this.http.listen(this.port, this.ip === "*" ? undefined : this.ip, on_running);
1758
+ this.http.on("error", on_error);
1759
+ if (this.https !== undefined) {
1760
+ this.https.listen(this.https_port, this.ip === "*" ? undefined : this.ip, on_running);
1761
+ this.https.on("error", on_error);
1762
+ }
1763
+ // Set signals.
1764
+ let graceful_shutdown_shutting_down = false;
1765
+ const graceful_shutdown = async () => {
1766
+ if (graceful_shutdown_shutting_down)
1767
+ return;
1768
+ graceful_shutdown_shutting_down = true;
1769
+ try {
1770
+ await this.stop();
1771
+ }
1772
+ catch (e) {
1773
+ this.log.error("Shutdown error:", e);
1774
+ }
1775
+ finally {
1776
+ process.exit(0);
1777
+ }
1778
+ };
1779
+ process.on('SIGTERM', graceful_shutdown);
1780
+ process.on('SIGINT', graceful_shutdown);
1781
+ // Send running message.
1782
+ if (process.env.VOLT_FILE_WATCHER === "1") {
1783
+ new vlib.Path(process.env.VOLT_STARTED_FILE).save_sync("1");
1784
+ }
1785
+ // Start browser.
1786
+ // if (this.browser_preview) {
1787
+ // await this.browser_preview.start();
1788
+ // await this.browser_preview.navigate(this.full_domain);
1789
+ // }
1790
+ /* @performance */ this.performance.end("listen");
1791
+ }
1792
+ // On start callbacks.
1793
+ this.performance.start();
1794
+ for (const callback of this.events.get("start")) {
1795
+ const res = callback({ forked });
1796
+ if (res instanceof Promise) {
1797
+ await res;
1798
+ }
1799
+ }
1800
+ /* @performance */ this.performance.end("on-start-callbacks");
1801
+ // Start browser preview on primary node.
1802
+ // if (this.browser_preview && !forked) {
1803
+ // await this.browser_preview.start();
1804
+ // await this.browser_preview.navigate(this.full_domain);
1805
+ // }
1806
+ /* @performance */
1807
+ console.log(this.performance.dump());
1808
+ // console.log(this.performance.dump(v => v >= 50));
1809
+ // debug(2, () => this.performance.dump(v => v >= 50));
1810
+ }
1811
+ // Stop the server.
1812
+ /**
1813
+ * Stop the server.
1814
+ * @example
1815
+ * ...
1816
+ * server.stop();
1817
+ */
1818
+ async stop() {
1819
+ this.log(0, "Stopping the server...");
1820
+ // On stop callbacks.
1821
+ for (const callback of this.events.get("stop")) {
1822
+ const res = callback();
1823
+ if (res instanceof Promise) {
1824
+ await res;
1825
+ }
1826
+ }
1827
+ // Stop rate limit.
1828
+ if (this.rate_limit) {
1829
+ await this.rate_limit.stop();
1830
+ }
1831
+ // Stop sockets.
1832
+ if (this.https)
1833
+ this.https.close();
1834
+ if (this.http)
1835
+ this.http.close();
1836
+ await this.db.close();
1837
+ // Stop the logger.
1838
+ this.log.stop();
1839
+ // setTimeout(() => {
1840
+ // thread_monitor.dump_active_resources({
1841
+ // // min_age: 5000,
1842
+ // // exclude_types: ['TIMERWRAP'],
1843
+ // include_internal: false
1844
+ // });
1845
+ // }, 6000);
1846
+ }
1847
+ // ---------------------------------------------------------
1848
+ // Events.
1849
+ /** Add an event callback. */
1850
+ on(name, callback) {
1851
+ this.events.add(name, callback);
1852
+ return this;
1853
+ }
1854
+ /** Remove an event callback. */
1855
+ off(name, callback) {
1856
+ this.events.remove(name, callback);
1857
+ return this;
1858
+ }
1859
+ // ---------------------------------------------------------
1860
+ // Endpoints.
1861
+ /**
1862
+ * Add a single endpoint.
1863
+ * Only supports a single endpoint due to parameter inference.
1864
+ * @note An error is thrown when the endpoint route already exists.
1865
+ * @template Response User inputted response type that will be returned as response, optionaly typing used for consistency.
1866
+ * @template S system template for inferring the endpoint callback parameters.
1867
+ * @param endpoint The endpoint or endpoint options to add.
1868
+ * @returns A registered endpoint object that can for instance be used to infer the endpoint parameters.
1869
+ */
1870
+ endpoint(endpoint) {
1871
+ const e = endpoint instanceof Endpoint ? endpoint : new Endpoint(endpoint);
1872
+ this._check_duplicate_route(e.route);
1873
+ this.endpoints.set(e.route.id, e);
1874
+ return {
1875
+ Params: undefined,
1876
+ method: e.route.method,
1877
+ Method: e.route.method,
1878
+ endpoint: e.route.endpoint,
1879
+ Endpoint: e.route.endpoint,
1880
+ route: e.route,
1881
+ };
1882
+ }
1883
+ // Add an error endpoint.
1884
+ /**
1885
+ * Add an endpoint per error status code.
1886
+ * @param status_code
1887
+ * The status code of the error.
1888
+ *
1889
+ * The supported status codes are:
1890
+ * * `404`
1891
+ * * `400` (Will not be used when the endpoint uses an API callback).
1892
+ * * `403`
1893
+ * * `404`
1894
+ * * `500`
1895
+ * @param endpoint The error endpoint or error endpoint options
1896
+ */
1897
+ error_endpoint(status_code, endpoint) {
1898
+ const e = endpoint instanceof Endpoint ? endpoint : new Endpoint(endpoint);
1899
+ this._check_duplicate_route(e.route);
1900
+ this.err_endpoints.set(status_code, e);
1901
+ return this;
1902
+ }
1903
+ // ---------------------------------------------------------
1904
+ // Content Security Policy.
1905
+ // Add a csp.
1906
+ /**
1907
+ * Add an url to the Content-Security-Policy. This function does not overwrite the existing key's value.
1908
+ * @warning This function no longer has any effect when `Server.start()` has been called.
1909
+ * @param key The Content-Security-Policy key, e.g. `script-src`.
1910
+ * @param value The value to add to the Content-Security-Policy key.
1911
+ * @example
1912
+ * ...
1913
+ * server.add_csp("script-src", "somewebsite.com");
1914
+ * server.add_csp("upgrade-insecure-requests");
1915
+ */
1916
+ add_csp(key, value = null) {
1917
+ if (this.csp[key] === undefined) {
1918
+ this.csp[key] = "";
1919
+ }
1920
+ if (Array.isArray(value)) {
1921
+ value.forEach((v) => {
1922
+ if (typeof v === "string" && v.length > 0) {
1923
+ this.csp[key] += " " + v.trim();
1924
+ }
1925
+ });
1926
+ }
1927
+ else if (typeof value === "string" && value.length > 0) {
1928
+ this.csp[key] += " " + value.trim();
1929
+ }
1930
+ }
1931
+ // Remove a csp.
1932
+ /**
1933
+ * Remove an url from the Content-Security-Policy. This function does not overwrite the existing key's value.
1934
+ * @warning This function no longer has any effect when `Server.start()` has been called.
1935
+ * @param key The Content-Security-Policy key, e.g. `script-src`.
1936
+ * @param value The value to remove from the Content-Security-Policy key.
1937
+ * @example
1938
+ * ...
1939
+ * server.remove_csp("script-src", "somewebsite.com");
1940
+ * server.remove_csp("upgrade-insecure-requests");
1941
+ */
1942
+ remove_csp(key, value = null) {
1943
+ if (this.csp[key] === undefined) {
1944
+ return;
1945
+ }
1946
+ if (typeof value === "string" && value.length > 0) {
1947
+ this.csp[key] = this.csp[key].replaceAll(value, "");
1948
+ }
1949
+ else {
1950
+ delete this.csp[key];
1951
+ }
1952
+ }
1953
+ // Delete a csp key.
1954
+ /**
1955
+ * Delete an key from the Content-Security-Policy.
1956
+ * @warning This function no longer has any effect when `Server.start()` has been called.
1957
+ * @param key The Content-Security-Policy key, e.g. `script-src`.
1958
+ * @example
1959
+ * ...
1960
+ * server.del_csp("script-src");
1961
+ * server.del_csp("upgrade-insecure-requests");
1962
+ */
1963
+ del_csp(key) {
1964
+ delete this.csp[key];
1965
+ }
1966
+ // ---------------------------------------------------------
1967
+ // Status.
1968
+ // Fetch status.
1969
+ /**
1970
+ * This function is meant to be used when the server is in production mode, it will make an API request to your server through the defined `Server.domain` parameter.
1971
+ * @note This function can be called without initializing the server.
1972
+ * @param type The wanted output type. Either an `object` or a `string` type for CLI purposes.
1973
+ */
1974
+ async fetch_status(type = "object") {
1975
+ // Load key.
1976
+ const key_path = this.source.join(".status/key");
1977
+ if (!key_path.exists()) {
1978
+ throw new Error("No status key has been generated yet. Start your server first.");
1979
+ }
1980
+ const key = key_path.load_sync();
1981
+ // Make request.
1982
+ const { body: status } = await vlib.request({
1983
+ host: this.domain,
1984
+ endpoint: "/.status",
1985
+ method: "GET",
1986
+ params: { key },
1987
+ query: true,
1988
+ json: true,
1989
+ });
1990
+ // String type.
1991
+ if (type === "string") {
1992
+ if (status.running_since != null) {
1993
+ status.running_since = new vlib.Date(status.running_since).format("%d-%m-%y %H:%M:%S");
1994
+ }
1995
+ let str = `${this.domain}:\n`;
1996
+ Object.keys(status).forEach((key) => {
1997
+ str += ` * ${key}: ${status[key]}\n`;
1998
+ });
1999
+ str = str.substr(0, str.length - 1);
2000
+ return str;
2001
+ }
2002
+ // Response.
2003
+ return status;
2004
+ }
2005
+ // ---------------------------------------------------------
2006
+ // TLS.
2007
+ /** Generate a key and csr for tls. */
2008
+ async generate_ssl_key({ output_path, ec = true, }) {
2009
+ // Args.
2010
+ if (output_path == null) {
2011
+ throw Error("Define parameter \"path\".");
2012
+ }
2013
+ // Paths.
2014
+ const key = new vlib.Path(output_path);
2015
+ if (key.exists()) {
2016
+ throw Error(`Key path "${key.str()}" already exists, remove the file manually to continue.`);
2017
+ }
2018
+ // Generate the private key using the EC parameters file
2019
+ const proc = new vlib.Proc();
2020
+ await proc.start({
2021
+ command: "openssl",
2022
+ args: ec
2023
+ ? ["ecparam", "-genkey", "-name", "secp384r1", "-out", key.str()]
2024
+ : ["genpkey", "-algorithm", "RSA", "-pkeyopt", "rsa_keygen_bits:2048", "-out", key.str()],
2025
+ opts: { stdio: "inherit" },
2026
+ });
2027
+ if (proc.exit_status != 0) {
2028
+ throw Error(`Encountered an error while generating the private key [${proc.exit_status}]: ${proc.err}`);
2029
+ }
2030
+ }
2031
+ /** Generate a csr for tls. */
2032
+ async generate_csr({ output_path, key_path, name, domain, organization_unit, country_code, province, city, }) {
2033
+ // Args.
2034
+ if (key_path == null) {
2035
+ throw Error("Define parameter \"key_path\".");
2036
+ }
2037
+ if (organization_unit == null) {
2038
+ throw Error("Define parameter \"organization_unit\".");
2039
+ }
2040
+ // Paths.
2041
+ const key = new vlib.Path(key_path);
2042
+ if (!key.exists()) {
2043
+ throw Error(`Key path "${key.str()}" does not exist.`);
2044
+ }
2045
+ const csr = new vlib.Path(output_path);
2046
+ if (csr.exists()) {
2047
+ throw Error(`CSR path "${csr.str()}" already exists, remove the file manually to continue.`);
2048
+ }
2049
+ // Generate the CSR using the generated private key
2050
+ const proc = new vlib.Proc();
2051
+ await proc.start({
2052
+ command: "openssl",
2053
+ args: [
2054
+ "req", "-new", "-key", key.str(), "-out", csr.str(),
2055
+ "-subj",
2056
+ `/C=${country_code}/ST=${province}/L=${city}/O=${name}/OU=${organization_unit}/CN=${domain}`
2057
+ ],
2058
+ opts: { stdio: "inherit" },
2059
+ });
2060
+ if (proc.exit_status != 0) {
2061
+ throw Error(`Encountered an error while generating the CSR [${proc.exit_status}]: ${proc.err}`);
2062
+ }
2063
+ this.log(0, `Generated the tls key with CSR for domain "${this.domain}".`);
2064
+ }
2065
+ // ---------------------------------------------------------
2066
+ // DEPRECATED
2067
+ // these will all be removed and replaced when using stripe instead of paddle.
2068
+ /** Called for each product in a successful one-time payment. Override to implement your logic. */
2069
+ async on_payment({ product, payment }) { }
2070
+ /** Called for each product in a successful subscription. Override to implement your logic. */
2071
+ async on_subscription({ product, payment }) { }
2072
+ // On failed one-time or recurring payment.
2073
+ // async on_failed_payment({ payment }: { payment: any }): Promise<void> {}
2074
+ /** Called when a cancellation succeeds. Override to implement your logic. */
2075
+ async on_cancellation({ payment, line_items }) { }
2076
+ // On failed cancellation.
2077
+ // async on_failed_cancellation({ payment, line_items }: { payment: any; line_items: any[] }): Promise<void> {}
2078
+ /** Called when a refund succeeds. The line items array are the items that were refunded. */
2079
+ async on_refund({ payment, line_items }) { }
2080
+ /** Called when a refund fails. The line items array are the items where the refund failed. */
2081
+ async on_failed_refund({ payment, line_items }) { }
2082
+ /** Called when a chargeback occurs. The line items array are the items that were charged back. */
2083
+ async on_chargeback({ payment, line_items }) { }
2084
+ /** Called when a chargeback fails. The line items array are the items where the chargeback failed. */
2085
+ async on_failed_chargeback({ payment, line_items }) { }
2086
+ // Mail template.
2087
+ /** Build the base email layout used by the various transactional email builders. */
2088
+ _mail_template({ max_width = 400, children = [], }) {
2089
+ this.assert_mail();
2090
+ const style = this.mail.style;
2091
+ const { Title, Text, Image, Table, TableRow, TableData, VStack } = MailUI;
2092
+ // Create header.
2093
+ let header;
2094
+ if (this.company.stroke_icon != null) {
2095
+ header = [
2096
+ Image(`${this.full_domain}${this.company.stroke_icon ?? ""}`).height(16),
2097
+ ];
2098
+ }
2099
+ else if (this.company.icon != null) {
2100
+ header = [
2101
+ Image(`${this.full_domain}${this.company.icon ?? ""}`).frame(20, 40),
2102
+ ];
2103
+ }
2104
+ if (header) {
2105
+ header = Table(TableRow(...header)
2106
+ .wrap(true)
2107
+ .center()
2108
+ .center_vertical()).margin_bottom(15);
2109
+ }
2110
+ // Create mail.
2111
+ return MailUI.Mail(Table(TableData(Table(
2112
+ // Header.
2113
+ header,
2114
+ // Widget.
2115
+ Table(...children)
2116
+ .background_color(style.widget_bg ?? "")
2117
+ .border(`1px solid ${style.widget_border ?? ""}`)
2118
+ .border_radius("10px")
2119
+ .padding(40, 25, 25, 25)
2120
+ .margin(0),
2121
+ // Copyright.
2122
+ Table(TableRow(Text(`Copyright © ${new Date().getFullYear()} ${this.company.name}, ${this.company.legal_name} All Rights Included.\n` +
2123
+ `${this.company.street} ${this.company.house_number}, ${this.company.postal_code}, ${this.company.city}, ${this.company.province}, ${this.company.country}.\n` +
2124
+ (this.company.tax_id == null ? "" : `VAT ID ${this.company.tax_id}`))
2125
+ .white_space("pre")
2126
+ .display("inline-block")
2127
+ .font_size(11)
2128
+ .color(style.footer_fg)
2129
+ .margin(0)).center().center_vertical()).margin(0, 0, 10, 0)).max_width(max_width)).center()).padding(25, 20, 25, 20)).font_family(style.font).background(style.bg);
2130
+ }
2131
+ // Render payment line items.
2132
+ /** Helper that renders a list of payment line items for use in transactional emails. */
2133
+ _render_mail_payment_line_items({ payment, line_items, show_total_due = false }) {
2134
+ if (!this.payments)
2135
+ throw new Error("Payments not initialized");
2136
+ this.assert_mail();
2137
+ // Shortcuts.
2138
+ const style = this.mail.style;
2139
+ const { Title, Text, Image, Table, TableRow, TableData, VStack } = MailUI;
2140
+ // Render payment line item for a mail.
2141
+ const _render_mail_payment_line_item = ({ name, desc, unit_cost, quantity, total_cost, font_weight = "normal", divider = true, color = style.text_fg, }) => {
2142
+ return [
2143
+ Table(TableRow(TableData(Text(name)
2144
+ .color(color)
2145
+ .font_size(14)
2146
+ .text_wrap("wrap")
2147
+ .overflow_wrap("break-word")
2148
+ .word_wrap("break-word")
2149
+ .font_weight(font_weight)).width("25%").margin_right(10), TableData(Text(desc)
2150
+ .color(color)
2151
+ .font_size(14)
2152
+ .text_wrap("wrap")
2153
+ .overflow_wrap("break-word")
2154
+ .word_wrap("break-word")
2155
+ .font_weight(font_weight)).width("35%").margin_right(10), TableData(Text(unit_cost)
2156
+ .color(color)
2157
+ .font_size(14)
2158
+ .text_wrap("wrap")
2159
+ .overflow_wrap("break-word")
2160
+ .word_wrap("break-word")
2161
+ .font_weight(font_weight)).fixed_width("13.32%").margin_right(10), TableData(Text(quantity)
2162
+ .color(color)
2163
+ .font_size(14)
2164
+ .text_wrap("wrap")
2165
+ .overflow_wrap("break-word")
2166
+ .word_wrap("break-word")
2167
+ .font_weight(font_weight)).fixed_width("13.32%").margin_right(10), TableData(Text(total_cost)
2168
+ .color(color)
2169
+ .font_size(14)
2170
+ .text_wrap("wrap")
2171
+ .overflow_wrap("break-word")
2172
+ .word_wrap("break-word")
2173
+ .font_weight(font_weight)).fixed_width("13.32%")).width("100%").styles({ "vertical-align": "baseline" })).width("100%"),
2174
+ !divider
2175
+ ? null
2176
+ : TableRow(TableData(VStack()
2177
+ .background_color(style.text_fg)
2178
+ .frame("100%", 1)
2179
+ .margin(5, 0, 10, 0)).frame("100%", 1)).width("100%"),
2180
+ ];
2181
+ };
2182
+ // Render a divider.
2183
+ const render_divider = () => {
2184
+ return TableRow(TableData(VStack()
2185
+ .background_color(style.divider_bg)
2186
+ .frame("100%", 1)
2187
+ .margin(5, 0, 10, 0)).frame("100%", 1)).width("100%");
2188
+ };
2189
+ // Vars.
2190
+ let currency;
2191
+ let subtotal = 0;
2192
+ let subtotal_tax = 0;
2193
+ let total = 0;
2194
+ payment.line_items.walk((item) => {
2195
+ if (!this.payments)
2196
+ throw new Error("Payments not initialized");
2197
+ if (typeof item.product === "string") {
2198
+ item.product = this.payments.get_product_sync(item.product);
2199
+ }
2200
+ if (currency == null) {
2201
+ const c = Utils.get_currency_symbol(item.product.currency);
2202
+ if (c == null) {
2203
+ this.log.error(`Failed to create a payment mail: `, new Error(`Unable to determine the currency of payment "${payment.id}".`));
2204
+ }
2205
+ currency = c ?? "?";
2206
+ }
2207
+ subtotal += item.subtotal;
2208
+ subtotal_tax += item.tax;
2209
+ total += item.total;
2210
+ });
2211
+ let total_due = payment.status === "open" ? total : 0;
2212
+ return [
2213
+ render_divider(),
2214
+ line_items.map((item, index) => {
2215
+ return Table(TableRow(TableData(Image(item.product.icon)
2216
+ .frame(35, 35)
2217
+ .margin_right(15)).width("auto"), TableData(Table(Text(item.product.name)
2218
+ .color(style.title_fg)
2219
+ .font_size(14)
2220
+ .font_weight("bold")
2221
+ .margin(0)
2222
+ .ellipsis_overflow(true), Text(item.product.description)
2223
+ .color(style.text_fg)
2224
+ .font_size(14)
2225
+ .margin(0)
2226
+ .ellipsis_overflow(true))).width("100%"), TableData(Text(`${currency} ${item.subtotal.toFixed(2)}`)
2227
+ .color(style.title_fg)
2228
+ .font_size(14)
2229
+ .font_weight("bold")
2230
+ .margin(0)
2231
+ .white_space("nowrap")).width("100%")).wrap(true).leading_vertical().width("100%")).width("100%");
2232
+ }),
2233
+ render_divider(),
2234
+ Table([
2235
+ ["Subtotal:", `${currency} ${subtotal.toFixed(2)}`],
2236
+ ["Tax:", `${currency} ${subtotal_tax.toFixed(2)}`],
2237
+ ["Total:", `${currency} ${total.toFixed(2)}`],
2238
+ ].map((item) => {
2239
+ return TableRow(TableData().width("100%"), TableData(Text(item[0])
2240
+ .color(style.title_fg)
2241
+ .font_size(14)
2242
+ .ellipsis_overflow(true)
2243
+ .font_weight("bold")).min_width(75), TableData(Text(item[1])
2244
+ .color(style.title_fg)
2245
+ .font_size(14)
2246
+ .white_space("nowrap")
2247
+ .font_weight("bold"))
2248
+ // .min_width(50)
2249
+ ).wrap(true);
2250
+ // .text_align("right")
2251
+ })),
2252
+ ];
2253
+ }
2254
+ /** Assert mail is configured. */
2255
+ assert_mail() {
2256
+ if (!this.mail) {
2257
+ throw new ExternalError({ message: "Mail is not configured." });
2258
+ }
2259
+ }
2260
+ // On 2fa mail.
2261
+ /** Build the 2FA verification email content. */
2262
+ on_2fa_mail({ code, username, email, date, ip, device }) {
2263
+ this.assert_mail();
2264
+ const style = this.mail.style;
2265
+ const { Title, Text, Image, Table, TableRow, TableData, VStack } = MailUI;
2266
+ return this._mail_template({
2267
+ max_width: 400,
2268
+ children: [
2269
+ // Title.
2270
+ TableRow(Title("Verification Required")
2271
+ .color(style.title_fg)
2272
+ .width("fit-content")
2273
+ .font_size(26)).center(),
2274
+ // Text.
2275
+ TableRow(Text("Please confirm your request with this 2FA code.")
2276
+ .center()
2277
+ .margin(10, 0, 20, 0)
2278
+ .color(style.text_fg)
2279
+ .font_size(18)),
2280
+ // Auth info.
2281
+ [
2282
+ ["Username", username],
2283
+ ["Email", email],
2284
+ ["Date", date],
2285
+ ["Ip Address", ip],
2286
+ ["Device", device],
2287
+ ].map((item) => {
2288
+ return [
2289
+ TableRow(VStack()
2290
+ .margin_right(7.5)
2291
+ // .background("linear-gradient(135deg, #4830C4, #6E399E, #421959)")
2292
+ .background_color(style.text_fg)
2293
+ .border_radius("50%")
2294
+ .frame(5, 5), Text(`<span style='font-weight: 600'>${item[0]}:</span> ${item[1]}`)
2295
+ .color(style.text_fg)
2296
+ .font_size(16)
2297
+ .text_wrap("wrap")
2298
+ .overflow_wrap("break-word")
2299
+ .word_wrap("break-word")).wrap(true).center_vertical(),
2300
+ TableRow().fixed_frame(5, 5),
2301
+ ];
2302
+ }),
2303
+ // 2FA code.
2304
+ TableRow(Text(code)
2305
+ .background(style.button_bg)
2306
+ .border_radius("10px")
2307
+ .padding(10, 15)
2308
+ .center()
2309
+ .color(style.button_fg)
2310
+ .width("100%")
2311
+ .margin(20, 0, 0, 0)),
2312
+ // Text.
2313
+ TableRow(Text("This 2FA code will be valid for 5 minutes.")
2314
+ .color(style.text_fg)
2315
+ .font_style("italic")
2316
+ .font_size(12)
2317
+ .margin_top(20)
2318
+ .center()),
2319
+ ],
2320
+ });
2321
+ }
2322
+ // On successfull payment mail.
2323
+ /** Build the successful payment email content. */
2324
+ on_payment_mail({ payment }) {
2325
+ this.assert_mail();
2326
+ // Shortcuts.
2327
+ const style = this.mail.style;
2328
+ const { Title, Text, Image, Table, TableRow, TableData, VStack } = MailUI;
2329
+ // Create mail.
2330
+ return this._mail_template({
2331
+ max_width: 600,
2332
+ children: [
2333
+ // Title.
2334
+ TableRow(Title("Successful Payment")
2335
+ .color(style.title_fg)
2336
+ .width("fit-content")
2337
+ .font_size(26)).center(),
2338
+ // Text.
2339
+ TableRow(Text("We're delighted to inform you that your payment has been successfully processed. Thank you for your purchase.")
2340
+ .margin(10, 0, 20, 0)
2341
+ .color(style.text_fg)
2342
+ .font_size(16)
2343
+ .center()),
2344
+ // Image.
2345
+ TableRow(Image(`${this.full_domain}/volt_static/payments/party.png`)
2346
+ .frame(60, 60)
2347
+ .margin(0, 0, 30, 0)).center(),
2348
+ // Title.
2349
+ TableRow(Title("Order Summary")
2350
+ .color(style.subtitle_fg)
2351
+ .font_size(18)
2352
+ .margin(0)),
2353
+ TableRow(Text("A summary of your order can be found below or in the attached invoice PDF.")
2354
+ .margin(5, 0, 20, 0)
2355
+ .color(style.text_fg)
2356
+ .font_size(16)),
2357
+ // Line items.
2358
+ this._render_mail_payment_line_items({ payment, line_items: payment.line_items, show_total_due: true }),
2359
+ // Bottom spacing.
2360
+ VStack()
2361
+ .margin_bottom(15)
2362
+ ],
2363
+ });
2364
+ }
2365
+ // On failed payment mail.
2366
+ /** Build the failed payment email content. */
2367
+ on_failed_payment_mail({ payment }) {
2368
+ this.assert_mail();
2369
+ // Shortcuts.
2370
+ const style = this.mail.style;
2371
+ const { Title, Text, Image, ImageMask, Table, TableRow, TableData, VStack } = MailUI;
2372
+ // Create mail.
2373
+ return this._mail_template({
2374
+ max_width: 800,
2375
+ children: [
2376
+ // Title.
2377
+ TableRow(Title("Payment Failed")
2378
+ .color(style.title_fg)
2379
+ .width("fit-content")
2380
+ .font_size(26)).center(),
2381
+ // Text.
2382
+ TableRow(Text("We regret to inform you that your payment could not be processed successfully. We understand the inconvenience this may cause. Please try again, or contact customer support if the problem persists.")
2383
+ .margin(10, 0, 20, 0)
2384
+ .color(style.text_fg)
2385
+ .font_size(16)
2386
+ .center()),
2387
+ // Image.
2388
+ TableRow(ImageMask(`${this.full_domain}/volt_static/payments/error.png`)
2389
+ .frame(40, 40)
2390
+ .mask_color("#E8454E")
2391
+ .margin(0, 0, 30, 0)).center(),
2392
+ // Title.
2393
+ TableRow(Title("Order Summary")
2394
+ .color(style.subtitle_fg)
2395
+ .font_size(18)
2396
+ .margin(0)),
2397
+ TableRow(Text("A summary of your failed order can be found below.")
2398
+ .margin(5, 0, 20, 0)
2399
+ .color(style.text_fg)
2400
+ .font_size(16)),
2401
+ // Line items.
2402
+ this._render_mail_payment_line_items({ payment, line_items: payment.line_items }),
2403
+ // Bottom spacing.
2404
+ VStack()
2405
+ .margin_bottom(15)
2406
+ ],
2407
+ });
2408
+ }
2409
+ // On cancellation mail.
2410
+ /** Build the successful cancellation email content. */
2411
+ on_cancellation_mail({ payment, line_items }) {
2412
+ this.assert_mail();
2413
+ // Shortcuts.
2414
+ const style = this.mail.style;
2415
+ const { Title, Text, Image, Table, TableRow, TableData, VStack } = MailUI;
2416
+ // Create mail.
2417
+ return this._mail_template({
2418
+ max_width: 800,
2419
+ children: [
2420
+ // Title.
2421
+ TableRow(Title("Successful Cancellation")
2422
+ .color(style.title_fg)
2423
+ .width("fit-content")
2424
+ .font_size(26)).center(),
2425
+ // Text.
2426
+ TableRow(Text("Your recent cancellation request has been successfully processed.")
2427
+ .margin(10, 0, 20, 0)
2428
+ .color(style.text_fg)
2429
+ .font_size(16)
2430
+ .center()),
2431
+ // Image.
2432
+ TableRow(Image(`${this.full_domain}/volt_static/payments/check.png`)
2433
+ .frame(40, 40)
2434
+ .margin(0, 0, 30, 0)).center(),
2435
+ // Title.
2436
+ TableRow(Title("Cancelled Summary")
2437
+ .color(style.subtitle_fg)
2438
+ .font_size(18)
2439
+ .margin(0)),
2440
+ TableRow(Text("A summary of your cancelled products.")
2441
+ .margin(5, 0, 20, 0)
2442
+ .color(style.text_fg)
2443
+ .font_size(16)),
2444
+ // Line items.
2445
+ this._render_mail_payment_line_items({ payment, line_items }),
2446
+ // Bottom spacing.
2447
+ VStack()
2448
+ .margin_bottom(15)
2449
+ ],
2450
+ });
2451
+ }
2452
+ // On refund mail.
2453
+ /** Build the failed cancellation email content. */
2454
+ on_failed_cancellation_mail({ payment }) {
2455
+ this.assert_mail();
2456
+ // Shortcuts.
2457
+ const style = this.mail.style;
2458
+ const { Title, Text, Image, ImageMask, Table, TableRow, TableData, VStack } = MailUI;
2459
+ // Create mail.
2460
+ return this._mail_template({
2461
+ max_width: 800,
2462
+ children: [
2463
+ // Title.
2464
+ TableRow(Title("Cancellation Failed")
2465
+ .color(style.title_fg)
2466
+ .width("fit-content")
2467
+ .font_size(26)).center(),
2468
+ // Text.
2469
+ TableRow(Text("We regret to inform you that your recent cancellation request has encountered an issue and could not be processed successfully. We understand the inconvenience this may cause. If you believe you are eligible for a cancellation, please try again or contact customer support.")
2470
+ .margin(10, 0, 20, 0)
2471
+ .color(style.text_fg)
2472
+ .font_size(16)
2473
+ .center()).center(),
2474
+ // Image.
2475
+ TableRow(ImageMask(`${this.full_domain}/volt_static/payments/error.png`)
2476
+ .frame(40, 40)
2477
+ .mask_color("#E8454E")
2478
+ .margin(0, 0, 30, 0)).center(),
2479
+ // Title.
2480
+ TableRow(Title("Cancellation Summary")
2481
+ .color(style.subtitle_fg)
2482
+ .font_size(18)
2483
+ .margin(0)),
2484
+ TableRow(Text("A summary of your cancellation request.")
2485
+ .margin(5, 0, 20, 0)
2486
+ .color(style.text_fg)
2487
+ .font_size(16)),
2488
+ // Line items.
2489
+ this._render_mail_payment_line_items({ payment, line_items: payment.line_items }),
2490
+ // Bottom spacing.
2491
+ VStack()
2492
+ .margin_bottom(15)
2493
+ ],
2494
+ });
2495
+ }
2496
+ // On refund mail.
2497
+ /** Build the successful refund email content. */
2498
+ on_refund_mail({ payment, line_items }) {
2499
+ this.assert_mail();
2500
+ // Shortcuts.
2501
+ const style = this.mail.style;
2502
+ const { Title, Text, Image, Table, TableRow, TableData, VStack } = MailUI;
2503
+ // Create mail.
2504
+ return this._mail_template({
2505
+ max_width: 800,
2506
+ children: [
2507
+ // Title.
2508
+ TableRow(Title("Chargeback Successful")
2509
+ .color(style.title_fg)
2510
+ .width("fit-content")
2511
+ .font_size(26)).center(),
2512
+ // Text.
2513
+ TableRow(Text("We're delighted to inform you that your recent refund request has been successfully processed. The charged amount will soon be credited back to your account.")
2514
+ .margin(10, 0, 20, 0)
2515
+ .color(style.text_fg)
2516
+ .font_size(16)
2517
+ .center()),
2518
+ // Image.
2519
+ TableRow(Image(`${this.full_domain}/volt_static/payments/party.png`)
2520
+ .frame(60, 60)
2521
+ .margin(0, 0, 30, 0)).center(),
2522
+ // Title.
2523
+ TableRow(Title("Refund Summary")
2524
+ .color(style.subtitle_fg)
2525
+ .font_size(18)
2526
+ .margin(0)),
2527
+ TableRow(Text("A summary of your refunded products.")
2528
+ .margin(5, 0, 20, 0)
2529
+ .color(style.text_fg)
2530
+ .font_size(16)),
2531
+ // Line items.
2532
+ this._render_mail_payment_line_items({ payment, line_items }),
2533
+ // Bottom spacing.
2534
+ VStack()
2535
+ .margin_bottom(15)
2536
+ ],
2537
+ });
2538
+ }
2539
+ // On refund mail.
2540
+ /** Build the failed refund email content. */
2541
+ on_failed_refund_mail({ payment, line_items }) {
2542
+ this.assert_mail();
2543
+ // Shortcuts.
2544
+ const style = this.mail.style;
2545
+ const { Title, Text, Image, ImageMask, Table, TableRow, TableData, VStack } = MailUI;
2546
+ // Create mail.
2547
+ return this._mail_template({
2548
+ max_width: 800,
2549
+ children: [
2550
+ // Title.
2551
+ TableRow(Title("Refund Failed")
2552
+ .color(style.title_fg)
2553
+ .width("fit-content")
2554
+ .font_size(26)).center(),
2555
+ // Text.
2556
+ TableRow(Text("We regret to inform you that your recent refund request has encountered an issue and could not be processed successfully. We understand the inconvenience this may cause. If you believe you are eligible for a refund, please try again or contact customer support.")
2557
+ .margin(10, 0, 20, 0)
2558
+ .color(style.text_fg)
2559
+ .font_size(16)
2560
+ .center()).center(),
2561
+ // Image.
2562
+ TableRow(ImageMask(`${this.full_domain}/volt_static/payments/error.png`)
2563
+ .frame(40, 40)
2564
+ .mask_color("#E8454E")
2565
+ .margin(0, 0, 30, 0)).center(),
2566
+ // Title.
2567
+ TableRow(Title("Refund Summary")
2568
+ .color(style.subtitle_fg)
2569
+ .font_size(18)
2570
+ .margin(0)),
2571
+ TableRow(Text("A summary of your refund request.")
2572
+ .margin(5, 0, 20, 0)
2573
+ .color(style.text_fg)
2574
+ .font_size(16)),
2575
+ // Line items.
2576
+ this._render_mail_payment_line_items({ payment, line_items }),
2577
+ // Bottom spacing.
2578
+ VStack()
2579
+ .margin_bottom(15)
2580
+ ],
2581
+ });
2582
+ }
2583
+ // On refund mail.
2584
+ /** Build the successful chargeback email content. */
2585
+ on_chargeback_mail({ payment, line_items }) {
2586
+ this.assert_mail();
2587
+ // Shortcuts.
2588
+ const style = this.mail.style;
2589
+ const { Title, Text, Image, Table, TableRow, TableData, VStack } = MailUI;
2590
+ // Create mail.
2591
+ return this._mail_template({
2592
+ max_width: 800,
2593
+ children: [
2594
+ // Title.
2595
+ TableRow(Title("Successful Refund")
2596
+ .color(style.title_fg)
2597
+ .width("fit-content")
2598
+ .font_size(26)).center(),
2599
+ // Text.
2600
+ TableRow(Text("We're delighted to inform you that your recent chargeback request has been successfully processed. The charged amount will soon be credited back to your account.")
2601
+ .margin(10, 0, 20, 0)
2602
+ .color(style.text_fg)
2603
+ .font_size(16)
2604
+ .center()),
2605
+ // Image.
2606
+ TableRow(Image(`${this.full_domain}/volt_static/payments/party.png`)
2607
+ .frame(60, 60)
2608
+ .margin(0, 0, 30, 0)).center(),
2609
+ // Title.
2610
+ TableRow(Title("Chargeback Summary")
2611
+ .color(style.subtitle_fg)
2612
+ .font_size(18)
2613
+ .margin(0)),
2614
+ TableRow(Text("A summary of the items charged back.")
2615
+ .margin(5, 0, 20, 0)
2616
+ .color(style.text_fg)
2617
+ .font_size(16)),
2618
+ // Line items.
2619
+ this._render_mail_payment_line_items({ payment, line_items }),
2620
+ // Bottom spacing.
2621
+ VStack()
2622
+ .margin_bottom(15)
2623
+ ],
2624
+ });
2625
+ }
2626
+ // On refund mail.
2627
+ /** Build the failed chargeback email content. */
2628
+ on_failed_chargeback_mail({ payment, line_items }) {
2629
+ this.assert_mail();
2630
+ // Shortcuts.
2631
+ const style = this.mail.style;
2632
+ const { Title, Text, Image, ImageMask, Table, TableRow, TableData, VStack } = MailUI;
2633
+ // Create mail.
2634
+ return this._mail_template({
2635
+ max_width: 800,
2636
+ children: [
2637
+ // Title.
2638
+ TableRow(Title("Chargeback Failed")
2639
+ .color(style.title_fg)
2640
+ .width("fit-content")
2641
+ .font_size(26)).center(),
2642
+ // Text.
2643
+ TableRow(Text("We regret to inform you that your recent chargeback request has been declined.")
2644
+ .margin(10, 0, 20, 0)
2645
+ .color(style.text_fg)
2646
+ .font_size(16)
2647
+ .center()).center(),
2648
+ // Image.
2649
+ TableRow(ImageMask(`${this.full_domain}/volt_static/payments/error.png`)
2650
+ .frame(40, 40)
2651
+ .mask_color("#E8454E")
2652
+ .margin(0, 0, 30, 0)).center(),
2653
+ // Title.
2654
+ TableRow(Title("Chargeback Summary")
2655
+ .color(style.subtitle_fg)
2656
+ .font_size(18)
2657
+ .margin(0)),
2658
+ TableRow(Text("A summary of your chargeback request.")
2659
+ .margin(5, 0, 20, 0)
2660
+ .color(style.text_fg)
2661
+ .font_size(16)),
2662
+ // Line items.
2663
+ this._render_mail_payment_line_items({ payment, line_items }),
2664
+ // Bottom spacing.
2665
+ VStack()
2666
+ .margin_bottom(15)
2667
+ ],
2668
+ });
2669
+ }
2670
+ }