@whatwg-node/node-fetch
Version:
Fetch API implementation for Node
305 lines (304 loc) • 10.9 kB
JavaScript
import { inspect } from 'node:util';
import { PonyfillIteratorObject } from './IteratorObject.js';
export function isHeadersLike(headers) {
return headers?.get && headers?.forEach;
}
export class PonyfillHeaders {
headersInit;
_map;
objectNormalizedKeysOfHeadersInit = [];
objectOriginalKeysOfHeadersInit = [];
_setCookies;
constructor(headersInit) {
this.headersInit = headersInit;
}
// perf: we don't need to build `this.map` for Requests, as we can access the headers directly
_get(key) {
const normalized = key.toLowerCase();
if (normalized === 'set-cookie' && this._setCookies?.length) {
return this._setCookies.join(', ');
}
// If the map is built, reuse it
if (this._map) {
return this._map.get(normalized) || null;
}
// If the map is not built, try to get the value from the this.headersInit
if (this.headersInit == null) {
return null;
}
if (Array.isArray(this.headersInit)) {
const found = this.headersInit.filter(([headerKey]) => headerKey.toLowerCase() === normalized);
if (found.length === 0) {
return null;
}
if (found.length === 1) {
return found[0][1];
}
return found.map(([, value]) => value).join(', ');
}
else if (isHeadersLike(this.headersInit)) {
return this.headersInit.get(normalized);
}
else {
const initValue = this.headersInit[key] || this.headersInit[normalized];
if (initValue != null) {
return initValue;
}
if (!this.objectNormalizedKeysOfHeadersInit.length) {
Object.keys(this.headersInit).forEach(k => {
this.objectOriginalKeysOfHeadersInit.push(k);
this.objectNormalizedKeysOfHeadersInit.push(k.toLowerCase());
});
}
const index = this.objectNormalizedKeysOfHeadersInit.indexOf(normalized);
if (index === -1) {
return null;
}
const originalKey = this.objectOriginalKeysOfHeadersInit[index];
return this.headersInit[originalKey];
}
}
// perf: Build the map of headers lazily, only when we need to access all headers or write to it.
// I could do a getter here, but I'm too lazy to type `getter`.
getMap() {
if (!this._map) {
this._setCookies ||= [];
if (this.headersInit != null) {
if (Array.isArray(this.headersInit)) {
this._map = new Map();
for (const [key, value] of this.headersInit) {
const normalizedKey = key.toLowerCase();
if (normalizedKey === 'set-cookie') {
if (Array.isArray(value)) {
this._setCookies.push(...value);
}
else if (value != null) {
this._setCookies.push(value);
}
continue;
}
this._map.set(normalizedKey, value);
}
}
else if (isHeadersLike(this.headersInit)) {
this._map = new Map();
this.headersInit.forEach((value, key) => {
if (key === 'set-cookie') {
this._setCookies ||= [];
if (Array.isArray(value)) {
this._setCookies.push(...value);
}
else if (value != null) {
this._setCookies.push(value);
}
return;
}
this._map.set(key, value);
});
}
else {
this._map = new Map();
for (const initKey in this.headersInit) {
const initValue = this.headersInit[initKey];
if (initValue != null) {
const normalizedKey = initKey.toLowerCase();
if (normalizedKey === 'set-cookie') {
this._setCookies ||= [];
if (Array.isArray(initValue)) {
this._setCookies.push(...initValue);
continue;
}
this._setCookies.push(initValue);
continue;
}
this._map.set(normalizedKey, initValue);
}
}
}
}
else {
this._map = new Map();
}
}
return this._map;
}
append(name, value) {
const key = name.toLowerCase();
if (key === 'set-cookie') {
this._setCookies ||= [];
this._setCookies.push(value);
return;
}
const existingValue = this.getMap().get(key);
const finalValue = existingValue ? `${existingValue}, ${value}` : value;
this.getMap().set(key, finalValue);
}
get(name) {
const value = this._get(name);
if (value == null) {
return null;
}
return value.toString();
}
has(name) {
const key = name.toLowerCase();
if (key === 'set-cookie') {
return !!this._setCookies?.length;
}
return !!this._get(name); // we might need to check if header exists and not just check if it's not nullable
}
set(name, value) {
const key = name.toLowerCase();
if (key === 'set-cookie') {
this._setCookies = [value];
return;
}
if (!this._map && this.headersInit != null) {
if (Array.isArray(this.headersInit)) {
const found = this.headersInit.find(([headerKey]) => headerKey.toLowerCase() === key);
if (found) {
found[1] = value;
}
else {
this.headersInit.push([key, value]);
}
return;
}
else if (isHeadersLike(this.headersInit)) {
this.headersInit.set(key, value);
return;
}
else {
this.headersInit[key] = value;
return;
}
}
this.getMap().set(key, value);
}
delete(name) {
const key = name.toLowerCase();
if (key === 'set-cookie') {
this._setCookies = [];
return;
}
this.getMap().delete(key);
}
forEach(callback) {
this._setCookies?.forEach(setCookie => {
callback(setCookie, 'set-cookie', this);
});
if (!this._map) {
if (this.headersInit) {
if (Array.isArray(this.headersInit)) {
this.headersInit.forEach(([key, value]) => {
callback(value, key, this);
});
return;
}
if (isHeadersLike(this.headersInit)) {
this.headersInit.forEach(callback);
return;
}
Object.entries(this.headersInit).forEach(([key, value]) => {
if (value != null) {
callback(value, key, this);
}
});
}
return;
}
this.getMap().forEach((value, key) => {
callback(value, key, this);
});
}
*_keys() {
if (this._setCookies?.length) {
yield 'set-cookie';
}
if (!this._map) {
if (this.headersInit) {
if (Array.isArray(this.headersInit)) {
yield* this.headersInit.map(([key]) => key)[Symbol.iterator]();
return;
}
if (isHeadersLike(this.headersInit)) {
yield* this.headersInit.keys();
return;
}
yield* Object.keys(this.headersInit)[Symbol.iterator]();
return;
}
}
yield* this.getMap().keys();
}
keys() {
return new PonyfillIteratorObject(this._keys(), 'HeadersIterator');
}
*_values() {
if (this._setCookies?.length) {
yield* this._setCookies;
}
if (!this._map) {
if (this.headersInit) {
if (Array.isArray(this.headersInit)) {
yield* this.headersInit.map(([, value]) => value)[Symbol.iterator]();
return;
}
if (isHeadersLike(this.headersInit)) {
yield* this.headersInit.values();
return;
}
yield* Object.values(this.headersInit)[Symbol.iterator]();
return;
}
}
yield* this.getMap().values();
}
values() {
return new PonyfillIteratorObject(this._values(), 'HeadersIterator');
}
*_entries() {
if (this._setCookies?.length) {
yield* this._setCookies.map(cookie => ['set-cookie', cookie]);
}
if (!this._map) {
if (this.headersInit) {
if (Array.isArray(this.headersInit)) {
yield* this.headersInit;
return;
}
if (isHeadersLike(this.headersInit)) {
yield* this.headersInit.entries();
return;
}
yield* Object.entries(this.headersInit);
return;
}
}
yield* this.getMap().entries();
}
entries() {
return new PonyfillIteratorObject(this._entries(), 'HeadersIterator');
}
getSetCookie() {
if (!this._setCookies) {
this.getMap();
}
return this._setCookies;
}
[Symbol.iterator]() {
return this.entries();
}
[Symbol.for('nodejs.util.inspect.custom')]() {
const record = {};
this.forEach((value, key) => {
if (key === 'set-cookie') {
record['set-cookie'] = this._setCookies || [];
}
else {
record[key] = value?.includes(',') ? value.split(',').map(el => el.trim()) : value;
}
});
return `Headers ${inspect(record)}`;
}
}