nexus-shared 1.1.16 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nexus-shared",
3
- "version": "1.1.16",
3
+ "version": "1.1.18",
4
4
  "description": "Nexus shared React components for Next.js",
5
5
  "type": "module",
6
6
  "sideEffects": [
@@ -1,6 +1,5 @@
1
1
  import { EIcons } from "../interfaces/icon-interfaces";
2
- import { EMenuTargets, EMenuVisibility, IClientMenu, IPanelMenu, IUtilityMenu } from "../interfaces/menu-interfaces";
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(40, 40, 40)",
42
+ backgroundColor: "rgb(60, 60, 60)",
43
43
  }}
44
44
  id={ROOT_LOADER_IDENTITY}
45
45
  >
46
- <img src={FILES.LOGO.ICON} alt={"logo"} style={{ minHeight: "3rem", minWidth: "3rem", width: "3rem", height: "3rem" }} />
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
- '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
- }, []);
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
+ };
@@ -42,3 +42,4 @@ export function Debounce<T extends (...args: any[]) => void>(func: T, delay?: nu
42
42
 
43
43
  return debounced;
44
44
  }
45
+ export const Delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
package/src/index.ts CHANGED
@@ -37,5 +37,6 @@ export * from "./interfaces/type-interfaces";
37
37
 
38
38
  // Core
39
39
  export * from "./nexus.environments";
40
+ export * from "./services/http-services";
40
41
  export * from "./sso-server";
41
-
42
+
@@ -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
- export enum EActionMethods {
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: EActionMethods;
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 { EActionMethods } from "./http-interfaces";
2
- import { ENexusModules } from "./system-interfaces";
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: EActionMethods;
8
- nexusModule: ENexusModules;
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 ENexusModules {
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
- Identity = 1 << 2, // 3 | 4
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
@@ -0,0 +1,2 @@
1
+ /** Loads environment variables for nexus-shared consumers (no-op when Next.js already loaded .env). */
2
+ export {};
@@ -1,17 +1,21 @@
1
- import { ENexusModules } from "./interfaces/system-interfaces";
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
- CURRENT_MODULE: process.env.CURRENT_MODULE !== undefined ? (parseInt(process.env.CURRENT_MODULE) as ENexusModules) : undefined,
27
- CURRENT_MODULE_NAME: process.env.CURRENT_MODULE_NAME,
28
- CURRENT_MODULE_SHORT_NAME: process.env.CURRENT_MODULE_SHORT_NAME,
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) */
@@ -40,5 +45,5 @@ export const NEXUS_CONFIG = {
40
45
  /** Default timeout for HTTP requests (in milliseconds) */
41
46
  DEFAULT_HTTP_REQUEST_TIMEOUT: NEXUS_INFO.IS_PRODUCTION ? 60000 : 1000000,
42
47
 
43
- DEFAULT_BACKGROUND: process.env.DEFAULT_BACKGROUND ?? EBackgrounds.Background1,
48
+ DEFAULT_BACKGROUND: (process.env.DEFAULT_BACKGROUND as EBackgrounds) ?? EBackgrounds.Background1,
44
49
  };
@@ -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
+ };