Skip to content

Commit

Permalink
Refactor [@novu/client] : remove axios dependency and use fetch inste…
Browse files Browse the repository at this point in the history
…ad (#5554)

* refactor: Update HttpClient to use fetch API instead of axios

* refactor: Remove axios dependency from shared package.json and pnpm-lock.yaml

* feat: add apiversion option

* refactor: move http-client to @novu/client

* feat: move http-client to @novu/client

* feat: add error handling and update the constructor params

* feat: overload constructor
  • Loading branch information
BiswaViraj authored May 21, 2024
1 parent 110878e commit 06a7260
Show file tree
Hide file tree
Showing 11 changed files with 146 additions and 76 deletions.
4 changes: 2 additions & 2 deletions libs/shared-web/src/api/api.client.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import axios from 'axios';
import { IParamObject } from '@novu/shared';
import { CustomDataType } from '@novu/shared';

import { API_ROOT } from '../config';

Expand Down Expand Up @@ -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)
Expand Down
1 change: 0 additions & 1 deletion libs/shared/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@
}
},
"dependencies": {
"axios": "^1.6.2",
"class-transformer": "0.5.1",
"class-validator": "0.14.0"
},
Expand Down
60 changes: 0 additions & 60 deletions libs/shared/src/services/http-client/api.client.ts

This file was deleted.

1 change: 0 additions & 1 deletion libs/shared/src/services/http-client/index.ts

This file was deleted.

1 change: 0 additions & 1 deletion libs/shared/src/services/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
export * from './http-client';
export * from './feature-flags';
8 changes: 8 additions & 0 deletions libs/shared/src/types/shared/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,11 @@ export interface IResponseError {
message: string;
statusCode: number;
}

export interface IPaginatedResponse<T = unknown> {
data: T[];
hasMore: boolean;
totalCount: number;
pageSize: number;
page: number;
}
34 changes: 26 additions & 8 deletions packages/client/src/api/api.service.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,45 @@
import {
IMessage,
HttpClient,
ButtonTypeEnum,
MessageActionStatusEnum,
IParamObject,
CustomDataType,
IPaginatedResponse,
} from '@novu/shared';
import { HttpClient } from '../http-client';
import {
ITabCountQuery,
IStoreQuery,
IUserPreferenceSettings,
IUnseenCountQuery,
IUnreadCountQuery,
IUserGlobalPreferenceSettings,
ApiOptions,
} from '../index';

export class ApiService {
private httpClient: HttpClient;

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) {
Expand Down Expand Up @@ -107,7 +125,7 @@ export class ApiService {
`/widgets/notifications/feed`,
{
page,
payload: payloadString,
...(payloadString && { payload: payloadString }),
...rest,
}
);
Expand Down Expand Up @@ -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
);
}

Expand Down
103 changes: 103 additions & 0 deletions packages/client/src/http-client/http-client.ts
Original file line number Diff line number Diff line change
@@ -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<string, string>;

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}`
);
}
}
}
1 change: 1 addition & 0 deletions packages/client/src/http-client/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './http-client';
6 changes: 6 additions & 0 deletions packages/client/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,10 @@ export type PreferenceSettingsType = {
enabled: boolean;
channels: IPreferenceChannels;
};

export type ApiOptions = {
apiVersion?: string;
backendUrl?: string;
};

export { ApiService } from './api/api.service';
3 changes: 0 additions & 3 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 06a7260

Please sign in to comment.