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

use browser fetch on renderer

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>
This commit is contained in:
Jari Kolehmainen 2021-09-22 17:37:34 +03:00
parent 070fc1bb44
commit 7e20856641
4 changed files with 57 additions and 35 deletions

View File

@ -48,25 +48,17 @@ if (typeof window === "undefined") {
}); });
} else { } else {
apiBase = new JsonApi({ apiBase = new JsonApi({
serverAddress: `http://127.0.0.1:${window.location.port}`, serverAddress: `http://localhost:${window.location.port}`,
apiBase: apiPrefix, apiBase: apiPrefix,
debug: isDevelopment || isDebugging, debug: isDevelopment || isDebugging,
}, {
headers: {
"Host": window.location.host
}
}); });
} }
if (isClusterPageContext()) { if (isClusterPageContext()) {
apiKube = new KubeJsonApi({ apiKube = new KubeJsonApi({
serverAddress: `http://127.0.0.1:${window.location.port}`, serverAddress: `http://${window.location.host}:${window.location.port}`,
apiBase: apiKubePrefix, apiBase: apiKubePrefix,
debug: isDevelopment debug: isDevelopment
}, {
headers: {
"Host": window.location.host
}
}); });
} }

View File

@ -21,8 +21,9 @@
// Base http-service / json-api class // Base http-service / json-api class
import { randomBytes } from "crypto";
import { merge } from "lodash"; import { merge } from "lodash";
import fetch, { Response, RequestInit } from "node-fetch"; import nodeFetch, { Response as NodeResponse, RequestInit as NodeRequestInit } from "node-fetch";
import { stringify } from "querystring"; import { stringify } from "querystring";
import { EventEmitter } from "../../common/event-emitter"; import { EventEmitter } from "../../common/event-emitter";
import logger from "../../common/logger"; import logger from "../../common/logger";
@ -44,11 +45,14 @@ export interface JsonApiParams<D = any> {
export interface JsonApiLog { export interface JsonApiLog {
method: string; method: string;
reqUrl: string; reqUrl: string;
reqInit: RequestInit; reqInit: RequestInit | NodeRequestInit;
data?: any; data?: any;
error?: any; error?: any;
} }
export type JsonRequestInit = RequestInit | NodeRequestInit;
export type JsonResponse = Response | NodeResponse;
export interface JsonApiConfig { export interface JsonApiConfig {
apiBase: string; apiBase: string;
serverAddress: string; serverAddress: string;
@ -65,22 +69,22 @@ export class JsonApi<D = JsonApiData, P extends JsonApiParams = JsonApiParams> {
debug: false debug: false
}; };
constructor(public readonly config: JsonApiConfig, protected reqInit?: RequestInit) { constructor(public readonly config: JsonApiConfig, protected reqInit?: JsonRequestInit) {
this.config = Object.assign({}, JsonApi.configDefault, config); this.config = Object.assign({}, JsonApi.configDefault, config);
this.reqInit = merge({}, JsonApi.reqInitDefault, reqInit); this.reqInit = merge({}, JsonApi.reqInitDefault, reqInit);
this.parseResponse = this.parseResponse.bind(this); this.parseResponse = this.parseResponse.bind(this);
} }
public onData = new EventEmitter<[D, Response]>(); public onData = new EventEmitter<[D, JsonResponse]>();
public onError = new EventEmitter<[JsonApiErrorParsed, Response]>(); public onError = new EventEmitter<[JsonApiErrorParsed, JsonResponse]>();
get<T = D>(path: string, params?: P, reqInit: RequestInit = {}) { get<T = D>(path: string, params?: P, reqInit: RequestInit | NodeRequestInit = {}) {
return this.request<T>(path, params, { ...reqInit, method: "get" }); return this.request<T>(path, params, { ...reqInit, method: "get" });
} }
getResponse(path: string, params?: P, init: RequestInit = {}): Promise<Response> { getResponse(path: string, params?: P, init: JsonRequestInit = {}): Promise<JsonResponse> {
let reqUrl = `${this.config.serverAddress}${this.config.apiBase}${path}`; let reqUrl = this.buildRequestUrl(path, true);
const reqInit: RequestInit = merge({}, this.reqInit, init); const reqInit = merge({}, this.reqInit, init);
const { query } = params || {} as P; const { query } = params || {} as P;
if (!reqInit.method) { if (!reqInit.method) {
@ -93,28 +97,42 @@ export class JsonApi<D = JsonApiData, P extends JsonApiParams = JsonApiParams> {
reqUrl += (reqUrl.includes("?") ? "&" : "?") + queryString; reqUrl += (reqUrl.includes("?") ? "&" : "?") + queryString;
} }
return fetch(reqUrl, reqInit); if (window) {
return fetch(reqUrl, reqInit as RequestInit);
} else {
return nodeFetch(reqUrl, reqInit as NodeRequestInit);
}
} }
post<T = D>(path: string, params?: P, reqInit: RequestInit = {}) { post<T = D>(path: string, params?: P, reqInit: JsonRequestInit = {}) {
return this.request<T>(path, params, { ...reqInit, method: "post" }); return this.request<T>(path, params, { ...reqInit, method: "post" });
} }
put<T = D>(path: string, params?: P, reqInit: RequestInit = {}) { put<T = D>(path: string, params?: P, reqInit: JsonRequestInit = {}) {
return this.request<T>(path, params, { ...reqInit, method: "put" }); return this.request<T>(path, params, { ...reqInit, method: "put" });
} }
patch<T = D>(path: string, params?: P, reqInit: RequestInit = {}) { patch<T = D>(path: string, params?: P, reqInit: JsonRequestInit = {}) {
return this.request<T>(path, params, { ...reqInit, method: "PATCH" }); return this.request<T>(path, params, { ...reqInit, method: "PATCH" });
} }
del<T = D>(path: string, params?: P, reqInit: RequestInit = {}) { del<T = D>(path: string, params?: P, reqInit: JsonRequestInit = {}) {
return this.request<T>(path, params, { ...reqInit, method: "delete" }); return this.request<T>(path, params, { ...reqInit, method: "delete" });
} }
protected async request<D>(path: string, params?: P, init: RequestInit = {}) { protected buildRequestUrl(path: string, useSubdomain = false) {
let reqUrl = `${this.config.serverAddress}${this.config.apiBase}${path}`; if (window) {
const reqInit: RequestInit = merge({}, this.reqInit, init); const subdomain = useSubdomain ? `${randomBytes(2).toString("hex")}.` : "";
return `http://${subdomain}${window.location.host}${this.config.apiBase}${path}`;
} else {
return `${this.config.serverAddress}${this.config.apiBase}${path}`;
}
}
protected async request<D>(path: string, params?: P, init: JsonRequestInit = {}) {
let reqUrl = this.buildRequestUrl(path);
const reqInit = merge({}, this.reqInit, init);
const { data, query } = params || {} as P; const { data, query } = params || {} as P;
if (data && !reqInit.body) { if (data && !reqInit.body) {
@ -132,12 +150,20 @@ export class JsonApi<D = JsonApiData, P extends JsonApiParams = JsonApiParams> {
reqInit, reqInit,
}; };
const res = await fetch(reqUrl, reqInit); const res = await this.fetch(reqUrl, reqInit);
return this.parseResponse<D>(res, infoLog); return this.parseResponse<D>(res, infoLog);
} }
protected async parseResponse<D>(res: Response, log: JsonApiLog): Promise<D> { protected fetch(reqUrl: string, reqInit: JsonRequestInit) {
if (window) {
return fetch(reqUrl, reqInit as RequestInit);
} else {
return nodeFetch(reqUrl, reqInit as NodeRequestInit);
}
}
protected async parseResponse<D>(res: JsonResponse, log: JsonApiLog): Promise<D> {
const { status } = res; const { status } = res;
const text = await res.text(); const text = await res.text();
@ -169,7 +195,7 @@ export class JsonApi<D = JsonApiData, P extends JsonApiParams = JsonApiParams> {
throw error; throw error;
} }
protected parseError(error: JsonApiError | string, res: Response): string[] { protected parseError(error: JsonApiError | string, res: JsonResponse): string[] {
if (typeof error === "string") { if (typeof error === "string") {
return [error]; return [error];
} }

View File

@ -36,6 +36,7 @@ import { noop } from "../utils";
import type { RequestInit } from "node-fetch"; import type { RequestInit } from "node-fetch";
import AbortController from "abort-controller"; import AbortController from "abort-controller";
import { Agent, AgentOptions } from "https"; import { Agent, AgentOptions } from "https";
import { ReadableWebToNodeStream } from "readable-web-to-node-stream";
export interface IKubeApiOptions<T extends KubeObject> { export interface IKubeApiOptions<T extends KubeObject> {
/** /**
@ -117,7 +118,7 @@ export interface IRemoteKubeApiConfig {
export function forCluster<T extends KubeObject>(cluster: ILocalKubeApiConfig, kubeClass: KubeObjectConstructor<T>): KubeApi<T> { export function forCluster<T extends KubeObject>(cluster: ILocalKubeApiConfig, kubeClass: KubeObjectConstructor<T>): KubeApi<T> {
const url = new URL(apiBase.config.serverAddress); const url = new URL(apiBase.config.serverAddress);
const request = new KubeJsonApi({ const request = new KubeJsonApi({
serverAddress: apiBase.config.serverAddress, serverAddress: window ? `${cluster.metadata.uid}.localhost:${url.port}` : apiBase.config.serverAddress,
apiBase: apiKubePrefix, apiBase: apiKubePrefix,
debug: isDevelopment, debug: isDevelopment,
}, { }, {
@ -507,21 +508,24 @@ export class KubeApi<T extends KubeObject> {
return callback(null, response); return callback(null, response);
} }
const body = window ? new ReadableWebToNodeStream(response.body as ReadableStream): response.body as NodeJS.ReadableStream;
["end", "close", "error"].forEach((eventName) => { ["end", "close", "error"].forEach((eventName) => {
response.body.on(eventName, () => { body.on(eventName, () => {
clearTimeout(timedRetry);
if (errorReceived) return; // kubernetes errors should be handled in a callback if (errorReceived) return; // kubernetes errors should be handled in a callback
if (signal.aborted) return; if (signal.aborted) return;
logger.info(`[KUBE-API] watch (${watchId}) ${eventName} ${watchUrl}`); logger.info(`[KUBE-API] watch (${watchId}) ${eventName} ${watchUrl}`);
clearTimeout(timedRetry);
timedRetry = setTimeout(() => { // we did not get any kubernetes errors so let's retry timedRetry = setTimeout(() => { // we did not get any kubernetes errors so let's retry
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(body).on("data", (line) => {
try { try {
const event: IKubeWatchEvent<KubeJsonApiData> = JSON.parse(line); const event: IKubeWatchEvent<KubeJsonApiData> = JSON.parse(line);

View File

@ -348,7 +348,7 @@ export abstract class KubeObjectStore<T extends KubeObject> extends ItemStore<T>
const { signal } = abortController; const { signal } = abortController;
const callback = (data: IKubeWatchEvent<T>, error: any) => { const callback = (data: IKubeWatchEvent<T>, error: any) => {
if (!this.isLoaded || error?.type === "aborted") return; if (!this.isLoaded || error instanceof DOMException || error?.type === "aborted") return;
if (error instanceof Response) { if (error instanceof Response) {
if (error.status === 404 || error.status === 401) { if (error.status === 404 || error.status === 401) {