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