diff --git a/libs/shared-web/src/api/api.client.ts b/libs/shared-web/src/api/api.client.ts index 5e7b8ae1b7a..c7000585f77 100644 --- a/libs/shared-web/src/api/api.client.ts +++ b/libs/shared-web/src/api/api.client.ts @@ -1,5 +1,5 @@ import axios from 'axios'; -import { IParamObject } from '@novu/shared'; +import { CustomDataType } from '@novu/shared'; import { API_ROOT } from '../config'; @@ -44,7 +44,7 @@ export const api = { return Promise.reject(error?.response?.data || error?.response || error); }); }, - post(url: string, payload, params?: IParamObject) { + post(url: string, payload, params?: CustomDataType) { return axios .post(`${API_ROOT}${url}`, payload, { params, headers: getHeaders() }) .then((response) => response.data?.data) diff --git a/libs/shared/package.json b/libs/shared/package.json index 517d34fe556..99aed076ad9 100644 --- a/libs/shared/package.json +++ b/libs/shared/package.json @@ -38,7 +38,6 @@ } }, "dependencies": { - "axios": "^1.6.2", "class-transformer": "0.5.1", "class-validator": "0.14.0" }, diff --git a/libs/shared/src/services/http-client/api.client.ts b/libs/shared/src/services/http-client/api.client.ts deleted file mode 100644 index bab90d5fb22..00000000000 --- a/libs/shared/src/services/http-client/api.client.ts +++ /dev/null @@ -1,60 +0,0 @@ -import axios, { AxiosInstance } from 'axios'; - -//SubscriberCustomData -export interface IParamObject { - [key: string]: string | string[] | number | boolean; -} - -export interface IPaginatedResponse { - data: T[]; - hasMore: boolean; - totalCount: number; - pageSize: number; - page: number; -} - -export class HttpClient { - private axiosClient: AxiosInstance; - - constructor(private backendUrl: string) { - this.axiosClient = axios.create({ - baseURL: backendUrl + '/v1', - }); - } - - setAuthorizationToken(token: string) { - this.axiosClient.defaults.headers.common.Authorization = `Bearer ${token}`; - } - - disposeAuthorizationToken() { - delete this.axiosClient.defaults.headers.common.Authorization; - } - - async getFullResponse(url: string, params?: IParamObject) { - return await this.axiosClient - .get(url, { - params, - }) - .then((response) => response.data); - } - - async get(url: string, params?: IParamObject) { - return await this.axiosClient - .get(url, { - params, - }) - .then((response) => response.data.data); - } - - async post(url: string, body = {}) { - return await this.axiosClient.post(url, body).then((response) => response.data.data); - } - - async patch(url: string, body = {}) { - return await this.axiosClient.patch(url, body).then((response) => response.data.data); - } - - async delete(url: string, body = {}) { - return await this.axiosClient.delete(url, body).then((response) => response.data.data); - } -} diff --git a/libs/shared/src/services/http-client/index.ts b/libs/shared/src/services/http-client/index.ts deleted file mode 100644 index 0552afceb61..00000000000 --- a/libs/shared/src/services/http-client/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './api.client'; diff --git a/libs/shared/src/services/index.ts b/libs/shared/src/services/index.ts index abc3bd5800e..9dbcb3101c2 100644 --- a/libs/shared/src/services/index.ts +++ b/libs/shared/src/services/index.ts @@ -1,2 +1 @@ -export * from './http-client'; export * from './feature-flags'; diff --git a/libs/shared/src/types/shared/index.ts b/libs/shared/src/types/shared/index.ts index 7c0ec79b37a..068f7055510 100644 --- a/libs/shared/src/types/shared/index.ts +++ b/libs/shared/src/types/shared/index.ts @@ -9,3 +9,11 @@ export interface IResponseError { message: string; statusCode: number; } + +export interface IPaginatedResponse { + data: T[]; + hasMore: boolean; + totalCount: number; + pageSize: number; + page: number; +} diff --git a/packages/client/src/api/api.service.ts b/packages/client/src/api/api.service.ts index 6e69c7af4ef..0de7b9e1e18 100644 --- a/packages/client/src/api/api.service.ts +++ b/packages/client/src/api/api.service.ts @@ -1,11 +1,11 @@ import { IMessage, - HttpClient, ButtonTypeEnum, MessageActionStatusEnum, - IParamObject, + CustomDataType, IPaginatedResponse, } from '@novu/shared'; +import { HttpClient } from '../http-client'; import { ITabCountQuery, IStoreQuery, @@ -13,6 +13,7 @@ import { IUnseenCountQuery, IUnreadCountQuery, IUserGlobalPreferenceSettings, + ApiOptions, } from '../index'; export class ApiService { @@ -20,8 +21,25 @@ export class ApiService { isAuthenticated = false; - constructor(private backendUrl: string) { - this.httpClient = new HttpClient(backendUrl); + constructor(backendUrl: string, apiVersion?: ApiOptions['apiVersion']); + constructor(options?: ApiOptions); + constructor(...args: any) { + if (arguments.length === 2) { + this.httpClient = new HttpClient({ + backendUrl: args[0], + apiVersion: args[1], + }); + } else if (arguments.length === 1) { + if (typeof args[0] === 'object') { + this.httpClient = new HttpClient(args[0]); + } else if (typeof args[0] === 'string') { + this.httpClient = new HttpClient({ + backendUrl: args[0], + }); + } + } else { + this.httpClient = new HttpClient(); + } } setAuthorizationToken(token: string) { @@ -107,7 +125,7 @@ export class ApiService { `/widgets/notifications/feed`, { page, - payload: payloadString, + ...(payloadString && { payload: payloadString }), ...rest, } ); @@ -138,21 +156,21 @@ export class ApiService { async getUnseenCount(query: IUnseenCountQuery = {}) { return await this.httpClient.get( '/widgets/notifications/unseen', - query as unknown as IParamObject + query as unknown as CustomDataType ); } async getUnreadCount(query: IUnreadCountQuery = {}) { return await this.httpClient.get( '/widgets/notifications/unread', - query as unknown as IParamObject + query as unknown as CustomDataType ); } async getTabCount(query: ITabCountQuery = {}) { return await this.httpClient.get( '/widgets/notifications/count', - query as unknown as IParamObject + query as unknown as CustomDataType ); } diff --git a/packages/client/src/http-client/http-client.ts b/packages/client/src/http-client/http-client.ts new file mode 100644 index 00000000000..bad5afc7481 --- /dev/null +++ b/packages/client/src/http-client/http-client.ts @@ -0,0 +1,103 @@ +import { ApiOptions } from '..'; +import { CustomDataType } from '@novu/shared'; + +const DEFAULT_API_VERSION = 'v1'; +const DEFAULT_BACKEND_URL = 'https://api.novu.co'; +export class HttpClient { + private backendUrl: string; + private apiVersion: string; + private headers: Record; + + constructor({ + apiVersion = DEFAULT_API_VERSION, + backendUrl = DEFAULT_BACKEND_URL, + }: ApiOptions = {}) { + this.apiVersion = apiVersion; + this.backendUrl = `${backendUrl}/${this.apiVersion}`; + this.headers = { + 'Content-Type': 'application/json', + }; + } + + setAuthorizationToken(token: string) { + this.headers.Authorization = `Bearer ${token}`; + } + + disposeAuthorizationToken() { + delete this.headers.Authorization; + } + + async getFullResponse(url: string, params?: CustomDataType) { + const response = await this.doFetch(url + this.getQueryString(params)); + + return await response.json(); + } + + async get(url: string, params?: CustomDataType) { + const response = await this.doFetch(url + this.getQueryString(params)); + const data = await response.json(); + + return data.data; + } + + async post(url: string, body = {}) { + const response = await this.doFetch(url, { + method: 'POST', + body: JSON.stringify(body), + }); + const data = await response.json(); + + return data.data; + } + + async patch(url: string, body = {}) { + const response = await this.doFetch(url, { + method: 'PATCH', + body: JSON.stringify(body), + }); + const data = await response.json(); + + return data.data; + } + + async delete(url: string, body = {}) { + const response = await this.doFetch(url, { + method: 'DELETE', + body: JSON.stringify(body), + }); + const data = await response.json(); + + return data.data; + } + + private getQueryString(params?: CustomDataType) { + if (!params) return ''; + + const queryString = new URLSearchParams(params as any); + + return '?' + queryString.toString(); + } + + private async doFetch(url: string, options: RequestInit = {}) { + try { + const response = await fetch(this.backendUrl + url, { + ...options, + headers: this.headers, + }); + await this.checkResponseStatus(response); + + return response; + } catch (error) { + throw error; + } + } + + private async checkResponseStatus(response: Response) { + if (!response.ok) { + const errorData = await response.json(); + throw new Error( + `HTTP error! Status: ${response.status}, Message: ${errorData.message}` + ); + } + } +} diff --git a/packages/client/src/http-client/index.ts b/packages/client/src/http-client/index.ts new file mode 100644 index 00000000000..29d4d49b678 --- /dev/null +++ b/packages/client/src/http-client/index.ts @@ -0,0 +1 @@ +export * from './http-client'; diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts index 0d63330f163..55302463ea8 100644 --- a/packages/client/src/index.ts +++ b/packages/client/src/index.ts @@ -49,4 +49,10 @@ export type PreferenceSettingsType = { enabled: boolean; channels: IPreferenceChannels; }; + +export type ApiOptions = { + apiVersion?: string; + backendUrl?: string; +}; + export { ApiService } from './api/api.service'; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1133da2d49d..7ac1f7e2bfe 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3076,9 +3076,6 @@ importers: libs/shared: dependencies: - axios: - specifier: ^1.6.2 - version: 1.6.2 class-transformer: specifier: 0.5.1 version: 0.5.1