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
package/middleware/i18n.d.ts
DELETED
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
import { Context, Middleware } from "../index.js";
|
|
2
|
-
export type TranslationMap = {
|
|
3
|
-
[key: string]: string | TranslationMap;
|
|
4
|
-
};
|
|
5
|
-
export type loadTranslations = (language: string) => Promise<{
|
|
6
|
-
translations: TranslationMap;
|
|
7
|
-
expiresAt?: Date | number;
|
|
8
|
-
}>;
|
|
9
|
-
export type I18nOptions = {
|
|
10
|
-
/**
|
|
11
|
-
* 🌐 Function to load translations dynamically
|
|
12
|
-
* @param language - Language code to load (e.g., "en-US")
|
|
13
|
-
* @returns Promise with translations map and optional expiration
|
|
14
|
-
*/
|
|
15
|
-
loadTranslations: (language: string) => Promise<{
|
|
16
|
-
translations: TranslationMap;
|
|
17
|
-
expiresAt?: Date | number;
|
|
18
|
-
}>;
|
|
19
|
-
/**
|
|
20
|
-
* ⏱️ Default cache duration in milliseconds
|
|
21
|
-
* @default 3600000 (1 hour)
|
|
22
|
-
*/
|
|
23
|
-
defaultCacheDuration?: number;
|
|
24
|
-
/**
|
|
25
|
-
* 🔄 Function to check if cached translations are stale
|
|
26
|
-
* @default Checks expiration time
|
|
27
|
-
* @example
|
|
28
|
-
* isCacheValid: (cached, lang) => {
|
|
29
|
-
* return cached.expiresAt > Date.now() &&
|
|
30
|
-
* cached.version === getCurrentVersion(lang);
|
|
31
|
-
* }
|
|
32
|
-
*/
|
|
33
|
-
isCacheValid?: (cached: {
|
|
34
|
-
translations: TranslationMap;
|
|
35
|
-
expiresAt: number;
|
|
36
|
-
}, language: string) => boolean;
|
|
37
|
-
/**
|
|
38
|
-
* 🔍 Custom language detection function
|
|
39
|
-
* @default Checks query param → cookie → Accept-Language header → default
|
|
40
|
-
* @example
|
|
41
|
-
* detectLanguage: (ctx) => ctx.cookies.get('user_lang') || 'en'
|
|
42
|
-
*/
|
|
43
|
-
detectLanguage?: (ctx: Context) => string;
|
|
44
|
-
/**
|
|
45
|
-
* 🏠 Default fallback language
|
|
46
|
-
* @default "en"
|
|
47
|
-
*/
|
|
48
|
-
defaultLanguage?: string;
|
|
49
|
-
/**
|
|
50
|
-
* 🔀 Language fallback chain (most specific to least)
|
|
51
|
-
* @example ["fr-CA", "fr", "en"] // Try Canadian French → French → English
|
|
52
|
-
*/
|
|
53
|
-
fallbackChain?: string[];
|
|
54
|
-
/**
|
|
55
|
-
* 🗝️ Context property name for translation function
|
|
56
|
-
* @default "t"
|
|
57
|
-
*/
|
|
58
|
-
translationFunctionKey?: string;
|
|
59
|
-
/**
|
|
60
|
-
* 💬 Message formatting function
|
|
61
|
-
* @default Basic template replacement ({{key}})
|
|
62
|
-
* @example
|
|
63
|
-
* formatMessage: (msg, vars) => {
|
|
64
|
-
* return msg.replace(/\{(\w+)\}/g, (_, k) => vars[k]);
|
|
65
|
-
* }
|
|
66
|
-
*/
|
|
67
|
-
formatMessage?: (message: string, options?: Record<string, any>) => string;
|
|
68
|
-
/**
|
|
69
|
-
* 🗃️ Cache loaded translations
|
|
70
|
-
* @default true
|
|
71
|
-
*/
|
|
72
|
-
cacheTranslations?: boolean;
|
|
73
|
-
};
|
|
74
|
-
/**
|
|
75
|
-
* 🌍 Advanced i18n middleware with dynamic loading and fallback support
|
|
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
|
-
declare const i18n: (options: I18nOptions) => Middleware;
|
|
102
|
-
export { i18n, i18n as default };
|
package/middleware/i18n.js
DELETED
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
import { GlobalConfig } from "../core/config.js";
|
|
2
|
-
import { getCookie } from "../utils/cookie.js";
|
|
3
|
-
const i18n = (options) => {
|
|
4
|
-
const { loadTranslations, defaultCacheDuration = 3600000, isCacheValid = (cached) => cached.expiresAt > Date.now(), detectLanguage = (ctx) => {
|
|
5
|
-
if (ctx.req.query.lang) {
|
|
6
|
-
return ctx.req.query.lang;
|
|
7
|
-
}
|
|
8
|
-
else if (getCookie(ctx, "lang")) {
|
|
9
|
-
return ctx.cookies?.["lang"];
|
|
10
|
-
}
|
|
11
|
-
else if (ctx.req.header("accept-language")) {
|
|
12
|
-
const lang = ctx.req.header("accept-language")?.split(",")[0];
|
|
13
|
-
if (lang)
|
|
14
|
-
return lang;
|
|
15
|
-
}
|
|
16
|
-
else if (options.defaultLanguage) {
|
|
17
|
-
return options.defaultLanguage;
|
|
18
|
-
}
|
|
19
|
-
return "en";
|
|
20
|
-
}, defaultLanguage = "en", fallbackChain = [], translationFunctionKey = "t", formatMessage = (message, options = {}) => {
|
|
21
|
-
return Object.entries(options).reduce((msg, [key, value]) => msg.replace(new RegExp(`{{${key}}}`, "g"), String(value)), message);
|
|
22
|
-
}, cacheTranslations = true, } = options;
|
|
23
|
-
const translationCache = {};
|
|
24
|
-
return async function i18n(ctx, next) {
|
|
25
|
-
try {
|
|
26
|
-
const detectedLanguage = detectLanguage(ctx);
|
|
27
|
-
const languageChain = [
|
|
28
|
-
detectedLanguage,
|
|
29
|
-
...fallbackChain,
|
|
30
|
-
defaultLanguage,
|
|
31
|
-
].filter(Boolean);
|
|
32
|
-
let translations = null;
|
|
33
|
-
let selectedLanguage = defaultLanguage;
|
|
34
|
-
for (const lang of languageChain) {
|
|
35
|
-
const normalizedLang = lang.split("-")[0].toLowerCase();
|
|
36
|
-
if (cacheTranslations && translationCache[normalizedLang]) {
|
|
37
|
-
const cached = translationCache[normalizedLang];
|
|
38
|
-
if (isCacheValid(cached, normalizedLang)) {
|
|
39
|
-
translations = cached.translations;
|
|
40
|
-
selectedLanguage = lang;
|
|
41
|
-
break;
|
|
42
|
-
}
|
|
43
|
-
delete translationCache[normalizedLang];
|
|
44
|
-
}
|
|
45
|
-
try {
|
|
46
|
-
const { translations: loadedTranslations, expiresAt } = await loadTranslations(normalizedLang);
|
|
47
|
-
let expirationTime = Date.now() + defaultCacheDuration;
|
|
48
|
-
if (expiresAt instanceof Date) {
|
|
49
|
-
expirationTime = expiresAt.getTime();
|
|
50
|
-
}
|
|
51
|
-
else if (typeof expiresAt === "number") {
|
|
52
|
-
expirationTime = expiresAt;
|
|
53
|
-
}
|
|
54
|
-
translations = loadedTranslations;
|
|
55
|
-
selectedLanguage = lang;
|
|
56
|
-
if (cacheTranslations) {
|
|
57
|
-
translationCache[normalizedLang] = {
|
|
58
|
-
translations,
|
|
59
|
-
expiresAt: expirationTime,
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
break;
|
|
63
|
-
}
|
|
64
|
-
catch (error) {
|
|
65
|
-
GlobalConfig.debugging.warn(`Translation load failed for ${lang}:`, error);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
if (!translations) {
|
|
69
|
-
throw new Error("No translations available");
|
|
70
|
-
}
|
|
71
|
-
ctx[translationFunctionKey] = (key, options) => {
|
|
72
|
-
const value = key.split(".").reduce((acc, k) => {
|
|
73
|
-
return acc && typeof acc === "object" ? acc[k] : undefined;
|
|
74
|
-
}, translations);
|
|
75
|
-
const message = typeof value === "string" ? value : key;
|
|
76
|
-
return formatMessage(message, options);
|
|
77
|
-
};
|
|
78
|
-
ctx.language = selectedLanguage;
|
|
79
|
-
ctx.languageChain = languageChain;
|
|
80
|
-
return await next();
|
|
81
|
-
}
|
|
82
|
-
catch (error) {
|
|
83
|
-
GlobalConfig.debugging.error("i18n processing error:", error);
|
|
84
|
-
ctx.setStatus = 500;
|
|
85
|
-
throw error;
|
|
86
|
-
}
|
|
87
|
-
};
|
|
88
|
-
};
|
|
89
|
-
export { i18n, i18n as default };
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
import { Context, Middleware } from "../index.js";
|
|
2
|
-
export type LazyModuleLoader<T> = () => Promise<T>;
|
|
3
|
-
export interface CacheItem<T = any> {
|
|
4
|
-
module: T;
|
|
5
|
-
expiresAt: number;
|
|
6
|
-
}
|
|
7
|
-
export interface LazyLoadOptions<T> {
|
|
8
|
-
/**
|
|
9
|
-
* 🗺️ Key to identify the module to load. This can be a function that extracts the module name from the request context.
|
|
10
|
-
* @default (ctx) => ctx.req.params[queryKeyModule] || ctx.req.query[queryKeyModule]
|
|
11
|
-
*/
|
|
12
|
-
moduleKey?: (ctx: Context) => string;
|
|
13
|
-
/**
|
|
14
|
-
* 🛠️ Function that returns a loader function for the specified module, used to dynamically load the module.
|
|
15
|
-
* If this function returns null, it indicates no loader is available for the module.
|
|
16
|
-
*/
|
|
17
|
-
getModuleLoader: (ctx: Context) => Promise<LazyModuleLoader<T> | null> | null | LazyModuleLoader<T>;
|
|
18
|
-
/**
|
|
19
|
-
* 🔍 Query parameter name to select which module to load (e.g., "module").
|
|
20
|
-
* @default "module"
|
|
21
|
-
*/
|
|
22
|
-
queryKeyModule?: string;
|
|
23
|
-
/**
|
|
24
|
-
* 📦 Key to attach the loaded module to the context object.
|
|
25
|
-
* @default "module"
|
|
26
|
-
*/
|
|
27
|
-
moduleContextKey?: string;
|
|
28
|
-
/**
|
|
29
|
-
* 🔄 Enable caching of loaded modules to avoid re-loading them repeatedly.
|
|
30
|
-
* @default true
|
|
31
|
-
*/
|
|
32
|
-
enableCache?: boolean;
|
|
33
|
-
/**
|
|
34
|
-
* 🔄 Custom cache storage implementation (e.g., using `Map`, `Redis`, etc.).
|
|
35
|
-
* By default, it uses a `Map<string, CacheItem<T>>`.
|
|
36
|
-
*/
|
|
37
|
-
cacheStorage?: {
|
|
38
|
-
get: (key: string) => CacheItem<T> | undefined;
|
|
39
|
-
set: (key: string, value: CacheItem<T>) => void;
|
|
40
|
-
delete: (key: string) => void;
|
|
41
|
-
};
|
|
42
|
-
/**
|
|
43
|
-
* ⏳ Cache Time-To-Live (TTL) in milliseconds. This determines how long cached modules are valid.
|
|
44
|
-
* @default 3600000 (1 hour)
|
|
45
|
-
*/
|
|
46
|
-
cacheTTL?: number;
|
|
47
|
-
/**
|
|
48
|
-
* 🌟 Lifecycle hooks for the module loading process.
|
|
49
|
-
* These hooks allow for custom actions at various stages of loading the module (e.g., when a module is loaded or when cache is hit).
|
|
50
|
-
*/
|
|
51
|
-
lifecycleHooks?: {
|
|
52
|
-
onLoad?: (moduleName: string, ctx: Context) => void;
|
|
53
|
-
onError?: (moduleName: string, error: Error, ctx: Context) => void;
|
|
54
|
-
onComplete?: (moduleName: string, module: T, ctx: Context) => void;
|
|
55
|
-
onCacheHit?: (moduleName: string, module: T, ctx: Context) => void;
|
|
56
|
-
onCacheSet?: (moduleName: string, module: T, ctx: Context) => void;
|
|
57
|
-
};
|
|
58
|
-
/**
|
|
59
|
-
* 🛡️ Module validation function to ensure the module meets specific criteria before use.
|
|
60
|
-
* This function will be called after the module is loaded to verify its structure or behavior.
|
|
61
|
-
* If validation fails, an error is thrown.
|
|
62
|
-
*/
|
|
63
|
-
validateModule?: (module: T) => boolean;
|
|
64
|
-
}
|
|
65
|
-
/**
|
|
66
|
-
* Middleware for handling lazy loading of modules. This middleware allows dynamically loading modules based on route or query parameters.
|
|
67
|
-
* It supports caching, lifecycle hooks, and module validation.
|
|
68
|
-
*
|
|
69
|
-
* @param options - Custom options for lazy loading, including caching, hooks, and module validation.
|
|
70
|
-
* @returns A middleware function to use in your application.
|
|
71
|
-
*/
|
|
72
|
-
declare const lazyLoader: <T = any>(options: LazyLoadOptions<T>) => Middleware;
|
|
73
|
-
export { lazyLoader, lazyLoader as default };
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import { GlobalConfig } from "../core/config.js";
|
|
2
|
-
const lazyLoader = (options) => {
|
|
3
|
-
const { moduleKey = (ctx) => ctx.req.params[queryKeyModule] || ctx.req.query[queryKeyModule], getModuleLoader, queryKeyModule = "module", moduleContextKey = "module", cacheTTL = 3600000, enableCache = true, cacheStorage, lifecycleHooks = {}, validateModule, } = options;
|
|
4
|
-
let storage = cacheStorage;
|
|
5
|
-
if (enableCache && !cacheStorage) {
|
|
6
|
-
storage = new Map();
|
|
7
|
-
}
|
|
8
|
-
return async function lazyLoader(ctx, next) {
|
|
9
|
-
let moduleName = moduleKey(ctx) ||
|
|
10
|
-
ctx.req.params[queryKeyModule] ||
|
|
11
|
-
ctx.req.query[queryKeyModule];
|
|
12
|
-
if (!moduleName) {
|
|
13
|
-
GlobalConfig.debugging.warn("No module specified for lazy loading.");
|
|
14
|
-
return await next();
|
|
15
|
-
}
|
|
16
|
-
try {
|
|
17
|
-
if (enableCache) {
|
|
18
|
-
const cached = storage.get(moduleName);
|
|
19
|
-
if (cached) {
|
|
20
|
-
if (cached.expiresAt < Date.now()) {
|
|
21
|
-
storage.delete(moduleName);
|
|
22
|
-
}
|
|
23
|
-
else {
|
|
24
|
-
GlobalConfig.debugging.info(`Using cached module: ${moduleName}`);
|
|
25
|
-
ctx[moduleContextKey] = cached?.module;
|
|
26
|
-
lifecycleHooks.onCacheHit?.(moduleName, ctx[moduleContextKey], ctx);
|
|
27
|
-
lifecycleHooks.onComplete?.(moduleName, ctx[moduleContextKey], ctx);
|
|
28
|
-
return await next();
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
if (!getModuleLoader) {
|
|
33
|
-
throw new Error(`No module loader found for module: ${moduleName}`);
|
|
34
|
-
}
|
|
35
|
-
const moduleLoader = await getModuleLoader(ctx);
|
|
36
|
-
if (!moduleLoader) {
|
|
37
|
-
throw new Error(`No loader found for module: ${moduleName}`);
|
|
38
|
-
}
|
|
39
|
-
lifecycleHooks.onLoad?.(moduleName, ctx);
|
|
40
|
-
const module = await moduleLoader();
|
|
41
|
-
if (validateModule && !validateModule(module)) {
|
|
42
|
-
throw new Error(`Module validation failed for: ${moduleName}`);
|
|
43
|
-
}
|
|
44
|
-
if (enableCache) {
|
|
45
|
-
storage.set(moduleName, {
|
|
46
|
-
module,
|
|
47
|
-
expiresAt: Date.now() + cacheTTL,
|
|
48
|
-
});
|
|
49
|
-
lifecycleHooks.onCacheSet?.(moduleName, module, ctx);
|
|
50
|
-
}
|
|
51
|
-
ctx[moduleContextKey] = module;
|
|
52
|
-
if (module.init && typeof module.init === "function") {
|
|
53
|
-
const initResult = await module.init(ctx);
|
|
54
|
-
if (initResult) {
|
|
55
|
-
return initResult;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
lifecycleHooks.onComplete?.(moduleName, module, ctx);
|
|
59
|
-
GlobalConfig.debugging.success(`Successfully loaded module: ${moduleName}`);
|
|
60
|
-
}
|
|
61
|
-
catch (error) {
|
|
62
|
-
GlobalConfig.debugging.error(`Error loading module: ${moduleName}`, error);
|
|
63
|
-
lifecycleHooks.onError?.(moduleName, error, ctx);
|
|
64
|
-
ctx.setStatus = 500;
|
|
65
|
-
throw error;
|
|
66
|
-
}
|
|
67
|
-
return await next();
|
|
68
|
-
};
|
|
69
|
-
};
|
|
70
|
-
export { lazyLoader, lazyLoader as default };
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { Context } from "../core/context.js";
|
|
2
|
-
import { HttpBaseResponse, Middleware } from "../types/index.js";
|
|
3
|
-
export type TimeoutOptions = {
|
|
4
|
-
/**
|
|
5
|
-
* ⏳ Function to dynamically determine the timeout duration (in milliseconds).
|
|
6
|
-
*/
|
|
7
|
-
getTimeout: (ctx: Context) => number;
|
|
8
|
-
/**
|
|
9
|
-
* 🚫 Custom function to handle timeout errors.
|
|
10
|
-
*/
|
|
11
|
-
onTimeout?: (ctx: Context, error: Error) => HttpBaseResponse;
|
|
12
|
-
/**
|
|
13
|
-
* 📝 Logging function for timeout events.
|
|
14
|
-
*/
|
|
15
|
-
logTimeoutEvent?: (ctx: Context, error: Error) => void;
|
|
16
|
-
/**
|
|
17
|
-
* 🛠️ Custom function to clean up resources after a timeout.
|
|
18
|
-
*/
|
|
19
|
-
cleanup?: (ctx: Context) => void;
|
|
20
|
-
};
|
|
21
|
-
/**
|
|
22
|
-
* Middleware to enforce fully dynamic request timeouts.
|
|
23
|
-
* @param options - Custom options for dynamic timeout handling.
|
|
24
|
-
*/
|
|
25
|
-
declare const requestTimeout: (options: TimeoutOptions) => Middleware;
|
|
26
|
-
export { requestTimeout, requestTimeout as default };
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import { GlobalConfig } from "../core/config.js";
|
|
2
|
-
const requestTimeout = (options) => {
|
|
3
|
-
const { getTimeout, onTimeout = (ctx) => {
|
|
4
|
-
ctx.setStatus = 504;
|
|
5
|
-
ctx.body = { error: "Request timed out." };
|
|
6
|
-
}, logTimeoutEvent = (ctx, error) => {
|
|
7
|
-
GlobalConfig.debugging.warn(`[TIMEOUT] ${error.message}: ${ctx.method} ${ctx.pathname}`);
|
|
8
|
-
}, cleanup = () => {
|
|
9
|
-
}, } = options;
|
|
10
|
-
return async function requestTimeout(ctx, next) {
|
|
11
|
-
let timeoutId = null;
|
|
12
|
-
try {
|
|
13
|
-
const timeout = getTimeout(ctx);
|
|
14
|
-
const timeoutPromise = new Promise((_, reject) => {
|
|
15
|
-
timeoutId = setTimeout(() => {
|
|
16
|
-
const timeoutError = new Error("Request timed out.");
|
|
17
|
-
logTimeoutEvent(ctx, timeoutError);
|
|
18
|
-
reject(timeoutError);
|
|
19
|
-
}, timeout);
|
|
20
|
-
});
|
|
21
|
-
return await Promise.race([next(), timeoutPromise]);
|
|
22
|
-
}
|
|
23
|
-
catch (error) {
|
|
24
|
-
if (error.message === "Request timed out.") {
|
|
25
|
-
onTimeout(ctx, error);
|
|
26
|
-
}
|
|
27
|
-
else {
|
|
28
|
-
throw error;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
finally {
|
|
32
|
-
if (timeoutId) {
|
|
33
|
-
clearTimeout(timeoutId);
|
|
34
|
-
}
|
|
35
|
-
cleanup(ctx);
|
|
36
|
-
}
|
|
37
|
-
};
|
|
38
|
-
};
|
|
39
|
-
export { requestTimeout, requestTimeout as default };
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import { Context, Middleware } from "../index.js";
|
|
2
|
-
export type DynamicHeaderValue = string | ((ctx: Context) => string | undefined);
|
|
3
|
-
export type SecurityHeaderOptions = {
|
|
4
|
-
/**
|
|
5
|
-
* 🔵 Content Security Policy header value
|
|
6
|
-
* @default "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline';"
|
|
7
|
-
* @example
|
|
8
|
-
* contentSecurityPolicy: "default-src 'none'; script-src 'self' https://trusted.cdn.com"
|
|
9
|
-
* contentSecurityPolicy: (ctx) => ctx.isAdmin ? "default-src 'self'" : "default-src 'none'"
|
|
10
|
-
*/
|
|
11
|
-
contentSecurityPolicy?: DynamicHeaderValue;
|
|
12
|
-
/**
|
|
13
|
-
* 🟢 Whether to set X-Frame-Options header (clickjacking protection)
|
|
14
|
-
* @default true
|
|
15
|
-
* @example
|
|
16
|
-
* frameGuard: true // Always enable
|
|
17
|
-
* frameGuard: (ctx) => !ctx.pathname.startsWith('/embed/') // Disable for embed routes
|
|
18
|
-
*/
|
|
19
|
-
frameGuard?: boolean | ((ctx: Context) => boolean);
|
|
20
|
-
/**
|
|
21
|
-
* 🟣 HTTP Strict Transport Security (HSTS) header
|
|
22
|
-
* @default true
|
|
23
|
-
* @example
|
|
24
|
-
* hsts: true // Enable with 2 year duration
|
|
25
|
-
* hsts: (ctx) => ctx.secure // Enable only for HTTPS
|
|
26
|
-
*/
|
|
27
|
-
hsts?: boolean | ((ctx: Context) => boolean);
|
|
28
|
-
/**
|
|
29
|
-
* 🟠 XSS Protection header
|
|
30
|
-
* @default true
|
|
31
|
-
* @example
|
|
32
|
-
* xssProtection: false // Disable legacy XSS filter
|
|
33
|
-
*/
|
|
34
|
-
xssProtection?: boolean | ((ctx: Context) => boolean);
|
|
35
|
-
/**
|
|
36
|
-
* 🟡 X-Content-Type-Options header (MIME sniffing prevention)
|
|
37
|
-
* @default true
|
|
38
|
-
* @example
|
|
39
|
-
* noSniff: false // Only disable if you have specific needs
|
|
40
|
-
*/
|
|
41
|
-
noSniff?: boolean | ((ctx: Context) => boolean);
|
|
42
|
-
/**
|
|
43
|
-
* 🔴 Referrer Policy header
|
|
44
|
-
* @default "no-referrer"
|
|
45
|
-
* @example
|
|
46
|
-
* referrerPolicy: "strict-origin-when-cross-origin"
|
|
47
|
-
*/
|
|
48
|
-
referrerPolicy?: DynamicHeaderValue;
|
|
49
|
-
/**
|
|
50
|
-
* 🟤 Permissions Policy header (formerly Feature Policy)
|
|
51
|
-
* @default "geolocation=(), microphone=(), camera=()"
|
|
52
|
-
* @example
|
|
53
|
-
* permissionsPolicy: "geolocation=(self 'https://example.com')"
|
|
54
|
-
*/
|
|
55
|
-
permissionsPolicy?: DynamicHeaderValue;
|
|
56
|
-
};
|
|
57
|
-
/**
|
|
58
|
-
* 🛡️ Comprehensive security headers middleware
|
|
59
|
-
*
|
|
60
|
-
* Sets multiple security-related HTTP headers with dynamic configuration.
|
|
61
|
-
* All headers can be configured per-request using functions.
|
|
62
|
-
*
|
|
63
|
-
* @param {SecurityHeaderOptions} [options={}] - Configuration options
|
|
64
|
-
* @returns {Middleware} Middleware function
|
|
65
|
-
*
|
|
66
|
-
* @example
|
|
67
|
-
* // Basic usage with defaults
|
|
68
|
-
* app.use(secureHeaders());
|
|
69
|
-
*
|
|
70
|
-
* // Custom configuration
|
|
71
|
-
* app.use(secureHeaders({
|
|
72
|
-
* contentSecurityPolicy: "default-src 'self'",
|
|
73
|
-
* frameGuard: (ctx) => !ctx.isEmbedded,
|
|
74
|
-
* referrerPolicy: "strict-origin-when-cross-origin"
|
|
75
|
-
* }));
|
|
76
|
-
*/
|
|
77
|
-
declare const secureHeaders: (options?: SecurityHeaderOptions) => Middleware<any>;
|
|
78
|
-
export { secureHeaders, secureHeaders as default };
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
const secureHeaders = (options = {}) => {
|
|
2
|
-
return async function secureHeaders(ctx, next) {
|
|
3
|
-
const resolveValue = (value) => {
|
|
4
|
-
return typeof value === "function" ? value(ctx) : value;
|
|
5
|
-
};
|
|
6
|
-
const contentSecurityPolicy = resolveValue(options.contentSecurityPolicy) ||
|
|
7
|
-
"default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline';";
|
|
8
|
-
const frameGuard = resolveValue(options.frameGuard) ?? true;
|
|
9
|
-
const hsts = resolveValue(options.hsts) ?? true;
|
|
10
|
-
const xssProtection = resolveValue(options.xssProtection) ?? true;
|
|
11
|
-
const noSniff = resolveValue(options.noSniff) ?? true;
|
|
12
|
-
const referrerPolicy = resolveValue(options.referrerPolicy) || "no-referrer";
|
|
13
|
-
const permissionsPolicy = resolveValue(options.permissionsPolicy) ||
|
|
14
|
-
"geolocation=(), microphone=(), camera=()";
|
|
15
|
-
if (contentSecurityPolicy) {
|
|
16
|
-
ctx.setHeader("Content-Security-Policy", contentSecurityPolicy);
|
|
17
|
-
}
|
|
18
|
-
if (frameGuard) {
|
|
19
|
-
ctx.setHeader("X-Frame-Options", "DENY");
|
|
20
|
-
}
|
|
21
|
-
if (hsts) {
|
|
22
|
-
ctx.setHeader("Strict-Transport-Security", "max-age=63072000; includeSubDomains");
|
|
23
|
-
}
|
|
24
|
-
if (xssProtection) {
|
|
25
|
-
ctx.setHeader("X-XSS-Protection", "1; mode=block");
|
|
26
|
-
}
|
|
27
|
-
if (noSniff) {
|
|
28
|
-
ctx.setHeader("X-Content-Type-Options", "nosniff");
|
|
29
|
-
}
|
|
30
|
-
if (referrerPolicy) {
|
|
31
|
-
ctx.setHeader("Referrer-Policy", referrerPolicy);
|
|
32
|
-
}
|
|
33
|
-
if (permissionsPolicy) {
|
|
34
|
-
ctx.setHeader("Permissions-Policy", permissionsPolicy);
|
|
35
|
-
}
|
|
36
|
-
return await next();
|
|
37
|
-
};
|
|
38
|
-
};
|
|
39
|
-
export { secureHeaders, secureHeaders as default };
|