@vandenberghinc/volt 1.1.2

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 (451) hide show
  1. package/.vrepo +28 -0
  2. package/.vscode/tasks.json +87 -0
  3. package/README.md +67 -0
  4. package/backend/dist/cjs/blacklist.d.ts +10 -0
  5. package/backend/dist/cjs/blacklist.js +53 -0
  6. package/backend/dist/cjs/cli.d.ts +2 -0
  7. package/backend/dist/cjs/cli.js +263 -0
  8. package/backend/dist/cjs/database.d.ts +364 -0
  9. package/backend/dist/cjs/database.js +1962 -0
  10. package/backend/dist/cjs/endpoint.d.ts +57 -0
  11. package/backend/dist/cjs/endpoint.js +425 -0
  12. package/backend/dist/cjs/file_watcher.d.ts +44 -0
  13. package/backend/dist/cjs/file_watcher.js +348 -0
  14. package/backend/dist/cjs/frontend.d.ts +13 -0
  15. package/backend/dist/cjs/frontend.js +30 -0
  16. package/backend/dist/cjs/image_endpoint.d.ts +24 -0
  17. package/backend/dist/cjs/image_endpoint.js +210 -0
  18. package/backend/dist/cjs/logger.d.ts +5 -0
  19. package/backend/dist/cjs/logger.js +16 -0
  20. package/backend/dist/cjs/meta.d.ts +50 -0
  21. package/backend/dist/cjs/meta.js +153 -0
  22. package/backend/dist/cjs/mutex.d.ts +24 -0
  23. package/backend/dist/cjs/mutex.js +52 -0
  24. package/backend/dist/cjs/package.json +1 -0
  25. package/backend/dist/cjs/payments/paddle.d.ts +161 -0
  26. package/backend/dist/cjs/payments/paddle.js +2301 -0
  27. package/backend/dist/cjs/plugins/browser.d.ts +36 -0
  28. package/backend/dist/cjs/plugins/browser.js +183 -0
  29. package/backend/dist/cjs/plugins/communication.d.ts +70 -0
  30. package/backend/dist/cjs/plugins/communication.js +177 -0
  31. package/backend/dist/cjs/plugins/css.d.ts +10 -0
  32. package/backend/dist/cjs/plugins/css.js +71 -0
  33. package/backend/dist/cjs/plugins/mail.d.ts +277 -0
  34. package/backend/dist/cjs/plugins/mail.js +1419 -0
  35. package/backend/dist/cjs/plugins/pdf.d.ts +757 -0
  36. package/backend/dist/cjs/plugins/pdf.js +1694 -0
  37. package/backend/dist/cjs/plugins/thread_monitor.d.ts +18 -0
  38. package/backend/dist/cjs/plugins/thread_monitor.js +127 -0
  39. package/backend/dist/cjs/plugins/ts/compiler.d.ts +132 -0
  40. package/backend/dist/cjs/plugins/ts/compiler.js +944 -0
  41. package/backend/dist/cjs/plugins/ts/preprocessing.d.ts +14 -0
  42. package/backend/dist/cjs/plugins/ts/preprocessing.js +762 -0
  43. package/backend/dist/cjs/rate_limit.d.ts +65 -0
  44. package/backend/dist/cjs/rate_limit.js +463 -0
  45. package/backend/dist/cjs/request.deprc.d.ts +48 -0
  46. package/backend/dist/cjs/request.deprc.js +572 -0
  47. package/backend/dist/cjs/response.deprc.d.ts +55 -0
  48. package/backend/dist/cjs/response.deprc.js +275 -0
  49. package/backend/dist/cjs/server.d.ts +311 -0
  50. package/backend/dist/cjs/server.js +3475 -0
  51. package/backend/dist/cjs/splash_screen.d.ts +35 -0
  52. package/backend/dist/cjs/splash_screen.js +152 -0
  53. package/backend/dist/cjs/status.d.ts +60 -0
  54. package/backend/dist/cjs/status.js +199 -0
  55. package/backend/dist/cjs/stream.d.ts +75 -0
  56. package/backend/dist/cjs/stream.js +954 -0
  57. package/backend/dist/cjs/users.d.ts +111 -0
  58. package/backend/dist/cjs/users.js +1945 -0
  59. package/backend/dist/cjs/utils.d.ts +27 -0
  60. package/backend/dist/cjs/utils.js +329 -0
  61. package/backend/dist/cjs/view.d.ts +52 -0
  62. package/backend/dist/cjs/view.js +568 -0
  63. package/backend/dist/cjs/vinc.d.ts +2 -0
  64. package/backend/dist/cjs/vinc.dev.d.ts +2 -0
  65. package/backend/dist/cjs/vinc.dev.js +42 -0
  66. package/backend/dist/cjs/vinc.js +42 -0
  67. package/backend/dist/cjs/volt.d.ts +15 -0
  68. package/backend/dist/cjs/volt.js +64 -0
  69. package/backend/dist/css/adyen.css +92 -0
  70. package/backend/dist/css/volt.css +65 -0
  71. package/backend/dist/esm/blacklist.d.ts +10 -0
  72. package/backend/dist/esm/blacklist.js +49 -0
  73. package/backend/dist/esm/cli.d.ts +2 -0
  74. package/backend/dist/esm/cli.js +228 -0
  75. package/backend/dist/esm/database.d.ts +364 -0
  76. package/backend/dist/esm/database.js +1957 -0
  77. package/backend/dist/esm/endpoint.d.ts +57 -0
  78. package/backend/dist/esm/endpoint.js +421 -0
  79. package/backend/dist/esm/file_watcher.d.ts +44 -0
  80. package/backend/dist/esm/file_watcher.js +313 -0
  81. package/backend/dist/esm/frontend.d.ts +13 -0
  82. package/backend/dist/esm/frontend.js +27 -0
  83. package/backend/dist/esm/image_endpoint.d.ts +24 -0
  84. package/backend/dist/esm/image_endpoint.js +206 -0
  85. package/backend/dist/esm/logger.d.ts +5 -0
  86. package/backend/dist/esm/logger.js +13 -0
  87. package/backend/dist/esm/meta.d.ts +50 -0
  88. package/backend/dist/esm/meta.js +149 -0
  89. package/backend/dist/esm/mutex.d.ts +24 -0
  90. package/backend/dist/esm/mutex.js +48 -0
  91. package/backend/dist/esm/payments/paddle.d.ts +161 -0
  92. package/backend/dist/esm/payments/paddle.js +2261 -0
  93. package/backend/dist/esm/plugins/browser.d.ts +36 -0
  94. package/backend/dist/esm/plugins/browser.js +176 -0
  95. package/backend/dist/esm/plugins/communication.d.ts +70 -0
  96. package/backend/dist/esm/plugins/communication.js +169 -0
  97. package/backend/dist/esm/plugins/css.d.ts +10 -0
  98. package/backend/dist/esm/plugins/css.js +64 -0
  99. package/backend/dist/esm/plugins/mail.d.ts +277 -0
  100. package/backend/dist/esm/plugins/mail.js +1403 -0
  101. package/backend/dist/esm/plugins/pdf.d.ts +757 -0
  102. package/backend/dist/esm/plugins/pdf.js +1694 -0
  103. package/backend/dist/esm/plugins/thread_monitor.d.ts +18 -0
  104. package/backend/dist/esm/plugins/thread_monitor.js +120 -0
  105. package/backend/dist/esm/plugins/ts/compiler.d.ts +132 -0
  106. package/backend/dist/esm/plugins/ts/compiler.js +907 -0
  107. package/backend/dist/esm/plugins/ts/preprocessing.d.ts +14 -0
  108. package/backend/dist/esm/plugins/ts/preprocessing.js +724 -0
  109. package/backend/dist/esm/rate_limit.d.ts +65 -0
  110. package/backend/dist/esm/rate_limit.js +425 -0
  111. package/backend/dist/esm/request.deprc.d.ts +48 -0
  112. package/backend/dist/esm/request.deprc.js +572 -0
  113. package/backend/dist/esm/response.deprc.d.ts +55 -0
  114. package/backend/dist/esm/response.deprc.js +275 -0
  115. package/backend/dist/esm/server.d.ts +311 -0
  116. package/backend/dist/esm/server.js +3435 -0
  117. package/backend/dist/esm/splash_screen.d.ts +35 -0
  118. package/backend/dist/esm/splash_screen.js +148 -0
  119. package/backend/dist/esm/status.d.ts +60 -0
  120. package/backend/dist/esm/status.js +196 -0
  121. package/backend/dist/esm/stream.d.ts +75 -0
  122. package/backend/dist/esm/stream.js +947 -0
  123. package/backend/dist/esm/users.d.ts +111 -0
  124. package/backend/dist/esm/users.js +1908 -0
  125. package/backend/dist/esm/utils.d.ts +27 -0
  126. package/backend/dist/esm/utils.js +324 -0
  127. package/backend/dist/esm/view.d.ts +52 -0
  128. package/backend/dist/esm/view.js +561 -0
  129. package/backend/dist/esm/vinc.d.ts +2 -0
  130. package/backend/dist/esm/vinc.dev.d.ts +2 -0
  131. package/backend/dist/esm/vinc.dev.js +6 -0
  132. package/backend/dist/esm/vinc.js +6 -0
  133. package/backend/dist/esm/volt.d.ts +15 -0
  134. package/backend/dist/esm/volt.js +23 -0
  135. package/backend/dist/esm-dev/blacklist.d.ts +10 -0
  136. package/backend/dist/esm-dev/blacklist.js +49 -0
  137. package/backend/dist/esm-dev/cli.d.ts +2 -0
  138. package/backend/dist/esm-dev/cli.js +228 -0
  139. package/backend/dist/esm-dev/database.d.ts +364 -0
  140. package/backend/dist/esm-dev/database.js +1957 -0
  141. package/backend/dist/esm-dev/endpoint.d.ts +57 -0
  142. package/backend/dist/esm-dev/endpoint.js +421 -0
  143. package/backend/dist/esm-dev/file_watcher.d.ts +44 -0
  144. package/backend/dist/esm-dev/file_watcher.js +313 -0
  145. package/backend/dist/esm-dev/frontend.d.ts +13 -0
  146. package/backend/dist/esm-dev/frontend.js +27 -0
  147. package/backend/dist/esm-dev/image_endpoint.d.ts +24 -0
  148. package/backend/dist/esm-dev/image_endpoint.js +206 -0
  149. package/backend/dist/esm-dev/logger.d.ts +5 -0
  150. package/backend/dist/esm-dev/logger.js +13 -0
  151. package/backend/dist/esm-dev/meta.d.ts +50 -0
  152. package/backend/dist/esm-dev/meta.js +149 -0
  153. package/backend/dist/esm-dev/mutex.d.ts +24 -0
  154. package/backend/dist/esm-dev/mutex.js +48 -0
  155. package/backend/dist/esm-dev/payments/paddle.d.ts +161 -0
  156. package/backend/dist/esm-dev/payments/paddle.js +2261 -0
  157. package/backend/dist/esm-dev/plugins/browser.d.ts +36 -0
  158. package/backend/dist/esm-dev/plugins/browser.js +176 -0
  159. package/backend/dist/esm-dev/plugins/communication.d.ts +70 -0
  160. package/backend/dist/esm-dev/plugins/communication.js +169 -0
  161. package/backend/dist/esm-dev/plugins/css.d.ts +10 -0
  162. package/backend/dist/esm-dev/plugins/css.js +64 -0
  163. package/backend/dist/esm-dev/plugins/mail.d.ts +277 -0
  164. package/backend/dist/esm-dev/plugins/mail.js +1403 -0
  165. package/backend/dist/esm-dev/plugins/pdf.d.ts +757 -0
  166. package/backend/dist/esm-dev/plugins/pdf.js +1694 -0
  167. package/backend/dist/esm-dev/plugins/thread_monitor.d.ts +18 -0
  168. package/backend/dist/esm-dev/plugins/thread_monitor.js +120 -0
  169. package/backend/dist/esm-dev/plugins/ts/compiler.d.ts +132 -0
  170. package/backend/dist/esm-dev/plugins/ts/compiler.js +907 -0
  171. package/backend/dist/esm-dev/plugins/ts/preprocessing.d.ts +14 -0
  172. package/backend/dist/esm-dev/plugins/ts/preprocessing.js +724 -0
  173. package/backend/dist/esm-dev/rate_limit.d.ts +65 -0
  174. package/backend/dist/esm-dev/rate_limit.js +425 -0
  175. package/backend/dist/esm-dev/request.deprc.d.ts +48 -0
  176. package/backend/dist/esm-dev/request.deprc.js +572 -0
  177. package/backend/dist/esm-dev/response.deprc.d.ts +55 -0
  178. package/backend/dist/esm-dev/response.deprc.js +275 -0
  179. package/backend/dist/esm-dev/server.d.ts +311 -0
  180. package/backend/dist/esm-dev/server.js +3435 -0
  181. package/backend/dist/esm-dev/splash_screen.d.ts +35 -0
  182. package/backend/dist/esm-dev/splash_screen.js +148 -0
  183. package/backend/dist/esm-dev/status.d.ts +60 -0
  184. package/backend/dist/esm-dev/status.js +196 -0
  185. package/backend/dist/esm-dev/stream.d.ts +75 -0
  186. package/backend/dist/esm-dev/stream.js +947 -0
  187. package/backend/dist/esm-dev/users.d.ts +111 -0
  188. package/backend/dist/esm-dev/users.js +1908 -0
  189. package/backend/dist/esm-dev/utils.d.ts +27 -0
  190. package/backend/dist/esm-dev/utils.js +324 -0
  191. package/backend/dist/esm-dev/view.d.ts +52 -0
  192. package/backend/dist/esm-dev/view.js +561 -0
  193. package/backend/dist/esm-dev/vinc.d.ts +2 -0
  194. package/backend/dist/esm-dev/vinc.dev.d.ts +2 -0
  195. package/backend/dist/esm-dev/vinc.dev.js +6 -0
  196. package/backend/dist/esm-dev/vinc.js +6 -0
  197. package/backend/dist/esm-dev/volt.d.ts +15 -0
  198. package/backend/dist/esm-dev/volt.js +23 -0
  199. package/backend/src/blacklist.ts +69 -0
  200. package/backend/src/cli.js +245 -0
  201. package/backend/src/database.ts +2241 -0
  202. package/backend/src/endpoint.ts +494 -0
  203. package/backend/src/file_watcher.ts +359 -0
  204. package/backend/src/frontend.ts +35 -0
  205. package/backend/src/globals.d.ts +8 -0
  206. package/backend/src/image_endpoint.ts +258 -0
  207. package/backend/src/logger.ts +18 -0
  208. package/backend/src/meta.ts +202 -0
  209. package/backend/src/mutex.ts +51 -0
  210. package/backend/src/payments/paddle.ts +2659 -0
  211. package/backend/src/plugins/browser.ts +188 -0
  212. package/backend/src/plugins/communication.ts +204 -0
  213. package/backend/src/plugins/css.ts +84 -0
  214. package/backend/src/plugins/fonts/Menlo-Bold.ttf +0 -0
  215. package/backend/src/plugins/fonts/Menlo-Regular.ttf +0 -0
  216. package/backend/src/plugins/mail.ts +1720 -0
  217. package/backend/src/plugins/pdf.js +1932 -0
  218. package/backend/src/plugins/thread_monitor.ts +164 -0
  219. package/backend/src/plugins/ts/compiler.ts +1242 -0
  220. package/backend/src/plugins/ts/preprocessing.ts +812 -0
  221. package/backend/src/rate_limit.ts +503 -0
  222. package/backend/src/request.deprc.js +626 -0
  223. package/backend/src/response.deprc.js +354 -0
  224. package/backend/src/server.ts +4149 -0
  225. package/backend/src/splash_screen.ts +192 -0
  226. package/backend/src/status.ts +199 -0
  227. package/backend/src/stream.ts +1070 -0
  228. package/backend/src/users.ts +2077 -0
  229. package/backend/src/utils.ts +359 -0
  230. package/backend/src/view.ts +655 -0
  231. package/backend/src/vinc.dev.js +6 -0
  232. package/backend/src/vinc.ts +6 -0
  233. package/backend/src/volt.js +25 -0
  234. package/backend/tsconfig.cjs.json +29 -0
  235. package/backend/tsconfig.esm.dev.json +34 -0
  236. package/backend/tsconfig.esm.json +30 -0
  237. package/backend/tsconfig.json +2 -0
  238. package/frontend/compile.js +436 -0
  239. package/frontend/dist/elements/base.d.ts +9891 -0
  240. package/frontend/dist/elements/base.js +8818 -0
  241. package/frontend/dist/elements/module.d.ts +16 -0
  242. package/frontend/dist/elements/module.js +178 -0
  243. package/frontend/dist/modules/array.d.ts +37 -0
  244. package/frontend/dist/modules/array.js +284 -0
  245. package/frontend/dist/modules/auth.d.ts +45 -0
  246. package/frontend/dist/modules/auth.js +138 -0
  247. package/frontend/dist/modules/colors.d.ts +26 -0
  248. package/frontend/dist/modules/colors.js +340 -0
  249. package/frontend/dist/modules/compression.d.ts +6 -0
  250. package/frontend/dist/modules/compression.js +999 -0
  251. package/frontend/dist/modules/cookies.d.ts +17 -0
  252. package/frontend/dist/modules/cookies.js +166 -0
  253. package/frontend/dist/modules/date.d.ts +142 -0
  254. package/frontend/dist/modules/date.js +493 -0
  255. package/frontend/dist/modules/events.d.ts +7 -0
  256. package/frontend/dist/modules/events.js +90 -0
  257. package/frontend/dist/modules/google.d.ts +10 -0
  258. package/frontend/dist/modules/google.js +53 -0
  259. package/frontend/dist/modules/meta.d.ts +9 -0
  260. package/frontend/dist/modules/meta.js +45 -0
  261. package/frontend/dist/modules/mutex.d.ts +8 -0
  262. package/frontend/dist/modules/mutex.js +52 -0
  263. package/frontend/dist/modules/number.d.ts +12 -0
  264. package/frontend/dist/modules/number.js +8 -0
  265. package/frontend/dist/modules/object.d.ts +50 -0
  266. package/frontend/dist/modules/object.js +147 -0
  267. package/frontend/dist/modules/paddle.d.ts +1403 -0
  268. package/frontend/dist/modules/paddle.js +2641 -0
  269. package/frontend/dist/modules/scheme.d.ts +207 -0
  270. package/frontend/dist/modules/scheme.js +649 -0
  271. package/frontend/dist/modules/settings.d.ts +3 -0
  272. package/frontend/dist/modules/settings.js +4 -0
  273. package/frontend/dist/modules/statics.d.ts +4 -0
  274. package/frontend/dist/modules/statics.js +45 -0
  275. package/frontend/dist/modules/string.d.ts +163 -0
  276. package/frontend/dist/modules/string.js +291 -0
  277. package/frontend/dist/modules/support.d.ts +18 -0
  278. package/frontend/dist/modules/support.js +102 -0
  279. package/frontend/dist/modules/themes.d.ts +8 -0
  280. package/frontend/dist/modules/themes.js +17 -0
  281. package/frontend/dist/modules/user.d.ts +58 -0
  282. package/frontend/dist/modules/user.js +279 -0
  283. package/frontend/dist/modules/utils.d.ts +58 -0
  284. package/frontend/dist/modules/utils.js +1159 -0
  285. package/frontend/dist/types/gradient.d.ts +12 -0
  286. package/frontend/dist/types/gradient.js +79 -0
  287. package/frontend/dist/ui/border_button.d.ts +177 -0
  288. package/frontend/dist/ui/border_button.js +235 -0
  289. package/frontend/dist/ui/button.d.ts +42 -0
  290. package/frontend/dist/ui/button.js +114 -0
  291. package/frontend/dist/ui/canvas.d.ts +56 -0
  292. package/frontend/dist/ui/canvas.js +411 -0
  293. package/frontend/dist/ui/checkbox.d.ts +72 -0
  294. package/frontend/dist/ui/checkbox.js +277 -0
  295. package/frontend/dist/ui/code.d.ts +232 -0
  296. package/frontend/dist/ui/code.js +977 -0
  297. package/frontend/dist/ui/color.d.ts +1 -0
  298. package/frontend/dist/ui/color.js +110 -0
  299. package/frontend/dist/ui/context_menu.d.ts +30 -0
  300. package/frontend/dist/ui/context_menu.js +211 -0
  301. package/frontend/dist/ui/css.d.ts +10 -0
  302. package/frontend/dist/ui/css.js +44 -0
  303. package/frontend/dist/ui/divider.d.ts +18 -0
  304. package/frontend/dist/ui/divider.js +82 -0
  305. package/frontend/dist/ui/dropdown.d.ts +115 -0
  306. package/frontend/dist/ui/dropdown.js +446 -0
  307. package/frontend/dist/ui/for_each.d.ts +38 -0
  308. package/frontend/dist/ui/for_each.js +97 -0
  309. package/frontend/dist/ui/form.d.ts +25 -0
  310. package/frontend/dist/ui/form.js +227 -0
  311. package/frontend/dist/ui/frame_modes.d.ts +28 -0
  312. package/frontend/dist/ui/frame_modes.js +116 -0
  313. package/frontend/dist/ui/google_map.d.ts +31 -0
  314. package/frontend/dist/ui/google_map.js +111 -0
  315. package/frontend/dist/ui/gradient.d.ts +24 -0
  316. package/frontend/dist/ui/gradient.js +115 -0
  317. package/frontend/dist/ui/image.d.ts +138 -0
  318. package/frontend/dist/ui/image.js +570 -0
  319. package/frontend/dist/ui/input.d.ts +316 -0
  320. package/frontend/dist/ui/input.js +1187 -0
  321. package/frontend/dist/ui/link.d.ts +39 -0
  322. package/frontend/dist/ui/link.js +146 -0
  323. package/frontend/dist/ui/list.d.ts +33 -0
  324. package/frontend/dist/ui/list.js +161 -0
  325. package/frontend/dist/ui/loader_button.d.ts +108 -0
  326. package/frontend/dist/ui/loader_button.js +207 -0
  327. package/frontend/dist/ui/loaders.d.ts +60 -0
  328. package/frontend/dist/ui/loaders.js +150 -0
  329. package/frontend/dist/ui/popup.d.ts +84 -0
  330. package/frontend/dist/ui/popup.js +331 -0
  331. package/frontend/dist/ui/pseudo.d.ts +16 -0
  332. package/frontend/dist/ui/pseudo.js +81 -0
  333. package/frontend/dist/ui/scroller.d.ts +131 -0
  334. package/frontend/dist/ui/scroller.js +1251 -0
  335. package/frontend/dist/ui/slider.d.ts +35 -0
  336. package/frontend/dist/ui/slider.js +203 -0
  337. package/frontend/dist/ui/spacer.d.ts +20 -0
  338. package/frontend/dist/ui/spacer.js +83 -0
  339. package/frontend/dist/ui/span.d.ts +11 -0
  340. package/frontend/dist/ui/span.js +75 -0
  341. package/frontend/dist/ui/stack.d.ts +123 -0
  342. package/frontend/dist/ui/stack.js +344 -0
  343. package/frontend/dist/ui/steps.d.ts +72 -0
  344. package/frontend/dist/ui/steps.js +306 -0
  345. package/frontend/dist/ui/style.d.ts +12 -0
  346. package/frontend/dist/ui/style.js +78 -0
  347. package/frontend/dist/ui/switch.d.ts +44 -0
  348. package/frontend/dist/ui/switch.js +280 -0
  349. package/frontend/dist/ui/table.d.ts +118 -0
  350. package/frontend/dist/ui/table.js +411 -0
  351. package/frontend/dist/ui/tabs.d.ts +85 -0
  352. package/frontend/dist/ui/tabs.js +392 -0
  353. package/frontend/dist/ui/text.d.ts +19 -0
  354. package/frontend/dist/ui/text.js +88 -0
  355. package/frontend/dist/ui/theme.d.ts +25 -0
  356. package/frontend/dist/ui/theme.js +237 -0
  357. package/frontend/dist/ui/title.d.ts +36 -0
  358. package/frontend/dist/ui/title.js +127 -0
  359. package/frontend/dist/ui/ui.d.ts +38 -0
  360. package/frontend/dist/ui/ui.js +41 -0
  361. package/frontend/dist/ui/view.d.ts +25 -0
  362. package/frontend/dist/ui/view.js +93 -0
  363. package/frontend/dist/volt.d.ts +22 -0
  364. package/frontend/dist/volt.js +27 -0
  365. package/frontend/exports.json +1340 -0
  366. package/frontend/src/css/adyen.css +92 -0
  367. package/frontend/src/css/volt.css +65 -0
  368. package/frontend/src/elements/base.ts +16790 -0
  369. package/frontend/src/elements/module.ts +184 -0
  370. package/frontend/src/elements/types.d.ts +155 -0
  371. package/frontend/src/modules/array.ts +366 -0
  372. package/frontend/src/modules/auth.ts +188 -0
  373. package/frontend/src/modules/colors.ts +449 -0
  374. package/frontend/src/modules/compression.ts +67 -0
  375. package/frontend/src/modules/cookies.ts +182 -0
  376. package/frontend/src/modules/date.js +535 -0
  377. package/frontend/src/modules/date.ts +583 -0
  378. package/frontend/src/modules/events.ts +96 -0
  379. package/frontend/src/modules/google.ts +60 -0
  380. package/frontend/src/modules/meta.ts +59 -0
  381. package/frontend/src/modules/mutex.ts +59 -0
  382. package/frontend/src/modules/number.ts +20 -0
  383. package/frontend/src/modules/object.ts +212 -0
  384. package/frontend/src/modules/paddle.ts +2990 -0
  385. package/frontend/src/modules/scheme.ts +740 -0
  386. package/frontend/src/modules/settings.ts +5 -0
  387. package/frontend/src/modules/statics.ts +47 -0
  388. package/frontend/src/modules/string.ts +500 -0
  389. package/frontend/src/modules/support.ts +118 -0
  390. package/frontend/src/modules/themes.ts +24 -0
  391. package/frontend/src/modules/user.ts +321 -0
  392. package/frontend/src/modules/utils.ts +1260 -0
  393. package/frontend/src/static/admin/admin.png +0 -0
  394. package/frontend/src/static/admin/password.webp +0 -0
  395. package/frontend/src/static/icons/copy.webp +0 -0
  396. package/frontend/src/static/payments/arrow.long.webp +0 -0
  397. package/frontend/src/static/payments/arrow.long2.webp +0 -0
  398. package/frontend/src/static/payments/cancelled.webp +0 -0
  399. package/frontend/src/static/payments/check.sign.webp +0 -0
  400. package/frontend/src/static/payments/check.webp +0 -0
  401. package/frontend/src/static/payments/close.webp +0 -0
  402. package/frontend/src/static/payments/error.webp +0 -0
  403. package/frontend/src/static/payments/exclamation.webp +0 -0
  404. package/frontend/src/static/payments/minus.webp +0 -0
  405. package/frontend/src/static/payments/party.webp +0 -0
  406. package/frontend/src/static/payments/plus.webp +0 -0
  407. package/frontend/src/static/payments/shopping_cart.webp +0 -0
  408. package/frontend/src/static/payments/trash.webp +0 -0
  409. package/frontend/src/types/global.d.ts +4 -0
  410. package/frontend/src/types/gradient.ts +87 -0
  411. package/frontend/src/ui/any_element.d.ts +5 -0
  412. package/frontend/src/ui/border_button.ts +320 -0
  413. package/frontend/src/ui/button.ts +62 -0
  414. package/frontend/src/ui/canvas.ts +431 -0
  415. package/frontend/src/ui/checkbox.ts +284 -0
  416. package/frontend/src/ui/code.ts +1049 -0
  417. package/frontend/src/ui/color.ts +117 -0
  418. package/frontend/src/ui/context_menu.ts +194 -0
  419. package/frontend/src/ui/css.ts +57 -0
  420. package/frontend/src/ui/divider.ts +28 -0
  421. package/frontend/src/ui/dropdown.ts +503 -0
  422. package/frontend/src/ui/for_each.ts +71 -0
  423. package/frontend/src/ui/form.ts +208 -0
  424. package/frontend/src/ui/frame_modes.ts +140 -0
  425. package/frontend/src/ui/google_map.ts +70 -0
  426. package/frontend/src/ui/gradient.ts +73 -0
  427. package/frontend/src/ui/image.ts +587 -0
  428. package/frontend/src/ui/input.ts +1284 -0
  429. package/frontend/src/ui/link.ts +77 -0
  430. package/frontend/src/ui/list.ts +88 -0
  431. package/frontend/src/ui/loader_button.ts +192 -0
  432. package/frontend/src/ui/loaders.ts +126 -0
  433. package/frontend/src/ui/popup.ts +370 -0
  434. package/frontend/src/ui/pseudo.ts +33 -0
  435. package/frontend/src/ui/scroller.ts +1324 -0
  436. package/frontend/src/ui/slider.ts +215 -0
  437. package/frontend/src/ui/spacer.ts +29 -0
  438. package/frontend/src/ui/span.ts +23 -0
  439. package/frontend/src/ui/stack.ts +238 -0
  440. package/frontend/src/ui/steps.ts +334 -0
  441. package/frontend/src/ui/style.ts +26 -0
  442. package/frontend/src/ui/switch.ts +286 -0
  443. package/frontend/src/ui/table.ts +323 -0
  444. package/frontend/src/ui/tabs.ts +441 -0
  445. package/frontend/src/ui/text.ts +38 -0
  446. package/frontend/src/ui/theme.ts +279 -0
  447. package/frontend/src/ui/title.ts +64 -0
  448. package/frontend/src/ui/ui.ts +47 -0
  449. package/frontend/src/ui/view.ts +44 -0
  450. package/frontend/src/volt.ts +31 -0
  451. package/package.json +58 -0
@@ -0,0 +1,4149 @@
1
+ /*
2
+ * Author: Daan van den Bergh
3
+ * Copyright: © 2022 - 2024 Daan van den Bergh.
4
+ */
5
+
6
+ // ---------------------------------------------------------
7
+ // Libraries.
8
+
9
+ import * as http from "http";
10
+ import * as http2 from "http2";
11
+ import * as crypto from "crypto";
12
+ import * as nodemailer from 'nodemailer';
13
+ // import * as libcluster from 'cluster';
14
+ import libcluster from 'cluster';
15
+ import * as os from 'os';
16
+ import CleanCSS from 'clean-css';
17
+
18
+ // ---------------------------------------------------------
19
+ // Imports.
20
+
21
+ import { vlib } from "@vinc";
22
+ import { vhighlight } from "@vinc";
23
+ import { Utils, FrontendError } from "./utils.js";
24
+ import { Meta } from './meta.js';
25
+ import * as Mail from './plugins/mail.js';
26
+ import { Status } from "./status.js";
27
+ import { Mutex } from "./mutex.js";
28
+ import { Endpoint, EndpointOptions } from "./endpoint.js";
29
+ import { ImageEndpoint } from "./image_endpoint.js";
30
+ import { Stream } from "./stream.js";
31
+ import { Database, Collection, UIDCollection } from "./database.js";
32
+ import { StaticFileWatcher, FileWatcher } from "./file_watcher.js";
33
+ import { Users } from "./users.js";
34
+ import { Paddle } from "./payments/paddle.js";
35
+ import { RateLimits, RateLimitServer, RateLimitClient } from "./rate_limit.js";
36
+ import { Blacklist } from "./blacklist.js";
37
+ import logger, { LogSource } from "./logger.js";
38
+ import * as TSCompiler from "./plugins/ts/compiler.js";
39
+ import * as TSPreprocessing from "./plugins/ts/preprocessing.js";
40
+ import { BrowserPreview } from "./plugins/browser.js";
41
+
42
+ const log_source = new LogSource("Server");
43
+
44
+ import { fileURLToPath } from 'url';
45
+ import { dirname, join } from 'path';
46
+ // @ts-ignore
47
+ declare var __dirname; var __dirname = typeof __dirname !== 'undefined' ? __dirname : dirname(fileURLToPath(new URL('./package.json', import.meta.url)));
48
+
49
+
50
+ import { ThreadMonitor } from "./plugins/thread_monitor.js";
51
+ const thread_monitor = new ThreadMonitor()
52
+ thread_monitor.start();
53
+
54
+
55
+
56
+ // ---------------------------------------------------------
57
+ // Types
58
+
59
+ // None type.
60
+ declare global {
61
+ type none = null | undefined;
62
+ }
63
+
64
+ export interface CompanyInfo {
65
+ name: string;
66
+ legal_name: string;
67
+ street: string;
68
+ house_number: string;
69
+ postal_code: string;
70
+ city: string;
71
+ province: string;
72
+ country: string;
73
+ country_code: string;
74
+ tax_id?: string;
75
+ type?: string;
76
+ icon?: string;
77
+ icon_path?: string;
78
+ stroke_icon?: string;
79
+ stroke_icon_path?: string;
80
+ }
81
+
82
+ export interface TLSConfig {
83
+ cert: string;
84
+ key: string;
85
+ ca?: string | null;
86
+ passphrase?: string;
87
+ }
88
+
89
+ export interface MailStyle {
90
+ font: string;
91
+ title_fg: string;
92
+ subtitle_fg: string;
93
+ text_fg: string;
94
+ button_fg: string;
95
+ footer_fg: string;
96
+ bg: string;
97
+ widget_bg: string;
98
+ widget_border: string;
99
+ button_bg: string;
100
+ divider_bg: string;
101
+ }
102
+
103
+ export interface RateLimitConfig {
104
+ server?: {
105
+ ip?: string | null;
106
+ port?: number;
107
+ https?: any | null;
108
+ };
109
+ client?: {
110
+ ip?: string | null;
111
+ port?: number;
112
+ url?: string | null;
113
+ };
114
+ }
115
+
116
+ export interface AdminConfig {
117
+ password: string | null;
118
+ ips: string[];
119
+ tokens?: Array<{
120
+ token: string;
121
+ expiration: number;
122
+ }>;
123
+ }
124
+
125
+ export interface TypeScriptConfig {
126
+ compiler_opts: Record<string, any>;
127
+ output?: string;
128
+ }
129
+
130
+ export interface StaticDirectory {
131
+ path: string;
132
+ endpoint?: string;
133
+ cache?: number | boolean;
134
+ endpoints_cache?: Record<string, boolean | number>;
135
+ exclude?: Array<string | RegExp>;
136
+ }
137
+
138
+ export interface MailAttachment {
139
+ filename: string;
140
+ path?: string;
141
+ content: any;
142
+ }
143
+
144
+ interface DatabaseCollections {}
145
+
146
+ // ---------------------------------------------------------
147
+ // The server object.
148
+ // @todo redirect to https on http also important for seo.
149
+ // @todo convert throw new Error to frontend errors.
150
+ // @todo figure out with what settings nodejs should be started for heavy servers, for example larger memory size `--max-old-space-size`
151
+ // @todo implement usage of multiple cpu's using lib `cluster`.
152
+ // @todo when rendering pages the user could use a special OptimizeText() function which will be optimized for copy writing and seo when adding loading static files. Quite hard but would be sublime (writesonic is a good platform).
153
+
154
+ /* @docs:
155
+ @nav: Backend
156
+ @chapter: Server
157
+ @title: Server
158
+ @description:
159
+ The backend server class.
160
+ When the https parameters `certificate` and `private_key` are defined, the server will run automatically on http and https.
161
+ @parameter:
162
+ @name: production
163
+ @description: Whether the server is in production more, or in development mode.
164
+ @type: boolean
165
+ @required: true
166
+ @parameter:
167
+ @name: ip
168
+ @description: The ip where the server will run on.
169
+ @type: string
170
+ @required: true
171
+ @parameter:
172
+ @name: port
173
+ @description: The port where the server will run on. Leave the port `null` to run on port `80` for http and on port `443` for https.
174
+ @type: number
175
+ @parameter:
176
+ @name: tls
177
+ @description: The tls settings for HTTPS.
178
+ @type: object
179
+ @attribute:
180
+ @name: cert
181
+ @description: The path to the certificate.
182
+ @type: string
183
+ @attribute:
184
+ @name: key
185
+ @description: The path to the private key file.
186
+ @type: string
187
+ @attribute:
188
+ @name: ca
189
+ @description: The path to the ca bundle file.
190
+ @type: null, string
191
+ @attribute:
192
+ @name: passphrase
193
+ @description: The passphrase of the private key.
194
+ @type: string
195
+ @parameter:
196
+ @name: domain
197
+ @description: The full domain url without `http://` or `https://`.
198
+ @type: string
199
+ @required: true
200
+ @parameter:
201
+ @name: source
202
+ @description: The path to the source directory of your website. This may either be the source directory of your code, or the source directory where files will be stored for your website.
203
+ @type: string
204
+ @required: true
205
+ @parameter:
206
+ @name: is_primary
207
+ @description: Used to indicate if the current server is the primary node.
208
+ @type: string
209
+ @required: true
210
+ @parameter:
211
+ @name: statics
212
+ @description: Array with paths to static directories or static directory objects.
213
+ @type: string[], vlib.Path[], StaticDirectory
214
+ @required: true
215
+ @attributes_type: StaticDirectory
216
+ @attr:
217
+ @name: path
218
+ @descr: The path to the static directory or file.
219
+ @required: true
220
+ @attr:
221
+ @name: endpoint
222
+ @descr: The base endpoint of the static directory, by default the path's name will be used.
223
+ @required: false
224
+ @attr:
225
+ @name: cache
226
+ @descr: Enable caching for the static endpoints, this value will be used for parameter `Endpoint.cache`.
227
+ @type: boolean | number
228
+ @default: true
229
+ @required: false
230
+ @attr:
231
+ @name: endpoints_cache
232
+ @descr: This attribute can be used to define a specific cache policy per endpoint from this static directory. Must be formatted as `{<endpoint>: <cache>}`, the cache value will be used for parameter `Endpoint.cache`.
233
+ @default: {}
234
+ @required: false
235
+ @attr:
236
+ @name: exclude
237
+ @descr: An array of paths to exlude. The array may contain regexes.
238
+ @default: {}
239
+ @required: false
240
+ @parameter:
241
+ @name: database
242
+ @description:
243
+ The mongodb database settings.
244
+
245
+ The parameter can be defined as a `string` type as the database uri, or as an object with parameters for the <Link #Database>Database</Link> object.
246
+
247
+ When parameter `Server.is_primary` is defined as `false`, the database should always be defined as the database uri `string`. Since the secondary node will connect with the primary node.
248
+ @type: string, object, boolean
249
+ @required: true
250
+ @parameter:
251
+ @name: default_headers
252
+ @description: Used to override the default headers generated by volt. Leave parameter `default_headers` as `null` to let volt automatically generate the default headers.
253
+ @type: object
254
+ @parameter:
255
+ @name: favicon
256
+ @description: The path to the favicon.
257
+ @type: string
258
+ @parameter:
259
+ @name: token_expiration
260
+ @description: The token a sign in token will be valid in seconds.
261
+ @type: number
262
+ @parameter:
263
+ @name: enable_2fa
264
+ @description: Enable 2fa for user authentication.
265
+ @type: boolean
266
+ @required: true
267
+ @parameter:
268
+ @name: enable_account_activation
269
+ @description: Enable account activation by email after a user signs up.
270
+ @type: boolean
271
+ @required: true
272
+ @parameter:
273
+ @name: meta
274
+ @description: The default meta object.
275
+ @type: object, volt.Meta
276
+ @parameter:
277
+ @name: company
278
+ @type: object
279
+ @description: Your company information.
280
+ @attribute:
281
+ @name: name
282
+ @type: string
283
+ @required: true
284
+ @description: The name of your company.
285
+ @attribute:
286
+ @name: legal_name
287
+ @type: string
288
+ @required: true
289
+ @description: The legal name of your company.
290
+ @attribute:
291
+ @name: street
292
+ @type: string
293
+ @required: true
294
+ @description: The street name of your company's address.
295
+ @attribute:
296
+ @name: house_number
297
+ @type: string
298
+ @required: true
299
+ @description: The house number or house name of your company's address.
300
+ @attribute:
301
+ @name: postal_code
302
+ @type: string
303
+ @required: true
304
+ @description: The postal code or zip code of your company's address.
305
+ @attribute:
306
+ @name: city
307
+ @type: string
308
+ @required: true
309
+ @description: The city of your company's address.
310
+ @attribute:
311
+ @name: province
312
+ @type: string
313
+ @required: true
314
+ @description: The province or state of your company's address.
315
+ @attribute:
316
+ @name: country
317
+ @type: string
318
+ @required: true
319
+ @description: The country name of your company's address.
320
+ @attribute:
321
+ @name: country_code
322
+ @type: string
323
+ @required: true
324
+ @description: The two-letter ISO country code of your company's location.
325
+ @attribute:
326
+ @name: tax_id
327
+ @type: string
328
+ @required: false
329
+ @description: The tax id of your company.
330
+ @attribute:
331
+ @name: type
332
+ @type: string
333
+ @description: The type of company.
334
+ @attribute:
335
+ @name: icon
336
+ @type: string
337
+ @required: true
338
+ @description: The endpoint url path of your company's icon, png format. This must be an endpoint url since access to the file path is also required for creating invoices.
339
+ @attribute:
340
+ @name: stroke_icon
341
+ @type: string
342
+ @required: true
343
+ @description: The endpoint url path of your company's stroke icon, png format. In payment invoices the stroke icon precedes the default icon. This must be an endpoint url since access to the file path is also required for creating invoices.
344
+ @parameter:
345
+ @name: smtp
346
+ @description:
347
+ The smpt arguments object.
348
+ More information about the arguments can be found at the nodemailer <Link https://nodemailer.com/smtp/>documentation</Link>.
349
+ @type: object
350
+ @attribute:
351
+ @name: sender
352
+ @description:
353
+ The smtp sender address may either be a string with the email address, e.g. `your@email.com`.
354
+ Or an array with the sender name and email address, e.g. `["Sender", "your@email.com"]`.
355
+ @type: string, array
356
+ @attribute:
357
+ @name: host
358
+ @description: The mail server's host address.
359
+ @type: string
360
+ @attribute:
361
+ @name: port
362
+ @description: The mail server's port.
363
+ @type: number
364
+ @attribute:
365
+ @name: secure
366
+ @description: Enable secure options.
367
+ @type: boolean
368
+ @attr:
369
+ @name: auth
370
+ @description: The authentication settings.
371
+ @type: object
372
+ @attribute:
373
+ @name: user
374
+ @description: The email used for authentication.
375
+ @type: string
376
+ @attribute:
377
+ @name: pass
378
+ @description: The password used for authentication.
379
+ @type: string
380
+ @parameter:
381
+ @name: payments
382
+ @type: object
383
+ @description: The arguments for the payment class. The `type` attribute is used to indicate the payment provider, the other attributes are arguments for the payment class <Link #Paddle>Paddle</Link>.
384
+ @attribute:
385
+ @name: type
386
+ @type: string
387
+ @description: The payment provider name.
388
+ @required: true
389
+ @enum:
390
+ @value: paddle
391
+ @desc: Payment provider Paddle.
392
+ @parameter:
393
+ @name: google_tag
394
+ @description: The google tag id.
395
+ @type: string
396
+ @parameter:
397
+ @name: file_watcher
398
+ @description: The file watcher arguments, define to enable file watching. The parameter may either be an FileWatcher object, an object with arguments. The process argument `--no-file-watcher` or environment variable `VOLT_NO_FILE_WATCHER="1"` can always be used to (temporarily) disable the file watcher.
399
+ @type: FileWatcher, object
400
+ @parameter:
401
+ @name: mail_style
402
+ @description: The mail settings to customize automatically generated mails.
403
+ @type: object
404
+ @attribute:
405
+ @name: font
406
+ @description: The font family.
407
+ @type: string
408
+ @attribute:
409
+ @name: button_bg
410
+ @description: The background color of the button's in your mails.
411
+ @type: string
412
+ @parameter:
413
+ @name: offline
414
+ @description: Boolean indicating if the development server is being run offline.
415
+ @type: boolean
416
+ @parameter:
417
+ @name: multiprocessing
418
+ @description: Enable multiprocessing when in production mode.
419
+ @type: boolean
420
+ @def: true
421
+ @parameter:
422
+ @name: processes
423
+ @description: The number of processes when multiprocessing is enabled. By default the number of CPU's will be used for the amount of processes.
424
+ @type: null, number
425
+ @def: null
426
+ @parameter:
427
+ @name: rate_limit
428
+ @description:
429
+ The rate limit server and client settings. Rate limiting works with a centralizer websocket server and secondary clients.
430
+ @type: object
431
+ @required: false
432
+ @attribute:
433
+ @name: server
434
+ @type: object
435
+ @description:
436
+ The server configuration.
437
+
438
+ By default the primary server instance will start the rate limit service.
439
+
440
+ However, when parameter `rate_limit.server` is `false`. All rate limit instances will use a client to connect to an already running rate limit instance. If so, you must manually set up this rate limt server.
441
+ @attribute:
442
+ @name: ip
443
+ @description:
444
+ The ip to which the rate limiting server will bind. By default the rate limit server will run on localhost only.
445
+ @type: null, string
446
+ @attribute:
447
+ @name: port
448
+ @description:
449
+ The port to which the rate limiting server will bind. The default port is `51234`.
450
+ @type: number
451
+ @def: 51234
452
+ @attribute:
453
+ @name: https
454
+ @description:
455
+ To enable https on the server you must define a `https.createServer` configuration. Otherwise, the rate limit server will run on http.
456
+ @type: null, object
457
+ @attribute:
458
+ @name: client
459
+ @description:
460
+ The client configuration.
461
+ @attribute:
462
+ @name: ip
463
+ @description:
464
+ The ip address of the primary node with the rate limiting server. The primary node is indicated by the `Server.is_primary` parameter.
465
+
466
+ When `Server.is_primary` is true, the rate limiting server will listen on the private ip address of your current machine.
467
+ @type: null, string
468
+ @attribute:
469
+ @name: port
470
+ @description:
471
+ The port of the primary node with the rate limiting server. The default port is `51234`.
472
+ @type: number
473
+ @def: 51234
474
+ @attribute:
475
+ @name: url
476
+ @description:
477
+ The full websocket url of the server. If defined this takes precedence over parameters `ip` and `port`.
478
+
479
+ This can be useful when `rate_limit.server` is `false`.
480
+ @type: null, string
481
+ @parameter:
482
+ @name: keys
483
+ @description:
484
+ The array with names of crypto keys. The keys will be generated and stored in the database when they do not exist. The keys will be accessable as `Server.keys.$name`.
485
+
486
+ The array items may be a string representing the name of the key, or an object containing the name and the length of the key.
487
+ @type: array[string], array[object]
488
+ @required: false
489
+ @attribute:
490
+ @name: name
491
+ @description:
492
+ The name of the key.
493
+ @type: string
494
+ @attribute:
495
+ @name: length
496
+ @description:
497
+ The length of the key.
498
+ @type: number
499
+ @parameter:
500
+ @name: additional_sitemap_endpoints
501
+ @description:
502
+ An array with additional endpoints that will be added to the sitemap. By default all endpoints where attribute `view` is defined will be added the sitemap.
503
+ @type: array[string]
504
+ @parameter:
505
+ @name: daemon
506
+ @description:
507
+ The optional settings for the service daemons. The service daemons can be disabled by passing value `false` to parameter `daemon`.
508
+
509
+ By default this settings will also partially be used for the database service daemon.
510
+ @type: object
511
+ @attr:
512
+ @name: user
513
+ @desc: The executing user of the service daemon.
514
+ @type: string
515
+ @attr:
516
+ @name: group
517
+ @desc: The executing group of the service daemon.
518
+ @type: string
519
+ @attr:
520
+ @name: args
521
+ @desc: The arguments for the start command.
522
+ @type: array[string]
523
+ @attr:
524
+ @name: env
525
+ @desc: The environment variables for the service daemon.
526
+ @type: object
527
+ @attr:
528
+ @name: description
529
+ @desc: The description of the service daemon.
530
+ @type: string
531
+ @attr:
532
+ @name: logs
533
+ @desc: The path to the log file.
534
+ @type: string
535
+ @attr:
536
+ @name: errors
537
+ @desc: The path to the error log file.
538
+ @type: string
539
+ @parameter:
540
+ @name: admin
541
+ @description:
542
+ Administrator settings used for protected administrator endpoints.
543
+ @type: object
544
+ @attr:
545
+ @name: password
546
+ @desc: The password used for administrator endpoints.
547
+ @type: string
548
+ @attr:
549
+ @name: ips
550
+ @desc: IP addresses used by the website administrator. These ip's will be used to create a whitelist for administrator endpoints.
551
+ @type: string[]
552
+ @parameter:
553
+ @name: ts
554
+ @description:
555
+ Specify typescript options.
556
+ @type: object
557
+ @attr:
558
+ @name: output
559
+ @desc: The output directory for typescript endpoint source files.
560
+ @type: string
561
+ @attr:
562
+ @name: compiler_options
563
+ @desc: The compiler options for the typescript files.
564
+ @type: object
565
+ @required: false
566
+
567
+ @attribute:
568
+ @name: users
569
+ @type: object
570
+ @attribute:
571
+ @name: public
572
+ @type: UIDCollection
573
+ @desc:
574
+ The database collection for public data of users.
575
+
576
+ More information about the collection's functions can be found at <Type>UIDCollection</Type>
577
+ @warning:
578
+ The authenticated user always has read and write access to all data inside the user's protected directory through the backend rest api. Any other users or unauthenticated users do not have access to this data.
579
+ @attribute:
580
+ @name: protected
581
+ @type: UIDCollection
582
+ @desc:
583
+ The database collection for public data of users.
584
+
585
+ More information about the collection's functions can be found at <Type>UIDCollection</Type>
586
+ @warning:
587
+ The authenticated user always has read access to all data inside the user's protected directory through the backend rest api. Any other users or unauthenticated users do not have access to this data.
588
+ @attribute:
589
+ @name: private
590
+ @type: UIDCollection
591
+ @desc:
592
+ The database collection for public data of users.
593
+
594
+ More information about the collection's functions can be found at <Type>UIDCollection</Type>
595
+ @note:
596
+ The user has no read or write access to the private directory.
597
+ @attribute:
598
+ @name: storage
599
+ @type: Collection
600
+ @desc:
601
+ The database storage collection for the website's system backend data.
602
+
603
+ More information about the collection's functions can be found at <Type>Collection</Type>
604
+
605
+ */
606
+
607
+ // @tdo implement 3D secure "requires_action" status for a refund and payment intent.
608
+ // https://stripe.com/docs/payments/3d-secure
609
+
610
+ // @ts-ignore
611
+ export class Server {
612
+
613
+ // Static attributes.
614
+ static content_type_mimes: Array<[string, string]> = [
615
+ [".html", "text/html"],
616
+ [".htm", "text/html"],
617
+ [".shtml", "text/html"],
618
+ [".css", "text/css"],
619
+ [".xml", "application/xml"],
620
+ [".gif", "image/gif"],
621
+ [".jpeg", "image/jpeg"],
622
+ [".jpg", "image/jpeg"],
623
+ [".js", "application/javascript"],
624
+ [".atom", "application/atom+xml"],
625
+ [".rss", "application/rss+xml"],
626
+ [".mml", "text/mathml"],
627
+ [".txt", "text/plain"],
628
+ [".jad", "text/vnd.sun.j2me.app-descriptor"],
629
+ [".wml", "text/vnd.wap.wml"],
630
+ [".htc", "text/x-component"],
631
+ [".png", "image/png"],
632
+ [".tif", "image/tiff"],
633
+ [".tiff", "image/tiff"],
634
+ [".wbmp", "image/vnd.wap.wbmp"],
635
+ [".ico", "image/x-icon"],
636
+ [".jng", "image/x-jng"],
637
+ [".bmp", "image/x-ms-bmp"],
638
+ [".svg", "image/svg+xml"],
639
+ [".svgz", "image/svg+xml"],
640
+ [".webp", "image/webp"],
641
+ [".woff", "font/woff"],
642
+ [".woff2", "font/woff2"],
643
+ [".jar", "application/java-archive"],
644
+ [".war", "application/java-archive"],
645
+ [".ear", "application/java-archive"],
646
+ [".json", "application/json"],
647
+ [".hqx", "application/mac-binhex40"],
648
+ [".doc", "application/msword"],
649
+ [".pdf", "application/pdf"],
650
+ [".ps", "application/postscript"],
651
+ [".eps", "application/postscript"],
652
+ [".ai", "application/postscript"],
653
+ [".rtf", "application/rtf"],
654
+ [".m3u8", "application/vnd.apple.mpegurl"],
655
+ [".xls", "application/vnd.ms-excel"],
656
+ [".eot", "application/vnd.ms-fontobject"],
657
+ [".ppt", "application/vnd.ms-powerpoint"],
658
+ [".wmlc", "application/vnd.wap.wmlc"],
659
+ [".kml", "application/vnd.google-earth.kml+xml"],
660
+ [".kmz", "application/vnd.google-earth.kmz"],
661
+ [".7z", "application/x-7z-compressed"],
662
+ [".cco", "application/x-cocoa"],
663
+ [".jardiff", "application/x-java-archive-diff"],
664
+ [".jnlp", "application/x-java-jnlp-file"],
665
+ [".run", "application/x-makeself"],
666
+ [".pl", "application/x-perl"],
667
+ [".pm", "application/x-perl"],
668
+ [".prc", "application/x-pilot"],
669
+ [".pdb", "application/x-pilot"],
670
+ [".rar", "application/x-rar-compressed"],
671
+ [".rpm", "application/x-redhat-package-manager"],
672
+ [".sea", "application/x-sea"],
673
+ [".swf", "application/x-shockwave-flash"],
674
+ [".sit", "application/x-stuffit"],
675
+ [".tcl", "application/x-tcl"],
676
+ [".tk", "application/x-tcl"],
677
+ [".der", "application/x-x509-ca-cert"],
678
+ [".pem", "application/x-x509-ca-cert"],
679
+ [".crt", "application/x-x509-ca-cert"],
680
+ [".xpi", "application/x-xpinstall"],
681
+ [".xhtml", "application/xhtml+xml"],
682
+ [".xspf", "application/xspf+xml"],
683
+ [".zip", "application/zip"],
684
+ [".bin", "application/octet-stream"],
685
+ [".exe", "application/octet-stream"],
686
+ [".dll", "application/octet-stream"],
687
+ [".deb", "application/octet-stream"],
688
+ [".dmg", "application/octet-stream"],
689
+ [".iso", "application/octet-stream"],
690
+ [".img", "application/octet-stream"],
691
+ [".msi", "application/octet-stream"],
692
+ [".msp", "application/octet-stream"],
693
+ [".msm", "application/octet-stream"],
694
+ [".docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"],
695
+ [".xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"],
696
+ [".pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation"],
697
+ [".mid", "audio/midi"],
698
+ [".midi", "audio/midi"],
699
+ [".kar", "audio/midi"],
700
+ [".mp3", "audio/mpeg"],
701
+ [".ogg", "audio/ogg"],
702
+ [".m4a", "audio/x-m4a"],
703
+ [".ra", "audio/x-realaudio"],
704
+ [".3gpp", "video/3gpp"],
705
+ [".3gp", "video/3gpp"],
706
+ [".ts", "video/mp2t"],
707
+ [".mp4", "video/mp4"],
708
+ [".mpeg", "video/mpeg"],
709
+ [".mpg", "video/mpeg"],
710
+ [".mov", "video/quicktime"],
711
+ [".webm", "video/webm"],
712
+ [".flv", "video/x-flv"],
713
+ [".m4v", "video/x-m4v"],
714
+ [".mng", "video/x-mng"],
715
+ [".asx", "video/x-ms-asf"],
716
+ [".asf", "video/x-ms-asf"],
717
+ [".wmv", "video/x-ms-wmv"],
718
+ [".avi", "video/x-msvideo"],
719
+ ];
720
+
721
+ public log!: (level: number, ...args: any[]) => void;
722
+ public error!: (mode: LogSource | string | Error, ...errs: (string | Error)[]) => void;
723
+
724
+ static compressed_extensions: string[] = [
725
+ ".png",
726
+ ".jpg",
727
+ ".jpeg",
728
+ ".gif",
729
+ ".webp",
730
+ ".bmp",
731
+ ".tiff",
732
+ ".ico",
733
+ ".svg",
734
+ ".svgz",
735
+ ".mng",
736
+ ".apng",
737
+ ".jfif",
738
+ ".jp2",
739
+ ".jpx",
740
+ ".j2k",
741
+ ".jpm",
742
+ ".jpf",
743
+ ".heif",
744
+ ".mp3",
745
+ ".ogg",
746
+ ".wav",
747
+ ".flac",
748
+ ".m4a",
749
+ ".aac",
750
+ ".wma",
751
+ ".ra",
752
+ ".mid",
753
+ ".mp4",
754
+ ".webm",
755
+ ".mkv",
756
+ ".mov",
757
+ ".avi",
758
+ ".wmv",
759
+ ".mpg",
760
+ ".mpeg",
761
+ ".flv",
762
+ ];
763
+
764
+ // Instance properties
765
+ public ip: string;
766
+ public port: number;
767
+ public domain: string;
768
+ public full_domain: string;
769
+ public source: vlib.Path; // vlib.Path type
770
+ public is_primary: boolean;
771
+ public statics: Array<string | StaticDirectory | vlib.Path>;
772
+ public statics_aspect_ratios: Map<string | RegExp, any>;
773
+ public favicon?: string;
774
+ public enable_2fa: boolean;
775
+ public enable_account_activation: boolean;
776
+ public token_expiration: number;
777
+ public google_tag?: string;
778
+ public production: boolean;
779
+ public multiprocessing: boolean;
780
+ public processes: number;
781
+ public company: CompanyInfo;
782
+ public meta: Meta;
783
+ public mail_style: MailStyle;
784
+ public online: boolean;
785
+ public offline: boolean;
786
+ // private honey_pot_key: string | null;
787
+ private _keys: Array<string | {name: string, length: number}>;
788
+ public additional_sitemap_endpoints: string[];
789
+ public log_level: number;
790
+ public tls?: TLSConfig;
791
+ public file_watcher?: FileWatcher;
792
+ public admin: AdminConfig;
793
+ public ts: TypeScriptConfig;
794
+ public performance: vlib.Performance;
795
+ public csp: Record<string, string>;
796
+ public default_headers: Record<string, string>;
797
+ public http!: http.Server;
798
+ public https!: http2.Http2SecureServer;
799
+ public endpoints: Map<string, Endpoint>;
800
+ public err_endpoints: Map<number, Endpoint>;
801
+ public db!: Database & DatabaseCollections;
802
+ public _sys_db!: Collection; // needs to be public for the RateLimit classes.
803
+ public storage!: Collection;
804
+ public smtp?: nodemailer.Transporter;
805
+ public smtp_sender?: string | [string, string];
806
+ public rate_limit?: RateLimitServer | RateLimitClient;
807
+ public blacklist?: Blacklist;
808
+ private _hash_key: string | null = null;
809
+ public keys: Record<string, string>;
810
+ private _on_start: Array<(args: {forked: boolean}) => void | Promise<void>>;
811
+ private _on_stop: Array<() => void | Promise<void>>;
812
+ public browser_preview?: BrowserPreview;
813
+ public static_file_watcher: StaticFileWatcher;
814
+ public is_file_watcher: boolean;
815
+ public daemon?: vlib.Daemon;
816
+ private _stop_tscompiler_watcher?: () => void;
817
+
818
+ public users!: Users;
819
+ public payments!: Paddle;
820
+
821
+ public status: typeof Status;
822
+ public rate_limits: typeof RateLimits;
823
+ public logger: typeof logger;
824
+
825
+ constructor({
826
+ ip = "127.0.0.1",
827
+ port = 8000,
828
+ domain,
829
+ is_primary = true,
830
+ source,
831
+ database = "mongodb://localhost:27017/main",
832
+ statics = [],
833
+ favicon = undefined,
834
+ company,
835
+ meta = new Meta(),
836
+ tls = undefined,
837
+ smtp = undefined,
838
+ mail_style = {
839
+ font: '"Helvetica", sans-serif',
840
+ title_fg: "#121B23",
841
+ subtitle_fg: "#121B23",
842
+ text_fg: "#1F2F3D",
843
+ button_fg: "#FFFFFF",
844
+ footer_fg: "#686B80",
845
+ bg: "#EEEEEE",
846
+ widget_bg: "#FFFFFF",
847
+ widget_border: "#E6E6E6",
848
+ button_bg: "#1F2F3D",
849
+ divider_bg: "#706780",
850
+ },
851
+ rate_limit = {
852
+ server: {
853
+ ip: null,
854
+ port: RateLimitServer.default_port,
855
+ https: null,
856
+ },
857
+ client: {
858
+ ip: null,
859
+ port: RateLimitServer.default_port,
860
+ url: null,
861
+ },
862
+ },
863
+ keys = [],
864
+ payments = null,
865
+ default_headers = null,
866
+ google_tag = undefined,
867
+ token_expiration = 86400,
868
+ enable_2fa = false,
869
+ enable_account_activation = true,
870
+ // honey_pot_key = null,
871
+ production = false,
872
+ multiprocessing = true,
873
+ processes = null,
874
+ file_watcher = {},
875
+ offline = false,
876
+ additional_sitemap_endpoints = [],
877
+ log_level = 0,
878
+ daemon = {},
879
+ admin = {
880
+ password: null,
881
+ ips: [],
882
+ },
883
+ ts = {
884
+ compiler_opts: {},
885
+ output: undefined,
886
+ },
887
+ browser_preview = undefined,
888
+ }: {
889
+ ip?: string;
890
+ port?: number;
891
+ domain: string;
892
+ is_primary?: boolean;
893
+ source: string;
894
+ database?: string | Record<string, any> | boolean;
895
+ statics?: Array<string | vlib.Path | StaticDirectory>;
896
+ favicon?: string;
897
+ company: CompanyInfo;
898
+ meta?: Meta | Record<string, any>;
899
+ tls?: TLSConfig;
900
+ smtp?: nodemailer.TransportOptions;
901
+ mail_style?: Partial<MailStyle>;
902
+ rate_limit?: RateLimitConfig;
903
+ keys?: Array<string | {name: string, length: number}>;
904
+ payments?: any;
905
+ default_headers?: Record<string, any> | null;
906
+ google_tag?: string;
907
+ token_expiration?: number;
908
+ enable_2fa?: boolean;
909
+ enable_account_activation?: boolean;
910
+ // honey_pot_key?: string | null;
911
+ production?: boolean;
912
+ multiprocessing?: boolean;
913
+ processes?: number | null;
914
+ file_watcher?: FileWatcher | Record<string, any> | boolean;
915
+ offline?: boolean;
916
+ additional_sitemap_endpoints?: string[];
917
+ log_level?: number;
918
+ daemon?: Record<string, any> | boolean;
919
+ admin?: Partial<AdminConfig>;
920
+ ts?: Partial<TypeScriptConfig>;
921
+ browser_preview?: string;
922
+ }) {
923
+
924
+ // @debug
925
+ // Async hook for tracking active processes during stop().
926
+ // const async_resource_map = new Map();
927
+ // this.async_hook = require('async_hooks').createHook({
928
+ // init(async_id, type, trigger_async_id, resource) {
929
+ // const ignoredTypes = ['TickObject', 'PROMISE'];
930
+ // if (ignoredTypes.includes(type)) {
931
+ // return; // Skip logging for these async types
932
+ // }
933
+
934
+ // // Capture the stack trace of the function that initiated the async operation
935
+ // const stack = new Error("SKIPAFTER").stack.split("SKIPAFTER")[1].trim();
936
+
937
+ // // Log async_id and initiating function call stack
938
+ // // console.log(`Init async_id: ${async_id}`)
939
+ // // console.log(`Init async_id: ${async_id}, type: ${type}, trigger: ${trigger_async_id}\nStack: ${stack}`);
940
+
941
+ // // Store the async resource and stack trace
942
+ // async_resource_map.set(async_id, { type, stack });
943
+ // },
944
+ // destroy(async_id) {
945
+ // // When an async resource is destroyed, remove it from the map
946
+ // // console.log(`Destroy async_id: ${async_id}`);
947
+ // async_resource_map.delete(async_id);
948
+ // },
949
+ // // before(async_id) {
950
+ // // console.log(`Before async_id: ${async_id}`);
951
+ // // },
952
+ // // after(async_id) {
953
+ // // console.log(`After async_id: ${async_id}`);
954
+ // // },
955
+ // })
956
+ // this.async_hook.resource_map = async_resource_map;
957
+ // this.async_hook.enable();
958
+
959
+ // Verify args.
960
+ vlib.Scheme.verify({object: arguments[0], err_prefix: "Server: ", check_unknown: true, scheme: {
961
+ ip: "string",
962
+ port: "number",
963
+ domain: "string",
964
+ statics: {type: "array", default: []},
965
+ is_primary: {type: "boolean", default: true},
966
+ source: "string",
967
+ database: {
968
+ type: ["string", "object", "boolean"],
969
+ required: false,
970
+ scheme: {...(Database.constructor_scheme as any), _server: undefined},
971
+ },
972
+ favicon: {type: "string", required: false},
973
+ // honey_pot_key: {type: "string", default: null},
974
+ company: {
975
+ type: "object",
976
+ default: {},
977
+ scheme: {
978
+ name: "string",
979
+ legal_name: "string",
980
+ street: "string",
981
+ house_number: "string",
982
+ postal_code: "string",
983
+ city: "string",
984
+ province: "string",
985
+ country: "string",
986
+ country_code: "string",
987
+ tax_id: {type: "string", default: null},
988
+ icon: {type: "string", default: null},
989
+ icon_path: {type: "string", default: null},
990
+ stroke_icon: {type: "string", default: null},
991
+ stroke_icon_path: {type: "string", default: null},
992
+ }
993
+ },
994
+ meta: {type: "object", required: false},
995
+ tls: {
996
+ type: ["object"],
997
+ required: false,
998
+ scheme: {
999
+ cert: "string",
1000
+ key: "string",
1001
+ ca: {type: "string", default: null},
1002
+ passphrase: {type: "string", default: null},
1003
+ }
1004
+ },
1005
+ rate_limit: {
1006
+ type: "object",
1007
+ default: {
1008
+ ip: null,
1009
+ port: RateLimitServer.default_port,
1010
+ },
1011
+ scheme: {
1012
+ server: {type: "object", default: {}, scheme: {
1013
+ ip: {type: "string", default: null},
1014
+ port: {type: "number", default: RateLimitServer.default_port},
1015
+ https: {type: "object", default: null},
1016
+ }},
1017
+ client: {type: "object", default: {}, scheme: {
1018
+ ip: {type: "string", default: null},
1019
+ port: {type: "number", default: RateLimitServer.default_port},
1020
+ url: {type: "string", default: null},
1021
+ }},
1022
+ },
1023
+ },
1024
+ keys: {type: "array", default: []},
1025
+ smtp: {type: ["null", "object"], required: false},
1026
+ mail_style: {
1027
+ type: "object",
1028
+ required: false,
1029
+ scheme: {
1030
+ font: {type: "string", default: '"Helvetica", sans-serif'},
1031
+ title_fg: {type: "string", default: "#121B23"},
1032
+ subtitle_fg: {type: "string", default: "#121B23"},
1033
+ text_fg: {type: "string", default: "#1F2F3D"},
1034
+ button_fg: {type: "string", default: "#FFFFFF"},
1035
+ footer_fg: {type: "string", default: "#686B80"},
1036
+ bg: {type: "string", default: "#EEEEEE"},
1037
+ widget_bg: {type: "string", default: "#FFFFFF"},
1038
+ button_bg: {type: "string", default: "#421959"},
1039
+ widget_border: {type: "string", default: "#E6E6E6"},
1040
+ divider_bg: {type: "string", default: "#E6E6E6"},
1041
+ }
1042
+ },
1043
+ payments: {type: ["null", "object"], required: false},
1044
+ default_headers: {type: ["null", "object"], required: false},
1045
+ google_tag: {type: "string", required: false},
1046
+ token_expiration: {type: "number", required: false},
1047
+ enable_2fa: {type: "boolean", required: false},
1048
+ enable_account_activation: {type: "boolean", required: false},
1049
+ production: {type: "boolean", required: false},
1050
+ multiprocessing: {type: "boolean", required: false, default: true},
1051
+ processes: {type: "number", required: false, default: null},
1052
+ file_watcher: {type: ["null", "boolean", "object", FileWatcher], required: false},
1053
+ offline: {type: "boolean", default: false},
1054
+ additional_sitemap_endpoints: {type: "array", default: []},
1055
+ log_level: {type: "number", default: 0},
1056
+ daemon: {type: ["object", "boolean"], default: {}},
1057
+ admin: {type: "object", default: {}, attributes: {
1058
+ ips: {type: "array", default: []},
1059
+ password: {
1060
+ type: "string",
1061
+ verify: (param: string, attrs) => (param.length < 10 ? `Parameter "Server.admin.password" must have a length of at least 10 characters.` : undefined),
1062
+ },
1063
+ }},
1064
+ ts: {
1065
+ type: "object",
1066
+ required: false,
1067
+ scheme: {
1068
+ compiler_opts: {type: "object", default: {}},
1069
+ output: "string",
1070
+ },
1071
+ },
1072
+ browser_preview: {type: "string", required: false, default: undefined},
1073
+ }});
1074
+
1075
+ // Assign attributes directly.
1076
+ this.port = port;
1077
+ this.ip = ip;
1078
+ this.is_primary = is_primary && libcluster.isPrimary;
1079
+ this.source = new vlib.Path(source);
1080
+ this.favicon = favicon;
1081
+ this.enable_2fa = enable_2fa;
1082
+ this.enable_account_activation = enable_account_activation;
1083
+ this.token_expiration = token_expiration;
1084
+ this.google_tag = google_tag;
1085
+ this.production = production;
1086
+ this.multiprocessing = multiprocessing;
1087
+ this.processes = processes == null ? os.cpus().length : processes;
1088
+ this.company = company;
1089
+ this.mail_style = mail_style as MailStyle;
1090
+ this.offline = offline;
1091
+ this.online = !offline;
1092
+ // this.honey_pot_key = honey_pot_key;
1093
+ this._keys = keys;
1094
+ this.additional_sitemap_endpoints = additional_sitemap_endpoints;
1095
+ this.log_level = log_level;
1096
+ this.tls = tls;
1097
+ // this.file_watcher = file_watcher;
1098
+ this.admin = admin as AdminConfig;
1099
+ this.ts = ts as TypeScriptConfig;
1100
+ this.endpoints = new Map();
1101
+ this.err_endpoints = new Map();
1102
+
1103
+ /* @performance */ this.performance = new vlib.Performance("Server performance");
1104
+
1105
+ // Assign objects to server so it is easy to access.
1106
+ this.status = Status;
1107
+ this.logger = logger;
1108
+ this.rate_limits = RateLimits;
1109
+
1110
+ // Add global rate limit.
1111
+ this.rate_limits.add({group: "global", interval: 60, limit: 1000});
1112
+
1113
+ // Check source.
1114
+ if (!this.source.exists()) {
1115
+ throw Error(`Source directory "${this.source.str()}" does not exist.`);
1116
+ }
1117
+ this.source = this.source.abs();
1118
+
1119
+ // Set domain.
1120
+ this.domain = domain!.replace("https://","").replace("http://","");
1121
+ while (this.domain.length > 0 && this.domain.charAt(this.domain.length - 1) === "/") {
1122
+ this.domain = this.domain.substr(0, this.domain.length - 1)
1123
+ }
1124
+
1125
+ // Set full domain.
1126
+ this.full_domain = `http${tls == null || tls.key == null ? "" : "s"}://${domain}`; // also required for Stripe.
1127
+ while (this.full_domain.charAt(this.full_domain.length - 1) === "/") {
1128
+ this.full_domain = this.full_domain.substr(0, this.full_domain.length - 1);
1129
+ }
1130
+
1131
+ // Set statics.
1132
+ this.statics = statics;
1133
+ this.statics_aspect_ratios = new Map();
1134
+
1135
+ // Add the default static to statics.
1136
+ this.statics.push({
1137
+ path: `${__dirname}/../../../frontend/src/static/`,
1138
+ endpoint: "/volt_static",
1139
+ });
1140
+
1141
+ // Set meta.
1142
+ if (!(meta instanceof Meta)) {
1143
+ meta = new Meta(meta);
1144
+ }
1145
+ if (favicon != null && meta.favicon == null) {
1146
+ meta.favicon = this.full_domain + "/favicon.ico";
1147
+ }
1148
+ if (favicon != null && meta.image == null) {
1149
+ meta.image = this.full_domain + "/favicon.ico";
1150
+ } else if (meta.image != null && !meta.image.startsWith("http")) {
1151
+ meta.image = this.full_domain + meta.image;
1152
+ }
1153
+ this.meta = meta as Meta;
1154
+
1155
+ // Default headers.
1156
+ const base_default_headers: Record<string, string> = {
1157
+ "Vary": "Origin",
1158
+ "Referrer-Policy": "same-origin",
1159
+ "Access-Control-Allow-Methods": "GET, POST, PUT, PATCH, DELETE, OPTIONS",
1160
+ "Access-Control-Allow-Origin": "*",
1161
+ 'Access-Control-Allow-Headers': 'Content-Type, Authorization',
1162
+ 'Access-Control-Allow-Credentials': 'true',
1163
+ "X-XSS-Protection": "1; mode=block",
1164
+ "X-Content-Type-Options": "nosniff",
1165
+ "frame-ancestors": 'none',
1166
+ "X-Frame-Options": "DENY",
1167
+ "Strict-Transport-Security": "max-age=31536000",
1168
+ }
1169
+ const default_csp: Record<string, string> = {
1170
+ "default-src": "'self' https://*.google-analytics.com",
1171
+ "img-src": `'self' http://${this.domain} https://${this.domain} https://*.google-analytics.com https://raw.githubusercontent.com/vandenberghinc/ `,
1172
+ "script-src": "'self' 'unsafe-inline' https://ajax.googleapis.com https://www.googletagmanager.com https://googletagmanager.com https://*.google-analytics.com https://code.jquery.com https://cdn.jsdelivr.net/npm/@vandenberghinc/",
1173
+ "style-src": "'self' 'unsafe-inline' https://cdn.jsdelivr.net/npm/@vandenberghinc/",
1174
+ }
1175
+ if (default_headers == null) {
1176
+ this.csp = default_csp;
1177
+ this.default_headers = {...base_default_headers};
1178
+ } else {
1179
+ if (default_headers["Content-Security-Policy"] != null && typeof default_headers["Content-Security-Policy"] !== "object") {
1180
+ throw Error("The Content-Security-Policy of the default headers must be an object with values for each csp key, e.g. \"{'script-src': '...'}\".");
1181
+ }
1182
+ this.csp = default_headers["Content-Security-Policy"] != null ? default_headers["Content-Security-Policy"] : default_csp;
1183
+ Object.keys(base_default_headers).forEach(key => {
1184
+ if (default_headers[key] === undefined) {
1185
+ default_headers[key] = base_default_headers[key];
1186
+ }
1187
+ })
1188
+ this.default_headers = default_headers;
1189
+ }
1190
+
1191
+ // Initialize payments.
1192
+ if (payments) {
1193
+ // if (payments.type === "adyen") {
1194
+ // this.payments = new Adyen({
1195
+ // _server: this,
1196
+ // ...payments,
1197
+ // })
1198
+ // } else
1199
+ if (payments.type === "paddle") {
1200
+ this.payments = new Paddle({
1201
+ _server: this,
1202
+ ...payments,
1203
+ })
1204
+ } else {
1205
+ throw Error(`Invalid payment processor type "${payments.type}", valid types are "paddle" or "adyen".`)
1206
+ }
1207
+ }
1208
+
1209
+ // Define your list of endpoints
1210
+ this.endpoints = new Map();
1211
+ this.err_endpoints = new Map();
1212
+
1213
+ // Browser preview.
1214
+ if (browser_preview) {
1215
+ this.browser_preview = new BrowserPreview(browser_preview);
1216
+ }
1217
+
1218
+ // Static file watcher.
1219
+ this.static_file_watcher = new StaticFileWatcher(this);
1220
+
1221
+ // Initialize file watcher.
1222
+ if (file_watcher !== false) {
1223
+ if (file_watcher == null) {
1224
+ // Null.
1225
+ file_watcher = {};
1226
+ }
1227
+ if (!(file_watcher as Record<string, any>).source) {
1228
+ // Source.
1229
+ (file_watcher as Record<string, any>).source = this.source.str();
1230
+ }
1231
+ if (!(file_watcher instanceof FileWatcher)) {
1232
+ // Initialize.
1233
+ file_watcher = new FileWatcher(file_watcher as any);
1234
+ }
1235
+ this.file_watcher = file_watcher as FileWatcher;
1236
+ }
1237
+ else {
1238
+ this.file_watcher = undefined;
1239
+ }
1240
+
1241
+ // File watcher.
1242
+ if (
1243
+ process.env.VOLT_NO_FILE_WATCHER == null &&
1244
+ !process.argv.includes("--no-file-watcher") &&
1245
+ this.production === false &&
1246
+ file_watcher !== false &&
1247
+ process.env.VOLT_FILE_WATCHER !== '1'
1248
+ ) {
1249
+
1250
+ // Disable primary for when users access is_primary.
1251
+ this.is_primary = false;
1252
+
1253
+ // Enable file watcher.
1254
+ this.is_file_watcher = true;
1255
+ }
1256
+
1257
+ // No file watcher.
1258
+ else {
1259
+
1260
+ // Disable file watcher.
1261
+ this.is_file_watcher = false;
1262
+
1263
+ // Set logger.
1264
+ logger.log_level = this.log_level;
1265
+ this.log = logger.log.bind(logger);
1266
+ this.error = logger.error.bind(logger);
1267
+
1268
+ // Initialize the service daemon.
1269
+ // Must be initialized before initializing the database.
1270
+ if (daemon !== false) {
1271
+ const log_source = this.source.join(".logs");
1272
+ if (!log_source.exists()) {
1273
+ log_source.mkdir_sync();
1274
+ }
1275
+ this.daemon = new vlib.Daemon({
1276
+ name: this.domain.replaceAll(".", ""),
1277
+ user: (daemon as Record<string, any>).user || os.userInfo().username,
1278
+ group: (daemon as Record<string, any>).group || null,
1279
+ command: "volt --service --start",
1280
+ cwd: this.source.str(),
1281
+ args: (daemon as Record<string, any>).args || [],
1282
+ env: (daemon as Record<string, any>).env || {},
1283
+ description: (daemon as Record<string, any>).description || `Service daemon for website ${this.domain}.`,
1284
+ auto_restart: true,
1285
+ logs: (daemon as Record<string, any>).logs || log_source.join("logs").str(),
1286
+ errors: (daemon as Record<string, any>).errors || log_source.join("errors").str(),
1287
+ })
1288
+ }
1289
+
1290
+ // Initialize the database class.
1291
+ if (typeof database === "string") {
1292
+ this.db = new Database({uri: database, _server: this});
1293
+ } else if (database !== false) {
1294
+ this.db = new Database({...database as Record<string, any>, _server: this});
1295
+ }
1296
+
1297
+ // Initialize the users class.
1298
+ this.users = new Users(this);
1299
+
1300
+ // The smtp instance.
1301
+ if (smtp) {
1302
+ this.smtp_sender = smtp.sender;
1303
+ this.smtp = nodemailer.createTransport(smtp);
1304
+ }
1305
+
1306
+ // The rate limit server/client.
1307
+ if (this.is_primary) {
1308
+ this.rate_limit = new RateLimitServer({...(rate_limit.server ?? {}), _server: this});
1309
+ } else {
1310
+ if (rate_limit.server?.https) {
1311
+ (rate_limit.client as Record<string, any>).https = true;
1312
+ }
1313
+ this.rate_limit = new RateLimitClient({...(rate_limit.client ?? {}), _server: this});
1314
+ }
1315
+
1316
+ // Blacklist class.
1317
+ // if (this.honey_pot_key) {
1318
+ // this.blacklist = new Blacklist({api_key: this.honey_pot_key});
1319
+ // }
1320
+
1321
+ }
1322
+
1323
+ // Other keys.
1324
+ this.keys = {};
1325
+
1326
+ // Start callbacks.
1327
+ this._on_start = [];
1328
+ this._on_stop = [];
1329
+ }
1330
+
1331
+ // ---------------------------------------------------------
1332
+ // Utils.
1333
+
1334
+ // Get a content type from an extension.
1335
+ get_content_type(extension: string): string {
1336
+ let content_type = Server.content_type_mimes.find((item) => {
1337
+ if (item[0] == extension) {
1338
+ return item[1];
1339
+ }
1340
+ })?.[1];
1341
+ if (content_type == null) {
1342
+ content_type = "application/octet-stream";
1343
+ }
1344
+ return content_type;
1345
+ }
1346
+
1347
+ // Set log level.
1348
+ set_log_level(level: number): void {
1349
+ this.log_level = level;
1350
+ logger.log_level = level;
1351
+ }
1352
+
1353
+ // ---------------------------------------------------------
1354
+ // Crypto (private).
1355
+
1356
+ // Generate a crypto key.
1357
+ generate_crypto_key(length: number = 32): string {
1358
+ return crypto.randomBytes(length).toString('hex');
1359
+ }
1360
+
1361
+ // Create a sha hmac with the master key.
1362
+ hmac(key: string, data: string, algo: string = "sha256"): string {
1363
+ const hmac = crypto.createHmac(algo, key);
1364
+ hmac.update(data);
1365
+ return hmac.digest("hex");
1366
+ }
1367
+
1368
+ _hmac(data: string): string {
1369
+ if (!this._hash_key) {
1370
+ throw new Error("Hash key not initialized");
1371
+ }
1372
+ const hmac = crypto.createHmac("sha256", this._hash_key);
1373
+ hmac.update(data);
1374
+ return hmac.digest("hex");
1375
+ }
1376
+
1377
+ // Hash without a key.
1378
+ hash(data: string | object, algo: string = "sha256"): string {
1379
+ if (typeof data !== "string") {
1380
+ data = JSON.stringify(data);
1381
+ }
1382
+ return crypto.createHash(algo).update(data).digest('hex');
1383
+ }
1384
+
1385
+ // ---------------------------------------------------------
1386
+ // Headers (private).
1387
+
1388
+ // Initialize the default headers.
1389
+ private _init_default_headers(): void {
1390
+ let csp: string[] = [];
1391
+ Object.entries(this.csp).forEach(([key, value]) => {
1392
+ csp.push(key);
1393
+ if (typeof value === "string" && value.length > 0) {
1394
+ csp.push(" ");
1395
+ csp.push(value);
1396
+ }
1397
+ csp.push(";");
1398
+ });
1399
+ this.default_headers["Content-Security-Policy"] = csp.join("");
1400
+ }
1401
+
1402
+ // Add header defaults.
1403
+ private _set_header_defaults(stream: Stream): void {
1404
+ stream.set_headers(this.default_headers);
1405
+ }
1406
+
1407
+ // ---------------------------------------------------------
1408
+ // Endpoints (private).
1409
+
1410
+ // Find endpoint.
1411
+ private _find_endpoint(endpoint: string, method: string | null = null): Endpoint | undefined {
1412
+ const result = this.endpoints.get(endpoint + ":" + method);
1413
+ if (!result) {
1414
+ for (const e of this.endpoints.values()) {
1415
+ if (
1416
+ e.endpoint instanceof RegExp &&
1417
+ e.method === method &&
1418
+ e.endpoint.test(endpoint)
1419
+ ) {
1420
+ return e;
1421
+ }
1422
+ }
1423
+ }
1424
+ return result;
1425
+ }
1426
+
1427
+ // Create default endpoints.
1428
+ private _create_default_endpoints(): string[] {
1429
+ // Vars.
1430
+ const additional_file_watcher_paths: string[] = [];
1431
+
1432
+ // Add favicon.
1433
+ if (this.favicon != null) {
1434
+ const favicon = new vlib.Path(this.favicon);
1435
+ if (favicon.exists() === false) {
1436
+ throw Error(`Specified favicon path "${favicon}" does not exist.`);
1437
+ }
1438
+ this.endpoint(new Endpoint({
1439
+ method: "GET",
1440
+ endpoint: "/favicon.ico",
1441
+ data: favicon.load_sync({type: "buffer"}),
1442
+ content_type: this.get_content_type(favicon.extension()),
1443
+ _is_static: true,
1444
+ }))
1445
+ // additional_file_watcher_paths.push(favicon.str());
1446
+ }
1447
+
1448
+ // Create status endpoint.
1449
+ const status_dir = this.source.join(".status");
1450
+ if (!status_dir.exists()) { status_dir.mkdir_sync(); }
1451
+ const status_key_path = status_dir.join("key");
1452
+ let status_key: string;
1453
+ if (!status_key_path.exists()) {
1454
+ status_key = this.generate_crypto_key(32)
1455
+ status_key_path.save_sync(status_key);
1456
+ } else {
1457
+ status_key = status_key_path.load_sync();
1458
+ }
1459
+ this.endpoint(new Endpoint({
1460
+ method: "GET",
1461
+ endpoint: "/.status",
1462
+ content_type: "application/json",
1463
+ params: {
1464
+ key: "string",
1465
+ },
1466
+ callback: async (stream: any, params: {key: string}) => {
1467
+ // Check key.
1468
+ if (params.key !== status_key) {
1469
+ return stream.send({
1470
+ status: 403,
1471
+ headers: {"Content-Type": "text/plain"},
1472
+ data: "Access Denied",
1473
+ })
1474
+ }
1475
+
1476
+ // Default status info.
1477
+ const status: Record<string, any> = {};
1478
+ status.ip = this.ip;
1479
+ if (this.http) {
1480
+ status.http_port = this.port == null ? 80 : (this.port);
1481
+ }
1482
+ if (this.https) {
1483
+ status.https_port = this.port == null ? 443 : (this.port + 1);
1484
+ }
1485
+
1486
+ // Load data.
1487
+ const data = await this._sys_db.load("status", {
1488
+ default: {
1489
+ running_since: null,
1490
+ running_threads: 0,
1491
+ total_threads: 0,
1492
+ }
1493
+ });
1494
+ Object.assign(status, data);
1495
+
1496
+ // Response.
1497
+ return stream.send({
1498
+ status: 200,
1499
+ headers: {"Content-Type": "application/json"},
1500
+ data: status,
1501
+ })
1502
+ },
1503
+ }))
1504
+
1505
+ // Default static endpoints.
1506
+ const defaults = [
1507
+ {
1508
+ method: "GET",
1509
+ endpoint: "/volt/volt.css",
1510
+ content_type: "text/css",
1511
+ path: new vlib.Path(`${__dirname}/../css/volt.css`),
1512
+ },
1513
+ {
1514
+ method: "GET",
1515
+ endpoint: "/vhighlight/vhighlight.css",
1516
+ content_type: "text/css",
1517
+ path: new vlib.Path(vhighlight.web_exports.css),
1518
+ },
1519
+ {
1520
+ method: "GET",
1521
+ endpoint: "/vhighlight/vhighlight.js",
1522
+ content_type: "application/javascript",
1523
+ path: new vlib.Path(vhighlight.web_exports.js),
1524
+ },
1525
+ ]
1526
+ defaults.forEach((item) => {
1527
+ this.endpoint(
1528
+ new Endpoint({
1529
+ method: item.method,
1530
+ endpoint: item.endpoint,
1531
+ content_type: item.content_type,
1532
+ compress: (item as any).compress,
1533
+ _path: item.path.str(),
1534
+ _templates: (item as any).templates,
1535
+ })
1536
+ ._load_data_by_path(this)
1537
+ )
1538
+ })
1539
+
1540
+ // Handler.
1541
+ return additional_file_watcher_paths;
1542
+ }
1543
+
1544
+ // Create the sitemap endpoint.
1545
+ private _create_sitemap(): void {
1546
+ let sitemap = "";
1547
+ sitemap += "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
1548
+ sitemap += "<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n";
1549
+ for (const endpoint of this.endpoints.values()) {
1550
+ if (endpoint.sitemap) {
1551
+ sitemap += `<url>\n <loc>${this.full_domain}/${endpoint.endpoint}</loc>\n</url>\n`; // @todo not compatiable with regex endpoints
1552
+ }
1553
+ }
1554
+ this.additional_sitemap_endpoints.forEach((endpoint) => {
1555
+ while (endpoint.length > 0 && endpoint.charAt(0) === "/") {
1556
+ endpoint = endpoint.substr(1);
1557
+ }
1558
+ sitemap += `<url>\n <loc>${this.full_domain}/${endpoint}</loc>\n</url>\n`;
1559
+ })
1560
+ sitemap += "</urlset>\n";
1561
+ this.endpoint(new Endpoint({
1562
+ method: "GET",
1563
+ endpoint: "/sitemap.xml",
1564
+ data: sitemap,
1565
+ content_type: "application/xml",
1566
+ compress: false,
1567
+ }))
1568
+ }
1569
+
1570
+ // Create the robots.txt endpoint.
1571
+ private _create_robots_txt(): void {
1572
+ let robots = "User-agent: *\n";
1573
+ let disallowed = 0;
1574
+ for (const endpoint of this.endpoints.values()) {
1575
+ if (!endpoint.robots) {
1576
+ robots += `Disallow: ${endpoint.endpoint}\n`; // @todo not compatiable with regex endpoints
1577
+ disallowed++;
1578
+ }
1579
+ }
1580
+ if (disallowed === 0) {
1581
+ robots += `Disallow: \n`;
1582
+ }
1583
+ robots += `\nSitemap: ${this.full_domain}/sitemap.xml`;
1584
+ this.endpoint(new Endpoint({
1585
+ method: "GET",
1586
+ endpoint: "/robots.txt",
1587
+ content_type: "text/plain",
1588
+ data: robots,
1589
+ compress: false,
1590
+ }))
1591
+ }
1592
+
1593
+ // Create admin endpoint.
1594
+ private _create_admin_endpoint(): void {
1595
+ // Add admin tokens.
1596
+ this.admin.tokens = [];
1597
+
1598
+ // Verify token.
1599
+ const verify_token = (token: string): boolean => {
1600
+ const now = Date.now();
1601
+ let new_tokens: Array<{token: string, expiration: number}> = [];
1602
+ let verified = false;
1603
+ this.admin.tokens!.forEach((i) => {
1604
+ if (now < i.expiration) {
1605
+ if (i.token === token) {
1606
+ verified = true;
1607
+ }
1608
+ new_tokens.push(i);
1609
+ }
1610
+ })
1611
+ this.admin.tokens = new_tokens;
1612
+ return verified;
1613
+ }
1614
+
1615
+ // Admin data.
1616
+ this.endpoint(new Endpoint({
1617
+ method: "POST",
1618
+ endpoint: "/admin/auth",
1619
+ content_type: "application/json",
1620
+ rate_limit: {
1621
+ group: "volt.admin.auth",
1622
+ limit: 5,
1623
+ interval: 60,
1624
+ },
1625
+ params: {
1626
+ password: "string",
1627
+ },
1628
+ ip_whitelist: this.admin.ips,
1629
+ callback: async (stream: any, params: {password: string}) => {
1630
+ // Check key.
1631
+ if (params.password !== this.admin.password) {
1632
+ return stream.send({
1633
+ status: 403,
1634
+ headers: {"Content-Type": "text/plain"},
1635
+ data: "Access Denied",
1636
+ })
1637
+ }
1638
+
1639
+ // Generate token.
1640
+ const token = {
1641
+ token: String.random(32),
1642
+ expiration: Date.now() + 3600 * 1000,
1643
+ };
1644
+ this.admin.tokens!.push(token)
1645
+
1646
+ // Response.
1647
+ return stream.send({
1648
+ status: 200,
1649
+ headers: {"Content-Type": "application/json"},
1650
+ data: token,
1651
+ })
1652
+ },
1653
+ }))
1654
+
1655
+ // Admin data.
1656
+ this.endpoint(new Endpoint({
1657
+ method: "GET",
1658
+ endpoint: "/admin/data",
1659
+ content_type: "application/json",
1660
+ rate_limit: "global",
1661
+ params: {
1662
+ token: "string",
1663
+ },
1664
+ ip_whitelist: this.admin.ips,
1665
+ callback: async (stream: any, params: {token: string}) => {
1666
+ // Verify token.
1667
+ if (!verify_token(params.token)) {
1668
+ return stream.send({
1669
+ status: 403,
1670
+ headers: {"Content-Type": "text/plain"},
1671
+ data: "Access Denied",
1672
+ })
1673
+ }
1674
+
1675
+ // Data.
1676
+ const data: Record<string, any> = {};
1677
+
1678
+ // Parse subscriptions.
1679
+ const subscriptions = await this.payments._get_all_active_subscriptions();
1680
+ data.subscriptions = subscriptions.length;
1681
+
1682
+ // Load data.
1683
+ const status = await this._sys_db.load("status", {
1684
+ default: {
1685
+ running_since: null,
1686
+ running_threads: 0,
1687
+ total_threads: 0,
1688
+ }
1689
+ });
1690
+ Object.assign(data, status);
1691
+
1692
+ // System data.
1693
+ data.cpu_usage = vlib.System.cpu_usage();
1694
+ data.memory_usage = vlib.System.memory_usage();
1695
+ data.network_usage = await vlib.System.network_usage();
1696
+
1697
+ // Users.
1698
+ data.users = (await this.users.list()).length;
1699
+
1700
+ // Response.
1701
+ return stream.send({
1702
+ status: 200,
1703
+ headers: {"Content-Type": "application/json"},
1704
+ data: data,
1705
+ })
1706
+ },
1707
+ }))
1708
+
1709
+ // Admin view.
1710
+ this.endpoint(new Endpoint({
1711
+ method: "GET",
1712
+ endpoint: "/admin",
1713
+ content_type: "application/json",
1714
+ rate_limit: "global",
1715
+ params: {
1716
+ password: "string",
1717
+ },
1718
+ ip_whitelist: this.admin.ips,
1719
+ sitemap: false,
1720
+ robots: false,
1721
+ view: {
1722
+ templates: {
1723
+ DOMAIN: this.domain,
1724
+ },
1725
+ callback: () => {
1726
+ // Style.
1727
+ const style = {
1728
+ bg: "#F2F3F6",
1729
+ sub_bg: "#FAFAFA",
1730
+ fg: "#000000",
1731
+ sub_fg: "#9099B4",
1732
+ border: "#D6D6D6",
1733
+ tint: "#64B878", //"#8EB8EB", //"#4E9CF7",
1734
+ }
1735
+
1736
+ // ... rest of the admin view implementation remains the same as it's client-side JavaScript ...
1737
+ },
1738
+ },
1739
+ }))
1740
+ }
1741
+
1742
+ // Initialize statics.
1743
+ private async _initialize_statics(): Promise<string[]> {
1744
+
1745
+ // Static paths for the file watcher.
1746
+ const static_paths: string[] = [];
1747
+
1748
+ // Add static file.
1749
+ const add_static_file = async (
1750
+ path: any, // vlib.Path type
1751
+ endpoint: string,
1752
+ cache: boolean | number = true
1753
+ ): Promise<void> => {
1754
+
1755
+ // Add to static paths.
1756
+ static_paths.push(path.str());
1757
+
1758
+ // Get content type.
1759
+ const content_type = this.get_content_type(path.extension());
1760
+
1761
+ // console.log("Add static file", endpoint, path.str())
1762
+
1763
+ // Image endpoint with supported transformation.
1764
+ if (ImageEndpoint.supported_images.includes(path.extension())) {
1765
+ const e = new ImageEndpoint({
1766
+ endpoint,
1767
+ content_type,
1768
+ path,
1769
+ cache,
1770
+ rate_limit: "global",
1771
+ _is_static: true,
1772
+ });
1773
+ const aspect_ratio = await e.get_aspect_ratio();
1774
+ if (aspect_ratio != null) {
1775
+ this.statics_aspect_ratios.set(e.endpoint, aspect_ratio)
1776
+ }
1777
+ this.endpoint(e)
1778
+ }
1779
+
1780
+ // Default static endpoint.
1781
+ else {
1782
+ // Create endpoint.
1783
+ this.endpoint(
1784
+ new Endpoint({
1785
+ method: "GET",
1786
+ endpoint,
1787
+ content_type,
1788
+ compress: !Server.compressed_extensions.includes(path.extension()),
1789
+ cache,
1790
+ rate_limit: "global",
1791
+ _path: path.str(),
1792
+ _is_static: true,
1793
+ })
1794
+ ._load_data_by_path(this)
1795
+ )
1796
+ }
1797
+ }
1798
+
1799
+ // Initialize statics.
1800
+ const add_static = async (opts: string | StaticDirectory | null): Promise<void> => {
1801
+ if (opts == null) { return; }
1802
+ if (typeof opts === "object") {
1803
+ // Check object.
1804
+ vlib.Scheme.verify({
1805
+ object: opts,
1806
+ check_unknown: true,
1807
+ scheme: {
1808
+ path: "string",
1809
+ endpoint: {type: "string", default: null},
1810
+ cache: {type: ["boolean", "number"], default: true},
1811
+ endpoints_cache: {type: "object", default: {}},
1812
+ exclude: {type: "array", default: []},
1813
+ }
1814
+ });
1815
+
1816
+ // Vars.
1817
+ const exclude = [/.*\.DS_Store/, /.*\.cache/, /.*\.old/, /.*\.ignore/, ...opts.exclude || []]
1818
+ const paths: any[] = []; // vlib.Path[]
1819
+ const source = new vlib.Path(opts.path).abs();
1820
+ const source_len = source.str().length;
1821
+ const is_dir = source.is_dir();
1822
+
1823
+ // Is excluded.
1824
+ const is_excluded = (path: string | RegExp): boolean => {
1825
+ return exclude.some(pattern => {
1826
+ if (path instanceof RegExp) {
1827
+ if (pattern instanceof RegExp) {
1828
+ return pattern.source === path.source;
1829
+ } else {
1830
+ return path.test(String(pattern));
1831
+ }
1832
+ } else {
1833
+ if (pattern instanceof RegExp) {
1834
+ return pattern.test(String(path));
1835
+ } else {
1836
+ return path === pattern;
1837
+ }
1838
+ }
1839
+ });
1840
+ }
1841
+
1842
+ // Initialize endpoint.
1843
+ opts.endpoint = opts.endpoint || `/${source.name()}`;
1844
+ if (opts.endpoint.charAt(0) != "/") {
1845
+ opts.endpoint = "/" + opts.endpoint;
1846
+ }
1847
+ while (opts.endpoint.charAt(opts.endpoint.length - 1) == "/") {
1848
+ opts.endpoint = opts.endpoint.slice(0, -1);
1849
+ }
1850
+
1851
+ // Not a directory.
1852
+ if (!is_dir) {
1853
+ return await add_static_file(
1854
+ source,
1855
+ opts.endpoint,
1856
+ opts.cache,
1857
+ )
1858
+ }
1859
+
1860
+ // First extract all paths recursively.
1861
+ // non recursive to ignore .old etc dirs.
1862
+ const read_dir = async (path: any): Promise<void> => { // vlib.Path
1863
+ const dir_paths = await path.paths();
1864
+ const promises: Promise<void>[] = [];
1865
+ for (let i = 0; i < dir_paths.length; i++) {
1866
+ if (!is_excluded(dir_paths[i])) {
1867
+ // @todo excluded does not work `.old` etc is still included and DS_Store.
1868
+ if (dir_paths[i].is_dir()) {
1869
+ promises.push(read_dir(dir_paths[i]));
1870
+ } else {
1871
+ paths.push(dir_paths[i]);
1872
+ }
1873
+ }
1874
+ };
1875
+ await Promise.all(promises);
1876
+ }
1877
+ if (is_dir) {
1878
+ await read_dir(source);
1879
+ }
1880
+
1881
+ // Convert paths into a static object.
1882
+ for (const path of paths) {
1883
+ const endpoint = `${opts.endpoint}${path.str().substr(source_len)}`;
1884
+ await add_static_file(
1885
+ path,
1886
+ endpoint,
1887
+ opts.endpoints_cache === undefined ? opts.cache : opts.endpoints_cache[endpoint] ?? opts.cache,
1888
+ )
1889
+ }
1890
+
1891
+ }
1892
+ else if (typeof opts === "string") {
1893
+ await add_static({path: opts});
1894
+ }
1895
+ }
1896
+
1897
+ // Iterate.
1898
+ for (let i = 0; i < this.statics.length; i++) {
1899
+ if (this.statics[i] instanceof vlib.Path) {
1900
+ this.statics[i] = (this.statics[i] as vlib.Path).str();
1901
+ }
1902
+ await add_static(this.statics[i] as any);
1903
+ }
1904
+
1905
+ // Response.
1906
+ return static_paths;
1907
+ }
1908
+
1909
+ // @todo this one is not TS
1910
+ // Initialize, compile & bundle js & ts.
1911
+ async _init_ts() {
1912
+
1913
+ // Create dir.
1914
+ // Use ts.output instead of a /tmp/ dir since the node_modules for example is not present here.
1915
+ const tmp_volt_ts = new vlib.Path(this.ts.output ?? "/tmp/ts-dummy/").abs();
1916
+ if (this.ts.output && !tmp_volt_ts.exists()) {
1917
+ tmp_volt_ts.mkdir_sync();
1918
+ }
1919
+
1920
+ // Gather all entry paths.
1921
+ const server = this;
1922
+ let has_ts = false;
1923
+ const volt_frontend = new vlib.Path(`${__dirname}/../../../frontend/dist/`).abs().str();
1924
+ const input_paths: string[] = [
1925
+ volt_frontend + "/volt.js",
1926
+ ];
1927
+ const entries: Record<string, {
1928
+ endpoints: Endpoint[],
1929
+ path: string,
1930
+ extension: string,
1931
+ bundle(): Promise<void>;
1932
+ }> = {}
1933
+ for (const endpoint of this.endpoints.values()) {
1934
+ if (endpoint.view?.is_js_ts_view) {
1935
+ input_paths.push(endpoint.view.source_path!.str())
1936
+ let output_path = tmp_volt_ts.join(endpoint.view.source_path!).abs();
1937
+ const extension = output_path.extension();
1938
+ if (!has_ts && /.tsx?/.test(extension)) {
1939
+ has_ts = true;
1940
+ }
1941
+ const output = output_path.str().slice(0, -extension.length) + ".js";
1942
+ if (entries[output] === undefined) {
1943
+ entries[output] = {
1944
+ endpoints: [endpoint],
1945
+ path: output,
1946
+ extension,
1947
+ async bundle() {
1948
+ server.rate_limit?.reset_all(); // reset api limits.
1949
+ let bundle = undefined;
1950
+ let had_bundle = this.endpoints[0]?.view?.bundle != null;
1951
+ for (let e = 0; e < this.endpoints.length; e++) {
1952
+ if (bundle === undefined) {
1953
+ bundle = await this.endpoints[e]?.view?._bundle_ts(this.path);
1954
+ } else {
1955
+ await this.endpoints[e]?.view?._bundle_ts(this.path, bundle);
1956
+ }
1957
+ if (had_bundle && server.browser_preview) {
1958
+ await server.browser_preview.refresh(this.endpoints[e].endpoint);
1959
+ }
1960
+ }
1961
+ },
1962
+ };
1963
+ } else {
1964
+ entries[output].endpoints.push(endpoint);
1965
+ }
1966
+ }
1967
+ }
1968
+ const entry_values = Object.values(entries);
1969
+ if (has_ts && this.ts.output == null) {
1970
+ throw new Error(`Due to detected typescript endpoint source files the output path attribute "Server.ts.output" must be defined.`);
1971
+ }
1972
+
1973
+ // For production just compile & bundle.
1974
+ if (this.is_file_watcher || this.production) {
1975
+ await TSCompiler.compile({
1976
+ entry_paths: input_paths,
1977
+ output: tmp_volt_ts.str(),
1978
+ preprocess: TSPreprocessing.volt_auto_imports,
1979
+ compiler_opts: {
1980
+ target: "ES2021",
1981
+ module: "esnext",
1982
+ moduleResolution: "node",
1983
+ lib: ["ES2021", "DOM"],
1984
+ declaration: false,
1985
+ strict: true,
1986
+ esModuleInterop: true,
1987
+ allowJs: true,
1988
+ checkJs: false,
1989
+ noImplicitAny: false,
1990
+ skipLibCheck: true,
1991
+ types: [],
1992
+ ...this.ts.compiler_opts,
1993
+ rootDir: "/", // @warning is required to reliably match input and output file paths.
1994
+ },
1995
+ })
1996
+ let excluded = new Set();
1997
+ for (let i = 0; i < entry_values.length; i++) {
1998
+ await entry_values[i].bundle();
1999
+
2000
+ // Add file watcher excludes.
2001
+ if (this.is_file_watcher) {
2002
+ for (const endpoint of entry_values[i].endpoints) {
2003
+ // console.log(entry_values[i].path, endpoint.view.bundle.inputs)
2004
+ for (let path of endpoint.view?.bundle?.inputs ?? []) {
2005
+ const vpath = new vlib.Path(path).abs();
2006
+ const ext = vpath.extension();
2007
+ path = vpath.str();
2008
+ if (path.startsWith(tmp_volt_ts.str())) {
2009
+ const js_path = path.slice(tmp_volt_ts.str().length);
2010
+ const ts_path = path.slice(tmp_volt_ts.str().length, -ext.length) + ".ts";
2011
+ if (new vlib.Path(js_path).exists() && !excluded.has(js_path)) {
2012
+ // console.log("Exclude", js_path)
2013
+ excluded.add(js_path);
2014
+ this.file_watcher!.add_exclude(js_path);
2015
+ }
2016
+ if (new vlib.Path(ts_path).exists() && !excluded.has(ts_path)) {
2017
+ // console.log("Exclude", ts_path)
2018
+ excluded.add(ts_path);
2019
+ this.file_watcher!.add_exclude(ts_path);
2020
+ }
2021
+ }
2022
+ }
2023
+ // process.exit(1)
2024
+ }
2025
+ }
2026
+ }
2027
+ }
2028
+
2029
+ // Development.
2030
+ else {
2031
+
2032
+ // Compile with watcher enabled.
2033
+ const res = await TSCompiler.compile({
2034
+ entry_paths: input_paths,
2035
+ output: tmp_volt_ts.str(),
2036
+ preprocess: TSPreprocessing.volt_auto_imports,
2037
+ error_limit: 10,
2038
+ compiler_opts: {
2039
+ target: "ES2021",
2040
+ module: "esnext",
2041
+ moduleResolution: "node",
2042
+ lib: ["ES2021", "DOM"],
2043
+ declaration: false,
2044
+ strict: true,
2045
+ esModuleInterop: true,
2046
+ allowJs: true,
2047
+ checkJs: false,
2048
+ noImplicitAny: false,
2049
+ skipLibCheck: true,
2050
+ types: [],
2051
+ ...this.ts.compiler_opts,
2052
+ rootDir: "/", // @warning is required to reliably match input and output file paths.
2053
+ },
2054
+ watch: {
2055
+ enabled: true,
2056
+ log_level: 0,
2057
+ on_change: async (file_name) => {
2058
+ // console.log("File", file_name, "has changed.")
2059
+ logger.log(2, log_source, `Typescript output file ${file_name} has changed.`);
2060
+
2061
+ // Check if the file path is part of an endpoint.
2062
+ const entry = entries[file_name];
2063
+ if (entry != null) {
2064
+ await entry.bundle();
2065
+ }
2066
+
2067
+ // Check every other entries inputs' to see if that entry should be rebundled as well.
2068
+ for (let i = 0; i < entry_values.length; i++) {
2069
+ const entry = entry_values[i]
2070
+ if (entry.path !== file_name && entry.endpoints.iterate(e => e.view?.bundle?.inputs?.includes(file_name) ? true : undefined)) {
2071
+ await entry.bundle();
2072
+ }
2073
+ }
2074
+ },
2075
+ },
2076
+ });
2077
+ if (res.errors.length > 0) {
2078
+ res.debug();
2079
+ }
2080
+ for (let i = 0; i < entry_values.length; i++) {
2081
+ await entry_values[i].bundle();
2082
+ }
2083
+ this._stop_tscompiler_watcher = res.stop;
2084
+ }
2085
+ }
2086
+
2087
+ // ---------------------------------------------------------
2088
+ // Server (private).
2089
+
2090
+ // Initialize.
2091
+ // Initialize.
2092
+ async initialize(): Promise<void> {
2093
+ /* @performance */ this.performance.start()
2094
+
2095
+ // File watcher.
2096
+ if (this.is_file_watcher) {
2097
+ // Disable primary for when users access is_primary.
2098
+ this.is_primary = false;
2099
+
2100
+ // Create default endpoints for excluded static file watch paths.
2101
+ this._create_default_endpoints();
2102
+
2103
+ // Exclude the typescript output directory from the file watcher.
2104
+ if (this.ts.output) {
2105
+ this.file_watcher!.add_exclude(this.ts.output);
2106
+ }
2107
+
2108
+ // Add the volt backend source files to the additional files.
2109
+ this.file_watcher!.add_path(__dirname);
2110
+ this.file_watcher!.add_exclude(`${__dirname}/frontend_globals.js`);
2111
+
2112
+ // Excluded.
2113
+ this.file_watcher!.add_exclude(this.source.join(".db"));
2114
+ this.file_watcher!.add_exclude(this.source.join(".rate_limit"));
2115
+ this.file_watcher!.add_exclude(this.source.join(".logs"));
2116
+
2117
+ // Exclude static files.
2118
+ this.statics.forEach(item => {
2119
+ if (typeof item === "string" || item instanceof vlib.Path) {
2120
+ this.file_watcher!.add_exclude(item);
2121
+ }
2122
+ else {
2123
+ this.file_watcher!.add_exclude(item.path)
2124
+ }
2125
+ })
2126
+
2127
+ // Initialize all endpoints since this is required for _init_ts()
2128
+ for (const endpoint of this.endpoints.values()) {
2129
+ await endpoint._initialize(this);
2130
+ }
2131
+ for (const endpoint of this.err_endpoints.values()) {
2132
+ await endpoint._initialize(this);
2133
+ }
2134
+
2135
+ // Add typescript/js view endpoint excludes.
2136
+ await this._init_ts();
2137
+
2138
+ if (!this.production) {
2139
+ [
2140
+ `${process.env.PERSISTANCE}/private/dev/vinc/vlib/js/vlib.js`,
2141
+ `${process.env.PERSISTANCE}/private/dev/vinc/vhighlight/vhighlight.js`,
2142
+ ].forEach(path => {
2143
+ const vpath = new vlib.Path(path);
2144
+ if (vpath.exists()) {
2145
+ this.file_watcher!.add_exclude(vpath.str());
2146
+ }
2147
+ })
2148
+ }
2149
+
2150
+ // Stop.
2151
+ return;
2152
+ }
2153
+
2154
+ // No file watcher.
2155
+ else {
2156
+ // Create HTTPS server.
2157
+ if (this.tls) {
2158
+ this.https = http2.createSecureServer(
2159
+ {
2160
+ key: new vlib.Path(this.tls.key).load_sync({ encoding: 'utf8' }),
2161
+ cert: new vlib.Path(this.tls.cert).load_sync({ encoding: 'utf8' }),
2162
+ ca: this.tls.ca == null ? null : new vlib.Path(this.tls.ca).load_sync({ encoding: 'utf8' }),
2163
+ passphrase: this.tls.passphrase,
2164
+ allowHTTP1: true,
2165
+ },
2166
+ // Support for http1.
2167
+ // Does not work, requests get triggered on the stream and on this callback.
2168
+ (req: http2.Http2ServerRequest, res: http2.Http2ServerResponse) => {
2169
+ if (req.httpVersion.charAt(0) !== "2") {
2170
+ this._serve(undefined, undefined, req, res)
2171
+ }
2172
+ },
2173
+ );
2174
+ this.https.on('stream', (stream: http2.ServerHttp2Stream, headers: any) => {
2175
+ this._serve(stream, headers, undefined, undefined)
2176
+ });
2177
+ }
2178
+
2179
+ // Payments require HTTPS in production.
2180
+ else if (this.production && this.payments) {
2181
+ throw Error("Accepting payments in production mode requires HTTPS.");
2182
+ }
2183
+
2184
+ // Redirect HTTP requests to HTTPS.
2185
+ if (this.tls) {
2186
+ this.http = http.createServer((request: http.IncomingMessage, response: http.ServerResponse) => {
2187
+ response.writeHead(301, { Location: `https://${request.headers.host}${request.url}` });
2188
+ response.end();
2189
+ });
2190
+ } else {
2191
+ this.http = http.createServer((req: http.IncomingMessage, res: http.ServerResponse) => {
2192
+ this._serve(undefined, undefined, req, res)
2193
+ });
2194
+ }
2195
+
2196
+ /* @performance */ this.performance.end("create-http-server");
2197
+ }
2198
+
2199
+ // No file watcher.
2200
+ const file_watcher_restart = process.argv.includes("--file-watcher-restart");
2201
+
2202
+ // Start the database.
2203
+ if (this.db) {
2204
+ await this.db.initialize();
2205
+
2206
+ // Database collections.
2207
+ this._sys_db = this.db.create_collection("_sys"); // the volt sys collection.
2208
+
2209
+ // Accessible collections.
2210
+ this.storage = this.db.create_collection("_storage"); // the sys backend storage collection for the user's server.
2211
+
2212
+ /* @performance */ this.performance.end("init-db");
2213
+
2214
+ // Load keys.
2215
+ const keys_document = await this._sys_db.load("keys");
2216
+ const gen_user_crypto_key = (doc: Record<string, any>, key: string | {name: string, length: number}) => {
2217
+ if (typeof key === "string") {
2218
+ doc[key] = this.generate_crypto_key(32);
2219
+ } else {
2220
+ if (key.length == null) {
2221
+ throw Error(`Crypto key object "${JSON.stringify(key)}" does not contain a "length" attribute.`);
2222
+ }
2223
+ if (typeof key.length !== "number") {
2224
+ throw Error(`Crypto key object "${JSON.stringify(key)}" has an invalid type fo attribute "length", the valid type is "number".`);
2225
+ }
2226
+ if (key.name == null) {
2227
+ throw Error(`Crypto key object "${JSON.stringify(key)}" does not contain a "name" attribute.`);
2228
+ }
2229
+ if (typeof key.name !== "string") {
2230
+ throw Error(`Crypto key object "${JSON.stringify(key)}" has an invalid type fo attribute "name", the valid type is "string".`);
2231
+ }
2232
+ doc[key.name] = this.generate_crypto_key(key.length);
2233
+ this.keys[key.name] = doc[key.name];
2234
+ }
2235
+ }
2236
+ if (keys_document == null) {
2237
+ this._hash_key = this.generate_crypto_key(32);
2238
+ const doc: Record<string, string> = {
2239
+ _master_sha256: this._hash_key,
2240
+ };
2241
+ this._keys.forEach((key) => {
2242
+ gen_user_crypto_key(doc, key);
2243
+ })
2244
+ await this._sys_db.save("keys", doc);
2245
+ } else {
2246
+ // Check hash key.
2247
+ this._hash_key = keys_document._master_sha256;
2248
+ let perform_save = false;
2249
+ if (this._hash_key === undefined) {
2250
+ this._hash_key = this.generate_crypto_key(32);
2251
+ keys_document._master_sha256 = this._hash_key;
2252
+ perform_save = true;
2253
+ }
2254
+
2255
+ // Check crypto keys.
2256
+ this._keys.forEach((key) => {
2257
+ let name = typeof key === "string" ? key : key.name;
2258
+ if (keys_document[name] == null) {
2259
+ gen_user_crypto_key(keys_document, key);
2260
+ perform_save = true;
2261
+ }
2262
+ this.keys[name] = keys_document[name];
2263
+ })
2264
+
2265
+ // Save.
2266
+ if (perform_save) {
2267
+ await this._sys_db.save("keys", keys_document);
2268
+ }
2269
+ }
2270
+
2271
+ /* @performance */ this.performance.end("load-keys");
2272
+ }
2273
+
2274
+ // Initialize default headers.
2275
+ this._init_default_headers();
2276
+ /* @performance */ this.performance.end("init-default-headers");
2277
+
2278
+ // Create default endpoints.
2279
+ this._create_default_endpoints();
2280
+ /* @performance */ this.performance.end("create-default-endpoints");
2281
+
2282
+ // Create admin endpoints.
2283
+ this._create_admin_endpoint();
2284
+ /* @performance */ this.performance.end("create-admin-endpoints");
2285
+
2286
+ // Create static endpoints.
2287
+ await this._initialize_statics();
2288
+ /* @performance */ this.performance.end("create-static-endpoints");
2289
+
2290
+ // Initialize all endpoints.
2291
+ // Must be done after initializing static and default endpoints to support html embeddings.
2292
+ for (const endpoint of this.endpoints.values()) {
2293
+ await endpoint._initialize(this);
2294
+ }
2295
+ for (const endpoint of this.err_endpoints.values()) {
2296
+ await endpoint._initialize(this);
2297
+ }
2298
+ /* @performance */ this.performance.end("init-endpoints");
2299
+
2300
+ // Initialize users.
2301
+ if (this.db) {
2302
+ this.users._initialize();
2303
+ /* @performance */ this.performance.end("init-users");
2304
+ }
2305
+
2306
+ // Database preview endpoints (only when production mode is disabled).
2307
+ if (this.db) {
2308
+ this.db._initialize_db_preview();
2309
+ /* @performance */ this.performance.end("init-db-preview");
2310
+ }
2311
+
2312
+ // Payments.
2313
+ if (this.payments !== undefined) {
2314
+ await this.payments._initialize();
2315
+ }
2316
+ /* @performance */ this.performance.end("init-payments");
2317
+
2318
+ // Get the icon and stroke icon file paths when defined.
2319
+ if (this.company.stroke_icon || this.company.icon) {
2320
+ for (const endpoint of this.endpoints.values()) {
2321
+ if (this.company.stroke_icon_path == null && endpoint.endpoint === this.company.stroke_icon) {
2322
+ this.company.stroke_icon_path = endpoint._path ?? undefined;
2323
+ }
2324
+ if (this.company.icon_path == null && endpoint.endpoint === this.company.icon) {
2325
+ this.company.icon_path = endpoint._path ?? undefined;
2326
+ }
2327
+ }
2328
+ if (this.company.stroke_icon != null && this.company.stroke_icon_path == null) {
2329
+ throw Error(`Unable to find the company's stroke icon endpoint "${this.company.stroke_icon}".`);
2330
+ }
2331
+ if (this.company.icon != null && this.company.icon_path == null) {
2332
+ throw Error(`Unable to find the company's icon endpoint "${this.company.icon}".`);
2333
+ }
2334
+ }
2335
+
2336
+ /* @performance */ this.performance.end("init-icons");
2337
+
2338
+ // Create sitemap when it does not exist.
2339
+ // Must be done at the end of initialization func since some funcs might still create endpoints.
2340
+ if (this._find_endpoint("sitemap.xml") == null) {
2341
+ this._create_sitemap();
2342
+ }
2343
+ /* @performance */ this.performance.end("create-sitemap");
2344
+
2345
+ // Create robots.txt when it does not exist.
2346
+ // Must be done at the end of initialization func since some funcs might still create endpoints.
2347
+ if (this._find_endpoint("robots.txt") == null) {
2348
+ this._create_robots_txt();
2349
+ }
2350
+ /* @performance */ this.performance.end("create-robots.txt");
2351
+ }
2352
+
2353
+ // Serve a client.
2354
+ // @todo implement rate limiting.
2355
+ // @todo save internal server errors.
2356
+ async _serve(
2357
+ http2_stream?: http2.ServerHttp2Stream,
2358
+ headers?: Record<string, string>,
2359
+ req?: http.IncomingMessage | http2.Http2ServerRequest,
2360
+ res?: http.ServerResponse | http2.Http2ServerResponse
2361
+ ): Promise<void> {
2362
+ try {
2363
+
2364
+ // Convert stream.
2365
+ const stream = new Stream(http2_stream, headers, req, res);
2366
+
2367
+ // Vars.
2368
+ let endpoint: Endpoint | undefined;
2369
+ let method: string;
2370
+ let endpoint_url: string;
2371
+
2372
+ // Log endpoint result.
2373
+ const log_endpoint_result = (message: string | null = null, status: number | null = null) => {
2374
+ let log_level = 0;
2375
+ // if (endpoint) {
2376
+ // log_level = endpoint.is_static ? 1 : 0;
2377
+ // }
2378
+ if (status == null) {
2379
+ status = stream.status_code;
2380
+ }
2381
+ logger.log(log_level, log_source, `${method}:${endpoint_url}: ${message ? message : Status.get_description(status as number)} [${status}] (${stream.ip}).`);
2382
+ };
2383
+
2384
+ // Serve error endpoint.
2385
+ const serve_error_endpoint = async (status_code: number) => {
2386
+ // Get default response.
2387
+ const is_api_endpoint = endpoint && endpoint.callback != null;
2388
+ let default_response;
2389
+ switch (status_code) {
2390
+ case 400:
2391
+ default_response = {
2392
+ status: 400,
2393
+ headers: {"Content-Type": is_api_endpoint ? "application/json" : "text/plain"},
2394
+ data: is_api_endpoint ? {error: "Bad Request"} : "Bad Request",
2395
+ };
2396
+ break;
2397
+ case 403:
2398
+ default_response = {
2399
+ status: 403,
2400
+ headers: {"Content-Type": is_api_endpoint ? "application/json" : "text/plain"},
2401
+ data: is_api_endpoint ? {error: "Access Denied"} : "Access Denied",
2402
+ };
2403
+ break;
2404
+ case 404:
2405
+ default_response = {
2406
+ status: 404,
2407
+ headers: {"Content-Type": is_api_endpoint ? "application/json" : "text/plain"},
2408
+ data: is_api_endpoint ? {error: "Not Found"} : "Not Found",
2409
+ };
2410
+ break;
2411
+ case 500:
2412
+ default:
2413
+ default_response = {
2414
+ status: 500,
2415
+ headers: {"Content-Type": is_api_endpoint ? "application/json" : "text/plain"},
2416
+ data: is_api_endpoint ? {error: "Internal Server Error"} : "Internal Server Error",
2417
+ };
2418
+ break;
2419
+ }
2420
+
2421
+ // Serve error endpoint or default response.
2422
+ if (!this.err_endpoints.has(status_code)) {
2423
+ stream.send(default_response);
2424
+ } else {
2425
+ const err_endpoint = this.err_endpoints.get(status_code);
2426
+ if (err_endpoint) {
2427
+ try {
2428
+ await err_endpoint._serve(stream, status_code);
2429
+ } catch (err: any) {
2430
+ logger.error(log_source, `Error endpoint ${status_code}: `, err);
2431
+ stream.send(default_response);
2432
+ }
2433
+ }
2434
+ // @todo also serve something here.
2435
+ }
2436
+ };
2437
+
2438
+ // Check ip against blacklist.
2439
+ if (this.online && this.blacklist !== undefined && !this.blacklist.verify(stream.ip)) {
2440
+ await serve_error_endpoint(403);
2441
+ log_endpoint_result();
2442
+ return;
2443
+ }
2444
+
2445
+ // Check if the request matches any of the defined endpoints.
2446
+ method = stream.method;
2447
+ endpoint_url = stream.endpoint;
2448
+ endpoint = this._find_endpoint(endpoint_url, method);
2449
+
2450
+ // console.log(method, endpoint_url, endpoint)
2451
+
2452
+ // No endpoint found.
2453
+ if (!endpoint) {
2454
+ // Check OPTIONS request.
2455
+ if (method === "OPTIONS") {
2456
+ const original_method = stream.headers['access-control-request-method'];
2457
+ const original_endpoint = this._find_endpoint(endpoint_url, original_method);
2458
+ if (original_endpoint) {
2459
+ // Set headers.
2460
+ this._set_header_defaults(stream);
2461
+ original_endpoint._set_headers(stream);
2462
+
2463
+ // When any cors origin is allowed and origin is present then respond with that origin.
2464
+ if (stream.headers.origin && this.default_headers["Access-Control-Allow-Origin"] === "*") {
2465
+ stream.remove_header("Access-Control-Allow-Origin", "access-control-allow-origin");
2466
+ stream.set_header("Access-Control-Allow-Origin", stream.headers.origin);
2467
+ }
2468
+
2469
+ // Send.
2470
+ stream.send({status: Status.no_content});
2471
+ log_endpoint_result();
2472
+ return;
2473
+ }
2474
+ }
2475
+
2476
+ // Respond with 404.
2477
+ await serve_error_endpoint(404);
2478
+ log_endpoint_result();
2479
+ return;
2480
+ }
2481
+
2482
+ // Check rate limit.
2483
+ if (this.online && this.production && this.rate_limit !== undefined && endpoint.rate_limits.length > 0) {
2484
+ const result = await this.rate_limit.limit(stream.ip, endpoint.rate_limits);
2485
+ if (result != null) {
2486
+ stream.send({
2487
+ status: 429,
2488
+ headers: {
2489
+ "Content-Type": "text/plain",
2490
+ "X-RateLimit-Reset": result,
2491
+ },
2492
+ data: `Rate limit exceeded, please try again in ${Math.floor((result - Date.now()) / 1000)} seconds.`,
2493
+ });
2494
+ log_endpoint_result();
2495
+ return;
2496
+ }
2497
+ }
2498
+
2499
+ // Parse the request parameters.
2500
+ try {
2501
+ await stream.join();
2502
+ } catch (err: any) {
2503
+ logger.error(log_source, `${method}:${endpoint_url}: `, err);
2504
+ await serve_error_endpoint(500);
2505
+ log_endpoint_result();
2506
+ return;
2507
+ }
2508
+ try {
2509
+ stream._parse_params();
2510
+ } catch (err: any) {
2511
+ logger.error(log_source, `${method}:${endpoint_url}: `, err);
2512
+ await serve_error_endpoint(400);
2513
+ log_endpoint_result();
2514
+ return;
2515
+ }
2516
+
2517
+ // Set default headers.
2518
+ this._set_header_defaults(stream);
2519
+
2520
+ // When any cors origin is allowed and origin is present then respond with that origin.
2521
+ if (stream.headers.origin && this.default_headers["Access-Control-Allow-Origin"] === "*") {
2522
+ stream.remove_header("Access-Control-Allow-Origin", "access-control-allow-origin");
2523
+ stream.set_header("Access-Control-Allow-Origin", stream.headers.origin);
2524
+ }
2525
+
2526
+ // Do not authenticate on static endpoints, unless "authenticated" flag is somehow enabled.
2527
+ if (!endpoint.is_static || endpoint.authenticated) {
2528
+ // Always perform authentication so the stream.uid will also be assigned even when the endpoint is not authenticated.
2529
+ const auth_result = await this.users._authenticate(stream);
2530
+
2531
+ // Reset cookies when authentication has failed.
2532
+ if (auth_result != null && !endpoint.is_static) {
2533
+ this.users._reset_cookies(stream);
2534
+ }
2535
+
2536
+ // When the endpoint is authenticated and the authentication has failed then send the error response.
2537
+ if (auth_result != null && endpoint.authenticated) {
2538
+ stream.send(auth_result);
2539
+ log_endpoint_result();
2540
+ return;
2541
+ }
2542
+ }
2543
+
2544
+ // Serve endpoint.
2545
+ try {
2546
+ await endpoint._serve(stream);
2547
+ } catch (err: any) {
2548
+ logger.error(log_source, `${method}:${endpoint_url}: `, err);
2549
+ if (!stream.destroyed && !stream.closed) {
2550
+ await serve_error_endpoint(500);
2551
+ log_endpoint_result();
2552
+ }
2553
+ return;
2554
+ }
2555
+
2556
+ // Check if the response has been sent.
2557
+ if (!stream.finished) {
2558
+ logger.error(log_source, `${method}:${endpoint_url}: `, "Unfinished response.");
2559
+ await serve_error_endpoint(500);
2560
+ log_endpoint_result();
2561
+ return;
2562
+ }
2563
+
2564
+ // Log.
2565
+ log_endpoint_result();
2566
+ } catch (err: any) {
2567
+ logger.error(log_source, "Fatal error:", err);
2568
+ }
2569
+ }
2570
+
2571
+
2572
+ // ---------------------------------------------------------
2573
+ // Server.
2574
+
2575
+ // Start the server.
2576
+ /* @docs:
2577
+ * @title: Start
2578
+ * @description:
2579
+ * Start the server.
2580
+ * @usage:
2581
+ * ...
2582
+ * server.start();
2583
+ */
2584
+ async start(): Promise<void> {
2585
+
2586
+ // Always initialize, even when forking.
2587
+ await this.initialize();
2588
+
2589
+ // Inside file watcher process.
2590
+ if (this.is_file_watcher) {
2591
+ this.file_watcher!.start();
2592
+ await this.file_watcher!.promise;
2593
+ return ;
2594
+ }
2595
+
2596
+ // Start static file watcher.
2597
+ if (!this.production) {
2598
+ this.static_file_watcher.start();
2599
+ }
2600
+
2601
+ // Start the rate limiting client/server, also when forking.
2602
+ if (this.db) {
2603
+ /* @performance */ this.performance.start();
2604
+ if (this.rate_limit) {
2605
+ await this.rate_limit.start();
2606
+ }
2607
+ /* @performance */ this.performance.end("init-rate-limit");
2608
+ }
2609
+
2610
+ // Initialize, compile and bundle ts and js.
2611
+ await this._init_ts();
2612
+
2613
+ // Production & Master.
2614
+ let forked = false;
2615
+ if (this.production && this.multiprocessing && libcluster.isPrimary) {
2616
+
2617
+ // Vars.
2618
+ let active_threads = 0;
2619
+ const thread_ids: Record<string, string> = {};
2620
+ const restart_limiters: Record<string, vlib.TimeLimiter> = {};
2621
+
2622
+ // Start thread.
2623
+ const start_thread = (thread_id: string, restart = false) => {
2624
+ // Fork.
2625
+ const worker = libcluster.fork();
2626
+
2627
+ // Log.
2628
+ logger.log(restart ? 0 : 1, log_source, `Starting thread ${worker.process.pid}.`);
2629
+
2630
+ // Cache thread id.
2631
+ thread_ids[worker.process.pid!] = thread_id;
2632
+
2633
+ // Increment active threads.
2634
+ ++active_threads;
2635
+ };
2636
+
2637
+ // Fork workers.
2638
+ for (let i = 0; i < this.processes; i++) {
2639
+ // Generate thread id.
2640
+ let thread_id;
2641
+ while ((thread_id = String.random(8)) && Object.values(thread_ids).includes(thread_id)) {}
2642
+
2643
+ // Create limiter.
2644
+ restart_limiters[thread_id] = new vlib.TimeLimiter({ limit: 3, duration: 60 * 1000 });
2645
+
2646
+ // Start thread.
2647
+ start_thread(thread_id);
2648
+ }
2649
+
2650
+ // Save status.
2651
+ await this._sys_db.save("status", {
2652
+ running_since: Date.now(),
2653
+ total_threads: active_threads,
2654
+ running_threads: active_threads,
2655
+ });
2656
+
2657
+ // On exit.
2658
+ libcluster.addListener('exit', async (worker, code, signal) => {
2659
+ // Fetch thread id.
2660
+ const thread_id = thread_ids[worker.process.pid!];
2661
+ delete thread_ids[worker.process.pid!];
2662
+
2663
+ // Logs.
2664
+ logger.error(log_source, `Thread ${worker.process.pid} crashed.`);
2665
+
2666
+ // Restart with limit.
2667
+ const limiter = restart_limiters[thread_id];
2668
+ if (limiter != null && limiter.limit()) {
2669
+ --active_threads;
2670
+ start_thread(thread_id, true);
2671
+ }
2672
+ // Reached limit, shutdown thread.
2673
+ else {
2674
+ logger.error(log_source, `Thread ${worker.process.pid} is being shut down due too its periodic restart limit.`);
2675
+ --active_threads;
2676
+ await this._sys_db.save("status", { running_threads: active_threads });
2677
+ if (active_threads === 0) {
2678
+ logger.error(log_source, `All threads died, stopping server.`);
2679
+ process.exit(0);
2680
+ }
2681
+ }
2682
+ });
2683
+ } else {
2684
+ forked = this.production && this.multiprocessing;
2685
+
2686
+ // Set default port.
2687
+ let http_port: number, https_port: number;
2688
+ if (this.port == null) {
2689
+ http_port = 80;
2690
+ https_port = 443;
2691
+ } else {
2692
+ http_port = this.port;
2693
+ https_port = this.port + 1;
2694
+ }
2695
+
2696
+ // Callbacks.
2697
+ let is_running = false;
2698
+ const on_running = () => {
2699
+ if (!is_running) {
2700
+ is_running = true;
2701
+ if (this.https !== undefined) {
2702
+ logger.log(0, log_source, `Running on http://${this.ip}:${http_port} and https://${this.ip}:${https_port}.`);
2703
+ } else {
2704
+ logger.log(0, log_source, `Running on http://${this.ip}:${http_port}.`);
2705
+ }
2706
+ }
2707
+ };
2708
+ const on_error = (error: NodeJS.ErrnoException) => {
2709
+ if (error.syscall !== 'listen') {
2710
+ throw error;
2711
+ }
2712
+ switch (error.code) {
2713
+ case 'EACCES':
2714
+ console.error(`Error: Address ${this.ip}:${this.port} requires elevated privileges.`);
2715
+ process.exit(1);
2716
+ break;
2717
+ case 'EADDRINUSE':
2718
+ console.error(`Error: Address ${this.ip}:${this.port} is already in use.`);
2719
+ process.exit(1);
2720
+ break;
2721
+ default:
2722
+ throw error;
2723
+ }
2724
+ };
2725
+
2726
+ // Listen.
2727
+ this.http.listen(http_port, this.ip, on_running);
2728
+ this.http.on("error", on_error);
2729
+ if (this.https !== undefined) {
2730
+ this.https.listen(https_port, this.ip, on_running);
2731
+ this.https.on("error", on_error);
2732
+ }
2733
+
2734
+ // Set signals.
2735
+ process.on('SIGTERM', () => process.exit(0));
2736
+ process.on('SIGINT', () => process.exit(0));
2737
+
2738
+ // Send running message.
2739
+ if (process.env.VOLT_FILE_WATCHER === "1") {
2740
+ new vlib.Path(process.env.VOLT_STARTED_FILE as string).save_sync("1");
2741
+ }
2742
+
2743
+ // Start browser.
2744
+ // if (this.browser_preview) {
2745
+ // await this.browser_preview.start();
2746
+ // await this.browser_preview.navigate(this.full_domain);
2747
+ // }
2748
+
2749
+ /* @performance */ this.performance.end("listen");
2750
+ }
2751
+
2752
+ // On start callbacks.
2753
+ for (const callback of this._on_start) {
2754
+ const res = callback({ forked });
2755
+ if (res instanceof Promise) {
2756
+ await res;
2757
+ }
2758
+ }
2759
+
2760
+ // Start browser preview on primary node.
2761
+ if (this.browser_preview && !forked) {
2762
+ await this.browser_preview.start();
2763
+ await this.browser_preview.navigate(this.full_domain);
2764
+ }
2765
+
2766
+ // /* @performance */ this.performance.dump();
2767
+ }
2768
+
2769
+ /* @docs:
2770
+ * @title: On start
2771
+ * @description:
2772
+ * Set an (async) callback which will be executed at the end of `server.start()`.
2773
+ * The callback may take arguments `({forked <boolean>})`.
2774
+ * @usage:
2775
+ * ...
2776
+ * server.on_start(({forked}) => console.log("Hello World!"));
2777
+ */
2778
+ on_start(callback: ({ forked }: { forked: boolean }) => void | Promise<void>): void {
2779
+ this._on_start.append(callback);
2780
+ }
2781
+
2782
+ // Stop the server.
2783
+ /* @docs:
2784
+ * @title: Stop
2785
+ * @description:
2786
+ * Stop the server.
2787
+ * @usage:
2788
+ * ...
2789
+ * server.stop();
2790
+ */
2791
+ async stop(): Promise<void> {
2792
+ logger.log(0, log_source, "Stopping the server...");
2793
+
2794
+ // On stop callbacks.
2795
+ for (const callback of this._on_stop) {
2796
+ const res = callback();
2797
+ if (res instanceof Promise) {
2798
+ await res;
2799
+ }
2800
+ }
2801
+
2802
+ // Stop rate limit.
2803
+ if (this.rate_limit) {
2804
+ await this.rate_limit.stop();
2805
+ }
2806
+
2807
+ // Stop view source file watcher.
2808
+ if (this._stop_tscompiler_watcher) {
2809
+ logger.log(0, log_source, "Stopping typescript watcher.");
2810
+ this._stop_tscompiler_watcher();
2811
+ }
2812
+ if (this.static_file_watcher) {
2813
+ this.static_file_watcher.stop();
2814
+ }
2815
+
2816
+ // Stop sockets.
2817
+ if (this.https) {
2818
+ await this.https.close();
2819
+ }
2820
+ if (this.http) {
2821
+ await this.http.close();
2822
+ }
2823
+ if (this.db) {
2824
+ await this.db.close();
2825
+ }
2826
+
2827
+ // Stop the logger.
2828
+ logger.stop();
2829
+
2830
+ // setTimeout(() => {
2831
+ // thread_monitor.dump_active_resources({
2832
+ // // min_age: 5000,
2833
+ // // exclude_types: ['TIMERWRAP'],
2834
+ // include_internal: false
2835
+ // });
2836
+ // }, 6000);
2837
+
2838
+
2839
+
2840
+
2841
+ }
2842
+
2843
+ /* @docs:
2844
+ * @title: On stop
2845
+ * @description:
2846
+ * Set an (async) callback which will be executed at the start of `server.stop()`.
2847
+ * @usage:
2848
+ * ...
2849
+ * server.on_stop(() => console.log("Hello World!"));
2850
+ */
2851
+ on_stop(callback: () => void | Promise<void>): void {
2852
+ this._on_stop.append(callback);
2853
+ }
2854
+
2855
+ // Fetch status.
2856
+ /* @docs:
2857
+ @title: Fetch status.
2858
+ @desc: 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.
2859
+ @note: This function can be called without initializing the server.
2860
+ @param:
2861
+ @name: type
2862
+ @desc: The wanted output type. Either an `object` or a `string` type for CLI purposes.
2863
+ */
2864
+ async fetch_status(type: "object" | "string" = "object"): Promise<string | Record<string, any>> {
2865
+
2866
+ // Load key.
2867
+ const key_path = this.source.join(".status/key");
2868
+ if (!key_path.exists()) {
2869
+ throw new Error("No status key has been generated yet. Start your server first.");
2870
+ }
2871
+ const key = key_path.load_sync();
2872
+
2873
+ // Make request.
2874
+ const { body: status } = await vlib.request({
2875
+ host: this.domain,
2876
+ endpoint: "/.status",
2877
+ method: "GET",
2878
+ params: { key },
2879
+ query: true,
2880
+ json: true,
2881
+ });
2882
+
2883
+ // String type.
2884
+ if (type === "string") {
2885
+ if (status.running_since != null) {
2886
+ status.running_since = new vlib.Date(status.running_since).format("%d-%m-%y %H:%M:%S");
2887
+ }
2888
+ let str = `${this.domain}:\n`;
2889
+ Object.keys(status).forEach((key) => {
2890
+ str += ` * ${key}: ${status[key]}\n`;
2891
+ });
2892
+ str = str.substr(0, str.length - 1);
2893
+ return str;
2894
+ }
2895
+
2896
+ // Response.
2897
+ return status;
2898
+ }
2899
+
2900
+ // ---------------------------------------------------------
2901
+ // Content Security Policy.
2902
+
2903
+ // Add a csp.
2904
+ /* @docs:
2905
+ * @title: Add CSP
2906
+ * @description: Add an url to the Content-Security-Policy. This function does not overwrite the existing key's value.
2907
+ * @warning: This function no longer has any effect when `Server.start()` has been called.
2908
+ * @parameter:
2909
+ * @name: key
2910
+ * @description: The Content-Security-Policy key, e.g. `script-src`.
2911
+ * @type: string
2912
+ * @parameter:
2913
+ * @name: value
2914
+ * @description: The value to add to the Content-Security-Policy key.
2915
+ * @type: null, string, string[]
2916
+ * @usage:
2917
+ * ...
2918
+ * server.add_csp("script-src", "somewebsite.com");
2919
+ * server.add_csp("upgrade-insecure-requests");
2920
+ */
2921
+ add_csp(key: string, value: null | string | string[] = null): void {
2922
+ if (this.csp[key] === undefined) {
2923
+ this.csp[key] = "";
2924
+ }
2925
+ if (Array.isArray(value)) {
2926
+ value.forEach((v: string) => {
2927
+ if (typeof v === "string" && v.length > 0) {
2928
+ this.csp[key] += " " + v.trim();
2929
+ }
2930
+ });
2931
+ } else if (typeof value === "string" && value.length > 0) {
2932
+ this.csp[key] += " " + value.trim();
2933
+ }
2934
+ }
2935
+
2936
+ // Remove a csp.
2937
+ /* @docs:
2938
+ * @title: Remove CSP
2939
+ * @description: Remove an url from the Content-Security-Policy. This function does not overwrite the existing key's value.
2940
+ * @warning: This function no longer has any effect when `Server.start()` has been called.
2941
+ * @parameter:
2942
+ * @name: key
2943
+ * @description: The Content-Security-Policy key, e.g. `script-src`.
2944
+ * @type: string
2945
+ * @parameter:
2946
+ * @name: value
2947
+ * @description: The value to remove from the Content-Security-Policy key.
2948
+ * @type: null, string
2949
+ * @usage:
2950
+ * ...
2951
+ * server.remove_csp("script-src", "somewebsite.com");
2952
+ * server.remove_csp("upgrade-insecure-requests");
2953
+ */
2954
+ remove_csp(key: string, value: null | string = null): void {
2955
+ if (this.csp[key] === undefined) {
2956
+ return;
2957
+ }
2958
+ if (typeof value === "string" && value.length > 0) {
2959
+ this.csp[key] = this.csp[key].replaceAll(value, "");
2960
+ } else {
2961
+ delete this.csp[key];
2962
+ }
2963
+ }
2964
+
2965
+ // Delete a csp key.
2966
+ /* @docs:
2967
+ * @title: Delete CSP
2968
+ * @description: Delete an key from the Content-Security-Policy.
2969
+ * @warning: This function no longer has any effect when `Server.start()` has been called.
2970
+ * @parameter:
2971
+ * @name: key
2972
+ * @description: The Content-Security-Policy key, e.g. `script-src`.
2973
+ * @type: string
2974
+ * @usage:
2975
+ * ...
2976
+ * server.del_csp("script-src");
2977
+ * server.del_csp("upgrade-insecure-requests");
2978
+ */
2979
+ del_csp(key: string): void {
2980
+ delete this.csp[key];
2981
+ }
2982
+
2983
+ // ---------------------------------------------------------
2984
+ // TLS.
2985
+
2986
+ // Generate a key and csr for tls.
2987
+ async generate_tls_key({path, organization_unit = "IT", ec = true}: {path: string; organization_unit?: string; ec?: boolean}): Promise<void> {
2988
+ // Args.
2989
+ if (path == null) {
2990
+ throw Error("Define parameter \"path\".");
2991
+ }
2992
+ if (organization_unit == null) {
2993
+ throw Error("Define parameter \"organization_unit\".");
2994
+ }
2995
+
2996
+ // Paths.
2997
+ const vpath: vlib.Path = new vlib.Path(path);
2998
+ const key = vpath.join("key.key");
2999
+ const csr = vpath.join("csr.csr");
3000
+ if (!vpath.exists()) {
3001
+ vpath.mkdir_sync();
3002
+ }
3003
+ if (key.exists()) {
3004
+ throw Error(`Key path "${key.str()}" already exists, remove the file manually to continue.`);
3005
+ }
3006
+ if (csr.exists()) {
3007
+ throw Error(`CSR path "${csr.str()}" already exists, remove the file manually to continue.`);
3008
+ }
3009
+
3010
+ // Generate the private key using the EC parameters file
3011
+ const proc = new vlib.Proc();
3012
+ await proc.start({
3013
+ command: "openssl",
3014
+ args: ec
3015
+ ? ["ecparam", "-genkey", "-name", "secp384r1", "-out", key.str()]
3016
+ : ["genpkey", "-algorithm", "RSA", "-pkeyopt", "rsa_keygen_bits:2048", "-out", key.str()]
3017
+ ,
3018
+ opts: { stdio: "inherit" },
3019
+ });
3020
+ if (proc.exit_status != 0) {
3021
+ throw Error(`Encountered an error while generating the private key [${proc.exit_status}]: ${proc.err}`);
3022
+ }
3023
+
3024
+ // Generate the CSR using the generated private key
3025
+ await proc.start({
3026
+ command: "openssl",
3027
+ args: [
3028
+ "req", "-new", "-key", key.str(), "-out", csr.str(),
3029
+ "-subj",
3030
+ "\"" +
3031
+ "/C=" + this.company.country_code +
3032
+ "/ST=" + this.company.province +
3033
+ "/L=" + this.company.city +
3034
+ "/O=" + this.company.name +
3035
+ "/OU=" + organization_unit +
3036
+ "/CN=" + this.domain +
3037
+ "\""
3038
+ ],
3039
+ opts: { stdio: "inherit" },
3040
+ });
3041
+ if (proc.exit_status != 0) {
3042
+ throw Error(`Encountered an error while generating the CSR [${proc.exit_status}]: ${proc.err}`);
3043
+ }
3044
+ logger.log(0, log_source, `Generated the tls key with CSR for domain "${this.domain}".`);
3045
+ }
3046
+
3047
+ // ---------------------------------------------------------
3048
+ // Endpoints.
3049
+
3050
+ // Add one or multiple endpoints.
3051
+ /* @docs:
3052
+ @title: Add endpoint(s)
3053
+ @description: Add one or multiple endpoints.
3054
+ @parameter:
3055
+ @name: ...endpoints
3056
+ @description:
3057
+ The endpoint parameters.
3058
+
3059
+ An endpoint parameter can either be a `Endpoint` class or an `object` with the `Endpoint` arguments.
3060
+ @type: Endpoint, object
3061
+ */
3062
+ endpoint(...endpoints: (none | Endpoint | EndpointOptions | (none | EndpointOptions | Endpoint)[])[]): this {
3063
+ for (let i = 0; i < endpoints.length; i++) {
3064
+ let init_endpoint = endpoints[i];
3065
+
3066
+ // Skip.
3067
+ if (init_endpoint == null) {
3068
+ continue;
3069
+ }
3070
+
3071
+ // Is array of endpoints.
3072
+ if (Array.isArray(init_endpoint)) {
3073
+ this.endpoint(...init_endpoint);
3074
+ continue;
3075
+ }
3076
+
3077
+ // Initialize endpoint.
3078
+ if (!(init_endpoint instanceof Endpoint)) {
3079
+ init_endpoint = new Endpoint(init_endpoint);
3080
+ }
3081
+ const endpoint = init_endpoint as Endpoint;
3082
+
3083
+ // Build view.
3084
+ if (endpoint.view != null) {
3085
+ if (endpoint.view.meta == null) {
3086
+ endpoint.view.meta = this.meta.copy();
3087
+ } else if (typeof endpoint.view.meta === "object" && !(endpoint.view.meta instanceof Meta)) {
3088
+ endpoint.view.meta = new Meta(endpoint.view.meta);
3089
+ }
3090
+ }
3091
+
3092
+ // Add endpoint.
3093
+ this.endpoints.set(`${endpoint.endpoint}:${endpoint.method}`, endpoint);
3094
+ if (!this.production) {
3095
+ if (endpoint._path && this.file_watcher?.add_exclude) {
3096
+ this.file_watcher!.add_exclude(endpoint._path);
3097
+ }
3098
+ this.static_file_watcher.add(endpoint);
3099
+ }
3100
+ }
3101
+ return this;
3102
+ }
3103
+
3104
+ // Add an error endpoint.
3105
+ /* @docs:
3106
+ @title: Add error endpoint
3107
+ @description:
3108
+ Add an endpoint per error status code.
3109
+ @parameter:
3110
+ @name: status_code
3111
+ @type: number
3112
+ @description:
3113
+ The status code of the error.
3114
+
3115
+ The supported status codes are:
3116
+ * `404`
3117
+ * `400` (Will not be used when the endpoint uses an API callback).
3118
+ * `403`
3119
+ * `404`
3120
+ * `500`
3121
+ @parameter:
3122
+ @name: endpoint
3123
+ @description:
3124
+ The endpoint parameters.
3125
+
3126
+ An endpoint parameter can either be a `Endpoint` class or an `object` with the `Endpoint` arguments.
3127
+ @type: Endpoint, object
3128
+ */
3129
+ error_endpoint(status_code: number, endpoint: Endpoint | EndpointOptions): this {
3130
+ this.err_endpoints.set(
3131
+ status_code,
3132
+ endpoint instanceof Endpoint ? endpoint : new Endpoint(endpoint)
3133
+ );
3134
+ return this;
3135
+ }
3136
+
3137
+ // ---------------------------------------------------------
3138
+ // Functions.
3139
+
3140
+ // Send a mail.
3141
+ /* @docs:
3142
+ * @title: Send Mail
3143
+ * @description: Send one or multiple mails.
3144
+ * @note: Make sure the domain's DNS records SPF and DKIM are properly configured when sending attachments.
3145
+ * @return:
3146
+ * Returns a promise that will be resolved or rejected when the mail has been sent.
3147
+ * @parameter:
3148
+ * @name: sender
3149
+ * @description:
3150
+ * The sender address.
3151
+ * A sender address may either be a string with the email address, e.g. `your@email.com`.
3152
+ * Or an array with the sender name and email address, e.g. `["Sender", "your@email.com"]`.
3153
+ * @type: string, array
3154
+ * @parameter:
3155
+ * @name: recipients
3156
+ * @description:
3157
+ * The recipient addresses.
3158
+ * A reciepient address may either be a string with the email address, e.g. `your@email.com`.
3159
+ * Or an array with the sender name and email address, e.g. `["Sender", "your@email.com"]`.
3160
+ * @type: array[string, array]
3161
+ * @parameter:
3162
+ * @name: subject
3163
+ * @description: The subject text.
3164
+ * @type: string
3165
+ * @parameter:
3166
+ * @name: body
3167
+ * @description: The body text.
3168
+ * @type: string
3169
+ * @parameter:
3170
+ * @name: attachments
3171
+ * @description: An array with absolute file paths for attachments, or an array with nodemailer attachment objects.
3172
+ * @type: array[string], array[object]
3173
+ * @usage:
3174
+ * ...
3175
+ * await server.send_mail({
3176
+ * sender: ["Sender Name", "sender\@email.com"],
3177
+ * recipients: [
3178
+ * ["Recipient Name", "recipient1\@email.com"],
3179
+ * "recipient2\@email.com",
3180
+ * },
3181
+ * subject: "Example Mail",
3182
+ * body: "Hello World!",
3183
+ * attachments: ["/path/to/image.png"]
3184
+ * });
3185
+ */
3186
+ async send_mail({
3187
+ sender = undefined,
3188
+ recipients = [],
3189
+ subject = undefined,
3190
+ body = "",
3191
+ attachments = [],
3192
+ }: {
3193
+ sender?: string | [string, string];
3194
+ recipients?: (string | [string, string])[];
3195
+ subject?: string;
3196
+ body?: string | Mail.MailElement;
3197
+ attachments?: (string | vlib.Path | MailAttachment)[];
3198
+ }): Promise<void> {
3199
+
3200
+ // Not enabled.
3201
+ if (this.smtp === undefined) {
3202
+ throw new Error("SMTP is not enabled, define the required server argument on initialization to enable smtp.");
3203
+ }
3204
+
3205
+ // Convert MailElement to html.
3206
+ if (body instanceof Mail.MailElement) {
3207
+ body = body.html();
3208
+ }
3209
+
3210
+ // Check args.
3211
+ if (sender == null && this.smtp_sender != null) {
3212
+ sender = this.smtp_sender;
3213
+ }
3214
+ if (recipients.length === 0) {
3215
+ throw new Error(`The mail has no recipients.`);
3216
+ }
3217
+ if (sender == null) {
3218
+ throw new Error(`Parameter "sender" should be a defined value of type "string" or "array".`);
3219
+ }
3220
+
3221
+ // Format address wrapper.
3222
+ const format_address = (address: string | [string, string]): string => {
3223
+ if (Array.isArray(address)) {
3224
+ return `${address[0]} <${address[1]}>`;
3225
+ }
3226
+ return address;
3227
+ };
3228
+
3229
+ // Create to array.
3230
+ const to: string[] = [];
3231
+ recipients.forEach((address) => to.push(format_address(address)));
3232
+
3233
+ // Create attachments array.
3234
+ let attached_files: MailAttachment[] = [];
3235
+ if (attachments != null) {
3236
+ attachments.forEach((path: string | vlib.Path | MailAttachment) => {
3237
+ if (path instanceof vlib.Path) {
3238
+ attached_files.push({
3239
+ filename: path.name(),
3240
+ path: path.str(),
3241
+ content: path.load_sync(),
3242
+ });
3243
+ } else if (typeof path === "string") {
3244
+ const p = new vlib.Path(path);
3245
+ attached_files.push({
3246
+ filename: p.name(),
3247
+ path: path,
3248
+ content: p.load_sync(),
3249
+ });
3250
+ } else {
3251
+ attached_files.push(path as MailAttachment);
3252
+ }
3253
+ });
3254
+ }
3255
+
3256
+ // Send mail.
3257
+ try {
3258
+ await this.smtp.sendMail({
3259
+ from: format_address(sender),
3260
+ to: to,
3261
+ subject: subject,
3262
+ html: body,
3263
+ attachments: attached_files,
3264
+ });
3265
+ } catch (error: any) {
3266
+ throw new Error(error.message); // to keep readable stacktrace.
3267
+ }
3268
+ }
3269
+
3270
+ // ---------------------------------------------------------
3271
+ // Default callbacks.
3272
+ // These can all be overwritten by the user.
3273
+ // @todo add scheme for payment params.
3274
+
3275
+ // On delete user.
3276
+ /* @docs:
3277
+ * @title: On delete user
3278
+ * @description: This function can be overridden with a callback for when a user is deleted.
3279
+ * @parameter:
3280
+ * @name: uid
3281
+ * @description: The uid of the deleted user.
3282
+ * @type: string, array
3283
+ * @usage:
3284
+ * ...
3285
+ * server.on_delete_user = ({uid}) => {}
3286
+ */
3287
+ async on_delete_user({ uid }: { uid: string | string[] }): Promise<void> {}
3288
+
3289
+ // On successfull one-time payment.
3290
+ // This gets called for every product in the payment.
3291
+ async on_payment({ product, payment }: { product: any; payment: any }): Promise<void> {}
3292
+
3293
+ // On successfull subscription.
3294
+ // This gets called for every product in the payment.
3295
+ async on_subscription({ product, payment }: { product: any; payment: any }): Promise<void> {}
3296
+
3297
+ // On failed one-time or recurring payment.
3298
+ // async on_failed_payment({ payment }: { payment: any }): Promise<void> {}
3299
+
3300
+ // On successfull cancellation.
3301
+ async on_cancellation({ payment, line_items }: { payment: any; line_items: any[] }): Promise<void> {}
3302
+
3303
+ // On failed cancellation.
3304
+ // async on_failed_cancellation({ payment, line_items }: { payment: any; line_items: any[] }): Promise<void> {}
3305
+
3306
+ // On successfull refund.
3307
+ // The line items array are the items were refunded.
3308
+ async on_refund({ payment, line_items }: { payment: any; line_items: any[] }): Promise<void> {}
3309
+
3310
+ // On failed refund.
3311
+ // The line items array are the items were the refund failed.
3312
+ async on_failed_refund({ payment, line_items }: { payment: any; line_items: any[] }): Promise<void> {}
3313
+
3314
+ // On chargeback.
3315
+ // The line items array are the items were charged back.
3316
+ async on_chargeback({ payment, line_items }: { payment: any; line_items: any[] }): Promise<void> {}
3317
+
3318
+ // On failed chargeback.
3319
+ // The line items array are the items were the chargeback failed.
3320
+ async on_failed_chargeback({ payment, line_items }: { payment: any; line_items: any[] }): Promise<void> {}
3321
+
3322
+ // Mail template.
3323
+ _mail_template({
3324
+ max_width = 400,
3325
+ children = [],
3326
+ }: {
3327
+ max_width?: number;
3328
+ children?: any[];
3329
+ }): any {
3330
+ const style = this.mail_style;
3331
+ const { Title, Text, Image, Table, TableRow, TableData, VStack } = Mail;
3332
+
3333
+ // Create header.
3334
+ let header;
3335
+ if (this.company.stroke_icon != null) {
3336
+ header = [
3337
+ Image(`${this.full_domain}/${this.company.stroke_icon}`).height(16),
3338
+ ];
3339
+ } else if (this.company.icon != null) {
3340
+ header = [
3341
+ Image(`${this.full_domain}/${this.company.icon}`).frame(20, 40),
3342
+ ];
3343
+ }
3344
+ if (header) {
3345
+ header = Table(
3346
+ TableRow(...header)
3347
+ .wrap(true)
3348
+ .center()
3349
+ .center_vertical()
3350
+ ).margin_bottom(15);
3351
+ }
3352
+
3353
+ // Create mail.
3354
+ return Mail.Mail(
3355
+ Table(
3356
+ TableData(
3357
+ Table(
3358
+ // Header.
3359
+ header,
3360
+
3361
+ // Widget.
3362
+ Table(...children)
3363
+ .background_color(style.widget_bg ?? "")
3364
+ .border(`1px solid ${style.widget_border ?? ""}`)
3365
+ .border_radius("10px")
3366
+ .padding(40, 25, 25, 25)
3367
+ .margin(0),
3368
+
3369
+ // Copyright.
3370
+ Table(
3371
+ TableRow(
3372
+ Text(
3373
+ `Copyright © ${new Date().getFullYear()} ${this.company.name}, ${this.company.legal_name} All Rights Included.\n` +
3374
+ `${this.company.street} ${this.company.house_number}, ${this.company.postal_code}, ${this.company.city}, ${this.company.province}, ${this.company.country}.\n` +
3375
+ (this.company.tax_id == null ? "" : `VAT ID ${this.company.tax_id}`)
3376
+ )
3377
+ .white_space("pre")
3378
+ .display("inline-block")
3379
+ .font_size(11)
3380
+ .color(style.footer_fg)
3381
+ .margin(0)
3382
+ ).center().center_vertical(),
3383
+ ).margin(0, 0, 10, 0)
3384
+ ).max_width(max_width)
3385
+ ).center()
3386
+ ).padding(25, 20, 25, 20)
3387
+ ).font_family(style.font).background(style.bg);
3388
+ }
3389
+
3390
+ // Render payment line items.
3391
+ _render_mail_payment_line_items({ payment, line_items, show_total_due = false }: {
3392
+ payment: any;
3393
+ line_items: any[];
3394
+ show_total_due?: boolean;
3395
+ }): any[] {
3396
+ // Shortcuts.
3397
+ const style = this.mail_style;
3398
+ const { Title, Text, Image, Table, TableRow, TableData, VStack } = Mail;
3399
+
3400
+ // Render payment line item for a mail.
3401
+ const _render_mail_payment_line_item = ({
3402
+ name,
3403
+ desc,
3404
+ unit_cost,
3405
+ quantity,
3406
+ total_cost,
3407
+ font_weight = "normal",
3408
+ divider = true,
3409
+ color = style.text_fg,
3410
+ }: {
3411
+ name: string;
3412
+ desc: string;
3413
+ unit_cost: string;
3414
+ quantity: string;
3415
+ total_cost: string;
3416
+ font_weight?: string;
3417
+ divider?: boolean;
3418
+ color?: string;
3419
+ }): any[] => {
3420
+ return [
3421
+ Table(
3422
+ TableRow(
3423
+ TableData(
3424
+ Text(name)
3425
+ .color(color)
3426
+ .font_size(14)
3427
+ .text_wrap("wrap")
3428
+ .overflow_wrap("break-word")
3429
+ .word_wrap("break-word")
3430
+ .font_weight(font_weight)
3431
+ ).width("25%").margin_right(10),
3432
+ TableData(
3433
+ Text(desc)
3434
+ .color(color)
3435
+ .font_size(14)
3436
+ .text_wrap("wrap")
3437
+ .overflow_wrap("break-word")
3438
+ .word_wrap("break-word")
3439
+ .font_weight(font_weight)
3440
+ ).width("35%").margin_right(10),
3441
+ TableData(
3442
+ Text(unit_cost)
3443
+ .color(color)
3444
+ .font_size(14)
3445
+ .text_wrap("wrap")
3446
+ .overflow_wrap("break-word")
3447
+ .word_wrap("break-word")
3448
+ .font_weight(font_weight)
3449
+ ).fixed_width("13.32%").margin_right(10),
3450
+ TableData(
3451
+ Text(quantity)
3452
+ .color(color)
3453
+ .font_size(14)
3454
+ .text_wrap("wrap")
3455
+ .overflow_wrap("break-word")
3456
+ .word_wrap("break-word")
3457
+ .font_weight(font_weight)
3458
+ ).fixed_width("13.32%").margin_right(10),
3459
+ TableData(
3460
+ Text(total_cost)
3461
+ .color(color)
3462
+ .font_size(14)
3463
+ .text_wrap("wrap")
3464
+ .overflow_wrap("break-word")
3465
+ .word_wrap("break-word")
3466
+ .font_weight(font_weight)
3467
+ ).fixed_width("13.32%"),
3468
+ ).width("100%").styles({ "vertical-align": "baseline" }),
3469
+ ).width("100%"),
3470
+
3471
+ !divider
3472
+ ? null
3473
+ : TableRow(
3474
+ TableData(
3475
+ VStack()
3476
+ .background_color(style.text_fg)
3477
+ .frame("100%", 1)
3478
+ .margin(5, 0, 10, 0)
3479
+ ).frame("100%", 1)
3480
+ ).width("100%"),
3481
+ ];
3482
+ };
3483
+
3484
+ // Render a divider.
3485
+ const render_divider = (): any => {
3486
+ return TableRow(
3487
+ TableData(
3488
+ VStack()
3489
+ .background_color(style.divider_bg)
3490
+ .frame("100%", 1)
3491
+ .margin(5, 0, 10, 0)
3492
+ ).frame("100%", 1)
3493
+ ).width("100%");
3494
+ };
3495
+
3496
+ // Vars.
3497
+ let currency: string | undefined;
3498
+ let subtotal = 0;
3499
+ let subtotal_tax = 0;
3500
+ let total = 0;
3501
+ payment.line_items.iterate((item: any) => {
3502
+ if (typeof item.product === "string") {
3503
+ item.product = this.payments.get_product_sync(item.product);
3504
+ }
3505
+ if (currency == null) {
3506
+ const c = Utils.get_currency_symbol(item.product.currency);
3507
+ if (c == null) {
3508
+ logger.error(log_source, `Failed to create a payment mail: `, new Error(`Unable to determine the currency of payment "${payment.id}".`));
3509
+ }
3510
+ currency = c ?? "?";
3511
+ }
3512
+ subtotal += item.subtotal;
3513
+ subtotal_tax += item.tax;
3514
+ total += item.total;
3515
+ });
3516
+ let total_due = payment.status === "open" ? total : 0;
3517
+
3518
+ return [
3519
+ render_divider(),
3520
+ line_items.iterate_append((item: any, index: number) => {
3521
+ return Table(
3522
+ TableRow(
3523
+ TableData(
3524
+ Image(item.product.icon)
3525
+ .frame(35, 35)
3526
+ .margin_right(15)
3527
+ ).width("auto"),
3528
+ TableData(
3529
+ Table(
3530
+ Text(item.product.name)
3531
+ .color(style.title_fg)
3532
+ .font_size(14)
3533
+ .font_weight("bold")
3534
+ .margin(0)
3535
+ .ellipsis_overflow(true),
3536
+ Text(item.product.description)
3537
+ .color(style.text_fg)
3538
+ .font_size(14)
3539
+ .margin(0)
3540
+ .ellipsis_overflow(true)
3541
+ )
3542
+ ).width("100%"),
3543
+ TableData(
3544
+ Text(`${currency} ${item.subtotal.toFixed(2)}`)
3545
+ .color(style.title_fg)
3546
+ .font_size(14)
3547
+ .font_weight("bold")
3548
+ .margin(0)
3549
+ .white_space("nowrap")
3550
+ ).width("100%")
3551
+ ).wrap(true).leading_vertical().width("100%")
3552
+ ).width("100%");
3553
+ }),
3554
+ render_divider(),
3555
+ Table(
3556
+ [
3557
+ ["Subtotal:", `${currency} ${subtotal.toFixed(2)}`],
3558
+ ["Tax:", `${currency} ${subtotal_tax.toFixed(2)}`],
3559
+ ["Total:", `${currency} ${total.toFixed(2)}`],
3560
+ ].iterate_append((item: string[]) => {
3561
+ return TableRow(
3562
+ TableData().width("100%"),
3563
+ TableData(
3564
+ Text(item[0])
3565
+ .color(style.title_fg)
3566
+ .font_size(14)
3567
+ .ellipsis_overflow(true)
3568
+ .font_weight("bold")
3569
+ ).min_width(75),
3570
+ TableData(
3571
+ Text(item[1])
3572
+ .color(style.title_fg)
3573
+ .font_size(14)
3574
+ .white_space("nowrap")
3575
+ .font_weight("bold")
3576
+ )
3577
+ // .min_width(50)
3578
+ ).wrap(true);
3579
+ // .text_align("right")
3580
+ })
3581
+ ),
3582
+ ];
3583
+ }
3584
+
3585
+ // On 2fa mail.
3586
+ on_2fa_mail({ code, username, email, date, ip, device }: { code: string; username: string; email: string; date: string; ip: string; device: string }): any {
3587
+ const style = this.mail_style;
3588
+ const { Title, Text, Image, Table, TableRow, TableData, VStack } = Mail;
3589
+ return this._mail_template({
3590
+ max_width: 400,
3591
+ children: [
3592
+
3593
+ // Title.
3594
+ TableRow(
3595
+ Title("Verification Required")
3596
+ .color(style.title_fg)
3597
+ .width("fit-content")
3598
+ .font_size(26)
3599
+ ).center(),
3600
+
3601
+ // Text.
3602
+ TableRow(
3603
+ Text("Please confirm your request with this 2FA code.")
3604
+ .center()
3605
+ .margin(10, 0, 20, 0)
3606
+ .color(style.text_fg)
3607
+ .font_size(18)
3608
+ ),
3609
+
3610
+ // Auth info.
3611
+ [
3612
+ ["Username", username],
3613
+ ["Email", email],
3614
+ ["Date", date],
3615
+ ["Ip Address", ip],
3616
+ ["Device", device],
3617
+ ].iterate_append((item: string[]) => {
3618
+ return [
3619
+
3620
+ TableRow(
3621
+ VStack()
3622
+ .margin_right(7.5)
3623
+ // .background("linear-gradient(135deg, #4830C4, #6E399E, #421959)")
3624
+ .background_color(style.text_fg)
3625
+ .border_radius("50%")
3626
+ .frame(5, 5),
3627
+ Text(`<span style='font-weight: 600'>${item[0]}:</span> ${item[1]}`)
3628
+ .color(style.text_fg)
3629
+ .font_size(16)
3630
+ .text_wrap("wrap")
3631
+ .overflow_wrap("break-word")
3632
+ .word_wrap("break-word"),
3633
+ ).wrap(true).center_vertical(),
3634
+
3635
+ TableRow().fixed_frame(5, 5),
3636
+ ];
3637
+ }),
3638
+
3639
+ // 2FA code.
3640
+ TableRow(
3641
+ Text(code)
3642
+ .background(style.button_bg)
3643
+ .border_radius("10px")
3644
+ .padding(10, 15)
3645
+ .center()
3646
+ .color(style.button_fg)
3647
+ .width("100%")
3648
+ .margin(20, 0, 0, 0)
3649
+ ),
3650
+
3651
+ // Text.
3652
+ TableRow(
3653
+ Text("This 2FA code will be valid for 5 minutes.")
3654
+ .color(style.text_fg)
3655
+ .font_style("italic")
3656
+ .font_size(12)
3657
+ .margin_top(20)
3658
+ .center(),
3659
+ ),
3660
+ ],
3661
+ });
3662
+ }
3663
+
3664
+ // On successfull payment mail.
3665
+ on_payment_mail({ payment }: { payment: any }): any {
3666
+
3667
+ // Shortcuts.
3668
+ const style = this.mail_style;
3669
+ const { Title, Text, Image, Table, TableRow, TableData, VStack } = Mail;
3670
+
3671
+ // Create mail.
3672
+ return this._mail_template({
3673
+ max_width: 600,
3674
+ children: [
3675
+
3676
+ // Title.
3677
+ TableRow(
3678
+ Title("Successful Payment")
3679
+ .color(style.title_fg)
3680
+ .width("fit-content")
3681
+ .font_size(26)
3682
+ ).center(),
3683
+
3684
+ // Text.
3685
+ TableRow(
3686
+ Text("We're delighted to inform you that your payment has been successfully processed. Thank you for your purchase.")
3687
+ .margin(10, 0, 20, 0)
3688
+ .color(style.text_fg)
3689
+ .font_size(16)
3690
+ .center()
3691
+ ),
3692
+
3693
+ // Image.
3694
+ TableRow(
3695
+ Image(`${this.full_domain}/volt_static/payments/party.png`)
3696
+ .frame(60, 60)
3697
+ .margin(0, 0, 30, 0)
3698
+ ).center(),
3699
+
3700
+ // Title.
3701
+ TableRow(
3702
+ Title("Order Summary")
3703
+ .color(style.subtitle_fg)
3704
+ .font_size(18)
3705
+ .margin(0)
3706
+ ),
3707
+ TableRow(
3708
+ Text("A summary of your order can be found below or in the attachmed invoice pdf.")
3709
+ .margin(5, 0, 20, 0)
3710
+ .color(style.text_fg)
3711
+ .font_size(16)
3712
+ ),
3713
+
3714
+ // Line items.
3715
+ this._render_mail_payment_line_items({ payment, line_items: payment.line_items, show_total_due: true }),
3716
+
3717
+
3718
+ // Bottom spacing.
3719
+ VStack()
3720
+ .margin_bottom(15)
3721
+ ],
3722
+ });
3723
+ }
3724
+
3725
+ // On failed payment mail.
3726
+ on_failed_payment_mail({ payment }: { payment: any }): any {
3727
+
3728
+ // Shortcuts.
3729
+ const style = this.mail_style;
3730
+ const { Title, Text, Image, ImageMask, Table, TableRow, TableData, VStack } = Mail;
3731
+
3732
+ // Create mail.
3733
+ return this._mail_template({
3734
+ max_width: 800,
3735
+ children: [
3736
+
3737
+ // Title.
3738
+ TableRow(
3739
+ Title("Payment Failed")
3740
+ .color(style.title_fg)
3741
+ .width("fit-content")
3742
+ .font_size(26)
3743
+ ).center(),
3744
+
3745
+ // Text.
3746
+ TableRow(
3747
+ Text("We regret to inform you that your payment has encountered an issue and could not be processed successfully. We understand the inconvenience this may cause. Please try again, please contact customer support if the problem persists.")
3748
+ .margin(10, 0, 20, 0)
3749
+ .color(style.text_fg)
3750
+ .font_size(16)
3751
+ .center()
3752
+ ),
3753
+
3754
+ // Image.
3755
+ TableRow(
3756
+ ImageMask(`${this.full_domain}/volt_static/payments/error.png`)
3757
+ .frame(40, 40)
3758
+ .mask_color("#E8454E")
3759
+ .margin(0, 0, 30, 0)
3760
+ ).center(),
3761
+
3762
+ // Title.
3763
+ TableRow(
3764
+ Title("Order Summary")
3765
+ .color(style.subtitle_fg)
3766
+ .font_size(18)
3767
+ .margin(0)
3768
+ ),
3769
+ TableRow(
3770
+ Text("A summary of your failed order can be found below.")
3771
+ .margin(5, 0, 20, 0)
3772
+ .color(style.text_fg)
3773
+ .font_size(16)
3774
+ ),
3775
+
3776
+ // Line items.
3777
+ this._render_mail_payment_line_items({ payment, line_items: payment.line_items }),
3778
+
3779
+ // Bottom spacing.
3780
+ VStack()
3781
+ .margin_bottom(15)
3782
+ ],
3783
+ });
3784
+ }
3785
+
3786
+ // On cancellation mail.
3787
+ on_cancellation_mail({ payment, line_items }: { payment: any; line_items: any[] }): any {
3788
+
3789
+ // Shortcuts.
3790
+ const style = this.mail_style;
3791
+ const { Title, Text, Image, Table, TableRow, TableData, VStack } = Mail;
3792
+
3793
+ // Create mail.
3794
+ return this._mail_template({
3795
+ max_width: 800,
3796
+ children: [
3797
+
3798
+ // Title.
3799
+ TableRow(
3800
+ Title("Successfull Cancellation")
3801
+ .color(style.title_fg)
3802
+ .width("fit-content")
3803
+ .font_size(26)
3804
+ ).center(),
3805
+
3806
+ // Text.
3807
+ TableRow(
3808
+ Text("Your recent cancellation request has been successfully processed.")
3809
+ .margin(10, 0, 20, 0)
3810
+ .color(style.text_fg)
3811
+ .font_size(16)
3812
+ .center()
3813
+ ),
3814
+
3815
+ // Image.
3816
+ TableRow(
3817
+ Image(`${this.full_domain}/volt_static/payments/check.png`)
3818
+ .frame(40, 40)
3819
+ .margin(0, 0, 30, 0)
3820
+ ).center(),
3821
+
3822
+ // Title.
3823
+ TableRow(
3824
+ Title("Cancelled Summary")
3825
+ .color(style.subtitle_fg)
3826
+ .font_size(18)
3827
+ .margin(0)
3828
+ ),
3829
+ TableRow(
3830
+ Text("A summary of your cancelled products.")
3831
+ .margin(5, 0, 20, 0)
3832
+ .color(style.text_fg)
3833
+ .font_size(16)
3834
+ ),
3835
+
3836
+ // Line items.
3837
+ this._render_mail_payment_line_items({ payment, line_items }),
3838
+
3839
+ // Bottom spacing.
3840
+ VStack()
3841
+ .margin_bottom(15)
3842
+ ],
3843
+ });
3844
+ }
3845
+
3846
+ // On refund mail.
3847
+ on_failed_cancellation_mail({ payment }: { payment: any }): any {
3848
+
3849
+ // Shortcuts.
3850
+ const style = this.mail_style;
3851
+ const { Title, Text, Image, ImageMask, Table, TableRow, TableData, VStack } = Mail;
3852
+
3853
+ // Create mail.
3854
+ return this._mail_template({
3855
+ max_width: 800,
3856
+ children: [
3857
+
3858
+ // Title.
3859
+ TableRow(
3860
+ Title("Cancellation Failed")
3861
+ .color(style.title_fg)
3862
+ .width("fit-content")
3863
+ .font_size(26)
3864
+ ).center(),
3865
+
3866
+ // Text.
3867
+ TableRow(
3868
+ 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.")
3869
+ .margin(10, 0, 20, 0)
3870
+ .color(style.text_fg)
3871
+ .font_size(16)
3872
+ .center()
3873
+ ).center(),
3874
+
3875
+ // Image.
3876
+ TableRow(
3877
+ ImageMask(`${this.full_domain}/volt_static/payments/error.png`)
3878
+ .frame(40, 40)
3879
+ .mask_color("#E8454E")
3880
+ .margin(0, 0, 30, 0)
3881
+ ).center(),
3882
+
3883
+ // Title.
3884
+ TableRow(
3885
+ Title("Cancellation Summary")
3886
+ .color(style.subtitle_fg)
3887
+ .font_size(18)
3888
+ .margin(0)
3889
+ ),
3890
+ TableRow(
3891
+ Text("A summary of your cancellation request.")
3892
+ .margin(5, 0, 20, 0)
3893
+ .color(style.text_fg)
3894
+ .font_size(16)
3895
+ ),
3896
+
3897
+ // Line items.
3898
+ this._render_mail_payment_line_items({ payment, line_items: payment.line_items }),
3899
+
3900
+ // Bottom spacing.
3901
+ VStack()
3902
+ .margin_bottom(15)
3903
+ ],
3904
+ });
3905
+ }
3906
+
3907
+ // On refund mail.
3908
+ on_refund_mail({ payment, line_items }: { payment: any; line_items: any[] }): any {
3909
+
3910
+ // Shortcuts.
3911
+ const style = this.mail_style;
3912
+ const { Title, Text, Image, Table, TableRow, TableData, VStack } = Mail;
3913
+
3914
+ // Create mail.
3915
+ return this._mail_template({
3916
+ max_width: 800,
3917
+ children: [
3918
+
3919
+ // Title.
3920
+ TableRow(
3921
+ Title("Successful Refund")
3922
+ .color(style.title_fg)
3923
+ .width("fit-content")
3924
+ .font_size(26)
3925
+ ).center(),
3926
+
3927
+ // Text.
3928
+ TableRow(
3929
+ 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.")
3930
+ .margin(10, 0, 20, 0)
3931
+ .color(style.text_fg)
3932
+ .font_size(16)
3933
+ .center()
3934
+ ),
3935
+
3936
+ // Image.
3937
+ TableRow(
3938
+ Image(`${this.full_domain}/volt_static/payments/party.png`)
3939
+ .frame(60, 60)
3940
+ .margin(0, 0, 30, 0)
3941
+ ).center(),
3942
+
3943
+ // Title.
3944
+ TableRow(
3945
+ Title("Refund Summary")
3946
+ .color(style.subtitle_fg)
3947
+ .font_size(18)
3948
+ .margin(0)
3949
+ ),
3950
+ TableRow(
3951
+ Text("A summary of your refunded products.")
3952
+ .margin(5, 0, 20, 0)
3953
+ .color(style.text_fg)
3954
+ .font_size(16)
3955
+ ),
3956
+
3957
+ // Line items.
3958
+ this._render_mail_payment_line_items({ payment, line_items }),
3959
+
3960
+ // Bottom spacing.
3961
+ VStack()
3962
+ .margin_bottom(15)
3963
+ ],
3964
+ });
3965
+ }
3966
+
3967
+ // On refund mail.
3968
+ on_failed_refund_mail({ payment, line_items }: { payment: any; line_items: any[] }): any {
3969
+
3970
+ // Shortcuts.
3971
+ const style = this.mail_style;
3972
+ const { Title, Text, Image, ImageMask, Table, TableRow, TableData, VStack } = Mail;
3973
+
3974
+ // Create mail.
3975
+ return this._mail_template({
3976
+ max_width: 800,
3977
+ children: [
3978
+
3979
+ // Title.
3980
+ TableRow(
3981
+ Title("Refund Failed")
3982
+ .color(style.title_fg)
3983
+ .width("fit-content")
3984
+ .font_size(26)
3985
+ ).center(),
3986
+
3987
+ // Text.
3988
+ TableRow(
3989
+ 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.")
3990
+ .margin(10, 0, 20, 0)
3991
+ .color(style.text_fg)
3992
+ .font_size(16)
3993
+ .center()
3994
+ ).center(),
3995
+
3996
+ // Image.
3997
+ TableRow(
3998
+ ImageMask(`${this.full_domain}/volt_static/payments/error.png`)
3999
+ .frame(40, 40)
4000
+ .mask_color("#E8454E")
4001
+ .margin(0, 0, 30, 0)
4002
+ ).center(),
4003
+
4004
+ // Title.
4005
+ TableRow(
4006
+ Title("Refund Summary")
4007
+ .color(style.subtitle_fg)
4008
+ .font_size(18)
4009
+ .margin(0)
4010
+ ),
4011
+ TableRow(
4012
+ Text("A summary of your refund request.")
4013
+ .margin(5, 0, 20, 0)
4014
+ .color(style.text_fg)
4015
+ .font_size(16)
4016
+ ),
4017
+
4018
+ // Line items.
4019
+ this._render_mail_payment_line_items({ payment, line_items }),
4020
+
4021
+ // Bottom spacing.
4022
+ VStack()
4023
+ .margin_bottom(15)
4024
+ ],
4025
+ });
4026
+ }
4027
+
4028
+ // On refund mail.
4029
+ on_chargeback_mail({ payment, line_items }: { payment: any; line_items: any[] }): any {
4030
+
4031
+ // Shortcuts.
4032
+ const style = this.mail_style;
4033
+ const { Title, Text, Image, Table, TableRow, TableData, VStack } = Mail;
4034
+
4035
+ // Create mail.
4036
+ return this._mail_template({
4037
+ max_width: 800,
4038
+ children: [
4039
+
4040
+ // Title.
4041
+ TableRow(
4042
+ Title("Successful Refund")
4043
+ .color(style.title_fg)
4044
+ .width("fit-content")
4045
+ .font_size(26)
4046
+ ).center(),
4047
+
4048
+ // Text.
4049
+ TableRow(
4050
+ 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.")
4051
+ .margin(10, 0, 20, 0)
4052
+ .color(style.text_fg)
4053
+ .font_size(16)
4054
+ .center()
4055
+ ),
4056
+
4057
+ // Image.
4058
+ TableRow(
4059
+ Image(`${this.full_domain}/volt_static/payments/party.png`)
4060
+ .frame(60, 60)
4061
+ .margin(0, 0, 30, 0)
4062
+ ).center(),
4063
+
4064
+ // Title.
4065
+ TableRow(
4066
+ Title("Chargeback Summary")
4067
+ .color(style.subtitle_fg)
4068
+ .font_size(18)
4069
+ .margin(0)
4070
+ ),
4071
+ TableRow(
4072
+ Text("A summary of your refundend products.")
4073
+ .margin(5, 0, 20, 0)
4074
+ .color(style.text_fg)
4075
+ .font_size(16)
4076
+ ),
4077
+
4078
+ // Line items.
4079
+ this._render_mail_payment_line_items({ payment, line_items }),
4080
+
4081
+ // Bottom spacing.
4082
+ VStack()
4083
+ .margin_bottom(15)
4084
+ ],
4085
+ });
4086
+ }
4087
+
4088
+ // On refund mail.
4089
+ on_failed_chargeback_mail({ payment, line_items }: { payment: any; line_items: any[] }): any {
4090
+
4091
+ // Shortcuts.
4092
+ const style = this.mail_style;
4093
+ const { Title, Text, Image, ImageMask, Table, TableRow, TableData, VStack } = Mail;
4094
+
4095
+ // Create mail.
4096
+ return this._mail_template({
4097
+ max_width: 800,
4098
+ children: [
4099
+
4100
+ // Title.
4101
+ TableRow(
4102
+ Title("Chargeback Failed")
4103
+ .color(style.title_fg)
4104
+ .width("fit-content")
4105
+ .font_size(26)
4106
+ ).center(),
4107
+
4108
+ // Text.
4109
+ TableRow(
4110
+ Text("We regret to inform you that your recent chargeback request has been declined.")
4111
+ .margin(10, 0, 20, 0)
4112
+ .color(style.text_fg)
4113
+ .font_size(16)
4114
+ .center()
4115
+ ).center(),
4116
+
4117
+ // Image.
4118
+ TableRow(
4119
+ ImageMask(`${this.full_domain}/volt_static/payments/error.png`)
4120
+ .frame(40, 40)
4121
+ .mask_color("#E8454E")
4122
+ .margin(0, 0, 30, 0)
4123
+ ).center(),
4124
+
4125
+ // Title.
4126
+ TableRow(
4127
+ Title("Chargeback Summary")
4128
+ .color(style.subtitle_fg)
4129
+ .font_size(18)
4130
+ .margin(0)
4131
+ ),
4132
+ TableRow(
4133
+ Text("A summary of your chargeback request.")
4134
+ .margin(5, 0, 20, 0)
4135
+ .color(style.text_fg)
4136
+ .font_size(16)
4137
+ ),
4138
+
4139
+ // Line items.
4140
+ this._render_mail_payment_line_items({ payment, line_items }),
4141
+
4142
+ // Bottom spacing.
4143
+ VStack()
4144
+ .margin_bottom(15)
4145
+ ],
4146
+ });
4147
+ }
4148
+ }
4149
+ export default Server;