tezx 3.0.9-beta → 3.0.11-beta
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/bun/index.d.ts +4 -2
- package/bun/index.js +3 -2
- package/bun/ws.d.ts +37 -0
- package/bun/ws.js +23 -0
- package/cjs/bun/index.js +37 -5
- package/cjs/bun/ws.js +25 -0
- package/cjs/core/context.js +86 -88
- package/cjs/core/error.js +41 -0
- package/cjs/core/request.js +6 -6
- package/cjs/core/router.js +6 -6
- package/cjs/core/server.js +45 -63
- package/cjs/index.js +5 -2
- package/cjs/middleware/basic-auth.js +28 -54
- package/cjs/middleware/bearer-auth.js +34 -0
- package/cjs/middleware/cors.js +14 -24
- package/cjs/middleware/index.js +25 -0
- package/cjs/middleware/logger.js +6 -3
- package/cjs/middleware/pagination.js +1 -1
- package/cjs/middleware/powered-by.js +1 -1
- package/cjs/middleware/rate-limiter.js +20 -7
- package/cjs/middleware/request-id.js +4 -7
- package/cjs/middleware/sanitize-headers.js +8 -40
- package/cjs/middleware/xss-protection.js +2 -6
- package/cjs/registry/RadixRouter.js +72 -23
- package/cjs/utils/cookie.js +1 -1
- package/cjs/utils/rateLimit.js +2 -2
- package/cjs/utils/regexRouter.js +1 -0
- package/cjs/utils/response.js +21 -30
- package/core/context.d.ts +68 -68
- package/core/context.js +87 -89
- package/core/error.d.ts +95 -0
- package/core/error.js +37 -0
- package/core/request.d.ts +2 -2
- package/core/request.js +6 -6
- package/core/router.d.ts +11 -6
- package/core/router.js +6 -6
- package/core/server.js +45 -63
- package/index.d.ts +5 -3
- package/index.js +4 -2
- package/middleware/basic-auth.d.ts +38 -66
- package/middleware/basic-auth.js +28 -54
- package/middleware/bearer-auth.d.ts +52 -0
- package/middleware/bearer-auth.js +30 -0
- package/middleware/cors.d.ts +7 -21
- package/middleware/cors.js +14 -24
- package/middleware/index.d.ts +9 -0
- package/middleware/index.js +9 -0
- package/middleware/logger.d.ts +3 -1
- package/middleware/logger.js +6 -3
- package/middleware/pagination.d.ts +8 -6
- package/middleware/pagination.js +1 -1
- package/middleware/powered-by.js +1 -1
- package/middleware/rate-limiter.d.ts +0 -4
- package/middleware/rate-limiter.js +20 -7
- package/middleware/request-id.d.ts +1 -1
- package/middleware/request-id.js +3 -6
- package/middleware/sanitize-headers.d.ts +3 -11
- package/middleware/sanitize-headers.js +8 -40
- package/middleware/xss-protection.d.ts +1 -1
- package/middleware/xss-protection.js +1 -5
- package/package.json +6 -1
- package/registry/RadixRouter.js +72 -23
- package/types/index.d.ts +2 -1
- package/utils/cookie.js +1 -1
- package/utils/rateLimit.d.ts +1 -2
- package/utils/rateLimit.js +2 -2
- package/utils/regexRouter.js +1 -0
- package/utils/response.d.ts +12 -14
- package/utils/response.js +20 -29
- package/cjs/middleware/cache-control.js +0 -93
- package/cjs/middleware/detect-bot.js +0 -66
- package/cjs/middleware/detect-locale.js +0 -45
- package/cjs/middleware/i18n.js +0 -93
- package/cjs/middleware/lazy-loader.js +0 -74
- package/cjs/middleware/request-timeout.js +0 -43
- package/cjs/middleware/secure-headers.js +0 -43
- package/middleware/cache-control.d.ts +0 -56
- package/middleware/cache-control.js +0 -56
- package/middleware/detect-bot.d.ts +0 -111
- package/middleware/detect-bot.js +0 -62
- package/middleware/detect-locale.d.ts +0 -56
- package/middleware/detect-locale.js +0 -41
- package/middleware/i18n.d.ts +0 -102
- package/middleware/i18n.js +0 -89
- package/middleware/lazy-loader.d.ts +0 -73
- package/middleware/lazy-loader.js +0 -70
- package/middleware/request-timeout.d.ts +0 -26
- package/middleware/request-timeout.js +0 -39
- package/middleware/secure-headers.d.ts +0 -78
- package/middleware/secure-headers.js +0 -39
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Middleware } from "../types/index.js";
|
|
1
|
+
import { Middleware, ResHeaderKey } from "../types/index.js";
|
|
2
2
|
export type SanitizeHeadersOptions = {
|
|
3
3
|
/**
|
|
4
4
|
* 🟢 Whitelist of allowed headers (case-insensitive)
|
|
@@ -6,22 +6,14 @@ export type SanitizeHeadersOptions = {
|
|
|
6
6
|
* @example
|
|
7
7
|
* whitelist: ['content-type', 'authorization'] // Only allow these headers
|
|
8
8
|
*/
|
|
9
|
-
whitelist?:
|
|
9
|
+
whitelist?: ResHeaderKey[];
|
|
10
10
|
/**
|
|
11
11
|
* 🔴 Blacklist of disallowed headers (case-insensitive)
|
|
12
12
|
* @default [] (block none if empty)
|
|
13
13
|
* @example
|
|
14
14
|
* blacklist: ['x-powered-by', 'server'] // Block server info headers
|
|
15
15
|
*/
|
|
16
|
-
blacklist?:
|
|
17
|
-
/**
|
|
18
|
-
* 🟠 Allow potentially unsafe characters in header values
|
|
19
|
-
* @default false
|
|
20
|
-
* @warning Enabling this may reduce security
|
|
21
|
-
* @example
|
|
22
|
-
* allowUnsafeCharacters: true // Allow CR/LF in headers
|
|
23
|
-
*/
|
|
24
|
-
allowUnsafeCharacters?: boolean;
|
|
16
|
+
blacklist?: ResHeaderKey[];
|
|
25
17
|
};
|
|
26
18
|
/**
|
|
27
19
|
* 🧼 Middleware to sanitize HTTP headers for security and compliance
|
|
@@ -1,50 +1,18 @@
|
|
|
1
|
-
import { GlobalConfig } from "../core/config.js";
|
|
2
1
|
const sanitizeHeaders = (options = {}) => {
|
|
3
|
-
const { whitelist = [], blacklist = [],
|
|
2
|
+
const { whitelist = [], blacklist = [], } = options;
|
|
4
3
|
const normalizedWhitelist = whitelist.map((h) => h.toLowerCase());
|
|
5
4
|
const normalizedBlacklist = blacklist.map((h) => h.toLowerCase());
|
|
5
|
+
let lWhite = normalizedWhitelist.length;
|
|
6
6
|
return async function sanitizeHeaders(ctx, next) {
|
|
7
|
-
|
|
8
|
-
for (const key
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
if (normalizedWhitelist.length > 0 &&
|
|
12
|
-
!normalizedWhitelist.includes(normalizedKey)) {
|
|
13
|
-
GlobalConfig.debugging.warn(`🚫 Header "${key}" not in whitelist - removed`);
|
|
14
|
-
continue;
|
|
7
|
+
await next();
|
|
8
|
+
for (const key of ctx.headers.keys()) {
|
|
9
|
+
if (lWhite > 0 && !normalizedWhitelist.includes(key)) {
|
|
10
|
+
ctx.headers.delete(key);
|
|
15
11
|
}
|
|
16
|
-
if (normalizedBlacklist.includes(
|
|
17
|
-
|
|
18
|
-
continue;
|
|
12
|
+
if (normalizedBlacklist.includes(key)) {
|
|
13
|
+
ctx.headers.delete(key);
|
|
19
14
|
}
|
|
20
|
-
if (!isValidHeaderName(normalizedKey)) {
|
|
21
|
-
GlobalConfig.debugging.warn(`⚠️ Invalid header name: "${normalizedKey}" - removed`);
|
|
22
|
-
continue;
|
|
23
|
-
}
|
|
24
|
-
const sanitizedValue = sanitizeHeaderValue(value, allowUnsafeCharacters);
|
|
25
|
-
if (!sanitizedValue) {
|
|
26
|
-
GlobalConfig.debugging.warn(`⚠️ Header "${key}" has invalid/empty value - removed`);
|
|
27
|
-
continue;
|
|
28
|
-
}
|
|
29
|
-
sanitizedHeaders[normalizedKey] = sanitizedValue;
|
|
30
|
-
}
|
|
31
|
-
for (const k in sanitizedHeaders) {
|
|
32
|
-
let v = sanitizedHeaders[k];
|
|
33
|
-
ctx.setHeader(k, v);
|
|
34
15
|
}
|
|
35
|
-
ctx.clearHeader = sanitizedHeaders;
|
|
36
|
-
return await next();
|
|
37
16
|
};
|
|
38
17
|
};
|
|
39
|
-
const isValidHeaderName = (name) => {
|
|
40
|
-
const HEADER_NAME_REGEX = /^[a-zA-Z0-9\-_]+$/;
|
|
41
|
-
return HEADER_NAME_REGEX.test(name);
|
|
42
|
-
};
|
|
43
|
-
const sanitizeHeaderValue = (value, allowUnsafeCharacters) => {
|
|
44
|
-
let sanitized = value.trim();
|
|
45
|
-
if (!allowUnsafeCharacters) {
|
|
46
|
-
sanitized = sanitized.replace(/[\x00-\x1F\x7F]/g, "");
|
|
47
|
-
}
|
|
48
|
-
return sanitized;
|
|
49
|
-
};
|
|
50
18
|
export { sanitizeHeaders as default, sanitizeHeaders };
|
|
@@ -41,4 +41,4 @@ export type XSSProtectionOptions = {
|
|
|
41
41
|
* }));
|
|
42
42
|
*/
|
|
43
43
|
declare const xssProtection: (options?: XSSProtectionOptions) => (ctx: Context, next: NextCallback) => Promise<void>;
|
|
44
|
-
export { xssProtection
|
|
44
|
+
export { xssProtection as default, xssProtection };
|
|
@@ -1,23 +1,19 @@
|
|
|
1
|
-
import { GlobalConfig } from "../core/config.js";
|
|
2
1
|
const xssProtection = (options = {}) => {
|
|
3
2
|
const { enabled = true, mode = "block", fallbackCSP = "default-src 'self'; script-src 'self';", } = options;
|
|
4
3
|
return async function xssProtection(ctx, next) {
|
|
5
4
|
const isEnabled = typeof enabled === "function" ? enabled(ctx) : enabled;
|
|
6
5
|
if (!isEnabled) {
|
|
7
|
-
GlobalConfig.debugging.warn("🟠 XSS protection is disabled.");
|
|
8
6
|
return await next();
|
|
9
7
|
}
|
|
10
8
|
const xssHeaderValue = mode === "block" ? "1; mode=block" : "1";
|
|
11
9
|
ctx.setHeader("X-XSS-Protection", xssHeaderValue);
|
|
12
|
-
GlobalConfig.debugging.warn(`🟢 X-XSS-Protection set to: ${xssHeaderValue}`);
|
|
13
10
|
if (fallbackCSP) {
|
|
14
11
|
const existingCSP = ctx.req.header("content-security-policy");
|
|
15
12
|
if (!existingCSP) {
|
|
16
13
|
ctx.setHeader("Content-Security-Policy", fallbackCSP);
|
|
17
|
-
GlobalConfig.debugging.warn(`🟣 Fallback CSP set to: ${fallbackCSP}`);
|
|
18
14
|
}
|
|
19
15
|
}
|
|
20
16
|
return await next();
|
|
21
17
|
};
|
|
22
18
|
};
|
|
23
|
-
export { xssProtection
|
|
19
|
+
export { xssProtection as default, xssProtection };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tezx",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.11-beta",
|
|
4
4
|
"description": "TezX is a high-performance, lightweight JavaScript framework designed for speed, scalability, and flexibility. It enables efficient routing, middleware management, and static file serving with minimal configuration. Fully compatible with Node.js, Deno, and Bun.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "cjs/index.js",
|
|
@@ -47,6 +47,11 @@
|
|
|
47
47
|
"require": "./cjs/registry/index.js",
|
|
48
48
|
"types": "./registry/index.d.ts"
|
|
49
49
|
},
|
|
50
|
+
"./middleware": {
|
|
51
|
+
"import": "./middleware/index.js",
|
|
52
|
+
"require": "./cjs/middleware/index.js",
|
|
53
|
+
"types": "./middleware/index.d.ts"
|
|
54
|
+
},
|
|
50
55
|
"./middleware/*": {
|
|
51
56
|
"import": "./middleware/*.js",
|
|
52
57
|
"require": "./cjs/middleware/*.js",
|
package/registry/RadixRouter.js
CHANGED
|
@@ -36,20 +36,18 @@ export class RadixRouter {
|
|
|
36
36
|
search(method, path) {
|
|
37
37
|
let params = {};
|
|
38
38
|
let middlewares = [];
|
|
39
|
-
const { success, node } = this._match(method, this.root,
|
|
39
|
+
const { success, node } = this._match(method, this.root, path?.split("/")?.filter(Boolean), 0, params, middlewares);
|
|
40
40
|
if (success && node) {
|
|
41
41
|
const handlers = node.handlers?.[method] ?? [];
|
|
42
42
|
return { method, params, handlers, middlewares };
|
|
43
43
|
}
|
|
44
44
|
return { method, params: {}, handlers: [], middlewares };
|
|
45
45
|
}
|
|
46
|
-
_match(method, node, segments, index, params, middlewares
|
|
47
|
-
if (node
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
middlewares.push(mw);
|
|
52
|
-
}
|
|
46
|
+
_match(method, node, segments, index, params, middlewares) {
|
|
47
|
+
if (node?.handlers?.ALL) {
|
|
48
|
+
const mw = node.handlers?.ALL;
|
|
49
|
+
for (let i = 0; i < mw.length; i++) {
|
|
50
|
+
middlewares.push(mw[i]);
|
|
53
51
|
}
|
|
54
52
|
}
|
|
55
53
|
if (index === segments.length) {
|
|
@@ -58,40 +56,59 @@ export class RadixRouter {
|
|
|
58
56
|
const opt = node.children[":"];
|
|
59
57
|
if (opt?.isOptional) {
|
|
60
58
|
params[opt.paramName] = null;
|
|
61
|
-
return this._match(method, opt, segments, index, params, middlewares
|
|
59
|
+
return this._match(method, opt, segments, index, params, middlewares);
|
|
62
60
|
}
|
|
63
61
|
return { success: false, node: node };
|
|
64
62
|
}
|
|
65
63
|
const wc = node.children["*"];
|
|
66
|
-
if (wc?.handlers?.ALL) {
|
|
67
|
-
for (const mw of wc.handlers.ALL) {
|
|
68
|
-
if (!seen.has(mw)) {
|
|
69
|
-
seen.add(mw);
|
|
70
|
-
middlewares.push(mw);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
64
|
const seg = segments[index];
|
|
75
65
|
if (node.children[seg]) {
|
|
76
|
-
const res = this._match(method, node.children[seg], segments, index + 1, params, middlewares
|
|
77
|
-
if (res.success)
|
|
66
|
+
const res = this._match(method, node.children[seg], segments, index + 1, params, middlewares);
|
|
67
|
+
if (res.success) {
|
|
68
|
+
if (wc?.handlers?.ALL) {
|
|
69
|
+
const mw = wc.handlers?.ALL;
|
|
70
|
+
for (let i = 0; i < mw.length; i++) {
|
|
71
|
+
middlewares.push(mw[i]);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
78
74
|
return res;
|
|
75
|
+
}
|
|
79
76
|
}
|
|
80
77
|
const dyn = node.children[":"];
|
|
81
78
|
if (dyn) {
|
|
82
79
|
params[dyn.paramName] = seg;
|
|
83
|
-
const res = this._match(method, dyn, segments, index + 1, params, middlewares
|
|
84
|
-
if (res.success)
|
|
80
|
+
const res = this._match(method, dyn, segments, index + 1, params, middlewares);
|
|
81
|
+
if (res.success) {
|
|
82
|
+
if (wc?.handlers?.ALL) {
|
|
83
|
+
const mw = wc.handlers?.ALL;
|
|
84
|
+
for (let i = 0; i < mw.length; i++) {
|
|
85
|
+
middlewares.push(mw[i]);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
85
88
|
return res;
|
|
89
|
+
}
|
|
86
90
|
if (dyn.isOptional) {
|
|
87
91
|
params[dyn.paramName] = null;
|
|
88
|
-
const skip = this._match(method, dyn, segments, index, params, middlewares
|
|
89
|
-
if (skip.success)
|
|
92
|
+
const skip = this._match(method, dyn, segments, index, params, middlewares);
|
|
93
|
+
if (skip.success) {
|
|
94
|
+
if (wc?.handlers?.ALL) {
|
|
95
|
+
const mw = wc.handlers?.ALL;
|
|
96
|
+
for (let i = 0; i < mw.length; i++) {
|
|
97
|
+
middlewares.push(mw[i]);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
90
100
|
return skip;
|
|
101
|
+
}
|
|
91
102
|
}
|
|
92
103
|
}
|
|
93
104
|
if (wc) {
|
|
94
105
|
let wildcard = segments.slice(index).join("/");
|
|
106
|
+
if (wc?.handlers?.ALL) {
|
|
107
|
+
const mw = wc.handlers?.ALL;
|
|
108
|
+
for (let i = 0; i < mw.length; i++) {
|
|
109
|
+
middlewares.push(mw[i]);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
95
112
|
if (wildcard) {
|
|
96
113
|
params[wc.paramName] = wildcard;
|
|
97
114
|
return { node: wc, success: true };
|
|
@@ -124,3 +141,35 @@ export class RadixRouter {
|
|
|
124
141
|
return result;
|
|
125
142
|
}
|
|
126
143
|
}
|
|
144
|
+
const routes = [
|
|
145
|
+
"/",
|
|
146
|
+
"/users",
|
|
147
|
+
"/users/:id",
|
|
148
|
+
"/users/:id/profile",
|
|
149
|
+
"/posts/:postId?",
|
|
150
|
+
"/files/*",
|
|
151
|
+
"/admin/settings",
|
|
152
|
+
"/search/:term?",
|
|
153
|
+
"/categories/:categoryId/products/:productId",
|
|
154
|
+
"/about",
|
|
155
|
+
];
|
|
156
|
+
const testPaths = [
|
|
157
|
+
"/",
|
|
158
|
+
"/users",
|
|
159
|
+
"/users/123",
|
|
160
|
+
"/users/123/profile",
|
|
161
|
+
"/posts",
|
|
162
|
+
"/posts/456",
|
|
163
|
+
"/files/path/to/file.txt",
|
|
164
|
+
"/admin/settings",
|
|
165
|
+
"/search",
|
|
166
|
+
"/search/nodejs",
|
|
167
|
+
"/categories/12/products/999",
|
|
168
|
+
"/notfound",
|
|
169
|
+
];
|
|
170
|
+
const router = new RadixRouter();
|
|
171
|
+
let x = function xx() {
|
|
172
|
+
return {
|
|
173
|
+
body: "3453455",
|
|
174
|
+
};
|
|
175
|
+
};
|
package/types/index.d.ts
CHANGED
|
@@ -128,6 +128,7 @@ export interface CookieOptions {
|
|
|
128
128
|
*/
|
|
129
129
|
export type Runtime = "bun" | "deno" | "node";
|
|
130
130
|
import { Context } from "../core/context.js";
|
|
131
|
+
import { TezXError } from "../core/error.js";
|
|
131
132
|
import { RequestHeader, ResponseHeader } from "./headers.js";
|
|
132
133
|
/**
|
|
133
134
|
* Options to customize static file serving behavior.
|
|
@@ -233,7 +234,7 @@ export type Middleware<T extends Record<string, any> = {}, Path extends string =
|
|
|
233
234
|
* @param ctx - The context object where the error occurred.
|
|
234
235
|
* @returns An HTTP response or a promise that resolves to an HTTP response.
|
|
235
236
|
*/
|
|
236
|
-
export type ErrorHandler<T extends Record<string, any> = {}> = (err:
|
|
237
|
+
export type ErrorHandler<T extends Record<string, any> = {}> = (err: TezXError, ctx: Ctx<T>) => HttpBaseResponse;
|
|
237
238
|
/**
|
|
238
239
|
* Configuration options for parsing and validating multipart/form-data.
|
|
239
240
|
*/
|
package/utils/cookie.js
CHANGED
|
@@ -28,7 +28,7 @@ export function allCookies(ctx) {
|
|
|
28
28
|
return cookies;
|
|
29
29
|
}
|
|
30
30
|
export function setCookie(ctx, name, value, options) {
|
|
31
|
-
ctx.setHeader("Set-Cookie", `${name}=${value}; ${serializeOptions(options
|
|
31
|
+
ctx.setHeader("Set-Cookie", `${name}=${value}; ${serializeOptions(options ?? {})}`);
|
|
32
32
|
}
|
|
33
33
|
export function deleteCookie(ctx, name, options) {
|
|
34
34
|
ctx.setHeader("Set-Cookie", `${name}=; ${serializeOptions({ ...options, maxAge: 0, expires: new Date(0) })}`);
|
package/utils/rateLimit.d.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { Context } from "..";
|
|
2
1
|
export declare function createRateLimitDefaultStorage(): {
|
|
3
2
|
get: (key: string) => {
|
|
4
3
|
count: number;
|
|
@@ -13,7 +12,7 @@ export declare function createRateLimitDefaultStorage(): {
|
|
|
13
12
|
}>;
|
|
14
13
|
clearExpired: () => void;
|
|
15
14
|
};
|
|
16
|
-
export declare function isRateLimit(
|
|
15
|
+
export declare function isRateLimit(key: string, store: any, maxRequests: number, windowMs: number): {
|
|
17
16
|
check: boolean;
|
|
18
17
|
entry: any;
|
|
19
18
|
};
|
package/utils/rateLimit.js
CHANGED
|
@@ -13,8 +13,8 @@ export function createRateLimitDefaultStorage() {
|
|
|
13
13
|
},
|
|
14
14
|
};
|
|
15
15
|
}
|
|
16
|
-
export function isRateLimit(
|
|
17
|
-
store
|
|
16
|
+
export function isRateLimit(key, store, maxRequests, windowMs) {
|
|
17
|
+
store?.clearExpired();
|
|
18
18
|
const now = Date.now();
|
|
19
19
|
let entry = store.get(key) || { count: 0, resetTime: now + windowMs };
|
|
20
20
|
if (now < entry.resetTime) {
|
package/utils/regexRouter.js
CHANGED
package/utils/response.d.ts
CHANGED
|
@@ -1,20 +1,18 @@
|
|
|
1
1
|
import { Context } from "../core/context.js";
|
|
2
|
-
import {
|
|
2
|
+
import { TezXError } from "../core/error.js";
|
|
3
|
+
import { HttpBaseResponse } from "../types/index.js";
|
|
3
4
|
export declare let notFoundResponse: (ctx: Context) => HttpBaseResponse;
|
|
4
|
-
export declare function handleErrorResponse(
|
|
5
|
+
export declare function handleErrorResponse(err: TezXError | undefined, ctx: Context): Promise<HttpBaseResponse>;
|
|
6
|
+
/**
|
|
7
|
+
* Converts a template literal array and values into a single string.
|
|
8
|
+
* If a plain string is passed, it returns the string itself.
|
|
9
|
+
*
|
|
10
|
+
* @param input - A string or a template literal array.
|
|
11
|
+
* @param values - Values for template literal placeholders.
|
|
12
|
+
* @returns Concatenated string.
|
|
13
|
+
*/
|
|
14
|
+
export declare function toString(input: string | readonly string[], values: any[]): string;
|
|
5
15
|
export declare function determineContentTypeBody(body: any): {
|
|
6
16
|
type: string;
|
|
7
17
|
body: any;
|
|
8
18
|
};
|
|
9
|
-
/**
|
|
10
|
-
* Creates a Response object with minimal GC overhead.
|
|
11
|
-
* Merges only once and supports a forced Content-Type.
|
|
12
|
-
*
|
|
13
|
-
* @param {BodyInit} body - The body content.
|
|
14
|
-
* @param {string} type - The Content-Type header.
|
|
15
|
-
* @param {ResponseInit} init - Optional ResponseInit.
|
|
16
|
-
* @param {ResponseHeader} baseHeaders - Default headers from the class.
|
|
17
|
-
* @param {number} defaultStatus - Default status from the class.
|
|
18
|
-
* @returns {Response}
|
|
19
|
-
*/
|
|
20
|
-
export declare function newResponse(body: BodyInit | null, type: string, init: ResponseInit | undefined, baseHeaders: ResponseHeaders, defaultStatus: number): Response;
|
package/utils/response.js
CHANGED
|
@@ -1,17 +1,29 @@
|
|
|
1
|
+
import { GlobalConfig } from "../core/config.js";
|
|
2
|
+
import { TezXError } from "../core/error.js";
|
|
1
3
|
export let notFoundResponse = (ctx) => {
|
|
2
4
|
const { method, pathname } = ctx;
|
|
3
5
|
return ctx.text(`${method}: '${pathname}' could not find\n`, {
|
|
4
6
|
status: 404,
|
|
5
7
|
});
|
|
6
8
|
};
|
|
7
|
-
export async function handleErrorResponse(
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
export async function handleErrorResponse(err = TezXError.internal(), ctx) {
|
|
10
|
+
if (err instanceof TezXError) {
|
|
11
|
+
GlobalConfig.debugging.error(err.details ?? err?.message);
|
|
12
|
+
return ctx.status(err.statusCode ?? 500).send(err.details ?? err?.message ?? "Internal Server Error");
|
|
11
13
|
}
|
|
12
|
-
return
|
|
13
|
-
|
|
14
|
-
|
|
14
|
+
return await handleErrorResponse(TezXError.internal(), ctx);
|
|
15
|
+
}
|
|
16
|
+
export function toString(input, values) {
|
|
17
|
+
if (typeof input === "string") {
|
|
18
|
+
return input;
|
|
19
|
+
}
|
|
20
|
+
let result = "";
|
|
21
|
+
for (let i = 0; i < input.length; i++) {
|
|
22
|
+
result += input[i];
|
|
23
|
+
if (i < values.length)
|
|
24
|
+
result += values[i];
|
|
25
|
+
}
|
|
26
|
+
return result;
|
|
15
27
|
}
|
|
16
28
|
export function determineContentTypeBody(body) {
|
|
17
29
|
if (typeof body === "string" ||
|
|
@@ -31,7 +43,7 @@ export function determineContentTypeBody(body) {
|
|
|
31
43
|
if (typeof Blob !== "undefined" && body instanceof Blob) {
|
|
32
44
|
return { type: body.type || "application/octet-stream", body };
|
|
33
45
|
}
|
|
34
|
-
if (typeof body === "object" && typeof body
|
|
46
|
+
if (typeof body === "object" && typeof body?.pipe === "function") {
|
|
35
47
|
return { type: "application/octet-stream", body };
|
|
36
48
|
}
|
|
37
49
|
if (typeof body === "object") {
|
|
@@ -42,24 +54,3 @@ export function determineContentTypeBody(body) {
|
|
|
42
54
|
}
|
|
43
55
|
return { type: "text/plain; charset=utf-8", body: String(body ?? "") };
|
|
44
56
|
}
|
|
45
|
-
export function newResponse(body, type, init = {}, baseHeaders, defaultStatus) {
|
|
46
|
-
let headers;
|
|
47
|
-
if (init.headers) {
|
|
48
|
-
headers = {
|
|
49
|
-
"Content-Type": type,
|
|
50
|
-
...baseHeaders,
|
|
51
|
-
...init.headers,
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
else {
|
|
55
|
-
headers = {
|
|
56
|
-
"Content-Type": type,
|
|
57
|
-
...baseHeaders,
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
return new Response(body, {
|
|
61
|
-
status: init.status || defaultStatus,
|
|
62
|
-
statusText: init.statusText,
|
|
63
|
-
headers,
|
|
64
|
-
});
|
|
65
|
-
}
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.default = exports.cacheControl = void 0;
|
|
37
|
-
const config_js_1 = require("../core/config.js");
|
|
38
|
-
const cacheControl = (options) => {
|
|
39
|
-
const { defaultSettings, useWeakETag = false, rules = [], onError = (error, ctx) => {
|
|
40
|
-
ctx.setStatus = 500;
|
|
41
|
-
ctx.body = { error: "Failed to set cache headers." };
|
|
42
|
-
}, logEvent = (event, ctx, error) => {
|
|
43
|
-
if (event === "error") {
|
|
44
|
-
config_js_1.GlobalConfig.debugging.error(`[CACHE] ${event.toUpperCase()}: ${error?.message}`);
|
|
45
|
-
}
|
|
46
|
-
else {
|
|
47
|
-
config_js_1.GlobalConfig.debugging.success(`[CACHE] ${event.toUpperCase()} for ${ctx.method} ${ctx.pathname}`);
|
|
48
|
-
}
|
|
49
|
-
}, } = options;
|
|
50
|
-
return async function cacheControl(ctx, next) {
|
|
51
|
-
if (!["GET", "HEAD"].includes(ctx.method)) {
|
|
52
|
-
return await next();
|
|
53
|
-
}
|
|
54
|
-
try {
|
|
55
|
-
await next();
|
|
56
|
-
const matchedRule = rules.find((rule) => rule.condition(ctx)) || null;
|
|
57
|
-
const { maxAge, scope, enableETag, vary } = matchedRule || defaultSettings;
|
|
58
|
-
const cacheControlValue = `${scope}, max-age=${maxAge}`;
|
|
59
|
-
ctx.setHeader("Cache-Control", cacheControlValue);
|
|
60
|
-
const expiresDate = new Date(Date.now() + maxAge * 1000).toUTCString();
|
|
61
|
-
ctx.setHeader("Expires", expiresDate);
|
|
62
|
-
if (vary?.length) {
|
|
63
|
-
ctx.setHeader("Vary", vary.join(", "));
|
|
64
|
-
}
|
|
65
|
-
if (enableETag) {
|
|
66
|
-
const responseBody = typeof ctx.resBody === "string"
|
|
67
|
-
? ctx.resBody
|
|
68
|
-
: JSON.stringify(ctx.resBody ?? "");
|
|
69
|
-
const etag = await generateETag(responseBody, useWeakETag);
|
|
70
|
-
const ifNoneMatch = ctx.req.header("if-none-match");
|
|
71
|
-
if (ifNoneMatch === etag) {
|
|
72
|
-
ctx.setStatus = 304;
|
|
73
|
-
ctx.body = null;
|
|
74
|
-
logEvent("cached", ctx);
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
ctx.setHeader("ETag", etag);
|
|
78
|
-
}
|
|
79
|
-
logEvent("cached", ctx);
|
|
80
|
-
}
|
|
81
|
-
catch (error) {
|
|
82
|
-
logEvent("error", ctx, error);
|
|
83
|
-
return onError?.(error, ctx);
|
|
84
|
-
}
|
|
85
|
-
};
|
|
86
|
-
};
|
|
87
|
-
exports.cacheControl = cacheControl;
|
|
88
|
-
exports.default = cacheControl;
|
|
89
|
-
const generateETag = async (content, weak = false) => {
|
|
90
|
-
const crypto = await Promise.resolve().then(() => __importStar(require("node:crypto")));
|
|
91
|
-
const hash = crypto.createHash("md5").update(content).digest("hex");
|
|
92
|
-
return weak ? `W/"${hash}"` : `"${hash}"`;
|
|
93
|
-
};
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.detectBot = void 0;
|
|
4
|
-
const config_js_1 = require("../core/config.js");
|
|
5
|
-
const rateLimit_js_1 = require("../utils/rateLimit.js");
|
|
6
|
-
const detectBot = (options = {}) => {
|
|
7
|
-
const { botUserAgents = ["bot", "spider", "crawl", "slurp"], maxRequests = 30, windowMs = 60000, isBlacklisted = async () => false, queryKeyBot = "bot", onBotDetected = "block", enableRateLimiting = false, customBotDetector = async () => false, customBlockedResponse = (ctx, { reason }) => {
|
|
8
|
-
ctx.setStatus = 403;
|
|
9
|
-
return ctx.json({ error: `Bot detected: ${reason}` });
|
|
10
|
-
}, storage, confidenceThreshold = 0.5, } = options;
|
|
11
|
-
let store = storage;
|
|
12
|
-
if (enableRateLimiting) {
|
|
13
|
-
store = (0, rateLimit_js_1.createRateLimitDefaultStorage)();
|
|
14
|
-
}
|
|
15
|
-
return async function detectBot(ctx, next) {
|
|
16
|
-
const detectionResult = {
|
|
17
|
-
isBot: false,
|
|
18
|
-
indicators: [],
|
|
19
|
-
};
|
|
20
|
-
const userAgent = ctx.header("user-agent")?.toLowerCase() || "";
|
|
21
|
-
const remoteAddress = `${ctx.req.remoteAddress?.address}:${ctx.req.remoteAddress?.port}` ||
|
|
22
|
-
"unknown";
|
|
23
|
-
const isBotQuery = ctx.req.query[queryKeyBot] === "true";
|
|
24
|
-
if (botUserAgents.some((agent) => userAgent.includes(agent))) {
|
|
25
|
-
detectionResult.indicators.push("User-Agent");
|
|
26
|
-
}
|
|
27
|
-
if (await isBlacklisted(ctx, remoteAddress)) {
|
|
28
|
-
detectionResult.indicators.push("Blacklisted IP");
|
|
29
|
-
}
|
|
30
|
-
if (isBotQuery) {
|
|
31
|
-
detectionResult.indicators.push("Query Parameter");
|
|
32
|
-
}
|
|
33
|
-
const key = `${ctx.req.remoteAddress.address}:${ctx.req.remoteAddress.port}`;
|
|
34
|
-
if (enableRateLimiting &&
|
|
35
|
-
(0, rateLimit_js_1.isRateLimit)(ctx, key, store, maxRequests, windowMs).check) {
|
|
36
|
-
detectionResult.indicators.push("Rate Limiting");
|
|
37
|
-
}
|
|
38
|
-
if (await customBotDetector(ctx)) {
|
|
39
|
-
detectionResult.indicators.push("Custom Detector");
|
|
40
|
-
}
|
|
41
|
-
detectionResult.isBot = detectionResult.indicators.length > 0;
|
|
42
|
-
if (detectionResult.indicators.length > 1) {
|
|
43
|
-
detectionResult.reason = "Multiple Indicators";
|
|
44
|
-
const confidence = Math.min(0.3 * detectionResult.indicators.length, 1);
|
|
45
|
-
detectionResult.isBot = confidence >= confidenceThreshold;
|
|
46
|
-
}
|
|
47
|
-
else if (detectionResult.indicators.length === 1) {
|
|
48
|
-
detectionResult.reason = detectionResult.indicators[0];
|
|
49
|
-
}
|
|
50
|
-
if (detectionResult.isBot) {
|
|
51
|
-
config_js_1.GlobalConfig.debugging.warn(`Bot detected: ${detectionResult.reason}`, {
|
|
52
|
-
ip: remoteAddress,
|
|
53
|
-
userAgent,
|
|
54
|
-
indicators: detectionResult.indicators,
|
|
55
|
-
});
|
|
56
|
-
if (onBotDetected === "block") {
|
|
57
|
-
return customBlockedResponse(ctx, detectionResult);
|
|
58
|
-
}
|
|
59
|
-
else if (typeof onBotDetected === "function") {
|
|
60
|
-
return onBotDetected(ctx, detectionResult);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
return await next();
|
|
64
|
-
};
|
|
65
|
-
};
|
|
66
|
-
exports.detectBot = detectBot;
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.default = exports.detectLocale = void 0;
|
|
4
|
-
const config_js_1 = require("../core/config.js");
|
|
5
|
-
const cookie_js_1 = require("../utils/cookie.js");
|
|
6
|
-
const detectLocale = (options) => {
|
|
7
|
-
const { supportedLocales, defaultLocale = "en", queryKeyLocale = "lang", cookieKeyLocale = "locale", localeContextKey = "locale", customLocaleDetector, } = options;
|
|
8
|
-
return async function detectLocale(ctx, next) {
|
|
9
|
-
let detectedLocale;
|
|
10
|
-
const queryLocale = ctx.req.query[queryKeyLocale];
|
|
11
|
-
if (queryLocale && supportedLocales.includes(queryLocale)) {
|
|
12
|
-
detectedLocale = queryLocale;
|
|
13
|
-
}
|
|
14
|
-
if (!detectedLocale) {
|
|
15
|
-
const cookieLocale = (0, cookie_js_1.getCookie)(ctx, cookieKeyLocale);
|
|
16
|
-
if (cookieLocale && supportedLocales.includes(cookieLocale)) {
|
|
17
|
-
detectedLocale = cookieLocale;
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
if (!detectedLocale) {
|
|
21
|
-
const acceptLanguage = ctx.req.header("accept-language");
|
|
22
|
-
if (acceptLanguage) {
|
|
23
|
-
const preferredLocales = acceptLanguage
|
|
24
|
-
.split(",")
|
|
25
|
-
.map((lang) => lang.split(";")[0].trim())
|
|
26
|
-
.filter((lang) => supportedLocales.includes(lang));
|
|
27
|
-
detectedLocale = preferredLocales[0];
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
if (!detectedLocale && customLocaleDetector) {
|
|
31
|
-
const customLocale = customLocaleDetector(ctx);
|
|
32
|
-
if (customLocale && supportedLocales.includes(customLocale)) {
|
|
33
|
-
detectedLocale = customLocale;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
if (!detectedLocale) {
|
|
37
|
-
detectedLocale = defaultLocale;
|
|
38
|
-
}
|
|
39
|
-
ctx[localeContextKey] = detectedLocale;
|
|
40
|
-
config_js_1.GlobalConfig.debugging.success(`Detected locale: ${detectedLocale}`);
|
|
41
|
-
return await next();
|
|
42
|
-
};
|
|
43
|
-
};
|
|
44
|
-
exports.detectLocale = detectLocale;
|
|
45
|
-
exports.default = detectLocale;
|