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