Skip to content

Commit 05d1af4

Browse files
committed
refactor: rename Api to API and update imports across the codebase
1 parent a252b07 commit 05d1af4

File tree

18 files changed

+359
-302
lines changed

18 files changed

+359
-302
lines changed

.eslintignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ scripts/*.cjs
33

44
# Files with ESLint errors/warnings
55
src/Common/AddCDButton/AddCDButton.tsx
6-
src/Common/Api.ts
76
src/Common/AppStatus/AppStatus.tsx
87
src/Common/AppStatus/utils.ts
98
src/Common/BreadCrumb/BreadCrumb.tsx

src/Common/API/CoreAPI.ts

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
import { API_STATUS_CODES, APIOptions, FALLBACK_REQUEST_TIMEOUT, Host, ResponseType, ServerErrors } from '..'
2+
import { CoreAPIConstructorParamsType, FetchAPIParamsType, FetchInTimeParamsType } from './types'
3+
import { handleServerError } from './utils'
4+
5+
class CoreAPI {
6+
handleLogout: () => void
7+
8+
host: string
9+
10+
timeout: number
11+
12+
constructor({ handleLogout, host, timeout }: CoreAPIConstructorParamsType) {
13+
this.handleLogout = handleLogout
14+
this.host = host || Host
15+
this.timeout = timeout || FALLBACK_REQUEST_TIMEOUT
16+
}
17+
18+
private fetchAPI = async <K = object>({
19+
url,
20+
type,
21+
data,
22+
signal,
23+
preventAutoLogout = false,
24+
isMultipartRequest,
25+
}: FetchAPIParamsType<K>): Promise<ResponseType> => {
26+
const options: RequestInit = {
27+
method: type,
28+
signal,
29+
body: data ? JSON.stringify(data) : undefined,
30+
}
31+
// eslint-disable-next-line dot-notation
32+
options['credentials'] = 'include' as RequestCredentials
33+
return fetch(
34+
`${this.host}/${url}`,
35+
!isMultipartRequest
36+
? options
37+
: ({
38+
method: type,
39+
body: data,
40+
} as RequestInit),
41+
).then(
42+
// eslint-disable-next-line consistent-return
43+
async (response) => {
44+
const contentType = response.headers.get('Content-Type')
45+
if (response.status === API_STATUS_CODES.UNAUTHORIZED) {
46+
if (preventAutoLogout) {
47+
throw new ServerErrors({
48+
code: API_STATUS_CODES.UNAUTHORIZED,
49+
errors: [
50+
{
51+
code: API_STATUS_CODES.UNAUTHORIZED,
52+
internalMessage: 'Please login again',
53+
userMessage: 'Please login again',
54+
},
55+
],
56+
})
57+
} else {
58+
this.handleLogout()
59+
// Using this way to ensure that the user is redirected to the login page
60+
// and the component has enough time to get unmounted otherwise the component re-renders
61+
// and try to access some property of a variable and log exception to sentry
62+
// FIXME: Fix this later after analyzing impact
63+
// eslint-disable-next-line no-return-await
64+
return await new Promise((resolve) => {
65+
setTimeout(() => {
66+
resolve({ code: API_STATUS_CODES.UNAUTHORIZED, status: 'Unauthorized', result: [] })
67+
}, 1000)
68+
})
69+
}
70+
} else if (response.status >= 300 && response.status <= 599) {
71+
// FIXME: Fix this later after analyzing impact
72+
// eslint-disable-next-line no-return-await
73+
return await handleServerError(contentType, response)
74+
} else {
75+
if (contentType === 'application/json') {
76+
return response.json().then((responseBody) => {
77+
if (responseBody.code >= 300 && responseBody.code <= 599) {
78+
// Test Code in Response Body, despite successful HTTP Response Code
79+
throw new ServerErrors({ code: responseBody.code, errors: responseBody.errors })
80+
} else {
81+
// Successful Response. Expected Response Type {code, result, status}
82+
return responseBody
83+
}
84+
})
85+
}
86+
if (contentType === 'octet-stream' || contentType === 'application/octet-stream') {
87+
// used in getArtifact() API only
88+
return response
89+
}
90+
}
91+
},
92+
(error) => {
93+
// Network call fails. Handle Failed to Fetch
94+
const err = {
95+
code: 0,
96+
userMessage: error.message,
97+
internalMessage: error.message,
98+
moreInfo: error.message,
99+
}
100+
throw new ServerErrors({ code: 0, errors: [err] })
101+
},
102+
)
103+
}
104+
105+
private fetchInTime = <T = object>({
106+
url,
107+
type,
108+
data,
109+
options,
110+
isMultipartRequest,
111+
}: FetchInTimeParamsType<T>): Promise<ResponseType> => {
112+
const controller = options?.abortControllerRef?.current ?? new AbortController()
113+
const signal = options?.abortControllerRef?.current?.signal || options?.signal || controller.signal
114+
const timeoutPromise: Promise<ResponseType> = new Promise((resolve, reject) => {
115+
const timeout = options?.timeout || this.timeout
116+
117+
setTimeout(() => {
118+
controller.abort()
119+
if (options?.abortControllerRef?.current) {
120+
// eslint-disable-next-line no-param-reassign
121+
options.abortControllerRef.current = new AbortController()
122+
}
123+
124+
// Note: This is not catered in case abortControllerRef is passed since
125+
// the API is rejected with abort signal from line 202
126+
// FIXME: Remove once signal is removed
127+
// eslint-disable-next-line prefer-promise-reject-errors
128+
reject({
129+
code: API_STATUS_CODES.REQUEST_TIMEOUT,
130+
errors: [
131+
{
132+
code: API_STATUS_CODES.REQUEST_TIMEOUT,
133+
internalMessage: 'Request cancelled',
134+
userMessage: 'Request Cancelled',
135+
},
136+
],
137+
})
138+
}, timeout)
139+
})
140+
return Promise.race([
141+
this.fetchAPI({
142+
url,
143+
type,
144+
data,
145+
signal,
146+
preventAutoLogout: options?.preventAutoLogout || false,
147+
isMultipartRequest,
148+
}),
149+
timeoutPromise,
150+
]).catch((err) => {
151+
if (err instanceof ServerErrors) {
152+
throw err
153+
} else {
154+
// FIXME: Can be removed once signal is removed
155+
throw new ServerErrors({
156+
code: API_STATUS_CODES.REQUEST_TIMEOUT,
157+
errors: [
158+
{
159+
code: API_STATUS_CODES.REQUEST_TIMEOUT,
160+
internalMessage: 'That took longer than expected.',
161+
userMessage: 'That took longer than expected.',
162+
},
163+
],
164+
})
165+
}
166+
})
167+
}
168+
169+
post = <T = any, K = object>(
170+
url: string,
171+
data: K,
172+
options?: APIOptions,
173+
isMultipartRequest?: boolean,
174+
): Promise<ResponseType<T>> => this.fetchInTime<K>({ url, type: 'POST', data, options, isMultipartRequest })
175+
176+
put = <T = any, K = object>(url: string, data: K, options?: APIOptions): Promise<ResponseType<T>> =>
177+
this.fetchInTime<K>({ url, type: 'PUT', data, options })
178+
179+
patch = <T = any, K = object>(url: string, data: K, options?: APIOptions): Promise<ResponseType<T>> =>
180+
this.fetchInTime<K>({ url, type: 'PATCH', data, options })
181+
182+
get = <T = any>(url: string, options?: APIOptions): Promise<ResponseType<T>> =>
183+
this.fetchInTime({ url, type: 'GET', data: null, options })
184+
185+
trash = <T = any, K = object>(url: string, data?: K, options?: APIOptions): Promise<ResponseType<T>> =>
186+
this.fetchInTime<K>({ url, type: 'DELETE', data, options })
187+
}
188+
189+
export default CoreAPI

src/Common/API/constants.ts

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
export const RESPONSE_MESSAGES = {
2+
100: 'Continue',
3+
101: 'Switching Protocols',
4+
102: 'Processing(WebDAV)',
5+
200: 'OK',
6+
201: 'Created',
7+
202: 'Accepted',
8+
203: 'Non - Authoritative Information',
9+
204: 'No Content',
10+
205: 'Reset Content',
11+
206: 'Partial Content',
12+
207: 'Multi - Status(WebDAV)',
13+
208: 'Already Reported(WebDAV)',
14+
226: 'IM Used',
15+
300: 'Multiple Choices',
16+
301: 'Moved Permanently',
17+
302: 'Found',
18+
303: 'See Other',
19+
304: 'Not Modified',
20+
305: 'Use Proxy',
21+
307: 'Temporary Redirect',
22+
308: 'Permanent Redirect(experimental)',
23+
400: 'Bad Request',
24+
401: 'Unauthorized',
25+
402: 'Payment Required',
26+
403: 'Forbidden',
27+
404: 'Not Found',
28+
405: 'Method Not Allowed',
29+
406: 'Not Acceptable',
30+
407: 'Proxy Authentication Required',
31+
408: 'Request Timeout',
32+
409: 'Conflict',
33+
410: 'Gone',
34+
411: 'Length Required',
35+
412: 'Precondition Failed',
36+
413: 'Request Entity Too Large',
37+
414: 'Request - URI Too Long',
38+
415: 'Unsupported Media Type',
39+
416: 'Requested Range Not Satisfiable',
40+
417: 'Expectation Failed',
41+
418: "I'm a teapot",
42+
420: 'Enhance Your Calm(Twitter)',
43+
422: 'Unprocessable Entity(WebDAV)',
44+
423: 'Locked(WebDAV)',
45+
424: 'Failed Dependency(WebDAV)',
46+
425: 'Reserved for WebDAV',
47+
426: 'Upgrade Required',
48+
428: 'Precondition Required',
49+
429: 'Too Many Requests',
50+
431: 'Request Header Fields Too Large',
51+
444: 'No Response(Nginx)',
52+
449: 'Retry With(Microsoft)',
53+
450: 'Blocked by Windows Parental Controls(Microsoft)',
54+
451: 'Unavailable For Legal Reasons',
55+
499: 'Client Closed Request(Nginx)',
56+
500: 'Internal Server Error',
57+
501: 'Not Implemented',
58+
502: 'Bad Gateway',
59+
503: 'Service Unavailable',
60+
504: 'Gateway Timeout',
61+
505: 'HTTP Version Not Supported',
62+
506: 'Variant Also Negotiates(Experimental)',
63+
507: 'Insufficient Storage(WebDAV)',
64+
508: 'Loop Detected(WebDAV)',
65+
509: 'Bandwidth Limit Exceeded(Apache)',
66+
510: 'Not Extended',
67+
511: 'Network Authentication Required',
68+
598: 'Network read timeout error',
69+
599: 'Network connect timeout error',
70+
}

src/Common/API/index.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import CoreAPI from './CoreAPI'
2+
import { handleDashboardLogout } from './utils'
3+
4+
const dashboardAPI = new CoreAPI({
5+
handleLogout: handleDashboardLogout,
6+
timeout: (window as any)?._env_?.GLOBAL_API_TIMEOUT,
7+
})
8+
9+
export const { post, put, patch, get, trash } = dashboardAPI
10+
export { getIsRequestAborted, abortPreviousRequests } from './utils'
11+
export { default as CoreAPI } from './CoreAPI'

src/Common/API/types.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { APIOptions } from '..'
2+
3+
export interface CoreAPIConstructorParamsType {
4+
handleLogout: () => void
5+
/**
6+
* @default Host
7+
*/
8+
host?: string
9+
/**
10+
* @default FALLBACK_REQUEST_TIMEOUT
11+
*/
12+
timeout?: number
13+
}
14+
15+
export interface FetchInTimeParamsType<Data = object> {
16+
url: string
17+
type: 'POST' | 'PUT' | 'PATCH' | 'GET' | 'DELETE'
18+
data: Data
19+
options?: APIOptions
20+
isMultipartRequest?: boolean
21+
}
22+
23+
export interface FetchAPIParamsType<Data = object> extends Omit<FetchInTimeParamsType<Data>, 'options'> {
24+
signal: AbortSignal
25+
/**
26+
* @default false
27+
*/
28+
preventAutoLogout?: boolean
29+
}

src/Common/API/utils.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { ServerErrors } from '@Common/ServerError'
2+
import { MutableRefObject } from 'react'
3+
import { URLS } from '@Common/Constants'
4+
import { RESPONSE_MESSAGES } from './constants'
5+
6+
export const handleServerError = async (contentType: string, response: Response) => {
7+
// Test for HTTP Status Code
8+
const code = response.status
9+
let status: string = response.statusText || RESPONSE_MESSAGES[code]
10+
const serverError = new ServerErrors({ code, errors: [] })
11+
if (contentType !== 'application/json') {
12+
// used for better debugging,
13+
status = `${RESPONSE_MESSAGES[code]}. Please try again.`
14+
} else {
15+
const responseBody = await response.json()
16+
if (responseBody.errors) {
17+
serverError.errors = responseBody.errors
18+
}
19+
}
20+
serverError.errors =
21+
serverError.errors.length > 0 ? serverError.errors : [{ code, internalMessage: status, userMessage: status }]
22+
throw serverError
23+
}
24+
25+
/**
26+
* Aborts the previous request before triggering next request
27+
*/
28+
export const abortPreviousRequests = <T>(
29+
callback: () => Promise<T>,
30+
abortControllerRef: MutableRefObject<AbortController>,
31+
): Promise<T> => {
32+
abortControllerRef.current.abort()
33+
// eslint-disable-next-line no-param-reassign
34+
abortControllerRef.current = new AbortController()
35+
return callback()
36+
}
37+
38+
/**
39+
* Returns true if the error is due to a aborted request
40+
*/
41+
export const getIsRequestAborted = (error) =>
42+
// The 0 code is common for aborted and blocked requests
43+
error && error.code === 0 && error.message.search('abort|aborted') > -1
44+
45+
export const handleDashboardLogout = () => {
46+
const continueParam = `${window.location.pathname.replace(window.__BASE_URL__, '')}${window.location.search}`
47+
window.location.href = `${window.location.origin}${window.__BASE_URL__}${URLS.LOGIN_SSO}?continue=${continueParam}`
48+
}

0 commit comments

Comments
 (0)