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 authored and GitHub committed 2022-11-14 10:46:53 -05:00
1 parent 6d9a76835c
commit 86ac417cab
20 files changed
+371 -237

No files matched your search

+1 -2
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",
@@ -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);
+6 -2
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>;
+89 -33
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: {},
+17 -4
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 {
-1
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) => {
+6
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));
}
@@ -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,
+1 -1
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", () => ({
@@ -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;
@@ -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;
@@ -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;
@@ -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;