nexus-shared 1.1.17 → 1.1.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/api-services/system-service.tsx +1 -2
- package/src/client.ts +1 -1
- package/src/components/layouts/global-layout.tsx +4 -2
- package/src/components/layouts/layout-helpers.tsx +23 -23
- package/src/helpers/utility-helpers.tsx +1 -0
- package/src/index.ts +2 -1
- package/src/interfaces/exception-interfaces.tsx +99 -0
- package/src/interfaces/http-interfaces.tsx +38 -2
- package/src/interfaces/permission-interfaces.tsx +4 -4
- package/src/interfaces/sso-interfaces.tsx +52 -0
- package/src/interfaces/system-interfaces.tsx +2 -2
- package/src/load-env.ts +2 -0
- package/src/nexus.environments.tsx +21 -16
- package/src/services/http-client-services.tsx +60 -0
- package/src/services/http-services.tsx +143 -0
package/package.json
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { EIcons } from "../interfaces/icon-interfaces";
|
|
2
|
-
import {
|
|
3
|
-
import { ENexusModules } from "../interfaces/system-interfaces";
|
|
2
|
+
import { EMenuVisibility, IClientMenu, IPanelMenu, IUtilityMenu } from "../interfaces/menu-interfaces";
|
|
4
3
|
|
|
5
4
|
export const GetClientMenus = async (): Promise<IClientMenu[]> => {
|
|
6
5
|
return [
|
package/src/client.ts
CHANGED
|
@@ -21,10 +21,10 @@ export * from "./components/inputs/input";
|
|
|
21
21
|
export * from "./components/inputs/input-box";
|
|
22
22
|
export * from "./components/inputs/input-element";
|
|
23
23
|
export * from "./helpers/browser-helpers";
|
|
24
|
+
export * from "./services/http-client-services";
|
|
24
25
|
export * from "./services/loader-service";
|
|
25
26
|
export * from "./services/localstorage-service";
|
|
26
27
|
export * from "./services/theme-service";
|
|
27
|
-
|
|
28
28
|
// Core
|
|
29
29
|
export * from "./nexus-client";
|
|
30
30
|
export * from "./sso-client";
|
|
@@ -39,11 +39,13 @@ export const GlobalLayout = async ({ children, globalLayout }: Readonly<{ childr
|
|
|
39
39
|
display: "flex",
|
|
40
40
|
justifyContent: "center",
|
|
41
41
|
alignItems: "center",
|
|
42
|
-
backgroundColor: "rgb(
|
|
42
|
+
backgroundColor: "rgb(60, 60, 60)",
|
|
43
43
|
}}
|
|
44
44
|
id={ROOT_LOADER_IDENTITY}
|
|
45
45
|
>
|
|
46
|
-
<
|
|
46
|
+
<a href={"/"} className="p5 h r bg52" style={{ marginTop: "8rem" }} title="Visit Home Page">
|
|
47
|
+
<img src={FILES.LOGO.ICON} alt={"logo"} style={{ minHeight: "3rem", minWidth: "3rem", width: "3rem", height: "3rem" }} />
|
|
48
|
+
</a>
|
|
47
49
|
</div>
|
|
48
50
|
{children}
|
|
49
51
|
<Script type="module" src="https://cdn.jsdelivr.net/npm/ionicons@5.5.2/dist/ionicons/ionicons.esm.js" strategy="lazyOnload"></Script>
|
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import { useEffect } from "react";
|
|
4
|
-
import { RemoveElement } from "../../helpers/browser-helpers";
|
|
5
|
-
import { IGlobalLayout, ROOT_LOADER_IDENTITY } from "../../interfaces/layout-interfaces";
|
|
6
|
-
import { DEFAULT_THEME_INFORMATIONS } from "../../interfaces/theme-interfaces";
|
|
7
|
-
import { O } from "../../nexus-client";
|
|
8
|
-
import { InitializeStyles } from "../../services/theme-service";
|
|
9
|
-
|
|
10
|
-
export const LayoutHelpers = ({ globalLayout }: Readonly<{ globalLayout: IGlobalLayout }>) => {
|
|
11
|
-
useEffect(() => {
|
|
12
|
-
globalLayout.systemThemes = DEFAULT_THEME_INFORMATIONS;
|
|
13
|
-
O.systemThemes = DEFAULT_THEME_INFORMATIONS;
|
|
14
|
-
|
|
15
|
-
const timeout = setTimeout(() => {
|
|
16
|
-
clearTimeout(timeout);
|
|
17
|
-
RemoveElement(ROOT_LOADER_IDENTITY, 500);
|
|
18
|
-
InitializeStyles();
|
|
19
|
-
}, 50);
|
|
20
|
-
}, []);
|
|
21
|
-
|
|
22
|
-
return <div className="nexus-popup-container"></div>;
|
|
23
|
-
};
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useEffect } from "react";
|
|
4
|
+
import { RemoveElement } from "../../helpers/browser-helpers";
|
|
5
|
+
import { IGlobalLayout, ROOT_LOADER_IDENTITY } from "../../interfaces/layout-interfaces";
|
|
6
|
+
import { DEFAULT_THEME_INFORMATIONS } from "../../interfaces/theme-interfaces";
|
|
7
|
+
import { O } from "../../nexus-client";
|
|
8
|
+
import { InitializeStyles } from "../../services/theme-service";
|
|
9
|
+
|
|
10
|
+
export const LayoutHelpers = ({ globalLayout }: Readonly<{ globalLayout: IGlobalLayout }>) => {
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
globalLayout.systemThemes = DEFAULT_THEME_INFORMATIONS;
|
|
13
|
+
O.systemThemes = DEFAULT_THEME_INFORMATIONS;
|
|
14
|
+
|
|
15
|
+
const timeout = setTimeout(() => {
|
|
16
|
+
clearTimeout(timeout);
|
|
17
|
+
RemoveElement(ROOT_LOADER_IDENTITY, 500);
|
|
18
|
+
InitializeStyles();
|
|
19
|
+
}, 50);
|
|
20
|
+
}, [globalLayout]);
|
|
21
|
+
|
|
22
|
+
return <div className="nexus-popup-container"></div>;
|
|
23
|
+
};
|
package/src/index.ts
CHANGED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
export enum EErrorCodes {
|
|
2
|
+
Unknown = 0,
|
|
3
|
+
|
|
4
|
+
/// <summary>
|
|
5
|
+
/// Indicates that the format of the input is invalid.
|
|
6
|
+
/// </summary>
|
|
7
|
+
FormatInvalid = 1001,
|
|
8
|
+
|
|
9
|
+
/// <summary>
|
|
10
|
+
/// Indicates that the operation failed due to lack of authorization.
|
|
11
|
+
/// </summary>
|
|
12
|
+
/// <remarks>Use this value to represent scenarios where access is denied because the user or
|
|
13
|
+
/// process does not have the required permissions.</remarks>
|
|
14
|
+
Unauthorized = 2001,
|
|
15
|
+
|
|
16
|
+
/// <summary>
|
|
17
|
+
/// Indicates that the requested operation is forbidden due to insufficient permissions.
|
|
18
|
+
/// </summary>
|
|
19
|
+
Forbidden = 2002,
|
|
20
|
+
|
|
21
|
+
/// <summary>
|
|
22
|
+
/// Indicates that the authentication token has expired.
|
|
23
|
+
/// </summary>
|
|
24
|
+
/// <remarks>This value can be used to identify error conditions where a user's session or access
|
|
25
|
+
/// token is no longer valid and re-authentication is required.</remarks>
|
|
26
|
+
TokenExpired = 2003,
|
|
27
|
+
|
|
28
|
+
/// <summary>
|
|
29
|
+
/// Indicates that the requested resource could not be found.
|
|
30
|
+
/// </summary>
|
|
31
|
+
ResourceNotFound = 3001,
|
|
32
|
+
|
|
33
|
+
/// <summary>
|
|
34
|
+
/// Indicates that the specified resource already exists.
|
|
35
|
+
/// </summary>
|
|
36
|
+
AlreadyExists = 3002,
|
|
37
|
+
|
|
38
|
+
/// <summary>
|
|
39
|
+
/// Indicates that the requested operation or feature is not implemented.
|
|
40
|
+
/// </summary>
|
|
41
|
+
/// <remarks>Use this value to represent an error condition where a requested functionality is not
|
|
42
|
+
/// available or has not been provided by the system.</remarks>
|
|
43
|
+
NotImplemented = 4001,
|
|
44
|
+
|
|
45
|
+
/// <summary>
|
|
46
|
+
/// Indicates that a compile-time error has occurred.
|
|
47
|
+
/// </summary>
|
|
48
|
+
CompileTimeError = 4002,
|
|
49
|
+
|
|
50
|
+
/// <summary>
|
|
51
|
+
/// Indicates that a runtime error occurred during compilation.
|
|
52
|
+
/// </summary>
|
|
53
|
+
CompileRuntimeError = 4003,
|
|
54
|
+
}
|
|
55
|
+
export enum EExceptionCategories {
|
|
56
|
+
/// <summary>
|
|
57
|
+
/// Indicates that the associated value or member is related to validation operations.
|
|
58
|
+
/// </summary>
|
|
59
|
+
Validation = 1,
|
|
60
|
+
|
|
61
|
+
/// <summary>
|
|
62
|
+
/// Specifies that the item relates to security functionality or concerns.
|
|
63
|
+
/// </summary>
|
|
64
|
+
Security = 2,
|
|
65
|
+
|
|
66
|
+
/// <summary>
|
|
67
|
+
/// Represents an input/output file resource type.
|
|
68
|
+
/// </summary>
|
|
69
|
+
/// <remarks>Use this value to indicate that the resource is a file used for input or output
|
|
70
|
+
/// operations. This can be useful when distinguishing between different resource types in file handling or
|
|
71
|
+
/// resource management scenarios.</remarks>
|
|
72
|
+
IO_File = 3,
|
|
73
|
+
|
|
74
|
+
/// <summary>
|
|
75
|
+
/// Indicates that the log entry is related to database operations.
|
|
76
|
+
/// </summary>
|
|
77
|
+
Database = 4,
|
|
78
|
+
|
|
79
|
+
/// <summary>
|
|
80
|
+
/// Specifies that the log entry is associated with application-level events or messages.
|
|
81
|
+
/// </summary>
|
|
82
|
+
Application = 5,
|
|
83
|
+
|
|
84
|
+
/// <summary>
|
|
85
|
+
/// Indicates an internal service type that is not intended for external use.
|
|
86
|
+
/// </summary>
|
|
87
|
+
/// <remarks>This value is typically used to distinguish services that are only accessible within
|
|
88
|
+
/// the system or application. It may be used for internal routing, diagnostics, or other infrastructure
|
|
89
|
+
/// purposes.</remarks>
|
|
90
|
+
InternalService = 6,
|
|
91
|
+
|
|
92
|
+
/// <summary>
|
|
93
|
+
/// Indicates that the log entry is related to an external service.
|
|
94
|
+
/// </summary>
|
|
95
|
+
/// <remarks>Use this value to categorize logs that pertain to interactions with third-party or
|
|
96
|
+
/// external systems, such as APIs or external databases. This can help in filtering and analyzing logs specific
|
|
97
|
+
/// to external dependencies.</remarks>
|
|
98
|
+
ExternalService = 7,
|
|
99
|
+
}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
import { EErrorCodes, EExceptionCategories } from "./exception-interfaces";
|
|
2
|
+
import { IInterface, UN } from "./type-interfaces";
|
|
3
|
+
|
|
4
|
+
export enum EActions {
|
|
2
5
|
Get = 1,
|
|
3
6
|
Post = 2,
|
|
4
7
|
Put = 3,
|
|
@@ -6,10 +9,43 @@ export enum EActionMethods {
|
|
|
6
9
|
}
|
|
7
10
|
export interface IApiInformations {
|
|
8
11
|
api: string;
|
|
9
|
-
method:
|
|
12
|
+
method: EActions;
|
|
10
13
|
}
|
|
11
14
|
export interface IGetApiInformations extends IApiInformations {
|
|
12
15
|
cacheCode?: string;
|
|
13
16
|
/** In Minutes */
|
|
14
17
|
cacheLifetime?: number;
|
|
15
18
|
}
|
|
19
|
+
|
|
20
|
+
export interface IRootRespose extends IInterface {
|
|
21
|
+
message: string | UN;
|
|
22
|
+
messageCode: string | UN;
|
|
23
|
+
isSuccess: boolean;
|
|
24
|
+
code: number;
|
|
25
|
+
exceptionCategory: EExceptionCategories | UN;
|
|
26
|
+
errorCode: EErrorCodes | UN;
|
|
27
|
+
inputName: string | UN;
|
|
28
|
+
result: any | UN;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface IErrorRespose extends IRootRespose {
|
|
32
|
+
exception: any | UN;
|
|
33
|
+
}
|
|
34
|
+
export interface ISuccessResponse extends IRootRespose {}
|
|
35
|
+
export enum EBody {
|
|
36
|
+
JSON = "application/json",
|
|
37
|
+
MULTIPART_FORM_DATA = "application/x-www-form-urlencoded",
|
|
38
|
+
FORM_DATA = "multipart/form-data",
|
|
39
|
+
}
|
|
40
|
+
export const GetMethod = (method?: EActions) => {
|
|
41
|
+
switch (method) {
|
|
42
|
+
case EActions.Delete:
|
|
43
|
+
return "DELETE";
|
|
44
|
+
case EActions.Post:
|
|
45
|
+
return "POST";
|
|
46
|
+
case EActions.Put:
|
|
47
|
+
return "PUT";
|
|
48
|
+
default:
|
|
49
|
+
return "GET";
|
|
50
|
+
}
|
|
51
|
+
};
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { EActions } from "./http-interfaces";
|
|
2
|
+
import { EModules } from "./system-interfaces";
|
|
3
3
|
|
|
4
4
|
export interface IAccessAction {
|
|
5
5
|
controllerName: string;
|
|
6
6
|
actionName: string;
|
|
7
|
-
actionMethod:
|
|
8
|
-
nexusModule:
|
|
7
|
+
actionMethod: EActions;
|
|
8
|
+
nexusModule: EModules;
|
|
9
9
|
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { UN } from "nexus-shared";
|
|
2
|
+
export interface SSOAuthResponse {
|
|
3
|
+
accessToken: string;
|
|
4
|
+
refreshToken: string;
|
|
5
|
+
expiresTokenIn: number;
|
|
6
|
+
expiresRefreshTokenIn: number;
|
|
7
|
+
returnPath?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface CookieInfo {
|
|
11
|
+
ssoSession: string;
|
|
12
|
+
accessToken: string | UN;
|
|
13
|
+
refreshToken: string | UN;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface IRedirectContext {
|
|
17
|
+
redirectUri: string;
|
|
18
|
+
/** OAuth CSRF nonce sent to the client callback. */
|
|
19
|
+
state: string;
|
|
20
|
+
/** Relative path to return to after sign-in completes. */
|
|
21
|
+
returnPath: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface ISignInParams {
|
|
25
|
+
redirect_uri: string | UN;
|
|
26
|
+
state: string | UN;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface ISsoSessionResponse {
|
|
30
|
+
code: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface ILoginRequest {
|
|
34
|
+
username: string;
|
|
35
|
+
password: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface ILoginResponse {
|
|
39
|
+
ssoSession: string;
|
|
40
|
+
displayName: string;
|
|
41
|
+
role: string;
|
|
42
|
+
userId: number;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface IDemoUserInfo {
|
|
46
|
+
id: number;
|
|
47
|
+
username: string;
|
|
48
|
+
email: string;
|
|
49
|
+
displayName: string;
|
|
50
|
+
role: string;
|
|
51
|
+
passwordHint: string;
|
|
52
|
+
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
export enum
|
|
1
|
+
export enum EModules {
|
|
2
2
|
None = 0,
|
|
3
3
|
|
|
4
4
|
Migrations = 1 << 0, // 1 | 1
|
|
5
5
|
Configuration = 1 << 1, // 2 | 2
|
|
6
|
-
|
|
6
|
+
Authentication = 1 << 2, // 3 | 4
|
|
7
7
|
Enterprise = 1 << 3, // 4 | 8
|
|
8
8
|
Drive = 1 << 4, // 5 | 16
|
|
9
9
|
Message = 1 << 5, // 6 | 32
|
package/src/load-env.ts
ADDED
|
@@ -1,17 +1,21 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { EModules } from "./interfaces/system-interfaces";
|
|
2
2
|
import { EBackgrounds } from "./interfaces/theme-interfaces";
|
|
3
3
|
|
|
4
4
|
export const APIS = {
|
|
5
|
-
DRIVE: process.env.API_DRIVE,
|
|
6
|
-
AUTHENTICATION: process.env.API_AUTHENTICATION,
|
|
7
|
-
CONFIGURATION: process.env.API_CONFIGURATION,
|
|
8
|
-
CURRENT: process.env.API_CURRENT,
|
|
5
|
+
DRIVE: process.env.API_DRIVE as string,
|
|
6
|
+
AUTHENTICATION: process.env.API_AUTHENTICATION as string,
|
|
7
|
+
CONFIGURATION: process.env.API_CONFIGURATION as string,
|
|
8
|
+
CURRENT: process.env.API_CURRENT as string,
|
|
9
|
+
ENTERPRISE: process.env.API_ENTERPRISE as string,
|
|
10
|
+
MESSAGE: process.env.API_MESSAGE as string,
|
|
11
|
+
ACCOUNTING: process.env.API_ACCOUNTING as string,
|
|
12
|
+
REPORT: process.env.API_REPORT as string,
|
|
9
13
|
};
|
|
10
14
|
|
|
11
15
|
export const URLS = {
|
|
12
|
-
CURRENT: process.env.URL_CURRENT,
|
|
13
|
-
AUTHENTICATION: process.env.URL_AUTHENTICATION,
|
|
14
|
-
SIGN_IN: process.env.URL_AUTHENTICATION_SIGN_IN,
|
|
16
|
+
CURRENT: process.env.URL_CURRENT as string,
|
|
17
|
+
AUTHENTICATION: process.env.URL_AUTHENTICATION as string,
|
|
18
|
+
SIGN_IN: process.env.URL_AUTHENTICATION_SIGN_IN as string,
|
|
15
19
|
};
|
|
16
20
|
const FILE = (apiName: string, filePath: string, fileName: string) => `${APIS.DRIVE}/Resources/${apiName}/${encodeURIComponent(filePath)}/${encodeURIComponent(fileName)}`;
|
|
17
21
|
export const FILES = {
|
|
@@ -23,15 +27,16 @@ export const FILES = {
|
|
|
23
27
|
SCRIPT: (fileName: string, isStyles: boolean = false) => FILE("Script", isStyles ? "css" : "js", fileName),
|
|
24
28
|
};
|
|
25
29
|
export const NEXUS_INFO = {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
30
|
+
IS_ADMIN_MODULE: process.env.IS_ADMIN_MODULE !== "false",
|
|
31
|
+
CURRENT_MODULE: process.env.CURRENT_MODULE !== undefined ? (parseInt(process.env.CURRENT_MODULE) as EModules) : undefined,
|
|
32
|
+
CURRENT_MODULE_NAME: process.env.CURRENT_MODULE_NAME as string,
|
|
33
|
+
CURRENT_MODULE_SHORT_NAME: process.env.CURRENT_MODULE_SHORT_NAME as string,
|
|
29
34
|
IS_PRODUCTION: process.env.NODE_ENV === "production",
|
|
30
|
-
SOFTWARE_COMPANY_SHORT_NAME: process.env.SOFTWARE_COMPANY_SHORT_NAME,
|
|
31
|
-
SOFTWARE_COMPANY_FULL_NAME: process.env.SOFTWARE_COMPANY_FULL_NAME,
|
|
32
|
-
SOFTWARE_VERSION: process.env.SOFTWARE_VERSION,
|
|
33
|
-
SOFTWARE_LAST_UPDATE_DATE: process.env.SOFTWARE_LAST_UPDATE_DATE,
|
|
34
|
-
SOFTWARE_COMPANY_WEBSITE: process.env.SOFTWARE_COMPANY_WEBSITE,
|
|
35
|
+
SOFTWARE_COMPANY_SHORT_NAME: process.env.SOFTWARE_COMPANY_SHORT_NAME as string,
|
|
36
|
+
SOFTWARE_COMPANY_FULL_NAME: process.env.SOFTWARE_COMPANY_FULL_NAME as string,
|
|
37
|
+
SOFTWARE_VERSION: process.env.SOFTWARE_VERSION as string,
|
|
38
|
+
SOFTWARE_LAST_UPDATE_DATE: process.env.SOFTWARE_LAST_UPDATE_DATE as string,
|
|
39
|
+
SOFTWARE_COMPANY_WEBSITE: process.env.SOFTWARE_COMPANY_WEBSITE as string,
|
|
35
40
|
};
|
|
36
41
|
export const NEXUS_CONFIG = {
|
|
37
42
|
/** Default revalidate time for static generation (in minutes) */
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { EBody, IErrorRespose, ISuccessResponse } from "../interface";
|
|
4
|
+
import { HttpDelete, HttpGet, HttpPost, HttpPut } from "./http-services";
|
|
5
|
+
|
|
6
|
+
/** // if same url is called multiple times, it will only send one request and return the same promise
|
|
7
|
+
// this is to prevent multiple requests to the same url at the same time
|
|
8
|
+
// this is useful for example when user is typing in a search box and multiple requests are sent to the server
|
|
9
|
+
// this will prevent multiple requests to the same url and return the same promise
|
|
10
|
+
// this will also prevent multiple requests to the same url when user is navigating to different pages and the same url is called in each page
|
|
11
|
+
// If country Dropdown is called multiple times, it will only send one request and return the same promise
|
|
12
|
+
// this is useful for example when user is typing in a search box and multiple requests are sent to the server
|
|
13
|
+
*/
|
|
14
|
+
const pendingRequests: { [url: string]: Promise<any> | undefined } = {};
|
|
15
|
+
export const Get = async (apiUrl: string, successMessage?: boolean, errorMessage?: boolean, revalidate?: number): Promise<ISuccessResponse | IErrorRespose> => {
|
|
16
|
+
if (pendingRequests[apiUrl] !== undefined) return pendingRequests[apiUrl]!;
|
|
17
|
+
|
|
18
|
+
pendingRequests[apiUrl] = HttpGet(apiUrl)
|
|
19
|
+
.then((response) => handleResponse(response, successMessage ?? false, errorMessage ?? true))
|
|
20
|
+
.finally(() => {
|
|
21
|
+
delete pendingRequests[apiUrl];
|
|
22
|
+
});
|
|
23
|
+
return pendingRequests[apiUrl]!;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const Put = async (apiUrl: string, body?: any | null, bodyType?: EBody, successMessage?: boolean, errorMessage?: boolean, revalidate?: number) => {
|
|
27
|
+
return await handleResponse(await HttpPut(apiUrl, body, bodyType, null, revalidate, null), successMessage ?? true, errorMessage ?? true);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export const Post = async (apiUrl: string, body?: any | null, bodyType?: EBody, successMessage?: boolean, errorMessage?: boolean, revalidate?: number) => {
|
|
31
|
+
const isFindListing = Array.isArray(body) && apiUrl.endsWith("/FindListing");
|
|
32
|
+
|
|
33
|
+
if (!isFindListing) {
|
|
34
|
+
return await handleResponse(await HttpPost(apiUrl, body, bodyType, null, revalidate, null), successMessage ?? true, errorMessage ?? true);
|
|
35
|
+
} else {
|
|
36
|
+
const apiKey = `${apiUrl}__${(body as string[]).sort().join(",")}`;
|
|
37
|
+
|
|
38
|
+
if (pendingRequests[apiKey]) return pendingRequests[apiKey];
|
|
39
|
+
|
|
40
|
+
pendingRequests[apiKey] = HttpPost(apiUrl, body, bodyType, null, revalidate, null)
|
|
41
|
+
.then((response) => handleResponse(response, successMessage ?? true, errorMessage ?? true))
|
|
42
|
+
.finally(() => {
|
|
43
|
+
delete pendingRequests[apiKey];
|
|
44
|
+
});
|
|
45
|
+
return pendingRequests[apiKey];
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export const Delete = async (apiUrl: string, successMessage?: boolean, errorMessage?: boolean, revalidate?: number) => {
|
|
50
|
+
return await handleResponse(await HttpDelete(apiUrl, null, revalidate, null), successMessage ?? true, errorMessage ?? true);
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const handleResponse = async (result: ISuccessResponse | IErrorRespose, successMessage?: boolean, errorMessage?: boolean): Promise<ISuccessResponse | IErrorRespose> => {
|
|
54
|
+
if (result.isSuccess && !(result as IErrorRespose).exception) {
|
|
55
|
+
// TODO: if (successMessage) SuccessMessage("Request Success!", result.message ?? "Success");
|
|
56
|
+
return result;
|
|
57
|
+
}
|
|
58
|
+
// TODO: if (errorMessage) ErrorMessage("Request Failed!", result.message ?? "Failed");
|
|
59
|
+
return result;
|
|
60
|
+
};
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { Delay } from "../helpers/utility-helpers";
|
|
2
|
+
import { EActions, EBody, EModules, GetMethod, IErrorRespose, ISuccessResponse } from "../interface";
|
|
3
|
+
import { EErrorCodes, EExceptionCategories } from "../interfaces/exception-interfaces";
|
|
4
|
+
import { APIS, NEXUS_CONFIG, NEXUS_INFO } from "../nexus.environments";
|
|
5
|
+
|
|
6
|
+
const MAX_RETRIES = NEXUS_INFO.IS_PRODUCTION ? 2 : 1;
|
|
7
|
+
const RETRY_DELAY_MS = NEXUS_INFO.IS_PRODUCTION ? 50 : 1;
|
|
8
|
+
|
|
9
|
+
const NEXUS_HEADERS = (formdata?: any, httpMethod: EActions = EActions.Get, bodyType: EBody = EBody.JSON, token?: string | null, revalidate?: number | null, browserId?: string | null): RequestInit & { next?: { revalidate: number } } => {
|
|
10
|
+
const headers = new Headers();
|
|
11
|
+
if (token && token.length > 50) headers.append("authorization", `${token}`);
|
|
12
|
+
if (browserId) headers.append("bid", browserId);
|
|
13
|
+
if (bodyType === EBody.JSON && (httpMethod === EActions.Post || httpMethod === EActions.Put)) {
|
|
14
|
+
headers.append("Content-Type", "application/json");
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const options: RequestInit & { next?: { revalidate: number } } = {
|
|
18
|
+
method: GetMethod(httpMethod),
|
|
19
|
+
headers,
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
if (formdata) {
|
|
23
|
+
options.body = bodyType === EBody.JSON ? JSON.stringify(formdata) : formdata;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (!revalidate || revalidate <= 0) {
|
|
27
|
+
// No caching
|
|
28
|
+
options.cache = "no-store";
|
|
29
|
+
} else {
|
|
30
|
+
// Use Next.js ISR caching
|
|
31
|
+
options.next = { revalidate: revalidate ?? 0 };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return options;
|
|
35
|
+
};
|
|
36
|
+
const shouldRetry = (error: any) => error.name === "AbortError" || error.code === "ECONNREFUSED" || error.message.includes("fetch") || error.message.includes("network");
|
|
37
|
+
const safeFetch = async (fetchFunc: () => Promise<Response>): Promise<ISuccessResponse | IErrorRespose> => {
|
|
38
|
+
for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
|
|
39
|
+
try {
|
|
40
|
+
const response = await fetchFunc();
|
|
41
|
+
if (!response.ok) return await handleResponse(response);
|
|
42
|
+
return await response.json();
|
|
43
|
+
} catch (error: any) {
|
|
44
|
+
if (attempt < MAX_RETRIES - 1 && shouldRetry(error)) {
|
|
45
|
+
await Delay(RETRY_DELAY_MS * (attempt + 1));
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
return handleNetworkError(error);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return handleError({ status: 500, message: "Maximum retry attempts exceeded." });
|
|
52
|
+
};
|
|
53
|
+
const fetchWithTimeout = async (url: string, options?: RequestInit, timeout = NEXUS_CONFIG.DEFAULT_HTTP_REQUEST_TIMEOUT): Promise<Response> => {
|
|
54
|
+
const controller = new AbortController();
|
|
55
|
+
const id = setTimeout(() => controller.abort(), timeout);
|
|
56
|
+
try {
|
|
57
|
+
const response = await fetch(url, { ...options, signal: controller.signal });
|
|
58
|
+
clearTimeout(id);
|
|
59
|
+
return response;
|
|
60
|
+
} catch (error) {
|
|
61
|
+
clearTimeout(id);
|
|
62
|
+
throw error;
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
export const HttpGet = async (apiUrl: string, token?: string | null, revalidate?: number, browserId?: string | null) => {
|
|
66
|
+
return await safeFetch(() => fetchWithTimeout(apiUrl, NEXUS_HEADERS(undefined, EActions.Get, EBody.JSON, token, revalidate, browserId)));
|
|
67
|
+
};
|
|
68
|
+
export const HttpPost = async (apiUrl: string, body?: any, bodyType?: EBody, token?: string | null, revalidate?: number, browserId?: string | null) => {
|
|
69
|
+
return await safeFetch(() => fetchWithTimeout(apiUrl, NEXUS_HEADERS(body, EActions.Post, bodyType, token, revalidate, browserId)));
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export const HttpPut = async (apiUrl: string, body?: any, bodyType?: EBody, token?: string | null, revalidate?: number, browserId?: string | null) => {
|
|
73
|
+
return await safeFetch(() => fetchWithTimeout(apiUrl, NEXUS_HEADERS(body, EActions.Put, bodyType, token, revalidate, browserId)));
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export const HttpDelete = async (apiUrl: string, token?: string | null, revalidate?: number, browserId?: string | null) => {
|
|
77
|
+
return await safeFetch(() => fetchWithTimeout(apiUrl, NEXUS_HEADERS(undefined, EActions.Delete, undefined, token, revalidate, browserId)));
|
|
78
|
+
};
|
|
79
|
+
export const GetRootApi = (nexusModule: EModules) => {
|
|
80
|
+
switch (nexusModule) {
|
|
81
|
+
case EModules.Authentication:
|
|
82
|
+
return APIS.AUTHENTICATION;
|
|
83
|
+
case EModules.Configuration:
|
|
84
|
+
return APIS.CONFIGURATION;
|
|
85
|
+
case EModules.Enterprise:
|
|
86
|
+
return APIS.ENTERPRISE;
|
|
87
|
+
case EModules.Drive:
|
|
88
|
+
return APIS.DRIVE;
|
|
89
|
+
case EModules.Message:
|
|
90
|
+
return APIS.MESSAGE;
|
|
91
|
+
case EModules.Accounting:
|
|
92
|
+
return APIS.ACCOUNTING;
|
|
93
|
+
case EModules.Report:
|
|
94
|
+
return APIS.REPORT;
|
|
95
|
+
default:
|
|
96
|
+
throw new Error("Invalid Nexus Module");
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
const handleResponse = async (response: Response): Promise<IErrorRespose> => {
|
|
100
|
+
const status = response.status;
|
|
101
|
+
try {
|
|
102
|
+
const errorData = await response.json();
|
|
103
|
+
return handleError({ ...errorData, status });
|
|
104
|
+
} catch {
|
|
105
|
+
let message = "Unexpected server error.";
|
|
106
|
+
switch (status) {
|
|
107
|
+
case 401:
|
|
108
|
+
case 403:
|
|
109
|
+
message = "You do not have permission to access this resource.";
|
|
110
|
+
break;
|
|
111
|
+
case 404:
|
|
112
|
+
case 410:
|
|
113
|
+
message = "Resource not found.";
|
|
114
|
+
break;
|
|
115
|
+
case 405:
|
|
116
|
+
message = "Method not allowed.";
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
return handleError({ status, message });
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
export const handleError = (error: any): IErrorRespose => {
|
|
123
|
+
return {
|
|
124
|
+
isSuccess: false,
|
|
125
|
+
exception: error,
|
|
126
|
+
code: error?.code,
|
|
127
|
+
message: error?.message || "An unknown error occurred.",
|
|
128
|
+
messageCode: error?.key || "GENERIC_ERROR",
|
|
129
|
+
exceptionCategory: error?.exceptionCategory || EExceptionCategories.Application,
|
|
130
|
+
errorCode: error?.errorCode || EErrorCodes.Unknown,
|
|
131
|
+
inputName: error?.inputName || undefined,
|
|
132
|
+
result: null,
|
|
133
|
+
};
|
|
134
|
+
};
|
|
135
|
+
const handleNetworkError = (error: any): IErrorRespose => {
|
|
136
|
+
console.error("\n\nNetwork error encountered:", error);
|
|
137
|
+
return handleError({
|
|
138
|
+
status: 503,
|
|
139
|
+
message: "Unable to connect to the server. Please check your network or backend status.",
|
|
140
|
+
key: "NETWORK_ERROR",
|
|
141
|
+
exception: error,
|
|
142
|
+
});
|
|
143
|
+
};
|