nexus-shared 1.1.17 → 1.1.19

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.
Files changed (58) hide show
  1. package/package.json +1 -1
  2. package/src/api-services/system-service.tsx +1 -2
  3. package/src/client.ts +3 -2
  4. package/src/components/documents/button.tsx +5 -1
  5. package/src/components/inputs/input-box.tsx +4 -2
  6. package/src/components/inputs/input-form.tsx +126 -14
  7. package/src/components/layouts/global-layout.tsx +4 -2
  8. package/src/components/layouts/layout-helpers.tsx +23 -23
  9. package/src/helpers/browser-helpers.tsx +41 -1
  10. package/src/helpers/input-helpers.tsx +34 -4
  11. package/src/helpers/utility-helpers.tsx +1 -0
  12. package/src/helpers/validation-helpers.tsx +302 -0
  13. package/src/index.ts +3 -18
  14. package/src/interface.ts +4 -1
  15. package/src/interfaces/button-interfaces.tsx +2 -2
  16. package/src/interfaces/exception-interfaces.tsx +99 -0
  17. package/src/interfaces/http-interfaces.tsx +120 -3
  18. package/src/interfaces/input-interfaces.tsx +61 -11
  19. package/src/interfaces/message-interfaces.tsx +32 -0
  20. package/src/interfaces/permission-interfaces.tsx +4 -4
  21. package/src/interfaces/storage-interfaces.tsx +1 -0
  22. package/src/interfaces/system-interfaces.tsx +2 -2
  23. package/src/interfaces/type-interfaces.tsx +8 -2
  24. package/src/interfaces/user-interfaces.tsx +17 -0
  25. package/src/nexus.environments.tsx +27 -17
  26. package/src/proxy-api/proxy-backend.tsx +60 -0
  27. package/src/proxy-api/proxy-constants.tsx +28 -0
  28. package/src/proxy-api/proxy-helpers.tsx +91 -0
  29. package/src/proxy-api-client.tsx +1 -0
  30. package/src/proxy-api-server.tsx +2 -0
  31. package/src/services/http-client-services.tsx +75 -0
  32. package/src/services/http-services.tsx +158 -0
  33. package/src/services/loader-service.tsx +5 -4
  34. package/src/services/localstorage-service.tsx +59 -0
  35. package/src/services/message-services.tsx +383 -0
  36. package/src/sso-config/auth-token-validation.ts +20 -0
  37. package/src/sso-config/callback-route.tsx +34 -0
  38. package/src/sso-config/cookie-encryption.ts +51 -0
  39. package/src/sso-config/cookie-helpers.tsx +77 -0
  40. package/src/sso-config/forgot-password-route.tsx +36 -0
  41. package/src/sso-config/oauth-callback-state.ts +58 -0
  42. package/src/sso-config/pkce-helpers.ts +16 -0
  43. package/src/sso-config/provider-complete-route.tsx +15 -0
  44. package/src/sso-config/redirect-context-actions.tsx +46 -0
  45. package/src/sso-config/redirect-context-helpers.tsx +144 -0
  46. package/src/sso-config/redirect-context-persist.tsx +18 -0
  47. package/src/sso-config/redirect-context-route.tsx +45 -0
  48. package/src/sso-config/refresh-route.tsx +22 -0
  49. package/src/sso-config/sign-in-route.tsx +69 -0
  50. package/src/sso-config/sign-out-route.tsx +53 -0
  51. package/src/sso-config/sign-up-route.tsx +46 -0
  52. package/src/sso-config/sso-backend-flow.ts +45 -0
  53. package/src/sso-config/sso-complete-route.tsx +46 -0
  54. package/src/sso-config/sso-config.ts +57 -0
  55. package/src/sso-config/sso-interfaces.tsx +68 -0
  56. package/src/sso-config/sso-response-helpers.tsx +20 -0
  57. package/src/sso-server.tsx +21 -1
  58. package/src/styles/nexus.dialog.css +3 -0
@@ -1,6 +1,7 @@
1
1
  import { EButtonTypes, ETargets } from "./button-interfaces";
2
2
  import { DatasourceTypes } from "./datasource-interfaces";
3
3
  import { IColumn } from "./datatable-interfaces";
4
+ import { EActions, IApiInformations, IErrorRespose, ISuccessResponse } from "./http-interfaces";
4
5
  import { EIcons, EIconTypes } from "./icon-interfaces";
5
6
  import { EFlexPositions, EOpacity, ESizes } from "./layout-interfaces";
6
7
  import { EBackgrounds } from "./theme-interfaces";
@@ -11,11 +12,16 @@ export type OnElementLoad = (inputBox: HTMLElement | null, inputWrapper: HTMLEle
11
12
  export type AutoCompletes = "on" | "off";
12
13
  export type ForceChangeDefaultValue<T> = (defaultValue: T, type?: EValueChangeTypes, invokeOnChanged?: boolean | UN) => boolean;
13
14
  export type OnFormChangeHandler = (currentValue: any, isInitilizing: boolean, isForcedChanged: boolean, isDisposed: boolean, currentKey: string, index: number, type?: EValueChangeTypes, dataSource?: any | any[]) => void;
15
+ export type OnFormSubmitHandler = (currentValue: any, isValidForm: boolean, event: Event, form: IFormCreateResults, defaultValue: any | UN) => void;
16
+ export type OnApiFormSubmitHandler = (currentValue: any, apiResponse: ISuccessResponse | IErrorRespose, formResults: ISubmitFormCreateResults, defaultValue: any | UN) => void;
17
+ export type OverrideValidation = (currentValue: any, isValidForm: boolean, formReference: IFormCreateResults | null, action?: EActions | UN) => boolean;
18
+ export type RegexTypes = ERegexs | string[] | UN;
14
19
 
15
20
  export const DEFAULT_INPUT_BACKGROUND = EBackgrounds.Background1;
16
21
  export const MAX_INPUT_TYPE = 1900;
17
22
  export const DEFAULT_INPUT_BORDER_SIZE = 1;
18
23
  export const DEFAULT_TEXTAREA_ROWS = 3;
24
+ export const DEFAULT_ROW_GAP = 3;
19
25
 
20
26
  export const REQUIRED_TEXT1 = "✲";
21
27
 
@@ -28,18 +34,20 @@ export enum EValueChangeTypes {
28
34
  }
29
35
  export enum ERegexs {
30
36
  Fullname = "^[A-Za-z]+(?: [A-Za-z]+)*[0-9]?$",
31
- Username = "^[A-Za-z0-9]{7,30}$",
32
- ContactNumber = "^[0-9]{10}$",
33
- /** Allow Null */
34
- ContactNumber_NULL = "^$|^[0-9]{10}$",
35
37
  Email = "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$",
36
38
  /** Allow Null */
37
39
  Email_NULL = "^$|^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+.[a-zA-Z]{2,}$",
38
40
  EmailUsername = "^(?=.*[a-zA-Z0-9])[a-zA-Z0-9]+@[a-zA-Z0-9]+\\.[a-zA-Z]{2,}$|[a-zA-Z0-9]{8,100}$",
39
41
  Password = "^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*()_+{}|:,.<>?/~]).{8,30}$",
40
- EmailUsernameContact = "^(?:[A-Za-z]+[A-Za-z0-9]{2,30}|[0-9]{10}|[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,})$",
41
42
  Hexadecimal = "^[0-9A-Fa-f]{8}$",
42
43
  ContactEmail = "^(?:[0-9]{10}|[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,})$",
44
+
45
+ /** Allow Null */
46
+ ContactNumber_NULL = "^$|^[0-9]{10}$",
47
+
48
+ ContactNumber = "^[0-9]{10}$",
49
+ Username = "^(?!.*[._-]{2})[a-zA-Z0-9]+(?:[._-][a-zA-Z0-9]+)*$",
50
+ EmailUsernameMobile = "^(?:[0-9]{10}|(?!.*[._-]{2})[a-zA-Z0-9]+(?:[._-][a-zA-Z0-9]+)*|[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[A-Za-z]{2,})$",
43
51
  }
44
52
  export enum EInputTypes {
45
53
  Textbox = 1,
@@ -119,7 +127,7 @@ export interface ISubmitbutton extends IElement {
119
127
  }
120
128
  export interface ITextbox extends IInputParameters<string | null> {
121
129
  isPassword?: boolean | UN;
122
- regexKey?: ERegexs | UN;
130
+ regexKey?: RegexTypes | UN;
123
131
  }
124
132
  export interface ISlider extends IInputParameters<number | null> {
125
133
  step?: number | UN;
@@ -133,7 +141,7 @@ export interface IRadiobox extends ICheckbox {}
133
141
  export interface ITextarea extends IInputParameters<string | null> {
134
142
  isShowPreview?: boolean | UN;
135
143
  rows?: number | UN;
136
- regexKey?: ERegexs | UN;
144
+ regexKey?: RegexTypes | UN;
137
145
  }
138
146
  export interface INumber extends IInputParameters<number | null> {
139
147
  floatingPoint?: number | UN;
@@ -176,18 +184,60 @@ export interface IForm {
176
184
  onChanged: OnFormChangeHandler;
177
185
  index: number;
178
186
  isTabularForm: boolean;
179
- parentIdentifier: HTMLElement | UN | string;
180
187
  defaultValue?: any | UN;
181
188
  }
189
+ export interface ISubmitForm {
190
+ inputs: IInputRootParameters<any>[];
191
+ onChanged: OnFormChangeHandler | UN;
192
+ defaultValue?: DatasourceTypes | UN;
193
+ onSubmit: OnFormSubmitHandler;
194
+ onCancelClick?: ClickHandler | UN;
195
+ overrideValidation?: OverrideValidation | UN;
196
+ }
197
+
198
+ export interface IApiForm {
199
+ inputs: IInputRootParameters<any>[];
200
+ api: IApiInformations;
201
+ onApiSubmit: OnApiFormSubmitHandler;
202
+ onChanged: OnFormChangeHandler | UN;
203
+ onSubmit: OnFormSubmitHandler | UN;
204
+ defaultValue?: DatasourceTypes | UN;
205
+ onCancelClick?: ClickHandler | UN;
206
+ overrideValidation?: OverrideValidation | UN;
207
+ }
208
+
182
209
  export interface IFormCreateResults {
183
210
  inputResults: IInputCreateResults[];
184
211
  formElement: HTMLElement;
185
212
  dispose: (invokeOnChanged?: boolean) => void;
186
213
  forceChangeDefaultValue: ForceChangeDefaultValue<any> | null;
187
214
  }
215
+ export interface ISubmitFormCreateResults {
216
+ formElement: HTMLElement;
217
+ dispose: () => void;
218
+ formObject: IFormCreateResults;
219
+ }
188
220
 
189
221
  export const GetForms = {
190
- FORM: (inputs: IInputRootParameters<any>[], parentIdentifier: HTMLElement | string | UN, onChanged: OnFormChangeHandler, defaultValue?: any, index?: number, isTabularForm?: boolean): IForm => ({ inputs, onChanged, index: index ?? 0, isTabularForm: isTabularForm ?? false, parentIdentifier, defaultValue }),
222
+ FORM: (inputs: IInputRootParameters<any>[], onChanged: OnFormChangeHandler, defaultValue?: any | UN, index?: number | UN, isTabularForm?: boolean | UN): IForm => ({ inputs, onChanged, index: index ?? 0, isTabularForm: isTabularForm ?? false, defaultValue }),
223
+ SUBMITFORM: (inputs: IInputRootParameters<any>[], onSubmit: OnFormSubmitHandler, defaultValue?: DatasourceTypes | UN, overrideValidation?: OverrideValidation | UN, onChanged?: OnFormChangeHandler | UN, onCancelClick?: ClickHandler | UN): ISubmitForm => ({
224
+ inputs,
225
+ onChanged,
226
+ defaultValue,
227
+ onSubmit,
228
+ onCancelClick,
229
+ overrideValidation,
230
+ }),
231
+ APIFORM: (inputs: IInputRootParameters<any>[], api: IApiInformations, onApiSubmit: OnApiFormSubmitHandler, defaultValue?: DatasourceTypes | UN, onSubmit?: OnFormSubmitHandler | UN, overrideValidation?: OverrideValidation | UN, onCancelClick?: ClickHandler | UN, onChanged?: OnFormChangeHandler | UN): IApiForm => ({
232
+ inputs,
233
+ api,
234
+ onApiSubmit,
235
+ onChanged,
236
+ defaultValue,
237
+ onSubmit,
238
+ onCancelClick,
239
+ overrideValidation,
240
+ }),
191
241
  };
192
242
  export const GetInputs = {
193
243
  ICON: (icon: EIcons, iconSize?: ESizes | UN, boxSize?: ESizes | UN, opacity?: EOpacity | UN, type?: EIconTypes | UN, background?: EBackgrounds | UN, onClicked?: ClickHandler | UN, onMouseEnter?: ClickHandler | UN): IInputIcon => ({
@@ -200,7 +250,7 @@ export const GetInputs = {
200
250
  onClicked: onClicked,
201
251
  onMouseEnter: onMouseEnter,
202
252
  }),
203
- TEXTBOX: (id: string, columnSize: number, displayOrder: number, label?: string | UN, placeholder?: string | UN, validatorKey?: string | UN, minLength?: number | UN, maxLength?: number | UN, regexKey?: ERegexs | UN, isPassword?: boolean | UN, isReadonly?: boolean | UN, isAutoComplete?: boolean | UN, defaultValue?: string | UN, background?: EBackgrounds | UN, borderSize?: number | UN, inputSize?: ESizes | UN, icon?: IInputIcon | UN, defaultClass?: string | UN, onChanged?: OnInputChangeHandler<string | null> | UN): ITextbox => ({
253
+ TEXTBOX: (id: string, columnSize: number, displayOrder: number, label?: string | UN, placeholder?: string | UN, validatorKey?: string | UN, minLength?: number | UN, maxLength?: number | UN, regexKey?: RegexTypes | UN, isPassword?: boolean | UN, isReadonly?: boolean | UN, isAutoComplete?: boolean | UN, defaultValue?: string | UN, background?: EBackgrounds | UN, borderSize?: number | UN, inputSize?: ESizes | UN, icon?: IInputIcon | UN, defaultClass?: string | UN, onChanged?: OnInputChangeHandler<string | null> | UN): ITextbox => ({
204
254
  id: id,
205
255
  columnSize: columnSize,
206
256
  displayOrder: displayOrder,
@@ -223,7 +273,7 @@ export const GetInputs = {
223
273
  inputType: EInputTypes.Textbox,
224
274
  autoComplete: isAutoComplete !== false ? undefined : "off",
225
275
  }),
226
- TEXTAREA: (id: string, columnSize: number, displayOrder: number, label?: string | UN, placeholder?: string | UN, validatorKey?: string | UN, minLength?: number | UN, maxLength?: number | UN, rows?: number | UN, isShowPreview?: boolean | UN, defaultValue?: string | UN, regexKey?: ERegexs | UN, isReadonly?: boolean | UN, isAutoComplete?: boolean | UN, background?: EBackgrounds | UN, borderSize?: number | UN, inputSize?: ESizes | UN, icon?: IInputIcon | UN, defaultClass?: string | UN, onChanged?: OnInputChangeHandler<string | null> | UN): ITextarea => ({
276
+ TEXTAREA: (id: string, columnSize: number, displayOrder: number, label?: string | UN, placeholder?: string | UN, validatorKey?: string | UN, minLength?: number | UN, maxLength?: number | UN, rows?: number | UN, isShowPreview?: boolean | UN, defaultValue?: string | UN, regexKey?: RegexTypes | UN, isReadonly?: boolean | UN, isAutoComplete?: boolean | UN, background?: EBackgrounds | UN, borderSize?: number | UN, inputSize?: ESizes | UN, icon?: IInputIcon | UN, defaultClass?: string | UN, onChanged?: OnInputChangeHandler<string | null> | UN): ITextarea => ({
227
277
  id: id,
228
278
  columnSize: columnSize,
229
279
  displayOrder: displayOrder,
@@ -0,0 +1,32 @@
1
+ import { EIcons } from "./icon-interfaces";
2
+ import { EBackgrounds } from "./theme-interfaces";
3
+
4
+ export enum EMessageTypes {
5
+ Information = EBackgrounds.InfoColor,
6
+ Success = EBackgrounds.SuccessColor,
7
+ Warning = EBackgrounds.WarningColor,
8
+ Error = EBackgrounds.ErrorColor,
9
+ }
10
+ export enum EMessageIcons {
11
+ Information = EIcons.INFORMATION,
12
+ Success = EIcons.CHECKMARK_DOUBLE,
13
+ Warning = EIcons.WARNING,
14
+ Error = EIcons.CLOSE,
15
+ Question = EIcons.HELP,
16
+ Delete = EIcons.TRASH,
17
+ Bin = EIcons.TRASH_BIN,
18
+ }
19
+ export enum EMessageResult {
20
+ OK = "OK",
21
+ Yes = "Yes",
22
+ No = "No",
23
+ Cancel = "Cancel",
24
+ Undefined = "Undefined",
25
+ }
26
+
27
+ export enum EMessageBoxButtons {
28
+ OK = "OK",
29
+ YES_NO = "Yes_No",
30
+ YES_CANCEL = "Yes_Cancel",
31
+ YES_NO_CANCEL = "Yes_No_Cancel",
32
+ }
@@ -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
  }
@@ -1,3 +1,4 @@
1
1
  export const LOCAL_STORAGE_KEYS = {
2
2
  ACTIVE_THEME: "nexus-themes",
3
+ FORM_BUTTON_POSITION: "nexus-form-button-position",
3
4
  };
@@ -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
@@ -15,8 +15,14 @@ export type ObjectCallbackHandler<T> = CallbackHandler<T, T>;
15
15
  export type ObjectNullCallbackHandler<T> = CallbackHandler<T | UN, T | UN>;
16
16
  export type ArrayCallbackHandler<T> = CallbackHandler<T[], T[]>;
17
17
  export type ArrayNullCallbackHandler<T> = CallbackHandler<T[] | UN, T[] | UN>;
18
- export type VoidCallbackHandler = CallbackHandler<void, void>;
19
-
18
+ export type VoidCallbackHandler = CallbackHandler<void | any | any[] | UN, void>;
19
+ export type PopupMessageCallback = (type: ECallbackTypes) => void;
20
20
  export interface IInterface {
21
21
  // Blank Interface
22
22
  }
23
+
24
+ export enum ECallbackTypes {
25
+ Initialize = 0,
26
+ Destroy = 1,
27
+ ForceChangeValue = 2,
28
+ }
@@ -0,0 +1,17 @@
1
+ import { EModules } from "./system-interfaces";
2
+
3
+ export enum EUserRoles {
4
+ Guest = 0,
5
+ User = 1,
6
+ Admin = 2,
7
+ SuperAdmin = 3,
8
+ }
9
+
10
+ export interface IUserValidationResponse {
11
+ isValidUser: boolean;
12
+ isValidCompany: boolean;
13
+ isValidBranch: boolean;
14
+ isValidFiscalYear: boolean;
15
+ userRole: EUserRoles;
16
+ assignedModules: EModules;
17
+ }
@@ -1,19 +1,27 @@
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,
19
+ ALLOWED_ORIGINS: (process.env.ALLOWED_ORIGINS ?? "")
20
+ .split(",")
21
+ .map((origin) => origin.trim())
22
+ .filter(Boolean),
15
23
  };
16
- const FILE = (apiName: string, filePath: string, fileName: string) => `${APIS.DRIVE}/Resources/${apiName}/${encodeURIComponent(filePath)}/${encodeURIComponent(fileName)}`;
24
+ const FILE = (apiName: string, filePath: string, fileName: string) => `${process.env.API_RESOURCES_BASE_PATH ?? `/api/app/${EModules.Drive}/Resources`}/${apiName}/${encodeURIComponent(filePath)}/${encodeURIComponent(fileName)}`;
17
25
  export const FILES = {
18
26
  LOGO: {
19
27
  ICON: FILE("Image", "logos", "icon-logo"),
@@ -23,15 +31,17 @@ export const FILES = {
23
31
  SCRIPT: (fileName: string, isStyles: boolean = false) => FILE("Script", isStyles ? "css" : "js", fileName),
24
32
  };
25
33
  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,
34
+ IS_ADMIN_MODULE: process.env.IS_ADMIN_MODULE !== "false",
35
+ CURRENT_MODULE: process.env.CURRENT_MODULE !== undefined ? (parseInt(process.env.CURRENT_MODULE) as EModules) : undefined,
36
+ CURRENT_MODULE_NAME: process.env.CURRENT_MODULE_NAME as string,
37
+ CURRENT_MODULE_SHORT_NAME: process.env.CURRENT_MODULE_SHORT_NAME as string,
29
38
  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,
39
+ SOFTWARE_COMPANY_SHORT_NAME: process.env.SOFTWARE_COMPANY_SHORT_NAME as string,
40
+ SOFTWARE_COMPANY_FULL_NAME: process.env.SOFTWARE_COMPANY_FULL_NAME as string,
41
+ SOFTWARE_VERSION: process.env.SOFTWARE_VERSION as string,
42
+ SOFTWARE_LAST_UPDATE_DATE: process.env.SOFTWARE_LAST_UPDATE_DATE as string,
43
+ SOFTWARE_COMPANY_WEBSITE: process.env.SOFTWARE_COMPANY_WEBSITE as string,
44
+ IS_PROXY_SERVER: process.env.IS_PROXY_SERVER !== "false",
35
45
  };
36
46
  export const NEXUS_CONFIG = {
37
47
  /** Default revalidate time for static generation (in minutes) */
@@ -0,0 +1,60 @@
1
+ import { NextRequest, NextResponse } from "next/server";
2
+ import { EErrorCodes } from "../interfaces/exception-interfaces";
3
+ import { GetRootApi } from "../services/http-services";
4
+ import { DecryptCookieValue } from "../sso-config/cookie-encryption";
5
+ import { ALLOWED_MODULES, PROXY_ALLOWED_METHODS } from "./proxy-constants";
6
+ import { BuildBackendUrl, BuildForwardHeaders, ProxyErrorResponse, ReadRequestBody, SanitizeResponseHeaders } from "./proxy-helpers";
7
+
8
+ const GENERIC_PROXY_ERROR = "Unable to reach the backend service.";
9
+ const MODULE_NOT_ALLOWED_ERROR = "This module is not allowed for proxy requests.";
10
+
11
+ const ACCESS_TOKEN_COOKIE = "access_token";
12
+
13
+ export async function ProxyToBackend(req: NextRequest, moduleParam: string, pathSegments: string[]): Promise<NextResponse> {
14
+ const method = req.method.toUpperCase();
15
+
16
+ if (!PROXY_ALLOWED_METHODS.has(method)) {
17
+ return ProxyErrorResponse(`Method ${method} is not supported.`, 405, "METHOD_NOT_SUPPORTED", EErrorCodes.Forbidden);
18
+ }
19
+
20
+ const module = ALLOWED_MODULES.includes(moduleParam) ? parseInt(moduleParam) : null;
21
+ if (!module) return ProxyErrorResponse(MODULE_NOT_ALLOWED_ERROR, 400, "INVALID_MODULE");
22
+ const backendBase = GetRootApi(module);
23
+ const backendUrl = BuildBackendUrl(backendBase, pathSegments, req.nextUrl.searchParams);
24
+
25
+ return ProxyToBackendRequest(backendUrl, backendBase, module, method, req);
26
+ }
27
+
28
+ export async function ProxyToBackendRequest(backendFullUrl: string, backendUrlBase: string, module: number, method: string, req: NextRequest): Promise<NextResponse> {
29
+ // BuildForwardHeaders already strips the Cookie header (see PROXY_REQUEST_HEADERS_TO_SKIP).
30
+ const headers = BuildForwardHeaders(req);
31
+
32
+ // Decrypt the access token from the httpOnly cookie and inject it as a Bearer token.
33
+ // The raw encrypted blob is never forwarded — only the plaintext token reaches the backend.
34
+ const encryptedToken = req.cookies.get(ACCESS_TOKEN_COOKIE)?.value;
35
+ if (encryptedToken) {
36
+ const token = DecryptCookieValue(encryptedToken);
37
+ if (token) headers.set("Authorization", `Bearer ${token}`);
38
+ }
39
+
40
+ const body = await ReadRequestBody(req, method);
41
+
42
+ let backendResponse: Response;
43
+ try {
44
+ backendResponse = await fetch(backendFullUrl, {
45
+ method,
46
+ headers,
47
+ body,
48
+ redirect: "manual",
49
+ });
50
+ } catch {
51
+ return ProxyErrorResponse(GENERIC_PROXY_ERROR, 502, "BACKEND_UNREACHABLE");
52
+ }
53
+ const responseHeaders = SanitizeResponseHeaders(backendResponse.headers, backendUrlBase, module);
54
+ const responseBody = await backendResponse.arrayBuffer();
55
+
56
+ return new NextResponse(responseBody, {
57
+ status: backendResponse.status,
58
+ headers: responseHeaders,
59
+ });
60
+ }
@@ -0,0 +1,28 @@
1
+ import { EModules } from "../interfaces/system-interfaces";
2
+
3
+
4
+ /** HTTP methods supported by the backend proxy. */
5
+ export const PROXY_ALLOWED_METHODS = new Set(["GET", "POST", "PUT", "DELETE", "PATCH"]);
6
+
7
+ /** Request headers that must not be forwarded to the backend. */
8
+ export const PROXY_REQUEST_HEADERS_TO_SKIP = new Set(["host", "connection", "content-length", "transfer-encoding", "keep-alive", "upgrade", "cookie"]);
9
+
10
+ /** Response headers that must not be forwarded to the browser. */
11
+ export const PROXY_RESPONSE_HEADERS_TO_SKIP = new Set(["connection", "transfer-encoding", "keep-alive", "proxy-authenticate", "proxy-authorization", "te", "trailer", "upgrade"]);
12
+
13
+ /** Public path prefix the browser uses to reach the proxy. */
14
+
15
+ /** Allowed modules for proxy requests. */
16
+
17
+ export const ALLOWED_MODULES = [
18
+ EModules.Authentication.toString(),
19
+ EModules.Configuration.toString(),
20
+ EModules.Enterprise.toString(),
21
+ EModules.Drive.toString(),
22
+ EModules.Message.toString(),
23
+ EModules.Accounting.toString(),
24
+ EModules.Report.toString(),
25
+ EModules.Portfolio.toString(),
26
+ EModules.Subscription.toString(),
27
+ EModules.Reservation.toString(),
28
+ ]
@@ -0,0 +1,91 @@
1
+ import { NextRequest, NextResponse } from "next/server";
2
+ import { EErrorCodes, EExceptionCategories } from "../interfaces/exception-interfaces";
3
+ import { GetResponse, PROXY_PUBLIC_BASE_PATH } from "../interfaces/http-interfaces";
4
+ import { EModules } from "../interfaces/system-interfaces";
5
+ import { PROXY_REQUEST_HEADERS_TO_SKIP, PROXY_RESPONSE_HEADERS_TO_SKIP } from "./proxy-constants";
6
+
7
+ const BACKEND_NOT_CONFIGURED_ERROR = "Backend API is not configured.";
8
+
9
+ export function BuildBackendUrl(backendBase: string, pathSegments: string[], searchParams: URLSearchParams): string {
10
+ if (pathSegments.some((segment) => segment === ".." || segment === "." || segment.includes("\\"))) throw new Error("Invalid path.");
11
+ else if (!backendBase) throw new Error(BACKEND_NOT_CONFIGURED_ERROR);
12
+
13
+ const path = pathSegments.map(encodeURIComponent).join("/");
14
+ const query = searchParams.toString();
15
+
16
+ return query ? `${backendBase}/${path}?${query}` : `${backendBase}/${path}`;
17
+ }
18
+
19
+ export function BuildForwardHeaders(req: NextRequest): Headers {
20
+ const headers = new Headers();
21
+
22
+ req.headers.forEach((value, key) => {
23
+ if (PROXY_REQUEST_HEADERS_TO_SKIP.has(key.toLowerCase())) {
24
+ return;
25
+ }
26
+
27
+ headers.set(key, value);
28
+ });
29
+
30
+ return headers;
31
+ }
32
+
33
+ export async function ReadRequestBody(req: NextRequest, method: string): Promise<BodyInit | undefined> {
34
+ if (method === "GET" || method === "HEAD" || method === "OPTIONS") {
35
+ return undefined;
36
+ }
37
+
38
+ return await req.arrayBuffer();
39
+ }
40
+
41
+ export function ProxyErrorResponse(message: string, status: number, messageCode = "PROXY_ERROR", errorCode: EErrorCodes = EErrorCodes.Unknown): NextResponse {
42
+ return NextResponse.json(GetResponse.ERROR(message, messageCode, EExceptionCategories.Application, errorCode, undefined, null, status), { status });
43
+ }
44
+ function stripBackendBasePath(pathname: string, backendBaseUrl: string): string {
45
+ try {
46
+ const basePath = new URL(backendBaseUrl).pathname.replace(/\/+$/, "");
47
+ if (pathname.startsWith(basePath)) {
48
+ return pathname.slice(basePath.length).replace(/^\/+/, "");
49
+ }
50
+ } catch {
51
+ // Fall through to pathname cleanup below.
52
+ }
53
+
54
+ return pathname.replace(/^\/+/, "");
55
+ }
56
+ export function SanitizeResponseHeaders(headers: Headers, backendBaseUrl: string, module: EModules): Headers {
57
+ const sanitized = new Headers();
58
+ let backendHost: string | undefined;
59
+
60
+ try {
61
+ backendHost = new URL(backendBaseUrl).host;
62
+ } catch {
63
+ backendHost = undefined;
64
+ }
65
+
66
+ headers.forEach((value, key) => {
67
+ const lowerKey = key.toLowerCase();
68
+
69
+ if (PROXY_RESPONSE_HEADERS_TO_SKIP.has(lowerKey)) {
70
+ return;
71
+ }
72
+
73
+ if (lowerKey === "location" && backendHost) {
74
+ try {
75
+ const location = new URL(value, backendBaseUrl);
76
+ if (location.host === backendHost) {
77
+ const relativePath = stripBackendBasePath(location.pathname, backendBaseUrl);
78
+ const proxyPath = `${PROXY_PUBLIC_BASE_PATH}/${module}/${relativePath}${location.search}`;
79
+ sanitized.set(key, proxyPath);
80
+ return;
81
+ }
82
+ } catch {
83
+ // Relative locations are forwarded as-is.
84
+ }
85
+ }
86
+
87
+ sanitized.set(key, value);
88
+ });
89
+
90
+ return sanitized;
91
+ }
@@ -0,0 +1 @@
1
+ export * from "./proxy-api/proxy-constants";
@@ -0,0 +1,2 @@
1
+ export * from "./proxy-api/proxy-backend";
2
+ export * from "./proxy-api/proxy-constants";
@@ -0,0 +1,75 @@
1
+ "use client";
2
+
3
+ /**
4
+ * Client-side HTTP utilities for `"use client"` pages and components.
5
+ * Adds UI-level response handling (loader/toast hooks). For Route Handlers and
6
+ * Server Components use `http-services` (`HttpPost`, `HttpGet`, …) from `nexus-shared` instead.
7
+ */
8
+ import { DatasourceTypes, EBody, GetApiInformations, GetResponse, IApiInformations, IErrorRespose, IInterface, ISuccessResponse, UN } from "../interface";
9
+ import { HttpDelete, HttpGet, HttpPost, HttpPut } from "./http-services";
10
+ import { ErrorMessage, SuccessMessage } from "./message-services";
11
+
12
+ /** // if same url is called multiple times, it will only send one request and return the same promise
13
+ // this is to prevent multiple requests to the same url at the same time
14
+ // this is useful for example when user is typing in a search box and multiple requests are sent to the server
15
+ // this will prevent multiple requests to the same url and return the same promise
16
+ // 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
17
+ // If country Dropdown is called multiple times, it will only send one request and return the same promise
18
+ // this is useful for example when user is typing in a search box and multiple requests are sent to the server
19
+ */
20
+ const pendingRequests: { [url: string]: Promise<any> | undefined } = {};
21
+ export const Get = async (apiUrl: string, successMessage?: boolean | UN, errorMessage?: boolean | UN): Promise<ISuccessResponse | IErrorRespose> => {
22
+ if (pendingRequests[apiUrl] !== undefined) return pendingRequests[apiUrl]!;
23
+
24
+ pendingRequests[apiUrl] = HttpGet(apiUrl)
25
+ .then((response) => handleResponse(response, successMessage ?? false, errorMessage ?? true))
26
+ .finally(() => {
27
+ delete pendingRequests[apiUrl];
28
+ });
29
+ return pendingRequests[apiUrl]!;
30
+ };
31
+
32
+ export const Put = async (apiUrl: string, body?: any | UN, bodyType?: EBody | UN, successMessage?: boolean | UN, errorMessage?: boolean | UN) => {
33
+ return await handleResponse(await HttpPut(apiUrl, body, bodyType, null, 0, null), successMessage ?? true, errorMessage ?? true);
34
+ };
35
+
36
+ export const Post = async (apiUrl: string, body?: any | UN, bodyType?: EBody | UN, successMessage?: boolean | UN, errorMessage?: boolean | UN) => {
37
+ const isFindListing = Array.isArray(body) && apiUrl.endsWith("/FindListing");
38
+
39
+ if (!isFindListing) {
40
+ return await handleResponse(await HttpPost(apiUrl, body, bodyType, null, 0, null), successMessage ?? true, errorMessage ?? true);
41
+ } else {
42
+ const apiKey = `${apiUrl}__${(body as string[]).sort().join(",")}`;
43
+
44
+ if (pendingRequests[apiKey]) return pendingRequests[apiKey];
45
+
46
+ pendingRequests[apiKey] = HttpPost(apiUrl, body, bodyType, null, 0, null)
47
+ .then((response) => handleResponse(response, successMessage ?? true, errorMessage ?? true))
48
+ .finally(() => {
49
+ delete pendingRequests[apiKey];
50
+ });
51
+ return pendingRequests[apiKey];
52
+ }
53
+ };
54
+
55
+ export const Delete = async (apiUrl: string, successMessage?: boolean | UN, errorMessage?: boolean | UN) => {
56
+ return await handleResponse(await HttpDelete(apiUrl, null, 0, null), successMessage ?? true, errorMessage ?? true);
57
+ };
58
+ export const SubmitApi = async (api: DatasourceTypes | UN, parameters: IInterface | IInterface[] | UN, successMessage?: boolean | UN, errorMessage?: boolean | UN): Promise<IErrorRespose | ISuccessResponse> => {
59
+ if (!api || Array.isArray(api) || !GetApiInformations.IS_API(api)) return Promise.resolve(GetResponse.SUCCESS(api));
60
+ const apiInfo = api as IApiInformations;
61
+
62
+ const url = GetApiInformations.BUILD_API(apiInfo, parameters);
63
+
64
+ // TODO:
65
+ return Post(url, parameters, null, successMessage, errorMessage);
66
+ };
67
+
68
+ const handleResponse = async (result: ISuccessResponse | IErrorRespose, successMessage?: boolean, errorMessage?: boolean): Promise<ISuccessResponse | IErrorRespose> => {
69
+ if (result.isSuccess && !(result as IErrorRespose).exception) {
70
+ if (successMessage) SuccessMessage("Request Success!", result.message ?? "Success");
71
+ return result;
72
+ }
73
+ if (errorMessage) ErrorMessage("Request Failed!", result.message ?? "Failed");
74
+ return result;
75
+ };