tezx 2.0.11 → 3.0.0
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/README.md +122 -89
- package/bun/getConnInfo.d.ts +21 -0
- package/bun/getConnInfo.js +9 -0
- package/bun/index.d.ts +10 -4
- package/bun/index.js +8 -4
- package/bun/ws.d.ts +48 -0
- package/bun/ws.js +58 -0
- package/cjs/bun/getConnInfo.js +12 -0
- package/cjs/bun/index.js +35 -7
- package/cjs/bun/ws.js +63 -0
- package/cjs/core/config.js +2 -12
- package/cjs/core/context.js +131 -379
- package/cjs/core/error.js +49 -0
- package/cjs/core/request.js +79 -131
- package/cjs/core/router.js +54 -387
- package/cjs/core/server.js +83 -202
- package/cjs/deno/env.js +4 -4
- package/cjs/deno/getConnInfo.js +18 -0
- package/cjs/deno/index.js +11 -18
- package/cjs/deno/serveStatic.js +53 -0
- package/cjs/deno/ws.js +39 -0
- package/cjs/helper/index.js +46 -10
- package/cjs/index.js +5 -7
- package/cjs/jwt/node.js +94 -0
- package/cjs/jwt/web.js +178 -0
- package/cjs/middleware/basic-auth.js +42 -0
- package/cjs/middleware/bearer-auth.js +34 -0
- package/cjs/middleware/cache-control.js +44 -0
- package/cjs/middleware/cors.js +11 -21
- package/cjs/middleware/detect-bot.js +57 -0
- package/cjs/middleware/i18n.js +73 -60
- package/cjs/middleware/index.js +8 -46
- package/cjs/middleware/logger.js +9 -4
- package/cjs/middleware/pagination.js +3 -2
- package/cjs/middleware/powered-by.js +3 -2
- package/cjs/middleware/rate-limiter.js +38 -0
- package/cjs/middleware/request-id.js +4 -5
- package/cjs/middleware/sanitize-headers.js +22 -0
- package/cjs/middleware/secure-headers copy.js +143 -0
- package/cjs/middleware/secure-headers.js +157 -0
- package/cjs/middleware/{xssProtection.js → xss-protection.js} +5 -8
- package/cjs/node/env.js +7 -7
- package/cjs/node/getConnInfo.js +16 -0
- package/cjs/node/index.js +17 -18
- package/cjs/node/mount-node.js +59 -0
- package/cjs/node/serveStatic.js +56 -0
- package/cjs/node/toWebRequest.js +25 -0
- package/cjs/node/ws.js +82 -0
- package/cjs/registry/RadixRouter.js +148 -0
- package/cjs/registry/index.js +17 -0
- package/cjs/types/headers.js +2 -0
- package/cjs/types/index.js +13 -0
- package/cjs/utils/buffer.js +17 -0
- package/cjs/utils/colors.js +2 -0
- package/cjs/utils/cookie.js +59 -0
- package/cjs/utils/file.js +136 -0
- package/cjs/utils/formData.js +60 -10
- package/cjs/utils/generateID.js +37 -0
- package/cjs/utils/low-level.js +115 -0
- package/cjs/utils/{staticFile.js → mimeTypes.js} +0 -87
- package/cjs/utils/rateLimit.js +41 -0
- package/cjs/utils/response.js +65 -0
- package/cjs/{core/environment.js → utils/runtime.js} +2 -1
- package/cjs/utils/url.js +65 -30
- package/core/config.d.ts +2 -7
- package/core/config.js +2 -12
- package/core/context.d.ts +209 -164
- package/core/context.js +131 -346
- package/core/error.d.ts +96 -0
- package/core/error.js +44 -0
- package/core/request.d.ts +67 -107
- package/core/request.js +78 -130
- package/core/router.d.ts +138 -133
- package/core/router.js +53 -352
- package/core/server.d.ts +99 -38
- package/core/server.js +83 -202
- package/deno/env.js +3 -3
- package/deno/getConnInfo.d.ts +21 -0
- package/deno/getConnInfo.js +15 -0
- package/deno/index.d.ts +9 -4
- package/deno/index.js +7 -4
- package/deno/serveStatic.d.ts +28 -0
- package/deno/serveStatic.js +49 -0
- package/deno/ws.d.ts +42 -0
- package/deno/ws.js +36 -0
- package/helper/index.d.ts +29 -15
- package/helper/index.js +27 -7
- package/index.d.ts +10 -8
- package/index.js +4 -5
- package/jwt/node.d.ts +39 -0
- package/jwt/node.js +87 -0
- package/jwt/web.d.ts +14 -0
- package/jwt/web.js +174 -0
- package/middleware/basic-auth.d.ts +56 -0
- package/middleware/basic-auth.js +38 -0
- package/middleware/bearer-auth.d.ts +53 -0
- package/middleware/bearer-auth.js +30 -0
- package/middleware/cache-control.d.ts +30 -0
- package/middleware/cache-control.js +40 -0
- package/middleware/cors.d.ts +30 -3
- package/middleware/cors.js +12 -22
- package/middleware/detect-bot.d.ts +113 -0
- package/middleware/detect-bot.js +53 -0
- package/middleware/i18n.d.ts +166 -73
- package/middleware/i18n.js +73 -60
- package/middleware/index.d.ts +8 -32
- package/middleware/index.js +8 -44
- package/middleware/logger.d.ts +5 -2
- package/middleware/logger.js +9 -4
- package/middleware/pagination.d.ts +9 -6
- package/middleware/pagination.js +3 -2
- package/middleware/powered-by.d.ts +2 -1
- package/middleware/powered-by.js +3 -2
- package/middleware/{rateLimiter.d.ts → rate-limiter.d.ts} +15 -9
- package/middleware/rate-limiter.js +34 -0
- package/middleware/request-id.d.ts +2 -1
- package/middleware/request-id.js +5 -6
- package/middleware/{sanitizeHeader.d.ts → sanitize-headers.d.ts} +5 -19
- package/middleware/sanitize-headers.js +18 -0
- package/middleware/secure-headers copy.d.ts +15 -0
- package/middleware/secure-headers copy.js +136 -0
- package/middleware/secure-headers.d.ts +132 -0
- package/middleware/secure-headers.js +153 -0
- package/middleware/{xssProtection.d.ts → xss-protection.d.ts} +2 -1
- package/middleware/xss-protection.js +19 -0
- package/node/env.js +4 -4
- package/node/getConnInfo.d.ts +21 -0
- package/node/getConnInfo.js +13 -0
- package/node/index.d.ts +13 -4
- package/node/index.js +11 -4
- package/node/mount-node.d.ts +11 -0
- package/node/mount-node.js +56 -0
- package/node/serveStatic.d.ts +36 -0
- package/node/serveStatic.js +52 -0
- package/node/toWebRequest.js +22 -0
- package/node/ws.d.ts +56 -0
- package/node/ws.js +46 -0
- package/package.json +39 -30
- package/registry/RadixRouter.d.ts +40 -0
- package/registry/RadixRouter.js +144 -0
- package/registry/index.d.ts +2 -0
- package/registry/index.js +1 -0
- package/types/headers.d.ts +2 -0
- package/types/headers.js +1 -0
- package/types/index.d.ts +318 -18
- package/types/index.js +12 -1
- package/utils/buffer.d.ts +1 -0
- package/utils/buffer.js +14 -0
- package/utils/colors.d.ts +24 -0
- package/utils/colors.js +2 -0
- package/utils/cookie.d.ts +55 -0
- package/utils/cookie.js +53 -0
- package/utils/file.d.ts +38 -0
- package/utils/file.js +96 -0
- package/utils/formData.d.ts +41 -1
- package/utils/formData.js +58 -9
- package/utils/generateID.d.ts +42 -0
- package/utils/generateID.js +32 -0
- package/utils/httpStatusMap.d.ts +14 -0
- package/utils/low-level.d.ts +58 -0
- package/utils/low-level.js +108 -0
- package/utils/mimeTypes.d.ts +4 -0
- package/utils/{staticFile.js → mimeTypes.js} +0 -53
- package/utils/rateLimit.d.ts +18 -0
- package/utils/rateLimit.js +37 -0
- package/utils/response.d.ts +18 -0
- package/utils/response.js +58 -0
- package/{core/environment.d.ts → utils/runtime.d.ts} +1 -0
- package/{core/environment.js → utils/runtime.js} +1 -0
- package/utils/url.d.ts +42 -14
- package/utils/url.js +61 -27
- package/bun/adapter.d.ts +0 -127
- package/bun/adapter.js +0 -97
- package/cjs/bun/adapter.js +0 -100
- package/cjs/core/MiddlewareConfigure.js +0 -68
- package/cjs/core/common.js +0 -15
- package/cjs/deno/adpater.js +0 -67
- package/cjs/helper/common.js +0 -17
- package/cjs/middleware/basicAuth.js +0 -71
- package/cjs/middleware/cacheControl.js +0 -90
- package/cjs/middleware/detectBot.js +0 -104
- package/cjs/middleware/detectLocale.js +0 -43
- package/cjs/middleware/lazyLoadModules.js +0 -73
- package/cjs/middleware/rateLimiter.js +0 -24
- package/cjs/middleware/requestTimeout.js +0 -42
- package/cjs/middleware/sanitizeHeader.js +0 -51
- package/cjs/middleware/secureHeaders.js +0 -42
- package/cjs/node/adapter.js +0 -138
- package/cjs/utils/regexRouter.js +0 -58
- package/cjs/utils/state.js +0 -34
- package/cjs/utils/toWebRequest.js +0 -35
- package/cjs/ws/deno.js +0 -20
- package/cjs/ws/index.js +0 -53
- package/cjs/ws/node.js +0 -65
- package/core/MiddlewareConfigure.d.ts +0 -15
- package/core/MiddlewareConfigure.js +0 -63
- package/core/common.d.ts +0 -21
- package/core/common.js +0 -11
- package/deno/adpater.d.ts +0 -38
- package/deno/adpater.js +0 -64
- package/helper/common.d.ts +0 -5
- package/helper/common.js +0 -14
- package/middleware/basicAuth.d.ts +0 -81
- package/middleware/basicAuth.js +0 -67
- package/middleware/cacheControl.d.ts +0 -48
- package/middleware/cacheControl.js +0 -53
- package/middleware/detectBot.d.ts +0 -121
- package/middleware/detectBot.js +0 -98
- package/middleware/detectLocale.d.ts +0 -55
- package/middleware/detectLocale.js +0 -39
- package/middleware/lazyLoadModules.d.ts +0 -72
- package/middleware/lazyLoadModules.js +0 -69
- package/middleware/rateLimiter.js +0 -20
- package/middleware/requestTimeout.d.ts +0 -25
- package/middleware/requestTimeout.js +0 -38
- package/middleware/sanitizeHeader.js +0 -47
- package/middleware/secureHeaders.d.ts +0 -78
- package/middleware/secureHeaders.js +0 -38
- package/middleware/xssProtection.js +0 -22
- package/node/adapter.d.ts +0 -46
- package/node/adapter.js +0 -102
- package/utils/regexRouter.d.ts +0 -66
- package/utils/regexRouter.js +0 -53
- package/utils/state.d.ts +0 -50
- package/utils/state.js +0 -30
- package/utils/staticFile.d.ts +0 -10
- package/utils/toWebRequest.js +0 -32
- package/ws/deno.d.ts +0 -6
- package/ws/deno.js +0 -16
- package/ws/index.d.ts +0 -180
- package/ws/index.js +0 -50
- package/ws/node.d.ts +0 -7
- package/ws/node.js +0 -28
- /package/{utils → node}/toWebRequest.d.ts +0 -0
package/middleware/cors.d.ts
CHANGED
|
@@ -1,10 +1,37 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Middleware } from "../types/index.js";
|
|
2
2
|
export type CorsOptions = {
|
|
3
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Allowed origins for CORS.
|
|
5
|
+
* Can be a string, an array of strings, or a function that returns a boolean.
|
|
6
|
+
*/
|
|
7
|
+
origin?: string | string[] | ((reqOrigin: string) => boolean);
|
|
8
|
+
/**
|
|
9
|
+
* Allowed HTTP methods for CORS requests.
|
|
10
|
+
* Defaults to ['GET', 'POST', 'PUT', 'DELETE'].
|
|
11
|
+
*/
|
|
4
12
|
methods?: string[];
|
|
13
|
+
/**
|
|
14
|
+
* Allowed headers for CORS requests.
|
|
15
|
+
* Defaults to ['Content-Type', 'Authorization'].
|
|
16
|
+
*/
|
|
5
17
|
allowedHeaders?: string[];
|
|
18
|
+
/**
|
|
19
|
+
* Headers exposed to the browser.
|
|
20
|
+
*/
|
|
6
21
|
exposedHeaders?: string[];
|
|
22
|
+
/**
|
|
23
|
+
* Indicates whether credentials are allowed.
|
|
24
|
+
*/
|
|
7
25
|
credentials?: boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Preflight cache duration in seconds.
|
|
28
|
+
*/
|
|
8
29
|
maxAge?: number;
|
|
9
30
|
};
|
|
10
|
-
|
|
31
|
+
/**
|
|
32
|
+
* Middleware for handling Cross-Origin Resource Sharing (CORS).
|
|
33
|
+
*
|
|
34
|
+
* @param option - Configuration options for CORS.
|
|
35
|
+
*/
|
|
36
|
+
declare function cors<T extends Record<string, any> = {}, Path extends string = any>(option?: CorsOptions): Middleware<T, Path>;
|
|
37
|
+
export { cors, cors as default };
|
package/middleware/cors.js
CHANGED
|
@@ -1,33 +1,25 @@
|
|
|
1
|
-
|
|
2
|
-
const {
|
|
1
|
+
function cors(option = {}) {
|
|
2
|
+
const { credentials, maxAge, origin } = option;
|
|
3
|
+
let methods = (option.methods || ["GET", "POST", "PUT", "DELETE"]).join(", ");
|
|
4
|
+
let allowedHeaders = (option.allowedHeaders || ["Content-Type", "Authorization"]).join(", ");
|
|
5
|
+
let exposedHeaders = option?.exposedHeaders?.join(", ");
|
|
3
6
|
return async function cors(ctx, next) {
|
|
4
|
-
const reqOrigin = ctx.req.
|
|
7
|
+
const reqOrigin = ctx.req.header("origin") || "";
|
|
5
8
|
let allowOrigin = "*";
|
|
6
9
|
if (typeof origin === "string") {
|
|
7
10
|
allowOrigin = origin;
|
|
8
11
|
}
|
|
9
|
-
else if (origin instanceof RegExp) {
|
|
10
|
-
allowOrigin = origin.test(reqOrigin) ? reqOrigin : "";
|
|
11
|
-
}
|
|
12
12
|
else if (Array.isArray(origin)) {
|
|
13
|
-
|
|
14
|
-
if (typeof item === "string") {
|
|
15
|
-
return item === reqOrigin;
|
|
16
|
-
}
|
|
17
|
-
else if (item instanceof RegExp) {
|
|
18
|
-
return item.test(reqOrigin);
|
|
19
|
-
}
|
|
20
|
-
});
|
|
21
|
-
allowOrigin = isAllowed ? reqOrigin : "";
|
|
13
|
+
allowOrigin = origin.includes(reqOrigin) ? reqOrigin : "";
|
|
22
14
|
}
|
|
23
15
|
else if (typeof origin === "function") {
|
|
24
16
|
allowOrigin = origin(reqOrigin) ? reqOrigin : "";
|
|
25
17
|
}
|
|
26
18
|
ctx.headers.set("Access-Control-Allow-Origin", allowOrigin);
|
|
27
|
-
ctx.headers.set("Access-Control-Allow-Methods",
|
|
28
|
-
ctx.headers.set("Access-Control-Allow-Headers",
|
|
19
|
+
ctx.headers.set("Access-Control-Allow-Methods", methods);
|
|
20
|
+
ctx.headers.set("Access-Control-Allow-Headers", allowedHeaders);
|
|
29
21
|
if (exposedHeaders) {
|
|
30
|
-
ctx.headers.set("Access-Control-Expose-Headers", exposedHeaders
|
|
22
|
+
ctx.headers.set("Access-Control-Expose-Headers", exposedHeaders);
|
|
31
23
|
}
|
|
32
24
|
if (credentials) {
|
|
33
25
|
ctx.headers.set("Access-Control-Allow-Credentials", "true");
|
|
@@ -36,11 +28,9 @@ export function cors(option = {}) {
|
|
|
36
28
|
ctx.headers.set("Access-Control-Max-Age", maxAge.toString());
|
|
37
29
|
}
|
|
38
30
|
if (ctx.req.method === "OPTIONS") {
|
|
39
|
-
return new Response(null, {
|
|
40
|
-
status: 204,
|
|
41
|
-
headers: ctx.headers,
|
|
42
|
-
});
|
|
31
|
+
return new Response(null, { status: 204, headers: ctx.headers });
|
|
43
32
|
}
|
|
44
33
|
return await next();
|
|
45
34
|
};
|
|
46
35
|
}
|
|
36
|
+
export { cors, cors as default };
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { Context, Middleware } from "../index.js";
|
|
2
|
+
import { HttpBaseResponse } from "../types/index.js";
|
|
3
|
+
/**
|
|
4
|
+
* ⚙️ Configuration options for the `detectBot` middleware.
|
|
5
|
+
*/
|
|
6
|
+
export type DetectBotOptions = {
|
|
7
|
+
/**
|
|
8
|
+
* 🤖 List of known bot-like User-Agent patterns.
|
|
9
|
+
* @default ["bot", "spider", "crawl", "slurp"]
|
|
10
|
+
* @example
|
|
11
|
+
* botUserAgents: ["bot", "crawler", "indexer"]
|
|
12
|
+
*/
|
|
13
|
+
botUserAgents?: string[];
|
|
14
|
+
/**
|
|
15
|
+
* ⚖️ Enable rate-limiting based bot detection.
|
|
16
|
+
* Requires `getConnInfo()` import from TezX runtime.
|
|
17
|
+
* @default false
|
|
18
|
+
* @example
|
|
19
|
+
* enableRateLimiting: true
|
|
20
|
+
*/
|
|
21
|
+
enableRateLimiting?: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* ⚠️ Maximum allowed requests in the rate-limit window.
|
|
24
|
+
* Only used when `enableRateLimiting` is true.
|
|
25
|
+
* @default 30
|
|
26
|
+
*/
|
|
27
|
+
maxRequests?: number;
|
|
28
|
+
/**
|
|
29
|
+
* ⏱️ Time window for rate-limiting (in milliseconds).
|
|
30
|
+
* @default 60000 (1 minute)
|
|
31
|
+
*/
|
|
32
|
+
windowMs?: number;
|
|
33
|
+
/**
|
|
34
|
+
* 🔄 Custom rate-limit storage implementation.
|
|
35
|
+
* Allows integration with Redis, Memcached, or in-memory stores.
|
|
36
|
+
* @default In-memory Map
|
|
37
|
+
* @example
|
|
38
|
+
* storage: {
|
|
39
|
+
* get: (key) => myRedisClient.get(key),
|
|
40
|
+
* set: (key, value) => myRedisClient.set(key, value),
|
|
41
|
+
* clearExpired: () => {}
|
|
42
|
+
* }
|
|
43
|
+
*/
|
|
44
|
+
storage?: {
|
|
45
|
+
get: (key: string) => {
|
|
46
|
+
count: number;
|
|
47
|
+
resetTime: number;
|
|
48
|
+
} | undefined;
|
|
49
|
+
set: (key: string, value: {
|
|
50
|
+
count: number;
|
|
51
|
+
resetTime: number;
|
|
52
|
+
}) => void;
|
|
53
|
+
clearExpired: () => void;
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* 🚫 Optional IP blacklist checker.
|
|
57
|
+
* Should return true if the current request’s IP is banned or suspicious.
|
|
58
|
+
* @default () => false
|
|
59
|
+
* @example
|
|
60
|
+
* isBlacklisted: (ctx) => ctx.req.remoteAddress.address === "192.168.0.10"
|
|
61
|
+
*/
|
|
62
|
+
isBlacklisted?: (ctx: Context) => boolean | Promise<boolean>;
|
|
63
|
+
/**
|
|
64
|
+
* 🧠 Custom bot detector function.
|
|
65
|
+
* Use this for advanced heuristics (e.g., suspicious query params or headers).
|
|
66
|
+
* @example
|
|
67
|
+
* customBotDetector: (ctx) => ctx.query?.token === "weird"
|
|
68
|
+
*/
|
|
69
|
+
customBotDetector?: (ctx: Context) => boolean | Promise<boolean>;
|
|
70
|
+
/**
|
|
71
|
+
* 🛡️ Action executed when a bot is detected.
|
|
72
|
+
* Can return a custom HTTP response (e.g., JSON, HTML, or redirect).
|
|
73
|
+
* @default Responds with 403 and JSON error.
|
|
74
|
+
* @example
|
|
75
|
+
* onBotDetected: (ctx, reason) => ctx.status(403).json({ error: `Blocked: ${reason}` })
|
|
76
|
+
*/
|
|
77
|
+
onBotDetected?: (ctx: Context, reason: string) => HttpBaseResponse;
|
|
78
|
+
};
|
|
79
|
+
/**
|
|
80
|
+
* 🤖 Smart Bot Detection Middleware
|
|
81
|
+
*
|
|
82
|
+
* Detects automated or malicious requests using a combination of:
|
|
83
|
+
* - User-Agent analysis
|
|
84
|
+
* - IP blacklisting
|
|
85
|
+
* - Rate limiting
|
|
86
|
+
* - Custom detection logic
|
|
87
|
+
*
|
|
88
|
+
* 🧩 Supports:
|
|
89
|
+
* - Bun (uses precompiled RegExp for speed)
|
|
90
|
+
* - Node.js / Deno (optimized `includes()` loop)
|
|
91
|
+
*
|
|
92
|
+
* ⚙️ Requirements (for rate limiting):
|
|
93
|
+
* You must import `getConnInfo` from your runtime adapter:
|
|
94
|
+
* ```ts
|
|
95
|
+
* import { getConnInfo } from "tezx/bun";
|
|
96
|
+
* // or
|
|
97
|
+
* import { getConnInfo } from "tezx/node";
|
|
98
|
+
* // or
|
|
99
|
+
* import { getConnInfo } from "tezx/deno";
|
|
100
|
+
* ```
|
|
101
|
+
*
|
|
102
|
+
* 📦 Example Usage:
|
|
103
|
+
* ```ts
|
|
104
|
+
* import { detectBot } from "tezx/middleware/detectBot";
|
|
105
|
+
*
|
|
106
|
+
* app.use(detectBot({
|
|
107
|
+
* enableRateLimiting: true,
|
|
108
|
+
* botUserAgents: ["bot", "crawler"],
|
|
109
|
+
* onBotDetected: (ctx, reason) => ctx.status(403).json({ error: `Bot detected: ${reason}` })
|
|
110
|
+
* }));
|
|
111
|
+
* ```
|
|
112
|
+
*/
|
|
113
|
+
export declare const detectBot: (opts?: DetectBotOptions) => Middleware;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { GlobalConfig } from "../core/config.js";
|
|
2
|
+
import { createRateLimitDefaultStorage, isRateLimit, } from "../utils/rateLimit.js";
|
|
3
|
+
import { runtime } from "../utils/runtime.js";
|
|
4
|
+
export const detectBot = (opts = {}) => {
|
|
5
|
+
const botUAs = opts.botUserAgents || ["bot", "spider", "crawl", "slurp"];
|
|
6
|
+
let checkBot;
|
|
7
|
+
if (runtime === "bun") {
|
|
8
|
+
const botRegex = new RegExp(botUAs.join("|"), "i");
|
|
9
|
+
checkBot = (ua) => botRegex.test(ua);
|
|
10
|
+
}
|
|
11
|
+
else {
|
|
12
|
+
checkBot = (ua) => {
|
|
13
|
+
for (const b of botUAs)
|
|
14
|
+
if (ua.includes(b))
|
|
15
|
+
return true;
|
|
16
|
+
return false;
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
const maxReq = opts.maxRequests || 30;
|
|
20
|
+
const winMs = opts.windowMs || 60000;
|
|
21
|
+
const enableRL = !!opts.enableRateLimiting;
|
|
22
|
+
const onBot = opts.onBotDetected ??
|
|
23
|
+
((ctx, reason) => ctx.status(403).json({ error: `Bot detected: ${reason}` }));
|
|
24
|
+
let store = opts.storage;
|
|
25
|
+
if (enableRL && !store)
|
|
26
|
+
store = createRateLimitDefaultStorage();
|
|
27
|
+
return async (ctx, next) => {
|
|
28
|
+
const ua = ctx.headers.get("user-agent") || "";
|
|
29
|
+
if (checkBot(ua)) {
|
|
30
|
+
return onBot?.(ctx, "User-Agent");
|
|
31
|
+
}
|
|
32
|
+
if (await opts?.isBlacklisted?.(ctx)) {
|
|
33
|
+
return onBot?.(ctx, "Blacklisted IP");
|
|
34
|
+
}
|
|
35
|
+
if (enableRL) {
|
|
36
|
+
const addr = ctx.req.remoteAddress;
|
|
37
|
+
if (!addr) {
|
|
38
|
+
GlobalConfig.debugging.warn("[TezX detectBot] Missing remoteAddress. Use `getConnInfo(ctx)` to enable rate limiting.");
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
const key = `${addr.address}:${addr.port || 0}`;
|
|
42
|
+
const { check, entry } = isRateLimit(key, store, maxReq, winMs);
|
|
43
|
+
if (check && addr.address) {
|
|
44
|
+
return onBot?.(ctx, `Rate limit exceeded. Retry after ${Math.ceil((entry.resetTime - Date.now()) / 1000)} seconds.`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (await opts.customBotDetector?.(ctx)) {
|
|
49
|
+
return onBot?.(ctx, "Custom Detector");
|
|
50
|
+
}
|
|
51
|
+
return await next();
|
|
52
|
+
};
|
|
53
|
+
};
|
package/middleware/i18n.d.ts
CHANGED
|
@@ -2,100 +2,193 @@ import { Context, Middleware } from "../index.js";
|
|
|
2
2
|
export type TranslationMap = {
|
|
3
3
|
[key: string]: string | TranslationMap;
|
|
4
4
|
};
|
|
5
|
-
export type loadTranslations = (language: string) => Promise<
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
export type loadTranslations = (language: string) => Promise<TranslationMap>;
|
|
6
|
+
/**
|
|
7
|
+
* 📦 Interface defining a pluggable cache adapter for i18n translations.
|
|
8
|
+
*
|
|
9
|
+
* You can implement this interface to provide custom caching logic —
|
|
10
|
+
* for example, in-memory, Redis, file-based, or distributed cache.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* const redisCache: I18nCacheAdapter = {
|
|
15
|
+
* async get(lang) {
|
|
16
|
+
* const data = await redis.get(`i18n:${lang}`);
|
|
17
|
+
* return data ? JSON.parse(data) : null;
|
|
18
|
+
* },
|
|
19
|
+
* async set(lang, data) {
|
|
20
|
+
* await redis.set(`i18n:${lang}`, JSON.stringify(data), 'PX', data.expiresAt - Date.now());
|
|
21
|
+
* },
|
|
22
|
+
* async delete(lang) {
|
|
23
|
+
* await redis.del(`i18n:${lang}`);
|
|
24
|
+
* }
|
|
25
|
+
* };
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export interface I18nCacheAdapter {
|
|
10
29
|
/**
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* @
|
|
30
|
+
* Retrieve cached translations for a specific language.
|
|
31
|
+
*
|
|
32
|
+
* @param lang - The language code (e.g., `"en"`, `"bn"`, `"fr"`)
|
|
33
|
+
* @returns A Promise resolving to the cached translations object
|
|
34
|
+
* or `null` if not available or expired.
|
|
14
35
|
*/
|
|
15
|
-
|
|
36
|
+
get(lang: string): Promise<{
|
|
16
37
|
translations: TranslationMap;
|
|
17
|
-
expiresAt
|
|
18
|
-
}>;
|
|
19
|
-
/**
|
|
20
|
-
* ⏱️ Default cache duration in milliseconds
|
|
21
|
-
* @default 3600000 (1 hour)
|
|
22
|
-
*/
|
|
23
|
-
defaultCacheDuration?: number;
|
|
38
|
+
expiresAt: number;
|
|
39
|
+
} | null>;
|
|
24
40
|
/**
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* @
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
* cached.version === getCurrentVersion(lang);
|
|
31
|
-
* }
|
|
41
|
+
* Store translations in cache for a specific language.
|
|
42
|
+
*
|
|
43
|
+
* @param lang - The language code (e.g., `"en"`, `"es"`)
|
|
44
|
+
* @param data - An object containing the translation map and expiration timestamp.
|
|
45
|
+
* @returns A Promise that resolves when caching is complete.
|
|
32
46
|
*/
|
|
33
|
-
|
|
47
|
+
set(lang: string, data: {
|
|
34
48
|
translations: TranslationMap;
|
|
35
49
|
expiresAt: number;
|
|
36
|
-
}
|
|
50
|
+
}): Promise<void>;
|
|
51
|
+
/**
|
|
52
|
+
* Remove cached translations for a specific language.
|
|
53
|
+
*
|
|
54
|
+
* @param lang - The language code to delete.
|
|
55
|
+
* @returns A Promise that resolves when deletion is complete.
|
|
56
|
+
*/
|
|
57
|
+
delete(lang: string): Promise<void>;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* ⚙️ Configuration options for the TezX i18n middleware.
|
|
61
|
+
*
|
|
62
|
+
* This configuration provides flexibility for:
|
|
63
|
+
* - dynamic translation loading
|
|
64
|
+
* - custom caching strategies
|
|
65
|
+
* - language detection logic
|
|
66
|
+
* - message formatting and interpolation
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* ```ts
|
|
70
|
+
* app.use(i18n({
|
|
71
|
+
* loadTranslations: lang => import(`./locales/${lang}.json`),
|
|
72
|
+
* detectLanguage: ctx => ctx.req.query.lang || 'en',
|
|
73
|
+
* cacheTranslations: true,
|
|
74
|
+
* cacheStorage: redisCache,
|
|
75
|
+
* defaultLanguage: 'en',
|
|
76
|
+
* formatMessage: (msg, vars) => msg.replace(/\{\{(\w+)\}\}/g, (_, k) => vars[k])
|
|
77
|
+
* }));
|
|
78
|
+
* ```
|
|
79
|
+
*/
|
|
80
|
+
export type I18nOptions = {
|
|
81
|
+
/**
|
|
82
|
+
* 🌐 Function responsible for loading translation data dynamically.
|
|
83
|
+
*
|
|
84
|
+
* This can load translations from local JSON, database, API, etc.
|
|
85
|
+
*
|
|
86
|
+
* @param language - Language code to load (e.g., `"en"`, `"bn-BD"`)
|
|
87
|
+
* @returns Promise resolving to translation map for that language.
|
|
88
|
+
*/
|
|
89
|
+
loadTranslations: loadTranslations;
|
|
90
|
+
/**
|
|
91
|
+
* ⏱️ Default cache duration in milliseconds.
|
|
92
|
+
*
|
|
93
|
+
* Defines how long translations remain valid before reloading.
|
|
94
|
+
*
|
|
95
|
+
* @default 3600000 (1 hour)
|
|
96
|
+
*/
|
|
97
|
+
defaultCacheDuration?: number;
|
|
37
98
|
/**
|
|
38
|
-
* 🔍
|
|
39
|
-
*
|
|
99
|
+
* 🔍 Function to detect the preferred user language.
|
|
100
|
+
*
|
|
101
|
+
* Determines language priority from request context.
|
|
102
|
+
* Common strategies include:
|
|
103
|
+
* - URL query parameter
|
|
104
|
+
* - Cookie
|
|
105
|
+
* - HTTP `Accept-Language` header
|
|
106
|
+
*
|
|
40
107
|
* @example
|
|
41
|
-
* detectLanguage: (ctx) => ctx.cookies.
|
|
108
|
+
* detectLanguage: (ctx) => ctx.req.query.lang || ctx.cookies.lang || 'en'
|
|
42
109
|
*/
|
|
43
|
-
detectLanguage
|
|
110
|
+
detectLanguage: (ctx: Context) => string;
|
|
44
111
|
/**
|
|
45
|
-
* 🏠 Default fallback language
|
|
112
|
+
* 🏠 Default fallback language.
|
|
113
|
+
*
|
|
114
|
+
* Used when detected language translations are unavailable.
|
|
115
|
+
*
|
|
46
116
|
* @default "en"
|
|
47
117
|
*/
|
|
48
118
|
defaultLanguage?: string;
|
|
49
119
|
/**
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*/
|
|
53
|
-
fallbackChain?: string[];
|
|
54
|
-
/**
|
|
55
|
-
* 🗝️ Context property name for translation function
|
|
120
|
+
* 🗝️ Context property key where the translation function (`t`) is attached.
|
|
121
|
+
*
|
|
56
122
|
* @default "t"
|
|
123
|
+
* @example
|
|
124
|
+
* ```ts
|
|
125
|
+
* ctx.t("greeting.hello");
|
|
126
|
+
* ctx.t("user.welcome", { name: "Rakibul" });
|
|
127
|
+
* ```
|
|
57
128
|
*/
|
|
58
129
|
translationFunctionKey?: string;
|
|
59
130
|
/**
|
|
60
|
-
* 💬
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
131
|
+
* 💬 Custom message formatting function.
|
|
132
|
+
*
|
|
133
|
+
* Handles variable interpolation (e.g., replacing `{{name}}` with actual value).
|
|
134
|
+
* You can override this for advanced templating logic.
|
|
135
|
+
*
|
|
136
|
+
* @param message - The base translation string.
|
|
137
|
+
* @param vars - Optional variable map for placeholders.
|
|
138
|
+
* @returns Formatted string with variables replaced.
|
|
139
|
+
*
|
|
140
|
+
* @default
|
|
141
|
+
* ```ts
|
|
142
|
+
* (msg, vars = {}) =>
|
|
143
|
+
* Object.entries(vars).reduce(
|
|
144
|
+
* (m, [k, v]) => m.replace(new RegExp(`{{${k}}}`, "g"), String(v)),
|
|
145
|
+
* msg
|
|
146
|
+
* );
|
|
147
|
+
* ```
|
|
66
148
|
*/
|
|
67
|
-
formatMessage?: (message: string,
|
|
149
|
+
formatMessage?: (message: string, vars?: Record<string, any>) => string;
|
|
68
150
|
/**
|
|
69
|
-
*
|
|
70
|
-
*
|
|
151
|
+
* 🔒 Custom cache validation logic.
|
|
152
|
+
*
|
|
153
|
+
* Checks whether cached translations are still valid.
|
|
154
|
+
*
|
|
155
|
+
* @param cached - Cached object with translations and expiry timestamp.
|
|
156
|
+
* @param lang - The language being validated.
|
|
157
|
+
* @returns `true` if cache is valid, otherwise `false`.
|
|
158
|
+
*
|
|
159
|
+
* @default
|
|
160
|
+
* ```ts
|
|
161
|
+
* (cached) => cached.expiresAt > Date.now()
|
|
162
|
+
* ```
|
|
163
|
+
*/
|
|
164
|
+
isCacheValid?: (cached: {
|
|
165
|
+
translations: TranslationMap;
|
|
166
|
+
expiresAt: number;
|
|
167
|
+
}, lang: string) => boolean;
|
|
168
|
+
/**
|
|
169
|
+
* 🗃️ Whether to enable caching for translations.
|
|
170
|
+
*
|
|
171
|
+
* If disabled, translations are always loaded fresh.
|
|
172
|
+
*
|
|
173
|
+
* @default false
|
|
71
174
|
*/
|
|
72
175
|
cacheTranslations?: boolean;
|
|
176
|
+
/**
|
|
177
|
+
* 💾 Optional external cache adapter for distributed or persistent caching.
|
|
178
|
+
*
|
|
179
|
+
* Supports any system implementing `I18nCacheAdapter` interface —
|
|
180
|
+
* such as Redis, filesystem, in-memory store, or custom backends.
|
|
181
|
+
*
|
|
182
|
+
* @example
|
|
183
|
+
* ```ts
|
|
184
|
+
* app.use(i18n({
|
|
185
|
+
* loadTranslations,
|
|
186
|
+
* cacheTranslations: true,
|
|
187
|
+
* cacheStorage: redisCache
|
|
188
|
+
* }));
|
|
189
|
+
* ```
|
|
190
|
+
*/
|
|
191
|
+
cacheStorage?: I18nCacheAdapter;
|
|
73
192
|
};
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
*
|
|
77
|
-
* Features:
|
|
78
|
-
* - Hierarchical language resolution
|
|
79
|
-
* - Nested translation keys
|
|
80
|
-
* - Template interpolation
|
|
81
|
-
* - Optional caching
|
|
82
|
-
* - Custom language detection
|
|
83
|
-
*
|
|
84
|
-
* @param {I18nOptions} options - Configuration options
|
|
85
|
-
* @returns {Middleware} Configured middleware
|
|
86
|
-
*
|
|
87
|
-
* @example
|
|
88
|
-
* // Basic usage
|
|
89
|
-
* app.use(i18n({
|
|
90
|
-
* loadTranslations: lang => import(`./locales/${lang}.json`),
|
|
91
|
-
* defaultLanguage: 'en'
|
|
92
|
-
* }));
|
|
93
|
-
*
|
|
94
|
-
* // With caching and custom detection
|
|
95
|
-
* app.use(i18n({
|
|
96
|
-
* loadTranslations: fetchTranslations,
|
|
97
|
-
* detectLanguage: ctx => ctx.get('X-Language'),
|
|
98
|
-
* cacheTranslations: true
|
|
99
|
-
* }));
|
|
100
|
-
*/
|
|
101
|
-
export declare const i18n: (options: I18nOptions) => Middleware;
|
|
193
|
+
declare const i18n: (options: I18nOptions) => Middleware;
|
|
194
|
+
export { i18n as default, i18n };
|