canal007/voice-bot
0
1'use strict';2 3const http = require('http');4const https = require('https');5const zlib = require('zlib');6const fs = require('fs');7const mime = require('mrmime');8const dataUriToBuffer = require('data-uri-to-buffer');9const buffer = require('buffer');10const Stream = require('stream');11const util = require('util');12const blob = require('@web-std/blob');13const file = require('@web-std/file');14const formData = require('@web-std/form-data');15const crypto = require('crypto');16const multipartParser = require('@web3-storage/multipart-parser');17const url = require('url');18const abortController = require('abort-controller');19 20class FetchBaseError extends Error {21 /**22 * @param {string} message 23 * @param {string} type 24 */25 constructor(message, type) {26 super(message);27 // Hide custom error implementation details from end-users28 Error.captureStackTrace(this, this.constructor);29 30 this.type = type;31 }32 33 get name() {34 return this.constructor.name;35 }36 37 get [Symbol.toStringTag]() {38 return this.constructor.name;39 }40}41 42/**43 * @typedef {{44 * address?: string45 * code: string46 * dest?: string47 * errno: number48 * info?: object49 * message: string50 * path?: string51 * port?: number52 * syscall: string53 * }} SystemError54*/55 56/**57 * FetchError interface for operational errors58 */59class FetchError extends FetchBaseError {60 /**61 * @param {string} message - Error message for human62 * @param {string} type - Error type for machine63 * @param {SystemError} [systemError] - For Node.js system error64 */65 constructor(message, type, systemError) {66 super(message, type);67 // When err.type is `system`, err.erroredSysCall contains system error and err.code contains system error code68 if (systemError) {69 // eslint-disable-next-line no-multi-assign70 this.code = this.errno = systemError.code;71 this.erroredSysCall = systemError.syscall;72 }73 }74}75 76/**77 * Is.js78 *79 * Object type checks.80 */81 82const NAME = Symbol.toStringTag;83 84/**85 * Check if `obj` is a URLSearchParams object86 * ref: https://github.com/node-fetch/node-fetch/issues/296#issuecomment-30759814387 *88 * @param {any} object89 * @return {obj is URLSearchParams}90 */91const isURLSearchParameters = (object) => {92 return (93 typeof object === "object" &&94 typeof object.append === "function" &&95 typeof object.delete === "function" &&96 typeof object.get === "function" &&97 typeof object.getAll === "function" &&98 typeof object.has === "function" &&99 typeof object.set === "function" &&100 typeof object.sort === "function" &&101 object[NAME] === "URLSearchParams"102 );103};104 105/**106 * Check if `object` is a W3C `Blob` object (which `File` inherits from)107 *108 * @param {*} object109 * @return {object is Blob}110 */111const isBlob = (object) => {112 return (113 typeof object === "object" &&114 typeof object.arrayBuffer === "function" &&115 typeof object.type === "string" &&116 typeof object.stream === "function" &&117 typeof object.constructor === "function" &&118 /^(Blob|File)$/.test(object[NAME])119 );120};121 122/**123 * Check if `obj` is a spec-compliant `FormData` object124 *125 * @param {*} object126 * @return {object is FormData}127 */128function isFormData(object) {129 return (130 typeof object === "object" &&131 typeof object.append === "function" &&132 typeof object.set === "function" &&133 typeof object.get === "function" &&134 typeof object.getAll === "function" &&135 typeof object.delete === "function" &&136 typeof object.keys === "function" &&137 typeof object.values === "function" &&138 typeof object.entries === "function" &&139 typeof object.constructor === "function" &&140 object[NAME] === "FormData"141 );142}143 144/**145 * Detect form data input from form-data module146 *147 * @param {any} value148 * @returns {value is Stream & {getBoundary():string, hasKnownLength():boolean, getLengthSync():number|null}}149 */150const isMultipartFormDataStream = (value) => {151 return (152 value instanceof Stream === true &&153 typeof value.getBoundary === "function" &&154 typeof value.hasKnownLength === "function" &&155 typeof value.getLengthSync === "function"156 );157};158 159/**160 * Check if `obj` is an instance of AbortSignal.161 *162 * @param {any} object163 * @return {obj is AbortSignal}164 */165const isAbortSignal = (object) => {166 return (167 typeof object === "object" &&168 (object[NAME] === "AbortSignal" || object[NAME] === "EventTarget")169 );170};171 172/**173 * Check if `value` is a ReadableStream.174 *175 * @param {*} value176 * @returns {value is ReadableStream}177 */178const isReadableStream = (value) => {179 return (180 typeof value === "object" &&181 typeof value.getReader === "function" &&182 typeof value.cancel === "function" &&183 typeof value.tee === "function"184 );185};186 187/**188 *189 * @param {any} value190 * @returns {value is Iterable<unknown>}191 */192const isIterable = (value) => value && Symbol.iterator in value;193 194const carriage = '\r\n';195const dashes = '-'.repeat(2);196const carriageLength = Buffer.byteLength(carriage);197 198/**199 * @param {string} boundary200 */201const getFooter = boundary => `${dashes}${boundary}${dashes}${carriage.repeat(2)}`;202 203/**204 * @param {string} boundary205 * @param {string} name206 * @param {*} field207 *208 * @return {string}209 */210function getHeader(boundary, name, field) {211 let header = '';212 213 header += `${dashes}${boundary}${carriage}`;214 header += `Content-Disposition: form-data; name="${name}"`;215 216 if (isBlob(field)) {217 const { name = 'blob', type } = /** @type {Blob & {name?:string}} */ (field);218 header += `; filename="${name}"${carriage}`;219 header += `Content-Type: ${type || 'application/octet-stream'}`;220 }221 222 return `${header}${carriage.repeat(2)}`;223}224 225/**226 * @return {string}227 */228const getBoundary = () => crypto.randomBytes(8).toString('hex');229 230/**231 * @param {FormData} form232 * @param {string} boundary233 */234async function * formDataIterator(form, boundary) {235 const encoder = new TextEncoder();236 for (const [name, value] of form) {237 yield encoder.encode(getHeader(boundary, name, value));238 239 if (isBlob(value)) {240 // @ts-ignore - we know our streams implement aysnc iteration241 yield * value.stream();242 } else {243 yield encoder.encode(value);244 }245 246 yield encoder.encode(carriage);247 }248 249 yield encoder.encode(getFooter(boundary));250}251 252/**253 * @param {FormData} form254 * @param {string} boundary255 */256function getFormDataLength(form, boundary) {257 let length = 0;258 259 for (const [name, value] of form) {260 length += Buffer.byteLength(getHeader(boundary, name, value));261 262 if (isBlob(value)) {263 length += value.size;264 } else {265 length += Buffer.byteLength(String(value));266 }267 268 length += carriageLength;269 }270 271 length += Buffer.byteLength(getFooter(boundary));272 273 return length;274}275 276/**277 * @param {Body & {headers?:Headers}} source278 */279const toFormData = async (source) => {280 let { body, headers } = source;281 const contentType = headers?.get('Content-Type') || '';282 283 if (contentType.startsWith('application/x-www-form-urlencoded') && body != null) {284 const form = new formData.FormData();285 let bodyText = await source.text();286 new URLSearchParams(bodyText).forEach((v, k) => form.append(k, v));287 return form;288 }289 290 const [type, boundary] = contentType.split(/\s*;\s*boundary=/);291 if (type === 'multipart/form-data' && boundary != null && body != null) {292 const form = new formData.FormData();293 const parts = multipartParser.iterateMultipart(body, boundary);294 for await (const { name, data, filename, contentType } of parts) {295 if (typeof filename === 'string') {296 form.append(name, new file.File([data], filename, { type: contentType }));297 } else if (typeof filename !== 'undefined') {298 form.append(name, new file.File([], '', { type: contentType }));299 } else {300 form.append(name, new TextDecoder().decode(data), filename);301 }302 }303 return form304 } else {305 throw new TypeError('Could not parse content as FormData.')306 }307};308 309const encoder = new util.TextEncoder();310const decoder = new util.TextDecoder();311 312/**313 * @param {string} text314 */315const encode = text => encoder.encode(text);316 317/**318 * @param {Uint8Array} bytes319 */320const decode = bytes => decoder.decode(bytes);321 322// @ts-check323const {readableHighWaterMark} = new Stream.Readable();324 325const INTERNALS$2 = Symbol('Body internals');326 327/**328 * Body mixin329 *330 * Ref: https://fetch.spec.whatwg.org/#body331 * @implements {globalThis.Body}332 */333 334class Body {335 /**336 * @param {BodyInit|Stream|null} body337 * @param {{size?:number}} options338 */339 constructor(body, {340 size = 0341 } = {}) {342 const state = {343 /** @type {null|ReadableStream<Uint8Array>} */344 body: null,345 /** @type {string|null} */346 type: null,347 /** @type {number|null} */348 size: null,349 /** @type {null|string} */350 boundary: null,351 disturbed: false,352 /** @type {null|Error} */353 error: null354 };355 /** @private */356 this[INTERNALS$2] = state;357 358 if (body === null) {359 // Body is undefined or null360 state.body = null;361 state.size = 0;362 } else if (isURLSearchParameters(body)) {363 // Body is a URLSearchParams364 const bytes = encode(body.toString());365 state.body = fromBytes(bytes);366 state.size = bytes.byteLength;367 state.type = 'application/x-www-form-urlencoded;charset=UTF-8';368 } else if (isBlob(body)) {369 // Body is blob370 state.size = body.size;371 state.type = body.type || null;372 state.body = body.stream();373 } else if (body instanceof Uint8Array) {374 // Body is Buffer375 state.body = fromBytes(body);376 state.size = body.byteLength;377 } else if (util.types.isAnyArrayBuffer(body)) {378 // Body is ArrayBuffer379 const bytes = new Uint8Array(body);380 state.body = fromBytes(bytes);381 state.size = bytes.byteLength;382 } else if (ArrayBuffer.isView(body)) {383 // Body is ArrayBufferView384 const bytes = new Uint8Array(body.buffer, body.byteOffset, body.byteLength);385 state.body = fromBytes(bytes);386 state.size = bytes.byteLength;387 } else if (isReadableStream(body)) {388 // Body is stream389 state.body = body;390 } else if (isFormData(body)) {391 // Body is an instance of formdata-node392 const boundary = `NodeFetchFormDataBoundary${getBoundary()}`;393 state.type = `multipart/form-data; boundary=${boundary}`;394 state.size = getFormDataLength(body, boundary);395 state.body = fromAsyncIterable(formDataIterator(body, boundary));396 } else if (isMultipartFormDataStream(body)) {397 state.type = `multipart/form-data; boundary=${body.getBoundary()}`;398 state.size = body.hasKnownLength() ? body.getLengthSync() : null;399 state.body = fromStream(body);400 } else if (body instanceof Stream) {401 state.body = fromStream(body);402 } else {403 // None of the above404 // coerce to string then buffer405 const bytes = encode(String(body));406 state.type = 'text/plain;charset=UTF-8';407 state.size = bytes.byteLength;408 state.body = fromBytes(bytes);409 }410 411 this.size = size;412 413 // if (body instanceof Stream) {414 // body.on('error', err => {415 // const error = err instanceof FetchBaseError ?416 // err :417 // new FetchError(`Invalid response body while trying to fetch ${this.url}: ${err.message}`, 'system', err);418 // this[INTERNALS].error = error;419 // });420 // }421 }422 423 /** @type {Headers} */424 /* c8 ignore next 3 */425 get headers() {426 throw new TypeError(`'get headers' called on an object that does not implements interface.`)427 }428 429 get body() {430 return this[INTERNALS$2].body;431 }432 433 get bodyUsed() {434 return this[INTERNALS$2].disturbed;435 }436 437 /**438 * Decode response as ArrayBuffer439 *440 * @return {Promise<ArrayBuffer>}441 */442 async arrayBuffer() {443 const {buffer, byteOffset, byteLength} = await consumeBody(this);444 return buffer.slice(byteOffset, byteOffset + byteLength);445 }446 447 /**448 * Return raw response as Blob449 *450 * @return Promise451 */452 async blob() {453 const ct = (this.headers && this.headers.get('content-type')) || (this[INTERNALS$2].body && this[INTERNALS$2].type) || '';454 const buf = await consumeBody(this);455 456 return new blob.Blob([buf], {457 type: ct458 });459 }460 461 /**462 * Decode response as json463 *464 * @return Promise465 */466 async json() {467 return JSON.parse(await this.text());468 }469 470 /**471 * Decode response as text472 *473 * @return Promise474 */475 async text() {476 const buffer = await consumeBody(this);477 return decode(buffer);478 }479 480 /**481 * @returns {Promise<FormData>}482 */483 484 async formData() {485 return toFormData(this)486 }487}488 489// In browsers, all properties are enumerable.490Object.defineProperties(Body.prototype, {491 body: {enumerable: true},492 bodyUsed: {enumerable: true},493 arrayBuffer: {enumerable: true},494 blob: {enumerable: true},495 json: {enumerable: true},496 text: {enumerable: true},497 formData: {enumerable: true}498});499 500/**501 * Consume and convert an entire Body to a Buffer.502 *503 * Ref: https://fetch.spec.whatwg.org/#concept-body-consume-body504 *505 * @param {Body & {url?:string}} data506 * @return {Promise<Uint8Array>}507 */508async function consumeBody(data) {509 const state = data[INTERNALS$2];510 if (state.disturbed) {511 throw new TypeError(`body used already for: ${data.url}`);512 }513 514 state.disturbed = true;515 516 if (state.error) {517 throw state.error;518 }519 520 const {body} = state;521 522 // Body is null523 if (body === null) {524 return new Uint8Array(0);525 }526 527 // Body is stream528 // get ready to actually consume the body529 /** @type {[Uint8Array|null, Uint8Array[], number]} */530 const [buffer, chunks, limit] = data.size > 0 ?531 [new Uint8Array(data.size), [], data.size] :532 [null, [], Infinity];533 let offset = 0;534 535 const source = streamIterator(body);536 try {537 for await (const chunk of source) {538 const bytes = chunk instanceof Uint8Array ?539 chunk :540 Buffer.from(chunk);541 542 if (offset + bytes.byteLength > limit) {543 const error = new FetchError(`content size at ${data.url} over limit: ${limit}`, 'max-size');544 source.throw(error);545 throw error;546 } else if (buffer) {547 buffer.set(bytes, offset);548 } else {549 chunks.push(bytes);550 }551 552 offset += bytes.byteLength;553 }554 555 if (buffer) {556 if (offset < buffer.byteLength) {557 throw new FetchError(`Premature close of server response while trying to fetch ${data.url}`, 'premature-close');558 } else {559 return buffer;560 }561 } else {562 return writeBytes(new Uint8Array(offset), chunks);563 }564 } catch (error) {565 if (error instanceof FetchBaseError) {566 throw error;567 // @ts-expect-error - we know it will have a name568 } else if (error && error.name === 'AbortError') {569 throw error;570 } else {571 const e = /** @type {import('./errors/fetch-error').SystemError} */(error);572 // Other errors, such as incorrect content-encoding573 throw new FetchError(`Invalid response body while trying to fetch ${data.url}: ${e.message}`, 'system', e);574 }575 }576}577 578/**579 * Clone body given Res/Req instance580 *581 * @param {Body} instance Response or Request instance582 * @return {ReadableStream<Uint8Array> | null}583 */584const clone = instance => {585 const {body} = instance;586 587 // Don't allow cloning a used body588 if (instance.bodyUsed) {589 throw new Error('cannot clone body after it is used');590 }591 592 if (!body) {593 return null;594 }595 596 const [left, right] = body.tee();597 instance[INTERNALS$2].body = left;598 return right;599};600 601/**602 * Performs the operation "extract a `Content-Type` value from |object|" as603 * specified in the specification:604 * https://fetch.spec.whatwg.org/#concept-bodyinit-extract605 *606 * This function assumes that instance.body is present.607 *608 * @param {Body} source Any options.body input609 * @returns {string | null}610 */611const extractContentType = source => source[INTERNALS$2].type;612 613/**614 * The Fetch Standard treats this as if "total bytes" is a property on the body.615 * For us, we have to explicitly get it with a function.616 *617 * ref: https://fetch.spec.whatwg.org/#concept-body-total-bytes618 *619 * @param {Body} source - Body object from the Body instance.620 * @returns {number | null}621 */622const getTotalBytes = source => source[INTERNALS$2].size;623 624/**625 * Write a Body to a Node.js WritableStream (e.g. http.Request) object.626 *627 * @param {Stream.Writable} dest - The stream to write to.628 * @param {Body} source - Body object from the Body instance.629 * @returns {void}630 */631const writeToStream = (dest, {body}) => {632 if (body === null) {633 // Body is null634 dest.end();635 } else {636 Stream.Readable.from(streamIterator(body)).pipe(dest);637 }638};639 640/**641 * @template T642 * @implements {AsyncGenerator<T, void, void>}643 */644class StreamIterableIterator {645 /**646 * @param {ReadableStream<T>} stream647 */648 constructor(stream) {649 this.stream = stream;650 this.reader = null;651 }652 653 /**654 * @returns {AsyncGenerator<T, void, void>}655 */656 [Symbol.asyncIterator]() {657 return this;658 }659 660 getReader() {661 if (this.reader) {662 return this.reader;663 }664 665 const reader = this.stream.getReader();666 this.reader = reader;667 return reader;668 }669 670 /**671 * @returns {Promise<IteratorResult<T, void>>}672 */673 next() {674 return /** @type {Promise<IteratorResult<T, void>>} */ (this.getReader().read());675 }676 677 /**678 * @returns {Promise<IteratorResult<T, void>>}679 */680 async return() {681 if (this.reader) {682 await this.reader.cancel();683 }684 685 return {done: true, value: undefined};686 }687 688 /**689 * 690 * @param {any} error 691 * @returns {Promise<IteratorResult<T, void>>}692 */693 async throw(error) {694 await this.getReader().cancel(error);695 return {done: true, value: undefined};696 }697}698 699/**700 * @template T701 * @param {ReadableStream<T>} stream702 */703const streamIterator = stream => new StreamIterableIterator(stream);704 705/**706 * @param {Uint8Array} buffer707 * @param {Uint8Array[]} chunks708 */709const writeBytes = (buffer, chunks) => {710 let offset = 0;711 for (const chunk of chunks) {712 buffer.set(chunk, offset);713 offset += chunk.byteLength;714 }715 716 return buffer;717};718 719/**720 * @param {Uint8Array} bytes721 * @returns {ReadableStream<Uint8Array>}722 */723// @ts-ignore724const fromBytes = bytes => new blob.ReadableStream({725 start(controller) {726 controller.enqueue(bytes);727 controller.close();728 }729});730 731/**732 * @param {AsyncIterable<Uint8Array>} content733 * @returns {ReadableStream<Uint8Array>}734 */735const fromAsyncIterable = content =>736 // @ts-ignore737 new blob.ReadableStream(new AsyncIterablePump(content));738 739/**740 * @implements {UnderlyingSource<Uint8Array>}741 */742class AsyncIterablePump {743 /**744 * @param {AsyncIterable<Uint8Array>} source745 */746 constructor(source) {747 this.source = source[Symbol.asyncIterator]();748 }749 750 /**751 * @param {ReadableStreamController<Uint8Array>} controller752 */753 async pull(controller) {754 try {755 while (controller.desiredSize || 0 > 0) {756 // eslint-disable-next-line no-await-in-loop757 const next = await this.source.next();758 if (next.done) {759 controller.close();760 break;761 } else {762 controller.enqueue(next.value);763 }764 }765 } catch (error) {766 controller.error(error);767 }768 }769 770 /**771 * @param {any} [reason]772 */773 cancel(reason) {774 if (reason) {775 if (typeof this.source.throw === 'function') {776 this.source.throw(reason);777 } else if (typeof this.source.return === 'function') {778 this.source.return();779 }780 } else if (typeof this.source.return === 'function') {781 this.source.return();782 }783 }784}785 786/**787 * @param {Stream & {readableHighWaterMark?:number}} source788 * @returns {ReadableStream<Uint8Array>}789 */790const fromStream = source => {791 const pump = new StreamPump(source);792 const stream = new blob.ReadableStream(pump, pump);793 return stream;794};795 796/**797 * @implements {UnderlyingSource<Uint8Array>}798 * @implements {QueuingStrategy<Uint8Array>}799 */800class StreamPump {801 /**802 * @param {Stream & {803 * readableHighWaterMark?: number804 * readable?:boolean,805 * resume?: () => void,806 * pause?: () => void807 * destroy?: (error?:Error) => void808 * }} stream809 */810 constructor(stream) {811 this.highWaterMark = stream.readableHighWaterMark || readableHighWaterMark;812 this.accumalatedSize = 0;813 this.stream = stream;814 this.enqueue = this.enqueue.bind(this);815 this.error = this.error.bind(this);816 this.close = this.close.bind(this);817 }818 819 /**820 * @param {Uint8Array} [chunk]821 */822 size(chunk) {823 return chunk?.byteLength || 0;824 }825 826 /**827 * @param {ReadableStreamController<Uint8Array>} controller828 */829 start(controller) {830 this.controller = controller;831 this.stream.on('data', this.enqueue);832 this.stream.once('error', this.error);833 this.stream.once('end', this.close);834 this.stream.once('close', this.close);835 }836 837 pull() {838 this.resume();839 }840 841 /**842 * @param {any} [reason]843 */844 cancel(reason) {845 if (this.stream.destroy) {846 this.stream.destroy(reason);847 }848 849 this.stream.off('data', this.enqueue);850 this.stream.off('error', this.error);851 this.stream.off('end', this.close);852 this.stream.off('close', this.close);853 }854 855 /**856 * @param {Uint8Array|string} chunk857 */858 enqueue(chunk) {859 if (this.controller) {860 try {861 const bytes = chunk instanceof Uint8Array ?862 chunk :863 Buffer.from(chunk);864 865 const available = (this.controller.desiredSize || 0) - bytes.byteLength;866 this.controller.enqueue(bytes);867 if (available <= 0) {868 this.pause();869 }870 } catch {871 this.controller.error(new Error('Could not create Buffer, chunk must be of type string or an instance of Buffer, ArrayBuffer, or Array or an Array-like Object'));872 this.cancel();873 }874 }875 }876 877 pause() {878 if (this.stream.pause) {879 this.stream.pause();880 }881 }882 883 resume() {884 if (this.stream.readable && this.stream.resume) {885 this.stream.resume();886 }887 }888 889 close() {890 if (this.controller) {891 this.controller.close();892 delete this.controller;893 }894 }895 896 /**897 * @param {Error} error 898 */899 error(error) {900 if (this.controller) {901 this.controller.error(error);902 delete this.controller;903 }904 }905}906 907/**908 * Headers.js909 *910 * Headers class offers convenient helpers911 */912 913const validators = /** @type {{validateHeaderName?:(name:string) => any, validateHeaderValue?:(name:string, value:string) => any}} */914(http);915 916const validateHeaderName = typeof validators.validateHeaderName === 'function' ?917 validators.validateHeaderName :918 /**919 * @param {string} name 920 */921 name => {922 if (!/^[\^`\-\w!#$%&'*+.|~]+$/.test(name)) {923 const err = new TypeError(`Header name must be a valid HTTP token [${name}]`);924 Object.defineProperty(err, 'code', {value: 'ERR_INVALID_HTTP_TOKEN'});925 throw err;926 }927 };928 929const validateHeaderValue = typeof validators.validateHeaderValue === 'function' ?930 validators.validateHeaderValue :931 /**932 * @param {string} name 933 * @param {string} value 934 */935 (name, value) => {936 if (/[^\t\u0020-\u007E\u0080-\u00FF]/.test(value)) {937 const err = new TypeError(`Invalid character in header content ["${name}"]`);938 Object.defineProperty(err, 'code', {value: 'ERR_INVALID_CHAR'});939 throw err;940 }941 };942 943/**944 * @typedef {Headers | Record<string, string> | Iterable<readonly [string, string]> | Iterable<Iterable<string>>} HeadersInit945 */946 947/**948 * This Fetch API interface allows you to perform various actions on HTTP request and response headers.949 * These actions include retrieving, setting, adding to, and removing.950 * A Headers object has an associated header list, which is initially empty and consists of zero or more name and value pairs.951 * You can add to this using methods like append() (see Examples.)952 * In all methods of this interface, header names are matched by case-insensitive byte sequence.953 *954 * @implements {globalThis.Headers}955 */956class Headers$1 extends URLSearchParams {957 /**958 * Headers class959 *960 * @constructor961 * @param {HeadersInit} [init] - Response headers962 */963 constructor(init) {964 // Validate and normalize init object in [name, value(s)][]965 /** @type {string[][]} */966 let result = [];967 if (init instanceof Headers$1) {968 const raw = init.raw();969 for (const [name, values] of Object.entries(raw)) {970 result.push(...values.map(value => [name, value]));971 }972 } else if (init == null) ; else if (isIterable(init)) {973 // Sequence<sequence<ByteString>>974 // Note: per spec we have to first exhaust the lists then process them975 result = [...init]976 .map(pair => {977 if (978 typeof pair !== 'object' || util.types.isBoxedPrimitive(pair)979 ) {980 throw new TypeError('Each header pair must be an iterable object');981 }982 983 return [...pair];984 }).map(pair => {985 if (pair.length !== 2) {986 throw new TypeError('Each header pair must be a name/value tuple');987 }988 989 return [...pair];990 });991 } else if (typeof init === "object" && init !== null) {992 // Record<ByteString, ByteString>993 result.push(...Object.entries(init));994 } else {995 throw new TypeError('Failed to construct \'Headers\': The provided value is not of type \'(sequence<sequence<ByteString>> or record<ByteString, ByteString>)');996 }997 998 // Validate and lowercase999 result =1000 result.length > 0 ?1001 result.map(([name, value]) => {1002 validateHeaderName(name);1003 validateHeaderValue(name, String(value));1004 return [String(name).toLowerCase(), String(value)];1005 }) :1006 [];1007 1008 super(result);1009 1010 // Returning a Proxy that will lowercase key names, validate parameters and sort keys1011 // eslint-disable-next-line no-constructor-return1012 return new Proxy(this, {1013 get(target, p, receiver) {1014 switch (p) {1015 case 'append':1016 case 'set':1017 /**1018 * @param {string} name1019 * @param {string} value1020 */1021 return (name, value) => {1022 validateHeaderName(name);1023 validateHeaderValue(name, String(value));1024 return URLSearchParams.prototype[p].call(1025 receiver,1026 String(name).toLowerCase(),1027 String(value)1028 );1029 };1030 1031 case 'delete':1032 case 'has':1033 case 'getAll':1034 /**1035 * @param {string} name1036 */1037 return name => {1038 validateHeaderName(name);1039 // @ts-ignore1040 return URLSearchParams.prototype[p].call(1041 receiver,1042 String(name).toLowerCase()1043 );1044 };1045 1046 case 'keys':1047 return () => {1048 target.sort();1049 return new Set(URLSearchParams.prototype.keys.call(target)).keys();1050 };1051 1052 default:1053 return Reflect.get(target, p, receiver);1054 }1055 }1056 /* c8 ignore next */1057 });1058 }1059 1060 get [Symbol.toStringTag]() {1061 return this.constructor.name;1062 }1063 1064 toString() {1065 return Object.prototype.toString.call(this);1066 }1067 1068 /**1069 * 1070 * @param {string} name 1071 */1072 get(name) {1073 const values = this.getAll(name);1074 if (values.length === 0) {1075 return null;1076 }1077 1078 let value = values.join(', ');1079 if (/^content-encoding$/i.test(name)) {1080 value = value.toLowerCase();1081 }1082 1083 return value;1084 }1085 1086 /**1087 * @param {(value: string, key: string, parent: this) => void} callback 1088 * @param {any} thisArg 1089 * @returns {void}1090 */1091 forEach(callback, thisArg = undefined) {1092 for (const name of this.keys()) {1093 if (name.toLowerCase() === 'set-cookie') {1094 let cookies = this.getAll(name);1095 while (cookies.length > 0) {1096 Reflect.apply(callback, thisArg, [cookies.shift(), name, this]);1097 }1098 } else {1099 Reflect.apply(callback, thisArg, [this.get(name), name, this]);1100 }1101 }1102 }1103 1104 /**1105 * @returns {IterableIterator<string>}1106 */1107 * values() {1108 for (const name of this.keys()) {1109 if (name.toLowerCase() === 'set-cookie') {1110 let cookies = this.getAll(name);1111 while (cookies.length > 0) {1112 yield /** @type {string} */(cookies.shift());1113 }1114 } else {1115 yield /** @type {string} */(this.get(name));1116 }1117 }1118 }1119 1120 /**1121 * @returns {IterableIterator<[string, string]>}1122 */1123 * entries() {1124 for (const name of this.keys()) {1125 if (name.toLowerCase() === 'set-cookie') {1126 let cookies = this.getAll(name);1127 while (cookies.length > 0) {1128 yield [name, /** @type {string} */(cookies.shift())];1129 }1130 } else {1131 yield [name, /** @type {string} */(this.get(name))];1132 }1133 }1134 }1135 1136 [Symbol.iterator]() {1137 return this.entries();1138 }1139 1140 /**1141 * Node-fetch non-spec method1142 * returning all headers and their values as array1143 * @returns {Record<string, string[]>}1144 */1145 raw() {1146 return [...this.keys()].reduce((result, key) => {1147 result[key] = this.getAll(key);1148 return result;1149 }, /** @type {Record<string, string[]>} */({}));1150 }1151 1152 /**1153 * For better console.log(headers) and also to convert Headers into Node.js Request compatible format1154 */1155 [Symbol.for('nodejs.util.inspect.custom')]() {1156 return [...this.keys()].reduce((result, key) => {1157 const values = this.getAll(key);1158 // Http.request() only supports string as Host header.1159 // This hack makes specifying custom Host header possible.1160 if (key === 'host') {1161 result[key] = values[0];1162 } else {1163 result[key] = values.length > 1 ? values : values[0];1164 }1165 1166 return result;1167 }, /** @type {Record<string, string|string[]>} */({}));1168 }1169}1170 1171/**1172 * Re-shaping object for Web IDL tests1173 * Only need to do it for overridden methods1174 */1175Object.defineProperties(1176 Headers$1.prototype,1177 ['get', 'entries', 'forEach', 'values'].reduce((result, property) => {1178 result[property] = {enumerable: true};1179 return result;1180 }, /** @type {Record<string, {enumerable:true}>} */ ({}))1181);1182 1183/**1184 * Create a Headers object from an http.IncomingMessage.rawHeaders, ignoring those that do1185 * not conform to HTTP grammar productions.1186 * @param {import('http').IncomingMessage['rawHeaders']} headers1187 */1188function fromRawHeaders(headers = []) {1189 return new Headers$1(1190 headers1191 // Split into pairs1192 .reduce((result, value, index, array) => {1193 if (index % 2 === 0) {1194 result.push(array.slice(index, index + 2));1195 }1196 1197 return result;1198 }, /** @type {string[][]} */([]))1199 .filter(([name, value]) => {1200 try {