Mirror: A Node.js fetch shim using built-in Request, Response, and Headers (but without native fetch)
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

fix: Fix `init` properties not taking precedence over `Request` passed as input (#37)

authored by

Phil Pluckthun and committed by
GitHub
3289e23f 54540801

+38 -22
+5
.changeset/fresh-bugs-sort.md
··· 1 + --- 2 + 'fetch-nodeshim': patch 3 + --- 4 + 5 + Fix `fetch(new Request(...), init)` case, where `init` should take precedence over the request
+33 -22
src/fetch.ts
··· 71 71 72 72 /** Normalize methods and disallow special methods */ 73 73 const methodToHttpOption = (method: string | undefined): string => { 74 - switch (method) { 74 + const normalized = method?.toUpperCase(); 75 + switch (normalized) { 75 76 case 'CONNECT': 76 77 case 'TRACE': 77 78 case 'TRACK': 78 79 throw new TypeError( 79 80 `Failed to construct 'Request': '${method}' HTTP method is unsupported.` 80 81 ); 82 + case 'DELETE': 83 + case 'GET': 84 + case 'HEAD': 85 + case 'OPTIONS': 86 + case 'POST': 87 + case 'PUT': 88 + return normalized; 81 89 default: 82 - return method ? method.toUpperCase() : 'GET'; 90 + return method ?? 'GET'; 83 91 } 84 92 }; 85 93 ··· 138 146 139 147 async function _fetch( 140 148 input: string | URL | Request, 141 - requestInit?: RequestInit 149 + init?: RequestInit 142 150 ): Promise<Response> { 143 151 const initFromRequest = isRequest(input); 144 152 const initUrl = initFromRequest ? input.url : input; 145 - const initBody = initFromRequest ? input.body : requestInit?.body || null; 146 - const signal = initFromRequest 147 - ? input.signal 148 - : requestInit?.signal || undefined; 153 + const initBody = init?.body ?? (initFromRequest ? input.body : null); 154 + const signal = init?.signal ?? (initFromRequest ? input.signal : undefined); 149 155 const redirect = toRedirectOption( 150 - initFromRequest ? input.redirect : requestInit?.redirect 156 + init?.redirect ?? (initFromRequest ? input.redirect : undefined) 151 157 ); 152 158 153 159 let requestUrl = new URL(initUrl); ··· 155 161 let redirects = 0; 156 162 157 163 const requestHeaders = new Headers( 158 - requestInit?.headers || (initFromRequest ? input.headers : undefined) 164 + init?.headers ?? (initFromRequest ? input.headers : undefined) 159 165 ); 160 166 const requestOptions = { 161 167 ...urlToHttpOptions(requestUrl), 162 168 timeout: 5_000, 163 - method: methodToHttpOption( 164 - initFromRequest ? input.method : requestInit?.method 165 - ), 169 + method: methodToHttpOption(initFromRequest ? input.method : init?.method), 166 170 signal, 167 171 } satisfies http.RequestOptions; 168 172 ··· 209 213 incoming.socket.unref(); 210 214 incoming.on('error', destroy); 211 215 212 - const init = { 216 + const responseInit = { 213 217 status: incoming.statusCode, 214 218 statusText: incoming.statusMessage, 215 219 headers: headersOfRawHeaders(incoming.rawHeaders), 216 220 } satisfies ResponseInit; 217 221 218 - if (isRedirectCode(init.status)) { 219 - const location = init.headers.get('Location'); 222 + if (isRedirectCode(responseInit.status)) { 223 + const location = responseInit.headers.get('Location'); 220 224 const locationURL = 221 225 location != null ? parseURL(location, requestUrl) : null; 222 226 if (redirect === 'error') { ··· 227 231 ); 228 232 return; 229 233 } else if (redirect === 'manual' && location) { 230 - init.headers.set('Location', locationURL?.href ?? location); 234 + responseInit.headers.set('Location', locationURL?.href ?? location); 231 235 } else if (redirect === 'follow') { 232 236 if (locationURL === null) { 233 237 reject( ··· 247 251 } 248 252 249 253 if ( 250 - init.status === 303 || 251 - ((init.status === 301 || init.status === 302) && method === 'POST') 254 + responseInit.status === 303 || 255 + ((responseInit.status === 301 || responseInit.status === 302) && 256 + method === 'POST') 252 257 ) { 253 258 requestBody = extractBody(null); 254 259 requestOptions.method = 'GET'; ··· 272 277 } 273 278 274 279 let body: Readable | null = incoming; 275 - const encoding = init.headers.get('Content-Encoding')?.toLowerCase(); 276 - if (method === 'HEAD' || init.status === 204 || init.status === 304) { 280 + const encoding = responseInit.headers 281 + .get('Content-Encoding') 282 + ?.toLowerCase(); 283 + if ( 284 + method === 'HEAD' || 285 + responseInit.status === 204 || 286 + responseInit.status === 304 287 + ) { 277 288 body = null; 278 289 } else if (encoding != null) { 279 - init.headers.set('Content-Encoding', encoding); 290 + responseInit.headers.set('Content-Encoding', encoding); 280 291 body = pipeline(body, createContentDecoder(encoding), destroy); 281 292 outgoing.on('error', destroy); 282 293 } ··· 288 299 } 289 300 290 301 resolve( 291 - createResponse(body, init, { 302 + createResponse(body, responseInit, { 292 303 type: 'default', 293 304 url: requestUrl.toString(), 294 305 redirected: redirects > 0,