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.
Files changed (90) hide show
  1. package/bun/index.d.ts +4 -2
  2. package/bun/index.js +3 -2
  3. package/bun/ws.d.ts +37 -0
  4. package/bun/ws.js +23 -0
  5. package/cjs/bun/index.js +37 -5
  6. package/cjs/bun/ws.js +25 -0
  7. package/cjs/core/context.js +86 -88
  8. package/cjs/core/error.js +41 -0
  9. package/cjs/core/request.js +6 -6
  10. package/cjs/core/router.js +6 -6
  11. package/cjs/core/server.js +45 -63
  12. package/cjs/index.js +5 -2
  13. package/cjs/middleware/basic-auth.js +28 -54
  14. package/cjs/middleware/bearer-auth.js +34 -0
  15. package/cjs/middleware/cors.js +14 -24
  16. package/cjs/middleware/index.js +25 -0
  17. package/cjs/middleware/logger.js +6 -3
  18. package/cjs/middleware/pagination.js +1 -1
  19. package/cjs/middleware/powered-by.js +1 -1
  20. package/cjs/middleware/rate-limiter.js +20 -7
  21. package/cjs/middleware/request-id.js +4 -7
  22. package/cjs/middleware/sanitize-headers.js +8 -40
  23. package/cjs/middleware/xss-protection.js +2 -6
  24. package/cjs/registry/RadixRouter.js +72 -23
  25. package/cjs/utils/cookie.js +1 -1
  26. package/cjs/utils/rateLimit.js +2 -2
  27. package/cjs/utils/regexRouter.js +1 -0
  28. package/cjs/utils/response.js +21 -30
  29. package/core/context.d.ts +68 -68
  30. package/core/context.js +87 -89
  31. package/core/error.d.ts +95 -0
  32. package/core/error.js +37 -0
  33. package/core/request.d.ts +2 -2
  34. package/core/request.js +6 -6
  35. package/core/router.d.ts +11 -6
  36. package/core/router.js +6 -6
  37. package/core/server.js +45 -63
  38. package/index.d.ts +5 -3
  39. package/index.js +4 -2
  40. package/middleware/basic-auth.d.ts +38 -66
  41. package/middleware/basic-auth.js +28 -54
  42. package/middleware/bearer-auth.d.ts +52 -0
  43. package/middleware/bearer-auth.js +30 -0
  44. package/middleware/cors.d.ts +7 -21
  45. package/middleware/cors.js +14 -24
  46. package/middleware/index.d.ts +9 -0
  47. package/middleware/index.js +9 -0
  48. package/middleware/logger.d.ts +3 -1
  49. package/middleware/logger.js +6 -3
  50. package/middleware/pagination.d.ts +8 -6
  51. package/middleware/pagination.js +1 -1
  52. package/middleware/powered-by.js +1 -1
  53. package/middleware/rate-limiter.d.ts +0 -4
  54. package/middleware/rate-limiter.js +20 -7
  55. package/middleware/request-id.d.ts +1 -1
  56. package/middleware/request-id.js +3 -6
  57. package/middleware/sanitize-headers.d.ts +3 -11
  58. package/middleware/sanitize-headers.js +8 -40
  59. package/middleware/xss-protection.d.ts +1 -1
  60. package/middleware/xss-protection.js +1 -5
  61. package/package.json +6 -1
  62. package/registry/RadixRouter.js +72 -23
  63. package/types/index.d.ts +2 -1
  64. package/utils/cookie.js +1 -1
  65. package/utils/rateLimit.d.ts +1 -2
  66. package/utils/rateLimit.js +2 -2
  67. package/utils/regexRouter.js +1 -0
  68. package/utils/response.d.ts +12 -14
  69. package/utils/response.js +20 -29
  70. package/cjs/middleware/cache-control.js +0 -93
  71. package/cjs/middleware/detect-bot.js +0 -66
  72. package/cjs/middleware/detect-locale.js +0 -45
  73. package/cjs/middleware/i18n.js +0 -93
  74. package/cjs/middleware/lazy-loader.js +0 -74
  75. package/cjs/middleware/request-timeout.js +0 -43
  76. package/cjs/middleware/secure-headers.js +0 -43
  77. package/middleware/cache-control.d.ts +0 -56
  78. package/middleware/cache-control.js +0 -56
  79. package/middleware/detect-bot.d.ts +0 -111
  80. package/middleware/detect-bot.js +0 -62
  81. package/middleware/detect-locale.d.ts +0 -56
  82. package/middleware/detect-locale.js +0 -41
  83. package/middleware/i18n.d.ts +0 -102
  84. package/middleware/i18n.js +0 -89
  85. package/middleware/lazy-loader.d.ts +0 -73
  86. package/middleware/lazy-loader.js +0 -70
  87. package/middleware/request-timeout.d.ts +0 -26
  88. package/middleware/request-timeout.js +0 -39
  89. package/middleware/secure-headers.d.ts +0 -78
  90. package/middleware/secure-headers.js +0 -39
@@ -1,93 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.default = exports.i18n = void 0;
4
- const config_js_1 = require("../core/config.js");
5
- const cookie_js_1 = require("../utils/cookie.js");
6
- const i18n = (options) => {
7
- const { loadTranslations, defaultCacheDuration = 3600000, isCacheValid = (cached) => cached.expiresAt > Date.now(), detectLanguage = (ctx) => {
8
- if (ctx.req.query.lang) {
9
- return ctx.req.query.lang;
10
- }
11
- else if ((0, cookie_js_1.getCookie)(ctx, "lang")) {
12
- return ctx.cookies?.["lang"];
13
- }
14
- else if (ctx.req.header("accept-language")) {
15
- const lang = ctx.req.header("accept-language")?.split(",")[0];
16
- if (lang)
17
- return lang;
18
- }
19
- else if (options.defaultLanguage) {
20
- return options.defaultLanguage;
21
- }
22
- return "en";
23
- }, defaultLanguage = "en", fallbackChain = [], translationFunctionKey = "t", formatMessage = (message, options = {}) => {
24
- return Object.entries(options).reduce((msg, [key, value]) => msg.replace(new RegExp(`{{${key}}}`, "g"), String(value)), message);
25
- }, cacheTranslations = true, } = options;
26
- const translationCache = {};
27
- return async function i18n(ctx, next) {
28
- try {
29
- const detectedLanguage = detectLanguage(ctx);
30
- const languageChain = [
31
- detectedLanguage,
32
- ...fallbackChain,
33
- defaultLanguage,
34
- ].filter(Boolean);
35
- let translations = null;
36
- let selectedLanguage = defaultLanguage;
37
- for (const lang of languageChain) {
38
- const normalizedLang = lang.split("-")[0].toLowerCase();
39
- if (cacheTranslations && translationCache[normalizedLang]) {
40
- const cached = translationCache[normalizedLang];
41
- if (isCacheValid(cached, normalizedLang)) {
42
- translations = cached.translations;
43
- selectedLanguage = lang;
44
- break;
45
- }
46
- delete translationCache[normalizedLang];
47
- }
48
- try {
49
- const { translations: loadedTranslations, expiresAt } = await loadTranslations(normalizedLang);
50
- let expirationTime = Date.now() + defaultCacheDuration;
51
- if (expiresAt instanceof Date) {
52
- expirationTime = expiresAt.getTime();
53
- }
54
- else if (typeof expiresAt === "number") {
55
- expirationTime = expiresAt;
56
- }
57
- translations = loadedTranslations;
58
- selectedLanguage = lang;
59
- if (cacheTranslations) {
60
- translationCache[normalizedLang] = {
61
- translations,
62
- expiresAt: expirationTime,
63
- };
64
- }
65
- break;
66
- }
67
- catch (error) {
68
- config_js_1.GlobalConfig.debugging.warn(`Translation load failed for ${lang}:`, error);
69
- }
70
- }
71
- if (!translations) {
72
- throw new Error("No translations available");
73
- }
74
- ctx[translationFunctionKey] = (key, options) => {
75
- const value = key.split(".").reduce((acc, k) => {
76
- return acc && typeof acc === "object" ? acc[k] : undefined;
77
- }, translations);
78
- const message = typeof value === "string" ? value : key;
79
- return formatMessage(message, options);
80
- };
81
- ctx.language = selectedLanguage;
82
- ctx.languageChain = languageChain;
83
- return await next();
84
- }
85
- catch (error) {
86
- config_js_1.GlobalConfig.debugging.error("i18n processing error:", error);
87
- ctx.setStatus = 500;
88
- throw error;
89
- }
90
- };
91
- };
92
- exports.i18n = i18n;
93
- exports.default = i18n;
@@ -1,74 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.default = exports.lazyLoader = void 0;
4
- const config_js_1 = require("../core/config.js");
5
- const lazyLoader = (options) => {
6
- const { moduleKey = (ctx) => ctx.req.params[queryKeyModule] || ctx.req.query[queryKeyModule], getModuleLoader, queryKeyModule = "module", moduleContextKey = "module", cacheTTL = 3600000, enableCache = true, cacheStorage, lifecycleHooks = {}, validateModule, } = options;
7
- let storage = cacheStorage;
8
- if (enableCache && !cacheStorage) {
9
- storage = new Map();
10
- }
11
- return async function lazyLoader(ctx, next) {
12
- let moduleName = moduleKey(ctx) ||
13
- ctx.req.params[queryKeyModule] ||
14
- ctx.req.query[queryKeyModule];
15
- if (!moduleName) {
16
- config_js_1.GlobalConfig.debugging.warn("No module specified for lazy loading.");
17
- return await next();
18
- }
19
- try {
20
- if (enableCache) {
21
- const cached = storage.get(moduleName);
22
- if (cached) {
23
- if (cached.expiresAt < Date.now()) {
24
- storage.delete(moduleName);
25
- }
26
- else {
27
- config_js_1.GlobalConfig.debugging.info(`Using cached module: ${moduleName}`);
28
- ctx[moduleContextKey] = cached?.module;
29
- lifecycleHooks.onCacheHit?.(moduleName, ctx[moduleContextKey], ctx);
30
- lifecycleHooks.onComplete?.(moduleName, ctx[moduleContextKey], ctx);
31
- return await next();
32
- }
33
- }
34
- }
35
- if (!getModuleLoader) {
36
- throw new Error(`No module loader found for module: ${moduleName}`);
37
- }
38
- const moduleLoader = await getModuleLoader(ctx);
39
- if (!moduleLoader) {
40
- throw new Error(`No loader found for module: ${moduleName}`);
41
- }
42
- lifecycleHooks.onLoad?.(moduleName, ctx);
43
- const module = await moduleLoader();
44
- if (validateModule && !validateModule(module)) {
45
- throw new Error(`Module validation failed for: ${moduleName}`);
46
- }
47
- if (enableCache) {
48
- storage.set(moduleName, {
49
- module,
50
- expiresAt: Date.now() + cacheTTL,
51
- });
52
- lifecycleHooks.onCacheSet?.(moduleName, module, ctx);
53
- }
54
- ctx[moduleContextKey] = module;
55
- if (module.init && typeof module.init === "function") {
56
- const initResult = await module.init(ctx);
57
- if (initResult) {
58
- return initResult;
59
- }
60
- }
61
- lifecycleHooks.onComplete?.(moduleName, module, ctx);
62
- config_js_1.GlobalConfig.debugging.success(`Successfully loaded module: ${moduleName}`);
63
- }
64
- catch (error) {
65
- config_js_1.GlobalConfig.debugging.error(`Error loading module: ${moduleName}`, error);
66
- lifecycleHooks.onError?.(moduleName, error, ctx);
67
- ctx.setStatus = 500;
68
- throw error;
69
- }
70
- return await next();
71
- };
72
- };
73
- exports.lazyLoader = lazyLoader;
74
- exports.default = lazyLoader;
@@ -1,43 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.default = exports.requestTimeout = void 0;
4
- const config_js_1 = require("../core/config.js");
5
- const requestTimeout = (options) => {
6
- const { getTimeout, onTimeout = (ctx) => {
7
- ctx.setStatus = 504;
8
- ctx.body = { error: "Request timed out." };
9
- }, logTimeoutEvent = (ctx, error) => {
10
- config_js_1.GlobalConfig.debugging.warn(`[TIMEOUT] ${error.message}: ${ctx.method} ${ctx.pathname}`);
11
- }, cleanup = () => {
12
- }, } = options;
13
- return async function requestTimeout(ctx, next) {
14
- let timeoutId = null;
15
- try {
16
- const timeout = getTimeout(ctx);
17
- const timeoutPromise = new Promise((_, reject) => {
18
- timeoutId = setTimeout(() => {
19
- const timeoutError = new Error("Request timed out.");
20
- logTimeoutEvent(ctx, timeoutError);
21
- reject(timeoutError);
22
- }, timeout);
23
- });
24
- return await Promise.race([next(), timeoutPromise]);
25
- }
26
- catch (error) {
27
- if (error.message === "Request timed out.") {
28
- onTimeout(ctx, error);
29
- }
30
- else {
31
- throw error;
32
- }
33
- }
34
- finally {
35
- if (timeoutId) {
36
- clearTimeout(timeoutId);
37
- }
38
- cleanup(ctx);
39
- }
40
- };
41
- };
42
- exports.requestTimeout = requestTimeout;
43
- exports.default = requestTimeout;
@@ -1,43 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.default = exports.secureHeaders = void 0;
4
- const secureHeaders = (options = {}) => {
5
- return async function secureHeaders(ctx, next) {
6
- const resolveValue = (value) => {
7
- return typeof value === "function" ? value(ctx) : value;
8
- };
9
- const contentSecurityPolicy = resolveValue(options.contentSecurityPolicy) ||
10
- "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline';";
11
- const frameGuard = resolveValue(options.frameGuard) ?? true;
12
- const hsts = resolveValue(options.hsts) ?? true;
13
- const xssProtection = resolveValue(options.xssProtection) ?? true;
14
- const noSniff = resolveValue(options.noSniff) ?? true;
15
- const referrerPolicy = resolveValue(options.referrerPolicy) || "no-referrer";
16
- const permissionsPolicy = resolveValue(options.permissionsPolicy) ||
17
- "geolocation=(), microphone=(), camera=()";
18
- if (contentSecurityPolicy) {
19
- ctx.setHeader("Content-Security-Policy", contentSecurityPolicy);
20
- }
21
- if (frameGuard) {
22
- ctx.setHeader("X-Frame-Options", "DENY");
23
- }
24
- if (hsts) {
25
- ctx.setHeader("Strict-Transport-Security", "max-age=63072000; includeSubDomains");
26
- }
27
- if (xssProtection) {
28
- ctx.setHeader("X-XSS-Protection", "1; mode=block");
29
- }
30
- if (noSniff) {
31
- ctx.setHeader("X-Content-Type-Options", "nosniff");
32
- }
33
- if (referrerPolicy) {
34
- ctx.setHeader("Referrer-Policy", referrerPolicy);
35
- }
36
- if (permissionsPolicy) {
37
- ctx.setHeader("Permissions-Policy", permissionsPolicy);
38
- }
39
- return await next();
40
- };
41
- };
42
- exports.secureHeaders = secureHeaders;
43
- exports.default = secureHeaders;
@@ -1,56 +0,0 @@
1
- import { Context } from "../core/context.js";
2
- import { HttpBaseResponse, Middleware } from "../types/index.js";
3
- export type CacheRule = {
4
- /**
5
- * 🎯 Condition to determine if this rule applies.
6
- */
7
- condition: (ctx: Context) => boolean;
8
- /**
9
- * ⏳ Maximum age (in seconds) for caching.
10
- */
11
- maxAge: number;
12
- /**
13
- * 🌐 Cache scope: "public" or "private".
14
- */
15
- scope: "public" | "private";
16
- /**
17
- * πŸ”„ Enable or disable revalidation with ETag.
18
- */
19
- enableETag: boolean;
20
- /**
21
- * 🏷️ Vary header for cache variations.
22
- */
23
- vary?: string[];
24
- };
25
- export type CacheSettings = Pick<CacheRule, "maxAge" | "scope" | "enableETag" | "vary">;
26
- export type CacheOptions = {
27
- /**
28
- * πŸ§ͺ Weak ETag generation (optional).
29
- */
30
- useWeakETag?: boolean;
31
- /**
32
- * Error handler for cache middleware.
33
- * @param error - The error that occurred.
34
- * @param ctx - The current request context.
35
- * @returns An HTTP response to send when an error occurs.
36
- */
37
- onError?: (error: Error, ctx: Context) => HttpBaseResponse;
38
- /**
39
- * πŸ“ Logging function for cache events.
40
- */
41
- logEvent?: (event: "cached" | "no-cache" | "error", ctx: Context, error?: Error) => void;
42
- /**
43
- * πŸ› οΈ Default cache settings.
44
- */
45
- defaultSettings: CacheSettings;
46
- /**
47
- * πŸ”§ Custom rules for dynamic caching behavior.
48
- */
49
- rules?: CacheRule[];
50
- };
51
- /**
52
- * Middleware to manage HTTP caching headers dynamically.
53
- * @param options - Custom options for dynamic caching behavior.
54
- */
55
- declare const cacheControl: (options: CacheOptions) => Middleware;
56
- export { cacheControl, cacheControl as default };
@@ -1,56 +0,0 @@
1
- import { GlobalConfig } from "../core/config.js";
2
- const cacheControl = (options) => {
3
- const { defaultSettings, useWeakETag = false, rules = [], onError = (error, ctx) => {
4
- ctx.setStatus = 500;
5
- ctx.body = { error: "Failed to set cache headers." };
6
- }, logEvent = (event, ctx, error) => {
7
- if (event === "error") {
8
- GlobalConfig.debugging.error(`[CACHE] ${event.toUpperCase()}: ${error?.message}`);
9
- }
10
- else {
11
- GlobalConfig.debugging.success(`[CACHE] ${event.toUpperCase()} for ${ctx.method} ${ctx.pathname}`);
12
- }
13
- }, } = options;
14
- return async function cacheControl(ctx, next) {
15
- if (!["GET", "HEAD"].includes(ctx.method)) {
16
- return await next();
17
- }
18
- try {
19
- await next();
20
- const matchedRule = rules.find((rule) => rule.condition(ctx)) || null;
21
- const { maxAge, scope, enableETag, vary } = matchedRule || defaultSettings;
22
- const cacheControlValue = `${scope}, max-age=${maxAge}`;
23
- ctx.setHeader("Cache-Control", cacheControlValue);
24
- const expiresDate = new Date(Date.now() + maxAge * 1000).toUTCString();
25
- ctx.setHeader("Expires", expiresDate);
26
- if (vary?.length) {
27
- ctx.setHeader("Vary", vary.join(", "));
28
- }
29
- if (enableETag) {
30
- const responseBody = typeof ctx.resBody === "string"
31
- ? ctx.resBody
32
- : JSON.stringify(ctx.resBody ?? "");
33
- const etag = await generateETag(responseBody, useWeakETag);
34
- const ifNoneMatch = ctx.req.header("if-none-match");
35
- if (ifNoneMatch === etag) {
36
- ctx.setStatus = 304;
37
- ctx.body = null;
38
- logEvent("cached", ctx);
39
- return;
40
- }
41
- ctx.setHeader("ETag", etag);
42
- }
43
- logEvent("cached", ctx);
44
- }
45
- catch (error) {
46
- logEvent("error", ctx, error);
47
- return onError?.(error, ctx);
48
- }
49
- };
50
- };
51
- const generateETag = async (content, weak = false) => {
52
- const crypto = await import("node:crypto");
53
- const hash = crypto.createHash("md5").update(content).digest("hex");
54
- return weak ? `W/"${hash}"` : `"${hash}"`;
55
- };
56
- export { cacheControl, cacheControl as default };
@@ -1,111 +0,0 @@
1
- import { Context, Middleware } from "../index.js";
2
- import { HttpBaseResponse } from "../types/index.js";
3
- export type DetectBotReason = "User-Agent" | "Blacklisted IP" | "Query Parameter" | "Rate Limiting" | "Custom Detector" | "Multiple Indicators";
4
- export type BotDetectionResult = {
5
- isBot: boolean;
6
- reason?: DetectBotReason;
7
- indicators: string[];
8
- };
9
- export type DetectBotOptions = {
10
- /**
11
- * πŸ€– List of bot-like user-agent substrings to detect
12
- * @default ["bot", "spider", "crawl", "slurp"]
13
- */
14
- botUserAgents?: string[];
15
- /**
16
- * ⚠️ Maximum allowed requests in the time window
17
- * @default 30 requests
18
- */
19
- maxRequests?: number;
20
- /**
21
- * ⏱️ Time window in milliseconds for rate limiting
22
- * @default 60000 (1 minute)
23
- */
24
- windowMs?: number;
25
- /**
26
- * 🚫 IP blacklist checker
27
- * @default () => false
28
- */
29
- isBlacklisted?: (ctx: Context, remoteAddress: string) => boolean | Promise<boolean>;
30
- /**
31
- * πŸ” Query parameter name for bot identification
32
- * @default "bot"
33
- */
34
- queryKeyBot?: string;
35
- /**
36
- * πŸ›‘οΈ Action to take when bot is detected
37
- * @default "block"
38
- */
39
- onBotDetected?: "block" | ((ctx: Context, result: BotDetectionResult) => HttpBaseResponse);
40
- /**
41
- * βš–οΈ Enable rate-limiting based detection
42
- * @default false
43
- */
44
- enableRateLimiting?: boolean;
45
- /**
46
- * πŸ”Ž Custom bot detection logic
47
- * @default () => false
48
- */
49
- customBotDetector?: (ctx: Context) => boolean | Promise<boolean>;
50
- /**
51
- * βœ‰οΈ Custom response for blocked requests
52
- */
53
- customBlockedResponse?: (ctx: Context, result: BotDetectionResult) => HttpBaseResponse;
54
- /**
55
- * πŸ”„ Custom cache storage implementation (e.g., using `Map`, `Redis`, etc.).
56
- * By default, it uses a `Map<string, { count: number; resetTime: number }>`.
57
- */
58
- storage?: {
59
- get: (key: string) => {
60
- count: number;
61
- resetTime: number;
62
- } | undefined;
63
- set: (key: string, value: {
64
- count: number;
65
- resetTime: number;
66
- }) => void;
67
- clearExpired: () => void;
68
- };
69
- /**
70
- * πŸ“Š Minimum confidence score to consider as bot (0-1)
71
- * @default 0.5
72
- */
73
- confidenceThreshold?: number;
74
- };
75
- /**
76
- * πŸ€– Advanced bot detection middleware with multiple detection methods
77
- * @requires
78
- *
79
- ```ts
80
- import { getConnInfo } from "tezx/bun";
81
- // or
82
- import { getConnInfo } from "tezx/deno";
83
- // or
84
- import { getConnInfo } from "tezx/node";
85
- ```
86
- * Features:
87
- * - User-Agent analysis
88
- * - IP blacklisting
89
- * - Query parameter detection
90
- * - Rate limiting
91
- * - Custom detection logic
92
- * - Confidence-based scoring
93
- *
94
- * @param {DetectBotOptions} options - Configuration options
95
- * @returns {Middleware} Configured middleware
96
- *
97
- * @example
98
- * // Basic usage
99
- * app.use(detectBot());
100
- *
101
- * // Custom configuration
102
- * app.use(detectBot({
103
- * botUserAgents: ["bot", "crawler"],
104
- * isBlacklistedIP: async (ip) => await checkIPReputation(ip),
105
- * onBotDetected: (ctx, { reason }) => {
106
- * ctx.status = 403;
107
- * return ctx.json({ error: `Bot detected (${reason})` });
108
- * }
109
- * }));
110
- */
111
- export declare const detectBot: (options?: DetectBotOptions) => Middleware;
@@ -1,62 +0,0 @@
1
- import { GlobalConfig } from "../core/config.js";
2
- import { createRateLimitDefaultStorage, isRateLimit, } from "../utils/rateLimit.js";
3
- export const detectBot = (options = {}) => {
4
- 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 }) => {
5
- ctx.setStatus = 403;
6
- return ctx.json({ error: `Bot detected: ${reason}` });
7
- }, storage, confidenceThreshold = 0.5, } = options;
8
- let store = storage;
9
- if (enableRateLimiting) {
10
- store = createRateLimitDefaultStorage();
11
- }
12
- return async function detectBot(ctx, next) {
13
- const detectionResult = {
14
- isBot: false,
15
- indicators: [],
16
- };
17
- const userAgent = ctx.header("user-agent")?.toLowerCase() || "";
18
- const remoteAddress = `${ctx.req.remoteAddress?.address}:${ctx.req.remoteAddress?.port}` ||
19
- "unknown";
20
- const isBotQuery = ctx.req.query[queryKeyBot] === "true";
21
- if (botUserAgents.some((agent) => userAgent.includes(agent))) {
22
- detectionResult.indicators.push("User-Agent");
23
- }
24
- if (await isBlacklisted(ctx, remoteAddress)) {
25
- detectionResult.indicators.push("Blacklisted IP");
26
- }
27
- if (isBotQuery) {
28
- detectionResult.indicators.push("Query Parameter");
29
- }
30
- const key = `${ctx.req.remoteAddress.address}:${ctx.req.remoteAddress.port}`;
31
- if (enableRateLimiting &&
32
- isRateLimit(ctx, key, store, maxRequests, windowMs).check) {
33
- detectionResult.indicators.push("Rate Limiting");
34
- }
35
- if (await customBotDetector(ctx)) {
36
- detectionResult.indicators.push("Custom Detector");
37
- }
38
- detectionResult.isBot = detectionResult.indicators.length > 0;
39
- if (detectionResult.indicators.length > 1) {
40
- detectionResult.reason = "Multiple Indicators";
41
- const confidence = Math.min(0.3 * detectionResult.indicators.length, 1);
42
- detectionResult.isBot = confidence >= confidenceThreshold;
43
- }
44
- else if (detectionResult.indicators.length === 1) {
45
- detectionResult.reason = detectionResult.indicators[0];
46
- }
47
- if (detectionResult.isBot) {
48
- GlobalConfig.debugging.warn(`Bot detected: ${detectionResult.reason}`, {
49
- ip: remoteAddress,
50
- userAgent,
51
- indicators: detectionResult.indicators,
52
- });
53
- if (onBotDetected === "block") {
54
- return customBlockedResponse(ctx, detectionResult);
55
- }
56
- else if (typeof onBotDetected === "function") {
57
- return onBotDetected(ctx, detectionResult);
58
- }
59
- }
60
- return await next();
61
- };
62
- };
@@ -1,56 +0,0 @@
1
- import { Context, Middleware } from "../index.js";
2
- /**
3
- * Options for the detectLocale middleware.
4
- */
5
- export type DetectLocaleOptions = {
6
- /**
7
- * 🌐 List of allowed locales.
8
- * e.g., ["en", "fr", "bn"]
9
- */
10
- supportedLocales: string[];
11
- /**
12
- * 🏠 Default locale if none is matched from query, cookie, or headers.
13
- * @default "en"
14
- */
15
- defaultLocale?: string;
16
- /**
17
- * πŸ” Name of the query parameter to check for locale.
18
- * Example: /?lang=fr
19
- * @default "lang"
20
- */
21
- queryKeyLocale?: string;
22
- /**
23
- * πŸͺ Name of the cookie used to store locale preference.
24
- * @default "locale"
25
- */
26
- cookieKeyLocale?: string;
27
- /**
28
- * πŸ—ΊοΈ Key under which the locale will be attached to the context object.
29
- * Example: ctx.locale = "en"
30
- * @default "locale"
31
- */
32
- localeContextKey?: string;
33
- /**
34
- * πŸ› οΈ Optional custom function to programmatically detect locale.
35
- * Called last before fallback.
36
- * Should return a supported locale or undefined.
37
- */
38
- customLocaleDetector?: (ctx: Context) => string | undefined;
39
- };
40
- /**
41
- * 🌍 Middleware that detects and sets the user's preferred locale.
42
- *
43
- * Detection order:
44
- * 1. Query parameter (e.g., ?lang=fr)
45
- * 2. Cookie value (e.g., locale=fr)
46
- * 3. Accept-Language HTTP header
47
- * 4. Custom detector function (if provided)
48
- * 5. Default locale (fallback)
49
- *
50
- * The detected locale is stored in `ctx[localeContextKey]`.
51
- *
52
- * @param options - Configuration options for locale detection.
53
- * @returns Middleware function that attaches locale to the context.
54
- */
55
- declare const detectLocale: (options: DetectLocaleOptions) => Middleware;
56
- export { detectLocale, detectLocale as default };
@@ -1,41 +0,0 @@
1
- import { GlobalConfig } from "../core/config.js";
2
- import { getCookie } from "../utils/cookie.js";
3
- const detectLocale = (options) => {
4
- const { supportedLocales, defaultLocale = "en", queryKeyLocale = "lang", cookieKeyLocale = "locale", localeContextKey = "locale", customLocaleDetector, } = options;
5
- return async function detectLocale(ctx, next) {
6
- let detectedLocale;
7
- const queryLocale = ctx.req.query[queryKeyLocale];
8
- if (queryLocale && supportedLocales.includes(queryLocale)) {
9
- detectedLocale = queryLocale;
10
- }
11
- if (!detectedLocale) {
12
- const cookieLocale = getCookie(ctx, cookieKeyLocale);
13
- if (cookieLocale && supportedLocales.includes(cookieLocale)) {
14
- detectedLocale = cookieLocale;
15
- }
16
- }
17
- if (!detectedLocale) {
18
- const acceptLanguage = ctx.req.header("accept-language");
19
- if (acceptLanguage) {
20
- const preferredLocales = acceptLanguage
21
- .split(",")
22
- .map((lang) => lang.split(";")[0].trim())
23
- .filter((lang) => supportedLocales.includes(lang));
24
- detectedLocale = preferredLocales[0];
25
- }
26
- }
27
- if (!detectedLocale && customLocaleDetector) {
28
- const customLocale = customLocaleDetector(ctx);
29
- if (customLocale && supportedLocales.includes(customLocale)) {
30
- detectedLocale = customLocale;
31
- }
32
- }
33
- if (!detectedLocale) {
34
- detectedLocale = defaultLocale;
35
- }
36
- ctx[localeContextKey] = detectedLocale;
37
- GlobalConfig.debugging.success(`Detected locale: ${detectedLocale}`);
38
- return await next();
39
- };
40
- };
41
- export { detectLocale, detectLocale as default };