diff --git a/package-lock.json b/package-lock.json index 555cd6a28c..94bac37414 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4008,6 +4008,10 @@ "resolved": "packages/infrastructure/jest", "link": true }, + "node_modules/@k8slens/json-api": { + "resolved": "packages/utility-features/json-api", + "link": true + }, "node_modules/@k8slens/keyboard-shortcuts": { "resolved": "packages/business-features/keyboard-shortcuts", "link": true @@ -38315,6 +38319,28 @@ "@k8slens/webpack": "6.5.0-alpha.4" } }, + "packages/utility-features/json-api": { + "name": "@k8slens/json-api", + "version": "1.0.0-alpha.1", + "license": "MIT", + "dependencies": { + "@k8slens/node-fetch": "^6.5.0-alpha.3" + }, + "devDependencies": { + "@k8slens/eslint-config": "^6.5.0-alpha.2", + "@k8slens/jest": "^6.5.0-alpha.4", + "@k8slens/typescript": "^6.5.0-alpha.2", + "@types/node": "^16.18.25", + "type-fest": "^2.14.0" + }, + "peerDependencies": { + "@k8slens/event-emitter": "^1.0.0-alpha.1", + "@k8slens/logger": "^1.0.0-alpha.5", + "@k8slens/utilities": "^1.0.0-alpha.1", + "lodash": "^4.17.15", + "rfc6902": "^5.0.1" + } + }, "packages/utility-features/react-testing-library-discovery": { "name": "@k8slens/react-testing-library-discovery", "version": "1.0.0-alpha.3", @@ -41913,6 +41939,17 @@ } } }, + "@k8slens/json-api": { + "version": "file:packages/utility-features/json-api", + "requires": { + "@k8slens/eslint-config": "^6.5.0-alpha.2", + "@k8slens/jest": "^6.5.0-alpha.4", + "@k8slens/node-fetch": "^6.5.0-alpha.3", + "@k8slens/typescript": "^6.5.0-alpha.2", + "@types/node": "^16.18.25", + "type-fest": "^2.14.0" + } + }, "@k8slens/keyboard-shortcuts": { "version": "file:packages/business-features/keyboard-shortcuts", "requires": { diff --git a/packages/core/src/common/fetch/fetch.injectable.ts b/packages/core/src/common/fetch/fetch.injectable.ts index 82dd141964..6a323b1bc5 100644 --- a/packages/core/src/common/fetch/fetch.injectable.ts +++ b/packages/core/src/common/fetch/fetch.injectable.ts @@ -3,14 +3,13 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import type { RequestInit, Response } from "@k8slens/node-fetch"; import fetch from "@k8slens/node-fetch"; -export type Fetch = (url: string, init?: RequestInit) => Promise; +export type Fetch = typeof fetch; const fetchInjectable = getInjectable({ id: "fetch", - instantiate: () => fetch as Fetch, + instantiate: () => fetch, causesSideEffects: true, }); diff --git a/packages/core/src/common/k8s-api/create-json-api.injectable.ts b/packages/core/src/common/k8s-api/create-json-api.injectable.ts index 87215d13c8..b8a67a424d 100644 --- a/packages/core/src/common/k8s-api/create-json-api.injectable.ts +++ b/packages/core/src/common/k8s-api/create-json-api.injectable.ts @@ -8,8 +8,8 @@ import type { RequestInit } from "@k8slens/node-fetch"; import lensProxyCertificateInjectable from "../certificate/lens-proxy-certificate.injectable"; import fetchInjectable from "../fetch/fetch.injectable"; import { loggerInjectionToken } from "@k8slens/logger"; -import type { JsonApiConfig, JsonApiData, JsonApiDependencies, JsonApiParams } from "./json-api"; -import { JsonApi } from "./json-api"; +import type { JsonApiConfig, JsonApiData, JsonApiDependencies, JsonApiParams } from "@k8slens/json-api"; +import { JsonApi } from "@k8slens/json-api"; export type CreateJsonApi = = JsonApiParams>(config: JsonApiConfig, reqInit?: RequestInit) => JsonApi; diff --git a/packages/core/src/common/k8s-api/create-kube-json-api.injectable.ts b/packages/core/src/common/k8s-api/create-kube-json-api.injectable.ts index df0659634a..f75096990f 100644 --- a/packages/core/src/common/k8s-api/create-kube-json-api.injectable.ts +++ b/packages/core/src/common/k8s-api/create-kube-json-api.injectable.ts @@ -8,7 +8,7 @@ import type { RequestInit } from "@k8slens/node-fetch"; import lensProxyCertificateInjectable from "../certificate/lens-proxy-certificate.injectable"; import fetchInjectable from "../fetch/fetch.injectable"; import { loggerInjectionToken } from "@k8slens/logger"; -import type { JsonApiConfig, JsonApiDependencies } from "./json-api"; +import type { JsonApiConfig, JsonApiDependencies } from "@k8slens/json-api"; import { KubeJsonApi } from "./kube-json-api"; export type CreateKubeJsonApi = (config: JsonApiConfig, reqInit?: RequestInit) => KubeJsonApi; diff --git a/packages/core/src/common/k8s-api/kube-json-api.ts b/packages/core/src/common/k8s-api/kube-json-api.ts index 5b8188451e..5cecf113a7 100644 --- a/packages/core/src/common/k8s-api/kube-json-api.ts +++ b/packages/core/src/common/k8s-api/kube-json-api.ts @@ -3,8 +3,8 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import type { JsonApiError } from "./json-api"; -import { JsonApi } from "./json-api"; +import type { JsonApiError } from "@k8slens/json-api"; +import { JsonApi } from "@k8slens/json-api"; import type { Response } from "@k8slens/node-fetch"; import type { KubeJsonApiData } from "@k8slens/kube-object"; diff --git a/packages/core/src/common/utils/get-error-message.ts b/packages/core/src/common/utils/get-error-message.ts index 84e6c8083a..305d5a9ce9 100644 --- a/packages/core/src/common/utils/get-error-message.ts +++ b/packages/core/src/common/utils/get-error-message.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import { JsonApiErrorParsed } from "../k8s-api/json-api"; +import { JsonApiErrorParsed } from "@k8slens/json-api"; export const getErrorMessage = (error: unknown): string => { if (typeof error === "string") { diff --git a/packages/core/src/extensions/common-api/k8s-api.ts b/packages/core/src/extensions/common-api/k8s-api.ts index 6c34703b54..e9bed8de24 100644 --- a/packages/core/src/extensions/common-api/k8s-api.ts +++ b/packages/core/src/extensions/common-api/k8s-api.ts @@ -25,7 +25,7 @@ import { loggerInjectionToken } from "@k8slens/logger"; import maybeKubeApiInjectable from "../../common/k8s-api/maybe-kube-api.injectable"; import { DeploymentApi as InternalDeploymentApi, IngressApi as InternalIngressApi, NodeApi, PersistentVolumeClaimApi, PodApi } from "../../common/k8s-api/endpoints"; import { storesAndApisCanBeCreatedInjectionToken } from "../../common/k8s-api/stores-apis-can-be-created.token"; -import type { JsonApiConfig } from "../../common/k8s-api/json-api"; +import type { JsonApiConfig } from "@k8slens/json-api"; import type { KubeJsonApi as InternalKubeJsonApi } from "../../common/k8s-api/kube-json-api"; import createKubeJsonApiInjectable from "../../common/k8s-api/create-kube-json-api.injectable"; import type { RequestInit } from "@k8slens/node-fetch"; diff --git a/packages/core/src/features/cluster/namespaces/edit-namespace-from-new-tab.test.tsx b/packages/core/src/features/cluster/namespaces/edit-namespace-from-new-tab.test.tsx index 5fde13873c..6d0ed13b70 100644 --- a/packages/core/src/features/cluster/namespaces/edit-namespace-from-new-tab.test.tsx +++ b/packages/core/src/features/cluster/namespaces/edit-namespace-from-new-tab.test.tsx @@ -24,7 +24,7 @@ import type { ApiKubeGet } from "../../../renderer/k8s/api-kube-get.injectable"; import apiKubePatchInjectable from "../../../renderer/k8s/api-kube-patch.injectable"; import apiKubeGetInjectable from "../../../renderer/k8s/api-kube-get.injectable"; import type { BaseKubeJsonApiObjectMetadata, KubeObjectScope, KubeJsonApiData } from "@k8slens/kube-object"; -import { JsonApiErrorParsed } from "../../../common/k8s-api/json-api"; +import { JsonApiErrorParsed } from "@k8slens/json-api"; import type { ShowNotification } from "../../../renderer/components/notifications"; import React from "react"; diff --git a/packages/core/src/renderer/components/notifications/notifications.store.ts b/packages/core/src/renderer/components/notifications/notifications.store.ts index 97310c03f6..d4e16dd0d2 100644 --- a/packages/core/src/renderer/components/notifications/notifications.store.ts +++ b/packages/core/src/renderer/components/notifications/notifications.store.ts @@ -6,7 +6,7 @@ import type React from "react"; import { action, observable, makeObservable } from "mobx"; import uniqueId from "lodash/uniqueId"; -import type { JsonApiErrorParsed } from "../../../common/k8s-api/json-api"; +import type { JsonApiErrorParsed } from "@k8slens/json-api"; import type { SetRequired } from "type-fest"; import autoBind from "auto-bind"; diff --git a/packages/core/src/renderer/components/notifications/notifications.tsx b/packages/core/src/renderer/components/notifications/notifications.tsx index 7724be4dd7..64874dbb50 100644 --- a/packages/core/src/renderer/components/notifications/notifications.tsx +++ b/packages/core/src/renderer/components/notifications/notifications.tsx @@ -8,7 +8,7 @@ import "./notifications.scss"; import React from "react"; import { reaction } from "mobx"; import { disposeOnUnmount, observer } from "mobx-react"; -import { JsonApiErrorParsed } from "../../../common/k8s-api/json-api"; +import { JsonApiErrorParsed } from "@k8slens/json-api"; import type { Disposer } from "@k8slens/utilities"; import { cssNames, prevDefault } from "@k8slens/utilities"; import type { CreateNotificationOptions, Notification, NotificationMessage, NotificationsStore } from "./notifications.store"; diff --git a/packages/core/src/renderer/components/notifications/show-checked-error.injectable.ts b/packages/core/src/renderer/components/notifications/show-checked-error.injectable.ts index 4f8f613c5c..afc30c4e4e 100644 --- a/packages/core/src/renderer/components/notifications/show-checked-error.injectable.ts +++ b/packages/core/src/renderer/components/notifications/show-checked-error.injectable.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import { JsonApiErrorParsed } from "../../../common/k8s-api/json-api"; +import { JsonApiErrorParsed } from "@k8slens/json-api"; import { loggerInjectionToken } from "@k8slens/logger"; import type { Disposer } from "@k8slens/utilities"; import type { CreateNotificationOptions } from "./notifications.store"; diff --git a/packages/core/src/renderer/port-forward/port-forward-store/port-forward-store.ts b/packages/core/src/renderer/port-forward/port-forward-store/port-forward-store.ts index 500b3fdba3..431c92dd1a 100644 --- a/packages/core/src/renderer/port-forward/port-forward-store/port-forward-store.ts +++ b/packages/core/src/renderer/port-forward/port-forward-store/port-forward-store.ts @@ -11,7 +11,7 @@ import type { ForwardedPort } from "../port-forward-item"; import { PortForwardItem } from "../port-forward-item"; import { waitUntilFree } from "tcp-port-used"; import type { Logger } from "@k8slens/logger"; -import type { JsonApi } from "../../../common/k8s-api/json-api"; +import type { JsonApi } from "@k8slens/json-api"; import type { RequestActivePortForward } from "./request-active-port-forward.injectable"; import autoBind from "auto-bind"; diff --git a/packages/utility-features/json-api/.eslintrc.js b/packages/utility-features/json-api/.eslintrc.js new file mode 100644 index 0000000000..19a14e85a4 --- /dev/null +++ b/packages/utility-features/json-api/.eslintrc.js @@ -0,0 +1,6 @@ +module.exports = { + extends: "@k8slens/eslint-config/eslint", + parserOptions: { + project: "./tsconfig.json", + }, +}; \ No newline at end of file diff --git a/packages/utility-features/json-api/.prettierrc b/packages/utility-features/json-api/.prettierrc new file mode 100644 index 0000000000..edd47b479e --- /dev/null +++ b/packages/utility-features/json-api/.prettierrc @@ -0,0 +1 @@ +"@k8slens/eslint-config/prettier" diff --git a/packages/utility-features/json-api/index.ts b/packages/utility-features/json-api/index.ts new file mode 100644 index 0000000000..26a6c167c3 --- /dev/null +++ b/packages/utility-features/json-api/index.ts @@ -0,0 +1 @@ +export * from "./src/json-api"; diff --git a/packages/utility-features/json-api/jest.config.js b/packages/utility-features/json-api/jest.config.js new file mode 100644 index 0000000000..c6074967eb --- /dev/null +++ b/packages/utility-features/json-api/jest.config.js @@ -0,0 +1 @@ +module.exports = require("@k8slens/jest").monorepoPackageConfig(__dirname).configForNode; diff --git a/packages/utility-features/json-api/package.json b/packages/utility-features/json-api/package.json new file mode 100644 index 0000000000..272f77d27d --- /dev/null +++ b/packages/utility-features/json-api/package.json @@ -0,0 +1,51 @@ +{ + "name": "@k8slens/json-api", + "private": false, + "version": "1.0.0-alpha.1", + "description": "JSON api client", + "type": "commonjs", + "publishConfig": { + "access": "public", + "registry": "https://registry.npmjs.org/" + }, + "files": [ + "dist" + ], + "repository": { + "type": "git", + "url": "git+https://github.com/lensapp/lens.git" + }, + "main": "dist/index.js", + "types": "dist/index.d.ts", + "author": { + "name": "OpenLens Authors", + "email": "info@k8slens.dev" + }, + "license": "MIT", + "homepage": "https://github.com/lensapp/lens", + "scripts": { + "build": "webpack", + "clean": "rimraf dist/", + "dev": "webpack --mode=development --watch", + "test": "jest --coverage --runInBand", + "lint": "lens-lint", + "lint:fix": "lens-lint --fix" + }, + "dependencies": { + "@k8slens/node-fetch": "^6.5.0-alpha.3" + }, + "peerDependencies": { + "@k8slens/event-emitter": "^1.0.0-alpha.1", + "@k8slens/logger": "^1.0.0-alpha.5", + "@k8slens/utilities": "^1.0.0-alpha.1", + "lodash": "^4.17.15", + "rfc6902": "^5.0.1" + }, + "devDependencies": { + "@k8slens/eslint-config": "^6.5.0-alpha.2", + "@k8slens/jest": "^6.5.0-alpha.4", + "@k8slens/typescript": "^6.5.0-alpha.2", + "@types/node": "^16.18.25", + "type-fest": "^2.14.0" + } +} diff --git a/packages/core/src/common/k8s-api/json-api.ts b/packages/utility-features/json-api/src/json-api.ts similarity index 84% rename from packages/core/src/common/k8s-api/json-api.ts rename to packages/utility-features/json-api/src/json-api.ts index 3b08a345ad..0765ec870a 100644 --- a/packages/core/src/common/k8s-api/json-api.ts +++ b/packages/utility-features/json-api/src/json-api.ts @@ -3,18 +3,16 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -// Base http-service / json-api class - import { Agent as HttpAgent } from "http"; import { Agent as HttpsAgent } from "https"; import { merge } from "lodash"; -import type { Response, RequestInit } from "@k8slens/node-fetch"; import { stringify } from "querystring"; import type { Patch } from "rfc6902"; import type { PartialDeep, ValueOf } from "type-fest"; import { EventEmitter } from "@k8slens/event-emitter"; import type { Logger } from "@k8slens/logger"; -import type { Fetch } from "../fetch/fetch.injectable"; +import type Fetch from "@k8slens/node-fetch"; +import type { RequestInit, Response } from "@k8slens/node-fetch"; import type { Defaulted } from "@k8slens/utilities"; import { isObject, isString, json } from "@k8slens/utilities"; @@ -74,33 +72,58 @@ export interface JsonApiConfig { const httpAgent = new HttpAgent({ keepAlive: true }); const httpsAgent = new HttpsAgent({ keepAlive: true }); -export type QueryParam = string | number | boolean | null | undefined | readonly string[] | readonly number[] | readonly boolean[]; +export type QueryParam = + | string + | number + | boolean + | null + | undefined + | readonly string[] + | readonly number[] + | readonly boolean[]; export type QueryParams = Partial>; -export type ParamsAndQuery = ( - ValueOf extends QueryParam - ? Params & { query?: Query } - : Params & { query?: undefined } -); +export type ParamsAndQuery = ValueOf extends QueryParam + ? Params & { query?: Query } + : Params & { query?: undefined }; export interface JsonApiDependencies { - fetch: Fetch; + fetch: typeof Fetch; readonly logger: Logger; } +export class JsonApiErrorParsed { + isUsedForNotification = false; + + constructor(private error: JsonApiError | DOMException | KubeJsonApiError, private messages: string[]) {} + + get isAborted() { + return this.error.code === DOMException.ABORT_ERR; + } + + toString() { + return this.messages.join("\n"); + } +} + export class JsonApi = JsonApiParams> { static readonly reqInitDefault = { headers: { "content-type": "application/json", }, }; - protected readonly reqInit: Defaulted; + + protected readonly reqInit: Defaulted; static readonly configDefault: Partial = { debug: false, }; - constructor(protected readonly dependencies: JsonApiDependencies, public readonly config: JsonApiConfig, reqInit?: RequestInit) { + constructor( + protected readonly dependencies: JsonApiDependencies, + public readonly config: JsonApiConfig, + reqInit?: RequestInit, + ) { this.config = Object.assign({}, JsonApi.configDefault, config); this.reqInit = merge({}, JsonApi.reqInitDefault, reqInit); this.parseResponse = this.parseResponse.bind(this); @@ -108,7 +131,9 @@ export class JsonApi = Js } public readonly onData = new EventEmitter<[Data, Response]>(); + public readonly onError = new EventEmitter<[JsonApiErrorParsed, Response]>(); + private readonly getRequestOptions: GetRequestOptions; async getResponse( @@ -163,7 +188,7 @@ export class JsonApi = Js patch( path: string, - params?: (ParamsAndQuery, Query> & { data?: Patch | PartialDeep }), + params?: ParamsAndQuery, Query> & { data?: Patch | PartialDeep }, reqInit: RequestInit = {}, ) { return this.request(path, params, { ...reqInit, method: "patch" }); @@ -183,12 +208,7 @@ export class JsonApi = Js init: Defaulted, ) { let reqUrl = `${this.config.serverAddress}${this.config.apiBase}${path}`; - const reqInit = merge( - {}, - this.reqInit, - await this.getRequestOptions(), - init, - ); + const reqInit = merge({}, this.reqInit, await this.getRequestOptions(), init); const { data, query } = params || {}; if (data && !reqInit.body) { @@ -208,7 +228,7 @@ export class JsonApi = Js const res = await this.dependencies.fetch(reqUrl, reqInit); - return await this.parseResponse(res, infoLog) as OutData; + return (await this.parseResponse(res, infoLog)) as OutData; } protected async parseResponse(res: Response, log: JsonApiLog): Promise { @@ -216,9 +236,7 @@ export class JsonApi = Js const text = await res.text(); const parseResponse = json.parse(text || "{}"); - const data = parseResponse.callWasSuccessful - ? parseResponse.response as Data - : text as Data; + const data = parseResponse.callWasSuccessful ? (parseResponse.response as Data) : (text as Data); if (status >= 200 && status < 300) { this.onData.emit(data, res); @@ -229,6 +247,7 @@ export class JsonApi = Js if (log.method === "GET" && res.status === 403) { this.writeLog({ ...log, error: data }); + // eslint-disable-next-line @typescript-eslint/no-throw-literal throw data; } @@ -237,6 +256,7 @@ export class JsonApi = Js this.onError.emit(error, res); this.writeLog({ ...log, error }); + // eslint-disable-next-line @typescript-eslint/no-throw-literal throw error; } @@ -252,7 +272,7 @@ export class JsonApi = Js const { errors, message } = error as { errors?: { title: string }[]; message?: string }; if (Array.isArray(errors)) { - return errors.map(error => error.title); + return errors.map((error) => error.title); } if (isString(message)) { @@ -268,18 +288,3 @@ export class JsonApi = Js this.dependencies.logger.debug(`[JSON-API] request ${method} ${reqUrl}`, params); } } - -export class JsonApiErrorParsed { - isUsedForNotification = false; - - constructor(private error: JsonApiError | DOMException | KubeJsonApiError, private messages: string[]) { - } - - get isAborted() { - return this.error.code === DOMException.ABORT_ERR; - } - - toString() { - return this.messages.join("\n"); - } -} diff --git a/packages/utility-features/json-api/tsconfig.json b/packages/utility-features/json-api/tsconfig.json new file mode 100644 index 0000000000..d140381aa7 --- /dev/null +++ b/packages/utility-features/json-api/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "@k8slens/typescript/config/base.json", + "include": ["**/*.ts"], + "compilerOptions": { + "moduleResolution": "node" + } +} diff --git a/packages/utility-features/json-api/webpack.config.js b/packages/utility-features/json-api/webpack.config.js new file mode 100644 index 0000000000..c1089b1b44 --- /dev/null +++ b/packages/utility-features/json-api/webpack.config.js @@ -0,0 +1 @@ +module.exports = require("@k8slens/webpack").configForNode; \ No newline at end of file