diff --git a/lib/utils/ofetch.ts b/lib/utils/ofetch.ts index 2cddd52e3cd268..2a66a6f8cddca6 100644 --- a/lib/utils/ofetch.ts +++ b/lib/utils/ofetch.ts @@ -1,6 +1,9 @@ import { createFetch } from 'ofetch'; import { config } from '@/config'; import logger from '@/utils/logger'; +import { register } from 'node-network-devtools'; + +process.env.NODE_ENV === 'dev' && register(); const rofetch = createFetch().create({ retryStatusCodes: [400, 408, 409, 425, 429, 500, 502, 503, 504], diff --git a/lib/utils/request-rewriter/fetch.test.ts b/lib/utils/request-rewriter/fetch.test.ts new file mode 100644 index 00000000000000..ccb4e448c0d026 --- /dev/null +++ b/lib/utils/request-rewriter/fetch.test.ts @@ -0,0 +1,93 @@ +import { getCurrentCell, setCurrentCell } from 'node-network-devtools'; +import { useCustomHeader } from './fetch'; +import { describe, beforeEach, afterEach, test, expect } from 'vitest'; + +const getInitRequest = () => + ({ + requestHeaders: {} as Record, + id: '', + loadCallFrames: () => {}, + cookies: '', + requestData: '', + responseData: '', + responseHeaders: {}, + responseInfo: {}, + }) satisfies NonNullable>['request']; + +enum Env { + dev = 'dev', + production = 'production', + test = 'test', +} + +describe('useCustomHeader', () => { + let originalEnv: string; + + beforeEach(() => { + originalEnv = process.env.NODE_ENV || Env.test; + }); + + afterEach(() => { + process.env.NODE_ENV = originalEnv; + }); + + test('should register request with custom headers in dev environment', () => { + process.env.NODE_ENV = Env.dev; + + const headers = new Headers(); + const headerText = 'authorization'; + const headerValue = 'Bearer token'; + headers.set(headerText, headerValue); + + const req = getInitRequest(); + setCurrentCell({ + request: req, + pipes: [], + isAborted: false, + }); + + useCustomHeader(headers); + + const cell = getCurrentCell(); + expect(cell).toBeDefined(); + + let request = req; + if (cell) { + for (const { pipe } of cell.pipes) { + request = pipe(request); + } + } + + expect(request.requestHeaders[headerText]).toEqual(headerValue); + }); + + test('should not register request in non-dev environment', () => { + process.env.NODE_ENV = Env.production; + + const headers = new Headers(); + const headerText = 'content-type'; + const headerValue = 'application/json'; + + headers.set(headerText, headerValue); + const req = getInitRequest(); + + setCurrentCell({ + request: req, + pipes: [], + isAborted: false, + }); + useCustomHeader(headers); + + const cell = getCurrentCell(); + expect(cell).toBeDefined(); + + let request = req; + if (cell) { + for (const { pipe } of cell.pipes) { + request = pipe(request); + } + } + + expect(req.requestHeaders[headerText]).toBeUndefined(); + }); +}); diff --git a/lib/utils/request-rewriter/fetch.ts b/lib/utils/request-rewriter/fetch.ts index 136e93bc86753c..9985701100d0d2 100644 --- a/lib/utils/request-rewriter/fetch.ts +++ b/lib/utils/request-rewriter/fetch.ts @@ -3,6 +3,7 @@ import { config } from '@/config'; import undici, { Request, RequestInfo, RequestInit } from 'undici'; import proxy from '@/utils/proxy'; import { RateLimiterMemory, RateLimiterQueue } from 'rate-limiter-flexible'; +import { useRegisterRequest } from 'node-network-devtools'; const limiter = new RateLimiterMemory({ points: 10, @@ -14,6 +15,16 @@ const limiterQueue = new RateLimiterQueue(limiter, { maxQueueSize: 5000, }); +export const useCustomHeader = (headers: Headers) => { + process.env.NODE_ENV === 'dev' && + useRegisterRequest((req) => { + for (const [key, value] of headers.entries()) { + req.requestHeaders[key] = value; + } + return req; + }); +}; + const wrappedFetch: typeof undici.fetch = async (input: RequestInfo, init?: RequestInit) => { const request = new Request(input, init); const options: RequestInit = {}; @@ -46,6 +57,8 @@ const wrappedFetch: typeof undici.fetch = async (input: RequestInfo, init?: Requ request.headers.delete('x-prefer-proxy'); } + useCustomHeader(request.headers); + // proxy if (!init?.dispatcher && proxy.dispatcher && (proxy.proxyObj.strategy !== 'on_retry' || isRetry)) { const proxyRegex = new RegExp(proxy.proxyObj.url_regex); diff --git a/package.json b/package.json index 9f332055fa8c24..4253290716ed55 100644 --- a/package.json +++ b/package.json @@ -186,6 +186,7 @@ "lint-staged": "15.2.10", "mockdate": "3.0.5", "msw": "2.4.3", + "node-network-devtools": "1.0.22", "prettier": "3.3.3", "remark-parse": "11.0.0", "supertest": "7.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e43948748ce9e7..1109876d96e8aa 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -414,6 +414,9 @@ importers: msw: specifier: 2.4.3 version: 2.4.3(typescript@5.6.3) + node-network-devtools: + specifier: 1.0.22 + version: 1.0.22(bufferutil@4.0.8)(undici@6.20.1)(utf-8-validate@5.0.10) prettier: specifier: 3.3.3 version: 3.3.3 @@ -2723,6 +2726,10 @@ packages: resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} engines: {node: '>= 0.4'} + define-lazy-prop@2.0.0: + resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==} + engines: {node: '>=8'} + degenerator@5.0.1: resolution: {integrity: sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==} engines: {node: '>= 14'} @@ -3666,6 +3673,11 @@ packages: resolution: {integrity: sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==} engines: {node: '>= 0.4'} + is-docker@2.2.1: + resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} + engines: {node: '>=8'} + hasBin: true + is-extendable@0.1.1: resolution: {integrity: sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==} engines: {node: '>=0.10.0'} @@ -3747,6 +3759,10 @@ packages: resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} engines: {node: '>=10'} + is-wsl@2.2.0: + resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} + engines: {node: '>=8'} + isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} @@ -4337,6 +4353,11 @@ packages: resolution: {integrity: sha512-vv8fJuOUCCvSPjDjBLlMqYMHob4aGjkmrkaE42/mZr0VT+ZAU10jRF8oTnX9+pgU9/vYJ8P7YT3Vd6ajkmzSCw==} engines: {node: '>=0.12'} + node-network-devtools@1.0.22: + resolution: {integrity: sha512-frZ+j3UNvtxdo2YNEAZIKxAHcMuiCUkkm6rP3bYSfnBKfAybidNVOLae1dlirv2mv1THfPBKp9+sVq/5mEfOCw==} + peerDependencies: + undici: ^6 + node-releases@2.0.18: resolution: {integrity: sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==} @@ -4429,6 +4450,10 @@ packages: resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} engines: {node: '>=18'} + open@8.4.2: + resolution: {integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==} + engines: {node: '>=12'} + openapi-fetch@0.11.3: resolution: {integrity: sha512-r18fERgpxFrI4pv79ABD1dqFetWz7pTfwRd7jQmRm/lFdCDpWF43kvHUiOqOZu+tWsMydDJMpJN1hlZ9inRvfA==} @@ -8345,6 +8370,8 @@ snapshots: es-errors: 1.3.0 gopd: 1.0.1 + define-lazy-prop@2.0.0: {} + degenerator@5.0.1: dependencies: ast-types: 0.13.4 @@ -9541,6 +9568,8 @@ snapshots: dependencies: hasown: 2.0.2 + is-docker@2.2.1: {} + is-extendable@0.1.1: {} is-extglob@2.1.1: {} @@ -9589,6 +9618,10 @@ snapshots: is-unicode-supported@0.1.0: {} + is-wsl@2.2.0: + dependencies: + is-docker: 2.2.1 + isexe@2.0.0: {} isobject@3.0.1: {} @@ -10263,6 +10296,16 @@ snapshots: dependencies: write-file-atomic: 1.3.4 + node-network-devtools@1.0.22(bufferutil@4.0.8)(undici@6.20.1)(utf-8-validate@5.0.10): + dependencies: + iconv-lite: 0.6.3 + open: 8.4.2 + undici: 6.20.1 + ws: 8.18.0(bufferutil@4.0.8)(utf-8-validate@5.0.10) + transitivePeerDependencies: + - bufferutil + - utf-8-validate + node-releases@2.0.18: {} nodemailer@6.9.13: {} @@ -10354,6 +10397,12 @@ snapshots: dependencies: mimic-function: 5.0.1 + open@8.4.2: + dependencies: + define-lazy-prop: 2.0.0 + is-docker: 2.2.1 + is-wsl: 2.2.0 + openapi-fetch@0.11.3: dependencies: openapi-typescript-helpers: 0.0.13