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/**/*", "**/static/**/*",
"**/site/**/*", "**/site/**/*",
"extensions/*/*.tgz", "extensions/*/*.tgz",
"build/webpack/**/*",
], ],
settings: { settings: {
react: { react: {

1
.gitignore vendored
View File

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

View File

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

View File

@ -8,15 +8,19 @@ import { open } from "fs/promises";
import type { WriteStream } from "fs-extra"; import type { WriteStream } from "fs-extra";
import { constants, ensureDir, unlink } from "fs-extra"; import { constants, ensureDir, unlink } from "fs-extra";
import path from "path"; import path from "path";
import fetch from "node-fetch"; import type * as FetchModule from "node-fetch";
import { promisify } from "util"; import { promisify } from "util";
import { pipeline as _pipeline, Transform, Writable } from "stream"; import { pipeline as _pipeline, Transform, Writable } from "stream";
import type { SingleBar } from "cli-progress"; import type { SingleBar } from "cli-progress";
import { MultiBar } from "cli-progress"; import { MultiBar } from "cli-progress";
import { extract } from "tar-stream"; import { extract } from "tar-stream";
import gunzip from "gunzip-maybe"; import gunzip from "gunzip-maybe";
import { getBinaryName, normalizedPlatform } from "../src/common/vars"; import { getBinaryName } from "../src/common/vars";
import { isErrnoException } from "../src/common/utils"; import { isErrnoException, setTimeoutFor } from "../src/common/utils";
type Response = FetchModule.Response;
type RequestInfo = FetchModule.RequestInfo;
type RequestInit = FetchModule.RequestInit;
const pipeline = promisify(_pipeline); const pipeline = promisify(_pipeline);
@ -29,6 +33,10 @@ interface BinaryDownloaderArgs {
readonly baseDir: string; readonly baseDir: string;
} }
interface BinaryDownloaderDependencies {
fetch: (url: RequestInfo, init?: RequestInit) => Promise<Response>;
}
abstract class BinaryDownloader { abstract class BinaryDownloader {
protected abstract readonly url: string; protected abstract readonly url: string;
protected readonly bar: SingleBar; protected readonly bar: SingleBar;
@ -38,7 +46,7 @@ abstract class BinaryDownloader {
return [file]; 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.bar = multiBar.create(1, 0, args);
this.target = path.join(args.baseDir, args.platform, args.fileArch, args.binaryName); this.target = path.join(args.baseDir, args.platform, args.fileArch, args.binaryName);
} }
@ -49,8 +57,10 @@ abstract class BinaryDownloader {
} }
const controller = new AbortController(); 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, signal: controller.signal,
}); });
const total = Number(stream.headers.get("content-length")); 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); 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( await pipeline(
stream.body, stream.body,
new Transform({ new Transform({
@ -108,10 +122,10 @@ abstract class BinaryDownloader {
class LensK8sProxyDownloader extends BinaryDownloader { class LensK8sProxyDownloader extends BinaryDownloader {
protected readonly url: string; 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 }); 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}`; 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 { class KubectlDownloader extends BinaryDownloader {
protected readonly url: string; 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 }); 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}`; 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 { class HelmDownloader extends BinaryDownloader {
protected readonly url: string; 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 }); 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`; 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"; type SupportedPlatform = "darwin" | "linux" | "windows";
const importFetchModule = new Function('return import("node-fetch")') as () => Promise<typeof FetchModule>;
async function main() { 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({ const multiBar = new MultiBar({
align: "left", align: "left",
clearOnComplete: false, clearOnComplete: false,
@ -171,21 +202,21 @@ async function main() {
}); });
const baseDir = path.join(__dirname, "..", "binaries", "client"); const baseDir = path.join(__dirname, "..", "binaries", "client");
const downloaders: BinaryDownloader[] = [ const downloaders: BinaryDownloader[] = [
new LensK8sProxyDownloader({ new LensK8sProxyDownloader(deps, {
version: packageInfo.config.k8sProxyVersion, version: packageInfo.config.k8sProxyVersion,
platform: normalizedPlatform, platform: normalizedPlatform,
downloadArch: "amd64", downloadArch: "amd64",
fileArch: "x64", fileArch: "x64",
baseDir, baseDir,
}, multiBar), }, multiBar),
new KubectlDownloader({ new KubectlDownloader(deps, {
version: packageInfo.config.bundledKubectlVersion, version: packageInfo.config.bundledKubectlVersion,
platform: normalizedPlatform, platform: normalizedPlatform,
downloadArch: "amd64", downloadArch: "amd64",
fileArch: "x64", fileArch: "x64",
baseDir, baseDir,
}, multiBar), }, multiBar),
new HelmDownloader({ new HelmDownloader(deps, {
version: packageInfo.config.bundledHelmVersion, version: packageInfo.config.bundledHelmVersion,
platform: normalizedPlatform, platform: normalizedPlatform,
downloadArch: "amd64", downloadArch: "amd64",
@ -196,21 +227,21 @@ async function main() {
if (normalizedPlatform !== "windows") { if (normalizedPlatform !== "windows") {
downloaders.push( downloaders.push(
new LensK8sProxyDownloader({ new LensK8sProxyDownloader(deps, {
version: packageInfo.config.k8sProxyVersion, version: packageInfo.config.k8sProxyVersion,
platform: normalizedPlatform, platform: normalizedPlatform,
downloadArch: "arm64", downloadArch: "arm64",
fileArch: "arm64", fileArch: "arm64",
baseDir, baseDir,
}, multiBar), }, multiBar),
new KubectlDownloader({ new KubectlDownloader(deps, {
version: packageInfo.config.bundledKubectlVersion, version: packageInfo.config.bundledKubectlVersion,
platform: normalizedPlatform, platform: normalizedPlatform,
downloadArch: "arm64", downloadArch: "arm64",
fileArch: "arm64", fileArch: "arm64",
baseDir, baseDir,
}, multiBar), }, multiBar),
new HelmDownloader({ new HelmDownloader(deps, {
version: packageInfo.config.bundledHelmVersion, version: packageInfo.config.bundledHelmVersion,
platform: normalizedPlatform, platform: normalizedPlatform,
downloadArch: "arm64", downloadArch: "arm64",

View File

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

View File

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

View File

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

View File

@ -3,8 +3,12 @@
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
import { getInjectable } from "@ogre-tools/injectable"; import { getInjectable } from "@ogre-tools/injectable";
import fetch from "node-fetch"; import type * as FetchModule from "node-fetch";
import type { RequestInit, Response } 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>; 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 fetchInjectable from "../../fetch/fetch.injectable";
import type { CreateKubeApiForRemoteCluster } from "../create-kube-api-for-remote-cluster.injectable"; import type { CreateKubeApiForRemoteCluster } from "../create-kube-api-for-remote-cluster.injectable";
import createKubeApiForRemoteClusterInjectable 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 type { AsyncFnMock } from "@async-fn/jest";
import asyncFn from "@async-fn/jest"; import asyncFn from "@async-fn/jest";
import { flushPromises } from "../../test-utils/flush-promises"; import { flushPromises } from "../../test-utils/flush-promises";
import createKubeJsonApiInjectable from "../create-kube-json-api.injectable"; import createKubeJsonApiInjectable from "../create-kube-json-api.injectable";
import type { IKubeWatchEvent } from "../kube-watch-event"; import type { IKubeWatchEvent } from "../kube-watch-event";
import type { KubeJsonApiDataFor } from "../kube-object"; import type { KubeJsonApiDataFor } from "../kube-object";
import type { Response, Headers as NodeFetchHeaders } from "node-fetch";
import AbortController from "abort-controller"; 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", () => { describe("createKubeApiForRemoteCluster", () => {
let createKubeApiForRemoteCluster: CreateKubeApiForRemoteCluster; let createKubeApiForRemoteCluster: CreateKubeApiForRemoteCluster;
let fetchMock: AsyncFnMock<Fetch>; let fetchMock: AsyncFnMock<Fetch>;
beforeEach(() => { beforeEach(async () => {
const di = getDiForUnitTesting({ doGeneralOverrides: true }); const di = getDiForUnitTesting({ doGeneralOverrides: true });
fetchMock = asyncFn(); fetchMock = asyncFn();
@ -94,7 +150,7 @@ describe("createKubeApiForRemoteCluster", () => {
beforeEach(async () => { beforeEach(async () => {
await fetchMock.resolveSpecific( await fetchMock.resolveSpecific(
["https://127.0.0.1:6443/api/v1/pods"], ["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", kind: "PodList",
apiVersion: "v1", apiVersion: "v1",
metadata:{ metadata:{
@ -118,7 +174,7 @@ describe("KubeApi", () => {
let registerApiSpy: jest.SpiedFunction<ApiManager["registerApi"]>; let registerApiSpy: jest.SpiedFunction<ApiManager["registerApi"]>;
let fetchMock: AsyncFnMock<Fetch>; let fetchMock: AsyncFnMock<Fetch>;
beforeEach(() => { beforeEach(async () => {
const di = getDiForUnitTesting({ doGeneralOverrides: true }); const di = getDiForUnitTesting({ doGeneralOverrides: true });
fetchMock = asyncFn(); fetchMock = asyncFn();
@ -172,7 +228,7 @@ describe("KubeApi", () => {
beforeEach(async () => { beforeEach(async () => {
await fetchMock.resolveSpecific( await fetchMock.resolveSpecific(
["http://127.0.0.1:9999/api-kube/apis/networking.k8s.io/v1"], ["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: [{ resources: [{
name: "ingresses", name: "ingresses",
}], }],
@ -196,7 +252,7 @@ describe("KubeApi", () => {
beforeEach(async () => { beforeEach(async () => {
await fetchMock.resolveSpecific( await fetchMock.resolveSpecific(
["http://127.0.0.1:9999/api-kube/apis/networking.k8s.io"], ["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: { preferredVersion: {
version: "v1", version: "v1",
}, },
@ -234,7 +290,7 @@ describe("KubeApi", () => {
beforeEach(async () => { beforeEach(async () => {
await fetchMock.resolveSpecific( await fetchMock.resolveSpecific(
["http://127.0.0.1:9999/api-kube/apis/networking.k8s.io/v1/namespaces/default/ingresses/foo"], ["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; result = await getCall;
}); });
@ -274,7 +330,7 @@ describe("KubeApi", () => {
beforeEach(async () => { beforeEach(async () => {
await fetchMock.resolveSpecific( await fetchMock.resolveSpecific(
["http://127.0.0.1:9999/api-kube/apis/networking.k8s.io/v1/namespaces/default/ingresses/foo1"], ["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; result = await getCall;
}); });
@ -292,7 +348,7 @@ describe("KubeApi", () => {
beforeEach(async () => { beforeEach(async () => {
await fetchMock.resolveSpecific( await fetchMock.resolveSpecific(
["http://127.0.0.1:9999/api-kube/apis/networking.k8s.io/v1/namespaces/default/ingresses/foo"], ["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", apiVersion: "v1",
kind: "Ingress", kind: "Ingress",
metadata: { metadata: {
@ -341,7 +397,7 @@ describe("KubeApi", () => {
beforeEach(async () => { beforeEach(async () => {
await fetchMock.resolveSpecific( await fetchMock.resolveSpecific(
["http://127.0.0.1:9999/api-kube/apis/networking.k8s.io/v1/namespaces/default/ingresses/foo1"], ["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; result = await getCall;
}); });
@ -359,7 +415,7 @@ describe("KubeApi", () => {
beforeEach(async () => { beforeEach(async () => {
await fetchMock.resolveSpecific( await fetchMock.resolveSpecific(
["http://127.0.0.1:9999/api-kube/apis/networking.k8s.io/v1"], ["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: [], resources: [],
})), })),
); );
@ -381,7 +437,7 @@ describe("KubeApi", () => {
beforeEach(async () => { beforeEach(async () => {
await fetchMock.resolveSpecific( await fetchMock.resolveSpecific(
["http://127.0.0.1:9999/api-kube/apis/extensions/v1beta1"], ["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: [{ resources: [{
name: "ingresses", name: "ingresses",
}], }],
@ -405,7 +461,7 @@ describe("KubeApi", () => {
beforeEach(async () => { beforeEach(async () => {
await fetchMock.resolveSpecific( await fetchMock.resolveSpecific(
["http://127.0.0.1:9999/api-kube/apis/extensions"], ["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: { preferredVersion: {
version: "v1beta1", version: "v1beta1",
}, },
@ -443,7 +499,7 @@ describe("KubeApi", () => {
beforeEach(async () => { beforeEach(async () => {
await fetchMock.resolveSpecific( await fetchMock.resolveSpecific(
["http://127.0.0.1:9999/api-kube/apis/extensions/v1beta1/namespaces/default/ingresses/foo"], ["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; result = await getCall;
}); });
@ -483,7 +539,7 @@ describe("KubeApi", () => {
beforeEach(async () => { beforeEach(async () => {
await fetchMock.resolveSpecific( await fetchMock.resolveSpecific(
["http://127.0.0.1:9999/api-kube/apis/extensions/v1beta1/namespaces/default/ingresses/foo1"], ["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; result = await getCall;
}); });
@ -501,7 +557,7 @@ describe("KubeApi", () => {
beforeEach(async () => { beforeEach(async () => {
await fetchMock.resolveSpecific( await fetchMock.resolveSpecific(
["http://127.0.0.1:9999/api-kube/apis/extensions/v1beta1/namespaces/default/ingresses/foo"], ["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", apiVersion: "v1beta1",
kind: "Ingress", kind: "Ingress",
metadata: { metadata: {
@ -550,7 +606,7 @@ describe("KubeApi", () => {
beforeEach(async () => { beforeEach(async () => {
await fetchMock.resolveSpecific( await fetchMock.resolveSpecific(
["http://127.0.0.1:9999/api-kube/apis/extensions/v1beta1/namespaces/default/ingresses/foo1"], ["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; result = await getCall;
}); });
@ -604,7 +660,7 @@ describe("KubeApi", () => {
beforeEach(async () => { beforeEach(async () => {
await fetchMock.resolveSpecific( await fetchMock.resolveSpecific(
["http://127.0.0.1:9999/api-kube/apis/apps/v1/namespaces/default/deployments/test"], ["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", apiVersion: "v1",
kind: "Deployment", kind: "Deployment",
metadata: { metadata: {
@ -657,7 +713,7 @@ describe("KubeApi", () => {
beforeEach(async () => { beforeEach(async () => {
await fetchMock.resolveSpecific( await fetchMock.resolveSpecific(
["http://127.0.0.1:9999/api-kube/apis/apps/v1/namespaces/default/deployments/test"], ["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", apiVersion: "v1",
kind: "Deployment", kind: "Deployment",
metadata: { metadata: {
@ -710,7 +766,7 @@ describe("KubeApi", () => {
beforeEach(async () => { beforeEach(async () => {
await fetchMock.resolveSpecific( await fetchMock.resolveSpecific(
["http://127.0.0.1:9999/api-kube/apis/apps/v1/namespaces/default/deployments/test"], ["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", apiVersion: "v1",
kind: "Deployment", kind: "Deployment",
metadata: { metadata: {
@ -768,7 +824,7 @@ describe("KubeApi", () => {
beforeEach(async () => { beforeEach(async () => {
fetchMock.resolveSpecific( fetchMock.resolveSpecific(
["http://127.0.0.1:9999/api-kube/api/v1/namespaces/default/pods/foo?propagationPolicy=Background"], ["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 () => { beforeEach(async () => {
fetchMock.resolveSpecific( fetchMock.resolveSpecific(
["http://127.0.0.1:9999/api-kube/api/v1/namespaces/default/pods/foo?propagationPolicy=Background"], ["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 () => { beforeEach(async () => {
fetchMock.resolveSpecific( fetchMock.resolveSpecific(
["http://127.0.0.1:9999/api-kube/api/v1/namespaces/test/pods/foo?propagationPolicy=Background"], ["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 () => { beforeEach(async () => {
fetchMock.resolveSpecific( fetchMock.resolveSpecific(
["http://127.0.0.1:9999/api-kube/api/v1/namespaces/foo?propagationPolicy=Background"], ["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 () => { beforeEach(async () => {
fetchMock.resolveSpecific( fetchMock.resolveSpecific(
["http://127.0.0.1:9999/api-kube/api/v1/namespaces/foo?propagationPolicy=Background"], ["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; 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; 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; 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 () => { beforeEach(async () => {
await fetchMock.resolveSpecific( await fetchMock.resolveSpecific(
["http://127.0.0.1:9999/api-kube/api/v1/namespaces/default/pods"], ["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", kind: "Pod",
apiVersion: "v1", apiVersion: "v1",
metadata: { metadata: {
@ -1462,7 +1518,7 @@ describe("KubeApi", () => {
beforeEach(async () => { beforeEach(async () => {
await fetchMock.resolveSpecific( await fetchMock.resolveSpecific(
["http://127.0.0.1:9999/api-kube/api/v1/namespaces/default/pods/foobar"], ["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", kind: "Pod",
apiVersion: "v1", apiVersion: "v1",
metadata: { metadata: {
@ -1530,7 +1586,7 @@ describe("KubeApi", () => {
beforeEach(async () => { beforeEach(async () => {
await fetchMock.resolveSpecific( await fetchMock.resolveSpecific(
["http://127.0.0.1:9999/api-kube/api/v1/pods"], ["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", kind: "PodList",
apiVersion: "v1", apiVersion: "v1",
metadata: {}, metadata: {},
@ -1572,7 +1628,7 @@ describe("KubeApi", () => {
beforeEach(async () => { beforeEach(async () => {
await fetchMock.resolveSpecific( await fetchMock.resolveSpecific(
["http://127.0.0.1:9999/api-kube/api/v1/pods"], ["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", kind: "PodList",
apiVersion: "v1", apiVersion: "v1",
metadata: {}, metadata: {},
@ -1614,7 +1670,7 @@ describe("KubeApi", () => {
beforeEach(async () => { beforeEach(async () => {
await fetchMock.resolveSpecific( await fetchMock.resolveSpecific(
["http://127.0.0.1:9999/api-kube/api/v1/namespaces/default/pods"], ["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", kind: "PodList",
apiVersion: "v1", apiVersion: "v1",
metadata: {}, metadata: {},

View File

@ -14,7 +14,7 @@ import byline from "byline";
import type { IKubeWatchEvent } from "./kube-watch-event"; import type { IKubeWatchEvent } from "./kube-watch-event";
import type { KubeJsonApiData, KubeJsonApi } from "./kube-json-api"; import type { KubeJsonApiData, KubeJsonApi } from "./kube-json-api";
import type { Disposer } from "../utils"; 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 { RequestInit, Response } from "node-fetch";
import type { Patch } from "rfc6902"; import type { Patch } from "rfc6902";
import assert from "assert"; import assert from "assert";
@ -643,11 +643,12 @@ export class KubeApi<
clearTimeout(timedRetry); clearTimeout(timedRetry);
}); });
setTimeoutFor(abortController, 600 * 1000);
const requestParams = timeout ? { query: { timeoutSeconds: timeout }} : {}; const requestParams = timeout ? { query: { timeoutSeconds: timeout }} : {};
const watchUrl = this.getWatchUrl(namespace); const watchUrl = this.getWatchUrl(namespace);
const responsePromise = this.request.getResponse(watchUrl, requestParams, { const responsePromise = this.request.getResponse(watchUrl, requestParams, {
signal: abortController.signal, signal: abortController.signal,
timeout: 600_000,
}); });
logger.info(`[KUBE-API] watch (${watchId}) ${retry === true ? "retried" : "started"} ${watchUrl}`); logger.info(`[KUBE-API] watch (${watchId}) ${retry === true ? "retried" : "started"} ${watchUrl}`);
@ -686,7 +687,19 @@ export class KubeApi<
}, timeout * 1000 * 1.1); }, 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, () => { response.body.on(eventName, () => {
// We only retry if we haven't retried, haven't aborted and haven't received k8s error // 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 // 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 }); this.watch({ ...opts, namespace, callback, watchId, retry: true });
}, 1000); }, 1000);
}); });
}); }
byline(response.body).on("data", (line) => { byline(response.body).on("data", (line) => {
try { try {

View File

@ -473,7 +473,6 @@ export abstract class KubeObjectStore<
callback, callback,
}); });
// TODO: upgrade node-fetch once we are starting to use ES modules
const signal = abortController.signal; const signal = abortController.signal;
const callback: KubeApiWatchCallback<D> = (data, error) => { 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) => { instantiate: (di) => {
const isProduction = di.inject(isProductionInjectable); const isProduction = di.inject(isProductionInjectable);
return !isProduction return isProduction
? process.cwd() ? process.resourcesPath
: process.resourcesPath; : process.cwd();
}, },
causesSideEffects: true, causesSideEffects: true,

View File

@ -4,7 +4,7 @@
*/ */
import type { LensApiRequest, Route } from "../router/route"; 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"; import { getDiForUnitTesting } from "../getDiForUnitTesting";
jest.mock("electron", () => ({ 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" resolved "https://registry.yarnpkg.com/@types/ms/-/ms-0.7.31.tgz#31b7ca6407128a3d2bbc27fe2d21b345397f6197"
integrity sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA== 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@*": "@types/node@*":
version "17.0.24" version "17.0.24"
resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.24.tgz#20ba1bf69c1b4ab405c7a01e950c4f446b05029f" resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.24.tgz#20ba1bf69c1b4ab405c7a01e950c4f446b05029f"
integrity sha512-aveCYRQbgTH9Pssp1voEP7HiuWlD2jW2BO56w+bVrJn04i61yh6mRfoKO6hEYQD9vF+W8Chkwc6j1M36uPkx4g== 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" version "16.18.3"
resolved "https://registry.yarnpkg.com/@types/node/-/node-16.18.3.tgz#d7f7ba828ad9e540270f01ce00d391c54e6e0abc" resolved "https://registry.yarnpkg.com/@types/node/-/node-16.18.3.tgz#d7f7ba828ad9e540270f01ce00d391c54e6e0abc"
integrity sha512-jh6m0QUhIRcZpNv7Z/rpN+ZWXOicUUQbSoWks7Htkbb9IjFQj4kzcX/xFCkjstCj5flMsN8FiSvt+q+Tcs4Llg== 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": "@types/parse-json@^4.0.0":
version "4.0.0" version "4.0.0"
resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0"
@ -4552,6 +4537,11 @@ dashdash@^1.12.0:
dependencies: dependencies:
assert-plus "^1.0.0" 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: data-urls@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-2.0.0.tgz#156485a72963a970f5d5821aaf642bef2bf2db9b" 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" resolved "https://registry.yarnpkg.com/fecha/-/fecha-4.2.3.tgz#4d9ccdbc61e8629b259fdca67e65891448d569fd"
integrity sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw== 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: figures@^3.0.0:
version "3.2.0" version "3.2.0"
resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af" 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" combined-stream "^1.0.6"
mime-types "^2.1.12" 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: forwarded@0.2.0:
version "0.2.0" version "0.2.0"
resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" 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" resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-5.0.0.tgz#7d7e6f9ef89043befdb20c1989c905ebde18c501"
integrity sha512-CvkDw2OEnme7ybCykJpVcKH+uAOLV2qLqiyla128dN9TkEWfrYmxG6C2boDe5KcNQqZF3orkqzGgOMvZ/JNekA== integrity sha512-CvkDw2OEnme7ybCykJpVcKH+uAOLV2qLqiyla128dN9TkEWfrYmxG6C2boDe5KcNQqZF3orkqzGgOMvZ/JNekA==
node-fetch@^2.6.7: node-domexception@^1.0.0:
version "2.6.7" version "1.0.0"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" resolved "https://registry.yarnpkg.com/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5"
integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== 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: 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: node-forge@^0.10.0:
version "0.10.0" version "0.10.0"
@ -12069,11 +12081,6 @@ tr46@^3.0.0:
dependencies: dependencies:
punycode "^2.1.1" 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: traverse-chain@~0.1.0:
version "0.1.0" version "0.1.0"
resolved "https://registry.yarnpkg.com/traverse-chain/-/traverse-chain-0.1.0.tgz#61dbc2d53b69ff6091a12a168fd7d433107e40f1" resolved "https://registry.yarnpkg.com/traverse-chain/-/traverse-chain-0.1.0.tgz#61dbc2d53b69ff6091a12a168fd7d433107e40f1"
@ -12601,10 +12608,10 @@ wcwidth@^1.0.0:
dependencies: dependencies:
defaults "^1.0.3" defaults "^1.0.3"
webidl-conversions@^3.0.0: web-streams-polyfill@^3.0.3:
version "3.0.1" version "3.2.1"
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz#71c2718c52b45fd49dbeee88634b3a60ceab42a6"
integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== integrity sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==
webidl-conversions@^5.0.0: webidl-conversions@^5.0.0:
version "5.0.0" version "5.0.0"
@ -12795,14 +12802,6 @@ whatwg-url@^11.0.0:
tr46 "^3.0.0" tr46 "^3.0.0"
webidl-conversions "^7.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: whatwg-url@^8.0.0, whatwg-url@^8.5.0:
version "8.7.0" version "8.7.0"
resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-8.7.0.tgz#656a78e510ff8f3937bc0bcbe9f5c0ac35941b77" resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-8.7.0.tgz#656a78e510ff8f3937bc0bcbe9f5c0ac35941b77"