From 5decadcc70b6b4c80db175ca7ea96d8b351962b9 Mon Sep 17 00:00:00 2001 From: David_LY Date: Fri, 19 Jan 2024 16:54:26 +0100 Subject: [PATCH 1/2] network package updated: axios is replaced by ky --- .../private/test-utils/src/__mocks__/ky.ts | 23 ++++++++ .../typescript-config/tsconfig.react.json | 3 +- packages/public/network/package.json | 20 ++++--- packages/public/network/src/api/config.ts | 10 +++- packages/public/network/src/api/react.ts | 8 ++- .../src/api/requests/inspections/requests.ts | 17 +++--- .../public/network/src/api/requests/types.ts | 9 ++- .../public/network/test/api/config.test.ts | 20 +++---- .../api/requests/inspections/requests.test.ts | 56 +++++++++++++------ yarn.lock | 26 ++++----- 10 files changed, 126 insertions(+), 66 deletions(-) create mode 100644 packages/private/test-utils/src/__mocks__/ky.ts diff --git a/packages/private/test-utils/src/__mocks__/ky.ts b/packages/private/test-utils/src/__mocks__/ky.ts new file mode 100644 index 000000000..7913868b9 --- /dev/null +++ b/packages/private/test-utils/src/__mocks__/ky.ts @@ -0,0 +1,23 @@ +const i18nextInstanceMock: any = { + language: 'en', + changeLanguage: jest.fn(() => Promise.resolve(undefined)), + on: jest.fn(), + use: jest.fn(() => i18nextInstanceMock), + init: jest.fn(() => Promise.resolve(undefined)), +}; + +function createMockKyRequestFn() { + return jest.fn(() => + Promise.resolve({ + status: 200, + json: jest.fn(() => Promise.resolve({})), + }), + ); +} + +export = { + /* Actual exports */ + + /* Mocks */ + get: createMockKyRequestFn(), +}; diff --git a/packages/private/typescript-config/tsconfig.react.json b/packages/private/typescript-config/tsconfig.react.json index 9012430c3..9b9f53e03 100644 --- a/packages/private/typescript-config/tsconfig.react.json +++ b/packages/private/typescript-config/tsconfig.react.json @@ -5,7 +5,8 @@ "lib": [ "dom", "dom.iterable", - "esnext" + "esnext", + "es2015" ], "isolatedModules": true, "jsx": "react-jsx", diff --git a/packages/public/network/package.json b/packages/public/network/package.json index 8cf099689..c807bb7af 100644 --- a/packages/public/network/package.json +++ b/packages/public/network/package.json @@ -5,8 +5,8 @@ "packageManager": "yarn@3.2.4", "description": "MonkJs core package used to communicate with the API", "author": "monkvision", - "main": "lib/index.js", - "types": "lib/index.d.ts", + "main": "lib/src/index.js", + "types": "lib/src/index.d.ts", "files": [ "package.json", "README.md", @@ -24,6 +24,15 @@ "lint": "yarn run prettier && yarn run eslint", "lint:fix": "yarn run prettier:fix && yarn run eslint:fix" }, + "dependencies": { + "jsonwebtoken": "^9.0.2", + "jwt-decode": "^4.0.0", + "ky": "^1.2.0" + }, + "peerDependencies": { + "react": "^17.0.2", + "react-dom": "^17.0.2" + }, "devDependencies": { "@monkvision/common": "4.0.0", "@monkvision/eslint-config-base": "4.0.0", @@ -66,10 +75,5 @@ "bugs": { "url": "https://github.com/monkvision/monkjs/issues" }, - "homepage": "https://github.com/monkvision/monkjs", - "dependencies": { - "axios": "^1.6.2", - "jsonwebtoken": "^9.0.2", - "jwt-decode": "^4.0.0" - } + "homepage": "https://github.com/monkvision/monkjs" } diff --git a/packages/public/network/src/api/config.ts b/packages/public/network/src/api/config.ts index bfb41a008..8802904a7 100644 --- a/packages/public/network/src/api/config.ts +++ b/packages/public/network/src/api/config.ts @@ -1,4 +1,3 @@ -import { AxiosRequestConfig } from 'axios'; import packageJson from '../../package.json'; export const sdkVersion = packageJson.version; @@ -17,7 +16,12 @@ export interface MonkAPIConfig { authToken: string; } -export function getBaseAxiosConfig(config: MonkAPIConfig): AxiosRequestConfig { +export interface KyConfig { + baseUrl: string; + headers: Record; +} + +export function getKyConfig(config: MonkAPIConfig): KyConfig { const apiDomain = config.apiDomain.endsWith('/') ? config.apiDomain.substring(0, config.apiDomain.length - 1) : config.apiDomain; @@ -25,7 +29,7 @@ export function getBaseAxiosConfig(config: MonkAPIConfig): AxiosRequestConfig { ? config.authToken : `Bearer ${config.authToken}`; return { - baseURL: `https://${apiDomain}`, + baseUrl: `https://${apiDomain}`, headers: { 'Access-Control-Allow-Origin': '*', 'Authorization': authorizationHeader, diff --git a/packages/public/network/src/api/react.ts b/packages/public/network/src/api/react.ts index 741384523..c1426eda2 100644 --- a/packages/public/network/src/api/react.ts +++ b/packages/public/network/src/api/react.ts @@ -1,6 +1,10 @@ -import { useMonkState } from '@monkvision/common'; +import { + useMonkState, + MonkAction, + MonkActionType, + MonkUpdateStateAction, +} from '@monkvision/common'; import { Dispatch } from 'react'; -import { MonkAction, MonkActionType, MonkUpdateStateAction } from '@monkvision/common/src'; import { MonkAPIConfig } from './config'; import { MonkAPIRequest, MonkAPIResponse } from './requests/types'; import { MonkApi } from './requests'; diff --git a/packages/public/network/src/api/requests/inspections/requests.ts b/packages/public/network/src/api/requests/inspections/requests.ts index 824110c44..32b522608 100644 --- a/packages/public/network/src/api/requests/inspections/requests.ts +++ b/packages/public/network/src/api/requests/inspections/requests.ts @@ -1,5 +1,5 @@ -import axios from 'axios'; -import { getBaseAxiosConfig, MonkAPIConfig } from '../../config'; +import ky from 'ky'; +import { getKyConfig, MonkAPIConfig } from '../../config'; import { MonkAPIRequest } from '../types'; import { ApiInspectionGet } from '../../apiModels'; import { mapGetInspectionResponse } from './mappers'; @@ -11,15 +11,16 @@ export const getInspection: MonkAPIRequest<[id: string], ApiInspectionGet> = asy id: string, config: MonkAPIConfig, ) => { - const axiosResponse = await axios.request({ - ...getBaseAxiosConfig(config), - method: 'get', - url: `/inspections/${id}`, + const { baseUrl, headers } = getKyConfig(config); + const response = await ky.get(`${baseUrl}/inspections/${id}`, { + headers, }); + const body = await response.json(); return { payload: { - entities: mapGetInspectionResponse(axiosResponse.data), + entities: mapGetInspectionResponse(body), }, - axiosResponse, + response, + body, }; }; diff --git a/packages/public/network/src/api/requests/types.ts b/packages/public/network/src/api/requests/types.ts index 75e4a6771..6f9d06e04 100644 --- a/packages/public/network/src/api/requests/types.ts +++ b/packages/public/network/src/api/requests/types.ts @@ -1,4 +1,3 @@ -import { AxiosResponse } from 'axios'; import { MonkUpdateStatePayload } from '@monkvision/common'; import { MonkAPIConfig } from '../config'; @@ -11,9 +10,13 @@ export interface MonkAPIResponse { */ payload: MonkUpdateStatePayload; /** - * The raw response object obtained from Axios when making the request. + * The raw response object obtained from the fetch method when making the request. */ - axiosResponse: AxiosResponse; + response: Response; + /** + * The body of the response. + */ + body: T; } /** diff --git a/packages/public/network/test/api/config.test.ts b/packages/public/network/test/api/config.test.ts index f8325b5b5..aa8e20462 100644 --- a/packages/public/network/test/api/config.test.ts +++ b/packages/public/network/test/api/config.test.ts @@ -1,5 +1,5 @@ import packageJson from '../../package.json'; -import { getBaseAxiosConfig, MonkAPIConfig, sdkVersion } from '../../src/api/config'; +import { getKyConfig, MonkAPIConfig, sdkVersion } from '../../src/api/config'; describe('Network package API global config utils', () => { describe('sdkVersion global constant', () => { @@ -8,30 +8,30 @@ describe('Network package API global config utils', () => { }); }); - describe('getBaseAxiosConfig function', () => { + describe('getKyConfig function', () => { const baseConfig: MonkAPIConfig = { apiDomain: 'testapidomain', authToken: 'Bearer testtoken', }; it('should set the baseURL property', () => { - expect(getBaseAxiosConfig(baseConfig)).toEqual( + expect(getKyConfig(baseConfig)).toEqual( expect.objectContaining({ - baseURL: `https://${baseConfig.apiDomain}`, + baseUrl: `https://${baseConfig.apiDomain}`, }), ); }); it('should remove the ending slash from the baseURL property', () => { - expect(getBaseAxiosConfig({ ...baseConfig, apiDomain: `${baseConfig.apiDomain}/` })).toEqual( + expect(getKyConfig({ ...baseConfig, apiDomain: `${baseConfig.apiDomain}/` })).toEqual( expect.objectContaining({ - baseURL: `https://${baseConfig.apiDomain}`, + baseUrl: `https://${baseConfig.apiDomain}`, }), ); }); it('should set the Access-Control-Allow-Origin header', () => { - expect(getBaseAxiosConfig(baseConfig)).toEqual( + expect(getKyConfig(baseConfig)).toEqual( expect.objectContaining({ headers: expect.objectContaining({ 'Access-Control-Allow-Origin': '*', @@ -41,7 +41,7 @@ describe('Network package API global config utils', () => { }); it('should set the Authorization header', () => { - expect(getBaseAxiosConfig(baseConfig)).toEqual( + expect(getKyConfig(baseConfig)).toEqual( expect.objectContaining({ headers: expect.objectContaining({ Authorization: baseConfig.authToken, @@ -52,7 +52,7 @@ describe('Network package API global config utils', () => { it('should add the "Bearer " prefix to the token if it is missing', () => { const authToken = 'testtokentest'; - expect(getBaseAxiosConfig({ ...baseConfig, authToken })).toEqual( + expect(getKyConfig({ ...baseConfig, authToken })).toEqual( expect.objectContaining({ headers: expect.objectContaining({ Authorization: `Bearer ${authToken}`, @@ -62,7 +62,7 @@ describe('Network package API global config utils', () => { }); it('should set the X-Monk-SDK-Version header', () => { - expect(getBaseAxiosConfig(baseConfig)).toEqual( + expect(getKyConfig(baseConfig)).toEqual( expect.objectContaining({ headers: expect.objectContaining({ 'X-Monk-SDK-Version': packageJson.version, diff --git a/packages/public/network/test/api/requests/inspections/requests.test.ts b/packages/public/network/test/api/requests/inspections/requests.test.ts index ee31ce937..634811d1e 100644 --- a/packages/public/network/test/api/requests/inspections/requests.test.ts +++ b/packages/public/network/test/api/requests/inspections/requests.test.ts @@ -1,36 +1,56 @@ -jest.mock('axios', () => ({ - request: jest.fn(() => Promise.resolve({ data: { test: 'hello' } })), -})); +import ky, { ResponsePromise } from 'ky'; +import { mapGetInspectionResponse } from '../../../../src/api/requests/inspections/mappers'; +import { getKyConfig, MonkAPIConfig } from '../../../../src/api/config'; +import { getInspection } from '../../../../src/api/requests/inspections'; + jest.mock('../../../../src/api/config', () => ({ - getBaseAxiosConfig: jest.fn(() => ({ baseUrl: 'test' })), + getKyConfig: jest.fn(() => ({ baseUrl: 'test', headers: { test: 'hello' } })), })); jest.mock('../../../../src/api/requests/inspections/mappers', () => ({ mapGetInspectionResponse: jest.fn(() => ({ inspections: [{ id: 'test' }] })), })); -import axios from 'axios'; -import { mapGetInspectionResponse } from '../../../../src/api/requests/inspections/mappers'; -import { getBaseAxiosConfig, MonkAPIConfig } from '../../../../src/api/config'; -import { getInspection } from '../../../../src/api/requests/inspections'; - describe('Inspection requests', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + describe('getInspection function', () => { - it('should pass the proper params to the axios request', async () => { + it('should pass the proper params to the ky request', async () => { const id = 'inspection-id-test'; const config: MonkAPIConfig = { apiDomain: 'test', authToken: 'wow' }; await getInspection(id, config); - expect(getBaseAxiosConfig).toHaveBeenCalledWith(config); - expect(axios.request).toHaveBeenCalledWith({ - ...getBaseAxiosConfig(config), - method: 'get', - url: `/inspections/${id}`, - }); + expect(getKyConfig).toHaveBeenCalledWith(config); + expect(ky.get).toHaveBeenCalledWith( + `${getKyConfig({} as MonkAPIConfig).baseUrl}/inspections/${id}`, + { headers: getKyConfig(config).headers }, + ); + }); + + it('should return the ky response', async () => { + const status = 404; + jest.spyOn(ky, 'get').mockImplementationOnce( + () => + Promise.resolve({ + status, + json: () => Promise.resolve({}), + }) as ResponsePromise, + ); + const result = await getInspection('test', {} as MonkAPIConfig); + expect(result.response).toEqual(expect.objectContaining({ status })); }); - it('should return the axiosResponse', async () => { + it('should return the response body', async () => { + const body = { hello: 'world' }; + jest.spyOn(ky, 'get').mockImplementationOnce( + () => + Promise.resolve({ + json: () => Promise.resolve(body), + }) as ResponsePromise, + ); const result = await getInspection('test', {} as MonkAPIConfig); - expect(result.axiosResponse).toEqual(await (axios.request as jest.Mock)()); + expect(result.body).toEqual(body); }); it('should return the mapped entities', async () => { diff --git a/yarn.lock b/yarn.lock index d712e71cb..896245bbe 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3721,7 +3721,7 @@ __metadata: languageName: unknown linkType: soft -"@monkvision/network@workspace:packages/public/network": +"@monkvision/network@4.0.0, @monkvision/network@workspace:packages/public/network": version: 0.0.0-use.local resolution: "@monkvision/network@workspace:packages/public/network" dependencies: @@ -3737,7 +3737,6 @@ __metadata: "@types/node": ^18.11.9 "@typescript-eslint/eslint-plugin": ^5.43.0 "@typescript-eslint/parser": ^5.43.0 - axios: ^1.6.2 eslint: ^8.29.0 eslint-config-airbnb-base: ^15.0.0 eslint-config-prettier: ^8.5.0 @@ -3751,12 +3750,16 @@ __metadata: jest: ^29.3.1 jsonwebtoken: ^9.0.2 jwt-decode: ^4.0.0 + ky: ^1.2.0 mkdirp: ^1.0.4 prettier: ^2.7.1 regexpp: ^3.2.0 rimraf: ^3.0.2 ts-jest: ^29.0.3 typescript: ^4.8.4 + peerDependencies: + react: ^17.0.2 + react-dom: ^17.0.2 languageName: unknown linkType: soft @@ -6713,17 +6716,6 @@ __metadata: languageName: node linkType: hard -"axios@npm:^1.6.2": - version: 1.6.2 - resolution: "axios@npm:1.6.2" - dependencies: - follow-redirects: ^1.15.0 - form-data: ^4.0.0 - proxy-from-env: ^1.1.0 - checksum: 4a7429e2b784be0f2902ca2680964391eae7236faa3967715f30ea45464b98ae3f1c6f631303b13dfe721b17126b01f486c7644b9ef276bfc63112db9fd379f8 - languageName: node - linkType: hard - "axobject-query@npm:^3.1.1": version: 3.2.1 resolution: "axobject-query@npm:3.2.1" @@ -14134,6 +14126,13 @@ __metadata: languageName: node linkType: hard +"ky@npm:^1.2.0": + version: 1.2.0 + resolution: "ky@npm:1.2.0" + checksum: 10d436364d3e49e0c702adf90dfd0494812ba12853dfc4c1552a13150d769dfe0af339cd105f1076861f35ce1fb242e875858a2adb4fce62de0a5b5c7e9739fa + languageName: node + linkType: hard + "language-subtag-registry@npm:~0.3.2": version: 0.3.22 resolution: "language-subtag-registry@npm:0.3.22" @@ -15216,6 +15215,7 @@ __metadata: "@monkvision/common-ui-web": 4.0.0 "@monkvision/inspection-capture-web": 4.0.0 "@monkvision/monitoring": 4.0.0 + "@monkvision/network": 4.0.0 "@monkvision/sentry": 4.0.0 "@monkvision/sights": 4.0.0 "@monkvision/types": 4.0.0 From 6077aec4d96808323361ab7fd3c3b231039b52b5 Mon Sep 17 00:00:00 2001 From: David_LY Date: Fri, 19 Jan 2024 17:16:18 +0100 Subject: [PATCH 2/2] esnext removed from typescript config --- packages/private/typescript-config/tsconfig.react.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/private/typescript-config/tsconfig.react.json b/packages/private/typescript-config/tsconfig.react.json index 9b9f53e03..9012430c3 100644 --- a/packages/private/typescript-config/tsconfig.react.json +++ b/packages/private/typescript-config/tsconfig.react.json @@ -5,8 +5,7 @@ "lib": [ "dom", "dom.iterable", - "esnext", - "es2015" + "esnext" ], "isolatedModules": true, "jsx": "react-jsx",