diff --git a/backend/node_modules/.package-lock.json b/backend/node_modules/.package-lock.json
index be5e579..4d9c84e 100644
--- a/backend/node_modules/.package-lock.json
+++ b/backend/node_modules/.package-lock.json
@@ -595,6 +595,26 @@
"node-pre-gyp": "bin/node-pre-gyp"
}
},
+ "node_modules/@mapbox/node-pre-gyp/node_modules/node-fetch": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
+ "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
+ "license": "MIT",
+ "dependencies": {
+ "whatwg-url": "^5.0.0"
+ },
+ "engines": {
+ "node": "4.x || >=6.0.0"
+ },
+ "peerDependencies": {
+ "encoding": "^0.1.0"
+ },
+ "peerDependenciesMeta": {
+ "encoding": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -1278,6 +1298,15 @@
"node": ">= 10"
}
},
+ "node_modules/data-uri-to-buffer": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
+ "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 12"
+ }
+ },
"node_modules/date-fns": {
"version": "2.30.0",
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz",
@@ -1778,6 +1807,29 @@
"reusify": "^1.0.4"
}
},
+ "node_modules/fetch-blob": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz",
+ "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/jimmywarting"
+ },
+ {
+ "type": "paypal",
+ "url": "https://paypal.me/jimmywarting"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "node-domexception": "^1.0.0",
+ "web-streams-polyfill": "^3.0.3"
+ },
+ "engines": {
+ "node": "^12.20 || >= 14.13"
+ }
+ },
"node_modules/file-entry-cache": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
@@ -1902,6 +1954,18 @@
"node": ">= 6"
}
},
+ "node_modules/formdata-polyfill": {
+ "version": "4.0.10",
+ "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
+ "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
+ "license": "MIT",
+ "dependencies": {
+ "fetch-blob": "^3.1.2"
+ },
+ "engines": {
+ "node": ">=12.20.0"
+ }
+ },
"node_modules/forwarded": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
@@ -2816,6 +2880,26 @@
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz",
"integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA=="
},
+ "node_modules/node-domexception": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
+ "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
+ "deprecated": "Use your platform's native DOMException instead",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/jimmywarting"
+ },
+ {
+ "type": "github",
+ "url": "https://paypal.me/jimmywarting"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.5.0"
+ }
+ },
"node_modules/node-ensure": {
"version": "0.0.0",
"resolved": "https://registry.npmjs.org/node-ensure/-/node-ensure-0.0.0.tgz",
@@ -2823,22 +2907,21 @@
"license": "MIT"
},
"node_modules/node-fetch": {
- "version": "2.7.0",
- "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
- "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz",
+ "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==",
+ "license": "MIT",
"dependencies": {
- "whatwg-url": "^5.0.0"
+ "data-uri-to-buffer": "^4.0.0",
+ "fetch-blob": "^3.1.4",
+ "formdata-polyfill": "^4.0.10"
},
"engines": {
- "node": "4.x || >=6.0.0"
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
- "peerDependencies": {
- "encoding": "^0.1.0"
- },
- "peerDependenciesMeta": {
- "encoding": {
- "optional": true
- }
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/node-fetch"
}
},
"node_modules/nodemailer": {
@@ -3124,6 +3207,51 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
+ "node_modules/playwright": {
+ "version": "1.55.1",
+ "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.55.1.tgz",
+ "integrity": "sha512-cJW4Xd/G3v5ovXtJJ52MAOclqeac9S/aGGgRzLabuF8TnIb6xHvMzKIa6JmrRzUkeXJgfL1MhukP0NK6l39h3A==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "playwright-core": "1.55.1"
+ },
+ "bin": {
+ "playwright": "cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "fsevents": "2.3.2"
+ }
+ },
+ "node_modules/playwright-core": {
+ "version": "1.55.1",
+ "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.55.1.tgz",
+ "integrity": "sha512-Z6Mh9mkwX+zxSlHqdr5AOcJnfp+xUWLCt9uKV18fhzA8eyxUd8NUWzAjxUh55RZKSYwDGX0cfaySdhZJGMoJ+w==",
+ "license": "Apache-2.0",
+ "bin": {
+ "playwright-core": "cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/playwright/node_modules/fsevents": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
+ "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
+ "hasInstallScript": true,
+ "ideallyInert": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
"node_modules/prelude-ls": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
@@ -3792,7 +3920,8 @@
"node_modules/tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
- "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
+ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
+ "license": "MIT"
},
"node_modules/tslib": {
"version": "2.7.0",
@@ -3992,15 +4121,26 @@
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true
},
+ "node_modules/web-streams-polyfill": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",
+ "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
"node_modules/webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
- "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
+ "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
+ "license": "BSD-2-Clause"
},
"node_modules/whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
+ "license": "MIT",
"dependencies": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
diff --git a/backend/node_modules/node-fetch/LICENSE.md b/backend/node_modules/node-fetch/LICENSE.md
index 660ffec..41ca1b6 100644
--- a/backend/node_modules/node-fetch/LICENSE.md
+++ b/backend/node_modules/node-fetch/LICENSE.md
@@ -1,6 +1,6 @@
The MIT License (MIT)
-Copyright (c) 2016 David Frank
+Copyright (c) 2016 - 2020 Node Fetch Team
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/backend/node_modules/node-fetch/README.md b/backend/node_modules/node-fetch/README.md
index 55f09b7..badc2b1 100644
--- a/backend/node_modules/node-fetch/README.md
+++ b/backend/node_modules/node-fetch/README.md
@@ -1,17 +1,24 @@
-node-fetch
-==========
+
+

+
+
A light-weight module that brings Fetch API to Node.js.
+

+

+

+

+

+

+
+
+
Consider supporting us on our Open Collective:
+
+
+

+
-[![npm version][npm-image]][npm-url]
-[![build status][travis-image]][travis-url]
-[![coverage status][codecov-image]][codecov-url]
-[![install size][install-size-image]][install-size-url]
-[![Discord][discord-image]][discord-url]
+---
-A light-weight module that brings `window.fetch` to Node.js
-
-(We are looking for [v2 maintainers and collaborators](https://github.com/bitinn/node-fetch/issues/567))
-
-[![Backers][opencollective-image]][opencollective-url]
+**You might be looking for the [v2 docs](https://github.com/node-fetch/node-fetch/tree/2.x#readme)**
@@ -20,32 +27,53 @@ A light-weight module that brings `window.fetch` to Node.js
- [Difference from client-side fetch](#difference-from-client-side-fetch)
- [Installation](#installation)
- [Loading and configuring the module](#loading-and-configuring-the-module)
+- [Upgrading](#upgrading)
- [Common Usage](#common-usage)
- - [Plain text or HTML](#plain-text-or-html)
- - [JSON](#json)
- - [Simple Post](#simple-post)
- - [Post with JSON](#post-with-json)
- - [Post with form parameters](#post-with-form-parameters)
- - [Handling exceptions](#handling-exceptions)
- - [Handling client and server errors](#handling-client-and-server-errors)
+ - [Plain text or HTML](#plain-text-or-html)
+ - [JSON](#json)
+ - [Simple Post](#simple-post)
+ - [Post with JSON](#post-with-json)
+ - [Post with form parameters](#post-with-form-parameters)
+ - [Handling exceptions](#handling-exceptions)
+ - [Handling client and server errors](#handling-client-and-server-errors)
+ - [Handling cookies](#handling-cookies)
- [Advanced Usage](#advanced-usage)
- - [Streams](#streams)
- - [Buffer](#buffer)
- - [Accessing Headers and other Meta data](#accessing-headers-and-other-meta-data)
- - [Extract Set-Cookie Header](#extract-set-cookie-header)
- - [Post data using a file stream](#post-data-using-a-file-stream)
- - [Post with form-data (detect multipart)](#post-with-form-data-detect-multipart)
- - [Request cancellation with AbortSignal](#request-cancellation-with-abortsignal)
+ - [Streams](#streams)
+ - [Accessing Headers and other Metadata](#accessing-headers-and-other-metadata)
+ - [Extract Set-Cookie Header](#extract-set-cookie-header)
+ - [Post data using a file](#post-data-using-a-file)
+ - [Request cancellation with AbortSignal](#request-cancellation-with-abortsignal)
- [API](#api)
- - [fetch(url[, options])](#fetchurl-options)
- - [Options](#options)
- - [Class: Request](#class-request)
- - [Class: Response](#class-response)
- - [Class: Headers](#class-headers)
- - [Interface: Body](#interface-body)
- - [Class: FetchError](#class-fetcherror)
-- [License](#license)
+ - [fetch(url[, options])](#fetchurl-options)
+ - [Options](#options)
+ - [Default Headers](#default-headers)
+ - [Custom Agent](#custom-agent)
+ - [Custom highWaterMark](#custom-highwatermark)
+ - [Insecure HTTP Parser](#insecure-http-parser)
+ - [Class: Request](#class-request)
+ - [new Request(input[, options])](#new-requestinput-options)
+ - [Class: Response](#class-response)
+ - [new Response([body[, options]])](#new-responsebody-options)
+ - [response.ok](#responseok)
+ - [response.redirected](#responseredirected)
+ - [response.type](#responsetype)
+ - [Class: Headers](#class-headers)
+ - [new Headers([init])](#new-headersinit)
+ - [Interface: Body](#interface-body)
+ - [body.body](#bodybody)
+ - [body.bodyUsed](#bodybodyused)
+ - [body.arrayBuffer()](#bodyarraybuffer)
+ - [body.blob()](#bodyblob)
+ - [body.formData()](#formdata)
+ - [body.json()](#bodyjson)
+ - [body.text()](#bodytext)
+ - [Class: FetchError](#class-fetcherror)
+ - [Class: AbortError](#class-aborterror)
+- [TypeScript](#typescript)
- [Acknowledgement](#acknowledgement)
+- [Team](#team)
+ - [Former](#former)
+- [License](#license)
@@ -53,147 +81,254 @@ A light-weight module that brings `window.fetch` to Node.js
Instead of implementing `XMLHttpRequest` in Node.js to run browser-specific [Fetch polyfill](https://github.com/github/fetch), why not go from native `http` to `fetch` API directly? Hence, `node-fetch`, minimal code for a `window.fetch` compatible API on Node.js runtime.
-See Matt Andrews' [isomorphic-fetch](https://github.com/matthew-andrews/isomorphic-fetch) or Leonardo Quixada's [cross-fetch](https://github.com/lquixada/cross-fetch) for isomorphic usage (exports `node-fetch` for server-side, `whatwg-fetch` for client-side).
+See Jason Miller's [isomorphic-unfetch](https://www.npmjs.com/package/isomorphic-unfetch) or Leonardo Quixada's [cross-fetch](https://github.com/lquixada/cross-fetch) for isomorphic usage (exports `node-fetch` for server-side, `whatwg-fetch` for client-side).
## Features
- Stay consistent with `window.fetch` API.
- Make conscious trade-off when following [WHATWG fetch spec][whatwg-fetch] and [stream spec](https://streams.spec.whatwg.org/) implementation details, document known differences.
-- Use native promise but allow substituting it with [insert your favorite promise library].
-- Use native Node streams for body on both request and response.
-- Decode content encoding (gzip/deflate) properly and convert string output (such as `res.text()` and `res.json()`) to UTF-8 automatically.
-- Useful extensions such as timeout, redirect limit, response size limit, [explicit errors](ERROR-HANDLING.md) for troubleshooting.
+- Use native promise and async functions.
+- Use native Node streams for body, on both request and response.
+- Decode content encoding (gzip/deflate/brotli) properly, and convert string output (such as `res.text()` and `res.json()`) to UTF-8 automatically.
+- Useful extensions such as redirect limit, response size limit, [explicit errors][error-handling.md] for troubleshooting.
## Difference from client-side fetch
-- See [Known Differences](LIMITS.md) for details.
+- See known differences:
+ - [As of v3.x](docs/v3-LIMITS.md)
+ - [As of v2.x](docs/v2-LIMITS.md)
- If you happen to use a missing feature that `window.fetch` offers, feel free to open an issue.
- Pull requests are welcomed too!
## Installation
-Current stable release (`2.x`)
+Current stable release (`3.x`) requires at least Node.js 12.20.0.
```sh
-$ npm install node-fetch
+npm install node-fetch
```
## Loading and configuring the module
-We suggest you load the module via `require` until the stabilization of ES modules in node:
+
+### ES Modules (ESM)
+
```js
-const fetch = require('node-fetch');
+import fetch from 'node-fetch';
```
-If you are using a Promise library other than native, set it through `fetch.Promise`:
-```js
-const Bluebird = require('bluebird');
+### CommonJS
-fetch.Promise = Bluebird;
+`node-fetch` from v3 is an ESM-only module - you are not able to import it with `require()`.
+
+If you cannot switch to ESM, please use v2 which remains compatible with CommonJS. Critical bug fixes will continue to be published for v2.
+
+```sh
+npm install node-fetch@2
```
+Alternatively, you can use the async `import()` function from CommonJS to load `node-fetch` asynchronously:
+
+```js
+// mod.cjs
+const fetch = (...args) => import('node-fetch').then(({default: fetch}) => fetch(...args));
+```
+
+### Providing global access
+
+To use `fetch()` without importing it, you can patch the `global` object in node:
+
+```js
+// fetch-polyfill.js
+import fetch, {
+ Blob,
+ blobFrom,
+ blobFromSync,
+ File,
+ fileFrom,
+ fileFromSync,
+ FormData,
+ Headers,
+ Request,
+ Response,
+} from 'node-fetch'
+
+if (!globalThis.fetch) {
+ globalThis.fetch = fetch
+ globalThis.Headers = Headers
+ globalThis.Request = Request
+ globalThis.Response = Response
+}
+
+// index.js
+import './fetch-polyfill'
+
+// ...
+```
+
+## Upgrading
+
+Using an old version of node-fetch? Check out the following files:
+
+- [2.x to 3.x upgrade guide](docs/v3-UPGRADE-GUIDE.md)
+- [1.x to 2.x upgrade guide](docs/v2-UPGRADE-GUIDE.md)
+- [Changelog](https://github.com/node-fetch/node-fetch/releases)
+
## Common Usage
-NOTE: The documentation below is up-to-date with `2.x` releases; see the [`1.x` readme](https://github.com/bitinn/node-fetch/blob/1.x/README.md), [changelog](https://github.com/bitinn/node-fetch/blob/1.x/CHANGELOG.md) and [2.x upgrade guide](UPGRADE-GUIDE.md) for the differences.
+NOTE: The documentation below is up-to-date with `3.x` releases, if you are using an older version, please check how to [upgrade](#upgrading).
-#### Plain text or HTML
-```js
-fetch('https://github.com/')
- .then(res => res.text())
- .then(body => console.log(body));
-```
-
-#### JSON
+### Plain text or HTML
```js
+import fetch from 'node-fetch';
-fetch('https://api.github.com/users/github')
- .then(res => res.json())
- .then(json => console.log(json));
+const response = await fetch('https://github.com/');
+const body = await response.text();
+
+console.log(body);
```
-#### Simple Post
-```js
-fetch('https://httpbin.org/post', { method: 'POST', body: 'a=1' })
- .then(res => res.json()) // expecting a json response
- .then(json => console.log(json));
-```
-
-#### Post with JSON
+### JSON
```js
-const body = { a: 1 };
+import fetch from 'node-fetch';
-fetch('https://httpbin.org/post', {
- method: 'post',
- body: JSON.stringify(body),
- headers: { 'Content-Type': 'application/json' },
- })
- .then(res => res.json())
- .then(json => console.log(json));
+const response = await fetch('https://api.github.com/users/github');
+const data = await response.json();
+
+console.log(data);
```
-#### Post with form parameters
-`URLSearchParams` is available in Node.js as of v7.5.0. See [official documentation](https://nodejs.org/api/url.html#url_class_urlsearchparams) for more usage methods.
+### Simple Post
+
+```js
+import fetch from 'node-fetch';
+
+const response = await fetch('https://httpbin.org/post', {method: 'POST', body: 'a=1'});
+const data = await response.json();
+
+console.log(data);
+```
+
+### Post with JSON
+
+```js
+import fetch from 'node-fetch';
+
+const body = {a: 1};
+
+const response = await fetch('https://httpbin.org/post', {
+ method: 'post',
+ body: JSON.stringify(body),
+ headers: {'Content-Type': 'application/json'}
+});
+const data = await response.json();
+
+console.log(data);
+```
+
+### Post with form parameters
+
+`URLSearchParams` is available on the global object in Node.js as of v10.0.0. See [official documentation](https://nodejs.org/api/url.html#url_class_urlsearchparams) for more usage methods.
NOTE: The `Content-Type` header is only set automatically to `x-www-form-urlencoded` when an instance of `URLSearchParams` is given as such:
```js
-const { URLSearchParams } = require('url');
+import fetch from 'node-fetch';
const params = new URLSearchParams();
params.append('a', 1);
-fetch('https://httpbin.org/post', { method: 'POST', body: params })
- .then(res => res.json())
- .then(json => console.log(json));
+const response = await fetch('https://httpbin.org/post', {method: 'POST', body: params});
+const data = await response.json();
+
+console.log(data);
```
-#### Handling exceptions
-NOTE: 3xx-5xx responses are *NOT* exceptions and should be handled in `then()`; see the next section for more information.
+### Handling exceptions
-Adding a catch to the fetch promise chain will catch *all* exceptions, such as errors originating from node core libraries, network errors and operational errors, which are instances of FetchError. See the [error handling document](ERROR-HANDLING.md) for more details.
+NOTE: 3xx-5xx responses are _NOT_ exceptions, and should be handled in `then()`, see the next section.
+
+Wrapping the fetch function into a `try/catch` block will catch _all_ exceptions, such as errors originating from node core libraries, like network errors, and operational errors which are instances of FetchError. See the [error handling document][error-handling.md] for more details.
```js
-fetch('https://domain.invalid/')
- .catch(err => console.error(err));
+import fetch from 'node-fetch';
+
+try {
+ await fetch('https://domain.invalid/');
+} catch (error) {
+ console.log(error);
+}
```
-#### Handling client and server errors
+### Handling client and server errors
+
It is common to create a helper function to check that the response contains no client (4xx) or server (5xx) error responses:
```js
-function checkStatus(res) {
- if (res.ok) { // res.status >= 200 && res.status < 300
- return res;
- } else {
- throw MyCustomError(res.statusText);
- }
+import fetch from 'node-fetch';
+
+class HTTPResponseError extends Error {
+ constructor(response) {
+ super(`HTTP Error Response: ${response.status} ${response.statusText}`);
+ this.response = response;
+ }
}
-fetch('https://httpbin.org/status/400')
- .then(checkStatus)
- .then(res => console.log('will not get here...'))
+const checkStatus = response => {
+ if (response.ok) {
+ // response.status >= 200 && response.status < 300
+ return response;
+ } else {
+ throw new HTTPResponseError(response);
+ }
+}
+
+const response = await fetch('https://httpbin.org/status/400');
+
+try {
+ checkStatus(response);
+} catch (error) {
+ console.error(error);
+
+ const errorBody = await error.response.text();
+ console.error(`Error body: ${errorBody}`);
+}
```
+### Handling cookies
+
+Cookies are not stored by default. However, cookies can be extracted and passed by manipulating request and response headers. See [Extract Set-Cookie Header](#extract-set-cookie-header) for details.
+
## Advanced Usage
-#### Streams
-The "Node.js way" is to use streams when possible:
+### Streams
+
+The "Node.js way" is to use streams when possible. You can pipe `res.body` to another stream. This example uses [stream.pipeline](https://nodejs.org/api/stream.html#stream_stream_pipeline_streams_callback) to attach stream error handlers and wait for the download to complete.
```js
-fetch('https://assets-cdn.github.com/images/modules/logos_page/Octocat.png')
- .then(res => {
- const dest = fs.createWriteStream('./octocat.png');
- res.body.pipe(dest);
- });
+import {createWriteStream} from 'node:fs';
+import {pipeline} from 'node:stream';
+import {promisify} from 'node:util'
+import fetch from 'node-fetch';
+
+const streamPipeline = promisify(pipeline);
+
+const response = await fetch('https://github.githubassets.com/images/modules/logos_page/Octocat.png');
+
+if (!response.ok) throw new Error(`unexpected response ${response.statusText}`);
+
+await streamPipeline(response.body, createWriteStream('./octocat.png'));
```
In Node.js 14 you can also use async iterators to read `body`; however, be careful to catch
errors -- the longer a response runs, the more likely it is to encounter an error.
```js
-const fetch = require('node-fetch');
+import fetch from 'node-fetch';
+
const response = await fetch('https://httpbin.org/stream/3');
+
try {
for await (const chunk of response.body) {
console.dir(JSON.parse(chunk.toString()));
@@ -208,141 +343,156 @@ did not mature until Node.js 14, so you need to do some extra work to ensure you
directly from the stream and wait on it response to fully close.
```js
-const fetch = require('node-fetch');
+import fetch from 'node-fetch';
+
const read = async body => {
- let error;
- body.on('error', err => {
- error = err;
- });
- for await (const chunk of body) {
- console.dir(JSON.parse(chunk.toString()));
- }
- return new Promise((resolve, reject) => {
- body.on('close', () => {
- error ? reject(error) : resolve();
- });
- });
+ let error;
+ body.on('error', err => {
+ error = err;
+ });
+
+ for await (const chunk of body) {
+ console.dir(JSON.parse(chunk.toString()));
+ }
+
+ return new Promise((resolve, reject) => {
+ body.on('close', () => {
+ error ? reject(error) : resolve();
+ });
+ });
};
+
try {
- const response = await fetch('https://httpbin.org/stream/3');
- await read(response.body);
+ const response = await fetch('https://httpbin.org/stream/3');
+ await read(response.body);
} catch (err) {
- console.error(err.stack);
+ console.error(err.stack);
}
```
-#### Buffer
-If you prefer to cache binary data in full, use buffer(). (NOTE: `buffer()` is a `node-fetch`-only API)
+### Accessing Headers and other Metadata
```js
-const fileType = require('file-type');
+import fetch from 'node-fetch';
-fetch('https://assets-cdn.github.com/images/modules/logos_page/Octocat.png')
- .then(res => res.buffer())
- .then(buffer => fileType(buffer))
- .then(type => { /* ... */ });
+const response = await fetch('https://github.com/');
+
+console.log(response.ok);
+console.log(response.status);
+console.log(response.statusText);
+console.log(response.headers.raw());
+console.log(response.headers.get('content-type'));
```
-#### Accessing Headers and other Meta data
-```js
-fetch('https://github.com/')
- .then(res => {
- console.log(res.ok);
- console.log(res.status);
- console.log(res.statusText);
- console.log(res.headers.raw());
- console.log(res.headers.get('content-type'));
- });
-```
-
-#### Extract Set-Cookie Header
+### Extract Set-Cookie Header
Unlike browsers, you can access raw `Set-Cookie` headers manually using `Headers.raw()`. This is a `node-fetch` only API.
```js
-fetch(url).then(res => {
- // returns an array of values, instead of a string of comma-separated values
- console.log(res.headers.raw()['set-cookie']);
-});
+import fetch from 'node-fetch';
+
+const response = await fetch('https://example.com');
+
+// Returns an array of values, instead of a string of comma-separated values
+console.log(response.headers.raw()['set-cookie']);
```
-#### Post data using a file stream
+### Post data using a file
```js
-const { createReadStream } = require('fs');
+import fetch, {
+ Blob,
+ blobFrom,
+ blobFromSync,
+ File,
+ fileFrom,
+ fileFromSync,
+} from 'node-fetch'
-const stream = createReadStream('input.txt');
+const mimetype = 'text/plain'
+const blob = fileFromSync('./input.txt', mimetype)
+const url = 'https://httpbin.org/post'
-fetch('https://httpbin.org/post', { method: 'POST', body: stream })
- .then(res => res.json())
- .then(json => console.log(json));
+const response = await fetch(url, { method: 'POST', body: blob })
+const data = await response.json()
+
+console.log(data)
```
-#### Post with form-data (detect multipart)
+node-fetch comes with a spec-compliant [FormData] implementations for posting
+multipart/form-data payloads
```js
-const FormData = require('form-data');
+import fetch, { FormData, File, fileFrom } from 'node-fetch'
-const form = new FormData();
-form.append('a', 1);
+const httpbin = 'https://httpbin.org/post'
+const formData = new FormData()
+const binary = new Uint8Array([ 97, 98, 99 ])
+const abc = new File([binary], 'abc.txt', { type: 'text/plain' })
-fetch('https://httpbin.org/post', { method: 'POST', body: form })
- .then(res => res.json())
- .then(json => console.log(json));
+formData.set('greeting', 'Hello, world!')
+formData.set('file-upload', abc, 'new name.txt')
-// OR, using custom headers
-// NOTE: getHeaders() is non-standard API
+const response = await fetch(httpbin, { method: 'POST', body: formData })
+const data = await response.json()
-const form = new FormData();
-form.append('a', 1);
-
-const options = {
- method: 'POST',
- body: form,
- headers: form.getHeaders()
-}
-
-fetch('https://httpbin.org/post', options)
- .then(res => res.json())
- .then(json => console.log(json));
+console.log(data)
```
-#### Request cancellation with AbortSignal
+If you for some reason need to post a stream coming from any arbitrary place,
+then you can append a [Blob] or a [File] look-a-like item.
-> NOTE: You may cancel streamed requests only on Node >= v8.0.0
+The minimum requirement is that it has:
+1. A `Symbol.toStringTag` getter or property that is either `Blob` or `File`
+2. A known size.
+3. And either a `stream()` method or a `arrayBuffer()` method that returns a ArrayBuffer.
+
+The `stream()` must return any async iterable object as long as it yields Uint8Array (or Buffer)
+so Node.Readable streams and whatwg streams works just fine.
+
+```js
+formData.append('upload', {
+ [Symbol.toStringTag]: 'Blob',
+ size: 3,
+ *stream() {
+ yield new Uint8Array([97, 98, 99])
+ },
+ arrayBuffer() {
+ return new Uint8Array([97, 98, 99]).buffer
+ }
+}, 'abc.txt')
+```
+
+### Request cancellation with AbortSignal
You may cancel requests with `AbortController`. A suggested implementation is [`abort-controller`](https://www.npmjs.com/package/abort-controller).
An example of timing out a request after 150ms could be achieved as the following:
```js
-import AbortController from 'abort-controller';
+import fetch, { AbortError } from 'node-fetch';
+
+// AbortController was added in node v14.17.0 globally
+const AbortController = globalThis.AbortController || await import('abort-controller')
const controller = new AbortController();
-const timeout = setTimeout(
- () => { controller.abort(); },
- 150,
-);
+const timeout = setTimeout(() => {
+ controller.abort();
+}, 150);
-fetch(url, { signal: controller.signal })
- .then(res => res.json())
- .then(
- data => {
- useData(data)
- },
- err => {
- if (err.name === 'AbortError') {
- // request was aborted
- }
- },
- )
- .finally(() => {
- clearTimeout(timeout);
- });
+try {
+ const response = await fetch('https://example.com', {signal: controller.signal});
+ const data = await response.json();
+} catch (error) {
+ if (error instanceof AbortError) {
+ console.log('request was aborted');
+ }
+} finally {
+ clearTimeout(timeout);
+}
```
-See [test cases](https://github.com/bitinn/node-fetch/blob/master/test/test.js) for more examples.
-
+See [test cases](https://github.com/node-fetch/node-fetch/blob/master/test/) for more examples.
## API
@@ -354,46 +504,50 @@ See [test cases](https://github.com/bitinn/node-fetch/blob/master/test/test.js)
Perform an HTTP(S) fetch.
-`url` should be an absolute url, such as `https://example.com/`. A path-relative URL (`/file/under/root`) or protocol-relative URL (`//can-be-http-or-https.com/`) will result in a rejected `Promise`.
+`url` should be an absolute URL, such as `https://example.com/`. A path-relative URL (`/file/under/root`) or protocol-relative URL (`//can-be-http-or-https.com/`) will result in a rejected `Promise`.
+
### Options
The default values are shown after each option key.
```js
{
- // These properties are part of the Fetch Standard
- method: 'GET',
- headers: {}, // request headers. format is the identical to that accepted by the Headers constructor (see below)
- body: null, // request body. can be null, a string, a Buffer, a Blob, or a Node.js Readable stream
- redirect: 'follow', // set to `manual` to extract redirect headers, `error` to reject redirect
- signal: null, // pass an instance of AbortSignal to optionally abort requests
+ // These properties are part of the Fetch Standard
+ method: 'GET',
+ headers: {}, // Request headers. format is the identical to that accepted by the Headers constructor (see below)
+ body: null, // Request body. can be null, or a Node.js Readable stream
+ redirect: 'follow', // Set to `manual` to extract redirect headers, `error` to reject redirect
+ signal: null, // Pass an instance of AbortSignal to optionally abort requests
- // The following properties are node-fetch extensions
- follow: 20, // maximum redirect count. 0 to not follow redirect
- timeout: 0, // req/res timeout in ms, it resets on redirect. 0 to disable (OS limit applies). Signal is recommended instead.
- compress: true, // support gzip/deflate content encoding. false to disable
- size: 0, // maximum response body size in bytes. 0 to disable
- agent: null // http(s).Agent instance or function that returns an instance (see below)
+ // The following properties are node-fetch extensions
+ follow: 20, // maximum redirect count. 0 to not follow redirect
+ compress: true, // support gzip/deflate content encoding. false to disable
+ size: 0, // maximum response body size in bytes. 0 to disable
+ agent: null, // http(s).Agent instance or function that returns an instance (see below)
+ highWaterMark: 16384, // the maximum number of bytes to store in the internal buffer before ceasing to read from the underlying resource.
+ insecureHTTPParser: false // Use an insecure HTTP parser that accepts invalid HTTP headers when `true`.
}
```
-##### Default Headers
+#### Default Headers
If no values are set, the following request headers will be sent automatically:
-Header | Value
-------------------- | --------------------------------------------------------
-`Accept-Encoding` | `gzip,deflate` _(when `options.compress === true`)_
-`Accept` | `*/*`
-`Content-Length` | _(automatically calculated, if possible)_
-`Transfer-Encoding` | `chunked` _(when `req.body` is a stream)_
-`User-Agent` | `node-fetch/1.0 (+https://github.com/bitinn/node-fetch)`
+| Header | Value |
+| ------------------- | ------------------------------------------------------ |
+| `Accept-Encoding` | `gzip, deflate, br` (when `options.compress === true`) |
+| `Accept` | `*/*` |
+| `Content-Length` | _(automatically calculated, if possible)_ |
+| `Host` | _(host and port information from the target URI)_ |
+| `Transfer-Encoding` | `chunked` _(when `req.body` is a stream)_ |
+| `User-Agent` | `node-fetch` |
+
Note: when `body` is a `Stream`, `Content-Length` is not set automatically.
-##### Custom Agent
+#### Custom Agent
The `agent` option allows you to specify networking related options which are out of the scope of Fetch, including and not limited to the following:
@@ -408,25 +562,85 @@ If no agent is specified, the default agent provided by Node.js is used. Note th
In addition, the `agent` option accepts a function that returns `http`(s)`.Agent` instance given current [URL](https://nodejs.org/api/url.html), this is useful during a redirection chain across HTTP and HTTPS protocol.
```js
+import http from 'node:http';
+import https from 'node:https';
+
const httpAgent = new http.Agent({
- keepAlive: true
+ keepAlive: true
});
const httpsAgent = new https.Agent({
- keepAlive: true
+ keepAlive: true
});
const options = {
- agent: function (_parsedURL) {
- if (_parsedURL.protocol == 'http:') {
- return httpAgent;
- } else {
- return httpsAgent;
- }
- }
+ agent: function(_parsedURL) {
+ if (_parsedURL.protocol == 'http:') {
+ return httpAgent;
+ } else {
+ return httpsAgent;
+ }
+ }
+};
+```
+
+
+
+#### Custom highWaterMark
+
+Stream on Node.js have a smaller internal buffer size (16kB, aka `highWaterMark`) from client-side browsers (>1MB, not consistent across browsers). Because of that, when you are writing an isomorphic app and using `res.clone()`, it will hang with large response in Node.
+
+The recommended way to fix this problem is to resolve cloned response in parallel:
+
+```js
+import fetch from 'node-fetch';
+
+const response = await fetch('https://example.com');
+const r1 = response.clone();
+
+const results = await Promise.all([response.json(), r1.text()]);
+
+console.log(results[0]);
+console.log(results[1]);
+```
+
+If for some reason you don't like the solution above, since `3.x` you are able to modify the `highWaterMark` option:
+
+```js
+import fetch from 'node-fetch';
+
+const response = await fetch('https://example.com', {
+ // About 1MB
+ highWaterMark: 1024 * 1024
+});
+
+const result = await res.clone().arrayBuffer();
+console.dir(result);
+```
+
+#### Insecure HTTP Parser
+
+Passed through to the `insecureHTTPParser` option on http(s).request. See [`http.request`](https://nodejs.org/api/http.html#http_http_request_url_options_callback) for more information.
+
+#### Manual Redirect
+
+The `redirect: 'manual'` option for node-fetch is different from the browser & specification, which
+results in an [opaque-redirect filtered response](https://fetch.spec.whatwg.org/#concept-filtered-response-opaque-redirect).
+node-fetch gives you the typical [basic filtered response](https://fetch.spec.whatwg.org/#concept-filtered-response-basic) instead.
+
+```js
+import fetch from 'node-fetch';
+
+const response = await fetch('https://httpbin.org/status/301', { redirect: 'manual' });
+
+if (response.status === 301 || response.status === 302) {
+ const locationURL = new URL(response.headers.get('location'), response.url);
+ const response2 = await fetch(locationURL, { redirect: 'manual' });
+ console.dir(response2);
}
```
+
### Class: Request
An HTTP(S) request containing information about URL, method, headers, and the body. This class implements the [Body](#iface-body) interface.
@@ -435,8 +649,6 @@ Due to the nature of Node.js, the following properties are not implemented at th
- `type`
- `destination`
-- `referrer`
-- `referrerPolicy`
- `mode`
- `credentials`
- `cache`
@@ -449,35 +661,34 @@ The following node-fetch extension properties are provided:
- `compress`
- `counter`
- `agent`
+- `highWaterMark`
See [options](#fetch-options) for exact meaning of these extensions.
#### new Request(input[, options])
-*(spec-compliant)*
+_(spec-compliant)_
- `input` A string representing a URL, or another `Request` (which will be cloned)
-- `options` [Options][#fetch-options] for the HTTP(S) request
+- `options` [Options](#fetch-options) for the HTTP(S) request
Constructs a new `Request` object. The constructor is identical to that in the [browser](https://developer.mozilla.org/en-US/docs/Web/API/Request/Request).
In most cases, directly `fetch(url, options)` is simpler than creating a `Request` object.
+
### Class: Response
An HTTP(S) response. This class implements the [Body](#iface-body) interface.
The following properties are not implemented in node-fetch at this moment:
-- `Response.error()`
-- `Response.redirect()`
-- `type`
- `trailer`
#### new Response([body[, options]])
-*(spec-compliant)*
+_(spec-compliant)_
- `body` A `String` or [`Readable` stream][node-readable]
- `options` A [`ResponseInit`][response-init] options dictionary
@@ -488,24 +699,31 @@ Because Node.js does not implement service workers (for which this class was des
#### response.ok
-*(spec-compliant)*
+_(spec-compliant)_
Convenience property representing if the request ended normally. Will evaluate to true if the response status was greater than or equal to 200 but smaller than 300.
#### response.redirected
-*(spec-compliant)*
+_(spec-compliant)_
Convenience property representing if the request has been redirected at least once. Will evaluate to true if the internal redirect counter is greater than 0.
+#### response.type
+
+_(deviation from spec)_
+
+Convenience property representing the response's type. node-fetch only supports `'default'` and `'error'` and does not make use of [filtered responses](https://fetch.spec.whatwg.org/#concept-filtered-response).
+
+
### Class: Headers
This class allows manipulating and iterating over a set of HTTP headers. All methods specified in the [Fetch Standard][whatwg-fetch] are implemented.
#### new Headers([init])
-*(spec-compliant)*
+_(spec-compliant)_
- `init` Optional argument to pre-fill the `Headers` object
@@ -513,122 +731,142 @@ Construct a new `Headers` object. `init` can be either `null`, a `Headers` objec
```js
// Example adapted from https://fetch.spec.whatwg.org/#example-headers-class
+import {Headers} from 'node-fetch';
const meta = {
- 'Content-Type': 'text/xml',
- 'Breaking-Bad': '<3'
+ 'Content-Type': 'text/xml'
};
const headers = new Headers(meta);
// The above is equivalent to
-const meta = [
- [ 'Content-Type', 'text/xml' ],
- [ 'Breaking-Bad', '<3' ]
-];
+const meta = [['Content-Type', 'text/xml']];
const headers = new Headers(meta);
// You can in fact use any iterable objects, like a Map or even another Headers
const meta = new Map();
meta.set('Content-Type', 'text/xml');
-meta.set('Breaking-Bad', '<3');
const headers = new Headers(meta);
const copyOfHeaders = new Headers(headers);
```
+
### Interface: Body
`Body` is an abstract interface with methods that are applicable to both `Request` and `Response` classes.
-The following methods are not yet implemented in node-fetch at this moment:
-
-- `formData()`
-
#### body.body
-*(deviation from spec)*
+_(deviation from spec)_
-* Node.js [`Readable` stream][node-readable]
+- Node.js [`Readable` stream][node-readable]
Data are encapsulated in the `Body` object. Note that while the [Fetch Standard][whatwg-fetch] requires the property to always be a WHATWG `ReadableStream`, in node-fetch it is a Node.js [`Readable` stream][node-readable].
#### body.bodyUsed
-*(spec-compliant)*
+_(spec-compliant)_
-* `Boolean`
+- `Boolean`
A boolean property for if this body has been consumed. Per the specs, a consumed body cannot be used again.
#### body.arrayBuffer()
+
+#### body.formData()
+
#### body.blob()
+
#### body.json()
+
#### body.text()
-*(spec-compliant)*
+`fetch` comes with methods to parse `multipart/form-data` payloads as well as
+`x-www-form-urlencoded` bodies using `.formData()` this comes from the idea that
+Service Worker can intercept such messages before it's sent to the server to
+alter them. This is useful for anybody building a server so you can use it to
+parse & consume payloads.
-* Returns: Promise
+
+Code example
-Consume the body and return a promise that will resolve to one of these formats.
+```js
+import http from 'node:http'
+import { Response } from 'node-fetch'
-#### body.buffer()
+http.createServer(async function (req, res) {
+ const formData = await new Response(req, {
+ headers: req.headers // Pass along the boundary value
+ }).formData()
+ const allFields = [...formData]
-*(node-fetch extension)*
+ const file = formData.get('uploaded-files')
+ const arrayBuffer = await file.arrayBuffer()
+ const text = await file.text()
+ const whatwgReadableStream = file.stream()
-* Returns: Promise<Buffer>
+ // other was to consume the request could be to do:
+ const json = await new Response(req).json()
+ const text = await new Response(req).text()
+ const arrayBuffer = await new Response(req).arrayBuffer()
+ const blob = await new Response(req, {
+ headers: req.headers // So that `type` inherits `Content-Type`
+ }.blob()
+})
+```
-Consume the body and return a promise that will resolve to a Buffer.
-
-#### body.textConverted()
-
-*(node-fetch extension)*
-
-* Returns: Promise<String>
-
-Identical to `body.text()`, except instead of always converting to UTF-8, encoding sniffing will be performed and text converted to UTF-8 if possible.
-
-(This API requires an optional dependency of the npm package [encoding](https://www.npmjs.com/package/encoding), which you need to install manually. `webpack` users may see [a warning message](https://github.com/bitinn/node-fetch/issues/412#issuecomment-379007792) due to this optional dependency.)
+
+
### Class: FetchError
-*(node-fetch extension)*
+_(node-fetch extension)_
An operational error in the fetching process. See [ERROR-HANDLING.md][] for more info.
+
### Class: AbortError
-*(node-fetch extension)*
+_(node-fetch extension)_
An Error thrown when the request is aborted in response to an `AbortSignal`'s `abort` event. It has a `name` property of `AbortError`. See [ERROR-HANDLING.MD][] for more info.
+## TypeScript
+
+**Since `3.x` types are bundled with `node-fetch`, so you don't need to install any additional packages.**
+
+For older versions please use the type definitions from [DefinitelyTyped](https://github.com/DefinitelyTyped/DefinitelyTyped):
+
+```sh
+npm install --save-dev @types/node-fetch@2.x
+```
+
## Acknowledgement
Thanks to [github/fetch](https://github.com/github/fetch) for providing a solid implementation reference.
-`node-fetch` v1 was maintained by [@bitinn](https://github.com/bitinn); v2 was maintained by [@TimothyGu](https://github.com/timothygu), [@bitinn](https://github.com/bitinn) and [@jimmywarting](https://github.com/jimmywarting); v2 readme is written by [@jkantr](https://github.com/jkantr).
+## Team
+
+| [](https://github.com/bitinn) | [](https://github.com/jimmywarting) | [](https://github.com/xxczaki) | [](https://github.com/Richienb) | [](https://github.com/gr2m) |
+| ----------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------- |
+| [David Frank](https://bitinn.net/) | [Jimmy Wärting](https://jimmy.warting.se/) | [Antoni Kepinski](https://kepinski.ch) | [Richie Bendall](https://www.richie-bendall.ml/) | [Gregor Martynus](https://twitter.com/gr2m) |
+
+###### Former
+
+- [Timothy Gu](https://github.com/timothygu)
+- [Jared Kantrowitz](https://github.com/jkantr)
## License
-MIT
+[MIT](LICENSE.md)
-[npm-image]: https://flat.badgen.net/npm/v/node-fetch
-[npm-url]: https://www.npmjs.com/package/node-fetch
-[travis-image]: https://flat.badgen.net/travis/bitinn/node-fetch
-[travis-url]: https://travis-ci.org/bitinn/node-fetch
-[codecov-image]: https://flat.badgen.net/codecov/c/github/bitinn/node-fetch/master
-[codecov-url]: https://codecov.io/gh/bitinn/node-fetch
-[install-size-image]: https://flat.badgen.net/packagephobia/install/node-fetch
-[install-size-url]: https://packagephobia.now.sh/result?p=node-fetch
-[discord-image]: https://img.shields.io/discord/619915844268326952?color=%237289DA&label=Discord&style=flat-square
-[discord-url]: https://discord.gg/Zxbndcm
-[opencollective-image]: https://opencollective.com/node-fetch/backers.svg
-[opencollective-url]: https://opencollective.com/node-fetch
[whatwg-fetch]: https://fetch.spec.whatwg.org/
[response-init]: https://fetch.spec.whatwg.org/#responseinit
[node-readable]: https://nodejs.org/api/stream.html#stream_readable_streams
[mdn-headers]: https://developer.mozilla.org/en-US/docs/Web/API/Headers
-[LIMITS.md]: https://github.com/bitinn/node-fetch/blob/master/LIMITS.md
-[ERROR-HANDLING.md]: https://github.com/bitinn/node-fetch/blob/master/ERROR-HANDLING.md
-[UPGRADE-GUIDE.md]: https://github.com/bitinn/node-fetch/blob/master/UPGRADE-GUIDE.md
+[error-handling.md]: https://github.com/node-fetch/node-fetch/blob/master/docs/ERROR-HANDLING.md
+[FormData]: https://developer.mozilla.org/en-US/docs/Web/API/FormData
+[Blob]: https://developer.mozilla.org/en-US/docs/Web/API/Blob
+[File]: https://developer.mozilla.org/en-US/docs/Web/API/File
diff --git a/backend/node_modules/node-fetch/browser.js b/backend/node_modules/node-fetch/browser.js
deleted file mode 100644
index ee86265..0000000
--- a/backend/node_modules/node-fetch/browser.js
+++ /dev/null
@@ -1,25 +0,0 @@
-"use strict";
-
-// ref: https://github.com/tc39/proposal-global
-var getGlobal = function () {
- // the only reliable means to get the global object is
- // `Function('return this')()`
- // However, this causes CSP violations in Chrome apps.
- if (typeof self !== 'undefined') { return self; }
- if (typeof window !== 'undefined') { return window; }
- if (typeof global !== 'undefined') { return global; }
- throw new Error('unable to locate global object');
-}
-
-var globalObject = getGlobal();
-
-module.exports = exports = globalObject.fetch;
-
-// Needed for TypeScript and Webpack.
-if (globalObject.fetch) {
- exports.default = globalObject.fetch.bind(globalObject);
-}
-
-exports.Headers = globalObject.Headers;
-exports.Request = globalObject.Request;
-exports.Response = globalObject.Response;
diff --git a/backend/node_modules/node-fetch/lib/index.es.js b/backend/node_modules/node-fetch/lib/index.es.js
deleted file mode 100644
index aae9799..0000000
--- a/backend/node_modules/node-fetch/lib/index.es.js
+++ /dev/null
@@ -1,1777 +0,0 @@
-process.emitWarning("The .es.js file is deprecated. Use .mjs instead.");
-
-import Stream from 'stream';
-import http from 'http';
-import Url from 'url';
-import whatwgUrl from 'whatwg-url';
-import https from 'https';
-import zlib from 'zlib';
-
-// Based on https://github.com/tmpvar/jsdom/blob/aa85b2abf07766ff7bf5c1f6daafb3726f2f2db5/lib/jsdom/living/blob.js
-
-// fix for "Readable" isn't a named export issue
-const Readable = Stream.Readable;
-
-const BUFFER = Symbol('buffer');
-const TYPE = Symbol('type');
-
-class Blob {
- constructor() {
- this[TYPE] = '';
-
- const blobParts = arguments[0];
- const options = arguments[1];
-
- const buffers = [];
- let size = 0;
-
- if (blobParts) {
- const a = blobParts;
- const length = Number(a.length);
- for (let i = 0; i < length; i++) {
- const element = a[i];
- let buffer;
- if (element instanceof Buffer) {
- buffer = element;
- } else if (ArrayBuffer.isView(element)) {
- buffer = Buffer.from(element.buffer, element.byteOffset, element.byteLength);
- } else if (element instanceof ArrayBuffer) {
- buffer = Buffer.from(element);
- } else if (element instanceof Blob) {
- buffer = element[BUFFER];
- } else {
- buffer = Buffer.from(typeof element === 'string' ? element : String(element));
- }
- size += buffer.length;
- buffers.push(buffer);
- }
- }
-
- this[BUFFER] = Buffer.concat(buffers);
-
- let type = options && options.type !== undefined && String(options.type).toLowerCase();
- if (type && !/[^\u0020-\u007E]/.test(type)) {
- this[TYPE] = type;
- }
- }
- get size() {
- return this[BUFFER].length;
- }
- get type() {
- return this[TYPE];
- }
- text() {
- return Promise.resolve(this[BUFFER].toString());
- }
- arrayBuffer() {
- const buf = this[BUFFER];
- const ab = buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
- return Promise.resolve(ab);
- }
- stream() {
- const readable = new Readable();
- readable._read = function () {};
- readable.push(this[BUFFER]);
- readable.push(null);
- return readable;
- }
- toString() {
- return '[object Blob]';
- }
- slice() {
- const size = this.size;
-
- const start = arguments[0];
- const end = arguments[1];
- let relativeStart, relativeEnd;
- if (start === undefined) {
- relativeStart = 0;
- } else if (start < 0) {
- relativeStart = Math.max(size + start, 0);
- } else {
- relativeStart = Math.min(start, size);
- }
- if (end === undefined) {
- relativeEnd = size;
- } else if (end < 0) {
- relativeEnd = Math.max(size + end, 0);
- } else {
- relativeEnd = Math.min(end, size);
- }
- const span = Math.max(relativeEnd - relativeStart, 0);
-
- const buffer = this[BUFFER];
- const slicedBuffer = buffer.slice(relativeStart, relativeStart + span);
- const blob = new Blob([], { type: arguments[2] });
- blob[BUFFER] = slicedBuffer;
- return blob;
- }
-}
-
-Object.defineProperties(Blob.prototype, {
- size: { enumerable: true },
- type: { enumerable: true },
- slice: { enumerable: true }
-});
-
-Object.defineProperty(Blob.prototype, Symbol.toStringTag, {
- value: 'Blob',
- writable: false,
- enumerable: false,
- configurable: true
-});
-
-/**
- * fetch-error.js
- *
- * FetchError interface for operational errors
- */
-
-/**
- * Create FetchError instance
- *
- * @param String message Error message for human
- * @param String type Error type for machine
- * @param String systemError For Node.js system error
- * @return FetchError
- */
-function FetchError(message, type, systemError) {
- Error.call(this, message);
-
- this.message = message;
- this.type = type;
-
- // when err.type is `system`, err.code contains system error code
- if (systemError) {
- this.code = this.errno = systemError.code;
- }
-
- // hide custom error implementation details from end-users
- Error.captureStackTrace(this, this.constructor);
-}
-
-FetchError.prototype = Object.create(Error.prototype);
-FetchError.prototype.constructor = FetchError;
-FetchError.prototype.name = 'FetchError';
-
-let convert;
-try {
- convert = require('encoding').convert;
-} catch (e) {}
-
-const INTERNALS = Symbol('Body internals');
-
-// fix an issue where "PassThrough" isn't a named export for node <10
-const PassThrough = Stream.PassThrough;
-
-/**
- * Body mixin
- *
- * Ref: https://fetch.spec.whatwg.org/#body
- *
- * @param Stream body Readable stream
- * @param Object opts Response options
- * @return Void
- */
-function Body(body) {
- var _this = this;
-
- var _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
- _ref$size = _ref.size;
-
- let size = _ref$size === undefined ? 0 : _ref$size;
- var _ref$timeout = _ref.timeout;
- let timeout = _ref$timeout === undefined ? 0 : _ref$timeout;
-
- if (body == null) {
- // body is undefined or null
- body = null;
- } else if (isURLSearchParams(body)) {
- // body is a URLSearchParams
- body = Buffer.from(body.toString());
- } else if (isBlob(body)) ; else if (Buffer.isBuffer(body)) ; else if (Object.prototype.toString.call(body) === '[object ArrayBuffer]') {
- // body is ArrayBuffer
- body = Buffer.from(body);
- } else if (ArrayBuffer.isView(body)) {
- // body is ArrayBufferView
- body = Buffer.from(body.buffer, body.byteOffset, body.byteLength);
- } else if (body instanceof Stream) ; else {
- // none of the above
- // coerce to string then buffer
- body = Buffer.from(String(body));
- }
- this[INTERNALS] = {
- body,
- disturbed: false,
- error: null
- };
- this.size = size;
- this.timeout = timeout;
-
- if (body instanceof Stream) {
- body.on('error', function (err) {
- const error = err.name === 'AbortError' ? err : new FetchError(`Invalid response body while trying to fetch ${_this.url}: ${err.message}`, 'system', err);
- _this[INTERNALS].error = error;
- });
- }
-}
-
-Body.prototype = {
- get body() {
- return this[INTERNALS].body;
- },
-
- get bodyUsed() {
- return this[INTERNALS].disturbed;
- },
-
- /**
- * Decode response as ArrayBuffer
- *
- * @return Promise
- */
- arrayBuffer() {
- return consumeBody.call(this).then(function (buf) {
- return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
- });
- },
-
- /**
- * Return raw response as Blob
- *
- * @return Promise
- */
- blob() {
- let ct = this.headers && this.headers.get('content-type') || '';
- return consumeBody.call(this).then(function (buf) {
- return Object.assign(
- // Prevent copying
- new Blob([], {
- type: ct.toLowerCase()
- }), {
- [BUFFER]: buf
- });
- });
- },
-
- /**
- * Decode response as json
- *
- * @return Promise
- */
- json() {
- var _this2 = this;
-
- return consumeBody.call(this).then(function (buffer) {
- try {
- return JSON.parse(buffer.toString());
- } catch (err) {
- return Body.Promise.reject(new FetchError(`invalid json response body at ${_this2.url} reason: ${err.message}`, 'invalid-json'));
- }
- });
- },
-
- /**
- * Decode response as text
- *
- * @return Promise
- */
- text() {
- return consumeBody.call(this).then(function (buffer) {
- return buffer.toString();
- });
- },
-
- /**
- * Decode response as buffer (non-spec api)
- *
- * @return Promise
- */
- buffer() {
- return consumeBody.call(this);
- },
-
- /**
- * Decode response as text, while automatically detecting the encoding and
- * trying to decode to UTF-8 (non-spec api)
- *
- * @return Promise
- */
- textConverted() {
- var _this3 = this;
-
- return consumeBody.call(this).then(function (buffer) {
- return convertBody(buffer, _this3.headers);
- });
- }
-};
-
-// In browsers, all properties are enumerable.
-Object.defineProperties(Body.prototype, {
- body: { enumerable: true },
- bodyUsed: { enumerable: true },
- arrayBuffer: { enumerable: true },
- blob: { enumerable: true },
- json: { enumerable: true },
- text: { enumerable: true }
-});
-
-Body.mixIn = function (proto) {
- for (const name of Object.getOwnPropertyNames(Body.prototype)) {
- // istanbul ignore else: future proof
- if (!(name in proto)) {
- const desc = Object.getOwnPropertyDescriptor(Body.prototype, name);
- Object.defineProperty(proto, name, desc);
- }
- }
-};
-
-/**
- * Consume and convert an entire Body to a Buffer.
- *
- * Ref: https://fetch.spec.whatwg.org/#concept-body-consume-body
- *
- * @return Promise
- */
-function consumeBody() {
- var _this4 = this;
-
- if (this[INTERNALS].disturbed) {
- return Body.Promise.reject(new TypeError(`body used already for: ${this.url}`));
- }
-
- this[INTERNALS].disturbed = true;
-
- if (this[INTERNALS].error) {
- return Body.Promise.reject(this[INTERNALS].error);
- }
-
- let body = this.body;
-
- // body is null
- if (body === null) {
- return Body.Promise.resolve(Buffer.alloc(0));
- }
-
- // body is blob
- if (isBlob(body)) {
- body = body.stream();
- }
-
- // body is buffer
- if (Buffer.isBuffer(body)) {
- return Body.Promise.resolve(body);
- }
-
- // istanbul ignore if: should never happen
- if (!(body instanceof Stream)) {
- return Body.Promise.resolve(Buffer.alloc(0));
- }
-
- // body is stream
- // get ready to actually consume the body
- let accum = [];
- let accumBytes = 0;
- let abort = false;
-
- return new Body.Promise(function (resolve, reject) {
- let resTimeout;
-
- // allow timeout on slow response body
- if (_this4.timeout) {
- resTimeout = setTimeout(function () {
- abort = true;
- reject(new FetchError(`Response timeout while trying to fetch ${_this4.url} (over ${_this4.timeout}ms)`, 'body-timeout'));
- }, _this4.timeout);
- }
-
- // handle stream errors
- body.on('error', function (err) {
- if (err.name === 'AbortError') {
- // if the request was aborted, reject with this Error
- abort = true;
- reject(err);
- } else {
- // other errors, such as incorrect content-encoding
- reject(new FetchError(`Invalid response body while trying to fetch ${_this4.url}: ${err.message}`, 'system', err));
- }
- });
-
- body.on('data', function (chunk) {
- if (abort || chunk === null) {
- return;
- }
-
- if (_this4.size && accumBytes + chunk.length > _this4.size) {
- abort = true;
- reject(new FetchError(`content size at ${_this4.url} over limit: ${_this4.size}`, 'max-size'));
- return;
- }
-
- accumBytes += chunk.length;
- accum.push(chunk);
- });
-
- body.on('end', function () {
- if (abort) {
- return;
- }
-
- clearTimeout(resTimeout);
-
- try {
- resolve(Buffer.concat(accum, accumBytes));
- } catch (err) {
- // handle streams that have accumulated too much data (issue #414)
- reject(new FetchError(`Could not create Buffer from response body for ${_this4.url}: ${err.message}`, 'system', err));
- }
- });
- });
-}
-
-/**
- * Detect buffer encoding and convert to target encoding
- * ref: http://www.w3.org/TR/2011/WD-html5-20110113/parsing.html#determining-the-character-encoding
- *
- * @param Buffer buffer Incoming buffer
- * @param String encoding Target encoding
- * @return String
- */
-function convertBody(buffer, headers) {
- if (typeof convert !== 'function') {
- throw new Error('The package `encoding` must be installed to use the textConverted() function');
- }
-
- const ct = headers.get('content-type');
- let charset = 'utf-8';
- let res, str;
-
- // header
- if (ct) {
- res = /charset=([^;]*)/i.exec(ct);
- }
-
- // no charset in content type, peek at response body for at most 1024 bytes
- str = buffer.slice(0, 1024).toString();
-
- // html5
- if (!res && str) {
- res = / 0 && arguments[0] !== undefined ? arguments[0] : undefined;
-
- this[MAP] = Object.create(null);
-
- if (init instanceof Headers) {
- const rawHeaders = init.raw();
- const headerNames = Object.keys(rawHeaders);
-
- for (const headerName of headerNames) {
- for (const value of rawHeaders[headerName]) {
- this.append(headerName, value);
- }
- }
-
- return;
- }
-
- // We don't worry about converting prop to ByteString here as append()
- // will handle it.
- if (init == null) ; else if (typeof init === 'object') {
- const method = init[Symbol.iterator];
- if (method != null) {
- if (typeof method !== 'function') {
- throw new TypeError('Header pairs must be iterable');
- }
-
- // sequence>
- // Note: per spec we have to first exhaust the lists then process them
- const pairs = [];
- for (const pair of init) {
- if (typeof pair !== 'object' || typeof pair[Symbol.iterator] !== 'function') {
- throw new TypeError('Each header pair must be iterable');
- }
- pairs.push(Array.from(pair));
- }
-
- for (const pair of pairs) {
- if (pair.length !== 2) {
- throw new TypeError('Each header pair must be a name/value tuple');
- }
- this.append(pair[0], pair[1]);
- }
- } else {
- // record
- for (const key of Object.keys(init)) {
- const value = init[key];
- this.append(key, value);
- }
- }
- } else {
- throw new TypeError('Provided initializer must be an object');
- }
- }
-
- /**
- * Return combined header value given name
- *
- * @param String name Header name
- * @return Mixed
- */
- get(name) {
- name = `${name}`;
- validateName(name);
- const key = find(this[MAP], name);
- if (key === undefined) {
- return null;
- }
-
- return this[MAP][key].join(', ');
- }
-
- /**
- * Iterate over all headers
- *
- * @param Function callback Executed for each item with parameters (value, name, thisArg)
- * @param Boolean thisArg `this` context for callback function
- * @return Void
- */
- forEach(callback) {
- let thisArg = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : undefined;
-
- let pairs = getHeaders(this);
- let i = 0;
- while (i < pairs.length) {
- var _pairs$i = pairs[i];
- const name = _pairs$i[0],
- value = _pairs$i[1];
-
- callback.call(thisArg, value, name, this);
- pairs = getHeaders(this);
- i++;
- }
- }
-
- /**
- * Overwrite header values given name
- *
- * @param String name Header name
- * @param String value Header value
- * @return Void
- */
- set(name, value) {
- name = `${name}`;
- value = `${value}`;
- validateName(name);
- validateValue(value);
- const key = find(this[MAP], name);
- this[MAP][key !== undefined ? key : name] = [value];
- }
-
- /**
- * Append a value onto existing header
- *
- * @param String name Header name
- * @param String value Header value
- * @return Void
- */
- append(name, value) {
- name = `${name}`;
- value = `${value}`;
- validateName(name);
- validateValue(value);
- const key = find(this[MAP], name);
- if (key !== undefined) {
- this[MAP][key].push(value);
- } else {
- this[MAP][name] = [value];
- }
- }
-
- /**
- * Check for header name existence
- *
- * @param String name Header name
- * @return Boolean
- */
- has(name) {
- name = `${name}`;
- validateName(name);
- return find(this[MAP], name) !== undefined;
- }
-
- /**
- * Delete all header values given name
- *
- * @param String name Header name
- * @return Void
- */
- delete(name) {
- name = `${name}`;
- validateName(name);
- const key = find(this[MAP], name);
- if (key !== undefined) {
- delete this[MAP][key];
- }
- }
-
- /**
- * Return raw headers (non-spec api)
- *
- * @return Object
- */
- raw() {
- return this[MAP];
- }
-
- /**
- * Get an iterator on keys.
- *
- * @return Iterator
- */
- keys() {
- return createHeadersIterator(this, 'key');
- }
-
- /**
- * Get an iterator on values.
- *
- * @return Iterator
- */
- values() {
- return createHeadersIterator(this, 'value');
- }
-
- /**
- * Get an iterator on entries.
- *
- * This is the default iterator of the Headers object.
- *
- * @return Iterator
- */
- [Symbol.iterator]() {
- return createHeadersIterator(this, 'key+value');
- }
-}
-Headers.prototype.entries = Headers.prototype[Symbol.iterator];
-
-Object.defineProperty(Headers.prototype, Symbol.toStringTag, {
- value: 'Headers',
- writable: false,
- enumerable: false,
- configurable: true
-});
-
-Object.defineProperties(Headers.prototype, {
- get: { enumerable: true },
- forEach: { enumerable: true },
- set: { enumerable: true },
- append: { enumerable: true },
- has: { enumerable: true },
- delete: { enumerable: true },
- keys: { enumerable: true },
- values: { enumerable: true },
- entries: { enumerable: true }
-});
-
-function getHeaders(headers) {
- let kind = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'key+value';
-
- const keys = Object.keys(headers[MAP]).sort();
- return keys.map(kind === 'key' ? function (k) {
- return k.toLowerCase();
- } : kind === 'value' ? function (k) {
- return headers[MAP][k].join(', ');
- } : function (k) {
- return [k.toLowerCase(), headers[MAP][k].join(', ')];
- });
-}
-
-const INTERNAL = Symbol('internal');
-
-function createHeadersIterator(target, kind) {
- const iterator = Object.create(HeadersIteratorPrototype);
- iterator[INTERNAL] = {
- target,
- kind,
- index: 0
- };
- return iterator;
-}
-
-const HeadersIteratorPrototype = Object.setPrototypeOf({
- next() {
- // istanbul ignore if
- if (!this || Object.getPrototypeOf(this) !== HeadersIteratorPrototype) {
- throw new TypeError('Value of `this` is not a HeadersIterator');
- }
-
- var _INTERNAL = this[INTERNAL];
- const target = _INTERNAL.target,
- kind = _INTERNAL.kind,
- index = _INTERNAL.index;
-
- const values = getHeaders(target, kind);
- const len = values.length;
- if (index >= len) {
- return {
- value: undefined,
- done: true
- };
- }
-
- this[INTERNAL].index = index + 1;
-
- return {
- value: values[index],
- done: false
- };
- }
-}, Object.getPrototypeOf(Object.getPrototypeOf([][Symbol.iterator]())));
-
-Object.defineProperty(HeadersIteratorPrototype, Symbol.toStringTag, {
- value: 'HeadersIterator',
- writable: false,
- enumerable: false,
- configurable: true
-});
-
-/**
- * Export the Headers object in a form that Node.js can consume.
- *
- * @param Headers headers
- * @return Object
- */
-function exportNodeCompatibleHeaders(headers) {
- const obj = Object.assign({ __proto__: null }, headers[MAP]);
-
- // http.request() only supports string as Host header. This hack makes
- // specifying custom Host header possible.
- const hostHeaderKey = find(headers[MAP], 'Host');
- if (hostHeaderKey !== undefined) {
- obj[hostHeaderKey] = obj[hostHeaderKey][0];
- }
-
- return obj;
-}
-
-/**
- * Create a Headers object from an object of headers, ignoring those that do
- * not conform to HTTP grammar productions.
- *
- * @param Object obj Object of headers
- * @return Headers
- */
-function createHeadersLenient(obj) {
- const headers = new Headers();
- for (const name of Object.keys(obj)) {
- if (invalidTokenRegex.test(name)) {
- continue;
- }
- if (Array.isArray(obj[name])) {
- for (const val of obj[name]) {
- if (invalidHeaderCharRegex.test(val)) {
- continue;
- }
- if (headers[MAP][name] === undefined) {
- headers[MAP][name] = [val];
- } else {
- headers[MAP][name].push(val);
- }
- }
- } else if (!invalidHeaderCharRegex.test(obj[name])) {
- headers[MAP][name] = [obj[name]];
- }
- }
- return headers;
-}
-
-const INTERNALS$1 = Symbol('Response internals');
-
-// fix an issue where "STATUS_CODES" aren't a named export for node <10
-const STATUS_CODES = http.STATUS_CODES;
-
-/**
- * Response class
- *
- * @param Stream body Readable stream
- * @param Object opts Response options
- * @return Void
- */
-class Response {
- constructor() {
- let body = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
- let opts = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
-
- Body.call(this, body, opts);
-
- const status = opts.status || 200;
- const headers = new Headers(opts.headers);
-
- if (body != null && !headers.has('Content-Type')) {
- const contentType = extractContentType(body);
- if (contentType) {
- headers.append('Content-Type', contentType);
- }
- }
-
- this[INTERNALS$1] = {
- url: opts.url,
- status,
- statusText: opts.statusText || STATUS_CODES[status],
- headers,
- counter: opts.counter
- };
- }
-
- get url() {
- return this[INTERNALS$1].url || '';
- }
-
- get status() {
- return this[INTERNALS$1].status;
- }
-
- /**
- * Convenience property representing if the request ended normally
- */
- get ok() {
- return this[INTERNALS$1].status >= 200 && this[INTERNALS$1].status < 300;
- }
-
- get redirected() {
- return this[INTERNALS$1].counter > 0;
- }
-
- get statusText() {
- return this[INTERNALS$1].statusText;
- }
-
- get headers() {
- return this[INTERNALS$1].headers;
- }
-
- /**
- * Clone this response
- *
- * @return Response
- */
- clone() {
- return new Response(clone(this), {
- url: this.url,
- status: this.status,
- statusText: this.statusText,
- headers: this.headers,
- ok: this.ok,
- redirected: this.redirected
- });
- }
-}
-
-Body.mixIn(Response.prototype);
-
-Object.defineProperties(Response.prototype, {
- url: { enumerable: true },
- status: { enumerable: true },
- ok: { enumerable: true },
- redirected: { enumerable: true },
- statusText: { enumerable: true },
- headers: { enumerable: true },
- clone: { enumerable: true }
-});
-
-Object.defineProperty(Response.prototype, Symbol.toStringTag, {
- value: 'Response',
- writable: false,
- enumerable: false,
- configurable: true
-});
-
-const INTERNALS$2 = Symbol('Request internals');
-const URL = Url.URL || whatwgUrl.URL;
-
-// fix an issue where "format", "parse" aren't a named export for node <10
-const parse_url = Url.parse;
-const format_url = Url.format;
-
-/**
- * Wrapper around `new URL` to handle arbitrary URLs
- *
- * @param {string} urlStr
- * @return {void}
- */
-function parseURL(urlStr) {
- /*
- Check whether the URL is absolute or not
- Scheme: https://tools.ietf.org/html/rfc3986#section-3.1
- Absolute URL: https://tools.ietf.org/html/rfc3986#section-4.3
- */
- if (/^[a-zA-Z][a-zA-Z\d+\-.]*:/.exec(urlStr)) {
- urlStr = new URL(urlStr).toString();
- }
-
- // Fallback to old implementation for arbitrary URLs
- return parse_url(urlStr);
-}
-
-const streamDestructionSupported = 'destroy' in Stream.Readable.prototype;
-
-/**
- * Check if a value is an instance of Request.
- *
- * @param Mixed input
- * @return Boolean
- */
-function isRequest(input) {
- return typeof input === 'object' && typeof input[INTERNALS$2] === 'object';
-}
-
-function isAbortSignal(signal) {
- const proto = signal && typeof signal === 'object' && Object.getPrototypeOf(signal);
- return !!(proto && proto.constructor.name === 'AbortSignal');
-}
-
-/**
- * Request class
- *
- * @param Mixed input Url or Request instance
- * @param Object init Custom options
- * @return Void
- */
-class Request {
- constructor(input) {
- let init = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
-
- let parsedURL;
-
- // normalize input
- if (!isRequest(input)) {
- if (input && input.href) {
- // in order to support Node.js' Url objects; though WHATWG's URL objects
- // will fall into this branch also (since their `toString()` will return
- // `href` property anyway)
- parsedURL = parseURL(input.href);
- } else {
- // coerce input to a string before attempting to parse
- parsedURL = parseURL(`${input}`);
- }
- input = {};
- } else {
- parsedURL = parseURL(input.url);
- }
-
- let method = init.method || input.method || 'GET';
- method = method.toUpperCase();
-
- if ((init.body != null || isRequest(input) && input.body !== null) && (method === 'GET' || method === 'HEAD')) {
- throw new TypeError('Request with GET/HEAD method cannot have body');
- }
-
- let inputBody = init.body != null ? init.body : isRequest(input) && input.body !== null ? clone(input) : null;
-
- Body.call(this, inputBody, {
- timeout: init.timeout || input.timeout || 0,
- size: init.size || input.size || 0
- });
-
- const headers = new Headers(init.headers || input.headers || {});
-
- if (inputBody != null && !headers.has('Content-Type')) {
- const contentType = extractContentType(inputBody);
- if (contentType) {
- headers.append('Content-Type', contentType);
- }
- }
-
- let signal = isRequest(input) ? input.signal : null;
- if ('signal' in init) signal = init.signal;
-
- if (signal != null && !isAbortSignal(signal)) {
- throw new TypeError('Expected signal to be an instanceof AbortSignal');
- }
-
- this[INTERNALS$2] = {
- method,
- redirect: init.redirect || input.redirect || 'follow',
- headers,
- parsedURL,
- signal
- };
-
- // node-fetch-only options
- this.follow = init.follow !== undefined ? init.follow : input.follow !== undefined ? input.follow : 20;
- this.compress = init.compress !== undefined ? init.compress : input.compress !== undefined ? input.compress : true;
- this.counter = init.counter || input.counter || 0;
- this.agent = init.agent || input.agent;
- }
-
- get method() {
- return this[INTERNALS$2].method;
- }
-
- get url() {
- return format_url(this[INTERNALS$2].parsedURL);
- }
-
- get headers() {
- return this[INTERNALS$2].headers;
- }
-
- get redirect() {
- return this[INTERNALS$2].redirect;
- }
-
- get signal() {
- return this[INTERNALS$2].signal;
- }
-
- /**
- * Clone this request
- *
- * @return Request
- */
- clone() {
- return new Request(this);
- }
-}
-
-Body.mixIn(Request.prototype);
-
-Object.defineProperty(Request.prototype, Symbol.toStringTag, {
- value: 'Request',
- writable: false,
- enumerable: false,
- configurable: true
-});
-
-Object.defineProperties(Request.prototype, {
- method: { enumerable: true },
- url: { enumerable: true },
- headers: { enumerable: true },
- redirect: { enumerable: true },
- clone: { enumerable: true },
- signal: { enumerable: true }
-});
-
-/**
- * Convert a Request to Node.js http request options.
- *
- * @param Request A Request instance
- * @return Object The options object to be passed to http.request
- */
-function getNodeRequestOptions(request) {
- const parsedURL = request[INTERNALS$2].parsedURL;
- const headers = new Headers(request[INTERNALS$2].headers);
-
- // fetch step 1.3
- if (!headers.has('Accept')) {
- headers.set('Accept', '*/*');
- }
-
- // Basic fetch
- if (!parsedURL.protocol || !parsedURL.hostname) {
- throw new TypeError('Only absolute URLs are supported');
- }
-
- if (!/^https?:$/.test(parsedURL.protocol)) {
- throw new TypeError('Only HTTP(S) protocols are supported');
- }
-
- if (request.signal && request.body instanceof Stream.Readable && !streamDestructionSupported) {
- throw new Error('Cancellation of streamed requests with AbortSignal is not supported in node < 8');
- }
-
- // HTTP-network-or-cache fetch steps 2.4-2.7
- let contentLengthValue = null;
- if (request.body == null && /^(POST|PUT)$/i.test(request.method)) {
- contentLengthValue = '0';
- }
- if (request.body != null) {
- const totalBytes = getTotalBytes(request);
- if (typeof totalBytes === 'number') {
- contentLengthValue = String(totalBytes);
- }
- }
- if (contentLengthValue) {
- headers.set('Content-Length', contentLengthValue);
- }
-
- // HTTP-network-or-cache fetch step 2.11
- if (!headers.has('User-Agent')) {
- headers.set('User-Agent', 'node-fetch/1.0 (+https://github.com/bitinn/node-fetch)');
- }
-
- // HTTP-network-or-cache fetch step 2.15
- if (request.compress && !headers.has('Accept-Encoding')) {
- headers.set('Accept-Encoding', 'gzip,deflate');
- }
-
- let agent = request.agent;
- if (typeof agent === 'function') {
- agent = agent(parsedURL);
- }
-
- // HTTP-network fetch step 4.2
- // chunked encoding is handled by Node.js
-
- return Object.assign({}, parsedURL, {
- method: request.method,
- headers: exportNodeCompatibleHeaders(headers),
- agent
- });
-}
-
-/**
- * abort-error.js
- *
- * AbortError interface for cancelled requests
- */
-
-/**
- * Create AbortError instance
- *
- * @param String message Error message for human
- * @return AbortError
- */
-function AbortError(message) {
- Error.call(this, message);
-
- this.type = 'aborted';
- this.message = message;
-
- // hide custom error implementation details from end-users
- Error.captureStackTrace(this, this.constructor);
-}
-
-AbortError.prototype = Object.create(Error.prototype);
-AbortError.prototype.constructor = AbortError;
-AbortError.prototype.name = 'AbortError';
-
-const URL$1 = Url.URL || whatwgUrl.URL;
-
-// fix an issue where "PassThrough", "resolve" aren't a named export for node <10
-const PassThrough$1 = Stream.PassThrough;
-
-const isDomainOrSubdomain = function isDomainOrSubdomain(destination, original) {
- const orig = new URL$1(original).hostname;
- const dest = new URL$1(destination).hostname;
-
- return orig === dest || orig[orig.length - dest.length - 1] === '.' && orig.endsWith(dest);
-};
-
-/**
- * isSameProtocol reports whether the two provided URLs use the same protocol.
- *
- * Both domains must already be in canonical form.
- * @param {string|URL} original
- * @param {string|URL} destination
- */
-const isSameProtocol = function isSameProtocol(destination, original) {
- const orig = new URL$1(original).protocol;
- const dest = new URL$1(destination).protocol;
-
- return orig === dest;
-};
-
-/**
- * Fetch function
- *
- * @param Mixed url Absolute url or Request instance
- * @param Object opts Fetch options
- * @return Promise
- */
-function fetch(url, opts) {
-
- // allow custom promise
- if (!fetch.Promise) {
- throw new Error('native promise missing, set fetch.Promise to your favorite alternative');
- }
-
- Body.Promise = fetch.Promise;
-
- // wrap http.request into fetch
- return new fetch.Promise(function (resolve, reject) {
- // build request object
- const request = new Request(url, opts);
- const options = getNodeRequestOptions(request);
-
- const send = (options.protocol === 'https:' ? https : http).request;
- const signal = request.signal;
-
- let response = null;
-
- const abort = function abort() {
- let error = new AbortError('The user aborted a request.');
- reject(error);
- if (request.body && request.body instanceof Stream.Readable) {
- destroyStream(request.body, error);
- }
- if (!response || !response.body) return;
- response.body.emit('error', error);
- };
-
- if (signal && signal.aborted) {
- abort();
- return;
- }
-
- const abortAndFinalize = function abortAndFinalize() {
- abort();
- finalize();
- };
-
- // send request
- const req = send(options);
- let reqTimeout;
-
- if (signal) {
- signal.addEventListener('abort', abortAndFinalize);
- }
-
- function finalize() {
- req.abort();
- if (signal) signal.removeEventListener('abort', abortAndFinalize);
- clearTimeout(reqTimeout);
- }
-
- if (request.timeout) {
- req.once('socket', function (socket) {
- reqTimeout = setTimeout(function () {
- reject(new FetchError(`network timeout at: ${request.url}`, 'request-timeout'));
- finalize();
- }, request.timeout);
- });
- }
-
- req.on('error', function (err) {
- reject(new FetchError(`request to ${request.url} failed, reason: ${err.message}`, 'system', err));
-
- if (response && response.body) {
- destroyStream(response.body, err);
- }
-
- finalize();
- });
-
- fixResponseChunkedTransferBadEnding(req, function (err) {
- if (signal && signal.aborted) {
- return;
- }
-
- if (response && response.body) {
- destroyStream(response.body, err);
- }
- });
-
- /* c8 ignore next 18 */
- if (parseInt(process.version.substring(1)) < 14) {
- // Before Node.js 14, pipeline() does not fully support async iterators and does not always
- // properly handle when the socket close/end events are out of order.
- req.on('socket', function (s) {
- s.addListener('close', function (hadError) {
- // if a data listener is still present we didn't end cleanly
- const hasDataListener = s.listenerCount('data') > 0;
-
- // if end happened before close but the socket didn't emit an error, do it now
- if (response && hasDataListener && !hadError && !(signal && signal.aborted)) {
- const err = new Error('Premature close');
- err.code = 'ERR_STREAM_PREMATURE_CLOSE';
- response.body.emit('error', err);
- }
- });
- });
- }
-
- req.on('response', function (res) {
- clearTimeout(reqTimeout);
-
- const headers = createHeadersLenient(res.headers);
-
- // HTTP fetch step 5
- if (fetch.isRedirect(res.statusCode)) {
- // HTTP fetch step 5.2
- const location = headers.get('Location');
-
- // HTTP fetch step 5.3
- let locationURL = null;
- try {
- locationURL = location === null ? null : new URL$1(location, request.url).toString();
- } catch (err) {
- // error here can only be invalid URL in Location: header
- // do not throw when options.redirect == manual
- // let the user extract the errorneous redirect URL
- if (request.redirect !== 'manual') {
- reject(new FetchError(`uri requested responds with an invalid redirect URL: ${location}`, 'invalid-redirect'));
- finalize();
- return;
- }
- }
-
- // HTTP fetch step 5.5
- switch (request.redirect) {
- case 'error':
- reject(new FetchError(`uri requested responds with a redirect, redirect mode is set to error: ${request.url}`, 'no-redirect'));
- finalize();
- return;
- case 'manual':
- // node-fetch-specific step: make manual redirect a bit easier to use by setting the Location header value to the resolved URL.
- if (locationURL !== null) {
- // handle corrupted header
- try {
- headers.set('Location', locationURL);
- } catch (err) {
- // istanbul ignore next: nodejs server prevent invalid response headers, we can't test this through normal request
- reject(err);
- }
- }
- break;
- case 'follow':
- // HTTP-redirect fetch step 2
- if (locationURL === null) {
- break;
- }
-
- // HTTP-redirect fetch step 5
- if (request.counter >= request.follow) {
- reject(new FetchError(`maximum redirect reached at: ${request.url}`, 'max-redirect'));
- finalize();
- return;
- }
-
- // HTTP-redirect fetch step 6 (counter increment)
- // Create a new Request object.
- const requestOpts = {
- headers: new Headers(request.headers),
- follow: request.follow,
- counter: request.counter + 1,
- agent: request.agent,
- compress: request.compress,
- method: request.method,
- body: request.body,
- signal: request.signal,
- timeout: request.timeout,
- size: request.size
- };
-
- if (!isDomainOrSubdomain(request.url, locationURL) || !isSameProtocol(request.url, locationURL)) {
- for (const name of ['authorization', 'www-authenticate', 'cookie', 'cookie2']) {
- requestOpts.headers.delete(name);
- }
- }
-
- // HTTP-redirect fetch step 9
- if (res.statusCode !== 303 && request.body && getTotalBytes(request) === null) {
- reject(new FetchError('Cannot follow redirect with body being a readable stream', 'unsupported-redirect'));
- finalize();
- return;
- }
-
- // HTTP-redirect fetch step 11
- if (res.statusCode === 303 || (res.statusCode === 301 || res.statusCode === 302) && request.method === 'POST') {
- requestOpts.method = 'GET';
- requestOpts.body = undefined;
- requestOpts.headers.delete('content-length');
- }
-
- // HTTP-redirect fetch step 15
- resolve(fetch(new Request(locationURL, requestOpts)));
- finalize();
- return;
- }
- }
-
- // prepare response
- res.once('end', function () {
- if (signal) signal.removeEventListener('abort', abortAndFinalize);
- });
- let body = res.pipe(new PassThrough$1());
-
- const response_options = {
- url: request.url,
- status: res.statusCode,
- statusText: res.statusMessage,
- headers: headers,
- size: request.size,
- timeout: request.timeout,
- counter: request.counter
- };
-
- // HTTP-network fetch step 12.1.1.3
- const codings = headers.get('Content-Encoding');
-
- // HTTP-network fetch step 12.1.1.4: handle content codings
-
- // in following scenarios we ignore compression support
- // 1. compression support is disabled
- // 2. HEAD request
- // 3. no Content-Encoding header
- // 4. no content response (204)
- // 5. content not modified response (304)
- if (!request.compress || request.method === 'HEAD' || codings === null || res.statusCode === 204 || res.statusCode === 304) {
- response = new Response(body, response_options);
- resolve(response);
- return;
- }
-
- // For Node v6+
- // Be less strict when decoding compressed responses, since sometimes
- // servers send slightly invalid responses that are still accepted
- // by common browsers.
- // Always using Z_SYNC_FLUSH is what cURL does.
- const zlibOptions = {
- flush: zlib.Z_SYNC_FLUSH,
- finishFlush: zlib.Z_SYNC_FLUSH
- };
-
- // for gzip
- if (codings == 'gzip' || codings == 'x-gzip') {
- body = body.pipe(zlib.createGunzip(zlibOptions));
- response = new Response(body, response_options);
- resolve(response);
- return;
- }
-
- // for deflate
- if (codings == 'deflate' || codings == 'x-deflate') {
- // handle the infamous raw deflate response from old servers
- // a hack for old IIS and Apache servers
- const raw = res.pipe(new PassThrough$1());
- raw.once('data', function (chunk) {
- // see http://stackoverflow.com/questions/37519828
- if ((chunk[0] & 0x0F) === 0x08) {
- body = body.pipe(zlib.createInflate());
- } else {
- body = body.pipe(zlib.createInflateRaw());
- }
- response = new Response(body, response_options);
- resolve(response);
- });
- raw.on('end', function () {
- // some old IIS servers return zero-length OK deflate responses, so 'data' is never emitted.
- if (!response) {
- response = new Response(body, response_options);
- resolve(response);
- }
- });
- return;
- }
-
- // for br
- if (codings == 'br' && typeof zlib.createBrotliDecompress === 'function') {
- body = body.pipe(zlib.createBrotliDecompress());
- response = new Response(body, response_options);
- resolve(response);
- return;
- }
-
- // otherwise, use response as-is
- response = new Response(body, response_options);
- resolve(response);
- });
-
- writeToStream(req, request);
- });
-}
-function fixResponseChunkedTransferBadEnding(request, errorCallback) {
- let socket;
-
- request.on('socket', function (s) {
- socket = s;
- });
-
- request.on('response', function (response) {
- const headers = response.headers;
-
- if (headers['transfer-encoding'] === 'chunked' && !headers['content-length']) {
- response.once('close', function (hadError) {
- // tests for socket presence, as in some situations the
- // the 'socket' event is not triggered for the request
- // (happens in deno), avoids `TypeError`
- // if a data listener is still present we didn't end cleanly
- const hasDataListener = socket && socket.listenerCount('data') > 0;
-
- if (hasDataListener && !hadError) {
- const err = new Error('Premature close');
- err.code = 'ERR_STREAM_PREMATURE_CLOSE';
- errorCallback(err);
- }
- });
- }
- });
-}
-
-function destroyStream(stream, err) {
- if (stream.destroy) {
- stream.destroy(err);
- } else {
- // node < 8
- stream.emit('error', err);
- stream.end();
- }
-}
-
-/**
- * Redirect code matching
- *
- * @param Number code Status code
- * @return Boolean
- */
-fetch.isRedirect = function (code) {
- return code === 301 || code === 302 || code === 303 || code === 307 || code === 308;
-};
-
-// expose Promise
-fetch.Promise = global.Promise;
-
-export default fetch;
-export { Headers, Request, Response, FetchError, AbortError };
diff --git a/backend/node_modules/node-fetch/lib/index.js b/backend/node_modules/node-fetch/lib/index.js
deleted file mode 100644
index 567ff5d..0000000
--- a/backend/node_modules/node-fetch/lib/index.js
+++ /dev/null
@@ -1,1787 +0,0 @@
-'use strict';
-
-Object.defineProperty(exports, '__esModule', { value: true });
-
-function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
-
-var Stream = _interopDefault(require('stream'));
-var http = _interopDefault(require('http'));
-var Url = _interopDefault(require('url'));
-var whatwgUrl = _interopDefault(require('whatwg-url'));
-var https = _interopDefault(require('https'));
-var zlib = _interopDefault(require('zlib'));
-
-// Based on https://github.com/tmpvar/jsdom/blob/aa85b2abf07766ff7bf5c1f6daafb3726f2f2db5/lib/jsdom/living/blob.js
-
-// fix for "Readable" isn't a named export issue
-const Readable = Stream.Readable;
-
-const BUFFER = Symbol('buffer');
-const TYPE = Symbol('type');
-
-class Blob {
- constructor() {
- this[TYPE] = '';
-
- const blobParts = arguments[0];
- const options = arguments[1];
-
- const buffers = [];
- let size = 0;
-
- if (blobParts) {
- const a = blobParts;
- const length = Number(a.length);
- for (let i = 0; i < length; i++) {
- const element = a[i];
- let buffer;
- if (element instanceof Buffer) {
- buffer = element;
- } else if (ArrayBuffer.isView(element)) {
- buffer = Buffer.from(element.buffer, element.byteOffset, element.byteLength);
- } else if (element instanceof ArrayBuffer) {
- buffer = Buffer.from(element);
- } else if (element instanceof Blob) {
- buffer = element[BUFFER];
- } else {
- buffer = Buffer.from(typeof element === 'string' ? element : String(element));
- }
- size += buffer.length;
- buffers.push(buffer);
- }
- }
-
- this[BUFFER] = Buffer.concat(buffers);
-
- let type = options && options.type !== undefined && String(options.type).toLowerCase();
- if (type && !/[^\u0020-\u007E]/.test(type)) {
- this[TYPE] = type;
- }
- }
- get size() {
- return this[BUFFER].length;
- }
- get type() {
- return this[TYPE];
- }
- text() {
- return Promise.resolve(this[BUFFER].toString());
- }
- arrayBuffer() {
- const buf = this[BUFFER];
- const ab = buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
- return Promise.resolve(ab);
- }
- stream() {
- const readable = new Readable();
- readable._read = function () {};
- readable.push(this[BUFFER]);
- readable.push(null);
- return readable;
- }
- toString() {
- return '[object Blob]';
- }
- slice() {
- const size = this.size;
-
- const start = arguments[0];
- const end = arguments[1];
- let relativeStart, relativeEnd;
- if (start === undefined) {
- relativeStart = 0;
- } else if (start < 0) {
- relativeStart = Math.max(size + start, 0);
- } else {
- relativeStart = Math.min(start, size);
- }
- if (end === undefined) {
- relativeEnd = size;
- } else if (end < 0) {
- relativeEnd = Math.max(size + end, 0);
- } else {
- relativeEnd = Math.min(end, size);
- }
- const span = Math.max(relativeEnd - relativeStart, 0);
-
- const buffer = this[BUFFER];
- const slicedBuffer = buffer.slice(relativeStart, relativeStart + span);
- const blob = new Blob([], { type: arguments[2] });
- blob[BUFFER] = slicedBuffer;
- return blob;
- }
-}
-
-Object.defineProperties(Blob.prototype, {
- size: { enumerable: true },
- type: { enumerable: true },
- slice: { enumerable: true }
-});
-
-Object.defineProperty(Blob.prototype, Symbol.toStringTag, {
- value: 'Blob',
- writable: false,
- enumerable: false,
- configurable: true
-});
-
-/**
- * fetch-error.js
- *
- * FetchError interface for operational errors
- */
-
-/**
- * Create FetchError instance
- *
- * @param String message Error message for human
- * @param String type Error type for machine
- * @param String systemError For Node.js system error
- * @return FetchError
- */
-function FetchError(message, type, systemError) {
- Error.call(this, message);
-
- this.message = message;
- this.type = type;
-
- // when err.type is `system`, err.code contains system error code
- if (systemError) {
- this.code = this.errno = systemError.code;
- }
-
- // hide custom error implementation details from end-users
- Error.captureStackTrace(this, this.constructor);
-}
-
-FetchError.prototype = Object.create(Error.prototype);
-FetchError.prototype.constructor = FetchError;
-FetchError.prototype.name = 'FetchError';
-
-let convert;
-try {
- convert = require('encoding').convert;
-} catch (e) {}
-
-const INTERNALS = Symbol('Body internals');
-
-// fix an issue where "PassThrough" isn't a named export for node <10
-const PassThrough = Stream.PassThrough;
-
-/**
- * Body mixin
- *
- * Ref: https://fetch.spec.whatwg.org/#body
- *
- * @param Stream body Readable stream
- * @param Object opts Response options
- * @return Void
- */
-function Body(body) {
- var _this = this;
-
- var _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
- _ref$size = _ref.size;
-
- let size = _ref$size === undefined ? 0 : _ref$size;
- var _ref$timeout = _ref.timeout;
- let timeout = _ref$timeout === undefined ? 0 : _ref$timeout;
-
- if (body == null) {
- // body is undefined or null
- body = null;
- } else if (isURLSearchParams(body)) {
- // body is a URLSearchParams
- body = Buffer.from(body.toString());
- } else if (isBlob(body)) ; else if (Buffer.isBuffer(body)) ; else if (Object.prototype.toString.call(body) === '[object ArrayBuffer]') {
- // body is ArrayBuffer
- body = Buffer.from(body);
- } else if (ArrayBuffer.isView(body)) {
- // body is ArrayBufferView
- body = Buffer.from(body.buffer, body.byteOffset, body.byteLength);
- } else if (body instanceof Stream) ; else {
- // none of the above
- // coerce to string then buffer
- body = Buffer.from(String(body));
- }
- this[INTERNALS] = {
- body,
- disturbed: false,
- error: null
- };
- this.size = size;
- this.timeout = timeout;
-
- if (body instanceof Stream) {
- body.on('error', function (err) {
- const error = err.name === 'AbortError' ? err : new FetchError(`Invalid response body while trying to fetch ${_this.url}: ${err.message}`, 'system', err);
- _this[INTERNALS].error = error;
- });
- }
-}
-
-Body.prototype = {
- get body() {
- return this[INTERNALS].body;
- },
-
- get bodyUsed() {
- return this[INTERNALS].disturbed;
- },
-
- /**
- * Decode response as ArrayBuffer
- *
- * @return Promise
- */
- arrayBuffer() {
- return consumeBody.call(this).then(function (buf) {
- return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
- });
- },
-
- /**
- * Return raw response as Blob
- *
- * @return Promise
- */
- blob() {
- let ct = this.headers && this.headers.get('content-type') || '';
- return consumeBody.call(this).then(function (buf) {
- return Object.assign(
- // Prevent copying
- new Blob([], {
- type: ct.toLowerCase()
- }), {
- [BUFFER]: buf
- });
- });
- },
-
- /**
- * Decode response as json
- *
- * @return Promise
- */
- json() {
- var _this2 = this;
-
- return consumeBody.call(this).then(function (buffer) {
- try {
- return JSON.parse(buffer.toString());
- } catch (err) {
- return Body.Promise.reject(new FetchError(`invalid json response body at ${_this2.url} reason: ${err.message}`, 'invalid-json'));
- }
- });
- },
-
- /**
- * Decode response as text
- *
- * @return Promise
- */
- text() {
- return consumeBody.call(this).then(function (buffer) {
- return buffer.toString();
- });
- },
-
- /**
- * Decode response as buffer (non-spec api)
- *
- * @return Promise
- */
- buffer() {
- return consumeBody.call(this);
- },
-
- /**
- * Decode response as text, while automatically detecting the encoding and
- * trying to decode to UTF-8 (non-spec api)
- *
- * @return Promise
- */
- textConverted() {
- var _this3 = this;
-
- return consumeBody.call(this).then(function (buffer) {
- return convertBody(buffer, _this3.headers);
- });
- }
-};
-
-// In browsers, all properties are enumerable.
-Object.defineProperties(Body.prototype, {
- body: { enumerable: true },
- bodyUsed: { enumerable: true },
- arrayBuffer: { enumerable: true },
- blob: { enumerable: true },
- json: { enumerable: true },
- text: { enumerable: true }
-});
-
-Body.mixIn = function (proto) {
- for (const name of Object.getOwnPropertyNames(Body.prototype)) {
- // istanbul ignore else: future proof
- if (!(name in proto)) {
- const desc = Object.getOwnPropertyDescriptor(Body.prototype, name);
- Object.defineProperty(proto, name, desc);
- }
- }
-};
-
-/**
- * Consume and convert an entire Body to a Buffer.
- *
- * Ref: https://fetch.spec.whatwg.org/#concept-body-consume-body
- *
- * @return Promise
- */
-function consumeBody() {
- var _this4 = this;
-
- if (this[INTERNALS].disturbed) {
- return Body.Promise.reject(new TypeError(`body used already for: ${this.url}`));
- }
-
- this[INTERNALS].disturbed = true;
-
- if (this[INTERNALS].error) {
- return Body.Promise.reject(this[INTERNALS].error);
- }
-
- let body = this.body;
-
- // body is null
- if (body === null) {
- return Body.Promise.resolve(Buffer.alloc(0));
- }
-
- // body is blob
- if (isBlob(body)) {
- body = body.stream();
- }
-
- // body is buffer
- if (Buffer.isBuffer(body)) {
- return Body.Promise.resolve(body);
- }
-
- // istanbul ignore if: should never happen
- if (!(body instanceof Stream)) {
- return Body.Promise.resolve(Buffer.alloc(0));
- }
-
- // body is stream
- // get ready to actually consume the body
- let accum = [];
- let accumBytes = 0;
- let abort = false;
-
- return new Body.Promise(function (resolve, reject) {
- let resTimeout;
-
- // allow timeout on slow response body
- if (_this4.timeout) {
- resTimeout = setTimeout(function () {
- abort = true;
- reject(new FetchError(`Response timeout while trying to fetch ${_this4.url} (over ${_this4.timeout}ms)`, 'body-timeout'));
- }, _this4.timeout);
- }
-
- // handle stream errors
- body.on('error', function (err) {
- if (err.name === 'AbortError') {
- // if the request was aborted, reject with this Error
- abort = true;
- reject(err);
- } else {
- // other errors, such as incorrect content-encoding
- reject(new FetchError(`Invalid response body while trying to fetch ${_this4.url}: ${err.message}`, 'system', err));
- }
- });
-
- body.on('data', function (chunk) {
- if (abort || chunk === null) {
- return;
- }
-
- if (_this4.size && accumBytes + chunk.length > _this4.size) {
- abort = true;
- reject(new FetchError(`content size at ${_this4.url} over limit: ${_this4.size}`, 'max-size'));
- return;
- }
-
- accumBytes += chunk.length;
- accum.push(chunk);
- });
-
- body.on('end', function () {
- if (abort) {
- return;
- }
-
- clearTimeout(resTimeout);
-
- try {
- resolve(Buffer.concat(accum, accumBytes));
- } catch (err) {
- // handle streams that have accumulated too much data (issue #414)
- reject(new FetchError(`Could not create Buffer from response body for ${_this4.url}: ${err.message}`, 'system', err));
- }
- });
- });
-}
-
-/**
- * Detect buffer encoding and convert to target encoding
- * ref: http://www.w3.org/TR/2011/WD-html5-20110113/parsing.html#determining-the-character-encoding
- *
- * @param Buffer buffer Incoming buffer
- * @param String encoding Target encoding
- * @return String
- */
-function convertBody(buffer, headers) {
- if (typeof convert !== 'function') {
- throw new Error('The package `encoding` must be installed to use the textConverted() function');
- }
-
- const ct = headers.get('content-type');
- let charset = 'utf-8';
- let res, str;
-
- // header
- if (ct) {
- res = /charset=([^;]*)/i.exec(ct);
- }
-
- // no charset in content type, peek at response body for at most 1024 bytes
- str = buffer.slice(0, 1024).toString();
-
- // html5
- if (!res && str) {
- res = / 0 && arguments[0] !== undefined ? arguments[0] : undefined;
-
- this[MAP] = Object.create(null);
-
- if (init instanceof Headers) {
- const rawHeaders = init.raw();
- const headerNames = Object.keys(rawHeaders);
-
- for (const headerName of headerNames) {
- for (const value of rawHeaders[headerName]) {
- this.append(headerName, value);
- }
- }
-
- return;
- }
-
- // We don't worry about converting prop to ByteString here as append()
- // will handle it.
- if (init == null) ; else if (typeof init === 'object') {
- const method = init[Symbol.iterator];
- if (method != null) {
- if (typeof method !== 'function') {
- throw new TypeError('Header pairs must be iterable');
- }
-
- // sequence>
- // Note: per spec we have to first exhaust the lists then process them
- const pairs = [];
- for (const pair of init) {
- if (typeof pair !== 'object' || typeof pair[Symbol.iterator] !== 'function') {
- throw new TypeError('Each header pair must be iterable');
- }
- pairs.push(Array.from(pair));
- }
-
- for (const pair of pairs) {
- if (pair.length !== 2) {
- throw new TypeError('Each header pair must be a name/value tuple');
- }
- this.append(pair[0], pair[1]);
- }
- } else {
- // record
- for (const key of Object.keys(init)) {
- const value = init[key];
- this.append(key, value);
- }
- }
- } else {
- throw new TypeError('Provided initializer must be an object');
- }
- }
-
- /**
- * Return combined header value given name
- *
- * @param String name Header name
- * @return Mixed
- */
- get(name) {
- name = `${name}`;
- validateName(name);
- const key = find(this[MAP], name);
- if (key === undefined) {
- return null;
- }
-
- return this[MAP][key].join(', ');
- }
-
- /**
- * Iterate over all headers
- *
- * @param Function callback Executed for each item with parameters (value, name, thisArg)
- * @param Boolean thisArg `this` context for callback function
- * @return Void
- */
- forEach(callback) {
- let thisArg = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : undefined;
-
- let pairs = getHeaders(this);
- let i = 0;
- while (i < pairs.length) {
- var _pairs$i = pairs[i];
- const name = _pairs$i[0],
- value = _pairs$i[1];
-
- callback.call(thisArg, value, name, this);
- pairs = getHeaders(this);
- i++;
- }
- }
-
- /**
- * Overwrite header values given name
- *
- * @param String name Header name
- * @param String value Header value
- * @return Void
- */
- set(name, value) {
- name = `${name}`;
- value = `${value}`;
- validateName(name);
- validateValue(value);
- const key = find(this[MAP], name);
- this[MAP][key !== undefined ? key : name] = [value];
- }
-
- /**
- * Append a value onto existing header
- *
- * @param String name Header name
- * @param String value Header value
- * @return Void
- */
- append(name, value) {
- name = `${name}`;
- value = `${value}`;
- validateName(name);
- validateValue(value);
- const key = find(this[MAP], name);
- if (key !== undefined) {
- this[MAP][key].push(value);
- } else {
- this[MAP][name] = [value];
- }
- }
-
- /**
- * Check for header name existence
- *
- * @param String name Header name
- * @return Boolean
- */
- has(name) {
- name = `${name}`;
- validateName(name);
- return find(this[MAP], name) !== undefined;
- }
-
- /**
- * Delete all header values given name
- *
- * @param String name Header name
- * @return Void
- */
- delete(name) {
- name = `${name}`;
- validateName(name);
- const key = find(this[MAP], name);
- if (key !== undefined) {
- delete this[MAP][key];
- }
- }
-
- /**
- * Return raw headers (non-spec api)
- *
- * @return Object
- */
- raw() {
- return this[MAP];
- }
-
- /**
- * Get an iterator on keys.
- *
- * @return Iterator
- */
- keys() {
- return createHeadersIterator(this, 'key');
- }
-
- /**
- * Get an iterator on values.
- *
- * @return Iterator
- */
- values() {
- return createHeadersIterator(this, 'value');
- }
-
- /**
- * Get an iterator on entries.
- *
- * This is the default iterator of the Headers object.
- *
- * @return Iterator
- */
- [Symbol.iterator]() {
- return createHeadersIterator(this, 'key+value');
- }
-}
-Headers.prototype.entries = Headers.prototype[Symbol.iterator];
-
-Object.defineProperty(Headers.prototype, Symbol.toStringTag, {
- value: 'Headers',
- writable: false,
- enumerable: false,
- configurable: true
-});
-
-Object.defineProperties(Headers.prototype, {
- get: { enumerable: true },
- forEach: { enumerable: true },
- set: { enumerable: true },
- append: { enumerable: true },
- has: { enumerable: true },
- delete: { enumerable: true },
- keys: { enumerable: true },
- values: { enumerable: true },
- entries: { enumerable: true }
-});
-
-function getHeaders(headers) {
- let kind = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'key+value';
-
- const keys = Object.keys(headers[MAP]).sort();
- return keys.map(kind === 'key' ? function (k) {
- return k.toLowerCase();
- } : kind === 'value' ? function (k) {
- return headers[MAP][k].join(', ');
- } : function (k) {
- return [k.toLowerCase(), headers[MAP][k].join(', ')];
- });
-}
-
-const INTERNAL = Symbol('internal');
-
-function createHeadersIterator(target, kind) {
- const iterator = Object.create(HeadersIteratorPrototype);
- iterator[INTERNAL] = {
- target,
- kind,
- index: 0
- };
- return iterator;
-}
-
-const HeadersIteratorPrototype = Object.setPrototypeOf({
- next() {
- // istanbul ignore if
- if (!this || Object.getPrototypeOf(this) !== HeadersIteratorPrototype) {
- throw new TypeError('Value of `this` is not a HeadersIterator');
- }
-
- var _INTERNAL = this[INTERNAL];
- const target = _INTERNAL.target,
- kind = _INTERNAL.kind,
- index = _INTERNAL.index;
-
- const values = getHeaders(target, kind);
- const len = values.length;
- if (index >= len) {
- return {
- value: undefined,
- done: true
- };
- }
-
- this[INTERNAL].index = index + 1;
-
- return {
- value: values[index],
- done: false
- };
- }
-}, Object.getPrototypeOf(Object.getPrototypeOf([][Symbol.iterator]())));
-
-Object.defineProperty(HeadersIteratorPrototype, Symbol.toStringTag, {
- value: 'HeadersIterator',
- writable: false,
- enumerable: false,
- configurable: true
-});
-
-/**
- * Export the Headers object in a form that Node.js can consume.
- *
- * @param Headers headers
- * @return Object
- */
-function exportNodeCompatibleHeaders(headers) {
- const obj = Object.assign({ __proto__: null }, headers[MAP]);
-
- // http.request() only supports string as Host header. This hack makes
- // specifying custom Host header possible.
- const hostHeaderKey = find(headers[MAP], 'Host');
- if (hostHeaderKey !== undefined) {
- obj[hostHeaderKey] = obj[hostHeaderKey][0];
- }
-
- return obj;
-}
-
-/**
- * Create a Headers object from an object of headers, ignoring those that do
- * not conform to HTTP grammar productions.
- *
- * @param Object obj Object of headers
- * @return Headers
- */
-function createHeadersLenient(obj) {
- const headers = new Headers();
- for (const name of Object.keys(obj)) {
- if (invalidTokenRegex.test(name)) {
- continue;
- }
- if (Array.isArray(obj[name])) {
- for (const val of obj[name]) {
- if (invalidHeaderCharRegex.test(val)) {
- continue;
- }
- if (headers[MAP][name] === undefined) {
- headers[MAP][name] = [val];
- } else {
- headers[MAP][name].push(val);
- }
- }
- } else if (!invalidHeaderCharRegex.test(obj[name])) {
- headers[MAP][name] = [obj[name]];
- }
- }
- return headers;
-}
-
-const INTERNALS$1 = Symbol('Response internals');
-
-// fix an issue where "STATUS_CODES" aren't a named export for node <10
-const STATUS_CODES = http.STATUS_CODES;
-
-/**
- * Response class
- *
- * @param Stream body Readable stream
- * @param Object opts Response options
- * @return Void
- */
-class Response {
- constructor() {
- let body = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
- let opts = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
-
- Body.call(this, body, opts);
-
- const status = opts.status || 200;
- const headers = new Headers(opts.headers);
-
- if (body != null && !headers.has('Content-Type')) {
- const contentType = extractContentType(body);
- if (contentType) {
- headers.append('Content-Type', contentType);
- }
- }
-
- this[INTERNALS$1] = {
- url: opts.url,
- status,
- statusText: opts.statusText || STATUS_CODES[status],
- headers,
- counter: opts.counter
- };
- }
-
- get url() {
- return this[INTERNALS$1].url || '';
- }
-
- get status() {
- return this[INTERNALS$1].status;
- }
-
- /**
- * Convenience property representing if the request ended normally
- */
- get ok() {
- return this[INTERNALS$1].status >= 200 && this[INTERNALS$1].status < 300;
- }
-
- get redirected() {
- return this[INTERNALS$1].counter > 0;
- }
-
- get statusText() {
- return this[INTERNALS$1].statusText;
- }
-
- get headers() {
- return this[INTERNALS$1].headers;
- }
-
- /**
- * Clone this response
- *
- * @return Response
- */
- clone() {
- return new Response(clone(this), {
- url: this.url,
- status: this.status,
- statusText: this.statusText,
- headers: this.headers,
- ok: this.ok,
- redirected: this.redirected
- });
- }
-}
-
-Body.mixIn(Response.prototype);
-
-Object.defineProperties(Response.prototype, {
- url: { enumerable: true },
- status: { enumerable: true },
- ok: { enumerable: true },
- redirected: { enumerable: true },
- statusText: { enumerable: true },
- headers: { enumerable: true },
- clone: { enumerable: true }
-});
-
-Object.defineProperty(Response.prototype, Symbol.toStringTag, {
- value: 'Response',
- writable: false,
- enumerable: false,
- configurable: true
-});
-
-const INTERNALS$2 = Symbol('Request internals');
-const URL = Url.URL || whatwgUrl.URL;
-
-// fix an issue where "format", "parse" aren't a named export for node <10
-const parse_url = Url.parse;
-const format_url = Url.format;
-
-/**
- * Wrapper around `new URL` to handle arbitrary URLs
- *
- * @param {string} urlStr
- * @return {void}
- */
-function parseURL(urlStr) {
- /*
- Check whether the URL is absolute or not
- Scheme: https://tools.ietf.org/html/rfc3986#section-3.1
- Absolute URL: https://tools.ietf.org/html/rfc3986#section-4.3
- */
- if (/^[a-zA-Z][a-zA-Z\d+\-.]*:/.exec(urlStr)) {
- urlStr = new URL(urlStr).toString();
- }
-
- // Fallback to old implementation for arbitrary URLs
- return parse_url(urlStr);
-}
-
-const streamDestructionSupported = 'destroy' in Stream.Readable.prototype;
-
-/**
- * Check if a value is an instance of Request.
- *
- * @param Mixed input
- * @return Boolean
- */
-function isRequest(input) {
- return typeof input === 'object' && typeof input[INTERNALS$2] === 'object';
-}
-
-function isAbortSignal(signal) {
- const proto = signal && typeof signal === 'object' && Object.getPrototypeOf(signal);
- return !!(proto && proto.constructor.name === 'AbortSignal');
-}
-
-/**
- * Request class
- *
- * @param Mixed input Url or Request instance
- * @param Object init Custom options
- * @return Void
- */
-class Request {
- constructor(input) {
- let init = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
-
- let parsedURL;
-
- // normalize input
- if (!isRequest(input)) {
- if (input && input.href) {
- // in order to support Node.js' Url objects; though WHATWG's URL objects
- // will fall into this branch also (since their `toString()` will return
- // `href` property anyway)
- parsedURL = parseURL(input.href);
- } else {
- // coerce input to a string before attempting to parse
- parsedURL = parseURL(`${input}`);
- }
- input = {};
- } else {
- parsedURL = parseURL(input.url);
- }
-
- let method = init.method || input.method || 'GET';
- method = method.toUpperCase();
-
- if ((init.body != null || isRequest(input) && input.body !== null) && (method === 'GET' || method === 'HEAD')) {
- throw new TypeError('Request with GET/HEAD method cannot have body');
- }
-
- let inputBody = init.body != null ? init.body : isRequest(input) && input.body !== null ? clone(input) : null;
-
- Body.call(this, inputBody, {
- timeout: init.timeout || input.timeout || 0,
- size: init.size || input.size || 0
- });
-
- const headers = new Headers(init.headers || input.headers || {});
-
- if (inputBody != null && !headers.has('Content-Type')) {
- const contentType = extractContentType(inputBody);
- if (contentType) {
- headers.append('Content-Type', contentType);
- }
- }
-
- let signal = isRequest(input) ? input.signal : null;
- if ('signal' in init) signal = init.signal;
-
- if (signal != null && !isAbortSignal(signal)) {
- throw new TypeError('Expected signal to be an instanceof AbortSignal');
- }
-
- this[INTERNALS$2] = {
- method,
- redirect: init.redirect || input.redirect || 'follow',
- headers,
- parsedURL,
- signal
- };
-
- // node-fetch-only options
- this.follow = init.follow !== undefined ? init.follow : input.follow !== undefined ? input.follow : 20;
- this.compress = init.compress !== undefined ? init.compress : input.compress !== undefined ? input.compress : true;
- this.counter = init.counter || input.counter || 0;
- this.agent = init.agent || input.agent;
- }
-
- get method() {
- return this[INTERNALS$2].method;
- }
-
- get url() {
- return format_url(this[INTERNALS$2].parsedURL);
- }
-
- get headers() {
- return this[INTERNALS$2].headers;
- }
-
- get redirect() {
- return this[INTERNALS$2].redirect;
- }
-
- get signal() {
- return this[INTERNALS$2].signal;
- }
-
- /**
- * Clone this request
- *
- * @return Request
- */
- clone() {
- return new Request(this);
- }
-}
-
-Body.mixIn(Request.prototype);
-
-Object.defineProperty(Request.prototype, Symbol.toStringTag, {
- value: 'Request',
- writable: false,
- enumerable: false,
- configurable: true
-});
-
-Object.defineProperties(Request.prototype, {
- method: { enumerable: true },
- url: { enumerable: true },
- headers: { enumerable: true },
- redirect: { enumerable: true },
- clone: { enumerable: true },
- signal: { enumerable: true }
-});
-
-/**
- * Convert a Request to Node.js http request options.
- *
- * @param Request A Request instance
- * @return Object The options object to be passed to http.request
- */
-function getNodeRequestOptions(request) {
- const parsedURL = request[INTERNALS$2].parsedURL;
- const headers = new Headers(request[INTERNALS$2].headers);
-
- // fetch step 1.3
- if (!headers.has('Accept')) {
- headers.set('Accept', '*/*');
- }
-
- // Basic fetch
- if (!parsedURL.protocol || !parsedURL.hostname) {
- throw new TypeError('Only absolute URLs are supported');
- }
-
- if (!/^https?:$/.test(parsedURL.protocol)) {
- throw new TypeError('Only HTTP(S) protocols are supported');
- }
-
- if (request.signal && request.body instanceof Stream.Readable && !streamDestructionSupported) {
- throw new Error('Cancellation of streamed requests with AbortSignal is not supported in node < 8');
- }
-
- // HTTP-network-or-cache fetch steps 2.4-2.7
- let contentLengthValue = null;
- if (request.body == null && /^(POST|PUT)$/i.test(request.method)) {
- contentLengthValue = '0';
- }
- if (request.body != null) {
- const totalBytes = getTotalBytes(request);
- if (typeof totalBytes === 'number') {
- contentLengthValue = String(totalBytes);
- }
- }
- if (contentLengthValue) {
- headers.set('Content-Length', contentLengthValue);
- }
-
- // HTTP-network-or-cache fetch step 2.11
- if (!headers.has('User-Agent')) {
- headers.set('User-Agent', 'node-fetch/1.0 (+https://github.com/bitinn/node-fetch)');
- }
-
- // HTTP-network-or-cache fetch step 2.15
- if (request.compress && !headers.has('Accept-Encoding')) {
- headers.set('Accept-Encoding', 'gzip,deflate');
- }
-
- let agent = request.agent;
- if (typeof agent === 'function') {
- agent = agent(parsedURL);
- }
-
- // HTTP-network fetch step 4.2
- // chunked encoding is handled by Node.js
-
- return Object.assign({}, parsedURL, {
- method: request.method,
- headers: exportNodeCompatibleHeaders(headers),
- agent
- });
-}
-
-/**
- * abort-error.js
- *
- * AbortError interface for cancelled requests
- */
-
-/**
- * Create AbortError instance
- *
- * @param String message Error message for human
- * @return AbortError
- */
-function AbortError(message) {
- Error.call(this, message);
-
- this.type = 'aborted';
- this.message = message;
-
- // hide custom error implementation details from end-users
- Error.captureStackTrace(this, this.constructor);
-}
-
-AbortError.prototype = Object.create(Error.prototype);
-AbortError.prototype.constructor = AbortError;
-AbortError.prototype.name = 'AbortError';
-
-const URL$1 = Url.URL || whatwgUrl.URL;
-
-// fix an issue where "PassThrough", "resolve" aren't a named export for node <10
-const PassThrough$1 = Stream.PassThrough;
-
-const isDomainOrSubdomain = function isDomainOrSubdomain(destination, original) {
- const orig = new URL$1(original).hostname;
- const dest = new URL$1(destination).hostname;
-
- return orig === dest || orig[orig.length - dest.length - 1] === '.' && orig.endsWith(dest);
-};
-
-/**
- * isSameProtocol reports whether the two provided URLs use the same protocol.
- *
- * Both domains must already be in canonical form.
- * @param {string|URL} original
- * @param {string|URL} destination
- */
-const isSameProtocol = function isSameProtocol(destination, original) {
- const orig = new URL$1(original).protocol;
- const dest = new URL$1(destination).protocol;
-
- return orig === dest;
-};
-
-/**
- * Fetch function
- *
- * @param Mixed url Absolute url or Request instance
- * @param Object opts Fetch options
- * @return Promise
- */
-function fetch(url, opts) {
-
- // allow custom promise
- if (!fetch.Promise) {
- throw new Error('native promise missing, set fetch.Promise to your favorite alternative');
- }
-
- Body.Promise = fetch.Promise;
-
- // wrap http.request into fetch
- return new fetch.Promise(function (resolve, reject) {
- // build request object
- const request = new Request(url, opts);
- const options = getNodeRequestOptions(request);
-
- const send = (options.protocol === 'https:' ? https : http).request;
- const signal = request.signal;
-
- let response = null;
-
- const abort = function abort() {
- let error = new AbortError('The user aborted a request.');
- reject(error);
- if (request.body && request.body instanceof Stream.Readable) {
- destroyStream(request.body, error);
- }
- if (!response || !response.body) return;
- response.body.emit('error', error);
- };
-
- if (signal && signal.aborted) {
- abort();
- return;
- }
-
- const abortAndFinalize = function abortAndFinalize() {
- abort();
- finalize();
- };
-
- // send request
- const req = send(options);
- let reqTimeout;
-
- if (signal) {
- signal.addEventListener('abort', abortAndFinalize);
- }
-
- function finalize() {
- req.abort();
- if (signal) signal.removeEventListener('abort', abortAndFinalize);
- clearTimeout(reqTimeout);
- }
-
- if (request.timeout) {
- req.once('socket', function (socket) {
- reqTimeout = setTimeout(function () {
- reject(new FetchError(`network timeout at: ${request.url}`, 'request-timeout'));
- finalize();
- }, request.timeout);
- });
- }
-
- req.on('error', function (err) {
- reject(new FetchError(`request to ${request.url} failed, reason: ${err.message}`, 'system', err));
-
- if (response && response.body) {
- destroyStream(response.body, err);
- }
-
- finalize();
- });
-
- fixResponseChunkedTransferBadEnding(req, function (err) {
- if (signal && signal.aborted) {
- return;
- }
-
- if (response && response.body) {
- destroyStream(response.body, err);
- }
- });
-
- /* c8 ignore next 18 */
- if (parseInt(process.version.substring(1)) < 14) {
- // Before Node.js 14, pipeline() does not fully support async iterators and does not always
- // properly handle when the socket close/end events are out of order.
- req.on('socket', function (s) {
- s.addListener('close', function (hadError) {
- // if a data listener is still present we didn't end cleanly
- const hasDataListener = s.listenerCount('data') > 0;
-
- // if end happened before close but the socket didn't emit an error, do it now
- if (response && hasDataListener && !hadError && !(signal && signal.aborted)) {
- const err = new Error('Premature close');
- err.code = 'ERR_STREAM_PREMATURE_CLOSE';
- response.body.emit('error', err);
- }
- });
- });
- }
-
- req.on('response', function (res) {
- clearTimeout(reqTimeout);
-
- const headers = createHeadersLenient(res.headers);
-
- // HTTP fetch step 5
- if (fetch.isRedirect(res.statusCode)) {
- // HTTP fetch step 5.2
- const location = headers.get('Location');
-
- // HTTP fetch step 5.3
- let locationURL = null;
- try {
- locationURL = location === null ? null : new URL$1(location, request.url).toString();
- } catch (err) {
- // error here can only be invalid URL in Location: header
- // do not throw when options.redirect == manual
- // let the user extract the errorneous redirect URL
- if (request.redirect !== 'manual') {
- reject(new FetchError(`uri requested responds with an invalid redirect URL: ${location}`, 'invalid-redirect'));
- finalize();
- return;
- }
- }
-
- // HTTP fetch step 5.5
- switch (request.redirect) {
- case 'error':
- reject(new FetchError(`uri requested responds with a redirect, redirect mode is set to error: ${request.url}`, 'no-redirect'));
- finalize();
- return;
- case 'manual':
- // node-fetch-specific step: make manual redirect a bit easier to use by setting the Location header value to the resolved URL.
- if (locationURL !== null) {
- // handle corrupted header
- try {
- headers.set('Location', locationURL);
- } catch (err) {
- // istanbul ignore next: nodejs server prevent invalid response headers, we can't test this through normal request
- reject(err);
- }
- }
- break;
- case 'follow':
- // HTTP-redirect fetch step 2
- if (locationURL === null) {
- break;
- }
-
- // HTTP-redirect fetch step 5
- if (request.counter >= request.follow) {
- reject(new FetchError(`maximum redirect reached at: ${request.url}`, 'max-redirect'));
- finalize();
- return;
- }
-
- // HTTP-redirect fetch step 6 (counter increment)
- // Create a new Request object.
- const requestOpts = {
- headers: new Headers(request.headers),
- follow: request.follow,
- counter: request.counter + 1,
- agent: request.agent,
- compress: request.compress,
- method: request.method,
- body: request.body,
- signal: request.signal,
- timeout: request.timeout,
- size: request.size
- };
-
- if (!isDomainOrSubdomain(request.url, locationURL) || !isSameProtocol(request.url, locationURL)) {
- for (const name of ['authorization', 'www-authenticate', 'cookie', 'cookie2']) {
- requestOpts.headers.delete(name);
- }
- }
-
- // HTTP-redirect fetch step 9
- if (res.statusCode !== 303 && request.body && getTotalBytes(request) === null) {
- reject(new FetchError('Cannot follow redirect with body being a readable stream', 'unsupported-redirect'));
- finalize();
- return;
- }
-
- // HTTP-redirect fetch step 11
- if (res.statusCode === 303 || (res.statusCode === 301 || res.statusCode === 302) && request.method === 'POST') {
- requestOpts.method = 'GET';
- requestOpts.body = undefined;
- requestOpts.headers.delete('content-length');
- }
-
- // HTTP-redirect fetch step 15
- resolve(fetch(new Request(locationURL, requestOpts)));
- finalize();
- return;
- }
- }
-
- // prepare response
- res.once('end', function () {
- if (signal) signal.removeEventListener('abort', abortAndFinalize);
- });
- let body = res.pipe(new PassThrough$1());
-
- const response_options = {
- url: request.url,
- status: res.statusCode,
- statusText: res.statusMessage,
- headers: headers,
- size: request.size,
- timeout: request.timeout,
- counter: request.counter
- };
-
- // HTTP-network fetch step 12.1.1.3
- const codings = headers.get('Content-Encoding');
-
- // HTTP-network fetch step 12.1.1.4: handle content codings
-
- // in following scenarios we ignore compression support
- // 1. compression support is disabled
- // 2. HEAD request
- // 3. no Content-Encoding header
- // 4. no content response (204)
- // 5. content not modified response (304)
- if (!request.compress || request.method === 'HEAD' || codings === null || res.statusCode === 204 || res.statusCode === 304) {
- response = new Response(body, response_options);
- resolve(response);
- return;
- }
-
- // For Node v6+
- // Be less strict when decoding compressed responses, since sometimes
- // servers send slightly invalid responses that are still accepted
- // by common browsers.
- // Always using Z_SYNC_FLUSH is what cURL does.
- const zlibOptions = {
- flush: zlib.Z_SYNC_FLUSH,
- finishFlush: zlib.Z_SYNC_FLUSH
- };
-
- // for gzip
- if (codings == 'gzip' || codings == 'x-gzip') {
- body = body.pipe(zlib.createGunzip(zlibOptions));
- response = new Response(body, response_options);
- resolve(response);
- return;
- }
-
- // for deflate
- if (codings == 'deflate' || codings == 'x-deflate') {
- // handle the infamous raw deflate response from old servers
- // a hack for old IIS and Apache servers
- const raw = res.pipe(new PassThrough$1());
- raw.once('data', function (chunk) {
- // see http://stackoverflow.com/questions/37519828
- if ((chunk[0] & 0x0F) === 0x08) {
- body = body.pipe(zlib.createInflate());
- } else {
- body = body.pipe(zlib.createInflateRaw());
- }
- response = new Response(body, response_options);
- resolve(response);
- });
- raw.on('end', function () {
- // some old IIS servers return zero-length OK deflate responses, so 'data' is never emitted.
- if (!response) {
- response = new Response(body, response_options);
- resolve(response);
- }
- });
- return;
- }
-
- // for br
- if (codings == 'br' && typeof zlib.createBrotliDecompress === 'function') {
- body = body.pipe(zlib.createBrotliDecompress());
- response = new Response(body, response_options);
- resolve(response);
- return;
- }
-
- // otherwise, use response as-is
- response = new Response(body, response_options);
- resolve(response);
- });
-
- writeToStream(req, request);
- });
-}
-function fixResponseChunkedTransferBadEnding(request, errorCallback) {
- let socket;
-
- request.on('socket', function (s) {
- socket = s;
- });
-
- request.on('response', function (response) {
- const headers = response.headers;
-
- if (headers['transfer-encoding'] === 'chunked' && !headers['content-length']) {
- response.once('close', function (hadError) {
- // tests for socket presence, as in some situations the
- // the 'socket' event is not triggered for the request
- // (happens in deno), avoids `TypeError`
- // if a data listener is still present we didn't end cleanly
- const hasDataListener = socket && socket.listenerCount('data') > 0;
-
- if (hasDataListener && !hadError) {
- const err = new Error('Premature close');
- err.code = 'ERR_STREAM_PREMATURE_CLOSE';
- errorCallback(err);
- }
- });
- }
- });
-}
-
-function destroyStream(stream, err) {
- if (stream.destroy) {
- stream.destroy(err);
- } else {
- // node < 8
- stream.emit('error', err);
- stream.end();
- }
-}
-
-/**
- * Redirect code matching
- *
- * @param Number code Status code
- * @return Boolean
- */
-fetch.isRedirect = function (code) {
- return code === 301 || code === 302 || code === 303 || code === 307 || code === 308;
-};
-
-// expose Promise
-fetch.Promise = global.Promise;
-
-module.exports = exports = fetch;
-Object.defineProperty(exports, "__esModule", { value: true });
-exports.default = exports;
-exports.Headers = Headers;
-exports.Request = Request;
-exports.Response = Response;
-exports.FetchError = FetchError;
-exports.AbortError = AbortError;
diff --git a/backend/node_modules/node-fetch/lib/index.mjs b/backend/node_modules/node-fetch/lib/index.mjs
deleted file mode 100644
index 2863dd9..0000000
--- a/backend/node_modules/node-fetch/lib/index.mjs
+++ /dev/null
@@ -1,1775 +0,0 @@
-import Stream from 'stream';
-import http from 'http';
-import Url from 'url';
-import whatwgUrl from 'whatwg-url';
-import https from 'https';
-import zlib from 'zlib';
-
-// Based on https://github.com/tmpvar/jsdom/blob/aa85b2abf07766ff7bf5c1f6daafb3726f2f2db5/lib/jsdom/living/blob.js
-
-// fix for "Readable" isn't a named export issue
-const Readable = Stream.Readable;
-
-const BUFFER = Symbol('buffer');
-const TYPE = Symbol('type');
-
-class Blob {
- constructor() {
- this[TYPE] = '';
-
- const blobParts = arguments[0];
- const options = arguments[1];
-
- const buffers = [];
- let size = 0;
-
- if (blobParts) {
- const a = blobParts;
- const length = Number(a.length);
- for (let i = 0; i < length; i++) {
- const element = a[i];
- let buffer;
- if (element instanceof Buffer) {
- buffer = element;
- } else if (ArrayBuffer.isView(element)) {
- buffer = Buffer.from(element.buffer, element.byteOffset, element.byteLength);
- } else if (element instanceof ArrayBuffer) {
- buffer = Buffer.from(element);
- } else if (element instanceof Blob) {
- buffer = element[BUFFER];
- } else {
- buffer = Buffer.from(typeof element === 'string' ? element : String(element));
- }
- size += buffer.length;
- buffers.push(buffer);
- }
- }
-
- this[BUFFER] = Buffer.concat(buffers);
-
- let type = options && options.type !== undefined && String(options.type).toLowerCase();
- if (type && !/[^\u0020-\u007E]/.test(type)) {
- this[TYPE] = type;
- }
- }
- get size() {
- return this[BUFFER].length;
- }
- get type() {
- return this[TYPE];
- }
- text() {
- return Promise.resolve(this[BUFFER].toString());
- }
- arrayBuffer() {
- const buf = this[BUFFER];
- const ab = buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
- return Promise.resolve(ab);
- }
- stream() {
- const readable = new Readable();
- readable._read = function () {};
- readable.push(this[BUFFER]);
- readable.push(null);
- return readable;
- }
- toString() {
- return '[object Blob]';
- }
- slice() {
- const size = this.size;
-
- const start = arguments[0];
- const end = arguments[1];
- let relativeStart, relativeEnd;
- if (start === undefined) {
- relativeStart = 0;
- } else if (start < 0) {
- relativeStart = Math.max(size + start, 0);
- } else {
- relativeStart = Math.min(start, size);
- }
- if (end === undefined) {
- relativeEnd = size;
- } else if (end < 0) {
- relativeEnd = Math.max(size + end, 0);
- } else {
- relativeEnd = Math.min(end, size);
- }
- const span = Math.max(relativeEnd - relativeStart, 0);
-
- const buffer = this[BUFFER];
- const slicedBuffer = buffer.slice(relativeStart, relativeStart + span);
- const blob = new Blob([], { type: arguments[2] });
- blob[BUFFER] = slicedBuffer;
- return blob;
- }
-}
-
-Object.defineProperties(Blob.prototype, {
- size: { enumerable: true },
- type: { enumerable: true },
- slice: { enumerable: true }
-});
-
-Object.defineProperty(Blob.prototype, Symbol.toStringTag, {
- value: 'Blob',
- writable: false,
- enumerable: false,
- configurable: true
-});
-
-/**
- * fetch-error.js
- *
- * FetchError interface for operational errors
- */
-
-/**
- * Create FetchError instance
- *
- * @param String message Error message for human
- * @param String type Error type for machine
- * @param String systemError For Node.js system error
- * @return FetchError
- */
-function FetchError(message, type, systemError) {
- Error.call(this, message);
-
- this.message = message;
- this.type = type;
-
- // when err.type is `system`, err.code contains system error code
- if (systemError) {
- this.code = this.errno = systemError.code;
- }
-
- // hide custom error implementation details from end-users
- Error.captureStackTrace(this, this.constructor);
-}
-
-FetchError.prototype = Object.create(Error.prototype);
-FetchError.prototype.constructor = FetchError;
-FetchError.prototype.name = 'FetchError';
-
-let convert;
-try {
- convert = require('encoding').convert;
-} catch (e) {}
-
-const INTERNALS = Symbol('Body internals');
-
-// fix an issue where "PassThrough" isn't a named export for node <10
-const PassThrough = Stream.PassThrough;
-
-/**
- * Body mixin
- *
- * Ref: https://fetch.spec.whatwg.org/#body
- *
- * @param Stream body Readable stream
- * @param Object opts Response options
- * @return Void
- */
-function Body(body) {
- var _this = this;
-
- var _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
- _ref$size = _ref.size;
-
- let size = _ref$size === undefined ? 0 : _ref$size;
- var _ref$timeout = _ref.timeout;
- let timeout = _ref$timeout === undefined ? 0 : _ref$timeout;
-
- if (body == null) {
- // body is undefined or null
- body = null;
- } else if (isURLSearchParams(body)) {
- // body is a URLSearchParams
- body = Buffer.from(body.toString());
- } else if (isBlob(body)) ; else if (Buffer.isBuffer(body)) ; else if (Object.prototype.toString.call(body) === '[object ArrayBuffer]') {
- // body is ArrayBuffer
- body = Buffer.from(body);
- } else if (ArrayBuffer.isView(body)) {
- // body is ArrayBufferView
- body = Buffer.from(body.buffer, body.byteOffset, body.byteLength);
- } else if (body instanceof Stream) ; else {
- // none of the above
- // coerce to string then buffer
- body = Buffer.from(String(body));
- }
- this[INTERNALS] = {
- body,
- disturbed: false,
- error: null
- };
- this.size = size;
- this.timeout = timeout;
-
- if (body instanceof Stream) {
- body.on('error', function (err) {
- const error = err.name === 'AbortError' ? err : new FetchError(`Invalid response body while trying to fetch ${_this.url}: ${err.message}`, 'system', err);
- _this[INTERNALS].error = error;
- });
- }
-}
-
-Body.prototype = {
- get body() {
- return this[INTERNALS].body;
- },
-
- get bodyUsed() {
- return this[INTERNALS].disturbed;
- },
-
- /**
- * Decode response as ArrayBuffer
- *
- * @return Promise
- */
- arrayBuffer() {
- return consumeBody.call(this).then(function (buf) {
- return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
- });
- },
-
- /**
- * Return raw response as Blob
- *
- * @return Promise
- */
- blob() {
- let ct = this.headers && this.headers.get('content-type') || '';
- return consumeBody.call(this).then(function (buf) {
- return Object.assign(
- // Prevent copying
- new Blob([], {
- type: ct.toLowerCase()
- }), {
- [BUFFER]: buf
- });
- });
- },
-
- /**
- * Decode response as json
- *
- * @return Promise
- */
- json() {
- var _this2 = this;
-
- return consumeBody.call(this).then(function (buffer) {
- try {
- return JSON.parse(buffer.toString());
- } catch (err) {
- return Body.Promise.reject(new FetchError(`invalid json response body at ${_this2.url} reason: ${err.message}`, 'invalid-json'));
- }
- });
- },
-
- /**
- * Decode response as text
- *
- * @return Promise
- */
- text() {
- return consumeBody.call(this).then(function (buffer) {
- return buffer.toString();
- });
- },
-
- /**
- * Decode response as buffer (non-spec api)
- *
- * @return Promise
- */
- buffer() {
- return consumeBody.call(this);
- },
-
- /**
- * Decode response as text, while automatically detecting the encoding and
- * trying to decode to UTF-8 (non-spec api)
- *
- * @return Promise
- */
- textConverted() {
- var _this3 = this;
-
- return consumeBody.call(this).then(function (buffer) {
- return convertBody(buffer, _this3.headers);
- });
- }
-};
-
-// In browsers, all properties are enumerable.
-Object.defineProperties(Body.prototype, {
- body: { enumerable: true },
- bodyUsed: { enumerable: true },
- arrayBuffer: { enumerable: true },
- blob: { enumerable: true },
- json: { enumerable: true },
- text: { enumerable: true }
-});
-
-Body.mixIn = function (proto) {
- for (const name of Object.getOwnPropertyNames(Body.prototype)) {
- // istanbul ignore else: future proof
- if (!(name in proto)) {
- const desc = Object.getOwnPropertyDescriptor(Body.prototype, name);
- Object.defineProperty(proto, name, desc);
- }
- }
-};
-
-/**
- * Consume and convert an entire Body to a Buffer.
- *
- * Ref: https://fetch.spec.whatwg.org/#concept-body-consume-body
- *
- * @return Promise
- */
-function consumeBody() {
- var _this4 = this;
-
- if (this[INTERNALS].disturbed) {
- return Body.Promise.reject(new TypeError(`body used already for: ${this.url}`));
- }
-
- this[INTERNALS].disturbed = true;
-
- if (this[INTERNALS].error) {
- return Body.Promise.reject(this[INTERNALS].error);
- }
-
- let body = this.body;
-
- // body is null
- if (body === null) {
- return Body.Promise.resolve(Buffer.alloc(0));
- }
-
- // body is blob
- if (isBlob(body)) {
- body = body.stream();
- }
-
- // body is buffer
- if (Buffer.isBuffer(body)) {
- return Body.Promise.resolve(body);
- }
-
- // istanbul ignore if: should never happen
- if (!(body instanceof Stream)) {
- return Body.Promise.resolve(Buffer.alloc(0));
- }
-
- // body is stream
- // get ready to actually consume the body
- let accum = [];
- let accumBytes = 0;
- let abort = false;
-
- return new Body.Promise(function (resolve, reject) {
- let resTimeout;
-
- // allow timeout on slow response body
- if (_this4.timeout) {
- resTimeout = setTimeout(function () {
- abort = true;
- reject(new FetchError(`Response timeout while trying to fetch ${_this4.url} (over ${_this4.timeout}ms)`, 'body-timeout'));
- }, _this4.timeout);
- }
-
- // handle stream errors
- body.on('error', function (err) {
- if (err.name === 'AbortError') {
- // if the request was aborted, reject with this Error
- abort = true;
- reject(err);
- } else {
- // other errors, such as incorrect content-encoding
- reject(new FetchError(`Invalid response body while trying to fetch ${_this4.url}: ${err.message}`, 'system', err));
- }
- });
-
- body.on('data', function (chunk) {
- if (abort || chunk === null) {
- return;
- }
-
- if (_this4.size && accumBytes + chunk.length > _this4.size) {
- abort = true;
- reject(new FetchError(`content size at ${_this4.url} over limit: ${_this4.size}`, 'max-size'));
- return;
- }
-
- accumBytes += chunk.length;
- accum.push(chunk);
- });
-
- body.on('end', function () {
- if (abort) {
- return;
- }
-
- clearTimeout(resTimeout);
-
- try {
- resolve(Buffer.concat(accum, accumBytes));
- } catch (err) {
- // handle streams that have accumulated too much data (issue #414)
- reject(new FetchError(`Could not create Buffer from response body for ${_this4.url}: ${err.message}`, 'system', err));
- }
- });
- });
-}
-
-/**
- * Detect buffer encoding and convert to target encoding
- * ref: http://www.w3.org/TR/2011/WD-html5-20110113/parsing.html#determining-the-character-encoding
- *
- * @param Buffer buffer Incoming buffer
- * @param String encoding Target encoding
- * @return String
- */
-function convertBody(buffer, headers) {
- if (typeof convert !== 'function') {
- throw new Error('The package `encoding` must be installed to use the textConverted() function');
- }
-
- const ct = headers.get('content-type');
- let charset = 'utf-8';
- let res, str;
-
- // header
- if (ct) {
- res = /charset=([^;]*)/i.exec(ct);
- }
-
- // no charset in content type, peek at response body for at most 1024 bytes
- str = buffer.slice(0, 1024).toString();
-
- // html5
- if (!res && str) {
- res = / 0 && arguments[0] !== undefined ? arguments[0] : undefined;
-
- this[MAP] = Object.create(null);
-
- if (init instanceof Headers) {
- const rawHeaders = init.raw();
- const headerNames = Object.keys(rawHeaders);
-
- for (const headerName of headerNames) {
- for (const value of rawHeaders[headerName]) {
- this.append(headerName, value);
- }
- }
-
- return;
- }
-
- // We don't worry about converting prop to ByteString here as append()
- // will handle it.
- if (init == null) ; else if (typeof init === 'object') {
- const method = init[Symbol.iterator];
- if (method != null) {
- if (typeof method !== 'function') {
- throw new TypeError('Header pairs must be iterable');
- }
-
- // sequence>
- // Note: per spec we have to first exhaust the lists then process them
- const pairs = [];
- for (const pair of init) {
- if (typeof pair !== 'object' || typeof pair[Symbol.iterator] !== 'function') {
- throw new TypeError('Each header pair must be iterable');
- }
- pairs.push(Array.from(pair));
- }
-
- for (const pair of pairs) {
- if (pair.length !== 2) {
- throw new TypeError('Each header pair must be a name/value tuple');
- }
- this.append(pair[0], pair[1]);
- }
- } else {
- // record
- for (const key of Object.keys(init)) {
- const value = init[key];
- this.append(key, value);
- }
- }
- } else {
- throw new TypeError('Provided initializer must be an object');
- }
- }
-
- /**
- * Return combined header value given name
- *
- * @param String name Header name
- * @return Mixed
- */
- get(name) {
- name = `${name}`;
- validateName(name);
- const key = find(this[MAP], name);
- if (key === undefined) {
- return null;
- }
-
- return this[MAP][key].join(', ');
- }
-
- /**
- * Iterate over all headers
- *
- * @param Function callback Executed for each item with parameters (value, name, thisArg)
- * @param Boolean thisArg `this` context for callback function
- * @return Void
- */
- forEach(callback) {
- let thisArg = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : undefined;
-
- let pairs = getHeaders(this);
- let i = 0;
- while (i < pairs.length) {
- var _pairs$i = pairs[i];
- const name = _pairs$i[0],
- value = _pairs$i[1];
-
- callback.call(thisArg, value, name, this);
- pairs = getHeaders(this);
- i++;
- }
- }
-
- /**
- * Overwrite header values given name
- *
- * @param String name Header name
- * @param String value Header value
- * @return Void
- */
- set(name, value) {
- name = `${name}`;
- value = `${value}`;
- validateName(name);
- validateValue(value);
- const key = find(this[MAP], name);
- this[MAP][key !== undefined ? key : name] = [value];
- }
-
- /**
- * Append a value onto existing header
- *
- * @param String name Header name
- * @param String value Header value
- * @return Void
- */
- append(name, value) {
- name = `${name}`;
- value = `${value}`;
- validateName(name);
- validateValue(value);
- const key = find(this[MAP], name);
- if (key !== undefined) {
- this[MAP][key].push(value);
- } else {
- this[MAP][name] = [value];
- }
- }
-
- /**
- * Check for header name existence
- *
- * @param String name Header name
- * @return Boolean
- */
- has(name) {
- name = `${name}`;
- validateName(name);
- return find(this[MAP], name) !== undefined;
- }
-
- /**
- * Delete all header values given name
- *
- * @param String name Header name
- * @return Void
- */
- delete(name) {
- name = `${name}`;
- validateName(name);
- const key = find(this[MAP], name);
- if (key !== undefined) {
- delete this[MAP][key];
- }
- }
-
- /**
- * Return raw headers (non-spec api)
- *
- * @return Object
- */
- raw() {
- return this[MAP];
- }
-
- /**
- * Get an iterator on keys.
- *
- * @return Iterator
- */
- keys() {
- return createHeadersIterator(this, 'key');
- }
-
- /**
- * Get an iterator on values.
- *
- * @return Iterator
- */
- values() {
- return createHeadersIterator(this, 'value');
- }
-
- /**
- * Get an iterator on entries.
- *
- * This is the default iterator of the Headers object.
- *
- * @return Iterator
- */
- [Symbol.iterator]() {
- return createHeadersIterator(this, 'key+value');
- }
-}
-Headers.prototype.entries = Headers.prototype[Symbol.iterator];
-
-Object.defineProperty(Headers.prototype, Symbol.toStringTag, {
- value: 'Headers',
- writable: false,
- enumerable: false,
- configurable: true
-});
-
-Object.defineProperties(Headers.prototype, {
- get: { enumerable: true },
- forEach: { enumerable: true },
- set: { enumerable: true },
- append: { enumerable: true },
- has: { enumerable: true },
- delete: { enumerable: true },
- keys: { enumerable: true },
- values: { enumerable: true },
- entries: { enumerable: true }
-});
-
-function getHeaders(headers) {
- let kind = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'key+value';
-
- const keys = Object.keys(headers[MAP]).sort();
- return keys.map(kind === 'key' ? function (k) {
- return k.toLowerCase();
- } : kind === 'value' ? function (k) {
- return headers[MAP][k].join(', ');
- } : function (k) {
- return [k.toLowerCase(), headers[MAP][k].join(', ')];
- });
-}
-
-const INTERNAL = Symbol('internal');
-
-function createHeadersIterator(target, kind) {
- const iterator = Object.create(HeadersIteratorPrototype);
- iterator[INTERNAL] = {
- target,
- kind,
- index: 0
- };
- return iterator;
-}
-
-const HeadersIteratorPrototype = Object.setPrototypeOf({
- next() {
- // istanbul ignore if
- if (!this || Object.getPrototypeOf(this) !== HeadersIteratorPrototype) {
- throw new TypeError('Value of `this` is not a HeadersIterator');
- }
-
- var _INTERNAL = this[INTERNAL];
- const target = _INTERNAL.target,
- kind = _INTERNAL.kind,
- index = _INTERNAL.index;
-
- const values = getHeaders(target, kind);
- const len = values.length;
- if (index >= len) {
- return {
- value: undefined,
- done: true
- };
- }
-
- this[INTERNAL].index = index + 1;
-
- return {
- value: values[index],
- done: false
- };
- }
-}, Object.getPrototypeOf(Object.getPrototypeOf([][Symbol.iterator]())));
-
-Object.defineProperty(HeadersIteratorPrototype, Symbol.toStringTag, {
- value: 'HeadersIterator',
- writable: false,
- enumerable: false,
- configurable: true
-});
-
-/**
- * Export the Headers object in a form that Node.js can consume.
- *
- * @param Headers headers
- * @return Object
- */
-function exportNodeCompatibleHeaders(headers) {
- const obj = Object.assign({ __proto__: null }, headers[MAP]);
-
- // http.request() only supports string as Host header. This hack makes
- // specifying custom Host header possible.
- const hostHeaderKey = find(headers[MAP], 'Host');
- if (hostHeaderKey !== undefined) {
- obj[hostHeaderKey] = obj[hostHeaderKey][0];
- }
-
- return obj;
-}
-
-/**
- * Create a Headers object from an object of headers, ignoring those that do
- * not conform to HTTP grammar productions.
- *
- * @param Object obj Object of headers
- * @return Headers
- */
-function createHeadersLenient(obj) {
- const headers = new Headers();
- for (const name of Object.keys(obj)) {
- if (invalidTokenRegex.test(name)) {
- continue;
- }
- if (Array.isArray(obj[name])) {
- for (const val of obj[name]) {
- if (invalidHeaderCharRegex.test(val)) {
- continue;
- }
- if (headers[MAP][name] === undefined) {
- headers[MAP][name] = [val];
- } else {
- headers[MAP][name].push(val);
- }
- }
- } else if (!invalidHeaderCharRegex.test(obj[name])) {
- headers[MAP][name] = [obj[name]];
- }
- }
- return headers;
-}
-
-const INTERNALS$1 = Symbol('Response internals');
-
-// fix an issue where "STATUS_CODES" aren't a named export for node <10
-const STATUS_CODES = http.STATUS_CODES;
-
-/**
- * Response class
- *
- * @param Stream body Readable stream
- * @param Object opts Response options
- * @return Void
- */
-class Response {
- constructor() {
- let body = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
- let opts = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
-
- Body.call(this, body, opts);
-
- const status = opts.status || 200;
- const headers = new Headers(opts.headers);
-
- if (body != null && !headers.has('Content-Type')) {
- const contentType = extractContentType(body);
- if (contentType) {
- headers.append('Content-Type', contentType);
- }
- }
-
- this[INTERNALS$1] = {
- url: opts.url,
- status,
- statusText: opts.statusText || STATUS_CODES[status],
- headers,
- counter: opts.counter
- };
- }
-
- get url() {
- return this[INTERNALS$1].url || '';
- }
-
- get status() {
- return this[INTERNALS$1].status;
- }
-
- /**
- * Convenience property representing if the request ended normally
- */
- get ok() {
- return this[INTERNALS$1].status >= 200 && this[INTERNALS$1].status < 300;
- }
-
- get redirected() {
- return this[INTERNALS$1].counter > 0;
- }
-
- get statusText() {
- return this[INTERNALS$1].statusText;
- }
-
- get headers() {
- return this[INTERNALS$1].headers;
- }
-
- /**
- * Clone this response
- *
- * @return Response
- */
- clone() {
- return new Response(clone(this), {
- url: this.url,
- status: this.status,
- statusText: this.statusText,
- headers: this.headers,
- ok: this.ok,
- redirected: this.redirected
- });
- }
-}
-
-Body.mixIn(Response.prototype);
-
-Object.defineProperties(Response.prototype, {
- url: { enumerable: true },
- status: { enumerable: true },
- ok: { enumerable: true },
- redirected: { enumerable: true },
- statusText: { enumerable: true },
- headers: { enumerable: true },
- clone: { enumerable: true }
-});
-
-Object.defineProperty(Response.prototype, Symbol.toStringTag, {
- value: 'Response',
- writable: false,
- enumerable: false,
- configurable: true
-});
-
-const INTERNALS$2 = Symbol('Request internals');
-const URL = Url.URL || whatwgUrl.URL;
-
-// fix an issue where "format", "parse" aren't a named export for node <10
-const parse_url = Url.parse;
-const format_url = Url.format;
-
-/**
- * Wrapper around `new URL` to handle arbitrary URLs
- *
- * @param {string} urlStr
- * @return {void}
- */
-function parseURL(urlStr) {
- /*
- Check whether the URL is absolute or not
- Scheme: https://tools.ietf.org/html/rfc3986#section-3.1
- Absolute URL: https://tools.ietf.org/html/rfc3986#section-4.3
- */
- if (/^[a-zA-Z][a-zA-Z\d+\-.]*:/.exec(urlStr)) {
- urlStr = new URL(urlStr).toString();
- }
-
- // Fallback to old implementation for arbitrary URLs
- return parse_url(urlStr);
-}
-
-const streamDestructionSupported = 'destroy' in Stream.Readable.prototype;
-
-/**
- * Check if a value is an instance of Request.
- *
- * @param Mixed input
- * @return Boolean
- */
-function isRequest(input) {
- return typeof input === 'object' && typeof input[INTERNALS$2] === 'object';
-}
-
-function isAbortSignal(signal) {
- const proto = signal && typeof signal === 'object' && Object.getPrototypeOf(signal);
- return !!(proto && proto.constructor.name === 'AbortSignal');
-}
-
-/**
- * Request class
- *
- * @param Mixed input Url or Request instance
- * @param Object init Custom options
- * @return Void
- */
-class Request {
- constructor(input) {
- let init = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
-
- let parsedURL;
-
- // normalize input
- if (!isRequest(input)) {
- if (input && input.href) {
- // in order to support Node.js' Url objects; though WHATWG's URL objects
- // will fall into this branch also (since their `toString()` will return
- // `href` property anyway)
- parsedURL = parseURL(input.href);
- } else {
- // coerce input to a string before attempting to parse
- parsedURL = parseURL(`${input}`);
- }
- input = {};
- } else {
- parsedURL = parseURL(input.url);
- }
-
- let method = init.method || input.method || 'GET';
- method = method.toUpperCase();
-
- if ((init.body != null || isRequest(input) && input.body !== null) && (method === 'GET' || method === 'HEAD')) {
- throw new TypeError('Request with GET/HEAD method cannot have body');
- }
-
- let inputBody = init.body != null ? init.body : isRequest(input) && input.body !== null ? clone(input) : null;
-
- Body.call(this, inputBody, {
- timeout: init.timeout || input.timeout || 0,
- size: init.size || input.size || 0
- });
-
- const headers = new Headers(init.headers || input.headers || {});
-
- if (inputBody != null && !headers.has('Content-Type')) {
- const contentType = extractContentType(inputBody);
- if (contentType) {
- headers.append('Content-Type', contentType);
- }
- }
-
- let signal = isRequest(input) ? input.signal : null;
- if ('signal' in init) signal = init.signal;
-
- if (signal != null && !isAbortSignal(signal)) {
- throw new TypeError('Expected signal to be an instanceof AbortSignal');
- }
-
- this[INTERNALS$2] = {
- method,
- redirect: init.redirect || input.redirect || 'follow',
- headers,
- parsedURL,
- signal
- };
-
- // node-fetch-only options
- this.follow = init.follow !== undefined ? init.follow : input.follow !== undefined ? input.follow : 20;
- this.compress = init.compress !== undefined ? init.compress : input.compress !== undefined ? input.compress : true;
- this.counter = init.counter || input.counter || 0;
- this.agent = init.agent || input.agent;
- }
-
- get method() {
- return this[INTERNALS$2].method;
- }
-
- get url() {
- return format_url(this[INTERNALS$2].parsedURL);
- }
-
- get headers() {
- return this[INTERNALS$2].headers;
- }
-
- get redirect() {
- return this[INTERNALS$2].redirect;
- }
-
- get signal() {
- return this[INTERNALS$2].signal;
- }
-
- /**
- * Clone this request
- *
- * @return Request
- */
- clone() {
- return new Request(this);
- }
-}
-
-Body.mixIn(Request.prototype);
-
-Object.defineProperty(Request.prototype, Symbol.toStringTag, {
- value: 'Request',
- writable: false,
- enumerable: false,
- configurable: true
-});
-
-Object.defineProperties(Request.prototype, {
- method: { enumerable: true },
- url: { enumerable: true },
- headers: { enumerable: true },
- redirect: { enumerable: true },
- clone: { enumerable: true },
- signal: { enumerable: true }
-});
-
-/**
- * Convert a Request to Node.js http request options.
- *
- * @param Request A Request instance
- * @return Object The options object to be passed to http.request
- */
-function getNodeRequestOptions(request) {
- const parsedURL = request[INTERNALS$2].parsedURL;
- const headers = new Headers(request[INTERNALS$2].headers);
-
- // fetch step 1.3
- if (!headers.has('Accept')) {
- headers.set('Accept', '*/*');
- }
-
- // Basic fetch
- if (!parsedURL.protocol || !parsedURL.hostname) {
- throw new TypeError('Only absolute URLs are supported');
- }
-
- if (!/^https?:$/.test(parsedURL.protocol)) {
- throw new TypeError('Only HTTP(S) protocols are supported');
- }
-
- if (request.signal && request.body instanceof Stream.Readable && !streamDestructionSupported) {
- throw new Error('Cancellation of streamed requests with AbortSignal is not supported in node < 8');
- }
-
- // HTTP-network-or-cache fetch steps 2.4-2.7
- let contentLengthValue = null;
- if (request.body == null && /^(POST|PUT)$/i.test(request.method)) {
- contentLengthValue = '0';
- }
- if (request.body != null) {
- const totalBytes = getTotalBytes(request);
- if (typeof totalBytes === 'number') {
- contentLengthValue = String(totalBytes);
- }
- }
- if (contentLengthValue) {
- headers.set('Content-Length', contentLengthValue);
- }
-
- // HTTP-network-or-cache fetch step 2.11
- if (!headers.has('User-Agent')) {
- headers.set('User-Agent', 'node-fetch/1.0 (+https://github.com/bitinn/node-fetch)');
- }
-
- // HTTP-network-or-cache fetch step 2.15
- if (request.compress && !headers.has('Accept-Encoding')) {
- headers.set('Accept-Encoding', 'gzip,deflate');
- }
-
- let agent = request.agent;
- if (typeof agent === 'function') {
- agent = agent(parsedURL);
- }
-
- // HTTP-network fetch step 4.2
- // chunked encoding is handled by Node.js
-
- return Object.assign({}, parsedURL, {
- method: request.method,
- headers: exportNodeCompatibleHeaders(headers),
- agent
- });
-}
-
-/**
- * abort-error.js
- *
- * AbortError interface for cancelled requests
- */
-
-/**
- * Create AbortError instance
- *
- * @param String message Error message for human
- * @return AbortError
- */
-function AbortError(message) {
- Error.call(this, message);
-
- this.type = 'aborted';
- this.message = message;
-
- // hide custom error implementation details from end-users
- Error.captureStackTrace(this, this.constructor);
-}
-
-AbortError.prototype = Object.create(Error.prototype);
-AbortError.prototype.constructor = AbortError;
-AbortError.prototype.name = 'AbortError';
-
-const URL$1 = Url.URL || whatwgUrl.URL;
-
-// fix an issue where "PassThrough", "resolve" aren't a named export for node <10
-const PassThrough$1 = Stream.PassThrough;
-
-const isDomainOrSubdomain = function isDomainOrSubdomain(destination, original) {
- const orig = new URL$1(original).hostname;
- const dest = new URL$1(destination).hostname;
-
- return orig === dest || orig[orig.length - dest.length - 1] === '.' && orig.endsWith(dest);
-};
-
-/**
- * isSameProtocol reports whether the two provided URLs use the same protocol.
- *
- * Both domains must already be in canonical form.
- * @param {string|URL} original
- * @param {string|URL} destination
- */
-const isSameProtocol = function isSameProtocol(destination, original) {
- const orig = new URL$1(original).protocol;
- const dest = new URL$1(destination).protocol;
-
- return orig === dest;
-};
-
-/**
- * Fetch function
- *
- * @param Mixed url Absolute url or Request instance
- * @param Object opts Fetch options
- * @return Promise
- */
-function fetch(url, opts) {
-
- // allow custom promise
- if (!fetch.Promise) {
- throw new Error('native promise missing, set fetch.Promise to your favorite alternative');
- }
-
- Body.Promise = fetch.Promise;
-
- // wrap http.request into fetch
- return new fetch.Promise(function (resolve, reject) {
- // build request object
- const request = new Request(url, opts);
- const options = getNodeRequestOptions(request);
-
- const send = (options.protocol === 'https:' ? https : http).request;
- const signal = request.signal;
-
- let response = null;
-
- const abort = function abort() {
- let error = new AbortError('The user aborted a request.');
- reject(error);
- if (request.body && request.body instanceof Stream.Readable) {
- destroyStream(request.body, error);
- }
- if (!response || !response.body) return;
- response.body.emit('error', error);
- };
-
- if (signal && signal.aborted) {
- abort();
- return;
- }
-
- const abortAndFinalize = function abortAndFinalize() {
- abort();
- finalize();
- };
-
- // send request
- const req = send(options);
- let reqTimeout;
-
- if (signal) {
- signal.addEventListener('abort', abortAndFinalize);
- }
-
- function finalize() {
- req.abort();
- if (signal) signal.removeEventListener('abort', abortAndFinalize);
- clearTimeout(reqTimeout);
- }
-
- if (request.timeout) {
- req.once('socket', function (socket) {
- reqTimeout = setTimeout(function () {
- reject(new FetchError(`network timeout at: ${request.url}`, 'request-timeout'));
- finalize();
- }, request.timeout);
- });
- }
-
- req.on('error', function (err) {
- reject(new FetchError(`request to ${request.url} failed, reason: ${err.message}`, 'system', err));
-
- if (response && response.body) {
- destroyStream(response.body, err);
- }
-
- finalize();
- });
-
- fixResponseChunkedTransferBadEnding(req, function (err) {
- if (signal && signal.aborted) {
- return;
- }
-
- if (response && response.body) {
- destroyStream(response.body, err);
- }
- });
-
- /* c8 ignore next 18 */
- if (parseInt(process.version.substring(1)) < 14) {
- // Before Node.js 14, pipeline() does not fully support async iterators and does not always
- // properly handle when the socket close/end events are out of order.
- req.on('socket', function (s) {
- s.addListener('close', function (hadError) {
- // if a data listener is still present we didn't end cleanly
- const hasDataListener = s.listenerCount('data') > 0;
-
- // if end happened before close but the socket didn't emit an error, do it now
- if (response && hasDataListener && !hadError && !(signal && signal.aborted)) {
- const err = new Error('Premature close');
- err.code = 'ERR_STREAM_PREMATURE_CLOSE';
- response.body.emit('error', err);
- }
- });
- });
- }
-
- req.on('response', function (res) {
- clearTimeout(reqTimeout);
-
- const headers = createHeadersLenient(res.headers);
-
- // HTTP fetch step 5
- if (fetch.isRedirect(res.statusCode)) {
- // HTTP fetch step 5.2
- const location = headers.get('Location');
-
- // HTTP fetch step 5.3
- let locationURL = null;
- try {
- locationURL = location === null ? null : new URL$1(location, request.url).toString();
- } catch (err) {
- // error here can only be invalid URL in Location: header
- // do not throw when options.redirect == manual
- // let the user extract the errorneous redirect URL
- if (request.redirect !== 'manual') {
- reject(new FetchError(`uri requested responds with an invalid redirect URL: ${location}`, 'invalid-redirect'));
- finalize();
- return;
- }
- }
-
- // HTTP fetch step 5.5
- switch (request.redirect) {
- case 'error':
- reject(new FetchError(`uri requested responds with a redirect, redirect mode is set to error: ${request.url}`, 'no-redirect'));
- finalize();
- return;
- case 'manual':
- // node-fetch-specific step: make manual redirect a bit easier to use by setting the Location header value to the resolved URL.
- if (locationURL !== null) {
- // handle corrupted header
- try {
- headers.set('Location', locationURL);
- } catch (err) {
- // istanbul ignore next: nodejs server prevent invalid response headers, we can't test this through normal request
- reject(err);
- }
- }
- break;
- case 'follow':
- // HTTP-redirect fetch step 2
- if (locationURL === null) {
- break;
- }
-
- // HTTP-redirect fetch step 5
- if (request.counter >= request.follow) {
- reject(new FetchError(`maximum redirect reached at: ${request.url}`, 'max-redirect'));
- finalize();
- return;
- }
-
- // HTTP-redirect fetch step 6 (counter increment)
- // Create a new Request object.
- const requestOpts = {
- headers: new Headers(request.headers),
- follow: request.follow,
- counter: request.counter + 1,
- agent: request.agent,
- compress: request.compress,
- method: request.method,
- body: request.body,
- signal: request.signal,
- timeout: request.timeout,
- size: request.size
- };
-
- if (!isDomainOrSubdomain(request.url, locationURL) || !isSameProtocol(request.url, locationURL)) {
- for (const name of ['authorization', 'www-authenticate', 'cookie', 'cookie2']) {
- requestOpts.headers.delete(name);
- }
- }
-
- // HTTP-redirect fetch step 9
- if (res.statusCode !== 303 && request.body && getTotalBytes(request) === null) {
- reject(new FetchError('Cannot follow redirect with body being a readable stream', 'unsupported-redirect'));
- finalize();
- return;
- }
-
- // HTTP-redirect fetch step 11
- if (res.statusCode === 303 || (res.statusCode === 301 || res.statusCode === 302) && request.method === 'POST') {
- requestOpts.method = 'GET';
- requestOpts.body = undefined;
- requestOpts.headers.delete('content-length');
- }
-
- // HTTP-redirect fetch step 15
- resolve(fetch(new Request(locationURL, requestOpts)));
- finalize();
- return;
- }
- }
-
- // prepare response
- res.once('end', function () {
- if (signal) signal.removeEventListener('abort', abortAndFinalize);
- });
- let body = res.pipe(new PassThrough$1());
-
- const response_options = {
- url: request.url,
- status: res.statusCode,
- statusText: res.statusMessage,
- headers: headers,
- size: request.size,
- timeout: request.timeout,
- counter: request.counter
- };
-
- // HTTP-network fetch step 12.1.1.3
- const codings = headers.get('Content-Encoding');
-
- // HTTP-network fetch step 12.1.1.4: handle content codings
-
- // in following scenarios we ignore compression support
- // 1. compression support is disabled
- // 2. HEAD request
- // 3. no Content-Encoding header
- // 4. no content response (204)
- // 5. content not modified response (304)
- if (!request.compress || request.method === 'HEAD' || codings === null || res.statusCode === 204 || res.statusCode === 304) {
- response = new Response(body, response_options);
- resolve(response);
- return;
- }
-
- // For Node v6+
- // Be less strict when decoding compressed responses, since sometimes
- // servers send slightly invalid responses that are still accepted
- // by common browsers.
- // Always using Z_SYNC_FLUSH is what cURL does.
- const zlibOptions = {
- flush: zlib.Z_SYNC_FLUSH,
- finishFlush: zlib.Z_SYNC_FLUSH
- };
-
- // for gzip
- if (codings == 'gzip' || codings == 'x-gzip') {
- body = body.pipe(zlib.createGunzip(zlibOptions));
- response = new Response(body, response_options);
- resolve(response);
- return;
- }
-
- // for deflate
- if (codings == 'deflate' || codings == 'x-deflate') {
- // handle the infamous raw deflate response from old servers
- // a hack for old IIS and Apache servers
- const raw = res.pipe(new PassThrough$1());
- raw.once('data', function (chunk) {
- // see http://stackoverflow.com/questions/37519828
- if ((chunk[0] & 0x0F) === 0x08) {
- body = body.pipe(zlib.createInflate());
- } else {
- body = body.pipe(zlib.createInflateRaw());
- }
- response = new Response(body, response_options);
- resolve(response);
- });
- raw.on('end', function () {
- // some old IIS servers return zero-length OK deflate responses, so 'data' is never emitted.
- if (!response) {
- response = new Response(body, response_options);
- resolve(response);
- }
- });
- return;
- }
-
- // for br
- if (codings == 'br' && typeof zlib.createBrotliDecompress === 'function') {
- body = body.pipe(zlib.createBrotliDecompress());
- response = new Response(body, response_options);
- resolve(response);
- return;
- }
-
- // otherwise, use response as-is
- response = new Response(body, response_options);
- resolve(response);
- });
-
- writeToStream(req, request);
- });
-}
-function fixResponseChunkedTransferBadEnding(request, errorCallback) {
- let socket;
-
- request.on('socket', function (s) {
- socket = s;
- });
-
- request.on('response', function (response) {
- const headers = response.headers;
-
- if (headers['transfer-encoding'] === 'chunked' && !headers['content-length']) {
- response.once('close', function (hadError) {
- // tests for socket presence, as in some situations the
- // the 'socket' event is not triggered for the request
- // (happens in deno), avoids `TypeError`
- // if a data listener is still present we didn't end cleanly
- const hasDataListener = socket && socket.listenerCount('data') > 0;
-
- if (hasDataListener && !hadError) {
- const err = new Error('Premature close');
- err.code = 'ERR_STREAM_PREMATURE_CLOSE';
- errorCallback(err);
- }
- });
- }
- });
-}
-
-function destroyStream(stream, err) {
- if (stream.destroy) {
- stream.destroy(err);
- } else {
- // node < 8
- stream.emit('error', err);
- stream.end();
- }
-}
-
-/**
- * Redirect code matching
- *
- * @param Number code Status code
- * @return Boolean
- */
-fetch.isRedirect = function (code) {
- return code === 301 || code === 302 || code === 303 || code === 307 || code === 308;
-};
-
-// expose Promise
-fetch.Promise = global.Promise;
-
-export default fetch;
-export { Headers, Request, Response, FetchError, AbortError };
diff --git a/backend/node_modules/node-fetch/package.json b/backend/node_modules/node-fetch/package.json
index e0be176..2b4e858 100644
--- a/backend/node_modules/node-fetch/package.json
+++ b/backend/node_modules/node-fetch/package.json
@@ -1,89 +1,131 @@
{
- "name": "node-fetch",
- "version": "2.7.0",
- "description": "A light-weight module that brings window.fetch to node.js",
- "main": "lib/index.js",
- "browser": "./browser.js",
- "module": "lib/index.mjs",
- "files": [
- "lib/index.js",
- "lib/index.mjs",
- "lib/index.es.js",
- "browser.js"
- ],
- "engines": {
- "node": "4.x || >=6.0.0"
- },
- "scripts": {
- "build": "cross-env BABEL_ENV=rollup rollup -c",
- "prepare": "npm run build",
- "test": "cross-env BABEL_ENV=test mocha --require babel-register --throw-deprecation test/test.js",
- "report": "cross-env BABEL_ENV=coverage nyc --reporter lcov --reporter text mocha -R spec test/test.js",
- "coverage": "cross-env BABEL_ENV=coverage nyc --reporter json --reporter text mocha -R spec test/test.js && codecov -f coverage/coverage-final.json"
- },
- "repository": {
- "type": "git",
- "url": "https://github.com/bitinn/node-fetch.git"
- },
- "keywords": [
- "fetch",
- "http",
- "promise"
- ],
- "author": "David Frank",
- "license": "MIT",
- "bugs": {
- "url": "https://github.com/bitinn/node-fetch/issues"
- },
- "homepage": "https://github.com/bitinn/node-fetch",
- "dependencies": {
- "whatwg-url": "^5.0.0"
- },
- "peerDependencies": {
- "encoding": "^0.1.0"
- },
- "peerDependenciesMeta": {
- "encoding": {
- "optional": true
- }
- },
- "devDependencies": {
- "@ungap/url-search-params": "^0.1.2",
- "abort-controller": "^1.1.0",
- "abortcontroller-polyfill": "^1.3.0",
- "babel-core": "^6.26.3",
- "babel-plugin-istanbul": "^4.1.6",
- "babel-plugin-transform-async-generator-functions": "^6.24.1",
- "babel-polyfill": "^6.26.0",
- "babel-preset-env": "1.4.0",
- "babel-register": "^6.16.3",
- "chai": "^3.5.0",
- "chai-as-promised": "^7.1.1",
- "chai-iterator": "^1.1.1",
- "chai-string": "~1.3.0",
- "codecov": "3.3.0",
- "cross-env": "^5.2.0",
- "form-data": "^2.3.3",
- "is-builtin-module": "^1.0.0",
- "mocha": "^5.0.0",
- "nyc": "11.9.0",
- "parted": "^0.1.1",
- "promise": "^8.0.3",
- "resumer": "0.0.0",
- "rollup": "^0.63.4",
- "rollup-plugin-babel": "^3.0.7",
- "string-to-arraybuffer": "^1.0.2",
- "teeny-request": "3.7.0"
- },
- "release": {
- "branches": [
- "+([0-9]).x",
- "main",
- "next",
- {
- "name": "beta",
- "prerelease": true
- }
- ]
+ "name": "node-fetch",
+ "version": "3.3.2",
+ "description": "A light-weight module that brings Fetch API to node.js",
+ "main": "./src/index.js",
+ "sideEffects": false,
+ "type": "module",
+ "files": [
+ "src",
+ "@types/index.d.ts"
+ ],
+ "types": "./@types/index.d.ts",
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "scripts": {
+ "test": "mocha",
+ "coverage": "c8 report --reporter=text-lcov | coveralls",
+ "test-types": "tsd",
+ "lint": "xo"
+ },
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/node-fetch/node-fetch.git"
+ },
+ "keywords": [
+ "fetch",
+ "http",
+ "promise",
+ "request",
+ "curl",
+ "wget",
+ "xhr",
+ "whatwg"
+ ],
+ "author": "David Frank",
+ "license": "MIT",
+ "bugs": {
+ "url": "https://github.com/node-fetch/node-fetch/issues"
+ },
+ "homepage": "https://github.com/node-fetch/node-fetch",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/node-fetch"
+ },
+ "devDependencies": {
+ "abort-controller": "^3.0.0",
+ "abortcontroller-polyfill": "^1.7.1",
+ "busboy": "^1.4.0",
+ "c8": "^7.7.2",
+ "chai": "^4.3.4",
+ "chai-as-promised": "^7.1.1",
+ "chai-iterator": "^3.0.2",
+ "chai-string": "^1.5.0",
+ "coveralls": "^3.1.0",
+ "form-data": "^4.0.0",
+ "formdata-node": "^4.2.4",
+ "mocha": "^9.1.3",
+ "p-timeout": "^5.0.0",
+ "stream-consumers": "^1.0.1",
+ "tsd": "^0.14.0",
+ "xo": "^0.39.1"
+ },
+ "dependencies": {
+ "data-uri-to-buffer": "^4.0.0",
+ "fetch-blob": "^3.1.4",
+ "formdata-polyfill": "^4.0.10"
+ },
+ "tsd": {
+ "cwd": "@types",
+ "compilerOptions": {
+ "esModuleInterop": true
}
+ },
+ "xo": {
+ "envs": [
+ "node",
+ "browser"
+ ],
+ "ignores": [
+ "example.js"
+ ],
+ "rules": {
+ "complexity": 0,
+ "import/extensions": 0,
+ "import/no-useless-path-segments": 0,
+ "import/no-anonymous-default-export": 0,
+ "import/no-named-as-default": 0,
+ "unicorn/import-index": 0,
+ "unicorn/no-array-reduce": 0,
+ "unicorn/prefer-node-protocol": 0,
+ "unicorn/numeric-separators-style": 0,
+ "unicorn/explicit-length-check": 0,
+ "capitalized-comments": 0,
+ "node/no-unsupported-features/es-syntax": 0,
+ "@typescript-eslint/member-ordering": 0
+ },
+ "overrides": [
+ {
+ "files": "test/**/*.js",
+ "envs": [
+ "node",
+ "mocha"
+ ],
+ "rules": {
+ "max-nested-callbacks": 0,
+ "no-unused-expressions": 0,
+ "no-warning-comments": 0,
+ "new-cap": 0,
+ "guard-for-in": 0,
+ "unicorn/no-array-for-each": 0,
+ "unicorn/prevent-abbreviations": 0,
+ "promise/prefer-await-to-then": 0,
+ "ava/no-import-test-files": 0
+ }
+ }
+ ]
+ },
+ "runkitExampleFilename": "example.js",
+ "release": {
+ "branches": [
+ "+([0-9]).x",
+ "main",
+ "next",
+ {
+ "name": "beta",
+ "prerelease": true
+ }
+ ]
+ }
}
diff --git a/backend/package-lock.json b/backend/package-lock.json
index 622f34b..215bb37 100644
--- a/backend/package-lock.json
+++ b/backend/package-lock.json
@@ -22,8 +22,10 @@
"jsonwebtoken": "^9.0.2",
"multer": "^1.4.5-lts.1",
"mysql2": "^3.10.3",
+ "node-fetch": "^3.3.2",
"nodemailer": "^6.9.14",
"pdf-parse": "^1.1.1",
+ "playwright": "^1.55.1",
"sequelize": "^6.37.3",
"sharp": "^0.33.5"
},
@@ -607,6 +609,26 @@
"node-pre-gyp": "bin/node-pre-gyp"
}
},
+ "node_modules/@mapbox/node-pre-gyp/node_modules/node-fetch": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
+ "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
+ "license": "MIT",
+ "dependencies": {
+ "whatwg-url": "^5.0.0"
+ },
+ "engines": {
+ "node": "4.x || >=6.0.0"
+ },
+ "peerDependencies": {
+ "encoding": "^0.1.0"
+ },
+ "peerDependenciesMeta": {
+ "encoding": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -1290,6 +1312,15 @@
"node": ">= 10"
}
},
+ "node_modules/data-uri-to-buffer": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
+ "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 12"
+ }
+ },
"node_modules/date-fns": {
"version": "2.30.0",
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz",
@@ -1790,6 +1821,29 @@
"reusify": "^1.0.4"
}
},
+ "node_modules/fetch-blob": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz",
+ "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/jimmywarting"
+ },
+ {
+ "type": "paypal",
+ "url": "https://paypal.me/jimmywarting"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "node-domexception": "^1.0.0",
+ "web-streams-polyfill": "^3.0.3"
+ },
+ "engines": {
+ "node": "^12.20 || >= 14.13"
+ }
+ },
"node_modules/file-entry-cache": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
@@ -1914,6 +1968,18 @@
"node": ">= 6"
}
},
+ "node_modules/formdata-polyfill": {
+ "version": "4.0.10",
+ "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
+ "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
+ "license": "MIT",
+ "dependencies": {
+ "fetch-blob": "^3.1.2"
+ },
+ "engines": {
+ "node": ">=12.20.0"
+ }
+ },
"node_modules/forwarded": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
@@ -2827,6 +2893,26 @@
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz",
"integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA=="
},
+ "node_modules/node-domexception": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
+ "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
+ "deprecated": "Use your platform's native DOMException instead",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/jimmywarting"
+ },
+ {
+ "type": "github",
+ "url": "https://paypal.me/jimmywarting"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.5.0"
+ }
+ },
"node_modules/node-ensure": {
"version": "0.0.0",
"resolved": "https://registry.npmjs.org/node-ensure/-/node-ensure-0.0.0.tgz",
@@ -2834,22 +2920,21 @@
"license": "MIT"
},
"node_modules/node-fetch": {
- "version": "2.7.0",
- "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
- "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz",
+ "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==",
+ "license": "MIT",
"dependencies": {
- "whatwg-url": "^5.0.0"
+ "data-uri-to-buffer": "^4.0.0",
+ "fetch-blob": "^3.1.4",
+ "formdata-polyfill": "^4.0.10"
},
"engines": {
- "node": "4.x || >=6.0.0"
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
- "peerDependencies": {
- "encoding": "^0.1.0"
- },
- "peerDependenciesMeta": {
- "encoding": {
- "optional": true
- }
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/node-fetch"
}
},
"node_modules/nodemailer": {
@@ -3135,6 +3220,50 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
+ "node_modules/playwright": {
+ "version": "1.55.1",
+ "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.55.1.tgz",
+ "integrity": "sha512-cJW4Xd/G3v5ovXtJJ52MAOclqeac9S/aGGgRzLabuF8TnIb6xHvMzKIa6JmrRzUkeXJgfL1MhukP0NK6l39h3A==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "playwright-core": "1.55.1"
+ },
+ "bin": {
+ "playwright": "cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "fsevents": "2.3.2"
+ }
+ },
+ "node_modules/playwright-core": {
+ "version": "1.55.1",
+ "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.55.1.tgz",
+ "integrity": "sha512-Z6Mh9mkwX+zxSlHqdr5AOcJnfp+xUWLCt9uKV18fhzA8eyxUd8NUWzAjxUh55RZKSYwDGX0cfaySdhZJGMoJ+w==",
+ "license": "Apache-2.0",
+ "bin": {
+ "playwright-core": "cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/playwright/node_modules/fsevents": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
+ "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
"node_modules/prelude-ls": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
@@ -3803,7 +3932,8 @@
"node_modules/tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
- "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
+ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
+ "license": "MIT"
},
"node_modules/tslib": {
"version": "2.7.0",
@@ -4002,15 +4132,26 @@
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true
},
+ "node_modules/web-streams-polyfill": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",
+ "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
"node_modules/webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
- "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
+ "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
+ "license": "BSD-2-Clause"
},
"node_modules/whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
+ "license": "MIT",
"dependencies": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
diff --git a/backend/package.json b/backend/package.json
index d8d7371..f438459 100644
--- a/backend/package.json
+++ b/backend/package.json
@@ -26,8 +26,10 @@
"jsonwebtoken": "^9.0.2",
"multer": "^1.4.5-lts.1",
"mysql2": "^3.10.3",
+ "node-fetch": "^3.3.2",
"nodemailer": "^6.9.14",
"pdf-parse": "^1.1.1",
+ "playwright": "^1.55.1",
"sequelize": "^6.37.3",
"sharp": "^0.33.5"
},
diff --git a/backend/routes/nuscoreApiRoutes.js b/backend/routes/nuscoreApiRoutes.js
new file mode 100644
index 0000000..1020c04
--- /dev/null
+++ b/backend/routes/nuscoreApiRoutes.js
@@ -0,0 +1,193 @@
+import express from 'express';
+import fetch from 'node-fetch';
+
+const router = express.Router();
+
+// Cookie-Store für nuscore-Sessions
+const cookieStore = new Map();
+
+// Hilfsfunktion zum Extrahieren von Cookies aus Set-Cookie Headers
+function extractCookies(setCookieHeaders) {
+ const cookies = {};
+ if (setCookieHeaders) {
+ setCookieHeaders.forEach(cookie => {
+ const [nameValue] = cookie.split(';');
+ const [name, value] = nameValue.split('=');
+ if (name && value) {
+ cookies[name.trim()] = value.trim();
+ }
+ });
+ }
+ return cookies;
+}
+
+// Hilfsfunktion zum Formatieren von Cookies für Requests
+function formatCookies(cookies) {
+ return Object.entries(cookies)
+ .map(([name, value]) => `${name}=${value}`)
+ .join('; ');
+}
+
+// Meeting-Info API-Endpunkt
+router.get('/meetinginfo/:code', async (req, res) => {
+ const { code } = req.params;
+ console.log(`📊 Meeting-Info API für Code: ${code}`);
+
+ try {
+ // Hole Cookies für diesen Code (falls vorhanden)
+ const cookies = cookieStore.get(code) || {};
+
+ const url = `https://ttde-apps.liga.nu/nuliga/rs/tt/2022/meetingentry/reports/${code}/meetinginfo`;
+
+ const response = await fetch(url, {
+ method: 'GET',
+ headers: {
+ 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:141.0) Gecko/20100101 Firefox/141.0',
+ 'Accept': 'application/json, text/plain, */*',
+ 'Accept-Language': 'de,en-US;q=0.7,en;q=0.3',
+ 'Accept-Encoding': 'gzip, deflate, br, zstd',
+ 'Referer': 'https://ttde-apps.liga.nu/nuliga/nuscore-tt/meetings-list',
+ 'Sec-Fetch-Dest': 'empty',
+ 'Sec-Fetch-Mode': 'cors',
+ 'Sec-Fetch-Site': 'same-origin',
+ 'Connection': 'keep-alive',
+ ...(Object.keys(cookies).length > 0 && { 'Cookie': formatCookies(cookies) })
+ }
+ });
+
+ if (!response.ok) {
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
+ }
+
+ const data = await response.json();
+
+ // Speichere neue Cookies falls vorhanden
+ const newCookies = extractCookies(response.headers.raw()['set-cookie']);
+ if (Object.keys(newCookies).length > 0) {
+ cookieStore.set(code, { ...cookies, ...newCookies });
+ console.log(`🍪 Cookies für Code ${code} gespeichert:`, Object.keys(newCookies));
+ }
+
+ // CORS-Header setzen
+ res.set({
+ 'Content-Type': 'application/json; charset=utf-8',
+ 'Access-Control-Allow-Origin': '*',
+ 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
+ 'Access-Control-Allow-Headers': 'Content-Type',
+ 'Cache-Control': 'no-cache, no-store, must-revalidate'
+ });
+
+ res.json(data);
+ console.log(`✅ Meeting-Info für Code ${code} erfolgreich abgerufen`);
+
+ } catch (error) {
+ console.error(`❌ Fehler beim Abrufen der Meeting-Info für Code ${code}:`, error);
+ res.status(500).json({
+ error: 'Fehler beim Abrufen der Meeting-Info',
+ details: error.message
+ });
+ }
+});
+
+// Cookie-Initialisierung (für den ersten Request)
+router.post('/init-cookies/:code', async (req, res) => {
+ const { code } = req.params;
+ console.log(`🍪 Cookie-Initialisierung für Code: ${code}`);
+
+ try {
+ // Erster Request an die nuscore-Seite um Cookies zu erhalten
+ const response = await fetch(`https://ttde-apps.liga.nu/nuliga/nuscore-tt/meetings-list?code=${code}`, {
+ method: 'GET',
+ headers: {
+ 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:141.0) Gecko/20100101 Firefox/141.0',
+ 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8',
+ 'Accept-Language': 'de,en-US;q=0.7,en;q=0.3',
+ 'Accept-Encoding': 'gzip, deflate, br, zstd',
+ 'Connection': 'keep-alive'
+ }
+ });
+
+ // Extrahiere Cookies
+ const cookies = extractCookies(response.headers.raw()['set-cookie']);
+ if (Object.keys(cookies).length > 0) {
+ cookieStore.set(code, cookies);
+ console.log(`🍪 Cookies für Code ${code} initialisiert:`, Object.keys(cookies));
+ }
+
+ res.json({
+ success: true,
+ cookies: Object.keys(cookies),
+ message: `Cookies für Code ${code} initialisiert`
+ });
+
+ } catch (error) {
+ console.error(`❌ Fehler bei Cookie-Initialisierung für Code ${code}:`, error);
+ res.status(500).json({
+ error: 'Fehler bei Cookie-Initialisierung',
+ details: error.message
+ });
+ }
+});
+
+// Detaillierte Meeting-Daten API-Endpunkt
+router.get('/meetingdetails/:uuid', async (req, res) => {
+ const { uuid } = req.params;
+ console.log(`📊 Meeting-Details API für UUID: ${uuid}`);
+
+ try {
+ // Hole Cookies für diesen Code (falls vorhanden)
+ const cookies = cookieStore.get(uuid) || {};
+
+ const url = `https://ttde-apps.liga.nu/nuliga/rs/tt/2022/meetingentry/reports/${uuid}`;
+
+ const response = await fetch(url, {
+ method: 'GET',
+ headers: {
+ 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:141.0) Gecko/20100101 Firefox/141.0',
+ 'Accept': 'application/json, text/plain, */*',
+ 'Accept-Language': 'de,en-US;q=0.7,en;q=0.3',
+ 'Accept-Encoding': 'gzip, deflate, br, zstd',
+ 'Referer': 'https://ttde-apps.liga.nu/nuliga/nuscore-tt/meetings-list',
+ 'Sec-Fetch-Dest': 'empty',
+ 'Sec-Fetch-Mode': 'cors',
+ 'Sec-Fetch-Site': 'same-origin',
+ 'Connection': 'keep-alive',
+ ...(Object.keys(cookies).length > 0 && { 'Cookie': formatCookies(cookies) })
+ }
+ });
+
+ if (!response.ok) {
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
+ }
+
+ const data = await response.json();
+
+ // Speichere neue Cookies falls vorhanden
+ const newCookies = extractCookies(response.headers.raw()['set-cookie']);
+ if (Object.keys(newCookies).length > 0) {
+ cookieStore.set(uuid, { ...cookies, ...newCookies });
+ console.log(`🍪 Cookies für UUID ${uuid} gespeichert:`, Object.keys(newCookies));
+ }
+
+ // CORS-Header setzen
+ res.set({
+ 'Content-Type': 'application/json; charset=utf-8',
+ 'Access-Control-Allow-Origin': '*',
+ 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
+ 'Access-Control-Allow-Headers': 'Content-Type',
+ 'Cache-Control': 'no-cache, no-store, must-revalidate'
+ });
+
+ res.json(data);
+ console.log(`✅ Meeting-Details für UUID ${uuid} erfolgreich abgerufen`);
+
+ } catch (error) {
+ console.error(`❌ Fehler beim Abrufen der Meeting-Details für UUID ${uuid}:`, error);
+ res.status(500).json({
+ error: 'Fehler beim Abrufen der Meeting-Details',
+ details: error.message
+ });
+ }
+});
+
+export default router;
diff --git a/backend/routes/nuscoreProxyRoutes.js b/backend/routes/nuscoreProxyRoutes.js
new file mode 100644
index 0000000..09cf3cf
--- /dev/null
+++ b/backend/routes/nuscoreProxyRoutes.js
@@ -0,0 +1,78 @@
+import express from 'express';
+import nuscoreProxyService from '../services/nuscoreProxyService.js';
+
+const router = express.Router();
+
+// Hauptroute für nuscore-Seite
+router.get('/', async (req, res) => {
+ try {
+ const { code, pin } = req.query;
+
+ if (!code) {
+ return res.status(400).json({
+ error: 'Code-Parameter ist erforderlich'
+ });
+ }
+
+ console.log(`📊 Proxy-Anfrage für Code: ${code}, PIN: ${pin || 'nicht angegeben'}`);
+
+ const html = await nuscoreProxyService.proxyNuscorePage(code, pin);
+
+ // CORS-Header setzen für iframe-Einbettung
+ res.set({
+ 'Content-Type': 'text/html; charset=utf-8',
+ 'Access-Control-Allow-Origin': '*',
+ 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
+ 'Access-Control-Allow-Headers': 'Content-Type',
+ 'X-Frame-Options': 'ALLOWALL',
+ 'Cache-Control': 'no-cache, no-store, must-revalidate'
+ });
+
+ res.send(html);
+
+ } catch (error) {
+ console.error('❌ Proxy-Fehler:', error);
+ res.status(500).json({
+ error: 'Fehler beim Laden der nuscore-Seite',
+ details: error.message
+ });
+ }
+});
+
+// Asset-Proxy für CSS, JS, Bilder etc.
+router.get('/assets/*', async (req, res) => {
+ try {
+ const assetPath = req.params[0];
+ const originalUrl = `https://ttde-apps.liga.nu/nuliga/nuscore-tt/${assetPath}`;
+
+ console.log(`📦 Lade Asset: ${originalUrl}`);
+
+ const { content, contentType } = await nuscoreProxyService.proxyAsset(originalUrl);
+
+ res.set({
+ 'Content-Type': contentType,
+ 'Access-Control-Allow-Origin': '*',
+ 'Cache-Control': 'public, max-age=3600'
+ });
+
+ res.send(content);
+
+ } catch (error) {
+ console.error(`❌ Asset-Fehler für ${req.params[0]}:`, error.message);
+ res.status(404).json({
+ error: 'Asset nicht gefunden',
+ path: req.params[0]
+ });
+ }
+});
+
+// Health-Check Route
+router.get('/health', (req, res) => {
+ res.json({
+ status: 'ok',
+ service: 'nuscore-proxy',
+ timestamp: new Date().toISOString()
+ });
+});
+
+export default router;
diff --git a/backend/routes/nuscoreSimpleProxyRoutes.js b/backend/routes/nuscoreSimpleProxyRoutes.js
new file mode 100644
index 0000000..37028ef
--- /dev/null
+++ b/backend/routes/nuscoreSimpleProxyRoutes.js
@@ -0,0 +1,140 @@
+import express from 'express';
+import fetch from 'node-fetch';
+
+const router = express.Router();
+
+// Einfacher HTTP-Proxy ohne Playwright
+router.get('/', async (req, res) => {
+ try {
+ const { code, pin } = req.query;
+
+ if (!code) {
+ return res.status(400).json({
+ error: 'Code-Parameter ist erforderlich'
+ });
+ }
+
+ console.log(`📊 Einfacher Proxy für Code: ${code}, PIN: ${pin || 'nicht angegeben'}`);
+
+ // Direkte HTTP-Anfrage an nuscore
+ const nuscoreUrl = `https://ttde-apps.liga.nu/nuliga/nuscore-tt/meetings-list?code=${encodeURIComponent(code)}`;
+
+ const response = await fetch(nuscoreUrl, {
+ headers: {
+ 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
+ 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
+ 'Accept-Language': 'de,en-US;q=0.7,en;q=0.3',
+ 'Accept-Encoding': 'gzip, deflate, br',
+ 'DNT': '1',
+ 'Connection': 'keep-alive',
+ 'Upgrade-Insecure-Requests': '1'
+ },
+ timeout: 30000
+ });
+
+ if (!response.ok) {
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
+ }
+
+ let html = await response.text();
+
+ // Einfache HTML-Modifikationen
+ html = html
+ .replace(/]*http-equiv=["']content-security-policy["'][^>]*>/gi, '')
+ .replace(/]*http-equiv=["']x-frame-options["'][^>]*>/gi, '')
+ .replace(/]*http-equiv=["']x-content-type-options["'][^>]*>/gi, '');
+
+ // Entferne Service Worker
+ html = html.replace(
+ /navigator\.serviceWorker\.register\([^)]+\)/g,
+ '// Service Worker deaktiviert für Proxy'
+ );
+
+ // Korrigiere alle Asset-URLs zu absoluten URLs
+ html = html.replace(
+ /src="\.\//g,
+ 'src="https://ttde-apps.liga.nu/nuliga/nuscore-tt/'
+ );
+ html = html.replace(
+ /href="\.\//g,
+ 'href="https://ttde-apps.liga.nu/nuliga/nuscore-tt/'
+ );
+
+ // Korrigiere auch URLs die bereits auf localhost zeigen
+ html = html.replace(
+ /src="http:\/\/localhost:3000\/nuliga\/nuscore-tt\//g,
+ 'src="https://ttde-apps.liga.nu/nuliga/nuscore-tt/'
+ );
+ html = html.replace(
+ /href="http:\/\/localhost:3000\/nuliga\/nuscore-tt\//g,
+ 'href="https://ttde-apps.liga.nu/nuliga/nuscore-tt/'
+ );
+
+ // Entferne vorhandene base-Tags und füge unseres hinzu
+ html = html.replace(
+ /]*>/gi,
+ ''
+ );
+
+ // Füge base-Tag hinzu für korrekte Asset-Auflösung
+ html = html.replace(
+ '',
+ ''
+ );
+
+ // Debug: Logge die HTML-Struktur
+ console.log('🔍 HTML nach base-Tag:', html.substring(html.indexOf('') + 7));
+
+ // Füge PIN-Injektion hinzu falls PIN vorhanden
+ if (pin) {
+ const pinScript = `
+
+ `;
+ html = html.replace('', `${pinScript}`);
+ }
+
+ // CORS-Header setzen für iframe-Einbettung
+ res.set({
+ 'Content-Type': 'text/html; charset=utf-8',
+ 'Access-Control-Allow-Origin': '*',
+ 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
+ 'Access-Control-Allow-Headers': 'Content-Type',
+ 'X-Frame-Options': 'ALLOWALL',
+ 'Content-Security-Policy': "frame-ancestors *;",
+ 'Cache-Control': 'no-cache, no-store, must-revalidate'
+ });
+
+ res.send(html);
+ console.log('✅ Einfacher Proxy erfolgreich');
+
+ } catch (error) {
+ console.error('❌ Einfacher Proxy-Fehler:', error);
+ res.status(500).json({
+ error: 'Fehler beim Laden der nuscore-Seite',
+ details: error.message
+ });
+ }
+});
+
+// Health-Check Route
+router.get('/health', (req, res) => {
+ res.json({
+ status: 'ok',
+ service: 'nuscore-simple-proxy',
+ timestamp: new Date().toISOString()
+ });
+});
+
+export default router;
diff --git a/backend/server.js b/backend/server.js
index ea4a109..2eb07e9 100644
--- a/backend/server.js
+++ b/backend/server.js
@@ -38,6 +38,8 @@ import teamRoutes from './routes/teamRoutes.js';
import clubTeamRoutes from './routes/clubTeamRoutes.js';
import teamDocumentRoutes from './routes/teamDocumentRoutes.js';
import seasonRoutes from './routes/seasonRoutes.js';
+import nuscoreSimpleProxyRoutes from './routes/nuscoreSimpleProxyRoutes.js';
+import nuscoreApiRoutes from './routes/nuscoreApiRoutes.js';
const app = express();
const port = process.env.PORT || 3000;
@@ -87,6 +89,8 @@ app.use('/api/teams', teamRoutes);
app.use('/api/club-teams', clubTeamRoutes);
app.use('/api/team-documents', teamDocumentRoutes);
app.use('/api/seasons', seasonRoutes);
+app.use('/api/proxy/nuscore', nuscoreSimpleProxyRoutes);
+app.use('/api/nuscore', nuscoreApiRoutes);
app.use(express.static(path.join(__dirname, '../frontend/dist')));
diff --git a/backend/services/nuscoreProxyService.js b/backend/services/nuscoreProxyService.js
new file mode 100644
index 0000000..f1b62a7
--- /dev/null
+++ b/backend/services/nuscoreProxyService.js
@@ -0,0 +1,148 @@
+import { chromium } from 'playwright';
+
+class NuscoreProxyService {
+ constructor() {
+ this.browser = null;
+ this.context = null;
+ }
+
+ async initialize() {
+ if (!this.browser) {
+ console.log('🚀 Initialisiere Playwright Browser...');
+ this.browser = await chromium.launch({
+ headless: true,
+ args: [
+ '--no-sandbox',
+ '--disable-setuid-sandbox',
+ '--disable-dev-shm-usage',
+ '--disable-web-security',
+ '--disable-features=VizDisplayCompositor'
+ ]
+ });
+
+ this.context = await this.browser.newContext({
+ userAgent: 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
+ viewport: { width: 1920, height: 1080 },
+ ignoreHTTPSErrors: true
+ });
+
+ console.log('✅ Playwright Browser initialisiert');
+ }
+ }
+
+ async proxyNuscorePage(code, pin = null) {
+ try {
+ await this.initialize();
+
+ console.log(`🔍 Lade nuscore-Seite für Code: ${code}`);
+
+ const page = await this.context.newPage();
+
+ // Navigiere direkt zur nuscore-Seite ohne Request-Interception
+ const nuscoreUrl = `https://ttde-apps.liga.nu/nuliga/nuscore-tt/meetings-list?code=${encodeURIComponent(code)}`;
+ await page.goto(nuscoreUrl, {
+ waitUntil: 'load',
+ timeout: 30000
+ });
+
+ console.log('📄 Seite geladen');
+
+ // Optional: PIN automatisch einfügen falls vorhanden
+ if (pin) {
+ console.log(`🔑 Versuche PIN ${pin} einzufügen...`);
+ try {
+ // Suche nach PIN-Eingabefeld
+ const pinInput = await page.locator('input[type="password"][placeholder*="PIN"], input[placeholder*="Pin"], input[placeholder*="pin"]').first();
+ if (await pinInput.isVisible()) {
+ await pinInput.fill(pin);
+ console.log('✅ PIN eingefügt');
+
+ // Optional: Submit-Button klicken falls vorhanden
+ const submitBtn = await page.locator('button[type="submit"], input[type="submit"], button:has-text("Einloggen"), button:has-text("Anmelden")').first();
+ if (await submitBtn.isVisible()) {
+ await submitBtn.click();
+ await page.waitForLoadState('load', { timeout: 5000 });
+ }
+ }
+ } catch (error) {
+ console.log('⚠️ PIN-Einfügung fehlgeschlagen:', error.message);
+ }
+ }
+
+ // HTML-Inhalt abrufen und modifizieren
+ const html = await page.content();
+
+ // HTML modifizieren für Proxy-Betrieb
+ const modifiedHtml = this.modifyHtmlForProxy(html, code);
+
+ await page.close();
+
+ console.log('✅ nuscore-Seite erfolgreich proxiert');
+ return modifiedHtml;
+
+ } catch (error) {
+ console.error('❌ Fehler beim Proxying der nuscore-Seite:', error);
+ throw error;
+ }
+ }
+
+ modifyHtmlForProxy(html, code) {
+ // Entferne problematische Headers/Meta-Tags
+ let modified = html
+ .replace(/]*http-equiv=["']content-security-policy["'][^>]*>/gi, '')
+ .replace(/]*http-equiv=["']x-frame-options["'][^>]*>/gi, '')
+ .replace(/]*http-equiv=["']x-content-type-options["'][^>]*>/gi, '');
+
+ // Entferne Service Worker Registrierung (kann Probleme verursachen)
+ modified = modified.replace(
+ /navigator\.serviceWorker\.register\([^)]+\)/g,
+ '// Service Worker deaktiviert für Proxy'
+ );
+
+ return modified;
+ }
+
+ async proxyAsset(url) {
+ try {
+ await this.initialize();
+
+ const page = await this.context.newPage();
+ const response = await page.goto(url, {
+ waitUntil: 'networkidle',
+ timeout: 15000
+ });
+
+ if (!response || !response.ok()) {
+ throw new Error(`Asset nicht erreichbar: ${url}`);
+ }
+
+ const content = await response.body();
+ const contentType = response.headers()['content-type'] || 'application/octet-stream';
+
+ await page.close();
+
+ return { content, contentType };
+
+ } catch (error) {
+ console.error(`❌ Fehler beim Laden von Asset ${url}:`, error.message);
+ throw error;
+ }
+ }
+
+ async cleanup() {
+ if (this.context) {
+ await this.context.close();
+ this.context = null;
+ }
+ if (this.browser) {
+ await this.browser.close();
+ this.browser = null;
+ }
+ console.log('🧹 Playwright Browser bereinigt');
+ }
+}
+
+// Singleton-Instanz
+const nuscoreProxyService = new NuscoreProxyService();
+
+export default nuscoreProxyService;
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 042b7c8..1d7018e 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -10,6 +10,7 @@
"dependencies": {
"axios": "^1.7.3",
"core-js": "^3.8.3",
+ "crypto-js": "^4.2.0",
"html2canvas": "^1.4.1",
"jspdf": "^2.5.2",
"jspdf-autotable": "^5.0.2",
@@ -1375,6 +1376,12 @@
"node": ">= 8"
}
},
+ "node_modules/crypto-js": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz",
+ "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==",
+ "license": "MIT"
+ },
"node_modules/css-line-break": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz",
diff --git a/frontend/package.json b/frontend/package.json
index b2cf85f..39e3378 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -10,6 +10,7 @@
"dependencies": {
"axios": "^1.7.3",
"core-js": "^3.8.3",
+ "crypto-js": "^4.2.0",
"html2canvas": "^1.4.1",
"jspdf": "^2.5.2",
"jspdf-autotable": "^5.0.2",
diff --git a/frontend/src/components/DialogManager.vue b/frontend/src/components/DialogManager.vue
index ee81de5..ba4bd1f 100644
--- a/frontend/src/components/DialogManager.vue
+++ b/frontend/src/components/DialogManager.vue
@@ -54,11 +54,13 @@
+
+
diff --git a/frontend/src/components/MatchReportDialog.vue b/frontend/src/components/MatchReportDialog.vue
index f6b7ee8..6046f43 100644
--- a/frontend/src/components/MatchReportDialog.vue
+++ b/frontend/src/components/MatchReportDialog.vue
@@ -1,5 +1,18 @@
+
+
+
+
+
+
+
+
+