UNPKG

nextjs-interceptor

Version:

A powerful and flexible interceptor middleware for Next.js applications, providing seamless request/response manipulation capabilities

130 lines (129 loc) 4.69 kB
export class InterceptorRegistry { constructor() { this.interceptors = new Map(); } use(config, handler) { if (this.interceptors.has(config.id)) { console.warn(`拦截器 ${config.id} 已存在,将被覆盖`); } this.interceptors.set(config.id, { config: { ...config, priority: config.priority || 0, }, handler, }); return this; } getPatterns() { return Array.from(this.interceptors.values()).flatMap((i) => Array.isArray(i.config.pattern) ? i.config.pattern : [i.config.pattern]); } /** * 获取所有拦截器的排除模式 * @returns 排除模式数组 */ getExcludePatterns() { return Array.from(this.interceptors.values()) .filter((i) => i.config.exclude) .flatMap((i) => Array.isArray(i.config.exclude) ? i.config.exclude : [i.config.exclude]); } getMatchers() { return this.getPatterns(); } async handle(request, event) { const sortedInterceptors = this.getSortedInterceptors(); // 依次执行每个拦截器 for (const interceptor of sortedInterceptors) { // 如果拦截器不匹配当前请求,跳过 if (!this.matches(request, interceptor.config)) { continue; } // 执行拦截器 const response = await interceptor.handler(request, event); // 如果拦截器返回了 Response,直接返回结果 // 如果返回 null,继续执行下一个拦截器 if (response !== null) { return response; } } // 所有拦截器都执行完了,返回 null return; } getSortedInterceptors() { return Array.from(this.interceptors.values()) .sort((a, b) => (a.config.priority || 0) - (b.config.priority || 0)); } matchesPattern(req, pattern) { const patterns = Array.isArray(pattern) ? pattern : [pattern]; const pathname = req.nextUrl.pathname; return patterns.some((p) => { if (typeof p === "string") { return new RegExp(p).test(pathname); } return p.test(pathname); }); } /** * 检查请求路径是否匹配排除模式 * @param req - Next.js 请求对象 * @param exclude - 排除模式:字符串、正则表达式或它们的数组 * @returns 如果匹配任意一个排除模式则返回 true */ matchesExcludePattern(req, exclude) { if (!exclude) return false; const excludePatterns = Array.isArray(exclude) ? exclude : [exclude]; const pathname = req.nextUrl.pathname; return excludePatterns.some((p) => { if (typeof p === "string") { return new RegExp(p).test(pathname); } return p.test(pathname); }); } matchesConditions(req, conditions) { if (!conditions) return true; const { headers, query, cookies } = conditions; if (headers && !this.matchesRecord(Object.fromEntries(req.headers.entries()), headers)) { return false; } if (query && !this.matchesRecord(Object.fromEntries(req.nextUrl.searchParams.entries()), query)) { return false; } const _cookies = {}; req.cookies.getAll().forEach((cookie) => { _cookies[cookie.name] = cookie.value; }); if (cookies && !this.matchesRecord(_cookies, cookies)) { return false; } return true; } matchesRecord(actual, expected) { return Object.entries(expected).every(([key, value]) => { const actualValue = actual[key]; if (!actualValue) return false; if (value instanceof RegExp) { return value.test(actualValue); } return value === actualValue; }); } matches(req, config) { return (this.matchesPattern(req, config.pattern) && !this.matchesExcludePattern(req, config.exclude) && this.matchesConditions(req, config.conditions)); } } const singleton = () => { return new InterceptorRegistry(); }; const interceptorRegistry = globalThis.interceptorRegistry ?? singleton(); if (process.env.NODE_ENV !== "production") globalThis.interceptorRegistry = interceptorRegistry; export { interceptorRegistry }; export const interceptorMiddleware = interceptorRegistry.handle.bind(interceptorRegistry);