@vandenberghinc/volt 1.2.6 → 1.2.7
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/backend/dist/cjs/backend/src/blacklist.d.ts +12 -0
- package/backend/dist/cjs/backend/src/blacklist.js +78 -0
- package/backend/dist/cjs/backend/src/cli.d.ts +2 -0
- package/backend/dist/cjs/backend/src/cli.js +198 -0
- package/backend/dist/cjs/backend/src/database/collection.d.ts +1765 -0
- package/backend/dist/cjs/backend/src/database/collection.js +3301 -0
- package/backend/dist/cjs/backend/src/database/database.d.ts +92 -0
- package/backend/dist/cjs/backend/src/database/database.js +170 -0
- package/backend/dist/cjs/backend/src/database/document.d.ts +1 -0
- package/backend/dist/cjs/backend/src/database/document.js +15 -0
- package/backend/dist/cjs/backend/src/database/filters/filters.d.ts +6 -0
- package/backend/dist/cjs/backend/src/database/filters/filters.js +15 -0
- package/backend/dist/cjs/backend/src/database/filters/strict_filter.d.ts +223 -0
- package/backend/dist/cjs/backend/src/database/filters/strict_filter.js +15 -0
- package/backend/dist/cjs/backend/src/database/filters/strict_filter_test.d.ts +1 -0
- package/backend/dist/cjs/backend/src/database/filters/strict_filter_test.js +443 -0
- package/backend/dist/cjs/backend/src/database/filters/strict_filter_test_v0.d.ts +1 -0
- package/backend/dist/cjs/backend/src/database/filters/strict_filter_test_v0.js +15 -0
- package/backend/dist/cjs/backend/src/database/filters/strict_filter_v0.d.ts +50 -0
- package/backend/dist/cjs/backend/src/database/filters/strict_filter_v0.js +15 -0
- package/backend/dist/cjs/backend/src/database/filters/strict_filter_v1.d.ts +76 -0
- package/backend/dist/cjs/backend/src/database/filters/strict_filter_v1.js +15 -0
- package/backend/dist/cjs/backend/src/database/filters/strict_filter_v2.d.ts +75 -0
- package/backend/dist/cjs/backend/src/database/filters/strict_filter_v2.js +15 -0
- package/backend/dist/cjs/backend/src/database/filters/strict_filter_v3.d.ts +219 -0
- package/backend/dist/cjs/backend/src/database/filters/strict_filter_v3.js +15 -0
- package/backend/dist/cjs/backend/src/database/filters/strict_update_filter.d.ts +165 -0
- package/backend/dist/cjs/backend/src/database/filters/strict_update_filter.js +15 -0
- package/backend/dist/cjs/backend/src/database/filters/strict_update_filter_test.d.ts +5 -0
- package/backend/dist/cjs/backend/src/database/filters/strict_update_filter_test.js +355 -0
- package/backend/dist/cjs/backend/src/database/flatten.d.ts +78 -0
- package/backend/dist/cjs/backend/src/database/flatten.js +53 -0
- package/backend/dist/cjs/backend/src/database/flatten_test.d.ts +1 -0
- package/backend/dist/cjs/backend/src/database/flatten_test.js +175 -0
- package/backend/dist/cjs/backend/src/database/quota/quoata_v2.d.ts +533 -0
- package/backend/dist/cjs/backend/src/database/quota/quoata_v2.js +1046 -0
- package/backend/dist/cjs/backend/src/database/quota/quota.d.ts +551 -0
- package/backend/dist/cjs/backend/src/database/quota/quota.js +1108 -0
- package/backend/dist/cjs/backend/src/database/quota/quota_v1.d.ts +534 -0
- package/backend/dist/cjs/backend/src/database/quota/quota_v1.js +1087 -0
- package/backend/dist/cjs/backend/src/database/quota/safe_int.d.ts +412 -0
- package/backend/dist/cjs/backend/src/database/quota/safe_int.js +745 -0
- package/backend/dist/cjs/backend/src/endpoint.d.ts +346 -0
- package/backend/dist/cjs/backend/src/endpoint.js +468 -0
- package/backend/dist/cjs/backend/src/errors/index.d.ts +7 -0
- package/backend/dist/cjs/backend/src/errors/index.js +25 -0
- package/backend/dist/cjs/backend/src/errors/internal_external.d.ts +52 -0
- package/backend/dist/cjs/backend/src/errors/internal_external.js +95 -0
- package/backend/dist/cjs/backend/src/errors/invalid_usage_error.d.ts +41 -0
- package/backend/dist/cjs/backend/src/errors/invalid_usage_error.js +47 -0
- package/backend/dist/cjs/backend/src/errors/system_error.d.ts +261 -0
- package/backend/dist/cjs/backend/src/errors/system_error.js +436 -0
- package/backend/dist/cjs/backend/src/events.d.ts +97 -0
- package/backend/dist/cjs/backend/src/events.js +15 -0
- package/backend/dist/cjs/backend/src/frontend.d.ts +13 -0
- package/backend/dist/cjs/backend/src/frontend.js +56 -0
- package/backend/dist/cjs/backend/src/image_endpoint.d.ts +44 -0
- package/backend/dist/cjs/backend/src/image_endpoint.js +185 -0
- package/backend/dist/cjs/backend/src/index.d.ts +23 -0
- package/backend/dist/cjs/backend/src/index.js +70 -0
- package/backend/dist/cjs/backend/src/logger.d.ts +5 -0
- package/backend/dist/cjs/backend/src/logger.js +15 -0
- package/backend/dist/cjs/backend/src/meta.d.ts +112 -0
- package/backend/dist/cjs/backend/src/meta.js +181 -0
- package/backend/dist/cjs/backend/src/payments/paddle.d.ts +329 -0
- package/backend/dist/cjs/backend/src/payments/paddle.js +1996 -0
- package/backend/dist/cjs/backend/src/payments/stripe/checkout.d.ts +113 -0
- package/backend/dist/cjs/backend/src/payments/stripe/checkout.js +295 -0
- package/backend/dist/cjs/backend/src/payments/stripe/customers.d.ts +17 -0
- package/backend/dist/cjs/backend/src/payments/stripe/customers.js +164 -0
- package/backend/dist/cjs/backend/src/payments/stripe/error.d.ts +74 -0
- package/backend/dist/cjs/backend/src/payments/stripe/error.js +64 -0
- package/backend/dist/cjs/backend/src/payments/stripe/events.d.ts +155 -0
- package/backend/dist/cjs/backend/src/payments/stripe/events.js +15 -0
- package/backend/dist/cjs/backend/src/payments/stripe/meters.d.ts +105 -0
- package/backend/dist/cjs/backend/src/payments/stripe/meters.js +230 -0
- package/backend/dist/cjs/backend/src/payments/stripe/payment_methods.d.ts +58 -0
- package/backend/dist/cjs/backend/src/payments/stripe/payment_methods.js +109 -0
- package/backend/dist/cjs/backend/src/payments/stripe/products.d.ts +519 -0
- package/backend/dist/cjs/backend/src/payments/stripe/products.js +650 -0
- package/backend/dist/cjs/backend/src/payments/stripe/stripe.d.ts +215 -0
- package/backend/dist/cjs/backend/src/payments/stripe/stripe.js +468 -0
- package/backend/dist/cjs/backend/src/payments/stripe/subscriptions.d.ts +172 -0
- package/backend/dist/cjs/backend/src/payments/stripe/subscriptions.js +557 -0
- package/backend/dist/cjs/backend/src/payments/stripe/utils.d.ts +63 -0
- package/backend/dist/cjs/backend/src/payments/stripe/utils.js +118 -0
- package/backend/dist/cjs/backend/src/payments/stripe/webhooks.d.ts +105 -0
- package/backend/dist/cjs/backend/src/payments/stripe/webhooks.js +627 -0
- package/backend/dist/cjs/backend/src/plugins/browser.d.ts +1 -0
- package/backend/dist/cjs/backend/src/plugins/browser.js +15 -0
- package/backend/dist/cjs/backend/src/plugins/communication.d.ts +70 -0
- package/backend/dist/cjs/backend/src/plugins/communication.js +196 -0
- package/backend/dist/cjs/backend/src/plugins/mail/mail.d.ts +255 -0
- package/backend/dist/cjs/backend/src/plugins/mail/mail.js +381 -0
- package/backend/dist/cjs/backend/src/plugins/mail/ui.d.ts +297 -0
- package/backend/dist/cjs/backend/src/plugins/mail/ui.js +1370 -0
- package/backend/dist/cjs/backend/src/plugins/pdf.d.ts +1 -0
- package/backend/dist/cjs/backend/src/plugins/pdf.js +1456 -0
- package/backend/dist/cjs/backend/src/plugins/thread_monitor.d.ts +18 -0
- package/backend/dist/cjs/backend/src/plugins/thread_monitor.js +116 -0
- package/backend/dist/cjs/backend/src/rate_limit.d.ts +148 -0
- package/backend/dist/cjs/backend/src/rate_limit.js +543 -0
- package/backend/dist/cjs/backend/src/route.d.ts +39 -0
- package/backend/dist/cjs/backend/src/route.js +172 -0
- package/backend/dist/cjs/backend/src/server.d.ts +502 -0
- package/backend/dist/cjs/backend/src/server.js +1710 -0
- package/backend/dist/cjs/backend/src/server.old.d.ts +594 -0
- package/backend/dist/cjs/backend/src/server.old.js +2058 -0
- package/backend/dist/cjs/backend/src/splash_screen.d.ts +93 -0
- package/backend/dist/cjs/backend/src/splash_screen.js +119 -0
- package/backend/dist/cjs/backend/src/status.d.ts +89 -0
- package/backend/dist/cjs/backend/src/status.js +211 -0
- package/backend/dist/cjs/backend/src/stream.d.ts +494 -0
- package/backend/dist/cjs/backend/src/stream.js +1370 -0
- package/backend/dist/cjs/backend/src/users.d.ts +926 -0
- package/backend/dist/cjs/backend/src/users.js +2223 -0
- package/backend/dist/cjs/backend/src/utils.d.ts +22 -0
- package/backend/dist/cjs/backend/src/utils.js +626 -0
- package/backend/dist/cjs/backend/src/view.d.ts +115 -0
- package/backend/dist/cjs/backend/src/view.js +519 -0
- package/backend/dist/cjs/backend/src/vinc.d.ts +6 -0
- package/backend/dist/cjs/backend/src/vinc.js +40 -0
- package/backend/dist/cjs/backend/src/volt.d.ts +24 -0
- package/backend/dist/cjs/backend/src/volt.js +72 -0
- package/backend/dist/cjs/frontend/src/modules/request.d.ts +70 -0
- package/backend/dist/cjs/frontend/src/modules/request.js +99 -0
- package/backend/dist/cjs/package.json +1 -0
- package/backend/dist/esm/backend/src/blacklist.d.ts +12 -0
- package/backend/dist/esm/backend/src/blacklist.js +52 -0
- package/backend/dist/esm/backend/src/cli.d.ts +2 -0
- package/backend/dist/esm/backend/src/cli.js +211 -0
- package/backend/dist/esm/backend/src/database/collection.d.ts +1765 -0
- package/backend/dist/esm/backend/src/database/collection.js +3779 -0
- package/backend/dist/esm/backend/src/database/database.d.ts +92 -0
- package/backend/dist/esm/backend/src/database/database.js +214 -0
- package/backend/dist/esm/backend/src/database/document.d.ts +1 -0
- package/backend/dist/esm/backend/src/database/document.js +558 -0
- package/backend/dist/esm/backend/src/database/filters/filters.d.ts +6 -0
- package/backend/dist/esm/backend/src/database/filters/filters.js +1 -0
- package/backend/dist/esm/backend/src/database/filters/strict_filter.d.ts +223 -0
- package/backend/dist/esm/backend/src/database/filters/strict_filter.js +3 -0
- package/backend/dist/esm/backend/src/database/filters/strict_filter_test.d.ts +1 -0
- package/backend/dist/esm/backend/src/database/filters/strict_filter_test.js +505 -0
- package/backend/dist/esm/backend/src/database/filters/strict_filter_test_v0.d.ts +1 -0
- package/backend/dist/esm/backend/src/database/filters/strict_filter_test_v0.js +712 -0
- package/backend/dist/esm/backend/src/database/filters/strict_filter_v0.d.ts +50 -0
- package/backend/dist/esm/backend/src/database/filters/strict_filter_v0.js +5 -0
- package/backend/dist/esm/backend/src/database/filters/strict_filter_v1.d.ts +76 -0
- package/backend/dist/esm/backend/src/database/filters/strict_filter_v1.js +44 -0
- package/backend/dist/esm/backend/src/database/filters/strict_filter_v2.d.ts +75 -0
- package/backend/dist/esm/backend/src/database/filters/strict_filter_v2.js +5 -0
- package/backend/dist/esm/backend/src/database/filters/strict_filter_v3.d.ts +219 -0
- package/backend/dist/esm/backend/src/database/filters/strict_filter_v3.js +1 -0
- package/backend/dist/esm/backend/src/database/filters/strict_update_filter.d.ts +165 -0
- package/backend/dist/esm/backend/src/database/filters/strict_update_filter.js +5 -0
- package/backend/dist/esm/backend/src/database/filters/strict_update_filter_test.d.ts +5 -0
- package/backend/dist/esm/backend/src/database/filters/strict_update_filter_test.js +415 -0
- package/backend/dist/esm/backend/src/database/flatten.d.ts +78 -0
- package/backend/dist/esm/backend/src/database/flatten.js +22 -0
- package/backend/dist/esm/backend/src/database/flatten_test.d.ts +1 -0
- package/backend/dist/esm/backend/src/database/flatten_test.js +174 -0
- package/backend/dist/esm/backend/src/database/quota/quoata_v2.d.ts +533 -0
- package/backend/dist/esm/backend/src/database/quota/quoata_v2.js +1155 -0
- package/backend/dist/esm/backend/src/database/quota/quota.d.ts +551 -0
- package/backend/dist/esm/backend/src/database/quota/quota.js +1219 -0
- package/backend/dist/esm/backend/src/database/quota/quota_v1.d.ts +534 -0
- package/backend/dist/esm/backend/src/database/quota/quota_v1.js +1242 -0
- package/backend/dist/esm/backend/src/database/quota/safe_int.d.ts +412 -0
- package/backend/dist/esm/backend/src/database/quota/safe_int.js +810 -0
- package/backend/dist/esm/backend/src/endpoint.d.ts +346 -0
- package/backend/dist/esm/backend/src/endpoint.js +479 -0
- package/backend/dist/esm/backend/src/errors/index.d.ts +7 -0
- package/backend/dist/esm/backend/src/errors/index.js +7 -0
- package/backend/dist/esm/backend/src/errors/internal_external.d.ts +52 -0
- package/backend/dist/esm/backend/src/errors/internal_external.js +86 -0
- package/backend/dist/esm/backend/src/errors/invalid_usage_error.d.ts +41 -0
- package/backend/dist/esm/backend/src/errors/invalid_usage_error.js +33 -0
- package/backend/dist/esm/backend/src/errors/system_error.d.ts +261 -0
- package/backend/dist/esm/backend/src/errors/system_error.js +444 -0
- package/backend/dist/esm/backend/src/events.d.ts +97 -0
- package/backend/dist/esm/backend/src/events.js +5 -0
- package/backend/dist/esm/backend/src/frontend.d.ts +13 -0
- package/backend/dist/esm/backend/src/frontend.js +23 -0
- package/backend/dist/esm/backend/src/image_endpoint.d.ts +44 -0
- package/backend/dist/esm/backend/src/image_endpoint.js +196 -0
- package/backend/dist/esm/backend/src/index.d.ts +23 -0
- package/backend/dist/esm/backend/src/index.js +26 -0
- package/backend/dist/esm/backend/src/logger.d.ts +5 -0
- package/backend/dist/esm/backend/src/logger.js +8 -0
- package/backend/dist/esm/backend/src/meta.d.ts +112 -0
- package/backend/dist/esm/backend/src/meta.js +152 -0
- package/backend/dist/esm/backend/src/payments/paddle.d.ts +329 -0
- package/backend/dist/esm/backend/src/payments/paddle.js +2276 -0
- package/backend/dist/esm/backend/src/payments/stripe/checkout.d.ts +113 -0
- package/backend/dist/esm/backend/src/payments/stripe/checkout.js +356 -0
- package/backend/dist/esm/backend/src/payments/stripe/customers.d.ts +17 -0
- package/backend/dist/esm/backend/src/payments/stripe/customers.js +193 -0
- package/backend/dist/esm/backend/src/payments/stripe/error.d.ts +74 -0
- package/backend/dist/esm/backend/src/payments/stripe/error.js +51 -0
- package/backend/dist/esm/backend/src/payments/stripe/events.d.ts +155 -0
- package/backend/dist/esm/backend/src/payments/stripe/events.js +5 -0
- package/backend/dist/esm/backend/src/payments/stripe/meters.d.ts +105 -0
- package/backend/dist/esm/backend/src/payments/stripe/meters.js +318 -0
- package/backend/dist/esm/backend/src/payments/stripe/payment_methods.d.ts +58 -0
- package/backend/dist/esm/backend/src/payments/stripe/payment_methods.js +135 -0
- package/backend/dist/esm/backend/src/payments/stripe/products.d.ts +519 -0
- package/backend/dist/esm/backend/src/payments/stripe/products.js +896 -0
- package/backend/dist/esm/backend/src/payments/stripe/stripe.d.ts +215 -0
- package/backend/dist/esm/backend/src/payments/stripe/stripe.js +464 -0
- package/backend/dist/esm/backend/src/payments/stripe/subscriptions.d.ts +172 -0
- package/backend/dist/esm/backend/src/payments/stripe/subscriptions.js +754 -0
- package/backend/dist/esm/backend/src/payments/stripe/utils.d.ts +63 -0
- package/backend/dist/esm/backend/src/payments/stripe/utils.js +131 -0
- package/backend/dist/esm/backend/src/payments/stripe/webhooks.d.ts +105 -0
- package/backend/dist/esm/backend/src/payments/stripe/webhooks.js +752 -0
- package/backend/dist/esm/backend/src/plugins/browser.d.ts +1 -0
- package/backend/dist/esm/backend/src/plugins/browser.js +170 -0
- package/backend/dist/esm/backend/src/plugins/communication.d.ts +70 -0
- package/backend/dist/esm/backend/src/plugins/communication.js +169 -0
- package/backend/dist/esm/backend/src/plugins/mail/mail.d.ts +255 -0
- package/backend/dist/esm/backend/src/plugins/mail/mail.js +396 -0
- package/backend/dist/esm/backend/src/plugins/mail/ui.d.ts +297 -0
- package/backend/dist/esm/backend/src/plugins/mail/ui.js +1400 -0
- package/backend/dist/esm/backend/src/plugins/pdf.d.ts +1 -0
- package/backend/dist/esm/backend/src/plugins/pdf.js +1694 -0
- package/backend/dist/esm/backend/src/plugins/thread_monitor.d.ts +18 -0
- package/backend/dist/esm/backend/src/plugins/thread_monitor.js +120 -0
- package/backend/dist/esm/backend/src/rate_limit.d.ts +148 -0
- package/backend/dist/esm/backend/src/rate_limit.js +667 -0
- package/backend/dist/esm/backend/src/route.d.ts +39 -0
- package/backend/dist/esm/backend/src/route.js +222 -0
- package/backend/dist/esm/backend/src/server.d.ts +502 -0
- package/backend/dist/esm/backend/src/server.js +2031 -0
- package/backend/dist/esm/backend/src/server.old.d.ts +594 -0
- package/backend/dist/esm/backend/src/server.old.js +2630 -0
- package/backend/dist/esm/backend/src/splash_screen.d.ts +93 -0
- package/backend/dist/esm/backend/src/splash_screen.js +156 -0
- package/backend/dist/esm/backend/src/status.d.ts +89 -0
- package/backend/dist/esm/backend/src/status.js +213 -0
- package/backend/dist/esm/backend/src/stream.d.ts +494 -0
- package/backend/dist/esm/backend/src/stream.js +1611 -0
- package/backend/dist/esm/backend/src/users.d.ts +926 -0
- package/backend/dist/esm/backend/src/users.js +2423 -0
- package/backend/dist/esm/backend/src/utils.d.ts +22 -0
- package/backend/dist/esm/backend/src/utils.js +463 -0
- package/backend/dist/esm/backend/src/view.d.ts +115 -0
- package/backend/dist/esm/backend/src/view.js +584 -0
- package/backend/dist/esm/backend/src/vinc.d.ts +6 -0
- package/backend/dist/esm/backend/src/vinc.js +6 -0
- package/backend/dist/esm/backend/src/volt.d.ts +24 -0
- package/backend/dist/esm/backend/src/volt.js +27 -0
- package/backend/dist/esm/frontend/src/modules/request.d.ts +70 -0
- package/backend/dist/esm/frontend/src/modules/request.js +117 -0
- package/frontend/dist/backend/src/database/collection.d.ts +1765 -0
- package/frontend/dist/backend/src/database/collection.js +3779 -0
- package/frontend/dist/backend/src/database/database.d.ts +92 -0
- package/frontend/dist/backend/src/database/database.js +214 -0
- package/frontend/dist/backend/src/database/filters/filters.d.ts +6 -0
- package/frontend/dist/backend/src/database/filters/filters.js +1 -0
- package/frontend/dist/backend/src/database/filters/strict_filter.d.ts +223 -0
- package/frontend/dist/backend/src/database/filters/strict_filter.js +3 -0
- package/frontend/dist/backend/src/database/filters/strict_update_filter.d.ts +165 -0
- package/frontend/dist/backend/src/database/filters/strict_update_filter.js +5 -0
- package/frontend/dist/backend/src/database/flatten.d.ts +78 -0
- package/frontend/dist/backend/src/database/flatten.js +22 -0
- package/frontend/dist/backend/src/endpoint.d.ts +346 -0
- package/frontend/dist/backend/src/endpoint.js +479 -0
- package/frontend/dist/backend/src/errors/index.d.ts +7 -0
- package/frontend/dist/backend/src/errors/index.js +7 -0
- package/frontend/dist/backend/src/errors/internal_external.d.ts +52 -0
- package/frontend/dist/backend/src/errors/internal_external.js +86 -0
- package/frontend/dist/backend/src/errors/invalid_usage_error.d.ts +41 -0
- package/frontend/dist/backend/src/errors/invalid_usage_error.js +33 -0
- package/frontend/dist/backend/src/errors/system_error.d.ts +261 -0
- package/frontend/dist/backend/src/errors/system_error.js +444 -0
- package/frontend/dist/backend/src/events.d.ts +97 -0
- package/frontend/dist/backend/src/events.js +5 -0
- package/frontend/dist/backend/src/frontend.d.ts +13 -0
- package/frontend/dist/backend/src/frontend.js +23 -0
- package/frontend/dist/backend/src/image_endpoint.d.ts +44 -0
- package/frontend/dist/backend/src/image_endpoint.js +196 -0
- package/frontend/dist/backend/src/meta.d.ts +112 -0
- package/frontend/dist/backend/src/meta.js +152 -0
- package/frontend/dist/backend/src/payments/paddle.d.ts +329 -0
- package/frontend/dist/backend/src/payments/paddle.js +2276 -0
- package/frontend/dist/backend/src/payments/stripe/checkout.d.ts +113 -0
- package/frontend/dist/backend/src/payments/stripe/checkout.js +356 -0
- package/frontend/dist/backend/src/payments/stripe/customers.d.ts +17 -0
- package/frontend/dist/backend/src/payments/stripe/customers.js +193 -0
- package/frontend/dist/backend/src/payments/stripe/error.d.ts +74 -0
- package/frontend/dist/backend/src/payments/stripe/error.js +51 -0
- package/frontend/dist/backend/src/payments/stripe/events.d.ts +155 -0
- package/frontend/dist/backend/src/payments/stripe/events.js +5 -0
- package/frontend/dist/backend/src/payments/stripe/meters.d.ts +105 -0
- package/frontend/dist/backend/src/payments/stripe/meters.js +318 -0
- package/frontend/dist/backend/src/payments/stripe/payment_methods.d.ts +58 -0
- package/frontend/dist/backend/src/payments/stripe/payment_methods.js +135 -0
- package/frontend/dist/backend/src/payments/stripe/products.d.ts +519 -0
- package/frontend/dist/backend/src/payments/stripe/products.js +896 -0
- package/frontend/dist/backend/src/payments/stripe/stripe.d.ts +215 -0
- package/frontend/dist/backend/src/payments/stripe/stripe.js +464 -0
- package/frontend/dist/backend/src/payments/stripe/subscriptions.d.ts +172 -0
- package/frontend/dist/backend/src/payments/stripe/subscriptions.js +754 -0
- package/frontend/dist/backend/src/payments/stripe/utils.d.ts +63 -0
- package/frontend/dist/backend/src/payments/stripe/utils.js +131 -0
- package/frontend/dist/backend/src/payments/stripe/webhooks.d.ts +105 -0
- package/frontend/dist/backend/src/payments/stripe/webhooks.js +752 -0
- package/frontend/dist/backend/src/plugins/mail/mail.d.ts +255 -0
- package/frontend/dist/backend/src/plugins/mail/mail.js +396 -0
- package/frontend/dist/backend/src/plugins/mail/ui.d.ts +297 -0
- package/frontend/dist/backend/src/plugins/mail/ui.js +1400 -0
- package/frontend/dist/backend/src/rate_limit.d.ts +148 -0
- package/frontend/dist/backend/src/rate_limit.js +667 -0
- package/frontend/dist/backend/src/route.d.ts +39 -0
- package/frontend/dist/backend/src/route.js +222 -0
- package/frontend/dist/backend/src/server.d.ts +502 -0
- package/frontend/dist/backend/src/server.js +2031 -0
- package/frontend/dist/backend/src/splash_screen.d.ts +93 -0
- package/frontend/dist/backend/src/splash_screen.js +156 -0
- package/frontend/dist/backend/src/status.d.ts +89 -0
- package/frontend/dist/backend/src/status.js +213 -0
- package/frontend/dist/backend/src/stream.d.ts +494 -0
- package/frontend/dist/backend/src/stream.js +1611 -0
- package/frontend/dist/backend/src/users.d.ts +926 -0
- package/frontend/dist/backend/src/users.js +2423 -0
- package/frontend/dist/backend/src/utils.d.ts +22 -0
- package/frontend/dist/backend/src/utils.js +463 -0
- package/frontend/dist/backend/src/view.d.ts +115 -0
- package/frontend/dist/backend/src/view.js +584 -0
- package/frontend/dist/frontend/src/css/adyen.css +92 -0
- package/frontend/dist/frontend/src/css/volt.css +75 -0
- package/frontend/dist/frontend/src/elements/base.d.ts +3743 -0
- package/frontend/dist/frontend/src/elements/base.js +12151 -0
- package/frontend/dist/frontend/src/elements/module.d.ts +95 -0
- package/frontend/dist/frontend/src/elements/module.js +216 -0
- package/frontend/dist/frontend/src/elements/register_element.d.ts +3 -0
- package/frontend/dist/frontend/src/elements/register_element.js +22 -0
- package/frontend/dist/frontend/src/elements/resize_query_manager.d.ts +0 -0
- package/frontend/dist/frontend/src/elements/resize_query_manager.js +150 -0
- package/frontend/dist/frontend/src/elements/types.d.ts +52 -0
- package/frontend/dist/frontend/src/elements/types.js +5 -0
- package/frontend/dist/frontend/src/index.d.ts +21 -0
- package/frontend/dist/frontend/src/index.js +29 -0
- package/frontend/dist/frontend/src/modules/attachment.d.ts +126 -0
- package/frontend/dist/frontend/src/modules/attachment.js +306 -0
- package/frontend/dist/frontend/src/modules/auth.d.ts +44 -0
- package/frontend/dist/frontend/src/modules/auth.js +80 -0
- package/frontend/dist/frontend/src/modules/color.d.ts +160 -0
- package/frontend/dist/frontend/src/modules/color.js +316 -0
- package/frontend/dist/frontend/src/modules/compression.d.ts +39 -0
- package/frontend/dist/frontend/src/modules/compression.js +102 -0
- package/frontend/dist/frontend/src/modules/cookies.d.ts +44 -0
- package/frontend/dist/frontend/src/modules/cookies.js +143 -0
- package/frontend/dist/frontend/src/modules/events.d.ts +31 -0
- package/frontend/dist/frontend/src/modules/events.js +79 -0
- package/frontend/dist/frontend/src/modules/google.d.ts +23 -0
- package/frontend/dist/frontend/src/modules/google.js +52 -0
- package/frontend/dist/frontend/src/modules/meta.d.ts +14 -0
- package/frontend/dist/frontend/src/modules/meta.js +48 -0
- package/frontend/dist/frontend/src/modules/paddle.d.ts +1207 -0
- package/frontend/dist/frontend/src/modules/paddle.js +2594 -0
- package/frontend/dist/frontend/src/modules/request.d.ts +70 -0
- package/frontend/dist/frontend/src/modules/request.js +117 -0
- package/frontend/dist/frontend/src/modules/settings.d.ts +3 -0
- package/frontend/dist/frontend/src/modules/settings.js +5 -0
- package/frontend/dist/frontend/src/modules/statics.d.ts +21 -0
- package/frontend/dist/frontend/src/modules/statics.js +43 -0
- package/frontend/dist/frontend/src/modules/stripe/cart.d.ts +112 -0
- package/frontend/dist/frontend/src/modules/stripe/cart.js +321 -0
- package/frontend/dist/frontend/src/modules/stripe/checkout.d.ts +7 -0
- package/frontend/dist/frontend/src/modules/stripe/checkout.js +37 -0
- package/frontend/dist/frontend/src/modules/stripe/index.m.d.ts +6 -0
- package/frontend/dist/frontend/src/modules/stripe/index.m.js +6 -0
- package/frontend/dist/frontend/src/modules/stripe/payments.d.ts +58 -0
- package/frontend/dist/frontend/src/modules/stripe/payments.js +92 -0
- package/frontend/dist/frontend/src/modules/support.d.ts +30 -0
- package/frontend/dist/frontend/src/modules/support.js +53 -0
- package/frontend/dist/frontend/src/modules/theme.d.ts +133 -0
- package/frontend/dist/frontend/src/modules/theme.js +406 -0
- package/frontend/dist/frontend/src/modules/themes.d.ts +12 -0
- package/frontend/dist/frontend/src/modules/themes.js +22 -0
- package/frontend/dist/frontend/src/modules/user.d.ts +164 -0
- package/frontend/dist/frontend/src/modules/user.js +270 -0
- package/frontend/dist/frontend/src/modules/utils.d.ts +176 -0
- package/frontend/dist/frontend/src/modules/utils.js +569 -0
- package/frontend/dist/frontend/src/types/gradient.d.ts +29 -0
- package/frontend/dist/frontend/src/types/gradient.js +79 -0
- package/frontend/dist/frontend/src/ui/border_button.d.ts +94 -0
- package/frontend/dist/frontend/src/ui/border_button.js +228 -0
- package/frontend/dist/frontend/src/ui/button.d.ts +241 -0
- package/frontend/dist/frontend/src/ui/button.js +682 -0
- package/frontend/dist/frontend/src/ui/canvas.d.ts +138 -0
- package/frontend/dist/frontend/src/ui/canvas.js +444 -0
- package/frontend/dist/frontend/src/ui/checkbox.d.ts +74 -0
- package/frontend/dist/frontend/src/ui/checkbox.js +321 -0
- package/frontend/dist/frontend/src/ui/code.d.ts +235 -0
- package/frontend/dist/frontend/src/ui/code.js +1007 -0
- package/frontend/dist/frontend/src/ui/context_menu.d.ts +36 -0
- package/frontend/dist/frontend/src/ui/context_menu.js +205 -0
- package/frontend/dist/frontend/src/ui/css.d.ts +16 -0
- package/frontend/dist/frontend/src/ui/css.js +48 -0
- package/frontend/dist/frontend/src/ui/divider.d.ts +15 -0
- package/frontend/dist/frontend/src/ui/divider.js +78 -0
- package/frontend/dist/frontend/src/ui/dropdown.d.ts +176 -0
- package/frontend/dist/frontend/src/ui/dropdown.js +481 -0
- package/frontend/dist/frontend/src/ui/for_each.d.ts +37 -0
- package/frontend/dist/frontend/src/ui/for_each.js +92 -0
- package/frontend/dist/frontend/src/ui/form.d.ts +34 -0
- package/frontend/dist/frontend/src/ui/form.js +233 -0
- package/frontend/dist/frontend/src/ui/frame_modes.d.ts +37 -0
- package/frontend/dist/frontend/src/ui/frame_modes.js +108 -0
- package/frontend/dist/frontend/src/ui/google_map.d.ts +24 -0
- package/frontend/dist/frontend/src/ui/google_map.js +106 -0
- package/frontend/dist/frontend/src/ui/gradient.d.ts +25 -0
- package/frontend/dist/frontend/src/ui/gradient.js +131 -0
- package/frontend/dist/frontend/src/ui/image.d.ts +111 -0
- package/frontend/dist/frontend/src/ui/image.js +576 -0
- package/frontend/dist/frontend/src/ui/input.d.ts +392 -0
- package/frontend/dist/frontend/src/ui/input.js +1201 -0
- package/frontend/dist/frontend/src/ui/link.d.ts +25 -0
- package/frontend/dist/frontend/src/ui/link.js +140 -0
- package/frontend/dist/frontend/src/ui/list.d.ts +37 -0
- package/frontend/dist/frontend/src/ui/list.js +170 -0
- package/frontend/dist/frontend/src/ui/loader_button.d.ts +80 -0
- package/frontend/dist/frontend/src/ui/loader_button.js +193 -0
- package/frontend/dist/frontend/src/ui/loaders.d.ts +57 -0
- package/frontend/dist/frontend/src/ui/loaders.js +157 -0
- package/frontend/dist/frontend/src/ui/popup.d.ts +94 -0
- package/frontend/dist/frontend/src/ui/popup.js +510 -0
- package/frontend/dist/frontend/src/ui/pseudo.d.ts +44 -0
- package/frontend/dist/frontend/src/ui/pseudo.js +154 -0
- package/frontend/dist/frontend/src/ui/scroller.d.ts +105 -0
- package/frontend/dist/frontend/src/ui/scroller.js +1253 -0
- package/frontend/dist/frontend/src/ui/slider.d.ts +45 -0
- package/frontend/dist/frontend/src/ui/slider.js +217 -0
- package/frontend/dist/frontend/src/ui/spacer.d.ts +15 -0
- package/frontend/dist/frontend/src/ui/spacer.js +78 -0
- package/frontend/dist/frontend/src/ui/span.d.ts +15 -0
- package/frontend/dist/frontend/src/ui/span.js +73 -0
- package/frontend/dist/frontend/src/ui/stack.d.ts +66 -0
- package/frontend/dist/frontend/src/ui/stack.js +335 -0
- package/frontend/dist/frontend/src/ui/steps.d.ts +131 -0
- package/frontend/dist/frontend/src/ui/steps.js +308 -0
- package/frontend/dist/frontend/src/ui/style.d.ts +17 -0
- package/frontend/dist/frontend/src/ui/style.js +73 -0
- package/frontend/dist/frontend/src/ui/switch.d.ts +69 -0
- package/frontend/dist/frontend/src/ui/switch.js +357 -0
- package/frontend/dist/frontend/src/ui/table.d.ts +100 -0
- package/frontend/dist/frontend/src/ui/table.js +405 -0
- package/frontend/dist/frontend/src/ui/tabs.d.ts +111 -0
- package/frontend/dist/frontend/src/ui/tabs.js +424 -0
- package/frontend/dist/frontend/src/ui/text.d.ts +15 -0
- package/frontend/dist/frontend/src/ui/text.js +83 -0
- package/frontend/dist/frontend/src/ui/title.d.ts +91 -0
- package/frontend/dist/frontend/src/ui/title.js +272 -0
- package/frontend/dist/frontend/src/ui/ui.d.ts +35 -0
- package/frontend/dist/frontend/src/ui/ui.js +38 -0
- package/frontend/dist/frontend/src/ui/view.d.ts +15 -0
- package/frontend/dist/frontend/src/ui/view.js +88 -0
- package/frontend/dist/frontend/src/volt.d.ts +20 -0
- package/frontend/dist/frontend/src/volt.js +27 -0
- package/package.json +7 -2
|
@@ -0,0 +1,1611 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @author Daan van den Bergh
|
|
3
|
+
* @copyright © 2025 - 2025 Daan van den Bergh. All rights reserved.
|
|
4
|
+
*/
|
|
5
|
+
// ---------------------------------------------------------
|
|
6
|
+
// Imports.
|
|
7
|
+
import * as vlib from "@vandenberghinc/vlib";
|
|
8
|
+
import { Transform } from "node:stream";
|
|
9
|
+
import * as fs from "node:fs";
|
|
10
|
+
import { pipeline } from "node:stream";
|
|
11
|
+
import * as zlib from "node:zlib";
|
|
12
|
+
import RateLimits from './rate_limit.js';
|
|
13
|
+
import { Utils } from "./utils.js";
|
|
14
|
+
const { debug } = vlib;
|
|
15
|
+
// ---------------------------------------------------------
|
|
16
|
+
// Request object.
|
|
17
|
+
/**
|
|
18
|
+
* The http2 stream wrapper object.
|
|
19
|
+
*
|
|
20
|
+
* @property headers The request headers.
|
|
21
|
+
*
|
|
22
|
+
* @nav Stream
|
|
23
|
+
* @docs
|
|
24
|
+
*/
|
|
25
|
+
export class Stream {
|
|
26
|
+
/** The request headers. */
|
|
27
|
+
headers;
|
|
28
|
+
/** Whether this stream is an HTTP/2 stream. */
|
|
29
|
+
http2;
|
|
30
|
+
/** Whether this stream is an HTTP/1.1 stream (when false, it's an HTTP/2 stream). */
|
|
31
|
+
http1;
|
|
32
|
+
/** The status code of the sent response. */
|
|
33
|
+
status_code;
|
|
34
|
+
/** Whether the response has been finished. */
|
|
35
|
+
finished;
|
|
36
|
+
/** The received body potentially decompressed as string. */
|
|
37
|
+
body;
|
|
38
|
+
/** The raw body as a Buffer, potentially decompressed. */
|
|
39
|
+
raw_body;
|
|
40
|
+
/** The body wired exactly as is, not decompressed etc. */
|
|
41
|
+
wire_body;
|
|
42
|
+
/** The internal promise that resolves when the body is fully received. */
|
|
43
|
+
promise;
|
|
44
|
+
/** The cached value of {@link normalize_ip} */
|
|
45
|
+
_normalized_ip;
|
|
46
|
+
s;
|
|
47
|
+
req;
|
|
48
|
+
res;
|
|
49
|
+
_ip;
|
|
50
|
+
_port;
|
|
51
|
+
_method;
|
|
52
|
+
_params;
|
|
53
|
+
_is_query_params;
|
|
54
|
+
_endpoint;
|
|
55
|
+
_query_string;
|
|
56
|
+
_cookies;
|
|
57
|
+
_uid;
|
|
58
|
+
res_cookies;
|
|
59
|
+
res_headers;
|
|
60
|
+
/**
|
|
61
|
+
* Create a new Stream wrapper for HTTP/1.1 or HTTP/2.
|
|
62
|
+
*
|
|
63
|
+
* @param stream The HTTP/2 stream (when using HTTP/2).
|
|
64
|
+
* @param headers The request headers.
|
|
65
|
+
* @param req The HTTP/1.1 request (when using HTTP/1.1).
|
|
66
|
+
* @param res The HTTP/1.1/HTTP/2 response object.
|
|
67
|
+
*/
|
|
68
|
+
constructor(stream, headers, req, res) {
|
|
69
|
+
// Parameters.
|
|
70
|
+
this.s = stream;
|
|
71
|
+
this.headers = (headers ?? {});
|
|
72
|
+
this.req = req;
|
|
73
|
+
this.res = res;
|
|
74
|
+
this.http2 = req == null;
|
|
75
|
+
this.http1 = req != null;
|
|
76
|
+
// HTTP1.
|
|
77
|
+
if (this.http1) {
|
|
78
|
+
this.headers = this.req.headers;
|
|
79
|
+
}
|
|
80
|
+
// Request attributes.
|
|
81
|
+
this._ip = this.http2 ? this.s.session.socket.remoteAddress : this.req.socket.remoteAddress;
|
|
82
|
+
this._port = this.http2 ? this.s.session.socket.remotePort : this.req.socket.remotePort;
|
|
83
|
+
this._method = this.http2 ? this.headers[':method'] : this.req.method;
|
|
84
|
+
this._params = undefined;
|
|
85
|
+
this._is_query_params = false;
|
|
86
|
+
this._endpoint = undefined;
|
|
87
|
+
this._query_string = undefined;
|
|
88
|
+
this._cookies = undefined;
|
|
89
|
+
this._uid = undefined;
|
|
90
|
+
// Response attributes
|
|
91
|
+
this.status_code = undefined;
|
|
92
|
+
this.finished = false;
|
|
93
|
+
this.res_cookies = [];
|
|
94
|
+
this.res_headers = this.http1 ? [] : {};
|
|
95
|
+
// Read body.
|
|
96
|
+
this.body = "";
|
|
97
|
+
this.raw_body = Buffer.alloc(0);
|
|
98
|
+
this.wire_body = Buffer.alloc(0);
|
|
99
|
+
this.promise = undefined;
|
|
100
|
+
this._recv_body();
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Receive and buffer the request body, handling optional gzip/deflate decompression.
|
|
104
|
+
* Sets {@link body} and resolves the internal promise used by {@link join}.
|
|
105
|
+
*/
|
|
106
|
+
_recv_body() {
|
|
107
|
+
this.promise = new Promise((resolve, reject) => {
|
|
108
|
+
// Buffers: decoded + wire.
|
|
109
|
+
const buffs = [];
|
|
110
|
+
const wire_buffs = [];
|
|
111
|
+
// Get decompress stream.
|
|
112
|
+
let decompress_stream;
|
|
113
|
+
const content_encoding = this.headers['content-encoding'];
|
|
114
|
+
if (content_encoding === 'gzip') {
|
|
115
|
+
decompress_stream = zlib.createGunzip();
|
|
116
|
+
}
|
|
117
|
+
else if (content_encoding === 'deflate') {
|
|
118
|
+
decompress_stream = zlib.createInflate();
|
|
119
|
+
}
|
|
120
|
+
const cleanup = () => {
|
|
121
|
+
if (decompress_stream) {
|
|
122
|
+
decompress_stream.close();
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
const on_error = (e) => {
|
|
126
|
+
cleanup();
|
|
127
|
+
reject(e);
|
|
128
|
+
};
|
|
129
|
+
// -------------------------
|
|
130
|
+
// HTTP2
|
|
131
|
+
// -------------------------
|
|
132
|
+
if (this.http2) {
|
|
133
|
+
const source = this.s;
|
|
134
|
+
// 1) Buffer wire bytes from the source stream (exact on-the-wire chunks).
|
|
135
|
+
source.on("data", (chunk) => {
|
|
136
|
+
wire_buffs.push(chunk);
|
|
137
|
+
});
|
|
138
|
+
// 2) Create decoded stream (maybe decompressed).
|
|
139
|
+
let decoded = source;
|
|
140
|
+
if (decompress_stream) {
|
|
141
|
+
decoded = source.pipe(decompress_stream);
|
|
142
|
+
}
|
|
143
|
+
source.on("error", on_error);
|
|
144
|
+
decoded.on("error", on_error);
|
|
145
|
+
// 3) Buffer decoded bytes.
|
|
146
|
+
decoded.on("data", (chunk) => {
|
|
147
|
+
buffs.push(chunk);
|
|
148
|
+
});
|
|
149
|
+
// IMPORTANT: resolve on *decoded* end, because that’s when decoded body is complete.
|
|
150
|
+
decoded.on("end", () => {
|
|
151
|
+
try {
|
|
152
|
+
this.wire_body = Buffer.concat(wire_buffs);
|
|
153
|
+
this.raw_body = Buffer.concat(buffs);
|
|
154
|
+
this.body = this.raw_body.toString("utf8");
|
|
155
|
+
cleanup();
|
|
156
|
+
resolve();
|
|
157
|
+
}
|
|
158
|
+
catch (e) {
|
|
159
|
+
on_error(e);
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
// -------------------------
|
|
165
|
+
// HTTP1
|
|
166
|
+
// -------------------------
|
|
167
|
+
const source = this.req;
|
|
168
|
+
// 1) Buffer wire bytes from the source request.
|
|
169
|
+
source.on("data", (chunk) => {
|
|
170
|
+
wire_buffs.push(chunk);
|
|
171
|
+
});
|
|
172
|
+
// 2) Create decoded stream (maybe decompressed).
|
|
173
|
+
let decoded = source;
|
|
174
|
+
if (decompress_stream) {
|
|
175
|
+
decoded = source.pipe(decompress_stream);
|
|
176
|
+
}
|
|
177
|
+
source.on("error", on_error);
|
|
178
|
+
decoded.on("error", on_error);
|
|
179
|
+
// 3) Buffer decoded bytes.
|
|
180
|
+
decoded.on("data", (chunk) => {
|
|
181
|
+
buffs.push(chunk);
|
|
182
|
+
});
|
|
183
|
+
decoded.on("end", () => {
|
|
184
|
+
try {
|
|
185
|
+
this.wire_body = Buffer.concat(wire_buffs);
|
|
186
|
+
this.raw_body = Buffer.concat(buffs);
|
|
187
|
+
this.body = this.raw_body.toString("utf8");
|
|
188
|
+
cleanup();
|
|
189
|
+
resolve();
|
|
190
|
+
}
|
|
191
|
+
catch (e) {
|
|
192
|
+
on_error(e);
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Parse and cache the request endpoint and query string.
|
|
199
|
+
* Populates {@link _endpoint} and {@link _query_string}.
|
|
200
|
+
* @private
|
|
201
|
+
*/
|
|
202
|
+
_parse_endoint() {
|
|
203
|
+
if (this._endpoint !== undefined) {
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
this._endpoint = this.http2 ? this.headers[":path"] : this.req.url;
|
|
207
|
+
let index;
|
|
208
|
+
if ((index = this._endpoint.indexOf("?")) !== -1) {
|
|
209
|
+
this._query_string = this._endpoint.substr(index + 1);
|
|
210
|
+
this._endpoint = this._endpoint.substr(0, index);
|
|
211
|
+
}
|
|
212
|
+
this._endpoint = this._endpoint.replace(/\/\//g, "/");
|
|
213
|
+
if (this._endpoint.length > 1 && this._endpoint.charAt(this._endpoint.length - 1) === "/") {
|
|
214
|
+
this._endpoint = this._endpoint.substr(0, this._endpoint.length - 1);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Parse and cache request parameters from the query string or JSON body.
|
|
219
|
+
* Returns the parsed params map.
|
|
220
|
+
*/
|
|
221
|
+
_parse_params() {
|
|
222
|
+
// Parse query string.
|
|
223
|
+
this._parse_endoint();
|
|
224
|
+
// Already parsed.
|
|
225
|
+
if (this._params !== undefined) {
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
// Initialize.
|
|
229
|
+
this._params = {};
|
|
230
|
+
// By query string.
|
|
231
|
+
if (this._query_string !== undefined) {
|
|
232
|
+
// As encoded json.
|
|
233
|
+
if (this._query_string.charAt(0) === "{") {
|
|
234
|
+
try {
|
|
235
|
+
this._params = JSON.parse(decodeURIComponent(this._query_string));
|
|
236
|
+
}
|
|
237
|
+
catch (err) {
|
|
238
|
+
throw Error(`Invalid json request query: ${err}.`);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
// As query string.
|
|
242
|
+
else {
|
|
243
|
+
// Assign.
|
|
244
|
+
this._is_query_params = true;
|
|
245
|
+
// Variables.
|
|
246
|
+
let is_key = true, key = "", value = "";
|
|
247
|
+
const number_regex = /^-?\d+(\.\d+)?$/;
|
|
248
|
+
// Callback.
|
|
249
|
+
const add_value = () => {
|
|
250
|
+
let output_value;
|
|
251
|
+
switch (value) {
|
|
252
|
+
case "true":
|
|
253
|
+
case "True":
|
|
254
|
+
output_value = true;
|
|
255
|
+
break;
|
|
256
|
+
case "false":
|
|
257
|
+
case "False":
|
|
258
|
+
output_value = false;
|
|
259
|
+
break;
|
|
260
|
+
case "null":
|
|
261
|
+
case "None":
|
|
262
|
+
case "undefined":
|
|
263
|
+
output_value = null;
|
|
264
|
+
break;
|
|
265
|
+
default:
|
|
266
|
+
output_value = decodeURIComponent(value.replaceAll("+", " "));
|
|
267
|
+
if (number_regex.test(output_value)) {
|
|
268
|
+
if (output_value.indexOf(".") !== -1) {
|
|
269
|
+
output_value = parseFloat(output_value);
|
|
270
|
+
}
|
|
271
|
+
else {
|
|
272
|
+
output_value = parseInt(output_value);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
break;
|
|
276
|
+
}
|
|
277
|
+
this._params[decodeURIComponent(key.replaceAll("+", " "))] = output_value;
|
|
278
|
+
key = "";
|
|
279
|
+
value = "";
|
|
280
|
+
is_key = true;
|
|
281
|
+
};
|
|
282
|
+
// Iterate
|
|
283
|
+
for (let i = 0; i < this._query_string.length; i++) {
|
|
284
|
+
const c = this._query_string.charAt(i);
|
|
285
|
+
if (is_key && c === "=") {
|
|
286
|
+
is_key = false;
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
else if (is_key === false && c === "&") {
|
|
290
|
+
add_value();
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
if (is_key) {
|
|
294
|
+
key += c;
|
|
295
|
+
}
|
|
296
|
+
else {
|
|
297
|
+
value += c;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
if (key.length > 0) {
|
|
301
|
+
add_value();
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
// By body.
|
|
306
|
+
else if (this.body.trim().charAt(0) === "{") {
|
|
307
|
+
try {
|
|
308
|
+
this._params = JSON.parse(this.body);
|
|
309
|
+
}
|
|
310
|
+
catch (err) {
|
|
311
|
+
throw Error(`Invalid json request body: ${err}.`);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
// Handler.
|
|
315
|
+
return this._params;
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Parses & returns the cookies cookies,
|
|
319
|
+
* while assigning it to {@link _cookies}
|
|
320
|
+
*
|
|
321
|
+
* @warning On subsequent calls cookies will be parsed again.
|
|
322
|
+
*/
|
|
323
|
+
_parse_cookies() {
|
|
324
|
+
// Reset cookies.
|
|
325
|
+
this._cookies = {};
|
|
326
|
+
// Vars.
|
|
327
|
+
const cookie_str = this.http2 ? this.headers["cookie"] : this.req.headers.cookie;
|
|
328
|
+
if (cookie_str == null) {
|
|
329
|
+
return this._cookies;
|
|
330
|
+
}
|
|
331
|
+
let key = "";
|
|
332
|
+
let value = "";
|
|
333
|
+
let cookie = {};
|
|
334
|
+
let cookie_length = 0;
|
|
335
|
+
let cookie_key = null;
|
|
336
|
+
let is_value = false;
|
|
337
|
+
let is_str = null;
|
|
338
|
+
// Append to cookie.
|
|
339
|
+
const append_to_cookie = () => {
|
|
340
|
+
if (key.length > 0) {
|
|
341
|
+
if (cookie_length === 0) {
|
|
342
|
+
cookie.value = value;
|
|
343
|
+
}
|
|
344
|
+
else {
|
|
345
|
+
cookie[key] = value;
|
|
346
|
+
}
|
|
347
|
+
++cookie_length;
|
|
348
|
+
}
|
|
349
|
+
key = "";
|
|
350
|
+
value = "";
|
|
351
|
+
is_value = false;
|
|
352
|
+
is_str = null;
|
|
353
|
+
};
|
|
354
|
+
// Append cookie.
|
|
355
|
+
const append_cookie = () => {
|
|
356
|
+
if (cookie_key != null) {
|
|
357
|
+
this._cookies[cookie_key] = cookie;
|
|
358
|
+
cookie_key = null;
|
|
359
|
+
cookie = {};
|
|
360
|
+
cookie_length = 0;
|
|
361
|
+
}
|
|
362
|
+
};
|
|
363
|
+
// Iterate.
|
|
364
|
+
for (let x = 0; x < cookie_str.length; x++) {
|
|
365
|
+
const c = cookie_str.charAt(x);
|
|
366
|
+
// Add char to value.
|
|
367
|
+
if (is_value) {
|
|
368
|
+
// End of cookie string.
|
|
369
|
+
if (is_str === c) {
|
|
370
|
+
value = value.substr(1, value.length - 1);
|
|
371
|
+
append_to_cookie();
|
|
372
|
+
}
|
|
373
|
+
// Cookie seperator.
|
|
374
|
+
else if (is_str == null && c === " ") {
|
|
375
|
+
append_to_cookie();
|
|
376
|
+
}
|
|
377
|
+
// End of cookie.
|
|
378
|
+
else if (is_str == null && c === ";") {
|
|
379
|
+
append_to_cookie();
|
|
380
|
+
append_cookie();
|
|
381
|
+
}
|
|
382
|
+
// Append to value.
|
|
383
|
+
else {
|
|
384
|
+
value += c;
|
|
385
|
+
if (value.length === 1 && (c === "\"" || c === "'")) {
|
|
386
|
+
is_str = c;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
// Skip whitespace in keys.
|
|
391
|
+
else if (c == " " || c == "\t") {
|
|
392
|
+
continue;
|
|
393
|
+
}
|
|
394
|
+
// End of cookie key.
|
|
395
|
+
else if (c == "=") {
|
|
396
|
+
if (cookie_key == null) {
|
|
397
|
+
cookie_key = key;
|
|
398
|
+
}
|
|
399
|
+
is_value = true;
|
|
400
|
+
}
|
|
401
|
+
// Add char to key.
|
|
402
|
+
else {
|
|
403
|
+
key += c;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
append_to_cookie();
|
|
407
|
+
append_cookie();
|
|
408
|
+
return this._cookies;
|
|
409
|
+
}
|
|
410
|
+
// ---------------------------------------------------------
|
|
411
|
+
// Functions.
|
|
412
|
+
/**
|
|
413
|
+
* Wait until the request body is fully received.
|
|
414
|
+
* Resolves when the internal receive promise completes.
|
|
415
|
+
*/
|
|
416
|
+
async join() {
|
|
417
|
+
await this.promise;
|
|
418
|
+
}
|
|
419
|
+
// Get the requests ip.
|
|
420
|
+
/**
|
|
421
|
+
* Get the request's ip.
|
|
422
|
+
*
|
|
423
|
+
* @example
|
|
424
|
+
* ```ts
|
|
425
|
+
* const ip = stream.ip;
|
|
426
|
+
* ```
|
|
427
|
+
* @docs
|
|
428
|
+
*/
|
|
429
|
+
get ip() {
|
|
430
|
+
return this._ip;
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* Retrieve the normalized IP address, suitable for rate limiting and logging.
|
|
434
|
+
* @throws {Error} If the IP is invalid.
|
|
435
|
+
* @returns The normalized IP.
|
|
436
|
+
* @docs
|
|
437
|
+
*/
|
|
438
|
+
normalized_ip() {
|
|
439
|
+
if (this._normalized_ip != null) {
|
|
440
|
+
return this._normalized_ip;
|
|
441
|
+
}
|
|
442
|
+
return this._normalized_ip = RateLimits.normalize_ip(this._ip);
|
|
443
|
+
}
|
|
444
|
+
// Get the requests port.
|
|
445
|
+
/**
|
|
446
|
+
* Get the request's port.
|
|
447
|
+
*
|
|
448
|
+
* @example
|
|
449
|
+
* ```ts
|
|
450
|
+
* const port = stream.port;
|
|
451
|
+
* ```
|
|
452
|
+
* @docs
|
|
453
|
+
*/
|
|
454
|
+
get port() {
|
|
455
|
+
return this._port;
|
|
456
|
+
}
|
|
457
|
+
// Get the method.
|
|
458
|
+
/**
|
|
459
|
+
* Get the request method.
|
|
460
|
+
*
|
|
461
|
+
* @example
|
|
462
|
+
* ```ts
|
|
463
|
+
* const method = stream.method;
|
|
464
|
+
* ```
|
|
465
|
+
* @docs
|
|
466
|
+
*/
|
|
467
|
+
get method() {
|
|
468
|
+
return this._method;
|
|
469
|
+
}
|
|
470
|
+
// Get the endpoint.
|
|
471
|
+
/**
|
|
472
|
+
* Get the request's endpoint. This will not include the query string.
|
|
473
|
+
*
|
|
474
|
+
* @example
|
|
475
|
+
* ```ts
|
|
476
|
+
* const endpoint = stream.endpoint;
|
|
477
|
+
* ```
|
|
478
|
+
* @docs
|
|
479
|
+
*/
|
|
480
|
+
get endpoint() {
|
|
481
|
+
if (this._endpoint !== undefined) {
|
|
482
|
+
return this._endpoint;
|
|
483
|
+
}
|
|
484
|
+
this._parse_endoint();
|
|
485
|
+
return this._endpoint;
|
|
486
|
+
}
|
|
487
|
+
// Get the params.
|
|
488
|
+
/**
|
|
489
|
+
* Get the request's query or body params.
|
|
490
|
+
*
|
|
491
|
+
* @example
|
|
492
|
+
* ```ts
|
|
493
|
+
* const params = stream.params;
|
|
494
|
+
* ```
|
|
495
|
+
* @docs
|
|
496
|
+
*/
|
|
497
|
+
get params() {
|
|
498
|
+
if (this._params !== undefined) {
|
|
499
|
+
return this._params;
|
|
500
|
+
}
|
|
501
|
+
this._parse_params();
|
|
502
|
+
return this._params;
|
|
503
|
+
}
|
|
504
|
+
/** Add a param (used by the server backend for path parameters). */
|
|
505
|
+
add_param(name, value) {
|
|
506
|
+
if (!this._params) {
|
|
507
|
+
this._params = {};
|
|
508
|
+
}
|
|
509
|
+
this._params[name] = value;
|
|
510
|
+
}
|
|
511
|
+
// Get a param by name and optionally by type.
|
|
512
|
+
/**
|
|
513
|
+
* Get a single query or body parameter with an optional type cast.
|
|
514
|
+
*
|
|
515
|
+
* @warning Throws an error when the parameter does not exist or when the type is different from the specified type(s), unless parameter `def` is defined.
|
|
516
|
+
*
|
|
517
|
+
* @param name The name of the parameter.
|
|
518
|
+
* @param type The type cast of the parameters, valid types are `[null, "boolean", "number", "string", "array", "object"]`.
|
|
519
|
+
* @param def
|
|
520
|
+
* The default value to return when the parameter does not exist.
|
|
521
|
+
*
|
|
522
|
+
* If the parameter is not defined and `def` is `undefined` then this function will throw an error.
|
|
523
|
+
* When `def` is `undefined` errors will be thrown, when `def` is `null` and the parameter is undefined then `null` will be returned as the default value.
|
|
524
|
+
*
|
|
525
|
+
* Errors will always be thrown when the incorrect type has been sent by the user.
|
|
526
|
+
* @example
|
|
527
|
+
* ```ts
|
|
528
|
+
* const param = stream.param("myparameter", "number", 10);
|
|
529
|
+
* ```
|
|
530
|
+
* @docs
|
|
531
|
+
*/
|
|
532
|
+
param(name, type = null, def = undefined) {
|
|
533
|
+
// Parse params.
|
|
534
|
+
this._parse_params();
|
|
535
|
+
// Get value.
|
|
536
|
+
let value = this._params[name];
|
|
537
|
+
// Check type.
|
|
538
|
+
if (type != null) {
|
|
539
|
+
// Vars.
|
|
540
|
+
let is_type_array = Array.isArray(type);
|
|
541
|
+
// Wrapper funcs.
|
|
542
|
+
const type_str = () => {
|
|
543
|
+
let str = "";
|
|
544
|
+
if (type != null) {
|
|
545
|
+
str += " type ";
|
|
546
|
+
if (is_type_array) {
|
|
547
|
+
let i = 0, one_but_last_i = type.length - 2;
|
|
548
|
+
type.forEach((item, i) => {
|
|
549
|
+
str += `"${item}"`;
|
|
550
|
+
if (i < one_but_last_i) {
|
|
551
|
+
str += ", ";
|
|
552
|
+
}
|
|
553
|
+
else if (i === one_but_last_i) {
|
|
554
|
+
str += " or ";
|
|
555
|
+
}
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
else {
|
|
559
|
+
str += `"${type}"`;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
return str;
|
|
563
|
+
};
|
|
564
|
+
const type_eq_or_includes = (match) => {
|
|
565
|
+
if (is_type_array) {
|
|
566
|
+
return type.includes(match);
|
|
567
|
+
}
|
|
568
|
+
return match === type;
|
|
569
|
+
};
|
|
570
|
+
// Check undefined.
|
|
571
|
+
if (value == null || value === "") {
|
|
572
|
+
if (def !== undefined) {
|
|
573
|
+
return def;
|
|
574
|
+
}
|
|
575
|
+
throw Error(`Define parameter "${name}"${type_str()}.`);
|
|
576
|
+
}
|
|
577
|
+
// Cast the value to another type when a query string was used.
|
|
578
|
+
if (this._is_query_params && type_eq_or_includes("string") === false) {
|
|
579
|
+
if (is_type_array === false) {
|
|
580
|
+
type = [type];
|
|
581
|
+
}
|
|
582
|
+
const success = type.some((type) => {
|
|
583
|
+
// Convert to string.
|
|
584
|
+
if (type === "string") {
|
|
585
|
+
return true;
|
|
586
|
+
}
|
|
587
|
+
// Convert to null.
|
|
588
|
+
if (type === "null" && value === "null") {
|
|
589
|
+
value = null;
|
|
590
|
+
return true;
|
|
591
|
+
}
|
|
592
|
+
// Convert to boolean.
|
|
593
|
+
const is_boolean = type === "boolean";
|
|
594
|
+
if (is_boolean && value === "true") {
|
|
595
|
+
value = true;
|
|
596
|
+
return true;
|
|
597
|
+
}
|
|
598
|
+
if (is_boolean && value === "false") {
|
|
599
|
+
value = false;
|
|
600
|
+
return true;
|
|
601
|
+
}
|
|
602
|
+
// Convert to array.
|
|
603
|
+
if (type === "array") {
|
|
604
|
+
value = value.split(",");
|
|
605
|
+
return true;
|
|
606
|
+
}
|
|
607
|
+
// Convert to object.
|
|
608
|
+
if (type === "object") {
|
|
609
|
+
const split = value.split(",");
|
|
610
|
+
value = {};
|
|
611
|
+
split.forEach((item) => {
|
|
612
|
+
const pair = item.split(":");
|
|
613
|
+
value[pair[0]] = pair[1];
|
|
614
|
+
});
|
|
615
|
+
return true;
|
|
616
|
+
}
|
|
617
|
+
// Convert to numeric.
|
|
618
|
+
if (type === "number" && /^-?\d+(\.\d+)?$/.test(value)) {
|
|
619
|
+
value = parseFloat(value);
|
|
620
|
+
return true;
|
|
621
|
+
}
|
|
622
|
+
});
|
|
623
|
+
if (!success) {
|
|
624
|
+
throw Error(`Parameter "${name}" should be of${type_str()}.`);
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
// Check the type when no query params are defined since JSON.parse already parsed the types.
|
|
628
|
+
else if (!this._is_query_params) {
|
|
629
|
+
const value_type = typeof value;
|
|
630
|
+
if (!is_type_array) {
|
|
631
|
+
type = [type];
|
|
632
|
+
}
|
|
633
|
+
const success = type.some((type) => {
|
|
634
|
+
const l_is_array = type === "array";
|
|
635
|
+
const l_is_null = type === "null";
|
|
636
|
+
// Same type.
|
|
637
|
+
if (!l_is_array && !l_is_null && type === value_type) {
|
|
638
|
+
return true;
|
|
639
|
+
}
|
|
640
|
+
// Check to null.
|
|
641
|
+
if (l_is_null && value == null) {
|
|
642
|
+
return true;
|
|
643
|
+
}
|
|
644
|
+
// Convert to array.
|
|
645
|
+
if (l_is_array && Array.isArray(value)) {
|
|
646
|
+
return true;
|
|
647
|
+
}
|
|
648
|
+
});
|
|
649
|
+
if (!success) {
|
|
650
|
+
throw Error(`Parameter "${name}" should be of${type_str()}.`);
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
// Check undefined.
|
|
655
|
+
else if (value == null || value === "") {
|
|
656
|
+
if (def !== undefined) {
|
|
657
|
+
return def;
|
|
658
|
+
}
|
|
659
|
+
throw Error(`Define parameter "${name}".`);
|
|
660
|
+
}
|
|
661
|
+
// Return value.
|
|
662
|
+
return value;
|
|
663
|
+
}
|
|
664
|
+
// Get the request cookies.
|
|
665
|
+
/**
|
|
666
|
+
* Get the request's cookies
|
|
667
|
+
*
|
|
668
|
+
* @example
|
|
669
|
+
* ```ts
|
|
670
|
+
* const cookies = stream.cookies;
|
|
671
|
+
* ```
|
|
672
|
+
* @docs
|
|
673
|
+
*/
|
|
674
|
+
get cookies() {
|
|
675
|
+
if (this._cookies != null)
|
|
676
|
+
return this._cookies;
|
|
677
|
+
return this._parse_cookies();
|
|
678
|
+
}
|
|
679
|
+
// DEPRECATED since its only available for http2.
|
|
680
|
+
// /**
|
|
681
|
+
// * Check if the stream is closed.
|
|
682
|
+
// *
|
|
683
|
+
// * @example
|
|
684
|
+
// * ```ts
|
|
685
|
+
// * const ip = stream.closed;
|
|
686
|
+
// * ```
|
|
687
|
+
// * @docs
|
|
688
|
+
// */
|
|
689
|
+
// get closed(): boolean {
|
|
690
|
+
// if (!this.http2) { throw new Error("This function is only supported for http2 streams."); }
|
|
691
|
+
// return this.s!.closed;
|
|
692
|
+
// }
|
|
693
|
+
// Check if the stream is destroyed
|
|
694
|
+
/**
|
|
695
|
+
* Check if the stream is destroyed.
|
|
696
|
+
*
|
|
697
|
+
* @example
|
|
698
|
+
* ```ts
|
|
699
|
+
* const ip = stream.destroyed;
|
|
700
|
+
* ```
|
|
701
|
+
* @docs
|
|
702
|
+
*/
|
|
703
|
+
get destroyed() {
|
|
704
|
+
if (this.http2) {
|
|
705
|
+
return this.s.destroyed;
|
|
706
|
+
}
|
|
707
|
+
else {
|
|
708
|
+
return this.req.destroyed;
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
// ---------------------------------------------------------
|
|
712
|
+
// Functions.
|
|
713
|
+
// Get the authenticated uid.
|
|
714
|
+
/**
|
|
715
|
+
* Get the authenticated uid; `undefined` when the request was not authenticated.
|
|
716
|
+
*
|
|
717
|
+
* @example
|
|
718
|
+
* ```ts
|
|
719
|
+
* const uid = stream.uid;
|
|
720
|
+
* ```
|
|
721
|
+
* @docs
|
|
722
|
+
*/
|
|
723
|
+
get uid() {
|
|
724
|
+
return this._uid;
|
|
725
|
+
}
|
|
726
|
+
set uid(value) {
|
|
727
|
+
this._uid = value;
|
|
728
|
+
}
|
|
729
|
+
/**
|
|
730
|
+
* Apply templates to an in-memory body.
|
|
731
|
+
* Only applies to string bodies to avoid corrupting binary payloads.
|
|
732
|
+
*/
|
|
733
|
+
apply_templates_to_body(input, templates) {
|
|
734
|
+
// Skip when there are no templates.
|
|
735
|
+
if (templates == null || Object.keys(templates).length === 0) {
|
|
736
|
+
return input;
|
|
737
|
+
}
|
|
738
|
+
// Only apply templates to string bodies.
|
|
739
|
+
if (typeof input !== "string") {
|
|
740
|
+
return input;
|
|
741
|
+
}
|
|
742
|
+
// Replace all template keys with their stringified values.
|
|
743
|
+
let out = input;
|
|
744
|
+
for (const key of Object.keys(templates)) {
|
|
745
|
+
const value = templates[key];
|
|
746
|
+
// Convert non-string template values to a string.
|
|
747
|
+
const value_str = typeof value === "string" ? value : JSON.stringify(value);
|
|
748
|
+
// Replace all occurrences of the key.
|
|
749
|
+
out = out.split(`{{${key}}}`).join(value_str);
|
|
750
|
+
}
|
|
751
|
+
return out;
|
|
752
|
+
}
|
|
753
|
+
/**
|
|
754
|
+
* Create a transform stream that applies templates across chunk boundaries.
|
|
755
|
+
* This avoids missing replacements when a template key is split between chunks.
|
|
756
|
+
*/
|
|
757
|
+
create_template_replace_transform(templates) {
|
|
758
|
+
// Precompute keys and the longest key length for boundary-safe streaming.
|
|
759
|
+
const keys = Object.keys(templates);
|
|
760
|
+
const max_key_len = keys.reduce((max, k) => Math.max(max, k.length), 0);
|
|
761
|
+
// Keep enough tail bytes to cover a key split between chunks.
|
|
762
|
+
const keep_len = Math.max(0, max_key_len - 1);
|
|
763
|
+
// Carry tail across chunks.
|
|
764
|
+
let carry = "";
|
|
765
|
+
return new Transform({
|
|
766
|
+
transform(chunk, _enc, cb) {
|
|
767
|
+
try {
|
|
768
|
+
// Merge with carry to handle split keys across chunks.
|
|
769
|
+
const str = carry + chunk.toString("utf8");
|
|
770
|
+
// Keep a tail so we don't split a key.
|
|
771
|
+
const cut_idx = Math.max(0, str.length - keep_len);
|
|
772
|
+
const safe_head = str.slice(0, cut_idx);
|
|
773
|
+
// Persist the tail for the next chunk.
|
|
774
|
+
carry = str.slice(cut_idx);
|
|
775
|
+
// Replace templates in the safe head.
|
|
776
|
+
let out = safe_head;
|
|
777
|
+
for (const key of keys) {
|
|
778
|
+
const value = templates[key];
|
|
779
|
+
const value_str = typeof value === "string" ? value : JSON.stringify(value);
|
|
780
|
+
out = out.split(`{{${key}}}`).join(value_str);
|
|
781
|
+
}
|
|
782
|
+
cb(null, out);
|
|
783
|
+
}
|
|
784
|
+
catch (err) {
|
|
785
|
+
cb(err);
|
|
786
|
+
}
|
|
787
|
+
},
|
|
788
|
+
flush(cb) {
|
|
789
|
+
try {
|
|
790
|
+
// Flush remaining carry.
|
|
791
|
+
let out = carry;
|
|
792
|
+
for (const key of keys) {
|
|
793
|
+
const value = templates[key];
|
|
794
|
+
const value_str = typeof value === "string" ? value : JSON.stringify(value);
|
|
795
|
+
out = out.split(`{{${key}}}`).join(value_str);
|
|
796
|
+
}
|
|
797
|
+
cb(null, out);
|
|
798
|
+
}
|
|
799
|
+
catch (err) {
|
|
800
|
+
cb(err);
|
|
801
|
+
}
|
|
802
|
+
},
|
|
803
|
+
});
|
|
804
|
+
}
|
|
805
|
+
/** Create output headers for http2. */
|
|
806
|
+
create_http2_headers(status, new_headers) {
|
|
807
|
+
// Convert ResponseHeaderValue to Node-compatible header values.
|
|
808
|
+
const normalize_header_value = (v) => {
|
|
809
|
+
if (v == null)
|
|
810
|
+
return undefined;
|
|
811
|
+
if (typeof v === "boolean")
|
|
812
|
+
return v ? "true" : "false";
|
|
813
|
+
if (typeof v === "number")
|
|
814
|
+
return v;
|
|
815
|
+
if (typeof v === "string")
|
|
816
|
+
return v;
|
|
817
|
+
return String(v);
|
|
818
|
+
};
|
|
819
|
+
// Start with any headers set earlier via set_header/set_headers.
|
|
820
|
+
const out_headers = {
|
|
821
|
+
":status": status,
|
|
822
|
+
};
|
|
823
|
+
// Merge previously queued headers for http2.
|
|
824
|
+
if (!Array.isArray(this.res_headers)) {
|
|
825
|
+
for (const [k, v] of Object.entries(this.res_headers)) {
|
|
826
|
+
const nv = normalize_header_value(v);
|
|
827
|
+
if (nv !== undefined)
|
|
828
|
+
out_headers[k.toLowerCase()] = nv;
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
// Merge call-specific headers last so they win.
|
|
832
|
+
for (const [k, v] of Object.entries(new_headers)) {
|
|
833
|
+
const nv = normalize_header_value(v);
|
|
834
|
+
if (nv !== undefined)
|
|
835
|
+
out_headers[k.toLowerCase()] = nv;
|
|
836
|
+
}
|
|
837
|
+
// Attach any cookies staged via set_cookie/set_cookies.
|
|
838
|
+
if (this.res_cookies.length > 0) {
|
|
839
|
+
out_headers["set-cookie"] = this.res_cookies;
|
|
840
|
+
}
|
|
841
|
+
return out_headers;
|
|
842
|
+
}
|
|
843
|
+
/** Assign http headers to response. */
|
|
844
|
+
set_http1_headers(status, headers) {
|
|
845
|
+
if (!this.res) {
|
|
846
|
+
throw new Error("HTTP/1.1 response is missing.");
|
|
847
|
+
}
|
|
848
|
+
// Set status code.
|
|
849
|
+
this.res.statusCode = status;
|
|
850
|
+
// Set headers.
|
|
851
|
+
for (let i = 0; i < this.res_headers.length; i++) {
|
|
852
|
+
this.res.setHeader(this.res_headers[i][0].toLowerCase(), this.res_headers[i][1]);
|
|
853
|
+
}
|
|
854
|
+
Object.keys(headers).forEach((key) => {
|
|
855
|
+
const v = headers[key];
|
|
856
|
+
if (v != null) {
|
|
857
|
+
this.res?.setHeader(key.toLowerCase(), typeof v === "boolean" ? v.toString() : v);
|
|
858
|
+
}
|
|
859
|
+
});
|
|
860
|
+
}
|
|
861
|
+
/**
|
|
862
|
+
* Send a response.
|
|
863
|
+
* @example
|
|
864
|
+
* ```ts
|
|
865
|
+
* stream.send({status: 200, data: "Hello World!"});
|
|
866
|
+
* ```
|
|
867
|
+
* @docs
|
|
868
|
+
*/
|
|
869
|
+
send({ status = 200, headers = {}, data, compress = false, from_file, templates, }) {
|
|
870
|
+
// Assign sent status code.
|
|
871
|
+
this.status_code = status;
|
|
872
|
+
// The body to send as non `ResponseBody` type.
|
|
873
|
+
let body = data;
|
|
874
|
+
// Convert body primitivies to string.
|
|
875
|
+
if (typeof body === "boolean" || typeof body === "number") {
|
|
876
|
+
body = body.toString();
|
|
877
|
+
}
|
|
878
|
+
// -----------------------------------------
|
|
879
|
+
// Helpers.
|
|
880
|
+
/** Get the accept-encoding header from the request. */
|
|
881
|
+
const get_accept_encoding = () => {
|
|
882
|
+
// Prefer the cached request headers on the stream wrapper.
|
|
883
|
+
// For http2 these are the real pseudo/header map; for http1 this is req.headers.
|
|
884
|
+
const accept_encoding = this.headers?.["accept-encoding"];
|
|
885
|
+
if (typeof accept_encoding === "string") {
|
|
886
|
+
return accept_encoding;
|
|
887
|
+
}
|
|
888
|
+
// Node can sometimes provide a string[] header shape.
|
|
889
|
+
if (Array.isArray(accept_encoding) && accept_encoding.length > 0) {
|
|
890
|
+
return accept_encoding.join(", ");
|
|
891
|
+
}
|
|
892
|
+
// Fallback to the raw http1 request headers if present.
|
|
893
|
+
const req_accept_encoding = this.req?.headers?.["accept-encoding"];
|
|
894
|
+
if (typeof req_accept_encoding === "string") {
|
|
895
|
+
return req_accept_encoding;
|
|
896
|
+
}
|
|
897
|
+
if (Array.isArray(req_accept_encoding) && req_accept_encoding.length > 0) {
|
|
898
|
+
return req_accept_encoding.join(", ");
|
|
899
|
+
}
|
|
900
|
+
// Default when header is absent.
|
|
901
|
+
return "";
|
|
902
|
+
};
|
|
903
|
+
// Apply templates to in-memory bodies before any compression.
|
|
904
|
+
body = this.apply_templates_to_body(body, templates);
|
|
905
|
+
// -----------------------------------------
|
|
906
|
+
// HTTP2
|
|
907
|
+
// -----------------------------------------
|
|
908
|
+
if (this.http2) {
|
|
909
|
+
const stream = this.s;
|
|
910
|
+
// Create http headers.
|
|
911
|
+
const out_headers = this.create_http2_headers(status, headers);
|
|
912
|
+
// -------------------------------------------------------
|
|
913
|
+
// From_file fast path (http2)
|
|
914
|
+
// -------------------------------------------------------
|
|
915
|
+
if (from_file) {
|
|
916
|
+
const from_path = from_file instanceof vlib.Path ? from_file : new vlib.Path(from_file);
|
|
917
|
+
// Only apply templates when defined.
|
|
918
|
+
const needs_template_replace = templates != null && Object.keys(templates).length > 0;
|
|
919
|
+
const should_gzip = compress
|
|
920
|
+
&& get_accept_encoding().includes("gzip")
|
|
921
|
+
&& !(Utils.is_compressed_extension(from_path.extension()) ?? false);
|
|
922
|
+
// Add content type.
|
|
923
|
+
const content_type = Utils.mime_type(from_path.extension());
|
|
924
|
+
if (content_type && out_headers["content-type"] == null) {
|
|
925
|
+
out_headers["content-type"] = content_type;
|
|
926
|
+
}
|
|
927
|
+
// Only apply templates to text-like responses.
|
|
928
|
+
const is_text_response = typeof content_type === "string"
|
|
929
|
+
&& (content_type.startsWith("text/")
|
|
930
|
+
|| content_type === "application/javascript"
|
|
931
|
+
|| content_type === "application/json"
|
|
932
|
+
|| content_type === "image/svg+xml"
|
|
933
|
+
|| content_type === "application/xml"
|
|
934
|
+
|| content_type === "text/xml");
|
|
935
|
+
const should_apply_templates = needs_template_replace && is_text_response;
|
|
936
|
+
// Only set gzip headers if we actually gzip.
|
|
937
|
+
if (should_gzip) {
|
|
938
|
+
out_headers["content-encoding"] = "gzip";
|
|
939
|
+
out_headers["vary"] = "Accept-Encoding";
|
|
940
|
+
// Do not set content-length when streaming gzip.
|
|
941
|
+
delete out_headers["content-length"];
|
|
942
|
+
}
|
|
943
|
+
// Do not set content-length when template replacement is enabled.
|
|
944
|
+
if (should_apply_templates) {
|
|
945
|
+
delete out_headers["content-length"];
|
|
946
|
+
}
|
|
947
|
+
// If we are NOT gzipping and NOT replacing, use respondWithFile for best performance.
|
|
948
|
+
if (!should_gzip && !should_apply_templates && typeof stream.respondWithFile === "function") {
|
|
949
|
+
// respondWithFile handles opening/streaming internally.
|
|
950
|
+
stream.respondWithFile(from_path.toString(), out_headers, {});
|
|
951
|
+
if (debug.on(3))
|
|
952
|
+
debug("Sending http2 file response: ", status, " - file: ", from_path.toString());
|
|
953
|
+
this.finished = true;
|
|
954
|
+
return this;
|
|
955
|
+
}
|
|
956
|
+
// Manual stream for gzip and/or template replacement.
|
|
957
|
+
stream.respond(out_headers);
|
|
958
|
+
const file_read_stream = fs.createReadStream(from_path.toString());
|
|
959
|
+
const transforms = [];
|
|
960
|
+
// Replace templates on-the-fly for text-like responses.
|
|
961
|
+
if (should_apply_templates) {
|
|
962
|
+
transforms.push(this.create_template_replace_transform(templates));
|
|
963
|
+
}
|
|
964
|
+
// Stream gzip to avoid blocking the event loop.
|
|
965
|
+
if (should_gzip) {
|
|
966
|
+
transforms.push(zlib.createGzip({ level: zlib.constants.Z_BEST_COMPRESSION }));
|
|
967
|
+
}
|
|
968
|
+
// Pipe: file -> (template_replace?) -> (gzip?) -> http2 stream.
|
|
969
|
+
pipeline(file_read_stream, ...transforms, stream, (err) => {
|
|
970
|
+
if (err) {
|
|
971
|
+
// Close the stream to avoid leaking resources on pipeline error.
|
|
972
|
+
try {
|
|
973
|
+
stream.close();
|
|
974
|
+
}
|
|
975
|
+
catch { }
|
|
976
|
+
}
|
|
977
|
+
});
|
|
978
|
+
if (debug.on(3))
|
|
979
|
+
debug("Sending http2 streamed file response: ", status, " - file: ", from_path.toString());
|
|
980
|
+
this.finished = true;
|
|
981
|
+
return this;
|
|
982
|
+
}
|
|
983
|
+
// -------------------------------------------------------
|
|
984
|
+
// Normal body path (http2)
|
|
985
|
+
// -------------------------------------------------------
|
|
986
|
+
else {
|
|
987
|
+
// Is json.
|
|
988
|
+
if (body && typeof body === "object" && Buffer.isBuffer(body) === false && (body instanceof Uint8Array) === false) {
|
|
989
|
+
out_headers["content-type"] = "application/json";
|
|
990
|
+
body = JSON.stringify(body);
|
|
991
|
+
// Apply templates after stringify, since body just became a string.
|
|
992
|
+
body = this.apply_templates_to_body(body, templates);
|
|
993
|
+
}
|
|
994
|
+
// Convert objects to string (kept from your logic).
|
|
995
|
+
if (body
|
|
996
|
+
&& typeof body === "object"
|
|
997
|
+
&& !(body instanceof Buffer)
|
|
998
|
+
&& !(body instanceof Uint8Array)) {
|
|
999
|
+
body = JSON.stringify(body);
|
|
1000
|
+
// Apply templates after stringify.
|
|
1001
|
+
body = this.apply_templates_to_body(body, templates);
|
|
1002
|
+
}
|
|
1003
|
+
const should_gzip_body = compress
|
|
1004
|
+
&& !!body
|
|
1005
|
+
&& get_accept_encoding().includes("gzip");
|
|
1006
|
+
if (should_gzip_body) {
|
|
1007
|
+
out_headers["content-encoding"] = "gzip";
|
|
1008
|
+
out_headers["vary"] = "Accept-Encoding";
|
|
1009
|
+
// No content-length if we compress asynchronously.
|
|
1010
|
+
delete out_headers["content-length"];
|
|
1011
|
+
}
|
|
1012
|
+
// Respond.
|
|
1013
|
+
stream.respond(out_headers);
|
|
1014
|
+
// End.
|
|
1015
|
+
if (debug.on(3))
|
|
1016
|
+
debug("Sending response: ", status, " - has body: ", !!body);
|
|
1017
|
+
if (!body) {
|
|
1018
|
+
stream.end();
|
|
1019
|
+
this.finished = true;
|
|
1020
|
+
return this;
|
|
1021
|
+
}
|
|
1022
|
+
// gzip async (non-blocking) for in-memory bodies.
|
|
1023
|
+
else if (should_gzip_body) {
|
|
1024
|
+
const raw_buffer = (typeof body === "string")
|
|
1025
|
+
? Buffer.from(body)
|
|
1026
|
+
: (Buffer.isBuffer(body) || body instanceof Uint8Array)
|
|
1027
|
+
? Buffer.from(body)
|
|
1028
|
+
: Buffer.from(JSON.stringify(body));
|
|
1029
|
+
zlib.gzip(raw_buffer, { level: zlib.constants.Z_BEST_COMPRESSION }, (err, gz_buffer) => {
|
|
1030
|
+
if (err) {
|
|
1031
|
+
// Fallback: send uncompressed if gzip fails.
|
|
1032
|
+
stream.end(raw_buffer);
|
|
1033
|
+
return;
|
|
1034
|
+
}
|
|
1035
|
+
stream.end(gz_buffer);
|
|
1036
|
+
});
|
|
1037
|
+
this.finished = true;
|
|
1038
|
+
return this;
|
|
1039
|
+
}
|
|
1040
|
+
// Non-gzipped body path.
|
|
1041
|
+
else {
|
|
1042
|
+
if (Buffer.isBuffer(body) || body instanceof Uint8Array) {
|
|
1043
|
+
stream.end(body);
|
|
1044
|
+
}
|
|
1045
|
+
else {
|
|
1046
|
+
stream.end(Buffer.from(body));
|
|
1047
|
+
}
|
|
1048
|
+
this.finished = true;
|
|
1049
|
+
return this;
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
// -----------------------------------------
|
|
1054
|
+
// HTTP1
|
|
1055
|
+
// -----------------------------------------
|
|
1056
|
+
else {
|
|
1057
|
+
const req = this.req;
|
|
1058
|
+
const res = this.res;
|
|
1059
|
+
// Set http1 headers.
|
|
1060
|
+
this.set_http1_headers(status, headers);
|
|
1061
|
+
// -------------------------------------------------------
|
|
1062
|
+
// From_file path (http1)
|
|
1063
|
+
// -------------------------------------------------------
|
|
1064
|
+
if (from_file) {
|
|
1065
|
+
// Ensure we dont create a new path if not needed, to use caching.
|
|
1066
|
+
const from_path = from_file instanceof vlib.Path ? from_file : new vlib.Path(from_file);
|
|
1067
|
+
// Add content type.
|
|
1068
|
+
const content_type = Utils.mime_type(from_path.extension());
|
|
1069
|
+
if (content_type) {
|
|
1070
|
+
res.setHeader("Content-Type", content_type);
|
|
1071
|
+
}
|
|
1072
|
+
// Only apply templates when defined.
|
|
1073
|
+
const needs_template_replace = templates != null && Object.keys(templates).length > 0;
|
|
1074
|
+
// Only apply templates to text-like responses.
|
|
1075
|
+
const is_text_response = typeof content_type === "string"
|
|
1076
|
+
&& (content_type.startsWith("text/")
|
|
1077
|
+
|| content_type === "application/javascript"
|
|
1078
|
+
|| content_type === "application/json"
|
|
1079
|
+
|| content_type === "image/svg+xml"
|
|
1080
|
+
|| content_type === "application/xml"
|
|
1081
|
+
|| content_type === "text/xml");
|
|
1082
|
+
const should_apply_templates = needs_template_replace && is_text_response;
|
|
1083
|
+
const should_gzip = compress
|
|
1084
|
+
&& get_accept_encoding().includes("gzip")
|
|
1085
|
+
&& !(Utils.is_compressed_extension(from_path.extension()) ?? false);
|
|
1086
|
+
// If we gzip, do not set content-length (streaming).
|
|
1087
|
+
if (should_gzip) {
|
|
1088
|
+
res.setHeader("Content-Encoding", "gzip");
|
|
1089
|
+
res.setHeader("Vary", "Accept-Encoding");
|
|
1090
|
+
res.removeHeader("Content-Length");
|
|
1091
|
+
}
|
|
1092
|
+
else if (!should_apply_templates) {
|
|
1093
|
+
// Only set content-length when no transforms are applied.
|
|
1094
|
+
try {
|
|
1095
|
+
if (from_path.is_file()) {
|
|
1096
|
+
res.setHeader("Content-Length", from_path.size);
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
catch {
|
|
1100
|
+
// Ignore stat errors, stream will error if file missing.
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
// If we replace templates, content-length is not reliable.
|
|
1104
|
+
if (should_apply_templates) {
|
|
1105
|
+
res.removeHeader("Content-Length");
|
|
1106
|
+
}
|
|
1107
|
+
const file_read_stream = fs.createReadStream(from_path.toString());
|
|
1108
|
+
const transforms = [];
|
|
1109
|
+
// Replace templates on-the-fly for text-like responses.
|
|
1110
|
+
if (should_apply_templates) {
|
|
1111
|
+
transforms.push(this.create_template_replace_transform(templates));
|
|
1112
|
+
}
|
|
1113
|
+
// Stream gzip to avoid blocking event loop.
|
|
1114
|
+
if (should_gzip) {
|
|
1115
|
+
transforms.push(zlib.createGzip({ level: zlib.constants.Z_BEST_COMPRESSION }));
|
|
1116
|
+
}
|
|
1117
|
+
// Pipe: file -> (template_replace?) -> (gzip?) -> http1 response.
|
|
1118
|
+
pipeline(file_read_stream, ...transforms, res, (err) => {
|
|
1119
|
+
if (err) {
|
|
1120
|
+
// Destroy the response to stop work on error.
|
|
1121
|
+
try {
|
|
1122
|
+
res.destroy(err);
|
|
1123
|
+
}
|
|
1124
|
+
catch { }
|
|
1125
|
+
}
|
|
1126
|
+
});
|
|
1127
|
+
if (debug.on(3))
|
|
1128
|
+
debug("Sending http1 streamed file response: ", status, " - file: ", from_path.toString());
|
|
1129
|
+
this.finished = true;
|
|
1130
|
+
return this;
|
|
1131
|
+
}
|
|
1132
|
+
// -------------------------------------------------------
|
|
1133
|
+
// Normal body path (http1)
|
|
1134
|
+
// -------------------------------------------------------
|
|
1135
|
+
else {
|
|
1136
|
+
// Convert data.
|
|
1137
|
+
if (body && typeof body === "object" && Buffer.isBuffer(body) === false && (body instanceof Uint8Array) === false) {
|
|
1138
|
+
res.setHeader("Content-Type", "application/json");
|
|
1139
|
+
body = JSON.stringify(body);
|
|
1140
|
+
// Apply templates after stringify.
|
|
1141
|
+
body = this.apply_templates_to_body(body, templates);
|
|
1142
|
+
}
|
|
1143
|
+
const should_gzip_body = compress
|
|
1144
|
+
&& !!body
|
|
1145
|
+
&& get_accept_encoding().includes("gzip");
|
|
1146
|
+
// gzip async (non-blocking)
|
|
1147
|
+
if (should_gzip_body) {
|
|
1148
|
+
res.setHeader("Content-Encoding", "gzip");
|
|
1149
|
+
res.setHeader("Vary", "Accept-Encoding");
|
|
1150
|
+
res.removeHeader("Content-Length");
|
|
1151
|
+
const raw_buffer = (typeof body === "string")
|
|
1152
|
+
? Buffer.from(body)
|
|
1153
|
+
: (Buffer.isBuffer(body) || body instanceof Uint8Array)
|
|
1154
|
+
? Buffer.from(body)
|
|
1155
|
+
: Buffer.from(JSON.stringify(body));
|
|
1156
|
+
zlib.gzip(raw_buffer, { level: zlib.constants.Z_BEST_COMPRESSION }, (err, gz_buffer) => {
|
|
1157
|
+
if (err) {
|
|
1158
|
+
res.end(raw_buffer);
|
|
1159
|
+
return;
|
|
1160
|
+
}
|
|
1161
|
+
res.end(gz_buffer);
|
|
1162
|
+
});
|
|
1163
|
+
if (debug.on(3))
|
|
1164
|
+
debug("Sending http1 response: ", status, " - has body: ", !!body, " - gzip: true");
|
|
1165
|
+
}
|
|
1166
|
+
// Set data.
|
|
1167
|
+
else if (body) {
|
|
1168
|
+
res.end(body); // Do not use toString() here or it will cause issues with writing binary data.
|
|
1169
|
+
}
|
|
1170
|
+
else {
|
|
1171
|
+
res.end();
|
|
1172
|
+
}
|
|
1173
|
+
// Set as finished.
|
|
1174
|
+
this.finished = true;
|
|
1175
|
+
return this;
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
// Send a successs response.
|
|
1180
|
+
/**
|
|
1181
|
+
* Send a response
|
|
1182
|
+
*
|
|
1183
|
+
* @param options The response options.
|
|
1184
|
+
* @param options.status The response status.
|
|
1185
|
+
* @param options.headers The response headers.
|
|
1186
|
+
* @param options.data The data of the response body to send.
|
|
1187
|
+
* @param options.compress Whether the response should be gzip-compressed.
|
|
1188
|
+
* @example
|
|
1189
|
+
* ```ts
|
|
1190
|
+
* stream.success({data: "Hello World!"});
|
|
1191
|
+
* ```
|
|
1192
|
+
* @docs
|
|
1193
|
+
*/
|
|
1194
|
+
success({ status = 200, headers = {}, data, from_file, compress = false } = {}) {
|
|
1195
|
+
if (debug.on(3))
|
|
1196
|
+
debug("Sending [success] response: ", status, " - body: ", data);
|
|
1197
|
+
return this.send({ status, headers, data, compress, from_file });
|
|
1198
|
+
}
|
|
1199
|
+
// Send an error response.
|
|
1200
|
+
/**
|
|
1201
|
+
* Send an error response
|
|
1202
|
+
*
|
|
1203
|
+
* @param options The error response options.
|
|
1204
|
+
* @param options.message The error message.
|
|
1205
|
+
* @param options.type The error type.
|
|
1206
|
+
* @param options.invalid_fields The invalid fields when validation fails.
|
|
1207
|
+
* @param options.status The response status.
|
|
1208
|
+
* @param options.headers The response headers.
|
|
1209
|
+
* @param options.compress Whether the response should be gzip-compressed.
|
|
1210
|
+
* @param options.data Optional data to include in the error response, nested in the JSON response under field `data`.
|
|
1211
|
+
* @example
|
|
1212
|
+
* ```ts
|
|
1213
|
+
* stream.error({ message: "Some error occurred", status: 400 });
|
|
1214
|
+
* ```
|
|
1215
|
+
* @docs
|
|
1216
|
+
*/
|
|
1217
|
+
error({ message, type = "APIError", invalid_fields = {}, status = 500, headers = {}, compress = false, data, }) {
|
|
1218
|
+
if (debug.on(3))
|
|
1219
|
+
debug("Sending [error] response: ", status, " - message: ", message);
|
|
1220
|
+
const api_error = {
|
|
1221
|
+
error: {
|
|
1222
|
+
type,
|
|
1223
|
+
message,
|
|
1224
|
+
status,
|
|
1225
|
+
invalid_fields,
|
|
1226
|
+
},
|
|
1227
|
+
data,
|
|
1228
|
+
};
|
|
1229
|
+
return this.send({ status, headers, compress, data: api_error });
|
|
1230
|
+
}
|
|
1231
|
+
/**
|
|
1232
|
+
* Stream a response through a transform pipeline with an optional gzip step and a hard byte limit.
|
|
1233
|
+
*
|
|
1234
|
+
* @param options Pipeline options.
|
|
1235
|
+
* @param options.status The HTTP status code to send.
|
|
1236
|
+
* @param options.headers The response headers to send.
|
|
1237
|
+
* @param options.body The readable stream to pipe into the response.
|
|
1238
|
+
* @param options.transforms Optional transform streams applied in order.
|
|
1239
|
+
* @param options.compress When true, gzip-compresses the streamed response if the client supports it.
|
|
1240
|
+
* @param options.max_bytes The maximum number of bytes allowed to be written to the client.
|
|
1241
|
+
* Set to `-1` for unlimited (use with caution).
|
|
1242
|
+
*/
|
|
1243
|
+
pipeline({ status = 200, headers = {}, body, transforms = [], compress = false, max_bytes = 10 * 1024 * 1024, }) {
|
|
1244
|
+
// Prevent double-sending on the same stream wrapper.
|
|
1245
|
+
if (this.finished) {
|
|
1246
|
+
throw new Error("Cannot pipeline a response that has already been finished.");
|
|
1247
|
+
}
|
|
1248
|
+
// Validate the status code early for predictable responses.
|
|
1249
|
+
if (!Number.isInteger(status) || status < 100 || status > 599) {
|
|
1250
|
+
throw new Error("Invalid status code.");
|
|
1251
|
+
}
|
|
1252
|
+
// Validate the max_bytes limit to prevent unbounded streaming.
|
|
1253
|
+
if (!Number.isFinite(max_bytes)) {
|
|
1254
|
+
throw new Error("Invalid max_bytes value.");
|
|
1255
|
+
}
|
|
1256
|
+
// Validate transform list to avoid accidental misuse and runaway pipelines.
|
|
1257
|
+
if (!Array.isArray(transforms) || transforms.length > 32) {
|
|
1258
|
+
throw new Error("Invalid transforms configuration.");
|
|
1259
|
+
}
|
|
1260
|
+
// Assign the sent status code for bookkeeping.
|
|
1261
|
+
this.status_code = status;
|
|
1262
|
+
// Mark as finished once we start writing headers and streaming.
|
|
1263
|
+
this.finished = true;
|
|
1264
|
+
// Create all streams list in the order they should be applied.
|
|
1265
|
+
const all_streams = [body, ...transforms];
|
|
1266
|
+
// Resolve accept-encoding from request headers for gzip negotiation.
|
|
1267
|
+
const accept_encoding_header = this.headers?.["accept-encoding"];
|
|
1268
|
+
const accept_encoding = typeof accept_encoding_header === "string"
|
|
1269
|
+
? accept_encoding_header
|
|
1270
|
+
: Array.isArray(accept_encoding_header)
|
|
1271
|
+
? accept_encoding_header.join(", ")
|
|
1272
|
+
: "";
|
|
1273
|
+
// Detect if a content-encoding is already set to avoid double-compressing.
|
|
1274
|
+
const has_content_encoding = (() => {
|
|
1275
|
+
for (const k of Object.keys(headers ?? {})) {
|
|
1276
|
+
if (k.toLowerCase() === "content-encoding")
|
|
1277
|
+
return true;
|
|
1278
|
+
}
|
|
1279
|
+
const existing = this.get_header("content-encoding");
|
|
1280
|
+
return existing != null;
|
|
1281
|
+
})();
|
|
1282
|
+
// Decide whether to gzip based on client support and existing encoding.
|
|
1283
|
+
const should_gzip = compress === true && accept_encoding.includes("gzip") && !has_content_encoding;
|
|
1284
|
+
// Append gzip as a transform so we stream-compress without buffering.
|
|
1285
|
+
if (should_gzip) {
|
|
1286
|
+
all_streams.push(zlib.createGzip({ level: zlib.constants.Z_BEST_COMPRESSION }));
|
|
1287
|
+
}
|
|
1288
|
+
// Create a hard byte limiter to protect memory/bandwidth and stop abuse.
|
|
1289
|
+
if (max_bytes >= 0) {
|
|
1290
|
+
let written = 0;
|
|
1291
|
+
const limiter = new Transform({
|
|
1292
|
+
transform(chunk, _enc, cb) {
|
|
1293
|
+
written += chunk.length;
|
|
1294
|
+
if (written > max_bytes) {
|
|
1295
|
+
cb(new Error("Response exceeded max_bytes."));
|
|
1296
|
+
return;
|
|
1297
|
+
}
|
|
1298
|
+
cb(null, chunk);
|
|
1299
|
+
},
|
|
1300
|
+
});
|
|
1301
|
+
all_streams.push(limiter);
|
|
1302
|
+
}
|
|
1303
|
+
// Prepare a cleanup routine to stop work when the client disconnects or a stream errors.
|
|
1304
|
+
const cleanup = (err) => {
|
|
1305
|
+
for (const s of all_streams) {
|
|
1306
|
+
if ("destroy" in s && typeof s.destroy === "function") {
|
|
1307
|
+
s.destroy(err);
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
};
|
|
1311
|
+
// Send headers and start piping depending on protocol.
|
|
1312
|
+
if (this.http2) {
|
|
1313
|
+
// Ensure the underlying HTTP/2 stream exists.
|
|
1314
|
+
const h2 = this.s;
|
|
1315
|
+
if (!h2) {
|
|
1316
|
+
throw new Error("HTTP/2 stream is missing.");
|
|
1317
|
+
}
|
|
1318
|
+
// Start with any headers set earlier via set_header/set_headers.
|
|
1319
|
+
const out_headers = this.create_http2_headers(status, headers);
|
|
1320
|
+
// Set gzip headers only when we actually gzip.
|
|
1321
|
+
if (should_gzip) {
|
|
1322
|
+
out_headers["content-encoding"] = "gzip";
|
|
1323
|
+
out_headers["vary"] = "Accept-Encoding";
|
|
1324
|
+
}
|
|
1325
|
+
// Strip content-length because streaming/transforms make it unreliable.
|
|
1326
|
+
delete out_headers["content-length"];
|
|
1327
|
+
// Write headers once, before streaming data.
|
|
1328
|
+
h2.respond(out_headers);
|
|
1329
|
+
// Abort work when the client disconnects.
|
|
1330
|
+
h2.once("close", () => { cleanup(new Error("Client disconnected.")); });
|
|
1331
|
+
// Pipe: body -> transforms... -> (gzip?) -> (limiter?) -> http2 stream.
|
|
1332
|
+
pipeline(...all_streams, h2, (err) => {
|
|
1333
|
+
if (err) {
|
|
1334
|
+
// Destroy the stream to stop further writes on error.
|
|
1335
|
+
cleanup(err instanceof Error ? err : new Error("Pipeline failed."));
|
|
1336
|
+
try {
|
|
1337
|
+
h2.close();
|
|
1338
|
+
}
|
|
1339
|
+
catch { }
|
|
1340
|
+
}
|
|
1341
|
+
});
|
|
1342
|
+
return this;
|
|
1343
|
+
}
|
|
1344
|
+
else {
|
|
1345
|
+
// HTTP/1.1 response path.
|
|
1346
|
+
const res = this.res;
|
|
1347
|
+
if (!res) {
|
|
1348
|
+
throw new Error("HTTP/1.1 response is missing.");
|
|
1349
|
+
}
|
|
1350
|
+
// Set http1 headers.
|
|
1351
|
+
this.set_http1_headers(status, headers);
|
|
1352
|
+
// Set gzip headers only when we actually gzip.
|
|
1353
|
+
if (should_gzip) {
|
|
1354
|
+
res.setHeader("Content-Encoding", "gzip");
|
|
1355
|
+
res.setHeader("Vary", "Accept-Encoding");
|
|
1356
|
+
}
|
|
1357
|
+
// Strip content-length because streaming/transforms make it unreliable.
|
|
1358
|
+
res.removeHeader("Content-Length");
|
|
1359
|
+
// Abort work when the client disconnects.
|
|
1360
|
+
res.once("close", () => { cleanup(new Error("Client disconnected.")); });
|
|
1361
|
+
// Pipe: body -> transforms... -> (gzip?) -> (limiter?) -> http1 response.
|
|
1362
|
+
pipeline(...all_streams, res, (err) => {
|
|
1363
|
+
if (err) {
|
|
1364
|
+
// Destroy the response to stop further writes on error.
|
|
1365
|
+
cleanup(err instanceof Error ? err : new Error("Pipeline failed."));
|
|
1366
|
+
try {
|
|
1367
|
+
res.destroy();
|
|
1368
|
+
}
|
|
1369
|
+
catch { }
|
|
1370
|
+
}
|
|
1371
|
+
});
|
|
1372
|
+
}
|
|
1373
|
+
return this;
|
|
1374
|
+
}
|
|
1375
|
+
// Set headers.
|
|
1376
|
+
/**
|
|
1377
|
+
* Add a new header to the response data.
|
|
1378
|
+
*
|
|
1379
|
+
* @param name The header name.
|
|
1380
|
+
* @param value The header value.
|
|
1381
|
+
* @example
|
|
1382
|
+
* ```ts
|
|
1383
|
+
* stream.set_header("Connection", "close");
|
|
1384
|
+
* ```
|
|
1385
|
+
* @docs
|
|
1386
|
+
*/
|
|
1387
|
+
set_header(name, value) {
|
|
1388
|
+
name = name.toLowerCase();
|
|
1389
|
+
if (this.http2) {
|
|
1390
|
+
this.res_headers[name] = value;
|
|
1391
|
+
}
|
|
1392
|
+
else {
|
|
1393
|
+
this.res_headers.append([name, value]);
|
|
1394
|
+
}
|
|
1395
|
+
return this;
|
|
1396
|
+
}
|
|
1397
|
+
// Set headers.
|
|
1398
|
+
/**
|
|
1399
|
+
* Add new headers to the response data.
|
|
1400
|
+
*
|
|
1401
|
+
* @param headers The new response headers.
|
|
1402
|
+
* @example
|
|
1403
|
+
* ```ts
|
|
1404
|
+
* stream.set_headers({"Connection": "close"});
|
|
1405
|
+
* ```
|
|
1406
|
+
* @docs
|
|
1407
|
+
*/
|
|
1408
|
+
set_headers(headers = {}) {
|
|
1409
|
+
if (headers == null) {
|
|
1410
|
+
return this;
|
|
1411
|
+
}
|
|
1412
|
+
if (this.http2) {
|
|
1413
|
+
Object.keys(headers).forEach((key) => {
|
|
1414
|
+
this.res_headers[key.toLowerCase()] = headers[key];
|
|
1415
|
+
});
|
|
1416
|
+
}
|
|
1417
|
+
else {
|
|
1418
|
+
Object.keys(headers).forEach((key) => {
|
|
1419
|
+
this.res_headers.append([key.toLowerCase(), headers[key]]);
|
|
1420
|
+
});
|
|
1421
|
+
}
|
|
1422
|
+
return this;
|
|
1423
|
+
}
|
|
1424
|
+
/**
|
|
1425
|
+
* Get an added response header.
|
|
1426
|
+
*
|
|
1427
|
+
* @param name The header name.
|
|
1428
|
+
* @example
|
|
1429
|
+
* ```ts
|
|
1430
|
+
* stream.get_header("Connection");
|
|
1431
|
+
* ```
|
|
1432
|
+
* @docs
|
|
1433
|
+
*/
|
|
1434
|
+
get_header(name) {
|
|
1435
|
+
name = name.toLowerCase();
|
|
1436
|
+
if (this.http2) {
|
|
1437
|
+
return this.res_headers[name];
|
|
1438
|
+
}
|
|
1439
|
+
else {
|
|
1440
|
+
return this.res_headers.find((h) => h[0] === name)?.[1];
|
|
1441
|
+
}
|
|
1442
|
+
}
|
|
1443
|
+
/**
|
|
1444
|
+
* Remove header names from the response data.
|
|
1445
|
+
*
|
|
1446
|
+
* @param names The header names to remove.
|
|
1447
|
+
* @example
|
|
1448
|
+
* ```ts
|
|
1449
|
+
* stream.remove_header("Connection", "User-Agent");
|
|
1450
|
+
* ```
|
|
1451
|
+
* @docs
|
|
1452
|
+
*/
|
|
1453
|
+
remove_header(...names) {
|
|
1454
|
+
// Normalize header names.
|
|
1455
|
+
names = names.map((n) => n.toLowerCase());
|
|
1456
|
+
if (this.http1) {
|
|
1457
|
+
const headers = [];
|
|
1458
|
+
for (let i = 0; i < this.res_headers.length; i++) {
|
|
1459
|
+
if (!names.includes(this.res_headers[i][0])) {
|
|
1460
|
+
headers.push(this.res_headers[i]);
|
|
1461
|
+
}
|
|
1462
|
+
}
|
|
1463
|
+
this.res_headers = headers;
|
|
1464
|
+
}
|
|
1465
|
+
else {
|
|
1466
|
+
for (let i = 0; i < names.length; i++) {
|
|
1467
|
+
delete this.res_headers[names[i]];
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1470
|
+
return this;
|
|
1471
|
+
}
|
|
1472
|
+
/**
|
|
1473
|
+
* Alias of {@link remove_header}.
|
|
1474
|
+
*
|
|
1475
|
+
* @param names The header names to remove.
|
|
1476
|
+
*/
|
|
1477
|
+
remove_headers(...names) {
|
|
1478
|
+
return this.remove_header(...names);
|
|
1479
|
+
}
|
|
1480
|
+
/**
|
|
1481
|
+
* Set a cookie to be sent with the response.
|
|
1482
|
+
*
|
|
1483
|
+
* Accepts either:
|
|
1484
|
+
* 1) a pre-built cookie header string (used as-is, no validation), or
|
|
1485
|
+
* 2) a structured object describing the cookie, from which a standards-compliant
|
|
1486
|
+
* cookie string will be generated.
|
|
1487
|
+
*
|
|
1488
|
+
* If a cookie with the same name already exists in the pending response list,
|
|
1489
|
+
* it will be replaced.
|
|
1490
|
+
*
|
|
1491
|
+
* @warning Cookies are only included in the response when using `send()`,
|
|
1492
|
+
* `success()` or `error()`.
|
|
1493
|
+
*
|
|
1494
|
+
* @example
|
|
1495
|
+
* ```ts
|
|
1496
|
+
* stream.set_cookie("sid=abc123; Path=/; SameSite=Lax; Secure; HttpOnly");
|
|
1497
|
+
*
|
|
1498
|
+
* stream.set_cookie({
|
|
1499
|
+
* name: "sid",
|
|
1500
|
+
* value: session_id,
|
|
1501
|
+
* http_only: true,
|
|
1502
|
+
* secure: true,
|
|
1503
|
+
* same_site: "Lax",
|
|
1504
|
+
* path: "/",
|
|
1505
|
+
* max_age: 60 * 60 * 24 * 14,
|
|
1506
|
+
* });
|
|
1507
|
+
* ```
|
|
1508
|
+
*/
|
|
1509
|
+
set_cookie(cookie) {
|
|
1510
|
+
// If the user provided a raw cookie string, trust it and use it as-is.
|
|
1511
|
+
if (typeof cookie === "string") {
|
|
1512
|
+
const cookie_str = cookie.trim();
|
|
1513
|
+
const name_end = cookie_str.indexOf("=");
|
|
1514
|
+
if (name_end !== -1) {
|
|
1515
|
+
const name = cookie_str.substring(0, name_end);
|
|
1516
|
+
for (let i = 0; i < this.res_cookies.length; i++) {
|
|
1517
|
+
if (this.res_cookies[i].startsWith(name)) {
|
|
1518
|
+
this.res_cookies[i] = cookie_str;
|
|
1519
|
+
return this;
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
}
|
|
1523
|
+
this.res_cookies.push(cookie_str);
|
|
1524
|
+
return this;
|
|
1525
|
+
}
|
|
1526
|
+
// Structured cookie path (commercial-grade, predictable, minimal validation)
|
|
1527
|
+
const { name, value, path = "/", domain, max_age, expires, secure, http_only, same_site, prefix, extra, } = cookie;
|
|
1528
|
+
if (!name || typeof name !== "string") {
|
|
1529
|
+
throw new Error("set_cookie: cookie.name must be a non-empty string");
|
|
1530
|
+
}
|
|
1531
|
+
const full_name = `${prefix ?? ""}${name}`;
|
|
1532
|
+
// Enforce prefix rules (light but correct)
|
|
1533
|
+
if (prefix === "__Host-") {
|
|
1534
|
+
if (domain) {
|
|
1535
|
+
throw new Error("__Host- cookies must not include a domain attribute");
|
|
1536
|
+
}
|
|
1537
|
+
if (path !== "/") {
|
|
1538
|
+
throw new Error("__Host- cookies must have path='/'");
|
|
1539
|
+
}
|
|
1540
|
+
if (!secure) {
|
|
1541
|
+
throw new Error("__Host- cookies require secure=true");
|
|
1542
|
+
}
|
|
1543
|
+
}
|
|
1544
|
+
if (prefix === "__Secure-" && !secure) {
|
|
1545
|
+
throw new Error("__Secure- cookies require secure=true");
|
|
1546
|
+
}
|
|
1547
|
+
const encoded_value = value === null || typeof value === "undefined"
|
|
1548
|
+
? ""
|
|
1549
|
+
: encodeURIComponent(String(value));
|
|
1550
|
+
const parts = [];
|
|
1551
|
+
parts.push(`${full_name}=${encoded_value}`);
|
|
1552
|
+
if (path)
|
|
1553
|
+
parts.push(`Path=${path}`);
|
|
1554
|
+
if (domain)
|
|
1555
|
+
parts.push(`Domain=${domain}`);
|
|
1556
|
+
if (typeof max_age === "number" && Number.isFinite(max_age)) {
|
|
1557
|
+
parts.push(`Max-Age=${Math.trunc(max_age)}`);
|
|
1558
|
+
}
|
|
1559
|
+
if (expires) {
|
|
1560
|
+
const exp = expires instanceof Date ? expires.toUTCString() : String(expires).trim();
|
|
1561
|
+
if (exp)
|
|
1562
|
+
parts.push(`Expires=${exp}`);
|
|
1563
|
+
}
|
|
1564
|
+
if (secure)
|
|
1565
|
+
parts.push("Secure");
|
|
1566
|
+
if (http_only)
|
|
1567
|
+
parts.push("HttpOnly");
|
|
1568
|
+
if (same_site)
|
|
1569
|
+
parts.push(`SameSite=${same_site}`);
|
|
1570
|
+
if (extra && Array.isArray(extra)) {
|
|
1571
|
+
for (const attr of extra) {
|
|
1572
|
+
const trimmed = String(attr).trim();
|
|
1573
|
+
if (trimmed)
|
|
1574
|
+
parts.push(trimmed);
|
|
1575
|
+
}
|
|
1576
|
+
}
|
|
1577
|
+
const cookie_str = parts.join("; ");
|
|
1578
|
+
const name_end = cookie_str.indexOf("=");
|
|
1579
|
+
if (name_end !== -1) {
|
|
1580
|
+
const existing_name = cookie_str.substring(0, name_end);
|
|
1581
|
+
for (let i = 0; i < this.res_cookies.length; i++) {
|
|
1582
|
+
if (this.res_cookies[i].startsWith(existing_name)) {
|
|
1583
|
+
this.res_cookies[i] = cookie_str;
|
|
1584
|
+
return this;
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
this.res_cookies.push(cookie_str);
|
|
1589
|
+
return this;
|
|
1590
|
+
}
|
|
1591
|
+
// Set cookies.
|
|
1592
|
+
/**
|
|
1593
|
+
* Set cookies that will be sent with the response.
|
|
1594
|
+
*
|
|
1595
|
+
* @warning Will only be added to the response when the user uses `send()`, `success()` or `error()`.
|
|
1596
|
+
* @param cookies The cookie strings.
|
|
1597
|
+
* @example
|
|
1598
|
+
* ```ts
|
|
1599
|
+
* stream.set_cookies("MyCookie1=Hello World;", "MyCookie2=Hello Universe;");
|
|
1600
|
+
* ```
|
|
1601
|
+
* @docs
|
|
1602
|
+
*/
|
|
1603
|
+
set_cookies(...cookies) {
|
|
1604
|
+
for (let i = 0; i < cookies.length; i++) {
|
|
1605
|
+
this.set_cookie(cookies[i]);
|
|
1606
|
+
}
|
|
1607
|
+
return this;
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1610
|
+
;
|
|
1611
|
+
;
|