1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00

Upgrade to latest node-fetch (#6046)

* Upgrade to latest node-fetch

- Introduce injection tokens for retriving the implementation of
  node-fetch via await import() calls

- Add webpack file for compiling node-fetch to electron-renderer single
  file format

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Update lock file

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Add type packages which were removed for some reason

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Add more dev @types deps that weren't added last time

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Simpify by using webpack to create a commonjs package

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix build for integration tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix unit tests on CI not having all deps

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Revert accidental timeout change

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Replace manually specifying nodeJS externals with preset

Signed-off-by: Sebastian Malton <sebastian@malton.name>

Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
Sebastian Malton 2022-11-14 07:46:53 -08:00 committed by GitHub
parent 6d9a76835c
commit 86ac417cab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 371 additions and 237 deletions

View File

@ -12,6 +12,7 @@ module.exports = {
"**/static/**/*",
"**/site/**/*",
"extensions/*/*.tgz",
"build/webpack/**/*",
],
settings: {
react: {

1
.gitignore vendored
View File

@ -17,3 +17,4 @@ types/extension-renderer-api.d.ts
extensions/*/dist
docs/extensions/api
site/
build/webpack/

View File

@ -44,7 +44,7 @@ tag-release:
scripts/tag-release.sh $(CMD_ARGS)
.PHONY: test
test: binaries/client
test: node_modules binaries/client
yarn run jest $(or $(CMD_ARGS), "src")
.PHONY: integration

View File

@ -8,15 +8,19 @@ import { open } from "fs/promises";
import type { WriteStream } from "fs-extra";
import { constants, ensureDir, unlink } from "fs-extra";
import path from "path";
import fetch from "node-fetch";
import type * as FetchModule from "node-fetch";
import { promisify } from "util";
import { pipeline as _pipeline, Transform, Writable } from "stream";
import type { SingleBar } from "cli-progress";
import { MultiBar } from "cli-progress";
import { extract } from "tar-stream";
import gunzip from "gunzip-maybe";
import { getBinaryName, normalizedPlatform } from "../src/common/vars";
import { isErrnoException } from "../src/common/utils";
import { getBinaryName } from "../src/common/vars";
import { isErrnoException, setTimeoutFor } from "../src/common/utils";
type Response = FetchModule.Response;
type RequestInfo = FetchModule.RequestInfo;
type RequestInit = FetchModule.RequestInit;
const pipeline = promisify(_pipeline);
@ -29,6 +33,10 @@ interface BinaryDownloaderArgs {
readonly baseDir: string;
}
interface BinaryDownloaderDependencies {
fetch: (url: RequestInfo, init?: RequestInit) => Promise<Response>;
}
abstract class BinaryDownloader {
protected abstract readonly url: string;
protected readonly bar: SingleBar;
@ -38,7 +46,7 @@ abstract class BinaryDownloader {
return [file];
}
constructor(public readonly args: BinaryDownloaderArgs, multiBar: MultiBar) {
constructor(protected readonly dependencies: BinaryDownloaderDependencies, public readonly args: BinaryDownloaderArgs, multiBar: MultiBar) {
this.bar = multiBar.create(1, 0, args);
this.target = path.join(args.baseDir, args.platform, args.fileArch, args.binaryName);
}
@ -49,8 +57,10 @@ abstract class BinaryDownloader {
}
const controller = new AbortController();
const stream = await fetch(this.url, {
timeout: 15 * 60 * 1000, // 15min
setTimeoutFor(controller, 15 * 60 * 1000);
const stream = await this.dependencies.fetch(this.url, {
signal: controller.signal,
});
const total = Number(stream.headers.get("content-length"));
@ -72,6 +82,10 @@ abstract class BinaryDownloader {
*/
const handle = fileHandle = await open(this.target, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL);
if (!stream.body) {
throw new Error("no body on stream");
}
await pipeline(
stream.body,
new Transform({
@ -108,10 +122,10 @@ abstract class BinaryDownloader {
class LensK8sProxyDownloader extends BinaryDownloader {
protected readonly url: string;
constructor(args: Omit<BinaryDownloaderArgs, "binaryName">, bar: MultiBar) {
constructor(deps: BinaryDownloaderDependencies, args: Omit<BinaryDownloaderArgs, "binaryName">, bar: MultiBar) {
const binaryName = getBinaryName("lens-k8s-proxy", { forPlatform: args.platform });
super({ ...args, binaryName }, bar);
super(deps, { ...args, binaryName }, bar);
this.url = `https://github.com/lensapp/lens-k8s-proxy/releases/download/v${args.version}/lens-k8s-proxy-${args.platform}-${args.downloadArch}`;
}
}
@ -119,10 +133,10 @@ class LensK8sProxyDownloader extends BinaryDownloader {
class KubectlDownloader extends BinaryDownloader {
protected readonly url: string;
constructor(args: Omit<BinaryDownloaderArgs, "binaryName">, bar: MultiBar) {
constructor(deps: BinaryDownloaderDependencies, args: Omit<BinaryDownloaderArgs, "binaryName">, bar: MultiBar) {
const binaryName = getBinaryName("kubectl", { forPlatform: args.platform });
super({ ...args, binaryName }, bar);
super(deps, { ...args, binaryName }, bar);
this.url = `https://storage.googleapis.com/kubernetes-release/release/v${args.version}/bin/${args.platform}/${args.downloadArch}/${binaryName}`;
}
}
@ -130,10 +144,10 @@ class KubectlDownloader extends BinaryDownloader {
class HelmDownloader extends BinaryDownloader {
protected readonly url: string;
constructor(args: Omit<BinaryDownloaderArgs, "binaryName">, bar: MultiBar) {
constructor(deps: BinaryDownloaderDependencies, args: Omit<BinaryDownloaderArgs, "binaryName">, bar: MultiBar) {
const binaryName = getBinaryName("helm", { forPlatform: args.platform });
super({ ...args, binaryName }, bar);
super(deps, { ...args, binaryName }, bar);
this.url = `https://get.helm.sh/helm-v${args.version}-${args.platform}-${args.downloadArch}.tar.gz`;
}
@ -160,7 +174,24 @@ class HelmDownloader extends BinaryDownloader {
type SupportedPlatform = "darwin" | "linux" | "windows";
const importFetchModule = new Function('return import("node-fetch")') as () => Promise<typeof FetchModule>;
async function main() {
const deps: BinaryDownloaderDependencies = {
fetch: (await importFetchModule()).default,
};
const normalizedPlatform = (() => {
switch (process.platform) {
case "darwin":
return "darwin";
case "linux":
return "linux";
case "win32":
return "windows";
default:
throw new Error(`platform=${process.platform} is unsupported`);
}
})();
const multiBar = new MultiBar({
align: "left",
clearOnComplete: false,
@ -171,21 +202,21 @@ async function main() {
});
const baseDir = path.join(__dirname, "..", "binaries", "client");
const downloaders: BinaryDownloader[] = [
new LensK8sProxyDownloader({
new LensK8sProxyDownloader(deps, {
version: packageInfo.config.k8sProxyVersion,
platform: normalizedPlatform,
downloadArch: "amd64",
fileArch: "x64",
baseDir,
}, multiBar),
new KubectlDownloader({
new KubectlDownloader(deps, {
version: packageInfo.config.bundledKubectlVersion,
platform: normalizedPlatform,
downloadArch: "amd64",
fileArch: "x64",
baseDir,
}, multiBar),
new HelmDownloader({
new HelmDownloader(deps, {
version: packageInfo.config.bundledHelmVersion,
platform: normalizedPlatform,
downloadArch: "amd64",
@ -196,21 +227,21 @@ async function main() {
if (normalizedPlatform !== "windows") {
downloaders.push(
new LensK8sProxyDownloader({
new LensK8sProxyDownloader(deps, {
version: packageInfo.config.k8sProxyVersion,
platform: normalizedPlatform,
downloadArch: "arm64",
fileArch: "arm64",
baseDir,
}, multiBar),
new KubectlDownloader({
new KubectlDownloader(deps, {
version: packageInfo.config.bundledKubectlVersion,
platform: normalizedPlatform,
downloadArch: "arm64",
fileArch: "arm64",
baseDir,
}, multiBar),
new HelmDownloader({
new HelmDownloader(deps, {
version: packageInfo.config.bundledHelmVersion,
platform: normalizedPlatform,
downloadArch: "arm64",

View File

@ -26,6 +26,8 @@
"compile:main": "yarn run webpack --config webpack/main.ts",
"compile:renderer": "yarn run webpack --config webpack/renderer.ts",
"compile:extension-types": "yarn run webpack --config webpack/extensions.ts",
"compile:node-fetch": "yarn run webpack --config ./webpack/node-fetch.ts",
"postinstall": "yarn run compile:node-fetch",
"npm:fix-package-version": "yarn run ts-node build/set_npm_version.ts",
"build:linux": "yarn run compile && electron-builder --linux --dir",
"build:mac": "yarn run compile && electron-builder --mac --dir",
@ -262,7 +264,7 @@
"moment-timezone": "^0.5.38",
"monaco-editor": "^0.29.1",
"monaco-editor-webpack-plugin": "^5.0.0",
"node-fetch": "^2.6.7",
"node-fetch": "^3.2.10",
"node-pty": "0.10.1",
"npm": "^8.19.3",
"p-limit": "^3.1.0",
@ -332,9 +334,7 @@
"@types/memorystream": "^0.3.0",
"@types/mini-css-extract-plugin": "^2.4.0",
"@types/mock-fs": "^4.13.1",
"@types/node": "^16.18.3",
"@types/node-fetch": "^2.6.2",
"@types/npm": "^2.0.32",
"@types/node": "^16.18.2",
"@types/proper-lockfile": "^4.1.2",
"@types/randomcolor": "^0.5.6",
"@types/react": "^17.0.45",

View File

@ -4,7 +4,6 @@
*/
import { getInjectable } from "@ogre-tools/injectable";
import type { RequestInit, Response } from "node-fetch";
import type { JsonValue } from "type-fest";
import type { AsyncResult } from "../utils/async-result";
import fetchInjectable from "./fetch.injectable";
@ -12,7 +11,7 @@ export interface DownloadJsonOptions {
signal?: AbortSignal | null | undefined;
}
export type DownloadJson = (url: string, opts?: DownloadJsonOptions) => Promise<AsyncResult<JsonValue, string>>;
export type DownloadJson = (url: string, opts?: DownloadJsonOptions) => Promise<AsyncResult<unknown, string>>;
const downloadJsonInjectable = getInjectable({
id: "download-json",

View File

@ -3,9 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getGlobalOverride } from "../test-utils/get-global-override";
import { getGlobalOverrideForFunction } from "../test-utils/get-global-override-for-function";
import fetchInjectable from "./fetch.injectable";
export default getGlobalOverride(fetchInjectable, () => () => {
throw new Error("tried to fetch a resource without override in test");
});
export default getGlobalOverrideForFunction(fetchInjectable);

View File

@ -3,8 +3,12 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import fetch from "node-fetch";
import type { RequestInit, Response } from "node-fetch";
import type * as FetchModule from "node-fetch";
const { NodeFetch: { default: fetch }} = require("../../../build/webpack/node-fetch.bundle") as { NodeFetch: typeof FetchModule };
type Response = FetchModule.Response;
type RequestInit = FetchModule.RequestInit;
export type Fetch = (url: string, init?: RequestInit) => Promise<Response>;

View File

@ -15,20 +15,76 @@ import type { Fetch } from "../../fetch/fetch.injectable";
import fetchInjectable from "../../fetch/fetch.injectable";
import type { CreateKubeApiForRemoteCluster } from "../create-kube-api-for-remote-cluster.injectable";
import createKubeApiForRemoteClusterInjectable from "../create-kube-api-for-remote-cluster.injectable";
import { Response } from "node-fetch";
import type { AsyncFnMock } from "@async-fn/jest";
import asyncFn from "@async-fn/jest";
import { flushPromises } from "../../test-utils/flush-promises";
import createKubeJsonApiInjectable from "../create-kube-json-api.injectable";
import type { IKubeWatchEvent } from "../kube-watch-event";
import type { KubeJsonApiDataFor } from "../kube-object";
import type { Response, Headers as NodeFetchHeaders } from "node-fetch";
import AbortController from "abort-controller";
const createMockResponseFromString = (url: string, data: string, statusCode = 200) => {
const res: jest.Mocked<Response> = {
buffer: jest.fn(async () => { throw new Error("buffer() is not supported"); }),
clone: jest.fn(() => res),
arrayBuffer: jest.fn(async () => { throw new Error("arrayBuffer() is not supported"); }),
blob: jest.fn(async () => { throw new Error("blob() is not supported"); }),
body: new PassThrough(),
bodyUsed: false,
headers: new Headers() as NodeFetchHeaders,
json: jest.fn(async () => JSON.parse(await res.text())),
ok: 200 <= statusCode && statusCode < 300,
redirected: 300 <= statusCode && statusCode < 400,
size: data.length,
status: statusCode,
statusText: "some-text",
text: jest.fn(async () => data),
type: "basic",
url,
formData: jest.fn(async () => { throw new Error("formData() is not supported"); }),
};
return res;
};
const createMockResponseFromStream = (url: string, stream: NodeJS.ReadableStream, statusCode = 200) => {
const res: jest.Mocked<Response> = {
buffer: jest.fn(async () => { throw new Error("buffer() is not supported"); }),
clone: jest.fn(() => res),
arrayBuffer: jest.fn(async () => { throw new Error("arrayBuffer() is not supported"); }),
blob: jest.fn(async () => { throw new Error("blob() is not supported"); }),
body: stream,
bodyUsed: false,
headers: new Headers() as NodeFetchHeaders,
json: jest.fn(async () => JSON.parse(await res.text())),
ok: 200 <= statusCode && statusCode < 300,
redirected: 300 <= statusCode && statusCode < 400,
size: 10,
status: statusCode,
statusText: "some-text",
text: jest.fn(() => {
const chunks: Buffer[] = [];
return new Promise((resolve, reject) => {
stream.on("data", (chunk) => chunks.push(Buffer.from(chunk)));
stream.on("error", (err) => reject(err));
stream.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")));
});
}),
type: "basic",
url,
formData: jest.fn(async () => { throw new Error("formData() is not supported"); }),
};
return res;
};
describe("createKubeApiForRemoteCluster", () => {
let createKubeApiForRemoteCluster: CreateKubeApiForRemoteCluster;
let fetchMock: AsyncFnMock<Fetch>;
beforeEach(() => {
beforeEach(async () => {
const di = getDiForUnitTesting({ doGeneralOverrides: true });
fetchMock = asyncFn();
@ -94,7 +150,7 @@ describe("createKubeApiForRemoteCluster", () => {
beforeEach(async () => {
await fetchMock.resolveSpecific(
["https://127.0.0.1:6443/api/v1/pods"],
new Response(JSON.stringify({
createMockResponseFromString("https://127.0.0.1:6443/api/v1/pods", JSON.stringify({
kind: "PodList",
apiVersion: "v1",
metadata:{
@ -118,7 +174,7 @@ describe("KubeApi", () => {
let registerApiSpy: jest.SpiedFunction<ApiManager["registerApi"]>;
let fetchMock: AsyncFnMock<Fetch>;
beforeEach(() => {
beforeEach(async () => {
const di = getDiForUnitTesting({ doGeneralOverrides: true });
fetchMock = asyncFn();
@ -172,7 +228,7 @@ describe("KubeApi", () => {
beforeEach(async () => {
await fetchMock.resolveSpecific(
["http://127.0.0.1:9999/api-kube/apis/networking.k8s.io/v1"],
new Response(JSON.stringify({
createMockResponseFromString("http://127.0.0.1:9999/api-kube/apis/networking.k8s.io/v1", JSON.stringify({
resources: [{
name: "ingresses",
}],
@ -196,7 +252,7 @@ describe("KubeApi", () => {
beforeEach(async () => {
await fetchMock.resolveSpecific(
["http://127.0.0.1:9999/api-kube/apis/networking.k8s.io"],
new Response(JSON.stringify({
createMockResponseFromString("http://127.0.0.1:9999/api-kube/apis/networking.k8s.io", JSON.stringify({
preferredVersion: {
version: "v1",
},
@ -234,7 +290,7 @@ describe("KubeApi", () => {
beforeEach(async () => {
await fetchMock.resolveSpecific(
["http://127.0.0.1:9999/api-kube/apis/networking.k8s.io/v1/namespaces/default/ingresses/foo"],
new Response(JSON.stringify({})),
createMockResponseFromString("http://127.0.0.1:9999/api-kube/apis/networking.k8s.io/v1/namespaces/default/ingresses/foo", JSON.stringify({})),
);
result = await getCall;
});
@ -274,7 +330,7 @@ describe("KubeApi", () => {
beforeEach(async () => {
await fetchMock.resolveSpecific(
["http://127.0.0.1:9999/api-kube/apis/networking.k8s.io/v1/namespaces/default/ingresses/foo1"],
new Response(JSON.stringify({})),
createMockResponseFromString("http://127.0.0.1:9999/api-kube/apis/networking.k8s.io/v1/namespaces/default/ingresses/foo1", JSON.stringify({})),
);
result = await getCall;
});
@ -292,7 +348,7 @@ describe("KubeApi", () => {
beforeEach(async () => {
await fetchMock.resolveSpecific(
["http://127.0.0.1:9999/api-kube/apis/networking.k8s.io/v1/namespaces/default/ingresses/foo"],
new Response(JSON.stringify({
createMockResponseFromString("http://127.0.0.1:9999/api-kube/apis/networking.k8s.io/v1/namespaces/default/ingresses/foo", JSON.stringify({
apiVersion: "v1",
kind: "Ingress",
metadata: {
@ -341,7 +397,7 @@ describe("KubeApi", () => {
beforeEach(async () => {
await fetchMock.resolveSpecific(
["http://127.0.0.1:9999/api-kube/apis/networking.k8s.io/v1/namespaces/default/ingresses/foo1"],
new Response(JSON.stringify({})),
createMockResponseFromString("http://127.0.0.1:9999/api-kube/apis/networking.k8s.io/v1/namespaces/default/ingresses/foo1", JSON.stringify({})),
);
result = await getCall;
});
@ -359,7 +415,7 @@ describe("KubeApi", () => {
beforeEach(async () => {
await fetchMock.resolveSpecific(
["http://127.0.0.1:9999/api-kube/apis/networking.k8s.io/v1"],
new Response(JSON.stringify({
createMockResponseFromString("http://127.0.0.1:9999/api-kube/apis/networking.k8s.io/v1", JSON.stringify({
resources: [],
})),
);
@ -381,7 +437,7 @@ describe("KubeApi", () => {
beforeEach(async () => {
await fetchMock.resolveSpecific(
["http://127.0.0.1:9999/api-kube/apis/extensions/v1beta1"],
new Response(JSON.stringify({
createMockResponseFromString("http://127.0.0.1:9999/api-kube/apis/extensions/v1beta1", JSON.stringify({
resources: [{
name: "ingresses",
}],
@ -405,7 +461,7 @@ describe("KubeApi", () => {
beforeEach(async () => {
await fetchMock.resolveSpecific(
["http://127.0.0.1:9999/api-kube/apis/extensions"],
new Response(JSON.stringify({
createMockResponseFromString("http://127.0.0.1:9999/api-kube/apis/extensions", JSON.stringify({
preferredVersion: {
version: "v1beta1",
},
@ -443,7 +499,7 @@ describe("KubeApi", () => {
beforeEach(async () => {
await fetchMock.resolveSpecific(
["http://127.0.0.1:9999/api-kube/apis/extensions/v1beta1/namespaces/default/ingresses/foo"],
new Response(JSON.stringify({})),
createMockResponseFromString("http://127.0.0.1:9999/api-kube/apis/extensions/v1beta1/namespaces/default/ingresses/foo", JSON.stringify({})),
);
result = await getCall;
});
@ -483,7 +539,7 @@ describe("KubeApi", () => {
beforeEach(async () => {
await fetchMock.resolveSpecific(
["http://127.0.0.1:9999/api-kube/apis/extensions/v1beta1/namespaces/default/ingresses/foo1"],
new Response(JSON.stringify({})),
createMockResponseFromString("http://127.0.0.1:9999/api-kube/apis/extensions/v1beta1/namespaces/default/ingresses/foo1", JSON.stringify({})),
);
result = await getCall;
});
@ -501,7 +557,7 @@ describe("KubeApi", () => {
beforeEach(async () => {
await fetchMock.resolveSpecific(
["http://127.0.0.1:9999/api-kube/apis/extensions/v1beta1/namespaces/default/ingresses/foo"],
new Response(JSON.stringify({
createMockResponseFromString("http://127.0.0.1:9999/api-kube/apis/extensions/v1beta1/namespaces/default/ingresses/foo", JSON.stringify({
apiVersion: "v1beta1",
kind: "Ingress",
metadata: {
@ -550,7 +606,7 @@ describe("KubeApi", () => {
beforeEach(async () => {
await fetchMock.resolveSpecific(
["http://127.0.0.1:9999/api-kube/apis/extensions/v1beta1/namespaces/default/ingresses/foo1"],
new Response(JSON.stringify({})),
createMockResponseFromString("http://127.0.0.1:9999/api-kube/apis/extensions/v1beta1/namespaces/default/ingresses/foo1", JSON.stringify({})),
);
result = await getCall;
});
@ -604,7 +660,7 @@ describe("KubeApi", () => {
beforeEach(async () => {
await fetchMock.resolveSpecific(
["http://127.0.0.1:9999/api-kube/apis/apps/v1/namespaces/default/deployments/test"],
new Response(JSON.stringify({
createMockResponseFromString("http://127.0.0.1:9999/api-kube/apis/apps/v1/namespaces/default/deployments/test", JSON.stringify({
apiVersion: "v1",
kind: "Deployment",
metadata: {
@ -657,7 +713,7 @@ describe("KubeApi", () => {
beforeEach(async () => {
await fetchMock.resolveSpecific(
["http://127.0.0.1:9999/api-kube/apis/apps/v1/namespaces/default/deployments/test"],
new Response(JSON.stringify({
createMockResponseFromString("http://127.0.0.1:9999/api-kube/apis/apps/v1/namespaces/default/deployments/test", JSON.stringify({
apiVersion: "v1",
kind: "Deployment",
metadata: {
@ -710,7 +766,7 @@ describe("KubeApi", () => {
beforeEach(async () => {
await fetchMock.resolveSpecific(
["http://127.0.0.1:9999/api-kube/apis/apps/v1/namespaces/default/deployments/test"],
new Response(JSON.stringify({
createMockResponseFromString("http://127.0.0.1:9999/api-kube/apis/apps/v1/namespaces/default/deployments/test", JSON.stringify({
apiVersion: "v1",
kind: "Deployment",
metadata: {
@ -768,7 +824,7 @@ describe("KubeApi", () => {
beforeEach(async () => {
fetchMock.resolveSpecific(
["http://127.0.0.1:9999/api-kube/api/v1/namespaces/default/pods/foo?propagationPolicy=Background"],
new Response("{}"),
createMockResponseFromString("http://127.0.0.1:9999/api-kube/api/v1/namespaces/default/pods/foo?propagationPolicy=Background", "{}"),
);
});
@ -804,7 +860,7 @@ describe("KubeApi", () => {
beforeEach(async () => {
fetchMock.resolveSpecific(
["http://127.0.0.1:9999/api-kube/api/v1/namespaces/default/pods/foo?propagationPolicy=Background"],
new Response("{}"),
createMockResponseFromString("http://127.0.0.1:9999/api-kube/api/v1/namespaces/default/pods/foo?propagationPolicy=Background", "{}"),
);
});
@ -840,7 +896,7 @@ describe("KubeApi", () => {
beforeEach(async () => {
fetchMock.resolveSpecific(
["http://127.0.0.1:9999/api-kube/api/v1/namespaces/test/pods/foo?propagationPolicy=Background"],
new Response("{}"),
createMockResponseFromString("http://127.0.0.1:9999/api-kube/api/v1/namespaces/test/pods/foo?propagationPolicy=Background", "{}"),
);
});
@ -886,7 +942,7 @@ describe("KubeApi", () => {
beforeEach(async () => {
fetchMock.resolveSpecific(
["http://127.0.0.1:9999/api-kube/api/v1/namespaces/foo?propagationPolicy=Background"],
new Response("{}"),
createMockResponseFromString("http://127.0.0.1:9999/api-kube/api/v1/namespaces/foo?propagationPolicy=Background", "{}"),
);
});
@ -922,7 +978,7 @@ describe("KubeApi", () => {
beforeEach(async () => {
fetchMock.resolveSpecific(
["http://127.0.0.1:9999/api-kube/api/v1/namespaces/foo?propagationPolicy=Background"],
new Response("{}"),
createMockResponseFromString("http://127.0.0.1:9999/api-kube/api/v1/namespaces/foo?propagationPolicy=Background", "{}"),
);
});
@ -995,7 +1051,7 @@ describe("KubeApi", () => {
return isMatch;
},
new Response(stream),
createMockResponseFromStream("http://127.0.0.1:9999/api-kube/api/v1/namespaces/kube-system/pods?watch=1&resourceVersion=", stream),
);
});
@ -1091,7 +1147,7 @@ describe("KubeApi", () => {
return isMatch;
},
new Response(stream),
createMockResponseFromStream("http://127.0.0.1:9999/api-kube/api/v1/namespaces/kube-system/pods?watch=1&resourceVersion=", stream),
);
});
@ -1186,7 +1242,7 @@ describe("KubeApi", () => {
return isMatch;
},
new Response(stream),
createMockResponseFromStream("http://127.0.0.1:9999/api-kube/api/v1/namespaces/kube-system/pods?watch=1&resourceVersion=&timeoutSeconds=60", stream),
);
});
@ -1350,7 +1406,7 @@ describe("KubeApi", () => {
beforeEach(async () => {
await fetchMock.resolveSpecific(
["http://127.0.0.1:9999/api-kube/api/v1/namespaces/default/pods"],
new Response(JSON.stringify({
createMockResponseFromString("http://127.0.0.1:9999/api-kube/api/v1/namespaces/default/pods", JSON.stringify({
kind: "Pod",
apiVersion: "v1",
metadata: {
@ -1462,7 +1518,7 @@ describe("KubeApi", () => {
beforeEach(async () => {
await fetchMock.resolveSpecific(
["http://127.0.0.1:9999/api-kube/api/v1/namespaces/default/pods/foobar"],
new Response(JSON.stringify({
createMockResponseFromString("http://127.0.0.1:9999/api-kube/api/v1/namespaces/default/pods/foobar", JSON.stringify({
kind: "Pod",
apiVersion: "v1",
metadata: {
@ -1530,7 +1586,7 @@ describe("KubeApi", () => {
beforeEach(async () => {
await fetchMock.resolveSpecific(
["http://127.0.0.1:9999/api-kube/api/v1/pods"],
new Response(JSON.stringify({
createMockResponseFromString("http://127.0.0.1:9999/api-kube/api/v1/pods", JSON.stringify({
kind: "PodList",
apiVersion: "v1",
metadata: {},
@ -1572,7 +1628,7 @@ describe("KubeApi", () => {
beforeEach(async () => {
await fetchMock.resolveSpecific(
["http://127.0.0.1:9999/api-kube/api/v1/pods"],
new Response(JSON.stringify({
createMockResponseFromString("http://127.0.0.1:9999/api-kube/api/v1/pods", JSON.stringify({
kind: "PodList",
apiVersion: "v1",
metadata: {},
@ -1614,7 +1670,7 @@ describe("KubeApi", () => {
beforeEach(async () => {
await fetchMock.resolveSpecific(
["http://127.0.0.1:9999/api-kube/api/v1/namespaces/default/pods"],
new Response(JSON.stringify({
createMockResponseFromString("http://127.0.0.1:9999/api-kube/api/v1/namespaces/default/pods", JSON.stringify({
kind: "PodList",
apiVersion: "v1",
metadata: {},

View File

@ -14,7 +14,7 @@ import byline from "byline";
import type { IKubeWatchEvent } from "./kube-watch-event";
import type { KubeJsonApiData, KubeJsonApi } from "./kube-json-api";
import type { Disposer } from "../utils";
import { isDefined, noop, WrappedAbortController } from "../utils";
import { setTimeoutFor, isDefined, noop, WrappedAbortController } from "../utils";
import type { RequestInit, Response } from "node-fetch";
import type { Patch } from "rfc6902";
import assert from "assert";
@ -643,11 +643,12 @@ export class KubeApi<
clearTimeout(timedRetry);
});
setTimeoutFor(abortController, 600 * 1000);
const requestParams = timeout ? { query: { timeoutSeconds: timeout }} : {};
const watchUrl = this.getWatchUrl(namespace);
const responsePromise = this.request.getResponse(watchUrl, requestParams, {
signal: abortController.signal,
timeout: 600_000,
});
logger.info(`[KUBE-API] watch (${watchId}) ${retry === true ? "retried" : "started"} ${watchUrl}`);
@ -686,7 +687,19 @@ export class KubeApi<
}, timeout * 1000 * 1.1);
}
["end", "close", "error"].forEach((eventName) => {
if (!response.body) {
logger.error(`[KUBE-API]: watch (${watchId}) did not return a body`);
requestRetried = true;
clearTimeout(timedRetry);
timedRetry = setTimeout(() => { // we did not get any kubernetes errors so let's retry
this.watch({ ...opts, namespace, callback, watchId, retry: true });
}, 1000);
return;
}
for (const eventName of ["end", "close", "error"]) {
response.body.on(eventName, () => {
// We only retry if we haven't retried, haven't aborted and haven't received k8s error
// kubernetes errors (=errorReceived set) should be handled in a callback
@ -703,7 +716,7 @@ export class KubeApi<
this.watch({ ...opts, namespace, callback, watchId, retry: true });
}, 1000);
});
});
}
byline(response.body).on("data", (line) => {
try {

View File

@ -473,7 +473,6 @@ export abstract class KubeObjectStore<
callback,
});
// TODO: upgrade node-fetch once we are starting to use ES modules
const signal = abortController.signal;
const callback: KubeApiWatchCallback<D> = (data, error) => {

View File

@ -18,3 +18,9 @@ export class WrappedAbortController extends AbortController {
});
}
}
export function setTimeoutFor(controller: AbortController, timeout: number): void {
const handle = setTimeout(() => controller.abort(), timeout);
controller.signal.addEventListener("abort", () => clearTimeout(handle));
}

View File

@ -11,9 +11,9 @@ const lensResourcesDirInjectable = getInjectable({
instantiate: (di) => {
const isProduction = di.inject(isProductionInjectable);
return !isProduction
? process.cwd()
: process.resourcesPath;
return isProduction
? process.resourcesPath
: process.cwd();
},
causesSideEffects: true,

View File

@ -4,7 +4,7 @@
*/
import type { LensApiRequest, Route } from "../router/route";
import staticFileRouteInjectable from "../routes/static-file-route.injectable";
import staticFileRouteInjectable from "../routes/files/static-file-route.injectable";
import { getDiForUnitTesting } from "../getDiForUnitTesting";
jest.mock("electron", () => ({

View File

@ -0,0 +1,33 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import httpProxy from "http-proxy";
import { webpackDevServerPort } from "../../../../webpack/vars";
import { publicPath } from "../../../common/vars";
import appNameInjectable from "../../../common/vars/app-name.injectable";
import type { LensApiRequest, RouteResponse } from "../../router/route";
const devStaticFileRouteHandlerInjectable = getInjectable({
id: "dev-static-file-route-handler",
instantiate: (di) => {
const proxy = httpProxy.createProxy();
const appName = di.inject(appNameInjectable);
const proxyTarget = `http://127.0.0.1:${webpackDevServerPort}`;
return async ({ raw: { req, res }}: LensApiRequest<"/{path*}">): Promise<RouteResponse<Buffer>> => {
if (req.url === "/" || !req.url) {
req.url = `${publicPath}/${appName}.html`;
} else if (!req.url.startsWith("/build/")) {
return { statusCode: 404 };
}
proxy.web(req, res, { target: proxyTarget });
return { proxy };
};
},
});
export default devStaticFileRouteHandlerInjectable;

View File

@ -0,0 +1,61 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import readFileBufferInjectable from "../../../common/fs/read-file-buffer.injectable";
import joinPathsInjectable from "../../../common/path/join-paths.injectable";
import staticFilesDirectoryInjectable from "../../../common/vars/static-files-directory.injectable";
import appNameInjectable from "../../../common/vars/app-name.injectable";
import type { LensApiRequest } from "../../router/route";
import path from "path";
import type { SupportedFileExtension } from "../../router/router-content-types";
import { contentTypes } from "../../router/router-content-types";
import loggerInjectable from "../../../common/logger.injectable";
import { publicPath } from "../../../common/vars";
const prodStaticFileRouteHandlerInjectable = getInjectable({
id: "prod-static-file-route-handler",
instantiate: (di) => {
const readFileBuffer = di.inject(readFileBufferInjectable);
const joinPaths = di.inject(joinPathsInjectable);
const staticFilesDirectory = di.inject(staticFilesDirectoryInjectable);
const appName = di.inject(appNameInjectable);
const logger = di.inject(loggerInjectable);
return async ({ params }: LensApiRequest<"/{path*}">) => {
let filePath = params.path;
for (let retryCount = 0; retryCount < 5; retryCount += 1) {
const assetFilePath = joinPaths(staticFilesDirectory, filePath);
if (!assetFilePath.startsWith(staticFilesDirectory)) {
return { statusCode: 404 };
}
try {
const fileExtension = path
.extname(assetFilePath)
.slice(1) as SupportedFileExtension;
const contentType = contentTypes[fileExtension] || contentTypes.txt;
return { response: await readFileBuffer(assetFilePath), contentType };
} catch (err) {
if (retryCount > 5) {
logger.error("handleStaticFile:", String(err));
return { statusCode: 404 };
}
filePath = `${publicPath}/${appName}.html`;
}
}
return { statusCode: 404 };
};
},
});
export default prodStaticFileRouteHandlerInjectable;

View File

@ -0,0 +1,28 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getRouteInjectable } from "../../router/router.injectable";
import isDevelopmentInjectable from "../../../common/vars/is-development.injectable";
import { route } from "../../router/route";
import prodStaticFileRouteHandlerInjectable from "./production.injectable";
import devStaticFileRouteHandlerInjectable from "./development.injectable";
const staticFileRouteInjectable = getRouteInjectable({
id: "static-file-route",
instantiate: (di) => {
const isDevelopment = di.inject(isDevelopmentInjectable);
return route({
method: "get",
path: `/{path*}`,
})(
isDevelopment
? di.inject(devStaticFileRouteHandlerInjectable)
: di.inject(prodStaticFileRouteHandlerInjectable),
);
},
});
export default staticFileRouteInjectable;

View File

@ -1,126 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { SupportedFileExtension } from "../router/router-content-types";
import { contentTypes } from "../router/router-content-types";
import logger from "../logger";
import { getRouteInjectable } from "../router/router.injectable";
import { publicPath } from "../../common/vars";
import path from "path";
import isDevelopmentInjectable from "../../common/vars/is-development.injectable";
import httpProxy from "http-proxy";
import readFileBufferInjectable from "../../common/fs/read-file-buffer.injectable";
import type { JoinPaths } from "../../common/path/join-paths.injectable";
import joinPathsInjectable from "../../common/path/join-paths.injectable";
import { webpackDevServerPort } from "../../../webpack/vars";
import type { LensApiRequest, RouteResponse } from "../router/route";
import { route } from "../router/route";
import staticFilesDirectoryInjectable from "../../common/vars/static-files-directory.injectable";
import appNameInjectable from "../../common/vars/app-name.injectable";
import type { GetAbsolutePath } from "../../common/path/get-absolute-path.injectable";
import getAbsolutePathInjectable from "../../common/path/get-absolute-path.injectable";
interface ProductionDependencies {
readFileBuffer: (path: string) => Promise<Buffer>;
joinPaths: JoinPaths;
getAbsolutePath: GetAbsolutePath;
staticFilesDirectory: string;
appName: string;
}
const handleStaticFileInProduction = ({
readFileBuffer,
getAbsolutePath,
joinPaths,
staticFilesDirectory,
appName,
}: ProductionDependencies) => (
async ({ params }: LensApiRequest<"/{path*}">): Promise<RouteResponse<Buffer>> => {
let filePath = params.path;
for (let retryCount = 0; retryCount < 5; retryCount += 1) {
const asset = joinPaths(staticFilesDirectory, filePath);
const normalizedFilePath = getAbsolutePath(asset);
if (!normalizedFilePath.startsWith(staticFilesDirectory)) {
return { statusCode: 404 };
}
try {
const fileExtension = path
.extname(asset)
.slice(1) as SupportedFileExtension;
const contentType = contentTypes[fileExtension] || contentTypes.txt;
return { response: await readFileBuffer(asset), contentType };
} catch (err) {
if (retryCount > 5) {
logger.error("handleStaticFile:", String(err));
return { statusCode: 404 };
}
filePath = `${publicPath}/${appName}.html`;
}
}
return { statusCode: 404 };
}
);
interface DevelopmentDependencies {
proxy: httpProxy;
appName: string;
}
const handleStaticFileInDevelopment = ({
proxy,
appName,
}: DevelopmentDependencies) => (
({ raw: { req, res }}: LensApiRequest<"/{path*}">): RouteResponse<Buffer> => {
if (req.url === "/" || !req.url?.startsWith("/build/")) {
req.url = `${publicPath}/${appName}.html`;
}
proxy.web(req, res, {
target: `http://127.0.0.1:${webpackDevServerPort}`,
});
return { proxy };
}
);
const staticFileRouteInjectable = getRouteInjectable({
id: "static-file-route",
instantiate: (di) => {
const isDevelopment = di.inject(isDevelopmentInjectable);
const readFileBuffer = di.inject(readFileBufferInjectable);
const joinPaths = di.inject(joinPathsInjectable);
const getAbsolutePath = di.inject(getAbsolutePathInjectable);
const staticFilesDirectory = di.inject(staticFilesDirectoryInjectable);
const appName = di.inject(appNameInjectable);
return route({
method: "get",
path: `/{path*}`,
})(
isDevelopment
? handleStaticFileInDevelopment({
proxy: httpProxy.createProxy(),
appName,
})
: handleStaticFileInProduction({
readFileBuffer,
joinPaths,
staticFilesDirectory,
appName,
getAbsolutePath,
}),
);
},
});
export default staticFileRouteInjectable;

31
webpack/node-fetch.ts Normal file
View File

@ -0,0 +1,31 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import path from "path";
export default {
entry: "./node_modules/node-fetch/src/index.js",
output: {
path: path.resolve(__dirname, "..", "build", "webpack"),
filename: "node-fetch.bundle.js",
library: {
name: "NodeFetch",
type: "commonjs",
},
clean: true,
asyncChunks: false, // This is required so that only one file is created
},
mode: "production",
target: "electron-renderer",
optimization: {
concatenateModules: true,
minimize: true,
},
externalsPresets: {
node: true,
},
resolve: {
extensions: [".js"],
},
};

View File

@ -2221,31 +2221,16 @@
resolved "https://registry.yarnpkg.com/@types/ms/-/ms-0.7.31.tgz#31b7ca6407128a3d2bbc27fe2d21b345397f6197"
integrity sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==
"@types/node-fetch@^2.6.2":
version "2.6.2"
resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.6.2.tgz#d1a9c5fd049d9415dce61571557104dec3ec81da"
integrity sha512-DHqhlq5jeESLy19TYhLakJ07kNumXWjcDdxXsLUMJZ6ue8VZJj4kLPQVE/2mdHh3xZziNF1xppu5lwmS53HR+A==
dependencies:
"@types/node" "*"
form-data "^3.0.0"
"@types/node@*":
version "17.0.24"
resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.24.tgz#20ba1bf69c1b4ab405c7a01e950c4f446b05029f"
integrity sha512-aveCYRQbgTH9Pssp1voEP7HiuWlD2jW2BO56w+bVrJn04i61yh6mRfoKO6hEYQD9vF+W8Chkwc6j1M36uPkx4g==
"@types/node@^16.11.26", "@types/node@^16.18.3":
"@types/node@^16.11.26", "@types/node@^16.18.2":
version "16.18.3"
resolved "https://registry.yarnpkg.com/@types/node/-/node-16.18.3.tgz#d7f7ba828ad9e540270f01ce00d391c54e6e0abc"
integrity sha512-jh6m0QUhIRcZpNv7Z/rpN+ZWXOicUUQbSoWks7Htkbb9IjFQj4kzcX/xFCkjstCj5flMsN8FiSvt+q+Tcs4Llg==
"@types/npm@^2.0.32":
version "2.0.32"
resolved "https://registry.yarnpkg.com/@types/npm/-/npm-2.0.32.tgz#036682075b9c2116b510fe24b52a5b932e3a99d5"
integrity sha512-9Lg4woNVzJCtac0lET91H65lbO+8YXfk0nmlmoPGhHXMdaVEDloH6zOPIYMy2n39z/aCXXQR0nax66EDekAyIQ==
dependencies:
"@types/node" "*"
"@types/parse-json@^4.0.0":
version "4.0.0"
resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0"
@ -4552,6 +4537,11 @@ dashdash@^1.12.0:
dependencies:
assert-plus "^1.0.0"
data-uri-to-buffer@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-4.0.0.tgz#b5db46aea50f6176428ac05b73be39a57701a64b"
integrity sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA==
data-urls@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-2.0.0.tgz#156485a72963a970f5d5821aaf642bef2bf2db9b"
@ -5899,6 +5889,14 @@ fecha@^4.2.0:
resolved "https://registry.yarnpkg.com/fecha/-/fecha-4.2.3.tgz#4d9ccdbc61e8629b259fdca67e65891448d569fd"
integrity sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==
fetch-blob@^3.1.2, fetch-blob@^3.1.4:
version "3.2.0"
resolved "https://registry.yarnpkg.com/fetch-blob/-/fetch-blob-3.2.0.tgz#f09b8d4bbd45adc6f0c20b7e787e793e309dcce9"
integrity sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==
dependencies:
node-domexception "^1.0.0"
web-streams-polyfill "^3.0.3"
figures@^3.0.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af"
@ -6127,6 +6125,13 @@ form-data@~2.3.2:
combined-stream "^1.0.6"
mime-types "^2.1.12"
formdata-polyfill@^4.0.10:
version "4.0.10"
resolved "https://registry.yarnpkg.com/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz#24807c31c9d402e002ab3d8c720144ceb8848423"
integrity sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==
dependencies:
fetch-blob "^3.1.2"
forwarded@0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811"
@ -9104,12 +9109,19 @@ node-addon-api@^5.0.0:
resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-5.0.0.tgz#7d7e6f9ef89043befdb20c1989c905ebde18c501"
integrity sha512-CvkDw2OEnme7ybCykJpVcKH+uAOLV2qLqiyla128dN9TkEWfrYmxG6C2boDe5KcNQqZF3orkqzGgOMvZ/JNekA==
node-fetch@^2.6.7:
version "2.6.7"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad"
integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==
node-domexception@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5"
integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==
node-fetch@^3.2.10:
version "3.2.10"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-3.2.10.tgz#e8347f94b54ae18b57c9c049ef641cef398a85c8"
integrity sha512-MhuzNwdURnZ1Cp4XTazr69K0BTizsBroX7Zx3UgDSVcZYKF/6p0CBe4EUb/hLqmzVhl0UpYfgRljQ4yxE+iCxA==
dependencies:
whatwg-url "^5.0.0"
data-uri-to-buffer "^4.0.0"
fetch-blob "^3.1.4"
formdata-polyfill "^4.0.10"
node-forge@^0.10.0:
version "0.10.0"
@ -12069,11 +12081,6 @@ tr46@^3.0.0:
dependencies:
punycode "^2.1.1"
tr46@~0.0.3:
version "0.0.3"
resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a"
integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==
traverse-chain@~0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/traverse-chain/-/traverse-chain-0.1.0.tgz#61dbc2d53b69ff6091a12a168fd7d433107e40f1"
@ -12601,10 +12608,10 @@ wcwidth@^1.0.0:
dependencies:
defaults "^1.0.3"
webidl-conversions@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"
integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==
web-streams-polyfill@^3.0.3:
version "3.2.1"
resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz#71c2718c52b45fd49dbeee88634b3a60ceab42a6"
integrity sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==
webidl-conversions@^5.0.0:
version "5.0.0"
@ -12795,14 +12802,6 @@ whatwg-url@^11.0.0:
tr46 "^3.0.0"
webidl-conversions "^7.0.0"
whatwg-url@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d"
integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==
dependencies:
tr46 "~0.0.3"
webidl-conversions "^3.0.0"
whatwg-url@^8.0.0, whatwg-url@^8.5.0:
version "8.7.0"
resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-8.7.0.tgz#656a78e510ff8f3937bc0bcbe9f5c0ac35941b77"