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